1use std::borrow::ToOwned;
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, OnceLock};
11use std::time::Instant;
12use std::{iter, str};
13
14use app_units::Au;
15use base::id::PainterId;
16use bitflags::bitflags;
17use euclid::default::{Point2D, Rect};
18use euclid::num::Zero;
19use fonts_traits::FontDescriptor;
20use log::debug;
21use malloc_size_of_derive::MallocSizeOf;
22use parking_lot::RwLock;
23use read_fonts::tables::os2::{Os2, SelectionFlags};
24use read_fonts::types::Tag;
25use rustc_hash::FxHashMap;
26use serde::{Deserialize, Serialize};
27use smallvec::SmallVec;
28use style::computed_values::font_variant_caps;
29use style::properties::style_structs::Font as FontStyleStruct;
30use style::values::computed::font::{
31 FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
32};
33use style::values::computed::{FontStretch, FontStyle, FontSynthesis, FontWeight};
34use unicode_script::Script;
35use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
36
37use crate::platform::font::{FontTable, PlatformFont};
38use crate::platform::font_list::fallback_font_families;
39use crate::{
40 EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
41 FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
42 FontTemplateRefMethods, GlyphId, GlyphStore, LocalFontIdentifier, ShapedGlyph, Shaper,
43};
44
45pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
46pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
47pub(crate) const KERN: Tag = Tag::new(b"kern");
48pub(crate) const SBIX: Tag = Tag::new(b"sbix");
49pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
50pub(crate) const COLR: Tag = Tag::new(b"COLR");
51pub(crate) const BASE: Tag = Tag::new(b"BASE");
52pub(crate) const LIGA: Tag = Tag::new(b"liga");
53
54pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
55
56static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
58
59pub trait PlatformFontMethods: Sized {
65 #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
66 fn new_from_template(
67 template: FontTemplateRef,
68 pt_size: Option<Au>,
69 variations: &[FontVariation],
70 data: &Option<FontData>,
71 synthetic_bold: bool,
72 ) -> Result<PlatformFont, &'static str> {
73 let template = template.borrow();
74 let font_identifier = template.identifier.clone();
75
76 match font_identifier {
77 FontIdentifier::Local(font_identifier) => Self::new_from_local_font_identifier(
78 font_identifier,
79 pt_size,
80 variations,
81 synthetic_bold,
82 ),
83 FontIdentifier::Web(_) => Self::new_from_data(
84 font_identifier,
85 data.as_ref()
86 .expect("Should never create a web font without data."),
87 pt_size,
88 variations,
89 synthetic_bold,
90 ),
91 }
92 }
93
94 fn new_from_local_font_identifier(
95 font_identifier: LocalFontIdentifier,
96 pt_size: Option<Au>,
97 variations: &[FontVariation],
98 synthetic_bold: bool,
99 ) -> Result<PlatformFont, &'static str>;
100
101 fn new_from_data(
102 font_identifier: FontIdentifier,
103 data: &FontData,
104 pt_size: Option<Au>,
105 variations: &[FontVariation],
106 synthetic_bold: bool,
107 ) -> Result<PlatformFont, &'static str>;
108
109 fn descriptor(&self) -> FontTemplateDescriptor;
112
113 fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
114 fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
115 fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
116
117 fn metrics(&self) -> FontMetrics;
118 fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
119 fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
120
121 fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
123
124 fn variations(&self) -> &[FontVariation];
126
127 fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
128 let mut style = FontStyle::NORMAL;
129 if os2.fs_selection().contains(SelectionFlags::ITALIC) {
130 style = FontStyle::ITALIC;
131 }
132
133 let weight = FontWeight::from_float(os2.us_weight_class() as f32);
134 let stretch = match os2.us_width_class() {
135 1 => FontStretch::ULTRA_CONDENSED,
136 2 => FontStretch::EXTRA_CONDENSED,
137 3 => FontStretch::CONDENSED,
138 4 => FontStretch::SEMI_CONDENSED,
139 5 => FontStretch::NORMAL,
140 6 => FontStretch::SEMI_EXPANDED,
141 7 => FontStretch::EXPANDED,
142 8 => FontStretch::EXTRA_EXPANDED,
143 9 => FontStretch::ULTRA_EXPANDED,
144 _ => FontStretch::NORMAL,
145 };
146
147 FontTemplateDescriptor::new(weight, stretch, style)
148 }
149}
150
151pub(crate) type FractionalPixel = f64;
153
154pub(crate) trait FontTableMethods {
155 fn buffer(&self) -> &[u8];
156}
157
158#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
159pub struct FontMetrics {
160 pub underline_size: Au,
161 pub underline_offset: Au,
162 pub strikeout_size: Au,
163 pub strikeout_offset: Au,
164 pub leading: Au,
165 pub x_height: Au,
166 pub em_size: Au,
167 pub ascent: Au,
168 pub descent: Au,
169 pub max_advance: Au,
170 pub average_advance: Au,
171 pub line_gap: Au,
172 pub zero_horizontal_advance: Option<Au>,
173 pub ic_horizontal_advance: Option<Au>,
174 pub space_advance: Au,
177}
178
179impl FontMetrics {
180 pub fn empty() -> Arc<Self> {
183 static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
184 EMPTY.get_or_init(Default::default).clone()
185 }
186}
187
188#[derive(Debug, Default)]
189struct CachedShapeData {
190 glyph_advances: HashMap<GlyphId, FractionalPixel>,
191 glyph_indices: HashMap<char, Option<GlyphId>>,
192 shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
193}
194
195impl malloc_size_of::MallocSizeOf for CachedShapeData {
196 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
197 let shaped_text_size = self
200 .shaped_text
201 .iter()
202 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
203 .sum::<usize>();
204 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
205 }
206}
207
208pub struct Font {
209 pub(crate) handle: PlatformFont,
210 pub(crate) template: FontTemplateRef,
211 pub metrics: Arc<FontMetrics>,
212 pub descriptor: FontDescriptor,
213
214 data_and_index: OnceLock<FontDataAndIndex>,
217
218 shaper: OnceLock<Shaper>,
219 cached_shape_data: RwLock<CachedShapeData>,
220 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
221
222 pub(crate) synthesized_small_caps: Option<FontRef>,
226
227 has_color_bitmap_or_colr_table: OnceLock<bool>,
231
232 can_do_fast_shaping: OnceLock<bool>,
239}
240
241impl std::fmt::Debug for Font {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 f.debug_struct("Font")
244 .field("template", &self.template)
245 .field("descriptor", &self.descriptor)
246 .finish()
247 }
248}
249
250impl malloc_size_of::MallocSizeOf for Font {
251 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
252 self.metrics.size_of(ops) +
256 self.descriptor.size_of(ops) +
257 self.cached_shape_data.read().size_of(ops) +
258 self.font_instance_key
259 .read()
260 .values()
261 .map(|key| key.size_of(ops))
262 .sum::<usize>()
263 }
264}
265
266impl Font {
267 pub fn new(
268 template: FontTemplateRef,
269 descriptor: FontDescriptor,
270 data: Option<FontData>,
271 synthesized_small_caps: Option<FontRef>,
272 ) -> Result<Font, &'static str> {
273 let synthetic_bold = {
274 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
275 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
276
277 is_bold && allows_synthetic_bold
278 };
279
280 let handle = PlatformFont::new_from_template(
281 template.clone(),
282 Some(descriptor.pt_size),
283 &descriptor.variation_settings,
284 &data,
285 synthetic_bold,
286 )?;
287 let metrics = Arc::new(handle.metrics());
288
289 Ok(Font {
290 handle,
291 template,
292 metrics,
293 descriptor,
294 data_and_index: data
295 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
296 .unwrap_or_default(),
297 shaper: OnceLock::new(),
298 cached_shape_data: Default::default(),
299 font_instance_key: Default::default(),
300 synthesized_small_caps,
301 has_color_bitmap_or_colr_table: OnceLock::new(),
302 can_do_fast_shaping: OnceLock::new(),
303 })
304 }
305
306 pub fn identifier(&self) -> FontIdentifier {
308 self.template.identifier()
309 }
310
311 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
312 self.handle.webrender_font_instance_flags()
313 }
314
315 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
316 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
317 self.table_for_tag(SBIX).is_some() ||
318 self.table_for_tag(CBDT).is_some() ||
319 self.table_for_tag(COLR).is_some()
320 })
321 }
322
323 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
324 *self
325 .font_instance_key
326 .write()
327 .entry(painter_id)
328 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
329 }
330
331 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
334 if let Some(data_and_index) = self.data_and_index.get() {
335 return Ok(data_and_index);
336 }
337
338 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
339 unreachable!("All web fonts should already have initialized data");
340 };
341 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
342 return Err(FontDataError::FailedToLoad);
343 };
344
345 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
346 Ok(data_and_index)
347 }
348
349 pub(crate) fn variations(&self) -> &[FontVariation] {
350 self.handle.variations()
351 }
352}
353
354bitflags! {
355 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
356 pub struct ShapingFlags: u8 {
357 const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
359 const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
361 const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
363 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
365 const RTL_FLAG = 1 << 4;
367 const KEEP_ALL_FLAG = 1 << 5;
369 }
370}
371
372#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
374pub struct ShapingOptions {
375 pub letter_spacing: Option<Au>,
378 pub word_spacing: Au,
380 pub script: Script,
382 pub flags: ShapingFlags,
384}
385
386#[derive(Clone, Debug, Eq, Hash, PartialEq)]
388struct ShapeCacheEntry {
389 text: String,
390 options: ShapingOptions,
391}
392
393impl Font {
394 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
395 let lookup_key = ShapeCacheEntry {
396 text: text.to_owned(),
397 options: *options,
398 };
399 {
400 let cache = self.cached_shape_data.read();
401 if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
402 return shaped_text.clone();
403 }
404 }
405
406 let start_time = Instant::now();
407 let glyphs = if self.can_do_fast_shaping(text, options) {
408 debug!("shape_text: Using ASCII fast path.");
409 self.shape_text_fast(text, options)
410 } else {
411 debug!("shape_text: Using Harfbuzz.");
412 self.shaper
413 .get_or_init(|| Shaper::new(self))
414 .shape_text(self, text, options)
415 };
416
417 let shaped_text = Arc::new(glyphs);
418 let mut cache = self.cached_shape_data.write();
419 cache.shaped_text.insert(lookup_key, shaped_text.clone());
420
421 TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
422 ((Instant::now() - start_time).as_nanos()) as usize,
423 Ordering::Relaxed,
424 );
425
426 shaped_text
427 }
428
429 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
434 options.script == Script::Latin &&
435 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
436 *self.can_do_fast_shaping.get_or_init(|| {
437 self.table_for_tag(KERN).is_some() &&
438 self.table_for_tag(GPOS).is_none() &&
439 self.table_for_tag(GSUB).is_none()
440 }) &&
441 text.is_ascii()
442 }
443
444 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> GlyphStore {
446 let mut glyph_store = GlyphStore::new(text, text.len(), options);
447 let mut prev_glyph_id = None;
448 for (string_byte_offset, byte) in text.bytes().enumerate() {
449 let character = byte as char;
450 let Some(glyph_id) = self.glyph_index(character) else {
451 continue;
452 };
453
454 let mut advance = advance_for_shaped_glyph(
455 Au::from_f64_px(self.glyph_h_advance(glyph_id)),
456 character,
457 options,
458 );
459 let offset = prev_glyph_id.map(|prev| {
460 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
461 advance += h_kerning;
462 Point2D::new(h_kerning, Au::zero())
463 });
464
465 glyph_store.add_glyph(
466 character,
467 &ShapedGlyph {
468 glyph_id,
469 string_byte_offset,
470 advance,
471 offset,
472 },
473 );
474 prev_glyph_id = Some(glyph_id);
475 }
476 glyph_store
477 }
478
479 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
480 let result = self.handle.table_for_tag(tag);
481 let status = if result.is_some() {
482 "Found"
483 } else {
484 "Didn't find"
485 };
486
487 debug!(
488 "{} font table[{}] in {:?},",
489 status,
490 str::from_utf8(tag.as_ref()).unwrap(),
491 self.identifier()
492 );
493 result
494 }
495
496 #[inline]
497 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
498 {
499 let cache = self.cached_shape_data.read();
500 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
501 return *glyph;
502 }
503 }
504 let codepoint = match self.descriptor.variant {
505 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
506 font_variant_caps::T::Normal => codepoint,
507 };
508 let glyph_index = self.handle.glyph_index(codepoint);
509
510 let mut cache = self.cached_shape_data.write();
511 cache.glyph_indices.insert(codepoint, glyph_index);
512 glyph_index
513 }
514
515 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
516 self.glyph_index(codepoint).is_some()
517 }
518
519 pub(crate) fn glyph_h_kerning(
520 &self,
521 first_glyph: GlyphId,
522 second_glyph: GlyphId,
523 ) -> FractionalPixel {
524 self.handle.glyph_h_kerning(first_glyph, second_glyph)
525 }
526
527 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
528 {
529 let cache = self.cached_shape_data.read();
530 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
531 return *width;
532 }
533 }
534
535 let new_width = self
536 .handle
537 .glyph_h_advance(glyph_id)
538 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
539 let mut cache = self.cached_shape_data.write();
540 cache.glyph_advances.insert(glyph_id, new_width);
541 new_width
542 }
543
544 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
545 self.handle.typographic_bounds(glyph_id)
546 }
547
548 pub fn baseline(&self) -> Option<FontBaseline> {
550 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
551 }
552}
553
554#[derive(Clone, Debug, MallocSizeOf)]
555pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
556
557impl Deref for FontRef {
558 type Target = Arc<Font>;
559 fn deref(&self) -> &Self::Target {
560 &self.0
561 }
562}
563
564#[derive(MallocSizeOf)]
568pub struct FontGroup {
569 descriptor: FontDescriptor,
570 families: SmallVec<[FontGroupFamily; 8]>,
571}
572
573impl FontGroup {
574 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
575 let families: SmallVec<[FontGroupFamily; 8]> = style
576 .font_family
577 .families
578 .iter()
579 .map(FontGroupFamily::local_or_web)
580 .collect();
581
582 FontGroup {
583 descriptor,
584 families,
585 }
586 }
587
588 pub fn find_by_codepoint(
593 &self,
594 font_context: &FontContext,
595 codepoint: char,
596 next_codepoint: Option<char>,
597 first_fallback: Option<FontRef>,
598 lang: Option<String>,
599 ) -> Option<FontRef> {
600 let codepoint = match codepoint {
604 '\t' => ' ',
605 _ => codepoint,
606 };
607
608 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
609
610 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
611 options.character.is_ascii_lowercase();
612 let font_or_synthesized_small_caps = |font: FontRef| {
613 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
614 return font.synthesized_small_caps.clone();
615 }
616 Some(font)
617 };
618
619 let font_has_glyph_and_presentation = |font: &FontRef| {
620 match options.presentation_preference {
622 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
623 return false;
624 },
625 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
626 return false;
627 },
628 _ => {},
629 }
630 font.has_glyph_for(options.character)
631 };
632
633 let char_in_template =
634 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
635
636 if let Some(font) = self.find(
637 font_context,
638 &char_in_template,
639 &font_has_glyph_and_presentation,
640 ) {
641 return font_or_synthesized_small_caps(font);
642 }
643
644 if let Some(ref first_fallback) = first_fallback {
645 if char_in_template(first_fallback.template.clone()) &&
646 font_has_glyph_and_presentation(first_fallback)
647 {
648 return font_or_synthesized_small_caps(first_fallback.clone());
649 }
650 }
651
652 if let Some(font) = self.find_fallback(
653 font_context,
654 options.clone(),
655 &char_in_template,
656 &font_has_glyph_and_presentation,
657 ) {
658 return font_or_synthesized_small_caps(font);
659 }
660
661 self.first(font_context)
662 }
663
664 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
666 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
674 let font_predicate = |_: &FontRef| true;
675 self.find(font_context, &space_in_template, &font_predicate)
676 .or_else(|| {
677 self.find_fallback(
678 font_context,
679 FallbackFontSelectionOptions::default(),
680 &space_in_template,
681 &font_predicate,
682 )
683 })
684 }
685
686 fn find(
690 &self,
691 font_context: &FontContext,
692 template_predicate: &impl Fn(FontTemplateRef) -> bool,
693 font_predicate: &impl Fn(&FontRef) -> bool,
694 ) -> Option<FontRef> {
695 self.families
696 .iter()
697 .flat_map(|family| family.templates(font_context, &self.descriptor))
698 .find_map(|template| {
699 template.font_if_matches(
700 font_context,
701 &self.descriptor,
702 template_predicate,
703 font_predicate,
704 )
705 })
706 }
707
708 fn find_fallback(
713 &self,
714 font_context: &FontContext,
715 options: FallbackFontSelectionOptions,
716 template_predicate: &impl Fn(FontTemplateRef) -> bool,
717 font_predicate: &impl Fn(&FontRef) -> bool,
718 ) -> Option<FontRef> {
719 iter::once(FontFamilyDescriptor::default())
720 .chain(
721 fallback_font_families(options)
722 .into_iter()
723 .map(|family_name| {
724 let family = SingleFontFamily::FamilyName(FamilyName {
725 name: family_name.into(),
726 syntax: FontFamilyNameSyntax::Quoted,
727 });
728 FontFamilyDescriptor::new(family, FontSearchScope::Local)
729 }),
730 )
731 .find_map(|family_descriptor| {
732 FontGroupFamily::from(family_descriptor)
733 .templates(font_context, &self.descriptor)
734 .find_map(|template| {
735 template.font_if_matches(
736 font_context,
737 &self.descriptor,
738 template_predicate,
739 font_predicate,
740 )
741 })
742 })
743 }
744}
745
746#[derive(MallocSizeOf)]
753struct FontGroupFamilyTemplate {
754 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
755 template: FontTemplateRef,
756 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
757 font: OnceLock<Option<FontRef>>,
758}
759
760impl From<FontTemplateRef> for FontGroupFamilyTemplate {
761 fn from(template: FontTemplateRef) -> Self {
762 Self {
763 template,
764 font: Default::default(),
765 }
766 }
767}
768
769impl FontGroupFamilyTemplate {
770 fn font(
771 &self,
772 font_context: &FontContext,
773 font_descriptor: &FontDescriptor,
774 ) -> Option<FontRef> {
775 self.font
776 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
777 .clone()
778 }
779
780 fn font_if_matches(
781 &self,
782 font_context: &FontContext,
783 font_descriptor: &FontDescriptor,
784 template_predicate: &impl Fn(FontTemplateRef) -> bool,
785 font_predicate: &impl Fn(&FontRef) -> bool,
786 ) -> Option<FontRef> {
787 if !template_predicate(self.template.clone()) {
788 return None;
789 }
790 self.font(font_context, font_descriptor)
791 .filter(font_predicate)
792 }
793}
794
795#[derive(MallocSizeOf)]
800struct FontGroupFamily {
801 family_descriptor: FontFamilyDescriptor,
802 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
803}
804
805impl From<FontFamilyDescriptor> for FontGroupFamily {
806 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
807 Self {
808 family_descriptor,
809 members: Default::default(),
810 }
811 }
812}
813
814impl FontGroupFamily {
815 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
816 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
817 }
818
819 fn templates(
820 &self,
821 font_context: &FontContext,
822 font_descriptor: &FontDescriptor,
823 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
824 self.members
825 .get_or_init(|| {
826 font_context
827 .matching_templates(font_descriptor, &self.family_descriptor)
828 .into_iter()
829 .map(Into::into)
830 .collect()
831 })
832 .iter()
833 }
834}
835
836#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
838pub enum FontSearchScope {
839 Any,
841
842 Local,
844}
845
846#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
848pub struct FontFamilyDescriptor {
849 pub(crate) family: SingleFontFamily,
850 pub(crate) scope: FontSearchScope,
851}
852
853impl FontFamilyDescriptor {
854 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
855 FontFamilyDescriptor { family, scope }
856 }
857
858 fn default() -> FontFamilyDescriptor {
859 FontFamilyDescriptor {
860 family: SingleFontFamily::Generic(GenericFontFamily::None),
861 scope: FontSearchScope::Local,
862 }
863 }
864}
865
866pub struct FontBaseline {
867 pub ideographic_baseline: f32,
868 pub alphabetic_baseline: f32,
869 pub hanging_baseline: f32,
870}
871
872#[cfg(all(
888 any(target_os = "linux", target_os = "macos"),
889 not(target_env = "ohos")
890))]
891pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
892 if value < mapping[0].0 {
893 return mapping[0].1;
894 }
895
896 for window in mapping.windows(2) {
897 let (font_config_value_a, css_value_a) = window[0];
898 let (font_config_value_b, css_value_b) = window[1];
899
900 if value >= font_config_value_a && value <= font_config_value_b {
901 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
902 return css_value_a + ((css_value_b - css_value_a) * ratio);
903 }
904 }
905
906 mapping[mapping.len() - 1].1
907}
908
909pub(super) fn advance_for_shaped_glyph(
911 mut advance: Au,
912 character: char,
913 options: &ShapingOptions,
914) -> Au {
915 if let Some(letter_spacing) = options.letter_spacing {
916 advance += letter_spacing;
917 };
918
919 if character == ' ' || character == '\u{a0}' {
924 advance += options.word_spacing;
926 }
927
928 advance
929}