Rename gfx to fonts (#32556)

This crate only takes care of fonts now as graphics related things are
split into other crates. In addition, this exposes data structures at
the top of the crate, hiding the implementation details and making it
simpler to import them.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Martin Robinson 2024-06-19 22:26:19 +02:00 committed by GitHub
parent 9f8118abc7
commit cd2ab36759
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
159 changed files with 224 additions and 266 deletions

View file

@ -0,0 +1,75 @@
[package]
name = "fonts"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
publish.workspace = true
[lib]
name = "fonts"
path = "lib.rs"
test = false
doctest = false
[dependencies]
app_units = { workspace = true }
atomic_refcell = { workspace = true }
base = { workspace = true }
bitflags = { workspace = true }
cssparser = { workspace = true }
crossbeam-channel = { workspace = true }
euclid = { workspace = true }
fnv = { workspace = true }
fontsan = { git = "https://github.com/servo/fontsan" }
fonts_traits = { workspace = true }
harfbuzz-sys = "0.6.1"
ipc-channel = { workspace = true }
lazy_static = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
malloc_size_of = { workspace = true }
malloc_size_of_derive = { workspace = true }
net_traits = { workspace = true }
parking_lot = { workspace = true }
range = { path = "../range" }
serde = { workspace = true }
servo_arc = { workspace = true }
servo_atoms = { workspace = true }
servo_url = { path = "../url" }
smallvec = { workspace = true, features = ["union"] }
surfman = { workspace = true }
style = { workspace = true }
unicode-bidi = { workspace = true, features = ["with_serde"] }
unicode-properties = { workspace = true }
unicode-script = { workspace = true }
url = { workspace = true }
webrender_api = { workspace = true }
webrender_traits = { workspace = true }
xi-unicode = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
byteorder = { workspace = true }
core-foundation = "0.9"
core-graphics = "0.23"
core-text = "20.1"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
harfbuzz-sys = { version = "0.6", features = ["bundled"] }
freetype-sys = { workspace = true }
servo_allocator = { path = "../allocator" }
[target.'cfg(all(target_os = "linux", not(target_env = "ohos")))'.dependencies]
fontconfig_sys = { package = "yeslogic-fontconfig-sys", version = "5" }
[target.'cfg(target_os = "android")'.dependencies]
xml-rs = "0.8"
[target.'cfg(target_env = "ohos")'.dependencies]
harfbuzz-sys = { version = "0.6.1", features = ["bundled"] }
[target.'cfg(target_os = "windows")'.dependencies]
harfbuzz-sys = { version = "0.6", features = ["bundled"] }
dwrote = "0.11"
truetype = { version = "0.47.3", features = ["ignore-invalid-language-ids"] }

886
components/fonts/font.rs Normal file
View file

@ -0,0 +1,886 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::ToOwned;
use std::collections::HashMap;
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, Size2D};
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use servo_atoms::{atom, Atom};
use smallvec::SmallVec;
use style::computed_values::font_variant_caps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{GenericFontFamily, SingleFontFamily};
use style::values::computed::{FontStretch, FontStyle, FontWeight};
use unicode_script::Script;
use webrender_api::{FontInstanceFlags, FontInstanceKey};
use crate::font_cache_thread::{FontIdentifier, FontSource};
use crate::font_context::FontContext;
use crate::font_template::{FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods};
use crate::platform::font::{FontTable, PlatformFont};
pub use crate::platform::font_list::fallback_font_families;
use crate::{
ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, GlyphData, GlyphId,
GlyphStore, Shaper,
};
#[macro_export]
macro_rules! ot_tag {
($t1:expr, $t2:expr, $t3:expr, $t4:expr) => {
(($t1 as u32) << 24) | (($t2 as u32) << 16) | (($t3 as u32) << 8) | ($t4 as u32)
};
}
pub const GPOS: u32 = ot_tag!('G', 'P', 'O', 'S');
pub const GSUB: u32 = ot_tag!('G', 'S', 'U', 'B');
pub const KERN: u32 = ot_tag!('k', 'e', 'r', 'n');
pub const SBIX: u32 = ot_tag!('s', 'b', 'i', 'x');
pub const CBDT: u32 = ot_tag!('C', 'B', 'D', 'T');
pub const COLR: u32 = ot_tag!('C', 'O', 'L', 'R');
pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
// PlatformFont encapsulates access to the platform's font API,
// e.g. quartz, FreeType. It provides access to metrics and tables
// needed by the text shaper as well as access to the underlying font
// resources needed by the graphics layer to draw glyphs.
pub trait PlatformFontMethods: Sized {
fn new_from_template(
template: FontTemplateRef,
pt_size: Option<Au>,
) -> Result<PlatformFont, &'static str> {
let data = template.data();
let face_index = template.identifier().index();
let font_identifier = template.borrow().identifier.clone();
Self::new_from_data(font_identifier, data, face_index, pt_size)
}
fn new_from_data(
font_identifier: FontIdentifier,
data: Arc<Vec<u8>>,
face_index: u32,
pt_size: Option<Au>,
) -> Result<PlatformFont, &'static str>;
/// Get a [`FontTemplateDescriptor`] from a [`PlatformFont`]. This is used to get
/// descriptors for web fonts.
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;
/// Can this font do basic horizontal LTR shaping without Harfbuzz?
fn can_do_fast_shaping(&self) -> bool;
fn metrics(&self) -> FontMetrics;
fn table_for_tag(&self, _: FontTableTag) -> Option<FontTable>;
/// Get the necessary [`FontInstanceFlags`]` for this font.
fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
}
// Used to abstract over the shaper's choice of fixed int representation.
pub type FractionalPixel = f64;
pub type FontTableTag = u32;
trait FontTableTagConversions {
fn tag_to_str(&self) -> String;
}
impl FontTableTagConversions for FontTableTag {
fn tag_to_str(&self) -> String {
let bytes = [
(self >> 24) as u8,
(self >> 16) as u8,
(self >> 8) as u8,
*self as u8,
];
str::from_utf8(&bytes).unwrap().to_owned()
}
}
pub trait FontTableMethods {
fn buffer(&self) -> &[u8];
}
#[derive(Clone, Debug, 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>,
}
impl FontMetrics {
/// Create an empty [`FontMetrics`] mainly to be used in situations where
/// no font can be found.
pub fn empty() -> Self {
Self {
underline_size: Au(0),
underline_offset: Au(0),
strikeout_size: Au(0),
strikeout_offset: Au(0),
leading: Au(0),
x_height: Au(0),
em_size: Au(0),
ascent: Au(0),
descent: Au(0),
max_advance: Au(0),
average_advance: Au(0),
line_gap: Au(0),
zero_horizontal_advance: None,
ic_horizontal_advance: None,
}
}
}
/// `FontDescriptor` describes the parameters of a `Font`. It represents rendering a given font
/// template at a particular size, with a particular font-variant-caps applied, etc. This contrasts
/// with `FontTemplateDescriptor` in that the latter represents only the parameters inherent in the
/// font data (weight, stretch, etc.).
#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct FontDescriptor {
pub weight: FontWeight,
pub stretch: FontStretch,
pub style: FontStyle,
pub variant: font_variant_caps::T,
pub pt_size: Au,
}
impl Eq for FontDescriptor {}
impl<'a> From<&'a FontStyleStruct> for FontDescriptor {
fn from(style: &'a FontStyleStruct) -> Self {
FontDescriptor {
weight: style.font_weight,
stretch: style.font_stretch,
style: style.font_style,
variant: style.font_variant_caps,
pt_size: Au::from_f32_px(style.font_size.computed_size().px()),
}
}
}
#[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 {
// Estimate the size of the shaped text cache. This will be smaller, because
// HashMap has some overhead, but we are mainly interested in the actual data.
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
}
}
#[derive(Debug)]
pub struct Font {
pub handle: PlatformFont,
pub template: FontTemplateRef,
pub metrics: FontMetrics,
pub descriptor: FontDescriptor,
shaper: OnceLock<Shaper>,
cached_shape_data: RwLock<CachedShapeData>,
pub font_key: FontInstanceKey,
/// If this is a synthesized small caps font, then this font reference is for
/// the version of the font used to replace lowercase ASCII letters. It's up
/// to the consumer of this font to properly use this reference.
pub synthesized_small_caps: Option<FontRef>,
/// Whether or not this font supports color bitmaps or a COLR table. This is
/// essentially equivalent to whether or not we use it for emoji presentation.
/// This is cached, because getting table data is expensive.
has_color_bitmap_or_colr_table: OnceLock<bool>,
}
impl malloc_size_of::MallocSizeOf for Font {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
// TODO: Collect memory usage for platform fonts and for shapers.
// This skips the template, because they are already stored in the template cache.
self.metrics.size_of(ops) +
self.descriptor.size_of(ops) +
self.cached_shape_data.read().size_of(ops) +
self.font_key.size_of(ops)
}
}
impl Font {
pub fn new(
template: FontTemplateRef,
descriptor: FontDescriptor,
synthesized_small_caps: Option<FontRef>,
) -> Result<Font, &'static str> {
let handle = PlatformFont::new_from_template(template.clone(), Some(descriptor.pt_size))?;
let metrics = handle.metrics();
Ok(Font {
handle,
template,
shaper: OnceLock::new(),
descriptor,
metrics,
cached_shape_data: Default::default(),
font_key: FontInstanceKey::default(),
synthesized_small_caps,
has_color_bitmap_or_colr_table: OnceLock::new(),
})
}
/// A unique identifier for the font, allowing comparison.
pub fn identifier(&self) -> FontIdentifier {
self.template.identifier()
}
pub fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
self.handle.webrender_font_instance_flags()
}
pub 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()
})
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShapingFlags: u8 {
/// Set if the text is entirely whitespace.
const IS_WHITESPACE_SHAPING_FLAG = 0x01;
/// Set if we are to ignore ligatures.
const IGNORE_LIGATURES_SHAPING_FLAG = 0x02;
/// Set if we are to disable kerning.
const DISABLE_KERNING_SHAPING_FLAG = 0x04;
/// Text direction is right-to-left.
const RTL_FLAG = 0x08;
/// Set if word-break is set to keep-all.
const KEEP_ALL_FLAG = 0x10;
}
}
/// Various options that control text shaping.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShapingOptions {
/// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
/// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null.
pub letter_spacing: Option<Au>,
/// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property.
pub word_spacing: Au,
/// The Unicode script property of the characters in this run.
pub script: Script,
/// Various flags.
pub flags: ShapingFlags,
}
/// An entry in the shape cache.
#[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 this = self as *const Font;
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 is_single_preserved_newline = text.len() == 1 && text.chars().next() == Some('\n');
let start_time = Instant::now();
let mut glyphs = GlyphStore::new(
text.len(),
options
.flags
.contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
is_single_preserved_newline,
options.flags.contains(ShapingFlags::RTL_FLAG),
);
if self.can_do_fast_shaping(text, options) {
debug!("shape_text: Using ASCII fast path.");
self.shape_text_fast(text, options, &mut glyphs);
} else {
debug!("shape_text: Using Harfbuzz.");
self.shaper
.get_or_init(|| Shaper::new(this))
.shape_text(text, options, &mut glyphs);
}
let shaped_text = Arc::new(glyphs);
let mut cache = self.cached_shape_data.write();
cache.shaped_text.insert(lookup_key, shaped_text.clone());
let end_time = Instant::now();
TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
(end_time.duration_since(start_time).as_nanos()) as usize,
Ordering::Relaxed,
);
shaped_text
}
fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
options.script == Script::Latin &&
!options.flags.contains(ShapingFlags::RTL_FLAG) &&
self.handle.can_do_fast_shaping() &&
text.is_ascii()
}
/// Fast path for ASCII text that only needs simple horizontal LTR kerning.
fn shape_text_fast(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
let mut prev_glyph_id = None;
for (i, byte) in text.bytes().enumerate() {
let character = byte as char;
let glyph_id = match self.glyph_index(character) {
Some(id) => id,
None => continue,
};
let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
if character == ' ' {
// https://drafts.csswg.org/css-text-3/#word-spacing-property
advance += options.word_spacing;
}
if let Some(letter_spacing) = options.letter_spacing {
advance += letter_spacing;
}
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(0))
});
let glyph = GlyphData::new(glyph_id, advance, offset, true, true);
glyphs.add_glyph_for_byte_index(ByteIndex(i as isize), character, &glyph);
prev_glyph_id = Some(glyph_id);
}
glyphs.finalize_changes();
}
pub fn table_for_tag(&self, tag: FontTableTag) -> 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,
tag.tag_to_str(),
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 fn has_glyph_for(&self, codepoint: char) -> bool {
self.glyph_index(codepoint).is_some()
}
pub 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;
}
}
// TODO: Need a fallback strategy.
let new_width = match self.handle.glyph_h_advance(glyph_id) {
Some(adv) => adv,
None => 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 type FontRef = Arc<Font>;
/// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by
/// `TextRun` to decide which font to render a character with. If none of the fonts listed in the
/// styles are suitable, a fallback font may be used.
#[derive(Debug, MallocSizeOf)]
pub struct FontGroup {
descriptor: FontDescriptor,
families: SmallVec<[FontGroupFamily; 8]>,
#[ignore_malloc_size_of = "This measured in the FontContext font cache."]
last_matching_fallback: Option<FontRef>,
}
impl FontGroup {
pub fn new(style: &FontStyleStruct) -> FontGroup {
let descriptor = FontDescriptor::from(style);
let families: SmallVec<[FontGroupFamily; 8]> = style
.font_family
.families
.iter()
.map(FontGroupFamily::new)
.collect();
FontGroup {
descriptor,
families,
last_matching_fallback: None,
}
}
/// Finds the first font, or else the first fallback font, which contains a glyph for
/// `codepoint`. If no such font is found, returns the first available font or fallback font
/// (which will cause a "glyph not found" character to be rendered). If no font at all can be
/// found, returns None.
pub fn find_by_codepoint<S: FontSource>(
&mut self,
font_context: &FontContext<S>,
codepoint: char,
next_codepoint: Option<char>,
) -> Option<FontRef> {
let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint);
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| {
// Do not select this font if it goes against our emoji preference.
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);
}
if let Some(ref last_matching_fallback) = self.last_matching_fallback {
if char_in_template(last_matching_fallback.template.clone()) &&
font_has_glyph_and_presentation(last_matching_fallback)
{
return font_or_synthesized_small_caps(last_matching_fallback.clone());
}
}
if let Some(font) = self.find_fallback(
font_context,
options,
char_in_template,
font_has_glyph_and_presentation,
) {
self.last_matching_fallback = Some(font.clone());
return font_or_synthesized_small_caps(font);
}
self.first(font_context)
}
/// Find the first available font in the group, or the first available fallback font.
pub fn first<S: FontSource>(&mut self, font_context: &FontContext<S>) -> Option<FontRef> {
// From https://drafts.csswg.org/css-fonts/#first-available-font:
// > The first available font, used for example in the definition of font-relative lengths
// > such as ex or in the definition of the line-height property, is defined to be the first
// > font for which the character U+0020 (space) is not excluded by a unicode-range, given the
// > font families in the font-family list (or a user agents default font if none are
// > available).
// > Note: it does not matter whether that font actually has a glyph for the space character.
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(
font_context,
FallbackFontSelectionOptions::default(),
space_in_template,
font_predicate,
)
})
}
/// Attempts to find a font which matches the given `template_predicate` and `font_predicate`.
/// This method mutates because we may need to load new font data in the process of finding
/// a suitable font.
fn find<S, TemplatePredicate, FontPredicate>(
&mut self,
font_context: &FontContext<S>,
template_predicate: TemplatePredicate,
font_predicate: FontPredicate,
) -> Option<FontRef>
where
S: FontSource,
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
let font_descriptor = self.descriptor.clone();
self.families
.iter_mut()
.filter_map(|font_group_family| {
font_group_family.find(
&font_descriptor,
font_context,
&template_predicate,
&font_predicate,
)
})
.next()
}
/// Attempts to find a suitable fallback font which matches the given `template_predicate` and
/// `font_predicate`. The default family (i.e. "serif") will be tried first, followed by
/// platform-specific family names. If a `codepoint` is provided, then its Unicode block may be
/// used to refine the list of family names which will be tried.
fn find_fallback<S, TemplatePredicate, FontPredicate>(
&mut self,
font_context: &FontContext<S>,
options: FallbackFontSelectionOptions,
template_predicate: TemplatePredicate,
font_predicate: FontPredicate,
) -> Option<FontRef>
where
S: FontSource,
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
iter::once(FontFamilyDescriptor::serif())
.chain(fallback_font_families(options).into_iter().map(|family| {
FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Local)
}))
.filter_map(|family_descriptor| {
FontGroupFamily {
family_descriptor,
members: None,
}
.find(
&self.descriptor,
font_context,
&template_predicate,
&font_predicate,
)
})
.next()
}
}
/// A [`FontGroupFamily`] can have multiple members if it is a "composite face", meaning
/// that it is defined by multiple `@font-face` declarations which vary only by their
/// `unicode-range` descriptors. In this case, font selection will select a single member
/// that contains the necessary unicode character. Unicode ranges are specified by the
/// [`FontGroupFamilyMember::template`] member.
#[derive(Debug, MallocSizeOf)]
struct FontGroupFamilyMember {
#[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: Option<FontRef>,
loaded: bool,
}
/// A `FontGroupFamily` is a single font family in a `FontGroup`. It corresponds to one of the
/// families listed in the `font-family` CSS property. The corresponding font data is lazy-loaded,
/// only if actually needed. A single `FontGroupFamily` can have multiple fonts, in the case that
/// individual fonts only cover part of the Unicode range.
#[derive(Debug, MallocSizeOf)]
struct FontGroupFamily {
family_descriptor: FontFamilyDescriptor,
members: Option<Vec<FontGroupFamilyMember>>,
}
impl FontGroupFamily {
fn new(family: &SingleFontFamily) -> FontGroupFamily {
let family_descriptor =
FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Any);
FontGroupFamily {
family_descriptor,
members: None,
}
}
fn find<S, TemplatePredicate, FontPredicate>(
&mut self,
font_descriptor: &FontDescriptor,
font_context: &FontContext<S>,
template_predicate: &TemplatePredicate,
font_predicate: &FontPredicate,
) -> Option<FontRef>
where
S: FontSource,
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
self.members(font_descriptor, font_context)
.filter_map(|member| {
if !template_predicate(member.template.clone()) {
return None;
}
if !member.loaded {
member.font = font_context.font(member.template.clone(), font_descriptor);
member.loaded = true;
}
if matches!(&member.font, Some(font) if font_predicate(font)) {
return member.font.clone();
}
None
})
.next()
}
fn members<S: FontSource>(
&mut self,
font_descriptor: &FontDescriptor,
font_context: &FontContext<S>,
) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
let family_descriptor = &self.family_descriptor;
let members = self.members.get_or_insert_with(|| {
font_context
.matching_templates(font_descriptor, family_descriptor)
.into_iter()
.map(|template| FontGroupFamilyMember {
template,
loaded: false,
font: None,
})
.collect()
});
members.iter_mut()
}
}
pub struct RunMetrics {
// may be negative due to negative width (i.e., kerning of '.' in 'P.T.')
pub advance_width: Au,
pub ascent: Au, // nonzero
pub descent: Au, // nonzero
// this bounding box is relative to the left origin baseline.
// so, bounding_box.position.y = -ascent
pub bounding_box: Rect<Au>,
}
impl RunMetrics {
pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
let bounds = Rect::new(
Point2D::new(Au(0), -ascent),
Size2D::new(advance, ascent + descent),
);
// TODO(Issue #125): support loose and tight bounding boxes; using the
// ascent+descent and advance is sometimes too generous and
// looking at actual glyph extents can yield a tighter box.
RunMetrics {
advance_width: advance,
bounding_box: bounds,
ascent,
descent,
}
}
}
pub fn get_and_reset_text_shaping_performance_counter() -> usize {
TEXT_SHAPING_PERFORMANCE_COUNTER.swap(0, Ordering::SeqCst)
}
/// The scope within which we will look for a font.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FontSearchScope {
/// All fonts will be searched, including those specified via `@font-face` rules.
Any,
/// Only local system fonts will be searched.
Local,
}
/// A font family name used in font selection.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FontFamilyName {
/// A specific name such as `"Arial"`
Specific(Atom),
/// A generic name such as `sans-serif`
Generic(Atom),
}
impl FontFamilyName {
pub fn name(&self) -> &str {
match *self {
FontFamilyName::Specific(ref name) => name,
FontFamilyName::Generic(ref name) => name,
}
}
}
impl<'a> From<&'a SingleFontFamily> for FontFamilyName {
fn from(other: &'a SingleFontFamily) -> FontFamilyName {
match *other {
SingleFontFamily::FamilyName(ref family_name) => {
FontFamilyName::Specific(family_name.name.clone())
},
SingleFontFamily::Generic(generic) => FontFamilyName::Generic(match generic {
GenericFontFamily::None => panic!("Shouldn't appear in style"),
GenericFontFamily::Serif => atom!("serif"),
GenericFontFamily::SansSerif => atom!("sans-serif"),
GenericFontFamily::Monospace => atom!("monospace"),
GenericFontFamily::Cursive => atom!("cursive"),
GenericFontFamily::Fantasy => atom!("fantasy"),
GenericFontFamily::SystemUi => atom!("system-ui"),
}),
}
}
}
impl<'a> From<&'a str> for FontFamilyName {
fn from(other: &'a str) -> FontFamilyName {
FontFamilyName::Specific(Atom::from(other))
}
}
/// The font family parameters for font selection.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct FontFamilyDescriptor {
pub name: FontFamilyName,
pub scope: FontSearchScope,
}
impl FontFamilyDescriptor {
pub fn new(name: FontFamilyName, scope: FontSearchScope) -> FontFamilyDescriptor {
FontFamilyDescriptor { name, scope }
}
fn serif() -> FontFamilyDescriptor {
FontFamilyDescriptor {
name: FontFamilyName::Generic(atom!("serif")),
scope: FontSearchScope::Local,
}
}
pub fn name(&self) -> &str {
self.name.name()
}
}
/// Given a mapping array `mapping` and a value, map that value onto
/// the value specified by the array. For instance, for FontConfig
/// values of weights, we would map these onto the CSS [0..1000] range
/// by creating an array as below. Values that fall between two mapped
/// values, will be adjusted by the weighted mean.
///
/// ```rust
/// let mapping = [
/// (0., 0.),
/// (FC_WEIGHT_REGULAR as f64, 400 as f64),
/// (FC_WEIGHT_BOLD as f64, 700 as f64),
/// (FC_WEIGHT_EXTRABLACK as f64, 1000 as f64),
/// ];
/// let mapped_weight = apply_font_config_to_style_mapping(&mapping, weight as f64);
/// ```
#[cfg(any(target_os = "linux", target_os = "macos"))]
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
}

View file

