diff options
author | Imanol Fernandez <mortimergoro@gmail.com> | 2017-06-02 18:40:08 +0200 |
---|---|---|
committer | Imanol Fernandez <mortimergoro@gmail.com> | 2017-06-02 18:40:08 +0200 |
commit | 19fbec9d546a31af072b9552eecc0541dc7b36a8 (patch) | |
tree | 7ff2f560daebafc768be94939ed52d69b54bc992 | |
parent | fa158a78b6f976156a93bcccae3fdd27046faf50 (diff) | |
download | servo-19fbec9d546a31af072b9552eecc0541dc7b36a8.tar.gz servo-19fbec9d546a31af072b9552eecc0541dc7b36a8.zip |
Ged rid of libfontconfig in Android. Query available fonts from Android system font configuration files.
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | components/gfx/Cargo.toml | 5 | ||||
-rw-r--r-- | components/gfx/lib.rs | 4 | ||||
-rw-r--r-- | components/gfx/platform/freetype/android/font_list.rs | 481 | ||||
-rw-r--r-- | components/gfx/platform/freetype/font_list.rs | 8 | ||||
-rw-r--r-- | components/gfx/platform/mod.rs | 8 | ||||
-rw-r--r-- | ports/servo/main.rs | 2 |
7 files changed, 498 insertions, 11 deletions
diff --git a/Cargo.lock b/Cargo.lock index 4fb92f7ec9f..fce24203be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index dfbe6ab582b..78b7f958650 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -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" diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs index b7e23df0d83..3f356bb0cea 100644 --- a/components/gfx/lib.rs +++ b/components/gfx/lib.rs @@ -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; diff --git a/components/gfx/platform/freetype/android/font_list.rs b/components/gfx/platform/freetype/android/font_list.rs new file mode 100644 index 00000000000..358b9a2104b --- /dev/null +++ b/components/gfx/platform/freetype/android/font_list.rs @@ -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"; diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs index 1b87a18221d..8f2898ae680 100644 --- a/components/gfx/platform/freetype/font_list.rs +++ b/components/gfx/platform/freetype/font_list.rs @@ -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"; diff --git a/components/gfx/platform/mod.rs b/components/gfx/platform/mod.rs index 9a505c14284..897fbe9e8d3 100644 --- a/components/gfx/platform/mod.rs +++ b/components/gfx/platform/mod.rs @@ -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; diff --git a/ports/servo/main.rs b/ports/servo/main.rs index bec8ffa8089..7d390988be0 100644 --- a/ports/servo/main.rs +++ b/ports/servo/main.rs @@ -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() }; } |