blob: 289cd8db99c3278cbb0e978d469995f81efea4df [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 "third_party/blink/renderer/core/css/css_basic_shape_values.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_value_pair.h"
#include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace cssvalue {
static String BuildCircleString(const String& radius,
const String& center_x,
const String& center_y) {
char at[] = "at";
char separator[] = " ";
StringBuilder result;
result.Append("circle(");
if (!radius.IsNull())
result.Append(radius);
if (!center_x.IsNull() || !center_y.IsNull()) {
if (!radius.IsNull())
result.Append(separator);
result.Append(at);
result.Append(separator);
result.Append(center_x);
result.Append(separator);
result.Append(center_y);
}
result.Append(')');
return result.ToString();
}
static String SerializePositionOffset(const CSSValuePair& offset,
const CSSValuePair& other) {
if ((ToCSSIdentifierValue(offset.First()).GetValueID() == CSSValueLeft &&
ToCSSIdentifierValue(other.First()).GetValueID() == CSSValueTop) ||
(ToCSSIdentifierValue(offset.First()).GetValueID() == CSSValueTop &&
ToCSSIdentifierValue(other.First()).GetValueID() == CSSValueLeft))
return offset.Second().CssText();
return offset.CssText();
}
static CSSValuePair* BuildSerializablePositionOffset(CSSValue* offset,
CSSValueID default_side) {
CSSValueID side = default_side;
const CSSPrimitiveValue* amount = nullptr;
if (!offset) {
side = CSSValueCenter;
} else if (offset->IsIdentifierValue()) {
side = ToCSSIdentifierValue(offset)->GetValueID();
} else if (offset->IsValuePair()) {
side = ToCSSIdentifierValue(ToCSSValuePair(*offset).First()).GetValueID();
amount = &ToCSSPrimitiveValue(ToCSSValuePair(*offset).Second());
if ((side == CSSValueRight || side == CSSValueBottom) &&
amount->IsPercentage()) {
side = default_side;
amount =
CSSPrimitiveValue::Create(100 - amount->GetFloatValue(),
CSSPrimitiveValue::UnitType::kPercentage);
}
} else {
amount = ToCSSPrimitiveValue(offset);
}
if (side == CSSValueCenter) {
side = default_side;
amount =
CSSPrimitiveValue::Create(50, CSSPrimitiveValue::UnitType::kPercentage);
} else if (!amount || (amount->IsLength() && !amount->GetFloatValue())) {
if (side == CSSValueRight || side == CSSValueBottom)
amount = CSSPrimitiveValue::Create(
100, CSSPrimitiveValue::UnitType::kPercentage);
else
amount = CSSPrimitiveValue::Create(
0, CSSPrimitiveValue::UnitType::kPercentage);
side = default_side;
}
return CSSValuePair::Create(CSSIdentifierValue::Create(side), amount,
CSSValuePair::kKeepIdenticalValues);
}
String CSSBasicShapeCircleValue::CustomCSSText() const {
CSSValuePair* normalized_cx =
BuildSerializablePositionOffset(center_x_, CSSValueLeft);
CSSValuePair* normalized_cy =
BuildSerializablePositionOffset(center_y_, CSSValueTop);
String radius;
if (radius_ &&
!(radius_->IsIdentifierValue() &&
ToCSSIdentifierValue(*radius_).GetValueID() == CSSValueClosestSide))
radius = radius_->CssText();
return BuildCircleString(
radius, SerializePositionOffset(*normalized_cx, *normalized_cy),
SerializePositionOffset(*normalized_cy, *normalized_cx));
}
bool CSSBasicShapeCircleValue::Equals(
const CSSBasicShapeCircleValue& other) const {
return DataEquivalent(center_x_, other.center_x_) &&
DataEquivalent(center_y_, other.center_y_) &&
DataEquivalent(radius_, other.radius_);
}
void CSSBasicShapeCircleValue::TraceAfterDispatch(blink::Visitor* visitor) {
visitor->Trace(center_x_);
visitor->Trace(center_y_);
visitor->Trace(radius_);
CSSValue::TraceAfterDispatch(visitor);
}
static String BuildEllipseString(const String& radius_x,
const String& radius_y,
const String& center_x,
const String& center_y) {
char at[] = "at";
char separator[] = " ";
StringBuilder result;
result.Append("ellipse(");
bool needs_separator = false;
if (!radius_x.IsNull()) {
result.Append(radius_x);
needs_separator = true;
}
if (!radius_y.IsNull()) {
if (needs_separator)
result.Append(separator);
result.Append(radius_y);
needs_separator = true;
}
if (!center_x.IsNull() || !center_y.IsNull()) {
if (needs_separator)
result.Append(separator);
result.Append(at);
result.Append(separator);
result.Append(center_x);
result.Append(separator);
result.Append(center_y);
}
result.Append(')');
return result.ToString();
}
String CSSBasicShapeEllipseValue::CustomCSSText() const {
CSSValuePair* normalized_cx =
BuildSerializablePositionOffset(center_x_, CSSValueLeft);
CSSValuePair* normalized_cy =
BuildSerializablePositionOffset(center_y_, CSSValueTop);
String radius_x;
String radius_y;
if (radius_x_) {
bool should_serialize_radius_x_value =
!(radius_x_->IsIdentifierValue() &&
ToCSSIdentifierValue(*radius_x_).GetValueID() == CSSValueClosestSide);
bool should_serialize_radius_y_value = false;
if (radius_y_) {
should_serialize_radius_y_value = !(
radius_y_->IsIdentifierValue() &&
ToCSSIdentifierValue(*radius_y_).GetValueID() == CSSValueClosestSide);
if (should_serialize_radius_y_value)
radius_y = radius_y_->CssText();
}
if (should_serialize_radius_x_value ||
(!should_serialize_radius_x_value && should_serialize_radius_y_value))
radius_x = radius_x_->CssText();
}
return BuildEllipseString(
radius_x, radius_y,
SerializePositionOffset(*normalized_cx, *normalized_cy),
SerializePositionOffset(*normalized_cy, *normalized_cx));
}
bool CSSBasicShapeEllipseValue::Equals(
const CSSBasicShapeEllipseValue& other) const {
return DataEquivalent(center_x_, other.center_x_) &&
DataEquivalent(center_y_, other.center_y_) &&
DataEquivalent(radius_x_, other.radius_x_) &&
DataEquivalent(radius_y_, other.radius_y_);
}
void CSSBasicShapeEllipseValue::TraceAfterDispatch(blink::Visitor* visitor) {
visitor->Trace(center_x_);
visitor->Trace(center_y_);
visitor->Trace(radius_x_);
visitor->Trace(radius_y_);
CSSValue::TraceAfterDispatch(visitor);
}
static String BuildPolygonString(const WindRule& wind_rule,
const Vector<String>& points) {
DCHECK(!(points.size() % 2));
StringBuilder result;
const char kEvenOddOpening[] = "polygon(evenodd, ";
const char kNonZeroOpening[] = "polygon(";
const char kCommaSeparator[] = ", ";
static_assert(sizeof(kEvenOddOpening) > sizeof(kNonZeroOpening),
"polygon string openings should be the same length");
// Compute the required capacity in advance to reduce allocations.
wtf_size_t length = sizeof(kEvenOddOpening) - 1;
for (wtf_size_t i = 0; i < points.size(); i += 2) {
if (i)
length += (sizeof(kCommaSeparator) - 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 (wind_rule == RULE_EVENODD)
result.Append(kEvenOddOpening);
else
result.Append(kNonZeroOpening);
for (wtf_size_t i = 0; i < points.size(); i += 2) {
if (i)
result.Append(kCommaSeparator);
result.Append(points[i]);
result.Append(' ');
result.Append(points[i + 1]);
}
result.Append(')');
return result.ToString();
}
String CSSBasicShapePolygonValue::CustomCSSText() const {
Vector<String> points;
points.ReserveInitialCapacity(values_.size());
for (wtf_size_t i = 0; i < values_.size(); ++i)
points.push_back(values_.at(i)->CssText());
return BuildPolygonString(wind_rule_, points);
}
bool CSSBasicShapePolygonValue::Equals(
const CSSBasicShapePolygonValue& other) const {
return CompareCSSValueVector(values_, other.values_);
}
void CSSBasicShapePolygonValue::TraceAfterDispatch(blink::Visitor* visitor) {
visitor->Trace(values_);
CSSValue::TraceAfterDispatch(visitor);
}
static bool BuildInsetRadii(Vector<String>& radii,
const String& top_left_radius,
const String& top_right_radius,
const String& bottom_right_radius,
const String& bottom_left_radius) {
bool show_bottom_left = top_right_radius != bottom_left_radius;
bool show_bottom_right =
show_bottom_left || (bottom_right_radius != top_left_radius);
bool show_top_right =
show_bottom_right || (top_right_radius != top_left_radius);
radii.push_back(top_left_radius);
if (show_top_right)
radii.push_back(top_right_radius);
if (show_bottom_right)
radii.push_back(bottom_right_radius);
if (show_bottom_left)
radii.push_back(bottom_left_radius);
return radii.size() == 1 && radii[0] == "0px";
}
static String BuildInsetString(const String& top,
const String& right,
const String& bottom,
const String& left,
const String& top_left_radius_width,
const String& top_left_radius_height,
const String& top_right_radius_width,
const String& top_right_radius_height,
const String& bottom_right_radius_width,
const String& bottom_right_radius_height,
const String& bottom_left_radius_width,
const String& bottom_left_radius_height) {
char opening[] = "inset(";
char separator[] = " ";
char corners_separator[] = "round";
StringBuilder result;
result.Append(opening);
result.Append(top);
bool show_left_arg = !left.IsNull() && left != right;
bool show_bottom_arg = !bottom.IsNull() && (bottom != top || show_left_arg);
bool show_right_arg = !right.IsNull() && (right != top || show_bottom_arg);
if (show_right_arg) {
result.Append(separator);
result.Append(right);
}
if (show_bottom_arg) {
result.Append(separator);
result.Append(bottom);
}
if (show_left_arg) {
result.Append(separator);
result.Append(left);
}
if (!top_left_radius_width.IsNull() && !top_left_radius_height.IsNull()) {
Vector<String> horizontal_radii;
bool are_default_corner_radii = BuildInsetRadii(
horizontal_radii, top_left_radius_width, top_right_radius_width,
bottom_right_radius_width, bottom_left_radius_width);
Vector<String> vertical_radii;
are_default_corner_radii &= BuildInsetRadii(
vertical_radii, top_left_radius_height, top_right_radius_height,
bottom_right_radius_height, bottom_left_radius_height);
if (!are_default_corner_radii) {
result.Append(separator);
result.Append(corners_separator);
for (wtf_size_t i = 0; i < horizontal_radii.size(); ++i) {
result.Append(separator);
result.Append(horizontal_radii[i]);
}
if (horizontal_radii != vertical_radii) {
result.Append(separator);
result.Append('/');
for (wtf_size_t i = 0; i < vertical_radii.size(); ++i) {
result.Append(separator);
result.Append(vertical_radii[i]);
}
}
}
}
result.Append(')');
return result.ToString();
}
static inline void UpdateCornerRadiusWidthAndHeight(
const CSSValuePair* corner_radius,
String& width,
String& height) {
if (!corner_radius)
return;
width = corner_radius->First().CssText();
height = corner_radius->Second().CssText();
}
String CSSBasicShapeInsetValue::CustomCSSText() const {
String top_left_radius_width;
String top_left_radius_height;
String top_right_radius_width;
String top_right_radius_height;
String bottom_right_radius_width;
String bottom_right_radius_height;
String bottom_left_radius_width;
String bottom_left_radius_height;
UpdateCornerRadiusWidthAndHeight(TopLeftRadius(), top_left_radius_width,
top_left_radius_height);
UpdateCornerRadiusWidthAndHeight(TopRightRadius(), top_right_radius_width,
top_right_radius_height);
UpdateCornerRadiusWidthAndHeight(BottomRightRadius(),
bottom_right_radius_width,
bottom_right_radius_height);
UpdateCornerRadiusWidthAndHeight(BottomLeftRadius(), bottom_left_radius_width,
bottom_left_radius_height);
return BuildInsetString(
top_ ? top_->CssText() : String(), right_ ? right_->CssText() : String(),
bottom_ ? bottom_->CssText() : String(),
left_ ? left_->CssText() : String(), top_left_radius_width,
top_left_radius_height, top_right_radius_width, top_right_radius_height,
bottom_right_radius_width, bottom_right_radius_height,
bottom_left_radius_width, bottom_left_radius_height);
}
bool CSSBasicShapeInsetValue::Equals(
const CSSBasicShapeInsetValue& other) const {
return DataEquivalent(top_, other.top_) &&
DataEquivalent(right_, other.right_) &&
DataEquivalent(bottom_, other.bottom_) &&
DataEquivalent(left_, other.left_) &&
DataEquivalent(top_left_radius_, other.top_left_radius_) &&
DataEquivalent(top_right_radius_, other.top_right_radius_) &&
DataEquivalent(bottom_right_radius_, other.bottom_right_radius_) &&
DataEquivalent(bottom_left_radius_, other.bottom_left_radius_);
}
void CSSBasicShapeInsetValue::TraceAfterDispatch(blink::Visitor* visitor) {
visitor->Trace(top_);
visitor->Trace(right_);
visitor->Trace(bottom_);
visitor->Trace(left_);
visitor->Trace(top_left_radius_);
visitor->Trace(top_right_radius_);
visitor->Trace(bottom_right_radius_);
visitor->Trace(bottom_left_radius_);
CSSValue::TraceAfterDispatch(visitor);
}
} // namespace cssvalue
} // namespace blink