1mod font_fallbacks;
2mod font_features;
3pub(crate) mod font_inspection;
4mod font_introspection;
5mod font_variations;
6mod line;
7mod line_layout;
8mod line_wrapper;
9mod paragraph;
10
11pub use font_fallbacks::*;
12pub use font_features::*;
13pub use font_introspection::*;
14pub use font_variations::*;
15pub use line::*;
16pub use line_layout::*;
17pub use line_wrapper::*;
18pub use paragraph::*;
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21
22use crate::{
23 Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
24 StrikethroughStyle, TextRenderingMode, UnderlineStyle, px,
25};
26use anyhow::Context as _;
27use collections::{FxHashMap, FxHashSet};
28use core::fmt;
29use derive_more::{Add, Deref, FromStr, Sub};
30use itertools::Itertools;
31use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
32use smallvec::{SmallVec, smallvec};
33use std::{
34 borrow::Cow,
35 cell::Cell,
36 cmp,
37 collections::VecDeque,
38 fmt::{Debug, Display, Formatter},
39 hash::{Hash, Hasher},
40 ops::{Deref, DerefMut, Range},
41 sync::{
42 Arc,
43 atomic::{AtomicUsize, Ordering},
44 },
45};
46
47#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
49#[repr(C)]
50pub struct FontId(pub usize);
51
52#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
54pub struct FontFamilyId(pub usize);
55
56pub const SUBPIXEL_VARIANTS_X: u8 = 4;
58
59pub const SUBPIXEL_VARIANTS_Y: u8 = 1;
61
62const UNDERLINE_DESCENT_OFFSET_FACTOR: f32 = 0.618;
64
65pub fn underline_y_offset(line_height: Pixels, ascent: Pixels, descent: Pixels) -> Pixels {
67 let padding_top = (line_height - ascent - descent) / 2.;
68 padding_top + ascent + descent * UNDERLINE_DESCENT_OFFSET_FACTOR
69}
70
71const MAX_REPORTED_MISSING_GLYPHS: usize = 1024;
72
73#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
75pub enum FallbackFontClass {
76 Proportional,
78 Monospace,
80}
81
82#[derive(Clone, Debug, Eq, Hash, PartialEq)]
84pub struct MissingGlyph {
85 grapheme: SharedString,
86 font_class: FallbackFontClass,
87}
88
89impl MissingGlyph {
90 pub fn new(grapheme: SharedString, font_class: FallbackFontClass) -> Self {
92 Self {
93 grapheme,
94 font_class,
95 }
96 }
97
98 pub fn grapheme(&self) -> &str {
100 &self.grapheme
101 }
102
103 pub fn font_class(&self) -> FallbackFontClass {
105 self.font_class
106 }
107}
108
109pub trait MissingGlyphSink: Send + Sync {
111 fn report(&self, missing_glyphs: Vec<MissingGlyph>);
113}
114
115#[derive(Default)]
116struct MissingGlyphState {
117 reported: FxHashSet<MissingGlyph>,
118 reported_order: VecDeque<MissingGlyph>,
119 generation: usize,
120}
121
122impl MissingGlyphState {
123 fn reset(&mut self, generation: usize) {
124 self.reported.clear();
125 self.reported_order.clear();
126 self.generation = generation;
127 }
128}
129
130struct QueuedMissingGlyph {
131 generation: usize,
132 missing_glyph: MissingGlyph,
133}
134
135struct MissingGlyphReporter {
137 generation: Arc<AtomicUsize>,
138 sender: async_channel::Sender<QueuedMissingGlyph>,
139}
140
141impl MissingGlyphSink for MissingGlyphReporter {
142 fn report(&self, missing_glyphs: Vec<MissingGlyph>) {
143 if self.sender.is_closed() {
144 return;
145 }
146
147 let generation = self.generation.load(Ordering::Acquire);
148 for missing_glyph in missing_glyphs.into_iter().unique() {
151 let queued = QueuedMissingGlyph {
152 generation,
153 missing_glyph,
154 };
155 if self.sender.try_send(queued).is_err() {
156 break;
157 }
158 }
159 }
160}
161
162impl MissingGlyphReporter {
163 fn reset(&self) {
164 self.generation.fetch_add(1, Ordering::AcqRel);
165 }
166}
167
168pub(crate) struct MissingGlyphReceiver {
170 state: MissingGlyphState,
171 generation: Arc<AtomicUsize>,
172 receiver: async_channel::Receiver<QueuedMissingGlyph>,
173}
174
175impl MissingGlyphReceiver {
176 pub(crate) async fn recv(
182 &mut self,
183 ) -> std::result::Result<Vec<MissingGlyph>, async_channel::RecvError> {
184 loop {
185 let queued = self.receiver.recv().await?;
186 let mut missing_glyphs = Vec::new();
187 for queued in std::iter::once(queued)
188 .chain(std::iter::from_fn(|| self.receiver.try_recv().ok()))
189 .take(MAX_REPORTED_MISSING_GLYPHS)
190 {
191 let generation = self.generation.load(Ordering::Acquire);
192 if self.state.generation != generation {
193 self.state.reset(generation);
194 missing_glyphs.clear();
195 }
196 if queued.generation != generation
197 || !self.state.reported.insert(queued.missing_glyph.clone())
198 {
199 continue;
200 }
201 self.state
202 .reported_order
203 .push_back(queued.missing_glyph.clone());
204 missing_glyphs.push(queued.missing_glyph);
205 if self.state.reported.len() > MAX_REPORTED_MISSING_GLYPHS
206 && let Some(expired) = self.state.reported_order.pop_front()
207 {
208 self.state.reported.remove(&expired);
209 }
210 }
211 if !missing_glyphs.is_empty() {
212 return Ok(missing_glyphs);
213 }
214 let mut yielded = false;
217 std::future::poll_fn(|cx| {
218 if std::mem::replace(&mut yielded, true) {
219 std::task::Poll::Ready(())
220 } else {
221 cx.waker().wake_by_ref();
222 std::task::Poll::Pending
223 }
224 })
225 .await;
226 }
227 }
228}
229
230impl Drop for MissingGlyphReceiver {
231 fn drop(&mut self) {
232 self.receiver.close();
233 while self.receiver.try_recv().is_ok() {}
234 }
235}
236
237pub struct TextSystem {
239 platform_text_system: Arc<dyn PlatformTextSystem>,
240 font_ids_by_font: RwLock<FxHashMap<Font, CachedFont>>,
241 font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
242 raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
243 wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<CharWidthCache>>>,
244 fallback_font_stack: SmallVec<[Font; 2]>,
245 font_generation: Arc<AtomicUsize>,
246 missing_glyph_reporter: Arc<MissingGlyphReporter>,
247 missing_glyph_receiver: Mutex<Option<MissingGlyphReceiver>>,
248}
249
250#[derive(Clone)]
251enum CachedFont {
252 Direct(FontId),
253 Fallback(FontId),
254 Missing(Arc<anyhow::Error>),
255}
256
257impl TextSystem {
258 pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
260 let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
261 let missing_glyph_generation = Arc::<AtomicUsize>::default();
262 TextSystem {
263 platform_text_system,
264 font_metrics: RwLock::default(),
265 raster_bounds: RwLock::default(),
266 font_ids_by_font: RwLock::default(),
267 wrapper_pool: Mutex::default(),
268 fallback_font_stack: smallvec![
269 font(".ZedMono"),
271 font(".ZedSans"),
272 font("Helvetica"),
273 font("Segoe UI"), font("Ubuntu"), font("Adwaita Sans"), font("Cantarell"), font("Noto Sans"), font("DejaVu Sans"),
279 font("Arial"), ],
281 font_generation: Arc::default(),
282 missing_glyph_reporter: Arc::new(MissingGlyphReporter {
283 generation: missing_glyph_generation.clone(),
284 sender,
285 }),
286 missing_glyph_receiver: Mutex::new(Some(MissingGlyphReceiver {
287 state: MissingGlyphState::default(),
288 generation: missing_glyph_generation,
289 receiver,
290 })),
291 }
292 }
293
294 pub fn all_font_names(&self) -> Vec<String> {
298 let mut names = self.platform_text_system.all_font_names();
299 names.sort_unstable();
300 names.dedup();
301 names
302 }
303
304 pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
309 self.platform_text_system.add_fonts(fonts)?;
310 self.font_ids_by_font.write().clear();
311 self.missing_glyph_reporter.reset();
312 self.font_generation.fetch_add(1, Ordering::Release);
313 Ok(())
314 }
315
316 pub(crate) fn take_missing_glyph_receiver(&self) -> Option<MissingGlyphReceiver> {
321 self.missing_glyph_receiver
322 .try_lock()
323 .and_then(|mut receiver| receiver.take())
324 }
325
326 pub(crate) fn enable_missing_glyph_reporting(&self) {
327 self.platform_text_system
328 .set_missing_glyph_sink(Some(self.missing_glyph_reporter.clone()));
329 }
330
331 pub(crate) fn disable_missing_glyph_reporting(&self) {
332 self.platform_text_system.set_missing_glyph_sink(None);
333 self.missing_glyph_reporter.reset();
334 }
335
336 #[cfg(test)]
337 pub(crate) fn report_missing_glyphs_in_test(&self, missing_glyphs: Vec<MissingGlyph>) {
338 self.missing_glyph_reporter.report(missing_glyphs);
339 }
340
341 pub fn font_capabilities(&self) -> FontCapabilities {
343 self.platform_text_system.font_capabilities()
344 }
345 pub fn font_axes(&self, font_id: FontId) -> Result<FontQuery<Vec<VariationAxisInfo>>> {
347 self.platform_text_system.font_axes(font_id)
348 }
349
350 pub fn opentype_features(
354 &self,
355 font_id: FontId,
356 ) -> Result<FontQuery<Vec<OpenTypeFeatureInfo>>> {
357 self.platform_text_system.opentype_features(font_id)
358 }
359
360 pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
362 let lock = self.font_ids_by_font.read();
363 lock.iter()
364 .filter_map(|(font, result)| match result {
365 CachedFont::Direct(font_id) if *font_id == id => Some(font.clone()),
366 _ => None,
367 })
368 .next()
369 }
370
371 pub fn resolve_font(&self, font: &Font) -> FontId {
378 if let Some(CachedFont::Direct(font_id) | CachedFont::Fallback(font_id)) =
379 self.font_ids_by_font.read().get(font)
380 {
381 return *font_id;
382 }
383 self.resolve_font_uncached(font)
384 }
385
386 pub fn prewarm_fonts(&self, fonts: &[Font]) {
392 let mut font_ids = SmallVec::<[FontId; 8]>::new();
393 for font in fonts {
394 let font_id = self.resolve_font(font);
395 if !font_ids.contains(&font_id) {
396 font_ids.push(font_id);
397 }
398 }
399 self.platform_text_system.prewarm_fonts(&font_ids);
400 }
401
402 pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
406 self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
407 }
408
409 pub fn typographic_bounds(
411 &self,
412 font_id: FontId,
413 font_size: Pixels,
414 character: char,
415 ) -> Result<Bounds<Pixels>> {
416 let glyph_id = self
417 .platform_text_system
418 .glyph_for_char(font_id, character)
419 .with_context(|| format!("glyph not found for character '{character}'"))?;
420 let bounds = self
421 .platform_text_system
422 .typographic_bounds(font_id, glyph_id)?;
423 Ok(self.read_metrics(font_id, |metrics| {
424 (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
425 }))
426 }
427
428 pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
430 let glyph_id = self
431 .platform_text_system
432 .glyph_for_char(font_id, ch)
433 .with_context(|| format!("glyph not found for character '{ch}'"))?;
434 let result = self.platform_text_system.advance(font_id, glyph_id)?
435 / self.units_per_em(font_id) as f32;
436
437 Ok(result * font_size)
438 }
439
440 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
443 let mut buffer = [0; 4];
444 let buffer = ch.encode_utf8(&mut buffer);
445 self.platform_text_system
446 .layout_line(
447 buffer,
448 font_size,
449 &[FontRun {
450 len: buffer.len(),
451 font_id,
452 }],
453 )
454 .width
455 }
456
457 pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
461 Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
462 }
463
464 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
468 Ok(self.advance(font_id, font_size, 'm')?.width)
469 }
470
471 pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
475 Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
476 }
477
478 pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
482 Ok(self.advance(font_id, font_size, '0')?.width)
483 }
484
485 pub fn units_per_em(&self, font_id: FontId) -> u32 {
489 self.read_metrics(font_id, |metrics| metrics.units_per_em)
490 }
491
492 pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
494 self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
495 }
496
497 pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
499 self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
500 }
501
502 pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
504 self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
505 }
506
507 pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
510 self.read_metrics(font_id, |metrics| metrics.descent(font_size))
511 }
512
513 pub fn baseline_offset(
515 &self,
516 font_id: FontId,
517 font_size: Pixels,
518 line_height: Pixels,
519 ) -> Pixels {
520 let ascent = self.ascent(font_id, font_size);
521 let descent = self.descent(font_id, font_size);
522 let padding_top = (line_height - ascent - descent) / 2.;
523 padding_top + ascent
524 }
525
526 fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
527 let lock = self.font_metrics.upgradable_read();
528
529 if let Some(metrics) = lock.get(&font_id) {
530 read(metrics)
531 } else {
532 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
533 let metrics = lock
534 .entry(font_id)
535 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
536 read(metrics)
537 }
538 }
539
540 pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
542 let lock = &mut self.wrapper_pool.lock();
543 let font_id = self.resolve_font(&font);
544 let char_widths = lock
545 .entry(FontIdWithSize { font_id, font_size })
546 .or_default()
547 .pop()
548 .unwrap_or_default();
549 let wrapper = LineWrapper::new(font_id, font_size, self.clone(), char_widths);
550
551 LineWrapperHandle {
552 wrapper: Some(wrapper),
553 text_system: self.clone(),
554 }
555 }
556
557 pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
559 let raster_bounds = self.raster_bounds.upgradable_read();
560 if let Some(bounds) = raster_bounds.get(params) {
561 Ok(*bounds)
562 } else {
563 let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
564 let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
565 raster_bounds.insert(params.clone(), bounds);
566 Ok(bounds)
567 }
568 }
569
570 pub(crate) fn rasterize_glyph(
571 &self,
572 params: &RenderGlyphParams,
573 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
574 let raster_bounds = self.raster_bounds(params)?;
575 self.platform_text_system
576 .rasterize_glyph(params, raster_bounds)
577 }
578
579 pub(crate) fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
581 self.platform_text_system.glyph_dilation_for_color(color)
582 }
583
584 pub(crate) fn recommended_rendering_mode(
587 &self,
588 font_id: FontId,
589 font_size: Pixels,
590 ) -> TextRenderingMode {
591 self.platform_text_system
592 .recommended_rendering_mode(font_id, font_size)
593 }
594
595 fn resolve_font_uncached(&self, font: &Font) -> FontId {
596 let mut font_error = None;
597 let font_id = {
598 let mut font_ids = self.font_ids_by_font.upgradable_read();
599 if let Some(CachedFont::Direct(font_id) | CachedFont::Fallback(font_id)) =
600 font_ids.get(font)
601 {
602 return *font_id;
603 }
604
605 let mut lookup_font = |candidate: &Font| {
606 let cached = if let Some(cached) = font_ids.get(candidate) {
607 cached.clone()
608 } else {
609 let cached = match self.platform_text_system.font_id(candidate) {
610 Ok(font_id) => CachedFont::Direct(font_id),
611 Err(error) => CachedFont::Missing(Arc::new(error)),
612 };
613 font_ids.with_upgraded(|font_ids| {
614 font_ids.insert(candidate.clone(), cached.clone());
615 });
616 cached
617 };
618 match cached {
619 CachedFont::Direct(font_id) => Some(font_id),
620 CachedFont::Fallback(_) => None,
621 CachedFont::Missing(error) => {
622 if candidate == font {
623 font_error = Some(error);
624 }
625 None
626 }
627 }
628 };
629
630 if let Some(font_id) = lookup_font(font) {
631 Some(font_id)
632 } else {
633 let font_id = self.fallback_font_stack.iter().find_map(lookup_font);
634 if let Some(font_id) = font_id {
635 font_ids.with_upgraded(|font_ids| {
636 font_ids.insert(font.clone(), CachedFont::Fallback(font_id));
637 });
638 }
639 font_id
640 }
641 };
642 if let Some(error) = font_error {
643 log::warn!("failed to load font {font:?}: {error:#}");
644 }
645 font_id.unwrap_or_else(|| {
646 panic!(
647 "failed to resolve font '{}' or any of the fallbacks: {}",
648 font.family,
649 self.fallback_font_stack
650 .iter()
651 .map(|fallback| &fallback.family)
652 .join(", ")
653 );
654 })
655 }
656}
657
658#[derive(Deref)]
660pub struct WindowTextSystem {
661 line_layout_cache: LineLayoutCache,
662 font_runs_buffer: Cell<Vec<FontRun>>,
663 #[deref]
664 text_system: Arc<TextSystem>,
665}
666
667impl WindowTextSystem {
668 pub fn new(text_system: Arc<TextSystem>) -> Self {
670 Self {
671 line_layout_cache: LineLayoutCache::new(
672 text_system.platform_text_system.clone(),
673 text_system.font_generation.clone(),
674 ),
675 font_runs_buffer: Cell::default(),
676 text_system,
677 }
678 }
679
680 pub(crate) fn layout_index(&self) -> LineLayoutIndex {
681 self.line_layout_cache.layout_index()
682 }
683
684 pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
685 self.line_layout_cache.reuse_layouts(index)
686 }
687
688 pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
689 self.line_layout_cache.truncate_layouts(index)
690 }
691
692 pub fn shape_line(
699 &self,
700 text: SharedString,
701 font_size: Pixels,
702 runs: &[TextRun],
703 force_width: Option<Pixels>,
704 ) -> ShapedLine {
705 debug_assert!(
706 text.find('\n').is_none(),
707 "text argument should not contain newlines"
708 );
709
710 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
711 for run in runs {
712 if let Some(last_run) = decoration_runs.last_mut()
713 && last_run.color == run.color
714 && last_run.underline == run.underline
715 && last_run.strikethrough == run.strikethrough
716 && last_run.background_color == run.background_color
717 {
718 last_run.len += run.len as u32;
719 continue;
720 }
721 decoration_runs.push(DecorationRun {
722 len: run.len as u32,
723 color: run.color,
724 background_color: run.background_color,
725 underline: run.underline,
726 strikethrough: run.strikethrough,
727 });
728 }
729
730 let layout = self.layout_line(&text, font_size, runs, force_width);
731
732 ShapedLine {
733 layout,
734 text,
735 decoration_runs,
736 }
737 }
738
739 pub fn shape_text(
743 &self,
744 text: SharedString,
745 font_size: Pixels,
746 runs: &[TextRun],
747 wrap_width: Option<Pixels>,
748 line_clamp: Option<usize>,
749 ) -> Result<SmallVec<[WrappedLine; 1]>> {
750 let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
751 let mut font_runs = self.font_runs_buffer.take();
752
753 let mut lines = SmallVec::new();
754 let max_wrap_lines = line_clamp;
755 let mut wrapped_lines = 0;
756
757 let mut process_line = |line_text: SharedString, line_start, line_end| {
758 font_runs.clear();
759
760 let mut decoration_runs = <Vec<DecorationRun>>::with_capacity(32);
761 let mut run_start = line_start;
762 while run_start < line_end {
763 let Some(run) = runs.peek_mut() else {
764 log::warn!("`TextRun`s do not cover the entire to be shaped text");
765 break;
766 };
767
768 let run_len_within_line = cmp::min(line_end - run_start, run.len);
769
770 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
771 && last_run.color == run.color
772 && last_run.underline == run.underline
773 && last_run.strikethrough == run.strikethrough
774 && last_run.background_color == run.background_color
775 {
776 last_run.len += run_len_within_line as u32;
777 false
778 } else {
779 decoration_runs.push(DecorationRun {
780 len: run_len_within_line as u32,
781 color: run.color,
782 background_color: run.background_color,
783 underline: run.underline,
784 strikethrough: run.strikethrough,
785 });
786 true
787 };
788
789 let font_id = self.resolve_font(&run.font);
790 if let Some(font_run) = font_runs.last_mut()
791 && font_id == font_run.font_id
792 && !decoration_changed
793 {
794 font_run.len += run_len_within_line;
795 } else {
796 font_runs.push(FontRun {
797 len: run_len_within_line,
798 font_id,
799 });
800 }
801
802 run.len -= run_len_within_line;
804 if run.len == 0 {
805 runs.next();
806 }
807 run_start += run_len_within_line;
808 }
809
810 let layout = self.line_layout_cache.layout_wrapped_line(
811 &line_text,
812 font_size,
813 &font_runs,
814 wrap_width,
815 max_wrap_lines.map(|max| max.saturating_sub(wrapped_lines)),
816 );
817 wrapped_lines += layout.wrap_boundaries.len();
818
819 lines.push(WrappedLine {
820 layout,
821 decoration_runs,
822 text: line_text,
823 });
824
825 if let Some(run) = runs.peek_mut() {
827 run.len -= 1;
828 if run.len == 0 {
829 runs.next();
830 }
831 }
832 };
833
834 let mut split_lines = text.split('\n');
835
836 if let Some(first_line) = split_lines.next()
838 && let Some(second_line) = split_lines.next()
839 {
840 let mut line_start = 0;
841 process_line(
842 SharedString::new(first_line),
843 line_start,
844 line_start + first_line.len(),
845 );
846 line_start += first_line.len() + '\n'.len_utf8();
847 process_line(
848 SharedString::new(second_line),
849 line_start,
850 line_start + second_line.len(),
851 );
852 for line_text in split_lines {
853 line_start += line_text.len() + '\n'.len_utf8();
854 process_line(
855 SharedString::new(line_text),
856 line_start,
857 line_start + line_text.len(),
858 );
859 }
860 } else {
861 let end = text.len();
862 process_line(text, 0, end);
863 }
864
865 self.font_runs_buffer.set(font_runs);
866
867 Ok(lines)
868 }
869
870 pub(crate) fn finish_frame(&self) {
871 self.line_layout_cache.finish_frame()
872 }
873
874 pub fn layout_line(
879 &self,
880 text: &str,
881 font_size: Pixels,
882 runs: &[TextRun],
883 force_width: Option<Pixels>,
884 ) -> Arc<LineLayout> {
885 let mut last_run = None::<&TextRun>;
886 let mut font_runs = self.font_runs_buffer.take();
887 font_runs.clear();
888
889 for run in runs.iter() {
890 let decoration_changed = if let Some(last_run) = last_run
891 && last_run.color == run.color
892 && last_run.underline == run.underline
893 && last_run.strikethrough == run.strikethrough
894 {
897 false
898 } else {
899 last_run = Some(run);
900 true
901 };
902
903 let font_id = self.resolve_font(&run.font);
904 if let Some(font_run) = font_runs.last_mut()
905 && font_id == font_run.font_id
906 && !decoration_changed
907 {
908 font_run.len += run.len;
909 } else {
910 font_runs.push(FontRun {
911 len: run.len,
912 font_id,
913 });
914 }
915 }
916
917 let layout = self.line_layout_cache.layout_line(
918 &SharedString::new(text),
919 font_size,
920 &font_runs,
921 force_width,
922 );
923
924 self.font_runs_buffer.set(font_runs);
925
926 layout
927 }
928
929 pub fn layout_paragraph(
933 &self,
934 text: SharedString,
935 font_size: Pixels,
936 runs: &[FontRun],
937 width: Pixels,
938 ) -> Result<Arc<dyn PlatformParagraphLayout>> {
939 self.line_layout_cache
940 .layout_paragraph(text, font_size, runs, width)
941 }
942
943 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
945 let mut buffer = [0; 4];
946 let buffer: &_ = ch.encode_utf8(&mut buffer);
947 self.line_layout_cache
948 .layout_line(
949 buffer,
950 font_size,
951 &[FontRun {
952 len: buffer.len(),
953 font_id,
954 }],
955 None,
956 )
957 .width
958 }
959
960 pub fn em_layout_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
962 self.layout_width(font_id, font_size, 'm')
963 }
964}
965
966#[derive(Hash, Eq, PartialEq)]
967struct FontIdWithSize {
968 font_id: FontId,
969 font_size: Pixels,
970}
971
972pub struct LineWrapperHandle {
974 wrapper: Option<LineWrapper>,
975 text_system: Arc<TextSystem>,
976}
977
978impl Drop for LineWrapperHandle {
979 fn drop(&mut self) {
980 let mut state = self.text_system.wrapper_pool.lock();
981 let wrapper = self.wrapper.take().unwrap();
982 state
983 .get_mut(&FontIdWithSize {
984 font_id: wrapper.font_id,
985 font_size: wrapper.font_size,
986 })
987 .unwrap()
988 .push(wrapper.into_char_widths());
989 }
990}
991
992impl Deref for LineWrapperHandle {
993 type Target = LineWrapper;
994
995 fn deref(&self) -> &Self::Target {
996 self.wrapper.as_ref().unwrap()
997 }
998}
999
1000impl DerefMut for LineWrapperHandle {
1001 fn deref_mut(&mut self) -> &mut Self::Target {
1002 self.wrapper.as_mut().unwrap()
1003 }
1004}
1005
1006#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)]
1009#[serde(transparent)]
1010pub struct FontWeight(pub f32);
1011
1012impl Display for FontWeight {
1013 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1014 write!(f, "{}", self.0)
1015 }
1016}
1017
1018impl From<f32> for FontWeight {
1019 fn from(weight: f32) -> Self {
1020 FontWeight(weight)
1021 }
1022}
1023
1024impl Default for FontWeight {
1025 #[inline]
1026 fn default() -> FontWeight {
1027 FontWeight::NORMAL
1028 }
1029}
1030
1031impl Hash for FontWeight {
1032 fn hash<H: Hasher>(&self, state: &mut H) {
1033 state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
1034 }
1035}
1036
1037impl Eq for FontWeight {}
1038
1039impl FontWeight {
1040 pub const THIN: FontWeight = FontWeight(100.0);
1042 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
1044 pub const LIGHT: FontWeight = FontWeight(300.0);
1046 pub const NORMAL: FontWeight = FontWeight(400.0);
1048 pub const MEDIUM: FontWeight = FontWeight(500.0);
1050 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
1052 pub const BOLD: FontWeight = FontWeight(700.0);
1054 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
1056 pub const BLACK: FontWeight = FontWeight(900.0);
1058
1059 pub const ALL: [FontWeight; 9] = [
1061 Self::THIN,
1062 Self::EXTRA_LIGHT,
1063 Self::LIGHT,
1064 Self::NORMAL,
1065 Self::MEDIUM,
1066 Self::SEMIBOLD,
1067 Self::BOLD,
1068 Self::EXTRA_BOLD,
1069 Self::BLACK,
1070 ];
1071}
1072
1073impl schemars::JsonSchema for FontWeight {
1074 fn schema_name() -> std::borrow::Cow<'static, str> {
1075 "FontWeight".into()
1076 }
1077
1078 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
1079 use schemars::json_schema;
1080 json_schema!({
1081 "type": "number",
1082 "minimum": Self::THIN,
1083 "maximum": Self::BLACK,
1084 "default": Self::default(),
1085 "description": "Font weight value between 100 (thin) and 900 (black)"
1086 })
1087 }
1088}
1089
1090#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
1092pub enum FontStyle {
1093 #[default]
1095 Normal,
1096 Italic,
1098 Oblique,
1100}
1101
1102impl Display for FontStyle {
1103 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1104 Debug::fmt(self, f)
1105 }
1106}
1107
1108#[derive(Clone, Debug, PartialEq, Eq, Default)]
1110pub struct TextRun {
1111 pub len: usize,
1113 pub font: Font,
1115 pub color: Hsla,
1117 pub background_color: Option<Hsla>,
1119 pub underline: Option<UnderlineStyle>,
1121 pub strikethrough: Option<StrikethroughStyle>,
1123}
1124
1125#[cfg(all(target_os = "macos", test))]
1126impl TextRun {
1127 fn with_len(&self, len: usize) -> Self {
1128 let mut this = self.clone();
1129 this.len = len;
1130 this
1131 }
1132}
1133
1134#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1136#[repr(C)]
1137pub struct GlyphId(pub u32);
1138
1139#[derive(Clone, Debug, PartialEq)]
1145#[expect(missing_docs)]
1146pub struct RenderGlyphParams {
1147 pub font_id: FontId,
1148 pub glyph_id: GlyphId,
1149 pub font_size: Pixels,
1150 pub subpixel_variant: Point<u8>,
1151 pub scale_factor: f32,
1152 pub is_emoji: bool,
1153 pub subpixel_rendering: bool,
1154 pub dilation: u8,
1155}
1156
1157impl Eq for RenderGlyphParams {}
1158
1159impl Hash for RenderGlyphParams {
1160 fn hash<H: Hasher>(&self, state: &mut H) {
1161 self.font_id.0.hash(state);
1162 self.glyph_id.0.hash(state);
1163 self.font_size.0.to_bits().hash(state);
1164 self.subpixel_variant.hash(state);
1165 self.scale_factor.to_bits().hash(state);
1166 self.is_emoji.hash(state);
1167 self.subpixel_rendering.hash(state);
1168 self.dilation.hash(state);
1169 }
1170}
1171
1172#[derive(Clone, Debug, Eq, PartialEq, Hash)]
1174pub struct Font {
1175 pub family: SharedString,
1179
1180 pub features: FontFeatures,
1182
1183 pub variations: FontVariations,
1185
1186 pub fallbacks: Option<FontFallbacks>,
1188
1189 pub weight: FontWeight,
1191
1192 pub style: FontStyle,
1194}
1195
1196impl Default for Font {
1197 fn default() -> Self {
1198 font(".SystemUIFont")
1199 }
1200}
1201
1202pub fn font(family: impl Into<SharedString>) -> Font {
1204 Font {
1205 family: family.into(),
1206 features: FontFeatures::default(),
1207 variations: FontVariations::default(),
1208 weight: FontWeight::default(),
1209 style: FontStyle::default(),
1210 fallbacks: None,
1211 }
1212}
1213
1214impl Font {
1215 pub fn bold(mut self) -> Self {
1217 self.weight = FontWeight::BOLD;
1218 self
1219 }
1220
1221 pub fn italic(mut self) -> Self {
1223 self.style = FontStyle::Italic;
1224 self
1225 }
1226}
1227
1228#[derive(Clone, Copy, Debug)]
1231pub struct FontMetrics {
1232 pub units_per_em: u32,
1235
1236 pub ascent: f32,
1238
1239 pub descent: f32,
1241
1242 pub line_gap: f32,
1244
1245 pub underline_position: f32,
1247
1248 pub underline_thickness: f32,
1250
1251 pub cap_height: f32,
1253
1254 pub x_height: f32,
1256
1257 pub bounding_box: Bounds<f32>,
1260}
1261
1262impl FontMetrics {
1263 pub fn ascent(&self, font_size: Pixels) -> Pixels {
1265 Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
1266 }
1267
1268 pub fn descent(&self, font_size: Pixels) -> Pixels {
1270 Pixels((self.descent / self.units_per_em as f32) * font_size.0)
1271 }
1272
1273 pub fn line_gap(&self, font_size: Pixels) -> Pixels {
1275 Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
1276 }
1277
1278 pub fn underline_position(&self, font_size: Pixels) -> Pixels {
1280 Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
1281 }
1282
1283 pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
1285 Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
1286 }
1287
1288 pub fn cap_height(&self, font_size: Pixels) -> Pixels {
1290 Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
1291 }
1292
1293 pub fn x_height(&self, font_size: Pixels) -> Pixels {
1295 Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
1296 }
1297
1298 pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
1300 (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
1301 }
1302}
1303
1304#[allow(unused)]
1306pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
1307 match name {
1311 ".SystemUIFont" => system,
1312 ".ZedSans" | "Zed Plex Sans" => "IBM Plex Sans",
1313 ".ZedMono" | "Zed Plex Mono" => "Lilex",
1314 _ => name,
1315 }
1316}
1317
1318#[allow(unused)]
1320pub fn font_name_with_fallbacks_shared<'a>(
1321 name: &'a SharedString,
1322 system: &'a SharedString,
1323) -> &'a SharedString {
1324 match name.as_str() {
1328 ".SystemUIFont" => system,
1329 ".ZedSans" | "Zed Plex Sans" => const { &SharedString::new_static("IBM Plex Sans") },
1330 ".ZedMono" | "Zed Plex Mono" => const { &SharedString::new_static("Lilex") },
1331 _ => name,
1332 }
1333}
1334
1335#[cfg(test)]
1336mod font_resolution_tests {
1337 use super::*;
1338 use crate::NoopTextSystem;
1339
1340 #[test]
1341 fn caches_resolved_font_fallbacks() {
1342 let mut text_system = TextSystem::new(Arc::new(NoopTextSystem::new()));
1343 let requested = font("Missing Font").bold().italic();
1344 let missing = CachedFont::Missing(Arc::new(anyhow::anyhow!("missing font")));
1345 {
1346 let mut font_ids = text_system.font_ids_by_font.write();
1347 font_ids.insert(requested.clone(), missing.clone());
1348 for fallback in &text_system.fallback_font_stack {
1349 font_ids.insert(fallback.clone(), missing.clone());
1350 }
1351 font_ids.insert(font("Arial"), CachedFont::Direct(FontId(42)));
1352 }
1353
1354 assert_eq!(text_system.resolve_font(&requested), FontId(42));
1355 assert_eq!(text_system.get_font_for_id(FontId(42)), Some(font("Arial")));
1356
1357 text_system.fallback_font_stack.clear();
1358 assert_eq!(text_system.resolve_font(&requested), FontId(42));
1359 }
1360}
1361
1362#[cfg(test)]
1363mod line_wrapper_pool_tests {
1364 use super::*;
1365 use crate::NoopTextSystem;
1366
1367 #[test]
1368 fn pooled_line_wrappers_do_not_retain_text_system() {
1369 let text_system = Arc::new(TextSystem::new(Arc::new(NoopTextSystem::new())));
1370 let weak_text_system = Arc::downgrade(&text_system);
1371
1372 let mut wrapper = text_system.line_wrapper(font(".ZedMono"), px(16.));
1373 let fragments = [LineFragment::text("hello world")];
1374 wrapper
1375 .wrap_line(&fragments, px(40.), IndentAdjustment::default())
1376 .for_each(drop);
1377 drop(wrapper);
1378 drop(text_system);
1379
1380 assert!(weak_text_system.upgrade().is_none());
1381 }
1382}
1383
1384#[cfg(test)]
1385mod missing_glyph_tests {
1386 use super::*;
1387 use futures::FutureExt as _;
1388
1389 #[test]
1390 fn bounds_retained_missing_glyphs() {
1391 let (reporter, mut receiver) = missing_glyph_channel();
1392 reporter.report(
1393 (0..MAX_REPORTED_MISSING_GLYPHS)
1394 .map(|index| {
1395 MissingGlyph::new(index.to_string().into(), FallbackFontClass::Proportional)
1396 })
1397 .collect(),
1398 );
1399 assert!(receiver.recv().now_or_never().unwrap().is_ok());
1400
1401 let newest = MissingGlyph::new("newest".into(), FallbackFontClass::Monospace);
1402 reporter.report(vec![newest.clone()]);
1403 assert!(receiver.recv().now_or_never().unwrap().is_ok());
1404
1405 let state = &receiver.state;
1406 assert_eq!(state.reported.len(), MAX_REPORTED_MISSING_GLYPHS);
1407 assert_eq!(state.reported_order.len(), MAX_REPORTED_MISSING_GLYPHS);
1408 assert!(state.reported.contains(&newest));
1409 }
1410
1411 #[test]
1412 fn dropping_receiver_closes_and_clears_reports() {
1413 let (reporter, receiver) = missing_glyph_channel();
1414 reporter.report(vec![missing_glyph("missing")]);
1415
1416 drop(receiver);
1417
1418 assert!(reporter.sender.is_closed());
1419 assert!(reporter.sender.is_empty());
1420 }
1421
1422 fn missing_glyph_channel() -> (MissingGlyphReporter, MissingGlyphReceiver) {
1423 let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
1424 let generation = Arc::<AtomicUsize>::default();
1425 let reporter = MissingGlyphReporter {
1426 generation: generation.clone(),
1427 sender,
1428 };
1429 let receiver = MissingGlyphReceiver {
1430 state: MissingGlyphState::default(),
1431 generation,
1432 receiver,
1433 };
1434 (reporter, receiver)
1435 }
1436
1437 fn missing_glyph(grapheme: &'static str) -> MissingGlyph {
1438 MissingGlyph::new(grapheme.into(), FallbackFontClass::Proportional)
1439 }
1440}