blob: 054f6eeb66182f3728ddf2c41994a9c89338d324 [file] [log] [blame]
/*
* Copyright (C) 2011 Adobe Systems Incorporated. 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 "CSSBasicShapes.h"
#include "CSSMarkup.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSValuePool.h"
#include "Pair.h"
#include "SVGPathByteStream.h"
#include "SVGPathUtilities.h"
#include <wtf/text/StringBuilder.h>
namespace WebCore {
static String serializePositionOffset(const Pair& offset, const Pair& other)
{
if ((offset.first()->valueID() == CSSValueLeft && other.first()->valueID() == CSSValueTop)
|| (offset.first()->valueID() == CSSValueTop && other.first()->valueID() == CSSValueLeft))
return offset.second()->cssText();
return offset.cssText();
}
static Ref<CSSPrimitiveValue> buildSerializablePositionOffset(CSSPrimitiveValue* offset, CSSValueID defaultSide)
{
CSSValueID side = defaultSide;
RefPtr<CSSPrimitiveValue> amount;
if (!offset)
side = CSSValueCenter;
else if (offset->isValueID())
side = offset->valueID();
else if (Pair* pair = offset->pairValue()) {
side = pair->first()->valueID();
amount = pair->second();
} else
amount = offset;
auto& cssValuePool = CSSValuePool::singleton();
if (!amount)
amount = cssValuePool.createValue(Length(side == CSSValueCenter ? 50 : 0, LengthType::Percent));
if (side == CSSValueCenter)
side = defaultSide;
else if ((side == CSSValueRight || side == CSSValueBottom)
&& amount->isPercentage()) {
side = defaultSide;
amount = cssValuePool.createValue(Length(100 - amount->floatValue(), LengthType::Percent));
} else if (amount->isLength() && !amount->floatValue()) {
if (side == CSSValueRight || side == CSSValueBottom)
amount = cssValuePool.createValue(Length(100, LengthType::Percent));
else
amount = cssValuePool.createValue(Length(0, LengthType::Percent));
side = defaultSide;
}
return cssValuePool.createValue(Pair::create(cssValuePool.createValue(side), WTFMove(amount)));
}
static String buildCircleString(const String& radius, const String& centerX, const String& centerY)
{
StringBuilder result;
result.append("circle(");
if (!radius.isNull())
result.append(radius);
if (!centerX.isNull() || !centerY.isNull()) {
if (!radius.isNull())
result.append(' ');
result.append("at ", centerX, ' ', centerY);
}
result.append(')');
return result.toString();
}
String CSSBasicShapeCircle::cssText() const
{
Ref<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX.get(), CSSValueLeft);
Ref<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY.get(), CSSValueTop);
String radius;
if (m_radius && m_radius->valueID() != CSSValueClosestSide)
radius = m_radius->cssText();
return buildCircleString(radius,
serializePositionOffset(*normalizedCX->pairValue(), *normalizedCY->pairValue()),
serializePositionOffset(*normalizedCY->pairValue(), *normalizedCX->pairValue()));
}
bool CSSBasicShapeCircle::equals(const CSSBasicShape& shape) const
{
if (!is<CSSBasicShapeCircle>(shape))
return false;
const CSSBasicShapeCircle& other = downcast<CSSBasicShapeCircle>(shape);
return compareCSSValuePtr(m_centerX, other.m_centerX)
&& compareCSSValuePtr(m_centerY, other.m_centerY)
&& compareCSSValuePtr(m_radius, other.m_radius);
}
static String buildEllipseString(const String& radiusX, const String& radiusY, const String& centerX, const String& centerY)
{
StringBuilder result;
result.append("ellipse(");
bool needsSeparator = false;
if (!radiusX.isNull()) {
result.append(radiusX);
needsSeparator = true;
}
if (!radiusY.isNull()) {
if (needsSeparator)
result.append(' ');
result.append(radiusY);
needsSeparator = true;
}
if (!centerX.isNull() || !centerY.isNull()) {
if (needsSeparator)
result.append(' ');
result.append("at ", centerX, ' ', centerY);
}
result.append(')');
return result.toString();
}
String CSSBasicShapeEllipse::cssText() const
{
Ref<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX.get(), CSSValueLeft);
Ref<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY.get(), CSSValueTop);
String radiusX;
String radiusY;
if (m_radiusX) {
ASSERT(m_radiusY);
bool radiusXClosestSide = m_radiusX->valueID() == CSSValueClosestSide;
bool radiusYClosestSide = m_radiusY->valueID() == CSSValueClosestSide;
if (!radiusXClosestSide || !radiusYClosestSide) {
radiusX = m_radiusX->cssText();
radiusY = m_radiusY->cssText();
}
}
return buildEllipseString(radiusX, radiusY,
serializePositionOffset(*normalizedCX->pairValue(), *normalizedCY->pairValue()),
serializePositionOffset(*normalizedCY->pairValue(), *normalizedCX->pairValue()));
}
bool CSSBasicShapeEllipse::equals(const CSSBasicShape& shape) const
{
if (!is<CSSBasicShapeEllipse>(shape))
return false;
const CSSBasicShapeEllipse& other = downcast<CSSBasicShapeEllipse>(shape);
return compareCSSValuePtr(m_centerX, other.m_centerX)
&& compareCSSValuePtr(m_centerY, other.m_centerY)
&& compareCSSValuePtr(m_radiusX, other.m_radiusX)
&& compareCSSValuePtr(m_radiusY, other.m_radiusY);
}
CSSBasicShapePath::CSSBasicShapePath(std::unique_ptr<SVGPathByteStream>&& pathData)
: m_byteStream(WTFMove(pathData))
{
}
static String buildPathString(const WindRule& windRule, const String& path, const String& box)
{
StringBuilder result;
if (windRule == WindRule::EvenOdd)
result.append("path(evenodd, ");
else
result.append("path(");
serializeString(path, result);
result.append(')');
if (box.length()) {
result.append(' ');
result.append(box);
}
return result.toString();
}
String CSSBasicShapePath::cssText() const
{
String pathString;
buildStringFromByteStream(*m_byteStream, pathString, UnalteredParsing);
return buildPathString(m_windRule, pathString, m_referenceBox ? m_referenceBox->cssText() : String());
}
bool CSSBasicShapePath::equals(const CSSBasicShape& otherShape) const
{
if (!is<CSSBasicShapePath>(otherShape))
return false;
auto& otherShapePath = downcast<CSSBasicShapePath>(otherShape);
return windRule() == otherShapePath.windRule() && pathData() == otherShapePath.pathData();
}
static String buildPolygonString(const WindRule& windRule, const Vector<String>& points)
{
ASSERT(!(points.size() % 2));
StringBuilder result;
char evenOddOpening[] = "polygon(evenodd, ";
char nonZeroOpening[] = "polygon(";
char commaSeparator[] = ", ";
static_assert(sizeof(evenOddOpening) >= sizeof(nonZeroOpening), "polygon evenodd is longest string opening");
// Compute the required capacity in advance to reduce allocations.
size_t length = sizeof(evenOddOpening) - 1;
for (size_t i = 0; i < points.size(); i += 2) {
if (i)
length += (sizeof(commaSeparator) - 1);
// add length of two strings, plus one for the space separator.
length += points[i].length() + 1 + points[i + 1].length();
}
result.reserveCapacity(length);
if (windRule == WindRule::EvenOdd)
result.append(evenOddOpening);
else
result.append(nonZeroOpening);
for (size_t i = 0; i < points.size(); i += 2) {
if (i)
result.append(commaSeparator);
result.append(points[i], ' ', points[i + 1]);
}
result.append(')');
return result.toString();
}
String CSSBasicShapePolygon::cssText() const
{
auto points = m_values.map([](auto& shapeValue) {
return shapeValue->cssText();
});
return buildPolygonString(m_windRule, WTFMove(points));
}
bool CSSBasicShapePolygon::equals(const CSSBasicShape& shape) const
{
if (!is<CSSBasicShapePolygon>(shape))
return false;
return compareCSSValueVector<CSSPrimitiveValue>(m_values, downcast<CSSBasicShapePolygon>(shape).m_values);
}
static bool buildInsetRadii(Vector<String>& radii, const String& topLeftRadius, const String& topRightRadius, const String& bottomRightRadius, const String& bottomLeftRadius)
{
bool showBottomLeft = topRightRadius != bottomLeftRadius;
bool showBottomRight = showBottomLeft || (bottomRightRadius != topLeftRadius);
bool showTopRight = showBottomRight || (topRightRadius != topLeftRadius);
radii.append(topLeftRadius);
if (showTopRight)
radii.append(topRightRadius);
if (showBottomRight)
radii.append(bottomRightRadius);
if (showBottomLeft)
radii.append(bottomLeftRadius);
return radii.size() == 1 && radii[0] == "0px"_s;
}
static String buildInsetString(const String& top, const String& right, const String& bottom, const String& left,
const String& topLeftRadiusWidth, const String& topLeftRadiusHeight,
const String& topRightRadiusWidth, const String& topRightRadiusHeight,
const String& bottomRightRadiusWidth, const String& bottomRightRadiusHeight,
const String& bottomLeftRadiusWidth, const String& bottomLeftRadiusHeight)
{
StringBuilder result;
result.append("inset(", top);
bool showLeftArg = !left.isNull() && left != right;
bool showBottomArg = !bottom.isNull() && (bottom != top || showLeftArg);
bool showRightArg = !right.isNull() && (right != top || showBottomArg);
if (showRightArg)
result.append(' ', right);
if (showBottomArg)
result.append(' ', bottom);
if (showLeftArg)
result.append(' ', left);
if (!topLeftRadiusWidth.isNull() && !topLeftRadiusHeight.isNull()) {
Vector<String> horizontalRadii;
bool areDefaultCornerRadii = buildInsetRadii(horizontalRadii, topLeftRadiusWidth, topRightRadiusWidth, bottomRightRadiusWidth, bottomLeftRadiusWidth);
Vector<String> verticalRadii;
areDefaultCornerRadii &= buildInsetRadii(verticalRadii, topLeftRadiusHeight, topRightRadiusHeight, bottomRightRadiusHeight, bottomLeftRadiusHeight);
if (!areDefaultCornerRadii) {
result.append(" round");
for (auto& radius : horizontalRadii)
result.append(' ', radius);
if (verticalRadii.size() != horizontalRadii.size()
|| !WTF::VectorComparer<false, String>::compare(verticalRadii.data(), horizontalRadii.data(), verticalRadii.size())) {
result.append(" /");
for (auto& radius : verticalRadii)
result.append(' ', radius);
}
}
}
result.append(')');
return result.toString();
}
static inline void updateCornerRadiusWidthAndHeight(CSSPrimitiveValue* corner, String& width, String& height)
{
if (!corner)
return;
Pair* radius = corner->pairValue();
width = radius->first() ? radius->first()->cssText() : "0"_str;
if (radius->second())
height = radius->second()->cssText();
}
String CSSBasicShapeInset::cssText() const
{
String topLeftRadiusWidth;
String topLeftRadiusHeight;
String topRightRadiusWidth;
String topRightRadiusHeight;
String bottomRightRadiusWidth;
String bottomRightRadiusHeight;
String bottomLeftRadiusWidth;
String bottomLeftRadiusHeight;
updateCornerRadiusWidthAndHeight(topLeftRadius(), topLeftRadiusWidth, topLeftRadiusHeight);
updateCornerRadiusWidthAndHeight(topRightRadius(), topRightRadiusWidth, topRightRadiusHeight);
updateCornerRadiusWidthAndHeight(bottomRightRadius(), bottomRightRadiusWidth, bottomRightRadiusHeight);
updateCornerRadiusWidthAndHeight(bottomLeftRadius(), bottomLeftRadiusWidth, bottomLeftRadiusHeight);
return buildInsetString(m_top ? m_top->cssText() : String(),
m_right ? m_right->cssText() : String(),
m_bottom ? m_bottom->cssText() : String(),
m_left ? m_left->cssText() : String(),
topLeftRadiusWidth,
topLeftRadiusHeight,
topRightRadiusWidth,
topRightRadiusHeight,
bottomRightRadiusWidth,
bottomRightRadiusHeight,
bottomLeftRadiusWidth,
bottomLeftRadiusHeight);
}
bool CSSBasicShapeInset::equals(const CSSBasicShape& shape) const
{
if (!is<CSSBasicShapeInset>(shape))
return false;
const CSSBasicShapeInset& other = downcast<CSSBasicShapeInset>(shape);
return compareCSSValuePtr(m_top, other.m_top)
&& compareCSSValuePtr(m_right, other.m_right)
&& compareCSSValuePtr(m_bottom, other.m_bottom)
&& compareCSSValuePtr(m_left, other.m_left)
&& compareCSSValuePtr(m_topLeftRadius, other.m_topLeftRadius)
&& compareCSSValuePtr(m_topRightRadius, other.m_topRightRadius)
&& compareCSSValuePtr(m_bottomRightRadius, other.m_bottomRightRadius)
&& compareCSSValuePtr(m_bottomLeftRadius, other.m_bottomLeftRadius);
}
} // namespace WebCore