@ -0,0 +1,555 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::ops::{Deref, RangeInclusive};
use std::sync::Arc;
use std::{fmt, thread};
use app_units::Au;
use atomic_refcell::AtomicRefCell;
use ipc_channel::ipc::{self, IpcBytesReceiver, IpcBytesSender, IpcReceiver, IpcSender};
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use servo_atoms::Atom;
use servo_url::ServoUrl;
use style::font_face::{FontFaceRuleData, FontStyle as FontFaceStyle};
use style::values::computed::font::{FixedPoint, FontStyleFixedPoint};
use style::values::computed::{FontStretch, FontWeight};
use style::values::specified::FontStretch as SpecifiedFontStretch;
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use webrender_traits::WebRenderFontApi;
use crate::font::{FontDescriptor, FontFamilyName};
use crate::font_store::FontStore;
use crate::font_template::{
FontTemplate, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods,
};
use crate::platform::font_list::{
for_each_available_family, for_each_variation, system_default_family, LocalFontIdentifier,
SANS_SERIF_FONT_FAMILY,
};
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FontIdentifier {
Local(LocalFontIdentifier),
Web(ServoUrl),
}
impl FontIdentifier {
pub fn index(&self) -> u32 {
match *self {
Self::Local(ref local_font_identifier) => local_font_identifier.index(),
Self::Web(_) => 0,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SerializedFontTemplate {
identifier: FontIdentifier,
descriptor: FontTemplateDescriptor,
bytes_receiver: ipc_channel::ipc::IpcBytesReceiver,
}
/// Commands that the FontContext sends to the font cache thread.
#[derive(Debug, Deserialize, Serialize)]
pub enum Command {
GetFontTemplates(
Option<FontDescriptor>,
FontFamilyName,
IpcSender<Vec<SerializedFontTemplate>>,
),
GetFontInstance(
FontIdentifier,
Au,
FontInstanceFlags,
IpcSender<FontInstanceKey>,
),
GetWebFont(IpcBytesReceiver, u32, IpcSender<FontKey>),
GetWebFontInstance(FontKey, f32, FontInstanceFlags, IpcSender<FontInstanceKey>),
Exit(IpcSender<()>),
Ping,
}
/// The font cache thread itself. It maintains a list of reference counted
/// font templates that are currently in use.
struct FontCache {
port: IpcReceiver<Command>,
generic_fonts: HashMap<FontFamilyName, LowercaseString>,
font_data: HashMap<FontIdentifier, Arc<Vec<u8>>>,
local_families: FontStore,
webrender_api: Box<dyn WebRenderFontApi>,
webrender_fonts: HashMap<FontIdentifier, FontKey>,
font_instances: HashMap<(FontKey, Au), FontInstanceKey>,
}
fn populate_generic_fonts() -> HashMap<FontFamilyName, LowercaseString> {
let mut generic_fonts = HashMap::with_capacity(5);
append_map(&mut generic_fonts, "serif", "Times New Roman");
append_map(&mut generic_fonts, "sans-serif", SANS_SERIF_FONT_FAMILY);
append_map(&mut generic_fonts, "cursive", "Apple Chancery");
append_map(&mut generic_fonts, "fantasy", "Papyrus");
append_map(&mut generic_fonts, "monospace", "Menlo");
fn append_map(
generic_fonts: &mut HashMap<FontFamilyName, LowercaseString>,
generic_name: &str,
mapped_name: &str,
) {
let family_name = match system_default_family(generic_name) {
Some(system_default) => LowercaseString::new(&system_default),
None => LowercaseString::new(mapped_name),
};
let generic_name = FontFamilyName::Generic(Atom::from(generic_name));
generic_fonts.insert(generic_name, family_name);
}
generic_fonts
}
impl FontCache {
fn run(&mut self) {
loop {
let msg = self.port.recv().unwrap();
match msg {
Command::GetFontTemplates(descriptor_to_match, font_family_name, result) => {
let templates =
self.find_font_templates(descriptor_to_match.as_ref(), &font_family_name);
debug!("Found templates for descriptor {descriptor_to_match:?}: ");
debug!(" {templates:?}");
let (serialized_templates, senders): (
Vec<SerializedFontTemplate>,
Vec<(FontTemplateRef, IpcBytesSender)>,
) = templates
.into_iter()
.map(|template| {
let (bytes_sender, bytes_receiver) =
ipc::bytes_channel().expect("failed to create IPC channel");
(
SerializedFontTemplate {
identifier: template.identifier().clone(),
descriptor: template.descriptor().clone(),
bytes_receiver,
},
(template.clone(), bytes_sender),
)
})
.unzip();
let _ = result.send(serialized_templates);
// NB: This will load the font into memory if it hasn't been loaded already.
for (font_template, bytes_sender) in senders.iter() {
let identifier = font_template.identifier();
let data = self
.font_data
.entry(identifier)
.or_insert_with(|| font_template.data());
let _ = bytes_sender.send(data);
}
},
Command::GetFontInstance(identifier, pt_size, flags, result) => {
let _ = result.send(self.get_font_instance(identifier, pt_size, flags));
},
Command::GetWebFont(bytes_receiver, font_index, result_sender) => {
self.webrender_api.forward_add_font_message(
bytes_receiver,
font_index,
result_sender,
);
},
Command::GetWebFontInstance(
font_key,
font_size,
font_instance_flags,
result_sender,
) => {
self.webrender_api.forward_add_font_instance_message(
font_key,
font_size,
font_instance_flags,
result_sender,
);
},
Command::Ping => (),
Command::Exit(result) => {
let _ = result.send(());
break;
},
}
}
}
fn refresh_local_families(&mut self) {
self.local_families.clear();
for_each_available_family(|family_name| {
let family_name = LowercaseString::new(&family_name);
self.local_families.families.entry(family_name).or_default();
});
}
fn transform_family(&self, family_name: &FontFamilyName) -> LowercaseString {
match self.generic_fonts.get(family_name) {
None => LowercaseString::from(family_name),
Some(mapped_family) => (*mapped_family).clone(),
}
}
fn find_font_templates(
&mut self,
descriptor_to_match: Option<&FontDescriptor>,
family_name: &FontFamilyName,
) -> Vec<FontTemplateRef> {
// TODO(Issue #188): look up localized font family names if canonical name not found
// look up canonical name
// TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.
// if such family exists, try to match style to a font
let family_name = self.transform_family(family_name);
self.local_families
.families
.get_mut(&family_name)
.map(|font_templates| {
if font_templates.templates.is_empty() {
for_each_variation(&family_name, |font_template| {
font_templates.add_template(font_template);
});
}
font_templates.find_for_descriptor(descriptor_to_match)
})
.unwrap_or_default()
}
fn get_font_instance(
&mut self,
identifier: FontIdentifier,
pt_size: Au,
flags: FontInstanceFlags,
) -> FontInstanceKey {
let webrender_font_api = &self.webrender_api;
let webrender_fonts = &mut self.webrender_fonts;
let font_data = self
.font_data
.get(&identifier)
.expect("Got unexpected FontIdentifier")
.clone();
let font_key = *webrender_fonts
.entry(identifier.clone())
.or_insert_with(|| {
// CoreText cannot reliably create CoreTextFonts for system fonts stored
// as part of TTC files, so on CoreText platforms, create a system font in
// WebRender using the LocalFontIdentifier. This has the downside of
// causing the font to be loaded into memory again (bummer!), so only do
// this for those platforms.
#[cfg(target_os = "macos")]
if let FontIdentifier::Local(local_font_identifier) = identifier {
return webrender_font_api
.add_system_font(local_font_identifier.native_font_handle());
}
webrender_font_api.add_font(font_data, identifier.index())
});
*self
.font_instances
.entry((font_key, pt_size))
.or_insert_with(|| {
webrender_font_api.add_font_instance(font_key, pt_size.to_f32_px(), flags)
})
}
}
pub trait FontSource: Clone {
fn find_matching_font_templates(
&self,
descriptor_to_match: Option<&FontDescriptor>,
font_family_name: &FontFamilyName,
) -> Vec<FontTemplateRef>;
fn get_system_font_instance(
&self,
font_identifier: FontIdentifier,
size: Au,
flags: FontInstanceFlags,
) -> FontInstanceKey;
fn get_web_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey;
fn get_web_font_instance(
&self,
font_key: FontKey,
size: f32,
flags: FontInstanceFlags,
) -> FontInstanceKey;
}
/// The public interface to the font cache thread, used by per-thread `FontContext` instances (via
/// the `FontSource` trait), and also by layout.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FontCacheThread {
chan: IpcSender<Command>,
}
/// A version of `FontStyle` from Stylo that is serializable. Normally this is not
/// because the specified version of `FontStyle` contains floats.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum ComputedFontStyleDescriptor {
Normal,
Italic,
Oblique(FontStyleFixedPoint, FontStyleFixedPoint),
}
/// This data structure represents the various optional descriptors that can be
/// applied to a `@font-face` rule in CSS. These are used to create a [`FontTemplate`]
/// from the given font data used as the source of the `@font-face` rule. If values
/// like weight, stretch, and style are not specified they are initialized based
/// on the contents of the font itself.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct CSSFontFaceDescriptors {
pub family_name: LowercaseString,
pub weight: Option<(FontWeight, FontWeight)>,
pub stretch: Option<(FontStretch, FontStretch)>,
pub style: Option<ComputedFontStyleDescriptor>,
pub unicode_range: Option<Vec<RangeInclusive<u32>>>,
}
impl CSSFontFaceDescriptors {
pub fn new(family_name: &str) -> Self {
CSSFontFaceDescriptors {
family_name: LowercaseString::new(family_name),
..Default::default()
}
}
}
impl From<&FontFaceRuleData> for CSSFontFaceDescriptors {
fn from(rule_data: &FontFaceRuleData) -> Self {
let family_name = rule_data
.family
.as_ref()
.expect("Expected rule to contain a font family.")
.name
.clone();
let weight = rule_data
.weight
.as_ref()
.map(|weight_range| (weight_range.0.compute(), weight_range.1.compute()));
let stretch_to_computed = |specified: SpecifiedFontStretch| match specified {
SpecifiedFontStretch::Stretch(percentage) => {
FontStretch::from_percentage(percentage.compute().0)
},
SpecifiedFontStretch::Keyword(keyword) => keyword.compute(),
SpecifiedFontStretch::System(_) => FontStretch::NORMAL,
};
let stretch = rule_data.stretch.as_ref().map(|stretch_range| {
(
stretch_to_computed(stretch_range.0),
stretch_to_computed(stretch_range.1),
)
});
fn style_to_computed(specified: &FontFaceStyle) -> ComputedFontStyleDescriptor {
match specified {
FontFaceStyle::Normal => ComputedFontStyleDescriptor::Normal,
FontFaceStyle::Italic => ComputedFontStyleDescriptor::Italic,
FontFaceStyle::Oblique(angle_a, angle_b) => ComputedFontStyleDescriptor::Oblique(
FixedPoint::from_float(angle_a.degrees()),
FixedPoint::from_float(angle_b.degrees()),
),
}
}
let style = rule_data.style.as_ref().map(style_to_computed);
let unicode_range = rule_data
.unicode_range
.as_ref()
.map(|ranges| ranges.iter().map(|range| range.start..=range.end).collect());
CSSFontFaceDescriptors {
family_name: LowercaseString::new(&family_name),
weight,
stretch,
style,
unicode_range,
}
}
}
impl FontCacheThread {
pub fn new(webrender_api: Box<dyn WebRenderFontApi + Send>) -> FontCacheThread {
let (chan, port) = ipc::channel().unwrap();
thread::Builder::new()
.name("FontCache".to_owned())
.spawn(move || {
// TODO: Allow users to specify these.
let generic_fonts = populate_generic_fonts();
#[allow(clippy::default_constructed_unit_structs)]
let mut cache = FontCache {
port,
generic_fonts,
font_data: HashMap::new(),
local_families: Default::default(),
webrender_api,
webrender_fonts: HashMap::new(),
font_instances: HashMap::new(),
};
cache.refresh_local_families();
cache.run();
})
.expect("Thread spawning failed");
FontCacheThread { chan }
}
pub fn exit(&self) {
let (response_chan, response_port) = ipc::channel().unwrap();
self.chan
.send(Command::Exit(response_chan))
.expect("Couldn't send FontCacheThread exit message");
response_port
.recv()
.expect("Couldn't receive FontCacheThread reply");
}
}
impl FontSource for FontCacheThread {
fn get_system_font_instance(
&self,
identifier: FontIdentifier,
size: Au,
flags: FontInstanceFlags,
) -> FontInstanceKey {
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
self.chan
.send(Command::GetFontInstance(
identifier,
size,
flags,
response_chan,
))
.expect("failed to send message to font cache thread");
let instance_key = response_port.recv();
if instance_key.is_err() {
let font_thread_has_closed = self.chan.send(Command::Ping).is_err();
assert!(
font_thread_has_closed,
"Failed to receive a response from live font cache"
);
panic!("Font cache thread has already exited.");
}
instance_key.unwrap()
}
fn find_matching_font_templates(
&self,
descriptor_to_match: Option<&FontDescriptor>,
font_family_name: &FontFamilyName,
) -> Vec<FontTemplateRef> {
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
self.chan
.send(Command::GetFontTemplates(
descriptor_to_match.cloned(),
font_family_name.clone(),
response_chan,
))
.expect("failed to send message to font cache thread");
let reply = response_port.recv();
if reply.is_err() {
let font_thread_has_closed = self.chan.send(Command::Ping).is_err();
assert!(
font_thread_has_closed,
"Failed to receive a response from live font cache"
);
panic!("Font cache thread has already exited.");
}
reply
.unwrap()
.into_iter()
.map(|serialized_font_template| {
let font_data = serialized_font_template.bytes_receiver.recv().ok();
Arc::new(AtomicRefCell::new(FontTemplate {
identifier: serialized_font_template.identifier,
descriptor: serialized_font_template.descriptor.clone(),
data: font_data.map(Arc::new),
stylesheet: None,
}))
})
.collect()
}
fn get_web_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey {
let (result_sender, result_receiver) =
ipc::channel().expect("failed to create IPC channel");
let (bytes_sender, bytes_receiver) =
ipc::bytes_channel().expect("failed to create IPC channel");
let _ = self
.chan
.send(Command::GetWebFont(bytes_receiver, index, result_sender));
let _ = bytes_sender.send(&data);
result_receiver.recv().unwrap()
}
fn get_web_font_instance(
&self,
font_key: FontKey,
font_size: f32,
font_flags: FontInstanceFlags,
) -> FontInstanceKey {
let (result_sender, result_receiver) =
ipc::channel().expect("failed to create IPC channel");
let _ = self.chan.send(Command::GetWebFontInstance(
font_key,
font_size,
font_flags,
result_sender,
));
result_receiver.recv().unwrap()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct LowercaseString {
inner: String,
}
impl LowercaseString {
pub fn new(s: &str) -> LowercaseString {
LowercaseString {
inner: s.to_lowercase(),
}
}
}
impl<'a> From<&'a FontFamilyName> for LowercaseString {
fn from(family_name: &'a FontFamilyName) -> LowercaseString {
LowercaseString::new(family_name.name())
}
}
impl Deref for LowercaseString {
type Target = str;
#[inline]
fn deref(&self) -> &str {
&self.inner
}
}
impl fmt::Display for LowercaseString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}

View file

@ -0,0 +1,773 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::{HashMap, HashSet};
use std::default::Default;
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use app_units::Au;
use crossbeam_channel::unbounded;
use fnv::FnvHasher;
use fonts_traits::WebFontLoadFinishedCallback;
use log::{debug, trace};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::request::{Destination, Referrer, RequestBuilder};
use net_traits::{fetch_async, CoreResourceThread, FetchResponseMsg, ResourceThreads};
use parking_lot::{Mutex, ReentrantMutex, RwLock};
use servo_arc::Arc as ServoArc;
use style::computed_values::font_variant_caps::T as FontVariantCaps;
use style::font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, UrlSource};
use style::media_queries::Device;
use style::properties::style_structs::Font as FontStyleStruct;
use style::shared_lock::SharedRwLockReadGuard;
use style::stylesheets::{DocumentStyleSheet, StylesheetInDocument};
use style::Atom;
use url::Url;
use webrender_api::{FontInstanceKey, FontKey};
use crate::font::{
Font, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontGroup, FontRef, FontSearchScope,
};
use crate::font_cache_thread::{
CSSFontFaceDescriptors, FontIdentifier, FontSource, LowercaseString,
};
use crate::font_store::{CrossThreadFontStore, CrossThreadWebRenderFontStore};
use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods};
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
/// The FontContext represents the per-thread/thread state necessary for
/// working with fonts. It is the public API used by the layout and
/// paint code. It talks directly to the font cache thread where
/// required.
pub struct FontContext<S: FontSource> {
font_source: ReentrantMutex<S>,
resource_threads: ReentrantMutex<CoreResourceThread>,
cache: CachingFontSource<S>,
web_fonts: CrossThreadFontStore,
webrender_font_store: CrossThreadWebRenderFontStore,
have_removed_web_fonts: AtomicBool,
}
impl<S: FontSource> MallocSizeOf for FontContext<S> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.cache.size_of(ops)
}
}
impl<S: FontSource> FontContext<S> {
pub fn new(font_source: S, resource_threads: ResourceThreads) -> FontContext<S> {
#[allow(clippy::default_constructed_unit_structs)]
FontContext {
font_source: ReentrantMutex::new(font_source.clone()),
resource_threads: ReentrantMutex::new(resource_threads.core_thread),
cache: CachingFontSource::new(font_source),
web_fonts: Arc::new(RwLock::default()),
webrender_font_store: Arc::new(RwLock::default()),
have_removed_web_fonts: AtomicBool::new(false),
}
}
pub fn web_fonts_still_loading(&self) -> usize {
self.web_fonts.read().number_of_fonts_still_loading()
}
/// Handle the situation where a web font finishes loading, specifying if the load suceeded or failed.
fn handle_web_font_load_finished(
&self,
finished_callback: &WebFontLoadFinishedCallback,
succeeded: bool,
) {
if succeeded {
self.cache.invalidate_after_web_font_load();
}
finished_callback(succeeded);
}
/// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
/// Font groups are cached, so subsequent calls with the same `style` will return a reference
/// to an existing `FontGroup`.
pub fn font_group(&self, style: ServoArc<FontStyleStruct>) -> Arc<RwLock<FontGroup>> {
let font_size = style.font_size.computed_size().into();
self.font_group_with_size(style, font_size)
}
/// Like [`Self::font_group`], but overriding the size found in the [`FontStyleStruct`] with the given size
/// in pixels.
pub fn font_group_with_size(
&self,
style: ServoArc<FontStyleStruct>,
size: Au,
) -> Arc<RwLock<FontGroup>> {
self.cache.font_group_with_size(style, size)
}
/// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a
/// reference to the same underlying `Font`.
pub fn font(
&self,
font_template: FontTemplateRef,
font_descriptor: &FontDescriptor,
) -> Option<FontRef> {
self.get_font_maybe_synthesizing_small_caps(
font_template,
font_descriptor,
true, /* synthesize_small_caps */
)
}
fn get_font_maybe_synthesizing_small_caps(
&self,
font_template: FontTemplateRef,
font_descriptor: &FontDescriptor,
synthesize_small_caps: bool,
) -> Option<FontRef> {
// TODO: (Bug #3463): Currently we only support fake small-caps
// painting. We should also support true small-caps (where the
// font supports it) in the future.
let synthesized_small_caps_font =
if font_descriptor.variant == FontVariantCaps::SmallCaps && synthesize_small_caps {
let mut small_caps_descriptor = font_descriptor.clone();
small_caps_descriptor.pt_size =
font_descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR);
self.get_font_maybe_synthesizing_small_caps(
font_template.clone(),
&small_caps_descriptor,
false, /* synthesize_small_caps */
)
} else {
None
};
let cache_key = FontCacheKey {
font_identifier: font_template.identifier(),
font_descriptor: font_descriptor.clone(),
};
if let Some(font) = self.cache.fonts.read().get(&cache_key).cloned() {
return font;
}
debug!(
"FontContext::font cache miss for font_template={:?} font_descriptor={:?}",
font_template, font_descriptor
);
// TODO: Inserting `None` into the cache here is a bit bogus. Instead we should somehow
// mark this template as invalid so it isn't tried again.
let font = self
.create_font(
font_template,
font_descriptor.to_owned(),
synthesized_small_caps_font,
)
.ok();
self.cache.fonts.write().insert(cache_key, font.clone());
font
}
pub fn matching_templates(
&self,
descriptor_to_match: &FontDescriptor,
family_descriptor: &FontFamilyDescriptor,
) -> Vec<FontTemplateRef> {
// First try to find an appropriate web font that matches this descriptor.
if family_descriptor.scope == FontSearchScope::Any {
let family_name = LowercaseString::from(&family_descriptor.name);
if let Some(templates) = self
.web_fonts
.read()
.families
.get(&family_name)
.map(|templates| templates.find_for_descriptor(Some(descriptor_to_match)))
{
return templates;
}
}
// If not request a matching font from the system font cache.
self.cache
.matching_templates(descriptor_to_match, family_descriptor)
}
/// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the
/// cache thread and a `FontDescriptor` which contains the styling parameters.
fn create_font(
&self,
font_template: FontTemplateRef,
font_descriptor: FontDescriptor,
synthesized_small_caps: Option<FontRef>,
) -> Result<FontRef, &'static str> {
let mut font = Font::new(
font_template.clone(),
font_descriptor.clone(),
synthesized_small_caps,
)?;
let font_source = self.font_source.lock();
font.font_key = match font_template.identifier() {
FontIdentifier::Local(_) => font_source.get_system_font_instance(
font_template.identifier(),
font_descriptor.pt_size,
font.webrender_font_instance_flags(),
),
FontIdentifier::Web(_) => self.webrender_font_store.write().get_font_instance(
&*font_source,
font_template.clone(),
font_descriptor.pt_size,
font.webrender_font_instance_flags(),
),
};
Ok(Arc::new(font))
}
}
#[derive(Clone)]
pub struct WebFontDownloadState {
pub css_font_face_descriptors: Arc<CSSFontFaceDescriptors>,
remaining_sources: Vec<Source>,
finished_callback: WebFontLoadFinishedCallback,
core_resource_thread: CoreResourceThread,
local_fonts: Arc<HashMap<Atom, Option<FontTemplateRef>>>,
pub stylesheet: DocumentStyleSheet,
}
pub trait FontContextWebFontMethods {
fn add_all_web_fonts_from_stylesheet(
&self,
stylesheet: &DocumentStyleSheet,
guard: &SharedRwLockReadGuard,
device: &Device,
finished_callback: WebFontLoadFinishedCallback,
synchronous: bool,
) -> usize;
fn process_next_web_font_source(&self, web_font_download_state: WebFontDownloadState);
fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet);
fn collect_unused_webrender_resources(&self, all: bool)
-> (Vec<FontKey>, Vec<FontInstanceKey>);
}
impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontContext<S>> {
fn add_all_web_fonts_from_stylesheet(
&self,
stylesheet: &DocumentStyleSheet,
guard: &SharedRwLockReadGuard,
device: &Device,
finished_callback: WebFontLoadFinishedCallback,
synchronous: bool,
) -> usize {
let (finished_callback, synchronous_receiver) = if synchronous {
let (sender, receiver) = unbounded();
let finished_callback = move |_succeeded: bool| {
let _ = sender.send(());
};
(
Arc::new(finished_callback) as WebFontLoadFinishedCallback,
Some(receiver),
)
} else {
(finished_callback, None)
};
let mut number_loading = 0;
stylesheet.effective_font_face_rules(device, guard, |rule| {
let font_face = match rule.font_face() {
Some(font_face) => font_face,
None => return,
};
let sources: Vec<Source> = font_face
.sources()
.0
.iter()
.rev()
.filter(is_supported_web_font_source)
.cloned()
.collect();
if sources.is_empty() {
return;
}
// Fetch all local fonts first, beacause if we try to fetch them later on during the process of
// loading the list of web font `src`s we may be running in the context of the router thread, which
// means we won't be able to seend IPC messages to the FontCacheThread.
//
// TODO: This is completely wrong. The specification says that `local()` font-family should match
// against full PostScript names, but this is matching against font family names. This works...
// sometimes.
let mut local_fonts = HashMap::new();
for source in sources.iter() {
if let Source::Local(family_name) = source {
local_fonts
.entry(family_name.name.clone())
.or_insert_with(|| {
let family_name = FontFamilyName::Specific(family_name.name.clone());
self.font_source
.lock()
.find_matching_font_templates(None, &family_name)
.first()
.cloned()
});
}
}
number_loading += 1;
self.web_fonts
.write()
.handle_web_font_load_started_for_stylesheet(stylesheet);
self.process_next_web_font_source(WebFontDownloadState {
css_font_face_descriptors: Arc::new(rule.into()),
remaining_sources: sources,
finished_callback: finished_callback.clone(),
core_resource_thread: self.resource_threads.lock().clone(),
local_fonts: Arc::new(local_fonts),
stylesheet: stylesheet.clone(),
});
// If the load is synchronous wait for it to be signalled.
if let Some(ref synchronous_receiver) = synchronous_receiver {
synchronous_receiver.recv().unwrap();
}
});
number_loading
}
fn process_next_web_font_source(&self, mut state: WebFontDownloadState) {
let Some(source) = state.remaining_sources.pop() else {
self.web_fonts
.write()
.handle_web_font_failed_to_load(&state);
self.handle_web_font_load_finished(&state.finished_callback, false);
return;
};
let this = self.clone();
let web_font_family_name = state.css_font_face_descriptors.family_name.clone();
match source {
Source::Url(url_source) => {
RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state)
},
Source::Local(ref local_family_name) => {
if let Some(new_template) = state
.local_fonts
.get(&local_family_name.name)
.cloned()
.flatten()
.and_then(|local_template| {
FontTemplate::new_for_local_web_font(
local_template,
&state.css_font_face_descriptors,
state.stylesheet.clone(),
)
.ok()
})
{
let not_cancelled = self
.web_fonts
.write()
.handle_web_font_loaded(&state, new_template);
self.handle_web_font_load_finished(&state.finished_callback, not_cancelled);
} else {
this.process_next_web_font_source(state);
}
},
}
}
fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet) {
let mut web_fonts = self.web_fonts.write();
let mut fonts = self.cache.fonts.write();
let mut font_groups = self.cache.resolved_font_groups.write();
// Cancel any currently in-progress web font loads.
web_fonts.handle_stylesheet_removed(stylesheet);
let mut removed_any = false;
for family in web_fonts.families.values_mut() {
removed_any |= family.remove_templates_for_stylesheet(stylesheet);
}
if !removed_any {
return;
};
fonts.retain(|_, font| match font {
Some(font) => font.template.borrow().stylesheet.as_ref() != Some(stylesheet),
_ => true,
});
// Removing this stylesheet modified the available fonts, so invalidate the cache
// of resolved font groups.
font_groups.clear();
// Ensure that we clean up any WebRender resources on the next display list update.
self.have_removed_web_fonts.store(true, Ordering::Relaxed);
}
fn collect_unused_webrender_resources(
&self,
all: bool,
) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
if all {
let mut webrender_font_store = self.webrender_font_store.write();
self.have_removed_web_fonts.store(false, Ordering::Relaxed);
return webrender_font_store.remove_all_fonts();
}
if !self.have_removed_web_fonts.load(Ordering::Relaxed) {
return (Vec::new(), Vec::new());
}
// Lock everything to prevent adding new fonts while we are cleaning up the old ones.
let web_fonts = self.web_fonts.write();
let _fonts = self.cache.fonts.write();
let _font_groups = self.cache.resolved_font_groups.write();
let mut webrender_font_store = self.webrender_font_store.write();
let mut unused_identifiers: HashSet<FontIdentifier> = webrender_font_store
.webrender_font_key_map
.keys()
.cloned()
.collect();
for templates in web_fonts.families.values() {
templates.for_all_identifiers(|identifier| {
unused_identifiers.remove(identifier);
});
}
self.have_removed_web_fonts.store(false, Ordering::Relaxed);
webrender_font_store.remove_all_fonts_for_identifiers(unused_identifiers)
}
}
struct RemoteWebFontDownloader<FCT: FontSource> {
font_context: Arc<FontContext<FCT>>,
url: ServoArc<Url>,
web_font_family_name: LowercaseString,
response_valid: Mutex<bool>,
response_data: Mutex<Vec<u8>>,
}
enum DownloaderResponseResult {
InProcess,
Finished,
Failure,
}
impl<FCT: FontSource + Send + 'static> RemoteWebFontDownloader<FCT> {
fn download(
url_source: UrlSource,
font_context: Arc<FontContext<FCT>>,
web_font_family_name: LowercaseString,
state: WebFontDownloadState,
) {
// https://drafts.csswg.org/css-fonts/#font-fetching-requirements
let url = match url_source.url.url() {
Some(url) => url.clone(),
None => return,
};
// FIXME: This shouldn't use NoReferrer, but the current documents url
let request = RequestBuilder::new(url.clone().into(), Referrer::NoReferrer)
.destination(Destination::Font);
debug!("Loading @font-face {} from {}", web_font_family_name, url);
let downloader = Self {
font_context,
url,
web_font_family_name,
response_valid: Mutex::new(false),
response_data: Mutex::default(),
};
let core_resource_thread_clone = state.core_resource_thread.clone();
fetch_async(
request,
&core_resource_thread_clone,
move |response_message| match downloader.handle_web_font_fetch_message(response_message)
{
DownloaderResponseResult::InProcess => {},
DownloaderResponseResult::Finished => {
if !downloader.process_downloaded_font_and_signal_completion(&state) {
downloader
.font_context
.process_next_web_font_source(state.clone())
}
},
DownloaderResponseResult::Failure => downloader
.font_context
.process_next_web_font_source(state.clone()),
},
)
}
/// After a download finishes, try to process the downloaded data, returning true if
/// the font is added successfully to the [`FontContext`] or false if it isn't.
fn process_downloaded_font_and_signal_completion(&self, state: &WebFontDownloadState) -> bool {
if self
.font_context
.web_fonts
.read()
.font_load_cancelled_for_stylesheet(&state.stylesheet)
{
self.font_context
.handle_web_font_load_finished(&state.finished_callback, false);
// Returning true here prevents trying to load the next font on the source list.
return true;
}
let font_data = std::mem::take(&mut *self.response_data.lock());
trace!(
"@font-face {} data={:?}",
self.web_font_family_name,
font_data
);
let font_data = match fontsan::process(&font_data) {
Ok(bytes) => bytes,
Err(error) => {
debug!(
"Sanitiser rejected web font: family={} url={:?} with {error:?}",
self.web_font_family_name, self.url,
);
return false;
},
};
let Ok(new_template) = FontTemplate::new_for_remote_web_font(
self.url.clone().into(),
Arc::new(font_data),
&state.css_font_face_descriptors,
Some(state.stylesheet.clone()),
) else {
return false;
};
let not_cancelled = self
.font_context
.web_fonts
.write()
.handle_web_font_loaded(state, new_template);
self.font_context
.handle_web_font_load_finished(&state.finished_callback, not_cancelled);
// If the load was canceled above, then we still want to return true from this function in
// order to halt any attempt to load sources that come later on the source list.
true
}
fn handle_web_font_fetch_message(
&self,
response_message: FetchResponseMsg,
) -> DownloaderResponseResult {
match response_message {
FetchResponseMsg::ProcessRequestBody | FetchResponseMsg::ProcessRequestEOF => {
DownloaderResponseResult::InProcess
},
FetchResponseMsg::ProcessResponse(meta_result) => {
trace!(
"@font-face {} metadata ok={:?}",
self.web_font_family_name,
meta_result.is_ok()
);
*self.response_valid.lock() = meta_result.is_ok();
DownloaderResponseResult::InProcess
},
FetchResponseMsg::ProcessResponseChunk(new_bytes) => {
trace!(
"@font-face {} chunk={:?}",
self.web_font_family_name,
new_bytes
);
if *self.response_valid.lock() {
self.response_data.lock().extend(new_bytes)
}
DownloaderResponseResult::InProcess
},
FetchResponseMsg::ProcessResponseEOF(response) => {
trace!(
"@font-face {} EOF={:?}",
self.web_font_family_name,
response
);
if response.is_err() || !*self.response_valid.lock() {
return DownloaderResponseResult::Failure;
}
DownloaderResponseResult::Finished
},
}
}
}
#[derive(Default)]
pub struct CachingFontSource<FCT: FontSource> {
font_cache_thread: ReentrantMutex<FCT>,
fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>,
templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>,
resolved_font_groups:
RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>,
}
impl<FCT: FontSource> CachingFontSource<FCT> {
fn new(font_cache_thread: FCT) -> Self {
Self {
font_cache_thread: ReentrantMutex::new(font_cache_thread),
fonts: Default::default(),
templates: Default::default(),
resolved_font_groups: Default::default(),
}
}
fn invalidate_after_web_font_load(&self) {
self.resolved_font_groups.write().clear();
}
pub fn matching_templates(
&self,
descriptor_to_match: &FontDescriptor,
family_descriptor: &FontFamilyDescriptor,
) -> Vec<FontTemplateRef> {
let cache_key = FontTemplateCacheKey {
font_descriptor: descriptor_to_match.clone(),
family_descriptor: family_descriptor.clone(),
};
if let Some(templates) = self.templates.read().get(&cache_key).cloned() {
return templates;
}
debug!(
"CachingFontSource: cache miss for template_descriptor={:?} family_descriptor={:?}",
descriptor_to_match, family_descriptor
);
let templates = self
.font_cache_thread
.lock()
.find_matching_font_templates(Some(descriptor_to_match), &family_descriptor.name);
self.templates.write().insert(cache_key, templates.clone());
templates
}
pub fn font_group_with_size(
&self,
style: ServoArc<FontStyleStruct>,
size: Au,
) -> Arc<RwLock<FontGroup>> {
let cache_key = FontGroupCacheKey { size, style };
if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
return font_group.clone();
}
let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style)));
self.resolved_font_groups
.write()
.insert(cache_key, font_group.clone());
font_group
}
}
impl<FCT: FontSource> MallocSizeOf for CachingFontSource<FCT> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
let font_cache_size = self
.fonts
.read()
.iter()
.map(|(key, font)| {
key.size_of(ops) + font.as_ref().map_or(0, |font| (*font).size_of(ops))
})
.sum::<usize>();
let font_template_cache_size = self
.templates
.read()
.iter()
.map(|(key, templates)| {
let templates_size = templates
.iter()
.map(|template| template.borrow().size_of(ops))
.sum::<usize>();
key.size_of(ops) + templates_size
})
.sum::<usize>();
let font_group_cache_size = self
.resolved_font_groups
.read()
.iter()
.map(|(key, font_group)| key.size_of(ops) + (*font_group.read()).size_of(ops))
.sum::<usize>();
font_cache_size + font_template_cache_size + font_group_cache_size
}
}
#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
struct FontCacheKey {
font_identifier: FontIdentifier,
font_descriptor: FontDescriptor,
}
#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
struct FontTemplateCacheKey {
font_descriptor: FontDescriptor,
family_descriptor: FontFamilyDescriptor,
}
#[derive(Debug, MallocSizeOf)]
struct FontGroupCacheKey {
#[ignore_malloc_size_of = "This is also stored as part of styling."]
style: ServoArc<FontStyleStruct>,
size: Au,
}
impl PartialEq for FontGroupCacheKey {
fn eq(&self, other: &FontGroupCacheKey) -> bool {
self.style == other.style && self.size == other.size
}
}
impl Eq for FontGroupCacheKey {}
impl Hash for FontGroupCacheKey {
fn hash<H>(&self, hasher: &mut H)
where
H: Hasher,
{
self.style.hash.hash(hasher)
}
}
fn is_supported_web_font_source(source: &&Source) -> bool {
let url_source = match source {
Source::Url(ref url_source) => url_source,
Source::Local(_) => return true,
};
let format_hint = match url_source.format_hint {
Some(ref format_hint) => format_hint,
None => return true,
};
if matches!(
format_hint,
FontFaceSourceFormat::Keyword(
FontFaceSourceFormatKeyword::Truetype |
FontFaceSourceFormatKeyword::Opentype |
FontFaceSourceFormatKeyword::Woff |
FontFaceSourceFormatKeyword::Woff2
)
) {
return true;
}
if let FontFaceSourceFormat::String(string) = format_hint {
return string == "truetype" ||
string == "opentype" ||
string == "woff" ||
string == "woff2";
}
false
}

View file

@ -0,0 +1,396 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use app_units::Au;
use atomic_refcell::AtomicRefCell;
use log::warn;
use parking_lot::RwLock;
use style::stylesheets::DocumentStyleSheet;
use style::values::computed::{FontStyle, FontWeight};
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use crate::font::FontDescriptor;
use crate::font_cache_thread::{FontIdentifier, FontSource, LowercaseString};
use crate::font_context::WebFontDownloadState;
use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique};
#[derive(Default)]
pub struct FontStore {
pub(crate) families: HashMap<LowercaseString, FontTemplates>,
web_fonts_loading: Vec<(DocumentStyleSheet, usize)>,
}
pub(crate) type CrossThreadFontStore = Arc<RwLock<FontStore>>;
impl FontStore {
pub(crate) fn clear(&mut self) {
self.families.clear();
}
pub(crate) fn font_load_cancelled_for_stylesheet(
&self,
stylesheet: &DocumentStyleSheet,
) -> bool {
!self
.web_fonts_loading
.iter()
.any(|(loading_stylesheet, _)| loading_stylesheet == stylesheet)
}
pub(crate) fn handle_stylesheet_removed(&mut self, stylesheet: &DocumentStyleSheet) {
self.web_fonts_loading
.retain(|(loading_stylesheet, _)| loading_stylesheet != stylesheet);
}
pub(crate) fn handle_web_font_load_started_for_stylesheet(
&mut self,
stylesheet: &DocumentStyleSheet,
) {
if let Some((_, count)) = self
.web_fonts_loading
.iter_mut()
.find(|(loading_stylesheet, _)| loading_stylesheet == stylesheet)
{
*count += 1;
} else {
self.web_fonts_loading.push((stylesheet.clone(), 1))
}
}
fn remove_one_web_font_loading_for_stylesheet(&mut self, stylesheet: &DocumentStyleSheet) {
if let Some((_, count)) = self
.web_fonts_loading
.iter_mut()
.find(|(loading_stylesheet, _)| loading_stylesheet == stylesheet)
{
*count -= 1;
}
self.web_fonts_loading.retain(|(_, count)| *count != 0);
}
pub(crate) fn handle_web_font_failed_to_load(&mut self, state: &WebFontDownloadState) {
self.remove_one_web_font_loading_for_stylesheet(&state.stylesheet);
}
/// Handle a web font load finishing, adding the new font to the [`FontStore`]. If the web font
/// load was canceled (for instance, if the stylesheet was removed), then do nothing and return
/// false.
pub(crate) fn handle_web_font_loaded(
&mut self,
state: &WebFontDownloadState,
new_template: FontTemplate,
) -> bool {
// Abort processing this web font if the originating stylesheet was removed.
if self.font_load_cancelled_for_stylesheet(&state.stylesheet) {
return false;
}
let family_name = state.css_font_face_descriptors.family_name.clone();
self.families
.entry(family_name)
.or_default()
.add_template(new_template);
self.remove_one_web_font_loading_for_stylesheet(&state.stylesheet);
true
}
pub(crate) fn number_of_fonts_still_loading(&self) -> usize {
self.web_fonts_loading.iter().map(|(_, count)| count).sum()
}
}
#[derive(Default)]
pub struct WebRenderFontStore {
pub(crate) webrender_font_key_map: HashMap<FontIdentifier, FontKey>,
pub(crate) webrender_font_instance_map: HashMap<(FontKey, Au), FontInstanceKey>,
}
pub(crate) type CrossThreadWebRenderFontStore = Arc<RwLock<WebRenderFontStore>>;
impl WebRenderFontStore {
pub(crate) fn get_font_instance<FCT: FontSource>(
&mut self,
font_cache_thread: &FCT,
font_template: FontTemplateRef,
pt_size: Au,
flags: FontInstanceFlags,
) -> FontInstanceKey {
let webrender_font_key_map = &mut self.webrender_font_key_map;
let identifier = font_template.identifier().clone();
let font_key = *webrender_font_key_map
.entry(identifier.clone())
.or_insert_with(|| {
font_cache_thread.get_web_font(font_template.data(), identifier.index())
});
*self
.webrender_font_instance_map
.entry((font_key, pt_size))
.or_insert_with(|| {
font_cache_thread.get_web_font_instance(font_key, pt_size.to_f32_px(), flags)
})
}
pub(crate) fn remove_all_fonts(&mut self) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
(
self.webrender_font_key_map
.drain()
.map(|(_, key)| key)
.collect(),
self.webrender_font_instance_map
.drain()
.map(|(_, key)| key)
.collect(),
)
}
pub(crate) fn remove_all_fonts_for_identifiers(
&mut self,
identifiers: HashSet<FontIdentifier>,
) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
let mut removed_keys: HashSet<FontKey> = HashSet::new();
self.webrender_font_key_map.retain(|identifier, font_key| {
if identifiers.contains(identifier) {
removed_keys.insert(*font_key);
false
} else {
true
}
});
let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
self.webrender_font_instance_map
.retain(|(font_key, _), instance_key| {
if removed_keys.contains(font_key) {
removed_instance_keys.insert(*instance_key);
false
} else {
true
}
});
(
removed_keys.into_iter().collect(),
removed_instance_keys.into_iter().collect(),
)
}
}
/// A struct that represents the available templates in a "simple family." A simple family
/// is one that contains <= 4 available faces: regular, bold, italic, and bold italic. Having
/// this simple family abstraction makes font matching much faster for families that don't
/// have a complex set of fonts.
///
/// This optimization is taken from:
/// https://searchfox.org/mozilla-central/source/gfx/thebes/gfxFontEntry.cpp.
#[derive(Clone, Debug, Default)]
struct SimpleFamily {
regular: Option<FontTemplateRef>,
bold: Option<FontTemplateRef>,
italic: Option<FontTemplateRef>,
bold_italic: Option<FontTemplateRef>,
}
impl SimpleFamily {
/// Find a font in this family that matches a given descriptor.
fn find_for_descriptor(&self, descriptor_to_match: &FontDescriptor) -> Option<FontTemplateRef> {
let want_bold = descriptor_to_match.weight >= FontWeight::BOLD_THRESHOLD;
let want_italic = descriptor_to_match.style != FontStyle::NORMAL;
// This represents the preference of which font to return from the [`SimpleFamily`],
// given what kind of font we are requesting.
let preference = match (want_bold, want_italic) {
(true, true) => [&self.bold_italic, &self.italic, &self.bold, &self.regular],
(true, false) => [&self.bold, &self.regular, &self.bold_italic, &self.italic],
(false, true) => [&self.italic, &self.bold_italic, &self.regular, &self.bold],
(false, false) => [&self.regular, &self.bold, &self.italic, &self.bold_italic],
};
preference
.iter()
.filter_map(|template| (*template).clone())
.next()
}
fn remove_templates_for_stylesheet(&mut self, stylesheet: &DocumentStyleSheet) {
let remove_if_template_matches = |template: &mut Option<FontTemplateRef>| {
if template.as_ref().map_or(false, |template| {
template.borrow().stylesheet.as_ref() == Some(stylesheet)
}) {
*template = None;
}
};
remove_if_template_matches(&mut self.regular);
remove_if_template_matches(&mut self.bold);
remove_if_template_matches(&mut self.italic);
remove_if_template_matches(&mut self.bold_italic);
}
pub(crate) fn for_all_identifiers(&self, mut callback: impl FnMut(&FontIdentifier)) {
let mut call_if_not_none = |template: &Option<FontTemplateRef>| {
if let Some(template) = template {
callback(&template.identifier())
}
};
call_if_not_none(&self.regular);
call_if_not_none(&self.bold);
call_if_not_none(&self.italic);
call_if_not_none(&self.bold_italic);
}
}
/// A list of font templates that make up a given font family.
#[derive(Clone, Debug)]
pub struct FontTemplates {
pub(crate) templates: Vec<FontTemplateRef>,
simple_family: Option<SimpleFamily>,
}
impl Default for FontTemplates {
fn default() -> Self {
Self {
templates: Default::default(),
simple_family: Some(SimpleFamily::default()),
}
}
}
impl FontTemplates {
/// Find a font in this family that matches a given descriptor.
pub fn find_for_descriptor(
&self,
descriptor_to_match: Option<&FontDescriptor>,
) -> Vec<FontTemplateRef> {
let Some(descriptor_to_match) = descriptor_to_match else {
return self.templates.clone();
};
if self.templates.len() == 1 {
return vec![self.templates[0].clone()];
}
if let Some(template) = self
.simple_family
.as_ref()
.and_then(|simple_family| simple_family.find_for_descriptor(descriptor_to_match))
{
return vec![template];
}
let mut best_templates = Vec::new();
let mut best_distance = f32::MAX;
for template in self.templates.iter() {
let distance = template.descriptor_distance(descriptor_to_match);
if distance < best_distance {
best_templates = vec![template.clone()];
best_distance = distance
} else if distance == best_distance {
best_templates.push(template.clone());
}
}
if !best_templates.is_empty() {
return best_templates;
}
// If a request is made for a font family that exists,
// pick the first valid font in the family if we failed
// to find an exact match for the descriptor.
if let Some(template) = self.templates.first() {
return vec![template.clone()];
}
Vec::new()
}
pub fn add_template(&mut self, new_template: FontTemplate) {
for existing_template in &self.templates {
let existing_template = existing_template.borrow();
if *existing_template.identifier() == new_template.identifier &&
existing_template.descriptor == new_template.descriptor
{
return;
}
}
let new_template = Arc::new(AtomicRefCell::new(new_template));
self.templates.push(new_template.clone());
self.update_simple_family(new_template);
}
fn update_simple_family(&mut self, added_template: FontTemplateRef) {
// If this was detected to not be a simple family before, it cannot ever be one
// in the future.
let Some(simple_family) = self.simple_family.as_mut() else {
return;
};
if self.templates.len() > 4 {
self.simple_family = None;
return;
}
// Variation fonts are never simple families.
if added_template.descriptor().is_variation_font() {
self.simple_family = None;
return;
}
let Some(first) = self.templates.first() else {
warn!("Called before adding any templates.");
return;
};
// If the stretch between any of these fonts differ, it cannot be a simple family nor if this
// font is oblique.
let stretch = added_template.descriptor().stretch.0;
let style = added_template.descriptor().style.0;
if first.descriptor().stretch.0 != stretch || style.is_oblique() {
self.simple_family = None;
return;
}
let weight = added_template.descriptor().weight.0;
let supports_bold = weight >= FontWeight::BOLD_THRESHOLD;
let is_italic = style == FontStyle::ITALIC;
let slot = match (supports_bold, is_italic) {
(true, true) => &mut simple_family.bold_italic,
(true, false) => &mut simple_family.bold,
(false, true) => &mut simple_family.italic,
(false, false) => &mut simple_family.regular,
};
// If the slot was already filled there are two fonts with the same simple properties
// and this isn't a simple family.
if slot.is_some() {
self.simple_family = None;
return;
}
slot.replace(added_template);
}
pub(crate) fn remove_templates_for_stylesheet(
&mut self,
stylesheet: &DocumentStyleSheet,
) -> bool {
let length_before = self.templates.len();
self.templates
.retain(|template| template.borrow().stylesheet.as_ref() != Some(stylesheet));
if let Some(simple_family) = self.simple_family.as_mut() {
simple_family.remove_templates_for_stylesheet(stylesheet);
}
length_before != self.templates.len()
}
pub(crate) fn for_all_identifiers(&self, mut callback: impl FnMut(&FontIdentifier)) {
for template in self.templates.iter() {
callback(&template.borrow().identifier);
}
if let Some(ref simple_family) = self.simple_family {
simple_family.for_all_identifiers(callback)
}
}
}

View file

