1use std::ops::Range;
2
3use densa_text::{Font, GlyphId, GlyphRun, PositionedGlyph};
4use parley::{Alignment, AlignmentOptions, FontData, Layout, PositionedLayoutItem};
5
6use crate::style::{check_width, ParagraphError, TextAlign};
7use densa_core::Color;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct LineBox {
12 pub baseline: f32,
14 pub top: f32,
16 pub bottom: f32,
18 pub left: f32,
20 pub width: f32,
22 pub text_range: Range<usize>,
24}
25
26pub(crate) type FaceKey = (usize, usize, u32);
33
34pub(crate) fn bytes_key(bytes: &[u8], index: u32) -> FaceKey {
36 (bytes.as_ptr() as usize, bytes.len(), index)
37}
38
39pub(crate) fn face_key(data: &FontData) -> FaceKey {
41 bytes_key(data.data.data(), data.index)
42}
43
44#[derive(Debug)]
46pub(crate) struct Face {
47 pub(crate) key: FaceKey,
48 pub(crate) font: Font,
49}
50
51#[derive(Debug, Clone, Copy)]
52struct RunRecord {
53 face: usize,
54 px: f32,
55 color: Color,
56 glyphs: (u32, u32),
57}
58
59pub struct Paragraph {
69 layout: Layout<Color>,
70 text_len: usize,
71 align: TextAlign,
72 max_width: Option<f32>,
73 faces: Vec<Face>,
74 glyphs: Vec<PositionedGlyph>,
75 runs: Vec<RunRecord>,
76 lines: Vec<LineBox>,
77}
78
79impl std::fmt::Debug for Paragraph {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("Paragraph")
83 .field("align", &self.align)
84 .field("max_width", &self.max_width)
85 .field("faces", &self.faces.len())
86 .field("glyphs", &self.glyphs.len())
87 .field("runs", &self.runs.len())
88 .field("lines", &self.lines)
89 .finish_non_exhaustive()
90 }
91}
92
93impl Paragraph {
94 pub(crate) fn new(
97 layout: Layout<Color>,
98 text_len: usize,
99 align: TextAlign,
100 max_width: Option<f32>,
101 resolve: &mut dyn FnMut(&FontData) -> Option<Font>,
102 ) -> Result<Self, ParagraphError> {
103 let mut paragraph = Self {
104 layout,
105 text_len,
106 align,
107 max_width,
108 faces: Vec::new(),
109 glyphs: Vec::new(),
110 runs: Vec::new(),
111 lines: Vec::new(),
112 };
113 paragraph.break_and_flatten(resolve)?;
114 Ok(paragraph)
115 }
116
117 pub(crate) fn layout(&self) -> &Layout<Color> {
119 &self.layout
120 }
121
122 pub(crate) fn text_len(&self) -> usize {
124 self.text_len
125 }
126
127 pub fn width(&self) -> f32 {
129 self.layout.width()
130 }
131
132 pub fn height(&self) -> f32 {
137 self.lines.last().map_or(0.0, |line| line.bottom)
138 }
139
140 pub fn max_width(&self) -> Option<f32> {
142 self.max_width
143 }
144
145 pub fn lines(&self) -> &[LineBox] {
147 &self.lines
148 }
149
150 pub fn glyph_count(&self) -> usize {
152 self.glyphs.len()
153 }
154
155 pub fn runs(&self) -> impl ExactSizeIterator<Item = GlyphRun<'_>> + '_ {
157 self.runs.iter().map(|run| {
158 let (start, end) = run.glyphs;
159 GlyphRun {
160 font: &self.faces[run.face].font,
161 px: run.px,
162 color: run.color,
163 glyphs: &self.glyphs[start as usize..end as usize],
164 }
165 })
166 }
167
168 pub fn reflow(&mut self, max_width: Option<f32>) -> Result<(), ParagraphError> {
174 self.max_width = check_width(max_width)?;
175 self.break_and_flatten(&mut |_| None)
177 }
178
179 fn break_and_flatten(
180 &mut self,
181 resolve: &mut dyn FnMut(&FontData) -> Option<Font>,
182 ) -> Result<(), ParagraphError> {
183 self.layout.break_all_lines(self.max_width);
184 let options = AlignmentOptions::default();
185 self.layout.align(alignment(self.align), options);
186
187 self.glyphs.clear();
188 self.runs.clear();
189 self.lines.clear();
190 for line in self.layout.lines() {
191 let metrics = line.metrics();
192 self.lines.push(LineBox {
193 baseline: metrics.baseline,
194 top: metrics.block_min_coord,
195 bottom: metrics.block_max_coord,
196 left: metrics.offset,
197 width: metrics.advance,
198 text_range: clamp(line.text_range(), self.text_len),
200 });
201 for item in line.items() {
202 let PositionedLayoutItem::GlyphRun(run) = item else {
203 continue; };
205 let data = run.run().font();
206 let key = face_key(data);
207 let face = match self.faces.iter().position(|f| f.key == key) {
208 Some(face) => face,
209 None => {
210 let font = resolve(data).ok_or(ParagraphError::UnreadableFont)?;
213 self.faces.push(Face { key, font });
214 self.faces.len() - 1
215 }
216 };
217 let start = self.glyphs.len() as u32;
219 self.glyphs
220 .extend(run.positioned_glyphs().map(|g| PositionedGlyph {
221 id: GlyphId(g.id),
222 x: g.x,
223 y: g.y,
224 }));
225 let end = self.glyphs.len() as u32;
226 self.runs.push(RunRecord {
227 face,
228 px: run.run().font_size(),
229 color: run.style().brush,
230 glyphs: (start, end),
231 });
232 }
233 }
234 Ok(())
235 }
236}
237
238fn clamp(range: Range<usize>, len: usize) -> Range<usize> {
239 range.start.min(len)..range.end.min(len)
240}
241
242fn alignment(align: TextAlign) -> Alignment {
243 match align {
244 TextAlign::Start => Alignment::Start,
245 TextAlign::End => Alignment::End,
246 TextAlign::Left => Alignment::Left,
247 TextAlign::Center => Alignment::Center,
248 TextAlign::Right => Alignment::Right,
249 TextAlign::Justify => Alignment::Justify,
250 }
251}