Skip to main content

cosmic_text/
layout.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use core::fmt::Display;
4
5use core::ops::Range;
6
7use crate::{math, CacheKey, CacheKeyFlags, Color, GlyphDecorationData, LayoutRun};
8#[cfg(not(feature = "std"))]
9use alloc::vec::Vec;
10
11#[cfg(not(feature = "std"))]
12use core_maths::CoreFloat;
13
14/// A laid out glyph
15#[derive(Clone, Debug)]
16pub struct LayoutGlyph {
17    /// Start index of cluster in original line
18    pub start: usize,
19    /// End index of cluster in original line
20    pub end: usize,
21    /// Font size of the glyph
22    pub font_size: f32,
23    /// Font weight of the glyph
24    pub font_weight: fontdb::Weight,
25    /// Line height of the glyph, will override buffer setting
26    pub line_height_opt: Option<f32>,
27    /// Font id of the glyph
28    pub font_id: fontdb::ID,
29    /// Font id of the glyph
30    pub glyph_id: u16,
31    /// X offset of hitbox
32    pub x: f32,
33    /// Y offset of hitbox
34    pub y: f32,
35    /// Width of hitbox
36    pub w: f32,
37    /// Ascender of the glyph in pixels
38    ///
39    /// This is the distance from the baseline to the top of the glyph
40    pub ascender: f32,
41    /// Descender of the glyph in pixels
42    ///
43    /// This is the distance from the baseline to the bottom of the glyph
44    pub descender: f32,
45    /// Unicode `BiDi` embedding level, character is left-to-right if `level` is divisible by 2
46    pub level: unicode_bidi::Level,
47    /// X offset in line
48    ///
49    /// If you are dealing with physical coordinates, use [`Self::physical`] to obtain a
50    /// [`PhysicalGlyph`] for rendering.
51    ///
52    /// This offset is useful when you are dealing with logical units and you do not care or
53    /// cannot guarantee pixel grid alignment. For instance, when you want to use the glyphs
54    /// for vectorial text, apply linear transformations to the layout, etc.
55    pub x_offset: f32,
56    /// Y offset in line
57    ///
58    /// If you are dealing with physical coordinates, use [`Self::physical`] to obtain a
59    /// [`PhysicalGlyph`] for rendering.
60    ///
61    /// This offset is useful when you are dealing with logical units and you do not care or
62    /// cannot guarantee pixel grid alignment. For instance, when you want to use the glyphs
63    /// for vectorial text, apply linear transformations to the layout, etc.
64    pub y_offset: f32,
65    /// Optional color override
66    pub color_opt: Option<Color>,
67    /// Metadata from `Attrs`
68    pub metadata: usize,
69    /// [`CacheKeyFlags`]
70    pub cache_key_flags: CacheKeyFlags,
71}
72
73/// A span of consecutive glyphs sharing the same text decoration.
74#[derive(Clone, Debug, PartialEq)]
75pub struct DecorationSpan {
76    /// Range of glyph indices in `LayoutLine::glyphs` covered by this span
77    pub glyph_range: Range<usize>,
78    /// The decoration config and metrics
79    pub data: GlyphDecorationData,
80    /// Fallback color from the first glyph's `color_opt`
81    pub color_opt: Option<Color>,
82    /// Font size from the first glyph (used to scale EM-unit metrics)
83    pub font_size: f32,
84}
85
86impl DecorationSpan {
87    /// The horizontal extent of this span's glyphs within `run`, which must
88    /// be the layout run containing the span. Empty if the span covers no
89    /// glyphs.
90    pub fn x_range(&self, run: &LayoutRun) -> Range<f32> {
91        // Compute the extent as min/max over all glyphs, not first/last,
92        // because RTL paragraphs store glyphs in right-to-left order.
93        let mut x_min = f32::INFINITY;
94        let mut x_max = f32::NEG_INFINITY;
95        for glyph in &run.glyphs[self.glyph_range.clone()] {
96            x_min = x_min.min(glyph.x);
97            x_max = x_max.max(glyph.x + glyph.w);
98        }
99
100        if x_max > x_min {
101            x_min..x_max
102        } else {
103            0.0..0.0
104        }
105    }
106}
107
108#[derive(Clone, Debug)]
109pub struct PhysicalGlyph {
110    /// Cache key, see [`CacheKey`]
111    pub cache_key: CacheKey,
112    /// Integer component of X offset in line
113    pub x: i32,
114    /// Integer component of Y offset in line
115    pub y: i32,
116}
117
118impl LayoutGlyph {
119    pub fn physical(&self, offset: (f32, f32), scale: f32) -> PhysicalGlyph {
120        let x_offset = self.font_size * self.x_offset;
121        let y_offset = self.font_size * self.y_offset;
122
123        let (cache_key, x, y) = CacheKey::new(
124            self.font_id,
125            self.glyph_id,
126            self.font_size * scale,
127            (
128                (self.x + x_offset) * scale + offset.0,
129                math::truncf((self.y - y_offset) * scale + offset.1), // Hinting in Y axis
130            ),
131            self.font_weight,
132            self.cache_key_flags,
133        );
134
135        PhysicalGlyph { cache_key, x, y }
136    }
137}
138
139/// A line of laid out glyphs
140#[derive(Clone, Debug)]
141pub struct LayoutLine {
142    /// Width of the line
143    pub w: f32,
144    /// Maximum ascent of the glyphs in line
145    pub max_ascent: f32,
146    /// Maximum descent of the glyphs in line
147    pub max_descent: f32,
148    /// Maximum line height of any spans in line
149    pub line_height_opt: Option<f32>,
150    /// Glyphs in line
151    pub glyphs: Vec<LayoutGlyph>,
152    /// Text decoration spans covering ranges of glyphs
153    pub decorations: Vec<DecorationSpan>,
154}
155
156/// Wrapping mode
157#[derive(Debug, Eq, PartialEq, Clone, Copy)]
158pub enum Wrap {
159    /// No wrapping
160    None,
161    /// Wraps at a glyph level
162    Glyph,
163    /// Wraps at the word level
164    Word,
165    /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself
166    WordOrGlyph,
167}
168
169impl Display for Wrap {
170    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171        match self {
172            Self::None => write!(f, "No Wrap"),
173            Self::Word => write!(f, "Word Wrap"),
174            Self::WordOrGlyph => write!(f, "Word Wrap or Character"),
175            Self::Glyph => write!(f, "Character"),
176        }
177    }
178}
179
180/// Align or justify
181#[derive(Debug, Eq, PartialEq, Clone, Copy)]
182pub enum Align {
183    Left,
184    Right,
185    Center,
186    Justified,
187    End,
188}
189
190impl Display for Align {
191    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
192        match self {
193            Self::Left => write!(f, "Left"),
194            Self::Right => write!(f, "Right"),
195            Self::Center => write!(f, "Center"),
196            Self::Justified => write!(f, "Justified"),
197            Self::End => write!(f, "End"),
198        }
199    }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Default)]
203pub enum Ellipsize {
204    /// No Ellipsizing
205    #[default]
206    None,
207    /// Ellipsizes the start of the last visual line that fits within the `EllipsizeHeightLimit`
208    Start(EllipsizeHeightLimit),
209    /// Ellipsizes the middle of the last visual line that fits within the `EllipsizeHeightLimit`.
210    Middle(EllipsizeHeightLimit),
211    /// Ellipsizes the end of the last visual line that fits within the `EllipsizeHeightLimit`.
212    End(EllipsizeHeightLimit),
213}
214
215#[derive(Debug, Clone, Copy, PartialEq)]
216pub enum EllipsizeHeightLimit {
217    /// Number of lines to show before ellipsizing the rest. Only works if `Wrap` is NOT set to
218    /// `Wrap::None`. Otherwise, it will be ignored and the behavior will be the same as `Lines(1)`
219    Lines(usize),
220    /// Ellipsizes the last line that fits within the given height limit. If `Wrap` is set to
221    /// `Wrap::None`, the behavior will be the same as `Lines(1)`
222    Height(f32),
223}
224
225/// Metrics hinting strategy
226#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
227pub enum Hinting {
228    /// No metrics hinting.
229    ///
230    /// Glyphs will have subpixel coordinates.
231    ///
232    /// This is the default.
233    #[default]
234    Disabled,
235
236    /// Metrics hinting.
237    ///
238    /// Glyphs will be snapped to integral coordinates in the X-axis during layout.
239    /// This can improve readability for smaller text and/or low-DPI screens.
240    ///
241    /// However, in order to get the right effect, you must use physical coordinates
242    /// during layout and avoid further scaling when rendering. Otherwise, the rounding
243    /// errors can accumulate and glyph distances may look erratic.
244    ///
245    /// In other words, metrics hinting makes layouting dependent of the target
246    /// resolution.
247    Enabled,
248}