CGContextRef
gfxQuartzNativeDrawing::BeginNativeDrawing()
{
  NS_ASSERTION(!mCGContext, "BeginNativeDrawing called when drawing already in progress");

  DrawTarget *dt = mDrawTarget;
  if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() ||
      dt->GetBackendType() != BackendType::SKIA || dt->IsRecording()) {
    // We need a DrawTarget that we can get a CGContextRef from:
    Matrix transform = dt->GetTransform();

    mNativeRect = transform.TransformBounds(mNativeRect);
    mNativeRect.RoundOut();
    if (mNativeRect.IsEmpty()) {
      return nullptr;
    }

    mTempDrawTarget =
      Factory::CreateDrawTarget(BackendType::SKIA,
                                IntSize::Truncate(mNativeRect.width, mNativeRect.height),
                                SurfaceFormat::B8G8R8A8);
    if (!mTempDrawTarget) {
      return nullptr;
    }

    transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
    mTempDrawTarget->SetTransform(transform);

    dt = mTempDrawTarget;
  } else {
    // Clip the DT in case BorrowedCGContext needs to create a new layer.
    // This prevents it from creating a new layer the size of the window.
    // But make sure that this clip is device pixel aligned.
    Matrix transform = dt->GetTransform();

    Rect deviceRect = transform.TransformBounds(mNativeRect);
    deviceRect.RoundOut();
    mNativeRect = transform.Inverse().TransformBounds(deviceRect);
    mDrawTarget->PushClipRect(mNativeRect);
  }

  MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
  mCGContext = mBorrowedContext.Init(dt);

  if (NS_WARN_IF(!mCGContext)) {
    // Failed borrowing CG context, so we need to clean up.
    if (!mTempDrawTarget) {
      mDrawTarget->PopClip();
    }
    return nullptr;
  }

  return mCGContext;
}
Example #2
0
void
DrawTargetD2D1::Mask(const Pattern &aSource,
                     const Pattern &aMask,
                     const DrawOptions &aOptions)
{
  PrepareForDrawing(aOptions.mCompositionOp, aSource);

  RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions.mAlpha);
  RefPtr<ID2D1Brush> mask = CreateBrushForPattern(aMask, 1.0f);
  mDC->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr,
                                       D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
                                       D2D1::IdentityMatrix(),
                                       1.0f, mask),
                 nullptr);

  Rect rect(0, 0, (Float)mSize.width, (Float)mSize.height);
  Matrix mat = mTransform;
  mat.Invert();
  
  mDC->FillRectangle(D2DRect(mat.TransformBounds(rect)), source);

  mDC->PopLayer();

  FinalizeDrawing(aOptions.mCompositionOp, aSource);
}
Example #3
0
bool
SVGLineElement::GetGeometryBounds(
  Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform)
{
  float x1, y1, x2, y2;
  GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);

  if (aStrokeOptions.mLineWidth <= 0) {
    *aBounds = Rect(aTransform * Point(x1, y1), Size());
    aBounds->ExpandToEnclose(aTransform * Point(x2, y2));
    return true;
  }

  if (aStrokeOptions.mLineCap == CapStyle::ROUND) {
    if (!aTransform.IsRectilinear()) {
      // TODO: handle this case.
      return false;
    }
    Rect bounds(Point(x1, y1), Size());
    bounds.ExpandToEnclose(Point(x2, y2));
    bounds.Inflate(aStrokeOptions.mLineWidth / 2.f);
    *aBounds = aTransform.TransformBounds(bounds);
    return true;
  }

  Float length = Float(NS_hypot(x2 - x1, y2 - y1));
  Float xDelta;
  Float yDelta;

  if (aStrokeOptions.mLineCap == CapStyle::BUTT) {
    if (length == 0.f) {
      xDelta = yDelta = 0.f;
    } else {
      Float ratio = aStrokeOptions.mLineWidth / 2.f / length;
      xDelta = ratio * (y2 - y1);
      yDelta = ratio * (x2 - x1);
    }
  } else {
    MOZ_ASSERT(aStrokeOptions.mLineCap == CapStyle::SQUARE);
    if (length == 0.f) {
      xDelta = yDelta = aStrokeOptions.mLineWidth / 2.f;
    } else {
      Float ratio = aStrokeOptions.mLineWidth / 2.f / length;
      xDelta = yDelta = ratio * (fabs(y2 - y1) + fabs(x2 - x1));
    }
  }

  Point points[4];

  points[0] = Point(x1 - xDelta, y1 - yDelta);
  points[1] = Point(x1 + xDelta, y1 + yDelta);
  points[2] = Point(x2 + xDelta, y2 + yDelta);
  points[3] = Point(x2 - xDelta, y2 - yDelta);

  *aBounds = Rect(aTransform * points[0], Size());
  for (uint32_t i = 1; i < 4; ++i) {
    aBounds->ExpandToEnclose(aTransform * points[i]);
  }
  return true;
}
Example #4
0
bool
nsSVGPolyElement::GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
                                    CapStyle aCapStyle, const Matrix& aTransform)
{
  const SVGPointList &points = mPoints.GetAnimValue();

  if (!points.Length()) {
    // Rendering of the element is disabled
    aBounds->SetEmpty();
    return true;
  }

  if (aStrokeWidth > 0) {
    // We don't handle stroke-miterlimit etc. yet
    return false;
  }

  if (aTransform.IsRectilinear()) {
    // We can avoid transforming each point and just transform the result.
    // Important for large point lists.
    Rect bounds(points[0], Size());
    for (uint32_t i = 1; i < points.Length(); ++i) {
      bounds.ExpandToEnclose(points[i]);
    }
    *aBounds = aTransform.TransformBounds(bounds);
  } else {
    *aBounds = Rect(aTransform * points[0], Size());
    for (uint32_t i = 1; i < points.Length(); ++i) {
      aBounds->ExpandToEnclose(aTransform * points[i]);
    }
  }
  return true;
}
void
gfxContext::Paint(gfxFloat alpha)
{
  PROFILER_LABEL("gfxContext", "Paint",
    js::ProfileEntry::Category::GRAPHICS);

  AzureState &state = CurrentState();

  if (state.sourceSurface && !state.sourceSurfCairo &&
      !state.patternTransformChanged)
  {
    // This is the case where a PopGroupToSource has been done and this
    // paint is executed without changing the transform or the source.
    Matrix oldMat = mDT->GetTransform();

    IntSize surfSize = state.sourceSurface->GetSize();

    mDT->SetTransform(Matrix::Translation(-state.deviceOffset.x,
                                          -state.deviceOffset.y));

    mDT->DrawSurface(state.sourceSurface,
                     Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)),
                     Rect(Point(), Size(surfSize.width, surfSize.height)),
                     DrawSurfaceOptions(), DrawOptions(alpha, GetOp()));
    mDT->SetTransform(oldMat);
    return;
  }

  Matrix mat = mDT->GetTransform();
  mat.Invert();
  Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));

  mDT->FillRect(paintRect, PatternFromState(this),
                DrawOptions(Float(alpha), GetOp()));
}
gfxRect
gfxContext::DeviceToUser(const gfxRect& rect) const
{
  Matrix matrix = mTransform;
  matrix.Invert();
  return ThebesRect(matrix.TransformBounds(ToRect(rect)));
}
Example #7
0
Rect
PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
                         const Matrix &aTransform) const
{
  // 10.7 has CGPathCreateCopyByStrokingPath which we could use
  // instead of this scratch context business
  CGContextRef cg = ScratchContext();

  CGContextSaveGState(cg);

  CGContextBeginPath(cg);
  CGContextAddPath(cg, mPath);

  SetStrokeOptions(cg, aStrokeOptions);

  CGContextReplacePathWithStrokedPath(cg);
  Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg));

  CGContextRestoreGState(cg);

  if (!bounds.IsFinite()) {
    return Rect();
  }

  return aTransform.TransformBounds(bounds);
}
bool
SVGPolyElement::GetGeometryBounds(Rect* aBounds,
                                  const StrokeOptions& aStrokeOptions,
                                  const Matrix& aToBoundsSpace,
                                  const Matrix* aToNonScalingStrokeSpace)
{
  const SVGPointList &points = mPoints.GetAnimValue();

  if (!points.Length()) {
    // Rendering of the element is disabled
    aBounds->SetEmpty();
    return true;
  }

  if (aStrokeOptions.mLineWidth > 0 || aToNonScalingStrokeSpace) {
    // We don't handle non-scaling-stroke or stroke-miterlimit etc. yet
    return false;
  }

  if (aToBoundsSpace.IsRectilinear()) {
    // We can avoid transforming each point and just transform the result.
    // Important for large point lists.
    Rect bounds(points[0], Size());
    for (uint32_t i = 1; i < points.Length(); ++i) {
      bounds.ExpandToEnclose(points[i]);
    }
    *aBounds = aToBoundsSpace.TransformBounds(bounds);
  } else {
    *aBounds = Rect(aToBoundsSpace.TransformPoint(points[0]), Size());
    for (uint32_t i = 1; i < points.Length(); ++i) {
      aBounds->ExpandToEnclose(aToBoundsSpace.TransformPoint(points[i]));
    }
  }
  return true;
}
Example #9
0
bool
PathSkia::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
                              const Point &aPoint,
                              const Matrix &aTransform) const
{
  Matrix inverse = aTransform;
  inverse.Invert();
  Point transformed = inverse * aPoint;

  SkPaint paint;
  StrokeOptionsToPaint(paint, aStrokeOptions);

  SkPath strokePath;
  paint.getFillPath(mPath, &strokePath);

  Rect bounds = aTransform.TransformBounds(SkRectToRect(strokePath.getBounds()));

  if (aPoint.x < bounds.x || aPoint.y < bounds.y ||
      aPoint.x > bounds.XMost() || aPoint.y > bounds.YMost()) {
    return false;
  }

  SkRegion pointRect;
  pointRect.setRect(int32_t(SkFloatToScalar(transformed.x - 1.f)),
                    int32_t(SkFloatToScalar(transformed.y - 1.f)),
                    int32_t(SkFloatToScalar(transformed.x + 1.f)),
                    int32_t(SkFloatToScalar(transformed.y + 1.f)));

  SkRegion pathRegion;
  
  return pathRegion.setPath(strokePath, pointRect);
}
Example #10
0
//XXX: what should these functions return for an empty path?
// currently they return CGRectNull {inf,inf, 0, 0}
Rect
PathCG::GetBounds(const Matrix &aTransform) const
{
  //XXX: are these bounds tight enough
  Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath));

  //XXX: currently this returns the bounds of the transformed bounds
  // this is strictly looser than the bounds of the transformed path
  return aTransform.TransformBounds(bounds);
}
Example #11
0
Rect
PathCairo::GetBounds(const Matrix &aTransform) const
{
  CairoTempMatrix(*mPathContext, mTransform);

  double x1, y1, x2, y2;

  cairo_path_extents(*mPathContext, &x1, &y1, &x2, &y2);
  Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1));
  return aTransform.TransformBounds(bounds);
}
Example #12
0
Rect
PathCairo::GetBounds(const Matrix &aTransform) const
{
  EnsureContainingContext();

  double x1, y1, x2, y2;

  cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2);
  Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1));
  return aTransform.TransformBounds(bounds);
}
Example #13
0
Rect
PathSkia::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
                           const Matrix &aTransform) const
{
  SkPaint paint;
  StrokeOptionsToPaint(paint, aStrokeOptions);
  
  SkPath result;
  paint.getFillPath(mPath, &result);

  Rect bounds = SkRectToRect(result.getBounds());
  return aTransform.TransformBounds(bounds);
}
Example #14
0
Rect
PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
                            const Matrix &aTransform) const
{
  EnsureContainingContext();

  double x1, y1, x2, y2;

  SetCairoStrokeOptions(mContainingContext, aStrokeOptions);

  cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2);
  Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1));
  return aTransform.TransformBounds(bounds);
}
gfxRect
gfxContext::GetClipExtents()
{
  Rect rect = GetAzureDeviceSpaceClipBounds();

  if (rect.width == 0 || rect.height == 0) {
    return gfxRect(0, 0, 0, 0);
  }

  Matrix mat = mTransform;
  mat.Invert();
  rect = mat.TransformBounds(rect);

  return ThebesRect(rect);
}
Example #16
0
/* For the purposes of the update/invalidation logic pretend to
   be a rectangle. */
