Implement font fallback

Prior to this change, if none of the fonts specified in CSS contained a
glyph for a codepoint, we tried only one fallback font. If that font
didn't contain the glyph, we'd give up.

With this change, we try multiple fonts in turn. The font names we try
differ across each platform, and based on the codepoint we're trying to
match. The current implementation is heavily inspired by the analogous
code in Gecko, but I've used to ucd lib to make it more readable,
whereas Gecko matches raw unicode ranges.

This fixes some of the issues reported in #17267, although colour emoji
support is not implemented.

== Notes on changes to WPT metadata ==

=== css/css-text/i18n/css3-text-line-break-opclns-* ===

A bunch of these have started failing on macos when they previously
passed.

These tests check that the browser automatically inserts line breaks
near certain characters that are classified as "opening and closing
punctuation". The idea is that if we have e.g. an opening parenthesis,
it does not make sense for it to appear at the end of a line box; it
should "stick" to the next character and go into the next line box.

Before this change, a lot of these codepoints rendered as a missing
glyph on Mac and Linux. In some cases, that meant that the test was
passing.

After this change, a bunch of these codepoints are now rendering glyphs
on Mac (but not Linux). In some cases, the test should continue to pass
where it previously did when rendering with the missing glyph.

However, it seems this has also exposed a layout bug. The "ref" div in
these tests contains a <br> element, and it seems that this, combined
with these punctuation characters, makes the spacing between glyphs ever
so slightly different to the "test" div. (Speculation: might be
something to do with shaping?)

Therefore I've had to mark a bunch of these tests failing on mac.

=== css/css-text/i18n/css3-text-line-break-baspglwj-* ===

Some of these previously passed on Mac due to a missing glyph. Now that
we're rendering the correct glyph, they are failing.

=== css/css-text/word-break/word-break-normal-bo-000.html ===

The characters now render correctly on Mac, and the test is passing. But
we do not find a suitable fallback font on Linux, so it is still failing
on that platform.

=== css/css-text/word-break/word-break-break-all-007.html ===

This was previously passing on Mac, but only because missing character
glyphs were rendered. Now that a fallback font is able to be found, it
(correctly) fails.

=== mozilla/tests/css/font_fallback_* ===

These are new tests added in this commit. 01 and 02 are marked failing
on Linux because the builders don't have the appropriate fonts installed
(that will be a follow-up).

Fix build errors from rebase

FontTemplateDescriptor can no longer just derive(Hash). We need to
implement it on each component part, because the components now
generally wrap floats, which do not impl Hash because of NaN. However in
this case we know that we won't have a NaN, so it is safe to manually
impl Hash.
This commit is contained in:
Jon Leighton 2018-02-26 13:41:20 +00:00
parent 15a677c639
commit 691c6c6f1a
81 changed files with 1192 additions and 360 deletions

7
Cargo.lock generated
View file

