Auto merge of #15356 - servo:font-face-descriptors, r=Manishearth

Add support for more @font-face descriptors

<!-- Please describe your changes on the following line: -->

Part of https://bugzilla.mozilla.org/show_bug.cgi?id=1290237. I’ll add conversions to `nsCSSValue` separately because that requires new C++ functions in the stylo repository.

r? @bholley

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #__ (github issue number if applicable).

<!-- Either: -->
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because _____

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15356)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-02-03 05:09:30 -08:00 committed by GitHub
commit 48f3cc8325
10 changed files with 370 additions and 162 deletions

View file

@ -25,7 +25,7 @@ use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use std::u32; use std::u32;
use style::font_face::{EffectiveSources, Source}; use style::font_face::{EffectiveSources, Source};
use style::properties::longhands::font_family::computed_value::FontFamily; use style::properties::longhands::font_family::computed_value::{FontFamily, FamilyName};
use webrender_traits; use webrender_traits;
/// A list of font templates that make up a given font family. /// A list of font templates that make up a given font family.
@ -269,7 +269,7 @@ impl FontCache {
}); });
} }
Source::Local(ref font) => { Source::Local(ref font) => {
let font_face_name = LowercaseString::new(font.name()); let font_face_name = LowercaseString::new(&font.0);
let templates = &mut self.web_families.get_mut(&family_name).unwrap(); let templates = &mut self.web_families.get_mut(&family_name).unwrap();
let mut found = false; let mut found = false;
for_each_variation(&font_face_name, |path| { for_each_variation(&font_face_name, |path| {
@ -461,8 +461,8 @@ impl FontCacheThread {
} }
} }
pub fn add_web_font(&self, family: FontFamily, sources: EffectiveSources, sender: IpcSender<()>) { pub fn add_web_font(&self, family: FamilyName, sources: EffectiveSources, sender: IpcSender<()>) {
self.chan.send(Command::AddWebFont(LowercaseString::new(family.name()), sources, sender)).unwrap(); self.chan.send(Command::AddWebFont(LowercaseString::new(&family.0), sources, sender)).unwrap();
} }
pub fn exit(&self) { pub fn exit(&self) {

View file

@ -8,13 +8,14 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use computed_values::font_family::FontFamily; #[cfg(feature = "gecko")]
use computed_values::{font_style, font_weight, font_stretch};
use computed_values::font_family::FamilyName;
use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser}; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
use parser::{ParserContext, log_css_error, Parse}; use parser::{ParserContext, log_css_error, Parse};
use properties::longhands::font_family::parse_one_family;
use std::fmt; use std::fmt;
use std::iter; use std::iter;
use style_traits::ToCss; use style_traits::{ToCss, OneOrMoreCommaSeparated};
use values::specified::url::SpecifiedUrl; use values::specified::url::SpecifiedUrl;
/// A source for a font-face rule. /// A source for a font-face rule.
@ -24,7 +25,7 @@ pub enum Source {
/// A `url()` source. /// A `url()` source.
Url(UrlSource), Url(UrlSource),
/// A `local()` source. /// A `local()` source.
Local(FontFamily), Local(FamilyName),
} }
impl ToCss for Source { impl ToCss for Source {
@ -45,6 +46,8 @@ impl ToCss for Source {
} }
} }
impl OneOrMoreCommaSeparated for Source {}
/// A `UrlSource` represents a font-face source that has been specified with a /// A `UrlSource` represents a font-face source that has been specified with a
/// `url()` function. /// `url()` function.
/// ///
@ -66,76 +69,32 @@ impl ToCss for UrlSource {
} }
} }
/// A `@font-face` rule.
///
/// https://drafts.csswg.org/css-fonts/#font-face-rule
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct FontFaceRule {
/// The font family specified with the `font-family` property declaration.
pub family: FontFamily,
/// The list of sources specified with the different `src` property
/// declarations.
pub sources: Vec<Source>,
}
impl ToCss for FontFaceRule {
// Serialization of FontFaceRule is not specced.
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
try!(dest.write_str("@font-face { font-family: "));
try!(self.family.to_css(dest));
try!(dest.write_str(";"));
if self.sources.len() > 0 {
try!(dest.write_str(" src: "));
let mut iter = self.sources.iter();
try!(iter.next().unwrap().to_css(dest));
for source in iter {
try!(dest.write_str(", "));
try!(source.to_css(dest));
}
try!(dest.write_str(";"));
}
dest.write_str(" }")
}
}
/// Parse the block inside a `@font-face` rule. /// Parse the block inside a `@font-face` rule.
/// ///
/// Note that the prelude parsing code lives in the `stylesheets` module. /// Note that the prelude parsing code lives in the `stylesheets` module.
pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser) pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser)
-> Result<FontFaceRule, ()> { -> Result<FontFaceRule, ()> {
let mut family = None; let mut rule = FontFaceRule::initial();
let mut src = None; {
let mut iter = DeclarationListParser::new(input, FontFaceRuleParser { context: context }); let parser = FontFaceRuleParser {
while let Some(declaration) = iter.next() { context: context,
match declaration { rule: &mut rule,
Err(range) => { missing: MissingDescriptors::new(),
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
if let Err(range) = declaration {
let pos = range.start; let pos = range.start;
let message = format!("Unsupported @font-face descriptor declaration: '{}'", let message = format!("Unsupported @font-face descriptor declaration: '{}'",
iter.input.slice(range)); iter.input.slice(range));
log_css_error(iter.input, pos, &*message, context); log_css_error(iter.input, pos, &*message, context);
} }
Ok(FontFaceDescriptorDeclaration::Family(value)) => { }
family = Some(value); if iter.parser.missing.any() {
} return Err(())
Ok(FontFaceDescriptorDeclaration::Src(value)) => {
src = Some(value);
}
} }
} }
match (family, src) { Ok(rule)
(Some(family), Some(src)) => {
Ok(FontFaceRule {
family: family,
sources: src,
})
}
_ => Err(())
}
} }
/// A list of effective sources that we send over through IPC to the font cache. /// A list of effective sources that we send over through IPC to the font cache.
@ -171,63 +130,237 @@ impl iter::Iterator for EffectiveSources {
} }
} }
enum FontFaceDescriptorDeclaration {
Family(FontFamily),
Src(Vec<Source>),
}
struct FontFaceRuleParser<'a, 'b: 'a> { struct FontFaceRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>, context: &'a ParserContext<'b>,
rule: &'a mut FontFaceRule,
missing: MissingDescriptors,
} }
/// Default methods reject all at rules. /// Default methods reject all at rules.
impl<'a, 'b> AtRuleParser for FontFaceRuleParser<'a, 'b> { impl<'a, 'b> AtRuleParser for FontFaceRuleParser<'a, 'b> {
type Prelude = (); type Prelude = ();
type AtRule = FontFaceDescriptorDeclaration; type AtRule = ();
} }
impl Parse for Source {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Source, ()> {
if input.try(|input| input.expect_function_matching("local")).is_ok() {
return input.parse_nested_block(|input| {
FamilyName::parse(context, input)
}).map(Source::Local)
}
impl<'a, 'b> DeclarationParser for FontFaceRuleParser<'a, 'b> { let url = SpecifiedUrl::parse(context, input)?;
type Declaration = FontFaceDescriptorDeclaration;
fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<FontFaceDescriptorDeclaration, ()> { // Parsing optional format()
match_ignore_ascii_case! { name, let format_hints = if input.try(|input| input.expect_function_matching("format")).is_ok() {
"font-family" => { input.parse_nested_block(|input| {
Ok(FontFaceDescriptorDeclaration::Family(try!( input.parse_comma_separated(|input| {
parse_one_family(input)))) Ok(input.expect_string()?.into_owned())
}, })
"src" => { })?
Ok(FontFaceDescriptorDeclaration::Src(try!(input.parse_comma_separated(|input| { } else {
parse_one_src(self.context, input) vec![]
})))) };
},
_ => Err(()) Ok(Source::Url(UrlSource {
url: url,
format_hints: format_hints,
}))
}
}
macro_rules! font_face_descriptors {
(
mandatory descriptors = [
$( #[$m_doc: meta] $m_name: tt $m_ident: ident: $m_ty: ty = $m_initial: expr, )*
]
optional descriptors = [
$( #[$o_doc: meta] $o_name: tt $o_ident: ident: $o_ty: ty = $o_initial: expr, )*
]
) => {
/// A `@font-face` rule.
///
/// https://drafts.csswg.org/css-fonts/#font-face-rule
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct FontFaceRule {
$(
#[$m_doc]
pub $m_ident: $m_ty,
)*
$(
#[$o_doc]
pub $o_ident: $o_ty,
)*
}
struct MissingDescriptors {
$(
$m_ident: bool,
)*
}
impl MissingDescriptors {
fn new() -> Self {
MissingDescriptors {
$(
$m_ident: true,
)*
}
}
fn any(&self) -> bool {
$(
self.$m_ident
)||*
}
}
impl FontFaceRule {
fn initial() -> Self {
FontFaceRule {
$(
$m_ident: $m_initial,
)*
$(
$o_ident: $o_initial,
)*
}
}
}
impl ToCss for FontFaceRule {
// Serialization of FontFaceRule is not specced.
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
dest.write_str("@font-face {\n")?;
$(
dest.write_str(concat!(" ", $m_name, ": "))?;
self.$m_ident.to_css(dest)?;
dest.write_str(";\n")?;
)*
$(
// Because of parse_font_face_block,
// this condition is always true for "src" and "font-family".
// But it can be false for other descriptors.
if self.$o_ident != $o_initial {
dest.write_str(concat!(" ", $o_name, ": "))?;
self.$o_ident.to_css(dest)?;
dest.write_str(";\n")?;
}
)*
dest.write_str("}")
}
}
impl<'a, 'b> DeclarationParser for FontFaceRuleParser<'a, 'b> {
type Declaration = ();
fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
match_ignore_ascii_case! { name,
$(
$m_name => {
self.rule.$m_ident = Parse::parse(self.context, input)?;
self.missing.$m_ident = false
},
)*
$(
$o_name => self.rule.$o_ident = Parse::parse(self.context, input)?,
)*
_ => return Err(())
}
Ok(())
}
} }
} }
} }
fn parse_one_src(context: &ParserContext, input: &mut Parser) -> Result<Source, ()> { /// css-name rust_identifier: Type = initial_value,
if input.try(|input| input.expect_function_matching("local")).is_ok() { #[cfg(feature = "gecko")]
return Ok(Source::Local(try!(input.parse_nested_block(parse_one_family)))) font_face_descriptors! {
mandatory descriptors = [
/// The name of this font face
"font-family" family: FamilyName = FamilyName(atom!("")),
/// The alternative sources for this font face.
"src" sources: Vec<Source> = Vec::new(),
]
optional descriptors = [
/// The style of this font face
"font-style" style: font_style::T = font_style::T::normal,
/// The weight of this font face
"font-weight" weight: font_weight::T = font_weight::T::Weight400 /* normal */,
/// The stretch of this font face
"font-stretch" stretch: font_stretch::T = font_stretch::T::normal,
/// The ranges of code points outside of which this font face should not be used.
"unicode-range" unicode_range: Vec<unicode_range::Range> = vec![
unicode_range::Range { start: 0, end: unicode_range::MAX }
],
]
}
#[cfg(feature = "servo")]
font_face_descriptors! {
mandatory descriptors = [
/// The name of this font face
"font-family" family: FamilyName = FamilyName(atom!("")),
/// The alternative sources for this font face.
"src" sources: Vec<Source> = Vec::new(),
]
optional descriptors = [
]
}
/// https://drafts.csswg.org/css-fonts/#unicode-range-desc
#[cfg(feature = "gecko")]
pub mod unicode_range {
use cssparser::{Parser, Token};
use parser::{ParserContext, Parse};
use std::fmt;
use style_traits::{ToCss, OneOrMoreCommaSeparated};
/// Maximum value of the end of a range
pub const MAX: u32 = ::std::char::MAX as u32;
/// A single range: https://drafts.csswg.org/css-fonts/#urange-value
#[derive(Debug, PartialEq, Eq)]
pub struct Range {
/// Start of the range, inclusive
pub start: u32,
/// End of the range, inclusive
pub end: u32,
} }
let url = try!(SpecifiedUrl::parse(context, input)); impl OneOrMoreCommaSeparated for Range {}
// Parsing optional format() impl Parse for Range {
let format_hints = if input.try(|input| input.expect_function_matching("format")).is_ok() { fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
try!(input.parse_nested_block(|input| { // FIXME: The unicode-range token has been removed from the CSS Syntax spec,
input.parse_comma_separated(|input| { // cssparser should be updated accordingly
Ok((try!(input.expect_string())).into_owned()) // and implement https://drafts.csswg.org/css-syntax/#urange instead
}) match input.next() {
})) Ok(Token::UnicodeRange(start, end)) => {
} else { if end <= MAX && start <= end {
vec![] Ok(Range { start: start, end: end })
}; } else {
Err(())
}
}
_ => Err(())
}
}
}
Ok(Source::Url(UrlSource { impl ToCss for Range {
url: url, fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
format_hints: format_hints, Token::UnicodeRange(self.start, self.end).to_css(dest)
})) }
}
} }

