diff --git a/src/lib.rs b/src/lib.rs index 19b1824a..432ef29f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9442,34 +9442,75 @@ mod tests { ); minify_test("@font-face {src: local(Test);}", "@font-face{src:local(Test)}"); minify_test("@font-face {src: local(Foo Bar);}", "@font-face{src:local(Foo Bar)}"); + minify_test( "@font-face {src: url(\"test.woff\") format(woff);}", "@font-face{src:url(test.woff)format(\"woff\")}", ); minify_test( - "@font-face {src: url(\"test.woff\") format(woff), url(test.ttf) format(truetype);}", - "@font-face{src:url(test.woff)format(\"woff\"),url(test.ttf)format(\"truetype\")}", + "@font-face {src: url(\"test.ttc\") format(collection), url(test.ttf) format(truetype);}", + "@font-face{src:url(test.ttc)format(\"collection\"),url(test.ttf)format(\"truetype\")}", + ); + minify_test( + "@font-face {src: url(\"test.otf\") format(opentype) tech(feature-aat);}", + "@font-face{src:url(test.otf)format(\"opentype\")tech(feature-aat)}", + ); + minify_test( + "@font-face {src: url(\"test.woff\") format(woff) tech(color-colrv1);}", + "@font-face{src:url(test.woff)format(\"woff\")tech(color-colrv1)}", + ); + minify_test( + "@font-face {src: url(\"test.woff2\") format(woff2) tech(variations);}", + "@font-face{src:url(test.woff2)format(\"woff2\")tech(variations)}", ); minify_test( - "@font-face {src: url(\"test.woff\") format(woff supports features(opentype));}", - "@font-face{src:url(test.woff)format(\"woff\" supports features(opentype))}", + "@font-face {src: url(\"test.woff\") format(woff) tech(palettes);}", + "@font-face{src:url(test.woff)format(\"woff\")tech(palettes)}", ); + // multiple tech minify_test( - "@font-face {src: url(\"test.woff\") format(woff supports color(COLRv1));}", - "@font-face{src:url(test.woff)format(\"woff\" supports color(colrv1))}", + "@font-face {src: url(\"test.woff\") format(woff) tech(feature-opentype, color-sbix);}", + "@font-face{src:url(test.woff)format(\"woff\")tech(feature-opentype,color-sbix)}", ); minify_test( - "@font-face {src: url(\"test.woff\") format(woff supports variations);}", - "@font-face{src:url(test.woff)format(\"woff\" supports variations)}", + "@font-face {src: url(\"test.woff\") format(woff) tech(incremental, color-svg, feature-graphite, feature-aat);}", + "@font-face{src:url(test.woff)format(\"woff\")tech(incremental,color-svg,feature-graphite,feature-aat)}", ); + // format() function must precede tech() if both are present minify_test( - "@font-face {src: url(\"test.woff\") format(woff supports palettes);}", - "@font-face{src:url(test.woff)format(\"woff\" supports palettes)}", + "@font-face {src: url(\"foo.ttf\") format(opentype) tech(color-colrv1);}", + "@font-face{src:url(foo.ttf)format(\"opentype\")tech(color-colrv1)}", ); + // only have tech is valid minify_test( - "@font-face {src: url(\"test.woff\") format(woff supports features(opentype) color(sbix));}", - "@font-face{src:url(test.woff)format(\"woff\" supports features(opentype) color(sbix))}", + "@font-face {src: url(\"foo.ttf\") tech(color-SVG);}", + "@font-face{src:url(foo.ttf)tech(color-svg)}", ); + // CGQAQ: if tech and format both presence, order is matter, tech before format is invalid + // but now just return raw token, we don't have strict mode yet. + // ref: https://github.com/parcel-bundler/parcel-css/pull/255#issuecomment-1219049998 + minify_test( + "@font-face {src: url(\"foo.ttf\") tech(palettes color-colrv0 variations) format(opentype);}", + "@font-face{src:url(foo.ttf) tech(palettes color-colrv0 variations)format(opentype)}", + ); + // TODO(CGQAQ): make this test pass when we have strict mode + // ref: https://github.com/web-platform-tests/wpt/blob/9f8a6ccc41aa725e8f51f4f096f686313bb88d8d/css/css-fonts/parsing/font-face-src-tech.html#L45 + // error_test( + // "@font-face {src: url(\"foo.ttf\") tech(feature-opentype) format(opentype);}", + // ParserError::AtRuleBodyInvalid, + // ); + // error_test( + // "@font-face {src: url(\"foo.ttf\") tech();}", + // ParserError::AtRuleBodyInvalid, + // ); + // error_test( + // "@font-face {src: url(\"foo.ttf\") tech(\"feature-opentype\");}", + // ParserError::AtRuleBodyInvalid, + // ); + // error_test( + // "@font-face {src: url(\"foo.ttf\") tech(\"color-colrv0\");}", + // ParserError::AtRuleBodyInvalid, + // ); minify_test( "@font-face {src: local(\"\") url(\"test.woff\");}", "@font-face{src:local(\"\")url(test.woff)}", diff --git a/src/rules/font_face.rs b/src/rules/font_face.rs index 492f65bf..a61feefe 100644 --- a/src/rules/font_face.rs +++ b/src/rules/font_face.rs @@ -69,8 +69,15 @@ pub enum Source<'i> { impl<'i> Parse<'i> for Source<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - if let Ok(url) = input.try_parse(UrlSource::parse) { - return Ok(Source::Url(url)); + match input.try_parse(UrlSource::parse) { + Ok(url) => return Ok(Source::Url(url)), + e @ Err(ParseError { + kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid), + .. + }) => { + return Err(e.err().unwrap()); + } + _ => {} } input.expect_function_matching("local")?; @@ -104,7 +111,9 @@ pub struct UrlSource<'i> { pub url: Url<'i>, /// Optional `format()` function. #[cfg_attr(feature = "serde", serde(borrow))] - pub format: Option>, + pub format: Option>, + /// Optional `tech()` function. + pub tech: Vec, } impl<'i> Parse<'i> for UrlSource<'i> { @@ -112,12 +121,18 @@ impl<'i> Parse<'i> for UrlSource<'i> { let url = Url::parse(input)?; let format = if input.try_parse(|input| input.expect_function_matching("format")).is_ok() { - Some(input.parse_nested_block(Format::parse)?) + Some(input.parse_nested_block(FontFormat::parse)?) } else { None }; - Ok(UrlSource { url, format }) + let tech = if input.try_parse(|input| input.expect_function_matching("tech")).is_ok() { + input.parse_nested_block(Vec::::parse)? + } else { + vec![] + }; + + Ok(UrlSource { url, format, tech }) } } @@ -133,57 +148,12 @@ impl<'i> ToCss for UrlSource<'i> { format.to_css(dest)?; dest.write_char(')')?; } - Ok(()) - } -} - -/// The `format()` function within the [src](https://drafts.csswg.org/css-fonts/#src-desc) -/// property of an `@font-face` rule. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Format<'i> { - /// A font format name. - #[cfg_attr(feature = "serde", serde(borrow))] - pub format: FontFormat<'i>, - /// The `supports()` function. - // TODO: did this get renamed to `tech()`? - pub supports: Vec, -} -impl<'i> Parse<'i> for Format<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - let format = FontFormat::parse(input)?; - let mut supports = vec![]; - if input.try_parse(|input| input.expect_ident_matching("supports")).is_ok() { - loop { - if let Ok(technology) = input.try_parse(FontTechnology::parse) { - supports.push(technology) - } else { - break; - } - } - } - Ok(Format { format, supports }) - } -} - -impl<'i> ToCss for Format<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - self.format.to_css(dest)?; - if !self.supports.is_empty() { - dest.write_str(" supports ")?; - let mut first = true; - for technology in &self.supports { - if first { - first = false; - } else { - dest.write_char(' ')?; - } - technology.to_css(dest)?; - } + if !self.tech.is_empty() { + dest.whitespace()?; + dest.write_str("tech(")?; + self.tech.to_css(dest)?; + dest.write_char(')')?; } Ok(()) } @@ -199,9 +169,10 @@ impl<'i> ToCss for Format<'i> { serde(tag = "type", content = "value", rename_all = "kebab-case") )] pub enum FontFormat<'i> { - /// A WOFF font. + /// [src](https://drafts.csswg.org/css-fonts/#font-format-definitions) + /// A WOFF 1.0 font. WOFF, - /// A WOFF v2 font. + /// A WOFF 2.0 font. WOFF2, /// A TrueType font. TrueType, @@ -209,7 +180,7 @@ pub enum FontFormat<'i> { OpenType, /// An Embedded OpenType (.eot) font. EmbeddedOpenType, - /// A font collection. + /// OpenType Collection. Collection, /// An SVG font. SVG, @@ -258,103 +229,46 @@ impl<'i> ToCss for FontFormat<'i> { } enum_property! { - /// A font feature tech descriptor in the `supports()`function of the - /// [src](https://drafts.csswg.org/css-fonts/#src-desc) - /// property of an `@font-face` rule. - pub enum FontFeatureTechnology { - /// Supports OpenType features. - OpenType, - /// Supports Apple Advanced Typography features. - AAT, - /// Supports Graphite features. - Graphite, - } -} - -enum_property! { - /// A color font tech descriptor in the `supports()`function of the + /// A font format keyword in the `format()` function of the the /// [src](https://drafts.csswg.org/css-fonts/#src-desc) /// property of an `@font-face` rule. - pub enum ColorFontTechnology { + pub enum FontTechnology { + /// A font feature tech descriptor in the `tech()`function of the + /// [src](https://drafts.csswg.org/css-fonts/#font-feature-tech-values) + /// property of an `@font-face` rule. + /// Supports OpenType Features. + /// https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + "feature-opentype": FeatureOpentype, + /// Supports Apple Advanced Typography Font Features. + /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html + "feature-aat": FeatureAat, + /// Supports Graphite Table Format. + /// https://scripts.sil.org/cms/scripts/render_download.php?site_id=nrsi&format=file&media_id=GraphiteBinaryFormat_3_0&filename=GraphiteBinaryFormat_3_0.pdf + "feature-graphite": FeatureGraphite, + + /// A color font tech descriptor in the `tech()`function of the + /// [src](https://drafts.csswg.org/css-fonts/#src-desc) + /// property of an `@font-face` rule. /// Supports the `COLR` v0 table. - COLRv0, + "color-colrv0": ColorCOLRv0, /// Supports the `COLR` v1 table. - COLRv1, - /// Supports SVG glyphs. - SVG, + "color-colrv1": ColorCOLRv1, + /// Supports the `SVG` table. + "color-svg": ColorSVG, /// Supports the `sbix` table. - SBIX, + "color-sbix": ColorSbix, /// Supports the `CBDT` table. - CBDT, - } -} - -/// A font technology descriptor in the `supports()`function of the -/// [src](https://drafts.csswg.org/css-fonts/#src-desc) -/// property of an `@font-face` rule. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type", content = "value", rename_all = "kebab-case") -)] -pub enum FontTechnology { - /// Supports font features. - Features(FontFeatureTechnology), - /// Supports variations. - Variations, - /// Supports color glyphs. - Color(ColorFontTechnology), - /// Supports color palettes. - Palettes, -} - -impl<'i> Parse<'i> for FontTechnology { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - let location = input.current_source_location(); - match input.next()? { - Token::Function(f) => { - match_ignore_ascii_case! { &f, - "features" => Ok(FontTechnology::Features(input.parse_nested_block(FontFeatureTechnology::parse)?)), - "color" => Ok(FontTechnology::Color(input.parse_nested_block(ColorFontTechnology::parse)?)), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(f.clone()) - )) - } - } - Token::Ident(ident) => { - match_ignore_ascii_case! { &ident, - "variations" => Ok(FontTechnology::Variations), - "palettes" => Ok(FontTechnology::Palettes), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } - tok => Err(location.new_unexpected_token_error(tok.clone())), - } - } -} - -impl ToCss for FontTechnology { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - FontTechnology::Features(f) => { - dest.write_str("features(")?; - f.to_css(dest)?; - dest.write_char(')') - } - FontTechnology::Color(c) => { - dest.write_str("color(")?; - c.to_css(dest)?; - dest.write_char(')') - } - FontTechnology::Variations => dest.write_str("variations"), - FontTechnology::Palettes => dest.write_str("palettes"), - } + "color-cbdt": ColorCBDT, + + /// Supports Variations + /// The variations tech refers to the support of font variations + "variations": Variations, + /// Supports Palettes + /// The palettes tech refers to support for font palettes + "palettes": Palettes, + /// Supports Incremental + /// The incremental tech refers to client support for incremental font loading, using either the range-request or the patch-subset method + "incremental": Incremental, } }