mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Ged rid of libfontconfig in Android. Query available fonts from Android system font configuration files.
This commit is contained in:
parent
fa158a78b6
commit
19fbec9d54
7 changed files with 498 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1055,6 +1055,7 @@ dependencies = [
|
|||
"unicode-script 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender_traits 0.39.0 (git+https://github.com/servo/webrender)",
|
||||
"xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"xml5ever 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -50,8 +50,13 @@ core-text = "4.0"
|
|||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||
freetype = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
servo-fontconfig = "0.2.1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
xml5ever = {version = "0.7", features = ["unstable"]}
|
||||
|
||||
[target.'cfg(any(target_feature = "sse2", target_feature = "neon"))'.dependencies]
|
||||
simd = "0.2.0"
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ extern crate bitflags;
|
|||
extern crate euclid;
|
||||
extern crate fnv;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
extern crate fontconfig;
|
||||
extern crate fontsan;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
|
@ -73,6 +73,8 @@ extern crate unicode_bidi;
|
|||
extern crate unicode_script;
|
||||
extern crate webrender_traits;
|
||||
extern crate xi_unicode;
|
||||
#[cfg(target_os = "android")]
|
||||
extern crate xml5ever;
|
||||
|
||||
#[deny(unsafe_code)]
|
||||
pub mod display_list;
|
||||
|
|
481
components/gfx/platform/freetype/android/font_list.rs
Normal file
481
components/gfx/platform/freetype/android/font_list.rs
Normal file
|
@ -0,0 +1,481 @@
|
|||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::Path;
|
||||
use xml5ever::Attribute;
|
||||
use xml5ever::driver::parse_document;
|
||||
use xml5ever::rcdom::*;
|
||||
use xml5ever::rcdom::{Node, RcDom};
|
||||
use xml5ever::tendril::TendrilSink;
|
||||
|
||||
lazy_static! {
|
||||
static ref FONT_LIST: FontList = FontList::new();
|
||||
}
|
||||
|
||||
// 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>,
|
||||
}
|
||||
|
||||
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"
|
||||
];
|
||||
|
||||
// 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()
|
||||
});
|
||||
|
||||
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 xml = match Self::load_file(path) {
|
||||
Ok(xml) => xml,
|
||||
_=> { return None; },
|
||||
};
|
||||
|
||||
let dom: RcDom = parse_document(RcDom::default(), Default::default())
|
||||
.one(xml);
|
||||
let doc = &dom.document;
|
||||
|
||||
// find familyset root node
|
||||
let children = doc.children.borrow();
|
||||
let familyset = children.iter().find(|child| {
|
||||
match child.data {
|
||||
NodeData::Element { ref name, .. } => &*name.local == "familyset",
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
let familyset = match familyset {
|
||||
Some(node) => node,
|
||||
_ => { return None; }
|
||||
};
|
||||
|
||||
// Parse familyset node
|
||||
let mut families = Vec::new();
|
||||
let mut aliases = Vec::new();
|
||||
|
||||
for node in familyset.children.borrow().iter() {
|
||||
match node.data {
|
||||
NodeData::Element { ref name, ref attrs, .. } => {
|
||||
if &*name.local == "family" {
|
||||
Self::parse_family(&node, attrs, &mut families);
|
||||
} else if &*name.local == "alias" {
|
||||
// aliases come after the fonts they reference. -->
|
||||
if !families.is_empty() {
|
||||
Self::parse_alias(attrs, &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 = [
|
||||
("san-serif", "Roboto-Regular.ttf"),
|
||||
("Droid Sans", "DroidSans.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,
|
||||
}]
|
||||
}
|
||||
}). collect()
|
||||
}
|
||||
|
||||
// All Android fonts are located in /system/fonts
|
||||
fn font_absolute_path(filename: &str) -> String {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
fn load_file(path: &str) -> Result<String, io::Error> {
|
||||
let mut file = try!(File::open(path));
|
||||
let mut content = String::new();
|
||||
try!(file.read_to_string(&mut content));
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
// 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: &RefCell<Vec<Attribute>>, out:&mut Vec<FontFamily>) {
|
||||
// Fallback to old Android API v17 xml format if required
|
||||
let using_api_17 = familyset.children.borrow().iter().any(|node| {
|
||||
match node.data {
|
||||
NodeData::Element { ref name, .. } => &*name.local == "nameset",
|
||||
_=> false,
|
||||
}
|
||||
});
|
||||
if using_api_17 {
|
||||
Self::parse_family_v17(familyset, out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse family name
|
||||
let name = match Self::find_attrib("name", attrs) {
|
||||
Some(name) => name,
|
||||
_ => { return; },
|
||||
};
|
||||
|
||||
let mut fonts = Vec::new();
|
||||
// Parse font variants
|
||||
for node in familyset.children.borrow().iter() {
|
||||
match node.data {
|
||||
NodeData::Element { ref name, ref attrs, .. } => {
|
||||
if &*name.local == "font" {
|
||||
FontList::parse_font(&node, attrs, &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.children.borrow().iter() {
|
||||
match node.data {
|
||||
NodeData::Element { ref name, .. } => {
|
||||
if &*name.local == "nameset" {
|
||||
Self::collect_contents_with_tag(node, "name", &mut nameset);
|
||||
} else if &*name.local == "fileset" {
|
||||
Self::collect_contents_with_tag(node, "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,
|
||||
}).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(node: &Node, attrs: &RefCell<Vec<Attribute>>, out:&mut Vec<Font>) {
|
||||
// Parse font filename
|
||||
let filename = match Self::text_content(node) {
|
||||
Some(filename) => filename,
|
||||
_ => { return; }
|
||||
};
|
||||
|
||||
// Parse font weight
|
||||
let weight = Self::find_attrib("weight", attrs).and_then(|w| w.parse().ok());
|
||||
|
||||
out.push(Font {
|
||||
filename: filename,
|
||||
weight: weight,
|
||||
})
|
||||
}
|
||||
|
||||
// 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: &RefCell<Vec<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: &RefCell<Vec<Attribute>>) -> Option<String> {
|
||||
attrs.borrow().iter().find(|attr| &*attr.name.local == name).map(|s| String::from(&s.value))
|
||||
}
|
||||
|
||||
fn text_content(node: &Node) -> Option<String> {
|
||||
node.children.borrow().get(0).and_then(|child| {
|
||||
match child.data {
|
||||
NodeData::Text { ref contents } => {
|
||||
let mut result = String::new();
|
||||
result.push_str(&contents.borrow());
|
||||
Some(result)
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_contents_with_tag(node: &Node, tag: &str, out:&mut Vec<String>) {
|
||||
for child in node.children.borrow().iter() {
|
||||
match child.data {
|
||||
NodeData::Element { ref name, .. } => {
|
||||
if &*name.local == tag {
|
||||
if let Some(content) = Self::text_content(child) {
|
||||
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(String)
|
||||
{
|
||||
println!("Variatioooon {:?}", family_name);
|
||||
if let Some(family) = FONT_LIST.find_family(family_name) {
|
||||
for font in &family.fonts {
|
||||
callback(FontList::font_absolute_path(&font.filename));
|
||||
}
|
||||
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, _) => callback(FontList::font_absolute_path(&font.filename)),
|
||||
(Some(w1), Some(w2)) => {
|
||||
if w1 == w2 {
|
||||
callback(FontList::font_absolute_path(&font.filename))
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_resort_font_families() -> Vec<String> {
|
||||
vec!(
|
||||
"sans-serif".to_owned(),
|
||||
"Droid Sans".to_owned(),
|
||||
"serif".to_owned(),
|
||||
)
|
||||
}
|
||||
|
||||
pub static SANS_SERIF_FONT_FAMILY: &'static str = "sans-serif";
|
|
@ -141,11 +141,6 @@ pub fn last_resort_font_families() -> Vec<String> {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn last_resort_font_families() -> Vec<String> {
|
||||
vec!("Roboto".to_owned())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn last_resort_font_families() -> Vec<String> {
|
||||
vec!(
|
||||
|
@ -153,9 +148,6 @@ pub fn last_resort_font_families() -> Vec<String> {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub static SANS_SERIF_FONT_FAMILY: &'static str = "Roboto";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub static SANS_SERIF_FONT_FAMILY: &'static str = "DejaVu Sans";
|
||||
|
||||
|
|
|
@ -29,8 +29,14 @@ mod freetype {
|
|||
pub mod font;
|
||||
pub mod font_context;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod font_list;
|
||||
#[cfg(target_os = "android")]
|
||||
mod android {
|
||||
pub mod font_list;
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
pub use self::android::font_list;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub mod font_template;
|
||||
|
|
|
@ -204,7 +204,7 @@ impl app::NestedEventLoopListener for BrowserWrapper {
|
|||
#[cfg(target_os = "android")]
|
||||
fn setup_logging() {
|
||||
// Piping logs from stdout/stderr to logcat happens in android_injected_glue.
|
||||
::std::env::set_var("RUST_LOG", "debug");
|
||||
::std::env::set_var("RUST_LOG", "error");
|
||||
|
||||
unsafe { android_injected_glue::ffi::app_dummy() };
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue