use std::ops::{Deref, DerefMut};
use super::{
core::{Angle, Anything, Length, Number, Percentage},
list_of::{ListOf, SpaceOrComma},
};
use lightningcss::values::length::LengthValue;
pub use lightningcss::{
properties::{
display::{Display, Visibility},
effects::FilterList,
font::{Font, FontSize, FontStretch, FontStyle, FontWeight},
masking::{ClipPath, MaskType},
overflow::Overflow,
svg::{
ColorInterpolation, ColorRendering, ImageRendering, Marker, ShapeRendering,
StrokeDasharray, StrokeLinecap, StrokeLinejoin, TextRendering,
},
text::{Direction, Spacing, TextDecoration, UnicodeBidi},
ui::Cursor,
},
values::{length::LengthOrNumber, shape::FillRule},
};
#[cfg(feature = "parse")]
use oxvg_parse::{error::Error, Parse, Parser};
#[cfg(feature = "serialize")]
use oxvg_serialize::{error::PrinterError, Printer, ToValue};
use smallvec::{smallvec, SmallVec};
use crate::enum_attr;
enum_attr!(
AlignmentBaseline {
Auto: "auto",
Baseline: "baseline",
BeforeEdge: "before-edge",
TextBeforeEdge: "text-before-edge",
Middle: "middle",
Central: "central",
AfterEdge: "after-edge",
TextAfterEdge: "text-after-edge",
Ideographic: "ideographic",
Alphabetic: "alphabetic",
Hanging: "hanging",
Mathematical: "mathematical",
}
);
#[derive(Clone, Debug, PartialEq)]
pub enum BaselineShift {
Baseline,
Sub,
Super,
Top,
Center,
Bottom,
Percentage(Percentage),
Length(LengthValue),
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for BaselineShift {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| {
let ident: &str = input.expect_ident().map_err(|_| ())?;
Ok(match ident {
"baseline" => Self::Baseline,
"sub" => Self::Sub,
"super" => Self::Super,
"top" => Self::Top,
"center" => Self::Center,
"bottom" => Self::Bottom,
_ => return Err(()),
})
})
.or_else(|()| input.try_parse(Percentage::parse).map(Self::Percentage))
.or_else(|_| LengthValue::parse(input).map(Self::Length))
}
}
#[cfg(feature = "serialize")]
impl ToValue for BaselineShift {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Baseline => dest.write_char('0'),
Self::Sub => dest.write_str("sub"),
Self::Super => dest.write_str("super"),
Self::Top => dest.write_str("top"),
Self::Center => dest.write_str("center"),
Self::Bottom => dest.write_str("bottom"),
Self::Percentage(percentage) => percentage.write_value(dest),
Self::Length(length) => length.write_value(dest),
}
}
}
#[test]
fn baseline_shift() {
assert_eq!(
BaselineShift::parse_string("baseline"),
Ok(BaselineShift::Baseline)
);
assert_eq!(BaselineShift::parse_string("sub"), Ok(BaselineShift::Sub));
assert_eq!(
BaselineShift::parse_string("super"),
Ok(BaselineShift::Super)
);
assert_eq!(BaselineShift::parse_string("top"), Ok(BaselineShift::Top));
assert_eq!(
BaselineShift::parse_string("center"),
Ok(BaselineShift::Center)
);
assert_eq!(
BaselineShift::parse_string("bottom"),
Ok(BaselineShift::Bottom)
);
assert_eq!(
BaselineShift::parse_string("10%"),
Ok(BaselineShift::Percentage(Percentage(0.1)))
);
assert_eq!(
BaselineShift::parse_string("10em"),
Ok(BaselineShift::Length(LengthValue::Em(10.0)))
);
assert!(BaselineShift::parse_string("invalid").is_err());
}
#[derive(Clone, Debug, PartialEq)]
pub enum Clip {
Shape([Number; 4]),
Auto,
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for Clip {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| input.expect_ident_matching("auto").map(|()| Self::Auto))
.or_else(|_| {
input.expect_str("rect(")?;
input.skip_whitespace();
input.skip_char(',');
input.skip_whitespace();
let top = Number::parse(input)?;
input.skip_whitespace();
input.skip_char(',');
input.skip_whitespace();
let right = Number::parse(input)?;
input.skip_whitespace();
input.skip_char(',');
input.skip_whitespace();
let bottom = Number::parse(input)?;
input.skip_whitespace();
input.skip_char(',');
input.skip_whitespace();
let left = Number::parse(input)?;
input.skip_whitespace();
input.expect_char(')')?;
Ok(Self::Shape([top, right, bottom, left]))
})
}
}
#[cfg(feature = "serialize")]
impl ToValue for Clip {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Auto => dest.write_str("auto"),
Self::Shape([top, right, bottom, left]) => {
dest.write_str("rect(")?;
top.write_value(dest)?;
dest.write_char(',')?;
right.write_value(dest)?;
dest.write_char(',')?;
bottom.write_value(dest)?;
dest.write_char(',')?;
left.write_value(dest)?;
dest.write_char(')')
}
}
}
}
#[test]
fn clip() {
assert_eq!(
Clip::parse_string("rect(1, 2, 3, 4)"),
Ok(Clip::Shape([1.0, 2.0, 3.0, 4.0]))
);
assert_eq!(Clip::parse_string("auto"), Ok(Clip::Auto));
assert_eq!(
Clip::parse_string("circle(1, 2)"),
Err(Error::ExpectedString {
expected: "rect(",
received: "circl"
})
);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ColorProfile<'i> {
Auto,
SRGB,
NameOrIRI(Anything<'i>),
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for ColorProfile<'input> {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| {
let ident: &str = input.expect_ident().map_err(|_| ())?;
if ident.to_lowercase() == "srgb" {
return Ok(Self::SRGB);
}
Ok(match ident {
"auto" => Self::Auto,
_ => return Err(()),
})
})
.or_else(|()| Anything::parse(input).map(Self::NameOrIRI))
}
}
#[cfg(feature = "serialize")]
impl ToValue for ColorProfile<'_> {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Auto => dest.write_str("auto"),
Self::SRGB => dest.write_str("sRGB"),
Self::NameOrIRI(name) => name.write_value(dest),
}
}
}
#[test]
fn color_profile() {
assert_eq!(ColorProfile::parse_string("auto"), Ok(ColorProfile::Auto));
assert_eq!(ColorProfile::parse_string("srgb"), Ok(ColorProfile::SRGB));
assert_eq!(ColorProfile::parse_string("sRGB"), Ok(ColorProfile::SRGB));
assert_eq!(
ColorProfile::parse_string("name"),
Ok(ColorProfile::NameOrIRI("name".into()))
);
}
enum_attr!(
DominantBaseline {
Auto: "auto",
UseScript: "use-script",
NoChange: "no-change",
ResetSize: "reset-size",
Ideographic: "ideographic",
Alphabetic: "alphabetic",
Hanging: "hanging",
Mathematical: "mathematical",
Central: "central",
Middle: "middle",
TextAfterEdge: "text-after-edge",
TextBeforeEdge: "text-before-edge",
}
);
#[derive(Clone, Debug, PartialEq)]
pub enum EnableBackground {
Accumulate,
New(Option<(Number, Number, Number, Number)>),
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for EnableBackground {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| {
input
.expect_ident_matching("accumulate")
.map(|()| Self::Accumulate)
})
.or_else(|_| {
input.expect_ident_matching("new")?;
input.skip_whitespace();
if let Ok(x) = input.try_parse(Number::parse) {
input.skip_whitespace();
let y = Number::parse(input)?;
input.skip_whitespace();
let width = Number::parse(input)?;
input.skip_whitespace();
let height = Number::parse(input)?;
Ok(Self::New(Some((x, y, width, height))))
} else {
Ok(Self::New(None))
}
})
}
}
#[cfg(feature = "serialize")]
impl ToValue for EnableBackground {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Accumulate => dest.write_str("accumulate"),
Self::New(None) => dest.write_str("new"),
Self::New(Some((x, y, width, height))) => {
dest.write_str("new ")?;
x.write_value(dest)?;
dest.write_char(' ')?;
y.write_value(dest)?;
dest.write_char(' ')?;
width.write_value(dest)?;
dest.write_char(' ')?;
height.write_value(dest)
}
}
}
}
#[test]
fn enable_background() {
assert_eq!(
EnableBackground::parse_string("accumulate"),
Ok(EnableBackground::Accumulate)
);
assert_eq!(
EnableBackground::parse_string("new"),
Ok(EnableBackground::New(None))
);
assert_eq!(
EnableBackground::parse_string("new 1 2 3 4"),
Ok(EnableBackground::New(Some((1.0, 2.0, 3.0, 4.0))))
);
assert_eq!(
EnableBackground::parse_string("accumulate new"),
Err(Error::ExpectedDone)
);
assert_eq!(
EnableBackground::parse_string("new accumulate"),
Err(Error::ExpectedDone)
);
assert_eq!(
EnableBackground::parse_string("new 1 2 3"),
Err(Error::InvalidNumber)
);
assert_eq!(
EnableBackground::parse_string("new 1 2 3 4 5"),
Err(Error::ExpectedDone)
);
}
#[derive(Clone, Debug, PartialEq)]
pub struct FontFamily<'input>(
pub ListOf<lightningcss::properties::font::FontFamily<'input>, SpaceOrComma>,
);
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for FontFamily<'input> {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
Ok(Self(ListOf::parse(input)?))
}
}
#[cfg(feature = "serialize")]
impl ToValue for FontFamily<'_> {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.0.write_value(dest)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FontSizeAdjust {
Number(Number),
None,
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for FontSizeAdjust {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| input.expect_ident_matching("none").map(|()| Self::None))
.or_else(|_| Number::parse(input).map(Self::Number))
}
}
#[cfg(feature = "serialize")]
impl ToValue for FontSizeAdjust {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Number(number) => number.write_value(dest),
Self::None => dest.write_str("none"),
}
}
}
#[test]
fn font_size_adjust() {
assert_eq!(
FontSizeAdjust::parse_string("10"),
Ok(FontSizeAdjust::Number(10.0))
);
assert_eq!(
FontSizeAdjust::parse_string("none"),
Ok(FontSizeAdjust::None)
);
assert_eq!(
FontSizeAdjust::parse_string("invalid"),
Err(Error::InvalidNumber)
);
}
#[derive(Clone, Debug, PartialEq)]
pub enum FontVariant {
Normal,
None,
Some {
font_variant_ligatures: FontVariantLigatures,
font_variant_caps: Option<FontVariantCaps>,
font_variant_numeric: FontVariantNumeric,
font_variant_east_asian: FontVariantEastAsian,
font_variant_position: Option<FontVariantPosition>,
font_variant_emoji: Option<FontVariantEmoji>,
},
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for FontVariant {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| {
let str: &str = input.expect_ident().map_err(|_| ())?;
Ok(match str {
"normal" => Self::Normal,
"none" => Self::None,
_ => return Err(()),
})
})
.or_else(|()| {
let mut font_variant_ligatures: Option<FontVariantLigatures> = None;
let mut font_variant_caps: Option<FontVariantCaps> = None;
let mut font_variant_numeric: Option<FontVariantNumeric> = None;
let mut font_variant_east_asian: Option<FontVariantEastAsian> = None;
let mut font_variant_position: Option<FontVariantPosition> = None;
let mut font_variant_emoji: Option<FontVariantEmoji> = None;
loop {
input.skip_whitespace();
if font_variant_ligatures.is_none() {
if let Ok(value) = input.try_parse(FontVariantLigatures::parse) {
font_variant_ligatures = Some(value);
continue;
}
}
if font_variant_caps.is_none() {
if let Ok(value) = input.try_parse(FontVariantCaps::parse) {
font_variant_caps = Some(value);
continue;
}
}
if font_variant_numeric.is_none() {
if let Ok(value) = input.try_parse(FontVariantNumeric::parse) {
font_variant_numeric = Some(value);
continue;
}
}
if font_variant_east_asian.is_none() {
if let Ok(value) = input.try_parse(FontVariantEastAsian::parse) {
font_variant_east_asian = Some(value);
continue;
}
}
if font_variant_position.is_none() {
if let Ok(value) = input.try_parse(FontVariantPosition::parse) {
font_variant_position = Some(value);
continue;
}
}
if font_variant_emoji.is_none() {
if let Ok(value) = input.try_parse(FontVariantEmoji::parse) {
font_variant_emoji = Some(value);
continue;
}
}
break;
}
Ok(Self::Some {
font_variant_ligatures: font_variant_ligatures.unwrap_or_default(),
font_variant_caps,
font_variant_numeric: font_variant_numeric.unwrap_or_default(),
font_variant_east_asian: font_variant_east_asian.unwrap_or_default(),
font_variant_position,
font_variant_emoji,
})
})
}
}
#[cfg(feature = "serialize")]
impl ToValue for FontVariant {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Normal => dest.write_str("normal"),
Self::None => dest.write_str("none"),
Self::Some {
font_variant_ligatures,
font_variant_caps,
font_variant_numeric,
font_variant_east_asian,
font_variant_position,
font_variant_emoji,
} => {
let mut after = false;
if *font_variant_ligatures != FontVariantLigatures::default() {
font_variant_ligatures.write_value(dest)?;
after = true;
}
if let Some(value) = font_variant_caps {
if after {
dest.write_char(' ')?;
}
value.write_value(dest)?;
after = true;
}
if *font_variant_numeric != FontVariantNumeric::default() {
if after {
dest.write_char(' ')?;
}
font_variant_numeric.write_value(dest)?;
after = true;
}
if *font_variant_east_asian != FontVariantEastAsian::default() {
if after {
dest.write_char(' ')?;
}
font_variant_east_asian.write_value(dest)?;
after = true;
}
if let Some(value) = font_variant_position {
if after {
dest.write_char(' ')?;
}
value.write_value(dest)?;
after = true;
}
if let Some(value) = font_variant_emoji {
if after {
dest.write_char(' ')?;
}
value.write_value(dest)?;
}
Ok(())
}
}
}
}
#[test]
fn font_variant() {
assert_eq!(FontVariant::parse_string("normal"), Ok(FontVariant::Normal));
assert_eq!(FontVariant::parse_string("none"), Ok(FontVariant::None));
assert_eq!(
FontVariant::parse_string("no-common-ligatures proportional-nums"),
Ok(FontVariant::Some {
font_variant_ligatures: FontVariantLigatures {
common_lig_values: Some(CommonLigValues::NoCommonLigatures),
discretionary_lig_values: None,
historical_lig_values: None,
contextual_alt_values: None
},
font_variant_caps: None,
font_variant_numeric: FontVariantNumeric {
numeric_figure_values: None,
numeric_spacing_values: Some(NumericSpacingValues::ProportionalNums),
numeric_fraction_values: None,
ordinal: false,
slashed_zero: false
},
font_variant_east_asian: FontVariantEastAsian {
east_asian_variant_values: None,
east_asian_width_values: None,
ruby: false
},
font_variant_position: None,
font_variant_emoji: None
})
);
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FontVariantLigatures {
common_lig_values: Option<CommonLigValues>,
discretionary_lig_values: Option<DiscretionaryLigValues>,
historical_lig_values: Option<HistoricalLigValues>,
contextual_alt_values: Option<ContextualAltValues>,
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for FontVariantLigatures {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
let mut result = FontVariantLigatures {
common_lig_values: None,
discretionary_lig_values: None,
historical_lig_values: None,
contextual_alt_values: None,
};
loop {
if result.common_lig_values.is_none() {
if let Ok(value) = input.try_parse(CommonLigValues::parse) {
result.common_lig_values = Some(value);
continue;
}
}
if result.discretionary_lig_values.is_none() {
if let Ok(value) = input.try_parse(DiscretionaryLigValues::parse) {
result.discretionary_lig_values = Some(value);
continue;
}
}
if result.historical_lig_values.is_none() {
if let Ok(value) = input.try_parse(HistoricalLigValues::parse) {
result.historical_lig_values = Some(value);
continue;
}
}
if result.contextual_alt_values.is_none() {
if let Ok(value) = input.try_parse(ContextualAltValues::parse) {
result.contextual_alt_values = Some(value);
continue;
}
}
break;
}
Ok(result)
}
}
#[cfg(feature = "serialize")]
impl ToValue for FontVariantLigatures {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if let Some(value) = &self.common_lig_values {
value.write_value(dest)?;
}
if let Some(value) = &self.discretionary_lig_values {
value.write_value(dest)?;
}
if let Some(value) = &self.historical_lig_values {
value.write_value(dest)?;
}
if let Some(value) = &self.contextual_alt_values {
value.write_value(dest)?;
}
Ok(())
}
}
enum_attr!(
CommonLigValues {
CommonLigatures: "common-ligatures",
NoCommonLigatures: "no-common-ligatures",
}
);
enum_attr!(
DiscretionaryLigValues {
DiscretionaryLigatures: "discretionary-ligatures",
NoDiscretionaryLigatures: "no-discretionary-ligatures",
}
);
enum_attr!(
HistoricalLigValues {
HistoricalLigatures: "historical-ligatures",
NoHistoricalLigatures: "no-historical-ligatures" ,
}
);
enum_attr!(
ContextualAltValues {
Contextual: "contextual",
NoContextual: "no-contextual",
}
);
enum_attr!(
FontVariantCaps {
SmallCaps: "small-caps",
AllSmallCaps: "all-small-caps",
PetiteCaps: "petite-caps",
AllPetiteCaps: "all-petite-caps",
Unicase: "unicase",
TitlingCaps: "titling-caps",
}
);
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FontVariantNumeric {
numeric_figure_values: Option<NumericFigureValues>,
numeric_spacing_values: Option<NumericSpacingValues>,
numeric_fraction_values: Option<NumericFractionValues>,
ordinal: bool,
slashed_zero: bool,
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for FontVariantNumeric {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
let mut result = Self {
numeric_figure_values: None,
numeric_spacing_values: None,
numeric_fraction_values: None,
ordinal: false,
slashed_zero: false,
};
loop {
if result.numeric_figure_values.is_none() {
if let Ok(value) = input.try_parse(NumericFigureValues::parse) {
result.numeric_figure_values = Some(value);
continue;
}
}
if result.numeric_spacing_values.is_none() {
if let Ok(value) = input.try_parse(NumericSpacingValues::parse) {
result.numeric_spacing_values = Some(value);
continue;
}
}
if result.numeric_fraction_values.is_none() {
if let Ok(value) = input.try_parse(NumericFractionValues::parse) {
result.numeric_fraction_values = Some(value);
continue;
}
}
if !result.ordinal {
result.ordinal = input
.try_parse(|input| input.expect_ident_matching("ordinal"))
.is_ok();
if result.ordinal {
continue;
}
}
if !result.slashed_zero {
result.slashed_zero = input
.try_parse(|input| input.expect_ident_matching("slashed-zero"))
.is_ok();
if result.slashed_zero {
continue;
}
}
break;
}
Ok(result)
}
}
#[cfg(feature = "serialize")]
impl ToValue for FontVariantNumeric {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if let Some(value) = &self.numeric_figure_values {
value.write_value(dest)?;
}
if let Some(value) = &self.numeric_spacing_values {
value.write_value(dest)?;
}
if let Some(value) = &self.numeric_fraction_values {
value.write_value(dest)?;
}
if self.ordinal {
dest.write_str("ordinal")?;
}
if self.slashed_zero {
dest.write_str("slashed-zero")?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct GlyphOrientationHorizontal(pub Angle);
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for GlyphOrientationHorizontal {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| input.try_parse(Angle::parse).map(Self))
.or_else(|_| Number::parse(input).map(Angle::Deg).map(Self))
}
}
#[cfg(feature = "serialize")]
impl ToValue for GlyphOrientationHorizontal {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.0.write_value(dest)
}
}
#[test]
fn glyph_orientation_horizontal() {
assert_eq!(
GlyphOrientationHorizontal::parse_string("90deg"),
Ok(GlyphOrientationHorizontal(Angle::Deg(90.0)))
);
assert_eq!(
GlyphOrientationHorizontal::parse_string("90"),
Ok(GlyphOrientationHorizontal(Angle::Deg(90.0)))
);
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum GlyphOrientationVertical {
#[default]
Auto,
Angle(Angle),
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for GlyphOrientationVertical {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| input.expect_ident_matching("auto").map(|()| Self::Auto))
.or_else(|_| input.try_parse(Angle::parse).map(Self::Angle))
.or_else(|_| {
input
.try_parse(Number::parse)
.map(Angle::Deg)
.map(Self::Angle)
})
}
}
#[cfg(feature = "serialize")]
impl ToValue for GlyphOrientationVertical {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Auto => dest.write_str("auto"),
Self::Angle(angle) => angle.write_value(dest),
}
}
}
#[test]
fn glyph_orientation_vertical() {
assert_eq!(
GlyphOrientationVertical::parse_string("auto"),
Ok(GlyphOrientationVertical::Auto)
);
assert_eq!(
GlyphOrientationVertical::parse_string("90deg"),
Ok(GlyphOrientationVertical::Angle(Angle::Deg(90.0)))
);
assert_eq!(
GlyphOrientationVertical::parse_string("90"),
Ok(GlyphOrientationVertical::Angle(Angle::Deg(90.0)))
);
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Kerning {
#[default]
Auto,
Length(Length),
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for Kerning {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
input
.try_parse(|input| input.expect_ident_matching("auto").map(|()| Self::Auto))
.or_else(|_| Length::parse(input).map(Self::Length))
}
}
#[cfg(feature = "serialize")]
impl ToValue for Kerning {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Auto => dest.write_str("auto"),
Self::Length(length) => length.write_value(dest),
}
}
}
#[test]
fn kerning() {
assert_eq!(Kerning::parse_string("auto"), Ok(Kerning::Auto));
assert_eq!(
Kerning::parse_string("10em"),
Ok(Kerning::Length(Length::Length(LengthValue::Em(10.0))))
);
}
#[derive(Clone, Debug, PartialEq)]
pub struct LengthPercentage(pub lightningcss::values::length::LengthPercentage);
impl LengthPercentage {
pub fn px(val: f32) -> Self {
Self(lightningcss::values::length::LengthPercentage::px(val))
}
#[allow(non_snake_case)]
pub fn Percentage(percentage: Percentage) -> Self {
Self(lightningcss::values::length::LengthPercentage::Percentage(
percentage,
))
}
#[allow(non_snake_case)]
pub fn Length(length: LengthValue) -> Self {
Self(lightningcss::values::length::LengthPercentage::Dimension(
length,
))
}
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for LengthPercentage {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
lightningcss::values::length::LengthPercentage::parse(input).map(Self)
}
}
#[cfg(feature = "serialize")]
impl ToValue for LengthPercentage {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if let Self(lightningcss::values::length::LengthPercentage::Dimension(LengthValue::Px(
px,
))) = self
{
px.write_value(dest)
} else {
self.0.write_value(dest)
}
}
}
impl Deref for LengthPercentage {
type Target = lightningcss::values::length::LengthPercentage;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for LengthPercentage {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[test]
fn length_percentage() {
assert_eq!(
LengthPercentage::parse_string("10"),
Ok(LengthPercentage(
lightningcss::values::length::LengthPercentage::px(10.0)
))
);
assert_eq!(
LengthPercentage::parse_string("10em"),
Ok(LengthPercentage(
lightningcss::values::length::LengthPercentage::Dimension(LengthValue::Em(10.0))
))
);
assert_eq!(
LengthPercentage::parse_string("10%"),
Ok(LengthPercentage(
lightningcss::values::length::LengthPercentage::Percentage(Percentage(0.1))
))
);
}
#[derive(Clone, Debug, PartialEq)]
pub struct Mask<'input>(pub ListOf<lightningcss::properties::masking::Mask<'input>, SpaceOrComma>);
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for Mask<'input> {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
ListOf::parse(input).map(Self)
}
}
#[cfg(feature = "serialize")]
impl ToValue for Mask<'_> {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.0.write_value(dest)
}
}
enum_attr!(
NumericFigureValues {
LiningNums: "lining-nums",
OldstyleNums: "oldstyle-nums",
}
);
enum_attr!(
NumericSpacingValues {
ProportionalNums: "proportional-nums",
TabularNums: "tabular-nums",
}
);
enum_attr!(
NumericFractionValues {
DiagonalFractions: "diagonal-fractions",
StackedFractions: "stacked-fractions",
}
);
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FontVariantEastAsian {
east_asian_variant_values: Option<EastAsianVariantValues>,
east_asian_width_values: Option<EastAsianWidthValues>,
ruby: bool,
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for FontVariantEastAsian {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
let mut result = Self {
east_asian_variant_values: None,
east_asian_width_values: None,
ruby: false,
};
loop {
if result.east_asian_variant_values.is_none() {
if let Ok(value) = input.try_parse(EastAsianVariantValues::parse) {
result.east_asian_variant_values = Some(value);
continue;
}
}
if result.east_asian_width_values.is_none() {
if let Ok(value) = input.try_parse(EastAsianWidthValues::parse) {
result.east_asian_width_values = Some(value);
continue;
}
}
if !result.ruby {
result.ruby = input
.try_parse(|input| input.expect_ident_matching("ruby"))
.is_ok();
if result.ruby {
continue;
}
}
break;
}
Ok(result)
}
}
#[cfg(feature = "serialize")]
impl ToValue for FontVariantEastAsian {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if let Some(value) = &self.east_asian_variant_values {
value.write_value(dest)?;
}
if let Some(value) = &self.east_asian_width_values {
value.write_value(dest)?;
}
if self.ruby {
dest.write_str("ruby")?;
}
Ok(())
}
}
enum_attr!(
EastAsianVariantValues {
Jis78: "jis78",
Jis83: "jis83",
Jis90: "jis90",
Jis04: "jis04",
Simplified: "simplified",
Traditional: "traditional" ,
}
);
enum_attr!(
EastAsianWidthValues {
FullWidth: "full-width",
ProportionalWidth: "proportional-width",
}
);
enum_attr!(
FontVariantPosition {
Sub: "sub",
Super: "super",
}
);
enum_attr!(
FontVariantEmoji {
Text: "text",
Emoji: "emoji",
Unicode: "unicode",
}
);
enum_attr!(
Paint {
Stroke: "stroke",
Fill: "fill",
Markers: "markers",
}
);
#[derive(Debug, PartialEq, Clone)]
pub struct PaintOrder(pub SmallVec<[Paint; 3]>);
impl PaintOrder {
pub fn normal() -> Self {
Self(smallvec![Paint::Fill, Paint::Stroke, Paint::Markers])
}
pub fn is_normal(&self) -> bool {
let inner = &self.0;
inner.first().is_none_or(|a| *a == Paint::Fill)
&& inner.get(1).is_none_or(|b| *b == Paint::Stroke)
&& inner.get(2).is_none_or(|c| *c == Paint::Markers)
}
}
#[cfg(feature = "parse")]
impl<'input> Parse<'input> for PaintOrder {
fn parse<'t>(input: &mut Parser<'input>) -> Result<Self, Error<'input>> {
let normal = Self::normal();
if input
.try_parse(|input| input.expect_ident_matching("normal"))
.is_ok()
{
return Ok(normal);
}
let mut paint_order = SmallVec::with_capacity(3);
for _ in 0..3 {
if let Ok(paint) = input.try_parse(Paint::parse) {
input.skip_whitespace();
paint_order.push(paint);
} else {
break;
}
}
if paint_order.is_empty() {
return Err(Error::ExpectedIdent {
expected: "a set of paint-order values",
received: "nothing",
});
}
for paint in normal.0 {
if !paint_order.contains(&paint) {
paint_order.push(paint);
}
}
if paint_order.len() > 3 {
return Err(Error::InvalidRange);
}
debug_assert_eq!(paint_order.len(), 3);
Ok(Self(paint_order))
}
}
#[cfg(feature = "serialize")]
impl ToValue for PaintOrder {
fn write_value<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
assert_eq!(self.0.len(), 3);
if self.is_normal() {
return dest.write_str("normal");
}
self.0[0].write_value(dest)?;
dest.write_char(' ')?;
self.0[1].write_value(dest)?;
dest.write_char(' ')?;
self.0[2].write_value(dest)
}
}
#[test]
fn paint_order() {
assert_eq!(PaintOrder::parse_string("normal"), Ok(PaintOrder::normal()));
assert_eq!(
PaintOrder::parse_string("fill stroke markers"),
Ok(PaintOrder::normal())
);
assert_eq!(
PaintOrder::parse_string("stroke"),
Ok(PaintOrder(smallvec![
Paint::Stroke,
Paint::Fill,
Paint::Markers
]))
);
assert_eq!(
PaintOrder::parse_string("markers stroke fill"),
Ok(PaintOrder(smallvec![
Paint::Markers,
Paint::Stroke,
Paint::Fill,
]))
);
assert_eq!(
PaintOrder::parse_string(""),
Err(Error::ExpectedIdent {
expected: "a set of paint-order values",
received: "nothing"
})
);
assert_eq!(
PaintOrder::parse_string("stroke stroke fill"),
Err(Error::InvalidRange)
);
assert_eq!(
PaintOrder::parse_string("stroke fill markers stroke"),
Err(Error::ExpectedDone)
);
assert_eq!(
PaintOrder::parse_string("howdy pardner"),
Err(Error::ExpectedIdent {
expected: "a set of paint-order values",
received: "nothing"
})
);
}
pub type Position = lightningcss::values::position::Position;
enum_attr!(
PointerEvents {
Auto: "auto",
BoundingBox: "bounding-box",
VisiblePainted: "visiblePainted",
VisibleFill: "visibleFill",
VisibleStroke: "visibleStroke",
Visible: "visible",
Painted: "painted",
Fill: "fill",
Stroke: "stroke",
All: "all",
None: "none",
}
);
enum_attr!(
TextAnchor {
Start: "start",
Middle: "middle",
End: "end",
}
);
enum_attr!(
VectorEffect {
None: "none",
NonScalingStroke: "non-scaling-stroke",
NonScalingSize: "non-scaling-size",
NonRotation: "non-rotation",
FixedPosition: "fixed-position",
}
);
enum_attr!(
WritingMode {
LrTb: "lr-tb",
RlTb: "rl-tb",
TbRl: "tb-rl",
Lr: "lr",
Rl: "rl",
Tb: "tb",
HorizontalTb: "horizontal-tb",
VerticalRl: "vertical-rl",
VerticalLr: "vertical-lr",
SidewaysRl: "sideways-rl",
SidewaysLr: "sideways-lr",
}
);