blob: 7b2c563cd6110f46172234f8f55b11b162b63d57 [file]
/*
* 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