mirror of
https://github.com/servo/servo.git
synced 2025-08-15 18:35:33 +01:00
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:
parent
9f8118abc7
commit
cd2ab36759
159 changed files with 224 additions and 266 deletions
580
components/fonts/platform/freetype/android/font_list.rs
Normal file
580
components/fonts/platform/freetype/android/font_list.rs
Normal 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";
|
49
components/fonts/platform/freetype/android/xml.rs
Normal file
49
components/fonts/platform/freetype/android/xml.rs
Normal 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)
|
||||
}
|
550
components/fonts/platform/freetype/font.rs
Normal file
550
components/fonts/platform/freetype/font.rs
Normal 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;
|
||||
}
|
299
components/fonts/platform/freetype/font_list.rs
Normal file
299
components/fonts/platform/freetype/font_list.rs
Normal 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))
|
||||
}
|
115
components/fonts/platform/freetype/library_handle.rs
Normal file
115
components/fonts/platform/freetype/library_handle.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
239
components/fonts/platform/freetype/ohos/font_list.rs
Normal file
239
components/fonts/platform/freetype/ohos/font_list.rs
Normal 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";
|
101
components/fonts/platform/macos/core_text_font_cache.rs
Normal file
101
components/fonts/platform/macos/core_text_font_cache.rs
Normal 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)
|
||||
}
|
||||
}
|
375
components/fonts/platform/macos/font.rs
Normal file
375
components/fonts/platform/macos/font.rs
Normal 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)
|
||||
}
|
||||
}
|
213
components/fonts/platform/macos/font_list.rs
Normal file
213
components/fonts/platform/macos/font_list.rs
Normal 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";
|
298
components/fonts/platform/mod.rs
Normal file
298
components/fonts/platform/mod.rs
Normal 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");
|
||||
}
|
276
components/fonts/platform/windows/font.rs
Normal file
276
components/fonts/platform/windows/font.rs
Normal 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()
|
||||
}
|
||||
}
|
379
components/fonts/platform/windows/font_list.rs
Normal file
379
components/fonts/platform/windows/font_list.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue