Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement OVERLAP_SIMPLE / OVERLAP_COMPOUND #402

Closed
aaronbell opened this issue Aug 28, 2020 · 19 comments
Closed

Implement OVERLAP_SIMPLE / OVERLAP_COMPOUND #402

aaronbell opened this issue Aug 28, 2020 · 19 comments

Comments

@aaronbell
Copy link

aaronbell commented Aug 28, 2020

I'm not sure if I missed a setting, but it appears that ufo2ft.compileVariableTTF doesn't implement the OVERLAP_SIMPLE / OVERLAP_COMPOUND flag that FreeType relies on to render overlapping paths correctly.

More information on the issue here: microsoft/cascadia-code#350

For Cascadia Code, I adapted this code in my build script to resolve this issue:

def overlapFlag(varFont):
    
    glyf = varFont["glyf"]

    for glyph_name in glyf.keys():
        glyph = glyf[glyph_name]
        # Set OVERLAP_COMPOUND bit for compound glyphs
        if glyph.isComposite():
            glyph.components[0].flags |= 0x400
        # Set OVERLAP_SIMPLE bit for simple glyphs
        elif glyph.numberOfContours > 0:
            glyph.flags[0] |= 0x40
    return varFont

Is this functionality available in ufo2ft? Else, where would be the best place to implement something like this?

@madig
Copy link
Collaborator

madig commented Aug 28, 2020

I petitioned @anthrotype to include that snippet in ufo2ft but he wondered if we break something with that. So, we asked Apple... and got a bit of a non-response. So. Yeah. Maybe we simply need to put it in and rely on scream testing? We certainly do it internally for our future VF releases.

Or maybe we need to test for any overlaps before setting the flags, to not unnecessarily impact performance where not needed? Hm, hm, hm.

@aaronbell
Copy link
Author

aaronbell commented Aug 28, 2020

So, we asked Apple... and got a bit of a non-response. So. Yeah.

My understanding (as I mentioned on the other thread) is that Apple assumes the flags are set for Variable Fonts. So it doesn't really matter to them either way.

I asked the folks at Microsoft and they said that they essentially ignore the flag. So it is really just FreeType to worry about.

Or maybe we need to test for any overlaps before setting the flags, to not unnecessarily impact performance where not needed? Hm, hm, hm.

This would definitely be the most elegant approach, but brute force is so much more fun. 😆

@anthrotype
Copy link
Member

Honestly I wasn't aware freetype produced such glitchy renderings of overlapping contours in VFs until reading this. The "known limitation" is also confirmed on this ft-devel thread https://www.mail-archive.com/[email protected]/msg11473.html

IIUC, this has been like that for long time, but only recently a new 4x4 oversampling feature that mitigates this was added but not enabled by default for VFs for performance reasons. The latest FreeType is instead using the OVERLAP glyf flags to determine whether to enable the oversampling, thus shifting the responsibility of detecting overlaps on the font developer/compiler; probably because doing that at runtime would be even more expensive.

I can understand why they may want to do that, however I am not sure if freetype can rely on those flags being set "correctly" by the font developer, at least for the current set of VFs out there.

