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 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<PainterId, 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(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
330 *self
331 .font_instance_key
332 .write()
333 .entry(painter_id)
334 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
335 }
336
337 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
340 if let Some(data_and_index) = self.data_and_index.get() {
341 return Ok(data_and_index);
342 }
343
344 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
345 unreachable!("All web fonts should already have initialized data");
346 };
347 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
348 return Err(FontDataError::FailedToLoad);
349 };
350
351 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
352 Ok(data_and_index)
353 }
354
355 pub(crate) fn variations(&self) -> &[FontVariation] {
356 self.handle.variations()
357 }
358}
359
360bitflags! {
361 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
362 pub struct ShapingFlags: u8 {
363 const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
365 const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
367 const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
369 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
371 const RTL_FLAG = 1 << 4;
373 const KEEP_ALL_FLAG = 1 << 5;
375 }
376}
377
378#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
380pub struct ShapingOptions {
381 pub letter_spacing: Option<Au>,
384 pub word_spacing: Au,
386 pub script: Script,
388 pub flags: ShapingFlags,
390}
391
392#[derive(Clone, Debug, Eq, Hash, PartialEq)]
394struct ShapeCacheEntry {
395 text: String,
396 options: ShapingOptions,
397}
398
399impl Font {
400 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
401 let lookup_key = ShapeCacheEntry {
402 text: text.to_owned(),
403 options: *options,
404 };
405 {
406 let cache = self.cached_shape_data.read();
407 if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
408 return shaped_text.clone();
409 }
410 }
411
412 let is_single_preserved_newline = text.len() == 1 && text.starts_with('\n');
413 let start_time = Instant::now();
414 let mut glyphs = GlyphStore::new(
415 text.len(),
416 options
417 .flags
418 .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
419 options
420 .flags
421 .contains(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG),
422 is_single_preserved_newline,
423 options.flags.contains(ShapingFlags::RTL_FLAG),
424 );
425
426 if self.can_do_fast_shaping(text, options) {
427 debug!("shape_text: Using ASCII fast path.");
428 self.shape_text_fast(text, options, &mut glyphs);
429 } else {
430 debug!("shape_text: Using Harfbuzz.");
431 self.shape_text_harfbuzz(text, options, &mut glyphs);
432 }
433
434 let shaped_text = Arc::new(glyphs);
435 let mut cache = self.cached_shape_data.write();
436 cache.shaped_text.insert(lookup_key, shaped_text.clone());
437
438 let end_time = Instant::now();
439 TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
440 (end_time.duration_since(start_time).as_nanos()) as usize,
441 Ordering::Relaxed,
442 );
443
444 shaped_text
445 }
446
447 fn shape_text_harfbuzz(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
448 self.shaper
449 .get_or_init(|| Shaper::new(self))
450 .shape_text(text, options, glyphs);
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, glyphs: &mut GlyphStore) {
470 let mut prev_glyph_id = None;
471 for (i, byte) in text.bytes().enumerate() {
472 let character = byte as char;
473 let glyph_id = match self.glyph_index(character) {
474 Some(id) => id,
475 None => 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 let glyph = GlyphData::new(glyph_id, advance, offset, true, true);
490 glyphs.add_glyph_for_byte_index(ByteIndex(i as isize), character, &glyph);
491 prev_glyph_id = Some(glyph_id);
492 }
493 glyphs.finalize_changes();
494 }
495
496 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
497 let result = self.handle.table_for_tag(tag);
498 let status = if result.is_some() {
499 "Found"
500 } else {
501 "Didn't find"
502 };
503
504 debug!(
505 "{} font table[{}] in {:?},",
506 status,
507 str::from_utf8(tag.as_ref()).unwrap(),
508 self.identifier()
509 );
510 result
511 }
512
513 #[inline]
514 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
515 {
516 let cache = self.cached_shape_data.read();
517 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
518 return *glyph;
519 }
520 }
521 let codepoint = match self.descriptor.variant {
522 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
523 font_variant_caps::T::Normal => codepoint,
524 };
525 let glyph_index = self.handle.glyph_index(codepoint);
526
527 let mut cache = self.cached_shape_data.write();
528 cache.glyph_indices.insert(codepoint, glyph_index);
529 glyph_index
530 }
531
532 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
533 self.glyph_index(codepoint).is_some()
534 }
535
536 pub(crate) fn glyph_h_kerning(
537 &self,
538 first_glyph: GlyphId,
539 second_glyph: GlyphId,
540 ) -> FractionalPixel {
541 self.handle.glyph_h_kerning(first_glyph, second_glyph)
542 }
543
544 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
545 {
546 let cache = self.cached_shape_data.read();
547 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
548 return *width;
549 }
550 }
551
552 let new_width = match self.handle.glyph_h_advance(glyph_id) {
554 Some(adv) => adv,
555 None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel,
556 };
557
558 let mut cache = self.cached_shape_data.write();
559 cache.glyph_advances.insert(glyph_id, new_width);
560 new_width
561 }
562
563 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
564 self.handle.typographic_bounds(glyph_id)
565 }
566
567 pub fn baseline(&self) -> Option<FontBaseline> {
569 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
570 }
571}
572
573#[derive(Clone, MallocSizeOf)]
574pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
575
576impl Deref for FontRef {
577 type Target = Arc<Font>;
578 fn deref(&self) -> &Self::Target {
579 &self.0
580 }
581}
582
583#[derive(MallocSizeOf)]
587pub struct FontGroup {
588 descriptor: FontDescriptor,
589 families: SmallVec<[FontGroupFamily; 8]>,
590}
591
592impl FontGroup {
593 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
594 let families: SmallVec<[FontGroupFamily; 8]> = style
595 .font_family
596 .families
597 .iter()
598 .map(FontGroupFamily::new)
599 .collect();
600
601 FontGroup {
602 descriptor,
603 families,
604 }
605 }
606
607 pub fn find_by_codepoint(
612 &mut self,
613 font_context: &FontContext,
614 codepoint: char,
615 next_codepoint: Option<char>,
616 first_fallback: Option<FontRef>,
617 lang: Option<String>,
618 ) -> Option<FontRef> {
619 let codepoint = match codepoint {
623 '\t' => ' ',
624 _ => codepoint,
625 };
626
627 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
628
629 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
630 options.character.is_ascii_lowercase();
631 let font_or_synthesized_small_caps = |font: FontRef| {
632 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
633 return font.synthesized_small_caps.clone();
634 }
635 Some(font)
636 };
637
638 let font_has_glyph_and_presentation = |font: &FontRef| {
639 match options.presentation_preference {
641 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
642 return false;
643 },
644 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
645 return false;
646 },
647 _ => {},
648 }
649 font.has_glyph_for(options.character)
650 };
651
652 let char_in_template =
653 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
654
655 if let Some(font) = self.find(
656 font_context,
657 char_in_template,
658 font_has_glyph_and_presentation,
659 ) {
660 return font_or_synthesized_small_caps(font);
661 }
662
663 if let Some(ref first_fallback) = first_fallback {
664 if char_in_template(first_fallback.template.clone()) &&
665 font_has_glyph_and_presentation(first_fallback)
666 {
667 return font_or_synthesized_small_caps(first_fallback.clone());
668 }
669 }
670
671 if let Some(font) = self.find_fallback(
672 font_context,
673 options.clone(),
674 char_in_template,
675 font_has_glyph_and_presentation,
676 ) {
677 return font_or_synthesized_small_caps(font);
678 }
679
680 self.first(font_context)
681 }
682
683 pub fn first(&mut self, font_context: &FontContext) -> Option<FontRef> {
685 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
693 let font_predicate = |_: &FontRef| true;
694 self.find(font_context, space_in_template, font_predicate)
695 .or_else(|| {
696 self.find_fallback(
697 font_context,
698 FallbackFontSelectionOptions::default(),
699 space_in_template,
700 font_predicate,
701 )
702 })
703 }
704
705 fn find<TemplatePredicate, FontPredicate>(
709 &mut self,
710 font_context: &FontContext,
711 template_predicate: TemplatePredicate,
712 font_predicate: FontPredicate,
713 ) -> Option<FontRef>
714 where
715 TemplatePredicate: Fn(FontTemplateRef) -> bool,
716 FontPredicate: Fn(&FontRef) -> bool,
717 {
718 let font_descriptor = self.descriptor.clone();
719 self.families
720 .iter_mut()
721 .filter_map(|font_group_family| {
722 font_group_family.find(
723 &font_descriptor,
724 font_context,
725 &template_predicate,
726 &font_predicate,
727 )
728 })
729 .next()
730 }
731
732 fn find_fallback<TemplatePredicate, FontPredicate>(
737 &mut self,
738 font_context: &FontContext,
739 options: FallbackFontSelectionOptions,
740 template_predicate: TemplatePredicate,
741 font_predicate: FontPredicate,
742 ) -> Option<FontRef>
743 where
744 TemplatePredicate: Fn(FontTemplateRef) -> bool,
745 FontPredicate: Fn(&FontRef) -> bool,
746 {
747 iter::once(FontFamilyDescriptor::default())
748 .chain(
749 fallback_font_families(options)
750 .into_iter()
751 .map(|family_name| {
752 let family = SingleFontFamily::FamilyName(FamilyName {
753 name: family_name.into(),
754 syntax: FontFamilyNameSyntax::Quoted,
755 });
756 FontFamilyDescriptor::new(family, FontSearchScope::Local)
757 }),
758 )
759 .filter_map(|family_descriptor| {
760 FontGroupFamily {
761 family_descriptor,
762 members: None,
763 }
764 .find(
765 &self.descriptor,
766 font_context,
767 &template_predicate,
768 &font_predicate,
769 )
770 })
771 .next()
772 }
773}
774
775#[derive(MallocSizeOf)]
781struct FontGroupFamilyMember {
782 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
783 template: FontTemplateRef,
784 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
785 font: Option<FontRef>,
786 loaded: bool,
787}
788
789#[derive(MallocSizeOf)]
794struct FontGroupFamily {
795 family_descriptor: FontFamilyDescriptor,
796 members: Option<Vec<FontGroupFamilyMember>>,
797}
798
799impl FontGroupFamily {
800 fn new(family: &SingleFontFamily) -> FontGroupFamily {
801 FontGroupFamily {
802 family_descriptor: FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any),
803 members: None,
804 }
805 }
806
807 fn find<TemplatePredicate, FontPredicate>(
808 &mut self,
809 font_descriptor: &FontDescriptor,
810 font_context: &FontContext,
811 template_predicate: &TemplatePredicate,
812 font_predicate: &FontPredicate,
813 ) -> Option<FontRef>
814 where
815 TemplatePredicate: Fn(FontTemplateRef) -> bool,
816 FontPredicate: Fn(&FontRef) -> bool,
817 {
818 self.members(font_descriptor, font_context)
819 .filter_map(|member| {
820 if !template_predicate(member.template.clone()) {
821 return None;
822 }
823
824 if !member.loaded {
825 member.font = font_context.font(member.template.clone(), font_descriptor);
826 member.loaded = true;
827 }
828 if matches!(&member.font, Some(font) if font_predicate(font)) {
829 return member.font.clone();
830 }
831
832 None
833 })
834 .next()
835 }
836
837 fn members(
838 &mut self,
839 font_descriptor: &FontDescriptor,
840 font_context: &FontContext,
841 ) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
842 let family_descriptor = &self.family_descriptor;
843 let members = self.members.get_or_insert_with(|| {
844 font_context
845 .matching_templates(font_descriptor, family_descriptor)
846 .into_iter()
847 .map(|template| FontGroupFamilyMember {
848 template,
849 loaded: false,
850 font: None,
851 })
852 .collect()
853 });
854
855 members.iter_mut()
856 }
857}
858
859#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
861pub enum FontSearchScope {
862 Any,
864
865 Local,
867}
868
869#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
871pub struct FontFamilyDescriptor {
872 pub(crate) family: SingleFontFamily,
873 pub(crate) scope: FontSearchScope,
874}
875
876impl FontFamilyDescriptor {
877 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
878 FontFamilyDescriptor { family, scope }
879 }
880
881 fn default() -> FontFamilyDescriptor {
882 FontFamilyDescriptor {
883 family: SingleFontFamily::Generic(GenericFontFamily::None),
884 scope: FontSearchScope::Local,
885 }
886 }
887}
888
889pub struct FontBaseline {
890 pub ideographic_baseline: f32,
891 pub alphabetic_baseline: f32,
892 pub hanging_baseline: f32,
893}
894
895#[cfg(all(
911 any(target_os = "linux", target_os = "macos"),
912 not(target_env = "ohos")
913))]
914pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
915 if value < mapping[0].0 {
916 return mapping[0].1;
917 }
918
919 for window in mapping.windows(2) {
920 let (font_config_value_a, css_value_a) = window[0];
921 let (font_config_value_b, css_value_b) = window[1];
922
923 if value >= font_config_value_a && value <= font_config_value_b {
924 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
925 return css_value_a + ((css_value_b - css_value_a) * ratio);
926 }
927 }
928
929 mapping[mapping.len() - 1].1
930}
931
932pub(super) fn advance_for_shaped_glyph(
934 mut advance: Au,
935 character: char,
936 options: &ShapingOptions,
937) -> Au {
938 if let Some(letter_spacing) = options.letter_spacing {
939 advance += letter_spacing;
940 };
941
942 if character == ' ' || character == '\u{a0}' {
947 advance += options.word_spacing;
949 }
950
951 advance
952}