bool
SVGImageElement::GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
                                   CapStyle aCapStyle, const Matrix& aTransform)
{
  Rect rect;
  GetAnimatedLengthValues(&rect.x, &rect.y, &rect.width,
                          &rect.height, nullptr);

  if (rect.IsEmpty()) {
    // Rendering of the element disabled
    rect.SetEmpty(); // Make sure width/height are zero and not negative
  }

  *aBounds = aTransform.TransformBounds(rect);
  return true;
}
Example #17
0
/* For the purposes of the update/invalidation logic pretend to
   be a rectangle. */
bool
SVGImageElement::GetGeometryBounds(Rect* aBounds,
                                   const StrokeOptions& aStrokeOptions,
                                   const Matrix& aToBoundsSpace,
                                   const Matrix* aToNonScalingStrokeSpace)
{
  Rect rect;
  GetAnimatedLengthValues(&rect.x, &rect.y, &rect.width,
                          &rect.height, nullptr);

  if (rect.IsEmpty()) {
    // Rendering of the element disabled
    rect.SetEmpty(); // Make sure width/height are zero and not negative
  }

  *aBounds = aToBoundsSpace.TransformBounds(rect);
  return true;
}
/* SVG font code can change the transform after having set the pattern on the
 * context. When the pattern is set it is in user space, if the transform is
 * changed after doing so the pattern needs to be converted back into userspace.
 * We just store the old pattern transform here so that we only do the work
 * needed here if the pattern is actually used.
 * We need to avoid doing this when this ChangeTransform comes from a restore,
 * since the current pattern and the current transform are both part of the
 * state we know the new CurrentState()'s values are valid. But if we assume
 * a change they might become invalid since patternTransformChanged is part of
 * the state and might be false for the restored AzureState.
 */
