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