1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "config.h" 6 #include "core/paint/InlineFlowBoxPainter.h" 7 8 #include "core/paint/BoxPainter.h" 9 #include "core/rendering/InlineFlowBox.h" 10 #include "core/rendering/PaintInfo.h" 11 #include "core/rendering/RenderBlock.h" 12 #include "core/rendering/RenderInline.h" 13 #include "core/rendering/RenderLayer.h" 14 #include "core/rendering/RenderView.h" 15 #include "platform/graphics/GraphicsContextStateSaver.h" 16 17 namespace blink { 18 19 void InlineFlowBoxPainter::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, const LayoutUnit lineTop, const LayoutUnit lineBottom) 20 { 21 LayoutRect overflowRect(m_inlineFlowBox.visualOverflowRect(lineTop, lineBottom)); 22 m_inlineFlowBox.flipForWritingMode(overflowRect); 23 overflowRect.moveBy(paintOffset); 24 25 if (!paintInfo.rect.intersects(pixelSnappedIntRect(overflowRect))) 26 return; 27 28 if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) { 29 // Add ourselves to the paint info struct's list of inlines that need to paint their 30 // outlines. 31 if (m_inlineFlowBox.renderer().style()->visibility() == VISIBLE && m_inlineFlowBox.renderer().style()->hasOutline() && !m_inlineFlowBox.isRootInlineBox()) { 32 RenderInline& inlineFlow = toRenderInline(m_inlineFlowBox.renderer()); 33 34 RenderBlock* cb = 0; 35 bool containingBlockPaintsContinuationOutline = inlineFlow.continuation() || inlineFlow.isInlineElementContinuation(); 36 if (containingBlockPaintsContinuationOutline) { 37 // FIXME: See https://bugs.webkit.org/show_bug.cgi?id=54690. We currently don't reconnect inline continuations 38 // after a child removal. As a result, those merged inlines do not get seperated and hence not get enclosed by 39 // anonymous blocks. In this case, it is better to bail out and paint it ourself. 40 RenderBlock* enclosingAnonymousBlock = m_inlineFlowBox.renderer().containingBlock(); 41 if (!enclosingAnonymousBlock->isAnonymousBlock()) { 42 containingBlockPaintsContinuationOutline = false; 43 } else { 44 cb = enclosingAnonymousBlock->containingBlock(); 45 for (RenderBoxModelObject* box = m_inlineFlowBox.boxModelObject(); box != cb; box = box->parent()->enclosingBoxModelObject()) { 46 if (box->hasSelfPaintingLayer()) { 47 containingBlockPaintsContinuationOutline = false; 48 break; 49 } 50 } 51 } 52 } 53 54 if (containingBlockPaintsContinuationOutline) { 55 // Add ourselves to the containing block of the entire continuation so that it can 56 // paint us atomically. 57 cb->addContinuationWithOutline(toRenderInline(m_inlineFlowBox.renderer().node()->renderer())); 58 } else if (!inlineFlow.isInlineElementContinuation()) { 59 paintInfo.outlineObjects()->add(&inlineFlow); 60 } 61 } 62 } else if (paintInfo.phase == PaintPhaseMask) { 63 paintMask(paintInfo, paintOffset); 64 return; 65 } else if (paintInfo.phase == PaintPhaseForeground) { 66 // Paint our background, border and box-shadow. 67 paintBoxDecorationBackground(paintInfo, paintOffset); 68 } 69 70 // Paint our children. 71 if (paintInfo.phase != PaintPhaseSelfOutline) { 72 PaintInfo childInfo(paintInfo); 73 childInfo.phase = paintInfo.phase == PaintPhaseChildOutlines ? PaintPhaseOutline : paintInfo.phase; 74 75 if (childInfo.paintingRoot && childInfo.paintingRoot->isDescendantOf(&m_inlineFlowBox.renderer())) 76 childInfo.paintingRoot = 0; 77 else 78 childInfo.updatePaintingRootForChildren(&m_inlineFlowBox.renderer()); 79 80 for (InlineBox* curr = m_inlineFlowBox.firstChild(); curr; curr = curr->nextOnLine()) { 81 if (curr->renderer().isText() || !curr->boxModelObject()->hasSelfPaintingLayer()) 82 curr->paint(childInfo, paintOffset, lineTop, lineBottom); 83 } 84 } 85 } 86 87 void InlineFlowBoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, const FillLayer& fillLayer, const LayoutRect& rect, CompositeOperator op) 88 { 89 // FIXME: This should be a for loop or similar. It's a little non-trivial to do so, however, since the layers need to be 90 // painted in reverse order. 91 if (fillLayer.next()) 92 paintFillLayers(paintInfo, c, *fillLayer.next(), rect, op); 93 paintFillLayer(paintInfo, c, fillLayer, rect, op); 94 } 95 96 void InlineFlowBoxPainter::paintFillLayer(const PaintInfo& paintInfo, const Color& c, const FillLayer& fillLayer, const LayoutRect& rect, CompositeOperator op) 97 { 98 StyleImage* img = fillLayer.image(); 99 bool hasFillImage = img && img->canRender(m_inlineFlowBox.renderer(), m_inlineFlowBox.renderer().style()->effectiveZoom()); 100 if ((!hasFillImage && !m_inlineFlowBox.renderer().style()->hasBorderRadius()) || (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) || !m_inlineFlowBox.parent()) { 101 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), paintInfo, c, fillLayer, rect, BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op); 102 } else if (m_inlineFlowBox.renderer().style()->boxDecorationBreak() == DCLONE) { 103 GraphicsContextStateSaver stateSaver(*paintInfo.context); 104 paintInfo.context->clip(LayoutRect(rect.x(), rect.y(), m_inlineFlowBox.width(), m_inlineFlowBox.height())); 105 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), paintInfo, c, fillLayer, rect, BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op); 106 } else { 107 // We have a fill image that spans multiple lines. 108 // We need to adjust tx and ty by the width of all previous lines. 109 // Think of background painting on inlines as though you had one long line, a single continuous 110 // strip. Even though that strip has been broken up across multiple lines, you still paint it 111 // as though you had one single line. This means each line has to pick up the background where 112 // the previous line left off. 113 LayoutUnit logicalOffsetOnLine = 0; 114 LayoutUnit totalLogicalWidth; 115 if (m_inlineFlowBox.renderer().style()->direction() == LTR) { 116 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = curr->prevLineBox()) 117 logicalOffsetOnLine += curr->logicalWidth(); 118 totalLogicalWidth = logicalOffsetOnLine; 119 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->nextLineBox()) 120 totalLogicalWidth += curr->logicalWidth(); 121 } else { 122 for (InlineFlowBox* curr = m_inlineFlowBox.nextLineBox(); curr; curr = curr->nextLineBox()) 123 logicalOffsetOnLine += curr->logicalWidth(); 124 totalLogicalWidth = logicalOffsetOnLine; 125 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->prevLineBox()) 126 totalLogicalWidth += curr->logicalWidth(); 127 } 128 LayoutUnit stripX = rect.x() - (m_inlineFlowBox.isHorizontal() ? logicalOffsetOnLine : LayoutUnit()); 129 LayoutUnit stripY = rect.y() - (m_inlineFlowBox.isHorizontal() ? LayoutUnit() : logicalOffsetOnLine); 130 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogicalWidth : static_cast<LayoutUnit>(m_inlineFlowBox.width()); 131 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? static_cast<LayoutUnit>(m_inlineFlowBox.height()) : totalLogicalWidth; 132 133 GraphicsContextStateSaver stateSaver(*paintInfo.context); 134 paintInfo.context->clip(LayoutRect(rect.x(), rect.y(), m_inlineFlowBox.width(), m_inlineFlowBox.height())); 135 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), paintInfo, c, fillLayer, LayoutRect(stripX, stripY, stripWidth, stripHeight), BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op); 136 } 137 } 138 139 void InlineFlowBoxPainter::paintBoxShadow(const PaintInfo& info, RenderStyle* s, ShadowStyle shadowStyle, const LayoutRect& paintRect) 140 { 141 if ((!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) || !m_inlineFlowBox.parent()) { 142 BoxPainter::paintBoxShadow(info, paintRect, s, shadowStyle); 143 } else { 144 // FIXME: We can do better here in the multi-line case. We want to push a clip so that the shadow doesn't 145 // protrude incorrectly at the edges, and we want to possibly include shadows cast from the previous/following lines 146 BoxPainter::paintBoxShadow(info, paintRect, s, shadowStyle, m_inlineFlowBox.includeLogicalLeftEdge(), m_inlineFlowBox.includeLogicalRightEdge()); 147 } 148 } 149 150 151 static LayoutRect clipRectForNinePieceImageStrip(InlineFlowBox* box, const NinePieceImage& image, const LayoutRect& paintRect) 152 { 153 LayoutRect clipRect(paintRect); 154 RenderStyle* style = box->renderer().style(); 155 LayoutBoxExtent outsets = style->imageOutsets(image); 156 if (box->isHorizontal()) { 157 clipRect.setY(paintRect.y() - outsets.top()); 158 clipRect.setHeight(paintRect.height() + outsets.top() + outsets.bottom()); 159 if (box->includeLogicalLeftEdge()) { 160 clipRect.setX(paintRect.x() - outsets.left()); 161 clipRect.setWidth(paintRect.width() + outsets.left()); 162 } 163 if (box->includeLogicalRightEdge()) 164 clipRect.setWidth(clipRect.width() + outsets.right()); 165 } else { 166 clipRect.setX(paintRect.x() - outsets.left()); 167 clipRect.setWidth(paintRect.width() + outsets.left() + outsets.right()); 168 if (box->includeLogicalLeftEdge()) { 169 clipRect.setY(paintRect.y() - outsets.top()); 170 clipRect.setHeight(paintRect.height() + outsets.top()); 171 } 172 if (box->includeLogicalRightEdge()) 173 clipRect.setHeight(clipRect.height() + outsets.bottom()); 174 } 175 return clipRect; 176 } 177 178 void InlineFlowBoxPainter::paintBoxDecorationBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 179 { 180 ASSERT(paintInfo.phase == PaintPhaseForeground); 181 if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.renderer()) || m_inlineFlowBox.renderer().style()->visibility() != VISIBLE) 182 return; 183 184 // You can use p::first-line to specify a background. If so, the root line boxes for 185 // a line may actually have to paint a background. 186 RenderStyle* styleToUse = m_inlineFlowBox.renderer().style(m_inlineFlowBox.isFirstLineStyle()); 187 bool shouldPaintBoxDecorationBackground; 188 if (m_inlineFlowBox.parent()) 189 shouldPaintBoxDecorationBackground = m_inlineFlowBox.renderer().hasBoxDecorationBackground(); 190 else 191 shouldPaintBoxDecorationBackground = m_inlineFlowBox.isFirstLineStyle() && styleToUse != m_inlineFlowBox.renderer().style(); 192 193 if (!shouldPaintBoxDecorationBackground) 194 return; 195 196 LayoutRect frameRect = roundedFrameRectClampedToLineTopAndBottomIfNeeded(); 197 198 // Move x/y to our coordinates. 199 LayoutRect localRect(frameRect); 200 m_inlineFlowBox.flipForWritingMode(localRect); 201 LayoutPoint adjustedPaintOffset = paintOffset + localRect.location(); 202 203 LayoutRect paintRect = LayoutRect(adjustedPaintOffset, frameRect.size()); 204 205 // Shadow comes first and is behind the background and border. 206 if (!m_inlineFlowBox.boxModelObject()->boxShadowShouldBeAppliedToBackground(BackgroundBleedNone, &m_inlineFlowBox)) 207 paintBoxShadow(paintInfo, styleToUse, Normal, paintRect); 208 209 Color backgroundColor = m_inlineFlowBox.renderer().resolveColor(styleToUse, CSSPropertyBackgroundColor); 210 paintFillLayers(paintInfo, backgroundColor, styleToUse->backgroundLayers(), paintRect); 211 paintBoxShadow(paintInfo, styleToUse, Inset, paintRect); 212 213 // :first-line cannot be used to put borders on a line. Always paint borders with our 214 // non-first-line style. 215 if (m_inlineFlowBox.parent() && m_inlineFlowBox.renderer().style()->hasBorder()) { 216 const NinePieceImage& borderImage = m_inlineFlowBox.renderer().style()->borderImage(); 217 StyleImage* borderImageSource = borderImage.image(); 218 bool hasBorderImage = borderImageSource && borderImageSource->canRender(m_inlineFlowBox.renderer(), styleToUse->effectiveZoom()); 219 if (hasBorderImage && !borderImageSource->isLoaded()) 220 return; // Don't paint anything while we wait for the image to load. 221 222 // The simple case is where we either have no border image or we are the only box for this object. 223 // In those cases only a single call to draw is required. 224 if (!hasBorderImage || (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox())) { 225 BoxPainter::paintBorder(*m_inlineFlowBox.boxModelObject(), paintInfo, paintRect, m_inlineFlowBox.renderer().style(m_inlineFlowBox.isFirstLineStyle()), BackgroundBleedNone, m_inlineFlowBox.includeLogicalLeftEdge(), m_inlineFlowBox.includeLogicalRightEdge()); 226 } else { 227 // We have a border image that spans multiple lines. 228 // We need to adjust tx and ty by the width of all previous lines. 229 // Think of border image painting on inlines as though you had one long line, a single continuous 230 // strip. Even though that strip has been broken up across multiple lines, you still paint it 231 // as though you had one single line. This means each line has to pick up the image where 232 // the previous line left off. 233 // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, 234 // but it isn't even clear how this should work at all. 235 LayoutUnit logicalOffsetOnLine = 0; 236 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = curr->prevLineBox()) 237 logicalOffsetOnLine += curr->logicalWidth(); 238 LayoutUnit totalLogicalWidth = logicalOffsetOnLine; 239 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->nextLineBox()) 240 totalLogicalWidth += curr->logicalWidth(); 241 LayoutUnit stripX = adjustedPaintOffset.x() - (m_inlineFlowBox.isHorizontal() ? logicalOffsetOnLine : LayoutUnit()); 242 LayoutUnit stripY = adjustedPaintOffset.y() - (m_inlineFlowBox.isHorizontal() ? LayoutUnit() : logicalOffsetOnLine); 243 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogicalWidth : frameRect.width(); 244 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? frameRect.height() : totalLogicalWidth; 245 246 LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBox, borderImage, paintRect); 247 GraphicsContextStateSaver stateSaver(*paintInfo.context); 248 paintInfo.context->clip(clipRect); 249 BoxPainter::paintBorder(*m_inlineFlowBox.boxModelObject(), paintInfo, LayoutRect(stripX, stripY, stripWidth, stripHeight), m_inlineFlowBox.renderer().style(m_inlineFlowBox.isFirstLineStyle())); 250 } 251 } 252 } 253 254 void InlineFlowBoxPainter::paintMask(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 255 { 256 if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.renderer()) || m_inlineFlowBox.renderer().style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask) 257 return; 258 259 LayoutRect frameRect = roundedFrameRectClampedToLineTopAndBottomIfNeeded(); 260 261 // Move x/y to our coordinates. 262 LayoutRect localRect(frameRect); 263 m_inlineFlowBox.flipForWritingMode(localRect); 264 LayoutPoint adjustedPaintOffset = paintOffset + localRect.location(); 265 266 const NinePieceImage& maskNinePieceImage = m_inlineFlowBox.renderer().style()->maskBoxImage(); 267 StyleImage* maskBoxImage = m_inlineFlowBox.renderer().style()->maskBoxImage().image(); 268 269 // Figure out if we need to push a transparency layer to render our mask. 270 bool pushTransparencyLayer = false; 271 bool compositedMask = m_inlineFlowBox.renderer().hasLayer() && m_inlineFlowBox.boxModelObject()->layer()->hasCompositedMask(); 272 bool flattenCompositingLayers = m_inlineFlowBox.renderer().view()->frameView() && m_inlineFlowBox.renderer().view()->frameView()->paintBehavior() & PaintBehaviorFlattenCompositingLayers; 273 CompositeOperator compositeOp = CompositeSourceOver; 274 if (!compositedMask || flattenCompositingLayers) { 275 if ((maskBoxImage && m_inlineFlowBox.renderer().style()->maskLayers().hasImage()) || m_inlineFlowBox.renderer().style()->maskLayers().next()) 276 pushTransparencyLayer = true; 277 278 compositeOp = CompositeDestinationIn; 279 if (pushTransparencyLayer) { 280 paintInfo.context->setCompositeOperation(CompositeDestinationIn); 281 paintInfo.context->beginTransparencyLayer(1.0f); 282 compositeOp = CompositeSourceOver; 283 } 284 } 285 286 LayoutRect paintRect = LayoutRect(adjustedPaintOffset, frameRect.size()); 287 paintFillLayers(paintInfo, Color::transparent, m_inlineFlowBox.renderer().style()->maskLayers(), paintRect, compositeOp); 288 289 bool hasBoxImage = maskBoxImage && maskBoxImage->canRender(m_inlineFlowBox.renderer(), m_inlineFlowBox.renderer().style()->effectiveZoom()); 290 if (!hasBoxImage || !maskBoxImage->isLoaded()) { 291 if (pushTransparencyLayer) 292 paintInfo.context->endLayer(); 293 return; // Don't paint anything while we wait for the image to load. 294 } 295 296 // The simple case is where we are the only box for this object. In those 297 // cases only a single call to draw is required. 298 if (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) { 299 BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paintInfo.context, LayoutRect(adjustedPaintOffset, frameRect.size()), m_inlineFlowBox.renderer().style(), maskNinePieceImage, compositeOp); 300 } else { 301 // We have a mask image that spans multiple lines. 302 // We need to adjust _tx and _ty by the width of all previous lines. 303 LayoutUnit logicalOffsetOnLine = 0; 304 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = curr->prevLineBox()) 305 logicalOffsetOnLine += curr->logicalWidth(); 306 LayoutUnit totalLogicalWidth = logicalOffsetOnLine; 307 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->nextLineBox()) 308 totalLogicalWidth += curr->logicalWidth(); 309 LayoutUnit stripX = adjustedPaintOffset.x() - (m_inlineFlowBox.isHorizontal() ? logicalOffsetOnLine : LayoutUnit()); 310 LayoutUnit stripY = adjustedPaintOffset.y() - (m_inlineFlowBox.isHorizontal() ? LayoutUnit() : logicalOffsetOnLine); 311 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogicalWidth : frameRect.width(); 312 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? frameRect.height() : totalLogicalWidth; 313 314 LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBox, maskNinePieceImage, paintRect); 315 GraphicsContextStateSaver stateSaver(*paintInfo.context); 316 paintInfo.context->clip(clipRect); 317 BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paintInfo.context, LayoutRect(stripX, stripY, stripWidth, stripHeight), m_inlineFlowBox.renderer().style(), maskNinePieceImage, compositeOp); 318 } 319 320 if (pushTransparencyLayer) 321 paintInfo.context->endLayer(); 322 } 323 324 LayoutRect InlineFlowBoxPainter::roundedFrameRectClampedToLineTopAndBottomIfNeeded() const 325 { 326 // Pixel snap rect painting. 327 LayoutRect rect = m_inlineFlowBox.roundedFrameRect(); 328 329 bool noQuirksMode = m_inlineFlowBox.renderer().document().inNoQuirksMode(); 330 if (!noQuirksMode && !m_inlineFlowBox.hasTextChildren() && !(m_inlineFlowBox.descendantsHaveSameLineHeightAndBaseline() && m_inlineFlowBox.hasTextDescendants())) { 331 const RootInlineBox& rootBox = m_inlineFlowBox.root(); 332 LayoutUnit logicalTop = m_inlineFlowBox.isHorizontal() ? rect.y() : rect.x(); 333 LayoutUnit logicalHeight = m_inlineFlowBox.isHorizontal() ? rect.height() : rect.width(); 334 LayoutUnit bottom = std::min(rootBox.lineBottom(), logicalTop + logicalHeight); 335 logicalTop = std::max(rootBox.lineTop(), logicalTop); 336 logicalHeight = bottom - logicalTop; 337 if (m_inlineFlowBox.isHorizontal()) { 338 rect.setY(logicalTop); 339 rect.setHeight(logicalHeight); 340 } else { 341 rect.setX(logicalTop); 342 rect.setWidth(logicalHeight); 343 } 344 } 345 return rect; 346 } 347 348 } // namespace blink 349