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> {
89 let mut names = self.platform_text_system.all_font_names();
90 names.extend(
91 self.fallback_font_stack
92 .iter()
93 .map(|font| font.family.to_string()),
94 );
95 names.push(".SystemUIFont".to_string());
96 names.sort_unstable();
97 names.dedup();
98 names
99 }
100
101 pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
103 self.platform_text_system.add_fonts(fonts)
104 }
105
106 fn font_id(&self, font: &Font) -> Result<FontId> {
108 fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
109 match font_id {
110 Ok(font_id) => Ok(*font_id),
111 Err(err) => Err(anyhow!("{err}")),
112 }
113 }
114
115 let font_id = self
116 .font_ids_by_font
117 .read()
118 .get(font)
119 .map(clone_font_id_result);
120 if let Some(font_id) = font_id {
121 font_id
122 } else {
123 let font_id = self.platform_text_system.font_id(font);
124 self.font_ids_by_font
125 .write()
126 .insert(font.clone(), clone_font_id_result(&font_id));
127 font_id
128 }
129 }
130
131 pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
133 let lock = self.font_ids_by_font.read();
134 lock.iter()
135 .filter_map(|(font, result)| match result {
136 Ok(font_id) if *font_id == id => Some(font.clone()),
137 _ => None,
138 })
139 .next()
140 }
141
142 pub fn resolve_font(&self, font: &Font) -> FontId {
149 if let Ok(font_id) = self.font_id(font) {
150 return font_id;
151 }
152 for fallback in &self.fallback_font_stack {
153 if let Ok(font_id) = self.font_id(fallback) {
154 return font_id;
155 }
156 }
157
158 panic!(
159 "failed to resolve font '{}' or any of the fallbacks: {}",
160 font.family,
161 self.fallback_font_stack
162 .iter()
163 .map(|fallback| &fallback.family)
164 .join(", ")
165 );
166 }
167
168 pub fn prewarm_fonts(&self, fonts: &[Font]) {
174 let mut font_ids = SmallVec::<[FontId; 8]>::new();
175 for font in fonts {
176 let font_id = self.resolve_font(font);
177 if !font_ids.contains(&font_id) {
178 font_ids.push(font_id);
179 }
180 }
181 self.platform_text_system.prewarm_fonts(&font_ids);
182 }
183
184 pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
188 self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
189 }
190
191 pub fn typographic_bounds(
193 &self,
194 font_id: FontId,
195 font_size: Pixels,
196 character: char,
197 ) -> Result<Bounds<Pixels>> {
198 let glyph_id = self
199 .platform_text_system
200 .glyph_for_char(font_id, character)
201 .with_context(|| format!("glyph not found for character '{character}'"))?;
202 let bounds = self
203 .platform_text_system
204 .typographic_bounds(font_id, glyph_id)?;
205 Ok(self.read_metrics(font_id, |metrics| {
206 (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
207 }))
208 }
209
210 pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
212 let glyph_id = self
213 .platform_text_system
214 .glyph_for_char(font_id, ch)
215 .with_context(|| format!("glyph not found for character '{ch}'"))?;
216 let result = self.platform_text_system.advance(font_id, glyph_id)?
217 / self.units_per_em(font_id) as f32;
218
219 Ok(result * font_size)
220 }
221
222 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
225 let mut buffer = [0; 4];
226 let buffer = ch.encode_utf8(&mut buffer);
227 self.platform_text_system
228 .layout_line(
229 buffer,
230 font_size,
231 &[FontRun {
232 len: buffer.len(),
233 font_id,
234 }],
235 )
236 .width
237 }
238
239 pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
243 Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
244 }
245
246 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
250 Ok(self.advance(font_id, font_size, 'm')?.width)
251 }
252
253 pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
257 Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
258 }
259
260 pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
264 Ok(self.advance(font_id, font_size, '0')?.width)
265 }
266
267 pub fn units_per_em(&self, font_id: FontId) -> u32 {
271 self.read_metrics(font_id, |metrics| metrics.units_per_em)
272 }
273
274 pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
276 self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
277 }
278
279 pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
281 self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
282 }
283
284 pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
286 self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
287 }
288
289 pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
292 self.read_metrics(font_id, |metrics| metrics.descent(font_size))
293 }
294
295 pub fn baseline_offset(
297 &self,
298 font_id: FontId,
299 font_size: Pixels,
300 line_height: Pixels,
301 ) -> Pixels {
302 let ascent = self.ascent(font_id, font_size);
303 let descent = self.descent(font_id, font_size);
304 let padding_top = (line_height - ascent - descent) / 2.;
305 padding_top + ascent
306 }
307
308 fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
309 let lock = self.font_metrics.upgradable_read();
310
311 if let Some(metrics) = lock.get(&font_id) {
312 read(metrics)
313 } else {
314 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
315 let metrics = lock
316 .entry(font_id)
317 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
318 read(metrics)
319 }
320 }
321
322 pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
324 let lock = &mut self.wrapper_pool.lock();
325 let font_id = self.resolve_font(&font);
326 let wrappers = lock
327 .entry(FontIdWithSize { font_id, font_size })
328 .or_default();
329 let wrapper = wrappers
330 .pop()
331 .unwrap_or_else(|| LineWrapper::new(font_id, font_size, self.clone()));
332
333 LineWrapperHandle {
334 wrapper: Some(wrapper),
335 text_system: self.clone(),
336 }
337 }
338
339 pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
341 let raster_bounds = self.raster_bounds.upgradable_read();
342 if let Some(bounds) = raster_bounds.get(params) {
343 Ok(*bounds)
344 } else {
345 let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
346 let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
347 raster_bounds.insert(params.clone(), bounds);
348 Ok(bounds)
349 }
350 }
351
352 pub(crate) fn rasterize_glyph(
353 &self,
354 params: &RenderGlyphParams,
355 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
356 let raster_bounds = self.raster_bounds(params)?;
357 self.platform_text_system
358 .rasterize_glyph(params, raster_bounds)
359 }
360
361 pub(crate) fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
363 self.platform_text_system.glyph_dilation_for_color(color)
364 }
365
366 pub(crate) fn recommended_rendering_mode(
369 &self,
370 font_id: FontId,
371 font_size: Pixels,
372 ) -> TextRenderingMode {
373 self.platform_text_system
374 .recommended_rendering_mode(font_id, font_size)
375 }
376}
377
378#[derive(Deref)]
380pub struct WindowTextSystem {
381 line_layout_cache: LineLayoutCache,
382 #[deref]
383 text_system: Arc<TextSystem>,
384}
385
386impl WindowTextSystem {
387 pub fn new(text_system: Arc<TextSystem>) -> Self {
389 Self {
390 line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
391 text_system,
392 }
393 }
394
395 pub(crate) fn layout_index(&self) -> LineLayoutIndex {
396 self.line_layout_cache.layout_index()
397 }
398
399 pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
400 self.line_layout_cache.reuse_layouts(index)
401 }
402
403 pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
404 self.line_layout_cache.truncate_layouts(index)
405 }
406
407 pub fn shape_line(
414 &self,
415 text: SharedString,
416 font_size: Pixels,
417 runs: &[TextRun],
418 force_width: Option<Pixels>,
419 ) -> ShapedLine {
420 debug_assert!(
421 text.find('\n').is_none(),
422 "text argument should not contain newlines"
423 );
424
425 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
426 for run in runs {
427 if let Some(last_run) = decoration_runs.last_mut()
428 && last_run.color == run.color
429 && last_run.underline == run.underline
430 && last_run.strikethrough == run.strikethrough
431 && last_run.background_color == run.background_color
432 {
433 last_run.len += run.len as u32;
434 continue;
435 }
436 decoration_runs.push(DecorationRun {
437 len: run.len as u32,
438 color: run.color,
439 background_color: run.background_color,
440 underline: run.underline,
441 strikethrough: run.strikethrough,
442 });
443 }
444
445 let layout = self.layout_line(&text, font_size, runs, force_width);
446
447 ShapedLine {
448 layout,
449 text,
450 decoration_runs,
451 }
452 }
453
454 pub fn shape_line_by_hash(
465 &self,
466 text_hash: u64,
467 text_len: usize,
468 font_size: Pixels,
469 runs: &[TextRun],
470 force_width: Option<Pixels>,
471 materialize_text: impl FnOnce() -> SharedString,
472 ) -> ShapedLine {
473 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
474 for run in runs {
475 if let Some(last_run) = decoration_runs.last_mut()
476 && last_run.color == run.color
477 && last_run.underline == run.underline
478 && last_run.strikethrough == run.strikethrough
479 && last_run.background_color == run.background_color
480 {
481 last_run.len += run.len as u32;
482 continue;
483 }
484 decoration_runs.push(DecorationRun {
485 len: run.len as u32,
486 color: run.color,
487 background_color: run.background_color,
488 underline: run.underline,
489 strikethrough: run.strikethrough,
490 });
491 }
492
493 let mut used_force_width = force_width;
494 let layout = self.layout_line_by_hash(
495 text_hash,
496 text_len,
497 font_size,
498 runs,
499 used_force_width,
500 || {
501 let text = materialize_text();
502 debug_assert!(
503 text.find('\n').is_none(),
504 "text argument should not contain newlines"
505 );
506 text
507 },
508 );
509
510 let text: SharedString = SharedString::new_static("");
514
515 ShapedLine {
516 layout,
517 text,
518 decoration_runs,
519 }
520 }
521
522 pub fn shape_text(
526 &self,
527 text: SharedString,
528 font_size: Pixels,
529 runs: &[TextRun],
530 wrap_width: Option<Pixels>,
531 line_clamp: Option<usize>,
532 ) -> Result<SmallVec<[WrappedLine; 1]>> {
533 let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
534 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
535
536 let mut lines = SmallVec::new();
537 let mut max_wrap_lines = line_clamp;
538 let mut wrapped_lines = 0;
539
540 let mut process_line = |line_text: SharedString, line_start, line_end| {
541 font_runs.clear();
542
543 let mut decoration_runs = <Vec<DecorationRun>>::with_capacity(32);
544 let mut run_start = line_start;
545 while run_start < line_end {
546 let Some(run) = runs.peek_mut() else {
547 log::warn!("`TextRun`s do not cover the entire to be shaped text");
548 break;
549 };
550
551 let run_len_within_line = cmp::min(line_end - run_start, run.len);
552
553 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
554 && last_run.color == run.color
555 && last_run.underline == run.underline
556 && last_run.strikethrough == run.strikethrough
557 && last_run.background_color == run.background_color
558 {
559 last_run.len += run_len_within_line as u32;
560 false
561 } else {
562 decoration_runs.push(DecorationRun {
563 len: run_len_within_line as u32,
564 color: run.color,
565 background_color: run.background_color,
566 underline: run.underline,
567 strikethrough: run.strikethrough,
568 });
569 true
570 };
571
572 let font_id = self.resolve_font(&run.font);
573 if let Some(font_run) = font_runs.last_mut()
574 && font_id == font_run.font_id
575 && !decoration_changed
576 {
577 font_run.len += run_len_within_line;
578 } else {
579 font_runs.push(FontRun {
580 len: run_len_within_line,
581 font_id,
582 });
583 }
584
585 run.len -= run_len_within_line;
587 if run.len == 0 {
588 runs.next();
589 }
590 run_start += run_len_within_line;
591 }
592
593 let layout = self.line_layout_cache.layout_wrapped_line(
594 &line_text,
595 font_size,
596 &font_runs,
597 wrap_width,
598 max_wrap_lines.map(|max| max.saturating_sub(wrapped_lines)),
599 );
600 wrapped_lines += layout.wrap_boundaries.len();
601
602 lines.push(WrappedLine {
603 layout,
604 decoration_runs,
605 text: line_text,
606 });
607
608 if let Some(run) = runs.peek_mut() {
610 run.len -= 1;
611 if run.len == 0 {
612 runs.next();
613 }
614 }
615 };
616
617 let mut split_lines = text.split('\n');
618
619 if let Some(first_line) = split_lines.next()
621 && let Some(second_line) = split_lines.next()
622 {
623 let mut line_start = 0;
624 process_line(
625 SharedString::new(first_line),
626 line_start,
627 line_start + first_line.len(),
628 );
629 line_start += first_line.len() + '\n'.len_utf8();
630 process_line(
631 SharedString::new(second_line),
632 line_start,
633 line_start + second_line.len(),
634 );
635 for line_text in split_lines {
636 line_start += line_text.len() + '\n'.len_utf8();
637 process_line(
638 SharedString::new(line_text),
639 line_start,
640 line_start + line_text.len(),
641 );
642 }
643 } else {
644 let end = text.len();
645 process_line(text, 0, end);
646 }
647
648 self.font_runs_pool.lock().push(font_runs);
649
650 Ok(lines)
651 }
652
653 pub(crate) fn finish_frame(&self) {
654 self.line_layout_cache.finish_frame()
655 }
656
657 pub fn layout_line(
662 &self,
663 text: &str,
664 font_size: Pixels,
665 runs: &[TextRun],
666 force_width: Option<Pixels>,
667 ) -> Arc<LineLayout> {
668 let mut last_run = None::<&TextRun>;
669 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
670 font_runs.clear();
671
672 for run in runs.iter() {
673 let decoration_changed = if let Some(last_run) = last_run
674 && last_run.color == run.color
675 && last_run.underline == run.underline
676 && last_run.strikethrough == run.strikethrough
677 {
680 false
681 } else {
682 last_run = Some(run);
683 true
684 };
685
686 let font_id = self.resolve_font(&run.font);
687 if let Some(font_run) = font_runs.last_mut()
688 && font_id == font_run.font_id
689 && !decoration_changed
690 {
691 font_run.len += run.len;
692 } else {
693 font_runs.push(FontRun {
694 len: run.len,
695 font_id,
696 });
697 }
698 }
699
700 let layout = self.line_layout_cache.layout_line(
701 &SharedString::new(text),
702 font_size,
703 &font_runs,
704 force_width,
705 );
706
707 self.font_runs_pool.lock().push(font_runs);
708
709 layout
710 }
711
712 pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
714 let mut buffer = [0; 4];
715 let buffer: &_ = ch.encode_utf8(&mut buffer);
716 self.line_layout_cache
717 .layout_line(
718 buffer,
719 font_size,
720 &[FontRun {
721 len: buffer.len(),
722 font_id,
723 }],
724 None,
725 )
726 .width
727 }
728
729 pub fn em_layout_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
731 self.layout_width(font_id, font_size, 'm')
732 }
733
734 pub fn try_layout_line_by_hash(
743 &self,
744 text_hash: u64,
745 text_len: usize,
746 font_size: Pixels,
747 runs: &[TextRun],
748 force_width: Option<Pixels>,
749 ) -> Option<Arc<LineLayout>> {
750 let mut last_run = None::<&TextRun>;
751 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
752 font_runs.clear();
753
754 for run in runs.iter() {
755 let decoration_changed = if let Some(last_run) = last_run
756 && last_run.color == run.color
757 && last_run.underline == run.underline
758 && last_run.strikethrough == run.strikethrough
759 {
762 false
763 } else {
764 last_run = Some(run);
765 true
766 };
767
768 let font_id = self.resolve_font(&run.font);
769 if let Some(font_run) = font_runs.last_mut()
770 && font_id == font_run.font_id
771 && !decoration_changed
772 {
773 font_run.len += run.len;
774 } else {
775 font_runs.push(FontRun {
776 len: run.len,
777 font_id,
778 });
779 }
780 }
781
782 let layout = self.line_layout_cache.try_layout_line_by_hash(
783 text_hash,
784 text_len,
785 font_size,
786 &font_runs,
787 force_width,
788 );
789
790 self.font_runs_pool.lock().push(font_runs);
791
792 layout
793 }
794
795 pub fn layout_line_by_hash(
804 &self,
805 text_hash: u64,
806 text_len: usize,
807 font_size: Pixels,
808 runs: &[TextRun],
809 force_width: Option<Pixels>,
810 materialize_text: impl FnOnce() -> SharedString,
811 ) -> Arc<LineLayout> {
812 let mut last_run = None::<&TextRun>;
813 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
814 font_runs.clear();
815
816 for run in runs.iter() {
817 let decoration_changed = if let Some(last_run) = last_run
818 && last_run.color == run.color
819 && last_run.underline == run.underline
820 && last_run.strikethrough == run.strikethrough
821 {
824 false
825 } else {
826 last_run = Some(run);
827 true
828 };
829
830 let font_id = self.resolve_font(&run.font);
831 if let Some(font_run) = font_runs.last_mut()
832 && font_id == font_run.font_id
833 && !decoration_changed
834 {
835 font_run.len += run.len;
836 } else {
837 font_runs.push(FontRun {
838 len: run.len,
839 font_id,
840 });
841 }
842 }
843
844 let layout = self.line_layout_cache.layout_line_by_hash(
845 text_hash,
846 text_len,
847 font_size,
848 &font_runs,
849 force_width,
850 materialize_text,
851 );
852
853 self.font_runs_pool.lock().push(font_runs);
854
855 layout
856 }
857}
858
859#[derive(Hash, Eq, PartialEq)]
860struct FontIdWithSize {
861 font_id: FontId,
862 font_size: Pixels,
863}
864
865pub struct LineWrapperHandle {
867 wrapper: Option<LineWrapper>,
868 text_system: Arc<TextSystem>,
869}
870
871impl Drop for LineWrapperHandle {
872 fn drop(&mut self) {
873 let mut state = self.text_system.wrapper_pool.lock();
874 let wrapper = self.wrapper.take().unwrap();
875 state
876 .get_mut(&FontIdWithSize {
877 font_id: wrapper.font_id,
878 font_size: wrapper.font_size,
879 })
880 .unwrap()
881 .push(wrapper);
882 }
883}
884
885impl Deref for LineWrapperHandle {
886 type Target = LineWrapper;
887
888 fn deref(&self) -> &Self::Target {
889 self.wrapper.as_ref().unwrap()
890 }
891}
892
893impl DerefMut for LineWrapperHandle {
894 fn deref_mut(&mut self) -> &mut Self::Target {
895 self.wrapper.as_mut().unwrap()
896 }
897}
898
899#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)]
902#[serde(transparent)]
903pub struct FontWeight(pub f32);
904
905impl Display for FontWeight {
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 write!(f, "{}", self.0)
908 }
909}
910
911impl From<f32> for FontWeight {
912 fn from(weight: f32) -> Self {
913 FontWeight(weight)
914 }
915}
916
917impl Default for FontWeight {
918 #[inline]
919 fn default() -> FontWeight {
920 FontWeight::NORMAL
921 }
922}
923
924impl Hash for FontWeight {
925 fn hash<H: Hasher>(&self, state: &mut H) {
926 state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
927 }
928}
929
930impl Eq for FontWeight {}
931
932impl FontWeight {
933 pub const THIN: FontWeight = FontWeight(100.0);
935 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
937 pub const LIGHT: FontWeight = FontWeight(300.0);
939 pub const NORMAL: FontWeight = FontWeight(400.0);
941 pub const MEDIUM: FontWeight = FontWeight(500.0);
943 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
945 pub const BOLD: FontWeight = FontWeight(700.0);
947 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
949 pub const BLACK: FontWeight = FontWeight(900.0);
951
952 pub const ALL: [FontWeight; 9] = [
954 Self::THIN,
955 Self::EXTRA_LIGHT,
956 Self::LIGHT,
957 Self::NORMAL,
958 Self::MEDIUM,
959 Self::SEMIBOLD,
960 Self::BOLD,
961 Self::EXTRA_BOLD,
962 Self::BLACK,
963 ];
964}
965
966impl schemars::JsonSchema for FontWeight {
967 fn schema_name() -> std::borrow::Cow<'static, str> {
968 "FontWeight".into()
969 }
970
971 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
972 use schemars::json_schema;
973 json_schema!({
974 "type": "number",
975 "minimum": Self::THIN,
976 "maximum": Self::BLACK,
977 "default": Self::default(),
978 "description": "Font weight value between 100 (thin) and 900 (black)"
979 })
980 }
981}
982
983#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
985pub enum FontStyle {
986 #[default]
988 Normal,
989 Italic,
991 Oblique,
993}
994
995impl Display for FontStyle {
996 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
997 Debug::fmt(self, f)
998 }
999}
1000
1001#[derive(Clone, Debug, PartialEq, Eq, Default)]
1003pub struct TextRun {
1004 pub len: usize,
1006 pub font: Font,
1008 pub color: Hsla,
1010 pub background_color: Option<Hsla>,
1012 pub underline: Option<UnderlineStyle>,
1014 pub strikethrough: Option<StrikethroughStyle>,
1016}
1017
1018#[cfg(all(target_os = "macos", test))]
1019impl TextRun {
1020 fn with_len(&self, len: usize) -> Self {
1021 let mut this = self.clone();
1022 this.len = len;
1023 this
1024 }
1025}
1026
1027#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1029#[repr(C)]
1030pub struct GlyphId(pub u32);
1031
1032#[derive(Clone, Debug, PartialEq)]
1038#[expect(missing_docs)]
1039pub struct RenderGlyphParams {
1040 pub font_id: FontId,
1041 pub glyph_id: GlyphId,
1042 pub font_size: Pixels,
1043 pub subpixel_variant: Point<u8>,
1044 pub scale_factor: f32,
1045 pub is_emoji: bool,
1046 pub subpixel_rendering: bool,
1047 pub dilation: u8,
1048}
1049
1050impl Eq for RenderGlyphParams {}
1051
1052impl Hash for RenderGlyphParams {
1053 fn hash<H: Hasher>(&self, state: &mut H) {
1054 self.font_id.0.hash(state);
1055 self.glyph_id.0.hash(state);
1056 self.font_size.0.to_bits().hash(state);
1057 self.subpixel_variant.hash(state);
1058 self.scale_factor.to_bits().hash(state);
1059 self.is_emoji.hash(state);
1060 self.subpixel_rendering.hash(state);
1061 self.dilation.hash(state);
1062 }
1063}
1064
1065#[derive(Clone, Debug, Eq, PartialEq, Hash)]
1067pub struct Font {
1068 pub family: SharedString,
1072
1073 pub features: FontFeatures,
1075
1076 pub fallbacks: Option<FontFallbacks>,
1078
1079 pub weight: FontWeight,
1081
1082 pub style: FontStyle,
1084}
1085
1086impl Default for Font {
1087 fn default() -> Self {
1088 font(".SystemUIFont")
1089 }
1090}
1091
1092pub fn font(family: impl Into<SharedString>) -> Font {
1094 Font {
1095 family: family.into(),
1096 features: FontFeatures::default(),
1097 weight: FontWeight::default(),
1098 style: FontStyle::default(),
1099 fallbacks: None,
1100 }
1101}
1102
1103impl Font {
1104 pub fn bold(mut self) -> Self {
1106 self.weight = FontWeight::BOLD;
1107 self
1108 }
1109
1110 pub fn italic(mut self) -> Self {
1112 self.style = FontStyle::Italic;
1113 self
1114 }
1115}
1116
1117#[derive(Clone, Copy, Debug)]
1120pub struct FontMetrics {
1121 pub units_per_em: u32,
1124
1125 pub ascent: f32,
1127
1128 pub descent: f32,
1130
1131 pub line_gap: f32,
1133
1134 pub underline_position: f32,
1136
1137 pub underline_thickness: f32,
1139
1140 pub cap_height: f32,
1142
1143 pub x_height: f32,
1145
1146 pub bounding_box: Bounds<f32>,
1149}
1150
1151impl FontMetrics {
1152 pub fn ascent(&self, font_size: Pixels) -> Pixels {
1154 Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
1155 }
1156
1157 pub fn descent(&self, font_size: Pixels) -> Pixels {
1159 Pixels((self.descent / self.units_per_em as f32) * font_size.0)
1160 }
1161
1162 pub fn line_gap(&self, font_size: Pixels) -> Pixels {
1164 Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
1165 }
1166
1167 pub fn underline_position(&self, font_size: Pixels) -> Pixels {
1169 Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
1170 }
1171
1172 pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
1174 Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
1175 }
1176
1177 pub fn cap_height(&self, font_size: Pixels) -> Pixels {
1179 Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
1180 }
1181
1182 pub fn x_height(&self, font_size: Pixels) -> Pixels {
1184 Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
1185 }
1186
1187 pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
1189 (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
1190 }
1191}
1192
1193#[allow(unused)]
1195pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
1196 match name {
1200 ".SystemUIFont" => system,
1201 ".ZedSans" | "Zed Plex Sans" => "IBM Plex Sans",
1202 ".ZedMono" | "Zed Plex Mono" => "Lilex",
1203 _ => name,
1204 }
1205}
1206
1207#[allow(unused)]
1209pub fn font_name_with_fallbacks_shared<'a>(
1210 name: &'a SharedString,
1211 system: &'a SharedString,
1212) -> &'a SharedString {
1213 match name.as_str() {
1217 ".SystemUIFont" => system,
1218 ".ZedSans" | "Zed Plex Sans" => const { &SharedString::new_static("IBM Plex Sans") },
1219 ".ZedMono" | "Zed Plex Mono" => const { &SharedString::new_static("Lilex") },
1220 _ => name,
1221 }
1222}