blob: 6e594310e8f11347cd825cf4160c2e10517e3e93 [file] [log] [blame]
/*
* Copyright (C) 2012 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/basic_shape_functions.h"
#include "base/memory/scoped_refptr.h"
#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_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_path_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h"
#include "third_party/blink/renderer/core/css/css_ray_value.h"
#include "third_party/blink/renderer/core/css/css_value.h"
#include "third_party/blink/renderer/core/css/css_value_pair.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
#include "third_party/blink/renderer/core/style/basic_shapes.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/style_ray.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
namespace blink {
static StyleRay::RaySize KeywordToRaySize(CSSValueID id) {
switch (id) {
case CSSValueID::kClosestSide:
return StyleRay::RaySize::kClosestSide;
case CSSValueID::kClosestCorner:
return StyleRay::RaySize::kClosestCorner;
case CSSValueID::kFarthestSide:
return StyleRay::RaySize::kFarthestSide;
case CSSValueID::kFarthestCorner:
return StyleRay::RaySize::kFarthestCorner;
case CSSValueID::kSides:
return StyleRay::RaySize::kSides;
default:
NOTREACHED();
return StyleRay::RaySize::kClosestSide;
}
}
static CSSValueID RaySizeToKeyword(StyleRay::RaySize size) {
switch (size) {
case StyleRay::RaySize::kClosestSide:
return CSSValueID::kClosestSide;
case StyleRay::RaySize::kClosestCorner:
return CSSValueID::kClosestCorner;
case StyleRay::RaySize::kFarthestSide:
return CSSValueID::kFarthestSide;
case StyleRay::RaySize::kFarthestCorner:
return CSSValueID::kFarthestCorner;
case StyleRay::RaySize::kSides:
return CSSValueID::kSides;
}
NOTREACHED();
return CSSValueID::kInvalid;
}
static CSSValue* ValueForCenterCoordinate(
const ComputedStyle& style,
const BasicShapeCenterCoordinate& center,
EBoxOrient orientation) {
if (center.GetDirection() == BasicShapeCenterCoordinate::kTopLeft) {
return CSSValue::Create(center.length(), style.EffectiveZoom());
}
CSSValueID keyword = orientation == EBoxOrient::kHorizontal
? CSSValueID::kRight
: CSSValueID::kBottom;
return MakeGarbageCollected<CSSValuePair>(
CSSIdentifierValue::Create(keyword),
CSSValue::Create(center.length(), style.EffectiveZoom()),
CSSValuePair::kDropIdenticalValues);
}
static CSSValuePair* ValueForLengthSize(const LengthSize& length_size,
const ComputedStyle& style) {
return MakeGarbageCollected<CSSValuePair>(
CSSValue::Create(length_size.Width(), style.EffectiveZoom()),
CSSValue::Create(length_size.Height(), style.EffectiveZoom()),
CSSValuePair::kKeepIdenticalValues);
}
static CSSValue* BasicShapeRadiusToCSSValue(const ComputedStyle& style,
const BasicShapeRadius& radius) {
switch (radius.GetType()) {
case BasicShapeRadius::kValue:
return CSSValue::Create(radius.Value(), style.EffectiveZoom());
case BasicShapeRadius::kClosestSide:
return CSSIdentifierValue::Create(CSSValueID::kClosestSide);
case BasicShapeRadius::kFarthestSide:
return CSSIdentifierValue::Create(CSSValueID::kFarthestSide);
}
NOTREACHED();
return nullptr;
}
template <typename BasicShapeClass, typename CSSValueClass>
static void InitializeBorderRadius(BasicShapeClass* rect,
const StyleResolverState& state,
const CSSValueClass& rect_value) {
rect->SetTopLeftRadius(
ConvertToLengthSize(state, rect_value.TopLeftRadius()));
rect->SetTopRightRadius(
ConvertToLengthSize(state, rect_value.TopRightRadius()));
rect->SetBottomRightRadius(
ConvertToLengthSize(state, rect_value.BottomRightRadius()));
rect->SetBottomLeftRadius(
ConvertToLengthSize(state, rect_value.BottomLeftRadius()));
}
template <typename BasicShapeClass, typename CSSValueClass>
static void InitializeBorderRadius(CSSValueClass* css_value,
const ComputedStyle& style,
const BasicShapeClass* rect) {
css_value->SetTopLeftRadius(ValueForLengthSize(rect->TopLeftRadius(), style));
css_value->SetTopRightRadius(
ValueForLengthSize(rect->TopRightRadius(), style));
css_value->SetBottomRightRadius(
ValueForLengthSize(rect->BottomRightRadius(), style));
css_value->SetBottomLeftRadius(
ValueForLengthSize(rect->BottomLeftRadius(), style));
}
CSSValue* ValueForBasicShape(const ComputedStyle& style,
const BasicShape* basic_shape) {
switch (basic_shape->GetType()) {
case BasicShape::kStyleRayType: {
const StyleRay& ray = To<StyleRay>(*basic_shape);
const CSSValue* center_x =
ray.HasExplicitCenter()
? ValueForCenterCoordinate(style, ray.CenterX(),
EBoxOrient::kHorizontal)
: nullptr;
const CSSValue* center_y =
ray.HasExplicitCenter()
? ValueForCenterCoordinate(style, ray.CenterY(),
EBoxOrient::kVertical)
: nullptr;
return MakeGarbageCollected<cssvalue::CSSRayValue>(
*CSSNumericLiteralValue::Create(
ray.Angle(), CSSPrimitiveValue::UnitType::kDegrees),
*CSSIdentifierValue::Create(RaySizeToKeyword(ray.Size())),
(ray.Contain() ? CSSIdentifierValue::Create(CSSValueID::kContain)
: nullptr),
center_x, center_y);
}
case BasicShape::kStylePathType:
return To<StylePath>(basic_shape)->ComputedCSSValue();
case BasicShape::kBasicShapeCircleType: {
const BasicShapeCircle* circle = To<BasicShapeCircle>(basic_shape);
cssvalue::CSSBasicShapeCircleValue* circle_value =
MakeGarbageCollected<cssvalue::CSSBasicShapeCircleValue>();
if (circle->HasExplicitCenter()) {
circle_value->SetCenterX(ValueForCenterCoordinate(
style, circle->CenterX(), EBoxOrient::kHorizontal));
circle_value->SetCenterY(ValueForCenterCoordinate(
style, circle->CenterY(), EBoxOrient::kVertical));
}
circle_value->SetRadius(
BasicShapeRadiusToCSSValue(style, circle->Radius()));
return circle_value;
}
case BasicShape::kBasicShapeEllipseType: {
const BasicShapeEllipse* ellipse = To<BasicShapeEllipse>(basic_shape);
auto* ellipse_value =
MakeGarbageCollected<cssvalue::CSSBasicShapeEllipseValue>();
if (ellipse->HasExplicitCenter()) {
ellipse_value->SetCenterX(ValueForCenterCoordinate(
style, ellipse->CenterX(), EBoxOrient::kHorizontal));
ellipse_value->SetCenterY(ValueForCenterCoordinate(
style, ellipse->CenterY(), EBoxOrient::kVertical));
}
ellipse_value->SetRadiusX(
BasicShapeRadiusToCSSValue(style, ellipse->RadiusX()));
ellipse_value->SetRadiusY(
BasicShapeRadiusToCSSValue(style, ellipse->RadiusY()));
return ellipse_value;
}
case BasicShape::kBasicShapePolygonType: {
const BasicShapePolygon* polygon = To<BasicShapePolygon>(basic_shape);
auto* polygon_value =
MakeGarbageCollected<cssvalue::CSSBasicShapePolygonValue>();
polygon_value->SetWindRule(polygon->GetWindRule());
const Vector<Length>& values = polygon->Values();
for (unsigned i = 0; i < values.size(); i += 2) {
polygon_value->AppendPoint(
CSSPrimitiveValue::CreateFromLength(values.at(i),
style.EffectiveZoom()),
CSSPrimitiveValue::CreateFromLength(values.at(i + 1),
style.EffectiveZoom()));
}
return polygon_value;
}
case BasicShape::kBasicShapeInsetType: {
const BasicShapeInset* inset = To<BasicShapeInset>(basic_shape);
cssvalue::CSSBasicShapeInsetValue* inset_value =
MakeGarbageCollected<cssvalue::CSSBasicShapeInsetValue>();
inset_value->SetTop(CSSPrimitiveValue::CreateFromLength(
inset->Top(), style.EffectiveZoom()));
inset_value->SetRight(CSSPrimitiveValue::CreateFromLength(
inset->Right(), style.EffectiveZoom()));
inset_value->SetBottom(CSSPrimitiveValue::CreateFromLength(
inset->Bottom(), style.EffectiveZoom()));
inset_value->SetLeft(CSSPrimitiveValue::CreateFromLength(
inset->Left(), style.EffectiveZoom()));
InitializeBorderRadius(inset_value, style, inset);
return inset_value;
}
default:
return nullptr;
}
}
static Length ConvertToLength(const StyleResolverState& state,
const CSSPrimitiveValue* value) {
if (!value) {
return Length::Fixed(0);
}
return value->ConvertToLength(state.CssToLengthConversionData());
}
static LengthSize ConvertToLengthSize(const StyleResolverState& state,
const CSSValuePair* value) {
if (!value) {
return LengthSize(Length::Fixed(0), Length::Fixed(0));
}
return LengthSize(
ConvertToLength(state, &To<CSSPrimitiveValue>(value->First())),
ConvertToLength(state, &To<CSSPrimitiveValue>(value->Second())));
}
static BasicShapeCenterCoordinate ConvertToCenterCoordinate(
const StyleResolverState& state,
const CSSValue* value) {
BasicShapeCenterCoordinate::Direction direction;
Length offset = Length::Fixed(0);
CSSValueID keyword = CSSValueID::kTop;
if (!value) {
keyword = CSSValueID::kCenter;
} else if (auto* identifier_value = DynamicTo<CSSIdentifierValue>(value)) {
keyword = identifier_value->GetValueID();
} else if (auto* value_pair = DynamicTo<CSSValuePair>(value)) {
keyword = To<CSSIdentifierValue>(value_pair->First()).GetValueID();
offset =
ConvertToLength(state, &To<CSSPrimitiveValue>(value_pair->Second()));
} else {
offset = ConvertToLength(state, To<CSSPrimitiveValue>(value));
}
switch (keyword) {
case CSSValueID::kTop:
case CSSValueID::kLeft:
direction = BasicShapeCenterCoordinate::kTopLeft;
break;
case CSSValueID::kRight:
case CSSValueID::kBottom:
direction = BasicShapeCenterCoordinate::kBottomRight;
break;
case CSSValueID::kCenter:
direction = BasicShapeCenterCoordinate::kTopLeft;
offset = Length::Percent(50);
break;
default:
NOTREACHED();
direction = BasicShapeCenterCoordinate::kTopLeft;
break;
}
return BasicShapeCenterCoordinate(direction, offset);
}
static BasicShapeRadius CssValueToBasicShapeRadius(
const StyleResolverState& state,
const CSSValue* radius) {
if (!radius) {
return BasicShapeRadius(BasicShapeRadius::kClosestSide);
}
if (auto* radius_identifier_value = DynamicTo<CSSIdentifierValue>(radius)) {
switch (radius_identifier_value->GetValueID()) {
case CSSValueID::kClosestSide:
return BasicShapeRadius(BasicShapeRadius::kClosestSide);
case CSSValueID::kFarthestSide:
return BasicShapeRadius(BasicShapeRadius::kFarthestSide);
default:
NOTREACHED();
break;
}
}
return BasicShapeRadius(
ConvertToLength(state, To<CSSPrimitiveValue>(radius)));
}
scoped_refptr<BasicShape> BasicShapeForValue(
const StyleResolverState& state,
const CSSValue& basic_shape_value) {
scoped_refptr<BasicShape> basic_shape;
if (const auto* circle_value =
DynamicTo<cssvalue::CSSBasicShapeCircleValue>(basic_shape_value)) {
scoped_refptr<BasicShapeCircle> circle = BasicShapeCircle::Create();
circle->SetCenterX(
ConvertToCenterCoordinate(state, circle_value->CenterX()));
circle->SetCenterY(
ConvertToCenterCoordinate(state, circle_value->CenterY()));
circle->SetRadius(
CssValueToBasicShapeRadius(state, circle_value->Radius()));
circle->SetHasExplicitCenter(circle_value->CenterX());
basic_shape = std::move(circle);
} else if (const auto* ellipse_value =
DynamicTo<cssvalue::CSSBasicShapeEllipseValue>(
basic_shape_value)) {
scoped_refptr<BasicShapeEllipse> ellipse = BasicShapeEllipse::Create();
ellipse->SetCenterX(
ConvertToCenterCoordinate(state, ellipse_value->CenterX()));
ellipse->SetCenterY(
ConvertToCenterCoordinate(state, ellipse_value->CenterY()));
ellipse->SetRadiusX(
CssValueToBasicShapeRadius(state, ellipse_value->RadiusX()));
ellipse->SetRadiusY(
CssValueToBasicShapeRadius(state, ellipse_value->RadiusY()));
ellipse->SetHasExplicitCenter(ellipse_value->CenterX());
basic_shape = std::move(ellipse);
} else if (const auto* polygon_value =
DynamicTo<cssvalue::CSSBasicShapePolygonValue>(
basic_shape_value)) {
scoped_refptr<BasicShapePolygon> polygon = BasicShapePolygon::Create();
polygon->SetWindRule(polygon_value->GetWindRule());
const HeapVector<Member<CSSPrimitiveValue>>& values =
polygon_value->Values();
for (unsigned i = 0; i < values.size(); i += 2) {
polygon->AppendPoint(ConvertToLength(state, values.at(i).Get()),
ConvertToLength(state, values.at(i + 1).Get()));
}
basic_shape = std::move(polygon);
} else if (const auto* inset_value =
DynamicTo<cssvalue::CSSBasicShapeInsetValue>(
basic_shape_value)) {
scoped_refptr<BasicShapeInset> rect = BasicShapeInset::Create();
rect->SetTop(
ConvertToLength(state, To<CSSPrimitiveValue>(inset_value->Top())));
rect->SetRight(
ConvertToLength(state, To<CSSPrimitiveValue>(inset_value->Right())));
rect->SetBottom(
ConvertToLength(state, To<CSSPrimitiveValue>(inset_value->Bottom())));
rect->SetLeft(
ConvertToLength(state, To<CSSPrimitiveValue>(inset_value->Left())));
InitializeBorderRadius(rect.get(), state, *inset_value);
basic_shape = std::move(rect);
} else if (const auto* rect_value =
DynamicTo<cssvalue::CSSBasicShapeRectValue>(
basic_shape_value)) {
scoped_refptr<BasicShapeInset> inset = BasicShapeInset::Create();
// Spec: All <basic-shape-rect> functions compute to the equivalent
// inset() function. NOTE: Given `rect(t r b l)`, the equivalent function
// is `inset(t calc(100% - r) calc(100% - b) l)`.
// See: https://drafts.csswg.org/css-shapes/#basic-shape-computed-values
auto get_inset_length = [&](const CSSValue& edge,
bool is_right_or_bottom) -> Length {
// Auto values coincide with the corresponding edge of the reference
// box (https://drafts.csswg.org/css-shapes/#funcdef-basic-shape-rect),
// so the inset of any auto value will be 0.
if (auto* auto_value = DynamicTo<CSSIdentifierValue>(edge)) {
DCHECK_EQ(auto_value->GetValueID(), CSSValueID::kAuto);
return Length::Percent(0);
}
Length edge_length = ConvertToLength(state, &To<CSSPrimitiveValue>(edge));
return is_right_or_bottom ? edge_length.SubtractFromOneHundredPercent()
: edge_length;
};
inset->SetTop(get_inset_length(*rect_value->Top(), false));
inset->SetRight(get_inset_length(*rect_value->Right(), true));
inset->SetBottom(get_inset_length(*rect_value->Bottom(), true));
inset->SetLeft(get_inset_length(*rect_value->Left(), false));
InitializeBorderRadius(inset.get(), state, *rect_value);
basic_shape = std::move(inset);
} else if (const auto* xywh_value =
DynamicTo<cssvalue::CSSBasicShapeXYWHValue>(
basic_shape_value)) {
scoped_refptr<BasicShapeInset> inset = BasicShapeInset::Create();
// Spec: All <basic-shape-rect> functions compute to the equivalent
// inset() function. NOTE: Given `xywh(x y w h)`, the equivalent function
// is `inset(y calc(100% - x - w) calc(100% - y - h) x)`.
// See: https://drafts.csswg.org/css-shapes/#basic-shape-computed-values
// and https://github.com/w3c/csswg-drafts/issues/9053
inset->SetLeft(ConvertToLength(state, xywh_value->X()));
// calc(100% - (x + w)) = calc(100% - x - w).
inset->SetRight(inset->Left()
.Add(ConvertToLength(state, xywh_value->Width()))
.SubtractFromOneHundredPercent());
inset->SetTop(ConvertToLength(state, xywh_value->Y()));
// calc(100% - (y + h)) = calc(100% - y - h).
inset->SetBottom(inset->Top()
.Add(ConvertToLength(state, xywh_value->Height()))
.SubtractFromOneHundredPercent());
InitializeBorderRadius(inset.get(), state, *xywh_value);
basic_shape = std::move(inset);
} else if (const auto* ray_value =
DynamicTo<cssvalue::CSSRayValue>(basic_shape_value)) {
float angle =
ray_value->Angle().ComputeDegrees(state.CssToLengthConversionData());
StyleRay::RaySize size = KeywordToRaySize(ray_value->Size().GetValueID());
bool contain = !!ray_value->Contain();
basic_shape =
StyleRay::Create(angle, size, contain,
ConvertToCenterCoordinate(state, ray_value->CenterX()),
ConvertToCenterCoordinate(state, ray_value->CenterY()),
ray_value->CenterX());
} else if (const auto* path_value =
DynamicTo<cssvalue::CSSPathValue>(basic_shape_value)) {
basic_shape = path_value->GetStylePath();
} else {
NOTREACHED();
}
return basic_shape;
}
} // namespace blink