441 lines
12 KiB
Rust
441 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 https://mozilla.org/MPL/2.0/. */
|
|
|
|
//! Specified types for SVG properties.
|
|
|
|
use crate::parser::{Parse, ParserContext};
|
|
use crate::values::generics::svg as generic;
|
|
use crate::values::specified::color::Color;
|
|
use crate::values::specified::url::SpecifiedUrl;
|
|
use crate::values::specified::AllowQuirks;
|
|
use crate::values::specified::LengthPercentage;
|
|
use crate::values::specified::SVGPathData;
|
|
use crate::values::specified::{NonNegativeLengthPercentage, Opacity};
|
|
use crate::values::CustomIdent;
|
|
use cssparser::{Parser, Token};
|
|
use std::fmt::{self, Write};
|
|
use style_traits::{CommaWithSpace, CssWriter, ParseError, Separator};
|
|
use style_traits::{StyleParseErrorKind, ToCss};
|
|
|
|
/// Specified SVG Paint value
|
|
pub type SVGPaint = generic::GenericSVGPaint<Color, SpecifiedUrl>;
|
|
|
|
/// <length> | <percentage> | <number> | context-value
|
|
pub type SVGLength = generic::GenericSVGLength<LengthPercentage>;
|
|
|
|
/// A non-negative version of SVGLength.
|
|
pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>;
|
|
|
|
/// [ <length> | <percentage> | <number> ]# | context-value
|
|
pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>;
|
|
|
|
/// Whether the `context-value` value is enabled.
|
|
#[cfg(feature = "gecko")]
|
|
pub fn is_context_value_enabled() -> bool {
|
|
static_prefs::pref!("gfx.font_rendering.opentype_svg.enabled")
|
|
}
|
|
|
|
/// Whether the `context-value` value is enabled.
|
|
#[cfg(not(feature = "gecko"))]
|
|
pub fn is_context_value_enabled() -> bool {
|
|
false
|
|
}
|
|
|
|
macro_rules! parse_svg_length {
|
|
($ty:ty, $lp:ty) => {
|
|
impl Parse for $ty {
|
|
fn parse<'i, 't>(
|
|
context: &ParserContext,
|
|
input: &mut Parser<'i, 't>,
|
|
) -> Result<Self, ParseError<'i>> {
|
|
if let Ok(lp) =
|
|
input.try_parse(|i| <$lp>::parse_quirky(context, i, AllowQuirks::Always))
|
|
{
|
|
return Ok(generic::SVGLength::LengthPercentage(lp));
|
|
}
|
|
|
|
try_match_ident_ignore_ascii_case! { input,
|
|
"context-value" if is_context_value_enabled() => {
|
|
Ok(generic::SVGLength::ContextValue)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
parse_svg_length!(SVGLength, LengthPercentage);
|
|
parse_svg_length!(SVGWidth, NonNegativeLengthPercentage);
|
|
|
|
impl Parse for SVGStrokeDashArray {
|
|
fn parse<'i, 't>(
|
|
context: &ParserContext,
|
|
input: &mut Parser<'i, 't>,
|
|
) -> Result<Self, ParseError<'i>> {
|
|
if let Ok(values) = input.try_parse(|i| {
|
|
CommaWithSpace::parse(i, |i| {
|
|
NonNegativeLengthPercentage::parse_quirky(context, i, AllowQuirks::Always)
|
|
})
|
|
}) {
|
|
return Ok(generic::SVGStrokeDashArray::Values(values.into()));
|
|
}
|
|
|
|
try_match_ident_ignore_ascii_case! { input,
|
|
"context-value" if is_context_value_enabled() => {
|
|
Ok(generic::SVGStrokeDashArray::ContextValue)
|
|
},
|
|
"none" => Ok(generic::SVGStrokeDashArray::Values(Default::default())),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <opacity-value> | context-fill-opacity | context-stroke-opacity
|
|
pub type SVGOpacity = generic::SVGOpacity<Opacity>;
|
|
|
|
/// The specified value for a single CSS paint-order property.
|
|
#[repr(u8)]
|
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)]
|
|
pub enum PaintOrder {
|
|
/// `normal` variant
|
|
Normal = 0,
|
|
/// `fill` variant
|
|
Fill = 1,
|
|
/// `stroke` variant
|
|
Stroke = 2,
|
|
/// `markers` variant
|
|
Markers = 3,
|
|
}
|
|
|
|
/// Number of non-normal components
|
|
pub const PAINT_ORDER_COUNT: u8 = 3;
|
|
|
|
/// Number of bits for each component
|
|
pub const PAINT_ORDER_SHIFT: u8 = 2;
|
|
|
|
/// Mask with above bits set
|
|
pub const PAINT_ORDER_MASK: u8 = 0b11;
|
|
|
|
/// The specified value is tree `PaintOrder` values packed into the
|
|
/// bitfields below, as a six-bit field, of 3 two-bit pairs
|
|
///
|
|
/// Each pair can be set to FILL, STROKE, or MARKERS
|
|
/// Lowest significant bit pairs are highest priority.
|
|
/// `normal` is the empty bitfield. The three pairs are
|
|
/// never zero in any case other than `normal`.
|
|
///
|
|
/// Higher priority values, i.e. the values specified first,
|
|
/// will be painted first (and may be covered by paintings of lower priority)
|
|
#[derive(
|
|
Clone,
|
|
Copy,
|
|
Debug,
|
|
MallocSizeOf,
|
|
PartialEq,
|
|
SpecifiedValueInfo,
|
|
ToComputedValue,
|
|
ToResolvedValue,
|
|
ToShmem,
|
|
)]
|
|
#[repr(transparent)]
|
|
pub struct SVGPaintOrder(pub u8);
|
|
|
|
impl SVGPaintOrder {
|
|
/// Get default `paint-order` with `0`
|
|
pub fn normal() -> Self {
|
|
SVGPaintOrder(0)
|
|
}
|
|
|
|
/// Get variant of `paint-order`
|
|
pub fn order_at(&self, pos: u8) -> PaintOrder {
|
|
match (self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK {
|
|
0 => PaintOrder::Normal,
|
|
1 => PaintOrder::Fill,
|
|
2 => PaintOrder::Stroke,
|
|
3 => PaintOrder::Markers,
|
|
_ => unreachable!("this cannot happen"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parse for SVGPaintOrder {
|
|
fn parse<'i, 't>(
|
|
_context: &ParserContext,
|
|
input: &mut Parser<'i, 't>,
|
|
) -> Result<SVGPaintOrder, ParseError<'i>> {
|
|
if let Ok(()) = input.try_parse(|i| i.expect_ident_matching("normal")) {
|
|
return Ok(SVGPaintOrder::normal());
|
|
}
|
|
|
|
let mut value = 0;
|
|
// bitfield representing what we've seen so far
|
|
// bit 1 is fill, bit 2 is stroke, bit 3 is markers
|
|
let mut seen = 0;
|
|
let mut pos = 0;
|
|
|
|
loop {
|
|
let result: Result<_, ParseError> = input.try_parse(|input| {
|
|
try_match_ident_ignore_ascii_case! { input,
|
|
"fill" => Ok(PaintOrder::Fill),
|
|
"stroke" => Ok(PaintOrder::Stroke),
|
|
"markers" => Ok(PaintOrder::Markers),
|
|
}
|
|
});
|
|
|
|
match result {
|
|
Ok(val) => {
|
|
if (seen & (1 << val as u8)) != 0 {
|
|
// don't parse the same ident twice
|
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
|
}
|
|
|
|
value |= (val as u8) << (pos * PAINT_ORDER_SHIFT);
|
|
seen |= 1 << (val as u8);
|
|
pos += 1;
|
|
},
|
|
Err(_) => break,
|
|
}
|
|
}
|
|
|
|
if value == 0 {
|
|
// Couldn't find any keyword
|
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
|
}
|
|
|
|
// fill in rest
|
|
for i in pos..PAINT_ORDER_COUNT {
|
|
for paint in 1..(PAINT_ORDER_COUNT + 1) {
|
|
// if not seen, set bit at position, mark as seen
|
|
if (seen & (1 << paint)) == 0 {
|
|
seen |= 1 << paint;
|
|
value |= paint << (i * PAINT_ORDER_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(SVGPaintOrder(value))
|
|
}
|
|
}
|
|
|
|
impl ToCss for SVGPaintOrder {
|
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
where
|
|
W: Write,
|
|
{
|
|
if self.0 == 0 {
|
|
return dest.write_str("normal");
|
|
}
|
|
|
|
let mut last_pos_to_serialize = 0;
|
|
for i in (1..PAINT_ORDER_COUNT).rev() {
|
|
let component = self.order_at(i);
|
|
let earlier_component = self.order_at(i - 1);
|
|
if component < earlier_component {
|
|
last_pos_to_serialize = i - 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for pos in 0..last_pos_to_serialize + 1 {
|
|
if pos != 0 {
|
|
dest.write_char(' ')?
|
|
}
|
|
self.order_at(pos).to_css(dest)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// The context properties we understand.
|
|
#[derive(
|
|
Clone,
|
|
Copy,
|
|
Eq,
|
|
Debug,
|
|
Default,
|
|
MallocSizeOf,
|
|
PartialEq,
|
|
SpecifiedValueInfo,
|
|
ToComputedValue,
|
|
ToResolvedValue,
|
|
ToShmem,
|
|
)]
|
|
#[repr(C)]
|
|
pub struct ContextPropertyBits(u8);
|
|
bitflags! {
|
|
impl ContextPropertyBits: u8 {
|
|
/// `fill`
|
|
const FILL = 1 << 0;
|
|
/// `stroke`
|
|
const STROKE = 1 << 1;
|
|
/// `fill-opacity`
|
|
const FILL_OPACITY = 1 << 2;
|
|
/// `stroke-opacity`
|
|
const STROKE_OPACITY = 1 << 3;
|
|
}
|
|
}
|
|
|
|
/// Specified MozContextProperties value.
|
|
/// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)
|
|
#[derive(
|
|
Clone,
|
|
Debug,
|
|
Default,
|
|
MallocSizeOf,
|
|
PartialEq,
|
|
SpecifiedValueInfo,
|
|
ToComputedValue,
|
|
ToCss,
|
|
ToResolvedValue,
|
|
ToShmem,
|
|
)]
|
|
#[repr(C)]
|
|
pub struct MozContextProperties {
|
|
#[css(iterable, if_empty = "none")]
|
|
#[ignore_malloc_size_of = "Arc"]
|
|
idents: crate::ArcSlice<CustomIdent>,
|
|
#[css(skip)]
|
|
bits: ContextPropertyBits,
|
|
}
|
|
|
|
impl Parse for MozContextProperties {
|
|
fn parse<'i, 't>(
|
|
_context: &ParserContext,
|
|
input: &mut Parser<'i, 't>,
|
|
) -> Result<MozContextProperties, ParseError<'i>> {
|
|
let mut values = vec![];
|
|
let mut bits = ContextPropertyBits::empty();
|
|
loop {
|
|
{
|
|
let location = input.current_source_location();
|
|
let ident = input.expect_ident()?;
|
|
|
|
if ident.eq_ignore_ascii_case("none") && values.is_empty() {
|
|
return Ok(Self::default());
|
|
}
|
|
|
|
let ident = CustomIdent::from_ident(location, ident, &["all", "none", "auto"])?;
|
|
|
|
if ident.0 == atom!("fill") {
|
|
bits.insert(ContextPropertyBits::FILL);
|
|
} else if ident.0 == atom!("stroke") {
|
|
bits.insert(ContextPropertyBits::STROKE);
|
|
} else if ident.0 == atom!("fill-opacity") {
|
|
bits.insert(ContextPropertyBits::FILL_OPACITY);
|
|
} else if ident.0 == atom!("stroke-opacity") {
|
|
bits.insert(ContextPropertyBits::STROKE_OPACITY);
|
|
}
|
|
|
|
values.push(ident);
|
|
}
|
|
|
|
let location = input.current_source_location();
|
|
match input.next() {
|
|
Ok(&Token::Comma) => continue,
|
|
Err(..) => break,
|
|
Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
|
|
}
|
|
}
|
|
|
|
if values.is_empty() {
|
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
|
}
|
|
|
|
Ok(MozContextProperties {
|
|
idents: crate::ArcSlice::from_iter(values.into_iter()),
|
|
bits,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// The svg d property type.
|
|
///
|
|
/// https://svgwg.org/svg2-draft/paths.html#TheDProperty
|
|
#[derive(
|
|
Animate,
|
|
Clone,
|
|
ComputeSquaredDistance,
|
|
Debug,
|
|
Deserialize,
|
|
MallocSizeOf,
|
|
PartialEq,
|
|
Serialize,
|
|
SpecifiedValueInfo,
|
|
ToAnimatedValue,
|
|
ToAnimatedZero,
|
|
ToComputedValue,
|
|
ToCss,
|
|
ToResolvedValue,
|
|
ToShmem,
|
|
)]
|
|
#[repr(C, u8)]
|
|
pub enum DProperty {
|
|
/// Path value for path(<string>) or just a <string>.
|
|
#[css(function)]
|
|
Path(SVGPathData),
|
|
/// None value.
|
|
#[animation(error)]
|
|
None,
|
|
}
|
|
|
|
impl DProperty {
|
|
/// return none.
|
|
#[inline]
|
|
pub fn none() -> Self {
|
|
DProperty::None
|
|
}
|
|
}
|
|
|
|
impl Parse for DProperty {
|
|
fn parse<'i, 't>(
|
|
context: &ParserContext,
|
|
input: &mut Parser<'i, 't>,
|
|
) -> Result<Self, ParseError<'i>> {
|
|
// Parse none.
|
|
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
|
|
return Ok(DProperty::none());
|
|
}
|
|
|
|
// Parse possible functions.
|
|
input.expect_function_matching("path")?;
|
|
let path_data = input.parse_nested_block(|i| Parse::parse(context, i))?;
|
|
Ok(DProperty::Path(path_data))
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Clone,
|
|
Copy,
|
|
Debug,
|
|
Default,
|
|
Eq,
|
|
MallocSizeOf,
|
|
Parse,
|
|
PartialEq,
|
|
SpecifiedValueInfo,
|
|
ToComputedValue,
|
|
ToCss,
|
|
ToResolvedValue,
|
|
ToShmem,
|
|
)]
|
|
#[css(bitflags(single = "none", mixed = "non-scaling-stroke"))]
|
|
#[repr(C)]
|
|
/// https://svgwg.org/svg2-draft/coords.html#VectorEffects
|
|
pub struct VectorEffect(u8);
|
|
bitflags! {
|
|
impl VectorEffect: u8 {
|
|
/// `none`
|
|
const NONE = 0;
|
|
/// `non-scaling-stroke`
|
|
const NON_SCALING_STROKE = 1 << 0;
|
|
}
|
|
}
|
|
|
|
impl VectorEffect {
|
|
/// Returns the initial value of vector-effect
|
|
#[inline]
|
|
pub fn none() -> Self {
|
|
Self::NONE
|
|
}
|
|
}
|