Skip to content

Make the GDI font provider the default in CADability.Forms (#412) - #503

Merged
dsn27 merged 1 commit into
feature/gdi-glyph-comparisonfrom
feature/gdi-fonts-in-forms
Oct 19, 2026
Merged

dsn27 merged 1 commit into
feature/gdi-glyph-comparisonfrom
feature/gdi-fonts-in-forms

Conversation

@dsn27

@dsn27 dsn27 commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Part of #412. Stacked on #472. Retarget it once the font stack is merged.

Why

With #468, texts get their fonts from FontProvider.Current. On every platform, Windows included, that is the managed TrueType reader. Even with #470, #471, #472 and #500, its outlines and synthetic styles are not always identical to those of GDI and GDI+, which CADability used before. Existing Windows Forms applications should keep their texts exactly as they were.

What

  • CADability.Forms makes GdiFontProvider the default. This happens as soon as one of its main classes is used: CadControl, CadCanvas, CadFrame, PaintToOpenGL or PaintToGDI. Their static constructors call GdiFontProvider.MakeDefault(), and only the first call does anything. A static constructor runs before the first instance and before any base constructor, so the provider is in place before FrameImpl gets to texts.
  • The application's choice is kept. If an application sets FontProvider.Current itself, that provider stays, whether it was set before or after CADability.Forms was loaded. On Windows, an application can still choose the managed provider with FontProvider.Current = new ManagedFontProvider();.
  • The kernel's own default stays the managed provider. Applications without CADability.Forms see no change: the browser, other platforms, and renderers implemented directly against IPaintTo3D.

FontProvider tells the two cases apart. The setter marks the provider as the application's choice, and the new internal FontProvider.SetDefault replaces only a default. CADability.Forms reaches it through InternalsVisibleTo (same key as the tests), so the PR adds no public API. The caches are cleared outside the new lock, because they have locks of their own and a thread that holds one of them may ask for Current.

Tests

FontProviderDefaultTests saves and restores the static state and does not run in parallel with other tests.

  • On every platform:
    • The default is the managed provider.
    • A front end can replace the default, before and after the default was first used.
    • The application's provider is kept, whether it was set before or after the front end.
  • Windows only:
    • MakeDefault makes GDI the default and keeps the application's choice.
    • The static constructors of the five classes call GdiFontProvider.MakeDefault, checked in their IL.

The test assembly sets its own provider in [AssemblyInitialize] (TestFonts), so the other tests keep the managed reader with the test fonts.

Locally on Linux:

  • CADability.Tests: 683 passed, 0 failed.
  • The Windows build of the tests compiles (-p:OS=Windows_NT).
  • The Windows tests run in this PR's Windows job.

Generated by Claude Code

With #468 the texts get their fonts from FontProvider.Current, which is the
managed TrueType reader on every platform. To keep the texts of existing
Windows Forms applications exactly as before, CADability.Forms now makes its
GdiFontProvider the default as soon as one of its main classes is used
(CadControl, CadCanvas, CadFrame, PaintToOpenGL, PaintToGDI): their static
constructors call GdiFontProvider.MakeDefault.

The kernel keeps the managed provider as its own default, so nothing changes
for applications without CADability.Forms (the browser, other platforms,
renderers of their own). A provider the application sets through
FontProvider.Current is kept, whether it is set before or after
CADability.Forms is loaded, so an application can still choose the managed
provider on Windows.

FontProvider tells the two cases apart: the setter marks the provider as the
application's choice, the new internal FontProvider.SetDefault replaces only
a default. CADability.Forms reaches it through InternalsVisibleTo, so there
is no new public API.

Refs #412

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
@dsn27
dsn27 merged commit cac8016 into feature/gdi-glyph-comparison Oct 19, 2026
2 checks passed
@dsn27
dsn27 deleted the feature/gdi-fonts-in-forms branch October 19, 2026 10:00
dsn27 added a commit that referenced this pull request Oct 19, 2026
…rences (#412, step 6) (#472)

* Add a managed TrueType reader for text on every platform (#412)

First part of step 6 of #412. Text gets its glyphs from GDI today
(GraphicsPath.AddString, gdi32 for widths and kerning), which does not
exist outside Windows. The new internal class TrueTypeFont reads font
files itself:

- .ttf files and every font of a .ttc collection; CFF-based .otf and
  bitmap-only fonts are recognized and report HasTrueTypeOutlines=false
- glyph contours from glyf/loca with composite glyphs resolved (offsets,
  scales, 2x2 transforms, point matching)
- cmap formats 0, 4, 6 and 12, including symbol fonts
- advance widths (hmtx), metrics (head, hhea, OS/2) and names (name)
- kerning pairs of the version 0 kern table, as GDI reads them
- GetPathData returns the outline in the layout of a GDI+ GraphicsPath
  after AddString, so GlyphShapeBuilder.PathDataToPath2D can take it
  unchanged; the quadratic segments become cubic ones by exact degree
  elevation

Nothing uses it yet: the font service that replaces GDI in Text and
FontCache follows in the next pull request.

The tests compare names, metrics, glyph indices, advance widths, all
contours of 112 characters and the kerning with reference data that
fontTools wrote from the same font (Liberation Sans, SIL OFL 1.1, added
under tests/CADability.Tests/Files/Fonts together with the script that
wrote the reference). They also check that the outline is exactly the
quadratic curves, a font collection, and invalid input.

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

* Get the fonts of texts from a font service of the kernel instead of GDI (#412)

Second part of step 6 of #412. Text and FontCache no longer use GDI or
GDI+: the glyph outlines, advance widths, kerning, metrics and the list
of font families come from the static, settable FontProvider.Current,
not from FrameImpl.MainFrame, so texts work without a frame and on
every platform.

- FontProvider and FontFace (public, new) describe what the kernel
  needs. A face returns its glyphs as Bezier path data in the layout of
  GraphicsPath.AddString, which the kernel turns into geometry with
  GlyphShapeBuilder as before, so no provider can flatten the outlines.
- ManagedFontProvider (public, new) is the default on all platforms,
  as decided for #412. It indexes the TrueType fonts of the system font
  directories by reading only their header tables, and loads a font
  file when a face is used. Files and font data can be added.
- FontCache no longer creates a GDI device context in its constructor
  or deletes it in a finalizer, which ended the process outside Windows.
- Without any font, painting and the extent of a text leave it out;
  GetOutline and similar explicit requests throw an
  InvalidOperationException that says what to do.
- ShowPropertyFont and DimensionStyle list the families of the provider.
- CADability.Forms.GdiFontProvider (public, new) is the old GDI
  implementation for applications that want it.

TrueTypeFont reads the underline and strikeout metrics, adds both as
rectangles like GDI+ does, and can read only the header tables of a
font file.

Tests: both test projects register Liberation Sans in an assembly
initializer, so texts have a font even on a machine without fonts. The
text tests that were inconclusive on Linux ("text needs GDI") now run
there; new FontProviderTests cover text without a frame, the default
face for unknown fonts, the exception without fonts, underline and
strikeout, style fallback, header-only reading and the finalizer.

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

* Read the CFF outlines of OpenType fonts in the managed font reader (#412)

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

* Read variable fonts: gvar, CFF2, avar and HVAR (#412)

The managed font reader now handles variable OpenType fonts. The named
instances of a variable font become faces of their own, with family names
as GDI makes them ("Bahnschrift Light Condensed", "Segoe UI Variable
Display Semibold"), so these fonts no longer fall back to their default
instance or, for CFF2, disappear from the font list.

- FontVariations parses fvar, avar, the gvar header and HVAR, normalizes
  user coordinates and computes tuple scalars like fontTools'
  supportScalar (peak and intermediate regions).
- TrueType outlines: gvar deltas with shared and private point numbers,
  packed deltas, interpolation of untouched points (IUP), phantom points
  and the offsets of composite components.
- CFF2 outlines: 32-bit INDEX counts, the Top DICT outside an INDEX,
  FDSelect format 4, vsindex and blend with the ItemVariationStore.
- Advance widths come from HVAR, or from the phantom points without HVAR.
  They are not rounded. MVAR is not applied.

The test fonts are subsets of Source Sans 3 VF (OFL-1.1), renamed to
"VarTest Sans" because "Source" is a Reserved Font Name. Their reference
outlines and advance widths were written with fontTools.

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

* Compare the managed font reader with GDI on Windows (#412)

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

* Find a family by the name GDI cuts to 31 characters (#412)

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

* Lay out glyphs, metrics and decorations like GDI (#412)

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

* Name and measure variable instances like Windows (#412)

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

* Correct the documentation of HasOutlines and HasCffOutlines for CFF2 (#412)

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

* Adapt the font cache test of #497 to the font service (#412)

ClearingTheFontCacheDisposesTheGlyphLists reflected on the 'kerning' field
of the GDI FontCache, which this branch replaces by the 'faces' of
FontProvider.Current, so it failed with a NullReferenceException on
Windows. It now checks that Clear empties the faces, and it runs on every
platform, because the font cache no longer needs GDI.

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

* Pass Unicode code points instead of chars to FontFace (#412)

FontFace.GetAdvanceWidth, GetKerning and GetGlyphPath took a char, a UTF-16
code unit. A character outside the Basic Multilingual Plane, e.g. most emoji,
is a surrogate pair of two chars in a string, so it could never reach a font:
the text went through the font char by char. They now take a code point
(int). FontFace is new public API of this PR, so this is the time to change
it; after a release, every custom font provider would break.

- Text and FontCache go through the text as code points (Text.CodePoints):
  a surrogate pair is one character, a lone surrogate stays the char it is.
- The managed face passes the code point on to the cmap, which already
  supports format 12.
- The GDI face measures a character beyond U+FFFF with
  GetTextExtentPoint32, because GetCharABCWidths only takes UTF-16 chars,
  and reads the kerning pairs as unsigned values.

The new FontCodePointTests check with a recording font provider that "a",
an emoji and "b" reach the font as three characters, with their widths,
kerning and outlines; they fail when the surrogate pair is not combined.

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

* Lock the shared sets of the parallel STEP import

With StepImport.Parallel, CreateEntity runs on several threads, but it added
mapped items to the HashSet mappedItems and, for assemblies, products to the
HashSet products without a lock (the other place that adds products locks
it). Concurrent Add calls can corrupt a HashSet, and the enumeration of
mappedItems after the creation then met a null entry: a
NullReferenceException in ImportStep.Read.

StepCyclicReferenceTest from #498 imports a file with mapped items 20 times
in parallel and showed this now and then on the 2-core CI runner. With the
test process limited to 2 cores, it failed 3 of 30 times without this change
and 0 of 60 times with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
(cherry picked from commit 85e21f1)

* Make the GDI font provider the default in CADability.Forms (#503)

With #468 the texts get their fonts from FontProvider.Current, which is the
managed TrueType reader on every platform. To keep the texts of existing
Windows Forms applications exactly as before, CADability.Forms now makes its
GdiFontProvider the default as soon as one of its main classes is used
(CadControl, CadCanvas, CadFrame, PaintToOpenGL, PaintToGDI): their static
constructors call GdiFontProvider.MakeDefault.

The kernel keeps the managed provider as its own default, so nothing changes
for applications without CADability.Forms (the browser, other platforms,
renderers of their own). A provider the application sets through
FontProvider.Current is kept, whether it is set before or after
CADability.Forms is loaded, so an application can still choose the managed
provider on Windows.

FontProvider tells the two cases apart: the setter marks the provider as the
application's choice, the new internal FontProvider.SetDefault replaces only
a default. CADability.Forms reaches it through InternalsVisibleTo, so there
is no new public API.

Refs #412


Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
dsn27 added a commit that referenced this pull request Oct 19, 2026
* Add a managed TrueType reader for text on every platform (#412)

First part of step 6 of #412. Text gets its glyphs from GDI today
(GraphicsPath.AddString, gdi32 for widths and kerning), which does not
exist outside Windows. The new internal class TrueTypeFont reads font
files itself:

- .ttf files and every font of a .ttc collection; CFF-based .otf and
  bitmap-only fonts are recognized and report HasTrueTypeOutlines=false
- glyph contours from glyf/loca with composite glyphs resolved (offsets,
  scales, 2x2 transforms, point matching)
- cmap formats 0, 4, 6 and 12, including symbol fonts
- advance widths (hmtx), metrics (head, hhea, OS/2) and names (name)
- kerning pairs of the version 0 kern table, as GDI reads them
- GetPathData returns the outline in the layout of a GDI+ GraphicsPath
  after AddString, so GlyphShapeBuilder.PathDataToPath2D can take it
  unchanged; the quadratic segments become cubic ones by exact degree
  elevation

Nothing uses it yet: the font service that replaces GDI in Text and
FontCache follows in the next pull request.

The tests compare names, metrics, glyph indices, advance widths, all
contours of 112 characters and the kerning with reference data that
fontTools wrote from the same font (Liberation Sans, SIL OFL 1.1, added
under tests/CADability.Tests/Files/Fonts together with the script that
wrote the reference). They also check that the outline is exactly the
quadratic curves, a font collection, and invalid input.

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

* Get the fonts of texts from a font service of the kernel instead of GDI (#412)

Second part of step 6 of #412. Text and FontCache no longer use GDI or
GDI+: the glyph outlines, advance widths, kerning, metrics and the list
of font families come from the static, settable FontProvider.Current,
not from FrameImpl.MainFrame, so texts work without a frame and on
every platform.

- FontProvider and FontFace (public, new) describe what the kernel
  needs. A face returns its glyphs as Bezier path data in the layout of
  GraphicsPath.AddString, which the kernel turns into geometry with
  GlyphShapeBuilder as before, so no provider can flatten the outlines.
- ManagedFontProvider (public, new) is the default on all platforms,
  as decided for #412. It indexes the TrueType fonts of the system font
  directories by reading only their header tables, and loads a font
  file when a face is used. Files and font data can be added.
- FontCache no longer creates a GDI device context in its constructor
  or deletes it in a finalizer, which ended the process outside Windows.
- Without any font, painting and the extent of a text leave it out;
  GetOutline and similar explicit requests throw an
  InvalidOperationException that says what to do.
- ShowPropertyFont and DimensionStyle list the families of the provider.
- CADability.Forms.GdiFontProvider (public, new) is the old GDI
  implementation for applications that want it.

TrueTypeFont reads the underline and strikeout metrics, adds both as
rectangles like GDI+ does, and can read only the header tables of a
font file.

Tests: both test projects register Liberation Sans in an assembly
initializer, so texts have a font even on a machine without fonts. The
text tests that were inconclusive on Linux ("text needs GDI") now run
there; new FontProviderTests cover text without a frame, the default
face for unknown fonts, the exception without fonts, underline and
strikeout, style fallback, header-only reading and the finalizer.

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

* Read the CFF outlines of OpenType fonts in the managed font reader (#412)

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

* Read variable fonts: gvar, CFF2, avar and HVAR (#412)

The managed font reader now handles variable OpenType fonts. The named
instances of a variable font become faces of their own, with family names
as GDI makes them ("Bahnschrift Light Condensed", "Segoe UI Variable
Display Semibold"), so these fonts no longer fall back to their default
instance or, for CFF2, disappear from the font list.

- FontVariations parses fvar, avar, the gvar header and HVAR, normalizes
  user coordinates and computes tuple scalars like fontTools'
  supportScalar (peak and intermediate regions).
- TrueType outlines: gvar deltas with shared and private point numbers,
  packed deltas, interpolation of untouched points (IUP), phantom points
  and the offsets of composite components.
- CFF2 outlines: 32-bit INDEX counts, the Top DICT outside an INDEX,
  FDSelect format 4, vsindex and blend with the ItemVariationStore.
- Advance widths come from HVAR, or from the phantom points without HVAR.
  They are not rounded. MVAR is not applied.

The test fonts are subsets of Source Sans 3 VF (OFL-1.1), renamed to
"VarTest Sans" because "Source" is a Reserved Font Name. Their reference
outlines and advance widths were written with fontTools.

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

* Compare the managed font reader with GDI on Windows (#412)

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

* Find a family by the name GDI cuts to 31 characters (#412)

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

* Lay out glyphs, metrics and decorations like GDI (#412)

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

* Name and measure variable instances like Windows (#412)

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

* Correct the documentation of HasOutlines and HasCffOutlines for CFF2 (#412)

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

* Adapt the font cache test of #497 to the font service (#412)

ClearingTheFontCacheDisposesTheGlyphLists reflected on the 'kerning' field
of the GDI FontCache, which this branch replaces by the 'faces' of
FontProvider.Current, so it failed with a NullReferenceException on
Windows. It now checks that Clear empties the faces, and it runs on every
platform, because the font cache no longer needs GDI.

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

* Pass Unicode code points instead of chars to FontFace (#412)

FontFace.GetAdvanceWidth, GetKerning and GetGlyphPath took a char, a UTF-16
code unit. A character outside the Basic Multilingual Plane, e.g. most emoji,
is a surrogate pair of two chars in a string, so it could never reach a font:
the text went through the font char by char. They now take a code point
(int). FontFace is new public API of this PR, so this is the time to change
it; after a release, every custom font provider would break.

- Text and FontCache go through the text as code points (Text.CodePoints):
  a surrogate pair is one character, a lone surrogate stays the char it is.
- The managed face passes the code point on to the cmap, which already
  supports format 12.
- The GDI face measures a character beyond U+FFFF with
  GetTextExtentPoint32, because GetCharABCWidths only takes UTF-16 chars,
  and reads the kerning pairs as unsigned values.

The new FontCodePointTests check with a recording font provider that "a",
an emoji and "b" reach the font as three characters, with their widths,
kerning and outlines; they fail when the surrogate pair is not combined.

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

* Lock the shared sets of the parallel STEP import

With StepImport.Parallel, CreateEntity runs on several threads, but it added
mapped items to the HashSet mappedItems and, for assemblies, products to the
HashSet products without a lock (the other place that adds products locks
it). Concurrent Add calls can corrupt a HashSet, and the enumeration of
mappedItems after the creation then met a null entry: a
NullReferenceException in ImportStep.Read.

StepCyclicReferenceTest from #498 imports a file with mapped items 20 times
in parallel and showed this now and then on the 2-core CI runner. With the
test process limited to 2 cores, it failed 3 of 30 times without this change
and 0 of 60 times with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
(cherry picked from commit 85e21f1)

* Synthesize a missing bold or italic face like GDI+ (#412)

GDI+ reports every style of every family as available and synthesizes the
missing ones, so the texts of CADability before the font service showed a
bold or italic text in a family without that face (e.g. Microsoft Sans
Serif, Lucida Console, Impact) as synthetic bold or italic. The managed
provider took the closest face instead, so such a text would have lost its
style on Windows.

ManagedFontProvider now wraps the closest face in a SyntheticFontFace:

- Italic shears the glyphs by 0x5700/0x10000 (18.77 degrees) around the
  baseline; the advance widths stay. GDI and GDI+ do exactly this.
- Bold moves the outline outwards by half the strength on every side
  (FT_Outline_EmboldenXY of FreeType) and then by half the strength to the
  right and up: the left side and the baseline stay, every edge facing
  right moves right by ceiling(em / 50) font units and every edge facing up
  moves up by one unit less. A face with a weight of 600 or more is not
  made wider. The advance widths stay, as with the GDI widths CADability
  used.

The rules were measured with GDI+ on the Windows runner for all installed
families without a bold face. TrueTypeFont reads usWeightClass (or the
wght axis of an instance) for the weight rule.

Tests: the emboldened outlines of Lucida Console and Microsoft Sans Serif
'I' equal what GDI+ returned on Windows to 0.01 font units; holes get
smaller; the strength for 256 to 4096 units per em; italic and bold of the
regular-only Liberation Sans. SyntheticStyleGdiTests compares the synthetic
styles with GDI+ on Windows for several families.

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

* Make the quadratic TrueType outline wider for synthetic bold

The first Windows comparison showed the straight edges equal to GDI+, but
curves off by a few font units ('o') and some glyphs by up to 35 ('8',
serifs of Sylfaen and Gabriola). GDI+ makes the glyph wider on the outline
of the font, before its quadratic segments become cubic ones: the points it
moves and the limit by the shorter edge depend on that point set. The
emboldening now works on the quadratic TrueType contours in font units; a
CFF outline is made wider after it was drawn, as before.

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

* Keep the synthetic bold shift within half the strength per axis like GDI+

Measured point by point against GDI+ on the Windows runner: GDI+ does not
limit the shift at an inner corner by the length of the shorter edge, as
FT_Outline_EmboldenXY does, so the short edges of the rounded serif corners
of Sylfaen move by the whole strength. Instead it keeps the shift of every
point within half the strength on each axis, so that the sharp corners of
the '8' of Microsoft Sans Serif do not shoot out by up to 24 font units.

The comparison test now matches the points of the managed and the GDI+
outline by position, because GDI+ starts the contours of MS Gothic at
another point.

Refs #412

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

* Compare Sylfaen and Gabriola with GDI+ within 2% of the em height

GDI+ moves single points of these two families differently from the other
five, without a rule that could be found (e.g. a point in the middle of a
straight slanted edge of the Sylfaen '8' only moves up). The differences are
up to 1.6% and 0.85% of the em height; the other families keep the
tolerance of 2 font units.

Refs #412

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

* Make the GDI font provider the default in CADability.Forms (#503)

With #468 the texts get their fonts from FontProvider.Current, which is the
managed TrueType reader on every platform. To keep the texts of existing
Windows Forms applications exactly as before, CADability.Forms now makes its
GdiFontProvider the default as soon as one of its main classes is used
(CadControl, CadCanvas, CadFrame, PaintToOpenGL, PaintToGDI): their static
constructors call GdiFontProvider.MakeDefault.

The kernel keeps the managed provider as its own default, so nothing changes
for applications without CADability.Forms (the browser, other platforms,
renderers of their own). A provider the application sets through
FontProvider.Current is kept, whether it is set before or after
CADability.Forms is loaded, so an application can still choose the managed
provider on Windows.

FontProvider tells the two cases apart: the setter marks the provider as the
application's choice, the new internal FontProvider.SetDefault replaces only
a default. CADability.Forms reaches it through InternalsVisibleTo, so there
is no new public API.

Refs #412


Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
dsn27 added a commit that referenced this pull request Oct 19, 2026
… 9b) (#479)

* Add a managed TrueType reader for text on every platform (#412)

First part of step 6 of #412. Text gets its glyphs from GDI today
(GraphicsPath.AddString, gdi32 for widths and kerning), which does not
exist outside Windows. The new internal class TrueTypeFont reads font
files itself:

- .ttf files and every font of a .ttc collection; CFF-based .otf and
  bitmap-only fonts are recognized and report HasTrueTypeOutlines=false
- glyph contours from glyf/loca with composite glyphs resolved (offsets,
  scales, 2x2 transforms, point matching)
- cmap formats 0, 4, 6 and 12, including symbol fonts
- advance widths (hmtx), metrics (head, hhea, OS/2) and names (name)
- kerning pairs of the version 0 kern table, as GDI reads them
- GetPathData returns the outline in the layout of a GDI+ GraphicsPath
  after AddString, so GlyphShapeBuilder.PathDataToPath2D can take it
  unchanged; the quadratic segments become cubic ones by exact degree
  elevation

Nothing uses it yet: the font service that replaces GDI in Text and
FontCache follows in the next pull request.

The tests compare names, metrics, glyph indices, advance widths, all
contours of 112 characters and the kerning with reference data that
fontTools wrote from the same font (Liberation Sans, SIL OFL 1.1, added
under tests/CADability.Tests/Files/Fonts together with the script that
wrote the reference). They also check that the outline is exactly the
quadratic curves, a font collection, and invalid input.

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

* Get the fonts of texts from a font service of the kernel instead of GDI (#412)

Second part of step 6 of #412. Text and FontCache no longer use GDI or
GDI+: the glyph outlines, advance widths, kerning, metrics and the list
of font families come from the static, settable FontProvider.Current,
not from FrameImpl.MainFrame, so texts work without a frame and on
every platform.

- FontProvider and FontFace (public, new) describe what the kernel
  needs. A face returns its glyphs as Bezier path data in the layout of
  GraphicsPath.AddString, which the kernel turns into geometry with
  GlyphShapeBuilder as before, so no provider can flatten the outlines.
- ManagedFontProvider (public, new) is the default on all platforms,
  as decided for #412. It indexes the TrueType fonts of the system font
  directories by reading only their header tables, and loads a font
  file when a face is used. Files and font data can be added.
- FontCache no longer creates a GDI device context in its constructor
  or deletes it in a finalizer, which ended the process outside Windows.
- Without any font, painting and the extent of a text leave it out;
  GetOutline and similar explicit requests throw an
  InvalidOperationException that says what to do.
- ShowPropertyFont and DimensionStyle list the families of the provider.
- CADability.Forms.GdiFontProvider (public, new) is the old GDI
  implementation for applications that want it.

TrueTypeFont reads the underline and strikeout metrics, adds both as
rectangles like GDI+ does, and can read only the header tables of a
font file.

Tests: both test projects register Liberation Sans in an assembly
initializer, so texts have a font even on a machine without fonts. The
text tests that were inconclusive on Linux ("text needs GDI") now run
there; new FontProviderTests cover text without a frame, the default
face for unknown fonts, the exception without fonts, underline and
strikeout, style fallback, header-only reading and the finalizer.

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

* Read the CFF outlines of OpenType fonts in the managed font reader (#412)

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

* Read variable fonts: gvar, CFF2, avar and HVAR (#412)

The managed font reader now handles variable OpenType fonts. The named
instances of a variable font become faces of their own, with family names
as GDI makes them ("Bahnschrift Light Condensed", "Segoe UI Variable
Display Semibold"), so these fonts no longer fall back to their default
instance or, for CFF2, disappear from the font list.

- FontVariations parses fvar, avar, the gvar header and HVAR, normalizes
  user coordinates and computes tuple scalars like fontTools'
  supportScalar (peak and intermediate regions).
- TrueType outlines: gvar deltas with shared and private point numbers,
  packed deltas, interpolation of untouched points (IUP), phantom points
  and the offsets of composite components.
- CFF2 outlines: 32-bit INDEX counts, the Top DICT outside an INDEX,
  FDSelect format 4, vsindex and blend with the ItemVariationStore.
- Advance widths come from HVAR, or from the phantom points without HVAR.
  They are not rounded. MVAR is not applied.

The test fonts are subsets of Source Sans 3 VF (OFL-1.1), renamed to
"VarTest Sans" because "Source" is a Reserved Font Name. Their reference
outlines and advance widths were written with fontTools.

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

* Compare the managed font reader with GDI on Windows (#412)

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

* Find a family by the name GDI cuts to 31 characters (#412)

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

* Lay out glyphs, metrics and decorations like GDI (#412)

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

* Name and measure variable instances like Windows (#412)

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

* Correct the documentation of HasOutlines and HasCffOutlines for CFF2 (#412)

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

* Test texts without frame or GDI: save, export, finalizers (#412)

Step 9 of #412 asks for headless tests in the Linux CI that create,
measure and export a text and check that the process survives the
finalizers. Creating and measuring was already tested with the font
service; these tests add:

- saving a text to JSON and reading it back, with the same extent;
- exporting a text to DXF and reading it back (string, size, location,
  and an extent measured with the managed font reader);
- running the finalizers after texts have been measured and outlined.

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

* Adapt the font cache test of #497 to the font service (#412)

ClearingTheFontCacheDisposesTheGlyphLists reflected on the 'kerning' field
of the GDI FontCache, which this branch replaces by the 'faces' of
FontProvider.Current, so it failed with a NullReferenceException on
Windows. It now checks that Clear empties the faces, and it runs on every
platform, because the font cache no longer needs GDI.

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

* Pass Unicode code points instead of chars to FontFace (#412)

FontFace.GetAdvanceWidth, GetKerning and GetGlyphPath took a char, a UTF-16
code unit. A character outside the Basic Multilingual Plane, e.g. most emoji,
is a surrogate pair of two chars in a string, so it could never reach a font:
the text went through the font char by char. They now take a code point
(int). FontFace is new public API of this PR, so this is the time to change
it; after a release, every custom font provider would break.

- Text and FontCache go through the text as code points (Text.CodePoints):
  a surrogate pair is one character, a lone surrogate stays the char it is.
- The managed face passes the code point on to the cmap, which already
  supports format 12.
- The GDI face measures a character beyond U+FFFF with
  GetTextExtentPoint32, because GetCharABCWidths only takes UTF-16 chars,
  and reads the kerning pairs as unsigned values.

The new FontCodePointTests check with a recording font provider that "a",
an emoji and "b" reach the font as three characters, with their widths,
kerning and outlines; they fail when the surrogate pair is not combined.

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

* Lock the shared sets of the parallel STEP import

With StepImport.Parallel, CreateEntity runs on several threads, but it added
mapped items to the HashSet mappedItems and, for assemblies, products to the
HashSet products without a lock (the other place that adds products locks
it). Concurrent Add calls can corrupt a HashSet, and the enumeration of
mappedItems after the creation then met a null entry: a
NullReferenceException in ImportStep.Read.

StepCyclicReferenceTest from #498 imports a file with mapped items 20 times
in parallel and showed this now and then on the 2-core CI runner. With the
test process limited to 2 cores, it failed 3 of 30 times without this change
and 0 of 60 times with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5
(cherry picked from commit 85e21f1)

* Make the GDI font provider the default in CADability.Forms (#503)

With #468 the texts get their fonts from FontProvider.Current, which is the
managed TrueType reader on every platform. To keep the texts of existing
Windows Forms applications exactly as before, CADability.Forms now makes its
GdiFontProvider the default as soon as one of its main classes is used
(CadControl, CadCanvas, CadFrame, PaintToOpenGL, PaintToGDI): their static
constructors call GdiFontProvider.MakeDefault.

The kernel keeps the managed provider as its own default, so nothing changes
for applications without CADability.Forms (the browser, other platforms,
renderers of their own). A provider the application sets through
FontProvider.Current is kept, whether it is set before or after
CADability.Forms is loaded, so an application can still choose the managed
provider on Windows.

FontProvider tells the two cases apart: the setter marks the provider as the
application's choice, the new internal FontProvider.SetDefault replaces only
a default. CADability.Forms reaches it through InternalsVisibleTo, so there
is no new public API.

Refs #412


Claude-Session: https://claude.ai/code/session_01YXvDW91oXpy7RquGHNxgR5

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
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