1use cosmic_text::{
8 Attrs, Buffer, CacheKeyFlags, Ellipsize, EllipsizeHeightLimit, FontSystem, Metrics, Shaping,
9 Style as FontStyle, SwashContent, Weight, Wrap,
10};
11use rustc_hash::FxHashMap;
12
13use crate::atlas::GlyphAtlas;
14use crate::color::Color;
15use crate::display::{Clip, ClipId, Quad, QuadKind};
16use crate::geom::{Rect, Size, Vec2};
17use crate::key::Key;
18use crate::resources::Resources;
19use crate::retain::Kept;
20use crate::spec::{FontFamily, TextStyle, TextWrap, UnderlineStyle};
21use crate::tree::TextId;
22use crate::value::Value;
23
24pub(crate) struct Raster {
31 ctx: swash::scale::ScaleContext,
32 axes: FxHashMap<cosmic_text::fontdb::ID, Option<crate::weights::WghtAxis>>,
34 pub(crate) subpixel: bool,
38}
39
40impl Raster {
41 fn new() -> Self {
42 Self {
43 ctx: swash::scale::ScaleContext::new(),
44 axes: FxHashMap::default(),
45 subpixel: false,
46 }
47 }
48
49 fn image(
61 &mut self,
62 fs: &mut FontSystem,
63 key: cosmic_text::CacheKey,
64 format: swash::zeno::Format,
65 ) -> Option<cosmic_text::SwashImage> {
66 use swash::scale::{Render, Source, StrikeWith};
67 use swash::zeno::{Angle, Transform, Vector};
68 let font = fs.get_font(key.font_id, key.font_weight)?;
69 let swash_font = font.as_swash();
70 let axis = self
71 .axes
72 .entry(key.font_id)
73 .or_insert_with(|| {
74 let weight = fs.db().face(key.font_id).map_or(400, |face| face.weight.0);
75 crate::weights::WghtAxis::read(swash_font, weight)
76 })
77 .as_ref();
78 let size = f32::from_bits(key.font_size_bits);
79 let face_weight = f32::from(key.font_weight.0);
80 let bold = key.flags.contains(crate::weights::SYNTHETIC_BOLD);
81 let asked = if bold {
82 f32::from(Weight::BOLD.0)
83 } else {
84 face_weight
85 };
86 let at = axis.map(|axis| axis.coordinate(asked));
87 let embolden = bold
90 && match (axis, at) {
91 (Some(axis), Some(at)) => at <= axis.coordinate(face_weight),
92 _ => true,
93 };
94 let mut scaler = self
95 .ctx
96 .builder(swash_font)
97 .size(size)
98 .hint(!key.flags.contains(CacheKeyFlags::DISABLE_HINTING));
99 if let Some(at) = at {
100 scaler = scaler.normalized_coords(
101 swash_font
102 .variations()
103 .normalized_coords([(swash::Tag::from_be_bytes(*b"wght"), at)]),
104 );
105 }
106 let mut scaler = scaler.build();
107 let offset = if key.flags.contains(CacheKeyFlags::PIXEL_FONT) {
108 Vector::new(key.x_bin.as_float().round(), key.y_bin.as_float().round())
109 } else {
110 Vector::new(key.x_bin.as_float(), key.y_bin.as_float())
111 };
112 Render::new(&[
113 Source::ColorOutline(0),
114 Source::ColorBitmap(StrikeWith::BestFit),
115 Source::Outline,
116 ])
117 .format(format)
118 .offset(offset)
119 .embolden(if embolden {
120 crate::weights::embolden_strength(size)
121 } else {
122 0.0
123 })
124 .transform(
125 key.flags
126 .contains(CacheKeyFlags::FAKE_ITALIC)
127 .then(|| Transform::skew(Angle::from_degrees(14.0), Angle::from_degrees(0.0))),
128 )
129 .render(&mut scaler, key.glyph_id)
130 }
131}
132
133pub(crate) fn raster_glyph(
135 key: cosmic_text::CacheKey,
136 fs: &mut FontSystem,
137 raster: &mut Raster,
138 atlas: &mut crate::atlas::GlyphAtlas,
139) -> Option<crate::atlas::GlyphSlot> {
140 atlas.get_or_insert(key, || {
141 let format = if raster.subpixel {
142 swash::zeno::Format::Subpixel
143 } else {
144 swash::zeno::Format::Alpha
145 };
146 let image = raster.image(fs, key, format)?;
147 if image.placement.width == 0 || image.placement.height == 0 {
148 return None;
149 }
150 let (data, is_color, subpixel) = match image.content {
151 SwashContent::Mask => {
152 let mut rgba = Vec::with_capacity(image.data.len() * 4);
153 for &a in image.data.iter() {
154 rgba.extend_from_slice(&[255, 255, 255, a]);
155 }
156 (rgba, false, false)
157 }
158 SwashContent::Color => (image.data.to_vec(), true, false),
159 SwashContent::SubpixelMask => {
160 let mut rgba = image.data.to_vec();
164 for px in rgba.as_chunks_mut::<4>().0 {
165 px[3] = px[0].max(px[1]).max(px[2]);
166 }
167 (rgba, false, true)
168 }
169 };
170 Some(crate::atlas::RasterGlyph {
171 w: image.placement.width,
172 h: image.placement.height,
173 left: image.placement.left,
174 top: image.placement.top,
175 color: is_color,
176 subpixel,
177 data,
178 })
179 })
180}
181
182pub(crate) fn glyph_kind(slot: &crate::atlas::GlyphSlot) -> QuadKind {
184 if slot.color_glyph {
185 QuadKind::GlyphColor
186 } else if slot.subpixel {
187 QuadKind::GlyphSubpixel
188 } else {
189 QuadKind::GlyphMask
190 }
191}
192
193pub const DEFAULT_TEXT_CACHE_BYTES: usize = 64 << 20;
199
200const ENTRY_BASE_BYTES: usize = 4096;
210const ENTRY_GLYPH_BYTES: usize = 480;
211
212pub const LONG_LINE_BYTES: usize = 4096;
218const CHUNK_BYTES: usize = 1024;
223const LONG_SALT: u64 = 0x5f5f_6c6f_6e67_5f5f;
226const COPY_SALT: u64 = 0x5f5f_636f_7079_5f5f;
229
230const EVICT_TO_NUMERATOR: usize = 3;
234const EVICT_TO_DENOMINATOR: usize = 4;
235
236pub(crate) struct CachedText {
237 buffer: Buffer,
238 content: String,
241 wrap: Option<f32>,
243 intrinsic: Size,
245 max_lines: usize,
248 clamp_w: bool,
251 last_used: u64,
252 bytes: usize,
254 glyphs: Vec<GlyphTemplate>,
257 deco: Vec<DecoTemplate>,
261 span_deco: Vec<SpanDeco>,
264 glyphs_built_for: Option<(Option<u32>, u64)>,
266 claimed: u64,
271 claimed_wrap: Option<f32>,
272 breaks_anywhere: bool,
275 min_content: Option<f32>,
279}
280
281struct GlyphTemplate {
282 x: f32,
283 y: f32,
284 w: f32,
285 h: f32,
286 uv: [u32; 4],
287 kind: QuadKind,
288 color: Option<Color>,
290 byte: u32,
293}
294
295struct DecoTemplate {
297 x: f32,
298 y: f32,
299 w: f32,
300 h: f32,
301 color: Option<Color>,
304 under: bool,
306 style: UnderlineStyle,
309 radius: f32,
312}
313
314#[derive(Clone, Copy, Debug)]
323pub(crate) struct JoinBg {
324 pub quad: u32,
325 pub radius: f32,
326 pub outer: ClipId,
327 pub own: Option<(f32, f32)>,
328}
329
330#[derive(Clone, Copy, Default)]
332struct SpanDeco {
333 underline: bool,
334 underline_color: Option<Color>,
335 underline_style: UnderlineStyle,
336 strikethrough: bool,
337 bg: Option<Color>,
338 bg_radius: f32,
341}
342
343impl SpanDeco {
344 fn of_style(style: &TextStyle) -> Self {
345 Self {
346 underline: style.underline,
347 underline_color: style.underline_color,
348 underline_style: style.underline_style,
349 strikethrough: style.strikethrough,
350 bg: None,
351 bg_radius: 0.0,
352 }
353 }
354
355 fn any(&self) -> bool {
356 self.underline || self.strikethrough || self.bg.is_some()
357 }
358}
359
360#[derive(Clone, Copy, Debug)]
363pub struct Span<'a> {
364 pub text: &'a str,
365 pub color: Option<Color>,
366 pub bold: bool,
367 pub italic: bool,
368 pub underline: bool,
370 pub underline_color: Option<Color>,
372 pub underline_style: UnderlineStyle,
374 pub strikethrough: bool,
376 pub bg: Option<Color>,
379 pub bg_radius: f32,
386}
387
388impl<'a> Span<'a> {
389 pub fn new(text: &'a str) -> Self {
390 Self {
391 text,
392 color: None,
393 bold: false,
394 italic: false,
395 underline: false,
396 underline_color: None,
397 underline_style: UnderlineStyle::Solid,
398 strikethrough: false,
399 bg: None,
400 bg_radius: 0.0,
401 }
402 }
403
404 pub fn color(mut self, c: Color) -> Self {
405 self.color = Some(c);
406 self
407 }
408
409 pub fn bold(mut self) -> Self {
410 self.bold = true;
411 self
412 }
413
414 pub fn italic(mut self) -> Self {
415 self.italic = true;
416 self
417 }
418
419 pub fn underline(mut self) -> Self {
420 self.underline = true;
421 self
422 }
423
424 pub fn underline_color(mut self, c: Color) -> Self {
426 self.underline = true;
427 self.underline_color = Some(c);
428 self
429 }
430
431 pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
433 self.underline = true;
434 self.underline_style = s;
435 self
436 }
437
438 pub fn strikethrough(mut self) -> Self {
439 self.strikethrough = true;
440 self
441 }
442
443 pub fn bg(mut self, c: Color) -> Self {
444 self.bg = Some(c);
445 self
446 }
447
448 pub fn bg_radius(mut self, r: f32) -> Self {
451 self.bg_radius = r.max(0.0);
452 self
453 }
454
455 fn attrs_only(&self) -> SpanAttrs {
458 SpanAttrs {
459 color: self.color,
460 bold: self.bold,
461 italic: self.italic,
462 underline: self.underline,
463 underline_color: self.underline_color,
464 underline_style: self.underline_style,
465 strikethrough: self.strikethrough,
466 bg: self.bg,
467 bg_radius: self.bg_radius,
468 }
469 }
470
471 fn attrs(
472 &self,
473 family: cosmic_text::Family<'a>,
474 weights: crate::weights::Weights,
475 ) -> Attrs<'a> {
476 let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
480 if self.italic {
481 attrs = attrs.style(FontStyle::Italic);
482 }
483 if let Some(c) = self.color {
484 attrs = attrs.color(cosmic_text::Color::rgba(
485 (c.r * 255.0) as u8,
486 (c.g * 255.0) as u8,
487 (c.b * 255.0) as u8,
488 (c.a * 255.0) as u8,
489 ));
490 }
491 attrs
492 }
493}
494
495#[derive(Clone, Copy)]
499struct SpanAttrs {
500 color: Option<Color>,
501 bold: bool,
502 italic: bool,
503 underline: bool,
504 underline_color: Option<Color>,
505 underline_style: UnderlineStyle,
506 strikethrough: bool,
507 bg: Option<Color>,
508 bg_radius: f32,
509}
510
511impl SpanAttrs {
512 fn span<'a>(&self, text: &'a str) -> Span<'a> {
513 Span {
514 text,
515 color: self.color,
516 bold: self.bold,
517 italic: self.italic,
518 underline: self.underline,
519 underline_color: self.underline_color,
520 underline_style: self.underline_style,
521 strikethrough: self.strikethrough,
522 bg: self.bg,
523 bg_radius: self.bg_radius,
524 }
525 }
526}
527
528#[derive(Clone)]
531struct OwnedSpan {
532 start: usize,
533 end: usize,
534 attrs: SpanAttrs,
535}
536
537fn chunk_spans<'a>(
542 content: &'a str,
543 spans: &[OwnedSpan],
544 start: usize,
545 end: usize,
546) -> Vec<Span<'a>> {
547 let first = spans.partition_point(|s| s.end <= start);
548 spans[first..]
549 .iter()
550 .take_while(|s| s.start < end)
551 .filter_map(|s| {
552 let (a, b) = (s.start.max(start), s.end.min(end));
553 (a < b).then(|| s.attrs.span(&content[a..b]))
554 })
555 .collect()
556}
557
558struct FrameText {
559 cache_key: u64,
560 color: Color,
561}
562
563pub(crate) enum Entry {
571 Run(CachedText),
572 Long(LongLine),
573}
574
575impl Entry {
576 pub(crate) fn content(&self) -> &str {
577 match self {
578 Entry::Run(e) => &e.content,
579 Entry::Long(l) => &l.content,
580 }
581 }
582
583 fn last_used(&self) -> u64 {
584 match self {
585 Entry::Run(e) => e.last_used,
586 Entry::Long(l) => l.last_used,
587 }
588 }
589
590 fn touch(&mut self, frame_no: u64) {
591 match self {
592 Entry::Run(e) => e.last_used = frame_no,
593 Entry::Long(l) => l.last_used = frame_no,
594 }
595 }
596
597 fn bytes(&self) -> usize {
599 match self {
600 Entry::Run(e) => e.bytes,
601 Entry::Long(l) => l.bytes,
602 }
603 }
604
605 fn run(&self) -> Option<&CachedText> {
606 match self {
607 Entry::Run(e) => Some(e),
608 Entry::Long(_) => None,
609 }
610 }
611
612 fn run_mut(&mut self) -> Option<&mut CachedText> {
613 match self {
614 Entry::Run(e) => Some(e),
615 Entry::Long(_) => None,
616 }
617 }
618
619 fn long(&self) -> Option<&LongLine> {
620 match self {
621 Entry::Long(l) => Some(l),
622 Entry::Run(_) => None,
623 }
624 }
625
626 fn long_mut(&mut self) -> Option<&mut LongLine> {
627 match self {
628 Entry::Long(l) => Some(l),
629 Entry::Run(_) => None,
630 }
631 }
632}
633
634#[derive(Clone)]
638struct Chunk {
639 start: usize,
640 end: usize,
641 key: u64,
642 width: Option<f32>,
643 rows: Vec<RowStart>,
646}
647
648#[derive(Clone, Copy, Debug)]
652struct RowStart {
653 byte: u32,
654 x: f32,
655}
656
657#[derive(Clone)]
670pub(crate) struct LongLine {
671 content: String,
672 spans: Vec<OwnedSpan>,
676 style: TextStyle,
679 wrap: TextWrap,
685 wrap_w: Option<f32>,
688 starts: Vec<(u32, f32)>,
693 chunks: Vec<Chunk>,
694 prefix: Vec<f32>,
697 line_h: f32,
699 avg: f32,
702 last_used: u64,
703 claimed: u64,
706 laid_rows: u32,
713 asked_rows: u32,
714 bytes: usize,
715}
716
717impl LongLine {
718 fn chunk_at(&self, x: f32) -> usize {
720 match self.prefix[1..].partition_point(|&p| p <= x) {
721 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
722 i => i,
723 }
724 }
725
726 fn chunk_of_byte(&self, byte: usize) -> usize {
728 match self.chunks.partition_point(|c| c.end <= byte) {
729 i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
730 i => i,
731 }
732 }
733
734 fn width(&self) -> f32 {
735 self.prefix.last().copied().unwrap_or(0.0)
736 }
737
738 fn rows(&self) -> u32 {
740 match self.starts.last() {
741 Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
742 _ => 1,
743 }
744 }
745
746 fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
749 let n = self.chunks.len();
750 if n == 0 || self.starts.len() != n + 1 {
751 return None;
752 }
753 let first = self.starts[1..].partition_point(|s| s.0 < ra);
754 let last = self.starts[..n].partition_point(|s| s.0 <= rb);
755 (first < last).then_some((first, last - 1))
756 }
757
758 fn reprefix(&mut self) {
760 let mut at = 0.0f32;
761 self.prefix.clear();
762 self.prefix.push(0.0);
763 for c in &self.chunks {
764 at += c
765 .width
766 .unwrap_or_else(|| (c.end - c.start) as f32 * self.avg);
767 self.prefix.push(at);
768 }
769 }
770}
771
772fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
786 use unicode_segmentation::UnicodeSegmentation;
787 if content.len() < LONG_LINE_BYTES {
788 return vec![(0, content.len())];
789 }
790 let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
791 let mut start = 0usize;
792 while start < content.len() {
793 let mut window_end = (start + CHUNK_BYTES).min(content.len());
797 while !content.is_char_boundary(window_end) {
798 window_end -= 1;
799 }
800 let end = if window_end == content.len() {
801 window_end
802 } else {
803 let window = &content[start..window_end];
804 let half = CHUNK_BYTES / 2;
805 let mut from = half.min(window.len());
809 while !window.is_char_boundary(from) {
810 from += 1;
811 }
812 let (mut tab, mut space) = (None, None);
813 for (i, c) in window[from..].char_indices() {
814 if c.is_whitespace() {
815 let end = from + i + c.len_utf8();
816 space = Some(end);
817 if c == '\t' {
818 tab = Some(end);
819 }
820 }
821 }
822 match tab.or(space) {
823 Some(i) => start + i,
824 None => {
825 let mut cut = 0usize;
827 for (i, _) in window.grapheme_indices(true) {
828 if i == 0 {
829 continue;
830 }
831 cut = i;
832 }
833 if cut == 0 { window_end } else { start + cut }
834 }
835 }
836 };
837 out.push((start, end));
838 start = end;
839 }
840 out
841}
842
843pub(crate) const PLACE_ANCESTORS: usize = 4;
848
849pub(crate) struct Ancestry {
853 pub(crate) keys: [Key; PLACE_ANCESTORS],
854 pub(crate) depth: usize,
855 pub(crate) none_at: Option<usize>,
856}
857
858pub(crate) struct TextPlace {
863 pub(crate) key: Key,
864 ancestors: [Key; PLACE_ANCESTORS],
866 depth: u8,
867 none_at: u8,
873 cache_key: u64,
874 origin: Vec2,
876 scope: Option<Key>,
880 drawn: bool,
888}
889
890impl TextPlace {
891 fn answers_to(&self, key: Key) -> bool {
892 if self.key == key {
893 return true;
894 }
895 self.ancestors[..self.depth as usize]
899 .iter()
900 .position(|k| *k == key)
901 .is_some_and(|d| d <= self.none_at as usize)
902 }
903}
904
905pub(crate) struct ScopeRun<'a> {
911 pub place: &'a TextPlace,
912 pub text: &'a Entry,
914 pub base: usize,
915}
916
917impl ScopeRun<'_> {
918 pub(crate) fn span(&self) -> (usize, usize) {
921 (self.base, self.base + self.text.content().len())
922 }
923}
924
925#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
938pub struct TextHit {
939 pub byte: usize,
940 pub line: u32,
941}
942
943impl TextHit {
944 pub fn to_value(self) -> Value {
946 Value::map([
947 ("byte", Value::Int(self.byte as i64)),
948 ("line", Value::Int(self.line as i64)),
949 ])
950 }
951}
952
953#[derive(Clone, Copy, Debug, Default, PartialEq)]
958pub struct TextMetrics {
959 pub width: f32,
960 pub height: f32,
961 pub lines: u32,
963}
964
965impl TextMetrics {
966 pub fn to_value(self) -> Value {
968 Value::map([
969 ("width", Value::float(self.width)),
970 ("height", Value::float(self.height)),
971 ("lines", Value::Int(self.lines as i64)),
972 ])
973 }
974}
975
976pub struct TextSystem {
984 raster: Raster,
985 joins: Vec<JoinBg>,
988 entries: FxHashMap<u64, Entry>,
991 bytes: usize,
994 budget: usize,
995 frame: Kept<FrameText>,
1000 places: Kept<TextPlace>,
1004 scale: f32,
1005 frame_no: u64,
1006 owed: bool,
1009}
1010
1011pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1014 let mut out = cosmic_text::FontFeatures::new();
1015 for (tag, value) in f.iter() {
1016 out.set(cosmic_text::FeatureTag::new(tag), value);
1017 }
1018 out
1019}
1020
1021#[cfg(target_os = "macos")]
1032const DEFAULT_FAMILIES: [&[&str]; 3] = [
1033 &["Helvetica Neue", "Helvetica"],
1034 &["Times New Roman", "Times"],
1035 &["SF Mono", "Menlo", "Monaco"],
1036];
1037#[cfg(target_os = "windows")]
1038const DEFAULT_FAMILIES: [&[&str]; 3] = [
1039 &["Segoe UI", "Arial"],
1040 &["Times New Roman"],
1041 &["Cascadia Mono", "Consolas", "Courier New"],
1042];
1043#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1044const DEFAULT_FAMILIES: [&[&str]; 3] = [
1045 &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1046 &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1047 &[
1048 "DejaVu Sans Mono",
1049 "Noto Sans Mono",
1050 "Liberation Mono",
1051 "Ubuntu Mono",
1052 ],
1053];
1054
1055fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1058 list.iter().copied().find(|name| {
1059 db.faces()
1060 .any(|face| face.families.iter().any(|(f, _)| f == name))
1061 })
1062}
1063
1064pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1078 let system = system_fonts();
1079 let fonts = font_system_over(system.locale.clone(), system.db.clone(), &[]);
1080 (fonts, system)
1081}
1082
1083pub(crate) fn font_system_over(
1088 locale: String,
1089 db: cosmic_text::fontdb::Database,
1090 first: &[String],
1091) -> FontSystem {
1092 FontSystem::new_with_locale_and_db_and_fallback(locale, db, AppFallback::new(first))
1093}
1094
1095pub(crate) struct AppFallback {
1102 platform: cosmic_text::PlatformFallback,
1103 first: &'static [&'static str],
1104 common: &'static [&'static str],
1105 scripts: std::sync::Mutex<
1108 rustc_hash::FxHashMap<(unicode_script::Script, String), &'static [&'static str]>,
1109 >,
1110}
1111
1112fn keep(names: Vec<&'static str>) -> &'static [&'static str] {
1115 static LISTS: std::sync::Mutex<Vec<&'static [&'static str]>> =
1116 std::sync::Mutex::new(Vec::new());
1117 let mut lists = LISTS.lock().unwrap_or_else(|e| e.into_inner());
1118 if let Some(&kept) = lists.iter().find(|l| ***l == names[..]) {
1119 return kept;
1120 }
1121 let kept: &'static [&'static str] = Box::leak(names.into_boxed_slice());
1122 lists.push(kept);
1123 kept
1124}
1125
1126fn keep_name(name: &str) -> &'static str {
1127 static NAMES: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
1128 let mut names = NAMES.lock().unwrap_or_else(|e| e.into_inner());
1129 if let Some(&kept) = names.iter().find(|n| **n == name) {
1130 return kept;
1131 }
1132 let kept: &'static str = Box::leak(name.to_string().into_boxed_str());
1133 names.push(kept);
1134 kept
1135}
1136
1137impl AppFallback {
1138 pub(crate) fn new(first: &[String]) -> Self {
1139 use cosmic_text::Fallback;
1140 let platform = cosmic_text::PlatformFallback;
1141 let first = keep(first.iter().map(|n| keep_name(n)).collect());
1142 let common = keep(
1143 first
1144 .iter()
1145 .chain(platform.common_fallback())
1146 .copied()
1147 .collect(),
1148 );
1149 Self {
1150 platform,
1151 first,
1152 common,
1153 scripts: Default::default(),
1154 }
1155 }
1156}
1157
1158impl cosmic_text::Fallback for AppFallback {
1159 fn common_fallback(&self) -> &[&'static str] {
1160 self.common
1161 }
1162
1163 fn forbidden_fallback(&self) -> &[&'static str] {
1164 self.platform.forbidden_fallback()
1165 }
1166
1167 fn script_fallback(&self, script: unicode_script::Script, locale: &str) -> &[&'static str] {
1168 let theirs = self.platform.script_fallback(script, locale);
1169 if self.first.is_empty() {
1170 return theirs;
1171 }
1172 let mut scripts = self.scripts.lock().unwrap_or_else(|e| e.into_inner());
1173 scripts
1174 .entry((script, locale.to_string()))
1175 .or_insert_with(|| keep(self.first.iter().chain(theirs).copied().collect()))
1176 }
1177}
1178
1179pub(crate) struct SystemFonts {
1183 locale: String,
1184 db: cosmic_text::fontdb::Database,
1185 pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1187}
1188
1189static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1191
1192pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1196 let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1197 held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1198 .clone()
1199}
1200
1201pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1204 let fresh = std::sync::Arc::new(scan_system_fonts());
1205 *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1206 fresh
1207}
1208
1209fn scan_system_fonts() -> SystemFonts {
1210 let (locale, db) = FontSystem::new().into_locale_and_db();
1211 SystemFonts::from_db(locale, db)
1212}
1213
1214impl SystemFonts {
1215 pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1218 let all: Vec<_> = db.faces().map(|face| face.id).collect();
1219 keep_measurable(&mut db, all);
1220 pin_default_families(&mut db);
1221 let faces = db.faces().filter_map(face_file).collect();
1222 Self { locale, db, faces }
1223 }
1224
1225 #[cfg(test)]
1227 pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1228 &self.db
1229 }
1230
1231 #[cfg(test)]
1232 pub(crate) fn locale(&self) -> &str {
1233 &self.locale
1234 }
1235}
1236
1237pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1240 use cosmic_text::fontdb::Source;
1241 match &face.source {
1242 Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1243 Source::Binary(_) => None,
1244 }
1245}
1246
1247pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1251 let [sans, serif, mono] = DEFAULT_FAMILIES;
1252 if let Some(name) = first_installed(db, sans) {
1253 db.set_sans_serif_family(name);
1254 }
1255 if let Some(name) = first_installed(db, serif) {
1256 db.set_serif_family(name);
1257 }
1258 if let Some(name) = first_installed(db, mono) {
1259 db.set_monospace_family(name);
1260 }
1261}
1262
1263#[cfg(test)]
1267fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1268 let system = SystemFonts::from_db(locale, db);
1269 FontSystem::new_with_locale_and_db(system.locale, system.db)
1270}
1271
1272pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1283 use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1284 let Ok(font) = FontRef::from_index(data, index) else {
1285 return false;
1286 };
1287 font.head()
1288 .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1289 && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1290 && font.hmtx().is_ok()
1291}
1292
1293const FILES_PER_CHECKER: usize = 64;
1296
1297pub(crate) fn keep_measurable(
1309 db: &mut cosmic_text::fontdb::Database,
1310 ids: Vec<cosmic_text::fontdb::ID>,
1311) -> Vec<cosmic_text::fontdb::ID> {
1312 use cosmic_text::fontdb::{ID, Source};
1313 let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1315 let mut groups = Vec::new();
1316 for &id in &ids {
1317 match db.face(id).map(|face| &face.source) {
1318 Some(Source::File(path) | Source::SharedFile(path, _)) => {
1319 by_file.entry(path).or_default().push(id);
1320 }
1321 Some(Source::Binary(_)) => groups.push(vec![id]),
1322 None => {}
1323 }
1324 }
1325 groups.extend(by_file.into_values());
1326 let db_ref = &*db;
1327 let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1328 let mut out = Vec::new();
1329 for group in groups {
1330 let read = db_ref.with_face_data(group[0], |data, _| {
1331 group
1332 .iter()
1333 .filter(|&&id| {
1334 db_ref
1335 .face(id)
1336 .is_none_or(|face| !measurable(data, face.index))
1337 })
1338 .copied()
1339 .collect::<Vec<_>>()
1340 });
1341 out.extend(read.unwrap_or_else(|| group.clone()));
1342 }
1343 out
1344 };
1345 let checkers = std::thread::available_parallelism()
1346 .map_or(1, |n| n.get())
1347 .min(groups.len().div_ceil(FILES_PER_CHECKER));
1348 let unmeasurable = if checkers <= 1 {
1349 unmeasurable_in(&groups)
1350 } else {
1351 let per = groups.len().div_ceil(checkers);
1352 std::thread::scope(|scope| {
1353 let parts: Vec<_> = groups
1354 .chunks(per)
1355 .map(|part| {
1356 let spawned = std::thread::Builder::new()
1357 .name("kui-font-check".into())
1358 .spawn_scoped(scope, move || unmeasurable_in(part));
1359 (part, spawned.ok())
1360 })
1361 .collect();
1362 let mut out = Vec::new();
1363 for (part, checker) in parts {
1364 out.extend(match checker {
1365 Some(checker) => checker
1366 .join()
1367 .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1368 None => unmeasurable_in(part),
1370 });
1371 }
1372 out
1373 })
1374 };
1375 for &id in &unmeasurable {
1376 db.remove_face(id);
1377 }
1378 ids.into_iter()
1379 .filter(|id| !unmeasurable.contains(id))
1380 .collect()
1381}
1382
1383pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1387 let db = fs.db();
1388 [
1389 db.family_name(&cosmic_text::Family::SansSerif),
1390 db.family_name(&cosmic_text::Family::Serif),
1391 db.family_name(&cosmic_text::Family::Monospace),
1392 ]
1393}
1394
1395impl TextSystem {
1396 pub fn new() -> Self {
1397 Self {
1398 raster: Raster::new(),
1399 joins: Vec::new(),
1400 entries: FxHashMap::default(),
1401 bytes: 0,
1402 budget: DEFAULT_TEXT_CACHE_BYTES,
1403 frame: Kept::default(),
1404 places: Kept::default(),
1405 scale: 1.0,
1406 frame_no: 0,
1407 owed: false,
1408 }
1409 }
1410
1411 pub(crate) fn take_owed(&mut self) -> bool {
1414 std::mem::take(&mut self.owed)
1415 }
1416
1417 pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1420 std::mem::take(&mut self.joins)
1421 }
1422
1423 pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1425 &mut self.raster
1426 }
1427
1428 pub(crate) fn subpixel(&self) -> bool {
1429 self.raster.subpixel
1430 }
1431
1432 pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1435 let changed = self.raster.subpixel != on;
1436 self.raster.subpixel = on;
1437 changed
1438 }
1439
1440 pub fn budget(&self) -> usize {
1443 self.budget
1444 }
1445
1446 pub fn set_budget(&mut self, bytes: usize) {
1449 self.budget = bytes;
1450 }
1451
1452 pub fn bytes(&self) -> usize {
1454 self.bytes
1455 }
1456
1457 pub fn len(&self) -> usize {
1460 self.entries.len() - self.long_lines()
1461 }
1462
1463 pub fn long_lines(&self) -> usize {
1465 self.entries.values().filter(|e| e.long().is_some()).count()
1466 }
1467
1468 pub fn is_empty(&self) -> bool {
1469 self.len() == 0
1470 }
1471
1472 fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1479 if self.bytes <= self.budget {
1480 return;
1481 }
1482 let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1483 let drawn_last_frame = self.frame_no.saturating_sub(1);
1484 let mut order: Vec<(u64, u64)> = self
1489 .entries
1490 .iter()
1491 .filter(|(_, e)| e.last_used() < drawn_last_frame)
1492 .map(|(k, e)| (e.last_used(), *k))
1493 .collect();
1494 order.sort_unstable();
1495 for (_, key) in order {
1496 if self.bytes <= floor {
1497 break;
1498 }
1499 let freed = self.entries.remove(&key).map(|e| e.bytes());
1500 self.bytes -= freed.unwrap_or(0);
1501 }
1502 fs.shape_run_cache.trim(0);
1511 }
1512
1513 pub(crate) fn begin_frame(
1517 &mut self,
1518 fs: &mut FontSystem,
1519 scale: f32,
1520 keep_prev: bool,
1521 frame_no: u64,
1522 ) {
1523 self.joins.clear();
1524 if (scale - self.scale).abs() > f32::EPSILON {
1526 self.entries.clear();
1527 self.bytes = 0;
1528 }
1529 self.scale = scale;
1530 self.frame.begin(keep_prev);
1531 self.places.begin(true);
1534 self.frame_no = frame_no;
1536 if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1537 let mut freed = 0usize;
1538 self.entries.retain(|_, e| {
1539 let keep = e.last_used() >= cutoff;
1540 if !keep {
1541 freed += e.bytes();
1542 }
1543 keep
1544 });
1545 self.bytes -= freed;
1546 fs.shape_run_cache.trim(2);
1549 }
1550 self.evict_to_budget(fs);
1554 }
1555
1556 pub(crate) fn forget_shaped(&mut self) {
1559 self.entries.clear();
1560 self.bytes = 0;
1561 }
1562
1563 fn insert(&mut self, key: u64, entry: Entry) {
1565 self.bytes += entry.bytes();
1566 if let Some(old) = self.entries.insert(key, entry) {
1567 self.bytes -= old.bytes();
1568 }
1569 }
1570
1571 fn run(&self, key: u64) -> Option<&CachedText> {
1573 self.entries.get(&key)?.run()
1574 }
1575
1576 fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1577 self.entries.get_mut(&key)?.run_mut()
1578 }
1579
1580 fn long(&self, key: u64) -> Option<&LongLine> {
1582 self.entries.get(&key)?.long()
1583 }
1584
1585 fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1586 self.entries.get_mut(&key)?.long_mut()
1587 }
1588
1589 pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1590 let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1593 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1594 mix(&style.size.to_bits().to_le_bytes());
1595 mix(&style.line_height.to_bits().to_le_bytes());
1596 mix(&scale.to_bits().to_le_bytes());
1597 mix(&[style.wrap as u8, style.ellipsis as u8]);
1598 mix(&style.max_lines.to_le_bytes());
1599 let (tag, font) = match style.family {
1600 FontFamily::Sans => (0u8, 0u64),
1601 FontFamily::Serif => (1, 0),
1602 FontFamily::Mono => (2, 0),
1603 FontFamily::Custom(id) => (3, id.to_ffi()),
1604 };
1605 mix(&[tag]);
1606 mix(&font.to_le_bytes());
1607 for (t, v) in style.features.iter() {
1608 mix(t);
1609 mix(&v.to_le_bytes());
1610 }
1611 mix(&[
1615 style.underline as u8,
1616 style.strikethrough as u8,
1617 style.underline_style as u8,
1618 style.underline_color.is_some() as u8,
1619 ]);
1620 if let Some(c) = style.underline_color {
1621 mix(&c.to_hex().to_le_bytes());
1622 }
1623 h
1624 }
1625
1626 fn intern(
1630 &mut self,
1631 content: &str,
1632 style: &TextStyle,
1633 res: &Resources,
1634 fs: &mut FontSystem,
1635 ) -> u64 {
1636 let key = Self::style_key(content, style, self.scale);
1637 self.intern_keyed(key, content, style, res, fs)
1638 }
1639
1640 fn intern_keyed(
1642 &mut self,
1643 key: u64,
1644 content: &str,
1645 style: &TextStyle,
1646 res: &Resources,
1647 fs: &mut FontSystem,
1648 ) -> u64 {
1649 let frame_no = self.frame_no;
1650 let scale = self.scale;
1651 if !self.entries.contains_key(&key) {
1652 let mut buffer = new_buffer(fs, style, scale);
1653 buffer.set_text(
1657 content,
1658 &res.weights_of(style.family).apply(
1659 Attrs::new()
1660 .family(res.family_of(style.family))
1661 .font_features(cosmic_features(&style.features)),
1662 false,
1663 ),
1664 Shaping::Advanced,
1665 None,
1666 );
1667 let entry = CachedText::new(
1668 buffer,
1669 content.to_string(),
1670 style,
1671 vec![SpanDeco::of_style(style)],
1672 fs,
1673 frame_no,
1674 );
1675 self.insert(key, Entry::Run(entry));
1676 }
1677 self.entries
1678 .get_mut(&key)
1679 .expect("just inserted")
1680 .touch(frame_no);
1681 key
1682 }
1683
1684 pub(crate) fn content(&self, id: TextId) -> &str {
1686 let key = self.frame[id.0 as usize].cache_key;
1687 self.entries.get(&key).map_or("", |e| e.content())
1688 }
1689
1690 pub(crate) fn access_runs(
1696 &self,
1697 id: TextId,
1698 src: crate::access::RunSource,
1699 run_no: &mut usize,
1700 out: &mut Vec<crate::access::AccessRun>,
1701 ) {
1702 use crate::access::{RowGlyphs, push_row_runs};
1703 let Some(ft) = self.frame.get(id.0 as usize) else {
1704 return;
1705 };
1706 let line = match self.entries.get(&ft.cache_key) {
1707 Some(Entry::Run(e)) => {
1708 crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1709 return;
1710 }
1711 Some(Entry::Long(line)) => line,
1712 None => return,
1713 };
1714 let whole = [RowStart { byte: 0, x: 0.0 }];
1715 for (i, c) in line.chunks.iter().enumerate() {
1716 let Some(run) = c
1717 .width
1718 .and_then(|_| self.run(c.key))
1719 .and_then(|e| e.buffer.layout_runs().next())
1720 else {
1721 continue;
1722 };
1723 let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1726 (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1727 (&c.rows[..], row0, head_x)
1728 }
1729 (Some(_), _) => continue,
1730 (None, _) => (&whole[..], 0, line.prefix[i]),
1731 };
1732 for (r, rs) in rows.iter().enumerate() {
1733 let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1734 let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1735 let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1736 if a == b {
1737 continue;
1738 }
1739 let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1740 push_row_runs(
1741 &RowGlyphs {
1742 glyphs: &run.glyphs[a..b],
1743 text: run.text,
1744 line: 0,
1745 top: (row0 as usize + r) as f32 * line.line_h,
1746 height: line.line_h,
1747 rtl: run.rtl,
1748 dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1749 base: c.start,
1750 newline: last && src.newline_after_last,
1751 },
1752 &src,
1753 run_no,
1754 out,
1755 );
1756 }
1757 }
1758 }
1759
1760 pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1766 match self.frame.prev().get(id.0 as usize) {
1767 Some(t) => (t.cache_key, t.color),
1768 None => (0, Color::TRANSPARENT),
1769 }
1770 }
1771
1772 pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1778 let frame_no = self.frame_no;
1779 let e = self.entries.get_mut(&cache_key)?;
1780 e.touch(frame_no);
1781 let cache_key = if e.long().is_some() {
1782 self.claim_long(cache_key)
1783 } else {
1784 cache_key
1785 };
1786 self.frame.push(FrameText { cache_key, color });
1787 Some(TextId((self.frame.len() - 1) as u32))
1788 }
1789
1790 pub fn add(
1792 &mut self,
1793 content: &str,
1794 style: &TextStyle,
1795 res: &Resources,
1796 fs: &mut FontSystem,
1797 ) -> TextId {
1798 let key = self.intern_any(content, style, res, fs);
1801 let key = if could_be_long(content.len(), style) {
1802 self.claim_long(key)
1803 } else {
1804 key
1805 };
1806 self.frame.push(FrameText {
1807 cache_key: key,
1808 color: style.color_or_default(),
1809 });
1810 TextId((self.frame.len() - 1) as u32)
1811 }
1812
1813 #[inline(never)]
1822 fn claim_long(&mut self, key: u64) -> u64 {
1823 let frame_no = self.frame_no;
1824 let mut at = key;
1825 let mut n = 0u64;
1826 loop {
1827 match self.entries.get_mut(&at) {
1828 Some(Entry::Long(l)) if l.claimed != frame_no => {
1829 l.claimed = frame_no;
1830 l.last_used = frame_no;
1831 return at;
1832 }
1833 Some(Entry::Run(_)) if n == 0 => return key,
1835 Some(_) => {}
1836 None => {
1837 let Some(line) = self.long(key) else {
1838 return key;
1839 };
1840 let mut copy = line.clone();
1841 copy.claimed = frame_no;
1842 copy.last_used = frame_no;
1843 self.insert(at, Entry::Long(copy));
1844 return at;
1845 }
1846 }
1847 n += 1;
1848 at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
1849 }
1850 }
1851
1852 fn intern_any(
1858 &mut self,
1859 content: &str,
1860 style: &TextStyle,
1861 res: &Resources,
1862 fs: &mut FontSystem,
1863 ) -> u64 {
1864 if !could_be_long(content.len(), style) {
1865 return self.intern(content, style, res, fs);
1866 }
1867 let key = Self::style_key(content, style, self.scale);
1868 let long_key = key ^ LONG_SALT;
1869 let frame_no = self.frame_no;
1870 if let Some(e) = self.entries.get_mut(&long_key) {
1871 e.touch(frame_no);
1872 return long_key;
1873 }
1874 if let Some(e) = self.entries.get_mut(&key) {
1875 e.touch(frame_no);
1876 return key;
1877 }
1878 if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
1879 self.intern_keyed(key, content, style, res, fs)
1880 } else {
1881 self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
1882 }
1883 }
1884
1885 fn intern_rich_any(
1890 &mut self,
1891 spans: &[Span<'_>],
1892 base: &TextStyle,
1893 res: &Resources,
1894 fs: &mut FontSystem,
1895 ) -> u64 {
1896 let len: usize = spans.iter().map(|s| s.text.len()).sum();
1897 if !could_be_long(len, base) {
1898 return self.intern_rich(spans, base, res, fs);
1899 }
1900 let key = Self::rich_key(spans, base, self.scale);
1901 let long_key = key ^ LONG_SALT;
1902 let frame_no = self.frame_no;
1903 if let Some(e) = self.entries.get_mut(&long_key) {
1904 e.touch(frame_no);
1905 return long_key;
1906 }
1907 if let Some(e) = self.entries.get_mut(&key) {
1908 e.touch(frame_no);
1909 return key;
1910 }
1911 if spans.iter().any(|s| has_line_break(s.text))
1912 || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
1913 {
1914 return self.intern_rich_keyed(key, spans, base, res, fs);
1915 }
1916 let mut content = String::with_capacity(len);
1917 let mut owned = Vec::with_capacity(spans.len());
1918 for s in spans {
1919 let start = content.len();
1920 content.push_str(s.text);
1921 owned.push(OwnedSpan {
1922 start,
1923 end: content.len(),
1924 attrs: s.attrs_only(),
1925 });
1926 }
1927 self.build_long(long_key, content, owned, base, res, fs)
1928 }
1929
1930 fn build_long(
1935 &mut self,
1936 key: u64,
1937 content: String,
1938 spans: Vec<OwnedSpan>,
1939 style: &TextStyle,
1940 res: &Resources,
1941 fs: &mut FontSystem,
1942 ) -> u64 {
1943 let frame_no = self.frame_no;
1944 let wrap = style.wrap;
1945 let style = &TextStyle {
1946 wrap: TextWrap::None,
1947 ..*style
1948 };
1949 let scale = self.scale;
1950 let chunks: Vec<Chunk> = chunk_ranges(&content)
1951 .into_iter()
1952 .map(|(start, end)| Chunk {
1953 start,
1954 end,
1955 key: if spans.is_empty() {
1956 Self::style_key(&content[start..end], style, scale)
1957 } else {
1958 Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
1959 },
1960 width: None,
1961 rows: Vec::new(),
1962 })
1963 .collect();
1964 let (w0, line_h) = match chunks.first() {
1967 Some(c) => {
1968 let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
1969 let e = self.run(k).expect("just interned");
1970 (e.intrinsic.w, e.buffer.metrics().line_height)
1971 }
1972 None => (0.0, style.line_height * self.scale),
1973 };
1974 let avg = chunks
1975 .first()
1976 .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
1977 let mut line = LongLine {
1978 content,
1979 spans,
1980 style: *style,
1981 wrap,
1982 wrap_w: None,
1983 starts: Vec::new(),
1984 chunks,
1985 prefix: Vec::new(),
1986 line_h,
1987 avg,
1988 last_used: frame_no,
1989 claimed: u64::MAX,
1990 laid_rows: 1,
1991 asked_rows: u32::MAX,
1992 bytes: 0,
1993 };
1994 if let Some(c) = line.chunks.first_mut() {
1995 c.width = Some(w0);
1996 }
1997 line.reprefix();
1998 line.bytes = ENTRY_BASE_BYTES
1999 + line.content.len()
2000 + line.chunks.len() * 48
2001 + line.spans.len() * std::mem::size_of::<OwnedSpan>();
2002 self.insert(key, Entry::Long(line));
2006 key
2007 }
2008
2009 #[allow(clippy::too_many_arguments)]
2013 fn shape_chunk(
2014 &mut self,
2015 content: &str,
2016 spans: &[OwnedSpan],
2017 start: usize,
2018 end: usize,
2019 style: &TextStyle,
2020 res: &Resources,
2021 fs: &mut FontSystem,
2022 ) -> u64 {
2023 if spans.is_empty() {
2024 self.intern(&content[start..end], style, res, fs)
2025 } else {
2026 let spans = chunk_spans(content, spans, start, end);
2027 self.intern_rich(&spans, style, res, fs)
2028 }
2029 }
2030
2031 fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
2035 let (chunk_key, known) = {
2036 let c = &self.long(key).expect("a long line").chunks[i];
2037 (c.key, c.width)
2038 };
2039 let frame_no = self.frame_no;
2040 if known.is_some()
2041 && let Some(e) = self.run_mut(chunk_key)
2042 {
2043 e.last_used = frame_no;
2044 return false;
2045 }
2046 let (text, spans, style) = {
2049 let line = self.long(key).expect("a long line");
2050 let c = &line.chunks[i];
2051 let first = line.spans.partition_point(|s| s.end <= c.start);
2052 let spans: Vec<OwnedSpan> = line.spans[first..]
2053 .iter()
2054 .take_while(|s| s.start < c.end)
2055 .map(|s| OwnedSpan {
2056 start: s.start.max(c.start) - c.start,
2057 end: s.end.min(c.end) - c.start,
2058 attrs: s.attrs,
2059 })
2060 .collect();
2061 (line.content[c.start..c.end].to_string(), spans, line.style)
2062 };
2063 let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
2064 let w = self.run(k).expect("just interned").intrinsic.w;
2065 let line = self.long_mut(key).expect("just read");
2066 line.chunks[i].key = k;
2067 if line.chunks[i].width != Some(w) {
2068 line.chunks[i].width = Some(w);
2069 line.reprefix();
2070 }
2071 true
2072 }
2073
2074 fn relayout_long(&mut self, key: u64, w: Option<f32>) {
2082 let Some(w) = w else {
2083 let line = self.long_mut(key).expect("a long line");
2084 line.wrap_w = None;
2085 line.starts.clear();
2086 for c in &mut line.chunks {
2087 c.rows.clear();
2088 }
2089 return;
2090 };
2091 let w = w.max(1.0);
2092 let line = self.long(key).expect("a long line");
2095 let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2096 starts.push((0, 0.0));
2097 let (mut row, mut x) = (0u32, 0.0f32);
2098 let rows: Vec<Vec<RowStart>> = line
2099 .chunks
2100 .iter()
2101 .map(|c| {
2102 let shaped = c.width.and_then(|_| self.run(c.key));
2103 let rows = match shaped {
2104 Some(e) => {
2105 let text = &line.content[c.start..c.end];
2106 let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2107 row += rows.len() as u32 - 1;
2108 x = end;
2109 rows
2110 }
2111 None => {
2112 let est = x + c
2113 .width
2114 .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2115 let added = (est / w).floor();
2116 row += added as u32;
2117 x = est - added * w;
2118 Vec::new()
2119 }
2120 };
2121 starts.push((row, x));
2122 rows
2123 })
2124 .collect();
2125 let line = self.long_mut(key).expect("a long line");
2126 line.wrap_w = Some(w);
2127 line.starts = starts;
2128 for (c, rows) in line.chunks.iter_mut().zip(rows) {
2129 c.rows = rows;
2130 }
2131 }
2132
2133 fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2137 let (wrap, width, line_h, cur) = {
2138 let l = self.long(key).expect("a long line");
2139 (l.wrap, l.width(), l.line_h, l.wrap_w)
2140 };
2141 let target = match max_w {
2142 Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2143 _ => None,
2144 };
2145 let differs = match (cur, target) {
2146 (None, None) => false,
2147 (Some(a), Some(b)) => (a - b).abs() > 0.5,
2148 _ => true,
2149 };
2150 if differs {
2151 self.relayout_long(key, target);
2152 }
2153 match target {
2154 Some(w) => {
2155 let rows = self.long(key).expect("a long line").rows();
2156 (Size::new(w, rows as f32 * line_h), rows)
2157 }
2158 None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2159 }
2160 }
2161
2162 pub fn measure(
2168 &mut self,
2169 content: &str,
2170 style: &TextStyle,
2171 res: &Resources,
2172 fs: &mut FontSystem,
2173 max_w: Option<f32>,
2174 ) -> TextMetrics {
2175 let key = self.intern_any(content, style, res, fs);
2176 self.measure_any(key, fs, max_w)
2177 }
2178
2179 fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2181 if self.long(key).is_some() {
2182 let scale = self.scale;
2183 let (size, lines) = match max_w {
2186 Some(m) => {
2187 let held = self.long(key).expect("checked").wrap_w;
2190 let measured = self.long_size(key, Some(m * scale));
2191 if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2192 self.relayout_long(key, held);
2193 }
2194 measured
2195 }
2196 None => {
2197 let line = self.long(key).expect("checked");
2198 (Size::new(line.width(), line.line_h), 1)
2199 }
2200 };
2201 return TextMetrics {
2202 width: size.w / scale,
2203 height: size.h / scale,
2204 lines,
2205 };
2206 }
2207 self.measure_key(key, fs, max_w)
2208 }
2209
2210 pub fn measure_rich(
2212 &mut self,
2213 spans: &[Span<'_>],
2214 base: &TextStyle,
2215 res: &Resources,
2216 fs: &mut FontSystem,
2217 max_w: Option<f32>,
2218 ) -> TextMetrics {
2219 let key = self.intern_rich_any(spans, base, res, fs);
2220 self.measure_any(key, fs, max_w)
2221 }
2222
2223 fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2224 let scale = self.scale;
2225 let entry = self.run_mut(key).expect("just interned");
2226 let target = wrap_target(entry, max_w, scale);
2227 wrap_entry(entry, fs, target);
2228 let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2229 if entry.clamp_w
2230 && let Some(t) = target
2231 {
2232 m.w = m.w.min(t);
2233 }
2234 TextMetrics {
2235 width: m.w / scale,
2236 height: m.h / scale,
2237 lines,
2238 }
2239 }
2240
2241 pub fn add_rich(
2245 &mut self,
2246 spans: &[Span<'_>],
2247 base: &TextStyle,
2248 res: &Resources,
2249 fs: &mut FontSystem,
2250 ) -> TextId {
2251 let key = self.intern_rich_any(spans, base, res, fs);
2252 let len = spans.iter().map(|s| s.text.len()).sum();
2253 let key = if could_be_long(len, base) {
2254 self.claim_long(key)
2255 } else {
2256 key
2257 };
2258 self.frame.push(FrameText {
2259 cache_key: key,
2260 color: base.color_or_default(),
2261 });
2262 TextId((self.frame.len() - 1) as u32)
2263 }
2264
2265 fn intern_rich(
2266 &mut self,
2267 spans: &[Span<'_>],
2268 base: &TextStyle,
2269 res: &Resources,
2270 fs: &mut FontSystem,
2271 ) -> u64 {
2272 let key = Self::rich_key(spans, base, self.scale);
2273 self.intern_rich_keyed(key, spans, base, res, fs)
2274 }
2275
2276 fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2280 let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2281 for s in spans {
2282 key = crate::key::mix_content(key, s.text.as_bytes());
2283 let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2284 mix(&[
2285 s.bold as u8,
2286 s.italic as u8,
2287 s.color.is_some() as u8,
2288 s.underline as u8,
2289 s.strikethrough as u8,
2290 s.bg.is_some() as u8,
2291 s.underline_style as u8,
2292 s.underline_color.is_some() as u8,
2293 ]);
2294 mix(&s.bg_radius.to_bits().to_le_bytes());
2295 for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2296 mix(&c.r.to_bits().to_le_bytes());
2297 mix(&c.g.to_bits().to_le_bytes());
2298 mix(&c.b.to_bits().to_le_bytes());
2299 mix(&c.a.to_bits().to_le_bytes());
2300 }
2301 }
2302 key
2303 }
2304
2305 fn intern_rich_keyed(
2307 &mut self,
2308 key: u64,
2309 spans: &[Span<'_>],
2310 base: &TextStyle,
2311 res: &Resources,
2312 fs: &mut FontSystem,
2313 ) -> u64 {
2314 let frame_no = self.frame_no;
2315 let scale = self.scale;
2316 if !self.entries.contains_key(&key) {
2317 let mut buffer = new_buffer(fs, base, scale);
2318 let family = res.family_of(base.family);
2319 let weights = res.weights_of(base.family);
2320 let features = cosmic_features(&base.features);
2321 buffer.set_rich_text(
2324 spans.iter().enumerate().map(|(i, s)| {
2325 (
2326 s.text,
2327 s.attrs(family, weights)
2328 .font_features(features.clone())
2329 .metadata(i),
2330 )
2331 }),
2332 &weights.apply(
2333 Attrs::new().family(family).font_features(features.clone()),
2334 false,
2335 ),
2336 Shaping::Advanced,
2337 None,
2338 );
2339 let content = spans.iter().map(|s| s.text).collect::<String>();
2340 let decos = spans
2341 .iter()
2342 .map(|s| SpanDeco {
2343 underline: s.underline || base.underline,
2344 underline_color: s.underline_color.or(base.underline_color),
2346 underline_style: if s.underline {
2347 s.underline_style
2348 } else {
2349 base.underline_style
2350 },
2351 strikethrough: s.strikethrough || base.strikethrough,
2352 bg: s.bg,
2353 bg_radius: s.bg_radius,
2354 })
2355 .collect();
2356 let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2357 self.insert(key, Entry::Run(entry));
2358 }
2359 self.entries
2360 .get_mut(&key)
2361 .expect("just inserted")
2362 .touch(frame_no);
2363 key
2364 }
2365
2366 fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2369 let key = self.frame[id.0 as usize].cache_key;
2370 self.run_mut(key).expect("frame text missing from cache")
2371 }
2372
2373 fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2378 let (scale, frame_no) = (self.scale, self.frame_no);
2379 let entry = self.entry_mut(id);
2380 let target = wrap_target(entry, Some(max_w_logical), scale);
2381 if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2382 self.own_wrap(id, target, fs);
2383 return;
2384 }
2385 entry.claimed = frame_no;
2386 entry.claimed_wrap = target;
2387 wrap_entry(entry, fs, target);
2388 }
2389
2390 #[inline(never)]
2398 fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2399 let frame_no = self.frame_no;
2400 let key = self.frame[id.0 as usize].cache_key;
2401 let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2403 let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2404 if self.run(copy).is_none() {
2405 let fork = self.run(key).expect("frame text").fork(frame_no);
2406 self.insert(copy, Entry::Run(fork));
2407 }
2408 self.frame[id.0 as usize].cache_key = copy;
2409 let entry = self.run_mut(copy).expect("just made");
2410 entry.last_used = frame_no;
2411 entry.claimed = frame_no;
2412 entry.claimed_wrap = target;
2413 wrap_entry(entry, fs, target);
2414 }
2415
2416 #[allow(clippy::too_many_arguments)]
2421 pub(crate) fn emit(
2422 &mut self,
2423 id: TextId,
2424 origin: Vec2,
2425 node: Size,
2426 clip: Clip,
2427 clip_id: ClipId,
2428 clips: &mut Vec<Clip>,
2429 res: &Resources,
2430 fs: &mut FontSystem,
2431 atlas: &mut GlyphAtlas,
2432 out: &mut Vec<Quad>,
2433 sel: Option<((usize, usize), Color)>,
2434 ) {
2435 let from = self.joins.len();
2436 self.emit_text(
2437 id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2438 );
2439 if self.joins.len() == from {
2440 return;
2441 }
2442 let key = self.frame[id.0 as usize].cache_key;
2445 let clamps = match self.entries.get(&key) {
2446 Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2447 Some(Entry::Run(e)) => e.clamp_w,
2448 None => false,
2449 };
2450 let own = clamps.then(|| {
2451 let ox = crate::geom::snap_px(origin.x * self.scale);
2452 (ox, ox + (node.w * self.scale).ceil())
2453 });
2454 for j in &mut self.joins[from..] {
2455 j.outer = clip_id;
2456 j.own = own;
2457 }
2458 }
2459
2460 #[allow(clippy::too_many_arguments)]
2466 #[inline(never)]
2467 fn emit_long(
2468 &mut self,
2469 key: u64,
2470 node: Size,
2471 ox: f32,
2472 oy: f32,
2473 nudge: Vec2,
2474 color: Color,
2475 clip: Clip,
2476 clip_id: ClipId,
2477 clips: &mut Vec<Clip>,
2478 res: &Resources,
2479 fs: &mut FontSystem,
2480 atlas: &mut GlyphAtlas,
2481 out: &mut Vec<Quad>,
2482 sel: Option<((usize, usize), Color)>,
2483 ) {
2484 let scale = self.scale;
2485 let line = self.long(key).expect("a long line");
2486 let (clip, clip_id) = if line.wrap == TextWrap::None {
2492 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2493 let clip = clip.intersect(own, crate::display::SQUARE);
2494 if clip.rect.w <= 0.0 || clip.rect.h <= 0.0 {
2495 return;
2496 }
2497 (clip, crate::display::intern_clip(clips, clip))
2498 } else {
2499 (clip, clip_id)
2500 };
2501 if let Some(((from, to), tint)) = sel {
2502 let rects = self.long_highlight(line, from, to);
2503 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2504 }
2505 if let Some(w) = line.wrap_w {
2506 self.emit_long_rows(
2507 key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2508 );
2509 return;
2510 }
2511 let (first, last) = {
2512 if line.chunks.is_empty() {
2513 return;
2514 }
2515 let a = line.chunk_at(clip.rect.x - ox).saturating_sub(1);
2516 let b = (line.chunk_at(clip.rect.x + clip.rect.w - ox) + 1).min(line.chunks.len() - 1);
2517 (a, b)
2518 };
2519 for i in first..=last {
2520 self.ensure_chunk(key, i, res, fs);
2521 }
2522 let frame_no = self.frame_no;
2523 self.long_mut(key).expect("checked").last_used = frame_no;
2524 for i in first..=last {
2525 let (chunk_key, x) = {
2526 let line = self.long(key).expect("checked");
2527 (line.chunks[i].key, line.prefix[i])
2528 };
2529 let raster = &mut self.raster;
2530 let entry = self
2531 .entries
2532 .get_mut(&chunk_key)
2533 .and_then(Entry::run_mut)
2534 .expect("just ensured");
2535 emit_entry(
2536 entry,
2537 ox + x,
2538 oy,
2539 nudge,
2540 color,
2541 clip,
2542 clip_id,
2543 raster,
2544 fs,
2545 atlas,
2546 out,
2547 &mut self.joins,
2548 );
2549 }
2550 }
2551
2552 #[allow(clippy::too_many_arguments)]
2554 fn emit_text(
2555 &mut self,
2556 id: TextId,
2557 origin: Vec2,
2558 node: Size,
2559 clip: Clip,
2560 clip_id: ClipId,
2561 clips: &mut Vec<Clip>,
2562 res: &Resources,
2563 fs: &mut FontSystem,
2564 atlas: &mut GlyphAtlas,
2565 out: &mut Vec<Quad>,
2566 sel: Option<((usize, usize), Color)>,
2571 ) {
2572 let FrameText {
2573 cache_key: key,
2574 color,
2575 } = self.frame[id.0 as usize];
2576 let scale = self.scale;
2577 let ox = crate::geom::snap_px(origin.x * scale);
2578 let oy = crate::geom::snap_px(origin.y * scale);
2579 let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2582 if self.long(key).is_some() {
2585 self.emit_long(
2586 key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2587 );
2588 return;
2589 }
2590 self.ensure_wrap(id, node.w, fs);
2591 let raster = &mut self.raster;
2592 let entry = self
2593 .entries
2594 .get_mut(&key)
2595 .and_then(Entry::run_mut)
2596 .expect("frame text missing from cache");
2597
2598 let (clip, clip_id) = if entry.clamp_w {
2601 let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2602 let clip = clip.intersect(own, crate::display::SQUARE);
2603 (clip, crate::display::intern_clip(clips, clip))
2604 } else {
2605 (clip, clip_id)
2606 };
2607 if let Some(((from, to), tint)) = sel {
2608 let rects = Self::run_highlight(entry, from, to);
2609 push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2610 }
2611 emit_entry(
2612 entry,
2613 ox,
2614 oy,
2615 nudge,
2616 color,
2617 clip,
2618 clip_id,
2619 raster,
2620 fs,
2621 atlas,
2622 out,
2623 &mut self.joins,
2624 );
2625 }
2626}
2627
2628fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2642 Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2643}
2644
2645fn push_highlight(
2648 rects: &[Rect],
2649 ox: f32,
2650 oy: f32,
2651 nudge: Vec2,
2652 tint: Color,
2653 clip: ClipId,
2654 out: &mut Vec<Quad>,
2655) {
2656 for r in rects {
2657 out.push(Quad {
2658 rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2659 color: tint,
2660 border_color: Color::TRANSPARENT,
2661 radius: [0.0; 4],
2662 border_w: 0.0,
2663 blur: 0.0,
2664 kind: QuadKind::Solid,
2665 uv: [0; 4],
2666 clip,
2667 });
2668 }
2669}
2670
2671impl TextSystem {
2672 #[allow(clippy::too_many_arguments)]
2676 fn emit_long_rows(
2677 &mut self,
2678 key: u64,
2679 w: f32,
2680 ox: f32,
2681 oy: f32,
2682 nudge: Vec2,
2683 color: Color,
2684 clip: Clip,
2685 clip_id: ClipId,
2686 res: &Resources,
2687 fs: &mut FontSystem,
2688 atlas: &mut GlyphAtlas,
2689 out: &mut Vec<Quad>,
2690 ) {
2691 let (first, last) = {
2692 let line = self.long(key).expect("a long line");
2693 let ra = ((clip.rect.y - oy) / line.line_h).floor().max(0.0) as u32;
2694 let rb = ((clip.rect.y + clip.rect.h - oy) / line.line_h)
2695 .floor()
2696 .max(0.0) as u32;
2697 let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
2698 return;
2699 };
2700 (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
2701 };
2702 let mut fresh = false;
2703 for i in first..=last {
2704 fresh |= self.ensure_chunk(key, i, res, fs);
2705 }
2706 if fresh {
2707 self.relayout_long(key, Some(w));
2710 let line = self.long_mut(key).expect("checked");
2711 let rows = line.rows();
2712 if rows != line.laid_rows && rows != line.asked_rows {
2713 line.asked_rows = rows;
2714 self.owed = true;
2715 }
2716 }
2717 let frame_no = self.frame_no;
2718 self.long_mut(key).expect("checked").last_used = frame_no;
2719 for i in first..=last {
2720 let chunk_key = self.long(key).expect("checked").chunks[i].key;
2721 let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
2725 self.entries.get_disjoint_mut([&key, &chunk_key])
2726 else {
2727 continue;
2728 };
2729 let (row0, head_x) = line.starts[i];
2730 let chunk = &line.chunks[i];
2731 if chunk.rows.is_empty() {
2732 continue;
2733 }
2734 let raster = &mut self.raster;
2735 emit_entry_rows(
2736 entry,
2737 ox,
2738 oy + row0 as f32 * line.line_h,
2739 nudge,
2740 head_x,
2741 &chunk.rows,
2742 line.line_h,
2743 color,
2744 clip,
2745 clip_id,
2746 raster,
2747 fs,
2748 atlas,
2749 out,
2750 &mut self.joins,
2751 );
2752 }
2753 }
2754}
2755
2756#[allow(clippy::too_many_arguments)]
2761fn emit_entry_rows(
2762 entry: &mut CachedText,
2763 ox: f32,
2764 oy: f32,
2765 nudge: Vec2,
2766 head_x: f32,
2767 rows: &[RowStart],
2768 line_h: f32,
2769 color: Color,
2770 clip: Clip,
2771 clip_id: ClipId,
2772 raster: &mut Raster,
2773 fs: &mut FontSystem,
2774 atlas: &mut GlyphAtlas,
2775 out: &mut Vec<Quad>,
2776 joins: &mut Vec<JoinBg>,
2777) {
2778 build_templates(entry, raster, fs, atlas);
2779 let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
2785 let (x0, x1) = (d.x, d.x + d.w);
2786 for r in 0..rows.len() {
2787 let ra = rows[r].x;
2788 let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
2789 let (a, b) = (x0.max(ra), x1.min(rb));
2790 if b <= a {
2791 continue;
2792 }
2793 let dx = if r == 0 { head_x } else { 0.0 } - ra;
2794 let x = ox + a + dx;
2795 let y = oy + d.y + r as f32 * line_h;
2796 if y >= clip.rect.y + clip.rect.h {
2797 break;
2798 }
2799 if y + d.h <= clip.rect.y
2800 || x >= clip.rect.x + clip.rect.w
2801 || x + (b - a) <= clip.rect.x
2802 {
2803 continue;
2804 }
2805 let quad = Quad {
2806 rect: if d.under {
2807 on_pixels(x, y, b - a, d.h, nudge)
2808 } else {
2809 Rect::new(x, y, b - a, d.h)
2810 },
2811 color: d.color.unwrap_or(color),
2812 border_color: Color::TRANSPARENT,
2813 radius: [0.0; 4],
2814 border_w: 0.0,
2815 blur: 0.0,
2816 kind: QuadKind::Solid,
2817 clip: clip_id,
2818 uv: [0; 4],
2819 };
2820 if d.under && d.radius > 0.0 {
2824 joins.push(JoinBg {
2825 quad: out.len() as u32,
2826 radius: d.radius,
2827 outer: clip_id,
2828 own: None,
2829 });
2830 }
2831 if d.under || d.style == UnderlineStyle::Solid {
2832 out.push(quad);
2833 } else {
2834 crate::deco::push_line(
2835 out,
2836 d.style,
2837 x,
2838 y,
2839 b - a,
2840 d.h,
2841 d.color.unwrap_or(color),
2842 clip_id,
2843 );
2844 }
2845 }
2846 };
2847 for d in entry.deco.iter().filter(|d| d.under) {
2848 row_pieces(d, out);
2849 }
2850 let mut r = 0usize;
2851 for g in &entry.glyphs {
2852 while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
2853 r += 1;
2854 }
2855 let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
2856 let x = ox + g.x + dx;
2857 let y = oy + g.y + r as f32 * line_h;
2858 if y >= clip.rect.y + clip.rect.h {
2865 break;
2866 }
2867 if y + g.h <= clip.rect.y || x >= clip.rect.x + clip.rect.w || x + g.w <= clip.rect.x {
2868 continue;
2869 }
2870 out.push(Quad {
2871 rect: Rect::new(x, y, g.w, g.h),
2872 color: g.color.unwrap_or(color),
2873 border_color: Color::TRANSPARENT,
2874 radius: [0.0; 4],
2875 border_w: 0.0,
2876 blur: 0.0,
2877 kind: g.kind,
2878 clip: clip_id,
2879 uv: g.uv,
2880 });
2881 }
2882 for d in entry.deco.iter().filter(|d| !d.under) {
2883 row_pieces(d, out);
2884 }
2885}
2886
2887fn break_rows(
2900 buffer: &Buffer,
2901 text: &str,
2902 wrap: TextWrap,
2903 w: f32,
2904 head_x: f32,
2905) -> (Vec<RowStart>, f32) {
2906 let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
2907 let Some(run) = buffer.layout_runs().next() else {
2908 return (rows, head_x);
2909 };
2910 let glyphs = run.glyphs;
2911 let blank = |g: &cosmic_text::LayoutGlyph| {
2912 text.get(g.start..g.end)
2913 .is_some_and(|s| s.chars().all(char::is_whitespace))
2914 };
2915 let spaces = wrap == TextWrap::BreakSpaces;
2916 let mut pieces: Vec<(usize, usize)> = Vec::new();
2917 match wrap {
2918 TextWrap::Word | TextWrap::BreakSpaces => {
2919 let mut at = 0usize;
2920 let mut piece = |from: usize, to: usize| {
2921 if !spaces {
2922 pieces.push((from, to));
2923 return;
2924 }
2925 let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
2928 if ink > 0 {
2929 pieces.push((from, from + ink));
2930 }
2931 for (i, c) in text[from + ink..to].char_indices() {
2932 let at = from + ink + i;
2933 pieces.push((at, at + c.len_utf8()));
2934 }
2935 };
2936 for (i, _) in unicode_linebreak::linebreaks(text) {
2937 if i > at {
2938 piece(at, i);
2939 at = i;
2940 }
2941 }
2942 if at < text.len() {
2943 piece(at, text.len());
2944 }
2945 }
2946 TextWrap::Glyph | TextWrap::None => {
2947 pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
2948 }
2949 }
2950 let mut row_x0 = 0.0f32;
2951 let mut avail = w - head_x;
2952 let mut gi = 0usize;
2953 for (s, e) in pieces {
2954 let g0 = gi;
2955 while gi < glyphs.len() && glyphs[gi].start < e {
2956 gi += 1;
2957 }
2958 if g0 == gi {
2959 continue;
2960 }
2961 let piece = &glyphs[g0..gi];
2962 let x0 = piece[0].x;
2963 let ink_end = piece
2964 .iter()
2965 .rev()
2966 .find(|g| spaces || !blank(g))
2967 .map_or(x0, |g| g.x + g.w);
2968 let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
2974 if ink_end - row_x0 > avail && opens {
2975 rows.push(RowStart {
2976 byte: s as u32,
2977 x: x0,
2978 });
2979 row_x0 = x0;
2980 avail = w;
2981 }
2982 if ink_end - row_x0 > avail {
2983 for g in piece {
2985 if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
2986 rows.push(RowStart {
2987 byte: g.start as u32,
2988 x: g.x,
2989 });
2990 row_x0 = g.x;
2991 avail = w;
2992 }
2993 }
2994 }
2995 }
2996 let end = if rows.len() == 1 {
2997 head_x + run.line_w
2998 } else {
2999 run.line_w - row_x0
3000 };
3001 (rows, end)
3002}
3003
3004fn build_templates(
3007 entry: &mut CachedText,
3008 raster: &mut Raster,
3009 fs: &mut FontSystem,
3010 atlas: &mut GlyphAtlas,
3011) {
3012 {
3013 let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
3015 if entry.glyphs_built_for != Some(built_for) {
3016 entry.glyphs.clear();
3017 entry.deco.clear();
3018 let lines = line_cap(entry.max_lines);
3019 let decorated = entry.span_deco.iter().any(SpanDeco::any);
3020 for run in entry.buffer.layout_runs().take(lines) {
3021 if decorated {
3022 build_decorations(&run, &entry.span_deco, fs, &mut entry.deco);
3023 }
3024 for glyph in run.glyphs.iter() {
3025 let physical = glyph.physical((0.0, 0.0), 1.0);
3026 let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
3027 continue;
3028 };
3029 entry.glyphs.push(GlyphTemplate {
3030 x: physical.x as f32 + slot.left as f32,
3031 y: run.line_y.round() + physical.y as f32 - slot.top as f32,
3032 w: slot.w as f32,
3033 h: slot.h as f32,
3034 uv: [slot.x, slot.y, slot.w, slot.h],
3035 kind: glyph_kind(&slot),
3036 color: glyph
3037 .color_opt
3038 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3039 byte: glyph.start as u32,
3040 });
3041 }
3042 }
3043 entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
3047 }
3048 }
3049}
3050
3051#[allow(clippy::too_many_arguments)]
3055fn emit_entry(
3056 entry: &mut CachedText,
3057 ox: f32,
3058 oy: f32,
3059 nudge: Vec2,
3060 color: Color,
3061 clip: Clip,
3062 clip_id: ClipId,
3063 raster: &mut Raster,
3064 fs: &mut FontSystem,
3065 atlas: &mut GlyphAtlas,
3066 out: &mut Vec<Quad>,
3067 joins: &mut Vec<JoinBg>,
3068) {
3069 build_templates(entry, raster, fs, atlas);
3070 {
3071 let inside = |x: f32, y: f32, w: f32, h: f32| {
3072 oy + y + h >= clip.rect.y
3073 && oy + y <= clip.rect.y + clip.rect.h
3074 && ox + x < clip.rect.x + clip.rect.w
3075 && ox + x + w > clip.rect.x
3076 };
3077 let deco_quad = |d: &DecoTemplate| Quad {
3078 rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
3079 color: d.color.unwrap_or(color),
3080 border_color: Color::TRANSPARENT,
3081 radius: [0.0; 4],
3082 border_w: 0.0,
3083 blur: 0.0,
3084 kind: QuadKind::Solid,
3085 clip: clip_id,
3086 uv: [0; 4],
3087 };
3088 for d in entry
3093 .deco
3094 .iter()
3095 .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3096 {
3097 if d.radius > 0.0 {
3098 joins.push(JoinBg {
3099 quad: out.len() as u32,
3100 radius: d.radius,
3101 outer: clip_id,
3102 own: None,
3103 });
3104 }
3105 out.push(Quad {
3106 rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3107 ..deco_quad(d)
3108 });
3109 }
3110
3111 out.extend(
3115 entry
3116 .glyphs
3117 .iter()
3118 .filter(|g| {
3119 oy + g.y + g.h >= clip.rect.y
3120 && ox + g.x < clip.rect.x + clip.rect.w
3121 && ox + g.x + g.w > clip.rect.x
3122 })
3123 .take_while(|g| oy + g.y <= clip.rect.y + clip.rect.h)
3124 .map(|g| Quad {
3125 rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3126 color: g.color.unwrap_or(color),
3127 border_color: Color::TRANSPARENT,
3128 radius: [0.0; 4],
3129 border_w: 0.0,
3130 blur: 0.0,
3131 kind: g.kind,
3132 clip: clip_id,
3133 uv: g.uv,
3134 }),
3135 );
3136 for d in entry
3137 .deco
3138 .iter()
3139 .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3140 {
3141 match d.style {
3144 UnderlineStyle::Solid => out.push(deco_quad(d)),
3145 style => crate::deco::push_line(
3146 out,
3147 style,
3148 ox + d.x,
3149 oy + d.y,
3150 d.w,
3151 d.h,
3152 d.color.unwrap_or(color),
3153 clip_id,
3154 ),
3155 }
3156 }
3157 }
3158}
3159
3160fn could_be_long(len: usize, style: &TextStyle) -> bool {
3168 (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3169 && style.max_lines == 0
3170 && !style.ellipsis
3171}
3172
3173fn has_line_break(content: &str) -> bool {
3176 content.bytes().any(|b| b == b'\n' || b == b'\r')
3177}
3178
3179fn has_rtl(content: &str) -> bool {
3187 !content.is_ascii()
3188 && content.chars().any(|c| {
3189 matches!(c as u32,
3190 0x0590..=0x08FF
3191 | 0x200F | 0x202B | 0x202E | 0x2067
3192 | 0xFB1D..=0xFDFF
3193 | 0xFE70..=0xFEFF
3194 | 0x10800..=0x10FFF
3195 | 0x1E800..=0x1EFFF)
3196 })
3197}
3198
3199fn build_decorations(
3210 run: &cosmic_text::LayoutRun<'_>,
3211 spans: &[SpanDeco],
3212 fs: &mut FontSystem,
3213 out: &mut Vec<DecoTemplate>,
3214) {
3215 let mut last_bg: Option<usize> = None;
3218 let mut i = 0;
3219 while i < run.glyphs.len() {
3220 let span_no = run.glyphs[i].metadata;
3221 let mut j = i + 1;
3222 while j < run.glyphs.len() && run.glyphs[j].metadata == span_no {
3223 j += 1;
3224 }
3225 let deco = spans.get(span_no).copied().unwrap_or_default();
3226 if deco.any() {
3227 let group = &run.glyphs[i..j];
3228 let x0 = group.iter().map(|g| g.x).fold(f32::INFINITY, f32::min);
3229 let x1 = group
3230 .iter()
3231 .map(|g| g.x + g.w)
3232 .fold(f32::NEG_INFINITY, f32::max);
3233 let w = (x1 - x0).max(0.0);
3234 match (deco.bg, last_bg.map(|k| &mut out[k])) {
3235 (Some(bg), Some(prev))
3236 if prev.color == Some(bg)
3237 && prev.radius == deco.bg_radius
3238 && (prev.x + prev.w - x0).abs() < 0.01 =>
3239 {
3240 prev.w = (x1 - prev.x).max(prev.w);
3241 }
3242 (Some(bg), _) => {
3243 last_bg = Some(out.len());
3244 out.push(DecoTemplate {
3245 x: x0,
3246 y: run.line_top,
3247 w,
3248 h: run.line_height,
3249 color: Some(bg),
3250 under: true,
3251 style: UnderlineStyle::Solid,
3252 radius: deco.bg_radius,
3253 });
3254 }
3255 (None, _) => last_bg = None,
3256 }
3257 if deco.underline || deco.strikethrough {
3258 let first = &group[0];
3259 let metrics = fs
3260 .get_font(first.font_id, first.font_weight)
3261 .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3262 let (under_off, strike_off, stroke) = match metrics {
3265 Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3266 None => (
3267 -0.1 * first.font_size,
3268 0.3 * first.font_size,
3269 0.06 * first.font_size,
3270 ),
3271 };
3272 let stroke = stroke.max(1.0).round();
3273 let baseline = run.line_y.round();
3274 if deco.underline {
3275 out.push(DecoTemplate {
3276 x: x0,
3277 y: (baseline - under_off).round(),
3278 w,
3279 h: stroke,
3280 color: deco.underline_color.or_else(|| {
3281 first
3282 .color_opt
3283 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a()))
3284 }),
3285 under: false,
3286 style: deco.underline_style,
3287 radius: 0.0,
3288 });
3289 }
3290 if deco.strikethrough {
3291 out.push(DecoTemplate {
3292 x: x0,
3293 y: (baseline - strike_off).round(),
3294 w,
3295 h: stroke,
3296 color: first
3297 .color_opt
3298 .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3299 under: false,
3300 style: UnderlineStyle::Solid,
3301 radius: 0.0,
3302 });
3303 }
3304 }
3305 } else {
3306 last_bg = None;
3307 }
3308 i = j;
3309 }
3310}
3311
3312impl TextSystem {
3313 pub(crate) fn place(
3318 &mut self,
3319 key: Key,
3320 ancestry: &Ancestry,
3321 id: TextId,
3322 origin: Vec2,
3323 scope: Option<Key>,
3324 drawn: bool,
3325 ) {
3326 let cache_key = self.frame[id.0 as usize].cache_key;
3327 self.places.push(TextPlace {
3328 key,
3329 ancestors: ancestry.keys,
3330 depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3331 none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3332 cache_key,
3333 origin,
3334 scope,
3335 drawn,
3336 });
3337 }
3338
3339 fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3345 let list = if prev {
3346 self.places.prev()
3347 } else {
3348 &self.places
3349 };
3350 let mut base = 0usize;
3351 let mut out = Vec::new();
3352 for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3353 let Some(entry) = self.entries.get(&place.cache_key) else {
3354 continue;
3355 };
3356 out.push((place, entry, base));
3357 base += entry.content().len();
3358 }
3359 out
3360 }
3361
3362 pub(crate) fn content_len(&self, id: TextId) -> usize {
3365 let key = self.frame[id.0 as usize].cache_key;
3366 self.entries.get(&key).map_or(0, |e| e.content().len())
3367 }
3368
3369 pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3378 let list = if prev {
3379 self.places.prev()
3380 } else {
3381 &self.places
3382 };
3383 let mut base = 0usize;
3384 let mut out = Vec::new();
3385 for place in list.iter().filter(|p| p.scope == Some(scope)) {
3386 let Some(text) = self.entries.get(&place.cache_key) else {
3387 continue;
3388 };
3389 let len = text.content().len();
3390 out.push(ScopeRun { place, text, base });
3391 base += len;
3392 }
3393 out
3394 }
3395
3396 pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3403 let runs = self.scope_runs(scope, prev);
3404 let px = point.x * self.scale;
3405 let py = point.y * self.scale;
3406 let gap = |r: &Rect| {
3407 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3408 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3409 (dy, dx)
3410 };
3411 let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3412 let ga = gap(&self.scope_box(a));
3413 let gb = gap(&self.scope_box(b));
3414 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3415 })?;
3416 let byte = self.hit_in_run(run, point)?;
3417 Some((run.place.key, byte))
3418 }
3419
3420 fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3422 self.entry_box(run.place, run.text)
3423 }
3424
3425 fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
3430 match entry {
3431 Entry::Run(e) => self.physical_box(place, e),
3432 Entry::Long(l) => {
3433 let (ox, oy) = self.physical_origin(place);
3434 let w = l.wrap_w.unwrap_or_else(|| l.width());
3435 Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
3436 }
3437 }
3438 }
3439
3440 fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
3442 let (_, oy) = self.physical_origin(place);
3443 match entry {
3444 Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
3445 Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
3446 }
3447 }
3448
3449 fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
3452 match run.text {
3453 Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
3454 Entry::Run(e) => {
3455 let (ox, oy) = self.physical_origin(run.place);
3456 let cursor = e
3457 .buffer
3458 .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
3459 Some(e.byte_of(cursor))
3460 }
3461 }
3462 }
3463
3464 pub(crate) fn word_at(
3471 &self,
3472 scope: Key,
3473 node: Key,
3474 byte: usize,
3475 prev: bool,
3476 ) -> Option<(usize, usize)> {
3477 let run = self
3478 .scope_runs(scope, prev)
3479 .into_iter()
3480 .find(|r| r.place.key == node)?;
3481 let content = run.text.content();
3482 Some(word_range(content, byte))
3483 }
3484
3485 pub(crate) fn scope_offset(
3491 &self,
3492 scope: Key,
3493 node: Key,
3494 byte: usize,
3495 prev: bool,
3496 ) -> Option<usize> {
3497 let run = self
3498 .scope_runs(scope, prev)
3499 .into_iter()
3500 .find(|r| r.place.key == node)?;
3501 Some(run.base + byte.min(run.text.content().len()))
3502 }
3503
3504 pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3519 let (from, to) = (from.min(to), from.max(to));
3520 let mut out = String::new();
3521 let mut prev_run: Option<&ScopeRun<'_>> = None;
3522 for run in &self.scope_runs(scope, prev) {
3523 let (start, end) = run.span();
3524 if end <= from || start >= to {
3525 continue;
3526 }
3527 if let Some(p) = prev_run
3528 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3529 {
3530 out.push_str("<br>");
3531 }
3532 let content = run.text.content();
3533 let lo = floor_boundary(content, from.saturating_sub(start));
3534 let hi = floor_boundary(content, (to - start).min(content.len()));
3535 match run.text {
3536 Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
3537 Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
3538 }
3539 prev_run = Some(run);
3540 }
3541 out
3542 }
3543
3544 fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
3550 for c in &line.chunks {
3551 let (a, b) = (lo.max(c.start), hi.min(c.end));
3552 if a >= b {
3553 continue;
3554 }
3555 match c.width.and_then(|_| self.run(c.key)) {
3556 Some(e) => html_of_run(e, a - c.start, b - c.start, out),
3557 None => escape_into(&line.content[a..b], out),
3558 }
3559 }
3560 }
3561
3562 pub(crate) fn scope_selection_anchor(
3572 &self,
3573 scope: Key,
3574 from: usize,
3575 to: usize,
3576 prev: bool,
3577 ) -> Option<Vec2> {
3578 let (from, to) = (from.min(to), from.max(to));
3579 for run in self.scope_runs(scope, prev) {
3580 let (start, end) = run.span();
3581 if !run.place.drawn || end <= from || start >= to {
3582 continue;
3583 }
3584 let content = run.text.content();
3585 let lo = floor_boundary(content, from.saturating_sub(start));
3586 let hi = floor_boundary(content, (to - start).min(content.len()));
3587 let (ox, oy) = self.physical_origin(run.place);
3588 match run.text {
3589 Entry::Run(e) => {
3590 if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
3591 return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
3592 }
3593 }
3594 Entry::Long(l) => {
3595 if let Some((x, y)) = self.long_caret_local(l, lo) {
3596 return Some(Vec2::new(
3597 (ox + x) / self.scale,
3598 (oy + y + self.long_baseline(l)) / self.scale,
3599 ));
3600 }
3601 }
3602 }
3603 }
3604 None
3605 }
3606
3607 fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
3610 let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
3611 for run in entry.buffer.layout_runs() {
3612 if run.line_i < a.line || run.line_i > b.line {
3613 continue;
3614 }
3615 if let Some((x, _)) = run.highlight(a, b).next() {
3616 return Some((x, run.line_y));
3617 }
3618 }
3619 None
3620 }
3621
3622 pub(crate) fn scope_selection_rect(
3628 &self,
3629 scope: Key,
3630 from: usize,
3631 to: usize,
3632 prev: bool,
3633 ) -> Option<Rect> {
3634 let (from, to) = (from.min(to), from.max(to));
3635 let mut out: Option<Rect> = None;
3636 for run in self.scope_runs(scope, prev) {
3637 let (start, end) = run.span();
3638 if !run.place.drawn || end <= from || start >= to {
3639 continue;
3640 }
3641 let content = run.text.content();
3642 let lo = floor_boundary(content, from.saturating_sub(start));
3643 let hi = floor_boundary(content, (to - start).min(content.len()));
3644 let (ox, oy) = self.physical_origin(run.place);
3650 let rects = match run.text {
3651 Entry::Long(l) => self.long_highlight(l, lo, hi),
3652 Entry::Run(e) => Self::run_highlight(e, lo, hi),
3653 };
3654 for r in rects {
3655 let r = Rect::new(
3656 (ox + r.x) / self.scale,
3657 (oy + r.y) / self.scale,
3658 r.w / self.scale,
3659 r.h / self.scale,
3660 );
3661 out = Some(match out {
3662 None => r,
3663 Some(o) => o.union(&r),
3664 });
3665 }
3666 }
3667 out
3668 }
3669
3670 pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
3675 let (from, to) = (from.min(to), from.max(to));
3676 let runs = self.scope_runs(scope, prev);
3677 let mut out = String::new();
3678 let mut reserved = false;
3679 let mut prev_run: Option<&ScopeRun<'_>> = None;
3680 for run in &runs {
3681 let (start, end) = run.span();
3682 if end <= from || start >= to {
3683 continue;
3684 }
3685 if !reserved {
3686 out.reserve(to - from + runs.len());
3687 reserved = true;
3688 }
3689 if let Some(p) = prev_run
3690 && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
3691 {
3692 out.push('\n');
3693 }
3694 let content = run.text.content();
3695 let lo = from.saturating_sub(start).min(content.len());
3696 let hi = (to - start).min(content.len());
3697 out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
3698 prev_run = Some(run);
3699 }
3700 out
3701 }
3702
3703 fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
3707 let (ox, oy) = self.physical_origin(place);
3708 let px = point.x * self.scale - ox;
3709 let py = point.y * self.scale - oy;
3710 if line.chunks.is_empty() {
3711 return Some(TextHit { byte: 0, line: 0 });
3712 }
3713 if let Some(w) = line.wrap_w {
3714 return self.long_hit_wrapped(line, w, px, py);
3715 }
3716 if px >= line.width() {
3717 return Some(TextHit {
3718 byte: line.content.len(),
3719 line: 0,
3720 });
3721 }
3722 let i = line.chunk_at(px.max(0.0));
3723 let c = &line.chunks[i];
3724 let local = px - line.prefix[i];
3725 let byte = match self.run(c.key) {
3726 Some(e) if c.width.is_some() => {
3727 let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
3728 c.start + cursor.index.min(c.end - c.start)
3729 }
3730 _ => {
3731 let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
3732 let mut b = (c.start + est).min(c.end);
3733 while !line.content.is_char_boundary(b) {
3734 b -= 1;
3735 }
3736 b
3737 }
3738 };
3739 Some(TextHit { byte, line: 0 })
3740 }
3741
3742 fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
3746 let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
3747 let Some((a, b)) = line.chunks_on_rows(row, row) else {
3748 return Some(TextHit {
3749 byte: line.content.len(),
3750 line: row,
3751 });
3752 };
3753 let i = (a..=b)
3756 .find(|&j| {
3757 let (r1, end_x) = line.starts[j + 1];
3758 px < if r1 == row { end_x } else { w }
3759 })
3760 .unwrap_or(b);
3761 let c = &line.chunks[i];
3762 let (row0, head_x) = line.starts[i];
3763 let r = (row - row0) as usize;
3764 let byte = match self.run(c.key) {
3765 Some(e) if r < c.rows.len() => {
3766 let rs = c.rows[r];
3767 let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
3768 let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
3769 let lo = rs.byte as usize;
3770 let hi = c
3771 .rows
3772 .get(r + 1)
3773 .map_or(c.end - c.start, |n| n.byte as usize);
3774 c.start + cursor.index.clamp(lo, hi)
3775 }
3776 _ => {
3777 let linear = r as f32 * w + px - head_x;
3780 let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
3781 let mut b = (c.start + est).min(c.end);
3782 while !line.content.is_char_boundary(b) {
3783 b -= 1;
3784 }
3785 b
3786 }
3787 };
3788 let next_row = self
3793 .long_caret_local(line, byte)
3794 .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
3795 let byte = if next_row && byte > 0 {
3796 floor_boundary(&line.content, byte - 1)
3797 } else {
3798 byte
3799 };
3800 Some(TextHit { byte, line: row })
3801 }
3802
3803 fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
3806 let (ox, oy) = self.physical_origin(place);
3807 let scale = self.scale;
3808 let (x, y) = self.long_caret_local(line, byte)?;
3809 Some(Rect::new(
3810 (ox + x) / scale,
3811 (oy + y) / scale,
3812 0.0,
3813 line.line_h / scale,
3814 ))
3815 }
3816
3817 fn long_baseline(&self, line: &LongLine) -> f32 {
3823 line.chunks
3824 .first()
3825 .and_then(|c| self.run(c.key))
3826 .and_then(|e| e.buffer.layout_runs().next())
3827 .map_or(line.line_h * 0.8, |r| r.line_y.round())
3828 }
3829
3830 fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
3835 let byte = byte.min(line.content.len());
3836 if let Some(w) = line.wrap_w
3837 && !line.chunks.is_empty()
3838 {
3839 let i = line.chunk_of_byte(byte);
3840 let c = &line.chunks[i];
3841 let local = byte - c.start;
3842 let (row0, head_x) = line.starts[i];
3843 let (r, x) = match self.run(c.key) {
3844 Some(e) if !c.rows.is_empty() => {
3845 let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
3846 let run = e.buffer.layout_runs().next()?;
3847 let cx = caret_x(&run, local.min(c.end - c.start));
3848 (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
3849 }
3850 _ => {
3851 let linear = head_x + local as f32 * line.avg;
3852 let r = (linear / w).floor();
3853 (r as usize, linear - r * w)
3854 }
3855 };
3856 let y = (row0 as usize + r) as f32 * line.line_h;
3857 return Some((x, y));
3858 }
3859 let x = if line.chunks.is_empty() {
3860 0.0
3861 } else {
3862 let i = line.chunk_of_byte(byte);
3863 let c = &line.chunks[i];
3864 let local = byte - c.start;
3865 match self.run(c.key) {
3866 Some(e) if c.width.is_some() => {
3867 let run = e.buffer.layout_runs().next()?;
3868 line.prefix[i] + caret_x(&run, local.min(c.end - c.start))
3869 }
3870 _ => line.prefix[i] + local as f32 * line.avg,
3871 }
3872 };
3873 Some((x, 0.0))
3874 }
3875
3876 fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
3882 let Some((x0, y0)) = self.long_caret_local(line, from) else {
3883 return Vec::new();
3884 };
3885 let Some((x1, y1)) = self.long_caret_local(line, to) else {
3886 return Vec::new();
3887 };
3888 let h = line.line_h;
3889 if (y1 - y0).abs() < f32::EPSILON {
3890 return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
3891 }
3892 let w = line.wrap_w.unwrap_or_else(|| line.width());
3894 let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
3895 let mut y = y0 + h;
3896 while y < y1 - h / 2.0 {
3897 out.push(Rect::new(0.0, y, w, h));
3898 y += h;
3899 }
3900 out.push(Rect::new(0.0, y1, x1.max(0.0), h));
3901 out
3902 }
3903
3904 fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
3910 let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
3911 let mut out = Vec::new();
3912 for run in entry.buffer.layout_runs() {
3913 if run.line_i < a.line || run.line_i > b.line {
3914 continue;
3915 }
3916 let h = entry.buffer.metrics().line_height;
3917 let mut any = false;
3918 for (x, w) in run.highlight(a, b) {
3919 any = true;
3920 out.push(Rect::new(x, run.line_top, w.max(2.0), h));
3921 }
3922 if !any && run.glyphs.is_empty() && b.line > run.line_i {
3925 out.push(Rect::new(0.0, run.line_top, 2.0, h));
3926 }
3927 }
3928 out
3929 }
3930
3931 fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
3934 (
3935 crate::geom::snap_px(place.origin.x * self.scale),
3936 crate::geom::snap_px(place.origin.y * self.scale),
3937 )
3938 }
3939
3940 fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
3942 let (ox, oy) = self.physical_origin(place);
3943 let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
3944 Rect::new(ox, oy, size.w, size.h)
3945 }
3946
3947 pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
3951 let runs = self.runs_of(key, prev);
3952 if runs.is_empty() {
3953 return None;
3954 }
3955 let px = point.x * self.scale;
3956 let py = point.y * self.scale;
3957 let gap = |r: &Rect| {
3960 let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3961 let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3962 (dy, dx)
3963 };
3964 let (place, entry, base) = runs
3965 .iter()
3966 .min_by(|a, b| {
3967 let ga = gap(&self.entry_box(a.0, a.1));
3968 let gb = gap(&self.entry_box(b.0, b.1));
3969 ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3970 })
3971 .copied()?;
3972 let (ox, oy) = self.physical_origin(place);
3973 let (byte, hit_top) = match entry {
3974 Entry::Long(line) => {
3975 let hit = self.long_hit(place, line, point)?;
3976 (hit.byte, oy + hit.line as f32 * line.line_h)
3977 }
3978 Entry::Run(entry) => {
3979 let bx = self.physical_box(place, entry);
3986 let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
3987 let cursor = entry.buffer.hit(px - ox, py - oy)?;
3988 let row =
3989 visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
3990 let top = entry
3991 .buffer
3992 .layout_runs()
3993 .nth(row)
3994 .map_or(0.0, |r| r.line_top);
3995 (entry.byte_of(cursor), oy + top)
3996 }
3997 };
3998 let mut tops: Vec<f32> = runs
4003 .iter()
4004 .flat_map(|(p, e, _)| self.row_tops(p, e))
4005 .collect();
4006 tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4007 tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
4008 let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
4009 Some(TextHit {
4010 byte: base + byte,
4011 line: line as u32,
4012 })
4013 }
4014
4015 pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
4019 let runs = self.runs_of(key, prev);
4020 let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
4021 let byte = byte.min(total);
4022 let (place, entry, base) = runs
4023 .iter()
4024 .find(|(_, e, base)| byte < base + e.content().len())
4025 .or_else(|| runs.last())
4026 .copied()?;
4027 let byte = (byte - base).min(entry.content().len());
4028 let entry = match entry {
4029 Entry::Long(line) => return self.long_caret(place, line, byte),
4030 Entry::Run(entry) => entry,
4031 };
4032 let (ox, oy) = self.physical_origin(place);
4033 let cursor = entry.cursor_of(byte);
4034 let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
4035 let run = entry.buffer.layout_runs().nth(run_no)?;
4036 let x = caret_x(&run, cursor.index);
4037 let scale = self.scale;
4038 Some(Rect::new(
4039 (ox + x) / scale,
4040 (oy + run.line_top) / scale,
4041 0.0,
4042 run.line_height / scale,
4043 ))
4044 }
4045}
4046
4047fn word_range(content: &str, byte: usize) -> (usize, usize) {
4057 if content.is_empty() {
4058 return (0, 0);
4059 }
4060 #[derive(PartialEq)]
4061 enum Class {
4062 Word,
4063 Space,
4064 Other,
4065 }
4066 let class = |c: char| {
4067 if c.is_alphanumeric() || c == '_' {
4068 Class::Word
4069 } else if c.is_whitespace() {
4070 Class::Space
4071 } else {
4072 Class::Other
4073 }
4074 };
4075 let at = floor_boundary(content, byte.min(content.len()));
4078 let at = if at >= content.len() {
4079 content.char_indices().next_back().map_or(0, |(i, _)| i)
4080 } else {
4081 at
4082 };
4083 let Some(here) = content[at..].chars().next().map(class) else {
4084 return (at, at);
4085 };
4086 let mut start = at;
4087 for (i, c) in content[..at].char_indices().rev() {
4088 if class(c) != here {
4089 break;
4090 }
4091 start = i;
4092 }
4093 let mut end = at;
4094 for (i, c) in content[at..].char_indices() {
4095 if class(c) != here {
4096 break;
4097 }
4098 end = at + i + c.len_utf8();
4099 }
4100 (start, end)
4101}
4102
4103fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4108 let starts = line_starts(&entry.buffer, &entry.content);
4109 for (li, line) in entry.buffer.lines.iter().enumerate() {
4110 let base = starts.get(li).copied().unwrap_or(0);
4111 let text = line.text();
4112 if li > 0 {
4113 if base > lo && base <= hi {
4116 out.push_str("<br>");
4117 }
4118 }
4119 let attrs_list = line.attrs_list();
4125 let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4126 for (i, _) in text.char_indices() {
4127 let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4128 match spans.last_mut() {
4129 Some((_, end, prev)) if *prev == a && *end == i => {
4130 *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4131 }
4132 _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4133 }
4134 }
4135 for (start, end, attrs) in &spans {
4136 let (s, e) = (base + start, base + end);
4137 let (s, e) = (s.max(lo), e.min(hi));
4138 if s >= e {
4139 continue;
4140 }
4141 let piece = &entry.content[s..e];
4142 let bold = attrs.weight > attrs_list.defaults().weight
4147 || attrs
4148 .cache_key_flags
4149 .contains(crate::weights::SYNTHETIC_BOLD);
4150 let italic = attrs.style != cosmic_text::Style::Normal;
4151 if let Some(c) = attrs.color_opt {
4152 let _ = std::fmt::Write::write_fmt(
4153 out,
4154 format_args!(
4155 "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4156 c.r(),
4157 c.g(),
4158 c.b()
4159 ),
4160 );
4161 }
4162 if bold {
4163 out.push_str("<b>");
4164 }
4165 if italic {
4166 out.push_str("<i>");
4167 }
4168 escape_into(piece, out);
4169 if italic {
4170 out.push_str("</i>");
4171 }
4172 if bold {
4173 out.push_str("</b>");
4174 }
4175 if attrs.color_opt.is_some() {
4176 out.push_str("</span>");
4177 }
4178 }
4179 }
4180}
4181
4182fn escape_into(s: &str, out: &mut String) {
4186 for ch in s.chars() {
4187 match ch {
4188 '&' => out.push_str("&"),
4189 '<' => out.push_str("<"),
4190 '>' => out.push_str(">"),
4191 _ => out.push(ch),
4192 }
4193 }
4194}
4195
4196fn floor_boundary(s: &str, i: usize) -> usize {
4202 let mut i = i.min(s.len());
4203 while i > 0 && !s.is_char_boundary(i) {
4204 i -= 1;
4205 }
4206 i
4207}
4208
4209fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4210 paragraph_starts(buffer, content).collect()
4211}
4212
4213fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4219 let mut at = 0usize;
4220 buffer.lines.iter().map(move |line| {
4221 let start = at.min(content.len());
4222 at += line.text().len();
4223 let rest = &content[at.min(content.len())..];
4224 for ending in ["\r\n", "\n\r", "\n", "\r"] {
4225 if rest.starts_with(ending) {
4226 at += ending.len();
4227 break;
4228 }
4229 }
4230 start
4231 })
4232}
4233
4234fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4244 let mut last_of_line = None;
4245 let mut before = None;
4246 for (n, run) in buffer.layout_runs().enumerate() {
4247 if run.line_i != line_i {
4248 if last_of_line.is_some() {
4249 break;
4250 }
4251 continue;
4252 }
4253 last_of_line = Some(n);
4254 if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4255 return Some((n, n));
4256 }
4257 if run
4258 .glyphs
4259 .iter()
4260 .map(|g| g.start)
4261 .min()
4262 .is_some_and(|s| s <= index)
4263 {
4264 before = Some(n);
4265 }
4266 }
4267 before.or(last_of_line).map(|n| (n, n))
4268}
4269
4270fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4274 if let Some(g) = run
4275 .glyphs
4276 .iter()
4277 .find(|g| g.start <= index && index < g.end)
4278 {
4279 return if g.level.is_rtl() { g.x + g.w } else { g.x };
4280 }
4281 match run.glyphs.last() {
4282 Some(g) if !g.level.is_rtl() => g.x + g.w,
4283 Some(g) => g.x,
4284 None => 0.0,
4285 }
4286}
4287
4288impl Default for TextSystem {
4289 fn default() -> Self {
4290 Self::new()
4291 }
4292}
4293
4294fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4297 let max_w = max_w_logical?;
4298 let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4299 (!intrinsic_fits).then_some(max_w * scale)
4300}
4301
4302pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4310 match (current, target) {
4311 (None, None) => false,
4312 (Some(a), Some(b)) => (a - b).abs() > 0.5,
4313 _ => true,
4314 }
4315}
4316
4317fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4318 if wrap_differs(entry.wrap, target) {
4319 entry.buffer.set_size(target, None);
4320 entry.buffer.shape_until_scroll(fs, false);
4321 entry.wrap = target;
4322 }
4323}
4324
4325impl CachedText {
4326 fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4329 let start = paragraph_starts(&self.buffer, &self.content)
4330 .nth(cursor.line)
4331 .unwrap_or(0);
4332 (start + cursor.index).min(self.content.len())
4333 }
4334
4335 fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4341 let mut li = 0;
4342 let mut start = 0;
4343 for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4344 if s <= byte {
4345 (li, start) = (i, s);
4346 } else {
4347 break;
4348 }
4349 }
4350 let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4351 cosmic_text::Cursor::new(li, (byte - start).min(len))
4352 }
4353
4354 fn new(
4355 mut buffer: Buffer,
4356 content: String,
4357 style: &TextStyle,
4358 span_deco: Vec<SpanDeco>,
4359 fs: &mut FontSystem,
4360 frame_no: u64,
4361 ) -> Self {
4362 buffer.shape_until_scroll(fs, false);
4363 let max_lines = line_budget(style);
4364 let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4365 let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4366 Self {
4367 buffer,
4368 bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4369 content,
4370 wrap: None,
4371 intrinsic,
4372 max_lines,
4373 clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4374 last_used: frame_no,
4375 glyphs: Vec::new(),
4376 deco: Vec::new(),
4377 span_deco,
4378 glyphs_built_for: None,
4379 claimed: u64::MAX,
4380 claimed_wrap: None,
4381 breaks_anywhere: style.wrap == TextWrap::Glyph,
4382 min_content: None,
4383 }
4384 }
4385
4386 fn fork(&self, frame_no: u64) -> Self {
4390 Self {
4391 buffer: self.buffer.clone(),
4392 content: self.content.clone(),
4393 wrap: self.wrap,
4394 intrinsic: self.intrinsic,
4395 max_lines: self.max_lines,
4396 clamp_w: self.clamp_w,
4397 last_used: frame_no,
4398 bytes: self.bytes,
4399 glyphs: Vec::new(),
4400 deco: Vec::new(),
4401 span_deco: self.span_deco.clone(),
4402 glyphs_built_for: None,
4403 claimed: u64::MAX,
4404 claimed_wrap: None,
4405 breaks_anywhere: self.breaks_anywhere,
4406 min_content: self.min_content,
4407 }
4408 }
4409
4410 fn min_content(&mut self) -> f32 {
4421 if let Some(w) = self.min_content {
4422 return w;
4423 }
4424 let w = if self.clamp_w {
4425 self.intrinsic.w
4426 } else {
4427 widest_unbreakable(&self.buffer, self.breaks_anywhere)
4428 };
4429 self.min_content = Some(w);
4430 w
4431 }
4432}
4433
4434fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
4438 let mut widest = 0.0f32;
4439 let mut line = usize::MAX;
4440 let mut breaks: Vec<usize> = Vec::new();
4441 let (mut run_w, mut ink) = (0.0f32, 0.0f32);
4442 for run in buffer.layout_runs() {
4443 if run.line_i != line {
4444 widest = widest.max(ink);
4445 (run_w, ink) = (0.0, 0.0);
4446 line = run.line_i;
4447 breaks.clear();
4448 if !anywhere {
4449 breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
4450 }
4451 }
4452 for g in run.glyphs {
4453 if anywhere || breaks.binary_search(&g.start).is_ok() {
4454 widest = widest.max(ink);
4455 (run_w, ink) = (0.0, 0.0);
4456 }
4457 run_w += g.w;
4458 let blank = run
4459 .text
4460 .get(g.start..g.end)
4461 .is_some_and(|t| t.chars().all(char::is_whitespace));
4462 if !blank {
4463 ink = run_w;
4464 }
4465 }
4466 }
4467 widest.max(ink)
4468}
4469
4470fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
4473 let metrics = Metrics::new(style.size * scale, style.line_height * scale);
4474 let mut buffer = Buffer::new(fs, metrics);
4475 buffer.set_wrap(match style.wrap {
4476 TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
4477 TextWrap::Glyph => Wrap::Glyph,
4478 TextWrap::None => Wrap::None,
4479 });
4480 if style.ellipsis {
4481 let lines = line_budget(style).max(1);
4482 buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
4483 }
4484 buffer.set_size(None, None);
4485 buffer
4486}
4487
4488fn line_budget(style: &TextStyle) -> usize {
4491 if style.max_lines > 0 {
4492 style.max_lines as usize
4493 } else if style.ellipsis {
4494 1
4495 } else {
4496 0
4497 }
4498}
4499
4500fn line_cap(max_lines: usize) -> usize {
4501 if max_lines == 0 {
4502 usize::MAX
4503 } else {
4504 max_lines
4505 }
4506}
4507
4508pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
4514 let mut w = 0.0f32;
4515 let mut lines = 0u32;
4516 for run in buffer.layout_runs().take(line_cap(max_lines)) {
4517 w = w.max(run.line_w);
4518 lines += 1;
4519 }
4520 (
4521 Size::new(w, lines as f32 * buffer.metrics().line_height),
4522 lines,
4523 )
4524}
4525
4526impl TextSystem {
4527 pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
4528 let scale = self.scale;
4529 if let Some(line) = self.long_of(id) {
4530 return Size::new(line.width() / scale, line.line_h / scale);
4531 }
4532 let e = self.entry_mut(id);
4533 Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
4534 }
4535
4536 pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
4541 let scale = self.scale;
4542 if self.long_of(id).is_some() {
4543 return 0.0;
4544 }
4545 self.entry_mut(id).min_content() / scale
4546 }
4547
4548 fn long_of(&self, id: TextId) -> Option<&LongLine> {
4550 self.long(self.frame[id.0 as usize].cache_key)
4551 }
4552
4553 pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
4554 let scale = self.scale;
4555 if self.long_of(id).is_some() {
4556 let key = self.frame[id.0 as usize].cache_key;
4559 let (size, rows) = self.long_size(key, Some(max_w * scale));
4560 self.long_mut(key).expect("a long line").laid_rows = rows;
4561 return Size::new(size.w / scale, size.h / scale);
4562 }
4563 self.ensure_wrap(id, max_w, fs);
4564 let e = self.entry_mut(id);
4565 let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
4566 if e.clamp_w {
4567 m.w = m.w.min(max_w * scale);
4570 }
4571 Size::new(m.w / scale, m.h / scale)
4572 }
4573}
4574
4575impl TextSystem {
4576 pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
4581 let scale = self.scale;
4582 if let Some(line) = self.long_of(id) {
4583 return self.long_baseline(line) / scale;
4584 }
4585 let e = self.entry_mut(id);
4586 let b = e
4587 .buffer
4588 .layout_runs()
4589 .next()
4590 .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
4591 b / scale
4592 }
4593}
4594
4595#[cfg(test)]
4598mod default_families {
4599 use super::*;
4600 use cosmic_text::Family;
4601 use cosmic_text::fontdb::FaceInfo;
4602
4603 fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
4606 let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
4607 buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
4608 buffer.shape_until_scroll(fs, false);
4609 let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
4610 fs.db().face(id).cloned()
4611 }
4612
4613 #[test]
4614 fn mono_is_an_upright_monospaced_face() {
4615 let mut fs = new_font_system().0;
4616 if !fs.db().faces().any(|f| f.monospaced) {
4617 eprintln!("skipped: no monospaced face installed");
4618 return;
4619 }
4620 let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
4621 let mono = default_families(&fs)[2].to_string();
4622 assert_eq!(
4623 face.style,
4624 cosmic_text::Style::Normal,
4625 "Mono ({mono}) resolved to {:?}",
4626 face.post_script_name
4627 );
4628 assert!(
4629 face.monospaced,
4630 "Mono ({mono}) resolved to {:?}, which is not monospaced",
4631 face.post_script_name
4632 );
4633 }
4634
4635 #[test]
4636 fn sans_and_serif_are_upright_and_not_monospaced() {
4637 let mut fs = new_font_system().0;
4638 if fs.db().faces().next().is_none() {
4639 eprintln!("skipped: no face installed");
4640 return;
4641 }
4642 let [sans, serif, _] = default_families(&fs).map(str::to_string);
4643 for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
4644 let face = resolved(&mut fs, family).expect("M shapes");
4645 assert_eq!(
4646 face.style,
4647 cosmic_text::Style::Normal,
4648 "{name} resolved to {:?}",
4649 face.post_script_name
4650 );
4651 assert!(
4652 !face.monospaced,
4653 "{name} resolved to {:?}, which is monospaced",
4654 face.post_script_name
4655 );
4656 }
4657 }
4658
4659 #[test]
4662 fn pinned_names_are_installed_or_cosmic_texts_own() {
4663 let fs = new_font_system().0;
4664 let db = fs.db();
4665 for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
4666 let installed = db
4667 .faces()
4668 .any(|f| f.families.iter().any(|(fam, _)| fam == name));
4669 let on_list = list.contains(&name);
4670 assert!(
4671 installed == on_list,
4672 "{name}: installed {installed}, on the platform list {on_list}"
4673 );
4674 }
4675 }
4676}
4677
4678#[cfg(test)]
4685mod unmeasurable_faces {
4686 use super::*;
4687 use crate::testing::{font_face, han_face, unmeasurable_face};
4688 use cosmic_text::Family;
4689 use cosmic_text::fontdb::{Database, Source};
4690
4691 fn font_system() -> FontSystem {
4696 let mut db = Database::new();
4697 for bytes in [
4698 font_face("Kui Mono", 400, false, true),
4699 unmeasurable_face("Kui Bitmap"),
4700 han_face("Kui Han"),
4701 ] {
4702 db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
4703 }
4704 let mut fs = font_system_with("en-US".into(), db);
4705 fs.db_mut().set_monospace_family("Kui Mono");
4706 fs
4707 }
4708
4709 fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
4710 fs.db()
4711 .face(id)
4712 .map_or_else(String::new, |f| f.families[0].0.clone())
4713 }
4714
4715 #[test]
4716 fn a_face_without_metrics_is_not_in_the_database() {
4717 let fs = font_system();
4718 let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
4719 assert_eq!(families, ["Kui Mono", "Kui Han"]);
4720 }
4721
4722 #[test]
4726 fn han_in_mono_falls_back_to_a_face_that_measures() {
4727 let mut fs = font_system();
4728 let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
4729 let attrs = Attrs::new().family(Family::Monospace);
4730 buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
4731 buffer.shape_until_scroll(&mut fs, false);
4732 let run = buffer.layout_runs().next().expect("one line");
4733 let glyphs: Vec<(&str, f32, f32, String)> = run
4734 .glyphs
4735 .iter()
4736 .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
4737 .collect();
4738 for &(text, x, w, _) in &glyphs {
4739 assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
4740 }
4741 let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
4742 assert_eq!(
4743 faces,
4744 [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
4745 );
4746 let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
4748 assert_eq!(xs, [0.0, 7.0, 14.0]);
4749 for glyph in run.glyphs {
4750 glyph.physical((0.0, 0.0), 1.0);
4751 }
4752 }
4753}