TEST_F(PaintPropertyTreeBuilderTest, PositionAndScroll) { loadTestData("position-and-scroll.html"); Element* scroller = document().getElementById("scroller"); scroller->scrollTo(0, 100); FrameView* frameView = document().view(); frameView->updateAllLifecyclePhases(); ObjectPaintProperties* scrollerProperties = scroller->layoutObject()->objectPaintProperties(); EXPECT_EQ(TransformationMatrix().translate(0, -100), scrollerProperties->scrollTranslation()->matrix()); EXPECT_EQ(frameView->scrollTranslation(), scrollerProperties->scrollTranslation()->parent()); EXPECT_EQ(frameView->scrollTranslation(), scrollerProperties->overflowClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(120, 340, 400, 300), scrollerProperties->overflowClip()->clipRect()); EXPECT_EQ(frameView->contentClip(), scrollerProperties->overflowClip()->parent()); // The relative-positioned element should have accumulated box offset (exclude scrolling), // and should be affected by ancestor scroll transforms. Element* relPos = document().getElementById("rel-pos"); ObjectPaintProperties* relPosProperties = relPos->layoutObject()->objectPaintProperties(); EXPECT_EQ(TransformationMatrix().translate(680, 1120), relPosProperties->paintOffsetTranslation()->matrix()); EXPECT_EQ(scrollerProperties->scrollTranslation(), relPosProperties->paintOffsetTranslation()->parent()); EXPECT_EQ(relPosProperties->transform(), relPosProperties->overflowClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(0, 0, 400, 0), relPosProperties->overflowClip()->clipRect()); EXPECT_EQ(scrollerProperties->overflowClip(), relPosProperties->overflowClip()->parent()); // The absolute-positioned element should not be affected by non-positioned scroller at all. Element* absPos = document().getElementById("abs-pos"); ObjectPaintProperties* absPosProperties = absPos->layoutObject()->objectPaintProperties(); EXPECT_EQ(TransformationMatrix().translate(123, 456), absPosProperties->paintOffsetTranslation()->matrix()); EXPECT_EQ(frameView->scrollTranslation(), absPosProperties->paintOffsetTranslation()->parent()); EXPECT_EQ(absPosProperties->transform(), absPosProperties->overflowClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(), absPosProperties->overflowClip()->clipRect()); EXPECT_EQ(frameView->contentClip(), absPosProperties->overflowClip()->parent()); }
TEST_F(PaintPropertyTreeBuilderTest, FixedPosition) { loadTestData("fixed-position.html"); FrameView* frameView = document().view(); // target1 is a fixed-position element inside an absolute-position scrolling element. // It should be attached under the viewport to skip scrolling and offset of the parent. Element* target1 = document().getElementById("target1"); ObjectPaintProperties* target1Properties = target1->layoutObject()->objectPaintProperties(); EXPECT_EQ(TransformationMatrix().translate(200, 150), target1Properties->paintOffsetTranslation()->matrix()); EXPECT_EQ(frameView->preTranslation(), target1Properties->paintOffsetTranslation()->parent()); EXPECT_EQ(target1Properties->paintOffsetTranslation(), target1Properties->overflowClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(0, 0, 100, 100), target1Properties->overflowClip()->clipRect()); // Likewise, it inherits clip from the viewport, skipping overflow clip of the scroller. EXPECT_EQ(frameView->contentClip(), target1Properties->overflowClip()->parent()); // target2 is a fixed-position element inside a transformed scrolling element. // It should be attached under the scrolled box of the transformed element. Element* target2 = document().getElementById("target2"); ObjectPaintProperties* target2Properties = target2->layoutObject()->objectPaintProperties(); Element* scroller = document().getElementById("scroller"); ObjectPaintProperties* scrollerProperties = scroller->layoutObject()->objectPaintProperties(); EXPECT_EQ(TransformationMatrix().translate(200, 150), target2Properties->paintOffsetTranslation()->matrix()); EXPECT_EQ(scrollerProperties->scrollTranslation(), target2Properties->paintOffsetTranslation()->parent()); EXPECT_EQ(target2Properties->paintOffsetTranslation(), target2Properties->overflowClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(0, 0, 100, 100), target2Properties->overflowClip()->clipRect()); EXPECT_EQ(scrollerProperties->overflowClip(), target2Properties->overflowClip()->parent()); }
static void paintSliderRangeHighlight(const IntRect& rect, const ComputedStyle& style, GraphicsContext* context, int startPosition, int endPosition, Color startColor, Color endColor) { // Calculate border radius; need to avoid being smaller than half the slider height // because of https://bugs.webkit.org/show_bug.cgi?id=30143. float borderRadius = rect.height() / 2.0f; FloatSize radii(borderRadius, borderRadius); // Calculate highlight rectangle and edge dimensions. int startOffset = startPosition; int endOffset = rect.width() - endPosition; int rangeWidth = endPosition - startPosition; if (rangeWidth <= 0) return; // Make sure the range width is bigger than border radius at the edges to retain rounded corners. if (startOffset < borderRadius && rangeWidth < borderRadius) rangeWidth = borderRadius; if (endOffset < borderRadius && rangeWidth < borderRadius) rangeWidth = borderRadius; // Set rectangle to highlight range. IntRect highlightRect = rect; highlightRect.move(startOffset, 0); highlightRect.setWidth(rangeWidth); // Don't bother drawing an empty area. if (highlightRect.isEmpty()) return; // Calculate white-grey gradient. FloatPoint sliderTopLeft = highlightRect.location(); FloatPoint sliderBottomLeft = sliderTopLeft; sliderBottomLeft.move(0, highlightRect.height()); RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderBottomLeft); gradient->addColorStop(0.0, startColor); gradient->addColorStop(1.0, endColor); // Fill highlight rectangle with gradient, potentially rounded if on left or right edge. context->save(); context->setFillGradient(gradient); if (startOffset < borderRadius && endOffset < borderRadius) context->fillRoundedRect(FloatRoundedRect(highlightRect, radii, radii, radii, radii), startColor); else if (startOffset < borderRadius) context->fillRoundedRect(FloatRoundedRect(highlightRect, radii, FloatSize(0, 0), radii, FloatSize(0, 0)), startColor); else if (endOffset < borderRadius) context->fillRoundedRect(FloatRoundedRect(highlightRect, FloatSize(0, 0), radii, FloatSize(0, 0), radii), startColor); else context->fillRect(highlightRect); context->restore(); }
void Path::addRoundedRect(const FloatRect& rect, const FloatSize& roundingRadii, RoundedRectStrategy strategy) { if (rect.isEmpty()) return; FloatSize radius(roundingRadii); FloatSize halfSize(rect.width() / 2, rect.height() / 2); // Apply the SVG corner radius constraints, per the rect section of the SVG shapes spec: if // one of rx,ry is negative, then the other corner radius value is used. If both values are // negative then rx = ry = 0. If rx is greater than half of the width of the rectangle // then set rx to half of the width; ry is handled similarly. if (radius.width() < 0) radius.setWidth((radius.height() < 0) ? 0 : radius.height()); if (radius.height() < 0) radius.setHeight(radius.width()); if (radius.width() > halfSize.width()) radius.setWidth(halfSize.width()); if (radius.height() > halfSize.height()) radius.setHeight(halfSize.height()); addRoundedRect(FloatRoundedRect(rect, radius, radius, radius, radius), strategy); }
TEST(FloatRoundedRectTest, zeroRadii) { FloatRoundedRect r = FloatRoundedRect(1, 2, 3, 4); EXPECT_EQ(FloatRect(1, 2, 3, 4), r.rect()); EXPECT_EQ(FloatSize(), r.radii().topLeft()); EXPECT_EQ(FloatSize(), r.radii().topRight()); EXPECT_EQ(FloatSize(), r.radii().bottomLeft()); EXPECT_EQ(FloatSize(), r.radii().bottomRight()); EXPECT_TRUE(r.radii().isZero()); EXPECT_FALSE(r.isRounded()); EXPECT_FALSE(r.isEmpty()); EXPECT_EQ(FloatRect(1, 2, 0, 0), r.topLeftCorner()); EXPECT_EQ(FloatRect(4, 2, 0, 0), r.topRightCorner()); EXPECT_EQ(FloatRect(4, 6, 0, 0), r.bottomRightCorner()); EXPECT_EQ(FloatRect(1, 6, 0, 0), r.bottomLeftCorner()); TEST_INTERCEPTS(r, 2, r.rect().x(), r.rect().maxX()); TEST_INTERCEPTS(r, 4, r.rect().x(), r.rect().maxX()); TEST_INTERCEPTS(r, 6, r.rect().x(), r.rect().maxX()); float minXIntercept; float maxXIntercept; EXPECT_FALSE(r.xInterceptsAtY(1, minXIntercept, maxXIntercept)); EXPECT_FALSE(r.xInterceptsAtY(7, minXIntercept, maxXIntercept)); // The FloatRoundedRect::expandRadii() function doesn't change radii FloatSizes that // are <= zero. Same as RoundedRect::expandRadii(). r.expandRadii(20); r.shrinkRadii(10); EXPECT_TRUE(r.radii().isZero()); }
/* * FloatRoundedRect geometry for this test. Corner radii are in parens, x and y intercepts * for the elliptical corners are noted. The rectangle itself is at 0,0 with width and height 100. * * (10, 15) x=10 x=90 (10, 20) * (--+---------+--) * y=15 +--| |-+ y=20 * | | * | | * y=85 + -| |- + y=70 * (--+---------+--) * (25, 15) x=25 x=80 (20, 30) */ TEST(FloatRoundedRectTest, ellipticalCorners) { FloatSize cornerSize(10, 20); FloatRoundedRect::Radii cornerRadii; cornerRadii.setTopLeft(FloatSize(10, 15)); cornerRadii.setTopRight(FloatSize(10, 20)); cornerRadii.setBottomLeft(FloatSize(25, 15)); cornerRadii.setBottomRight(FloatSize(20, 30)); FloatRoundedRect r(FloatRect(0, 0, 100, 100), cornerRadii); EXPECT_EQ(r.radii(), FloatRoundedRect::Radii(FloatSize(10, 15), FloatSize(10, 20), FloatSize(25, 15), FloatSize(20, 30))); EXPECT_EQ(r, FloatRoundedRect(FloatRect(0, 0, 100, 100), cornerRadii)); EXPECT_EQ(FloatRect(0, 0, 10, 15), r.topLeftCorner()); EXPECT_EQ(FloatRect(90, 0, 10, 20), r.topRightCorner()); EXPECT_EQ(FloatRect(0, 85, 25, 15), r.bottomLeftCorner()); EXPECT_EQ(FloatRect(80, 70, 20, 30), r.bottomRightCorner()); TEST_INTERCEPTS(r, 5, 2.5464401, 96.61438); TEST_INTERCEPTS(r, 15, 0, 99.682457); TEST_INTERCEPTS(r, 20, 0, 100); TEST_INTERCEPTS(r, 50, 0, 100); TEST_INTERCEPTS(r, 70, 0, 100); TEST_INTERCEPTS(r, 85, 0, 97.320511); TEST_INTERCEPTS(r, 95, 6.3661003, 91.05542); float minXIntercept; float maxXIntercept; EXPECT_FALSE(r.xInterceptsAtY(-1, minXIntercept, maxXIntercept)); EXPECT_FALSE(r.xInterceptsAtY(101, minXIntercept, maxXIntercept)); }
bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r) { HTMLMediaElement* mediaElement = parentMediaElement(o); if (!mediaElement) return false; GraphicsContext* context = paintInfo.context; context->save(); context->setStrokeStyle(NoStroke); float mediaDuration = mediaElement->duration(); float totalTrackWidth = r.width(); RenderStyle* style = &o.style(); RefPtr<TimeRanges> timeRanges = mediaElement->buffered(); for (unsigned index = 0; index < timeRanges->length(); ++index) { float start = timeRanges->start(index, IGNORE_EXCEPTION); float end = timeRanges->end(index, IGNORE_EXCEPTION); float startRatio = start / mediaDuration; float lengthRatio = (end - start) / mediaDuration; if (!lengthRatio) continue; IntRect rangeRect(r); rangeRect.setWidth(lengthRatio * totalTrackWidth); if (index) rangeRect.move(startRatio * totalTrackWidth, 0); context->fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style->visitedDependentColor(CSSPropertyColor), style->colorSpace()); } context->restore(); return false; }
bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect) { HTMLMediaElement* mediaElement = parentMediaElement(renderObject); if (!mediaElement) return true; float volume = mediaElement->muted() ? 0.0f : mediaElement->volume(); if (!volume) return true; GraphicsContext* context = paintInfo.context; context->save(); context->setStrokeStyle(NoStroke); int rectHeight = rect.height(); float trackHeight = rectHeight * volume; RenderStyle* style = &renderObject.style(); IntRect volumeRect(rect); volumeRect.move(0, rectHeight - trackHeight); volumeRect.setHeight(ceil(trackHeight)); context->fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style->visitedDependentColor(CSSPropertyColor), style->colorSpace()); context->restore(); return false; }
void PaintPropertyTreeBuilder::updateOverflowClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) { if (!object.isBox()) return; const LayoutBox& box = toLayoutBox(object); // The <input> elements can't have contents thus CSS overflow property doesn't apply. // However for layout purposes we do generate child layout objects for them, e.g. button label. // We should clip the overflow from those children. This is called control clip and we // technically treat them like overflow clip. LayoutRect clipRect; if (box.hasControlClip()) clipRect = box.controlClipRect(context.paintOffset); else if (box.hasOverflowClip()) clipRect = box.overflowClipRect(context.paintOffset); else return; RefPtr<ClipPaintPropertyNode> borderRadiusClip; if (box.styleRef().hasBorderRadius()) { auto innerBorder = box.styleRef().getRoundedInnerBorderFor( LayoutRect(context.paintOffset, box.size())); borderRadiusClip = ClipPaintPropertyNode::create( context.currentTransform, innerBorder, context.currentClip); } RefPtr<ClipPaintPropertyNode> overflowClip = ClipPaintPropertyNode::create( context.currentTransform, FloatRoundedRect(FloatRect(clipRect)), borderRadiusClip ? borderRadiusClip.release() : context.currentClip); context.currentClip = overflowClip.get(); object.getMutableForPainting().ensureObjectPaintProperties().setOverflowClip(overflowClip.release()); }
static PassRefPtr<ClipPaintPropertyNode> createOverflowClipIfNeeded(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) { if (!object.isBox()) return nullptr; const LayoutBox& box = toLayoutBox(object); // The <input> elements can't have contents thus CSS overflow property doesn't apply. // However for layout purposes we do generate child layout objects for them, e.g. button label. // We should clip the overflow from those children. This is called control clip and we // technically treat them like overflow clip. LayoutRect clipRect; if (box.hasControlClip()) clipRect = box.controlClipRect(context.paintOffset); else if (box.hasOverflowClip()) clipRect = box.overflowClipRect(context.paintOffset); else return nullptr; RefPtr<ClipPaintPropertyNode> newClipNodeForBorderRadiusClip; const ComputedStyle& style = box.styleRef(); if (style.hasBorderRadius()) { newClipNodeForBorderRadiusClip = ClipPaintPropertyNode::create( context.currentTransform, style.getRoundedInnerBorderFor(LayoutRect(context.paintOffset, box.size())), context.currentClip); } RefPtr<ClipPaintPropertyNode> newClipNodeForOverflowClip = ClipPaintPropertyNode::create( context.currentTransform, FloatRoundedRect(FloatRect(clipRect)), newClipNodeForBorderRadiusClip ? newClipNodeForBorderRadiusClip.release() : context.currentClip); context.currentClip = newClipNodeForOverflowClip.get(); return newClipNodeForOverflowClip.release(); }
static void paintRoundedSliderBackground(const IntRect& rect, const ComputedStyle& style, GraphicsContext* context, Color sliderBackgroundColor ) { float borderRadius = rect.height() / 2; FloatSize radii(borderRadius, borderRadius); context->fillRoundedRect(FloatRoundedRect(rect, radii, radii, radii, radii), sliderBackgroundColor); }
RoundedInnerRectClipper::RoundedInnerRectClipper(LayoutObject& layoutObject, const PaintInfo& paintInfo, const LayoutRect& rect, const FloatRoundedRect& clipRect, RoundedInnerRectClipperBehavior behavior) : m_layoutObject(layoutObject) , m_paintInfo(paintInfo) , m_useDisplayItemList(RuntimeEnabledFeatures::slimmingPaintEnabled() && behavior == ApplyToDisplayListIfEnabled) , m_clipType(m_useDisplayItemList ? m_paintInfo.displayItemTypeForClipping() : DisplayItem::ClipBoxPaintPhaseFirst) { Vector<FloatRoundedRect> roundedRectClips; if (clipRect.isRenderable()) { roundedRectClips.append(clipRect); } else { // We create a rounded rect for each of the corners and clip it, while making sure we clip opposing corners together. if (!clipRect.radii().topLeft().isEmpty() || !clipRect.radii().bottomRight().isEmpty()) { FloatRect topCorner(clipRect.rect().x(), clipRect.rect().y(), rect.maxX() - clipRect.rect().x(), rect.maxY() - clipRect.rect().y()); FloatRoundedRect::Radii topCornerRadii; topCornerRadii.setTopLeft(clipRect.radii().topLeft()); roundedRectClips.append(FloatRoundedRect(topCorner, topCornerRadii)); FloatRect bottomCorner(rect.x().toFloat(), rect.y().toFloat(), clipRect.rect().maxX() - rect.x().toFloat(), clipRect.rect().maxY() - rect.y().toFloat()); FloatRoundedRect::Radii bottomCornerRadii; bottomCornerRadii.setBottomRight(clipRect.radii().bottomRight()); roundedRectClips.append(FloatRoundedRect(bottomCorner, bottomCornerRadii)); } if (!clipRect.radii().topRight().isEmpty() || !clipRect.radii().bottomLeft().isEmpty()) { FloatRect topCorner(rect.x().toFloat(), clipRect.rect().y(), clipRect.rect().maxX() - rect.x().toFloat(), rect.maxY() - clipRect.rect().y()); FloatRoundedRect::Radii topCornerRadii; topCornerRadii.setTopRight(clipRect.radii().topRight()); roundedRectClips.append(FloatRoundedRect(topCorner, topCornerRadii)); FloatRect bottomCorner(clipRect.rect().x(), rect.y().toFloat(), rect.maxX() - clipRect.rect().x(), clipRect.rect().maxY() - rect.y().toFloat()); FloatRoundedRect::Radii bottomCornerRadii; bottomCornerRadii.setBottomLeft(clipRect.radii().bottomLeft()); roundedRectClips.append(FloatRoundedRect(bottomCorner, bottomCornerRadii)); } } if (m_useDisplayItemList) { ASSERT(m_paintInfo.context->displayItemList()); if (m_paintInfo.context->displayItemList()->displayItemConstructionIsDisabled()) return; m_paintInfo.context->displayItemList()->createAndAppend<ClipDisplayItem>(layoutObject, m_clipType, LayoutRect::infiniteIntRect(), roundedRectClips); } else { ClipDisplayItem clipDisplayItem(layoutObject, m_clipType, LayoutRect::infiniteIntRect(), roundedRectClips); clipDisplayItem.replay(*paintInfo.context); } }
TEST_F(PaintPropertyTreeBuilderTest, BorderRadiusClip) { setBodyInnerHTML( "<style>" " body {" " margin: 0px;" " }" " #div {" " border-radius: 12px 34px 56px 78px;" " border-top: 45px solid;" " border-right: 50px solid;" " border-bottom: 55px solid;" " border-left: 60px solid;" " width: 500px;" " height: 400px;" " overflow: scroll;" " }" "</style>" "<div id='div'></div>"); FrameView* frameView = document().view(); LayoutObject& div = *document().getElementById("div")->layoutObject(); ObjectPaintProperties* divProperties = div.objectPaintProperties(); EXPECT_EQ(frameView->scrollTranslation(), divProperties->overflowClip()->localTransformSpace()); // The overflow clip rect includes only the padding box. // padding box = border box(500+60+50, 400+45+55) - border outset(60+50, 45+55) - scrollbars(15, 15) EXPECT_EQ(FloatRoundedRect(60, 45, 500, 400), divProperties->overflowClip()->clipRect()); const ClipPaintPropertyNode* borderRadiusClip = divProperties->overflowClip()->parent(); EXPECT_EQ(frameView->scrollTranslation(), borderRadiusClip->localTransformSpace()); // The border radius clip is the area enclosed by inner border edge, including the scrollbars. // As the border-radius is specified in outer radius, the inner radius is calculated by: // inner radius = max(outer radius - border width, 0) // In the case that two adjacent borders have different width, the inner radius of the corner // may transition from one value to the other. i.e. being an ellipse. EXPECT_EQ( FloatRoundedRect( FloatRect(60, 45, 500, 400), // = border box(610, 500) - border outset(110, 100) FloatSize(0, 0), // (top left) = max((12, 12) - (60, 45), (0, 0)) FloatSize(0, 0), // (top right) = max((34, 34) - (50, 45), (0, 0)) FloatSize(18, 23), // (bottom left) = max((78, 78) - (60, 55), (0, 0)) FloatSize(6, 1)), // (bottom right) = max((56, 56) - (50, 55), (0, 0)) borderRadiusClip->clipRect()); EXPECT_EQ(frameView->contentClip(), borderRadiusClip->parent()); }
void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace) { if (paintingDisabled()) return; if (hasShadow()) platformContext()->shadowBlur().drawRectShadow(this, FloatRoundedRect(rect)); fillRectWithColor(platformContext()->cr(), rect, color); }
void Path::addPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) { // Start at upper-left (after corner radii), add clock-wise. m_path.addRRect(FloatRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius), SkPath::kCW_Direction, 0); }
FloatRoundedRect FloatRoundedRect::paddingBounds(float padding) const { ASSERT(padding >= 0); if (!padding || isEmpty()) return *this; float boundsX = x() + std::min(width() / 2, padding); float boundsY = y() + std::min(height() / 2, padding); float boundsWidth = std::max(0.0f, width() - padding * 2); float boundsHeight = std::max(0.0f, height() - padding * 2); float boundsRadiusX = std::max(0.0f, rx() - padding); float boundsRadiusY = std::max(0.0f, ry() - padding); return FloatRoundedRect(FloatRect(boundsX, boundsY, boundsWidth, boundsHeight), FloatSize(boundsRadiusX, boundsRadiusY)); }
FloatRoundedRect FloatRoundedRect::marginBounds(float margin) const { ASSERT(margin >= 0); if (!margin) return *this; float boundsX = x() - margin; float boundsY = y() - margin; float boundsWidth = width() + margin * 2; float boundsHeight = height() + margin * 2; float boundsRadiusX = rx() + margin; float boundsRadiusY = ry() + margin; return FloatRoundedRect(FloatRect(boundsX, boundsY, boundsWidth, boundsHeight), FloatSize(boundsRadiusX, boundsRadiusY)); }
void PaintPropertyTreeBuilder::updateOverflowClip( const LayoutObject& object, PaintPropertyTreeBuilderContext& context) { if (!object.isBox()) return; const LayoutBox& box = toLayoutBox(object); // The <input> elements can't have contents thus CSS overflow property // doesn't apply. However for layout purposes we do generate child layout // objects for them, e.g. button label. We should clip the overflow from // those children. This is called control clip and we technically treat them // like overflow clip. LayoutRect clipRect; if (box.hasControlClip()) { clipRect = box.controlClipRect(context.current.paintOffset); } else if (box.hasOverflowClip() || box.styleRef().containsPaint() || (box.isSVGRoot() && toLayoutSVGRoot(box).shouldApplyViewportClip())) { clipRect = LayoutRect( pixelSnappedIntRect(box.overflowClipRect(context.current.paintOffset))); } else { if (auto* properties = object.getMutableForPainting().paintProperties()) { properties->clearInnerBorderRadiusClip(); properties->clearOverflowClip(); } return; } const auto* currentClip = context.current.clip; if (box.styleRef().hasBorderRadius()) { auto innerBorder = box.styleRef().getRoundedInnerBorderFor( LayoutRect(context.current.paintOffset, box.size())); object.getMutableForPainting() .ensurePaintProperties() .updateInnerBorderRadiusClip(context.current.clip, context.current.transform, innerBorder); currentClip = object.paintProperties()->innerBorderRadiusClip(); } else if (auto* properties = object.getMutableForPainting().paintProperties()) { properties->clearInnerBorderRadiusClip(); } object.getMutableForPainting().ensurePaintProperties().updateOverflowClip( currentClip, context.current.transform, FloatRoundedRect(FloatRect(clipRect))); const auto* properties = object.paintProperties(); if (properties && properties->overflowClip()) context.current.clip = properties->overflowClip(); }
void RenderWidget::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!shouldPaint(paintInfo, paintOffset)) return; LayoutPoint adjustedPaintOffset = paintOffset + location(); if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) paintBoxDecorations(paintInfo, adjustedPaintOffset); if (paintInfo.phase == PaintPhaseMask) { paintMask(paintInfo, adjustedPaintOffset); return; } if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && hasOutline()) paintOutline(paintInfo, LayoutRect(adjustedPaintOffset, size())); if (paintInfo.phase != PaintPhaseForeground) return; if (style().hasBorderRadius()) { LayoutRect borderRect = LayoutRect(adjustedPaintOffset, size()); if (borderRect.isEmpty()) return; // 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(borderRect, paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), true, true)); clipRoundedInnerRect(paintInfo.context(), borderRect, roundedInnerRect); } if (m_widget) paintContents(paintInfo, paintOffset); if (style().hasBorderRadius()) paintInfo.context().restore(); // Paint a partially transparent wash over selected widgets. if (isSelected() && !document().printing()) { // FIXME: selectionRect() is in absolute, not painting coordinates. paintInfo.context().fillRect(snappedIntRect(selectionRect()), selectionBackgroundColor(), style().colorSpace()); } if (hasLayer() && layer()->canResize()) layer()->paintResizer(paintInfo.context(), roundedIntPoint(adjustedPaintOffset), paintInfo.rect); }
const Path& BasicShapeInset::path(const FloatRect& boundingBox) { float left = floatValueForLength(m_left, boundingBox.width()); float top = floatValueForLength(m_top, boundingBox.height()); auto rect = FloatRect(left + boundingBox.x(), top + boundingBox.y(), std::max<float>(boundingBox.width() - left - floatValueForLength(m_right, boundingBox.width()), 0), std::max<float>(boundingBox.height() - top - floatValueForLength(m_bottom, boundingBox.height()), 0)); auto radii = FloatRoundedRect::Radii(floatSizeForLengthSize(m_topLeftRadius, boundingBox), floatSizeForLengthSize(m_topRightRadius, boundingBox), floatSizeForLengthSize(m_bottomLeftRadius, boundingBox), floatSizeForLengthSize(m_bottomRightRadius, boundingBox)); radii.scale(calcBorderRadiiConstraintScaleFor(rect, radii)); return cachedRoundedRectPath(FloatRoundedRect(rect, radii)); }
void PaintPropertyTreeBuilder::buildTreeRootNodes(FrameView& rootFrame, PaintPropertyTreeBuilderContext& context) { // Only create extra root clip and transform nodes when RLS is enabled, because the main frame // unconditionally create frame translation / clip nodes otherwise. if (rootFrame.frame().settings() && rootFrame.frame().settings()->rootLayerScrolls()) { transformRoot = TransformPaintPropertyNode::create(TransformationMatrix(), FloatPoint3D(), nullptr); context.currentTransform = context.transformForAbsolutePosition = context.transformForFixedPosition = transformRoot.get(); clipRoot = ClipPaintPropertyNode::create(transformRoot, FloatRoundedRect(LayoutRect::infiniteIntRect()), nullptr); context.currentClip = context.clipForAbsolutePosition = context.clipForFixedPosition = clipRoot.get(); } // The root frame never creates effect node so we unconditionally create a root node here. effectRoot = EffectPaintPropertyNode::create(1.0, nullptr); context.currentEffect = effectRoot.get(); }
void PaintPropertyTreeBuilder::updateCssClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) { if (!object.hasClip()) return; ASSERT(object.canContainAbsolutePositionObjects()); // Create clip node for descendants that are not fixed position. // We don't have to setup context.clipForAbsolutePosition here because this object must be // a container for absolute position descendants, and will copy from in-flow context later // at updateOutOfFlowContext() step. LayoutRect clipRect = toLayoutBox(object).clipRect(context.paintOffset); RefPtr<ClipPaintPropertyNode> clipNode = ClipPaintPropertyNode::create( context.currentTransform, FloatRoundedRect(FloatRect(clipRect)), context.currentClip); context.currentClip = clipNode.get(); object.getMutableForPainting().ensureObjectPaintProperties().setCssClip(clipNode.release()); }
void BasicShapeInset::path(Path& path, const FloatRect& boundingBox) { ASSERT(path.isEmpty()); float left = floatValueForLength(m_left, boundingBox.width()); float top = floatValueForLength(m_top, boundingBox.height()); FloatRoundedRect r = FloatRoundedRect( FloatRect( left + boundingBox.x(), top + boundingBox.y(), std::max<float>(boundingBox.width() - left - floatValueForLength(m_right, boundingBox.width()), 0), std::max<float>(boundingBox.height() - top - floatValueForLength(m_bottom, boundingBox.height()), 0) ), floatSizeForLengthSize(m_topLeftRadius, boundingBox), floatSizeForLengthSize(m_topRightRadius, boundingBox), floatSizeForLengthSize(m_bottomLeftRadius, boundingBox), floatSizeForLengthSize(m_bottomRightRadius, boundingBox) ); path.addRoundedRect(r); }
TEST_F(PaintPropertyTreeBuilderTest, FrameScrollingTraditional) { setBodyInnerHTML("<style> body { height: 10000px; } </style>"); document().domWindow()->scrollTo(0, 100); FrameView* frameView = document().view(); frameView->updateAllLifecyclePhases(); EXPECT_EQ(TransformationMatrix(), frameView->preTranslation()->matrix()); EXPECT_EQ(nullptr, frameView->preTranslation()->parent()); EXPECT_EQ(TransformationMatrix().translate(0, -100), frameView->scrollTranslation()->matrix()); EXPECT_EQ(frameView->preTranslation(), frameView->scrollTranslation()->parent()); EXPECT_EQ(frameView->preTranslation(), frameView->contentClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), frameView->contentClip()->clipRect()); EXPECT_EQ(nullptr, frameView->contentClip()->parent()); LayoutView* layoutView = document().layoutView(); ObjectPaintProperties* layoutViewProperties = layoutView->objectPaintProperties(); EXPECT_EQ(nullptr, layoutViewProperties); }
void PaintPropertyTreeBuilder::updateCssClip( const LayoutObject& object, PaintPropertyTreeBuilderContext& context) { if (object.hasClip()) { // Create clip node for descendants that are not fixed position. // We don't have to setup context.absolutePosition.clip here because this // object must be a container for absolute position descendants, and will // copy from in-flow context later at updateOutOfFlowContext() step. DCHECK(object.canContainAbsolutePositionObjects()); LayoutRect clipRect = toLayoutBox(object).clipRect(context.current.paintOffset); context.current.clip = object.getMutableForPainting().ensurePaintProperties().updateCssClip( context.current.clip, context.current.transform, FloatRoundedRect(FloatRect(clipRect))); return; } if (auto* properties = object.getMutableForPainting().paintProperties()) properties->clearCssClip(); }
TEST_F(PaintPropertyTreeBuilderTest, ControlClip) { setBodyInnerHTML( "<style>" " body {" " margin: 0;" " }" " input {" " border-width: 5px;" " padding: 0;" " }" "</style>" "<input id='button' type='button' style='width:345px; height:123px' value='some text'/>"); FrameView* frameView = document().view(); LayoutObject& button = *document().getElementById("button")->layoutObject(); ObjectPaintProperties* buttonProperties = button.objectPaintProperties(); EXPECT_EQ(frameView->scrollTranslation(), buttonProperties->overflowClip()->localTransformSpace()); EXPECT_EQ(FloatRoundedRect(5, 5, 335, 113), buttonProperties->overflowClip()->clipRect()); EXPECT_EQ(frameView->contentClip(), buttonProperties->overflowClip()->parent()); }
void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!shouldPaint(paintInfo, paintOffset)) return; #ifndef NDEBUG SetLayoutNeededForbiddenScope scope(this); #endif LayoutPoint adjustedPaintOffset = paintOffset + location(); if (hasVisibleBoxDecorations() && 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()); } }
DragImageRef createDragImageForLink(URL& url, const String& inLabel, FontRenderingMode fontRenderingMode) { // This is more or less an exact match for the Mac OS X code. const Font* labelFont; const Font* urlFont; FontCachePurgePreventer fontCachePurgePreventer; if (fontRenderingMode == AlternateRenderingMode) { static const Font alternateRenderingModeLabelFont = dragLabelFont(DragLinkLabelFontsize, true, AlternateRenderingMode); static const Font alternateRenderingModeURLFont = dragLabelFont(DragLinkUrlFontSize, false, AlternateRenderingMode); labelFont = &alternateRenderingModeLabelFont; urlFont = &alternateRenderingModeURLFont; } else { static const Font normalRenderingModeLabelFont = dragLabelFont(DragLinkLabelFontsize, true, NormalRenderingMode); static const Font normalRenderingModeURLFont = dragLabelFont(DragLinkUrlFontSize, false, NormalRenderingMode); labelFont = &normalRenderingModeLabelFont; urlFont = &normalRenderingModeURLFont; } bool drawURLString = true; bool clipURLString = false; bool clipLabelString = false; String urlString = url.string(); String label = inLabel; if (label.isEmpty()) { drawURLString = false; label = urlString; } // First step in drawing the link drag image width. TextRun labelRun(label.impl()); TextRun urlRun(urlString.impl()); IntSize labelSize(labelFont->width(labelRun), labelFont->fontMetrics().ascent() + labelFont->fontMetrics().descent()); if (labelSize.width() > MaxDragLabelStringWidth) { labelSize.setWidth(MaxDragLabelStringWidth); clipLabelString = true; } IntSize urlStringSize; IntSize imageSize(labelSize.width() + DragLabelBorderX * 2, labelSize.height() + DragLabelBorderY * 2); if (drawURLString) { urlStringSize.setWidth(urlFont->width(urlRun)); urlStringSize.setHeight(urlFont->fontMetrics().ascent() + urlFont->fontMetrics().descent()); imageSize.setHeight(imageSize.height() + urlStringSize.height()); if (urlStringSize.width() > MaxDragLabelStringWidth) { imageSize.setWidth(MaxDragLabelWidth); clipURLString = true; } else imageSize.setWidth(std::max(labelSize.width(), urlStringSize.width()) + DragLabelBorderX * 2); } // We now know how big the image needs to be, so we create and // fill the background HWndDC dc(0); auto workingDC = adoptGDIObject(::CreateCompatibleDC(dc)); if (!workingDC) return 0; PlatformGraphicsContext* contextRef; auto image = allocImage(workingDC.get(), imageSize, &contextRef); if (!image) return 0; ::SelectObject(workingDC.get(), image.get()); GraphicsContext context(contextRef); // On Mac alpha is {0.7, 0.7, 0.7, 0.8}, however we can't control alpha // for drag images on win, so we use 1 static const Color backgroundColor(140, 140, 140); static const IntSize radii(DragLabelRadius, DragLabelRadius); IntRect rect(0, 0, imageSize.width(), imageSize.height()); context.fillRoundedRect(FloatRoundedRect(rect, radii, radii, radii, radii), backgroundColor, ColorSpaceDeviceRGB); // Draw the text static const Color topColor(0, 0, 0, 255); // original alpha = 0.75 static const Color bottomColor(255, 255, 255, 127); // original alpha = 0.5 if (drawURLString) { if (clipURLString) urlString = StringTruncator::rightTruncate(urlString, imageSize.width() - (DragLabelBorderX * 2.0f), *urlFont, StringTruncator::EnableRoundingHacks); IntPoint textPos(DragLabelBorderX, imageSize.height() - (LabelBorderYOffset + urlFont->fontMetrics().descent())); WebCoreDrawDoubledTextAtPoint(context, urlString, textPos, *urlFont, topColor, bottomColor); } if (clipLabelString) label = StringTruncator::rightTruncate(label, imageSize.width() - (DragLabelBorderX * 2.0f), *labelFont, StringTruncator::EnableRoundingHacks); IntPoint textPos(DragLabelBorderX, DragLabelBorderY + labelFont->pixelSize()); WebCoreDrawDoubledTextAtPoint(context, label, textPos, *labelFont, topColor, bottomColor); deallocContext(contextRef); return image.leak(); }
std::unique_ptr<Shape> Shape::createShape(const BasicShape* basicShape, const LayoutSize& logicalBoxSize, WritingMode writingMode, float margin) { ASSERT(basicShape); bool horizontalWritingMode = isHorizontalWritingMode(writingMode); float boxWidth = horizontalWritingMode ? logicalBoxSize.width() : logicalBoxSize.height(); float boxHeight = horizontalWritingMode ? logicalBoxSize.height() : logicalBoxSize.width(); std::unique_ptr<Shape> shape; switch (basicShape->type()) { case BasicShape::BasicShapeCircleType: { const BasicShapeCircle* circle = static_cast<const BasicShapeCircle*>(basicShape); float centerX = floatValueForCenterCoordinate(circle->centerX(), boxWidth); float centerY = floatValueForCenterCoordinate(circle->centerY(), boxHeight); float radius = circle->floatValueForRadiusInBox(boxWidth, boxHeight); FloatPoint logicalCenter = physicalPointToLogical(FloatPoint(centerX, centerY), logicalBoxSize.height(), writingMode); shape = createCircleShape(logicalCenter, radius); break; } case BasicShape::BasicShapeEllipseType: { const BasicShapeEllipse* ellipse = static_cast<const BasicShapeEllipse*>(basicShape); float centerX = floatValueForCenterCoordinate(ellipse->centerX(), boxWidth); float centerY = floatValueForCenterCoordinate(ellipse->centerY(), boxHeight); float radiusX = ellipse->floatValueForRadiusInBox(ellipse->radiusX(), centerX, boxWidth); float radiusY = ellipse->floatValueForRadiusInBox(ellipse->radiusY(), centerY, boxHeight); FloatPoint logicalCenter = physicalPointToLogical(FloatPoint(centerX, centerY), logicalBoxSize.height(), writingMode); shape = createEllipseShape(logicalCenter, FloatSize(radiusX, radiusY)); break; } case BasicShape::BasicShapePolygonType: { const BasicShapePolygon& polygon = *static_cast<const BasicShapePolygon*>(basicShape); const Vector<Length>& values = polygon.values(); size_t valuesSize = values.size(); ASSERT(!(valuesSize % 2)); std::unique_ptr<Vector<FloatPoint>> vertices = std::make_unique<Vector<FloatPoint>>(valuesSize / 2); for (unsigned i = 0; i < valuesSize; i += 2) { FloatPoint vertex( floatValueForLength(values.at(i), boxWidth), floatValueForLength(values.at(i + 1), boxHeight)); (*vertices)[i / 2] = physicalPointToLogical(vertex, logicalBoxSize.height(), writingMode); } shape = createPolygonShape(WTF::move(vertices), polygon.windRule()); break; } case BasicShape::BasicShapeInsetType: { const BasicShapeInset& inset = *static_cast<const BasicShapeInset*>(basicShape); float left = floatValueForLength(inset.left(), boxWidth); float top = floatValueForLength(inset.top(), boxHeight); FloatRect rect(left, top, std::max<float>(boxWidth - left - floatValueForLength(inset.right(), boxWidth), 0), std::max<float>(boxHeight - top - floatValueForLength(inset.bottom(), boxHeight), 0)); FloatRect logicalRect = physicalRectToLogical(rect, logicalBoxSize.height(), writingMode); FloatSize boxSize(boxWidth, boxHeight); FloatSize topLeftRadius = physicalSizeToLogical(floatSizeForLengthSize(inset.topLeftRadius(), boxSize), writingMode); FloatSize topRightRadius = physicalSizeToLogical(floatSizeForLengthSize(inset.topRightRadius(), boxSize), writingMode); FloatSize bottomLeftRadius = physicalSizeToLogical(floatSizeForLengthSize(inset.bottomLeftRadius(), boxSize), writingMode); FloatSize bottomRightRadius = physicalSizeToLogical(floatSizeForLengthSize(inset.bottomRightRadius(), boxSize), writingMode); FloatRoundedRect::Radii cornerRadii(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); cornerRadii.scale(calcBorderRadiiConstraintScaleFor(logicalRect, cornerRadii)); shape = createInsetShape(FloatRoundedRect(logicalRect, cornerRadii)); break; } default: ASSERT_NOT_REACHED(); } shape->m_writingMode = writingMode; shape->m_margin = margin; return shape; }
bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r) { RenderStyle* style = &o.style(); paintInfo.context->fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style->visitedDependentColor(CSSPropertyColor), style->colorSpace()); return false; }