googlefonts / fontations

Reading and writing font files
Apache License 2.0
205 stars 16 forks source link

[Skrifa] Low overhead retrieval of adjusted metrics #790

Open drott opened 4 months ago

drott commented 4 months ago

In Skia, metrics retrieval is a first stage of glyph cache entry initialisation. There are two methods called on a Skia typeface backend: generateMetrics() and depending on glyph format then generateImage(), generateDrawable() or generatePath().

Depending on hinting configuration or if the font is variable without variable mvar, in the generateMetrics() stage, the adjusted metrics from Scaler::outline may be needed without an actual draw call to draw the glyph.

Currently, the Scaler call signature is: pub fn outline(&mut self, glyph_id: GlyphId, pen: &mut impl Pen) -> Result<ScalerMetrics>

One way to retrieve the metrics only is then to perform a call with a Pen performing only no-ops, but on the Skrifa side the path would be traversed.

For efficiency, it would be useful to have access to only the ScalerMetrics/AdjustedMetrics and the hinting or phantom point placement based on var-coords would only execute on phantom points and not the whole contour.

This could be done by wrapping the Pen argument in Option<> or providing a separate function.

dfrg commented 4 months ago

We could possibly add OutlineGlyph::adjusted_metrics() that is basically the same as draw() but without the pen. For TrueType this would save the outline to path conversion cost but not the loading/scaling/hinting cost. I can't think of any low overhead method to compute hinted metrics. We need to run the program over the whole outline.

dfrg commented 4 months ago

Matching FreeType here has been a bit of a problem due to the fact that FreeType offers multiple versions of the same metric that are computed in different ways. There is also a bit of an impedance mismatch between what we offer and what Skia expects.

I'd like to propose the following:

Add a new compat_metrics(&self, draw_settings: DrawSettings, glyph_id: GlyphId) -> CompatMetrics method to OutlineGlyphCollection. The new CompatMetrics type will contain advance_width, linear_advance_width and bounding_box which exactly match FT and should be sufficient to support what Skia gathers here. Vertical metrics are still TODO but I can prioritize when necessary. I'd like to gate this behind a compat_metrics feature as I think it might be confusing for non-Skia users and we should encourage usage of precise linear metrics when FT compatibility is not strictly required.

As a bonus, change GlyphMetrics to return the most precise values possible, fulfilling point 2 of #693. Maybe we can move Skia to this at some point in the future.

@drott @rsheeter thoughts?

drott commented 4 months ago

From my point of view, 1) I am okay with moving to CompatMetrics for FreeType-style scaled metrics. 2) Is it just me? I find it hard to understand or make sense of the term "linear metrics" and different context seem to attach different meaning to it. Sometimes it's used in Skia to describe metrics that scale with font size without reshaping or applying OpenType features at higher sizes? So unless it's really some kind of appropriate technical term here, can we use different, hopefully more descriptive names? 3) As mentioned, Skia has two stages: a) onGenerateMetrics(), at which point we have to make a glyph format choice, and identify metrics that will be used for the lifetime of this SkTypeface for this glyph b) onGenerateImage/onGeneratePath/onGenerateDrawable: Rasterize or generate an SkPath/SkDrawable at which point we have to produce a visual glyph - but we can't change the metrics here anymore - after the call to draw() and retrieving AdjustedMetrics.

Metrics Choice

So in the a) stage we need to make a decision on where to get the glyphs from. My understanding is:

CC @bungeman

Does that sound reasonable?

dfrg commented 4 months ago

Thanks for the additional context.

Linear is the technically correct terminology because the plot of font size vs metric is literally a line :) In practice, the plot becomes discontinuous only when metrics are modified by hinting so maybe “unhinted” would offer more clarity? In FT specifically, linearHoriAdvance is also scaled and rounded differently with different precision as well.

For outline glyphs, my intention is that we’ll handle the hinted/unhinted case on our end in compat_metrics (DrawSettings has enough info for this although fallback to auto hinting might be a bit of a wrinkle. Let me think on this).

For bitmaps, I’ll have to double check but I believe FT uses the metrics from the bitmap tables for EBDT/CBDT. COLRv1 and sbix would use CompatMetrics.

drott commented 4 months ago

Ok, so looks like we are roughly on the same page? Yes, linear is "without hinting". As far as I remember from recently looking into it, the bitmap metrics are not used, at least not in Skia - usually hmtx would be available, so bitmap metrics only as fallback, I'd say? And IIRC, SBIX does not have any - there, it's always htmx.

dfrg commented 4 months ago

Yes, on the same page. I’ll get started on this early next week.

You are correct for sbix but it appears that the other bitmap tables do fill the glyph slot metrics with bitmap specific data: https://github.com/freetype/freetype/blob/546237e1bbbb1269b5f76a878ea5eed3c8e268b5/src/truetype/ttgload.c#L2140. I don’t think this matters so much for color bitmaps but small monochromatic bitmaps really do need the exact values so we should probably support it.