void
gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform)
{
  AzureState &state = CurrentState();

  if (aUpdatePatternTransform && (state.pattern || state.sourceSurface)
      && !state.patternTransformChanged) {
    state.patternTransform = GetDTTransform();
    state.patternTransformChanged = true;
  }

  if (mPathIsRect) {
    Matrix invMatrix = aNewMatrix;
    
    invMatrix.Invert();

    Matrix toNewUS = mTransform * invMatrix;

    if (toNewUS.IsRectilinear()) {
      mRect = toNewUS.TransformBounds(mRect);
      mRect.NudgeToIntegers();
    } else {
      mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);

      mPathBuilder->MoveTo(toNewUS * mRect.TopLeft());
      mPathBuilder->LineTo(toNewUS * mRect.TopRight());
      mPathBuilder->LineTo(toNewUS * mRect.BottomRight());
      mPathBuilder->LineTo(toNewUS * mRect.BottomLeft());
      mPathBuilder->Close();

      mPathIsRect = false;
    }

    // No need to consider the transform changed now!
    mTransformChanged = false;
  } else if ((mPath || mPathBuilder) && !mTransformChanged) {
    mTransformChanged = true;
    mPathTransform = mTransform;
  }

  mTransform = aNewMatrix;

  mDT->SetTransform(GetDTTransform());
}
SVGBBox
nsSVGForeignObjectFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
                                             uint32_t aFlags)
{
  SVGForeignObjectElement *content =
    static_cast<SVGForeignObjectElement*>(mContent);

  float x, y, w, h;
  content->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);

  if (w < 0.0f) w = 0.0f;
  if (h < 0.0f) h = 0.0f;

  if (aToBBoxUserspace.IsSingular()) {
    // XXX ReportToConsole
    return SVGBBox();
  }
  return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
}
Example #20
0
ScissorClip::ScissorClip(DrawTargetNVpr* aDrawTarget,
                         TemporaryRef<ScissorClip> aPrevious,
                         const Matrix& aTransform,
                         const Rect& aClipRect, bool& aSuccess)
  : Clip(aDrawTarget, aPrevious)
{
  aSuccess = false;

  if (!aTransform.IsRectilinear()) {
    return;
  }

  if (!aTransform.TransformBounds(aClipRect).ToIntRect(&mScissorRect)) {
    return;
  }

  if (mPrevious) {
    mScissorRect.IntersectRect(mScissorRect, mPrevious->mScissorRect);
  }

  aSuccess = true;
}
SVGBBox
nsSVGInnerSVGFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
                                        uint32_t aFlags)
{
  // XXXjwatt It seems like authors would want the result to be clipped by the
  // viewport we establish if IsScrollableOverflow() is true.  We should
  // consider doing that.  See bug 1350755.

  SVGBBox bbox;

  if (aFlags & nsSVGUtils::eForGetClientRects) {
    // XXXjwatt For consistency with the old code this code includes the
    // viewport we establish in the result, but only includes the bounds of our
    // descendants if they are not clipped to that viewport.  However, this is
    // both inconsistent with Chrome and with the specs.  See bug 1350755.
    // Ideally getClientRects/getBoundingClientRect should be consistent with
    // getBBox.
    float x, y, w, h;
    static_cast<SVGSVGElement*>(mContent)->
      GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
    if (w < 0.0f) w = 0.0f;
    if (h < 0.0f) h = 0.0f;
    Rect viewport(x, y, w, h);
    bbox = aToBBoxUserspace.TransformBounds(viewport);
    if (StyleDisplay()->IsScrollableOverflow()) {
      return bbox;
    }
    // Else we're not clipping to our viewport so we fall through and include
    // the bounds of our children.
  }

  SVGBBox descendantsBbox =
    nsSVGDisplayContainerFrame::GetBBoxContribution(aToBBoxUserspace, aFlags);

  bbox.UnionEdges(descendantsBbox);

  return bbox;
}
Example #22
0
void
SVGContentUtils::RectilinearGetStrokeBounds(const Rect& aRect,
                                            const Matrix& aToBoundsSpace,
                                            const Matrix& aToNonScalingStrokeSpace,
                                            float aStrokeWidth,
                                            Rect* aBounds)
{
  MOZ_ASSERT(aToBoundsSpace.IsRectilinear(),
             "aToBoundsSpace must be rectilinear");
  MOZ_ASSERT(aToNonScalingStrokeSpace.IsRectilinear(),
             "aToNonScalingStrokeSpace must be rectilinear");

  Matrix nonScalingToSource = aToNonScalingStrokeSpace.Inverse();
  Matrix nonScalingToBounds = nonScalingToSource * aToBoundsSpace;

  *aBounds = aToBoundsSpace.TransformBounds(aRect);

  // Compute the amounts dx and dy that nonScalingToBounds scales a half-width
  // stroke in the x and y directions, and then inflate aBounds by those amounts
  // so that when aBounds is transformed back to non-scaling-stroke space
  // it will map onto the correct stroked bounds.

  Float dx = 0.0f;
  Float dy = 0.0f;
  // nonScalingToBounds is rectilinear, so either _12 and _21 are zero or _11
  // and _22 are zero, and in each case the non-zero entries (from among _11,
  // _12, _21, _22) simply scale the stroke width in the x and y directions.
  if (FuzzyEqual(nonScalingToBounds._12, 0) &&
      FuzzyEqual(nonScalingToBounds._21, 0)) {
    dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._11);
    dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._22);
  } else {
    dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._21);
    dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._12);
  }

  aBounds->Inflate(dx, dy);
}
Example #23
0
Rect
PathSkia::GetBounds(const Matrix &aTransform) const
{
  Rect bounds = SkRectToRect(mPath.getBounds());
  return aTransform.TransformBounds(bounds);
}
already_AddRefed<SourceSurface>
nsSVGPatternFrame::PaintPattern(const DrawTarget* aDrawTarget,
                                Matrix* patternMatrix,
                                const Matrix &aContextMatrix,
                                nsIFrame *aSource,
                                nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
                                float aGraphicOpacity,
                                const gfxRect *aOverrideBounds,
                                imgDrawingParams& aImgParams)
{
  /*
   * General approach:
   *    Set the content geometry stuff
   *    Calculate our bbox (using x,y,width,height & patternUnits &
   *                        patternTransform)
   *    Create the surface
   *    Calculate the content transformation matrix
   *    Get our children (we may need to get them from another Pattern)
   *    Call SVGPaint on all of our children
   *    Return
   */

  nsSVGPatternFrame* patternWithChildren = GetPatternWithChildren();
  if (!patternWithChildren) {
    // Either no kids or a bad reference
    return nullptr;
  }
  nsIFrame* firstKid = patternWithChildren->mFrames.FirstChild();

  const nsSVGViewBox& viewBox = GetViewBox();

  uint16_t patternContentUnits =
    GetEnumValue(SVGPatternElement::PATTERNCONTENTUNITS);
  uint16_t patternUnits =
    GetEnumValue(SVGPatternElement::PATTERNUNITS);

  /*
   * Get the content geometry information.  This is a little tricky --
   * our parent is probably a <defs>, but we are rendering in the context
   * of some geometry source.  Our content geometry information needs to
   * come from our rendering parent as opposed to our content parent.  We
   * get that information from aSource, which is passed to us from the
   * backend renderer.
   *
   * There are three "geometries" that we need:
   *   1) The bounding box for the pattern.  We use this to get the
   *      width and height for the surface, and as the return to
   *      GetBBox.
   *   2) The transformation matrix for the pattern.  This is not *quite*
   *      the same as the canvas transformation matrix that we will
   *      provide to our rendering children since we "fudge" it a little
   *      to get the renderer to handle the translations correctly for us.
   *   3) The CTM that we return to our children who make up the pattern.
   */

  // Get all of the information we need from our "caller" -- i.e.
  // the geometry that is being rendered with a pattern
  gfxRect callerBBox;
  if (NS_FAILED(GetTargetGeometry(&callerBBox,
                                  viewBox,
                                  patternContentUnits, patternUnits,
                                  aSource,
                                  aContextMatrix,
                                  aOverrideBounds))) {
    return nullptr;
  }

  // Construct the CTM that we will provide to our children when we
  // render them into the tile.
  gfxMatrix ctm = ConstructCTM(viewBox, patternContentUnits, patternUnits,
                               callerBBox, aContextMatrix, aSource);
  if (ctm.IsSingular()) {
    return nullptr;
  }

  if (patternWithChildren->mCTM) {
    *patternWithChildren->mCTM = ctm;
  } else {
    patternWithChildren->mCTM = new gfxMatrix(ctm);
  }

  // Get the bounding box of the pattern.  This will be used to determine
  // the size of the surface, and will also be used to define the bounding
  // box for the pattern tile.
  gfxRect bbox = GetPatternRect(patternUnits, callerBBox, aContextMatrix, aSource);
  if (bbox.Width() <= 0.0 || bbox.Height() <= 0.0) {
    return nullptr;
  }

  // Get the pattern transform
  Matrix patternTransform = ToMatrix(GetPatternTransform());

  // revert the vector effect transform so that the pattern appears unchanged
  if (aFillOrStroke == &nsStyleSVG::mStroke) {
    gfxMatrix userToOuterSVG;
    if (nsSVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
      patternTransform *= ToMatrix(userToOuterSVG);
      if (patternTransform.IsSingular()) {
        NS_WARNING("Singular matrix painting non-scaling-stroke");
        return nullptr;
      }
    }
  }

  // Get the transformation matrix that we will hand to the renderer's pattern
  // routine.
  *patternMatrix = GetPatternMatrix(patternUnits, patternTransform,
                                    bbox, callerBBox, aContextMatrix);
  if (patternMatrix->IsSingular()) {
    return nullptr;
  }

  // Now that we have all of the necessary geometries, we can
  // create our surface.
  gfxRect transformedBBox = ThebesRect(patternTransform.TransformBounds(ToRect(bbox)));

  bool resultOverflows;
  IntSize surfaceSize =
    nsSVGUtils::ConvertToSurfaceSize(
      transformedBBox.Size(), &resultOverflows);

  // 0 disables rendering, < 0 is an error
  if (surfaceSize.width <= 0 || surfaceSize.height <= 0) {
    return nullptr;
  }

  gfxFloat patternWidth = bbox.Width();
  gfxFloat patternHeight = bbox.Height();

  if (resultOverflows ||
      patternWidth != surfaceSize.width ||
      patternHeight != surfaceSize.height) {
    // scale drawing to pattern surface size
    gfxMatrix tempTM =
      gfxMatrix(surfaceSize.width / patternWidth, 0.0,
                0.0, surfaceSize.height / patternHeight,
                0.0, 0.0);
    patternWithChildren->mCTM->PreMultiply(tempTM);

    // and rescale pattern to compensate
    patternMatrix->PreScale(patternWidth / surfaceSize.width,
                            patternHeight / surfaceSize.height);
  }

  RefPtr<DrawTarget> dt =
    aDrawTarget->CreateSimilarDrawTarget(surfaceSize, SurfaceFormat::B8G8R8A8);
  if (!dt || !dt->IsValid()) {
    return nullptr;
  }
  dt->ClearRect(Rect(0, 0, surfaceSize.width, surfaceSize.height));

  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
  MOZ_ASSERT(ctx); // already checked the draw target above

  if (aGraphicOpacity != 1.0f) {
    ctx->Save();
    ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aGraphicOpacity);
  }

  // OK, now render -- note that we use "firstKid", which
  // we got at the beginning because it takes care of the
  // referenced pattern situation for us

  if (aSource->IsFrameOfType(nsIFrame::eSVGGeometry)) {
    // Set the geometrical parent of the pattern we are rendering
    patternWithChildren->mSource = static_cast<SVGGeometryFrame*>(aSource);
  }

  // Delay checking NS_FRAME_DRAWING_AS_PAINTSERVER bit until here so we can
  // give back a clear surface if there's a loop
  if (!(patternWithChildren->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)) {
    AutoSetRestorePaintServerState paintServer(patternWithChildren);
    for (nsIFrame* kid = firstKid; kid;
         kid = kid->GetNextSibling()) {
      // The CTM of each frame referencing us can be different
      nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
      if (SVGFrame) {
        SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
      }
      gfxMatrix tm = *(patternWithChildren->mCTM);
      if (kid->GetContent()->IsSVGElement()) {
        tm = static_cast<nsSVGElement*>(kid->GetContent())->
               PrependLocalTransformsTo(tm, eUserSpaceToParent);
      }

      nsSVGUtils::PaintFrameWithEffects(kid, *ctx, tm, aImgParams);
    }
  }

  patternWithChildren->mSource = nullptr;

  if (aGraphicOpacity != 1.0f) {
    ctx->PopGroupAndBlend();
    ctx->Restore();
  }

  // caller now owns the surface
  return dt->Snapshot();
}
bool
SVGLineElement::GetGeometryBounds(Rect* aBounds,
                                  const StrokeOptions& aStrokeOptions,
                                  const Matrix& aToBoundsSpace,
                                  const Matrix* aToNonScalingStrokeSpace)
{
  float x1, y1, x2, y2;
  GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);

  if (aStrokeOptions.mLineWidth <= 0) {
    *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x1, y1)), Size());
    aBounds->ExpandToEnclose(aToBoundsSpace.TransformPoint(Point(x2, y2)));
    return true;
  }

  // transform from non-scaling-stroke space to the space in which we compute
  // bounds
  Matrix nonScalingToBounds;
  if (aToNonScalingStrokeSpace) {
    MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular());
    Matrix nonScalingToUser = aToNonScalingStrokeSpace->Inverse();
    nonScalingToBounds = nonScalingToUser * aToBoundsSpace;
  }

  if (aStrokeOptions.mLineCap == CapStyle::ROUND) {
    if (!aToBoundsSpace.IsRectilinear() ||
        (aToNonScalingStrokeSpace &&
         !aToNonScalingStrokeSpace->IsRectilinear())) {
      // TODO: handle this case.
      return false;
    }
    Rect bounds(Point(x1, y1), Size());
    bounds.ExpandToEnclose(Point(x2, y2));
    if (aToNonScalingStrokeSpace) {
      bounds = aToNonScalingStrokeSpace->TransformBounds(bounds);
      bounds.Inflate(aStrokeOptions.mLineWidth / 2.f);
      *aBounds = nonScalingToBounds.TransformBounds(bounds);
    } else {
      bounds.Inflate(aStrokeOptions.mLineWidth / 2.f);
      *aBounds = aToBoundsSpace.TransformBounds(bounds);
    }
    return true;
  }

  // Handle butt and square linecap, normal and non-scaling stroke cases
  // together: start with endpoints (x1, y1), (x2, y2) in the stroke space,
  // compute the four corners of the stroked line, transform the corners to
  // bounds space, and compute bounds there.

  if (aToNonScalingStrokeSpace) {
    Point nonScalingSpaceP1, nonScalingSpaceP2;
    nonScalingSpaceP1 = aToNonScalingStrokeSpace->TransformPoint(Point(x1, y1));
    nonScalingSpaceP2 = aToNonScalingStrokeSpace->TransformPoint(Point(x2, y2));
    x1 = nonScalingSpaceP1.x;
    y1 = nonScalingSpaceP1.y;
    x2 = nonScalingSpaceP2.x;
    y2 = nonScalingSpaceP2.y;
  }

  Float length = Float(NS_hypot(x2 - x1, y2 - y1));
  Float xDelta;
  Float yDelta;
  Point points[4];

  if (aStrokeOptions.mLineCap == CapStyle::BUTT) {
    if (length == 0.f) {
      xDelta = yDelta = 0.f;
    } else {
      Float ratio = aStrokeOptions.mLineWidth / 2.f / length;
      xDelta = ratio * (y2 - y1);
      yDelta = ratio * (x2 - x1);
    }
    points[0] = Point(x1 - xDelta, y1 + yDelta);
    points[1] = Point(x1 + xDelta, y1 - yDelta);
    points[2] = Point(x2 + xDelta, y2 - yDelta);
    points[3] = Point(x2 - xDelta, y2 + yDelta);
  } else {
    MOZ_ASSERT(aStrokeOptions.mLineCap == CapStyle::SQUARE);
    if (length == 0.f) {
      xDelta = yDelta = aStrokeOptions.mLineWidth / 2.f;
      points[0] = Point(x1 - xDelta, y1 + yDelta);
      points[1] = Point(x1 - xDelta, y1 - yDelta);
      points[2] = Point(x1 + xDelta, y1 - yDelta);
      points[3] = Point(x1 + xDelta, y1 + yDelta);
    } else {
      Float ratio = aStrokeOptions.mLineWidth / 2.f / length;
      yDelta = ratio * (x2 - x1);
      xDelta = ratio * (y2 - y1);
      points[0] = Point(x1 - yDelta - xDelta, y1 - xDelta + yDelta);
      points[1] = Point(x1 - yDelta + xDelta, y1 - xDelta - yDelta);
      points[2] = Point(x2 + yDelta + xDelta, y2 + xDelta - yDelta);
      points[3] = Point(x2 + yDelta - xDelta, y2 + xDelta + yDelta);
    }
  }

  const Matrix& toBoundsSpace = aToNonScalingStrokeSpace ?
    nonScalingToBounds : aToBoundsSpace;

  *aBounds = Rect(toBoundsSpace.TransformPoint(points[0]), Size());
  for (uint32_t i = 1; i < 4; ++i) {
    aBounds->ExpandToEnclose(toBoundsSpace.TransformPoint(points[i]));
  }

  return true;
}