Skip to main content

fret_render_text/
prepare_layout.rs

1use crate::geometry::{
2    TextLineCluster, caret_stops_for_slice, metrics_from_wrapped_lines,
3    shaped_line_visual_x_bounds_px,
4};
5use crate::line_layout::TextLineLayout;
6use crate::parley_shaper::{ParleyGlyph, ShapedCluster};
7use crate::wrapper::WrappedLayout;
8use fret_core::{TextAlign, TextConstraints, TextMetrics, geometry::Px};
9use std::ops::Range;
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
13pub struct PreparedLine {
14    layout: TextLineLayout,
15    glyphs: Vec<ParleyGlyph>,
16}
17
18#[derive(Debug, Clone)]
19pub struct PreparedLayout {
20    kept_end: usize,
21    metrics: TextMetrics,
22    lines: Vec<PreparedLine>,
23    first_line_caret_stops: Vec<(usize, Px)>,
24    missing_glyphs: u32,
25}
26
27impl PreparedLine {
28    fn new(layout: TextLineLayout, glyphs: Vec<ParleyGlyph>) -> Self {
29        Self { layout, glyphs }
30    }
31
32    pub fn layout(&self) -> &TextLineLayout {
33        &self.layout
34    }
35
36    pub fn glyphs(&self) -> &[ParleyGlyph] {
37        &self.glyphs
38    }
39
40    pub fn into_parts(self) -> (TextLineLayout, Vec<ParleyGlyph>) {
41        (self.layout, self.glyphs)
42    }
43}
44
45impl PreparedLayout {
46    fn new(
47        kept_end: usize,
48        metrics: TextMetrics,
49        lines: Vec<PreparedLine>,
50        first_line_caret_stops: Vec<(usize, Px)>,
51        missing_glyphs: u32,
52    ) -> Self {
53        Self {
54            kept_end,
55            metrics,
56            lines,
57            first_line_caret_stops,
58            missing_glyphs,
59        }
60    }
61
62    pub fn kept_end(&self) -> usize {
63        self.kept_end
64    }
65
66    pub fn metrics(&self) -> TextMetrics {
67        self.metrics
68    }
69
70    pub fn lines(&self) -> &[PreparedLine] {
71        &self.lines
72    }
73
74    pub fn first_line_caret_stops(&self) -> &[(usize, Px)] {
75        &self.first_line_caret_stops
76    }
77
78    pub fn missing_glyphs(&self) -> u32 {
79        self.missing_glyphs
80    }
81
82    pub fn into_parts(self) -> (usize, TextMetrics, Vec<PreparedLine>, Vec<(usize, Px)>, u32) {
83        (
84            self.kept_end,
85            self.metrics,
86            self.lines,
87            self.first_line_caret_stops,
88            self.missing_glyphs,
89        )
90    }
91}
92
93fn align_offset_px_for_line(
94    constraints: TextConstraints,
95    scale: f32,
96    line_min_x_px: f32,
97    line_visual_width_px: f32,
98) -> f32 {
99    let container_width_px = constraints
100        .max_width
101        .map(|w| w.0 * scale)
102        .unwrap_or_else(|| line_visual_width_px.max(0.0));
103    let slack_px = (container_width_px - line_visual_width_px.max(0.0)).max(0.0);
104    let target_left_px = match constraints.align {
105        TextAlign::Start => 0.0,
106        TextAlign::Center => slack_px * 0.5,
107        TextAlign::End => slack_px,
108    };
109    target_left_px - line_min_x_px
110}
111
112fn clusters_for_line(
113    line_clusters: &[ShapedCluster],
114    line_range: Range<usize>,
115    kept_end: usize,
116    line_align_offset_px: f32,
117    scale: f32,
118) -> Arc<[TextLineCluster]> {
119    if line_clusters.is_empty() {
120        return Arc::from([]);
121    }
122
123    let mut out: Vec<TextLineCluster> = Vec::with_capacity(line_clusters.len());
124    for c in line_clusters {
125        let text_range = c.text_range();
126        let start = (line_range.start + text_range.start).min(kept_end);
127        let end = (line_range.start + text_range.end).min(kept_end);
128        if start >= end {
129            continue;
130        }
131
132        let x0 = ((c.x0() + line_align_offset_px) / scale).max(0.0);
133        let x1 = ((c.x1() + line_align_offset_px) / scale).max(0.0);
134        let x0 = if x0.is_finite() { Px(x0) } else { Px(0.0) };
135        let x1 = if x1.is_finite() { Px(x1) } else { Px(0.0) };
136
137        out.push(TextLineCluster::new(start..end, x0, x1, c.is_rtl()));
138    }
139
140    Arc::from(out)
141}
142
143pub fn prepare_layout_from_wrapped(
144    text: &str,
145    wrapped: WrappedLayout,
146    constraints: TextConstraints,
147    scale: f32,
148    snap_vertical: bool,
149) -> PreparedLayout {
150    let (_, kept_end, line_ranges, mut wrapped_lines) = wrapped.into_parts();
151
152    let first_baseline_px = wrapped_lines
153        .first()
154        .map(|l| l.baseline().max(0.0))
155        .unwrap_or(0.0);
156    let first_baseline_px = if snap_vertical && let Some(first) = wrapped_lines.first() {
157        let top_px = 0.0_f32;
158        let bottom_px = (top_px + first.line_height().max(0.0)).round().max(top_px);
159        let height_px = (bottom_px - top_px).max(0.0);
160        (top_px + first.baseline().max(0.0))
161            .round()
162            .clamp(top_px, top_px + height_px)
163    } else {
164        first_baseline_px
165    };
166
167    let metrics = metrics_from_wrapped_lines(&wrapped_lines, scale);
168
169    let mut out_lines: Vec<PreparedLine> = Vec::with_capacity(wrapped_lines.len().max(1));
170    let mut first_line_caret_stops: Vec<(usize, Px)> = Vec::new();
171    let mut missing_glyphs: u32 = 0;
172
173    let mut line_top_px = 0.0_f32;
174
175    for (i, (range, mut line)) in line_ranges
176        .into_iter()
177        .zip(wrapped_lines.drain(..))
178        .enumerate()
179    {
180        if snap_vertical {
181            line_top_px = line_top_px.round();
182        }
183
184        let line_height_px_raw = line.line_height().max(0.0);
185        let line_baseline_px_raw = line.baseline().max(0.0);
186
187        let (line_height_px, baseline_pos_px) = if snap_vertical {
188            let bottom_px = (line_top_px + line_height_px_raw).round().max(line_top_px);
189            let height_px = (bottom_px - line_top_px).max(0.0);
190            let baseline_pos_px = (line_top_px + line_baseline_px_raw)
191                .round()
192                .clamp(line_top_px, line_top_px + height_px);
193            (height_px, baseline_pos_px)
194        } else {
195            (line_height_px_raw, line_top_px + line_baseline_px_raw)
196        };
197
198        let line_offset_px = baseline_pos_px - first_baseline_px;
199
200        let slice = &text[range.clone()];
201        let (line_min_x_px, line_max_x_px) = shaped_line_visual_x_bounds_px(&line);
202        let line_visual_width_px = (line_max_x_px - line_min_x_px).max(0.0);
203        let line_align_offset_px =
204            align_offset_px_for_line(constraints, scale, line_min_x_px, line_visual_width_px);
205        let line_align_offset = Px(line_align_offset_px / scale);
206
207        let clusters = clusters_for_line(
208            line.clusters(),
209            range.clone(),
210            kept_end,
211            line_align_offset_px,
212            scale,
213        );
214
215        let mut caret_stops = caret_stops_for_slice(
216            slice,
217            range.start,
218            line.clusters(),
219            line_visual_width_px.max(0.0),
220            scale,
221            kept_end,
222        );
223        if line_align_offset.0 != 0.0 {
224            for (_, x) in caret_stops.iter_mut() {
225                *x = Px(x.0 + line_align_offset.0);
226            }
227        }
228        if i == 0 {
229            first_line_caret_stops = caret_stops.clone();
230        }
231
232        for g in line.glyphs_mut().iter_mut() {
233            if g.id() == 0 {
234                missing_glyphs = missing_glyphs.saturating_add(1);
235            }
236            g.set_x(g.x() + line_align_offset_px);
237            g.set_y(g.y() + line_offset_px);
238            let glyph_range = g.text_range();
239            g.set_text_range((range.start + glyph_range.start)..(range.start + glyph_range.end));
240        }
241
242        let layout = TextLineLayout::new(
243            range.start,
244            range.end.min(kept_end),
245            Px((line_visual_width_px / scale).max(0.0)),
246            Px((line_top_px / scale).max(0.0)),
247            Px((baseline_pos_px / scale).max(0.0)),
248            Px(((line_height_px / scale).max(0.0)).max(1.0)),
249            Px((line.ascent().abs().max(0.0) / scale).max(0.0)),
250            Px((line.descent().abs().max(0.0) / scale).max(0.0)),
251            Px((line.ink_ascent().abs().max(0.0) / scale).max(0.0)),
252            Px((line.ink_descent().abs().max(0.0) / scale).max(0.0)),
253            caret_stops,
254            clusters,
255        );
256
257        out_lines.push(PreparedLine::new(layout, line.take_glyphs()));
258
259        line_top_px += line_height_px;
260    }
261
262    // Safety: ensure we always return at least one line layout even for empty text.
263    if out_lines.is_empty() {
264        let caret_stops = vec![(0, Px(0.0))];
265        first_line_caret_stops = caret_stops.clone();
266        out_lines.push(PreparedLine::new(
267            TextLineLayout::new(
268                0,
269                0,
270                Px(0.0),
271                Px(0.0),
272                Px(0.0),
273                Px(1.0),
274                Px(0.0),
275                Px(0.0),
276                Px(0.0),
277                Px(0.0),
278                caret_stops,
279                Arc::from([]),
280            ),
281            Vec::new(),
282        ));
283    }
284
285    PreparedLayout::new(
286        kept_end,
287        metrics,
288        out_lines,
289        first_line_caret_stops,
290        missing_glyphs,
291    )
292}