use std::convert::TryInto;
use std::str::FromStr;
use cssparser::{Parser, Token};
use language_tags::LanguageTag;
use crate::dasharray::Dasharray;
use crate::error::*;
use crate::filter::FilterValueList;
use crate::font_props::{
Font, FontFamily, FontSize, FontWeight, GlyphOrientationVertical, LetterSpacing, LineHeight,
};
use crate::iri::Iri;
use crate::length::*;
use crate::paint_server::PaintServer;
use crate::parse_identifiers;
use crate::parsers::Parse;
use crate::properties::ComputedValues;
use crate::property_macros::Property;
use crate::rect::Rect;
use crate::transform::TransformProperty;
use crate::unit_interval::UnitInterval;
use crate::{impl_default, impl_property, make_property};
make_property!(
BaselineShift,
default: Length::<Both>::default(),
newtype: Length<Both>,
property_impl: {
impl Property for BaselineShift {
fn inherits_automatically() -> bool {
false
}
fn compute(&self, v: &ComputedValues) -> Self {
let font_size = v.font_size().value();
let parent = v.baseline_shift();
match (self.0.unit, parent.0.unit) {
(LengthUnit::Percent, _) => {
BaselineShift(Length::<Both>::new(self.0.length * font_size.length + parent.0.length, font_size.unit))
}
(x, y) if x == y || parent.0.length == 0.0 => {
BaselineShift(Length::<Both>::new(self.0.length + parent.0.length, self.0.unit))
}
_ => {
parent
}
}
}
}
},
parse_impl: {
impl Parse for BaselineShift {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<BaselineShift, crate::error::ParseError<'i>> {
parser.try_parse(|p| Ok(BaselineShift(Length::<Both>::parse(p)?)))
.or_else(|_: ParseError<'_>| {
Ok(parse_identifiers!(
parser,
"baseline" => BaselineShift(Length::<Both>::new(0.0, LengthUnit::Percent)),
"sub" => BaselineShift(Length::<Both>::new(-0.2, LengthUnit::Percent)),
"super" => BaselineShift(Length::<Both>::new(0.4, LengthUnit::Percent)),
)?)
})
}
}
}
);
make_property!(
ClipPath,
default: Iri::None,
inherits_automatically: false,
newtype_parse: Iri,
);
make_property!(
ClipRule,
default: NonZero,
inherits_automatically: true,
identifiers:
"nonzero" => NonZero,
"evenodd" => EvenOdd,
);
make_property!(
Color,
default: crate::color::Color::Rgba(crate::color::RGBA::new(0, 0, 0, 1.0)),
inherits_automatically: true,
newtype_parse: crate::color::Color,
);
make_property!(
ColorInterpolationFilters,
default: LinearRgb,
inherits_automatically: true,
identifiers:
"auto" => Auto,
"linearRGB" => LinearRgb,
"sRGB" => Srgb,
);
make_property!(
CX,
default: Length::<Horizontal>::default(),
inherits_automatically: false,
newtype_parse: Length<Horizontal>,
);
make_property!(
CY,
default: Length::<Vertical>::default(),
inherits_automatically: false,
newtype_parse: Length<Vertical>,
);
make_property!(
Direction,
default: Ltr,
inherits_automatically: true,
identifiers:
"ltr" => Ltr,
"rtl" => Rtl,
);
make_property!(
Display,
default: Inline,
inherits_automatically: false,
identifiers:
"inline" => Inline,
"block" => Block,
"list-item" => ListItem,
"run-in" => RunIn,
"compact" => Compact,
"marker" => Marker,
"table" => Table,
"inline-table" => InlineTable,
"table-row-group" => TableRowGroup,
"table-header-group" => TableHeaderGroup,
"table-footer-group" => TableFooterGroup,
"table-row" => TableRow,
"table-column-group" => TableColumnGroup,
"table-column" => TableColumn,
"table-cell" => TableCell,
"table-caption" => TableCaption,
"none" => None,
);
make_property!(
DominantBaseline,
default: Auto,
inherits_automatically: true,
identifiers:
"auto" => Auto,
"ideographic" => Ideographic,
"alphabetic" => Alphabetic,
"hanging" => Hanging,
"mathematical" => Mathematical,
"central" => Central,
"middle" => Middle,
"text-after-edge" => TextAfterEdge,
"text-before-edge" => TextBeforeEdge,
"text-top" => TextTop,
"text-bottom" => TextBottom,
);
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EnableBackground {
Accumulate,
New(Option<Rect>),
}
make_property!(
EnableBackground,
default: EnableBackground::Accumulate,
inherits_automatically: false,
parse_impl: {
impl Parse for EnableBackground {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
let loc = parser.current_source_location();
if parser
.try_parse(|p| p.expect_ident_matching("accumulate"))
.is_ok()
{
return Ok(EnableBackground::Accumulate);
}
if parser.try_parse(|p| p.expect_ident_matching("new")).is_ok() {
parser.try_parse(|p| -> Result<_, ParseError<'_>> {
let x = f64::parse(p)?;
let y = f64::parse(p)?;
let w = f64::parse(p)?;
let h = f64::parse(p)?;
Ok(EnableBackground::New(Some(Rect::new(x, y, x + w, y + h))))
}).or(Ok(EnableBackground::New(None)))
} else {
Err(loc.new_custom_error(ValueErrorKind::parse_error("invalid syntax for 'enable-background' property")))
}
}
}
}
);
#[cfg(test)]
#[test]
fn parses_enable_background() {
assert_eq!(
EnableBackground::parse_str("accumulate").unwrap(),
EnableBackground::Accumulate
);
assert_eq!(
EnableBackground::parse_str("new").unwrap(),
EnableBackground::New(None)
);
assert_eq!(
EnableBackground::parse_str("new 1 2 3 4").unwrap(),
EnableBackground::New(Some(Rect::new(1.0, 2.0, 4.0, 6.0)))
);
assert!(EnableBackground::parse_str("new foo").is_err());
assert!(EnableBackground::parse_str("plonk").is_err());
}
make_property!(
Fill,
default: PaintServer::SolidColor(crate::color::Color::Rgba(
crate::color::RGBA::new(0, 0, 0, 1.0)
)),
inherits_automatically: true,
newtype_parse: PaintServer,
);
make_property!(
FillOpacity,
default: UnitInterval(1.0),
inherits_automatically: true,
newtype_parse: UnitInterval,
);
make_property!(
FillRule,
default: NonZero,
inherits_automatically: true,
identifiers:
"nonzero" => NonZero,
"evenodd" => EvenOdd,
);
#[derive(Debug, Clone, PartialEq)]
pub enum Filter {
None,
List(FilterValueList),
}
make_property!(
Filter,
default: Filter::None,
inherits_automatically: false,
parse_impl: {
impl Parse for Filter {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
if parser
.try_parse(|p| p.expect_ident_matching("none"))
.is_ok()
{
return Ok(Filter::None);
}
Ok(Filter::List(FilterValueList::parse(parser)?))
}
}
}
);
make_property!(
FloodColor,
default: crate::color::Color::Rgba(crate::color::RGBA::new(0, 0, 0, 1.0)),
inherits_automatically: false,
newtype_parse: crate::color::Color,
);
make_property!(
FloodOpacity,
default: UnitInterval(1.0),
inherits_automatically: false,
newtype_parse: UnitInterval,
);
make_property!(
Font,
default: Font::Spec(Default::default()),
inherits_automatically: true,
);
make_property!(
FontFamily,
default: FontFamily("Times New Roman".to_string()),
inherits_automatically: true,
);
make_property!(
FontSize,
default: FontSize::Value(Length::<Both>::new(12.0, LengthUnit::Px)),
property_impl: {
impl Property for FontSize {
fn inherits_automatically() -> bool {
true
}
fn compute(&self, v: &ComputedValues) -> Self {
self.compute(v)
}
}
}
);
make_property!(
FontStretch,
default: Normal,
inherits_automatically: true,
identifiers:
"normal" => Normal,
"wider" => Wider,
"narrower" => Narrower,
"ultra-condensed" => UltraCondensed,
"extra-condensed" => ExtraCondensed,
"condensed" => Condensed,
"semi-condensed" => SemiCondensed,
"semi-expanded" => SemiExpanded,
"expanded" => Expanded,
"extra-expanded" => ExtraExpanded,
"ultra-expanded" => UltraExpanded,
);
make_property!(
FontStyle,
default: Normal,
inherits_automatically: true,
identifiers:
"normal" => Normal,
"italic" => Italic,
"oblique" => Oblique,
);
make_property!(
FontVariant,
default: Normal,
inherits_automatically: true,
identifiers:
"normal" => Normal,
"small-caps" => SmallCaps,
);
make_property!(
FontWeight,
default: FontWeight::Normal,
property_impl: {
impl Property for FontWeight {
fn inherits_automatically() -> bool {
true
}
fn compute(&self, v: &ComputedValues) -> Self {
self.compute(&v.font_weight())
}
}
}
);
make_property!(
GlyphOrientationVertical,
default: GlyphOrientationVertical::Auto,
inherits_automatically: false,
);
make_property!(
Height,
default: LengthOrAuto::<Vertical>::Auto,
inherits_automatically: false,
newtype_parse: LengthOrAuto<Vertical>,
);
make_property!(
ImageRendering,
default: Auto,
inherits_automatically: true,
identifiers:
"auto" => Auto,
"smooth" => Smooth,
"optimizeQuality" => OptimizeQuality,
"high-quality" => HighQuality,
"crisp-edges" => CrispEdges,
"optimizeSpeed" => OptimizeSpeed,
"pixelated" => Pixelated,
);
make_property!(
Isolation,
default: Auto,
inherits_automatically: false,
identifiers:
"auto" => Auto,
"isolate" => Isolate,
);
make_property!(
LetterSpacing,
default: LetterSpacing::Normal,
property_impl: {
impl Property for LetterSpacing {
fn inherits_automatically() -> bool {
true
}
fn compute(&self, _v: &ComputedValues) -> Self {
self.compute()
}
}
}
);
make_property!(
LineHeight,
default: LineHeight::Normal,
inherits_automatically: true,
);
make_property!(
LightingColor,
default: crate::color::Color::Rgba(crate::color::RGBA::new(255, 255, 255, 1.0)),
inherits_automatically: false,
newtype_parse: crate::color::Color,
);
make_property!(
Marker,
default: Iri::None,
inherits_automatically: true,
newtype_parse: Iri,
);
make_property!(
MarkerEnd,
default: Iri::None,
inherits_automatically: true,
newtype_parse: Iri,
);
make_property!(
MarkerMid,
default: Iri::None,
inherits_automatically: true,
newtype_parse: Iri,
);
make_property!(
MarkerStart,
default: Iri::None,
inherits_automatically: true,
newtype_parse: Iri,
);
make_property!(
Mask,
default: Iri::None,
inherits_automatically: false,
newtype_parse: Iri,
);
make_property!(
MaskType,
default: Luminance,
inherits_automatically: false,
identifiers:
"luminance" => Luminance,
"alpha" => Alpha,
);
make_property!(
MixBlendMode,
default: Normal,
inherits_automatically: false,
identifiers:
"normal" => Normal,
"multiply" => Multiply,
"screen" => Screen,
"overlay" => Overlay,
"darken" => Darken,
"lighten" => Lighten,
"color-dodge" => ColorDodge,
"color-burn" => ColorBurn,
"hard-light" => HardLight,
"soft-light" => SoftLight,
"difference" => Difference,
"exclusion" => Exclusion,
"hue" => Hue,
"saturation" => Saturation,
"color" => Color,
"luminosity" => Luminosity,
);
make_property!(
Opacity,
default: UnitInterval(1.0),
inherits_automatically: false,
newtype_parse: UnitInterval,
);
make_property!(
Overflow,
default: Visible,
inherits_automatically: false,
identifiers:
"visible" => Visible,
"hidden" => Hidden,
"scroll" => Scroll,
"auto" => Auto,
);
impl Overflow {
pub fn overflow_allowed(&self) -> bool {
matches!(*self, Overflow::Auto | Overflow::Visible)
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PaintTarget {
Fill,
Stroke,
Markers,
}
make_property!(
PaintOrder,
inherits_automatically: true,
fields: {
targets: [PaintTarget; 3], default: [PaintTarget::Fill, PaintTarget::Stroke, PaintTarget::Markers],
}
parse_impl: {
impl Parse for PaintOrder {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<PaintOrder, ParseError<'i>> {
let allowed_targets = 3;
let mut targets = Vec::with_capacity(allowed_targets);
if parser.try_parse(|p| p.expect_ident_matching("normal")).is_ok() {
return Ok(PaintOrder::default());
}
while !parser.is_exhausted() {
let loc = parser.current_source_location();
let token = parser.next()?;
let value = match token {
Token::Ident(cow) if cow.eq_ignore_ascii_case("fill") && !targets.contains(&PaintTarget::Fill) => PaintTarget::Fill,
Token::Ident(cow) if cow.eq_ignore_ascii_case("stroke") && !targets.contains(&PaintTarget::Stroke) => PaintTarget::Stroke,
Token::Ident(cow) if cow.eq_ignore_ascii_case("markers") && !targets.contains(&PaintTarget::Markers) => PaintTarget::Markers,
_ => return Err(loc.new_basic_unexpected_token_error(token.clone()).into()),
};
targets.push(value);
};
for &target in &[PaintTarget::Fill, PaintTarget::Stroke, PaintTarget::Markers] {
if !targets.contains(&target) {
targets.push(target);
}
}
Ok(PaintOrder {
targets: targets[..].try_into().expect("Incorrect number of targets in paint-order")
})
}
}
}
);
#[cfg(test)]
#[test]
fn parses_paint_order() {
assert_eq!(
PaintOrder::parse_str("normal").unwrap(),
PaintOrder {
targets: [PaintTarget::Fill, PaintTarget::Stroke, PaintTarget::Markers]
}
);
assert_eq!(
PaintOrder::parse_str("markers fill").unwrap(),
PaintOrder {
targets: [PaintTarget::Markers, PaintTarget::Fill, PaintTarget::Stroke]
}
);
assert_eq!(
PaintOrder::parse_str("stroke").unwrap(),
PaintOrder {
targets: [PaintTarget::Stroke, PaintTarget::Fill, PaintTarget::Markers]
}
);
assert!(PaintOrder::parse_str("stroke stroke").is_err());
assert!(PaintOrder::parse_str("markers stroke fill hello").is_err());
}
make_property!(
R,
default: ULength::<Both>::default(),
inherits_automatically: false,
newtype_parse: ULength<Both>,
);
make_property!(
RX,
default: LengthOrAuto::<Horizontal>::Auto,
inherits_automatically: false,
newtype_parse: LengthOrAuto<Horizontal>,
);
make_property!(
RY,
default: LengthOrAuto::<Vertical>::Auto,
inherits_automatically: false,
newtype_parse: LengthOrAuto<Vertical>,
);
make_property!(
ShapeRendering,
default: Auto,
inherits_automatically: true,
identifiers:
"auto" => Auto,
"optimizeSpeed" => OptimizeSpeed,
"geometricPrecision" => GeometricPrecision,
"crispEdges" => CrispEdges,
);
make_property!(
StopColor,
default: crate::color::Color::Rgba(crate::color::RGBA::new(0, 0, 0, 1.0)),
inherits_automatically: false,
newtype_parse: crate::color::Color,
);
make_property!(
StopOpacity,
default: UnitInterval(1.0),
inherits_automatically: false,
newtype_parse: UnitInterval,
);
make_property!(
Stroke,
default: PaintServer::None,
inherits_automatically: true,
newtype_parse: PaintServer,
);
make_property!(
StrokeDasharray,
default: Dasharray::default(),
inherits_automatically: true,
newtype_parse: Dasharray,
);
make_property!(
StrokeDashoffset,
default: Length::<Both>::default(),
inherits_automatically: true,
newtype_parse: Length<Both>,
);
make_property!(
StrokeLinecap,
default: Butt,
inherits_automatically: true,
identifiers:
"butt" => Butt,
"round" => Round,
"square" => Square,
);
make_property!(
StrokeLinejoin,
default: Miter,
inherits_automatically: true,
identifiers:
"miter" => Miter,
"round" => Round,
"bevel" => Bevel,
);
make_property!(
StrokeMiterlimit,
default: 4f64,
inherits_automatically: true,
newtype_parse: f64,
);
make_property!(
StrokeOpacity,
default: UnitInterval(1.0),
inherits_automatically: true,
newtype_parse: UnitInterval,
);
make_property!(
StrokeWidth,
default: Length::<Both>::new(1.0, LengthUnit::Px),
inherits_automatically: true,
newtype_parse: Length::<Both>,
);
make_property!(
TextAnchor,
default: Start,
inherits_automatically: true,
identifiers:
"start" => Start,
"middle" => Middle,
"end" => End,
);
make_property!(
TextDecoration,
inherits_automatically: false,
fields: {
overline: bool, default: false,
underline: bool, default: false,
strike: bool, default: false,
}
parse_impl: {
impl Parse for TextDecoration {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<TextDecoration, ParseError<'i>> {
let mut overline = false;
let mut underline = false;
let mut strike = false;
if parser.try_parse(|p| p.expect_ident_matching("none")).is_ok() {
return Ok(TextDecoration::default());
}
while !parser.is_exhausted() {
let loc = parser.current_source_location();
let token = parser.next()?;
match token {
Token::Ident(cow) if cow.eq_ignore_ascii_case("overline") => overline = true,
Token::Ident(cow) if cow.eq_ignore_ascii_case("underline") => underline = true,
Token::Ident(cow) if cow.eq_ignore_ascii_case("line-through") => strike = true,
_ => return Err(loc.new_basic_unexpected_token_error(token.clone()).into()),
}
}
Ok(TextDecoration {
overline,
underline,
strike,
})
}
}
}
);
#[cfg(test)]
#[test]
fn parses_text_decoration() {
assert_eq!(
TextDecoration::parse_str("none").unwrap(),
TextDecoration {
overline: false,
underline: false,
strike: false,
}
);
assert_eq!(
TextDecoration::parse_str("overline").unwrap(),
TextDecoration {
overline: true,
underline: false,
strike: false,
}
);
assert_eq!(
TextDecoration::parse_str("underline").unwrap(),
TextDecoration {
overline: false,
underline: true,
strike: false,
}
);
assert_eq!(
TextDecoration::parse_str("line-through").unwrap(),
TextDecoration {
overline: false,
underline: false,
strike: true,
}
);
assert_eq!(
TextDecoration::parse_str("underline overline").unwrap(),
TextDecoration {
overline: true,
underline: true,
strike: false,
}
);
assert!(TextDecoration::parse_str("airline").is_err())
}
make_property!(
TextOrientation,
default: Mixed,
inherits_automatically: true,
identifiers:
"mixed" => Mixed,
"upright" => Upright,
"sideways" => Sideways,
);
impl From<GlyphOrientationVertical> for TextOrientation {
fn from(o: GlyphOrientationVertical) -> TextOrientation {
match o {
GlyphOrientationVertical::Auto => TextOrientation::Mixed,
GlyphOrientationVertical::Angle0 => TextOrientation::Upright,
GlyphOrientationVertical::Angle90 => TextOrientation::Sideways,
}
}
}
make_property!(
TextRendering,
default: Auto,
inherits_automatically: true,
identifiers:
"auto" => Auto,
"optimizeSpeed" => OptimizeSpeed,
"optimizeLegibility" => OptimizeLegibility,
"geometricPrecision" => GeometricPrecision,
);
make_property!(
#[allow(unused)]
Transform,
default: TransformProperty::None,
inherits_automatically: false,
newtype_parse: TransformProperty,
);
make_property!(
UnicodeBidi,
default: Normal,
inherits_automatically: false,
identifiers:
"normal" => Normal,
"embed" => Embed,
"isolate" => Isolate,
"bidi-override" => BidiOverride,
"isolate-override" => IsolateOverride,
"plaintext" => Plaintext,
);
make_property!(
VectorEffect,
default: None,
inherits_automatically: false,
identifiers:
"none" => None,
"non-scaling-stroke" => NonScalingStroke,
);
make_property!(
Visibility,
default: Visible,
inherits_automatically: true,
identifiers:
"visible" => Visible,
"hidden" => Hidden,
"collapse" => Collapse,
);
make_property!(
Width,
default: LengthOrAuto::<Horizontal>::Auto,
inherits_automatically: false,
newtype_parse: LengthOrAuto<Horizontal>,
);
make_property!(
WritingMode,
default: HorizontalTb,
identifiers: {
"horizontal-tb" => HorizontalTb,
"vertical-rl" => VerticalRl,
"vertical-lr" => VerticalLr,
"lr" => Lr,
"lr-tb" => LrTb,
"rl" => Rl,
"rl-tb" => RlTb,
"tb" => Tb,
"tb-rl" => TbRl,
},
property_impl: {
impl Property for WritingMode {
fn inherits_automatically() -> bool {
true
}
fn compute(&self, _v: &ComputedValues) -> Self {
use WritingMode::*;
match *self {
Lr | LrTb | Rl | RlTb => HorizontalTb,
Tb | TbRl => VerticalRl,
_ => *self,
}
}
}
}
);
impl WritingMode {
pub fn is_horizontal(self) -> bool {
use WritingMode::*;
matches!(self, HorizontalTb | Lr | LrTb | Rl | RlTb)
}
}
make_property!(
X,
default: Length::<Horizontal>::default(),
inherits_automatically: false,
newtype_parse: Length<Horizontal>,
);
make_property!(
XmlLang,
default: None,
inherits_automatically: true,
newtype: Option<Box<LanguageTag>>,
parse_impl: {
impl Parse for XmlLang {
fn parse<'i>(
parser: &mut Parser<'i, '_>,
) -> Result<XmlLang, ParseError<'i>> {
let language_tag = parser.expect_ident()?;
let language_tag = LanguageTag::from_str(language_tag).map_err(|_| {
parser.new_custom_error(ValueErrorKind::parse_error("invalid syntax for 'xml:lang' parameter"))
})?;
Ok(XmlLang(Some(Box::new(language_tag))))
}
}
},
);
#[cfg(test)]
#[test]
fn parses_xml_lang() {
assert_eq!(
XmlLang::parse_str("es-MX").unwrap(),
XmlLang(Some(Box::new(LanguageTag::from_str("es-MX").unwrap())))
);
assert!(XmlLang::parse_str("").is_err());
}
make_property!(
XmlSpace,
default: Default,
inherits_automatically: true,
identifiers:
"default" => Default,
"preserve" => Preserve,
);
make_property!(
Y,
default: Length::<Vertical>::default(),
inherits_automatically: false,
newtype_parse: Length<Vertical>,
);
make_property!(
WhiteSpace,
default: Normal,
inherits_automatically: true,
identifiers:
"normal" => Normal,
"pre" => Pre,
"nowrap" => NoWrap,
"pre-wrap" => PreWrap,
"break-spaces" => BreakSpaces,
"pre-line" => PreLine,
);