Skip to content

Commit a145be0

Browse files
Merge pull request #76 from SixLabors/js/v4
Update to latest major releases
2 parents 50ad50a + c2cf654 commit a145be0

136 files changed

Lines changed: 14296 additions & 2557 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ on:
99
- main
1010

1111
jobs:
12-
prose:
13-
runs-on: ubuntu-latest
14-
steps:
15-
- name: Checkout
16-
uses: actions/checkout@v6
12+
# prose:
13+
# runs-on: ubuntu-latest
14+
# steps:
15+
# - name: Checkout
16+
# uses: actions/checkout@v6
1717

18-
- name: Vale
19-
uses: errata-ai/vale-action@reviewdog
20-
with:
21-
files: articles/.
22-
env:
23-
# Required
24-
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
18+
# - name: Vale
19+
# uses: errata-ai/vale-action@reviewdog
20+
# with:
21+
# files: articles/.
22+
# env:
23+
# # Required
24+
# GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
2525

2626
deploy:
27-
needs: [prose]
27+
# needs: [prose]
2828

2929
runs-on: windows-latest
3030

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@
1414
path = ext/Fonts
1515
url = https://github.com/SixLabors/Fonts
1616
ignore = dirty
17+
[submodule "ext/PolygonClipper"]
18+
path = ext/PolygonClipper
19+
url = https://github.com/SixLabors/PolygonClipper
20+
ignore = dirty

api/index.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
# API Documentation
22

