예제 #1
0
TEST_F(LayoutSVGRootTest, VisualRectMappingWithViewportClipWithoutBorder) {
  setBodyInnerHTML(
      "<svg id='root' style='width: 200px; height: 100px; overflow: hidden' "
      "viewBox='0 0 200 100'>"
      "   <rect id='rect' x='80' y='80' width='100' height='100'/>"
      "</svg>");

  const LayoutSVGRoot& root =
      *toLayoutSVGRoot(getLayoutObjectByElementId("root"));
  const LayoutSVGShape& svgRect =
      *toLayoutSVGShape(getLayoutObjectByElementId("rect"));

  LayoutRect rect = SVGLayoutSupport::visualRectInAncestorSpace(svgRect, root);
  // (80, 80, 100, 100) clipped by (0, 0, 200, 100).
  EXPECT_EQ(LayoutRect(80, 80, 100, 20), rect);

  LayoutRect rootVisualRect =
      static_cast<const LayoutObject&>(root).localVisualRect();
  // SVG root doesn't have box decoration background, so just use clipped
  // overflow of children.
  EXPECT_EQ(LayoutRect(80, 80, 100, 20), rootVisualRect);

  rect = rootVisualRect;
  EXPECT_TRUE(root.mapToVisualRectInAncestorSpace(&root, rect));
  EXPECT_EQ(LayoutRect(80, 80, 100, 20), rect);
}
예제 #2
0
TEST_F(LayoutSVGRootTest, VisualRectMappingWithViewportClipAndBorder) {
  setBodyInnerHTML(
      "<svg id='root' style='border: 10px solid red; width: 200px; height: "
      "100px; overflow: hidden' viewBox='0 0 200 100'>"
      "   <rect id='rect' x='80' y='80' width='100' height='100'/>"
      "</svg>");

  const LayoutSVGRoot& root =
      *toLayoutSVGRoot(getLayoutObjectByElementId("root"));
  const LayoutSVGShape& svgRect =
      *toLayoutSVGShape(getLayoutObjectByElementId("rect"));

  LayoutRect rect = SVGLayoutSupport::visualRectInAncestorSpace(svgRect, root);
  // (80, 80, 100, 100) added by root's content rect offset from border rect,
  // clipped by (10, 10, 200, 100).
  EXPECT_EQ(LayoutRect(90, 90, 100, 20), rect);

  LayoutRect rootVisualRect =
      static_cast<const LayoutObject&>(root).localVisualRect();
  // SVG root with overflow:hidden doesn't include overflow from children, just
  // border box rect.
  EXPECT_EQ(LayoutRect(0, 0, 220, 120), rootVisualRect);

  rect = rootVisualRect;
  EXPECT_TRUE(root.mapToVisualRectInAncestorSpace(&root, rect));
  // LayoutSVGRoot should not apply overflow clip on its own rect.
  EXPECT_EQ(LayoutRect(0, 0, 220, 120), rect);
}
예제 #3
0
TEST_F(LayoutSVGRootTest, VisualRectMappingWithoutViewportClipWithBorder) {
  setBodyInnerHTML(
      "<svg id='root' style='border: 10px solid red; width: 200px; height: "
      "100px; overflow: visible' viewBox='0 0 200 100'>"
      "   <rect id='rect' x='80' y='80' width='100' height='100'/>"
      "</svg>");

  const LayoutSVGRoot& root =
      *toLayoutSVGRoot(getLayoutObjectByElementId("root"));
  const LayoutSVGShape& svgRect =
      *toLayoutSVGShape(getLayoutObjectByElementId("rect"));

  LayoutRect rect = SVGLayoutSupport::visualRectInAncestorSpace(svgRect, root);
  // (80, 80, 100, 100) added by root's content rect offset from border rect,
  // not clipped.
  EXPECT_EQ(LayoutRect(90, 90, 100, 100), rect);

  LayoutRect rootVisualRect =
      static_cast<const LayoutObject&>(root).localVisualRect();
  // SVG root's overflow includes overflow from descendants.
  EXPECT_EQ(LayoutRect(0, 0, 220, 190), rootVisualRect);

  rect = rootVisualRect;
  EXPECT_TRUE(root.mapToVisualRectInAncestorSpace(&root, rect));
  EXPECT_EQ(LayoutRect(0, 0, 220, 190), rect);
}
예제 #4
0
void SVGLayoutSupport::layoutChildren(
    LayoutObject* firstChild, bool forceLayout, bool screenScalingFactorChanged, bool layoutSizeChanged)
{
    for (LayoutObject* child = firstChild; child; child = child->nextSibling()) {
        bool forceChildLayout = forceLayout;

        if (screenScalingFactorChanged) {
            // If the screen scaling factor changed we need to update the text
            // metrics (note: this also happens for layoutSizeChanged=true).
            if (child->isSVGText())
                toLayoutSVGText(child)->setNeedsTextMetricsUpdate();
            forceChildLayout = true;
        }

        if (layoutSizeChanged) {
            // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths
            if (SVGElement* element = child->node()->isSVGElement() ? toSVGElement(child->node()) : 0) {
                if (element->hasRelativeLengths()) {
                    // FIXME: this should be done on invalidation, not during layout.
                    // When the layout size changed and when using relative values tell the LayoutSVGShape to update its shape object
                    if (child->isSVGShape()) {
                        toLayoutSVGShape(child)->setNeedsShapeUpdate();
                    } else if (child->isSVGText()) {
                        toLayoutSVGText(child)->setNeedsTextMetricsUpdate();
                        toLayoutSVGText(child)->setNeedsPositioningValuesUpdate();
                    }

                    forceChildLayout = true;
                }
            }
        }

        // Resource containers are nasty: they can invalidate clients outside the current SubtreeLayoutScope.
        // Since they only care about viewport size changes (to resolve their relative lengths), we trigger
        // their invalidation directly from SVGSVGElement::svgAttributeChange() or at a higher
        // SubtreeLayoutScope (in LayoutView::layout()). We do not create a SubtreeLayoutScope for
        // resources because their ability to reference each other leads to circular layout. We protect
        // against that within the layout code for resources, but it causes assertions if we use a
        // SubTreeLayoutScope for them.
        if (child->isSVGResourceContainer()) {
            // Lay out any referenced resources before the child.
            layoutResourcesIfNeeded(child);
            child->layoutIfNeeded();
        } else {
            SubtreeLayoutScope layoutScope(*child);
            if (forceChildLayout)
                layoutScope.setNeedsLayout(child, LayoutInvalidationReason::SvgChanged);

            // Lay out any referenced resources before the child.
            layoutResourcesIfNeeded(child);
            child->layoutIfNeeded();
        }
    }
}
bool SVGGeometryElement::isPointInStroke(PassRefPtrWillBeRawPtr<SVGPointTearOff> point) const
{
    document().updateLayoutIgnorePendingStylesheets();

    // FIXME: Eventually we should support isPointInStroke for display:none elements.
    if (!layoutObject() || !layoutObject()->isSVGShape())
        return false;

    HitTestRequest request(HitTestRequest::ReadOnly);
    PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_GEOMETRY_HITTESTING, request, layoutObject()->style()->pointerEvents());
    hitRules.canHitFill = false;
    return toLayoutSVGShape(layoutObject())->nodeAtFloatPointInternal(request, point->target()->value(), hitRules);
}
예제 #6
0
void SVGLayoutSupport::layoutChildren(LayoutObject* start, bool selfNeedsLayout)
{
    // When hasRelativeLengths() is false, no descendants have relative lengths
    // (hence no one is interested in viewport size changes).
    bool layoutSizeChanged = toSVGElement(start->node())->hasRelativeLengths()
        && layoutSizeOfNearestViewportChanged(start);
    bool transformChanged = transformToRootChanged(start);

    for (LayoutObject* child = start->slowFirstChild(); child; child = child->nextSibling()) {
        bool forceLayout = selfNeedsLayout;

        if (transformChanged) {
            // If the transform changed we need to update the text metrics (note: this also happens for layoutSizeChanged=true).
            if (child->isSVGText())
                toLayoutSVGText(child)->setNeedsTextMetricsUpdate();
            forceLayout = true;
        }

        if (layoutSizeChanged) {
            // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths
            if (SVGElement* element = child->node()->isSVGElement() ? toSVGElement(child->node()) : 0) {
                if (element->hasRelativeLengths()) {
                    // FIXME: this should be done on invalidation, not during layout.
                    // When the layout size changed and when using relative values tell the LayoutSVGShape to update its shape object
                    if (child->isSVGShape()) {
                        toLayoutSVGShape(child)->setNeedsShapeUpdate();
                    } else if (child->isSVGText()) {
                        toLayoutSVGText(child)->setNeedsTextMetricsUpdate();
                        toLayoutSVGText(child)->setNeedsPositioningValuesUpdate();
                    }

                    forceLayout = true;
                }
            }
        }

        SubtreeLayoutScope layoutScope(*child);
        // Resource containers are nasty: they can invalidate clients outside the current SubtreeLayoutScope.
        // Since they only care about viewport size changes (to resolve their relative lengths), we trigger
        // their invalidation directly from SVGSVGElement::svgAttributeChange() or at a higher
        // SubtreeLayoutScope (in LayoutView::layout()).
        if (forceLayout && !child->isSVGResourceContainer())
            layoutScope.setNeedsLayout(child, LayoutInvalidationReason::SvgChanged);

        // Lay out any referenced resources before the child.
        layoutResourcesIfNeeded(child);
        child->layoutIfNeeded();
    }
}
void SVGLineElement::svgAttributeChanged(const QualifiedName& attrName)
{
    if (attrName == SVGNames::x1Attr
        || attrName == SVGNames::y1Attr
        || attrName == SVGNames::x2Attr
        || attrName == SVGNames::y2Attr) {
        updateRelativeLengthsInformation();

        LayoutSVGShape* layoutObject = toLayoutSVGShape(this->layoutObject());
        if (!layoutObject)
            return;

        SVGElement::InvalidationGuard invalidationGuard(this);
        layoutObject->setNeedsShapeUpdate();
        markForLayoutAndParentResourceInvalidation(layoutObject);
        return;
    }

    SVGGeometryElement::svgAttributeChanged(attrName);
}
예제 #8
0
void SVGCircleElement::svgAttributeChanged(const QualifiedName& attrName) {
  if (attrName == SVGNames::rAttr || attrName == SVGNames::cxAttr ||
      attrName == SVGNames::cyAttr) {
    SVGElement::InvalidationGuard invalidationGuard(this);

    invalidateSVGPresentationAttributeStyle();
    setNeedsStyleRecalc(LocalStyleChange,
                        StyleChangeReasonForTracing::fromAttribute(attrName));
    updateRelativeLengthsInformation();

    LayoutSVGShape* layoutObject = toLayoutSVGShape(this->layoutObject());
    if (!layoutObject)
      return;

    layoutObject->setNeedsShapeUpdate();
    markForLayoutAndParentResourceInvalidation(layoutObject);
    return;
  }

  SVGGraphicsElement::svgAttributeChanged(attrName);
}
예제 #9
0
void SVGLayoutSupport::computeContainerBoundingBoxes(
    const LayoutObject* container,
    FloatRect& objectBoundingBox,
    bool& objectBoundingBoxValid,
    FloatRect& strokeBoundingBox,
    FloatRect& paintInvalidationBoundingBox) {
  objectBoundingBox = FloatRect();
  objectBoundingBoxValid = false;
  strokeBoundingBox = FloatRect();

  // When computing the strokeBoundingBox, we use the paintInvalidationRects of
  // the container's children so that the container's stroke includes the
  // resources applied to the children (such as clips and filters). This allows
  // filters applied to containers to correctly bound the children, and also
  // improves inlining of SVG content, as the stroke bound is used in that
  // situation also.
  for (LayoutObject* current = container->slowFirstChild(); current;
       current = current->nextSibling()) {
    if (current->isSVGHiddenContainer())
      continue;

    // Don't include elements in the union that do not layout.
    if (current->isSVGShape() && toLayoutSVGShape(current)->isShapeEmpty())
      continue;

    if (current->isSVGText() &&
        !toLayoutSVGText(current)->isObjectBoundingBoxValid())
      continue;

    const AffineTransform& transform = current->localToSVGParentTransform();
    updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, current,
                            transform.mapRect(current->objectBoundingBox()));
    strokeBoundingBox.unite(transform.mapRect(
        current->paintInvalidationRectInLocalSVGCoordinates()));
  }

  paintInvalidationBoundingBox = strokeBoundingBox;
}
예제 #10
0
LayoutRect SVGLayoutSupport::transformPaintInvalidationRect(
    const LayoutObject& object,
    const AffineTransform& rootTransform,
    const FloatRect& localRect) {
  FloatRect adjustedRect = rootTransform.mapRect(localRect);

  if (object.isSVGShape() && object.styleRef().svgStyle().hasStroke()) {
    if (float strokeWidthForHairlinePadding =
            toLayoutSVGShape(object).strokeWidth()) {
      // For hairline strokes (stroke-width < 1 in device space), Skia
      // rasterizes up to 0.4(9) off the stroke center. That means
      // enclosingIntRect is not enough - we must also pad to 0.5.
      // This is still fragile as it misses out on CC/DSF CTM components.
      const FloatSize strokeSize = rootTransform.mapSize(FloatSize(
          strokeWidthForHairlinePadding, strokeWidthForHairlinePadding));
      if (strokeSize.width() < 1 || strokeSize.height() < 1) {
        float pad =
            0.5f - std::min(strokeSize.width(), strokeSize.height()) / 2;
        DCHECK_GT(pad, 0);
        // Additionally, square/round caps can potentially introduce an outset
        // <= 0.5
        if (object.styleRef().svgStyle().capStyle() != ButtCap)
          pad += 0.5f;
        adjustedRect.inflate(pad);
      }
    }
  }

  if (adjustedRect.isEmpty())
    return LayoutRect();

  // Use enclosingIntRect because we cannot properly apply subpixel offset of
  // the SVGRoot since we don't know the desired subpixel accumulation at this
  // point.
  return LayoutRect(enclosingIntRect(adjustedRect));
}
예제 #11
0
LayoutRect SVGLayoutSupport::transformPaintInvalidationRect(const LayoutObject& object, const AffineTransform& rootTransform, const FloatRect& localRect)
{
    FloatRect adjustedRect = rootTransform.mapRect(localRect);

    if (object.isSVGShape() && object.styleRef().svgStyle().hasStroke()) {
        if (float strokeWidthForHairlinePadding = toLayoutSVGShape(object).strokeWidth()) {
            // For hairline strokes (stroke-width < 1 in device space), Skia rasterizes up to 0.4(9) off
            // the stroke center. That means enclosingIntRect is not enough - we must also pad to 0.5.
            // This is still fragile as it misses out on CC/DSF CTM components.
            const FloatSize strokeSize = rootTransform.mapSize(
                FloatSize(strokeWidthForHairlinePadding, strokeWidthForHairlinePadding));
            if (strokeSize.width() < 1 || strokeSize.height() < 1) {
                const float pad = 0.5f - std::min(strokeSize.width(), strokeSize.height()) / 2;
                ASSERT(pad > 0);
                adjustedRect.inflate(pad);
            }
        }
    }

    if (adjustedRect.isEmpty())
        return LayoutRect();

    return LayoutRect(enclosingIntRect(adjustedRect));
}