1use super::{FontFamily, FontStyle, TextAffinity, TextAlign, TextOverflow, TextStyle};
8use icu_segmenter::GraphemeClusterSegmenter;
9pub use incular_assets::FontId;
10use incular_assets::{FontBytes, FontHandle};
11use incular_core::{Offset, Size};
12use incular_rendering::{GlyphPosition, GlyphRun};
13use parley::{
14 Alignment, AlignmentOptions, FontContext, FontStack, FontWeight as ParleyFontWeight, Layout,
15 LayoutContext, PositionedLayoutItem, StyleProperty,
16};
17use std::{
18 borrow::Cow,
19 collections::{HashMap, HashSet, VecDeque, hash_map::DefaultHasher},
20 hash::{Hash, Hasher},
21 sync::{Arc, OnceLock},
22};
23
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct TextMetrics {
26 pub size: Size,
27 pub baseline: f32,
28 pub line_height: f32,
29}
30
31#[derive(Clone, Copy, Debug)]
38pub struct TextClusterSpan {
39 pub start: usize,
40 pub end: usize,
41 pub left: f32,
42 pub right: f32,
43 pub rtl: bool,
45}
46
47impl TextLine {
48 #[must_use]
66 pub fn selection_spans(&self, start: usize, end: usize) -> Vec<(f32, f32)> {
67 if start >= end {
68 return Vec::new();
69 }
70 let mut spans: Vec<(f32, f32)> = self
71 .clusters
72 .iter()
73 .filter(|span| span.left < span.right && span.start < end && start < span.end)
74 .map(|span| (span.left, span.right))
75 .collect();
76 if spans.is_empty() && self.start == self.end && start <= self.start && self.end <= end {
77 spans.push((self.offset, self.offset));
78 }
79 spans.sort_by(|left, right| left.0.total_cmp(&right.0));
80 let mut merged: Vec<(f32, f32)> = Vec::with_capacity(spans.len());
81 for (left, right) in spans {
82 if let Some(last) = merged.last_mut()
83 && left <= last.1
84 {
85 last.1 = last.1.max(right);
86 } else {
87 merged.push((left, right));
88 }
89 }
90 merged
91 }
92}
93
94#[derive(Clone, Debug)]
95pub struct TextLine {
96 pub runs: Arc<[Arc<GlyphRun>]>,
97 pub glyphs: Arc<[GlyphPosition]>,
98 pub clusters: Arc<[TextClusterSpan]>,
99 pub width: f32,
100 pub offset: f32,
106 pub baseline: f32,
107 pub start: usize,
108 pub caret_end: usize,
111 pub end: usize,
112}
113
114#[derive(Clone, Debug)]
115pub struct FontRunDebug {
116 pub range: std::ops::Range<usize>,
117 pub font: FontId,
118 pub direction: &'static str,
119}
120
121#[derive(Clone, Copy, Debug, PartialEq)]
128pub struct TextCaretPosition {
129 pub offset: usize,
130 pub affinity: TextAffinity,
131 pub x: f32,
132 pub line: usize,
133}
134
135#[derive(Clone, Debug)]
136struct ClusterPosition {
137 range: std::ops::Range<usize>,
138 left: f32,
139 right: f32,
140 rtl: bool,
141 ligature_start: bool,
142 ligature_continuation: bool,
143}
144
145impl ClusterPosition {
146 fn include(&mut self, other: &Self) {
147 self.range.start = self.range.start.min(other.range.start);
148 self.range.end = self.range.end.max(other.range.end);
149 self.left = self.left.min(other.left);
150 self.right = self.right.max(other.right);
151 self.ligature_start |= other.ligature_start;
152 self.ligature_continuation |= other.ligature_continuation;
153 }
154
155 fn can_join(&self, other: &Self) -> bool {
156 self.rtl == other.rtl
157 && if self.rtl {
158 other.range.end == self.range.start
159 } else {
160 self.range.end == other.range.start
161 }
162 }
163}
164
165#[derive(Clone, Debug)]
166pub struct TextLayout {
167 pub lines: Arc<[TextLine]>,
168 pub metrics: TextMetrics,
169 pub font_runs: Arc<[FontRunDebug]>,
170 caret_positions: OnceLock<Arc<[TextCaretPosition]>>,
173 pub overflowed: bool,
176}
177
178#[derive(Clone, Copy, Debug, PartialEq)]
181pub struct TextLayoutOptions {
182 pub max_width: Option<f32>,
183 pub align: TextAlign,
184 pub soft_wrap: bool,
185 pub max_lines: Option<usize>,
186 pub overflow: TextOverflow,
187}
188
189impl TextLayoutOptions {
190 #[must_use]
191 pub const fn new(max_width: Option<f32>, align: TextAlign) -> Self {
192 Self {
193 max_width,
194 align,
195 soft_wrap: true,
196 max_lines: None,
197 overflow: TextOverflow::Clip,
198 }
199 }
200
201 #[must_use]
202 pub const fn soft_wrap(mut self, soft_wrap: bool) -> Self {
203 self.soft_wrap = soft_wrap;
204 self
205 }
206
207 #[must_use]
208 pub const fn max_lines(mut self, max_lines: Option<usize>) -> Self {
209 self.max_lines = max_lines;
210 self
211 }
212
213 #[must_use]
214 pub const fn overflow(mut self, overflow: TextOverflow) -> Self {
215 self.overflow = overflow;
216 self
217 }
218}
219
220impl Default for TextLayoutOptions {
221 fn default() -> Self {
222 Self::new(None, TextAlign::Start)
223 }
224}
225
226impl TextLayout {
227 #[must_use]
228 pub fn glyph_count(&self) -> usize {
229 self.lines.iter().map(|line| line.glyphs.len()).sum()
230 }
231
232 #[must_use]
235 pub fn line_caret_positions(&self, line: usize) -> &[TextCaretPosition] {
236 let caret_positions = self
237 .caret_positions
238 .get_or_init(|| self.derive_caret_positions());
239 let Some(start) = caret_positions
240 .iter()
241 .position(|position| position.line == line)
242 else {
243 return &[];
244 };
245 let end = start
246 + caret_positions
247 .iter()
248 .skip(start)
249 .take_while(|position| position.line == line)
250 .count();
251 &caret_positions[start..end]
252 }
253
254 fn derive_caret_positions(&self) -> Arc<[TextCaretPosition]> {
258 let mut positions = Vec::new();
259 for (index, line) in self.lines.iter().enumerate() {
260 let stop = |offset, affinity, x| TextCaretPosition {
261 offset,
262 affinity,
263 x,
264 line: index,
265 };
266 let first = positions.len();
267 for cluster in line.clusters.iter() {
268 let (left, right) = if cluster.rtl {
269 (cluster.end, cluster.start)
270 } else {
271 (cluster.start, cluster.end)
272 };
273 let (left_affinity, right_affinity) = if cluster.rtl {
274 (TextAffinity::Upstream, TextAffinity::Downstream)
275 } else {
276 (TextAffinity::Downstream, TextAffinity::Upstream)
277 };
278 positions.push(stop(left, left_affinity, cluster.left));
279 positions.push(stop(right, right_affinity, cluster.right));
280 }
281 let line_stops = &positions[first..];
282 if !line_stops.iter().any(|stop| stop.offset == line.start) {
283 positions.push(stop(line.start, TextAffinity::Downstream, line.offset));
284 }
285 if line.caret_end > line.start
286 && !positions[first..]
287 .iter()
288 .any(|stop| stop.offset == line.caret_end)
289 {
290 positions.push(stop(
291 line.caret_end,
292 TextAffinity::Upstream,
293 line.offset + line.width,
294 ));
295 }
296 positions[first..].sort_by(|left, right| {
297 left.x
298 .total_cmp(&right.x)
299 .then_with(|| left.offset.cmp(&right.offset))
300 });
301 }
302 positions.into()
303 }
304}
305
306#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
309pub struct TextDiagnostics {
310 pub layouts_requested: u64,
311 pub cache_hits: u64,
312 pub cache_misses: u64,
313 pub font_resolution_requests: u64,
314 pub font_resolution_cache_hits: u64,
315 pub font_resolution_cache_misses: u64,
316 pub coverage_queries: u64,
317 pub fallback_runs: u64,
318 pub fallback_fonts_used: u64,
319 pub missing_clusters: u64,
320 pub shaping_runs: u64,
321 pub paragraphs_considered: u64,
324 pub paragraphs_reshaped: u64,
327 pub parley_layouts_reused: u64,
329 pub documents_composed: u64,
331}
332
333#[derive(Hash, PartialEq, Eq)]
334struct LayoutKey {
335 text: String,
336 family: FontFamily,
337 fallbacks: Arc<[FontFamily]>,
338 size: u32,
339 weight: u16,
340 style: FontStyle,
341 line_height: Option<(u8, u32)>,
342 letter_spacing: u32,
343 font_variations: Option<Arc<str>>,
344 font_features: Option<Arc<str>>,
345 width: Option<u32>,
346 align: TextAlign,
347 soft_wrap: bool,
348 max_lines: Option<usize>,
349 overflow: TextOverflow,
350 generation: u64,
351}
352
353impl LayoutKey {
354 fn new(text: &str, style: &TextStyle, options: TextLayoutOptions, generation: u64) -> Self {
355 Self {
356 text: text.into(),
357 family: style.family.clone(),
358 fallbacks: style.fallback_families.clone(),
359 size: style.size.to_bits(),
360 weight: style.weight.0,
361 style: style.style,
362 line_height: style.line_height.map(|lh| match lh {
363 crate::LineHeight::Normal => (0, 0),
364 crate::LineHeight::Multiplier(m) => (1, m.to_bits()),
365 crate::LineHeight::Absolute(a) => (2, a.to_bits()),
366 }),
367 letter_spacing: style.letter_spacing.to_bits(),
368 font_variations: style.font_variations.clone(),
369 font_features: style.font_features.clone(),
370 width: options.max_width.map(f32::to_bits),
371 align: options.align,
372 soft_wrap: options.soft_wrap,
373 max_lines: options.max_lines,
374 overflow: options.overflow,
375 generation,
376 }
377 }
378}
379
380pub struct TextEngine {
383 font_handles: HashMap<(usize, usize, u32), FontHandle>,
389 font_context: FontContext,
390 layout_context: LayoutContext<()>,
391 generation: u64,
392 cache: HashMap<Arc<LayoutKey>, Arc<TextLayout>>,
393 order: VecDeque<Arc<LayoutKey>>,
394 diagnostics: TextDiagnostics,
395}
396
397impl Default for TextEngine {
398 fn default() -> Self {
399 Self::new()
400 }
401}
402
403impl TextEngine {
404 #[must_use]
405 pub fn new() -> Self {
406 Self {
407 font_handles: HashMap::new(),
408 font_context: FontContext::new(),
409 layout_context: LayoutContext::new(),
410 generation: 1,
411 cache: HashMap::new(),
412 order: VecDeque::new(),
413 diagnostics: TextDiagnostics::default(),
414 }
415 }
416
417 #[must_use]
418 pub const fn diagnostics(&self) -> TextDiagnostics {
419 self.diagnostics
420 }
421
422 #[must_use]
423 pub const fn font_database_generation(&self) -> u64 {
424 self.generation
425 }
426
427 pub fn register_font_bytes(&mut self, bytes: impl Into<Vec<u8>>) {
431 self.font_context
432 .collection
433 .register_fonts(parley::fontique::Blob::from(bytes.into()), None);
434 self.generation = self
435 .generation
436 .checked_add(1)
437 .expect("font database generation exhausted");
438 self.cache.clear();
439 self.order.clear();
440 }
441
442 pub fn layout(
443 &mut self,
444 text: &str,
445 style: &TextStyle,
446 max_width: Option<f32>,
447 align: TextAlign,
448 ) -> Arc<TextLayout> {
449 self.layout_with_options(text, style, TextLayoutOptions::new(max_width, align))
450 }
451
452 pub fn layout_with_options(
454 &mut self,
455 text: &str,
456 style: &TextStyle,
457 options: TextLayoutOptions,
458 ) -> Arc<TextLayout> {
459 self.diagnostics.layouts_requested += 1;
460 if text.contains('\n')
468 && options.max_lines.is_none()
469 && (options.soft_wrap || options.overflow == TextOverflow::Visible)
470 {
471 let layout = self.layout_document(text, style, options);
472 return Arc::new(layout);
473 }
474 let key = LayoutKey::new(text, style, options, self.generation);
475 if let Some(layout) = self.cache.get(&key) {
476 self.diagnostics.cache_hits += 1;
477 return layout.clone();
478 }
479
480 self.diagnostics.cache_misses += 1;
481 let layout = Arc::new(self.shape_and_wrap(text, style, options));
482 self.store(key, layout.clone());
483 layout
484 }
485
486 fn store(&mut self, key: LayoutKey, layout: Arc<TextLayout>) {
489 const LAYOUT_CACHE_CAPACITY: usize = 2048;
490 if self.order.len() == LAYOUT_CACHE_CAPACITY
491 && let Some(old) = self.order.pop_front()
492 {
493 self.cache.remove(old.as_ref());
494 }
495 let key = Arc::new(key);
498 self.order.push_back(key.clone());
499 self.cache.insert(key, layout);
500 }
501
502 fn layout_document(
506 &mut self,
507 text: &str,
508 style: &TextStyle,
509 options: TextLayoutOptions,
510 ) -> TextLayout {
511 self.diagnostics.documents_composed += 1;
512 let paragraph_options = TextLayoutOptions {
513 max_lines: None,
514 overflow: TextOverflow::Visible,
515 ..options
516 };
517 let mut byte_start = 0usize;
518 let mut lines: Vec<TextLine> = Vec::new();
519 let mut font_runs = Vec::new();
520 let mut width = 0f32;
521 let mut height = 0f32;
522 let mut line_height = 0f32;
523 let mut overflowed = false;
524 for paragraph in text.split('\n') {
525 self.diagnostics.paragraphs_considered += 1;
526 let key = LayoutKey::new(paragraph, style, paragraph_options, self.generation);
527 let paragraph_layout = if let Some(existing) = self.cache.get(&key) {
528 self.diagnostics.parley_layouts_reused += 1;
529 self.diagnostics.cache_hits += 1;
530 existing.clone()
531 } else {
532 self.diagnostics.paragraphs_reshaped += 1;
533 self.diagnostics.cache_misses += 1;
534 let built = Arc::new(self.shape_and_wrap(paragraph, style, paragraph_options));
535 self.store(key, built.clone());
536 built
537 };
538 for run in paragraph_layout.font_runs.iter() {
541 let mut rebased = run.clone();
542 rebased.range.start += byte_start;
543 rebased.range.end += byte_start;
544 font_runs.push(rebased);
545 }
546 for line in paragraph_layout.lines.iter() {
547 let glyphs: Vec<GlyphPosition> = line
548 .glyphs
549 .iter()
550 .map(|glyph| {
551 let mut shifted = *glyph;
552 shifted.offset.y -= height;
555 shifted.cluster += byte_start as u32;
558 shifted
559 })
560 .collect();
561 let shifted_runs: Vec<Arc<GlyphRun>> = line
562 .runs
563 .iter()
564 .map(|run| {
565 let mut shifted = (**run).clone();
566 shifted.origin.y += height;
567 Arc::new(shifted)
568 })
569 .collect();
570 lines.push(TextLine {
571 runs: shifted_runs.into(),
572 glyphs: glyphs.into(),
573 clusters: line
574 .clusters
575 .iter()
576 .map(|span| TextClusterSpan {
577 start: span.start + byte_start,
578 end: span.end + byte_start,
579 left: span.left,
580 right: span.right,
581 rtl: span.rtl,
582 })
583 .collect(),
584 width: line.width,
585 offset: line.offset,
586 baseline: line.baseline + height,
587 start: line.start + byte_start,
588 caret_end: line.caret_end + byte_start,
589 end: line.end + byte_start,
590 });
591 }
592 width = width.max(paragraph_layout.metrics.size.width);
593 height += paragraph_layout.metrics.size.height;
594 line_height = paragraph_layout.metrics.line_height;
595 overflowed |= paragraph_layout.overflowed;
596 byte_start += paragraph.len() + 1; }
598 if lines.is_empty() {
599 lines.push(TextLine {
600 runs: Arc::new([]),
601 glyphs: Arc::new([]),
602 clusters: Arc::new([]),
603 width: 0.,
604 offset: 0.,
605 baseline: 0.,
606 start: 0,
607 caret_end: 0,
608 end: 0,
609 });
610 }
611 let baseline = lines[0].baseline;
612 TextLayout {
613 lines: lines.into(),
614 metrics: TextMetrics {
615 size: Size::new(width, height),
616 baseline,
617 line_height,
618 },
619 font_runs: font_runs.into(),
620 caret_positions: OnceLock::new(),
621 overflowed,
622 }
623 }
624
625 fn font_stack(style: &TextStyle) -> FontStack<'static> {
626 let mut names = vec![family_name(&style.family)];
627 names.extend(style.fallback_families.iter().map(family_name));
628 FontStack::Source(names.join(", ").into())
629 }
630
631 fn shape_and_wrap(
632 &mut self,
633 text: &str,
634 style: &TextStyle,
635 options: TextLayoutOptions,
636 ) -> TextLayout {
637 let wrap_width = options.soft_wrap.then_some(options.max_width).flatten();
638 let layout = self.shape_raw(text, style, wrap_width, options.max_width, options.align);
639 let horizontal_overflow = !options.soft_wrap
640 && options
641 .max_width
642 .is_some_and(|width| layout.metrics.size.width > width);
643 let line_limit = options
644 .max_lines
645 .or_else(|| horizontal_overflow.then_some(1));
646 let line_overflow = line_limit.is_some_and(|limit| layout.lines.len() > limit);
647 if (!horizontal_overflow && !line_overflow) || options.overflow == TextOverflow::Visible {
648 return layout;
649 }
650
651 let limit = line_limit.unwrap_or(layout.lines.len());
652 match options.overflow {
653 TextOverflow::Visible => layout,
654 TextOverflow::Clip | TextOverflow::Fade => clipped_layout(layout, limit),
655 TextOverflow::Ellipsis => self.ellipsized_layout(text, style, options, limit),
656 }
657 }
658
659 fn ellipsized_layout(
660 &mut self,
661 text: &str,
662 style: &TextStyle,
663 options: TextLayoutOptions,
664 limit: usize,
665 ) -> TextLayout {
666 if limit == 0 {
667 return clipped_layout(
668 self.shape_raw("", style, None, options.max_width, options.align),
669 0,
670 );
671 }
672 let wrap_width = options.soft_wrap.then_some(options.max_width).flatten();
673 let initial = self.shape_raw(text, style, wrap_width, options.max_width, options.align);
674 let end = initial
675 .lines
676 .get(limit.saturating_sub(1))
677 .map_or(0, |line| line.caret_end.min(text.len()));
678 let mut end = grapheme_boundary_at_or_before(text, end);
679
680 loop {
681 let mut candidate = text[..end].to_owned();
682 candidate.push('\u{2026}');
683 let shaped = self.shape_raw(
687 &candidate,
688 style,
689 wrap_width,
690 options.max_width,
691 options.align,
692 );
693 let fits_width = options.max_width.is_none_or(|width| {
694 shaped
695 .lines
696 .iter()
697 .all(|line| line.width <= width + f32::EPSILON)
698 });
699 if shaped.lines.len() <= limit && fits_width {
700 return TextLayout {
701 overflowed: true,
702 ..shaped
703 };
704 }
705 let previous = previous_grapheme_boundary(text, end);
706 if previous == end {
707 return TextLayout {
708 overflowed: true,
709 ..shaped
710 };
711 }
712 end = previous;
713 }
714 }
715
716 fn shape_raw(
717 &mut self,
718 text: &str,
719 style: &TextStyle,
720 max_width: Option<f32>,
721 align_width: Option<f32>,
722 align: TextAlign,
723 ) -> TextLayout {
724 let mut builder =
725 self.layout_context
726 .ranged_builder(&mut self.font_context, text, 1.0, true);
727 builder.push_default(StyleProperty::FontStack(Self::font_stack(style)));
728 builder.push_default(StyleProperty::FontSize(style.size.max(0.0)));
729 builder.push_default(StyleProperty::FontWeight(ParleyFontWeight::new(
730 style.weight.0 as f32,
731 )));
732 builder.push_default(StyleProperty::FontStyle(match style.style {
733 FontStyle::Normal => parley::FontStyle::Normal,
734 FontStyle::Italic => parley::FontStyle::Italic,
735 }));
736 builder.push_default(StyleProperty::LetterSpacing(style.letter_spacing));
737 if let Some(settings) = &style.font_variations {
738 builder.push_default(StyleProperty::FontVariations(parley::FontSettings::Source(
739 Cow::Owned(settings.to_string()),
740 )));
741 }
742 if let Some(settings) = &style.font_features {
743 builder.push_default(StyleProperty::FontFeatures(parley::FontSettings::Source(
744 Cow::Owned(settings.to_string()),
745 )));
746 }
747 if let Some(line_height) = style.line_height
748 && let Some(parley_lh) = line_height.to_parley(style.size)
749 {
750 builder.push_default(StyleProperty::LineHeight(parley_lh));
751 }
752
753 let mut layout: Layout<()> = builder.build(text);
754 layout.break_all_lines(max_width);
755 layout.align(
759 align_width,
760 match align {
761 TextAlign::Start => Alignment::Start,
762 TextAlign::Center => Alignment::Center,
763 TextAlign::End => Alignment::End,
764 TextAlign::Justify => Alignment::Justify,
765 },
766 AlignmentOptions::default(),
767 );
768
769 self.translate_layout(&layout, text)
770 }
771
772 fn font_handle_for(&mut self, font: &parley::FontData) -> FontHandle {
773 font_handle(self, font)
774 }
775
776 fn translate_layout(&mut self, layout: &Layout<()>, text: &str) -> TextLayout {
777 let mut lines = Vec::new();
778 let mut font_runs = Vec::new();
779 let mut used_fonts = HashSet::new();
780 let primary_handle = layout
783 .lines()
784 .flat_map(|line| line.runs())
785 .next()
786 .map(|run| self.font_handle_for(run.font()));
787 let primary = primary_handle.as_ref().map(FontHandle::id);
788
789 for line in layout.lines() {
790 let metrics = line.metrics();
791 let range = line.text_range();
792 let mut runs: Vec<Arc<GlyphRun>> = Vec::new();
793 let mut glyphs = Vec::new();
794 let mut caret_end = range.start;
795 let mut cluster_positions: Vec<ClusterPosition> = Vec::new();
802 let mut cluster_position_index: HashMap<(usize, usize), usize> = HashMap::new();
803 let mut cluster_x = metrics.offset;
804 for run in line.runs() {
805 let rtl = run.is_rtl();
806 for cluster in run.visual_clusters() {
807 let cluster_range = cluster.text_range();
808 caret_end = caret_end.max(cluster_range.end);
812 let edge = cluster_x + cluster.advance();
813 let ligature_start = cluster.is_ligature_start();
814 let ligature_continuation = cluster.is_ligature_continuation();
815 let mut cluster_glyphs = cluster.glyphs();
816 let Some(first_glyph) = cluster_glyphs.next() else {
817 let is_join_control =
823 text.get(cluster_range.clone()).is_some_and(|component| {
824 !component.is_empty()
825 && component.chars().all(|character| {
826 matches!(character, '\u{200C}' | '\u{200D}')
827 })
828 });
829 if ligature_continuation && !is_join_control {
830 let position = ClusterPosition {
831 range: cluster_range.clone(),
832 left: cluster_x.min(edge),
833 right: cluster_x.max(edge),
834 rtl,
835 ligature_start,
836 ligature_continuation,
837 };
838 let key = (cluster_range.start, cluster_range.end);
839 if let Some(index) = cluster_position_index.get(&key).copied() {
840 cluster_positions[index].include(&position);
841 } else {
842 cluster_position_index.insert(key, cluster_positions.len());
843 cluster_positions.push(position);
844 }
845 }
846 cluster_x = edge;
847 continue;
848 };
849 let glyph_advance = std::iter::once(first_glyph)
850 .chain(cluster_glyphs)
851 .map(|glyph| glyph.advance)
852 .sum::<f32>();
853 let visible_edge = cluster_x + cluster.advance().max(glyph_advance);
859 let left = cluster_x.min(visible_edge);
860 let right = cluster_x.max(visible_edge);
861 let position = ClusterPosition {
862 range: cluster_range.clone(),
863 left,
864 right,
865 rtl,
866 ligature_start,
867 ligature_continuation,
868 };
869 let key = (cluster_range.start, cluster_range.end);
870 if let Some(index) = cluster_position_index.get(&key).copied() {
871 cluster_positions[index].include(&position);
872 } else {
873 cluster_position_index.insert(key, cluster_positions.len());
874 cluster_positions.push(position);
875 }
876 cluster_x = edge;
877 }
878 }
879 for item in line.items() {
880 let PositionedLayoutItem::GlyphRun(parley_run) = item else {
881 continue;
882 };
883 let run = parley_run.run();
884 let font = self.font_handle_for(run.font());
885 let font_id = font.id();
886 if Some(font_id) != primary {
887 self.diagnostics.fallback_runs += 1;
888 }
889 used_fonts.insert(font_id);
890 self.diagnostics.shaping_runs += 1;
891
892 let visible_glyphs = parley_run.glyphs().collect::<Vec<_>>();
909 let all_clustered_glyphs = run
910 .visual_clusters()
911 .flat_map(|cluster| {
912 let cluster_range = cluster.text_range();
913 cluster
914 .glyphs()
915 .map(move |glyph| (glyph, cluster_range.clone()))
916 })
917 .collect::<Vec<_>>();
918 let first_glyph = all_clustered_glyphs
919 .windows(visible_glyphs.len())
920 .position(|candidate| {
921 candidate
922 .iter()
923 .zip(&visible_glyphs)
924 .all(|((glyph, _), visible)| glyph == visible)
925 })
926 .unwrap_or(0);
927 let clusters = all_clustered_glyphs
928 .iter()
929 .skip(first_glyph)
930 .map(|(_, range)| range.clone())
931 .take(visible_glyphs.len())
932 .collect::<Vec<_>>();
933 let positions: Vec<_> = parley_run
934 .positioned_glyphs()
935 .zip(
936 clusters
937 .iter()
938 .map(|range| range.start as u32)
939 .chain(std::iter::repeat(run.text_range().start as u32)),
940 )
941 .map(|(glyph, cluster)| GlyphPosition {
942 id: glyph.id as u16,
943 offset: Offset::new(glyph.x, -glyph.y),
949 advance: glyph.advance,
950 cluster,
951 })
952 .collect();
953 if runs.len() == 1 {
954 glyphs.extend(runs[0].glyphs.iter().copied());
955 }
956 if !runs.is_empty() {
957 glyphs.extend(positions.iter().copied());
958 }
959 runs.push(Arc::new(GlyphRun {
960 font,
961 font_size: run.font_size(),
962 origin: Offset::ZERO,
963 glyphs: positions.into(),
964 }));
965 font_runs.push(FontRunDebug {
966 range: run.text_range(),
967 font: font_id,
968 direction: if run.is_rtl() { "rtl" } else { "ltr" },
969 });
970 }
971 let cluster_spans: Vec<_> = merge_ligature_components(cluster_positions)
974 .iter()
975 .map(|position| TextClusterSpan {
976 start: position.range.start,
977 end: position.range.end,
978 left: position.left,
979 right: position.right,
980 rtl: position.rtl,
981 })
982 .collect();
983 let glyphs = if runs.len() == 1 {
987 runs[0].glyphs.clone()
988 } else {
989 glyphs.into()
990 };
991 lines.push(TextLine {
992 runs: runs.into(),
993 glyphs,
994 clusters: cluster_spans.into(),
995 width: metrics.advance,
996 offset: metrics.offset,
997 baseline: metrics.baseline,
998 start: range.start,
999 caret_end,
1000 end: range.end,
1001 });
1002 }
1003
1004 if lines.is_empty() {
1005 lines.push(TextLine {
1006 runs: Arc::new([]),
1007 glyphs: Arc::new([]),
1008 clusters: Arc::new([]),
1009 width: 0.0,
1010 offset: 0.0,
1011 baseline: 0.0,
1012 start: 0,
1013 caret_end: 0,
1014 end: 0,
1015 });
1016 }
1017 self.diagnostics.fallback_fonts_used += used_fonts.len().saturating_sub(1) as u64;
1018 let baseline = lines[0].baseline;
1019 let line_height = layout
1020 .lines()
1021 .next()
1022 .map_or(0.0, |line| line.metrics().line_height);
1023 TextLayout {
1024 lines: lines.into(),
1025 metrics: TextMetrics {
1026 size: Size::new(layout.width(), layout.height()),
1027 baseline,
1028 line_height,
1029 },
1030 font_runs: font_runs.into(),
1031 caret_positions: OnceLock::new(),
1032 overflowed: false,
1033 }
1034 }
1035}
1036
1037fn merge_ligature_components(positions: Vec<ClusterPosition>) -> Vec<ClusterPosition> {
1042 let mut merged = Vec::with_capacity(positions.len());
1043 let mut pending: Option<ClusterPosition> = None;
1044 let mut pending_has_start = false;
1045
1046 for position in positions {
1047 let is_start = position.ligature_start;
1048 let is_continuation = position.ligature_continuation;
1049 if !is_start && !is_continuation {
1050 if let Some(pending) = pending.take() {
1051 merged.push(pending);
1052 }
1053 pending_has_start = false;
1054 merged.push(position);
1055 continue;
1056 }
1057 if is_start && is_continuation {
1059 if let Some(pending) = pending.take() {
1060 merged.push(pending);
1061 }
1062 pending_has_start = false;
1063 merged.push(position);
1064 continue;
1065 }
1066
1067 if position.rtl {
1068 if is_continuation {
1069 if pending_has_start {
1072 if let Some(pending) = pending.take() {
1073 merged.push(pending);
1074 }
1075 pending_has_start = false;
1076 }
1077 let can_extend = pending
1078 .as_ref()
1079 .is_some_and(|pending| pending.can_join(&position));
1080 if can_extend {
1081 pending
1082 .as_mut()
1083 .expect("checked pending ligature group")
1084 .include(&position);
1085 } else {
1086 if let Some(pending) = pending.take() {
1087 merged.push(pending);
1088 }
1089 pending_has_start = false;
1090 pending = Some(position);
1091 }
1092 } else {
1093 match pending.take() {
1095 Some(mut pending_group)
1096 if !pending_has_start && pending_group.can_join(&position) =>
1097 {
1098 pending_group.include(&position);
1099 merged.push(pending_group);
1100 }
1101 Some(pending_group) => {
1102 merged.push(pending_group);
1103 merged.push(position);
1104 }
1105 None => merged.push(position),
1106 }
1107 pending_has_start = false;
1108 }
1109 } else if is_start {
1110 if let Some(pending) = pending.take() {
1111 merged.push(pending);
1112 }
1113 pending = Some(position);
1114 pending_has_start = true;
1115 } else if pending.is_some() {
1116 if pending
1120 .as_ref()
1121 .is_some_and(|pending_group| pending_group.can_join(&position))
1122 {
1123 pending
1124 .as_mut()
1125 .expect("checked pending ligature group")
1126 .include(&position);
1127 } else {
1128 let pending_group = pending.take().expect("pending ligature group");
1129 merged.push(pending_group);
1130 pending = Some(position);
1131 pending_has_start = false;
1132 }
1133 } else {
1134 pending = Some(position);
1137 pending_has_start = false;
1138 }
1139 }
1140
1141 if let Some(pending) = pending {
1142 merged.push(pending);
1143 }
1144 merged
1145}
1146
1147fn clipped_layout(mut layout: TextLayout, limit: usize) -> TextLayout {
1148 let lines = layout.lines.iter().take(limit).cloned().collect::<Vec<_>>();
1149 let width = lines
1150 .iter()
1151 .fold(0.0_f32, |width, line| width.max(line.width));
1152 layout.metrics.size = Size::new(width, layout.metrics.line_height * lines.len() as f32);
1153 layout.metrics.baseline = lines.first().map_or(0.0, |line| line.baseline);
1154 layout.lines = lines.into();
1155 layout.overflowed = true;
1156 layout
1157}
1158
1159fn grapheme_boundary_at_or_before(text: &str, offset: usize) -> usize {
1160 GraphemeClusterSegmenter::new()
1161 .segment_str(text)
1162 .take_while(|boundary| *boundary <= offset)
1163 .last()
1164 .unwrap_or(0)
1165}
1166
1167fn previous_grapheme_boundary(text: &str, offset: usize) -> usize {
1168 GraphemeClusterSegmenter::new()
1169 .segment_str(text)
1170 .take_while(|boundary| *boundary < offset)
1171 .last()
1172 .unwrap_or(0)
1173}
1174
1175fn family_name(family: &FontFamily) -> String {
1176 match family {
1177 FontFamily::SansSerif => "sans-serif".into(),
1178 FontFamily::Serif => "serif".into(),
1179 FontFamily::Monospace => "monospace".into(),
1180 FontFamily::Cursive => "cursive".into(),
1181 FontFamily::SystemUi => "system-ui".into(),
1182 FontFamily::Named(name) => format!("'{name}'"),
1183 }
1184}
1185
1186type FontBlobKey = (usize, usize, u32);
1190
1191fn font_blob_key(font: &parley::FontData) -> FontBlobKey {
1192 (
1193 font.data.as_ref().as_ptr() as usize,
1194 font.data.as_ref().len(),
1195 font.index,
1196 )
1197}
1198
1199fn font_handle(engine: &mut TextEngine, font: &parley::FontData) -> FontHandle {
1202 let key = font_blob_key(font);
1203 if let Some(handle) = engine.font_handles.get(&key) {
1204 return handle.clone();
1205 }
1206 let mut hasher = DefaultHasher::new();
1207 font.data.as_ref().hash(&mut hasher);
1208 hasher.write_u64(u64::from(font.index));
1209 let id = FontId(hasher.finish());
1210 let handle = FontHandle::with_face_index(
1213 id,
1214 FontBytes::from_shared(font.data.clone().into_raw_parts().0),
1215 font.index,
1216 );
1217 engine.font_handles.insert(key, handle.clone());
1218 handle
1219}