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