Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
26 changes: 13 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ on:
- main

jobs:
prose:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
# prose:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v6

- name: Vale
uses: errata-ai/vale-action@reviewdog
with:
files: articles/.
env:
# Required
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
# - name: Vale
# uses: errata-ai/vale-action@reviewdog
# with:
# files: articles/.
# env:
# # Required
# GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

deploy:
needs: [prose]
# needs: [prose]

runs-on: windows-latest

Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
path = ext/Fonts
url = https://github.com/SixLabors/Fonts
ignore = dirty
[submodule "ext/PolygonClipper"]
path = ext/PolygonClipper
url = https://github.com/SixLabors/PolygonClipper
ignore = dirty
8 changes: 7 additions & 1 deletion api/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# API Documentation

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.
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.

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.

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.

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.
6 changes: 4 additions & 2 deletions api/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
- name: ImageSharp.Web
href: ImageSharp.Web/SixLabors.ImageSharp.Web.yml
- name: ImageSharp.Web.Providers.Azure
href: ImageSharp.Web.Providers.Azure/SixLabors.ImageSharp.Web.Providers.Azure.yml
href: ImageSharp.Web.Providers.Azure/SixLabors.ImageSharp.Web.Azure.Resolvers.yml
- name: ImageSharp.Web.Providers.AWS
href: ImageSharp.Web.Providers.AWS/SixLabors.ImageSharp.Web.Providers.AWS.yml
href: ImageSharp.Web.Providers.AWS/SixLabors.ImageSharp.Web.AWS.yml
- name: Fonts
href: Fonts/SixLabors.Fonts.yml
- name: PolygonClipper
href: PolygonClipper/SixLabors.PolygonClipper.yml
153 changes: 153 additions & 0 deletions articles/fonts/caretsandselection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Selection and Bidi Drag

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.

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).

### The shape of a selection

