forked from mirrors/gecko-dev
Bug 1429298 - Part 2: Define offset-path and implement it in style system. r=emilio
Define OffsetPath & SVGPathData on the servo-side, and StyleMotion & StyleSVGPath on the gecko-side. We parse the SVG Path string into a vector of PathCommand. To build the gfx::Path, we will convert it into gfx::Path later in a different patch. The basic flow is: * Parse SVG Path String into SVGPathData (in Rust). * Use cbindgen to make sure the layout of PathCommand and StylePathCommand, and then set the Box[PathCommand] into nsTArray<StylePathCommand>. * Try to convert nsTArray<StylePathCommand> into gfx::Path. (This part will be implemented in a different patch.) Finally, we use the gfx::Path to create a motion path transform. The layout implementation is in the later patch. Differential Revision: https://phabricator.services.mozilla.com/D2963
This commit is contained in:
parent
a3bac78905
commit
a8bd6dfc8a
20 changed files with 923 additions and 5 deletions
|
|
@ -210,6 +210,7 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
|
|||
"min-block-size",
|
||||
"-moz-min-font-size-ratio",
|
||||
"min-inline-size",
|
||||
"offset-path",
|
||||
"padding-block-end",
|
||||
"padding-block-start",
|
||||
"padding-inline-end",
|
||||
|
|
|
|||
|
|
@ -2949,6 +2949,7 @@ exports.CSS_PROPERTIES = {
|
|||
"rotate",
|
||||
"scale",
|
||||
"translate",
|
||||
"offset-path",
|
||||
"scroll-behavior",
|
||||
"scroll-snap-type-x",
|
||||
"scroll-snap-type-y",
|
||||
|
|
@ -9331,6 +9332,10 @@ exports.PREFERENCES = [
|
|||
"font-variation-settings",
|
||||
"layout.css.font-variations.enabled"
|
||||
],
|
||||
[
|
||||
"offset-path",
|
||||
"layout.css.motion-path.enabled"
|
||||
],
|
||||
[
|
||||
"rotate",
|
||||
"layout.css.individual-transform.enabled"
|
||||
|
|
|
|||
|
|
@ -2409,6 +2409,10 @@ nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
|
|||
MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have URL source type!");
|
||||
return;
|
||||
|
||||
case StyleShapeSourceType::Path:
|
||||
MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have Path source type!");
|
||||
return;
|
||||
|
||||
case StyleShapeSourceType::Image: {
|
||||
float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
|
||||
mShapeInfo = ShapeInfo::CreateImageShape(shapeOutside.GetShapeImage(),
|
||||
|
|
|
|||
|
|
@ -1950,6 +1950,35 @@ Gecko_NewShapeImage(mozilla::StyleShapeSource* aShape)
|
|||
aShape->SetShapeImage(MakeUnique<nsStyleImage>());
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* aShape)
|
||||
{
|
||||
MOZ_ASSERT(aShape);
|
||||
aShape->SetPath(MakeUnique<mozilla::StyleSVGPath>());
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_SetStyleMotion(UniquePtr<mozilla::StyleMotion>* aMotion,
|
||||
mozilla::StyleMotion* aValue)
|
||||
{
|
||||
MOZ_ASSERT(aMotion);
|
||||
aMotion->reset(aValue);
|
||||
}
|
||||
|
||||
mozilla::StyleMotion*
|
||||
Gecko_NewStyleMotion()
|
||||
{
|
||||
return new StyleMotion();
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
|
||||
const mozilla::StyleMotion* aOther)
|
||||
{
|
||||
MOZ_ASSERT(aMotion);
|
||||
*aMotion = aOther ? MakeUnique<StyleMotion>(*aOther) : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -524,6 +524,12 @@ void Gecko_NewBasicShape(mozilla::StyleShapeSource* shape,
|
|||
mozilla::StyleBasicShapeType type);
|
||||
void Gecko_NewShapeImage(mozilla::StyleShapeSource* shape);
|
||||
void Gecko_StyleShapeSource_SetURLValue(mozilla::StyleShapeSource* shape, mozilla::css::URLValue* uri);
|
||||
void Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* shape);
|
||||
void Gecko_SetStyleMotion(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
|
||||
mozilla::StyleMotion* aValue);
|
||||
mozilla::StyleMotion* Gecko_NewStyleMotion();
|
||||
void Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* motion,
|
||||
const mozilla::StyleMotion* other);
|
||||
|
||||
void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
|
||||
void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@ structs-types = [
|
|||
"mozilla::FontWeight",
|
||||
"mozilla::MallocSizeOf",
|
||||
"mozilla::OriginFlags",
|
||||
"mozilla::StyleMotion",
|
||||
"mozilla::UniquePtr",
|
||||
"mozilla::StyleDisplayMode",
|
||||
"ServoRawOffsetArc",
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ SERIALIZED_PREDEFINED_TYPES = [
|
|||
"NonNegativeLength",
|
||||
"NonNegativeLengthOrPercentage",
|
||||
"ListStyleType",
|
||||
"OffsetPath",
|
||||
"Opacity",
|
||||
"Resize",
|
||||
"url::ImageUrlOrNone",
|
||||
|
|
|
|||
|
|
@ -5048,6 +5048,11 @@ nsComputedDOMStyle::GetShapeSource(
|
|||
SetValueToStyleImage(*aShapeSource.GetShapeImage(), val);
|
||||
return val.forget();
|
||||
}
|
||||
case StyleShapeSourceType::Path: {
|
||||
// Bug 1246764: we have to support this for clip-path. For now, no one
|
||||
// uses this.
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected SVG Path type.");
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ enum class StyleShapeSourceType : uint8_t {
|
|||
Image, // shape-outside only
|
||||
Shape,
|
||||
Box,
|
||||
Path, // SVG path function
|
||||
};
|
||||
|
||||
// -moz-stack-sizing
|
||||
|
|
|
|||
|
|
@ -1039,6 +1039,9 @@ StyleShapeSource::operator==(const StyleShapeSource& aOther) const
|
|||
|
||||
case StyleShapeSourceType::Box:
|
||||
return mReferenceBox == aOther.mReferenceBox;
|
||||
|
||||
case StyleShapeSourceType::Path:
|
||||
return *mSVGPath == *aOther.mSVGPath;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected shape source type!");
|
||||
|
|
@ -1090,6 +1093,15 @@ StyleShapeSource::SetBasicShape(UniquePtr<StyleBasicShape> aBasicShape,
|
|||
mType = StyleShapeSourceType::Shape;
|
||||
}
|
||||
|
||||
void
|
||||
StyleShapeSource::SetPath(UniquePtr<StyleSVGPath> aPath)
|
||||
{
|
||||
MOZ_ASSERT(aPath);
|
||||
DoDestroy();
|
||||
new (&mSVGPath) UniquePtr<StyleSVGPath>(std::move(aPath));
|
||||
mType = StyleShapeSourceType::Path;
|
||||
}
|
||||
|
||||
void
|
||||
StyleShapeSource::SetReferenceBox(StyleGeometryBox aReferenceBox)
|
||||
{
|
||||
|
|
@ -1123,6 +1135,10 @@ StyleShapeSource::DoCopy(const StyleShapeSource& aOther)
|
|||
case StyleShapeSourceType::Box:
|
||||
SetReferenceBox(aOther.GetReferenceBox());
|
||||
break;
|
||||
|
||||
case StyleShapeSourceType::Path:
|
||||
SetPath(MakeUnique<StyleSVGPath>(*aOther.GetPath()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1137,6 +1153,9 @@ StyleShapeSource::DoDestroy()
|
|||
case StyleShapeSourceType::URL:
|
||||
mShapeImage.~UniquePtr<nsStyleImage>();
|
||||
break;
|
||||
case StyleShapeSourceType::Path:
|
||||
mSVGPath.~UniquePtr<StyleSVGPath>();
|
||||
break;
|
||||
case StyleShapeSourceType::None:
|
||||
case StyleShapeSourceType::Box:
|
||||
// Not a union type, so do nothing.
|
||||
|
|
@ -3617,6 +3636,9 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
|
|||
, mSpecifiedRotate(aSource.mSpecifiedRotate)
|
||||
, mSpecifiedTranslate(aSource.mSpecifiedTranslate)
|
||||
, mSpecifiedScale(aSource.mSpecifiedScale)
|
||||
, mMotion(aSource.mMotion
|
||||
? MakeUnique<StyleMotion>(*aSource.mMotion)
|
||||
: nullptr)
|
||||
, mCombinedTransform(aSource.mCombinedTransform)
|
||||
, mTransformOrigin{ aSource.mTransformOrigin[0],
|
||||
aSource.mTransformOrigin[1],
|
||||
|
|
@ -3735,6 +3757,29 @@ CompareTransformValues(const RefPtr<nsCSSValueSharedList>& aList,
|
|||
return result;
|
||||
}
|
||||
|
||||
static inline nsChangeHint
|
||||
CompareMotionValues(const StyleMotion* aMotion,
|
||||
const StyleMotion* aNewMotion)
|
||||
{
|
||||
nsChangeHint result = nsChangeHint(0);
|
||||
|
||||
// TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
|
||||
// (or UpdateTransformLayer) if there's already a transform.
|
||||
if (!aMotion != !aNewMotion ||
|
||||
(aMotion && *aMotion != *aNewMotion)) {
|
||||
// Set the same hints as what we use for transform because motion path is
|
||||
// a kind of transform and will be combined with other transforms.
|
||||
result |= nsChangeHint_UpdateTransformLayer;
|
||||
if ((aMotion && aMotion->HasPath()) &&
|
||||
(aNewMotion && aNewMotion->HasPath())) {
|
||||
result |= nsChangeHint_UpdatePostTransformOverflow;
|
||||
} else {
|
||||
result |= nsChangeHint_UpdateOverflow;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
nsChangeHint
|
||||
nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
|
||||
{
|
||||
|
|
@ -3866,6 +3911,7 @@ nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
|
|||
aNewData.mSpecifiedTranslate);
|
||||
transformHint |= CompareTransformValues(mSpecifiedScale,
|
||||
aNewData.mSpecifiedScale);
|
||||
transformHint |= CompareMotionValues(mMotion.get(), aNewData.mMotion.get());
|
||||
|
||||
const nsChangeHint kUpdateOverflowAndRepaintHint =
|
||||
nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
|
||||
|
|
|
|||
|
|
@ -1971,6 +1971,27 @@ private:
|
|||
nsStyleCorners mRadius;
|
||||
};
|
||||
|
||||
struct StyleSVGPath final
|
||||
{
|
||||
const nsTArray<StylePathCommand>& Path() const
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
bool operator==(const StyleSVGPath& aOther) const
|
||||
{
|
||||
return mPath == aOther.mPath;
|
||||
}
|
||||
|
||||
bool operator!=(const StyleSVGPath& aOther) const
|
||||
{
|
||||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
private:
|
||||
nsTArray<StylePathCommand> mPath;
|
||||
};
|
||||
|
||||
struct StyleShapeSource final
|
||||
{
|
||||
StyleShapeSource();
|
||||
|
|
@ -2035,6 +2056,13 @@ struct StyleShapeSource final
|
|||
|
||||
void SetReferenceBox(StyleGeometryBox aReferenceBox);
|
||||
|
||||
const StyleSVGPath* GetPath() const
|
||||
{
|
||||
MOZ_ASSERT(mType == StyleShapeSourceType::Path, "Wrong shape source type!");
|
||||
return mSVGPath.get();
|
||||
}
|
||||
void SetPath(UniquePtr<StyleSVGPath> aPath);
|
||||
|
||||
private:
|
||||
void* operator new(size_t) = delete;
|
||||
|
||||
|
|
@ -2044,13 +2072,41 @@ private:
|
|||
union {
|
||||
mozilla::UniquePtr<StyleBasicShape> mBasicShape;
|
||||
mozilla::UniquePtr<nsStyleImage> mShapeImage;
|
||||
// TODO: Bug 1429298, implement SVG Path function.
|
||||
mozilla::UniquePtr<StyleSVGPath> mSVGPath;
|
||||
// TODO: Bug 1480665, implement ray() function.
|
||||
};
|
||||
StyleShapeSourceType mType = StyleShapeSourceType::None;
|
||||
StyleGeometryBox mReferenceBox = StyleGeometryBox::NoBox;
|
||||
};
|
||||
|
||||
struct StyleMotion final
|
||||
{
|
||||
bool operator==(const StyleMotion& aOther) const
|
||||
{
|
||||
return mOffsetPath == aOther.mOffsetPath;
|
||||
}
|
||||
|
||||
bool operator!=(const StyleMotion& aOther) const
|
||||
{
|
||||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
const StyleShapeSource& OffsetPath() const
|
||||
{
|
||||
return mOffsetPath;
|
||||
}
|
||||
|
||||
bool HasPath() const
|
||||
{
|
||||
// Bug 1186329: We have to check other acceptable types after supporting
|
||||
// different values of offset-path. e.g. basic-shapes, ray.
|
||||
return mOffsetPath.GetType() == StyleShapeSourceType::Path;
|
||||
}
|
||||
|
||||
private:
|
||||
StyleShapeSource mOffsetPath;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
||||
|
|
@ -2125,6 +2181,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
|||
RefPtr<nsCSSValueSharedList> mSpecifiedRotate;
|
||||
RefPtr<nsCSSValueSharedList> mSpecifiedTranslate;
|
||||
RefPtr<nsCSSValueSharedList> mSpecifiedScale;
|
||||
mozilla::UniquePtr<mozilla::StyleMotion> mMotion;
|
||||
|
||||
// Used to store the final combination of mSpecifiedTranslate,
|
||||
// mSpecifiedRotate, mSpecifiedScale and mSpecifiedTransform.
|
||||
|
|
@ -2380,7 +2437,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
|||
return mSpecifiedTransform || mSpecifiedRotate || mSpecifiedTranslate ||
|
||||
mSpecifiedScale ||
|
||||
mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
|
||||
(mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM);
|
||||
(mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) ||
|
||||
(mMotion && mMotion->HasPath());
|
||||
}
|
||||
|
||||
bool HasIndividualTransform() const {
|
||||
|
|
|
|||
|
|
@ -8173,6 +8173,28 @@ if (IsCSSPropertyPrefEnabled("layout.css.scrollbar-width.enabled")) {
|
|||
};
|
||||
}
|
||||
|
||||
if (IsCSSPropertyPrefEnabled("layout.css.motion-path.enabled")) {
|
||||
gCSSProperties["offset-path"] = {
|
||||
domProp: "offsetPath",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: [ "none" ],
|
||||
other_values: [
|
||||
"path('M 10 10 20 20 H 90 V 90 Z')",
|
||||
"path('M10 10 20,20H90V90Z')",
|
||||
"path('M 10 10 C 20 20, 40 20, 50 10')",
|
||||
"path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')",
|
||||
"path('M 10 80 Q 95 10 180 80')",
|
||||
"path('M 10 80 Q 52.5 10, 95 80 T 180 80')",
|
||||
"path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')",
|
||||
"path('M100-200h20z')",
|
||||
"path('M10,10L20.6.5z')"
|
||||
],
|
||||
invalid_values: [ "path('')", "path()", "path(a)", "path('M 10 Z')" ,
|
||||
"path('M 10-10 20')", "path('M 10 10 C 20 20 40 20')" ]
|
||||
};
|
||||
}
|
||||
|
||||
const OVERFLOW_MOZKWS = [
|
||||
"-moz-scrollbars-none",
|
||||
"-moz-scrollbars-horizontal",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated usi
|
|||
* a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
|
||||
* 2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate style -o layout/style/ServoStyleConsts.h`
|
||||
*/"""
|
||||
include_guard = "mozilla_ServoStyleConsts_h"
|
||||
include_version = true
|
||||
braces = "SameLine"
|
||||
line_length = 80
|
||||
|
|
@ -22,5 +23,5 @@ derive_helper_methods = true
|
|||
|
||||
[export]
|
||||
prefix = "Style"
|
||||
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
|
||||
item_types = ["enums"]
|
||||
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode", "StylePathCommand"]
|
||||
item_types = ["enums", "structs", "typedefs"]
|
||||
|
|
|
|||
|
|
@ -638,6 +638,7 @@ pub mod basic_shape {
|
|||
use values::computed::basic_shape::{BasicShape, ClippingShape, FloatAreaShape, ShapeRadius};
|
||||
use values::computed::border::{BorderCornerRadius, BorderRadius};
|
||||
use values::computed::length::LengthOrPercentage;
|
||||
use values::computed::motion::OffsetPath;
|
||||
use values::computed::position;
|
||||
use values::computed::url::ComputedUrl;
|
||||
use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
|
||||
|
|
@ -669,6 +670,7 @@ pub mod basic_shape {
|
|||
Some(ShapeSource::Shape(shape, reference_box))
|
||||
},
|
||||
StyleShapeSourceType::URL | StyleShapeSourceType::Image => None,
|
||||
StyleShapeSourceType::Path => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -710,6 +712,29 @@ pub mod basic_shape {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a StyleShapeSource> for OffsetPath {
|
||||
fn from(other: &'a StyleShapeSource) -> Self {
|
||||
use gecko_bindings::structs::StylePathCommand;
|
||||
use values::specified::motion::{SVGPathData, PathCommand};
|
||||
match other.mType {
|
||||
StyleShapeSourceType::Path => {
|
||||
let gecko_path = unsafe { &*other.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
|
||||
let result: Vec<PathCommand> =
|
||||
gecko_path.mPath.iter().map(|gecko: &StylePathCommand| {
|
||||
// unsafe: cbindgen ensures the representation is the same.
|
||||
unsafe{ ::std::mem::transmute(*gecko) }
|
||||
}).collect();
|
||||
OffsetPath::Path(SVGPathData::new(result.into_boxed_slice()))
|
||||
},
|
||||
StyleShapeSourceType::None => OffsetPath::none(),
|
||||
StyleShapeSourceType::Shape |
|
||||
StyleShapeSourceType::Box |
|
||||
StyleShapeSourceType::URL |
|
||||
StyleShapeSourceType::Image => unreachable!("Unsupported offset-path type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a StyleBasicShape> for BasicShape {
|
||||
fn from(other: &'a StyleBasicShape) -> Self {
|
||||
match other.mType {
|
||||
|
|
|
|||
|
|
@ -3053,7 +3053,7 @@ fn static_assert() {
|
|||
scroll-snap-points-x scroll-snap-points-y
|
||||
scroll-snap-type-x scroll-snap-type-y scroll-snap-coordinate
|
||||
perspective-origin -moz-binding will-change
|
||||
overscroll-behavior-x overscroll-behavior-y
|
||||
offset-path overscroll-behavior-x overscroll-behavior-y
|
||||
overflow-clip-box-inline overflow-clip-box-block
|
||||
perspective-origin -moz-binding will-change
|
||||
shape-outside contain touch-action translate
|
||||
|
|
@ -3681,6 +3681,51 @@ fn static_assert() {
|
|||
${impl_simple_copy("contain", "mContain")}
|
||||
|
||||
${impl_simple_type_with_conversion("touch_action")}
|
||||
|
||||
pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) {
|
||||
use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_NewStyleSVGPath};
|
||||
use gecko_bindings::bindings::Gecko_SetStyleMotion;
|
||||
use gecko_bindings::structs::StyleShapeSourceType;
|
||||
use values::specified::OffsetPath;
|
||||
|
||||
let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() };
|
||||
match v {
|
||||
OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None,
|
||||
OffsetPath::Path(servo_path) => {
|
||||
motion.mOffsetPath.mType = StyleShapeSourceType::Path;
|
||||
let gecko_path = unsafe {
|
||||
let ref mut source = motion.mOffsetPath;
|
||||
Gecko_NewStyleSVGPath(source);
|
||||
&mut source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap().mPath
|
||||
};
|
||||
unsafe { gecko_path.set_len(servo_path.commands().len() as u32) };
|
||||
debug_assert_eq!(gecko_path.len(), servo_path.commands().len());
|
||||
for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.iter_mut()) {
|
||||
// unsafe: cbindgen ensures the representation is the same.
|
||||
*gecko = unsafe { transmute(*servo) };
|
||||
}
|
||||
},
|
||||
}
|
||||
unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) };
|
||||
}
|
||||
|
||||
pub fn clone_offset_path(&self) -> longhands::offset_path::computed_value::T {
|
||||
use values::specified::OffsetPath;
|
||||
match unsafe { self.gecko.mMotion.mPtr.as_ref() } {
|
||||
None => OffsetPath::none(),
|
||||
Some(v) => (&v.mOffsetPath).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_offset_path_from(&mut self, other: &Self) {
|
||||
use gecko_bindings::bindings::Gecko_CopyStyleMotions;
|
||||
unsafe { Gecko_CopyStyleMotions(&mut self.gecko.mMotion, other.gecko.mMotion.mPtr) };
|
||||
}
|
||||
|
||||
pub fn reset_offset_path(&mut self, other: &Self) {
|
||||
self.copy_offset_path_from(other);
|
||||
}
|
||||
|
||||
</%self:impl_trait>
|
||||
|
||||
<%def name="simple_image_array_property(name, shorthand, field_name)">
|
||||
|
|
|
|||
|
|
@ -356,6 +356,17 @@ ${helpers.predefined_type(
|
|||
servo_restyle_damage="reflow_out_of_flow"
|
||||
)}
|
||||
|
||||
// Motion Path Module Level 1
|
||||
${helpers.predefined_type(
|
||||
"offset-path",
|
||||
"OffsetPath",
|
||||
"computed::OffsetPath::none()",
|
||||
animation_value_type="none",
|
||||
gecko_pref="layout.css.motion-path.enabled",
|
||||
flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
|
||||
spec="https://drafts.fxtf.org/motion-1/#offset-path-property"
|
||||
)}
|
||||
|
||||
// CSSOM View Module
|
||||
// https://www.w3.org/TR/cssom-view-1/
|
||||
${helpers.single_keyword("scroll-behavior",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercent
|
|||
pub use self::list::Quotes;
|
||||
#[cfg(feature = "gecko")]
|
||||
pub use self::list::ListStyleType;
|
||||
pub use self::motion::OffsetPath;
|
||||
pub use self::outline::OutlineStyle;
|
||||
pub use self::percentage::{Percentage, NonNegativePercentage};
|
||||
pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
|
||||
|
|
@ -100,6 +101,7 @@ pub mod gecko;
|
|||
pub mod image;
|
||||
pub mod length;
|
||||
pub mod list;
|
||||
pub mod motion;
|
||||
pub mod outline;
|
||||
pub mod percentage;
|
||||
pub mod position;
|
||||
|
|
|
|||
10
servo/components/style/values/computed/motion.rs
Normal file
10
servo/components/style/values/computed/motion.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Computed types for CSS values that are related to motion path.
|
||||
|
||||
/// A computed offset-path. The computed value is as specified value.
|
||||
///
|
||||
/// https://drafts.fxtf.org/motion-1/#offset-path-property
|
||||
pub use values::specified::motion::OffsetPath as OffsetPath;
|
||||
|
|
@ -58,6 +58,7 @@ pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercent
|
|||
pub use self::list::Quotes;
|
||||
#[cfg(feature = "gecko")]
|
||||
pub use self::list::ListStyleType;
|
||||
pub use self::motion::OffsetPath;
|
||||
pub use self::outline::OutlineStyle;
|
||||
pub use self::rect::LengthOrNumberRect;
|
||||
pub use self::resolution::Resolution;
|
||||
|
|
@ -101,6 +102,7 @@ pub mod image;
|
|||
pub mod length;
|
||||
pub mod list;
|
||||
pub mod outline;
|
||||
pub mod motion;
|
||||
pub mod percentage;
|
||||
pub mod position;
|
||||
pub mod rect;
|
||||
|
|
|
|||
643
servo/components/style/values/specified/motion.rs
Normal file
643
servo/components/style/values/specified/motion.rs
Normal file
|
|
@ -0,0 +1,643 @@
|
|||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Specified types for CSS values that are related to motion path.
|
||||
|
||||
use cssparser::Parser;
|
||||
use parser::{Parse, ParserContext};
|
||||
use std::fmt::{self, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
use style_traits::values::SequenceWriter;
|
||||
use values::CSSFloat;
|
||||
|
||||
/// The offset-path value.
|
||||
///
|
||||
/// https://drafts.fxtf.org/motion-1/#offset-path-property
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
|
||||
pub enum OffsetPath {
|
||||
// We could merge SVGPathData into ShapeSource, so we could reuse them. However,
|
||||
// we don't want to support other value for offset-path, so use SVGPathData only for now.
|
||||
/// Path value for path(<string>).
|
||||
#[css(function)]
|
||||
Path(SVGPathData),
|
||||
/// None value.
|
||||
None,
|
||||
// Bug 1186329: Implement ray(), <basic-shape>, <geometry-box>, and <url>.
|
||||
}
|
||||
|
||||
impl OffsetPath {
|
||||
/// Return None.
|
||||
#[inline]
|
||||
pub fn none() -> Self {
|
||||
OffsetPath::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for OffsetPath {
|
||||
fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
// Parse none.
|
||||
if input.try(|i| i.expect_ident_matching("none")).is_ok() {
|
||||
return Ok(OffsetPath::none());
|
||||
}
|
||||
|
||||
// Parse possible functions.
|
||||
let location = input.current_source_location();
|
||||
let function = input.expect_function()?.clone();
|
||||
input.parse_nested_block(move |i| {
|
||||
match_ignore_ascii_case! { &function,
|
||||
// Bug 1186329: Implement the parser for ray(), <basic-shape>, <geometry-box>,
|
||||
// and <url>.
|
||||
"path" => SVGPathData::parse(context, i).map(OffsetPath::Path),
|
||||
_ => {
|
||||
Err(location.new_custom_error(
|
||||
StyleParseErrorKind::UnexpectedFunction(function.clone())
|
||||
))
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// SVG Path parser.
|
||||
struct PathParser<'a> {
|
||||
chars: Peekable<Chars<'a>>,
|
||||
path: Vec<PathCommand>,
|
||||
}
|
||||
|
||||
impl<'a> PathParser<'a> {
|
||||
/// Parse a sub-path.
|
||||
fn parse_subpath(&mut self) -> Result<(), ()> {
|
||||
// Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
|
||||
// (i.e. not a valid moveto-drawto-command-group).
|
||||
self.parse_moveto()?;
|
||||
|
||||
// Handle other commands.
|
||||
loop {
|
||||
skip_wsp(&mut self.chars);
|
||||
if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') {
|
||||
break;
|
||||
}
|
||||
|
||||
match self.chars.next() {
|
||||
Some(command) => {
|
||||
let abs = command.is_uppercase();
|
||||
match command {
|
||||
'Z' | 'z' => {
|
||||
// Note: A "closepath" coulbe be followed immediately by "moveto" or
|
||||
// any other command, so we don't break this loop.
|
||||
self.path.push(PathCommand::ClosePath);
|
||||
},
|
||||
'L' | 'l' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_lineto(abs)?;
|
||||
},
|
||||
'H' | 'h' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_h_lineto(abs)?;
|
||||
},
|
||||
'V' | 'v' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_v_lineto(abs)?;
|
||||
},
|
||||
'C' | 'c' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_curveto(abs)?;
|
||||
},
|
||||
'S' | 's' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_smooth_curveto(abs)?;
|
||||
},
|
||||
'Q' | 'q' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_quadratic_bezier_curveto(abs)?;
|
||||
},
|
||||
'T' | 't' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_smooth_quadratic_bezier_curveto(abs)?;
|
||||
},
|
||||
'A' | 'a' => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.parse_elliprical_arc(abs)?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
},
|
||||
_ => break, // no more commands.
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse "moveto" command.
|
||||
fn parse_moveto(&mut self) -> Result<(), ()> {
|
||||
let command = match self.chars.next() {
|
||||
Some(c) if c == 'M' || c == 'm' => c,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
skip_wsp(&mut self.chars);
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
let absolute = command == 'M';
|
||||
self.path.push(PathCommand::MoveTo { point, absolute } );
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
return Ok(());
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
|
||||
// If a moveto is followed by multiple pairs of coordinates, the subsequent
|
||||
// pairs are treated as implicit lineto commands.
|
||||
self.parse_lineto(absolute)
|
||||
}
|
||||
|
||||
/// Parse "lineto" command.
|
||||
fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
self.path.push(PathCommand::LineTo { point, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse horizontal "lineto" command.
|
||||
fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let x = parse_number(&mut self.chars)?;
|
||||
self.path.push(PathCommand::HorizontalLineTo { x, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse vertical "lineto" command.
|
||||
fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let y = parse_number(&mut self.chars)?;
|
||||
self.path.push(PathCommand::VerticalLineTo { y, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse cubic Bézier curve command.
|
||||
fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let control1 = parse_coord(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let control2 = parse_coord(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
|
||||
self.path.push(PathCommand::CurveTo { control1, control2, point, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse smooth "curveto" command.
|
||||
fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let control2 = parse_coord(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
|
||||
self.path.push(PathCommand::SmoothCurveTo { control2, point, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse quadratic Bézier curve command.
|
||||
fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let control1 = parse_coord(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
|
||||
self.path.push(PathCommand::QuadBezierCurveTo { control1, point, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse smooth quadratic Bézier curveto command.
|
||||
fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
loop {
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
|
||||
self.path.push(PathCommand::SmoothQuadBezierCurveTo { point, absolute });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse elliptical arc curve command.
|
||||
fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
// Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
|
||||
let parse_flag = |iter: &mut Peekable<Chars>| -> Result<bool, ()> {
|
||||
let value = match iter.peek() {
|
||||
Some(c) if *c == '0' || *c == '1' => *c == '1',
|
||||
_ => return Err(()),
|
||||
};
|
||||
iter.next();
|
||||
Ok(value)
|
||||
};
|
||||
|
||||
loop {
|
||||
let rx = parse_number(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let ry = parse_number(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let angle = parse_number(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let large_arc_flag = parse_flag(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let sweep_flag = parse_flag(&mut self.chars)?;
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
|
||||
self.path.push(
|
||||
PathCommand::EllipticalArc {
|
||||
rx, ry, angle, large_arc_flag, sweep_flag, point, absolute
|
||||
}
|
||||
);
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The SVG path data.
|
||||
///
|
||||
/// https://www.w3.org/TR/SVG11/paths.html#PathData
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)]
|
||||
pub struct SVGPathData(Box<[PathCommand]>);
|
||||
|
||||
impl SVGPathData {
|
||||
/// Return SVGPathData by a slice of PathCommand.
|
||||
#[inline]
|
||||
pub fn new(cmd: Box<[PathCommand]>) -> Self {
|
||||
debug_assert!(!cmd.is_empty());
|
||||
SVGPathData(cmd)
|
||||
}
|
||||
|
||||
/// Get the array of PathCommand.
|
||||
#[inline]
|
||||
pub fn commands(&self) -> &[PathCommand] {
|
||||
debug_assert!(!self.0.is_empty());
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for SVGPathData {
|
||||
#[inline]
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write
|
||||
{
|
||||
dest.write_char('"')?;
|
||||
{
|
||||
let mut writer = SequenceWriter::new(dest, " ");
|
||||
for command in self.0.iter() {
|
||||
writer.item(command)?;
|
||||
}
|
||||
}
|
||||
dest.write_char('"')
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for SVGPathData {
|
||||
// We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
|
||||
// the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
|
||||
// e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
|
||||
// is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
|
||||
// str::Char iterator to check each character.
|
||||
fn parse<'i, 't>(
|
||||
_context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let location = input.current_source_location();
|
||||
let path_string = input.expect_string()?.as_ref();
|
||||
if path_string.is_empty() {
|
||||
// Treat an empty string as invalid, so we will not set it.
|
||||
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
// Parse the svg path string as multiple sub-paths.
|
||||
let mut path_parser = PathParser {
|
||||
chars: path_string.chars().peekable(),
|
||||
path: Vec::new(),
|
||||
};
|
||||
while skip_wsp(&mut path_parser.chars) {
|
||||
if path_parser.parse_subpath().is_err() {
|
||||
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SVGPathData::new(path_parser.path.into_boxed_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The SVG path command.
|
||||
/// The fields of these commands are self-explanatory, so we skip the documents.
|
||||
/// Note: the index of the control points, e.g. control1, control2, are mapping to the control
|
||||
/// points of the Bézier curve in the spec.
|
||||
///
|
||||
/// https://www.w3.org/TR/SVG11/paths.html#PathData
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
|
||||
#[allow(missing_docs)]
|
||||
#[repr(C, u8)]
|
||||
pub enum PathCommand {
|
||||
/// The unknown type.
|
||||
/// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN
|
||||
Unknown,
|
||||
/// The "moveto" command.
|
||||
MoveTo { point: CoordPair, absolute: bool },
|
||||
/// The "lineto" command.
|
||||
LineTo { point: CoordPair, absolute: bool },
|
||||
/// The horizontal "lineto" command.
|
||||
HorizontalLineTo { x: CSSFloat, absolute: bool },
|
||||
/// The vertical "lineto" command.
|
||||
VerticalLineTo { y: CSSFloat, absolute: bool },
|
||||
/// The cubic Bézier curve command.
|
||||
CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool },
|
||||
/// The smooth curve command.
|
||||
SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool },
|
||||
/// The quadratic Bézier curve command.
|
||||
QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool },
|
||||
/// The smooth quadratic Bézier curve command.
|
||||
SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool },
|
||||
/// The elliptical arc curve command.
|
||||
EllipticalArc {
|
||||
rx: CSSFloat,
|
||||
ry: CSSFloat,
|
||||
angle: CSSFloat,
|
||||
large_arc_flag: bool,
|
||||
sweep_flag: bool,
|
||||
point: CoordPair,
|
||||
absolute: bool
|
||||
},
|
||||
/// The "closepath" command.
|
||||
ClosePath,
|
||||
}
|
||||
|
||||
impl ToCss for PathCommand {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write
|
||||
{
|
||||
use self::PathCommand::*;
|
||||
match *self {
|
||||
Unknown => dest.write_str("X"),
|
||||
ClosePath => dest.write_str("Z"),
|
||||
MoveTo { point, absolute } => {
|
||||
dest.write_char(if absolute { 'M' } else { 'm' })?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
}
|
||||
LineTo { point, absolute } => {
|
||||
dest.write_char(if absolute { 'L' } else { 'l' })?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
}
|
||||
CurveTo { control1, control2, point, absolute } => {
|
||||
dest.write_char(if absolute { 'C' } else { 'c' })?;
|
||||
dest.write_char(' ')?;
|
||||
control1.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
control2.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
QuadBezierCurveTo { control1, point, absolute } => {
|
||||
dest.write_char(if absolute { 'Q' } else { 'q' })?;
|
||||
dest.write_char(' ')?;
|
||||
control1.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => {
|
||||
dest.write_char(if absolute { 'A' } else { 'a' })?;
|
||||
dest.write_char(' ')?;
|
||||
rx.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
ry.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
angle.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
(large_arc_flag as i32).to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
(sweep_flag as i32).to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
HorizontalLineTo { x, absolute } => {
|
||||
dest.write_char(if absolute { 'H' } else { 'h' })?;
|
||||
dest.write_char(' ')?;
|
||||
x.to_css(dest)
|
||||
},
|
||||
VerticalLineTo { y, absolute } => {
|
||||
dest.write_char(if absolute { 'V' } else { 'v' })?;
|
||||
dest.write_char(' ')?;
|
||||
y.to_css(dest)
|
||||
},
|
||||
SmoothCurveTo { control2, point, absolute } => {
|
||||
dest.write_char(if absolute { 'S' } else { 's' })?;
|
||||
dest.write_char(' ')?;
|
||||
control2.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
SmoothQuadBezierCurveTo { point, absolute } => {
|
||||
dest.write_char(if absolute { 'T' } else { 't' })?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The path coord type.
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
#[repr(C)]
|
||||
pub struct CoordPair(CSSFloat, CSSFloat);
|
||||
|
||||
impl CoordPair {
|
||||
/// Create a CoordPair.
|
||||
#[inline]
|
||||
pub fn new(x: CSSFloat, y: CSSFloat) -> Self {
|
||||
CoordPair(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a pair of numbers into CoordPair.
|
||||
fn parse_coord(iter: &mut Peekable<Chars>) -> Result<CoordPair, ()> {
|
||||
let x = parse_number(iter)?;
|
||||
skip_comma_wsp(iter);
|
||||
let y = parse_number(iter)?;
|
||||
Ok(CoordPair::new(x, y))
|
||||
}
|
||||
|
||||
/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
|
||||
/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
|
||||
/// point number. In other words, the logic here is similar with that of
|
||||
/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
|
||||
/// input is a Peekable and we only accept an integer of a floating point number.
|
||||
///
|
||||
/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
|
||||
fn parse_number(iter: &mut Peekable<Chars>) -> Result<CSSFloat, ()> {
|
||||
// 1. Check optional sign.
|
||||
let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
|
||||
if iter.next().unwrap() == '-' { -1. } else { 1. }
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
||||
// 2. Check integer part.
|
||||
let mut integral_part: f64 = 0.;
|
||||
let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') {
|
||||
// If the first digit in integer part is neither a dot nor a digit, this is not a number.
|
||||
if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
|
||||
integral_part =
|
||||
integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
|
||||
}
|
||||
|
||||
iter.peek().map_or(false, |&n: &char| n == '.')
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
// 3. Check fractional part.
|
||||
let mut fractional_part: f64 = 0.;
|
||||
if got_dot {
|
||||
// Consume '.'.
|
||||
iter.next();
|
||||
// If the first digit in fractional part is not a digit, this is not a number.
|
||||
if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut factor = 0.1;
|
||||
while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
|
||||
fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor;
|
||||
factor *= 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut value = sign * (integral_part + fractional_part);
|
||||
|
||||
// 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
|
||||
// treat the numbers after 'E' or 'e' are in the exponential part.
|
||||
if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') {
|
||||
// Consume 'E' or 'e'.
|
||||
iter.next();
|
||||
let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
|
||||
if iter.next().unwrap() == '-' { -1. } else { 1. }
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
||||
let mut exp: f64 = 0.;
|
||||
while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
|
||||
exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
|
||||
}
|
||||
|
||||
value *= f64::powf(10., exp * exp_sign);
|
||||
}
|
||||
|
||||
if value.is_finite() {
|
||||
Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip all svg whitespaces, and return true if |iter| hasn't finished.
|
||||
#[inline]
|
||||
fn skip_wsp(iter: &mut Peekable<Chars>) -> bool {
|
||||
// Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
|
||||
// However, SVG 2 has one extra whitespace: \u{C}.
|
||||
// Therefore, we follow the newest spec for the definition of whitespace,
|
||||
// i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace().
|
||||
while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) {
|
||||
iter.next();
|
||||
}
|
||||
iter.peek().is_some()
|
||||
}
|
||||
|
||||
/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
|
||||
#[inline]
|
||||
fn skip_comma_wsp(iter: &mut Peekable<Chars>) -> bool {
|
||||
if !skip_wsp(iter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if *iter.peek().unwrap() != ',' {
|
||||
return true;
|
||||
}
|
||||
iter.next();
|
||||
|
||||
skip_wsp(iter)
|
||||
}
|
||||
Loading…
Reference in a new issue