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