mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
416 lines
15 KiB
C++
416 lines
15 KiB
C++
/*
|
|
* 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 "sky/engine/core/css/CSSBasicShapes.h"
|
|
|
|
#include "sky/engine/core/css/CSSValuePool.h"
|
|
#include "sky/engine/core/css/Pair.h"
|
|
#include "sky/engine/platform/Length.h"
|
|
#include "sky/engine/wtf/text/StringBuilder.h"
|
|
|
|
using namespace WTF;
|
|
|
|
namespace blink {
|
|
|
|
DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(CSSBasicShape)
|
|
|
|
static String buildCircleString(const String& radius, const String& centerX, const String& centerY, const String& box)
|
|
{
|
|
char at[] = "at";
|
|
char separator[] = " ";
|
|
StringBuilder result;
|
|
result.appendLiteral("circle(");
|
|
if (!radius.isNull())
|
|
result.append(radius);
|
|
|
|
if (!centerX.isNull() || !centerY.isNull()) {
|
|
if (!radius.isNull())
|
|
result.appendLiteral(separator);
|
|
result.append(at);
|
|
result.appendLiteral(separator);
|
|
result.append(centerX);
|
|
result.appendLiteral(separator);
|
|
result.append(centerY);
|
|
}
|
|
result.append(')');
|
|
if (box.length()) {
|
|
result.appendLiteral(separator);
|
|
result.append(box);
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
static String serializePositionOffset(const Pair& offset, const Pair& other)
|
|
{
|
|
if ((offset.first()->getValueID() == CSSValueLeft && other.first()->getValueID() == CSSValueTop)
|
|
|| (offset.first()->getValueID() == CSSValueTop && other.first()->getValueID() == CSSValueLeft))
|
|
return offset.second()->cssText();
|
|
return offset.cssText();
|
|
}
|
|
|
|
static PassRefPtr<CSSPrimitiveValue> buildSerializablePositionOffset(PassRefPtr<CSSPrimitiveValue> offset, CSSValueID defaultSide)
|
|
{
|
|
CSSValueID side = defaultSide;
|
|
RefPtr<CSSPrimitiveValue> amount = nullptr;
|
|
|
|
if (!offset) {
|
|
side = CSSValueCenter;
|
|
} else if (offset->isValueID()) {
|
|
side = offset->getValueID();
|
|
} else if (Pair* pair = offset->getPairValue()) {
|
|
side = pair->first()->getValueID();
|
|
amount = pair->second();
|
|
} else {
|
|
amount = offset;
|
|
}
|
|
|
|
if (side == CSSValueCenter) {
|
|
side = defaultSide;
|
|
amount = cssValuePool().createValue(50, CSSPrimitiveValue::CSS_PERCENTAGE);
|
|
} else if ((side == CSSValueRight || side == CSSValueBottom)
|
|
&& amount->isPercentage()) {
|
|
side = defaultSide;
|
|
amount = cssValuePool().createValue(100 - amount->getFloatValue(), CSSPrimitiveValue::CSS_PERCENTAGE);
|
|
} else if (amount->isLength() && !amount->getFloatValue()) {
|
|
if (side == CSSValueRight || side == CSSValueBottom)
|
|
amount = cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE);
|
|
else
|
|
amount = cssValuePool().createValue(0, CSSPrimitiveValue::CSS_PERCENTAGE);
|
|
side = defaultSide;
|
|
}
|
|
|
|
return cssValuePool().createValue(Pair::create(cssValuePool().createValue(side), amount.release(), Pair::KeepIdenticalValues));
|
|
}
|
|
|
|
String CSSBasicShapeCircle::cssText() const
|
|
{
|
|
RefPtr<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX, CSSValueLeft);
|
|
RefPtr<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY, CSSValueTop);
|
|
|
|
String radius;
|
|
if (m_radius && m_radius->getValueID() != CSSValueClosestSide)
|
|
radius = m_radius->cssText();
|
|
|
|
return buildCircleString(radius,
|
|
serializePositionOffset(*normalizedCX->getPairValue(), *normalizedCY->getPairValue()),
|
|
serializePositionOffset(*normalizedCY->getPairValue(), *normalizedCX->getPairValue()),
|
|
m_referenceBox ? m_referenceBox->cssText() : String());
|
|
}
|
|
|
|
bool CSSBasicShapeCircle::equals(const CSSBasicShape& shape) const
|
|
{
|
|
if (shape.type() != CSSBasicShapeCircleType)
|
|
return false;
|
|
|
|
const CSSBasicShapeCircle& other = static_cast<const CSSBasicShapeCircle&>(shape);
|
|
return compareCSSValuePtr(m_centerX, other.m_centerX)
|
|
&& compareCSSValuePtr(m_centerY, other.m_centerY)
|
|
&& compareCSSValuePtr(m_radius, other.m_radius)
|
|
&& compareCSSValuePtr(m_referenceBox, other.m_referenceBox);
|
|
}
|
|
|
|
static String buildEllipseString(const String& radiusX, const String& radiusY, const String& centerX, const String& centerY, const String& box)
|
|
{
|
|
char at[] = "at";
|
|
char separator[] = " ";
|
|
StringBuilder result;
|
|
result.appendLiteral("ellipse(");
|
|
bool needsSeparator = false;
|
|
if (!radiusX.isNull()) {
|
|
result.append(radiusX);
|
|
needsSeparator = true;
|
|
}
|
|
if (!radiusY.isNull()) {
|
|
if (needsSeparator)
|
|
result.appendLiteral(separator);
|
|
result.append(radiusY);
|
|
needsSeparator = true;
|
|
}
|
|
|
|
if (!centerX.isNull() || !centerY.isNull()) {
|
|
if (needsSeparator)
|
|
result.appendLiteral(separator);
|
|
result.appendLiteral(at);
|
|
result.appendLiteral(separator);
|
|
result.append(centerX);
|
|
result.appendLiteral(separator);
|
|
result.append(centerY);
|
|
}
|
|
result.append(')');
|
|
if (box.length()) {
|
|
result.appendLiteral(separator);
|
|
result.append(box);
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
String CSSBasicShapeEllipse::cssText() const
|
|
{
|
|
RefPtr<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX, CSSValueLeft);
|
|
RefPtr<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY, CSSValueTop);
|
|
|
|
String radiusX;
|
|
String radiusY;
|
|
if (m_radiusX) {
|
|
bool shouldSerializeRadiusXValue = m_radiusX->getValueID() != CSSValueClosestSide;
|
|
bool shouldSerializeRadiusYValue = false;
|
|
|
|
if (m_radiusY) {
|
|
shouldSerializeRadiusYValue = m_radiusY->getValueID() != CSSValueClosestSide;
|
|
if (shouldSerializeRadiusYValue)
|
|
radiusY = m_radiusY->cssText();
|
|
}
|
|
if (shouldSerializeRadiusXValue || (!shouldSerializeRadiusXValue && shouldSerializeRadiusYValue))
|
|
radiusX = m_radiusX->cssText();
|
|
}
|
|
|
|
return buildEllipseString(radiusX, radiusY,
|
|
serializePositionOffset(*normalizedCX->getPairValue(), *normalizedCY->getPairValue()),
|
|
serializePositionOffset(*normalizedCY->getPairValue(), *normalizedCX->getPairValue()),
|
|
m_referenceBox ? m_referenceBox->cssText() : String());
|
|
}
|
|
|
|
bool CSSBasicShapeEllipse::equals(const CSSBasicShape& shape) const
|
|
{
|
|
if (shape.type() != CSSBasicShapeEllipseType)
|
|
return false;
|
|
|
|
const CSSBasicShapeEllipse& other = static_cast<const 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)
|
|
&& compareCSSValuePtr(m_referenceBox, other.m_referenceBox);
|
|
}
|
|
|
|
static String buildPolygonString(const WindRule& windRule, const Vector<String>& points, const String& box)
|
|
{
|
|
ASSERT(!(points.size() % 2));
|
|
|
|
StringBuilder result;
|
|
const char evenOddOpening[] = "polygon(evenodd, ";
|
|
const char nonZeroOpening[] = "polygon(";
|
|
const char commaSeparator[] = ", ";
|
|
COMPILE_ASSERT(sizeof(evenOddOpening) > sizeof(nonZeroOpening), polygon_string_openings_have_same_length);
|
|
|
|
// 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();
|
|
}
|
|
if (!box.isEmpty())
|
|
length += box.length() + 1;
|
|
result.reserveCapacity(length);
|
|
|
|
if (windRule == RULE_EVENODD)
|
|
result.appendLiteral(evenOddOpening);
|
|
else
|
|
result.appendLiteral(nonZeroOpening);
|
|
|
|
for (size_t i = 0; i < points.size(); i += 2) {
|
|
if (i)
|
|
result.appendLiteral(commaSeparator);
|
|
result.append(points[i]);
|
|
result.append(' ');
|
|
result.append(points[i + 1]);
|
|
}
|
|
|
|
result.append(')');
|
|
|
|
if (!box.isEmpty()) {
|
|
result.append(' ');
|
|
result.append(box);
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
String CSSBasicShapePolygon::cssText() const
|
|
{
|
|
Vector<String> points;
|
|
points.reserveInitialCapacity(m_values.size());
|
|
|
|
for (size_t i = 0; i < m_values.size(); ++i)
|
|
points.append(m_values.at(i)->cssText());
|
|
|
|
return buildPolygonString(m_windRule, points, m_referenceBox ? m_referenceBox->cssText() : String());
|
|
}
|
|
|
|
bool CSSBasicShapePolygon::equals(const CSSBasicShape& shape) const
|
|
{
|
|
if (shape.type() != CSSBasicShapePolygonType)
|
|
return false;
|
|
|
|
const CSSBasicShapePolygon& rhs = static_cast<const CSSBasicShapePolygon&>(shape);
|
|
|
|
if (!compareCSSValuePtr(m_referenceBox, rhs.m_referenceBox))
|
|
return false;
|
|
|
|
return compareCSSValueVector(m_values, rhs.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";
|
|
}
|
|
|
|
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)
|
|
{
|
|
char opening[] = "inset(";
|
|
char separator[] = " ";
|
|
char cornersSeparator[] = "round";
|
|
StringBuilder result;
|
|
result.appendLiteral(opening);
|
|
result.append(top);
|
|
bool showLeftArg = !left.isNull() && left != right;
|
|
bool showBottomArg = !bottom.isNull() && (bottom != top || showLeftArg);
|
|
bool showRightArg = !right.isNull() && (right != top || showBottomArg);
|
|
if (showRightArg) {
|
|
result.appendLiteral(separator);
|
|
result.append(right);
|
|
}
|
|
if (showBottomArg) {
|
|
result.appendLiteral(separator);
|
|
result.append(bottom);
|
|
}
|
|
if (showLeftArg) {
|
|
result.appendLiteral(separator);
|
|
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.appendLiteral(separator);
|
|
result.appendLiteral(cornersSeparator);
|
|
|
|
for (size_t i = 0; i < horizontalRadii.size(); ++i) {
|
|
result.appendLiteral(separator);
|
|
result.append(horizontalRadii[i]);
|
|
}
|
|
if (horizontalRadii != verticalRadii) {
|
|
result.appendLiteral(separator);
|
|
result.appendLiteral("/");
|
|
|
|
for (size_t i = 0; i < verticalRadii.size(); ++i) {
|
|
result.appendLiteral(separator);
|
|
result.append(verticalRadii[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result.append(')');
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
static inline void updateCornerRadiusWidthAndHeight(CSSPrimitiveValue* corner, String& width, String& height)
|
|
{
|
|
if (!corner)
|
|
return;
|
|
|
|
Pair* radius = corner->getPairValue();
|
|
width = radius->first() ? radius->first()->cssText() : String("0");
|
|
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 (shape.type() != CSSBasicShapeInsetType)
|
|
return false;
|
|
|
|
const CSSBasicShapeInset& other = static_cast<const 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 blink
|
|
|