1mod font_fallbacks;
2mod font_features;
3mod line;
4mod line_layout;
5mod line_wrapper;
6
7pub use font_fallbacks::*;
8pub use font_features::*;
9pub use line::*;
10pub use line_layout::*;
11pub use line_wrapper::*;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
17 StrikethroughStyle, TextRenderingMode, UnderlineStyle, px,
18};
19use anyhow::{Context as _, anyhow};
20use collections::{FxHashMap, FxHashSet};
21use core::fmt;
22use derive_more::{Add, Deref, FromStr, Sub};
23use itertools::Itertools;
24use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
25use smallvec::{SmallVec, smallvec};
26use std::{
27 borrow::Cow,
28 cmp,
29 collections::VecDeque,
30 fmt::{Debug, Display, Formatter},
31 hash::{Hash, Hasher},
32 ops::{Deref, DerefMut, Range},
33 sync::{
34 Arc,
35 atomic::{AtomicUsize, Ordering},
36 },
37};
38
39#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
41#[repr(C)]
42pub struct FontId(pub usize);
43
44#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
46pub struct FontFamilyId(pub usize);
47
48pub const SUBPIXEL_VARIANTS_X: u8 = 4;
50
51pub const SUBPIXEL_VARIANTS_Y: u8 = 1;
53
54const UNDERLINE_DESCENT_OFFSET_FACTOR: f32 = 0.618;
56
57pub fn underline_y_offset(line_height: Pixels, ascent: Pixels, descent: Pixels) -> Pixels {
59 let padding_top = (line_height - ascent - descent) / 2.;
60 padding_top + ascent + descent * UNDERLINE_DESCENT_OFFSET_FACTOR
61}
62
63const MAX_REPORTED_MISSING_GLYPHS: usize = 1024;
64
65#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
67pub enum FallbackFontClass {
68 Proportional,
70 Monospace,
72}
73
74#[derive(Clone, Debug, Eq, Hash, PartialEq)]
76pub struct MissingGlyph {
77 grapheme: SharedString,
78 font_class: FallbackFontClass,
79}
80
81impl MissingGlyph {
82 pub fn new(grapheme: SharedString, font_class: FallbackFontClass) -> Self {
84 Self {
85 grapheme,
86 font_class,
87 }
88 }
89
90 pub fn grapheme(&self) -> &str {
92 &self.grapheme
93 }
94
95 pub fn font_class(&self) -> FallbackFontClass {
97 self.font_class
98 }
99}
100
101pub trait MissingGlyphSink: Send + Sync {
103 fn report(&self, missing_glyphs: Vec<MissingGlyph>);
105}
106
107#[derive(Default)]
108struct MissingGlyphState {
109 reported: FxHashSet<MissingGlyph>,
110 reported_order: VecDeque<MissingGlyph>,
111 generation: usize,
112}
113
114impl MissingGlyphState {
115 fn reset(&mut self, generation: usize) {
116 self.reported.clear();
117 self.reported_order.clear();
118 self.generation = generation;
119 }
120}
121
122struct QueuedMissingGlyph {
123 generation: usize,
124 missing_glyph: MissingGlyph,
125}
126
127struct MissingGlyphReporter {
129 generation: Arc<AtomicUsize>,
130 sender: async_channel::Sender<QueuedMissingGlyph>,
131}
132
133impl MissingGlyphSink for MissingGlyphReporter {
134 fn report(&self, missing_glyphs: Vec<MissingGlyph>) {
135 if self.sender.is_closed() {
136 return;
137 }
138
139 let generation = self.generation.load(Ordering::Acquire);
140 for missing_glyph in missing_glyphs.into_iter().unique() {
143 let queued = QueuedMissingGlyph {
144 generation,
145 missing_glyph,
146 };
147 if self.sender.try_send(queued).is_err() {
148 break;
149 }
150 }
151 }
152}
153
154impl MissingGlyphReporter {
155 fn reset(&self) {
156 self.generation.fetch_add(1, Ordering::AcqRel);
157 }
158}
159
160pub(crate) struct MissingGlyphReceiver {
162 state: MissingGlyphState,
163 generation: Arc<AtomicUsize>,
164 receiver: async_channel::Receiver<QueuedMissingGlyph>,
165}
166
167impl MissingGlyphReceiver {
168 pub(crate) async fn recv(
174 &mut self,
175 ) -> std::result::Result<Vec<MissingGlyph>, async_channel::RecvError> {
176 loop {
177 let queued = self.receiver.recv().await?;
178 let mut missing_glyphs = Vec::new();
179 for queued in std::iter::once(queued)
180 .chain(std::iter::from_fn(|| self.receiver.try_recv().ok()))
181 .take(MAX_REPORTED_MISSING_GLYPHS)
182 {
183 let generation = self.generation.load(Ordering::Acquire);
184 if self.state.generation != generation {
185 self.state.reset(generation);
186 missing_glyphs.clear();
187 }
188 if queued.generation != generation
189 || !self.state.reported.insert(queued.missing_glyph.clone())
190 {
191 continue;
192 }
193 self.state
194 .reported_order
195 .push_back(queued.missing_glyph.clone());
196 missing_glyphs.push(queued.missing_glyph);
197 if self.state.reported.len() > MAX_REPORTED_MISSING_GLYPHS
198 && let Some(expired) = self.state.reported_order.pop_front()
199 {
200 self.state.reported.remove(&expired);
201 }
202 }
203 if !missing_glyphs.is_empty() {
204 return Ok(missing_glyphs);
205 }
206 let mut yielded = false;
209 std::future::poll_fn(|cx| {
210 if std::mem::replace(&mut yielded, true) {
211 std::task::Poll::Ready(())
212 } else {
213 cx.waker().wake_by_ref();
214 std::task::Poll::Pending
215 }
216 })
217 .await;
218 }
219 }
220}
221
222impl Drop for MissingGlyphReceiver {
223 fn drop(&mut self) {
224 self.receiver.close();
225 while self.receiver.try_recv().is_ok() {}
226 }
227}
228
229pub struct TextSystem {
231 platform_text_system: Arc<dyn PlatformTextSystem>,
232 font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
233 font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
234 raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
235 wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
236 font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
237 fallback_font_stack: SmallVec<[Font; 2]>,
238 font_generation: Arc<AtomicUsize>,
239 missing_glyph_reporter: Arc<MissingGlyphReporter>,
240 missing_glyph_receiver: Mutex<Option<MissingGlyphReceiver>>,
241}
242
243impl TextSystem {
244 pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
246 let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
247 let missing_glyph_generation = Arc::<AtomicUsize>::default();
248 TextSystem {
249 platform_text_system,
250 font_metrics: RwLock::default(),
251 raster_bounds: RwLock::default(),
252 font_ids_by_font: RwLock::default(),
253 wrapper_pool: Mutex::default(),
254 font_runs_pool: Mutex::default(),
255 fallback_font_stack: smallvec![
256 font(".ZedMono"),
258 font(".ZedSans"),
259 font("Helvetica"),
260 font("Segoe UI"), font("Ubuntu"), font("Adwaita Sans"), font("Cantarell"), font("Noto Sans"), font("DejaVu Sans"),
266 font("Arial"), ],
268 font_generation: Arc::default(),
269 missing_glyph_reporter: Arc::new(MissingGlyphReporter {
270 generation: missing_glyph_generation.clone(),
271 sender,
272 }),
273 missing_glyph_receiver: Mutex::new(Some(MissingGlyphReceiver {
274 state: MissingGlyphState::default(),
275 generation: missing_glyph_generation,
276 receiver,
277 })),
278 }
279 }
280
281 pub fn all_font_names(&self) -> Vec<String> {
285 let mut names = self.platform_text_system.all_font_names();
286 names.sort_unstable();
287 names.dedup();
288 names
289 }
290
291 pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
296 self.platform_text_system.add_fonts(fonts)?;
297 self.font_ids_by_font.write().clear();
298 self.missing_glyph_reporter.reset();
299 self.font_generation.fetch_add(1, Ordering::Release);
300 Ok(())
301 }
302
303 pub(crate) fn take_missing_glyph_receiver(&self) -> Option<MissingGlyphReceiver> {
308 self.missing_glyph_receiver
309 .try_lock()
310 .and_then(|mut receiver| receiver.take())
311 }
312
313 pub(crate) fn enable_missing_glyph_reporting(&self) {
314 self.platform_text_system
315 .set_missing_glyph_sink(Some(self.missing_glyph_reporter.clone()));
316 }
317
318 pub(crate) fn disable_missing_glyph_reporting(&self) {
319 self.platform_text_system.set_missing_glyph_sink(None);
320 self.missing_glyph_reporter.reset();
321 }
322
323 #[cfg(test)]
324 pub(crate) fn report_missing_glyphs_in_test(&self, missing_glyphs: Vec<MissingGlyph>) {
325 self.missing_glyph_reporter.report(missing_glyphs);
326 }
327
328 fn font_id(&self, font: &Font) -> Result<FontId> {
330 fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
331 match font_id {
332 Ok(font_id) => Ok(*font_id),
333 Err(err) => Err(anyhow!("{err}")),
334 }
335 }
336
337 let font_id = self
338 .font_ids_by_font
339 .read()
340 .get(font)
341 .map(clone_font_id_result);
342 if let Some(font_id) = font_id {
343 font_id
344 } else {
345 let font_id = self.platform_text_system.font_id(font);
346 self.font_ids_by_font
347 .write()
348 .insert(font.clone(), clone_font_id_result(&font_id));
349 font_id
350 }
351 }
352
353 pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
355 let lock = self.font_ids_by_font.read();
356 lock.iter()
357 .filter_map(|(font, result)| match result {
358 Ok(font_id) if *font_id == id => Some(font.clone()),
359 _ => None,
360 })
361 .next()
362 }
363
364 pub fn resolve_font(&self, font: &Font) -> FontId {
371 if let Ok(font_id) = self.font_id(font) {
372 return font_id;
373 }
374 for fallback in &self.fallback_font_stack {
375 if let Ok(font_id) = self.font_id(fallback) {
376 return font_id;
377 }
378 }
379
380 panic!(
381 "failed to resolve font '{}' or any of the fallbacks: {}",
382 font.family,
383 self.fallback_font_stack
384 .iter()
385 .map(|fallback| &fallback.family)
386 .join(", ")
387 );
388 }
389
390 pub fn prewarm_fonts(&self, fonts: &[Font]) {
396 let mut font_ids = SmallVec::<[FontId; 8]>::new();
397 for font in fonts {
398 let font_id = self.resolve_font(font);
399 if !font_ids.contains(&font_id) {
400 font_ids.push(font_id);
401 }
402 }
403 self.platform_text_system.prewarm_fonts(&font_ids);
404 }
405
406 pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
410 self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
411 }
412
413 pub fn typographic_bounds(
415 &self,
416 font_id: FontId,
417 font_size: Pixels,
418 character: char,
419 ) -> Result<Bounds<Pixels>> {
420 let glyph_id = self
421 .platform_text_system
422 .glyph_for_char(font_id, character)
423 .with_context(|| format!("glyph not found for character '{character}'"))?;
424 let bounds = self
425 .platform_text_system
426 .typographic_bounds(font_id, glyph_id)?;
427 Ok(self.read_metrics(font_id, |metrics| {
428 (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
429 }))
430 }
431
432 pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
434 let glyph_id = self
435 .platform_text_system
436 .glyph_for_char(font_id, ch)
437 .with_context(|| format!("glyph not found for character '{ch}'"))?;
438 let result = self.platform_text_system.advance(font_id, glyph_id)?
439 / self.units_per_em(font_id) as f32;
440
441 Ok(result * font_size)
442 }
443
444 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
447 let mut buffer = [0; 4];
448 let buffer = ch.encode_utf8(&mut buffer);
449 self.platform_text_system
450 .layout_line(
451 buffer,
452 font_size,
453 &[FontRun {
454 len: buffer.len(),
455 font_id,
456 }],
457 )
458 .width
459 }
460
461 pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
465 Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
466 }
467
468 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
472 Ok(self.advance(font_id, font_size, 'm')?.width)
473 }
474
475 pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
479 Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
480 }
481
482 pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
486 Ok(self.advance(font_id, font_size, '0')?.width)
487 }
488
489 pub fn units_per_em(&self, font_id: FontId) -> u32 {
493 self.read_metrics(font_id, |metrics| metrics.units_per_em)
494 }
495
496 pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
498 self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
499 }
500
501 pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
503 self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
504 }
505
506 pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
508 self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
509 }
510
511 pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
514 self.read_metrics(font_id, |metrics| metrics.descent(font_size))
515 }
516
517 pub fn baseline_offset(
519 &self,
520 font_id: FontId,
521 font_size: Pixels,
522 line_height: Pixels,
523 ) -> Pixels {
524 let ascent = self.ascent(font_id, font_size);
525 let descent = self.descent(font_id, font_size);
526 let padding_top = (line_height - ascent - descent) / 2.;
527 padding_top + ascent
528 }
529
530 fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
531 let lock = self.font_metrics.upgradable_read();
532
533 if let Some(metrics) = lock.get(&font_id) {
534 read(metrics)
535 } else {
536 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
537 let metrics = lock
538 .entry(font_id)
539 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
540 read(metrics)
541 }
542 }
543
544 pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
546 let lock = &mut self.wrapper_pool.lock();
547 let font_id = self.resolve_font(&font);
548 let wrappers = lock
549 .entry(FontIdWithSize { font_id, font_size })
550 .or_default();
551 let wrapper = wrappers
552 .pop()
553 .unwrap_or_else(|| LineWrapper::new(font_id, font_size, self.clone()));
554
555 LineWrapperHandle {
556 wrapper: Some(wrapper),
557 text_system: self.clone(),
558 }
559 }
560
561 pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
563 let raster_bounds = self.raster_bounds.upgradable_read();
564 if let Some(bounds) = raster_bounds.get(params) {
565 Ok(*bounds)
566 } else {
567 let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
568 let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
569 raster_bounds.insert(params.clone(), bounds);
570 Ok(bounds)
571 }
572 }
573
574 pub(crate) fn rasterize_glyph(
575 &self,
576 params: &RenderGlyphParams,
577 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
578 let raster_bounds = self.raster_bounds(params)?;
579 self.platform_text_system
580 .rasterize_glyph(params, raster_bounds)
581 }
582
583 pub(crate) fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
585 self.platform_text_system.glyph_dilation_for_color(color)
586 }
587
588 pub(crate) fn recommended_rendering_mode(
591 &self,
592 font_id: FontId,
593 font_size: Pixels,
594 ) -> TextRenderingMode {
595 self.platform_text_system
596 .recommended_rendering_mode(font_id, font_size)
597 }
598}
599
600#[derive(Deref)]
602pub struct WindowTextSystem {
603 line_layout_cache: LineLayoutCache,
604 #[deref]
605 text_system: Arc<TextSystem>,
606}
607
608impl WindowTextSystem {
609 pub fn new(text_system: Arc<TextSystem>) -> Self {
611 Self {
612 line_layout_cache: LineLayoutCache::new(
613 text_system.platform_text_system.clone(),
614 text_system.font_generation.clone(),
615 ),
616 text_system,
617 }
618 }
619
620 pub(crate) fn layout_index(&self) -> LineLayoutIndex {
621 self.line_layout_cache.layout_index()
622 }
623
624 pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
625 self.line_layout_cache.reuse_layouts(index)
626 }
627
628 pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
629 self.line_layout_cache.truncate_layouts(index)
630 }
631
632 pub fn shape_line(
639 &self,
640 text: SharedString,
641 font_size: Pixels,
642 runs: &[TextRun],
643 force_width: Option<Pixels>,
644 ) -> ShapedLine {
645 debug_assert!(
646 text.find('\n').is_none(),
647 "text argument should not contain newlines"
648 );
649
650 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
651 for run in runs {
652 if let Some(last_run) = decoration_runs.last_mut()
653 && last_run.color == run.color
654 && last_run.underline == run.underline
655 && last_run.strikethrough == run.strikethrough
656 && last_run.background_color == run.background_color
657 {
658 last_run.len += run.len as u32;
659 continue;
660 }
661 decoration_runs.push(DecorationRun {
662 len: run.len as u32,
663 color: run.color,
664 background_color: run.background_color,
665 underline: run.underline,
666 strikethrough: run.strikethrough,
667 });
668 }
669
670 let layout = self.layout_line(&text, font_size, runs, force_width);
671
672 ShapedLine {
673 layout,
674 text,
675 decoration_runs,
676 }
677 }
678
679 pub fn shape_line_by_hash(
690 &self,
691 text_hash: u64,
692 text_len: usize,
693 font_size: Pixels,
694 runs: &[TextRun],
695 force_width: Option<Pixels>,
696 materialize_text: impl FnOnce() -> SharedString,
697 ) -> ShapedLine {
698 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
699 for run in runs {
700 if let Some(last_run) = decoration_runs.last_mut()
701 && last_run.color == run.color
702 && last_run.underline == run.underline
703 && last_run.strikethrough == run.strikethrough
704 && last_run.background_color == run.background_color
705 {
706 last_run.len += run.len as u32;
707 continue;
708 }
709 decoration_runs.push(DecorationRun {
710 len: run.len as u32,
711 color: run.color,
712 background_color: run.background_color,
713 underline: run.underline,
714 strikethrough: run.strikethrough,
715 });
716 }
717
718 let mut used_force_width = force_width;
719 let layout = self.layout_line_by_hash(
720 text_hash,
721 text_len,
722 font_size,
723 runs,
724 used_force_width,
725 || {
726 let text = materialize_text();
727 debug_assert!(
728 text.find('\n').is_none(),
729 "text argument should not contain newlines"
730 );
731 text
732 },
733 );
734
735 let text: SharedString = SharedString::new_static("");
739
740 ShapedLine {
741 layout,
742 text,
743 decoration_runs,
744 }
745 }
746
747 pub fn shape_text(
751 &self,
752 text: SharedString,
753 font_size: Pixels,
754 runs: &[TextRun],
755 wrap_width: Option<Pixels>,
756 line_clamp: Option<usize>,
757 ) -> Result<SmallVec<[WrappedLine; 1]>> {
758 let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
759 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
760
761 let mut lines = SmallVec::new();
762 let mut max_wrap_lines = line_clamp;
763 let mut wrapped_lines = 0;
764
765 let mut process_line = |line_text: SharedString, line_start, line_end| {
766 font_runs.clear();
767
768 let mut decoration_runs = <Vec<DecorationRun>>::with_capacity(32);
769 let mut run_start = line_start;
770 while run_start < line_end {
771 let Some(run) = runs.peek_mut() else {
772 log::warn!("`TextRun`s do not cover the entire to be shaped text");
773 break;
774 };
775
776 let run_len_within_line = cmp::min(line_end - run_start, run.len);
777
778 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
779 && last_run.color == run.color
780 && last_run.underline == run.underline
781 && last_run.strikethrough == run.strikethrough
782 && last_run.background_color == run.background_color
783 {
784 last_run.len += run_len_within_line as u32;
785 false
786 } else {
787 decoration_runs.push(DecorationRun {
788 len: run_len_within_line as u32,
789 color: run.color,
790 background_color: run.background_color,
791 underline: run.underline,
792 strikethrough: run.strikethrough,
793 });
794 true
795 };
796
797 let font_id = self.resolve_font(&run.font);
798 if let Some(font_run) = font_runs.last_mut()
799 && font_id == font_run.font_id
800 && !decoration_changed
801 {
802 font_run.len += run_len_within_line;
803 } else {
804 font_runs.push(FontRun {
805 len: run_len_within_line,
806 font_id,
807 });
808 }
809
810 run.len -= run_len_within_line;
812 if run.len == 0 {
813 runs.next();
814 }
815 run_start += run_len_within_line;
816 }
817
818 let layout = self.line_layout_cache.layout_wrapped_line(
819 &line_text,
820 font_size,
821 &font_runs,
822 wrap_width,
823 max_wrap_lines.map(|max| max.saturating_sub(wrapped_lines)),
824 );
825 wrapped_lines += layout.wrap_boundaries.len();
826
827 lines.push(WrappedLine {
828 layout,
829 decoration_runs,
830 text: line_text,
831 });
832
833 if let Some(run) = runs.peek_mut() {
835 run.len -= 1;
836 if run.len == 0 {
837 runs.next();
838 }
839 }
840 };
841
842 let mut split_lines = text.split('\n');
843
844 if let Some(first_line) = split_lines.next()
846 && let Some(second_line) = split_lines.next()
847 {
848 let mut line_start = 0;
849 process_line(
850 SharedString::new(first_line),
851 line_start,
852 line_start + first_line.len(),
853 );
854 line_start += first_line.len() + '\n'.len_utf8();
855 process_line(
856 SharedString::new(second_line),
857 line_start,
858 line_start + second_line.len(),
859 );
860 for line_text in split_lines {
861 line_start += line_text.len() + '\n'.len_utf8();
862 process_line(
863 SharedString::new(line_text),
864 line_start,
865 line_start + line_text.len(),
866 );
867 }
868 } else {
869 let end = text.len();
870 process_line(text, 0, end);
871 }
872
873 self.font_runs_pool.lock().push(font_runs);
874
875 Ok(lines)
876 }
877
878 pub(crate) fn finish_frame(&self) {
879 self.line_layout_cache.finish_frame()
880 }
881
882 pub fn layout_line(
887 &self,
888 text: &str,
889 font_size: Pixels,
890 runs: &[TextRun],
891 force_width: Option<Pixels>,
892 ) -> Arc<LineLayout> {
893 let mut last_run = None::<&TextRun>;
894 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
895 font_runs.clear();
896
897 for run in runs.iter() {
898 let decoration_changed = if let Some(last_run) = last_run
899 && last_run.color == run.color
900 && last_run.underline == run.underline
901 && last_run.strikethrough == run.strikethrough
902 {
905 false
906 } else {
907 last_run = Some(run);
908 true
909 };
910
911 let font_id = self.resolve_font(&run.font);
912 if let Some(font_run) = font_runs.last_mut()
913 && font_id == font_run.font_id
914 && !decoration_changed
915 {
916 font_run.len += run.len;
917 } else {
918 font_runs.push(FontRun {
919 len: run.len,
920 font_id,
921 });
922 }
923 }
924
925 let layout = self.line_layout_cache.layout_line(
926 &SharedString::new(text),
927 font_size,
928 &font_runs,
929 force_width,
930 );
931
932 self.font_runs_pool.lock().push(font_runs);
933
934 layout
935 }
936
937 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
939 let mut buffer = [0; 4];
940 let buffer: &_ = ch.encode_utf8(&mut buffer);
941 self.line_layout_cache
942 .layout_line(
943 buffer,
944 font_size,
945 &[FontRun {
946 len: buffer.len(),
947 font_id,
948 }],
949 None,
950 )
951 .width
952 }
953
954 pub fn em_layout_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
956 self.layout_width(font_id, font_size, 'm')
957 }
958
959 pub fn try_layout_line_by_hash(
968 &self,
969 text_hash: u64,
970 text_len: usize,
971 font_size: Pixels,
972 runs: &[TextRun],
973 force_width: Option<Pixels>,
974 ) -> Option<Arc<LineLayout>> {
975 let mut last_run = None::<&TextRun>;
976 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
977 font_runs.clear();
978
979 for run in runs.iter() {
980 let decoration_changed = if let Some(last_run) = last_run
981 && last_run.color == run.color
982 && last_run.underline == run.underline
983 && last_run.strikethrough == run.strikethrough
984 {
987 false
988 } else {
989 last_run = Some(run);
990 true
991 };
992
993 let font_id = self.resolve_font(&run.font);
994 if let Some(font_run) = font_runs.last_mut()
995 && font_id == font_run.font_id
996 && !decoration_changed
997 {
998 font_run.len += run.len;
999 } else {
1000 font_runs.push(FontRun {
1001 len: run.len,
1002 font_id,
1003 });
1004 }
1005 }
1006
1007 let layout = self.line_layout_cache.try_layout_line_by_hash(
1008 text_hash,
1009 text_len,
1010 font_size,
1011 &font_runs,
1012 force_width,
1013 );
1014
1015 self.font_runs_pool.lock().push(font_runs);
1016
1017 layout
1018 }
1019
1020 pub fn layout_line_by_hash(
1029 &self,
1030 text_hash: u64,
1031 text_len: usize,
1032 font_size: Pixels,
1033 runs: &[TextRun],
1034 force_width: Option<Pixels>,
1035 materialize_text: impl FnOnce() -> SharedString,
1036 ) -> Arc<LineLayout> {
1037 let mut last_run = None::<&TextRun>;
1038 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
1039 font_runs.clear();
1040
1041 for run in runs.iter() {
1042 let decoration_changed = if let Some(last_run) = last_run
1043 && last_run.color == run.color
1044 && last_run.underline == run.underline
1045 && last_run.strikethrough == run.strikethrough
1046 {
1049 false
1050 } else {
1051 last_run = Some(run);
1052 true
1053 };
1054
1055 let font_id = self.resolve_font(&run.font);
1056 if let Some(font_run) = font_runs.last_mut()
1057 && font_id == font_run.font_id
1058 && !decoration_changed
1059 {
1060 font_run.len += run.len;
1061 } else {
1062 font_runs.push(FontRun {
1063 len: run.len,
1064 font_id,
1065 });
1066 }
1067 }
1068
1069 let layout = self.line_layout_cache.layout_line_by_hash(
1070 text_hash,
1071 text_len,
1072 font_size,
1073 &font_runs,
1074 force_width,
1075 materialize_text,
1076 );
1077
1078 self.font_runs_pool.lock().push(font_runs);
1079
1080 layout
1081 }
1082}
1083
1084#[derive(Hash, Eq, PartialEq)]
1085struct FontIdWithSize {
1086 font_id: FontId,
1087 font_size: Pixels,
1088}
1089
1090pub struct LineWrapperHandle {
1092 wrapper: Option<LineWrapper>,
1093 text_system: Arc<TextSystem>,
1094}
1095
1096impl Drop for LineWrapperHandle {
1097 fn drop(&mut self) {
1098 let mut state = self.text_system.wrapper_pool.lock();
1099 let wrapper = self.wrapper.take().unwrap();
1100 state
1101 .get_mut(&FontIdWithSize {
1102 font_id: wrapper.font_id,
1103 font_size: wrapper.font_size,
1104 })
1105 .unwrap()
1106 .push(wrapper);
1107 }
1108}
1109
1110impl Deref for LineWrapperHandle {
1111 type Target = LineWrapper;
1112
1113 fn deref(&self) -> &Self::Target {
1114 self.wrapper.as_ref().unwrap()
1115 }
1116}
1117
1118impl DerefMut for LineWrapperHandle {
1119 fn deref_mut(&mut self) -> &mut Self::Target {
1120 self.wrapper.as_mut().unwrap()
1121 }
1122}
1123
1124#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)]
1127#[serde(transparent)]
1128pub struct FontWeight(pub f32);
1129
1130impl Display for FontWeight {
1131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1132 write!(f, "{}", self.0)
1133 }
1134}
1135
1136impl From<f32> for FontWeight {
1137 fn from(weight: f32) -> Self {
1138 FontWeight(weight)
1139 }
1140}
1141
1142impl Default for FontWeight {
1143 #[inline]
1144 fn default() -> FontWeight {
1145 FontWeight::NORMAL
1146 }
1147}
1148
1149impl Hash for FontWeight {
1150 fn hash<H: Hasher>(&self, state: &mut H) {
1151 state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
1152 }
1153}
1154
1155impl Eq for FontWeight {}
1156
1157impl FontWeight {
1158 pub const THIN: FontWeight = FontWeight(100.0);
1160 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
1162 pub const LIGHT: FontWeight = FontWeight(300.0);
1164 pub const NORMAL: FontWeight = FontWeight(400.0);
1166 pub const MEDIUM: FontWeight = FontWeight(500.0);
1168 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
1170 pub const BOLD: FontWeight = FontWeight(700.0);
1172 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
1174 pub const BLACK: FontWeight = FontWeight(900.0);
1176
1177 pub const ALL: [FontWeight; 9] = [
1179 Self::THIN,
1180 Self::EXTRA_LIGHT,
1181 Self::LIGHT,
1182 Self::NORMAL,
1183 Self::MEDIUM,
1184 Self::SEMIBOLD,
1185 Self::BOLD,
1186 Self::EXTRA_BOLD,
1187 Self::BLACK,
1188 ];
1189}
1190
1191impl schemars::JsonSchema for FontWeight {
1192 fn schema_name() -> std::borrow::Cow<'static, str> {
1193 "FontWeight".into()
1194 }
1195
1196 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
1197 use schemars::json_schema;
1198 json_schema!({
1199 "type": "number",
1200 "minimum": Self::THIN,
1201 "maximum": Self::BLACK,
1202 "default": Self::default(),
1203 "description": "Font weight value between 100 (thin) and 900 (black)"
1204 })
1205 }
1206}
1207
1208#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
1210pub enum FontStyle {
1211 #[default]
1213 Normal,
1214 Italic,
1216 Oblique,
1218}
1219
1220impl Display for FontStyle {
1221 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1222 Debug::fmt(self, f)
1223 }
1224}
1225
1226#[derive(Clone, Debug, PartialEq, Eq, Default)]
1228pub struct TextRun {
1229 pub len: usize,
1231 pub font: Font,
1233 pub color: Hsla,
1235 pub background_color: Option<Hsla>,
1237 pub underline: Option<UnderlineStyle>,
1239 pub strikethrough: Option<StrikethroughStyle>,
1241}
1242
1243#[cfg(all(target_os = "macos", test))]
1244impl TextRun {
1245 fn with_len(&self, len: usize) -> Self {
1246 let mut this = self.clone();
1247 this.len = len;
1248 this
1249 }
1250}
1251
1252#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1254#[repr(C)]
1255pub struct GlyphId(pub u32);
1256
1257#[derive(Clone, Debug, PartialEq)]
1263#[expect(missing_docs)]
1264pub struct RenderGlyphParams {
1265 pub font_id: FontId,
1266 pub glyph_id: GlyphId,
1267 pub font_size: Pixels,
1268 pub subpixel_variant: Point<u8>,
1269 pub scale_factor: f32,
1270 pub is_emoji: bool,
1271 pub subpixel_rendering: bool,
1272 pub dilation: u8,
1273}
1274
1275impl Eq for RenderGlyphParams {}
1276
1277impl Hash for RenderGlyphParams {
1278 fn hash<H: Hasher>(&self, state: &mut H) {
1279 self.font_id.0.hash(state);
1280 self.glyph_id.0.hash(state);
1281 self.font_size.0.to_bits().hash(state);
1282 self.subpixel_variant.hash(state);
1283 self.scale_factor.to_bits().hash(state);
1284 self.is_emoji.hash(state);
1285 self.subpixel_rendering.hash(state);
1286 self.dilation.hash(state);
1287 }
1288}
1289
1290#[derive(Clone, Debug, Eq, PartialEq, Hash)]
1292pub struct Font {
1293 pub family: SharedString,
1297
1298 pub features: FontFeatures,
1300
1301 pub fallbacks: Option<FontFallbacks>,
1303
1304 pub weight: FontWeight,
1306
1307 pub style: FontStyle,
1309}
1310
1311impl Default for Font {
1312 fn default() -> Self {
1313 font(".SystemUIFont")
1314 }
1315}
1316
1317pub fn font(family: impl Into<SharedString>) -> Font {
1319 Font {
1320 family: family.into(),
1321 features: FontFeatures::default(),
1322 weight: FontWeight::default(),
1323 style: FontStyle::default(),
1324 fallbacks: None,
1325 }
1326}
1327
1328impl Font {
1329 pub fn bold(mut self) -> Self {
1331 self.weight = FontWeight::BOLD;
1332 self
1333 }
1334
1335 pub fn italic(mut self) -> Self {
1337 self.style = FontStyle::Italic;
1338 self
1339 }
1340}
1341
1342#[derive(Clone, Copy, Debug)]
1345pub struct FontMetrics {
1346 pub units_per_em: u32,
1349
1350 pub ascent: f32,
1352
1353 pub descent: f32,
1355
1356 pub line_gap: f32,
1358
1359 pub underline_position: f32,
1361
1362 pub underline_thickness: f32,
1364
1365 pub cap_height: f32,
1367
1368 pub x_height: f32,
1370
1371 pub bounding_box: Bounds<f32>,
1374}
1375
1376impl FontMetrics {
1377 pub fn ascent(&self, font_size: Pixels) -> Pixels {
1379 Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
1380 }
1381
1382 pub fn descent(&self, font_size: Pixels) -> Pixels {
1384 Pixels((self.descent / self.units_per_em as f32) * font_size.0)
1385 }
1386
1387 pub fn line_gap(&self, font_size: Pixels) -> Pixels {
1389 Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
1390 }
1391
1392 pub fn underline_position(&self, font_size: Pixels) -> Pixels {
1394 Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
1395 }
1396
1397 pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
1399 Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
1400 }
1401
1402 pub fn cap_height(&self, font_size: Pixels) -> Pixels {
1404 Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
1405 }
1406
1407 pub fn x_height(&self, font_size: Pixels) -> Pixels {
1409 Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
1410 }
1411
1412 pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
1414 (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
1415 }
1416}
1417
1418#[allow(unused)]
1420pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
1421 match name {
1425 ".SystemUIFont" => system,
1426 ".ZedSans" | "Zed Plex Sans" => "IBM Plex Sans",
1427 ".ZedMono" | "Zed Plex Mono" => "Lilex",
1428 _ => name,
1429 }
1430}
1431
1432#[allow(unused)]
1434pub fn font_name_with_fallbacks_shared<'a>(
1435 name: &'a SharedString,
1436 system: &'a SharedString,
1437) -> &'a SharedString {
1438 match name.as_str() {
1442 ".SystemUIFont" => system,
1443 ".ZedSans" | "Zed Plex Sans" => const { &SharedString::new_static("IBM Plex Sans") },
1444 ".ZedMono" | "Zed Plex Mono" => const { &SharedString::new_static("Lilex") },
1445 _ => name,
1446 }
1447}
1448
1449#[cfg(test)]
1450mod missing_glyph_tests {
1451 use super::*;
1452 use futures::FutureExt as _;
1453
1454 #[test]
1455 fn bounds_retained_missing_glyphs() {
1456 let (reporter, mut receiver) = missing_glyph_channel();
1457 reporter.report(
1458 (0..MAX_REPORTED_MISSING_GLYPHS)
1459 .map(|index| {
1460 MissingGlyph::new(index.to_string().into(), FallbackFontClass::Proportional)
1461 })
1462 .collect(),
1463 );
1464 assert!(receiver.recv().now_or_never().unwrap().is_ok());
1465
1466 let newest = MissingGlyph::new("newest".into(), FallbackFontClass::Monospace);
1467 reporter.report(vec![newest.clone()]);
1468 assert!(receiver.recv().now_or_never().unwrap().is_ok());
1469
1470 let state = &receiver.state;
1471 assert_eq!(state.reported.len(), MAX_REPORTED_MISSING_GLYPHS);
1472 assert_eq!(state.reported_order.len(), MAX_REPORTED_MISSING_GLYPHS);
1473 assert!(state.reported.contains(&newest));
1474 }
1475
1476 #[test]
1477 fn dropping_receiver_closes_and_clears_reports() {
1478 let (reporter, receiver) = missing_glyph_channel();
1479 reporter.report(vec![missing_glyph("missing")]);
1480
1481 drop(receiver);
1482
1483 assert!(reporter.sender.is_closed());
1484 assert!(reporter.sender.is_empty());
1485 }
1486
1487 fn missing_glyph_channel() -> (MissingGlyphReporter, MissingGlyphReceiver) {
1488 let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
1489 let generation = Arc::<AtomicUsize>::default();
1490 let reporter = MissingGlyphReporter {
1491 generation: generation.clone(),
1492 sender,
1493 };
1494 let receiver = MissingGlyphReceiver {
1495 state: MissingGlyphState::default(),
1496 generation,
1497 receiver,
1498 };
1499 (reporter, receiver)
1500 }
1501
1502 fn missing_glyph(grapheme: &'static str) -> MissingGlyph {
1503 MissingGlyph::new(grapheme.into(), FallbackFontClass::Proportional)
1504 }
1505}