1use std::borrow::ToOwned;
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::ops::Deref;
9use std::sync::{Arc, OnceLock};
10use std::{iter, str};
11
12use app_units::Au;
13use atomic_refcell::AtomicRef;
14use bitflags::bitflags;
15use euclid::default::{Point2D, Rect};
16use euclid::num::Zero;
17use font_types::NameId;
18use fonts_traits::FontDescriptor;
19use icu_locale_core::subtags::Language;
20use icu_properties::props::{EnumeratedProperty, GeneralCategory};
21use log::debug;
22use malloc_size_of_derive::MallocSizeOf;
23use parking_lot::RwLock;
24use read_fonts::collections::int_set::Domain;
25use read_fonts::tables::fvar::Fvar;
26use read_fonts::tables::name::Name as NameTable;
27use read_fonts::tables::os2::{Os2, SelectionFlags};
28use read_fonts::types::Tag;
29use read_fonts::{FontRead, ReadError};
30use rustc_hash::FxHashMap;
31use serde::{Deserialize, Serialize};
32use servo_base::id::PainterId;
33use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
34use skrifa::string::LocalizedString;
35use smallvec::SmallVec;
36use style::Atom;
37use style::computed_values::font_optical_sizing::T as FontOpticalSizing;
38use style::computed_values::font_variant_caps;
39use style::computed_values::font_variant_position::T as FontVariantPosition;
40use style::properties::style_structs::Font as FontStyleStruct;
41use style::values::computed::font::{
42 FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
43};
44use style::values::computed::{
45 FontFeatureSettings, FontStretch, FontStyle, FontSynthesis, FontVariantEastAsian,
46 FontVariantLigatures, FontVariantNumeric, FontWeight,
47};
48use unicode_script::Script;
49use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
50
51use crate::font_feature_values::ResolvedFontVariantAlternates;
52use crate::platform::font::{FontTable, PlatformFont};
53use crate::platform::font_list::fallback_font_families;
54use crate::{
55 EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
56 FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
57 FontTemplateRefMethods, GlyphId, LocalFontIdentifier, ShapedGlyph, ShapedText, Shaper,
58 compute_used_font_features,
59};
60
61pub(crate) const AFRC: Tag = Tag::new(b"afrc");
62pub(crate) const BASE: Tag = Tag::new(b"BASE");
63pub(crate) const CALT: Tag = Tag::new(b"calt");
64pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
65pub(crate) const CLIG: Tag = Tag::new(b"clig");
66pub(crate) const COLR: Tag = Tag::new(b"COLR");
67pub(crate) const CWSH: Tag = Tag::new(b"cwsh");
68pub(crate) const FRAC: Tag = Tag::new(b"frac");
69pub(crate) const DLIG: Tag = Tag::new(b"dlig");
70pub(crate) const FVAR: Tag = Tag::new(b"fvar");
71pub(crate) const FWID: Tag = Tag::new(b"fwid");
72pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
73pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
74pub(crate) const HIST: Tag = Tag::new(b"hist");
75pub(crate) const HLIG: Tag = Tag::new(b"hlig");
76pub(crate) const ITAL: Tag = Tag::new(b"ital");
77pub(crate) const JP04: Tag = Tag::new(b"jp04");
78pub(crate) const JP78: Tag = Tag::new(b"jp78");
79pub(crate) const JP83: Tag = Tag::new(b"jp83");
80pub(crate) const JP90: Tag = Tag::new(b"jp90");
81pub(crate) const KERN: Tag = Tag::new(b"kern");
82pub(crate) const LIGA: Tag = Tag::new(b"liga");
83pub(crate) const LNUM: Tag = Tag::new(b"lnum");
84pub(crate) const NALT: Tag = Tag::new(b"nalt");
85pub(crate) const NAME: Tag = Tag::new(b"name");
86pub(crate) const ONUM: Tag = Tag::new(b"onum");
87pub(crate) const ORNM: Tag = Tag::new(b"ornm");
88pub(crate) const ORDN: Tag = Tag::new(b"ordn");
89pub(crate) const PNUM: Tag = Tag::new(b"pnum");
90pub(crate) const PWID: Tag = Tag::new(b"pwid");
91pub(crate) const RUBY: Tag = Tag::new(b"ruby");
92pub(crate) const SALT: Tag = Tag::new(b"salt");
93pub(crate) const SBIX: Tag = Tag::new(b"sbix");
94pub(crate) const SLNT: Tag = Tag::new(b"slnt");
95pub(crate) const SMPL: Tag = Tag::new(b"smpl");
96pub(crate) const SUBS: Tag = Tag::new(b"subs");
97pub(crate) const SUPS: Tag = Tag::new(b"sups");
98pub(crate) const SWSH: Tag = Tag::new(b"swsh");
99pub(crate) const TNUM: Tag = Tag::new(b"tnum");
100pub(crate) const TRAD: Tag = Tag::new(b"trad");
101pub(crate) const ZERO: Tag = Tag::new(b"zero");
102
103pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
104
105pub trait PlatformFontMethods: Sized {
111 #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
112 fn new_from_template(
113 template: FontTemplateRef,
114 pt_size: Option<Au>,
115 data: &Option<FontData>,
116 synthetic_bold: bool,
117 ) -> Result<PlatformFont, &'static str> {
118 let template = template.borrow();
119 let font_identifier = template.identifier.clone();
120
121 match font_identifier {
122 FontIdentifier::Local(font_identifier) => {
123 Self::new_from_local_font_identifier(font_identifier, pt_size, synthetic_bold)
124 },
125 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
126 font_identifier,
127 data.as_ref()
128 .expect("Should never create a web font without data."),
129 pt_size,
130 synthetic_bold,
131 ),
132 }
133 }
134
135 fn new_from_local_font_identifier(
136 font_identifier: LocalFontIdentifier,
137 pt_size: Option<Au>,
138 synthetic_bold: bool,
139 ) -> Result<PlatformFont, &'static str>;
140
141 fn new_from_data(
142 font_identifier: FontIdentifier,
143 data: &FontData,
144 pt_size: Option<Au>,
145 synthetic_bold: bool,
146 ) -> Result<PlatformFont, &'static str>;
147
148 fn copy_with_variations(
154 self,
155 _font_identifer: &FontIdentifier,
156 _variations: &[FontVariation],
157 ) -> Result<Self, &'static str>;
158
159 fn descriptor(&self) -> FontTemplateDescriptor;
162
163 fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
164 fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
165 fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
166
167 fn metrics(&self) -> FontMetrics;
168 fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
169 fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
170
171 fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
173
174 fn variations(&self) -> &[FontVariation];
176
177 fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
178 let mut style = FontStyle::NORMAL;
179 if os2.fs_selection().contains(SelectionFlags::ITALIC) {
180 style = FontStyle::ITALIC;
181 }
182
183 let weight = FontWeight::from_float(os2.us_weight_class() as f32);
184 let stretch = match os2.us_width_class() {
185 1 => FontStretch::ULTRA_CONDENSED,
186 2 => FontStretch::EXTRA_CONDENSED,
187 3 => FontStretch::CONDENSED,
188 4 => FontStretch::SEMI_CONDENSED,
189 5 => FontStretch::NORMAL,
190 6 => FontStretch::SEMI_EXPANDED,
191 7 => FontStretch::EXPANDED,
192 8 => FontStretch::EXTRA_EXPANDED,
193 9 => FontStretch::ULTRA_EXPANDED,
194 _ => FontStretch::NORMAL,
195 };
196
197 FontTemplateDescriptor::new(weight, stretch, style)
198 }
199}
200
201pub(crate) type FractionalPixel = f64;
203
204pub(crate) trait FontTableMethods {
205 fn buffer(&self) -> &[u8];
206 fn parse_as_specific_table<'a, Table>(&'a self) -> Result<Table, ReadError>
207 where
208 Table: FontRead<'a, Args = ()>,
209 {
210 Table::read(read_fonts::FontData::new(self.buffer()))
211 }
212}
213
214#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
215pub struct FontMetrics {
216 pub underline_size: Au,
217 pub underline_offset: Au,
218 pub strikeout_size: Au,
219 pub strikeout_offset: Au,
220 pub leading: Au,
221 pub x_height: Au,
222 pub em_size: Au,
223 pub ascent: Au,
224 pub descent: Au,
225 pub max_advance: Au,
226 pub average_advance: Au,
227 pub line_gap: Au,
228 pub zero_horizontal_advance: Option<Au>,
229 pub ic_horizontal_advance: Option<Au>,
230 pub space_advance: Au,
233}
234
235impl FontMetrics {
236 pub fn empty() -> Arc<Self> {
239 static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
240 EMPTY.get_or_init(Default::default).clone()
241 }
242
243 pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
246 self.ascent != other.ascent ||
247 self.descent != other.descent ||
248 self.line_gap != other.line_gap
249 }
250}
251
252#[derive(Debug, Default)]
253struct CachedShapeData {
254 glyph_advances: FxHashMap<GlyphId, FractionalPixel>,
255 glyph_indices: FxHashMap<char, Option<GlyphId>>,
256 shaped_text: HashMap<ShapeCacheEntry, Arc<ShapedText>>,
257}
258
259impl malloc_size_of::MallocSizeOf for CachedShapeData {
260 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
261 let shaped_text_size = self
264 .shaped_text
265 .iter()
266 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
267 .sum::<usize>();
268 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
269 }
270}
271
272pub struct Font {
273 pub(crate) handle: PlatformFont,
274 pub(crate) template: FontTemplateRef,
275 pub metrics: Arc<FontMetrics>,
276 pub descriptor: FontDescriptor,
277
278 data_and_index: OnceLock<FontDataAndIndex>,
281
282 shaper: OnceLock<Shaper>,
283 cached_shape_data: RwLock<CachedShapeData>,
284 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
285
286 pub(crate) synthesized_small_caps: Option<FontRef>,
290
291 has_color_bitmap_or_colr_table: OnceLock<bool>,
295
296 can_do_fast_shaping: OnceLock<bool>,
303
304 family_name: OnceLock<Result<Atom, NoUsableFamilyName>>,
309}
310
311#[derive(Clone)]
315struct NoUsableFamilyName;
316
317impl std::fmt::Debug for Font {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 f.debug_struct("Font")
320 .field("template", &self.template)
321 .field("descriptor", &self.descriptor)
322 .finish()
323 }
324}
325
326impl malloc_size_of::MallocSizeOf for Font {
327 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
328 self.metrics.size_of(ops) +
332 self.descriptor.size_of(ops) +
333 self.cached_shape_data.read().size_of(ops) +
334 self.font_instance_key
335 .read()
336 .values()
337 .map(|key| key.size_of(ops))
338 .sum::<usize>()
339 }
340}
341
342impl Font {
343 pub fn new(
344 template: FontTemplateRef,
345 descriptor: FontDescriptor,
346 data: Option<FontData>,
347 synthesized_small_caps: Option<FontRef>,
348 ) -> Result<Font, &'static str> {
349 let synthetic_bold = {
350 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
351 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
352
353 is_bold && allows_synthetic_bold
354 };
355
356 let handle = PlatformFont::new_from_template(
357 template.clone(),
358 Some(descriptor.pt_size),
359 &data,
360 synthetic_bold,
361 )?;
362 let variation_axes = VariationAxes::from_platform_font(&handle);
363
364 let handle = if servo_config::pref!(layout_variable_fonts_enabled) &&
366 variation_axes.contains(VariationAxes::IS_VARIABLE)
367 {
368 let used_variations = compute_variations(&descriptor, &template, variation_axes);
369 handle.copy_with_variations(&template.identifier(), &used_variations)?
370 } else {
371 handle
372 };
373
374 let metrics = Arc::new(handle.metrics());
375
376 Ok(Font {
377 handle,
378 template,
379 metrics,
380 descriptor,
381 data_and_index: data
382 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
383 .unwrap_or_default(),
384 shaper: OnceLock::new(),
385 cached_shape_data: Default::default(),
386 font_instance_key: Default::default(),
387 synthesized_small_caps,
388 has_color_bitmap_or_colr_table: OnceLock::new(),
389 can_do_fast_shaping: OnceLock::new(),
390 family_name: Default::default(),
391 })
392 }
393
394 pub fn identifier(&self) -> AtomicRef<'_, FontIdentifier> {
396 self.template.identifier()
397 }
398
399 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
400 self.handle.webrender_font_instance_flags()
401 }
402
403 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
404 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
405 self.table_for_tag(SBIX).is_some() ||
406 self.table_for_tag(CBDT).is_some() ||
407 self.table_for_tag(COLR).is_some()
408 })
409 }
410
411 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
412 *self
413 .font_instance_key
414 .write()
415 .entry(painter_id)
416 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
417 }
418
419 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
422 if let Some(data_and_index) = self.data_and_index.get() {
423 return Ok(data_and_index);
424 }
425
426 let FontIdentifier::Local(local_font_identifier) = &*self.identifier() else {
427 unreachable!("All web fonts should already have initialized data");
428 };
429 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
430 return Err(FontDataError::FailedToLoad);
431 };
432
433 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
434 Ok(data_and_index)
435 }
436
437 pub(crate) fn variations(&self) -> &[FontVariation] {
438 self.handle.variations()
439 }
440}
441
442bitflags! {
443 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
444 pub struct ShapingFlags: u8 {
445 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
447 const RTL_FLAG = 1 << 4;
449 const KEEP_ALL_FLAG = 1 << 5;
451 }
452}
453
454#[derive(Clone, Debug, Eq, Hash, PartialEq)]
456pub struct ShapingOptions {
457 pub letter_spacing: Option<Au>,
462 pub word_spacing: Option<Au>,
464 pub script: Script,
466 pub language: Language,
468 pub ligatures: FontVariantLigatures,
470 pub numeric: FontVariantNumeric,
472 pub east_asian: FontVariantEastAsian,
474 pub feature_settings: FontFeatureSettings,
476 pub position: FontVariantPosition,
478 pub alternates: ResolvedFontVariantAlternates,
480 pub flags: ShapingFlags,
482}
483
484impl ShapingOptions {
485 pub(crate) fn letter_spacing_for_character(&self, character: char) -> Option<Au> {
486 self.letter_spacing
490 .filter(|_| GeneralCategory::for_char(character) != GeneralCategory::Format)
491 }
492}
493
494#[derive(Clone, Debug, Eq, Hash, PartialEq)]
496struct ShapeCacheEntry {
497 text: String,
498 letter_spacing: Option<Au>,
499 word_spacing: Option<Au>,
500 script: Script,
501 language: Language,
502 font_features: Box<[(Tag, u32)]>,
503 flags: ShapingFlags,
504}
505
506impl Font {
507 #[servo_tracing::instrument(name = "Font::shape_text", skip_all)]
508 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<ShapedText> {
509 let font_features =
510 compute_used_font_features(options, self.template.borrow().font_face_rule.as_deref())
511 .collect();
512 let lookup_key = ShapeCacheEntry {
513 text: text.to_owned(),
514 letter_spacing: options.letter_spacing,
515 word_spacing: options.word_spacing,
516 script: options.script,
517 language: options.language,
518 flags: options.flags,
519 font_features,
520 };
521
522 if let Some(shaped_text) = self.cached_shape_data.read().shaped_text.get(&lookup_key) {
523 return shaped_text.clone();
524 }
525
526 let glyphs = if self.can_do_fast_shaping(text, options) {
527 debug!("shape_text: Using ASCII fast path.");
528 self.shape_text_fast(text, options)
529 } else {
530 debug!("shape_text: Using Harfbuzz.");
531 self.shaper.get_or_init(|| Shaper::new(self)).shape_text(
532 text,
533 options,
534 &lookup_key.font_features,
535 )
536 };
537
538 let shaped_text = Arc::new(glyphs);
539 let mut cache = self.cached_shape_data.write();
540 cache.shaped_text.insert(lookup_key, shaped_text.clone());
541
542 shaped_text
543 }
544
545 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
550 options.script == Script::Latin &&
551 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
552 *self.can_do_fast_shaping.get_or_init(|| {
553 self.table_for_tag(KERN).is_some() &&
554 self.table_for_tag(GPOS).is_none() &&
555 self.table_for_tag(GSUB).is_none()
556 }) &&
557 text.is_ascii()
558 }
559
560 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> ShapedText {
562 let mut glyph_store = ShapedText::new(text.len(), false );
563 let mut prev_glyph_id = None;
564 for (string_byte_offset, byte) in text.bytes().enumerate() {
565 let character = byte as char;
566 let Some(glyph_id) = self.glyph_index(character) else {
567 continue;
568 };
569
570 let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
571 let offset = prev_glyph_id.map(|prev| {
572 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
573 advance += h_kerning;
574 Point2D::new(h_kerning, Au::zero())
575 });
576
577 let mut glyph = ShapedGlyph {
578 glyph_id,
579 string_byte_offset,
580 advance,
581 offset,
582 };
583 glyph.adjust_for_character(character, options);
584
585 glyph_store.add_glyph(character, &glyph);
586 prev_glyph_id = Some(glyph_id);
587 }
588 glyph_store
589 }
590
591 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
592 let result = self.handle.table_for_tag(tag);
593 let status = if result.is_some() {
594 "Found"
595 } else {
596 "Didn't find"
597 };
598
599 debug!(
600 "{} font table[{}] in {:?},",
601 status,
602 str::from_utf8(tag.as_ref()).unwrap(),
603 self.identifier()
604 );
605 result
606 }
607
608 #[inline]
609 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
610 {
611 let cache = self.cached_shape_data.read();
612 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
613 return *glyph;
614 }
615 }
616 let codepoint = match self.descriptor.variant {
617 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
618 font_variant_caps::T::Normal => codepoint,
619 };
620 let glyph_index = self.handle.glyph_index(codepoint);
621
622 let mut cache = self.cached_shape_data.write();
623 cache.glyph_indices.insert(codepoint, glyph_index);
624 glyph_index
625 }
626
627 pub fn has_glyph_for(&self, codepoint: char) -> bool {
628 self.glyph_index(codepoint).is_some()
629 }
630
631 pub(crate) fn glyph_h_kerning(
632 &self,
633 first_glyph: GlyphId,
634 second_glyph: GlyphId,
635 ) -> FractionalPixel {
636 self.handle.glyph_h_kerning(first_glyph, second_glyph)
637 }
638
639 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
640 {
641 let cache = self.cached_shape_data.read();
642 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
643 return *width;
644 }
645 }
646
647 let new_width = self
648 .handle
649 .glyph_h_advance(glyph_id)
650 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
651 let mut cache = self.cached_shape_data.write();
652 cache.glyph_advances.insert(glyph_id, new_width);
653 new_width
654 }
655
656 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
657 self.handle.typographic_bounds(glyph_id)
658 }
659
660 pub fn baseline(&self) -> Option<FontBaseline> {
662 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
663 }
664
665 #[cfg(not(target_os = "macos"))]
666 pub(crate) fn find_fallback_using_system_font_api(
667 &self,
668 _: &FallbackFontSelectionOptions,
669 ) -> Option<FontRef> {
670 None
671 }
672
673 fn get_family_name_from_font_data(&self) -> Result<Atom, NoUsableFamilyName> {
674 let name_table = self.table_for_tag(NAME).ok_or(NoUsableFamilyName)?;
675 let name_table = NameTable::read(read_fonts::FontData::new(name_table.buffer()))
676 .map_err(|_| NoUsableFamilyName)?;
677
678 let mut best_rank = -1;
684 let mut best_string = None;
685 for (index, name_record) in name_table
686 .name_record()
687 .iter()
688 .filter(|name_record| name_record.name_id() == NameId::FAMILY_NAME)
689 .enumerate()
690 {
691 let localized_string = LocalizedString::new(&name_table, name_record);
692 let rank = match (index, localized_string.language()) {
693 (_, Some("en-US")) => {
694 best_string = Some(localized_string);
695 break;
696 },
697 (_, Some("en")) => 2,
698 (_, None) => 1,
699 (0, _) => 0,
700 _ => continue,
701 };
702 if rank > best_rank {
703 best_rank = rank;
704 best_string = Some(localized_string);
705 }
706 }
707
708 best_string
709 .map(|best_string| best_string.chars().collect::<String>().into())
710 .ok_or(NoUsableFamilyName)
711 }
712
713 pub fn family_name(&self) -> Option<Atom> {
718 self.template
719 .font_face_rule()
720 .and_then(|font_face_rule| {
721 AtomicRef::filter_map(font_face_rule, |rule| rule.descriptors.font_family.as_ref())
722 })
723 .map(|font_family| font_family.name.clone())
724 .or_else(|| {
725 self.family_name
726 .get_or_init(|| self.get_family_name_from_font_data())
727 .clone()
728 .ok()
729 })
730 }
731}
732
733#[derive(Clone, Debug, MallocSizeOf)]
734pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
735
736impl PartialEq for FontRef {
737 fn eq(&self, other: &Self) -> bool {
738 Arc::ptr_eq(self, other)
739 }
740}
741
742impl Deref for FontRef {
743 type Target = Arc<Font>;
744 fn deref(&self) -> &Self::Target {
745 &self.0
746 }
747}
748
749#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
750pub struct FallbackKey {
751 script: Script,
752 unicode_block: Option<UnicodeBlock>,
753 language: Language,
754}
755
756impl FallbackKey {
757 fn new(options: &FallbackFontSelectionOptions) -> Self {
758 Self {
759 script: Script::from(options.character),
760 unicode_block: options.character.block(),
761 language: options.language,
762 }
763 }
764}
765
766#[derive(MallocSizeOf)]
770pub struct FontGroup {
771 descriptor: FontDescriptor,
774 families: SmallVec<[FontGroupFamily; 8]>,
777 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
782}
783
784impl FontGroup {
785 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
786 let families: SmallVec<[FontGroupFamily; 8]> = style
787 .font_family
788 .families
789 .iter()
790 .map(FontGroupFamily::local_or_web)
791 .collect();
792
793 FontGroup {
794 descriptor,
795 families,
796 fallbacks: Default::default(),
797 }
798 }
799
800 pub fn find_by_codepoint(
805 &self,
806 font_context: &FontContext,
807 codepoint: char,
808 next_codepoint: Option<char>,
809 language: Language,
810 ) -> Option<FontRef> {
811 let codepoint = match codepoint {
815 '\t' => ' ',
816 _ => codepoint,
817 };
818
819 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, language);
820
821 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
822 options.character.is_ascii_lowercase();
823 let font_or_synthesized_small_caps = |font: FontRef| {
824 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
825 return font.synthesized_small_caps.clone();
826 }
827 Some(font)
828 };
829
830 let font_has_glyph_and_presentation = |font: &FontRef| {
831 match options.presentation_preference {
833 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
834 return false;
835 },
836 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
837 return false;
838 },
839 _ => {},
840 }
841 font.has_glyph_for(options.character)
842 };
843
844 let char_in_template =
845 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
846
847 if let Some(font) = self.find(
848 font_context,
849 &char_in_template,
850 &font_has_glyph_and_presentation,
851 ) {
852 return font_or_synthesized_small_caps(font);
853 }
854
855 let fallback_key = FallbackKey::new(&options);
856 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) &&
857 char_in_template(fallback.template.clone()) &&
858 font_has_glyph_and_presentation(fallback)
859 {
860 return font_or_synthesized_small_caps(fallback.clone());
861 }
862
863 if let Some(font) = self.find_fallback_using_system_font_list(
864 font_context,
865 options.clone(),
866 &char_in_template,
867 &font_has_glyph_and_presentation,
868 ) {
869 let fallback = font_or_synthesized_small_caps(font);
870 if let Some(fallback) = fallback.clone() {
871 self.fallbacks.write().insert(fallback_key, fallback);
872 }
873 return fallback;
874 }
875
876 let first_font = self.first(font_context);
877 if let Some(fallback) = first_font
878 .as_ref()
879 .and_then(|font| font.find_fallback_using_system_font_api(&options)) &&
880 font_has_glyph_and_presentation(&fallback)
881 {
882 return Some(fallback);
883 }
884
885 first_font
886 }
887
888 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
890 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
898 let font_predicate = |_: &FontRef| true;
899 self.find(font_context, &space_in_template, &font_predicate)
900 .or_else(|| {
901 self.find_fallback_using_system_font_list(
902 font_context,
903 FallbackFontSelectionOptions::default(),
904 &space_in_template,
905 &font_predicate,
906 )
907 })
908 }
909
910 fn find(
914 &self,
915 font_context: &FontContext,
916 template_predicate: &impl Fn(FontTemplateRef) -> bool,
917 font_predicate: &impl Fn(&FontRef) -> bool,
918 ) -> Option<FontRef> {
919 self.families
920 .iter()
921 .flat_map(|family| family.templates(font_context, &self.descriptor))
922 .find_map(|template| {
923 template.font_if_matches(
924 font_context,
925 &self.descriptor,
926 template_predicate,
927 font_predicate,
928 )
929 })
930 }
931
932 fn find_fallback_using_system_font_list(
938 &self,
939 font_context: &FontContext,
940 options: FallbackFontSelectionOptions,
941 template_predicate: &impl Fn(FontTemplateRef) -> bool,
942 font_predicate: &impl Fn(&FontRef) -> bool,
943 ) -> Option<FontRef> {
944 iter::once(FontFamilyDescriptor::default())
945 .chain(
946 fallback_font_families(options)
947 .into_iter()
948 .map(|family_name| {
949 let family = SingleFontFamily::FamilyName(FamilyName {
950 name: family_name.into(),
951 syntax: FontFamilyNameSyntax::Quoted,
952 });
953 FontFamilyDescriptor::new(family, FontSearchScope::Local)
954 }),
955 )
956 .find_map(|family_descriptor| {
957 FontGroupFamily::from(family_descriptor)
958 .templates(font_context, &self.descriptor)
959 .find_map(|template| {
960 template.font_if_matches(
961 font_context,
962 &self.descriptor,
963 template_predicate,
964 font_predicate,
965 )
966 })
967 })
968 }
969}
970
971#[derive(MallocSizeOf)]
978struct FontGroupFamilyTemplate {
979 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
980 template: FontTemplateRef,
981 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
982 font: OnceLock<Option<FontRef>>,
983}
984
985impl From<FontTemplateRef> for FontGroupFamilyTemplate {
986 fn from(template: FontTemplateRef) -> Self {
987 Self {
988 template,
989 font: Default::default(),
990 }
991 }
992}
993
994impl FontGroupFamilyTemplate {
995 fn font(
996 &self,
997 font_context: &FontContext,
998 font_descriptor: &FontDescriptor,
999 ) -> Option<FontRef> {
1000 self.font
1001 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
1002 .clone()
1003 }
1004
1005 fn font_if_matches(
1006 &self,
1007 font_context: &FontContext,
1008 font_descriptor: &FontDescriptor,
1009 template_predicate: &impl Fn(FontTemplateRef) -> bool,
1010 font_predicate: &impl Fn(&FontRef) -> bool,
1011 ) -> Option<FontRef> {
1012 if !template_predicate(self.template.clone()) {
1013 return None;
1014 }
1015 self.font(font_context, font_descriptor)
1016 .filter(font_predicate)
1017 }
1018}
1019
1020#[derive(MallocSizeOf)]
1025struct FontGroupFamily {
1026 family_descriptor: FontFamilyDescriptor,
1027 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
1028}
1029
1030impl From<FontFamilyDescriptor> for FontGroupFamily {
1031 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
1032 Self {
1033 family_descriptor,
1034 members: Default::default(),
1035 }
1036 }
1037}
1038
1039impl FontGroupFamily {
1040 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
1041 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
1042 }
1043
1044 fn templates(
1045 &self,
1046 font_context: &FontContext,
1047 font_descriptor: &FontDescriptor,
1048 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
1049 self.members
1050 .get_or_init(|| {
1051 font_context
1052 .matching_templates(font_descriptor, &self.family_descriptor)
1053 .into_iter()
1054 .map(Into::into)
1055 .collect()
1056 })
1057 .iter()
1058 }
1059}
1060
1061#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1063pub enum FontSearchScope {
1064 Any,
1066
1067 Local,
1069}
1070
1071#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1073pub struct FontFamilyDescriptor {
1074 pub(crate) family: SingleFontFamily,
1075 pub(crate) scope: FontSearchScope,
1076}
1077
1078impl FontFamilyDescriptor {
1079 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
1080 FontFamilyDescriptor { family, scope }
1081 }
1082
1083 fn default() -> FontFamilyDescriptor {
1084 FontFamilyDescriptor {
1085 family: SingleFontFamily::Generic(GenericFontFamily::None),
1086 scope: FontSearchScope::Local,
1087 }
1088 }
1089}
1090
1091pub struct FontBaseline {
1092 pub ideographic_baseline: f32,
1093 pub alphabetic_baseline: f32,
1094 pub hanging_baseline: f32,
1095}
1096
1097#[cfg(all(
1113 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
1114 not(target_env = "ohos")
1115))]
1116pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
1117 if value < mapping[0].0 {
1118 return mapping[0].1;
1119 }
1120
1121 for window in mapping.windows(2) {
1122 let (font_config_value_a, css_value_a) = window[0];
1123 let (font_config_value_b, css_value_b) = window[1];
1124
1125 if value >= font_config_value_a && value <= font_config_value_b {
1126 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
1127 return css_value_a + ((css_value_b - css_value_a) * ratio);
1128 }
1129 }
1130
1131 mapping[mapping.len() - 1].1
1132}
1133
1134fn compute_variations(
1136 descriptor: &FontDescriptor,
1137 template: &FontTemplateRef,
1138 variation_axes: VariationAxes,
1139) -> Vec<FontVariation> {
1140 let font_face_rule = template.font_face_rule();
1141
1142 let mut variations: Vec<FontVariation> = vec![];
1144
1145 let mut add_variation = |variation: FontVariation| {
1146 if !variations
1147 .iter()
1148 .any(|existing_variation| existing_variation.tag == variation.tag)
1149 {
1150 variations.push(variation);
1151 }
1152 };
1153
1154 descriptor
1158 .variation_settings
1159 .iter()
1160 .copied()
1161 .for_each(&mut add_variation);
1162
1163 if let Some(font_face_rule) = &font_face_rule {
1169 if let Some(variation_settings) =
1172 font_face_rule.descriptors.font_variation_settings.as_ref()
1173 {
1174 variation_settings
1175 .0
1176 .iter()
1177 .map(|variation| FontVariation {
1178 tag: variation.tag.0,
1179 value: variation.value.get().expect(
1180 "The value is enforced to be resolvable at parse time \
1181 (see FontVariationSettings::parse_for_font_face_rule).",
1182 ),
1183 })
1184 .for_each(&mut add_variation);
1185 }
1186 }
1187
1188 add_variation(FontVariation {
1200 tag: Tag::new(b"wght").to_u32(),
1201 value: descriptor.weight.value(),
1202 });
1203
1204 add_variation(FontVariation {
1205 tag: Tag::new(b"wdth").to_u32(),
1206 value: descriptor.stretch.0.to_float(),
1207 });
1208
1209 if variation_axes.intersects(VariationAxes::ITAL | VariationAxes::SLNT) {
1210 let clamped_font_style = font_face_rule
1211 .as_ref()
1212 .and_then(|font_face_rule| font_face_rule.descriptors.font_style.as_ref())
1213 .map(|font_style_range| {
1214 let computed_font_style_range =
1215 font_style_range.compute().expect("never returns None");
1216
1217 if computed_font_style_range.0 == FontStyle::ITALIC {
1218 debug_assert_eq!(computed_font_style_range.1, FontStyle::ITALIC);
1219 return FontStyle::ITALIC;
1220 }
1221 debug_assert_ne!(computed_font_style_range.1, FontStyle::ITALIC);
1222
1223 let specified_angle = if descriptor.style == FontStyle::ITALIC {
1224 FontStyle::DEFAULT_OBLIQUE_DEGREES as f32
1225 } else {
1226 descriptor.style.oblique_degrees()
1227 };
1228
1229 let clamped_angle = specified_angle
1230 .min(computed_font_style_range.1.oblique_degrees())
1231 .max(computed_font_style_range.0.oblique_degrees());
1232 FontStyle::oblique(clamped_angle)
1233 })
1234 .unwrap_or(descriptor.style);
1235
1236 let use_ital_axis = (variation_axes.contains(VariationAxes::ITAL) &&
1242 clamped_font_style == FontStyle::ITALIC) ||
1243 !variation_axes.contains(VariationAxes::SLNT);
1244 if use_ital_axis {
1245 add_variation(FontVariation {
1246 tag: ITAL.to_u32(),
1247 value: (clamped_font_style != FontStyle::NORMAL) as u32 as f32,
1248 });
1249 } else {
1250 if clamped_font_style == FontStyle::ITALIC {
1253 add_variation(FontVariation {
1254 tag: SLNT.to_u32(),
1255 value: (-FontStyle::DEFAULT_OBLIQUE_DEGREES) as f32,
1256 });
1257 } else {
1258 add_variation(FontVariation {
1259 tag: SLNT.to_u32(),
1260 value: -clamped_font_style.oblique_degrees(),
1261 });
1262 }
1263 }
1264 }
1265
1266 if descriptor.optical_sizing == FontOpticalSizing::Auto {
1268 add_variation(FontVariation {
1269 tag: Tag::new(b"opsz").to_u32(),
1270 value: descriptor.pt_size.to_f32_px(),
1271 });
1272 }
1273
1274 variations
1275}
1276
1277bitflags! {
1278 #[derive(Clone, Copy, Debug)]
1279 struct VariationAxes: u8 {
1280 const IS_VARIABLE = 1;
1282
1283 const ITAL = 1 << 1;
1287
1288 const SLNT = 1 << 2;
1292 }
1293}
1294
1295impl VariationAxes {
1296 fn from_platform_font(platform_font: &PlatformFont) -> Self {
1297 let Some(fvar_table) = platform_font.table_for_tag(FVAR) else {
1298 return VariationAxes::empty();
1300 };
1301
1302 let Some(variation_axes) = fvar_table
1303 .parse_as_specific_table::<Fvar<'_>>()
1304 .ok()
1305 .and_then(|fvar: Fvar<'_>| fvar.axis_instance_arrays().ok())
1306 .map(|instance_arrays| instance_arrays.axes())
1307 else {
1308 return VariationAxes::empty();
1310 };
1311
1312 if variation_axes.is_empty() {
1313 return VariationAxes::empty();
1314 }
1315
1316 let mut result = VariationAxes::IS_VARIABLE;
1317 for axis in variation_axes {
1318 match axis.axis_tag() {
1319 ITAL => result |= VariationAxes::ITAL,
1320 SLNT => result |= VariationAxes::SLNT,
1321 _ => {},
1322 }
1323 }
1324
1325 result
1326 }
1327}