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}