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 | |
41 | namespace WebCore { |
42 | |
43 | static 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 | |
51 | static 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 | |
87 | static 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 | |
110 | String 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 | |
124 | bool 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 | |
135 | static 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 | |
167 | String 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 | |
191 | bool 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 | |
203 | CSSBasicShapePath::CSSBasicShapePath(std::unique_ptr<SVGPathByteStream>&& pathData) |
204 | : m_byteStream(WTFMove(pathData)) |
205 | { |
206 | } |
207 | |
208 | static 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 | |
227 | String 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 | |
235 | bool 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 | |
244 | static 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 | |
283 | String 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 | |
294 | bool 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 | |
302 | static 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 | |
319 | static 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 [] = "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 | |
380 | static 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 | |
391 | String 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 | |
421 | bool 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 | |