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
281struct GlyphTemplate {
282 x: f32,
283 y: f32,
284 w: f32,
285 h: f32,
286 uv: [u32; 4],
287 kind: QuadKind,
288 color: Option<Color>,
290 byte: u32,
293}
294
295struct DecoTemplate {
297 x: f32,
298 y: f32,
299 w: f32,
300 h: f32,
301 color: Option<Color>,
304 under: bool,
306 style: UnderlineStyle,
309 radius: f32,
312}
313
314#[derive(Clone, Copy, Debug)]
323pub(crate) struct JoinBg {
324 pub quad: u32,
325 pub radius: f32,
326 pub outer: ClipId,
327 pub own: Option<(f32, f32)>,
328}
329
330#[derive(Clone, Copy, Default)]
332struct SpanDeco {
333 underline: bool,
334 underline_color: Option<Color>,
335 underline_style: UnderlineStyle,
336 strikethrough: bool,
337 bg: Option<Color>,
338 bg_radius: f32,
341}
342
343impl SpanDeco {
344 fn of_style(style: &TextStyle) -> Self {
345 Self {
346 underline: style.underline,
347 underline_color: style.underline_color,
348 underline_style: style.underline_style,
349 strikethrough: style.strikethrough,
350 bg: None,
351 bg_radius: 0.0,
352 }
353 }
354
355 fn any(&self) -> bool {
356 self.underline || self.strikethrough || self.bg.is_some()
357 }
358}
359
360#[derive(Clone, Copy, Debug)]
363pub struct Span<'a> {
364 pub text: &'a str,
365 pub color: Option<Color>,
366 pub bold: bool,
367 pub italic: bool,
368 pub underline: bool,
370 pub underline_color: Option<Color>,
372 pub underline_style: UnderlineStyle,
374 pub strikethrough: bool,
376 pub bg: Option<Color>,
379 pub bg_radius: f32,
386 pub family: Option<FontFamily>,
393 pub size: Option<f32>,
399}
400
401impl<'a> Span<'a> {
402 pub fn new(text: &'a str) -> Self {
403 Self {
404 text,
405 color: None,
406 bold: false,
407 italic: false,
408 underline: false,
409 underline_color: None,
410 underline_style: UnderlineStyle::Solid,
411 strikethrough: false,
412 bg: None,
413 bg_radius: 0.0,
414 family: None,
415 size: None,
416 }
417 }
418
419 pub fn family(mut self, family: FontFamily) -> Self {
421 self.family = Some(family);
422 self
423 }
424
425 pub fn mono(self) -> Self {
433 self.family(FontFamily::Mono)
434 }
435
436 pub fn size(mut self, px: f32) -> Self {
439 self.size = (px.is_finite() && px > 0.0).then_some(px);
440 self
441 }
442
443 pub fn color(mut self, c: Color) -> Self {
444 self.color = Some(c);
445 self
446 }
447
448 pub fn bold(mut self) -> Self {
449 self.bold = true;
450 self
451 }
452
453 pub fn italic(mut self) -> Self {
454 self.italic = true;
455 self
456 }
457
458 pub fn underline(mut self) -> Self {
459 self.underline = true;
460 self
461 }
462
463 pub fn underline_color(mut self, c: Color) -> Self {
465 self.underline = true;
466 self.underline_color = Some(c);
467 self
468 }
469
470 pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
472 self.underline = true;
473 self.underline_style = s;
474 self
475 }
476
477 pub fn strikethrough(mut self) -> Self {
478 self.strikethrough = true;
479 self
480 }
481
482 pub fn bg(mut self, c: Color) -> Self {
483 self.bg = Some(c);
484 self
485 }
486
487 pub fn bg_radius(mut self, r: f32) -> Self {
490 self.bg_radius = r.max(0.0);
491 self
492 }
493
494 fn attrs_only(&self) -> SpanAttrs {
497 SpanAttrs {
498 color: self.color,
499 bold: self.bold,
500 italic: self.italic,
501 underline: self.underline,
502 underline_color: self.underline_color,
503 underline_style: self.underline_style,
504 strikethrough: self.strikethrough,
505 bg: self.bg,
506 bg_radius: self.bg_radius,
507 family: self.family,
508 size: self.size,
509 }
510 }
511
512 fn attrs<'r>(
513 &self,
514 family: cosmic_text::Family<'r>,
515 weights: crate::weights::Weights,
516 ) -> Attrs<'r> {
517 let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
521 if self.italic {
522 attrs = attrs.style(FontStyle::Italic);
523 }
524 if let Some(c) = self.color {
525 attrs = attrs.color(cosmic_text::Color::rgba(
526 (c.r * 255.0) as u8,
527 (c.g * 255.0) as u8,
528 (c.b * 255.0) as u8,
529 (c.a * 255.0) as u8,
530 ));
531 }
532 attrs
533 }
534}
535
536#[derive(Clone, Copy)]
540struct SpanAttrs {
541 color: Option<Color>,
542 bold: bool,
543 italic: bool,
544 underline: bool,
545 underline_color: Option<Color>,
546 underline_style: UnderlineStyle,
547 strikethrough: bool,
548 bg: Option<Color>,
549 bg_radius: f32,
550 family: Option<FontFamily>,
551 size: Option<f32>,
552}
553
554impl SpanAttrs {
555 fn span<'a>(&self, text: &'a str) -> Span<'a> {
556 Span {
557 text,
558 color: self.color,
559 bold: self.bold,
560 italic: self.italic,
561 underline: self.underline,
562 underline_color: self.underline_color,
563 underline_style: self.underline_style,
564 strikethrough: self.strikethrough,
565 bg: self.bg,
566 bg_radius: self.bg_radius,
567 family: self.family,
568 size: self.size,
569 }
570 }
571}
572
573#[derive(Clone)]
576struct OwnedSpan {
577 start: usize,
578 end: usize,
579 attrs: SpanAttrs,
580}
581
582fn chunk_spans<'a>(
587 content: &'a str,
588 spans: &[OwnedSpan],
589 start: usize,
590 end: usize,
591) -> Vec<Span<'a>> {
592 let first = spans.partition_point(|s| s.end <= start);
593 spans[first..]
594 .iter()
595 .take_while(|s| s.start < end)
596 .filter_map(|s| {
597 let (a, b) = (s.start.max(start), s.end.min(end));
598 (a < b).then(|| s.attrs.span(&content[a..b]))
599 })
600 .collect()
601}
602
603struct FrameText {
604 cache_key: u64,
605 color: Color,
606}
607
608pub(crate) enum Entry {
616 Run(CachedText),
617 Long(LongLine),
618}
619
620impl Entry {
621 pub(crate) fn content(&self) -> &str {
622 match self {
623 Entry::Run(e) => &e.content,
624 Entry::Long(l) => &l.content,
625 }
626 }
627
628 fn last_used(&self) -> u64 {
629 match self {
630 Entry::Run(e) => e.last_used,
631 Entry::Long(l) => l.last_used,
632 }
633 }
634
635 fn touch(&mut self, frame_no: u64) {
636 match self {
637 Entry::Run(e) => e.last_used = frame_no,
638 Entry::Long(l) => l.last_used = frame_no,
639 }
640 }
641
642 fn bytes(&self) -> usize {
644 match self {
645 Entry::Run(e) => e.bytes,
646 Entry::Long(l) => l.bytes,
647 }
648 }
649
650 fn run(&self) -> Option<&CachedText> {
651 match self {
652 Entry::Run(e) => Some(e),
653 Entry::Long(_) => None,
654 }
655 }
656
657 fn run_mut(&mut self) -> Option<&mut CachedText> {
658 match self {
659 Entry::Run(e) => Some(e),
660 Entry::Long(_) => None,
661 }
662 }
663
664 fn long(&self) -> Option<&LongLine> {
665 match self {
666 Entry::Long(l) => Some(l),
667 Entry::Run(_) => None,
668 }
669 }
670
671 fn long_mut(&mut self) -> Option<&mut LongLine> {
672 match self {
673 Entry::Long(l) => Some(l),
674 Entry::Run(_) => None,
675 }
676 }
677}
678
679#[derive(Clone)]
683struct Chunk {
684 start: usize,
685 end: usize,
686 key: u64,
687 width: Option<f32>,
689 tab: Option<(f32, f32)>,
695 rows: Vec<RowStart>,
698}
699
700#[derive(Clone, Copy, Debug)]
704struct RowStart {
705 byte: u32,
706 x: f32,
707}
708
709#[derive(Clone)]
722pub(crate) struct LongLine {
723 content: String,
724 spans: Vec<OwnedSpan>,
728 style: TextStyle,
731 wrap: TextWrap,
737 wrap_w: Option<f32>,
740 starts: Vec<(u32, f32)>,
745 chunks: Vec<Chunk>,
746 prefix: Vec<f32>,
749 line_h: f32,
751 avg: f32,
754 last_used: u64,
755 claimed: u64,
758 laid_rows: u32,
765 asked_rows: u32,
766 bytes: usize,
767}
768
769impl LongLine {
770 fn chunk_at(&self, x: f32) -> usize {
772 match self.prefix[1..].partition_point(|&p| p <= x) {
773 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
774 i => i,
775 }
776 }
777
778 fn chunk_of_byte(&self, byte: usize) -> usize {
780 match self.chunks.partition_point(|c| c.end <= byte) {
781 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
782 i => i,
783 }
784 }
785
786 fn width(&self) -> f32 {
787 self.prefix.last().copied().unwrap_or(0.0)
788 }
789
790 fn rows(&self) -> u32 {
792 match self.starts.last() {
793 Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
794 _ => 1,
795 }
796 }
797
798 fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
801 let n = self.chunks.len();
802 if n == 0 || self.starts.len() != n + 1 {
803 return None;
804 }
805 let first = self.starts[1..].partition_point(|s| s.0 < ra);
806 let last = self.starts[..n].partition_point(|s| s.0 <= rb);
807 (first < last).then_some((first, last - 1))
808 }
809
810 fn reprefix(&mut self) {
817 let mut at = 0.0f32;
818 self.prefix.clear();
819 self.prefix.push(0.0);
820 for c in &self.chunks {
821 at = match (c.width, c.tab) {
822 (Some(_), Some((tab_x, every))) => next_tab_stop(at + tab_x, every),
823 (Some(w), None) => at + w,
824 (None, _) => at + (c.end - c.start) as f32 * self.avg,
825 };
826 self.prefix.push(at);
827 }
828 }
829
830 fn tab_shift(&self, i: usize) -> f32 {
833 let c = &self.chunks[i];
834 match (c.width, c.tab) {
835 (Some(w), Some(_)) => self.prefix[i + 1] - self.prefix[i] - w,
836 _ => 0.0,
837 }
838 }
839
840 fn end_shift(&self, i: usize, local: usize) -> f32 {
845 let c = &self.chunks[i];
846 match local >= c.end - c.start {
847 true => self.tab_shift(i),
848 false => 0.0,
849 }
850 }
851
852 fn placed_tab_hit(&self, i: usize, x: f32) -> Option<usize> {
859 let c = &self.chunks[i];
860 let (tab_x, _) = c.tab?;
861 c.width?;
862 if x < tab_x {
863 return None;
864 }
865 let drawn_end = self.prefix[i + 1] - self.prefix[i];
866 Some(match x < (tab_x + drawn_end) / 2.0 {
867 true => c.end - 1,
868 false => c.end,
869 })
870 }
871}
872
873fn next_tab_stop(x: f32, every: f32) -> f32 {
879 if every <= 0.0 {
880 return x;
881 }
882 (((x + 1.0 / 16.0) / every).floor() + 1.0) * every
883}
884
885fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
902 use unicode_segmentation::UnicodeSegmentation;
903 if content.len() < LONG_LINE_BYTES {
904 return vec![(0, content.len())];
905 }
906 let bytes = content.as_bytes();
907 let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
908 let mut start = 0usize;
909 while start < content.len() {
910 let mut window_end = (start + CHUNK_BYTES).min(content.len());
914 while !content.is_char_boundary(window_end) {
915 window_end -= 1;
916 }
917 let window = &content[start..window_end];
918 let mut from = (CHUNK_BYTES / 2).min(window.len());
920 while !window.is_char_boundary(from) {
921 from += 1;
922 }
923 let off_stops = start > 0 && bytes[start - 1] != b'\t';
927 let early_tab = match off_stops {
928 true => window[..from].find('\t'),
929 false => None,
930 };
931 let end = if let Some(t) = early_tab {
932 start + t + 1
933 } else if window_end == content.len() && !(off_stops && window[from..].contains('\t')) {
934 window_end
935 } else {
936 let (mut first_tab, mut last_tab, mut space) = (None, None, None);
940 for (i, c) in window[from..].char_indices() {
941 if c.is_whitespace() {
942 let end = from + i + c.len_utf8();
943 space = Some(end);
944 if c == '\t' {
945 first_tab = first_tab.or(Some(end));
946 last_tab = Some(end);
947 }
948 }
949 }
950 let tab = if off_stops { first_tab } else { last_tab };
951 match tab.or(space) {
952 Some(i) => start + i,
953 None => {
954 let mut cut = 0usize;
956 for (i, _) in window.grapheme_indices(true) {
957 if i == 0 {
958 continue;
959 }
960 cut = i;
961 }
962 if cut == 0 { window_end } else { start + cut }
963 }
964 }
965 };
966 out.push((start, end));
967 start = end;
968 }
969 out
970}
971
972pub(crate) const PLACE_ANCESTORS: usize = 4;
977
978pub(crate) struct Ancestry {
982 pub(crate) keys: [Key; PLACE_ANCESTORS],
983 pub(crate) depth: usize,
984 pub(crate) none_at: Option<usize>,
985}
986
987pub(crate) struct TextPlace {
992 pub(crate) key: Key,
993 ancestors: [Key; PLACE_ANCESTORS],
995 depth: u8,
996 none_at: u8,
1002 cache_key: u64,
1003 origin: Vec2,
1005 scope: Option<Key>,
1009 drawn: bool,
1017}
1018
1019impl TextPlace {
1020 fn answers_to(&self, key: Key) -> bool {
1021 if self.key == key {
1022 return true;
1023 }
1024 self.ancestors[..self.depth as usize]
1028 .iter()
1029 .position(|k| *k == key)
1030 .is_some_and(|d| d <= self.none_at as usize)
1031 }
1032}
1033
1034pub(crate) struct ScopeRun<'a> {
1040 pub place: &'a TextPlace,
1041 pub text: &'a Entry,
1043 pub base: usize,
1044}
1045
1046impl ScopeRun<'_> {
1047 pub(crate) fn span(&self) -> (usize, usize) {
1050 (self.base, self.base + self.text.content().len())
1051 }
1052}
1053
1054#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1067pub struct TextHit {
1068 pub byte: usize,
1069 pub line: u32,
1070}
1071
1072impl TextHit {
1073 pub fn to_value(self) -> Value {
1075 Value::map([
1076 ("byte", Value::Int(self.byte as i64)),
1077 ("line", Value::Int(self.line as i64)),
1078 ])
1079 }
1080}
1081
1082#[derive(Clone, Copy, Debug, Default, PartialEq)]
1087pub struct TextMetrics {
1088 pub width: f32,
1089 pub height: f32,
1090 pub lines: u32,
1092}
1093
1094impl TextMetrics {
1095 pub fn to_value(self) -> Value {
1097 Value::map([
1098 ("width", Value::float(self.width)),
1099 ("height", Value::float(self.height)),
1100 ("lines", Value::Int(self.lines as i64)),
1101 ])
1102 }
1103}
1104
1105pub struct TextSystem {
1113 raster: Raster,
1114 joins: Vec<JoinBg>,
1117 entries: FxHashMap<u64, Entry>,
1120 bytes: usize,
1123 budget: usize,
1124 frame: Kept<FrameText>,
1129 places: Kept<TextPlace>,
1133 scale: f32,
1134 frame_no: u64,
1135 owed: bool,
1138}
1139
1140pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1143 let mut out = cosmic_text::FontFeatures::new();
1144 for (tag, value) in f.iter() {
1145 out.set(cosmic_text::FeatureTag::new(tag), value);
1146 }
1147 out
1148}
1149
1150#[cfg(target_os = "macos")]
1161const DEFAULT_FAMILIES: [&[&str]; 3] = [
1162 &["Helvetica Neue", "Helvetica"],
1163 &["Times New Roman", "Times"],
1164 &["SF Mono", "Menlo", "Monaco"],
1165];
1166#[cfg(target_os = "windows")]
1167const DEFAULT_FAMILIES: [&[&str]; 3] = [
1168 &["Segoe UI", "Arial"],
1169 &["Times New Roman"],
1170 &["Cascadia Mono", "Consolas", "Courier New"],
1171];
1172#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1173const DEFAULT_FAMILIES: [&[&str]; 3] = [
1174 &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1175 &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1176 &[
1177 "DejaVu Sans Mono",
1178 "Noto Sans Mono",
1179 "Liberation Mono",
1180 "Ubuntu Mono",
1181 ],
1182];
1183
1184fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1187 list.iter().copied().find(|name| {
1188 db.faces()
1189 .any(|face| face.families.iter().any(|(f, _)| f == name))
1190 })
1191}
1192
1193pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1207 let system = system_fonts();
1208 let fonts = font_system_over(system.locale.clone(), system.db.clone(), &[]);
1209 (fonts, system)
1210}
1211
1212pub(crate) fn font_system_over(
1217 locale: String,
1218 db: cosmic_text::fontdb::Database,
1219 first: &[String],
1220) -> FontSystem {
1221 FontSystem::new_with_locale_and_db_and_fallback(locale, db, AppFallback::new(first))
1222}
1223
1224pub(crate) struct AppFallback {
1231 platform: cosmic_text::PlatformFallback,
1232 first: &'static [&'static str],
1233 common: &'static [&'static str],
1234 scripts: std::sync::Mutex<
1237 rustc_hash::FxHashMap<(unicode_script::Script, String), &'static [&'static str]>,
1238 >,
1239}
1240
1241fn keep(names: Vec<&'static str>) -> &'static [&'static str] {
1244 static LISTS: std::sync::Mutex<Vec<&'static [&'static str]>> =
1245 std::sync::Mutex::new(Vec::new());
1246 let mut lists = LISTS.lock().unwrap_or_else(|e| e.into_inner());
1247 if let Some(&kept) = lists.iter().find(|l| ***l == names[..]) {
1248 return kept;
1249 }
1250 let kept: &'static [&'static str] = Box::leak(names.into_boxed_slice());
1251 lists.push(kept);
1252 kept
1253}
1254
1255fn keep_name(name: &str) -> &'static str {
1256 static NAMES: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
1257 let mut names = NAMES.lock().unwrap_or_else(|e| e.into_inner());
1258 if let Some(&kept) = names.iter().find(|n| **n == name) {
1259 return kept;
1260 }
1261 let kept: &'static str = Box::leak(name.to_string().into_boxed_str());
1262 names.push(kept);
1263 kept
1264}
1265
1266impl AppFallback {
1267 pub(crate) fn new(first: &[String]) -> Self {
1268 use cosmic_text::Fallback;
1269 let platform = cosmic_text::PlatformFallback;
1270 let first = keep(first.iter().map(|n| keep_name(n)).collect());
1271 let common = keep(
1272 first
1273 .iter()
1274 .chain(platform.common_fallback())
1275 .copied()
1276 .collect(),
1277 );
1278 Self {
1279 platform,
1280 first,
1281 common,
1282 scripts: Default::default(),
1283 }
1284 }
1285}
1286
1287impl cosmic_text::Fallback for AppFallback {
1288 fn common_fallback(&self) -> &[&'static str] {
1289 self.common
1290 }
1291
1292 fn forbidden_fallback(&self) -> &[&'static str] {
1293 self.platform.forbidden_fallback()
1294 }
1295
1296 fn script_fallback(&self, script: unicode_script::Script, locale: &str) -> &[&'static str] {
1297 let theirs = self.platform.script_fallback(script, locale);
1298 if self.first.is_empty() {
1299 return theirs;
1300 }
1301 let mut scripts = self.scripts.lock().unwrap_or_else(|e| e.into_inner());
1302 scripts
1303 .entry((script, locale.to_string()))
1304 .or_insert_with(|| keep(self.first.iter().chain(theirs).copied().collect()))
1305 }
1306}
1307
1308pub(crate) struct SystemFonts {
1312 locale: String,
1313 db: cosmic_text::fontdb::Database,
1314 pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1316}
1317
1318static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1320
1321pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1325 let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1326 held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1327 .clone()
1328}
1329
1330pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1333 let fresh = std::sync::Arc::new(scan_system_fonts());
1334 *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1335 fresh
1336}
1337
1338fn scan_system_fonts() -> SystemFonts {
1339 let (locale, db) = FontSystem::new().into_locale_and_db();
1340 SystemFonts::from_db(locale, db)
1341}
1342
1343impl SystemFonts {
1344 pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1347 let all: Vec<_> = db.faces().map(|face| face.id).collect();
1348 keep_measurable(&mut db, all);
1349 pin_default_families(&mut db);
1350 let faces = db.faces().filter_map(face_file).collect();
1351 Self { locale, db, faces }
1352 }
1353
1354 #[cfg(test)]
1356 pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1357 &self.db
1358 }
1359
1360 #[cfg(test)]
1361 pub(crate) fn locale(&self) -> &str {
1362 &self.locale
1363 }
1364}
1365
1366pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1369 use cosmic_text::fontdb::Source;
1370 match &face.source {
1371 Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1372 Source::Binary(_) => None,
1373 }
1374}
1375
1376pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1380 let [sans, serif, mono] = DEFAULT_FAMILIES;
1381 if let Some(name) = first_installed(db, sans) {
1382 db.set_sans_serif_family(name);
1383 }
1384 if let Some(name) = first_installed(db, serif) {
1385 db.set_serif_family(name);
1386 }
1387 if let Some(name) = first_installed(db, mono) {
1388 db.set_monospace_family(name);
1389 }
1390}
1391
1392#[cfg(test)]
1396fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1397 let system = SystemFonts::from_db(locale, db);
1398 FontSystem::new_with_locale_and_db(system.locale, system.db)
1399}
1400
1401pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1412 use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1413 let Ok(font) = FontRef::from_index(data, index) else {
1414 return false;
1415 };
1416 font.head()
1417 .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1418 && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1419 && font.hmtx().is_ok()
1420}
1421
1422const FILES_PER_CHECKER: usize = 64;
1425
1426pub(crate) fn keep_measurable(
1438 db: &mut cosmic_text::fontdb::Database,
1439 ids: Vec<cosmic_text::fontdb::ID>,
1440) -> Vec<cosmic_text::fontdb::ID> {
1441 use cosmic_text::fontdb::{ID, Source};
1442 let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1444 let mut groups = Vec::new();
1445 for &id in &ids {
1446 match db.face(id).map(|face| &face.source) {
1447 Some(Source::File(path) | Source::SharedFile(path, _)) => {
1448 by_file.entry(path).or_default().push(id);
1449 }
1450 Some(Source::Binary(_)) => groups.push(vec![id]),
1451 None => {}
1452 }
1453 }
1454 groups.extend(by_file.into_values());
1455 let db_ref = &*db;
1456 let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1457 let mut out = Vec::new();
1458 for group in groups {
1459 let read = db_ref.with_face_data(group[0], |data, _| {
1460 group
1461 .iter()
1462 .filter(|&&id| {
1463 db_ref
1464 .face(id)
1465 .is_none_or(|face| !measurable(data, face.index))
1466 })
1467 .copied()
1468 .collect::<Vec<_>>()
1469 });
1470 out.extend(read.unwrap_or_else(|| group.clone()));
1471 }
1472 out
1473 };
1474 let checkers = std::thread::available_parallelism()
1475 .map_or(1, |n| n.get())
1476 .min(groups.len().div_ceil(FILES_PER_CHECKER));
1477 let unmeasurable = if checkers <= 1 {
1478 unmeasurable_in(&groups)
1479 } else {
1480 let per = groups.len().div_ceil(checkers);
1481 std::thread::scope(|scope| {
1482 let parts: Vec<_> = groups
1483 .chunks(per)
1484 .map(|part| {
1485 let spawned = std::thread::Builder::new()
1486 .name("kui-font-check".into())
1487 .spawn_scoped(scope, move || unmeasurable_in(part));
1488 (part, spawned.ok())
1489 })
1490 .collect();
1491 let mut out = Vec::new();
1492 for (part, checker) in parts {
1493 out.extend(match checker {
1494 Some(checker) => checker
1495 .join()
1496 .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1497 None => unmeasurable_in(part),
1499 });
1500 }
1501 out
1502 })
1503 };
1504 for &id in &unmeasurable {
1505 db.remove_face(id);
1506 }
1507 ids.into_iter()
1508 .filter(|id| !unmeasurable.contains(id))
1509 .collect()
1510}
1511
1512pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1516 let db = fs.db();
1517 [
1518 db.family_name(&cosmic_text::Family::SansSerif),
1519 db.family_name(&cosmic_text::Family::Serif),
1520 db.family_name(&cosmic_text::Family::Monospace),
1521 ]
1522}
1523
1524impl TextSystem {
1525 pub fn new() -> Self {
1526 Self {
1527 raster: Raster::new(),
1528 joins: Vec::new(),
1529 entries: FxHashMap::default(),
1530 bytes: 0,
1531 budget: DEFAULT_TEXT_CACHE_BYTES,
1532 frame: Kept::default(),
1533 places: Kept::default(),
1534 scale: 1.0,
1535 frame_no: 0,
1536 owed: false,
1537 }
1538 }
1539
1540 pub(crate) fn take_owed(&mut self) -> bool {
1543 std::mem::take(&mut self.owed)
1544 }
1545
1546 pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1549 std::mem::take(&mut self.joins)
1550 }
1551
1552 pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1554 &mut self.raster
1555 }
1556
1557 pub(crate) fn subpixel(&self) -> bool {
1558 self.raster.subpixel
1559 }
1560
1561 pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1564 let changed = self.raster.subpixel != on;
1565 self.raster.subpixel = on;
1566 changed
1567 }
1568
1569 pub fn budget(&self) -> usize {
1572 self.budget
1573 }
1574
1575 pub fn set_budget(&mut self, bytes: usize) {
1578 self.budget = bytes;
1579 }
1580
1581 pub fn bytes(&self) -> usize {
1583 self.bytes
1584 }
1585
1586 pub fn len(&self) -> usize {
1589 self.entries.len() - self.long_lines()
1590 }
1591
1592 pub fn long_lines(&self) -> usize {
1594 self.entries.values().filter(|e| e.long().is_some()).count()
1595 }
1596
1597 pub fn is_empty(&self) -> bool {
1598 self.len() == 0
1599 }
1600
1601 fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1608 if self.bytes <= self.budget {
1609 return;
1610 }
1611 let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1612 let drawn_last_frame = self.frame_no.saturating_sub(1);
1613 let mut order: Vec<(u64, u64)> = self
1618 .entries
1619 .iter()
1620 .filter(|(_, e)| e.last_used() < drawn_last_frame)
1621 .map(|(k, e)| (e.last_used(), *k))
1622 .collect();
1623 order.sort_unstable();
1624 for (_, key) in order {
1625 if self.bytes <= floor {
1626 break;
1627 }
1628 let freed = self.entries.remove(&key).map(|e| e.bytes());
1629 self.bytes -= freed.unwrap_or(0);
1630 }
1631 fs.shape_run_cache.trim(0);
1640 }
1641
1642 pub(crate) fn begin_frame(
1646 &mut self,
1647 fs: &mut FontSystem,
1648 scale: f32,
1649 keep_prev: bool,
1650 frame_no: u64,
1651 ) {
1652 self.joins.clear();
1653 if (scale - self.scale).abs() > f32::EPSILON {
1655 self.entries.clear();
1656 self.bytes = 0;
1657 }
1658 self.scale = scale;
1659 self.frame.begin(keep_prev);
1660 self.places.begin(true);
1663 self.frame_no = frame_no;
1665 if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1666 let mut freed = 0usize;
1667 self.entries.retain(|_, e| {
1668 let keep = e.last_used() >= cutoff;
1669 if !keep {
1670 freed += e.bytes();
1671 }
1672 keep
1673 });
1674 self.bytes -= freed;
1675 fs.shape_run_cache.trim(2);
1678 }
1679 self.evict_to_budget(fs);
1683 }
1684
1685 pub(crate) fn forget_shaped(&mut self) {
1688 self.entries.clear();
1689 self.bytes = 0;
1690 }
1691
1692 fn insert(&mut self, key: u64, entry: Entry) {
1694 self.bytes += entry.bytes();
1695 if let Some(old) = self.entries.insert(key, entry) {
1696 self.bytes -= old.bytes();
1697 }
1698 }
1699
1700 fn run(&self, key: u64) -> Option<&CachedText> {
1702 self.entries.get(&key)?.run()
1703 }
1704
1705 fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1706 self.entries.get_mut(&key)?.run_mut()
1707 }
1708
1709 fn long(&self, key: u64) -> Option<&LongLine> {
1711 self.entries.get(&key)?.long()
1712 }
1713
1714 fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1715 self.entries.get_mut(&key)?.long_mut()
1716 }
1717
1718 pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1719 let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1722 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1723 mix(&style.size.to_bits().to_le_bytes());
1724 mix(&style.line_height.to_bits().to_le_bytes());
1725 mix(&scale.to_bits().to_le_bytes());
1726 mix(&[style.wrap as u8, style.ellipsis as u8]);
1727 mix(&style.max_lines.to_le_bytes());
1728 let (tag, font) = match style.family {
1729 FontFamily::Sans => (0u8, 0u64),
1730 FontFamily::Serif => (1, 0),
1731 FontFamily::Mono => (2, 0),
1732 FontFamily::Custom(id) => (3, id.to_ffi()),
1733 };
1734 mix(&[tag]);
1735 mix(&font.to_le_bytes());
1736 for (t, v) in style.features.iter() {
1737 mix(t);
1738 mix(&v.to_le_bytes());
1739 }
1740 mix(&[style.bold as u8]);
1745 mix(&[
1746 style.underline as u8,
1747 style.strikethrough as u8,
1748 style.underline_style as u8,
1749 style.underline_color.is_some() as u8,
1750 ]);
1751 if let Some(c) = style.underline_color {
1752 mix(&c.to_hex().to_le_bytes());
1753 }
1754 h
1755 }
1756
1757 fn intern(
1761 &mut self,
1762 content: &str,
1763 style: &TextStyle,
1764 res: &Resources,
1765 fs: &mut FontSystem,
1766 ) -> u64 {
1767 let key = Self::style_key(content, style, self.scale);
1768 self.intern_keyed(key, content, style, res, fs)
1769 }
1770
1771 fn intern_keyed(
1773 &mut self,
1774 key: u64,
1775 content: &str,
1776 style: &TextStyle,
1777 res: &Resources,
1778 fs: &mut FontSystem,
1779 ) -> u64 {
1780 let frame_no = self.frame_no;
1781 let scale = self.scale;
1782 if !self.entries.contains_key(&key) {
1783 let mut buffer = new_buffer(fs, style, scale);
1784 buffer.set_text(
1788 content,
1789 &res.weights_of(style.family).apply(
1790 Attrs::new()
1791 .family(res.family_of(style.family))
1792 .font_features(cosmic_features(&style.features)),
1793 style.bold,
1794 ),
1795 Shaping::Advanced,
1796 None,
1797 );
1798 let entry = CachedText::new(
1799 buffer,
1800 content.to_string(),
1801 style,
1802 vec![SpanDeco::of_style(style)],
1803 fs,
1804 frame_no,
1805 );
1806 self.insert(key, Entry::Run(entry));
1807 }
1808 self.entries
1809 .get_mut(&key)
1810 .expect("just inserted")
1811 .touch(frame_no);
1812 key
1813 }
1814
1815 pub(crate) fn content(&self, id: TextId) -> &str {
1817 let key = self.frame[id.0 as usize].cache_key;
1818 self.entries.get(&key).map_or("", |e| e.content())
1819 }
1820
1821 pub(crate) fn access_runs(
1827 &self,
1828 id: TextId,
1829 src: crate::access::RunSource,
1830 run_no: &mut usize,
1831 out: &mut Vec<crate::access::AccessRun>,
1832 ) {
1833 use crate::access::{RowGlyphs, push_row_runs};
1834 let Some(ft) = self.frame.get(id.0 as usize) else {
1835 return;
1836 };
1837 let line = match self.entries.get(&ft.cache_key) {
1838 Some(Entry::Run(e)) => {
1839 crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1840 return;
1841 }
1842 Some(Entry::Long(line)) => line,
1843 None => return,
1844 };
1845 let whole = [RowStart { byte: 0, x: 0.0 }];
1846 for (i, c) in line.chunks.iter().enumerate() {
1847 let Some(run) = c
1848 .width
1849 .and_then(|_| self.run(c.key))
1850 .and_then(|e| e.buffer.layout_runs().next())
1851 else {
1852 continue;
1853 };
1854 let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1857 (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1858 (&c.rows[..], row0, head_x)
1859 }
1860 (Some(_), _) => continue,
1861 (None, _) => (&whole[..], 0, line.prefix[i]),
1862 };
1863 for (r, rs) in rows.iter().enumerate() {
1864 let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1865 let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1866 let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1867 if a == b {
1868 continue;
1869 }
1870 let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1871 push_row_runs(
1872 &RowGlyphs {
1873 glyphs: &run.glyphs[a..b],
1874 text: run.text,
1875 line: 0,
1876 top: (row0 as usize + r) as f32 * line.line_h,
1877 height: line.line_h,
1878 rtl: run.rtl,
1879 dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1880 base: c.start,
1881 newline: last && src.newline_after_last,
1882 },
1883 &src,
1884 run_no,
1885 out,
1886 );
1887 }
1888 }
1889 }
1890
1891 pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1897 match self.frame.prev().get(id.0 as usize) {
1898 Some(t) => (t.cache_key, t.color),
1899 None => (0, Color::TRANSPARENT),
1900 }
1901 }
1902
1903 pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1909 let frame_no = self.frame_no;
1910 let e = self.entries.get_mut(&cache_key)?;
1911 e.touch(frame_no);
1912 let cache_key = if e.long().is_some() {
1913 self.claim_long(cache_key)
1914 } else {
1915 cache_key
1916 };
1917 self.frame.push(FrameText { cache_key, color });
1918 Some(TextId((self.frame.len() - 1) as u32))
1919 }
1920
1921 pub fn add(
1923 &mut self,
1924 content: &str,
1925 style: &TextStyle,
1926 res: &Resources,
1927 fs: &mut FontSystem,
1928 ) -> TextId {
1929 let key = self.intern_any(content, style, res, fs);
1932 let key = if could_be_long(content.len(), style) {
1933 self.claim_long(key)
1934 } else {
1935 key
1936 };
1937 self.frame.push(FrameText {
1938 cache_key: key,
1939 color: style.color_or_default(),
1940 });
1941 TextId((self.frame.len() - 1) as u32)
1942 }
1943
1944 #[inline(never)]
1953 fn claim_long(&mut self, key: u64) -> u64 {
1954 let frame_no = self.frame_no;
1955 let mut at = key;
1956 let mut n = 0u64;
1957 loop {
1958 match self.entries.get_mut(&at) {
1959 Some(Entry::Long(l)) if l.claimed != frame_no => {
1960 l.claimed = frame_no;
1961 l.last_used = frame_no;
1962 return at;
1963 }
1964 Some(Entry::Run(_)) if n == 0 => return key,
1966 Some(_) => {}
1967 None => {
1968 let Some(line) = self.long(key) else {
1969 return key;
1970 };
1971 let mut copy = line.clone();
1972 copy.claimed = frame_no;
1973 copy.last_used = frame_no;
1974 self.insert(at, Entry::Long(copy));
1975 return at;
1976 }
1977 }
1978 n += 1;
1979 at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
1980 }
1981 }
1982
1983 fn intern_any(
1989 &mut self,
1990 content: &str,
1991 style: &TextStyle,
1992 res: &Resources,
1993 fs: &mut FontSystem,
1994 ) -> u64 {
1995 if !could_be_long(content.len(), style) {
1996 return self.intern(content, style, res, fs);
1997 }
1998 let key = Self::style_key(content, style, self.scale);
1999 let long_key = key ^ LONG_SALT;
2000 let frame_no = self.frame_no;
2001 if let Some(e) = self.entries.get_mut(&long_key) {
2002 e.touch(frame_no);
2003 return long_key;
2004 }
2005 if let Some(e) = self.entries.get_mut(&key) {
2006 e.touch(frame_no);
2007 return key;
2008 }
2009 if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
2010 self.intern_keyed(key, content, style, res, fs)
2011 } else {
2012 self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
2013 }
2014 }
2015
2016 fn intern_rich_any(
2021 &mut self,
2022 spans: &[Span<'_>],
2023 base: &TextStyle,
2024 res: &Resources,
2025 fs: &mut FontSystem,
2026 ) -> u64 {
2027 let len: usize = spans.iter().map(|s| s.text.len()).sum();
2028 if !could_be_long(len, base) {
2029 return self.intern_rich(spans, base, res, fs);
2030 }
2031 let key = Self::rich_key(spans, base, self.scale);
2032 let long_key = key ^ LONG_SALT;
2033 let frame_no = self.frame_no;
2034 if let Some(e) = self.entries.get_mut(&long_key) {
2035 e.touch(frame_no);
2036 return long_key;
2037 }
2038 if let Some(e) = self.entries.get_mut(&key) {
2039 e.touch(frame_no);
2040 return key;
2041 }
2042 if spans
2045 .iter()
2046 .any(|s| has_line_break(s.text) || s.size.is_some())
2047 || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
2048 {
2049 return self.intern_rich_keyed(key, spans, base, res, fs);
2050 }
2051 let mut content = String::with_capacity(len);
2052 let mut owned = Vec::with_capacity(spans.len());
2053 for s in spans {
2054 let start = content.len();
2055 content.push_str(s.text);
2056 owned.push(OwnedSpan {
2057 start,
2058 end: content.len(),
2059 attrs: s.attrs_only(),
2060 });
2061 }
2062 self.build_long(long_key, content, owned, base, res, fs)
2063 }
2064
2065 fn build_long(
2070 &mut self,
2071 key: u64,
2072 content: String,
2073 spans: Vec<OwnedSpan>,
2074 style: &TextStyle,
2075 res: &Resources,
2076 fs: &mut FontSystem,
2077 ) -> u64 {
2078 let frame_no = self.frame_no;
2079 let wrap = style.wrap;
2080 let style = &TextStyle {
2081 wrap: TextWrap::None,
2082 ..*style
2083 };
2084 let scale = self.scale;
2085 let chunks: Vec<Chunk> = chunk_ranges(&content)
2086 .into_iter()
2087 .map(|(start, end)| Chunk {
2088 start,
2089 end,
2090 key: if spans.is_empty() {
2091 Self::style_key(&content[start..end], style, scale)
2092 } else {
2093 Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
2094 },
2095 width: None,
2096 tab: None,
2097 rows: Vec::new(),
2098 })
2099 .collect();
2100 let (w0, line_h) = match chunks.first() {
2103 Some(c) => {
2104 let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
2105 let e = self.run(k).expect("just interned");
2106 (e.intrinsic.w, e.buffer.metrics().line_height)
2107 }
2108 None => (0.0, style.line_height * self.scale),
2109 };
2110 let avg = chunks
2111 .first()
2112 .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
2113 let mut line = LongLine {
2114 content,
2115 spans,
2116 style: *style,
2117 wrap,
2118 wrap_w: None,
2119 starts: Vec::new(),
2120 chunks,
2121 prefix: Vec::new(),
2122 line_h,
2123 avg,
2124 last_used: frame_no,
2125 claimed: u64::MAX,
2126 laid_rows: 1,
2127 asked_rows: u32::MAX,
2128 bytes: 0,
2129 };
2130 if let Some(c) = line.chunks.first_mut() {
2131 c.width = Some(w0);
2132 }
2133 line.reprefix();
2134 line.bytes = ENTRY_BASE_BYTES
2135 + line.content.len()
2136 + line.chunks.len() * 48
2137 + line.spans.len() * std::mem::size_of::<OwnedSpan>();
2138 self.insert(key, Entry::Long(line));
2142 key
2143 }
2144
2145 #[allow(clippy::too_many_arguments)]
2149 fn shape_chunk(
2150 &mut self,
2151 content: &str,
2152 spans: &[OwnedSpan],
2153 start: usize,
2154 end: usize,
2155 style: &TextStyle,
2156 res: &Resources,
2157 fs: &mut FontSystem,
2158 ) -> u64 {
2159 if spans.is_empty() {
2160 self.intern(&content[start..end], style, res, fs)
2161 } else {
2162 let spans = chunk_spans(content, spans, start, end);
2163 self.intern_rich(&spans, style, res, fs)
2164 }
2165 }
2166
2167 fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
2171 let (chunk_key, known) = {
2172 let c = &self.long(key).expect("a long line").chunks[i];
2173 (c.key, c.width)
2174 };
2175 let frame_no = self.frame_no;
2176 if known.is_some()
2177 && let Some(e) = self.run_mut(chunk_key)
2178 {
2179 e.last_used = frame_no;
2180 return false;
2181 }
2182 let (text, spans, style, off_stops) = {
2185 let line = self.long(key).expect("a long line");
2186 let c = &line.chunks[i];
2187 let first = line.spans.partition_point(|s| s.end <= c.start);
2188 let spans: Vec<OwnedSpan> = line.spans[first..]
2189 .iter()
2190 .take_while(|s| s.start < c.end)
2191 .map(|s| OwnedSpan {
2192 start: s.start.max(c.start) - c.start,
2193 end: s.end.min(c.end) - c.start,
2194 attrs: s.attrs,
2195 })
2196 .collect();
2197 let bytes = line.content.as_bytes();
2198 let off_stops = c.start > 0 && bytes[c.start - 1] != b'\t';
2199 (
2200 line.content[c.start..c.end].to_string(),
2201 spans,
2202 line.style,
2203 off_stops,
2204 )
2205 };
2206 let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
2207 let w = self.run(k).expect("just interned").intrinsic.w;
2208 let tab = match off_stops && text.ends_with('\t') {
2209 true => self.chunk_tab(k, &text, &spans, &style, res, fs),
2210 false => None,
2211 };
2212 let line = self.long_mut(key).expect("just read");
2213 line.chunks[i].key = k;
2214 line.chunks[i].tab = tab;
2215 if line.chunks[i].width != Some(w) {
2216 line.chunks[i].width = Some(w);
2217 line.reprefix();
2218 }
2219 true
2220 }
2221
2222 #[inline(never)]
2228 #[allow(clippy::too_many_arguments)]
2229 fn chunk_tab(
2230 &mut self,
2231 chunk: u64,
2232 text: &str,
2233 spans: &[OwnedSpan],
2234 style: &TextStyle,
2235 res: &Resources,
2236 fs: &mut FontSystem,
2237 ) -> Option<(f32, f32)> {
2238 let at = text.len() - 1;
2239 let tab_x = {
2240 let run = self.run(chunk)?.buffer.layout_runs().next()?;
2241 if run.rtl {
2242 return None;
2243 }
2244 run.glyphs.iter().rev().find(|g| g.start == at)?.x
2245 };
2246 let alone = self.shape_chunk(text, spans, at, text.len(), style, res, fs);
2247 let every = self.run(alone)?.intrinsic.w;
2248 Some((tab_x, every))
2249 }
2250
2251 fn relayout_long(&mut self, key: u64, w: Option<f32>) {
2259 let Some(w) = w else {
2260 let line = self.long_mut(key).expect("a long line");
2261 line.wrap_w = None;
2262 line.starts.clear();
2263 for c in &mut line.chunks {
2264 c.rows.clear();
2265 }
2266 return;
2267 };
2268 let w = w.max(1.0);
2269 let line = self.long(key).expect("a long line");
2272 let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2273 starts.push((0, 0.0));
2274 let (mut row, mut x) = (0u32, 0.0f32);
2275 let rows: Vec<Vec<RowStart>> = line
2276 .chunks
2277 .iter()
2278 .enumerate()
2279 .map(|(i, c)| {
2280 let shaped = c.width.and_then(|_| self.run(c.key));
2281 let rows = match shaped {
2282 Some(e) => {
2283 let text = &line.content[c.start..c.end];
2284 let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2285 row += rows.len() as u32 - 1;
2286 x = end + line.tab_shift(i);
2290 rows
2291 }
2292 None => {
2293 let est = x + c
2294 .width
2295 .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2296 let added = (est / w).floor();
2297 row += added as u32;
2298 x = est - added * w;
2299 Vec::new()
2300 }
2301 };
2302 starts.push((row, x));
2303 rows
2304 })
2305 .collect();
2306 let line = self.long_mut(key).expect("a long line");
2307 line.wrap_w = Some(w);
2308 line.starts = starts;
2309 for (c, rows) in line.chunks.iter_mut().zip(rows) {
2310 c.rows = rows;
2311 }
2312 }
2313
2314 fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2318 let (wrap, width, line_h, cur) = {
2319 let l = self.long(key).expect("a long line");
2320 (l.wrap, l.width(), l.line_h, l.wrap_w)
2321 };
2322 let target = match max_w {
2323 Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2324 _ => None,
2325 };
2326 let differs = match (cur, target) {
2327 (None, None) => false,
2328 (Some(a), Some(b)) => (a - b).abs() > 0.5,
2329 _ => true,
2330 };
2331 if differs {
2332 self.relayout_long(key, target);
2333 }
2334 match target {
2335 Some(w) => {
2336 let rows = self.long(key).expect("a long line").rows();
2337 (Size::new(w, rows as f32 * line_h), rows)
2338 }
2339 None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2340 }
2341 }
2342
2343 pub fn measure(
2349 &mut self,
2350 content: &str,
2351 style: &TextStyle,
2352 res: &Resources,
2353 fs: &mut FontSystem,
2354 max_w: Option<f32>,
2355 ) -> TextMetrics {
2356 let key = self.intern_any(content, style, res, fs);
2357 self.measure_any(key, fs, max_w)
2358 }
2359
2360 fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2362 if self.long(key).is_some() {
2363 let scale = self.scale;
2364 let (size, lines) = match max_w {
2367 Some(m) => {
2368 let held = self.long(key).expect("checked").wrap_w;
2371 let measured = self.long_size(key, Some(m * scale));
2372 if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2373 self.relayout_long(key, held);
2374 }
2375 measured
2376 }
2377 None => {
2378 let line = self.long(key).expect("checked");
2379 (Size::new(line.width(), line.line_h), 1)
2380 }
2381 };
2382 return TextMetrics {
2383 width: size.w / scale,
2384 height: size.h / scale,
2385 lines,
2386 };
2387 }
2388 self.measure_key(key, fs, max_w)
2389 }
2390
2391 pub fn measure_rich(
2393 &mut self,
2394 spans: &[Span<'_>],
2395 base: &TextStyle,
2396 res: &Resources,
2397 fs: &mut FontSystem,
2398 max_w: Option<f32>,
2399 ) -> TextMetrics {
2400 let key = self.intern_rich_any(spans, base, res, fs);
2401 self.measure_any(key, fs, max_w)
2402 }
2403
2404 fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2405 let scale = self.scale;
2406 let entry = self.run_mut(key).expect("just interned");
2407 let target = wrap_target(entry, max_w, scale);
2408 let key = if entry.claimed != u64::MAX && wrap_differs(entry.wrap, target) {
2415 self.wrap_copy(key, target, fs)
2416 } else {
2417 key
2418 };
2419 let entry = self.run_mut(key).expect("just interned");
2420 wrap_entry(entry, fs, target);
2421 let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2422 if entry.clamp_w
2423 && let Some(t) = target
2424 {
2425 m.w = m.w.min(t);
2426 }
2427 TextMetrics {
2428 width: m.w / scale,
2429 height: m.h / scale,
2430 lines,
2431 }
2432 }
2433
2434 pub fn add_rich(
2438 &mut self,
2439 spans: &[Span<'_>],
2440 base: &TextStyle,
2441 res: &Resources,
2442 fs: &mut FontSystem,
2443 ) -> TextId {
2444 let key = self.intern_rich_any(spans, base, res, fs);
2445 let len = spans.iter().map(|s| s.text.len()).sum();
2446 let key = if could_be_long(len, base) {
2447 self.claim_long(key)
2448 } else {
2449 key
2450 };
2451 self.frame.push(FrameText {
2452 cache_key: key,
2453 color: base.color_or_default(),
2454 });
2455 TextId((self.frame.len() - 1) as u32)
2456 }
2457
2458 fn intern_rich(
2459 &mut self,
2460 spans: &[Span<'_>],
2461 base: &TextStyle,
2462 res: &Resources,
2463 fs: &mut FontSystem,
2464 ) -> u64 {
2465 let key = Self::rich_key(spans, base, self.scale);
2466 self.intern_rich_keyed(key, spans, base, res, fs)
2467 }
2468
2469 fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2473 let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2474 for s in spans {
2475 key = crate::key::mix_content(key, s.text.as_bytes());
2476 let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2477 mix(&[
2478 s.bold as u8,
2479 s.italic as u8,
2480 s.color.is_some() as u8,
2481 s.underline as u8,
2482 s.strikethrough as u8,
2483 s.bg.is_some() as u8,
2484 s.underline_style as u8,
2485 s.underline_color.is_some() as u8,
2486 ]);
2487 mix(&s.bg_radius.to_bits().to_le_bytes());
2488 let (tag, font) = match s.family {
2491 None => (0u8, 0u64),
2492 Some(FontFamily::Sans) => (1, 0),
2493 Some(FontFamily::Serif) => (2, 0),
2494 Some(FontFamily::Mono) => (3, 0),
2495 Some(FontFamily::Custom(id)) => (4, id.to_ffi()),
2496 };
2497 mix(&[tag]);
2498 mix(&font.to_le_bytes());
2499 mix(&s.size.map_or(0, f32::to_bits).to_le_bytes());
2500 for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2501 mix(&c.r.to_bits().to_le_bytes());
2502 mix(&c.g.to_bits().to_le_bytes());
2503 mix(&c.b.to_bits().to_le_bytes());
2504 mix(&c.a.to_bits().to_le_bytes());
2505 }
2506 }
2507 key
2508 }
2509
2510 fn intern_rich_keyed(
2512 &mut self,
2513 key: u64,
2514 spans: &[Span<'_>],
2515 base: &TextStyle,
2516 res: &Resources,
2517 fs: &mut FontSystem,
2518 ) -> u64 {
2519 let frame_no = self.frame_no;
2520 let scale = self.scale;
2521 if !self.entries.contains_key(&key) {
2522 let mut buffer = new_buffer(fs, base, scale);
2523 let family = res.family_of(base.family);
2524 let weights = res.weights_of(base.family);
2525 let features = cosmic_features(&base.features);
2526 let sized = spans.iter().any(|s| s.size.is_some());
2531 let metrics_of = |size: Option<f32>| {
2532 let size = size.unwrap_or(base.size);
2533 let ratio = if base.size > 0.0 {
2534 base.line_height / base.size
2535 } else {
2536 1.0
2537 };
2538 shaper_metrics(size * scale, size * ratio * scale)
2539 };
2540 buffer.set_rich_text(
2544 spans.iter().enumerate().map(|(i, s)| {
2545 let (family, weights) = match s.family {
2546 Some(f) => (res.family_of(f), res.weights_of(f)),
2547 None => (family, weights),
2548 };
2549 let mut attrs = s
2550 .attrs(family, weights)
2551 .font_features(features.clone())
2552 .metadata(i);
2553 if base.bold && !s.bold {
2556 attrs = weights.apply(attrs, true);
2557 }
2558 if sized {
2559 attrs = attrs.metrics(metrics_of(s.size));
2560 }
2561 (s.text, attrs)
2562 }),
2563 &{
2564 let attrs = weights.apply(
2565 Attrs::new().family(family).font_features(features.clone()),
2566 base.bold,
2567 );
2568 if sized {
2569 attrs.metrics(metrics_of(None))
2570 } else {
2571 attrs
2572 }
2573 },
2574 Shaping::Advanced,
2575 None,
2576 );
2577 let content = spans.iter().map(|s| s.text).collect::<String>();
2578 let decos = spans
2579 .iter()
2580 .map(|s| SpanDeco {
2581 underline: s.underline || base.underline,
2582 underline_color: s.underline_color.or(base.underline_color),
2584 underline_style: if s.underline {
2585 s.underline_style
2586 } else {
2587 base.underline_style
2588 },
2589 strikethrough: s.strikethrough || base.strikethrough,
2590 bg: s.bg,
2591 bg_radius: s.bg_radius,
2592 })
2593 .collect();
2594 let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2595 self.insert(key, Entry::Run(entry));
2596 }
2597 self.entries
2598 .get_mut(&key)
2599 .expect("just inserted")
2600 .touch(frame_no);
2601 key
2602 }
2603
2604 fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2607 let key = self.frame[id.0 as usize].cache_key;
2608 self.run_mut(key).expect("frame text missing from cache")
2609 }
2610
2611 fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2616 let (scale, frame_no) = (self.scale, self.frame_no);
2617 let entry = self.entry_mut(id);
2618 let target = wrap_target(entry, Some(max_w_logical), scale);
2619 if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2620 self.own_wrap(id, target, fs);
2621 return;
2622 }
2623 entry.claimed = frame_no;
2624 entry.claimed_wrap = target;
2625 wrap_entry(entry, fs, target);
2626 }
2627
2628 #[inline(never)]
2636 fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2637 let frame_no = self.frame_no;
2638 let key = self.frame[id.0 as usize].cache_key;
2639 let copy = self.wrap_copy(key, target, fs);
2640 self.frame[id.0 as usize].cache_key = copy;
2641 let entry = self.run_mut(copy).expect("just made");
2642 entry.claimed = frame_no;
2643 entry.claimed_wrap = target;
2644 }
2645
2646 #[inline(never)]
2652 fn wrap_copy(&mut self, key: u64, target: Option<f32>, fs: &mut FontSystem) -> u64 {
2653 let frame_no = self.frame_no;
2654 let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2656 let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2657 if self.run(copy).is_none() {
2658 let fork = self.run(key).expect("a run").fork(frame_no);
2659 self.insert(copy, Entry::Run(fork));
2660 }
2661 let entry = self.run_mut(copy).expect("just made");
2662 entry.last_used = frame_no;
2663 wrap_entry(entry, fs, target);
2664 copy
2665 }
2666
2667 #[allow(clippy::too_many_arguments)]
2672 pub(crate) fn emit(
2673 &mut self,
2674 id: TextId,
2675 origin: Vec2,
2676 node: Size,
2677 clip: Clip,
2678 clip_id: ClipId,
2679 clips: &mut Vec<Clip>,
2680 res: &Resources,
2681 fs: &mut FontSystem,
2682 atlas: &mut GlyphAtlas,
2683 out: &mut Vec<Quad>,
2684 sel: Option<((usize, usize), Color)>,
2685 ) {
2686 let from = self.joins.len();
2687 self.emit_text(
2688 id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2689 );
2690 if self.joins.len() == from {
2691 return;
2692 }
2693 let key = self.frame[id.0 as usize].cache_key;
2696 let clamps = match self.entries.get(&key) {
2697 Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2698 Some(Entry::Run(e)) => e.clamp_w,
2699 None => false,
2700 };
2701 let own = clamps.then(|| {
2702 let ox = crate::geom::snap_px(origin.x * self.scale);
2703 (ox, ox + (node.w * self.scale).ceil())
2704 });
2705 for j in &mut self.joins[from..] {
2706 j.outer = clip_id;
2707 j.own = own;
2708 }
2709 }
2710
2711 #[allow(clippy::too_many_arguments)]
2717 #[inline(never)]
2718 fn emit_long(
2719 &mut self,
2720 key: u64,
2721 node: Size,
2722 ox: f32,
2723 oy: f32,
2724 nudge: Vec2,
2725 color: Color,
2726 clip: Clip,
2727 clip_id: ClipId,
2728 clips: &mut Vec<Clip>,
2729 res: &Resources,
2730 fs: &mut FontSystem,
2731 atlas: &mut GlyphAtlas,
2732 out: &mut Vec<Quad>,
2733 sel: Option<((usize, usize), Color)>,
2734 ) {
2735 let scale = self.scale;
2736 let line = self.long(key).expect("a long line");
2737 let (clip, clip_id) = if line.wrap == TextWrap::None {
2743 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2744 let clip = clip.intersect(own, crate::display::SQUARE);
2745 let vis = clip.visible();
2746 if vis.w <= 0.0 || vis.h <= 0.0 {
2747 return;
2748 }
2749 (clip, crate::display::intern_clip(clips, clip))
2750 } else {
2751 (clip, clip_id)
2752 };
2753 let vis = clip.visible();
2755 if let Some(((from, to), tint)) = sel {
2756 let rects = self.long_highlight(line, from, to);
2757 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2758 }
2759 if let Some(w) = line.wrap_w {
2760 self.emit_long_rows(
2761 key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2762 );
2763 return;
2764 }
2765 let (first, last) = {
2766 if line.chunks.is_empty() {
2767 return;
2768 }
2769 let a = line.chunk_at(vis.x - ox).saturating_sub(1);
2770 let b = (line.chunk_at(vis.x + vis.w - ox) + 1).min(line.chunks.len() - 1);
2771 (a, b)
2772 };
2773 for i in first..=last {
2774 self.ensure_chunk(key, i, res, fs);
2775 }
2776 let frame_no = self.frame_no;
2777 self.long_mut(key).expect("checked").last_used = frame_no;
2778 for i in first..=last {
2779 let (chunk_key, x) = {
2780 let line = self.long(key).expect("checked");
2781 (line.chunks[i].key, line.prefix[i])
2782 };
2783 let raster = &mut self.raster;
2784 let entry = self
2785 .entries
2786 .get_mut(&chunk_key)
2787 .and_then(Entry::run_mut)
2788 .expect("just ensured");
2789 emit_entry(
2790 entry,
2791 ox + x,
2792 oy,
2793 nudge,
2794 color,
2795 clip,
2796 clip_id,
2797 raster,
2798 fs,
2799 atlas,
2800 out,
2801 &mut self.joins,
2802 );
2803 }
2804 }
2805
2806 #[allow(clippy::too_many_arguments)]
2808 fn emit_text(
2809 &mut self,
2810 id: TextId,
2811 origin: Vec2,
2812 node: Size,
2813 clip: Clip,
2814 clip_id: ClipId,
2815 clips: &mut Vec<Clip>,
2816 res: &Resources,
2817 fs: &mut FontSystem,
2818 atlas: &mut GlyphAtlas,
2819 out: &mut Vec<Quad>,
2820 sel: Option<((usize, usize), Color)>,
2825 ) {
2826 let FrameText {
2827 cache_key: key,
2828 color,
2829 } = self.frame[id.0 as usize];
2830 let scale = self.scale;
2831 let ox = crate::geom::snap_px(origin.x * scale);
2832 let oy = crate::geom::snap_px(origin.y * scale);
2833 let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2836 if self.long(key).is_some() {
2839 self.emit_long(
2840 key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2841 );
2842 return;
2843 }
2844 self.ensure_wrap(id, node.w, fs);
2845 let raster = &mut self.raster;
2846 let entry = self
2847 .entries
2848 .get_mut(&key)
2849 .and_then(Entry::run_mut)
2850 .expect("frame text missing from cache");
2851
2852 let (clip, clip_id) = if entry.clamp_w {
2855 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2856 let clip = clip.intersect(own, crate::display::SQUARE);
2857 (clip, crate::display::intern_clip(clips, clip))
2858 } else {
2859 (clip, clip_id)
2860 };
2861 if let Some(((from, to), tint)) = sel {
2862 let rects = Self::run_highlight(entry, from, to);
2863 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2864 }
2865 emit_entry(
2866 entry,
2867 ox,
2868 oy,
2869 nudge,
2870 color,
2871 clip,
2872 clip_id,
2873 raster,
2874 fs,
2875 atlas,
2876 out,
2877 &mut self.joins,
2878 );
2879 }
2880}
2881
2882fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2896 Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2897}
2898
2899fn push_highlight(
2902 rects: &[Rect],
2903 ox: f32,
2904 oy: f32,
2905 nudge: Vec2,
2906 tint: Color,
2907 clip: ClipId,
2908 out: &mut Vec<Quad>,
2909) {
2910 for r in rects {
2911 out.push(Quad {
2912 rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2913 color: tint,
2914 border_color: Color::TRANSPARENT,
2915 radius: [0.0; 4],
2916 border_w: 0.0,
2917 blur: 0.0,
2918 kind: QuadKind::Solid,
2919 uv: [0; 4],
2920 clip,
2921 });
2922 }
2923}
2924
2925impl TextSystem {
2926 #[allow(clippy::too_many_arguments)]
2930 fn emit_long_rows(
2931 &mut self,
2932 key: u64,
2933 w: f32,
2934 ox: f32,
2935 oy: f32,
2936 nudge: Vec2,
2937 color: Color,
2938 clip: Clip,
2939 clip_id: ClipId,
2940 res: &Resources,
2941 fs: &mut FontSystem,
2942 atlas: &mut GlyphAtlas,
2943 out: &mut Vec<Quad>,
2944 ) {
2945 let vis = clip.visible();
2946 let (first, last) = {
2947 let line = self.long(key).expect("a long line");
2948 let ra = ((vis.y - oy) / line.line_h).floor().max(0.0) as u32;
2949 let rb = ((vis.y + vis.h - oy) / line.line_h).floor().max(0.0) as u32;
2950 let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
2951 return;
2952 };
2953 (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
2954 };
2955 let mut fresh = false;
2956 for i in first..=last {
2957 fresh |= self.ensure_chunk(key, i, res, fs);
2958 }
2959 if fresh {
2960 self.relayout_long(key, Some(w));
2963 let line = self.long_mut(key).expect("checked");
2964 let rows = line.rows();
2965 if rows != line.laid_rows && rows != line.asked_rows {
2966 line.asked_rows = rows;
2967 self.owed = true;
2968 }
2969 }
2970 let frame_no = self.frame_no;
2971 self.long_mut(key).expect("checked").last_used = frame_no;
2972 for i in first..=last {
2973 let chunk_key = self.long(key).expect("checked").chunks[i].key;
2974 let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
2978 self.entries.get_disjoint_mut([&key, &chunk_key])
2979 else {
2980 continue;
2981 };
2982 let (row0, head_x) = line.starts[i];
2983 let chunk = &line.chunks[i];
2984 if chunk.rows.is_empty() {
2985 continue;
2986 }
2987 let raster = &mut self.raster;
2988 emit_entry_rows(
2989 entry,
2990 ox,
2991 oy + row0 as f32 * line.line_h,
2992 nudge,
2993 head_x,
2994 &chunk.rows,
2995 line.line_h,
2996 color,
2997 clip,
2998 clip_id,
2999 raster,
3000 fs,
3001 atlas,
3002 out,
3003 &mut self.joins,
3004 );
3005 }
3006 }
3007}
3008
3009#[allow(clippy::too_many_arguments)]
3014fn emit_entry_rows(
3015 entry: &mut CachedText,
3016 ox: f32,
3017 oy: f32,
3018 nudge: Vec2,
3019 head_x: f32,
3020 rows: &[RowStart],
3021 line_h: f32,
3022 color: Color,
3023 clip: Clip,
3024 clip_id: ClipId,
3025 raster: &mut Raster,
3026 fs: &mut FontSystem,
3027 atlas: &mut GlyphAtlas,
3028 out: &mut Vec<Quad>,
3029 joins: &mut Vec<JoinBg>,
3030) {
3031 build_templates(entry, raster, fs, atlas);
3032 let vis = clip.visible();
3034 let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
3040 let (x0, x1) = (d.x, d.x + d.w);
3041 for r in 0..rows.len() {
3042 let ra = rows[r].x;
3043 let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
3044 let (a, b) = (x0.max(ra), x1.min(rb));
3045 if b <= a {
3046 continue;
3047 }
3048 let dx = if r == 0 { head_x } else { 0.0 } - ra;
3049 let x = ox + a + dx;
3050 let y = oy + d.y + r as f32 * line_h;
3051 if y >= vis.y + vis.h {
3052 break;
3053 }
3054 if y + d.h <= vis.y || x >= vis.x + vis.w || x + (b - a) <= vis.x {
3055 continue;
3056 }
3057 let quad = Quad {
3058 rect: if d.under {
3059 on_pixels(x, y, b - a, d.h, nudge)
3060 } else {
3061 Rect::new(x, y, b - a, d.h)
3062 },
3063 color: d.color.unwrap_or(color),
3064 border_color: Color::TRANSPARENT,
3065 radius: [0.0; 4],
3066 border_w: 0.0,
3067 blur: 0.0,
3068 kind: QuadKind::Solid,
3069 clip: clip_id,
3070 uv: [0; 4],
3071 };
3072 if d.under && d.radius > 0.0 {
3076 joins.push(JoinBg {
3077 quad: out.len() as u32,
3078 radius: d.radius,
3079 outer: clip_id,
3080 own: None,
3081 });
3082 }
3083 if d.under || d.style == UnderlineStyle::Solid {
3084 out.push(quad);
3085 } else {
3086 crate::deco::push_line(
3087 out,
3088 d.style,
3089 x,
3090 y,
3091 b - a,
3092 d.h,
3093 d.color.unwrap_or(color),
3094 clip_id,
3095 );
3096 }
3097 }
3098 };
3099 for d in entry.deco.iter().filter(|d| d.under) {
3100 row_pieces(d, out);
3101 }
3102 let mut r = 0usize;
3103 for g in &entry.glyphs {
3104 while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
3105 r += 1;
3106 }
3107 let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
3108 let x = ox + g.x + dx;
3109 let y = oy + g.y + r as f32 * line_h;
3110 if y >= vis.y + vis.h {
3117 break;
3118 }
3119 if y + g.h <= vis.y || x >= vis.x + vis.w || x + g.w <= vis.x {
3120 continue;
3121 }
3122 out.push(Quad {
3123 rect: Rect::new(x, y, g.w, g.h),
3124 color: g.color.unwrap_or(color),
3125 border_color: Color::TRANSPARENT,
3126 radius: [0.0; 4],
3127 border_w: 0.0,
3128 blur: 0.0,
3129 kind: g.kind,
3130 clip: clip_id,
3131 uv: g.uv,
3132 });
3133 }
3134 for d in entry.deco.iter().filter(|d| !d.under) {
3135 row_pieces(d, out);
3136 }
3137}
3138
3139fn break_rows(
3152 buffer: &Buffer,
3153 text: &str,
3154 wrap: TextWrap,
3155 w: f32,
3156 head_x: f32,
3157) -> (Vec<RowStart>, f32) {
3158 let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
3159 let Some(run) = buffer.layout_runs().next() else {
3160 return (rows, head_x);
3161 };
3162 let glyphs = run.glyphs;
3163 let blank = |g: &cosmic_text::LayoutGlyph| {
3164 text.get(g.start..g.end)
3165 .is_some_and(|s| s.chars().all(char::is_whitespace))
3166 };
3167 let spaces = wrap == TextWrap::BreakSpaces;
3168 let mut pieces: Vec<(usize, usize)> = Vec::new();
3169 match wrap {
3170 TextWrap::Word | TextWrap::BreakSpaces => {
3171 let mut at = 0usize;
3172 let mut piece = |from: usize, to: usize| {
3173 if !spaces {
3174 pieces.push((from, to));
3175 return;
3176 }
3177 let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
3180 if ink > 0 {
3181 pieces.push((from, from + ink));
3182 }
3183 for (i, c) in text[from + ink..to].char_indices() {
3184 let at = from + ink + i;
3185 pieces.push((at, at + c.len_utf8()));
3186 }
3187 };
3188 for (i, _) in unicode_linebreak::linebreaks(text) {
3189 if i > at {
3190 piece(at, i);
3191 at = i;
3192 }
3193 }
3194 if at < text.len() {
3195 piece(at, text.len());
3196 }
3197 }
3198 TextWrap::Glyph | TextWrap::None => {
3199 pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
3200 }
3201 }
3202 let mut row_x0 = 0.0f32;
3203 let mut avail = w - head_x;
3204 let mut gi = 0usize;
3205 for (s, e) in pieces {
3206 let g0 = gi;
3207 while gi < glyphs.len() && glyphs[gi].start < e {
3208 gi += 1;
3209 }
3210 if g0 == gi {
3211 continue;
3212 }
3213 let piece = &glyphs[g0..gi];
3214 let x0 = piece[0].x;
3215 let ink_end = piece
3216 .iter()
3217 .rev()
3218 .find(|g| spaces || !blank(g))
3219 .map_or(x0, |g| g.x + g.w);
3220 let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
3226 if ink_end - row_x0 > avail && opens {
3227 rows.push(RowStart {
3228 byte: s as u32,
3229 x: x0,
3230 });
3231 row_x0 = x0;
3232 avail = w;
3233 }
3234 if ink_end - row_x0 > avail {
3235 for g in piece {
3237 if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
3238 rows.push(RowStart {
3239 byte: g.start as u32,
3240 x: g.x,
3241 });
3242 row_x0 = g.x;
3243 avail = w;
3244 }
3245 }
3246 }
3247 }
3248 let end = if rows.len() == 1 {
3249 head_x + run.line_w
3250 } else {
3251 run.line_w - row_x0
3252 };
3253 (rows, end)
3254}
3255
3256fn build_templates(
3259 entry: &mut CachedText,
3260 raster: &mut Raster,
3261 fs: &mut FontSystem,
3262 atlas: &mut GlyphAtlas,
3263) {
3264 {
3265 let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
3267 if entry.glyphs_built_for != Some(built_for) {
3268 entry.glyphs.clear();
3269 entry.deco.clear();
3270 let lines = line_cap(entry.max_lines);
3271 let decorated = entry.span_deco.iter().any(SpanDeco::any);
3272 for run in entry.buffer.layout_runs().take(lines) {
3273 if decorated {
3274 build_decorations(&run, &entry.span_deco, fs, &mut entry.deco);
3275 }
3276 for glyph in run.glyphs.iter() {
3277 let physical = glyph.physical((0.0, 0.0), 1.0);
3278 let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
3279 continue;
3280 };
3281 entry.glyphs.push(GlyphTemplate {
3282 x: physical.x as f32 + slot.left as f32,
3283 y: run.line_y.round() + physical.y as f32 - slot.top as f32,
3284 w: slot.w as f32,
3285 h: slot.h as f32,
3286 uv: [slot.x, slot.y, slot.w, slot.h],
3287 kind: glyph_kind(&slot),
3288 color: glyph
3289 .color_opt
3290 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3291 byte: glyph.start as u32,
3292 });
3293 }
3294 }
3295 entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
3299 }
3300 }
3301}
3302
3303#[allow(clippy::too_many_arguments)]
3307fn emit_entry(
3308 entry: &mut CachedText,
3309 ox: f32,
3310 oy: f32,
3311 nudge: Vec2,
3312 color: Color,
3313 clip: Clip,
3314 clip_id: ClipId,
3315 raster: &mut Raster,
3316 fs: &mut FontSystem,
3317 atlas: &mut GlyphAtlas,
3318 out: &mut Vec<Quad>,
3319 joins: &mut Vec<JoinBg>,
3320) {
3321 build_templates(entry, raster, fs, atlas);
3322 let vis = clip.visible();
3324 {
3325 let inside = |x: f32, y: f32, w: f32, h: f32| {
3326 oy + y + h >= vis.y
3327 && oy + y <= vis.y + vis.h
3328 && ox + x < vis.x + vis.w
3329 && ox + x + w > vis.x
3330 };
3331 let deco_quad = |d: &DecoTemplate| Quad {
3332 rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
3333 color: d.color.unwrap_or(color),
3334 border_color: Color::TRANSPARENT,
3335 radius: [0.0; 4],
3336 border_w: 0.0,
3337 blur: 0.0,
3338 kind: QuadKind::Solid,
3339 clip: clip_id,
3340 uv: [0; 4],
3341 };
3342 for d in entry
3347 .deco
3348 .iter()
3349 .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3350 {
3351 if d.radius > 0.0 {
3352 joins.push(JoinBg {
3353 quad: out.len() as u32,
3354 radius: d.radius,
3355 outer: clip_id,
3356 own: None,
3357 });
3358 }
3359 out.push(Quad {
3360 rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3361 ..deco_quad(d)
3362 });
3363 }
3364
3365 out.extend(
3369 entry
3370 .glyphs
3371 .iter()
3372 .filter(|g| {
3373 oy + g.y + g.h >= vis.y && ox + g.x < vis.x + vis.w && ox + g.x + g.w > vis.x
3374 })
3375 .take_while(|g| oy + g.y <= vis.y + vis.h)
3376 .map(|g| Quad {
3377 rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3378 color: g.color.unwrap_or(color),
3379 border_color: Color::TRANSPARENT,
3380 radius: [0.0; 4],
3381 border_w: 0.0,
3382 blur: 0.0,
3383 kind: g.kind,
3384 clip: clip_id,
3385 uv: g.uv,
3386 }),
3387 );
3388 for d in entry
3389 .deco
3390 .iter()
3391 .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3392 {
3393 match d.style {
3396 UnderlineStyle::Solid => out.push(deco_quad(d)),
3397 style => crate::deco::push_line(
3398 out,
3399 style,
3400 ox + d.x,
3401 oy + d.y,
3402 d.w,
3403 d.h,
3404 d.color.unwrap_or(color),
3405 clip_id,
3406 ),
3407 }
3408 }
3409 }
3410}
3411
3412fn could_be_long(len: usize, style: &TextStyle) -> bool {
3420 (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3421 && style.max_lines == 0
3422 && !style.ellipsis
3423}
3424
3425fn has_line_break(content: &str) -> bool {
3428 content.bytes().any(|b| b == b'\n' || b == b'\r')
3429}
3430
3431fn has_rtl(content: &str) -> bool {
3439 !content.is_ascii()
3440 && content.chars().any(|c| {
3441 matches!(c as u32,
3442 0x0590..=0x08FF
3443 | 0x200F | 0x202B | 0x202E | 0x2067
3444 | 0xFB1D..=0xFDFF
3445 | 0xFE70..=0xFEFF
3446 | 0x10800..=0x10FFF
3447 | 0x1E800..=0x1EFFF)
3448 })
3449}
3450
3451fn build_decorations(
3462 run: &cosmic_text::LayoutRun<'_>,
3463 spans: &[SpanDeco],
3464 fs: &mut FontSystem,
3465 out: &mut Vec<DecoTemplate>,
3466) {
3467 let mut last_bg: Option<usize> = None;
3470 let mut i = 0;
3471 while i < run.glyphs.len() {
3472 let span_no = run.glyphs[i].metadata;
3473 let mut j = i + 1;
3474 while j < run.glyphs.len() && run.glyphs[j].metadata == span_no {
3475 j += 1;
3476 }
3477 let deco = spans.get(span_no).copied().unwrap_or_default();
3478 if deco.any() {
3479 let group = &run.glyphs[i..j];
3480 let x0 = group.iter().map(|g| g.x).fold(f32::INFINITY, f32::min);
3481 let x1 = group
3482 .iter()
3483 .map(|g| g.x + g.w)
3484 .fold(f32::NEG_INFINITY, f32::max);
3485 let w = (x1 - x0).max(0.0);
3486 match (deco.bg, last_bg.map(|k| &mut out[k])) {
3487 (Some(bg), Some(prev))
3488 if prev.color == Some(bg)
3489 && prev.radius == deco.bg_radius
3490 && (prev.x + prev.w - x0).abs() < 0.01 =>
3491 {
3492 prev.w = (x1 - prev.x).max(prev.w);
3493 }
3494 (Some(bg), _) => {
3495 let (y, h) = match group[0].line_height_opt {
3499 Some(lh) if lh < run.line_height - 0.01 => {
3500 let g = &group[0];
3501 let (asc, desc) = fs
3502 .get_font(g.font_id, g.font_weight)
3503 .map(|font| {
3504 let m = font.as_swash().metrics(&[]).scale(g.font_size);
3505 (m.ascent, m.descent)
3506 })
3507 .unwrap_or((0.8 * g.font_size, 0.2 * g.font_size));
3508 let mid = run.line_y - (asc - desc) / 2.0;
3509 (mid - lh / 2.0, lh)
3510 }
3511 _ => (run.line_top, run.line_height),
3512 };
3513 last_bg = Some(out.len());
3514 out.push(DecoTemplate {
3515 x: x0,
3516 y,
3517 w,
3518 h,
3519 color: Some(bg),
3520 under: true,
3521 style: UnderlineStyle::Solid,
3522 radius: deco.bg_radius,
3523 });
3524 }
3525 (None, _) => last_bg = None,
3526 }
3527 if deco.underline || deco.strikethrough {
3528 let first = &group[0];
3529 let metrics = fs
3530 .get_font(first.font_id, first.font_weight)
3531 .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3532 let (under_off, strike_off, stroke) = match metrics {
3535 Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3536 None => (
3537 -0.1 * first.font_size,
3538 0.3 * first.font_size,
3539 0.06 * first.font_size,
3540 ),
3541 };
3542 let stroke = stroke.max(1.0).round();
3543 let baseline = run.line_y.round();
3544 if deco.underline {
3545 out.push(DecoTemplate {
3546 x: x0,
3547 y: (baseline - under_off).round(),
3548 w,
3549 h: stroke,
3550 color: deco.underline_color.or_else(|| {
3551 first
3552 .color_opt
3553 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a()))
3554 }),
3555 under: false,
3556 style: deco.underline_style,
3557 radius: 0.0,
3558 });
3559 }
3560 if deco.strikethrough {
3561 out.push(DecoTemplate {
3562 x: x0,
3563 y: (baseline - strike_off).round(),
3564 w,
3565 h: stroke,
3566 color: first
3567 .color_opt
3568 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3569 under: false,
3570 style: UnderlineStyle::Solid,
3571 radius: 0.0,
3572 });
3573 }
3574 }
3575 } else {
3576 last_bg = None;
3577 }
3578 i = j;
3579 }
3580}
3581
3582impl TextSystem {
3583 pub(crate) fn place(
3588 &mut self,
3589 key: Key,
3590 ancestry: &Ancestry,
3591 id: TextId,
3592 origin: Vec2,
3593 scope: Option<Key>,
3594 drawn: bool,
3595 ) {
3596 let cache_key = self.frame[id.0 as usize].cache_key;
3597 self.places.push(TextPlace {
3598 key,
3599 ancestors: ancestry.keys,
3600 depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3601 none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3602 cache_key,
3603 origin,
3604 scope,
3605 drawn,
3606 });
3607 }
3608
3609 fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3615 let list = if prev {
3616 self.places.prev()
3617 } else {
3618 &self.places
3619 };
3620 let mut base = 0usize;
3621 let mut out = Vec::new();
3622 for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3623 let Some(entry) = self.entries.get(&place.cache_key) else {
3624 continue;
3625 };
3626 out.push((place, entry, base));
3627 base += entry.content().len();
3628 }
3629 out
3630 }
3631
3632 pub(crate) fn content_len(&self, id: TextId) -> usize {
3635 let key = self.frame[id.0 as usize].cache_key;
3636 self.entries.get(&key).map_or(0, |e| e.content().len())
3637 }
3638
3639 pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3648 let list = if prev {
3649 self.places.prev()
3650 } else {
3651 &self.places
3652 };
3653 let mut base = 0usize;
3654 let mut out = Vec::new();
3655 for place in list.iter().filter(|p| p.scope == Some(scope)) {
3656 let Some(text) = self.entries.get(&place.cache_key) else {
3657 continue;
3658 };
3659 let len = text.content().len();
3660 out.push(ScopeRun { place, text, base });
3661 base += len;
3662 }
3663 out
3664 }
3665
3666 pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3673 let runs = self.scope_runs(scope, prev);
3674 let px = point.x * self.scale;
3675 let py = point.y * self.scale;
3676 let gap = |r: &Rect| {
3677 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3678 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3679 (dy, dx)
3680 };
3681 let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3682 let ga = gap(&self.scope_box(a));
3683 let gb = gap(&self.scope_box(b));
3684 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3685 })?;
3686 let byte = self.hit_in_run(run, point)?;
3687 Some((run.place.key, byte))
3688 }
3689
3690 fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3692 self.entry_box(run.place, run.text)
3693 }
3694
3695 fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
3700 match entry {
3701 Entry::Run(e) => self.physical_box(place, e),
3702 Entry::Long(l) => {
3703 let (ox, oy) = self.physical_origin(place);
3704 let w = l.wrap_w.unwrap_or_else(|| l.width());
3705 Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
3706 }
3707 }
3708 }
3709
3710 fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
3712 let (_, oy) = self.physical_origin(place);
3713 match entry {
3714 Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
3715 Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
3716 }
3717 }
3718
3719 fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
3722 match run.text {
3723 Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
3724 Entry::Run(e) => {
3725 let (ox, oy) = self.physical_origin(run.place);
3726 let cursor = e
3727 .buffer
3728 .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
3729 Some(e.byte_of(cursor))
3730 }
3731 }
3732 }
3733
3734 pub(crate) fn word_at(
3741 &self,
3742 scope: Key,
3743 node: Key,
3744 byte: usize,
3745 prev: bool,
3746 ) -> Option<(usize, usize)> {
3747 let run = self
3748 .scope_runs(scope, prev)
3749 .into_iter()
3750 .find(|r| r.place.key == node)?;
3751 let content = run.text.content();
3752 Some(word_range(content, byte))
3753 }
3754
3755 pub(crate) fn scope_offset(
3761 &self,
3762 scope: Key,
3763 node: Key,
3764 byte: usize,
3765 prev: bool,
3766 ) -> Option<usize> {
3767 let run = self
3768 .scope_runs(scope, prev)
3769 .into_iter()
3770 .find(|r| r.place.key == node)?;
3771 Some(run.base + byte.min(run.text.content().len()))
3772 }
3773
3774 pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3789 let (from, to) = (from.min(to), from.max(to));
3790 let mut out = String::new();
3791 let mut prev_run: Option<&ScopeRun<'_>> = None;
3792 for run in &self.scope_runs(scope, prev) {
3793 let (start, end) = run.span();
3794 if end <= from || start >= to {
3795 continue;
3796 }
3797 if let Some(p) = prev_run
3798 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3799 {
3800 out.push_str("<br>");
3801 }
3802 let content = run.text.content();
3803 let lo = floor_boundary(content, from.saturating_sub(start));
3804 let hi = floor_boundary(content, (to - start).min(content.len()));
3805 match run.text {
3806 Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
3807 Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
3808 }
3809 prev_run = Some(run);
3810 }
3811 out
3812 }
3813
3814 fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
3820 for c in &line.chunks {
3821 let (a, b) = (lo.max(c.start), hi.min(c.end));
3822 if a >= b {
3823 continue;
3824 }
3825 match c.width.and_then(|_| self.run(c.key)) {
3826 Some(e) => html_of_run(e, a - c.start, b - c.start, out),
3827 None => escape_into(&line.content[a..b], out),
3828 }
3829 }
3830 }
3831
3832 pub(crate) fn scope_selection_anchor(
3842 &self,
3843 scope: Key,
3844 from: usize,
3845 to: usize,
3846 prev: bool,
3847 ) -> Option<Vec2> {
3848 let (from, to) = (from.min(to), from.max(to));
3849 for run in self.scope_runs(scope, prev) {
3850 let (start, end) = run.span();
3851 if !run.place.drawn || end <= from || start >= to {
3852 continue;
3853 }
3854 let content = run.text.content();
3855 let lo = floor_boundary(content, from.saturating_sub(start));
3856 let hi = floor_boundary(content, (to - start).min(content.len()));
3857 let (ox, oy) = self.physical_origin(run.place);
3858 match run.text {
3859 Entry::Run(e) => {
3860 if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
3861 return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
3862 }
3863 }
3864 Entry::Long(l) => {
3865 if let Some((x, y)) = self.long_caret_local(l, lo) {
3866 return Some(Vec2::new(
3867 (ox + x) / self.scale,
3868 (oy + y + self.long_baseline(l)) / self.scale,
3869 ));
3870 }
3871 }
3872 }
3873 }
3874 None
3875 }
3876
3877 fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
3880 let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
3881 for run in entry.buffer.layout_runs() {
3882 if run.line_i < a.line || run.line_i > b.line {
3883 continue;
3884 }
3885 if let Some((x, _)) = run.highlight(a, b).next() {
3886 return Some((x, run.line_y));
3887 }
3888 }
3889 None
3890 }
3891
3892 pub(crate) fn scope_selection_rect(
3898 &self,
3899 scope: Key,
3900 from: usize,
3901 to: usize,
3902 prev: bool,
3903 ) -> Option<Rect> {
3904 let (from, to) = (from.min(to), from.max(to));
3905 let mut out: Option<Rect> = None;
3906 for run in self.scope_runs(scope, prev) {
3907 let (start, end) = run.span();
3908 if !run.place.drawn || end <= from || start >= to {
3909 continue;
3910 }
3911 let content = run.text.content();
3912 let lo = floor_boundary(content, from.saturating_sub(start));
3913 let hi = floor_boundary(content, (to - start).min(content.len()));
3914 let (ox, oy) = self.physical_origin(run.place);
3920 let rects = match run.text {
3921 Entry::Long(l) => self.long_highlight(l, lo, hi),
3922 Entry::Run(e) => Self::run_highlight(e, lo, hi),
3923 };
3924 for r in rects {
3925 let r = Rect::new(
3926 (ox + r.x) / self.scale,
3927 (oy + r.y) / self.scale,
3928 r.w / self.scale,
3929 r.h / self.scale,
3930 );
3931 out = Some(match out {
3932 None => r,
3933 Some(o) => o.union(&r),
3934 });
3935 }
3936 }
3937 out
3938 }
3939
3940 pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3945 let (from, to) = (from.min(to), from.max(to));
3946 let runs = self.scope_runs(scope, prev);
3947 let mut out = String::new();
3948 let mut reserved = false;
3949 let mut prev_run: Option<&ScopeRun<'_>> = None;
3950 for run in &runs {
3951 let (start, end) = run.span();
3952 if end <= from || start >= to {
3953 continue;
3954 }
3955 if !reserved {
3956 out.reserve(to - from + runs.len());
3957 reserved = true;
3958 }
3959 if let Some(p) = prev_run
3960 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3961 {
3962 out.push('\n');
3963 }
3964 let content = run.text.content();
3965 let lo = from.saturating_sub(start).min(content.len());
3966 let hi = (to - start).min(content.len());
3967 out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
3968 prev_run = Some(run);
3969 }
3970 out
3971 }
3972
3973 fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
3977 let (ox, oy) = self.physical_origin(place);
3978 let px = point.x * self.scale - ox;
3979 let py = point.y * self.scale - oy;
3980 if line.chunks.is_empty() {
3981 return Some(TextHit { byte: 0, line: 0 });
3982 }
3983 if let Some(w) = line.wrap_w {
3984 return self.long_hit_wrapped(line, w, px, py);
3985 }
3986 if px >= line.width() {
3987 return Some(TextHit {
3988 byte: line.content.len(),
3989 line: 0,
3990 });
3991 }
3992 let i = line.chunk_at(px.max(0.0));
3993 let c = &line.chunks[i];
3994 let local = px - line.prefix[i];
3995 let byte = match self.run(c.key) {
3996 Some(_) if let Some(b) = line.placed_tab_hit(i, local) => b,
3997 Some(e) if c.width.is_some() => {
3998 let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
3999 c.start + cursor.index.min(c.end - c.start)
4000 }
4001 _ => {
4002 let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
4003 let mut b = (c.start + est).min(c.end);
4004 while !line.content.is_char_boundary(b) {
4005 b -= 1;
4006 }
4007 b
4008 }
4009 };
4010 Some(TextHit { byte, line: 0 })
4011 }
4012
4013 fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
4017 let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
4018 let Some((a, b)) = line.chunks_on_rows(row, row) else {
4019 return Some(TextHit {
4020 byte: line.content.len(),
4021 line: row,
4022 });
4023 };
4024 let i = (a..=b)
4027 .find(|&j| {
4028 let (r1, end_x) = line.starts[j + 1];
4029 px < if r1 == row { end_x } else { w }
4030 })
4031 .unwrap_or(b);
4032 let c = &line.chunks[i];
4033 let (row0, head_x) = line.starts[i];
4034 let r = (row - row0) as usize;
4035 let byte = match self.run(c.key) {
4036 Some(e) if r < c.rows.len() => {
4037 let rs = c.rows[r];
4038 let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
4039 let lo = rs.byte as usize;
4040 let hi = c
4041 .rows
4042 .get(r + 1)
4043 .map_or(c.end - c.start, |n| n.byte as usize);
4044 let tab_here = (lo..hi).contains(&(c.end - c.start - 1));
4046 match line.placed_tab_hit(i, local_x) {
4047 Some(b) if tab_here => b,
4048 _ => {
4049 let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
4050 c.start + cursor.index.clamp(lo, hi)
4051 }
4052 }
4053 }
4054 _ => {
4055 let linear = r as f32 * w + px - head_x;
4058 let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
4059 let mut b = (c.start + est).min(c.end);
4060 while !line.content.is_char_boundary(b) {
4061 b -= 1;
4062 }
4063 b
4064 }
4065 };
4066 let next_row = self
4071 .long_caret_local(line, byte)
4072 .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
4073 let byte = if next_row && byte > 0 {
4074 floor_boundary(&line.content, byte - 1)
4075 } else {
4076 byte
4077 };
4078 Some(TextHit { byte, line: row })
4079 }
4080
4081 fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
4084 let (ox, oy) = self.physical_origin(place);
4085 let scale = self.scale;
4086 let (x, y) = self.long_caret_local(line, byte)?;
4087 Some(Rect::new(
4088 (ox + x) / scale,
4089 (oy + y) / scale,
4090 0.0,
4091 line.line_h / scale,
4092 ))
4093 }
4094
4095 fn long_baseline(&self, line: &LongLine) -> f32 {
4101 line.chunks
4102 .first()
4103 .and_then(|c| self.run(c.key))
4104 .and_then(|e| e.buffer.layout_runs().next())
4105 .map_or(line.line_h * 0.8, |r| r.line_y.round())
4106 }
4107
4108 fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
4113 let byte = byte.min(line.content.len());
4114 if let Some(w) = line.wrap_w
4115 && !line.chunks.is_empty()
4116 {
4117 let i = line.chunk_of_byte(byte);
4118 let c = &line.chunks[i];
4119 let local = byte - c.start;
4120 let (row0, head_x) = line.starts[i];
4121 let (r, x) = match self.run(c.key) {
4122 Some(e) if !c.rows.is_empty() => {
4123 let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
4124 let run = e.buffer.layout_runs().next()?;
4125 let cx = caret_x(&run, local.min(c.end - c.start)) + line.end_shift(i, local);
4126 (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
4127 }
4128 _ => {
4129 let linear = head_x + local as f32 * line.avg;
4130 let r = (linear / w).floor();
4131 (r as usize, linear - r * w)
4132 }
4133 };
4134 let y = (row0 as usize + r) as f32 * line.line_h;
4135 return Some((x, y));
4136 }
4137 let x = if line.chunks.is_empty() {
4138 0.0
4139 } else {
4140 let i = line.chunk_of_byte(byte);
4141 let c = &line.chunks[i];
4142 let local = byte - c.start;
4143 match self.run(c.key) {
4144 Some(e) if c.width.is_some() => {
4145 let run = e.buffer.layout_runs().next()?;
4146 let x = caret_x(&run, local.min(c.end - c.start));
4147 line.prefix[i] + x + line.end_shift(i, local)
4148 }
4149 _ => line.prefix[i] + local as f32 * line.avg,
4150 }
4151 };
4152 Some((x, 0.0))
4153 }
4154
4155 fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
4161 let Some((x0, y0)) = self.long_caret_local(line, from) else {
4162 return Vec::new();
4163 };
4164 let Some((x1, y1)) = self.long_caret_local(line, to) else {
4165 return Vec::new();
4166 };
4167 let h = line.line_h;
4168 if (y1 - y0).abs() < f32::EPSILON {
4169 return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
4170 }
4171 let w = line.wrap_w.unwrap_or_else(|| line.width());
4173 let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
4174 let mut y = y0 + h;
4175 while y < y1 - h / 2.0 {
4176 out.push(Rect::new(0.0, y, w, h));
4177 y += h;
4178 }
4179 out.push(Rect::new(0.0, y1, x1.max(0.0), h));
4180 out
4181 }
4182
4183 fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
4189 let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
4190 let mut out = Vec::new();
4191 for run in entry.buffer.layout_runs() {
4192 if run.line_i < a.line || run.line_i > b.line {
4193 continue;
4194 }
4195 let h = run.line_height;
4198 let mut any = false;
4199 for (x, w) in run.highlight(a, b) {
4200 any = true;
4201 out.push(Rect::new(x, run.line_top, w.max(2.0), h));
4202 }
4203 if !any && run.glyphs.is_empty() && b.line > run.line_i {
4206 out.push(Rect::new(0.0, run.line_top, 2.0, h));
4207 }
4208 }
4209 out
4210 }
4211
4212 fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
4215 (
4216 crate::geom::snap_px(place.origin.x * self.scale),
4217 crate::geom::snap_px(place.origin.y * self.scale),
4218 )
4219 }
4220
4221 fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
4223 let (ox, oy) = self.physical_origin(place);
4224 let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
4225 Rect::new(ox, oy, size.w, size.h)
4226 }
4227
4228 pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
4232 let runs = self.runs_of(key, prev);
4233 if runs.is_empty() {
4234 return None;
4235 }
4236 let px = point.x * self.scale;
4237 let py = point.y * self.scale;
4238 let gap = |r: &Rect| {
4241 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
4242 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
4243 (dy, dx)
4244 };
4245 let (place, entry, base) = runs
4246 .iter()
4247 .min_by(|a, b| {
4248 let ga = gap(&self.entry_box(a.0, a.1));
4249 let gb = gap(&self.entry_box(b.0, b.1));
4250 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
4251 })
4252 .copied()?;
4253 let (ox, oy) = self.physical_origin(place);
4254 let (byte, hit_top) = match entry {
4255 Entry::Long(line) => {
4256 let hit = self.long_hit(place, line, point)?;
4257 (hit.byte, oy + hit.line as f32 * line.line_h)
4258 }
4259 Entry::Run(entry) => {
4260 let bx = self.physical_box(place, entry);
4267 let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
4268 let cursor = entry.buffer.hit(px - ox, py - oy)?;
4269 let row =
4270 visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
4271 let top = entry
4272 .buffer
4273 .layout_runs()
4274 .nth(row)
4275 .map_or(0.0, |r| r.line_top);
4276 (entry.byte_of(cursor), oy + top)
4277 }
4278 };
4279 let mut tops: Vec<f32> = runs
4284 .iter()
4285 .flat_map(|(p, e, _)| self.row_tops(p, e))
4286 .collect();
4287 tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4288 tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
4289 let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
4290 Some(TextHit {
4291 byte: base + byte,
4292 line: line as u32,
4293 })
4294 }
4295
4296 pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
4300 let runs = self.runs_of(key, prev);
4301 let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
4302 let byte = byte.min(total);
4303 let (place, entry, base) = runs
4304 .iter()
4305 .find(|(_, e, base)| byte < base + e.content().len())
4306 .or_else(|| runs.last())
4307 .copied()?;
4308 let byte = (byte - base).min(entry.content().len());
4309 let entry = match entry {
4310 Entry::Long(line) => return self.long_caret(place, line, byte),
4311 Entry::Run(entry) => entry,
4312 };
4313 let (ox, oy) = self.physical_origin(place);
4314 let cursor = entry.cursor_of(byte);
4315 let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
4316 let run = entry.buffer.layout_runs().nth(run_no)?;
4317 let x = caret_x(&run, cursor.index);
4318 let scale = self.scale;
4319 Some(Rect::new(
4320 (ox + x) / scale,
4321 (oy + run.line_top) / scale,
4322 0.0,
4323 run.line_height / scale,
4324 ))
4325 }
4326}
4327
4328fn word_range(content: &str, byte: usize) -> (usize, usize) {
4338 if content.is_empty() {
4339 return (0, 0);
4340 }
4341 #[derive(PartialEq)]
4342 enum Class {
4343 Word,
4344 Space,
4345 Other,
4346 }
4347 let class = |c: char| {
4348 if c.is_alphanumeric() || c == '_' {
4349 Class::Word
4350 } else if c.is_whitespace() {
4351 Class::Space
4352 } else {
4353 Class::Other
4354 }
4355 };
4356 let at = floor_boundary(content, byte.min(content.len()));
4359 let at = if at >= content.len() {
4360 content.char_indices().next_back().map_or(0, |(i, _)| i)
4361 } else {
4362 at
4363 };
4364 let Some(here) = content[at..].chars().next().map(class) else {
4365 return (at, at);
4366 };
4367 let mut start = at;
4368 for (i, c) in content[..at].char_indices().rev() {
4369 if class(c) != here {
4370 break;
4371 }
4372 start = i;
4373 }
4374 let mut end = at;
4375 for (i, c) in content[at..].char_indices() {
4376 if class(c) != here {
4377 break;
4378 }
4379 end = at + i + c.len_utf8();
4380 }
4381 (start, end)
4382}
4383
4384fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4389 let starts = line_starts(&entry.buffer, &entry.content);
4390 for (li, line) in entry.buffer.lines.iter().enumerate() {
4391 let base = starts.get(li).copied().unwrap_or(0);
4392 let text = line.text();
4393 if li > 0 {
4394 if base > lo && base <= hi {
4397 out.push_str("<br>");
4398 }
4399 }
4400 let attrs_list = line.attrs_list();
4406 let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4407 for (i, _) in text.char_indices() {
4408 let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4409 match spans.last_mut() {
4410 Some((_, end, prev)) if *prev == a && *end == i => {
4411 *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4412 }
4413 _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4414 }
4415 }
4416 for (start, end, attrs) in &spans {
4417 let (s, e) = (base + start, base + end);
4418 let (s, e) = (s.max(lo), e.min(hi));
4419 if s >= e {
4420 continue;
4421 }
4422 let piece = &entry.content[s..e];
4423 let bold = attrs.weight > attrs_list.defaults().weight
4428 || attrs
4429 .cache_key_flags
4430 .contains(crate::weights::SYNTHETIC_BOLD);
4431 let italic = attrs.style != cosmic_text::Style::Normal;
4432 if let Some(c) = attrs.color_opt {
4433 let _ = std::fmt::Write::write_fmt(
4434 out,
4435 format_args!(
4436 "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4437 c.r(),
4438 c.g(),
4439 c.b()
4440 ),
4441 );
4442 }
4443 if bold {
4444 out.push_str("<b>");
4445 }
4446 if italic {
4447 out.push_str("<i>");
4448 }
4449 escape_into(piece, out);
4450 if italic {
4451 out.push_str("</i>");
4452 }
4453 if bold {
4454 out.push_str("</b>");
4455 }
4456 if attrs.color_opt.is_some() {
4457 out.push_str("</span>");
4458 }
4459 }
4460 }
4461}
4462
4463fn escape_into(s: &str, out: &mut String) {
4467 for ch in s.chars() {
4468 match ch {
4469 '&' => out.push_str("&"),
4470 '<' => out.push_str("<"),
4471 '>' => out.push_str(">"),
4472 _ => out.push(ch),
4473 }
4474 }
4475}
4476
4477fn floor_boundary(s: &str, i: usize) -> usize {
4483 let mut i = i.min(s.len());
4484 while i > 0 && !s.is_char_boundary(i) {
4485 i -= 1;
4486 }
4487 i
4488}
4489
4490fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4491 paragraph_starts(buffer, content).collect()
4492}
4493
4494fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4500 let mut at = 0usize;
4501 buffer.lines.iter().map(move |line| {
4502 let start = at.min(content.len());
4503 at += line.text().len();
4504 let rest = &content[at.min(content.len())..];
4505 for ending in ["\r\n", "\n\r", "\n", "\r"] {
4506 if rest.starts_with(ending) {
4507 at += ending.len();
4508 break;
4509 }
4510 }
4511 start
4512 })
4513}
4514
4515fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4525 let mut last_of_line = None;
4526 let mut before = None;
4527 for (n, run) in buffer.layout_runs().enumerate() {
4528 if run.line_i != line_i {
4529 if last_of_line.is_some() {
4530 break;
4531 }
4532 continue;
4533 }
4534 last_of_line = Some(n);
4535 if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4536 return Some((n, n));
4537 }
4538 if run
4539 .glyphs
4540 .iter()
4541 .map(|g| g.start)
4542 .min()
4543 .is_some_and(|s| s <= index)
4544 {
4545 before = Some(n);
4546 }
4547 }
4548 before.or(last_of_line).map(|n| (n, n))
4549}
4550
4551fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4555 if let Some(g) = run
4556 .glyphs
4557 .iter()
4558 .find(|g| g.start <= index && index < g.end)
4559 {
4560 return if g.level.is_rtl() { g.x + g.w } else { g.x };
4561 }
4562 match run.glyphs.last() {
4563 Some(g) if !g.level.is_rtl() => g.x + g.w,
4564 Some(g) => g.x,
4565 None => 0.0,
4566 }
4567}
4568
4569impl Default for TextSystem {
4570 fn default() -> Self {
4571 Self::new()
4572 }
4573}
4574
4575fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4578 let max_w = max_w_logical?;
4579 let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4580 (!intrinsic_fits).then_some(max_w * scale)
4581}
4582
4583pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4591 match (current, target) {
4592 (None, None) => false,
4593 (Some(a), Some(b)) => (a - b).abs() > 0.5,
4594 _ => true,
4595 }
4596}
4597
4598fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4599 if wrap_differs(entry.wrap, target) {
4600 entry.buffer.set_size(target, None);
4601 entry.buffer.shape_until_scroll(fs, false);
4602 entry.wrap = target;
4603 }
4604}
4605
4606impl CachedText {
4607 fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4610 let start = paragraph_starts(&self.buffer, &self.content)
4611 .nth(cursor.line)
4612 .unwrap_or(0);
4613 (start + cursor.index).min(self.content.len())
4614 }
4615
4616 fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4622 let mut li = 0;
4623 let mut start = 0;
4624 for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4625 if s <= byte {
4626 (li, start) = (i, s);
4627 } else {
4628 break;
4629 }
4630 }
4631 let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4632 cosmic_text::Cursor::new(li, (byte - start).min(len))
4633 }
4634
4635 fn new(
4636 mut buffer: Buffer,
4637 content: String,
4638 style: &TextStyle,
4639 span_deco: Vec<SpanDeco>,
4640 fs: &mut FontSystem,
4641 frame_no: u64,
4642 ) -> Self {
4643 buffer.shape_until_scroll(fs, false);
4644 let max_lines = line_budget(style);
4645 let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4646 let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4647 Self {
4648 buffer,
4649 bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4650 content,
4651 wrap: None,
4652 intrinsic,
4653 max_lines,
4654 clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4655 last_used: frame_no,
4656 glyphs: Vec::new(),
4657 deco: Vec::new(),
4658 span_deco,
4659 glyphs_built_for: None,
4660 claimed: u64::MAX,
4661 claimed_wrap: None,
4662 breaks_anywhere: style.wrap == TextWrap::Glyph,
4663 min_content: None,
4664 }
4665 }
4666
4667 fn fork(&self, frame_no: u64) -> Self {
4671 Self {
4672 buffer: self.buffer.clone(),
4673 content: self.content.clone(),
4674 wrap: self.wrap,
4675 intrinsic: self.intrinsic,
4676 max_lines: self.max_lines,
4677 clamp_w: self.clamp_w,
4678 last_used: frame_no,
4679 bytes: self.bytes,
4680 glyphs: Vec::new(),
4681 deco: Vec::new(),
4682 span_deco: self.span_deco.clone(),
4683 glyphs_built_for: None,
4684 claimed: u64::MAX,
4685 claimed_wrap: None,
4686 breaks_anywhere: self.breaks_anywhere,
4687 min_content: self.min_content,
4688 }
4689 }
4690
4691 fn min_content(&mut self) -> f32 {
4702 if let Some(w) = self.min_content {
4703 return w;
4704 }
4705 let w = if self.clamp_w {
4706 self.intrinsic.w
4707 } else {
4708 widest_unbreakable(&self.buffer, self.breaks_anywhere)
4709 };
4710 self.min_content = Some(w);
4711 w
4712 }
4713}
4714
4715fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
4719 let mut widest = 0.0f32;
4720 let mut line = usize::MAX;
4721 let mut breaks: Vec<usize> = Vec::new();
4722 let (mut run_w, mut ink) = (0.0f32, 0.0f32);
4723 for run in buffer.layout_runs() {
4724 if run.line_i != line {
4725 widest = widest.max(ink);
4726 (run_w, ink) = (0.0, 0.0);
4727 line = run.line_i;
4728 breaks.clear();
4729 if !anywhere {
4730 breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
4731 }
4732 }
4733 for g in run.glyphs {
4734 if anywhere || breaks.binary_search(&g.start).is_ok() {
4735 widest = widest.max(ink);
4736 (run_w, ink) = (0.0, 0.0);
4737 }
4738 run_w += g.w;
4739 let blank = run
4740 .text
4741 .get(g.start..g.end)
4742 .is_some_and(|t| t.chars().all(char::is_whitespace));
4743 if !blank {
4744 ink = run_w;
4745 }
4746 }
4747 }
4748 widest.max(ink)
4749}
4750
4751pub(crate) fn shaper_metrics(size: f32, line_height: f32) -> Metrics {
4758 let px = |v: f32| if v.is_finite() { v.max(1.0) } else { 1.0 };
4759 Metrics::new(px(size), px(line_height))
4760}
4761
4762fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
4765 let metrics = shaper_metrics(style.size * scale, style.line_height * scale);
4766 let mut buffer = Buffer::new(fs, metrics);
4767 buffer.set_wrap(match style.wrap {
4768 TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
4769 TextWrap::Glyph => Wrap::Glyph,
4770 TextWrap::None => Wrap::None,
4771 });
4772 if style.ellipsis {
4773 let lines = line_budget(style).max(1);
4774 buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
4775 }
4776 buffer.set_size(None, None);
4777 buffer
4778}
4779
4780fn line_budget(style: &TextStyle) -> usize {
4783 if style.max_lines > 0 {
4784 style.max_lines as usize
4785 } else if style.ellipsis {
4786 1
4787 } else {
4788 0
4789 }
4790}
4791
4792fn line_cap(max_lines: usize) -> usize {
4793 if max_lines == 0 {
4794 usize::MAX
4795 } else {
4796 max_lines
4797 }
4798}
4799
4800pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
4806 let mut w = 0.0f32;
4807 let mut lines = 0u32;
4808 let lh = buffer.metrics().line_height;
4812 let (mut sum, mut uniform) = (0.0f32, true);
4813 for run in buffer.layout_runs().take(line_cap(max_lines)) {
4814 w = w.max(run.line_w);
4815 lines += 1;
4816 sum += run.line_height;
4817 uniform &= run.line_height == lh;
4818 }
4819 (
4820 Size::new(w, if uniform { lines as f32 * lh } else { sum }),
4821 lines,
4822 )
4823}
4824
4825impl TextSystem {
4826 pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
4827 let scale = self.scale;
4828 if let Some(line) = self.long_of(id) {
4829 return Size::new(line.width() / scale, line.line_h / scale);
4830 }
4831 let e = self.entry_mut(id);
4832 Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
4833 }
4834
4835 pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
4840 let scale = self.scale;
4841 if self.long_of(id).is_some() {
4842 return 0.0;
4843 }
4844 self.entry_mut(id).min_content() / scale
4845 }
4846
4847 fn long_of(&self, id: TextId) -> Option<&LongLine> {
4849 self.long(self.frame[id.0 as usize].cache_key)
4850 }
4851
4852 pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
4853 let scale = self.scale;
4854 if self.long_of(id).is_some() {
4855 let key = self.frame[id.0 as usize].cache_key;
4858 let (size, rows) = self.long_size(key, Some(max_w * scale));
4859 self.long_mut(key).expect("a long line").laid_rows = rows;
4860 return Size::new(size.w / scale, size.h / scale);
4861 }
4862 self.ensure_wrap(id, max_w, fs);
4863 let e = self.entry_mut(id);
4864 let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
4865 if e.clamp_w {
4866 m.w = m.w.min(max_w * scale);
4869 }
4870 Size::new(m.w / scale, m.h / scale)
4871 }
4872}
4873
4874impl TextSystem {
4875 pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
4880 let scale = self.scale;
4881 if let Some(line) = self.long_of(id) {
4882 return self.long_baseline(line) / scale;
4883 }
4884 let e = self.entry_mut(id);
4885 let b = e
4886 .buffer
4887 .layout_runs()
4888 .next()
4889 .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
4890 b / scale
4891 }
4892}
4893
4894#[cfg(test)]
4897mod default_families {
4898 use super::*;
4899 use cosmic_text::Family;
4900 use cosmic_text::fontdb::FaceInfo;
4901
4902 fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
4905 let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
4906 buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
4907 buffer.shape_until_scroll(fs, false);
4908 let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
4909 fs.db().face(id).cloned()
4910 }
4911
4912 #[test]
4913 fn mono_is_an_upright_monospaced_face() {
4914 let mut fs = new_font_system().0;
4915 if !fs.db().faces().any(|f| f.monospaced) {
4916 eprintln!("skipped: no monospaced face installed");
4917 return;
4918 }
4919 let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
4920 let mono = default_families(&fs)[2].to_string();
4921 assert_eq!(
4922 face.style,
4923 cosmic_text::Style::Normal,
4924 "Mono ({mono}) resolved to {:?}",
4925 face.post_script_name
4926 );
4927 assert!(
4928 face.monospaced,
4929 "Mono ({mono}) resolved to {:?}, which is not monospaced",
4930 face.post_script_name
4931 );
4932 }
4933
4934 #[test]
4935 fn sans_and_serif_are_upright_and_not_monospaced() {
4936 let mut fs = new_font_system().0;
4937 if fs.db().faces().next().is_none() {
4938 eprintln!("skipped: no face installed");
4939 return;
4940 }
4941 let [sans, serif, _] = default_families(&fs).map(str::to_string);
4942 for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
4943 let face = resolved(&mut fs, family).expect("M shapes");
4944 assert_eq!(
4945 face.style,
4946 cosmic_text::Style::Normal,
4947 "{name} resolved to {:?}",
4948 face.post_script_name
4949 );
4950 assert!(
4951 !face.monospaced,
4952 "{name} resolved to {:?}, which is monospaced",
4953 face.post_script_name
4954 );
4955 }
4956 }
4957
4958 #[test]
4961 fn pinned_names_are_installed_or_cosmic_texts_own() {
4962 let fs = new_font_system().0;
4963 let db = fs.db();
4964 for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
4965 let installed = db
4966 .faces()
4967 .any(|f| f.families.iter().any(|(fam, _)| fam == name));
4968 let on_list = list.contains(&name);
4969 assert!(
4970 installed == on_list,
4971 "{name}: installed {installed}, on the platform list {on_list}"
4972 );
4973 }
4974 }
4975}
4976
4977#[cfg(test)]
4984mod unmeasurable_faces {
4985 use super::*;
4986 use crate::testing::{font_face, han_face, unmeasurable_face};
4987 use cosmic_text::Family;
4988 use cosmic_text::fontdb::{Database, Source};
4989
4990 fn font_system() -> FontSystem {
4995 let mut db = Database::new();
4996 for bytes in [
4997 font_face("Kui Mono", 400, false, true),
4998 unmeasurable_face("Kui Bitmap"),
4999 han_face("Kui Han"),
5000 ] {
5001 db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
5002 }
5003 let mut fs = font_system_with("en-US".into(), db);
5004 fs.db_mut().set_monospace_family("Kui Mono");
5005 fs
5006 }
5007
5008 fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
5009 fs.db()
5010 .face(id)
5011 .map_or_else(String::new, |f| f.families[0].0.clone())
5012 }
5013
5014 #[test]
5015 fn a_face_without_metrics_is_not_in_the_database() {
5016 let fs = font_system();
5017 let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
5018 assert_eq!(families, ["Kui Mono", "Kui Han"]);
5019 }
5020
5021 #[test]
5025 fn han_in_mono_falls_back_to_a_face_that_measures() {
5026 let mut fs = font_system();
5027 let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
5028 let attrs = Attrs::new().family(Family::Monospace);
5029 buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
5030 buffer.shape_until_scroll(&mut fs, false);
5031 let run = buffer.layout_runs().next().expect("one line");
5032 let glyphs: Vec<(&str, f32, f32, String)> = run
5033 .glyphs
5034 .iter()
5035 .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
5036 .collect();
5037 for &(text, x, w, _) in &glyphs {
5038 assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
5039 }
5040 let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
5041 assert_eq!(
5042 faces,
5043 [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
5044 );
5045 let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
5047 assert_eq!(xs, [0.0, 7.0, 14.0]);
5048 for glyph in run.glyphs {
5049 glyph.physical((0.0, 0.0), 1.0);
5050 }
5051 }
5052}