3-
The API documentation is automatically generated from source-code-level comments. Often, more information can be found by looking into the [source code](https://github.com/sixlabors) itself.
3+
The API documentation is generated from the public source comments for the Six Labors libraries. Use it when you need exact type names, overloads, constructor signatures, enum values, default behavior, inherited members, and namespace-level navigation.
4+
5+
These reference pages are designed to sit alongside the article guides. Start with the articles when you are learning a feature or choosing an approach, then use the API reference when you need the precise member contract for implementation work.
6+
7+
The API reference covers the libraries documented on this site, including ImageSharp, ImageSharp.Drawing, ImageSharp.Web, Fonts, and PolygonClipper. Each product area is grouped by namespace so you can move from high-level entry points, such as image processing extensions or drawing canvas APIs, down to the supporting options, primitives, and model types.
8+
9+
When a reference page does not answer a design question, check the matching article section first. The source repositories remain available on [GitHub](https://github.com/sixlabors) for contributors and for cases where you need to inspect implementation details that are intentionally not part of the public API contract.

api/toc.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
- name: ImageSharp.Web
66
href: ImageSharp.Web/SixLabors.ImageSharp.Web.yml
77
- name: ImageSharp.Web.Providers.Azure
8-
href: ImageSharp.Web.Providers.Azure/SixLabors.ImageSharp.Web.Providers.Azure.yml
8+
href: ImageSharp.Web.Providers.Azure/SixLabors.ImageSharp.Web.Azure.Resolvers.yml
99
- name: ImageSharp.Web.Providers.AWS
10-
href: ImageSharp.Web.Providers.AWS/SixLabors.ImageSharp.Web.Providers.AWS.yml
10+
href: ImageSharp.Web.Providers.AWS/SixLabors.ImageSharp.Web.AWS.yml
1111
- name: Fonts
1212
href: Fonts/SixLabors.Fonts.yml
13+
- name: PolygonClipper
14+
href: PolygonClipper/SixLabors.PolygonClipper.yml
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Selection and Bidi Drag
2+
3+
Once you can hit-test a point and place a caret, the next step is painting selection ranges. Fonts returns selection geometry as a list of rectangles in visual order so editor-style UIs can paint browser-shaped selections without reimplementing bidi or line-box rules.
4+
5+
For the underlying types — [`TextHit`](xref:SixLabors.Fonts.TextHit), [`CaretPosition`](xref:SixLabors.Fonts.CaretPosition), [`CaretPlacement`](xref:SixLabors.Fonts.CaretPlacement), and [`CaretMovement`](xref:SixLabors.Fonts.CaretMovement) — see [Hit Testing and Caret Movement](texthittesting.md).
6+
7+
### The shape of a selection
8+
9+
[`GetSelectionBounds(...)`](xref:SixLabors.Fonts.TextMetrics.GetSelectionBounds*) returns `ReadOnlyMemory<FontRectangle>`. Use `.Span` when drawing, and store the memory itself if the selection needs to be retained alongside other layout state.
10+
11+
```csharp
12+
using System;
13+
using SixLabors.Fonts;
14+
15+
ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(anchor, focus);
16+
17+
foreach (FontRectangle rectangle in selection.Span)
18+
{
19+
FillSelectionRectangle(rectangle);
20+
}
21+
```
22+
23+
A single logical selection can be visually discontinuous inside one line when it crosses bidi runs. Returning multiple rectangles allows browser-style selection where the unselected visual gap stays unpainted.
24+
25+
Do not sort, union, or merge the returned rectangles unless the UI explicitly wants a different visual.
26+
27+
### Pointer selection
28+
29+
For pointer drags, hit-test both endpoints and pass the hits to the selection API. The [`TextHit`](xref:SixLabors.Fonts.TextHit) overload converts both endpoints to logical insertion indices for you.
30+
31+
```csharp
32+
using System.Numerics;
33+
using SixLabors.Fonts;
34+
35+
TextHit anchor = metrics.HitTest(new Vector2(downX, downY));
36+
TextHit focus = metrics.HitTest(new Vector2(moveX, moveY));
37+
38+
ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(anchor, focus);
39+
```
40+
41+
This keeps trailing-edge and bidi handling inside the library.
42+
43+
### Keyboard selection
44+
45+
For keyboard selection, keep an anchor caret fixed and move the focus caret. Shift+Right-style behavior updates only the focus caret.
46+
47+
```csharp
48+
using SixLabors.Fonts;
49+
50+
CaretPosition anchor = metrics.GetCaret(CaretPlacement.Start);
51+
CaretPosition focus = anchor;
52+
53+
focus = metrics.MoveCaret(focus, CaretMovement.Next);
54+
55+
ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(anchor, focus);
56+
```
57+
58+
Selecting whole words via keyboard is the same shape: move the focus by `NextWord` or `PreviousWord`.
59+
60+
### Word selection
61+
62+
For double-click word selection, find the word containing the hit and ask for its selection bounds.
63+
64+
```csharp
65+
using SixLabors.Fonts;
66+
67+
TextHit hit = metrics.HitTest(doubleClickPosition);
68+
WordMetrics word = metrics.GetWordMetrics(hit);
69+
70+
ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(word);
71+
```
72+
73+
The [`GraphemeMetrics`](xref:SixLabors.Fonts.GraphemeMetrics) overload selects exactly one grapheme, which is useful for caret-region overlays:
74+
75+
```csharp
76+
using SixLabors.Fonts;
77+
78+
GraphemeMetrics grapheme = metrics.GraphemeMetrics[index];
79+
ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(grapheme);
80+
```
81+
82+
### Bidi drag selection
83+
84+
Consider a left-to-right paragraph whose source text is:
85+
86+
```text
87+
Tall שלום עرب
88+
```
89+
90+
The right-to-left run can paint with Arabic before Hebrew. When a user drags from the left edge of `Tall` toward the Hebrew word, the visual selection can become split:
91+
92+
```text
93+
[Tall ] עرب [שלום]
94+
```
95+
96+
Application code should not manually decide which physical edge of the Hebrew glyph means "before" or "after". The hit-test result already carries the logical insertion index, and the selection result is already split into the visual rectangles that should be painted.
97+
98+
```csharp
99+
using SixLabors.Fonts;
100+
101+
TextHit anchor = metrics.HitTest(mouseDown);
102+
TextHit focus = metrics.HitTest(mouseMove);
103+
104+
ReadOnlyMemory<FontRectangle> rectangles = metrics.GetSelectionBounds(anchor, focus);
105+
```
106+
107+
Just paint every rectangle. The library produces the correct visual gaps.
108+
109+
### Hard line breaks
110+
111+
Hard line breaks that end non-empty lines are trimmed with trailing breaking whitespace. Hard line breaks that own a blank line remain in the metrics and contribute their own selection rectangle so the blank line still highlights when the selection crosses it.
112+
113+
For text with two hard breaks in the middle:
114+
115+
```text
116+
Tall عرب שלום
117+
118+
Small مرحبا שלום
119+
```
120+
121+
A full selection paints three visual rows: the first text line, the blank line, and the second text line. The line break that ends a non-empty line does not add a separate painted box; the line break that owns the blank line does. Callers should not special-case this — paint the rectangles `GetSelectionBounds` returns.
122+
123+
Consumers that inspect individual graphemes can use [`GraphemeMetrics.IsLineBreak`](xref:SixLabors.Fonts.GraphemeMetrics.IsLineBreak) to identify the blank-line hard breaks that remain in the metrics.
124+
125+
In `TextInteractionMode.Editor`, a hard break that ends the text produces an additional blank line so a selection can extend past the final newline; `TextInteractionMode.Paragraph` omits that trailing blank line. See [Hit Testing and Caret Movement](texthittesting.md) for the full mode comparison.
126+
127+
### Per-line selection
128+
129+
[`LineLayout`](xref:SixLabors.Fonts.LineLayout) exposes the same selection overloads when the caller knows the selection is line-local:
130+
131+
```csharp
132+
using SixLabors.Fonts;
133+
134+
LineLayout line = layouts.Span[lineIndex];
135+
136+
ReadOnlyMemory<FontRectangle> selection = line.GetSelectionBounds(anchor, focus);
137+
ReadOnlyMemory<FontRectangle> wordSelection = line.GetSelectionBounds(word);
138+
```
139+
140+
Use the full [`TextMetrics`](xref:SixLabors.Fonts.TextMetrics) overloads for selections that can cross line boundaries; use [`LineLayout`](xref:SixLabors.Fonts.LineLayout) only when interaction is bounded to one line.
141+
142+
### Stable line-box geometry
143+
144+
Per-line selection uses the line-box height rather than per-glyph height, which matches normal text editor and browser behavior: selecting mixed font sizes on the same line paints a consistent line-height rectangle rather than one rectangle per glyph height. The selection geometry stays visually stable across mixed fonts and font sizes.
145+
146+
For a wider tour of the measurement model and how line metrics are derived, see [Measuring Text](measuringtext.md).
147+
148+
### Practical guidance
149+
150+
- Paint the selection rectangles returned by the API instead of reconstructing selection geometry yourself.
151+
- Keep anchor and focus as logical text positions; let the metrics map them into visual rectangles.
152+
- Use editor interaction mode when selections must include terminal blank lines.
153+
- Test mixed LTR/RTL selections with real strings, not only simple Latin text.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Check Glyph Coverage Before Choosing Fallbacks
2+
3+
Before you wire up fallback families, it helps to know what your primary font can already cover. This recipe shows a quick way to probe individual scalar values with [`Font.TryGetGlyphs(...)`](xref:SixLabors.Fonts.Font.TryGetGlyphs*) or scan a string so you can make fallback decisions based on actual glyph coverage instead of guesswork.
4+
5+
### Check individual code points
6+
7+
```csharp
8+
using SixLabors.Fonts;
9+
using SixLabors.Fonts.Unicode;
10+
11+
Font font = SystemFonts.CreateFont("Segoe UI", 16);
12+
13+
bool hasLatinA = font.TryGetGlyphs(new CodePoint('A'), out _);
14+
bool hasOmega = font.TryGetGlyphs(new CodePoint(0x03A9), out _); // Ω GREEK CAPITAL LETTER OMEGA
15+
bool hasEmoji = font.TryGetGlyphs(new CodePoint(0x1F600), out _); // 😀 GRINNING FACE
16+
```
17+
18+
### Scan a whole string for missing glyphs
19+
20+
```csharp
21+
using System.Collections.Generic;
22+
using SixLabors.Fonts;
23+
using SixLabors.Fonts.Unicode;
24+
25+
string text = "Hello 123 مرحبا 😀";
26+
Font font = SystemFonts.CreateFont("Segoe UI", 16);
27+
List<CodePoint> missing = new();
28+
29+
foreach (CodePoint codePoint in text.AsSpan().EnumerateCodePoints())
30+
{
31+
if (!font.TryGetGlyphs(codePoint, out _))
32+
{
33+
missing.Add(codePoint);
34+
}
35+
}
36+
```
37+
38+
This is a simple way to decide whether you need `FallbackFontFamilies` before you measure or render the text.
39+
40+
If you want a broader face-level view instead of checking a specific string, use [`Font.FontMetrics.GetAvailableCodePoints()`](xref:SixLabors.Fonts.FontMetrics.GetAvailableCodePoints*).
41+
42+
Glyph coverage is only the first question. A font can contain glyphs for individual code points but still lack the shaping behavior, marks, variation sequences, or color glyph data needed for the text to look right in a real script. Use coverage checks to choose candidate fallback families, then measure or render with the same `TextOptions` you will use in production.
43+
44+
Emoji and complex scripts are the usual cases where this distinction matters. A visible emoji can be a grapheme made from several code points, and Arabic, Indic, or Southeast Asian scripts can require shaping features that are not captured by a one-code-point probe.
45+
46+
For the conceptual fallback guidance, see [Fallback Fonts and Multilingual Text](fallbackfonts.md). For face-level coverage inspection, see [Font Metrics](fontmetrics.md).
47+
48+
### Practical guidance
49+
50+
- Use coverage checks to choose fallback candidates, not to prove final rendered quality.
51+
- Test grapheme clusters such as emoji sequences as whole strings with production layout options.
52+
- Prefer face-level coverage inspection when building diagnostics or font picker tooling.
53+
- Keep fallback order intentional so broad-coverage fonts do not hide preferred design choices.

articles/fonts/colorfonts.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Color Fonts
2+
3+
Color fonts are one of the clearest signs of how much richer modern text rendering has become. Instead of a single monochrome outline, a glyph can carry layers, gradients, or even SVG content, and Fonts exposes that support explicitly through [`ColorFontSupport`](xref:SixLabors.Fonts.ColorFontSupport).
4+
5+
Fonts has comprehensive support for the major OpenType color-font technologies it exposes publicly:
6+
7+
- `ColorFontSupport.ColrV0` for layered solid-color glyphs defined by COLR and CPAL tables
8+
- `ColorFontSupport.ColrV1` for paint-graph glyphs with gradients, transforms, and richer composition
9+
- `ColorFontSupport.Svg` for color glyphs stored in the OpenType SVG table
10+
11+
### Enable or restrict color-font support
12+
13+
[`TextOptions.ColorFontSupport`](xref:SixLabors.Fonts.TextOptions.ColorFontSupport) controls which color-font technologies are honored during layout and rendering.
14+
15+
```csharp
16+
using SixLabors.Fonts;
17+
18+
FontCollection collection = new();
19+
FontFamily family = collection.Add("fonts/NotoColorEmoji-Regular.ttf");
20+
Font font = family.CreateFont(32);
21+
22+
TextOptions options = new(font)
23+
{
24+
ColorFontSupport = ColorFontSupport.ColrV1 | ColorFontSupport.ColrV0 | ColorFontSupport.Svg
25+
};
26+
```
27+
28+
[`TextOptions`](xref:SixLabors.Fonts.TextOptions) enables all three by default, so you usually only need to set this property when you want to disable color glyphs or restrict the allowed formats.
29+
30+
### Force monochrome output
31+
32+
Set [`ColorFontSupport.None`](xref:SixLabors.Fonts.ColorFontSupport.None) when you want color-font-capable text to fall back to monochrome outline rendering.
33+
34+
```csharp
35+
using SixLabors.Fonts;
36+
37+
FontCollection collection = new();
38+
FontFamily family = collection.Add("fonts/NotoColorEmoji-Regular.ttf");
39+
Font font = family.CreateFont(32);
40+
41+
TextOptions options = new(font)
42+
{
43+
ColorFontSupport = ColorFontSupport.None
44+
};
45+
```
46+
47+
### What happens in custom renderers
48+
49+
When a resolved glyph is a painted color glyph, Fonts streams it through [`IGlyphRenderer`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer) as one or more layers.
50+
51+
That means custom renderers should pay attention to:
52+
53+
- [`GlyphRendererParameters.GlyphType`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters.GlyphType)
54+
- [`BeginLayer(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginLayer*)
55+
- [`Paint`](xref:SixLabors.Fonts.Rendering.Paint)
56+
- [`FillRule`](xref:SixLabors.Fonts.Rendering.FillRule)
57+
- [`ClipQuad`](xref:SixLabors.Fonts.ClipQuad)
58+
59+
Depending on the font technology in use, the `Paint` passed to `BeginLayer(...)` may be:
60+
61+
- [`SolidPaint`](xref:SixLabors.Fonts.Rendering.SolidPaint)
62+
- [`LinearGradientPaint`](xref:SixLabors.Fonts.Rendering.LinearGradientPaint)
63+
- [`RadialGradientPaint`](xref:SixLabors.Fonts.Rendering.RadialGradientPaint)
64+
- [`SweepGradientPaint`](xref:SixLabors.Fonts.Rendering.SweepGradientPaint)
65+
66+
If your renderer ignores paint information, the glyph can still be drawn, but it will no longer preserve the font's intended color presentation.
67+
68+
### Inspect color glyphs directly
69+
70+
If you need to inspect a glyph without running full text layout, use [`Font.TryGetGlyphs(...)`](xref:SixLabors.Fonts.Font.TryGetGlyphs*) with explicit color support.
71+
72+
```csharp
73+
using SixLabors.Fonts;
74+
using SixLabors.Fonts.Unicode;
75+
76+
FontCollection collection = new();
77+
FontFamily family = collection.Add("fonts/NotoColorEmoji-Regular.ttf");
78+
Font font = family.CreateFont(32);
79+
80+
if (font.TryGetGlyphs(
81+
new CodePoint(0x1F600), // 😀 GRINNING FACE
82+
ColorFontSupport.ColrV1 | ColorFontSupport.ColrV0 | ColorFontSupport.Svg,
83+
out Glyph? glyph))
84+
{
85+
bool isPainted = glyph.GlyphMetrics.GlyphType == GlyphType.Painted;
86+
}
87+
```
88+
89+
### COLR vs SVG in practice
90+
91+
At a high level:
92+
93+
- COLR v0 uses layered shapes with palette colors
94+
- COLR v1 extends that model with richer paint graphs, gradients, transforms, and clipping
95+
- SVG glyphs carry SVG-authored painted content
96+
97+
Fonts resolves those technologies into a common painted-glyph rendering flow, which is why custom renderers can consume them through the same layer and paint callbacks.
98+
99+
### Measurement and rendering stay aligned
100+
101+
Color-font support is part of text layout, not just final painting. If you measure text with one `ColorFontSupport` configuration and render with another, you can create drift between the measured and rendered result.
102+
103+
Use the same [`TextOptions`](xref:SixLabors.Fonts.TextOptions) instance for both [`TextMeasurer`](xref:SixLabors.Fonts.TextMeasurer) and [`TextRenderer`](xref:SixLabors.Fonts.Rendering.TextRenderer) when you want a guaranteed match.
104+
105+
For renderer implementation details, see [Custom Rendering](customrendering.md). For fallback across multiple families, see [Fallback Fonts and Multilingual Text](fallbackfonts.md).
106+
107+
### Practical guidance
108+
109+
- Use the same `ColorFontSupport` setting when measuring and rendering.
110+
- Test the actual emoji or color glyph set you intend to support; technologies vary by font.
111+
- Decide fallback order deliberately when both monochrome and color families can cover the same text.
112+
- Custom renderers should handle painted glyph callbacks even if most text is outline-based.

0 commit comments

Comments
 (0)