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 if size > LARGE_GLYPH {
116 let page = crate::atlas::MAX_ATLAS_SIZE as f32;
117 let fits = match scaler.scale_outline(key.glyph_id) {
118 Some(outline) => {
119 let b = outline.bounds();
120 b.width() <= page && b.height() <= page
121 }
122 None => size <= page,
123 };
124 if !fits {
125 return None;
126 }
127 }
128 let offset = if key.flags.contains(CacheKeyFlags::PIXEL_FONT) {
129 Vector::new(key.x_bin.as_float().round(), key.y_bin.as_float().round())
130 } else {
131 Vector::new(key.x_bin.as_float(), key.y_bin.as_float())
132 };
133 Render::new(&[
134 Source::ColorOutline(0),
135 Source::ColorBitmap(StrikeWith::BestFit),
136 Source::Outline,
137 ])
138 .format(format)
139 .offset(offset)
140 .embolden(if embolden {
141 crate::weights::embolden_strength(size)
142 } else {
143 0.0
144 })
145 .transform(
146 key.flags
147 .contains(CacheKeyFlags::FAKE_ITALIC)
148 .then(|| Transform::skew(Angle::from_degrees(14.0), Angle::from_degrees(0.0))),
149 )
150 .render(&mut scaler, key.glyph_id)
151 }
152}
153
154const LARGE_GLYPH: f32 = 1024.0;
158
159pub(crate) fn raster_glyph(
161 key: cosmic_text::CacheKey,
162 fs: &mut FontSystem,
163 raster: &mut Raster,
164 atlas: &mut crate::atlas::GlyphAtlas,
165) -> Option<crate::atlas::GlyphSlot> {
166 atlas.get_or_insert(key, || {
167 let format = if raster.subpixel {
168 swash::zeno::Format::Subpixel
169 } else {
170 swash::zeno::Format::Alpha
171 };
172 let image = raster.image(fs, key, format)?;
173 if image.placement.width == 0 || image.placement.height == 0 {
174 return None;
175 }
176 let (data, is_color, subpixel) = match image.content {
177 SwashContent::Mask => {
178 let mut rgba = Vec::with_capacity(image.data.len() * 4);
179 for &a in image.data.iter() {
180 rgba.extend_from_slice(&[255, 255, 255, a]);
181 }
182 (rgba, false, false)
183 }
184 SwashContent::Color => (image.data.to_vec(), true, false),
185 SwashContent::SubpixelMask => {
186 let mut rgba = image.data.to_vec();
190 for px in rgba.as_chunks_mut::<4>().0 {
191 px[3] = px[0].max(px[1]).max(px[2]);
192 }
193 (rgba, false, true)
194 }
195 };
196 Some(crate::atlas::RasterGlyph {
197 w: image.placement.width,
198 h: image.placement.height,
199 left: image.placement.left,
200 top: image.placement.top,
201 color: is_color,
202 subpixel,
203 data,
204 })
205 })
206}
207
208pub(crate) fn glyph_kind(slot: &crate::atlas::GlyphSlot) -> QuadKind {
210 if slot.color_glyph {
211 QuadKind::GlyphColor
212 } else if slot.subpixel {
213 QuadKind::GlyphSubpixel
214 } else {
215 QuadKind::GlyphMask
216 }
217}
218
219pub const DEFAULT_TEXT_CACHE_BYTES: usize = 64 << 20;
225
226const ENTRY_BASE_BYTES: usize = 4096;
236const ENTRY_GLYPH_BYTES: usize = 480;
237
238pub const LONG_LINE_BYTES: usize = 4096;
244const CHUNK_BYTES: usize = 1024;
249const LONG_SALT: u64 = 0x5f5f_6c6f_6e67_5f5f;
252const COPY_SALT: u64 = 0x5f5f_636f_7079_5f5f;
255
256const EVICT_TO_NUMERATOR: usize = 3;
260const EVICT_TO_DENOMINATOR: usize = 4;
261
262pub(crate) struct CachedText {
263 buffer: Buffer,
264 content: String,
267 wrap: Option<f32>,
269 intrinsic: Size,
271 max_lines: usize,
274 clamp_w: bool,
277 last_used: u64,
278 bytes: usize,
280 glyphs: Vec<GlyphTemplate>,
283 deco: Vec<DecoTemplate>,
287 span_deco: Vec<SpanDeco>,
290 glyphs_built_for: Option<(Option<u32>, u64)>,
292 claimed: u64,
297 claimed_wrap: Option<f32>,
298 breaks_anywhere: bool,
301 min_content: Option<f32>,
305}
306
307#[derive(Clone, Copy)]
308struct GlyphTemplate {
309 x: f32,
310 y: f32,
311 w: f32,
312 h: f32,
313 uv: [u32; 4],
314 kind: QuadKind,
315 color: Option<Color>,
320 byte: u32,
324}
325
326struct DecoTemplate {
328 x: f32,
329 y: f32,
330 w: f32,
331 h: f32,
332 color: Option<Color>,
335 under: bool,
337 style: UnderlineStyle,
340 radius: f32,
343}
344
345#[derive(Clone, Copy, Debug)]
354pub(crate) struct JoinBg {
355 pub quad: u32,
356 pub radius: f32,
357 pub outer: ClipId,
358 pub own: Option<(f32, f32)>,
359}
360
361#[derive(Clone, Copy, Default)]
364struct SpanDeco {
365 start: u32,
367 color: Option<Color>,
370 underline: bool,
371 underline_color: Option<Color>,
372 underline_style: UnderlineStyle,
373 strikethrough: bool,
374 bg: Option<Color>,
375 bg_radius: f32,
378}
379
380impl SpanDeco {
381 fn of_style(style: &TextStyle) -> Self {
382 Self {
383 start: 0,
384 color: None,
385 underline: style.underline,
386 underline_color: style.underline_color,
387 underline_style: style.underline_style,
388 strikethrough: style.strikethrough,
389 bg: None,
390 bg_radius: 0.0,
391 }
392 }
393
394 fn any(&self) -> bool {
395 self.underline || self.strikethrough || self.bg.is_some()
396 }
397}
398
399#[derive(Clone, Copy, Debug)]
402pub struct Span<'a> {
403 pub text: &'a str,
404 pub color: Option<Color>,
405 pub bold: bool,
406 pub italic: bool,
407 pub underline: bool,
409 pub underline_color: Option<Color>,
411 pub underline_style: UnderlineStyle,
413 pub strikethrough: bool,
415 pub bg: Option<Color>,
418 pub bg_radius: f32,
425 pub family: Option<FontFamily>,
432 pub size: Option<f32>,
438}
439
440impl<'a> Span<'a> {
441 pub fn new(text: &'a str) -> Self {
442 Self {
443 text,
444 color: None,
445 bold: false,
446 italic: false,
447 underline: false,
448 underline_color: None,
449 underline_style: UnderlineStyle::Solid,
450 strikethrough: false,
451 bg: None,
452 bg_radius: 0.0,
453 family: None,
454 size: None,
455 }
456 }
457
458 pub fn family(mut self, family: FontFamily) -> Self {
460 self.family = Some(family);
461 self
462 }
463
464 pub fn mono(self) -> Self {
472 self.family(FontFamily::Mono)
473 }
474
475 pub fn size(mut self, px: f32) -> Self {
478 self.size = (px.is_finite() && px > 0.0).then_some(px);
479 self
480 }
481
482 pub fn color(mut self, c: Color) -> Self {
483 self.color = Some(c);
484 self
485 }
486
487 pub fn bold(mut self) -> Self {
488 self.bold = true;
489 self
490 }
491
492 pub fn italic(mut self) -> Self {
493 self.italic = true;
494 self
495 }
496
497 pub fn underline(mut self) -> Self {
498 self.underline = true;
499 self
500 }
501
502 pub fn underline_color(mut self, c: Color) -> Self {
504 self.underline = true;
505 self.underline_color = Some(c);
506 self
507 }
508
509 pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
511 self.underline = true;
512 self.underline_style = s;
513 self
514 }
515
516 pub fn strikethrough(mut self) -> Self {
517 self.strikethrough = true;
518 self
519 }
520
521 pub fn bg(mut self, c: Color) -> Self {
522 self.bg = Some(c);
523 self
524 }
525
526 pub fn bg_radius(mut self, r: f32) -> Self {
529 self.bg_radius = r.max(0.0);
530 self
531 }
532
533 fn attrs_only(&self) -> SpanAttrs {
536 SpanAttrs {
537 color: self.color,
538 bold: self.bold,
539 italic: self.italic,
540 underline: self.underline,
541 underline_color: self.underline_color,
542 underline_style: self.underline_style,
543 strikethrough: self.strikethrough,
544 bg: self.bg,
545 bg_radius: self.bg_radius,
546 family: self.family,
547 size: self.size,
548 }
549 }
550
551 fn attrs<'r>(
552 &self,
553 family: cosmic_text::Family<'r>,
554 weights: crate::weights::Weights,
555 ) -> Attrs<'r> {
556 let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
560 if self.italic {
561 attrs = attrs.style(FontStyle::Italic);
562 }
563 if let Some(c) = self.color {
564 attrs = attrs.color(shaper_color(c));
565 }
566 attrs
567 }
568}
569
570fn shaper_color(c: Color) -> cosmic_text::Color {
572 cosmic_text::Color::rgba(
573 (c.r * 255.0) as u8,
574 (c.g * 255.0) as u8,
575 (c.b * 255.0) as u8,
576 (c.a * 255.0) as u8,
577 )
578}
579
580fn shaped_color(c: Color) -> Color {
584 let c = shaper_color(c);
585 Color::rgba8(c.r(), c.g(), c.b(), c.a())
586}
587
588#[derive(Clone, Copy)]
592struct SpanAttrs {
593 color: Option<Color>,
594 bold: bool,
595 italic: bool,
596 underline: bool,
597 underline_color: Option<Color>,
598 underline_style: UnderlineStyle,
599 strikethrough: bool,
600 bg: Option<Color>,
601 bg_radius: f32,
602 family: Option<FontFamily>,
603 size: Option<f32>,
604}
605
606impl SpanAttrs {
607 fn span<'a>(&self, text: &'a str) -> Span<'a> {
608 Span {
609 text,
610 color: self.color,
611 bold: self.bold,
612 italic: self.italic,
613 underline: self.underline,
614 underline_color: self.underline_color,
615 underline_style: self.underline_style,
616 strikethrough: self.strikethrough,
617 bg: self.bg,
618 bg_radius: self.bg_radius,
619 family: self.family,
620 size: self.size,
621 }
622 }
623}
624
625#[derive(Clone)]
628struct OwnedSpan {
629 start: usize,
630 end: usize,
631 attrs: SpanAttrs,
632}
633
634fn chunk_spans<'a>(
639 content: &'a str,
640 spans: &[OwnedSpan],
641 start: usize,
642 end: usize,
643) -> Vec<Span<'a>> {
644 let first = spans.partition_point(|s| s.end <= start);
645 spans[first..]
646 .iter()
647 .take_while(|s| s.start < end)
648 .filter_map(|s| {
649 let (a, b) = (s.start.max(start), s.end.min(end));
650 (a < b).then(|| s.attrs.span(&content[a..b]))
651 })
652 .collect()
653}
654
655struct FrameText {
656 cache_key: u64,
657 color: Color,
658}
659
660pub(crate) enum Entry {
668 Run(CachedText),
669 Long(LongLine),
670}
671
672impl Entry {
673 pub(crate) fn content(&self) -> &str {
674 match self {
675 Entry::Run(e) => &e.content,
676 Entry::Long(l) => &l.content,
677 }
678 }
679
680 fn last_used(&self) -> u64 {
681 match self {
682 Entry::Run(e) => e.last_used,
683 Entry::Long(l) => l.last_used,
684 }
685 }
686
687 fn touch(&mut self, frame_no: u64) {
688 match self {
689 Entry::Run(e) => e.last_used = frame_no,
690 Entry::Long(l) => l.last_used = frame_no,
691 }
692 }
693
694 fn bytes(&self) -> usize {
696 match self {
697 Entry::Run(e) => e.bytes,
698 Entry::Long(l) => l.bytes,
699 }
700 }
701
702 fn run(&self) -> Option<&CachedText> {
703 match self {
704 Entry::Run(e) => Some(e),
705 Entry::Long(_) => None,
706 }
707 }
708
709 fn run_mut(&mut self) -> Option<&mut CachedText> {
710 match self {
711 Entry::Run(e) => Some(e),
712 Entry::Long(_) => None,
713 }
714 }
715
716 fn long(&self) -> Option<&LongLine> {
717 match self {
718 Entry::Long(l) => Some(l),
719 Entry::Run(_) => None,
720 }
721 }
722
723 fn long_mut(&mut self) -> Option<&mut LongLine> {
724 match self {
725 Entry::Long(l) => Some(l),
726 Entry::Run(_) => None,
727 }
728 }
729}
730
731#[derive(Clone)]
735struct Chunk {
736 start: usize,
737 end: usize,
738 key: u64,
739 width: Option<f32>,
741 tab: Option<(f32, f32)>,
747 rows: Vec<RowStart>,
750}
751
752#[derive(Clone, Copy, Debug)]
756struct RowStart {
757 byte: u32,
758 x: f32,
759}
760
761#[derive(Clone)]
774pub(crate) struct LongLine {
775 content: String,
776 spans: Vec<OwnedSpan>,
780 style: TextStyle,
783 wrap: TextWrap,
789 wrap_w: Option<f32>,
792 starts: Vec<(u32, f32)>,
797 chunks: Vec<Chunk>,
798 prefix: Vec<f32>,
801 line_h: f32,
803 avg: f32,
806 last_used: u64,
807 claimed: u64,
810 laid_rows: u32,
817 asked_rows: u32,
818 bytes: usize,
819}
820
821impl LongLine {
822 fn chunk_at(&self, x: f32) -> usize {
824 match self.prefix[1..].partition_point(|&p| p <= x) {
825 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
826 i => i,
827 }
828 }
829
830 fn chunk_of_byte(&self, byte: usize) -> usize {
832 match self.chunks.partition_point(|c| c.end <= byte) {
833 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
834 i => i,
835 }
836 }
837
838 fn width(&self) -> f32 {
839 self.prefix.last().copied().unwrap_or(0.0)
840 }
841
842 fn rows(&self) -> u32 {
844 match self.starts.last() {
845 Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
846 _ => 1,
847 }
848 }
849
850 fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
853 let n = self.chunks.len();
854 if n == 0 || self.starts.len() != n + 1 {
855 return None;
856 }
857 let first = self.starts[1..].partition_point(|s| s.0 < ra);
858 let last = self.starts[..n].partition_point(|s| s.0 <= rb);
859 (first < last).then_some((first, last - 1))
860 }
861
862 fn reprefix(&mut self) {
869 let mut at = 0.0f32;
870 self.prefix.clear();
871 self.prefix.push(0.0);
872 for c in &self.chunks {
873 at = match (c.width, c.tab) {
874 (Some(_), Some((tab_x, every))) => next_tab_stop(at + tab_x, every),
875 (Some(w), None) => at + w,
876 (None, _) => at + (c.end - c.start) as f32 * self.avg,
877 };
878 self.prefix.push(at);
879 }
880 }
881
882 fn tab_shift(&self, i: usize) -> f32 {
885 let c = &self.chunks[i];
886 match (c.width, c.tab) {
887 (Some(w), Some(_)) => self.prefix[i + 1] - self.prefix[i] - w,
888 _ => 0.0,
889 }
890 }
891
892 fn end_shift(&self, i: usize, local: usize) -> f32 {
897 let c = &self.chunks[i];
898 match local >= c.end - c.start {
899 true => self.tab_shift(i),
900 false => 0.0,
901 }
902 }
903
904 fn placed_tab_hit(&self, i: usize, x: f32) -> Option<usize> {
911 let c = &self.chunks[i];
912 let (tab_x, _) = c.tab?;
913 c.width?;
914 if x < tab_x {
915 return None;
916 }
917 let drawn_end = self.prefix[i + 1] - self.prefix[i];
918 Some(match x < (tab_x + drawn_end) / 2.0 {
919 true => c.end - 1,
920 false => c.end,
921 })
922 }
923}
924
925fn next_tab_stop(x: f32, every: f32) -> f32 {
931 if every <= 0.0 {
932 return x;
933 }
934 (((x + 1.0 / 16.0) / every).floor() + 1.0) * every
935}
936
937fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
954 use unicode_segmentation::UnicodeSegmentation;
955 if content.len() < LONG_LINE_BYTES {
956 return vec![(0, content.len())];
957 }
958 let bytes = content.as_bytes();
959 let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
960 let mut start = 0usize;
961 while start < content.len() {
962 let mut window_end = (start + CHUNK_BYTES).min(content.len());
966 while !content.is_char_boundary(window_end) {
967 window_end -= 1;
968 }
969 let window = &content[start..window_end];
970 let mut from = (CHUNK_BYTES / 2).min(window.len());
972 while !window.is_char_boundary(from) {
973 from += 1;
974 }
975 let off_stops = start > 0 && bytes[start - 1] != b'\t';
979 let early_tab = match off_stops {
980 true => window[..from].find('\t'),
981 false => None,
982 };
983 let end = if let Some(t) = early_tab {
984 start + t + 1
985 } else if window_end == content.len() && !(off_stops && window[from..].contains('\t')) {
986 window_end
987 } else {
988 let (mut first_tab, mut last_tab, mut space) = (None, None, None);
992 for (i, c) in window[from..].char_indices() {
993 if c.is_whitespace() {
994 let end = from + i + c.len_utf8();
995 space = Some(end);
996 if c == '\t' {
997 first_tab = first_tab.or(Some(end));
998 last_tab = Some(end);
999 }
1000 }
1001 }
1002 let tab = if off_stops { first_tab } else { last_tab };
1003 match tab.or(space) {
1004 Some(i) => start + i,
1005 None => {
1006 let mut cut = 0usize;
1008 for (i, _) in window.grapheme_indices(true) {
1009 if i == 0 {
1010 continue;
1011 }
1012 cut = i;
1013 }
1014 if cut == 0 { window_end } else { start + cut }
1015 }
1016 }
1017 };
1018 out.push((start, end));
1019 start = end;
1020 }
1021 out
1022}
1023
1024pub(crate) const PLACE_ANCESTORS: usize = 4;
1029
1030pub(crate) struct Ancestry {
1034 pub(crate) keys: [Key; PLACE_ANCESTORS],
1035 pub(crate) depth: usize,
1036 pub(crate) none_at: Option<usize>,
1037}
1038
1039pub(crate) struct TextPlace {
1044 pub(crate) key: Key,
1045 ancestors: [Key; PLACE_ANCESTORS],
1047 depth: u8,
1048 none_at: u8,
1054 cache_key: u64,
1055 origin: Vec2,
1057 scope: Option<Key>,
1061 drawn: bool,
1069}
1070
1071impl TextPlace {
1072 fn answers_to(&self, key: Key) -> bool {
1073 if self.key == key {
1074 return true;
1075 }
1076 self.ancestors[..self.depth as usize]
1080 .iter()
1081 .position(|k| *k == key)
1082 .is_some_and(|d| d <= self.none_at as usize)
1083 }
1084}
1085
1086pub(crate) struct ScopeRun<'a> {
1092 pub place: &'a TextPlace,
1093 pub text: &'a Entry,
1095 pub base: usize,
1096}
1097
1098impl ScopeRun<'_> {
1099 pub(crate) fn span(&self) -> (usize, usize) {
1102 (self.base, self.base + self.text.content().len())
1103 }
1104}
1105
1106#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1119pub struct TextHit {
1120 pub byte: usize,
1121 pub line: u32,
1122}
1123
1124impl TextHit {
1125 pub fn to_value(self) -> Value {
1127 Value::map([
1128 ("byte", Value::Int(self.byte as i64)),
1129 ("line", Value::Int(self.line as i64)),
1130 ])
1131 }
1132}
1133
1134#[derive(Clone, Copy, Debug, Default, PartialEq)]
1139pub struct TextMetrics {
1140 pub width: f32,
1141 pub height: f32,
1142 pub lines: u32,
1144}
1145
1146impl TextMetrics {
1147 pub fn to_value(self) -> Value {
1149 Value::map([
1150 ("width", Value::float(self.width)),
1151 ("height", Value::float(self.height)),
1152 ("lines", Value::Int(self.lines as i64)),
1153 ])
1154 }
1155}
1156
1157pub struct TextSystem {
1165 raster: Raster,
1166 joins: Vec<JoinBg>,
1169 entries: FxHashMap<u64, Entry>,
1172 bytes: usize,
1175 budget: usize,
1176 frame: Kept<FrameText>,
1181 places: Kept<TextPlace>,
1185 scale: f32,
1186 frame_no: u64,
1187 owed: bool,
1190}
1191
1192pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1195 let mut out = cosmic_text::FontFeatures::new();
1196 for (tag, value) in f.iter() {
1197 out.set(cosmic_text::FeatureTag::new(tag), value);
1198 }
1199 out
1200}
1201
1202#[cfg(target_os = "macos")]
1213const DEFAULT_FAMILIES: [&[&str]; 3] = [
1214 &["Helvetica Neue", "Helvetica"],
1215 &["Times New Roman", "Times"],
1216 &["SF Mono", "Menlo", "Monaco"],
1217];
1218#[cfg(target_os = "windows")]
1219const DEFAULT_FAMILIES: [&[&str]; 3] = [
1220 &["Segoe UI", "Arial"],
1221 &["Times New Roman"],
1222 &["Cascadia Mono", "Consolas", "Courier New"],
1223];
1224#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1225const DEFAULT_FAMILIES: [&[&str]; 3] = [
1226 &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1227 &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1228 &[
1229 "DejaVu Sans Mono",
1230 "Noto Sans Mono",
1231 "Liberation Mono",
1232 "Ubuntu Mono",
1233 ],
1234];
1235
1236fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1239 list.iter().copied().find(|name| {
1240 db.faces()
1241 .any(|face| face.families.iter().any(|(f, _)| f == name))
1242 })
1243}
1244
1245pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1259 let system = system_fonts();
1260 let fonts = font_system_over(system.locale.clone(), system.db.clone(), &[]);
1261 (fonts, system)
1262}
1263
1264pub(crate) fn font_system_over(
1269 locale: String,
1270 db: cosmic_text::fontdb::Database,
1271 first: &[String],
1272) -> FontSystem {
1273 FontSystem::new_with_locale_and_db_and_fallback(locale, db, AppFallback::new(first))
1274}
1275
1276pub(crate) struct AppFallback {
1283 platform: cosmic_text::PlatformFallback,
1284 first: &'static [&'static str],
1285 common: &'static [&'static str],
1286 scripts: std::sync::Mutex<
1289 rustc_hash::FxHashMap<(unicode_script::Script, String), &'static [&'static str]>,
1290 >,
1291}
1292
1293fn keep(names: Vec<&'static str>) -> &'static [&'static str] {
1296 static LISTS: std::sync::Mutex<Vec<&'static [&'static str]>> =
1297 std::sync::Mutex::new(Vec::new());
1298 let mut lists = LISTS.lock().unwrap_or_else(|e| e.into_inner());
1299 if let Some(&kept) = lists.iter().find(|l| ***l == names[..]) {
1300 return kept;
1301 }
1302 let kept: &'static [&'static str] = Box::leak(names.into_boxed_slice());
1303 lists.push(kept);
1304 kept
1305}
1306
1307fn keep_name(name: &str) -> &'static str {
1308 static NAMES: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
1309 let mut names = NAMES.lock().unwrap_or_else(|e| e.into_inner());
1310 if let Some(&kept) = names.iter().find(|n| **n == name) {
1311 return kept;
1312 }
1313 let kept: &'static str = Box::leak(name.to_string().into_boxed_str());
1314 names.push(kept);
1315 kept
1316}
1317
1318impl AppFallback {
1319 pub(crate) fn new(first: &[String]) -> Self {
1320 use cosmic_text::Fallback;
1321 let platform = cosmic_text::PlatformFallback;
1322 let first = keep(first.iter().map(|n| keep_name(n)).collect());
1323 let common = keep(
1324 first
1325 .iter()
1326 .chain(platform.common_fallback())
1327 .copied()
1328 .collect(),
1329 );
1330 Self {
1331 platform,
1332 first,
1333 common,
1334 scripts: Default::default(),
1335 }
1336 }
1337}
1338
1339impl cosmic_text::Fallback for AppFallback {
1340 fn common_fallback(&self) -> &[&'static str] {
1341 self.common
1342 }
1343
1344 fn forbidden_fallback(&self) -> &[&'static str] {
1345 self.platform.forbidden_fallback()
1346 }
1347
1348 fn script_fallback(&self, script: unicode_script::Script, locale: &str) -> &[&'static str] {
1349 let theirs = self.platform.script_fallback(script, locale);
1350 if self.first.is_empty() {
1351 return theirs;
1352 }
1353 let mut scripts = self.scripts.lock().unwrap_or_else(|e| e.into_inner());
1354 scripts
1355 .entry((script, locale.to_string()))
1356 .or_insert_with(|| keep(self.first.iter().chain(theirs).copied().collect()))
1357 }
1358}
1359
1360pub(crate) struct SystemFonts {
1364 locale: String,
1365 db: cosmic_text::fontdb::Database,
1366 pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1368}
1369
1370static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1372
1373pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1377 let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1378 held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1379 .clone()
1380}
1381
1382pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1385 let fresh = std::sync::Arc::new(scan_system_fonts());
1386 *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1387 fresh
1388}
1389
1390fn scan_system_fonts() -> SystemFonts {
1391 let (locale, db) = FontSystem::new().into_locale_and_db();
1392 SystemFonts::from_db(locale, db)
1393}
1394
1395impl SystemFonts {
1396 pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1399 let all: Vec<_> = db.faces().map(|face| face.id).collect();
1400 keep_measurable(&mut db, all);
1401 pin_default_families(&mut db);
1402 let faces = db.faces().filter_map(face_file).collect();
1403 Self { locale, db, faces }
1404 }
1405
1406 #[cfg(test)]
1408 pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1409 &self.db
1410 }
1411
1412 #[cfg(test)]
1413 pub(crate) fn locale(&self) -> &str {
1414 &self.locale
1415 }
1416}
1417
1418pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1421 use cosmic_text::fontdb::Source;
1422 match &face.source {
1423 Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1424 Source::Binary(_) => None,
1425 }
1426}
1427
1428pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1432 let [sans, serif, mono] = DEFAULT_FAMILIES;
1433 if let Some(name) = first_installed(db, sans) {
1434 db.set_sans_serif_family(name);
1435 }
1436 if let Some(name) = first_installed(db, serif) {
1437 db.set_serif_family(name);
1438 }
1439 if let Some(name) = first_installed(db, mono) {
1440 db.set_monospace_family(name);
1441 }
1442}
1443
1444#[cfg(test)]
1448fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1449 let system = SystemFonts::from_db(locale, db);
1450 FontSystem::new_with_locale_and_db(system.locale, system.db)
1451}
1452
1453pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1464 use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1465 let Ok(font) = FontRef::from_index(data, index) else {
1466 return false;
1467 };
1468 font.head()
1469 .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1470 && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1471 && font.hmtx().is_ok()
1472}
1473
1474const FILES_PER_CHECKER: usize = 64;
1477
1478pub(crate) fn keep_measurable(
1490 db: &mut cosmic_text::fontdb::Database,
1491 ids: Vec<cosmic_text::fontdb::ID>,
1492) -> Vec<cosmic_text::fontdb::ID> {
1493 use cosmic_text::fontdb::{ID, Source};
1494 let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1496 let mut groups = Vec::new();
1497 for &id in &ids {
1498 match db.face(id).map(|face| &face.source) {
1499 Some(Source::File(path) | Source::SharedFile(path, _)) => {
1500 by_file.entry(path).or_default().push(id);
1501 }
1502 Some(Source::Binary(_)) => groups.push(vec![id]),
1503 None => {}
1504 }
1505 }
1506 groups.extend(by_file.into_values());
1507 let db_ref = &*db;
1508 let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1509 let mut out = Vec::new();
1510 for group in groups {
1511 let read = db_ref.with_face_data(group[0], |data, _| {
1512 group
1513 .iter()
1514 .filter(|&&id| {
1515 db_ref
1516 .face(id)
1517 .is_none_or(|face| !measurable(data, face.index))
1518 })
1519 .copied()
1520 .collect::<Vec<_>>()
1521 });
1522 out.extend(read.unwrap_or_else(|| group.clone()));
1523 }
1524 out
1525 };
1526 let checkers = std::thread::available_parallelism()
1527 .map_or(1, |n| n.get())
1528 .min(groups.len().div_ceil(FILES_PER_CHECKER));
1529 let unmeasurable = if checkers <= 1 {
1530 unmeasurable_in(&groups)
1531 } else {
1532 let per = groups.len().div_ceil(checkers);
1533 std::thread::scope(|scope| {
1534 let parts: Vec<_> = groups
1535 .chunks(per)
1536 .map(|part| {
1537 let spawned = std::thread::Builder::new()
1538 .name("kui-font-check".into())
1539 .spawn_scoped(scope, move || unmeasurable_in(part));
1540 (part, spawned.ok())
1541 })
1542 .collect();
1543 let mut out = Vec::new();
1544 for (part, checker) in parts {
1545 out.extend(match checker {
1546 Some(checker) => checker
1547 .join()
1548 .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1549 None => unmeasurable_in(part),
1551 });
1552 }
1553 out
1554 })
1555 };
1556 for &id in &unmeasurable {
1557 db.remove_face(id);
1558 }
1559 ids.into_iter()
1560 .filter(|id| !unmeasurable.contains(id))
1561 .collect()
1562}
1563
1564pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1568 let db = fs.db();
1569 [
1570 db.family_name(&cosmic_text::Family::SansSerif),
1571 db.family_name(&cosmic_text::Family::Serif),
1572 db.family_name(&cosmic_text::Family::Monospace),
1573 ]
1574}
1575
1576impl TextSystem {
1577 pub fn new() -> Self {
1578 Self {
1579 raster: Raster::new(),
1580 joins: Vec::new(),
1581 entries: FxHashMap::default(),
1582 bytes: 0,
1583 budget: DEFAULT_TEXT_CACHE_BYTES,
1584 frame: Kept::default(),
1585 places: Kept::default(),
1586 scale: 1.0,
1587 frame_no: 0,
1588 owed: false,
1589 }
1590 }
1591
1592 pub(crate) fn take_owed(&mut self) -> bool {
1595 std::mem::take(&mut self.owed)
1596 }
1597
1598 pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1601 std::mem::take(&mut self.joins)
1602 }
1603
1604 pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1606 &mut self.raster
1607 }
1608
1609 pub(crate) fn subpixel(&self) -> bool {
1610 self.raster.subpixel
1611 }
1612
1613 pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1616 let changed = self.raster.subpixel != on;
1617 self.raster.subpixel = on;
1618 changed
1619 }
1620
1621 pub fn budget(&self) -> usize {
1624 self.budget
1625 }
1626
1627 pub fn set_budget(&mut self, bytes: usize) {
1630 self.budget = bytes;
1631 }
1632
1633 pub fn bytes(&self) -> usize {
1635 self.bytes
1636 }
1637
1638 pub fn len(&self) -> usize {
1641 self.entries.len() - self.long_lines()
1642 }
1643
1644 pub fn long_lines(&self) -> usize {
1646 self.entries.values().filter(|e| e.long().is_some()).count()
1647 }
1648
1649 pub fn is_empty(&self) -> bool {
1650 self.len() == 0
1651 }
1652
1653 fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1660 if self.bytes <= self.budget {
1661 return;
1662 }
1663 let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1664 let drawn_last_frame = self.frame_no.saturating_sub(1);
1665 let mut order: Vec<(u64, u64)> = self
1670 .entries
1671 .iter()
1672 .filter(|(_, e)| e.last_used() < drawn_last_frame)
1673 .map(|(k, e)| (e.last_used(), *k))
1674 .collect();
1675 order.sort_unstable();
1676 for (_, key) in order {
1677 if self.bytes <= floor {
1678 break;
1679 }
1680 let freed = self.entries.remove(&key).map(|e| e.bytes());
1681 self.bytes -= freed.unwrap_or(0);
1682 }
1683 fs.shape_run_cache.trim(0);
1692 }
1693
1694 pub(crate) fn begin_frame(
1698 &mut self,
1699 fs: &mut FontSystem,
1700 scale: f32,
1701 keep_prev: bool,
1702 frame_no: u64,
1703 ) {
1704 self.joins.clear();
1705 if (scale - self.scale).abs() > f32::EPSILON {
1707 self.entries.clear();
1708 self.bytes = 0;
1709 }
1710 self.scale = scale;
1711 self.frame.begin(keep_prev);
1712 self.places.begin(true);
1715 self.frame_no = frame_no;
1717 if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1718 let mut freed = 0usize;
1719 self.entries.retain(|_, e| {
1720 let keep = e.last_used() >= cutoff;
1721 if !keep {
1722 freed += e.bytes();
1723 }
1724 keep
1725 });
1726 self.bytes -= freed;
1727 fs.shape_run_cache.trim(2);
1730 }
1731 self.evict_to_budget(fs);
1735 }
1736
1737 pub(crate) fn forget_shaped(&mut self) {
1740 self.entries.clear();
1741 self.bytes = 0;
1742 }
1743
1744 fn insert(&mut self, key: u64, entry: Entry) {
1746 self.bytes += entry.bytes();
1747 if let Some(old) = self.entries.insert(key, entry) {
1748 self.bytes -= old.bytes();
1749 }
1750 }
1751
1752 fn run(&self, key: u64) -> Option<&CachedText> {
1754 self.entries.get(&key)?.run()
1755 }
1756
1757 fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1758 self.entries.get_mut(&key)?.run_mut()
1759 }
1760
1761 fn long(&self, key: u64) -> Option<&LongLine> {
1763 self.entries.get(&key)?.long()
1764 }
1765
1766 fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1767 self.entries.get_mut(&key)?.long_mut()
1768 }
1769
1770 pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1771 let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1774 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1775 mix(&style.size.to_bits().to_le_bytes());
1776 mix(&style.line_height.to_bits().to_le_bytes());
1777 mix(&scale.to_bits().to_le_bytes());
1778 mix(&[style.wrap as u8, style.ellipsis as u8]);
1779 mix(&style.max_lines.to_le_bytes());
1780 let (tag, font) = match style.family {
1781 FontFamily::Sans => (0u8, 0u64),
1782 FontFamily::Serif => (1, 0),
1783 FontFamily::Mono => (2, 0),
1784 FontFamily::Custom(id) => (3, id.to_ffi()),
1785 };
1786 mix(&[tag]);
1787 mix(&font.to_le_bytes());
1788 for (t, v) in style.features.iter() {
1789 mix(t);
1790 mix(&v.to_le_bytes());
1791 }
1792 mix(&[style.bold as u8]);
1797 mix(&[
1798 style.underline as u8,
1799 style.strikethrough as u8,
1800 style.underline_style as u8,
1801 style.underline_color.is_some() as u8,
1802 ]);
1803 if let Some(c) = style.underline_color {
1804 mix(&c.to_hex().to_le_bytes());
1805 }
1806 h
1807 }
1808
1809 fn intern(
1813 &mut self,
1814 content: &str,
1815 style: &TextStyle,
1816 res: &Resources,
1817 fs: &mut FontSystem,
1818 ) -> u64 {
1819 let key = Self::style_key(content, style, self.scale);
1820 self.intern_keyed(key, content, style, res, fs)
1821 }
1822
1823 fn intern_keyed(
1825 &mut self,
1826 key: u64,
1827 content: &str,
1828 style: &TextStyle,
1829 res: &Resources,
1830 fs: &mut FontSystem,
1831 ) -> u64 {
1832 let frame_no = self.frame_no;
1833 let scale = self.scale;
1834 if !self.entries.contains_key(&key) {
1835 let mut buffer = new_buffer(fs, style, scale);
1836 buffer.set_text(
1840 &one_paragraph(content),
1841 &res.weights_of(style.family).apply(
1842 Attrs::new()
1843 .family(res.family_of(style.family))
1844 .font_features(cosmic_features(&style.features)),
1845 style.bold,
1846 ),
1847 Shaping::Advanced,
1848 None,
1849 );
1850 let entry = CachedText::new(
1851 buffer,
1852 content.to_string(),
1853 style,
1854 vec![SpanDeco::of_style(style)],
1855 fs,
1856 frame_no,
1857 );
1858 self.insert(key, Entry::Run(entry));
1859 }
1860 self.entries
1861 .get_mut(&key)
1862 .expect("just inserted")
1863 .touch(frame_no);
1864 key
1865 }
1866
1867 pub(crate) fn content(&self, id: TextId) -> &str {
1869 let key = self.frame[id.0 as usize].cache_key;
1870 self.entries.get(&key).map_or("", |e| e.content())
1871 }
1872
1873 pub(crate) fn access_runs(
1879 &self,
1880 id: TextId,
1881 src: crate::access::RunSource,
1882 run_no: &mut usize,
1883 out: &mut Vec<crate::access::AccessRun>,
1884 ) {
1885 use crate::access::{RowGlyphs, push_row_runs};
1886 let Some(ft) = self.frame.get(id.0 as usize) else {
1887 return;
1888 };
1889 let line = match self.entries.get(&ft.cache_key) {
1890 Some(Entry::Run(e)) => {
1891 crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1892 return;
1893 }
1894 Some(Entry::Long(line)) => line,
1895 None => return,
1896 };
1897 let whole = [RowStart { byte: 0, x: 0.0 }];
1898 for (i, c) in line.chunks.iter().enumerate() {
1899 let Some(run) = c
1900 .width
1901 .and_then(|_| self.run(c.key))
1902 .and_then(|e| e.buffer.layout_runs().next())
1903 else {
1904 continue;
1905 };
1906 let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1909 (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1910 (&c.rows[..], row0, head_x)
1911 }
1912 (Some(_), _) => continue,
1913 (None, _) => (&whole[..], 0, line.prefix[i]),
1914 };
1915 for (r, rs) in rows.iter().enumerate() {
1916 let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1917 let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1918 let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1919 if a == b {
1920 continue;
1921 }
1922 let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1923 push_row_runs(
1924 &RowGlyphs {
1925 glyphs: &run.glyphs[a..b],
1926 text: run.text,
1927 line: 0,
1928 top: (row0 as usize + r) as f32 * line.line_h,
1929 height: line.line_h,
1930 rtl: run.rtl,
1931 dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1932 base: c.start,
1933 newline: last && src.newline_after_last,
1934 },
1935 &src,
1936 run_no,
1937 out,
1938 );
1939 }
1940 }
1941 }
1942
1943 pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1949 match self.frame.prev().get(id.0 as usize) {
1950 Some(t) => (t.cache_key, t.color),
1951 None => (0, Color::TRANSPARENT),
1952 }
1953 }
1954
1955 pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1961 let frame_no = self.frame_no;
1962 let e = self.entries.get_mut(&cache_key)?;
1963 e.touch(frame_no);
1964 let cache_key = if e.long().is_some() {
1965 self.claim_long(cache_key)
1966 } else {
1967 cache_key
1968 };
1969 self.frame.push(FrameText { cache_key, color });
1970 Some(TextId((self.frame.len() - 1) as u32))
1971 }
1972
1973 pub fn add(
1975 &mut self,
1976 content: &str,
1977 style: &TextStyle,
1978 res: &Resources,
1979 fs: &mut FontSystem,
1980 ) -> TextId {
1981 let key = self.intern_any(content, style, res, fs);
1984 let key = if could_be_long(content.len(), style) {
1985 self.claim_long(key)
1986 } else {
1987 key
1988 };
1989 self.frame.push(FrameText {
1990 cache_key: key,
1991 color: style.color_or_default(),
1992 });
1993 TextId((self.frame.len() - 1) as u32)
1994 }
1995
1996 #[inline(never)]
2005 fn claim_long(&mut self, key: u64) -> u64 {
2006 let frame_no = self.frame_no;
2007 let mut at = key;
2008 let mut n = 0u64;
2009 loop {
2010 match self.entries.get_mut(&at) {
2011 Some(Entry::Long(l)) if l.claimed != frame_no => {
2012 l.claimed = frame_no;
2013 l.last_used = frame_no;
2014 return at;
2015 }
2016 Some(Entry::Run(_)) if n == 0 => return key,
2018 Some(_) => {}
2019 None => {
2020 let Some(line) = self.long(key) else {
2021 return key;
2022 };
2023 let mut copy = line.clone();
2024 copy.claimed = frame_no;
2025 copy.last_used = frame_no;
2026 self.insert(at, Entry::Long(copy));
2027 return at;
2028 }
2029 }
2030 n += 1;
2031 at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
2032 }
2033 }
2034
2035 fn intern_any(
2041 &mut self,
2042 content: &str,
2043 style: &TextStyle,
2044 res: &Resources,
2045 fs: &mut FontSystem,
2046 ) -> u64 {
2047 if !could_be_long(content.len(), style) {
2048 return self.intern(content, style, res, fs);
2049 }
2050 let key = Self::style_key(content, style, self.scale);
2051 let long_key = key ^ LONG_SALT;
2052 let frame_no = self.frame_no;
2053 if let Some(e) = self.entries.get_mut(&long_key) {
2054 e.touch(frame_no);
2055 return long_key;
2056 }
2057 if let Some(e) = self.entries.get_mut(&key) {
2058 e.touch(frame_no);
2059 return key;
2060 }
2061 if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
2062 self.intern_keyed(key, content, style, res, fs)
2063 } else {
2064 self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
2065 }
2066 }
2067
2068 fn intern_rich_any(
2073 &mut self,
2074 spans: &[Span<'_>],
2075 base: &TextStyle,
2076 res: &Resources,
2077 fs: &mut FontSystem,
2078 ) -> u64 {
2079 let len: usize = spans.iter().map(|s| s.text.len()).sum();
2080 if !could_be_long(len, base) {
2081 return self.intern_rich(spans, base, res, fs);
2082 }
2083 let key = Self::rich_key(spans, base, self.scale);
2084 let long_key = key ^ LONG_SALT;
2085 let frame_no = self.frame_no;
2086 if let Some(e) = self.entries.get_mut(&long_key) {
2087 e.touch(frame_no);
2088 return long_key;
2089 }
2090 if let Some(e) = self.entries.get_mut(&key) {
2091 e.touch(frame_no);
2092 return key;
2093 }
2094 if spans
2097 .iter()
2098 .any(|s| has_line_break(s.text) || s.size.is_some())
2099 || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
2100 {
2101 return self.intern_rich_keyed(key, spans, base, res, fs);
2102 }
2103 let mut content = String::with_capacity(len);
2104 let mut owned = Vec::with_capacity(spans.len());
2105 for s in spans {
2106 let start = content.len();
2107 content.push_str(s.text);
2108 owned.push(OwnedSpan {
2109 start,
2110 end: content.len(),
2111 attrs: s.attrs_only(),
2112 });
2113 }
2114 self.build_long(long_key, content, owned, base, res, fs)
2115 }
2116
2117 fn build_long(
2122 &mut self,
2123 key: u64,
2124 content: String,
2125 spans: Vec<OwnedSpan>,
2126 style: &TextStyle,
2127 res: &Resources,
2128 fs: &mut FontSystem,
2129 ) -> u64 {
2130 let frame_no = self.frame_no;
2131 let wrap = style.wrap;
2132 let style = &TextStyle {
2133 wrap: TextWrap::None,
2134 ..*style
2135 };
2136 let scale = self.scale;
2137 let chunks: Vec<Chunk> = chunk_ranges(&content)
2138 .into_iter()
2139 .map(|(start, end)| Chunk {
2140 start,
2141 end,
2142 key: if spans.is_empty() {
2143 Self::style_key(&content[start..end], style, scale)
2144 } else {
2145 Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
2146 },
2147 width: None,
2148 tab: None,
2149 rows: Vec::new(),
2150 })
2151 .collect();
2152 let (w0, line_h) = match chunks.first() {
2155 Some(c) => {
2156 let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
2157 let e = self.run(k).expect("just interned");
2158 (e.intrinsic.w, e.buffer.metrics().line_height)
2159 }
2160 None => (0.0, style.line_height * self.scale),
2161 };
2162 let avg = chunks
2163 .first()
2164 .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
2165 let mut line = LongLine {
2166 content,
2167 spans,
2168 style: *style,
2169 wrap,
2170 wrap_w: None,
2171 starts: Vec::new(),
2172 chunks,
2173 prefix: Vec::new(),
2174 line_h,
2175 avg,
2176 last_used: frame_no,
2177 claimed: u64::MAX,
2178 laid_rows: 1,
2179 asked_rows: u32::MAX,
2180 bytes: 0,
2181 };
2182 if let Some(c) = line.chunks.first_mut() {
2183 c.width = Some(w0);
2184 }
2185 line.reprefix();
2186 line.bytes = ENTRY_BASE_BYTES
2187 + line.content.len()
2188 + line.chunks.len() * 48
2189 + line.spans.len() * std::mem::size_of::<OwnedSpan>();
2190 self.insert(key, Entry::Long(line));
2194 key
2195 }
2196
2197 #[allow(clippy::too_many_arguments)]
2201 fn shape_chunk(
2202 &mut self,
2203 content: &str,
2204 spans: &[OwnedSpan],
2205 start: usize,
2206 end: usize,
2207 style: &TextStyle,
2208 res: &Resources,
2209 fs: &mut FontSystem,
2210 ) -> u64 {
2211 if spans.is_empty() {
2212 self.intern(&content[start..end], style, res, fs)
2213 } else {
2214 let spans = chunk_spans(content, spans, start, end);
2215 self.intern_rich(&spans, style, res, fs)
2216 }
2217 }
2218
2219 fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
2223 let (chunk_key, known) = {
2224 let c = &self.long(key).expect("a long line").chunks[i];
2225 (c.key, c.width)
2226 };
2227 let frame_no = self.frame_no;
2228 if known.is_some()
2229 && let Some(e) = self.run_mut(chunk_key)
2230 {
2231 e.last_used = frame_no;
2232 return false;
2233 }
2234 let (text, spans, style, off_stops) = {
2237 let line = self.long(key).expect("a long line");
2238 let c = &line.chunks[i];
2239 let first = line.spans.partition_point(|s| s.end <= c.start);
2240 let spans: Vec<OwnedSpan> = line.spans[first..]
2241 .iter()
2242 .take_while(|s| s.start < c.end)
2243 .map(|s| OwnedSpan {
2244 start: s.start.max(c.start) - c.start,
2245 end: s.end.min(c.end) - c.start,
2246 attrs: s.attrs,
2247 })
2248 .collect();
2249 let bytes = line.content.as_bytes();
2250 let off_stops = c.start > 0 && bytes[c.start - 1] != b'\t';
2251 (
2252 line.content[c.start..c.end].to_string(),
2253 spans,
2254 line.style,
2255 off_stops,
2256 )
2257 };
2258 let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
2259 let w = self.run(k).expect("just interned").intrinsic.w;
2260 let tab = match off_stops && text.ends_with('\t') {
2261 true => self.chunk_tab(k, &text, &spans, &style, res, fs),
2262 false => None,
2263 };
2264 let line = self.long_mut(key).expect("just read");
2265 line.chunks[i].key = k;
2266 line.chunks[i].tab = tab;
2267 if line.chunks[i].width != Some(w) {
2268 line.chunks[i].width = Some(w);
2269 line.reprefix();
2270 }
2271 true
2272 }
2273
2274 #[inline(never)]
2280 #[allow(clippy::too_many_arguments)]
2281 fn chunk_tab(
2282 &mut self,
2283 chunk: u64,
2284 text: &str,
2285 spans: &[OwnedSpan],
2286 style: &TextStyle,
2287 res: &Resources,
2288 fs: &mut FontSystem,
2289 ) -> Option<(f32, f32)> {
2290 let at = text.len() - 1;
2291 let tab_x = {
2292 let run = self.run(chunk)?.buffer.layout_runs().next()?;
2293 if run.rtl {
2294 return None;
2295 }
2296 run.glyphs.iter().rev().find(|g| g.start == at)?.x
2297 };
2298 let alone = self.shape_chunk(text, spans, at, text.len(), style, res, fs);
2299 let every = self.run(alone)?.intrinsic.w;
2300 Some((tab_x, every))
2301 }
2302
2303 fn relayout_long(&mut self, key: u64, w: Option<f32>) {
2311 let Some(w) = w else {
2312 let line = self.long_mut(key).expect("a long line");
2313 line.wrap_w = None;
2314 line.starts.clear();
2315 for c in &mut line.chunks {
2316 c.rows.clear();
2317 }
2318 return;
2319 };
2320 let w = w.max(1.0);
2321 let line = self.long(key).expect("a long line");
2324 let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2325 starts.push((0, 0.0));
2326 let (mut row, mut x) = (0u32, 0.0f32);
2327 let rows: Vec<Vec<RowStart>> = line
2328 .chunks
2329 .iter()
2330 .enumerate()
2331 .map(|(i, c)| {
2332 let shaped = c.width.and_then(|_| self.run(c.key));
2333 let rows = match shaped {
2334 Some(e) => {
2335 let text = &line.content[c.start..c.end];
2336 let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2337 row += rows.len() as u32 - 1;
2338 x = end + line.tab_shift(i);
2342 rows
2343 }
2344 None => {
2345 let est = x + c
2346 .width
2347 .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2348 let added = (est / w).floor();
2349 row += added as u32;
2350 x = est - added * w;
2351 Vec::new()
2352 }
2353 };
2354 starts.push((row, x));
2355 rows
2356 })
2357 .collect();
2358 let line = self.long_mut(key).expect("a long line");
2359 line.wrap_w = Some(w);
2360 line.starts = starts;
2361 for (c, rows) in line.chunks.iter_mut().zip(rows) {
2362 c.rows = rows;
2363 }
2364 }
2365
2366 fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2370 let (wrap, width, line_h, cur) = {
2371 let l = self.long(key).expect("a long line");
2372 (l.wrap, l.width(), l.line_h, l.wrap_w)
2373 };
2374 let target = match max_w {
2375 Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2376 _ => None,
2377 };
2378 let differs = match (cur, target) {
2379 (None, None) => false,
2380 (Some(a), Some(b)) => (a - b).abs() > 0.5,
2381 _ => true,
2382 };
2383 if differs {
2384 self.relayout_long(key, target);
2385 }
2386 match target {
2387 Some(w) => {
2388 let rows = self.long(key).expect("a long line").rows();
2389 (Size::new(w, rows as f32 * line_h), rows)
2390 }
2391 None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2392 }
2393 }
2394
2395 pub fn measure(
2401 &mut self,
2402 content: &str,
2403 style: &TextStyle,
2404 res: &Resources,
2405 fs: &mut FontSystem,
2406 max_w: Option<f32>,
2407 ) -> TextMetrics {
2408 let key = self.intern_any(content, style, res, fs);
2409 self.measure_any(key, fs, max_w)
2410 }
2411
2412 fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2414 if self.long(key).is_some() {
2415 let scale = self.scale;
2416 let (size, lines) = match max_w {
2419 Some(m) => {
2420 let held = self.long(key).expect("checked").wrap_w;
2423 let measured = self.long_size(key, Some(m * scale));
2424 if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2425 self.relayout_long(key, held);
2426 }
2427 measured
2428 }
2429 None => {
2430 let line = self.long(key).expect("checked");
2431 (Size::new(line.width(), line.line_h), 1)
2432 }
2433 };
2434 return TextMetrics {
2435 width: size.w / scale,
2436 height: size.h / scale,
2437 lines,
2438 };
2439 }
2440 self.measure_key(key, fs, max_w)
2441 }
2442
2443 pub fn measure_rich(
2445 &mut self,
2446 spans: &[Span<'_>],
2447 base: &TextStyle,
2448 res: &Resources,
2449 fs: &mut FontSystem,
2450 max_w: Option<f32>,
2451 ) -> TextMetrics {
2452 let key = self.intern_rich_any(spans, base, res, fs);
2453 self.measure_any(key, fs, max_w)
2454 }
2455
2456 fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2457 let scale = self.scale;
2458 let entry = self.run_mut(key).expect("just interned");
2459 let target = wrap_target(entry, max_w, scale);
2460 let key = if entry.claimed != u64::MAX && wrap_differs(entry.wrap, target) {
2467 self.wrap_copy(key, target, fs)
2468 } else {
2469 key
2470 };
2471 let entry = self.run_mut(key).expect("just interned");
2472 wrap_entry(entry, fs, target);
2473 let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2474 if entry.clamp_w
2475 && let Some(t) = target
2476 {
2477 m.w = m.w.min(t);
2478 }
2479 TextMetrics {
2480 width: m.w / scale,
2481 height: m.h / scale,
2482 lines,
2483 }
2484 }
2485
2486 pub fn add_rich(
2490 &mut self,
2491 spans: &[Span<'_>],
2492 base: &TextStyle,
2493 res: &Resources,
2494 fs: &mut FontSystem,
2495 ) -> TextId {
2496 let key = self.intern_rich_any(spans, base, res, fs);
2497 let len = spans.iter().map(|s| s.text.len()).sum();
2498 let key = if could_be_long(len, base) {
2499 self.claim_long(key)
2500 } else {
2501 key
2502 };
2503 self.frame.push(FrameText {
2504 cache_key: key,
2505 color: base.color_or_default(),
2506 });
2507 TextId((self.frame.len() - 1) as u32)
2508 }
2509
2510 fn intern_rich(
2511 &mut self,
2512 spans: &[Span<'_>],
2513 base: &TextStyle,
2514 res: &Resources,
2515 fs: &mut FontSystem,
2516 ) -> u64 {
2517 let key = Self::rich_key(spans, base, self.scale);
2518 self.intern_rich_keyed(key, spans, base, res, fs)
2519 }
2520
2521 fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2525 let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2526 for s in spans {
2527 key = crate::key::mix_content(key, s.text.as_bytes());
2528 let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2529 mix(&[
2530 s.bold as u8,
2531 s.italic as u8,
2532 s.color.is_some() as u8,
2533 s.underline as u8,
2534 s.strikethrough as u8,
2535 s.bg.is_some() as u8,
2536 s.underline_style as u8,
2537 s.underline_color.is_some() as u8,
2538 ]);
2539 mix(&s.bg_radius.to_bits().to_le_bytes());
2540 let (tag, font) = match s.family {
2543 None => (0u8, 0u64),
2544 Some(FontFamily::Sans) => (1, 0),
2545 Some(FontFamily::Serif) => (2, 0),
2546 Some(FontFamily::Mono) => (3, 0),
2547 Some(FontFamily::Custom(id)) => (4, id.to_ffi()),
2548 };
2549 mix(&[tag]);
2550 mix(&font.to_le_bytes());
2551 mix(&s.size.map_or(0, f32::to_bits).to_le_bytes());
2552 for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2553 mix(&c.r.to_bits().to_le_bytes());
2554 mix(&c.g.to_bits().to_le_bytes());
2555 mix(&c.b.to_bits().to_le_bytes());
2556 mix(&c.a.to_bits().to_le_bytes());
2557 }
2558 }
2559 key
2560 }
2561
2562 fn intern_rich_keyed(
2564 &mut self,
2565 key: u64,
2566 spans: &[Span<'_>],
2567 base: &TextStyle,
2568 res: &Resources,
2569 fs: &mut FontSystem,
2570 ) -> u64 {
2571 let frame_no = self.frame_no;
2572 let scale = self.scale;
2573 if !self.entries.contains_key(&key) {
2574 let mut buffer = new_buffer(fs, base, scale);
2575 let family = res.family_of(base.family);
2576 let weights = res.weights_of(base.family);
2577 let features = cosmic_features(&base.features);
2578 let sized = spans.iter().any(|s| s.size.is_some());
2583 let texts: Vec<_> = spans.iter().map(|s| one_paragraph(s.text)).collect();
2584 let metrics_of = |size: Option<f32>| {
2585 let size = size.unwrap_or(base.size);
2586 let ratio = if base.size > 0.0 {
2587 base.line_height / base.size
2588 } else {
2589 1.0
2590 };
2591 shaper_metrics(size * scale, size * ratio * scale)
2592 };
2593 buffer.set_rich_text(
2597 spans.iter().enumerate().map(|(i, s)| {
2598 let (family, weights) = match s.family {
2599 Some(f) => (res.family_of(f), res.weights_of(f)),
2600 None => (family, weights),
2601 };
2602 let mut attrs = s
2603 .attrs(family, weights)
2604 .font_features(features.clone())
2605 .metadata(i);
2606 if base.bold && !s.bold {
2609 attrs = weights.apply(attrs, true);
2610 }
2611 if sized {
2612 attrs = attrs.metrics(metrics_of(s.size));
2613 }
2614 (&*texts[i], attrs)
2615 }),
2616 &{
2617 let attrs = weights.apply(
2618 Attrs::new().family(family).font_features(features.clone()),
2619 base.bold,
2620 );
2621 if sized {
2622 attrs.metrics(metrics_of(None))
2623 } else {
2624 attrs
2625 }
2626 },
2627 Shaping::Advanced,
2628 None,
2629 );
2630 let content = spans.iter().map(|s| s.text).collect::<String>();
2631 let mut start = 0u32;
2632 let decos = spans
2633 .iter()
2634 .map(|s| SpanDeco {
2635 start: {
2636 let at = start;
2637 start += s.text.len() as u32;
2638 at
2639 },
2640 color: s.color.map(shaped_color),
2641 underline: s.underline || base.underline,
2642 underline_color: s.underline_color.or(base.underline_color),
2644 underline_style: if s.underline {
2645 s.underline_style
2646 } else {
2647 base.underline_style
2648 },
2649 strikethrough: s.strikethrough || base.strikethrough,
2650 bg: s.bg,
2651 bg_radius: s.bg_radius,
2652 })
2653 .collect();
2654 let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2655 self.insert(key, Entry::Run(entry));
2656 }
2657 self.entries
2658 .get_mut(&key)
2659 .expect("just inserted")
2660 .touch(frame_no);
2661 key
2662 }
2663
2664 fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2667 let key = self.frame[id.0 as usize].cache_key;
2668 self.run_mut(key).expect("frame text missing from cache")
2669 }
2670
2671 fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2676 let (scale, frame_no) = (self.scale, self.frame_no);
2677 let entry = self.entry_mut(id);
2678 let target = wrap_target(entry, Some(max_w_logical), scale);
2679 if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2680 self.own_wrap(id, target, fs);
2681 return;
2682 }
2683 entry.claimed = frame_no;
2684 entry.claimed_wrap = target;
2685 wrap_entry(entry, fs, target);
2686 }
2687
2688 #[inline(never)]
2696 fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2697 let frame_no = self.frame_no;
2698 let key = self.frame[id.0 as usize].cache_key;
2699 let copy = self.wrap_copy(key, target, fs);
2700 self.frame[id.0 as usize].cache_key = copy;
2701 let entry = self.run_mut(copy).expect("just made");
2702 entry.claimed = frame_no;
2703 entry.claimed_wrap = target;
2704 }
2705
2706 #[inline(never)]
2712 fn wrap_copy(&mut self, key: u64, target: Option<f32>, fs: &mut FontSystem) -> u64 {
2713 let frame_no = self.frame_no;
2714 let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2716 let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2717 if self.run(copy).is_none() {
2718 let fork = self.run(key).expect("a run").fork(frame_no);
2719 self.insert(copy, Entry::Run(fork));
2720 }
2721 let entry = self.run_mut(copy).expect("just made");
2722 entry.last_used = frame_no;
2723 wrap_entry(entry, fs, target);
2724 copy
2725 }
2726
2727 #[allow(clippy::too_many_arguments)]
2732 pub(crate) fn emit(
2733 &mut self,
2734 id: TextId,
2735 origin: Vec2,
2736 node: Size,
2737 clip: Clip,
2738 clip_id: ClipId,
2739 clips: &mut Vec<Clip>,
2740 res: &Resources,
2741 fs: &mut FontSystem,
2742 atlas: &mut GlyphAtlas,
2743 out: &mut Vec<Quad>,
2744 sel: Option<((usize, usize), Color)>,
2745 ) {
2746 let from = self.joins.len();
2747 self.emit_text(
2748 id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2749 );
2750 if self.joins.len() == from {
2751 return;
2752 }
2753 let key = self.frame[id.0 as usize].cache_key;
2756 let clamps = match self.entries.get(&key) {
2757 Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2758 Some(Entry::Run(e)) => e.clamp_w,
2759 None => false,
2760 };
2761 let own = clamps.then(|| {
2762 let ox = crate::geom::snap_px(origin.x * self.scale);
2763 (ox, ox + (node.w * self.scale).ceil())
2764 });
2765 for j in &mut self.joins[from..] {
2766 j.outer = clip_id;
2767 j.own = own;
2768 }
2769 }
2770
2771 #[allow(clippy::too_many_arguments)]
2777 #[inline(never)]
2778 fn emit_long(
2779 &mut self,
2780 key: u64,
2781 node: Size,
2782 ox: f32,
2783 oy: f32,
2784 nudge: Vec2,
2785 color: Color,
2786 clip: Clip,
2787 clip_id: ClipId,
2788 clips: &mut Vec<Clip>,
2789 res: &Resources,
2790 fs: &mut FontSystem,
2791 atlas: &mut GlyphAtlas,
2792 out: &mut Vec<Quad>,
2793 sel: Option<((usize, usize), Color)>,
2794 ) {
2795 let scale = self.scale;
2796 let line = self.long(key).expect("a long line");
2797 let (clip, clip_id) = if line.wrap == TextWrap::None {
2803 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2804 let clip = clip.intersect(own, crate::display::SQUARE);
2805 let vis = clip.visible();
2806 if vis.w <= 0.0 || vis.h <= 0.0 {
2807 return;
2808 }
2809 (clip, crate::display::intern_clip(clips, clip))
2810 } else {
2811 (clip, clip_id)
2812 };
2813 let vis = clip.visible();
2815 if let Some(((from, to), tint)) = sel {
2816 let rects = self.long_highlight(line, from, to);
2817 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2818 }
2819 if let Some(w) = line.wrap_w {
2820 self.emit_long_rows(
2821 key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2822 );
2823 return;
2824 }
2825 let (first, last) = {
2826 if line.chunks.is_empty() {
2827 return;
2828 }
2829 let a = line.chunk_at(vis.x - ox).saturating_sub(1);
2830 let b = (line.chunk_at(vis.x + vis.w - ox) + 1).min(line.chunks.len() - 1);
2831 (a, b)
2832 };
2833 for i in first..=last {
2834 self.ensure_chunk(key, i, res, fs);
2835 }
2836 let frame_no = self.frame_no;
2837 self.long_mut(key).expect("checked").last_used = frame_no;
2838 for i in first..=last {
2839 let (chunk_key, x) = {
2840 let line = self.long(key).expect("checked");
2841 (line.chunks[i].key, line.prefix[i])
2842 };
2843 let raster = &mut self.raster;
2844 let entry = self
2845 .entries
2846 .get_mut(&chunk_key)
2847 .and_then(Entry::run_mut)
2848 .expect("just ensured");
2849 emit_entry(
2850 entry,
2851 ox + x,
2852 oy,
2853 nudge,
2854 color,
2855 clip,
2856 clip_id,
2857 raster,
2858 fs,
2859 atlas,
2860 out,
2861 &mut self.joins,
2862 );
2863 }
2864 }
2865
2866 #[allow(clippy::too_many_arguments)]
2868 fn emit_text(
2869 &mut self,
2870 id: TextId,
2871 origin: Vec2,
2872 node: Size,
2873 clip: Clip,
2874 clip_id: ClipId,
2875 clips: &mut Vec<Clip>,
2876 res: &Resources,
2877 fs: &mut FontSystem,
2878 atlas: &mut GlyphAtlas,
2879 out: &mut Vec<Quad>,
2880 sel: Option<((usize, usize), Color)>,
2885 ) {
2886 let FrameText {
2887 cache_key: key,
2888 color,
2889 } = self.frame[id.0 as usize];
2890 let scale = self.scale;
2891 let ox = crate::geom::snap_px(origin.x * scale);
2892 let oy = crate::geom::snap_px(origin.y * scale);
2893 let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2896 if self.long(key).is_some() {
2899 self.emit_long(
2900 key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2901 );
2902 return;
2903 }
2904 self.ensure_wrap(id, node.w, fs);
2905 let raster = &mut self.raster;
2906 let entry = self
2907 .entries
2908 .get_mut(&key)
2909 .and_then(Entry::run_mut)
2910 .expect("frame text missing from cache");
2911
2912 let (clip, clip_id) = if entry.clamp_w {
2915 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2916 let clip = clip.intersect(own, crate::display::SQUARE);
2917 (clip, crate::display::intern_clip(clips, clip))
2918 } else {
2919 (clip, clip_id)
2920 };
2921 if let Some(((from, to), tint)) = sel {
2922 let rects = Self::run_highlight(entry, from, to);
2923 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2924 }
2925 emit_entry(
2926 entry,
2927 ox,
2928 oy,
2929 nudge,
2930 color,
2931 clip,
2932 clip_id,
2933 raster,
2934 fs,
2935 atlas,
2936 out,
2937 &mut self.joins,
2938 );
2939 }
2940}
2941
2942fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2956 Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2957}
2958
2959fn push_highlight(
2962 rects: &[Rect],
2963 ox: f32,
2964 oy: f32,
2965 nudge: Vec2,
2966 tint: Color,
2967 clip: ClipId,
2968 out: &mut Vec<Quad>,
2969) {
2970 for r in rects {
2971 out.push(Quad {
2972 rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2973 color: tint,
2974 border_color: Color::TRANSPARENT,
2975 radius: [0.0; 4],
2976 border_w: 0.0,
2977 blur: 0.0,
2978 kind: QuadKind::Solid,
2979 uv: [0; 4],
2980 clip,
2981 });
2982 }
2983}
2984
2985impl TextSystem {
2986 #[allow(clippy::too_many_arguments)]
2990 fn emit_long_rows(
2991 &mut self,
2992 key: u64,
2993 w: f32,
2994 ox: f32,
2995 oy: f32,
2996 nudge: Vec2,
2997 color: Color,
2998 clip: Clip,
2999 clip_id: ClipId,
3000 res: &Resources,
3001 fs: &mut FontSystem,
3002 atlas: &mut GlyphAtlas,
3003 out: &mut Vec<Quad>,
3004 ) {
3005 let vis = clip.visible();
3006 let (first, last) = {
3007 let line = self.long(key).expect("a long line");
3008 let ra = ((vis.y - oy) / line.line_h).floor().max(0.0) as u32;
3009 let rb = ((vis.y + vis.h - oy) / line.line_h).floor().max(0.0) as u32;
3010 let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
3011 return;
3012 };
3013 (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
3014 };
3015 let mut fresh = false;
3016 for i in first..=last {
3017 fresh |= self.ensure_chunk(key, i, res, fs);
3018 }
3019 if fresh {
3020 self.relayout_long(key, Some(w));
3023 let line = self.long_mut(key).expect("checked");
3024 let rows = line.rows();
3025 if rows != line.laid_rows && rows != line.asked_rows {
3026 line.asked_rows = rows;
3027 self.owed = true;
3028 }
3029 }
3030 let frame_no = self.frame_no;
3031 self.long_mut(key).expect("checked").last_used = frame_no;
3032 for i in first..=last {
3033 let chunk_key = self.long(key).expect("checked").chunks[i].key;
3034 let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
3038 self.entries.get_disjoint_mut([&key, &chunk_key])
3039 else {
3040 continue;
3041 };
3042 let (row0, head_x) = line.starts[i];
3043 let chunk = &line.chunks[i];
3044 if chunk.rows.is_empty() {
3045 continue;
3046 }
3047 let raster = &mut self.raster;
3048 emit_entry_rows(
3049 entry,
3050 ox,
3051 oy + row0 as f32 * line.line_h,
3052 nudge,
3053 head_x,
3054 &chunk.rows,
3055 line.line_h,
3056 color,
3057 clip,
3058 clip_id,
3059 raster,
3060 fs,
3061 atlas,
3062 out,
3063 &mut self.joins,
3064 );
3065 }
3066 }
3067}
3068
3069#[allow(clippy::too_many_arguments)]
3074fn emit_entry_rows(
3075 entry: &mut CachedText,
3076 ox: f32,
3077 oy: f32,
3078 nudge: Vec2,
3079 head_x: f32,
3080 rows: &[RowStart],
3081 line_h: f32,
3082 color: Color,
3083 clip: Clip,
3084 clip_id: ClipId,
3085 raster: &mut Raster,
3086 fs: &mut FontSystem,
3087 atlas: &mut GlyphAtlas,
3088 out: &mut Vec<Quad>,
3089 joins: &mut Vec<JoinBg>,
3090) {
3091 build_templates(entry, raster, fs, atlas);
3092 let vis = clip.visible();
3094 let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
3100 let (x0, x1) = (d.x, d.x + d.w);
3101 for r in 0..rows.len() {
3102 let ra = rows[r].x;
3103 let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
3104 let (a, b) = (x0.max(ra), x1.min(rb));
3105 if b <= a {
3106 continue;
3107 }
3108 let dx = if r == 0 { head_x } else { 0.0 } - ra;
3109 let x = ox + a + dx;
3110 let y = oy + d.y + r as f32 * line_h;
3111 if y >= vis.y + vis.h {
3112 break;
3113 }
3114 if y + d.h <= vis.y || x >= vis.x + vis.w || x + (b - a) <= vis.x {
3115 continue;
3116 }
3117 let quad = Quad {
3118 rect: if d.under {
3119 on_pixels(x, y, b - a, d.h, nudge)
3120 } else {
3121 Rect::new(x, y, b - a, d.h)
3122 },
3123 color: d.color.unwrap_or(color),
3124 border_color: Color::TRANSPARENT,
3125 radius: [0.0; 4],
3126 border_w: 0.0,
3127 blur: 0.0,
3128 kind: QuadKind::Solid,
3129 clip: clip_id,
3130 uv: [0; 4],
3131 };
3132 if d.under && d.radius > 0.0 {
3136 joins.push(JoinBg {
3137 quad: out.len() as u32,
3138 radius: d.radius,
3139 outer: clip_id,
3140 own: None,
3141 });
3142 }
3143 if d.under || d.style == UnderlineStyle::Solid {
3144 out.push(quad);
3145 } else {
3146 crate::deco::push_line(
3147 out,
3148 d.style,
3149 x,
3150 y,
3151 b - a,
3152 d.h,
3153 d.color.unwrap_or(color),
3154 clip_id,
3155 );
3156 }
3157 }
3158 };
3159 for d in entry.deco.iter().filter(|d| d.under) {
3160 row_pieces(d, out);
3161 }
3162 let mut r = 0usize;
3163 for g in &entry.glyphs {
3164 while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
3165 r += 1;
3166 }
3167 let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
3168 let x = ox + g.x + dx;
3169 let y = oy + g.y + r as f32 * line_h;
3170 if y >= vis.y + vis.h {
3177 break;
3178 }
3179 if y + g.h <= vis.y || x >= vis.x + vis.w || x + g.w <= vis.x {
3180 continue;
3181 }
3182 out.push(Quad {
3183 rect: Rect::new(x, y, g.w, g.h),
3184 color: g.color.unwrap_or(color),
3185 border_color: Color::TRANSPARENT,
3186 radius: [0.0; 4],
3187 border_w: 0.0,
3188 blur: 0.0,
3189 kind: g.kind,
3190 clip: clip_id,
3191 uv: g.uv,
3192 });
3193 }
3194 for d in entry.deco.iter().filter(|d| !d.under) {
3195 row_pieces(d, out);
3196 }
3197}
3198
3199fn break_rows(
3212 buffer: &Buffer,
3213 text: &str,
3214 wrap: TextWrap,
3215 w: f32,
3216 head_x: f32,
3217) -> (Vec<RowStart>, f32) {
3218 let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
3219 let Some(run) = buffer.layout_runs().next() else {
3220 return (rows, head_x);
3221 };
3222 let glyphs = run.glyphs;
3223 let blank = |g: &cosmic_text::LayoutGlyph| {
3224 text.get(g.start..g.end)
3225 .is_some_and(|s| s.chars().all(char::is_whitespace))
3226 };
3227 let spaces = wrap == TextWrap::BreakSpaces;
3228 let mut pieces: Vec<(usize, usize)> = Vec::new();
3229 match wrap {
3230 TextWrap::Word | TextWrap::BreakSpaces => {
3231 let mut at = 0usize;
3232 let mut piece = |from: usize, to: usize| {
3233 if !spaces {
3234 pieces.push((from, to));
3235 return;
3236 }
3237 let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
3240 if ink > 0 {
3241 pieces.push((from, from + ink));
3242 }
3243 for (i, c) in text[from + ink..to].char_indices() {
3244 let at = from + ink + i;
3245 pieces.push((at, at + c.len_utf8()));
3246 }
3247 };
3248 for (i, _) in unicode_linebreak::linebreaks(text) {
3249 if i > at {
3250 piece(at, i);
3251 at = i;
3252 }
3253 }
3254 if at < text.len() {
3255 piece(at, text.len());
3256 }
3257 }
3258 TextWrap::Glyph | TextWrap::None => {
3259 pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
3260 }
3261 }
3262 let mut row_x0 = 0.0f32;
3263 let mut avail = w - head_x;
3264 let mut gi = 0usize;
3265 for (s, e) in pieces {
3266 let g0 = gi;
3267 while gi < glyphs.len() && glyphs[gi].start < e {
3268 gi += 1;
3269 }
3270 if g0 == gi {
3271 continue;
3272 }
3273 let piece = &glyphs[g0..gi];
3274 let x0 = piece[0].x;
3275 let ink_end = piece
3276 .iter()
3277 .rev()
3278 .find(|g| spaces || !blank(g))
3279 .map_or(x0, |g| g.x + g.w);
3280 let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
3286 if ink_end - row_x0 > avail && opens {
3287 rows.push(RowStart {
3288 byte: s as u32,
3289 x: x0,
3290 });
3291 row_x0 = x0;
3292 avail = w;
3293 }
3294 if ink_end - row_x0 > avail {
3295 for g in piece {
3297 if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
3298 rows.push(RowStart {
3299 byte: g.start as u32,
3300 x: g.x,
3301 });
3302 row_x0 = g.x;
3303 avail = w;
3304 }
3305 }
3306 }
3307 }
3308 let end = if rows.len() == 1 {
3309 head_x + run.line_w
3310 } else {
3311 run.line_w - row_x0
3312 };
3313 (rows, end)
3314}
3315
3316fn build_templates(
3319 entry: &mut CachedText,
3320 raster: &mut Raster,
3321 fs: &mut FontSystem,
3322 atlas: &mut GlyphAtlas,
3323) {
3324 {
3325 let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
3327 if entry.glyphs_built_for != Some(built_for) {
3328 entry.glyphs.clear();
3329 entry.deco.clear();
3330 let lines = line_cap(entry.max_lines);
3331 let decorated = entry.span_deco.iter().any(SpanDeco::any);
3332 let painted = entry.span_deco.windows(2).any(|w| w[0].color != w[1].color);
3335 let starts = if (decorated || painted) && entry.span_deco.len() > 1 {
3336 line_starts(&entry.buffer, &entry.content)
3337 } else {
3338 Vec::new()
3339 };
3340 let mut places = Places::default();
3341 for run in entry.buffer.layout_runs().take(lines) {
3342 if decorated || painted {
3343 let base = starts.get(run.line_i).copied().unwrap_or(0);
3344 places.of_line(&run, base, &entry.span_deco, painted);
3345 }
3346 if decorated {
3347 build_decorations(&run, &places.parts, &entry.span_deco, fs, &mut entry.deco);
3348 }
3349 for (i, glyph) in run.glyphs.iter().enumerate() {
3350 let physical = glyph.physical((0.0, 0.0), 1.0);
3351 let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
3352 continue;
3353 };
3354 let template = GlyphTemplate {
3355 x: physical.x as f32 + slot.left as f32,
3356 y: run.line_y.round() + physical.y as f32 - slot.top as f32,
3357 w: slot.w as f32,
3358 h: slot.h as f32,
3359 uv: [slot.x, slot.y, slot.w, slot.h],
3360 kind: glyph_kind(&slot),
3361 color: glyph
3362 .color_opt
3363 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3364 byte: glyph.start as u32,
3365 };
3366 if painted && template.kind != QuadKind::GlyphColor {
3368 places.push_painted(
3369 template,
3370 i,
3371 run.glyphs,
3372 &entry.span_deco,
3373 &mut entry.glyphs,
3374 );
3375 } else {
3376 entry.glyphs.push(template);
3377 }
3378 }
3379 }
3380 entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
3384 }
3385 }
3386}
3387
3388#[allow(clippy::too_many_arguments)]
3392fn emit_entry(
3393 entry: &mut CachedText,
3394 ox: f32,
3395 oy: f32,
3396 nudge: Vec2,
3397 color: Color,
3398 clip: Clip,
3399 clip_id: ClipId,
3400 raster: &mut Raster,
3401 fs: &mut FontSystem,
3402 atlas: &mut GlyphAtlas,
3403 out: &mut Vec<Quad>,
3404 joins: &mut Vec<JoinBg>,
3405) {
3406 build_templates(entry, raster, fs, atlas);
3407 let vis = clip.visible();
3409 {
3410 let inside = |x: f32, y: f32, w: f32, h: f32| {
3411 oy + y + h >= vis.y
3412 && oy + y <= vis.y + vis.h
3413 && ox + x < vis.x + vis.w
3414 && ox + x + w > vis.x
3415 };
3416 let deco_quad = |d: &DecoTemplate| Quad {
3417 rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
3418 color: d.color.unwrap_or(color),
3419 border_color: Color::TRANSPARENT,
3420 radius: [0.0; 4],
3421 border_w: 0.0,
3422 blur: 0.0,
3423 kind: QuadKind::Solid,
3424 clip: clip_id,
3425 uv: [0; 4],
3426 };
3427 for d in entry
3432 .deco
3433 .iter()
3434 .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3435 {
3436 if d.radius > 0.0 {
3437 joins.push(JoinBg {
3438 quad: out.len() as u32,
3439 radius: d.radius,
3440 outer: clip_id,
3441 own: None,
3442 });
3443 }
3444 out.push(Quad {
3445 rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3446 ..deco_quad(d)
3447 });
3448 }
3449
3450 out.extend(
3454 entry
3455 .glyphs
3456 .iter()
3457 .filter(|g| {
3458 oy + g.y + g.h >= vis.y && ox + g.x < vis.x + vis.w && ox + g.x + g.w > vis.x
3459 })
3460 .take_while(|g| oy + g.y <= vis.y + vis.h)
3461 .map(|g| Quad {
3462 rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3463 color: g.color.unwrap_or(color),
3464 border_color: Color::TRANSPARENT,
3465 radius: [0.0; 4],
3466 border_w: 0.0,
3467 blur: 0.0,
3468 kind: g.kind,
3469 clip: clip_id,
3470 uv: g.uv,
3471 }),
3472 );
3473 for d in entry
3474 .deco
3475 .iter()
3476 .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3477 {
3478 match d.style {
3481 UnderlineStyle::Solid => out.push(deco_quad(d)),
3482 style => crate::deco::push_line(
3483 out,
3484 style,
3485 ox + d.x,
3486 oy + d.y,
3487 d.w,
3488 d.h,
3489 d.color.unwrap_or(color),
3490 clip_id,
3491 ),
3492 }
3493 }
3494 }
3495}
3496
3497fn could_be_long(len: usize, style: &TextStyle) -> bool {
3505 (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3506 && style.max_lines == 0
3507 && !style.ellipsis
3508}
3509
3510fn has_line_break(content: &str) -> bool {
3513 content.bytes().any(|b| b == b'\n' || b == b'\r')
3514}
3515
3516fn has_rtl(content: &str) -> bool {
3524 !content.is_ascii()
3525 && content.chars().any(|c| {
3526 matches!(c as u32,
3527 0x0590..=0x08FF
3528 | 0x200F | 0x202B | 0x202E | 0x2067
3529 | 0xFB1D..=0xFDFF
3530 | 0xFE70..=0xFEFF
3531 | 0x10800..=0x10FFF
3532 | 0x1E800..=0x1EFFF)
3533 })
3534}
3535
3536#[derive(Clone, Copy, Debug)]
3540struct Part {
3541 x0: f32,
3542 x1: f32,
3543 span: usize,
3545 glyph: usize,
3547}
3548
3549#[derive(Default)]
3552struct Places {
3553 parts: Vec<Part>,
3555 by_x: Vec<u32>,
3558 cuts: Vec<u32>,
3560}
3561
3562impl Places {
3563 fn of_line(
3571 &mut self,
3572 run: &cosmic_text::LayoutRun<'_>,
3573 base: usize,
3574 spans: &[SpanDeco],
3575 painted: bool,
3576 ) {
3577 use unicode_segmentation::UnicodeSegmentation;
3578 self.parts.clear();
3579 let last = spans.len().saturating_sub(1);
3580 for (gi, g) in run.glyphs.iter().enumerate() {
3581 let span = g.metadata.min(last);
3583 let crosses = spans
3584 .get(span + 1)
3585 .is_some_and(|next| (next.start as usize) < base + g.end);
3586 let cluster = run.text.get(g.start..g.end).filter(|_| crosses);
3587 let Some(cluster) = cluster else {
3588 self.parts.push(Part {
3589 x0: g.x,
3590 x1: g.x + g.w,
3591 span,
3592 glyph: gi,
3593 });
3594 continue;
3595 };
3596 let share = g.w / cluster.graphemes(true).count().max(1) as f32;
3597 for (k, (i, _)) in cluster.grapheme_indices(true).enumerate() {
3598 let byte = base + g.start + i;
3599 let span = spans
3600 .partition_point(|s| s.start as usize <= byte)
3601 .saturating_sub(1);
3602 let (x0, x1) = if g.level.is_rtl() {
3603 let x1 = g.x + g.w - k as f32 * share;
3604 (x1 - share, x1)
3605 } else {
3606 let x0 = g.x + k as f32 * share;
3607 (x0, x0 + share)
3608 };
3609 match self.parts.last_mut() {
3610 Some(p) if p.glyph == gi && p.span == span => {
3611 p.x0 = p.x0.min(x0);
3612 p.x1 = p.x1.max(x1);
3613 }
3614 _ => self.parts.push(Part {
3615 x0,
3616 x1,
3617 span,
3618 glyph: gi,
3619 }),
3620 }
3621 }
3622 }
3623 self.by_x.clear();
3624 if painted {
3625 let parts = &self.parts;
3626 self.by_x.extend(
3627 (0..parts.len() as u32).filter(|&k| parts[k as usize].x1 > parts[k as usize].x0),
3628 );
3629 if !self
3631 .by_x
3632 .is_sorted_by(|&a, &b| parts[a as usize].x0 <= parts[b as usize].x0)
3633 {
3634 self.by_x
3635 .sort_by(|&a, &b| parts[a as usize].x0.total_cmp(&parts[b as usize].x0));
3636 }
3637 }
3638 }
3639
3640 fn push_painted(
3655 &mut self,
3656 t: GlyphTemplate,
3657 gi: usize,
3658 glyphs: &[cosmic_text::LayoutGlyph],
3659 spans: &[SpanDeco],
3660 out: &mut Vec<GlyphTemplate>,
3661 ) {
3662 let parts = &self.parts;
3663 let own = {
3664 let lo = parts.partition_point(|p| p.glyph < gi);
3665 let hi = parts.partition_point(|p| p.glyph <= gi);
3666 &parts[lo..hi]
3667 };
3668 if own.is_empty() || t.w < 2.0 {
3669 out.push(t);
3670 return;
3671 }
3672 let g = &glyphs[gi];
3673 let kin = |p: &Part| {
3674 let n = &glyphs[p.glyph];
3675 p.glyph == gi
3676 || n.font_id == g.font_id
3677 && n.font_weight == g.font_weight
3678 && n.font_size == g.font_size
3679 && n.cache_key_flags == g.cache_key_flags
3680 };
3681 let color_of = |p: &Part| spans.get(p.span).and_then(|s| s.color);
3682 let at = |x: f32| {
3685 let k = self
3686 .by_x
3687 .partition_point(|&k| parts[k as usize].x0 <= x)
3688 .checked_sub(1)
3689 .map(|k| &parts[self.by_x[k] as usize]);
3690 match k {
3691 Some(p) if x < p.x1 && kin(p) => color_of(p),
3692 _ => {
3693 let near = own
3694 .iter()
3695 .min_by(|a, b| {
3696 let d = |p: &Part| (p.x0 - x).max(x - p.x1).max(0.0);
3697 d(a).total_cmp(&d(b))
3698 })
3699 .expect("not empty");
3700 color_of(near)
3701 }
3702 }
3703 };
3704 let (x0, x1) = (t.x, t.x + t.w);
3705 let w = t.w as u32;
3706 self.cuts.clear();
3707 let from = self
3708 .by_x
3709 .partition_point(|&k| parts[k as usize].x0 < x0)
3710 .saturating_sub(1);
3711 for &k in &self.by_x[from..] {
3712 let p = &parts[k as usize];
3713 if p.x0 >= x1 {
3714 break;
3715 }
3716 for edge in [p.x0, p.x1] {
3717 let c = (edge - x0).round();
3718 if c >= 1.0 && c < t.w {
3719 self.cuts.push(c as u32);
3720 }
3721 }
3722 }
3723 if self.cuts.is_empty() {
3724 out.push(GlyphTemplate {
3725 color: at(x0 + t.w / 2.0),
3726 ..t
3727 });
3728 return;
3729 }
3730 self.cuts.sort_unstable();
3731 self.cuts.dedup();
3732 self.cuts.push(w);
3733 let first = out.len();
3734 let mut a = 0u32;
3735 for &b in &self.cuts {
3736 let color = at(x0 + (a + b) as f32 / 2.0);
3737 let sliced = out.len() > first;
3738 match out.last_mut() {
3739 Some(prev) if sliced && prev.color == color => {
3741 prev.w += (b - a) as f32;
3742 prev.uv[2] += b - a;
3743 }
3744 _ => out.push(GlyphTemplate {
3745 x: x0 + a as f32,
3746 w: (b - a) as f32,
3747 uv: [t.uv[0] + a, t.uv[1], b - a, t.uv[3]],
3748 color,
3749 ..t
3750 }),
3751 }
3752 a = b;
3753 }
3754 }
3755}
3756
3757fn build_decorations(
3769 run: &cosmic_text::LayoutRun<'_>,
3770 parts: &[Part],
3771 spans: &[SpanDeco],
3772 fs: &mut FontSystem,
3773 out: &mut Vec<DecoTemplate>,
3774) {
3775 let mut last_bg: Option<usize> = None;
3778 let mut i = 0;
3779 while i < parts.len() {
3780 let span_no = parts[i].span;
3781 let mut j = i + 1;
3782 while j < parts.len() && parts[j].span == span_no {
3783 j += 1;
3784 }
3785 let deco = spans.get(span_no).copied().unwrap_or_default();
3786 if deco.any() {
3787 let group = &parts[i..j];
3788 let x0 = group.iter().map(|p| p.x0).fold(f32::INFINITY, f32::min);
3789 let x1 = group.iter().map(|p| p.x1).fold(f32::NEG_INFINITY, f32::max);
3790 let head = &run.glyphs[group[0].glyph];
3791 let w = (x1 - x0).max(0.0);
3792 match (deco.bg, last_bg.map(|k| &mut out[k])) {
3793 (Some(bg), Some(prev))
3794 if prev.color == Some(bg)
3795 && prev.radius == deco.bg_radius
3796 && (prev.x + prev.w - x0).abs() < 0.01 =>
3797 {
3798 prev.w = (x1 - prev.x).max(prev.w);
3799 }
3800 (Some(bg), _) => {
3801 let (y, h) = match head.line_height_opt {
3805 Some(lh) if lh < run.line_height - 0.01 => {
3806 let g = head;
3807 let (asc, desc) = fs
3808 .get_font(g.font_id, g.font_weight)
3809 .map(|font| {
3810 let m = font.as_swash().metrics(&[]).scale(g.font_size);
3811 (m.ascent, m.descent)
3812 })
3813 .unwrap_or((0.8 * g.font_size, 0.2 * g.font_size));
3814 let mid = run.line_y - (asc - desc) / 2.0;
3815 (mid - lh / 2.0, lh)
3816 }
3817 _ => (run.line_top, run.line_height),
3818 };
3819 last_bg = Some(out.len());
3820 out.push(DecoTemplate {
3821 x: x0,
3822 y,
3823 w,
3824 h,
3825 color: Some(bg),
3826 under: true,
3827 style: UnderlineStyle::Solid,
3828 radius: deco.bg_radius,
3829 });
3830 }
3831 (None, _) => last_bg = None,
3832 }
3833 if deco.underline || deco.strikethrough {
3834 let first = head;
3835 let metrics = fs
3836 .get_font(first.font_id, first.font_weight)
3837 .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3838 let (under_off, strike_off, stroke) = match metrics {
3841 Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3842 None => (
3843 -0.1 * first.font_size,
3844 0.3 * first.font_size,
3845 0.06 * first.font_size,
3846 ),
3847 };
3848 let stroke = stroke.max(1.0).round();
3849 let baseline = run.line_y.round();
3850 if deco.underline {
3851 out.push(DecoTemplate {
3852 x: x0,
3853 y: (baseline - under_off).round(),
3854 w,
3855 h: stroke,
3856 color: deco.underline_color.or(deco.color),
3857 under: false,
3858 style: deco.underline_style,
3859 radius: 0.0,
3860 });
3861 }
3862 if deco.strikethrough {
3863 out.push(DecoTemplate {
3864 x: x0,
3865 y: (baseline - strike_off).round(),
3866 w,
3867 h: stroke,
3868 color: deco.color,
3869 under: false,
3870 style: UnderlineStyle::Solid,
3871 radius: 0.0,
3872 });
3873 }
3874 }
3875 } else {
3876 last_bg = None;
3877 }
3878 i = j;
3879 }
3880}
3881
3882impl TextSystem {
3883 pub(crate) fn place(
3888 &mut self,
3889 key: Key,
3890 ancestry: &Ancestry,
3891 id: TextId,
3892 origin: Vec2,
3893 scope: Option<Key>,
3894 drawn: bool,
3895 ) {
3896 let cache_key = self.frame[id.0 as usize].cache_key;
3897 self.places.push(TextPlace {
3898 key,
3899 ancestors: ancestry.keys,
3900 depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3901 none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3902 cache_key,
3903 origin,
3904 scope,
3905 drawn,
3906 });
3907 }
3908
3909 fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3915 let list = if prev {
3916 self.places.prev()
3917 } else {
3918 &self.places
3919 };
3920 let mut base = 0usize;
3921 let mut out = Vec::new();
3922 for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3923 let Some(entry) = self.entries.get(&place.cache_key) else {
3924 continue;
3925 };
3926 out.push((place, entry, base));
3927 base += entry.content().len();
3928 }
3929 out
3930 }
3931
3932 pub(crate) fn content_len(&self, id: TextId) -> usize {
3935 let key = self.frame[id.0 as usize].cache_key;
3936 self.entries.get(&key).map_or(0, |e| e.content().len())
3937 }
3938
3939 pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3948 let list = if prev {
3949 self.places.prev()
3950 } else {
3951 &self.places
3952 };
3953 let mut base = 0usize;
3954 let mut out = Vec::new();
3955 for place in list.iter().filter(|p| p.scope == Some(scope)) {
3956 let Some(text) = self.entries.get(&place.cache_key) else {
3957 continue;
3958 };
3959 let len = text.content().len();
3960 out.push(ScopeRun { place, text, base });
3961 base += len;
3962 }
3963 out
3964 }
3965
3966 pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3973 let runs = self.scope_runs(scope, prev);
3974 let px = point.x * self.scale;
3975 let py = point.y * self.scale;
3976 let gap = |r: &Rect| {
3977 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3978 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3979 (dy, dx)
3980 };
3981 let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3982 let ga = gap(&self.scope_box(a));
3983 let gb = gap(&self.scope_box(b));
3984 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3985 })?;
3986 let byte = self.hit_in_run(run, point)?;
3987 Some((run.place.key, byte))
3988 }
3989
3990 fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3992 self.entry_box(run.place, run.text)
3993 }
3994
3995 fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
4000 match entry {
4001 Entry::Run(e) => self.physical_box(place, e),
4002 Entry::Long(l) => {
4003 let (ox, oy) = self.physical_origin(place);
4004 let w = l.wrap_w.unwrap_or_else(|| l.width());
4005 Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
4006 }
4007 }
4008 }
4009
4010 fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
4012 let (_, oy) = self.physical_origin(place);
4013 match entry {
4014 Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
4015 Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
4016 }
4017 }
4018
4019 fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
4022 match run.text {
4023 Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
4024 Entry::Run(e) => {
4025 let (ox, oy) = self.physical_origin(run.place);
4026 let cursor = e
4027 .buffer
4028 .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
4029 Some(e.byte_of(cursor))
4030 }
4031 }
4032 }
4033
4034 pub(crate) fn word_at(
4041 &self,
4042 scope: Key,
4043 node: Key,
4044 byte: usize,
4045 prev: bool,
4046 ) -> Option<(usize, usize)> {
4047 let run = self
4048 .scope_runs(scope, prev)
4049 .into_iter()
4050 .find(|r| r.place.key == node)?;
4051 let content = run.text.content();
4052 Some(word_range(content, byte))
4053 }
4054
4055 pub(crate) fn scope_offset(
4061 &self,
4062 scope: Key,
4063 node: Key,
4064 byte: usize,
4065 prev: bool,
4066 ) -> Option<usize> {
4067 let run = self
4068 .scope_runs(scope, prev)
4069 .into_iter()
4070 .find(|r| r.place.key == node)?;
4071 Some(run.base + byte.min(run.text.content().len()))
4072 }
4073
4074 pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
4089 let (from, to) = (from.min(to), from.max(to));
4090 let mut out = String::new();
4091 let mut prev_run: Option<&ScopeRun<'_>> = None;
4092 for run in &self.scope_runs(scope, prev) {
4093 let (start, end) = run.span();
4094 if end <= from || start >= to {
4095 continue;
4096 }
4097 if let Some(p) = prev_run
4098 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
4099 {
4100 out.push_str("<br>");
4101 }
4102 let content = run.text.content();
4103 let lo = floor_boundary(content, from.saturating_sub(start));
4104 let hi = floor_boundary(content, (to - start).min(content.len()));
4105 match run.text {
4106 Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
4107 Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
4108 }
4109 prev_run = Some(run);
4110 }
4111 out
4112 }
4113
4114 fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
4120 for c in &line.chunks {
4121 let (a, b) = (lo.max(c.start), hi.min(c.end));
4122 if a >= b {
4123 continue;
4124 }
4125 match c.width.and_then(|_| self.run(c.key)) {
4126 Some(e) => html_of_run(e, a - c.start, b - c.start, out),
4127 None => escape_into(&line.content[a..b], out),
4128 }
4129 }
4130 }
4131
4132 pub(crate) fn scope_selection_anchor(
4142 &self,
4143 scope: Key,
4144 from: usize,
4145 to: usize,
4146 prev: bool,
4147 ) -> Option<Vec2> {
4148 let (from, to) = (from.min(to), from.max(to));
4149 for run in self.scope_runs(scope, prev) {
4150 let (start, end) = run.span();
4151 if !run.place.drawn || end <= from || start >= to {
4152 continue;
4153 }
4154 let content = run.text.content();
4155 let lo = floor_boundary(content, from.saturating_sub(start));
4156 let hi = floor_boundary(content, (to - start).min(content.len()));
4157 let (ox, oy) = self.physical_origin(run.place);
4158 match run.text {
4159 Entry::Run(e) => {
4160 if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
4161 return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
4162 }
4163 }
4164 Entry::Long(l) => {
4165 if let Some((x, y)) = self.long_caret_local(l, lo) {
4166 return Some(Vec2::new(
4167 (ox + x) / self.scale,
4168 (oy + y + self.long_baseline(l)) / self.scale,
4169 ));
4170 }
4171 }
4172 }
4173 }
4174 None
4175 }
4176
4177 fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
4180 let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
4181 for run in entry.buffer.layout_runs() {
4182 if run.line_i < a.line || run.line_i > b.line {
4183 continue;
4184 }
4185 if let Some((x, _)) = run.highlight(a, b).next() {
4186 return Some((x, run.line_y));
4187 }
4188 }
4189 None
4190 }
4191
4192 pub(crate) fn scope_selection_rect(
4198 &self,
4199 scope: Key,
4200 from: usize,
4201 to: usize,
4202 prev: bool,
4203 ) -> Option<Rect> {
4204 let (from, to) = (from.min(to), from.max(to));
4205 let mut out: Option<Rect> = None;
4206 for run in self.scope_runs(scope, prev) {
4207 let (start, end) = run.span();
4208 if !run.place.drawn || end <= from || start >= to {
4209 continue;
4210 }
4211 let content = run.text.content();
4212 let lo = floor_boundary(content, from.saturating_sub(start));
4213 let hi = floor_boundary(content, (to - start).min(content.len()));
4214 let (ox, oy) = self.physical_origin(run.place);
4220 let rects = match run.text {
4221 Entry::Long(l) => self.long_highlight(l, lo, hi),
4222 Entry::Run(e) => Self::run_highlight(e, lo, hi),
4223 };
4224 for r in rects {
4225 let r = Rect::new(
4226 (ox + r.x) / self.scale,
4227 (oy + r.y) / self.scale,
4228 r.w / self.scale,
4229 r.h / self.scale,
4230 );
4231 out = Some(match out {
4232 None => r,
4233 Some(o) => o.union(&r),
4234 });
4235 }
4236 }
4237 out
4238 }
4239
4240 pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
4245 let (from, to) = (from.min(to), from.max(to));
4246 let runs = self.scope_runs(scope, prev);
4247 let mut out = String::new();
4248 let mut reserved = false;
4249 let mut prev_run: Option<&ScopeRun<'_>> = None;
4250 for run in &runs {
4251 let (start, end) = run.span();
4252 if end <= from || start >= to {
4253 continue;
4254 }
4255 if !reserved {
4256 out.reserve(to - from + runs.len());
4257 reserved = true;
4258 }
4259 if let Some(p) = prev_run
4260 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
4261 {
4262 out.push('\n');
4263 }
4264 let content = run.text.content();
4265 let lo = from.saturating_sub(start).min(content.len());
4266 let hi = (to - start).min(content.len());
4267 out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
4268 prev_run = Some(run);
4269 }
4270 out
4271 }
4272
4273 fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
4277 let (ox, oy) = self.physical_origin(place);
4278 let px = point.x * self.scale - ox;
4279 let py = point.y * self.scale - oy;
4280 if line.chunks.is_empty() {
4281 return Some(TextHit { byte: 0, line: 0 });
4282 }
4283 if let Some(w) = line.wrap_w {
4284 return self.long_hit_wrapped(line, w, px, py);
4285 }
4286 if px >= line.width() {
4287 return Some(TextHit {
4288 byte: line.content.len(),
4289 line: 0,
4290 });
4291 }
4292 let i = line.chunk_at(px.max(0.0));
4293 let c = &line.chunks[i];
4294 let local = px - line.prefix[i];
4295 let byte = match self.run(c.key) {
4296 Some(_) if let Some(b) = line.placed_tab_hit(i, local) => b,
4297 Some(e) if c.width.is_some() => {
4298 let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
4299 c.start + cursor.index.min(c.end - c.start)
4300 }
4301 _ => {
4302 let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
4303 let mut b = (c.start + est).min(c.end);
4304 while !line.content.is_char_boundary(b) {
4305 b -= 1;
4306 }
4307 b
4308 }
4309 };
4310 Some(TextHit { byte, line: 0 })
4311 }
4312
4313 fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
4317 let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
4318 let Some((a, b)) = line.chunks_on_rows(row, row) else {
4319 return Some(TextHit {
4320 byte: line.content.len(),
4321 line: row,
4322 });
4323 };
4324 let i = (a..=b)
4327 .find(|&j| {
4328 let (r1, end_x) = line.starts[j + 1];
4329 px < if r1 == row { end_x } else { w }
4330 })
4331 .unwrap_or(b);
4332 let c = &line.chunks[i];
4333 let (row0, head_x) = line.starts[i];
4334 let r = (row - row0) as usize;
4335 let byte = match self.run(c.key) {
4336 Some(e) if r < c.rows.len() => {
4337 let rs = c.rows[r];
4338 let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
4339 let lo = rs.byte as usize;
4340 let hi = c
4341 .rows
4342 .get(r + 1)
4343 .map_or(c.end - c.start, |n| n.byte as usize);
4344 let tab_here = (lo..hi).contains(&(c.end - c.start - 1));
4346 match line.placed_tab_hit(i, local_x) {
4347 Some(b) if tab_here => b,
4348 _ => {
4349 let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
4350 c.start + cursor.index.clamp(lo, hi)
4351 }
4352 }
4353 }
4354 _ => {
4355 let linear = r as f32 * w + px - head_x;
4358 let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
4359 let mut b = (c.start + est).min(c.end);
4360 while !line.content.is_char_boundary(b) {
4361 b -= 1;
4362 }
4363 b
4364 }
4365 };
4366 let next_row = self
4371 .long_caret_local(line, byte)
4372 .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
4373 let byte = if next_row && byte > 0 {
4374 floor_boundary(&line.content, byte - 1)
4375 } else {
4376 byte
4377 };
4378 Some(TextHit { byte, line: row })
4379 }
4380
4381 fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
4384 let (ox, oy) = self.physical_origin(place);
4385 let scale = self.scale;
4386 let (x, y) = self.long_caret_local(line, byte)?;
4387 Some(Rect::new(
4388 (ox + x) / scale,
4389 (oy + y) / scale,
4390 0.0,
4391 line.line_h / scale,
4392 ))
4393 }
4394
4395 fn long_baseline(&self, line: &LongLine) -> f32 {
4401 line.chunks
4402 .first()
4403 .and_then(|c| self.run(c.key))
4404 .and_then(|e| e.buffer.layout_runs().next())
4405 .map_or(line.line_h * 0.8, |r| r.line_y.round())
4406 }
4407
4408 fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
4413 let byte = byte.min(line.content.len());
4414 if let Some(w) = line.wrap_w
4415 && !line.chunks.is_empty()
4416 {
4417 let i = line.chunk_of_byte(byte);
4418 let c = &line.chunks[i];
4419 let local = byte - c.start;
4420 let (row0, head_x) = line.starts[i];
4421 let (r, x) = match self.run(c.key) {
4422 Some(e) if !c.rows.is_empty() => {
4423 let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
4424 let run = e.buffer.layout_runs().next()?;
4425 let cx = caret_x(&run, local.min(c.end - c.start)) + line.end_shift(i, local);
4426 (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
4427 }
4428 _ => {
4429 let linear = head_x + local as f32 * line.avg;
4430 let r = (linear / w).floor();
4431 (r as usize, linear - r * w)
4432 }
4433 };
4434 let y = (row0 as usize + r) as f32 * line.line_h;
4435 return Some((x, y));
4436 }
4437 let x = if line.chunks.is_empty() {
4438 0.0
4439 } else {
4440 let i = line.chunk_of_byte(byte);
4441 let c = &line.chunks[i];
4442 let local = byte - c.start;
4443 match self.run(c.key) {
4444 Some(e) if c.width.is_some() => {
4445 let run = e.buffer.layout_runs().next()?;
4446 let x = caret_x(&run, local.min(c.end - c.start));
4447 line.prefix[i] + x + line.end_shift(i, local)
4448 }
4449 _ => line.prefix[i] + local as f32 * line.avg,
4450 }
4451 };
4452 Some((x, 0.0))
4453 }
4454
4455 fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
4461 let Some((x0, y0)) = self.long_caret_local(line, from) else {
4462 return Vec::new();
4463 };
4464 let Some((x1, y1)) = self.long_caret_local(line, to) else {
4465 return Vec::new();
4466 };
4467 let h = line.line_h;
4468 if (y1 - y0).abs() < f32::EPSILON {
4469 return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
4470 }
4471 let w = line.wrap_w.unwrap_or_else(|| line.width());
4473 let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
4474 let mut y = y0 + h;
4475 while y < y1 - h / 2.0 {
4476 out.push(Rect::new(0.0, y, w, h));
4477 y += h;
4478 }
4479 out.push(Rect::new(0.0, y1, x1.max(0.0), h));
4480 out
4481 }
4482
4483 fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
4489 let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
4490 let mut out = Vec::new();
4491 for run in entry.buffer.layout_runs() {
4492 if run.line_i < a.line || run.line_i > b.line {
4493 continue;
4494 }
4495 let h = run.line_height;
4498 let mut any = false;
4499 for (x, w) in run.highlight(a, b) {
4500 any = true;
4501 out.push(Rect::new(x, run.line_top, w.max(2.0), h));
4502 }
4503 if !any && run.glyphs.is_empty() && b.line > run.line_i {
4506 out.push(Rect::new(0.0, run.line_top, 2.0, h));
4507 }
4508 }
4509 out
4510 }
4511
4512 fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
4515 (
4516 crate::geom::snap_px(place.origin.x * self.scale),
4517 crate::geom::snap_px(place.origin.y * self.scale),
4518 )
4519 }
4520
4521 fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
4523 let (ox, oy) = self.physical_origin(place);
4524 let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
4525 Rect::new(ox, oy, size.w, size.h)
4526 }
4527
4528 pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
4532 let runs = self.runs_of(key, prev);
4533 if runs.is_empty() {
4534 return None;
4535 }
4536 let px = point.x * self.scale;
4537 let py = point.y * self.scale;
4538 let gap = |r: &Rect| {
4541 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
4542 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
4543 (dy, dx)
4544 };
4545 let (place, entry, base) = runs
4546 .iter()
4547 .min_by(|a, b| {
4548 let ga = gap(&self.entry_box(a.0, a.1));
4549 let gb = gap(&self.entry_box(b.0, b.1));
4550 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
4551 })
4552 .copied()?;
4553 let (ox, oy) = self.physical_origin(place);
4554 let (byte, hit_top) = match entry {
4555 Entry::Long(line) => {
4556 let hit = self.long_hit(place, line, point)?;
4557 (hit.byte, oy + hit.line as f32 * line.line_h)
4558 }
4559 Entry::Run(entry) => {
4560 let bx = self.physical_box(place, entry);
4567 let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
4568 let cursor = entry.buffer.hit(px - ox, py - oy)?;
4569 let row =
4570 visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
4571 let top = entry
4572 .buffer
4573 .layout_runs()
4574 .nth(row)
4575 .map_or(0.0, |r| r.line_top);
4576 (entry.byte_of(cursor), oy + top)
4577 }
4578 };
4579 let mut tops: Vec<f32> = runs
4584 .iter()
4585 .flat_map(|(p, e, _)| self.row_tops(p, e))
4586 .collect();
4587 tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4588 tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
4589 let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
4590 Some(TextHit {
4591 byte: base + byte,
4592 line: line as u32,
4593 })
4594 }
4595
4596 pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
4600 let runs = self.runs_of(key, prev);
4601 let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
4602 let byte = byte.min(total);
4603 let (place, entry, base) = runs
4604 .iter()
4605 .find(|(_, e, base)| byte < base + e.content().len())
4606 .or_else(|| runs.last())
4607 .copied()?;
4608 let byte = (byte - base).min(entry.content().len());
4609 let entry = match entry {
4610 Entry::Long(line) => return self.long_caret(place, line, byte),
4611 Entry::Run(entry) => entry,
4612 };
4613 let (ox, oy) = self.physical_origin(place);
4614 let cursor = entry.cursor_of(byte);
4615 let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
4616 let run = entry.buffer.layout_runs().nth(run_no)?;
4617 let x = caret_x(&run, cursor.index);
4618 let scale = self.scale;
4619 Some(Rect::new(
4620 (ox + x) / scale,
4621 (oy + run.line_top) / scale,
4622 0.0,
4623 run.line_height / scale,
4624 ))
4625 }
4626}
4627
4628fn word_range(content: &str, byte: usize) -> (usize, usize) {
4638 if content.is_empty() {
4639 return (0, 0);
4640 }
4641 #[derive(PartialEq)]
4642 enum Class {
4643 Word,
4644 Space,
4645 Other,
4646 }
4647 let class = |c: char| {
4648 if c.is_alphanumeric() || c == '_' {
4649 Class::Word
4650 } else if c.is_whitespace() {
4651 Class::Space
4652 } else {
4653 Class::Other
4654 }
4655 };
4656 let at = floor_boundary(content, byte.min(content.len()));
4659 let at = if at >= content.len() {
4660 content.char_indices().next_back().map_or(0, |(i, _)| i)
4661 } else {
4662 at
4663 };
4664 let Some(here) = content[at..].chars().next().map(class) else {
4665 return (at, at);
4666 };
4667 let mut start = at;
4668 for (i, c) in content[..at].char_indices().rev() {
4669 if class(c) != here {
4670 break;
4671 }
4672 start = i;
4673 }
4674 let mut end = at;
4675 for (i, c) in content[at..].char_indices() {
4676 if class(c) != here {
4677 break;
4678 }
4679 end = at + i + c.len_utf8();
4680 }
4681 (start, end)
4682}
4683
4684fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4689 let starts = line_starts(&entry.buffer, &entry.content);
4690 for (li, line) in entry.buffer.lines.iter().enumerate() {
4691 let base = starts.get(li).copied().unwrap_or(0);
4692 let text = line.text();
4693 if li > 0 {
4694 if base > lo && base <= hi {
4697 out.push_str("<br>");
4698 }
4699 }
4700 let attrs_list = line.attrs_list();
4706 let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4707 for (i, _) in text.char_indices() {
4708 let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4709 match spans.last_mut() {
4710 Some((_, end, prev)) if *prev == a && *end == i => {
4711 *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4712 }
4713 _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4714 }
4715 }
4716 for (start, end, attrs) in &spans {
4717 let (s, e) = (base + start, base + end);
4718 let (s, e) = (s.max(lo), e.min(hi));
4719 if s >= e {
4720 continue;
4721 }
4722 let piece = &entry.content[s..e];
4723 let bold = attrs.weight > attrs_list.defaults().weight
4728 || attrs
4729 .cache_key_flags
4730 .contains(crate::weights::SYNTHETIC_BOLD);
4731 let italic = attrs.style != cosmic_text::Style::Normal;
4732 if let Some(c) = attrs.color_opt {
4733 let _ = std::fmt::Write::write_fmt(
4734 out,
4735 format_args!(
4736 "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4737 c.r(),
4738 c.g(),
4739 c.b()
4740 ),
4741 );
4742 }
4743 if bold {
4744 out.push_str("<b>");
4745 }
4746 if italic {
4747 out.push_str("<i>");
4748 }
4749 escape_into(piece, out);
4750 if italic {
4751 out.push_str("</i>");
4752 }
4753 if bold {
4754 out.push_str("</b>");
4755 }
4756 if attrs.color_opt.is_some() {
4757 out.push_str("</span>");
4758 }
4759 }
4760 }
4761}
4762
4763fn escape_into(s: &str, out: &mut String) {
4767 for ch in s.chars() {
4768 match ch {
4769 '&' => out.push_str("&"),
4770 '<' => out.push_str("<"),
4771 '>' => out.push_str(">"),
4772 _ => out.push(ch),
4773 }
4774 }
4775}
4776
4777fn floor_boundary(s: &str, i: usize) -> usize {
4783 let mut i = i.min(s.len());
4784 while i > 0 && !s.is_char_boundary(i) {
4785 i -= 1;
4786 }
4787 i
4788}
4789
4790fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4791 paragraph_starts(buffer, content).collect()
4792}
4793
4794fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4800 let mut at = 0usize;
4801 buffer.lines.iter().map(move |line| {
4802 let start = at.min(content.len());
4803 at += line.text().len();
4804 let rest = &content[at.min(content.len())..];
4805 for ending in ["\r\n", "\n\r", "\n", "\r"] {
4806 if rest.starts_with(ending) {
4807 at += ending.len();
4808 break;
4809 }
4810 }
4811 start
4812 })
4813}
4814
4815fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4825 let mut last_of_line = None;
4826 let mut before = None;
4827 for (n, run) in buffer.layout_runs().enumerate() {
4828 if run.line_i != line_i {
4829 if last_of_line.is_some() {
4830 break;
4831 }
4832 continue;
4833 }
4834 last_of_line = Some(n);
4835 if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4836 return Some((n, n));
4837 }
4838 if run
4839 .glyphs
4840 .iter()
4841 .map(|g| g.start)
4842 .min()
4843 .is_some_and(|s| s <= index)
4844 {
4845 before = Some(n);
4846 }
4847 }
4848 before.or(last_of_line).map(|n| (n, n))
4849}
4850
4851fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4855 if let Some(g) = run
4856 .glyphs
4857 .iter()
4858 .find(|g| g.start <= index && index < g.end)
4859 {
4860 return if g.level.is_rtl() { g.x + g.w } else { g.x };
4861 }
4862 match run.glyphs.last() {
4863 Some(g) if !g.level.is_rtl() => g.x + g.w,
4864 Some(g) => g.x,
4865 None => 0.0,
4866 }
4867}
4868
4869impl Default for TextSystem {
4870 fn default() -> Self {
4871 Self::new()
4872 }
4873}
4874
4875fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4878 let max_w = max_w_logical?;
4879 let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4880 (!intrinsic_fits).then_some(max_w * scale)
4881}
4882
4883pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4891 match (current, target) {
4892 (None, None) => false,
4893 (Some(a), Some(b)) => (a - b).abs() > 0.5,
4894 _ => true,
4895 }
4896}
4897
4898fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4899 if wrap_differs(entry.wrap, target) {
4900 entry.buffer.set_size(target, None);
4901 entry.buffer.shape_until_scroll(fs, false);
4902 entry.wrap = target;
4903 }
4904}
4905
4906impl CachedText {
4907 fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4910 let start = paragraph_starts(&self.buffer, &self.content)
4911 .nth(cursor.line)
4912 .unwrap_or(0);
4913 (start + cursor.index).min(self.content.len())
4914 }
4915
4916 fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4922 let mut li = 0;
4923 let mut start = 0;
4924 for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4925 if s <= byte {
4926 (li, start) = (i, s);
4927 } else {
4928 break;
4929 }
4930 }
4931 let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4932 cosmic_text::Cursor::new(li, (byte - start).min(len))
4933 }
4934
4935 fn new(
4936 mut buffer: Buffer,
4937 content: String,
4938 style: &TextStyle,
4939 span_deco: Vec<SpanDeco>,
4940 fs: &mut FontSystem,
4941 frame_no: u64,
4942 ) -> Self {
4943 buffer.shape_until_scroll(fs, false);
4944 let max_lines = line_budget(style);
4945 let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4946 let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4947 Self {
4948 buffer,
4949 bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4950 content,
4951 wrap: None,
4952 intrinsic,
4953 max_lines,
4954 clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4955 last_used: frame_no,
4956 glyphs: Vec::new(),
4957 deco: Vec::new(),
4958 span_deco,
4959 glyphs_built_for: None,
4960 claimed: u64::MAX,
4961 claimed_wrap: None,
4962 breaks_anywhere: style.wrap == TextWrap::Glyph,
4963 min_content: None,
4964 }
4965 }
4966
4967 fn fork(&self, frame_no: u64) -> Self {
4971 Self {
4972 buffer: self.buffer.clone(),
4973 content: self.content.clone(),
4974 wrap: self.wrap,
4975 intrinsic: self.intrinsic,
4976 max_lines: self.max_lines,
4977 clamp_w: self.clamp_w,
4978 last_used: frame_no,
4979 bytes: self.bytes,
4980 glyphs: Vec::new(),
4981 deco: Vec::new(),
4982 span_deco: self.span_deco.clone(),
4983 glyphs_built_for: None,
4984 claimed: u64::MAX,
4985 claimed_wrap: None,
4986 breaks_anywhere: self.breaks_anywhere,
4987 min_content: self.min_content,
4988 }
4989 }
4990
4991 fn min_content(&mut self) -> f32 {
5002 if let Some(w) = self.min_content {
5003 return w;
5004 }
5005 let w = if self.clamp_w {
5006 self.intrinsic.w
5007 } else {
5008 widest_unbreakable(&self.buffer, self.breaks_anywhere)
5009 };
5010 self.min_content = Some(w);
5011 w
5012 }
5013}
5014
5015fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
5019 let mut widest = 0.0f32;
5020 let mut line = usize::MAX;
5021 let mut breaks: Vec<usize> = Vec::new();
5022 let (mut run_w, mut ink) = (0.0f32, 0.0f32);
5023 for run in buffer.layout_runs() {
5024 if run.line_i != line {
5025 widest = widest.max(ink);
5026 (run_w, ink) = (0.0, 0.0);
5027 line = run.line_i;
5028 breaks.clear();
5029 if !anywhere {
5030 breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
5031 }
5032 }
5033 for g in run.glyphs {
5034 if anywhere || breaks.binary_search(&g.start).is_ok() {
5035 widest = widest.max(ink);
5036 (run_w, ink) = (0.0, 0.0);
5037 }
5038 run_w += g.w;
5039 let blank = run
5040 .text
5041 .get(g.start..g.end)
5042 .is_some_and(|t| t.chars().all(char::is_whitespace));
5043 if !blank {
5044 ink = run_w;
5045 }
5046 }
5047 }
5048 widest.max(ink)
5049}
5050
5051pub(crate) fn shaper_metrics(size: f32, line_height: f32) -> Metrics {
5058 let px = |v: f32| if v.is_finite() { v.max(1.0) } else { 1.0 };
5059 Metrics::new(px(size), px(line_height))
5060}
5061
5062pub(crate) fn one_paragraph(s: &str) -> std::borrow::Cow<'_, str> {
5076 let ends = |c: char| matches!(c, '\u{1c}'..='\u{1e}' | '\u{85}' | '\u{2029}');
5077 if !s.contains(ends) {
5078 return std::borrow::Cow::Borrowed(s);
5079 }
5080 std::borrow::Cow::Owned(
5081 s.chars()
5082 .map(|c| match c {
5083 '\u{1c}'..='\u{1e}' => '\u{1f}',
5084 '\u{85}' => '\u{80}',
5085 '\u{2029}' => '\u{2028}',
5086 c => c,
5087 })
5088 .collect(),
5089 )
5090}
5091
5092fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
5095 let metrics = shaper_metrics(style.size * scale, style.line_height * scale);
5096 let mut buffer = Buffer::new(fs, metrics);
5097 buffer.set_wrap(match style.wrap {
5098 TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
5099 TextWrap::Glyph => Wrap::Glyph,
5100 TextWrap::None => Wrap::None,
5101 });
5102 if style.ellipsis {
5103 let lines = line_budget(style).max(1);
5104 buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
5105 }
5106 buffer.set_size(None, None);
5107 buffer
5108}
5109
5110fn line_budget(style: &TextStyle) -> usize {
5113 if style.max_lines > 0 {
5114 style.max_lines as usize
5115 } else if style.ellipsis {
5116 1
5117 } else {
5118 0
5119 }
5120}
5121
5122fn line_cap(max_lines: usize) -> usize {
5123 if max_lines == 0 {
5124 usize::MAX
5125 } else {
5126 max_lines
5127 }
5128}
5129
5130pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
5136 let mut w = 0.0f32;
5137 let mut lines = 0u32;
5138 let lh = buffer.metrics().line_height;
5142 let (mut sum, mut uniform) = (0.0f32, true);
5143 for run in buffer.layout_runs().take(line_cap(max_lines)) {
5144 w = w.max(run.line_w);
5145 lines += 1;
5146 sum += run.line_height;
5147 uniform &= run.line_height == lh;
5148 }
5149 (
5150 Size::new(w, if uniform { lines as f32 * lh } else { sum }),
5151 lines,
5152 )
5153}
5154
5155impl TextSystem {
5156 pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
5157 let scale = self.scale;
5158 if let Some(line) = self.long_of(id) {
5159 return Size::new(line.width() / scale, line.line_h / scale);
5160 }
5161 let e = self.entry_mut(id);
5162 Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
5163 }
5164
5165 pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
5170 let scale = self.scale;
5171 if self.long_of(id).is_some() {
5172 return 0.0;
5173 }
5174 self.entry_mut(id).min_content() / scale
5175 }
5176
5177 fn long_of(&self, id: TextId) -> Option<&LongLine> {
5179 self.long(self.frame[id.0 as usize].cache_key)
5180 }
5181
5182 pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
5183 let scale = self.scale;
5184 if self.long_of(id).is_some() {
5185 let key = self.frame[id.0 as usize].cache_key;
5188 let (size, rows) = self.long_size(key, Some(max_w * scale));
5189 self.long_mut(key).expect("a long line").laid_rows = rows;
5190 return Size::new(size.w / scale, size.h / scale);
5191 }
5192 self.ensure_wrap(id, max_w, fs);
5193 let e = self.entry_mut(id);
5194 let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
5195 if e.clamp_w {
5196 m.w = m.w.min(max_w * scale);
5199 }
5200 Size::new(m.w / scale, m.h / scale)
5201 }
5202}
5203
5204impl TextSystem {
5205 pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
5210 let scale = self.scale;
5211 if let Some(line) = self.long_of(id) {
5212 return self.long_baseline(line) / scale;
5213 }
5214 let e = self.entry_mut(id);
5215 let b = e
5216 .buffer
5217 .layout_runs()
5218 .next()
5219 .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
5220 b / scale
5221 }
5222}
5223
5224#[cfg(test)]
5227mod default_families {
5228 use super::*;
5229 use cosmic_text::Family;
5230 use cosmic_text::fontdb::FaceInfo;
5231
5232 fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
5235 let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
5236 buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
5237 buffer.shape_until_scroll(fs, false);
5238 let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
5239 fs.db().face(id).cloned()
5240 }
5241
5242 #[test]
5243 fn mono_is_an_upright_monospaced_face() {
5244 let mut fs = new_font_system().0;
5245 if !fs.db().faces().any(|f| f.monospaced) {
5246 eprintln!("skipped: no monospaced face installed");
5247 return;
5248 }
5249 let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
5250 let mono = default_families(&fs)[2].to_string();
5251 assert_eq!(
5252 face.style,
5253 cosmic_text::Style::Normal,
5254 "Mono ({mono}) resolved to {:?}",
5255 face.post_script_name
5256 );
5257 assert!(
5258 face.monospaced,
5259 "Mono ({mono}) resolved to {:?}, which is not monospaced",
5260 face.post_script_name
5261 );
5262 }
5263
5264 #[test]
5265 fn sans_and_serif_are_upright_and_not_monospaced() {
5266 let mut fs = new_font_system().0;
5267 if fs.db().faces().next().is_none() {
5268 eprintln!("skipped: no face installed");
5269 return;
5270 }
5271 let [sans, serif, _] = default_families(&fs).map(str::to_string);
5272 for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
5273 let face = resolved(&mut fs, family).expect("M shapes");
5274 assert_eq!(
5275 face.style,
5276 cosmic_text::Style::Normal,
5277 "{name} resolved to {:?}",
5278 face.post_script_name
5279 );
5280 assert!(
5281 !face.monospaced,
5282 "{name} resolved to {:?}, which is monospaced",
5283 face.post_script_name
5284 );
5285 }
5286 }
5287
5288 #[test]
5291 fn pinned_names_are_installed_or_cosmic_texts_own() {
5292 let fs = new_font_system().0;
5293 let db = fs.db();
5294 for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
5295 let installed = db
5296 .faces()
5297 .any(|f| f.families.iter().any(|(fam, _)| fam == name));
5298 let on_list = list.contains(&name);
5299 assert!(
5300 installed == on_list,
5301 "{name}: installed {installed}, on the platform list {on_list}"
5302 );
5303 }
5304 }
5305}
5306
5307#[cfg(test)]
5314mod unmeasurable_faces {
5315 use super::*;
5316 use crate::testing::{font_face, han_face, unmeasurable_face};
5317 use cosmic_text::Family;
5318 use cosmic_text::fontdb::{Database, Source};
5319
5320 fn font_system() -> FontSystem {
5325 let mut db = Database::new();
5326 for bytes in [
5327 font_face("Kui Mono", 400, false, true),
5328 unmeasurable_face("Kui Bitmap"),
5329 han_face("Kui Han"),
5330 ] {
5331 db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
5332 }
5333 let mut fs = font_system_with("en-US".into(), db);
5334 fs.db_mut().set_monospace_family("Kui Mono");
5335 fs
5336 }
5337
5338 fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
5339 fs.db()
5340 .face(id)
5341 .map_or_else(String::new, |f| f.families[0].0.clone())
5342 }
5343
5344 #[test]
5345 fn a_face_without_metrics_is_not_in_the_database() {
5346 let fs = font_system();
5347 let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
5348 assert_eq!(families, ["Kui Mono", "Kui Han"]);
5349 }
5350
5351 #[test]
5355 fn han_in_mono_falls_back_to_a_face_that_measures() {
5356 let mut fs = font_system();
5357 let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
5358 let attrs = Attrs::new().family(Family::Monospace);
5359 buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
5360 buffer.shape_until_scroll(&mut fs, false);
5361 let run = buffer.layout_runs().next().expect("one line");
5362 let glyphs: Vec<(&str, f32, f32, String)> = run
5363 .glyphs
5364 .iter()
5365 .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
5366 .collect();
5367 for &(text, x, w, _) in &glyphs {
5368 assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
5369 }
5370 let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
5371 assert_eq!(
5372 faces,
5373 [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
5374 );
5375 let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
5377 assert_eq!(xs, [0.0, 7.0, 14.0]);
5378 for glyph in run.glyphs {
5379 glyph.physical((0.0, 0.0), 1.0);
5380 }
5381 }
5382}