Skip to content

Commit 3e5f689

Browse files
Merge pull request #81 from SixLabors/js/vnext
Enhance font hinting and ANI documentation in ImageSharp
2 parents e799399 + 64c0f46 commit 3e5f689

31 files changed

Lines changed: 599 additions & 52 deletions

‎articles/fonts/colorfonts.md‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,34 @@ TextOptions options = new(font)
4444
};
4545
```
4646

47+
## Font palettes
48+
49+
COLR glyphs take their colors from the font's CPAL palette table. Many color fonts ship more than one palette, such as a default and a dark-background variant. [`TextOptions.FontPalette`](xref:SixLabors.Fonts.TextOptions.FontPalette) selects the palette that resolves the colors. Leave the value `null` to keep the font's default palette.
50+
51+
```csharp
52+
using SixLabors.Fonts;
53+
54+
Font font = SystemFonts.CreateFont("Segoe UI Emoji", 32);
55+
TextOptions options = new(font)
56+
{
57+
// Resolve COLR glyph colors with the font's second palette.
58+
// Palette indexes are zero-based; null keeps the font's default.
59+
FontPalette = new FontPalette(1)
60+
};
61+
```
62+
63+
A palette selection can also override individual entries. Each [`FontPaletteOverride`](xref:SixLabors.Fonts.FontPaletteOverride) replaces the palette color at one index:
64+
65+
```csharp
66+
// Start from the default palette, then replace entry 2 with opaque red.
67+
FontPalette palette = new(0,
68+
[
69+
new FontPaletteOverride(2, new GlyphColor(255, 0, 0, 255))
70+
]);
71+
```
72+
73+
[`TextRun.FontPalette`](xref:SixLabors.Fonts.TextRun.FontPalette) replaces the selection over a range of text. [`GlyphOptions.FontPalette`](xref:SixLabors.Fonts.GlyphOptions.FontPalette) applies the same selection when rendering or measuring by glyph ID.
74+
4775
## What happens in custom renderers
4876

4977
When a resolved glyph is a painted color glyph, Fonts streams it through [`IGlyphRenderer`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer) as one or more layers.
@@ -52,9 +80,10 @@ That means custom renderers should pay attention to:
5280

5381
- [`GlyphRendererParameters.GlyphType`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters.GlyphType)
5482
- [`BeginLayer(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginLayer*)
83+
- [`BeginGroup(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGroup*) and [`EndGroup()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndGroup*)
5584
- [`Paint`](xref:SixLabors.Fonts.Rendering.Paint)
5685
- [`FillRule`](xref:SixLabors.Fonts.Rendering.FillRule)
57-
- [`ClipQuad`](xref:SixLabors.Fonts.ClipQuad)
86+
- [`ClipBounds`](xref:SixLabors.Fonts.ClipBounds)
5887

5988
Depending on the font technology in use, the `Paint` passed to `BeginLayer(...)` may be:
6089

@@ -63,6 +92,8 @@ Depending on the font technology in use, the `Paint` passed to `BeginLayer(...)`
6392
- [`RadialGradientPaint`](xref:SixLabors.Fonts.Rendering.RadialGradientPaint)
6493
- [`SweepGradientPaint`](xref:SixLabors.Fonts.Rendering.SweepGradientPaint)
6594

95+
COLR v1 paint graphs can also form composite groups. Fonts wraps such content in [`BeginGroup(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGroup*) and [`EndGroup()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndGroup*) calls. Layers inside a group compose as one isolated unit. The finished group then blends onto the content below it with the [`CompositeMode`](xref:SixLabors.Fonts.Rendering.CompositeMode) given to `BeginGroup(...)`. A clip for the glyph arrives through [`GlyphRendererParameters.ClipBounds`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters.ClipBounds).
96+
6697
If your renderer ignores paint information, the glyph can still be drawn, but it will no longer preserve the font's intended color presentation.
6798

6899
## Inspect color glyphs directly