The Apple TrueType spec says those flags are required to support legacy environments like PostScript printers that only understand even-odd fill rule; they mean to say to the renderer that "this glyph contains overlapping contours or components; remove overlaps using non-zero winding to obtain simplified outlines that render the same when they are filled using even-odd rule".
Only Apple implementation read those private flags, and only for static fonts. The flags were never intended to be used for variable fonts, where the non-zero winding rule is the norm and overlaps can and do occur quite frequently (because it'd be hard to make interpolatable designs without them).

Only recently the OpenType spec made those flags public, but with a caveat that they are not required and only relevant for the apple platform. The OT spec also notes that their intent is to "avoid dropouts" and "ensure that non-zero-fill algorithm is used"; it does not mention the problem of inflated pixel coverage of overlapping paths.

fontmake (via ufo2ft) never sets these flags for VFs currently, because my understanding was that they are "legacy" stuff for old Apple-only implementations. The fonttools mutator/instancer do set the flags only when creating static instances from VFs -- to work around the fill algorithm issues on Apple renderers mentioned above.
But now FreeType is giving the flags new meanings, which I feel don't exactly align with my reading of the specs nor my understanding of the current font development practice.

Also I don't think any other font compiler out there sets the overlap flags when making VFs, for the same reasons.

Now I am not saying we should ignore the issue. Actually thanks for bringing this up to my attention.

@anthrotype
Copy link
Member

anthrotype commented Sep 11, 2020

time compexity of testing for intersections between contours or components is proportional to the square of the number of elements in a glyph as we need to test all pair combinations.
I am thinking we could define a new private glyph.lib key (boolean) that tells the compiler whether to set the OVERLAP flags on the given glyph. A user could add the flag sort of manually to the known problematic glyphs that are known beforehand to be self-overlapping, thus saving build time later on.

Then we can have a new pre-processing filter, which is disabled by default (and also, it's never enabled if overlaps are being removed -- the default with statics), which detects the self-overlapping contours/components and adds tha lib key, so that the outlineCompiler can subsequently apply it when creating the TTGlyph.

fonttools varLib will then set the flags on the default glyphs if any of the non-default glyph masters set them, as outlined in fonttools/fonttools#2059

@apodtele
Copy link

In static fonts "either set this flag in the derived glyph data, or else should merge contours to remove overlap". Such strong language would never appear in the specifications for something insignificant. FreeType is just asking to be explicit about overlaps so that at least some variable glyphs or fonts do not suffer from slow rendering.

Do we want to reward some variable fonts or just punish all of them?

@anthrotype
Copy link
Member

anthrotype commented Sep 15, 2020

nobody is questioning that for static fonts overlaps should be removed, or that when dynamically generating static instances from a variable font one should set the flags for broader interoperability with old apple rasterizers that only do even-odd fill.
The problem is interpreting those flags for variable fonts as well, which "often make use of overlapping contours" as the OT spec acknowledges. And doing so not for the "avoid dropouts" problem which these flags were meant to address, but for the inflated overlap coverage issue, which appear to be specific to FreeType's VF rendering.

@apodtele
Copy link

I do not think that the inflated coverage problem is specific to FreeType. I wish I could test it on macOS with unflagged overlap in a static font. Unfortunately I do not have macOS and you say that it will show as even-odd fill. So our discussion remains hypothetical for the time being.

@anthrotype
Copy link
Member

anthrotype commented Sep 15, 2020

on macOS with unflagged overlap in a static font

it will show as even-odd fill, as this issue (and many similar) reported fonttools/fonttools#1281
Again the problem is not the use of those overlap flags for static fonts, but for variable fonts.

Note that WOFF2 fonts strip those overlap flags (they cannot encode them in the transformed glyf table): google/woff2#123

Among the workarounds for the even-odd overlap issue above with static fonts, @Lorp suggested to add an empty fvar to trigger the Apple rasterizer into thinking the font is "variable", which everybody so far understands as often being "with overlaps" and as such not needing those legacy flags.

@apodtele
Copy link

Please understand my concern too. Why should FreeType waste CPU in variable glyphs without overlaps?

@Lorp
Copy link

Lorp commented Sep 15, 2020

@apodtele the issue for me is with static fonts made on the fly from variable fonts, as in FauxFoundry but also in printer drivers or anything exporting static content. For such fonts, the producer does not want to spend CPU cycles/coding time finding out whether there are overlaps in each glyph, but wants to express “there may be overlaps” for all glyphs. The producer also wants the full benefit of WOFF2 compression, so fonttools ttLib.woff2 compress --no-glyf-transform is undesirable.

@anthrotype a dummy axis was necessary in the end, rather than an empty fvar table. So the fvar table is this (as I wrote here but with @behdad’s nameID fix):

 <fvar>

    <Axis>
      <AxisTag>AAAA</AxisTag>
      <Flags>0x0</Flags>
      <MinValue>0.0</MinValue>
      <DefaultValue>0.0</DefaultValue>
      <MaxValue>0.0</MaxValue>
      <AxisNameID>0xFFFF</AxisNameID>
    </Axis>

  </fvar>

@apodtele
Copy link

@Lorp The inflated coverage artifact would not be visible in printers and they should indeed ignore the flags. Besides WOFF2 with transformed 'glyf', CFF2 cannot flag overlaps either. Those are all good arguments against OVERLAP_SIMPLE and OVERLAP_COMPOUND, but WOFF2 also supports null-trasformed 'glyf'.

On my side is quadrupling of rendering time in FreeType to deal with overlaps which is not something to ignore. To fully appreciate the problem we need to know the cost on other platforms too. Don't you agree? So we are stuck until we have hard numbers from macOS: difference between flagged and removed overlaps, specifically. Windows might have a different algorithm, slower but immune to overlaps.

@Lorp
Copy link

Lorp commented Sep 15, 2020

I believe the artefacts appear very visibly in print, possibly via any path that transforms the TTF into a PostScript font (one would have to check 100s of actual printers to be sure). The artefacts definitely appear in PDFs with embedded fonts, at least using macOS Preview or other Apple system renderers. To be clear, these are not like the edge effects that overlaps sometimes cause, but real winding errors with significant problems that non-specialists notice immediately.

WOFF2 with null-transformed 'glyf' is somewhat larger than transformed 'glyf', so it’s reasonable to want to avoid it especially when generating fonts on the fly for web delivery.

The 4:1 slowdown in FreeType is serious, thanks for that figure. I agree that decisions by font makers should be made with all possible knowledge of their effects in rendering performance.

@aaronbell
Copy link
Author

So funny story. We recently received a new bug about strange overshoot artifacts showing up in macOS. Turns out that it struggles with paths that have coincident edges: microsoft/cascadia-code#362

And it appears that setting "OVERLAP_SIMPLE" does not have any impact on this rendering as far as I can tell.

Fun! Anyway, I've submitted a bug report through Apple's feedback tool and we'll see what happens.

@madig
Copy link
Collaborator

madig commented Mar 25, 2022

I wrote a script to set overlap bits here: google/fonts#4405 (comment)

Performance measurements on two Noto fonts:

hyperfine "py mark-overlappers.py NotoSans-VF.ttf" "py mark-overlappers.py NotoSansCJKjp-VF.ttf"
Benchmark 1: py mark-overlappers.py NotoSans-VF.ttf
  Time (mean ± σ):      1.124 s ±  0.004 s    [User: 0.001 s, System: 0.008 s]
  Range (min … max):    1.116 s …  1.128 s    10 runs

Benchmark 2: py mark-overlappers.py NotoSansCJKjp-VF.ttf
  Time (mean ± σ):     44.025 s ±  0.589 s    [User: 0.000 s, System: 0.009 s]
  Range (min … max):   43.589 s … 45.465 s    10 runs

Summary
  'py mark-overlappers.py NotoSans-VF.ttf' ran
   39.17 ± 0.54 times faster than 'py mark-overlappers.py NotoSansCJKjp-VF.ttf'

The CJK is an extreme case in two respects, glyph and overlap count:

NotoSans-VF.ttf 3748 glyphs, 535 overlapping contours (14.27%), 44 overlapping components (1.17%)
NotoSansCJKjp-VF.ttf 65535 glyphs, 64169 overlapping contours (97.92%), 0 overlapping components (0.00%)

Edit: if you comment out the save at the end, you get significantly shorter processing times. Since we're interested in processing times and you have to save anyway at some point, this might be more interesting:

Benchmark 1: py mark-overlappers.py NotoSans-VF.ttf
  Time (mean ± σ):     594.0 ms ±   5.2 ms    [User: 2.9 ms, System: 5.5 ms]
  Range (min … max):   583.9 ms … 600.8 ms    10 runs

Benchmark 2: py mark-overlappers.py NotoSansCJKjp-VF.ttf
  Time (mean ± σ):     26.646 s ±  0.163 s    [User: 0.001 s, System: 0.006 s]
  Range (min … max):   26.359 s … 26.862 s    10 runs

Summary
  'py mark-overlappers.py NotoSans-VF.ttf' ran
   44.86 ± 0.48 times faster than 'py mark-overlappers.py NotoSansCJKjp-VF.ttf'

@khaledhosny
Copy link
Collaborator

It would be interesting to write a little program that rasterizes every glyph with FreeType, so we can measure the difference of marking all glyphs indiscriminately vs ones that actually overlap.

@chrissimpkins
Copy link
Member

Re: impact on compile time

Those times are probably a small fraction of the overall compile times for the two projects in your environment?

FWIW, it seems a shame to do full glyph set overlap detection computation at compile time when there are frequently large numbers of stable glyph definitions between compiles. Would be nice to serialize the data into sources.

@madig
Copy link
Collaborator

madig commented Mar 25, 2022

Those times are probably a small fraction of the overall compile times for the two projects in your environment?

Yes, this is nothing.

FWIW, it seems a shame to do full glyph set overlap detection computation at compile time when there are frequently large numbers of stable glyph definitions between compiles. Would be nice to serialize the data into sources.

I suppose this would be one way of doing it, but you'd always need to make sure the list is up to date 🤔

@anthrotype
Copy link
Member

I think the easiest thing for now is that we add support for setting those glyf flags if the UFO glyph.lib contains the key "public.truetype.overlap" defined in https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#publictruetypeoverlap

This way the font developer can set them (manually or through some script) directly on the sources. Maybe later on we can also add a pre-processor filter that checks for overlaps and adds the lib key if not set and the glyph requires one.

@anthrotype
Copy link
Member

anthrotype commented Aug 4, 2023

the easiest thing for now is that we add support for setting those glyf flags if the UFO glyph.lib contains the key "public.truetype.overlap" defined in https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#publictruetypeoverlap

this is implemented now so I will close this

#771

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants