bool LayoutSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
{
    FloatPoint point = nodeAtPoint;
    if (!SVGLayoutSupport::pointInClippingArea(this, point))
        return false;

    if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
        AffineTransform transform;
        transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
        transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
        point = transform.inverse().mapPoint(point);
    }

    AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->calculateAnimatedLocalTransform();
    if (!animatedLocalTransform.isInvertible())
        return false;

    point = animatedLocalTransform.inverse().mapPoint(point);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && !isSVGUseElement(*childElement))
            continue;
        IntPoint hitPoint;
        HitTestResult result(HitTestRequest::SVGClipContent, hitPoint);
        if (layoutObject->nodeAtFloatPoint(result, point, HitTestForeground))
            return true;
    }

    return false;
}
Example #2
0
bool SVGSVGElement::checkIntersectionOrEnclosure(
    const SVGElement& element,
    const FloatRect& rect,
    CheckIntersectionOrEnclosure mode) const {
  LayoutObject* layoutObject = element.layoutObject();
  ASSERT(!layoutObject || layoutObject->style());
  if (!layoutObject ||
      layoutObject->style()->pointerEvents() == EPointerEvents::None)
    return false;

  if (!isIntersectionOrEnclosureTarget(layoutObject))
    return false;

  AffineTransform ctm = toSVGGraphicsElement(element).computeCTM(
      AncestorScope, DisallowStyleUpdate, this);
  FloatRect mappedRepaintRect =
      ctm.mapRect(layoutObject->visualRectInLocalSVGCoordinates());

  bool result = false;
  switch (mode) {
    case CheckIntersection:
      result = intersectsAllowingEmpty(rect, mappedRepaintRect);
      break;
    case CheckEnclosure:
      result = rect.contains(mappedRepaintRect);
      break;
    default:
      ASSERT_NOT_REACHED();
      break;
  }

  return result;
}
void LayoutSVGResourceMasker::calculateMaskContentPaintInvalidationRect()
{
    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;
        m_maskContentBoundaries.unite(layoutObject->localToParentTransform().mapRect(layoutObject->paintInvalidationRectInLocalCoordinates()));
    }
}
sk_sp<const SkPicture> LayoutSVGResourceMasker::createContentPicture(
    AffineTransform& contentTransformation,
    const FloatRect& targetBoundingBox,
    GraphicsContext& context) {
    SVGUnitTypes::SVGUnitType contentUnits = toSVGMaskElement(element())
            ->maskContentUnits()
            ->currentValue()
            ->enumValue();
    if (contentUnits == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) {
        contentTransformation.translate(targetBoundingBox.x(),
                                        targetBoundingBox.y());
        contentTransformation.scaleNonUniform(targetBoundingBox.width(),
                                              targetBoundingBox.height());
    }

    if (m_maskContentPicture)
        return m_maskContentPicture;

    SubtreeContentTransformScope contentTransformScope(contentTransformation);

    // Using strokeBoundingBox instead of visualRectInLocalCoordinates
    // to avoid the intersection with local clips/mask, which may yield incorrect
    // results when mixing objectBoundingBox and userSpaceOnUse units.
    // http://crbug.com/294900
    FloatRect bounds = strokeBoundingBox();

    SkPictureBuilder pictureBuilder(bounds, nullptr, &context);

    ColorFilter maskContentFilter =
        style()->svgStyle().colorInterpolation() == CI_LINEARRGB
        ? ColorFilterSRGBToLinearRGB
        : ColorFilterNone;
    pictureBuilder.context().setColorFilter(maskContentFilter);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element());
            childElement;
            childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == EDisplay::None ||
                style->visibility() != EVisibility::Visible)
            continue;

        SVGPaintContext::paintSubtree(pictureBuilder.context(), layoutObject);
    }

    m_maskContentPicture = pictureBuilder.endRecording();
    return m_maskContentPicture;
}
void LayoutSVGResourceClipper::calculateClipContentPaintInvalidationRect()
{
    // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && !isSVGUseElement(*childElement))
            continue;
        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;
        m_clipBoundaries.unite(layoutObject->localToParentTransform().mapRect(layoutObject->paintInvalidationRectInLocalCoordinates()));
    }
    m_clipBoundaries = toSVGClipPathElement(element())->calculateAnimatedLocalTransform().mapRect(m_clipBoundaries);
}
void LayoutSVGResourceMasker::calculateMaskContentVisualRect() {
    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element());
            childElement;
            childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == EDisplay::None ||
                style->visibility() != EVisibility::Visible)
            continue;
        m_maskContentBoundaries.unite(
            layoutObject->localToSVGParentTransform().mapRect(
                layoutObject->visualRectInLocalSVGCoordinates()));
    }
}
PassRefPtr<const SkPicture> LayoutSVGResourceMasker::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox)
{
    SVGUnitTypes::SVGUnitType contentUnits = toSVGMaskElement(element())->maskContentUnits()->currentValue()->enumValue();
    if (contentUnits == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
        contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
        contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
    }

    if (m_maskContentPicture)
        return m_maskContentPicture;

    SubtreeContentTransformScope contentTransformScope(contentTransformation);

    // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
    // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
    // userSpaceOnUse units (http://crbug.com/294900).
    FloatRect bounds = strokeBoundingBox();

    OwnPtr<DisplayItemList> displayItemList;
    if (RuntimeEnabledFeatures::slimmingPaintEnabled())
        displayItemList = DisplayItemList::create();
    GraphicsContext context(nullptr, displayItemList.get());
    context.beginRecording(bounds);

    ColorFilter maskContentFilter = style()->svgStyle().colorInterpolation() == CI_LINEARRGB
                                    ? ColorFilterSRGBToLinearRGB : ColorFilterNone;
    context.setColorFilter(maskContentFilter);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;

        SVGPaintContext::paintSubtree(&context, layoutObject);
    }

    if (displayItemList)
        displayItemList->commitNewDisplayItemsAndReplay(context);
    m_maskContentPicture = context.endRecording();
    return m_maskContentPicture;
}
bool LayoutSVGResourceClipper::calculateClipContentPathIfNeeded()
{
    if (!m_clipContentPath.isEmpty())
        return true;

    // If the current clip-path gets clipped itself, we have to fallback to masking.
    if (style()->svgStyle().hasClipper())
        return false;

    unsigned opCount = 0;
    bool usingBuilder = false;
    SkOpBuilder clipPathBuilder;

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* childLayoutObject = childElement->layoutObject();
        if (!childLayoutObject)
            continue;
        // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
        if (childLayoutObject->isSVGText()) {
            m_clipContentPath.clear();
            return false;
        }
        if (!childElement->isSVGGraphicsElement())
            continue;

        const ComputedStyle* style = childLayoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;

        // Current shape in clip-path gets clipped too. Fallback to masking.
        if (style->svgStyle().hasClipper()) {
            m_clipContentPath.clear();
            return false;
        }

        // First clip shape.
        if (m_clipContentPath.isEmpty()) {
            if (isSVGGeometryElement(childElement))
                toSVGGeometryElement(childElement)->toClipPath(m_clipContentPath);
            else if (isSVGUseElement(childElement))
                toSVGUseElement(childElement)->toClipPath(m_clipContentPath);

            continue;
        }

        // Multiple shapes require PathOps. In some degenerate cases PathOps can exhibit quadratic
        // behavior, so we cap the number of ops to a reasonable count.
        const unsigned kMaxOps = 42;
        if (!RuntimeEnabledFeatures::pathOpsSVGClippingEnabled() || ++opCount > kMaxOps) {
            m_clipContentPath.clear();
            return false;
        }

        // Second clip shape => start using the builder.
        if (!usingBuilder) {
            clipPathBuilder.add(m_clipContentPath.skPath(), kUnion_SkPathOp);
            usingBuilder = true;
        }

        Path subPath;
        if (isSVGGeometryElement(childElement))
            toSVGGeometryElement(childElement)->toClipPath(subPath);
        else if (isSVGUseElement(childElement))
            toSVGUseElement(childElement)->toClipPath(subPath);

        clipPathBuilder.add(subPath.skPath(), kUnion_SkPathOp);
    }

    if (usingBuilder) {
        SkPath resolvedPath;
        clipPathBuilder.resolve(&resolvedPath);
        m_clipContentPath = resolvedPath;
    }

    return true;
}
PassRefPtr<const SkPicture> LayoutSVGResourceClipper::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox,
    GraphicsContext& context)
{
    ASSERT(frame());

    if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
        contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
        contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
    }

    if (m_clipContentPicture)
        return m_clipContentPicture;

    SubtreeContentTransformScope contentTransformScope(contentTransformation);

    // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
    // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
    // userSpaceOnUse units (http://crbug.com/294900).
    FloatRect bounds = strokeBoundingBox();

    SkPictureBuilder pictureBuilder(bounds, nullptr, &context);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;

        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;

        bool isUseElement = isSVGUseElement(*childElement);
        if (isUseElement) {
            const SVGGraphicsElement* clippingElement = toSVGUseElement(*childElement).targetGraphicsElementForClipping();
            if (!clippingElement)
                continue;

            layoutObject = clippingElement->layoutObject();
            if (!layoutObject)
                continue;
        }

        // Only shapes, paths and texts are allowed for clipping.
        if (!layoutObject->isSVGShape() && !layoutObject->isSVGText())
            continue;

        if (isUseElement)
            layoutObject = childElement->layoutObject();

        // Switch to a paint behavior where all children of this <clipPath> will be laid out using special constraints:
        // - fill-opacity/stroke-opacity/opacity set to 1
        // - masker/filter not applied when laying out the children
        // - fill is set to the initial fill paint server (solid, black)
        // - stroke is set to the initial stroke paint server (none)
        PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), PaintPhaseForeground, GlobalPaintNormalPhase, PaintLayerPaintingRenderingClipPathAsMask);
        layoutObject->paint(info, IntPoint());
    }

    m_clipContentPicture = pictureBuilder.endRecording();
    return m_clipContentPicture;
}