From 22aced7e8e59fac0687ff01eddf1866ab2f3a4a0 Mon Sep 17 00:00:00 2001 From: Simon Fraser Date: Tue, 30 Jul 2024 08:52:27 -0700 Subject: [PATCH] [CSS Shape function] Add parsing support and storage for shape() https://bugs.webkit.org/show_bug.cgi?id=277347 rdar://132814728 Reviewed by NOBODY (OOPS!). Implement parsing, property value and computed style support for the `shape()` function[1]. We support the syntax as of the most recent edit[2] but without the flexible order of points and control points due to [3]. Added BasicShapesShapeSegmentConversion.h/cpp with helpers to convert between CSS values and the internal shapes. CSSShapeValue::customCSSText() is implemented. BasicShapeShape stores its segments as a vector of std::variant<>, much as we do for Paths, which avoids heap allocations per segment. Tentative WPT are included. [1] https://drafts.csswg.org/css-shapes-2/#shape-function [2] https://github.com/w3c/csswg-drafts/commit/9770805c4e53 [3] https://github.com/w3c/csswg-drafts/issues/10666 * LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-computed.tentative.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-invalid.tentative.html: Added. * LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-valid.tentative.html: Added. * Source/WebCore/Sources.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/css/BasicShapeConversion.cpp: (WebCore::valueForBasicShape): (WebCore::convertToLengthPoint): (WebCore::basicShapeForValue): (WebCore::basicShapeShapeForValue): * Source/WebCore/css/BasicShapeConversion.h: * Source/WebCore/css/BasicShapesShapeSegmentConversion.cpp: Added. (WebCore::lengthToCSSValue): (WebCore::lengthPointToCSSValue): (WebCore::lengthSizeToCSSValue): (WebCore::toCSSShapeSegmentValue): (WebCore::fromCSSShapeSegmentValue): * Source/WebCore/css/BasicShapesShapeSegmentConversion.h: Copied from Source/WebCore/css/BasicShapeConversion.h. * Source/WebCore/css/CSSBasicShapes.cpp: (WebCore::CSSShapeValue::CSSShapeValue): (WebCore::CSSShapeValue::customCSSText const): (WebCore::CSSShapeValue::equals const): * Source/WebCore/css/CSSShapeSegmentValue.cpp: (WebCore::CSSShapeSegmentValue::customCSSText const): (WebCore::CSSShapeSegmentValue::toShapeSegment const): * Source/WebCore/css/CSSShapeSegmentValue.h: * Source/WebCore/css/CSSValueKeywords.in: * Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp: (WebCore::CSSPropertyParserHelpers::consumeCoordinatePair): (WebCore::CSSPropertyParserHelpers::consumeShapeCommand): (WebCore::CSSPropertyParserHelpers::consumeBasicShapeShape): (WebCore::CSSPropertyParserHelpers::consumeBasicShape): * Source/WebCore/rendering/style/BasicShapes.cpp: (WebCore::operator<<): * Source/WebCore/rendering/style/BasicShapes.h: * Source/WebCore/rendering/style/BasicShapesShape.cpp: Added. (WebCore::BasicShapeShape::create): (WebCore::BasicShapeShape::BasicShapeShape): (WebCore::BasicShapeShape::clone const): (WebCore::BasicShapeShape::path const): (WebCore::BasicShapeShape::canBlend const): (WebCore::BasicShapeShape::blend const): (WebCore::BasicShapeShape::operator== const): (WebCore::BasicShapeShape::dump const): (WebCore::operator<<): * Source/WebCore/rendering/style/BasicShapesShape.h: Added. (WebCore::ShapeSegmentBase::ShapeSegmentBase): (WebCore::ShapeSegmentBase::affinity const): --- .../shape-function-computed.tentative.html | 53 ++++ .../shape-function-invalid.tentative.html | 30 +++ .../shape-function-valid.tentative.html | 58 +++++ Source/WebCore/Sources.txt | 2 + .../WebCore/WebCore.xcodeproj/project.pbxproj | 8 + Source/WebCore/css/BasicShapeConversion.cpp | 66 ++++- Source/WebCore/css/BasicShapeConversion.h | 3 + .../css/BasicShapesShapeSegmentConversion.cpp | 103 ++++++++ .../css/BasicShapesShapeSegmentConversion.h | 45 ++++ Source/WebCore/css/CSSBasicShapes.cpp | 28 +- Source/WebCore/css/CSSShapeSegmentValue.cpp | 80 +++++- Source/WebCore/css/CSSShapeSegmentValue.h | 3 + Source/WebCore/css/CSSValueKeywords.in | 2 +- .../css/parser/CSSPropertyParserHelpers.cpp | 216 +++++++++++++++ .../WebCore/rendering/style/BasicShapes.cpp | 23 ++ Source/WebCore/rendering/style/BasicShapes.h | 7 +- .../rendering/style/BasicShapesShape.cpp | 143 ++++++++++ .../rendering/style/BasicShapesShape.h | 245 ++++++++++++++++++ 18 files changed, 1094 insertions(+), 21 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-computed.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-invalid.tentative.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-valid.tentative.html create mode 100644 Source/WebCore/css/BasicShapesShapeSegmentConversion.cpp create mode 100644 Source/WebCore/css/BasicShapesShapeSegmentConversion.h create mode 100644 Source/WebCore/rendering/style/BasicShapesShape.cpp create mode 100644 Source/WebCore/rendering/style/BasicShapesShape.h diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-computed.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-computed.tentative.html new file mode 100644 index 0000000000000..28f622f03bdd5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-computed.tentative.html @@ -0,0 +1,53 @@ + + + + +CSS Shapes Module Level 2: computed values for the shape() function + + + + + + + +
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-invalid.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-invalid.tentative.html new file mode 100644 index 0000000000000..76f0fcc94d6d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-invalid.tentative.html @@ -0,0 +1,30 @@ + + + + +CSS Shapes Module Level 1: parsing the shape() function + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-valid.tentative.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-valid.tentative.html new file mode 100644 index 0000000000000..ae64b39a2f192 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-shapes/shape-functions/shape-function-valid.tentative.html @@ -0,0 +1,58 @@ + + + + +CSS Shapes Module Level 2: parsing the shape() function + + + + + + + + +
+ + + diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt index 69af20240ee62..3328f33677187 100644 --- a/Source/WebCore/Sources.txt +++ b/Source/WebCore/Sources.txt @@ -826,6 +826,7 @@ crypto/keys/CryptoKeyRSA.cpp crypto/keys/CryptoKeyRSAComponents.cpp crypto/keys/CryptoKeyRaw.cpp css/BasicShapeConversion.cpp +css/BasicShapesShapeSegmentConversion.cpp css/CSSAnchorValue.cpp css/CSSAspectRatioValue.cpp css/CSSBackgroundRepeatValue.cpp @@ -2801,6 +2802,7 @@ rendering/shapes/RectangleShape.cpp rendering/shapes/Shape.cpp rendering/shapes/ShapeOutsideInfo.cpp rendering/style/BasicShapes.cpp +rendering/style/BasicShapesShape.cpp rendering/style/BorderData.cpp rendering/style/BorderValue.cpp rendering/style/ContentData.cpp diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj index 6b155b379fb05..9c4dc2ac8298e 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj @@ -7305,6 +7305,10 @@ 0FCF33230F2B9715004B6795 /* ColorCG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColorCG.cpp; sourceTree = ""; }; 0FCF332A0F2B9A25004B6795 /* WebLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebLayer.mm; sourceTree = ""; }; 0FCF332B0F2B9A25004B6795 /* WebLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebLayer.h; sourceTree = ""; }; + 0FD2C6A12C58485E00ED7278 /* BasicShapesShapeSegmentConversion.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BasicShapesShapeSegmentConversion.cpp; sourceTree = ""; }; + 0FD2C6A22C58485E00ED7278 /* BasicShapesShapeSegmentConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BasicShapesShapeSegmentConversion.h; sourceTree = ""; }; + 0FD2C6A32C5848A900ED7278 /* BasicShapesShape.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BasicShapesShape.h; sourceTree = ""; }; + 0FD2C6A42C5848A900ED7278 /* BasicShapesShape.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BasicShapesShape.cpp; sourceTree = ""; }; 0FD3080C117CF7E700A791F7 /* RenderFrameBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderFrameBase.cpp; sourceTree = ""; }; 0FD3080D117CF7E700A791F7 /* RenderFrameBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderFrameBase.h; sourceTree = ""; }; 0FD308D3117D168400A791F7 /* RenderIFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderIFrame.cpp; sourceTree = ""; }; @@ -33496,6 +33500,8 @@ children = ( FBD6AF8215EF21A3008B7110 /* BasicShapes.cpp */, FBD6AF8315EF21A3008B7110 /* BasicShapes.h */, + 0FD2C6A42C5848A900ED7278 /* BasicShapesShape.cpp */, + 0FD2C6A32C5848A900ED7278 /* BasicShapesShape.h */, 0F283A9123563126004794CA /* BorderData.cpp */, BC5EB5E00E81BE8700B25965 /* BorderData.h */, BCD34EEC28ECB6D800472470 /* BorderValue.cpp */, @@ -35973,6 +35979,8 @@ 4BAFD0DA21921EAD00C0AB64 /* typedom */, FBD6AF8415EF21D4008B7110 /* BasicShapeConversion.cpp */, FBD6AF8515EF21D4008B7110 /* BasicShapeConversion.h */, + 0FD2C6A12C58485E00ED7278 /* BasicShapesShapeSegmentConversion.cpp */, + 0FD2C6A22C58485E00ED7278 /* BasicShapesShapeSegmentConversion.h */, 713785EE28D9F4C50092D9F2 /* ComputedStyleExtractor.cpp */, 713785EF28D9F4C50092D9F2 /* ComputedStyleExtractor.h */, A80E6CDA0A1989CA007FB8C5 /* Counter.h */, diff --git a/Source/WebCore/css/BasicShapeConversion.cpp b/Source/WebCore/css/BasicShapeConversion.cpp index 721dc947bf2d8..e93449ecef457 100644 --- a/Source/WebCore/css/BasicShapeConversion.cpp +++ b/Source/WebCore/css/BasicShapeConversion.cpp @@ -31,11 +31,14 @@ #include "BasicShapeConversion.h" #include "BasicShapes.h" +#include "BasicShapesShape.h" +#include "BasicShapesShapeSegmentConversion.h" #include "CSSBasicShapes.h" #include "CSSCalcNegateNode.h" #include "CSSCalcOperationNode.h" #include "CSSCalcPrimitiveValueNode.h" #include "CSSPrimitiveValueMappings.h" +#include "CSSShapeSegmentValue.h" #include "CSSValuePair.h" #include "CSSValuePool.h" #include "CalculationValue.h" @@ -94,6 +97,9 @@ Ref valueForBasicShape(const RenderStyle& style, const BasicShape& bas auto createPair = [&](const LengthSize& size) { return CSSValuePair::create(createValue(size.width), createValue(size.height)); }; + auto createCoordinatePair = [&](const LengthPoint& point) { + return CSSValuePair::createNoncoalescing(createValue(point.x()), createValue(point.y())); + }; auto createReflectedSumValue = [&](const Length& a, const Length& b) { auto reflected = convertTo100PercentMinusLengthSum(a, b); return CSSPrimitiveValue::create(reflected, style); @@ -160,6 +166,18 @@ Ref valueForBasicShape(const RenderStyle& style, const BasicShape& bas createPair(rect.topLeftRadius()), createPair(rect.topRightRadius()), createPair(rect.bottomRightRadius()), createPair(rect.bottomLeftRadius())); } + case BasicShape::Type::Shape: { + auto& shape = uncheckedDowncast(basicShape); + + CSSValueListBuilder segments; + + for (auto& segment : shape.segments()) { + Ref value = toCSSShapeSegmentValue(style, segment); + segments.append(WTFMove(value)); + } + + return CSSShapeValue::create(shape.windRule(), createCoordinatePair(shape.startPoint()), WTFMove(segments)); + } } RELEASE_ASSERT_NOT_REACHED(); } @@ -182,6 +200,15 @@ static LengthSize convertToLengthSize(const CSSToLengthConversionData& conversio return { convertToLength(conversionData, value->protectedFirst()), convertToLength(conversionData, value->protectedSecond()) }; } +static LengthPoint convertToLengthPoint(const CSSToLengthConversionData& conversionData, const CSSValue& value) +{ + RefPtr pairValue = dynamicDowncast(value); + if (!pairValue) + return { }; + + return { convertToLength(conversionData, pairValue->first()), convertToLength(conversionData, pairValue->second()) }; +} + static BasicShapeCenterCoordinate convertToCenterCoordinate(const CSSToLengthConversionData& conversionData, const CSSValue* value) { CSSValueID keyword = CSSValueTop; @@ -241,7 +268,7 @@ static BasicShapeRadius cssValueToBasicShapeRadius(const CSSToLengthConversionDa Ref basicShapeForValue(const CSSToLengthConversionData& conversionData, const CSSValue& value, float zoom) { - if (auto* circleValue = dynamicDowncast(value)) { + if (RefPtr circleValue = dynamicDowncast(value)) { auto circle = BasicShapeCircle::create(); circle->setRadius(cssValueToBasicShapeRadius(conversionData, circleValue->protectedRadius().get())); circle->setCenterX(convertToCenterCoordinate(conversionData, circleValue->protectedCenterX().get())); @@ -249,7 +276,8 @@ Ref basicShapeForValue(const CSSToLengthConversionData& conversionDa circle->setPositionWasOmitted(!circleValue->centerX() && !circleValue->centerY()); return circle; } - if (auto* ellipseValue = dynamicDowncast(value)) { + + if (RefPtr ellipseValue = dynamicDowncast(value)) { auto ellipse = BasicShapeEllipse::create(); ellipse->setRadiusX(cssValueToBasicShapeRadius(conversionData, ellipseValue->protectedRadiusX().get())); ellipse->setRadiusY(cssValueToBasicShapeRadius(conversionData, ellipseValue->protectedRadiusY().get())); @@ -258,14 +286,16 @@ Ref basicShapeForValue(const CSSToLengthConversionData& conversionDa ellipse->setPositionWasOmitted(!ellipseValue->centerX() && !ellipseValue->centerY()); return ellipse; } - if (auto* polygonValue = dynamicDowncast(value)) { + + if (RefPtr polygonValue = dynamicDowncast(value)) { auto polygon = BasicShapePolygon::create(); polygon->setWindRule(polygonValue->windRule()); for (unsigned i = 0; i < polygonValue->size(); i += 2) polygon->appendPoint(convertToLength(conversionData, *polygonValue->protectedItem(i)), convertToLength(conversionData, *polygonValue->protectedItem(i + 1))); return polygon; } - if (auto* rectValue = dynamicDowncast(value)) { + + if (RefPtr rectValue = dynamicDowncast(value)) { auto rect = BasicShapeInset::create(); rect->setTop(convertToLength(conversionData, rectValue->protectedTop())); rect->setRight(convertToLength(conversionData, rectValue->protectedRight())); @@ -277,7 +307,8 @@ Ref basicShapeForValue(const CSSToLengthConversionData& conversionDa rect->setBottomLeftRadius(convertToLengthSize(conversionData, rectValue->protectedBottomLeftRadius().get())); return rect; } - if (auto* rectValue = dynamicDowncast(value)) { + + if (RefPtr rectValue = dynamicDowncast(value)) { auto rect = BasicShapeXywh::create(); rect->setInsetX(convertToLength(conversionData, rectValue->protectedInsetX().get())); rect->setInsetY(convertToLength(conversionData, rectValue->protectedInsetY().get())); @@ -290,7 +321,8 @@ Ref basicShapeForValue(const CSSToLengthConversionData& conversionDa rect->setBottomLeftRadius(convertToLengthSize(conversionData, rectValue->protectedBottomLeftRadius().get())); return rect; } - if (auto* rectValue = dynamicDowncast(value)) { + + if (RefPtr rectValue = dynamicDowncast(value)) { auto rect = BasicShapeRect::create(); rect->setTop(convertToLengthOrAuto(conversionData, rectValue->protectedTop())); rect->setRight(convertToLengthOrAuto(conversionData, rectValue->protectedRight())); @@ -304,9 +336,12 @@ Ref basicShapeForValue(const CSSToLengthConversionData& conversionDa return rect; } - if (auto* pathValue = dynamicDowncast(value)) + if (RefPtr pathValue = dynamicDowncast(value)) return basicShapePathForValue(*pathValue, zoom); + if (RefPtr shapeValue = dynamicDowncast(value)) + return basicShapeShapeForValue(*shapeValue, conversionData); + RELEASE_ASSERT_NOT_REACHED(); } @@ -318,6 +353,23 @@ Ref basicShapePathForValue(const CSSPathValue& value, float zoom return path; } +Ref basicShapeShapeForValue(const CSSShapeValue& shapeValue, const CSSToLengthConversionData& conversionData) +{ + Vector segments; + segments.reserveInitialCapacity(shapeValue.length()); + + for (auto& segment : shapeValue) { + RefPtr shapeSegment = dynamicDowncast(segment); + if (!shapeSegment) { + ASSERT_NOT_REACHED(); + continue; + } + segments.append(fromCSSShapeSegmentValue(conversionData, *shapeSegment)); + } + + return BasicShapeShape::create(shapeValue.windRule(), convertToLengthPoint(conversionData, shapeValue.protectedFromCoordinates().get()), WTFMove(segments)); +} + float floatValueForCenterCoordinate(const BasicShapeCenterCoordinate& center, float boxDimension) { float offset = floatValueForLength(center.length(), boxDimension); diff --git a/Source/WebCore/css/BasicShapeConversion.h b/Source/WebCore/css/BasicShapeConversion.h index 8371d3797e412..e395fea44d51e 100644 --- a/Source/WebCore/css/BasicShapeConversion.h +++ b/Source/WebCore/css/BasicShapeConversion.h @@ -36,7 +36,9 @@ namespace WebCore { class BasicShape; class BasicShapeCenterCoordinate; class BasicShapePath; +class BasicShapeShape; class CSSPathValue; +class CSSShapeValue; class CSSToLengthConversionData; class CSSValue; class RenderStyle; @@ -48,6 +50,7 @@ Ref valueForSVGPath(const BasicShapePath&, SVGPathConversion = SVGPath Ref basicShapeForValue(const CSSToLengthConversionData&, const CSSValue&, float zoom = 1); Ref basicShapePathForValue(const CSSPathValue&, float zoom = 1); +Ref basicShapeShapeForValue(const CSSShapeValue&, const CSSToLengthConversionData&); float floatValueForCenterCoordinate(const BasicShapeCenterCoordinate&, float); diff --git a/Source/WebCore/css/BasicShapesShapeSegmentConversion.cpp b/Source/WebCore/css/BasicShapesShapeSegmentConversion.cpp new file mode 100644 index 0000000000000..47985094a10af --- /dev/null +++ b/Source/WebCore/css/BasicShapesShapeSegmentConversion.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 Noam Rosenthal All rights reserved. + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "BasicShapesShapeSegmentConversion.h" + +#include "BasicShapesShape.h" +#include "CSSPrimitiveValueMappings.h" +#include "CSSShapeSegmentValue.h" +#include "CSSValue.h" +#include "CSSValuePair.h" + +namespace WebCore { + +static auto lengthToCSSValue(const Length& value, const RenderStyle& style) +{ + return CSSPrimitiveValue::create(value, style); +} + +static auto lengthPointToCSSValue(const LengthPoint& value, const RenderStyle& style) +{ + return CSSValuePair::createNoncoalescing( + CSSPrimitiveValue::create(value.x(), style), + CSSPrimitiveValue::create(value.y(), style)); +} + +static auto lengthSizeToCSSValue(const LengthSize& value, const RenderStyle& style) +{ + return CSSValuePair::create( + CSSPrimitiveValue::create(value.width, style), + CSSPrimitiveValue::create(value.height, style)); +} + +Ref toCSSShapeSegmentValue(const RenderStyle& style, const BasicShapeShape::ShapeSegment& segment) +{ + return WTF::switchOn(segment, + [&](const ShapeMoveSegment& segment) { + return CSSShapeSegmentValue::createMove(segment.affinity(), lengthPointToCSSValue(segment.offset(), style)); + }, + [&](const ShapeLineSegment& segment) { + return CSSShapeSegmentValue::createLine(segment.affinity(), lengthPointToCSSValue(segment.offset(), style)); + }, + [&](const ShapeHorizontalLineSegment& segment) { + return CSSShapeSegmentValue::createHorizontalLine(segment.affinity(), lengthToCSSValue(segment.length(), style)); + }, + [&](const ShapeVerticalLineSegment& segment) { + return CSSShapeSegmentValue::createVerticalLine(segment.affinity(), lengthToCSSValue(segment.length(), style)); + }, + [&](const ShapeCurveSegment& segment) { + if (segment.controlPoint2()) + return CSSShapeSegmentValue::createCubicCurve(segment.affinity(), lengthPointToCSSValue(segment.offset(), style), lengthPointToCSSValue(segment.controlPoint1(), style), lengthPointToCSSValue(segment.controlPoint2().value(), style)); + + return CSSShapeSegmentValue::createQuadraticCurve(segment.affinity(), lengthPointToCSSValue(segment.offset(), style), lengthPointToCSSValue(segment.controlPoint1(), style)); + }, + [&](const ShapeSmoothSegment& segment) { + if (segment.intermediatePoint()) + return CSSShapeSegmentValue::createSmoothCubicCurve(segment.affinity(), lengthPointToCSSValue(segment.offset(), style), lengthPointToCSSValue(segment.intermediatePoint().value(), style)); + + return CSSShapeSegmentValue::createSmoothQuadraticCurve(segment.affinity(), lengthPointToCSSValue(segment.offset(), style)); + }, + [&](const ShapeArcSegment& segment) { + return CSSShapeSegmentValue::createArc(segment.affinity(), lengthPointToCSSValue(segment.offset(), style), lengthSizeToCSSValue(segment.ellipseSize(), style), + segment.sweep() == RotationDirection::Clockwise ? CSSValueCw : CSSValueCcw, + segment.arcSize() == ShapeArcSegment::ArcSize::Large ? CSSValueLarge : CSSValueSmall, + CSSPrimitiveValue::create(segment.angle(), CSSUnitType::CSS_DEG)); + }, + [&](const ShapeCloseSegment&) { + return CSSShapeSegmentValue::createClose(); + } + ); +} + +#pragma mark - + +BasicShapeShape::ShapeSegment fromCSSShapeSegmentValue(const CSSToLengthConversionData& conversionData, const CSSShapeSegmentValue& cssShapeSegment) +{ + return cssShapeSegment.toShapeSegment(conversionData); +} + +} // namespace WebCore diff --git a/Source/WebCore/css/BasicShapesShapeSegmentConversion.h b/Source/WebCore/css/BasicShapesShapeSegmentConversion.h new file mode 100644 index 0000000000000..35fdde0c64351 --- /dev/null +++ b/Source/WebCore/css/BasicShapesShapeSegmentConversion.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 Noam Rosenthal All rights reserved. + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#pragma once + +#include "BasicShapesShape.h" +#include + +namespace WebCore { + +class CSSShapeSegmentValue; +class CSSToLengthConversionData; +class RenderStyle; + +Ref toCSSShapeSegmentValue(const RenderStyle&, const BasicShapeShape::ShapeSegment&); +BasicShapeShape::ShapeSegment fromCSSShapeSegmentValue(const CSSToLengthConversionData&, const CSSShapeSegmentValue&); + +} // namespace WebCore diff --git a/Source/WebCore/css/CSSBasicShapes.cpp b/Source/WebCore/css/CSSBasicShapes.cpp index eeba2d637bbba..71b8e42cc2b89 100644 --- a/Source/WebCore/css/CSSBasicShapes.cpp +++ b/Source/WebCore/css/CSSBasicShapes.cpp @@ -541,8 +541,8 @@ Ref CSSShapeValue::create(WindRule windRule, Ref&& return adoptRef(*new CSSShapeValue(windRule, WTFMove(fromCoordinates), WTFMove(shapeSegments))); } -CSSShapeValue::CSSShapeValue(WindRule windRule, Ref&& fromCoordinates, CSSValueListBuilder&& shapeCommands) - : CSSValueContainingVector(ShapeClass, CommaSeparator, WTFMove(shapeCommands)) +CSSShapeValue::CSSShapeValue(WindRule windRule, Ref&& fromCoordinates, CSSValueListBuilder&& shapeSegments) + : CSSValueContainingVector(ShapeClass, CommaSeparator, WTFMove(shapeSegments)) , m_fromCoordinates(WTFMove(fromCoordinates)) , m_windRule(windRule) { @@ -550,14 +550,28 @@ CSSShapeValue::CSSShapeValue(WindRule windRule, Ref&& fromCoordina String CSSShapeValue::customCSSText() const { - ASSERT_NOT_IMPLEMENTED_YET(); - return ""_s; + StringBuilder builder; + builder.append("shape("_s); + + if (windRule() == WindRule::EvenOdd) + builder.append("evenodd "_s); + + builder.append("from "_s, m_fromCoordinates->cssText(), ", "_s); + serializeItems(builder); + + builder.append(')'); + return builder.toString(); } -bool CSSShapeValue::equals(const CSSShapeValue&) const +bool CSSShapeValue::equals(const CSSShapeValue& other) const { - ASSERT_NOT_IMPLEMENTED_YET(); - return false; + if (windRule() != other.windRule()) + return false; + + if (!compareCSSValue(m_fromCoordinates, other.m_fromCoordinates)) + return false; + + return itemsEqual(other); } } // namespace WebCore diff --git a/Source/WebCore/css/CSSShapeSegmentValue.cpp b/Source/WebCore/css/CSSShapeSegmentValue.cpp index 1325a0b28af16..4d26d187c39c1 100644 --- a/Source/WebCore/css/CSSShapeSegmentValue.cpp +++ b/Source/WebCore/css/CSSShapeSegmentValue.cpp @@ -31,6 +31,7 @@ #include "CSSPrimitiveValue.h" #include "CSSToLengthConversionData.h" #include "CSSValuePair.h" +#include "CalculationValue.h" #include #include @@ -117,7 +118,7 @@ String CSSShapeSegmentValue::customCSSText() const ASSERT(m_data); - const auto command = [&]() { + const auto segmentName = [&]() { switch (type()) { case SegmentType::Move: return "move"_s; @@ -152,7 +153,7 @@ String CSSShapeSegmentValue::customCSSText() const case SegmentType::CubicCurve: case SegmentType::QuadraticCurve: case SegmentType::SmoothCubicCurve: - return " via "_s; + return " using "_s; case SegmentType::Arc: return " of "_s; default: @@ -163,7 +164,7 @@ String CSSShapeSegmentValue::customCSSText() const StringBuilder builder; auto byTo = m_data->affinity == CoordinateAffinity::Absolute ? " to "_s : " by "_s; - builder.append(command(), byTo, m_data->offset->cssText(), conjunction()); + builder.append(segmentName(), byTo, m_data->offset->cssText(), conjunction()); switch (m_data->type) { case SegmentDataType::Base: @@ -199,5 +200,76 @@ String CSSShapeSegmentValue::customCSSText() const return builder.toString(); } -} // namespace WebCore +BasicShapeShape::ShapeSegment CSSShapeSegmentValue::toShapeSegment(const CSSToLengthConversionData& conversionData) const +{ + auto toLength = [&](const CSSValue& value) -> Length { + RefPtr primitiveValue = dynamicDowncast(value); + if (!primitiveValue) + return Length(0, LengthType::Fixed); + + return primitiveValue->convertToLength(conversionData); + }; + + auto toLengthPoint = [&](const CSSValue& value) -> LengthPoint { + RefPtr pairValue = dynamicDowncast(value); + if (!pairValue) + return { }; + + return LengthPoint { toLength(pairValue->first()), toLength(pairValue->second()) }; + }; + + auto toLengthSize = [&](const CSSValue& value) -> LengthSize { + RefPtr pairValue = dynamicDowncast(value); + if (!pairValue) + return { }; + return LengthSize { toLength(pairValue->first()), toLength(pairValue->second()) }; + }; + + auto toDegrees = [](const CSSValue& value) { + RefPtr angleValue = dynamicDowncast(value); + if (!angleValue || !angleValue->isAngle()) + return 0.0; + + return angleValue->computeDegrees(); + }; + + switch (type()) { + case SegmentType::Move: + return ShapeMoveSegment(m_data->affinity, toLengthPoint(m_data->offset)); + case SegmentType::Line: + return ShapeLineSegment(m_data->affinity, toLengthPoint(m_data->offset)); + case SegmentType::HorizontalLine: + return ShapeHorizontalLineSegment(m_data->affinity, toLength(m_data->offset)); + case SegmentType::VerticalLine: + return ShapeVerticalLineSegment(m_data->affinity, toLength(m_data->offset)); + case SegmentType::CubicCurve: { + auto& twoPointData = static_cast(*m_data); + return ShapeCurveSegment(twoPointData.affinity, toLengthPoint(twoPointData.offset), toLengthPoint(twoPointData.p1), toLengthPoint(twoPointData.p2)); + } + case SegmentType::QuadraticCurve: { + auto& onePointData = static_cast(*m_data); + return ShapeCurveSegment(onePointData.affinity, toLengthPoint(onePointData.offset), toLengthPoint(onePointData.p1)); + } + case SegmentType::SmoothCubicCurve: { + auto& onePointData = static_cast(*m_data); + return ShapeSmoothSegment(onePointData.affinity, toLengthPoint(onePointData.offset), toLengthPoint(onePointData.p1)); + } + case SegmentType::SmoothQuadraticCurve: + return ShapeSmoothSegment(m_data->affinity, toLengthPoint(m_data->offset)); + case SegmentType::Arc: { + auto& arcData = static_cast(*m_data); + + auto arcSweep = arcData.arcSweep == CSSValueCcw ? RotationDirection::Counterclockwise : RotationDirection::Clockwise; + auto arcSize = arcData.arcSize == CSSValueLarge ? ShapeArcSegment::ArcSize::Large : ShapeArcSegment::ArcSize::Small; + return ShapeArcSegment(m_data->affinity, toLengthPoint(arcData.offset), toLengthSize(arcData.radius), arcSweep, arcSize, toDegrees(arcData.angle)); + } + case SegmentType::Close: + return ShapeCloseSegment(); + } + + ASSERT_NOT_REACHED(); + return ShapeCloseSegment(); +} + +} // namespace WebCore diff --git a/Source/WebCore/css/CSSShapeSegmentValue.h b/Source/WebCore/css/CSSShapeSegmentValue.h index 4af281ff5a3ef..6e6133e77f2ce 100644 --- a/Source/WebCore/css/CSSShapeSegmentValue.h +++ b/Source/WebCore/css/CSSShapeSegmentValue.h @@ -26,6 +26,7 @@ #pragma once +#include "BasicShapesShape.h" #include "CSSPrimitiveValue.h" #include "CSSValueKeywords.h" #include "CSSValueList.h" @@ -74,6 +75,8 @@ class CSSShapeSegmentValue : public CSSValue { static Ref createArc(CoordinateAffinity, Ref&& offset, Ref&& radius, CSSValueID sweep, CSSValueID size, Ref&& angle); + BasicShapeShape::ShapeSegment toShapeSegment(const CSSToLengthConversionData&) const; + private: enum class SegmentDataType : uint8_t { Base, OnePoint, TwoPoint, Arc }; diff --git a/Source/WebCore/css/CSSValueKeywords.in b/Source/WebCore/css/CSSValueKeywords.in index d0f9ef9479013..e5e085da19022 100644 --- a/Source/WebCore/css/CSSValueKeywords.in +++ b/Source/WebCore/css/CSSValueKeywords.in @@ -1357,7 +1357,7 @@ ccw // small by close -via +using of // @font-face src diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp index 1450347fc66e7..bebe8737b1109 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp @@ -86,6 +86,7 @@ #include "CSSRectValue.h" #include "CSSReflectValue.h" #include "CSSScrollValue.h" +#include "CSSShapeSegmentValue.h" #include "CSSSubgridValue.h" #include "CSSTimingFunctionValue.h" #include "CSSTransformListValue.h" @@ -99,6 +100,7 @@ #include "ColorInterpolation.h" #include "FontCustomPlatformData.h" #include "FontFace.h" +#include "LengthPoint.h" #include "Logging.h" #include "RenderStyleConstants.h" #include "SVGPathByteStream.h" @@ -2606,6 +2608,217 @@ static RefPtr consumeBasicShapePath(CSSParserTokenRange& args, Opt return CSSPathValue::create(WTFMove(byteStream), rule); } +static RefPtr consumeCoordinatePair(CSSParserTokenRange& range, const CSSParserContext& context) +{ + auto xDimension = consumeLengthOrPercent(range, context.mode); + if (!xDimension) + return nullptr; + + auto yDimension = consumeLengthOrPercent(range, context.mode); + if (!yDimension) + return nullptr; + + return CSSValuePair::createNoncoalescing(xDimension.releaseNonNull(), yDimension.releaseNonNull()); +} + +static RefPtr consumeShapeCommand(CSSParserTokenRange& range, const CSSParserContext& context, OptionSet) +{ + if (range.peek().type() != IdentToken) + return nullptr; + + auto consumeAffinity = [&]() -> std::optional { + if (range.peek().type() != IdentToken) + return std::nullopt; + + CSSValueID token = range.peek().id(); + if (token != CSSValueBy && token != CSSValueTo) + return std::nullopt; + + if (!consumeIdent(range)) + return std::nullopt; + + return token == CSSValueBy ? CoordinateAffinity::Relative : CoordinateAffinity::Absolute; + }; + + auto atEndOfCommand = [&] () { + return range.atEnd() || range.peek().type() == CommaToken; + }; + + auto id = range.consumeIncludingWhitespace().id(); + if (id == CSSValueMove) { + // = move + auto affinityValue = consumeAffinity(); + if (!affinityValue) + return nullptr; + + auto toCoordinates = consumeCoordinatePair(range, context); + if (!toCoordinates) + return nullptr; + + return CSSShapeSegmentValue::createMove(*affinityValue, toCoordinates.releaseNonNull()); + } else if (id == CSSValueLine) { + // = line + auto affinityValue = consumeAffinity(); + if (!affinityValue) + return nullptr; + + auto toCoordinates = consumeCoordinatePair(range, context); + if (!toCoordinates) + return nullptr; + + return CSSShapeSegmentValue::createLine(*affinityValue, toCoordinates.releaseNonNull()); + } else if (id == CSSValueHline || id == CSSValueVline) { + // = [hline | vline] + auto affinityValue = consumeAffinity(); + if (!affinityValue) + return nullptr; + + auto length = consumeLengthOrPercent(range, context.mode); + if (!length) + return nullptr; + + if (id == CSSValueHline) + return CSSShapeSegmentValue::createHorizontalLine(*affinityValue, length.releaseNonNull()); + + return CSSShapeSegmentValue::createVerticalLine(*affinityValue, length.releaseNonNull()); + } else if (id == CSSValueCurve) { + // = curve via {1,2} + auto affinityValue = consumeAffinity(); + if (!affinityValue) + return nullptr; + + auto toCoordinates = consumeCoordinatePair(range, context); + if (!toCoordinates) + return nullptr; + + if (!consumeIdent(range)) + return nullptr; + + auto controlPoint1 = consumeCoordinatePair(range, context); + if (!controlPoint1) + return nullptr; + + auto controlPoint2 = consumeCoordinatePair(range, context); + if (controlPoint2) + return CSSShapeSegmentValue::createCubicCurve(*affinityValue, toCoordinates.releaseNonNull(), controlPoint1.releaseNonNull(), controlPoint2.releaseNonNull()); + + return CSSShapeSegmentValue::createQuadraticCurve(*affinityValue, toCoordinates.releaseNonNull(), controlPoint1.releaseNonNull()); + } else if (id == CSSValueSmooth) { + // = smooth [via ]? + auto affinityValue = consumeAffinity(); + if (!affinityValue) + return nullptr; + + auto toCoordinates = consumeCoordinatePair(range, context); + if (!toCoordinates) + return nullptr; + + if (consumeIdent(range)) { + auto controlPoint = consumeCoordinatePair(range, context); + if (!controlPoint) + return nullptr; + + return CSSShapeSegmentValue::createSmoothCubicCurve(*affinityValue, toCoordinates.releaseNonNull(), controlPoint.releaseNonNull()); + } + + return CSSShapeSegmentValue::createSmoothQuadraticCurve(*affinityValue, toCoordinates.releaseNonNull()); + } else if (id == CSSValueArc) { + // arc of {1,2} [ || || rotate ]? + auto affinityValue = consumeAffinity(); + if (!affinityValue) + return nullptr; + + auto toCoordinates = consumeCoordinatePair(range, context); + if (!toCoordinates) + return nullptr; + + if (!consumeIdent(range)) + return nullptr; + + auto radiusX = consumeLengthOrPercent(range, context.mode); + auto radiusY = radiusX; + if (auto value = consumeLengthOrPercent(range, context.mode)) + radiusY = value; + + std::optional sweep; + std::optional size; + RefPtr angle; + + while (!atEndOfCommand()) { + auto ident = consumeIdent(range); + if (!ident) + return nullptr; + + switch (ident->valueID()) { + case CSSValueCw: + case CSSValueCcw: + if (sweep) + return nullptr; + sweep = ident->valueID(); + break; + case CSSValueLarge: + case CSSValueSmall: + if (size) + return nullptr; + size = ident->valueID(); + break; + case CSSValueRotate: + if (angle) + return nullptr; + angle = consumeAngle(range, context.mode, UnitlessQuirk::Forbid, UnitlessZeroQuirk::Forbid); + break; + default: + break; + } + } + + if (!angle) + angle = CSSPrimitiveValue::create(0, CSSUnitType::CSS_DEG); + + auto radius = CSSValuePair::create(radiusX.releaseNonNull(), radiusY.releaseNonNull()); + return CSSShapeSegmentValue::createArc(*affinityValue, toCoordinates.releaseNonNull(), WTFMove(radius), sweep.value_or(CSSValueCcw), size.value_or(CSSValueSmall), angle.releaseNonNull()); + + } else if (id == CSSValueClose) + return CSSShapeSegmentValue::createClose(); + + return nullptr; +} + +// https://drafts.csswg.org/css-shapes-2/#shape-function +static RefPtr consumeBasicShapeShape(CSSParserTokenRange& range, const CSSParserContext& context, OptionSet options) +{ + if (!context.cssShapeFunctionEnabled) + return nullptr; + + // shape() = shape( <'fill-rule'>? from , #) + WindRule rule = WindRule::NonZero; + if (identMatches(range.peek().id())) { + if (range.consumeIncludingWhitespace().id() == CSSValueEvenodd) + rule = WindRule::EvenOdd; + } + + if (!consumeIdent(range)) + return nullptr; + + auto fromCoordinates = consumeCoordinatePair(range, context); + if (!fromCoordinates) + return nullptr; + + if (!consumeCommaIncludingWhitespace(range)) + return nullptr; + + CSSValueListBuilder commands; + do { + auto command = consumeShapeCommand(range, context, options); + if (!command) + return nullptr; + + commands.append(command.releaseNonNull()); + } while (consumeCommaIncludingWhitespace(range)); + + return CSSShapeValue::create(rule, fromCoordinates.releaseNonNull(), WTFMove(commands)); +} + template static void complete4Sides(std::array& sides) { if (!sides[1]) @@ -2745,6 +2958,9 @@ static RefPtr consumeBasicShape(CSSParserTokenRange& range, const CSSP result = consumeBasicShapeXywh(args, context); else if (id == CSSValuePath) result = consumeBasicShapePath(args, options); + else if (id == CSSValueShape) + result = consumeBasicShapeShape(args, context, options); + if (!result || !args.atEnd()) return nullptr; diff --git a/Source/WebCore/rendering/style/BasicShapes.cpp b/Source/WebCore/rendering/style/BasicShapes.cpp index df9778f198a7a..38012373a0fcf 100644 --- a/Source/WebCore/rendering/style/BasicShapes.cpp +++ b/Source/WebCore/rendering/style/BasicShapes.cpp @@ -141,6 +141,8 @@ static const Path& cachedTransformedByteStreamPath(const SVGPathByteStream& stre return cache.get().get(SVGPathTransformedByteStream { stream, zoom, offset }); } +// MARK: - + Ref BasicShapeCircle::create(BasicShapeCenterCoordinate&& centerX, BasicShapeCenterCoordinate&& centerY, BasicShapeRadius&& radius) { return adoptRef(*new BasicShapeCircle(WTFMove(centerX), WTFMove(centerY), WTFMove(radius))); @@ -226,6 +228,8 @@ void BasicShapeCircle::dump(TextStream& ts) const ts.dumpProperty("radius", radius()); } +// MARK: - + Ref BasicShapeEllipse::create(BasicShapeCenterCoordinate&& centerX, BasicShapeCenterCoordinate&& centerY, BasicShapeRadius&& radiusX, BasicShapeRadius&& radiusY) { return adoptRef(*new BasicShapeEllipse(WTFMove(centerX), WTFMove(centerY), WTFMove(radiusX), WTFMove(radiusY))); @@ -327,6 +331,8 @@ void BasicShapeEllipse::dump(TextStream& ts) const ts.dumpProperty("radius-y", radiusY()); } +// MARK: - + Ref BasicShapeRect::create(Length&& top, Length&& right, Length&& bottom, Length&& left, LengthSize&& topLeftRadius, LengthSize&& topRightRadius, LengthSize&& bottomRightRadius, LengthSize&& bottomLeftRadius) { return adoptRef(*new BasicShapeRect(WTFMove(top), WTFMove(right), WTFMove(bottom), WTFMove(left), WTFMove(topLeftRadius), WTFMove(topRightRadius), WTFMove(bottomRightRadius), WTFMove(bottomLeftRadius))); @@ -434,6 +440,8 @@ void BasicShapeRect::dump(TextStream& ts) const ts.dumpProperty("bottom-left-radius", bottomLeftRadius()); } +// MARK: - + Ref BasicShapeXywh::create(Length&& insetX, Length&& insetY, Length&& width, Length&& height, LengthSize&& topLeftRadius, LengthSize&& topRightRadius, LengthSize&& bottomRightRadius, LengthSize&& bottomLeftRadius) { return adoptRef(*new BasicShapeXywh(WTFMove(insetX), WTFMove(insetY), WTFMove(width), WTFMove(height), WTFMove(topLeftRadius), WTFMove(topRightRadius), WTFMove(bottomRightRadius), WTFMove(bottomLeftRadius))); @@ -539,6 +547,8 @@ void BasicShapeXywh::dump(TextStream& ts) const } +// MARK: - + Ref BasicShapePolygon::create(WindRule windRule, Vector&& values) { return adoptRef(*new BasicShapePolygon(windRule, WTFMove(values))); @@ -687,6 +697,8 @@ void BasicShapePath::dump(TextStream& ts) const // FIXME: print the byte stream? } +// MARK: - + Ref BasicShapeInset::create(Length&& right, Length&& top, Length&& bottom, Length&& left, LengthSize&& topLeftRadius, LengthSize&& topRightRadius, LengthSize&& bottomRightRadius, LengthSize&& bottomLeftRadius) { return adoptRef(*new BasicShapeInset(WTFMove(right), WTFMove(top), WTFMove(bottom), WTFMove(left), WTFMove(topLeftRadius), WTFMove(topRightRadius), WTFMove(bottomRightRadius), WTFMove(bottomLeftRadius))); @@ -796,6 +808,17 @@ static TextStream& operator<<(TextStream& ts, BasicShapeRadius::Type radiusType) return ts; } +// MARK: - + +TextStream& operator<<(TextStream& ts, CoordinateAffinity affinity) +{ + switch (affinity) { + case CoordinateAffinity::Relative: ts << "relative"_s; break; + case CoordinateAffinity::Absolute: ts << "absolute"_s; break; + } + return ts; +} + TextStream& operator<<(TextStream& ts, const BasicShapeRadius& radius) { ts.dumpProperty("value", radius.value()); diff --git a/Source/WebCore/rendering/style/BasicShapes.h b/Source/WebCore/rendering/style/BasicShapes.h index 8bc6356f3b6fc..d1a356f693d39 100644 --- a/Source/WebCore/rendering/style/BasicShapes.h +++ b/Source/WebCore/rendering/style/BasicShapes.h @@ -55,17 +55,19 @@ enum class CoordinateAffinity : uint8_t { }; class BasicShape : public RefCounted { + WTF_MAKE_FAST_ALLOCATED; public: virtual ~BasicShape() = default; - enum class Type { + enum class Type : uint8_t { Polygon, Path, Circle, Ellipse, Inset, Rect, - Xywh + Xywh, + Shape }; virtual Ref clone() const = 0; @@ -495,6 +497,7 @@ class BasicShapeXywh final : public BasicShape { LengthSize m_bottomLeftRadius; }; +WTF::TextStream& operator<<(WTF::TextStream&, CoordinateAffinity); WTF::TextStream& operator<<(WTF::TextStream&, const BasicShapeRadius&); WTF::TextStream& operator<<(WTF::TextStream&, const BasicShapeCenterCoordinate&); WTF::TextStream& operator<<(WTF::TextStream&, const BasicShape&); diff --git a/Source/WebCore/rendering/style/BasicShapesShape.cpp b/Source/WebCore/rendering/style/BasicShapesShape.cpp new file mode 100644 index 0000000000000..f7630a59821a3 --- /dev/null +++ b/Source/WebCore/rendering/style/BasicShapesShape.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#include "BasicShapesShape.h" + +#include "AnimationUtilities.h" +#include "BasicShapeConversion.h" +#include "CalculationValue.h" +#include "FloatRect.h" +#include "FloatRoundedRect.h" +#include "LengthFunctions.h" +#include "Path.h" +#include "RenderBox.h" +#include +#include +#include + +namespace WebCore { + +Ref BasicShapeShape::create(WindRule windRule, const CoordinatePair& startPoint, Vector&& commands) +{ + return adoptRef(* new BasicShapeShape(windRule, startPoint, WTFMove(commands))); +} + +BasicShapeShape::BasicShapeShape(WindRule windRule, const CoordinatePair& startPoint, Vector&& commands) + : m_startPoint(startPoint) + , m_windRule(windRule) + , m_segments(WTFMove(commands)) +{ +} + +Ref BasicShapeShape::clone() const +{ + auto segmentsCopy = m_segments; + return BasicShapeShape::create(windRule(), startPoint(), WTFMove(segmentsCopy)); +} + +Path BasicShapeShape::path(const FloatRect&) const +{ + // Not yet implemented. + return Path(); +} + +bool BasicShapeShape::canBlend(const BasicShape&) const +{ + // Not yet implemented. + return false; +} + +Ref BasicShapeShape::blend(const BasicShape&, const BlendingContext&) const +{ + // Not yet implemented. + return BasicShapeShape::clone(); // FIXME wrong. +} + +bool BasicShapeShape::operator==(const BasicShape& other) const +{ + if (type() != other.type()) + return false; + + const auto& otherShape = downcast(other); + if (windRule() != otherShape.windRule()) + return false; + + if (startPoint() != otherShape.startPoint()) + return false; + + return segments() == otherShape.segments(); +} + +void BasicShapeShape::dump(TextStream& stream) const +{ + stream.dumpProperty("wind rule", windRule()); + stream.dumpProperty("start point", startPoint()); + stream << segments(); +} + +TextStream& operator<<(TextStream& stream, const BasicShapeShape::ShapeSegment& segment) +{ + WTF::switchOn(segment, + [&](const ShapeMoveSegment& segment) { + stream << "move" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.offset(); + }, + [&](const ShapeLineSegment& segment) { + stream << "line" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.offset(); + }, + [&](const ShapeHorizontalLineSegment& segment) { + stream << "hline" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.length(); + }, + [&](const ShapeVerticalLineSegment& segment) { + stream << "vline" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.length(); + }, + [&](const ShapeCurveSegment& segment) { + stream << "curve" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.offset() << " using " << segment.controlPoint1(); + if (segment.controlPoint2()) + stream << " " << segment.controlPoint2().value(); + }, + [&](const ShapeSmoothSegment& segment) { + stream << "smooth" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.offset(); + if (segment.intermediatePoint()) + stream << " using " << segment.intermediatePoint().value(); + }, + [&](const ShapeArcSegment& segment) { + stream << "arc" << (segment.affinity() == CoordinateAffinity::Relative ? " by "_s : " to "_s) << segment.offset() << " of " << segment.ellipseSize(); + stream << " " << segment.sweep() << (segment.arcSize() == ShapeArcSegment::ArcSize::Small ? " small " : " large ") << " " << segment.angle() << "deg"; + }, + [&](const ShapeCloseSegment&) { + stream << "close"; + } + ); + + return stream; +} + +} // namespace WebCore diff --git a/Source/WebCore/rendering/style/BasicShapesShape.h b/Source/WebCore/rendering/style/BasicShapesShape.h new file mode 100644 index 0000000000000..7516ee1361208 --- /dev/null +++ b/Source/WebCore/rendering/style/BasicShapesShape.h @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#pragma once + +#include "BasicShapes.h" +#include "LengthPoint.h" +#include "LengthSize.h" +#include "RotationDirection.h" + +namespace WTF { +class TextStream; +} + +namespace WebCore { + +using CoordinatePair = LengthPoint; + +class ShapeSegmentBase { +public: + explicit ShapeSegmentBase(CoordinateAffinity affinity) + : m_affinity(affinity) + { } + + bool operator==(const ShapeSegmentBase&) const = default; + + CoordinateAffinity affinity() const { return m_affinity; } + +private: + const CoordinateAffinity m_affinity; +}; + +class ShapeMoveSegment final : public ShapeSegmentBase { +public: + ShapeMoveSegment(CoordinateAffinity affinity, CoordinatePair&& offset) + : ShapeSegmentBase(affinity) + , m_offset(WTFMove(offset)) + { + } + + const CoordinatePair& offset() const { return m_offset; } + + bool operator==(const ShapeMoveSegment&) const = default; + +private: + CoordinatePair m_offset; +}; + +class ShapeLineSegment final : public ShapeSegmentBase { +public: + ShapeLineSegment(CoordinateAffinity affinity, CoordinatePair&& offset) + : ShapeSegmentBase(affinity) + , m_offset(WTFMove(offset)) + { + } + + const CoordinatePair& offset() const { return m_offset; } + + bool operator==(const ShapeLineSegment&) const = default; + +private: + CoordinatePair m_offset; +}; + +class ShapeHorizontalLineSegment final : public ShapeSegmentBase { +public: + ShapeHorizontalLineSegment(CoordinateAffinity affinity, Length&& length) + : ShapeSegmentBase(affinity) + , m_length(WTFMove(length)) + { + } + + Length length() const { return m_length; } + + bool operator==(const ShapeHorizontalLineSegment&) const = default; + +private: + Length m_length; +}; + +class ShapeVerticalLineSegment final : public ShapeSegmentBase { +public: + ShapeVerticalLineSegment(CoordinateAffinity affinity, Length&& length) + : ShapeSegmentBase(affinity) + , m_length(WTFMove(length)) + { + } + + Length length() const { return m_length; } + + bool operator==(const ShapeVerticalLineSegment&) const = default; + +private: + Length m_length; +}; + +class ShapeCurveSegment final : public ShapeSegmentBase { +public: + ShapeCurveSegment(CoordinateAffinity affinity, CoordinatePair&& offset, CoordinatePair&& controlPoint1, std::optional&& controlPoint2 = std::nullopt) + : ShapeSegmentBase(affinity) + , m_offset(WTFMove(offset)) + , m_controlPoint1(WTFMove(controlPoint1)) + , m_controlPoint2(WTFMove(controlPoint2)) + { + } + + const CoordinatePair& offset() const { return m_offset; } + const CoordinatePair& controlPoint1() const { return m_controlPoint1; } + const std::optional& controlPoint2() const { return m_controlPoint2; }; + + bool operator==(const ShapeCurveSegment&) const = default; + +private: + CoordinatePair m_offset; + CoordinatePair m_controlPoint1; + std::optional m_controlPoint2; +}; + +class ShapeSmoothSegment final : public ShapeSegmentBase { +public: + ShapeSmoothSegment(CoordinateAffinity affinity, CoordinatePair&& offset, std::optional&& intermediatePoint = std::nullopt) + : ShapeSegmentBase(affinity) + , m_offset(WTFMove(offset)) + , m_intermediatePoint(WTFMove(intermediatePoint)) + { + } + + const CoordinatePair& offset() const { return m_offset; } + const std::optional& intermediatePoint() const { return m_intermediatePoint; }; + + bool operator==(const ShapeSmoothSegment&) const = default; + +private: + CoordinatePair m_offset; + std::optional m_intermediatePoint; +}; + +class ShapeArcSegment final : public ShapeSegmentBase { +public: + enum class ArcSize : uint8_t { Small, Large }; + using AngleDegrees = double; + + ShapeArcSegment(CoordinateAffinity affinity, CoordinatePair&& offset, LengthSize&& ellipseSize, RotationDirection sweep, ArcSize arcSize, AngleDegrees angle) + : ShapeSegmentBase(affinity) + , m_offset(WTFMove(offset)) + , m_ellipseSize(WTFMove(ellipseSize)) + , m_arcSweep(sweep) + , m_arcSize(arcSize) + , m_angle(angle) + { + } + + const CoordinatePair& offset() const { return m_offset; } + const LengthSize& ellipseSize() const { return m_ellipseSize; } + RotationDirection sweep() const { return m_arcSweep; } + ArcSize arcSize() const { return m_arcSize; } + AngleDegrees angle() const { return m_angle; } + + bool operator==(const ShapeArcSegment&) const = default; + +private: + CoordinatePair m_offset; + LengthSize m_ellipseSize; + RotationDirection m_arcSweep { RotationDirection::Counterclockwise }; + ArcSize m_arcSize { ArcSize::Small }; + AngleDegrees m_angle { 0 }; +}; + +class ShapeCloseSegment { +public: + bool operator==(const ShapeCloseSegment&) const = default; +}; + +// https://drafts.csswg.org/css-shapes-2/#shape-function +class BasicShapeShape final : public BasicShape { +public: + Ref clone() const final; + + Type type() const final { return Type::Shape; } + const CoordinatePair& startPoint() const { return m_startPoint; } + + Path path(const FloatRect&) const final; + + bool canBlend(const BasicShape&) const final; + Ref blend(const BasicShape& from, const BlendingContext&) const final; + + bool operator==(const BasicShape&) const final; + + void dump(TextStream&) const final; + + using ShapeSegment = std::variant< + ShapeMoveSegment, + ShapeLineSegment, + ShapeHorizontalLineSegment, + ShapeVerticalLineSegment, + ShapeCurveSegment, + ShapeSmoothSegment, + ShapeArcSegment, + ShapeCloseSegment + >; + + const Vector& segments() const { return m_segments; } + + static Ref create(WindRule, const CoordinatePair&, Vector&&); + +private: + BasicShapeShape(WindRule, const CoordinatePair&, Vector&&); + + CoordinatePair m_startPoint; + const WindRule m_windRule { WindRule::NonZero }; + Vector m_segments; +}; + +WTF::TextStream& operator<<(WTF::TextStream&, const BasicShapeShape::ShapeSegment&); + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_BASIC_SHAPE(BasicShapeShape, BasicShape::Type::Shape)