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 | } |
362 | |