1use cosmic_text::{
8 Attrs, Buffer, CacheKeyFlags, Ellipsize, EllipsizeHeightLimit, FontSystem, Metrics, Shaping,
9 Style as FontStyle, SwashContent, Weight, Wrap,
10};
11use rustc_hash::FxHashMap;
12
13use crate::atlas::GlyphAtlas;
14use crate::color::Color;
15use crate::display::{Clip, ClipId, Quad, QuadKind};
16use crate::geom::{Rect, Size, Vec2};
17use crate::key::Key;
18use crate::resources::Resources;
19use crate::retain::Kept;
20use crate::spec::{FontFamily, TextStyle, TextWrap, UnderlineStyle};
21use crate::tree::TextId;
22use crate::value::Value;
23
24pub(crate) struct Raster {
31 ctx: swash::scale::ScaleContext,
32 axes: FxHashMap<cosmic_text::fontdb::ID, Option<crate::weights::WghtAxis>>,
34 pub(crate) subpixel: bool,
38}
39
40impl Raster {
41 fn new() -> Self {
42 Self {
43 ctx: swash::scale::ScaleContext::new(),
44 axes: FxHashMap::default(),
45 subpixel: false,
46 }
47 }
48
49 fn image(
61 &mut self,
62 fs: &mut FontSystem,
63 key: cosmic_text::CacheKey,
64 format: swash::zeno::Format,
65 ) -> Option<cosmic_text::SwashImage> {
66 use swash::scale::{Render, Source, StrikeWith};
67 use swash::zeno::{Angle, Transform, Vector};
68 let font = fs.get_font(key.font_id, key.font_weight)?;
69 let swash_font = font.as_swash();
70 let axis = self
71 .axes
72 .entry(key.font_id)
73 .or_insert_with(|| {
74 let weight = fs.db().face(key.font_id).map_or(400, |face| face.weight.0);
75 crate::weights::WghtAxis::read(swash_font, weight)
76 })
77 .as_ref();
78 let size = f32::from_bits(key.font_size_bits);
79 let face_weight = f32::from(key.font_weight.0);
80 let bold = key.flags.contains(crate::weights::SYNTHETIC_BOLD);
81 let asked = if bold {
82 f32::from(Weight::BOLD.0)
83 } else {
84 face_weight
85 };
86 let at = axis.map(|axis| axis.coordinate(asked));
87 let embolden = bold
90 && match (axis, at) {
91 (Some(axis), Some(at)) => at <= axis.coordinate(face_weight),
92 _ => true,
93 };
94 let mut scaler = self
95 .ctx
96 .builder(swash_font)
97 .size(size)
98 .hint(!key.flags.contains(CacheKeyFlags::DISABLE_HINTING));
99 if let Some(at) = at {
100 scaler = scaler.normalized_coords(
101 swash_font
102 .variations()
103 .normalized_coords([(swash::Tag::from_be_bytes(*b"wght"), at)]),
104 );
105 }
106 let mut scaler = scaler.build();
107 let offset = if key.flags.contains(CacheKeyFlags::PIXEL_FONT) {
108 Vector::new(key.x_bin.as_float().round(), key.y_bin.as_float().round())
109 } else {
110 Vector::new(key.x_bin.as_float(), key.y_bin.as_float())
111 };
112 Render::new(&[
113 Source::ColorOutline(0),
114 Source::ColorBitmap(StrikeWith::BestFit),
115 Source::Outline,
116 ])
117 .format(format)
118 .offset(offset)
119 .embolden(if embolden {
120 crate::weights::embolden_strength(size)
121 } else {
122 0.0
123 })
124 .transform(
125 key.flags
126 .contains(CacheKeyFlags::FAKE_ITALIC)
127 .then(|| Transform::skew(Angle::from_degrees(14.0), Angle::from_degrees(0.0))),
128 )
129 .render(&mut scaler, key.glyph_id)
130 }
131}
132
133pub(crate) fn raster_glyph(
135 key: cosmic_text::CacheKey,
136 fs: &mut FontSystem,
137 raster: &mut Raster,
138 atlas: &mut crate::atlas::GlyphAtlas,
139) -> Option<crate::atlas::GlyphSlot> {
140 atlas.get_or_insert(key, || {
141 let format = if raster.subpixel {
142 swash::zeno::Format::Subpixel
143 } else {
144 swash::zeno::Format::Alpha
145 };
146 let image = raster.image(fs, key, format)?;
147 if image.placement.width == 0 || image.placement.height == 0 {
148 return None;
149 }
150 let (data, is_color, subpixel) = match image.content {
151 SwashContent::Mask => {
152 let mut rgba = Vec::with_capacity(image.data.len() * 4);
153 for &a in image.data.iter() {
154 rgba.extend_from_slice(&[255, 255, 255, a]);
155 }
156 (rgba, false, false)
157 }
158 SwashContent::Color => (image.data.to_vec(), true, false),
159 SwashContent::SubpixelMask => {
160 let mut rgba = image.data.to_vec();
164 for px in rgba.as_chunks_mut::<4>().0 {
165 px[3] = px[0].max(px[1]).max(px[2]);
166 }
167 (rgba, false, true)
168 }
169 };
170 Some(crate::atlas::RasterGlyph {
171 w: image.placement.width,
172 h: image.placement.height,
173 left: image.placement.left,
174 top: image.placement.top,
175 color: is_color,
176 subpixel,
177 data,
178 })
179 })
180}
181
182pub(crate) fn glyph_kind(slot: &crate::atlas::GlyphSlot) -> QuadKind {
184 if slot.color_glyph {
185 QuadKind::GlyphColor
186 } else if slot.subpixel {
187 QuadKind::GlyphSubpixel
188 } else {
189 QuadKind::GlyphMask
190 }
191}
192
193pub const DEFAULT_TEXT_CACHE_BYTES: usize = 64 << 20;
199
200const ENTRY_BASE_BYTES: usize = 4096;
210const ENTRY_GLYPH_BYTES: usize = 480;
211
212pub const LONG_LINE_BYTES: usize = 4096;
218const CHUNK_BYTES: usize = 1024;
223const LONG_SALT: u64 = 0x5f5f_6c6f_6e67_5f5f;
226const COPY_SALT: u64 = 0x5f5f_636f_7079_5f5f;
229
230const EVICT_TO_NUMERATOR: usize = 3;
234const EVICT_TO_DENOMINATOR: usize = 4;
235
236pub(crate) struct CachedText {
237 buffer: Buffer,
238 content: String,
241 wrap: Option<f32>,
243 intrinsic: Size,
245 max_lines: usize,
248 clamp_w: bool,
251 last_used: u64,
252 bytes: usize,
254 glyphs: Vec<GlyphTemplate>,
257 deco: Vec<DecoTemplate>,
261 span_deco: Vec<SpanDeco>,
264 glyphs_built_for: Option<(Option<u32>, u64)>,
266 claimed: u64,
271 claimed_wrap: Option<f32>,
272 breaks_anywhere: bool,
275 min_content: Option<f32>,
279}
280
281struct GlyphTemplate {
282 x: f32,
283 y: f32,
284 w: f32,
285 h: f32,
286 uv: [u32; 4],
287 kind: QuadKind,
288 color: Option<Color>,
290 byte: u32,
293}
294
295struct DecoTemplate {
297 x: f32,
298 y: f32,
299 w: f32,
300 h: f32,
301 color: Option<Color>,
304 under: bool,
306 style: UnderlineStyle,
309 radius: f32,
312}
313
314#[derive(Clone, Copy, Debug)]
323pub(crate) struct JoinBg {
324 pub quad: u32,
325 pub radius: f32,
326 pub outer: ClipId,
327 pub own: Option<(f32, f32)>,
328}
329
330#[derive(Clone, Copy, Default)]
332struct SpanDeco {
333 underline: bool,
334 underline_color: Option<Color>,
335 underline_style: UnderlineStyle,
336 strikethrough: bool,
337 bg: Option<Color>,
338 bg_radius: f32,
341}
342
343impl SpanDeco {
344 fn of_style(style: &TextStyle) -> Self {
345 Self {
346 underline: style.underline,
347 underline_color: style.underline_color,
348 underline_style: style.underline_style,
349 strikethrough: style.strikethrough,
350 bg: None,
351 bg_radius: 0.0,
352 }
353 }
354
355 fn any(&self) -> bool {
356 self.underline || self.strikethrough || self.bg.is_some()
357 }
358}
359
360#[derive(Clone, Copy, Debug)]
363pub struct Span<'a> {
364 pub text: &'a str,
365 pub color: Option<Color>,
366 pub bold: bool,
367 pub italic: bool,
368 pub underline: bool,
371 pub underline_color: Option<Color>,
373 pub underline_style: UnderlineStyle,
375 pub strikethrough: bool,
377 pub bg: Option<Color>,
380 pub bg_radius: f32,
387}
388
389impl<'a> Span<'a> {
390 pub fn new(text: &'a str) -> Self {
391 Self {
392 text,
393 color: None,
394 bold: false,
395 italic: false,
396 underline: false,
397 underline_color: None,
398 underline_style: UnderlineStyle::Solid,
399 strikethrough: false,
400 bg: None,
401 bg_radius: 0.0,
402 }
403 }
404
405 pub fn color(mut self, c: Color) -> Self {
406 self.color = Some(c);
407 self
408 }
409
410 pub fn bold(mut self) -> Self {
411 self.bold = true;
412 self
413 }
414
415 pub fn italic(mut self) -> Self {
416 self.italic = true;
417 self
418 }
419
420 pub fn underline(mut self) -> Self {
421 self.underline = true;
422 self
423 }
424
425 pub fn underline_color(mut self, c: Color) -> Self {
427 self.underline = true;
428 self.underline_color = Some(c);
429 self
430 }
431
432 pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
434 self.underline = true;
435 self.underline_style = s;
436 self
437 }
438
439 pub fn strikethrough(mut self) -> Self {
440 self.strikethrough = true;
441 self
442 }
443
444 pub fn bg(mut self, c: Color) -> Self {
445 self.bg = Some(c);
446 self
447 }
448
449 pub fn bg_radius(mut self, r: f32) -> Self {
452 self.bg_radius = r.max(0.0);
453 self
454 }
455
456 fn attrs_only(&self) -> SpanAttrs {
459 SpanAttrs {
460 color: self.color,
461 bold: self.bold,
462 italic: self.italic,
463 underline: self.underline,
464 underline_color: self.underline_color,
465 underline_style: self.underline_style,
466 strikethrough: self.strikethrough,
467 bg: self.bg,
468 bg_radius: self.bg_radius,
469 }
470 }
471
472 fn attrs(
473 &self,
474 family: cosmic_text::Family<'a>,
475 weights: crate::weights::Weights,
476 ) -> Attrs<'a> {
477 let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
481 if self.italic {
482 attrs = attrs.style(FontStyle::Italic);
483 }
484 if let Some(c) = self.color {
485 attrs = attrs.color(cosmic_text::Color::rgba(
486 (c.r * 255.0) as u8,
487 (c.g * 255.0) as u8,
488 (c.b * 255.0) as u8,
489 (c.a * 255.0) as u8,
490 ));
491 }
492 attrs
493 }
494}
495
496#[derive(Clone, Copy)]
500struct SpanAttrs {
501 color: Option<Color>,
502 bold: bool,
503 italic: bool,
504 underline: bool,
505 underline_color: Option<Color>,
506 underline_style: UnderlineStyle,
507 strikethrough: bool,
508 bg: Option<Color>,
509 bg_radius: f32,
510}
511
512impl SpanAttrs {
513 fn span<'a>(&self, text: &'a str) -> Span<'a> {
514 Span {
515 text,
516 color: self.color,
517 bold: self.bold,
518 italic: self.italic,
519 underline: self.underline,
520 underline_color: self.underline_color,
521 underline_style: self.underline_style,
522 strikethrough: self.strikethrough,
523 bg: self.bg,
524 bg_radius: self.bg_radius,
525 }
526 }
527}
528
529#[derive(Clone)]
532struct OwnedSpan {
533 start: usize,
534 end: usize,
535 attrs: SpanAttrs,
536}
537
538fn chunk_spans<'a>(
543 content: &'a str,
544 spans: &[OwnedSpan],
545 start: usize,
546 end: usize,
547) -> Vec<Span<'a>> {
548 let first = spans.partition_point(|s| s.end <= start);
549 spans[first..]
550 .iter()
551 .take_while(|s| s.start < end)
552 .filter_map(|s| {
553 let (a, b) = (s.start.max(start), s.end.min(end));
554 (a < b).then(|| s.attrs.span(&content[a..b]))
555 })
556 .collect()
557}
558
559struct FrameText {
560 cache_key: u64,
561 color: Color,
562}
563
564pub(crate) enum Entry {
572 Run(CachedText),
573 Long(LongLine),
574}
575
576impl Entry {
577 pub(crate) fn content(&self) -> &str {
578 match self {
579 Entry::Run(e) => &e.content,
580 Entry::Long(l) => &l.content,
581 }
582 }
583
584 fn last_used(&self) -> u64 {
585 match self {
586 Entry::Run(e) => e.last_used,
587 Entry::Long(l) => l.last_used,
588 }
589 }
590
591 fn touch(&mut self, frame_no: u64) {
592 match self {
593 Entry::Run(e) => e.last_used = frame_no,
594 Entry::Long(l) => l.last_used = frame_no,
595 }
596 }
597
598 fn bytes(&self) -> usize {
600 match self {
601 Entry::Run(e) => e.bytes,
602 Entry::Long(l) => l.bytes,
603 }
604 }
605
606 fn run(&self) -> Option<&CachedText> {
607 match self {
608 Entry::Run(e) => Some(e),
609 Entry::Long(_) => None,
610 }
611 }
612
613 fn run_mut(&mut self) -> Option<&mut CachedText> {
614 match self {
615 Entry::Run(e) => Some(e),
616 Entry::Long(_) => None,
617 }
618 }
619
620 fn long(&self) -> Option<&LongLine> {
621 match self {
622 Entry::Long(l) => Some(l),
623 Entry::Run(_) => None,
624 }
625 }
626
627 fn long_mut(&mut self) -> Option<&mut LongLine> {
628 match self {
629 Entry::Long(l) => Some(l),
630 Entry::Run(_) => None,
631 }
632 }
633}
634
635#[derive(Clone)]
639struct Chunk {
640 start: usize,
641 end: usize,
642 key: u64,
643 width: Option<f32>,
644 rows: Vec<RowStart>,
647}
648
649#[derive(Clone, Copy, Debug)]
653struct RowStart {
654 byte: u32,
655 x: f32,
656}
657
658#[derive(Clone)]
671pub(crate) struct LongLine {
672 content: String,
673 spans: Vec<OwnedSpan>,
677 style: TextStyle,
680 wrap: TextWrap,
686 wrap_w: Option<f32>,
689 starts: Vec<(u32, f32)>,
694 chunks: Vec<Chunk>,
695 prefix: Vec<f32>,
698 line_h: f32,
700 avg: f32,
703 last_used: u64,
704 claimed: u64,
707 laid_rows: u32,
714 asked_rows: u32,
715 bytes: usize,
716}
717
718impl LongLine {
719 fn chunk_at(&self, x: f32) -> usize {
721 match self.prefix[1..].partition_point(|&p| p <= x) {
722 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
723 i => i,
724 }
725 }
726
727 fn chunk_of_byte(&self, byte: usize) -> usize {
729 match self.chunks.partition_point(|c| c.end <= byte) {
730 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
731 i => i,
732 }
733 }
734
735 fn width(&self) -> f32 {
736 self.prefix.last().copied().unwrap_or(0.0)
737 }
738
739 fn rows(&self) -> u32 {
741 match self.starts.last() {
742 Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
743 _ => 1,
744 }
745 }
746
747 fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
750 let n = self.chunks.len();
751 if n == 0 || self.starts.len() != n + 1 {
752 return None;
753 }
754 let first = self.starts[1..].partition_point(|s| s.0 < ra);
755 let last = self.starts[..n].partition_point(|s| s.0 <= rb);
756 (first < last).then_some((first, last - 1))
757 }
758
759 fn reprefix(&mut self) {
761 let mut at = 0.0f32;
762 self.prefix.clear();
763 self.prefix.push(0.0);
764 for c in &self.chunks {
765 at += c
766 .width
767 .unwrap_or_else(|| (c.end - c.start) as f32 * self.avg);
768 self.prefix.push(at);
769 }
770 }
771}
772
773fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
787 use unicode_segmentation::UnicodeSegmentation;
788 if content.len() < LONG_LINE_BYTES {
789 return vec![(0, content.len())];
790 }
791 let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
792 let mut start = 0usize;
793 while start < content.len() {
794 let mut window_end = (start + CHUNK_BYTES).min(content.len());
798 while !content.is_char_boundary(window_end) {
799 window_end -= 1;
800 }
801 let end = if window_end == content.len() {
802 window_end
803 } else {
804 let window = &content[start..window_end];
805 let half = CHUNK_BYTES / 2;
806 let mut from = half.min(window.len());
810 while !window.is_char_boundary(from) {
811 from += 1;
812 }
813 let (mut tab, mut space) = (None, None);
814 for (i, c) in window[from..].char_indices() {
815 if c.is_whitespace() {
816 let end = from + i + c.len_utf8();
817 space = Some(end);
818 if c == '\t' {
819 tab = Some(end);
820 }
821 }
822 }
823 match tab.or(space) {
824 Some(i) => start + i,
825 None => {
826 let mut cut = 0usize;
828 for (i, _) in window.grapheme_indices(true) {
829 if i == 0 {
830 continue;
831 }
832 cut = i;
833 }
834 if cut == 0 { window_end } else { start + cut }
835 }
836 }
837 };
838 out.push((start, end));
839 start = end;
840 }
841 out
842}
843
844pub(crate) const PLACE_ANCESTORS: usize = 4;
849
850pub(crate) struct Ancestry {
854 pub(crate) keys: [Key; PLACE_ANCESTORS],
855 pub(crate) depth: usize,
856 pub(crate) none_at: Option<usize>,
857}
858
859pub(crate) struct TextPlace {
864 pub(crate) key: Key,
865 ancestors: [Key; PLACE_ANCESTORS],
867 depth: u8,
868 none_at: u8,
874 cache_key: u64,
875 origin: Vec2,
877 scope: Option<Key>,
881 drawn: bool,
889}
890
891impl TextPlace {
892 fn answers_to(&self, key: Key) -> bool {
893 if self.key == key {
894 return true;
895 }
896 self.ancestors[..self.depth as usize]
900 .iter()
901 .position(|k| *k == key)
902 .is_some_and(|d| d <= self.none_at as usize)
903 }
904}
905
906pub(crate) struct ScopeRun<'a> {
912 pub place: &'a TextPlace,
913 pub text: &'a Entry,
915 pub base: usize,
916}
917
918impl ScopeRun<'_> {
919 pub(crate) fn span(&self) -> (usize, usize) {
922 (self.base, self.base + self.text.content().len())
923 }
924}
925
926#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
940pub struct TextHit {
941 pub byte: usize,
942 pub line: u32,
943}
944
945impl TextHit {
946 pub fn to_value(self) -> Value {
948 Value::map([
949 ("byte", Value::Int(self.byte as i64)),
950 ("line", Value::Int(self.line as i64)),
951 ])
952 }
953}
954
955#[derive(Clone, Copy, Debug, Default, PartialEq)]
960pub struct TextMetrics {
961 pub width: f32,
962 pub height: f32,
963 pub lines: u32,
965}
966
967impl TextMetrics {
968 pub fn to_value(self) -> Value {
970 Value::map([
971 ("width", Value::float(self.width)),
972 ("height", Value::float(self.height)),
973 ("lines", Value::Int(self.lines as i64)),
974 ])
975 }
976}
977
978pub struct TextSystem {
986 raster: Raster,
987 joins: Vec<JoinBg>,
990 entries: FxHashMap<u64, Entry>,
994 bytes: usize,
997 budget: usize,
998 frame: Kept<FrameText>,
1003 places: Kept<TextPlace>,
1007 scale: f32,
1008 frame_no: u64,
1009 owed: bool,
1013}
1014
1015pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1018 let mut out = cosmic_text::FontFeatures::new();
1019 for (tag, value) in f.iter() {
1020 out.set(cosmic_text::FeatureTag::new(tag), value);
1021 }
1022 out
1023}
1024
1025#[cfg(target_os = "macos")]
1036const DEFAULT_FAMILIES: [&[&str]; 3] = [
1037 &["Helvetica Neue", "Helvetica"],
1038 &["Times New Roman", "Times"],
1039 &["SF Mono", "Menlo", "Monaco"],
1040];
1041#[cfg(target_os = "windows")]
1042const DEFAULT_FAMILIES: [&[&str]; 3] = [
1043 &["Segoe UI", "Arial"],
1044 &["Times New Roman"],
1045 &["Cascadia Mono", "Consolas", "Courier New"],
1046];
1047#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1048const DEFAULT_FAMILIES: [&[&str]; 3] = [
1049 &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1050 &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1051 &[
1052 "DejaVu Sans Mono",
1053 "Noto Sans Mono",
1054 "Liberation Mono",
1055 "Ubuntu Mono",
1056 ],
1057];
1058
1059fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1062 list.iter().copied().find(|name| {
1063 db.faces()
1064 .any(|face| face.families.iter().any(|(f, _)| f == name))
1065 })
1066}
1067
1068pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1083 let system = system_fonts();
1084 let fonts = FontSystem::new_with_locale_and_db(system.locale.clone(), system.db.clone());
1085 (fonts, system)
1086}
1087
1088pub(crate) struct SystemFonts {
1092 locale: String,
1093 db: cosmic_text::fontdb::Database,
1094 pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1096}
1097
1098static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1100
1101pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1105 let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1106 held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1107 .clone()
1108}
1109
1110pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1113 let fresh = std::sync::Arc::new(scan_system_fonts());
1114 *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1115 fresh
1116}
1117
1118fn scan_system_fonts() -> SystemFonts {
1119 let (locale, db) = FontSystem::new().into_locale_and_db();
1120 SystemFonts::from_db(locale, db)
1121}
1122
1123impl SystemFonts {
1124 pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1127 let all: Vec<_> = db.faces().map(|face| face.id).collect();
1128 keep_measurable(&mut db, all);
1129 pin_default_families(&mut db);
1130 let faces = db.faces().filter_map(face_file).collect();
1131 Self { locale, db, faces }
1132 }
1133
1134 #[cfg(test)]
1136 pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1137 &self.db
1138 }
1139
1140 #[cfg(test)]
1141 pub(crate) fn locale(&self) -> &str {
1142 &self.locale
1143 }
1144}
1145
1146pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1149 use cosmic_text::fontdb::Source;
1150 match &face.source {
1151 Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1152 Source::Binary(_) => None,
1153 }
1154}
1155
1156pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1160 let [sans, serif, mono] = DEFAULT_FAMILIES;
1161 if let Some(name) = first_installed(db, sans) {
1162 db.set_sans_serif_family(name);
1163 }
1164 if let Some(name) = first_installed(db, serif) {
1165 db.set_serif_family(name);
1166 }
1167 if let Some(name) = first_installed(db, mono) {
1168 db.set_monospace_family(name);
1169 }
1170}
1171
1172#[cfg(test)]
1176fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1177 let system = SystemFonts::from_db(locale, db);
1178 FontSystem::new_with_locale_and_db(system.locale, system.db)
1179}
1180
1181pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1192 use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1193 let Ok(font) = FontRef::from_index(data, index) else {
1194 return false;
1195 };
1196 font.head()
1197 .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1198 && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1199 && font.hmtx().is_ok()
1200}
1201
1202const FILES_PER_CHECKER: usize = 64;
1205
1206pub(crate) fn keep_measurable(
1218 db: &mut cosmic_text::fontdb::Database,
1219 ids: Vec<cosmic_text::fontdb::ID>,
1220) -> Vec<cosmic_text::fontdb::ID> {
1221 use cosmic_text::fontdb::{ID, Source};
1222 let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1224 let mut groups = Vec::new();
1225 for &id in &ids {
1226 match db.face(id).map(|face| &face.source) {
1227 Some(Source::File(path) | Source::SharedFile(path, _)) => {
1228 by_file.entry(path).or_default().push(id);
1229 }
1230 Some(Source::Binary(_)) => groups.push(vec![id]),
1231 None => {}
1232 }
1233 }
1234 groups.extend(by_file.into_values());
1235 let db_ref = &*db;
1236 let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1237 let mut out = Vec::new();
1238 for group in groups {
1239 let read = db_ref.with_face_data(group[0], |data, _| {
1240 group
1241 .iter()
1242 .filter(|&&id| {
1243 db_ref
1244 .face(id)
1245 .is_none_or(|face| !measurable(data, face.index))
1246 })
1247 .copied()
1248 .collect::<Vec<_>>()
1249 });
1250 out.extend(read.unwrap_or_else(|| group.clone()));
1251 }
1252 out
1253 };
1254 let checkers = std::thread::available_parallelism()
1255 .map_or(1, |n| n.get())
1256 .min(groups.len().div_ceil(FILES_PER_CHECKER));
1257 let unmeasurable = if checkers <= 1 {
1258 unmeasurable_in(&groups)
1259 } else {
1260 let per = groups.len().div_ceil(checkers);
1261 std::thread::scope(|scope| {
1262 let parts: Vec<_> = groups
1263 .chunks(per)
1264 .map(|part| {
1265 let spawned = std::thread::Builder::new()
1266 .name("kui-font-check".into())
1267 .spawn_scoped(scope, move || unmeasurable_in(part));
1268 (part, spawned.ok())
1269 })
1270 .collect();
1271 let mut out = Vec::new();
1272 for (part, checker) in parts {
1273 out.extend(match checker {
1274 Some(checker) => checker
1275 .join()
1276 .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1277 None => unmeasurable_in(part),
1279 });
1280 }
1281 out
1282 })
1283 };
1284 for &id in &unmeasurable {
1285 db.remove_face(id);
1286 }
1287 ids.into_iter()
1288 .filter(|id| !unmeasurable.contains(id))
1289 .collect()
1290}
1291
1292pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1296 let db = fs.db();
1297 [
1298 db.family_name(&cosmic_text::Family::SansSerif),
1299 db.family_name(&cosmic_text::Family::Serif),
1300 db.family_name(&cosmic_text::Family::Monospace),
1301 ]
1302}
1303
1304impl TextSystem {
1305 pub fn new() -> Self {
1306 Self {
1307 raster: Raster::new(),
1308 joins: Vec::new(),
1309 entries: FxHashMap::default(),
1310 bytes: 0,
1311 budget: DEFAULT_TEXT_CACHE_BYTES,
1312 frame: Kept::default(),
1313 places: Kept::default(),
1314 scale: 1.0,
1315 frame_no: 0,
1316 owed: false,
1317 }
1318 }
1319
1320 pub(crate) fn take_owed(&mut self) -> bool {
1323 std::mem::take(&mut self.owed)
1324 }
1325
1326 pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1329 std::mem::take(&mut self.joins)
1330 }
1331
1332 pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1334 &mut self.raster
1335 }
1336
1337 pub(crate) fn subpixel(&self) -> bool {
1338 self.raster.subpixel
1339 }
1340
1341 pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1344 let changed = self.raster.subpixel != on;
1345 self.raster.subpixel = on;
1346 changed
1347 }
1348
1349 pub fn budget(&self) -> usize {
1352 self.budget
1353 }
1354
1355 pub fn set_budget(&mut self, bytes: usize) {
1358 self.budget = bytes;
1359 }
1360
1361 pub fn bytes(&self) -> usize {
1363 self.bytes
1364 }
1365
1366 pub fn len(&self) -> usize {
1369 self.entries.len() - self.long_lines()
1370 }
1371
1372 pub fn long_lines(&self) -> usize {
1374 self.entries.values().filter(|e| e.long().is_some()).count()
1375 }
1376
1377 pub fn is_empty(&self) -> bool {
1378 self.len() == 0
1379 }
1380
1381 fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1388 if self.bytes <= self.budget {
1389 return;
1390 }
1391 let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1392 let drawn_last_frame = self.frame_no.saturating_sub(1);
1393 let mut order: Vec<(u64, u64)> = self
1398 .entries
1399 .iter()
1400 .filter(|(_, e)| e.last_used() < drawn_last_frame)
1401 .map(|(k, e)| (e.last_used(), *k))
1402 .collect();
1403 order.sort_unstable();
1404 for (_, key) in order {
1405 if self.bytes <= floor {
1406 break;
1407 }
1408 let freed = self.entries.remove(&key).map(|e| e.bytes());
1409 self.bytes -= freed.unwrap_or(0);
1410 }
1411 fs.shape_run_cache.trim(0);
1420 }
1421
1422 pub(crate) fn begin_frame(
1426 &mut self,
1427 fs: &mut FontSystem,
1428 scale: f32,
1429 keep_prev: bool,
1430 frame_no: u64,
1431 ) {
1432 self.joins.clear();
1433 if (scale - self.scale).abs() > f32::EPSILON {
1435 self.entries.clear();
1436 self.bytes = 0;
1437 }
1438 self.scale = scale;
1439 self.frame.begin(keep_prev);
1440 self.places.begin(true);
1443 self.frame_no = frame_no;
1445 if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1446 let mut freed = 0usize;
1447 self.entries.retain(|_, e| {
1448 let keep = e.last_used() >= cutoff;
1449 if !keep {
1450 freed += e.bytes();
1451 }
1452 keep
1453 });
1454 self.bytes -= freed;
1455 fs.shape_run_cache.trim(2);
1458 }
1459 self.evict_to_budget(fs);
1463 }
1464
1465 pub(crate) fn forget_shaped(&mut self) {
1468 self.entries.clear();
1469 self.bytes = 0;
1470 }
1471
1472 fn insert(&mut self, key: u64, entry: Entry) {
1474 self.bytes += entry.bytes();
1475 if let Some(old) = self.entries.insert(key, entry) {
1476 self.bytes -= old.bytes();
1477 }
1478 }
1479
1480 fn run(&self, key: u64) -> Option<&CachedText> {
1482 self.entries.get(&key)?.run()
1483 }
1484
1485 fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1486 self.entries.get_mut(&key)?.run_mut()
1487 }
1488
1489 fn long(&self, key: u64) -> Option<&LongLine> {
1491 self.entries.get(&key)?.long()
1492 }
1493
1494 fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1495 self.entries.get_mut(&key)?.long_mut()
1496 }
1497
1498 pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1499 let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1502 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1503 mix(&style.size.to_bits().to_le_bytes());
1504 mix(&style.line_height.to_bits().to_le_bytes());
1505 mix(&scale.to_bits().to_le_bytes());
1506 mix(&[style.wrap as u8, style.ellipsis as u8]);
1507 mix(&style.max_lines.to_le_bytes());
1508 let (tag, font) = match style.family {
1509 FontFamily::Sans => (0u8, 0u64),
1510 FontFamily::Serif => (1, 0),
1511 FontFamily::Mono => (2, 0),
1512 FontFamily::Custom(id) => (3, id.to_ffi()),
1513 };
1514 mix(&[tag]);
1515 mix(&font.to_le_bytes());
1516 for (t, v) in style.features.iter() {
1517 mix(t);
1518 mix(&v.to_le_bytes());
1519 }
1520 mix(&[
1524 style.underline as u8,
1525 style.strikethrough as u8,
1526 style.underline_style as u8,
1527 style.underline_color.is_some() as u8,
1528 ]);
1529 if let Some(c) = style.underline_color {
1530 mix(&c.to_hex().to_le_bytes());
1531 }
1532 h
1533 }
1534
1535 fn intern(
1539 &mut self,
1540 content: &str,
1541 style: &TextStyle,
1542 res: &Resources,
1543 fs: &mut FontSystem,
1544 ) -> u64 {
1545 let key = Self::style_key(content, style, self.scale);
1546 self.intern_keyed(key, content, style, res, fs)
1547 }
1548
1549 fn intern_keyed(
1551 &mut self,
1552 key: u64,
1553 content: &str,
1554 style: &TextStyle,
1555 res: &Resources,
1556 fs: &mut FontSystem,
1557 ) -> u64 {
1558 let frame_no = self.frame_no;
1559 let scale = self.scale;
1560 if !self.entries.contains_key(&key) {
1561 let mut buffer = new_buffer(fs, style, scale);
1562 buffer.set_text(
1566 content,
1567 &res.weights_of(style.family).apply(
1568 Attrs::new()
1569 .family(res.family_of(style.family))
1570 .font_features(cosmic_features(&style.features)),
1571 false,
1572 ),
1573 Shaping::Advanced,
1574 None,
1575 );
1576 let entry = CachedText::new(
1577 buffer,
1578 content.to_string(),
1579 style,
1580 vec![SpanDeco::of_style(style)],
1581 fs,
1582 frame_no,
1583 );
1584 self.insert(key, Entry::Run(entry));
1585 }
1586 self.entries
1587 .get_mut(&key)
1588 .expect("just inserted")
1589 .touch(frame_no);
1590 key
1591 }
1592
1593 pub(crate) fn content(&self, id: TextId) -> &str {
1595 let key = self.frame[id.0 as usize].cache_key;
1596 self.entries.get(&key).map_or("", |e| e.content())
1597 }
1598
1599 pub(crate) fn access_runs(
1607 &self,
1608 id: TextId,
1609 src: crate::access::RunSource,
1610 run_no: &mut usize,
1611 out: &mut Vec<crate::access::AccessRun>,
1612 ) {
1613 use crate::access::{RowGlyphs, push_row_runs};
1614 let Some(ft) = self.frame.get(id.0 as usize) else {
1615 return;
1616 };
1617 let line = match self.entries.get(&ft.cache_key) {
1618 Some(Entry::Run(e)) => {
1619 crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1620 return;
1621 }
1622 Some(Entry::Long(line)) => line,
1623 None => return,
1624 };
1625 let whole = [RowStart { byte: 0, x: 0.0 }];
1626 for (i, c) in line.chunks.iter().enumerate() {
1627 let Some(run) = c
1628 .width
1629 .and_then(|_| self.run(c.key))
1630 .and_then(|e| e.buffer.layout_runs().next())
1631 else {
1632 continue;
1633 };
1634 let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1637 (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1638 (&c.rows[..], row0, head_x)
1639 }
1640 (Some(_), _) => continue,
1641 (None, _) => (&whole[..], 0, line.prefix[i]),
1642 };
1643 for (r, rs) in rows.iter().enumerate() {
1644 let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1645 let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1646 let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1647 if a == b {
1648 continue;
1649 }
1650 let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1651 push_row_runs(
1652 &RowGlyphs {
1653 glyphs: &run.glyphs[a..b],
1654 text: run.text,
1655 line: 0,
1656 top: (row0 as usize + r) as f32 * line.line_h,
1657 height: line.line_h,
1658 rtl: run.rtl,
1659 dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1660 base: c.start,
1661 newline: last && src.newline_after_last,
1662 },
1663 &src,
1664 run_no,
1665 out,
1666 );
1667 }
1668 }
1669 }
1670
1671 pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1677 match self.frame.prev().get(id.0 as usize) {
1678 Some(t) => (t.cache_key, t.color),
1679 None => (0, Color::TRANSPARENT),
1680 }
1681 }
1682
1683 pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1689 let frame_no = self.frame_no;
1690 let e = self.entries.get_mut(&cache_key)?;
1691 e.touch(frame_no);
1692 let cache_key = if e.long().is_some() {
1693 self.claim_long(cache_key)
1694 } else {
1695 cache_key
1696 };
1697 self.frame.push(FrameText { cache_key, color });
1698 Some(TextId((self.frame.len() - 1) as u32))
1699 }
1700
1701 pub fn add(
1703 &mut self,
1704 content: &str,
1705 style: &TextStyle,
1706 res: &Resources,
1707 fs: &mut FontSystem,
1708 ) -> TextId {
1709 let key = self.intern_any(content, style, res, fs);
1712 let key = if could_be_long(content.len(), style) {
1713 self.claim_long(key)
1714 } else {
1715 key
1716 };
1717 self.frame.push(FrameText {
1718 cache_key: key,
1719 color: style.color_or_default(),
1720 });
1721 TextId((self.frame.len() - 1) as u32)
1722 }
1723
1724 #[inline(never)]
1734 fn claim_long(&mut self, key: u64) -> u64 {
1735 let frame_no = self.frame_no;
1736 let mut at = key;
1737 let mut n = 0u64;
1738 loop {
1739 match self.entries.get_mut(&at) {
1740 Some(Entry::Long(l)) if l.claimed != frame_no => {
1741 l.claimed = frame_no;
1742 l.last_used = frame_no;
1743 return at;
1744 }
1745 Some(Entry::Run(_)) if n == 0 => return key,
1747 Some(_) => {}
1748 None => {
1749 let Some(line) = self.long(key) else {
1750 return key;
1751 };
1752 let mut copy = line.clone();
1753 copy.claimed = frame_no;
1754 copy.last_used = frame_no;
1755 self.insert(at, Entry::Long(copy));
1756 return at;
1757 }
1758 }
1759 n += 1;
1760 at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
1761 }
1762 }
1763
1764 fn intern_any(
1770 &mut self,
1771 content: &str,
1772 style: &TextStyle,
1773 res: &Resources,
1774 fs: &mut FontSystem,
1775 ) -> u64 {
1776 if !could_be_long(content.len(), style) {
1777 return self.intern(content, style, res, fs);
1778 }
1779 let key = Self::style_key(content, style, self.scale);
1780 let long_key = key ^ LONG_SALT;
1781 let frame_no = self.frame_no;
1782 if let Some(e) = self.entries.get_mut(&long_key) {
1783 e.touch(frame_no);
1784 return long_key;
1785 }
1786 if let Some(e) = self.entries.get_mut(&key) {
1787 e.touch(frame_no);
1788 return key;
1789 }
1790 if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
1791 self.intern_keyed(key, content, style, res, fs)
1792 } else {
1793 self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
1794 }
1795 }
1796
1797 fn intern_rich_any(
1802 &mut self,
1803 spans: &[Span<'_>],
1804 base: &TextStyle,
1805 res: &Resources,
1806 fs: &mut FontSystem,
1807 ) -> u64 {
1808 let len: usize = spans.iter().map(|s| s.text.len()).sum();
1809 if !could_be_long(len, base) {
1810 return self.intern_rich(spans, base, res, fs);
1811 }
1812 let key = Self::rich_key(spans, base, self.scale);
1813 let long_key = key ^ LONG_SALT;
1814 let frame_no = self.frame_no;
1815 if let Some(e) = self.entries.get_mut(&long_key) {
1816 e.touch(frame_no);
1817 return long_key;
1818 }
1819 if let Some(e) = self.entries.get_mut(&key) {
1820 e.touch(frame_no);
1821 return key;
1822 }
1823 if spans.iter().any(|s| has_line_break(s.text))
1824 || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
1825 {
1826 return self.intern_rich_keyed(key, spans, base, res, fs);
1827 }
1828 let mut content = String::with_capacity(len);
1829 let mut owned = Vec::with_capacity(spans.len());
1830 for s in spans {
1831 let start = content.len();
1832 content.push_str(s.text);
1833 owned.push(OwnedSpan {
1834 start,
1835 end: content.len(),
1836 attrs: s.attrs_only(),
1837 });
1838 }
1839 self.build_long(long_key, content, owned, base, res, fs)
1840 }
1841
1842 fn build_long(
1847 &mut self,
1848 key: u64,
1849 content: String,
1850 spans: Vec<OwnedSpan>,
1851 style: &TextStyle,
1852 res: &Resources,
1853 fs: &mut FontSystem,
1854 ) -> u64 {
1855 let frame_no = self.frame_no;
1856 let wrap = style.wrap;
1857 let style = &TextStyle {
1858 wrap: TextWrap::None,
1859 ..*style
1860 };
1861 let scale = self.scale;
1862 let chunks: Vec<Chunk> = chunk_ranges(&content)
1863 .into_iter()
1864 .map(|(start, end)| Chunk {
1865 start,
1866 end,
1867 key: if spans.is_empty() {
1868 Self::style_key(&content[start..end], style, scale)
1869 } else {
1870 Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
1871 },
1872 width: None,
1873 rows: Vec::new(),
1874 })
1875 .collect();
1876 let (w0, line_h) = match chunks.first() {
1879 Some(c) => {
1880 let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
1881 let e = self.run(k).expect("just interned");
1882 (e.intrinsic.w, e.buffer.metrics().line_height)
1883 }
1884 None => (0.0, style.line_height * self.scale),
1885 };
1886 let avg = chunks
1887 .first()
1888 .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
1889 let mut line = LongLine {
1890 content,
1891 spans,
1892 style: *style,
1893 wrap,
1894 wrap_w: None,
1895 starts: Vec::new(),
1896 chunks,
1897 prefix: Vec::new(),
1898 line_h,
1899 avg,
1900 last_used: frame_no,
1901 claimed: u64::MAX,
1902 laid_rows: 1,
1903 asked_rows: u32::MAX,
1904 bytes: 0,
1905 };
1906 if let Some(c) = line.chunks.first_mut() {
1907 c.width = Some(w0);
1908 }
1909 line.reprefix();
1910 line.bytes = ENTRY_BASE_BYTES
1911 + line.content.len()
1912 + line.chunks.len() * 48
1913 + line.spans.len() * std::mem::size_of::<OwnedSpan>();
1914 self.insert(key, Entry::Long(line));
1918 key
1919 }
1920
1921 #[allow(clippy::too_many_arguments)]
1925 fn shape_chunk(
1926 &mut self,
1927 content: &str,
1928 spans: &[OwnedSpan],
1929 start: usize,
1930 end: usize,
1931 style: &TextStyle,
1932 res: &Resources,
1933 fs: &mut FontSystem,
1934 ) -> u64 {
1935 if spans.is_empty() {
1936 self.intern(&content[start..end], style, res, fs)
1937 } else {
1938 let spans = chunk_spans(content, spans, start, end);
1939 self.intern_rich(&spans, style, res, fs)
1940 }
1941 }
1942
1943 fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
1947 let (chunk_key, known) = {
1948 let c = &self.long(key).expect("a long line").chunks[i];
1949 (c.key, c.width)
1950 };
1951 let frame_no = self.frame_no;
1952 if known.is_some()
1953 && let Some(e) = self.run_mut(chunk_key)
1954 {
1955 e.last_used = frame_no;
1956 return false;
1957 }
1958 let (text, spans, style) = {
1961 let line = self.long(key).expect("a long line");
1962 let c = &line.chunks[i];
1963 let first = line.spans.partition_point(|s| s.end <= c.start);
1964 let spans: Vec<OwnedSpan> = line.spans[first..]
1965 .iter()
1966 .take_while(|s| s.start < c.end)
1967 .map(|s| OwnedSpan {
1968 start: s.start.max(c.start) - c.start,
1969 end: s.end.min(c.end) - c.start,
1970 attrs: s.attrs,
1971 })
1972 .collect();
1973 (line.content[c.start..c.end].to_string(), spans, line.style)
1974 };
1975 let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
1976 let w = self.run(k).expect("just interned").intrinsic.w;
1977 let line = self.long_mut(key).expect("just read");
1978 line.chunks[i].key = k;
1979 if line.chunks[i].width != Some(w) {
1980 line.chunks[i].width = Some(w);
1981 line.reprefix();
1982 }
1983 true
1984 }
1985
1986 fn relayout_long(&mut self, key: u64, w: Option<f32>) {
1994 let Some(w) = w else {
1995 let line = self.long_mut(key).expect("a long line");
1996 line.wrap_w = None;
1997 line.starts.clear();
1998 for c in &mut line.chunks {
1999 c.rows.clear();
2000 }
2001 return;
2002 };
2003 let w = w.max(1.0);
2004 let line = self.long(key).expect("a long line");
2007 let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2008 starts.push((0, 0.0));
2009 let (mut row, mut x) = (0u32, 0.0f32);
2010 let rows: Vec<Vec<RowStart>> = line
2011 .chunks
2012 .iter()
2013 .map(|c| {
2014 let shaped = c.width.and_then(|_| self.run(c.key));
2015 let rows = match shaped {
2016 Some(e) => {
2017 let text = &line.content[c.start..c.end];
2018 let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2019 row += rows.len() as u32 - 1;
2020 x = end;
2021 rows
2022 }
2023 None => {
2024 let est = x + c
2025 .width
2026 .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2027 let added = (est / w).floor();
2028 row += added as u32;
2029 x = est - added * w;
2030 Vec::new()
2031 }
2032 };
2033 starts.push((row, x));
2034 rows
2035 })
2036 .collect();
2037 let line = self.long_mut(key).expect("a long line");
2038 line.wrap_w = Some(w);
2039 line.starts = starts;
2040 for (c, rows) in line.chunks.iter_mut().zip(rows) {
2041 c.rows = rows;
2042 }
2043 }
2044
2045 fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2049 let (wrap, width, line_h, cur) = {
2050 let l = self.long(key).expect("a long line");
2051 (l.wrap, l.width(), l.line_h, l.wrap_w)
2052 };
2053 let target = match max_w {
2054 Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2055 _ => None,
2056 };
2057 let differs = match (cur, target) {
2058 (None, None) => false,
2059 (Some(a), Some(b)) => (a - b).abs() > 0.5,
2060 _ => true,
2061 };
2062 if differs {
2063 self.relayout_long(key, target);
2064 }
2065 match target {
2066 Some(w) => {
2067 let rows = self.long(key).expect("a long line").rows();
2068 (Size::new(w, rows as f32 * line_h), rows)
2069 }
2070 None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2071 }
2072 }
2073
2074 pub fn measure(
2080 &mut self,
2081 content: &str,
2082 style: &TextStyle,
2083 res: &Resources,
2084 fs: &mut FontSystem,
2085 max_w: Option<f32>,
2086 ) -> TextMetrics {
2087 let key = self.intern_any(content, style, res, fs);
2088 self.measure_any(key, fs, max_w)
2089 }
2090
2091 fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2093 if self.long(key).is_some() {
2094 let scale = self.scale;
2095 let (size, lines) = match max_w {
2098 Some(m) => {
2099 let held = self.long(key).expect("checked").wrap_w;
2102 let measured = self.long_size(key, Some(m * scale));
2103 if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2104 self.relayout_long(key, held);
2105 }
2106 measured
2107 }
2108 None => {
2109 let line = self.long(key).expect("checked");
2110 (Size::new(line.width(), line.line_h), 1)
2111 }
2112 };
2113 return TextMetrics {
2114 width: size.w / scale,
2115 height: size.h / scale,
2116 lines,
2117 };
2118 }
2119 self.measure_key(key, fs, max_w)
2120 }
2121
2122 pub fn measure_rich(
2124 &mut self,
2125 spans: &[Span<'_>],
2126 base: &TextStyle,
2127 res: &Resources,
2128 fs: &mut FontSystem,
2129 max_w: Option<f32>,
2130 ) -> TextMetrics {
2131 let key = self.intern_rich_any(spans, base, res, fs);
2132 self.measure_any(key, fs, max_w)
2133 }
2134
2135 fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2136 let scale = self.scale;
2137 let entry = self.run_mut(key).expect("just interned");
2138 let target = wrap_target(entry, max_w, scale);
2139 wrap_entry(entry, fs, target);
2140 let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2141 if entry.clamp_w
2142 && let Some(t) = target
2143 {
2144 m.w = m.w.min(t);
2145 }
2146 TextMetrics {
2147 width: m.w / scale,
2148 height: m.h / scale,
2149 lines,
2150 }
2151 }
2152
2153 pub fn add_rich(
2158 &mut self,
2159 spans: &[Span<'_>],
2160 base: &TextStyle,
2161 res: &Resources,
2162 fs: &mut FontSystem,
2163 ) -> TextId {
2164 let key = self.intern_rich_any(spans, base, res, fs);
2165 let len = spans.iter().map(|s| s.text.len()).sum();
2166 let key = if could_be_long(len, base) {
2167 self.claim_long(key)
2168 } else {
2169 key
2170 };
2171 self.frame.push(FrameText {
2172 cache_key: key,
2173 color: base.color_or_default(),
2174 });
2175 TextId((self.frame.len() - 1) as u32)
2176 }
2177
2178 fn intern_rich(
2179 &mut self,
2180 spans: &[Span<'_>],
2181 base: &TextStyle,
2182 res: &Resources,
2183 fs: &mut FontSystem,
2184 ) -> u64 {
2185 let key = Self::rich_key(spans, base, self.scale);
2186 self.intern_rich_keyed(key, spans, base, res, fs)
2187 }
2188
2189 fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2193 let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2194 for s in spans {
2195 key = crate::key::mix_content(key, s.text.as_bytes());
2196 let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2197 mix(&[
2198 s.bold as u8,
2199 s.italic as u8,
2200 s.color.is_some() as u8,
2201 s.underline as u8,
2202 s.strikethrough as u8,
2203 s.bg.is_some() as u8,
2204 s.underline_style as u8,
2205 s.underline_color.is_some() as u8,
2206 ]);
2207 mix(&s.bg_radius.to_bits().to_le_bytes());
2208 for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2209 mix(&c.r.to_bits().to_le_bytes());
2210 mix(&c.g.to_bits().to_le_bytes());
2211 mix(&c.b.to_bits().to_le_bytes());
2212 mix(&c.a.to_bits().to_le_bytes());
2213 }
2214 }
2215 key
2216 }
2217
2218 fn intern_rich_keyed(
2220 &mut self,
2221 key: u64,
2222 spans: &[Span<'_>],
2223 base: &TextStyle,
2224 res: &Resources,
2225 fs: &mut FontSystem,
2226 ) -> u64 {
2227 let frame_no = self.frame_no;
2228 let scale = self.scale;
2229 if !self.entries.contains_key(&key) {
2230 let mut buffer = new_buffer(fs, base, scale);
2231 let family = res.family_of(base.family);
2232 let weights = res.weights_of(base.family);
2233 let features = cosmic_features(&base.features);
2234 buffer.set_rich_text(
2237 spans.iter().enumerate().map(|(i, s)| {
2238 (
2239 s.text,
2240 s.attrs(family, weights)
2241 .font_features(features.clone())
2242 .metadata(i),
2243 )
2244 }),
2245 &weights.apply(
2246 Attrs::new().family(family).font_features(features.clone()),
2247 false,
2248 ),
2249 Shaping::Advanced,
2250 None,
2251 );
2252 let content = spans.iter().map(|s| s.text).collect::<String>();
2253 let decos = spans
2254 .iter()
2255 .map(|s| SpanDeco {
2256 underline: s.underline || base.underline,
2257 underline_color: s.underline_color.or(base.underline_color),
2259 underline_style: if s.underline {
2260 s.underline_style
2261 } else {
2262 base.underline_style
2263 },
2264 strikethrough: s.strikethrough || base.strikethrough,
2265 bg: s.bg,
2266 bg_radius: s.bg_radius,
2267 })
2268 .collect();
2269 let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2270 self.insert(key, Entry::Run(entry));
2271 }
2272 self.entries
2273 .get_mut(&key)
2274 .expect("just inserted")
2275 .touch(frame_no);
2276 key
2277 }
2278
2279 fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2282 let key = self.frame[id.0 as usize].cache_key;
2283 self.run_mut(key).expect("frame text missing from cache")
2284 }
2285
2286 fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2291 let (scale, frame_no) = (self.scale, self.frame_no);
2292 let entry = self.entry_mut(id);
2293 let target = wrap_target(entry, Some(max_w_logical), scale);
2294 if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2295 self.own_wrap(id, target, fs);
2296 return;
2297 }
2298 entry.claimed = frame_no;
2299 entry.claimed_wrap = target;
2300 wrap_entry(entry, fs, target);
2301 }
2302
2303 #[inline(never)]
2311 fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2312 let frame_no = self.frame_no;
2313 let key = self.frame[id.0 as usize].cache_key;
2314 let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2316 let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2317 if self.run(copy).is_none() {
2318 let fork = self.run(key).expect("frame text").fork(frame_no);
2319 self.insert(copy, Entry::Run(fork));
2320 }
2321 self.frame[id.0 as usize].cache_key = copy;
2322 let entry = self.run_mut(copy).expect("just made");
2323 entry.last_used = frame_no;
2324 entry.claimed = frame_no;
2325 entry.claimed_wrap = target;
2326 wrap_entry(entry, fs, target);
2327 }
2328
2329 #[allow(clippy::too_many_arguments)]
2334 pub(crate) fn emit(
2335 &mut self,
2336 id: TextId,
2337 origin: Vec2,
2338 node: Size,
2339 clip: Clip,
2340 clip_id: ClipId,
2341 clips: &mut Vec<Clip>,
2342 res: &Resources,
2343 fs: &mut FontSystem,
2344 atlas: &mut GlyphAtlas,
2345 out: &mut Vec<Quad>,
2346 sel: Option<((usize, usize), Color)>,
2347 ) {
2348 let from = self.joins.len();
2349 self.emit_text(
2350 id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2351 );
2352 if self.joins.len() == from {
2353 return;
2354 }
2355 let key = self.frame[id.0 as usize].cache_key;
2358 let clamps = match self.entries.get(&key) {
2359 Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2360 Some(Entry::Run(e)) => e.clamp_w,
2361 None => false,
2362 };
2363 let own = clamps.then(|| {
2364 let ox = crate::geom::snap_px(origin.x * self.scale);
2365 (ox, ox + (node.w * self.scale).ceil())
2366 });
2367 for j in &mut self.joins[from..] {
2368 j.outer = clip_id;
2369 j.own = own;
2370 }
2371 }
2372
2373 #[allow(clippy::too_many_arguments)]
2379 #[inline(never)]
2380 fn emit_long(
2381 &mut self,
2382 key: u64,
2383 node: Size,
2384 ox: f32,
2385 oy: f32,
2386 nudge: Vec2,
2387 color: Color,
2388 clip: Clip,
2389 clip_id: ClipId,
2390 clips: &mut Vec<Clip>,
2391 res: &Resources,
2392 fs: &mut FontSystem,
2393 atlas: &mut GlyphAtlas,
2394 out: &mut Vec<Quad>,
2395 sel: Option<((usize, usize), Color)>,
2396 ) {
2397 let scale = self.scale;
2398 let line = self.long(key).expect("a long line");
2399 let (clip, clip_id) = if line.wrap == TextWrap::None {
2405 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2406 let clip = clip.intersect(own, crate::display::SQUARE);
2407 if clip.rect.w <= 0.0 || clip.rect.h <= 0.0 {
2408 return;
2409 }
2410 (clip, crate::display::intern_clip(clips, clip))
2411 } else {
2412 (clip, clip_id)
2413 };
2414 if let Some(((from, to), tint)) = sel {
2415 let rects = self.long_highlight(line, from, to);
2416 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2417 }
2418 if let Some(w) = line.wrap_w {
2419 self.emit_long_rows(
2420 key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2421 );
2422 return;
2423 }
2424 let (first, last) = {
2425 if line.chunks.is_empty() {
2426 return;
2427 }
2428 let a = line.chunk_at(clip.rect.x - ox).saturating_sub(1);
2429 let b = (line.chunk_at(clip.rect.x + clip.rect.w - ox) + 1).min(line.chunks.len() - 1);
2430 (a, b)
2431 };
2432 for i in first..=last {
2433 self.ensure_chunk(key, i, res, fs);
2434 }
2435 let frame_no = self.frame_no;
2436 self.long_mut(key).expect("checked").last_used = frame_no;
2437 for i in first..=last {
2438 let (chunk_key, x) = {
2439 let line = self.long(key).expect("checked");
2440 (line.chunks[i].key, line.prefix[i])
2441 };
2442 let raster = &mut self.raster;
2443 let entry = self
2444 .entries
2445 .get_mut(&chunk_key)
2446 .and_then(Entry::run_mut)
2447 .expect("just ensured");
2448 emit_entry(
2449 entry,
2450 ox + x,
2451 oy,
2452 nudge,
2453 color,
2454 clip,
2455 clip_id,
2456 raster,
2457 fs,
2458 atlas,
2459 out,
2460 &mut self.joins,
2461 );
2462 }
2463 }
2464
2465 #[allow(clippy::too_many_arguments)]
2467 fn emit_text(
2468 &mut self,
2469 id: TextId,
2470 origin: Vec2,
2471 node: Size,
2472 clip: Clip,
2473 clip_id: ClipId,
2474 clips: &mut Vec<Clip>,
2475 res: &Resources,
2476 fs: &mut FontSystem,
2477 atlas: &mut GlyphAtlas,
2478 out: &mut Vec<Quad>,
2479 sel: Option<((usize, usize), Color)>,
2484 ) {
2485 let FrameText {
2486 cache_key: key,
2487 color,
2488 } = self.frame[id.0 as usize];
2489 let scale = self.scale;
2490 let ox = crate::geom::snap_px(origin.x * scale);
2491 let oy = crate::geom::snap_px(origin.y * scale);
2492 let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2495 if self.long(key).is_some() {
2498 self.emit_long(
2499 key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2500 );
2501 return;
2502 }
2503 self.ensure_wrap(id, node.w, fs);
2504 let raster = &mut self.raster;
2505 let entry = self
2506 .entries
2507 .get_mut(&key)
2508 .and_then(Entry::run_mut)
2509 .expect("frame text missing from cache");
2510
2511 let (clip, clip_id) = if entry.clamp_w {
2514 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2515 let clip = clip.intersect(own, crate::display::SQUARE);
2516 (clip, crate::display::intern_clip(clips, clip))
2517 } else {
2518 (clip, clip_id)
2519 };
2520 if let Some(((from, to), tint)) = sel {
2521 let rects = Self::run_highlight(entry, from, to);
2522 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2523 }
2524 emit_entry(
2525 entry,
2526 ox,
2527 oy,
2528 nudge,
2529 color,
2530 clip,
2531 clip_id,
2532 raster,
2533 fs,
2534 atlas,
2535 out,
2536 &mut self.joins,
2537 );
2538 }
2539}
2540
2541fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2555 Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2556}
2557
2558fn push_highlight(
2561 rects: &[Rect],
2562 ox: f32,
2563 oy: f32,
2564 nudge: Vec2,
2565 tint: Color,
2566 clip: ClipId,
2567 out: &mut Vec<Quad>,
2568) {
2569 for r in rects {
2570 out.push(Quad {
2571 rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2572 color: tint,
2573 border_color: Color::TRANSPARENT,
2574 radius: [0.0; 4],
2575 border_w: 0.0,
2576 blur: 0.0,
2577 kind: QuadKind::Solid,
2578 uv: [0; 4],
2579 clip,
2580 });
2581 }
2582}
2583
2584impl TextSystem {
2585 #[allow(clippy::too_many_arguments)]
2589 fn emit_long_rows(
2590 &mut self,
2591 key: u64,
2592 w: f32,
2593 ox: f32,
2594 oy: f32,
2595 nudge: Vec2,
2596 color: Color,
2597 clip: Clip,
2598 clip_id: ClipId,
2599 res: &Resources,
2600 fs: &mut FontSystem,
2601 atlas: &mut GlyphAtlas,
2602 out: &mut Vec<Quad>,
2603 ) {
2604 let (first, last) = {
2605 let line = self.long(key).expect("a long line");
2606 let ra = ((clip.rect.y - oy) / line.line_h).floor().max(0.0) as u32;
2607 let rb = ((clip.rect.y + clip.rect.h - oy) / line.line_h)
2608 .floor()
2609 .max(0.0) as u32;
2610 let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
2611 return;
2612 };
2613 (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
2614 };
2615 let mut fresh = false;
2616 for i in first..=last {
2617 fresh |= self.ensure_chunk(key, i, res, fs);
2618 }
2619 if fresh {
2620 self.relayout_long(key, Some(w));
2623 let line = self.long_mut(key).expect("checked");
2624 let rows = line.rows();
2625 if rows != line.laid_rows && rows != line.asked_rows {
2626 line.asked_rows = rows;
2627 self.owed = true;
2628 }
2629 }
2630 let frame_no = self.frame_no;
2631 self.long_mut(key).expect("checked").last_used = frame_no;
2632 for i in first..=last {
2633 let chunk_key = self.long(key).expect("checked").chunks[i].key;
2634 let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
2638 self.entries.get_disjoint_mut([&key, &chunk_key])
2639 else {
2640 continue;
2641 };
2642 let (row0, head_x) = line.starts[i];
2643 let chunk = &line.chunks[i];
2644 if chunk.rows.is_empty() {
2645 continue;
2646 }
2647 let raster = &mut self.raster;
2648 emit_entry_rows(
2649 entry,
2650 ox,
2651 oy + row0 as f32 * line.line_h,
2652 nudge,
2653 head_x,
2654 &chunk.rows,
2655 line.line_h,
2656 color,
2657 clip,
2658 clip_id,
2659 raster,
2660 fs,
2661 atlas,
2662 out,
2663 &mut self.joins,
2664 );
2665 }
2666 }
2667}
2668
2669#[allow(clippy::too_many_arguments)]
2674fn emit_entry_rows(
2675 entry: &mut CachedText,
2676 ox: f32,
2677 oy: f32,
2678 nudge: Vec2,
2679 head_x: f32,
2680 rows: &[RowStart],
2681 line_h: f32,
2682 color: Color,
2683 clip: Clip,
2684 clip_id: ClipId,
2685 raster: &mut Raster,
2686 fs: &mut FontSystem,
2687 atlas: &mut GlyphAtlas,
2688 out: &mut Vec<Quad>,
2689 joins: &mut Vec<JoinBg>,
2690) {
2691 build_templates(entry, raster, fs, atlas);
2692 let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
2698 let (x0, x1) = (d.x, d.x + d.w);
2699 for r in 0..rows.len() {
2700 let ra = rows[r].x;
2701 let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
2702 let (a, b) = (x0.max(ra), x1.min(rb));
2703 if b <= a {
2704 continue;
2705 }
2706 let dx = if r == 0 { head_x } else { 0.0 } - ra;
2707 let x = ox + a + dx;
2708 let y = oy + d.y + r as f32 * line_h;
2709 if y >= clip.rect.y + clip.rect.h {
2710 break;
2711 }
2712 if y + d.h <= clip.rect.y
2713 || x >= clip.rect.x + clip.rect.w
2714 || x + (b - a) <= clip.rect.x
2715 {
2716 continue;
2717 }
2718 let quad = Quad {
2719 rect: if d.under {
2720 on_pixels(x, y, b - a, d.h, nudge)
2721 } else {
2722 Rect::new(x, y, b - a, d.h)
2723 },
2724 color: d.color.unwrap_or(color),
2725 border_color: Color::TRANSPARENT,
2726 radius: [0.0; 4],
2727 border_w: 0.0,
2728 blur: 0.0,
2729 kind: QuadKind::Solid,
2730 clip: clip_id,
2731 uv: [0; 4],
2732 };
2733 if d.under && d.radius > 0.0 {
2737 joins.push(JoinBg {
2738 quad: out.len() as u32,
2739 radius: d.radius,
2740 outer: clip_id,
2741 own: None,
2742 });
2743 }
2744 if d.under || d.style == UnderlineStyle::Solid {
2745 out.push(quad);
2746 } else {
2747 crate::deco::push_line(
2748 out,
2749 d.style,
2750 x,
2751 y,
2752 b - a,
2753 d.h,
2754 d.color.unwrap_or(color),
2755 clip_id,
2756 );
2757 }
2758 }
2759 };
2760 for d in entry.deco.iter().filter(|d| d.under) {
2761 row_pieces(d, out);
2762 }
2763 let mut r = 0usize;
2764 for g in &entry.glyphs {
2765 while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
2766 r += 1;
2767 }
2768 let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
2769 let x = ox + g.x + dx;
2770 let y = oy + g.y + r as f32 * line_h;
2771 if y >= clip.rect.y + clip.rect.h {
2778 break;
2779 }
2780 if y + g.h <= clip.rect.y || x >= clip.rect.x + clip.rect.w || x + g.w <= clip.rect.x {
2781 continue;
2782 }
2783 out.push(Quad {
2784 rect: Rect::new(x, y, g.w, g.h),
2785 color: g.color.unwrap_or(color),
2786 border_color: Color::TRANSPARENT,
2787 radius: [0.0; 4],
2788 border_w: 0.0,
2789 blur: 0.0,
2790 kind: g.kind,
2791 clip: clip_id,
2792 uv: g.uv,
2793 });
2794 }
2795 for d in entry.deco.iter().filter(|d| !d.under) {
2796 row_pieces(d, out);
2797 }
2798}
2799
2800fn break_rows(
2813 buffer: &Buffer,
2814 text: &str,
2815 wrap: TextWrap,
2816 w: f32,
2817 head_x: f32,
2818) -> (Vec<RowStart>, f32) {
2819 let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
2820 let Some(run) = buffer.layout_runs().next() else {
2821 return (rows, head_x);
2822 };
2823 let glyphs = run.glyphs;
2824 let blank = |g: &cosmic_text::LayoutGlyph| {
2825 text.get(g.start..g.end)
2826 .is_some_and(|s| s.chars().all(char::is_whitespace))
2827 };
2828 let spaces = wrap == TextWrap::BreakSpaces;
2829 let mut pieces: Vec<(usize, usize)> = Vec::new();
2830 match wrap {
2831 TextWrap::Word | TextWrap::BreakSpaces => {
2832 let mut at = 0usize;
2833 let mut piece = |from: usize, to: usize| {
2834 if !spaces {
2835 pieces.push((from, to));
2836 return;
2837 }
2838 let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
2841 if ink > 0 {
2842 pieces.push((from, from + ink));
2843 }
2844 for (i, c) in text[from + ink..to].char_indices() {
2845 let at = from + ink + i;
2846 pieces.push((at, at + c.len_utf8()));
2847 }
2848 };
2849 for (i, _) in unicode_linebreak::linebreaks(text) {
2850 if i > at {
2851 piece(at, i);
2852 at = i;
2853 }
2854 }
2855 if at < text.len() {
2856 piece(at, text.len());
2857 }
2858 }
2859 TextWrap::Glyph | TextWrap::None => {
2860 pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
2861 }
2862 }
2863 let mut row_x0 = 0.0f32;
2864 let mut avail = w - head_x;
2865 let mut gi = 0usize;
2866 for (s, e) in pieces {
2867 let g0 = gi;
2868 while gi < glyphs.len() && glyphs[gi].start < e {
2869 gi += 1;
2870 }
2871 if g0 == gi {
2872 continue;
2873 }
2874 let piece = &glyphs[g0..gi];
2875 let x0 = piece[0].x;
2876 let ink_end = piece
2877 .iter()
2878 .rev()
2879 .find(|g| spaces || !blank(g))
2880 .map_or(x0, |g| g.x + g.w);
2881 let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
2887 if ink_end - row_x0 > avail && opens {
2888 rows.push(RowStart {
2889 byte: s as u32,
2890 x: x0,
2891 });
2892 row_x0 = x0;
2893 avail = w;
2894 }
2895 if ink_end - row_x0 > avail {
2896 for g in piece {
2898 if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
2899 rows.push(RowStart {
2900 byte: g.start as u32,
2901 x: g.x,
2902 });
2903 row_x0 = g.x;
2904 avail = w;
2905 }
2906 }
2907 }
2908 }
2909 let end = if rows.len() == 1 {
2910 head_x + run.line_w
2911 } else {
2912 run.line_w - row_x0
2913 };
2914 (rows, end)
2915}
2916
2917fn build_templates(
2920 entry: &mut CachedText,
2921 raster: &mut Raster,
2922 fs: &mut FontSystem,
2923 atlas: &mut GlyphAtlas,
2924) {
2925 {
2926 let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
2928 if entry.glyphs_built_for != Some(built_for) {
2929 entry.glyphs.clear();
2930 entry.deco.clear();
2931 let lines = line_cap(entry.max_lines);
2932 let decorated = entry.span_deco.iter().any(SpanDeco::any);
2933 for run in entry.buffer.layout_runs().take(lines) {
2934 if decorated {
2935 build_decorations(&run, &entry.span_deco, fs, &mut entry.deco);
2936 }
2937 for glyph in run.glyphs.iter() {
2938 let physical = glyph.physical((0.0, 0.0), 1.0);
2939 let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
2940 continue;
2941 };
2942 entry.glyphs.push(GlyphTemplate {
2943 x: physical.x as f32 + slot.left as f32,
2944 y: run.line_y.round() + physical.y as f32 - slot.top as f32,
2945 w: slot.w as f32,
2946 h: slot.h as f32,
2947 uv: [slot.x, slot.y, slot.w, slot.h],
2948 kind: glyph_kind(&slot),
2949 color: glyph
2950 .color_opt
2951 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
2952 byte: glyph.start as u32,
2953 });
2954 }
2955 }
2956 entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
2960 }
2961 }
2962}
2963
2964#[allow(clippy::too_many_arguments)]
2968fn emit_entry(
2969 entry: &mut CachedText,
2970 ox: f32,
2971 oy: f32,
2972 nudge: Vec2,
2973 color: Color,
2974 clip: Clip,
2975 clip_id: ClipId,
2976 raster: &mut Raster,
2977 fs: &mut FontSystem,
2978 atlas: &mut GlyphAtlas,
2979 out: &mut Vec<Quad>,
2980 joins: &mut Vec<JoinBg>,
2981) {
2982 build_templates(entry, raster, fs, atlas);
2983 {
2984 let inside = |x: f32, y: f32, w: f32, h: f32| {
2985 oy + y + h >= clip.rect.y
2986 && oy + y <= clip.rect.y + clip.rect.h
2987 && ox + x < clip.rect.x + clip.rect.w
2988 && ox + x + w > clip.rect.x
2989 };
2990 let deco_quad = |d: &DecoTemplate| Quad {
2991 rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
2992 color: d.color.unwrap_or(color),
2993 border_color: Color::TRANSPARENT,
2994 radius: [0.0; 4],
2995 border_w: 0.0,
2996 blur: 0.0,
2997 kind: QuadKind::Solid,
2998 clip: clip_id,
2999 uv: [0; 4],
3000 };
3001 for d in entry
3006 .deco
3007 .iter()
3008 .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3009 {
3010 if d.radius > 0.0 {
3011 joins.push(JoinBg {
3012 quad: out.len() as u32,
3013 radius: d.radius,
3014 outer: clip_id,
3015 own: None,
3016 });
3017 }
3018 out.push(Quad {
3019 rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3020 ..deco_quad(d)
3021 });
3022 }
3023
3024 out.extend(
3028 entry
3029 .glyphs
3030 .iter()
3031 .filter(|g| {
3032 oy + g.y + g.h >= clip.rect.y
3033 && ox + g.x < clip.rect.x + clip.rect.w
3034 && ox + g.x + g.w > clip.rect.x
3035 })
3036 .take_while(|g| oy + g.y <= clip.rect.y + clip.rect.h)
3037 .map(|g| Quad {
3038 rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3039 color: g.color.unwrap_or(color),
3040 border_color: Color::TRANSPARENT,
3041 radius: [0.0; 4],
3042 border_w: 0.0,
3043 blur: 0.0,
3044 kind: g.kind,
3045 clip: clip_id,
3046 uv: g.uv,
3047 }),
3048 );
3049 for d in entry
3050 .deco
3051 .iter()
3052 .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3053 {
3054 match d.style {
3057 UnderlineStyle::Solid => out.push(deco_quad(d)),
3058 style => crate::deco::push_line(
3059 out,
3060 style,
3061 ox + d.x,
3062 oy + d.y,
3063 d.w,
3064 d.h,
3065 d.color.unwrap_or(color),
3066 clip_id,
3067 ),
3068 }
3069 }
3070 }
3071}
3072
3073fn could_be_long(len: usize, style: &TextStyle) -> bool {
3082 (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3083 && style.max_lines == 0
3084 && !style.ellipsis
3085}
3086
3087fn has_line_break(content: &str) -> bool {
3090 content.bytes().any(|b| b == b'\n' || b == b'\r')
3091}
3092
3093fn has_rtl(content: &str) -> bool {
3102 !content.is_ascii()
3103 && content.chars().any(|c| {
3104 matches!(c as u32,
3105 0x0590..=0x08FF
3106 | 0x200F | 0x202B | 0x202E | 0x2067
3107 | 0xFB1D..=0xFDFF
3108 | 0xFE70..=0xFEFF
3109 | 0x10800..=0x10FFF
3110 | 0x1E800..=0x1EFFF)
3111 })
3112}
3113
3114fn build_decorations(
3125 run: &cosmic_text::LayoutRun<'_>,
3126 spans: &[SpanDeco],
3127 fs: &mut FontSystem,
3128 out: &mut Vec<DecoTemplate>,
3129) {
3130 let mut last_bg: Option<usize> = None;
3133 let mut i = 0;
3134 while i < run.glyphs.len() {
3135 let span_no = run.glyphs[i].metadata;
3136 let mut j = i + 1;
3137 while j < run.glyphs.len() && run.glyphs[j].metadata == span_no {
3138 j += 1;
3139 }
3140 let deco = spans.get(span_no).copied().unwrap_or_default();
3141 if deco.any() {
3142 let group = &run.glyphs[i..j];
3143 let x0 = group.iter().map(|g| g.x).fold(f32::INFINITY, f32::min);
3144 let x1 = group
3145 .iter()
3146 .map(|g| g.x + g.w)
3147 .fold(f32::NEG_INFINITY, f32::max);
3148 let w = (x1 - x0).max(0.0);
3149 match (deco.bg, last_bg.map(|k| &mut out[k])) {
3150 (Some(bg), Some(prev))
3151 if prev.color == Some(bg)
3152 && prev.radius == deco.bg_radius
3153 && (prev.x + prev.w - x0).abs() < 0.01 =>
3154 {
3155 prev.w = (x1 - prev.x).max(prev.w);
3156 }
3157 (Some(bg), _) => {
3158 last_bg = Some(out.len());
3159 out.push(DecoTemplate {
3160 x: x0,
3161 y: run.line_top,
3162 w,
3163 h: run.line_height,
3164 color: Some(bg),
3165 under: true,
3166 style: UnderlineStyle::Solid,
3167 radius: deco.bg_radius,
3168 });
3169 }
3170 (None, _) => last_bg = None,
3171 }
3172 if deco.underline || deco.strikethrough {
3173 let first = &group[0];
3174 let metrics = fs
3175 .get_font(first.font_id, first.font_weight)
3176 .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3177 let (under_off, strike_off, stroke) = match metrics {
3180 Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3181 None => (
3182 -0.1 * first.font_size,
3183 0.3 * first.font_size,
3184 0.06 * first.font_size,
3185 ),
3186 };
3187 let stroke = stroke.max(1.0).round();
3188 let baseline = run.line_y.round();
3189 if deco.underline {
3190 out.push(DecoTemplate {
3191 x: x0,
3192 y: (baseline - under_off).round(),
3193 w,
3194 h: stroke,
3195 color: deco.underline_color.or_else(|| {
3196 first
3197 .color_opt
3198 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a()))
3199 }),
3200 under: false,
3201 style: deco.underline_style,
3202 radius: 0.0,
3203 });
3204 }
3205 if deco.strikethrough {
3206 out.push(DecoTemplate {
3207 x: x0,
3208 y: (baseline - strike_off).round(),
3209 w,
3210 h: stroke,
3211 color: first
3212 .color_opt
3213 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3214 under: false,
3215 style: UnderlineStyle::Solid,
3216 radius: 0.0,
3217 });
3218 }
3219 }
3220 } else {
3221 last_bg = None;
3222 }
3223 i = j;
3224 }
3225}
3226
3227impl TextSystem {
3228 pub(crate) fn place(
3233 &mut self,
3234 key: Key,
3235 ancestry: &Ancestry,
3236 id: TextId,
3237 origin: Vec2,
3238 scope: Option<Key>,
3239 drawn: bool,
3240 ) {
3241 let cache_key = self.frame[id.0 as usize].cache_key;
3242 self.places.push(TextPlace {
3243 key,
3244 ancestors: ancestry.keys,
3245 depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3246 none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3247 cache_key,
3248 origin,
3249 scope,
3250 drawn,
3251 });
3252 }
3253
3254 fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3261 let list = if prev {
3262 self.places.prev()
3263 } else {
3264 &self.places
3265 };
3266 let mut base = 0usize;
3267 let mut out = Vec::new();
3268 for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3269 let Some(entry) = self.entries.get(&place.cache_key) else {
3270 continue;
3271 };
3272 out.push((place, entry, base));
3273 base += entry.content().len();
3274 }
3275 out
3276 }
3277
3278 pub(crate) fn content_len(&self, id: TextId) -> usize {
3281 let key = self.frame[id.0 as usize].cache_key;
3282 self.entries.get(&key).map_or(0, |e| e.content().len())
3283 }
3284
3285 pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3294 let list = if prev {
3295 self.places.prev()
3296 } else {
3297 &self.places
3298 };
3299 let mut base = 0usize;
3300 let mut out = Vec::new();
3301 for place in list.iter().filter(|p| p.scope == Some(scope)) {
3302 let Some(text) = self.entries.get(&place.cache_key) else {
3303 continue;
3304 };
3305 let len = text.content().len();
3306 out.push(ScopeRun { place, text, base });
3307 base += len;
3308 }
3309 out
3310 }
3311
3312 pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3319 let runs = self.scope_runs(scope, prev);
3320 let px = point.x * self.scale;
3321 let py = point.y * self.scale;
3322 let gap = |r: &Rect| {
3323 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3324 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3325 (dy, dx)
3326 };
3327 let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3328 let ga = gap(&self.scope_box(a));
3329 let gb = gap(&self.scope_box(b));
3330 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3331 })?;
3332 let byte = self.hit_in_run(run, point)?;
3333 Some((run.place.key, byte))
3334 }
3335
3336 fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3338 self.entry_box(run.place, run.text)
3339 }
3340
3341 fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
3346 match entry {
3347 Entry::Run(e) => self.physical_box(place, e),
3348 Entry::Long(l) => {
3349 let (ox, oy) = self.physical_origin(place);
3350 let w = l.wrap_w.unwrap_or_else(|| l.width());
3351 Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
3352 }
3353 }
3354 }
3355
3356 fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
3358 let (_, oy) = self.physical_origin(place);
3359 match entry {
3360 Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
3361 Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
3362 }
3363 }
3364
3365 fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
3368 match run.text {
3369 Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
3370 Entry::Run(e) => {
3371 let (ox, oy) = self.physical_origin(run.place);
3372 let cursor = e
3373 .buffer
3374 .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
3375 Some(e.byte_of(cursor))
3376 }
3377 }
3378 }
3379
3380 pub(crate) fn word_at(
3387 &self,
3388 scope: Key,
3389 node: Key,
3390 byte: usize,
3391 prev: bool,
3392 ) -> Option<(usize, usize)> {
3393 let run = self
3394 .scope_runs(scope, prev)
3395 .into_iter()
3396 .find(|r| r.place.key == node)?;
3397 let content = run.text.content();
3398 Some(word_range(content, byte))
3399 }
3400
3401 pub(crate) fn scope_offset(
3407 &self,
3408 scope: Key,
3409 node: Key,
3410 byte: usize,
3411 prev: bool,
3412 ) -> Option<usize> {
3413 let run = self
3414 .scope_runs(scope, prev)
3415 .into_iter()
3416 .find(|r| r.place.key == node)?;
3417 Some(run.base + byte.min(run.text.content().len()))
3418 }
3419
3420 pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3435 let (from, to) = (from.min(to), from.max(to));
3436 let mut out = String::new();
3437 let mut prev_run: Option<&ScopeRun<'_>> = None;
3438 for run in &self.scope_runs(scope, prev) {
3439 let (start, end) = run.span();
3440 if end <= from || start >= to {
3441 continue;
3442 }
3443 if let Some(p) = prev_run
3444 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3445 {
3446 out.push_str("<br>");
3447 }
3448 let content = run.text.content();
3449 let lo = floor_boundary(content, from.saturating_sub(start));
3450 let hi = floor_boundary(content, (to - start).min(content.len()));
3451 match run.text {
3452 Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
3453 Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
3454 }
3455 prev_run = Some(run);
3456 }
3457 out
3458 }
3459
3460 fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
3467 for c in &line.chunks {
3468 let (a, b) = (lo.max(c.start), hi.min(c.end));
3469 if a >= b {
3470 continue;
3471 }
3472 match c.width.and_then(|_| self.run(c.key)) {
3473 Some(e) => html_of_run(e, a - c.start, b - c.start, out),
3474 None => escape_into(&line.content[a..b], out),
3475 }
3476 }
3477 }
3478
3479 pub(crate) fn scope_selection_anchor(
3489 &self,
3490 scope: Key,
3491 from: usize,
3492 to: usize,
3493 prev: bool,
3494 ) -> Option<Vec2> {
3495 let (from, to) = (from.min(to), from.max(to));
3496 for run in self.scope_runs(scope, prev) {
3497 let (start, end) = run.span();
3498 if !run.place.drawn || end <= from || start >= to {
3499 continue;
3500 }
3501 let content = run.text.content();
3502 let lo = floor_boundary(content, from.saturating_sub(start));
3503 let hi = floor_boundary(content, (to - start).min(content.len()));
3504 let (ox, oy) = self.physical_origin(run.place);
3505 match run.text {
3506 Entry::Run(e) => {
3507 if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
3508 return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
3509 }
3510 }
3511 Entry::Long(l) => {
3512 if let Some((x, y)) = self.long_caret_local(l, lo) {
3513 return Some(Vec2::new(
3514 (ox + x) / self.scale,
3515 (oy + y + self.long_baseline(l)) / self.scale,
3516 ));
3517 }
3518 }
3519 }
3520 }
3521 None
3522 }
3523
3524 fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
3527 let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
3528 for run in entry.buffer.layout_runs() {
3529 if run.line_i < a.line || run.line_i > b.line {
3530 continue;
3531 }
3532 if let Some((x, _)) = run.highlight(a, b).next() {
3533 return Some((x, run.line_y));
3534 }
3535 }
3536 None
3537 }
3538
3539 pub(crate) fn scope_selection_rect(
3545 &self,
3546 scope: Key,
3547 from: usize,
3548 to: usize,
3549 prev: bool,
3550 ) -> Option<Rect> {
3551 let (from, to) = (from.min(to), from.max(to));
3552 let mut out: Option<Rect> = None;
3553 for run in self.scope_runs(scope, prev) {
3554 let (start, end) = run.span();
3555 if !run.place.drawn || end <= from || start >= to {
3556 continue;
3557 }
3558 let content = run.text.content();
3559 let lo = floor_boundary(content, from.saturating_sub(start));
3560 let hi = floor_boundary(content, (to - start).min(content.len()));
3561 let (ox, oy) = self.physical_origin(run.place);
3567 let rects = match run.text {
3568 Entry::Long(l) => self.long_highlight(l, lo, hi),
3569 Entry::Run(e) => Self::run_highlight(e, lo, hi),
3570 };
3571 for r in rects {
3572 let r = Rect::new(
3573 (ox + r.x) / self.scale,
3574 (oy + r.y) / self.scale,
3575 r.w / self.scale,
3576 r.h / self.scale,
3577 );
3578 out = Some(match out {
3579 None => r,
3580 Some(o) => o.union(&r),
3581 });
3582 }
3583 }
3584 out
3585 }
3586
3587 pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3593 let (from, to) = (from.min(to), from.max(to));
3594 let runs = self.scope_runs(scope, prev);
3595 let mut out = String::new();
3596 let mut reserved = false;
3597 let mut prev_run: Option<&ScopeRun<'_>> = None;
3598 for run in &runs {
3599 let (start, end) = run.span();
3600 if end <= from || start >= to {
3601 continue;
3602 }
3603 if !reserved {
3604 out.reserve(to - from + runs.len());
3605 reserved = true;
3606 }
3607 if let Some(p) = prev_run
3608 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3609 {
3610 out.push('\n');
3611 }
3612 let content = run.text.content();
3613 let lo = from.saturating_sub(start).min(content.len());
3614 let hi = (to - start).min(content.len());
3615 out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
3616 prev_run = Some(run);
3617 }
3618 out
3619 }
3620
3621 fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
3625 let (ox, oy) = self.physical_origin(place);
3626 let px = point.x * self.scale - ox;
3627 let py = point.y * self.scale - oy;
3628 if line.chunks.is_empty() {
3629 return Some(TextHit { byte: 0, line: 0 });
3630 }
3631 if let Some(w) = line.wrap_w {
3632 return self.long_hit_wrapped(line, w, px, py);
3633 }
3634 if px >= line.width() {
3635 return Some(TextHit {
3636 byte: line.content.len(),
3637 line: 0,
3638 });
3639 }
3640 let i = line.chunk_at(px.max(0.0));
3641 let c = &line.chunks[i];
3642 let local = px - line.prefix[i];
3643 let byte = match self.run(c.key) {
3644 Some(e) if c.width.is_some() => {
3645 let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
3646 c.start + cursor.index.min(c.end - c.start)
3647 }
3648 _ => {
3649 let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
3650 let mut b = (c.start + est).min(c.end);
3651 while !line.content.is_char_boundary(b) {
3652 b -= 1;
3653 }
3654 b
3655 }
3656 };
3657 Some(TextHit { byte, line: 0 })
3658 }
3659
3660 fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
3664 let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
3665 let Some((a, b)) = line.chunks_on_rows(row, row) else {
3666 return Some(TextHit {
3667 byte: line.content.len(),
3668 line: row,
3669 });
3670 };
3671 let i = (a..=b)
3674 .find(|&j| {
3675 let (r1, end_x) = line.starts[j + 1];
3676 px < if r1 == row { end_x } else { w }
3677 })
3678 .unwrap_or(b);
3679 let c = &line.chunks[i];
3680 let (row0, head_x) = line.starts[i];
3681 let r = (row - row0) as usize;
3682 let byte = match self.run(c.key) {
3683 Some(e) if r < c.rows.len() => {
3684 let rs = c.rows[r];
3685 let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
3686 let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
3687 let lo = rs.byte as usize;
3688 let hi = c
3689 .rows
3690 .get(r + 1)
3691 .map_or(c.end - c.start, |n| n.byte as usize);
3692 c.start + cursor.index.clamp(lo, hi)
3693 }
3694 _ => {
3695 let linear = r as f32 * w + px - head_x;
3698 let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
3699 let mut b = (c.start + est).min(c.end);
3700 while !line.content.is_char_boundary(b) {
3701 b -= 1;
3702 }
3703 b
3704 }
3705 };
3706 let next_row = self
3711 .long_caret_local(line, byte)
3712 .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
3713 let byte = if next_row && byte > 0 {
3714 floor_boundary(&line.content, byte - 1)
3715 } else {
3716 byte
3717 };
3718 Some(TextHit { byte, line: row })
3719 }
3720
3721 fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
3724 let (ox, oy) = self.physical_origin(place);
3725 let scale = self.scale;
3726 let (x, y) = self.long_caret_local(line, byte)?;
3727 Some(Rect::new(
3728 (ox + x) / scale,
3729 (oy + y) / scale,
3730 0.0,
3731 line.line_h / scale,
3732 ))
3733 }
3734
3735 fn long_baseline(&self, line: &LongLine) -> f32 {
3742 line.chunks
3743 .first()
3744 .and_then(|c| self.run(c.key))
3745 .and_then(|e| e.buffer.layout_runs().next())
3746 .map_or(line.line_h * 0.8, |r| r.line_y.round())
3747 }
3748
3749 fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
3754 let byte = byte.min(line.content.len());
3755 if let Some(w) = line.wrap_w
3756 && !line.chunks.is_empty()
3757 {
3758 let i = line.chunk_of_byte(byte);
3759 let c = &line.chunks[i];
3760 let local = byte - c.start;
3761 let (row0, head_x) = line.starts[i];
3762 let (r, x) = match self.run(c.key) {
3763 Some(e) if !c.rows.is_empty() => {
3764 let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
3765 let run = e.buffer.layout_runs().next()?;
3766 let cx = caret_x(&run, local.min(c.end - c.start));
3767 (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
3768 }
3769 _ => {
3770 let linear = head_x + local as f32 * line.avg;
3771 let r = (linear / w).floor();
3772 (r as usize, linear - r * w)
3773 }
3774 };
3775 let y = (row0 as usize + r) as f32 * line.line_h;
3776 return Some((x, y));
3777 }
3778 let x = if line.chunks.is_empty() {
3779 0.0
3780 } else {
3781 let i = line.chunk_of_byte(byte);
3782 let c = &line.chunks[i];
3783 let local = byte - c.start;
3784 match self.run(c.key) {
3785 Some(e) if c.width.is_some() => {
3786 let run = e.buffer.layout_runs().next()?;
3787 line.prefix[i] + caret_x(&run, local.min(c.end - c.start))
3788 }
3789 _ => line.prefix[i] + local as f32 * line.avg,
3790 }
3791 };
3792 Some((x, 0.0))
3793 }
3794
3795 fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
3801 let Some((x0, y0)) = self.long_caret_local(line, from) else {
3802 return Vec::new();
3803 };
3804 let Some((x1, y1)) = self.long_caret_local(line, to) else {
3805 return Vec::new();
3806 };
3807 let h = line.line_h;
3808 if (y1 - y0).abs() < f32::EPSILON {
3809 return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
3810 }
3811 let w = line.wrap_w.unwrap_or_else(|| line.width());
3813 let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
3814 let mut y = y0 + h;
3815 while y < y1 - h / 2.0 {
3816 out.push(Rect::new(0.0, y, w, h));
3817 y += h;
3818 }
3819 out.push(Rect::new(0.0, y1, x1.max(0.0), h));
3820 out
3821 }
3822
3823 fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
3829 let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
3830 let mut out = Vec::new();
3831 for run in entry.buffer.layout_runs() {
3832 if run.line_i < a.line || run.line_i > b.line {
3833 continue;
3834 }
3835 let h = entry.buffer.metrics().line_height;
3836 let mut any = false;
3837 for (x, w) in run.highlight(a, b) {
3838 any = true;
3839 out.push(Rect::new(x, run.line_top, w.max(2.0), h));
3840 }
3841 if !any && run.glyphs.is_empty() && b.line > run.line_i {
3844 out.push(Rect::new(0.0, run.line_top, 2.0, h));
3845 }
3846 }
3847 out
3848 }
3849
3850 fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
3853 (
3854 crate::geom::snap_px(place.origin.x * self.scale),
3855 crate::geom::snap_px(place.origin.y * self.scale),
3856 )
3857 }
3858
3859 fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
3861 let (ox, oy) = self.physical_origin(place);
3862 let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
3863 Rect::new(ox, oy, size.w, size.h)
3864 }
3865
3866 pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
3870 let runs = self.runs_of(key, prev);
3871 if runs.is_empty() {
3872 return None;
3873 }
3874 let px = point.x * self.scale;
3875 let py = point.y * self.scale;
3876 let gap = |r: &Rect| {
3879 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3880 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3881 (dy, dx)
3882 };
3883 let (place, entry, base) = runs
3884 .iter()
3885 .min_by(|a, b| {
3886 let ga = gap(&self.entry_box(a.0, a.1));
3887 let gb = gap(&self.entry_box(b.0, b.1));
3888 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3889 })
3890 .copied()?;
3891 let (ox, oy) = self.physical_origin(place);
3892 let (byte, hit_top) = match entry {
3893 Entry::Long(line) => {
3894 let hit = self.long_hit(place, line, point)?;
3895 (hit.byte, oy + hit.line as f32 * line.line_h)
3896 }
3897 Entry::Run(entry) => {
3898 let bx = self.physical_box(place, entry);
3905 let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
3906 let cursor = entry.buffer.hit(px - ox, py - oy)?;
3907 let row =
3908 visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
3909 let top = entry
3910 .buffer
3911 .layout_runs()
3912 .nth(row)
3913 .map_or(0.0, |r| r.line_top);
3914 (entry.byte_of(cursor), oy + top)
3915 }
3916 };
3917 let mut tops: Vec<f32> = runs
3922 .iter()
3923 .flat_map(|(p, e, _)| self.row_tops(p, e))
3924 .collect();
3925 tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3926 tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
3927 let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
3928 Some(TextHit {
3929 byte: base + byte,
3930 line: line as u32,
3931 })
3932 }
3933
3934 pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
3938 let runs = self.runs_of(key, prev);
3939 let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
3940 let byte = byte.min(total);
3941 let (place, entry, base) = runs
3942 .iter()
3943 .find(|(_, e, base)| byte < base + e.content().len())
3944 .or_else(|| runs.last())
3945 .copied()?;
3946 let byte = (byte - base).min(entry.content().len());
3947 let entry = match entry {
3948 Entry::Long(line) => return self.long_caret(place, line, byte),
3949 Entry::Run(entry) => entry,
3950 };
3951 let (ox, oy) = self.physical_origin(place);
3952 let cursor = entry.cursor_of(byte);
3953 let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
3954 let run = entry.buffer.layout_runs().nth(run_no)?;
3955 let x = caret_x(&run, cursor.index);
3956 let scale = self.scale;
3957 Some(Rect::new(
3958 (ox + x) / scale,
3959 (oy + run.line_top) / scale,
3960 0.0,
3961 run.line_height / scale,
3962 ))
3963 }
3964}
3965
3966fn word_range(content: &str, byte: usize) -> (usize, usize) {
3976 if content.is_empty() {
3977 return (0, 0);
3978 }
3979 #[derive(PartialEq)]
3980 enum Class {
3981 Word,
3982 Space,
3983 Other,
3984 }
3985 let class = |c: char| {
3986 if c.is_alphanumeric() || c == '_' {
3987 Class::Word
3988 } else if c.is_whitespace() {
3989 Class::Space
3990 } else {
3991 Class::Other
3992 }
3993 };
3994 let at = floor_boundary(content, byte.min(content.len()));
3997 let at = if at >= content.len() {
3998 content.char_indices().next_back().map_or(0, |(i, _)| i)
3999 } else {
4000 at
4001 };
4002 let Some(here) = content[at..].chars().next().map(class) else {
4003 return (at, at);
4004 };
4005 let mut start = at;
4006 for (i, c) in content[..at].char_indices().rev() {
4007 if class(c) != here {
4008 break;
4009 }
4010 start = i;
4011 }
4012 let mut end = at;
4013 for (i, c) in content[at..].char_indices() {
4014 if class(c) != here {
4015 break;
4016 }
4017 end = at + i + c.len_utf8();
4018 }
4019 (start, end)
4020}
4021
4022fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4027 let starts = line_starts(&entry.buffer, &entry.content);
4028 for (li, line) in entry.buffer.lines.iter().enumerate() {
4029 let base = starts.get(li).copied().unwrap_or(0);
4030 let text = line.text();
4031 if li > 0 {
4032 if base > lo && base <= hi {
4035 out.push_str("<br>");
4036 }
4037 }
4038 let attrs_list = line.attrs_list();
4044 let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4045 for (i, _) in text.char_indices() {
4046 let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4047 match spans.last_mut() {
4048 Some((_, end, prev)) if *prev == a && *end == i => {
4049 *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4050 }
4051 _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4052 }
4053 }
4054 for (start, end, attrs) in &spans {
4055 let (s, e) = (base + start, base + end);
4056 let (s, e) = (s.max(lo), e.min(hi));
4057 if s >= e {
4058 continue;
4059 }
4060 let piece = &entry.content[s..e];
4061 let bold = attrs.weight > attrs_list.defaults().weight
4066 || attrs
4067 .cache_key_flags
4068 .contains(crate::weights::SYNTHETIC_BOLD);
4069 let italic = attrs.style != cosmic_text::Style::Normal;
4070 if let Some(c) = attrs.color_opt {
4071 let _ = std::fmt::Write::write_fmt(
4072 out,
4073 format_args!(
4074 "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4075 c.r(),
4076 c.g(),
4077 c.b()
4078 ),
4079 );
4080 }
4081 if bold {
4082 out.push_str("<b>");
4083 }
4084 if italic {
4085 out.push_str("<i>");
4086 }
4087 escape_into(piece, out);
4088 if italic {
4089 out.push_str("</i>");
4090 }
4091 if bold {
4092 out.push_str("</b>");
4093 }
4094 if attrs.color_opt.is_some() {
4095 out.push_str("</span>");
4096 }
4097 }
4098 }
4099}
4100
4101fn escape_into(s: &str, out: &mut String) {
4105 for ch in s.chars() {
4106 match ch {
4107 '&' => out.push_str("&"),
4108 '<' => out.push_str("<"),
4109 '>' => out.push_str(">"),
4110 _ => out.push(ch),
4111 }
4112 }
4113}
4114
4115fn floor_boundary(s: &str, i: usize) -> usize {
4120 let mut i = i.min(s.len());
4121 while i > 0 && !s.is_char_boundary(i) {
4122 i -= 1;
4123 }
4124 i
4125}
4126
4127fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4128 paragraph_starts(buffer, content).collect()
4129}
4130
4131fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4137 let mut at = 0usize;
4138 buffer.lines.iter().map(move |line| {
4139 let start = at.min(content.len());
4140 at += line.text().len();
4141 let rest = &content[at.min(content.len())..];
4142 for ending in ["\r\n", "\n\r", "\n", "\r"] {
4143 if rest.starts_with(ending) {
4144 at += ending.len();
4145 break;
4146 }
4147 }
4148 start
4149 })
4150}
4151
4152fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4162 let mut last_of_line = None;
4163 let mut before = None;
4164 for (n, run) in buffer.layout_runs().enumerate() {
4165 if run.line_i != line_i {
4166 if last_of_line.is_some() {
4167 break;
4168 }
4169 continue;
4170 }
4171 last_of_line = Some(n);
4172 if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4173 return Some((n, n));
4174 }
4175 if run
4176 .glyphs
4177 .iter()
4178 .map(|g| g.start)
4179 .min()
4180 .is_some_and(|s| s <= index)
4181 {
4182 before = Some(n);
4183 }
4184 }
4185 before.or(last_of_line).map(|n| (n, n))
4186}
4187
4188fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4192 if let Some(g) = run
4193 .glyphs
4194 .iter()
4195 .find(|g| g.start <= index && index < g.end)
4196 {
4197 return if g.level.is_rtl() { g.x + g.w } else { g.x };
4198 }
4199 match run.glyphs.last() {
4200 Some(g) if !g.level.is_rtl() => g.x + g.w,
4201 Some(g) => g.x,
4202 None => 0.0,
4203 }
4204}
4205
4206impl Default for TextSystem {
4207 fn default() -> Self {
4208 Self::new()
4209 }
4210}
4211
4212fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4215 let max_w = max_w_logical?;
4216 let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4217 (!intrinsic_fits).then_some(max_w * scale)
4218}
4219
4220pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4228 match (current, target) {
4229 (None, None) => false,
4230 (Some(a), Some(b)) => (a - b).abs() > 0.5,
4231 _ => true,
4232 }
4233}
4234
4235fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4236 if wrap_differs(entry.wrap, target) {
4237 entry.buffer.set_size(target, None);
4238 entry.buffer.shape_until_scroll(fs, false);
4239 entry.wrap = target;
4240 }
4241}
4242
4243impl CachedText {
4244 fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4247 let start = paragraph_starts(&self.buffer, &self.content)
4248 .nth(cursor.line)
4249 .unwrap_or(0);
4250 (start + cursor.index).min(self.content.len())
4251 }
4252
4253 fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4259 let mut li = 0;
4260 let mut start = 0;
4261 for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4262 if s <= byte {
4263 (li, start) = (i, s);
4264 } else {
4265 break;
4266 }
4267 }
4268 let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4269 cosmic_text::Cursor::new(li, (byte - start).min(len))
4270 }
4271
4272 fn new(
4273 mut buffer: Buffer,
4274 content: String,
4275 style: &TextStyle,
4276 span_deco: Vec<SpanDeco>,
4277 fs: &mut FontSystem,
4278 frame_no: u64,
4279 ) -> Self {
4280 buffer.shape_until_scroll(fs, false);
4281 let max_lines = line_budget(style);
4282 let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4283 let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4284 Self {
4285 buffer,
4286 bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4287 content,
4288 wrap: None,
4289 intrinsic,
4290 max_lines,
4291 clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4292 last_used: frame_no,
4293 glyphs: Vec::new(),
4294 deco: Vec::new(),
4295 span_deco,
4296 glyphs_built_for: None,
4297 claimed: u64::MAX,
4298 claimed_wrap: None,
4299 breaks_anywhere: style.wrap == TextWrap::Glyph,
4300 min_content: None,
4301 }
4302 }
4303
4304 fn fork(&self, frame_no: u64) -> Self {
4308 Self {
4309 buffer: self.buffer.clone(),
4310 content: self.content.clone(),
4311 wrap: self.wrap,
4312 intrinsic: self.intrinsic,
4313 max_lines: self.max_lines,
4314 clamp_w: self.clamp_w,
4315 last_used: frame_no,
4316 bytes: self.bytes,
4317 glyphs: Vec::new(),
4318 deco: Vec::new(),
4319 span_deco: self.span_deco.clone(),
4320 glyphs_built_for: None,
4321 claimed: u64::MAX,
4322 claimed_wrap: None,
4323 breaks_anywhere: self.breaks_anywhere,
4324 min_content: self.min_content,
4325 }
4326 }
4327
4328 fn min_content(&mut self) -> f32 {
4340 if let Some(w) = self.min_content {
4341 return w;
4342 }
4343 let w = if self.clamp_w {
4344 self.intrinsic.w
4345 } else {
4346 widest_unbreakable(&self.buffer, self.breaks_anywhere)
4347 };
4348 self.min_content = Some(w);
4349 w
4350 }
4351}
4352
4353fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
4357 let mut widest = 0.0f32;
4358 let mut line = usize::MAX;
4359 let mut breaks: Vec<usize> = Vec::new();
4360 let (mut run_w, mut ink) = (0.0f32, 0.0f32);
4361 for run in buffer.layout_runs() {
4362 if run.line_i != line {
4363 widest = widest.max(ink);
4364 (run_w, ink) = (0.0, 0.0);
4365 line = run.line_i;
4366 breaks.clear();
4367 if !anywhere {
4368 breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
4369 }
4370 }
4371 for g in run.glyphs {
4372 if anywhere || breaks.binary_search(&g.start).is_ok() {
4373 widest = widest.max(ink);
4374 (run_w, ink) = (0.0, 0.0);
4375 }
4376 run_w += g.w;
4377 let blank = run
4378 .text
4379 .get(g.start..g.end)
4380 .is_some_and(|t| t.chars().all(char::is_whitespace));
4381 if !blank {
4382 ink = run_w;
4383 }
4384 }
4385 }
4386 widest.max(ink)
4387}
4388
4389fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
4392 let metrics = Metrics::new(style.size * scale, style.line_height * scale);
4393 let mut buffer = Buffer::new(fs, metrics);
4394 buffer.set_wrap(match style.wrap {
4395 TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
4396 TextWrap::Glyph => Wrap::Glyph,
4397 TextWrap::None => Wrap::None,
4398 });
4399 if style.ellipsis {
4400 let lines = line_budget(style).max(1);
4401 buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
4402 }
4403 buffer.set_size(None, None);
4404 buffer
4405}
4406
4407fn line_budget(style: &TextStyle) -> usize {
4410 if style.max_lines > 0 {
4411 style.max_lines as usize
4412 } else if style.ellipsis {
4413 1
4414 } else {
4415 0
4416 }
4417}
4418
4419fn line_cap(max_lines: usize) -> usize {
4420 if max_lines == 0 {
4421 usize::MAX
4422 } else {
4423 max_lines
4424 }
4425}
4426
4427pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
4433 let mut w = 0.0f32;
4434 let mut lines = 0u32;
4435 for run in buffer.layout_runs().take(line_cap(max_lines)) {
4436 w = w.max(run.line_w);
4437 lines += 1;
4438 }
4439 (
4440 Size::new(w, lines as f32 * buffer.metrics().line_height),
4441 lines,
4442 )
4443}
4444
4445impl TextSystem {
4446 pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
4447 let scale = self.scale;
4448 if let Some(line) = self.long_of(id) {
4449 return Size::new(line.width() / scale, line.line_h / scale);
4450 }
4451 let e = self.entry_mut(id);
4452 Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
4453 }
4454
4455 pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
4460 let scale = self.scale;
4461 if self.long_of(id).is_some() {
4462 return 0.0;
4463 }
4464 self.entry_mut(id).min_content() / scale
4465 }
4466
4467 fn long_of(&self, id: TextId) -> Option<&LongLine> {
4469 self.long(self.frame[id.0 as usize].cache_key)
4470 }
4471
4472 pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
4473 let scale = self.scale;
4474 if self.long_of(id).is_some() {
4475 let key = self.frame[id.0 as usize].cache_key;
4478 let (size, rows) = self.long_size(key, Some(max_w * scale));
4479 self.long_mut(key).expect("a long line").laid_rows = rows;
4480 return Size::new(size.w / scale, size.h / scale);
4481 }
4482 self.ensure_wrap(id, max_w, fs);
4483 let e = self.entry_mut(id);
4484 let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
4485 if e.clamp_w {
4486 m.w = m.w.min(max_w * scale);
4489 }
4490 Size::new(m.w / scale, m.h / scale)
4491 }
4492}
4493
4494impl TextSystem {
4495 pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
4501 let scale = self.scale;
4502 if let Some(line) = self.long_of(id) {
4503 return self.long_baseline(line) / scale;
4504 }
4505 let e = self.entry_mut(id);
4506 let b = e
4507 .buffer
4508 .layout_runs()
4509 .next()
4510 .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
4511 b / scale
4512 }
4513}
4514
4515#[cfg(test)]
4518mod default_families {
4519 use super::*;
4520 use cosmic_text::Family;
4521 use cosmic_text::fontdb::FaceInfo;
4522
4523 fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
4526 let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
4527 buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
4528 buffer.shape_until_scroll(fs, false);
4529 let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
4530 fs.db().face(id).cloned()
4531 }
4532
4533 #[test]
4534 fn mono_is_an_upright_monospaced_face() {
4535 let mut fs = new_font_system().0;
4536 if !fs.db().faces().any(|f| f.monospaced) {
4537 eprintln!("skipped: no monospaced face installed");
4538 return;
4539 }
4540 let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
4541 let mono = default_families(&fs)[2].to_string();
4542 assert_eq!(
4543 face.style,
4544 cosmic_text::Style::Normal,
4545 "Mono ({mono}) resolved to {:?}",
4546 face.post_script_name
4547 );
4548 assert!(
4549 face.monospaced,
4550 "Mono ({mono}) resolved to {:?}, which is not monospaced",
4551 face.post_script_name
4552 );
4553 }
4554
4555 #[test]
4556 fn sans_and_serif_are_upright_and_not_monospaced() {
4557 let mut fs = new_font_system().0;
4558 if fs.db().faces().next().is_none() {
4559 eprintln!("skipped: no face installed");
4560 return;
4561 }
4562 let [sans, serif, _] = default_families(&fs).map(str::to_string);
4563 for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
4564 let face = resolved(&mut fs, family).expect("M shapes");
4565 assert_eq!(
4566 face.style,
4567 cosmic_text::Style::Normal,
4568 "{name} resolved to {:?}",
4569 face.post_script_name
4570 );
4571 assert!(
4572 !face.monospaced,
4573 "{name} resolved to {:?}, which is monospaced",
4574 face.post_script_name
4575 );
4576 }
4577 }
4578
4579 #[test]
4582 fn pinned_names_are_installed_or_cosmic_texts_own() {
4583 let fs = new_font_system().0;
4584 let db = fs.db();
4585 for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
4586 let installed = db
4587 .faces()
4588 .any(|f| f.families.iter().any(|(fam, _)| fam == name));
4589 let on_list = list.contains(&name);
4590 assert!(
4591 installed == on_list,
4592 "{name}: installed {installed}, on the platform list {on_list}"
4593 );
4594 }
4595 }
4596}
4597
4598#[cfg(test)]
4605mod unmeasurable_faces {
4606 use super::*;
4607 use crate::testing::{font_face, han_face, unmeasurable_face};
4608 use cosmic_text::Family;
4609 use cosmic_text::fontdb::{Database, Source};
4610
4611 fn font_system() -> FontSystem {
4616 let mut db = Database::new();
4617 for bytes in [
4618 font_face("Kui Mono", 400, false, true),
4619 unmeasurable_face("Kui Bitmap"),
4620 han_face("Kui Han"),
4621 ] {
4622 db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
4623 }
4624 let mut fs = font_system_with("en-US".into(), db);
4625 fs.db_mut().set_monospace_family("Kui Mono");
4626 fs
4627 }
4628
4629 fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
4630 fs.db()
4631 .face(id)
4632 .map_or_else(String::new, |f| f.families[0].0.clone())
4633 }
4634
4635 #[test]
4636 fn a_face_without_metrics_is_not_in_the_database() {
4637 let fs = font_system();
4638 let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
4639 assert_eq!(families, ["Kui Mono", "Kui Han"]);
4640 }
4641
4642 #[test]
4646 fn han_in_mono_falls_back_to_a_face_that_measures() {
4647 let mut fs = font_system();
4648 let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
4649 let attrs = Attrs::new().family(Family::Monospace);
4650 buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
4651 buffer.shape_until_scroll(&mut fs, false);
4652 let run = buffer.layout_runs().next().expect("one line");
4653 let glyphs: Vec<(&str, f32, f32, String)> = run
4654 .glyphs
4655 .iter()
4656 .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
4657 .collect();
4658 for &(text, x, w, _) in &glyphs {
4659 assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
4660 }
4661 let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
4662 assert_eq!(
4663 faces,
4664 [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
4665 );
4666 let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
4668 assert_eq!(xs, [0.0, 7.0, 14.0]);
4669 for glyph in run.glyphs {
4670 glyph.physical((0.0, 0.0), 1.0);
4671 }
4672 }
4673}