@ -0,0 +1,573 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fmt::{Debug, Error, Formatter};
use std::ops::RangeInclusive;
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use servo_url::ServoUrl;
use style::computed_values::font_stretch::T as FontStretch;
use style::computed_values::font_style::T as FontStyle;
use style::stylesheets::DocumentStyleSheet;
use style::values::computed::font::FontWeight;
use crate::font::{FontDescriptor, PlatformFontMethods};
use crate::font_cache_thread::{
CSSFontFaceDescriptors, ComputedFontStyleDescriptor, FontIdentifier,
};
use crate::platform::font::PlatformFont;
use crate::platform::font_list::LocalFontIdentifier;
/// A reference to a [`FontTemplate`] with shared ownership and mutability.
pub type FontTemplateRef = Arc<AtomicRefCell<FontTemplate>>;
/// Describes how to select a font from a given family. This is very basic at the moment and needs
/// to be expanded or refactored when we support more of the font styling parameters.
///
/// NB: If you change this, you will need to update `style::properties::compute_font_hash()`.
#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct FontTemplateDescriptor {
pub weight: (FontWeight, FontWeight),
pub stretch: (FontStretch, FontStretch),
pub style: (FontStyle, FontStyle),
#[ignore_malloc_size_of = "MallocSizeOf does not yet support RangeInclusive"]
pub unicode_range: Option<Vec<RangeInclusive<u32>>>,
}
impl Default for FontTemplateDescriptor {
fn default() -> Self {
Self::new(FontWeight::normal(), FontStretch::NORMAL, FontStyle::NORMAL)
}
}
/// FontTemplateDescriptor contains floats, which are not Eq because of NaN. However,
/// we know they will never be NaN, so we can manually implement Eq.
impl Eq for FontTemplateDescriptor {}
impl FontTemplateDescriptor {
#[inline]
pub fn new(weight: FontWeight, stretch: FontStretch, style: FontStyle) -> Self {
Self {
weight: (weight, weight),
stretch: (stretch, stretch),
style: (style, style),
unicode_range: None,
}
}
pub fn is_variation_font(&self) -> bool {
self.weight.0 != self.weight.1 ||
self.stretch.0 != self.stretch.1 ||
self.style.0 != self.style.1
}
/// Returns a score indicating how far apart visually the two font descriptors are. This is
/// used for implmenting the CSS Font Matching algorithm:
/// <https://drafts.csswg.org/css-fonts/#font-matching-algorithm>.
///
/// The smaller the score, the better the fonts match. 0 indicates an exact match. This must
/// be commutative (distance(A, B) == distance(B, A)).
#[inline]
fn distance_from(&self, target: &FontDescriptor) -> f32 {
let stretch_distance = target.stretch.match_distance(&self.stretch);
let style_distance = target.style.match_distance(&self.style);
let weight_distance = target.weight.match_distance(&self.weight);
// Sanity-check that the distances are within the expected range
// (update if implementation of the distance functions is changed).
assert!((0.0..=2000.0).contains(&stretch_distance));
assert!((0.0..=500.0).contains(&style_distance));
assert!((0.0..=1600.0).contains(&weight_distance));
// Factors used to weight the distances between the available and target font
// properties during font-matching. These ensure that we respect the CSS-fonts
// requirement that font-stretch >> font-style >> font-weight; and in addition,
// a mismatch between the desired and actual glyph presentation (emoji vs text)
// will take precedence over any of the style attributes.
//
// Also relevant for font selection is the emoji presentation preference, but this
// is handled later when filtering fonts based on the glyphs they contain.
const STRETCH_FACTOR: f32 = 1.0e8;
const STYLE_FACTOR: f32 = 1.0e4;
const WEIGHT_FACTOR: f32 = 1.0e0;
stretch_distance * STRETCH_FACTOR +
style_distance * STYLE_FACTOR +
weight_distance * WEIGHT_FACTOR
}
fn matches(&self, descriptor_to_match: &FontDescriptor) -> bool {
self.weight.0 <= descriptor_to_match.weight &&
self.weight.1 >= descriptor_to_match.weight &&
self.style.0 <= descriptor_to_match.style &&
self.style.1 >= descriptor_to_match.style &&
self.stretch.0 <= descriptor_to_match.stretch &&
self.stretch.1 >= descriptor_to_match.stretch
}
fn override_values_with_css_font_template_descriptors(
&mut self,
css_font_template_descriptors: &CSSFontFaceDescriptors,
) {
if let Some(weight) = css_font_template_descriptors.weight {
self.weight = weight;
}
self.style = match css_font_template_descriptors.style {
Some(ComputedFontStyleDescriptor::Italic) => (FontStyle::ITALIC, FontStyle::ITALIC),
Some(ComputedFontStyleDescriptor::Normal) => (FontStyle::NORMAL, FontStyle::NORMAL),
Some(ComputedFontStyleDescriptor::Oblique(angle_1, angle_2)) => (
FontStyle::oblique(angle_1.to_float()),
FontStyle::oblique(angle_2.to_float()),
),
None => self.style,
};
if let Some(stretch) = css_font_template_descriptors.stretch {
self.stretch = stretch;
}
if let Some(ref unicode_range) = css_font_template_descriptors.unicode_range {
self.unicode_range = Some(unicode_range.clone());
}
}
}
/// This describes all the information needed to create
/// font instance handles. It contains a unique
/// FontTemplateData structure that is platform specific.
#[derive(Clone)]
pub struct FontTemplate {
pub identifier: FontIdentifier,
pub descriptor: FontTemplateDescriptor,
/// The data to use for this [`FontTemplate`]. For web fonts, this is always filled, but
/// for local fonts, this is loaded only lazily in layout.
pub data: Option<Arc<Vec<u8>>>,
/// If this font is a web font, this is a reference to the stylesheet that
/// created it. This will be used to remove this font from caches, when the
/// stylesheet is removed.
pub stylesheet: Option<DocumentStyleSheet>,
}
impl malloc_size_of::MallocSizeOf for FontTemplate {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
self.identifier.size_of(ops) +
self.descriptor.size_of(ops) +
self.data.as_ref().map_or(0, |data| (*data).size_of(ops))
}
}
impl Debug for FontTemplate {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
self.identifier.fmt(f)
}
}
/// Holds all of the template information for a font that
/// is common, regardless of the number of instances of
/// this font handle per thread.
impl FontTemplate {
/// Create a new [`FontTemplate`] for a system font installed locally.
pub fn new_for_local_font(
identifier: LocalFontIdentifier,
descriptor: FontTemplateDescriptor,
) -> FontTemplate {
FontTemplate {
identifier: FontIdentifier::Local(identifier),
descriptor,
data: None,
stylesheet: None,
}
}
/// Create a new [`FontTemplate`] for a `@font-family` with a `url(...)` `src` font.
pub fn new_for_remote_web_font(
url: ServoUrl,
data: Arc<Vec<u8>>,
css_font_template_descriptors: &CSSFontFaceDescriptors,
stylesheet: Option<DocumentStyleSheet>,
) -> Result<FontTemplate, &'static str> {
let identifier = FontIdentifier::Web(url.clone());
let Ok(handle) = PlatformFont::new_from_data(identifier, data.clone(), 0, None) else {
return Err("Could not initialize platform font data for: {url:?}");
};
let mut descriptor = handle.descriptor();
descriptor
.override_values_with_css_font_template_descriptors(css_font_template_descriptors);
Ok(FontTemplate {
identifier: FontIdentifier::Web(url),
descriptor,
data: Some(data),
stylesheet,
})
}
/// Create a new [`FontTemplate`] for a `@font-family` with a `local(...)` `src`. This takes in
/// the template of the local font and creates a new one that reflects the properties specified
/// by `@font-family` in the stylesheet.
pub fn new_for_local_web_font(
local_template: FontTemplateRef,
css_font_template_descriptors: &CSSFontFaceDescriptors,
stylesheet: DocumentStyleSheet,
) -> Result<FontTemplate, &'static str> {
let mut alias_template = local_template.borrow().clone();
alias_template
.descriptor
.override_values_with_css_font_template_descriptors(css_font_template_descriptors);
alias_template.stylesheet = Some(stylesheet);
Ok(alias_template)
}
pub fn identifier(&self) -> &FontIdentifier {
&self.identifier
}
/// Returns a reference to the bytes in this font if they are in memory.
/// This function never performs disk I/O.
pub fn data_if_in_memory(&self) -> Option<Arc<Vec<u8>>> {
self.data.clone()
}
}
pub trait FontTemplateRefMethods {
/// Returns a reference to the data in this font. This may be a hugely expensive
/// operation (depending on the platform) which performs synchronous disk I/O
/// and should never be done lightly.
fn data(&self) -> Arc<Vec<u8>>;
/// Get the descriptor.
fn descriptor(&self) -> FontTemplateDescriptor;
/// Get the [`FontIdentifier`] for this template.
fn identifier(&self) -> FontIdentifier;
/// Returns true if the given descriptor matches the one in this [`FontTemplate`].
fn matches_font_descriptor(&self, descriptor_to_match: &FontDescriptor) -> bool;
/// Calculate the distance from this [`FontTemplate`]s descriptor and return it
/// or None if this is not a valid [`FontTemplate`].
fn descriptor_distance(&self, descriptor_to_match: &FontDescriptor) -> f32;
/// Whether or not this character is in the unicode ranges specified in
/// this temlates `@font-face` definition, if any.
fn char_in_unicode_range(&self, character: char) -> bool;
}
impl FontTemplateRefMethods for FontTemplateRef {
fn descriptor(&self) -> FontTemplateDescriptor {
self.borrow().descriptor.clone()
}
fn identifier(&self) -> FontIdentifier {
self.borrow().identifier.clone()
}
fn matches_font_descriptor(&self, descriptor_to_match: &FontDescriptor) -> bool {
self.descriptor().matches(descriptor_to_match)
}
fn descriptor_distance(&self, descriptor_to_match: &FontDescriptor) -> f32 {
self.descriptor().distance_from(descriptor_to_match)
}
fn data(&self) -> Arc<Vec<u8>> {
if let Some(data) = self.borrow().data.clone() {
return data;
}
let mut template = self.borrow_mut();
let identifier = template.identifier.clone();
template
.data
.get_or_insert_with(|| match identifier {
FontIdentifier::Local(local_identifier) => {
Arc::new(local_identifier.read_data_from_file())
},
FontIdentifier::Web(_) => unreachable!("Web fonts should always have data."),
})
.clone()
}
fn char_in_unicode_range(&self, character: char) -> bool {
let character = character as u32;
self.borrow()
.descriptor
.unicode_range
.as_ref()
.map_or(true, |ranges| {
ranges.iter().any(|range| range.contains(&character))
})
}
}
/// A trait for implementing the CSS font matching algorithm against various font features.
/// See <https://drafts.csswg.org/css-fonts/#font-matching-algorithm>.
///
/// This implementation is ported from Gecko at:
/// <https://searchfox.org/mozilla-central/rev/0529464f0d2981347ef581f7521ace8b7af7f7ac/gfx/thebes/gfxFontUtils.h#1217>.
trait FontMatchDistanceMethod: Sized {
fn match_distance(&self, range: &(Self, Self)) -> f32;
fn to_float(&self) -> f32;
}
impl FontMatchDistanceMethod for FontStretch {
fn match_distance(&self, range: &(Self, Self)) -> f32 {
// stretch distance ==> [0,2000]
const REVERSE_DISTANCE: f32 = 1000.0;
let min_stretch = range.0;
let max_stretch = range.1;
// The stretch value is a (non-negative) percentage; currently we support
// values in the range 0 .. 1000. (If the upper limit is ever increased,
// the kReverseDistance value used here may need to be adjusted.)
// If aTargetStretch is >100, we prefer larger values if available;
// if <=100, we prefer smaller values if available.
if *self < min_stretch {
if *self > FontStretch::NORMAL {
return min_stretch.to_float() - self.to_float();
}
return (min_stretch.to_float() - self.to_float()) + REVERSE_DISTANCE;
}
if *self > max_stretch {
if *self <= FontStretch::NORMAL {
return self.to_float() - max_stretch.to_float();
}
return (self.to_float() - max_stretch.to_float()) + REVERSE_DISTANCE;
}
0.0
}
fn to_float(&self) -> f32 {
self.0.to_float()
}
}
impl FontMatchDistanceMethod for FontWeight {
// Calculate weight distance with values in the range (0..1000). In general,
// heavier weights match towards even heavier weights while lighter weights
// match towards even lighter weights. Target weight values in the range
// [400..500] are special, since they will first match up to 500, then down
// towards 0, then up again towards 999.
//
// Example: with target 600 and font weight 800, distance will be 200. With
// target 300 and font weight 600, distance will be 900, since heavier
// weights are farther away than lighter weights. If the target is 5 and the
// font weight 995, the distance would be 1590 for the same reason.
fn match_distance(&self, range: &(Self, Self)) -> f32 {
// weight distance ==> [0,1600]
const NOT_WITHIN_CENTRAL_RANGE: f32 = 100.0;
const REVERSE_DISTANCE: f32 = 600.0;
let min_weight = range.0;
let max_weight = range.1;
if *self >= min_weight && *self <= max_weight {
// Target is within the face's range, so it's a perfect match
return 0.0;
}
if *self < FontWeight::NORMAL {
// Requested a lighter-than-400 weight
if max_weight < *self {
return self.to_float() - max_weight.to_float();
}
// Add reverse-search penalty for bolder faces
return (min_weight.to_float() - self.to_float()) + REVERSE_DISTANCE;
}
if *self > FontWeight::from_float(500.) {
// Requested a bolder-than-500 weight
if min_weight > *self {
return min_weight.to_float() - self.to_float();
}
// Add reverse-search penalty for lighter faces
return (self.to_float() - max_weight.to_float()) + REVERSE_DISTANCE;
}
// Special case for requested weight in the [400..500] range
if min_weight > *self {
if min_weight <= FontWeight::from_float(500.) {
// Bolder weight up to 500 is first choice
return min_weight.to_float() - self.to_float();
}
// Other bolder weights get a reverse-search penalty
return (min_weight.to_float() - self.to_float()) + REVERSE_DISTANCE;
}
// Lighter weights are not as good as bolder ones within [400..500]
(self.to_float() - max_weight.to_float()) + NOT_WITHIN_CENTRAL_RANGE
}
fn to_float(&self) -> f32 {
self.value()
}
}
impl FontMatchDistanceMethod for FontStyle {
fn match_distance(&self, range: &(Self, Self)) -> f32 {
// style distance ==> [0,500]
let min_style = range.0;
if *self == min_style {
return 0.0; // styles match exactly ==> 0
}
// bias added to angle difference when searching in the non-preferred
// direction from a target angle
const REVERSE: f32 = 100.0;
// bias added when we've crossed from positive to negative angles or
// vice versa
const NEGATE: f32 = 200.0;
if *self == FontStyle::NORMAL {
if min_style.is_oblique() {
// to distinguish oblique 0deg from normal, we add 1.0 to the angle
let min_angle = min_style.oblique_degrees();
if min_angle >= 0.0 {
return 1.0 + min_angle;
}
let max_style = range.1;
let max_angle = max_style.oblique_degrees();
if max_angle >= 0.0 {
// [min,max] range includes 0.0, so just return our minimum
return 1.0;
}
// negative oblique is even worse than italic
return NEGATE - max_angle;
}
// must be italic, which is worse than any non-negative oblique;
// treat as a match in the wrong search direction
assert!(min_style == FontStyle::ITALIC);
return REVERSE;
}
let default_oblique_angle = FontStyle::OBLIQUE.oblique_degrees();
if *self == FontStyle::ITALIC {
if min_style.is_oblique() {
let min_angle = min_style.oblique_degrees();
if min_angle >= default_oblique_angle {
return 1.0 + (min_angle - default_oblique_angle);
}
let max_style = range.1;
let max_angle = max_style.oblique_degrees();
if max_angle >= default_oblique_angle {
return 1.0;
}
if max_angle > 0.0 {
// wrong direction but still > 0, add bias of 100
return REVERSE + (default_oblique_angle - max_angle);
}
// negative oblique angle, add bias of 300
return REVERSE + NEGATE + (default_oblique_angle - max_angle);
}
// normal is worse than oblique > 0, but better than oblique <= 0
assert!(min_style == FontStyle::NORMAL);
return NEGATE;
}
// target is oblique <angle>: four different cases depending on
// the value of the <angle>, which determines the preferred direction
// of search
let target_angle = self.oblique_degrees();
if target_angle >= default_oblique_angle {
if min_style.is_oblique() {
let min_angle = min_style.oblique_degrees();
if min_angle >= target_angle {
return min_angle - target_angle;
}
let max_style = range.1;
let max_angle = max_style.oblique_degrees();
if max_angle >= target_angle {
return 0.0;
}
if max_angle > 0.0 {
return REVERSE + (target_angle - max_angle);
}
return REVERSE + NEGATE + (target_angle - max_angle);
}
if min_style == FontStyle::ITALIC {
return REVERSE + NEGATE;
}
return REVERSE + NEGATE + 1.0;
}
if target_angle <= -default_oblique_angle {
if min_style.is_oblique() {
let max_style = range.1;
let max_angle = max_style.oblique_degrees();
if max_angle <= target_angle {
return target_angle - max_angle;
}
let min_angle = min_style.oblique_degrees();
if min_angle <= target_angle {
return 0.0;
}
if min_angle < 0.0 {
return REVERSE + (min_angle - target_angle);
}
return REVERSE + NEGATE + (min_angle - target_angle);
}
if min_style == FontStyle::ITALIC {
return REVERSE + NEGATE;
}
return REVERSE + NEGATE + 1.0;
}
if target_angle >= 0.0 {
if min_style.is_oblique() {
let min_angle = min_style.oblique_degrees();
if min_angle > target_angle {
return REVERSE + (min_angle - target_angle);
}
let max_style = range.1;
let max_angle = max_style.oblique_degrees();
if max_angle >= target_angle {
return 0.0;
}
if max_angle > 0.0 {
return target_angle - max_angle;
}
return REVERSE + NEGATE + (target_angle - max_angle);
}
if min_style == FontStyle::ITALIC {
return REVERSE + NEGATE - 2.0;
}
return REVERSE + NEGATE - 1.0;
}
// last case: (targetAngle < 0.0 && targetAngle > kDefaultAngle)
if min_style.is_oblique() {
let max_style = range.1;
let max_angle = max_style.oblique_degrees();
if max_angle < target_angle {
return REVERSE + (target_angle - max_angle);
}
let min_angle = min_style.oblique_degrees();
if min_angle <= target_angle {
return 0.0;
}
if min_angle < 0.0 {
return min_angle - target_angle;
}
return REVERSE + NEGATE + (min_angle - target_angle);
}
if min_style == FontStyle::ITALIC {
return REVERSE + NEGATE - 2.0;
}
REVERSE + NEGATE - 1.0
}
fn to_float(&self) -> f32 {
unimplemented!("Don't know how to convert FontStyle to float.");
}
}
pub(crate) trait IsOblique {
fn is_oblique(&self) -> bool;
}
impl IsOblique for FontStyle {
fn is_oblique(&self) -> bool {
*self != FontStyle::NORMAL && *self != FontStyle::ITALIC
}
}

815
components/fonts/glyph.rs Normal file
View file

@ -0,0 +1,815 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp::{Ordering, PartialOrd};
use std::sync::Arc;
use std::vec::Vec;
use std::{fmt, mem, u16};
use app_units::Au;
use euclid::default::Point2D;
pub use fonts_traits::ByteIndex;
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use range::{self, EachIndex, Range, RangeIndex};
use serde::{Deserialize, Serialize};
/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly.
///
/// In the common case (reasonable glyph advances, no offsets from the font em-box, and one glyph
/// per character), we pack glyph advance, glyph id, and some flags into a single u32.
///
/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or
/// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information
/// in DetailedGlyphStore.
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub struct GlyphEntry {
value: u32,
}
impl GlyphEntry {
fn new(value: u32) -> GlyphEntry {
GlyphEntry { value }
}
fn initial() -> GlyphEntry {
GlyphEntry::new(0)
}
// Creates a GlyphEntry for the common case
fn simple(id: GlyphId, advance: Au) -> GlyphEntry {
assert!(is_simple_glyph_id(id));
assert!(is_simple_advance(advance));
let id_mask = id;
let Au(advance) = advance;
let advance_mask = (advance as u32) << GLYPH_ADVANCE_SHIFT;
GlyphEntry::new(id_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH)
}
// Create a GlyphEntry for uncommon case; should be accompanied by
// initialization of the actual DetailedGlyph data in DetailedGlyphStore
fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: usize) -> GlyphEntry {
assert!(glyph_count <= u16::MAX as usize);
debug!(
"creating complex glyph entry: starts_cluster={}, starts_ligature={}, \
glyph_count={}",
starts_cluster, starts_ligature, glyph_count
);
GlyphEntry::new(glyph_count as u32)
}
fn is_initial(&self) -> bool {
*self == GlyphEntry::initial()
}
}
/// The id of a particular glyph within a font
pub type GlyphId = u32;
// TODO: make this more type-safe.
const FLAG_CHAR_IS_WORD_SEPARATOR: u32 = 0x40000000;
const FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000;
// glyph advance; in Au's.
const GLYPH_ADVANCE_MASK: u32 = 0x3FFF0000;
const GLYPH_ADVANCE_SHIFT: u32 = 16;
const GLYPH_ID_MASK: u32 = 0x0000FFFF;
// Non-simple glyphs (more than one glyph per char; missing glyph,
// newline, tab, large advance, or nonzero x/y offsets) may have one
// or more detailed glyphs associated with them. They are stored in a
// side array so that there is a 1:1 mapping of GlyphEntry to
// unicode char.
// The number of detailed glyphs for this char.
const GLYPH_COUNT_MASK: u32 = 0x0000FFFF;
fn is_simple_glyph_id(id: GlyphId) -> bool {
(id & GLYPH_ID_MASK) == id
}
fn is_simple_advance(advance: Au) -> bool {
advance >= Au(0) && {
let unsigned_au = advance.0 as u32;
(unsigned_au & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT)) == unsigned_au
}
}
// Getters and setters for GlyphEntry. Setter methods are functional,
// because GlyphEntry is immutable and only a u32 in size.
impl GlyphEntry {
#[inline(always)]
fn advance(&self) -> Au {
Au::new(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as i32)
}
#[inline]
fn id(&self) -> GlyphId {
self.value & GLYPH_ID_MASK
}
/// True if the original character was a word separator. These include spaces
/// (U+0020), non-breaking spaces (U+00A0), and a few other characters
/// non-exhaustively listed in the specification. Other characters may map to the same
/// glyphs, but this function does not take mapping into account.
///
/// See <https://drafts.csswg.org/css-text/#word-separator>.
fn char_is_word_separator(&self) -> bool {
self.has_flag(FLAG_CHAR_IS_WORD_SEPARATOR)
}
#[inline(always)]
fn set_char_is_word_separator(&mut self) {
self.value |= FLAG_CHAR_IS_WORD_SEPARATOR;
}
fn glyph_count(&self) -> u16 {
assert!(!self.is_simple());
(self.value & GLYPH_COUNT_MASK) as u16
}
#[inline(always)]
fn is_simple(&self) -> bool {
self.has_flag(FLAG_IS_SIMPLE_GLYPH)
}
#[inline(always)]
fn has_flag(&self, flag: u32) -> bool {
(self.value & flag) != 0
}
}
// Stores data for a detailed glyph, in the case that several glyphs
// correspond to one character, or the glyph's data couldn't be packed.
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
struct DetailedGlyph {
id: GlyphId,
// glyph's advance, in the text's direction (LTR or RTL)
advance: Au,
// glyph's offset from the font's em-box (from top-left)
offset: Point2D<Au>,
}
impl DetailedGlyph {
fn new(id: GlyphId, advance: Au, offset: Point2D<Au>) -> DetailedGlyph {
DetailedGlyph {
id,
advance,
offset,
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
struct DetailedGlyphRecord {
// source string offset/GlyphEntry offset in the TextRun
entry_offset: ByteIndex,
// offset into the detailed glyphs buffer
detail_offset: usize,
}
impl Ord for DetailedGlyphRecord {
fn cmp(&self, other: &DetailedGlyphRecord) -> Ordering {
self.entry_offset.cmp(&other.entry_offset)
}
}
impl PartialOrd for DetailedGlyphRecord {
fn partial_cmp(&self, other: &DetailedGlyphRecord) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// Manages the lookup table for detailed glyphs. Sorting is deferred
// until a lookup is actually performed; this matches the expected
// usage pattern of setting/appending all the detailed glyphs, and
// then querying without setting.
#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
struct DetailedGlyphStore {
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
// optimization.
detail_buffer: Vec<DetailedGlyph>,
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
// optimization.
detail_lookup: Vec<DetailedGlyphRecord>,
lookup_is_sorted: bool,
}
impl<'a> DetailedGlyphStore {
fn new() -> DetailedGlyphStore {
DetailedGlyphStore {
detail_buffer: vec![], // TODO: default size?
detail_lookup: vec![],
lookup_is_sorted: false,
}
}
fn add_detailed_glyphs_for_entry(&mut self, entry_offset: ByteIndex, glyphs: &[DetailedGlyph]) {
let entry = DetailedGlyphRecord {
entry_offset,
detail_offset: self.detail_buffer.len(),
};
debug!(
"Adding entry[off={:?}] for detailed glyphs: {:?}",
entry_offset, glyphs
);
debug_assert!(!self.detail_lookup.contains(&entry));
self.detail_lookup.push(entry);
self.detail_buffer.extend_from_slice(glyphs);
self.lookup_is_sorted = false;
}
fn detailed_glyphs_for_entry(
&'a self,
entry_offset: ByteIndex,
count: u16,
) -> &'a [DetailedGlyph] {
debug!(
"Requesting detailed glyphs[n={}] for entry[off={:?}]",
count, entry_offset
);
// FIXME: Is this right? --pcwalton
// TODO: should fix this somewhere else
if count == 0 {
return &self.detail_buffer[0..0];
}
assert!((count as usize) <= self.detail_buffer.len());
assert!(self.lookup_is_sorted);
let key = DetailedGlyphRecord {
entry_offset,
detail_offset: 0, // unused
};
let i = self
.detail_lookup
.binary_search(&key)
.expect("Invalid index not found in detailed glyph lookup table!");
let main_detail_offset = self.detail_lookup[i].detail_offset;
assert!(main_detail_offset + (count as usize) <= self.detail_buffer.len());
// return a slice into the buffer
&self.detail_buffer[main_detail_offset..main_detail_offset + count as usize]
}
fn detailed_glyph_with_index(
&'a self,
entry_offset: ByteIndex,
detail_offset: u16,
) -> &'a DetailedGlyph {
assert!((detail_offset as usize) <= self.detail_buffer.len());
assert!(self.lookup_is_sorted);
let key = DetailedGlyphRecord {
entry_offset,
detail_offset: 0, // unused
};
let i = self
.detail_lookup
.binary_search(&key)
.expect("Invalid index not found in detailed glyph lookup table!");
let main_detail_offset = self.detail_lookup[i].detail_offset;
assert!(main_detail_offset + (detail_offset as usize) < self.detail_buffer.len());
&self.detail_buffer[main_detail_offset + (detail_offset as usize)]
}
fn ensure_sorted(&mut self) {
if self.lookup_is_sorted {
return;
}
// Sorting a unique vector is surprisingly hard. The following
// code is a good argument for using DVecs, but they require
// immutable locations thus don't play well with freezing.
// Thar be dragons here. You have been warned. (Tips accepted.)
let mut unsorted_records: Vec<DetailedGlyphRecord> = vec![];
mem::swap(&mut self.detail_lookup, &mut unsorted_records);
let mut mut_records: Vec<DetailedGlyphRecord> = unsorted_records;
mut_records.sort_by(|a, b| {
if a < b {
Ordering::Less
} else {
Ordering::Greater
}
});
let mut sorted_records = mut_records;
mem::swap(&mut self.detail_lookup, &mut sorted_records);
self.lookup_is_sorted = true;
}
}
// This struct is used by GlyphStore clients to provide new glyph data.
// It should be allocated on the stack and passed by reference to GlyphStore.
#[derive(Clone, Copy)]
pub struct GlyphData {
id: GlyphId,
advance: Au,
offset: Point2D<Au>,
cluster_start: bool,
ligature_start: bool,
}
impl GlyphData {
/// Creates a new entry for one glyph.
pub fn new(
id: GlyphId,
advance: Au,
offset: Option<Point2D<Au>>,
cluster_start: bool,
ligature_start: bool,
) -> GlyphData {
GlyphData {
id,
advance,
offset: offset.unwrap_or(Point2D::zero()),
cluster_start,
ligature_start,
}
}
}
// This enum is a proxy that's provided to GlyphStore clients when iterating
// through glyphs (either for a particular TextRun offset, or all glyphs).
// Rather than eagerly assembling and copying glyph data, it only retrieves
// values as they are needed from the GlyphStore, using provided offsets.
#[derive(Clone, Copy)]
pub enum GlyphInfo<'a> {
Simple(&'a GlyphStore, ByteIndex),
Detail(&'a GlyphStore, ByteIndex, u16),
}
impl<'a> GlyphInfo<'a> {
pub fn id(self) -> GlyphId {
match self {
GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].id(),
GlyphInfo::Detail(store, entry_i, detail_j) => {
store
.detail_store
.detailed_glyph_with_index(entry_i, detail_j)
.id
},
}
}
#[inline(always)]
// FIXME: Resolution conflicts with IteratorUtil trait so adding trailing _
pub fn advance(self) -> Au {
match self {
GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].advance(),
GlyphInfo::Detail(store, entry_i, detail_j) => {
store
.detail_store
.detailed_glyph_with_index(entry_i, detail_j)
.advance
},
}
}
#[inline]
pub fn offset(self) -> Option<Point2D<Au>> {
match self {
GlyphInfo::Simple(_, _) => None,
GlyphInfo::Detail(store, entry_i, detail_j) => Some(
store
.detail_store
.detailed_glyph_with_index(entry_i, detail_j)
.offset,
),
}
}
pub fn char_is_word_separator(self) -> bool {
let (store, entry_i) = match self {
GlyphInfo::Simple(store, entry_i) => (store, entry_i),
GlyphInfo::Detail(store, entry_i, _) => (store, entry_i),
};
store.char_is_word_separator(entry_i)
}
}
/// Stores the glyph data belonging to a text run.
///
/// Simple glyphs are stored inline in the `entry_buffer`, detailed glyphs are
/// stored as pointers into the `detail_store`.
///
/// ~~~ascii
/// +- GlyphStore --------------------------------+
/// | +---+---+---+---+---+---+---+ |
/// | entry_buffer: | | s | | s | | s | s | | d = detailed
/// | +-|-+---+-|-+---+-|-+---+---+ | s = simple
/// | | | | |
/// | | +---+-------+ |
/// | | | |
/// | +-V-+-V-+ |
/// | detail_store: | d | d | |
/// | +---+---+ |
/// +---------------------------------------------+
/// ~~~
#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
pub struct GlyphStore {
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
// optimization.
/// A buffer of glyphs within the text run, in the order in which they
/// appear in the input text.
/// Any changes will also need to be reflected in
/// transmute_entry_buffer_to_u32_buffer().
entry_buffer: Vec<GlyphEntry>,
/// A store of the detailed glyph data. Detailed glyphs contained in the
/// `entry_buffer` point to locations in this data structure.
detail_store: DetailedGlyphStore,
/// A cache of the advance of the entire glyph store.
total_advance: Au,
/// A cache of the number of word separators in the entire glyph store.
/// See <https://drafts.csswg.org/css-text/#word-separator>.
total_word_separators: usize,
/// Used to check if fast path should be used in glyph iteration.
has_detailed_glyphs: bool,
/// Whether or not this glyph store contains only glyphs for whitespace.
is_whitespace: bool,
/// Whether or not this glyph store contains only a single glyph for a single
/// preserved newline.
is_single_preserved_newline: bool,
is_rtl: bool,
}
impl<'a> GlyphStore {
/// Initializes the glyph store, but doesn't actually shape anything.
///
/// Use the `add_*` methods to store glyph data.
pub fn new(
length: usize,
is_whitespace: bool,
is_single_preserved_newline: bool,
is_rtl: bool,
) -> GlyphStore {
assert!(length > 0);
GlyphStore {
entry_buffer: vec![GlyphEntry::initial(); length],
detail_store: DetailedGlyphStore::new(),
total_advance: Au(0),
total_word_separators: 0,
has_detailed_glyphs: false,
is_whitespace,
is_single_preserved_newline,
is_rtl,
}
}
#[inline]
pub fn total_advance(&self) -> Au {
self.total_advance
}
#[inline]
pub fn len(&self) -> ByteIndex {
ByteIndex(self.entry_buffer.len() as isize)
}
#[inline]
pub fn is_whitespace(&self) -> bool {
self.is_whitespace
}
#[inline]
pub fn total_word_separators(&self) -> usize {
self.total_word_separators
}
pub fn finalize_changes(&mut self) {
self.detail_store.ensure_sorted();
self.cache_total_advance_and_word_separators()
}
#[inline(never)]
fn cache_total_advance_and_word_separators(&mut self) {
let mut total_advance = Au(0);
let mut total_word_separators = 0;
for glyph in self.iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), self.len())) {
total_advance += glyph.advance();
if glyph.char_is_word_separator() {
total_word_separators += 1;
}
}
self.total_advance = total_advance;
self.total_word_separators = total_word_separators;
}
/// Adds a single glyph.
pub fn add_glyph_for_byte_index(&mut self, i: ByteIndex, character: char, data: &GlyphData) {
let glyph_is_compressible = is_simple_glyph_id(data.id) &&
is_simple_advance(data.advance) &&
data.offset == Point2D::zero() &&
data.cluster_start; // others are stored in detail buffer
debug_assert!(data.ligature_start); // can't compress ligature continuation glyphs.
debug_assert!(i < self.len());
let mut entry = if glyph_is_compressible {
GlyphEntry::simple(data.id, data.advance)
} else {
let glyph = &[DetailedGlyph::new(data.id, data.advance, data.offset)];
self.has_detailed_glyphs = true;
self.detail_store.add_detailed_glyphs_for_entry(i, glyph);
GlyphEntry::complex(data.cluster_start, data.ligature_start, 1)
};
// This list is taken from the non-exhaustive list of word separator characters in
// the CSS Text Module Level 3 Spec:
// See https://drafts.csswg.org/css-text/#word-separator
if matches!(
character,
' ' |
'\u{00A0}' | // non-breaking space
'\u{1361}' | // Ethiopic word space
'\u{10100}' | // Aegean word separator
'\u{10101}' | // Aegean word separator
'\u{1039F}' | // Ugartic word divider
'\u{1091F}' // Phoenician word separator
) {
entry.set_char_is_word_separator();
}
self.entry_buffer[i.to_usize()] = entry;
}
pub fn add_glyphs_for_byte_index(&mut self, i: ByteIndex, data_for_glyphs: &[GlyphData]) {
assert!(i < self.len());
assert!(!data_for_glyphs.is_empty());
let glyph_count = data_for_glyphs.len();
let first_glyph_data = data_for_glyphs[0];
let glyphs_vec: Vec<DetailedGlyph> = (0..glyph_count)
.map(|i| {
DetailedGlyph::new(
data_for_glyphs[i].id,
data_for_glyphs[i].advance,
data_for_glyphs[i].offset,
)
})
.collect();
self.has_detailed_glyphs = true;
self.detail_store
.add_detailed_glyphs_for_entry(i, &glyphs_vec);
let entry = GlyphEntry::complex(
first_glyph_data.cluster_start,
first_glyph_data.ligature_start,
glyph_count,
);
debug!(
"Adding multiple glyphs[idx={:?}, count={}]: {:?}",
i, glyph_count, entry
);
self.entry_buffer[i.to_usize()] = entry;
}
#[inline]
pub fn iter_glyphs_for_byte_range(&'a self, range: &Range<ByteIndex>) -> GlyphIterator<'a> {
if range.begin() >= self.len() {
panic!("iter_glyphs_for_range: range.begin beyond length!");
}
if range.end() > self.len() {
panic!("iter_glyphs_for_range: range.end beyond length!");
}
GlyphIterator {
store: self,
byte_index: if self.is_rtl {
range.end()
} else {
range.begin() - ByteIndex(1)
},
byte_range: *range,
glyph_range: None,
}
}
// Scan the glyphs for a given range until we reach a given advance. Returns the index
// and advance of the glyph in the range at the given advance, if reached. Otherwise, returns the
// the number of glyphs and the advance for the given range.
#[inline]
pub fn range_index_of_advance(
&self,
range: &Range<ByteIndex>,
advance: Au,
extra_word_spacing: Au,
) -> (usize, Au) {
let mut index = 0;
let mut current_advance = Au(0);
for glyph in self.iter_glyphs_for_byte_range(range) {
if glyph.char_is_word_separator() {
current_advance += glyph.advance() + extra_word_spacing
} else {
current_advance += glyph.advance()
}
if current_advance > advance {
break;
}
index += 1;
}
(index, current_advance)
}
#[inline]
pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
if range.begin() == ByteIndex(0) && range.end() == self.len() {
self.total_advance + extra_word_spacing * (self.total_word_separators as i32)
} else {
self.advance_for_byte_range_simple_glyphs(range, extra_word_spacing)
}
}
#[inline]
pub fn advance_for_byte_range_simple_glyphs(
&self,
range: &Range<ByteIndex>,
extra_word_spacing: Au,
) -> Au {
self.iter_glyphs_for_byte_range(range)
.fold(Au(0), |advance, glyph| {
if glyph.char_is_word_separator() {
advance + glyph.advance() + extra_word_spacing
} else {
advance + glyph.advance()
}
})
}
pub fn char_is_word_separator(&self, i: ByteIndex) -> bool {
assert!(i < self.len());
self.entry_buffer[i.to_usize()].char_is_word_separator()
}
pub fn word_separator_count_in_range(&self, range: &Range<ByteIndex>) -> u32 {
let mut spaces = 0;
for index in range.each_index() {
if self.char_is_word_separator(index) {
spaces += 1
}
}
spaces
}
}
impl fmt::Debug for GlyphStore {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
writeln!(formatter, "GlyphStore:")?;
let mut detailed_buffer = self.detail_store.detail_buffer.iter();
for entry in self.entry_buffer.iter() {
if entry.is_simple() {
writeln!(
formatter,
" simple id={:?} advance={:?}",
entry.id(),
entry.advance()
)?;
continue;
}
if entry.is_initial() {
continue;
}
write!(formatter, " complex...")?;
if detailed_buffer.next().is_none() {
continue;
}
writeln!(
formatter,
" detailed id={:?} advance={:?}",
entry.id(),
entry.advance()
)?;
}
Ok(())
}
}
/// An iterator over the glyphs in a byte range in a `GlyphStore`.
pub struct GlyphIterator<'a> {
store: &'a GlyphStore,
byte_index: ByteIndex,
byte_range: Range<ByteIndex>,
glyph_range: Option<EachIndex<ByteIndex>>,
}
impl<'a> GlyphIterator<'a> {
// Slow path when there is a glyph range.
#[inline(never)]
fn next_glyph_range(&mut self) -> Option<GlyphInfo<'a>> {
match self.glyph_range.as_mut().unwrap().next() {
Some(j) => {
Some(GlyphInfo::Detail(
self.store,
self.byte_index,
j.get() as u16, /* ??? */
))
},
None => {
// No more glyphs for current character. Try to get another.
self.glyph_range = None;
self.next()
},
}
}
// Slow path when there is a complex glyph.
#[inline(never)]
fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: ByteIndex) -> Option<GlyphInfo<'a>> {
let glyphs = self
.store
.detail_store
.detailed_glyphs_for_entry(i, entry.glyph_count());
self.glyph_range = Some(range::each_index(
ByteIndex(0),
ByteIndex(glyphs.len() as isize),
));
self.next()
}
}
impl<'a> Iterator for GlyphIterator<'a> {
type Item = GlyphInfo<'a>;
// I tried to start with something simpler and apply FlatMap, but the
// inability to store free variables in the FlatMap struct was problematic.
//
// This function consists of the fast path and is designed to be inlined into its caller. The
// slow paths, which should not be inlined, are `next_glyph_range()` and
// `next_complex_glyph()`.
#[inline(always)]
fn next(&mut self) -> Option<GlyphInfo<'a>> {
// Would use 'match' here but it borrows contents in a way that interferes with mutation.
if self.glyph_range.is_some() {
return self.next_glyph_range();
}
// No glyph range. Look at next byte.
self.byte_index = self.byte_index +
if self.store.is_rtl {
ByteIndex(-1)
} else {
ByteIndex(1)
};
let i = self.byte_index;
if !self.byte_range.contains(i) {
return None;
}
debug_assert!(i < self.store.len());
let entry = self.store.entry_buffer[i.to_usize()];
if entry.is_simple() {
Some(GlyphInfo::Simple(self.store, i))
} else {
// Fall back to the slow path.
self.next_complex_glyph(&entry, i)
}
}
}
/// A single series of glyphs within a text run.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GlyphRun {
/// The glyphs.
pub glyph_store: Arc<GlyphStore>,
/// The byte range of characters in the containing run.
pub range: Range<ByteIndex>,
}
impl GlyphRun {
pub fn compare(&self, key: &ByteIndex) -> Ordering {
if *key < self.range.begin() {
Ordering::Greater
} else if *key >= self.range.end() {
Ordering::Less
} else {
Ordering::Equal
}
}
#[inline]
pub fn is_single_preserved_newline(&self) -> bool {
self.glyph_store.is_single_preserved_newline
}
}

