Esempio n. 1
0
void
nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
                                  nsIFrame* aClippedFrame,
                                  const gfxMatrix& aMatrix)
{
  MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");

  DrawTarget& aDrawTarget = *aContext.GetDrawTarget();

  // No need for AutoReferenceLimiter since simple clip paths can't create
  // a reference loop (they don't reference other clip paths).

  // Restore current transform after applying clip path:
  gfxContextMatrixAutoSaveRestore autoRestore(&aContext);

  RefPtr<Path> clipPath;

  nsISVGChildFrame* singleClipPathChild = nullptr;
  IsTrivial(&singleClipPathChild);

  if (singleClipPathChild) {
    nsSVGPathGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
    if (pathFrame) {
      nsSVGPathGeometryElement* pathElement =
        static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent());
      gfxMatrix toChildsUserSpace = pathElement->
        PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix,
                                 eUserSpaceToParent);
      gfxMatrix newMatrix =
        aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers();
      if (!newMatrix.IsSingular()) {
        aContext.SetMatrix(newMatrix);
        FillRule clipRule =
          nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
        clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule);
      }
    }
  }

  if (clipPath) {
    aContext.Clip(clipPath);
  } else {
    // The spec says clip away everything if we have no children or the
    // clipping path otherwise can't be resolved:
    aContext.Clip(Rect());
  }
}
/* static*/ void
nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext,
                                           nsIFrame* aFrame)
{
  auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;

#ifdef DEBUG
  StyleShapeSourceType type = clipPathStyle.GetType();
  MOZ_ASSERT(type == StyleShapeSourceType::Shape ||
             type == StyleShapeSourceType::Box,
             "This function is used with basic-shape and geometry-box only.");
#endif

  nsCSSClipPathInstance instance(aFrame, clipPathStyle);

  aContext.NewPath();
  RefPtr<Path> path = instance.CreateClipPath(aContext.GetDrawTarget());
  aContext.SetPath(path);
  aContext.Clip();
}
void
nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext,
                                              nsIFrame* aFrame,
                                              const nsRect& aDirtyRect,
                                              const nsRect& aBorderArea,
                                              nsDisplayListBuilder* aBuilder,
                                              LayerManager *aLayerManager)
{
#ifdef DEBUG
  NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
               (NS_SVGDisplayListPaintingEnabled() &&
                !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
               "Should not use nsSVGIntegrationUtils on this SVG frame");
#endif

  /* SVG defines the following rendering model:
   *
   *  1. Render geometry
   *  2. Apply filter
   *  3. Apply clipping, masking, group opacity
   *
   * We follow this, but perform a couple of optimizations:
   *
   * + Use cairo's clipPath when representable natively (single object
   *   clip region).
   *
   * + Merge opacity and masking if both used together.
   */

  const nsIContent* content = aFrame->GetContent();
  bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
  if (hasSVGLayout) {
    nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
    if (!svgChildFrame || !aFrame->GetContent()->IsSVGElement()) {
      NS_ASSERTION(false, "why?");
      return;
    }
    if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
      return; // The SVG spec says not to draw _anything_
    }
  }

  float opacity = aFrame->StyleDisplay()->mOpacity;
  if (opacity == 0.0f) {
    return;
  }
  if (opacity != 1.0f &&
      hasSVGLayout && nsSVGUtils::CanOptimizeOpacity(aFrame)) {
    opacity = 1.0f;
  }

  /* Properties are added lazily and may have been removed by a restyle,
     so make sure all applicable ones are set again. */
  nsIFrame* firstFrame =
    nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
  nsSVGEffects::EffectProperties effectProperties =
    nsSVGEffects::GetEffectProperties(firstFrame);

  bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
  nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);

  bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true;

  DrawTarget* drawTarget = aContext.GetDrawTarget();
  gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext);

  nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame);
  nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset;
  if (!firstFrame->IsFrameOfType(nsIFrame::eSVG)) {
    /* Snap the offset if the reference frame is not a SVG frame,
     * since other frames will be snapped to pixel when rendering. */
    offsetToBoundingBox = nsPoint(
      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x),
      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y));
  }

  // After applying only "offsetToBoundingBox", aCtx would have its origin at
  // the top left corner of aFrame's bounding box (over all continuations).
  // However, SVG painting needs the origin to be located at the origin of the
  // SVG frame's "user space", i.e. the space in which, for example, the
  // frame's BBox lives.
  // SVG geometry frames and foreignObject frames apply their own offsets, so
  // their position is relative to their user space. So for these frame types,
  // if we want aCtx to be in user space, we first need to subtract the
  // frame's position so that SVG painting can later add it again and the
  // frame is painted in the right place.

  gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
  nsPoint toUserSpace(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
                      nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
  nsPoint offsetToUserSpace = offsetToBoundingBox - toUserSpace;

  NS_ASSERTION(hasSVGLayout || offsetToBoundingBox == offsetToUserSpace,
               "For non-SVG frames there shouldn't be any additional offset");

  gfxPoint devPixelOffsetToUserSpace =
    nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
                                   aFrame->PresContext()->AppUnitsPerDevPixel());
  aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace));

  gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame);

  const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
  // Keep moving forward even if svgMaskFrame is nullptr or isOK is false.
  // This source is not a svg mask, but it still can be a correct mask image.
  nsSVGMaskFrame *svgMaskFrame = effectProperties.GetMaskFrame(&isOK);

  bool complexEffects = false;
  bool hasValidLayers = svgReset->mMask.HasLayerWithImage();

  // These are used if we require a temporary surface for a custom blend mode.
  RefPtr<gfxContext> target = &aContext;
  IntPoint targetOffset;

  /* Check if we need to do additional operations on this child's
   * rendering, which necessitates rendering into another surface. */
  if (opacity != 1.0f ||  (clipPathFrame && !isTrivialClip)
      || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL
      || svgMaskFrame || hasValidLayers) {
    complexEffects = true;

    aContext.Save();
    nsRect clipRect =
      aFrame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
    aContext.Clip(NSRectToSnappedRect(clipRect,
                                  aFrame->PresContext()->AppUnitsPerDevPixel(),
                                  *drawTarget));

    Matrix maskTransform;
    RefPtr<SourceSurface> maskSurface;
    if (svgMaskFrame) {
      maskSurface = svgMaskFrame->GetMaskForMaskedFrame(&aContext,
                                                     aFrame,
                                                     cssPxToDevPxMatrix,
                                                     opacity,
                                                     &maskTransform);
    } else if (hasValidLayers) {
      gfxRect clipRect = aContext.GetClipExtents();
      {
        gfxContextMatrixAutoSaveRestore matRestore(&aContext);

        aContext.SetMatrix(gfxMatrix());
        clipRect = aContext.GetClipExtents();
      }
      IntRect drawRect = RoundedOut(ToRect(clipRect));
      RefPtr<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::A8);
      if (!targetDT) {
        aContext.Restore();
        return;
      }

      RefPtr<gfxContext> target = new gfxContext(targetDT);
      target->SetMatrix(matrixAutoSaveRestore.Matrix() * gfxMatrix::Translation(-drawRect.TopLeft()));

      // Generate mask surface.
      uint32_t flags = aBuilder->GetBackgroundPaintFlags() |
                       nsCSSRendering::PAINTBG_MASK_IMAGE;
      nsRenderingContext rc(target);
      nsCSSRendering::PaintBackgroundWithSC(aFrame->PresContext(),
                                            rc,
                                            aFrame,
                                            aDirtyRect,
                                            aBorderArea,
                                            firstFrame->StyleContext(),
                                            *aFrame->StyleBorder(),
                                            flags,
                                            nullptr,
                                            -1);
      maskSurface = targetDT->Snapshot();

      // Compute mask transform.
      Matrix mat = ToMatrix(aContext.CurrentMatrix());
      mat.Invert();
      maskTransform = Matrix::Translation(drawRect.x, drawRect.y) * mat;
    }

    if ((svgMaskFrame || hasValidLayers) && !maskSurface) {
      // Entire surface is clipped out.
      aContext.Restore();
      return;
    }

    if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
      // Create a temporary context to draw to so we can blend it back with
      // another operator.
      gfxRect clipRect;
      {
        gfxContextMatrixAutoSaveRestore matRestore(&aContext);

        aContext.SetMatrix(gfxMatrix());
        clipRect = aContext.GetClipExtents();
      }

      IntRect drawRect = RoundedOut(ToRect(clipRect));

      RefPtr<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::B8G8R8A8);
      if (!targetDT) {
        aContext.Restore();
        return;
      }
      target = new gfxContext(targetDT);
      target->SetMatrix(aContext.CurrentMatrix() * gfxMatrix::Translation(-drawRect.TopLeft()));
      targetOffset = drawRect.TopLeft();
    }

    if (clipPathFrame && !isTrivialClip) {
      Matrix clippedMaskTransform;
      RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, cssPxToDevPxMatrix,
                                                                         &clippedMaskTransform, maskSurface, maskTransform);

      if (clipMaskSurface) {
        maskSurface = clipMaskSurface;
        maskTransform = clippedMaskTransform;
      }
    }

    if (opacity != 1.0f || svgMaskFrame  || hasValidLayers ||
        (clipPathFrame && !isTrivialClip)) {
      target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, maskSurface, maskTransform);
    }
  }

  /* If this frame has only a trivial clipPath, set up cairo's clipping now so
   * we can just do normal painting and get it clipped appropriately.
   */
  if (clipPathFrame && isTrivialClip) {
    aContext.Save();
    clipPathFrame->ApplyClipPath(aContext, aFrame, cssPxToDevPxMatrix);
  }

  /* Paint the child */
  if (effectProperties.HasValidFilter()) {
    RegularFramePaintCallback callback(aBuilder, aLayerManager,
                                       offsetToUserSpace);

    nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox;
    gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
    nsFilterInstance::PaintFilteredFrame(aFrame, target->GetDrawTarget(),
                                         tm, &callback, &dirtyRegion);
  } else {
    target->SetMatrix(matrixAutoSaveRestore.Matrix());
    BasicLayerManager* basic = static_cast<BasicLayerManager*>(aLayerManager);
    RefPtr<gfxContext> oldCtx = basic->GetTarget();
    basic->SetTarget(target);
    aLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder);
    basic->SetTarget(oldCtx);
  }

  if (clipPathFrame && isTrivialClip) {
    aContext.Restore();
  }

  /* No more effects, we're done. */
  if (!complexEffects) {
    return;
  }

  if (opacity != 1.0f || svgMaskFrame || hasValidLayers ||
      (clipPathFrame && !isTrivialClip)) {
    target->PopGroupAndBlend();
  }

  if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
    RefPtr<DrawTarget> targetDT = target->GetDrawTarget();
    target = nullptr;
    RefPtr<SourceSurface> targetSurf = targetDT->Snapshot();

    aContext.SetMatrix(gfxMatrix()); // This will be restored right after.
    RefPtr<gfxPattern> pattern = new gfxPattern(targetSurf, Matrix::Translation(targetOffset.x, targetOffset.y));
    aContext.SetPattern(pattern);
    aContext.Paint();
  }

  aContext.Restore();
}