[Fontations-backend] Color bitmap support.
Retrieve, place, scale and draw CBDT/CBLC and SBIX bitmap PNG glyphs.
Add ffi code to specifically handle format differences for SBIX and CBDT
and navigate the respective read_fonts retrieval functions.
Tested locally to closely match FreeType output with a modified
gm/fontations_ft_compare.cpp test.
When [1] lands, tested through various gm's that load CBDT and SBIX
fonts, for example gm/coloremoji.cpp, scaledemoji.cpp and
scaledemoji_rendering.cpp.
[1] https://skia-review.googlesource.com/c/skia/+/820216
Bug: skia:301560994
Cq-Include-Trybots: luci.skia.skia.primary:Build-Debian10-Clang-x86_64-Debug-Fontations,Build-Mac-Clang-x86_64-Debug-Fontations,Test-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Fontations,Test-Mac12-Clang-MacBookPro16.2-CPU-AppleIntel-x86_64-Debug-All-Fontations
Change-Id: Ic2966555e8fb787c057d6c72c5beb8313b5c1203
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/809056
Reviewed-by: Ben Wagner <bungeman@google.com>
Reviewed-by: Dominik Röttsches <drott@google.com>
Commit-Queue: Dominik Röttsches <drott@google.com>
diff --git a/gm/colrv1.cpp b/gm/colrv1.cpp
index e9c680c..fb1e229 100644
--- a/gm/colrv1.cpp
+++ b/gm/colrv1.cpp
@@ -23,8 +23,9 @@
#include "tools/ToolUtils.h"
#include "tools/fonts/FontToolUtils.h"
+#if defined(SK_TYPEFACE_FACTORY_FONTATIONS)
#include "include/ports/SkTypeface_fontations.h"
-// #include "include/ports/SkFontMgr_empty.h"
+#endif
#include <string.h>
#include <initializer_list>
diff --git a/gm/fontations.cpp b/gm/fontations.cpp
index 35784e3..4d59667 100644
--- a/gm/fontations.cpp
+++ b/gm/fontations.cpp
@@ -15,6 +15,7 @@
#include "include/ports/SkTypeface_fontations.h"
#include "tools/Resources.h"
+
namespace skiagm {
namespace {
diff --git a/src/ports/SkTypeface_fontations.cpp b/src/ports/SkTypeface_fontations.cpp
index 594cb2e..1db3c3e 100644
--- a/src/ports/SkTypeface_fontations.cpp
+++ b/src/ports/SkTypeface_fontations.cpp
@@ -6,10 +6,13 @@
*/
#include "include/ports/SkTypeface_fontations.h"
+#include "include/codec/SkCodec.h"
+#include "include/codec/SkPngDecoder.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkData.h"
#include "include/core/SkFontMetrics.h"
+#include "include/core/SkImage.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkStream.h"
#include "include/effects/SkGradientShader.h"
@@ -294,6 +297,7 @@
static const constexpr value_type PATH = 1;
static const constexpr value_type COLRv0 = 2;
static const constexpr value_type COLRv1 = 3;
+ static const constexpr value_type BITMAP = 4;
};
GlyphMetrics generateMetrics(const SkGlyph& glyph, SkArenaAlloc*) override {
@@ -322,6 +326,8 @@
fontations_ffi::has_colrv1_glyph(fBridgeFontRef, glyph.getGlyphID());
bool has_colrv0_glyph =
fontations_ffi::has_colrv0_glyph(fBridgeFontRef, glyph.getGlyphID());
+ bool has_bitmap_glyph =
+ fontations_ffi::has_bitmap_glyph(fBridgeFontRef, glyph.getGlyphID());
if (has_colrv1_glyph || has_colrv0_glyph) {
mx.extraBits = has_colrv1_glyph ? ScalerContextBits::COLRv1 : ScalerContextBits::COLRv0;
@@ -367,6 +373,44 @@
}
}
}
+ } else if (has_bitmap_glyph) {
+ mx.maskFormat = SkMask::kARGB32_Format;
+ mx.neverRequestPath = true;
+ mx.extraBits = ScalerContextBits::BITMAP;
+
+ rust::cxxbridge1::Box<fontations_ffi::BridgeBitmapGlyph> bitmap_glyph =
+ fontations_ffi::bitmap_glyph(fBridgeFontRef, glyph.getGlyphID(), scale.fY);
+ rust::cxxbridge1::Slice<const uint8_t> png_data =
+ fontations_ffi::png_data(*bitmap_glyph);
+ SkASSERT(png_data.size());
+
+ const fontations_ffi::BitmapMetrics bitmap_metrics =
+ fontations_ffi::bitmap_metrics(*bitmap_glyph);
+
+ const bool& placement_origin_bottom_left = bitmap_metrics.placement_origin_bottom_left;
+
+ std::unique_ptr<SkCodec> codec = SkPngDecoder::Decode(
+ SkData::MakeWithoutCopy(png_data.data(), png_data.size()), nullptr);
+ if (!codec) {
+ return mx;
+ }
+
+ SkRect bounds = SkRect::MakeEmpty();
+ SkImageInfo info = codec->getInfo();
+ float height_adjustment = placement_origin_bottom_left ? info.height() : 0;
+
+ bounds = SkRect::Make(info.bounds());
+ SkMatrix matrix = remainingMatrix;
+ SkScalar ratio = scale.fY / bitmap_metrics.ppem_y;
+ matrix.preScale(ratio, ratio);
+ matrix.preTranslate(bitmap_metrics.bearing_x,
+ -bitmap_metrics.bearing_y - height_adjustment);
+ if (this->isSubpixel()) {
+ matrix.postTranslate(SkFixedToScalar(glyph.getSubXFixed()),
+ SkFixedToScalar(glyph.getSubYFixed()));
+ }
+ matrix.mapRect(&bounds);
+ mx.bounds = SkRect::Make(bounds.roundOut());
} else {
// TODO: Retrieve from read_fonts and Skrifa - TrueType bbox or from path with
// hinting?
@@ -376,6 +420,63 @@
return mx;
}
+ void generatePngImage(const SkGlyph& glyph, void* imageBuffer) {
+ SkASSERT(glyph.maskFormat() == SkMask::kARGB32_Format);
+ SkBitmap dstBitmap;
+ dstBitmap.setInfo(
+ SkImageInfo::Make(
+ glyph.width(), glyph.height(), kN32_SkColorType, kPremul_SkAlphaType),
+ glyph.rowBytes());
+ dstBitmap.setPixels(imageBuffer);
+
+ SkCanvas canvas(dstBitmap);
+
+ canvas.translate(-glyph.left(), -glyph.top());
+
+ SkVector scale;
+ SkMatrix remainingMatrix;
+ if (!fRec.computeMatrices(
+ SkScalerContextRec::PreMatrixScale::kVertical, &scale, &remainingMatrix)) {
+ return;
+ }
+
+ rust::cxxbridge1::Box<fontations_ffi::BridgeBitmapGlyph> bitmap_glyph =
+ fontations_ffi::bitmap_glyph(fBridgeFontRef, glyph.getGlyphID(), scale.fY);
+ rust::cxxbridge1::Slice<const uint8_t> png_data = fontations_ffi::png_data(*bitmap_glyph);
+ SkASSERT(png_data.size());
+
+ std::unique_ptr<SkCodec> codec = SkPngDecoder::Decode(
+ SkData::MakeWithoutCopy(png_data.data(), png_data.size()), nullptr);
+
+ if (!codec) {
+ return;
+ }
+
+ auto [glyph_image, result] = codec->getImage();
+ if (result != SkCodec::Result::kSuccess) {
+ return;
+ }
+
+ canvas.clear(SK_ColorTRANSPARENT);
+ canvas.concat(remainingMatrix);
+
+ if (this->isSubpixel()) {
+ canvas.translate(SkFixedToScalar(glyph.getSubXFixed()),
+ SkFixedToScalar(glyph.getSubYFixed()));
+ }
+ const fontations_ffi::BitmapMetrics bitmapMetrics =
+ fontations_ffi::bitmap_metrics(*bitmap_glyph);
+ SkScalar ratio = scale.fY / bitmapMetrics.ppem_y;
+ canvas.scale(ratio, ratio);
+
+ float height_adjustment =
+ bitmapMetrics.placement_origin_bottom_left ? glyph_image->height() : 0;
+ canvas.translate(bitmapMetrics.bearing_x, -bitmapMetrics.bearing_y - height_adjustment);
+
+ SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNearest);
+ canvas.drawImage(glyph_image, 0, 0, sampling);
+ }
+
void generateImage(const SkGlyph& glyph, void* imageBuffer) override {
ScalerContextBits::value_type format = glyph.extraBits();
if (format == ScalerContextBits::PATH) {
@@ -410,6 +511,8 @@
canvas.translate(-glyph.left(), -glyph.top());
drawCOLRGlyph(glyph, fRec.fForegroundColor, &canvas);
+ } else if (format == ScalerContextBits::BITMAP) {
+ generatePngImage(glyph, imageBuffer);
} else {
SK_ABORT("Bad format");
}
@@ -481,6 +584,9 @@
}
};
ScalerContextBits::value_type format = glyph.extraBits();
+ if (format == ScalerContextBits::BITMAP) {
+ return nullptr;
+ }
SkASSERT(format == ScalerContextBits::COLRv1 || format == ScalerContextBits::COLRv0);
return sk_sp<SkDrawable>(new ColrGlyphDrawable(this, glyph));
}
diff --git a/src/ports/fontations/BUILD.bazel b/src/ports/fontations/BUILD.bazel
index f28985d..82b9bdd 100644
--- a/src/ports/fontations/BUILD.bazel
+++ b/src/ports/fontations/BUILD.bazel
@@ -42,6 +42,8 @@
":bridge_rust_side",
":fontations_ffi",
":path_bridge_include",
+ # For color bitmap fonts.
+ "//src/codec:png_decode",
],
)
diff --git a/src/ports/fontations/src/ffi.rs b/src/ports/fontations/src/ffi.rs
index c0fdd87..880deeb 100644
--- a/src/ports/fontations/src/ffi.rs
+++ b/src/ports/fontations/src/ffi.rs
@@ -5,6 +5,7 @@
use font_types::{BoundingBox, GlyphId, Pen};
use read_fonts::{tables::colr::CompositeMode, FileRef, FontRef, ReadError, TableProvider};
use skrifa::{
+ attribute::Style,
color::{Brush, ColorGlyphFormat, ColorPainter, Transform},
instance::{Location, Size},
metrics::{GlyphMetrics, Metrics},
@@ -15,11 +16,11 @@
};
use std::pin::Pin;
-use skrifa::attribute::{Style};
+use crate::bitmap::{bitmap_glyph, bitmap_metrics, has_bitmap_glyph, png_data, BridgeBitmapGlyph};
use crate::ffi::{
AxisWrapper, BridgeScalerMetrics, ColorPainterWrapper, ColorStop, PaletteOverride, PathWrapper,
- SkiaDesignCoordinate,
+ SkiaDesignCoordinate,BridgeFontStyle
};
fn lookup_glyph_or_zero(font_ref: &BridgeFontRef, codepoint: u32) -> u16 {
@@ -715,8 +716,6 @@
return color_stops.num_stops;
}
-use crate::ffi::BridgeFontStyle;
-
fn get_font_style(font_ref: &BridgeFontRef, style: &mut BridgeFontStyle) -> bool {
font_ref
.with_font(|f| {
@@ -751,7 +750,7 @@
.unwrap_or_default()
}
-struct BridgeFontRef<'a>(Option<FontRef<'a>>);
+pub struct BridgeFontRef<'a>(Option<FontRef<'a>>);
impl<'a> BridgeFontRef<'a> {
fn with_font<T>(&'a self, f: impl FnOnce(&'a FontRef) -> Option<T>) -> Option<T> {
@@ -778,6 +777,194 @@
pub num_stops: usize,
}
+mod bitmap {
+
+ use read_fonts::{
+ tables::{
+ bitmap::{BitmapContent, BitmapData, BitmapDataFormat, BitmapMetrics, BitmapSize},
+ sbix::{GlyphData, Strike},
+ },
+ FontRef, TableProvider,
+ };
+
+ use font_types::GlyphId;
+
+ use crate::{ffi::BitmapMetrics as FfiBitmapMetrics, BridgeFontRef};
+
+ pub enum BitmapPixelData<'a> {
+ PngData(&'a [u8]),
+ }
+
+ struct CblcGlyph<'a> {
+ bitmap_data: BitmapData<'a>,
+ ppem_x: u8,
+ ppem_y: u8,
+ }
+
+ struct SbixGlyph<'a> {
+ glyph_data: GlyphData<'a>,
+ ppem: u16,
+ }
+
+ #[derive(Default)]
+ pub struct BridgeBitmapGlyph<'a> {
+ pub data: Option<BitmapPixelData<'a>>,
+ pub metrics: FfiBitmapMetrics,
+ }
+
+ trait StrikeSizeRetrievable {
+ fn strike_size(&self) -> f32;
+ }
+
+ impl StrikeSizeRetrievable for &BitmapSize {
+ fn strike_size(&self) -> f32 {
+ self.ppem_y() as f32
+ }
+ }
+
+ impl StrikeSizeRetrievable for Strike<'_> {
+ fn strike_size(&self) -> f32 {
+ self.ppem() as f32
+ }
+ }
+
+ // Find the nearest larger strike size, or if no larger one is available, the nearest smaller.
+ fn best_strike_size<T>(strikes: impl Iterator<Item = T>, font_size: f32) -> Option<T>
+ where
+ T: StrikeSizeRetrievable,
+ {
+ // After a bigger strike size is found, the order of strike sizes smaller
+ // than the requested font size does not matter anymore. A new strike size
+ // is only an improvement if it gets closer to the requested font size (and
+ // is smaller than the current best, but bigger than font size). And vice
+ // versa: As long as we have found only smaller ones so far, only any strike
+ // size matters that is bigger than the current best.
+ strikes.reduce(|best, entry| {
+ let entry_size = entry.strike_size();
+ if (entry_size >= font_size && entry_size < best.strike_size())
+ || (best.strike_size() < font_size && entry_size > best.strike_size())
+ {
+ entry
+ } else {
+ best
+ }
+ })
+ }
+
+ fn sbix_glyph<'a>(
+ font_ref: &'a FontRef,
+ glyph_id: GlyphId,
+ font_size: Option<f32>,
+ ) -> Option<SbixGlyph<'a>> {
+ let sbix = font_ref.sbix().ok()?;
+ let mut strikes = sbix.strikes().iter().filter_map(|strike| strike.ok());
+
+ let best_strike = match font_size {
+ Some(size) => best_strike_size(strikes, size),
+ _ => strikes.next(),
+ }?;
+
+ Some(SbixGlyph {
+ ppem: best_strike.ppem(),
+ glyph_data: best_strike.glyph_data(glyph_id).ok()??,
+ })
+ }
+
+ fn cblc_glyph<'a>(
+ font_ref: &'a FontRef,
+ glyph_id: GlyphId,
+ font_size: Option<f32>,
+ ) -> Option<CblcGlyph<'a>> {
+ let cblc = font_ref.cblc().ok()?;
+ let cbdt = font_ref.cbdt().ok()?;
+
+ let strikes = &cblc.bitmap_sizes();
+ let best_strike = font_size
+ .and_then(|size| best_strike_size(strikes.iter(), size))
+ .or(strikes.get(0))?;
+
+ let location = best_strike.location(cblc.offset_data(), glyph_id).ok()?;
+
+ Some(CblcGlyph {
+ bitmap_data: cbdt.data(&location).ok()?,
+ ppem_x: best_strike.ppem_x,
+ ppem_y: best_strike.ppem_y,
+ })
+ }
+
+ pub fn has_bitmap_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool {
+ let glyph_id = GlyphId::new(glyph_id);
+ font_ref
+ .with_font(|font| {
+ let has_sbix = sbix_glyph(font, glyph_id, None).is_some();
+ let has_cblc = cblc_glyph(font, glyph_id, None).is_some();
+ Some(has_sbix || has_cblc)
+ })
+ .unwrap_or_default()
+ }
+
+ pub unsafe fn bitmap_glyph<'a>(
+ font_ref: &'a BridgeFontRef,
+ glyph_id: u16,
+ font_size: f32,
+ ) -> Box<BridgeBitmapGlyph<'a>> {
+ let glyph_id = GlyphId::new(glyph_id);
+ font_ref
+ .with_font(|font| {
+ if let Some(sbix_glyph) = sbix_glyph(font, glyph_id, Some(font_size)) {
+ return Some(Box::new(BridgeBitmapGlyph {
+ data: Some(BitmapPixelData::PngData(sbix_glyph.glyph_data.data())),
+ metrics: FfiBitmapMetrics {
+ bearing_x: sbix_glyph.glyph_data.origin_offset_x() as f32,
+ bearing_y: sbix_glyph.glyph_data.origin_offset_y() as f32,
+ ppem_x: sbix_glyph.ppem as f32,
+ ppem_y: sbix_glyph.ppem as f32,
+ placement_origin_bottom_left: true,
+ },
+ }));
+ } else if let Some(cblc_glyph) = cblc_glyph(font, glyph_id, Some(font_size)) {
+ let (bearing_x, bearing_y) = match cblc_glyph.bitmap_data.metrics {
+ BitmapMetrics::Small(small_metrics) => (
+ small_metrics.bearing_x() as f32,
+ small_metrics.bearing_y() as f32,
+ ),
+ BitmapMetrics::Big(big_metrics) => (
+ big_metrics.hori_bearing_x() as f32,
+ big_metrics.hori_bearing_y() as f32,
+ ),
+ };
+ if let BitmapContent::Data(BitmapDataFormat::Png, png_buffer) =
+ cblc_glyph.bitmap_data.content
+ {
+ return Some(Box::new(BridgeBitmapGlyph {
+ data: Some(BitmapPixelData::PngData(png_buffer)),
+ metrics: FfiBitmapMetrics {
+ bearing_x,
+ bearing_y,
+ ppem_x: cblc_glyph.ppem_x as f32,
+ ppem_y: cblc_glyph.ppem_y as f32,
+ ..Default::default()
+ },
+ }));
+ }
+ }
+ None
+ })
+ .unwrap_or_default()
+ }
+
+ pub unsafe fn png_data<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a [u8] {
+ match bitmap_glyph.data {
+ Some(BitmapPixelData::PngData(glyph_data)) => glyph_data,
+ _ => &[],
+ }
+ }
+
+ pub unsafe fn bitmap_metrics<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a FfiBitmapMetrics {
+ &bitmap_glyph.metrics
+ }
+}
+
#[cxx::bridge(namespace = "fontations_ffi")]
mod ffi {
struct ColorStop {
@@ -866,6 +1053,21 @@
pub width: i32,
}
+ #[derive(Default)]
+ struct BitmapMetrics {
+ bearing_x: f32,
+ bearing_y: f32,
+ // Not returning CBDT/CBLC encoded width and height values as these
+ // should be retrieved from the PNG, which is avoids a potential
+ // mismatch between stored metrics and actual image dimensions. ppem_*
+ // values are used to compute a scale factor on the client side.
+ ppem_x: f32,
+ ppem_y: f32,
+ // Account for the fact that Sbix and CBDT/CBLC have a different origin
+ // definition.
+ placement_origin_bottom_left: bool,
+ }
+
extern "Rust" {
type BridgeFontRef<'a>;
unsafe fn make_font_ref<'a>(font_data: &'a [u8], index: u32) -> Box<BridgeFontRef<'a>>;
@@ -933,6 +1135,16 @@
clip_box: &mut ClipBox,
) -> bool;
+ type BridgeBitmapGlyph<'a>;
+ fn has_bitmap_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool;
+ unsafe fn bitmap_glyph<'a>(
+ font_ref: &'a BridgeFontRef,
+ glyph_id: u16,
+ font_size: f32,
+ ) -> Box<BridgeBitmapGlyph<'a>>;
+ unsafe fn png_data<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a [u8];
+ unsafe fn bitmap_metrics<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a BitmapMetrics;
+
fn table_data(font_ref: &BridgeFontRef, tag: u32, offset: usize, data: &mut [u8]) -> usize;
fn table_tags(font_ref: &BridgeFontRef, tags: &mut [u32]) -> u16;
fn variation_position(