89
components/fonts/lib.rs Normal file
View file

@ -0,0 +1,89 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
mod font;
mod font_cache_thread;
mod font_context;
mod font_store;
mod font_template;
mod glyph;
#[allow(unsafe_code)]
pub mod platform;
mod shaper;
pub use font::*;
pub use font_cache_thread::*;
pub use font_context::*;
pub use font_store::*;
pub use font_template::*;
pub use glyph::*;
pub use shaper::*;
use unicode_properties::{emoji, EmojiStatus, UnicodeEmoji};
/// Whether or not font fallback selection prefers the emoji or text representation
/// of a character. If `None` then either presentation is acceptable.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EmojiPresentationPreference {
None,
Text,
Emoji,
}
#[derive(Clone, Copy, Debug)]
pub struct FallbackFontSelectionOptions {
pub character: char,
pub presentation_preference: EmojiPresentationPreference,
}
impl Default for FallbackFontSelectionOptions {
fn default() -> Self {
Self {
character: ' ',
presentation_preference: EmojiPresentationPreference::None,
}
}
}
impl FallbackFontSelectionOptions {
pub fn new(character: char, next_character: Option<char>) -> Self {
let presentation_preference = match next_character {
Some(next_character) if emoji::is_emoji_presentation_selector(next_character) => {
EmojiPresentationPreference::Emoji
},
Some(next_character) if emoji::is_text_presentation_selector(next_character) => {
EmojiPresentationPreference::Text
},
// We don't want to select emoji prsentation for any possible character that might be an emoji, because
// that includes characters such as '0' that are also used outside of emoji clusters. Instead, only
// select the emoji font for characters that explicitly have an emoji presentation (in the absence
// of the emoji presentation selectors above).
_ if matches!(
character.emoji_status(),
EmojiStatus::EmojiPresentation |
EmojiStatus::EmojiPresentationAndModifierBase |
EmojiStatus::EmojiPresentationAndEmojiComponent |
EmojiStatus::EmojiPresentationAndModifierAndEmojiComponent
) =>
{
EmojiPresentationPreference::Emoji
},
_ if character.is_emoji_char() => EmojiPresentationPreference::Text,
_ => EmojiPresentationPreference::None,
};
Self {
character,
presentation_preference,
}
}
}
pub(crate) fn float_to_fixed(before: usize, f: f64) -> i32 {
((1i32 << before) as f64 * f) as i32
}
pub(crate) fn fixed_to_float(before: usize, f: i32) -> f64 {
f as f64 * 1.0f64 / ((1i32 << before) as f64)
}

View file

@ -0,0 +1,580 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fs::File;
use std::io::Read;
use std::path::Path;
use base::text::{is_cjk, UnicodeBlock, UnicodeBlockMethod};
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use style::values::computed::{
FontStretch as StyleFontStretch, FontStyle as StyleFontStyle, FontWeight as StyleFontWeight,
};
use style::Atom;
use super::xml::{Attribute, Node};
use crate::{FallbackFontSelectionOptions, FontTemplate, FontTemplateDescriptor};
lazy_static::lazy_static! {
static ref FONT_LIST: FontList = FontList::new();
}
/// An identifier for a local font on Android systems.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct LocalFontIdentifier {
/// The path to the font.
pub path: Atom,
}
impl LocalFontIdentifier {
pub(crate) fn index(&self) -> u32 {
0
}
pub(crate) fn read_data_from_file(&self) -> Vec<u8> {
let mut bytes = Vec::new();
File::open(Path::new(&*self.path))
.expect("Couldn't open font file!")
.read_to_end(&mut bytes)
.unwrap();
bytes
}
}
// Android doesn't provide an API to query system fonts until Android O:
// https://developer.android.com/reference/android/text/FontConfig.html
// System font configuration files must be parsed until Android O version is set as the minimum target.
// Android uses XML files to handle font mapping configurations.
// On Android API 21+ font mappings are loaded from /etc/fonts.xml.
// Each entry consists of a family with various font names, or a font alias.
// Example:
// <familyset>
// <!-- first font is default -->
// <family name="sans-serif">
// <font weight="100" style="normal">Roboto-Thin.ttf</font>
// <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
// <font weight="300" style="normal">Roboto-Light.ttf</font>
// <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
// <font weight="400" style="normal">Roboto-Regular.ttf</font>
// <font weight="400" style="italic">Roboto-Italic.ttf</font>
// <font weight="500" style="normal">Roboto-Medium.ttf</font>
// <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
// <font weight="900" style="normal">Roboto-Black.ttf</font>
// <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
// <font weight="700" style="normal">Roboto-Bold.ttf</font>
// <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
// </family>//
// <!-- Note that aliases must come after the fonts they reference. -->
// <alias name="sans-serif-thin" to="sans-serif" weight="100" />
// <alias name="sans-serif-light" to="sans-serif" weight="300" />
// <alias name="sans-serif-medium" to="sans-serif" weight="500" />
// <alias name="sans-serif-black" to="sans-serif" weight="900" />
// <alias name="arial" to="sans-serif" />
// <alias name="helvetica" to="sans-serif" />
// <alias name="tahoma" to="sans-serif" />
// <alias name="verdana" to="sans-serif" />
// <family name="sans-serif-condensed">
// <font weight="300" style="normal">RobotoCondensed-Light.ttf</font>
// <font weight="300" style="italic">RobotoCondensed-LightItalic.ttf</font>
// <font weight="400" style="normal">RobotoCondensed-Regular.ttf</font>
// <font weight="400" style="italic">RobotoCondensed-Italic.ttf</font>
// <font weight="700" style="normal">RobotoCondensed-Bold.ttf</font>
// <font weight="700" style="italic">RobotoCondensed-BoldItalic.ttf</font>
// </family>
// <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
// </familyset>
// On Android API 17-20 font mappings are loaded from /system/etc/system_fonts.xml
// Each entry consists of a family with a nameset and a fileset.
// Example:
// <familyset>
// <family>
// <nameset>
// <name>sans-serif</name>
// <name>arial</name>
// <name>helvetica</name>
// <name>tahoma</name>
// <name>verdana</name>
// </nameset>
// <fileset>
// <file>Roboto-Regular.ttf</file>
// <file>Roboto-Bold.ttf</file>
// <file>Roboto-Italic.ttf</file>
// <file>Roboto-BoldItalic.ttf</file>
// </fileset>
// </family>//
// <family>
// <nameset>
// <name>sans-serif-light</name>
// </nameset>
// <fileset>
// <file>Roboto-Light.ttf</file>
// <file>Roboto-LightItalic.ttf</file>
// </fileset>
// </family>//
// <family>
// <nameset>
// <name>sans-serif-thin</name>
// </nameset>
// <fileset>
// <file>Roboto-Thin.ttf</file>
// <file>Roboto-ThinItalic.ttf</file>
// </fileset>
// </family>
// </familyset>
struct Font {
filename: String,
weight: Option<i32>,
style: Option<String>,
}
struct FontFamily {
name: String,
fonts: Vec<Font>,
}
struct FontAlias {
from: String,
to: String,
weight: Option<i32>,
}
struct FontList {
families: Vec<FontFamily>,
aliases: Vec<FontAlias>,
}
impl FontList {
fn new() -> FontList {
// Possible paths containing the font mapping xml file.
let paths = [
"/etc/fonts.xml",
"/system/etc/system_fonts.xml",
"/package/etc/fonts.xml",
];
// Try to load and parse paths until one of them success.
let mut result = None;
paths.iter().all(|path| {
result = Self::from_path(path);
!result.is_some()
});
if result.is_none() {
warn!("Couldn't find font list");
}
match result {
Some(result) => result,
// If no xml mapping file is found fallback to some default
// fonts expected to be on all Android devices.
None => FontList {
families: Self::fallback_font_families(),
aliases: Vec::new(),
},
}
}
// Creates a new FontList from a path to the font mapping xml file.
fn from_path(path: &str) -> Option<FontList> {
let bytes = std::fs::read(path).ok()?;
let nodes = super::xml::parse(&bytes).ok()?;
// find familyset root node
let familyset = nodes.iter().find_map(|e| match e {
Node::Element { name, children, .. } if name.local_name == "familyset" => {
Some(children)
},
_ => None,
})?;
// Parse familyset node
let mut families = Vec::new();
let mut aliases = Vec::new();
for node in familyset {
if let Node::Element {
name,
attributes,
children,
} = node
{
if name.local_name == "family" {
Self::parse_family(children, attributes, &mut families);
} else if name.local_name == "alias" {
// aliases come after the fonts they reference. -->
if !families.is_empty() {
Self::parse_alias(attributes, &mut aliases);
}
}
}
}
Some(FontList {
families: families,
aliases: aliases,
})
}
// Fonts expected to exist in Android devices.
// Only used in the unlikely case where no font xml mapping files are found.
fn fallback_font_families() -> Vec<FontFamily> {
let alternatives = [
("sans-serif", "Roboto-Regular.ttf"),
("Droid Sans", "DroidSans.ttf"),
(
"Lomino",
"/system/etc/ml/kali/Fonts/Lomino/Medium/LominoUI_Md.ttf",
),
];
alternatives
.iter()
.filter(|item| Path::new(&Self::font_absolute_path(item.1)).exists())
.map(|item| FontFamily {
name: item.0.into(),
fonts: vec![Font {
filename: item.1.into(),
weight: None,
style: None,
}],
})
.collect()
}
// All Android fonts are located in /system/fonts
fn font_absolute_path(filename: &str) -> String {
if filename.starts_with("/") {
String::from(filename)
} else {
format!("/system/fonts/{}", filename)
}
}
fn find_family(&self, name: &str) -> Option<&FontFamily> {
self.families.iter().find(|f| f.name == name)
}
fn find_alias(&self, name: &str) -> Option<&FontAlias> {
self.aliases.iter().find(|f| f.from == name)
}
// Parse family and font file names
// Example:
// <family name="sans-serif">
// <font weight="100" style="normal">Roboto-Thin.ttf</font>
// <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
// <font weight="300" style="normal">Roboto-Light.ttf</font>
// <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
// <font weight="400" style="normal">Roboto-Regular.ttf</font>
// </family>
fn parse_family(familyset: &[Node], attrs: &[Attribute], out: &mut Vec<FontFamily>) {
// Fallback to old Android API v17 xml format if required
let using_api_17 = familyset.iter().any(|node| match node {
Node::Element { name, .. } => name.local_name == "nameset",
_ => false,
});
if using_api_17 {
Self::parse_family_v17(familyset, out);
return;
}
// Parse family name
let name = if let Some(name) = Self::find_attrib("name", attrs) {
name
} else {
return;
};
let mut fonts = Vec::new();
// Parse font variants
for node in familyset {
match node {
Node::Element {
name,
attributes,
children,
} => {
if name.local_name == "font" {
FontList::parse_font(&children, attributes, &mut fonts);
}
},
_ => {},
}
}
out.push(FontFamily {
name: name,
fonts: fonts,
});
}
// Parse family and font file names for Androi API < 21
// Example:
// <family>
// <nameset>
// <name>sans-serif</name>
// <name>arial</name>
// <name>helvetica</name>
// <name>tahoma</name>
// <name>verdana</name>
// </nameset>
// <fileset>
// <file>Roboto-Regular.ttf</file>
// <file>Roboto-Bold.ttf</file>
// <file>Roboto-Italic.ttf</file>
// <file>Roboto-BoldItalic.ttf</file>
// </fileset>
// </family>
fn parse_family_v17(familyset: &[Node], out: &mut Vec<FontFamily>) {
let mut nameset = Vec::new();
let mut fileset = Vec::new();
for node in familyset {
if let Node::Element { name, children, .. } = node {
if name.local_name == "nameset" {
Self::collect_contents_with_tag(children, "name", &mut nameset);
} else if name.local_name == "fileset" {
Self::collect_contents_with_tag(children, "file", &mut fileset);
}
}
}
// Create a families for each variation
for name in nameset {
let fonts: Vec<Font> = fileset
.iter()
.map(|f| Font {
filename: f.clone(),
weight: None,
style: None,
})
.collect();
if !fonts.is_empty() {
out.push(FontFamily {
name: name,
fonts: fonts,
})
}
}
}
// Example:
// <font weight="100" style="normal">Roboto-Thin.ttf</font>
fn parse_font(nodes: &[Node], attrs: &[Attribute], out: &mut Vec<Font>) {
// Parse font filename
if let Some(filename) = Self::text_content(nodes) {
// Parse font weight
let weight = Self::find_attrib("weight", attrs).and_then(|w| w.parse().ok());
let style = Self::find_attrib("style", attrs);
out.push(Font {
filename,
weight,
style,
})
}
}
// Example:
// <alias name="sans-serif-thin" to="sans-serif" weight="100" />
// <alias name="sans-serif-light" to="sans-serif" weight="300" />
// <alias name="sans-serif-medium" to="sans-serif" weight="500" />
// <alias name="sans-serif-black" to="sans-serif" weight="900" />
// <alias name="arial" to="sans-serif" />
// <alias name="helvetica" to="sans-serif" />
// <alias name="tahoma" to="sans-serif" />
// <alias name="verdana" to="sans-serif" />
fn parse_alias(attrs: &[Attribute], out: &mut Vec<FontAlias>) {
// Parse alias name and referenced font
let from = match Self::find_attrib("name", attrs) {
Some(from) => from,
_ => {
return;
},
};
// Parse referenced font
let to = match Self::find_attrib("to", attrs) {
Some(to) => to,
_ => {
return;
},
};
// Parse optional weight filter
let weight = Self::find_attrib("weight", attrs).and_then(|w| w.parse().ok());
out.push(FontAlias {
from: from,
to: to,
weight: weight,
})
}
fn find_attrib(name: &str, attrs: &[Attribute]) -> Option<String> {
attrs
.iter()
.find(|attr| attr.name.local_name == name)
.map(|attr| attr.value.clone())
}
fn text_content(nodes: &[Node]) -> Option<String> {
nodes.get(0).and_then(|child| match child {
Node::Text(contents) => Some(contents.trim().into()),
Node::Element { .. } => None,
})
}
fn collect_contents_with_tag(nodes: &[Node], tag: &str, out: &mut Vec<String>) {
for node in nodes {
if let Node::Element { name, children, .. } = node {
if name.local_name == tag {
if let Some(content) = Self::text_content(children) {
out.push(content);
}
}
}
}
}
}
// Functions used by FontCacheThread
pub fn for_each_available_family<F>(mut callback: F)
where
F: FnMut(String),
{
for family in &FONT_LIST.families {
callback(family.name.clone());
}
for alias in &FONT_LIST.aliases {
callback(alias.from.clone());
}
}
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where
F: FnMut(FontTemplate),
{
let mut produce_font = |font: &Font| {
let local_font_identifier = LocalFontIdentifier {
path: Atom::from(FontList::font_absolute_path(&font.filename)),
};
let stretch = StyleFontStretch::NORMAL;
let weight = font
.weight
.map(|w| StyleFontWeight::from_float(w as f32))
.unwrap_or(StyleFontWeight::NORMAL);
let style = match font.style.as_deref() {
Some("italic") => StyleFontStyle::ITALIC,
Some("normal") => StyleFontStyle::NORMAL,
Some(value) => {
warn!(
"unknown value \"{value}\" for \"style\" attribute in the font {}",
font.filename
);
StyleFontStyle::NORMAL
},
None => StyleFontStyle::NORMAL,
};
let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
callback(FontTemplate::new_for_local_font(
local_font_identifier,
descriptor,
));
};
if let Some(family) = FONT_LIST.find_family(family_name) {
for font in &family.fonts {
produce_font(font);
}
return;
}
if let Some(alias) = FONT_LIST.find_alias(family_name) {
if let Some(family) = FONT_LIST.find_family(&alias.to) {
for font in &family.fonts {
match (alias.weight, font.weight) {
(None, _) => produce_font(font),
(Some(w1), Some(w2)) if w1 == w2 => produce_font(font),
_ => {},
}
}
}
}
}
pub fn system_default_family(generic_name: &str) -> Option<String> {
if let Some(family) = FONT_LIST.find_family(&generic_name) {
Some(family.name.clone())
} else if let Some(alias) = FONT_LIST.find_alias(&generic_name) {
Some(alias.from.clone())
} else {
// First font defined in the fonts.xml is the default on Android.
FONT_LIST.families.get(0).map(|family| family.name.clone())
}
}
// Based on gfxAndroidPlatform::GetCommonFallbackFonts() in Gecko
pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = vec![];
if let Some(block) = options.character.block() {
match block {
UnicodeBlock::Armenian => {
families.push("Droid Sans Armenian");
},
UnicodeBlock::Hebrew => {
families.push("Droid Sans Hebrew");
},
UnicodeBlock::Arabic => {
families.push("Droid Sans Arabic");
},
UnicodeBlock::Devanagari => {
families.push("Noto Sans Devanagari");
families.push("Droid Sans Devanagari");
},
UnicodeBlock::Tamil => {
families.push("Noto Sans Tamil");
families.push("Droid Sans Tamil");
},
UnicodeBlock::Thai => {
families.push("Noto Sans Thai");
families.push("Droid Sans Thai");
},
UnicodeBlock::Georgian | UnicodeBlock::GeorgianSupplement => {
families.push("Droid Sans Georgian");
},
UnicodeBlock::Ethiopic | UnicodeBlock::EthiopicSupplement => {
families.push("Droid Sans Ethiopic");
},
_ => {
if is_cjk(options.character) {
families.push("MotoyaLMaru");
families.push("Noto Sans CJK JP");
families.push("Droid Sans Japanese");
}
},
}
}
families.push("Droid Sans Fallback");
families
}
pub static SANS_SERIF_FONT_FAMILY: &'static str = "sans-serif";

View file

@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub(super) use xml::attribute::OwnedAttribute as Attribute;
use xml::reader::XmlEvent::*;
pub(super) enum Node {
Element {
name: xml::name::OwnedName,
attributes: Vec<xml::attribute::OwnedAttribute>,
children: Vec<Node>,
},
Text(String),
}
pub(super) fn parse(bytes: &[u8]) -> xml::reader::Result<Vec<Node>> {
let mut stack = Vec::new();
let mut nodes = Vec::new();
for result in xml::EventReader::new(&*bytes) {
match result? {
StartElement {
name, attributes, ..
} => {
stack.push((name, attributes, nodes));
nodes = Vec::new();
},
EndElement { .. } => {
let (name, attributes, mut parent_nodes) = stack.pop().unwrap();
parent_nodes.push(Node::Element {
name,
attributes,
children: nodes,
});
nodes = parent_nodes;
},
CData(s) | Characters(s) | Whitespace(s) => {
if let Some(Node::Text(text)) = nodes.last_mut() {
text.push_str(&s)
} else {
nodes.push(Node::Text(s))
}
},
StartDocument { .. } | EndDocument | ProcessingInstruction { .. } | Comment(..) => {},
}
}
assert!(stack.is_empty());
Ok(nodes)
}

View file

@ -0,0 +1,550 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::convert::TryInto;
use std::os::raw::c_long;
use std::sync::Arc;
use std::{mem, ptr};
use app_units::Au;
use freetype_sys::{
ft_sfnt_head, ft_sfnt_os2, FT_Byte, FT_Done_Face, FT_Error, FT_F26Dot6, FT_Face, FT_Fixed,
FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Load_Glyph,
FT_Long, FT_MulFix, FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Short,
FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_UShort, FT_Vector, FT_FACE_FLAG_COLOR,
FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, FT_KERNING_DEFAULT, FT_LOAD_COLOR,
FT_LOAD_DEFAULT, FT_STYLE_FLAG_ITALIC, TT_OS2,
};
use log::debug;
use parking_lot::ReentrantMutex;
use style::computed_values::font_stretch::T as FontStretch;
use style::computed_values::font_weight::T as FontWeight;
use style::values::computed::font::FontStyle;
use style::Zero;
use webrender_api::FontInstanceFlags;
use super::library_handle::FreeTypeLibraryHandle;
use crate::font::{
FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, PlatformFontMethods, GPOS, GSUB,
KERN,
};
use crate::font_cache_thread::FontIdentifier;
use crate::font_template::FontTemplateDescriptor;
use crate::glyph::GlyphId;
// This constant is not present in the freetype
// bindings due to bindgen not handling the way
// the macro is defined.
const FT_LOAD_TARGET_LIGHT: FT_UInt = 1 << 16;
/// Convert FreeType-style 26.6 fixed point to an [`f64`].
fn fixed_26_dot_6_to_float(fixed: FT_F26Dot6) -> f64 {
fixed as f64 / 64.0
}
#[derive(Debug)]
pub struct FontTable {
buffer: Vec<u8>,
}
impl FontTableMethods for FontTable {
fn buffer(&self) -> &[u8] {
&self.buffer
}
}
/// Data from the OS/2 table of an OpenType font.
/// See <https://www.microsoft.com/typography/otspec/os2.htm>
#[derive(Debug)]
struct OS2Table {
x_average_char_width: FT_Short,
us_weight_class: FT_UShort,
us_width_class: FT_UShort,
y_strikeout_size: FT_Short,
y_strikeout_position: FT_Short,
sx_height: FT_Short,
}
#[derive(Debug)]
#[allow(unused)]
pub struct PlatformFont {
/// The font data itself, which must stay valid for the lifetime of the
/// platform [`FT_Face`].
font_data: Arc<Vec<u8>>,
face: ReentrantMutex<FT_Face>,
requested_face_size: Au,
actual_face_size: Au,
can_do_fast_shaping: bool,
}
// FT_Face can be used in multiple threads, but from only one thread at a time.
// It's protected with a ReentrantMutex for PlatformFont.
// See https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_face.
unsafe impl Sync for PlatformFont {}
unsafe impl Send for PlatformFont {}
impl Drop for PlatformFont {
fn drop(&mut self) {
let face = self.face.lock();
assert!(!face.is_null());
unsafe {
// The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face`
// should be protected by a mutex.
// See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html.
let _guard = FreeTypeLibraryHandle::get().lock();
if FT_Done_Face(*face) != 0 {
panic!("FT_Done_Face failed");
}
}
}
}
fn create_face(data: Arc<Vec<u8>>, face_index: u32) -> Result<FT_Face, &'static str> {
unsafe {
let mut face: FT_Face = ptr::null_mut();
let library = FreeTypeLibraryHandle::get().lock();
// This is to support 32bit Android where FT_Long is defined as i32.
let face_index = face_index.try_into().unwrap();
let result = FT_New_Memory_Face(
library.freetype_library,
data.as_ptr(),
data.len() as FT_Long,
face_index,
&mut face,
);
if 0 != result || face.is_null() {
return Err("Could not create FreeType face");
}
Ok(face)
}
}
impl PlatformFontMethods for PlatformFont {
fn new_from_data(
_font_identifier: FontIdentifier,
data: Arc<Vec<u8>>,
face_index: u32,
requested_size: Option<Au>,
) -> Result<PlatformFont, &'static str> {
let face = create_face(data.clone(), face_index)?;
let (requested_face_size, actual_face_size) = match requested_size {
Some(requested_size) => (requested_size, face.set_size(requested_size)?),
None => (Au::zero(), Au::zero()),
};
let can_do_fast_shaping =
face.has_table(KERN) && !face.has_table(GPOS) && !face.has_table(GSUB);
Ok(PlatformFont {
face: ReentrantMutex::new(face),
font_data: data,
requested_face_size,
actual_face_size,
can_do_fast_shaping,
})
}
fn descriptor(&self) -> FontTemplateDescriptor {
let face = self.face.lock();
let style = if unsafe { (**face).style_flags & FT_STYLE_FLAG_ITALIC as c_long != 0 } {
FontStyle::ITALIC
} else {
FontStyle::NORMAL
};
let face = self.face.lock();
let os2_table = face.os2_table();
let weight = os2_table
.as_ref()
.map(|os2| FontWeight::from_float(os2.us_weight_class as f32))
.unwrap_or_else(FontWeight::normal);
let stretch = os2_table
.as_ref()
.map(|os2| 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,
})
.unwrap_or(FontStretch::NORMAL);
FontTemplateDescriptor::new(weight, stretch, style)
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
let face = self.face.lock();
assert!(!face.is_null());
unsafe {
let idx = FT_Get_Char_Index(*face, codepoint as FT_ULong);
if idx != 0 as FT_UInt {
Some(idx as GlyphId)
} else {
debug!(
"Invalid codepoint: U+{:04X} ('{}')",
codepoint as u32, codepoint
);
None
}
}
}
fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
let face = self.face.lock();
assert!(!face.is_null());
let mut delta = FT_Vector { x: 0, y: 0 };
unsafe {
FT_Get_Kerning(
*face,
first_glyph,
second_glyph,
FT_KERNING_DEFAULT,
&mut delta,
);
}
fixed_26_dot_6_to_float(delta.x) * self.unscalable_font_metrics_scale()
}
fn can_do_fast_shaping(&self) -> bool {
self.can_do_fast_shaping
}
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
let face = self.face.lock();
assert!(!face.is_null());
let load_flags = face.glyph_load_flags();
let result = unsafe { FT_Load_Glyph(*face, glyph as FT_UInt, load_flags) };
if 0 != result {
debug!("Unable to load glyph {}. reason: {:?}", glyph, result);
return None;
}
let void_glyph = unsafe { (**face).glyph };
let slot: FT_GlyphSlot = void_glyph;
assert!(!slot.is_null());
let advance = unsafe { (*slot).metrics.horiAdvance };
Some(fixed_26_dot_6_to_float(advance) * self.unscalable_font_metrics_scale())
}
fn metrics(&self) -> FontMetrics {
let face_ptr = *self.face.lock();
let face = unsafe { &*face_ptr };
// face.size is a *c_void in the bindings, presumably to avoid recursive structural types
let freetype_size: &FT_SizeRec = unsafe { mem::transmute(&(*face.size)) };
let freetype_metrics: &FT_Size_Metrics = &(freetype_size).metrics;
let mut max_advance;
let mut max_ascent;
let mut max_descent;
let mut line_height;
let mut y_scale = 0.0;
let mut em_height;
if face_ptr.scalable() {
// Prefer FT_Size_Metrics::y_scale to y_ppem as y_ppem does not have subpixel accuracy.
//
// FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its (fractional) value is a
// factor that converts vertical metrics from design units to units of 1/64 pixels, so
// that the result may be interpreted as pixels in 26.6 fixed point format.
//
// This converts the value to a float without losing precision.
y_scale = freetype_metrics.y_scale as f64 / 65535.0 / 64.0;
max_advance = (face.max_advance_width as f64) * y_scale;
max_ascent = (face.ascender as f64) * y_scale;
max_descent = -(face.descender as f64) * y_scale;
line_height = (face.height as f64) * y_scale;
em_height = (face.units_per_EM as f64) * y_scale;
} else {
max_advance = fixed_26_dot_6_to_float(freetype_metrics.max_advance);
max_ascent = fixed_26_dot_6_to_float(freetype_metrics.ascender);
max_descent = -fixed_26_dot_6_to_float(freetype_metrics.descender);
line_height = fixed_26_dot_6_to_float(freetype_metrics.height);
em_height = freetype_metrics.y_ppem as f64;
// FT_Face doc says units_per_EM and a bunch of following fields are "only relevant to
// scalable outlines". If it's an sfnt, we can get units_per_EM from the 'head' table
// instead; otherwise, we don't have a unitsPerEm value so we can't compute y_scale and
// x_scale.
let head = unsafe { FT_Get_Sfnt_Table(face_ptr, ft_sfnt_head) as *mut TtHeader };
if !head.is_null() && unsafe { (*head).table_version != 0xffff } {
// Bug 1267909 - Even if the font is not explicitly scalable, if the face has color
// bitmaps, it should be treated as scalable and scaled to the desired size. Metrics
// based on y_ppem need to be rescaled for the adjusted size.
if face_ptr.color() {
em_height = self.requested_face_size.to_f64_px();
let adjust_scale = em_height / (freetype_metrics.y_ppem as f64);
max_advance *= adjust_scale;
max_descent *= adjust_scale;
max_ascent *= adjust_scale;
line_height *= adjust_scale;
}
let units_per_em = unsafe { (*head).units_per_em } as f64;
y_scale = em_height / units_per_em;
}
}
// 'leading' is supposed to be the vertical distance between two baselines,
// reflected by the height attribute in freetype. On OS X (w/ CTFont),
// leading represents the distance between the bottom of a line descent to
// the top of the next line's ascent or: (line_height - ascent - descent),
// see http://stackoverflow.com/a/5635981 for CTFont implementation.
// Convert using a formula similar to what CTFont returns for consistency.
let leading = line_height - (max_ascent + max_descent);
let underline_size = face.underline_thickness as f64 * y_scale;
let underline_offset = face.underline_position as f64 * y_scale + 0.5;
// The default values for strikeout size and offset. Use OpenType spec's suggested position
// for Roman font as the default for offset.
let mut strikeout_size = underline_size;
let mut strikeout_offset = em_height * 409.0 / 2048.0 + 0.5 * strikeout_size;
// CSS 2.1, section 4.3.2 Lengths: "In the cases where it is
// impossible or impractical to determine the x-height, a value of
// 0.5em should be used."
let mut x_height = 0.5 * em_height;
let mut average_advance = 0.0;
if let Some(os2) = face_ptr.os2_table() {
if !os2.y_strikeout_size.is_zero() && !os2.y_strikeout_position.is_zero() {
strikeout_size = os2.y_strikeout_size as f64 * y_scale;
strikeout_offset = os2.y_strikeout_position as f64 * y_scale;
}
if !os2.sx_height.is_zero() {
x_height = os2.sx_height as f64 * y_scale;
}
if !os2.x_average_char_width.is_zero() {
average_advance = fixed_26_dot_6_to_float(unsafe {
FT_MulFix(
os2.x_average_char_width as FT_F26Dot6,
freetype_metrics.x_scale,
)
});
}
}
if average_advance.is_zero() {
average_advance = self
.glyph_index('0')
.and_then(|idx| self.glyph_h_advance(idx))
.map_or(max_advance, |advance| advance * y_scale);
}
let zero_horizontal_advance = self
.glyph_index('0')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px);
let ic_horizontal_advance = self
.glyph_index('\u{6C34}')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px);
FontMetrics {
underline_size: Au::from_f64_px(underline_size),
underline_offset: Au::from_f64_px(underline_offset),
strikeout_size: Au::from_f64_px(strikeout_size),
strikeout_offset: Au::from_f64_px(strikeout_offset),
leading: Au::from_f64_px(leading),
x_height: Au::from_f64_px(x_height),
em_size: Au::from_f64_px(em_height),
ascent: Au::from_f64_px(max_ascent),
descent: Au::from_f64_px(max_descent),
max_advance: Au::from_f64_px(max_advance),
average_advance: Au::from_f64_px(average_advance),
line_gap: Au::from_f64_px(line_height),
zero_horizontal_advance,
ic_horizontal_advance,
}
}
fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
let face = self.face.lock();
let tag = tag as FT_ULong;
unsafe {
// Get the length
let mut len = 0;
if 0 != FT_Load_Sfnt_Table(*face, tag, 0, ptr::null_mut(), &mut len) {
return None;
}
// Get the bytes
let mut buf = vec![0u8; len as usize];
if 0 != FT_Load_Sfnt_Table(*face, tag, 0, buf.as_mut_ptr(), &mut len) {
return None;
}
Some(FontTable { buffer: buf })
}
}
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
// On other platforms, we only pass this when we know that we are loading a font with
// color characters, but not passing this flag simply *prevents* WebRender from
// loading bitmaps. There's no harm to always passing it.
FontInstanceFlags::EMBEDDED_BITMAPS
}
}
impl PlatformFont {
/// Find the scale to use for metrics of unscalable fonts. Unscalable fonts, those using bitmap
/// glyphs, are scaled after glyph rasterization. In order for metrics to match the final scaled
/// font, we need to scale them based on the final size and the actual font size.
fn unscalable_font_metrics_scale(&self) -> f64 {
self.requested_face_size.to_f64_px() / self.actual_face_size.to_f64_px()
}
}
trait FreeTypeFaceHelpers {
fn scalable(self) -> bool;
fn color(self) -> bool;
fn set_size(self, pt_size: Au) -> Result<Au, &'static str>;
fn glyph_load_flags(self) -> FT_Int32;
fn has_table(self, tag: FontTableTag) -> bool;
fn os2_table(self) -> Option<OS2Table>;
}
impl FreeTypeFaceHelpers for FT_Face {
fn scalable(self) -> bool {
unsafe { (*self).face_flags & FT_FACE_FLAG_SCALABLE as c_long != 0 }
}
fn color(self) -> bool {
unsafe { (*self).face_flags & FT_FACE_FLAG_COLOR as c_long != 0 }
}
fn set_size(self, requested_size: Au) -> Result<Au, &'static str> {
if self.scalable() {
let size_in_fixed_point = (requested_size.to_f64_px() * 64.0 + 0.5) as FT_F26Dot6;
let result = unsafe { FT_Set_Char_Size(self, size_in_fixed_point, 0, 72, 72) };
if 0 != result {
return Err("FT_Set_Char_Size failed");
}
return Ok(requested_size);
}
let requested_size = (requested_size.to_f64_px() * 64.0) as FT_Pos;
let get_size_at_index = |index| unsafe {
(
(*(*self).available_sizes.offset(index as isize)).x_ppem,
(*(*self).available_sizes.offset(index as isize)).y_ppem,
)
};
let mut best_index = 0;
let mut best_size = get_size_at_index(0);
let mut best_dist = best_size.1 - requested_size;
for strike_index in 1..unsafe { (*self).num_fixed_sizes } {
let new_scale = get_size_at_index(strike_index);
let new_distance = new_scale.1 - requested_size;
// Distance is positive if strike is larger than desired size,
// or negative if smaller. If previously a found smaller strike,
// then prefer a larger strike. Otherwise, minimize distance.
if (best_dist < 0 && new_distance >= best_dist) || new_distance.abs() <= best_dist {
best_dist = new_distance;
best_size = new_scale;
best_index = strike_index;
}
}
if 0 == unsafe { FT_Select_Size(self, best_index) } {
Ok(Au::from_f64_px(best_size.1 as f64 / 64.0))
} else {
Err("FT_Select_Size failed")
}
}
fn glyph_load_flags(self) -> FT_Int32 {
let mut load_flags = FT_LOAD_DEFAULT;
// Default to slight hinting, which is what most
// Linux distros use by default, and is a better
// default than no hinting.
// TODO(gw): Make this configurable.
load_flags |= FT_LOAD_TARGET_LIGHT as i32;
let face_flags = unsafe { (*self).face_flags };
if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 {
// We only set FT_LOAD_COLOR if there are bitmap strikes; COLR (color-layer) fonts
// will be handled internally in Servo. In that case WebRender will just be asked to
// paint individual layers.
load_flags |= FT_LOAD_COLOR;
}
load_flags as FT_Int32
}
fn has_table(self, tag: FontTableTag) -> bool {
unsafe { 0 == FT_Load_Sfnt_Table(self, tag as FT_ULong, 0, ptr::null_mut(), &mut 0) }
}
fn os2_table(self) -> Option<OS2Table> {
unsafe {
let os2 = FT_Get_Sfnt_Table(self, ft_sfnt_os2) as *mut TT_OS2;
let valid = !os2.is_null() && (*os2).version != 0xffff;
if !valid {
return None;
}
Some(OS2Table {
x_average_char_width: (*os2).xAvgCharWidth,
us_weight_class: (*os2).usWeightClass,
us_width_class: (*os2).usWidthClass,
y_strikeout_size: (*os2).yStrikeoutSize,
y_strikeout_position: (*os2).yStrikeoutPosition,
sx_height: (*os2).sxHeight,
})
}
}
}
#[repr(C)]
struct TtHeader {
table_version: FT_Fixed,
font_revision: FT_Fixed,
checksum_adjust: FT_Long,
magic_number: FT_Long,
flags: FT_UShort,
units_per_em: FT_UShort,
created: [FT_ULong; 2],
modified: [FT_ULong; 2],
x_min: FT_Short,
y_min: FT_Short,
x_max: FT_Short,
y_max: FT_Short,
mac_style: FT_UShort,
lowest_rec_ppem: FT_UShort,
font_direction: FT_Short,
index_to_loc_format: FT_Short,
glyph_data_format: FT_Short,
}
extern "C" {
fn FT_Load_Sfnt_Table(
face: FT_Face,
tag: FT_ULong,
offset: FT_Long,
buffer: *mut FT_Byte,
length: *mut FT_ULong,
) -> FT_Error;
}

