void TableSectionPainter::paintObject(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { LayoutRect localPaintInvalidationRect = LayoutRect(paintInfo.cullRect().m_rect); localPaintInvalidationRect.moveBy(-paintOffset); LayoutRect tableAlignedRect = m_layoutTableSection.logicalRectForWritingModeAndDirection(localPaintInvalidationRect); CellSpan dirtiedRows = m_layoutTableSection.dirtiedRows(tableAlignedRect); CellSpan dirtiedColumns = m_layoutTableSection.dirtiedColumns(tableAlignedRect); if (dirtiedColumns.start() >= dirtiedColumns.end()) return; const HashSet<LayoutTableCell*>& overflowingCells = m_layoutTableSection.overflowingCells(); if (!m_layoutTableSection.hasMultipleCellLevels() && !overflowingCells.size()) { // Draw the dirty cells in the order that they appear. for (unsigned r = dirtiedRows.start(); r < dirtiedRows.end(); r++) { const LayoutTableRow* row = m_layoutTableSection.rowLayoutObjectAt(r); if (row && !row->hasSelfPaintingLayer()) TableRowPainter(*row).paintOutlineForRowIfNeeded(paintInfo, paintOffset); for (unsigned c = dirtiedColumns.start(); c < dirtiedColumns.end(); c++) { const LayoutTableSection::CellStruct& current = m_layoutTableSection.cellAt(r, c); const LayoutTableCell* cell = current.primaryCell(); if (!cell || (r > dirtiedRows.start() && m_layoutTableSection.primaryCellAt(r - 1, c) == cell) || (c > dirtiedColumns.start() && m_layoutTableSection.primaryCellAt(r, c - 1) == cell)) continue; paintCell(*cell, paintInfo, paintOffset); } } } else { // The overflowing cells should be scarce to avoid adding a lot of cells to the HashSet. #if ENABLE(ASSERT) unsigned totalRows = m_layoutTableSection.numRows(); unsigned totalCols = m_layoutTableSection.table()->columns().size(); ASSERT(overflowingCells.size() < totalRows * totalCols * gMaxAllowedOverflowingCellRatioForFastPaintPath); #endif // To make sure we properly paint invalidate the section, we paint invalidated all the overflowing cells that we collected. Vector<LayoutTableCell*> cells; copyToVector(overflowingCells, cells); HashSet<LayoutTableCell*> spanningCells; for (unsigned r = dirtiedRows.start(); r < dirtiedRows.end(); r++) { const LayoutTableRow* row = m_layoutTableSection.rowLayoutObjectAt(r); if (row && !row->hasSelfPaintingLayer()) TableRowPainter(*row).paintOutlineForRowIfNeeded(paintInfo, paintOffset); for (unsigned c = dirtiedColumns.start(); c < dirtiedColumns.end(); c++) { const LayoutTableSection::CellStruct& current = m_layoutTableSection.cellAt(r, c); if (!current.hasCells()) continue; for (unsigned i = 0; i < current.cells.size(); ++i) { if (overflowingCells.contains(current.cells[i])) continue; if (current.cells[i]->rowSpan() > 1 || current.cells[i]->colSpan() > 1) { if (!spanningCells.add(current.cells[i]).isNewEntry) continue; } cells.append(current.cells[i]); } } } // Sort the dirty cells by paint order. if (!overflowingCells.size()) std::stable_sort(cells.begin(), cells.end(), compareCellPositions); else std::sort(cells.begin(), cells.end(), compareCellPositionsWithOverflowingCells); for (unsigned i = 0; i < cells.size(); ++i) paintCell(*cells[i], paintInfo, paintOffset); } }
void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { // An empty viewport disables rendering. if (borderBoxRect().isEmpty()) return; // Don't paint, if the context explicitly disabled it. if (paintInfo.context().paintingDisabled()) return; // SVG outlines are painted during PaintPhaseForeground. if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) return; // An empty viewBox also disables rendering. // (http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute) if (svgSVGElement().hasEmptyViewBox()) return; Page* page = frame().page(); // Don't paint if we don't have kids, except if we have filters we should paint those. if (!firstChild()) { auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this); if (!resources || !resources->filter()) { if (page && paintInfo.phase == PaintPhaseForeground) page->addRelevantUnpaintedObject(this, visualOverflowRect()); return; } } if (page && paintInfo.phase == PaintPhaseForeground) page->addRelevantRepaintedObject(this, visualOverflowRect()); // Make a copy of the PaintInfo because applyTransform will modify the damage rect. PaintInfo childPaintInfo(paintInfo); childPaintInfo.context().save(); // Apply initial viewport clip if (shouldApplyViewportClip()) childPaintInfo.context().clip(snappedIntRect(overflowClipRect(paintOffset, currentRenderNamedFlowFragment()))); // Convert from container offsets (html renderers) to a relative transform (svg renderers). // Transform from our paint container's coordinate system to our local coords. IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset); childPaintInfo.applyTransform(AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y()) * localToBorderBoxTransform()); // SVGRenderingContext must be destroyed before we restore the childPaintInfo.context(), because a filter may have // changed the context and it is only reverted when the SVGRenderingContext destructor finishes applying the filter. { SVGRenderingContext renderingContext; bool continueRendering = true; if (childPaintInfo.phase == PaintPhaseForeground) { renderingContext.prepareToRenderSVGContent(*this, childPaintInfo); continueRendering = renderingContext.isRenderingPrepared(); } if (continueRendering) { childPaintInfo.updateSubtreePaintRootForChildren(this); for (auto& child : childrenOfType<RenderElement>(*this)) child.paint(childPaintInfo, location()); } } childPaintInfo.context().restore(); }
void ReplacedPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!m_layoutReplaced.shouldPaint(paintInfo, paintOffset)) return; LayoutPoint adjustedPaintOffset = paintOffset + m_layoutReplaced.location(); LayoutRect borderRect(adjustedPaintOffset, m_layoutReplaced.size()); if (m_layoutReplaced.hasBoxDecorationBackground() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) m_layoutReplaced.paintBoxDecorationBackground(paintInfo, adjustedPaintOffset); if (paintInfo.phase == PaintPhaseMask) { m_layoutReplaced.paintMask(paintInfo, adjustedPaintOffset); return; } if (paintInfo.phase == PaintPhaseClippingMask && (!m_layoutReplaced.hasLayer() || !m_layoutReplaced.layer()->hasCompositedClippingMask())) return; if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) { if (m_layoutReplaced.styleRef().outlineWidth()) ObjectPainter(m_layoutReplaced).paintOutline(paintInfo, adjustedPaintOffset); return; } if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && !m_layoutReplaced.canHaveChildren() && paintInfo.phase != PaintPhaseClippingMask) return; if (!paintInfo.shouldPaintWithinRoot(&m_layoutReplaced)) return; if (paintInfo.phase == PaintPhaseSelection) if (m_layoutReplaced.selectionState() == SelectionNone) return; { Optional<RoundedInnerRectClipper> clipper; bool completelyClippedOut = false; if (m_layoutReplaced.style()->hasBorderRadius()) { if (borderRect.isEmpty()) { completelyClippedOut = true; } else { // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. FloatRoundedRect roundedInnerRect = m_layoutReplaced.style()->getRoundedInnerBorderFor(borderRect, LayoutRectOutsets( -(m_layoutReplaced.paddingTop() + m_layoutReplaced.borderTop()), -(m_layoutReplaced.paddingRight() + m_layoutReplaced.borderRight()), -(m_layoutReplaced.paddingBottom() + m_layoutReplaced.borderBottom()), -(m_layoutReplaced.paddingLeft() + m_layoutReplaced.borderLeft())), true, true); clipper.emplace(m_layoutReplaced, paintInfo, borderRect, roundedInnerRect, ApplyToDisplayList); } } if (!completelyClippedOut) { if (paintInfo.phase == PaintPhaseClippingMask) { BoxPainter(m_layoutReplaced).paintClippingMask(paintInfo, adjustedPaintOffset); } else { m_layoutReplaced.paintReplaced(paintInfo, adjustedPaintOffset); } } } // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of // surrounding content. bool drawSelectionTint = paintInfo.phase == PaintPhaseForeground && m_layoutReplaced.selectionState() != SelectionNone && !paintInfo.isPrinting(); if (drawSelectionTint && !LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(*paintInfo.context, m_layoutReplaced, DisplayItem::SelectionTint, adjustedPaintOffset)) { LayoutRect selectionPaintingRect = m_layoutReplaced.localSelectionRect(); selectionPaintingRect.moveBy(adjustedPaintOffset); IntRect selectionPaintingIntRect = pixelSnappedIntRect(selectionPaintingRect); LayoutObjectDrawingRecorder drawingRecorder(*paintInfo.context, m_layoutReplaced, DisplayItem::SelectionTint, selectionPaintingIntRect, adjustedPaintOffset); paintInfo.context->fillRect(selectionPaintingIntRect, m_layoutReplaced.selectionBackgroundColor()); } }
void ViewPainter::paintBoxDecorationBackground(const PaintInfo& paintInfo) { if (paintInfo.skipRootBackground()) return; // This function overrides background painting for the LayoutView. // View background painting is special in the following ways: // 1. The view paints background for the root element, the background // positioning respects the positioning and transformation of the root // element. // 2. CSS background-clip is ignored, the background layers always expand to // cover the whole canvas. None of the stacking context effects (except // transformation) on the root element affects the background. // 3. The main frame is also responsible for painting the user-agent-defined // base background color. Conceptually it should be painted by the embedder // but painting it here allows culling and pre-blending optimization when // possible. GraphicsContext& context = paintInfo.context; if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible( context, m_layoutView, DisplayItem::kDocumentBackground)) return; // The background fill rect is the size of the LayoutView's main // GraphicsLayer. IntRect backgroundRect = pixelSnappedIntRect(m_layoutView.layer()->boundingBoxForCompositing()); const Document& document = m_layoutView.document(); const FrameView& frameView = *m_layoutView.frameView(); bool isMainFrame = document.isInMainFrame(); bool paintsBaseBackground = isMainFrame && !frameView.isTransparent(); bool shouldClearCanvas = paintsBaseBackground && (document.settings() && document.settings()->shouldClearDocumentBackground()); Color baseBackgroundColor = paintsBaseBackground ? frameView.baseBackgroundColor() : Color(); Color rootBackgroundColor = m_layoutView.style()->visitedDependentColor(CSSPropertyBackgroundColor); const LayoutObject* rootObject = document.documentElement() ? document.documentElement()->layoutObject() : nullptr; LayoutObjectDrawingRecorder recorder( context, m_layoutView, DisplayItem::kDocumentBackground, backgroundRect); // Special handling for print economy mode. bool forceBackgroundToWhite = BoxPainter::shouldForceWhiteBackgroundForPrintEconomy( m_layoutView.styleRef(), document); if (forceBackgroundToWhite) { // If for any reason the view background is not transparent, paint white // instead, otherwise keep transparent as is. if (paintsBaseBackground || rootBackgroundColor.alpha() || m_layoutView.style()->backgroundLayers().image()) context.fillRect(backgroundRect, Color::white, SkBlendMode::kSrc); return; } // Compute the enclosing rect of the view, in root element space. // // For background colors we can simply paint the document rect in the default // space. However for background image, the root element transform applies. // The strategy is to apply root element transform on the context and issue // draw commands in the local space, therefore we need to apply inverse // transform on the document rect to get to the root element space. bool backgroundRenderable = true; TransformationMatrix transform; IntRect paintRect = backgroundRect; if (!rootObject || !rootObject->isBox()) { backgroundRenderable = false; } else if (rootObject->hasLayer()) { const PaintLayer& rootLayer = *toLayoutBoxModelObject(rootObject)->layer(); LayoutPoint offset; rootLayer.convertToLayerCoords(nullptr, offset); transform.translate(offset.x(), offset.y()); transform.multiply( rootLayer.renderableTransform(paintInfo.getGlobalPaintFlags())); if (!transform.isInvertible()) { backgroundRenderable = false; } else { bool isClamped; paintRect = transform.inverse() .projectQuad(FloatQuad(backgroundRect), &isClamped) .enclosingBoundingBox(); backgroundRenderable = !isClamped; } } if (!backgroundRenderable) { if (baseBackgroundColor.alpha()) { context.fillRect( backgroundRect, baseBackgroundColor, shouldClearCanvas ? SkBlendMode::kSrc : SkBlendMode::kSrcOver); } else if (shouldClearCanvas) { context.fillRect(backgroundRect, Color(), SkBlendMode::kClear); } return; } BoxPainter::FillLayerOcclusionOutputList reversedPaintList; bool shouldDrawBackgroundInSeparateBuffer = BoxPainter(m_layoutView) .calculateFillLayerOcclusionCulling( reversedPaintList, m_layoutView.style()->backgroundLayers()); DCHECK(reversedPaintList.size()); // If the root background color is opaque, isolation group can be skipped // because the canvas // will be cleared by root background color. if (!rootBackgroundColor.hasAlpha()) shouldDrawBackgroundInSeparateBuffer = false; // We are going to clear the canvas with transparent pixels, isolation group // can be skipped. if (!baseBackgroundColor.alpha() && shouldClearCanvas) shouldDrawBackgroundInSeparateBuffer = false; if (shouldDrawBackgroundInSeparateBuffer) { if (baseBackgroundColor.alpha()) { context.fillRect( backgroundRect, baseBackgroundColor, shouldClearCanvas ? SkBlendMode::kSrc : SkBlendMode::kSrcOver); } context.beginLayer(); } Color combinedBackgroundColor = shouldDrawBackgroundInSeparateBuffer ? rootBackgroundColor : baseBackgroundColor.blend(rootBackgroundColor); if (combinedBackgroundColor.alpha()) { if (!combinedBackgroundColor.hasAlpha() && RuntimeEnabledFeatures::slimmingPaintV2Enabled()) recorder.setKnownToBeOpaque(); context.fillRect(backgroundRect, combinedBackgroundColor, (shouldDrawBackgroundInSeparateBuffer || shouldClearCanvas) ? SkBlendMode::kSrc : SkBlendMode::kSrcOver); } else if (shouldClearCanvas && !shouldDrawBackgroundInSeparateBuffer) { context.fillRect(backgroundRect, Color(), SkBlendMode::kClear); } for (auto it = reversedPaintList.rbegin(); it != reversedPaintList.rend(); ++it) { DCHECK((*it)->clip() == BorderFillBox); bool shouldPaintInViewportSpace = (*it)->attachment() == FixedBackgroundAttachment; if (shouldPaintInViewportSpace) { BoxPainter::paintFillLayer(m_layoutView, paintInfo, Color(), **it, LayoutRect(LayoutRect::infiniteIntRect()), BackgroundBleedNone); } else { context.save(); // TODO(trchen): We should be able to handle 3D-transformed root // background with slimming paint by using transform display items. context.concatCTM(transform.toAffineTransform()); BoxPainter::paintFillLayer(m_layoutView, paintInfo, Color(), **it, LayoutRect(paintRect), BackgroundBleedNone); context.restore(); } } if (shouldDrawBackgroundInSeparateBuffer) context.endLayer(); }
void BlockPainter::paintContents(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { DCHECK(!m_layoutBlock.childrenInline()); PaintInfo paintInfoForDescendants = paintInfo.forDescendants(); m_layoutBlock.paintChildren(paintInfoForDescendants, paintOffset); }
void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { ANNOTATE_GRAPHICS_CONTEXT(paintInfo, this); if (!shouldPaint(paintInfo, paintOffset)) return; LayoutPoint adjustedPaintOffset = paintOffset + location(); if (hasBoxDecorationBackground() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) paintBoxDecorationBackground(paintInfo, adjustedPaintOffset); if (paintInfo.phase == PaintPhaseMask) { paintMask(paintInfo, adjustedPaintOffset); return; } if (paintInfo.phase == PaintPhaseClippingMask && (!hasLayer() || !layer()->hasCompositedClippingMask())) return; LayoutRect paintRect = LayoutRect(adjustedPaintOffset, size()); if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) paintOutline(paintInfo, paintRect); if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && !canHaveChildren() && paintInfo.phase != PaintPhaseClippingMask) return; if (!paintInfo.shouldPaintWithinRoot(this)) return; bool drawSelectionTint = selectionState() != SelectionNone && !document().printing(); if (paintInfo.phase == PaintPhaseSelection) { if (selectionState() == SelectionNone) return; drawSelectionTint = false; } bool completelyClippedOut = false; if (style()->hasBorderRadius()) { LayoutRect borderRect = LayoutRect(adjustedPaintOffset, size()); if (borderRect.isEmpty()) completelyClippedOut = true; else { // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. paintInfo.context->save(); RoundedRect roundedInnerRect = style()->getRoundedInnerBorderFor(paintRect, paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), true, true); clipRoundedInnerRect(paintInfo.context, paintRect, roundedInnerRect); } } if (!completelyClippedOut) { if (paintInfo.phase == PaintPhaseClippingMask) { paintClippingMask(paintInfo, adjustedPaintOffset); } else { paintReplaced(paintInfo, adjustedPaintOffset); } if (style()->hasBorderRadius()) paintInfo.context->restore(); } // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of // surrounding content. if (drawSelectionTint) { LayoutRect selectionPaintingRect = localSelectionRect(); selectionPaintingRect.moveBy(adjustedPaintOffset); paintInfo.context->fillRect(pixelSnappedIntRect(selectionPaintingRect), selectionBackgroundColor()); } }
void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!shouldPaint(paintInfo, paintOffset)) return; #ifndef NDEBUG SetLayoutNeededForbiddenScope scope(this); #endif LayoutPoint adjustedPaintOffset = paintOffset + location(); if (hasBoxDecorations() && paintInfo.phase == PaintPhaseForeground) paintBoxDecorations(paintInfo, adjustedPaintOffset); if (paintInfo.phase == PaintPhaseMask) { paintMask(paintInfo, adjustedPaintOffset); return; } LayoutRect paintRect = LayoutRect(adjustedPaintOffset, size()); if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style().outlineWidth()) paintOutline(paintInfo, paintRect); if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && !canHaveChildren()) return; if (!paintInfo.shouldPaintWithinRoot(*this)) return; bool drawSelectionTint = shouldDrawSelectionTint(); if (paintInfo.phase == PaintPhaseSelection) { if (selectionState() == SelectionNone) return; drawSelectionTint = false; } bool completelyClippedOut = false; if (style().hasBorderRadius()) { LayoutRect borderRect = LayoutRect(adjustedPaintOffset, size()); if (borderRect.isEmpty()) completelyClippedOut = true; else { // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. paintInfo.context().save(); FloatRoundedRect roundedInnerRect = FloatRoundedRect(style().getRoundedInnerBorderFor(paintRect, paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), true, true)); clipRoundedInnerRect(paintInfo.context(), paintRect, roundedInnerRect); } } if (!completelyClippedOut) { paintReplaced(paintInfo, adjustedPaintOffset); if (style().hasBorderRadius()) paintInfo.context().restore(); } // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of // surrounding content. if (drawSelectionTint) { LayoutRect selectionPaintingRect = localSelectionRect(); selectionPaintingRect.moveBy(adjustedPaintOffset); paintInfo.context().fillRect(snappedIntRect(selectionPaintingRect), selectionBackgroundColor(), style().colorSpace()); } }
void RenderReplaced::paint(PaintInfo& paintInfo, int tx, int ty) { if (!shouldPaint(paintInfo, tx, ty)) return; tx += x(); ty += y(); if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) paintBoxDecorations(paintInfo, tx, ty); if (paintInfo.phase == PaintPhaseMask) { paintMask(paintInfo, tx, ty); return; } if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) paintOutline(paintInfo.context, tx, ty, width(), height()); if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection) return; if (!paintInfo.shouldPaintWithinRoot(this)) return; bool drawSelectionTint = selectionState() != SelectionNone && !document()->printing(); // On the device our selection background is opaque white, // which means images get turned white when they're selected. // Just draw them as if they weren't selected. #if OS(LINUX) && !(CPU(X86) || CPU(X86_64)) drawSelectionTint = false; #endif if (paintInfo.phase == PaintPhaseSelection) { if (selectionState() == SelectionNone) return; drawSelectionTint = false; } bool completelyClippedOut = false; if (style()->hasBorderRadius()) { IntRect borderRect = IntRect(tx, ty, width(), height()); if (borderRect.isEmpty()) completelyClippedOut = true; else { // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. paintInfo.context->save(); paintInfo.context->addRoundedRectClip(style()->getRoundedBorderFor(borderRect)); } } if (!completelyClippedOut) { paintReplaced(paintInfo, tx, ty); if (style()->hasBorderRadius()) paintInfo.context->restore(); } // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of // surrounding content. if (drawSelectionTint) { IntRect selectionPaintingRect = localSelectionRect(); selectionPaintingRect.move(tx, ty); paintInfo.context->fillRect(selectionPaintingRect, selectionBackgroundColor(), style()->colorSpace()); } }
void RenderMathMLMenclose::paint(PaintInfo& info, const LayoutPoint& paintOffset) { RenderMathMLRow::paint(info, paintOffset); if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE) return; LayoutUnit thickness = ruleThickness(); // Make a copy of the PaintInfo because applyTransform will modify its rect. PaintInfo paintInfo(info); GraphicsContextStateSaver stateSaver(paintInfo.context()); paintInfo.context().setStrokeThickness(thickness); paintInfo.context().setStrokeStyle(SolidStroke); paintInfo.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor)); paintInfo.context().setFillColor(Color::transparent); paintInfo.applyTransform(AffineTransform().translate(paintOffset + location())); // In the MathML in HTML5 implementation note, the "left" notation is described as follows: // - center of the left vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2 // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 if (hasNotation(MathMLMencloseElement::Left)) { LayoutUnit x = m_contentRect.x() - 7 * thickness / 2; LayoutUnit yStart = m_contentRect.y() - 4 * thickness; LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness; drawLine(info, x, yStart, x, yEnd); } // In the MathML in HTML5 implementation note, the "right" notation is described as follows: // - center of the right vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2 // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 if (hasNotation(MathMLMencloseElement::Right)) { LayoutUnit x = m_contentRect.maxX() + 7 * thickness / 2; LayoutUnit yStart = m_contentRect.y() - 4 * thickness; LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness; drawLine(info, x, yStart, x, yEnd); } // In the MathML in HTML5 implementation note, the "vertical" notation is horizontally centered. if (hasNotation(MathMLMencloseElement::VerticalStrike)) { LayoutUnit x = m_contentRect.x() + (m_contentRect.width() - thickness) / 2; LayoutUnit yStart = m_contentRect.y(); LayoutUnit yEnd = m_contentRect.maxY(); drawLine(info, x, yStart, x, yEnd); } // In the MathML in HTML5 implementation note, the "top" notation is described as follows: // - middle of the top horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2 // - left and right spaces have size 4\xi_8 if (hasNotation(MathMLMencloseElement::Top)) { LayoutUnit y = m_contentRect.y() - 7 * thickness / 2; LayoutUnit xStart = m_contentRect.x() - 4 * thickness; LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness; drawLine(info, xStart, y, xEnd, y); } // In the MathML in HTML5 implementation note, the "bottom" notation is described as follows: // - middle of the bottom horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2 // - left and right spaces have size 4\xi_8 if (hasNotation(MathMLMencloseElement::Bottom)) { LayoutUnit y = m_contentRect.maxY() + 7 * thickness / 2; LayoutUnit xStart = m_contentRect.x() - 4 * thickness; LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness; drawLine(info, xStart, y, xEnd, y); } // In the MathML in HTML5 implementation note, the "vertical" notation is vertically centered. if (hasNotation(MathMLMencloseElement::HorizontalStrike)) { LayoutUnit y = m_contentRect.y() + (m_contentRect.height() - thickness) / 2; LayoutUnit xStart = m_contentRect.x(); LayoutUnit xEnd = m_contentRect.maxX(); drawLine(info, xStart, y, xEnd, y); } // In the MathML in HTML5 implementation note, the "updiagonalstrike" goes from the bottom left corner // to the top right corner. if (hasNotation(MathMLMencloseElement::UpDiagonalStrike)) drawLine(info, m_contentRect.x(), m_contentRect.maxY(), m_contentRect.maxX(), m_contentRect.y()); // In the MathML in HTML5 implementation note, the "downdiagonalstrike" goes from the top left corner // to the bottom right corner. if (hasNotation(MathMLMencloseElement::DownDiagonalStrike)) drawLine(info, m_contentRect.x(), m_contentRect.y(), m_contentRect.maxX(), m_contentRect.maxY()); // In the MathML in HTML5 implementation note, the "roundedbox" has radii size 3\xi_8 and is obtained // by inflating the content box by 3\xi_8 + \xi_8/2 = 7\xi_8/2 if (hasNotation(MathMLMencloseElement::RoundedBox)) { LayoutSize radiiSize(3 * thickness, 3 * thickness); RoundedRect::Radii radii(radiiSize, radiiSize, radiiSize, radiiSize); RoundedRect roundedRect(m_contentRect, radii); roundedRect.inflate(7 * thickness / 2); Path path; path.addRoundedRect(roundedRect); paintInfo.context().strokePath(path); } // For longdiv, we use our own rules for now: // - top space is like "top" notation // - bottom space is like "bottom" notation // - right space is like "right" notation // - left space is longDivLeftSpace * \xi_8 // - We subtract half of the thickness from these spaces to obtain "top", "bottom", "left" // and "right" coordinates. // - The top bar is drawn from "right" to "left" and positioned at vertical offset "top". // - The left part is draw as a quadratic Bezier curve with end points going from "top" to // "bottom" and positioned at horizontal offset "left". // - In order to force the curvature of the left part, we use a middle point that is vertically // centered and shifted towards the right by longDivLeftSpace * \xi_8 if (hasNotation(MathMLMencloseElement::LongDiv)) { LayoutUnit top = m_contentRect.y() - 7 * thickness / 2; LayoutUnit bottom = m_contentRect.maxY() + 7 * thickness / 2; LayoutUnit left = m_contentRect.x() - longDivLeftSpace * thickness + thickness / 2; LayoutUnit right = m_contentRect.maxX() + 4 * thickness; LayoutUnit midX = left + longDivLeftSpace * thickness; LayoutUnit midY = (top + bottom) / 2; Path path; path.moveTo(LayoutPoint(right, top)); path.addLineTo(LayoutPoint(left, top)); path.addQuadCurveTo(LayoutPoint(midX, midY), FloatPoint(left, bottom)); paintInfo.context().strokePath(path); } // In the MathML in HTML5 implementation note, the "circle" notation is described as follows: // - The center and axes are the same as the content bounding box. // - The width of the bounding box is \xi_8/2 + contentWidth * \sqrt{2} + \xi_8/2 // - The height is \xi_8/2 + contentHeight * \sqrt{2} + \xi_8/2 if (hasNotation(MathMLMencloseElement::Circle)) { LayoutRect ellipseRect; ellipseRect.setWidth(m_contentRect.width() * sqrtOfTwoFloat + thickness); ellipseRect.setHeight(m_contentRect.height() * sqrtOfTwoFloat + thickness); ellipseRect.setX(m_contentRect.x() - (ellipseRect.width() - m_contentRect.width()) / 2); ellipseRect.setY(m_contentRect.y() - (ellipseRect.height() - m_contentRect.height()) / 2); Path path; path.addEllipse(ellipseRect); paintInfo.context().strokePath(path); } }
void RenderMathMLBlock::paint(PaintInfo& info, const LayoutPoint& paintOffset) { RenderBlock::paint(info, paintOffset); if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground) return; IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location()); GraphicsContextStateSaver stateSaver(info.context()); info.context().setStrokeThickness(1.0f); info.context().setStrokeStyle(SolidStroke); info.context().setStrokeColor(Color(0, 0, 255)); info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y())); info.context().drawLine(IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); int topStart = paddingTop(); info.context().setStrokeColor(Color(0, 255, 0)); info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + topStart), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + topStart)); int baseline = roundToInt(baselinePosition(AlphabeticBaseline, true, HorizontalLine)); info.context().setStrokeColor(Color(255, 0, 0)); info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + baseline), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + baseline)); }
void RootInlineBoxPainter::paintEllipsisBox(const PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit lineTop, LayoutUnit lineBottom) const { if (m_rootInlineBox.hasEllipsisBox() && paintInfo.shouldPaintWithinRoot(&m_rootInlineBox.layoutObject()) && m_rootInlineBox.lineLayoutItem().style()->visibility() == VISIBLE && paintInfo.phase == PaintPhaseForeground) m_rootInlineBox.ellipsisBox()->paint(paintInfo, paintOffset, lineTop, lineBottom); }
void TableCellPainter::paintCollapsedBorders(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { ASSERT(paintInfo.phase == PaintPhaseCollapsedTableBorders); if (!paintInfo.shouldPaintWithinRoot(&m_layoutTableCell) || m_layoutTableCell.style()->visibility() != VISIBLE) return; LayoutRect paintRect = paintBounds(paintOffset, AddOffsetFromParent); LayoutRect drawingCullRect(paintRect); drawingCullRect.expandEdges(m_layoutTableCell.table()->outerBorderTop(), m_layoutTableCell.table()->outerBorderRight(), m_layoutTableCell.table()->outerBorderBottom(), m_layoutTableCell.table()->outerBorderLeft()); if (drawingCullRect.y() >= paintInfo.rect.maxY()) return; if (drawingCullRect.maxY() <= paintInfo.rect.y()) return; const CollapsedBorderValue* tableCurrentBorderValue = m_layoutTableCell.table()->currentBorderValue(); if (!tableCurrentBorderValue) return; const ComputedStyle& styleForCellFlow = m_layoutTableCell.styleForCellFlow(); const CollapsedBorderValue& leftBorderValue = cachedCollapsedLeftBorder(styleForCellFlow); const CollapsedBorderValue& rightBorderValue = cachedCollapsedRightBorder(styleForCellFlow); const CollapsedBorderValue& topBorderValue = cachedCollapsedTopBorder(styleForCellFlow); const CollapsedBorderValue& bottomBorderValue = cachedCollapsedBottomBorder(styleForCellFlow); bool shouldPaintTop = topBorderValue.shouldPaint(*tableCurrentBorderValue); bool shouldPaintBottom = bottomBorderValue.shouldPaint(*tableCurrentBorderValue); bool shouldPaintLeft = leftBorderValue.shouldPaint(*tableCurrentBorderValue); bool shouldPaintRight = rightBorderValue.shouldPaint(*tableCurrentBorderValue); if (!shouldPaintTop && !shouldPaintBottom && !shouldPaintLeft && !shouldPaintRight) return; GraphicsContext* graphicsContext = paintInfo.context; LayoutObjectDrawingRecorder recorder(*graphicsContext, m_layoutTableCell, paintInfo.phase, drawingCullRect); if (recorder.canUseCachedDrawing()) return; bool antialias = BoxPainter::shouldAntialiasLines(graphicsContext); // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location. int topWidth = topBorderValue.width(); int bottomWidth = bottomBorderValue.width(); int leftWidth = leftBorderValue.width(); int rightWidth = rightBorderValue.width(); IntRect borderRect = pixelSnappedIntRect(paintRect.x() - leftWidth / 2, paintRect.y() - topWidth / 2, paintRect.width() + leftWidth / 2 + (rightWidth + 1) / 2, paintRect.height() + topWidth / 2 + (bottomWidth + 1) / 2); Color cellColor = m_layoutTableCell.resolveColor(CSSPropertyColor); // We never paint diagonals at the joins. We simply let the border with the highest // precedence paint on top of borders with lower precedence. if (shouldPaintTop) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.x(), borderRect.y(), borderRect.maxX(), borderRect.y() + topWidth, BSTop, topBorderValue.color().resolve(cellColor), collapsedBorderStyle(topBorderValue.style()), 0, 0, antialias); } if (shouldPaintBottom) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.x(), borderRect.maxY() - bottomWidth, borderRect.maxX(), borderRect.maxY(), BSBottom, bottomBorderValue.color().resolve(cellColor), collapsedBorderStyle(bottomBorderValue.style()), 0, 0, antialias); } if (shouldPaintLeft) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.x(), borderRect.y(), borderRect.x() + leftWidth, borderRect.maxY(), BSLeft, leftBorderValue.color().resolve(cellColor), collapsedBorderStyle(leftBorderValue.style()), 0, 0, antialias); } if (shouldPaintRight) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.maxX() - rightWidth, borderRect.y(), borderRect.maxX(), borderRect.maxY(), BSRight, rightBorderValue.color().resolve(cellColor), collapsedBorderStyle(rightBorderValue.style()), 0, 0, antialias); } }
void InlineFlowBoxPainter::paintMask(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.layoutObject()) || m_inlineFlowBox.layoutObject().style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask) return; LayoutRect frameRect = roundedFrameRectClampedToLineTopAndBottomIfNeeded(); // Move x/y to our coordinates. LayoutRect localRect(frameRect); m_inlineFlowBox.flipForWritingMode(localRect); LayoutPoint adjustedPaintOffset = paintOffset + localRect.location(); const NinePieceImage& maskNinePieceImage = m_inlineFlowBox.layoutObject().style()->maskBoxImage(); StyleImage* maskBoxImage = m_inlineFlowBox.layoutObject().style()->maskBoxImage().image(); // Figure out if we need to push a transparency layer to render our mask. bool pushTransparencyLayer = false; bool compositedMask = m_inlineFlowBox.layoutObject().hasLayer() && m_inlineFlowBox.boxModelObject()->layer()->hasCompositedMask(); bool flattenCompositingLayers = m_inlineFlowBox.layoutObject().view()->frameView() && m_inlineFlowBox.layoutObject().view()->frameView()->paintBehavior() & PaintBehaviorFlattenCompositingLayers; SkXfermode::Mode compositeOp = SkXfermode::kSrcOver_Mode; if (!compositedMask || flattenCompositingLayers) { if ((maskBoxImage && m_inlineFlowBox.layoutObject().style()->maskLayers().hasImage()) || m_inlineFlowBox.layoutObject().style()->maskLayers().next()) { pushTransparencyLayer = true; paintInfo.context->beginLayer(1.0f, SkXfermode::kDstIn_Mode); } else { // TODO(fmalita): passing a dst-in xfer mode down to paintFillLayers/paintNinePieceImage // seems dangerous: it is only correct if applied atomically (single draw call). While // the heuristic above presumably ensures that is the case, this approach seems super // fragile. We should investigate dropping this optimization in favour of the more // robust layer branch above. compositeOp = SkXfermode::kDstIn_Mode; } } LayoutRect paintRect = LayoutRect(adjustedPaintOffset, frameRect.size()); paintFillLayers(paintInfo, Color::transparent, m_inlineFlowBox.layoutObject().style()->maskLayers(), paintRect, compositeOp); bool hasBoxImage = maskBoxImage && maskBoxImage->canRender(m_inlineFlowBox.layoutObject(), m_inlineFlowBox.layoutObject().style()->effectiveZoom()); if (!hasBoxImage || !maskBoxImage->isLoaded()) { if (pushTransparencyLayer) paintInfo.context->endLayer(); return; // Don't paint anything while we wait for the image to load. } // The simple case is where we are the only box for this object. In those // cases only a single call to draw is required. if (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) { BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paintInfo.context, paintRect, m_inlineFlowBox.layoutObject().styleRef(), maskNinePieceImage, compositeOp); } else { // We have a mask image that spans multiple lines. // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, // but it isn't even clear how this should work at all. LayoutRect imageStripPaintRect = paintRectForImageStrip(adjustedPaintOffset, frameRect.size(), LTR); LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBox, maskNinePieceImage, paintRect); GraphicsContextStateSaver stateSaver(*paintInfo.context); paintInfo.context->clip(clipRect); BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paintInfo.context, imageStripPaintRect, m_inlineFlowBox.layoutObject().styleRef(), maskNinePieceImage, compositeOp); } if (pushTransparencyLayer) paintInfo.context->endLayer(); }
void InlineFlowBoxPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset, const LayoutUnit lineTop, const LayoutUnit lineBottom) { LayoutRect overflowRect(m_inlineFlowBox.visualOverflowRect(lineTop, lineBottom)); m_inlineFlowBox.flipForWritingMode(overflowRect); overflowRect.moveBy(paintOffset); if (!paintInfo.rect.intersects(pixelSnappedIntRect(overflowRect))) return; if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) { // Add ourselves to the paint info struct's list of inlines that need to paint their // outlines. if (m_inlineFlowBox.layoutObject().style()->visibility() == VISIBLE && m_inlineFlowBox.layoutObject().style()->hasOutline() && !m_inlineFlowBox.isRootInlineBox()) { LayoutInline& inlineFlow = toLayoutInline(m_inlineFlowBox.layoutObject()); LayoutBlock* cb = 0; bool containingBlockPaintsContinuationOutline = inlineFlow.continuation() || inlineFlow.isInlineElementContinuation(); if (containingBlockPaintsContinuationOutline) { // FIXME: See https://bugs.webkit.org/show_bug.cgi?id=54690. We currently don't reconnect inline continuations // after a child removal. As a result, those merged inlines do not get seperated and hence not get enclosed by // anonymous blocks. In this case, it is better to bail out and paint it ourself. LayoutBlock* enclosingAnonymousBlock = m_inlineFlowBox.layoutObject().containingBlock(); if (!enclosingAnonymousBlock->isAnonymousBlock()) { containingBlockPaintsContinuationOutline = false; } else { cb = enclosingAnonymousBlock->containingBlock(); for (LayoutBoxModelObject* box = m_inlineFlowBox.boxModelObject(); box != cb; box = box->parent()->enclosingBoxModelObject()) { if (box->hasSelfPaintingLayer()) { containingBlockPaintsContinuationOutline = false; break; } } } } if (containingBlockPaintsContinuationOutline) { // Add ourselves to the containing block of the entire continuation so that it can // paint us atomically. cb->addContinuationWithOutline(toLayoutInline(m_inlineFlowBox.layoutObject().node()->layoutObject())); } else if (!inlineFlow.isInlineElementContinuation()) { paintInfo.outlineObjects()->add(&inlineFlow); } } } else if (paintInfo.phase == PaintPhaseMask) { DrawingRecorder recorder(*paintInfo.context, m_inlineFlowBox, DisplayItem::paintPhaseToDrawingType(paintInfo.phase), pixelSnappedIntRect(overflowRect)); if (!recorder.canUseCachedDrawing()) paintMask(paintInfo, paintOffset); return; } else if (paintInfo.phase == PaintPhaseForeground) { // Paint our background, border and box-shadow. paintBoxDecorationBackground(paintInfo, paintOffset); } // Paint our children. if (paintInfo.phase != PaintPhaseSelfOutline) { PaintInfo childInfo(paintInfo); childInfo.phase = paintInfo.phase == PaintPhaseChildOutlines ? PaintPhaseOutline : paintInfo.phase; if (childInfo.paintingRoot && childInfo.paintingRoot->isDescendantOf(&m_inlineFlowBox.layoutObject())) childInfo.paintingRoot = 0; else childInfo.updatePaintingRootForChildren(&m_inlineFlowBox.layoutObject()); for (InlineBox* curr = m_inlineFlowBox.firstChild(); curr; curr = curr->nextOnLine()) { if (curr->layoutObject().isText() || !curr->boxModelObject()->hasSelfPaintingLayer()) curr->paint(childInfo, paintOffset, lineTop, lineBottom); } } }
void SVGInlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint&, LayoutUnit, LayoutUnit) { ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(truncation() == cNoTruncation); if (renderer()->style()->visibility() != VISIBLE) return; // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox. // If we ever need that for SVG, it's very easy to refactor and reuse the code. RenderObject* parentRenderer = parent()->renderer(); ASSERT(parentRenderer); bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; bool hasSelection = !parentRenderer->document()->printing() && selectionState() != RenderObject::SelectionNone; if (!hasSelection && paintSelectedTextOnly) return; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); if (!textShouldBePainted(textRenderer)) return; RenderStyle* style = parentRenderer->style(); ASSERT(style); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); bool hasFill = svgStyle->hasFill(); bool hasStroke = svgStyle->hasStroke(); RenderStyle* selectionStyle = style; if (hasSelection) { selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); if (selectionStyle) { const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); ASSERT(svgSelectionStyle); if (!hasFill) hasFill = svgSelectionStyle->hasFill(); if (!hasStroke) hasStroke = svgSelectionStyle->hasStroke(); } else selectionStyle = style; } AffineTransform fragmentTransform; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_textFragments.at(i); ASSERT(!m_paintingResource); GraphicsContextStateSaver stateSaver(*paintInfo.context); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) paintInfo.context->concatCTM(fragmentTransform); // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. int decorations = style->textDecorationsInEffect(); if (decorations & UNDERLINE) paintDecoration(paintInfo.context, UNDERLINE, fragment); if (decorations & OVERLINE) paintDecoration(paintInfo.context, OVERLINE, fragment); // Fill text if (hasFill) { m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); } // Stroke text if (hasStroke) { m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); } // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. if (decorations & LINE_THROUGH) paintDecoration(paintInfo.context, LINE_THROUGH, fragment); m_paintingResourceMode = ApplyToDefaultMode; } ASSERT(!m_paintingResource); }
void EllipsisBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit lineTop, LayoutUnit lineBottom) { GraphicsContext* context = paintInfo.context; RenderStyle* style = m_renderer->style(isFirstLineStyle()); const Font& font = style->font(); FloatPoint boxOrigin = locationIncludingFlipping(); LayoutPoint adjustedPaintOffset = paintOffset; if (!isHorizontal()) adjustedPaintOffset.move(0, -virtualLogicalHeight()); boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y()); FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), virtualLogicalHeight())); GraphicsContextStateSaver stateSaver(*context); if (!isHorizontal()) context->concatCTM(InlineTextBox::rotation(boxRect, InlineTextBox::Clockwise)); FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent()); Color styleTextColor = m_renderer->resolveColor(style, CSSPropertyWebkitTextFillColor); if (styleTextColor != context->fillColor()) context->setFillColor(styleTextColor); if (selectionState() != RenderObject::SelectionNone) { paintSelection(context, boxOrigin, style, font); // Select the correct color for painting the text. Color foreground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionForegroundColor(); if (foreground.isValid() && foreground != styleTextColor) context->setFillColor(foreground); } // Text shadows are disabled when printing. http://crbug.com/258321 const ShadowList* shadowList = context->printing() ? 0 : style->textShadow(); bool hasShadow = shadowList; if (hasShadow) { DrawLooper drawLooper; for (size_t i = shadowList->shadows().size(); i--; ) { const ShadowData& shadow = shadowList->shadows()[i]; int shadowX = isHorizontal() ? shadow.x() : shadow.y(); int shadowY = isHorizontal() ? shadow.y() : -shadow.x(); FloatSize offset(shadowX, shadowY); drawLooper.addShadow(offset, shadow.blur(), m_renderer->resolveColor(shadow.color()), DrawLooper::ShadowRespectsTransforms, DrawLooper::ShadowIgnoresAlpha); } drawLooper.addUnmodifiedContent(); context->setDrawLooper(drawLooper); } // FIXME: Why is this always LTR? Fix by passing correct text run flags below. TextRun textRun = RenderBlockFlow::constructTextRun(renderer(), font, m_str, style, TextRun::AllowTrailingExpansion); TextRunPaintInfo textRunPaintInfo(textRun); textRunPaintInfo.bounds = boxRect; context->drawText(font, textRunPaintInfo, textOrigin); // Restore the regular fill color. if (styleTextColor != context->fillColor()) context->setFillColor(styleTextColor); if (hasShadow) context->clearDrawLooper(); paintMarkupBox(paintInfo, paintOffset, lineTop, lineBottom, style); }
void TableCellPainter::paintCollapsedBorders(const PaintInfo& paintInfo, const LayoutPoint& paintOffset, const CollapsedBorderValue& currentBorderValue) { if (m_layoutTableCell.style()->visibility() != VISIBLE) return; const ComputedStyle& styleForCellFlow = m_layoutTableCell.styleForCellFlow(); const CollapsedBorderValue* leftBorderValue = cachedCollapsedLeftBorder(styleForCellFlow); const CollapsedBorderValue* rightBorderValue = cachedCollapsedRightBorder(styleForCellFlow); const CollapsedBorderValue* topBorderValue = cachedCollapsedTopBorder(styleForCellFlow); const CollapsedBorderValue* bottomBorderValue = cachedCollapsedBottomBorder(styleForCellFlow); int displayItemType = DisplayItem::TableCollapsedBorderBase; int topWidth = 0; int bottomWidth = 0; int leftWidth = 0; int rightWidth = 0; if (topBorderValue) { if (topBorderValue->shouldPaint(currentBorderValue)) displayItemType |= DisplayItem::TableCollapsedBorderTop; topWidth = topBorderValue->width(); } if (bottomBorderValue) { if (bottomBorderValue->shouldPaint(currentBorderValue)) displayItemType |= DisplayItem::TableCollapsedBorderBottom; bottomWidth = bottomBorderValue->width(); } if (leftBorderValue) { if (leftBorderValue->shouldPaint(currentBorderValue)) displayItemType |= DisplayItem::TableCollapsedBorderLeft; leftWidth = leftBorderValue->width(); } if (rightBorderValue) { if (rightBorderValue->shouldPaint(currentBorderValue)) displayItemType |= DisplayItem::TableCollapsedBorderRight; rightWidth = rightBorderValue->width(); } if (displayItemType == DisplayItem::TableCollapsedBorderBase) return; // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location. LayoutRect paintRect = paintBounds(paintOffset, AddOffsetFromParent); IntRect borderRect = pixelSnappedIntRect(paintRect.x() - leftWidth / 2, paintRect.y() - topWidth / 2, paintRect.width() + leftWidth / 2 + (rightWidth + 1) / 2, paintRect.height() + topWidth / 2 + (bottomWidth + 1) / 2); if (!paintInfo.cullRect().intersectsCullRect(borderRect)) return; GraphicsContext& graphicsContext = paintInfo.context; if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(graphicsContext, m_layoutTableCell, static_cast<DisplayItem::Type>(displayItemType))) return; LayoutObjectDrawingRecorder recorder(graphicsContext, m_layoutTableCell, static_cast<DisplayItem::Type>(displayItemType), borderRect); Color cellColor = m_layoutTableCell.resolveColor(CSSPropertyColor); // We never paint diagonals at the joins. We simply let the border with the highest // precedence paint on top of borders with lower precedence. if (displayItemType & DisplayItem::TableCollapsedBorderTop) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.x(), borderRect.y(), borderRect.maxX(), borderRect.y() + topWidth, BSTop, topBorderValue->color().resolve(cellColor), collapsedBorderStyle(topBorderValue->style()), 0, 0, true); } if (displayItemType & DisplayItem::TableCollapsedBorderBottom) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.x(), borderRect.maxY() - bottomWidth, borderRect.maxX(), borderRect.maxY(), BSBottom, bottomBorderValue->color().resolve(cellColor), collapsedBorderStyle(bottomBorderValue->style()), 0, 0, true); } if (displayItemType & DisplayItem::TableCollapsedBorderLeft) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.x(), borderRect.y(), borderRect.x() + leftWidth, borderRect.maxY(), BSLeft, leftBorderValue->color().resolve(cellColor), collapsedBorderStyle(leftBorderValue->style()), 0, 0, true); } if (displayItemType & DisplayItem::TableCollapsedBorderRight) { ObjectPainter::drawLineForBoxSide(graphicsContext, borderRect.maxX() - rightWidth, borderRect.y(), borderRect.maxX(), borderRect.maxY(), BSRight, rightBorderValue->color().resolve(cellColor), collapsedBorderStyle(rightBorderValue->style()), 0, 0, true); } }
void BlockPainter::paintObject(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled() && m_layoutBlock.childrenInline() && !paintInfo.context.paintController().skippingCache()) { if (m_layoutBlock.paintOffsetChanged(paintOffset)) { LineBoxListPainter(m_layoutBlock.lineBoxes()).invalidateLineBoxPaintOffsets(paintInfo); paintInfo.context.paintController().invalidatePaintOffset(m_layoutBlock); } // Set previousPaintOffset here in case that m_layoutBlock paints nothing and no // LayoutObjectDrawingRecorder updates its previousPaintOffset. // TODO(wangxianzhu): Integrate paint offset checking into new paint invalidation. m_layoutBlock.mutableForPainting().setPreviousPaintOffset(paintOffset); } const PaintPhase paintPhase = paintInfo.phase; if ((paintPhase == PaintPhaseSelfBlockBackground || paintPhase == PaintPhaseBlockBackground) && m_layoutBlock.style()->visibility() == VISIBLE && m_layoutBlock.hasBoxDecorationBackground()) m_layoutBlock.paintBoxDecorationBackground(paintInfo, paintOffset); if (paintPhase == PaintPhaseMask && m_layoutBlock.style()->visibility() == VISIBLE) { m_layoutBlock.paintMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseClippingMask && m_layoutBlock.style()->visibility() == VISIBLE) { BoxPainter(m_layoutBlock).paintClippingMask(paintInfo, paintOffset); return; } // FIXME: When Skia supports annotation rect covering (https://code.google.com/p/skia/issues/detail?id=3872), // this rect may be covered by foreground and descendant drawings. Then we may need a dedicated paint phase. if (paintPhase == PaintPhaseForeground && paintInfo.isPrinting()) ObjectPainter(m_layoutBlock).addPDFURLRectIfNeeded(paintInfo, paintOffset); { Optional<ScrollRecorder> scrollRecorder; Optional<PaintInfo> scrolledPaintInfo; if (m_layoutBlock.hasOverflowClip()) { IntSize scrollOffset = m_layoutBlock.scrolledContentOffset(); if (m_layoutBlock.layer()->scrollsOverflow() || !scrollOffset.isZero()) { scrollRecorder.emplace(paintInfo.context, m_layoutBlock, paintPhase, scrollOffset); scrolledPaintInfo.emplace(paintInfo); AffineTransform transform; transform.translate(-scrollOffset.width(), -scrollOffset.height()); scrolledPaintInfo->updateCullRect(transform); } } // We're done. We don't bother painting any children. if (paintPhase == PaintPhaseSelfBlockBackground || paintInfo.paintRootBackgroundOnly()) return; const PaintInfo& contentsPaintInfo = scrolledPaintInfo ? *scrolledPaintInfo : paintInfo; if (paintPhase != PaintPhaseSelfOutline) paintContents(contentsPaintInfo, paintOffset); if (paintPhase == PaintPhaseForeground && !paintInfo.isPrinting()) m_layoutBlock.paintSelection(contentsPaintInfo, paintOffset); // Fill in gaps in selection on lines and between blocks. if (paintPhase == PaintPhaseFloat || paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip) m_layoutBlock.paintFloats(contentsPaintInfo, paintOffset); } if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseSelfOutline) && m_layoutBlock.style()->hasOutline() && m_layoutBlock.style()->visibility() == VISIBLE) ObjectPainter(m_layoutBlock).paintOutline(paintInfo, paintOffset); // If the caret's node's layout object's containing block is this block, and the paint action is PaintPhaseForeground, // then paint the caret. if (paintPhase == PaintPhaseForeground && m_layoutBlock.hasCaret() && !LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(paintInfo.context, m_layoutBlock, DisplayItem::Caret, paintOffset)) { LayoutRect bounds = m_layoutBlock.visualOverflowRect(); bounds.moveBy(paintOffset); LayoutObjectDrawingRecorder recorder(paintInfo.context, m_layoutBlock, DisplayItem::Caret, bounds, paintOffset); paintCarets(paintInfo, paintOffset); } }
void RenderView::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint&) { if (!paintInfo.shouldPaintWithinRoot(*this)) return; // Check to see if we are enclosed by a layer that requires complex painting rules. If so, we cannot blit // when scrolling, and we need to use slow repaints. Examples of layers that require this are transparent layers, // layers with reflections, or transformed layers. // FIXME: This needs to be dynamic. We should be able to go back to blitting if we ever stop being inside // a transform, transparency layer, etc. Element* elt; for (elt = document().ownerElement(); elt && elt->renderer(); elt = elt->document().ownerElement()) { RenderLayer* layer = elt->renderer()->enclosingLayer(); if (layer->cannotBlitToWindow()) { frameView().setCannotBlitToWindow(); break; } #if USE(ACCELERATED_COMPOSITING) if (RenderLayer* compositingLayer = layer->enclosingCompositingLayerForRepaint()) { if (!compositingLayer->backing()->paintsIntoWindow()) { frameView().setCannotBlitToWindow(); break; } } #endif } if (document().ownerElement()) return; if (paintInfo.skipRootBackground()) return; bool rootFillsViewport = false; bool rootObscuresBackground = false; Element* documentElement = document().documentElement(); if (RenderElement* rootRenderer = documentElement ? documentElement->renderer() : 0) { // The document element's renderer is currently forced to be a block, but may not always be. RenderBox* rootBox = rootRenderer->isBox() ? toRenderBox(rootRenderer) : 0; rootFillsViewport = rootBox && !rootBox->x() && !rootBox->y() && rootBox->width() >= width() && rootBox->height() >= height(); rootObscuresBackground = rendererObscuresBackground(rootRenderer); } Page* page = document().page(); float pageScaleFactor = page ? page->pageScaleFactor() : 1; // If painting will entirely fill the view, no need to fill the background. if (rootFillsViewport && rootObscuresBackground && pageScaleFactor >= 1) return; // This code typically only executes if the root element's visibility has been set to hidden, // if there is a transform on the <html>, or if there is a page scale factor less than 1. // Only fill with the base background color (typically white) if we're the root document, // since iframes/frames with no background in the child document should show the parent's background. if (frameView().isTransparent()) // FIXME: This needs to be dynamic. We should be able to go back to blitting if we ever stop being transparent. frameView().setCannotBlitToWindow(); // The parent must show behind the child. else { Color baseColor = frameView().baseBackgroundColor(); if (baseColor.alpha()) { CompositeOperator previousOperator = paintInfo.context->compositeOperation(); paintInfo.context->setCompositeOperation(CompositeCopy); paintInfo.context->fillRect(paintInfo.rect, baseColor, style()->colorSpace()); paintInfo.context->setCompositeOperation(previousOperator); } else paintInfo.context->clearRect(paintInfo.rect); } }
void BlockPainter::paintOverflowControlsIfNeeded(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { PaintPhase phase = paintInfo.phase; if (m_layoutBlock.hasOverflowClip() && m_layoutBlock.style()->visibility() == VISIBLE && (phase == PaintPhaseSelfBlockBackground || phase == PaintPhaseBlockBackground) && paintInfo.shouldPaintWithinRoot(&m_layoutBlock) && !paintInfo.paintRootBackgroundOnly()) { Optional<ClipRecorder> clipRecorder; if (!m_layoutBlock.layer()->isSelfPaintingLayer()) { LayoutRect clipRect = m_layoutBlock.borderBoxRect(); clipRect.moveBy(paintOffset); clipRecorder.emplace(paintInfo.context, m_layoutBlock, DisplayItem::ClipScrollbarsToBoxBounds, clipRect); } ScrollableAreaPainter(*m_layoutBlock.layer()->scrollableArea()).paintOverflowControls(paintInfo.context, roundedIntPoint(paintOffset), paintInfo.cullRect(), false /* paintingOverlayControls */); } }
void ListMarkerPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.phase != PaintPhaseForeground) return; if (m_layoutListMarker.style()->visibility() != EVisibility::Visible) return; if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible( paintInfo.context, m_layoutListMarker, paintInfo.phase)) return; LayoutPoint boxOrigin(paintOffset + m_layoutListMarker.location()); LayoutRect overflowRect(m_layoutListMarker.visualOverflowRect()); overflowRect.moveBy(boxOrigin); IntRect pixelSnappedOverflowRect = pixelSnappedIntRect(overflowRect); if (!paintInfo.cullRect().intersectsCullRect(overflowRect)) return; LayoutObjectDrawingRecorder recorder(paintInfo.context, m_layoutListMarker, paintInfo.phase, pixelSnappedOverflowRect); LayoutRect box(boxOrigin, m_layoutListMarker.size()); IntRect marker = m_layoutListMarker.getRelativeMarkerRect(); marker.moveBy(roundedIntPoint(boxOrigin)); GraphicsContext& context = paintInfo.context; if (m_layoutListMarker.isImage()) { context.drawImage(m_layoutListMarker.image() ->image(m_layoutListMarker, marker.size(), m_layoutListMarker.styleRef().effectiveZoom()) .get(), marker); if (m_layoutListMarker.getSelectionState() != SelectionNone) { LayoutRect selRect = m_layoutListMarker.localSelectionRect(); selRect.moveBy(boxOrigin); context.fillRect( pixelSnappedIntRect(selRect), m_layoutListMarker.listItem()->selectionBackgroundColor()); } return; } LayoutListMarker::ListStyleCategory styleCategory = m_layoutListMarker.getListStyleCategory(); if (styleCategory == LayoutListMarker::ListStyleCategory::None) return; Color color(m_layoutListMarker.resolveColor(CSSPropertyColor)); if (BoxPainter::shouldForceWhiteBackgroundForPrintEconomy( m_layoutListMarker.styleRef(), m_layoutListMarker.listItem()->document())) color = TextPainter::textColorForWhiteBackground(color); // Apply the color to the list marker text. context.setFillColor(color); const EListStyleType listStyle = m_layoutListMarker.style()->listStyleType(); if (styleCategory == LayoutListMarker::ListStyleCategory::Symbol) { paintSymbol(context, color, marker, listStyle); return; } if (m_layoutListMarker.text().isEmpty()) return; const Font& font = m_layoutListMarker.style()->font(); TextRun textRun = constructTextRun(font, m_layoutListMarker.text(), m_layoutListMarker.styleRef()); GraphicsContextStateSaver stateSaver(context, false); if (!m_layoutListMarker.style()->isHorizontalWritingMode()) { marker.moveBy(roundedIntPoint(-boxOrigin)); marker = marker.transposedRect(); marker.moveBy( IntPoint(roundToInt(box.x()), roundToInt(box.y() - m_layoutListMarker.logicalHeight()))); stateSaver.save(); context.translate(marker.x(), marker.maxY()); context.rotate(static_cast<float>(deg2rad(90.))); context.translate(-marker.x(), -marker.maxY()); } TextRunPaintInfo textRunPaintInfo(textRun); textRunPaintInfo.bounds = marker; const SimpleFontData* fontData = m_layoutListMarker.style()->font().primaryFont(); IntPoint textOrigin = IntPoint( marker.x(), marker.y() + (fontData ? fontData->getFontMetrics().ascent() : 0)); // Text is not arbitrary. We can judge whether it's RTL from the first // character, and we only need to handle the direction RightToLeft for now. bool textNeedsReversing = WTF::Unicode::direction(m_layoutListMarker.text()[0]) == WTF::Unicode::RightToLeft; StringBuilder reversedText; if (textNeedsReversing) { unsigned length = m_layoutListMarker.text().length(); reversedText.reserveCapacity(length); for (int i = length - 1; i >= 0; --i) reversedText.append(m_layoutListMarker.text()[i]); DCHECK(reversedText.length() == length); textRun.setText(reversedText.toString()); } const UChar suffix = ListMarkerText::suffix(listStyle, m_layoutListMarker.listItem()->value()); UChar suffixStr[2] = {suffix, static_cast<UChar>(' ')}; TextRun suffixRun = constructTextRun(font, suffixStr, 2, m_layoutListMarker.styleRef(), m_layoutListMarker.style()->direction()); TextRunPaintInfo suffixRunInfo(suffixRun); suffixRunInfo.bounds = marker; if (m_layoutListMarker.style()->isLeftToRightDirection()) { context.drawText(font, textRunPaintInfo, textOrigin); context.drawText(font, suffixRunInfo, textOrigin + IntSize(font.width(textRun), 0)); } else { context.drawText(font, suffixRunInfo, textOrigin); context.drawText(font, textRunPaintInfo, textOrigin + IntSize(font.width(suffixRun), 0)); } }
void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { LayoutUnit cWidth = contentWidth(); LayoutUnit cHeight = contentHeight(); LayoutUnit leftBorder = borderLeft(); LayoutUnit topBorder = borderTop(); LayoutUnit leftPad = paddingLeft(); LayoutUnit topPad = paddingTop(); GraphicsContext& context = paintInfo.context(); float deviceScaleFactor = document().deviceScaleFactor(); Page* page = frame().page(); if (!imageResource().hasImage() || imageResource().errorOccurred()) { if (paintInfo.phase == PaintPhaseSelection) return; if (page && paintInfo.phase == PaintPhaseForeground) page->addRelevantUnpaintedObject(this, visualOverflowRect()); if (cWidth > 2 && cHeight > 2) { LayoutUnit borderWidth = LayoutUnit(1 / deviceScaleFactor); // Draw an outline rect where the image should be. context.setStrokeStyle(SolidStroke); context.setStrokeColor(Color::lightGray, style().colorSpace()); context.setFillColor(Color::transparent, style().colorSpace()); context.drawRect(snapRectToDevicePixels(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight), deviceScaleFactor), borderWidth); bool errorPictureDrawn = false; LayoutSize imageOffset; // When calculating the usable dimensions, exclude the pixels of // the ouline rect so the error image/alt text doesn't draw on it. LayoutUnit usableWidth = cWidth - 2 * borderWidth; LayoutUnit usableHeight = cHeight - 2 * borderWidth; RefPtr<Image> image = imageResource().image(); if (imageResource().errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) { // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution. std::pair<Image*, float> brokenImageAndImageScaleFactor = imageResource().cachedImage()->brokenImage(document().deviceScaleFactor()); image = brokenImageAndImageScaleFactor.first; FloatSize imageSize = image->size(); imageSize.scale(1 / brokenImageAndImageScaleFactor.second); // Center the error image, accounting for border and padding. LayoutUnit centerX = (usableWidth - imageSize.width()) / 2; if (centerX < 0) centerX = 0; LayoutUnit centerY = (usableHeight - imageSize.height()) / 2; if (centerY < 0) centerY = 0; imageOffset = LayoutSize(leftBorder + leftPad + centerX + borderWidth, topBorder + topPad + centerY + borderWidth); ImageOrientationDescription orientationDescription(shouldRespectImageOrientation()); #if ENABLE(CSS_IMAGE_ORIENTATION) orientationDescription.setImageOrientationEnum(style().imageOrientation()); #endif context.drawImage(*image, style().colorSpace(), snapRectToDevicePixels(LayoutRect(paintOffset + imageOffset, imageSize), deviceScaleFactor), orientationDescription); errorPictureDrawn = true; } if (!m_altText.isEmpty()) { String text = document().displayStringModifiedByEncoding(m_altText); context.setFillColor(style().visitedDependentColor(CSSPropertyColor), style().colorSpace()); const FontCascade& font = style().fontCascade(); const FontMetrics& fontMetrics = font.fontMetrics(); LayoutUnit ascent = fontMetrics.ascent(); LayoutPoint altTextOffset = paintOffset; altTextOffset.move(leftBorder + leftPad + (paddingWidth / 2) - borderWidth, topBorder + topPad + ascent + (paddingHeight / 2) - borderWidth); // Only draw the alt text if it'll fit within the content box, // and only if it fits above the error image. TextRun textRun = RenderBlock::constructTextRun(this, font, text, style()); LayoutUnit textWidth = font.width(textRun); if (errorPictureDrawn) { if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height()) context.drawText(font, textRun, altTextOffset); } else if (usableWidth >= textWidth && usableHeight >= fontMetrics.height()) context.drawText(font, textRun, altTextOffset); } } } else if (imageResource().hasImage() && cWidth > 0 && cHeight > 0) { RefPtr<Image> img = imageResource().image(cWidth, cHeight); if (!img || img->isNull()) { if (page && paintInfo.phase == PaintPhaseForeground) page->addRelevantUnpaintedObject(this, visualOverflowRect()); return; } LayoutRect contentBoxRect = this->contentBoxRect(); contentBoxRect.moveBy(paintOffset); LayoutRect replacedContentRect = this->replacedContentRect(intrinsicSize()); replacedContentRect.moveBy(paintOffset); bool clip = !contentBoxRect.contains(replacedContentRect); GraphicsContextStateSaver stateSaver(context, clip); if (clip) context.clip(contentBoxRect); paintIntoRect(context, snapRectToDevicePixels(replacedContentRect, deviceScaleFactor)); if (cachedImage() && page && paintInfo.phase == PaintPhaseForeground) { // For now, count images as unpainted if they are still progressively loading. We may want // to refine this in the future to account for the portion of the image that has painted. LayoutRect visibleRect = intersection(replacedContentRect, contentBoxRect); if (cachedImage()->isLoading()) page->addRelevantUnpaintedObject(this, visibleRect); else page->addRelevantRepaintedObject(this, visibleRect); } } }
void BlockPainter::paintObject(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { const PaintPhase paintPhase = paintInfo.phase; if (shouldPaintSelfBlockBackground(paintPhase)) { if (m_layoutBlock.style()->visibility() == EVisibility::Visible && m_layoutBlock.hasBoxDecorationBackground()) m_layoutBlock.paintBoxDecorationBackground(paintInfo, paintOffset); // We're done. We don't bother painting any children. if (paintPhase == PaintPhaseSelfBlockBackgroundOnly) return; } if (paintInfo.paintRootBackgroundOnly()) return; if (paintPhase == PaintPhaseMask && m_layoutBlock.style()->visibility() == EVisibility::Visible) { m_layoutBlock.paintMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseClippingMask && m_layoutBlock.style()->visibility() == EVisibility::Visible) { BoxPainter(m_layoutBlock).paintClippingMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseForeground && paintInfo.isPrinting()) ObjectPainter(m_layoutBlock).addPDFURLRectIfNeeded(paintInfo, paintOffset); if (paintPhase != PaintPhaseSelfOutlineOnly) { Optional<ScopedPaintChunkProperties> m_scopedScrollProperty; Optional<ScrollRecorder> scrollRecorder; Optional<PaintInfo> scrolledPaintInfo; if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { const auto* objectProperties = m_layoutBlock.paintProperties(); if (auto* scroll = objectProperties ? objectProperties->scroll() : nullptr) { PaintChunkProperties properties(paintInfo.context.getPaintController() .currentPaintChunkProperties()); auto* scrollTranslation = objectProperties->scrollTranslation(); DCHECK(scrollTranslation); properties.transform = scrollTranslation; properties.scroll = scroll; m_scopedScrollProperty.emplace( paintInfo.context.getPaintController(), m_layoutBlock, DisplayItem::paintPhaseToDrawingType(paintPhase), properties); scrolledPaintInfo.emplace(paintInfo); scrolledPaintInfo->updateCullRect( scrollTranslation->matrix().toAffineTransform()); } } else if (m_layoutBlock.hasOverflowClip()) { IntSize scrollOffset = m_layoutBlock.scrolledContentOffset(); if (m_layoutBlock.layer()->scrollsOverflow() || !scrollOffset.isZero()) { scrollRecorder.emplace(paintInfo.context, m_layoutBlock, paintPhase, scrollOffset); scrolledPaintInfo.emplace(paintInfo); AffineTransform transform; transform.translate(-scrollOffset.width(), -scrollOffset.height()); scrolledPaintInfo->updateCullRect(transform); } } const PaintInfo& contentsPaintInfo = scrolledPaintInfo ? *scrolledPaintInfo : paintInfo; if (m_layoutBlock.isLayoutBlockFlow()) { BlockFlowPainter blockFlowPainter(toLayoutBlockFlow(m_layoutBlock)); blockFlowPainter.paintContents(contentsPaintInfo, paintOffset); if (paintPhase == PaintPhaseFloat || paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip) blockFlowPainter.paintFloats(contentsPaintInfo, paintOffset); } else { paintContents(contentsPaintInfo, paintOffset); } } if (shouldPaintSelfOutline(paintPhase)) ObjectPainter(m_layoutBlock).paintOutline(paintInfo, paintOffset); // If the caret's node's layout object's containing block is this block, and // the paint action is PaintPhaseForeground, then paint the caret. if (paintPhase == PaintPhaseForeground && m_layoutBlock.hasCaret()) paintCarets(paintInfo, paintOffset); }
void BlockPainter::paintObject(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { const PaintPhase paintPhase = paintInfo.phase; if ((paintPhase == PaintPhaseBlockBackground || paintPhase == PaintPhaseChildBlockBackground) && m_layoutBlock.style()->visibility() == VISIBLE && m_layoutBlock.hasBoxDecorationBackground()) m_layoutBlock.paintBoxDecorationBackground(paintInfo, paintOffset); if (paintPhase == PaintPhaseMask && m_layoutBlock.style()->visibility() == VISIBLE) { m_layoutBlock.paintMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseClippingMask && m_layoutBlock.style()->visibility() == VISIBLE) { BoxPainter(m_layoutBlock).paintClippingMask(paintInfo, paintOffset); return; } // FIXME: When Skia supports annotation rect covering (https://code.google.com/p/skia/issues/detail?id=3872), // this rect may be covered by foreground and descendant drawings. Then we may need a dedicated paint phase. if (paintPhase == PaintPhaseForeground && paintInfo.isPrinting()) ObjectPainter(m_layoutBlock).addPDFURLRectIfNeeded(paintInfo, paintOffset); { Optional<ScrollRecorder> scrollRecorder; Optional<PaintInfo> scrolledPaintInfo; if (m_layoutBlock.hasOverflowClip()) { IntSize scrollOffset = m_layoutBlock.scrolledContentOffset(); if (m_layoutBlock.layer()->scrollsOverflow() || !scrollOffset.isZero()) { scrollRecorder.emplace(*paintInfo.context, m_layoutBlock, paintPhase, scrollOffset); scrolledPaintInfo.emplace(paintInfo); scrolledPaintInfo->rect.move(scrollOffset); } } // We're done. We don't bother painting any children. if (paintPhase == PaintPhaseBlockBackground || paintInfo.paintRootBackgroundOnly()) return; const PaintInfo& contentsPaintInfo = scrolledPaintInfo ? *scrolledPaintInfo : paintInfo; if (paintPhase != PaintPhaseSelfOutline) paintContents(contentsPaintInfo, paintOffset); if (paintPhase == PaintPhaseForeground && !paintInfo.isPrinting()) m_layoutBlock.paintSelection(contentsPaintInfo, paintOffset); // Fill in gaps in selection on lines and between blocks. if (paintPhase == PaintPhaseFloat || paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip) m_layoutBlock.paintFloats(contentsPaintInfo, paintOffset, paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip); } if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseSelfOutline) && m_layoutBlock.style()->hasOutline() && m_layoutBlock.style()->visibility() == VISIBLE) { // Don't paint focus ring for anonymous block continuation because the // inline element having outline-style:auto paints the whole focus ring. if (!m_layoutBlock.style()->outlineStyleIsAuto() || !m_layoutBlock.isAnonymousBlockContinuation()) ObjectPainter(m_layoutBlock).paintOutline(paintInfo, LayoutRect(paintOffset, m_layoutBlock.size()), visualOverflowRectWithPaintOffset(m_layoutBlock, paintOffset)); } if (paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseChildOutlines) paintContinuationOutlines(paintInfo, paintOffset); // If the caret's node's layout object's containing block is this block, and the paint action is PaintPhaseForeground, // then paint the caret. if (paintPhase == PaintPhaseForeground && hasCaret() && !LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(*paintInfo.context, m_layoutBlock, DisplayItem::Caret)) { LayoutObjectDrawingRecorder recorder(*paintInfo.context, m_layoutBlock, DisplayItem::Caret, visualOverflowRectWithPaintOffset(m_layoutBlock, paintOffset)); paintCarets(paintInfo, paintOffset); } }
void SVGInlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit) { ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(truncation() == cNoTruncation); if (renderer().style().visibility() != VISIBLE) return; // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox. // If we ever need that for SVG, it's very easy to refactor and reuse the code. auto& parentRenderer = parent()->renderer(); bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; bool hasSelection = !parentRenderer.document().printing() && selectionState() != RenderObject::SelectionNone; if (!hasSelection && paintSelectedTextOnly) return; if (!textShouldBePainted(renderer())) return; RenderStyle& style = parentRenderer.style(); const SVGRenderStyle& svgStyle = style.svgStyle(); bool hasFill = svgStyle.hasFill(); bool hasVisibleStroke = svgStyle.hasVisibleStroke(); RenderStyle* selectionStyle = &style; if (hasSelection) { selectionStyle = parentRenderer.getCachedPseudoStyle(SELECTION); if (selectionStyle) { const SVGRenderStyle& svgSelectionStyle = selectionStyle->svgStyle(); if (!hasFill) hasFill = svgSelectionStyle.hasFill(); if (!hasVisibleStroke) hasVisibleStroke = svgSelectionStyle.hasVisibleStroke(); } else selectionStyle = &style; } if (renderer().view().frameView().paintBehavior() & PaintBehaviorRenderingSVGMask) { hasFill = true; hasVisibleStroke = false; } AffineTransform fragmentTransform; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_textFragments.at(i); ASSERT(!m_paintingResource); GraphicsContextStateSaver stateSaver(paintInfo.context()); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) paintInfo.context().concatCTM(fragmentTransform); // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. int decorations = style.textDecorationsInEffect(); if (decorations & TextDecorationUnderline) paintDecoration(paintInfo.context(), TextDecorationUnderline, fragment); if (decorations & TextDecorationOverline) paintDecoration(paintInfo.context(), TextDecorationOverline, fragment); auto paintOrder = style.svgStyle().paintTypesForPaintOrder(); for (unsigned i = 0; i < paintOrder.size(); ++i) { switch (paintOrder.at(i)) { case PaintTypeFill: if (!hasFill) continue; m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; paintText(paintInfo.context(), &style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); break; case PaintTypeStroke: if (!hasVisibleStroke) continue; m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; paintText(paintInfo.context(), &style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); break; case PaintTypeMarkers: continue; } } // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. if (decorations & TextDecorationLineThrough) paintDecoration(paintInfo.context(), TextDecorationLineThrough, fragment); m_paintingResourceMode = ApplyToDefaultMode; } // Finally, paint the outline if any. if (renderer().style().hasOutline() && is<RenderInline>(parentRenderer)) downcast<RenderInline>(parentRenderer).paintOutline(paintInfo, paintOffset); ASSERT(!m_paintingResource); }
void BlockPainter::paintOverflowControlsIfNeeded(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { PaintPhase phase = paintInfo.phase; if (m_layoutBlock.hasOverflowClip() && m_layoutBlock.style()->visibility() == VISIBLE && (phase == PaintPhaseBlockBackground || phase == PaintPhaseChildBlockBackground) && paintInfo.shouldPaintWithinRoot(&m_layoutBlock) && !paintInfo.paintRootBackgroundOnly()) { ScrollableAreaPainter(*m_layoutBlock.layer()->scrollableArea()).paintOverflowControls(paintInfo.context, roundedIntPoint(paintOffset), paintInfo.rect, false /* paintingOverlayControls */); } }
void SVGInlineTextBoxPainter::paintTextFragments(const PaintInfo& paintInfo, LayoutObject& parentLayoutObject) { const ComputedStyle& style = parentLayoutObject.styleRef(); const SVGComputedStyle& svgStyle = style.svgStyle(); bool hasFill = svgStyle.hasFill(); bool hasVisibleStroke = svgStyle.hasVisibleStroke(); const ComputedStyle* selectionStyle = &style; bool shouldPaintSelection = this->shouldPaintSelection(); if (shouldPaintSelection) { selectionStyle = parentLayoutObject.getCachedPseudoStyle(SELECTION); if (selectionStyle) { const SVGComputedStyle& svgSelectionStyle = selectionStyle->svgStyle(); if (!hasFill) hasFill = svgSelectionStyle.hasFill(); if (!hasVisibleStroke) hasVisibleStroke = svgSelectionStyle.hasVisibleStroke(); } else { selectionStyle = &style; } } if (paintInfo.isRenderingClipPathAsMaskImage()) { hasFill = true; hasVisibleStroke = false; } AffineTransform fragmentTransform; unsigned textFragmentsSize = m_svgInlineTextBox.textFragments().size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_svgInlineTextBox.textFragments().at(i); GraphicsContextStateSaver stateSaver(*paintInfo.context, false); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) { stateSaver.save(); paintInfo.context->concatCTM(fragmentTransform); } // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. unsigned decorations = style.textDecorationsInEffect(); if (decorations & TextDecorationUnderline) paintDecoration(paintInfo, TextDecorationUnderline, fragment); if (decorations & TextDecorationOverline) paintDecoration(paintInfo, TextDecorationOverline, fragment); for (int i = 0; i < 3; i++) { switch (svgStyle.paintOrderType(i)) { case PT_FILL: if (hasFill) paintText(paintInfo, style, *selectionStyle, fragment, ApplyToFillMode, shouldPaintSelection); break; case PT_STROKE: if (hasVisibleStroke) paintText(paintInfo, style, *selectionStyle, fragment, ApplyToStrokeMode, shouldPaintSelection); break; case PT_MARKERS: // Markers don't apply to text break; default: ASSERT_NOT_REACHED(); break; } } // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. if (decorations & TextDecorationLineThrough) paintDecoration(paintInfo, TextDecorationLineThrough, fragment); } }
void SVGInlineTextBox::paintSelectionBackground(PaintInfo& paintInfo) { ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(truncation() == cNoTruncation); if (renderer()->style()->visibility() != VISIBLE) return; RenderObject* parentRenderer = parent()->renderer(); ASSERT(parentRenderer); ASSERT(!parentRenderer->document()->printing()); // Determine whether or not we're selected. bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; bool hasSelection = selectionState() != RenderObject::SelectionNone; if (!hasSelection || paintSelectedTextOnly) return; Color backgroundColor = renderer()->selectionBackgroundColor(); if (!backgroundColor.isValid() || !backgroundColor.alpha()) return; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); if (!textShouldBePainted(textRenderer)) return; RenderStyle* style = parentRenderer->style(); ASSERT(style); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); bool hasFill = svgStyle->hasFill(); bool hasStroke = svgStyle->hasStroke(); RenderStyle* selectionStyle = style; if (hasSelection) { selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); if (selectionStyle) { const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); ASSERT(svgSelectionStyle); if (!hasFill) hasFill = svgSelectionStyle->hasFill(); if (!hasStroke) hasStroke = svgSelectionStyle->hasStroke(); } else selectionStyle = style; } int startPosition, endPosition; selectionStartEnd(startPosition, endPosition); int fragmentStartPosition = 0; int fragmentEndPosition = 0; AffineTransform fragmentTransform; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_textFragments.at(i); ASSERT(!m_paintingResource); fragmentStartPosition = startPosition; fragmentEndPosition = endPosition; if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; GraphicsContextStateSaver stateSaver(*paintInfo.context); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) paintInfo.context->concatCTM(fragmentTransform); paintInfo.context->setFillColor(backgroundColor, style->colorSpace()); paintInfo.context->fillRect(selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style), backgroundColor, style->colorSpace()); m_paintingResourceMode = ApplyToDefaultMode; } ASSERT(!m_paintingResource); }
static SkPath::FillType fillRuleFromStyle(const PaintInfo& paintInfo, const SVGComputedStyle& svgStyle) { return WebCoreWindRuleToSkFillType(paintInfo.isRenderingClipPathAsMaskImage() ? svgStyle.clipRule() : svgStyle.fillRule()); }
void PartPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { LayoutPoint adjustedPaintOffset = paintOffset + m_layoutPart.location(); if (!ReplacedPainter(m_layoutPart) .shouldPaint(paintInfo, adjustedPaintOffset)) return; LayoutRect borderRect(adjustedPaintOffset, m_layoutPart.size()); if (m_layoutPart.hasBoxDecorationBackground() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) BoxPainter(m_layoutPart) .paintBoxDecorationBackground(paintInfo, adjustedPaintOffset); if (paintInfo.phase == PaintPhaseMask) { BoxPainter(m_layoutPart).paintMask(paintInfo, adjustedPaintOffset); return; } if (shouldPaintSelfOutline(paintInfo.phase)) ObjectPainter(m_layoutPart).paintOutline(paintInfo, adjustedPaintOffset); if (paintInfo.phase != PaintPhaseForeground) return; if (m_layoutPart.widget()) { // TODO(schenney) crbug.com/93805 Speculative release assert to verify that // the crashes we see in widget painting are due to a destroyed LayoutPart // object. CHECK(m_layoutPart.node()); Optional<RoundedInnerRectClipper> clipper; if (m_layoutPart.style()->hasBorderRadius()) { if (borderRect.isEmpty()) return; FloatRoundedRect roundedInnerRect = m_layoutPart.style()->getRoundedInnerBorderFor( borderRect, LayoutRectOutsets( -(m_layoutPart.paddingTop() + m_layoutPart.borderTop()), -(m_layoutPart.paddingRight() + m_layoutPart.borderRight()), -(m_layoutPart.paddingBottom() + m_layoutPart.borderBottom()), -(m_layoutPart.paddingLeft() + m_layoutPart.borderLeft())), true, true); clipper.emplace(m_layoutPart, paintInfo, borderRect, roundedInnerRect, ApplyToDisplayList); } m_layoutPart.paintContents(paintInfo, paintOffset); } // Paint a partially transparent wash over selected widgets. if (isSelected() && !paintInfo.isPrinting() && !LayoutObjectDrawingRecorder::useCachedDrawingIfPossible( paintInfo.context, m_layoutPart, paintInfo.phase)) { LayoutRect rect = m_layoutPart.localSelectionRect(); rect.moveBy(adjustedPaintOffset); IntRect selectionRect = pixelSnappedIntRect(rect); LayoutObjectDrawingRecorder drawingRecorder(paintInfo.context, m_layoutPart, paintInfo.phase, selectionRect); paintInfo.context.fillRect(selectionRect, m_layoutPart.selectionBackgroundColor()); } if (m_layoutPart.canResize()) ScrollableAreaPainter(*m_layoutPart.layer()->getScrollableArea()) .paintResizer(paintInfo.context, roundedIntPoint(adjustedPaintOffset), paintInfo.cullRect()); }