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};
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 /// Unicode `BiDi` embedding level, character is left-to-right if `level` is divisible by 2
38 pub level: unicode_bidi::Level,
39 /// X offset in line
40 ///
41 /// If you are dealing with physical coordinates, use [`Self::physical`] to obtain a
42 /// [`PhysicalGlyph`] for rendering.
43 ///
44 /// This offset is useful when you are dealing with logical units and you do not care or
45 /// cannot guarantee pixel grid alignment. For instance, when you want to use the glyphs
46 /// for vectorial text, apply linear transformations to the layout, etc.
47 pub x_offset: f32,
48 /// Y offset in line
49 ///
50 /// If you are dealing with physical coordinates, use [`Self::physical`] to obtain a
51 /// [`PhysicalGlyph`] for rendering.
52 ///
53 /// This offset is useful when you are dealing with logical units and you do not care or
54 /// cannot guarantee pixel grid alignment. For instance, when you want to use the glyphs
55 /// for vectorial text, apply linear transformations to the layout, etc.
56 pub y_offset: f32,
57 /// Optional color override
58 pub color_opt: Option<Color>,
59 /// Metadata from `Attrs`
60 pub metadata: usize,
61 /// [`CacheKeyFlags`]
62 pub cache_key_flags: CacheKeyFlags,
63}
64
65/// A span of consecutive glyphs sharing the same text decoration.
66#[derive(Clone, Debug, PartialEq)]
67pub struct DecorationSpan {
68 /// Range of glyph indices in `LayoutLine::glyphs` covered by this span
69 pub glyph_range: Range<usize>,
70 /// The decoration config and metrics
71 pub data: GlyphDecorationData,
72 /// Fallback color from the first glyph's `color_opt`
73 pub color_opt: Option<Color>,
74 /// Font size from the first glyph (used to scale EM-unit metrics)
75 pub font_size: f32,
76}
77
78#[derive(Clone, Debug)]
79pub struct PhysicalGlyph {
80 /// Cache key, see [`CacheKey`]
81 pub cache_key: CacheKey,
82 /// Integer component of X offset in line
83 pub x: i32,
84 /// Integer component of Y offset in line
85 pub y: i32,
86}
87
88impl LayoutGlyph {
89 pub fn physical(&self, offset: (f32, f32), scale: f32) -> PhysicalGlyph {
90 let x_offset = self.font_size * self.x_offset;
91 let y_offset = self.font_size * self.y_offset;
92
93 let (cache_key, x, y) = CacheKey::new(
94 self.font_id,
95 self.glyph_id,
96 self.font_size * scale,
97 (
98 (self.x + x_offset).mul_add(scale, offset.0),
99 math::truncf((self.y - y_offset).mul_add(scale, offset.1)), // Hinting in Y axis
100 ),
101 self.font_weight,
102 self.cache_key_flags,
103 );
104
105 PhysicalGlyph { cache_key, x, y }
106 }
107}
108
109/// A line of laid out glyphs
110#[derive(Clone, Debug)]
111pub struct LayoutLine {
112 /// Width of the line
113 pub w: f32,
114 /// Maximum ascent of the glyphs in line
115 pub max_ascent: f32,
116 /// Maximum descent of the glyphs in line
117 pub max_descent: f32,
118 /// Maximum line height of any spans in line
119 pub line_height_opt: Option<f32>,
120 /// Glyphs in line
121 pub glyphs: Vec<LayoutGlyph>,
122 /// Text decoration spans covering ranges of glyphs
123 pub decorations: Vec<DecorationSpan>,
124}
125
126/// Wrapping mode
127#[derive(Debug, Eq, PartialEq, Clone, Copy)]
128pub enum Wrap {
129 /// No wrapping
130 None,
131 /// Wraps at a glyph level
132 Glyph,
133 /// Wraps at the word level
134 Word,
135 /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself
136 WordOrGlyph,
137}
138
139impl Display for Wrap {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 match self {
142 Self::None => write!(f, "No Wrap"),
143 Self::Word => write!(f, "Word Wrap"),
144 Self::WordOrGlyph => write!(f, "Word Wrap or Character"),
145 Self::Glyph => write!(f, "Character"),
146 }
147 }
148}
149
150/// Align or justify
151#[derive(Debug, Eq, PartialEq, Clone, Copy)]
152pub enum Align {
153 Left,
154 Right,
155 Center,
156 Justified,
157 End,
158}
159
160impl Display for Align {
161 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162 match self {
163 Self::Left => write!(f, "Left"),
164 Self::Right => write!(f, "Right"),
165 Self::Center => write!(f, "Center"),
166 Self::Justified => write!(f, "Justified"),
167 Self::End => write!(f, "End"),
168 }
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Default)]
173pub enum Ellipsize {
174 /// No Ellipsizing
175 #[default]
176 None,
177 /// Ellipsizes the start of the last visual line that fits within the `EllipsizeHeightLimit`
178 Start(EllipsizeHeightLimit),
179 /// Ellipsizes the middle of the last visual line that fits within the `EllipsizeHeightLimit`.
180 Middle(EllipsizeHeightLimit),
181 /// Ellipsizes the end of the last visual line that fits within the `EllipsizeHeightLimit`.
182 End(EllipsizeHeightLimit),
183}
184
185#[derive(Debug, Clone, Copy, PartialEq)]
186pub enum EllipsizeHeightLimit {
187 /// Number of lines to show before ellipsizing the rest. Only works if `Wrap` is NOT set to
188 /// `Wrap::None`. Otherwise, it will be ignored and the behavior will be the same as `Lines(1)`
189 Lines(usize),
190 /// Ellipsizes the last line that fits within the given height limit. If `Wrap` is set to
191 /// `Wrap::None`, the behavior will be the same as `Lines(1)`
192 Height(f32),
193}
194
195/// Metrics hinting strategy
196#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
197pub enum Hinting {
198 /// No metrics hinting.
199 ///
200 /// Glyphs will have subpixel coordinates.
201 ///
202 /// This is the default.
203 #[default]
204 Disabled,
205
206 /// Metrics hinting.
207 ///
208 /// Glyphs will be snapped to integral coordinates in the X-axis during layout.
209 /// This can improve readability for smaller text and/or low-DPI screens.
210 ///
211 /// However, in order to get the right effect, you must use physical coordinates
212 /// during layout and avoid further scaling when rendering. Otherwise, the rounding
213 /// errors can accumulate and glyph distances may look erratic.
214 ///
215 /// In other words, metrics hinting makes layouting dependent of the target
216 /// resolution.
217 Enabled,
218}