‎articles/fonts/customrendering.md‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
Most developers meet Fonts through [ImageSharp.Drawing](../imagesharp.drawing/index.md), where the rendering surface is already handled for you. This page is for the next step down: when you want Fonts to do the shaping and glyph decomposition, but you want to decide how those glyphs are painted or exported.
77

8-
Custom rendering in Fonts is built around [`IGlyphRenderer`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer). [`TextRenderer.RenderTextTo(...)`](xref:SixLabors.Fonts.Rendering.TextRenderer.RenderTextTo*) performs layout and shaping, then sends the result to your renderer as glyphs, layers, figures, and path commands.
8+
Custom rendering in Fonts is built around [`IGlyphRenderer`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer). [`TextRenderer.RenderTo(...)`](xref:SixLabors.Fonts.Rendering.TextRenderer.RenderTo*) performs layout and shaping, then sends the result to your renderer as glyphs, layers, figures, and path commands.
99

1010
## When to use it
1111

@@ -27,9 +27,9 @@ For monochrome outline glyphs, the path callbacks are delivered inside [`BeginGl
2727
5. `SetDecoration(...)` for any decorations requested by `EnabledDecorations()`
2828
6. `EndText()`
2929

30-
Painted color glyphs add [`BeginLayer(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginLayer*) / [`EndLayer()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndLayer*) around each painted layer between [`BeginGlyph(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGlyph*) and [`EndGlyph()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndGlyph*).
30+
Painted color glyphs add [`BeginLayer(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginLayer*) / [`EndLayer()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndLayer*) around each painted layer between [`BeginGlyph(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGlyph*) and [`EndGlyph()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndGlyph*). COLR v1 composite content adds [`BeginGroup(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGroup*) / [`EndGroup()`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.EndGroup*) around layers that must compose as one isolated unit before they blend onto the content below.
3131

32-
[`BeginGlyph(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGlyph*) receives [`GlyphRendererParameters`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters), which identify the glyph instance being rendered, including the glyph ID, the glyph's [`CodePoint`](xref:SixLabors.Fonts.Unicode.CodePoint) value, font style, point size, DPI, layout mode, and active [`TextRun`](xref:SixLabors.Fonts.TextRun). Return `false` from `BeginGlyph(...)` if you want to skip rendering that glyph.
32+
[`BeginGlyph(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginGlyph*) receives [`GlyphRendererParameters`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters), which identify the glyph instance being rendered, including the glyph ID, the glyph's [`CodePoint`](xref:SixLabors.Fonts.Unicode.CodePoint) value, font style, point size, DPI, layout mode, hinting mode, and active [`TextRun`](xref:SixLabors.Fonts.TextRun). Return `false` from `BeginGlyph(...)` if you want to skip rendering that glyph. The parameters include everything that changes glyph geometry, so a renderer can use them as a cache key.
3333

3434
## A minimal renderer
3535

@@ -57,14 +57,22 @@ public sealed class RecordingGlyphRenderer : IGlyphRenderer
5757
{
5858
}
5959

60-
public void BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds)
60+
public void BeginLayer(Paint? paint, FillRule fillRule)
6161
{
6262
}
6363

6464
public void EndLayer()
6565
{
6666
}
6767

68+
public void BeginGroup(CompositeMode mode)
69+
{
70+
}
71+
72+
public void EndGroup()
73+
{
74+
}
75+
6876
public void BeginFigure()
6977
{
7078
}
@@ -114,7 +122,7 @@ TextOptions options = new(font)
114122
};
115123

116124
RecordingGlyphRenderer renderer = new();
117-
TextRenderer.RenderTextTo(renderer, "Hello world", options);
125+
TextRenderer.RenderTo(renderer, "Hello world", options);
118126
```
119127

120128
Replace `"Segoe UI"` with any installed family that exists on your machine.

‎articles/fonts/fallbackfonts.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,28 @@ Fallback families are searched in the order you provide them.
3434
- Put emoji fonts after your normal text families unless you explicitly want them to win earlier.
3535
- Keep the fallback list as small and intentional as possible so the selection stays predictable.
3636

37+
## Resolver-based fallback
38+
39+
[`FallbackFontFamilies`](xref:SixLabors.Fonts.TextOptions.FallbackFontFamilies) is a fixed list. [`TextOptions.FontFallbackResolver`](xref:SixLabors.Fonts.TextOptions.FontFallbackResolver) adds a dynamic last step behind it. Shaping consults the resolver only for code points that neither the primary font nor any fallback family can shape. It asks at most once per distinct unresolved code point per shaping operation. Leave the value `null` to render such code points as the missing-glyph outline.
40+
41+
[`SystemFonts.FallbackResolver`](xref:SixLabors.Fonts.SystemFonts.FallbackResolver) is a built-in resolver that selects from the fonts installed on the current machine:
42+
43+
```csharp
44+
using SixLabors.Fonts;
45+
46+
Font font = SystemFonts.CreateFont("Segoe UI", 18);
47+
TextOptions options = new(font)
48+
{
49+
// Ask the operating system for a family when no configured font
50+
// can shape a code point.
51+
FontFallbackResolver = SystemFonts.FallbackResolver
52+
};
53+
```
54+
55+
Installed fonts differ between machines, so output rendered with the system resolver can differ too. Pin `FallbackFontFamilies` for output that must stay identical everywhere, and use the resolver as a safety net behind them.
56+
57+
Implement [`IFontFallbackResolver`](xref:SixLabors.Fonts.IFontFallbackResolver) for a custom policy. The resolver receives the code point, the requested family, the style, and the culture. It returns the family that contains a glyph for the code point.
58+
3759
## Mixed-script example
3860

3961
This pattern works well for text that mixes Latin, Arabic, and emoji:

‎articles/fonts/fontmetrics.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The core identity and scaling properties are:
4141
- [`Description`](xref:SixLabors.Fonts.FontMetrics.Description) for the face metadata
4242
- [`UnitsPerEm`](xref:SixLabors.Fonts.FontMetrics.UnitsPerEm) for the design-space resolution of the font
4343
- [`ScaleFactor`](xref:SixLabors.Fonts.FontMetrics.ScaleFactor) for the face-level unit-to-point scaling used by glyph metrics
44+
- [`XHeight`](xref:SixLabors.Fonts.FontMetrics.XHeight) and [`CapHeight`](xref:SixLabors.Fonts.FontMetrics.CapHeight) for the lowercase and capital letter heights in design units, or `0` when the font does not provide them
4445

4546
`UnitsPerEm` is the important anchor for understanding almost every other metric on the typeface. Values like ascenders, underline positions, or glyph advances are stored in font units and should be interpreted relative to that em square.
4647

‎articles/fonts/hinting.md‎

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# TrueType Hinting
1+
# Font Hinting
22

3-
Font hinting is the use of instructions in a font to adjust outline glyphs for a raster grid. In TrueType fonts, those instructions are bytecode programs stored in the font. The rasterizer executes them at a given size and DPI to move outline points before the glyph is drawn.
3+
Font hinting adjusts outline glyphs for a raster grid. TrueType fonts carry bytecode programs that move outline points at a given size and DPI. CFF fonts declare stem hints and alignment zones that a rasterizer fits to the grid. Fonts supports both models.
44

55
## Why Hinting Exists
66

@@ -17,12 +17,13 @@ Hinting changes the scaled outline before rasterization. It can improve:
1717

1818
The effect is most visible for small UI text at ordinary screen DPI. At larger sizes or high-resolution outputs, the outline has more pixels available and hinting usually has less visible effect.
1919

20-
## How Fonts Applies Hinting
20+
## Hinting Modes
2121

22-
[`TextOptions.HintingMode`](xref:SixLabors.Fonts.TextOptions.HintingMode) controls whether Fonts applies TrueType hinting:
22+
[`TextOptions.HintingMode`](xref:SixLabors.Fonts.TextOptions.HintingMode) controls how Fonts applies hinting:
2323

2424
- [`HintingMode.None`](xref:SixLabors.Fonts.HintingMode.None) leaves glyph outlines unhinted.
25-
- [`HintingMode.Standard`](xref:SixLabors.Fonts.HintingMode.Standard) applies the library's FreeType v40-compatible TrueType hinting behavior.
25+
- [`HintingMode.Standard`](xref:SixLabors.Fonts.HintingMode.Standard) fits outlines to the pixel grid on the vertical axis only. Horizontal placement stays smooth.
26+
- [`HintingMode.Full`](xref:SixLabors.Fonts.HintingMode.Full) fits outlines and glyph origins to the pixel grid on both axes. This reproduces the classic bi-level grid fitting that font hints were originally authored for.
2627

2728
```csharp
2829
using SixLabors.Fonts;
@@ -37,13 +38,32 @@ TextOptions options = new(font)
3738

3839
The active font size and [`Dpi`](xref:SixLabors.Fonts.TextOptions.Dpi) matter because hinting targets a specific pixels-per-em scale.
3940

40-
## Fonts' Hinting Approach
41+
## Standard Hinting
4142

42-
Fonts uses a TrueType bytecode interpreter modeled on FreeType's v40 subpixel hinting behavior. In practical terms, that means Fonts preserves full vertical TrueType instruction processing while intentionally disabling horizontal hinting.
43+
Standard hinting models FreeType's v40 subpixel hinting behavior. For TrueType fonts, it preserves full vertical instruction processing while intentionally disabling horizontal hinting. For CFF fonts, it fits the vertical axis only from the declared hints.
4344

44-
This approach is designed for modern antialiased text rendering, where horizontal subpixel placement should remain smooth and glyph advances should not be forced into old bi-level grid-fitting behavior. It gives small text the vertical alignment benefits of TrueType hinting while avoiding legacy horizontal snapping that can make spacing and shapes less consistent in modern raster output.
45+
This mode is designed for modern antialiased text rendering, where horizontal subpixel placement remains smooth and glyph advances keep their fractional shaped values. It gives small text the vertical alignment benefits of hinting while avoiding legacy horizontal snapping that can make spacing less consistent.
4546

46-
When hinting is active, Fonts:
47+
## Full Hinting
48+
49+
Full hinting reproduces the classic rasterizer pipeline. It produces the sharpest results at small sizes, at the cost of shape fidelity and spacing regularity.
50+
51+
Under full hinting, Fonts:
52+
53+
- rounds the em size to a whole number of pixels, with a minimum of one pixel
54+
- executes the complete horizontal and vertical TrueType instruction set, or fits CFF outlines on both axes
55+
- aligns glyph origins and text decorations to the pixel grid on both axes
56+
- replaces fractional advances on the flow axis with whole-pixel advances
57+
58+
For TrueType fonts, the whole-pixel advance comes from the font's `hdmx` device record when the font carries one. Otherwise Fonts uses the hinted advance that instruction execution produced, or the design advance rounded to whole pixels for glyphs the interpreter could not hint. CFF fonts carry no device advance records, so the design advance rounds to whole pixels.
59+
60+
Whole-pixel advances change spacing. Fractional positioning adjustments on the flow axis, such as pair kerning, are dropped because the classic pipeline never applied them. Inter-glyph spacing can vary by up to half a pixel from the unhinted design. Measurement uses the same advances as rendering, so measured layout and rendered output stay in agreement.
61+
62+
Two cases keep their shaped fractional advances: subscript and superscript runs, and upright glyphs in vertical layout.
63+
64+
## The TrueType Pipeline
65+
66+
Fonts uses a TrueType bytecode interpreter. When hinting is active, Fonts:
4767

4868
- executes the font program from `fpgm` to initialize TrueType function definitions
4969
- scales the Control Value Table from `cvt ` for the current size and DPI
@@ -54,27 +74,32 @@ When hinting is active, Fonts:
5474
- executes each glyph's TrueType instructions against the resolved outline
5575
- leaves the outline unhinted if a glyph has no instructions, hinting is inhibited by the font program, or instruction execution fails
5676

57-
## TrueType Scope
77+
Some fonts are known to require hinting for correct rendering, and some are known to render badly with it. Fonts maintains compatibility lists that force such TrueType fonts to full hinting or to no hinting, regardless of the requested mode.
78+
79+
## The CFF Pipeline
5880

59-
Fonts applies this hinting path to TrueType outlines. It does not turn CFF or CFF2 outlines into hinted TrueType outlines, and it does not change which glyphs are selected for the text.
81+
CFF outlines carry no instructions. Instead, the font declares the regularities in its design:
6082

61-
Within the TrueType path, Fonts supports:
83+
- horizontal and vertical stem hints in each glyph's charstring
84+
- hint masks and counter masks that control which stems apply in each region of the glyph
85+
- alignment zones, standard stem widths, and related values in the Private DICT
6286

63-
- TrueType glyph instruction execution.
64-
- Standard TrueType hinting tables such as `fpgm`, `prep`, and `cvt `.
65-
- Per-glyph hinting at the active size and DPI.
66-
- `cvar`-driven control-value adjustments for variable TrueType fonts before hinting runs.
67-
- Hinted contour-point resolution for GPOS anchor data when a font uses contour-point anchors.
68-
- Font-specific compatibility behavior for fonts known to require hinting.
87+
When hinting is active, Fonts collects these declared hints while it evaluates the charstring, then fits the outline through a hint map. Standard hinting fits the vertical axis only, from the declared horizontal stem zones and alignment-zone flats. Full hinting fits both axes.
6988

70-
## When to Enable It
89+
## Choosing a Mode
7190

72-
Use [`HintingMode.Standard`](xref:SixLabors.Fonts.HintingMode.Standard) when rendering small TrueType UI text to a raster target and you want grid-fitted outlines.
91+
Use [`HintingMode.Standard`](xref:SixLabors.Fonts.HintingMode.Standard) when rendering small UI text to a raster target and you want grid-fitted outlines with smooth, fractional spacing.
92+
93+
Use [`HintingMode.Full`](xref:SixLabors.Fonts.HintingMode.Full) when you want the sharpest possible small text and accept the classic-rasterizer tradeoffs: whole-pixel spacing, dropped fractional kerning, and altered glyph shapes. It is also the mode to compare output against classic bi-level rasterizers.
7394

7495
Use [`HintingMode.None`](xref:SixLabors.Fonts.HintingMode.None) when you want raw outline behavior, when you are rendering large display text, or when the text is being treated as artwork rather than screen UI.
7596

7697
There is no universal best setting. Hinting is a raster-quality tradeoff: it can make small text clearer, but it can also move outlines away from their pure scaled design.
7798

99+
## Custom Renderers
100+
101+
The requested hinting mode is part of [`GlyphRendererParameters`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters). A renderer that caches glyph geometry can include the parameters in its cache key, so outlines fitted under one mode are never reused under another.
102+
78103
## Common Misunderstandings
79104

80105
Hinting does not:

‎articles/fonts/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ If you are new to Fonts, start with [Loading Fonts and Collections](gettingstart
107107
- [Text Layout and Options](textlayout.md)
108108
- [OpenType Features](opentypefeatures.md)
109109
- [Text Shaping](shaping.md)
110-
- [TrueType Hinting](hinting.md)
110+
- [Font Hinting](hinting.md)
111111
- [Color Fonts](colorfonts.md)
112112
- [Unicode, Code Points, and Graphemes](unicode.md)
113113
- [Fallback Fonts and Multilingual Text](fallbackfonts.md)

0 commit comments

Comments
 (0)