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}
387
388impl<'a> Span<'a> {
389 pub fn new(text: &'a str) -> Self {
390 Self {
391 text,
392 color: None,
393 bold: false,
394 italic: false,
395 underline: false,
396 underline_color: None,
397 underline_style: UnderlineStyle::Solid,
398 strikethrough: false,
399 bg: None,
400 bg_radius: 0.0,
401 }
402 }
403
404 pub fn color(mut self, c: Color) -> Self {
405 self.color = Some(c);
406 self
407 }
408
409 pub fn bold(mut self) -> Self {
410 self.bold = true;
411 self
412 }
413
414 pub fn italic(mut self) -> Self {
415 self.italic = true;
416 self
417 }
418
419 pub fn underline(mut self) -> Self {
420 self.underline = true;
421 self
422 }
423
424 pub fn underline_color(mut self, c: Color) -> Self {
426 self.underline = true;
427 self.underline_color = Some(c);
428 self
429 }
430
431 pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
433 self.underline = true;
434 self.underline_style = s;
435 self
436 }
437
438 pub fn strikethrough(mut self) -> Self {
439 self.strikethrough = true;
440 self
441 }
442
443 pub fn bg(mut self, c: Color) -> Self {
444 self.bg = Some(c);
445 self
446 }
447
448 pub fn bg_radius(mut self, r: f32) -> Self {
451 self.bg_radius = r.max(0.0);
452 self
453 }
454
455 fn attrs_only(&self) -> SpanAttrs {
458 SpanAttrs {
459 color: self.color,
460 bold: self.bold,
461 italic: self.italic,
462 underline: self.underline,
463 underline_color: self.underline_color,
464 underline_style: self.underline_style,
465 strikethrough: self.strikethrough,
466 bg: self.bg,
467 bg_radius: self.bg_radius,
468 }
469 }
470
471 fn attrs(
472 &self,
473 family: cosmic_text::Family<'a>,
474 weights: crate::weights::Weights,
475 ) -> Attrs<'a> {
476 let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
480 if self.italic {
481 attrs = attrs.style(FontStyle::Italic);
482 }
483 if let Some(c) = self.color {
484 attrs = attrs.color(cosmic_text::Color::rgba(
485 (c.r * 255.0) as u8,
486 (c.g * 255.0) as u8,
487 (c.b * 255.0) as u8,
488 (c.a * 255.0) as u8,
489 ));
490 }
491 attrs
492 }
493}
494
495#[derive(Clone, Copy)]
499struct SpanAttrs {
500 color: Option<Color>,
501 bold: bool,
502 italic: bool,
503 underline: bool,
504 underline_color: Option<Color>,
505 underline_style: UnderlineStyle,
506 strikethrough: bool,
507 bg: Option<Color>,
508 bg_radius: f32,
509}
510
511impl SpanAttrs {
512 fn span<'a>(&self, text: &'a str) -> Span<'a> {
513 Span {
514 text,
515 color: self.color,
516 bold: self.bold,
517 italic: self.italic,
518 underline: self.underline,
519 underline_color: self.underline_color,
520 underline_style: self.underline_style,
521 strikethrough: self.strikethrough,
522 bg: self.bg,
523 bg_radius: self.bg_radius,
524 }
525 }
526}
527
528#[derive(Clone)]
531struct OwnedSpan {
532 start: usize,
533 end: usize,
534 attrs: SpanAttrs,
535}
536
537fn chunk_spans<'a>(
542 content: &'a str,
543 spans: &[OwnedSpan],
544 start: usize,
545 end: usize,
546) -> Vec<Span<'a>> {
547 let first = spans.partition_point(|s| s.end <= start);
548 spans[first..]
549 .iter()
550 .take_while(|s| s.start < end)
551 .filter_map(|s| {
552 let (a, b) = (s.start.max(start), s.end.min(end));
553 (a < b).then(|| s.attrs.span(&content[a..b]))
554 })
555 .collect()
556}
557
558struct FrameText {
559 cache_key: u64,
560 color: Color,
561}
562
563pub(crate) enum Entry {
571 Run(CachedText),
572 Long(LongLine),
573}
574
575impl Entry {
576 pub(crate) fn content(&self) -> &str {
577 match self {
578 Entry::Run(e) => &e.content,
579 Entry::Long(l) => &l.content,
580 }
581 }
582
583 fn last_used(&self) -> u64 {
584 match self {
585 Entry::Run(e) => e.last_used,
586 Entry::Long(l) => l.last_used,
587 }
588 }
589
590 fn touch(&mut self, frame_no: u64) {
591 match self {
592 Entry::Run(e) => e.last_used = frame_no,
593 Entry::Long(l) => l.last_used = frame_no,
594 }
595 }
596
597 fn bytes(&self) -> usize {
599 match self {
600 Entry::Run(e) => e.bytes,
601 Entry::Long(l) => l.bytes,
602 }
603 }
604
605 fn run(&self) -> Option<&CachedText> {
606 match self {
607 Entry::Run(e) => Some(e),
608 Entry::Long(_) => None,
609 }
610 }
611
612 fn run_mut(&mut self) -> Option<&mut CachedText> {
613 match self {
614 Entry::Run(e) => Some(e),
615 Entry::Long(_) => None,
616 }
617 }
618
619 fn long(&self) -> Option<&LongLine> {
620 match self {
621 Entry::Long(l) => Some(l),
622 Entry::Run(_) => None,
623 }
624 }
625
626 fn long_mut(&mut self) -> Option<&mut LongLine> {
627 match self {
628 Entry::Long(l) => Some(l),
629 Entry::Run(_) => None,
630 }
631 }
632}
633
634#[derive(Clone)]
638struct Chunk {
639 start: usize,
640 end: usize,
641 key: u64,
642 width: Option<f32>,
644 tab: Option<(f32, f32)>,
650 rows: Vec<RowStart>,
653}
654
655#[derive(Clone, Copy, Debug)]
659struct RowStart {
660 byte: u32,
661 x: f32,
662}
663
664#[derive(Clone)]
677pub(crate) struct LongLine {
678 content: String,
679 spans: Vec<OwnedSpan>,
683 style: TextStyle,
686 wrap: TextWrap,
692 wrap_w: Option<f32>,
695 starts: Vec<(u32, f32)>,
700 chunks: Vec<Chunk>,
701 prefix: Vec<f32>,
704 line_h: f32,
706 avg: f32,
709 last_used: u64,
710 claimed: u64,
713 laid_rows: u32,
720 asked_rows: u32,
721 bytes: usize,
722}
723
724impl LongLine {
725 fn chunk_at(&self, x: f32) -> usize {
727 match self.prefix[1..].partition_point(|&p| p <= x) {
728 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
729 i => i,
730 }
731 }
732
733 fn chunk_of_byte(&self, byte: usize) -> usize {
735 match self.chunks.partition_point(|c| c.end <= byte) {
736 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
737 i => i,
738 }
739 }
740
741 fn width(&self) -> f32 {
742 self.prefix.last().copied().unwrap_or(0.0)
743 }
744
745 fn rows(&self) -> u32 {
747 match self.starts.last() {
748 Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
749 _ => 1,
750 }
751 }
752
753 fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
756 let n = self.chunks.len();
757 if n == 0 || self.starts.len() != n + 1 {
758 return None;
759 }
760 let first = self.starts[1..].partition_point(|s| s.0 < ra);
761 let last = self.starts[..n].partition_point(|s| s.0 <= rb);
762 (first < last).then_some((first, last - 1))
763 }
764
765 fn reprefix(&mut self) {
772 let mut at = 0.0f32;
773 self.prefix.clear();
774 self.prefix.push(0.0);
775 for c in &self.chunks {
776 at = match (c.width, c.tab) {
777 (Some(_), Some((tab_x, every))) => next_tab_stop(at + tab_x, every),
778 (Some(w), None) => at + w,
779 (None, _) => at + (c.end - c.start) as f32 * self.avg,
780 };
781 self.prefix.push(at);
782 }
783 }
784
785 fn tab_shift(&self, i: usize) -> f32 {
788 let c = &self.chunks[i];
789 match (c.width, c.tab) {
790 (Some(w), Some(_)) => self.prefix[i + 1] - self.prefix[i] - w,
791 _ => 0.0,
792 }
793 }
794
795 fn end_shift(&self, i: usize, local: usize) -> f32 {
800 let c = &self.chunks[i];
801 match local >= c.end - c.start {
802 true => self.tab_shift(i),
803 false => 0.0,
804 }
805 }
806
807 fn placed_tab_hit(&self, i: usize, x: f32) -> Option<usize> {
814 let c = &self.chunks[i];
815 let (tab_x, _) = c.tab?;
816 c.width?;
817 if x < tab_x {
818 return None;
819 }
820 let drawn_end = self.prefix[i + 1] - self.prefix[i];
821 Some(match x < (tab_x + drawn_end) / 2.0 {
822 true => c.end - 1,
823 false => c.end,
824 })
825 }
826}
827
828fn next_tab_stop(x: f32, every: f32) -> f32 {
834 if every <= 0.0 {
835 return x;
836 }
837 (((x + 1.0 / 16.0) / every).floor() + 1.0) * every
838}
839
840fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
857 use unicode_segmentation::UnicodeSegmentation;
858 if content.len() < LONG_LINE_BYTES {
859 return vec![(0, content.len())];
860 }
861 let bytes = content.as_bytes();
862 let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
863 let mut start = 0usize;
864 while start < content.len() {
865 let mut window_end = (start + CHUNK_BYTES).min(content.len());
869 while !content.is_char_boundary(window_end) {
870 window_end -= 1;
871 }
872 let window = &content[start..window_end];
873 let mut from = (CHUNK_BYTES / 2).min(window.len());
875 while !window.is_char_boundary(from) {
876 from += 1;
877 }
878 let off_stops = start > 0 && bytes[start - 1] != b'\t';
882 let early_tab = match off_stops {
883 true => window[..from].find('\t'),
884 false => None,
885 };
886 let end = if let Some(t) = early_tab {
887 start + t + 1
888 } else if window_end == content.len() && !(off_stops && window[from..].contains('\t')) {
889 window_end
890 } else {
891 let (mut first_tab, mut last_tab, mut space) = (None, None, None);
895 for (i, c) in window[from..].char_indices() {
896 if c.is_whitespace() {
897 let end = from + i + c.len_utf8();
898 space = Some(end);
899 if c == '\t' {
900 first_tab = first_tab.or(Some(end));
901 last_tab = Some(end);
902 }
903 }
904 }
905 let tab = if off_stops { first_tab } else { last_tab };
906 match tab.or(space) {
907 Some(i) => start + i,
908 None => {
909 let mut cut = 0usize;
911 for (i, _) in window.grapheme_indices(true) {
912 if i == 0 {
913 continue;
914 }
915 cut = i;
916 }
917 if cut == 0 { window_end } else { start + cut }
918 }
919 }
920 };
921 out.push((start, end));
922 start = end;
923 }
924 out
925}
926
927pub(crate) const PLACE_ANCESTORS: usize = 4;
932
933pub(crate) struct Ancestry {
937 pub(crate) keys: [Key; PLACE_ANCESTORS],
938 pub(crate) depth: usize,
939 pub(crate) none_at: Option<usize>,
940}
941
942pub(crate) struct TextPlace {
947 pub(crate) key: Key,
948 ancestors: [Key; PLACE_ANCESTORS],
950 depth: u8,
951 none_at: u8,
957 cache_key: u64,
958 origin: Vec2,
960 scope: Option<Key>,
964 drawn: bool,
972}
973
974impl TextPlace {
975 fn answers_to(&self, key: Key) -> bool {
976 if self.key == key {
977 return true;
978 }
979 self.ancestors[..self.depth as usize]
983 .iter()
984 .position(|k| *k == key)
985 .is_some_and(|d| d <= self.none_at as usize)
986 }
987}
988
989pub(crate) struct ScopeRun<'a> {
995 pub place: &'a TextPlace,
996 pub text: &'a Entry,
998 pub base: usize,
999}
1000
1001impl ScopeRun<'_> {
1002 pub(crate) fn span(&self) -> (usize, usize) {
1005 (self.base, self.base + self.text.content().len())
1006 }
1007}
1008
1009#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1022pub struct TextHit {
1023 pub byte: usize,
1024 pub line: u32,
1025}
1026
1027impl TextHit {
1028 pub fn to_value(self) -> Value {
1030 Value::map([
1031 ("byte", Value::Int(self.byte as i64)),
1032 ("line", Value::Int(self.line as i64)),
1033 ])
1034 }
1035}
1036
1037#[derive(Clone, Copy, Debug, Default, PartialEq)]
1042pub struct TextMetrics {
1043 pub width: f32,
1044 pub height: f32,
1045 pub lines: u32,
1047}
1048
1049impl TextMetrics {
1050 pub fn to_value(self) -> Value {
1052 Value::map([
1053 ("width", Value::float(self.width)),
1054 ("height", Value::float(self.height)),
1055 ("lines", Value::Int(self.lines as i64)),
1056 ])
1057 }
1058}
1059
1060pub struct TextSystem {
1068 raster: Raster,
1069 joins: Vec<JoinBg>,
1072 entries: FxHashMap<u64, Entry>,
1075 bytes: usize,
1078 budget: usize,
1079 frame: Kept<FrameText>,
1084 places: Kept<TextPlace>,
1088 scale: f32,
1089 frame_no: u64,
1090 owed: bool,
1093}
1094
1095pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1098 let mut out = cosmic_text::FontFeatures::new();
1099 for (tag, value) in f.iter() {
1100 out.set(cosmic_text::FeatureTag::new(tag), value);
1101 }
1102 out
1103}
1104
1105#[cfg(target_os = "macos")]
1116const DEFAULT_FAMILIES: [&[&str]; 3] = [
1117 &["Helvetica Neue", "Helvetica"],
1118 &["Times New Roman", "Times"],
1119 &["SF Mono", "Menlo", "Monaco"],
1120];
1121#[cfg(target_os = "windows")]
1122const DEFAULT_FAMILIES: [&[&str]; 3] = [
1123 &["Segoe UI", "Arial"],
1124 &["Times New Roman"],
1125 &["Cascadia Mono", "Consolas", "Courier New"],
1126];
1127#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1128const DEFAULT_FAMILIES: [&[&str]; 3] = [
1129 &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1130 &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1131 &[
1132 "DejaVu Sans Mono",
1133 "Noto Sans Mono",
1134 "Liberation Mono",
1135 "Ubuntu Mono",
1136 ],
1137];
1138
1139fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1142 list.iter().copied().find(|name| {
1143 db.faces()
1144 .any(|face| face.families.iter().any(|(f, _)| f == name))
1145 })
1146}
1147
1148pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1162 let system = system_fonts();
1163 let fonts = font_system_over(system.locale.clone(), system.db.clone(), &[]);
1164 (fonts, system)
1165}
1166
1167pub(crate) fn font_system_over(
1172 locale: String,
1173 db: cosmic_text::fontdb::Database,
1174 first: &[String],
1175) -> FontSystem {
1176 FontSystem::new_with_locale_and_db_and_fallback(locale, db, AppFallback::new(first))
1177}
1178
1179pub(crate) struct AppFallback {
1186 platform: cosmic_text::PlatformFallback,
1187 first: &'static [&'static str],
1188 common: &'static [&'static str],
1189 scripts: std::sync::Mutex<
1192 rustc_hash::FxHashMap<(unicode_script::Script, String), &'static [&'static str]>,
1193 >,
1194}
1195
1196fn keep(names: Vec<&'static str>) -> &'static [&'static str] {
1199 static LISTS: std::sync::Mutex<Vec<&'static [&'static str]>> =
1200 std::sync::Mutex::new(Vec::new());
1201 let mut lists = LISTS.lock().unwrap_or_else(|e| e.into_inner());
1202 if let Some(&kept) = lists.iter().find(|l| ***l == names[..]) {
1203 return kept;
1204 }
1205 let kept: &'static [&'static str] = Box::leak(names.into_boxed_slice());
1206 lists.push(kept);
1207 kept
1208}
1209
1210fn keep_name(name: &str) -> &'static str {
1211 static NAMES: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
1212 let mut names = NAMES.lock().unwrap_or_else(|e| e.into_inner());
1213 if let Some(&kept) = names.iter().find(|n| **n == name) {
1214 return kept;
1215 }
1216 let kept: &'static str = Box::leak(name.to_string().into_boxed_str());
1217 names.push(kept);
1218 kept
1219}
1220
1221impl AppFallback {
1222 pub(crate) fn new(first: &[String]) -> Self {
1223 use cosmic_text::Fallback;
1224 let platform = cosmic_text::PlatformFallback;
1225 let first = keep(first.iter().map(|n| keep_name(n)).collect());
1226 let common = keep(
1227 first
1228 .iter()
1229 .chain(platform.common_fallback())
1230 .copied()
1231 .collect(),
1232 );
1233 Self {
1234 platform,
1235 first,
1236 common,
1237 scripts: Default::default(),
1238 }
1239 }
1240}
1241
1242impl cosmic_text::Fallback for AppFallback {
1243 fn common_fallback(&self) -> &[&'static str] {
1244 self.common
1245 }
1246
1247 fn forbidden_fallback(&self) -> &[&'static str] {
1248 self.platform.forbidden_fallback()
1249 }
1250
1251 fn script_fallback(&self, script: unicode_script::Script, locale: &str) -> &[&'static str] {
1252 let theirs = self.platform.script_fallback(script, locale);
1253 if self.first.is_empty() {
1254 return theirs;
1255 }
1256 let mut scripts = self.scripts.lock().unwrap_or_else(|e| e.into_inner());
1257 scripts
1258 .entry((script, locale.to_string()))
1259 .or_insert_with(|| keep(self.first.iter().chain(theirs).copied().collect()))
1260 }
1261}
1262
1263pub(crate) struct SystemFonts {
1267 locale: String,
1268 db: cosmic_text::fontdb::Database,
1269 pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1271}
1272
1273static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1275
1276pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1280 let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1281 held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1282 .clone()
1283}
1284
1285pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1288 let fresh = std::sync::Arc::new(scan_system_fonts());
1289 *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1290 fresh
1291}
1292
1293fn scan_system_fonts() -> SystemFonts {
1294 let (locale, db) = FontSystem::new().into_locale_and_db();
1295 SystemFonts::from_db(locale, db)
1296}
1297
1298impl SystemFonts {
1299 pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1302 let all: Vec<_> = db.faces().map(|face| face.id).collect();
1303 keep_measurable(&mut db, all);
1304 pin_default_families(&mut db);
1305 let faces = db.faces().filter_map(face_file).collect();
1306 Self { locale, db, faces }
1307 }
1308
1309 #[cfg(test)]
1311 pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1312 &self.db
1313 }
1314
1315 #[cfg(test)]
1316 pub(crate) fn locale(&self) -> &str {
1317 &self.locale
1318 }
1319}
1320
1321pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1324 use cosmic_text::fontdb::Source;
1325 match &face.source {
1326 Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1327 Source::Binary(_) => None,
1328 }
1329}
1330
1331pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1335 let [sans, serif, mono] = DEFAULT_FAMILIES;
1336 if let Some(name) = first_installed(db, sans) {
1337 db.set_sans_serif_family(name);
1338 }
1339 if let Some(name) = first_installed(db, serif) {
1340 db.set_serif_family(name);
1341 }
1342 if let Some(name) = first_installed(db, mono) {
1343 db.set_monospace_family(name);
1344 }
1345}
1346
1347#[cfg(test)]
1351fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1352 let system = SystemFonts::from_db(locale, db);
1353 FontSystem::new_with_locale_and_db(system.locale, system.db)
1354}
1355
1356pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1367 use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1368 let Ok(font) = FontRef::from_index(data, index) else {
1369 return false;
1370 };
1371 font.head()
1372 .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1373 && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1374 && font.hmtx().is_ok()
1375}
1376
1377const FILES_PER_CHECKER: usize = 64;
1380
1381pub(crate) fn keep_measurable(
1393 db: &mut cosmic_text::fontdb::Database,
1394 ids: Vec<cosmic_text::fontdb::ID>,
1395) -> Vec<cosmic_text::fontdb::ID> {
1396 use cosmic_text::fontdb::{ID, Source};
1397 let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1399 let mut groups = Vec::new();
1400 for &id in &ids {
1401 match db.face(id).map(|face| &face.source) {
1402 Some(Source::File(path) | Source::SharedFile(path, _)) => {
1403 by_file.entry(path).or_default().push(id);
1404 }
1405 Some(Source::Binary(_)) => groups.push(vec![id]),
1406 None => {}
1407 }
1408 }
1409 groups.extend(by_file.into_values());
1410 let db_ref = &*db;
1411 let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1412 let mut out = Vec::new();
1413 for group in groups {
1414 let read = db_ref.with_face_data(group[0], |data, _| {
1415 group
1416 .iter()
1417 .filter(|&&id| {
1418 db_ref
1419 .face(id)
1420 .is_none_or(|face| !measurable(data, face.index))
1421 })
1422 .copied()
1423 .collect::<Vec<_>>()
1424 });
1425 out.extend(read.unwrap_or_else(|| group.clone()));
1426 }
1427 out
1428 };
1429 let checkers = std::thread::available_parallelism()
1430 .map_or(1, |n| n.get())
1431 .min(groups.len().div_ceil(FILES_PER_CHECKER));
1432 let unmeasurable = if checkers <= 1 {
1433 unmeasurable_in(&groups)
1434 } else {
1435 let per = groups.len().div_ceil(checkers);
1436 std::thread::scope(|scope| {
1437 let parts: Vec<_> = groups
1438 .chunks(per)
1439 .map(|part| {
1440 let spawned = std::thread::Builder::new()
1441 .name("kui-font-check".into())
1442 .spawn_scoped(scope, move || unmeasurable_in(part));
1443 (part, spawned.ok())
1444 })
1445 .collect();
1446 let mut out = Vec::new();
1447 for (part, checker) in parts {
1448 out.extend(match checker {
1449 Some(checker) => checker
1450 .join()
1451 .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1452 None => unmeasurable_in(part),
1454 });
1455 }
1456 out
1457 })
1458 };
1459 for &id in &unmeasurable {
1460 db.remove_face(id);
1461 }
1462 ids.into_iter()
1463 .filter(|id| !unmeasurable.contains(id))
1464 .collect()
1465}
1466
1467pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1471 let db = fs.db();
1472 [
1473 db.family_name(&cosmic_text::Family::SansSerif),
1474 db.family_name(&cosmic_text::Family::Serif),
1475 db.family_name(&cosmic_text::Family::Monospace),
1476 ]
1477}
1478
1479impl TextSystem {
1480 pub fn new() -> Self {
1481 Self {
1482 raster: Raster::new(),
1483 joins: Vec::new(),
1484 entries: FxHashMap::default(),
1485 bytes: 0,
1486 budget: DEFAULT_TEXT_CACHE_BYTES,
1487 frame: Kept::default(),
1488 places: Kept::default(),
1489 scale: 1.0,
1490 frame_no: 0,
1491 owed: false,
1492 }
1493 }
1494
1495 pub(crate) fn take_owed(&mut self) -> bool {
1498 std::mem::take(&mut self.owed)
1499 }
1500
1501 pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1504 std::mem::take(&mut self.joins)
1505 }
1506
1507 pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1509 &mut self.raster
1510 }
1511
1512 pub(crate) fn subpixel(&self) -> bool {
1513 self.raster.subpixel
1514 }
1515
1516 pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1519 let changed = self.raster.subpixel != on;
1520 self.raster.subpixel = on;
1521 changed
1522 }
1523
1524 pub fn budget(&self) -> usize {
1527 self.budget
1528 }
1529
1530 pub fn set_budget(&mut self, bytes: usize) {
1533 self.budget = bytes;
1534 }
1535
1536 pub fn bytes(&self) -> usize {
1538 self.bytes
1539 }
1540
1541 pub fn len(&self) -> usize {
1544 self.entries.len() - self.long_lines()
1545 }
1546
1547 pub fn long_lines(&self) -> usize {
1549 self.entries.values().filter(|e| e.long().is_some()).count()
1550 }
1551
1552 pub fn is_empty(&self) -> bool {
1553 self.len() == 0
1554 }
1555
1556 fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1563 if self.bytes <= self.budget {
1564 return;
1565 }
1566 let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1567 let drawn_last_frame = self.frame_no.saturating_sub(1);
1568 let mut order: Vec<(u64, u64)> = self
1573 .entries
1574 .iter()
1575 .filter(|(_, e)| e.last_used() < drawn_last_frame)
1576 .map(|(k, e)| (e.last_used(), *k))
1577 .collect();
1578 order.sort_unstable();
1579 for (_, key) in order {
1580 if self.bytes <= floor {
1581 break;
1582 }
1583 let freed = self.entries.remove(&key).map(|e| e.bytes());
1584 self.bytes -= freed.unwrap_or(0);
1585 }
1586 fs.shape_run_cache.trim(0);
1595 }
1596
1597 pub(crate) fn begin_frame(
1601 &mut self,
1602 fs: &mut FontSystem,
1603 scale: f32,
1604 keep_prev: bool,
1605 frame_no: u64,
1606 ) {
1607 self.joins.clear();
1608 if (scale - self.scale).abs() > f32::EPSILON {
1610 self.entries.clear();
1611 self.bytes = 0;
1612 }
1613 self.scale = scale;
1614 self.frame.begin(keep_prev);
1615 self.places.begin(true);
1618 self.frame_no = frame_no;
1620 if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1621 let mut freed = 0usize;
1622 self.entries.retain(|_, e| {
1623 let keep = e.last_used() >= cutoff;
1624 if !keep {
1625 freed += e.bytes();
1626 }
1627 keep
1628 });
1629 self.bytes -= freed;
1630 fs.shape_run_cache.trim(2);
1633 }
1634 self.evict_to_budget(fs);
1638 }
1639
1640 pub(crate) fn forget_shaped(&mut self) {
1643 self.entries.clear();
1644 self.bytes = 0;
1645 }
1646
1647 fn insert(&mut self, key: u64, entry: Entry) {
1649 self.bytes += entry.bytes();
1650 if let Some(old) = self.entries.insert(key, entry) {
1651 self.bytes -= old.bytes();
1652 }
1653 }
1654
1655 fn run(&self, key: u64) -> Option<&CachedText> {
1657 self.entries.get(&key)?.run()
1658 }
1659
1660 fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1661 self.entries.get_mut(&key)?.run_mut()
1662 }
1663
1664 fn long(&self, key: u64) -> Option<&LongLine> {
1666 self.entries.get(&key)?.long()
1667 }
1668
1669 fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1670 self.entries.get_mut(&key)?.long_mut()
1671 }
1672
1673 pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1674 let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1677 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1678 mix(&style.size.to_bits().to_le_bytes());
1679 mix(&style.line_height.to_bits().to_le_bytes());
1680 mix(&scale.to_bits().to_le_bytes());
1681 mix(&[style.wrap as u8, style.ellipsis as u8]);
1682 mix(&style.max_lines.to_le_bytes());
1683 let (tag, font) = match style.family {
1684 FontFamily::Sans => (0u8, 0u64),
1685 FontFamily::Serif => (1, 0),
1686 FontFamily::Mono => (2, 0),
1687 FontFamily::Custom(id) => (3, id.to_ffi()),
1688 };
1689 mix(&[tag]);
1690 mix(&font.to_le_bytes());
1691 for (t, v) in style.features.iter() {
1692 mix(t);
1693 mix(&v.to_le_bytes());
1694 }
1695 mix(&[
1699 style.underline as u8,
1700 style.strikethrough as u8,
1701 style.underline_style as u8,
1702 style.underline_color.is_some() as u8,
1703 ]);
1704 if let Some(c) = style.underline_color {
1705 mix(&c.to_hex().to_le_bytes());
1706 }
1707 h
1708 }
1709
1710 fn intern(
1714 &mut self,
1715 content: &str,
1716 style: &TextStyle,
1717 res: &Resources,
1718 fs: &mut FontSystem,
1719 ) -> u64 {
1720 let key = Self::style_key(content, style, self.scale);
1721 self.intern_keyed(key, content, style, res, fs)
1722 }
1723
1724 fn intern_keyed(
1726 &mut self,
1727 key: u64,
1728 content: &str,
1729 style: &TextStyle,
1730 res: &Resources,
1731 fs: &mut FontSystem,
1732 ) -> u64 {
1733 let frame_no = self.frame_no;
1734 let scale = self.scale;
1735 if !self.entries.contains_key(&key) {
1736 let mut buffer = new_buffer(fs, style, scale);
1737 buffer.set_text(
1741 content,
1742 &res.weights_of(style.family).apply(
1743 Attrs::new()
1744 .family(res.family_of(style.family))
1745 .font_features(cosmic_features(&style.features)),
1746 false,
1747 ),
1748 Shaping::Advanced,
1749 None,
1750 );
1751 let entry = CachedText::new(
1752 buffer,
1753 content.to_string(),
1754 style,
1755 vec![SpanDeco::of_style(style)],
1756 fs,
1757 frame_no,
1758 );
1759 self.insert(key, Entry::Run(entry));
1760 }
1761 self.entries
1762 .get_mut(&key)
1763 .expect("just inserted")
1764 .touch(frame_no);
1765 key
1766 }
1767
1768 pub(crate) fn content(&self, id: TextId) -> &str {
1770 let key = self.frame[id.0 as usize].cache_key;
1771 self.entries.get(&key).map_or("", |e| e.content())
1772 }
1773
1774 pub(crate) fn access_runs(
1780 &self,
1781 id: TextId,
1782 src: crate::access::RunSource,
1783 run_no: &mut usize,
1784 out: &mut Vec<crate::access::AccessRun>,
1785 ) {
1786 use crate::access::{RowGlyphs, push_row_runs};
1787 let Some(ft) = self.frame.get(id.0 as usize) else {
1788 return;
1789 };
1790 let line = match self.entries.get(&ft.cache_key) {
1791 Some(Entry::Run(e)) => {
1792 crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1793 return;
1794 }
1795 Some(Entry::Long(line)) => line,
1796 None => return,
1797 };
1798 let whole = [RowStart { byte: 0, x: 0.0 }];
1799 for (i, c) in line.chunks.iter().enumerate() {
1800 let Some(run) = c
1801 .width
1802 .and_then(|_| self.run(c.key))
1803 .and_then(|e| e.buffer.layout_runs().next())
1804 else {
1805 continue;
1806 };
1807 let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1810 (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1811 (&c.rows[..], row0, head_x)
1812 }
1813 (Some(_), _) => continue,
1814 (None, _) => (&whole[..], 0, line.prefix[i]),
1815 };
1816 for (r, rs) in rows.iter().enumerate() {
1817 let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1818 let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1819 let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1820 if a == b {
1821 continue;
1822 }
1823 let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1824 push_row_runs(
1825 &RowGlyphs {
1826 glyphs: &run.glyphs[a..b],
1827 text: run.text,
1828 line: 0,
1829 top: (row0 as usize + r) as f32 * line.line_h,
1830 height: line.line_h,
1831 rtl: run.rtl,
1832 dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1833 base: c.start,
1834 newline: last && src.newline_after_last,
1835 },
1836 &src,
1837 run_no,
1838 out,
1839 );
1840 }
1841 }
1842 }
1843
1844 pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1850 match self.frame.prev().get(id.0 as usize) {
1851 Some(t) => (t.cache_key, t.color),
1852 None => (0, Color::TRANSPARENT),
1853 }
1854 }
1855
1856 pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1862 let frame_no = self.frame_no;
1863 let e = self.entries.get_mut(&cache_key)?;
1864 e.touch(frame_no);
1865 let cache_key = if e.long().is_some() {
1866 self.claim_long(cache_key)
1867 } else {
1868 cache_key
1869 };
1870 self.frame.push(FrameText { cache_key, color });
1871 Some(TextId((self.frame.len() - 1) as u32))
1872 }
1873
1874 pub fn add(
1876 &mut self,
1877 content: &str,
1878 style: &TextStyle,
1879 res: &Resources,
1880 fs: &mut FontSystem,
1881 ) -> TextId {
1882 let key = self.intern_any(content, style, res, fs);
1885 let key = if could_be_long(content.len(), style) {
1886 self.claim_long(key)
1887 } else {
1888 key
1889 };
1890 self.frame.push(FrameText {
1891 cache_key: key,
1892 color: style.color_or_default(),
1893 });
1894 TextId((self.frame.len() - 1) as u32)
1895 }
1896
1897 #[inline(never)]
1906 fn claim_long(&mut self, key: u64) -> u64 {
1907 let frame_no = self.frame_no;
1908 let mut at = key;
1909 let mut n = 0u64;
1910 loop {
1911 match self.entries.get_mut(&at) {
1912 Some(Entry::Long(l)) if l.claimed != frame_no => {
1913 l.claimed = frame_no;
1914 l.last_used = frame_no;
1915 return at;
1916 }
1917 Some(Entry::Run(_)) if n == 0 => return key,
1919 Some(_) => {}
1920 None => {
1921 let Some(line) = self.long(key) else {
1922 return key;
1923 };
1924 let mut copy = line.clone();
1925 copy.claimed = frame_no;
1926 copy.last_used = frame_no;
1927 self.insert(at, Entry::Long(copy));
1928 return at;
1929 }
1930 }
1931 n += 1;
1932 at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
1933 }
1934 }
1935
1936 fn intern_any(
1942 &mut self,
1943 content: &str,
1944 style: &TextStyle,
1945 res: &Resources,
1946 fs: &mut FontSystem,
1947 ) -> u64 {
1948 if !could_be_long(content.len(), style) {
1949 return self.intern(content, style, res, fs);
1950 }
1951 let key = Self::style_key(content, style, self.scale);
1952 let long_key = key ^ LONG_SALT;
1953 let frame_no = self.frame_no;
1954 if let Some(e) = self.entries.get_mut(&long_key) {
1955 e.touch(frame_no);
1956 return long_key;
1957 }
1958 if let Some(e) = self.entries.get_mut(&key) {
1959 e.touch(frame_no);
1960 return key;
1961 }
1962 if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
1963 self.intern_keyed(key, content, style, res, fs)
1964 } else {
1965 self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
1966 }
1967 }
1968
1969 fn intern_rich_any(
1974 &mut self,
1975 spans: &[Span<'_>],
1976 base: &TextStyle,
1977 res: &Resources,
1978 fs: &mut FontSystem,
1979 ) -> u64 {
1980 let len: usize = spans.iter().map(|s| s.text.len()).sum();
1981 if !could_be_long(len, base) {
1982 return self.intern_rich(spans, base, res, fs);
1983 }
1984 let key = Self::rich_key(spans, base, self.scale);
1985 let long_key = key ^ LONG_SALT;
1986 let frame_no = self.frame_no;
1987 if let Some(e) = self.entries.get_mut(&long_key) {
1988 e.touch(frame_no);
1989 return long_key;
1990 }
1991 if let Some(e) = self.entries.get_mut(&key) {
1992 e.touch(frame_no);
1993 return key;
1994 }
1995 if spans.iter().any(|s| has_line_break(s.text))
1996 || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
1997 {
1998 return self.intern_rich_keyed(key, spans, base, res, fs);
1999 }
2000 let mut content = String::with_capacity(len);
2001 let mut owned = Vec::with_capacity(spans.len());
2002 for s in spans {
2003 let start = content.len();
2004 content.push_str(s.text);
2005 owned.push(OwnedSpan {
2006 start,
2007 end: content.len(),
2008 attrs: s.attrs_only(),
2009 });
2010 }
2011 self.build_long(long_key, content, owned, base, res, fs)
2012 }
2013
2014 fn build_long(
2019 &mut self,
2020 key: u64,
2021 content: String,
2022 spans: Vec<OwnedSpan>,
2023 style: &TextStyle,
2024 res: &Resources,
2025 fs: &mut FontSystem,
2026 ) -> u64 {
2027 let frame_no = self.frame_no;
2028 let wrap = style.wrap;
2029 let style = &TextStyle {
2030 wrap: TextWrap::None,
2031 ..*style
2032 };
2033 let scale = self.scale;
2034 let chunks: Vec<Chunk> = chunk_ranges(&content)
2035 .into_iter()
2036 .map(|(start, end)| Chunk {
2037 start,
2038 end,
2039 key: if spans.is_empty() {
2040 Self::style_key(&content[start..end], style, scale)
2041 } else {
2042 Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
2043 },
2044 width: None,
2045 tab: None,
2046 rows: Vec::new(),
2047 })
2048 .collect();
2049 let (w0, line_h) = match chunks.first() {
2052 Some(c) => {
2053 let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
2054 let e = self.run(k).expect("just interned");
2055 (e.intrinsic.w, e.buffer.metrics().line_height)
2056 }
2057 None => (0.0, style.line_height * self.scale),
2058 };
2059 let avg = chunks
2060 .first()
2061 .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
2062 let mut line = LongLine {
2063 content,
2064 spans,
2065 style: *style,
2066 wrap,
2067 wrap_w: None,
2068 starts: Vec::new(),
2069 chunks,
2070 prefix: Vec::new(),
2071 line_h,
2072 avg,
2073 last_used: frame_no,
2074 claimed: u64::MAX,
2075 laid_rows: 1,
2076 asked_rows: u32::MAX,
2077 bytes: 0,
2078 };
2079 if let Some(c) = line.chunks.first_mut() {
2080 c.width = Some(w0);
2081 }
2082 line.reprefix();
2083 line.bytes = ENTRY_BASE_BYTES
2084 + line.content.len()
2085 + line.chunks.len() * 48
2086 + line.spans.len() * std::mem::size_of::<OwnedSpan>();
2087 self.insert(key, Entry::Long(line));
2091 key
2092 }
2093
2094 #[allow(clippy::too_many_arguments)]
2098 fn shape_chunk(
2099 &mut self,
2100 content: &str,
2101 spans: &[OwnedSpan],
2102 start: usize,
2103 end: usize,
2104 style: &TextStyle,
2105 res: &Resources,
2106 fs: &mut FontSystem,
2107 ) -> u64 {
2108 if spans.is_empty() {
2109 self.intern(&content[start..end], style, res, fs)
2110 } else {
2111 let spans = chunk_spans(content, spans, start, end);
2112 self.intern_rich(&spans, style, res, fs)
2113 }
2114 }
2115
2116 fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
2120 let (chunk_key, known) = {
2121 let c = &self.long(key).expect("a long line").chunks[i];
2122 (c.key, c.width)
2123 };
2124 let frame_no = self.frame_no;
2125 if known.is_some()
2126 && let Some(e) = self.run_mut(chunk_key)
2127 {
2128 e.last_used = frame_no;
2129 return false;
2130 }
2131 let (text, spans, style, off_stops) = {
2134 let line = self.long(key).expect("a long line");
2135 let c = &line.chunks[i];
2136 let first = line.spans.partition_point(|s| s.end <= c.start);
2137 let spans: Vec<OwnedSpan> = line.spans[first..]
2138 .iter()
2139 .take_while(|s| s.start < c.end)
2140 .map(|s| OwnedSpan {
2141 start: s.start.max(c.start) - c.start,
2142 end: s.end.min(c.end) - c.start,
2143 attrs: s.attrs,
2144 })
2145 .collect();
2146 let bytes = line.content.as_bytes();
2147 let off_stops = c.start > 0 && bytes[c.start - 1] != b'\t';
2148 (
2149 line.content[c.start..c.end].to_string(),
2150 spans,
2151 line.style,
2152 off_stops,
2153 )
2154 };
2155 let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
2156 let w = self.run(k).expect("just interned").intrinsic.w;
2157 let tab = match off_stops && text.ends_with('\t') {
2158 true => self.chunk_tab(k, &text, &spans, &style, res, fs),
2159 false => None,
2160 };
2161 let line = self.long_mut(key).expect("just read");
2162 line.chunks[i].key = k;
2163 line.chunks[i].tab = tab;
2164 if line.chunks[i].width != Some(w) {
2165 line.chunks[i].width = Some(w);
2166 line.reprefix();
2167 }
2168 true
2169 }
2170
2171 #[inline(never)]
2177 #[allow(clippy::too_many_arguments)]
2178 fn chunk_tab(
2179 &mut self,
2180 chunk: u64,
2181 text: &str,
2182 spans: &[OwnedSpan],
2183 style: &TextStyle,
2184 res: &Resources,
2185 fs: &mut FontSystem,
2186 ) -> Option<(f32, f32)> {
2187 let at = text.len() - 1;
2188 let tab_x = {
2189 let run = self.run(chunk)?.buffer.layout_runs().next()?;
2190 if run.rtl {
2191 return None;
2192 }
2193 run.glyphs.iter().rev().find(|g| g.start == at)?.x
2194 };
2195 let alone = self.shape_chunk(text, spans, at, text.len(), style, res, fs);
2196 let every = self.run(alone)?.intrinsic.w;
2197 Some((tab_x, every))
2198 }
2199
2200 fn relayout_long(&mut self, key: u64, w: Option<f32>) {
2208 let Some(w) = w else {
2209 let line = self.long_mut(key).expect("a long line");
2210 line.wrap_w = None;
2211 line.starts.clear();
2212 for c in &mut line.chunks {
2213 c.rows.clear();
2214 }
2215 return;
2216 };
2217 let w = w.max(1.0);
2218 let line = self.long(key).expect("a long line");
2221 let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2222 starts.push((0, 0.0));
2223 let (mut row, mut x) = (0u32, 0.0f32);
2224 let rows: Vec<Vec<RowStart>> = line
2225 .chunks
2226 .iter()
2227 .enumerate()
2228 .map(|(i, c)| {
2229 let shaped = c.width.and_then(|_| self.run(c.key));
2230 let rows = match shaped {
2231 Some(e) => {
2232 let text = &line.content[c.start..c.end];
2233 let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2234 row += rows.len() as u32 - 1;
2235 x = end + line.tab_shift(i);
2239 rows
2240 }
2241 None => {
2242 let est = x + c
2243 .width
2244 .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2245 let added = (est / w).floor();
2246 row += added as u32;
2247 x = est - added * w;
2248 Vec::new()
2249 }
2250 };
2251 starts.push((row, x));
2252 rows
2253 })
2254 .collect();
2255 let line = self.long_mut(key).expect("a long line");
2256 line.wrap_w = Some(w);
2257 line.starts = starts;
2258 for (c, rows) in line.chunks.iter_mut().zip(rows) {
2259 c.rows = rows;
2260 }
2261 }
2262
2263 fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2267 let (wrap, width, line_h, cur) = {
2268 let l = self.long(key).expect("a long line");
2269 (l.wrap, l.width(), l.line_h, l.wrap_w)
2270 };
2271 let target = match max_w {
2272 Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2273 _ => None,
2274 };
2275 let differs = match (cur, target) {
2276 (None, None) => false,
2277 (Some(a), Some(b)) => (a - b).abs() > 0.5,
2278 _ => true,
2279 };
2280 if differs {
2281 self.relayout_long(key, target);
2282 }
2283 match target {
2284 Some(w) => {
2285 let rows = self.long(key).expect("a long line").rows();
2286 (Size::new(w, rows as f32 * line_h), rows)
2287 }
2288 None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2289 }
2290 }
2291
2292 pub fn measure(
2298 &mut self,
2299 content: &str,
2300 style: &TextStyle,
2301 res: &Resources,
2302 fs: &mut FontSystem,
2303 max_w: Option<f32>,
2304 ) -> TextMetrics {
2305 let key = self.intern_any(content, style, res, fs);
2306 self.measure_any(key, fs, max_w)
2307 }
2308
2309 fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2311 if self.long(key).is_some() {
2312 let scale = self.scale;
2313 let (size, lines) = match max_w {
2316 Some(m) => {
2317 let held = self.long(key).expect("checked").wrap_w;
2320 let measured = self.long_size(key, Some(m * scale));
2321 if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2322 self.relayout_long(key, held);
2323 }
2324 measured
2325 }
2326 None => {
2327 let line = self.long(key).expect("checked");
2328 (Size::new(line.width(), line.line_h), 1)
2329 }
2330 };
2331 return TextMetrics {
2332 width: size.w / scale,
2333 height: size.h / scale,
2334 lines,
2335 };
2336 }
2337 self.measure_key(key, fs, max_w)
2338 }
2339
2340 pub fn measure_rich(
2342 &mut self,
2343 spans: &[Span<'_>],
2344 base: &TextStyle,
2345 res: &Resources,
2346 fs: &mut FontSystem,
2347 max_w: Option<f32>,
2348 ) -> TextMetrics {
2349 let key = self.intern_rich_any(spans, base, res, fs);
2350 self.measure_any(key, fs, max_w)
2351 }
2352
2353 fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2354 let scale = self.scale;
2355 let entry = self.run_mut(key).expect("just interned");
2356 let target = wrap_target(entry, max_w, scale);
2357 let key = if entry.claimed != u64::MAX && wrap_differs(entry.wrap, target) {
2364 self.wrap_copy(key, target, fs)
2365 } else {
2366 key
2367 };
2368 let entry = self.run_mut(key).expect("just interned");
2369 wrap_entry(entry, fs, target);
2370 let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2371 if entry.clamp_w
2372 && let Some(t) = target
2373 {
2374 m.w = m.w.min(t);
2375 }
2376 TextMetrics {
2377 width: m.w / scale,
2378 height: m.h / scale,
2379 lines,
2380 }
2381 }
2382
2383 pub fn add_rich(
2387 &mut self,
2388 spans: &[Span<'_>],
2389 base: &TextStyle,
2390 res: &Resources,
2391 fs: &mut FontSystem,
2392 ) -> TextId {
2393 let key = self.intern_rich_any(spans, base, res, fs);
2394 let len = spans.iter().map(|s| s.text.len()).sum();
2395 let key = if could_be_long(len, base) {
2396 self.claim_long(key)
2397 } else {
2398 key
2399 };
2400 self.frame.push(FrameText {
2401 cache_key: key,
2402 color: base.color_or_default(),
2403 });
2404 TextId((self.frame.len() - 1) as u32)
2405 }
2406
2407 fn intern_rich(
2408 &mut self,
2409 spans: &[Span<'_>],
2410 base: &TextStyle,
2411 res: &Resources,
2412 fs: &mut FontSystem,
2413 ) -> u64 {
2414 let key = Self::rich_key(spans, base, self.scale);
2415 self.intern_rich_keyed(key, spans, base, res, fs)
2416 }
2417
2418 fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2422 let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2423 for s in spans {
2424 key = crate::key::mix_content(key, s.text.as_bytes());
2425 let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2426 mix(&[
2427 s.bold as u8,
2428 s.italic as u8,
2429 s.color.is_some() as u8,
2430 s.underline as u8,
2431 s.strikethrough as u8,
2432 s.bg.is_some() as u8,
2433 s.underline_style as u8,
2434 s.underline_color.is_some() as u8,
2435 ]);
2436 mix(&s.bg_radius.to_bits().to_le_bytes());
2437 for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2438 mix(&c.r.to_bits().to_le_bytes());
2439 mix(&c.g.to_bits().to_le_bytes());
2440 mix(&c.b.to_bits().to_le_bytes());
2441 mix(&c.a.to_bits().to_le_bytes());
2442 }
2443 }
2444 key
2445 }
2446
2447 fn intern_rich_keyed(
2449 &mut self,
2450 key: u64,
2451 spans: &[Span<'_>],
2452 base: &TextStyle,
2453 res: &Resources,
2454 fs: &mut FontSystem,
2455 ) -> u64 {
2456 let frame_no = self.frame_no;
2457 let scale = self.scale;
2458 if !self.entries.contains_key(&key) {
2459 let mut buffer = new_buffer(fs, base, scale);
2460 let family = res.family_of(base.family);
2461 let weights = res.weights_of(base.family);
2462 let features = cosmic_features(&base.features);
2463 buffer.set_rich_text(
2466 spans.iter().enumerate().map(|(i, s)| {
2467 (
2468 s.text,
2469 s.attrs(family, weights)
2470 .font_features(features.clone())
2471 .metadata(i),
2472 )
2473 }),
2474 &weights.apply(
2475 Attrs::new().family(family).font_features(features.clone()),
2476 false,
2477 ),
2478 Shaping::Advanced,
2479 None,
2480 );
2481 let content = spans.iter().map(|s| s.text).collect::<String>();
2482 let decos = spans
2483 .iter()
2484 .map(|s| SpanDeco {
2485 underline: s.underline || base.underline,
2486 underline_color: s.underline_color.or(base.underline_color),
2488 underline_style: if s.underline {
2489 s.underline_style
2490 } else {
2491 base.underline_style
2492 },
2493 strikethrough: s.strikethrough || base.strikethrough,
2494 bg: s.bg,
2495 bg_radius: s.bg_radius,
2496 })
2497 .collect();
2498 let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2499 self.insert(key, Entry::Run(entry));
2500 }
2501 self.entries
2502 .get_mut(&key)
2503 .expect("just inserted")
2504 .touch(frame_no);
2505 key
2506 }
2507
2508 fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2511 let key = self.frame[id.0 as usize].cache_key;
2512 self.run_mut(key).expect("frame text missing from cache")
2513 }
2514
2515 fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2520 let (scale, frame_no) = (self.scale, self.frame_no);
2521 let entry = self.entry_mut(id);
2522 let target = wrap_target(entry, Some(max_w_logical), scale);
2523 if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2524 self.own_wrap(id, target, fs);
2525 return;
2526 }
2527 entry.claimed = frame_no;
2528 entry.claimed_wrap = target;
2529 wrap_entry(entry, fs, target);
2530 }
2531
2532 #[inline(never)]
2540 fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2541 let frame_no = self.frame_no;
2542 let key = self.frame[id.0 as usize].cache_key;
2543 let copy = self.wrap_copy(key, target, fs);
2544 self.frame[id.0 as usize].cache_key = copy;
2545 let entry = self.run_mut(copy).expect("just made");
2546 entry.claimed = frame_no;
2547 entry.claimed_wrap = target;
2548 }
2549
2550 #[inline(never)]
2556 fn wrap_copy(&mut self, key: u64, target: Option<f32>, fs: &mut FontSystem) -> u64 {
2557 let frame_no = self.frame_no;
2558 let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2560 let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2561 if self.run(copy).is_none() {
2562 let fork = self.run(key).expect("a run").fork(frame_no);
2563 self.insert(copy, Entry::Run(fork));
2564 }
2565 let entry = self.run_mut(copy).expect("just made");
2566 entry.last_used = frame_no;
2567 wrap_entry(entry, fs, target);
2568 copy
2569 }
2570
2571 #[allow(clippy::too_many_arguments)]
2576 pub(crate) fn emit(
2577 &mut self,
2578 id: TextId,
2579 origin: Vec2,
2580 node: Size,
2581 clip: Clip,
2582 clip_id: ClipId,
2583 clips: &mut Vec<Clip>,
2584 res: &Resources,
2585 fs: &mut FontSystem,
2586 atlas: &mut GlyphAtlas,
2587 out: &mut Vec<Quad>,
2588 sel: Option<((usize, usize), Color)>,
2589 ) {
2590 let from = self.joins.len();
2591 self.emit_text(
2592 id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2593 );
2594 if self.joins.len() == from {
2595 return;
2596 }
2597 let key = self.frame[id.0 as usize].cache_key;
2600 let clamps = match self.entries.get(&key) {
2601 Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2602 Some(Entry::Run(e)) => e.clamp_w,
2603 None => false,
2604 };
2605 let own = clamps.then(|| {
2606 let ox = crate::geom::snap_px(origin.x * self.scale);
2607 (ox, ox + (node.w * self.scale).ceil())
2608 });
2609 for j in &mut self.joins[from..] {
2610 j.outer = clip_id;
2611 j.own = own;
2612 }
2613 }
2614
2615 #[allow(clippy::too_many_arguments)]
2621 #[inline(never)]
2622 fn emit_long(
2623 &mut self,
2624 key: u64,
2625 node: Size,
2626 ox: f32,
2627 oy: f32,
2628 nudge: Vec2,
2629 color: Color,
2630 clip: Clip,
2631 clip_id: ClipId,
2632 clips: &mut Vec<Clip>,
2633 res: &Resources,
2634 fs: &mut FontSystem,
2635 atlas: &mut GlyphAtlas,
2636 out: &mut Vec<Quad>,
2637 sel: Option<((usize, usize), Color)>,
2638 ) {
2639 let scale = self.scale;
2640 let line = self.long(key).expect("a long line");
2641 let (clip, clip_id) = if line.wrap == TextWrap::None {
2647 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2648 let clip = clip.intersect(own, crate::display::SQUARE);
2649 if clip.rect.w <= 0.0 || clip.rect.h <= 0.0 {
2650 return;
2651 }
2652 (clip, crate::display::intern_clip(clips, clip))
2653 } else {
2654 (clip, clip_id)
2655 };
2656 if let Some(((from, to), tint)) = sel {
2657 let rects = self.long_highlight(line, from, to);
2658 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2659 }
2660 if let Some(w) = line.wrap_w {
2661 self.emit_long_rows(
2662 key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2663 );
2664 return;
2665 }
2666 let (first, last) = {
2667 if line.chunks.is_empty() {
2668 return;
2669 }
2670 let a = line.chunk_at(clip.rect.x - ox).saturating_sub(1);
2671 let b = (line.chunk_at(clip.rect.x + clip.rect.w - ox) + 1).min(line.chunks.len() - 1);
2672 (a, b)
2673 };
2674 for i in first..=last {
2675 self.ensure_chunk(key, i, res, fs);
2676 }
2677 let frame_no = self.frame_no;
2678 self.long_mut(key).expect("checked").last_used = frame_no;
2679 for i in first..=last {
2680 let (chunk_key, x) = {
2681 let line = self.long(key).expect("checked");
2682 (line.chunks[i].key, line.prefix[i])
2683 };
2684 let raster = &mut self.raster;
2685 let entry = self
2686 .entries
2687 .get_mut(&chunk_key)
2688 .and_then(Entry::run_mut)
2689 .expect("just ensured");
2690 emit_entry(
2691 entry,
2692 ox + x,
2693 oy,
2694 nudge,
2695 color,
2696 clip,
2697 clip_id,
2698 raster,
2699 fs,
2700 atlas,
2701 out,
2702 &mut self.joins,
2703 );
2704 }
2705 }
2706
2707 #[allow(clippy::too_many_arguments)]
2709 fn emit_text(
2710 &mut self,
2711 id: TextId,
2712 origin: Vec2,
2713 node: Size,
2714 clip: Clip,
2715 clip_id: ClipId,
2716 clips: &mut Vec<Clip>,
2717 res: &Resources,
2718 fs: &mut FontSystem,
2719 atlas: &mut GlyphAtlas,
2720 out: &mut Vec<Quad>,
2721 sel: Option<((usize, usize), Color)>,
2726 ) {
2727 let FrameText {
2728 cache_key: key,
2729 color,
2730 } = self.frame[id.0 as usize];
2731 let scale = self.scale;
2732 let ox = crate::geom::snap_px(origin.x * scale);
2733 let oy = crate::geom::snap_px(origin.y * scale);
2734 let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2737 if self.long(key).is_some() {
2740 self.emit_long(
2741 key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2742 );
2743 return;
2744 }
2745 self.ensure_wrap(id, node.w, fs);
2746 let raster = &mut self.raster;
2747 let entry = self
2748 .entries
2749 .get_mut(&key)
2750 .and_then(Entry::run_mut)
2751 .expect("frame text missing from cache");
2752
2753 let (clip, clip_id) = if entry.clamp_w {
2756 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2757 let clip = clip.intersect(own, crate::display::SQUARE);
2758 (clip, crate::display::intern_clip(clips, clip))
2759 } else {
2760 (clip, clip_id)
2761 };
2762 if let Some(((from, to), tint)) = sel {
2763 let rects = Self::run_highlight(entry, from, to);
2764 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2765 }
2766 emit_entry(
2767 entry,
2768 ox,
2769 oy,
2770 nudge,
2771 color,
2772 clip,
2773 clip_id,
2774 raster,
2775 fs,
2776 atlas,
2777 out,
2778 &mut self.joins,
2779 );
2780 }
2781}
2782
2783fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2797 Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2798}
2799
2800fn push_highlight(
2803 rects: &[Rect],
2804 ox: f32,
2805 oy: f32,
2806 nudge: Vec2,
2807 tint: Color,
2808 clip: ClipId,
2809 out: &mut Vec<Quad>,
2810) {
2811 for r in rects {
2812 out.push(Quad {
2813 rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2814 color: tint,
2815 border_color: Color::TRANSPARENT,
2816 radius: [0.0; 4],
2817 border_w: 0.0,
2818 blur: 0.0,
2819 kind: QuadKind::Solid,
2820 uv: [0; 4],
2821 clip,
2822 });
2823 }
2824}
2825
2826impl TextSystem {
2827 #[allow(clippy::too_many_arguments)]
2831 fn emit_long_rows(
2832 &mut self,
2833 key: u64,
2834 w: f32,
2835 ox: f32,
2836 oy: f32,
2837 nudge: Vec2,
2838 color: Color,
2839 clip: Clip,
2840 clip_id: ClipId,
2841 res: &Resources,
2842 fs: &mut FontSystem,
2843 atlas: &mut GlyphAtlas,
2844 out: &mut Vec<Quad>,
2845 ) {
2846 let (first, last) = {
2847 let line = self.long(key).expect("a long line");
2848 let ra = ((clip.rect.y - oy) / line.line_h).floor().max(0.0) as u32;
2849 let rb = ((clip.rect.y + clip.rect.h - oy) / line.line_h)
2850 .floor()
2851 .max(0.0) as u32;
2852 let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
2853 return;
2854 };
2855 (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
2856 };
2857 let mut fresh = false;
2858 for i in first..=last {
2859 fresh |= self.ensure_chunk(key, i, res, fs);
2860 }
2861 if fresh {
2862 self.relayout_long(key, Some(w));
2865 let line = self.long_mut(key).expect("checked");
2866 let rows = line.rows();
2867 if rows != line.laid_rows && rows != line.asked_rows {
2868 line.asked_rows = rows;
2869 self.owed = true;
2870 }
2871 }
2872 let frame_no = self.frame_no;
2873 self.long_mut(key).expect("checked").last_used = frame_no;
2874 for i in first..=last {
2875 let chunk_key = self.long(key).expect("checked").chunks[i].key;
2876 let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
2880 self.entries.get_disjoint_mut([&key, &chunk_key])
2881 else {
2882 continue;
2883 };
2884 let (row0, head_x) = line.starts[i];
2885 let chunk = &line.chunks[i];
2886 if chunk.rows.is_empty() {
2887 continue;
2888 }
2889 let raster = &mut self.raster;
2890 emit_entry_rows(
2891 entry,
2892 ox,
2893 oy + row0 as f32 * line.line_h,
2894 nudge,
2895 head_x,
2896 &chunk.rows,
2897 line.line_h,
2898 color,
2899 clip,
2900 clip_id,
2901 raster,
2902 fs,
2903 atlas,
2904 out,
2905 &mut self.joins,
2906 );
2907 }
2908 }
2909}
2910
2911#[allow(clippy::too_many_arguments)]
2916fn emit_entry_rows(
2917 entry: &mut CachedText,
2918 ox: f32,
2919 oy: f32,
2920 nudge: Vec2,
2921 head_x: f32,
2922 rows: &[RowStart],
2923 line_h: f32,
2924 color: Color,
2925 clip: Clip,
2926 clip_id: ClipId,
2927 raster: &mut Raster,
2928 fs: &mut FontSystem,
2929 atlas: &mut GlyphAtlas,
2930 out: &mut Vec<Quad>,
2931 joins: &mut Vec<JoinBg>,
2932) {
2933 build_templates(entry, raster, fs, atlas);
2934 let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
2940 let (x0, x1) = (d.x, d.x + d.w);
2941 for r in 0..rows.len() {
2942 let ra = rows[r].x;
2943 let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
2944 let (a, b) = (x0.max(ra), x1.min(rb));
2945 if b <= a {
2946 continue;
2947 }
2948 let dx = if r == 0 { head_x } else { 0.0 } - ra;
2949 let x = ox + a + dx;
2950 let y = oy + d.y + r as f32 * line_h;
2951 if y >= clip.rect.y + clip.rect.h {
2952 break;
2953 }
2954 if y + d.h <= clip.rect.y
2955 || x >= clip.rect.x + clip.rect.w
2956 || x + (b - a) <= clip.rect.x
2957 {
2958 continue;
2959 }
2960 let quad = Quad {
2961 rect: if d.under {
2962 on_pixels(x, y, b - a, d.h, nudge)
2963 } else {
2964 Rect::new(x, y, b - a, d.h)
2965 },
2966 color: d.color.unwrap_or(color),
2967 border_color: Color::TRANSPARENT,
2968 radius: [0.0; 4],
2969 border_w: 0.0,
2970 blur: 0.0,
2971 kind: QuadKind::Solid,
2972 clip: clip_id,
2973 uv: [0; 4],
2974 };
2975 if d.under && d.radius > 0.0 {
2979 joins.push(JoinBg {
2980 quad: out.len() as u32,
2981 radius: d.radius,
2982 outer: clip_id,
2983 own: None,
2984 });
2985 }
2986 if d.under || d.style == UnderlineStyle::Solid {
2987 out.push(quad);
2988 } else {
2989 crate::deco::push_line(
2990 out,
2991 d.style,
2992 x,
2993 y,
2994 b - a,
2995 d.h,
2996 d.color.unwrap_or(color),
2997 clip_id,
2998 );
2999 }
3000 }
3001 };
3002 for d in entry.deco.iter().filter(|d| d.under) {
3003 row_pieces(d, out);
3004 }
3005 let mut r = 0usize;
3006 for g in &entry.glyphs {
3007 while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
3008 r += 1;
3009 }
3010 let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
3011 let x = ox + g.x + dx;
3012 let y = oy + g.y + r as f32 * line_h;
3013 if y >= clip.rect.y + clip.rect.h {
3020 break;
3021 }
3022 if y + g.h <= clip.rect.y || x >= clip.rect.x + clip.rect.w || x + g.w <= clip.rect.x {
3023 continue;
3024 }
3025 out.push(Quad {
3026 rect: Rect::new(x, y, g.w, g.h),
3027 color: g.color.unwrap_or(color),
3028 border_color: Color::TRANSPARENT,
3029 radius: [0.0; 4],
3030 border_w: 0.0,
3031 blur: 0.0,
3032 kind: g.kind,
3033 clip: clip_id,
3034 uv: g.uv,
3035 });
3036 }
3037 for d in entry.deco.iter().filter(|d| !d.under) {
3038 row_pieces(d, out);
3039 }
3040}
3041
3042fn break_rows(
3055 buffer: &Buffer,
3056 text: &str,
3057 wrap: TextWrap,
3058 w: f32,
3059 head_x: f32,
3060) -> (Vec<RowStart>, f32) {
3061 let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
3062 let Some(run) = buffer.layout_runs().next() else {
3063 return (rows, head_x);
3064 };
3065 let glyphs = run.glyphs;
3066 let blank = |g: &cosmic_text::LayoutGlyph| {
3067 text.get(g.start..g.end)
3068 .is_some_and(|s| s.chars().all(char::is_whitespace))
3069 };
3070 let spaces = wrap == TextWrap::BreakSpaces;
3071 let mut pieces: Vec<(usize, usize)> = Vec::new();
3072 match wrap {
3073 TextWrap::Word | TextWrap::BreakSpaces => {
3074 let mut at = 0usize;
3075 let mut piece = |from: usize, to: usize| {
3076 if !spaces {
3077 pieces.push((from, to));
3078 return;
3079 }
3080 let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
3083 if ink > 0 {
3084 pieces.push((from, from + ink));
3085 }
3086 for (i, c) in text[from + ink..to].char_indices() {
3087 let at = from + ink + i;
3088 pieces.push((at, at + c.len_utf8()));
3089 }
3090 };
3091 for (i, _) in unicode_linebreak::linebreaks(text) {
3092 if i > at {
3093 piece(at, i);
3094 at = i;
3095 }
3096 }
3097 if at < text.len() {
3098 piece(at, text.len());
3099 }
3100 }
3101 TextWrap::Glyph | TextWrap::None => {
3102 pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
3103 }
3104 }
3105 let mut row_x0 = 0.0f32;
3106 let mut avail = w - head_x;
3107 let mut gi = 0usize;
3108 for (s, e) in pieces {
3109 let g0 = gi;
3110 while gi < glyphs.len() && glyphs[gi].start < e {
3111 gi += 1;
3112 }
3113 if g0 == gi {
3114 continue;
3115 }
3116 let piece = &glyphs[g0..gi];
3117 let x0 = piece[0].x;
3118 let ink_end = piece
3119 .iter()
3120 .rev()
3121 .find(|g| spaces || !blank(g))
3122 .map_or(x0, |g| g.x + g.w);
3123 let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
3129 if ink_end - row_x0 > avail && opens {
3130 rows.push(RowStart {
3131 byte: s as u32,
3132 x: x0,
3133 });
3134 row_x0 = x0;
3135 avail = w;
3136 }
3137 if ink_end - row_x0 > avail {
3138 for g in piece {
3140 if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
3141 rows.push(RowStart {
3142 byte: g.start as u32,
3143 x: g.x,
3144 });
3145 row_x0 = g.x;
3146 avail = w;
3147 }
3148 }
3149 }
3150 }
3151 let end = if rows.len() == 1 {
3152 head_x + run.line_w
3153 } else {
3154 run.line_w - row_x0
3155 };
3156 (rows, end)
3157}
3158
3159fn build_templates(
3162 entry: &mut CachedText,
3163 raster: &mut Raster,
3164 fs: &mut FontSystem,
3165 atlas: &mut GlyphAtlas,
3166) {
3167 {
3168 let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
3170 if entry.glyphs_built_for != Some(built_for) {
3171 entry.glyphs.clear();
3172 entry.deco.clear();
3173 let lines = line_cap(entry.max_lines);
3174 let decorated = entry.span_deco.iter().any(SpanDeco::any);
3175 for run in entry.buffer.layout_runs().take(lines) {
3176 if decorated {
3177 build_decorations(&run, &entry.span_deco, fs, &mut entry.deco);
3178 }
3179 for glyph in run.glyphs.iter() {
3180 let physical = glyph.physical((0.0, 0.0), 1.0);
3181 let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
3182 continue;
3183 };
3184 entry.glyphs.push(GlyphTemplate {
3185 x: physical.x as f32 + slot.left as f32,
3186 y: run.line_y.round() + physical.y as f32 - slot.top as f32,
3187 w: slot.w as f32,
3188 h: slot.h as f32,
3189 uv: [slot.x, slot.y, slot.w, slot.h],
3190 kind: glyph_kind(&slot),
3191 color: glyph
3192 .color_opt
3193 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3194 byte: glyph.start as u32,
3195 });
3196 }
3197 }
3198 entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
3202 }
3203 }
3204}
3205
3206#[allow(clippy::too_many_arguments)]
3210fn emit_entry(
3211 entry: &mut CachedText,
3212 ox: f32,
3213 oy: f32,
3214 nudge: Vec2,
3215 color: Color,
3216 clip: Clip,
3217 clip_id: ClipId,
3218 raster: &mut Raster,
3219 fs: &mut FontSystem,
3220 atlas: &mut GlyphAtlas,
3221 out: &mut Vec<Quad>,
3222 joins: &mut Vec<JoinBg>,
3223) {
3224 build_templates(entry, raster, fs, atlas);
3225 {
3226 let inside = |x: f32, y: f32, w: f32, h: f32| {
3227 oy + y + h >= clip.rect.y
3228 && oy + y <= clip.rect.y + clip.rect.h
3229 && ox + x < clip.rect.x + clip.rect.w
3230 && ox + x + w > clip.rect.x
3231 };
3232 let deco_quad = |d: &DecoTemplate| Quad {
3233 rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
3234 color: d.color.unwrap_or(color),
3235 border_color: Color::TRANSPARENT,
3236 radius: [0.0; 4],
3237 border_w: 0.0,
3238 blur: 0.0,
3239 kind: QuadKind::Solid,
3240 clip: clip_id,
3241 uv: [0; 4],
3242 };
3243 for d in entry
3248 .deco
3249 .iter()
3250 .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3251 {
3252 if d.radius > 0.0 {
3253 joins.push(JoinBg {
3254 quad: out.len() as u32,
3255 radius: d.radius,
3256 outer: clip_id,
3257 own: None,
3258 });
3259 }
3260 out.push(Quad {
3261 rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3262 ..deco_quad(d)
3263 });
3264 }
3265
3266 out.extend(
3270 entry
3271 .glyphs
3272 .iter()
3273 .filter(|g| {
3274 oy + g.y + g.h >= clip.rect.y
3275 && ox + g.x < clip.rect.x + clip.rect.w
3276 && ox + g.x + g.w > clip.rect.x
3277 })
3278 .take_while(|g| oy + g.y <= clip.rect.y + clip.rect.h)
3279 .map(|g| Quad {
3280 rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3281 color: g.color.unwrap_or(color),
3282 border_color: Color::TRANSPARENT,
3283 radius: [0.0; 4],
3284 border_w: 0.0,
3285 blur: 0.0,
3286 kind: g.kind,
3287 clip: clip_id,
3288 uv: g.uv,
3289 }),
3290 );
3291 for d in entry
3292 .deco
3293 .iter()
3294 .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3295 {
3296 match d.style {
3299 UnderlineStyle::Solid => out.push(deco_quad(d)),
3300 style => crate::deco::push_line(
3301 out,
3302 style,
3303 ox + d.x,
3304 oy + d.y,
3305 d.w,
3306 d.h,
3307 d.color.unwrap_or(color),
3308 clip_id,
3309 ),
3310 }
3311 }
3312 }
3313}
3314
3315fn could_be_long(len: usize, style: &TextStyle) -> bool {
3323 (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3324 && style.max_lines == 0
3325 && !style.ellipsis
3326}
3327
3328fn has_line_break(content: &str) -> bool {
3331 content.bytes().any(|b| b == b'\n' || b == b'\r')
3332}
3333
3334fn has_rtl(content: &str) -> bool {
3342 !content.is_ascii()
3343 && content.chars().any(|c| {
3344 matches!(c as u32,
3345 0x0590..=0x08FF
3346 | 0x200F | 0x202B | 0x202E | 0x2067
3347 | 0xFB1D..=0xFDFF
3348 | 0xFE70..=0xFEFF
3349 | 0x10800..=0x10FFF
3350 | 0x1E800..=0x1EFFF)
3351 })
3352}
3353
3354fn build_decorations(
3365 run: &cosmic_text::LayoutRun<'_>,
3366 spans: &[SpanDeco],
3367 fs: &mut FontSystem,
3368 out: &mut Vec<DecoTemplate>,
3369) {
3370 let mut last_bg: Option<usize> = None;
3373 let mut i = 0;
3374 while i < run.glyphs.len() {
3375 let span_no = run.glyphs[i].metadata;
3376 let mut j = i + 1;
3377 while j < run.glyphs.len() && run.glyphs[j].metadata == span_no {
3378 j += 1;
3379 }
3380 let deco = spans.get(span_no).copied().unwrap_or_default();
3381 if deco.any() {
3382 let group = &run.glyphs[i..j];
3383 let x0 = group.iter().map(|g| g.x).fold(f32::INFINITY, f32::min);
3384 let x1 = group
3385 .iter()
3386 .map(|g| g.x + g.w)
3387 .fold(f32::NEG_INFINITY, f32::max);
3388 let w = (x1 - x0).max(0.0);
3389 match (deco.bg, last_bg.map(|k| &mut out[k])) {
3390 (Some(bg), Some(prev))
3391 if prev.color == Some(bg)
3392 && prev.radius == deco.bg_radius
3393 && (prev.x + prev.w - x0).abs() < 0.01 =>
3394 {
3395 prev.w = (x1 - prev.x).max(prev.w);
3396 }
3397 (Some(bg), _) => {
3398 last_bg = Some(out.len());
3399 out.push(DecoTemplate {
3400 x: x0,
3401 y: run.line_top,
3402 w,
3403 h: run.line_height,
3404 color: Some(bg),
3405 under: true,
3406 style: UnderlineStyle::Solid,
3407 radius: deco.bg_radius,
3408 });
3409 }
3410 (None, _) => last_bg = None,
3411 }
3412 if deco.underline || deco.strikethrough {
3413 let first = &group[0];
3414 let metrics = fs
3415 .get_font(first.font_id, first.font_weight)
3416 .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3417 let (under_off, strike_off, stroke) = match metrics {
3420 Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3421 None => (
3422 -0.1 * first.font_size,
3423 0.3 * first.font_size,
3424 0.06 * first.font_size,
3425 ),
3426 };
3427 let stroke = stroke.max(1.0).round();
3428 let baseline = run.line_y.round();
3429 if deco.underline {
3430 out.push(DecoTemplate {
3431 x: x0,
3432 y: (baseline - under_off).round(),
3433 w,
3434 h: stroke,
3435 color: deco.underline_color.or_else(|| {
3436 first
3437 .color_opt
3438 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a()))
3439 }),
3440 under: false,
3441 style: deco.underline_style,
3442 radius: 0.0,
3443 });
3444 }
3445 if deco.strikethrough {
3446 out.push(DecoTemplate {
3447 x: x0,
3448 y: (baseline - strike_off).round(),
3449 w,
3450 h: stroke,
3451 color: first
3452 .color_opt
3453 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3454 under: false,
3455 style: UnderlineStyle::Solid,
3456 radius: 0.0,
3457 });
3458 }
3459 }
3460 } else {
3461 last_bg = None;
3462 }
3463 i = j;
3464 }
3465}
3466
3467impl TextSystem {
3468 pub(crate) fn place(
3473 &mut self,
3474 key: Key,
3475 ancestry: &Ancestry,
3476 id: TextId,
3477 origin: Vec2,
3478 scope: Option<Key>,
3479 drawn: bool,
3480 ) {
3481 let cache_key = self.frame[id.0 as usize].cache_key;
3482 self.places.push(TextPlace {
3483 key,
3484 ancestors: ancestry.keys,
3485 depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3486 none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3487 cache_key,
3488 origin,
3489 scope,
3490 drawn,
3491 });
3492 }
3493
3494 fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3500 let list = if prev {
3501 self.places.prev()
3502 } else {
3503 &self.places
3504 };
3505 let mut base = 0usize;
3506 let mut out = Vec::new();
3507 for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3508 let Some(entry) = self.entries.get(&place.cache_key) else {
3509 continue;
3510 };
3511 out.push((place, entry, base));
3512 base += entry.content().len();
3513 }
3514 out
3515 }
3516
3517 pub(crate) fn content_len(&self, id: TextId) -> usize {
3520 let key = self.frame[id.0 as usize].cache_key;
3521 self.entries.get(&key).map_or(0, |e| e.content().len())
3522 }
3523
3524 pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3533 let list = if prev {
3534 self.places.prev()
3535 } else {
3536 &self.places
3537 };
3538 let mut base = 0usize;
3539 let mut out = Vec::new();
3540 for place in list.iter().filter(|p| p.scope == Some(scope)) {
3541 let Some(text) = self.entries.get(&place.cache_key) else {
3542 continue;
3543 };
3544 let len = text.content().len();
3545 out.push(ScopeRun { place, text, base });
3546 base += len;
3547 }
3548 out
3549 }
3550
3551 pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3558 let runs = self.scope_runs(scope, prev);
3559 let px = point.x * self.scale;
3560 let py = point.y * self.scale;
3561 let gap = |r: &Rect| {
3562 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3563 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3564 (dy, dx)
3565 };
3566 let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3567 let ga = gap(&self.scope_box(a));
3568 let gb = gap(&self.scope_box(b));
3569 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3570 })?;
3571 let byte = self.hit_in_run(run, point)?;
3572 Some((run.place.key, byte))
3573 }
3574
3575 fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3577 self.entry_box(run.place, run.text)
3578 }
3579
3580 fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
3585 match entry {
3586 Entry::Run(e) => self.physical_box(place, e),
3587 Entry::Long(l) => {
3588 let (ox, oy) = self.physical_origin(place);
3589 let w = l.wrap_w.unwrap_or_else(|| l.width());
3590 Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
3591 }
3592 }
3593 }
3594
3595 fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
3597 let (_, oy) = self.physical_origin(place);
3598 match entry {
3599 Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
3600 Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
3601 }
3602 }
3603
3604 fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
3607 match run.text {
3608 Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
3609 Entry::Run(e) => {
3610 let (ox, oy) = self.physical_origin(run.place);
3611 let cursor = e
3612 .buffer
3613 .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
3614 Some(e.byte_of(cursor))
3615 }
3616 }
3617 }
3618
3619 pub(crate) fn word_at(
3626 &self,
3627 scope: Key,
3628 node: Key,
3629 byte: usize,
3630 prev: bool,
3631 ) -> Option<(usize, usize)> {
3632 let run = self
3633 .scope_runs(scope, prev)
3634 .into_iter()
3635 .find(|r| r.place.key == node)?;
3636 let content = run.text.content();
3637 Some(word_range(content, byte))
3638 }
3639
3640 pub(crate) fn scope_offset(
3646 &self,
3647 scope: Key,
3648 node: Key,
3649 byte: usize,
3650 prev: bool,
3651 ) -> Option<usize> {
3652 let run = self
3653 .scope_runs(scope, prev)
3654 .into_iter()
3655 .find(|r| r.place.key == node)?;
3656 Some(run.base + byte.min(run.text.content().len()))
3657 }
3658
3659 pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3674 let (from, to) = (from.min(to), from.max(to));
3675 let mut out = String::new();
3676 let mut prev_run: Option<&ScopeRun<'_>> = None;
3677 for run in &self.scope_runs(scope, prev) {
3678 let (start, end) = run.span();
3679 if end <= from || start >= to {
3680 continue;
3681 }
3682 if let Some(p) = prev_run
3683 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3684 {
3685 out.push_str("<br>");
3686 }
3687 let content = run.text.content();
3688 let lo = floor_boundary(content, from.saturating_sub(start));
3689 let hi = floor_boundary(content, (to - start).min(content.len()));
3690 match run.text {
3691 Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
3692 Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
3693 }
3694 prev_run = Some(run);
3695 }
3696 out
3697 }
3698
3699 fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
3705 for c in &line.chunks {
3706 let (a, b) = (lo.max(c.start), hi.min(c.end));
3707 if a >= b {
3708 continue;
3709 }
3710 match c.width.and_then(|_| self.run(c.key)) {
3711 Some(e) => html_of_run(e, a - c.start, b - c.start, out),
3712 None => escape_into(&line.content[a..b], out),
3713 }
3714 }
3715 }
3716
3717 pub(crate) fn scope_selection_anchor(
3727 &self,
3728 scope: Key,
3729 from: usize,
3730 to: usize,
3731 prev: bool,
3732 ) -> Option<Vec2> {
3733 let (from, to) = (from.min(to), from.max(to));
3734 for run in self.scope_runs(scope, prev) {
3735 let (start, end) = run.span();
3736 if !run.place.drawn || end <= from || start >= to {
3737 continue;
3738 }
3739 let content = run.text.content();
3740 let lo = floor_boundary(content, from.saturating_sub(start));
3741 let hi = floor_boundary(content, (to - start).min(content.len()));
3742 let (ox, oy) = self.physical_origin(run.place);
3743 match run.text {
3744 Entry::Run(e) => {
3745 if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
3746 return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
3747 }
3748 }
3749 Entry::Long(l) => {
3750 if let Some((x, y)) = self.long_caret_local(l, lo) {
3751 return Some(Vec2::new(
3752 (ox + x) / self.scale,
3753 (oy + y + self.long_baseline(l)) / self.scale,
3754 ));
3755 }
3756 }
3757 }
3758 }
3759 None
3760 }
3761
3762 fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
3765 let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
3766 for run in entry.buffer.layout_runs() {
3767 if run.line_i < a.line || run.line_i > b.line {
3768 continue;
3769 }
3770 if let Some((x, _)) = run.highlight(a, b).next() {
3771 return Some((x, run.line_y));
3772 }
3773 }
3774 None
3775 }
3776
3777 pub(crate) fn scope_selection_rect(
3783 &self,
3784 scope: Key,
3785 from: usize,
3786 to: usize,
3787 prev: bool,
3788 ) -> Option<Rect> {
3789 let (from, to) = (from.min(to), from.max(to));
3790 let mut out: Option<Rect> = None;
3791 for run in self.scope_runs(scope, prev) {
3792 let (start, end) = run.span();
3793 if !run.place.drawn || end <= from || start >= to {
3794 continue;
3795 }
3796 let content = run.text.content();
3797 let lo = floor_boundary(content, from.saturating_sub(start));
3798 let hi = floor_boundary(content, (to - start).min(content.len()));
3799 let (ox, oy) = self.physical_origin(run.place);
3805 let rects = match run.text {
3806 Entry::Long(l) => self.long_highlight(l, lo, hi),
3807 Entry::Run(e) => Self::run_highlight(e, lo, hi),
3808 };
3809 for r in rects {
3810 let r = Rect::new(
3811 (ox + r.x) / self.scale,
3812 (oy + r.y) / self.scale,
3813 r.w / self.scale,
3814 r.h / self.scale,
3815 );
3816 out = Some(match out {
3817 None => r,
3818 Some(o) => o.union(&r),
3819 });
3820 }
3821 }
3822 out
3823 }
3824
3825 pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3830 let (from, to) = (from.min(to), from.max(to));
3831 let runs = self.scope_runs(scope, prev);
3832 let mut out = String::new();
3833 let mut reserved = false;
3834 let mut prev_run: Option<&ScopeRun<'_>> = None;
3835 for run in &runs {
3836 let (start, end) = run.span();
3837 if end <= from || start >= to {
3838 continue;
3839 }
3840 if !reserved {
3841 out.reserve(to - from + runs.len());
3842 reserved = true;
3843 }
3844 if let Some(p) = prev_run
3845 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3846 {
3847 out.push('\n');
3848 }
3849 let content = run.text.content();
3850 let lo = from.saturating_sub(start).min(content.len());
3851 let hi = (to - start).min(content.len());
3852 out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
3853 prev_run = Some(run);
3854 }
3855 out
3856 }
3857
3858 fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
3862 let (ox, oy) = self.physical_origin(place);
3863 let px = point.x * self.scale - ox;
3864 let py = point.y * self.scale - oy;
3865 if line.chunks.is_empty() {
3866 return Some(TextHit { byte: 0, line: 0 });
3867 }
3868 if let Some(w) = line.wrap_w {
3869 return self.long_hit_wrapped(line, w, px, py);
3870 }
3871 if px >= line.width() {
3872 return Some(TextHit {
3873 byte: line.content.len(),
3874 line: 0,
3875 });
3876 }
3877 let i = line.chunk_at(px.max(0.0));
3878 let c = &line.chunks[i];
3879 let local = px - line.prefix[i];
3880 let byte = match self.run(c.key) {
3881 Some(_) if let Some(b) = line.placed_tab_hit(i, local) => b,
3882 Some(e) if c.width.is_some() => {
3883 let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
3884 c.start + cursor.index.min(c.end - c.start)
3885 }
3886 _ => {
3887 let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
3888 let mut b = (c.start + est).min(c.end);
3889 while !line.content.is_char_boundary(b) {
3890 b -= 1;
3891 }
3892 b
3893 }
3894 };
3895 Some(TextHit { byte, line: 0 })
3896 }
3897
3898 fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
3902 let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
3903 let Some((a, b)) = line.chunks_on_rows(row, row) else {
3904 return Some(TextHit {
3905 byte: line.content.len(),
3906 line: row,
3907 });
3908 };
3909 let i = (a..=b)
3912 .find(|&j| {
3913 let (r1, end_x) = line.starts[j + 1];
3914 px < if r1 == row { end_x } else { w }
3915 })
3916 .unwrap_or(b);
3917 let c = &line.chunks[i];
3918 let (row0, head_x) = line.starts[i];
3919 let r = (row - row0) as usize;
3920 let byte = match self.run(c.key) {
3921 Some(e) if r < c.rows.len() => {
3922 let rs = c.rows[r];
3923 let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
3924 let lo = rs.byte as usize;
3925 let hi = c
3926 .rows
3927 .get(r + 1)
3928 .map_or(c.end - c.start, |n| n.byte as usize);
3929 let tab_here = (lo..hi).contains(&(c.end - c.start - 1));
3931 match line.placed_tab_hit(i, local_x) {
3932 Some(b) if tab_here => b,
3933 _ => {
3934 let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
3935 c.start + cursor.index.clamp(lo, hi)
3936 }
3937 }
3938 }
3939 _ => {
3940 let linear = r as f32 * w + px - head_x;
3943 let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
3944 let mut b = (c.start + est).min(c.end);
3945 while !line.content.is_char_boundary(b) {
3946 b -= 1;
3947 }
3948 b
3949 }
3950 };
3951 let next_row = self
3956 .long_caret_local(line, byte)
3957 .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
3958 let byte = if next_row && byte > 0 {
3959 floor_boundary(&line.content, byte - 1)
3960 } else {
3961 byte
3962 };
3963 Some(TextHit { byte, line: row })
3964 }
3965
3966 fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
3969 let (ox, oy) = self.physical_origin(place);
3970 let scale = self.scale;
3971 let (x, y) = self.long_caret_local(line, byte)?;
3972 Some(Rect::new(
3973 (ox + x) / scale,
3974 (oy + y) / scale,
3975 0.0,
3976 line.line_h / scale,
3977 ))
3978 }
3979
3980 fn long_baseline(&self, line: &LongLine) -> f32 {
3986 line.chunks
3987 .first()
3988 .and_then(|c| self.run(c.key))
3989 .and_then(|e| e.buffer.layout_runs().next())
3990 .map_or(line.line_h * 0.8, |r| r.line_y.round())
3991 }
3992
3993 fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
3998 let byte = byte.min(line.content.len());
3999 if let Some(w) = line.wrap_w
4000 && !line.chunks.is_empty()
4001 {
4002 let i = line.chunk_of_byte(byte);
4003 let c = &line.chunks[i];
4004 let local = byte - c.start;
4005 let (row0, head_x) = line.starts[i];
4006 let (r, x) = match self.run(c.key) {
4007 Some(e) if !c.rows.is_empty() => {
4008 let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
4009 let run = e.buffer.layout_runs().next()?;
4010 let cx = caret_x(&run, local.min(c.end - c.start)) + line.end_shift(i, local);
4011 (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
4012 }
4013 _ => {
4014 let linear = head_x + local as f32 * line.avg;
4015 let r = (linear / w).floor();
4016 (r as usize, linear - r * w)
4017 }
4018 };
4019 let y = (row0 as usize + r) as f32 * line.line_h;
4020 return Some((x, y));
4021 }
4022 let x = if line.chunks.is_empty() {
4023 0.0
4024 } else {
4025 let i = line.chunk_of_byte(byte);
4026 let c = &line.chunks[i];
4027 let local = byte - c.start;
4028 match self.run(c.key) {
4029 Some(e) if c.width.is_some() => {
4030 let run = e.buffer.layout_runs().next()?;
4031 let x = caret_x(&run, local.min(c.end - c.start));
4032 line.prefix[i] + x + line.end_shift(i, local)
4033 }
4034 _ => line.prefix[i] + local as f32 * line.avg,
4035 }
4036 };
4037 Some((x, 0.0))
4038 }
4039
4040 fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
4046 let Some((x0, y0)) = self.long_caret_local(line, from) else {
4047 return Vec::new();
4048 };
4049 let Some((x1, y1)) = self.long_caret_local(line, to) else {
4050 return Vec::new();
4051 };
4052 let h = line.line_h;
4053 if (y1 - y0).abs() < f32::EPSILON {
4054 return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
4055 }
4056 let w = line.wrap_w.unwrap_or_else(|| line.width());
4058 let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
4059 let mut y = y0 + h;
4060 while y < y1 - h / 2.0 {
4061 out.push(Rect::new(0.0, y, w, h));
4062 y += h;
4063 }
4064 out.push(Rect::new(0.0, y1, x1.max(0.0), h));
4065 out
4066 }
4067
4068 fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
4074 let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
4075 let mut out = Vec::new();
4076 for run in entry.buffer.layout_runs() {
4077 if run.line_i < a.line || run.line_i > b.line {
4078 continue;
4079 }
4080 let h = entry.buffer.metrics().line_height;
4081 let mut any = false;
4082 for (x, w) in run.highlight(a, b) {
4083 any = true;
4084 out.push(Rect::new(x, run.line_top, w.max(2.0), h));
4085 }
4086 if !any && run.glyphs.is_empty() && b.line > run.line_i {
4089 out.push(Rect::new(0.0, run.line_top, 2.0, h));
4090 }
4091 }
4092 out
4093 }
4094
4095 fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
4098 (
4099 crate::geom::snap_px(place.origin.x * self.scale),
4100 crate::geom::snap_px(place.origin.y * self.scale),
4101 )
4102 }
4103
4104 fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
4106 let (ox, oy) = self.physical_origin(place);
4107 let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
4108 Rect::new(ox, oy, size.w, size.h)
4109 }
4110
4111 pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
4115 let runs = self.runs_of(key, prev);
4116 if runs.is_empty() {
4117 return None;
4118 }
4119 let px = point.x * self.scale;
4120 let py = point.y * self.scale;
4121 let gap = |r: &Rect| {
4124 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
4125 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
4126 (dy, dx)
4127 };
4128 let (place, entry, base) = runs
4129 .iter()
4130 .min_by(|a, b| {
4131 let ga = gap(&self.entry_box(a.0, a.1));
4132 let gb = gap(&self.entry_box(b.0, b.1));
4133 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
4134 })
4135 .copied()?;
4136 let (ox, oy) = self.physical_origin(place);
4137 let (byte, hit_top) = match entry {
4138 Entry::Long(line) => {
4139 let hit = self.long_hit(place, line, point)?;
4140 (hit.byte, oy + hit.line as f32 * line.line_h)
4141 }
4142 Entry::Run(entry) => {
4143 let bx = self.physical_box(place, entry);
4150 let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
4151 let cursor = entry.buffer.hit(px - ox, py - oy)?;
4152 let row =
4153 visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
4154 let top = entry
4155 .buffer
4156 .layout_runs()
4157 .nth(row)
4158 .map_or(0.0, |r| r.line_top);
4159 (entry.byte_of(cursor), oy + top)
4160 }
4161 };
4162 let mut tops: Vec<f32> = runs
4167 .iter()
4168 .flat_map(|(p, e, _)| self.row_tops(p, e))
4169 .collect();
4170 tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4171 tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
4172 let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
4173 Some(TextHit {
4174 byte: base + byte,
4175 line: line as u32,
4176 })
4177 }
4178
4179 pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
4183 let runs = self.runs_of(key, prev);
4184 let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
4185 let byte = byte.min(total);
4186 let (place, entry, base) = runs
4187 .iter()
4188 .find(|(_, e, base)| byte < base + e.content().len())
4189 .or_else(|| runs.last())
4190 .copied()?;
4191 let byte = (byte - base).min(entry.content().len());
4192 let entry = match entry {
4193 Entry::Long(line) => return self.long_caret(place, line, byte),
4194 Entry::Run(entry) => entry,
4195 };
4196 let (ox, oy) = self.physical_origin(place);
4197 let cursor = entry.cursor_of(byte);
4198 let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
4199 let run = entry.buffer.layout_runs().nth(run_no)?;
4200 let x = caret_x(&run, cursor.index);
4201 let scale = self.scale;
4202 Some(Rect::new(
4203 (ox + x) / scale,
4204 (oy + run.line_top) / scale,
4205 0.0,
4206 run.line_height / scale,
4207 ))
4208 }
4209}
4210
4211fn word_range(content: &str, byte: usize) -> (usize, usize) {
4221 if content.is_empty() {
4222 return (0, 0);
4223 }
4224 #[derive(PartialEq)]
4225 enum Class {
4226 Word,
4227 Space,
4228 Other,
4229 }
4230 let class = |c: char| {
4231 if c.is_alphanumeric() || c == '_' {
4232 Class::Word
4233 } else if c.is_whitespace() {
4234 Class::Space
4235 } else {
4236 Class::Other
4237 }
4238 };
4239 let at = floor_boundary(content, byte.min(content.len()));
4242 let at = if at >= content.len() {
4243 content.char_indices().next_back().map_or(0, |(i, _)| i)
4244 } else {
4245 at
4246 };
4247 let Some(here) = content[at..].chars().next().map(class) else {
4248 return (at, at);
4249 };
4250 let mut start = at;
4251 for (i, c) in content[..at].char_indices().rev() {
4252 if class(c) != here {
4253 break;
4254 }
4255 start = i;
4256 }
4257 let mut end = at;
4258 for (i, c) in content[at..].char_indices() {
4259 if class(c) != here {
4260 break;
4261 }
4262 end = at + i + c.len_utf8();
4263 }
4264 (start, end)
4265}
4266
4267fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4272 let starts = line_starts(&entry.buffer, &entry.content);
4273 for (li, line) in entry.buffer.lines.iter().enumerate() {
4274 let base = starts.get(li).copied().unwrap_or(0);
4275 let text = line.text();
4276 if li > 0 {
4277 if base > lo && base <= hi {
4280 out.push_str("<br>");
4281 }
4282 }
4283 let attrs_list = line.attrs_list();
4289 let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4290 for (i, _) in text.char_indices() {
4291 let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4292 match spans.last_mut() {
4293 Some((_, end, prev)) if *prev == a && *end == i => {
4294 *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4295 }
4296 _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4297 }
4298 }
4299 for (start, end, attrs) in &spans {
4300 let (s, e) = (base + start, base + end);
4301 let (s, e) = (s.max(lo), e.min(hi));
4302 if s >= e {
4303 continue;
4304 }
4305 let piece = &entry.content[s..e];
4306 let bold = attrs.weight > attrs_list.defaults().weight
4311 || attrs
4312 .cache_key_flags
4313 .contains(crate::weights::SYNTHETIC_BOLD);
4314 let italic = attrs.style != cosmic_text::Style::Normal;
4315 if let Some(c) = attrs.color_opt {
4316 let _ = std::fmt::Write::write_fmt(
4317 out,
4318 format_args!(
4319 "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4320 c.r(),
4321 c.g(),
4322 c.b()
4323 ),
4324 );
4325 }
4326 if bold {
4327 out.push_str("<b>");
4328 }
4329 if italic {
4330 out.push_str("<i>");
4331 }
4332 escape_into(piece, out);
4333 if italic {
4334 out.push_str("</i>");
4335 }
4336 if bold {
4337 out.push_str("</b>");
4338 }
4339 if attrs.color_opt.is_some() {
4340 out.push_str("</span>");
4341 }
4342 }
4343 }
4344}
4345
4346fn escape_into(s: &str, out: &mut String) {
4350 for ch in s.chars() {
4351 match ch {
4352 '&' => out.push_str("&"),
4353 '<' => out.push_str("<"),
4354 '>' => out.push_str(">"),
4355 _ => out.push(ch),
4356 }
4357 }
4358}
4359
4360fn floor_boundary(s: &str, i: usize) -> usize {
4366 let mut i = i.min(s.len());
4367 while i > 0 && !s.is_char_boundary(i) {
4368 i -= 1;
4369 }
4370 i
4371}
4372
4373fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4374 paragraph_starts(buffer, content).collect()
4375}
4376
4377fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4383 let mut at = 0usize;
4384 buffer.lines.iter().map(move |line| {
4385 let start = at.min(content.len());
4386 at += line.text().len();
4387 let rest = &content[at.min(content.len())..];
4388 for ending in ["\r\n", "\n\r", "\n", "\r"] {
4389 if rest.starts_with(ending) {
4390 at += ending.len();
4391 break;
4392 }
4393 }
4394 start
4395 })
4396}
4397
4398fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4408 let mut last_of_line = None;
4409 let mut before = None;
4410 for (n, run) in buffer.layout_runs().enumerate() {
4411 if run.line_i != line_i {
4412 if last_of_line.is_some() {
4413 break;
4414 }
4415 continue;
4416 }
4417 last_of_line = Some(n);
4418 if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4419 return Some((n, n));
4420 }
4421 if run
4422 .glyphs
4423 .iter()
4424 .map(|g| g.start)
4425 .min()
4426 .is_some_and(|s| s <= index)
4427 {
4428 before = Some(n);
4429 }
4430 }
4431 before.or(last_of_line).map(|n| (n, n))
4432}
4433
4434fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4438 if let Some(g) = run
4439 .glyphs
4440 .iter()
4441 .find(|g| g.start <= index && index < g.end)
4442 {
4443 return if g.level.is_rtl() { g.x + g.w } else { g.x };
4444 }
4445 match run.glyphs.last() {
4446 Some(g) if !g.level.is_rtl() => g.x + g.w,
4447 Some(g) => g.x,
4448 None => 0.0,
4449 }
4450}
4451
4452impl Default for TextSystem {
4453 fn default() -> Self {
4454 Self::new()
4455 }
4456}
4457
4458fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4461 let max_w = max_w_logical?;
4462 let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4463 (!intrinsic_fits).then_some(max_w * scale)
4464}
4465
4466pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4474 match (current, target) {
4475 (None, None) => false,
4476 (Some(a), Some(b)) => (a - b).abs() > 0.5,
4477 _ => true,
4478 }
4479}
4480
4481fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4482 if wrap_differs(entry.wrap, target) {
4483 entry.buffer.set_size(target, None);
4484 entry.buffer.shape_until_scroll(fs, false);
4485 entry.wrap = target;
4486 }
4487}
4488
4489impl CachedText {
4490 fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4493 let start = paragraph_starts(&self.buffer, &self.content)
4494 .nth(cursor.line)
4495 .unwrap_or(0);
4496 (start + cursor.index).min(self.content.len())
4497 }
4498
4499 fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4505 let mut li = 0;
4506 let mut start = 0;
4507 for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4508 if s <= byte {
4509 (li, start) = (i, s);
4510 } else {
4511 break;
4512 }
4513 }
4514 let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4515 cosmic_text::Cursor::new(li, (byte - start).min(len))
4516 }
4517
4518 fn new(
4519 mut buffer: Buffer,
4520 content: String,
4521 style: &TextStyle,
4522 span_deco: Vec<SpanDeco>,
4523 fs: &mut FontSystem,
4524 frame_no: u64,
4525 ) -> Self {
4526 buffer.shape_until_scroll(fs, false);
4527 let max_lines = line_budget(style);
4528 let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4529 let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4530 Self {
4531 buffer,
4532 bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4533 content,
4534 wrap: None,
4535 intrinsic,
4536 max_lines,
4537 clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4538 last_used: frame_no,
4539 glyphs: Vec::new(),
4540 deco: Vec::new(),
4541 span_deco,
4542 glyphs_built_for: None,
4543 claimed: u64::MAX,
4544 claimed_wrap: None,
4545 breaks_anywhere: style.wrap == TextWrap::Glyph,
4546 min_content: None,
4547 }
4548 }
4549
4550 fn fork(&self, frame_no: u64) -> Self {
4554 Self {
4555 buffer: self.buffer.clone(),
4556 content: self.content.clone(),
4557 wrap: self.wrap,
4558 intrinsic: self.intrinsic,
4559 max_lines: self.max_lines,
4560 clamp_w: self.clamp_w,
4561 last_used: frame_no,
4562 bytes: self.bytes,
4563 glyphs: Vec::new(),
4564 deco: Vec::new(),
4565 span_deco: self.span_deco.clone(),
4566 glyphs_built_for: None,
4567 claimed: u64::MAX,
4568 claimed_wrap: None,
4569 breaks_anywhere: self.breaks_anywhere,
4570 min_content: self.min_content,
4571 }
4572 }
4573
4574 fn min_content(&mut self) -> f32 {
4585 if let Some(w) = self.min_content {
4586 return w;
4587 }
4588 let w = if self.clamp_w {
4589 self.intrinsic.w
4590 } else {
4591 widest_unbreakable(&self.buffer, self.breaks_anywhere)
4592 };
4593 self.min_content = Some(w);
4594 w
4595 }
4596}
4597
4598fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
4602 let mut widest = 0.0f32;
4603 let mut line = usize::MAX;
4604 let mut breaks: Vec<usize> = Vec::new();
4605 let (mut run_w, mut ink) = (0.0f32, 0.0f32);
4606 for run in buffer.layout_runs() {
4607 if run.line_i != line {
4608 widest = widest.max(ink);
4609 (run_w, ink) = (0.0, 0.0);
4610 line = run.line_i;
4611 breaks.clear();
4612 if !anywhere {
4613 breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
4614 }
4615 }
4616 for g in run.glyphs {
4617 if anywhere || breaks.binary_search(&g.start).is_ok() {
4618 widest = widest.max(ink);
4619 (run_w, ink) = (0.0, 0.0);
4620 }
4621 run_w += g.w;
4622 let blank = run
4623 .text
4624 .get(g.start..g.end)
4625 .is_some_and(|t| t.chars().all(char::is_whitespace));
4626 if !blank {
4627 ink = run_w;
4628 }
4629 }
4630 }
4631 widest.max(ink)
4632}
4633
4634pub(crate) fn shaper_metrics(size: f32, line_height: f32) -> Metrics {
4641 let px = |v: f32| if v.is_finite() { v.max(1.0) } else { 1.0 };
4642 Metrics::new(px(size), px(line_height))
4643}
4644
4645fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
4648 let metrics = shaper_metrics(style.size * scale, style.line_height * scale);
4649 let mut buffer = Buffer::new(fs, metrics);
4650 buffer.set_wrap(match style.wrap {
4651 TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
4652 TextWrap::Glyph => Wrap::Glyph,
4653 TextWrap::None => Wrap::None,
4654 });
4655 if style.ellipsis {
4656 let lines = line_budget(style).max(1);
4657 buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
4658 }
4659 buffer.set_size(None, None);
4660 buffer
4661}
4662
4663fn line_budget(style: &TextStyle) -> usize {
4666 if style.max_lines > 0 {
4667 style.max_lines as usize
4668 } else if style.ellipsis {
4669 1
4670 } else {
4671 0
4672 }
4673}
4674
4675fn line_cap(max_lines: usize) -> usize {
4676 if max_lines == 0 {
4677 usize::MAX
4678 } else {
4679 max_lines
4680 }
4681}
4682
4683pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
4689 let mut w = 0.0f32;
4690 let mut lines = 0u32;
4691 for run in buffer.layout_runs().take(line_cap(max_lines)) {
4692 w = w.max(run.line_w);
4693 lines += 1;
4694 }
4695 (
4696 Size::new(w, lines as f32 * buffer.metrics().line_height),
4697 lines,
4698 )
4699}
4700
4701impl TextSystem {
4702 pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
4703 let scale = self.scale;
4704 if let Some(line) = self.long_of(id) {
4705 return Size::new(line.width() / scale, line.line_h / scale);
4706 }
4707 let e = self.entry_mut(id);
4708 Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
4709 }
4710
4711 pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
4716 let scale = self.scale;
4717 if self.long_of(id).is_some() {
4718 return 0.0;
4719 }
4720 self.entry_mut(id).min_content() / scale
4721 }
4722
4723 fn long_of(&self, id: TextId) -> Option<&LongLine> {
4725 self.long(self.frame[id.0 as usize].cache_key)
4726 }
4727
4728 pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
4729 let scale = self.scale;
4730 if self.long_of(id).is_some() {
4731 let key = self.frame[id.0 as usize].cache_key;
4734 let (size, rows) = self.long_size(key, Some(max_w * scale));
4735 self.long_mut(key).expect("a long line").laid_rows = rows;
4736 return Size::new(size.w / scale, size.h / scale);
4737 }
4738 self.ensure_wrap(id, max_w, fs);
4739 let e = self.entry_mut(id);
4740 let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
4741 if e.clamp_w {
4742 m.w = m.w.min(max_w * scale);
4745 }
4746 Size::new(m.w / scale, m.h / scale)
4747 }
4748}
4749
4750impl TextSystem {
4751 pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
4756 let scale = self.scale;
4757 if let Some(line) = self.long_of(id) {
4758 return self.long_baseline(line) / scale;
4759 }
4760 let e = self.entry_mut(id);
4761 let b = e
4762 .buffer
4763 .layout_runs()
4764 .next()
4765 .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
4766 b / scale
4767 }
4768}
4769
4770#[cfg(test)]
4773mod default_families {
4774 use super::*;
4775 use cosmic_text::Family;
4776 use cosmic_text::fontdb::FaceInfo;
4777
4778 fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
4781 let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
4782 buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
4783 buffer.shape_until_scroll(fs, false);
4784 let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
4785 fs.db().face(id).cloned()
4786 }
4787
4788 #[test]
4789 fn mono_is_an_upright_monospaced_face() {
4790 let mut fs = new_font_system().0;
4791 if !fs.db().faces().any(|f| f.monospaced) {
4792 eprintln!("skipped: no monospaced face installed");
4793 return;
4794 }
4795 let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
4796 let mono = default_families(&fs)[2].to_string();
4797 assert_eq!(
4798 face.style,
4799 cosmic_text::Style::Normal,
4800 "Mono ({mono}) resolved to {:?}",
4801 face.post_script_name
4802 );
4803 assert!(
4804 face.monospaced,
4805 "Mono ({mono}) resolved to {:?}, which is not monospaced",
4806 face.post_script_name
4807 );
4808 }
4809
4810 #[test]
4811 fn sans_and_serif_are_upright_and_not_monospaced() {
4812 let mut fs = new_font_system().0;
4813 if fs.db().faces().next().is_none() {
4814 eprintln!("skipped: no face installed");
4815 return;
4816 }
4817 let [sans, serif, _] = default_families(&fs).map(str::to_string);
4818 for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
4819 let face = resolved(&mut fs, family).expect("M shapes");
4820 assert_eq!(
4821 face.style,
4822 cosmic_text::Style::Normal,
4823 "{name} resolved to {:?}",
4824 face.post_script_name
4825 );
4826 assert!(
4827 !face.monospaced,
4828 "{name} resolved to {:?}, which is monospaced",
4829 face.post_script_name
4830 );
4831 }
4832 }
4833
4834 #[test]
4837 fn pinned_names_are_installed_or_cosmic_texts_own() {
4838 let fs = new_font_system().0;
4839 let db = fs.db();
4840 for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
4841 let installed = db
4842 .faces()
4843 .any(|f| f.families.iter().any(|(fam, _)| fam == name));
4844 let on_list = list.contains(&name);
4845 assert!(
4846 installed == on_list,
4847 "{name}: installed {installed}, on the platform list {on_list}"
4848 );
4849 }
4850 }
4851}
4852
4853#[cfg(test)]
4860mod unmeasurable_faces {
4861 use super::*;
4862 use crate::testing::{font_face, han_face, unmeasurable_face};
4863 use cosmic_text::Family;
4864 use cosmic_text::fontdb::{Database, Source};
4865
4866 fn font_system() -> FontSystem {
4871 let mut db = Database::new();
4872 for bytes in [
4873 font_face("Kui Mono", 400, false, true),
4874 unmeasurable_face("Kui Bitmap"),
4875 han_face("Kui Han"),
4876 ] {
4877 db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
4878 }
4879 let mut fs = font_system_with("en-US".into(), db);
4880 fs.db_mut().set_monospace_family("Kui Mono");
4881 fs
4882 }
4883
4884 fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
4885 fs.db()
4886 .face(id)
4887 .map_or_else(String::new, |f| f.families[0].0.clone())
4888 }
4889
4890 #[test]
4891 fn a_face_without_metrics_is_not_in_the_database() {
4892 let fs = font_system();
4893 let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
4894 assert_eq!(families, ["Kui Mono", "Kui Han"]);
4895 }
4896
4897 #[test]
4901 fn han_in_mono_falls_back_to_a_face_that_measures() {
4902 let mut fs = font_system();
4903 let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
4904 let attrs = Attrs::new().family(Family::Monospace);
4905 buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
4906 buffer.shape_until_scroll(&mut fs, false);
4907 let run = buffer.layout_runs().next().expect("one line");
4908 let glyphs: Vec<(&str, f32, f32, String)> = run
4909 .glyphs
4910 .iter()
4911 .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
4912 .collect();
4913 for &(text, x, w, _) in &glyphs {
4914 assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
4915 }
4916 let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
4917 assert_eq!(
4918 faces,
4919 [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
4920 );
4921 let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
4923 assert_eq!(xs, [0.0, 7.0, 14.0]);
4924 for glyph in run.glyphs {
4925 glyph.physical((0.0, 0.0), 1.0);
4926 }
4927 }
4928}