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