blob: 9aeedbf17e07e73a9f0587a47cf4f970c7af2a18 [file] [log] [blame] [edit]
//! The `@font-feature-values` rule.
use super::Location;
use crate::error::{ParserError, PrinterError};
use crate::parser::ParserOptions;
use crate::printer::Printer;
use crate::properties::font::FamilyName;
use crate::traits::{Parse, ToCss};
use crate::values::ident::Ident;
use crate::values::number::CSSInteger;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
use indexmap::IndexMap;
use smallvec::SmallVec;
use std::fmt::Write;
/// A [@font-feature-values](https://drafts.csswg.org/css-fonts/#font-feature-values) rule.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct FontFeatureValuesRule<'i> {
/// The name of the font feature values.
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: Vec<FamilyName<'i>>,
/// The rules within the `@font-feature-values` rule.
pub rules: IndexMap<FontFeatureSubruleType, FontFeatureSubrule<'i>>,
/// The location of the rule in the source file.
#[cfg_attr(feature = "visitor", skip_visit)]
pub loc: Location,
}
impl<'i> FontFeatureValuesRule<'i> {
pub(crate) fn parse<'t, 'o>(
family_names: Vec<FamilyName<'i>>,
input: &mut Parser<'i, 't>,
loc: Location,
options: &ParserOptions<'o, 'i>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut rules = IndexMap::new();
let mut rule_parser = FontFeatureValuesRuleParser {
rules: &mut rules,
options,
};
let mut parser = RuleBodyParser::new(input, &mut rule_parser);
while let Some(decl_or_rule) = parser.next() {
if let Err((err, _)) = decl_or_rule {
if parser.parser.options.error_recovery {
parser.parser.options.warn(err);
continue;
}
return Err(err);
}
}
Ok(FontFeatureValuesRule {
name: family_names,
rules,
loc,
})
}
}
struct FontFeatureValuesRuleParser<'a, 'o, 'i> {
rules: &'a mut IndexMap<FontFeatureSubruleType, FontFeatureSubrule<'i>>,
options: &'a ParserOptions<'o, 'i>,
}
impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for FontFeatureValuesRuleParser<'a, 'o, 'i> {
type Declaration = ();
type Error = ParserError<'i>;
}
impl<'a, 'o, 'i> cssparser::AtRuleParser<'i> for FontFeatureValuesRuleParser<'a, 'o, 'i> {
type Prelude = FontFeatureSubruleType;
type AtRule = ();
type Error = ParserError<'i>;
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
let loc = input.current_source_location();
FontFeatureSubruleType::parse_string(&name)
.map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))
}
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
let loc = start.source_location();
let mut decls = IndexMap::new();
let mut has_existing = false;
let declarations = if let Some(rule) = self.rules.get_mut(&prelude) {
has_existing = true;
&mut rule.declarations
} else {
&mut decls
};
let mut decl_parser = FontFeatureDeclarationParser { declarations };
let mut parser = RuleBodyParser::new(input, &mut decl_parser);
while let Some(decl) = parser.next() {
if let Err((err, _)) = decl {
if self.options.error_recovery {
self.options.warn(err);
continue;
}
return Err(err);
}
}
if !has_existing {
self.rules.insert(
prelude,
FontFeatureSubrule {
name: prelude,
declarations: decls,
loc: Location {
source_index: self.options.source_index,
line: loc.line,
column: loc.column,
},
},
);
}
Ok(())
}
}
impl<'a, 'o, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a, 'o, 'i> {
type Prelude = ();
type QualifiedRule = ();
type Error = ParserError<'i>;
}
impl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for FontFeatureValuesRuleParser<'a, 'o, 'i> {
fn parse_declarations(&self) -> bool {
false
}
fn parse_qualified(&self) -> bool {
false
}
}
impl<'i> ToCss for FontFeatureValuesRule<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
#[cfg(feature = "sourcemap")]
dest.add_mapping(self.loc);
dest.write_str("@font-feature-values ")?;
self.name.to_css(dest)?;
dest.whitespace()?;
dest.write_char('{')?;
if !self.rules.is_empty() {
dest.newline()?;
for rule in self.rules.values() {
rule.to_css(dest)?;
dest.newline()?;
}
}
dest.write_char('}')
}
}
impl<'i> FontFeatureValuesRule<'i> {
pub(crate) fn merge(&mut self, other: &FontFeatureValuesRule<'i>) {
debug_assert_eq!(self.name, other.name);
for (prelude, rule) in &other.rules {
if let Some(existing) = self.rules.get_mut(prelude) {
existing
.declarations
.extend(rule.declarations.iter().map(|(k, v)| (k.clone(), v.clone())));
} else {
self.rules.insert(*prelude, rule.clone());
}
}
}
}
/// The name of the `@font-feature-values` sub-rule.
/// font-feature-value-type = <@stylistic> | <@historical-forms> | <@styleset> | <@character-variant>
/// | <@swash> | <@ornaments> | <@annotation>
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum FontFeatureSubruleType {
/// @stylistic = @stylistic { <declaration-list> }
Stylistic,
/// @historical-forms = @historical-forms { <declaration-list> }
HistoricalForms,
/// @styleset = @styleset { <declaration-list> }
Styleset,
/// @character-variant = @character-variant { <declaration-list> }
CharacterVariant,
/// @swash = @swash { <declaration-list> }
Swash,
/// @ornaments = @ornaments { <declaration-list> }
Ornaments,
/// @annotation = @annotation { <declaration-list> }
Annotation,
}
/// A sub-rule of `@font-feature-values`
/// https://drafts.csswg.org/css-fonts/#font-feature-values-syntax
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct FontFeatureSubrule<'i> {
/// The name of the `@font-feature-values` sub-rule.
pub name: FontFeatureSubruleType,
/// The declarations within the `@font-feature-values` sub-rules.
#[cfg_attr(feature = "serde", serde(borrow))]
pub declarations: IndexMap<Ident<'i>, SmallVec<[CSSInteger; 1]>>,
/// The location of the rule in the source file.
#[cfg_attr(feature = "visitor", skip_visit)]
pub loc: Location,
}
impl<'i> ToCss for FontFeatureSubrule<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: Write,
{
#[cfg(feature = "sourcemap")]
dest.add_mapping(self.loc);
dest.write_char('@')?;
self.name.to_css(dest)?;
dest.write_char('{')?;
dest.indent();
let len = self.declarations.len();
for (i, (name, value)) in self.declarations.iter().enumerate() {
dest.newline()?;
name.to_css(dest)?;
dest.delim(':', false)?;
let mut first = true;
for index in value {
if first {
first = false;
} else {
dest.write_char(' ')?;
}
index.to_css(dest)?;
}
if i != len - 1 || !dest.minify {
dest.write_char(';')?;
}
}
dest.dedent();
dest.newline()?;
dest.write_char('}')
}
}
struct FontFeatureDeclarationParser<'a, 'i> {
declarations: &'a mut IndexMap<Ident<'i>, SmallVec<[CSSInteger; 1]>>,
}
impl<'a, 'i> cssparser::DeclarationParser<'i> for FontFeatureDeclarationParser<'a, 'i> {
type Declaration = ();
type Error = ParserError<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut cssparser::Parser<'i, 't>,
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
let mut indices = SmallVec::new();
loop {
if let Ok(value) = CSSInteger::parse(input) {
indices.push(value);
} else {
break;
}
}
if indices.is_empty() {
return Err(input.new_custom_error(ParserError::InvalidValue));
}
self.declarations.insert(Ident(name.into()), indices);
Ok(())
}
}
/// Default methods reject all at rules.
impl<'a, 'i> AtRuleParser<'i> for FontFeatureDeclarationParser<'a, 'i> {
type Prelude = ();
type AtRule = ();
type Error = ParserError<'i>;
}
impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureDeclarationParser<'a, 'i> {
type Prelude = ();
type QualifiedRule = ();
type Error = ParserError<'i>;
}
impl<'a, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for FontFeatureDeclarationParser<'a, 'i> {
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}