Skip to main content

text_typeset/
types.rs

1/// Opaque handle to a registered font face.
2///
3/// Obtained from [`crate::TextFontService::register_font`] or [`crate::TextFontService::register_font_as`].
4/// Pass to [`crate::TextFontService::set_default_font`] to make it the default.
5#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
6pub struct FontFaceId(pub u32);
7
8// ── Render output ───────────────────────────────────────────────
9
10/// Everything needed to draw one frame.
11///
12/// Produced by [`crate::DocumentFlow::render`]. Contains glyph quads (textured rectangles
13/// from the atlas), inline image placeholders, and decoration rectangles
14/// (selections, cursor, underlines, table borders, etc.).
15///
16/// The adapter draws the frame in three passes:
17/// 1. Upload `atlas_pixels` as a GPU texture (only when `atlas_dirty` is true).
18/// 2. Draw each [`GlyphQuad`] as a textured rectangle from the atlas.
19/// 3. Draw each [`DecorationRect`] as a colored rectangle.
20pub struct RenderFrame {
21    /// True if the atlas texture changed since the last frame (needs re-upload).
22    pub atlas_dirty: bool,
23    /// Atlas texture width in pixels.
24    pub atlas_width: u32,
25    /// Atlas texture height in pixels.
26    pub atlas_height: u32,
27    /// RGBA pixel data, row-major. Length = `atlas_width * atlas_height * 4`.
28    pub atlas_pixels: Vec<u8>,
29    /// One textured rectangle per visible glyph.
30    pub glyphs: Vec<GlyphQuad>,
31    /// Inline image placeholders. The adapter loads the actual image data
32    /// (e.g., via `TextDocument::resource(name)`) and draws it at the given
33    /// screen position.
34    pub images: Vec<ImageQuad>,
35    /// Decoration rectangles: selections, cursor, underlines, strikeouts,
36    /// overlines, backgrounds, table borders, and cell backgrounds.
37    pub decorations: Vec<DecorationRect>,
38    /// Per-block glyph data for incremental updates. Keyed by block_id.
39    pub(crate) block_glyphs: Vec<(usize, Vec<GlyphQuad>)>,
40    /// Per-block decoration data (underlines, etc. — NOT cursor/selection).
41    pub(crate) block_decorations: Vec<(usize, Vec<DecorationRect>)>,
42    /// Per-block image data for incremental updates.
43    pub(crate) block_images: Vec<(usize, Vec<ImageQuad>)>,
44    /// Per-block height snapshot for detecting height changes in incremental render.
45    pub(crate) block_heights: std::collections::HashMap<usize, f32>,
46    /// Per-block glyph cache keys, parallel to [`Self::block_glyphs`]. Used by
47    /// [`crate::DocumentFlow::render_cursor_only`] and
48    /// [`crate::DocumentFlow::render_block_only`] to mark every cached
49    /// glyph as still-in-use in the shared `GlyphCache` — otherwise
50    /// glyphs reused via paint-cache hits (which never re-enter the
51    /// `cache.get()` path that refreshes timestamps) would age out and
52    /// their atlas slots could be reallocated for unrelated glyphs,
53    /// silently corrupting the cached `GlyphQuad`s' atlas references.
54    pub(crate) block_glyph_keys: Vec<(usize, Vec<crate::atlas::cache::GlyphCacheKey>)>,
55    /// Flat glyph cache keys, parallel to [`Self::glyphs`]. Rebuilt from
56    /// [`Self::block_glyph_keys`] by `rebuild_flat_frame`; passed to
57    /// [`crate::TextFontService::touch_glyphs`] on every cursor-only /
58    /// block-only paint so the shared atlas keeps visible glyphs alive.
59    pub(crate) glyph_keys: Vec<crate::atlas::cache::GlyphCacheKey>,
60    /// Snapshot of [`crate::TextFontService::eviction_epoch`] at the
61    /// moment this frame's atlas references were baked. Cursor-only
62    /// and block-only paths compare against the service's current
63    /// epoch and fall back to a full re-render if eviction has
64    /// happened since — defensive safety net behind the `touch_glyphs`
65    /// keep-alive mechanism.
66    pub(crate) atlas_eviction_epoch: u64,
67}
68
69/// A positioned glyph to draw as a textured quad from the atlas.
70///
71/// The adapter draws the rectangle at `screen` position, sampling from
72/// the `atlas` rectangle in the atlas texture, tinted with `color`.
73#[derive(Clone)]
74pub struct GlyphQuad {
75    /// Screen position and size: `[x, y, width, height]` in pixels.
76    pub screen: [f32; 4],
77    /// Atlas source rectangle: `[x, y, width, height]` in atlas pixel coordinates.
78    pub atlas: [f32; 4],
79    /// Glyph color: `[r, g, b, a]`, 0.0-1.0.
80    /// For normal text glyphs, this is the text color (default black).
81    /// For color emoji, this is `[1, 1, 1, 1]` (color is baked into the atlas).
82    pub color: [f32; 4],
83    /// `true` if the atlas region for this glyph holds a pre-multiplied
84    /// RGBA color bitmap (color emoji via COLR/CBDT/sbix). The renderer
85    /// must sample `texture.rgb` directly instead of using the texture
86    /// as an alpha mask tinted by [`color`](Self::color).
87    pub is_color: bool,
88}
89
90/// An inline image placeholder.
91///
92/// text-typeset computes the position and size but does NOT load or rasterize
93/// the image. The adapter retrieves the image data (e.g., from
94/// `TextDocument::resource(name)`) and draws it as a separate texture.
95#[derive(Clone)]
96pub struct ImageQuad {
97    /// Screen position and size: `[x, y, width, height]` in pixels.
98    pub screen: [f32; 4],
99    /// Image resource name (matches `FragmentContent::Image::name` from text-document).
100    pub name: String,
101    /// Document-absolute character offset of this image's single `U+FFFC`.
102    ///
103    /// The name alone cannot say *which* placement this is — a document may
104    /// hold one picture in three places — so anything that has to answer "is
105    /// THIS image inside the selection" needs the offset. Same value, derived
106    /// the same way, as the offset `hit_test` reports for a click on it.
107    pub char_offset: usize,
108}
109
110/// A colored rectangle for decorations (underlines, selections, borders, etc.).
111#[derive(Clone)]
112pub struct DecorationRect {
113    /// Screen position and size: `[x, y, width, height]` in pixels.
114    pub rect: [f32; 4],
115    /// Color: `[r, g, b, a]`, 0.0-1.0.
116    pub color: [f32; 4],
117    /// What kind of decoration this rectangle represents.
118    pub kind: DecorationKind,
119}
120
121/// The type of a [`DecorationRect`].
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum DecorationKind {
124    /// Selection highlight (translucent background behind selected text).
125    Selection,
126    /// Cursor caret (thin vertical line at the insertion point).
127    Cursor,
128    /// Underline (below baseline, from font metrics).
129    Underline,
130    /// Strikethrough (at x-height, from font metrics).
131    Strikeout,
132    /// Overline (at ascent line).
133    Overline,
134    /// Generic background (e.g., frame borders).
135    Background,
136    /// Block-level background color.
137    BlockBackground,
138    /// Table border line.
139    TableBorder,
140    /// Table cell background color.
141    TableCellBackground,
142    /// Text-level background highlight (behind individual text runs).
143    /// Adapters should draw these before glyph quads so text appears on top.
144    TextBackground,
145    /// Cell-level selection highlight (entire cell background when cells are
146    /// selected as a rectangular region, as opposed to text within cells).
147    CellSelection,
148}
149
150/// Underline style for text decorations.
151#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
152pub enum UnderlineStyle {
153    /// No underline.
154    #[default]
155    None,
156    /// Solid single underline.
157    Single,
158    /// Dashed underline.
159    Dash,
160    /// Dotted underline.
161    Dot,
162    /// Alternating dash-dot pattern.
163    DashDot,
164    /// Alternating dash-dot-dot pattern.
165    DashDotDot,
166    /// Wavy underline.
167    Wave,
168    /// Spell-check underline (wavy, typically red).
169    SpellCheck,
170}
171
172/// Vertical alignment for characters (superscript, subscript, etc.).
173#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
174pub enum VerticalAlignment {
175    /// Normal baseline alignment.
176    #[default]
177    Normal,
178    /// Superscript: smaller size, shifted up.
179    SuperScript,
180    /// Subscript: smaller size, shifted down.
181    SubScript,
182}
183
184// ── Hit testing ─────────────────────────────────────────────────
185
186/// Disambiguates the two visual placements a single character position
187/// can have at a soft-wrap boundary. A long paragraph that wraps across
188/// lines K and K+1 has one character position N that sits at both the
189/// END of line K and the START of line K+1; affinity picks which one
190/// the caret renders at and which line `Home`/`End`-style navigation
191/// considers "current".
192///
193/// Affinity is a display concern: it makes no sense without a layout
194/// engine and a wrap width. It is never persisted with the text model
195/// (cf. Cocoa `NSSelectionAffinity` on `NSTextView`, not on
196/// `NSTextStorage`; Chromium `PositionWithAffinity` at the editing
197/// layer, not on `Position`; same in Qt and CodeMirror).
198///
199/// At positions that are NOT wrap boundaries — the interior of a line,
200/// the start of the first wrap-line, the end of the last wrap-line of
201/// a paragraph — affinity is a no-op and the rendering is identical.
202#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
203pub enum CursorAffinity {
204    /// Place the caret at the END of the previous wrap line. This is
205    /// the visual "trailing" placement and the default for any
206    /// position not produced by an upstream-side interaction.
207    #[default]
208    Downstream,
209    /// Place the caret at the START of the next wrap line.
210    Upstream,
211}
212
213/// Result of [`crate::DocumentFlow::hit_test`] - maps a screen-space point to a
214/// document position.
215pub struct HitTestResult {
216    /// Absolute character position in the document.
217    pub position: usize,
218    /// Which side of a soft-wrap boundary the click landed on. When
219    /// the matched line's Y range contained the click and `position`
220    /// equals that line's `char_range.start` AND a preceding line in
221    /// the same block ends at the same position, the click is on the
222    /// upstream side of the boundary → `Upstream`. Otherwise
223    /// `Downstream`. At non-wrap positions the value is `Downstream`
224    /// (default) and does not affect anything.
225    pub affinity: CursorAffinity,
226    /// Which block (paragraph) was hit, identified by stable block ID.
227    pub block_id: usize,
228    /// Character offset within the block (0 = start of block).
229    pub offset_in_block: usize,
230    /// What region of the layout was hit.
231    pub region: HitRegion,
232    /// Tooltip text if the hit position has a tooltip. None otherwise.
233    pub tooltip: Option<String>,
234    /// When non-None, the hit position is inside a table cell.
235    /// Identifies the table by its stable table ID.
236    /// None for hits on top-level blocks, frame blocks, or outside any table.
237    pub table_id: Option<usize>,
238}
239
240/// What region of the layout a hit test landed in.
241#[derive(Debug)]
242pub enum HitRegion {
243    /// Inside a text run (normal text content).
244    Text,
245    /// In the block's left margin area (before any text content).
246    LeftMargin,
247    /// In the block's indent area.
248    Indent,
249    /// On a table border line.
250    TableBorder,
251    /// Below all content in the document.
252    BelowContent,
253    /// Past the end of a line (to the right of the last character).
254    PastLineEnd,
255    /// On an inline image.
256    Image { name: String },
257    /// On a hyperlink.
258    Link { href: String },
259}
260
261// ── Cursor display ──────────────────────────────────────────────
262
263/// Cursor display state for rendering.
264///
265/// The adapter reads cursor position from text-document's `TextCursor`
266/// and creates this struct to feed to [`crate::DocumentFlow::set_cursor`].
267/// text-typeset uses it to generate caret and selection decorations
268/// in the next [`crate::DocumentFlow::render`] call.
269pub struct CursorDisplay {
270    /// Cursor position (character offset in the document).
271    pub position: usize,
272    /// Selection anchor. Equals `position` when there is no selection.
273    /// When different from `position`, the range `[min(anchor, position), max(anchor, position))`
274    /// is highlighted as a selection.
275    pub anchor: usize,
276    /// Which side of a soft-wrap boundary the caret renders on (see
277    /// [`CursorAffinity`]). At non-boundary positions this is a
278    /// no-op; default `Downstream` (current behavior before affinity
279    /// was introduced).
280    pub affinity: CursorAffinity,
281    /// Whether the caret is visible (false during the blink-off phase).
282    /// The adapter manages the blink timer; text-typeset just respects this flag.
283    pub visible: bool,
284    /// When non-empty, render cell-level selection highlights instead of
285    /// text-level selection. Each tuple is `(table_id, row, col)` identifying
286    /// a selected cell. The adapter fills this from `TextCursor::selected_cells()`.
287    pub selected_cells: Vec<(usize, usize, usize)>,
288}
289
290// ── Scrolling ───────────────────────────────────────────────────
291
292/// Visual position and size of a laid-out block.
293///
294/// Returned by [`crate::DocumentFlow::block_visual_info`].
295pub struct BlockVisualInfo {
296    /// Block ID (matches `BlockSnapshot::block_id`).
297    pub block_id: usize,
298    /// Y position of the block's top edge relative to the document start, in pixels.
299    pub y: f32,
300    /// Total height of the block including margins, in pixels.
301    pub height: f32,
302}
303
304// ── OpenType features ───────────────────────────────────────────
305
306/// An OpenType feature toggle applied during shaping.
307///
308/// `tag` is the 4-byte feature tag (e.g. `*b"liga"`, `*b"smcp"`,
309/// `*b"tnum"`, `*b"ss01"`); `value` is the feature value — `0` disables
310/// it, `1` enables it, and some features (e.g. `aalt`) take an index.
311///
312/// Script-mandated features (Arabic joining, Indic reordering, etc.)
313/// always apply regardless of this list; these toggles control the
314/// *discretionary* typographic features a caller wants on or off.
315#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
316pub struct FontFeature {
317    /// The 4-byte OpenType feature tag.
318    pub tag: [u8; 4],
319    /// Feature value: `0` = off, `1` = on, or a feature-specific index.
320    pub value: u32,
321}
322
323impl FontFeature {
324    /// A feature tag turned on (`value = 1`).
325    pub const fn on(tag: [u8; 4]) -> Self {
326        Self { tag, value: 1 }
327    }
328
329    /// A feature tag turned off (`value = 0`).
330    pub const fn off(tag: [u8; 4]) -> Self {
331        Self { tag, value: 0 }
332    }
333
334    /// A feature tag with an explicit value.
335    pub const fn new(tag: [u8; 4], value: u32) -> Self {
336        Self { tag, value }
337    }
338}
339
340/// Hyphenation settings for line wrapping.
341///
342/// Presence (`Some`) enables hyphenation; the `language` selects the
343/// Knuth-Liang dictionary. Soft hyphens (U+00AD) always break and render a
344/// hyphen when enabled, regardless of language; dictionary hyphenation
345/// applies only when the language's patterns are compiled in (otherwise it
346/// silently falls back to soft-hyphen-only).
347#[derive(Clone, Copy, Debug, PartialEq, Eq)]
348pub struct Hyphenation {
349    /// ISO 639-1 language code, e.g. `*b"en"`, `*b"fr"`, `*b"de"`.
350    pub language: [u8; 2],
351}
352
353impl Hyphenation {
354    /// Hyphenation in the given ISO 639-1 language.
355    pub const fn new(language: [u8; 2]) -> Self {
356        Self { language }
357    }
358
359    /// English hyphenation (`en`).
360    pub const ENGLISH: Self = Self { language: *b"en" };
361}
362
363impl Default for Hyphenation {
364    fn default() -> Self {
365        Self::ENGLISH
366    }
367}
368
369// ── Single-line API ────────────────────────────────────────────
370
371/// Text formatting parameters for the single-line layout API.
372///
373/// Controls font selection, size, and text color. All fields are optional
374/// and fall back to the typesetter's defaults (default font, default size,
375/// default text color).
376#[derive(Clone, Debug, Default)]
377pub struct TextFormat {
378    /// Font family name (e.g., "Noto Sans", "monospace").
379    /// None means use the default font.
380    pub font_family: Option<String>,
381    /// Font weight (100-900). Overrides `font_bold`.
382    pub font_weight: Option<u32>,
383    /// Shorthand for weight 700. Ignored if `font_weight` is set.
384    pub font_bold: Option<bool>,
385    /// Italic style.
386    pub font_italic: Option<bool>,
387    /// Font size in pixels. None means use the default size.
388    pub font_size: Option<f32>,
389    /// Text color (RGBA, 0.0-1.0). None means use the typesetter's text color.
390    pub color: Option<[f32; 4]>,
391    /// Discretionary OpenType features to toggle during shaping (ligatures,
392    /// small caps, tabular numerals, stylistic sets, …). Empty = font defaults.
393    pub features: Vec<FontFeature>,
394    /// Hyphenation (Knuth-Liang dictionary + soft-hyphen breaks) for line
395    /// wrapping. `None` = disabled (default); most useful for justified
396    /// prose. See [`Hyphenation`].
397    pub hyphenation: Option<Hyphenation>,
398}
399
400/// Result of [`crate::DocumentFlow::layout_single_line`].
401///
402/// Contains the measured dimensions and GPU-ready glyph quads for a
403/// single line of text. No flow layout, line breaking, or bidi analysis
404/// is performed.
405pub struct SingleLineResult {
406    /// Total advance width of the shaped text, in pixels.
407    pub width: f32,
408    /// Line height (ascent + descent + leading), in pixels.
409    pub height: f32,
410    /// Distance from the top of the line to the baseline, in pixels.
411    pub baseline: f32,
412    /// Distance from baseline to the top of the underline, in logical
413    /// pixels. Positive = below the baseline. Sourced from the primary
414    /// font's `post` table.
415    pub underline_offset: f32,
416    /// Underline line thickness in logical pixels. Sourced from the
417    /// primary font's stroke size.
418    pub underline_thickness: f32,
419    /// GPU-ready glyph quads, positioned at y=0 (no scroll offset).
420    pub glyphs: Vec<GlyphQuad>,
421    /// Per-glyph cache keys, parallel to `glyphs`. Callers that cache
422    /// glyph output externally should pass these back to
423    /// [`crate::TextFontService::touch_glyphs`] each frame to prevent the
424    /// atlas from evicting still-visible glyphs.
425    pub glyph_keys: Vec<crate::atlas::cache::GlyphCacheKey>,
426    /// Per-span bounding rectangles for markup-aware layout
427    /// ([`crate::DocumentFlow::layout_single_line_markup`]). Empty for
428    /// the plain-text layout path.
429    pub spans: Vec<LaidOutSpan>,
430}
431
432/// A single laid-out span produced by the markup-aware layout path.
433///
434/// When a link wraps across two paragraph lines, it produces two
435/// `LaidOutSpan` entries sharing the same URL and byte_range but with
436/// distinct `line_index` / `rect`.
437#[derive(Debug, Clone)]
438pub struct LaidOutSpan {
439    pub kind: LaidOutSpanKind,
440    /// Which wrapped line this span piece lives on (0 for single-line).
441    pub line_index: usize,
442    /// Local-space rect: `[x, y, width, height]`, same space as glyph quads.
443    pub rect: [f32; 4],
444    /// Byte range into the original markup source.
445    pub byte_range: std::ops::Range<usize>,
446}
447
448/// Kind discriminator for [`LaidOutSpan`].
449#[derive(Debug, Clone)]
450pub enum LaidOutSpanKind {
451    Text,
452    Link { url: String },
453}
454
455/// Result of [`crate::DocumentFlow::layout_paragraph`].
456///
457/// Contains the measured dimensions and GPU-ready glyph quads for a
458/// multi-line paragraph wrapped at a fixed width. Glyphs are positioned
459/// in paragraph-local coordinates: `x = 0` is the left edge of the
460/// paragraph, `y = 0` is the top of the first line's line box. The
461/// adapter should offset all glyph quads by the paragraph's screen
462/// position before drawing.
463pub struct ParagraphResult {
464    /// Width of the widest laid-out line, in pixels. May be less than the
465    /// `max_width` passed to `layout_paragraph` if the content is narrower.
466    pub width: f32,
467    /// Total stacked paragraph height in pixels — sum of line heights for
468    /// all emitted lines.
469    pub height: f32,
470    /// Distance from `y = 0` to the baseline of the first line, in pixels.
471    pub baseline_first: f32,
472    /// Number of lines actually emitted (respects `max_lines` when set).
473    pub line_count: usize,
474    /// Line height (single line's ascent + descent + leading), in pixels.
475    /// Useful for callers that need to reason about per-line geometry.
476    pub line_height: f32,
477    /// Distance from baseline to the top of the underline, in logical
478    /// pixels. Positive = below the baseline. Sourced from the primary
479    /// font's `post` table.
480    pub underline_offset: f32,
481    /// Underline line thickness in logical pixels. Sourced from the
482    /// primary font's stroke size.
483    pub underline_thickness: f32,
484    /// GPU-ready glyph quads in paragraph-local coordinates.
485    pub glyphs: Vec<GlyphQuad>,
486    /// Per-glyph cache keys, parallel to `glyphs`. See
487    /// [`SingleLineResult::glyph_keys`].
488    pub glyph_keys: Vec<crate::atlas::cache::GlyphCacheKey>,
489    /// Per-span bounding rectangles for markup-aware layout
490    /// ([`crate::DocumentFlow::layout_paragraph_markup`]). Empty for
491    /// the plain-text layout path.
492    pub spans: Vec<LaidOutSpan>,
493}
494
495impl RenderFrame {
496    pub(crate) fn new() -> Self {
497        Self {
498            atlas_dirty: false,
499            atlas_width: 0,
500            atlas_height: 0,
501            atlas_pixels: Vec::new(),
502            glyphs: Vec::new(),
503            images: Vec::new(),
504            decorations: Vec::new(),
505            block_glyphs: Vec::new(),
506            block_decorations: Vec::new(),
507            block_images: Vec::new(),
508            block_heights: std::collections::HashMap::new(),
509            block_glyph_keys: Vec::new(),
510            glyph_keys: Vec::new(),
511            atlas_eviction_epoch: 0,
512        }
513    }
514}
515
516// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
517// CharacterGeometry — accessibility per-character advance data
518// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
519
520/// Per-character advance geometry for a laid-out text run.
521///
522/// Consumed by accessibility layers that need to populate AccessKit's
523/// `character_positions` and `character_widths` on a `Role::TextRun`
524/// node so screen reader highlight cursors and screen magnifiers can
525/// track the caret at character granularity.
526///
527/// `position` is measured in run-local coordinates: the first
528/// character of the requested range sits at `position == 0.0`, and
529/// subsequent characters accumulate their advance widths. `width` is
530/// the advance width of each character, in the same units.
531#[derive(Debug, Clone, Copy, PartialEq)]
532pub struct CharacterGeometry {
533    pub position: f32,
534    pub width: f32,
535}