Home | History | Annotate | Download | only in paint
      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