View file

@ -0,0 +1,299 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::convert::TryInto;
use std::ffi::CString;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::ptr;
use base::text::{UnicodeBlock, UnicodeBlockMethod};
use fontconfig_sys::constants::{
FC_FAMILY, FC_FILE, FC_FONTFORMAT, FC_INDEX, FC_SLANT, FC_SLANT_ITALIC, FC_SLANT_OBLIQUE,
FC_WEIGHT, FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABLACK, FC_WEIGHT_REGULAR, FC_WIDTH,
FC_WIDTH_CONDENSED, FC_WIDTH_EXPANDED, FC_WIDTH_EXTRACONDENSED, FC_WIDTH_EXTRAEXPANDED,
FC_WIDTH_NORMAL, FC_WIDTH_SEMICONDENSED, FC_WIDTH_SEMIEXPANDED, FC_WIDTH_ULTRACONDENSED,
FC_WIDTH_ULTRAEXPANDED,
};
use fontconfig_sys::{
FcChar8, FcConfigGetCurrent, FcConfigGetFonts, FcConfigSubstitute, FcDefaultSubstitute,
FcFontMatch, FcFontSetDestroy, FcFontSetList, FcMatchPattern, FcNameParse, FcObjectSetAdd,
FcObjectSetCreate, FcObjectSetDestroy, FcPattern, FcPatternAddString, FcPatternCreate,
FcPatternDestroy, FcPatternGetInteger, FcPatternGetString, FcResultMatch, FcSetSystem,
};
use libc::{c_char, c_int};
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use style::values::computed::{FontStretch, FontStyle, FontWeight};
use style::Atom;
use unicode_script::Script;
use super::c_str_to_string;
use crate::font::map_platform_values_to_style_values;
use crate::font_template::{FontTemplate, FontTemplateDescriptor};
use crate::platform::add_noto_fallback_families;
use crate::{EmojiPresentationPreference, FallbackFontSelectionOptions};
/// An identifier for a local font on systems using Freetype.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct LocalFontIdentifier {
/// The path to the font.
pub path: Atom,
/// The variation index within the font.
pub variation_index: i32,
}
impl LocalFontIdentifier {
pub(crate) fn index(&self) -> u32 {
self.variation_index.try_into().unwrap()
}
pub(crate) fn read_data_from_file(&self) -> Vec<u8> {
let mut bytes = Vec::new();
File::open(Path::new(&*self.path))
.expect("Couldn't open font file!")
.read_to_end(&mut bytes)
.unwrap();
bytes
}
}
pub fn for_each_available_family<F>(mut callback: F)
where
F: FnMut(String),
{
unsafe {
let config = FcConfigGetCurrent();
let font_set = FcConfigGetFonts(config, FcSetSystem);
for i in 0..((*font_set).nfont as isize) {
let font = (*font_set).fonts.offset(i);
let mut family: *mut FcChar8 = ptr::null_mut();
let mut format: *mut FcChar8 = ptr::null_mut();
let mut v: c_int = 0;
if FcPatternGetString(*font, FC_FONTFORMAT.as_ptr() as *mut c_char, v, &mut format) !=
FcResultMatch
{
continue;
}
// Skip bitmap fonts. They aren't supported by FreeType.
let fontformat = c_str_to_string(format as *const c_char);
if fontformat != "TrueType" && fontformat != "CFF" && fontformat != "Type 1" {
continue;
}
while FcPatternGetString(*font, FC_FAMILY.as_ptr() as *mut c_char, v, &mut family) ==
FcResultMatch
{
let family_name = c_str_to_string(family as *const c_char);
callback(family_name);
v += 1;
}
}
}
}
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where
F: FnMut(FontTemplate),
{
unsafe {
let config = FcConfigGetCurrent();
let mut font_set = FcConfigGetFonts(config, FcSetSystem);
let font_set_array_ptr = &mut font_set;
let pattern = FcPatternCreate();
assert!(!pattern.is_null());
let family_name_cstr: CString = CString::new(family_name).unwrap();
let ok = FcPatternAddString(
pattern,
FC_FAMILY.as_ptr() as *mut c_char,
family_name_cstr.as_ptr() as *const FcChar8,
);
assert_ne!(ok, 0);
let object_set = FcObjectSetCreate();
assert!(!object_set.is_null());
FcObjectSetAdd(object_set, FC_FILE.as_ptr() as *mut c_char);
FcObjectSetAdd(object_set, FC_INDEX.as_ptr() as *mut c_char);
FcObjectSetAdd(object_set, FC_WEIGHT.as_ptr() as *mut c_char);
FcObjectSetAdd(object_set, FC_SLANT.as_ptr() as *mut c_char);
FcObjectSetAdd(object_set, FC_WIDTH.as_ptr() as *mut c_char);
let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set);
debug!("Found {} variations for {}", (*matches).nfont, family_name);
for variation_index in 0..((*matches).nfont as isize) {
let font = (*matches).fonts.offset(variation_index);
let mut path: *mut FcChar8 = ptr::null_mut();
let result = FcPatternGetString(*font, FC_FILE.as_ptr() as *mut c_char, 0, &mut path);
assert_eq!(result, FcResultMatch);
let mut index: libc::c_int = 0;
let result =
FcPatternGetInteger(*font, FC_INDEX.as_ptr() as *mut c_char, 0, &mut index);
assert_eq!(result, FcResultMatch);
let Some(weight) = font_weight_from_fontconfig_pattern(*font) else {
continue;
};
let Some(stretch) = font_stretch_from_fontconfig_pattern(*font) else {
continue;
};
let Some(style) = font_style_from_fontconfig_pattern(*font) else {
continue;
};
let local_font_identifier = LocalFontIdentifier {
path: Atom::from(c_str_to_string(path as *const c_char)),
variation_index: index as i32,
};
let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
callback(FontTemplate::new_for_local_font(
local_font_identifier,
descriptor,
))
}
FcFontSetDestroy(matches);
FcPatternDestroy(pattern);
FcObjectSetDestroy(object_set);
}
}
pub fn system_default_family(generic_name: &str) -> Option<String> {
let generic_name_c = CString::new(generic_name).unwrap();
let generic_name_ptr = generic_name_c.as_ptr();
unsafe {
let pattern = FcNameParse(generic_name_ptr as *mut FcChar8);
FcConfigSubstitute(ptr::null_mut(), pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
let mut result = 0;
let family_match = FcFontMatch(ptr::null_mut(), pattern, &mut result);
let family_name = if result == FcResultMatch {
let mut match_string: *mut FcChar8 = ptr::null_mut();
FcPatternGetString(
family_match,
FC_FAMILY.as_ptr() as *mut c_char,
0,
&mut match_string,
);
let result = c_str_to_string(match_string as *const c_char);
FcPatternDestroy(family_match);
Some(result)
} else {
None
};
FcPatternDestroy(pattern);
family_name
}
}
pub static SANS_SERIF_FONT_FAMILY: &str = "DejaVu Sans";
// Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko
pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = Vec::new();
if options.presentation_preference == EmojiPresentationPreference::Emoji {
families.push("Noto Color Emoji");
}
add_noto_fallback_families(options, &mut families);
if matches!(
Script::from(options.character),
Script::Bopomofo | Script::Han
) {
families.push("WenQuanYi Micro Hei");
}
if let Some(block) = options.character.block() {
match block {
UnicodeBlock::HalfwidthandFullwidthForms |
UnicodeBlock::EnclosedIdeographicSupplement => families.push("WenQuanYi Micro Hei"),
UnicodeBlock::Hiragana |
UnicodeBlock::Katakana |
UnicodeBlock::KatakanaPhoneticExtensions => {
families.push("TakaoPGothic");
},
_ => {},
}
}
families.push("DejaVu Serif");
families.push("FreeSerif");
families.push("DejaVu Sans");
families.push("DejaVu Sans Mono");
families.push("FreeSans");
families.push("Symbola");
families.push("Droid Sans Fallback");
families
}
fn font_style_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStyle> {
let mut slant: c_int = 0;
unsafe {
if FcResultMatch != FcPatternGetInteger(pattern, FC_SLANT.as_ptr(), 0, &mut slant) {
return None;
}
}
Some(match slant {
FC_SLANT_ITALIC => FontStyle::ITALIC,
FC_SLANT_OBLIQUE => FontStyle::OBLIQUE,
_ => FontStyle::NORMAL,
})
}
fn font_stretch_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStretch> {
let mut width: c_int = 0;
unsafe {
if FcResultMatch != FcPatternGetInteger(pattern, FC_WIDTH.as_ptr(), 0, &mut width) {
return None;
}
}
let mapping = [
(FC_WIDTH_ULTRACONDENSED as f64, 0.5),
(FC_WIDTH_EXTRACONDENSED as f64, 0.625),
(FC_WIDTH_CONDENSED as f64, 0.75),
(FC_WIDTH_SEMICONDENSED as f64, 0.875),
(FC_WIDTH_NORMAL as f64, 1.0),
(FC_WIDTH_SEMIEXPANDED as f64, 1.125),
(FC_WIDTH_EXPANDED as f64, 1.25),
(FC_WIDTH_EXTRAEXPANDED as f64, 1.50),
(FC_WIDTH_ULTRAEXPANDED as f64, 2.00),
];
let mapped_width = map_platform_values_to_style_values(&mapping, width as f64);
Some(FontStretch::from_percentage(mapped_width as f32))
}
fn font_weight_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontWeight> {
let mut weight: c_int = 0;
unsafe {
let result = FcPatternGetInteger(pattern, FC_WEIGHT.as_ptr(), 0, &mut weight);
if result != FcResultMatch {
return None;
}
}
let mapping = [
(0., 0.),
(FC_WEIGHT_REGULAR as f64, 400_f64),
(FC_WEIGHT_BOLD as f64, 700_f64),
(FC_WEIGHT_EXTRABLACK as f64, 1000_f64),
];
let mapped_weight = map_platform_values_to_style_values(&mapping, weight as f64);
Some(FontWeight::from_float(mapped_weight as f32))
}

View file

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::os::raw::{c_long, c_void};
use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::OnceLock;
use freetype_sys::{
FT_Add_Default_Modules, FT_Done_Library, FT_Library, FT_Memory, FT_MemoryRec, FT_New_Library,
};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use parking_lot::Mutex;
use servo_allocator::libc_compat::{free, malloc, realloc};
use servo_allocator::usable_size;
static FREETYPE_MEMORY_USAGE: AtomicUsize = AtomicUsize::new(0);
static FREETYPE_LIBRARY_HANDLE: OnceLock<Mutex<FreeTypeLibraryHandle>> = OnceLock::new();
extern "C" fn ft_alloc(_: FT_Memory, req_size: c_long) -> *mut c_void {
unsafe {
let pointer = malloc(req_size as usize);
FREETYPE_MEMORY_USAGE.fetch_add(usable_size(pointer), Ordering::Relaxed);
pointer
}
}
extern "C" fn ft_free(_: FT_Memory, pointer: *mut c_void) {
unsafe {
FREETYPE_MEMORY_USAGE.fetch_sub(usable_size(pointer), Ordering::Relaxed);
free(pointer as *mut _);
}
}
extern "C" fn ft_realloc(
_: FT_Memory,
_old_size: c_long,
new_req_size: c_long,
old_pointer: *mut c_void,
) -> *mut c_void {
unsafe {
FREETYPE_MEMORY_USAGE.fetch_sub(usable_size(old_pointer), Ordering::Relaxed);
let new_pointer = realloc(old_pointer, new_req_size as usize);
FREETYPE_MEMORY_USAGE.fetch_add(usable_size(new_pointer), Ordering::Relaxed);
new_pointer
}
}
/// A FreeType library handle to be used for creating and dropping FreeType font faces.
/// It is very important that this handle lives as long as the faces themselves, which
/// is why only one of these is created for the entire execution of Servo and never
/// dropped during execution.
#[derive(Clone, Debug)]
pub(crate) struct FreeTypeLibraryHandle {
pub freetype_library: FT_Library,
freetype_memory: FT_Memory,
}
unsafe impl Sync for FreeTypeLibraryHandle {}
unsafe impl Send for FreeTypeLibraryHandle {}
impl Drop for FreeTypeLibraryHandle {
#[allow(unused)]
fn drop(&mut self) {
assert!(!self.freetype_library.is_null());
unsafe {
FT_Done_Library(self.freetype_library);
Box::from_raw(self.freetype_memory);
}
}
}
impl MallocSizeOf for FreeTypeLibraryHandle {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
unsafe {
FREETYPE_MEMORY_USAGE.load(Ordering::Relaxed) +
ops.malloc_size_of(self.freetype_library as *const _) +
ops.malloc_size_of(self.freetype_memory as *const _)
}
}
}
impl FreeTypeLibraryHandle {
/// Get the shared FreeType library handle. This is protected by a mutex because according to
/// the FreeType documentation:
///
/// > [Since 2.5.6] In multi-threaded applications it is easiest to use one FT_Library object per
/// > thread. In case this is too cumbersome, a single FT_Library object across threads is possible
/// > also, as long as a mutex lock is used around FT_New_Face and FT_Done_Face.
///
/// See <https://freetype.org/freetype2/docs/reference/ft2-library_setup.html>.
pub(crate) fn get() -> &'static Mutex<FreeTypeLibraryHandle> {
FREETYPE_LIBRARY_HANDLE.get_or_init(|| {
let freetype_memory = Box::into_raw(Box::new(FT_MemoryRec {
user: ptr::null_mut(),
alloc: ft_alloc,
free: ft_free,
realloc: ft_realloc,
}));
unsafe {
let mut freetype_library: FT_Library = ptr::null_mut();
let result = FT_New_Library(freetype_memory, &mut freetype_library);
if 0 != result {
panic!("Unable to initialize FreeType library");
}
FT_Add_Default_Modules(freetype_library);
Mutex::new(FreeTypeLibraryHandle {
freetype_library,
freetype_memory,
})
}
})
}
}

View file

@ -0,0 +1,239 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fs::File;
use std::io::Read;
use std::path::Path;
use base::text::{is_cjk, UnicodeBlock, UnicodeBlockMethod};
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use style::values::computed::{
FontStretch as StyleFontStretch, FontStyle as StyleFontStyle, FontWeight as StyleFontWeight,
};
use style::Atom;
use crate::{FallbackFontSelectionOptions, FontTemplate, FontTemplateDescriptor};
lazy_static::lazy_static! {
static ref FONT_LIST: FontList = FontList::new();
}
/// An identifier for a local font on OpenHarmony systems.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct LocalFontIdentifier {
/// The path to the font.
pub path: Atom,
}
impl LocalFontIdentifier {
pub(crate) fn index(&self) -> u32 {
0
}
pub(crate) fn read_data_from_file(&self) -> Vec<u8> {
let mut bytes = Vec::new();
File::open(Path::new(&*self.path))
.expect("Couldn't open font file!")
.read_to_end(&mut bytes)
.unwrap();
bytes
}
}
struct Font {
filename: String,
weight: Option<i32>,
style: Option<String>,
}
struct FontFamily {
name: String,
fonts: Vec<Font>,
}
struct FontAlias {
from: String,
to: String,
weight: Option<i32>,
}
struct FontList {
families: Vec<FontFamily>,
aliases: Vec<FontAlias>,
}
impl FontList {
fn new() -> FontList {
// We don't support parsing `/system/etc/fontconfig.json` yet.
FontList {
families: Self::fallback_font_families(),
aliases: Vec::new(),
}
}
// Fonts expected to exist in OpenHarmony devices.
// Used until parsing of the fontconfig.json file is implemented.
fn fallback_font_families() -> Vec<FontFamily> {
let alternatives = [
("HarmonyOS Sans", "HarmonyOS_Sans_SC_Regular.ttf"),
("sans-serif", "HarmonyOS_Sans_SC_Regular.ttf"),
];
alternatives
.iter()
.filter(|item| Path::new(&Self::font_absolute_path(item.1)).exists())
.map(|item| FontFamily {
name: item.0.into(),
fonts: vec![Font {
filename: item.1.into(),
weight: None,
style: None,
}],
})
.collect()
}
// OHOS fonts are located in /system/fonts
fn font_absolute_path(filename: &str) -> String {
if filename.starts_with("/") {
String::from(filename)
} else {
format!("/system/fonts/{}", filename)
}
}
fn find_family(&self, name: &str) -> Option<&FontFamily> {
self.families.iter().find(|f| f.name == name)
}
fn find_alias(&self, name: &str) -> Option<&FontAlias> {
self.aliases.iter().find(|f| f.from == name)
}
}
// Functions used by FontCacheThread
pub fn for_each_available_family<F>(mut callback: F)
where
F: FnMut(String),
{
for family in &FONT_LIST.families {
callback(family.name.clone());
}
for alias in &FONT_LIST.aliases {
callback(alias.from.clone());
}
}
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where
F: FnMut(FontTemplate),
{
let mut produce_font = |font: &Font| {
let local_font_identifier = LocalFontIdentifier {
path: Atom::from(FontList::font_absolute_path(&font.filename)),
};
let stretch = StyleFontStretch::NORMAL;
let weight = font
.weight
.map(|w| StyleFontWeight::from_float(w as f32))
.unwrap_or(StyleFontWeight::NORMAL);
let style = match font.style.as_deref() {
Some("italic") => StyleFontStyle::ITALIC,
Some("normal") => StyleFontStyle::NORMAL,
Some(value) => {
warn!(
"unknown value \"{value}\" for \"style\" attribute in the font {}",
font.filename
);
StyleFontStyle::NORMAL
},
None => StyleFontStyle::NORMAL,
};
let descriptor = FontTemplateDescriptor::new(weight, stretch, style);
callback(FontTemplate::new_for_local_font(
local_font_identifier,
descriptor,
));
};
if let Some(family) = FONT_LIST.find_family(family_name) {
for font in &family.fonts {
produce_font(font);
}
return;
}
if let Some(alias) = FONT_LIST.find_alias(family_name) {
if let Some(family) = FONT_LIST.find_family(&alias.to) {
for font in &family.fonts {
match (alias.weight, font.weight) {
(None, _) => produce_font(font),
(Some(w1), Some(w2)) if w1 == w2 => produce_font(font),
_ => {},
}
}
}
}
}
pub fn system_default_family(generic_name: &str) -> Option<String> {
if let Some(family) = FONT_LIST.find_family(&generic_name) {
Some(family.name.clone())
} else if let Some(alias) = FONT_LIST.find_alias(&generic_name) {
Some(alias.from.clone())
} else {
FONT_LIST.families.get(0).map(|family| family.name.clone())
}
}
// Based on fonts present in OpenHarmony.
pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = vec![];
if let Some(block) = options.character.block() {
match block {
UnicodeBlock::Hebrew => {
families.push("Noto Sans Hebrew");
},
UnicodeBlock::Arabic => {
families.push("HarmonyOS Sans Naskh Arabic");
},
UnicodeBlock::Devanagari => {
families.push("Noto Sans Devanagari");
},
UnicodeBlock::Tamil => {
families.push("Noto Sans Tamil");
},
UnicodeBlock::Thai => {
families.push("Noto Sans Thai");
},
UnicodeBlock::Georgian | UnicodeBlock::GeorgianSupplement => {
families.push("Noto Sans Georgian");
},
UnicodeBlock::Ethiopic | UnicodeBlock::EthiopicSupplement => {
families.push("Noto Sans Ethiopic");
},
_ => {
if is_cjk(options.character) {
families.push("Noto Sans JP");
families.push("Noto Sans KR");
}
},
}
}
families.push("HarmonyOS Sans");
families
}
pub static SANS_SERIF_FONT_FAMILY: &'static str = "HarmonyOS Sans";

View file

@ -0,0 +1,101 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use app_units::Au;
use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use core_foundation::url::{kCFURLPOSIXPathStyle, CFURL};
use core_graphics::data_provider::CGDataProvider;
use core_graphics::display::CFDictionary;
use core_graphics::font::CGFont;
use core_text::font::CTFont;
use core_text::font_descriptor::kCTFontURLAttribute;
use parking_lot::RwLock;
use crate::font_cache_thread::FontIdentifier;
/// A cache of `CTFont` to avoid having to create `CTFont` instances over and over. It is
/// always possible to create a `CTFont` using a `FontTemplate` even if it isn't in this
/// cache.
pub(crate) struct CoreTextFontCache(OnceLock<RwLock<HashMap<FontIdentifier, CachedCTFont>>>);
/// The global [`CoreTextFontCache`].
static CACHE: CoreTextFontCache = CoreTextFontCache(OnceLock::new());
/// A [`HashMap`] of cached [`CTFont`] for a single [`FontIdentifier`]. There is one [`CTFont`]
/// for each cached font size.
type CachedCTFont = HashMap<Au, CTFont>;
impl CoreTextFontCache {
pub(crate) fn core_text_font(
font_identifier: FontIdentifier,
data: Arc<Vec<u8>>,
pt_size: f64,
) -> Option<CTFont> {
//// If you pass a zero font size to one of the Core Text APIs, it'll replace it with
//// 12.0. We don't want that! (Issue #10492.)
let clamped_pt_size = pt_size.max(0.01);
let au_size = Au::from_f64_px(clamped_pt_size);
let cache = CACHE.0.get_or_init(Default::default);
{
let cache = cache.read();
if let Some(core_text_font) = cache
.get(&font_identifier)
.and_then(|identifier_cache| identifier_cache.get(&au_size))
{
return Some(core_text_font.clone());
}
}
let mut cache = cache.write();
let identifier_cache = cache
.entry(font_identifier.clone())
.or_insert_with(Default::default);
// It could be that between the time of the cache miss above and now, after the write lock
// on the cache has been acquired, the cache was populated with the data that we need. Thus
// check again and return the CTFont if it is is already cached.
if let Some(core_text_font) = identifier_cache.get(&au_size) {
return Some(core_text_font.clone());
}
let core_text_font = match font_identifier {
FontIdentifier::Local(local_font_identifier) => {
// Other platforms can instantiate a platform font by loading the data
// from a file and passing an index in the case the file is a TTC bundle.
// The only way to reliably load the correct font from a TTC bundle on
// macOS is to create the font using a descriptor with both the PostScript
// name and path.
let cf_name = CFString::new(&local_font_identifier.postscript_name);
let mut descriptor = core_text::font_descriptor::new_from_postscript_name(&cf_name);
let cf_path = CFString::new(&local_font_identifier.path);
let url_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontURLAttribute) };
let attributes = CFDictionary::from_CFType_pairs(&[(
url_attribute,
CFURL::from_file_system_path(cf_path, kCFURLPOSIXPathStyle, false),
)]);
if let Ok(descriptor_with_path) =
descriptor.create_copy_with_attributes(attributes.to_untyped())
{
descriptor = descriptor_with_path;
}
core_text::font::new_from_descriptor(&descriptor, clamped_pt_size)
},
FontIdentifier::Web(_) => {
let provider = CGDataProvider::from_buffer(data);
let cgfont = CGFont::from_data_provider(provider).ok()?;
core_text::font::new_from_CGFont(&cgfont, clamped_pt_size)
},
};
identifier_cache.insert(au_size, core_text_font.clone());
Some(core_text_font)
}
}

View file

@ -0,0 +1,375 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp::Ordering;
use std::ops::Range;
use std::sync::Arc;
use std::{fmt, ptr};
/// Implementation of Quartz (CoreGraphics) fonts.
use app_units::Au;
use byteorder::{BigEndian, ByteOrder};
use core_foundation::data::CFData;
use core_foundation::string::UniChar;
use core_graphics::font::CGGlyph;
use core_text::font::CTFont;
use core_text::font_descriptor::{
kCTFontDefaultOrientation, CTFontTraits, SymbolicTraitAccessors, TraitAccessors,
};
use log::debug;
use style::values::computed::font::{FontStretch, FontStyle, FontWeight};
use webrender_api::FontInstanceFlags;
use super::core_text_font_cache::CoreTextFontCache;
use crate::{
map_platform_values_to_style_values, FontIdentifier, FontMetrics, FontTableMethods,
FontTableTag, FontTemplateDescriptor, FractionalPixel, GlyphId, PlatformFontMethods, CBDT,
COLR, GPOS, GSUB, KERN, SBIX,
};
const KERN_PAIR_LEN: usize = 6;
pub struct FontTable {
data: CFData,
}
// assumes 72 points per inch, and 96 px per inch
fn pt_to_px(pt: f64) -> f64 {
pt / 72. * 96.
}
impl FontTable {
pub fn wrap(data: CFData) -> FontTable {
FontTable { data }
}
}
impl FontTableMethods for FontTable {
fn buffer(&self) -> &[u8] {
self.data.bytes()
}
}
#[derive(Debug)]
pub struct PlatformFont {
ctfont: CTFont,
/// A reference to this data used to create this [`PlatformFont`], ensuring the
/// data stays alive of the lifetime of this struct.
_data: Arc<Vec<u8>>,
h_kern_subtable: Option<CachedKernTable>,
can_do_fast_shaping: bool,
}
// From https://developer.apple.com/documentation/coretext:
// > All individual functions in Core Text are thread-safe. Font objects (CTFont,
// > CTFontDescriptor, and associated objects) can be used simultaneously by multiple
// > operations, work queues, or threads. However, the layout objects (CTTypesetter,
// > CTFramesetter, CTRun, CTLine, CTFrame, and associated objects) should be used in a
// > single operation, work queue, or thread.
//
// The other element is a read-only CachedKernTable which is stored in a CFData.
unsafe impl Sync for PlatformFont {}
unsafe impl Send for PlatformFont {}
impl PlatformFont {
/// Cache all the data needed for basic horizontal kerning. This is used only as a fallback or
/// fast path (when the GPOS table is missing or unnecessary) so it needn't handle every case.
fn find_h_kern_subtable(&self) -> Option<CachedKernTable> {
let font_table = self.table_for_tag(KERN)?;
let mut result = CachedKernTable {
font_table,
pair_data_range: 0..0,
px_per_font_unit: 0.0,
};
// Look for a subtable with horizontal kerning in format 0.
// https://www.microsoft.com/typography/otspec/kern.htm
const KERN_COVERAGE_HORIZONTAL_FORMAT_0: u16 = 1;
const SUBTABLE_HEADER_LEN: usize = 6;
const FORMAT_0_HEADER_LEN: usize = 8;
{
let table = result.font_table.buffer();
let version = BigEndian::read_u16(table);
if version != 0 {
return None;
}
let num_subtables = BigEndian::read_u16(&table[2..]);
let mut start = 4;
for _ in 0..num_subtables {
// TODO: Check the subtable version number?
let len = BigEndian::read_u16(&table[start + 2..]) as usize;
let cov = BigEndian::read_u16(&table[start + 4..]);
let end = start + len;
if cov == KERN_COVERAGE_HORIZONTAL_FORMAT_0 {
// Found a matching subtable.
if !result.pair_data_range.is_empty() {
debug!("Found multiple horizontal kern tables. Disable fast path.");
return None;
}
// Read the subtable header.
let subtable_start = start + SUBTABLE_HEADER_LEN;
let n_pairs = BigEndian::read_u16(&table[subtable_start..]) as usize;
let pair_data_start = subtable_start + FORMAT_0_HEADER_LEN;
result.pair_data_range = pair_data_start..end;
if result.pair_data_range.len() != n_pairs * KERN_PAIR_LEN {
debug!("Bad data in kern header. Disable fast path.");
return None;
}
let pt_per_font_unit =
self.ctfont.pt_size() / self.ctfont.units_per_em() as f64;
result.px_per_font_unit = pt_to_px(pt_per_font_unit);
}
start = end;
}
}
if !result.pair_data_range.is_empty() {
Some(result)
} else {
None
}
}
}
struct CachedKernTable {
font_table: FontTable,
pair_data_range: Range<usize>,
px_per_font_unit: f64,
}
impl CachedKernTable {
/// Search for a glyph pair in the kern table and return the corresponding value.
fn binary_search(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> Option<i16> {
let pairs = &self.font_table.buffer()[self.pair_data_range.clone()];
let query = first_glyph << 16 | second_glyph;
let (mut start, mut end) = (0, pairs.len() / KERN_PAIR_LEN);
while start < end {
let i = (start + end) / 2;
let key = BigEndian::read_u32(&pairs[i * KERN_PAIR_LEN..]);
match key.cmp(&query) {
Ordering::Less => start = i + 1,
Ordering::Equal => {
return Some(BigEndian::read_i16(&pairs[i * KERN_PAIR_LEN + 4..]))
},
Ordering::Greater => end = i,
}
}
None
}
}
impl fmt::Debug for CachedKernTable {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "CachedKernTable")
}
}
impl PlatformFontMethods for PlatformFont {
fn new_from_data(
font_identifier: FontIdentifier,
data: Arc<Vec<u8>>,
_face_index: u32,
pt_size: Option<Au>,
) -> Result<PlatformFont, &'static str> {
let size = match pt_size {
Some(s) => s.to_f64_px(),
None => 0.0,
};
let Some(core_text_font) =
CoreTextFontCache::core_text_font(font_identifier, data.clone(), size)
else {
return Err("Could not generate CTFont for FontTemplateData");
};
let mut handle = PlatformFont {
_data: data,
ctfont: core_text_font.clone_with_font_size(size),
h_kern_subtable: None,
can_do_fast_shaping: false,
};
handle.h_kern_subtable = handle.find_h_kern_subtable();
handle.can_do_fast_shaping = handle.h_kern_subtable.is_some() &&
handle.table_for_tag(GPOS).is_none() &&
handle.table_for_tag(GSUB).is_none();
Ok(handle)
}
fn descriptor(&self) -> FontTemplateDescriptor {
let traits = self.ctfont.all_traits();
FontTemplateDescriptor::new(traits.weight(), traits.stretch(), traits.style())
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
// CTFontGetGlyphsForCharacters takes UniChar, which are UTF-16 encoded characters. We are taking
// a char here which is a 32bit Unicode character. This will encode into a maximum of two
// UTF-16 code units and produce a maximum of 1 glyph. We could safely pass 2 as the length
// of the buffer to CTFontGetGlyphsForCharacters, but passing the actual number of encoded
// code units ensures that the resulting glyph is always placed in the first slot in the output
// buffer.
let mut characters: [UniChar; 2] = [0, 0];
let encoded_characters = codepoint.encode_utf16(&mut characters);
let mut glyphs: [CGGlyph; 2] = [0, 0];
let result = unsafe {
self.ctfont.get_glyphs_for_characters(
encoded_characters.as_ptr(),
glyphs.as_mut_ptr(),
encoded_characters.len() as isize,
)
};
// If the call failed or the glyph is the zero glyph no glyph was found for this character.
if !result || glyphs[0] == 0 {
return None;
}
Some(glyphs[0] as GlyphId)
}
fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
if let Some(ref table) = self.h_kern_subtable {
if let Some(font_units) = table.binary_search(first_glyph, second_glyph) {
return font_units as f64 * table.px_per_font_unit;
}
}
0.0
}
fn can_do_fast_shaping(&self) -> bool {
self.can_do_fast_shaping
}
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
let glyphs = [glyph as CGGlyph];
let advance = unsafe {
self.ctfont.get_advances_for_glyphs(
kCTFontDefaultOrientation,
&glyphs[0],
ptr::null_mut(),
1,
)
};
Some(advance as FractionalPixel)
}
fn metrics(&self) -> FontMetrics {
// TODO(mrobinson): Gecko first tries to get metrics from the SFNT tables via
// HarfBuzz and only afterward falls back to platform APIs. We should do something
// similar here. This will likely address issue #201 mentioned below.
let ascent = self.ctfont.ascent();
let descent = self.ctfont.descent();
let leading = self.ctfont.leading();
let x_height = self.ctfont.x_height();
let underline_thickness = self.ctfont.underline_thickness();
let line_gap = (ascent + descent + leading + 0.5).floor();
let max_advance = Au::from_f64_px(self.ctfont.bounding_box().size.width);
let zero_horizontal_advance = self
.glyph_index('0')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px);
let ic_horizontal_advance = self
.glyph_index('\u{6C34}')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px);
let average_advance = zero_horizontal_advance.unwrap_or(max_advance);
let metrics = FontMetrics {
underline_size: Au::from_f64_au(underline_thickness),
// TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table
// directly.
//
// see also: https://bugs.webkit.org/show_bug.cgi?id=16768
// see also: https://bugreports.qt-project.org/browse/QTBUG-13364
underline_offset: Au::from_f64_px(self.ctfont.underline_position()),
// There is no way to get these from CoreText or CoreGraphics APIs, so
// derive them from the other font metrics. These should eventually be
// found in the font tables directly when #201 is fixed.
strikeout_size: Au::from_f64_px(underline_thickness),
strikeout_offset: Au::from_f64_px((x_height + underline_thickness) / 2.0),
leading: Au::from_f64_px(leading),
x_height: Au::from_f64_px(x_height),
em_size: Au::from_f64_px(self.ctfont.pt_size()),
ascent: Au::from_f64_px(ascent),
descent: Au::from_f64_px(descent),
max_advance,
average_advance,
line_gap: Au::from_f64_px(line_gap),
zero_horizontal_advance,
ic_horizontal_advance,
};
debug!(
"Font metrics (@{} pt): {:?}",
self.ctfont.pt_size(),
metrics
);
metrics
}
fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
let result: Option<CFData> = self.ctfont.get_font_table(tag);
result.map(FontTable::wrap)
}
/// Get the necessary [`FontInstanceFlags`]` for this font.
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
// TODO: Should this also validate these tables?
if self.table_for_tag(COLR).is_some() ||
self.table_for_tag(CBDT).is_some() ||
self.table_for_tag(SBIX).is_some()
{
return FontInstanceFlags::EMBEDDED_BITMAPS;
}
FontInstanceFlags::empty()
}
}
pub(super) trait CoreTextFontTraitsMapping {
fn weight(&self) -> FontWeight;
fn style(&self) -> FontStyle;
fn stretch(&self) -> FontStretch;
}
impl CoreTextFontTraitsMapping for CTFontTraits {
fn weight(&self) -> FontWeight {
// From https://developer.apple.com/documentation/coretext/kctfontweighttrait?language=objc
// > The value returned is a CFNumberRef representing a float value between -1.0 and
// > 1.0 for normalized weight. The value of 0.0 corresponds to the regular or
// > medium font weight.
let mapping = [(-1., 0.), (0., 400.), (1., 1000.)];
let mapped_weight = map_platform_values_to_style_values(&mapping, self.normalized_weight());
FontWeight::from_float(mapped_weight as f32)
}
fn style(&self) -> FontStyle {
let slant = self.normalized_slant();
if slant == 0. && self.symbolic_traits().is_italic() {
return FontStyle::ITALIC;
}
if slant == 0. {
return FontStyle::NORMAL;
}
// From https://developer.apple.com/documentation/coretext/kctfontslanttrait?language=objc
// > The value returned is a CFNumberRef object representing a float value
// > between -1.0 and 1.0 for normalized slant angle. The value of 0.0
// > corresponds to 0 degrees clockwise rotation from the vertical and 1.0
// > corresponds to 30 degrees clockwise rotation.
let mapping = [(-1., -30.), (0., 0.), (1., 30.)];
let mapped_slant = map_platform_values_to_style_values(&mapping, slant);
FontStyle::oblique(mapped_slant as f32)
}
fn stretch(&self) -> FontStretch {
// From https://developer.apple.com/documentation/coretext/kctfontwidthtrait?language=objc
// > This value corresponds to the relative interglyph spacing for a given font.
// > The value returned is a CFNumberRef object representing a float between -1.0
// > and 1.0. The value of 0.0 corresponds to regular glyph spacing, and negative
// > values represent condensed glyph spacing.
FontStretch::from_percentage(self.normalized_width() as f32 + 1.0)
}
}

