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