1/*
2 * Copyright (C) 2011 Adobe Systems Incorporated. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following
13 * disclaimer in the documentation and/or other materials
14 * provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
26 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include "config.h"
31#include "CSSBasicShapes.h"
32
33#include "CSSMarkup.h"
34#include "CSSPrimitiveValueMappings.h"
35#include "CSSValuePool.h"
36#include "Pair.h"
37#include "SVGPathByteStream.h"
38#include "SVGPathUtilities.h"
39#include <wtf/text/StringBuilder.h>
40
41namespace WebCore {
42
43static String serializePositionOffset(const Pair& offset, const Pair& other)
44{
45 if ((offset.first()->valueID() == CSSValueLeft && other.first()->valueID() == CSSValueTop)
46 || (offset.first()->valueID() == CSSValueTop && other.first()->valueID() == CSSValueLeft))
47 return offset.second()->cssText();
48 return offset.cssText();
49}
50
51static Ref<CSSPrimitiveValue> buildSerializablePositionOffset(CSSPrimitiveValue* offset, CSSValueID defaultSide)
52{
53 CSSValueID side = defaultSide;
54 RefPtr<CSSPrimitiveValue> amount;
55
56 if (!offset)
57 side = CSSValueCenter;
58 else if (offset->isValueID())
59 side = offset->valueID();
60 else if (Pair* pair = offset->pairValue()) {
61 side = pair->first()->valueID();
62 amount = pair->second();
63 } else
64 amount = offset;
65
66 auto& cssValuePool = CSSValuePool::singleton();
67 if (!amount)
68 amount = cssValuePool.createValue(Length(side == CSSValueCenter ? 50 : 0, Percent));
69
70 if (side == CSSValueCenter)
71 side = defaultSide;
72 else if ((side == CSSValueRight || side == CSSValueBottom)
73 && amount->isPercentage()) {
74 side = defaultSide;
75 amount = cssValuePool.createValue(Length(100 - amount->floatValue(), Percent));
76 } else if (amount->isLength() && !amount->floatValue()) {
77 if (side == CSSValueRight || side == CSSValueBottom)
78 amount = cssValuePool.createValue(Length(100, Percent));
79 else
80 amount = cssValuePool.createValue(Length(0, Percent));
81 side = defaultSide;
82 }
83
84 return cssValuePool.createValue(Pair::create(cssValuePool.createValue(side), WTFMove(amount)));
85}
86
87static String buildCircleString(const String& radius, const String& centerX, const String& centerY)
88{
89 char opening[] = "circle(";
90 char at[] = "at";
91 char separator[] = " ";
92 StringBuilder result;
93 result.appendLiteral(opening);
94 if (!radius.isNull())
95 result.append(radius);
96
97 if (!centerX.isNull() || !centerY.isNull()) {
98 if (!radius.isNull())
99 result.appendLiteral(separator);
100 result.appendLiteral(at);
101 result.appendLiteral(separator);
102 result.append(centerX);
103 result.appendLiteral(separator);
104 result.append(centerY);
105 }
106 result.appendLiteral(")");
107 return result.toString();
108}
109
110String CSSBasicShapeCircle::cssText() const
111{
112 Ref<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX.get(), CSSValueLeft);
113 Ref<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY.get(), CSSValueTop);
114
115 String radius;
116 if (m_radius && m_radius->valueID() != CSSValueClosestSide)
117 radius = m_radius->cssText();
118
119 return buildCircleString(radius,
120 serializePositionOffset(*normalizedCX->pairValue(), *normalizedCY->pairValue()),
121 serializePositionOffset(*normalizedCY->pairValue(), *normalizedCX->pairValue()));
122}
123
124bool CSSBasicShapeCircle::equals(const CSSBasicShape& shape) const
125{
126 if (!is<CSSBasicShapeCircle>(shape))
127 return false;
128
129 const CSSBasicShapeCircle& other = downcast<CSSBasicShapeCircle>(shape);
130 return compareCSSValuePtr(m_centerX, other.m_centerX)
131 && compareCSSValuePtr(m_centerY, other.m_centerY)
132 && compareCSSValuePtr(m_radius, other.m_radius);
133}
134
135static String buildEllipseString(const String& radiusX, const String& radiusY, const String& centerX, const String& centerY)
136{
137 char opening[] = "ellipse(";
138 char at[] = "at";
139 char separator[] = " ";
140 StringBuilder result;
141 result.appendLiteral(opening);
142 bool needsSeparator = false;
143 if (!radiusX.isNull()) {
144 result.append(radiusX);
145 needsSeparator = true;
146 }
147 if (!radiusY.isNull()) {
148 if (needsSeparator)
149 result.appendLiteral(separator);
150 result.append(radiusY);
151 needsSeparator = true;
152 }
153
154 if (!centerX.isNull() || !centerY.isNull()) {
155 if (needsSeparator)
156 result.appendLiteral(separator);
157 result.appendLiteral(at);
158 result.appendLiteral(separator);
159 result.append(centerX);
160 result.appendLiteral(separator);
161 result.append(centerY);
162 }
163 result.appendLiteral(")");
164 return result.toString();
165}
166
167String CSSBasicShapeEllipse::cssText() const
168{
169 Ref<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX.get(), CSSValueLeft);
170 Ref<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY.get(), CSSValueTop);
171
172 String radiusX;
173 String radiusY;
174 if (m_radiusX) {
175 bool shouldSerializeRadiusXValue = m_radiusX->valueID() != CSSValueClosestSide;
176 bool shouldSerializeRadiusYValue = false;
177
178 if (m_radiusY) {
179 shouldSerializeRadiusYValue = m_radiusY->valueID() != CSSValueClosestSide;
180 if (shouldSerializeRadiusYValue)
181 radiusY = m_radiusY->cssText();
182 }
183 if (shouldSerializeRadiusXValue || (!shouldSerializeRadiusXValue && shouldSerializeRadiusYValue))
184 radiusX = m_radiusX->cssText();
185 }
186 return buildEllipseString(radiusX, radiusY,
187 serializePositionOffset(*normalizedCX->pairValue(), *normalizedCY->pairValue()),
188 serializePositionOffset(*normalizedCY->pairValue(), *normalizedCX->pairValue()));
189}
190
191bool CSSBasicShapeEllipse::equals(const CSSBasicShape& shape) const
192{
193 if (!is<CSSBasicShapeEllipse>(shape))
194 return false;
195
196 const CSSBasicShapeEllipse& other = downcast<CSSBasicShapeEllipse>(shape);
197 return compareCSSValuePtr(m_centerX, other.m_centerX)
198 && compareCSSValuePtr(m_centerY, other.m_centerY)
199 && compareCSSValuePtr(m_radiusX, other.m_radiusX)
200 && compareCSSValuePtr(m_radiusY, other.m_radiusY);
201}
202
203CSSBasicShapePath::CSSBasicShapePath(std::unique_ptr<SVGPathByteStream>&& pathData)
204 : m_byteStream(WTFMove(pathData))
205{
206}
207
208static String buildPathString(const WindRule& windRule, const String& path, const String& box)
209{
210 StringBuilder result;
211 if (windRule == WindRule::EvenOdd)
212 result.appendLiteral("path(evenodd, ");
213 else
214 result.appendLiteral("path(");
215
216 serializeString(path, result);
217 result.append(')');
218
219 if (box.length()) {
220 result.append(' ');
221 result.append(box);
222 }
223
224 return result.toString();
225}
226
227String CSSBasicShapePath::cssText() const
228{
229 String pathString;
230 buildStringFromByteStream(*m_byteStream, pathString, UnalteredParsing);
231
232 return buildPathString(m_windRule, pathString, m_referenceBox ? m_referenceBox->cssText() : String());
233}
234
235bool CSSBasicShapePath::equals(const CSSBasicShape& otherShape) const
236{
237 if (!is<CSSBasicShapePath>(otherShape))
238 return false;
239
240 auto& otherShapePath = downcast<CSSBasicShapePath>(otherShape);
241 return windRule() == otherShapePath.windRule() && pathData() == otherShapePath.pathData();
242}
243
244static String buildPolygonString(const WindRule& windRule, const Vector<String>& points)
245{
246 ASSERT(!(points.size() % 2));
247
248 StringBuilder result;
249 char evenOddOpening[] = "polygon(evenodd, ";
250 char nonZeroOpening[] = "polygon(";
251 char commaSeparator[] = ", ";
252 COMPILE_ASSERT(sizeof(evenOddOpening) >= sizeof(nonZeroOpening), polygon_evenodd_is_longest_string_opening);
253
254 // Compute the required capacity in advance to reduce allocations.
255 size_t length = sizeof(evenOddOpening) - 1;
256 for (size_t i = 0; i < points.size(); i += 2) {
257 if (i)
258 length += (sizeof(commaSeparator) - 1);
259 // add length of two strings, plus one for the space separator.
260 length += points[i].length() + 1 + points[i + 1].length();
261 }
262
263 result.reserveCapacity(length);
264
265 if (windRule == WindRule::EvenOdd)
266 result.appendLiteral(evenOddOpening);
267 else
268 result.appendLiteral(nonZeroOpening);
269
270 for (size_t i = 0; i < points.size(); i += 2) {
271 if (i)
272 result.appendLiteral(commaSeparator);
273 result.append(points[i]);
274 result.append(' ');
275 result.append(points[i + 1]);
276 }
277
278 result.append(')');
279
280 return result.toString();
281}
282
283String CSSBasicShapePolygon::cssText() const
284{
285 Vector<String> points;
286 points.reserveInitialCapacity(m_values.size());
287
288 for (auto& shapeValue : m_values)
289 points.uncheckedAppend(shapeValue->cssText());
290
291 return buildPolygonString(m_windRule, points);
292}
293
294bool CSSBasicShapePolygon::equals(const CSSBasicShape& shape) const
295{
296 if (!is<CSSBasicShapePolygon>(shape))
297 return false;
298
299 return compareCSSValueVector<CSSPrimitiveValue>(m_values, downcast<CSSBasicShapePolygon>(shape).m_values);
300}
301
302static bool buildInsetRadii(Vector<String>& radii, const String& topLeftRadius, const String& topRightRadius, const String& bottomRightRadius, const String& bottomLeftRadius)
303{
304 bool showBottomLeft = topRightRadius != bottomLeftRadius;
305 bool showBottomRight = showBottomLeft || (bottomRightRadius != topLeftRadius);
306 bool showTopRight = showBottomRight || (topRightRadius != topLeftRadius);
307
308 radii.append(topLeftRadius);
309 if (showTopRight)
310 radii.append(topRightRadius);
311 if (showBottomRight)
312 radii.append(bottomRightRadius);
313 if (showBottomLeft)
314 radii.append(bottomLeftRadius);
315
316 return radii.size() == 1 && radii[0] == "0px";
317}
318
319static String buildInsetString(const String& top, const String& right, const String& bottom, const String& left,
320 const String& topLeftRadiusWidth, const String& topLeftRadiusHeight,
321 const String& topRightRadiusWidth, const String& topRightRadiusHeight,
322 const String& bottomRightRadiusWidth, const String& bottomRightRadiusHeight,
323 const String& bottomLeftRadiusWidth, const String& bottomLeftRadiusHeight)
324{
325 char opening[] = "inset(";
326 char separator[] = " ";
327 char cornersSeparator[] = "round";
328 StringBuilder result;
329 result.appendLiteral(opening);
330 result.append(top);
331
332 bool showLeftArg = !left.isNull() && left != right;
333 bool showBottomArg = !bottom.isNull() && (bottom != top || showLeftArg);
334 bool showRightArg = !right.isNull() && (right != top || showBottomArg);
335 if (showRightArg) {
336 result.appendLiteral(separator);
337 result.append(right);
338 }
339 if (showBottomArg) {
340 result.appendLiteral(separator);
341 result.append(bottom);
342 }
343 if (showLeftArg) {
344 result.appendLiteral(separator);
345 result.append(left);
346 }
347
348 if (!topLeftRadiusWidth.isNull() && !topLeftRadiusHeight.isNull()) {
349 Vector<String> horizontalRadii;
350 bool areDefaultCornerRadii = buildInsetRadii(horizontalRadii, topLeftRadiusWidth, topRightRadiusWidth, bottomRightRadiusWidth, bottomLeftRadiusWidth);
351
352 Vector<String> verticalRadii;
353 areDefaultCornerRadii &= buildInsetRadii(verticalRadii, topLeftRadiusHeight, topRightRadiusHeight, bottomRightRadiusHeight, bottomLeftRadiusHeight);
354
355 if (!areDefaultCornerRadii) {
356 result.appendLiteral(separator);
357 result.appendLiteral(cornersSeparator);
358
359 for (size_t i = 0; i < horizontalRadii.size(); ++i) {
360 result.appendLiteral(separator);
361 result.append(horizontalRadii[i]);
362 }
363
364 if (verticalRadii.size() != horizontalRadii.size()
365 || !WTF::VectorComparer<false, String>::compare(verticalRadii.data(), horizontalRadii.data(), verticalRadii.size())) {
366 result.appendLiteral(separator);
367 result.appendLiteral("/");
368
369 for (size_t i = 0; i < verticalRadii.size(); ++i) {
370 result.appendLiteral(separator);
371 result.append(verticalRadii[i]);
372 }
373 }
374 }
375 }
376 result.append(')');
377 return result.toString();
378}
379
380static inline void updateCornerRadiusWidthAndHeight(CSSPrimitiveValue* corner, String& width, String& height)
381{
382 if (!corner)
383 return;
384
385 Pair* radius = corner->pairValue();
386 width = radius->first() ? radius->first()->cssText() : "0"_str;
387 if (radius->second())
388 height = radius->second()->cssText();
389}
390
391String CSSBasicShapeInset::cssText() const
392{
393 String topLeftRadiusWidth;
394 String topLeftRadiusHeight;
395 String topRightRadiusWidth;
396 String topRightRadiusHeight;
397 String bottomRightRadiusWidth;
398 String bottomRightRadiusHeight;
399 String bottomLeftRadiusWidth;
400 String bottomLeftRadiusHeight;
401
402 updateCornerRadiusWidthAndHeight(topLeftRadius(), topLeftRadiusWidth, topLeftRadiusHeight);
403 updateCornerRadiusWidthAndHeight(topRightRadius(), topRightRadiusWidth, topRightRadiusHeight);
404 updateCornerRadiusWidthAndHeight(bottomRightRadius(), bottomRightRadiusWidth, bottomRightRadiusHeight);
405 updateCornerRadiusWidthAndHeight(bottomLeftRadius(), bottomLeftRadiusWidth, bottomLeftRadiusHeight);
406
407 return buildInsetString(m_top ? m_top->cssText() : String(),
408 m_right ? m_right->cssText() : String(),
409 m_bottom ? m_bottom->cssText() : String(),
410 m_left ? m_left->cssText() : String(),
411 topLeftRadiusWidth,
412 topLeftRadiusHeight,
413 topRightRadiusWidth,
414 topRightRadiusHeight,
415 bottomRightRadiusWidth,
416 bottomRightRadiusHeight,
417 bottomLeftRadiusWidth,
418 bottomLeftRadiusHeight);
419}
420
421bool CSSBasicShapeInset::equals(const CSSBasicShape& shape) const
422{
423 if (!is<CSSBasicShapeInset>(shape))
424 return false;
425
426 const CSSBasicShapeInset& other = downcast<CSSBasicShapeInset>(shape);
427 return compareCSSValuePtr(m_top, other.m_top)
428 && compareCSSValuePtr(m_right, other.m_right)
429 && compareCSSValuePtr(m_bottom, other.m_bottom)
430 && compareCSSValuePtr(m_left, other.m_left)
431 && compareCSSValuePtr(m_topLeftRadius, other.m_topLeftRadius)
432 && compareCSSValuePtr(m_topRightRadius, other.m_topRightRadius)
433 && compareCSSValuePtr(m_bottomRightRadius, other.m_bottomRightRadius)
434 && compareCSSValuePtr(m_bottomLeftRadius, other.m_bottomLeftRadius);
435}
436
437} // namespace WebCore
438
439