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, 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
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
58pub struct TextSystemCacheStats {
59 pub lines: usize,
61 pub wrapped_lines: usize,
63 pub raster_bounds: usize,
65}
66
67pub struct TextSystem {
69 platform_text_system: Arc<dyn PlatformTextSystem>,
70 global_line_layout_cache: Arc<GlobalLineLayoutCache>,
71 font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
72 font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
73 raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
74 raster_bounds_max_entries: Option<usize>,
75 wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
76 font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
77 fallback_font_stack: SmallVec<[Font; 2]>,
78}
79
80impl TextSystem {
81 pub(crate) fn new(
82 platform_text_system: Arc<dyn PlatformTextSystem>,
83 text_budget: &crate::TextResourceBudget,
84 ) -> Self {
85 TextSystem {
86 platform_text_system,
87 global_line_layout_cache: Arc::new(GlobalLineLayoutCache::new(
88 text_budget.line_layout_cache_max_entries,
89 text_budget.line_layout_cache_low_watermark,
90 )),
91 font_metrics: RwLock::default(),
92 raster_bounds: RwLock::default(),
93 raster_bounds_max_entries: text_budget.raster_bounds_cache_max_entries,
94 font_ids_by_font: RwLock::default(),
95 wrapper_pool: Mutex::default(),
96 font_runs_pool: Mutex::default(),
97 fallback_font_stack: smallvec![
98 font(".ZedMono"),
100 font(".ZedSans"),
101 font("Helvetica"),
102 font("Segoe UI"), font("Cantarell"), font("Ubuntu"), font("Noto Sans"), font("DejaVu Sans")
107 ],
108 }
109 }
110
111 #[cfg(test)]
112 pub(crate) fn resource_budget_for_test(&self) -> (usize, usize, Option<usize>) {
113 let (max_entries, low_watermark) = self.global_line_layout_cache.budget_for_test();
114 (max_entries, low_watermark, self.raster_bounds_max_entries)
115 }
116
117 pub fn clear_layout_cache(&self) {
130 self.global_line_layout_cache.clear();
131 self.raster_bounds.write().clear();
132 }
133
134 pub fn layout_cache_stats(&self) -> TextSystemCacheStats {
137 let (lines, wrapped_lines) = self.global_line_layout_cache.entry_counts();
138 TextSystemCacheStats {
139 lines,
140 wrapped_lines,
141 raster_bounds: self.raster_bounds.read().len(),
142 }
143 }
144
145 pub fn all_font_names(&self) -> Vec<String> {
147 let mut names = self.platform_text_system.all_font_names();
148 names.extend(
149 self.fallback_font_stack
150 .iter()
151 .map(|font| font.family.to_string()),
152 );
153 names.push(".SystemUIFont".to_string());
154 names.sort();
155 names.dedup();
156 names
157 }
158
159 pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
161 self.platform_text_system.add_fonts(fonts)
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 glyph_dilation_for_color(&self, color: Hsla) -> u8 {
184 self.platform_text_system.glyph_dilation_for_color(color)
185 }
186
187 fn font_id(&self, font: &Font) -> Result<FontId> {
189 fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
190 match font_id {
191 Ok(font_id) => Ok(*font_id),
192 Err(err) => Err(anyhow!("{err}")),
193 }
194 }
195
196 let font_id = self
197 .font_ids_by_font
198 .read()
199 .get(font)
200 .map(clone_font_id_result);
201 if let Some(font_id) = font_id {
202 font_id
203 } else {
204 let font_id = self.platform_text_system.font_id(font);
205 self.font_ids_by_font
206 .write()
207 .insert(font.clone(), clone_font_id_result(&font_id));
208 font_id
209 }
210 }
211
212 pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
214 let lock = self.font_ids_by_font.read();
215 lock.iter()
216 .filter_map(|(font, result)| match result {
217 Ok(font_id) if *font_id == id => Some(font.clone()),
218 _ => None,
219 })
220 .next()
221 }
222
223 pub fn resolve_font(&self, font: &Font) -> FontId {
230 if let Ok(font_id) = self.font_id(font) {
231 return font_id;
232 }
233 for fallback in &self.fallback_font_stack {
234 if let Ok(font_id) = self.font_id(fallback) {
235 return font_id;
236 }
237 }
238
239 panic!(
240 "failed to resolve font '{}' or any of the fallbacks: {}",
241 font.family,
242 self.fallback_font_stack
243 .iter()
244 .map(|fallback| &fallback.family)
245 .join(", ")
246 );
247 }
248
249 pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
253 self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
254 }
255
256 pub fn typographic_bounds(
258 &self,
259 font_id: FontId,
260 font_size: Pixels,
261 character: char,
262 ) -> Result<Bounds<Pixels>> {
263 let glyph_id = self
264 .platform_text_system
265 .glyph_for_char(font_id, character)
266 .with_context(|| format!("glyph not found for character '{character}'"))?;
267 let bounds = self
268 .platform_text_system
269 .typographic_bounds(font_id, glyph_id)?;
270 Ok(self.read_metrics(font_id, |metrics| {
271 (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
272 }))
273 }
274
275 pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
277 let glyph_id = self
278 .platform_text_system
279 .glyph_for_char(font_id, ch)
280 .with_context(|| format!("glyph not found for character '{ch}'"))?;
281 let result = self.platform_text_system.advance(font_id, glyph_id)?
282 / self.units_per_em(font_id) as f32;
283
284 Ok(result * font_size)
285 }
286
287 pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
291 Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
292 }
293
294 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
298 Ok(self.advance(font_id, font_size, 'm')?.width)
299 }
300
301 pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
305 Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
306 }
307
308 pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
312 Ok(self.advance(font_id, font_size, '0')?.width)
313 }
314
315 pub fn units_per_em(&self, font_id: FontId) -> u32 {
319 self.read_metrics(font_id, |metrics| metrics.units_per_em)
320 }
321
322 pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
324 self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
325 }
326
327 pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
329 self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
330 }
331
332 pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
334 self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
335 }
336
337 pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
340 self.read_metrics(font_id, |metrics| metrics.descent(font_size))
341 }
342
343 pub fn baseline_offset(
345 &self,
346 font_id: FontId,
347 font_size: Pixels,
348 line_height: Pixels,
349 ) -> Pixels {
350 let ascent = self.ascent(font_id, font_size);
351 let descent = self.descent(font_id, font_size);
352 let padding_top = (line_height - ascent - descent) / 2.;
353 padding_top + ascent
354 }
355
356 fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
357 let lock = self.font_metrics.upgradable_read();
358
359 if let Some(metrics) = lock.get(&font_id) {
360 read(metrics)
361 } else {
362 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
363 let metrics = lock
364 .entry(font_id)
365 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
366 read(metrics)
367 }
368 }
369
370 pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
372 let lock = &mut self.wrapper_pool.lock();
373 let font_id = self.resolve_font(&font);
374 let wrappers = lock
375 .entry(FontIdWithSize { font_id, font_size })
376 .or_default();
377 let wrapper = wrappers.pop().unwrap_or_else(|| {
378 LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
379 });
380
381 LineWrapperHandle {
382 wrapper: Some(wrapper),
383 text_system: self.clone(),
384 }
385 }
386
387 pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
389 let raster_bounds = self.raster_bounds.upgradable_read();
390 if let Some(bounds) = raster_bounds.get(params) {
391 Ok(*bounds)
392 } else {
393 let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
394 if let Some(max) = self.raster_bounds_max_entries {
398 if raster_bounds.len() >= max {
399 raster_bounds.clear();
400 }
401 }
402 let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
403 raster_bounds.insert(params.clone(), bounds);
404 Ok(bounds)
405 }
406 }
407
408 pub(crate) fn rasterize_glyph(
409 &self,
410 params: &RenderGlyphParams,
411 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
412 let raster_bounds = self.raster_bounds(params)?;
413 self.platform_text_system
414 .rasterize_glyph(params, raster_bounds)
415 }
416}
417
418#[derive(Deref)]
420pub struct WindowTextSystem {
421 line_layout_cache: LineLayoutCache,
422 #[deref]
423 text_system: Arc<TextSystem>,
424}
425
426impl WindowTextSystem {
427 #[doc(hidden)]
428 pub fn new(text_system: Arc<TextSystem>) -> Self {
429 Self {
430 line_layout_cache: LineLayoutCache::new(
431 text_system.platform_text_system.clone(),
432 text_system.global_line_layout_cache.clone(),
433 ),
434 text_system,
435 }
436 }
437
438 pub(crate) fn layout_index(&self) -> LineLayoutIndex {
439 self.line_layout_cache.layout_index()
440 }
441
442 pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
443 self.line_layout_cache.reuse_layouts(index)
444 }
445
446 pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
447 self.line_layout_cache.truncate_layouts(index)
448 }
449
450 pub fn shape_line(
457 &self,
458 text: SharedString,
459 font_size: Pixels,
460 runs: &[TextRun],
461 force_width: Option<Pixels>,
462 ) -> ShapedLine {
463 self.shape_line_with_spacing(text, font_size, runs, force_width, None)
464 }
465
466 pub fn shape_line_with_spacing(
468 &self,
469 text: SharedString,
470 font_size: Pixels,
471 runs: &[TextRun],
472 force_width: Option<Pixels>,
473 letter_spacing: Option<Pixels>,
474 ) -> ShapedLine {
475 debug_assert!(
476 text.find('\n').is_none(),
477 "text argument should not contain newlines"
478 );
479
480 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
481 for run in runs {
482 if let Some(last_run) = decoration_runs.last_mut()
483 && last_run.color == run.color
484 && last_run.underline == run.underline
485 && last_run.strikethrough == run.strikethrough
486 && last_run.background_color == run.background_color
487 {
488 last_run.len += run.len as u32;
489 continue;
490 }
491 decoration_runs.push(DecorationRun {
492 len: run.len as u32,
493 color: run.color,
494 background_color: run.background_color,
495 underline: run.underline,
496 strikethrough: run.strikethrough,
497 });
498 }
499
500 let layout =
501 self.layout_line_with_spacing(&text, font_size, runs, force_width, letter_spacing);
502
503 ShapedLine {
504 layout,
505 text,
506 decoration_runs,
507 }
508 }
509
510 pub fn shape_text(
514 &self,
515 text: SharedString,
516 font_size: Pixels,
517 runs: &[TextRun],
518 wrap_width: Option<Pixels>,
519 line_clamp: Option<usize>,
520 ) -> Result<SmallVec<[WrappedLine; 1]>> {
521 let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
522 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
523
524 let mut lines = SmallVec::new();
525 let mut line_start = 0;
526 let mut max_wrap_lines = line_clamp.unwrap_or(usize::MAX);
527 let mut wrapped_lines = 0;
528
529 let mut process_line = |line_text: SharedString| {
530 font_runs.clear();
531 let line_end = line_start + line_text.len();
532
533 let mut last_font: Option<FontId> = None;
534 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
535 let mut run_start = line_start;
536 while run_start < line_end {
537 let Some(run) = runs.peek_mut() else {
538 break;
539 };
540
541 let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
542
543 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
544 && last_run.color == run.color
545 && last_run.underline == run.underline
546 && last_run.strikethrough == run.strikethrough
547 && last_run.background_color == run.background_color
548 {
549 last_run.len += run_len_within_line as u32;
550 false
551 } else {
552 decoration_runs.push(DecorationRun {
553 len: run_len_within_line as u32,
554 color: run.color,
555 background_color: run.background_color,
556 underline: run.underline,
557 strikethrough: run.strikethrough,
558 });
559 true
560 };
561
562 if let Some(font_run) = font_runs.last_mut()
563 && Some(font_run.font_id) == last_font
564 && !decoration_changed
565 {
566 font_run.len += run_len_within_line;
567 } else {
568 let font_id = self.resolve_font(&run.font);
569 last_font = Some(font_id);
570 font_runs.push(FontRun {
571 len: run_len_within_line,
572 font_id,
573 });
574 }
575
576 if run_len_within_line == run.len {
577 runs.next();
578 } else {
579 run.len -= run_len_within_line;
581 }
582 run_start += run_len_within_line;
583 }
584
585 let layout = self.line_layout_cache.layout_wrapped_line(
586 &line_text,
587 font_size,
588 &font_runs,
589 wrap_width,
590 Some(max_wrap_lines - wrapped_lines),
591 );
592 wrapped_lines += layout.wrap_boundaries.len();
593
594 lines.push(WrappedLine {
595 layout,
596 decoration_runs,
597 text: line_text,
598 });
599
600 line_start = line_end + 1;
602 if let Some(run) = runs.peek_mut() {
603 run.len -= 1;
604 if run.len == 0 {
605 runs.next();
606 }
607 }
608 };
609
610 let mut split_lines = text.split('\n');
611 let mut processed = false;
612
613 if let Some(first_line) = split_lines.next()
614 && let Some(second_line) = split_lines.next()
615 {
616 processed = true;
617 process_line(first_line.to_string().into());
618 process_line(second_line.to_string().into());
619 for line_text in split_lines {
620 process_line(line_text.to_string().into());
621 }
622 }
623
624 if !processed {
625 process_line(text);
626 }
627
628 self.font_runs_pool.lock().push(font_runs);
629
630 Ok(lines)
631 }
632
633 pub(crate) fn finish_frame(&self) {
634 self.line_layout_cache.finish_frame()
635 }
636
637 pub fn layout_line(
642 &self,
643 text: &str,
644 font_size: Pixels,
645 runs: &[TextRun],
646 force_width: Option<Pixels>,
647 ) -> Arc<LineLayout> {
648 self.layout_line_with_spacing(text, font_size, runs, force_width, None)
649 }
650
651 pub fn layout_line_with_spacing(
653 &self,
654 text: &str,
655 font_size: Pixels,
656 runs: &[TextRun],
657 force_width: Option<Pixels>,
658 letter_spacing: Option<Pixels>,
659 ) -> Arc<LineLayout> {
660 let mut last_run = None::<&TextRun>;
661 let mut last_font: Option<FontId> = None;
662 let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
663 font_runs.clear();
664
665 for run in runs.iter() {
666 let decoration_changed = if let Some(last_run) = last_run
667 && last_run.color == run.color
668 && last_run.underline == run.underline
669 && last_run.strikethrough == run.strikethrough
670 {
671 false
672 } else {
673 last_run = Some(run);
674 true
675 };
676
677 if let Some(font_run) = font_runs.last_mut()
678 && Some(font_run.font_id) == last_font
679 && !decoration_changed
680 {
681 font_run.len += run.len;
682 } else {
683 let font_id = self.resolve_font(&run.font);
684 last_font = Some(font_id);
685 font_runs.push(FontRun {
686 len: run.len,
687 font_id,
688 });
689 }
690 }
691
692 let layout = self.line_layout_cache.layout_line_with_spacing(
693 &SharedString::new(text),
694 font_size,
695 &font_runs,
696 force_width,
697 letter_spacing,
698 );
699
700 self.font_runs_pool.lock().push(font_runs);
701
702 layout
703 }
704}
705
706#[derive(Hash, Eq, PartialEq)]
707struct FontIdWithSize {
708 font_id: FontId,
709 font_size: Pixels,
710}
711
712pub struct LineWrapperHandle {
714 wrapper: Option<LineWrapper>,
715 text_system: Arc<TextSystem>,
716}
717
718impl Drop for LineWrapperHandle {
719 fn drop(&mut self) {
720 let mut state = self.text_system.wrapper_pool.lock();
721 let wrapper = self.wrapper.take().unwrap();
722 state
723 .get_mut(&FontIdWithSize {
724 font_id: wrapper.font_id,
725 font_size: wrapper.font_size,
726 })
727 .unwrap()
728 .push(wrapper);
729 }
730}
731
732impl Deref for LineWrapperHandle {
733 type Target = LineWrapper;
734
735 fn deref(&self) -> &Self::Target {
736 self.wrapper.as_ref().unwrap()
737 }
738}
739
740impl DerefMut for LineWrapperHandle {
741 fn deref_mut(&mut self) -> &mut Self::Target {
742 self.wrapper.as_mut().unwrap()
743 }
744}
745
746#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)]
749#[serde(transparent)]
750pub struct FontWeight(pub f32);
751
752impl Display for FontWeight {
753 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
754 write!(f, "{}", self.0)
755 }
756}
757
758impl From<f32> for FontWeight {
759 fn from(weight: f32) -> Self {
760 FontWeight(weight)
761 }
762}
763
764impl Default for FontWeight {
765 #[inline]
766 fn default() -> FontWeight {
767 FontWeight::NORMAL
768 }
769}
770
771impl Hash for FontWeight {
772 fn hash<H: Hasher>(&self, state: &mut H) {
773 state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
774 }
775}
776
777impl Eq for FontWeight {}
778
779impl FontWeight {
780 pub const THIN: FontWeight = FontWeight(100.0);
782 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
784 pub const LIGHT: FontWeight = FontWeight(300.0);
786 pub const NORMAL: FontWeight = FontWeight(400.0);
788 pub const MEDIUM: FontWeight = FontWeight(500.0);
790 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
792 pub const BOLD: FontWeight = FontWeight(700.0);
794 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
796 pub const BLACK: FontWeight = FontWeight(900.0);
798
799 pub const ALL: [FontWeight; 9] = [
801 Self::THIN,
802 Self::EXTRA_LIGHT,
803 Self::LIGHT,
804 Self::NORMAL,
805 Self::MEDIUM,
806 Self::SEMIBOLD,
807 Self::BOLD,
808 Self::EXTRA_BOLD,
809 Self::BLACK,
810 ];
811}
812
813impl schemars::JsonSchema for FontWeight {
814 fn schema_name() -> std::borrow::Cow<'static, str> {
815 "FontWeight".into()
816 }
817
818 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
819 use schemars::json_schema;
820 json_schema!({
821 "type": "number",
822 "minimum": Self::THIN,
823 "maximum": Self::BLACK,
824 "default": Self::default(),
825 "description": "Font weight value between 100 (thin) and 900 (black)"
826 })
827 }
828}
829
830#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
832pub enum FontStyle {
833 #[default]
835 Normal,
836 Italic,
838 Oblique,
840}
841
842impl Display for FontStyle {
843 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
844 Debug::fmt(self, f)
845 }
846}
847
848#[derive(Clone, Debug, PartialEq, Eq)]
850pub struct TextRun {
851 pub len: usize,
853 pub font: Font,
855 pub color: Hsla,
857 pub background_color: Option<Hsla>,
859 pub underline: Option<UnderlineStyle>,
861 pub strikethrough: Option<StrikethroughStyle>,
863}
864
865#[cfg(all(target_os = "macos", test))]
866impl TextRun {
867 fn with_len(&self, len: usize) -> Self {
868 let mut this = self.clone();
869 this.len = len;
870 this
871 }
872}
873
874#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
876#[repr(C)]
877pub struct GlyphId(pub u32);
878
879#[derive(Clone, Debug, PartialEq)]
880#[allow(missing_docs)]
881pub struct RenderGlyphParams {
882 pub font_id: FontId,
883 pub glyph_id: GlyphId,
884 pub font_size: Pixels,
885 pub subpixel_variant: Point<u8>,
886 pub scale_factor: f32,
887 pub is_emoji: bool,
888 pub dilation: u8,
889}
890
891impl Eq for RenderGlyphParams {}
892
893impl Hash for RenderGlyphParams {
894 fn hash<H: Hasher>(&self, state: &mut H) {
895 self.font_id.0.hash(state);
896 self.glyph_id.0.hash(state);
897 self.font_size.0.to_bits().hash(state);
898 self.subpixel_variant.hash(state);
899 self.scale_factor.to_bits().hash(state);
900 self.is_emoji.hash(state);
901 self.dilation.hash(state);
902 }
903}
904
905#[derive(Clone, Debug, Eq, PartialEq, Hash)]
907pub struct Font {
908 pub family: SharedString,
912
913 pub features: FontFeatures,
915
916 pub fallbacks: Option<FontFallbacks>,
918
919 pub weight: FontWeight,
921
922 pub style: FontStyle,
924}
925
926pub fn font(family: impl Into<SharedString>) -> Font {
928 Font {
929 family: family.into(),
930 features: FontFeatures::default(),
931 weight: FontWeight::default(),
932 style: FontStyle::default(),
933 fallbacks: None,
934 }
935}
936
937impl Font {
938 pub fn bold(mut self) -> Self {
940 self.weight = FontWeight::BOLD;
941 self
942 }
943
944 pub fn italic(mut self) -> Self {
946 self.style = FontStyle::Italic;
947 self
948 }
949}
950
951#[derive(Clone, Copy, Debug)]
954pub struct FontMetrics {
955 pub units_per_em: u32,
958
959 pub ascent: f32,
961
962 pub descent: f32,
964
965 pub line_gap: f32,
967
968 pub underline_position: f32,
970
971 pub underline_thickness: f32,
973
974 pub cap_height: f32,
976
977 pub x_height: f32,
979
980 pub bounding_box: Bounds<f32>,
983}
984
985impl FontMetrics {
986 pub fn ascent(&self, font_size: Pixels) -> Pixels {
988 Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
989 }
990
991 pub fn descent(&self, font_size: Pixels) -> Pixels {
993 Pixels((self.descent / self.units_per_em as f32) * font_size.0)
994 }
995
996 pub fn line_gap(&self, font_size: Pixels) -> Pixels {
998 Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
999 }
1000
1001 pub fn underline_position(&self, font_size: Pixels) -> Pixels {
1003 Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
1004 }
1005
1006 pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
1008 Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
1009 }
1010
1011 pub fn cap_height(&self, font_size: Pixels) -> Pixels {
1013 Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
1014 }
1015
1016 pub fn x_height(&self, font_size: Pixels) -> Pixels {
1018 Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
1019 }
1020
1021 pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
1023 (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
1024 }
1025}
1026
1027#[allow(unused)]
1028#[doc(hidden)]
1029pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
1030 match name {
1034 ".SystemUIFont" => system,
1035 ".ZedSans" | "Zed Plex Sans" => "IBM Plex Sans",
1036 ".ZedMono" | "Zed Plex Mono" => "Lilex",
1037 _ => name,
1038 }
1039}