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}
47
48/// A positioned glyph to draw as a textured quad from the atlas.
49///
50/// The adapter draws the rectangle at `screen` position, sampling from
51/// the `atlas` rectangle in the atlas texture, tinted with `color`.
52#[derive(Clone)]
53pub struct GlyphQuad {
54    /// Screen position and size: `[x, y, width, height]` in pixels.
55    pub screen: [f32; 4],
56    /// Atlas source rectangle: `[x, y, width, height]` in atlas pixel coordinates.
57    pub atlas: [f32; 4],
58    /// Glyph color: `[r, g, b, a]`, 0.0-1.0.
59    /// For normal text glyphs, this is the text color (default black).
60    /// For color emoji, this is `[1, 1, 1, 1]` (color is baked into the atlas).
61    pub color: [f32; 4],
62    /// `true` if the atlas region for this glyph holds a pre-multiplied
63    /// RGBA color bitmap (color emoji via COLR/CBDT/sbix). The renderer
64    /// must sample `texture.rgb` directly instead of using the texture
65    /// as an alpha mask tinted by [`color`](Self::color).
66    pub is_color: bool,
67}
68
69/// An inline image placeholder.
70///
71/// text-typeset computes the position and size but does NOT load or rasterize
72/// the image. The adapter retrieves the image data (e.g., from
73/// `TextDocument::resource(name)`) and draws it as a separate texture.
74#[derive(Clone)]
75pub struct ImageQuad {
76    /// Screen position and size: `[x, y, width, height]` in pixels.
77    pub screen: [f32; 4],
78    /// Image resource name (matches `FragmentContent::Image::name` from text-document).
79    pub name: String,
80}
81
82/// A colored rectangle for decorations (underlines, selections, borders, etc.).
83#[derive(Clone)]
84pub struct DecorationRect {
85    /// Screen position and size: `[x, y, width, height]` in pixels.
86    pub rect: [f32; 4],
87    /// Color: `[r, g, b, a]`, 0.0-1.0.
88    pub color: [f32; 4],
89    /// What kind of decoration this rectangle represents.
90    pub kind: DecorationKind,
91}
92
93/// The type of a [`DecorationRect`].
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum DecorationKind {
96    /// Selection highlight (translucent background behind selected text).
97    Selection,
98    /// Cursor caret (thin vertical line at the insertion point).
99    Cursor,
100    /// Underline (below baseline, from font metrics).
101    Underline,
102    /// Strikethrough (at x-height, from font metrics).
103    Strikeout,
104    /// Overline (at ascent line).
105    Overline,
106    /// Generic background (e.g., frame borders).
107    Background,
108    /// Block-level background color.
109    BlockBackground,
110    /// Table border line.
111    TableBorder,
112    /// Table cell background color.
113    TableCellBackground,
114    /// Text-level background highlight (behind individual text runs).
115    /// Adapters should draw these before glyph quads so text appears on top.
116    TextBackground,
117    /// Cell-level selection highlight (entire cell background when cells are
118    /// selected as a rectangular region, as opposed to text within cells).
119    CellSelection,
120}
121
122/// Underline style for text decorations.
123#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
124pub enum UnderlineStyle {
125    /// No underline.
126    #[default]
127    None,
128    /// Solid single underline.
129    Single,
130    /// Dashed underline.
131    Dash,
132    /// Dotted underline.
133    Dot,
134    /// Alternating dash-dot pattern.
135    DashDot,
136    /// Alternating dash-dot-dot pattern.
137    DashDotDot,
138    /// Wavy underline.
139    Wave,
140    /// Spell-check underline (wavy, typically red).
141    SpellCheck,
142}
143
144/// Vertical alignment for characters (superscript, subscript, etc.).
145#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
146pub enum VerticalAlignment {
147    /// Normal baseline alignment.
148    #[default]
149    Normal,
150    /// Superscript: smaller size, shifted up.
151    SuperScript,
152    /// Subscript: smaller size, shifted down.
153    SubScript,
154}
155
156// ── Hit testing ─────────────────────────────────────────────────
157
158/// Disambiguates the two visual placements a single character position
159/// can have at a soft-wrap boundary. A long paragraph that wraps across
160/// lines K and K+1 has one character position N that sits at both the
161/// END of line K and the START of line K+1; affinity picks which one
162/// the caret renders at and which line `Home`/`End`-style navigation
163/// considers "current".
164///
165/// Affinity is a display concern: it makes no sense without a layout
166/// engine and a wrap width. It is never persisted with the text model
167/// (cf. Cocoa `NSSelectionAffinity` on `NSTextView`, not on
168/// `NSTextStorage`; Chromium `PositionWithAffinity` at the editing
169/// layer, not on `Position`; same in Qt and CodeMirror).
170///
171/// At positions that are NOT wrap boundaries — the interior of a line,
172/// the start of the first wrap-line, the end of the last wrap-line of
173/// a paragraph — affinity is a no-op and the rendering is identical.
174#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
175pub enum CursorAffinity {
176    /// Place the caret at the END of the previous wrap line. This is
177    /// the visual "trailing" placement and the default for any
178    /// position not produced by an upstream-side interaction.
179    #[default]
180    Downstream,
181    /// Place the caret at the START of the next wrap line.
182    Upstream,
183}
184
185/// Result of [`crate::DocumentFlow::hit_test`] - maps a screen-space point to a
186/// document position.
187pub struct HitTestResult {
188    /// Absolute character position in the document.
189    pub position: usize,
190    /// Which side of a soft-wrap boundary the click landed on. When
191    /// the matched line's Y range contained the click and `position`
192    /// equals that line's `char_range.start` AND a preceding line in
193    /// the same block ends at the same position, the click is on the
194    /// upstream side of the boundary → `Upstream`. Otherwise
195    /// `Downstream`. At non-wrap positions the value is `Downstream`
196    /// (default) and does not affect anything.
197    pub affinity: CursorAffinity,
198    /// Which block (paragraph) was hit, identified by stable block ID.
199    pub block_id: usize,
200    /// Character offset within the block (0 = start of block).
201    pub offset_in_block: usize,
202    /// What region of the layout was hit.
203    pub region: HitRegion,
204    /// Tooltip text if the hit position has a tooltip. None otherwise.
205    pub tooltip: Option<String>,
206    /// When non-None, the hit position is inside a table cell.
207    /// Identifies the table by its stable table ID.
208    /// None for hits on top-level blocks, frame blocks, or outside any table.
209    pub table_id: Option<usize>,
210}
211
212/// What region of the layout a hit test landed in.
213#[derive(Debug)]
214pub enum HitRegion {
215    /// Inside a text run (normal text content).
216    Text,
217    /// In the block's left margin area (before any text content).
218    LeftMargin,
219    /// In the block's indent area.
220    Indent,
221    /// On a table border line.
222    TableBorder,
223    /// Below all content in the document.
224    BelowContent,
225    /// Past the end of a line (to the right of the last character).
226    PastLineEnd,
227    /// On an inline image.
228    Image { name: String },
229    /// On a hyperlink.
230    Link { href: String },
231}
232
233// ── Cursor display ──────────────────────────────────────────────
234
235/// Cursor display state for rendering.
236///
237/// The adapter reads cursor position from text-document's `TextCursor`
238/// and creates this struct to feed to [`crate::DocumentFlow::set_cursor`].
239/// text-typeset uses it to generate caret and selection decorations
240/// in the next [`crate::DocumentFlow::render`] call.
241pub struct CursorDisplay {
242    /// Cursor position (character offset in the document).
243    pub position: usize,
244    /// Selection anchor. Equals `position` when there is no selection.
245    /// When different from `position`, the range `[min(anchor, position), max(anchor, position))`
246    /// is highlighted as a selection.
247    pub anchor: usize,
248    /// Which side of a soft-wrap boundary the caret renders on (see
249    /// [`CursorAffinity`]). At non-boundary positions this is a
250    /// no-op; default `Downstream` (current behavior before affinity
251    /// was introduced).
252    pub affinity: CursorAffinity,
253    /// Whether the caret is visible (false during the blink-off phase).
254    /// The adapter manages the blink timer; text-typeset just respects this flag.
255    pub visible: bool,
256    /// When non-empty, render cell-level selection highlights instead of
257    /// text-level selection. Each tuple is `(table_id, row, col)` identifying
258    /// a selected cell. The adapter fills this from `TextCursor::selected_cells()`.
259    pub selected_cells: Vec<(usize, usize, usize)>,
260}
261
262// ── Scrolling ───────────────────────────────────────────────────
263
264/// Visual position and size of a laid-out block.
265///
266/// Returned by [`crate::DocumentFlow::block_visual_info`].
267pub struct BlockVisualInfo {
268    /// Block ID (matches `BlockSnapshot::block_id`).
269    pub block_id: usize,
270    /// Y position of the block's top edge relative to the document start, in pixels.
271    pub y: f32,
272    /// Total height of the block including margins, in pixels.
273    pub height: f32,
274}
275
276// ── Single-line API ────────────────────────────────────────────
277
278/// Text formatting parameters for the single-line layout API.
279///
280/// Controls font selection, size, and text color. All fields are optional
281/// and fall back to the typesetter's defaults (default font, default size,
282/// default text color).
283#[derive(Clone, Debug, Default)]
284pub struct TextFormat {
285    /// Font family name (e.g., "Noto Sans", "monospace").
286    /// None means use the default font.
287    pub font_family: Option<String>,
288    /// Font weight (100-900). Overrides `font_bold`.
289    pub font_weight: Option<u32>,
290    /// Shorthand for weight 700. Ignored if `font_weight` is set.
291    pub font_bold: Option<bool>,
292    /// Italic style.
293    pub font_italic: Option<bool>,
294    /// Font size in pixels. None means use the default size.
295    pub font_size: Option<f32>,
296    /// Text color (RGBA, 0.0-1.0). None means use the typesetter's text color.
297    pub color: Option<[f32; 4]>,
298}
299
300/// Result of [`crate::DocumentFlow::layout_single_line`].
301///
302/// Contains the measured dimensions and GPU-ready glyph quads for a
303/// single line of text. No flow layout, line breaking, or bidi analysis
304/// is performed.
305pub struct SingleLineResult {
306    /// Total advance width of the shaped text, in pixels.
307    pub width: f32,
308    /// Line height (ascent + descent + leading), in pixels.
309    pub height: f32,
310    /// Distance from the top of the line to the baseline, in pixels.
311    pub baseline: f32,
312    /// Distance from baseline to the top of the underline, in logical
313    /// pixels. Positive = below the baseline. Sourced from the primary
314    /// font's `post` table.
315    pub underline_offset: f32,
316    /// Underline line thickness in logical pixels. Sourced from the
317    /// primary font's stroke size.
318    pub underline_thickness: f32,
319    /// GPU-ready glyph quads, positioned at y=0 (no scroll offset).
320    pub glyphs: Vec<GlyphQuad>,
321    /// Per-glyph cache keys, parallel to `glyphs`. Callers that cache
322    /// glyph output externally should pass these back to
323    /// [`crate::TextFontService::touch_glyphs`] each frame to prevent the
324    /// atlas from evicting still-visible glyphs.
325    pub glyph_keys: Vec<crate::atlas::cache::GlyphCacheKey>,
326    /// Per-span bounding rectangles for markup-aware layout
327    /// ([`crate::DocumentFlow::layout_single_line_markup`]). Empty for
328    /// the plain-text layout path.
329    pub spans: Vec<LaidOutSpan>,
330}
331
332/// A single laid-out span produced by the markup-aware layout path.
333///
334/// When a link wraps across two paragraph lines, it produces two
335/// `LaidOutSpan` entries sharing the same URL and byte_range but with
336/// distinct `line_index` / `rect`.
337#[derive(Debug, Clone)]
338pub struct LaidOutSpan {
339    pub kind: LaidOutSpanKind,
340    /// Which wrapped line this span piece lives on (0 for single-line).
341    pub line_index: usize,
342    /// Local-space rect: `[x, y, width, height]`, same space as glyph quads.
343    pub rect: [f32; 4],
344    /// Byte range into the original markup source.
345    pub byte_range: std::ops::Range<usize>,
346}
347
348/// Kind discriminator for [`LaidOutSpan`].
349#[derive(Debug, Clone)]
350pub enum LaidOutSpanKind {
351    Text,
352    Link { url: String },
353}
354
355/// Result of [`crate::DocumentFlow::layout_paragraph`].
356///
357/// Contains the measured dimensions and GPU-ready glyph quads for a
358/// multi-line paragraph wrapped at a fixed width. Glyphs are positioned
359/// in paragraph-local coordinates: `x = 0` is the left edge of the
360/// paragraph, `y = 0` is the top of the first line's line box. The
361/// adapter should offset all glyph quads by the paragraph's screen
362/// position before drawing.
363pub struct ParagraphResult {
364    /// Width of the widest laid-out line, in pixels. May be less than the
365    /// `max_width` passed to `layout_paragraph` if the content is narrower.
366    pub width: f32,
367    /// Total stacked paragraph height in pixels — sum of line heights for
368    /// all emitted lines.
369    pub height: f32,
370    /// Distance from `y = 0` to the baseline of the first line, in pixels.
371    pub baseline_first: f32,
372    /// Number of lines actually emitted (respects `max_lines` when set).
373    pub line_count: usize,
374    /// Line height (single line's ascent + descent + leading), in pixels.
375    /// Useful for callers that need to reason about per-line geometry.
376    pub line_height: f32,
377    /// Distance from baseline to the top of the underline, in logical
378    /// pixels. Positive = below the baseline. Sourced from the primary
379    /// font's `post` table.
380    pub underline_offset: f32,
381    /// Underline line thickness in logical pixels. Sourced from the
382    /// primary font's stroke size.
383    pub underline_thickness: f32,
384    /// GPU-ready glyph quads in paragraph-local coordinates.
385    pub glyphs: Vec<GlyphQuad>,
386    /// Per-glyph cache keys, parallel to `glyphs`. See
387    /// [`SingleLineResult::glyph_keys`].
388    pub glyph_keys: Vec<crate::atlas::cache::GlyphCacheKey>,
389    /// Per-span bounding rectangles for markup-aware layout
390    /// ([`crate::DocumentFlow::layout_paragraph_markup`]). Empty for
391    /// the plain-text layout path.
392    pub spans: Vec<LaidOutSpan>,
393}
394
395impl RenderFrame {
396    pub(crate) fn new() -> Self {
397        Self {
398            atlas_dirty: false,
399            atlas_width: 0,
400            atlas_height: 0,
401            atlas_pixels: Vec::new(),
402            glyphs: Vec::new(),
403            images: Vec::new(),
404            decorations: Vec::new(),
405            block_glyphs: Vec::new(),
406            block_decorations: Vec::new(),
407            block_images: Vec::new(),
408            block_heights: std::collections::HashMap::new(),
409        }
410    }
411}
412
413// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
414// CharacterGeometry — accessibility per-character advance data
415// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
416
417/// Per-character advance geometry for a laid-out text run.
418///
419/// Consumed by accessibility layers that need to populate AccessKit's
420/// `character_positions` and `character_widths` on a `Role::TextRun`
421/// node so screen reader highlight cursors and screen magnifiers can
422/// track the caret at character granularity.
423///
424/// `position` is measured in run-local coordinates: the first
425/// character of the requested range sits at `position == 0.0`, and
426/// subsequent characters accumulate their advance widths. `width` is
427/// the advance width of each character, in the same units.
428#[derive(Debug, Clone, Copy, PartialEq)]
429pub struct CharacterGeometry {
430    pub position: f32,
431    pub width: f32,
432}