View file

@ -0,0 +1,213 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fs::File;
use std::io::Read;
use std::path::Path;
use base::text::{unicode_plane, UnicodeBlock, UnicodeBlockMethod};
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use style::Atom;
use unicode_script::Script;
use webrender_api::NativeFontHandle;
use crate::platform::add_noto_fallback_families;
use crate::platform::font::CoreTextFontTraitsMapping;
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontTemplate, FontTemplateDescriptor,
};
/// An identifier for a local font on a MacOS system. These values comes from the CoreText
/// CTFontCollection. Note that `path` here is required. We do not load fonts that do not
/// have paths.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct LocalFontIdentifier {
pub postscript_name: Atom,
pub path: Atom,
}
impl LocalFontIdentifier {
pub(crate) fn native_font_handle(&self) -> NativeFontHandle {
NativeFontHandle {
name: self.postscript_name.to_string(),
path: self.path.to_string(),
}
}
pub(crate) fn index(&self) -> u32 {
0
}
pub(crate) fn read_data_from_file(&self) -> Vec<u8> {
let mut bytes = Vec::new();
File::open(Path::new(&*self.path))
.expect("Couldn't open font file!")
.read_to_end(&mut bytes)
.unwrap();
bytes
}
}
pub fn for_each_available_family<F>(mut callback: F)
where
F: FnMut(String),
{
let family_names = core_text::font_collection::get_family_names();
for family_name in family_names.iter() {
callback(family_name.to_string());
}
}
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where
F: FnMut(FontTemplate),
{
debug!("Looking for faces of family: {}", family_name);
let family_collection = core_text::font_collection::create_for_family(family_name);
if let Some(family_collection) = family_collection {
if let Some(family_descriptors) = family_collection.get_descriptors() {
for family_descriptor in family_descriptors.iter() {
let path = family_descriptor.font_path();
let path = match path.as_ref().and_then(|path| path.to_str()) {
Some(path) => path,
None => continue,
};
let traits = family_descriptor.traits();
let descriptor =
FontTemplateDescriptor::new(traits.weight(), traits.stretch(), traits.style());
let identifier = LocalFontIdentifier {
postscript_name: Atom::from(family_descriptor.font_name()),
path: Atom::from(path),
};
callback(FontTemplate::new_for_local_font(identifier, descriptor));
}
}
}
}
pub fn system_default_family(_generic_name: &str) -> Option<String> {
None
}
/// Get the list of fallback fonts given an optional codepoint. This is
/// based on `gfxPlatformMac::GetCommonFallbackFonts()` in Gecko from
/// <https://searchfox.org/mozilla-central/source/gfx/thebes/gfxPlatformMac.cpp>.
pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = Vec::new();
if options.presentation_preference == EmojiPresentationPreference::Emoji {
families.push("Apple Color Emoji");
}
let script = Script::from(options.character);
if let Some(block) = options.character.block() {
match block {
// In most cases, COMMON and INHERITED characters will be merged into
// their context, but if they occur without any specific script context
// we'll just try common default fonts here.
_ if matches!(
script,
Script::Common |
Script::Inherited |
Script::Latin |
Script::Cyrillic |
Script::Greek
) =>
{
families.push("Lucida Grande");
},
// CJK-related script codes are a bit troublesome because of unification;
// we'll probably just get HAN much of the time, so the choice of which
// language font to try for fallback is rather arbitrary. Usually, though,
// we hope that font prefs will have handled this earlier.
_ if matches!(script, Script::Bopomofo | Script::Han) => {
// TODO: Need to differentiate between traditional and simplified Han here!
families.push("Songti SC");
if options.character as u32 > 0x10000 {
// macOS installations with MS Office may have these -ExtB fonts
families.push("SimSun-ExtB");
}
},
UnicodeBlock::Hiragana |
UnicodeBlock::Katakana |
UnicodeBlock::KatakanaPhoneticExtensions => {
families.push("Hiragino Sans");
families.push("Hiragino Kaku Gothic ProN");
},
UnicodeBlock::HangulJamo |
UnicodeBlock::HangulJamoExtendedA |
UnicodeBlock::HangulJamoExtendedB |
UnicodeBlock::HangulCompatibilityJamo |
UnicodeBlock::HangulSyllables => {
families.push("Nanum Gothic");
families.push("Apple SD Gothic Neo");
},
UnicodeBlock::Arabic => families.push("Geeza Pro"),
UnicodeBlock::Armenian => families.push("Mshtakan"),
UnicodeBlock::Bengali => families.push("Bangla Sangam MN"),
UnicodeBlock::Cherokee => families.push("Plantagenet Cherokee"),
UnicodeBlock::Deseret => families.push("Baskerville"),
UnicodeBlock::Devanagari | UnicodeBlock::DevanagariExtended => {
families.push("Devanagari Sangam MN")
},
UnicodeBlock::Ethiopic |
UnicodeBlock::EthiopicExtended |
UnicodeBlock::EthiopicExtendedA |
UnicodeBlock::EthiopicSupplement => families.push("Kefa"),
UnicodeBlock::Georgian | UnicodeBlock::GeorgianSupplement => families.push("Helvetica"),
UnicodeBlock::Gujarati => families.push("Gujarati Sangam MN"),
UnicodeBlock::Gurmukhi => families.push("Gurmukhi MN"),
UnicodeBlock::Hebrew => families.push("Lucida Grande"),
UnicodeBlock::Kannada => families.push("Kannada MN"),
UnicodeBlock::Khmer => families.push("Khmer MN"),
UnicodeBlock::Lao => families.push("Lao MN"),
UnicodeBlock::Malayalam => families.push("Malayalam Sangam MN"),
UnicodeBlock::Myanmar |
UnicodeBlock::MyanmarExtendedA |
UnicodeBlock::MyanmarExtendedB => families.push("Myanmar MN"),
UnicodeBlock::Oriya => families.push("Oriya Sangam MN"),
UnicodeBlock::Sinhala | UnicodeBlock::SinhalaArchaicNumbers => {
families.push("Sinhala Sangam MN")
},
UnicodeBlock::Tamil => families.push("Tamil MN"),
UnicodeBlock::Telugu => families.push("Telugu MN"),
UnicodeBlock::Thaana => {
families.push("Thonburi");
},
UnicodeBlock::Tibetan => families.push("Kailasa"),
UnicodeBlock::UnifiedCanadianAboriginalSyllabics |
UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => {
families.push("Euphemia UCAS")
},
UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => {
families.push("STHeiti");
},
UnicodeBlock::BraillePatterns => families.push("Apple Braille"),
_ => {},
}
}
add_noto_fallback_families(options, &mut families);
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane
let unicode_plane = unicode_plane(options.character);
if let 1 = unicode_plane {
let b = (options.character as u32) >> 8;
if b == 0x27 {
families.push("Zapf Dingbats");
}
families.push("Geneva");
families.push("Apple Symbols");
families.push("STIXGeneral");
families.push("Hiragino Sans");
families.push("Hiragino Kaku Gothic ProN");
}
families.push("Arial Unicode MS");
families
}
pub static SANS_SERIF_FONT_FAMILY: &str = "Helvetica";

View file

@ -0,0 +1,298 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#[cfg(any(target_os = "linux", target_os = "macos"))]
use base::text::{UnicodeBlock, UnicodeBlockMethod};
#[cfg(any(target_os = "linux", target_os = "macos"))]
use unicode_script::Script;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use crate::platform::freetype::{font, font_list, library_handle};
#[cfg(target_os = "macos")]
pub use crate::platform::macos::{core_text_font_cache, font, font_list};
#[cfg(target_os = "windows")]
pub use crate::platform::windows::{font, font_list};
#[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::FallbackFontSelectionOptions;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod freetype {
use std::ffi::CStr;
use std::str;
use libc::c_char;
/// Creates a String from the given null-terminated buffer.
/// Panics if the buffer does not contain UTF-8.
unsafe fn c_str_to_string(s: *const c_char) -> String {
str::from_utf8(CStr::from_ptr(s).to_bytes())
.unwrap()
.to_owned()
}
pub mod font;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
pub mod font_list;
#[cfg(target_os = "android")]
mod android {
pub mod font_list;
mod xml;
}
#[cfg(target_os = "android")]
pub use self::android::font_list;
#[cfg(target_env = "ohos")]
mod ohos {
pub mod font_list;
}
#[cfg(target_env = "ohos")]
pub use self::ohos::font_list;
pub mod library_handle;
}
#[cfg(target_os = "macos")]
mod macos {
pub mod core_text_font_cache;
pub mod font;
pub mod font_list;
}
#[cfg(target_os = "windows")]
mod windows {
pub mod font;
pub mod font_list;
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fn add_noto_fallback_families(
options: FallbackFontSelectionOptions,
families: &mut Vec<&'static str>,
) {
// TODO: Need to differentiate between traditional and simplified Han here!
let add_chinese_families = |families: &mut Vec<&str>| {
families.push("Noto Sans CJK HK");
families.push("Noto Sans CJK SC");
families.push("Noto Sans CJK TC");
families.push("Noto Sans HK");
families.push("Noto Sans SC");
families.push("Noto Sans TC");
};
match Script::from(options.character) {
// In most cases, COMMON and INHERITED characters will be merged into
// their context, but if they occur without any specific script context
// we'll just try common default fonts here.
Script::Common | Script::Inherited | Script::Latin | Script::Cyrillic | Script::Greek => {
families.push("Noto Sans");
},
// CJK-related script codes are a bit troublesome because of unification;
// we'll probably just get HAN much of the time, so the choice of which
// language font to try for fallback is rather arbitrary. Usually, though,
// we hope that font prefs will have handled this earlier.
Script::Bopomofo | Script::Han => add_chinese_families(families),
_ => {},
}
if let Some(block) = options.character.block() {
match block {
UnicodeBlock::HalfwidthandFullwidthForms |
UnicodeBlock::EnclosedIdeographicSupplement => add_chinese_families(families),
UnicodeBlock::Adlam => families.push("Noto Sans Adlam"),
UnicodeBlock::Ahom => families.push("Noto Serif Ahom"),
UnicodeBlock::AnatolianHieroglyphs => families.push("Noto Sans AnatoHiero"),
UnicodeBlock::Arabic |
UnicodeBlock::ArabicExtendedA |
UnicodeBlock::ArabicPresentationFormsA |
UnicodeBlock::ArabicPresentationFormsB => {
families.push("Noto Sans Arabic");
families.push("Noto Naskh Arabic");
},
UnicodeBlock::ArabicMathematicalAlphabeticSymbols => {
families.push("Noto Sans Math");
},
UnicodeBlock::Armenian => families.push("Noto Sans Armenian"),
UnicodeBlock::Avestan => families.push("Noto Sans Avestan"),
UnicodeBlock::Balinese => families.push("Noto Sans Balinese"),
UnicodeBlock::Bamum | UnicodeBlock::BamumSupplement => families.push("Noto Sans Bamum"),
UnicodeBlock::BassaVah => families.push("Noto Sans Bassa Vah"),
UnicodeBlock::Batak => families.push("Noto Sans Batak"),
UnicodeBlock::Bengali => families.push("Noto Sans Bengali"),
UnicodeBlock::Bhaiksuki => families.push("Noto Sans Bhaiksuki"),
UnicodeBlock::Brahmi => families.push("Noto Sans Brahmi"),
UnicodeBlock::BraillePatterns => {
// These characters appear to be in DejaVu Serif.
},
UnicodeBlock::Buginese => families.push("Noto Sans Buginese"),
UnicodeBlock::Buhid => families.push("Noto Sans Buhid"),
UnicodeBlock::Carian => families.push("Noto Sans Carian"),
UnicodeBlock::CaucasianAlbanian => families.push("Noto Sans Caucasian Albanian"),
UnicodeBlock::Chakma => families.push("Noto Sans Chakma"),
UnicodeBlock::Cham => families.push("Noto Sans Cham"),
UnicodeBlock::Cherokee | UnicodeBlock::CherokeeSupplement => {
families.push("Noto Sans Cherokee")
},
UnicodeBlock::Coptic => families.push("Noto Sans Coptic"),
UnicodeBlock::Cuneiform | UnicodeBlock::CuneiformNumbersandPunctuation => {
families.push("Noto Sans Cuneiform")
},
UnicodeBlock::CypriotSyllabary => families.push("Noto Sans Cypriot"),
UnicodeBlock::Deseret => families.push("Noto Sans Deseret"),
UnicodeBlock::Devanagari |
UnicodeBlock::DevanagariExtended |
UnicodeBlock::CommonIndicNumberForms => families.push("Noto Sans Devanagari"),
UnicodeBlock::Duployan => families.push("Noto Sans Duployan"),
UnicodeBlock::EgyptianHieroglyphs => families.push("Noto Sans Egyptian Hieroglyphs"),
UnicodeBlock::Elbasan => families.push("Noto Sans Elbasan"),
UnicodeBlock::Ethiopic |
UnicodeBlock::EthiopicExtended |
UnicodeBlock::EthiopicExtendedA |
UnicodeBlock::EthiopicSupplement => families.push("Noto Sans Ethiopic"),
UnicodeBlock::Georgian | UnicodeBlock::GeorgianSupplement => {
families.push("Noto Sans Georgian")
},
UnicodeBlock::Glagolitic | UnicodeBlock::GlagoliticSupplement => {
families.push("Noto Sans Glagolitic")
},
UnicodeBlock::Gothic => families.push("Noto Sans Gothic"),
UnicodeBlock::Grantha => families.push("Noto Sans Grantha"),
UnicodeBlock::Gujarati => families.push("Noto Sans Gujarati"),
UnicodeBlock::Gurmukhi => families.push("Noto Sans Gurmukhi"),
UnicodeBlock::HangulCompatibilityJamo |
UnicodeBlock::HangulJamo |
UnicodeBlock::HangulJamoExtendedA |
UnicodeBlock::HangulJamoExtendedB |
UnicodeBlock::HangulSyllables => {
families.push("Noto Sans KR");
families.push("Noto Sans CJK KR");
},
UnicodeBlock::HanifiRohingya => families.push("Noto Sans Hanifi Rohingya"),
UnicodeBlock::Hanunoo => families.push("Noto Sans Hanunoo"),
UnicodeBlock::Hatran => families.push("Noto Sans Hatran"),
UnicodeBlock::Hebrew => families.push("Noto Sans Hebrew"),
UnicodeBlock::Hiragana |
UnicodeBlock::Katakana |
UnicodeBlock::KatakanaPhoneticExtensions => {
families.push("Noto Sans JP");
families.push("Noto Sans CJK JP");
},
UnicodeBlock::ImperialAramaic => families.push("Noto Sans Imperial Aramaic"),
UnicodeBlock::InscriptionalPahlavi => families.push("Noto Sans Inscriptional Pahlavi"),
UnicodeBlock::InscriptionalParthian => {
families.push("Noto Sans Inscriptional Parthian")
},
UnicodeBlock::Javanese => families.push("Noto Sans Javanese"),
UnicodeBlock::Kaithi => families.push("Noto Sans Kaithi"),
UnicodeBlock::Kannada => families.push("Noto Sans Kannada"),
UnicodeBlock::KayahLi => families.push("Noto Sans Kayah Li"),
UnicodeBlock::Kharoshthi => families.push("Noto Sans Kharoshthi"),
UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => families.push("Noto Sans Khmer"),
UnicodeBlock::Khojki => families.push("Noto Sans Khojki"),
UnicodeBlock::Khudawadi => families.push("Noto Sans Khudawadi"),
UnicodeBlock::Lao => families.push("Noto Sans Lao"),
UnicodeBlock::Lepcha => families.push("Noto Sans Lepcha"),
UnicodeBlock::Limbu => families.push("Noto Sans Limbu"),
UnicodeBlock::LinearA => families.push("Noto Sans Linear A"),
UnicodeBlock::LinearBIdeograms | UnicodeBlock::LinearBSyllabary => {
families.push("Noto Sans Linear B")
},
UnicodeBlock::Lisu => families.push("Noto Sans Lisu"),
UnicodeBlock::Lycian => families.push("Noto Sans Lycian"),
UnicodeBlock::Lydian => families.push("Noto Sans Lydian"),
UnicodeBlock::Mahajani => families.push("Noto Sans Mahajani"),
UnicodeBlock::Malayalam => families.push("Noto Sans Malayalam"),
UnicodeBlock::Mandaic => families.push("Noto Sans Mandaic"),
UnicodeBlock::Manichaean => families.push("Noto Sans Manichaean"),
UnicodeBlock::Marchen => families.push("Noto Sans Marchen"),
UnicodeBlock::MeeteiMayek | UnicodeBlock::MeeteiMayekExtensions => {
families.push("Noto Sans Meetei Mayek")
},
UnicodeBlock::MendeKikakui => families.push("Noto Sans Mende Kikakui"),
UnicodeBlock::MeroiticCursive | UnicodeBlock::MeroiticHieroglyphs => {
families.push("Noto Sans Meroitic")
},
UnicodeBlock::Miao => families.push("Noto Sans Miao"),
UnicodeBlock::Modi => families.push("Noto Sans Modi"),
UnicodeBlock::Mongolian | UnicodeBlock::MongolianSupplement => {
families.push("Noto Sans Mongolian")
},
UnicodeBlock::Mro => families.push("Noto Sans Mro"),
UnicodeBlock::Multani => families.push("Noto Sans Multani"),
UnicodeBlock::MusicalSymbols => families.push("Noto Music"),
UnicodeBlock::Myanmar |
UnicodeBlock::MyanmarExtendedA |
UnicodeBlock::MyanmarExtendedB => families.push("Noto Sans Myanmar"),
UnicodeBlock::NKo => families.push("Noto Sans NKo"),
UnicodeBlock::Nabataean => families.push("Noto Sans Nabataean"),
UnicodeBlock::NewTaiLue => families.push("Noto Sans New Tai Lue"),
UnicodeBlock::Newa => families.push("Noto Sans Newa"),
UnicodeBlock::Ogham => families.push("Noto Sans Ogham"),
UnicodeBlock::OlChiki => families.push("Noto Sans Ol Chiki"),
UnicodeBlock::OldHungarian => families.push("Noto Sans Old Hungarian"),
UnicodeBlock::OldItalic => families.push("Noto Sans Old Italic"),
UnicodeBlock::OldNorthArabian => families.push("Noto Sans Old North Arabian"),
UnicodeBlock::OldPermic => families.push("Noto Sans Old Permic"),
UnicodeBlock::OldPersian => families.push("Noto Sans Old Persian"),
UnicodeBlock::OldSouthArabian => families.push("Noto Sans Old South Arabian"),
UnicodeBlock::OldTurkic => families.push("Noto Sans Old Turkic"),
UnicodeBlock::Oriya => families.push("Noto Sans Oriya"),
UnicodeBlock::Osage => families.push("Noto Sans Osage"),
UnicodeBlock::Osmanya => families.push("Noto Sans Osmanya"),
UnicodeBlock::PahawhHmong => families.push("Noto Sans Pahawh Hmong"),
UnicodeBlock::Palmyrene => families.push("Noto Sans Palmyrene"),
UnicodeBlock::PauCinHau => families.push("Noto Sans Pau Cin Hau"),
UnicodeBlock::Phagspa => families.push("Noto Sans PhagsPa"),
UnicodeBlock::Phoenician => families.push("Noto Sans Phoenician"),
UnicodeBlock::PsalterPahlavi => families.push("Noto Sans Psalter Pahlavi"),
UnicodeBlock::Rejang => families.push("Noto Sans Rejang"),
UnicodeBlock::Runic => families.push("Noto Sans Runic"),
UnicodeBlock::Samaritan => families.push("Noto Sans Samaritan"),
UnicodeBlock::Saurashtra => families.push("Noto Sans Saurashtra"),
UnicodeBlock::Sharada => families.push("Noto Sans Sharada"),
UnicodeBlock::Shavian => families.push("Noto Sans Shavian"),
UnicodeBlock::Siddham => families.push("Noto Sans Siddham"),
UnicodeBlock::Sinhala | UnicodeBlock::SinhalaArchaicNumbers => {
families.push("Noto Sans Sinhala")
},
UnicodeBlock::SoraSompeng => families.push("Noto Sans Sora Sompeng"),
UnicodeBlock::Sundanese => families.push("Noto Sans Sundanese"),
UnicodeBlock::SuttonSignWriting => families.push("Noto Sans SignWrit"),
UnicodeBlock::SylotiNagri => families.push("Noto Sans Syloti Nagri"),
UnicodeBlock::Syriac => families.push("Noto Sans Syriac"),
UnicodeBlock::Tagalog => families.push("Noto Sans Tagalog"),
UnicodeBlock::Tagbanwa => families.push("Noto Sans Tagbanwa"),
UnicodeBlock::TaiLe => families.push("Noto Sans Tai Le"),
UnicodeBlock::TaiTham => families.push("Noto Sans Tai Tham"),
UnicodeBlock::TaiViet => families.push("Noto Sans Tai Viet"),
UnicodeBlock::Takri => families.push("Noto Sans Takri"),
UnicodeBlock::Tamil => families.push("Noto Sans Tamil"),
UnicodeBlock::Tangut |
UnicodeBlock::TangutComponents |
UnicodeBlock::IdeographicSymbolsandPunctuation => families.push("Noto Serif Tangut"),
UnicodeBlock::Telugu => families.push("Noto Sans Telugu"),
UnicodeBlock::Thaana => {
families.push("Noto Sans Thaana");
},
UnicodeBlock::Thai => families.push("Noto Sans Thai"),
UnicodeBlock::Tibetan => families.push("Noto Serif Tibetan"),
UnicodeBlock::Tifinagh => families.push("Noto Sans Tifinagh"),
UnicodeBlock::Tirhuta => families.push("Noto Sans Tirhuta"),
UnicodeBlock::Ugaritic => families.push("Noto Sans Ugaritic"),
UnicodeBlock::UnifiedCanadianAboriginalSyllabics |
UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => {
families.push("Noto Sans Canadian Aboriginal")
},
UnicodeBlock::Vai => families.push("Noto Sans Vai"),
UnicodeBlock::WarangCiti => families.push("Noto Sans Warang Citi"),
UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => {
families.push("Noto Sans Yi");
},
UnicodeBlock::Wancho => families.push("Noto Sans Wancho"),
_ => {},
}
}
families.push("Noto Sans Symbols");
families.push("Noto Sans Symbols2");
}

View file

@ -0,0 +1,276 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// NOTE: https://www.chromium.org/directwrite-font-proxy has useful
// information for an approach that we'll likely need to take when the
// renderer moves to a sandboxed process.
use std::cmp::{max, min};
use std::fmt;
use std::io::Cursor;
use std::ops::Deref;
use std::sync::Arc;
use app_units::Au;
use dwrote::{FontFace, FontFile};
use log::{debug, warn};
use style::computed_values::font_stretch::T as StyleFontStretch;
use style::computed_values::font_weight::T as StyleFontWeight;
use style::values::computed::font::FontStyle as StyleFontStyle;
use truetype::tables::WindowsMetrics;
use truetype::value::Read;
use webrender_api::FontInstanceFlags;
use crate::{
ot_tag, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, FontTemplateDescriptor,
FractionalPixel, GlyphId, PlatformFontMethods,
};
// 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch
fn pt_to_px(pt: f64) -> f64 {
pt / 72. * 96.
}
fn em_to_px(em: f64) -> f64 {
em * 16.
}
fn au_from_em(em: f64) -> Au {
Au::from_f64_px(em_to_px(em))
}
fn au_from_pt(pt: f64) -> Au {
Au::from_f64_px(pt_to_px(pt))
}
pub struct FontTable {
data: Vec<u8>,
}
impl FontTable {
pub fn wrap(data: &[u8]) -> FontTable {
FontTable {
data: data.to_vec(),
}
}
}
impl FontTableMethods for FontTable {
fn buffer(&self) -> &[u8] {
&self.data
}
}
#[derive(Debug)]
pub struct PlatformFont {
face: Nondebug<FontFace>,
/// A reference to this data used to create this [`PlatformFont`], ensuring the
/// data stays alive of the lifetime of this struct.
_data: Arc<Vec<u8>>,
em_size: f32,
du_to_px: f32,
scaled_du_to_px: f32,
}
// Based on information from the Skia codebase, it seems that DirectWrite APIs from
// Windows 10 and beyond are thread safe. If problems arise from this, we can protect the
// platform font with a Mutex.
// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/ports/SkScalerContext_win_dw.cpp;l=56;bpv=0;bpt=1.
unsafe impl Sync for PlatformFont {}
unsafe impl Send for PlatformFont {}
struct Nondebug<T>(T);
impl<T> fmt::Debug for Nondebug<T> {
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
Ok(())
}
}
impl<T> Deref for Nondebug<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl PlatformFontMethods for PlatformFont {
fn new_from_data(
_font_identifier: FontIdentifier,
data: Arc<Vec<u8>>,
face_index: u32,
pt_size: Option<Au>,
) -> Result<Self, &'static str> {
let font_file = FontFile::new_from_data(data.clone()).ok_or("Could not create FontFile")?;
let face = font_file
.create_face(face_index, dwrote::DWRITE_FONT_SIMULATIONS_NONE)
.map_err(|_| "Could not create FontFace")?;
let pt_size = pt_size.unwrap_or(au_from_pt(12.));
let du_per_em = face.metrics().metrics0().designUnitsPerEm as f32;
let em_size = pt_size.to_f32_px() / 16.;
let design_units_per_pixel = du_per_em / 16.;
let design_units_to_pixels = 1. / design_units_per_pixel;
let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
Ok(PlatformFont {
face: Nondebug(face),
_data: data,
em_size,
du_to_px: design_units_to_pixels,
scaled_du_to_px: scaled_design_units_to_pixels,
})
}
fn descriptor(&self) -> FontTemplateDescriptor {
// We need the font (DWriteFont) in order to be able to query things like
// the family name, face name, weight, etc. On Windows 10, the
// DWriteFontFace3 interface provides this on the FontFace, but that's only
// available on Win10+.
//
// Instead, we do the parsing work using the truetype crate for raw fonts.
// We're just extracting basic info, so this is sufficient for now.
//
// The `dwrote` APIs take SFNT table tags in a reversed byte order, which
// is why `u32::swap_bytes()` is called here.
let windows_metrics_bytes = self
.face
.get_font_table(u32::swap_bytes(ot_tag!('O', 'S', '/', '2')));
if windows_metrics_bytes.is_none() {
warn!("Could not find OS/2 table in font.");
return FontTemplateDescriptor::default();
}
let mut cursor = Cursor::new(windows_metrics_bytes.as_ref().unwrap());
let Ok(table) = WindowsMetrics::read(&mut cursor) else {
warn!("Could not read OS/2 table in font.");
return FontTemplateDescriptor::default();
};
let (weight_val, width_val, italic_bool) = match table {
WindowsMetrics::Version0(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version1(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version2(ref m) |
WindowsMetrics::Version3(ref m) |
WindowsMetrics::Version4(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version5(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
};
let weight = StyleFontWeight::from_float(weight_val as f32);
let stretch = match min(9, max(1, width_val)) {
1 => StyleFontStretch::ULTRA_CONDENSED,
2 => StyleFontStretch::EXTRA_CONDENSED,
3 => StyleFontStretch::CONDENSED,
4 => StyleFontStretch::SEMI_CONDENSED,
5 => StyleFontStretch::NORMAL,
6 => StyleFontStretch::SEMI_EXPANDED,
7 => StyleFontStretch::EXPANDED,
8 => StyleFontStretch::EXTRA_EXPANDED,
9 => StyleFontStretch::ULTRA_CONDENSED,
_ => {
warn!("Unknown stretch size.");
StyleFontStretch::NORMAL
},
};
let style = if italic_bool {
StyleFontStyle::ITALIC
} else {
StyleFontStyle::NORMAL
};
FontTemplateDescriptor::new(weight, stretch, style)
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
let glyph = self.face.get_glyph_indices(&[codepoint as u32])[0];
if glyph == 0 {
return None;
}
Some(glyph as GlyphId)
}
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
if glyph == 0 {
return None;
}
let gm = self.face.get_design_glyph_metrics(&[glyph as u16], false)[0];
let f = (gm.advanceWidth as f32 * self.scaled_du_to_px) as FractionalPixel;
Some(f)
}
/// Can this font do basic horizontal LTR shaping without Harfbuzz?
fn can_do_fast_shaping(&self) -> bool {
// TODO copy CachedKernTable from the MacOS X implementation to
// somehwere global and use it here. We could also implement the
// IDirectWriteFontFace1 interface and use the glyph kerning pair
// methods there.
false
}
fn glyph_h_kerning(&self, _: GlyphId, _: GlyphId) -> FractionalPixel {
0.0
}
fn metrics(&self) -> FontMetrics {
let dm = self.face.metrics().metrics0();
let au_from_du = |du| -> Au { Au::from_f32_px(du as f32 * self.du_to_px) };
let au_from_du_s = |du| -> Au { Au::from_f32_px(du as f32 * self.scaled_du_to_px) };
// anything that we calculate and don't just pull out of self.face.metrics
// is pulled out here for clarity
let leading = dm.ascent - dm.capHeight;
let zero_horizontal_advance = self
.glyph_index('0')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px);
let ic_horizontal_advance = self
.glyph_index('\u{6C34}')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px);
let metrics = FontMetrics {
underline_size: au_from_du(dm.underlineThickness as i32),
underline_offset: au_from_du_s(dm.underlinePosition as i32),
strikeout_size: au_from_du(dm.strikethroughThickness as i32),
strikeout_offset: au_from_du_s(dm.strikethroughPosition as i32),
leading: au_from_du_s(leading as i32),
x_height: au_from_du_s(dm.xHeight as i32),
em_size: au_from_em(self.em_size as f64),
ascent: au_from_du_s(dm.ascent as i32),
descent: au_from_du_s(dm.descent as i32),
max_advance: au_from_pt(0.0), // FIXME
average_advance: au_from_pt(0.0), // FIXME
line_gap: au_from_du_s((dm.ascent + dm.descent + dm.lineGap as u16) as i32),
zero_horizontal_advance,
ic_horizontal_advance,
};
debug!("Font metrics (@{} pt): {:?}", self.em_size * 12., metrics);
metrics
}
fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
// dwrote (and presumably the Windows APIs) accept a reversed version of the table
// tag bytes, which means that `u32::swap_bytes` must be called here in order to
// use a byte order compatible with the rest of Servo.
self.face
.get_font_table(u32::swap_bytes(tag))
.map(|bytes| FontTable { data: bytes })
}
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
FontInstanceFlags::empty()
}
}

View file

@ -0,0 +1,379 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::hash::Hash;
use std::sync::Arc;
use base::text::{unicode_plane, UnicodeBlock, UnicodeBlockMethod};
use dwrote::{Font, FontCollection, FontDescriptor, FontStretch, FontStyle};
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use style::values::computed::{FontStyle as StyleFontStyle, FontWeight as StyleFontWeight};
use style::values::specified::font::FontStretchKeyword;
use crate::{
EmojiPresentationPreference, FallbackFontSelectionOptions, FontTemplate, FontTemplateDescriptor,
};
pub static SANS_SERIF_FONT_FAMILY: &str = "Arial";
pub fn system_default_family(_: &str) -> Option<String> {
Some("Verdana".to_owned())
}
pub fn for_each_available_family<F>(mut callback: F)
where
F: FnMut(String),
{
let system_fc = FontCollection::system();
for family in system_fc.families_iter() {
callback(family.name());
}
}
/// An identifier for a local font on a Windows system.
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub struct LocalFontIdentifier {
/// The FontDescriptor of this font.
#[ignore_malloc_size_of = "dwrote does not support MallocSizeOf"]
pub font_descriptor: Arc<FontDescriptor>,
}
impl LocalFontIdentifier {
pub fn index(&self) -> u32 {
FontCollection::system()
.get_font_from_descriptor(&self.font_descriptor)
.map_or(0, |font| font.create_font_face().get_index())
}
pub(crate) fn read_data_from_file(&self) -> Vec<u8> {
let font = FontCollection::system()
.get_font_from_descriptor(&self.font_descriptor)
.unwrap();
let face = font.create_font_face();
let files = face.get_files();
assert!(!files.is_empty());
files[0].get_font_file_bytes()
}
}
impl Eq for LocalFontIdentifier {}
impl Hash for LocalFontIdentifier {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.font_descriptor.family_name.hash(state);
self.font_descriptor.weight.to_u32().hash(state);
self.font_descriptor.stretch.to_u32().hash(state);
self.font_descriptor.style.to_u32().hash(state);
}
}
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where
F: FnMut(FontTemplate),
{
let system_fc = FontCollection::system();
if let Some(family) = system_fc.get_font_family_by_name(family_name) {
let count = family.get_font_count();
for i in 0..count {
let font = family.get_font(i);
let template_descriptor = (&font).into();
let local_font_identifier = LocalFontIdentifier {
font_descriptor: Arc::new(font.to_descriptor()),
};
callback(FontTemplate::new_for_local_font(
local_font_identifier,
template_descriptor,
))
}
}
}
// Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko
pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = Vec::new();
if options.presentation_preference == EmojiPresentationPreference::Emoji {
families.push("Segoe UI Emoji");
}
families.push("Arial");
match unicode_plane(options.character) {
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
0 => {
if let Some(block) = options.character.block() {
match block {
UnicodeBlock::CyrillicSupplement |
UnicodeBlock::Armenian |
UnicodeBlock::Hebrew => {
families.push("Estrangelo Edessa");
families.push("Cambria");
},
UnicodeBlock::Arabic | UnicodeBlock::ArabicSupplement => {
families.push("Microsoft Uighur");
},
UnicodeBlock::Syriac => {
families.push("Estrangelo Edessa");
},
UnicodeBlock::Thaana => {
families.push("MV Boli");
},
UnicodeBlock::NKo => {
families.push("Ebrima");
},
UnicodeBlock::Devanagari | UnicodeBlock::Bengali => {
families.push("Nirmala UI");
families.push("Utsaah");
families.push("Aparajita");
},
UnicodeBlock::Gurmukhi |
UnicodeBlock::Gujarati |
UnicodeBlock::Oriya |
UnicodeBlock::Tamil |
UnicodeBlock::Telugu |
UnicodeBlock::Kannada |
UnicodeBlock::Malayalam |
UnicodeBlock::Sinhala |
UnicodeBlock::Lepcha |
UnicodeBlock::OlChiki |
UnicodeBlock::CyrillicExtendedC |
UnicodeBlock::SundaneseSupplement |
UnicodeBlock::VedicExtensions => {
families.push("Nirmala UI");
},
UnicodeBlock::Thai => {
families.push("Leelawadee UI");
},
UnicodeBlock::Lao => {
families.push("Lao UI");
},
UnicodeBlock::Myanmar |
UnicodeBlock::MyanmarExtendedA |
UnicodeBlock::MyanmarExtendedB => {
families.push("Myanmar Text");
},
UnicodeBlock::HangulJamo |
UnicodeBlock::HangulJamoExtendedA |
UnicodeBlock::HangulSyllables |
UnicodeBlock::HangulJamoExtendedB |
UnicodeBlock::HangulCompatibilityJamo => {
families.push("Malgun Gothic");
},
UnicodeBlock::Ethiopic |
UnicodeBlock::EthiopicSupplement |
UnicodeBlock::EthiopicExtended |
UnicodeBlock::EthiopicExtendedA => {
families.push("Nyala");
},
UnicodeBlock::Cherokee => {
families.push("Plantagenet Cherokee");
},
UnicodeBlock::UnifiedCanadianAboriginalSyllabics |
UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => {
families.push("Euphemia");
families.push("Segoe UI");
},
UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => {
families.push("Khmer UI");
families.push("Leelawadee UI");
},
UnicodeBlock::Mongolian => {
families.push("Mongolian Baiti");
},
UnicodeBlock::TaiLe => {
families.push("Microsoft Tai Le");
},
UnicodeBlock::NewTaiLue => {
families.push("Microsoft New Tai Lue");
},
UnicodeBlock::Buginese |
UnicodeBlock::TaiTham |
UnicodeBlock::CombiningDiacriticalMarksExtended => {
families.push("Leelawadee UI");
},
UnicodeBlock::GeneralPunctuation |
UnicodeBlock::SuperscriptsandSubscripts |
UnicodeBlock::CurrencySymbols |
UnicodeBlock::CombiningDiacriticalMarksforSymbols |
UnicodeBlock::LetterlikeSymbols |
UnicodeBlock::NumberForms |
UnicodeBlock::Arrows |
UnicodeBlock::MathematicalOperators |
UnicodeBlock::MiscellaneousTechnical |
UnicodeBlock::ControlPictures |
UnicodeBlock::OpticalCharacterRecognition |
UnicodeBlock::EnclosedAlphanumerics |
UnicodeBlock::BoxDrawing |
UnicodeBlock::BlockElements |
UnicodeBlock::GeometricShapes |
UnicodeBlock::MiscellaneousSymbols |
UnicodeBlock::Dingbats |
UnicodeBlock::MiscellaneousMathematicalSymbolsA |
UnicodeBlock::SupplementalArrowsA |
UnicodeBlock::SupplementalArrowsB |
UnicodeBlock::MiscellaneousMathematicalSymbolsB |
UnicodeBlock::SupplementalMathematicalOperators |
UnicodeBlock::MiscellaneousSymbolsandArrows |
UnicodeBlock::Glagolitic |
UnicodeBlock::LatinExtendedC |
UnicodeBlock::Coptic => {
families.push("Segoe UI");
families.push("Segoe UI Symbol");
families.push("Cambria");
families.push("Meiryo");
families.push("Lucida Sans Unicode");
families.push("Ebrima");
},
UnicodeBlock::GeorgianSupplement |
UnicodeBlock::Tifinagh |
UnicodeBlock::CyrillicExtendedA |
UnicodeBlock::SupplementalPunctuation |
UnicodeBlock::CJKRadicalsSupplement |
UnicodeBlock::KangxiRadicals |
UnicodeBlock::IdeographicDescriptionCharacters => {
families.push("Segoe UI");
families.push("Segoe UI Symbol");
families.push("Meiryo");
},
UnicodeBlock::BraillePatterns => {
families.push("Segoe UI Symbol");
},
UnicodeBlock::CJKSymbolsandPunctuation |
UnicodeBlock::Hiragana |
UnicodeBlock::Katakana |
UnicodeBlock::Bopomofo |
UnicodeBlock::Kanbun |
UnicodeBlock::BopomofoExtended |
UnicodeBlock::CJKStrokes |
UnicodeBlock::KatakanaPhoneticExtensions |
UnicodeBlock::CJKUnifiedIdeographs => {
families.push("Microsoft YaHei");
families.push("Yu Gothic");
},
UnicodeBlock::EnclosedCJKLettersandMonths => {
families.push("Malgun Gothic");
},
UnicodeBlock::YijingHexagramSymbols => {
families.push("Segoe UI Symbol");
},
UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => {
families.push("Microsoft Yi Baiti");
families.push("Segoe UI");
},
UnicodeBlock::Vai |
UnicodeBlock::CyrillicExtendedB |
UnicodeBlock::Bamum |
UnicodeBlock::ModifierToneLetters |
UnicodeBlock::LatinExtendedD => {
families.push("Ebrima");
families.push("Segoe UI");
families.push("Cambria Math");
},
UnicodeBlock::SylotiNagri |
UnicodeBlock::CommonIndicNumberForms |
UnicodeBlock::Phagspa |
UnicodeBlock::Saurashtra |
UnicodeBlock::DevanagariExtended => {
families.push("Microsoft PhagsPa");
families.push("Nirmala UI");
},
UnicodeBlock::KayahLi | UnicodeBlock::Rejang | UnicodeBlock::Javanese => {
families.push("Malgun Gothic");
families.push("Javanese Text");
families.push("Leelawadee UI");
},
UnicodeBlock::AlphabeticPresentationForms => {
families.push("Microsoft Uighur");
families.push("Gabriola");
families.push("Sylfaen");
},
UnicodeBlock::ArabicPresentationFormsA |
UnicodeBlock::ArabicPresentationFormsB => {
families.push("Traditional Arabic");
families.push("Arabic Typesetting");
},
UnicodeBlock::VariationSelectors |
UnicodeBlock::VerticalForms |
UnicodeBlock::CombiningHalfMarks |
UnicodeBlock::CJKCompatibilityForms |
UnicodeBlock::SmallFormVariants |
UnicodeBlock::HalfwidthandFullwidthForms |
UnicodeBlock::Specials => {
families.push("Microsoft JhengHei");
},
_ => {},
}
}
},
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane
1 => {
families.push("Segoe UI Symbol");
families.push("Ebrima");
families.push("Nirmala UI");
families.push("Cambria Math");
},
_ => {},
}
families.push("Arial Unicode MS");
families
}
impl From<&Font> for FontTemplateDescriptor {
fn from(font: &Font) -> Self {
let style = match font.style() {
FontStyle::Normal => StyleFontStyle::NORMAL,
FontStyle::Oblique => StyleFontStyle::OBLIQUE,
FontStyle::Italic => StyleFontStyle::ITALIC,
};
let weight = StyleFontWeight::from_float(font.weight().to_u32() as f32);
let stretch = match font.stretch() {
FontStretch::Undefined => FontStretchKeyword::Normal,
FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed,
FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed,
FontStretch::Condensed => FontStretchKeyword::Condensed,
FontStretch::SemiCondensed => FontStretchKeyword::SemiCondensed,
FontStretch::Normal => FontStretchKeyword::Normal,
FontStretch::SemiExpanded => FontStretchKeyword::SemiExpanded,
FontStretch::Expanded => FontStretchKeyword::Expanded,
FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded,
FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded,
}
.compute();
FontTemplateDescriptor::new(weight, stretch, style)
}
}

