1use cosmic_text::{
8 Attrs, Buffer, CacheKeyFlags, Ellipsize, EllipsizeHeightLimit, FontSystem, Metrics, Shaping,
9 Style as FontStyle, SwashContent, Weight, Wrap,
10};
11use rustc_hash::FxHashMap;
12
13use crate::atlas::GlyphAtlas;
14use crate::color::Color;
15use crate::display::{Clip, ClipId, Quad, QuadKind};
16use crate::geom::{Rect, Size, Vec2};
17use crate::key::Key;
18use crate::resources::Resources;
19use crate::retain::Kept;
20use crate::spec::{FontFamily, TextStyle, TextWrap, UnderlineStyle};
21use crate::tree::TextId;
22use crate::value::Value;
23
24pub(crate) struct Raster {
31 ctx: swash::scale::ScaleContext,
32 axes: FxHashMap<cosmic_text::fontdb::ID, Option<crate::weights::WghtAxis>>,
34 pub(crate) subpixel: bool,
38}
39
40impl Raster {
41 fn new() -> Self {
42 Self {
43 ctx: swash::scale::ScaleContext::new(),
44 axes: FxHashMap::default(),
45 subpixel: false,
46 }
47 }
48
49 fn image(
61 &mut self,
62 fs: &mut FontSystem,
63 key: cosmic_text::CacheKey,
64 format: swash::zeno::Format,
65 ) -> Option<cosmic_text::SwashImage> {
66 use swash::scale::{Render, Source, StrikeWith};
67 use swash::zeno::{Angle, Transform, Vector};
68 let font = fs.get_font(key.font_id, key.font_weight)?;
69 let swash_font = font.as_swash();
70 let axis = self
71 .axes
72 .entry(key.font_id)
73 .or_insert_with(|| {
74 let weight = fs.db().face(key.font_id).map_or(400, |face| face.weight.0);
75 crate::weights::WghtAxis::read(swash_font, weight)
76 })
77 .as_ref();
78 let size = f32::from_bits(key.font_size_bits);
79 let face_weight = f32::from(key.font_weight.0);
80 let bold = key.flags.contains(crate::weights::SYNTHETIC_BOLD);
81 let asked = if bold {
82 f32::from(Weight::BOLD.0)
83 } else {
84 face_weight
85 };
86 let at = axis.map(|axis| axis.coordinate(asked));
87 let embolden = bold
90 && match (axis, at) {
91 (Some(axis), Some(at)) => at <= axis.coordinate(face_weight),
92 _ => true,
93 };
94 let mut scaler = self
95 .ctx
96 .builder(swash_font)
97 .size(size)
98 .hint(!key.flags.contains(CacheKeyFlags::DISABLE_HINTING));
99 if let Some(at) = at {
100 scaler = scaler.normalized_coords(
101 swash_font
102 .variations()
103 .normalized_coords([(swash::Tag::from_be_bytes(*b"wght"), at)]),
104 );
105 }
106 let mut scaler = scaler.build();
107 let offset = if key.flags.contains(CacheKeyFlags::PIXEL_FONT) {
108 Vector::new(key.x_bin.as_float().round(), key.y_bin.as_float().round())
109 } else {
110 Vector::new(key.x_bin.as_float(), key.y_bin.as_float())
111 };
112 Render::new(&[
113 Source::ColorOutline(0),
114 Source::ColorBitmap(StrikeWith::BestFit),
115 Source::Outline,
116 ])
117 .format(format)
118 .offset(offset)
119 .embolden(if embolden {
120 crate::weights::embolden_strength(size)
121 } else {
122 0.0
123 })
124 .transform(
125 key.flags
126 .contains(CacheKeyFlags::FAKE_ITALIC)
127 .then(|| Transform::skew(Angle::from_degrees(14.0), Angle::from_degrees(0.0))),
128 )
129 .render(&mut scaler, key.glyph_id)
130 }
131}
132
133pub(crate) fn raster_glyph(
135 key: cosmic_text::CacheKey,
136 fs: &mut FontSystem,
137 raster: &mut Raster,
138 atlas: &mut crate::atlas::GlyphAtlas,
139) -> Option<crate::atlas::GlyphSlot> {
140 atlas.get_or_insert(key, || {
141 let format = if raster.subpixel {
142 swash::zeno::Format::Subpixel
143 } else {
144 swash::zeno::Format::Alpha
145 };
146 let image = raster.image(fs, key, format)?;
147 if image.placement.width == 0 || image.placement.height == 0 {
148 return None;
149 }
150 let (data, is_color, subpixel) = match image.content {
151 SwashContent::Mask => {
152 let mut rgba = Vec::with_capacity(image.data.len() * 4);
153 for &a in image.data.iter() {
154 rgba.extend_from_slice(&[255, 255, 255, a]);
155 }
156 (rgba, false, false)
157 }
158 SwashContent::Color => (image.data.to_vec(), true, false),
159 SwashContent::SubpixelMask => {
160 let mut rgba = image.data.to_vec();
164 for px in rgba.as_chunks_mut::<4>().0 {
165 px[3] = px[0].max(px[1]).max(px[2]);
166 }
167 (rgba, false, true)
168 }
169 };
170 Some(crate::atlas::RasterGlyph {
171 w: image.placement.width,
172 h: image.placement.height,
173 left: image.placement.left,
174 top: image.placement.top,
175 color: is_color,
176 subpixel,
177 data,
178 })
179 })
180}
181
182pub(crate) fn glyph_kind(slot: &crate::atlas::GlyphSlot) -> QuadKind {
184 if slot.color_glyph {
185 QuadKind::GlyphColor
186 } else if slot.subpixel {
187 QuadKind::GlyphSubpixel
188 } else {
189 QuadKind::GlyphMask
190 }
191}
192
193pub const DEFAULT_TEXT_CACHE_BYTES: usize = 64 << 20;
199
200const ENTRY_BASE_BYTES: usize = 4096;
210const ENTRY_GLYPH_BYTES: usize = 480;
211
212pub const LONG_LINE_BYTES: usize = 4096;
218const CHUNK_BYTES: usize = 1024;
223const LONG_SALT: u64 = 0x5f5f_6c6f_6e67_5f5f;
226const COPY_SALT: u64 = 0x5f5f_636f_7079_5f5f;
229
230const EVICT_TO_NUMERATOR: usize = 3;
234const EVICT_TO_DENOMINATOR: usize = 4;
235
236pub(crate) struct CachedText {
237 buffer: Buffer,
238 content: String,
241 wrap: Option<f32>,
243 intrinsic: Size,
245 max_lines: usize,
248 clamp_w: bool,
251 last_used: u64,
252 bytes: usize,
254 glyphs: Vec<GlyphTemplate>,
257 deco: Vec<DecoTemplate>,
261 span_deco: Vec<SpanDeco>,
264 glyphs_built_for: Option<(Option<u32>, u64)>,
266 claimed: u64,
271 claimed_wrap: Option<f32>,
272 breaks_anywhere: bool,
275 min_content: Option<f32>,
279}
280
281#[derive(Clone, Copy)]
282struct GlyphTemplate {
283 x: f32,
284 y: f32,
285 w: f32,
286 h: f32,
287 uv: [u32; 4],
288 kind: QuadKind,
289 color: Option<Color>,
294 byte: u32,
298}
299
300struct DecoTemplate {
302 x: f32,
303 y: f32,
304 w: f32,
305 h: f32,
306 color: Option<Color>,
309 under: bool,
311 style: UnderlineStyle,
314 radius: f32,
317}
318
319#[derive(Clone, Copy, Debug)]
328pub(crate) struct JoinBg {
329 pub quad: u32,
330 pub radius: f32,
331 pub outer: ClipId,
332 pub own: Option<(f32, f32)>,
333}
334
335#[derive(Clone, Copy, Default)]
338struct SpanDeco {
339 start: u32,
341 color: Option<Color>,
344 underline: bool,
345 underline_color: Option<Color>,
346 underline_style: UnderlineStyle,
347 strikethrough: bool,
348 bg: Option<Color>,
349 bg_radius: f32,
352}
353
354impl SpanDeco {
355 fn of_style(style: &TextStyle) -> Self {
356 Self {
357 start: 0,
358 color: None,
359 underline: style.underline,
360 underline_color: style.underline_color,
361 underline_style: style.underline_style,
362 strikethrough: style.strikethrough,
363 bg: None,
364 bg_radius: 0.0,
365 }
366 }
367
368 fn any(&self) -> bool {
369 self.underline || self.strikethrough || self.bg.is_some()
370 }
371}
372
373#[derive(Clone, Copy, Debug)]
376pub struct Span<'a> {
377 pub text: &'a str,
378 pub color: Option<Color>,
379 pub bold: bool,
380 pub italic: bool,
381 pub underline: bool,
383 pub underline_color: Option<Color>,
385 pub underline_style: UnderlineStyle,
387 pub strikethrough: bool,
389 pub bg: Option<Color>,
392 pub bg_radius: f32,
399 pub family: Option<FontFamily>,
406 pub size: Option<f32>,
412}
413
414impl<'a> Span<'a> {
415 pub fn new(text: &'a str) -> Self {
416 Self {
417 text,
418 color: None,
419 bold: false,
420 italic: false,
421 underline: false,
422 underline_color: None,
423 underline_style: UnderlineStyle::Solid,
424 strikethrough: false,
425 bg: None,
426 bg_radius: 0.0,
427 family: None,
428 size: None,
429 }
430 }
431
432 pub fn family(mut self, family: FontFamily) -> Self {
434 self.family = Some(family);
435 self
436 }
437
438 pub fn mono(self) -> Self {
446 self.family(FontFamily::Mono)
447 }
448
449 pub fn size(mut self, px: f32) -> Self {
452 self.size = (px.is_finite() && px > 0.0).then_some(px);
453 self
454 }
455
456 pub fn color(mut self, c: Color) -> Self {
457 self.color = Some(c);
458 self
459 }
460
461 pub fn bold(mut self) -> Self {
462 self.bold = true;
463 self
464 }
465
466 pub fn italic(mut self) -> Self {
467 self.italic = true;
468 self
469 }
470
471 pub fn underline(mut self) -> Self {
472 self.underline = true;
473 self
474 }
475
476 pub fn underline_color(mut self, c: Color) -> Self {
478 self.underline = true;
479 self.underline_color = Some(c);
480 self
481 }
482
483 pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
485 self.underline = true;
486 self.underline_style = s;
487 self
488 }
489
490 pub fn strikethrough(mut self) -> Self {
491 self.strikethrough = true;
492 self
493 }
494
495 pub fn bg(mut self, c: Color) -> Self {
496 self.bg = Some(c);
497 self
498 }
499
500 pub fn bg_radius(mut self, r: f32) -> Self {
503 self.bg_radius = r.max(0.0);
504 self
505 }
506
507 fn attrs_only(&self) -> SpanAttrs {
510 SpanAttrs {
511 color: self.color,
512 bold: self.bold,
513 italic: self.italic,
514 underline: self.underline,
515 underline_color: self.underline_color,
516 underline_style: self.underline_style,
517 strikethrough: self.strikethrough,
518 bg: self.bg,
519 bg_radius: self.bg_radius,
520 family: self.family,
521 size: self.size,
522 }
523 }
524
525 fn attrs<'r>(
526 &self,
527 family: cosmic_text::Family<'r>,
528 weights: crate::weights::Weights,
529 ) -> Attrs<'r> {
530 let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
534 if self.italic {
535 attrs = attrs.style(FontStyle::Italic);
536 }
537 if let Some(c) = self.color {
538 attrs = attrs.color(shaper_color(c));
539 }
540 attrs
541 }
542}
543
544fn shaper_color(c: Color) -> cosmic_text::Color {
546 cosmic_text::Color::rgba(
547 (c.r * 255.0) as u8,
548 (c.g * 255.0) as u8,
549 (c.b * 255.0) as u8,
550 (c.a * 255.0) as u8,
551 )
552}
553
554fn shaped_color(c: Color) -> Color {
558 let c = shaper_color(c);
559 Color::rgba8(c.r(), c.g(), c.b(), c.a())
560}
561
562#[derive(Clone, Copy)]
566struct SpanAttrs {
567 color: Option<Color>,
568 bold: bool,
569 italic: bool,
570 underline: bool,
571 underline_color: Option<Color>,
572 underline_style: UnderlineStyle,
573 strikethrough: bool,
574 bg: Option<Color>,
575 bg_radius: f32,
576 family: Option<FontFamily>,
577 size: Option<f32>,
578}
579
580impl SpanAttrs {
581 fn span<'a>(&self, text: &'a str) -> Span<'a> {
582 Span {
583 text,
584 color: self.color,
585 bold: self.bold,
586 italic: self.italic,
587 underline: self.underline,
588 underline_color: self.underline_color,
589 underline_style: self.underline_style,
590 strikethrough: self.strikethrough,
591 bg: self.bg,
592 bg_radius: self.bg_radius,
593 family: self.family,
594 size: self.size,
595 }
596 }
597}
598
599#[derive(Clone)]
602struct OwnedSpan {
603 start: usize,
604 end: usize,
605 attrs: SpanAttrs,
606}
607
608fn chunk_spans<'a>(
613 content: &'a str,
614 spans: &[OwnedSpan],
615 start: usize,
616 end: usize,
617) -> Vec<Span<'a>> {
618 let first = spans.partition_point(|s| s.end <= start);
619 spans[first..]
620 .iter()
621 .take_while(|s| s.start < end)
622 .filter_map(|s| {
623 let (a, b) = (s.start.max(start), s.end.min(end));
624 (a < b).then(|| s.attrs.span(&content[a..b]))
625 })
626 .collect()
627}
628
629struct FrameText {
630 cache_key: u64,
631 color: Color,
632}
633
634pub(crate) enum Entry {
642 Run(CachedText),
643 Long(LongLine),
644}
645
646impl Entry {
647 pub(crate) fn content(&self) -> &str {
648 match self {
649 Entry::Run(e) => &e.content,
650 Entry::Long(l) => &l.content,
651 }
652 }
653
654 fn last_used(&self) -> u64 {
655 match self {
656 Entry::Run(e) => e.last_used,
657 Entry::Long(l) => l.last_used,
658 }
659 }
660
661 fn touch(&mut self, frame_no: u64) {
662 match self {
663 Entry::Run(e) => e.last_used = frame_no,
664 Entry::Long(l) => l.last_used = frame_no,
665 }
666 }
667
668 fn bytes(&self) -> usize {
670 match self {
671 Entry::Run(e) => e.bytes,
672 Entry::Long(l) => l.bytes,
673 }
674 }
675
676 fn run(&self) -> Option<&CachedText> {
677 match self {
678 Entry::Run(e) => Some(e),
679 Entry::Long(_) => None,
680 }
681 }
682
683 fn run_mut(&mut self) -> Option<&mut CachedText> {
684 match self {
685 Entry::Run(e) => Some(e),
686 Entry::Long(_) => None,
687 }
688 }
689
690 fn long(&self) -> Option<&LongLine> {
691 match self {
692 Entry::Long(l) => Some(l),
693 Entry::Run(_) => None,
694 }
695 }
696
697 fn long_mut(&mut self) -> Option<&mut LongLine> {
698 match self {
699 Entry::Long(l) => Some(l),
700 Entry::Run(_) => None,
701 }
702 }
703}
704
705#[derive(Clone)]
709struct Chunk {
710 start: usize,
711 end: usize,
712 key: u64,
713 width: Option<f32>,
715 tab: Option<(f32, f32)>,
721 rows: Vec<RowStart>,
724}
725
726#[derive(Clone, Copy, Debug)]
730struct RowStart {
731 byte: u32,
732 x: f32,
733}
734
735#[derive(Clone)]
748pub(crate) struct LongLine {
749 content: String,
750 spans: Vec<OwnedSpan>,
754 style: TextStyle,
757 wrap: TextWrap,
763 wrap_w: Option<f32>,
766 starts: Vec<(u32, f32)>,
771 chunks: Vec<Chunk>,
772 prefix: Vec<f32>,
775 line_h: f32,
777 avg: f32,
780 last_used: u64,
781 claimed: u64,
784 laid_rows: u32,
791 asked_rows: u32,
792 bytes: usize,
793}
794
795impl LongLine {
796 fn chunk_at(&self, x: f32) -> usize {
798 match self.prefix[1..].partition_point(|&p| p <= x) {
799 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
800 i => i,
801 }
802 }
803
804 fn chunk_of_byte(&self, byte: usize) -> usize {
806 match self.chunks.partition_point(|c| c.end <= byte) {
807 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
808 i => i,
809 }
810 }
811
812 fn width(&self) -> f32 {
813 self.prefix.last().copied().unwrap_or(0.0)
814 }
815
816 fn rows(&self) -> u32 {
818 match self.starts.last() {
819 Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
820 _ => 1,
821 }
822 }
823
824 fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
827 let n = self.chunks.len();
828 if n == 0 || self.starts.len() != n + 1 {
829 return None;
830 }
831 let first = self.starts[1..].partition_point(|s| s.0 < ra);
832 let last = self.starts[..n].partition_point(|s| s.0 <= rb);
833 (first < last).then_some((first, last - 1))
834 }
835
836 fn reprefix(&mut self) {
843 let mut at = 0.0f32;
844 self.prefix.clear();
845 self.prefix.push(0.0);
846 for c in &self.chunks {
847 at = match (c.width, c.tab) {
848 (Some(_), Some((tab_x, every))) => next_tab_stop(at + tab_x, every),
849 (Some(w), None) => at + w,
850 (None, _) => at + (c.end - c.start) as f32 * self.avg,
851 };
852 self.prefix.push(at);
853 }
854 }
855
856 fn tab_shift(&self, i: usize) -> f32 {
859 let c = &self.chunks[i];
860 match (c.width, c.tab) {
861 (Some(w), Some(_)) => self.prefix[i + 1] - self.prefix[i] - w,
862 _ => 0.0,
863 }
864 }
865
866 fn end_shift(&self, i: usize, local: usize) -> f32 {
871 let c = &self.chunks[i];
872 match local >= c.end - c.start {
873 true => self.tab_shift(i),
874 false => 0.0,
875 }
876 }
877
878 fn placed_tab_hit(&self, i: usize, x: f32) -> Option<usize> {
885 let c = &self.chunks[i];
886 let (tab_x, _) = c.tab?;
887 c.width?;
888 if x < tab_x {
889 return None;
890 }
891 let drawn_end = self.prefix[i + 1] - self.prefix[i];
892 Some(match x < (tab_x + drawn_end) / 2.0 {
893 true => c.end - 1,
894 false => c.end,
895 })
896 }
897}
898
899fn next_tab_stop(x: f32, every: f32) -> f32 {
905 if every <= 0.0 {
906 return x;
907 }
908 (((x + 1.0 / 16.0) / every).floor() + 1.0) * every
909}
910
911fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
928 use unicode_segmentation::UnicodeSegmentation;
929 if content.len() < LONG_LINE_BYTES {
930 return vec![(0, content.len())];
931 }
932 let bytes = content.as_bytes();
933 let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
934 let mut start = 0usize;
935 while start < content.len() {
936 let mut window_end = (start + CHUNK_BYTES).min(content.len());
940 while !content.is_char_boundary(window_end) {
941 window_end -= 1;
942 }
943 let window = &content[start..window_end];
944 let mut from = (CHUNK_BYTES / 2).min(window.len());
946 while !window.is_char_boundary(from) {
947 from += 1;
948 }
949 let off_stops = start > 0 && bytes[start - 1] != b'\t';
953 let early_tab = match off_stops {
954 true => window[..from].find('\t'),
955 false => None,
956 };
957 let end = if let Some(t) = early_tab {
958 start + t + 1
959 } else if window_end == content.len() && !(off_stops && window[from..].contains('\t')) {
960 window_end
961 } else {
962 let (mut first_tab, mut last_tab, mut space) = (None, None, None);
966 for (i, c) in window[from..].char_indices() {
967 if c.is_whitespace() {
968 let end = from + i + c.len_utf8();
969 space = Some(end);
970 if c == '\t' {
971 first_tab = first_tab.or(Some(end));
972 last_tab = Some(end);
973 }
974 }
975 }
976 let tab = if off_stops { first_tab } else { last_tab };
977 match tab.or(space) {
978 Some(i) => start + i,
979 None => {
980 let mut cut = 0usize;
982 for (i, _) in window.grapheme_indices(true) {
983 if i == 0 {
984 continue;
985 }
986 cut = i;
987 }
988 if cut == 0 { window_end } else { start + cut }
989 }
990 }
991 };
992 out.push((start, end));
993 start = end;
994 }
995 out
996}
997
998pub(crate) const PLACE_ANCESTORS: usize = 4;
1003
1004pub(crate) struct Ancestry {
1008 pub(crate) keys: [Key; PLACE_ANCESTORS],
1009 pub(crate) depth: usize,
1010 pub(crate) none_at: Option<usize>,
1011}
1012
1013pub(crate) struct TextPlace {
1018 pub(crate) key: Key,
1019 ancestors: [Key; PLACE_ANCESTORS],
1021 depth: u8,
1022 none_at: u8,
1028 cache_key: u64,
1029 origin: Vec2,
1031 scope: Option<Key>,
1035 drawn: bool,
1043}
1044
1045impl TextPlace {
1046 fn answers_to(&self, key: Key) -> bool {
1047 if self.key == key {
1048 return true;
1049 }
1050 self.ancestors[..self.depth as usize]
1054 .iter()
1055 .position(|k| *k == key)
1056 .is_some_and(|d| d <= self.none_at as usize)
1057 }
1058}
1059
1060pub(crate) struct ScopeRun<'a> {
1066 pub place: &'a TextPlace,
1067 pub text: &'a Entry,
1069 pub base: usize,
1070}
1071
1072impl ScopeRun<'_> {
1073 pub(crate) fn span(&self) -> (usize, usize) {
1076 (self.base, self.base + self.text.content().len())
1077 }
1078}
1079
1080#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1093pub struct TextHit {
1094 pub byte: usize,
1095 pub line: u32,
1096}
1097
1098impl TextHit {
1099 pub fn to_value(self) -> Value {
1101 Value::map([
1102 ("byte", Value::Int(self.byte as i64)),
1103 ("line", Value::Int(self.line as i64)),
1104 ])
1105 }
1106}
1107
1108#[derive(Clone, Copy, Debug, Default, PartialEq)]
1113pub struct TextMetrics {
1114 pub width: f32,
1115 pub height: f32,
1116 pub lines: u32,
1118}
1119
1120impl TextMetrics {
1121 pub fn to_value(self) -> Value {
1123 Value::map([
1124 ("width", Value::float(self.width)),
1125 ("height", Value::float(self.height)),
1126 ("lines", Value::Int(self.lines as i64)),
1127 ])
1128 }
1129}
1130
1131pub struct TextSystem {
1139 raster: Raster,
1140 joins: Vec<JoinBg>,
1143 entries: FxHashMap<u64, Entry>,
1146 bytes: usize,
1149 budget: usize,
1150 frame: Kept<FrameText>,
1155 places: Kept<TextPlace>,
1159 scale: f32,
1160 frame_no: u64,
1161 owed: bool,
1164}
1165
1166pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1169 let mut out = cosmic_text::FontFeatures::new();
1170 for (tag, value) in f.iter() {
1171 out.set(cosmic_text::FeatureTag::new(tag), value);
1172 }
1173 out
1174}
1175
1176#[cfg(target_os = "macos")]
1187const DEFAULT_FAMILIES: [&[&str]; 3] = [
1188 &["Helvetica Neue", "Helvetica"],
1189 &["Times New Roman", "Times"],
1190 &["SF Mono", "Menlo", "Monaco"],
1191];
1192#[cfg(target_os = "windows")]
1193const DEFAULT_FAMILIES: [&[&str]; 3] = [
1194 &["Segoe UI", "Arial"],
1195 &["Times New Roman"],
1196 &["Cascadia Mono", "Consolas", "Courier New"],
1197];
1198#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1199const DEFAULT_FAMILIES: [&[&str]; 3] = [
1200 &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1201 &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1202 &[
1203 "DejaVu Sans Mono",
1204 "Noto Sans Mono",
1205 "Liberation Mono",
1206 "Ubuntu Mono",
1207 ],
1208];
1209
1210fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1213 list.iter().copied().find(|name| {
1214 db.faces()
1215 .any(|face| face.families.iter().any(|(f, _)| f == name))
1216 })
1217}
1218
1219pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1233 let system = system_fonts();
1234 let fonts = font_system_over(system.locale.clone(), system.db.clone(), &[]);
1235 (fonts, system)
1236}
1237
1238pub(crate) fn font_system_over(
1243 locale: String,
1244 db: cosmic_text::fontdb::Database,
1245 first: &[String],
1246) -> FontSystem {
1247 FontSystem::new_with_locale_and_db_and_fallback(locale, db, AppFallback::new(first))
1248}
1249
1250pub(crate) struct AppFallback {
1257 platform: cosmic_text::PlatformFallback,
1258 first: &'static [&'static str],
1259 common: &'static [&'static str],
1260 scripts: std::sync::Mutex<
1263 rustc_hash::FxHashMap<(unicode_script::Script, String), &'static [&'static str]>,
1264 >,
1265}
1266
1267fn keep(names: Vec<&'static str>) -> &'static [&'static str] {
1270 static LISTS: std::sync::Mutex<Vec<&'static [&'static str]>> =
1271 std::sync::Mutex::new(Vec::new());
1272 let mut lists = LISTS.lock().unwrap_or_else(|e| e.into_inner());
1273 if let Some(&kept) = lists.iter().find(|l| ***l == names[..]) {
1274 return kept;
1275 }
1276 let kept: &'static [&'static str] = Box::leak(names.into_boxed_slice());
1277 lists.push(kept);
1278 kept
1279}
1280
1281fn keep_name(name: &str) -> &'static str {
1282 static NAMES: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
1283 let mut names = NAMES.lock().unwrap_or_else(|e| e.into_inner());
1284 if let Some(&kept) = names.iter().find(|n| **n == name) {
1285 return kept;
1286 }
1287 let kept: &'static str = Box::leak(name.to_string().into_boxed_str());
1288 names.push(kept);
1289 kept
1290}
1291
1292impl AppFallback {
1293 pub(crate) fn new(first: &[String]) -> Self {
1294 use cosmic_text::Fallback;
1295 let platform = cosmic_text::PlatformFallback;
1296 let first = keep(first.iter().map(|n| keep_name(n)).collect());
1297 let common = keep(
1298 first
1299 .iter()
1300 .chain(platform.common_fallback())
1301 .copied()
1302 .collect(),
1303 );
1304 Self {
1305 platform,
1306 first,
1307 common,
1308 scripts: Default::default(),
1309 }
1310 }
1311}
1312
1313impl cosmic_text::Fallback for AppFallback {
1314 fn common_fallback(&self) -> &[&'static str] {
1315 self.common
1316 }
1317
1318 fn forbidden_fallback(&self) -> &[&'static str] {
1319 self.platform.forbidden_fallback()
1320 }
1321
1322 fn script_fallback(&self, script: unicode_script::Script, locale: &str) -> &[&'static str] {
1323 let theirs = self.platform.script_fallback(script, locale);
1324 if self.first.is_empty() {
1325 return theirs;
1326 }
1327 let mut scripts = self.scripts.lock().unwrap_or_else(|e| e.into_inner());
1328 scripts
1329 .entry((script, locale.to_string()))
1330 .or_insert_with(|| keep(self.first.iter().chain(theirs).copied().collect()))
1331 }
1332}
1333
1334pub(crate) struct SystemFonts {
1338 locale: String,
1339 db: cosmic_text::fontdb::Database,
1340 pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1342}
1343
1344static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1346
1347pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1351 let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1352 held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1353 .clone()
1354}
1355
1356pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1359 let fresh = std::sync::Arc::new(scan_system_fonts());
1360 *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1361 fresh
1362}
1363
1364fn scan_system_fonts() -> SystemFonts {
1365 let (locale, db) = FontSystem::new().into_locale_and_db();
1366 SystemFonts::from_db(locale, db)
1367}
1368
1369impl SystemFonts {
1370 pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1373 let all: Vec<_> = db.faces().map(|face| face.id).collect();
1374 keep_measurable(&mut db, all);
1375 pin_default_families(&mut db);
1376 let faces = db.faces().filter_map(face_file).collect();
1377 Self { locale, db, faces }
1378 }
1379
1380 #[cfg(test)]
1382 pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1383 &self.db
1384 }
1385
1386 #[cfg(test)]
1387 pub(crate) fn locale(&self) -> &str {
1388 &self.locale
1389 }
1390}
1391
1392pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1395 use cosmic_text::fontdb::Source;
1396 match &face.source {
1397 Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1398 Source::Binary(_) => None,
1399 }
1400}
1401
1402pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1406 let [sans, serif, mono] = DEFAULT_FAMILIES;
1407 if let Some(name) = first_installed(db, sans) {
1408 db.set_sans_serif_family(name);
1409 }
1410 if let Some(name) = first_installed(db, serif) {
1411 db.set_serif_family(name);
1412 }
1413 if let Some(name) = first_installed(db, mono) {
1414 db.set_monospace_family(name);
1415 }
1416}
1417
1418#[cfg(test)]
1422fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1423 let system = SystemFonts::from_db(locale, db);
1424 FontSystem::new_with_locale_and_db(system.locale, system.db)
1425}
1426
1427pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1438 use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1439 let Ok(font) = FontRef::from_index(data, index) else {
1440 return false;
1441 };
1442 font.head()
1443 .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1444 && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1445 && font.hmtx().is_ok()
1446}
1447
1448const FILES_PER_CHECKER: usize = 64;
1451
1452pub(crate) fn keep_measurable(
1464 db: &mut cosmic_text::fontdb::Database,
1465 ids: Vec<cosmic_text::fontdb::ID>,
1466) -> Vec<cosmic_text::fontdb::ID> {
1467 use cosmic_text::fontdb::{ID, Source};
1468 let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1470 let mut groups = Vec::new();
1471 for &id in &ids {
1472 match db.face(id).map(|face| &face.source) {
1473 Some(Source::File(path) | Source::SharedFile(path, _)) => {
1474 by_file.entry(path).or_default().push(id);
1475 }
1476 Some(Source::Binary(_)) => groups.push(vec![id]),
1477 None => {}
1478 }
1479 }
1480 groups.extend(by_file.into_values());
1481 let db_ref = &*db;
1482 let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1483 let mut out = Vec::new();
1484 for group in groups {
1485 let read = db_ref.with_face_data(group[0], |data, _| {
1486 group
1487 .iter()
1488 .filter(|&&id| {
1489 db_ref
1490 .face(id)
1491 .is_none_or(|face| !measurable(data, face.index))
1492 })
1493 .copied()
1494 .collect::<Vec<_>>()
1495 });
1496 out.extend(read.unwrap_or_else(|| group.clone()));
1497 }
1498 out
1499 };
1500 let checkers = std::thread::available_parallelism()
1501 .map_or(1, |n| n.get())
1502 .min(groups.len().div_ceil(FILES_PER_CHECKER));
1503 let unmeasurable = if checkers <= 1 {
1504 unmeasurable_in(&groups)
1505 } else {
1506 let per = groups.len().div_ceil(checkers);
1507 std::thread::scope(|scope| {
1508 let parts: Vec<_> = groups
1509 .chunks(per)
1510 .map(|part| {
1511 let spawned = std::thread::Builder::new()
1512 .name("kui-font-check".into())
1513 .spawn_scoped(scope, move || unmeasurable_in(part));
1514 (part, spawned.ok())
1515 })
1516 .collect();
1517 let mut out = Vec::new();
1518 for (part, checker) in parts {
1519 out.extend(match checker {
1520 Some(checker) => checker
1521 .join()
1522 .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1523 None => unmeasurable_in(part),
1525 });
1526 }
1527 out
1528 })
1529 };
1530 for &id in &unmeasurable {
1531 db.remove_face(id);
1532 }
1533 ids.into_iter()
1534 .filter(|id| !unmeasurable.contains(id))
1535 .collect()
1536}
1537
1538pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1542 let db = fs.db();
1543 [
1544 db.family_name(&cosmic_text::Family::SansSerif),
1545 db.family_name(&cosmic_text::Family::Serif),
1546 db.family_name(&cosmic_text::Family::Monospace),
1547 ]
1548}
1549
1550impl TextSystem {
1551 pub fn new() -> Self {
1552 Self {
1553 raster: Raster::new(),
1554 joins: Vec::new(),
1555 entries: FxHashMap::default(),
1556 bytes: 0,
1557 budget: DEFAULT_TEXT_CACHE_BYTES,
1558 frame: Kept::default(),
1559 places: Kept::default(),
1560 scale: 1.0,
1561 frame_no: 0,
1562 owed: false,
1563 }
1564 }
1565
1566 pub(crate) fn take_owed(&mut self) -> bool {
1569 std::mem::take(&mut self.owed)
1570 }
1571
1572 pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1575 std::mem::take(&mut self.joins)
1576 }
1577
1578 pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1580 &mut self.raster
1581 }
1582
1583 pub(crate) fn subpixel(&self) -> bool {
1584 self.raster.subpixel
1585 }
1586
1587 pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1590 let changed = self.raster.subpixel != on;
1591 self.raster.subpixel = on;
1592 changed
1593 }
1594
1595 pub fn budget(&self) -> usize {
1598 self.budget
1599 }
1600
1601 pub fn set_budget(&mut self, bytes: usize) {
1604 self.budget = bytes;
1605 }
1606
1607 pub fn bytes(&self) -> usize {
1609 self.bytes
1610 }
1611
1612 pub fn len(&self) -> usize {
1615 self.entries.len() - self.long_lines()
1616 }
1617
1618 pub fn long_lines(&self) -> usize {
1620 self.entries.values().filter(|e| e.long().is_some()).count()
1621 }
1622
1623 pub fn is_empty(&self) -> bool {
1624 self.len() == 0
1625 }
1626
1627 fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1634 if self.bytes <= self.budget {
1635 return;
1636 }
1637 let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1638 let drawn_last_frame = self.frame_no.saturating_sub(1);
1639 let mut order: Vec<(u64, u64)> = self
1644 .entries
1645 .iter()
1646 .filter(|(_, e)| e.last_used() < drawn_last_frame)
1647 .map(|(k, e)| (e.last_used(), *k))
1648 .collect();
1649 order.sort_unstable();
1650 for (_, key) in order {
1651 if self.bytes <= floor {
1652 break;
1653 }
1654 let freed = self.entries.remove(&key).map(|e| e.bytes());
1655 self.bytes -= freed.unwrap_or(0);
1656 }
1657 fs.shape_run_cache.trim(0);
1666 }
1667
1668 pub(crate) fn begin_frame(
1672 &mut self,
1673 fs: &mut FontSystem,
1674 scale: f32,
1675 keep_prev: bool,
1676 frame_no: u64,
1677 ) {
1678 self.joins.clear();
1679 if (scale - self.scale).abs() > f32::EPSILON {
1681 self.entries.clear();
1682 self.bytes = 0;
1683 }
1684 self.scale = scale;
1685 self.frame.begin(keep_prev);
1686 self.places.begin(true);
1689 self.frame_no = frame_no;
1691 if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1692 let mut freed = 0usize;
1693 self.entries.retain(|_, e| {
1694 let keep = e.last_used() >= cutoff;
1695 if !keep {
1696 freed += e.bytes();
1697 }
1698 keep
1699 });
1700 self.bytes -= freed;
1701 fs.shape_run_cache.trim(2);
1704 }
1705 self.evict_to_budget(fs);
1709 }
1710
1711 pub(crate) fn forget_shaped(&mut self) {
1714 self.entries.clear();
1715 self.bytes = 0;
1716 }
1717
1718 fn insert(&mut self, key: u64, entry: Entry) {
1720 self.bytes += entry.bytes();
1721 if let Some(old) = self.entries.insert(key, entry) {
1722 self.bytes -= old.bytes();
1723 }
1724 }
1725
1726 fn run(&self, key: u64) -> Option<&CachedText> {
1728 self.entries.get(&key)?.run()
1729 }
1730
1731 fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1732 self.entries.get_mut(&key)?.run_mut()
1733 }
1734
1735 fn long(&self, key: u64) -> Option<&LongLine> {
1737 self.entries.get(&key)?.long()
1738 }
1739
1740 fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1741 self.entries.get_mut(&key)?.long_mut()
1742 }
1743
1744 pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1745 let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1748 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1749 mix(&style.size.to_bits().to_le_bytes());
1750 mix(&style.line_height.to_bits().to_le_bytes());
1751 mix(&scale.to_bits().to_le_bytes());
1752 mix(&[style.wrap as u8, style.ellipsis as u8]);
1753 mix(&style.max_lines.to_le_bytes());
1754 let (tag, font) = match style.family {
1755 FontFamily::Sans => (0u8, 0u64),
1756 FontFamily::Serif => (1, 0),
1757 FontFamily::Mono => (2, 0),
1758 FontFamily::Custom(id) => (3, id.to_ffi()),
1759 };
1760 mix(&[tag]);
1761 mix(&font.to_le_bytes());
1762 for (t, v) in style.features.iter() {
1763 mix(t);
1764 mix(&v.to_le_bytes());
1765 }
1766 mix(&[style.bold as u8]);
1771 mix(&[
1772 style.underline as u8,
1773 style.strikethrough as u8,
1774 style.underline_style as u8,
1775 style.underline_color.is_some() as u8,
1776 ]);
1777 if let Some(c) = style.underline_color {
1778 mix(&c.to_hex().to_le_bytes());
1779 }
1780 h
1781 }
1782
1783 fn intern(
1787 &mut self,
1788 content: &str,
1789 style: &TextStyle,
1790 res: &Resources,
1791 fs: &mut FontSystem,
1792 ) -> u64 {
1793 let key = Self::style_key(content, style, self.scale);
1794 self.intern_keyed(key, content, style, res, fs)
1795 }
1796
1797 fn intern_keyed(
1799 &mut self,
1800 key: u64,
1801 content: &str,
1802 style: &TextStyle,
1803 res: &Resources,
1804 fs: &mut FontSystem,
1805 ) -> u64 {
1806 let frame_no = self.frame_no;
1807 let scale = self.scale;
1808 if !self.entries.contains_key(&key) {
1809 let mut buffer = new_buffer(fs, style, scale);
1810 buffer.set_text(
1814 content,
1815 &res.weights_of(style.family).apply(
1816 Attrs::new()
1817 .family(res.family_of(style.family))
1818 .font_features(cosmic_features(&style.features)),
1819 style.bold,
1820 ),
1821 Shaping::Advanced,
1822 None,
1823 );
1824 let entry = CachedText::new(
1825 buffer,
1826 content.to_string(),
1827 style,
1828 vec![SpanDeco::of_style(style)],
1829 fs,
1830 frame_no,
1831 );
1832 self.insert(key, Entry::Run(entry));
1833 }
1834 self.entries
1835 .get_mut(&key)
1836 .expect("just inserted")
1837 .touch(frame_no);
1838 key
1839 }
1840
1841 pub(crate) fn content(&self, id: TextId) -> &str {
1843 let key = self.frame[id.0 as usize].cache_key;
1844 self.entries.get(&key).map_or("", |e| e.content())
1845 }
1846
1847 pub(crate) fn access_runs(
1853 &self,
1854 id: TextId,
1855 src: crate::access::RunSource,
1856 run_no: &mut usize,
1857 out: &mut Vec<crate::access::AccessRun>,
1858 ) {
1859 use crate::access::{RowGlyphs, push_row_runs};
1860 let Some(ft) = self.frame.get(id.0 as usize) else {
1861 return;
1862 };
1863 let line = match self.entries.get(&ft.cache_key) {
1864 Some(Entry::Run(e)) => {
1865 crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1866 return;
1867 }
1868 Some(Entry::Long(line)) => line,
1869 None => return,
1870 };
1871 let whole = [RowStart { byte: 0, x: 0.0 }];
1872 for (i, c) in line.chunks.iter().enumerate() {
1873 let Some(run) = c
1874 .width
1875 .and_then(|_| self.run(c.key))
1876 .and_then(|e| e.buffer.layout_runs().next())
1877 else {
1878 continue;
1879 };
1880 let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1883 (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1884 (&c.rows[..], row0, head_x)
1885 }
1886 (Some(_), _) => continue,
1887 (None, _) => (&whole[..], 0, line.prefix[i]),
1888 };
1889 for (r, rs) in rows.iter().enumerate() {
1890 let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1891 let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1892 let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1893 if a == b {
1894 continue;
1895 }
1896 let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1897 push_row_runs(
1898 &RowGlyphs {
1899 glyphs: &run.glyphs[a..b],
1900 text: run.text,
1901 line: 0,
1902 top: (row0 as usize + r) as f32 * line.line_h,
1903 height: line.line_h,
1904 rtl: run.rtl,
1905 dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1906 base: c.start,
1907 newline: last && src.newline_after_last,
1908 },
1909 &src,
1910 run_no,
1911 out,
1912 );
1913 }
1914 }
1915 }
1916
1917 pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1923 match self.frame.prev().get(id.0 as usize) {
1924 Some(t) => (t.cache_key, t.color),
1925 None => (0, Color::TRANSPARENT),
1926 }
1927 }
1928
1929 pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1935 let frame_no = self.frame_no;
1936 let e = self.entries.get_mut(&cache_key)?;
1937 e.touch(frame_no);
1938 let cache_key = if e.long().is_some() {
1939 self.claim_long(cache_key)
1940 } else {
1941 cache_key
1942 };
1943 self.frame.push(FrameText { cache_key, color });
1944 Some(TextId((self.frame.len() - 1) as u32))
1945 }
1946
1947 pub fn add(
1949 &mut self,
1950 content: &str,
1951 style: &TextStyle,
1952 res: &Resources,
1953 fs: &mut FontSystem,
1954 ) -> TextId {
1955 let key = self.intern_any(content, style, res, fs);
1958 let key = if could_be_long(content.len(), style) {
1959 self.claim_long(key)
1960 } else {
1961 key
1962 };
1963 self.frame.push(FrameText {
1964 cache_key: key,
1965 color: style.color_or_default(),
1966 });
1967 TextId((self.frame.len() - 1) as u32)
1968 }
1969
1970 #[inline(never)]
1979 fn claim_long(&mut self, key: u64) -> u64 {
1980 let frame_no = self.frame_no;
1981 let mut at = key;
1982 let mut n = 0u64;
1983 loop {
1984 match self.entries.get_mut(&at) {
1985 Some(Entry::Long(l)) if l.claimed != frame_no => {
1986 l.claimed = frame_no;
1987 l.last_used = frame_no;
1988 return at;
1989 }
1990 Some(Entry::Run(_)) if n == 0 => return key,
1992 Some(_) => {}
1993 None => {
1994 let Some(line) = self.long(key) else {
1995 return key;
1996 };
1997 let mut copy = line.clone();
1998 copy.claimed = frame_no;
1999 copy.last_used = frame_no;
2000 self.insert(at, Entry::Long(copy));
2001 return at;
2002 }
2003 }
2004 n += 1;
2005 at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
2006 }
2007 }
2008
2009 fn intern_any(
2015 &mut self,
2016 content: &str,
2017 style: &TextStyle,
2018 res: &Resources,
2019 fs: &mut FontSystem,
2020 ) -> u64 {
2021 if !could_be_long(content.len(), style) {
2022 return self.intern(content, style, res, fs);
2023 }
2024 let key = Self::style_key(content, style, self.scale);
2025 let long_key = key ^ LONG_SALT;
2026 let frame_no = self.frame_no;
2027 if let Some(e) = self.entries.get_mut(&long_key) {
2028 e.touch(frame_no);
2029 return long_key;
2030 }
2031 if let Some(e) = self.entries.get_mut(&key) {
2032 e.touch(frame_no);
2033 return key;
2034 }
2035 if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
2036 self.intern_keyed(key, content, style, res, fs)
2037 } else {
2038 self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
2039 }
2040 }
2041
2042 fn intern_rich_any(
2047 &mut self,
2048 spans: &[Span<'_>],
2049 base: &TextStyle,
2050 res: &Resources,
2051 fs: &mut FontSystem,
2052 ) -> u64 {
2053 let len: usize = spans.iter().map(|s| s.text.len()).sum();
2054 if !could_be_long(len, base) {
2055 return self.intern_rich(spans, base, res, fs);
2056 }
2057 let key = Self::rich_key(spans, base, self.scale);
2058 let long_key = key ^ LONG_SALT;
2059 let frame_no = self.frame_no;
2060 if let Some(e) = self.entries.get_mut(&long_key) {
2061 e.touch(frame_no);
2062 return long_key;
2063 }
2064 if let Some(e) = self.entries.get_mut(&key) {
2065 e.touch(frame_no);
2066 return key;
2067 }
2068 if spans
2071 .iter()
2072 .any(|s| has_line_break(s.text) || s.size.is_some())
2073 || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
2074 {
2075 return self.intern_rich_keyed(key, spans, base, res, fs);
2076 }
2077 let mut content = String::with_capacity(len);
2078 let mut owned = Vec::with_capacity(spans.len());
2079 for s in spans {
2080 let start = content.len();
2081 content.push_str(s.text);
2082 owned.push(OwnedSpan {
2083 start,
2084 end: content.len(),
2085 attrs: s.attrs_only(),
2086 });
2087 }
2088 self.build_long(long_key, content, owned, base, res, fs)
2089 }
2090
2091 fn build_long(
2096 &mut self,
2097 key: u64,
2098 content: String,
2099 spans: Vec<OwnedSpan>,
2100 style: &TextStyle,
2101 res: &Resources,
2102 fs: &mut FontSystem,
2103 ) -> u64 {
2104 let frame_no = self.frame_no;
2105 let wrap = style.wrap;
2106 let style = &TextStyle {
2107 wrap: TextWrap::None,
2108 ..*style
2109 };
2110 let scale = self.scale;
2111 let chunks: Vec<Chunk> = chunk_ranges(&content)
2112 .into_iter()
2113 .map(|(start, end)| Chunk {
2114 start,
2115 end,
2116 key: if spans.is_empty() {
2117 Self::style_key(&content[start..end], style, scale)
2118 } else {
2119 Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
2120 },
2121 width: None,
2122 tab: None,
2123 rows: Vec::new(),
2124 })
2125 .collect();
2126 let (w0, line_h) = match chunks.first() {
2129 Some(c) => {
2130 let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
2131 let e = self.run(k).expect("just interned");
2132 (e.intrinsic.w, e.buffer.metrics().line_height)
2133 }
2134 None => (0.0, style.line_height * self.scale),
2135 };
2136 let avg = chunks
2137 .first()
2138 .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
2139 let mut line = LongLine {
2140 content,
2141 spans,
2142 style: *style,
2143 wrap,
2144 wrap_w: None,
2145 starts: Vec::new(),
2146 chunks,
2147 prefix: Vec::new(),
2148 line_h,
2149 avg,
2150 last_used: frame_no,
2151 claimed: u64::MAX,
2152 laid_rows: 1,
2153 asked_rows: u32::MAX,
2154 bytes: 0,
2155 };
2156 if let Some(c) = line.chunks.first_mut() {
2157 c.width = Some(w0);
2158 }
2159 line.reprefix();
2160 line.bytes = ENTRY_BASE_BYTES
2161 + line.content.len()
2162 + line.chunks.len() * 48
2163 + line.spans.len() * std::mem::size_of::<OwnedSpan>();
2164 self.insert(key, Entry::Long(line));
2168 key
2169 }
2170
2171 #[allow(clippy::too_many_arguments)]
2175 fn shape_chunk(
2176 &mut self,
2177 content: &str,
2178 spans: &[OwnedSpan],
2179 start: usize,
2180 end: usize,
2181 style: &TextStyle,
2182 res: &Resources,
2183 fs: &mut FontSystem,
2184 ) -> u64 {
2185 if spans.is_empty() {
2186 self.intern(&content[start..end], style, res, fs)
2187 } else {
2188 let spans = chunk_spans(content, spans, start, end);
2189 self.intern_rich(&spans, style, res, fs)
2190 }
2191 }
2192
2193 fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
2197 let (chunk_key, known) = {
2198 let c = &self.long(key).expect("a long line").chunks[i];
2199 (c.key, c.width)
2200 };
2201 let frame_no = self.frame_no;
2202 if known.is_some()
2203 && let Some(e) = self.run_mut(chunk_key)
2204 {
2205 e.last_used = frame_no;
2206 return false;
2207 }
2208 let (text, spans, style, off_stops) = {
2211 let line = self.long(key).expect("a long line");
2212 let c = &line.chunks[i];
2213 let first = line.spans.partition_point(|s| s.end <= c.start);
2214 let spans: Vec<OwnedSpan> = line.spans[first..]
2215 .iter()
2216 .take_while(|s| s.start < c.end)
2217 .map(|s| OwnedSpan {
2218 start: s.start.max(c.start) - c.start,
2219 end: s.end.min(c.end) - c.start,
2220 attrs: s.attrs,
2221 })
2222 .collect();
2223 let bytes = line.content.as_bytes();
2224 let off_stops = c.start > 0 && bytes[c.start - 1] != b'\t';
2225 (
2226 line.content[c.start..c.end].to_string(),
2227 spans,
2228 line.style,
2229 off_stops,
2230 )
2231 };
2232 let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
2233 let w = self.run(k).expect("just interned").intrinsic.w;
2234 let tab = match off_stops && text.ends_with('\t') {
2235 true => self.chunk_tab(k, &text, &spans, &style, res, fs),
2236 false => None,
2237 };
2238 let line = self.long_mut(key).expect("just read");
2239 line.chunks[i].key = k;
2240 line.chunks[i].tab = tab;
2241 if line.chunks[i].width != Some(w) {
2242 line.chunks[i].width = Some(w);
2243 line.reprefix();
2244 }
2245 true
2246 }
2247
2248 #[inline(never)]
2254 #[allow(clippy::too_many_arguments)]
2255 fn chunk_tab(
2256 &mut self,
2257 chunk: u64,
2258 text: &str,
2259 spans: &[OwnedSpan],
2260 style: &TextStyle,
2261 res: &Resources,
2262 fs: &mut FontSystem,
2263 ) -> Option<(f32, f32)> {
2264 let at = text.len() - 1;
2265 let tab_x = {
2266 let run = self.run(chunk)?.buffer.layout_runs().next()?;
2267 if run.rtl {
2268 return None;
2269 }
2270 run.glyphs.iter().rev().find(|g| g.start == at)?.x
2271 };
2272 let alone = self.shape_chunk(text, spans, at, text.len(), style, res, fs);
2273 let every = self.run(alone)?.intrinsic.w;
2274 Some((tab_x, every))
2275 }
2276
2277 fn relayout_long(&mut self, key: u64, w: Option<f32>) {
2285 let Some(w) = w else {
2286 let line = self.long_mut(key).expect("a long line");
2287 line.wrap_w = None;
2288 line.starts.clear();
2289 for c in &mut line.chunks {
2290 c.rows.clear();
2291 }
2292 return;
2293 };
2294 let w = w.max(1.0);
2295 let line = self.long(key).expect("a long line");
2298 let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2299 starts.push((0, 0.0));
2300 let (mut row, mut x) = (0u32, 0.0f32);
2301 let rows: Vec<Vec<RowStart>> = line
2302 .chunks
2303 .iter()
2304 .enumerate()
2305 .map(|(i, c)| {
2306 let shaped = c.width.and_then(|_| self.run(c.key));
2307 let rows = match shaped {
2308 Some(e) => {
2309 let text = &line.content[c.start..c.end];
2310 let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2311 row += rows.len() as u32 - 1;
2312 x = end + line.tab_shift(i);
2316 rows
2317 }
2318 None => {
2319 let est = x + c
2320 .width
2321 .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2322 let added = (est / w).floor();
2323 row += added as u32;
2324 x = est - added * w;
2325 Vec::new()
2326 }
2327 };
2328 starts.push((row, x));
2329 rows
2330 })
2331 .collect();
2332 let line = self.long_mut(key).expect("a long line");
2333 line.wrap_w = Some(w);
2334 line.starts = starts;
2335 for (c, rows) in line.chunks.iter_mut().zip(rows) {
2336 c.rows = rows;
2337 }
2338 }
2339
2340 fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2344 let (wrap, width, line_h, cur) = {
2345 let l = self.long(key).expect("a long line");
2346 (l.wrap, l.width(), l.line_h, l.wrap_w)
2347 };
2348 let target = match max_w {
2349 Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2350 _ => None,
2351 };
2352 let differs = match (cur, target) {
2353 (None, None) => false,
2354 (Some(a), Some(b)) => (a - b).abs() > 0.5,
2355 _ => true,
2356 };
2357 if differs {
2358 self.relayout_long(key, target);
2359 }
2360 match target {
2361 Some(w) => {
2362 let rows = self.long(key).expect("a long line").rows();
2363 (Size::new(w, rows as f32 * line_h), rows)
2364 }
2365 None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2366 }
2367 }
2368
2369 pub fn measure(
2375 &mut self,
2376 content: &str,
2377 style: &TextStyle,
2378 res: &Resources,
2379 fs: &mut FontSystem,
2380 max_w: Option<f32>,
2381 ) -> TextMetrics {
2382 let key = self.intern_any(content, style, res, fs);
2383 self.measure_any(key, fs, max_w)
2384 }
2385
2386 fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2388 if self.long(key).is_some() {
2389 let scale = self.scale;
2390 let (size, lines) = match max_w {
2393 Some(m) => {
2394 let held = self.long(key).expect("checked").wrap_w;
2397 let measured = self.long_size(key, Some(m * scale));
2398 if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2399 self.relayout_long(key, held);
2400 }
2401 measured
2402 }
2403 None => {
2404 let line = self.long(key).expect("checked");
2405 (Size::new(line.width(), line.line_h), 1)
2406 }
2407 };
2408 return TextMetrics {
2409 width: size.w / scale,
2410 height: size.h / scale,
2411 lines,
2412 };
2413 }
2414 self.measure_key(key, fs, max_w)
2415 }
2416
2417 pub fn measure_rich(
2419 &mut self,
2420 spans: &[Span<'_>],
2421 base: &TextStyle,
2422 res: &Resources,
2423 fs: &mut FontSystem,
2424 max_w: Option<f32>,
2425 ) -> TextMetrics {
2426 let key = self.intern_rich_any(spans, base, res, fs);
2427 self.measure_any(key, fs, max_w)
2428 }
2429
2430 fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2431 let scale = self.scale;
2432 let entry = self.run_mut(key).expect("just interned");
2433 let target = wrap_target(entry, max_w, scale);
2434 let key = if entry.claimed != u64::MAX && wrap_differs(entry.wrap, target) {
2441 self.wrap_copy(key, target, fs)
2442 } else {
2443 key
2444 };
2445 let entry = self.run_mut(key).expect("just interned");
2446 wrap_entry(entry, fs, target);
2447 let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2448 if entry.clamp_w
2449 && let Some(t) = target
2450 {
2451 m.w = m.w.min(t);
2452 }
2453 TextMetrics {
2454 width: m.w / scale,
2455 height: m.h / scale,
2456 lines,
2457 }
2458 }
2459
2460 pub fn add_rich(
2464 &mut self,
2465 spans: &[Span<'_>],
2466 base: &TextStyle,
2467 res: &Resources,
2468 fs: &mut FontSystem,
2469 ) -> TextId {
2470 let key = self.intern_rich_any(spans, base, res, fs);
2471 let len = spans.iter().map(|s| s.text.len()).sum();
2472 let key = if could_be_long(len, base) {
2473 self.claim_long(key)
2474 } else {
2475 key
2476 };
2477 self.frame.push(FrameText {
2478 cache_key: key,
2479 color: base.color_or_default(),
2480 });
2481 TextId((self.frame.len() - 1) as u32)
2482 }
2483
2484 fn intern_rich(
2485 &mut self,
2486 spans: &[Span<'_>],
2487 base: &TextStyle,
2488 res: &Resources,
2489 fs: &mut FontSystem,
2490 ) -> u64 {
2491 let key = Self::rich_key(spans, base, self.scale);
2492 self.intern_rich_keyed(key, spans, base, res, fs)
2493 }
2494
2495 fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2499 let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2500 for s in spans {
2501 key = crate::key::mix_content(key, s.text.as_bytes());
2502 let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2503 mix(&[
2504 s.bold as u8,
2505 s.italic as u8,
2506 s.color.is_some() as u8,
2507 s.underline as u8,
2508 s.strikethrough as u8,
2509 s.bg.is_some() as u8,
2510 s.underline_style as u8,
2511 s.underline_color.is_some() as u8,
2512 ]);
2513 mix(&s.bg_radius.to_bits().to_le_bytes());
2514 let (tag, font) = match s.family {
2517 None => (0u8, 0u64),
2518 Some(FontFamily::Sans) => (1, 0),
2519 Some(FontFamily::Serif) => (2, 0),
2520 Some(FontFamily::Mono) => (3, 0),
2521 Some(FontFamily::Custom(id)) => (4, id.to_ffi()),
2522 };
2523 mix(&[tag]);
2524 mix(&font.to_le_bytes());
2525 mix(&s.size.map_or(0, f32::to_bits).to_le_bytes());
2526 for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2527 mix(&c.r.to_bits().to_le_bytes());
2528 mix(&c.g.to_bits().to_le_bytes());
2529 mix(&c.b.to_bits().to_le_bytes());
2530 mix(&c.a.to_bits().to_le_bytes());
2531 }
2532 }
2533 key
2534 }
2535
2536 fn intern_rich_keyed(
2538 &mut self,
2539 key: u64,
2540 spans: &[Span<'_>],
2541 base: &TextStyle,
2542 res: &Resources,
2543 fs: &mut FontSystem,
2544 ) -> u64 {
2545 let frame_no = self.frame_no;
2546 let scale = self.scale;
2547 if !self.entries.contains_key(&key) {
2548 let mut buffer = new_buffer(fs, base, scale);
2549 let family = res.family_of(base.family);
2550 let weights = res.weights_of(base.family);
2551 let features = cosmic_features(&base.features);
2552 let sized = spans.iter().any(|s| s.size.is_some());
2557 let metrics_of = |size: Option<f32>| {
2558 let size = size.unwrap_or(base.size);
2559 let ratio = if base.size > 0.0 {
2560 base.line_height / base.size
2561 } else {
2562 1.0
2563 };
2564 shaper_metrics(size * scale, size * ratio * scale)
2565 };
2566 buffer.set_rich_text(
2570 spans.iter().enumerate().map(|(i, s)| {
2571 let (family, weights) = match s.family {
2572 Some(f) => (res.family_of(f), res.weights_of(f)),
2573 None => (family, weights),
2574 };
2575 let mut attrs = s
2576 .attrs(family, weights)
2577 .font_features(features.clone())
2578 .metadata(i);
2579 if base.bold && !s.bold {
2582 attrs = weights.apply(attrs, true);
2583 }
2584 if sized {
2585 attrs = attrs.metrics(metrics_of(s.size));
2586 }
2587 (s.text, attrs)
2588 }),
2589 &{
2590 let attrs = weights.apply(
2591 Attrs::new().family(family).font_features(features.clone()),
2592 base.bold,
2593 );
2594 if sized {
2595 attrs.metrics(metrics_of(None))
2596 } else {
2597 attrs
2598 }
2599 },
2600 Shaping::Advanced,
2601 None,
2602 );
2603 let content = spans.iter().map(|s| s.text).collect::<String>();
2604 let mut start = 0u32;
2605 let decos = spans
2606 .iter()
2607 .map(|s| SpanDeco {
2608 start: {
2609 let at = start;
2610 start += s.text.len() as u32;
2611 at
2612 },
2613 color: s.color.map(shaped_color),
2614 underline: s.underline || base.underline,
2615 underline_color: s.underline_color.or(base.underline_color),
2617 underline_style: if s.underline {
2618 s.underline_style
2619 } else {
2620 base.underline_style
2621 },
2622 strikethrough: s.strikethrough || base.strikethrough,
2623 bg: s.bg,
2624 bg_radius: s.bg_radius,
2625 })
2626 .collect();
2627 let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2628 self.insert(key, Entry::Run(entry));
2629 }
2630 self.entries
2631 .get_mut(&key)
2632 .expect("just inserted")
2633 .touch(frame_no);
2634 key
2635 }
2636
2637 fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2640 let key = self.frame[id.0 as usize].cache_key;
2641 self.run_mut(key).expect("frame text missing from cache")
2642 }
2643
2644 fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2649 let (scale, frame_no) = (self.scale, self.frame_no);
2650 let entry = self.entry_mut(id);
2651 let target = wrap_target(entry, Some(max_w_logical), scale);
2652 if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2653 self.own_wrap(id, target, fs);
2654 return;
2655 }
2656 entry.claimed = frame_no;
2657 entry.claimed_wrap = target;
2658 wrap_entry(entry, fs, target);
2659 }
2660
2661 #[inline(never)]
2669 fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2670 let frame_no = self.frame_no;
2671 let key = self.frame[id.0 as usize].cache_key;
2672 let copy = self.wrap_copy(key, target, fs);
2673 self.frame[id.0 as usize].cache_key = copy;
2674 let entry = self.run_mut(copy).expect("just made");
2675 entry.claimed = frame_no;
2676 entry.claimed_wrap = target;
2677 }
2678
2679 #[inline(never)]
2685 fn wrap_copy(&mut self, key: u64, target: Option<f32>, fs: &mut FontSystem) -> u64 {
2686 let frame_no = self.frame_no;
2687 let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2689 let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2690 if self.run(copy).is_none() {
2691 let fork = self.run(key).expect("a run").fork(frame_no);
2692 self.insert(copy, Entry::Run(fork));
2693 }
2694 let entry = self.run_mut(copy).expect("just made");
2695 entry.last_used = frame_no;
2696 wrap_entry(entry, fs, target);
2697 copy
2698 }
2699
2700 #[allow(clippy::too_many_arguments)]
2705 pub(crate) fn emit(
2706 &mut self,
2707 id: TextId,
2708 origin: Vec2,
2709 node: Size,
2710 clip: Clip,
2711 clip_id: ClipId,
2712 clips: &mut Vec<Clip>,
2713 res: &Resources,
2714 fs: &mut FontSystem,
2715 atlas: &mut GlyphAtlas,
2716 out: &mut Vec<Quad>,
2717 sel: Option<((usize, usize), Color)>,
2718 ) {
2719 let from = self.joins.len();
2720 self.emit_text(
2721 id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2722 );
2723 if self.joins.len() == from {
2724 return;
2725 }
2726 let key = self.frame[id.0 as usize].cache_key;
2729 let clamps = match self.entries.get(&key) {
2730 Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2731 Some(Entry::Run(e)) => e.clamp_w,
2732 None => false,
2733 };
2734 let own = clamps.then(|| {
2735 let ox = crate::geom::snap_px(origin.x * self.scale);
2736 (ox, ox + (node.w * self.scale).ceil())
2737 });
2738 for j in &mut self.joins[from..] {
2739 j.outer = clip_id;
2740 j.own = own;
2741 }
2742 }
2743
2744 #[allow(clippy::too_many_arguments)]
2750 #[inline(never)]
2751 fn emit_long(
2752 &mut self,
2753 key: u64,
2754 node: Size,
2755 ox: f32,
2756 oy: f32,
2757 nudge: Vec2,
2758 color: Color,
2759 clip: Clip,
2760 clip_id: ClipId,
2761 clips: &mut Vec<Clip>,
2762 res: &Resources,
2763 fs: &mut FontSystem,
2764 atlas: &mut GlyphAtlas,
2765 out: &mut Vec<Quad>,
2766 sel: Option<((usize, usize), Color)>,
2767 ) {
2768 let scale = self.scale;
2769 let line = self.long(key).expect("a long line");
2770 let (clip, clip_id) = if line.wrap == TextWrap::None {
2776 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2777 let clip = clip.intersect(own, crate::display::SQUARE);
2778 let vis = clip.visible();
2779 if vis.w <= 0.0 || vis.h <= 0.0 {
2780 return;
2781 }
2782 (clip, crate::display::intern_clip(clips, clip))
2783 } else {
2784 (clip, clip_id)
2785 };
2786 let vis = clip.visible();
2788 if let Some(((from, to), tint)) = sel {
2789 let rects = self.long_highlight(line, from, to);
2790 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2791 }
2792 if let Some(w) = line.wrap_w {
2793 self.emit_long_rows(
2794 key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2795 );
2796 return;
2797 }
2798 let (first, last) = {
2799 if line.chunks.is_empty() {
2800 return;
2801 }
2802 let a = line.chunk_at(vis.x - ox).saturating_sub(1);
2803 let b = (line.chunk_at(vis.x + vis.w - ox) + 1).min(line.chunks.len() - 1);
2804 (a, b)
2805 };
2806 for i in first..=last {
2807 self.ensure_chunk(key, i, res, fs);
2808 }
2809 let frame_no = self.frame_no;
2810 self.long_mut(key).expect("checked").last_used = frame_no;
2811 for i in first..=last {
2812 let (chunk_key, x) = {
2813 let line = self.long(key).expect("checked");
2814 (line.chunks[i].key, line.prefix[i])
2815 };
2816 let raster = &mut self.raster;
2817 let entry = self
2818 .entries
2819 .get_mut(&chunk_key)
2820 .and_then(Entry::run_mut)
2821 .expect("just ensured");
2822 emit_entry(
2823 entry,
2824 ox + x,
2825 oy,
2826 nudge,
2827 color,
2828 clip,
2829 clip_id,
2830 raster,
2831 fs,
2832 atlas,
2833 out,
2834 &mut self.joins,
2835 );
2836 }
2837 }
2838
2839 #[allow(clippy::too_many_arguments)]
2841 fn emit_text(
2842 &mut self,
2843 id: TextId,
2844 origin: Vec2,
2845 node: Size,
2846 clip: Clip,
2847 clip_id: ClipId,
2848 clips: &mut Vec<Clip>,
2849 res: &Resources,
2850 fs: &mut FontSystem,
2851 atlas: &mut GlyphAtlas,
2852 out: &mut Vec<Quad>,
2853 sel: Option<((usize, usize), Color)>,
2858 ) {
2859 let FrameText {
2860 cache_key: key,
2861 color,
2862 } = self.frame[id.0 as usize];
2863 let scale = self.scale;
2864 let ox = crate::geom::snap_px(origin.x * scale);
2865 let oy = crate::geom::snap_px(origin.y * scale);
2866 let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2869 if self.long(key).is_some() {
2872 self.emit_long(
2873 key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2874 );
2875 return;
2876 }
2877 self.ensure_wrap(id, node.w, fs);
2878 let raster = &mut self.raster;
2879 let entry = self
2880 .entries
2881 .get_mut(&key)
2882 .and_then(Entry::run_mut)
2883 .expect("frame text missing from cache");
2884
2885 let (clip, clip_id) = if entry.clamp_w {
2888 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2889 let clip = clip.intersect(own, crate::display::SQUARE);
2890 (clip, crate::display::intern_clip(clips, clip))
2891 } else {
2892 (clip, clip_id)
2893 };
2894 if let Some(((from, to), tint)) = sel {
2895 let rects = Self::run_highlight(entry, from, to);
2896 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2897 }
2898 emit_entry(
2899 entry,
2900 ox,
2901 oy,
2902 nudge,
2903 color,
2904 clip,
2905 clip_id,
2906 raster,
2907 fs,
2908 atlas,
2909 out,
2910 &mut self.joins,
2911 );
2912 }
2913}
2914
2915fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2929 Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2930}
2931
2932fn push_highlight(
2935 rects: &[Rect],
2936 ox: f32,
2937 oy: f32,
2938 nudge: Vec2,
2939 tint: Color,
2940 clip: ClipId,
2941 out: &mut Vec<Quad>,
2942) {
2943 for r in rects {
2944 out.push(Quad {
2945 rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2946 color: tint,
2947 border_color: Color::TRANSPARENT,
2948 radius: [0.0; 4],
2949 border_w: 0.0,
2950 blur: 0.0,
2951 kind: QuadKind::Solid,
2952 uv: [0; 4],
2953 clip,
2954 });
2955 }
2956}
2957
2958impl TextSystem {
2959 #[allow(clippy::too_many_arguments)]
2963 fn emit_long_rows(
2964 &mut self,
2965 key: u64,
2966 w: f32,
2967 ox: f32,
2968 oy: f32,
2969 nudge: Vec2,
2970 color: Color,
2971 clip: Clip,
2972 clip_id: ClipId,
2973 res: &Resources,
2974 fs: &mut FontSystem,
2975 atlas: &mut GlyphAtlas,
2976 out: &mut Vec<Quad>,
2977 ) {
2978 let vis = clip.visible();
2979 let (first, last) = {
2980 let line = self.long(key).expect("a long line");
2981 let ra = ((vis.y - oy) / line.line_h).floor().max(0.0) as u32;
2982 let rb = ((vis.y + vis.h - oy) / line.line_h).floor().max(0.0) as u32;
2983 let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
2984 return;
2985 };
2986 (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
2987 };
2988 let mut fresh = false;
2989 for i in first..=last {
2990 fresh |= self.ensure_chunk(key, i, res, fs);
2991 }
2992 if fresh {
2993 self.relayout_long(key, Some(w));
2996 let line = self.long_mut(key).expect("checked");
2997 let rows = line.rows();
2998 if rows != line.laid_rows && rows != line.asked_rows {
2999 line.asked_rows = rows;
3000 self.owed = true;
3001 }
3002 }
3003 let frame_no = self.frame_no;
3004 self.long_mut(key).expect("checked").last_used = frame_no;
3005 for i in first..=last {
3006 let chunk_key = self.long(key).expect("checked").chunks[i].key;
3007 let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
3011 self.entries.get_disjoint_mut([&key, &chunk_key])
3012 else {
3013 continue;
3014 };
3015 let (row0, head_x) = line.starts[i];
3016 let chunk = &line.chunks[i];
3017 if chunk.rows.is_empty() {
3018 continue;
3019 }
3020 let raster = &mut self.raster;
3021 emit_entry_rows(
3022 entry,
3023 ox,
3024 oy + row0 as f32 * line.line_h,
3025 nudge,
3026 head_x,
3027 &chunk.rows,
3028 line.line_h,
3029 color,
3030 clip,
3031 clip_id,
3032 raster,
3033 fs,
3034 atlas,
3035 out,
3036 &mut self.joins,
3037 );
3038 }
3039 }
3040}
3041
3042#[allow(clippy::too_many_arguments)]
3047fn emit_entry_rows(
3048 entry: &mut CachedText,
3049 ox: f32,
3050 oy: f32,
3051 nudge: Vec2,
3052 head_x: f32,
3053 rows: &[RowStart],
3054 line_h: f32,
3055 color: Color,
3056 clip: Clip,
3057 clip_id: ClipId,
3058 raster: &mut Raster,
3059 fs: &mut FontSystem,
3060 atlas: &mut GlyphAtlas,
3061 out: &mut Vec<Quad>,
3062 joins: &mut Vec<JoinBg>,
3063) {
3064 build_templates(entry, raster, fs, atlas);
3065 let vis = clip.visible();
3067 let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
3073 let (x0, x1) = (d.x, d.x + d.w);
3074 for r in 0..rows.len() {
3075 let ra = rows[r].x;
3076 let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
3077 let (a, b) = (x0.max(ra), x1.min(rb));
3078 if b <= a {
3079 continue;
3080 }
3081 let dx = if r == 0 { head_x } else { 0.0 } - ra;
3082 let x = ox + a + dx;
3083 let y = oy + d.y + r as f32 * line_h;
3084 if y >= vis.y + vis.h {
3085 break;
3086 }
3087 if y + d.h <= vis.y || x >= vis.x + vis.w || x + (b - a) <= vis.x {
3088 continue;
3089 }
3090 let quad = Quad {
3091 rect: if d.under {
3092 on_pixels(x, y, b - a, d.h, nudge)
3093 } else {
3094 Rect::new(x, y, b - a, d.h)
3095 },
3096 color: d.color.unwrap_or(color),
3097 border_color: Color::TRANSPARENT,
3098 radius: [0.0; 4],
3099 border_w: 0.0,
3100 blur: 0.0,
3101 kind: QuadKind::Solid,
3102 clip: clip_id,
3103 uv: [0; 4],
3104 };
3105 if d.under && d.radius > 0.0 {
3109 joins.push(JoinBg {
3110 quad: out.len() as u32,
3111 radius: d.radius,
3112 outer: clip_id,
3113 own: None,
3114 });
3115 }
3116 if d.under || d.style == UnderlineStyle::Solid {
3117 out.push(quad);
3118 } else {
3119 crate::deco::push_line(
3120 out,
3121 d.style,
3122 x,
3123 y,
3124 b - a,
3125 d.h,
3126 d.color.unwrap_or(color),
3127 clip_id,
3128 );
3129 }
3130 }
3131 };
3132 for d in entry.deco.iter().filter(|d| d.under) {
3133 row_pieces(d, out);
3134 }
3135 let mut r = 0usize;
3136 for g in &entry.glyphs {
3137 while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
3138 r += 1;
3139 }
3140 let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
3141 let x = ox + g.x + dx;
3142 let y = oy + g.y + r as f32 * line_h;
3143 if y >= vis.y + vis.h {
3150 break;
3151 }
3152 if y + g.h <= vis.y || x >= vis.x + vis.w || x + g.w <= vis.x {
3153 continue;
3154 }
3155 out.push(Quad {
3156 rect: Rect::new(x, y, g.w, g.h),
3157 color: g.color.unwrap_or(color),
3158 border_color: Color::TRANSPARENT,
3159 radius: [0.0; 4],
3160 border_w: 0.0,
3161 blur: 0.0,
3162 kind: g.kind,
3163 clip: clip_id,
3164 uv: g.uv,
3165 });
3166 }
3167 for d in entry.deco.iter().filter(|d| !d.under) {
3168 row_pieces(d, out);
3169 }
3170}
3171
3172fn break_rows(
3185 buffer: &Buffer,
3186 text: &str,
3187 wrap: TextWrap,
3188 w: f32,
3189 head_x: f32,
3190) -> (Vec<RowStart>, f32) {
3191 let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
3192 let Some(run) = buffer.layout_runs().next() else {
3193 return (rows, head_x);
3194 };
3195 let glyphs = run.glyphs;
3196 let blank = |g: &cosmic_text::LayoutGlyph| {
3197 text.get(g.start..g.end)
3198 .is_some_and(|s| s.chars().all(char::is_whitespace))
3199 };
3200 let spaces = wrap == TextWrap::BreakSpaces;
3201 let mut pieces: Vec<(usize, usize)> = Vec::new();
3202 match wrap {
3203 TextWrap::Word | TextWrap::BreakSpaces => {
3204 let mut at = 0usize;
3205 let mut piece = |from: usize, to: usize| {
3206 if !spaces {
3207 pieces.push((from, to));
3208 return;
3209 }
3210 let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
3213 if ink > 0 {
3214 pieces.push((from, from + ink));
3215 }
3216 for (i, c) in text[from + ink..to].char_indices() {
3217 let at = from + ink + i;
3218 pieces.push((at, at + c.len_utf8()));
3219 }
3220 };
3221 for (i, _) in unicode_linebreak::linebreaks(text) {
3222 if i > at {
3223 piece(at, i);
3224 at = i;
3225 }
3226 }
3227 if at < text.len() {
3228 piece(at, text.len());
3229 }
3230 }
3231 TextWrap::Glyph | TextWrap::None => {
3232 pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
3233 }
3234 }
3235 let mut row_x0 = 0.0f32;
3236 let mut avail = w - head_x;
3237 let mut gi = 0usize;
3238 for (s, e) in pieces {
3239 let g0 = gi;
3240 while gi < glyphs.len() && glyphs[gi].start < e {
3241 gi += 1;
3242 }
3243 if g0 == gi {
3244 continue;
3245 }
3246 let piece = &glyphs[g0..gi];
3247 let x0 = piece[0].x;
3248 let ink_end = piece
3249 .iter()
3250 .rev()
3251 .find(|g| spaces || !blank(g))
3252 .map_or(x0, |g| g.x + g.w);
3253 let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
3259 if ink_end - row_x0 > avail && opens {
3260 rows.push(RowStart {
3261 byte: s as u32,
3262 x: x0,
3263 });
3264 row_x0 = x0;
3265 avail = w;
3266 }
3267 if ink_end - row_x0 > avail {
3268 for g in piece {
3270 if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
3271 rows.push(RowStart {
3272 byte: g.start as u32,
3273 x: g.x,
3274 });
3275 row_x0 = g.x;
3276 avail = w;
3277 }
3278 }
3279 }
3280 }
3281 let end = if rows.len() == 1 {
3282 head_x + run.line_w
3283 } else {
3284 run.line_w - row_x0
3285 };
3286 (rows, end)
3287}
3288
3289fn build_templates(
3292 entry: &mut CachedText,
3293 raster: &mut Raster,
3294 fs: &mut FontSystem,
3295 atlas: &mut GlyphAtlas,
3296) {
3297 {
3298 let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
3300 if entry.glyphs_built_for != Some(built_for) {
3301 entry.glyphs.clear();
3302 entry.deco.clear();
3303 let lines = line_cap(entry.max_lines);
3304 let decorated = entry.span_deco.iter().any(SpanDeco::any);
3305 let painted = entry.span_deco.windows(2).any(|w| w[0].color != w[1].color);
3308 let starts = if (decorated || painted) && entry.span_deco.len() > 1 {
3309 line_starts(&entry.buffer, &entry.content)
3310 } else {
3311 Vec::new()
3312 };
3313 let mut places = Places::default();
3314 for run in entry.buffer.layout_runs().take(lines) {
3315 if decorated || painted {
3316 let base = starts.get(run.line_i).copied().unwrap_or(0);
3317 places.of_line(&run, base, &entry.span_deco, painted);
3318 }
3319 if decorated {
3320 build_decorations(&run, &places.parts, &entry.span_deco, fs, &mut entry.deco);
3321 }
3322 for (i, glyph) in run.glyphs.iter().enumerate() {
3323 let physical = glyph.physical((0.0, 0.0), 1.0);
3324 let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
3325 continue;
3326 };
3327 let template = GlyphTemplate {
3328 x: physical.x as f32 + slot.left as f32,
3329 y: run.line_y.round() + physical.y as f32 - slot.top as f32,
3330 w: slot.w as f32,
3331 h: slot.h as f32,
3332 uv: [slot.x, slot.y, slot.w, slot.h],
3333 kind: glyph_kind(&slot),
3334 color: glyph
3335 .color_opt
3336 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3337 byte: glyph.start as u32,
3338 };
3339 if painted && template.kind != QuadKind::GlyphColor {
3341 places.push_painted(
3342 template,
3343 i,
3344 run.glyphs,
3345 &entry.span_deco,
3346 &mut entry.glyphs,
3347 );
3348 } else {
3349 entry.glyphs.push(template);
3350 }
3351 }
3352 }
3353 entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
3357 }
3358 }
3359}
3360
3361#[allow(clippy::too_many_arguments)]
3365fn emit_entry(
3366 entry: &mut CachedText,
3367 ox: f32,
3368 oy: f32,
3369 nudge: Vec2,
3370 color: Color,
3371 clip: Clip,
3372 clip_id: ClipId,
3373 raster: &mut Raster,
3374 fs: &mut FontSystem,
3375 atlas: &mut GlyphAtlas,
3376 out: &mut Vec<Quad>,
3377 joins: &mut Vec<JoinBg>,
3378) {
3379 build_templates(entry, raster, fs, atlas);
3380 let vis = clip.visible();
3382 {
3383 let inside = |x: f32, y: f32, w: f32, h: f32| {
3384 oy + y + h >= vis.y
3385 && oy + y <= vis.y + vis.h
3386 && ox + x < vis.x + vis.w
3387 && ox + x + w > vis.x
3388 };
3389 let deco_quad = |d: &DecoTemplate| Quad {
3390 rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
3391 color: d.color.unwrap_or(color),
3392 border_color: Color::TRANSPARENT,
3393 radius: [0.0; 4],
3394 border_w: 0.0,
3395 blur: 0.0,
3396 kind: QuadKind::Solid,
3397 clip: clip_id,
3398 uv: [0; 4],
3399 };
3400 for d in entry
3405 .deco
3406 .iter()
3407 .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3408 {
3409 if d.radius > 0.0 {
3410 joins.push(JoinBg {
3411 quad: out.len() as u32,
3412 radius: d.radius,
3413 outer: clip_id,
3414 own: None,
3415 });
3416 }
3417 out.push(Quad {
3418 rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3419 ..deco_quad(d)
3420 });
3421 }
3422
3423 out.extend(
3427 entry
3428 .glyphs
3429 .iter()
3430 .filter(|g| {
3431 oy + g.y + g.h >= vis.y && ox + g.x < vis.x + vis.w && ox + g.x + g.w > vis.x
3432 })
3433 .take_while(|g| oy + g.y <= vis.y + vis.h)
3434 .map(|g| Quad {
3435 rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3436 color: g.color.unwrap_or(color),
3437 border_color: Color::TRANSPARENT,
3438 radius: [0.0; 4],
3439 border_w: 0.0,
3440 blur: 0.0,
3441 kind: g.kind,
3442 clip: clip_id,
3443 uv: g.uv,
3444 }),
3445 );
3446 for d in entry
3447 .deco
3448 .iter()
3449 .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3450 {
3451 match d.style {
3454 UnderlineStyle::Solid => out.push(deco_quad(d)),
3455 style => crate::deco::push_line(
3456 out,
3457 style,
3458 ox + d.x,
3459 oy + d.y,
3460 d.w,
3461 d.h,
3462 d.color.unwrap_or(color),
3463 clip_id,
3464 ),
3465 }
3466 }
3467 }
3468}
3469
3470fn could_be_long(len: usize, style: &TextStyle) -> bool {
3478 (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3479 && style.max_lines == 0
3480 && !style.ellipsis
3481}
3482
3483fn has_line_break(content: &str) -> bool {
3486 content.bytes().any(|b| b == b'\n' || b == b'\r')
3487}
3488
3489fn has_rtl(content: &str) -> bool {
3497 !content.is_ascii()
3498 && content.chars().any(|c| {
3499 matches!(c as u32,
3500 0x0590..=0x08FF
3501 | 0x200F | 0x202B | 0x202E | 0x2067
3502 | 0xFB1D..=0xFDFF
3503 | 0xFE70..=0xFEFF
3504 | 0x10800..=0x10FFF
3505 | 0x1E800..=0x1EFFF)
3506 })
3507}
3508
3509#[derive(Clone, Copy, Debug)]
3513struct Part {
3514 x0: f32,
3515 x1: f32,
3516 span: usize,
3518 glyph: usize,
3520}
3521
3522#[derive(Default)]
3525struct Places {
3526 parts: Vec<Part>,
3528 by_x: Vec<u32>,
3531 cuts: Vec<u32>,
3533}
3534
3535impl Places {
3536 fn of_line(
3544 &mut self,
3545 run: &cosmic_text::LayoutRun<'_>,
3546 base: usize,
3547 spans: &[SpanDeco],
3548 painted: bool,
3549 ) {
3550 use unicode_segmentation::UnicodeSegmentation;
3551 self.parts.clear();
3552 let last = spans.len().saturating_sub(1);
3553 for (gi, g) in run.glyphs.iter().enumerate() {
3554 let span = g.metadata.min(last);
3556 let crosses = spans
3557 .get(span + 1)
3558 .is_some_and(|next| (next.start as usize) < base + g.end);
3559 let cluster = run.text.get(g.start..g.end).filter(|_| crosses);
3560 let Some(cluster) = cluster else {
3561 self.parts.push(Part {
3562 x0: g.x,
3563 x1: g.x + g.w,
3564 span,
3565 glyph: gi,
3566 });
3567 continue;
3568 };
3569 let share = g.w / cluster.graphemes(true).count().max(1) as f32;
3570 for (k, (i, _)) in cluster.grapheme_indices(true).enumerate() {
3571 let byte = base + g.start + i;
3572 let span = spans
3573 .partition_point(|s| s.start as usize <= byte)
3574 .saturating_sub(1);
3575 let (x0, x1) = if g.level.is_rtl() {
3576 let x1 = g.x + g.w - k as f32 * share;
3577 (x1 - share, x1)
3578 } else {
3579 let x0 = g.x + k as f32 * share;
3580 (x0, x0 + share)
3581 };
3582 match self.parts.last_mut() {
3583 Some(p) if p.glyph == gi && p.span == span => {
3584 p.x0 = p.x0.min(x0);
3585 p.x1 = p.x1.max(x1);
3586 }
3587 _ => self.parts.push(Part {
3588 x0,
3589 x1,
3590 span,
3591 glyph: gi,
3592 }),
3593 }
3594 }
3595 }
3596 self.by_x.clear();
3597 if painted {
3598 let parts = &self.parts;
3599 self.by_x.extend(
3600 (0..parts.len() as u32).filter(|&k| parts[k as usize].x1 > parts[k as usize].x0),
3601 );
3602 if !self
3604 .by_x
3605 .is_sorted_by(|&a, &b| parts[a as usize].x0 <= parts[b as usize].x0)
3606 {
3607 self.by_x
3608 .sort_by(|&a, &b| parts[a as usize].x0.total_cmp(&parts[b as usize].x0));
3609 }
3610 }
3611 }
3612
3613 fn push_painted(
3628 &mut self,
3629 t: GlyphTemplate,
3630 gi: usize,
3631 glyphs: &[cosmic_text::LayoutGlyph],
3632 spans: &[SpanDeco],
3633 out: &mut Vec<GlyphTemplate>,
3634 ) {
3635 let parts = &self.parts;
3636 let own = {
3637 let lo = parts.partition_point(|p| p.glyph < gi);
3638 let hi = parts.partition_point(|p| p.glyph <= gi);
3639 &parts[lo..hi]
3640 };
3641 if own.is_empty() || t.w < 2.0 {
3642 out.push(t);
3643 return;
3644 }
3645 let g = &glyphs[gi];
3646 let kin = |p: &Part| {
3647 let n = &glyphs[p.glyph];
3648 p.glyph == gi
3649 || n.font_id == g.font_id
3650 && n.font_weight == g.font_weight
3651 && n.font_size == g.font_size
3652 && n.cache_key_flags == g.cache_key_flags
3653 };
3654 let color_of = |p: &Part| spans.get(p.span).and_then(|s| s.color);
3655 let at = |x: f32| {
3658 let k = self
3659 .by_x
3660 .partition_point(|&k| parts[k as usize].x0 <= x)
3661 .checked_sub(1)
3662 .map(|k| &parts[self.by_x[k] as usize]);
3663 match k {
3664 Some(p) if x < p.x1 && kin(p) => color_of(p),
3665 _ => {
3666 let near = own
3667 .iter()
3668 .min_by(|a, b| {
3669 let d = |p: &Part| (p.x0 - x).max(x - p.x1).max(0.0);
3670 d(a).total_cmp(&d(b))
3671 })
3672 .expect("not empty");
3673 color_of(near)
3674 }
3675 }
3676 };
3677 let (x0, x1) = (t.x, t.x + t.w);
3678 let w = t.w as u32;
3679 self.cuts.clear();
3680 let from = self
3681 .by_x
3682 .partition_point(|&k| parts[k as usize].x0 < x0)
3683 .saturating_sub(1);
3684 for &k in &self.by_x[from..] {
3685 let p = &parts[k as usize];
3686 if p.x0 >= x1 {
3687 break;
3688 }
3689 for edge in [p.x0, p.x1] {
3690 let c = (edge - x0).round();
3691 if c >= 1.0 && c < t.w {
3692 self.cuts.push(c as u32);
3693 }
3694 }
3695 }
3696 if self.cuts.is_empty() {
3697 out.push(GlyphTemplate {
3698 color: at(x0 + t.w / 2.0),
3699 ..t
3700 });
3701 return;
3702 }
3703 self.cuts.sort_unstable();
3704 self.cuts.dedup();
3705 self.cuts.push(w);
3706 let first = out.len();
3707 let mut a = 0u32;
3708 for &b in &self.cuts {
3709 let color = at(x0 + (a + b) as f32 / 2.0);
3710 let sliced = out.len() > first;
3711 match out.last_mut() {
3712 Some(prev) if sliced && prev.color == color => {
3714 prev.w += (b - a) as f32;
3715 prev.uv[2] += b - a;
3716 }
3717 _ => out.push(GlyphTemplate {
3718 x: x0 + a as f32,
3719 w: (b - a) as f32,
3720 uv: [t.uv[0] + a, t.uv[1], b - a, t.uv[3]],
3721 color,
3722 ..t
3723 }),
3724 }
3725 a = b;
3726 }
3727 }
3728}
3729
3730fn build_decorations(
3742 run: &cosmic_text::LayoutRun<'_>,
3743 parts: &[Part],
3744 spans: &[SpanDeco],
3745 fs: &mut FontSystem,
3746 out: &mut Vec<DecoTemplate>,
3747) {
3748 let mut last_bg: Option<usize> = None;
3751 let mut i = 0;
3752 while i < parts.len() {
3753 let span_no = parts[i].span;
3754 let mut j = i + 1;
3755 while j < parts.len() && parts[j].span == span_no {
3756 j += 1;
3757 }
3758 let deco = spans.get(span_no).copied().unwrap_or_default();
3759 if deco.any() {
3760 let group = &parts[i..j];
3761 let x0 = group.iter().map(|p| p.x0).fold(f32::INFINITY, f32::min);
3762 let x1 = group.iter().map(|p| p.x1).fold(f32::NEG_INFINITY, f32::max);
3763 let head = &run.glyphs[group[0].glyph];
3764 let w = (x1 - x0).max(0.0);
3765 match (deco.bg, last_bg.map(|k| &mut out[k])) {
3766 (Some(bg), Some(prev))
3767 if prev.color == Some(bg)
3768 && prev.radius == deco.bg_radius
3769 && (prev.x + prev.w - x0).abs() < 0.01 =>
3770 {
3771 prev.w = (x1 - prev.x).max(prev.w);
3772 }
3773 (Some(bg), _) => {
3774 let (y, h) = match head.line_height_opt {
3778 Some(lh) if lh < run.line_height - 0.01 => {
3779 let g = head;
3780 let (asc, desc) = fs
3781 .get_font(g.font_id, g.font_weight)
3782 .map(|font| {
3783 let m = font.as_swash().metrics(&[]).scale(g.font_size);
3784 (m.ascent, m.descent)
3785 })
3786 .unwrap_or((0.8 * g.font_size, 0.2 * g.font_size));
3787 let mid = run.line_y - (asc - desc) / 2.0;
3788 (mid - lh / 2.0, lh)
3789 }
3790 _ => (run.line_top, run.line_height),
3791 };
3792 last_bg = Some(out.len());
3793 out.push(DecoTemplate {
3794 x: x0,
3795 y,
3796 w,
3797 h,
3798 color: Some(bg),
3799 under: true,
3800 style: UnderlineStyle::Solid,
3801 radius: deco.bg_radius,
3802 });
3803 }
3804 (None, _) => last_bg = None,
3805 }
3806 if deco.underline || deco.strikethrough {
3807 let first = head;
3808 let metrics = fs
3809 .get_font(first.font_id, first.font_weight)
3810 .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3811 let (under_off, strike_off, stroke) = match metrics {
3814 Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3815 None => (
3816 -0.1 * first.font_size,
3817 0.3 * first.font_size,
3818 0.06 * first.font_size,
3819 ),
3820 };
3821 let stroke = stroke.max(1.0).round();
3822 let baseline = run.line_y.round();
3823 if deco.underline {
3824 out.push(DecoTemplate {
3825 x: x0,
3826 y: (baseline - under_off).round(),
3827 w,
3828 h: stroke,
3829 color: deco.underline_color.or(deco.color),
3830 under: false,
3831 style: deco.underline_style,
3832 radius: 0.0,
3833 });
3834 }
3835 if deco.strikethrough {
3836 out.push(DecoTemplate {
3837 x: x0,
3838 y: (baseline - strike_off).round(),
3839 w,
3840 h: stroke,
3841 color: deco.color,
3842 under: false,
3843 style: UnderlineStyle::Solid,
3844 radius: 0.0,
3845 });
3846 }
3847 }
3848 } else {
3849 last_bg = None;
3850 }
3851 i = j;
3852 }
3853}
3854
3855impl TextSystem {
3856 pub(crate) fn place(
3861 &mut self,
3862 key: Key,
3863 ancestry: &Ancestry,
3864 id: TextId,
3865 origin: Vec2,
3866 scope: Option<Key>,
3867 drawn: bool,
3868 ) {
3869 let cache_key = self.frame[id.0 as usize].cache_key;
3870 self.places.push(TextPlace {
3871 key,
3872 ancestors: ancestry.keys,
3873 depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3874 none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3875 cache_key,
3876 origin,
3877 scope,
3878 drawn,
3879 });
3880 }
3881
3882 fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3888 let list = if prev {
3889 self.places.prev()
3890 } else {
3891 &self.places
3892 };
3893 let mut base = 0usize;
3894 let mut out = Vec::new();
3895 for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3896 let Some(entry) = self.entries.get(&place.cache_key) else {
3897 continue;
3898 };
3899 out.push((place, entry, base));
3900 base += entry.content().len();
3901 }
3902 out
3903 }
3904
3905 pub(crate) fn content_len(&self, id: TextId) -> usize {
3908 let key = self.frame[id.0 as usize].cache_key;
3909 self.entries.get(&key).map_or(0, |e| e.content().len())
3910 }
3911
3912 pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3921 let list = if prev {
3922 self.places.prev()
3923 } else {
3924 &self.places
3925 };
3926 let mut base = 0usize;
3927 let mut out = Vec::new();
3928 for place in list.iter().filter(|p| p.scope == Some(scope)) {
3929 let Some(text) = self.entries.get(&place.cache_key) else {
3930 continue;
3931 };
3932 let len = text.content().len();
3933 out.push(ScopeRun { place, text, base });
3934 base += len;
3935 }
3936 out
3937 }
3938
3939 pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3946 let runs = self.scope_runs(scope, prev);
3947 let px = point.x * self.scale;
3948 let py = point.y * self.scale;
3949 let gap = |r: &Rect| {
3950 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3951 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3952 (dy, dx)
3953 };
3954 let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3955 let ga = gap(&self.scope_box(a));
3956 let gb = gap(&self.scope_box(b));
3957 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3958 })?;
3959 let byte = self.hit_in_run(run, point)?;
3960 Some((run.place.key, byte))
3961 }
3962
3963 fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3965 self.entry_box(run.place, run.text)
3966 }
3967
3968 fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
3973 match entry {
3974 Entry::Run(e) => self.physical_box(place, e),
3975 Entry::Long(l) => {
3976 let (ox, oy) = self.physical_origin(place);
3977 let w = l.wrap_w.unwrap_or_else(|| l.width());
3978 Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
3979 }
3980 }
3981 }
3982
3983 fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
3985 let (_, oy) = self.physical_origin(place);
3986 match entry {
3987 Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
3988 Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
3989 }
3990 }
3991
3992 fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
3995 match run.text {
3996 Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
3997 Entry::Run(e) => {
3998 let (ox, oy) = self.physical_origin(run.place);
3999 let cursor = e
4000 .buffer
4001 .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
4002 Some(e.byte_of(cursor))
4003 }
4004 }
4005 }
4006
4007 pub(crate) fn word_at(
4014 &self,
4015 scope: Key,
4016 node: Key,
4017 byte: usize,
4018 prev: bool,
4019 ) -> Option<(usize, usize)> {
4020 let run = self
4021 .scope_runs(scope, prev)
4022 .into_iter()
4023 .find(|r| r.place.key == node)?;
4024 let content = run.text.content();
4025 Some(word_range(content, byte))
4026 }
4027
4028 pub(crate) fn scope_offset(
4034 &self,
4035 scope: Key,
4036 node: Key,
4037 byte: usize,
4038 prev: bool,
4039 ) -> Option<usize> {
4040 let run = self
4041 .scope_runs(scope, prev)
4042 .into_iter()
4043 .find(|r| r.place.key == node)?;
4044 Some(run.base + byte.min(run.text.content().len()))
4045 }
4046
4047 pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
4062 let (from, to) = (from.min(to), from.max(to));
4063 let mut out = String::new();
4064 let mut prev_run: Option<&ScopeRun<'_>> = None;
4065 for run in &self.scope_runs(scope, prev) {
4066 let (start, end) = run.span();
4067 if end <= from || start >= to {
4068 continue;
4069 }
4070 if let Some(p) = prev_run
4071 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
4072 {
4073 out.push_str("<br>");
4074 }
4075 let content = run.text.content();
4076 let lo = floor_boundary(content, from.saturating_sub(start));
4077 let hi = floor_boundary(content, (to - start).min(content.len()));
4078 match run.text {
4079 Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
4080 Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
4081 }
4082 prev_run = Some(run);
4083 }
4084 out
4085 }
4086
4087 fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
4093 for c in &line.chunks {
4094 let (a, b) = (lo.max(c.start), hi.min(c.end));
4095 if a >= b {
4096 continue;
4097 }
4098 match c.width.and_then(|_| self.run(c.key)) {
4099 Some(e) => html_of_run(e, a - c.start, b - c.start, out),
4100 None => escape_into(&line.content[a..b], out),
4101 }
4102 }
4103 }
4104
4105 pub(crate) fn scope_selection_anchor(
4115 &self,
4116 scope: Key,
4117 from: usize,
4118 to: usize,
4119 prev: bool,
4120 ) -> Option<Vec2> {
4121 let (from, to) = (from.min(to), from.max(to));
4122 for run in self.scope_runs(scope, prev) {
4123 let (start, end) = run.span();
4124 if !run.place.drawn || end <= from || start >= to {
4125 continue;
4126 }
4127 let content = run.text.content();
4128 let lo = floor_boundary(content, from.saturating_sub(start));
4129 let hi = floor_boundary(content, (to - start).min(content.len()));
4130 let (ox, oy) = self.physical_origin(run.place);
4131 match run.text {
4132 Entry::Run(e) => {
4133 if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
4134 return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
4135 }
4136 }
4137 Entry::Long(l) => {
4138 if let Some((x, y)) = self.long_caret_local(l, lo) {
4139 return Some(Vec2::new(
4140 (ox + x) / self.scale,
4141 (oy + y + self.long_baseline(l)) / self.scale,
4142 ));
4143 }
4144 }
4145 }
4146 }
4147 None
4148 }
4149
4150 fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
4153 let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
4154 for run in entry.buffer.layout_runs() {
4155 if run.line_i < a.line || run.line_i > b.line {
4156 continue;
4157 }
4158 if let Some((x, _)) = run.highlight(a, b).next() {
4159 return Some((x, run.line_y));
4160 }
4161 }
4162 None
4163 }
4164
4165 pub(crate) fn scope_selection_rect(
4171 &self,
4172 scope: Key,
4173 from: usize,
4174 to: usize,
4175 prev: bool,
4176 ) -> Option<Rect> {
4177 let (from, to) = (from.min(to), from.max(to));
4178 let mut out: Option<Rect> = None;
4179 for run in self.scope_runs(scope, prev) {
4180 let (start, end) = run.span();
4181 if !run.place.drawn || end <= from || start >= to {
4182 continue;
4183 }
4184 let content = run.text.content();
4185 let lo = floor_boundary(content, from.saturating_sub(start));
4186 let hi = floor_boundary(content, (to - start).min(content.len()));
4187 let (ox, oy) = self.physical_origin(run.place);
4193 let rects = match run.text {
4194 Entry::Long(l) => self.long_highlight(l, lo, hi),
4195 Entry::Run(e) => Self::run_highlight(e, lo, hi),
4196 };
4197 for r in rects {
4198 let r = Rect::new(
4199 (ox + r.x) / self.scale,
4200 (oy + r.y) / self.scale,
4201 r.w / self.scale,
4202 r.h / self.scale,
4203 );
4204 out = Some(match out {
4205 None => r,
4206 Some(o) => o.union(&r),
4207 });
4208 }
4209 }
4210 out
4211 }
4212
4213 pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
4218 let (from, to) = (from.min(to), from.max(to));
4219 let runs = self.scope_runs(scope, prev);
4220 let mut out = String::new();
4221 let mut reserved = false;
4222 let mut prev_run: Option<&ScopeRun<'_>> = None;
4223 for run in &runs {
4224 let (start, end) = run.span();
4225 if end <= from || start >= to {
4226 continue;
4227 }
4228 if !reserved {
4229 out.reserve(to - from + runs.len());
4230 reserved = true;
4231 }
4232 if let Some(p) = prev_run
4233 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
4234 {
4235 out.push('\n');
4236 }
4237 let content = run.text.content();
4238 let lo = from.saturating_sub(start).min(content.len());
4239 let hi = (to - start).min(content.len());
4240 out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
4241 prev_run = Some(run);
4242 }
4243 out
4244 }
4245
4246 fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
4250 let (ox, oy) = self.physical_origin(place);
4251 let px = point.x * self.scale - ox;
4252 let py = point.y * self.scale - oy;
4253 if line.chunks.is_empty() {
4254 return Some(TextHit { byte: 0, line: 0 });
4255 }
4256 if let Some(w) = line.wrap_w {
4257 return self.long_hit_wrapped(line, w, px, py);
4258 }
4259 if px >= line.width() {
4260 return Some(TextHit {
4261 byte: line.content.len(),
4262 line: 0,
4263 });
4264 }
4265 let i = line.chunk_at(px.max(0.0));
4266 let c = &line.chunks[i];
4267 let local = px - line.prefix[i];
4268 let byte = match self.run(c.key) {
4269 Some(_) if let Some(b) = line.placed_tab_hit(i, local) => b,
4270 Some(e) if c.width.is_some() => {
4271 let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
4272 c.start + cursor.index.min(c.end - c.start)
4273 }
4274 _ => {
4275 let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
4276 let mut b = (c.start + est).min(c.end);
4277 while !line.content.is_char_boundary(b) {
4278 b -= 1;
4279 }
4280 b
4281 }
4282 };
4283 Some(TextHit { byte, line: 0 })
4284 }
4285
4286 fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
4290 let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
4291 let Some((a, b)) = line.chunks_on_rows(row, row) else {
4292 return Some(TextHit {
4293 byte: line.content.len(),
4294 line: row,
4295 });
4296 };
4297 let i = (a..=b)
4300 .find(|&j| {
4301 let (r1, end_x) = line.starts[j + 1];
4302 px < if r1 == row { end_x } else { w }
4303 })
4304 .unwrap_or(b);
4305 let c = &line.chunks[i];
4306 let (row0, head_x) = line.starts[i];
4307 let r = (row - row0) as usize;
4308 let byte = match self.run(c.key) {
4309 Some(e) if r < c.rows.len() => {
4310 let rs = c.rows[r];
4311 let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
4312 let lo = rs.byte as usize;
4313 let hi = c
4314 .rows
4315 .get(r + 1)
4316 .map_or(c.end - c.start, |n| n.byte as usize);
4317 let tab_here = (lo..hi).contains(&(c.end - c.start - 1));
4319 match line.placed_tab_hit(i, local_x) {
4320 Some(b) if tab_here => b,
4321 _ => {
4322 let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
4323 c.start + cursor.index.clamp(lo, hi)
4324 }
4325 }
4326 }
4327 _ => {
4328 let linear = r as f32 * w + px - head_x;
4331 let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
4332 let mut b = (c.start + est).min(c.end);
4333 while !line.content.is_char_boundary(b) {
4334 b -= 1;
4335 }
4336 b
4337 }
4338 };
4339 let next_row = self
4344 .long_caret_local(line, byte)
4345 .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
4346 let byte = if next_row && byte > 0 {
4347 floor_boundary(&line.content, byte - 1)
4348 } else {
4349 byte
4350 };
4351 Some(TextHit { byte, line: row })
4352 }
4353
4354 fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
4357 let (ox, oy) = self.physical_origin(place);
4358 let scale = self.scale;
4359 let (x, y) = self.long_caret_local(line, byte)?;
4360 Some(Rect::new(
4361 (ox + x) / scale,
4362 (oy + y) / scale,
4363 0.0,
4364 line.line_h / scale,
4365 ))
4366 }
4367
4368 fn long_baseline(&self, line: &LongLine) -> f32 {
4374 line.chunks
4375 .first()
4376 .and_then(|c| self.run(c.key))
4377 .and_then(|e| e.buffer.layout_runs().next())
4378 .map_or(line.line_h * 0.8, |r| r.line_y.round())
4379 }
4380
4381 fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
4386 let byte = byte.min(line.content.len());
4387 if let Some(w) = line.wrap_w
4388 && !line.chunks.is_empty()
4389 {
4390 let i = line.chunk_of_byte(byte);
4391 let c = &line.chunks[i];
4392 let local = byte - c.start;
4393 let (row0, head_x) = line.starts[i];
4394 let (r, x) = match self.run(c.key) {
4395 Some(e) if !c.rows.is_empty() => {
4396 let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
4397 let run = e.buffer.layout_runs().next()?;
4398 let cx = caret_x(&run, local.min(c.end - c.start)) + line.end_shift(i, local);
4399 (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
4400 }
4401 _ => {
4402 let linear = head_x + local as f32 * line.avg;
4403 let r = (linear / w).floor();
4404 (r as usize, linear - r * w)
4405 }
4406 };
4407 let y = (row0 as usize + r) as f32 * line.line_h;
4408 return Some((x, y));
4409 }
4410 let x = if line.chunks.is_empty() {
4411 0.0
4412 } else {
4413 let i = line.chunk_of_byte(byte);
4414 let c = &line.chunks[i];
4415 let local = byte - c.start;
4416 match self.run(c.key) {
4417 Some(e) if c.width.is_some() => {
4418 let run = e.buffer.layout_runs().next()?;
4419 let x = caret_x(&run, local.min(c.end - c.start));
4420 line.prefix[i] + x + line.end_shift(i, local)
4421 }
4422 _ => line.prefix[i] + local as f32 * line.avg,
4423 }
4424 };
4425 Some((x, 0.0))
4426 }
4427
4428 fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
4434 let Some((x0, y0)) = self.long_caret_local(line, from) else {
4435 return Vec::new();
4436 };
4437 let Some((x1, y1)) = self.long_caret_local(line, to) else {
4438 return Vec::new();
4439 };
4440 let h = line.line_h;
4441 if (y1 - y0).abs() < f32::EPSILON {
4442 return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
4443 }
4444 let w = line.wrap_w.unwrap_or_else(|| line.width());
4446 let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
4447 let mut y = y0 + h;
4448 while y < y1 - h / 2.0 {
4449 out.push(Rect::new(0.0, y, w, h));
4450 y += h;
4451 }
4452 out.push(Rect::new(0.0, y1, x1.max(0.0), h));
4453 out
4454 }
4455
4456 fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
4462 let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
4463 let mut out = Vec::new();
4464 for run in entry.buffer.layout_runs() {
4465 if run.line_i < a.line || run.line_i > b.line {
4466 continue;
4467 }
4468 let h = run.line_height;
4471 let mut any = false;
4472 for (x, w) in run.highlight(a, b) {
4473 any = true;
4474 out.push(Rect::new(x, run.line_top, w.max(2.0), h));
4475 }
4476 if !any && run.glyphs.is_empty() && b.line > run.line_i {
4479 out.push(Rect::new(0.0, run.line_top, 2.0, h));
4480 }
4481 }
4482 out
4483 }
4484
4485 fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
4488 (
4489 crate::geom::snap_px(place.origin.x * self.scale),
4490 crate::geom::snap_px(place.origin.y * self.scale),
4491 )
4492 }
4493
4494 fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
4496 let (ox, oy) = self.physical_origin(place);
4497 let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
4498 Rect::new(ox, oy, size.w, size.h)
4499 }
4500
4501 pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
4505 let runs = self.runs_of(key, prev);
4506 if runs.is_empty() {
4507 return None;
4508 }
4509 let px = point.x * self.scale;
4510 let py = point.y * self.scale;
4511 let gap = |r: &Rect| {
4514 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
4515 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
4516 (dy, dx)
4517 };
4518 let (place, entry, base) = runs
4519 .iter()
4520 .min_by(|a, b| {
4521 let ga = gap(&self.entry_box(a.0, a.1));
4522 let gb = gap(&self.entry_box(b.0, b.1));
4523 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
4524 })
4525 .copied()?;
4526 let (ox, oy) = self.physical_origin(place);
4527 let (byte, hit_top) = match entry {
4528 Entry::Long(line) => {
4529 let hit = self.long_hit(place, line, point)?;
4530 (hit.byte, oy + hit.line as f32 * line.line_h)
4531 }
4532 Entry::Run(entry) => {
4533 let bx = self.physical_box(place, entry);
4540 let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
4541 let cursor = entry.buffer.hit(px - ox, py - oy)?;
4542 let row =
4543 visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
4544 let top = entry
4545 .buffer
4546 .layout_runs()
4547 .nth(row)
4548 .map_or(0.0, |r| r.line_top);
4549 (entry.byte_of(cursor), oy + top)
4550 }
4551 };
4552 let mut tops: Vec<f32> = runs
4557 .iter()
4558 .flat_map(|(p, e, _)| self.row_tops(p, e))
4559 .collect();
4560 tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4561 tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
4562 let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
4563 Some(TextHit {
4564 byte: base + byte,
4565 line: line as u32,
4566 })
4567 }
4568
4569 pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
4573 let runs = self.runs_of(key, prev);
4574 let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
4575 let byte = byte.min(total);
4576 let (place, entry, base) = runs
4577 .iter()
4578 .find(|(_, e, base)| byte < base + e.content().len())
4579 .or_else(|| runs.last())
4580 .copied()?;
4581 let byte = (byte - base).min(entry.content().len());
4582 let entry = match entry {
4583 Entry::Long(line) => return self.long_caret(place, line, byte),
4584 Entry::Run(entry) => entry,
4585 };
4586 let (ox, oy) = self.physical_origin(place);
4587 let cursor = entry.cursor_of(byte);
4588 let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
4589 let run = entry.buffer.layout_runs().nth(run_no)?;
4590 let x = caret_x(&run, cursor.index);
4591 let scale = self.scale;
4592 Some(Rect::new(
4593 (ox + x) / scale,
4594 (oy + run.line_top) / scale,
4595 0.0,
4596 run.line_height / scale,
4597 ))
4598 }
4599}
4600
4601fn word_range(content: &str, byte: usize) -> (usize, usize) {
4611 if content.is_empty() {
4612 return (0, 0);
4613 }
4614 #[derive(PartialEq)]
4615 enum Class {
4616 Word,
4617 Space,
4618 Other,
4619 }
4620 let class = |c: char| {
4621 if c.is_alphanumeric() || c == '_' {
4622 Class::Word
4623 } else if c.is_whitespace() {
4624 Class::Space
4625 } else {
4626 Class::Other
4627 }
4628 };
4629 let at = floor_boundary(content, byte.min(content.len()));
4632 let at = if at >= content.len() {
4633 content.char_indices().next_back().map_or(0, |(i, _)| i)
4634 } else {
4635 at
4636 };
4637 let Some(here) = content[at..].chars().next().map(class) else {
4638 return (at, at);
4639 };
4640 let mut start = at;
4641 for (i, c) in content[..at].char_indices().rev() {
4642 if class(c) != here {
4643 break;
4644 }
4645 start = i;
4646 }
4647 let mut end = at;
4648 for (i, c) in content[at..].char_indices() {
4649 if class(c) != here {
4650 break;
4651 }
4652 end = at + i + c.len_utf8();
4653 }
4654 (start, end)
4655}
4656
4657fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4662 let starts = line_starts(&entry.buffer, &entry.content);
4663 for (li, line) in entry.buffer.lines.iter().enumerate() {
4664 let base = starts.get(li).copied().unwrap_or(0);
4665 let text = line.text();
4666 if li > 0 {
4667 if base > lo && base <= hi {
4670 out.push_str("<br>");
4671 }
4672 }
4673 let attrs_list = line.attrs_list();
4679 let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4680 for (i, _) in text.char_indices() {
4681 let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4682 match spans.last_mut() {
4683 Some((_, end, prev)) if *prev == a && *end == i => {
4684 *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4685 }
4686 _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4687 }
4688 }
4689 for (start, end, attrs) in &spans {
4690 let (s, e) = (base + start, base + end);
4691 let (s, e) = (s.max(lo), e.min(hi));
4692 if s >= e {
4693 continue;
4694 }
4695 let piece = &entry.content[s..e];
4696 let bold = attrs.weight > attrs_list.defaults().weight
4701 || attrs
4702 .cache_key_flags
4703 .contains(crate::weights::SYNTHETIC_BOLD);
4704 let italic = attrs.style != cosmic_text::Style::Normal;
4705 if let Some(c) = attrs.color_opt {
4706 let _ = std::fmt::Write::write_fmt(
4707 out,
4708 format_args!(
4709 "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4710 c.r(),
4711 c.g(),
4712 c.b()
4713 ),
4714 );
4715 }
4716 if bold {
4717 out.push_str("<b>");
4718 }
4719 if italic {
4720 out.push_str("<i>");
4721 }
4722 escape_into(piece, out);
4723 if italic {
4724 out.push_str("</i>");
4725 }
4726 if bold {
4727 out.push_str("</b>");
4728 }
4729 if attrs.color_opt.is_some() {
4730 out.push_str("</span>");
4731 }
4732 }
4733 }
4734}
4735
4736fn escape_into(s: &str, out: &mut String) {
4740 for ch in s.chars() {
4741 match ch {
4742 '&' => out.push_str("&"),
4743 '<' => out.push_str("<"),
4744 '>' => out.push_str(">"),
4745 _ => out.push(ch),
4746 }
4747 }
4748}
4749
4750fn floor_boundary(s: &str, i: usize) -> usize {
4756 let mut i = i.min(s.len());
4757 while i > 0 && !s.is_char_boundary(i) {
4758 i -= 1;
4759 }
4760 i
4761}
4762
4763fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4764 paragraph_starts(buffer, content).collect()
4765}
4766
4767fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4773 let mut at = 0usize;
4774 buffer.lines.iter().map(move |line| {
4775 let start = at.min(content.len());
4776 at += line.text().len();
4777 let rest = &content[at.min(content.len())..];
4778 for ending in ["\r\n", "\n\r", "\n", "\r"] {
4779 if rest.starts_with(ending) {
4780 at += ending.len();
4781 break;
4782 }
4783 }
4784 start
4785 })
4786}
4787
4788fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4798 let mut last_of_line = None;
4799 let mut before = None;
4800 for (n, run) in buffer.layout_runs().enumerate() {
4801 if run.line_i != line_i {
4802 if last_of_line.is_some() {
4803 break;
4804 }
4805 continue;
4806 }
4807 last_of_line = Some(n);
4808 if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4809 return Some((n, n));
4810 }
4811 if run
4812 .glyphs
4813 .iter()
4814 .map(|g| g.start)
4815 .min()
4816 .is_some_and(|s| s <= index)
4817 {
4818 before = Some(n);
4819 }
4820 }
4821 before.or(last_of_line).map(|n| (n, n))
4822}
4823
4824fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4828 if let Some(g) = run
4829 .glyphs
4830 .iter()
4831 .find(|g| g.start <= index && index < g.end)
4832 {
4833 return if g.level.is_rtl() { g.x + g.w } else { g.x };
4834 }
4835 match run.glyphs.last() {
4836 Some(g) if !g.level.is_rtl() => g.x + g.w,
4837 Some(g) => g.x,
4838 None => 0.0,
4839 }
4840}
4841
4842impl Default for TextSystem {
4843 fn default() -> Self {
4844 Self::new()
4845 }
4846}
4847
4848fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4851 let max_w = max_w_logical?;
4852 let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4853 (!intrinsic_fits).then_some(max_w * scale)
4854}
4855
4856pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4864 match (current, target) {
4865 (None, None) => false,
4866 (Some(a), Some(b)) => (a - b).abs() > 0.5,
4867 _ => true,
4868 }
4869}
4870
4871fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4872 if wrap_differs(entry.wrap, target) {
4873 entry.buffer.set_size(target, None);
4874 entry.buffer.shape_until_scroll(fs, false);
4875 entry.wrap = target;
4876 }
4877}
4878
4879impl CachedText {
4880 fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4883 let start = paragraph_starts(&self.buffer, &self.content)
4884 .nth(cursor.line)
4885 .unwrap_or(0);
4886 (start + cursor.index).min(self.content.len())
4887 }
4888
4889 fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4895 let mut li = 0;
4896 let mut start = 0;
4897 for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4898 if s <= byte {
4899 (li, start) = (i, s);
4900 } else {
4901 break;
4902 }
4903 }
4904 let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4905 cosmic_text::Cursor::new(li, (byte - start).min(len))
4906 }
4907
4908 fn new(
4909 mut buffer: Buffer,
4910 content: String,
4911 style: &TextStyle,
4912 span_deco: Vec<SpanDeco>,
4913 fs: &mut FontSystem,
4914 frame_no: u64,
4915 ) -> Self {
4916 buffer.shape_until_scroll(fs, false);
4917 let max_lines = line_budget(style);
4918 let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4919 let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4920 Self {
4921 buffer,
4922 bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4923 content,
4924 wrap: None,
4925 intrinsic,
4926 max_lines,
4927 clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4928 last_used: frame_no,
4929 glyphs: Vec::new(),
4930 deco: Vec::new(),
4931 span_deco,
4932 glyphs_built_for: None,
4933 claimed: u64::MAX,
4934 claimed_wrap: None,
4935 breaks_anywhere: style.wrap == TextWrap::Glyph,
4936 min_content: None,
4937 }
4938 }
4939
4940 fn fork(&self, frame_no: u64) -> Self {
4944 Self {
4945 buffer: self.buffer.clone(),
4946 content: self.content.clone(),
4947 wrap: self.wrap,
4948 intrinsic: self.intrinsic,
4949 max_lines: self.max_lines,
4950 clamp_w: self.clamp_w,
4951 last_used: frame_no,
4952 bytes: self.bytes,
4953 glyphs: Vec::new(),
4954 deco: Vec::new(),
4955 span_deco: self.span_deco.clone(),
4956 glyphs_built_for: None,
4957 claimed: u64::MAX,
4958 claimed_wrap: None,
4959 breaks_anywhere: self.breaks_anywhere,
4960 min_content: self.min_content,
4961 }
4962 }
4963
4964 fn min_content(&mut self) -> f32 {
4975 if let Some(w) = self.min_content {
4976 return w;
4977 }
4978 let w = if self.clamp_w {
4979 self.intrinsic.w
4980 } else {
4981 widest_unbreakable(&self.buffer, self.breaks_anywhere)
4982 };
4983 self.min_content = Some(w);
4984 w
4985 }
4986}
4987
4988fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
4992 let mut widest = 0.0f32;
4993 let mut line = usize::MAX;
4994 let mut breaks: Vec<usize> = Vec::new();
4995 let (mut run_w, mut ink) = (0.0f32, 0.0f32);
4996 for run in buffer.layout_runs() {
4997 if run.line_i != line {
4998 widest = widest.max(ink);
4999 (run_w, ink) = (0.0, 0.0);
5000 line = run.line_i;
5001 breaks.clear();
5002 if !anywhere {
5003 breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
5004 }
5005 }
5006 for g in run.glyphs {
5007 if anywhere || breaks.binary_search(&g.start).is_ok() {
5008 widest = widest.max(ink);
5009 (run_w, ink) = (0.0, 0.0);
5010 }
5011 run_w += g.w;
5012 let blank = run
5013 .text
5014 .get(g.start..g.end)
5015 .is_some_and(|t| t.chars().all(char::is_whitespace));
5016 if !blank {
5017 ink = run_w;
5018 }
5019 }
5020 }
5021 widest.max(ink)
5022}
5023
5024pub(crate) fn shaper_metrics(size: f32, line_height: f32) -> Metrics {
5031 let px = |v: f32| if v.is_finite() { v.max(1.0) } else { 1.0 };
5032 Metrics::new(px(size), px(line_height))
5033}
5034
5035fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
5038 let metrics = shaper_metrics(style.size * scale, style.line_height * scale);
5039 let mut buffer = Buffer::new(fs, metrics);
5040 buffer.set_wrap(match style.wrap {
5041 TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
5042 TextWrap::Glyph => Wrap::Glyph,
5043 TextWrap::None => Wrap::None,
5044 });
5045 if style.ellipsis {
5046 let lines = line_budget(style).max(1);
5047 buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
5048 }
5049 buffer.set_size(None, None);
5050 buffer
5051}
5052
5053fn line_budget(style: &TextStyle) -> usize {
5056 if style.max_lines > 0 {
5057 style.max_lines as usize
5058 } else if style.ellipsis {
5059 1
5060 } else {
5061 0
5062 }
5063}
5064
5065fn line_cap(max_lines: usize) -> usize {
5066 if max_lines == 0 {
5067 usize::MAX
5068 } else {
5069 max_lines
5070 }
5071}
5072
5073pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
5079 let mut w = 0.0f32;
5080 let mut lines = 0u32;
5081 let lh = buffer.metrics().line_height;
5085 let (mut sum, mut uniform) = (0.0f32, true);
5086 for run in buffer.layout_runs().take(line_cap(max_lines)) {
5087 w = w.max(run.line_w);
5088 lines += 1;
5089 sum += run.line_height;
5090 uniform &= run.line_height == lh;
5091 }
5092 (
5093 Size::new(w, if uniform { lines as f32 * lh } else { sum }),
5094 lines,
5095 )
5096}
5097
5098impl TextSystem {
5099 pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
5100 let scale = self.scale;
5101 if let Some(line) = self.long_of(id) {
5102 return Size::new(line.width() / scale, line.line_h / scale);
5103 }
5104 let e = self.entry_mut(id);
5105 Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
5106 }
5107
5108 pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
5113 let scale = self.scale;
5114 if self.long_of(id).is_some() {
5115 return 0.0;
5116 }
5117 self.entry_mut(id).min_content() / scale
5118 }
5119
5120 fn long_of(&self, id: TextId) -> Option<&LongLine> {
5122 self.long(self.frame[id.0 as usize].cache_key)
5123 }
5124
5125 pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
5126 let scale = self.scale;
5127 if self.long_of(id).is_some() {
5128 let key = self.frame[id.0 as usize].cache_key;
5131 let (size, rows) = self.long_size(key, Some(max_w * scale));
5132 self.long_mut(key).expect("a long line").laid_rows = rows;
5133 return Size::new(size.w / scale, size.h / scale);
5134 }
5135 self.ensure_wrap(id, max_w, fs);
5136 let e = self.entry_mut(id);
5137 let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
5138 if e.clamp_w {
5139 m.w = m.w.min(max_w * scale);
5142 }
5143 Size::new(m.w / scale, m.h / scale)
5144 }
5145}
5146
5147impl TextSystem {
5148 pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
5153 let scale = self.scale;
5154 if let Some(line) = self.long_of(id) {
5155 return self.long_baseline(line) / scale;
5156 }
5157 let e = self.entry_mut(id);
5158 let b = e
5159 .buffer
5160 .layout_runs()
5161 .next()
5162 .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
5163 b / scale
5164 }
5165}
5166
5167#[cfg(test)]
5170mod default_families {
5171 use super::*;
5172 use cosmic_text::Family;
5173 use cosmic_text::fontdb::FaceInfo;
5174
5175 fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
5178 let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
5179 buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
5180 buffer.shape_until_scroll(fs, false);
5181 let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
5182 fs.db().face(id).cloned()
5183 }
5184
5185 #[test]
5186 fn mono_is_an_upright_monospaced_face() {
5187 let mut fs = new_font_system().0;
5188 if !fs.db().faces().any(|f| f.monospaced) {
5189 eprintln!("skipped: no monospaced face installed");
5190 return;
5191 }
5192 let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
5193 let mono = default_families(&fs)[2].to_string();
5194 assert_eq!(
5195 face.style,
5196 cosmic_text::Style::Normal,
5197 "Mono ({mono}) resolved to {:?}",
5198 face.post_script_name
5199 );
5200 assert!(
5201 face.monospaced,
5202 "Mono ({mono}) resolved to {:?}, which is not monospaced",
5203 face.post_script_name
5204 );
5205 }
5206
5207 #[test]
5208 fn sans_and_serif_are_upright_and_not_monospaced() {
5209 let mut fs = new_font_system().0;
5210 if fs.db().faces().next().is_none() {
5211 eprintln!("skipped: no face installed");
5212 return;
5213 }
5214 let [sans, serif, _] = default_families(&fs).map(str::to_string);
5215 for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
5216 let face = resolved(&mut fs, family).expect("M shapes");
5217 assert_eq!(
5218 face.style,
5219 cosmic_text::Style::Normal,
5220 "{name} resolved to {:?}",
5221 face.post_script_name
5222 );
5223 assert!(
5224 !face.monospaced,
5225 "{name} resolved to {:?}, which is monospaced",
5226 face.post_script_name
5227 );
5228 }
5229 }
5230
5231 #[test]
5234 fn pinned_names_are_installed_or_cosmic_texts_own() {
5235 let fs = new_font_system().0;
5236 let db = fs.db();
5237 for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
5238 let installed = db
5239 .faces()
5240 .any(|f| f.families.iter().any(|(fam, _)| fam == name));
5241 let on_list = list.contains(&name);
5242 assert!(
5243 installed == on_list,
5244 "{name}: installed {installed}, on the platform list {on_list}"
5245 );
5246 }
5247 }
5248}
5249
5250#[cfg(test)]
5257mod unmeasurable_faces {
5258 use super::*;
5259 use crate::testing::{font_face, han_face, unmeasurable_face};
5260 use cosmic_text::Family;
5261 use cosmic_text::fontdb::{Database, Source};
5262
5263 fn font_system() -> FontSystem {
5268 let mut db = Database::new();
5269 for bytes in [
5270 font_face("Kui Mono", 400, false, true),
5271 unmeasurable_face("Kui Bitmap"),
5272 han_face("Kui Han"),
5273 ] {
5274 db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
5275 }
5276 let mut fs = font_system_with("en-US".into(), db);
5277 fs.db_mut().set_monospace_family("Kui Mono");
5278 fs
5279 }
5280
5281 fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
5282 fs.db()
5283 .face(id)
5284 .map_or_else(String::new, |f| f.families[0].0.clone())
5285 }
5286
5287 #[test]
5288 fn a_face_without_metrics_is_not_in_the_database() {
5289 let fs = font_system();
5290 let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
5291 assert_eq!(families, ["Kui Mono", "Kui Han"]);
5292 }
5293
5294 #[test]
5298 fn han_in_mono_falls_back_to_a_face_that_measures() {
5299 let mut fs = font_system();
5300 let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
5301 let attrs = Attrs::new().family(Family::Monospace);
5302 buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
5303 buffer.shape_until_scroll(&mut fs, false);
5304 let run = buffer.layout_runs().next().expect("one line");
5305 let glyphs: Vec<(&str, f32, f32, String)> = run
5306 .glyphs
5307 .iter()
5308 .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
5309 .collect();
5310 for &(text, x, w, _) in &glyphs {
5311 assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
5312 }
5313 let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
5314 assert_eq!(
5315 faces,
5316 [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
5317 );
5318 let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
5320 assert_eq!(xs, [0.0, 7.0, 14.0]);
5321 for glyph in run.glyphs {
5322 glyph.physical((0.0, 0.0), 1.0);
5323 }
5324 }
5325}