• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
paint(PaintInfo & paintInfo,const LayoutPoint & paintOffset,const LayoutUnit lineTop,const LayoutUnit lineBottom)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 
paintFillLayers(const PaintInfo & paintInfo,const Color & c,const FillLayer & fillLayer,const LayoutRect & rect,CompositeOperator op)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 
paintFillLayer(const PaintInfo & paintInfo,const Color & c,const FillLayer & fillLayer,const LayoutRect & rect,CompositeOperator op)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 
paintBoxShadow(const PaintInfo & info,RenderStyle * s,ShadowStyle shadowStyle,const LayoutRect & paintRect)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 
clipRectForNinePieceImageStrip(InlineFlowBox * box,const NinePieceImage & image,const LayoutRect & paintRect)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 
paintBoxDecorationBackground(PaintInfo & paintInfo,const LayoutPoint & paintOffset)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 
paintMask(PaintInfo & paintInfo,const LayoutPoint & paintOffset)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 
roundedFrameRectClampedToLineTopAndBottomIfNeeded() const324 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