| /* |
| * Copyright (C) 2023 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 INC. ``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 INC. OR |
| * 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 "MotionPath.h" |
| |
| #include "BorderShape.h" |
| #include "GeometryUtilities.h" |
| #include "PathOperation.h" |
| #include "PathTraversalState.h" |
| #include "RenderBlock.h" |
| #include "RenderObjectInlines.h" |
| #include "StyleComputedStyle+GettersInlines.h" |
| #include "TransformOperationData.h" |
| #include "TransformationMatrix.h" |
| |
| namespace WebCore { |
| |
| static FloatPoint offsetFromContainer(const RenderObject& renderer, RenderBlock& container, const FloatRect& referenceRect) |
| { |
| auto offsetFromContainingBlock = renderer.offsetFromContainer(container, LayoutPoint()); |
| return FloatPoint(FloatPoint(offsetFromContainingBlock) - referenceRect.location()); |
| } |
| |
| static FloatRoundedRect containingBlockRectForRenderer(const RenderObject& renderer, RenderBlock& container, const Style::OffsetPath& offsetPath) |
| { |
| return WTF::switchOn(offsetPath, |
| [&](const Style::BoxPath& offsetPath) -> FloatRoundedRect { |
| auto referenceBox = offsetPath.referenceBox(); |
| auto referenceRect = container.referenceBoxRect(referenceBox); |
| auto borderShape = BorderShape::shapeForBorderRect(container.style(), LayoutRect(referenceRect)); |
| return borderShape.deprecatedPixelSnappedRoundedRect(protect(container.document())->deviceScaleFactor()); |
| }, |
| [&](const auto& offsetPath) -> FloatRoundedRect { |
| auto referenceBox = offsetPath.referenceBox(); |
| auto snappedRect = snapRectToDevicePixelsIfNeeded(container.referenceBoxRect(referenceBox), downcast<RenderLayerModelObject>(renderer)); |
| return FloatRoundedRect { snappedRect }; |
| }, |
| [&](const CSS::Keyword::None&) -> FloatRoundedRect { |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| ); |
| } |
| |
| static FloatPoint NODELETE normalPositionForOffsetPath(const Style::OffsetPath& offsetPath, const FloatRect& referenceRect) |
| { |
| if (WTF::holdsAlternative<Style::RayPath>(offsetPath) || WTF::holdsAlternative<Style::BasicShapePath>(offsetPath)) |
| return referenceRect.center(); |
| return { }; |
| } |
| |
| std::optional<MotionPathData> MotionPath::motionPathDataForRenderer(const RenderElement& renderer) |
| { |
| if (!is<RenderLayerModelObject>(renderer)) |
| return std::nullopt; |
| |
| auto& offsetPath = renderer.style().offsetPath(); |
| bool canBuildMotionPathData = WTF::switchOn(offsetPath, |
| [](const CSS::Keyword::None&) { |
| return false; |
| }, |
| [](const Style::BasicShapePath& offsetPath) { |
| return !std::holds_alternative<Style::PathFunction>(offsetPath.shape()); |
| }, |
| [](const auto&) { |
| return true; |
| } |
| ); |
| if (!canBuildMotionPathData) |
| return std::nullopt; |
| |
| auto startingPositionForOffsetPosition = [&](const Style::OffsetPosition& offsetPosition, const FloatRect& referenceRect, RenderBlock& container, Style::ZoomFactor zoom) -> FloatPoint { |
| return WTF::switchOn(offsetPosition, |
| [&](const CSS::Keyword::Normal&) { |
| // If offset-position is normal, the element does not have an offset starting position. |
| return normalPositionForOffsetPath(offsetPath, referenceRect); |
| }, |
| [&](const CSS::Keyword::Auto&) { |
| // If offset-position is auto, use top / left corner of the box. |
| return offsetFromContainer(renderer, container, referenceRect); |
| }, |
| [&](const Style::Position& position) { |
| return Style::evaluate<FloatPoint>(position, referenceRect.size(), zoom); |
| } |
| ); |
| }; |
| |
| CheckedPtr container = renderer.containingBlock(); |
| if (!container) |
| return std::nullopt; |
| |
| MotionPathData data; |
| data.containingBlockBoundingRect = containingBlockRectForRenderer(renderer, *container, offsetPath); |
| data.cornerCurvatures = BorderShape::shapeForBorderRect(container->style(), container->borderBoxRect()).cornerCurvatures(); |
| data.offsetFromContainingBlock = offsetFromContainer(renderer, *container, data.containingBlockBoundingRect.rect()); |
| |
| auto zoom = renderer.style().usedZoomForLength(); |
| auto& offsetPosition = renderer.style().offsetPosition(); |
| |
| WTF::switchOn(offsetPath, |
| [&](const Style::BasicShapePath&) { |
| data.usedStartingPosition = startingPositionForOffsetPosition(offsetPosition, data.containingBlockBoundingRect.rect(), *container, zoom); |
| }, |
| [&](const Style::RayPath& offsetPath) { |
| auto startingPosition = offsetPath.ray()->position; |
| data.usedStartingPosition = startingPosition |
| ? Style::evaluate<FloatPoint>(*startingPosition, data.containingBlockBoundingRect.rect().size(), zoom) |
| : startingPositionForOffsetPosition(offsetPosition, data.containingBlockBoundingRect.rect(), *container, zoom); |
| }, |
| [&](const auto&) { } |
| ); |
| |
| return data; |
| } |
| |
| static PathTraversalState traversalStateAtDistance(const Path& path, float distanceValue) |
| { |
| auto pathLength = path.length(); |
| float resolvedLength = 0; |
| if (path.isClosed()) { |
| if (pathLength) { |
| resolvedLength = fmod(distanceValue, pathLength); |
| if (resolvedLength < 0) |
| resolvedLength += pathLength; |
| } |
| } else |
| resolvedLength = clampTo<float>(distanceValue, 0, pathLength); |
| |
| ASSERT(resolvedLength >= 0); |
| return path.traversalStateAtLength(resolvedLength); |
| } |
| |
| void MotionPath::applyMotionPathTransform(TransformationMatrix& matrix, const TransformOperationData& transformData, FloatPoint transformOrigin, TransformBox transformBox, const Path& offsetPath, std::optional<FloatPoint> offsetAnchor, float offsetDistance, float offsetRotate, bool offsetRotateHasAuto) |
| { |
| auto boundingBox = transformData.boundingBox; |
| auto anchor = transformOrigin; |
| |
| if (offsetAnchor) |
| anchor = *offsetAnchor + boundingBox.location(); |
| |
| auto shiftToOrigin = anchor - transformOrigin; |
| |
| if (transformData.isSVGRenderer && transformBox != TransformBox::ViewBox) |
| anchor += boundingBox.location(); |
| |
| auto traversalState = traversalStateAtDistance(offsetPath, offsetDistance); |
| matrix.translate(traversalState.current().x(), traversalState.current().y()); |
| |
| // Shift element to the anchor specified by offset-anchor. |
| matrix.translate(-anchor.x(), -anchor.y()); |
| |
| matrix.translate(shiftToOrigin.width(), shiftToOrigin.height()); |
| |
| // Apply rotation. |
| if (offsetRotateHasAuto) |
| matrix.rotate(traversalState.normalAngle() + offsetRotate); |
| else |
| matrix.rotate(offsetRotate); |
| |
| matrix.translate(-shiftToOrigin.width(), -shiftToOrigin.height()); |
| } |
| |
| bool MotionPath::needsUpdateAfterContainingBlockLayout(const Style::OffsetPath& offsetPath) |
| { |
| return WTF::holdsAlternative<Style::RayPath>(offsetPath) |
| || WTF::holdsAlternative<Style::BoxPath>(offsetPath) |
| || WTF::holdsAlternative<Style::BasicShapePath>(offsetPath); |
| } |
| |
| FloatPoint NODELETE MotionPathData::currentOffset() const |
| { |
| return FloatPoint(usedStartingPosition - offsetFromContainingBlock); |
| } |
| |
| FloatRoundedRect NODELETE MotionPathData::offsetRect() const |
| { |
| auto rect = containingBlockBoundingRect; |
| auto shiftedPoint = offsetFromContainingBlock; |
| shiftedPoint.scale(-1); |
| rect.setLocation(shiftedPoint); |
| return rect; |
| } |
| |
| } // namespace WebCore |