use std::borrow::ToOwned;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::Instant;
use std::{iter, str};
use app_units::Au;
use bitflags::bitflags;
use euclid::default::{Point2D, Rect};
use euclid::num::Zero;
use fonts_traits::FontDescriptor;
use icu_locid::subtags::Language;
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::RwLock;
use read_fonts::tables::os2::{Os2, SelectionFlags};
use read_fonts::types::Tag;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use servo_base::id::PainterId;
use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
use smallvec::SmallVec;
use style::computed_values::font_variant_caps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
};
use style::values::computed::{FontStretch, FontStyle, FontSynthesis, FontWeight, XLang};
use unicode_script::Script;
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
use crate::platform::font::{FontTable, PlatformFont};
use crate::platform::font_list::fallback_font_families;
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
FontTemplateRefMethods, GlyphId, GlyphStore, LocalFontIdentifier, ShapedGlyph, Shaper,
};
pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
pub(crate) const KERN: Tag = Tag::new(b"kern");
pub(crate) const SBIX: Tag = Tag::new(b"sbix");
pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
pub(crate) const COLR: Tag = Tag::new(b"COLR");
pub(crate) const BASE: Tag = Tag::new(b"BASE");
pub(crate) const LIGA: Tag = Tag::new(b"liga");
pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub trait PlatformFontMethods: Sized {
#[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
fn new_from_template(
template: FontTemplateRef,
pt_size: Option<Au>,
variations: &[FontVariation],
data: &Option<FontData>,
synthetic_bold: bool,
) -> Result<PlatformFont, &'static str> {
let template = template.borrow();
let font_identifier = template.identifier.clone();
match font_identifier {
FontIdentifier::Local(font_identifier) => Self::new_from_local_font_identifier(
font_identifier,
pt_size,
variations,
synthetic_bold,
),
FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
font_identifier,
data.as_ref()
.expect("Should never create a web font without data."),
pt_size,
variations,
synthetic_bold,
),
}
}
fn new_from_local_font_identifier(
font_identifier: LocalFontIdentifier,
pt_size: Option<Au>,
variations: &[FontVariation],
synthetic_bold: bool,
) -> Result<PlatformFont, &'static str>;
fn new_from_data(
font_identifier: FontIdentifier,
data: &FontData,
pt_size: Option<Au>,
variations: &[FontVariation],
synthetic_bold: bool,
) -> Result<PlatformFont, &'static str>;
fn descriptor(&self) -> FontTemplateDescriptor;
fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
fn metrics(&self) -> FontMetrics;
fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
fn variations(&self) -> &[FontVariation];
fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
let mut style = FontStyle::NORMAL;
if os2.fs_selection().contains(SelectionFlags::ITALIC) {
style = FontStyle::ITALIC;
}
let weight = FontWeight::from_float(os2.us_weight_class() as f32);
let stretch = match os2.us_width_class() {
1 => FontStretch::ULTRA_CONDENSED,
2 => FontStretch::EXTRA_CONDENSED,
3 => FontStretch::CONDENSED,
4 => FontStretch::SEMI_CONDENSED,
5 => FontStretch::NORMAL,
6 => FontStretch::SEMI_EXPANDED,
7 => FontStretch::EXPANDED,
8 => FontStretch::EXTRA_EXPANDED,
9 => FontStretch::ULTRA_EXPANDED,
_ => FontStretch::NORMAL,
};
FontTemplateDescriptor::new(weight, stretch, style)
}
}
pub(crate) type FractionalPixel = f64;
pub(crate) trait FontTableMethods {
fn buffer(&self) -> &[u8];
}
#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub struct FontMetrics {
pub underline_size: Au,
pub underline_offset: Au,
pub strikeout_size: Au,
pub strikeout_offset: Au,
pub leading: Au,
pub x_height: Au,
pub em_size: Au,
pub ascent: Au,
pub descent: Au,
pub max_advance: Au,
pub average_advance: Au,
pub line_gap: Au,
pub zero_horizontal_advance: Option<Au>,
pub ic_horizontal_advance: Option<Au>,
pub space_advance: Au,
}
impl FontMetrics {
pub fn empty() -> Arc<Self> {
static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
EMPTY.get_or_init(Default::default).clone()
}
pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
self.ascent != other.ascent ||
self.descent != other.descent ||
self.line_gap != other.line_gap
}
}
#[derive(Debug, Default)]
struct CachedShapeData {
glyph_advances: HashMap<GlyphId, FractionalPixel>,
glyph_indices: HashMap<char, Option<GlyphId>>,
shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
}
impl malloc_size_of::MallocSizeOf for CachedShapeData {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
let shaped_text_size = self
.shaped_text
.iter()
.map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
.sum::<usize>();
self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
}
}
pub struct Font {
pub(crate) handle: PlatformFont,
pub(crate) template: FontTemplateRef,
pub metrics: Arc<FontMetrics>,
pub descriptor: FontDescriptor,
data_and_index: OnceLock<FontDataAndIndex>,
shaper: OnceLock<Shaper>,
cached_shape_data: RwLock<CachedShapeData>,
font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
pub(crate) synthesized_small_caps: Option<FontRef>,
has_color_bitmap_or_colr_table: OnceLock<bool>,
can_do_fast_shaping: OnceLock<bool>,
}
impl std::fmt::Debug for Font {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Font")
.field("template", &self.template)
.field("descriptor", &self.descriptor)
.finish()
}
}
impl malloc_size_of::MallocSizeOf for Font {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
self.metrics.size_of(ops) +
self.descriptor.size_of(ops) +
self.cached_shape_data.read().size_of(ops) +
self.font_instance_key
.read()
.values()
.map(|key| key.size_of(ops))
.sum::<usize>()
}
}
impl Font {
pub fn new(
template: FontTemplateRef,
descriptor: FontDescriptor,
data: Option<FontData>,
synthesized_small_caps: Option<FontRef>,
) -> Result<Font, &'static str> {
let synthetic_bold = {
let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
is_bold && allows_synthetic_bold
};
let handle = PlatformFont::new_from_template(
template.clone(),
Some(descriptor.pt_size),
&descriptor.variation_settings,
&data,
synthetic_bold,
)?;
let metrics = Arc::new(handle.metrics());
Ok(Font {
handle,
template,
metrics,
descriptor,
data_and_index: data
.map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
.unwrap_or_default(),
shaper: OnceLock::new(),
cached_shape_data: Default::default(),
font_instance_key: Default::default(),
synthesized_small_caps,
has_color_bitmap_or_colr_table: OnceLock::new(),
can_do_fast_shaping: OnceLock::new(),
})
}
pub fn identifier(&self) -> FontIdentifier {
self.template.identifier()
}
pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
self.handle.webrender_font_instance_flags()
}
pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
*self.has_color_bitmap_or_colr_table.get_or_init(|| {
self.table_for_tag(SBIX).is_some() ||
self.table_for_tag(CBDT).is_some() ||
self.table_for_tag(COLR).is_some()
})
}
pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
*self
.font_instance_key
.write()
.entry(painter_id)
.or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
}
pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
if let Some(data_and_index) = self.data_and_index.get() {
return Ok(data_and_index);
}
let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
unreachable!("All web fonts should already have initialized data");
};
let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
return Err(FontDataError::FailedToLoad);
};
let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
Ok(data_and_index)
}
pub(crate) fn variations(&self) -> &[FontVariation] {
self.handle.variations()
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShapingFlags: u8 {
const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
const RTL_FLAG = 1 << 4;
const KEEP_ALL_FLAG = 1 << 5;
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShapingOptions {
pub letter_spacing: Option<Au>,
pub word_spacing: Au,
pub script: Script,
pub language: Language,
pub flags: ShapingFlags,
}
impl ShapingOptions {
pub(crate) fn letter_spacing_for_character(&self, character: char) -> Option<Au> {
self.letter_spacing.filter(|_| {
icu_properties::maps::general_category().get(character) !=
icu_properties::GeneralCategory::Format
})
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ShapeCacheEntry {
text: String,
options: ShapingOptions,
}
impl Font {
pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
let lookup_key = ShapeCacheEntry {
text: text.to_owned(),
options: *options,
};
{
let cache = self.cached_shape_data.read();
if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
return shaped_text.clone();
}
}
let start_time = Instant::now();
let glyphs = if self.can_do_fast_shaping(text, options) {
debug!("shape_text: Using ASCII fast path.");
self.shape_text_fast(text, options)
} else {
debug!("shape_text: Using Harfbuzz.");
self.shaper
.get_or_init(|| Shaper::new(self))
.shape_text(self, text, options)
};
let shaped_text = Arc::new(glyphs);
let mut cache = self.cached_shape_data.write();
cache.shaped_text.insert(lookup_key, shaped_text.clone());
TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
((Instant::now() - start_time).as_nanos()) as usize,
Ordering::Relaxed,
);
shaped_text
}
pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
options.script == Script::Latin &&
!options.flags.contains(ShapingFlags::RTL_FLAG) &&
*self.can_do_fast_shaping.get_or_init(|| {
self.table_for_tag(KERN).is_some() &&
self.table_for_tag(GPOS).is_none() &&
self.table_for_tag(GSUB).is_none()
}) &&
text.is_ascii()
}
fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> GlyphStore {
let mut glyph_store = GlyphStore::new(text, text.len(), options);
let mut prev_glyph_id = None;
for (string_byte_offset, byte) in text.bytes().enumerate() {
let character = byte as char;
let Some(glyph_id) = self.glyph_index(character) else {
continue;
};
let mut advance = advance_for_shaped_glyph(
Au::from_f64_px(self.glyph_h_advance(glyph_id)),
character,
options,
);
let offset = prev_glyph_id.map(|prev| {
let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
advance += h_kerning;
Point2D::new(h_kerning, Au::zero())
});
glyph_store.add_glyph(
character,
&ShapedGlyph {
glyph_id,
string_byte_offset,
advance,
offset,
},
);
prev_glyph_id = Some(glyph_id);
}
glyph_store
}
pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
let result = self.handle.table_for_tag(tag);
let status = if result.is_some() {
"Found"
} else {
"Didn't find"
};
debug!(
"{} font table[{}] in {:?},",
status,
str::from_utf8(tag.as_ref()).unwrap(),
self.identifier()
);
result
}
#[inline]
pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
{
let cache = self.cached_shape_data.read();
if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
return *glyph;
}
}
let codepoint = match self.descriptor.variant {
font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
font_variant_caps::T::Normal => codepoint,
};
let glyph_index = self.handle.glyph_index(codepoint);
let mut cache = self.cached_shape_data.write();
cache.glyph_indices.insert(codepoint, glyph_index);
glyph_index
}
pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
self.glyph_index(codepoint).is_some()
}
pub(crate) fn glyph_h_kerning(
&self,
first_glyph: GlyphId,
second_glyph: GlyphId,
) -> FractionalPixel {
self.handle.glyph_h_kerning(first_glyph, second_glyph)
}
pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
{
let cache = self.cached_shape_data.read();
if let Some(width) = cache.glyph_advances.get(&glyph_id) {
return *width;
}
}
let new_width = self
.handle
.glyph_h_advance(glyph_id)
.unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
let mut cache = self.cached_shape_data.write();
cache.glyph_advances.insert(glyph_id, new_width);
new_width
}
pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
self.handle.typographic_bounds(glyph_id)
}
pub fn baseline(&self) -> Option<FontBaseline> {
self.shaper.get_or_init(|| Shaper::new(self)).baseline()
}
#[cfg(not(target_os = "macos"))]
pub(crate) fn find_fallback_using_system_font_api(
&self,
_: &FallbackFontSelectionOptions,
) -> Option<FontRef> {
None
}
}
#[derive(Clone, Debug, MallocSizeOf)]
pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
impl Deref for FontRef {
type Target = Arc<Font>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
pub struct FallbackKey {
script: Script,
unicode_block: Option<UnicodeBlock>,
lang: XLang,
}
impl FallbackKey {
fn new(options: &FallbackFontSelectionOptions) -> Self {
Self {
script: Script::from(options.character),
unicode_block: options.character.block(),
lang: options.lang.clone(),
}
}
}
#[derive(MallocSizeOf)]
pub struct FontGroup {
descriptor: FontDescriptor,
families: SmallVec<[FontGroupFamily; 8]>,
fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
}
impl FontGroup {
pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
let families: SmallVec<[FontGroupFamily; 8]> = style
.font_family
.families
.iter()
.map(FontGroupFamily::local_or_web)
.collect();
FontGroup {
descriptor,
families,
fallbacks: Default::default(),
}
}
pub fn find_by_codepoint(
&self,
font_context: &FontContext,
codepoint: char,
next_codepoint: Option<char>,
lang: XLang,
) -> Option<FontRef> {
let codepoint = match codepoint {
'\t' => ' ',
_ => codepoint,
};
let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
options.character.is_ascii_lowercase();
let font_or_synthesized_small_caps = |font: FontRef| {
if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
return font.synthesized_small_caps.clone();
}
Some(font)
};
let font_has_glyph_and_presentation = |font: &FontRef| {
match options.presentation_preference {
EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
return false;
},
EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
return false;
},
_ => {},
}
font.has_glyph_for(options.character)
};
let char_in_template =
|template: FontTemplateRef| template.char_in_unicode_range(options.character);
if let Some(font) = self.find(
font_context,
&char_in_template,
&font_has_glyph_and_presentation,
) {
return font_or_synthesized_small_caps(font);
}
let fallback_key = FallbackKey::new(&options);
if let Some(fallback) = self.fallbacks.read().get(&fallback_key) {
if char_in_template(fallback.template.clone()) &&
font_has_glyph_and_presentation(fallback)
{
return font_or_synthesized_small_caps(fallback.clone());
}
}
if let Some(font) = self.find_fallback_using_system_font_list(
font_context,
options.clone(),
&char_in_template,
&font_has_glyph_and_presentation,
) {
let fallback = font_or_synthesized_small_caps(font);
if let Some(fallback) = fallback.clone() {
self.fallbacks.write().insert(fallback_key, fallback);
}
return fallback;
}
let first_font = self.first(font_context);
if let Some(fallback) = first_font
.as_ref()
.and_then(|font| font.find_fallback_using_system_font_api(&options))
{
if font_has_glyph_and_presentation(&fallback) {
return Some(fallback);
}
}
first_font
}
pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
let font_predicate = |_: &FontRef| true;
self.find(font_context, &space_in_template, &font_predicate)
.or_else(|| {
self.find_fallback_using_system_font_list(
font_context,
FallbackFontSelectionOptions::default(),
&space_in_template,
&font_predicate,
)
})
}
fn find(
&self,
font_context: &FontContext,
template_predicate: &impl Fn(FontTemplateRef) -> bool,
font_predicate: &impl Fn(&FontRef) -> bool,
) -> Option<FontRef> {
self.families
.iter()
.flat_map(|family| family.templates(font_context, &self.descriptor))
.find_map(|template| {
template.font_if_matches(
font_context,
&self.descriptor,
template_predicate,
font_predicate,
)
})
}
fn find_fallback_using_system_font_list(
&self,
font_context: &FontContext,
options: FallbackFontSelectionOptions,
template_predicate: &impl Fn(FontTemplateRef) -> bool,
font_predicate: &impl Fn(&FontRef) -> bool,
) -> Option<FontRef> {
iter::once(FontFamilyDescriptor::default())
.chain(
fallback_font_families(options)
.into_iter()
.map(|family_name| {
let family = SingleFontFamily::FamilyName(FamilyName {
name: family_name.into(),
syntax: FontFamilyNameSyntax::Quoted,
});
FontFamilyDescriptor::new(family, FontSearchScope::Local)
}),
)
.find_map(|family_descriptor| {
FontGroupFamily::from(family_descriptor)
.templates(font_context, &self.descriptor)
.find_map(|template| {
template.font_if_matches(
font_context,
&self.descriptor,
template_predicate,
font_predicate,
)
})
})
}
}
#[derive(MallocSizeOf)]
struct FontGroupFamilyTemplate {
#[ignore_malloc_size_of = "This measured in the FontContext template cache."]
template: FontTemplateRef,
#[ignore_malloc_size_of = "This measured in the FontContext font cache."]
font: OnceLock<Option<FontRef>>,
}
impl From<FontTemplateRef> for FontGroupFamilyTemplate {
fn from(template: FontTemplateRef) -> Self {
Self {
template,
font: Default::default(),
}
}
}
impl FontGroupFamilyTemplate {
fn font(
&self,
font_context: &FontContext,
font_descriptor: &FontDescriptor,
) -> Option<FontRef> {
self.font
.get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
.clone()
}
fn font_if_matches(
&self,
font_context: &FontContext,
font_descriptor: &FontDescriptor,
template_predicate: &impl Fn(FontTemplateRef) -> bool,
font_predicate: &impl Fn(&FontRef) -> bool,
) -> Option<FontRef> {
if !template_predicate(self.template.clone()) {
return None;
}
self.font(font_context, font_descriptor)
.filter(font_predicate)
}
}
#[derive(MallocSizeOf)]
struct FontGroupFamily {
family_descriptor: FontFamilyDescriptor,
members: OnceLock<Vec<FontGroupFamilyTemplate>>,
}
impl From<FontFamilyDescriptor> for FontGroupFamily {
fn from(family_descriptor: FontFamilyDescriptor) -> Self {
Self {
family_descriptor,
members: Default::default(),
}
}
}
impl FontGroupFamily {
fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
}
fn templates(
&self,
font_context: &FontContext,
font_descriptor: &FontDescriptor,
) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
self.members
.get_or_init(|| {
font_context
.matching_templates(font_descriptor, &self.family_descriptor)
.into_iter()
.map(Into::into)
.collect()
})
.iter()
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FontSearchScope {
Any,
Local,
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct FontFamilyDescriptor {
pub(crate) family: SingleFontFamily,
pub(crate) scope: FontSearchScope,
}
impl FontFamilyDescriptor {
pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
FontFamilyDescriptor { family, scope }
}
fn default() -> FontFamilyDescriptor {
FontFamilyDescriptor {
family: SingleFontFamily::Generic(GenericFontFamily::None),
scope: FontSearchScope::Local,
}
}
}
pub struct FontBaseline {
pub ideographic_baseline: f32,
pub alphabetic_baseline: f32,
pub hanging_baseline: f32,
}
#[cfg(all(
any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
not(target_env = "ohos")
))]
pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
if value < mapping[0].0 {
return mapping[0].1;
}
for window in mapping.windows(2) {
let (font_config_value_a, css_value_a) = window[0];
let (font_config_value_b, css_value_b) = window[1];
if value >= font_config_value_a && value <= font_config_value_b {
let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
return css_value_a + ((css_value_b - css_value_a) * ratio);
}
}
mapping[mapping.len() - 1].1
}
pub(super) fn advance_for_shaped_glyph(
mut advance: Au,
character: char,
options: &ShapingOptions,
) -> Au {
if let Some(letter_spacing) = options.letter_spacing_for_character(character) {
advance += letter_spacing;
};
if character == ' ' || character == '\u{a0}' {
advance += options.word_spacing;
}
advance
}