@ -1051,6 +1051,7 @@ dependencies = [
"style 0.0.1", "style 0.0.1",
"time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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-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)", "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)", "webrender_api 0.57.2 (git+https://github.com/servo/webrender)",
@ -3319,6 +3320,11 @@ name = "typeable"
version = "0.1.2" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "uluru" name = "uluru"
version = "0.2.0" version = "0.2.0"
@ -4071,6 +4077,7 @@ dependencies = [
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" "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 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 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 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 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" "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a"

View file

@ -42,6 +42,7 @@ unicode-bidi = {version = "0.3", features = ["with_serde"]}
unicode-script = {version = "0.2", features = ["harfbuzz"]} unicode-script = {version = "0.2", features = ["harfbuzz"]}
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
xi-unicode = "0.1.0" xi-unicode = "0.1.0"
ucd = "0.1.1"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
byteorder = "1.0" byteorder = "1.0"

View file

@ -9,12 +9,14 @@ use font_template::FontTemplateDescriptor;
use ordered_float::NotNaN; use ordered_float::NotNaN;
use platform::font::{FontHandle, FontTable}; use platform::font::{FontHandle, FontTable};
use platform::font_context::FontContextHandle; use platform::font_context::FontContextHandle;
pub use platform::font_list::fallback_font_families;
use platform::font_template::FontTemplateData; use platform::font_template::FontTemplateData;
use servo_atoms::Atom; use servo_atoms::Atom;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::iter;
use std::rc::Rc; use std::rc::Rc;
use std::str; use std::str;
use std::sync::Arc; 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 /// 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 /// with `FontTemplateDescriptor` in that the latter represents only the parameters inherent in the
/// font data (weight, stretch, etc.). /// font data (weight, stretch, etc.).
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct FontDescriptor { pub struct FontDescriptor {
pub template_descriptor: FontTemplateDescriptor, pub template_descriptor: FontTemplateDescriptor,
pub variant: font_variant_caps::T, pub variant: font_variant_caps::T,
@ -329,6 +331,7 @@ pub type FontRef = Rc<RefCell<Font>>;
pub struct FontGroup { pub struct FontGroup {
descriptor: FontDescriptor, descriptor: FontDescriptor,
families: SmallVec<[FontGroupFamily; 8]>, families: SmallVec<[FontGroupFamily; 8]>,
last_matching_fallback: Option<FontRef>,
} }
impl FontGroup { impl FontGroup {
@ -337,10 +340,14 @@ impl FontGroup {
let families = let families =
style.font_family.0.iter() style.font_family.0.iter()
.map(|family| FontGroupFamily::new(descriptor.clone(), family.clone())) .map(|family| FontGroupFamily::new(descriptor.clone(), &family))
.collect(); .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 /// 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<S>, mut font_context: &mut FontContext<S>,
codepoint: char codepoint: char
) -> Option<FontRef> { ) -> Option<FontRef> {
self.find(&mut font_context, |font| font.borrow().has_glyph_for(codepoint)) let has_glyph = |font: &FontRef| font.borrow().has_glyph_for(codepoint);
.or_else(|| self.first(&mut font_context))
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<S: FontSource>( pub fn first<S: FontSource>(
&mut self, &mut self,
mut font_context: &mut FontContext<S> mut font_context: &mut FontContext<S>
) -> Option<FontRef> { ) -> Option<FontRef> {
self.find(&mut font_context, |_| true) 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 /// Find a font which returns true for `predicate`. This method mutates because we may need to
@ -368,19 +395,42 @@ impl FontGroup {
fn find<S, P>( fn find<S, P>(
&mut self, &mut self,
mut font_context: &mut FontContext<S>, mut font_context: &mut FontContext<S>,
mut predicate: P predicate: P,
) -> Option<FontRef> ) -> Option<FontRef>
where where
S: FontSource, S: FontSource,
P: FnMut(&FontRef) -> bool P: FnMut(&FontRef) -> bool,
{ {
self.families.iter_mut() self.families.iter_mut()
.filter_map(|family| family.font(&mut font_context)) .filter_map(|family| family.font(&mut font_context))
.find(|f| predicate(f)) .find(predicate)
.or_else(|| { }
font_context.fallback_font(&self.descriptor)
.into_iter().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<S, P>(
&mut self,
font_context: &mut FontContext<S>,
codepoint: Option<char>,
predicate: P,
) -> Option<FontRef>
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. /// only if actually needed.
#[derive(Debug)] #[derive(Debug)]
struct FontGroupFamily { struct FontGroupFamily {
descriptor: FontDescriptor, font_descriptor: FontDescriptor,
family: SingleFontFamily, family_descriptor: FontFamilyDescriptor,
loaded: bool, loaded: bool,
font: Option<FontRef>, font: Option<FontRef>,
} }
impl FontGroupFamily { 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 { FontGroupFamily {
descriptor, font_descriptor,
family, family_descriptor,
loaded: false, loaded: false,
font: None, font: None,
} }
@ -410,7 +465,7 @@ impl FontGroupFamily {
/// subsequent calls. /// subsequent calls.
fn font<S: FontSource>(&mut self, font_context: &mut FontContext<S>) -> Option<FontRef> { fn font<S: FontSource>(&mut self, font_context: &mut FontContext<S>) -> Option<FontRef> {
if !self.loaded { 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; 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); TEXT_SHAPING_PERFORMANCE_COUNTER.store(0, Ordering::SeqCst);
value 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 {
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()
}
}

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au; use app_units::Au;
use font::{FontFamilyDescriptor, FontFamilyName, FontSearchScope};
use font_context::FontSource; use font_context::FontSource;
use font_template::{FontTemplate, FontTemplateDescriptor}; use font_template::{FontTemplate, FontTemplateDescriptor};
use fontsan; use fontsan;
@ -13,7 +14,6 @@ use platform::font_context::FontContextHandle;
use platform::font_list::SANS_SERIF_FONT_FAMILY; use platform::font_list::SANS_SERIF_FONT_FAMILY;
use platform::font_list::for_each_available_family; use platform::font_list::for_each_available_family;
use platform::font_list::for_each_variation; 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_list::system_default_family;
use platform::font_template::FontTemplateData; use platform::font_template::FontTemplateData;
use servo_atoms::Atom; use servo_atoms::Atom;
@ -24,7 +24,7 @@ use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use style::font_face::{EffectiveSources, Source}; use style::font_face::{EffectiveSources, Source};
use style::values::computed::font::{SingleFontFamily, FamilyName}; use style::values::computed::font::FamilyName;
use webrender_api; use webrender_api;
/// A list of font templates that make up a given font family. /// A list of font templates that make up a given font family.
@ -103,8 +103,7 @@ impl FontTemplates {
/// Commands that the FontContext sends to the font cache thread. /// Commands that the FontContext sends to the font cache thread.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum Command { pub enum Command {
GetFontTemplate(SingleFontFamily, FontTemplateDescriptor, IpcSender<Reply>), GetFontTemplate(FontTemplateDescriptor, FontFamilyDescriptor, IpcSender<Reply>),
GetLastResortFontTemplate(FontTemplateDescriptor, IpcSender<Reply>),
GetFontInstance(webrender_api::FontKey, Au, IpcSender<webrender_api::FontInstanceKey>), GetFontInstance(webrender_api::FontKey, Au, IpcSender<webrender_api::FontInstanceKey>),
AddWebFont(LowercaseString, EffectiveSources, IpcSender<()>), AddWebFont(LowercaseString, EffectiveSources, IpcSender<()>),
AddDownloadedWebFont(LowercaseString, ServoUrl, Vec<u8>, IpcSender<()>), AddDownloadedWebFont(LowercaseString, ServoUrl, Vec<u8>, IpcSender<()>),
@ -123,7 +122,7 @@ pub enum Reply {
struct FontCache { struct FontCache {
port: IpcReceiver<Command>, port: IpcReceiver<Command>,
channel_to_self: IpcSender<Command>, channel_to_self: IpcSender<Command>,
generic_fonts: HashMap<SingleFontFamily, LowercaseString>, generic_fonts: HashMap<FontFamilyName, LowercaseString>,
local_families: HashMap<LowercaseString, FontTemplates>, local_families: HashMap<LowercaseString, FontTemplates>,
web_families: HashMap<LowercaseString, FontTemplates>, web_families: HashMap<LowercaseString, FontTemplates>,
font_context: FontContextHandle, font_context: FontContextHandle,
@ -133,27 +132,28 @@ struct FontCache {
font_instances: HashMap<(webrender_api::FontKey, Au), webrender_api::FontInstanceKey>, font_instances: HashMap<(webrender_api::FontKey, Au), webrender_api::FontInstanceKey>,
} }
fn populate_generic_fonts() -> HashMap<SingleFontFamily, LowercaseString> { fn populate_generic_fonts() -> HashMap<FontFamilyName, LowercaseString> {
let mut generic_fonts = HashMap::with_capacity(5); 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, "serif", "Times New Roman");
append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("sans-serif")), SANS_SERIF_FONT_FAMILY); append_map(&mut generic_fonts, "sans-serif", SANS_SERIF_FONT_FAMILY);
append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("cursive")), "Apple Chancery"); append_map(&mut generic_fonts, "cursive", "Apple Chancery");
append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("fantasy")), "Papyrus"); append_map(&mut generic_fonts, "fantasy", "Papyrus");
append_map(&mut generic_fonts, SingleFontFamily::Generic(atom!("monospace")), "Menlo"); append_map(&mut generic_fonts, "monospace", "Menlo");
fn append_map(generic_fonts: &mut HashMap<SingleFontFamily, LowercaseString>, fn append_map(
font_family: SingleFontFamily, generic_fonts: &mut HashMap<FontFamilyName, LowercaseString>,
mapped_name: &str) { generic_name: &str,
let family_name = { mapped_name: &str,
let opt_system_default = system_default_family(font_family.name()); ) {
match opt_system_default { let family_name = match system_default_family(generic_name) {
Some(system_default) => LowercaseString::new(&system_default), Some(system_default) => LowercaseString::new(&system_default),
None => LowercaseString::new(mapped_name) 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(); let msg = self.port.recv().unwrap();
match msg { match msg {
Command::GetFontTemplate(family, descriptor, result) => { Command::GetFontTemplate(template_descriptor, family_descriptor, result) => {
let maybe_font_template = self.find_font_template(&family, &descriptor); let maybe_font_template = self.find_font_template(&template_descriptor, &family_descriptor);
let _ = result.send(Reply::GetFontTemplateReply(maybe_font_template)); 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) => { Command::GetFontInstance(font_key, size, result) => {
let webrender_api = &self.webrender_api; let webrender_api = &self.webrender_api;
@ -320,23 +316,28 @@ impl FontCache {
}); });
} }
fn transform_family(&self, family: &SingleFontFamily) -> LowercaseString { fn transform_family(&self, family_name: &FontFamilyName) -> LowercaseString {
match self.generic_fonts.get(family) { match self.generic_fonts.get(family_name) {
None => LowercaseString::new(family.name()), None => LowercaseString::from(family_name),
Some(mapped_family) => (*mapped_family).clone() Some(mapped_family) => (*mapped_family).clone()
} }
} }
fn find_font_in_local_family(&mut self, family_name: &LowercaseString, desc: &FontTemplateDescriptor) fn find_font_in_local_family(
-> Option<Arc<FontTemplateData>> { &mut self,
template_descriptor: &FontTemplateDescriptor,
family_name: &FontFamilyName,
) -> Option<Arc<FontTemplateData>> {
let family_name = self.transform_family(family_name);
// TODO(Issue #188): look up localized font family names if canonical name not found // TODO(Issue #188): look up localized font family names if canonical name not found
// look up canonical name // look up canonical name
if self.local_families.contains_key(family_name) { if self.local_families.contains_key(&family_name) {
debug!("FontList: Found font family with name={}", &**family_name); debug!("FontList: Found font family with name={}", &*family_name);
let s = self.local_families.get_mut(family_name).unwrap(); let s = self.local_families.get_mut(&family_name).unwrap();
if s.templates.is_empty() { if s.templates.is_empty() {
for_each_variation(family_name, |path| { for_each_variation(&family_name, |path| {
s.add_template(Atom::from(&*path), None); 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'. // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.
// if such family exists, try to match style to a font // 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 { } else {
debug!("FontList: Couldn't find font family with name={}", &**family_name); debug!("FontList: Couldn't find font family with name={}", &*family_name);
None None
} }
} }
fn find_font_in_web_family(&mut self, family: &SingleFontFamily, desc: &FontTemplateDescriptor) fn find_font_in_web_family(
-> Option<Arc<FontTemplateData>> { &mut self,
let family_name = LowercaseString::new(family.name()); template_descriptor: &FontTemplateDescriptor,
family_name: &FontFamilyName,
) -> Option<Arc<FontTemplateData>> {
let family_name = LowercaseString::from(family_name);
if self.web_families.contains_key(&family_name) { if self.web_families.contains_key(&family_name) {
let templates = self.web_families.get_mut(&family_name).unwrap(); 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 { } else {
None None
} }
@ -385,32 +389,21 @@ impl FontCache {
} }
} }
fn find_font_template(&mut self, family: &SingleFontFamily, desc: &FontTemplateDescriptor) fn find_font_template(
-> Option<FontTemplateInfo> { &mut self,
let template = self.find_font_in_web_family(family, desc) template_descriptor: &FontTemplateDescriptor,
.or_else(|| { family_descriptor: &FontFamilyDescriptor,
let transformed_family = self.transform_family(family); ) -> Option<FontTemplateInfo> {
self.find_font_in_local_family(&transformed_family, desc) match family_descriptor.scope {
}); FontSearchScope::Any => {
self.find_font_in_web_family(&template_descriptor, &family_descriptor.name)
template.map(|template| { .or_else(|| self.find_font_in_local_family(&template_descriptor, &family_descriptor.name))
self.get_font_template_info(template)
})
} }
fn last_resort_font_template(&mut self, desc: &FontTemplateDescriptor) FontSearchScope::Local => {
-> FontTemplateInfo { self.find_font_in_local_family(&template_descriptor, &family_descriptor.name)
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)
} }
} }.map(|t| self.get_font_template_info(t))
panic!("Unable to find any fonts that match (do you have fallback fonts installed?)");
} }
} }
@ -480,11 +473,14 @@ impl FontSource for FontCacheThread {
instance_key.unwrap() instance_key.unwrap()
} }
fn find_font_template(&mut self, family: SingleFontFamily, desc: FontTemplateDescriptor) fn font_template(
-> Option<FontTemplateInfo> { &mut self,
template_descriptor: FontTemplateDescriptor,
family_descriptor: FontFamilyDescriptor,
) -> Option<FontTemplateInfo> {
let (response_chan, response_port) = let (response_chan, response_port) =
ipc::channel().expect("failed to create IPC channel"); 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"); .expect("failed to send message to font cache thread");
let reply = response_port.recv(); 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)] #[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 { impl Deref for LowercaseString {
type Target = str; type Target = str;

View file

@ -4,14 +4,13 @@
use app_units::Au; use app_units::Au;
use fnv::FnvHasher; 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_cache_thread::FontTemplateInfo;
use font_template::FontTemplateDescriptor; use font_template::FontTemplateDescriptor;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use platform::font::FontHandle; use platform::font::FontHandle;
pub use platform::font_context::FontContextHandle; pub use platform::font_context::FontContextHandle;
use servo_arc::Arc; use servo_arc::Arc;
use servo_atoms::Atom;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::default::Default; use std::default::Default;
@ -20,42 +19,10 @@ use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use style::computed_values::font_variant_caps::T as FontVariantCaps; use style::computed_values::font_variant_caps::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct; use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::SingleFontFamily;
use webrender_api; use webrender_api;
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h) static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
#[derive(Debug)]
struct FontCacheEntry {
family: Atom,
font: Option<FontRef>,
}
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 /// An epoch for the font context cache. The cache is flushed if the current epoch does not match
/// this one. /// this one.
static FONT_CACHE_EPOCH: AtomicUsize = ATOMIC_USIZE_INIT; static FONT_CACHE_EPOCH: AtomicUsize = ATOMIC_USIZE_INIT;
@ -63,13 +30,11 @@ static FONT_CACHE_EPOCH: AtomicUsize = ATOMIC_USIZE_INIT;
pub trait FontSource { pub trait FontSource {
fn get_font_instance(&mut self, key: webrender_api::FontKey, size: Au) -> webrender_api::FontInstanceKey; fn get_font_instance(&mut self, key: webrender_api::FontKey, size: Au) -> webrender_api::FontInstanceKey;
fn find_font_template( fn font_template(
&mut self, &mut self,
family: SingleFontFamily, template_descriptor: FontTemplateDescriptor,
desc: FontTemplateDescriptor family_descriptor: FontFamilyDescriptor,
) -> Option<FontTemplateInfo>; ) -> Option<FontTemplateInfo>;
fn last_resort_font_template(&mut self, desc: FontTemplateDescriptor) -> FontTemplateInfo;
} }
/// The FontContext represents the per-thread/thread state necessary for /// The FontContext represents the per-thread/thread state necessary for
@ -84,10 +49,7 @@ pub struct FontContext<S: FontSource> {
// TODO: The font context holds a strong ref to the cached fonts // 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. // so they will never be released. Find out a good time to drop them.
// See bug https://github.com/servo/servo/issues/3300 // See bug https://github.com/servo/servo/issues/3300
// font_cache: HashMap<FontCacheKey, Option<FontRef>>,
// GWTODO: Check on real pages if this is faster as Vec() or HashMap().
font_cache: Vec<FontCacheEntry>,
fallback_font_cache: Vec<FallbackFontCacheEntry>,
font_group_cache: font_group_cache:
HashMap<FontGroupCacheKey, Rc<RefCell<FontGroup>>, BuildHasherDefault<FnvHasher>>, HashMap<FontGroupCacheKey, Rc<RefCell<FontGroup>>, BuildHasherDefault<FnvHasher>>,
@ -101,33 +63,12 @@ impl<S: FontSource> FontContext<S> {
FontContext { FontContext {
platform_handle: handle, platform_handle: handle,
font_source, font_source,
font_cache: vec!(), font_cache: HashMap::new(),
fallback_font_cache: vec!(),
font_group_cache: HashMap::with_hasher(Default::default()), font_group_cache: HashMap::with_hasher(Default::default()),
epoch: 0, 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<Font, ()> {
// 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) { fn expire_font_caches_if_necessary(&mut self) {
let current_epoch = FONT_CACHE_EPOCH.load(Ordering::SeqCst); let current_epoch = FONT_CACHE_EPOCH.load(Ordering::SeqCst);
if current_epoch == self.epoch { if current_epoch == self.epoch {
@ -135,7 +76,6 @@ impl<S: FontSource> FontContext<S> {
} }
self.font_cache.clear(); self.font_cache.clear();
self.fallback_font_cache.clear();
self.font_group_cache.clear(); self.font_group_cache.clear();
self.epoch = current_epoch self.epoch = current_epoch
} }
@ -160,76 +100,61 @@ impl<S: FontSource> FontContext<S> {
font_group font_group
} }
/// Returns a reference to an existing font cache entry matching `descriptor` and `family`, if /// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a
/// there is one. /// reference to the same underlying `Font`.
fn font_cache_entry(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> Option<&FontCacheEntry> { pub fn font(
self.font_cache.iter() &mut self,
.find(|cache_entry| cache_entry.matches(&descriptor, &family)) font_descriptor: &FontDescriptor,
} family_descriptor: &FontFamilyDescriptor,
) -> Option<FontRef> {
/// Creates a new font cache entry matching `descriptor` and `family`. let cache_key = FontCacheKey {
fn create_font_cache_entry(&mut self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> FontCacheEntry { font_descriptor: font_descriptor.clone(),
let font = family_descriptor: family_descriptor.clone(),
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<FontRef> {
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<FallbackFontCacheEntry> {
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<FontRef> {
if let Some(cached_entry) = self.fallback_font_cache_entry(descriptor) {
return Some(cached_entry.font.clone())
}; };
if let Some(entry) = self.create_fallback_font_cache_entry(descriptor) { self.font_cache.get(&cache_key).map(|v| v.clone()).unwrap_or_else(|| {
let font = entry.font.clone(); debug!(
self.fallback_font_cache.push(entry); "FontContext::font cache miss for font_descriptor={:?} family_descriptor={:?}",
Some(font) font_descriptor,
} else { family_descriptor
None );
let font =
self.font_source.font_template(
font_descriptor.template_descriptor.clone(),
family_descriptor.clone(),
)
.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
})
} }
/// 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<Font, ()> {
// 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 +165,12 @@ impl<S: FontSource> MallocSizeOf for FontContext<S> {
} }
} }
#[derive(Debug, Eq, Hash, PartialEq)]
struct FontCacheKey {
font_descriptor: FontDescriptor,
family_descriptor: FontFamilyDescriptor,
}
#[derive(Debug)] #[derive(Debug)]
struct FontGroupCacheKey { struct FontGroupCacheKey {
style: Arc<FontStyleStruct>, style: Arc<FontStyleStruct>,

View file

@ -19,13 +19,18 @@ use style::values::computed::font::FontWeight;
/// to be expanded or refactored when we support more of the font styling parameters. /// 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()`. /// 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 struct FontTemplateDescriptor {
pub weight: FontWeight, pub weight: FontWeight,
pub stretch: FontStretch, pub stretch: FontStretch,
pub style: FontStyle, 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 { fn style_to_number(s: &FontStyle) -> f32 {
use style::values::generics::font::FontStyle as GenericFontStyle; use style::values::generics::font::FontStyle as GenericFontStyle;
@ -66,7 +71,7 @@ impl FontTemplateDescriptor {
// 0 <= weightPart <= 800 // 0 <= weightPart <= 800
let weight_part = (self.weight.0 - other.weight.0).abs(); let weight_part = (self.weight.0 - other.weight.0).abs();
// 0 <= stretchPart <= 8 // 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 style_part + weight_part + stretch_part
} }
} }

View file

@ -45,14 +45,15 @@ extern crate ordered_float;
extern crate range; extern crate range;
#[macro_use] extern crate serde; #[macro_use] extern crate serde;
extern crate servo_arc; extern crate servo_arc;
extern crate servo_url;
#[macro_use] extern crate servo_atoms; #[macro_use] extern crate servo_atoms;
extern crate servo_url;
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
#[cfg(any(target_feature = "sse2", target_feature = "neon"))] #[cfg(any(target_feature = "sse2", target_feature = "neon"))]
extern crate simd; extern crate simd;
extern crate smallvec; extern crate smallvec;
extern crate style; extern crate style;
extern crate time; extern crate time;
extern crate ucd;
extern crate unicode_bidi; extern crate unicode_bidi;
extern crate unicode_script; extern crate unicode_script;
extern crate webrender_api; extern crate webrender_api;

View file

@ -6,6 +6,8 @@ use std::cell::RefCell;
use std::fs::File; use std::fs::File;
use std::io::{self, Read}; use std::io::{self, Read};
use std::path::Path; use std::path::Path;
use text::util::is_cjk;
use ucd::{Codepoint, UnicodeBlock};
use xml5ever::Attribute; use xml5ever::Attribute;
use xml5ever::driver::parse_document; use xml5ever::driver::parse_document;
use xml5ever::rcdom::*; use xml5ever::rcdom::*;
@ -470,12 +472,61 @@ pub fn system_default_family(generic_name: &str) -> Option<String> {
} }
} }
pub fn last_resort_font_families() -> Vec<String> { // Based on gfxAndroidPlatform::GetCommonFallbackFonts() in Gecko
vec!( pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> {
"sans-serif".to_owned(), let mut families = vec!();
"Droid Sans".to_owned(),
"serif".to_owned(), 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"; pub static SANS_SERIF_FONT_FAMILY: &'static str = "sans-serif";

View file

@ -187,7 +187,7 @@ impl FontHandleMethods for FontHandle {
} else { } else {
FontStretchKeyword::Normal FontStretchKeyword::Normal
}.compute(); }.compute();
NonNegative(percentage) FontStretch(NonNegative(percentage))
} }
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {

View file

@ -10,10 +10,10 @@ use fontconfig::fontconfig::{FcFontSetList, FcObjectSetCreate, FcObjectSetDestro
use fontconfig::fontconfig::{FcObjectSetAdd, FcPatternGetInteger}; use fontconfig::fontconfig::{FcObjectSetAdd, FcPatternGetInteger};
use libc; use libc;
use libc::{c_char, c_int}; use libc::{c_char, c_int};
use std::borrow::ToOwned;
use std::ffi::CString; use std::ffi::CString;
use std::ptr; use std::ptr;
use super::c_str_to_string; use super::c_str_to_string;
use text::util::is_cjk;
static FC_FAMILY: &'static [u8] = b"family\0"; static FC_FAMILY: &'static [u8] = b"family\0";
static FC_FILE: &'static [u8] = b"file\0"; static FC_FILE: &'static [u8] = b"file\0";
@ -132,12 +132,25 @@ pub fn system_default_family(generic_name: &str) -> Option<String> {
} }
} }
pub fn last_resort_font_families() -> Vec<String> { pub static SANS_SERIF_FONT_FAMILY: &'static str = "DejaVu Sans";
vec!(
"Fira Sans".to_owned(), // Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko
"DejaVu Sans".to_owned(), pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> {
"Arial".to_owned() 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");
}
} }
pub static SANS_SERIF_FONT_FAMILY: &'static str = "DejaVu Sans"; families
}

View file

@ -229,7 +229,7 @@ impl FontHandleMethods for FontHandle {
use style::values::generics::NonNegative; use style::values::generics::NonNegative;
let normalized = self.ctfont.all_traits().normalized_width(); // [-1.0, 1.0] 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<GlyphId> { fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {

View file

@ -3,7 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use core_text; use core_text;
use std::borrow::ToOwned; use text::util::unicode_plane;
use ucd::{Codepoint, UnicodeBlock};
pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String) { pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String) {
let family_names = core_text::font_collection::get_family_names(); let family_names = core_text::font_collection::get_family_names();
@ -28,8 +29,162 @@ pub fn system_default_family(_generic_name: &str) -> Option<String> {
None None
} }
pub fn last_resort_font_families() -> Vec<String> { // Based on gfxPlatformMac::GetCommonFallbackFonts() in Gecko
vec!("Arial Unicode MS".to_owned(), "Arial".to_owned()) pub fn fallback_font_families(codepoint: Option<char>) -> 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"; pub static SANS_SERIF_FONT_FAMILY: &'static str = "Helvetica";

View file

@ -163,7 +163,7 @@ impl FontInfo {
let weight = StyleFontWeight(weight_val as f32); 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, 1 => FontStretchKeyword::UltraCondensed,
2 => FontStretchKeyword::ExtraCondensed, 2 => FontStretchKeyword::ExtraCondensed,
3 => FontStretchKeyword::Condensed, 3 => FontStretchKeyword::Condensed,
@ -174,7 +174,7 @@ impl FontInfo {
8 => FontStretchKeyword::ExtraExpanded, 8 => FontStretchKeyword::ExtraExpanded,
9 => FontStretchKeyword::UltraExpanded, 9 => FontStretchKeyword::UltraExpanded,
_ => return Err(()), _ => return Err(()),
}.compute()); }.compute()));
let style = if italic_bool { let style = if italic_bool {
GenericFontStyle::Italic GenericFontStyle::Italic
@ -212,7 +212,7 @@ impl FontInfo {
// slightly blacker black // slightly blacker black
FontWeight::ExtraBlack => 1000., FontWeight::ExtraBlack => 1000.,
}); });
let stretch = NonNegative(match font.stretch() { let stretch = StyleFontStretch(NonNegative(match font.stretch() {
FontStretch::Undefined => FontStretchKeyword::Normal, FontStretch::Undefined => FontStretchKeyword::Normal,
FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed, FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed,
FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed, FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed,
@ -223,7 +223,7 @@ impl FontInfo {
FontStretch::Expanded => FontStretchKeyword::Expanded, FontStretch::Expanded => FontStretchKeyword::Expanded,
FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded, FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded,
FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded, FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded,
}.compute()); }.compute()));
Ok(FontInfo { Ok(FontInfo {
family_name: font.family_name(), family_name: font.family_name(),

View file

@ -7,6 +7,8 @@ use servo_atoms::Atom;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;
use std::sync::atomic::{Ordering, AtomicUsize}; use std::sync::atomic::{Ordering, AtomicUsize};
use text::util::unicode_plane;
use ucd::{Codepoint, UnicodeBlock};
lazy_static! { lazy_static! {
static ref FONT_ATOM_COUNTER: AtomicUsize = AtomicUsize::new(1); static ref FONT_ATOM_COUNTER: AtomicUsize = AtomicUsize::new(1);
@ -19,10 +21,6 @@ pub fn system_default_family(_: &str) -> Option<String> {
Some("Verdana".to_owned()) Some("Verdana".to_owned())
} }
pub fn last_resort_font_families() -> Vec<String> {
vec!("Arial".to_owned())
}
pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String) { pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String) {
let system_fc = FontCollection::system(); let system_fc = FontCollection::system();
for family in system_fc.families_iter() { 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(); let fonts = FONT_ATOM_MAP.lock().unwrap();
FontCollection::system().get_font_from_descriptor(fonts.get(ident).unwrap()).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<char>) -> 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
}

View file

@ -10,7 +10,7 @@ extern crate style;
extern crate webrender_api; extern crate webrender_api;
use app_units::Au; use app_units::Au;
use gfx::font::FontHandleMethods; use gfx::font::{fallback_font_families, FontFamilyDescriptor, FontHandleMethods};
use gfx::font_cache_thread::{FontTemplates, FontTemplateInfo}; use gfx::font_cache_thread::{FontTemplates, FontTemplateInfo};
use gfx::font_context::{FontContext, FontContextHandle, FontSource}; use gfx::font_context::{FontContext, FontContextHandle, FontSource};
use gfx::font_template::FontTemplateDescriptor; use gfx::font_template::FontTemplateDescriptor;
@ -24,29 +24,31 @@ use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct; 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::{FamilyName, FamilyNameSyntax, FontFamily, FontFamilyList, FontSize};
use style::values::computed::font::{FontWeight, SingleFontFamily}; use style::values::computed::font::{FontStretch, FontWeight, SingleFontFamily};
use style::values::generics::NonNegative;
use style::values::generics::font::FontStyle; use style::values::generics::font::FontStyle;
struct TestFontSource { struct TestFontSource {
handle: FontContextHandle, handle: FontContextHandle,
families: HashMap<Atom, FontTemplates>, families: HashMap<String, FontTemplates>,
find_font_count: Rc<Cell<isize>>, find_font_count: Rc<Cell<isize>>,
} }
impl TestFontSource { impl TestFontSource {
fn new() -> TestFontSource { fn new() -> TestFontSource {
let mut csstest_ascii = FontTemplates::new(); 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(); 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(); let mut families = HashMap::new();
families.insert(Atom::from("CSSTest ASCII"), csstest_ascii); families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
families.insert(Atom::from("CSSTest Basic"), csstest_basic); families.insert("CSSTest Basic".to_owned(), csstest_basic);
families.insert(fallback_font_families(None)[0].to_owned(), fallback);
TestFontSource { TestFontSource {
handle: FontContextHandle::new(), 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 = [ let mut path: PathBuf = [
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"tests", "tests",
@ -65,9 +67,10 @@ impl TestFontSource {
path.push(format!("{}.ttf", name)); path.push(format!("{}.ttf", name));
let file = File::open(path).unwrap(); let file = File::open(path).unwrap();
let identifier = Atom::from(identifier.unwrap_or(name));
family.add_template( family.add_template(
Atom::from(name), identifier,
Some(file.bytes().map(|b| b.unwrap()).collect()) Some(file.bytes().map(|b| b.unwrap()).collect())
) )
} }
@ -78,17 +81,17 @@ impl FontSource for TestFontSource {
webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0) webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0)
} }
fn find_font_template( fn font_template(
&mut self, &mut self,
family: SingleFontFamily, template_descriptor: FontTemplateDescriptor,
desc: FontTemplateDescriptor family_descriptor: FontFamilyDescriptor,
) -> Option<FontTemplateInfo> { ) -> Option<FontTemplateInfo> {
let handle = &self.handle; let handle = &self.handle;
self.find_font_count.set(self.find_font_count.get() + 1); self.find_font_count.set(self.find_font_count.get() + 1);
self.families self.families
.get_mut(family.atom()) .get_mut(family_descriptor.name())
.and_then(|family| family.find_font_for_style(&desc, handle)) .and_then(|family| family.find_font_for_style(&template_descriptor, handle))
.map(|template| { .map(|template| {
FontTemplateInfo { FontTemplateInfo {
font_template: template, 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 { fn style() -> FontStyleStruct {
@ -109,7 +108,7 @@ fn style() -> FontStyleStruct {
font_variant_caps: FontVariantCaps::Normal, font_variant_caps: FontVariantCaps::Normal,
font_weight: FontWeight::normal(), font_weight: FontWeight::normal(),
font_size: FontSize::medium(), font_size: FontSize::medium(),
font_stretch: NonNegative(Percentage(1.)), font_stretch: FontStretch::hundred(),
hash: 0, hash: 0,
}; };
style.compute_font_hash(); style.compute_font_hash();
@ -162,14 +161,37 @@ fn test_font_group_find_by_codepoint() {
let group = context.font_group(Arc::new(style)); let group = context.font_group(Arc::new(style));
let font = group.borrow_mut().find_by_codepoint(&mut context, 'a').unwrap(); 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"); 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(); 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"); 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(); 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"); 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"
);
}

View file

@ -17,7 +17,7 @@ fn test_font_template_descriptor() {
use std::io::prelude::*; use std::io::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use style::values::computed::Percentage; 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::NonNegative;
use style::values::generics::font::FontStyle; use style::values::generics::font::FontStyle;
@ -45,25 +45,25 @@ fn test_font_template_descriptor() {
assert_eq!(descriptor("DejaVuSans"), FontTemplateDescriptor { assert_eq!(descriptor("DejaVuSans"), FontTemplateDescriptor {
weight: FontWeight::normal(), weight: FontWeight::normal(),
stretch: NonNegative(Percentage(1.)), stretch: FontStretch::hundred(),
style: FontStyle::Normal, style: FontStyle::Normal,
}); });
assert_eq!(descriptor("DejaVuSans-Bold"), FontTemplateDescriptor { assert_eq!(descriptor("DejaVuSans-Bold"), FontTemplateDescriptor {
weight: FontWeight::bold(), weight: FontWeight::bold(),
stretch: NonNegative(Percentage(1.)), stretch: FontStretch::hundred(),
style: FontStyle::Normal, style: FontStyle::Normal,
}); });
assert_eq!(descriptor("DejaVuSans-Oblique"), FontTemplateDescriptor { assert_eq!(descriptor("DejaVuSans-Oblique"), FontTemplateDescriptor {
weight: FontWeight::normal(), weight: FontWeight::normal(),
stretch: NonNegative(Percentage(1.)), stretch: FontStretch::hundred(),
style: FontStyle::Italic, style: FontStyle::Italic,
}); });
assert_eq!(descriptor("DejaVuSansCondensed-BoldOblique"), FontTemplateDescriptor { assert_eq!(descriptor("DejaVuSansCondensed-BoldOblique"), FontTemplateDescriptor {
weight: FontWeight::bold(), weight: FontWeight::bold(),
stretch: NonNegative(Percentage(0.875)), stretch: FontStretch(NonNegative(Percentage(0.875))),
style: FontStyle::Italic, style: FontStyle::Italic,
}); });
} }

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use ucd::{Codepoint, UnicodeBlock};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompressionMode { pub enum CompressionMode {
CompressNone, CompressNone,
@ -114,3 +116,42 @@ pub fn is_bidi_control(c: char) -> bool {
_ => false _ => 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
}

View file

@ -84,7 +84,7 @@ ${helpers.predefined_type("font-synthesis",
${helpers.predefined_type( ${helpers.predefined_type(
"font-stretch", "font-stretch",
"FontStretch", "FontStretch",
initial_value="computed::NonNegativePercentage::hundred()", initial_value="computed::FontStretch::hundred()",
initial_specified_value="specified::FontStretch::normal()", initial_specified_value="specified::FontStretch::normal()",
animation_value_type="Percentage", animation_value_type="Percentage",
flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",

View file

@ -2305,13 +2305,10 @@ pub mod style_structs {
pub fn compute_font_hash(&mut self) { pub fn compute_font_hash(&mut self) {
// Corresponds to the fields in // Corresponds to the fields in
// `gfx::font_template::FontTemplateDescriptor`. // `gfx::font_template::FontTemplateDescriptor`.
//
// FIXME(emilio): Where's font-style?
let mut hasher: FnvHasher = Default::default(); let mut hasher: FnvHasher = Default::default();
// We hash the floating point number with four decimal self.font_weight.hash(&mut hasher);
// places. self.font_stretch.hash(&mut hasher);
hasher.write_u64((self.font_weight.0 * 10000.).trunc() as u64); self.font_style.hash(&mut hasher);
hasher.write_u64(((self.font_stretch.0).0 * 10000.).trunc() as u64);
self.font_family.hash(&mut hasher); self.font_family.hash(&mut hasher);
self.hash = hasher.finish() self.hash = hasher.finish()
} }

View file

@ -15,21 +15,20 @@ use gecko_bindings::sugar::refptr::RefPtr;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use std::fmt::{self, Write}; use std::fmt::{self, Write};
#[cfg(feature = "gecko")]
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use std::slice; use std::slice;
use style_traits::{CssWriter, ParseError, ToCss}; use style_traits::{CssWriter, ParseError, ToCss};
use values::CSSFloat; use values::CSSFloat;
use values::animated::{ToAnimatedValue, ToAnimatedZero}; 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::generics::font::{self as generics, FeatureTagValue, FontSettings, VariationValue};
use values::specified::font::{self as specified, MIN_FONT_WEIGHT, MAX_FONT_WEIGHT}; use values::specified::font::{self as specified, MIN_FONT_WEIGHT, MAX_FONT_WEIGHT};
use values::specified::length::{FontBaseSize, NoCalcLength}; use values::specified::length::{FontBaseSize, NoCalcLength};
pub use values::computed::Length as MozScriptMinSize; pub use values::computed::Length as MozScriptMinSize;
pub use values::specified::font::{FontSynthesis, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use values::specified::font::{FontSynthesis, MozScriptSizeMultiplier, XLang, XTextZoom};
pub use values::computed::NonNegativePercentage as FontStretch;
/// A value for the font-weight property per: /// 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))] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
pub struct FontWeight(pub Number); pub struct FontWeight(pub Number);
impl Hash for FontWeight {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_u64((self.0 * 10000.).trunc() as u64);
}
}
impl ToAnimatedValue for FontWeight { impl ToAnimatedValue for FontWeight {
type AnimatedValue = Number; type AnimatedValue = Number;
@ -853,6 +858,12 @@ impl ToAnimatedValue for FontStyleAngle {
} }
} }
impl Hash for FontStyleAngle {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_u64((self.0.degrees() * 10000.).trunc() as u64);
}
}
/// The computed value of `font-style`. /// The computed value of `font-style`.
/// ///
/// FIXME(emilio): Angle should be a custom type to handle clamping during /// 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<H: Hasher>(&self, hasher: &mut H) {
hasher.write_u64((self.value() * 10000.).trunc() as u64);
}
}

View file

@ -228,7 +228,7 @@ impl Default for KeywordSize {
/// https://drafts.csswg.org/css-fonts-4/#font-style-prop /// https://drafts.csswg.org/css-fonts-4/#font-style-prop
#[allow(missing_docs)] #[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[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)] PartialEq, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero)]
pub enum FontStyle<Angle> { pub enum FontStyle<Angle> {
#[animation(error)] #[animation(error)]

View file

@ -156,7 +156,7 @@ impl SpecifiedValueInfo for CounterStyleOrNone {
/// A wrapper of Non-negative values. /// A wrapper of Non-negative values.
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[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, PartialEq, PartialOrd, SpecifiedValueInfo, ToAnimatedZero,
ToComputedValue, ToCss)] ToComputedValue, ToCss)]
pub struct NonNegative<T>(pub T); pub struct NonNegative<T>(pub T);

View file

@ -490,22 +490,22 @@ impl Parse for FontStretch {
} }
impl ToComputedValue for FontStretch { impl ToComputedValue for FontStretch {
type ComputedValue = NonNegative<ComputedPercentage>; type ComputedValue = computed::FontStretch;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self { match *self {
FontStretch::Stretch(ref percentage) => { FontStretch::Stretch(ref percentage) => {
NonNegative(percentage.to_computed_value(context)) computed::FontStretch(NonNegative(percentage.to_computed_value(context)))
}, },
FontStretch::Keyword(ref kw) => { FontStretch::Keyword(ref kw) => {
NonNegative(kw.compute()) computed::FontStretch(NonNegative(kw.compute()))
}, },
FontStretch::System(_) => self.compute_system(context), FontStretch::System(_) => self.compute_system(context),
} }
} }
fn from_computed_value(computed: &Self::ComputedValue) -> Self { 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))
} }
} }

View file

@ -1,3 +0,0 @@
[font-family-invalid-characters-001.xht]
type: reftest
expected: FAIL

View file

@ -1,3 +0,0 @@
[font-family-invalid-characters-003.xht]
type: reftest
expected: FAIL

View file

@ -1,3 +0,0 @@
[font-family-rule-001.xht]
type: reftest
expected: FAIL

View file

@ -0,0 +1,5 @@
[css3-text-line-break-baspglwj-015.html]
type: testharness
[ ]
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,5 @@
[css3-text-line-break-baspglwj-033.html]
type: testharness
[ ]
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,5 @@
[css3-text-line-break-baspglwj-034.html]
type: testharness
[ ]
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,5 @@
[css3-text-line-break-baspglwj-035.html]
type: testharness
[ ]
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,5 @@
[css3-text-line-break-baspglwj-037.html]
type: testharness
[ ]
expected:
if os == "mac": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-006.html] [css3-text-line-break-opclns-006.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-014.html] [css3-text-line-break-opclns-014.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-015.html] [css3-text-line-break-opclns-015.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-016.html] [css3-text-line-break-opclns-016.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-018.html] [css3-text-line-break-opclns-018.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-019.html] [css3-text-line-break-opclns-019.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-020.html] [css3-text-line-break-opclns-020.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-023.html] [css3-text-line-break-opclns-023.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-025.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-026.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-027.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-028.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-029.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-030.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-031.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-032.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-033.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-034.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-035.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-036.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-037.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-119.html] [css3-text-line-break-opclns-119.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-120.html] [css3-text-line-break-opclns-120.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-122.html] [css3-text-line-break-opclns-122.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-123.html] [css3-text-line-break-opclns-123.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-124.html] [css3-text-line-break-opclns-124.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-125.html] [css3-text-line-break-opclns-125.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,4 +1,3 @@
[css3-text-line-break-opclns-128.html] [css3-text-line-break-opclns-128.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-130.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-131.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-132.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-133.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-134.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-135.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-136.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-137.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-138.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-139.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-140.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-141.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -0,0 +1,4 @@
[css3-text-line-break-opclns-142.html]
type: reftest
expected:
if os == "mac": FAIL

View file

@ -1,4 +1,3 @@
[word-break-break-all-007.html] [word-break-break-all-007.html]
type: reftest type: reftest
expected: expected: FAIL
if os == "linux": FAIL

View file

@ -1,3 +1,4 @@
[word-break-normal-bo-000.html] [word-break-normal-bo-000.html]
type: reftest type: reftest
expected: FAIL expected:
if os == "linux": FAIL

View file

@ -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": [ "css/font_size.html": [
[ [
"/_mozilla/css/font_size.html", "/_mozilla/css/font_size.html",
@ -8248,6 +8284,11 @@
{} {}
] ]
], ],
"css/font_fallback_failed_ref.html": [
[
{}
]
],
"css/font_size_ref.html": [ "css/font_size_ref.html": [
[ [
{} {}
@ -61500,6 +61541,22 @@
"0c8f70a37e6d56370576c7f5ba7f1df359db8f9c", "0c8f70a37e6d56370576c7f5ba7f1df359db8f9c",
"support" "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": [ "css/font_size.html": [
"7c944acb4f7d909083888a355e6d664c23081b99", "7c944acb4f7d909083888a355e6d664c23081b99",
"reftest" "reftest"

View file

@ -0,0 +1,4 @@
[font_fallback_01.html]
type: reftest
expected:
if os == "linux": FAIL

View file

@ -0,0 +1,4 @@
[font_fallback_02.html]
type: reftest
expected:
if os == "linux": FAIL

View file

@ -0,0 +1,4 @@
<!doctype html>
<link rel="mismatch" href="font_fallback_failed_ref.html">
<meta name="assert" content="A fallback font must be found for a hiragana character">

View file

@ -0,0 +1,4 @@
<!doctype html>
<link rel="mismatch" href="font_fallback_failed_ref.html">
<meta name="assert" content="A fallback font must be found for a katakana character">

View file

@ -0,0 +1,4 @@
<!doctype html>
<link rel="mismatch" href="font_fallback_failed_ref.html">
<meta name="assert" content="A fallback font must be found for an emoji with text presentation">

View file

@ -0,0 +1,3 @@
<!doctype html>
<!-- This codepoint is in the Private Use Area, therefore we would expect it to render as a missing glyph. -->
&#xE000;