718
components/fonts/shaper.rs Normal file
View file

@ -0,0 +1,718 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![allow(unsafe_code)]
use std::os::raw::{c_char, c_int, c_uint, c_void};
use std::{char, cmp, ptr};
use app_units::Au;
use base::text::is_bidi_control;
use euclid::default::Point2D;
// Eventually we would like the shaper to be pluggable, as many operating systems have their own
// shapers. For now, however, HarfBuzz is a hard dependency.
use harfbuzz_sys::{
hb_blob_create, hb_blob_t, hb_bool_t, hb_buffer_add_utf8, hb_buffer_create, hb_buffer_destroy,
hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions, hb_buffer_get_length,
hb_buffer_set_direction, hb_buffer_set_script, hb_buffer_t, hb_codepoint_t,
hb_face_create_for_tables, hb_face_destroy, hb_face_t, hb_feature_t, hb_font_create,
hb_font_destroy, hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func,
hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem,
hb_font_set_scale, hb_font_t, hb_glyph_info_t, hb_glyph_position_t, hb_position_t, hb_shape,
hb_tag_t, HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_MEMORY_MODE_READONLY,
};
use lazy_static::lazy_static;
use log::debug;
use crate::platform::font::FontTable;
use crate::{
fixed_to_float, float_to_fixed, ot_tag, ByteIndex, Font, FontTableMethods, FontTableTag,
GlyphData, GlyphId, GlyphStore, ShapingFlags, ShapingOptions, KERN,
};
const NO_GLYPH: i32 = -1;
const LIGA: u32 = ot_tag!('l', 'i', 'g', 'a');
pub struct ShapedGlyphData {
count: usize,
glyph_infos: *mut hb_glyph_info_t,
pos_infos: *mut hb_glyph_position_t,
}
pub struct ShapedGlyphEntry {
codepoint: GlyphId,
advance: Au,
offset: Option<Point2D<Au>>,
}
impl ShapedGlyphData {
/// Create a new [`ShapedGlyphData`] from the given HarfBuzz buffer.
///
/// # Safety
///
/// Passing an invalid buffer pointer to this function results in undefined behavior.
pub unsafe fn new(buffer: *mut hb_buffer_t) -> ShapedGlyphData {
let mut glyph_count = 0;
let glyph_infos = hb_buffer_get_glyph_infos(buffer, &mut glyph_count);
assert!(!glyph_infos.is_null());
let mut pos_count = 0;
let pos_infos = hb_buffer_get_glyph_positions(buffer, &mut pos_count);
assert!(!pos_infos.is_null());
assert_eq!(glyph_count, pos_count);
ShapedGlyphData {
count: glyph_count as usize,
glyph_infos,
pos_infos,
}
}
#[inline(always)]
fn byte_offset_of_glyph(&self, i: usize) -> u32 {
assert!(i < self.count);
unsafe {
let glyph_info_i = self.glyph_infos.add(i);
(*glyph_info_i).cluster
}
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
/// Returns shaped glyph data for one glyph, and updates the y-position of the pen.
pub fn entry_for_glyph(&self, i: usize, y_pos: &mut Au) -> ShapedGlyphEntry {
assert!(i < self.count);
unsafe {
let glyph_info_i = self.glyph_infos.add(i);
let pos_info_i = self.pos_infos.add(i);
let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset);
let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset);
let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance);
let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance);
let x_offset = Au::from_f64_px(x_offset);
let y_offset = Au::from_f64_px(y_offset);
let x_advance = Au::from_f64_px(x_advance);
let y_advance = Au::from_f64_px(y_advance);
let offset = if x_offset == Au(0) && y_offset == Au(0) && y_advance == Au(0) {
None
} else {
// adjust the pen..
if y_advance > Au(0) {
*y_pos -= y_advance;
}
Some(Point2D::new(x_offset, *y_pos - y_offset))
};
ShapedGlyphEntry {
codepoint: (*glyph_info_i).codepoint as GlyphId,
advance: x_advance,
offset,
}
}
}
}
#[derive(Debug)]
pub struct Shaper {
hb_face: *mut hb_face_t,
hb_font: *mut hb_font_t,
font: *const Font,
}
// The HarfBuzz API is thread safe as well as our `Font`, so we can make the data
// structures here as thread-safe as well. This doesn't seem to be documented,
// but was expressed as one of the original goals of the HarfBuzz API.
unsafe impl Sync for Shaper {}
unsafe impl Send for Shaper {}
impl Drop for Shaper {
fn drop(&mut self) {
unsafe {
assert!(!self.hb_face.is_null());
hb_face_destroy(self.hb_face);
assert!(!self.hb_font.is_null());
hb_font_destroy(self.hb_font);
}
}
}
impl Shaper {
#[allow(clippy::not_unsafe_ptr_arg_deref)] // Has an unsafe block inside
pub fn new(font: *const Font) -> Shaper {
unsafe {
let hb_face: *mut hb_face_t = hb_face_create_for_tables(
Some(font_table_func),
font as *const c_void as *mut c_void,
None,
);
let hb_font: *mut hb_font_t = hb_font_create(hb_face);
// Set points-per-em. if zero, performs no hinting in that direction.
let pt_size = (*font).descriptor.pt_size.to_f64_px();
hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
// Set scaling. Note that this takes 16.16 fixed point.
hb_font_set_scale(
hb_font,
Shaper::float_to_fixed(pt_size) as c_int,
Shaper::float_to_fixed(pt_size) as c_int,
);
// configure static function callbacks.
hb_font_set_funcs(
hb_font,
HB_FONT_FUNCS.0,
font as *mut Font as *mut c_void,
None,
);
Shaper {
hb_face,
hb_font,
font,
}
}
}
fn float_to_fixed(f: f64) -> i32 {
float_to_fixed(16, f)
}
fn fixed_to_float(i: hb_position_t) -> f64 {
fixed_to_float(16, i)
}
}
pub fn unicode_to_hb_script(script: unicode_script::Script) -> harfbuzz_sys::hb_script_t {
use harfbuzz_sys::*;
use unicode_script::Script::*;
match script {
Adlam => HB_SCRIPT_ADLAM,
Ahom => HB_SCRIPT_AHOM,
Anatolian_Hieroglyphs => HB_SCRIPT_ANATOLIAN_HIEROGLYPHS,
Arabic => HB_SCRIPT_ARABIC,
Armenian => HB_SCRIPT_ARMENIAN,
Avestan => HB_SCRIPT_AVESTAN,
Balinese => HB_SCRIPT_BALINESE,
Bamum => HB_SCRIPT_BAMUM,
Bassa_Vah => HB_SCRIPT_BASSA_VAH,
Batak => HB_SCRIPT_BATAK,
Bengali => HB_SCRIPT_BENGALI,
Bhaiksuki => HB_SCRIPT_BHAIKSUKI,
Bopomofo => HB_SCRIPT_BOPOMOFO,
Brahmi => HB_SCRIPT_BRAHMI,
Braille => HB_SCRIPT_BRAILLE,
Buginese => HB_SCRIPT_BUGINESE,
Buhid => HB_SCRIPT_BUHID,
Canadian_Aboriginal => HB_SCRIPT_CANADIAN_SYLLABICS,
Carian => HB_SCRIPT_CARIAN,
Caucasian_Albanian => HB_SCRIPT_CAUCASIAN_ALBANIAN,
Chakma => HB_SCRIPT_CHAKMA,
Cham => HB_SCRIPT_CHAM,
Cherokee => HB_SCRIPT_CHEROKEE,
Chorasmian => HB_SCRIPT_CHORASMIAN,
Common => HB_SCRIPT_COMMON,
Coptic => HB_SCRIPT_COPTIC,
Cuneiform => HB_SCRIPT_CUNEIFORM,
Cypriot => HB_SCRIPT_CYPRIOT,
Cyrillic => HB_SCRIPT_CYRILLIC,
Deseret => HB_SCRIPT_DESERET,
Devanagari => HB_SCRIPT_DEVANAGARI,
Dives_Akuru => HB_SCRIPT_DIVES_AKURU,
Dogra => HB_SCRIPT_DOGRA,
Duployan => HB_SCRIPT_DUPLOYAN,
Egyptian_Hieroglyphs => HB_SCRIPT_EGYPTIAN_HIEROGLYPHS,
Elbasan => HB_SCRIPT_ELBASAN,
Elymaic => HB_SCRIPT_ELYMAIC,
Ethiopic => HB_SCRIPT_ETHIOPIC,
Georgian => HB_SCRIPT_GEORGIAN,
Glagolitic => HB_SCRIPT_GLAGOLITIC,
Gothic => HB_SCRIPT_GOTHIC,
Grantha => HB_SCRIPT_GRANTHA,
Greek => HB_SCRIPT_GREEK,
Gujarati => HB_SCRIPT_GUJARATI,
Gunjala_Gondi => HB_SCRIPT_GUNJALA_GONDI,
Gurmukhi => HB_SCRIPT_GURMUKHI,
Han => HB_SCRIPT_HAN,
Hangul => HB_SCRIPT_HANGUL,
Hanifi_Rohingya => HB_SCRIPT_HANIFI_ROHINGYA,
Hanunoo => HB_SCRIPT_HANUNOO,
Hatran => HB_SCRIPT_HATRAN,
Hebrew => HB_SCRIPT_HEBREW,
Hiragana => HB_SCRIPT_HIRAGANA,
Imperial_Aramaic => HB_SCRIPT_IMPERIAL_ARAMAIC,
Inherited => HB_SCRIPT_INHERITED,
Inscriptional_Pahlavi => HB_SCRIPT_INSCRIPTIONAL_PAHLAVI,
Inscriptional_Parthian => HB_SCRIPT_INSCRIPTIONAL_PARTHIAN,
Javanese => HB_SCRIPT_JAVANESE,
Kaithi => HB_SCRIPT_KAITHI,
Kannada => HB_SCRIPT_KANNADA,
Katakana => HB_SCRIPT_KATAKANA,
Kayah_Li => HB_SCRIPT_KAYAH_LI,
Kharoshthi => HB_SCRIPT_KHAROSHTHI,
Khitan_Small_Script => HB_SCRIPT_KHITAN_SMALL_SCRIPT,
Khmer => HB_SCRIPT_KHMER,
Khojki => HB_SCRIPT_KHOJKI,
Khudawadi => HB_SCRIPT_KHUDAWADI,
Lao => HB_SCRIPT_LAO,
Latin => HB_SCRIPT_LATIN,
Lepcha => HB_SCRIPT_LEPCHA,
Limbu => HB_SCRIPT_LIMBU,
Linear_A => HB_SCRIPT_LINEAR_A,
Linear_B => HB_SCRIPT_LINEAR_B,
Lisu => HB_SCRIPT_LISU,
Lycian => HB_SCRIPT_LYCIAN,
Lydian => HB_SCRIPT_LYDIAN,
Mahajani => HB_SCRIPT_MAHAJANI,
Makasar => HB_SCRIPT_MAKASAR,
Malayalam => HB_SCRIPT_MALAYALAM,
Mandaic => HB_SCRIPT_MANDAIC,
Manichaean => HB_SCRIPT_MANICHAEAN,
Marchen => HB_SCRIPT_MARCHEN,
Masaram_Gondi => HB_SCRIPT_MASARAM_GONDI,
Medefaidrin => HB_SCRIPT_MEDEFAIDRIN,
Meetei_Mayek => HB_SCRIPT_MEETEI_MAYEK,
Mende_Kikakui => HB_SCRIPT_MENDE_KIKAKUI,
Meroitic_Cursive => HB_SCRIPT_MEROITIC_CURSIVE,
Meroitic_Hieroglyphs => HB_SCRIPT_MEROITIC_HIEROGLYPHS,
Miao => HB_SCRIPT_MIAO,
Modi => HB_SCRIPT_MODI,
Mongolian => HB_SCRIPT_MONGOLIAN,
Mro => HB_SCRIPT_MRO,
Multani => HB_SCRIPT_MULTANI,
Myanmar => HB_SCRIPT_MYANMAR,
Nabataean => HB_SCRIPT_NABATAEAN,
Nandinagari => HB_SCRIPT_NANDINAGARI,
New_Tai_Lue => HB_SCRIPT_NEW_TAI_LUE,
Newa => HB_SCRIPT_NEWA,
Nko => HB_SCRIPT_NKO,
Nushu => HB_SCRIPT_NUSHU,
Nyiakeng_Puachue_Hmong => HB_SCRIPT_NYIAKENG_PUACHUE_HMONG,
Ogham => HB_SCRIPT_OGHAM,
Ol_Chiki => HB_SCRIPT_OL_CHIKI,
Old_Hungarian => HB_SCRIPT_OLD_HUNGARIAN,
Old_Italic => HB_SCRIPT_OLD_ITALIC,
Old_North_Arabian => HB_SCRIPT_OLD_NORTH_ARABIAN,
Old_Permic => HB_SCRIPT_OLD_PERMIC,
Old_Persian => HB_SCRIPT_OLD_PERSIAN,
Old_Sogdian => HB_SCRIPT_OLD_SOGDIAN,
Old_South_Arabian => HB_SCRIPT_OLD_SOUTH_ARABIAN,
Old_Turkic => HB_SCRIPT_OLD_TURKIC,
Oriya => HB_SCRIPT_ORIYA,
Osage => HB_SCRIPT_OSAGE,
Osmanya => HB_SCRIPT_OSMANYA,
Pahawh_Hmong => HB_SCRIPT_PAHAWH_HMONG,
Palmyrene => HB_SCRIPT_PALMYRENE,
Pau_Cin_Hau => HB_SCRIPT_PAU_CIN_HAU,
Phags_Pa => HB_SCRIPT_PHAGS_PA,
Phoenician => HB_SCRIPT_PHOENICIAN,
Psalter_Pahlavi => HB_SCRIPT_PSALTER_PAHLAVI,
Rejang => HB_SCRIPT_REJANG,
Runic => HB_SCRIPT_RUNIC,
Samaritan => HB_SCRIPT_SAMARITAN,
Saurashtra => HB_SCRIPT_SAURASHTRA,
Sharada => HB_SCRIPT_SHARADA,
Shavian => HB_SCRIPT_SHAVIAN,
Siddham => HB_SCRIPT_SIDDHAM,
SignWriting => HB_SCRIPT_SIGNWRITING,
Sinhala => HB_SCRIPT_SINHALA,
Sogdian => HB_SCRIPT_SOGDIAN,
Sora_Sompeng => HB_SCRIPT_SORA_SOMPENG,
Soyombo => HB_SCRIPT_SOYOMBO,
Sundanese => HB_SCRIPT_SUNDANESE,
Syloti_Nagri => HB_SCRIPT_SYLOTI_NAGRI,
Syriac => HB_SCRIPT_SYRIAC,
Tagalog => HB_SCRIPT_TAGALOG,
Tagbanwa => HB_SCRIPT_TAGBANWA,
Tai_Le => HB_SCRIPT_TAI_LE,
Tai_Tham => HB_SCRIPT_TAI_THAM,
Tai_Viet => HB_SCRIPT_TAI_VIET,
Takri => HB_SCRIPT_TAKRI,
Tamil => HB_SCRIPT_TAMIL,
Tangut => HB_SCRIPT_TANGUT,
Telugu => HB_SCRIPT_TELUGU,
Thaana => HB_SCRIPT_THAANA,
Thai => HB_SCRIPT_THAI,
Tibetan => HB_SCRIPT_TIBETAN,
Tifinagh => HB_SCRIPT_TIFINAGH,
Tirhuta => HB_SCRIPT_TIRHUTA,
Ugaritic => HB_SCRIPT_UGARITIC,
Unknown => HB_SCRIPT_UNKNOWN,
Vai => HB_SCRIPT_VAI,
Warang_Citi => HB_SCRIPT_WARANG_CITI,
Wancho => HB_SCRIPT_WANCHO,
Yezidi => HB_SCRIPT_YEZIDI,
Yi => HB_SCRIPT_YI,
Zanabazar_Square => HB_SCRIPT_ZANABAZAR_SQUARE,
_ => HB_SCRIPT_UNKNOWN,
}
}
impl Shaper {
/// Calculate the layout metrics associated with the given text when painted in a specific
/// font.
pub(crate) fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
unsafe {
let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
hb_buffer_set_direction(
hb_buffer,
if options.flags.contains(ShapingFlags::RTL_FLAG) {
HB_DIRECTION_RTL
} else {
HB_DIRECTION_LTR
},
);
hb_buffer_set_script(hb_buffer, unicode_to_hb_script(options.script));
hb_buffer_add_utf8(
hb_buffer,
text.as_ptr() as *const c_char,
text.len() as c_int,
0,
text.len() as c_int,
);
let mut features = Vec::new();
if options
.flags
.contains(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG)
{
features.push(hb_feature_t {
tag: LIGA,
value: 0,
start: 0,
end: hb_buffer_get_length(hb_buffer),
})
}
if options
.flags
.contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
{
features.push(hb_feature_t {
tag: KERN,
value: 0,
start: 0,
end: hb_buffer_get_length(hb_buffer),
})
}
hb_shape(
self.hb_font,
hb_buffer,
features.as_mut_ptr(),
features.len() as u32,
);
self.save_glyph_results(text, options, glyphs, hb_buffer);
hb_buffer_destroy(hb_buffer);
}
}
fn save_glyph_results(
&self,
text: &str,
options: &ShapingOptions,
glyphs: &mut GlyphStore,
buffer: *mut hb_buffer_t,
) {
let glyph_data = unsafe { ShapedGlyphData::new(buffer) };
let glyph_count = glyph_data.len();
let byte_max = text.len();
debug!(
"Shaped text[byte count={}], got back {} glyph info records.",
byte_max, glyph_count
);
// make map of what chars have glyphs
let mut byte_to_glyph = vec![NO_GLYPH; byte_max];
debug!("(glyph idx) -> (text byte offset)");
for i in 0..glyph_data.len() {
let loc = glyph_data.byte_offset_of_glyph(i) as usize;
if loc < byte_max {
byte_to_glyph[loc] = i as i32;
} else {
debug!(
"ERROR: tried to set out of range byte_to_glyph: idx={}, glyph idx={}",
loc, i
);
}
debug!("{} -> {}", i, loc);
}
debug!("text: {:?}", text);
debug!("(char idx): char->(glyph index):");
for (i, ch) in text.char_indices() {
debug!("{}: {:?} --> {}", i, ch, byte_to_glyph[i]);
}
let mut glyph_span = 0..0;
let mut byte_range = 0..0;
let mut y_pos = Au(0);
// main loop over each glyph. each iteration usually processes 1 glyph and 1+ chars.
// in cases with complex glyph-character associations, 2+ glyphs and 1+ chars can be
// processed.
while glyph_span.start < glyph_count {
debug!("Processing glyph at idx={}", glyph_span.start);
glyph_span.end = glyph_span.start;
byte_range.end = glyph_data.byte_offset_of_glyph(glyph_span.start) as usize;
while byte_range.end < byte_max {
byte_range.end += 1;
// Extend the byte range to include any following byte without its own glyph.
while byte_range.end < byte_max && byte_to_glyph[byte_range.end] == NO_GLYPH {
byte_range.end += 1;
}
// Extend the glyph range to include all glyphs covered by bytes processed so far.
let mut max_glyph_idx = glyph_span.end;
for glyph_idx in &byte_to_glyph[byte_range.clone()] {
if *glyph_idx != NO_GLYPH {
max_glyph_idx = cmp::max(*glyph_idx as usize + 1, max_glyph_idx);
}
}
if max_glyph_idx > glyph_span.end {
glyph_span.end = max_glyph_idx;
debug!("Extended glyph span to {:?}", glyph_span);
}
// if there's just one glyph, then we don't need further checks.
if glyph_span.len() == 1 {
break;
}
// if no glyphs were found yet, extend the char byte range more.
if glyph_span.is_empty() {
continue;
}
// If byte_range now includes all the byte offsets found in glyph_span, then we
// have found a contiguous "cluster" and can stop extending it.
let mut all_glyphs_are_within_cluster: bool = true;
for j in glyph_span.clone() {
let loc = glyph_data.byte_offset_of_glyph(j) as usize;
if !(byte_range.start <= loc && loc < byte_range.end) {
all_glyphs_are_within_cluster = false;
break;
}
}
if all_glyphs_are_within_cluster {
break;
}
// Otherwise, the bytes we have seen so far correspond to a non-contiguous set of
// glyphs. Keep extending byte_range until we fill in all the holes in the glyph
// span or reach the end of the text.
}
assert!(!byte_range.is_empty());
assert!(!glyph_span.is_empty());
// Now byte_range is the ligature clump formed by the glyphs in glyph_span.
// We will save these glyphs to the glyph store at the index of the first byte.
let byte_idx = ByteIndex(byte_range.start as isize);
if glyph_span.len() == 1 {
// Fast path: 1-to-1 mapping of byte offset to single glyph.
//
// TODO(Issue #214): cluster ranges need to be computed before
// shaping, and then consulted here.
// for now, just pretend that every character is a cluster start.
// (i.e., pretend there are no combining character sequences).
// 1-to-1 mapping of character to glyph also treated as ligature start.
//
// NB: When we acquire the ability to handle ligatures that cross word boundaries,
// we'll need to do something special to handle `word-spacing` properly.
let character = text[byte_range.clone()].chars().next().unwrap();
if is_bidi_control(character) {
// Don't add any glyphs for bidi control chars
} else if character == '\t' {
// Treat tabs in pre-formatted text as a fixed number of spaces.
//
// TODO: Proper tab stops.
const TAB_COLS: i32 = 8;
let (space_glyph_id, space_advance) = glyph_space_advance(self.font);
let advance = Au::from_f64_px(space_advance) * TAB_COLS;
let data =
GlyphData::new(space_glyph_id, advance, Default::default(), true, true);
glyphs.add_glyph_for_byte_index(byte_idx, character, &data);
} else {
let shape = glyph_data.entry_for_glyph(glyph_span.start, &mut y_pos);
let advance = self.advance_for_shaped_glyph(shape.advance, character, options);
let data = GlyphData::new(shape.codepoint, advance, shape.offset, true, true);
glyphs.add_glyph_for_byte_index(byte_idx, character, &data);
}
} else {
// collect all glyphs to be assigned to the first character.
let mut datas = vec![];
for glyph_i in glyph_span.clone() {
let shape = glyph_data.entry_for_glyph(glyph_i, &mut y_pos);
datas.push(GlyphData::new(
shape.codepoint,
shape.advance,
shape.offset,
true, // treat as cluster start
glyph_i > glyph_span.start,
));
// all but first are ligature continuations
}
// now add the detailed glyph entry.
glyphs.add_glyphs_for_byte_index(byte_idx, &datas);
}
glyph_span.start = glyph_span.end;
byte_range.start = byte_range.end;
}
// this must be called after adding all glyph data; it sorts the
// lookup table for finding detailed glyphs by associated char index.
glyphs.finalize_changes();
}
fn advance_for_shaped_glyph(
&self,
mut advance: Au,
character: char,
options: &ShapingOptions,
) -> Au {
if let Some(letter_spacing) = options.letter_spacing {
advance += letter_spacing;
};
// CSS 2.1 § 16.4 states that "word spacing affects each space (U+0020) and non-breaking
// space (U+00A0) left in the text after the white space processing rules have been
// applied. The effect of the property on other word-separator characters is undefined."
// We elect to only space the two required code points.
if character == ' ' || character == '\u{a0}' {
// https://drafts.csswg.org/css-text-3/#word-spacing-property
advance += options.word_spacing;
}
advance
}
}
/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
struct FontFuncs(*mut hb_font_funcs_t);
unsafe impl Sync for FontFuncs {}
lazy_static! {
static ref HB_FONT_FUNCS: FontFuncs = unsafe {
let hb_funcs = hb_font_funcs_create();
hb_font_funcs_set_nominal_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None);
hb_font_funcs_set_glyph_h_advance_func(
hb_funcs,
Some(glyph_h_advance_func),
ptr::null_mut(),
None,
);
FontFuncs(hb_funcs)
};
}
extern "C" fn glyph_func(
_: *mut hb_font_t,
font_data: *mut c_void,
unicode: hb_codepoint_t,
glyph: *mut hb_codepoint_t,
_: *mut c_void,
) -> hb_bool_t {
let font: *const Font = font_data as *const Font;
assert!(!font.is_null());
unsafe {
match (*font).glyph_index(char::from_u32(unicode).unwrap()) {
Some(g) => {
*glyph = g as hb_codepoint_t;
true as hb_bool_t
},
None => false as hb_bool_t,
}
}
}
extern "C" fn glyph_h_advance_func(
_: *mut hb_font_t,
font_data: *mut c_void,
glyph: hb_codepoint_t,
_: *mut c_void,
) -> hb_position_t {
let font: *mut Font = font_data as *mut Font;
assert!(!font.is_null());
unsafe {
let advance = (*font).glyph_h_advance(glyph as GlyphId);
Shaper::float_to_fixed(advance)
}
}
fn glyph_space_advance(font: *const Font) -> (hb_codepoint_t, f64) {
let space_unicode = ' ';
let space_glyph: hb_codepoint_t = match unsafe { (*font).glyph_index(space_unicode) } {
Some(g) => g as hb_codepoint_t,
None => panic!("No space info"),
};
let space_advance = unsafe { (*font).glyph_h_advance(space_glyph as GlyphId) };
(space_glyph, space_advance)
}
// Callback to get a font table out of a font.
extern "C" fn font_table_func(
_: *mut hb_face_t,
tag: hb_tag_t,
user_data: *mut c_void,
) -> *mut hb_blob_t {
unsafe {
// NB: These asserts have security implications.
let font = user_data as *const Font;
assert!(!font.is_null());
// TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
match (*font).table_for_tag(tag as FontTableTag) {
None => ptr::null_mut(),
Some(font_table) => {
// `Box::into_raw` intentionally leaks the FontTable so we don't destroy the buffer
// while HarfBuzz is using it. When HarfBuzz is done with the buffer, it will pass
// this raw pointer back to `destroy_blob_func` which will deallocate the Box.
let font_table_ptr = Box::into_raw(Box::new(font_table));
let buf = (*font_table_ptr).buffer();
// HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed.
let blob = hb_blob_create(
buf.as_ptr() as *const c_char,
buf.len() as c_uint,
HB_MEMORY_MODE_READONLY,
font_table_ptr as *mut c_void,
Some(destroy_blob_func),
);
assert!(!blob.is_null());
blob
},
}
}
}
extern "C" fn destroy_blob_func(font_table_ptr: *mut c_void) {
unsafe {
drop(Box::from_raw(font_table_ptr as *mut FontTable));
}
}

View file

@ -0,0 +1,322 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::rc::Rc;
use app_units::Au;
use fonts::{
fallback_font_families, CSSFontFaceDescriptors, FallbackFontSelectionOptions, FontContext,
FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontIdentifier, FontSearchScope,
FontSource, FontTemplate, FontTemplateRef, FontTemplates,
};
use ipc_channel::ipc;
use net_traits::ResourceThreads;
use servo_arc::Arc;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
FamilyName, FontFamily, FontFamilyList, FontFamilyNameSyntax, FontSize, FontStretch, FontStyle,
FontWeight, SingleFontFamily,
};
use style::values::computed::{FontLanguageOverride, XLang};
use style::values::generics::font::LineHeight;
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, IdNamespace};
#[derive(Clone)]
struct MockFontCacheThread {
families: RefCell<HashMap<String, FontTemplates>>,
find_font_count: Rc<Cell<isize>>,
}
impl MockFontCacheThread {
fn new() -> MockFontCacheThread {
let mut csstest_ascii = FontTemplates::default();
Self::add_face(&mut csstest_ascii, "csstest-ascii");
let mut csstest_basic = FontTemplates::default();
Self::add_face(&mut csstest_basic, "csstest-basic-regular");
let mut fallback = FontTemplates::default();
Self::add_face(&mut fallback, "csstest-basic-regular");
let mut families = HashMap::new();
families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
families.insert("CSSTest Basic".to_owned(), csstest_basic);
families.insert(
fallback_font_families(FallbackFontSelectionOptions::default())[0].to_owned(),
fallback,
);
MockFontCacheThread {
families: RefCell::new(families),
find_font_count: Rc::new(Cell::new(0)),
}
}
fn identifier_for_font_name(name: &str) -> FontIdentifier {
FontIdentifier::Web(Self::url_for_font_name(name))
}
fn url_for_font_name(name: &str) -> ServoUrl {
let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
.iter()
.collect();
path.push(format!("{}.ttf", name));
ServoUrl::from_file_path(path).unwrap()
}
fn add_face(family: &mut FontTemplates, name: &str) {
let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
.iter()
.collect();
path.push(format!("{}.ttf", name));
let file = File::open(path).unwrap();
let data: Vec<u8> = file.bytes().map(|b| b.unwrap()).collect();
family.add_template(
FontTemplate::new_for_remote_web_font(
Self::url_for_font_name(name),
std::sync::Arc::new(data),
&CSSFontFaceDescriptors::new(name),
None,
)
.unwrap(),
);
}
}
impl FontSource for MockFontCacheThread {
fn find_matching_font_templates(
&self,
descriptor_to_match: Option<&FontDescriptor>,
font_family_name: &FontFamilyName,
) -> Vec<FontTemplateRef> {
self.find_font_count.set(self.find_font_count.get() + 1);
self.families
.borrow_mut()
.get_mut(font_family_name.name())
.map(|family| family.find_for_descriptor(descriptor_to_match))
.unwrap_or_default()
}
fn get_system_font_instance(
&self,
_: FontIdentifier,
_: Au,
_: FontInstanceFlags,
) -> FontInstanceKey {
FontInstanceKey(IdNamespace(0), 0)
}
fn get_web_font(&self, _: std::sync::Arc<Vec<u8>>, _: u32) -> webrender_api::FontKey {
FontKey(IdNamespace(0), 0)
}
fn get_web_font_instance(
&self,
_: webrender_api::FontKey,
_: f32,
_: FontInstanceFlags,
) -> FontInstanceKey {
FontInstanceKey(IdNamespace(0), 0)
}
}
fn style() -> FontStyleStruct {
let mut style = FontStyleStruct {
font_family: FontFamily::serif(),
font_style: FontStyle::NORMAL,
font_variant_caps: FontVariantCaps::Normal,
font_weight: FontWeight::normal(),
font_size: FontSize::medium(),
font_stretch: FontStretch::hundred(),
hash: 0,
font_language_override: FontLanguageOverride::normal(),
line_height: LineHeight::Normal,
_x_lang: XLang::get_initial_value(),
};
style.compute_font_hash();
style
}
fn font_family(names: Vec<&str>) -> FontFamily {
let names: Vec<SingleFontFamily> = names
.into_iter()
.map(|name| {
SingleFontFamily::FamilyName(FamilyName {
name: Atom::from(name),
syntax: FontFamilyNameSyntax::Quoted,
})
})
.collect();
FontFamily {
families: FontFamilyList {
list: names.into_boxed_slice(),
},
is_system_font: false,
is_initial: false,
}
}
fn mock_resource_threads() -> ResourceThreads {
let (core_sender, _) = ipc::channel().unwrap();
let (storage_sender, _) = ipc::channel().unwrap();
ResourceThreads::new(core_sender, storage_sender)
}
#[test]
fn test_font_group_is_cached_by_style() {
let source = MockFontCacheThread::new();
let context = FontContext::new(source, mock_resource_threads());
let style1 = style();
let mut style2 = style();
style2.set_font_style(FontStyle::ITALIC);
assert!(
std::ptr::eq(
&*context.font_group(Arc::new(style1.clone())).read(),
&*context.font_group(Arc::new(style1.clone())).read()
),
"the same font group should be returned for two styles with the same hash"
);
assert!(
!std::ptr::eq(
&*context.font_group(Arc::new(style1.clone())).read(),
&*context.font_group(Arc::new(style2.clone())).read()
),
"different font groups should be returned for two styles with different hashes"
)
}
#[test]
fn test_font_group_find_by_codepoint() {
let source = MockFontCacheThread::new();
let count = source.find_font_count.clone();
let mut context = FontContext::new(source, mock_resource_threads());
let mut style = style();
style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));
let group = context.font_group(Arc::new(style));
let font = group
.write()
.find_by_codepoint(&mut context, 'a', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii")
);
assert_eq!(
count.get(),
1,
"only the first font in the list should have been loaded"
);
let font = group
.write()
.find_by_codepoint(&mut context, 'a', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii")
);
assert_eq!(
count.get(),
1,
"we shouldn't load the same font a second time"
);
let font = group
.write()
.find_by_codepoint(&mut context, 'á', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-basic-regular")
);
assert_eq!(count.get(), 2, "both fonts should now have been loaded");
}
#[test]
fn test_font_fallback() {
let source = MockFontCacheThread::new();
let mut context = FontContext::new(source, mock_resource_threads());
let mut style = style();
style.set_font_family(font_family(vec!["CSSTest ASCII"]));
let group = context.font_group(Arc::new(style));
let font = group
.write()
.find_by_codepoint(&mut context, 'a', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii"),
"a family in the group should be used if there is a matching glyph"
);
let font = group
.write()
.find_by_codepoint(&mut context, 'á', None)
.unwrap();
assert_eq!(
font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-basic-regular"),
"a fallback font should be used if there is no matching glyph in the group"
);
}
#[test]
fn test_font_template_is_cached() {
let source = MockFontCacheThread::new();
let count = source.find_font_count.clone();
let context = FontContext::new(source, mock_resource_threads());
let mut font_descriptor = FontDescriptor {
weight: FontWeight::normal(),
stretch: FontStretch::hundred(),
style: FontStyle::normal(),
variant: FontVariantCaps::Normal,
pt_size: Au(10),
};
let family_descriptor =
FontFamilyDescriptor::new(FontFamilyName::from("CSSTest Basic"), FontSearchScope::Any);
let font_template = context.matching_templates(&font_descriptor, &family_descriptor)[0].clone();
let font1 = context
.font(font_template.clone(), &font_descriptor)
.unwrap();
font_descriptor.pt_size = Au(20);
let font2 = context
.font(font_template.clone(), &font_descriptor)
.unwrap();
assert_ne!(
font1.descriptor.pt_size, font2.descriptor.pt_size,
"the same font should not have been returned"
);
assert_eq!(
count.get(),
1,
"we should only have fetched the template data from the cache thread once"
);
}

