Ged rid of libfontconfig in Android. Query available fonts from Android system font configuration files.

This commit is contained in:
Imanol Fernandez 2017-06-02 18:40:08 +02:00
parent fa158a78b6
commit 19fbec9d54
7 changed files with 498 additions and 11 deletions

1
Cargo.lock generated
View file

@ -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]]

View file

@ -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"

View file

@ -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;

View 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";

View file

@ -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";

View file

@ -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;

View file

@ -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() };
}