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