Skip to main content

blockworx_paint/
text.rs

1//! Text layout: the one thing a drawing cannot do for itself.
2//!
3//! Shaping, kerning and line breaking come from the host's own text engine
4//! rather than from an approximation of it, so a diagram exported with
5//! different line breaks than the canvas showed is unrepresentable. The
6//! backend implements [`TextLayout`]; the export consumes it.
7
8use blockworx_geom::{Vec2, WorldPx};
9
10use crate::{Font, FontChoice, Zoom};
11
12/// The glyph one column of a font is as wide as — the one CSS's `ch` unit
13/// measures.
14pub const COLUMN_GLYPH: &str = "0";
15
16/// A host's text engine, as the exporters need to read it.
17pub trait TextLayout {
18    /// The typeface this engine lays text out in. The SVG export traces its
19    /// outlines from that face's own bytes, so an engine that answered for a
20    /// different one would place one font's glyphs at another's pen positions.
21    fn typeface(&self) -> FontChoice;
22
23    /// Lay `text` out in `font`, word-wrapped at `wrap`
24    /// ([`WorldPx::UNBOUNDED`] for an unwrapped run). An exporter's engine
25    /// lays out at one pixel per point, so a resolution-independent format
26    /// inherits no display's DPI; the screen's lays out at the display's, so
27    /// a mark recorded through it lands where the screen draws it.
28    fn layout(&self, text: &str, font: &Font, wrap: WorldPx) -> Layout;
29}
30
31impl<T: TextLayout + ?Sized> TextLayout for &T {
32    fn typeface(&self) -> FontChoice {
33        (**self).typeface()
34    }
35
36    fn layout(&self, text: &str, font: &Font, wrap: WorldPx) -> Layout {
37        (**self).layout(text, font, wrap)
38    }
39}
40
41/// A laid-out run: what it measures, and where every glyph in it landed.
42#[derive(Clone, Debug)]
43pub struct Layout {
44    /// The run's bounding size — the widest row by the stack of all of them.
45    pub size: Vec2,
46    pub rows: Vec<Row>,
47}
48
49impl Layout {
50    /// This layout with every length divided by `zoom`: a run laid out at a
51    /// zoomed font, stated in the units it was asked for in.
52    #[must_use]
53    pub fn scaled_down(mut self, zoom: Zoom) -> Self {
54        let by = zoom.get();
55        self.size /= by;
56        for row in &mut self.rows {
57            row.pos /= by;
58            row.height /= by;
59            for glyph in &mut row.glyphs {
60                glyph.pos /= by;
61                glyph.advance /= by;
62                glyph.ascent /= by;
63            }
64        }
65        self
66    }
67}
68
69/// One row of a [`Layout`].
70#[derive(Clone, Debug)]
71pub struct Row {
72    /// The row's origin, relative to the layout's.
73    pub pos: Vec2,
74    /// The row's height — the line's, whether or not a glyph is on it.
75    pub height: f32,
76    /// Whether this row closes a paragraph — a row broken by a `\n` rather
77    /// than by the wrap width. The newline itself is no glyph of its own.
78    pub ends_with_newline: bool,
79    pub glyphs: Vec<Glyph>,
80}
81
82/// A glyph's index in its face — the identity a shaper resolves, which the
83/// character alone does not carry: `fi` in a face with `liga` is one glyph
84/// filed under two characters.
85#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
86pub struct GlyphId(pub u16);
87
88/// One placed glyph. A layout records the *character* a glyph stands for, not
89/// the glyph a shaper chose for it, so a many-to-one substitution arrives as
90/// its head plus zero-[`advance`](Self::advance) continuations.
91#[derive(Clone, Copy, Debug)]
92pub struct Glyph {
93    pub chr: char,
94    /// The glyph the shaper chose, where the engine reports one: `None` at a
95    /// continuation, whose ink is already drawn at its head, and throughout a
96    /// layout from an engine that reports characters only.
97    pub id: Option<GlyphId>,
98    /// Baseline position, relative to the row's origin.
99    pub pos: Vec2,
100    /// The pen advance this glyph carries.
101    pub advance: f32,
102    /// How far the font's baseline sits below the line's top edge.
103    pub ascent: f32,
104}