Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
be8329f
Add a managed TrueType reader for text on every platform (#412)
claude Oct 15, 2026
cb05b5b
Get the fonts of texts from a font service of the kernel instead of G…
claude Oct 15, 2026
ec20031
Add a Windows CI workflow that builds and tests with GDI+ and WinForm…
claude Oct 15, 2026
c7225e2
Make the DXF dimension tests independent of the line endings of the c…
claude Oct 15, 2026
fc5326e
Read the CFF outlines of OpenType fonts in the managed font reader (#…
claude Oct 15, 2026
5e7a0be
Read variable fonts: gvar, CFF2, avar and HVAR (#412)
claude Oct 15, 2026
4d939ef
Compare the managed font reader with GDI on Windows (#412)
claude Oct 15, 2026
e08061f
Find a family by the name GDI cuts to 31 characters (#412)
claude Oct 15, 2026
318a1ed
Lay out glyphs, metrics and decorations like GDI (#412)
claude Oct 15, 2026
191927d
Name and measure variable instances like Windows (#412)
claude Oct 15, 2026
b30fcd0
Correct the documentation of HasOutlines and HasCffOutlines for CFF2 …
claude Oct 15, 2026
1d19182
Merge branch 'feature/variable-fonts' into feature/gdi-glyph-comparison
claude Oct 15, 2026
45b1a61
Keep the images of pictures and icons as image file bytes (#412)
claude Oct 15, 2026
10f0d2d
Test files with pictures written on .NET Framework 4.8 (#412)
claude Oct 15, 2026
cfba619
Save page settings as a neutral PageSetup (#412)
claude Oct 15, 2026
0e2982f
Use a relative path in the path fallback test (#412)
claude Oct 15, 2026
cc470a1
Merge branch 'feature/picture-encoded-images' into feature/neutral-pa…
claude Oct 15, 2026
3bf4b6e
Import all CSS named colors from SVG (#412)
claude Oct 15, 2026
a4de85a
Test texts without frame or GDI: save, export, finalizers (#412)
claude Oct 15, 2026
65fd037
Test pictures without frame or GDI: measure, export, finalizers (#412)
claude Oct 15, 2026
dccb145
Merge remote-tracking branch 'origin/master' into feature/picture-enc…
claude Oct 16, 2026
238a773
Merge branch 'feature/picture-encoded-images' into feature/neutral-pa…
claude Oct 16, 2026
e8a51eb
Merge branch 'feature/neutral-page-setup' into test/headless-picture-…
claude Oct 16, 2026
9ef832f
Sample BSpline2D.AddToGraphicsPath on 0..1 (#412)
claude Oct 16, 2026
72b4ecd
Merge remote-tracking branch 'origin/test/headless-text-export' into …
claude Oct 16, 2026
5a44ee0
Merge remote-tracking branch 'origin/test/headless-picture-export' in…
claude Oct 16, 2026
2a595e5
Merge remote-tracking branch 'origin/ci/windows-build-and-test' into …
claude Oct 16, 2026
85e2281
Merge remote-tracking branch 'origin/test/svg-css-colors' into integr…
claude Oct 16, 2026
28591f9
Merge remote-tracking branch 'origin/fix/bspline2d-graphicspath' into…
claude Oct 16, 2026
9392945
Keep Picture.Clip without [Obsolete] (#412)
claude Oct 16, 2026
2319a01
Merge branch 'feature/picture-encoded-images' into feature/neutral-pa…
claude Oct 16, 2026
293c493
Keep the printing members without [Obsolete] (#412)
claude Oct 16, 2026
072f639
Merge branch 'feature/neutral-page-setup' into test/headless-picture-…
claude Oct 16, 2026
188d4a7
Merge remote-tracking branch 'origin/test/headless-picture-export' in…
claude Oct 16, 2026
3f1eb39
Merge master into feature/font-service
claude Oct 17, 2026
415fea4
Merge branch 'feature/font-service' into feature/cff-outlines
claude Oct 17, 2026
9a17a7b
Merge branch 'feature/cff-outlines' into feature/variable-fonts
claude Oct 17, 2026
95efb36
Merge branch 'feature/variable-fonts' into feature/gdi-glyph-comparison
claude Oct 17, 2026
020713a
Merge branch 'feature/gdi-glyph-comparison' into test/headless-text-e…
claude Oct 17, 2026
21ffff8
Merge branch 'test/headless-text-export' into integration/issue-412
claude Oct 17, 2026
ce437ab
Merge remote-tracking branch 'origin/master' into ci/windows-build-an…
claude Oct 17, 2026
970a5ee
Disambiguate PaintEventArgs in RendererRecoveryTests for the Windows …
claude Oct 17, 2026
113fa60
Merge branch 'ci/windows-build-and-test' into integration/issue-412
claude Oct 17, 2026
8d22a3f
Adapt the font cache test of #497 to the font service (#412)
claude Oct 17, 2026
8e4900a
Merge branch 'feature/font-service' into feature/cff-outlines
claude Oct 17, 2026
1e56c2e
Merge branch 'feature/cff-outlines' into feature/variable-fonts
claude Oct 17, 2026
1496f3b
Merge branch 'feature/variable-fonts' into feature/gdi-glyph-comparison
claude Oct 17, 2026
d90108c
Merge branch 'feature/gdi-glyph-comparison' into test/headless-text-e…
claude Oct 17, 2026
3b21a94
Merge branch 'test/headless-text-export' into integration/issue-412
claude Oct 17, 2026
6cd5777
Merge remote-tracking branch 'origin/master' into integration/issue-412
claude Oct 19, 2026
83debf3
Merge remote-tracking branch 'origin/master' into feature/managed-fon…
claude Oct 19, 2026
09dab93
Merge remote-tracking branch 'origin/master' into feature/picture-enc…
claude Oct 19, 2026
830f757
Merge remote-tracking branch 'origin/feature/managed-font-provider' i…
claude Oct 19, 2026
3163749
Pass Unicode code points instead of chars to FontFace (#412)
claude Oct 19, 2026
3e640f2
Merge branch 'feature/font-service' into feature/cff-outlines
claude Oct 19, 2026
8a07d38
Merge branch 'feature/cff-outlines' into feature/variable-fonts
claude Oct 19, 2026
23aa919
Merge branch 'feature/variable-fonts' into feature/gdi-glyph-comparison
claude Oct 19, 2026
cacc4d0
Merge branch 'feature/gdi-glyph-comparison' into test/headless-text-e…
claude Oct 19, 2026
2d09d7b
Merge branch 'test/headless-text-export' into integration/issue-412
claude Oct 19, 2026
dc316e2
Merge remote-tracking branch 'origin/feature/picture-encoded-images' …
claude Oct 19, 2026
ac0ad17
Lock the shared sets of the parallel STEP import
claude Oct 19, 2026
c54ed31
Merge branch 'feature/managed-font-provider' into feature/font-service
claude Oct 19, 2026
5b325e3
Merge branch 'feature/font-service' into feature/cff-outlines
claude Oct 19, 2026
6b5e200
Merge branch 'feature/cff-outlines' into feature/variable-fonts
claude Oct 19, 2026
b846857
Lock the shared sets of the parallel STEP import
claude Oct 19, 2026
cfd97de
Merge branch 'feature/variable-fonts' into feature/gdi-glyph-comparison
claude Oct 19, 2026
a81efbe
Merge branch 'feature/picture-encoded-images' into feature/neutral-pa…
claude Oct 19, 2026
0c867f0
Merge branch 'feature/neutral-page-setup' into test/headless-picture-…
claude Oct 19, 2026
9d4fd4b
Merge branch 'feature/gdi-glyph-comparison' into test/headless-text-e…
claude Oct 19, 2026
ce5dc5b
Merge branches 'test/headless-text-export' and 'test/headless-picture…
claude Oct 19, 2026
3ad812f
Synthesize a missing bold or italic face like GDI+ (#412)
claude Oct 19, 2026
15e2555
Merge branch 'feature/synthetic-bold-italic' into integration/issue-412
claude Oct 19, 2026
0565bb0
Make the quadratic TrueType outline wider for synthetic bold
claude Oct 19, 2026
ee067e1
Keep the synthetic bold shift within half the strength per axis like …
claude Oct 19, 2026
5151a54
Compare Sylfaen and Gabriola with GDI+ within 2% of the em height
claude Oct 19, 2026
51eadd9
Merge branch 'feature/synthetic-bold-italic' into integration/issue-412
claude Oct 19, 2026
b23e6bb
Make the GDI font provider the default in CADability.Forms
claude Oct 19, 2026
8e84ce1
Merge branch 'feature/gdi-fonts-in-forms' into integration/issue-412
claude Oct 19, 2026
cac8016
Make the GDI font provider the default in CADability.Forms (#503)
dsn27 Oct 19, 2026
3ba579f
Merge branch 'feature/gdi-glyph-comparison' into integration/issue-412
claude Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CADability.Forms/CadCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private static CADability.Substitutes.PaintEventArgs Subst(System.Windows.Forms.
private string currenToolTip;
private string currentCursor;
private Action<int> callbackCollapsed;
// texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider
static CadCanvas() { GdiFontProvider.MakeDefault(); }
public CadCanvas() : base()
{
toolTip = new ToolTip();
Expand Down
2 changes: 2 additions & 0 deletions CADability.Forms/CadControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public partial class CadControl : UserControl, ICommandHandler

#endregion PRIVATE FIELDS

// texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider
static CadControl() { GdiFontProvider.MakeDefault(); }
/// <summary>
/// Default constructor
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions CADability.Forms/CadFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ namespace CADability.Forms
/// </summary>
public class CadFrame : FrameImpl, IUIService
{
// texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider
static CadFrame() { GdiFontProvider.MakeDefault(); }

#region PRIVATE FIELDS

private ICommandHandler commandHandler;
Expand Down
239 changes: 239 additions & 0 deletions CADability.Forms/GdiFontProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace CADability.Forms
{
/// <summary>
/// A <see cref="FontProvider"/> that gets the fonts from GDI and GDI+, as CADability did before issue #412: the glyph
/// outlines from <see cref="GraphicsPath.AddString(string, FontFamily, int, float, PointF, StringFormat)"/>, the widths
/// and the kerning from gdi32. It knows every font Windows knows (also OpenType fonts with CFF outlines) and
/// synthesizes bold and italic faces a family does not have. As soon as one of the main classes of CADability.Forms is
/// used (<see cref="CadControl"/>, <see cref="CadCanvas"/>, <see cref="CadFrame"/>, <see cref="PaintToOpenGL"/> or
/// <see cref="PaintToGDI"/>), this provider becomes the default, so that Windows Forms applications show their texts
/// as before. An application that sets <see cref="FontProvider.Current"/> itself keeps its choice, e.g.
/// <c>FontProvider.Current = new ManagedFontProvider();</c> for the same fonts as on the other platforms.
/// </summary>
public class GdiFontProvider : FontProvider
{
private static int madeDefault;
/// <summary>
/// Makes a <see cref="GdiFontProvider"/> the default font provider, unless the application has chosen one. Called by the
/// static constructors of the main classes of CADability.Forms; only the first call does something.
/// </summary>
internal static void MakeDefault()
{
if (Interlocked.Exchange(ref madeDefault, 1) == 0) SetDefault(() => new GdiFontProvider());
}

private readonly Dictionary<(string family, bool bold, bool italic), GdiFontFace> faces = new Dictionary<(string, bool, bool), GdiFontFace>();
private HashSet<string> familyNames; // upper case

/// <inheritdoc/>
public override string[] GetFamilyNames()
{
return FontFamily.Families.Select(f => f.Name).ToArray();
}
/// <inheritdoc/>
public override FontFace GetFace(string familyName, bool bold, bool italic)
{
if (string.IsNullOrEmpty(familyName)) return null;
lock (faces)
{
if (familyNames == null) familyNames = new HashSet<string>(GetFamilyNames().Select(n => n.ToUpperInvariant()));
if (!familyNames.Contains(familyName.ToUpperInvariant())) return null;
if (!faces.TryGetValue((familyName.ToUpperInvariant(), bold, italic), out GdiFontFace face))
{
face = new GdiFontFace(new FontFamily(familyName), bold, italic);
faces[(familyName.ToUpperInvariant(), bold, italic)] = face;
}
return face;
}
}
/// <summary>
/// The generic sans serif family of GDI+, as before issue #412.
/// </summary>
public override FontFace GetDefaultFace(bool bold, bool italic)
{
using (FontFamily sansSerif = new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif))
{
return GetFace(sansSerif.Name, bold, italic);
}
}
}

/// <summary>
/// A face of the <see cref="GdiFontProvider"/>.
/// </summary>
internal sealed class GdiFontFace : FontFace
{
private readonly FontFamily family;
private readonly FontStyle style; // bold and italic only
private readonly int em;
private readonly Dictionary<int, double> widths = new Dictionary<int, double>();
private Dictionary<(int, int), double> kerning;

public GdiFontFace(FontFamily family, bool bold, bool italic)
{
this.family = family;
FontStyle requested = (bold ? FontStyle.Bold : FontStyle.Regular) | (italic ? FontStyle.Italic : FontStyle.Regular);
style = requested;
if (!family.IsStyleAvailable(style))
{
if (family.IsStyleAvailable(FontStyle.Regular)) style = FontStyle.Regular;
else if (family.IsStyleAvailable(FontStyle.Bold)) style = FontStyle.Bold;
else if (family.IsStyleAvailable(FontStyle.Italic)) style = FontStyle.Italic;
}
em = family.GetEmHeight(style);
if (em == 0) em = 1000;
}
public override string FamilyName => family.Name;
public override bool IsBold => (style & FontStyle.Bold) != 0;
public override bool IsItalic => (style & FontStyle.Italic) != 0;
public override int EmHeight => em;
public override int CellAscent => family.GetCellAscent(style);
public override int CellDescent => family.GetCellDescent(style);
public override int LineSpacing => family.GetLineSpacing(style);

public override double GetAdvanceWidth(int codePoint)
{
lock (widths)
{
if (widths.TryGetValue(codePoint, out double width)) return width;
WithSelectedFont(hdc =>
{
if (codePoint <= 0xFFFF)
{
ABC[] abc = new ABC[1];
GetCharABCWidths(hdc, (uint)codePoint, (uint)codePoint, abc);
width = (abc[0].abcA + abc[0].abcB + abc[0].abcC) / (double)em;
}
else
{ // GetCharABCWidths only takes UTF-16 chars: measure the surrogate pair as a string
string s = char.ConvertFromUtf32(codePoint);
GetTextExtentPoint32(hdc, s, s.Length, out SIZE size);
width = size.cx / (double)em;
}
});
widths[codePoint] = width;
return width;
}
}
public override double GetKerning(int left, int right)
{
lock (widths)
{
if (kerning == null)
{
Dictionary<(int, int), double> pairs = new Dictionary<(int, int), double>();
WithSelectedFont(hdc =>
{
int num = GetKerningPairs(hdc, 0, null);
if (num > 0)
{
KERNINGPAIR[] kp = new KERNINGPAIR[num];
GetKerningPairs(hdc, num, kp);
for (int i = 0; i < kp.Length; ++i) pairs[((ushort)kp[i].wFirst, (ushort)kp[i].wSecond)] = kp[i].iKernAmount / (double)em;
}
});
kerning = pairs;
}
kerning.TryGetValue((left, right), out double res);
return res;
}
}
public override void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types)
{
FontStyle fs = style | (underline ? FontStyle.Underline : FontStyle.Regular) | (strikeout ? FontStyle.Strikeout : FontStyle.Regular);
using (GraphicsPath path = new GraphicsPath())
using (StringFormat sf = (StringFormat)StringFormat.GenericTypographic.Clone())
{
sf.LineAlignment = StringAlignment.Near;
sf.Alignment = StringAlignment.Near;
path.AddString(AsString(codePoint), family, (int)fs, 1.0f, new PointF(0.0f, 0.0f), sf);
if (path.PointCount == 0)
{
points = new PointF[0];
types = new byte[0];
}
else
{
points = path.PathPoints;
types = path.PathTypes;
}
}
}

// a lone surrogate (not a valid code point) is passed on as the char it is
private static string AsString(int codePoint) => codePoint >= 0xD800 && codePoint <= 0xDFFF ? ((char)codePoint).ToString() : char.ConvertFromUtf32(codePoint);

/// <summary>
/// Selects this face with the em height as pixel size into a device context for the duration of <paramref name="action"/>.
/// The device context only lives during the call, so nothing is left to a finalizer.
/// </summary>
private void WithSelectedFont(Action<IntPtr> action)
{
IntPtr hdc = CreateCompatibleDC(IntPtr.Zero);
try
{
using (Font font = new Font(family, em, style, GraphicsUnit.Pixel))
{
IntPtr hfont = font.ToHfont();
IntPtr oldFont = SelectObject(hdc, hfont);
try
{
action(hdc);
}
finally
{
SelectObject(hdc, oldFont);
DeleteObject(hfont);
}
}
}
finally
{
DeleteDC(hdc);
}
}

[StructLayout(LayoutKind.Sequential)]
private struct ABC
{
public int abcA;
public uint abcB;
public int abcC;
}
[StructLayout(LayoutKind.Sequential)]
private struct KERNINGPAIR
{
public short wFirst;
public short wSecond;
public int iKernAmount;
}
[DllImport("gdi32.dll", EntryPoint = "GetCharABCWidthsW")]
private static extern bool GetCharABCWidths(IntPtr hdc, uint uFirstChar, uint uLastChar, [Out] ABC[] lpabc);
[StructLayout(LayoutKind.Sequential)]
private struct SIZE
{
public int cx;
public int cy;
}
[DllImport("gdi32.dll", EntryPoint = "GetTextExtentPoint32W", CharSet = CharSet.Unicode)]
private static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int c, out SIZE psizl);
[DllImport("gdi32.dll", EntryPoint = "GetKerningPairsW")]
private static extern int GetKerningPairs(IntPtr hdc, int cPairs, [Out] KERNINGPAIR[] lpkrnpair);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
}
}
2 changes: 2 additions & 0 deletions CADability.Forms/PaintToGDI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public void Dispose()
LazyInitializer.EnsureInitialized(ref fontFamilyNames, () =>
new HashSet<string>(FontFamily.Families.Select(f => f.Name.ToUpper())));

// texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider
static PaintToGDI() { GdiFontProvider.MakeDefault(); }
public PaintToGDI(Projection projection, Graphics graphics)
: this()
{
Expand Down
3 changes: 3 additions & 0 deletions CADability.Forms/PaintToOpenGL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal PaintToOpenGLException(String msg)
/// </summary>
public class PaintToOpenGL : IPaintTo3D, IPaintTo3DFlatText
{
// texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider
static PaintToOpenGL() { GdiFontProvider.MakeDefault(); }

#region IPaintTo3D data
public static int debugNumTriangles = 0;
public static Thread MainThread = null;
Expand Down
Loading