aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2017-06-06 13:29:05 -0700
committerGitHub <noreply@github.com>2017-06-06 13:29:05 -0700
commitf388c0ab1e4df8cbd94d58eb7657f36baaf813fe (patch)
tree670aca82a83d083fd44aba49314cf49e54b6314a
parent80488c4494911257e62c98388b36d94d74f9d330 (diff)
parent19fbec9d546a31af072b9552eecc0541dc7b36a8 (diff)
downloadservo-f388c0ab1e4df8cbd94d58eb7657f36baaf813fe.tar.gz
servo-f388c0ab1e4df8cbd94d58eb7657f36baaf813fe.zip
Auto merge of #17141 - MortimerGoro:android_fonts, r=mbrubeck
Ged rid of libfontconfig in Android Libfontconfig dependency is causing huge startup times in Android (20 seconds on each page load!). On other platforms fontconfig caches are already available or can be prebuilt on installation scripts, but this can't be done on Android. Updating libfontconfig dependency doesn't fix the problem either. This PR gets rid of libfontconfig in Android. It queries available fonts and variations from Android System font configuration files. Android doesn't provide an API to query system fonts until Android O (which is very far from the minimum API right now...) --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #16195 (github issue number if applicable). <!-- Either: --> - [x] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17141) <!-- Reviewable:end -->
-rw-r--r--Cargo.lock1
-rw-r--r--components/gfx/Cargo.toml5
-rw-r--r--components/gfx/lib.rs4
-rw-r--r--components/gfx/platform/freetype/android/font_list.rs481
-rw-r--r--components/gfx/platform/freetype/font_list.rs8
-rw-r--r--components/gfx/platform/mod.rs8
-rw-r--r--ports/servo/main.rs2
7 files changed, 498 insertions, 11 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4f18fb7386d..50bfbccb10e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1065,6 +1065,7 @@ dependencies = [
"unicode-script 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender_traits 0.40.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 0d6d34d2625..06003773dd5 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() };
}