View file

@ -0,0 +1,69 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// Test doesn't yet run on Mac, see https://github.com/servo/servo/pull/19928 for explanation.
#[cfg(not(target_os = "macos"))]
#[test]
fn test_font_template_descriptor() {
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::sync::Arc;
use fonts::platform::font::PlatformFont;
use fonts::{FontIdentifier, FontTemplateDescriptor, PlatformFontMethods};
use servo_url::ServoUrl;
use style::values::computed::font::{FontStretch, FontStyle, FontWeight};
fn descriptor(filename: &str) -> FontTemplateDescriptor {
let mut path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"tests",
"support",
"dejavu-fonts-ttf-2.37",
"ttf",
]
.iter()
.collect();
path.push(format!("{}.ttf", filename));
let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path.clone()).unwrap());
let file = File::open(path.clone()).unwrap();
let data = file.bytes().map(|b| b.unwrap()).collect();
let handle = PlatformFont::new_from_data(identifier, Arc::new(data), 0, None).unwrap();
handle.descriptor()
}
assert_eq!(
descriptor("DejaVuSans"),
FontTemplateDescriptor::new(
FontWeight::NORMAL,
FontStretch::hundred(),
FontStyle::NORMAL,
)
);
assert_eq!(
descriptor("DejaVuSans-Bold"),
FontTemplateDescriptor::new(FontWeight::BOLD, FontStretch::hundred(), FontStyle::NORMAL,)
);
assert_eq!(
descriptor("DejaVuSans-Oblique"),
FontTemplateDescriptor::new(
FontWeight::NORMAL,
FontStretch::hundred(),
FontStyle::ITALIC,
)
);
assert_eq!(
descriptor("DejaVuSansCondensed-BoldOblique"),
FontTemplateDescriptor::new(
FontWeight::BOLD,
FontStretch::from_percentage(0.875),
FontStyle::ITALIC,
)
);
}

View file

@ -0,0 +1,94 @@
Copyright (c) 2003-2008 SIL International (http://www.sil.org/),
with Reserved Font Names "Gentium" and "SIL".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 1 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that the font
names of derivative works are changed. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,25 @@
These fonts are a copy of the CSSTest fonts in web-platform-tests, so that we
can use them for unit-testing our font code. Here is the README from
web-platform-tests:
-----
These fonts were created to support the testing of the font features
in CSS, and are required to run some of the tests for those features.
The fonts are modified versions of Gentium Basic, licensed by SIL under
the Open Font License which allows modifications as long as the terms
of the license are met.
The original fonts were used to create the family 'CSSTest Basic'. This
family has four faces and can be used for testing bold / italics.
A subsetted version of this font with only glyphs for basic ASCII
characters is 'CSSTest ASCII'. This was used to make the other
variations. Most of the modications are to the name table and character
maps, for the most part glyphs were not modified.
The fonts are available for download both individually and as a
ZIP package below.
The files test.html and test.xhtml test that the fonts have been
correctly installed.

View file

@ -0,0 +1,57 @@
abysta at yandex.ru
Adrian Schroeter
Aleksey Chalabyan
Andrey Valentinovich Panov
Ben Laenen
Besarion Gugushvili
Bhikkhu Pesala
Clayborne Arevalo
Dafydd Harries
Danilo Segan
Davide Viti
David Jez
David Lawrence Ramsey
Denis Jacquerye
Dwayne Bailey
Eugeniy Meshcheryakov
Frédéric Wang
Gee Fung Sit
Heikki Lindroos
James Cloos
James Crippen
John Karp
Keenan Pepper
Lars Næsbye Christensen
Lior Halphon
MaEr
Mashrab Kuvatov
Max Berger
Mederic Boquien
Michael Everson
MihailJP
Misu Moldovan
Nguyen Thai Ngoc Duy
Nicolas Mailhot
Norayr Chilingarian
Olleg Samoylov
Ognyan Kulev
Ondrej Koala Vacha
Peter Cernak
Remy Oudompheng
Roozbeh Pournader
Rouben Hakobian
Sahak Petrosyan
Sami Tarazi
Sander Vesik
Stepan Roh
Stephen Hartke
Steve Tinney
Tavmjong Bah
Thomas Henlich
Tim May
Valentin Stoykov
Vasek Stodulka
Wesley Transue
Yoshiki Ohshima
$Id$

View file

@ -0,0 +1,3 @@
See http://dejavu.sourceforge.net/wiki/index.php/Bugs
$Id$

View file

@ -0,0 +1,187 @@
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
------------------------------
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and
associated documentation files (the "Font Software"), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.
TeX Gyre DJV Math
-----------------
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
(on behalf of TeX users groups) are in public domain.
Letters imported from Euler Fraktur from AMSfonts are (c) American
Mathematical Society (see below).
Bitstream Vera Fonts Copyright
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license (“Fonts”) and associated
documentation
files (the “Font Software”), to reproduce and distribute the Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute,
and/or sell copies of the Font Software, and to permit persons to whom
the Font Software is furnished to do so, subject to the following
conditions:
The above copyright and trademark notices and this permission notice
shall be
included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional
glyphs or characters may be added to the Fonts, only if the fonts are
renamed
to names not containing either the words “Bitstream” or the word “Vera”.
This License becomes null and void to the extent applicable to Fonts or
Font Software
that has been modified and is distributed under the “Bitstream Vera”
names.
The Font Software may be sold as part of a larger software package but
no copy
of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
INABILITY TO USE
THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of GNOME, the GNOME
Foundation,
and Bitstream Inc., shall not be used in advertising or otherwise to promote
the sale, use or other dealings in this Font Software without prior written
authorization from the GNOME Foundation or Bitstream Inc., respectively.
For further information, contact: fonts at gnome dot org.
AMSFonts (v. 2.2) copyright
The PostScript Type 1 implementation of the AMSFonts produced by and
previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
available for general use. This has been accomplished through the
cooperation
of a consortium of scientific publishers with Blue Sky Research and Y&Y.
Members of this consortium include:
Elsevier Science IBM Corporation Society for Industrial and Applied
Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
In order to assure the authenticity of these fonts, copyright will be
held by
the American Mathematical Society. This is not meant to restrict in any way
the legitimate use of the fonts, such as (but not limited to) electronic
distribution of documents containing these fonts, inclusion of these fonts
into other public domain or commercial font collections or computer
applications, use of the outline data to create derivative fonts and/or
faces, etc. However, the AMS does require that the AMS copyright notice be
removed from any derivative versions of the fonts which have been altered in
any way. In addition, to ensure the fidelity of TeX documents using Computer
Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
has requested that any alterations which yield different font metrics be
given a different name.
$Id$

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
[![Build Status](https://travis-ci.org/dejavu-fonts/dejavu-fonts.svg)](https://travis-ci.org/dejavu-fonts/dejavu-fonts)
DejaVu fonts 2.37 (c)2004-2016 DejaVu fonts team
------------------------------------------------
The DejaVu fonts are a font family based on the Bitstream Vera Fonts
(http://gnome.org/fonts/). Its purpose is to provide a wider range of
characters (see status.txt for more information) while maintaining the
original look and feel.
DejaVu fonts are based on Bitstream Vera fonts version 1.10.
Available fonts (Sans = sans serif, Mono = monospaced):
DejaVu Sans Mono
DejaVu Sans Mono Bold
DejaVu Sans Mono Bold Oblique
DejaVu Sans Mono Oblique
DejaVu Sans
DejaVu Sans Bold
DejaVu Sans Bold Oblique
DejaVu Sans Oblique
DejaVu Sans ExtraLight (experimental)
DejaVu Serif
DejaVu Serif Bold
DejaVu Serif Bold Italic (experimental)
DejaVu Serif Italic (experimental)
DejaVu Sans Condensed (experimental)
DejaVu Sans Condensed Bold (experimental)
DejaVu Sans Condensed Bold Oblique (experimental)
DejaVu Sans Condensed Oblique (experimental)
DejaVu Serif Condensed (experimental)
DejaVu Serif Condensed Bold (experimental)
DejaVu Serif Condensed Bold Italic (experimental)
DejaVu Serif Condensed Italic (experimental)
DejaVu Math TeX Gyre
All fonts are also available as derivative called DejaVu LGC with support
only for Latin, Greek and Cyrillic scripts.
For license information see LICENSE. What's new is described in NEWS. Known
bugs are in BUGS. All authors are mentioned in AUTHORS.
Fonts are published in source form as SFD files (Spline Font Database from
FontForge - http://fontforge.sf.net/) and in compiled form as TTF files
(TrueType fonts).
For more information go to http://dejavu.sourceforge.net/.
Characters from Arev fonts, Copyright (c) 2006 by Tavmjong Bah:
---------------------------
U+01BA, U+01BF, U+01F7, U+021C-U+021D, U+0220, U+0222-U+0223,
U+02B9, U+02BA, U+02BD, U+02C2-U+02C5, U+02d4-U+02D5,
U+02D7, U+02EC-U+02EE, U+0346-U+034E, U+0360, U+0362,
U+03E2-03EF, U+0460-0463, U+0466-U+0486, U+0488-U+0489, U+04A8-U+04A9,
U+0500-U+050F, U+2055-205E, U+20B0, U+20B2-U+20B3, U+2102, U+210D, U+210F,
U+2111, U+2113, U+2115, U+2118-U+211A, U+211C-U+211D, U+2124, U+2135,
U+213C-U+2140, U+2295-U+2298, U+2308-U+230B, U+26A2-U+26B1, U+2701-U+2704,
U+2706-U+2709, U+270C-U+274B, U+2758-U+275A, U+2761-U+2775, U+2780-U+2794,
U+2798-U+27AF, U+27B1-U+27BE, U+FB05-U+FB06
DejaVu Math TeX Gyre
--------------------
TeX Gyre DJV Math by B. Jackowski, P. Strzelczyk and P. Pianowski
(on behalf of TeX users groups).
$Id$

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<fontconfig>
<!-- /etc/fonts/conf.d/20-unhint-small-dejavu-sans-mono.conf
Disable hinting manually at smaller sizes (< 8ppem)
This is a copy of the Bistream Vera fonts fonts rule, as DejaVu is
derived from Vera.
The Bistream Vera fonts have GASP entries suggesting that hinting be
disabled below 8 ppem, but FreeType ignores those, preferring to use
the data found in the instructed hints. The initial Vera release
didn't include the right instructions in the 'prep' table.
-->
<match target="font">
<test name="family">
<string>DejaVu Sans Mono</string>
</test>
<test compare="less" name="pixelsize">
<double>7.5</double>
</test>
<edit name="hinting">
<bool>false</bool>
</edit>
</match>
</fontconfig>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<fontconfig>
<!-- /etc/fonts/conf.d/20-unhint-small-dejavu-sans.conf
Disable hinting manually at smaller sizes (< 8ppem)
This is a copy of the Bistream Vera fonts fonts rule, as DejaVu is
derived from Vera.
The Bistream Vera fonts have GASP entries suggesting that hinting be
disabled below 8 ppem, but FreeType ignores those, preferring to use
the data found in the instructed hints. The initial Vera release
didn't include the right instructions in the 'prep' table.
-->
<match target="font">
<test name="family">
<string>DejaVu Sans</string>
</test>
<test compare="less" name="pixelsize">
<double>7.5</double>
</test>
<edit name="hinting">
<bool>false</bool>
</edit>
</match>
</fontconfig>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<fontconfig>
<!-- /etc/fonts/conf.d/20-unhint-small-dejavu-serif.conf
Disable hinting manually at smaller sizes (< 8ppem)
This is a copy of the Bistream Vera fonts fonts rule, as DejaVu is
derived from Vera.
The Bistream Vera fonts have GASP entries suggesting that hinting be
disabled below 8 ppem, but FreeType ignores those, preferring to use
the data found in the instructed hints. The initial Vera release
didn't include the right instructions in the 'prep' table.
-->
<match target="font">
<test name="family">
<string>DejaVu Serif</string>
</test>
<test compare="less" name="pixelsize">
<double>7.5</double>
</test>
<edit name="hinting">
<bool>false</bool>
</edit>
</match>
</fontconfig>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<!-- /etc/fonts/conf.d/57-dejavu-sans-mono.conf
Define aliasing and other fontconfig settings for
DejaVu Sans Mono.
© 2006-2008 Nicolas Mailhot <nicolas.mailhot at laposte.net>
-->
<fontconfig>
<!-- Font substitution rules -->
<alias binding="same">
<family>Bepa Mono</family>
<accept>
<family>DejaVu Sans Mono</family>
</accept>
</alias>
<alias binding="same">
<family>Bitstream Prima Sans Mono</family>
<accept>
<family>DejaVu Sans Mono</family>
</accept>
</alias>
<alias binding="same">
<family>Bitstream Vera Sans Mono</family>
<accept>
<family>DejaVu Sans Mono</family>
</accept>
</alias>
<alias binding="same">
<family>DejaVu LGC Sans Mono</family>
<accept>
<family>DejaVu Sans Mono</family>
</accept>
</alias>
<alias binding="same">
<family>Olwen Sans Mono</family>
<accept>
<family>DejaVu Sans Mono</family>
</accept>
</alias>
<alias binding="same">
<family>SUSE Sans Mono</family>
<accept>
<family>DejaVu Sans Mono</family>
</accept>
</alias>
<!-- Generic name assignment -->
<alias>
<family>DejaVu Sans Mono</family>
<default>
<family>monospace</family>
</default>
</alias>
<!-- Generic name aliasing -->
<alias>
<family>monospace</family>
<prefer>
<family>DejaVu Sans Mono</family>
</prefer>
</alias>
</fontconfig>

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<!-- /etc/fonts/conf.d/57-dejavu-sans.conf
Define aliasing and other fontconfig settings for
DejaVu Sans.
© 2006-2008 Nicolas Mailhot <nicolas.mailhot at laposte.net>
-->
<fontconfig>
<!-- Font substitution rules -->
<alias binding="same">
<family>Arev Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>Bepa</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>Bitstream Prima Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>Bitstream Vera Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>DejaVu LGC Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>Hunky Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>Olwen Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>SUSE Sans</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<alias binding="same">
<family>Verajja</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<!-- In case VerajjaPDA stops declaring itself as Verajja -->
<alias binding="same">
<family>VerajjaPDA</family>
<accept>
<family>DejaVu Sans</family>
</accept>
</alias>
<!-- Generic name assignment -->
<alias>
<family>DejaVu Sans</family>
<default>
<family>sans-serif</family>
</default>
</alias>
<!-- Generic name aliasing -->
<alias>
<family>sans-serif</family>
<prefer>
<family>DejaVu Sans</family>
</prefer>
</alias>
</fontconfig>

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<!-- /etc/fonts/conf.d/57-dejavu-serif.conf
Define aliasing and other fontconfig settings for
DejaVu Serif.
© 2006-2008 Nicolas Mailhot <nicolas.mailhot at laposte.net>
-->
<fontconfig>
<!-- Font substitution rules -->
<alias binding="same">
<family>Bitstream Prima Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<alias binding="same">
<family>Bitstream Vera Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<alias binding="same">
<family>DejaVu LGC Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<alias binding="same">
<family>Hunky Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<alias binding="same">
<family>Olwen Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<alias binding="same">
<family>SUSE Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<!-- In case Verajja Serif stops declaring itself as DejaVu Serif -->
<alias binding="same">
<family>Verajja Serif</family>
<accept>
<family>DejaVu Serif</family>
</accept>
</alias>
<!-- Generic name assignment -->
<alias>
<family>DejaVu Serif</family>
<default>
<family>serif</family>
</default>
</alias>
<!-- Generic name aliasing -->
<alias>
<family>serif</family>
<prefer>
<family>DejaVu Serif</family>
</prefer>
</alias>
</fontconfig>

View file

@ -0,0 +1,250 @@
This is the language coverage file for DejaVu fonts
($Id$)
Sans Serif Sans Mono
aa Afar 100% (62/62) 100% (62/62) 100% (62/62)
ab Abkhazia 100% (90/90) 93% (84/90) 84% (76/90)
af Afrikaans 100% (69/69) 100% (69/69) 100% (69/69)
ak Akan 100% (73/73) 100% (73/73) 100% (73/73)
am Amharic (0/264) (0/264) (0/264)
an Aragonese 100% (66/66) 100% (66/66) 100% (66/66)
ar Arabic 100% (36/36) (0/36) 100% (36/36)
as Assamese (0/64) (0/64) (0/64)
ast Asturian/Bable/Leonese/Asturleonese 100% (70/70) 100% (70/70) 100% (70/70)
av Avaric 100% (67/67) 100% (67/67) 100% (67/67)
ay Aymara 100% (60/60) 100% (60/60) 100% (60/60)
az-az Azerbaijani in Azerbaijan 100% (66/66) 100% (66/66) 100% (66/66)
az-ir Azerbaijani in Iran 100% (40/40) (0/40) 100% (40/40)
ba Bashkir 100% (82/82) 100% (82/82) 97% (80/82)
be Byelorussian 100% (68/68) 100% (68/68) 100% (68/68)
ber-dz Berber in Algeria 100% (70/70) 100% (70/70) 100% (70/70)
ber-ma Berber in Morocco 100% (32/32) (0/32) (0/32)
bg Bulgarian 100% (60/60) 100% (60/60) 100% (60/60)
bh Bihari (Devanagari script) (0/68) (0/68) (0/68)
bho Bhojpuri (Devanagari script) (0/68) (0/68) (0/68)
bi Bislama 100% (58/58) 100% (58/58) 100% (58/58)
bin Edo or Bini 100% (78/78) 100% (78/78) 100% (78/78)
bm Bambara 100% (60/60) 100% (60/60) 100% (60/60)
bn Bengali (0/63) (0/63) (0/63)
bo Tibetan (0/95) (0/95) (0/95)
br Breton 100% (64/64) 100% (64/64) 100% (64/64)
brx Bodo (Devanagari script) (0/82) (0/82) (0/82)
bs Bosnian 100% (62/62) 100% (62/62) 100% (62/62)
bua Buriat (Buryat) 100% (70/70) 100% (70/70) 100% (70/70)
byn Blin/Bilin (0/255) (0/255) (0/255)
ca Catalan 100% (74/74) 100% (74/74) 100% (74/74)
ce Chechen 100% (67/67) 100% (67/67) 100% (67/67)
ch Chamorro 100% (58/58) 100% (58/58) 100% (58/58)
chm Mari (Lower Cheremis / Upper Cheremis) 100% (76/76) 100% (76/76) 100% (76/76)
chr Cherokee (0/85) (0/85) (0/85)
co Corsican 100% (84/84) 100% (84/84) 100% (84/84)
crh Crimean Tatar/Crimean Turkish 100% (68/68) 100% (68/68) 100% (68/68)
cs Czech 100% (82/82) 100% (82/82) 100% (82/82)
csb Kashubian 100% (74/74) 100% (74/74) 100% (74/74)
cu Old Church Slavonic 100% (103/103) 90% (93/103) 78% (81/103)
cv Chuvash 100% (74/74) 100% (74/74) 100% (74/74)
cy Welsh 100% (78/78) 100% (78/78) 100% (78/78)
da Danish 100% (70/70) 100% (70/70) 100% (70/70)
de German 100% (59/59) 100% (59/59) 100% (59/59)
doi Dogri (0/85) (0/85) (0/85)
dv Divehi/Dhivehi/Maldivian (0/49) (0/49) (0/49)
dz Dzongkha (0/95) (0/95) (0/95)
ee Ewe 100% (99/99) 100% (99/99) 100% (99/99)
el Greek 100% (69/69) 100% (69/69) 100% (69/69)
en English 100% (72/72) 100% (72/72) 100% (72/72)
eo Esperanto 100% (64/64) 100% (64/64) 100% (64/64)
es Spanish 100% (66/66) 100% (66/66) 100% (66/66)
et Estonian 100% (64/64) 100% (64/64) 100% (64/64)
eu Basque 100% (56/56) 100% (56/56) 100% (56/56)
fa Persian 100% (40/40) (0/40) 100% (40/40)
fat Fanti 100% (73/73) 100% (73/73) 100% (73/73)
ff Fulah (Fula) 100% (62/62) 100% (62/62) 100% (62/62)
fi Finnish 100% (62/62) 100% (62/62) 100% (62/62)
fil Filipino 100% (84/84) 100% (84/84) 100% (84/84)
fj Fijian 100% (52/52) 100% (52/52) 100% (52/52)
fo Faroese 100% (68/68) 100% (68/68) 100% (68/68)
fr French 100% (84/84) 100% (84/84) 100% (84/84)
fur Friulian 100% (66/66) 100% (66/66) 100% (66/66)
fy Frisian 100% (75/75) 100% (75/75) 100% (75/75)
ga Irish 100% (80/80) 100% (80/80) 100% (80/80)
gd Scots Gaelic 100% (70/70) 100% (70/70) 100% (70/70)
gez Ethiopic (Geez) (0/218) (0/218) (0/218)
gl Galician 100% (66/66) 100% (66/66) 100% (66/66)
gn Guarani 100% (70/70) 100% (70/70) 100% (70/70)
gu Gujarati (0/68) (0/68) (0/68)
gv Manx Gaelic 100% (54/54) 100% (54/54) 100% (54/54)
ha Hausa 100% (60/60) 100% (60/60) 100% (60/60)
haw Hawaiian 100% (63/63) 100% (63/63) 100% (63/63)
he Hebrew 100% (27/27) (0/27) (0/27)
hi Hindi (Devanagari script) (0/68) (0/68) (0/68)
hne Chhattisgarhi (0/68) (0/68) (0/68)
ho Hiri Motu 100% (52/52) 100% (52/52) 100% (52/52)
hr Croatian 100% (62/62) 100% (62/62) 100% (62/62)
hsb Upper Sorbian 100% (72/72) 100% (72/72) 100% (72/72)
ht Haitian/Haitian Creole 100% (56/56) 100% (56/56) 100% (56/56)
hu Hungarian 100% (70/70) 100% (70/70) 100% (70/70)
hy Armenian 100% (77/77) 100% (77/77) 100% (77/77)
hz Herero 100% (57/57) 100% (57/57) 100% (57/57)
ia Interlingua 100% (52/52) 100% (52/52) 100% (52/52)
id Indonesian 100% (54/54) 100% (54/54) 100% (54/54)
ie Interlingue 100% (52/52) 100% (52/52) 100% (52/52)
ig Igbo 100% (58/58) 100% (58/58) 100% (58/58)
ii Sichuan Yi/Nuosu (0/1165) (0/1165) (0/1165)
ik Inupiaq (Inupiak, Eskimo) 100% (68/68) 100% (68/68) 100% (68/68)
io Ido 100% (52/52) 100% (52/52) 100% (52/52)
is Icelandic 100% (70/70) 100% (70/70) 100% (70/70)
it Italian 100% (72/72) 100% (72/72) 100% (72/72)
iu Inuktitut 100% (161/161) (0/161) (0/161)
ja Japanese (0/2314) (0/2314) (0/2314)
jv Javanese 100% (56/56) 100% (56/56) 100% (56/56)
ka Georgian 100% (33/33) 100% (33/33) 100% (33/33)
kaa Kara-Kalpak (Karakalpak) 100% (78/78) 100% (78/78) 100% (78/78)
kab Kabyle 100% (70/70) 100% (70/70) 100% (70/70)
ki Kikuyu 100% (56/56) 100% (56/56) 100% (56/56)
kj Kuanyama/Kwanyama 100% (52/52) 100% (52/52) 100% (52/52)
kk Kazakh 100% (77/77) 100% (77/77) 100% (77/77)
kl Greenlandic 100% (81/81) 100% (81/81) 100% (81/81)
km Central Khmer (0/63) (0/63) (0/63)
kn Kannada (0/70) (0/70) (0/70)
ko Korean (0/2442) (0/2442) (0/2442)
kok Kokani (Devanagari script) (0/68) (0/68) (0/68)
kr Kanuri 100% (56/56) 100% (56/56) 100% (56/56)
ks Kashmiri 78% (26/33) (0/33) 69% (23/33)
ku-am Kurdish in Armenia 100% (64/64) 100% (64/64) 100% (64/64)
ku-iq Kurdish in Iraq 100% (32/32) (0/32) 87% (28/32)
ku-ir Kurdish in Iran 100% (32/32) (0/32) 87% (28/32)
ku-tr Kurdish in Turkey 100% (62/62) 100% (62/62) 100% (62/62)
kum Kumyk 100% (66/66) 100% (66/66) 100% (66/66)
kv Komi (Komi-Permyak/Komi-Siryan) 100% (70/70) 100% (70/70) 100% (70/70)
kw Cornish 100% (64/64) 100% (64/64) 100% (64/64)
kwm Kwambi 100% (52/52) 100% (52/52) 100% (52/52)
ky Kirgiz 100% (70/70) 100% (70/70) 100% (70/70)
la Latin 100% (68/68) 100% (68/68) 100% (68/68)
lah Lahnda 92% (25/27) (0/27) 85% (23/27)
lb Luxembourgish (Letzeburgesch) 100% (75/75) 100% (75/75) 100% (75/75)
lez Lezghian (Lezgian) 100% (67/67) 100% (67/67) 100% (67/67)
lg Ganda 100% (54/54) 100% (54/54) 100% (54/54)
li Limburgan/Limburger/Limburgish 100% (62/62) 100% (62/62) 100% (62/62)
ln Lingala 100% (81/81) 100% (81/81) 100% (81/81)
lo Lao 100% (55/55) (0/55) 83% (46/55)
lt Lithuanian 100% (70/70) 100% (70/70) 100% (70/70)
lv Latvian 100% (78/78) 100% (78/78) 100% (78/78)
mai Maithili (Devanagari script) (0/68) (0/68) (0/68)
mg Malagasy 100% (56/56) 100% (56/56) 100% (56/56)
mh Marshallese 100% (62/62) 100% (62/62) 100% (62/62)
mi Maori 100% (64/64) 100% (64/64) 100% (64/64)
mk Macedonian 100% (42/42) 100% (42/42) 100% (42/42)
ml Malayalam (0/68) (0/68) (0/68)
mn-cn Mongolian in China (0/130) (0/130) (0/130)
mn-mn Mongolian in Mongolia 100% (70/70) 100% (70/70) 100% (70/70)
mni Maniputi (0/78) (0/78) (0/78)
mo Moldavian 100% (128/128) 100% (128/128) 100% (128/128)
mr Marathi (Devanagari script) (0/68) (0/68) (0/68)
ms Malay 100% (52/52) 100% (52/52) 100% (52/52)
mt Maltese 100% (72/72) 100% (72/72) 100% (72/72)
my Burmese (Myanmar) (0/48) (0/48) (0/48)
na Nauru 100% (60/60) 100% (60/60) 100% (60/60)
nb Norwegian Bokmal 100% (70/70) 100% (70/70) 100% (70/70)
nds Low Saxon 100% (59/59) 100% (59/59) 100% (59/59)
ne Nepali (0/72) (0/72) (0/72)
ng Ndonga 100% (52/52) 100% (52/52) 100% (52/52)
nl Dutch 100% (82/82) 100% (82/82) 100% (82/82)
nn Norwegian Nynorsk 100% (76/76) 100% (76/76) 100% (76/76)
no Norwegian (Bokmal) 100% (70/70) 100% (70/70) 100% (70/70)
nqo N'Ko 91% (54/59) (0/59) (0/59)
nr Ndebele, South 100% (52/52) 100% (52/52) 100% (52/52)
nso Northern Sotho 100% (58/58) 100% (58/58) 100% (58/58)
nv Navajo/Navaho 100% (72/72) 100% (72/72) 100% (72/72)
ny Chichewa 100% (54/54) 100% (54/54) 100% (54/54)
oc Occitan 100% (70/70) 100% (70/70) 100% (70/70)
om Oromo or Galla 100% (52/52) 100% (52/52) 100% (52/52)
or Oriya (0/68) (0/68) (0/68)
os Ossetic 100% (66/66) 100% (66/66) 100% (66/66)
ota Ottoman Turkish 100% (37/37) (0/37) 97% (36/37)
pa Panjabi/Punjabi (0/63) (0/63) (0/63)
pa-pk Panjabi/Punjabi in Pakistan 92% (25/27) (0/27) 85% (23/27)
pap-an Papiamento in Netherlands Antilles 100% (72/72) 100% (72/72) 100% (72/72)
pap-aw Papiamento in Aruba 100% (54/54) 100% (54/54) 100% (54/54)
pes Western Farsi 100% (40/40) (0/40) 100% (40/40)
pl Polish 100% (70/70) 100% (70/70) 100% (70/70)
prs Dari/Eastern Farsi 100% (40/40) (0/40) 100% (40/40)
ps-af Pashto in Afghanistan 97% (48/49) (0/49) 77% (38/49)
ps-pk Pashto in Pakistan 95% (47/49) (0/49) 75% (37/49)
pt Portuguese 100% (82/82) 100% (82/82) 100% (82/82)
qu Quechua 100% (55/55) 100% (55/55) 100% (55/55)
quz Cusco Quechua 100% (55/55) 100% (55/55) 100% (55/55)
rm Rhaeto-Romance (Romansch) 100% (66/66) 100% (66/66) 100% (66/66)
rn Rundi 100% (52/52) 100% (52/52) 100% (52/52)
ro Romanian 100% (62/62) 100% (62/62) 100% (62/62)
ru Russian 100% (66/66) 100% (66/66) 100% (66/66)
rw Kinyarwanda 100% (52/52) 100% (52/52) 100% (52/52)
sa Sanskrit (Devanagari script) (0/68) (0/68) (0/68)
sah Yakut 100% (76/76) 100% (76/76) 100% (76/76)
sat Santali (Devanagari script) (0/70) (0/70) (0/70)
sc Sardinian 100% (62/62) 100% (62/62) 100% (62/62)
sco Scots 100% (56/56) 100% (56/56) 100% (56/56)
sd Sindhi 100% (54/54) (0/54) 79% (43/54)
se North Sami 100% (66/66) 100% (66/66) 100% (66/66)
sel Selkup (Ostyak-Samoyed) 100% (66/66) 100% (66/66) 100% (66/66)
sg Sango 100% (72/72) 100% (72/72) 100% (72/72)
sh Serbo-Croatian 100% (156/156) 100% (156/156) 98% (154/156)
shs Secwepemctsin 100% (48/48) 100% (48/48) 100% (48/48)
si Sinhala/Sinhalese (0/73) (0/73) (0/73)
sid Sidamo (0/281) (0/281) (0/281)
sk Slovak 100% (86/86) 100% (86/86) 100% (86/86)
sl Slovenian 100% (62/62) 100% (62/62) 100% (62/62)
sm Samoan 100% (53/53) 100% (53/53) 100% (53/53)
sma South Sami 100% (60/60) 100% (60/60) 100% (60/60)
smj Lule Sami 100% (60/60) 100% (60/60) 100% (60/60)
smn Inari Sami 100% (68/68) 100% (68/68) 100% (68/68)
sms Skolt Sami 100% (80/80) 100% (80/80) 97% (78/80)
sn Shona 100% (52/52) 100% (52/52) 100% (52/52)
so Somali 100% (52/52) 100% (52/52) 100% (52/52)
sq Albanian 100% (56/56) 100% (56/56) 100% (56/56)
sr Serbian 100% (60/60) 100% (60/60) 100% (60/60)
ss Swati 100% (52/52) 100% (52/52) 100% (52/52)
st Sotho, Southern 100% (52/52) 100% (52/52) 100% (52/52)
su Sundanese 100% (54/54) 100% (54/54) 100% (54/54)
sv Swedish 100% (68/68) 100% (68/68) 100% (68/68)
sw Swahili 100% (52/52) 100% (52/52) 100% (52/52)
syr Syriac (0/45) (0/45) (0/45)
ta Tamil (0/48) (0/48) (0/48)
te Telugu (0/70) (0/70) (0/70)
tg Tajik 100% (78/78) 100% (78/78) 97% (76/78)
th Thai 1% (1/74) 1% (1/74) 1% (1/74)
ti-er Eritrean Tigrinya (0/255) (0/255) (0/255)
ti-et Ethiopian Tigrinya (0/281) (0/281) (0/281)
tig Tigre (0/221) (0/221) (0/221)
tk Turkmen 100% (68/68) 100% (68/68) 100% (68/68)
tl Tagalog 100% (84/84) 100% (84/84) 100% (84/84)
tn Tswana 100% (58/58) 100% (58/58) 100% (58/58)
to Tonga 100% (53/53) 100% (53/53) 100% (53/53)
tr Turkish 100% (70/70) 100% (70/70) 100% (70/70)
ts Tsonga 100% (52/52) 100% (52/52) 100% (52/52)
tt Tatar 100% (76/76) 100% (76/76) 100% (76/76)
tw Twi 100% (73/73) 100% (73/73) 100% (73/73)
ty Tahitian 100% (65/65) 100% (65/65) 100% (65/65)
tyv Tuvinian 100% (70/70) 100% (70/70) 100% (70/70)
ug Uyghur 100% (33/33) (0/33) 78% (26/33)
uk Ukrainian 100% (72/72) 100% (72/72) 100% (72/72)
ur Urdu 92% (25/27) (0/27) 85% (23/27)
uz Uzbek 100% (52/52) 100% (52/52) 100% (52/52)
ve Venda 100% (62/62) 100% (62/62) 100% (62/62)
vi Vietnamese 100% (194/194) 100% (194/194) 76% (148/194)
vo Volapuk 100% (54/54) 100% (54/54) 100% (54/54)
vot Votic 100% (62/62) 100% (62/62) 100% (62/62)
wa Walloon 100% (70/70) 100% (70/70) 100% (70/70)
wal Wolaitta/Wolaytta (0/281) (0/281) (0/281)
wen Sorbian languages (lower and upper) 100% (76/76) 100% (76/76) 100% (76/76)
wo Wolof 100% (66/66) 100% (66/66) 100% (66/66)
xh Xhosa 100% (52/52) 100% (52/52) 100% (52/52)
yap Yapese 100% (58/58) 100% (58/58) 100% (58/58)
yi Yiddish 100% (27/27) (0/27) (0/27)
yo Yoruba 100% (119/119) 100% (119/119) 100% (119/119)
za Zhuang/Chuang 100% (52/52) 100% (52/52) 100% (52/52)
zh-cn Chinese (simplified) 0% (2/6765) 0% (2/6765) 0% (2/6765)
zh-hk Chinese Hong Kong Supplementary Character Set (0/1083) (0/1083) (0/1083)
zh-mo Chinese in Macau (0/1083) (0/1083) (0/1083)
zh-sg Chinese in Singapore 0% (2/6765) 0% (2/6765) 0% (2/6765)
zh-tw Chinese (traditional) (0/13063) (0/13063) (0/13063)
zu Zulu 100% (52/52) 100% (52/52) 100% (52/52)

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more