1use std::cell::RefCell;
4use std::collections::HashMap;
5use std::rc::{Rc, Weak};
6
7use crate::pretext_layout_line_text::{
8 build_line_text_from_range, line_has_discretionary_hyphen, segment_grapheme_offsets,
9};
10use crate::*;
11
12#[derive(Clone, Debug, PartialEq)]
13pub struct ShapedLayoutLine {
14 pub shaped_run: Rc<PretextShapedRun>,
15 pub width: f64,
16 pub start: LayoutCursor,
17 pub end: LayoutCursor,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct ShapedRichInlineFragment {
22 pub item_index: usize,
23 pub shaped_run: Rc<PretextShapedRun>,
24 pub gap_before: f64,
25 pub occupied_width: f64,
26 pub start: LayoutCursor,
27 pub end: LayoutCursor,
28}
29
30#[derive(Clone, Debug, PartialEq)]
31pub struct ShapedRichInlineLine {
32 pub fragments: Vec<ShapedRichInlineFragment>,
33 pub width: f64,
34 pub end: RichInlineCursor,
35}
36
37pub struct PreparedShapedText {
40 prepared: Rc<PreparedTextWithSegments>,
41 shared: Rc<PreparedShapingData>,
42}
43
44struct PreparedShapingData {
47 direction: PretextTextDirection,
48 text: String,
49 shaped_run: Rc<PretextShapedRun>,
50 grapheme_boundary_text_indexes: Vec<Vec<usize>>,
51 text_utf16_length: usize,
52 state: Rc<ShaperState>,
53 line_runs: RefCell<HashMap<(LayoutCursor, LayoutCursor), Rc<PretextShapedRun>>>,
54}
55
56pub(crate) struct PreparedShapedCacheEntry {
57 prepared: Weak<PreparedTextWithSegments>,
58 shared: Rc<PreparedShapingData>,
59 wrapper: Weak<PreparedShapedText>,
60}
61
62impl PreparedShapedText {
63 pub fn prepared(&self) -> &Rc<PreparedTextWithSegments> {
64 &self.prepared
65 }
66 pub fn direction(&self) -> PretextTextDirection {
67 self.shared.direction
68 }
69 pub fn options(&self) -> PretextShapeOptions {
70 PretextShapeOptions {
71 direction: self.shared.direction,
72 }
73 }
74 pub fn text(&self) -> &str {
75 &self.shared.text
76 }
77 pub fn shaped_run(&self) -> &Rc<PretextShapedRun> {
78 &self.shared.shaped_run
79 }
80
81 fn get_or_create_line_run(
82 &self,
83 start: LayoutCursor,
84 end: LayoutCursor,
85 ) -> Result<Rc<PretextShapedRun>, PretextError> {
86 validate_range(&self.prepared, start, end)?;
87 let key = (start, end);
88 if let Some(cached) = self.shared.line_runs.borrow().get(&key) {
89 return Ok(cached.clone());
90 }
91 let shaped = if can_slice_prepared_shaped_run(&self.prepared, start, end) {
92 Rc::new(slice_shaped_run(
93 &self.shared.shaped_run,
94 self.text_index(start),
95 self.text_index(end),
96 ))
97 } else {
98 let text = build_line_text_from_range(&self.prepared, start, end, false);
99 self.shared.state.shape_text(&text, self.options())?
100 };
101 Ok(self
103 .shared
104 .line_runs
105 .borrow_mut()
106 .entry(key)
107 .or_insert(shaped)
108 .clone())
109 }
110
111 fn text_index(&self, cursor: LayoutCursor) -> usize {
112 self.shared
113 .grapheme_boundary_text_indexes
114 .get(cursor.segment_index)
115 .and_then(|boundaries| {
116 boundaries.get(
117 cursor
118 .grapheme_index
119 .min(boundaries.len().saturating_sub(1)),
120 )
121 })
122 .copied()
123 .unwrap_or(self.shared.text_utf16_length)
124 }
125}
126
127impl PretextLayout {
128 pub fn shape_text(
129 &self,
130 text: &str,
131 font: &str,
132 options: PretextShapeOptions,
133 ) -> Result<Rc<PretextShapedRun>, PretextError> {
134 self.get_shaper_state(font)?.shape_text(text, options)
135 }
136
137 pub fn try_shape_text(
140 &self,
141 text: &str,
142 font: &str,
143 options: PretextShapeOptions,
144 ) -> Option<Rc<PretextShapedRun>> {
145 self.shape_text(text, font, options).ok()
146 }
147
148 pub fn shape_prepared_text(
149 &self,
150 prepared: Rc<PreparedTextWithSegments>,
151 options: PretextShapeOptions,
152 ) -> Result<Rc<PreparedShapedText>, PretextError> {
153 let key = (Rc::as_ptr(&prepared) as usize, options.direction);
154 let cached = {
155 let cache = self.prepared_shaped.borrow();
156 cache.get(&key).and_then(|entry| {
157 entry
158 .prepared
159 .upgrade()
160 .filter(|source| Rc::ptr_eq(source, &prepared))
161 .map(|_| (entry.shared.clone(), entry.wrapper.upgrade()))
162 })
163 };
164 if let Some((shared, wrapper)) = cached {
165 if let Some(wrapper) = wrapper {
166 return Ok(wrapper);
167 }
168 let wrapper = Rc::new(PreparedShapedText { prepared, shared });
169 if let Some(entry) = self.prepared_shaped.borrow_mut().get_mut(&key) {
170 entry.wrapper = Rc::downgrade(&wrapper);
171 }
172 return Ok(wrapper);
173 }
174 let generation = self.cache_generation.get();
175 let state = self.get_shaper_state(prepared.font())?;
176 let (text, grapheme_boundary_text_indexes, text_utf16_length) =
177 build_prepared_shaping_text(&prepared);
178 let shaped_run = state.shape_text(&text, options)?;
179 let shared = Rc::new(PreparedShapingData {
180 direction: options.direction,
181 text,
182 shaped_run,
183 grapheme_boundary_text_indexes,
184 text_utf16_length,
185 state,
186 line_runs: RefCell::new(HashMap::new()),
187 });
188 let shaped = Rc::new(PreparedShapedText { prepared, shared });
189 if generation == self.cache_generation.get() {
190 let retired = {
191 let mut cache = self.prepared_shaped.borrow_mut();
192 let dead_keys: Vec<_> = cache
193 .iter()
194 .filter(|(_, value)| value.prepared.strong_count() == 0)
195 .map(|(key, _)| *key)
196 .collect();
197 let mut retired: Vec<_> = dead_keys
198 .iter()
199 .filter_map(|key| cache.remove(key))
200 .collect();
201 retired.extend(cache.insert(
202 key,
203 PreparedShapedCacheEntry {
204 prepared: Rc::downgrade(&shaped.prepared),
205 shared: shaped.shared.clone(),
206 wrapper: Rc::downgrade(&shaped),
207 },
208 ));
209 retired
210 };
211 drop(retired);
213 }
214 Ok(shaped)
215 }
216
217 pub fn layout_next_shaped_line(
218 prepared: &PreparedShapedText,
219 start: LayoutCursor,
220 max_width: f64,
221 ) -> Result<Option<ShapedLayoutLine>, PretextError> {
222 Self::layout_next_line_range(&prepared.prepared, start, max_width)
223 .map(|line| Self::materialize_shaped_line_range(prepared, &line))
224 .transpose()
225 }
226
227 pub fn materialize_shaped_line_range(
228 prepared: &PreparedShapedText,
229 line: &LayoutLineRange,
230 ) -> Result<ShapedLayoutLine, PretextError> {
231 Ok(ShapedLayoutLine {
232 shaped_run: prepared.get_or_create_line_run(line.start, line.end)?,
233 width: line.width,
234 start: line.start,
235 end: line.end,
236 })
237 }
238
239 pub fn materialize_shaped_line_range_with_options(
241 &self,
242 prepared: Rc<PreparedTextWithSegments>,
243 line: &LayoutLineRange,
244 options: PretextShapeOptions,
245 ) -> Result<ShapedLayoutLine, PretextError> {
246 let shaped = self.shape_prepared_text(prepared, options)?;
247 Self::materialize_shaped_line_range(&shaped, line)
248 }
249
250 pub fn materialize_shaped_rich_inline_line_range(
251 &self,
252 prepared: &PreparedRichInline,
253 line: &RichInlineLineRange,
254 options: PretextShapeOptions,
255 ) -> Result<ShapedRichInlineLine, PretextError> {
256 let mut fragments = Vec::with_capacity(line.fragments.len());
257 for fragment in &line.fragments {
258 let item = prepared.prepared_item(fragment.item_index).ok_or_else(|| {
259 PretextError::InvalidInput("rich-inline fragment refers to a missing item".into())
260 })?;
261 let shaped = self.shape_prepared_text(item.clone(), options)?;
262 fragments.push(ShapedRichInlineFragment {
263 item_index: fragment.item_index,
264 shaped_run: shaped.get_or_create_line_run(fragment.start, fragment.end)?,
265 gap_before: fragment.gap_before,
266 occupied_width: fragment.occupied_width,
267 start: fragment.start,
268 end: fragment.end,
269 });
270 }
271 Ok(ShapedRichInlineLine {
272 fragments,
273 width: line.width,
274 end: line.end,
275 })
276 }
277
278 fn get_shaper_state(&self, font: &str) -> Result<Rc<ShaperState>, PretextError> {
279 if let Some(cached) = self.shaper_states.borrow().get(font) {
280 return Ok(cached.clone());
281 }
282 let generation = self.cache_generation.get();
283 let factory = self.text_shaper_factory()?;
284 let state = Rc::new(ShaperState {
285 shaper: factory.create_shaper(font)?,
286 cache: RefCell::new(HashMap::new()),
287 });
288 if generation == self.cache_generation.get() {
289 let displaced = self
290 .shaper_states
291 .borrow_mut()
292 .insert(font.to_owned(), state.clone());
293 drop(displaced);
294 }
295 Ok(state)
296 }
297}
298
299pub(crate) struct ShaperState {
300 shaper: Rc<dyn IPretextTextShaper>,
301 cache: RefCell<HashMap<PretextTextDirection, HashMap<String, Rc<PretextShapedRun>>>>,
304}
305
306impl ShaperState {
307 fn shape_text(
308 &self,
309 text: &str,
310 options: PretextShapeOptions,
311 ) -> Result<Rc<PretextShapedRun>, PretextError> {
312 if let Some(cached) = self
313 .cache
314 .borrow()
315 .get(&options.direction)
316 .and_then(|cache| cache.get(text))
317 {
318 return Ok(cached.clone());
319 }
320 let shaped = Rc::new(self.shaper.shape_text(text, options)?);
321 Ok(self
322 .cache
323 .borrow_mut()
324 .entry(options.direction)
325 .or_default()
326 .entry(text.into())
327 .or_insert(shaped)
328 .clone())
329 }
330}
331
332fn build_prepared_shaping_text(
333 prepared: &PreparedTextWithSegments,
334) -> (String, Vec<Vec<usize>>, usize) {
335 let capacity = prepared
336 .segments
337 .iter()
338 .enumerate()
339 .filter(|(index, _)| !is_invisible_break_segment(prepared, *index))
340 .map(|(_, text)| text.len())
341 .sum();
342 let mut text = String::with_capacity(capacity);
343 let mut boundaries = Vec::with_capacity(prepared.segments.len());
344 let mut utf16_length = 0;
345 for (index, segment) in prepared.segments.iter().enumerate() {
346 let offsets = segment_grapheme_offsets(prepared, index);
347 let mut segment_boundaries = vec![utf16_length; offsets.len()];
348 if !is_invisible_break_segment(prepared, index) {
349 for (grapheme_index, pair) in offsets.windows(2).enumerate() {
350 segment_boundaries[grapheme_index] = utf16_length;
351 let grapheme = &segment[pair[0]..pair[1]];
352 text.push_str(grapheme);
353 utf16_length += grapheme.encode_utf16().count();
354 }
355 if let Some(end) = segment_boundaries.last_mut() {
356 *end = utf16_length;
357 }
358 }
359 boundaries.push(segment_boundaries);
360 }
361 (text, boundaries, utf16_length)
362}
363
364fn validate_range(
365 prepared: &PreparedTextWithSegments,
366 start: LayoutCursor,
367 end: LayoutCursor,
368) -> Result<(), PretextError> {
369 let valid = |cursor: LayoutCursor| {
370 if cursor.segment_index == prepared.segments.len() {
371 cursor.grapheme_index == 0
372 } else {
373 cursor.segment_index < prepared.segments.len()
374 && cursor.grapheme_index
375 < segment_grapheme_offsets(prepared, cursor.segment_index).len()
376 }
377 };
378 if start > end || !valid(start) || !valid(end) {
379 Err(PretextError::InvalidInput(
380 "shaped line cursor is outside the prepared text".into(),
381 ))
382 } else {
383 Ok(())
384 }
385}
386
387fn can_slice_prepared_shaped_run(
388 prepared: &PreparedTextWithSegments,
389 start: LayoutCursor,
390 end: LayoutCursor,
391) -> bool {
392 !line_has_discretionary_hyphen(prepared, start, end.segment_index)
393 && is_shaping_safe_boundary(prepared, start)
394 && is_shaping_safe_boundary(prepared, end)
395}
396
397fn is_shaping_safe_boundary(prepared: &PreparedTextWithSegments, cursor: LayoutCursor) -> bool {
398 if cursor.segment_index >= prepared.segments.len() {
399 return true;
400 }
401 if cursor.grapheme_index == 0 {
402 return !is_invisible_break_boundary(prepared, cursor.segment_index);
403 }
404 cursor.grapheme_index
405 >= segment_grapheme_offsets(prepared, cursor.segment_index)
406 .len()
407 .saturating_sub(1)
408 && !is_invisible_break_boundary(prepared, cursor.segment_index + 1)
409}
410
411fn is_invisible_break_boundary(prepared: &PreparedTextWithSegments, index: usize) -> bool {
412 is_invisible_break_segment(prepared, index)
413 || index
414 .checked_sub(1)
415 .is_some_and(|previous| is_invisible_break_segment(prepared, previous))
416}
417
418fn is_invisible_break_segment(prepared: &PreparedTextWithSegments, index: usize) -> bool {
419 matches!(
420 prepared.kinds.get(index),
421 Some(
422 SegmentBreakKind::ZeroWidthBreak
423 | SegmentBreakKind::SoftHyphen
424 | SegmentBreakKind::HardBreak
425 )
426 )
427}
428
429fn slice_shaped_run(source: &PretextShapedRun, start: usize, end: usize) -> PretextShapedRun {
430 let mut glyphs = Vec::new();
431 let mut font_map = HashMap::new();
432 let mut font_runs: Vec<PretextShapedFontRun> = Vec::new();
433 let mut origin = None;
434 let mut advance_x = 0.0;
435 let mut advance_y = 0.0;
436 for glyph in source
437 .glyphs()
438 .iter()
439 .filter(|glyph| glyph.cluster >= start && glyph.cluster < end)
440 {
441 let (origin_x, origin_y) = *origin.get_or_insert((glyph.x, glyph.y));
442 let mapped_index = *font_map.entry(glyph.font_run_index).or_insert_with(|| {
443 let index = font_runs.len();
444 let font = source
445 .font_runs()
446 .iter()
447 .find(|run| run.index == glyph.font_run_index)
448 .or_else(|| source.font_runs().first());
449 font_runs.push(PretextShapedFontRun {
450 index,
451 font: font.map(|run| run.font.clone()).unwrap_or_default(),
452 first_glyph_index: glyphs.len(),
453 glyph_count: 0,
454 });
455 index
456 });
457 glyphs.push(PretextShapedGlyph {
458 cluster: glyph.cluster - start,
459 x: glyph.x - origin_x,
460 y: glyph.y - origin_y,
461 font_run_index: mapped_index,
462 ..*glyph
463 });
464 font_runs[mapped_index].glyph_count += 1;
465 advance_x += glyph.x_advance;
466 advance_y += glyph.y_advance;
467 }
468 PretextShapedRun::new(source.kind(), glyphs, font_runs, advance_x, advance_y)
469}