diff --git a/Cargo.lock b/Cargo.lock index 46e912a8548..c72223c0385 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,6 +1014,7 @@ dependencies = [ "style 0.0.1", "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "truetype 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ucd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-script 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "webrender_api 0.57.2 (git+https://github.com/servo/webrender)", @@ -3252,6 +3253,11 @@ name = "typeable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ucd" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "uluru" version = "0.2.0" @@ -4002,6 +4008,7 @@ dependencies = [ "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" "checksum truetype 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec30350633d6dac9dc1a625786b6cbe9150664be941aac2c35ad7199eab877" "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" +"checksum ucd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe4fa6e588762366f1eb4991ce59ad1b93651d0b769dfb4e4d1c5c4b943d1159" "checksum uluru 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "519130f0ea964ba540a9d8af1373738c2226f1d465eda07e61db29feb5479db9" "checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a" diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index eed47db1458..6ea0d21e2a4 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -42,6 +42,7 @@ unicode-bidi = {version = "0.3", features = ["with_serde"]} unicode-script = {version = "0.2", features = ["harfbuzz"]} webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} xi-unicode = "0.1.0" +ucd = "0.1.1" [target.'cfg(target_os = "macos")'.dependencies] byteorder = "1.0" diff --git a/components/gfx/font.rs b/components/gfx/font.rs index 2cf92e6a328..b45707330f2 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -9,12 +9,14 @@ use font_template::FontTemplateDescriptor; use ordered_float::NotNaN; use platform::font::{FontHandle, FontTable}; use platform::font_context::FontContextHandle; +pub use platform::font_list::fallback_font_families; use platform::font_template::FontTemplateData; use servo_atoms::Atom; use smallvec::SmallVec; use std::borrow::ToOwned; use std::cell::RefCell; use std::collections::HashMap; +use std::iter; use std::rc::Rc; use std::str; use std::sync::Arc; @@ -117,7 +119,7 @@ pub struct FontMetrics { /// template at a particular size, with a particular font-variant-caps applied, etc. This contrasts /// with `FontTemplateDescriptor` in that the latter represents only the parameters inherent in the /// font data (weight, stretch, etc.). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct FontDescriptor { pub template_descriptor: FontTemplateDescriptor, pub variant: font_variant_caps::T, @@ -329,6 +331,7 @@ pub type FontRef = Rc>; pub struct FontGroup { descriptor: FontDescriptor, families: SmallVec<[FontGroupFamily; 8]>, + last_matching_fallback: Option, } impl FontGroup { @@ -337,10 +340,14 @@ impl FontGroup { let families = style.font_family.0.iter() - .map(|family| FontGroupFamily::new(descriptor.clone(), family.clone())) + .map(|family| FontGroupFamily::new(descriptor.clone(), &family)) .collect(); - FontGroup { descriptor, families } + FontGroup { + descriptor, + families, + last_matching_fallback: None, + } } /// Finds the first font, or else the first fallback font, which contains a glyph for @@ -352,15 +359,35 @@ impl FontGroup { mut font_context: &mut FontContext, codepoint: char ) -> Option { - self.find(&mut font_context, |font| font.borrow().has_glyph_for(codepoint)) - .or_else(|| self.first(&mut font_context)) + let has_glyph = |font: &FontRef| font.borrow().has_glyph_for(codepoint); + + let font = self.find(&mut font_context, |font| has_glyph(font)); + if font.is_some() { + return font + } + + if let Some(ref fallback) = self.last_matching_fallback { + if has_glyph(&fallback) { + return self.last_matching_fallback.clone() + } + } + + let font = self.find_fallback(&mut font_context, Some(codepoint), has_glyph); + if font.is_some() { + self.last_matching_fallback = font.clone(); + return font + } + + self.first(&mut font_context) } + /// Find the first available font in the group, or the first available fallback font. pub fn first( &mut self, mut font_context: &mut FontContext ) -> Option { self.find(&mut font_context, |_| true) + .or_else(|| self.find_fallback(&mut font_context, None, |_| true)) } /// Find a font which returns true for `predicate`. This method mutates because we may need to @@ -368,19 +395,42 @@ impl FontGroup { fn find( &mut self, mut font_context: &mut FontContext, - mut predicate: P + predicate: P, ) -> Option where S: FontSource, - P: FnMut(&FontRef) -> bool + P: FnMut(&FontRef) -> bool, { self.families.iter_mut() .filter_map(|family| family.font(&mut font_context)) - .find(|f| predicate(f)) - .or_else(|| { - font_context.fallback_font(&self.descriptor) - .into_iter().find(predicate) - }) + .find(predicate) + } + + /// Attempts to find a suitable fallback font which matches the `predicate`. The default + /// family (i.e. "serif") will be tried first, followed by platform-specific family names. + /// If a `codepoint` is provided, then its Unicode block may be used to refine the list of + /// family names which will be tried. + fn find_fallback( + &mut self, + font_context: &mut FontContext, + codepoint: Option, + predicate: P, + ) -> Option + where + S: FontSource, + P: FnMut(&FontRef) -> bool, + { + iter::once(FontFamilyDescriptor::default()) + .chain( + fallback_font_families(codepoint).into_iter().map(|family| { + FontFamilyDescriptor::new( + FontFamilyName::from(family), + FontSearchScope::Local, + ) + }) + ) + .filter_map(|family| font_context.font(&self.descriptor, &family)) + .find(predicate) } } @@ -389,17 +439,22 @@ impl FontGroup { /// only if actually needed. #[derive(Debug)] struct FontGroupFamily { - descriptor: FontDescriptor, - family: SingleFontFamily, + font_descriptor: FontDescriptor, + family_descriptor: FontFamilyDescriptor, loaded: bool, font: Option, } impl FontGroupFamily { - fn new(descriptor: FontDescriptor, family: SingleFontFamily) -> FontGroupFamily { + fn new(font_descriptor: FontDescriptor, family: &SingleFontFamily) -> FontGroupFamily { + let family_descriptor = FontFamilyDescriptor::new( + FontFamilyName::from(family), + FontSearchScope::Any + ); + FontGroupFamily { - descriptor, - family, + font_descriptor, + family_descriptor, loaded: false, font: None, } @@ -410,7 +465,7 @@ impl FontGroupFamily { /// subsequent calls. fn font(&mut self, font_context: &mut FontContext) -> Option { if !self.loaded { - self.font = font_context.font(&self.descriptor, &self.family); + self.font = font_context.font(&self.font_descriptor, &self.family_descriptor); self.loaded = true; } @@ -451,3 +506,74 @@ pub fn get_and_reset_text_shaping_performance_counter() -> usize { TEXT_SHAPING_PERFORMANCE_COUNTER.store(0, Ordering::SeqCst); value } + +/// The scope within which we will look for a font. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum FontSearchScope { + /// All fonts will be searched, including those specified via `@font-face` rules. + Any, + + /// Only local system fonts will be searched. + Local, +} + +/// A font family name used in font selection. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum FontFamilyName { + /// A specific name such as `"Arial"` + Specific(Atom), + + /// A generic name such as `sans-serif` + Generic(Atom), +} + +impl FontFamilyName { + pub fn name(&self) -> &str { + match *self { + FontFamilyName::Specific(ref name) => name, + FontFamilyName::Generic(ref name) => name, + } + } +} + +impl<'a> From<&'a SingleFontFamily> for FontFamilyName { + fn from(other: &'a SingleFontFamily) -> FontFamilyName { + match *other { + SingleFontFamily::FamilyName(ref family_name) => + FontFamilyName::Specific(family_name.name.clone()), + + SingleFontFamily::Generic(ref generic_name) => + FontFamilyName::Generic(generic_name.clone()), + } + } +} + +impl<'a> From<&'a str> for FontFamilyName { + fn from(other: &'a str) -> FontFamilyName { + FontFamilyName::Specific(Atom::from(other)) + } +} + +/// The font family parameters for font selection. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct FontFamilyDescriptor { + pub name: FontFamilyName, + pub scope: FontSearchScope, +} + +impl FontFamilyDescriptor { + pub fn new(name: FontFamilyName, scope: FontSearchScope) -> FontFamilyDescriptor { + FontFamilyDescriptor { name, scope } + } + + fn default() -> FontFamilyDescriptor { + FontFamilyDescriptor { + name: FontFamilyName::Generic(atom!("serif")), + scope: FontSearchScope::Local, + } + } + + pub fn name(&self) -> &str { + self.name.name() + } +} diff --git a/components/gfx/font_cache_thread.rs b/components/gfx/font_cache_thread.rs index 39375191ae1..df04519e90b 100644 --- a/components/gfx/font_cache_thread.rs +++ b/components/gfx/font_cache_thread.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::Au; +use font::{FontFamilyDescriptor, FontFamilyName, FontSearchScope}; use font_context::FontSource; use font_template::{FontTemplate, FontTemplateDescriptor}; use fontsan; @@ -13,7 +14,6 @@ use platform::font_context::FontContextHandle; use platform::font_list::SANS_SERIF_FONT_FAMILY; use platform::font_list::for_each_available_family; use platform::font_list::for_each_variation; -use platform::font_list::last_resort_font_families; use platform::font_list::system_default_family; use platform::font_template::FontTemplateData; use servo_atoms::Atom; @@ -24,7 +24,7 @@ use std::collections::HashMap; use std::ops::Deref; use std::sync::{Arc, Mutex}; use style::font_face::{EffectiveSources, Source}; -use style::values::computed::font::{SingleFontFamily, FamilyName}; +use style::values::computed::font::FamilyName; use webrender_api; /// A list of font templates that make up a given font family. @@ -32,7 +32,7 @@ pub struct FontTemplates { templates: Vec, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct FontTemplateInfo { pub font_template: Arc, pub font_key: webrender_api::FontKey, @@ -103,8 +103,7 @@ impl FontTemplates { /// Commands that the FontContext sends to the font cache thread. #[derive(Debug, Deserialize, Serialize)] pub enum Command { - GetFontTemplate(SingleFontFamily, FontTemplateDescriptor, IpcSender), - GetLastResortFontTemplate(FontTemplateDescriptor, IpcSender), + GetFontTemplate(FontTemplateDescriptor, FontFamilyDescriptor, IpcSender), GetFontInstance(webrender_api::FontKey, Au, IpcSender), AddWebFont(LowercaseString, EffectiveSources, IpcSender<()>), AddDownloadedWebFont(LowercaseString, ServoUrl, Vec, IpcSender<()>), @@ -123,7 +122,7 @@ pub enum Reply { struct FontCache { port: IpcReceiver, channel_to_self: IpcSender, - generic_fonts: HashMap, + generic_fonts: HashMap, local_families: HashMap, web_families: HashMap, font_context: FontContextHandle, @@ -133,27 +132,28 @@ struct FontCache { font_instances: HashMap<(webrender_api::FontKey, Au), webrender_api::FontInstanceKey>, } -fn populate_generic_fonts() -> HashMap { +fn populate_generic_fonts() -> HashMap { let mut generic_fonts = HashMap::with_capacity(5); - append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("serif")), "Times New Roman"); - append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("sans-serif")), SANS_SERIF_FONT_FAMILY); - append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("cursive")), "Apple Chancery"); - append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("fantasy")), "Papyrus"); - append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("monospace")), "Menlo"); + append_map(&mut generic_fonts, "serif", "Times New Roman"); + append_map(&mut generic_fonts, "sans-serif", SANS_SERIF_FONT_FAMILY); + append_map(&mut generic_fonts, "cursive", "Apple Chancery"); + append_map(&mut generic_fonts, "fantasy", "Papyrus"); + append_map(&mut generic_fonts, "monospace", "Menlo"); - fn append_map(generic_fonts: &mut HashMap, - font_family: SingleFontFamily, - mapped_name: &str) { - let family_name = { - let opt_system_default = system_default_family(font_family.name()); - match opt_system_default { - Some(system_default) => LowercaseString::new(&system_default), - None => LowercaseString::new(mapped_name) - } + fn append_map( + generic_fonts: &mut HashMap, + generic_name: &str, + mapped_name: &str, + ) { + let family_name = match system_default_family(generic_name) { + Some(system_default) => LowercaseString::new(&system_default), + None => LowercaseString::new(mapped_name) }; - generic_fonts.insert(font_family, family_name); + let generic_name = FontFamilyName::Generic(Atom::from(generic_name)); + + generic_fonts.insert(generic_name, family_name); } @@ -166,14 +166,10 @@ impl FontCache { let msg = self.port.recv().unwrap(); match msg { - Command::GetFontTemplate(family, descriptor, result) => { - let maybe_font_template = self.find_font_template(&family, &descriptor); + Command::GetFontTemplate(template_descriptor, family_descriptor, result) => { + let maybe_font_template = self.find_font_template(&template_descriptor, &family_descriptor); let _ = result.send(Reply::GetFontTemplateReply(maybe_font_template)); } - Command::GetLastResortFontTemplate(descriptor, result) => { - let font_template = self.last_resort_font_template(&descriptor); - let _ = result.send(Reply::GetFontTemplateReply(Some(font_template))); - } Command::GetFontInstance(font_key, size, result) => { let webrender_api = &self.webrender_api; @@ -320,23 +316,28 @@ impl FontCache { }); } - fn transform_family(&self, family: &SingleFontFamily) -> LowercaseString { - match self.generic_fonts.get(family) { - None => LowercaseString::new(family.name()), + fn transform_family(&self, family_name: &FontFamilyName) -> LowercaseString { + match self.generic_fonts.get(family_name) { + None => LowercaseString::from(family_name), Some(mapped_family) => (*mapped_family).clone() } } - fn find_font_in_local_family(&mut self, family_name: &LowercaseString, desc: &FontTemplateDescriptor) - -> Option> { + fn find_font_in_local_family( + &mut self, + template_descriptor: &FontTemplateDescriptor, + family_name: &FontFamilyName, + ) -> Option> { + let family_name = self.transform_family(family_name); + // TODO(Issue #188): look up localized font family names if canonical name not found // look up canonical name - if self.local_families.contains_key(family_name) { - debug!("FontList: Found font family with name={}", &**family_name); - let s = self.local_families.get_mut(family_name).unwrap(); + if self.local_families.contains_key(&family_name) { + debug!("FontList: Found font family with name={}", &*family_name); + let s = self.local_families.get_mut(&family_name).unwrap(); if s.templates.is_empty() { - for_each_variation(family_name, |path| { + for_each_variation(&family_name, |path| { s.add_template(Atom::from(&*path), None); }); } @@ -344,20 +345,23 @@ impl FontCache { // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'. // if such family exists, try to match style to a font - s.find_font_for_style(desc, &self.font_context) + s.find_font_for_style(template_descriptor, &self.font_context) } else { - debug!("FontList: Couldn't find font family with name={}", &**family_name); + debug!("FontList: Couldn't find font family with name={}", &*family_name); None } } - fn find_font_in_web_family(&mut self, family: &SingleFontFamily, desc: &FontTemplateDescriptor) - -> Option> { - let family_name = LowercaseString::new(family.name()); + fn find_font_in_web_family( + &mut self, + template_descriptor: &FontTemplateDescriptor, + family_name: &FontFamilyName, + ) -> Option> { + let family_name = LowercaseString::from(family_name); if self.web_families.contains_key(&family_name) { let templates = self.web_families.get_mut(&family_name).unwrap(); - templates.find_font_for_style(desc, &self.font_context) + templates.find_font_for_style(template_descriptor, &self.font_context) } else { None } @@ -385,32 +389,21 @@ impl FontCache { } } - fn find_font_template(&mut self, family: &SingleFontFamily, desc: &FontTemplateDescriptor) - -> Option { - let template = self.find_font_in_web_family(family, desc) - .or_else(|| { - let transformed_family = self.transform_family(family); - self.find_font_in_local_family(&transformed_family, desc) - }); - - template.map(|template| { - self.get_font_template_info(template) - }) - } - - fn last_resort_font_template(&mut self, desc: &FontTemplateDescriptor) - -> FontTemplateInfo { - let last_resort = last_resort_font_families(); - - for family in &last_resort { - let family = LowercaseString::new(family); - let maybe_font_in_family = self.find_font_in_local_family(&family, desc); - if let Some(family) = maybe_font_in_family { - return self.get_font_template_info(family) + fn find_font_template( + &mut self, + template_descriptor: &FontTemplateDescriptor, + family_descriptor: &FontFamilyDescriptor, + ) -> Option { + match family_descriptor.scope { + FontSearchScope::Any => { + self.find_font_in_web_family(&template_descriptor, &family_descriptor.name) + .or_else(|| self.find_font_in_local_family(&template_descriptor, &family_descriptor.name)) } - } - panic!("Unable to find any fonts that match (do you have fallback fonts installed?)"); + FontSearchScope::Local => { + self.find_font_in_local_family(&template_descriptor, &family_descriptor.name) + } + }.map(|t| self.get_font_template_info(t)) } } @@ -480,11 +473,14 @@ impl FontSource for FontCacheThread { instance_key.unwrap() } - fn find_font_template(&mut self, family: SingleFontFamily, desc: FontTemplateDescriptor) - -> Option { + fn font_template( + &mut self, + template_descriptor: FontTemplateDescriptor, + family_descriptor: FontFamilyDescriptor, + ) -> Option { let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel"); - self.chan.send(Command::GetFontTemplate(family, desc, response_chan)) + self.chan.send(Command::GetFontTemplate(template_descriptor, family_descriptor, response_chan)) .expect("failed to send message to font cache thread"); let reply = response_port.recv(); @@ -501,27 +497,6 @@ impl FontSource for FontCacheThread { } } } - - fn last_resort_font_template(&mut self, desc: FontTemplateDescriptor) - -> FontTemplateInfo { - let (response_chan, response_port) = - ipc::channel().expect("failed to create IPC channel"); - self.chan.send(Command::GetLastResortFontTemplate(desc, response_chan)) - .expect("failed to send message to font cache thread"); - - let reply = response_port.recv(); - if reply.is_err() { - let font_thread_has_closed = self.chan.send(Command::Ping).is_err(); - assert!(font_thread_has_closed, "Failed to receive a response from live font cache"); - panic!("Font cache thread has already exited."); - } - - match reply.unwrap() { - Reply::GetFontTemplateReply(data) => { - data.unwrap() - } - } - } } #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] @@ -537,6 +512,12 @@ impl LowercaseString { } } +impl<'a> From<&'a FontFamilyName> for LowercaseString { + fn from(family_name: &'a FontFamilyName) -> LowercaseString { + LowercaseString::new(family_name.name()) + } +} + impl Deref for LowercaseString { type Target = str; diff --git a/components/gfx/font_context.rs b/components/gfx/font_context.rs index a680f59dc44..a09e6cf7b57 100644 --- a/components/gfx/font_context.rs +++ b/components/gfx/font_context.rs @@ -4,14 +4,13 @@ use app_units::Au; use fnv::FnvHasher; -use font::{Font, FontDescriptor, FontGroup, FontHandleMethods, FontRef}; +use font::{Font, FontDescriptor, FontFamilyDescriptor, FontGroup, FontHandleMethods, FontRef}; use font_cache_thread::FontTemplateInfo; use font_template::FontTemplateDescriptor; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use platform::font::FontHandle; pub use platform::font_context::FontContextHandle; use servo_arc::Arc; -use servo_atoms::Atom; use std::cell::RefCell; use std::collections::HashMap; use std::default::Default; @@ -20,42 +19,10 @@ use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use style::computed_values::font_variant_caps::T as FontVariantCaps; use style::properties::style_structs::Font as FontStyleStruct; -use style::values::computed::font::SingleFontFamily; use webrender_api; static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h) -#[derive(Debug)] -struct FontCacheEntry { - family: Atom, - font: Option, -} - -impl FontCacheEntry { - fn matches(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> bool { - if self.family != *family.atom() { - return false - } - - if let Some(ref font) = self.font { - (*font).borrow().descriptor == *descriptor - } else { - true - } - } -} - -#[derive(Debug)] -struct FallbackFontCacheEntry { - font: FontRef, -} - -impl FallbackFontCacheEntry { - fn matches(&self, descriptor: &FontDescriptor) -> bool { - self.font.borrow().descriptor == *descriptor - } -} - /// An epoch for the font context cache. The cache is flushed if the current epoch does not match /// this one. static FONT_CACHE_EPOCH: AtomicUsize = ATOMIC_USIZE_INIT; @@ -63,13 +30,11 @@ static FONT_CACHE_EPOCH: AtomicUsize = ATOMIC_USIZE_INIT; pub trait FontSource { fn get_font_instance(&mut self, key: webrender_api::FontKey, size: Au) -> webrender_api::FontInstanceKey; - fn find_font_template( + fn font_template( &mut self, - family: SingleFontFamily, - desc: FontTemplateDescriptor + template_descriptor: FontTemplateDescriptor, + family_descriptor: FontFamilyDescriptor, ) -> Option; - - fn last_resort_font_template(&mut self, desc: FontTemplateDescriptor) -> FontTemplateInfo; } /// The FontContext represents the per-thread/thread state necessary for @@ -84,10 +49,8 @@ pub struct FontContext { // TODO: The font context holds a strong ref to the cached fonts // so they will never be released. Find out a good time to drop them. // See bug https://github.com/servo/servo/issues/3300 - // - // GWTODO: Check on real pages if this is faster as Vec() or HashMap(). - font_cache: Vec, - fallback_font_cache: Vec, + font_cache: HashMap>, + font_template_cache: HashMap>, font_group_cache: HashMap>, BuildHasherDefault>, @@ -101,33 +64,13 @@ impl FontContext { FontContext { platform_handle: handle, font_source, - font_cache: vec!(), - fallback_font_cache: vec!(), + font_cache: HashMap::new(), + font_template_cache: HashMap::new(), font_group_cache: HashMap::with_hasher(Default::default()), epoch: 0, } } - /// Create a `Font` for use in layout calculations, from a `FontTemplateInfo` returned by the - /// cache thread (which contains the underlying font data) and a `FontDescriptor` which - /// contains the styling parameters. - fn create_font(&mut self, info: FontTemplateInfo, descriptor: FontDescriptor) -> Result { - // TODO: (Bug #3463): Currently we only support fake small-caps - // painting. We should also support true small-caps (where the - // font supports it) in the future. - let actual_pt_size = match descriptor.variant { - FontVariantCaps::SmallCaps => descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR), - FontVariantCaps::Normal => descriptor.pt_size, - }; - - let handle = FontHandle::new_from_template(&self.platform_handle, - info.font_template, - Some(actual_pt_size))?; - - let font_instance_key = self.font_source.get_font_instance(info.font_key, actual_pt_size); - Ok(Font::new(handle, descriptor.to_owned(), actual_pt_size, font_instance_key)) - } - fn expire_font_caches_if_necessary(&mut self) { let current_epoch = FONT_CACHE_EPOCH.load(Ordering::SeqCst); if current_epoch == self.epoch { @@ -135,7 +78,7 @@ impl FontContext { } self.font_cache.clear(); - self.fallback_font_cache.clear(); + self.font_template_cache.clear(); self.font_group_cache.clear(); self.epoch = current_epoch } @@ -160,76 +103,85 @@ impl FontContext { font_group } - /// Returns a reference to an existing font cache entry matching `descriptor` and `family`, if - /// there is one. - fn font_cache_entry(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> Option<&FontCacheEntry> { - self.font_cache.iter() - .find(|cache_entry| cache_entry.matches(&descriptor, &family)) - } - - /// Creates a new font cache entry matching `descriptor` and `family`. - fn create_font_cache_entry(&mut self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> FontCacheEntry { - let font = - self.font_source.find_font_template(family.clone(), descriptor.template_descriptor.clone()) - .and_then(|template_info| - self.create_font(template_info, descriptor.to_owned()).ok() - ) - .map(|font| Rc::new(RefCell::new(font))); - - FontCacheEntry { family: family.atom().to_owned(), font } - } - - /// Returns a font from `family` matching the `descriptor`. Fonts are cached, so repeated calls - /// will return a reference to the same underlying `Font`. - pub fn font(&mut self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> Option { - if let Some(entry) = self.font_cache_entry(descriptor, family) { - return entry.font.clone() - } - - let entry = self.create_font_cache_entry(descriptor, family); - let font = entry.font.clone(); - self.font_cache.push(entry); - font - } - - /// Returns a reference to an existing fallback font cache entry matching `descriptor`, if - /// there is one. - fn fallback_font_cache_entry(&self, descriptor: &FontDescriptor) -> Option<&FallbackFontCacheEntry> { - self.fallback_font_cache.iter() - .find(|cache_entry| cache_entry.matches(descriptor)) - } - - /// Creates a new fallback font cache entry matching `descriptor`. - fn create_fallback_font_cache_entry(&mut self, descriptor: &FontDescriptor) -> Option { - let template_info = self.font_source.last_resort_font_template(descriptor.template_descriptor.clone()); - - match self.create_font(template_info, descriptor.to_owned()) { - Ok(font) => - Some(FallbackFontCacheEntry { - font: Rc::new(RefCell::new(font)) - }), - - Err(_) => { - debug!("Failed to create fallback font!"); - None - } - } - } - - /// Returns a fallback font matching the `descriptor`. Fonts are cached, so repeated calls will - /// return a reference to the same underlying `Font`. - pub fn fallback_font(&mut self, descriptor: &FontDescriptor) -> Option { - if let Some(cached_entry) = self.fallback_font_cache_entry(descriptor) { - return Some(cached_entry.font.clone()) + /// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a + /// reference to the same underlying `Font`. + pub fn font( + &mut self, + font_descriptor: &FontDescriptor, + family_descriptor: &FontFamilyDescriptor, + ) -> Option { + let cache_key = FontCacheKey { + font_descriptor: font_descriptor.clone(), + family_descriptor: family_descriptor.clone(), }; - if let Some(entry) = self.create_fallback_font_cache_entry(descriptor) { - let font = entry.font.clone(); - self.fallback_font_cache.push(entry); - Some(font) - } else { - None - } + self.font_cache.get(&cache_key).map(|v| v.clone()).unwrap_or_else(|| { + debug!( + "FontContext::font cache miss for font_descriptor={:?} family_descriptor={:?}", + font_descriptor, + family_descriptor + ); + + let font = + self.font_template(&font_descriptor.template_descriptor, family_descriptor) + .and_then(|template_info| self.create_font(template_info, font_descriptor.to_owned()).ok()) + .map(|font| Rc::new(RefCell::new(font))); + + self.font_cache.insert(cache_key, font.clone()); + font + }) + } + + fn font_template( + &mut self, + template_descriptor: &FontTemplateDescriptor, + family_descriptor: &FontFamilyDescriptor + ) -> Option { + let cache_key = FontTemplateCacheKey { + template_descriptor: template_descriptor.clone(), + family_descriptor: family_descriptor.clone(), + }; + + self.font_template_cache.get(&cache_key).map(|v| v.clone()).unwrap_or_else(|| { + debug!( + "FontContext::font_template cache miss for template_descriptor={:?} family_descriptor={:?}", + template_descriptor, + family_descriptor + ); + + let template_info = self.font_source.font_template( + template_descriptor.clone(), + family_descriptor.clone(), + ); + + self.font_template_cache.insert(cache_key, template_info.clone()); + template_info + }) + } + + /// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the + /// cache thread and a `FontDescriptor` which contains the styling parameters. + fn create_font( + &mut self, + info: FontTemplateInfo, + descriptor: FontDescriptor + ) -> Result { + // TODO: (Bug #3463): Currently we only support fake small-caps + // painting. We should also support true small-caps (where the + // font supports it) in the future. + let actual_pt_size = match descriptor.variant { + FontVariantCaps::SmallCaps => descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR), + FontVariantCaps::Normal => descriptor.pt_size, + }; + + let handle = FontHandle::new_from_template( + &self.platform_handle, + info.font_template, + Some(actual_pt_size) + )?; + + let font_instance_key = self.font_source.get_font_instance(info.font_key, actual_pt_size); + Ok(Font::new(handle, descriptor.to_owned(), actual_pt_size, font_instance_key)) } } @@ -240,6 +192,18 @@ impl MallocSizeOf for FontContext { } } +#[derive(Debug, Eq, Hash, PartialEq)] +struct FontCacheKey { + font_descriptor: FontDescriptor, + family_descriptor: FontFamilyDescriptor, +} + +#[derive(Debug, Eq, Hash, PartialEq)] +struct FontTemplateCacheKey { + template_descriptor: FontTemplateDescriptor, + family_descriptor: FontFamilyDescriptor, +} + #[derive(Debug)] struct FontGroupCacheKey { style: Arc, diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs index 13c531f4d43..8a72360d0dc 100644 --- a/components/gfx/font_template.rs +++ b/components/gfx/font_template.rs @@ -19,13 +19,18 @@ use style::values::computed::font::FontWeight; /// to be expanded or refactored when we support more of the font styling parameters. /// /// NB: If you change this, you will need to update `style::properties::compute_font_hash()`. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Serialize)] pub struct FontTemplateDescriptor { pub weight: FontWeight, pub stretch: FontStretch, pub style: FontStyle, } + +/// FontTemplateDescriptor contains floats, which are not Eq because of NaN. However, +/// we know they will never be NaN, so we can manually implement Eq. +impl Eq for FontTemplateDescriptor {} + fn style_to_number(s: &FontStyle) -> f32 { use style::values::generics::font::FontStyle as GenericFontStyle; @@ -66,7 +71,7 @@ impl FontTemplateDescriptor { // 0 <= weightPart <= 800 let weight_part = (self.weight.0 - other.weight.0).abs(); // 0 <= stretchPart <= 8 - let stretch_part = ((self.stretch.0).0 - (other.stretch.0).0).abs(); + let stretch_part = (self.stretch.value() - other.stretch.value()).abs(); style_part + weight_part + stretch_part } } diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs index 9b1b455b6a4..9c3b9680f38 100644 --- a/components/gfx/lib.rs +++ b/components/gfx/lib.rs @@ -48,14 +48,15 @@ extern crate ordered_float; extern crate range; #[macro_use] extern crate serde; extern crate servo_arc; -extern crate servo_url; #[macro_use] extern crate servo_atoms; +extern crate servo_url; #[cfg(feature = "unstable")] #[cfg(any(target_feature = "sse2", target_feature = "neon"))] extern crate simd; extern crate smallvec; extern crate style; extern crate time; +extern crate ucd; extern crate unicode_bidi; extern crate unicode_script; extern crate webrender_api; diff --git a/components/gfx/platform/freetype/android/font_list.rs b/components/gfx/platform/freetype/android/font_list.rs index 6d5a1ea3061..e26e6c84e60 100644 --- a/components/gfx/platform/freetype/android/font_list.rs +++ b/components/gfx/platform/freetype/android/font_list.rs @@ -6,6 +6,8 @@ use std::cell::RefCell; use std::fs::File; use std::io::{self, Read}; use std::path::Path; +use text::util::is_cjk; +use ucd::{Codepoint, UnicodeBlock}; use xml5ever::Attribute; use xml5ever::driver::parse_document; use xml5ever::rcdom::*; @@ -470,12 +472,61 @@ pub fn system_default_family(generic_name: &str) -> Option { } } -pub fn last_resort_font_families() -> Vec { - vec!( - "sans-serif".to_owned(), - "Droid Sans".to_owned(), - "serif".to_owned(), - ) +// Based on gfxAndroidPlatform::GetCommonFallbackFonts() in Gecko +pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { + let mut families = vec!(); + + if let Some(block) = codepoint.and_then(|c| c.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(codepoint.unwrap()) { + 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"; diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs index 506d6be2117..6c2a6b92696 100644 --- a/components/gfx/platform/freetype/font.rs +++ b/components/gfx/platform/freetype/font.rs @@ -5,7 +5,7 @@ use app_units::Au; use font::{FontHandleMethods, FontMetrics, FontTableMethods}; use font::{FontTableTag, FractionalPixel, GPOS, GSUB, KERN}; -use freetype::freetype::{FT_Done_Face, FT_New_Memory_Face}; +use freetype::freetype::{FT_Done_Face, FT_New_Face, FT_New_Memory_Face}; use freetype::freetype::{FT_F26Dot6, FT_Face, FT_FaceRec}; use freetype::freetype::{FT_Get_Char_Index, FT_Get_Postscript_Name}; use freetype::freetype::{FT_Get_Kerning, FT_Get_Sfnt_Table, FT_Load_Sfnt_Table}; @@ -20,6 +20,7 @@ use platform::font_context::FontContextHandle; use platform::font_template::FontTemplateData; use servo_atoms::Atom; use std::{mem, ptr}; +use std::ffi::CString; use std::os::raw::{c_char, c_long}; use std::sync::Arc; use style::computed_values::font_stretch::T as FontStretch; @@ -87,6 +88,39 @@ impl Drop for FontHandle { } } +fn create_face( + lib: FT_Library, + template: &FontTemplateData, + pt_size: Option, +) -> Result { + unsafe { + let mut face: FT_Face = ptr::null_mut(); + let face_index = 0 as FT_Long; + + let result = if let Some(ref bytes) = template.bytes { + FT_New_Memory_Face(lib, bytes.as_ptr(), bytes.len() as FT_Long, face_index, &mut face) + } else { + // This will trigger a synchronous file read in the layout thread, which we may want to + // revisit at some point. See discussion here: + // + // https://github.com/servo/servo/pull/20506#issuecomment-378838800 + + let filename = CString::new(&*template.identifier).expect("filename contains NUL byte!"); + FT_New_Face(lib, filename.as_ptr(), face_index, &mut face) + }; + + if !succeeded(result) || face.is_null() { + return Err(()); + } + + if let Some(s) = pt_size { + FontHandle::set_char_size(face, s).or(Err(()))? + } + + Ok(face) + } +} + impl FontHandleMethods for FontHandle { fn new_from_template(fctx: &FontContextHandle, template: Arc, @@ -95,37 +129,19 @@ impl FontHandleMethods for FontHandle { let ft_ctx: FT_Library = fctx.ctx.ctx; if ft_ctx.is_null() { return Err(()); } - return create_face_from_buffer(ft_ctx, &template.bytes, pt_size).map(|face| { - let mut handle = FontHandle { - face: face, - font_data: template.clone(), - handle: fctx.clone(), - can_do_fast_shaping: false, - }; - // TODO (#11310): Implement basic support for GPOS and GSUB. - handle.can_do_fast_shaping = handle.has_table(KERN) && - !handle.has_table(GPOS) && - !handle.has_table(GSUB); - handle - }); + let face = create_face(ft_ctx, &template, pt_size)?; - fn create_face_from_buffer(lib: FT_Library, buffer: &[u8], pt_size: Option) - -> Result { - unsafe { - let mut face: FT_Face = ptr::null_mut(); - let face_index = 0 as FT_Long; - let result = FT_New_Memory_Face(lib, buffer.as_ptr(), buffer.len() as FT_Long, - face_index, &mut face); - - if !succeeded(result) || face.is_null() { - return Err(()); - } - if let Some(s) = pt_size { - FontHandle::set_char_size(face, s).or(Err(()))? - } - Ok(face) - } - } + let mut handle = FontHandle { + face: face, + font_data: template.clone(), + handle: fctx.clone(), + can_do_fast_shaping: false, + }; + // TODO (#11310): Implement basic support for GPOS and GSUB. + handle.can_do_fast_shaping = handle.has_table(KERN) && + !handle.has_table(GPOS) && + !handle.has_table(GSUB); + Ok(handle) } fn template(&self) -> Arc { @@ -187,7 +203,7 @@ impl FontHandleMethods for FontHandle { } else { FontStretchKeyword::Normal }.compute(); - NonNegative(percentage) + FontStretch(NonNegative(percentage)) } fn glyph_index(&self, codepoint: char) -> Option { @@ -197,7 +213,7 @@ impl FontHandleMethods for FontHandle { if idx != 0 as FT_UInt { Some(idx as GlyphId) } else { - debug!("Invalid codepoint: {}", codepoint); + debug!("Invalid codepoint: U+{:04X} ('{}')", codepoint as u32, codepoint); None } } diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs index 9b85db624d8..ddafb94a031 100644 --- a/components/gfx/platform/freetype/font_list.rs +++ b/components/gfx/platform/freetype/font_list.rs @@ -10,10 +10,10 @@ use fontconfig::fontconfig::{FcFontSetList, FcObjectSetCreate, FcObjectSetDestro use fontconfig::fontconfig::{FcObjectSetAdd, FcPatternGetInteger}; use libc; use libc::{c_char, c_int}; -use std::borrow::ToOwned; use std::ffi::CString; use std::ptr; use super::c_str_to_string; +use text::util::is_cjk; static FC_FAMILY: &'static [u8] = b"family\0"; static FC_FILE: &'static [u8] = b"file\0"; @@ -132,12 +132,25 @@ pub fn system_default_family(generic_name: &str) -> Option { } } -pub fn last_resort_font_families() -> Vec { - vec!( - "Fira Sans".to_owned(), - "DejaVu Sans".to_owned(), - "Arial".to_owned() - ) -} - pub static SANS_SERIF_FONT_FAMILY: &'static str = "DejaVu Sans"; + +// Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko +pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { + let mut families = vec!( + "DejaVu Serif", + "FreeSerif", + "DejaVu Sans", + "FreeSans", + ); + + if let Some(codepoint) = codepoint { + if is_cjk(codepoint) { + families.push("TakaoPGothic"); + families.push("Droid Sans Fallback"); + families.push("WenQuanYi Micro Hei"); + families.push("NanumGothic"); + } + } + + families +} diff --git a/components/gfx/platform/freetype/font_template.rs b/components/gfx/platform/freetype/font_template.rs index 13209e3a016..c4b4a9bc9e7 100644 --- a/components/gfx/platform/freetype/font_template.rs +++ b/components/gfx/platform/freetype/font_template.rs @@ -16,34 +16,21 @@ use webrender_api::NativeFontHandle; pub struct FontTemplateData { // If you add members here, review the Debug impl below - pub bytes: Vec, + pub bytes: Option>, pub identifier: Atom, } impl fmt::Debug for FontTemplateData { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("FontTemplateData") - .field("bytes", &format!("[{} bytes]", self.bytes.len())) + .field("bytes", &self.bytes.as_ref().map(|b| format!("[{} bytes]", b.len()))) .field("identifier", &self.identifier) .finish() } } impl FontTemplateData { - pub fn new(identifier: Atom, font_data: Option>) -> Result { - let bytes = match font_data { - Some(bytes) => { - bytes - }, - None => { - // TODO: Handle file load failure! - let mut file = File::open(&*identifier)?; - let mut buffer = vec![]; - file.read_to_end(&mut buffer).unwrap(); - buffer - }, - }; - + pub fn new(identifier: Atom, bytes: Option>) -> Result { Ok(FontTemplateData { bytes: bytes, identifier: identifier, @@ -54,17 +41,29 @@ impl FontTemplateData { /// operation (depending on the platform) which performs synchronous disk I/O /// and should never be done lightly. pub fn bytes(&self) -> Vec { - self.bytes.clone() + self.bytes_if_in_memory().unwrap_or_else(|| { + let mut file = File::open(&*self.identifier).expect("Couldn't open font file!"); + let mut buffer = vec![]; + file.read_to_end(&mut buffer).unwrap(); + buffer + }) } /// Returns a clone of the bytes in this font if they are in memory. This function never /// performs disk I/O. pub fn bytes_if_in_memory(&self) -> Option> { - Some(self.bytes()) + self.bytes.clone() } /// Returns the native font that underlies this font template, if applicable. pub fn native_font(&self) -> Option { - None + if self.bytes.is_none() { + Some(NativeFontHandle { + pathname: String::from(&*self.identifier), + index: 0, + }) + } else { + None + } } } diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs index 5eacf92ffd6..8025f1b33df 100644 --- a/components/gfx/platform/macos/font.rs +++ b/components/gfx/platform/macos/font.rs @@ -229,7 +229,7 @@ impl FontHandleMethods for FontHandle { use style::values::generics::NonNegative; let normalized = self.ctfont.all_traits().normalized_width(); // [-1.0, 1.0] - NonNegative(Percentage(normalized as f32 + 1.0)) + FontStretch(NonNegative(Percentage(normalized as f32 + 1.0))) } fn glyph_index(&self, codepoint: char) -> Option { diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs index d72111f9c1e..656cc043907 100644 --- a/components/gfx/platform/macos/font_list.rs +++ b/components/gfx/platform/macos/font_list.rs @@ -3,7 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use core_text; -use std::borrow::ToOwned; +use text::util::unicode_plane; +use ucd::{Codepoint, UnicodeBlock}; pub fn for_each_available_family(mut callback: F) where F: FnMut(String) { let family_names = core_text::font_collection::get_family_names(); @@ -28,8 +29,162 @@ pub fn system_default_family(_generic_name: &str) -> Option { None } -pub fn last_resort_font_families() -> Vec { - vec!("Arial Unicode MS".to_owned(), "Arial".to_owned()) +// Based on gfxPlatformMac::GetCommonFallbackFonts() in Gecko +pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { + let mut families = vec!("Lucida Grande"); + + if let Some(codepoint) = codepoint { + match unicode_plane(codepoint) { + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane + 0 => { + if let Some(block) = codepoint.block() { + match block { + UnicodeBlock::Arabic | + UnicodeBlock::Syriac | + UnicodeBlock::ArabicSupplement | + UnicodeBlock::Thaana | + UnicodeBlock::NKo => { + families.push("Geeza Pro"); + } + + UnicodeBlock::Devanagari => { + families.push("Devanagari Sangam MN"); + } + + UnicodeBlock::Gurmukhi => { + families.push("Gurmukhi MN"); + } + + UnicodeBlock::Gujarati => { + families.push("Gujarati Sangam MN"); + } + + UnicodeBlock::Tamil => { + families.push("Tamil MN"); + } + + UnicodeBlock::Lao => { + families.push("Lao MN"); + } + + UnicodeBlock::Tibetan => { + families.push("Songti SC"); + } + + UnicodeBlock::Myanmar => { + families.push("Myanmar MN"); + } + + UnicodeBlock::Ethiopic | + UnicodeBlock::EthiopicSupplement | + UnicodeBlock::EthiopicExtended | + UnicodeBlock::EthiopicExtendedA => { + families.push("Kefa"); + } + + UnicodeBlock::Cherokee => { + families.push("Plantagenet Cherokee"); + } + + UnicodeBlock::UnifiedCanadianAboriginalSyllabics | + UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { + families.push("Euphemia UCAS"); + } + + UnicodeBlock::Mongolian | + UnicodeBlock::YiSyllables | + UnicodeBlock::YiRadicals => { + families.push("STHeiti"); + } + + UnicodeBlock::Khmer | + UnicodeBlock::KhmerSymbols => { + families.push("Khmer MN"); + } + + UnicodeBlock::TaiLe => { + families.push("Microsoft Tai Le"); + } + + 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::SupplementalPunctuation => { + families.push("Hiragino Kaku Gothic ProN"); + families.push("Apple Symbols"); + families.push("Menlo"); + families.push("STIXGeneral"); + } + + UnicodeBlock::BraillePatterns => { + families.push("Apple Braille"); + } + + UnicodeBlock::Bopomofo | + UnicodeBlock::HangulCompatibilityJamo | + UnicodeBlock::Kanbun | + UnicodeBlock::BopomofoExtended | + UnicodeBlock::CJKStrokes | + UnicodeBlock::KatakanaPhoneticExtensions => { + families.push("Hiragino Sans GB"); + } + + UnicodeBlock::YijingHexagramSymbols | + UnicodeBlock::CyrillicExtendedB | + UnicodeBlock::Bamum | + UnicodeBlock::ModifierToneLetters | + UnicodeBlock::LatinExtendedD | + UnicodeBlock::ArabicPresentationFormsA | + UnicodeBlock::HalfwidthandFullwidthForms | + UnicodeBlock::Specials => { + families.push("Apple Symbols"); + } + + _ => {} + } + } + } + + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane + 1 => { + families.push("Apple Symbols"); + families.push("STIXGeneral"); + } + + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Ideographic_Plane + 2 => { + // Systems with MS Office may have these fonts + families.push("MingLiU-ExtB"); + families.push("SimSun-ExtB"); + } + + _ => {} + } + } + + families.push("Geneva"); + families.push("Arial Unicode MS"); + families } pub static SANS_SERIF_FONT_FAMILY: &'static str = "Helvetica"; diff --git a/components/gfx/platform/windows/font.rs b/components/gfx/platform/windows/font.rs index ed94d0fc5a2..e657996d119 100644 --- a/components/gfx/platform/windows/font.rs +++ b/components/gfx/platform/windows/font.rs @@ -163,7 +163,7 @@ impl FontInfo { let weight = StyleFontWeight(weight_val as f32); - let stretch = NonNegative(match min(9, max(1, width_val)) { + let stretch = StyleFontStretch(NonNegative(match min(9, max(1, width_val)) { 1 => FontStretchKeyword::UltraCondensed, 2 => FontStretchKeyword::ExtraCondensed, 3 => FontStretchKeyword::Condensed, @@ -174,7 +174,7 @@ impl FontInfo { 8 => FontStretchKeyword::ExtraExpanded, 9 => FontStretchKeyword::UltraExpanded, _ => return Err(()), - }.compute()); + }.compute())); let style = if italic_bool { GenericFontStyle::Italic @@ -212,7 +212,7 @@ impl FontInfo { // slightly blacker black FontWeight::ExtraBlack => 1000., }); - let stretch = NonNegative(match font.stretch() { + let stretch = StyleFontStretch(NonNegative(match font.stretch() { FontStretch::Undefined => FontStretchKeyword::Normal, FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed, FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed, @@ -223,7 +223,7 @@ impl FontInfo { FontStretch::Expanded => FontStretchKeyword::Expanded, FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded, FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded, - }.compute()); + }.compute())); Ok(FontInfo { family_name: font.family_name(), diff --git a/components/gfx/platform/windows/font_list.rs b/components/gfx/platform/windows/font_list.rs index 27dfd5e73fe..3271b77e7fb 100644 --- a/components/gfx/platform/windows/font_list.rs +++ b/components/gfx/platform/windows/font_list.rs @@ -7,6 +7,8 @@ use servo_atoms::Atom; use std::collections::HashMap; use std::sync::Mutex; use std::sync::atomic::{Ordering, AtomicUsize}; +use text::util::unicode_plane; +use ucd::{Codepoint, UnicodeBlock}; lazy_static! { static ref FONT_ATOM_COUNTER: AtomicUsize = AtomicUsize::new(1); @@ -19,10 +21,6 @@ pub fn system_default_family(_: &str) -> Option { Some("Verdana".to_owned()) } -pub fn last_resort_font_families() -> Vec { - vec!("Arial".to_owned()) -} - pub fn for_each_available_family(mut callback: F) where F: FnMut(String) { let system_fc = FontCollection::system(); for family in system_fc.families_iter() { @@ -69,3 +67,270 @@ pub fn font_from_atom(ident: &Atom) -> Font { let fonts = FONT_ATOM_MAP.lock().unwrap(); FontCollection::system().get_font_from_descriptor(fonts.get(ident).unwrap()).unwrap() } + +// Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko +pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { + let mut families = vec!("Arial"); + + if let Some(codepoint) = codepoint { + match unicode_plane(codepoint) { + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane + 0 => { + if let Some(block) = codepoint.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 +} diff --git a/components/gfx/tests/font_context.rs b/components/gfx/tests/font_context.rs index c918ef86e90..dc9d1ac2df5 100644 --- a/components/gfx/tests/font_context.rs +++ b/components/gfx/tests/font_context.rs @@ -10,7 +10,7 @@ extern crate style; extern crate webrender_api; use app_units::Au; -use gfx::font::FontHandleMethods; +use gfx::font::{fallback_font_families, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontSearchScope}; use gfx::font_cache_thread::{FontTemplates, FontTemplateInfo}; use gfx::font_context::{FontContext, FontContextHandle, FontSource}; use gfx::font_template::FontTemplateDescriptor; @@ -24,29 +24,31 @@ use std::path::PathBuf; use std::rc::Rc; use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; use style::properties::style_structs::Font as FontStyleStruct; -use style::values::computed::Percentage; use style::values::computed::font::{FamilyName, FamilyNameSyntax, FontFamily, FontFamilyList, FontSize}; -use style::values::computed::font::{FontWeight, SingleFontFamily}; -use style::values::generics::NonNegative; +use style::values::computed::font::{FontStretch, FontWeight, SingleFontFamily}; use style::values::generics::font::FontStyle; struct TestFontSource { handle: FontContextHandle, - families: HashMap, + families: HashMap, find_font_count: Rc>, } impl TestFontSource { fn new() -> TestFontSource { let mut csstest_ascii = FontTemplates::new(); - Self::add_face(&mut csstest_ascii, "csstest-ascii"); + Self::add_face(&mut csstest_ascii, "csstest-ascii", None); let mut csstest_basic = FontTemplates::new(); - Self::add_face(&mut csstest_basic, "csstest-basic-regular"); + Self::add_face(&mut csstest_basic, "csstest-basic-regular", None); + + let mut fallback = FontTemplates::new(); + Self::add_face(&mut fallback, "csstest-basic-regular", Some("fallback")); let mut families = HashMap::new(); - families.insert(Atom::from("CSSTest ASCII"), csstest_ascii); - families.insert(Atom::from("CSSTest Basic"), csstest_basic); + families.insert("CSSTest ASCII".to_owned(), csstest_ascii); + families.insert("CSSTest Basic".to_owned(), csstest_basic); + families.insert(fallback_font_families(None)[0].to_owned(), fallback); TestFontSource { handle: FontContextHandle::new(), @@ -55,7 +57,7 @@ impl TestFontSource { } } - fn add_face(family: &mut FontTemplates, name: &str) { + fn add_face(family: &mut FontTemplates, name: &str, identifier: Option<&str>) { let mut path: PathBuf = [ env!("CARGO_MANIFEST_DIR"), "tests", @@ -65,9 +67,10 @@ impl TestFontSource { path.push(format!("{}.ttf", name)); let file = File::open(path).unwrap(); + let identifier = Atom::from(identifier.unwrap_or(name)); family.add_template( - Atom::from(name), + identifier, Some(file.bytes().map(|b| b.unwrap()).collect()) ) } @@ -78,17 +81,17 @@ impl FontSource for TestFontSource { webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0) } - fn find_font_template( + fn font_template( &mut self, - family: SingleFontFamily, - desc: FontTemplateDescriptor + template_descriptor: FontTemplateDescriptor, + family_descriptor: FontFamilyDescriptor, ) -> Option { let handle = &self.handle; self.find_font_count.set(self.find_font_count.get() + 1); self.families - .get_mut(family.atom()) - .and_then(|family| family.find_font_for_style(&desc, handle)) + .get_mut(family_descriptor.name()) + .and_then(|family| family.find_font_for_style(&template_descriptor, handle)) .map(|template| { FontTemplateInfo { font_template: template, @@ -96,10 +99,6 @@ impl FontSource for TestFontSource { } }) } - - fn last_resort_font_template(&mut self, _desc: FontTemplateDescriptor) -> FontTemplateInfo { - unimplemented!(); - } } fn style() -> FontStyleStruct { @@ -109,7 +108,7 @@ fn style() -> FontStyleStruct { font_variant_caps: FontVariantCaps::Normal, font_weight: FontWeight::normal(), font_size: FontSize::medium(), - font_stretch: NonNegative(Percentage(1.)), + font_stretch: FontStretch::hundred(), hash: 0, }; style.compute_font_hash(); @@ -162,14 +161,72 @@ fn test_font_group_find_by_codepoint() { let group = context.font_group(Arc::new(style)); let font = group.borrow_mut().find_by_codepoint(&mut context, 'a').unwrap(); - assert_eq!(font.borrow().handle.family_name(), "CSSTest ASCII"); + assert_eq!(&*font.borrow().identifier(), "csstest-ascii"); assert_eq!(count.get(), 1, "only the first font in the list should have been loaded"); let font = group.borrow_mut().find_by_codepoint(&mut context, 'a').unwrap(); - assert_eq!(font.borrow().handle.family_name(), "CSSTest ASCII"); + assert_eq!(&*font.borrow().identifier(), "csstest-ascii"); assert_eq!(count.get(), 1, "we shouldn't load the same font a second time"); let font = group.borrow_mut().find_by_codepoint(&mut context, 'á').unwrap(); - assert_eq!(font.borrow().handle.family_name(), "CSSTest Basic"); + assert_eq!(&*font.borrow().identifier(), "csstest-basic-regular"); assert_eq!(count.get(), 2, "both fonts should now have been loaded"); } + +#[test] +fn test_font_fallback() { + let source = TestFontSource::new(); + let mut context = FontContext::new(source); + + let mut style = style(); + style.set_font_family(font_family(vec!("CSSTest ASCII"))); + + let group = context.font_group(Arc::new(style)); + + let font = group.borrow_mut().find_by_codepoint(&mut context, 'a').unwrap(); + assert_eq!( + &*font.borrow().identifier(), "csstest-ascii", + "a family in the group should be used if there is a matching glyph" + ); + + let font = group.borrow_mut().find_by_codepoint(&mut context, 'á').unwrap(); + assert_eq!( + &*font.borrow().identifier(), "fallback", + "a fallback font should be used if there is no matching glyph in the group" + ); +} + +#[test] +fn test_font_template_is_cached() { + let source = TestFontSource::new(); + let count = source.find_font_count.clone(); + let mut context = FontContext::new(source); + + let mut font_descriptor = FontDescriptor { + template_descriptor: FontTemplateDescriptor { + weight: FontWeight::normal(), + stretch: FontStretch::hundred(), + style: FontStyle::Normal, + }, + variant: FontVariantCaps::Normal, + pt_size: Au(10), + }; + + let family_descriptor = FontFamilyDescriptor::new( + FontFamilyName::from("CSSTest Basic"), + FontSearchScope::Any, + ); + + let font1 = context.font(&font_descriptor, &family_descriptor).unwrap(); + + font_descriptor.pt_size = Au(20); + let font2 = context.font(&font_descriptor, &family_descriptor).unwrap(); + + assert_ne!( + font1.borrow().actual_pt_size, + font2.borrow().actual_pt_size, + "the same font should not have been returned" + ); + + assert_eq!(count.get(), 1, "we should only have fetched the template data from the cache thread once"); +} diff --git a/components/gfx/tests/font_template.rs b/components/gfx/tests/font_template.rs index af31d8f229d..e5857ce4ef7 100644 --- a/components/gfx/tests/font_template.rs +++ b/components/gfx/tests/font_template.rs @@ -17,7 +17,7 @@ fn test_font_template_descriptor() { use std::io::prelude::*; use std::path::PathBuf; use style::values::computed::Percentage; - use style::values::computed::font::FontWeight; + use style::values::computed::font::{FontStretch, FontWeight}; use style::values::generics::NonNegative; use style::values::generics::font::FontStyle; @@ -45,25 +45,25 @@ fn test_font_template_descriptor() { assert_eq!(descriptor("DejaVuSans"), FontTemplateDescriptor { weight: FontWeight::normal(), - stretch: NonNegative(Percentage(1.)), + stretch: FontStretch::hundred(), style: FontStyle::Normal, }); assert_eq!(descriptor("DejaVuSans-Bold"), FontTemplateDescriptor { weight: FontWeight::bold(), - stretch: NonNegative(Percentage(1.)), + stretch: FontStretch::hundred(), style: FontStyle::Normal, }); assert_eq!(descriptor("DejaVuSans-Oblique"), FontTemplateDescriptor { weight: FontWeight::normal(), - stretch: NonNegative(Percentage(1.)), + stretch: FontStretch::hundred(), style: FontStyle::Italic, }); assert_eq!(descriptor("DejaVuSansCondensed-BoldOblique"), FontTemplateDescriptor { weight: FontWeight::bold(), - stretch: NonNegative(Percentage(0.875)), + stretch: FontStretch(NonNegative(Percentage(0.875))), style: FontStyle::Italic, }); } diff --git a/components/gfx/text/util.rs b/components/gfx/text/util.rs index 5be03cb2b78..f740c4a54e6 100644 --- a/components/gfx/text/util.rs +++ b/components/gfx/text/util.rs @@ -2,6 +2,8 @@ * 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 ucd::{Codepoint, UnicodeBlock}; + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CompressionMode { CompressNone, @@ -114,3 +116,42 @@ pub fn is_bidi_control(c: char) -> bool { _ => false } } + +pub fn unicode_plane(codepoint: char) -> u32 { + (codepoint as u32) >> 16 +} + +pub fn is_cjk(codepoint: char) -> bool { + if let Some(block) = codepoint.block() { + match block { + UnicodeBlock::CJKRadicalsSupplement | + UnicodeBlock::KangxiRadicals | + UnicodeBlock::IdeographicDescriptionCharacters | + UnicodeBlock::CJKSymbolsandPunctuation | + UnicodeBlock::Hiragana | + UnicodeBlock::Katakana | + UnicodeBlock::Bopomofo | + UnicodeBlock::HangulCompatibilityJamo | + UnicodeBlock::Kanbun | + UnicodeBlock::BopomofoExtended | + UnicodeBlock::CJKStrokes | + UnicodeBlock::KatakanaPhoneticExtensions | + UnicodeBlock::EnclosedCJKLettersandMonths | + UnicodeBlock::CJKCompatibility | + UnicodeBlock::CJKUnifiedIdeographsExtensionA | + UnicodeBlock::YijingHexagramSymbols | + UnicodeBlock::CJKUnifiedIdeographs | + UnicodeBlock::CJKCompatibilityIdeographs | + UnicodeBlock::CJKCompatibilityForms | + UnicodeBlock::HalfwidthandFullwidthForms => { + return true + } + + _ => {} + } + } + + + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Ideographic_Plane + unicode_plane(codepoint) == 2 +} diff --git a/components/layout/text.rs b/components/layout/text.rs index 71a5bcff158..d7763a8e570 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -215,61 +215,63 @@ impl TextRunScanner { let (mut start_position, mut end_position) = (0, 0); for (byte_index, character) in text.char_indices() { - let font = font_group.borrow_mut().find_by_codepoint(&mut font_context, character); + if !character.is_control() { + let font = font_group.borrow_mut().find_by_codepoint(&mut font_context, character); - let bidi_level = match bidi_levels { - Some(levels) => levels[*paragraph_bytes_processed], - None => bidi::Level::ltr(), - }; + let bidi_level = match bidi_levels { + Some(levels) => levels[*paragraph_bytes_processed], + None => bidi::Level::ltr(), + }; - // Break the run if the new character has a different explicit script than the - // previous characters. - // - // TODO: Special handling of paired punctuation characters. - // http://www.unicode.org/reports/tr24/#Common - let script = get_script(character); - let compatible_script = is_compatible(script, run_info.script); - if compatible_script && !is_specific(run_info.script) && is_specific(script) { - run_info.script = script; - } - - let selected = match selection { - Some(range) => range.contains(ByteIndex(byte_index as isize)), - None => false - }; - - // Now, if necessary, flush the mapping we were building up. - let flush_run = !run_info.has_font(&font) || - run_info.bidi_level != bidi_level || - !compatible_script; - let new_mapping_needed = flush_run || mapping.selected != selected; - - if new_mapping_needed { - // We ignore empty mappings at the very start of a fragment. - // The run info values are uninitialized at this point so - // flushing an empty mapping is pointless. - if end_position > 0 { - mapping.flush(&mut mappings, - &mut run_info, - &**text, - compression, - text_transform, - &mut last_whitespace, - &mut start_position, - end_position); + // Break the run if the new character has a different explicit script than the + // previous characters. + // + // TODO: Special handling of paired punctuation characters. + // http://www.unicode.org/reports/tr24/#Common + let script = get_script(character); + let compatible_script = is_compatible(script, run_info.script); + if compatible_script && !is_specific(run_info.script) && is_specific(script) { + run_info.script = script; } - if run_info.text.len() > 0 { - if flush_run { - run_info.flush(&mut run_info_list, &mut insertion_point); - run_info = RunInfo::new(); + + let selected = match selection { + Some(range) => range.contains(ByteIndex(byte_index as isize)), + None => false + }; + + // Now, if necessary, flush the mapping we were building up. + let flush_run = !run_info.has_font(&font) || + run_info.bidi_level != bidi_level || + !compatible_script; + let new_mapping_needed = flush_run || mapping.selected != selected; + + if new_mapping_needed { + // We ignore empty mappings at the very start of a fragment. + // The run info values are uninitialized at this point so + // flushing an empty mapping is pointless. + if end_position > 0 { + mapping.flush(&mut mappings, + &mut run_info, + &**text, + compression, + text_transform, + &mut last_whitespace, + &mut start_position, + end_position); } - mapping = RunMapping::new(&run_info_list[..], - fragment_index); + if run_info.text.len() > 0 { + if flush_run { + run_info.flush(&mut run_info_list, &mut insertion_point); + run_info = RunInfo::new(); + } + mapping = RunMapping::new(&run_info_list[..], + fragment_index); + } + run_info.font = font; + run_info.bidi_level = bidi_level; + run_info.script = script; + mapping.selected = selected; } - run_info.font = font; - run_info.bidi_level = bidi_level; - run_info.script = script; - mapping.selected = selected; } // Consume this character. diff --git a/components/style/properties/longhand/font.mako.rs b/components/style/properties/longhand/font.mako.rs index 7309aca8a38..0dca4a4c67e 100644 --- a/components/style/properties/longhand/font.mako.rs +++ b/components/style/properties/longhand/font.mako.rs @@ -84,7 +84,7 @@ ${helpers.predefined_type("font-synthesis", ${helpers.predefined_type( "font-stretch", "FontStretch", - initial_value="computed::NonNegativePercentage::hundred()", + initial_value="computed::FontStretch::hundred()", initial_specified_value="specified::FontStretch::normal()", animation_value_type="Percentage", flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 9e5d4c3b7f1..b39d93aff01 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -2305,13 +2305,10 @@ pub mod style_structs { pub fn compute_font_hash(&mut self) { // Corresponds to the fields in // `gfx::font_template::FontTemplateDescriptor`. - // - // FIXME(emilio): Where's font-style? let mut hasher: FnvHasher = Default::default(); - // We hash the floating point number with four decimal - // places. - hasher.write_u64((self.font_weight.0 * 10000.).trunc() as u64); - hasher.write_u64(((self.font_stretch.0).0 * 10000.).trunc() as u64); + self.font_weight.hash(&mut hasher); + self.font_stretch.hash(&mut hasher); + self.font_style.hash(&mut hasher); self.font_family.hash(&mut hasher); self.hash = hasher.finish() } diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index 1ca8400c16a..d486440163c 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -15,21 +15,20 @@ use gecko_bindings::sugar::refptr::RefPtr; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use std::fmt::{self, Write}; -#[cfg(feature = "gecko")] use std::hash::{Hash, Hasher}; #[cfg(feature = "servo")] use std::slice; use style_traits::{CssWriter, ParseError, ToCss}; use values::CSSFloat; use values::animated::{ToAnimatedValue, ToAnimatedZero}; -use values::computed::{Angle, Context, Integer, NonNegativeLength, Number, ToComputedValue}; +use values::computed::{Angle, Context, Integer, NonNegative, NonNegativeLength, NonNegativePercentage}; +use values::computed::{Number, Percentage, ToComputedValue}; use values::generics::font::{self as generics, FeatureTagValue, FontSettings, VariationValue}; use values::specified::font::{self as specified, MIN_FONT_WEIGHT, MAX_FONT_WEIGHT}; use values::specified::length::{FontBaseSize, NoCalcLength}; pub use values::computed::Length as MozScriptMinSize; pub use values::specified::font::{FontSynthesis, MozScriptSizeMultiplier, XLang, XTextZoom}; -pub use values::computed::NonNegativePercentage as FontStretch; /// A value for the font-weight property per: /// @@ -41,6 +40,12 @@ pub use values::computed::NonNegativePercentage as FontStretch; #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub struct FontWeight(pub Number); +impl Hash for FontWeight { + fn hash(&self, hasher: &mut H) { + hasher.write_u64((self.0 * 10000.).trunc() as u64); + } +} + impl ToAnimatedValue for FontWeight { type AnimatedValue = Number; @@ -853,6 +858,12 @@ impl ToAnimatedValue for FontStyleAngle { } } +impl Hash for FontStyleAngle { + fn hash(&self, hasher: &mut H) { + hasher.write_u64((self.0.degrees() * 10000.).trunc() as u64); + } +} + /// The computed value of `font-style`. /// /// FIXME(emilio): Angle should be a custom type to handle clamping during @@ -916,3 +927,43 @@ impl ToCss for FontStyle { } } } + +/// A value for the font-stretch property per: +/// +/// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, ToCss)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +pub struct FontStretch(pub NonNegativePercentage); + +impl FontStretch { + /// 100% + pub fn hundred() -> Self { + FontStretch(NonNegativePercentage::hundred()) + } + + /// The float value of the percentage + #[inline] + pub fn value(&self) -> CSSFloat { + ((self.0).0).0 + } +} + +impl ToAnimatedValue for FontStretch { + type AnimatedValue = Percentage; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + (self.0).0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + FontStretch(NonNegative(animated)) + } +} + +impl Hash for FontStretch { + fn hash(&self, hasher: &mut H) { + hasher.write_u64((self.value() * 10000.).trunc() as u64); + } +} diff --git a/components/style/values/generics/font.rs b/components/style/values/generics/font.rs index f5e62e0ab5f..02df291065f 100644 --- a/components/style/values/generics/font.rs +++ b/components/style/values/generics/font.rs @@ -228,7 +228,7 @@ impl Default for KeywordSize { /// https://drafts.csswg.org/css-fonts-4/#font-style-prop #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero)] pub enum FontStyle { #[animation(error)] diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index c68ed91426a..c3b29c93769 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -156,7 +156,7 @@ impl SpecifiedValueInfo for CounterStyleOrNone { /// A wrapper of Non-negative values. #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, Hash, MallocSizeOf, PartialEq, PartialOrd, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, ToCss)] pub struct NonNegative(pub T); diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index 266d7c52c09..f26f5ab74a6 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -490,22 +490,22 @@ impl Parse for FontStretch { } impl ToComputedValue for FontStretch { - type ComputedValue = NonNegative; + type ComputedValue = computed::FontStretch; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { FontStretch::Stretch(ref percentage) => { - NonNegative(percentage.to_computed_value(context)) + computed::FontStretch(NonNegative(percentage.to_computed_value(context))) }, FontStretch::Keyword(ref kw) => { - NonNegative(kw.compute()) + computed::FontStretch(NonNegative(kw.compute())) }, FontStretch::System(_) => self.compute_system(context), } } fn from_computed_value(computed: &Self::ComputedValue) -> Self { - FontStretch::Stretch(Percentage::from_computed_value(&computed.0)) + FontStretch::Stretch(Percentage::from_computed_value(&(computed.0).0)) } } diff --git a/tests/wpt/metadata/css/CSS2/fonts/font-family-invalid-characters-001.xht.ini b/tests/wpt/metadata/css/CSS2/fonts/font-family-invalid-characters-001.xht.ini deleted file mode 100644 index 4d1ed22c544..00000000000 --- a/tests/wpt/metadata/css/CSS2/fonts/font-family-invalid-characters-001.xht.ini +++ /dev/null @@ -1,3 +0,0 @@ -[font-family-invalid-characters-001.xht] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata/css/CSS2/fonts/font-family-invalid-characters-003.xht.ini b/tests/wpt/metadata/css/CSS2/fonts/font-family-invalid-characters-003.xht.ini deleted file mode 100644 index eb1bdeed63f..00000000000 --- a/tests/wpt/metadata/css/CSS2/fonts/font-family-invalid-characters-003.xht.ini +++ /dev/null @@ -1,3 +0,0 @@ -[font-family-invalid-characters-003.xht] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata/css/CSS2/fonts/font-family-rule-001.xht.ini b/tests/wpt/metadata/css/CSS2/fonts/font-family-rule-001.xht.ini deleted file mode 100644 index d03c54d2aa5..00000000000 --- a/tests/wpt/metadata/css/CSS2/fonts/font-family-rule-001.xht.ini +++ /dev/null @@ -1,3 +0,0 @@ -[font-family-rule-001.xht] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-015.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-015.html.ini new file mode 100644 index 00000000000..b2944714a37 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-015.html.ini @@ -0,0 +1,5 @@ +[css3-text-line-break-baspglwj-015.html] + type: testharness + [ ] + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-033.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-033.html.ini new file mode 100644 index 00000000000..6ae3d4d4574 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-033.html.ini @@ -0,0 +1,5 @@ +[css3-text-line-break-baspglwj-033.html] + type: testharness + [ ] + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-034.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-034.html.ini new file mode 100644 index 00000000000..8fe184eb98e --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-034.html.ini @@ -0,0 +1,5 @@ +[css3-text-line-break-baspglwj-034.html] + type: testharness + [ ] + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-035.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-035.html.ini new file mode 100644 index 00000000000..caf9ac5e95c --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-035.html.ini @@ -0,0 +1,5 @@ +[css3-text-line-break-baspglwj-035.html] + type: testharness + [ ] + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-037.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-037.html.ini new file mode 100644 index 00000000000..95fd8784e41 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-baspglwj-037.html.ini @@ -0,0 +1,5 @@ +[css3-text-line-break-baspglwj-037.html] + type: testharness + [ ] + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-006.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-006.html.ini index 197d51521a7..cdc62427d96 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-006.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-006.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-006.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-014.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-014.html.ini index 6cae1b5fd39..f1077a4ed9c 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-014.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-014.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-014.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-015.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-015.html.ini index ef5a5c13dcc..f979c174638 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-015.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-015.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-015.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-016.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-016.html.ini index c4bad14bb91..63c5f6a778b 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-016.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-016.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-016.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-018.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-018.html.ini index 3d29b63cd54..ebdd8cb09e1 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-018.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-018.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-018.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-019.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-019.html.ini index bb609b15f14..19c2a8dd998 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-019.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-019.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-019.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-020.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-020.html.ini index 79a3ff6e3ff..e7a42c9b605 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-020.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-020.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-020.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-023.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-023.html.ini index 800206f5900..4182bf08d8f 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-023.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-023.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-023.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-025.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-025.html.ini new file mode 100644 index 00000000000..917909cdca9 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-025.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-025.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-026.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-026.html.ini new file mode 100644 index 00000000000..8e0d0a460cc --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-026.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-026.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-027.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-027.html.ini new file mode 100644 index 00000000000..8d54ade282c --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-027.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-027.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-028.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-028.html.ini new file mode 100644 index 00000000000..f32f2b8abaf --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-028.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-028.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-029.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-029.html.ini new file mode 100644 index 00000000000..b11cd8b20d6 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-029.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-029.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-030.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-030.html.ini new file mode 100644 index 00000000000..4609487b38b --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-030.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-030.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-031.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-031.html.ini new file mode 100644 index 00000000000..8aff58db107 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-031.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-031.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-032.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-032.html.ini new file mode 100644 index 00000000000..dc8cf7818ef --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-032.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-032.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-033.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-033.html.ini new file mode 100644 index 00000000000..156a5f02edc --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-033.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-033.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-034.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-034.html.ini new file mode 100644 index 00000000000..d16116ea6ee --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-034.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-034.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-035.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-035.html.ini new file mode 100644 index 00000000000..2dc431dcef5 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-035.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-035.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-036.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-036.html.ini new file mode 100644 index 00000000000..b45db5927cb --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-036.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-036.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-037.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-037.html.ini new file mode 100644 index 00000000000..745f13596f4 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-037.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-037.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-119.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-119.html.ini index a486bfd4dbb..f3c7fca720c 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-119.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-119.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-119.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-120.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-120.html.ini index c7c9eedf700..9625057e151 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-120.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-120.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-120.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-122.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-122.html.ini index 1bae83706db..bb96543f1c8 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-122.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-122.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-122.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-123.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-123.html.ini index c45d2eb11e8..a6463e07e65 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-123.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-123.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-123.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-124.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-124.html.ini index 75d6e2cbe81..d1e73c90bfd 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-124.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-124.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-124.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-125.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-125.html.ini index 5697632f9b4..d7965a69364 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-125.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-125.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-125.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-128.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-128.html.ini index 19932cac3b2..d0132636eb5 100644 --- a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-128.html.ini +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-128.html.ini @@ -1,4 +1,3 @@ [css3-text-line-break-opclns-128.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-130.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-130.html.ini new file mode 100644 index 00000000000..90a9908eb57 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-130.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-130.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-131.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-131.html.ini new file mode 100644 index 00000000000..5718f0b4cf1 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-131.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-131.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-132.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-132.html.ini new file mode 100644 index 00000000000..8c1daa0e851 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-132.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-132.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-133.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-133.html.ini new file mode 100644 index 00000000000..43683c29d36 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-133.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-133.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-134.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-134.html.ini new file mode 100644 index 00000000000..6b1f4d8d0cf --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-134.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-134.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-135.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-135.html.ini new file mode 100644 index 00000000000..204d22ff55b --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-135.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-135.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-136.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-136.html.ini new file mode 100644 index 00000000000..0e0794ecf18 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-136.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-136.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-137.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-137.html.ini new file mode 100644 index 00000000000..ffe57871b87 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-137.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-137.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-138.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-138.html.ini new file mode 100644 index 00000000000..3941a5c2344 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-138.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-138.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-139.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-139.html.ini new file mode 100644 index 00000000000..59e58c99d5a --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-139.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-139.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-140.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-140.html.ini new file mode 100644 index 00000000000..b719e73370e --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-140.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-140.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-141.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-141.html.ini new file mode 100644 index 00000000000..0ad2710e4d7 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-141.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-141.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-142.html.ini b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-142.html.ini new file mode 100644 index 00000000000..cc85a3d0e94 --- /dev/null +++ b/tests/wpt/metadata/css/css-text/i18n/css3-text-line-break-opclns-142.html.ini @@ -0,0 +1,4 @@ +[css3-text-line-break-opclns-142.html] + type: reftest + expected: + if os == "mac": FAIL diff --git a/tests/wpt/metadata/css/css-text/word-break/word-break-break-all-007.html.ini b/tests/wpt/metadata/css/css-text/word-break/word-break-break-all-007.html.ini index 69aeb33957c..189f2d4dfec 100644 --- a/tests/wpt/metadata/css/css-text/word-break/word-break-break-all-007.html.ini +++ b/tests/wpt/metadata/css/css-text/word-break/word-break-break-all-007.html.ini @@ -1,4 +1,3 @@ [word-break-break-all-007.html] type: reftest - expected: - if os == "linux": FAIL + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/word-break/word-break-normal-bo-000.html.ini b/tests/wpt/metadata/css/css-text/word-break/word-break-normal-bo-000.html.ini index c7554ba00fb..fb935620095 100644 --- a/tests/wpt/metadata/css/css-text/word-break/word-break-normal-bo-000.html.ini +++ b/tests/wpt/metadata/css/css-text/word-break/word-break-normal-bo-000.html.ini @@ -1,3 +1,4 @@ [word-break-normal-bo-000.html] type: reftest - expected: FAIL + expected: + if os == "linux": FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 99ce5116e01..b634c2e37b7 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -2267,6 +2267,42 @@ {} ] ], + "css/font_fallback_01.html": [ + [ + "/_mozilla/css/font_fallback_01.html", + [ + [ + "/_mozilla/css/font_fallback_failed_ref.html", + "!=" + ] + ], + {} + ] + ], + "css/font_fallback_02.html": [ + [ + "/_mozilla/css/font_fallback_02.html", + [ + [ + "/_mozilla/css/font_fallback_failed_ref.html", + "!=" + ] + ], + {} + ] + ], + "css/font_fallback_03.html": [ + [ + "/_mozilla/css/font_fallback_03.html", + [ + [ + "/_mozilla/css/font_fallback_failed_ref.html", + "!=" + ] + ], + {} + ] + ], "css/font_size.html": [ [ "/_mozilla/css/font_size.html", @@ -8248,6 +8284,11 @@ {} ] ], + "css/font_fallback_failed_ref.html": [ + [ + {} + ] + ], "css/font_size_ref.html": [ [ {} @@ -61506,6 +61547,22 @@ "0c8f70a37e6d56370576c7f5ba7f1df359db8f9c", "support" ], + "css/font_fallback_01.html": [ + "1dee8f90d8d1ee1f91e9ad9d1442a3abfec5d596", + "reftest" + ], + "css/font_fallback_02.html": [ + "60d61d9d51206c8b741ef7914447329722950d67", + "reftest" + ], + "css/font_fallback_03.html": [ + "4ef0d7e423624e93b3f028d9baa5a11f9e8f28b2", + "reftest" + ], + "css/font_fallback_failed_ref.html": [ + "ebc502a8b3e0fd4dd5eb79a1dae83ab3e6774178", + "support" + ], "css/font_size.html": [ "7c944acb4f7d909083888a355e6d664c23081b99", "reftest" diff --git a/tests/wpt/mozilla/meta/css/font_fallback_01.html.ini b/tests/wpt/mozilla/meta/css/font_fallback_01.html.ini new file mode 100644 index 00000000000..58e23d74cf3 --- /dev/null +++ b/tests/wpt/mozilla/meta/css/font_fallback_01.html.ini @@ -0,0 +1,4 @@ +[font_fallback_01.html] + type: reftest + expected: + if os == "linux": FAIL diff --git a/tests/wpt/mozilla/meta/css/font_fallback_02.html.ini b/tests/wpt/mozilla/meta/css/font_fallback_02.html.ini new file mode 100644 index 00000000000..e4e5cd5638c --- /dev/null +++ b/tests/wpt/mozilla/meta/css/font_fallback_02.html.ini @@ -0,0 +1,4 @@ +[font_fallback_02.html] + type: reftest + expected: + if os == "linux": FAIL diff --git a/tests/wpt/mozilla/tests/css/font_fallback_01.html b/tests/wpt/mozilla/tests/css/font_fallback_01.html new file mode 100644 index 00000000000..306c26fd6f8 --- /dev/null +++ b/tests/wpt/mozilla/tests/css/font_fallback_01.html @@ -0,0 +1,4 @@ + + + +の diff --git a/tests/wpt/mozilla/tests/css/font_fallback_02.html b/tests/wpt/mozilla/tests/css/font_fallback_02.html new file mode 100644 index 00000000000..0ca0a826865 --- /dev/null +++ b/tests/wpt/mozilla/tests/css/font_fallback_02.html @@ -0,0 +1,4 @@ + + + +コ diff --git a/tests/wpt/mozilla/tests/css/font_fallback_03.html b/tests/wpt/mozilla/tests/css/font_fallback_03.html new file mode 100644 index 00000000000..9b413df19df --- /dev/null +++ b/tests/wpt/mozilla/tests/css/font_fallback_03.html @@ -0,0 +1,4 @@ + + + +✂ diff --git a/tests/wpt/mozilla/tests/css/font_fallback_failed_ref.html b/tests/wpt/mozilla/tests/css/font_fallback_failed_ref.html new file mode 100644 index 00000000000..6cc621b90ea --- /dev/null +++ b/tests/wpt/mozilla/tests/css/font_fallback_failed_ref.html @@ -0,0 +1,3 @@ + + +