mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 16:25:38 +00:00
7f9604757a
<!-- 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. --> Source-Repo: https://github.com/servo/servo Source-Revision: 48f3cc83257554dc09c4489fbfaf2f702a1083f3 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : e4dfe03f4703302e56c7c1c52ddd5bd9680d48fd
367 lines
12 KiB
Rust
367 lines
12 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
//! The [`@font-face`][ff] at-rule.
|
|
//!
|
|
//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
|
|
|
|
#![deny(missing_docs)]
|
|
|
|
#[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 parser::{ParserContext, log_css_error, Parse};
|
|
use std::fmt;
|
|
use std::iter;
|
|
use style_traits::{ToCss, OneOrMoreCommaSeparated};
|
|
use values::specified::url::SpecifiedUrl;
|
|
|
|
/// A source for a font-face rule.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
|
|
pub enum Source {
|
|
/// A `url()` source.
|
|
Url(UrlSource),
|
|
/// A `local()` source.
|
|
Local(FamilyName),
|
|
}
|
|
|
|
impl ToCss for Source {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
|
where W: fmt::Write,
|
|
{
|
|
match *self {
|
|
Source::Url(ref url) => {
|
|
try!(dest.write_str("url(\""));
|
|
try!(url.to_css(dest));
|
|
},
|
|
Source::Local(ref family) => {
|
|
try!(dest.write_str("local(\""));
|
|
try!(family.to_css(dest));
|
|
},
|
|
}
|
|
dest.write_str("\")")
|
|
}
|
|
}
|
|
|
|
impl OneOrMoreCommaSeparated for Source {}
|
|
|
|
/// A `UrlSource` represents a font-face source that has been specified with a
|
|
/// `url()` function.
|
|
///
|
|
/// https://drafts.csswg.org/css-fonts/#src-desc
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
|
|
pub struct UrlSource {
|
|
/// The specified url.
|
|
pub url: SpecifiedUrl,
|
|
/// The format hints specified with the `format()` function.
|
|
pub format_hints: Vec<String>,
|
|
}
|
|
|
|
impl ToCss for UrlSource {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
|
where W: fmt::Write,
|
|
{
|
|
dest.write_str(self.url.as_str())
|
|
}
|
|
}
|
|
|
|
/// Parse the block inside a `@font-face` rule.
|
|
///
|
|
/// Note that the prelude parsing code lives in the `stylesheets` module.
|
|
pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser)
|
|
-> Result<FontFaceRule, ()> {
|
|
let mut rule = FontFaceRule::initial();
|
|
{
|
|
let parser = FontFaceRuleParser {
|
|
context: context,
|
|
rule: &mut rule,
|
|
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 message = format!("Unsupported @font-face descriptor declaration: '{}'",
|
|
iter.input.slice(range));
|
|
log_css_error(iter.input, pos, &*message, context);
|
|
}
|
|
}
|
|
if iter.parser.missing.any() {
|
|
return Err(())
|
|
}
|
|
}
|
|
Ok(rule)
|
|
}
|
|
|
|
/// A list of effective sources that we send over through IPC to the font cache.
|
|
#[derive(Clone, Debug)]
|
|
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
|
|
pub struct EffectiveSources(Vec<Source>);
|
|
|
|
impl FontFaceRule {
|
|
/// Returns the list of effective sources for that font-face, that is the
|
|
/// sources which don't list any format hint, or the ones which list at
|
|
/// least "truetype" or "opentype".
|
|
pub fn effective_sources(&self) -> EffectiveSources {
|
|
EffectiveSources(self.sources.iter().rev().filter(|source| {
|
|
if let Source::Url(ref url_source) = **source {
|
|
let hints = &url_source.format_hints;
|
|
// We support only opentype fonts and truetype is an alias for
|
|
// that format. Sources without format hints need to be
|
|
// downloaded in case we support them.
|
|
hints.is_empty() || hints.iter().any(|hint| {
|
|
hint == "truetype" || hint == "opentype" || hint == "woff"
|
|
})
|
|
} else {
|
|
true
|
|
}
|
|
}).cloned().collect())
|
|
}
|
|
}
|
|
|
|
impl iter::Iterator for EffectiveSources {
|
|
type Item = Source;
|
|
fn next(&mut self) -> Option<Source> {
|
|
self.0.pop()
|
|
}
|
|
}
|
|
|
|
struct FontFaceRuleParser<'a, 'b: 'a> {
|
|
context: &'a ParserContext<'b>,
|
|
rule: &'a mut FontFaceRule,
|
|
missing: MissingDescriptors,
|
|
}
|
|
|
|
/// Default methods reject all at rules.
|
|
impl<'a, 'b> AtRuleParser for FontFaceRuleParser<'a, 'b> {
|
|
type Prelude = ();
|
|
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)
|
|
}
|
|
|
|
let url = SpecifiedUrl::parse(context, input)?;
|
|
|
|
// Parsing optional format()
|
|
let format_hints = if input.try(|input| input.expect_function_matching("format")).is_ok() {
|
|
input.parse_nested_block(|input| {
|
|
input.parse_comma_separated(|input| {
|
|
Ok(input.expect_string()?.into_owned())
|
|
})
|
|
})?
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
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(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// css-name rust_identifier: Type = initial_value,
|
|
#[cfg(feature = "gecko")]
|
|
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,
|
|
}
|
|
|
|
impl OneOrMoreCommaSeparated for Range {}
|
|
|
|
impl Parse for Range {
|
|
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
|
// FIXME: The unicode-range token has been removed from the CSS Syntax spec,
|
|
// cssparser should be updated accordingly
|
|
// and implement https://drafts.csswg.org/css-syntax/#urange instead
|
|
match input.next() {
|
|
Ok(Token::UnicodeRange(start, end)) => {
|
|
if end <= MAX && start <= end {
|
|
Ok(Range { start: start, end: end })
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
_ => Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToCss for Range {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
|
Token::UnicodeRange(self.start, self.end).to_css(dest)
|
|
}
|
|
}
|
|
}
|