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, Size2D};
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};
36pub use 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 const GPOS: Tag = Tag::new(b"GPOS");
44pub const GSUB: Tag = Tag::new(b"GSUB");
45pub const KERN: Tag = Tag::new(b"kern");
46pub const SBIX: Tag = Tag::new(b"sbix");
47pub const CBDT: Tag = Tag::new(b"CBDT");
48pub const COLR: Tag = Tag::new(b"COLR");
49pub const BASE: Tag = Tag::new(b"BASE");
50pub 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 type FractionalPixel = f64;
144
145pub 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 handle: PlatformFont,
216 pub 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 font_instance_key: OnceLock<FontInstanceKey>,
227
228 pub 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 fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
298 self.handle.webrender_font_instance_flags()
299 }
300
301 pub 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 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 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 fn has_glyph_for(&self, codepoint: char) -> bool {
511 self.glyph_index(codepoint).is_some()
512 }
513
514 pub fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
515 self.handle.glyph_h_kerning(first_glyph, second_glyph)
516 }
517
518 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
519 {
520 let cache = self.cached_shape_data.read();
521 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
522 return *width;
523 }
524 }
525
526 let new_width = match self.handle.glyph_h_advance(glyph_id) {
528 Some(adv) => adv,
529 None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel,
530 };
531
532 let mut cache = self.cached_shape_data.write();
533 cache.glyph_advances.insert(glyph_id, new_width);
534 new_width
535 }
536
537 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
538 self.handle.typographic_bounds(glyph_id)
539 }
540
541 pub fn baseline(&self) -> Option<FontBaseline> {
543 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
544 }
545}
546
547#[derive(Clone, MallocSizeOf)]
548pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
549
550impl Deref for FontRef {
551 type Target = Arc<Font>;
552 fn deref(&self) -> &Self::Target {
553 &self.0
554 }
555}
556
557#[derive(MallocSizeOf)]
561pub struct FontGroup {
562 descriptor: FontDescriptor,
563 families: SmallVec<[FontGroupFamily; 8]>,
564}
565
566impl FontGroup {
567 pub fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
568 let families: SmallVec<[FontGroupFamily; 8]> = style
569 .font_family
570 .families
571 .iter()
572 .map(FontGroupFamily::new)
573 .collect();
574
575 FontGroup {
576 descriptor,
577 families,
578 }
579 }
580
581 pub fn find_by_codepoint(
586 &mut self,
587 font_context: &FontContext,
588 codepoint: char,
589 next_codepoint: Option<char>,
590 first_fallback: Option<FontRef>,
591 ) -> Option<FontRef> {
592 let codepoint = match codepoint {
596 '\t' => ' ',
597 _ => codepoint,
598 };
599
600 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint);
601
602 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
603 options.character.is_ascii_lowercase();
604 let font_or_synthesized_small_caps = |font: FontRef| {
605 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
606 return font.synthesized_small_caps.clone();
607 }
608 Some(font)
609 };
610
611 let font_has_glyph_and_presentation = |font: &FontRef| {
612 match options.presentation_preference {
614 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
615 return false;
616 },
617 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
618 return false;
619 },
620 _ => {},
621 }
622 font.has_glyph_for(options.character)
623 };
624
625 let char_in_template =
626 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
627
628 if let Some(font) = self.find(
629 font_context,
630 char_in_template,
631 font_has_glyph_and_presentation,
632 ) {
633 return font_or_synthesized_small_caps(font);
634 }
635
636 if let Some(ref first_fallback) = first_fallback {
637 if char_in_template(first_fallback.template.clone()) &&
638 font_has_glyph_and_presentation(first_fallback)
639 {
640 return font_or_synthesized_small_caps(first_fallback.clone());
641 }
642 }
643
644 if let Some(font) = self.find_fallback(
645 font_context,
646 options,
647 char_in_template,
648 font_has_glyph_and_presentation,
649 ) {
650 return font_or_synthesized_small_caps(font);
651 }
652
653 self.first(font_context)
654 }
655
656 pub fn first(&mut self, font_context: &FontContext) -> Option<FontRef> {
658 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
666 let font_predicate = |_: &FontRef| true;
667 self.find(font_context, space_in_template, font_predicate)
668 .or_else(|| {
669 self.find_fallback(
670 font_context,
671 FallbackFontSelectionOptions::default(),
672 space_in_template,
673 font_predicate,
674 )
675 })
676 }
677
678 fn find<TemplatePredicate, FontPredicate>(
682 &mut self,
683 font_context: &FontContext,
684 template_predicate: TemplatePredicate,
685 font_predicate: FontPredicate,
686 ) -> Option<FontRef>
687 where
688 TemplatePredicate: Fn(FontTemplateRef) -> bool,
689 FontPredicate: Fn(&FontRef) -> bool,
690 {
691 let font_descriptor = self.descriptor.clone();
692 self.families
693 .iter_mut()
694 .filter_map(|font_group_family| {
695 font_group_family.find(
696 &font_descriptor,
697 font_context,
698 &template_predicate,
699 &font_predicate,
700 )
701 })
702 .next()
703 }
704
705 fn find_fallback<TemplatePredicate, FontPredicate>(
710 &mut self,
711 font_context: &FontContext,
712 options: FallbackFontSelectionOptions,
713 template_predicate: TemplatePredicate,
714 font_predicate: FontPredicate,
715 ) -> Option<FontRef>
716 where
717 TemplatePredicate: Fn(FontTemplateRef) -> bool,
718 FontPredicate: Fn(&FontRef) -> bool,
719 {
720 iter::once(FontFamilyDescriptor::default())
721 .chain(
722 fallback_font_families(options)
723 .into_iter()
724 .map(|family_name| {
725 let family = SingleFontFamily::FamilyName(FamilyName {
726 name: family_name.into(),
727 syntax: FontFamilyNameSyntax::Quoted,
728 });
729 FontFamilyDescriptor::new(family, FontSearchScope::Local)
730 }),
731 )
732 .filter_map(|family_descriptor| {
733 FontGroupFamily {
734 family_descriptor,
735 members: None,
736 }
737 .find(
738 &self.descriptor,
739 font_context,
740 &template_predicate,
741 &font_predicate,
742 )
743 })
744 .next()
745 }
746}
747
748#[derive(MallocSizeOf)]
754struct FontGroupFamilyMember {
755 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
756 template: FontTemplateRef,
757 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
758 font: Option<FontRef>,
759 loaded: bool,
760}
761
762#[derive(MallocSizeOf)]
767struct FontGroupFamily {
768 family_descriptor: FontFamilyDescriptor,
769 members: Option<Vec<FontGroupFamilyMember>>,
770}
771
772impl FontGroupFamily {
773 fn new(family: &SingleFontFamily) -> FontGroupFamily {
774 FontGroupFamily {
775 family_descriptor: FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any),
776 members: None,
777 }
778 }
779
780 fn find<TemplatePredicate, FontPredicate>(
781 &mut self,
782 font_descriptor: &FontDescriptor,
783 font_context: &FontContext,
784 template_predicate: &TemplatePredicate,
785 font_predicate: &FontPredicate,
786 ) -> Option<FontRef>
787 where
788 TemplatePredicate: Fn(FontTemplateRef) -> bool,
789 FontPredicate: Fn(&FontRef) -> bool,
790 {
791 self.members(font_descriptor, font_context)
792 .filter_map(|member| {
793 if !template_predicate(member.template.clone()) {
794 return None;
795 }
796
797 if !member.loaded {
798 member.font = font_context.font(member.template.clone(), font_descriptor);
799 member.loaded = true;
800 }
801 if matches!(&member.font, Some(font) if font_predicate(font)) {
802 return member.font.clone();
803 }
804
805 None
806 })
807 .next()
808 }
809
810 fn members(
811 &mut self,
812 font_descriptor: &FontDescriptor,
813 font_context: &FontContext,
814 ) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
815 let family_descriptor = &self.family_descriptor;
816 let members = self.members.get_or_insert_with(|| {
817 font_context
818 .matching_templates(font_descriptor, family_descriptor)
819 .into_iter()
820 .map(|template| FontGroupFamilyMember {
821 template,
822 loaded: false,
823 font: None,
824 })
825 .collect()
826 });
827
828 members.iter_mut()
829 }
830}
831
832pub struct RunMetrics {
833 pub advance_width: Au,
835 pub ascent: Au, pub descent: Au, pub bounding_box: Rect<Au>,
840}
841
842impl RunMetrics {
843 pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
844 let bounds = Rect::new(
845 Point2D::new(Au::zero(), -ascent),
846 Size2D::new(advance, ascent + descent),
847 );
848
849 RunMetrics {
854 advance_width: advance,
855 bounding_box: bounds,
856 ascent,
857 descent,
858 }
859 }
860}
861
862pub fn get_and_reset_text_shaping_performance_counter() -> usize {
864 TEXT_SHAPING_PERFORMANCE_COUNTER.swap(0, Ordering::SeqCst)
865}
866
867#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
869pub enum FontSearchScope {
870 Any,
872
873 Local,
875}
876
877#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
879pub struct FontFamilyDescriptor {
880 pub family: SingleFontFamily,
881 pub scope: FontSearchScope,
882}
883
884impl FontFamilyDescriptor {
885 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
886 FontFamilyDescriptor { family, scope }
887 }
888
889 fn default() -> FontFamilyDescriptor {
890 FontFamilyDescriptor {
891 family: SingleFontFamily::Generic(GenericFontFamily::None),
892 scope: FontSearchScope::Local,
893 }
894 }
895}
896
897pub struct FontBaseline {
898 pub ideographic_baseline: f32,
899 pub alphabetic_baseline: f32,
900 pub hanging_baseline: f32,
901}
902
903#[cfg(all(
919 any(target_os = "linux", target_os = "macos"),
920 not(target_env = "ohos")
921))]
922pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
923 if value < mapping[0].0 {
924 return mapping[0].1;
925 }
926
927 for window in mapping.windows(2) {
928 let (font_config_value_a, css_value_a) = window[0];
929 let (font_config_value_b, css_value_b) = window[1];
930
931 if value >= font_config_value_a && value <= font_config_value_b {
932 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
933 return css_value_a + ((css_value_b - css_value_a) * ratio);
934 }
935 }
936
937 mapping[mapping.len() - 1].1
938}
939
940pub(super) fn advance_for_shaped_glyph(
942 mut advance: Au,
943 character: char,
944 options: &ShapingOptions,
945) -> Au {
946 if let Some(letter_spacing) = options.letter_spacing {
947 advance += letter_spacing;
948 };
949
950 if character == ' ' || character == '\u{a0}' {
955 advance += options.word_spacing;
957 }
958
959 advance
960}