1/*
2 * Copyright (C) 2012 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
32#include "ShapeOutsideInfo.h"
33
34#include "BoxShape.h"
35#include "FloatingObjects.h"
36#include "LengthFunctions.h"
37#include "RenderBlockFlow.h"
38#include "RenderBox.h"
39#include "RenderFragmentContainer.h"
40#include "RenderImage.h"
41#include "RenderView.h"
42
43namespace WebCore {
44
45LayoutRect ShapeOutsideInfo::computedShapePhysicalBoundingBox() const
46{
47 LayoutRect physicalBoundingBox = computedShape().shapeMarginLogicalBoundingBox();
48 physicalBoundingBox.setX(physicalBoundingBox.x() + logicalLeftOffset());
49 physicalBoundingBox.setY(physicalBoundingBox.y() + logicalTopOffset());
50 if (m_renderer.style().isFlippedBlocksWritingMode())
51 physicalBoundingBox.setY(m_renderer.logicalHeight() - physicalBoundingBox.maxY());
52 if (!m_renderer.style().isHorizontalWritingMode())
53 physicalBoundingBox = physicalBoundingBox.transposedRect();
54 return physicalBoundingBox;
55}
56
57FloatPoint ShapeOutsideInfo::shapeToRendererPoint(const FloatPoint& point) const
58{
59 FloatPoint result = FloatPoint(point.x() + logicalLeftOffset(), point.y() + logicalTopOffset());
60 if (m_renderer.style().isFlippedBlocksWritingMode())
61 result.setY(m_renderer.logicalHeight() - result.y());
62 if (!m_renderer.style().isHorizontalWritingMode())
63 result = result.transposedPoint();
64 return result;
65}
66
67FloatSize ShapeOutsideInfo::shapeToRendererSize(const FloatSize& size) const
68{
69 if (!m_renderer.style().isHorizontalWritingMode())
70 return size.transposedSize();
71 return size;
72}
73
74static inline CSSBoxType referenceBox(const ShapeValue& shapeValue)
75{
76 if (shapeValue.cssBox() == CSSBoxType::BoxMissing) {
77 if (shapeValue.type() == ShapeValue::Type::Image)
78 return CSSBoxType::ContentBox;
79 return CSSBoxType::MarginBox;
80 }
81 return shapeValue.cssBox();
82}
83
84void ShapeOutsideInfo::setReferenceBoxLogicalSize(LayoutSize newReferenceBoxLogicalSize)
85{
86 bool isHorizontalWritingMode = m_renderer.containingBlock()->style().isHorizontalWritingMode();
87 switch (referenceBox(*m_renderer.style().shapeOutside())) {
88 case CSSBoxType::MarginBox:
89 if (isHorizontalWritingMode)
90 newReferenceBoxLogicalSize.expand(m_renderer.horizontalMarginExtent(), m_renderer.verticalMarginExtent());
91 else
92 newReferenceBoxLogicalSize.expand(m_renderer.verticalMarginExtent(), m_renderer.horizontalMarginExtent());
93 break;
94 case CSSBoxType::BorderBox:
95 break;
96 case CSSBoxType::PaddingBox:
97 if (isHorizontalWritingMode)
98 newReferenceBoxLogicalSize.shrink(m_renderer.horizontalBorderExtent(), m_renderer.verticalBorderExtent());
99 else
100 newReferenceBoxLogicalSize.shrink(m_renderer.verticalBorderExtent(), m_renderer.horizontalBorderExtent());
101 break;
102 case CSSBoxType::ContentBox:
103 if (isHorizontalWritingMode)
104 newReferenceBoxLogicalSize.shrink(m_renderer.horizontalBorderAndPaddingExtent(), m_renderer.verticalBorderAndPaddingExtent());
105 else
106 newReferenceBoxLogicalSize.shrink(m_renderer.verticalBorderAndPaddingExtent(), m_renderer.horizontalBorderAndPaddingExtent());
107 break;
108 case CSSBoxType::FillBox:
109 case CSSBoxType::StrokeBox:
110 case CSSBoxType::ViewBox:
111 case CSSBoxType::BoxMissing:
112 ASSERT_NOT_REACHED();
113 break;
114 }
115
116 if (m_referenceBoxLogicalSize == newReferenceBoxLogicalSize)
117 return;
118 markShapeAsDirty();
119 m_referenceBoxLogicalSize = newReferenceBoxLogicalSize;
120}
121
122static inline bool checkShapeImageOrigin(Document& document, const StyleImage& styleImage)
123{
124 if (styleImage.isGeneratedImage())
125 return true;
126
127 ASSERT(styleImage.cachedImage());
128 CachedImage& cachedImage = *(styleImage.cachedImage());
129 if (cachedImage.isOriginClean(&document.securityOrigin()))
130 return true;
131
132 const URL& url = cachedImage.url();
133 String urlString = url.isNull() ? "''" : url.stringCenterEllipsizedToLength();
134 document.addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Unsafe attempt to load URL " + urlString + ".");
135
136 return false;
137}
138
139static LayoutRect getShapeImageMarginRect(const RenderBox& renderBox, const LayoutSize& referenceBoxLogicalSize)
140{
141 LayoutPoint marginBoxOrigin(-renderBox.marginLogicalLeft() - renderBox.borderAndPaddingLogicalLeft(), -renderBox.marginBefore() - renderBox.borderBefore() - renderBox.paddingBefore());
142 LayoutSize marginBoxSizeDelta(renderBox.marginLogicalWidth() + renderBox.borderAndPaddingLogicalWidth(), renderBox.marginLogicalHeight() + renderBox.borderAndPaddingLogicalHeight());
143 LayoutSize marginRectSize(referenceBoxLogicalSize + marginBoxSizeDelta);
144 marginRectSize.clampNegativeToZero();
145 return LayoutRect(marginBoxOrigin, marginRectSize);
146}
147
148std::unique_ptr<Shape> ShapeOutsideInfo::createShapeForImage(StyleImage* styleImage, float shapeImageThreshold, WritingMode writingMode, float margin) const
149{
150 LayoutSize imageSize = m_renderer.calculateImageIntrinsicDimensions(styleImage, m_referenceBoxLogicalSize, RenderImage::ScaleByEffectiveZoom);
151 styleImage->setContainerContextForRenderer(m_renderer, imageSize, m_renderer.style().effectiveZoom());
152
153 const LayoutRect& marginRect = getShapeImageMarginRect(m_renderer, m_referenceBoxLogicalSize);
154 const LayoutRect& imageRect = is<RenderImage>(m_renderer)
155 ? downcast<RenderImage>(m_renderer).replacedContentRect()
156 : LayoutRect(LayoutPoint(), imageSize);
157
158 ASSERT(!styleImage->isPending());
159 RefPtr<Image> image = styleImage->image(const_cast<RenderBox*>(&m_renderer), imageSize);
160 return Shape::createRasterShape(image.get(), shapeImageThreshold, imageRect, marginRect, writingMode, margin);
161}
162
163const Shape& ShapeOutsideInfo::computedShape() const
164{
165 if (Shape* shape = m_shape.get())
166 return *shape;
167
168 const RenderStyle& style = m_renderer.style();
169 ASSERT(m_renderer.containingBlock());
170 const RenderStyle& containingBlockStyle = m_renderer.containingBlock()->style();
171
172 WritingMode writingMode = containingBlockStyle.writingMode();
173 float margin = floatValueForLength(m_renderer.style().shapeMargin(), m_renderer.containingBlock() ? m_renderer.containingBlock()->contentWidth() : 0_lu);
174 float shapeImageThreshold = style.shapeImageThreshold();
175 const ShapeValue& shapeValue = *style.shapeOutside();
176
177 switch (shapeValue.type()) {
178 case ShapeValue::Type::Shape:
179 ASSERT(shapeValue.shape());
180 m_shape = Shape::createShape(*shapeValue.shape(), m_referenceBoxLogicalSize, writingMode, margin);
181 break;
182 case ShapeValue::Type::Image:
183 ASSERT(shapeValue.isImageValid());
184 m_shape = createShapeForImage(shapeValue.image(), shapeImageThreshold, writingMode, margin);
185 break;
186 case ShapeValue::Type::Box: {
187 RoundedRect shapeRect = computeRoundedRectForBoxShape(referenceBox(shapeValue), m_renderer);
188 if (!containingBlockStyle.isHorizontalWritingMode())
189 shapeRect = shapeRect.transposedRect();
190 m_shape = Shape::createBoxShape(shapeRect, writingMode, margin);
191 break;
192 }
193 }
194
195 ASSERT(m_shape);
196 return *m_shape;
197}
198
199static inline LayoutUnit borderBeforeInWritingMode(const RenderBox& renderer, WritingMode writingMode)
200{
201 switch (writingMode) {
202 case TopToBottomWritingMode: return renderer.borderTop();
203 case BottomToTopWritingMode: return renderer.borderBottom();
204 case LeftToRightWritingMode: return renderer.borderLeft();
205 case RightToLeftWritingMode: return renderer.borderRight();
206 }
207
208 ASSERT_NOT_REACHED();
209 return renderer.borderBefore();
210}
211
212static inline LayoutUnit borderAndPaddingBeforeInWritingMode(const RenderBox& renderer, WritingMode writingMode)
213{
214 switch (writingMode) {
215 case TopToBottomWritingMode: return renderer.borderTop() + renderer.paddingTop();
216 case BottomToTopWritingMode: return renderer.borderBottom() + renderer.paddingBottom();
217 case LeftToRightWritingMode: return renderer.borderLeft() + renderer.paddingLeft();
218 case RightToLeftWritingMode: return renderer.borderRight() + renderer.paddingRight();
219 }
220
221 ASSERT_NOT_REACHED();
222 return renderer.borderAndPaddingBefore();
223}
224
225LayoutUnit ShapeOutsideInfo::logicalTopOffset() const
226{
227 switch (referenceBox(*m_renderer.style().shapeOutside())) {
228 case CSSBoxType::MarginBox:
229 return -m_renderer.marginBefore(&m_renderer.containingBlock()->style());
230 case CSSBoxType::BorderBox:
231 return 0_lu;
232 case CSSBoxType::PaddingBox:
233 return borderBeforeInWritingMode(m_renderer, m_renderer.containingBlock()->style().writingMode());
234 case CSSBoxType::ContentBox:
235 return borderAndPaddingBeforeInWritingMode(m_renderer, m_renderer.containingBlock()->style().writingMode());
236 case CSSBoxType::FillBox:
237 break;
238 case CSSBoxType::StrokeBox:
239 break;
240 case CSSBoxType::ViewBox:
241 break;
242 case CSSBoxType::BoxMissing:
243 break;
244 }
245
246 ASSERT_NOT_REACHED();
247 return 0_lu;
248}
249
250static inline LayoutUnit borderStartWithStyleForWritingMode(const RenderBox& renderer, const RenderStyle& style)
251{
252 if (style.isHorizontalWritingMode()) {
253 if (style.isLeftToRightDirection())
254 return renderer.borderLeft();
255
256 return renderer.borderRight();
257 }
258 if (style.isLeftToRightDirection())
259 return renderer.borderTop();
260
261 return renderer.borderBottom();
262}
263
264static inline LayoutUnit borderAndPaddingStartWithStyleForWritingMode(const RenderBox& renderer, const RenderStyle& style)
265{
266 if (style.isHorizontalWritingMode()) {
267 if (style.isLeftToRightDirection())
268 return renderer.borderLeft() + renderer.paddingLeft();
269
270 return renderer.borderRight() + renderer.paddingRight();
271 }
272 if (style.isLeftToRightDirection())
273 return renderer.borderTop() + renderer.paddingTop();
274
275 return renderer.borderBottom() + renderer.paddingBottom();
276}
277
278LayoutUnit ShapeOutsideInfo::logicalLeftOffset() const
279{
280 if (m_renderer.isRenderFragmentContainer())
281 return 0_lu;
282
283 switch (referenceBox(*m_renderer.style().shapeOutside())) {
284 case CSSBoxType::MarginBox:
285 return -m_renderer.marginStart(&m_renderer.containingBlock()->style());
286 case CSSBoxType::BorderBox:
287 return 0_lu;
288 case CSSBoxType::PaddingBox:
289 return borderStartWithStyleForWritingMode(m_renderer, m_renderer.containingBlock()->style());
290 case CSSBoxType::ContentBox:
291 return borderAndPaddingStartWithStyleForWritingMode(m_renderer, m_renderer.containingBlock()->style());
292 case CSSBoxType::FillBox:
293 break;
294 case CSSBoxType::StrokeBox:
295 break;
296 case CSSBoxType::ViewBox:
297 break;
298 case CSSBoxType::BoxMissing:
299 break;
300 }
301
302 ASSERT_NOT_REACHED();
303 return 0_lu;
304}
305
306bool ShapeOutsideInfo::isEnabledFor(const RenderBox& box)
307{
308 ShapeValue* shapeValue = box.style().shapeOutside();
309 if (!box.isFloating() || !shapeValue)
310 return false;
311
312 switch (shapeValue->type()) {
313 case ShapeValue::Type::Shape: return shapeValue->shape();
314 case ShapeValue::Type::Image: return shapeValue->isImageValid() && checkShapeImageOrigin(box.document(), *(shapeValue->image()));
315 case ShapeValue::Type::Box: return true;
316 }
317
318 ASSERT_NOT_REACHED();
319 return false;
320}
321
322ShapeOutsideDeltas ShapeOutsideInfo::computeDeltasForContainingBlockLine(const RenderBlockFlow& containingBlock, const FloatingObject& floatingObject, LayoutUnit lineTop, LayoutUnit lineHeight)
323{
324 // If we never constructed this shape during layout, we propably don't need to know about it outside of layout in the context of "containing block line".
325 if (!m_shape && !containingBlock.view().frameView().layoutContext().isInLayout())
326 return { };
327
328 ASSERT(lineHeight >= 0);
329 LayoutUnit borderBoxTop = containingBlock.logicalTopForFloat(floatingObject) + containingBlock.marginBeforeForChild(m_renderer);
330 LayoutUnit borderBoxLineTop = lineTop - borderBoxTop;
331
332 if (isShapeDirty() || !m_shapeOutsideDeltas.isForLine(borderBoxLineTop, lineHeight)) {
333 LayoutUnit referenceBoxLineTop = borderBoxLineTop - logicalTopOffset();
334 LayoutUnit floatMarginBoxWidth = std::max<LayoutUnit>(0_lu, containingBlock.logicalWidthForFloat(floatingObject));
335
336 if (computedShape().lineOverlapsShapeMarginBounds(referenceBoxLineTop, lineHeight)) {
337 LineSegment segment = computedShape().getExcludedInterval((borderBoxLineTop - logicalTopOffset()), std::min(lineHeight, shapeLogicalBottom() - borderBoxLineTop));
338 if (segment.isValid) {
339 LayoutUnit logicalLeftMargin = containingBlock.style().isLeftToRightDirection() ? containingBlock.marginStartForChild(m_renderer) : containingBlock.marginEndForChild(m_renderer);
340 LayoutUnit rawLeftMarginBoxDelta = segment.logicalLeft + logicalLeftOffset() + logicalLeftMargin;
341 LayoutUnit leftMarginBoxDelta = clampTo<LayoutUnit>(rawLeftMarginBoxDelta, 0_lu, floatMarginBoxWidth);
342
343 LayoutUnit logicalRightMargin = containingBlock.style().isLeftToRightDirection() ? containingBlock.marginEndForChild(m_renderer) : containingBlock.marginStartForChild(m_renderer);
344 LayoutUnit rawRightMarginBoxDelta = segment.logicalRight + logicalLeftOffset() - containingBlock.logicalWidthForChild(m_renderer) - logicalRightMargin;
345 LayoutUnit rightMarginBoxDelta = clampTo<LayoutUnit>(rawRightMarginBoxDelta, -floatMarginBoxWidth, 0_lu);
346
347 m_shapeOutsideDeltas = ShapeOutsideDeltas(leftMarginBoxDelta, rightMarginBoxDelta, true, borderBoxLineTop, lineHeight);
348 return m_shapeOutsideDeltas;
349 }
350 }
351
352 // Lines that do not overlap the shape should act as if the float
353 // wasn't there for layout purposes. So we set the deltas to remove the
354 // entire width of the float
355 m_shapeOutsideDeltas = ShapeOutsideDeltas(floatMarginBoxWidth, -floatMarginBoxWidth, false, borderBoxLineTop, lineHeight);
356 }
357
358 return m_shapeOutsideDeltas;
359}
360
361}
362