25f79e913a9806564f617e6dd1e6207674b47fc7
[WebKit-https.git] / Source / WebCore / rendering / shapes / ShapeOutsideInfo.cpp
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
43 namespace WebCore {
44
45 LayoutRect 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
57 FloatPoint 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
67 FloatSize ShapeOutsideInfo::shapeToRendererSize(const FloatSize& size) const
68 {
69     if (!m_renderer.style().isHorizontalWritingMode())
70         return size.transposedSize();
71     return size;
72 }
73
74 static 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
84 void 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
122 static 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
139 static 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
148 std::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
163 const 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
199 static 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
212 static 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
225 LayoutUnit 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
250 static 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
264 static 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
278 LayoutUnit 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
306 bool 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
322 ShapeOutsideDeltas 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 }