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
189#[derive(Debug, Default)]
190struct CachedShapeData {
191 glyph_advances: HashMap<GlyphId, FractionalPixel>,
192 glyph_indices: HashMap<char, Option<GlyphId>>,
193 shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
194}
195
196impl malloc_size_of::MallocSizeOf for CachedShapeData {
197 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
198 let shaped_text_size = self
201 .shaped_text
202 .iter()
203 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
204 .sum::<usize>();
205 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
206 }
207}
208
209pub struct Font {
210 pub(crate) handle: PlatformFont,
211 pub(crate) template: FontTemplateRef,
212 pub metrics: Arc<FontMetrics>,
213 pub descriptor: FontDescriptor,
214
215 data_and_index: OnceLock<FontDataAndIndex>,
218
219 shaper: OnceLock<Shaper>,
220 cached_shape_data: RwLock<CachedShapeData>,
221 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
222
223 pub(crate) synthesized_small_caps: Option<FontRef>,
227
228 has_color_bitmap_or_colr_table: OnceLock<bool>,
232
233 can_do_fast_shaping: OnceLock<bool>,
240}
241
242impl std::fmt::Debug for Font {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 f.debug_struct("Font")
245 .field("template", &self.template)
246 .field("descriptor", &self.descriptor)
247 .finish()
248 }
249}
250
251impl malloc_size_of::MallocSizeOf for Font {
252 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
253 self.metrics.size_of(ops) +
257 self.descriptor.size_of(ops) +
258 self.cached_shape_data.read().size_of(ops) +
259 self.font_instance_key
260 .read()
261 .values()
262 .map(|key| key.size_of(ops))
263 .sum::<usize>()
264 }
265}
266
267impl Font {
268 pub fn new(
269 template: FontTemplateRef,
270 descriptor: FontDescriptor,
271 data: Option<FontData>,
272 synthesized_small_caps: Option<FontRef>,
273 ) -> Result<Font, &'static str> {
274 let synthetic_bold = {
275 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
276 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
277
278 is_bold && allows_synthetic_bold
279 };
280
281 let handle = PlatformFont::new_from_template(
282 template.clone(),
283 Some(descriptor.pt_size),
284 &descriptor.variation_settings,
285 &data,
286 synthetic_bold,
287 )?;
288 let metrics = Arc::new(handle.metrics());
289
290 Ok(Font {
291 handle,
292 template,
293 metrics,
294 descriptor,
295 data_and_index: data
296 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
297 .unwrap_or_default(),
298 shaper: OnceLock::new(),
299 cached_shape_data: Default::default(),
300 font_instance_key: Default::default(),
301 synthesized_small_caps,
302 has_color_bitmap_or_colr_table: OnceLock::new(),
303 can_do_fast_shaping: OnceLock::new(),
304 })
305 }
306
307 pub fn identifier(&self) -> FontIdentifier {
309 self.template.identifier()
310 }
311
312 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
313 self.handle.webrender_font_instance_flags()
314 }
315
316 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
317 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
318 self.table_for_tag(SBIX).is_some() ||
319 self.table_for_tag(CBDT).is_some() ||
320 self.table_for_tag(COLR).is_some()
321 })
322 }
323
324 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
325 *self
326 .font_instance_key
327 .write()
328 .entry(painter_id)
329 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
330 }
331
332 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
335 if let Some(data_and_index) = self.data_and_index.get() {
336 return Ok(data_and_index);
337 }
338
339 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
340 unreachable!("All web fonts should already have initialized data");
341 };
342 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
343 return Err(FontDataError::FailedToLoad);
344 };
345
346 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
347 Ok(data_and_index)
348 }
349
350 pub(crate) fn variations(&self) -> &[FontVariation] {
351 self.handle.variations()
352 }
353}
354
355bitflags! {
356 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
357 pub struct ShapingFlags: u8 {
358 const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
360 const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
362 const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
364 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
366 const RTL_FLAG = 1 << 4;
368 const KEEP_ALL_FLAG = 1 << 5;
370 }
371}
372
373#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
375pub struct ShapingOptions {
376 pub letter_spacing: Option<Au>,
379 pub word_spacing: Au,
381 pub script: Script,
383 pub flags: ShapingFlags,
385}
386
387#[derive(Clone, Debug, Eq, Hash, PartialEq)]
389struct ShapeCacheEntry {
390 text: String,
391 options: ShapingOptions,
392}
393
394impl Font {
395 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
396 let lookup_key = ShapeCacheEntry {
397 text: text.to_owned(),
398 options: *options,
399 };
400 {
401 let cache = self.cached_shape_data.read();
402 if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
403 return shaped_text.clone();
404 }
405 }
406
407 let start_time = Instant::now();
408 let glyphs = if self.can_do_fast_shaping(text, options) {
409 debug!("shape_text: Using ASCII fast path.");
410 self.shape_text_fast(text, options)
411 } else {
412 debug!("shape_text: Using Harfbuzz.");
413 self.shaper
414 .get_or_init(|| Shaper::new(self))
415 .shape_text(self, text, options)
416 };
417
418 let shaped_text = Arc::new(glyphs);
419 let mut cache = self.cached_shape_data.write();
420 cache.shaped_text.insert(lookup_key, shaped_text.clone());
421
422 TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
423 ((Instant::now() - start_time).as_nanos()) as usize,
424 Ordering::Relaxed,
425 );
426
427 shaped_text
428 }
429
430 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
435 options.script == Script::Latin &&
436 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
437 *self.can_do_fast_shaping.get_or_init(|| {
438 self.table_for_tag(KERN).is_some() &&
439 self.table_for_tag(GPOS).is_none() &&
440 self.table_for_tag(GSUB).is_none()
441 }) &&
442 text.is_ascii()
443 }
444
445 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> GlyphStore {
447 let mut glyph_store = GlyphStore::new(text, text.len(), options);
448 let mut prev_glyph_id = None;
449 for (string_byte_offset, byte) in text.bytes().enumerate() {
450 let character = byte as char;
451 let Some(glyph_id) = self.glyph_index(character) else {
452 continue;
453 };
454
455 let mut advance = advance_for_shaped_glyph(
456 Au::from_f64_px(self.glyph_h_advance(glyph_id)),
457 character,
458 options,
459 );
460 let offset = prev_glyph_id.map(|prev| {
461 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
462 advance += h_kerning;
463 Point2D::new(h_kerning, Au::zero())
464 });
465
466 glyph_store.add_glyph(
467 character,
468 &ShapedGlyph {
469 glyph_id,
470 string_byte_offset,
471 advance,
472 offset,
473 },
474 );
475 prev_glyph_id = Some(glyph_id);
476 }
477 glyph_store
478 }
479
480 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
481 let result = self.handle.table_for_tag(tag);
482 let status = if result.is_some() {
483 "Found"
484 } else {
485 "Didn't find"
486 };
487
488 debug!(
489 "{} font table[{}] in {:?},",
490 status,
491 str::from_utf8(tag.as_ref()).unwrap(),
492 self.identifier()
493 );
494 result
495 }
496
497 #[inline]
498 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
499 {
500 let cache = self.cached_shape_data.read();
501 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
502 return *glyph;
503 }
504 }
505 let codepoint = match self.descriptor.variant {
506 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
507 font_variant_caps::T::Normal => codepoint,
508 };
509 let glyph_index = self.handle.glyph_index(codepoint);
510
511 let mut cache = self.cached_shape_data.write();
512 cache.glyph_indices.insert(codepoint, glyph_index);
513 glyph_index
514 }
515
516 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
517 self.glyph_index(codepoint).is_some()
518 }
519
520 pub(crate) fn glyph_h_kerning(
521 &self,
522 first_glyph: GlyphId,
523 second_glyph: GlyphId,
524 ) -> FractionalPixel {
525 self.handle.glyph_h_kerning(first_glyph, second_glyph)
526 }
527
528 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
529 {
530 let cache = self.cached_shape_data.read();
531 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
532 return *width;
533 }
534 }
535
536 let new_width = self
537 .handle
538 .glyph_h_advance(glyph_id)
539 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
540 let mut cache = self.cached_shape_data.write();
541 cache.glyph_advances.insert(glyph_id, new_width);
542 new_width
543 }
544
545 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
546 self.handle.typographic_bounds(glyph_id)
547 }
548
549 pub fn baseline(&self) -> Option<FontBaseline> {
551 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
552 }
553}
554
555#[derive(Clone, Debug, MallocSizeOf)]
556pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
557
558impl Deref for FontRef {
559 type Target = Arc<Font>;
560 fn deref(&self) -> &Self::Target {
561 &self.0
562 }
563}
564
565#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
566pub struct FallbackKey {
567 script: Script,
568 unicode_block: Option<UnicodeBlock>,
569 lang: XLang,
570}
571
572impl FallbackKey {
573 fn new(options: &FallbackFontSelectionOptions) -> Self {
574 Self {
575 script: Script::from(options.character),
576 unicode_block: options.character.block(),
577 lang: options.lang.clone(),
578 }
579 }
580}
581
582#[derive(MallocSizeOf)]
586pub struct FontGroup {
587 descriptor: FontDescriptor,
590 families: SmallVec<[FontGroupFamily; 8]>,
593 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
598}
599
600impl FontGroup {
601 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
602 let families: SmallVec<[FontGroupFamily; 8]> = style
603 .font_family
604 .families
605 .iter()
606 .map(FontGroupFamily::local_or_web)
607 .collect();
608
609 FontGroup {
610 descriptor,
611 families,
612 fallbacks: Default::default(),
613 }
614 }
615
616 pub fn find_by_codepoint(
621 &self,
622 font_context: &FontContext,
623 codepoint: char,
624 next_codepoint: Option<char>,
625 lang: XLang,
626 ) -> Option<FontRef> {
627 let codepoint = match codepoint {
631 '\t' => ' ',
632 _ => codepoint,
633 };
634
635 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
636
637 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
638 options.character.is_ascii_lowercase();
639 let font_or_synthesized_small_caps = |font: FontRef| {
640 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
641 return font.synthesized_small_caps.clone();
642 }
643 Some(font)
644 };
645
646 let font_has_glyph_and_presentation = |font: &FontRef| {
647 match options.presentation_preference {
649 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
650 return false;
651 },
652 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
653 return false;
654 },
655 _ => {},
656 }
657 font.has_glyph_for(options.character)
658 };
659
660 let char_in_template =
661 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
662
663 if let Some(font) = self.find(
664 font_context,
665 &char_in_template,
666 &font_has_glyph_and_presentation,
667 ) {
668 return font_or_synthesized_small_caps(font);
669 }
670
671 let fallback_key = FallbackKey::new(&options);
672 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) {
673 if char_in_template(fallback.template.clone()) &&
674 font_has_glyph_and_presentation(fallback)
675 {
676 return font_or_synthesized_small_caps(fallback.clone());
677 }
678 }
679
680 if let Some(font) = self.find_fallback(
681 font_context,
682 options.clone(),
683 &char_in_template,
684 &font_has_glyph_and_presentation,
685 ) {
686 let fallback = font_or_synthesized_small_caps(font);
687 if let Some(fallback) = fallback.clone() {
688 self.fallbacks.write().insert(fallback_key, fallback);
689 }
690 return fallback;
691 }
692
693 self.first(font_context)
694 }
695
696 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
698 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
706 let font_predicate = |_: &FontRef| true;
707 self.find(font_context, &space_in_template, &font_predicate)
708 .or_else(|| {
709 self.find_fallback(
710 font_context,
711 FallbackFontSelectionOptions::default(),
712 &space_in_template,
713 &font_predicate,
714 )
715 })
716 }
717
718 fn find(
722 &self,
723 font_context: &FontContext,
724 template_predicate: &impl Fn(FontTemplateRef) -> bool,
725 font_predicate: &impl Fn(&FontRef) -> bool,
726 ) -> Option<FontRef> {
727 self.families
728 .iter()
729 .flat_map(|family| family.templates(font_context, &self.descriptor))
730 .find_map(|template| {
731 template.font_if_matches(
732 font_context,
733 &self.descriptor,
734 template_predicate,
735 font_predicate,
736 )
737 })
738 }
739
740 fn find_fallback(
745 &self,
746 font_context: &FontContext,
747 options: FallbackFontSelectionOptions,
748 template_predicate: &impl Fn(FontTemplateRef) -> bool,
749 font_predicate: &impl Fn(&FontRef) -> bool,
750 ) -> Option<FontRef> {
751 iter::once(FontFamilyDescriptor::default())
752 .chain(
753 fallback_font_families(options)
754 .into_iter()
755 .map(|family_name| {
756 let family = SingleFontFamily::FamilyName(FamilyName {
757 name: family_name.into(),
758 syntax: FontFamilyNameSyntax::Quoted,
759 });
760 FontFamilyDescriptor::new(family, FontSearchScope::Local)
761 }),
762 )
763 .find_map(|family_descriptor| {
764 FontGroupFamily::from(family_descriptor)
765 .templates(font_context, &self.descriptor)
766 .find_map(|template| {
767 template.font_if_matches(
768 font_context,
769 &self.descriptor,
770 template_predicate,
771 font_predicate,
772 )
773 })
774 })
775 }
776}
777
778#[derive(MallocSizeOf)]
785struct FontGroupFamilyTemplate {
786 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
787 template: FontTemplateRef,
788 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
789 font: OnceLock<Option<FontRef>>,
790}
791
792impl From<FontTemplateRef> for FontGroupFamilyTemplate {
793 fn from(template: FontTemplateRef) -> Self {
794 Self {
795 template,
796 font: Default::default(),
797 }
798 }
799}
800
801impl FontGroupFamilyTemplate {
802 fn font(
803 &self,
804 font_context: &FontContext,
805 font_descriptor: &FontDescriptor,
806 ) -> Option<FontRef> {
807 self.font
808 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
809 .clone()
810 }
811
812 fn font_if_matches(
813 &self,
814 font_context: &FontContext,
815 font_descriptor: &FontDescriptor,
816 template_predicate: &impl Fn(FontTemplateRef) -> bool,
817 font_predicate: &impl Fn(&FontRef) -> bool,
818 ) -> Option<FontRef> {
819 if !template_predicate(self.template.clone()) {
820 return None;
821 }
822 self.font(font_context, font_descriptor)
823 .filter(font_predicate)
824 }
825}
826
827#[derive(MallocSizeOf)]
832struct FontGroupFamily {
833 family_descriptor: FontFamilyDescriptor,
834 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
835}
836
837impl From<FontFamilyDescriptor> for FontGroupFamily {
838 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
839 Self {
840 family_descriptor,
841 members: Default::default(),
842 }
843 }
844}
845
846impl FontGroupFamily {
847 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
848 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
849 }
850
851 fn templates(
852 &self,
853 font_context: &FontContext,
854 font_descriptor: &FontDescriptor,
855 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
856 self.members
857 .get_or_init(|| {
858 font_context
859 .matching_templates(font_descriptor, &self.family_descriptor)
860 .into_iter()
861 .map(Into::into)
862 .collect()
863 })
864 .iter()
865 }
866}
867
868#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
870pub enum FontSearchScope {
871 Any,
873
874 Local,
876}
877
878#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
880pub struct FontFamilyDescriptor {
881 pub(crate) family: SingleFontFamily,
882 pub(crate) scope: FontSearchScope,
883}
884
885impl FontFamilyDescriptor {
886 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
887 FontFamilyDescriptor { family, scope }
888 }
889
890 fn default() -> FontFamilyDescriptor {
891 FontFamilyDescriptor {
892 family: SingleFontFamily::Generic(GenericFontFamily::None),
893 scope: FontSearchScope::Local,
894 }
895 }
896}
897
898pub struct FontBaseline {
899 pub ideographic_baseline: f32,
900 pub alphabetic_baseline: f32,
901 pub hanging_baseline: f32,
902}
903
904#[cfg(all(
920 any(target_os = "linux", target_os = "macos"),
921 not(target_env = "ohos")
922))]
923pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
924 if value < mapping[0].0 {
925 return mapping[0].1;
926 }
927
928 for window in mapping.windows(2) {
929 let (font_config_value_a, css_value_a) = window[0];
930 let (font_config_value_b, css_value_b) = window[1];
931
932 if value >= font_config_value_a && value <= font_config_value_b {
933 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
934 return css_value_a + ((css_value_b - css_value_a) * ratio);
935 }
936 }
937
938 mapping[mapping.len() - 1].1
939}
940
941pub(super) fn advance_for_shaped_glyph(
943 mut advance: Au,
944 character: char,
945 options: &ShapingOptions,
946) -> Au {
947 if let Some(letter_spacing) = options.letter_spacing {
948 advance += letter_spacing;
949 };
950
951 if character == ' ' || character == '\u{a0}' {
956 advance += options.word_spacing;
958 }
959
960 advance
961}