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::RenderingGroupId;
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 ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
41 FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
42 FontTemplateRefMethods, GlyphData, GlyphId, GlyphStore, LocalFontIdentifier, 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() -> Self {
183 Self {
184 underline_size: Au::zero(),
185 underline_offset: Au::zero(),
186 strikeout_size: Au::zero(),
187 strikeout_offset: Au::zero(),
188 leading: Au::zero(),
189 x_height: Au::zero(),
190 em_size: Au::zero(),
191 ascent: Au::zero(),
192 descent: Au::zero(),
193 max_advance: Au::zero(),
194 average_advance: Au::zero(),
195 line_gap: Au::zero(),
196 zero_horizontal_advance: None,
197 ic_horizontal_advance: None,
198 space_advance: Au::zero(),
199 }
200 }
201}
202
203#[derive(Debug, Default)]
204struct CachedShapeData {
205 glyph_advances: HashMap<GlyphId, FractionalPixel>,
206 glyph_indices: HashMap<char, Option<GlyphId>>,
207 shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
208}
209
210impl malloc_size_of::MallocSizeOf for CachedShapeData {
211 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
212 let shaped_text_size = self
215 .shaped_text
216 .iter()
217 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
218 .sum::<usize>();
219 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
220 }
221}
222
223pub struct Font {
224 pub(crate) handle: PlatformFont,
225 pub(crate) template: FontTemplateRef,
226 pub metrics: FontMetrics,
227 pub descriptor: FontDescriptor,
228
229 data_and_index: OnceLock<FontDataAndIndex>,
232
233 shaper: OnceLock<Shaper>,
234 cached_shape_data: RwLock<CachedShapeData>,
235 font_instance_key: RwLock<FxHashMap<RenderingGroupId, FontInstanceKey>>,
236
237 pub(crate) synthesized_small_caps: Option<FontRef>,
241
242 has_color_bitmap_or_colr_table: OnceLock<bool>,
246
247 can_do_fast_shaping: OnceLock<bool>,
254}
255
256impl malloc_size_of::MallocSizeOf for Font {
257 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
258 self.metrics.size_of(ops) +
262 self.descriptor.size_of(ops) +
263 self.cached_shape_data.read().size_of(ops) +
264 self.font_instance_key
265 .read()
266 .values()
267 .map(|key| key.size_of(ops))
268 .sum::<usize>()
269 }
270}
271
272impl Font {
273 pub fn new(
274 template: FontTemplateRef,
275 descriptor: FontDescriptor,
276 data: Option<FontData>,
277 synthesized_small_caps: Option<FontRef>,
278 ) -> Result<Font, &'static str> {
279 let synthetic_bold = {
280 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
281 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
282
283 is_bold && allows_synthetic_bold
284 };
285
286 let handle = PlatformFont::new_from_template(
287 template.clone(),
288 Some(descriptor.pt_size),
289 &descriptor.variation_settings,
290 &data,
291 synthetic_bold,
292 )?;
293 let metrics = handle.metrics();
294
295 Ok(Font {
296 handle,
297 template,
298 metrics,
299 descriptor,
300 data_and_index: data
301 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
302 .unwrap_or_default(),
303 shaper: OnceLock::new(),
304 cached_shape_data: Default::default(),
305 font_instance_key: Default::default(),
306 synthesized_small_caps,
307 has_color_bitmap_or_colr_table: OnceLock::new(),
308 can_do_fast_shaping: OnceLock::new(),
309 })
310 }
311
312 pub fn identifier(&self) -> FontIdentifier {
314 self.template.identifier()
315 }
316
317 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
318 self.handle.webrender_font_instance_flags()
319 }
320
321 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
322 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
323 self.table_for_tag(SBIX).is_some() ||
324 self.table_for_tag(CBDT).is_some() ||
325 self.table_for_tag(COLR).is_some()
326 })
327 }
328
329 pub fn key(
330 &self,
331 rendering_group_id: RenderingGroupId,
332 font_context: &FontContext,
333 ) -> FontInstanceKey {
334 *self
335 .font_instance_key
336 .write()
337 .entry(rendering_group_id)
338 .or_insert_with(|| font_context.create_font_instance_key(self, rendering_group_id))
339 }
340
341 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
344 if let Some(data_and_index) = self.data_and_index.get() {
345 return Ok(data_and_index);
346 }
347
348 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
349 unreachable!("All web fonts should already have initialized data");
350 };
351 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
352 return Err(FontDataError::FailedToLoad);
353 };
354
355 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
356 Ok(data_and_index)
357 }
358
359 pub(crate) fn variations(&self) -> &[FontVariation] {
360 self.handle.variations()
361 }
362}
363
364bitflags! {
365 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
366 pub struct ShapingFlags: u8 {
367 const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
369 const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
371 const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
373 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
375 const RTL_FLAG = 1 << 4;
377 const KEEP_ALL_FLAG = 1 << 5;
379 }
380}
381
382#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
384pub struct ShapingOptions {
385 pub letter_spacing: Option<Au>,
388 pub word_spacing: Au,
390 pub script: Script,
392 pub flags: ShapingFlags,
394}
395
396#[derive(Clone, Debug, Eq, Hash, PartialEq)]
398struct ShapeCacheEntry {
399 text: String,
400 options: ShapingOptions,
401}
402
403impl Font {
404 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
405 let lookup_key = ShapeCacheEntry {
406 text: text.to_owned(),
407 options: *options,
408 };
409 {
410 let cache = self.cached_shape_data.read();
411 if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
412 return shaped_text.clone();
413 }
414 }
415
416 let is_single_preserved_newline = text.len() == 1 && text.starts_with('\n');
417 let start_time = Instant::now();
418 let mut glyphs = GlyphStore::new(
419 text.len(),
420 options
421 .flags
422 .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
423 options
424 .flags
425 .contains(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG),
426 is_single_preserved_newline,
427 options.flags.contains(ShapingFlags::RTL_FLAG),
428 );
429
430 if self.can_do_fast_shaping(text, options) {
431 debug!("shape_text: Using ASCII fast path.");
432 self.shape_text_fast(text, options, &mut glyphs);
433 } else {
434 debug!("shape_text: Using Harfbuzz.");
435 self.shape_text_harfbuzz(text, options, &mut glyphs);
436 }
437
438 let shaped_text = Arc::new(glyphs);
439 let mut cache = self.cached_shape_data.write();
440 cache.shaped_text.insert(lookup_key, shaped_text.clone());
441
442 let end_time = Instant::now();
443 TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
444 (end_time.duration_since(start_time).as_nanos()) as usize,
445 Ordering::Relaxed,
446 );
447
448 shaped_text
449 }
450
451 fn shape_text_harfbuzz(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
452 self.shaper
453 .get_or_init(|| Shaper::new(self))
454 .shape_text(text, options, glyphs);
455 }
456
457 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
462 options.script == Script::Latin &&
463 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
464 *self.can_do_fast_shaping.get_or_init(|| {
465 self.table_for_tag(KERN).is_some() &&
466 self.table_for_tag(GPOS).is_none() &&
467 self.table_for_tag(GSUB).is_none()
468 }) &&
469 text.is_ascii()
470 }
471
472 fn shape_text_fast(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
474 let mut prev_glyph_id = None;
475 for (i, byte) in text.bytes().enumerate() {
476 let character = byte as char;
477 let glyph_id = match self.glyph_index(character) {
478 Some(id) => id,
479 None => continue,
480 };
481
482 let mut advance = advance_for_shaped_glyph(
483 Au::from_f64_px(self.glyph_h_advance(glyph_id)),
484 character,
485 options,
486 );
487 let offset = prev_glyph_id.map(|prev| {
488 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
489 advance += h_kerning;
490 Point2D::new(h_kerning, Au::zero())
491 });
492
493 let glyph = GlyphData::new(glyph_id, advance, offset, true, true);
494 glyphs.add_glyph_for_byte_index(ByteIndex(i as isize), character, &glyph);
495 prev_glyph_id = Some(glyph_id);
496 }
497 glyphs.finalize_changes();
498 }
499
500 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
501 let result = self.handle.table_for_tag(tag);
502 let status = if result.is_some() {
503 "Found"
504 } else {
505 "Didn't find"
506 };
507
508 debug!(
509 "{} font table[{}] in {:?},",
510 status,
511 str::from_utf8(tag.as_ref()).unwrap(),
512 self.identifier()
513 );
514 result
515 }
516
517 #[inline]
518 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
519 {
520 let cache = self.cached_shape_data.read();
521 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
522 return *glyph;
523 }
524 }
525 let codepoint = match self.descriptor.variant {
526 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
527 font_variant_caps::T::Normal => codepoint,
528 };
529 let glyph_index = self.handle.glyph_index(codepoint);
530
531 let mut cache = self.cached_shape_data.write();
532 cache.glyph_indices.insert(codepoint, glyph_index);
533 glyph_index
534 }
535
536 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
537 self.glyph_index(codepoint).is_some()
538 }
539
540 pub(crate) fn glyph_h_kerning(
541 &self,
542 first_glyph: GlyphId,
543 second_glyph: GlyphId,
544 ) -> FractionalPixel {
545 self.handle.glyph_h_kerning(first_glyph, second_glyph)
546 }
547
548 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
549 {
550 let cache = self.cached_shape_data.read();
551 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
552 return *width;
553 }
554 }
555
556 let new_width = match self.handle.glyph_h_advance(glyph_id) {
558 Some(adv) => adv,
559 None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel,
560 };
561
562 let mut cache = self.cached_shape_data.write();
563 cache.glyph_advances.insert(glyph_id, new_width);
564 new_width
565 }
566
567 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
568 self.handle.typographic_bounds(glyph_id)
569 }
570
571 pub fn baseline(&self) -> Option<FontBaseline> {
573 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
574 }
575}
576
577#[derive(Clone, MallocSizeOf)]
578pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
579
580impl Deref for FontRef {
581 type Target = Arc<Font>;
582 fn deref(&self) -> &Self::Target {
583 &self.0
584 }
585}
586
587#[derive(MallocSizeOf)]
591pub struct FontGroup {
592 descriptor: FontDescriptor,
593 families: SmallVec<[FontGroupFamily; 8]>,
594}
595
596impl FontGroup {
597 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
598 let families: SmallVec<[FontGroupFamily; 8]> = style
599 .font_family
600 .families
601 .iter()
602 .map(FontGroupFamily::new)
603 .collect();
604
605 FontGroup {
606 descriptor,
607 families,
608 }
609 }
610
611 pub fn find_by_codepoint(
616 &mut self,
617 font_context: &FontContext,
618 codepoint: char,
619 next_codepoint: Option<char>,
620 first_fallback: Option<FontRef>,
621 lang: Option<String>,
622 ) -> Option<FontRef> {
623 let codepoint = match codepoint {
627 '\t' => ' ',
628 _ => codepoint,
629 };
630
631 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
632
633 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
634 options.character.is_ascii_lowercase();
635 let font_or_synthesized_small_caps = |font: FontRef| {
636 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
637 return font.synthesized_small_caps.clone();
638 }
639 Some(font)
640 };
641
642 let font_has_glyph_and_presentation = |font: &FontRef| {
643 match options.presentation_preference {
645 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
646 return false;
647 },
648 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
649 return false;
650 },
651 _ => {},
652 }
653 font.has_glyph_for(options.character)
654 };
655
656 let char_in_template =
657 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
658
659 if let Some(font) = self.find(
660 font_context,
661 char_in_template,
662 font_has_glyph_and_presentation,
663 ) {
664 return font_or_synthesized_small_caps(font);
665 }
666
667 if let Some(ref first_fallback) = first_fallback {
668 if char_in_template(first_fallback.template.clone()) &&
669 font_has_glyph_and_presentation(first_fallback)
670 {
671 return font_or_synthesized_small_caps(first_fallback.clone());
672 }
673 }
674
675 if let Some(font) = self.find_fallback(
676 font_context,
677 options.clone(),
678 char_in_template,
679 font_has_glyph_and_presentation,
680 ) {
681 return font_or_synthesized_small_caps(font);
682 }
683
684 self.first(font_context)
685 }
686
687 pub fn first(&mut self, font_context: &FontContext) -> Option<FontRef> {
689 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
697 let font_predicate = |_: &FontRef| true;
698 self.find(font_context, space_in_template, font_predicate)
699 .or_else(|| {
700 self.find_fallback(
701 font_context,
702 FallbackFontSelectionOptions::default(),
703 space_in_template,
704 font_predicate,
705 )
706 })
707 }
708
709 fn find<TemplatePredicate, FontPredicate>(
713 &mut self,
714 font_context: &FontContext,
715 template_predicate: TemplatePredicate,
716 font_predicate: FontPredicate,
717 ) -> Option<FontRef>
718 where
719 TemplatePredicate: Fn(FontTemplateRef) -> bool,
720 FontPredicate: Fn(&FontRef) -> bool,
721 {
722 let font_descriptor = self.descriptor.clone();
723 self.families
724 .iter_mut()
725 .filter_map(|font_group_family| {
726 font_group_family.find(
727 &font_descriptor,
728 font_context,
729 &template_predicate,
730 &font_predicate,
731 )
732 })
733 .next()
734 }
735
736 fn find_fallback<TemplatePredicate, FontPredicate>(
741 &mut self,
742 font_context: &FontContext,
743 options: FallbackFontSelectionOptions,
744 template_predicate: TemplatePredicate,
745 font_predicate: FontPredicate,
746 ) -> Option<FontRef>
747 where
748 TemplatePredicate: Fn(FontTemplateRef) -> bool,
749 FontPredicate: Fn(&FontRef) -> bool,
750 {
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 .filter_map(|family_descriptor| {
764 FontGroupFamily {
765 family_descriptor,
766 members: None,
767 }
768 .find(
769 &self.descriptor,
770 font_context,
771 &template_predicate,
772 &font_predicate,
773 )
774 })
775 .next()
776 }
777}
778
779#[derive(MallocSizeOf)]
785struct FontGroupFamilyMember {
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: Option<FontRef>,
790 loaded: bool,
791}
792
793#[derive(MallocSizeOf)]
798struct FontGroupFamily {
799 family_descriptor: FontFamilyDescriptor,
800 members: Option<Vec<FontGroupFamilyMember>>,
801}
802
803impl FontGroupFamily {
804 fn new(family: &SingleFontFamily) -> FontGroupFamily {
805 FontGroupFamily {
806 family_descriptor: FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any),
807 members: None,
808 }
809 }
810
811 fn find<TemplatePredicate, FontPredicate>(
812 &mut self,
813 font_descriptor: &FontDescriptor,
814 font_context: &FontContext,
815 template_predicate: &TemplatePredicate,
816 font_predicate: &FontPredicate,
817 ) -> Option<FontRef>
818 where
819 TemplatePredicate: Fn(FontTemplateRef) -> bool,
820 FontPredicate: Fn(&FontRef) -> bool,
821 {
822 self.members(font_descriptor, font_context)
823 .filter_map(|member| {
824 if !template_predicate(member.template.clone()) {
825 return None;
826 }
827
828 if !member.loaded {
829 member.font = font_context.font(member.template.clone(), font_descriptor);
830 member.loaded = true;
831 }
832 if matches!(&member.font, Some(font) if font_predicate(font)) {
833 return member.font.clone();
834 }
835
836 None
837 })
838 .next()
839 }
840
841 fn members(
842 &mut self,
843 font_descriptor: &FontDescriptor,
844 font_context: &FontContext,
845 ) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
846 let family_descriptor = &self.family_descriptor;
847 let members = self.members.get_or_insert_with(|| {
848 font_context
849 .matching_templates(font_descriptor, family_descriptor)
850 .into_iter()
851 .map(|template| FontGroupFamilyMember {
852 template,
853 loaded: false,
854 font: None,
855 })
856 .collect()
857 });
858
859 members.iter_mut()
860 }
861}
862
863#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
865pub enum FontSearchScope {
866 Any,
868
869 Local,
871}
872
873#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
875pub struct FontFamilyDescriptor {
876 pub(crate) family: SingleFontFamily,
877 pub(crate) scope: FontSearchScope,
878}
879
880impl FontFamilyDescriptor {
881 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
882 FontFamilyDescriptor { family, scope }
883 }
884
885 fn default() -> FontFamilyDescriptor {
886 FontFamilyDescriptor {
887 family: SingleFontFamily::Generic(GenericFontFamily::None),
888 scope: FontSearchScope::Local,
889 }
890 }
891}
892
893pub struct FontBaseline {
894 pub ideographic_baseline: f32,
895 pub alphabetic_baseline: f32,
896 pub hanging_baseline: f32,
897}
898
899#[cfg(all(
915 any(target_os = "linux", target_os = "macos"),
916 not(target_env = "ohos")
917))]
918pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
919 if value < mapping[0].0 {
920 return mapping[0].1;
921 }
922
923 for window in mapping.windows(2) {
924 let (font_config_value_a, css_value_a) = window[0];
925 let (font_config_value_b, css_value_b) = window[1];
926
927 if value >= font_config_value_a && value <= font_config_value_b {
928 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
929 return css_value_a + ((css_value_b - css_value_a) * ratio);
930 }
931 }
932
933 mapping[mapping.len() - 1].1
934}
935
936pub(super) fn advance_for_shaped_glyph(
938 mut advance: Au,
939 character: char,
940 options: &ShapingOptions,
941) -> Au {
942 if let Some(letter_spacing) = options.letter_spacing {
943 advance += letter_spacing;
944 };
945
946 if character == ' ' || character == '\u{a0}' {
951 advance += options.word_spacing;
953 }
954
955 advance
956}