1use crate::{
2 Bounds, DevicePixels, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
3 StrikethroughStyle, TextRenderingMode, UnderlineStyle, px,
4};
5use anyhow::{Context as _, anyhow};
6use collections::FxHashMap;
7use core::fmt;
8use derive_more::{Add, Deref, FromStr, Sub};
9use itertools::Itertools;
10use palette::Hsla;
11use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use smallvec::{SmallVec, smallvec};
15use std::{
16 borrow::Cow,
17 cmp,
18 fmt::{Debug, Display, Formatter},
19 hash::{Hash, Hasher},
20 ops::{Deref, DerefMut, Range},
21 sync::Arc,
22};
23
24mod font_fallbacks;
25mod font_features;
26mod line;
27mod line_layout;
28mod line_wrapper;
29
30pub use font_fallbacks::*;
31pub use font_features::*;
32pub use line::*;
33pub use line_layout::*;
34pub use line_wrapper::*;
35
36#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
38#[repr(C)]
39pub struct FontId(pub usize);
40
41#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
43pub struct FontFamilyId(pub usize);
44
45pub const SUBPIXEL_VARIANTS_X: u8 = 4;
47
48pub const SUBPIXEL_VARIANTS_Y: u8 = 1;
50
51pub struct TextSystem {
53 platform_text_system: Arc<dyn PlatformTextSystem>,
54 font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
55 font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
56 raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
57 wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
58 font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
59 fallback_font_stack: SmallVec<[Font; 2]>,
60}
61
62impl TextSystem {
63 pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
65 TextSystem {
66 platform_text_system,
67 font_metrics: RwLock::default(),
68 raster_bounds: RwLock::default(),
69 font_ids_by_font: RwLock::default(),
70 wrapper_pool: Mutex::default(),
71 font_runs_pool: Mutex::default(),
72 fallback_font_stack: smallvec![
73 font(".ZedMono"),
75 font(".ZedSans"),
76 font("Helvetica"),
77 font("Segoe UI"), font("Ubuntu"), font("Adwaita Sans"), font("Cantarell"), font("Noto Sans"), font("DejaVu Sans"),
83 font("Arial"), ],
85 }
86 }
87
88 pub fn all_font_names(&self) -> Vec<String> {
90 let mut names = self.platform_text_system.all_font_names();
91 names.extend(
92 self.fallback_font_stack
93 .iter()
94 .map(|font| font.family.to_string()),
95 );
96 names.push(".SystemUIFont".to_string());
97 names.sort_unstable();
98 names.dedup();
99 names
100 }
101
102 pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
104 self.platform_text_system.add_fonts(fonts)
105 }
106
107 fn font_id(&self, font: &Font) -> Result<FontId> {
109 fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
110 match font_id {
111 Ok(font_id) => Ok(*font_id),
112 Err(err) => Err(anyhow!("{err}")),
113 }
114 }
115
116 let font_id = self
117 .font_ids_by_font
118 .read()
119 .get(font)
120 .map(clone_font_id_result);
121 if let Some(font_id) = font_id {
122 font_id
123 } else {
124 let font_id = self.platform_text_system.font_id(font);
125 self.font_ids_by_font
126 .write()
127 .insert(font.clone(), clone_font_id_result(&font_id));
128 font_id
129 }
130 }
131
132 pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
134 let lock = self.font_ids_by_font.read();
135 lock.iter()
136 .filter_map(|(font, result)| match result {
137 Ok(font_id) if *font_id == id => Some(font.clone()),
138 _ => None,
139 })
140 .next()
141 }
142
143 pub fn resolve_font(&self, font: &Font) -> FontId {
150 if let Ok(font_id) = self.font_id(font) {
151 return font_id;
152 }
153 for fallback in &self.fallback_font_stack {
154 if let Ok(font_id) = self.font_id(fallback) {
155 return font_id;
156 }
157 }
158
159 panic!(
160 "failed to resolve font '{}' or any of the fallbacks: {}",
161 font.family,
162 self.fallback_font_stack
163 .iter()
164 .map(|fallback| &fallback.family)
165 .join(", ")
166 );
167 }
168
169 pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
173 self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
174 }
175
176 pub fn typographic_bounds(
178 &self,
179 font_id: FontId,
180 font_size: Pixels,
181 character: char,
182 ) -> Result<Bounds<Pixels>> {
183 let glyph_id = self
184 .platform_text_system
185 .glyph_for_char(font_id, character)
186 .with_context(|| format!("glyph not found for character '{character}'"))?;
187 let bounds = self
188 .platform_text_system
189 .typographic_bounds(font_id, glyph_id)?;
190 Ok(self.read_metrics(font_id, |metrics| {
191 (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
192 }))
193 }
194
195 pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
197 let glyph_id = self
198 .platform_text_system
199 .glyph_for_char(font_id, ch)
200 .with_context(|| format!("glyph not found for character '{ch}'"))?;
201 let result = self.platform_text_system.advance(font_id, glyph_id)?
202 / self.units_per_em(font_id) as f32;
203
204 Ok(result * font_size)
205 }
206
207 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
210 let mut buffer = [0; 4];
211 let buffer = ch.encode_utf8(&mut buffer);
212 self.platform_text_system
213 .layout_line(
214 buffer,
215 font_size,
216 &[FontRun {
217 len: buffer.len(),
218 font_id,
219 letter_spacing: None,
220 }],
221 )
222 .width
223 }
224
225 pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
229 Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
230 }
231
232 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
236 Ok(self.advance(font_id, font_size, 'm')?.width)
237 }
238
239 pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
243 Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
244 }
245
246 pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
250 Ok(self.advance(font_id, font_size, '0')?.width)
251 }
252
253 pub fn units_per_em(&self, font_id: FontId) -> u32 {
257 self.read_metrics(font_id, |metrics| metrics.units_per_em)
258 }
259
260 pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
262 self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
263 }
264
265 pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
267 self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
268 }
269
270 pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
272 self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
273 }
274
275 pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
278 self.read_metrics(font_id, |metrics| metrics.descent(font_size))
279 }
280
281 pub fn baseline_offset(
283 &self,
284 font_id: FontId,
285 font_size: Pixels,
286 line_height: Pixels,
287 ) -> Pixels {
288 let ascent = self.ascent(font_id, font_size);
289 let descent = self.descent(font_id, font_size);
290 let padding_top = (line_height - ascent - descent) / 2.;
291 padding_top + ascent
292 }
293
294 fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
295 let lock = self.font_metrics.upgradable_read();
296
297 if let Some(metrics) = lock.get(&font_id) {
298 read(metrics)
299 } else {
300 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
301 let metrics = lock
302 .entry(font_id)
303 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
304 read(metrics)
305 }
306 }
307
308 pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
310 let lock = &mut self.wrapper_pool.lock();
311 let font_id = self.resolve_font(&font);
312 let wrappers = lock
313 .entry(FontIdWithSize { font_id, font_size })
314 .or_default();
315 let wrapper = wrappers
316 .pop()
317 .unwrap_or_else(|| LineWrapper::new(font_id, font_size, self.clone()));
318
319 LineWrapperHandle {
320 wrapper: Some(wrapper),
321 text_system: self.clone(),
322 }
323 }
324
325 pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
327 let raster_bounds = self.raster_bounds.upgradable_read();
328 if let Some(bounds) = raster_bounds.get(params) {
329 Ok(*bounds)
330 } else {
331 let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
332 let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
333 raster_bounds.insert(params.clone(), bounds);
334 Ok(bounds)
335 }
336 }
337
338 pub(crate) fn rasterize_glyph(
339 &self,
340 params: &RenderGlyphParams,
341 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
342 let raster_bounds = self.raster_bounds(params)?;
343 self.platform_text_system
344 .rasterize_glyph(params, raster_bounds)
345 }
346
347 pub(crate) fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
349 self.platform_text_system.glyph_dilation_for_color(color)
350 }
351
352 pub(crate) fn recommended_rendering_mode(
355 &self,
356 font_id: FontId,
357 font_size: Pixels,
358 ) -> TextRenderingMode {
359 self.platform_text_system
360 .recommended_rendering_mode(font_id, font_size)
361 }
362}
363
364#[cfg(test)]
365impl TextSystem {
366 pub(crate) fn platform_text_system_for_tests(&self) -> Arc<dyn PlatformTextSystem> {
368 self.platform_text_system.clone()
369 }
370}
371
372#[derive(Deref)]
374pub struct WindowTextSystem {
375 line_layout_cache: LineLayoutCache,
376 #[deref]
377 text_system: Arc<TextSystem>,
378}
379
380impl WindowTextSystem {
381 pub fn new(text_system: Arc<TextSystem>) -> Self {
383 Self {
384 line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
385 text_system,
386 }
387 }
388
389 pub(crate) fn layout_index(&self) -> LineLayoutIndex {
390 self.line_layout_cache.layout_index()
391 }
392
393 pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
394 self.line_layout_cache.reuse_layouts(index)
395 }
396
397 pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
398 self.line_layout_cache.truncate_layouts(index)
399 }
400
401 pub fn shape_line(
408 &self,
409 text: SharedString,
410 font_size: Pixels,
411 runs: &[TextRun],
412 force_width: Option<Pixels>,
413 ) -> ShapedLine {
414 debug_assert!(
415 text.find('\n').is_none(),
416 "text argument should not contain newlines"
417 );
418
419 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
420 for run in runs {
421 if let Some(last_run) = decoration_runs.last_mut()
422 && last_run.color == run.color
423 && last_run.underline == run.underline
424 && last_run.strikethrough == run.strikethrough
425 && last_run.background_color == run.background_color
426 {
427 last_run.len += run.len as u32;
428 continue;
429 }
430 decoration_runs.push(DecorationRun {
431 len: run.len as u32,
432 color: run.color,
433 background_color: run.background_color,
434 underline: run.underline,
435 strikethrough: run.strikethrough,
436 });
437 }
438
439 let layout = self.layout_line(&text, font_size, runs, force_width);
440
441 ShapedLine {
442 layout,
443 text,
444 decoration_runs,
445 }
446 }
447
448 pub fn shape_line_by_hash(
459 &self,
460 text_hash: u64,
461 text_len: usize,
462 font_size: Pixels,
463 runs: &[TextRun],
464 force_width: Option<Pixels>,
465 materialize_text: impl FnOnce() -> SharedString,
466 ) -> ShapedLine {
467 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
468 for run in runs {
469 if let Some(last_run) = decoration_runs.last_mut()
470 && last_run.color == run.color
471 && last_run.underline == run.underline
472 && last_run.strikethrough == run.strikethrough
473 && last_run.background_color == run.background_color
474 {
475 last_run.len += run.len as u32;
476 continue;
477 }
478 decoration_runs.push(DecorationRun {
479 len: run.len as u32,
480 color: run.color,
481 background_color: run.background_color,
482 underline: run.underline,
483 strikethrough: run.strikethrough,
484 });
485 }
486
487 let mut used_force_width = force_width;
488 let layout = self.layout_line_by_hash(
489 text_hash,
490 text_len,
491 font_size,
492 runs,
493 used_force_width,
494 || {
495 let text = materialize_text();
496 debug_assert!(
497 text.find('\n').is_none(),
498 "text argument should not contain newlines"
499 );
500 text
501 },
502 );
503
504 let text: SharedString = SharedString::new_static("");
508
509 ShapedLine {
510 layout,
511 text,
512 decoration_runs,
513 }
514 }
515
516 pub fn shape_text<S: AsRef<str> + Into<SharedString>>(
529 &self,
530 text: S,
531 font_size: Pixels,
532 runs: &[TextRun],
533 wrap_width: Option<Pixels>,
534 line_clamp: Option<usize>,
535 ) -> Result<SmallVec<[WrappedLine; 1]>> {
536 let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
537 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
538
539 let mut lines = SmallVec::new();
540 let mut max_wrap_lines = line_clamp;
541 let mut wrapped_lines = 0;
542
543 let mut process_line = |line_text: SharedString, line_start, line_end| {
544 font_runs.clear();
545
546 let mut decoration_runs = <Vec<DecorationRun>>::with_capacity(32);
547 let mut run_start = line_start;
548 while run_start < line_end {
549 let Some(run) = runs.peek_mut() else {
550 log::warn!("`TextRun`s do not cover the entire to be shaped text");
551 break;
552 };
553
554 let run_len_within_line = cmp::min(line_end - run_start, run.len);
555
556 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
557 && last_run.color == run.color
558 && last_run.underline == run.underline
559 && last_run.strikethrough == run.strikethrough
560 && last_run.background_color == run.background_color
561 {
562 last_run.len += run_len_within_line as u32;
563 false
564 } else {
565 decoration_runs.push(DecorationRun {
566 len: run_len_within_line as u32,
567 color: run.color,
568 background_color: run.background_color,
569 underline: run.underline,
570 strikethrough: run.strikethrough,
571 });
572 true
573 };
574
575 let font_id = self.resolve_font(&run.font);
576 let letter_spacing = run.letter_spacing;
577 if let Some(font_run) = font_runs.last_mut()
578 && font_id == font_run.font_id
579 && font_run.letter_spacing == letter_spacing
580 && !decoration_changed
581 {
582 font_run.len += run_len_within_line;
583 } else {
584 font_runs.push(FontRun {
585 len: run_len_within_line,
586 font_id,
587 letter_spacing,
588 });
589 }
590
591 run.len -= run_len_within_line;
593 if run.len == 0 {
594 runs.next();
595 }
596 run_start += run_len_within_line;
597 }
598
599 let layout = self.line_layout_cache.layout_wrapped_line(
600 &line_text,
601 font_size,
602 &font_runs,
603 wrap_width,
604 max_wrap_lines.map(|max| max.saturating_sub(wrapped_lines)),
605 );
606 wrapped_lines += layout.wrap_boundaries.len();
607
608 lines.push(WrappedLine {
609 layout,
610 decoration_runs,
611 text: line_text,
612 });
613
614 if let Some(run) = runs.peek_mut() {
616 run.len -= 1;
617 if run.len == 0 {
618 runs.next();
619 }
620 }
621 };
622
623 let mut split_lines = text.as_ref().split('\n');
624
625 if let Some(first_line) = split_lines.next()
627 && let Some(second_line) = split_lines.next()
628 {
629 let mut line_start = 0;
630 process_line(
631 SharedString::new(first_line),
632 line_start,
633 line_start + first_line.len(),
634 );
635 line_start += first_line.len() + '\n'.len_utf8();
636 process_line(
637 SharedString::new(second_line),
638 line_start,
639 line_start + second_line.len(),
640 );
641 for line_text in split_lines {
642 line_start += line_text.len() + '\n'.len_utf8();
643 process_line(
644 SharedString::new(line_text),
645 line_start,
646 line_start + line_text.len(),
647 );
648 }
649 } else {
650 let end = text.as_ref().len();
651 process_line(text.into(), 0, end);
652 }
653
654 self.font_runs_pool.lock().push(font_runs);
655
656 Ok(lines)
657 }
658
659 pub(crate) fn finish_frame(&self) {
660 self.line_layout_cache.finish_frame()
661 }
662
663 pub fn layout_line(
668 &self,
669 text: &str,
670 font_size: Pixels,
671 runs: &[TextRun],
672 force_width: Option<Pixels>,
673 ) -> Arc<LineLayout> {
674 let mut last_run = None::<&TextRun>;
675 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
676 font_runs.clear();
677
678 for run in runs.iter() {
679 let decoration_changed = if let Some(last_run) = last_run
680 && last_run.color == run.color
681 && last_run.underline == run.underline
682 && last_run.strikethrough == run.strikethrough
683 {
686 false
687 } else {
688 last_run = Some(run);
689 true
690 };
691
692 let font_id = self.resolve_font(&run.font);
693 let letter_spacing = run.letter_spacing;
694 if let Some(font_run) = font_runs.last_mut()
695 && font_id == font_run.font_id
696 && font_run.letter_spacing == letter_spacing
697 && !decoration_changed
698 {
699 font_run.len += run.len;
700 } else {
701 font_runs.push(FontRun {
702 len: run.len,
703 font_id,
704 letter_spacing,
705 });
706 }
707 }
708
709 let layout = self.line_layout_cache.layout_line(
710 &SharedString::new(text),
711 font_size,
712 &font_runs,
713 force_width,
714 );
715
716 self.font_runs_pool.lock().push(font_runs);
717
718 layout
719 }
720
721 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
723 let mut buffer = [0; 4];
724 let buffer: &_ = ch.encode_utf8(&mut buffer);
725 self.line_layout_cache
726 .layout_line(
727 buffer,
728 font_size,
729 &[FontRun {
730 len: buffer.len(),
731 font_id,
732 letter_spacing: None,
733 }],
734 None,
735 )
736 .width
737 }
738
739 pub fn em_layout_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
741 self.layout_width(font_id, font_size, 'm')
742 }
743
744 pub fn try_layout_line_by_hash(
753 &self,
754 text_hash: u64,
755 text_len: usize,
756 font_size: Pixels,
757 runs: &[TextRun],
758 force_width: Option<Pixels>,
759 ) -> Option<Arc<LineLayout>> {
760 let mut last_run = None::<&TextRun>;
761 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
762 font_runs.clear();
763
764 for run in runs.iter() {
765 let decoration_changed = if let Some(last_run) = last_run
766 && last_run.color == run.color
767 && last_run.underline == run.underline
768 && last_run.strikethrough == run.strikethrough
769 {
772 false
773 } else {
774 last_run = Some(run);
775 true
776 };
777
778 let font_id = self.resolve_font(&run.font);
779 let letter_spacing = run.letter_spacing;
780 if let Some(font_run) = font_runs.last_mut()
781 && font_id == font_run.font_id
782 && font_run.letter_spacing == letter_spacing
783 && !decoration_changed
784 {
785 font_run.len += run.len;
786 } else {
787 font_runs.push(FontRun {
788 len: run.len,
789 font_id,
790 letter_spacing,
791 });
792 }
793 }
794
795 let layout = self.line_layout_cache.try_layout_line_by_hash(
796 text_hash,
797 text_len,
798 font_size,
799 &font_runs,
800 force_width,
801 );
802
803 self.font_runs_pool.lock().push(font_runs);
804
805 layout
806 }
807
808 pub fn layout_line_by_hash(
817 &self,
818 text_hash: u64,
819 text_len: usize,
820 font_size: Pixels,
821 runs: &[TextRun],
822 force_width: Option<Pixels>,
823 materialize_text: impl FnOnce() -> SharedString,
824 ) -> Arc<LineLayout> {
825 let mut last_run = None::<&TextRun>;
826 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
827 font_runs.clear();
828
829 for run in runs.iter() {
830 let decoration_changed = if let Some(last_run) = last_run
831 && last_run.color == run.color
832 && last_run.underline == run.underline
833 && last_run.strikethrough == run.strikethrough
834 {
837 false
838 } else {
839 last_run = Some(run);
840 true
841 };
842
843 let font_id = self.resolve_font(&run.font);
844 let letter_spacing = run.letter_spacing;
845 if let Some(font_run) = font_runs.last_mut()
846 && font_id == font_run.font_id
847 && font_run.letter_spacing == letter_spacing
848 && !decoration_changed
849 {
850 font_run.len += run.len;
851 } else {
852 font_runs.push(FontRun {
853 len: run.len,
854 font_id,
855 letter_spacing,
856 });
857 }
858 }
859
860 let layout = self.line_layout_cache.layout_line_by_hash(
861 text_hash,
862 text_len,
863 font_size,
864 &font_runs,
865 force_width,
866 materialize_text,
867 );
868
869 self.font_runs_pool.lock().push(font_runs);
870
871 layout
872 }
873}
874
875#[derive(Hash, Eq, PartialEq)]
876struct FontIdWithSize {
877 font_id: FontId,
878 font_size: Pixels,
879}
880
881pub struct LineWrapperHandle {
883 wrapper: Option<LineWrapper>,
884 text_system: Arc<TextSystem>,
885}
886
887impl Drop for LineWrapperHandle {
888 fn drop(&mut self) {
889 let mut state = self.text_system.wrapper_pool.lock();
890 let mut wrapper = self.wrapper.take().unwrap();
891 wrapper.set_letter_spacing(None);
892 state
893 .get_mut(&FontIdWithSize {
894 font_id: wrapper.font_id,
895 font_size: wrapper.font_size,
896 })
897 .unwrap()
898 .push(wrapper);
899 }
900}
901
902impl Deref for LineWrapperHandle {
903 type Target = LineWrapper;
904
905 fn deref(&self) -> &Self::Target {
906 self.wrapper.as_ref().unwrap()
907 }
908}
909
910impl DerefMut for LineWrapperHandle {
911 fn deref_mut(&mut self) -> &mut Self::Target {
912 self.wrapper.as_mut().unwrap()
913 }
914}
915
916#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)]
919#[serde(transparent)]
920pub struct FontWeight(pub f32);
921
922impl Display for FontWeight {
923 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
924 write!(f, "{}", self.0)
925 }
926}
927
928impl From<f32> for FontWeight {
929 fn from(weight: f32) -> Self {
930 FontWeight(weight)
931 }
932}
933
934impl Default for FontWeight {
935 #[inline]
936 fn default() -> FontWeight {
937 FontWeight::NORMAL
938 }
939}
940
941impl Hash for FontWeight {
942 fn hash<H: Hasher>(&self, state: &mut H) {
943 state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
944 }
945}
946
947impl Eq for FontWeight {}
948
949impl FontWeight {
950 pub const THIN: FontWeight = FontWeight(100.0);
952 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
954 pub const LIGHT: FontWeight = FontWeight(300.0);
956 pub const NORMAL: FontWeight = FontWeight(400.0);
958 pub const MEDIUM: FontWeight = FontWeight(500.0);
960 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
962 pub const BOLD: FontWeight = FontWeight(700.0);
964 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
966 pub const BLACK: FontWeight = FontWeight(900.0);
968
969 pub const ALL: [FontWeight; 9] = [
971 Self::THIN,
972 Self::EXTRA_LIGHT,
973 Self::LIGHT,
974 Self::NORMAL,
975 Self::MEDIUM,
976 Self::SEMIBOLD,
977 Self::BOLD,
978 Self::EXTRA_BOLD,
979 Self::BLACK,
980 ];
981}
982
983impl schemars::JsonSchema for FontWeight {
984 fn schema_name() -> std::borrow::Cow<'static, str> {
985 "FontWeight".into()
986 }
987
988 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
989 use schemars::json_schema;
990 json_schema!({
991 "type": "number",
992 "minimum": Self::THIN,
993 "maximum": Self::BLACK,
994 "default": Self::default(),
995 "description": "Font weight value between 100 (thin) and 900 (black)"
996 })
997 }
998}
999
1000#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
1002pub enum FontStyle {
1003 #[default]
1005 Normal,
1006 Italic,
1008 Oblique,
1010}
1011
1012impl Display for FontStyle {
1013 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1014 Debug::fmt(self, f)
1015 }
1016}
1017
1018#[derive(Clone, Debug, PartialEq, Default)]
1020pub struct TextRun {
1021 pub len: usize,
1023 pub font: Font,
1025 pub color: Hsla,
1027 pub background_color: Option<Hsla>,
1029 pub underline: Option<UnderlineStyle>,
1031 pub strikethrough: Option<StrikethroughStyle>,
1033 pub letter_spacing: Option<Pixels>,
1035}
1036
1037#[cfg(all(target_os = "macos", test))]
1038impl TextRun {
1039 fn with_len(&self, len: usize) -> Self {
1040 let mut this = self.clone();
1041 this.len = len;
1042 this
1043 }
1044}
1045
1046#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1048#[repr(C)]
1049pub struct GlyphId(pub u32);
1050
1051#[derive(Clone, Debug, PartialEq)]
1057#[expect(missing_docs)]
1058pub struct RenderGlyphParams {
1059 pub font_id: FontId,
1060 pub glyph_id: GlyphId,
1061 pub font_size: Pixels,
1062 pub subpixel_variant: Point<u8>,
1063 pub scale_factor: f32,
1064 pub is_emoji: bool,
1065 pub subpixel_rendering: bool,
1066 pub dilation: u8,
1067}
1068
1069impl Eq for RenderGlyphParams {}
1070
1071impl Hash for RenderGlyphParams {
1072 fn hash<H: Hasher>(&self, state: &mut H) {
1073 self.font_id.0.hash(state);
1074 self.glyph_id.0.hash(state);
1075 self.font_size.0.to_bits().hash(state);
1076 self.subpixel_variant.hash(state);
1077 self.scale_factor.to_bits().hash(state);
1078 self.is_emoji.hash(state);
1079 self.subpixel_rendering.hash(state);
1080 self.dilation.hash(state);
1081 }
1082}
1083
1084#[derive(Clone, Debug, Eq, PartialEq, Hash)]
1086pub struct Font {
1087 pub family: SharedString,
1091
1092 pub features: FontFeatures,
1094
1095 pub fallbacks: Option<FontFallbacks>,
1097
1098 pub weight: FontWeight,
1100
1101 pub style: FontStyle,
1103}
1104
1105impl Default for Font {
1106 fn default() -> Self {
1107 font(".SystemUIFont")
1108 }
1109}
1110
1111pub fn font(family: impl Into<SharedString>) -> Font {
1113 Font {
1114 family: family.into(),
1115 features: FontFeatures::default(),
1116 weight: FontWeight::default(),
1117 style: FontStyle::default(),
1118 fallbacks: None,
1119 }
1120}
1121
1122impl Font {
1123 pub fn bold(mut self) -> Self {
1125 self.weight = FontWeight::BOLD;
1126 self
1127 }
1128
1129 pub fn italic(mut self) -> Self {
1131 self.style = FontStyle::Italic;
1132 self
1133 }
1134}
1135
1136#[derive(Clone, Copy, Debug)]
1139pub struct FontMetrics {
1140 pub units_per_em: u32,
1143
1144 pub ascent: f32,
1146
1147 pub descent: f32,
1149
1150 pub line_gap: f32,
1152
1153 pub underline_position: f32,
1155
1156 pub underline_thickness: f32,
1158
1159 pub cap_height: f32,
1161
1162 pub x_height: f32,
1164
1165 pub bounding_box: Bounds<f32>,
1168}
1169
1170impl FontMetrics {
1171 pub fn ascent(&self, font_size: Pixels) -> Pixels {
1173 Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
1174 }
1175
1176 pub fn descent(&self, font_size: Pixels) -> Pixels {
1178 Pixels((self.descent / self.units_per_em as f32) * font_size.0)
1179 }
1180
1181 pub fn line_gap(&self, font_size: Pixels) -> Pixels {
1183 Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
1184 }
1185
1186 pub fn underline_position(&self, font_size: Pixels) -> Pixels {
1188 Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
1189 }
1190
1191 pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
1193 Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
1194 }
1195
1196 pub fn cap_height(&self, font_size: Pixels) -> Pixels {
1198 Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
1199 }
1200
1201 pub fn x_height(&self, font_size: Pixels) -> Pixels {
1203 Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
1204 }
1205
1206 pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
1208 (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
1209 }
1210}
1211
1212#[allow(unused)]
1214pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
1215 match name {
1219 ".SystemUIFont" => system,
1220 ".ZedSans" | "Zed Plex Sans" => "IBM Plex Sans",
1221 ".ZedMono" | "Zed Plex Mono" => "Lilex",
1222 _ => name,
1223 }
1224}
1225
1226#[allow(unused)]
1228pub fn font_name_with_fallbacks_shared<'a>(
1229 name: &'a SharedString,
1230 system: &'a SharedString,
1231) -> &'a SharedString {
1232 match name.as_str() {
1236 ".SystemUIFont" => system,
1237 ".ZedSans" | "Zed Plex Sans" => const { &SharedString::new_static("IBM Plex Sans") },
1238 ".ZedMono" | "Zed Plex Mono" => const { &SharedString::new_static("Lilex") },
1239 _ => name,
1240 }
1241}