Skip to content

Read the CFF outlines of OpenType fonts in the managed font reader - #470

Open
dsn27 wants to merge 3 commits into
feature/font-servicefrom
feature/cff-outlines
Open

dsn27 wants to merge 3 commits into
feature/font-servicefrom
feature/cff-outlines

Conversation

@dsn27

@dsn27 dsn27 commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Step 6 of #412, part 2b.

Stacked on #468 (which is stacked on #466). The base is feature/font-service, so the diff shows only this part. It is also merged into integration/issue-412 (#467).

Why

The managed reader of #466/#468 skipped OpenType fonts with CFF outlines. Many .otf files are such fonts, so a text in one of them fell back to the default face. #468 named this as a behaviour change for the release notes; with this PR that limitation is gone.

What

New internal CADability/CffOutlines.cs reads the CFF table:

  • Table structures: INDEX and DICT structures, and the FontMatrix. The FontMatrix is stored rounded (e.g. 0.00048828 for 1/2048), so a factor within 0.1 % of 1 counts as 1.
  • Type 2 charstring interpreter:
    • all path operators, including the specialized curves and lines (hvcurveto, vhcurveto, hhcurveto, vvcurveto, hlineto, vlineto, rcurveline, rlinecurve);
    • flex, hflex, hflex1, flex1;
    • local and global subroutines with their bias;
    • the width operand;
    • hints and hint masks (skipped, they do not change the outline);
    • the arithmetic and storage operators.
  • CID-keyed fonts: FDArray with a private dict and local subroutines per font dict, and FDSelect formats 0 and 3.

The cubic curves go into the GDI+ path data unchanged, so GlyphShapeBuilder gets exact curves as for TrueType.

Other changes:

  • TrueTypeFont gets HasCffOutlines and HasOutlines.
  • ManagedFontProvider also indexes .otf and .otc files.

Not supported:

  • CFF2, i.e. variable OpenType fonts with a CFF2 table. Such fonts are still left out.
  • The deprecated accent composition of endchar (seac), which modern fonts do not use.

Tests

make_cff_test_fonts.py makes two small test fonts (about 12 KB each) from a subset of Inter (SIL OFL 1.1, registered in REUSE.toml). Inter as shipped uses only the general operators, so the script rewrites the charstrings without changing the outlines:

  • InterCffTest.otf (name-keyed) uses the specialized operators, flex, hint masks, and local and global subroutines;
  • InterCidTest.otf has the same glyphs as a CID-keyed font with two font dicts, each with its own local subroutines.

The script asserts with fontTools that both fonts draw exactly the outlines of the original subset.

New CffOutlinesTests:

  • the drawing commands of all 104 characters of both fonts match fontTools command by command;
  • path data;
  • header-only reading of an .otf;
  • a Text in an OpenType font through the ManagedFontProvider.

Mutation check. I broke the interpreter deliberately in four places: the subroutine bias, flex, the fifth operand of hvcurveto, and FDSelect. Each break made a test fail; the FDSelect one only the CID test, as intended.

Checked locally, not in CI:

  • fontTools comparison: 18,672 glyphs sampled from all 47 CFF fonts of the Linux container, 7 of them CID-keyed (Unifont), give the same drawing commands as fontTools. 0 differences.
  • Robustness: over all 93 font files of the container, the reader now reads 94 of 95 fonts, 245,039 glyphs without an error (120,664 before). Only the color emoji bitmap font is left.

Local run on Linux:

  • CADability.Tests: 638 passed, 12 skipped, 0 failed.
  • CADability.ImportTests: 88 passed, 0 skipped, 0 failed.
  • The kernel and CADability.Forms build.

No public API change in this part and no change to the csproj version.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5


Generated by Claude Code

)

Step 6, part 2b of #412. The managed reader of #466 skipped OpenType
fonts with CFF outlines, which many .otf files have, so a text in such
a font fell back to the default face. The new internal CffOutlines
reads the "CFF " table:

- INDEX and DICT structures, the FontMatrix (which is stored rounded,
  e.g. 0.00048828 for 1/2048)
- the Type 2 charstring interpreter: all path operators including the
  specialized curves and lines, flex, hflex, hflex1, flex1, local and
  global subroutines with their bias, widths, hints and hint masks
  (skipped, they do not change the outline), the arithmetic operators
- CID-keyed fonts with FDArray and FDSelect (formats 0 and 3)

The cubic curves go into the GDI+ path data unchanged. Not supported:
CFF2 (variable OpenType fonts) and the deprecated accent composition of
endchar (seac). TrueTypeFont gets HasCffOutlines and HasOutlines, and
ManagedFontProvider indexes .otf and .otc files too.

Tests: make_cff_test_fonts.py makes two small test fonts from a subset
of Inter (SIL OFL 1.1): InterCffTest.otf rewritten to use subroutines,
the specialized operators, flex and hint masks, and InterCidTest.otf,
the same glyphs as a CID-keyed font with two font dicts. The outlines
of both are compared command by command with fontTools. Checked
locally against fontTools on 18,672 glyphs of the 47 CFF fonts of the
Linux container, 7 of them CID-keyed: all equal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5

This branch has not been deployed

No deployments
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

Successfully merging this pull request may close these issues.

2 participants