1use super::{
8 FontFamily, FontStyle, LineHeight, TextAffinity, TextAlign, TextHeightBehavior,
9 TextLeadingDistribution, TextOverflow, TextStyle,
10};
11use icu_segmenter::GraphemeClusterSegmenter;
12pub use incular_assets::FontId;
13use incular_assets::{FontBytes, FontHandle};
14use incular_config::TextDirection;
15use incular_core::{Offset, Size};
16use incular_rendering::{GlyphPosition, GlyphRun};
17use parley::{
18 Alignment, AlignmentOptions, BaseDirection, FontContext, FontWeight as ParleyFontWeight,
19 Layout, LayoutContext, PositionedLayoutItem, StyleProperty,
20};
21mod document;
22mod pool;
23pub use document::TextDocumentLayout;
24use pool::{DEFAULT_CACHE_BYTE_BUDGET, LAYOUT_CACHE_CAPACITY, LayoutPool};
25
26use std::{
27 borrow::Cow,
28 collections::{HashMap, HashSet, hash_map::DefaultHasher},
29 hash::{Hash, Hasher},
30 sync::{Arc, OnceLock},
31};
32
33#[derive(Clone, Copy, Debug, PartialEq)]
34pub struct TextMetrics {
35 pub size: Size,
36 pub baseline: f32,
37 pub line_height: f32,
38}
39
40#[derive(Clone, Copy, Debug)]
51pub struct TextClusterSpan {
52 pub start: u32,
53 pub end: u32,
54 pub left: f32,
55 pub right: f32,
56 pub rtl: bool,
58}
59
60impl TextClusterSpan {
61 #[must_use]
63 pub const fn range(&self) -> std::ops::Range<usize> {
64 self.start as usize..self.end as usize
65 }
66}
67
68impl TextLine {
69 #[must_use]
87 pub fn selection_spans(&self, start: usize, end: usize) -> Vec<(f32, f32)> {
88 if start >= end {
89 return Vec::new();
90 }
91 let mut spans: Vec<(f32, f32)> = self
92 .clusters
93 .iter()
94 .filter(|span| {
95 let range = span.range();
96 span.left < span.right && range.start < end && start < range.end
97 })
98 .map(|span| (span.left, span.right))
99 .collect();
100 if spans.is_empty() && self.start == self.end && start <= self.start && self.end <= end {
101 spans.push((self.offset, self.offset));
102 }
103 spans.sort_by(|left, right| left.0.total_cmp(&right.0));
104 let mut merged: Vec<(f32, f32)> = Vec::with_capacity(spans.len());
105 for (left, right) in spans {
106 if let Some(last) = merged.last_mut()
107 && left <= last.1
108 {
109 last.1 = last.1.max(right);
110 } else {
111 merged.push((left, right));
112 }
113 }
114 merged
115 }
116}
117
118#[derive(Clone, Debug)]
119pub struct TextLine {
120 pub runs: Arc<[Arc<GlyphRun>]>,
121 pub glyphs: Arc<[GlyphPosition]>,
122 pub clusters: Arc<[TextClusterSpan]>,
123 pub width: f32,
124 pub offset: f32,
130 pub baseline: f32,
131 pub start: usize,
132 pub caret_end: usize,
135 pub end: usize,
136}
137
138#[derive(Clone, Debug)]
139pub struct FontRunDebug {
140 pub range: std::ops::Range<usize>,
141 pub font: FontId,
142 pub direction: &'static str,
143}
144
145#[derive(Clone, Copy, Debug, PartialEq)]
152pub struct TextCaretPosition {
153 pub offset: usize,
154 pub affinity: TextAffinity,
155 pub x: f32,
156 pub line: usize,
157}
158
159#[derive(Clone, Debug)]
160struct ClusterPosition {
161 range: std::ops::Range<usize>,
162 left: f32,
163 right: f32,
164 rtl: bool,
165 ligature_start: bool,
166 ligature_continuation: bool,
167}
168
169impl ClusterPosition {
170 fn include(&mut self, other: &Self) {
171 self.range.start = self.range.start.min(other.range.start);
172 self.range.end = self.range.end.max(other.range.end);
173 self.left = self.left.min(other.left);
174 self.right = self.right.max(other.right);
175 self.ligature_start |= other.ligature_start;
176 self.ligature_continuation |= other.ligature_continuation;
177 }
178
179 fn can_join(&self, other: &Self) -> bool {
180 self.rtl == other.rtl
181 && if self.rtl {
182 other.range.end == self.range.start
183 } else {
184 self.range.end == other.range.start
185 }
186 }
187}
188
189type LineCaretStops = Arc<[OnceLock<Arc<[TextCaretPosition]>>]>;
191
192#[derive(Clone, Debug)]
193pub struct TextLayout {
194 pub lines: Arc<[TextLine]>,
195 pub metrics: TextMetrics,
196 pub font_runs: Arc<[FontRunDebug]>,
197 caret_positions: OnceLock<Arc<[TextCaretPosition]>>,
201 line_carets: OnceLock<LineCaretStops>,
205 pub overflowed: bool,
208}
209
210#[derive(Clone, Copy, Debug, PartialEq)]
213pub struct TextLayoutOptions {
214 pub max_width: Option<f32>,
215 pub align: TextAlign,
216 pub text_direction: Option<TextDirection>,
218 pub soft_wrap: bool,
219 pub max_lines: Option<usize>,
220 pub overflow: TextOverflow,
221 pub height_behavior: TextHeightBehavior,
224}
225
226impl TextLayoutOptions {
227 #[must_use]
228 pub const fn new(max_width: Option<f32>, align: TextAlign) -> Self {
229 Self {
230 max_width,
231 align,
232 text_direction: None,
233 soft_wrap: true,
234 max_lines: None,
235 overflow: TextOverflow::Clip,
236 height_behavior: TextHeightBehavior {
237 apply_height_to_first_ascent: true,
238 apply_height_to_last_descent: true,
239 leading_distribution: TextLeadingDistribution::Proportional,
240 },
241 }
242 }
243
244 #[must_use]
245 pub const fn height_behavior(mut self, behavior: TextHeightBehavior) -> Self {
246 self.height_behavior = behavior;
247 self
248 }
249
250 #[must_use]
251 pub const fn soft_wrap(mut self, soft_wrap: bool) -> Self {
252 self.soft_wrap = soft_wrap;
253 self
254 }
255
256 #[must_use]
257 pub const fn text_direction(mut self, direction: TextDirection) -> Self {
258 self.text_direction = Some(direction);
259 self
260 }
261
262 #[must_use]
263 pub const fn max_lines(mut self, max_lines: Option<usize>) -> Self {
264 self.max_lines = max_lines;
265 self
266 }
267
268 #[must_use]
269 pub const fn overflow(mut self, overflow: TextOverflow) -> Self {
270 self.overflow = overflow;
271 self
272 }
273}
274
275impl Default for TextLayoutOptions {
276 fn default() -> Self {
277 Self::new(None, TextAlign::Start)
278 }
279}
280
281impl TextLayout {
282 #[must_use]
283 pub fn glyph_count(&self) -> usize {
284 self.lines.iter().map(|line| line.glyphs.len()).sum()
285 }
286
287 #[must_use]
290 pub fn line_caret_positions(&self, line: usize) -> &[TextCaretPosition] {
291 if let Some(caret_positions) = self.caret_positions.get() {
292 let start = caret_positions.partition_point(|position| position.line < line);
294 let end = caret_positions.partition_point(|position| position.line <= line);
295 return &caret_positions[start..end];
296 }
297 if line >= self.lines.len() {
298 return &[];
299 }
300 let lines = self.line_carets.get_or_init(|| {
301 (0..self.lines.len())
302 .map(|_| OnceLock::new())
303 .collect::<Vec<_>>()
304 .into()
305 });
306 lines[line].get_or_init(|| {
307 let mut positions = Vec::new();
308 self.push_line_caret_positions(line, &mut positions);
309 positions.into()
310 })
311 }
312
313 fn derive_caret_positions(&self) -> Arc<[TextCaretPosition]> {
314 let mut positions = Vec::new();
315 for index in 0..self.lines.len() {
316 self.push_line_caret_positions(index, &mut positions);
317 }
318 positions.into()
319 }
320
321 fn push_line_caret_positions(&self, index: usize, positions: &mut Vec<TextCaretPosition>) {
325 let line = &self.lines[index];
326 let stop = |offset, affinity, x| TextCaretPosition {
327 offset,
328 affinity,
329 x,
330 line: index,
331 };
332 let first = positions.len();
333 for cluster in line.clusters.iter() {
334 let range = cluster.range();
335 let (left, right) = if cluster.rtl {
336 (range.end, range.start)
337 } else {
338 (range.start, range.end)
339 };
340 let (left_affinity, right_affinity) = if cluster.rtl {
341 (TextAffinity::Upstream, TextAffinity::Downstream)
342 } else {
343 (TextAffinity::Downstream, TextAffinity::Upstream)
344 };
345 positions.push(stop(left, left_affinity, cluster.left));
346 positions.push(stop(right, right_affinity, cluster.right));
347 }
348 let line_stops = &positions[first..];
349 if !line_stops.iter().any(|stop| stop.offset == line.start) {
350 positions.push(stop(line.start, TextAffinity::Downstream, line.offset));
351 }
352 if line.caret_end > line.start
353 && !positions[first..]
354 .iter()
355 .any(|stop| stop.offset == line.caret_end)
356 {
357 positions.push(stop(
358 line.caret_end,
359 TextAffinity::Upstream,
360 line.offset + line.width,
361 ));
362 }
363 positions[first..].sort_by(|left, right| {
364 left.x
365 .total_cmp(&right.x)
366 .then_with(|| left.offset.cmp(&right.offset))
367 });
368 }
369}
370
371#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
374pub struct TextDiagnostics {
375 pub layouts_requested: u64,
376 pub cache_hits: u64,
377 pub cache_misses: u64,
378 pub font_resolution_requests: u64,
379 pub font_resolution_cache_hits: u64,
380 pub font_resolution_cache_misses: u64,
381 pub coverage_queries: u64,
382 pub fallback_runs: u64,
383 pub fallback_fonts_used: u64,
384 pub missing_clusters: u64,
385 pub shaping_runs: u64,
386 pub paragraphs_considered: u64,
389 pub paragraphs_reshaped: u64,
392 pub parley_layouts_reused: u64,
394 pub documents_composed: u64,
396 pub documents_reused: u64,
398 pub width_reuses: u64,
400 pub cache_growths: u64,
402 pub cache_shrinks: u64,
405 pub paragraphs_carried: u64,
407 pub shapes_reused: u64,
409}
410
411#[derive(Hash, PartialEq, Eq)]
414struct LayoutKey<'a> {
415 text: Cow<'a, str>,
416 family: FontFamily,
417 fallbacks: Arc<[FontFamily]>,
418 size: u32,
419 weight: u16,
420 style: FontStyle,
421 line_height: Option<(u8, u32)>,
422 letter_spacing: u32,
423 font_variations: Option<Arc<str>>,
424 font_features: Option<Arc<str>>,
425 width: Option<u32>,
426 align: TextAlign,
427 text_direction: Option<TextDirection>,
428 soft_wrap: bool,
429 max_lines: Option<usize>,
430 overflow: TextOverflow,
431 height_behavior: TextHeightBehavior,
432 leading_distribution: Option<TextLeadingDistribution>,
433 generation: u64,
434}
435
436impl<'a> LayoutKey<'a> {
437 fn new(text: &'a str, style: &TextStyle, options: TextLayoutOptions, generation: u64) -> Self {
438 Self {
439 text: Cow::Borrowed(text),
440 family: style.family.clone(),
441 fallbacks: style.fallback_families.clone(),
442 size: style.size.to_bits(),
443 weight: style.weight.0,
444 style: style.style,
445 line_height: style.line_height.map(|lh| match lh {
446 crate::LineHeight::Normal => (0, 0),
447 crate::LineHeight::Multiplier(m) => (1, m.to_bits()),
448 crate::LineHeight::Absolute(a) => (2, a.to_bits()),
449 }),
450 letter_spacing: style.letter_spacing.to_bits(),
451 font_variations: style.font_variations.clone(),
452 font_features: style.font_features.clone(),
453 width: options.max_width.map(f32::to_bits),
454 align: options.align,
455 text_direction: options.text_direction,
456 soft_wrap: options.soft_wrap,
457 max_lines: options.max_lines,
458 overflow: options.overflow,
459 height_behavior: options.height_behavior,
460 leading_distribution: style.leading_distribution,
461 generation,
462 }
463 }
464
465 fn into_owned(self) -> LayoutKey<'static> {
466 LayoutKey {
467 text: Cow::Owned(self.text.into_owned()),
468 family: self.family,
469 fallbacks: self.fallbacks,
470 size: self.size,
471 weight: self.weight,
472 style: self.style,
473 line_height: self.line_height,
474 letter_spacing: self.letter_spacing,
475 font_variations: self.font_variations,
476 font_features: self.font_features,
477 width: self.width,
478 align: self.align,
479 text_direction: self.text_direction,
480 soft_wrap: self.soft_wrap,
481 max_lines: self.max_lines,
482 overflow: self.overflow,
483 height_behavior: self.height_behavior,
484 leading_distribution: self.leading_distribution,
485 generation: self.generation,
486 }
487 }
488}
489
490thread_local! {
492 static APPLICATION_FONTS: std::cell::RefCell<Vec<parley::fontique::Blob<u8>>> =
493 const { std::cell::RefCell::new(Vec::new()) };
494}
495
496pub fn register_application_font(bytes: impl Into<Vec<u8>>) {
506 let blob = parley::fontique::Blob::from(bytes.into());
507 APPLICATION_FONTS.with(|fonts| fonts.borrow_mut().push(blob));
508}
509
510#[derive(Default)]
511struct MissingCharacters {
512 reported: HashSet<char>,
514 pending: Vec<char>,
515 listener: Option<std::rc::Rc<dyn Fn()>>,
516}
517
518thread_local! {
519 static MISSING_CHARACTERS: std::cell::RefCell<MissingCharacters> =
520 std::cell::RefCell::new(MissingCharacters::default());
521}
522
523pub fn set_missing_characters_listener(listener: impl Fn() + 'static) {
529 MISSING_CHARACTERS.with_borrow_mut(|missing| {
530 missing.listener = Some(std::rc::Rc::new(listener));
531 });
532}
533
534#[must_use]
538pub fn take_missing_characters() -> Vec<char> {
539 MISSING_CHARACTERS.with_borrow_mut(|missing| std::mem::take(&mut missing.pending))
540}
541
542fn note_missing_characters(text: &str) {
543 let listener = MISSING_CHARACTERS.with_borrow_mut(|missing| {
544 let before = missing.pending.len();
545 for character in text.chars() {
546 if character.is_whitespace() || character.is_control() {
548 continue;
549 }
550 if missing.reported.insert(character) {
551 missing.pending.push(character);
552 }
553 }
554 (missing.pending.len() > before)
555 .then(|| missing.listener.clone())
556 .flatten()
557 });
558 if let Some(listener) = listener {
559 listener();
560 }
561}
562
563pub struct TextEngine {
566 font_handles: HashMap<(usize, usize, u32), FontHandle>,
572 font_context: FontContext,
573 layout_context: LayoutContext<()>,
574 generation: u64,
575 application_fonts: usize,
577 cache: LayoutPool,
578 unwrapped: LayoutPool,
581 shaped: LayoutPool<Layout<()>>,
584 cache_byte_budget: usize,
587 documents: Vec<ComposedDocument>,
589 retained_documents: Vec<document::RetainedDocument>,
591 diagnostics: TextDiagnostics,
592}
593
594const COMPOSED_DOCUMENTS: usize = 4;
597
598const fn cache_share(budget: usize) -> usize {
600 budget / 2
601}
602
603const fn minor_share(budget: usize) -> usize {
605 budget / 4
606}
607
608#[derive(Clone, Copy)]
610struct ParagraphStart {
611 byte: usize,
612 line: usize,
613 font_run: usize,
614 width: f32,
615 height: f32,
616 line_height: f32,
617 overflowed: bool,
618}
619
620struct ComposedDocument {
624 policy: LayoutKey<'static>,
626 overflow: TextOverflow,
628 text: String,
629 lines: Arc<[TextLine]>,
631 font_runs: Arc<[FontRunDebug]>,
632 starts: Vec<ParagraphStart>,
633 layout: Arc<TextLayout>,
634}
635
636impl Default for TextEngine {
637 fn default() -> Self {
638 Self::new()
639 }
640}
641
642impl TextEngine {
643 #[must_use]
644 pub fn new() -> Self {
645 Self {
646 font_handles: HashMap::new(),
647 font_context: FontContext::new(),
648 layout_context: LayoutContext::new(),
649 generation: 1,
650 application_fonts: 0,
651 cache: LayoutPool::new(cache_share(DEFAULT_CACHE_BYTE_BUDGET)),
652 unwrapped: LayoutPool::new(minor_share(DEFAULT_CACHE_BYTE_BUDGET)),
653 shaped: LayoutPool::new(minor_share(DEFAULT_CACHE_BYTE_BUDGET)),
654 cache_byte_budget: DEFAULT_CACHE_BYTE_BUDGET,
655 documents: Vec::new(),
656 retained_documents: Vec::new(),
657 diagnostics: TextDiagnostics::default(),
658 }
659 }
660
661 pub fn with_shared<R>(f: impl FnOnce(&mut Self) -> R) -> R {
669 thread_local! {
670 static SHARED: std::cell::RefCell<TextEngine> =
671 std::cell::RefCell::new(TextEngine::new());
672 }
673 SHARED.with(|shared| match shared.try_borrow_mut() {
674 Ok(mut engine) => f(&mut engine),
675 Err(_) => f(&mut Self::new()),
676 })
677 }
678
679 #[must_use]
680 pub const fn diagnostics(&self) -> TextDiagnostics {
681 let mut diagnostics = self.diagnostics;
682 diagnostics.cache_growths =
683 self.cache.growths + self.unwrapped.growths + self.shaped.growths;
684 diagnostics.cache_shrinks =
685 self.cache.shrinks + self.unwrapped.shrinks + self.shaped.shrinks;
686 diagnostics
687 }
688
689 #[must_use]
692 pub const fn cache_byte_budget(&self) -> usize {
693 self.cache_byte_budget
694 }
695
696 pub fn set_cache_byte_budget(&mut self, bytes: usize) {
702 self.cache_byte_budget = bytes;
703 self.cache.set_byte_budget(cache_share(bytes));
704 self.unwrapped.set_byte_budget(minor_share(bytes));
705 self.shaped.set_byte_budget(minor_share(bytes));
706 }
707
708 #[must_use]
711 pub const fn cache_bytes(&self) -> usize {
712 self.cache.bytes() + self.unwrapped.bytes() + self.shaped.bytes()
713 }
714
715 #[must_use]
719 pub fn has_pending_application_fonts(&self) -> bool {
720 APPLICATION_FONTS.with(|fonts| fonts.borrow().len() > self.application_fonts)
721 }
722
723 pub fn sync_application_fonts(&mut self) -> bool {
726 let pending = APPLICATION_FONTS.with(|fonts| {
727 fonts
728 .borrow()
729 .get(self.application_fonts..)
730 .map(<[_]>::to_vec)
731 .unwrap_or_default()
732 });
733 self.application_fonts += pending.len();
734 let registered = !pending.is_empty();
735 for blob in pending {
736 self.register_font_blob(blob);
737 }
738 registered
739 }
740
741 fn tick_pools(&mut self) {
743 self.cache.tick();
744 self.unwrapped.tick();
745 self.shaped.tick();
746 }
747
748 #[must_use]
749 pub const fn font_database_generation(&self) -> u64 {
750 self.generation
751 }
752
753 pub fn register_font_bytes(&mut self, bytes: impl Into<Vec<u8>>) {
757 self.register_font_blob(parley::fontique::Blob::from(bytes.into()));
758 }
759
760 fn register_font_blob(&mut self, blob: parley::fontique::Blob<u8>) {
761 use parley::fontique::GenericFamily;
762
763 let collection = &mut self.font_context.collection;
764 let families: Vec<_> = collection
765 .register_fonts(blob, None)
766 .into_iter()
767 .map(|(family, _)| family)
768 .collect();
769 for generic in [
770 GenericFamily::Serif,
771 GenericFamily::SansSerif,
772 GenericFamily::Monospace,
773 GenericFamily::Cursive,
774 GenericFamily::Fantasy,
775 GenericFamily::SystemUi,
776 GenericFamily::UiSerif,
777 GenericFamily::UiSansSerif,
778 GenericFamily::UiMonospace,
779 GenericFamily::UiRounded,
780 GenericFamily::Emoji,
781 GenericFamily::Math,
782 GenericFamily::FangSong,
783 ] {
784 if families.is_empty() {
785 continue;
786 }
787 if collection.generic_families(generic).next().is_none() {
788 collection.set_generic_families(generic, families.iter().copied());
789 } else if cfg!(target_arch = "wasm32") {
790 collection.append_generic_families(generic, families.iter().copied());
794 }
795 }
796 self.generation = self
797 .generation
798 .checked_add(1)
799 .expect("font database generation exhausted");
800 self.cache.clear();
801 self.unwrapped.clear();
802 self.shaped.clear();
803 self.documents.clear();
804 self.retained_documents.clear();
805 }
806
807 pub fn layout(
808 &mut self,
809 text: &str,
810 style: &TextStyle,
811 max_width: Option<f32>,
812 align: TextAlign,
813 ) -> Arc<TextLayout> {
814 self.layout_with_options(text, style, TextLayoutOptions::new(max_width, align))
815 }
816
817 pub fn layout_with_options(
819 &mut self,
820 text: &str,
821 style: &TextStyle,
822 options: TextLayoutOptions,
823 ) -> Arc<TextLayout> {
824 self.diagnostics.layouts_requested += 1;
825 self.sync_application_fonts();
826 self.tick_pools();
827 let document = text.contains('\n')
839 && options.max_lines.is_none()
840 && options.height_behavior.apply_height_to_first_ascent
841 && options.height_behavior.apply_height_to_last_descent
842 && (options.soft_wrap
843 || options.overflow == TextOverflow::Visible
844 || (options.max_width.is_some()
845 && matches!(options.overflow, TextOverflow::Clip | TextOverflow::Fade)));
846 if document {
852 return self.layout_document(text, style, options);
853 }
854 let key = LayoutKey::new(text, style, options, self.generation);
855 if let Some(layout) = self.cache.get(&key) {
856 self.diagnostics.cache_hits += 1;
857 return layout;
858 }
859 self.diagnostics.cache_misses += 1;
860 let (layout, reused) = self.shape_or_reuse(text, style, options);
861 if !reused {
865 self.cache.put(key, layout.clone());
866 }
867 layout
868 }
869
870 fn shape_or_reuse(
881 &mut self,
882 text: &str,
883 style: &TextStyle,
884 options: TextLayoutOptions,
885 ) -> (Arc<TextLayout>, bool) {
886 let key = LayoutKey::new(
887 text,
888 style,
889 TextLayoutOptions {
890 max_width: None,
891 ..options
892 },
893 self.generation,
894 );
895 if let Some(layout) = self.unwrapped.get(&key)
896 && options
897 .max_width
898 .is_none_or(|width| layout.metrics.size.width <= width)
899 {
900 self.diagnostics.width_reuses += 1;
901 return (layout, true);
902 }
903 let layout = Arc::new(self.shape_and_wrap(text, style, options));
904 if width_independent(&layout, options) {
905 self.unwrapped.put(key, layout.clone());
906 }
907 (layout, false)
908 }
909
910 fn reserve_document(&mut self, paragraphs: usize) {
914 self.cache.pin(paragraphs);
915 self.unwrapped.pin(paragraphs);
916 let required = paragraphs.saturating_add(LAYOUT_CACHE_CAPACITY.get());
917 if self.cache.capacity() < required {
918 let capacity = required.next_power_of_two();
919 self.cache.reserve(capacity);
920 self.unwrapped.reserve(capacity);
921 }
922 }
923
924 fn layout_document(
928 &mut self,
929 text: &str,
930 style: &TextStyle,
931 options: TextLayoutOptions,
932 ) -> Arc<TextLayout> {
933 let paragraph_options = TextLayoutOptions {
934 max_lines: None,
935 overflow: TextOverflow::Visible,
936 ..options
937 };
938 let policy = LayoutKey::new("", style, paragraph_options, self.generation);
939 if let Some(index) = self.documents.iter().position(|document| {
942 document.policy == policy
943 && document.overflow == options.overflow
944 && document.text == text
945 }) {
946 self.diagnostics.cache_hits += 1;
947 self.diagnostics.documents_reused += 1;
948 let document = self.documents.remove(index);
949 let layout = document.layout.clone();
950 self.documents.insert(0, document);
951 return layout;
952 }
953 self.diagnostics.documents_composed += 1;
954 self.reserve_document(text.bytes().filter(|byte| *byte == b'\n').count() + 1);
955 let mut lines: Vec<TextLine> = Vec::new();
956 let mut font_runs = Vec::new();
957 let mut starts = Vec::new();
958 let mut state = ParagraphStart {
959 byte: 0,
960 line: 0,
961 font_run: 0,
962 width: 0.,
963 height: 0.,
964 line_height: 0.,
965 overflowed: false,
966 };
967 let mut document_carets = has_join_controls(text).then(Vec::new);
968 if document_carets.is_none()
971 && let Some((index, carried)) = self.carried_paragraphs(&policy, text)
972 {
973 let previous = &self.documents[index];
974 state = previous.starts[carried];
975 lines.extend_from_slice(&previous.lines[..state.line]);
976 font_runs.extend_from_slice(&previous.font_runs[..state.font_run]);
977 starts.extend_from_slice(&previous.starts[..carried]);
978 self.diagnostics.paragraphs_carried += carried as u64;
979 }
980 let mut byte_start = state.byte;
981 let mut width = state.width;
982 let mut height = state.height;
983 let mut line_height = state.line_height;
984 let mut overflowed = state.overflowed;
985 for paragraph in text[byte_start..].split('\n') {
986 self.diagnostics.paragraphs_considered += 1;
987 starts.push(ParagraphStart {
988 byte: byte_start,
989 line: lines.len(),
990 font_run: font_runs.len(),
991 width,
992 height,
993 line_height,
994 overflowed,
995 });
996 let key = LayoutKey::new(paragraph, style, paragraph_options, self.generation);
997 let paragraph_layout = if let Some(existing) = self.cache.get(&key) {
998 self.diagnostics.parley_layouts_reused += 1;
999 self.diagnostics.cache_hits += 1;
1000 existing
1001 } else {
1002 self.diagnostics.paragraphs_reshaped += 1;
1003 self.diagnostics.cache_misses += 1;
1004 let (built, _) = self.shape_or_reuse(paragraph, style, paragraph_options);
1005 self.cache.put(key, built.clone());
1006 built
1007 };
1008 if let Some(carets) = &mut document_carets {
1009 let line_start = lines.len();
1010 let paragraph_carets = paragraph_layout
1011 .caret_positions
1012 .get_or_init(|| paragraph_layout.derive_caret_positions());
1013 carets.extend(paragraph_carets.iter().map(|position| TextCaretPosition {
1014 offset: position.offset + byte_start,
1015 line: position.line + line_start,
1016 ..*position
1017 }));
1018 }
1019 for run in paragraph_layout.font_runs.iter() {
1022 let mut rebased = run.clone();
1023 rebased.range.start += byte_start;
1024 rebased.range.end += byte_start;
1025 font_runs.push(rebased);
1026 }
1027 for line in paragraph_layout.lines.iter() {
1028 let glyphs: Arc<[GlyphPosition]> = line
1031 .glyphs
1032 .iter()
1033 .map(|glyph| {
1034 let mut shifted = *glyph;
1035 shifted.offset.y -= height;
1038 shifted.cluster += byte_start as u32;
1041 shifted
1042 })
1043 .collect();
1044 let shifted_runs: Arc<[Arc<GlyphRun>]> = line
1045 .runs
1046 .iter()
1047 .map(|run| {
1048 let mut shifted = (**run).clone();
1049 shifted.origin.y += height;
1050 Arc::new(shifted)
1051 })
1052 .collect();
1053 lines.push(TextLine {
1054 runs: shifted_runs,
1055 glyphs,
1056 clusters: line
1057 .clusters
1058 .iter()
1059 .map(|span| TextClusterSpan {
1060 start: span.start + byte_start as u32,
1061 end: span.end + byte_start as u32,
1062 left: span.left,
1063 right: span.right,
1064 rtl: span.rtl,
1065 })
1066 .collect(),
1067 width: line.width,
1068 offset: line.offset,
1069 baseline: line.baseline + height,
1070 start: line.start + byte_start,
1071 caret_end: line.caret_end + byte_start,
1072 end: line.end + byte_start,
1073 });
1074 }
1075 width = width.max(paragraph_layout.metrics.size.width);
1076 height += paragraph_layout.metrics.size.height;
1077 line_height = paragraph_layout.metrics.line_height;
1078 overflowed |= paragraph_layout.overflowed;
1079 byte_start += paragraph.len() + 1; }
1081 if lines.is_empty() {
1082 lines.push(TextLine {
1083 runs: Arc::new([]),
1084 glyphs: Arc::new([]),
1085 clusters: Arc::new([]),
1086 width: 0.,
1087 offset: 0.,
1088 baseline: 0.,
1089 start: 0,
1090 caret_end: 0,
1091 end: 0,
1092 });
1093 }
1094 let baseline = lines[0].baseline;
1095 let lines: Arc<[TextLine]> = lines.into();
1096 let font_runs: Arc<[FontRunDebug]> = font_runs.into();
1097 let layout = TextLayout {
1098 lines: lines.clone(),
1099 metrics: TextMetrics {
1100 size: Size::new(width, height),
1101 baseline,
1102 line_height,
1103 },
1104 font_runs: font_runs.clone(),
1105 caret_positions: document_carets
1106 .map(|carets| OnceLock::from(Arc::from(carets)))
1107 .unwrap_or_default(),
1108 line_carets: OnceLock::new(),
1109 overflowed,
1110 };
1111 let horizontal_overflow = !options.soft_wrap
1114 && options.overflow != TextOverflow::Visible
1115 && options.max_width.is_some_and(|max| width > max);
1116 let layout = Arc::new(if horizontal_overflow {
1117 clipped_layout(layout, 1)
1118 } else {
1119 layout
1120 });
1121 self.documents.truncate(COMPOSED_DOCUMENTS - 1);
1122 self.documents.insert(
1123 0,
1124 ComposedDocument {
1125 policy,
1126 overflow: options.overflow,
1127 text: text.to_owned(),
1128 lines,
1129 font_runs,
1130 starts,
1131 layout: layout.clone(),
1132 },
1133 );
1134 layout
1135 }
1136
1137 fn carried_paragraphs(&self, policy: &LayoutKey<'_>, text: &str) -> Option<(usize, usize)> {
1142 self.documents
1143 .iter()
1144 .enumerate()
1145 .filter(|(_, document)| document.policy == *policy)
1146 .map(|(index, document)| {
1147 let prefix = common_prefix_len(&document.text, text);
1148 let carried = document
1149 .starts
1150 .partition_point(|start| start.byte <= prefix)
1151 .saturating_sub(1);
1152 (index, carried)
1153 })
1154 .filter(|(_, carried)| *carried > 0)
1155 .max_by_key(|(_, carried)| *carried)
1156 }
1157
1158 fn font_stack(style: &TextStyle) -> parley::FontFamily<'static> {
1159 let mut names = vec![family_name(&style.family)];
1160 names.extend(style.fallback_families.iter().map(family_name));
1161 parley::FontFamily::Source(names.join(", ").into())
1162 }
1163
1164 fn shape_and_wrap(
1165 &mut self,
1166 text: &str,
1167 style: &TextStyle,
1168 options: TextLayoutOptions,
1169 ) -> TextLayout {
1170 let wrap_width = options.soft_wrap.then_some(options.max_width).flatten();
1171 let layout = self.shape_raw(
1172 text,
1173 style,
1174 wrap_width,
1175 options.max_width,
1176 options.align,
1177 options.text_direction,
1178 options.height_behavior,
1179 );
1180 let horizontal_overflow = !options.soft_wrap
1181 && options
1182 .max_width
1183 .is_some_and(|width| layout.metrics.size.width > width);
1184 let line_limit = options
1185 .max_lines
1186 .or_else(|| horizontal_overflow.then_some(1));
1187 let line_overflow = line_limit.is_some_and(|limit| layout.lines.len() > limit);
1188 if (!horizontal_overflow && !line_overflow) || options.overflow == TextOverflow::Visible {
1189 return layout;
1190 }
1191
1192 let limit = line_limit.unwrap_or(layout.lines.len());
1193 match options.overflow {
1194 TextOverflow::Visible => layout,
1195 TextOverflow::Clip | TextOverflow::Fade => clipped_layout(layout, limit),
1196 TextOverflow::Ellipsis => self.ellipsized_layout(text, style, options, limit),
1197 }
1198 }
1199
1200 fn ellipsized_layout(
1201 &mut self,
1202 text: &str,
1203 style: &TextStyle,
1204 options: TextLayoutOptions,
1205 limit: usize,
1206 ) -> TextLayout {
1207 if limit == 0 {
1208 return clipped_layout(
1209 self.shape_raw(
1210 "",
1211 style,
1212 None,
1213 options.max_width,
1214 options.align,
1215 options.text_direction,
1216 options.height_behavior,
1217 ),
1218 0,
1219 );
1220 }
1221 let wrap_width = options.soft_wrap.then_some(options.max_width).flatten();
1222 let initial = self.shape_raw(
1223 text,
1224 style,
1225 wrap_width,
1226 options.max_width,
1227 options.align,
1228 options.text_direction,
1229 options.height_behavior,
1230 );
1231 let end = initial
1232 .lines
1233 .get(limit.saturating_sub(1))
1234 .map_or(0, |line| line.caret_end.min(text.len()));
1235 let mut end = grapheme_boundary_at_or_before(text, end);
1236
1237 loop {
1238 let mut candidate = text[..end].to_owned();
1239 candidate.push('\u{2026}');
1240 let shaped = self.shape_raw(
1244 &candidate,
1245 style,
1246 wrap_width,
1247 options.max_width,
1248 options.align,
1249 options.text_direction,
1250 options.height_behavior,
1251 );
1252 let fits_width = options.max_width.is_none_or(|width| {
1253 shaped
1254 .lines
1255 .iter()
1256 .all(|line| line.width <= width + f32::EPSILON)
1257 });
1258 if shaped.lines.len() <= limit && fits_width {
1259 return TextLayout {
1260 overflowed: true,
1261 ..shaped
1262 };
1263 }
1264 let previous = previous_grapheme_boundary(text, end);
1265 if previous == end {
1266 return TextLayout {
1267 overflowed: true,
1268 ..shaped
1269 };
1270 }
1271 end = previous;
1272 }
1273 }
1274
1275 fn shape_paragraph(
1277 &mut self,
1278 text: &str,
1279 style: &TextStyle,
1280 max_width: Option<f32>,
1281 text_direction: Option<TextDirection>,
1282 ) -> Layout<()> {
1283 let mut builder =
1284 self.layout_context
1285 .ranged_builder(&mut self.font_context, text, 1.0, true);
1286 builder.set_base_direction(match text_direction {
1287 Some(TextDirection::Ltr) => BaseDirection::Ltr,
1288 Some(TextDirection::Rtl) => BaseDirection::Rtl,
1289 None => BaseDirection::Auto,
1290 });
1291 builder.push_default(StyleProperty::WhiteSpaceCollapse(
1294 parley::WhiteSpaceCollapse::Collapse,
1295 ));
1296 if max_width.is_none() {
1297 builder.push_default(StyleProperty::TextWrapMode(parley::TextWrapMode::NoWrap));
1298 }
1299 builder.push_default(StyleProperty::FontFamily(Self::font_stack(style)));
1300 builder.push_default(StyleProperty::FontSize(style.size.max(0.0)));
1301 builder.push_default(StyleProperty::FontWeight(ParleyFontWeight::new(
1302 style.weight.0 as f32,
1303 )));
1304 builder.push_default(StyleProperty::FontStyle(match style.style {
1305 FontStyle::Normal => parley::FontStyle::Normal,
1306 FontStyle::Italic => parley::FontStyle::Italic,
1307 }));
1308 builder.push_default(StyleProperty::LetterSpacing(style.letter_spacing));
1309 if let Some(settings) = &style.font_variations {
1310 builder.push_default(StyleProperty::FontVariations(
1311 parley::FontVariations::Source(Cow::Owned(settings.to_string())),
1312 ));
1313 }
1314 if let Some(settings) = &style.font_features {
1315 builder.push_default(StyleProperty::FontFeatures(parley::FontFeatures::Source(
1316 Cow::Owned(settings.to_string()),
1317 )));
1318 }
1319 if let Some(line_height) = style.line_height
1320 && let Some(parley_lh) = line_height.to_parley(style.size)
1321 {
1322 builder.push_default(StyleProperty::LineHeight(parley_lh));
1323 }
1324
1325 builder.build(text)
1326 }
1327
1328 #[allow(clippy::too_many_arguments)]
1329 fn shape_raw(
1330 &mut self,
1331 text: &str,
1332 style: &TextStyle,
1333 max_width: Option<f32>,
1334 align_width: Option<f32>,
1335 align: TextAlign,
1336 text_direction: Option<TextDirection>,
1337 height_behavior: TextHeightBehavior,
1338 ) -> TextLayout {
1339 let shaping_key = LayoutKey::new(
1343 text,
1344 style,
1345 TextLayoutOptions {
1346 max_width: max_width.map(|_| 1.),
1347 align: TextAlign::Start,
1348 text_direction,
1349 soft_wrap: true,
1350 max_lines: None,
1351 overflow: TextOverflow::Visible,
1352 height_behavior: TextHeightBehavior::default(),
1353 },
1354 self.generation,
1355 );
1356 let mut layout = match self.shaped.take(&shaping_key) {
1357 Some(layout) => {
1358 self.diagnostics.shapes_reused += 1;
1359 layout
1360 }
1361 None => self.shape_paragraph(text, style, max_width, text_direction),
1362 };
1363 layout.break_all_lines(align_width.or(max_width));
1364 layout.align(
1368 match align {
1369 TextAlign::Start => Alignment::Start,
1370 TextAlign::Center => Alignment::Center,
1371 TextAlign::End => Alignment::End,
1372 TextAlign::Justify => Alignment::Justify,
1373 TextAlign::Left => Alignment::Left,
1374 TextAlign::Right => Alignment::Right,
1375 },
1376 AlignmentOptions::default(),
1377 );
1378
1379 let mut translated = self.translate_layout(&layout, text);
1380 if matches!(
1381 style.line_height,
1382 Some(LineHeight::Multiplier(_) | LineHeight::Absolute(_))
1383 ) {
1384 apply_height_behavior(
1385 &mut translated,
1386 &layout,
1387 height_behavior,
1388 style.leading_distribution,
1389 );
1390 }
1391 if text.is_empty() {
1392 let width = align_width.unwrap_or(0.0).max(0.0);
1396 let offset = match (align, layout.is_rtl()) {
1397 (TextAlign::Right, _)
1398 | (TextAlign::Start | TextAlign::Justify, true)
1399 | (TextAlign::End, false) => width,
1400 (TextAlign::Center, _) => width * 0.5,
1401 _ => 0.0,
1402 };
1403 for line in Arc::make_mut(&mut translated.lines) {
1404 line.runs = Arc::new([]);
1405 line.glyphs = Arc::new([]);
1406 line.clusters = Arc::new([]);
1407 line.width = 0.0;
1408 line.offset = offset;
1409 }
1410 translated.metrics.size.width = 0.0;
1411 }
1412 if layout.len() > 1 {
1413 self.shaped.put(shaping_key, layout);
1414 }
1415 translated
1416 }
1417
1418 fn font_handle_for(&mut self, font: &parley::FontData) -> FontHandle {
1419 font_handle(self, font)
1420 }
1421
1422 fn translate_layout(&mut self, layout: &Layout<()>, text: &str) -> TextLayout {
1423 let mut lines = Vec::new();
1424 let mut font_runs = Vec::new();
1425 let mut used_fonts = HashSet::new();
1426 let primary_handle = layout
1429 .lines()
1430 .flat_map(|line| line.runs())
1431 .next()
1432 .map(|run| self.font_handle_for(run.font()));
1433 let primary = primary_handle.as_ref().map(FontHandle::id);
1434
1435 for line in layout.lines() {
1436 let metrics = line.metrics();
1437 let range = line.text_range();
1438 let mut runs: Vec<Arc<GlyphRun>> = Vec::new();
1439 let mut glyphs = Vec::new();
1440 let mut caret_end = range.start;
1441 let mut cluster_positions: Vec<ClusterPosition> = Vec::new();
1448 let mut cluster_position_index: HashMap<(usize, usize), usize> = HashMap::new();
1449 let mut cluster_x = metrics.offset;
1450 for run in line.runs() {
1451 let rtl = run.is_rtl();
1452 for cluster in run.visual_clusters() {
1453 let cluster_range = cluster.text_range();
1454 if cluster.glyphs().any(|glyph| glyph.id == 0)
1456 && let Some(source) = text.get(cluster_range.clone())
1457 {
1458 note_missing_characters(source);
1459 }
1460 caret_end = caret_end.max(cluster_range.end);
1464 let edge = cluster_x + cluster.advance();
1465 let ligature_start = cluster.is_ligature_start();
1466 let ligature_continuation = cluster.is_ligature_continuation();
1467 let mut cluster_glyphs = cluster.glyphs();
1468 let Some(first_glyph) = cluster_glyphs.next() else {
1469 let is_join_control =
1475 text.get(cluster_range.clone()).is_some_and(|component| {
1476 !component.is_empty()
1477 && component.chars().all(|character| {
1478 matches!(character, '\u{200C}' | '\u{200D}')
1479 })
1480 });
1481 if ligature_continuation && !is_join_control {
1482 let position = ClusterPosition {
1483 range: cluster_range.clone(),
1484 left: cluster_x.min(edge),
1485 right: cluster_x.max(edge),
1486 rtl,
1487 ligature_start,
1488 ligature_continuation,
1489 };
1490 let key = (cluster_range.start, cluster_range.end);
1491 if let Some(index) = cluster_position_index.get(&key).copied() {
1492 cluster_positions[index].include(&position);
1493 } else {
1494 cluster_position_index.insert(key, cluster_positions.len());
1495 cluster_positions.push(position);
1496 }
1497 }
1498 cluster_x = edge;
1499 continue;
1500 };
1501 let glyph_advance = std::iter::once(first_glyph)
1502 .chain(cluster_glyphs)
1503 .map(|glyph| glyph.advance)
1504 .sum::<f32>();
1505 let visible_edge = cluster_x + cluster.advance().max(glyph_advance);
1511 let left = cluster_x.min(visible_edge);
1512 let right = cluster_x.max(visible_edge);
1513 let position = ClusterPosition {
1514 range: cluster_range.clone(),
1515 left,
1516 right,
1517 rtl,
1518 ligature_start,
1519 ligature_continuation,
1520 };
1521 let key = (cluster_range.start, cluster_range.end);
1522 if let Some(index) = cluster_position_index.get(&key).copied() {
1523 cluster_positions[index].include(&position);
1524 } else {
1525 cluster_position_index.insert(key, cluster_positions.len());
1526 cluster_positions.push(position);
1527 }
1528 cluster_x = edge;
1529 }
1530 }
1531 for item in line.items() {
1532 let PositionedLayoutItem::GlyphRun(parley_run) = item else {
1533 continue;
1534 };
1535 let run = parley_run.run();
1536 let font = self.font_handle_for(run.font());
1537 let font_id = font.id();
1538 if Some(font_id) != primary {
1539 self.diagnostics.fallback_runs += 1;
1540 }
1541 used_fonts.insert(font_id);
1542 self.diagnostics.shaping_runs += 1;
1543
1544 let visible_glyphs = parley_run.glyphs().collect::<Vec<_>>();
1561 let all_clustered_glyphs = run
1562 .visual_clusters()
1563 .flat_map(|cluster| {
1564 let cluster_range = cluster.text_range();
1565 cluster
1566 .glyphs()
1567 .map(move |glyph| (glyph, cluster_range.clone()))
1568 })
1569 .collect::<Vec<_>>();
1570 let first_glyph = all_clustered_glyphs
1571 .windows(visible_glyphs.len())
1572 .position(|candidate| {
1573 candidate
1574 .iter()
1575 .zip(&visible_glyphs)
1576 .all(|((glyph, _), visible)| glyph == visible)
1577 })
1578 .unwrap_or(0);
1579 let clusters = all_clustered_glyphs
1580 .iter()
1581 .skip(first_glyph)
1582 .map(|(_, range)| range.clone())
1583 .take(visible_glyphs.len())
1584 .collect::<Vec<_>>();
1585 let positions: Vec<_> = parley_run
1586 .positioned_glyphs()
1587 .zip(
1588 clusters
1589 .iter()
1590 .map(|range| range.start as u32)
1591 .chain(std::iter::repeat(run.text_range().start as u32)),
1592 )
1593 .map(|(glyph, cluster)| GlyphPosition {
1594 id: glyph.id as u16,
1595 offset: Offset::new(glyph.x, -glyph.y),
1601 advance: glyph.advance,
1602 cluster,
1603 })
1604 .collect();
1605 if runs.len() == 1 {
1606 glyphs.extend(runs[0].glyphs.iter().copied());
1607 }
1608 if !runs.is_empty() {
1609 glyphs.extend(positions.iter().copied());
1610 }
1611 runs.push(Arc::new(GlyphRun {
1612 font,
1613 font_size: run.font_size(),
1614 origin: Offset::ZERO,
1615 glyphs: positions.into(),
1616 }));
1617 font_runs.push(FontRunDebug {
1618 range: run.text_range(),
1619 font: font_id,
1620 direction: if run.is_rtl() { "rtl" } else { "ltr" },
1621 });
1622 }
1623 let cluster_spans: Vec<_> = merge_ligature_components(cluster_positions)
1626 .iter()
1627 .map(|position| TextClusterSpan {
1628 start: position.range.start as u32,
1629 end: position.range.end as u32,
1630 left: position.left,
1631 right: position.right,
1632 rtl: position.rtl,
1633 })
1634 .collect();
1635 let glyphs = if runs.len() == 1 {
1639 runs[0].glyphs.clone()
1640 } else {
1641 glyphs.into()
1642 };
1643 lines.push(TextLine {
1644 runs: runs.into(),
1645 glyphs,
1646 clusters: cluster_spans.into(),
1647 width: metrics.advance,
1648 offset: metrics.offset,
1649 baseline: metrics.baseline,
1650 start: range.start,
1651 caret_end,
1652 end: range.end,
1653 });
1654 }
1655
1656 if lines.is_empty() {
1657 lines.push(TextLine {
1658 runs: Arc::new([]),
1659 glyphs: Arc::new([]),
1660 clusters: Arc::new([]),
1661 width: 0.0,
1662 offset: 0.0,
1663 baseline: 0.0,
1664 start: 0,
1665 caret_end: 0,
1666 end: 0,
1667 });
1668 }
1669 self.diagnostics.fallback_fonts_used += used_fonts.len().saturating_sub(1) as u64;
1670 let baseline = lines[0].baseline;
1671 let line_height = layout
1672 .lines()
1673 .next()
1674 .map_or(0.0, |line| line.metrics().line_height);
1675 let mut translated = TextLayout {
1676 lines: lines.into(),
1677 metrics: TextMetrics {
1678 size: Size::new(layout.width(), layout.height()),
1679 baseline,
1680 line_height,
1681 },
1682 font_runs: font_runs.into(),
1683 caret_positions: OnceLock::new(),
1684 line_carets: OnceLock::new(),
1685 overflowed: false,
1686 };
1687 if has_join_controls(text) {
1688 translated
1692 .caret_positions
1693 .get_or_init(|| translated.derive_caret_positions());
1694 for line in Arc::make_mut(&mut translated.lines) {
1695 let mut visible = Vec::with_capacity(line.clusters.len());
1696 for span in line.clusters.iter() {
1697 let Some(component) = text.get(span.range()) else {
1698 visible.push(*span);
1699 continue;
1700 };
1701 let mut start = span.start;
1702 for (offset, character) in component.char_indices() {
1703 if matches!(character, '\u{200C}' | '\u{200D}') {
1704 let end = span.start + offset as u32;
1705 if start < end {
1706 visible.push(TextClusterSpan {
1707 start,
1708 end,
1709 ..*span
1710 });
1711 }
1712 start = end + character.len_utf8() as u32;
1713 }
1714 }
1715 if start < span.end {
1716 visible.push(TextClusterSpan { start, ..*span });
1717 }
1718 }
1719 line.clusters = visible.into();
1720 }
1721 }
1722 translated
1723 }
1724}
1725
1726fn has_join_controls(text: &str) -> bool {
1727 text.contains('\u{200C}') || text.contains('\u{200D}')
1728}
1729
1730fn merge_ligature_components(positions: Vec<ClusterPosition>) -> Vec<ClusterPosition> {
1735 let mut merged = Vec::with_capacity(positions.len());
1736 let mut pending: Option<ClusterPosition> = None;
1737 let mut pending_has_start = false;
1738
1739 for position in positions {
1740 let is_start = position.ligature_start;
1741 let is_continuation = position.ligature_continuation;
1742 if !is_start && !is_continuation {
1743 if let Some(pending) = pending.take() {
1744 merged.push(pending);
1745 }
1746 pending_has_start = false;
1747 merged.push(position);
1748 continue;
1749 }
1750 if is_start && is_continuation {
1752 if let Some(pending) = pending.take() {
1753 merged.push(pending);
1754 }
1755 pending_has_start = false;
1756 merged.push(position);
1757 continue;
1758 }
1759
1760 if position.rtl {
1761 if is_continuation {
1762 if pending_has_start {
1765 if let Some(pending) = pending.take() {
1766 merged.push(pending);
1767 }
1768 pending_has_start = false;
1769 }
1770 let can_extend = pending
1771 .as_ref()
1772 .is_some_and(|pending| pending.can_join(&position));
1773 if can_extend {
1774 pending
1775 .as_mut()
1776 .expect("checked pending ligature group")
1777 .include(&position);
1778 } else {
1779 if let Some(pending) = pending.take() {
1780 merged.push(pending);
1781 }
1782 pending_has_start = false;
1783 pending = Some(position);
1784 }
1785 } else {
1786 match pending.take() {
1788 Some(mut pending_group)
1789 if !pending_has_start && pending_group.can_join(&position) =>
1790 {
1791 pending_group.include(&position);
1792 merged.push(pending_group);
1793 }
1794 Some(pending_group) => {
1795 merged.push(pending_group);
1796 merged.push(position);
1797 }
1798 None => merged.push(position),
1799 }
1800 pending_has_start = false;
1801 }
1802 } else if is_start {
1803 if let Some(pending) = pending.take() {
1804 merged.push(pending);
1805 }
1806 pending = Some(position);
1807 pending_has_start = true;
1808 } else if pending.is_some() {
1809 if pending
1813 .as_ref()
1814 .is_some_and(|pending_group| pending_group.can_join(&position))
1815 {
1816 pending
1817 .as_mut()
1818 .expect("checked pending ligature group")
1819 .include(&position);
1820 } else {
1821 let pending_group = pending.take().expect("pending ligature group");
1822 merged.push(pending_group);
1823 pending = Some(position);
1824 pending_has_start = false;
1825 }
1826 } else {
1827 pending = Some(position);
1830 pending_has_start = false;
1831 }
1832 }
1833
1834 if let Some(pending) = pending {
1835 merged.push(pending);
1836 }
1837 merged
1838}
1839
1840fn common_prefix_len(left: &str, right: &str) -> usize {
1842 let (left, right) = (left.as_bytes(), right.as_bytes());
1843 let mut prefix = 0;
1844 for (a, b) in left.chunks(64).zip(right.chunks(64)) {
1845 if a == b {
1846 prefix += a.len();
1847 } else {
1848 return prefix + a.iter().zip(b).take_while(|(x, y)| x == y).count();
1849 }
1850 }
1851 prefix
1852}
1853
1854fn width_independent(layout: &TextLayout, options: TextLayoutOptions) -> bool {
1857 matches!(
1858 options.align,
1859 TextAlign::Start | TextAlign::Left | TextAlign::Justify
1860 ) && options.text_direction != Some(TextDirection::Rtl)
1861 && !layout.overflowed
1862 && layout.lines.len() == 1
1863 && layout.lines[0].offset == 0.0
1864 && layout.font_runs.iter().all(|run| run.direction != "rtl")
1865}
1866
1867fn clipped_layout(mut layout: TextLayout, limit: usize) -> TextLayout {
1868 let lines = layout.lines.iter().take(limit).cloned().collect::<Vec<_>>();
1869 if let Some(carets) = layout.caret_positions.get_mut() {
1870 let retained = carets.partition_point(|position| position.line < lines.len());
1871 if retained < carets.len() {
1872 *carets = Arc::from(&carets[..retained]);
1873 }
1874 }
1875 let width = lines
1876 .iter()
1877 .fold(0.0_f32, |width, line| width.max(line.width));
1878 layout.metrics.size = Size::new(width, layout.metrics.line_height * lines.len() as f32);
1879 layout.metrics.baseline = lines.first().map_or(0.0, |line| line.baseline);
1880 layout.lines = lines.into();
1881 layout.line_carets = OnceLock::new();
1882 layout.overflowed = true;
1883 layout
1884}
1885
1886fn grapheme_boundary_at_or_before(text: &str, offset: usize) -> usize {
1887 GraphemeClusterSegmenter::new()
1888 .segment_str(text)
1889 .take_while(|boundary| *boundary <= offset)
1890 .last()
1891 .unwrap_or(0)
1892}
1893
1894fn previous_grapheme_boundary(text: &str, offset: usize) -> usize {
1895 GraphemeClusterSegmenter::new()
1896 .segment_str(text)
1897 .take_while(|boundary| *boundary < offset)
1898 .last()
1899 .unwrap_or(0)
1900}
1901
1902fn family_name(family: &FontFamily) -> String {
1903 match family {
1904 FontFamily::SansSerif => "sans-serif".into(),
1905 FontFamily::Serif => "serif".into(),
1906 FontFamily::Monospace => "monospace".into(),
1907 FontFamily::Cursive => "cursive".into(),
1908 FontFamily::SystemUi => "system-ui".into(),
1909 FontFamily::Named(name) => format!("'{name}'"),
1910 }
1911}
1912
1913type FontBlobKey = (usize, usize, u32);
1917
1918fn font_blob_key(font: &parley::FontData) -> FontBlobKey {
1919 (
1920 font.data.as_ref().as_ptr() as usize,
1921 font.data.as_ref().len(),
1922 font.index,
1923 )
1924}
1925
1926fn font_handle(engine: &mut TextEngine, font: &parley::FontData) -> FontHandle {
1929 let key = font_blob_key(font);
1930 if let Some(handle) = engine.font_handles.get(&key) {
1931 return handle.clone();
1932 }
1933 let mut hasher = DefaultHasher::new();
1934 font.data.as_ref().hash(&mut hasher);
1935 hasher.write_u64(u64::from(font.index));
1936 let id = FontId(hasher.finish());
1937 let handle = FontHandle::with_face_index(
1940 id,
1941 FontBytes::from_shared(font.data.clone().into_raw_parts().0),
1942 font.index,
1943 );
1944 engine.font_handles.insert(key, handle.clone());
1945 handle
1946}
1947
1948fn apply_height_behavior(
1956 translated: &mut TextLayout,
1957 layout: &Layout<()>,
1958 behavior: TextHeightBehavior,
1959 style_distribution: Option<TextLeadingDistribution>,
1960) {
1961 let distribution = style_distribution.unwrap_or(behavior.leading_distribution);
1962 let placements: Vec<(f32, f32, f32)> = layout
1964 .lines()
1965 .map(|line| {
1966 let metrics = line.metrics();
1967 let ascent = metrics.baseline - metrics.content_block_min_coord;
1968 let descent = metrics.content_block_max_coord - metrics.baseline;
1969 let height = metrics.block_max_coord - metrics.block_min_coord;
1970 let baseline = match distribution {
1971 TextLeadingDistribution::Proportional if ascent + descent > 0. => {
1972 metrics.block_min_coord + height * ascent / (ascent + descent)
1973 }
1974 _ => metrics.baseline,
1975 };
1976 (
1977 baseline - metrics.baseline,
1978 baseline - ascent - metrics.block_min_coord,
1979 metrics.block_max_coord - baseline - descent,
1980 )
1981 })
1982 .collect();
1983 let (Some(first), Some(last)) = (placements.first(), placements.last()) else {
1984 return;
1985 };
1986 let top = if behavior.apply_height_to_first_ascent {
1987 0.
1988 } else {
1989 first.1
1990 };
1991 let bottom = if behavior.apply_height_to_last_descent {
1992 0.
1993 } else {
1994 last.2
1995 };
1996 if top == 0. && bottom == 0. && placements.iter().all(|placement| placement.0 == 0.) {
1997 return;
1998 }
1999 for (line, placement) in Arc::make_mut(&mut translated.lines)
2000 .iter_mut()
2001 .zip(&placements)
2002 {
2003 let shift = placement.0 - top;
2004 if shift == 0. {
2005 continue;
2006 }
2007 line.baseline += shift;
2008 line.glyphs = line
2010 .glyphs
2011 .iter()
2012 .map(|glyph| {
2013 let mut shifted = *glyph;
2014 shifted.offset.y -= shift;
2015 shifted
2016 })
2017 .collect();
2018 line.runs = line
2019 .runs
2020 .iter()
2021 .map(|run| {
2022 let mut shifted = (**run).clone();
2023 shifted.origin.y += shift;
2024 Arc::new(shifted)
2025 })
2026 .collect();
2027 }
2028 translated.metrics.size.height = (translated.metrics.size.height - top - bottom).max(0.);
2029 translated.metrics.baseline = translated.lines[0].baseline;
2030}