[`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.

```csharp
using System;
using SixLabors.Fonts;

ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(anchor, focus);

foreach (FontRectangle rectangle in selection.Span)
{
FillSelectionRectangle(rectangle);
}
```

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.

Do not sort, union, or merge the returned rectangles unless the UI explicitly wants a different visual.

### Pointer selection

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.

```csharp
using System.Numerics;
using SixLabors.Fonts;

TextHit anchor = metrics.HitTest(new Vector2(downX, downY));
TextHit focus = metrics.HitTest(new Vector2(moveX, moveY));

ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(anchor, focus);
```

This keeps trailing-edge and bidi handling inside the library.

### Keyboard selection

For keyboard selection, keep an anchor caret fixed and move the focus caret. Shift+Right-style behavior updates only the focus caret.

```csharp
using SixLabors.Fonts;

CaretPosition anchor = metrics.GetCaret(CaretPlacement.Start);
CaretPosition focus = anchor;

focus = metrics.MoveCaret(focus, CaretMovement.Next);

ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(anchor, focus);
```

Selecting whole words via keyboard is the same shape: move the focus by `NextWord` or `PreviousWord`.

### Word selection

For double-click word selection, find the word containing the hit and ask for its selection bounds.

```csharp
using SixLabors.Fonts;

TextHit hit = metrics.HitTest(doubleClickPosition);
WordMetrics word = metrics.GetWordMetrics(hit);

ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(word);
```

The [`GraphemeMetrics`](xref:SixLabors.Fonts.GraphemeMetrics) overload selects exactly one grapheme, which is useful for caret-region overlays:

```csharp
using SixLabors.Fonts;

GraphemeMetrics grapheme = metrics.GraphemeMetrics[index];
ReadOnlyMemory<FontRectangle> selection = metrics.GetSelectionBounds(grapheme);
```

### Bidi drag selection

Consider a left-to-right paragraph whose source text is:

```text
Tall שלום עرب
```

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:

```text
[Tall ] עرب [שלום]
```

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.

```csharp
using SixLabors.Fonts;

TextHit anchor = metrics.HitTest(mouseDown);
TextHit focus = metrics.HitTest(mouseMove);

ReadOnlyMemory<FontRectangle> rectangles = metrics.GetSelectionBounds(anchor, focus);
```

Just paint every rectangle. The library produces the correct visual gaps.

### Hard line breaks

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.

For text with two hard breaks in the middle:

```text
Tall عرب שלום

Small مرحبا שלום
```

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.

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.

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.

### Per-line selection

[`LineLayout`](xref:SixLabors.Fonts.LineLayout) exposes the same selection overloads when the caller knows the selection is line-local:

```csharp
using SixLabors.Fonts;

LineLayout line = layouts.Span[lineIndex];

ReadOnlyMemory<FontRectangle> selection = line.GetSelectionBounds(anchor, focus);
ReadOnlyMemory<FontRectangle> wordSelection = line.GetSelectionBounds(word);
```

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.

### Stable line-box geometry

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.

For a wider tour of the measurement model and how line metrics are derived, see [Measuring Text](measuringtext.md).

### Practical guidance

- Paint the selection rectangles returned by the API instead of reconstructing selection geometry yourself.
- Keep anchor and focus as logical text positions; let the metrics map them into visual rectangles.
- Use editor interaction mode when selections must include terminal blank lines.
- Test mixed LTR/RTL selections with real strings, not only simple Latin text.
53 changes: 53 additions & 0 deletions articles/fonts/checkglyphcoverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Check Glyph Coverage Before Choosing Fallbacks

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.

### Check individual code points

```csharp
using SixLabors.Fonts;
using SixLabors.Fonts.Unicode;

Font font = SystemFonts.CreateFont("Segoe UI", 16);

bool hasLatinA = font.TryGetGlyphs(new CodePoint('A'), out _);
bool hasOmega = font.TryGetGlyphs(new CodePoint(0x03A9), out _); // Ω GREEK CAPITAL LETTER OMEGA
bool hasEmoji = font.TryGetGlyphs(new CodePoint(0x1F600), out _); // 😀 GRINNING FACE
```

### Scan a whole string for missing glyphs

```csharp
using System.Collections.Generic;
using SixLabors.Fonts;
using SixLabors.Fonts.Unicode;

string text = "Hello 123 مرحبا 😀";
Font font = SystemFonts.CreateFont("Segoe UI", 16);
List<CodePoint> missing = new();

foreach (CodePoint codePoint in text.AsSpan().EnumerateCodePoints())
{
if (!font.TryGetGlyphs(codePoint, out _))
{
missing.Add(codePoint);
}
}
```

This is a simple way to decide whether you need `FallbackFontFamilies` before you measure or render the text.

If you want a broader face-level view instead of checking a specific string, use [`Font.FontMetrics.GetAvailableCodePoints()`](xref:SixLabors.Fonts.FontMetrics.GetAvailableCodePoints*).

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.

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.

For the conceptual fallback guidance, see [Fallback Fonts and Multilingual Text](fallbackfonts.md). For face-level coverage inspection, see [Font Metrics](fontmetrics.md).

### Practical guidance

- Use coverage checks to choose fallback candidates, not to prove final rendered quality.
- Test grapheme clusters such as emoji sequences as whole strings with production layout options.
- Prefer face-level coverage inspection when building diagnostics or font picker tooling.
- Keep fallback order intentional so broad-coverage fonts do not hide preferred design choices.
112 changes: 112 additions & 0 deletions articles/fonts/colorfonts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Color Fonts

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).

Fonts has comprehensive support for the major OpenType color-font technologies it exposes publicly:

- `ColorFontSupport.ColrV0` for layered solid-color glyphs defined by COLR and CPAL tables
- `ColorFontSupport.ColrV1` for paint-graph glyphs with gradients, transforms, and richer composition
- `ColorFontSupport.Svg` for color glyphs stored in the OpenType SVG table

### Enable or restrict color-font support

[`TextOptions.ColorFontSupport`](xref:SixLabors.Fonts.TextOptions.ColorFontSupport) controls which color-font technologies are honored during layout and rendering.

```csharp
using SixLabors.Fonts;

FontCollection collection = new();
FontFamily family = collection.Add("fonts/NotoColorEmoji-Regular.ttf");
Font font = family.CreateFont(32);

TextOptions options = new(font)
{
ColorFontSupport = ColorFontSupport.ColrV1 | ColorFontSupport.ColrV0 | ColorFontSupport.Svg
};
```

[`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.

### Force monochrome output

Set [`ColorFontSupport.None`](xref:SixLabors.Fonts.ColorFontSupport.None) when you want color-font-capable text to fall back to monochrome outline rendering.

```csharp
using SixLabors.Fonts;

FontCollection collection = new();
FontFamily family = collection.Add("fonts/NotoColorEmoji-Regular.ttf");
Font font = family.CreateFont(32);

TextOptions options = new(font)
{
ColorFontSupport = ColorFontSupport.None
};
```

### What happens in custom renderers

When a resolved glyph is a painted color glyph, Fonts streams it through [`IGlyphRenderer`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer) as one or more layers.

That means custom renderers should pay attention to:

- [`GlyphRendererParameters.GlyphType`](xref:SixLabors.Fonts.Rendering.GlyphRendererParameters.GlyphType)
- [`BeginLayer(...)`](xref:SixLabors.Fonts.Rendering.IGlyphRenderer.BeginLayer*)
- [`Paint`](xref:SixLabors.Fonts.Rendering.Paint)
- [`FillRule`](xref:SixLabors.Fonts.Rendering.FillRule)
- [`ClipQuad`](xref:SixLabors.Fonts.ClipQuad)

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

- [`SolidPaint`](xref:SixLabors.Fonts.Rendering.SolidPaint)
- [`LinearGradientPaint`](xref:SixLabors.Fonts.Rendering.LinearGradientPaint)
- [`RadialGradientPaint`](xref:SixLabors.Fonts.Rendering.RadialGradientPaint)
- [`SweepGradientPaint`](xref:SixLabors.Fonts.Rendering.SweepGradientPaint)

If your renderer ignores paint information, the glyph can still be drawn, but it will no longer preserve the font's intended color presentation.

### Inspect color glyphs directly

If you need to inspect a glyph without running full text layout, use [`Font.TryGetGlyphs(...)`](xref:SixLabors.Fonts.Font.TryGetGlyphs*) with explicit color support.

```csharp
using SixLabors.Fonts;
using SixLabors.Fonts.Unicode;

FontCollection collection = new();
FontFamily family = collection.Add("fonts/NotoColorEmoji-Regular.ttf");
Font font = family.CreateFont(32);

if (font.TryGetGlyphs(
new CodePoint(0x1F600), // 😀 GRINNING FACE
ColorFontSupport.ColrV1 | ColorFontSupport.ColrV0 | ColorFontSupport.Svg,
out Glyph? glyph))
{
bool isPainted = glyph.GlyphMetrics.GlyphType == GlyphType.Painted;
}
```

### COLR vs SVG in practice

At a high level:

- COLR v0 uses layered shapes with palette colors
- COLR v1 extends that model with richer paint graphs, gradients, transforms, and clipping
- SVG glyphs carry SVG-authored painted content

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.

### Measurement and rendering stay aligned

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.

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.

For renderer implementation details, see [Custom Rendering](customrendering.md). For fallback across multiple families, see [Fallback Fonts and Multilingual Text](fallbackfonts.md).

### Practical guidance

- Use the same `ColorFontSupport` setting when measuring and rendering.
- Test the actual emoji or color glyph set you intend to support; technologies vary by font.
- Decide fallback order deliberately when both monochrome and color families can cover the same text.
- Custom renderers should handle painted glyph callbacks even if most text is outline-based.
Loading