View file

@ -11,6 +11,7 @@ use error_reporting::ParseErrorReporter;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI}; use gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI};
use servo_url::ServoUrl; use servo_url::ServoUrl;
use style_traits::OneOrMoreCommaSeparated;
use stylesheets::{MemoryHoleReporter, Origin}; use stylesheets::{MemoryHoleReporter, Origin};
/// Extra data that the style backend may need to parse stylesheets. /// Extra data that the style backend may need to parse stylesheets.
@ -102,3 +103,9 @@ pub trait Parse : Sized {
/// Returns an error on failure. /// Returns an error on failure.
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()>; fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()>;
} }
impl<T> Parse for Vec<T> where T: Parse + OneOrMoreCommaSeparated {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
input.parse_comma_separated(|input| T::parse(context, input))
}
}

View file

@ -944,7 +944,7 @@ fn static_assert() {
for family in &v.0 { for family in &v.0 {
match *family { match *family {
FontFamily::FamilyName(ref name) => { FontFamily::FamilyName(ref name) => {
unsafe { Gecko_FontFamilyList_AppendNamed(list, name.as_ptr()); } unsafe { Gecko_FontFamilyList_AppendNamed(list, name.0.as_ptr()); }
} }
FontFamily::Generic(ref name) => { FontFamily::Generic(ref name) => {
let family_type = let family_type =

View file

@ -380,6 +380,13 @@
-> Result<SpecifiedValue, ()> { -> Result<SpecifiedValue, ()> {
SpecifiedValue::parse(input) SpecifiedValue::parse(input)
} }
impl Parse for SpecifiedValue {
#[inline]
fn parse(_context: &ParserContext, input: &mut Parser)
-> Result<SpecifiedValue, ()> {
SpecifiedValue::parse(input)
}
}
</%def> </%def>
% if vector: % if vector:
<%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> <%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)">

View file

@ -10,7 +10,7 @@
additional_methods=[Method("compute_font_hash", is_mut=True)]) %> additional_methods=[Method("compute_font_hash", is_mut=True)]) %>
<%helpers:longhand name="font-family" animatable="False" need_index="True" <%helpers:longhand name="font-family" animatable="False" need_index="True"
spec="https://drafts.csswg.org/css-fonts/#propdef-font-family"> spec="https://drafts.csswg.org/css-fonts/#propdef-font-family">
use self::computed_value::FontFamily; use self::computed_value::{FontFamily, FamilyName};
use values::NoViewportPercentage; use values::NoViewportPercentage;
use values::computed::ComputedValueAsSpecified; use values::computed::ComputedValueAsSpecified;
pub use self::computed_value::T as SpecifiedValue; pub use self::computed_value::T as SpecifiedValue;
@ -28,15 +28,19 @@
#[derive(Debug, PartialEq, Eq, Clone, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
pub enum FontFamily { pub enum FontFamily {
FamilyName(Atom), FamilyName(FamilyName),
Generic(Atom), Generic(Atom),
} }
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
pub struct FamilyName(pub Atom);
impl FontFamily { impl FontFamily {
#[inline] #[inline]
pub fn atom(&self) -> &Atom { pub fn atom(&self) -> &Atom {
match *self { match *self {
FontFamily::FamilyName(ref name) => name, FontFamily::FamilyName(ref name) => &name.0,
FontFamily::Generic(ref name) => name, FontFamily::Generic(ref name) => name,
} }
} }
@ -67,18 +71,22 @@
"monospace" => return FontFamily::Generic(atom!("monospace")), "monospace" => return FontFamily::Generic(atom!("monospace")),
_ => {} _ => {}
} }
FontFamily::FamilyName(input) FontFamily::FamilyName(FamilyName(input))
}
}
impl ToCss for FamilyName {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_char('"')?;
write!(CssStringWriter::new(dest), "{}", self.0)?;
dest.write_char('"')
} }
} }
impl ToCss for FontFamily { impl ToCss for FontFamily {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self { match *self {
FontFamily::FamilyName(ref name) => { FontFamily::FamilyName(ref name) => name.to_css(dest),
dest.write_char('"')?;
write!(CssStringWriter::new(dest), "{}", name)?;
dest.write_char('"')
}
// All generic values accepted by the parser are known to not require escaping. // All generic values accepted by the parser are known to not require escaping.
FontFamily::Generic(ref name) => write!(dest, "{}", name), FontFamily::Generic(ref name) => write!(dest, "{}", name),
@ -107,56 +115,78 @@
pub fn get_initial_value() -> computed_value::T { pub fn get_initial_value() -> computed_value::T {
computed_value::T(vec![FontFamily::Generic(atom!("serif"))]) computed_value::T(vec![FontFamily::Generic(atom!("serif"))])
} }
/// <family-name># /// <family-name>#
/// <family-name> = <string> | [ <ident>+ ] /// <family-name> = <string> | [ <ident>+ ]
/// TODO: <generic-family> /// TODO: <generic-family>
pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> { pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
input.parse_comma_separated(parse_one_family).map(SpecifiedValue) Vec::<FontFamily>::parse(context, input).map(SpecifiedValue)
} }
pub fn parse_one_family(input: &mut Parser) -> Result<FontFamily, ()> {
if let Ok(value) = input.try(|input| input.expect_string()) {
return Ok(FontFamily::FamilyName(Atom::from(&*value)))
}
let first_ident = try!(input.expect_ident());
// FIXME(bholley): The fast thing to do here would be to look up the impl Parse for Vec<FontFamily> {
// string (as lowercase) in the static atoms table. We don't have an fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
// API to do that yet though, so we do the simple thing for now. input.parse_comma_separated(|input| FontFamily::parse(context, input))
let mut css_wide_keyword = false; }
match_ignore_ascii_case! { first_ident, }
"serif" => return Ok(FontFamily::Generic(atom!("serif"))),
"sans-serif" => return Ok(FontFamily::Generic(atom!("sans-serif"))),
"cursive" => return Ok(FontFamily::Generic(atom!("cursive"))),
"fantasy" => return Ok(FontFamily::Generic(atom!("fantasy"))),
"monospace" => return Ok(FontFamily::Generic(atom!("monospace"))),
// https://drafts.csswg.org/css-fonts/#propdef-font-family /// `FamilyName::parse` is based on `FontFamily::parse` and not the other way around
// "Font family names that happen to be the same as a keyword value /// because we want the former to exclude generic family keywords.
// (inherit, serif, sans-serif, monospace, fantasy, and cursive) impl Parse for FamilyName {
// must be quoted to prevent confusion with the keywords with the same names. fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
// The keywords initial and default are reserved for future use match FontFamily::parse(context, input) {
// and must also be quoted when used as font names. Ok(FontFamily::FamilyName(name)) => Ok(name),
// UAs must not consider these keywords as matching the <family-name> type." Ok(FontFamily::Generic(_)) |
"inherit" => css_wide_keyword = true, Err(()) => Err(())
"initial" => css_wide_keyword = true, }
"unset" => css_wide_keyword = true,
"default" => css_wide_keyword = true,
_ => {}
} }
}
let mut value = first_ident.into_owned(); impl Parse for FontFamily {
// These keywords are not allowed by themselves. fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
// The only way this value can be valid with with another keyword. if let Ok(value) = input.try(|input| input.expect_string()) {
if css_wide_keyword { return Ok(FontFamily::FamilyName(FamilyName(Atom::from(&*value))))
let ident = input.expect_ident()?; }
value.push_str(" "); let first_ident = try!(input.expect_ident());
value.push_str(&ident);
// FIXME(bholley): The fast thing to do here would be to look up the
// string (as lowercase) in the static atoms table. We don't have an
// API to do that yet though, so we do the simple thing for now.
let mut css_wide_keyword = false;
match_ignore_ascii_case! { first_ident,
"serif" => return Ok(FontFamily::Generic(atom!("serif"))),
"sans-serif" => return Ok(FontFamily::Generic(atom!("sans-serif"))),
"cursive" => return Ok(FontFamily::Generic(atom!("cursive"))),
"fantasy" => return Ok(FontFamily::Generic(atom!("fantasy"))),
"monospace" => return Ok(FontFamily::Generic(atom!("monospace"))),
// https://drafts.csswg.org/css-fonts/#propdef-font-family
// "Font family names that happen to be the same as a keyword value
// (inherit, serif, sans-serif, monospace, fantasy, and cursive)
// must be quoted to prevent confusion with the keywords with the same names.
// The keywords initial and default are reserved for future use
// and must also be quoted when used as font names.
// UAs must not consider these keywords as matching the <family-name> type."
"inherit" => css_wide_keyword = true,
"initial" => css_wide_keyword = true,
"unset" => css_wide_keyword = true,
"default" => css_wide_keyword = true,
_ => {}
}
let mut value = first_ident.into_owned();
// These keywords are not allowed by themselves.
// The only way this value can be valid with with another keyword.
if css_wide_keyword {
let ident = input.expect_ident()?;
value.push_str(" ");
value.push_str(&ident);
}
while let Ok(ident) = input.try(|input| input.expect_ident()) {
value.push_str(" ");
value.push_str(&ident);
}
Ok(FontFamily::FamilyName(FamilyName(Atom::from(value))))
} }
while let Ok(ident) = input.try(|input| input.expect_ident()) {
value.push_str(" ");
value.push_str(&ident);
}
Ok(FontFamily::FamilyName(Atom::from(value)))
} }
</%helpers:longhand> </%helpers:longhand>
@ -243,6 +273,20 @@ ${helpers.single_keyword("font-variant-caps",
} }
}) })
} }
/// Used in @font-face, where relative keywords are not allowed.
impl Parse for computed_value::T {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
match parse(context, input)? {
% for weight in range(100, 901, 100):
SpecifiedValue::Weight${weight} => Ok(computed_value::T::Weight${weight}),
% endfor
SpecifiedValue::Bolder |
SpecifiedValue::Lighter => Err(())
}
}
}
pub mod computed_value { pub mod computed_value {
use std::fmt; use std::fmt;
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]

View file

@ -12,8 +12,10 @@
${'font-variant-position' if product == 'gecko' else ''} ${'font-variant-position' if product == 'gecko' else ''}
${'font-language-override' if product == 'none' else ''}" ${'font-language-override' if product == 'none' else ''}"
spec="https://drafts.csswg.org/css-fonts-3/#propdef-font"> spec="https://drafts.csswg.org/css-fonts-3/#propdef-font">
use parser::Parse;
use properties::longhands::{font_style, font_variant, font_weight, font_stretch}; use properties::longhands::{font_style, font_variant, font_weight, font_stretch};
use properties::longhands::{font_size, line_height, font_family}; use properties::longhands::{font_size, line_height, font_family};
use properties::longhands::font_family::computed_value::FontFamily;
pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> { pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
let mut nb_normals = 0; let mut nb_normals = 0;
@ -69,7 +71,7 @@
} else { } else {
None None
}; };
let family = try!(input.parse_comma_separated(font_family::parse_one_family)); let family = Vec::<FontFamily>::parse(context, input)?;
Ok(Longhands { Ok(Longhands {
font_style: style, font_style: style,
font_variant: variant, font_variant: variant,

View file

@ -59,4 +59,4 @@ pub mod cursor;
pub mod values; pub mod values;
pub mod viewport; pub mod viewport;
pub use values::ToCss; pub use values::{ToCss, OneOrMoreCommaSeparated};

View file

@ -24,6 +24,21 @@ pub trait ToCss {
} }
} }
/// Marker trait to automatically implement ToCss for Vec<T>.
pub trait OneOrMoreCommaSeparated {}
impl<T> ToCss for Vec<T> where T: ToCss + OneOrMoreCommaSeparated {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
let mut iter = self.iter();
iter.next().unwrap().to_css(dest)?;
for item in iter {
dest.write_str(", ")?;
item.to_css(dest)?;
}
Ok(())
}
}
impl ToCss for Au { impl ToCss for Au {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
write!(dest, "{}px", self.to_f64_px()) write!(dest, "{}px", self.to_f64_px())

View file

@ -4,7 +4,7 @@
use gfx::font_cache_thread::FontCacheThread; use gfx::font_cache_thread::FontCacheThread;
use ipc_channel::ipc; use ipc_channel::ipc;
use style::computed_values::font_family::FontFamily; use style::computed_values::font_family::{FontFamily, FamilyName};
use style::font_face::{FontFaceRule, Source}; use style::font_face::{FontFaceRule, Source};
#[test] #[test]
@ -12,8 +12,8 @@ fn test_local_web_font() {
let (inp_chan, _) = ipc::channel().unwrap(); let (inp_chan, _) = ipc::channel().unwrap();
let (out_chan, out_receiver) = ipc::channel().unwrap(); let (out_chan, out_receiver) = ipc::channel().unwrap();
let font_cache_thread = FontCacheThread::new(inp_chan, None); let font_cache_thread = FontCacheThread::new(inp_chan, None);
let family_name = FontFamily::FamilyName(From::from("test family")); let family_name = FamilyName(From::from("test family"));
let variant_name = FontFamily::FamilyName(From::from("test font face")); let variant_name = FamilyName(From::from("test font face"));
let font_face_rule = FontFaceRule { let font_face_rule = FontFaceRule {
family: family_name.clone(), family: family_name.clone(),
sources: vec![Source::Local(variant_name)], sources: vec![Source::Local(variant_name)],