Repository navigation
Conversation
GdiFontComparisonTests checks on the fonts installed on Windows that the ManagedFontProvider, the default since the font service, gives the same results as the GdiFontProvider, which is how CADability got its fonts before: - every family GDI+ knows is known to the managed provider; - for a list of common fonts (TrueType, a collection and variable fonts), in every real style: the metrics, the advance widths (within one font unit) and the outlines (Hausdorff distance within two font units), including underline and strikeout; - for all installed families: the metrics, the advance widths and the outlines of a few characters. The tests only compile for net8.0-windows and run in the Windows CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
GDI cuts family names to 31 characters (LF_FACESIZE), e.g. "Bahnschrift SemiBold SemiConden", and texts saved with GDI keep that name. The ManagedFontProvider now resolves such a name to the only family whose name starts with it. The comparison with GDI checks that every family name of GDI resolves to a face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
The comparison with GDI on Windows found three differences, which a diagnostic run confirmed against the raw font tables: - Fonts that set USE_TYPO_METRICS (bit 7 of fsSelection) get their cell and line spacing from the typographic metrics of OS/2 in GDI+, not from usWinAscent and usWinDescent (Bahnschrift, Cascadia, Sitka, Cambria Math, Gabriola, Nirmala Text). - GDI+ centers the underline and the strikeout line on the positions of "post" and "OS/2"; they were drawn below them. - When xMin of a glyph is not its left side bearing in "hmtx", Windows, FreeType and fontTools move the outline so that the origin is at 0 (Times New Roman Italic, Symbol). The varied origin of a variable font is taken into account. The Windows test now compares the outlines with the unhinted outlines of GDI (GetGlyphOutline with GGO_UNHINTED): GraphicsPath.AddString returns hinted outlines, which differ from the design by a few font units in strongly hinted fonts and are wrong for Segoe Script, while the managed reader matches the unhinted outlines exactly. The underline and strikeout lines are still compared with GDI+. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
The comparison with GDI on Windows found more differences: - Windows names the instances of a variable font from "STAT": the names of the axis values in the axis ordering of "STAT", without the elidable ones. Segoe UI Variable has the instance "Light Display" in "fvar", which Windows calls "Segoe UI Variable Display Light". Without a usable "STAT" table the subfamily name of "fvar" is used as before. - "MVAR" is now applied to the metrics of an instance (typographic and Windows ascent and descent, underline, strikeout). Bahnschrift varies the size of its strikeout line. - A composite glyph with USE_MY_METRICS takes its origin from that component (Times New Roman Bold 'Ü'). The Windows test creates the GDI font with FW_DONTCARE for a regular face, because FW_NORMAL makes GDI embolden a lighter face such as "Cascadia Code ExtraLight". The strikeout values of the variable test fonts at their instances were added to the reference data with fontTools' instancer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
This was referenced Oct 15, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #412, step 6 (fonts). This PR is stacked on #471 (variable fonts), so its base is
feature/variable-fonts. It will be retargeted once #471 is merged.The comparison
GdiFontComparisonTests(Windows only,#if WINDOWS) compares theManagedFontProviderwith GDI, which is how CADability got its fonts before #412.TheManagedProviderKnowsAllFamiliesOfGdiFacesMatchGdiAllInstalledFontsMatchGdiFacesMatchGdichecks the metrics, the advance widths, the outlines and the underline and strikeout lines.AllInstalledFontsMatchGdichecks the metrics, the advance widths and a few outlines.The references and tolerances:
GetCharABCWidths, within 1 font unit.GraphicsPath.AddString, within 2 font units.GetGlyphOutlinewithGGO_NATIVE | GGO_UNHINTED), Hausdorff distance within 2 font units.GDI+ is not the reference for the outlines because
AddStringreturns hinted outlines. In strongly hinted fonts these differ from the design by a few font units, for example 17 units for '2' in Calibri and 42 for 'g' in Constantia. For Segoe Script they are simply wrong, up to 641 units. The managed reader matches the unhinted outlines exactly, with a distance of 0 in every case checked.What the first Windows run found, and the fixes
The causes were confirmed against the raw font tables with a throwaway diagnostic program on the Windows runner (branch
diag/gdi-fonts, not for merging):LF_FACESIZE), e.g. "Bahnschrift SemiBold SemiConden", and texts saved with GDI keep that name. The managed provider now resolves such a name to the only family whose name starts with it.USE_TYPO_METRICS. When bit 7 of fsSelection is set, GDI+ takes the cell (ascent, descent) and the line spacing from the typographic metrics of OS/2, not from usWinAscent and usWinDescent. This affects Bahnschrift, Cascadia, Sitka, Cambria Math, Gabriola, Nirmala Text and Sans Serif Collection. This changes the line layout of texts in these fonts, to what GDI+ did before Get the fonts of texts from a font service of the kernel instead of GDI #468.postandOS/2. Before, they were drawn below those positions.hmtx, Windows (and FreeType and fontTools) moves the outline so that the origin is at 0. This affects Times New Roman Italic and Symbol. A composite glyph withUSE_MY_METRICStakes the origin of that component instead (Times New Roman Bold 'Ü'). The varied origin of a variable font is taken into account.STAT. Windows builds the names from the axis values in the axis ordering ofSTATand leaves out the elidable ones. For example, the fvar instance "Light Display" of Segoe UI Variable becomes "Segoe UI Variable Display Light". Without a usableSTATtable, the subfamily name offvaris used as before.MVAR. It is now applied to the metrics of an instance: typographic and Windows ascent and descent, underline, and strikeout. Bahnschrift varies the size of its strikeout line this way. This removes the "MVAR is not applied" limitation of Read variable fonts: gvar, CFF2, avar and HVAR (#412, step 6) #471.One case is a GDI artifact rather than a reader difference. With
FW_NORMAL, GDI emboldens a lighter face such as the instance "Cascadia Code ExtraLight". The test therefore creates the GDI font withFW_DONTCAREfor a regular face.Tests on Linux
Each fix has a test that runs on every platform:
hmtxof Liberation Sans in memory;USE_MY_METRICS, through Liberation Sans 'Ü';USE_TYPO_METRICS, by patching fsSelection;STATordering and elision, by patching theSTATof VarTestSans;MVAR, against fontTools' instancer values, which were added toVarTestSans.reference.jsonbymake_variable_test_fonts.py.Mutation checks confirmed that these tests catch the following when they are switched off: the origin shift,
USE_MY_METRICS,MVAR, theSTATordering and the elision.Results
diag/gdi-fontsrun of this head):CADability.Tests675 passed, 2 skipped, 0 failed. This includes the GDI comparison and the reference bitmaps.CADability.Tests651 passed, 12 skipped;CADability.ImportTests88 passed.CADability.Formsbuilds.The Windows CI job (#469) is not part of this stack, so this PR's own CI runs on Linux only. The integration draft #467 runs both.
No public API change.
TrueTypeFontandFontVariationsare internal.🤖 Generated with Claude Code
https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
Generated by Claude Code