1mod font_fallbacks;
2mod font_features;
3mod line;
4mod line_layout;
5mod line_wrapper;
6
7pub use font_fallbacks::*;
8pub use font_features::*;
9pub use line::*;
10pub use line_layout::*;
11pub use line_wrapper::*;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
17 StrikethroughStyle, TextRenderingMode, UnderlineStyle, px,
18};
19use anyhow::{Context as _, anyhow};
20use collections::FxHashMap;
21use core::fmt;
22use derive_more::{Add, Deref, FromStr, Sub};
23use itertools::Itertools;
24use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
25use smallvec::{SmallVec, smallvec};
26use std::{
27 borrow::Cow,
28 cmp,
29 fmt::{Debug, Display, Formatter},
30 hash::{Hash, Hasher},
31 ops::{Deref, DerefMut, Range},
32 sync::Arc,
33};
34
35#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
37#[repr(C)]
38pub struct FontId(pub usize);
39
40#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
42pub struct FontFamilyId(pub usize);
43
44pub const SUBPIXEL_VARIANTS_X: u8 = 4;
46
47pub const SUBPIXEL_VARIANTS_Y: u8 = 1;
49
50pub struct TextSystem {
52 platform_text_system: Arc<dyn PlatformTextSystem>,
53 font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
54 font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
55 raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
56 wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
57 font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
58 fallback_font_stack: SmallVec<[Font; 2]>,
59}
60
61impl TextSystem {
62 pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
64 TextSystem {
65 platform_text_system,
66 font_metrics: RwLock::default(),
67 raster_bounds: RwLock::default(),
68 font_ids_by_font: RwLock::default(),
69 wrapper_pool: Mutex::default(),
70 font_runs_pool: Mutex::default(),
71 fallback_font_stack: smallvec![
72 font(".ZedMono"),
74 font(".ZedSans"),
75 font("Helvetica"),
76 font("Segoe UI"), font("Ubuntu"), font("Adwaita Sans"), font("Cantarell"), font("Noto Sans"), font("DejaVu Sans"),
82 font("Arial"), ],
84 }
85 }
86
87 pub fn all_font_names(&self) -> Vec<String> {
91 let mut names = self.platform_text_system.all_font_names();
92 names.sort_unstable();
93 names.dedup();
94 names
95 }
96
97 pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
99 self.platform_text_system.add_fonts(fonts)
100 }
101
102 fn font_id(&self, font: &Font) -> Result<FontId> {
104 fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
105 match font_id {
106 Ok(font_id) => Ok(*font_id),
107 Err(err) => Err(anyhow!("{err}")),
108 }
109 }
110
111 let font_id = self
112 .font_ids_by_font
113 .read()
114 .get(font)
115 .map(clone_font_id_result);
116 if let Some(font_id) = font_id {
117 font_id
118 } else {
119 let font_id = self.platform_text_system.font_id(font);
120 self.font_ids_by_font
121 .write()
122 .insert(font.clone(), clone_font_id_result(&font_id));
123 font_id
124 }
125 }
126
127 pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
129 let lock = self.font_ids_by_font.read();
130 lock.iter()
131 .filter_map(|(font, result)| match result {
132 Ok(font_id) if *font_id == id => Some(font.clone()),
133 _ => None,
134 })
135 .next()
136 }
137
138 pub fn resolve_font(&self, font: &Font) -> FontId {
145 if let Ok(font_id) = self.font_id(font) {
146 return font_id;
147 }
148 for fallback in &self.fallback_font_stack {
149 if let Ok(font_id) = self.font_id(fallback) {
150 return font_id;
151 }
152 }
153
154 panic!(
155 "failed to resolve font '{}' or any of the fallbacks: {}",
156 font.family,
157 self.fallback_font_stack
158 .iter()
159 .map(|fallback| &fallback.family)
160 .join(", ")
161 );
162 }
163
164 pub fn prewarm_fonts(&self, fonts: &[Font]) {
170 let mut font_ids = SmallVec::<[FontId; 8]>::new();
171 for font in fonts {
172 let font_id = self.resolve_font(font);
173 if !font_ids.contains(&font_id) {
174 font_ids.push(font_id);
175 }
176 }
177 self.platform_text_system.prewarm_fonts(&font_ids);
178 }
179
180 pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
184 self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
185 }
186
187 pub fn typographic_bounds(
189 &self,
190 font_id: FontId,
191 font_size: Pixels,
192 character: char,
193 ) -> Result<Bounds<Pixels>> {
194 let glyph_id = self
195 .platform_text_system
196 .glyph_for_char(font_id, character)
197 .with_context(|| format!("glyph not found for character '{character}'"))?;
198 let bounds = self
199 .platform_text_system
200 .typographic_bounds(font_id, glyph_id)?;
201 Ok(self.read_metrics(font_id, |metrics| {
202 (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
203 }))
204 }
205
206 pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
208 let glyph_id = self
209 .platform_text_system
210 .glyph_for_char(font_id, ch)
211 .with_context(|| format!("glyph not found for character '{ch}'"))?;
212 let result = self.platform_text_system.advance(font_id, glyph_id)?
213 / self.units_per_em(font_id) as f32;
214
215 Ok(result * font_size)
216 }
217
218 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
221 let mut buffer = [0; 4];
222 let buffer = ch.encode_utf8(&mut buffer);
223 self.platform_text_system
224 .layout_line(
225 buffer,
226 font_size,
227 &[FontRun {
228 len: buffer.len(),
229 font_id,
230 }],
231 )
232 .width
233 }
234
235 pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
239 Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
240 }
241
242 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
246 Ok(self.advance(font_id, font_size, 'm')?.width)
247 }
248
249 pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
253 Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
254 }
255
256 pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
260 Ok(self.advance(font_id, font_size, '0')?.width)
261 }
262
263 pub fn units_per_em(&self, font_id: FontId) -> u32 {
267 self.read_metrics(font_id, |metrics| metrics.units_per_em)
268 }
269
270 pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
272 self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
273 }
274
275 pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
277 self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
278 }
279
280 pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
282 self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
283 }
284
285 pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
288 self.read_metrics(font_id, |metrics| metrics.descent(font_size))
289 }
290
291 pub fn baseline_offset(
293 &self,
294 font_id: FontId,
295 font_size: Pixels,
296 line_height: Pixels,
297 ) -> Pixels {
298 let ascent = self.ascent(font_id, font_size);
299 let descent = self.descent(font_id, font_size);
300 let padding_top = (line_height - ascent - descent) / 2.;
301 padding_top + ascent
302 }
303
304 fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
305 let lock = self.font_metrics.upgradable_read();
306
307 if let Some(metrics) = lock.get(&font_id) {
308 read(metrics)
309 } else {
310 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
311 let metrics = lock
312 .entry(font_id)
313 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
314 read(metrics)
315 }
316 }
317
318 pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
320 let lock = &mut self.wrapper_pool.lock();
321 let font_id = self.resolve_font(&font);
322 let wrappers = lock
323 .entry(FontIdWithSize { font_id, font_size })
324 .or_default();
325 let wrapper = wrappers
326 .pop()
327 .unwrap_or_else(|| LineWrapper::new(font_id, font_size, self.clone()));
328
329 LineWrapperHandle {
330 wrapper: Some(wrapper),
331 text_system: self.clone(),
332 }
333 }
334
335 pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
337 let raster_bounds = self.raster_bounds.upgradable_read();
338 if let Some(bounds) = raster_bounds.get(params) {
339 Ok(*bounds)
340 } else {
341 let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
342 let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
343 raster_bounds.insert(params.clone(), bounds);
344 Ok(bounds)
345 }
346 }
347
348 pub(crate) fn rasterize_glyph(
349 &self,
350 params: &RenderGlyphParams,
351 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
352 let raster_bounds = self.raster_bounds(params)?;
353 self.platform_text_system
354 .rasterize_glyph(params, raster_bounds)
355 }
356
357 pub(crate) fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
359 self.platform_text_system.glyph_dilation_for_color(color)
360 }
361
362 pub(crate) fn recommended_rendering_mode(
365 &self,
366 font_id: FontId,
367 font_size: Pixels,
368 ) -> TextRenderingMode {
369 self.platform_text_system
370 .recommended_rendering_mode(font_id, font_size)
371 }
372}
373
374#[derive(Deref)]
376pub struct WindowTextSystem {
377 line_layout_cache: LineLayoutCache,
378 #[deref]
379 text_system: Arc<TextSystem>,
380}
381
382impl WindowTextSystem {
383 pub fn new(text_system: Arc<TextSystem>) -> Self {
385 Self {
386 line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
387 text_system,
388 }
389 }
390
391 pub(crate) fn layout_index(&self) -> LineLayoutIndex {
392 self.line_layout_cache.layout_index()
393 }
394
395 pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
396 self.line_layout_cache.reuse_layouts(index)
397 }
398
399 pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
400 self.line_layout_cache.truncate_layouts(index)
401 }
402
403 pub fn shape_line(
410 &self,
411 text: SharedString,
412 font_size: Pixels,
413 runs: &[TextRun],
414 force_width: Option<Pixels>,
415 ) -> ShapedLine {
416 debug_assert!(
417 text.find('\n').is_none(),
418 "text argument should not contain newlines"
419 );
420
421 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
422 for run in runs {
423 if let Some(last_run) = decoration_runs.last_mut()
424 && last_run.color == run.color
425 && last_run.underline == run.underline
426 && last_run.strikethrough == run.strikethrough
427 && last_run.background_color == run.background_color
428 {
429 last_run.len += run.len as u32;
430 continue;
431 }
432 decoration_runs.push(DecorationRun {
433 len: run.len as u32,
434 color: run.color,
435 background_color: run.background_color,
436 underline: run.underline,
437 strikethrough: run.strikethrough,
438 });
439 }
440
441 let layout = self.layout_line(&text, font_size, runs, force_width);
442
443 ShapedLine {
444 layout,
445 text,
446 decoration_runs,
447 }
448 }
449
450 pub fn shape_line_by_hash(
461 &self,
462 text_hash: u64,
463 text_len: usize,
464 font_size: Pixels,
465 runs: &[TextRun],
466 force_width: Option<Pixels>,
467 materialize_text: impl FnOnce() -> SharedString,
468 ) -> ShapedLine {
469 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
470 for run in runs {
471 if let Some(last_run) = decoration_runs.last_mut()
472 && last_run.color == run.color
473 && last_run.underline == run.underline
474 && last_run.strikethrough == run.strikethrough
475 && last_run.background_color == run.background_color
476 {
477 last_run.len += run.len as u32;
478 continue;
479 }
480 decoration_runs.push(DecorationRun {
481 len: run.len as u32,
482 color: run.color,
483 background_color: run.background_color,
484 underline: run.underline,
485 strikethrough: run.strikethrough,
486 });
487 }
488
489 let mut used_force_width = force_width;
490 let layout = self.layout_line_by_hash(
491 text_hash,
492 text_len,
493 font_size,
494 runs,
495 used_force_width,
496 || {
497 let text = materialize_text();
498 debug_assert!(
499 text.find('\n').is_none(),
500 "text argument should not contain newlines"
501 );
502 text
503 },
504 );
505
506 let text: SharedString = SharedString::new_static("");
510
511 ShapedLine {
512 layout,
513 text,
514 decoration_runs,
515 }
516 }
517
518 pub fn shape_text(
522 &self,
523 text: SharedString,
524 font_size: Pixels,
525 runs: &[TextRun],
526 wrap_width: Option<Pixels>,
527 line_clamp: Option<usize>,
528 ) -> Result<SmallVec<[WrappedLine; 1]>> {
529 let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
530 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
531
532 let mut lines = SmallVec::new();
533 let mut max_wrap_lines = line_clamp;
534 let mut wrapped_lines = 0;
535
536 let mut process_line = |line_text: SharedString, line_start, line_end| {
537 font_runs.clear();
538
539 let mut decoration_runs = <Vec<DecorationRun>>::with_capacity(32);
540 let mut run_start = line_start;
541 while run_start < line_end {
542 let Some(run) = runs.peek_mut() else {
543 log::warn!("`TextRun`s do not cover the entire to be shaped text");
544 break;
545 };
546
547 let run_len_within_line = cmp::min(line_end - run_start, run.len);
548
549 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
550 && last_run.color == run.color
551 && last_run.underline == run.underline
552 && last_run.strikethrough == run.strikethrough
553 && last_run.background_color == run.background_color
554 {
555 last_run.len += run_len_within_line as u32;
556 false
557 } else {
558 decoration_runs.push(DecorationRun {
559 len: run_len_within_line as u32,
560 color: run.color,
561 background_color: run.background_color,
562 underline: run.underline,
563 strikethrough: run.strikethrough,
564 });
565 true
566 };
567
568 let font_id = self.resolve_font(&run.font);
569 if let Some(font_run) = font_runs.last_mut()
570 && font_id == font_run.font_id
571 && !decoration_changed
572 {
573 font_run.len += run_len_within_line;
574 } else {
575 font_runs.push(FontRun {
576 len: run_len_within_line,
577 font_id,
578 });
579 }
580
581 run.len -= run_len_within_line;
583 if run.len == 0 {
584 runs.next();
585 }
586 run_start += run_len_within_line;
587 }
588
589 let layout = self.line_layout_cache.layout_wrapped_line(
590 &line_text,
591 font_size,
592 &font_runs,
593 wrap_width,
594 max_wrap_lines.map(|max| max.saturating_sub(wrapped_lines)),
595 );
596 wrapped_lines += layout.wrap_boundaries.len();
597
598 lines.push(WrappedLine {
599 layout,
600 decoration_runs,
601 text: line_text,
602 });
603
604 if let Some(run) = runs.peek_mut() {
606 run.len -= 1;
607 if run.len == 0 {
608 runs.next();
609 }
610 }
611 };
612
613 let mut split_lines = text.split('\n');
614
615 if let Some(first_line) = split_lines.next()
617 && let Some(second_line) = split_lines.next()
618 {
619 let mut line_start = 0;
620 process_line(
621 SharedString::new(first_line),
622 line_start,
623 line_start + first_line.len(),
624 );
625 line_start += first_line.len() + '\n'.len_utf8();
626 process_line(
627 SharedString::new(second_line),
628 line_start,
629 line_start + second_line.len(),
630 );
631 for line_text in split_lines {
632 line_start += line_text.len() + '\n'.len_utf8();
633 process_line(
634 SharedString::new(line_text),
635 line_start,
636 line_start + line_text.len(),
637 );
638 }
639 } else {
640 let end = text.len();
641 process_line(text, 0, end);
642 }
643
644 self.font_runs_pool.lock().push(font_runs);
645
646 Ok(lines)
647 }
648
649 pub(crate) fn finish_frame(&self) {
650 self.line_layout_cache.finish_frame()
651 }
652
653 pub fn layout_line(
658 &self,
659 text: &str,
660 font_size: Pixels,
661 runs: &[TextRun],
662 force_width: Option<Pixels>,
663 ) -> Arc<LineLayout> {
664 let mut last_run = None::<&TextRun>;
665 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
666 font_runs.clear();
667
668 for run in runs.iter() {
669 let decoration_changed = if let Some(last_run) = last_run
670 && last_run.color == run.color
671 && last_run.underline == run.underline
672 && last_run.strikethrough == run.strikethrough
673 {
676 false
677 } else {
678 last_run = Some(run);
679 true
680 };
681
682 let font_id = self.resolve_font(&run.font);
683 if let Some(font_run) = font_runs.last_mut()
684 && font_id == font_run.font_id
685 && !decoration_changed
686 {
687 font_run.len += run.len;
688 } else {
689 font_runs.push(FontRun {
690 len: run.len,
691 font_id,
692 });
693 }
694 }
695
696 let layout = self.line_layout_cache.layout_line(
697 &SharedString::new(text),
698 font_size,
699 &font_runs,
700 force_width,
701 );
702
703 self.font_runs_pool.lock().push(font_runs);
704
705 layout
706 }
707
708 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
710 let mut buffer = [0; 4];
711 let buffer: &_ = ch.encode_utf8(&mut buffer);
712 self.line_layout_cache
713 .layout_line(
714 buffer,
715 font_size,
716 &[FontRun {
717 len: buffer.len(),
718 font_id,
719 }],
720 None,
721 )
722 .width
723 }
724
725 pub fn em_layout_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
727 self.layout_width(font_id, font_size, 'm')
728 }
729
730 pub fn try_layout_line_by_hash(
739 &self,
740 text_hash: u64,
741 text_len: usize,
742 font_size: Pixels,
743 runs: &[TextRun],
744 force_width: Option<Pixels>,
745 ) -> Option<Arc<LineLayout>> {
746 let mut last_run = None::<&TextRun>;
747 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
748 font_runs.clear();
749
750 for run in runs.iter() {
751 let decoration_changed = if let Some(last_run) = last_run
752 && last_run.color == run.color
753 && last_run.underline == run.underline
754 && last_run.strikethrough == run.strikethrough
755 {
758 false
759 } else {
760 last_run = Some(run);
761 true
762 };
763
764 let font_id = self.resolve_font(&run.font);
765 if let Some(font_run) = font_runs.last_mut()
766 && font_id == font_run.font_id
767 && !decoration_changed
768 {
769 font_run.len += run.len;
770 } else {
771 font_runs.push(FontRun {
772 len: run.len,
773 font_id,
774 });
775 }
776 }
777
778 let layout = self.line_layout_cache.try_layout_line_by_hash(
779 text_hash,
780 text_len,
781 font_size,
782 &font_runs,
783 force_width,
784 );
785
786 self.font_runs_pool.lock().push(font_runs);
787
788 layout
789 }
790
791 pub fn layout_line_by_hash(
800 &self,
801 text_hash: u64,
802 text_len: usize,
803 font_size: Pixels,
804 runs: &[TextRun],
805 force_width: Option<Pixels>,
806 materialize_text: impl FnOnce() -> SharedString,
807 ) -> Arc<LineLayout> {
808 let mut last_run = None::<&TextRun>;
809 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
810 font_runs.clear();
811
812 for run in runs.iter() {
813 let decoration_changed = if let Some(last_run) = last_run
814 && last_run.color == run.color
815 && last_run.underline == run.underline
816 && last_run.strikethrough == run.strikethrough
817 {
820 false
821 } else {
822 last_run = Some(run);
823 true
824 };
825
826 let font_id = self.resolve_font(&run.font);
827 if let Some(font_run) = font_runs.last_mut()
828 && font_id == font_run.font_id
829 && !decoration_changed
830 {
831 font_run.len += run.len;
832 } else {
833 font_runs.push(FontRun {
834 len: run.len,
835 font_id,
836 });
837 }
838 }
839
840 let layout = self.line_layout_cache.layout_line_by_hash(
841 text_hash,
842 text_len,
843 font_size,
844 &font_runs,
845 force_width,
846 materialize_text,
847 );
848
849 self.font_runs_pool.lock().push(font_runs);
850
851 layout
852 }
853}
854
855#[derive(Hash, Eq, PartialEq)]
856struct FontIdWithSize {
857 font_id: FontId,
858 font_size: Pixels,
859}
860
861pub struct LineWrapperHandle {
863 wrapper: Option<LineWrapper>,
864 text_system: Arc<TextSystem>,
865}
866
867impl Drop for LineWrapperHandle {
868 fn drop(&mut self) {
869 let mut state = self.text_system.wrapper_pool.lock();
870 let wrapper = self.wrapper.take().unwrap();
871 state
872 .get_mut(&FontIdWithSize {
873 font_id: wrapper.font_id,
874 font_size: wrapper.font_size,
875 })
876 .unwrap()
877 .push(wrapper);
878 }
879}
880
881impl Deref for LineWrapperHandle {
882 type Target = LineWrapper;
883
884 fn deref(&self) -> &Self::Target {
885 self.wrapper.as_ref().unwrap()
886 }
887}
888
889impl DerefMut for LineWrapperHandle {
890 fn deref_mut(&mut self) -> &mut Self::Target {
891 self.wrapper.as_mut().unwrap()
892 }
893}
894
895#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)]
898#[serde(transparent)]
899pub struct FontWeight(pub f32);
900
901impl Display for FontWeight {
902 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
903 write!(f, "{}", self.0)
904 }
905}
906
907impl From<f32> for FontWeight {
908 fn from(weight: f32) -> Self {
909 FontWeight(weight)
910 }
911}
912
913impl Default for FontWeight {
914 #[inline]
915 fn default() -> FontWeight {
916 FontWeight::NORMAL
917 }
918}
919
920impl Hash for FontWeight {
921 fn hash<H: Hasher>(&self, state: &mut H) {
922 state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
923 }
924}
925
926impl Eq for FontWeight {}
927
928impl FontWeight {
929 pub const THIN: FontWeight = FontWeight(100.0);
931 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
933 pub const LIGHT: FontWeight = FontWeight(300.0);
935 pub const NORMAL: FontWeight = FontWeight(400.0);
937 pub const MEDIUM: FontWeight = FontWeight(500.0);
939 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
941 pub const BOLD: FontWeight = FontWeight(700.0);
943 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
945 pub const BLACK: FontWeight = FontWeight(900.0);
947
948 pub const ALL: [FontWeight; 9] = [
950 Self::THIN,
951 Self::EXTRA_LIGHT,
952 Self::LIGHT,
953 Self::NORMAL,
954 Self::MEDIUM,
955 Self::SEMIBOLD,
956 Self::BOLD,
957 Self::EXTRA_BOLD,
958 Self::BLACK,
959 ];
960}
961
962impl schemars::JsonSchema for FontWeight {
963 fn schema_name() -> std::borrow::Cow<'static, str> {
964 "FontWeight".into()
965 }
966
967 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
968 use schemars::json_schema;
969 json_schema!({
970 "type": "number",
971 "minimum": Self::THIN,
972 "maximum": Self::BLACK,
973 "default": Self::default(),
974 "description": "Font weight value between 100 (thin) and 900 (black)"
975 })
976 }
977}
978
979#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
981pub enum FontStyle {
982 #[default]
984 Normal,
985 Italic,
987 Oblique,
989}
990
991impl Display for FontStyle {
992 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
993 Debug::fmt(self, f)
994 }
995}
996
997#[derive(Clone, Debug, PartialEq, Eq, Default)]
999pub struct TextRun {
1000 pub len: usize,
1002 pub font: Font,
1004 pub color: Hsla,
1006 pub background_color: Option<Hsla>,
1008 pub underline: Option<UnderlineStyle>,
1010 pub strikethrough: Option<StrikethroughStyle>,
1012}
1013
1014#[cfg(all(target_os = "macos", test))]
1015impl TextRun {
1016 fn with_len(&self, len: usize) -> Self {
1017 let mut this = self.clone();
1018 this.len = len;
1019 this
1020 }
1021}
1022
1023#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1025#[repr(C)]
1026pub struct GlyphId(pub u32);
1027
1028#[derive(Clone, Debug, PartialEq)]
1034#[expect(missing_docs)]
1035pub struct RenderGlyphParams {
1036 pub font_id: FontId,
1037 pub glyph_id: GlyphId,
1038 pub font_size: Pixels,
1039 pub subpixel_variant: Point<u8>,
1040 pub scale_factor: f32,
1041 pub is_emoji: bool,
1042 pub subpixel_rendering: bool,
1043 pub dilation: u8,
1044}
1045
1046impl Eq for RenderGlyphParams {}
1047
1048impl Hash for RenderGlyphParams {
1049 fn hash<H: Hasher>(&self, state: &mut H) {
1050 self.font_id.0.hash(state);
1051 self.glyph_id.0.hash(state);
1052 self.font_size.0.to_bits().hash(state);
1053 self.subpixel_variant.hash(state);
1054 self.scale_factor.to_bits().hash(state);
1055 self.is_emoji.hash(state);
1056 self.subpixel_rendering.hash(state);
1057 self.dilation.hash(state);
1058 }
1059}
1060
1061#[derive(Clone, Debug, Eq, PartialEq, Hash)]
1063pub struct Font {
1064 pub family: SharedString,
1068
1069 pub features: FontFeatures,
1071
1072 pub fallbacks: Option<FontFallbacks>,
1074
1075 pub weight: FontWeight,
1077
1078 pub style: FontStyle,
1080}
1081
1082impl Default for Font {
1083 fn default() -> Self {
1084 font(".SystemUIFont")
1085 }
1086}
1087
1088pub fn font(family: impl Into<SharedString>) -> Font {
1090 Font {
1091 family: family.into(),
1092 features: FontFeatures::default(),
1093 weight: FontWeight::default(),
1094 style: FontStyle::default(),
1095 fallbacks: None,
1096 }
1097}
1098
1099impl Font {
1100 pub fn bold(mut self) -> Self {
1102 self.weight = FontWeight::BOLD;
1103 self
1104 }
1105
1106 pub fn italic(mut self) -> Self {
1108 self.style = FontStyle::Italic;
1109 self
1110 }
1111}
1112
1113#[derive(Clone, Copy, Debug)]
1116pub struct FontMetrics {
1117 pub units_per_em: u32,
1120
1121 pub ascent: f32,
1123
1124 pub descent: f32,
1126
1127 pub line_gap: f32,
1129
1130 pub underline_position: f32,
1132
1133 pub underline_thickness: f32,
1135
1136 pub cap_height: f32,
1138
1139 pub x_height: f32,
1141
1142 pub bounding_box: Bounds<f32>,
1145}
1146
1147impl FontMetrics {
1148 pub fn ascent(&self, font_size: Pixels) -> Pixels {
1150 Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
1151 }
1152
1153 pub fn descent(&self, font_size: Pixels) -> Pixels {
1155 Pixels((self.descent / self.units_per_em as f32) * font_size.0)
1156 }
1157
1158 pub fn line_gap(&self, font_size: Pixels) -> Pixels {
1160 Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
1161 }
1162
1163 pub fn underline_position(&self, font_size: Pixels) -> Pixels {
1165 Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
1166 }
1167
1168 pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
1170 Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
1171 }
1172
1173 pub fn cap_height(&self, font_size: Pixels) -> Pixels {
1175 Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
1176 }
1177
1178 pub fn x_height(&self, font_size: Pixels) -> Pixels {
1180 Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
1181 }
1182
1183 pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
1185 (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
1186 }
1187}
1188
1189#[allow(unused)]
1191pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
1192 match name {
1196 ".SystemUIFont" => system,
1197 ".ZedSans" | "Zed Plex Sans" => "IBM Plex Sans",
1198 ".ZedMono" | "Zed Plex Mono" => "Lilex",
1199 _ => name,
1200 }
1201}
1202
1203#[allow(unused)]
1205pub fn font_name_with_fallbacks_shared<'a>(
1206 name: &'a SharedString,
1207 system: &'a SharedString,
1208) -> &'a SharedString {
1209 match name.as_str() {
1213 ".SystemUIFont" => system,
1214 ".ZedSans" | "Zed Plex Sans" => const { &SharedString::new_static("IBM Plex Sans") },
1215 ".ZedMono" | "Zed Plex Mono" => const { &SharedString::new_static("Lilex") },
1216 _ => name,
1217 }
1218}