Last active
June 12, 2023 14:52
-
-
Save jrmuizel/d5560d35c46c102f20f4c681d537f713 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp | |
index d84935f7c3f00..712ba15324623 100644 | |
--- a/dom/canvas/CanvasRenderingContext2D.cpp | |
+++ b/dom/canvas/CanvasRenderingContext2D.cpp | |
@@ -1054,21 +1054,20 @@ CanvasRenderingContext2D::CanvasRenderingContext2D( | |
mOpaqueAttrValue(false), | |
mContextAttributesHasAlpha(true), | |
mOpaque(false), | |
mResetLayer(true), | |
mIPC(false), | |
mHasPendingStableStateCallback(false), | |
mIsEntireFrameInvalid(false), | |
mPredictManyRedrawCalls(false), | |
mFrameCaptureState(FrameCaptureState::CLEAN, | |
"CanvasRenderingContext2D::mFrameCaptureState"), | |
- mPathTransformWillUpdate(false), | |
mInvalidateCount(0), | |
mWriteOnly(false) { | |
sNumLivingContexts.infallibleInit(); | |
sErrorTarget.infallibleInit(); | |
sNumLivingContexts.set(sNumLivingContexts.get() + 1); | |
} | |
CanvasRenderingContext2D::~CanvasRenderingContext2D() { | |
RemovePostRefreshObserver(); | |
RemoveShutdownObserver(); | |
@@ -1511,22 +1510,20 @@ bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, | |
Redraw(); | |
mFrameCaptureState = captureState; | |
return true; | |
} | |
void CanvasRenderingContext2D::SetInitialState() { | |
// Set up the initial canvas defaults | |
mPathBuilder = nullptr; | |
mPath = nullptr; | |
- mDSPathBuilder = nullptr; | |
- mPathTransformWillUpdate = false; | |
mStyleStack.Clear(); | |
ContextState* state = mStyleStack.AppendElement(); | |
state->globalAlpha = 1.0; | |
state->colorStyles[Style::FILL] = NS_RGB(0, 0, 0); | |
state->colorStyles[Style::STROKE] = NS_RGB(0, 0, 0); | |
state->shadowColor = NS_RGBA(0, 0, 0, 0); | |
} | |
@@ -1954,90 +1951,106 @@ void CanvasRenderingContext2D::Save() { | |
// reasonable code. | |
mStyleStack.RemoveElementAt(0); | |
} | |
} | |
void CanvasRenderingContext2D::Restore() { | |
if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) { | |
return; | |
} | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
return; | |
} | |
for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) { | |
if (clipOrTransform.IsClip()) { | |
mTarget->PopClip(); | |
} | |
} | |
mStyleStack.RemoveLastElement(); | |
- mTarget->SetTransform(CurrentState().transform); | |
+ Matrix newMatrix = CurrentState().transform; | |
+ Matrix adjustMatrix = mTarget->GetTransform(); | |
+ | |
+ Matrix inverse = newMatrix; | |
+ if (inverse.Invert()) { | |
+ adjustMatrix = adjustMatrix * inverse; | |
+ } | |
+ TransformWillUpdate(adjustMatrix); | |
+ | |
+ mTarget->SetTransform(newMatrix); | |
} | |
// | |
// transformations | |
// | |
void CanvasRenderingContext2D::Scale(double aX, double aY, | |
ErrorResult& aError) { | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
return; | |
} | |
+ TransformWillUpdate(Matrix::Scaling(1/aX, 1/aY)); | |
Matrix newMatrix = mTarget->GetTransform(); | |
newMatrix.PreScale(aX, aY); | |
SetTransformInternal(newMatrix); | |
} | |
void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) { | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
return; | |
} | |
+ TransformWillUpdate(Matrix::Rotation(-aAngle)); | |
Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform(); | |
SetTransformInternal(newMatrix); | |
} | |
void CanvasRenderingContext2D::Translate(double aX, double aY, | |
ErrorResult& aError) { | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
return; | |
} | |
+ TransformWillUpdate(Matrix::Translation(-aX, -aY)); | |
Matrix newMatrix = mTarget->GetTransform(); | |
newMatrix.PreTranslate(aX, aY); | |
SetTransformInternal(newMatrix); | |
} | |
void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, | |
double aM22, double aDx, double aDy, | |
ErrorResult& aError) { | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
return; | |
} | |
Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); | |
+ Matrix inverse = newMatrix; | |
+ if (inverse.Invert()) { | |
+ TransformWillUpdate(inverse); | |
+ } | |
newMatrix *= mTarget->GetTransform(); | |
SetTransformInternal(newMatrix); | |
} | |
already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform( | |
ErrorResult& aError) { | |
EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
@@ -2045,41 +2058,57 @@ already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform( | |
} | |
RefPtr<DOMMatrix> matrix = | |
new DOMMatrix(GetParentObject(), mTarget->GetTransform()); | |
return matrix.forget(); | |
} | |
void CanvasRenderingContext2D::SetTransform(double aM11, double aM12, | |
double aM21, double aM22, | |
double aDx, double aDy, | |
ErrorResult& aError) { | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
return; | |
} | |
- SetTransformInternal(Matrix(aM11, aM12, aM21, aM22, aDx, aDy)); | |
+ Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); | |
+ Matrix adjustMatrix = mTarget->GetTransform(); | |
+ | |
+ Matrix inverse = newMatrix; | |
+ if (inverse.Invert()) { | |
+ adjustMatrix = adjustMatrix * inverse; | |
+ } | |
+ TransformWillUpdate(adjustMatrix); | |
+ SetTransformInternal(newMatrix); | |
} | |
void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit, | |
ErrorResult& aError) { | |
- TransformWillUpdate(); | |
+ EnsureTarget(); | |
if (!IsTargetValid()) { | |
aError.Throw(NS_ERROR_FAILURE); | |
return; | |
} | |
RefPtr<DOMMatrixReadOnly> matrix = | |
DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError); | |
if (!aError.Failed()) { | |
- SetTransformInternal(Matrix(*(matrix->GetInternal2D()))); | |
+ Matrix newMatrix = Matrix(*(matrix->GetInternal2D())); | |
+ Matrix adjustMatrix = mTarget->GetTransform(); | |
+ | |
+ Matrix inverse = newMatrix; | |
+ if (inverse.Invert()) { | |
+ adjustMatrix = adjustMatrix * inverse; | |
+ } | |
+ TransformWillUpdate(adjustMatrix); | |
+ SetTransformInternal(newMatrix); | |
} | |
} | |
void CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform) { | |
if (!aTransform.IsFinite()) { | |
return; | |
} | |
// Save the transform in the clip stack to be able to replay clips properly. | |
auto& clipsAndTransforms = CurrentState().clipsAndTransforms; | |
@@ -2941,22 +2970,20 @@ void CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW, | |
Redraw(); | |
} | |
// | |
// path bits | |
// | |
void CanvasRenderingContext2D::BeginPath() { | |
mPath = nullptr; | |
mPathBuilder = nullptr; | |
- mDSPathBuilder = nullptr; | |
- mPathTransformWillUpdate = false; | |
} | |
void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) { | |
EnsureUserSpacePath(aWinding); | |
if (!mPath || mPath->IsEmpty()) { | |
return; | |
} | |
const bool needBounds = NeedToCalculateBounds(); | |
@@ -3197,31 +3224,21 @@ void CanvasRenderingContext2D::Clip(const CanvasPath& aPath, | |
void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2, | |
double aY2, double aRadius, | |
ErrorResult& aError) { | |
if (aRadius < 0) { | |
return aError.ThrowIndexSizeError("Negative radius"); | |
} | |
EnsureWritablePath(); | |
// Current point in user space! | |
- Point p0; | |
- if (mPathBuilder) { | |
- p0 = mPathBuilder->CurrentPoint(); | |
- } else { | |
- Matrix invTransform = mTarget->GetTransform(); | |
- if (!invTransform.Invert()) { | |
- return; | |
- } | |
- | |
- p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint()); | |
- } | |
+ Point p0 = mPathBuilder->CurrentPoint(); | |
Point p1(aX1, aY1); | |
Point p2(aX2, aY2); | |
if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) { | |
return; | |
} | |
// Execute these calculations in double precision to avoid cumulative | |
// rounding errors. | |
@@ -3274,56 +3291,41 @@ void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2, | |
void CanvasRenderingContext2D::Arc(double aX, double aY, double aR, | |
double aStartAngle, double aEndAngle, | |
bool aAnticlockwise, ErrorResult& aError) { | |
if (aR < 0.0) { | |
return aError.ThrowIndexSizeError("Negative radius"); | |
} | |
EnsureWritablePath(); | |
- ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle, | |
+ mPathBuilder->Arc(Point(aX, aY), aR, aStartAngle, aEndAngle, | |
aAnticlockwise); | |
} | |
void CanvasRenderingContext2D::Rect(double aX, double aY, double aW, | |
double aH) { | |
EnsureWritablePath(); | |
if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) || | |
!std::isfinite(aH)) { | |
return; | |
} | |
- if (mPathBuilder) { | |
- mPathBuilder->MoveTo(Point(aX, aY)); | |
- if (aW == 0 && aH == 0) { | |
- return; | |
- } | |
- mPathBuilder->LineTo(Point(aX + aW, aY)); | |
- mPathBuilder->LineTo(Point(aX + aW, aY + aH)); | |
- mPathBuilder->LineTo(Point(aX, aY + aH)); | |
- mPathBuilder->Close(); | |
- } else { | |
- mDSPathBuilder->MoveTo( | |
- mTarget->GetTransform().TransformPoint(Point(aX, aY))); | |
- if (aW == 0 && aH == 0) { | |
+ mPathBuilder->MoveTo(Point(aX, aY)); | |
+ if (aW == 0 && aH == 0) { | |
return; | |
- } | |
- mDSPathBuilder->LineTo( | |
- mTarget->GetTransform().TransformPoint(Point(aX + aW, aY))); | |
- mDSPathBuilder->LineTo( | |
- mTarget->GetTransform().TransformPoint(Point(aX + aW, aY + aH))); | |
- mDSPathBuilder->LineTo( | |
- mTarget->GetTransform().TransformPoint(Point(aX, aY + aH))); | |
- mDSPathBuilder->Close(); | |
} | |
+ mPathBuilder->LineTo(Point(aX + aW, aY)); | |
+ mPathBuilder->LineTo(Point(aX + aW, aY + aH)); | |
+ mPathBuilder->LineTo(Point(aX, aY + aH)); | |
+ mPathBuilder->Close(); | |
} | |
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect | |
static void RoundRectImpl( | |
PathBuilder* aPathBuilder, const Maybe<Matrix>& aTransform, double aX, | |
double aY, double aW, double aH, | |
const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence& | |
aRadii, | |
ErrorResult& aError) { | |
// Step 1. If any of x, y, w, or h are infinite or NaN, then return. | |
@@ -3494,24 +3496,20 @@ static void RoundRectImpl( | |
void CanvasRenderingContext2D::RoundRect( | |
double aX, double aY, double aW, double aH, | |
const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence& | |
aRadii, | |
ErrorResult& aError) { | |
EnsureWritablePath(); | |
PathBuilder* builder = mPathBuilder; | |
Maybe<Matrix> transform = Nothing(); | |
- if (!builder) { | |
- builder = mDSPathBuilder; | |
- transform = Some(mTarget->GetTransform()); | |
- } | |
RoundRectImpl(builder, transform, aX, aY, aW, aH, aRadii, aError); | |
} | |
void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, | |
double aRadiusY, double aRotation, | |
double aStartAngle, double aEndAngle, | |
bool aAnticlockwise, | |
ErrorResult& aError) { | |
if (aRadiusX < 0.0 || aRadiusY < 0.0) { | |
@@ -3522,119 +3520,74 @@ void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, | |
ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, | |
aEndAngle, aAnticlockwise, aRotation); | |
} | |
void CanvasRenderingContext2D::EnsureWritablePath() { | |
EnsureTarget(); | |
// NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we | |
// go ahead and create a path anyway since callers depend on that. | |
- if (mDSPathBuilder) { | |
- return; | |
- } | |
- | |
FillRule fillRule = CurrentState().fillRule; | |
if (mPathBuilder) { | |
- if (mPathTransformWillUpdate) { | |
- mPath = mPathBuilder->Finish(); | |
- mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule); | |
- mPath = nullptr; | |
- mPathBuilder = nullptr; | |
- mPathTransformWillUpdate = false; | |
- } | |
return; | |
} | |
if (!mPath) { | |
- NS_ASSERTION( | |
- !mPathTransformWillUpdate, | |
- "mPathTransformWillUpdate should be false, if all paths are null"); | |
mPathBuilder = mTarget->CreatePathBuilder(fillRule); | |
- } else if (!mPathTransformWillUpdate) { | |
- mPathBuilder = mPath->CopyToBuilder(fillRule); | |
} else { | |
- mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule); | |
- mPathTransformWillUpdate = false; | |
- mPath = nullptr; | |
+ mPathBuilder = mPath->CopyToBuilder(fillRule); | |
} | |
} | |
void CanvasRenderingContext2D::EnsureUserSpacePath( | |
const CanvasWindingRule& aWinding) { | |
FillRule fillRule = CurrentState().fillRule; | |
if (aWinding == CanvasWindingRule::Evenodd) | |
fillRule = FillRule::FILL_EVEN_ODD; | |
EnsureTarget(); | |
if (!IsTargetValid()) { | |
return; | |
} | |
- if (!mPath && !mPathBuilder && !mDSPathBuilder) { | |
+ if (!mPath && !mPathBuilder) { | |
mPathBuilder = mTarget->CreatePathBuilder(fillRule); | |
} | |
if (mPathBuilder) { | |
mPath = mPathBuilder->Finish(); | |
mPathBuilder = nullptr; | |
} | |
- if (mPath && mPathTransformWillUpdate) { | |
- mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule); | |
- mPath = nullptr; | |
- mPathTransformWillUpdate = false; | |
- } | |
- | |
- if (mDSPathBuilder) { | |
- RefPtr<Path> dsPath; | |
- dsPath = mDSPathBuilder->Finish(); | |
- mDSPathBuilder = nullptr; | |
- | |
- Matrix inverse = mTarget->GetTransform(); | |
- if (!inverse.Invert()) { | |
- NS_WARNING("Could not invert transform"); | |
- return; | |
- } | |
- | |
- mPathBuilder = dsPath->TransformedCopyToBuilder(inverse, fillRule); | |
- mPath = mPathBuilder->Finish(); | |
- mPathBuilder = nullptr; | |
- } | |
- | |
if (mPath && mPath->GetFillRule() != fillRule) { | |
mPathBuilder = mPath->CopyToBuilder(fillRule); | |
mPath = mPathBuilder->Finish(); | |
mPathBuilder = nullptr; | |
} | |
NS_ASSERTION(mPath, "mPath should exist"); | |
} | |
-void CanvasRenderingContext2D::TransformWillUpdate() { | |
+void CanvasRenderingContext2D::TransformWillUpdate(const Matrix &aTransformToNew) { | |
EnsureTarget(); | |
if (!IsTargetValid()) { | |
return; | |
} | |
- // Store the matrix that would transform the current path to device | |
- // space. | |
- if (mPath || mPathBuilder) { | |
- if (!mPathTransformWillUpdate) { | |
- // If the transform has already been updated, but a device space builder | |
- // has not been created yet mPathToDS contains the right transform to | |
- // transform the current mPath into device space. | |
- // We should leave it alone. | |
- mPathToDS = mTarget->GetTransform(); | |
- } | |
- mPathTransformWillUpdate = true; | |
+ if (mPathBuilder) { | |
+ RefPtr<Path> path = mPathBuilder->Finish(); | |
+ mPathBuilder = path->TransformedCopyToBuilder(aTransformToNew); | |
+ } else if (mPath) { | |
+ mPathBuilder = mPath->TransformedCopyToBuilder(aTransformToNew); | |
+ mPath = nullptr; | |
} | |
} | |
// | |
// text | |
// | |
void CanvasRenderingContext2D::SetFont(const nsACString& aFont, | |
ErrorResult& aError) { | |
SetFontInternal(aFont, aError); | |
@@ -4876,24 +4829,20 @@ bool CanvasRenderingContext2D::IsPointInPath( | |
} else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting( | |
RFPTarget::CanvasImageExtractionPrompt)) { | |
return false; | |
} | |
EnsureUserSpacePath(aWinding); | |
if (!mPath) { | |
return false; | |
} | |
- if (mPathTransformWillUpdate) { | |
- return mPath->ContainsPoint(Point(aX, aY), mPathToDS); | |
- } | |
- | |
return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform()); | |
} | |
bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, | |
const CanvasPath& aPath, double aX, | |
double aY, | |
const CanvasWindingRule& aWinding, | |
nsIPrincipal& aSubjectPrincipal) { | |
return IsPointInPath(aCx, aPath, aX, aY, aWinding, Some(&aSubjectPrincipal)); | |
} | |
@@ -4945,24 +4894,20 @@ bool CanvasRenderingContext2D::IsPointInStroke( | |
if (!mPath) { | |
return false; | |
} | |
const ContextState& state = CurrentState(); | |
StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, state.lineCap, | |
state.miterLimit, state.dash.Length(), | |
state.dash.Elements(), state.dashOffset); | |
- if (mPathTransformWillUpdate) { | |
- return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS); | |
- } | |
- | |
return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), | |
mTarget->GetTransform()); | |
} | |
bool CanvasRenderingContext2D::IsPointInStroke( | |
JSContext* aCx, const CanvasPath& aPath, double aX, double aY, | |
nsIPrincipal& aSubjectPrincipal) { | |
return IsPointInStroke(aCx, aPath, aX, aY, Some(&aSubjectPrincipal)); | |
} | |
@@ -6454,21 +6399,21 @@ void CanvasPath::RoundRect( | |
void CanvasPath::Arc(double aX, double aY, double aRadius, double aStartAngle, | |
double aEndAngle, bool aAnticlockwise, | |
ErrorResult& aError) { | |
if (aRadius < 0.0) { | |
return aError.ThrowIndexSizeError("Negative radius"); | |
} | |
EnsurePathBuilder(); | |
- ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle, | |
+ mPathBuilder->Arc(Point(aX, aY), aRadius, aStartAngle, | |
aEndAngle, aAnticlockwise); | |
} | |
void CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY, | |
double rotation, double startAngle, double endAngle, | |
bool anticlockwise, ErrorResult& aError) { | |
if (radiusX < 0.0 || radiusY < 0.0) { | |
return aError.ThrowIndexSizeError("Negative radius"); | |
} | |
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h | |
index 424c10af1ad12..0a4d4fe97dc0c 100644 | |
--- a/dom/canvas/CanvasRenderingContext2D.h | |
+++ b/dom/canvas/CanvasRenderingContext2D.h | |
@@ -320,74 +320,54 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, | |
void SetFontKerning(const nsAString& aFontKerning); | |
void GetLetterSpacing(nsACString& aLetterSpacing); | |
void SetLetterSpacing(const nsACString& aLetterSpacing); | |
void GetWordSpacing(nsACString& aWordSpacing); | |
void SetWordSpacing(const nsACString& aWordSpacing); | |
void ClosePath() override { | |
EnsureWritablePath(); | |
- if (mPathBuilder) { | |
- mPathBuilder->Close(); | |
- } else { | |
- mDSPathBuilder->Close(); | |
- } | |
+ mPathBuilder->Close(); | |
} | |
void MoveTo(double aX, double aY) override { | |
EnsureWritablePath(); | |
mozilla::gfx::Point pos(ToFloat(aX), ToFloat(aY)); | |
if (!pos.IsFinite()) { | |
return; | |
} | |
- if (mPathBuilder) { | |
- mPathBuilder->MoveTo(pos); | |
- } else { | |
- mozilla::gfx::Point transformedPos = | |
- mTarget->GetTransform().TransformPoint(pos); | |
- mDSPathBuilder->MoveTo(transformedPos); | |
- } | |
+ mPathBuilder->MoveTo(pos); | |
} | |
void LineTo(double aX, double aY) override { | |
EnsureWritablePath(); | |
LineTo(mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))); | |
} | |
void QuadraticCurveTo(double aCpx, double aCpy, double aX, | |
double aY) override { | |
EnsureWritablePath(); | |
mozilla::gfx::Point cp1(ToFloat(aCpx), ToFloat(aCpy)); | |
mozilla::gfx::Point cp2(ToFloat(aX), ToFloat(aY)); | |
if (!cp1.IsFinite() || !cp2.IsFinite()) { | |
return; | |
} | |
- if (mPathBuilder) { | |
- if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) { | |
- return; | |
- } | |
- mPathBuilder->QuadraticBezierTo(cp1, cp2); | |
- } else { | |
- mozilla::gfx::Matrix transform = mTarget->GetTransform(); | |
- mozilla::gfx::Point transformedPos = transform.TransformPoint(cp1); | |
- if (transformedPos == mDSPathBuilder->CurrentPoint() && cp1 == cp2) { | |
- return; | |
- } | |
- mDSPathBuilder->QuadraticBezierTo(transformedPos, | |
- transform.TransformPoint(cp2)); | |
+ if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) { | |
+ return; | |
} | |
+ mPathBuilder->QuadraticBezierTo(cp1, cp2); | |
} | |
void BezierCurveTo(double aCp1x, double aCp1y, double aCp2x, double aCp2y, | |
double aX, double aY) override { | |
EnsureWritablePath(); | |
BezierTo(mozilla::gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)), | |
mozilla::gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)), | |
mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))); | |
} | |
@@ -511,58 +491,38 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, | |
PATTERN = 1, | |
GRADIENT = 2 | |
}; | |
enum class Style : uint8_t { STROKE = 0, FILL, MAX }; | |
void LineTo(const mozilla::gfx::Point& aPoint) { | |
if (!aPoint.IsFinite()) { | |
return; | |
} | |
- if (mPathBuilder) { | |
- if (mPathBuilder->CurrentPoint() == aPoint) { | |
- return; | |
- } | |
- mPathBuilder->LineTo(aPoint); | |
- } else { | |
- mozilla::gfx::Point transformedPt = | |
- mTarget->GetTransform().TransformPoint(aPoint); | |
- if (mDSPathBuilder->CurrentPoint() == transformedPt) { | |
- return; | |
- } | |
- mDSPathBuilder->LineTo(transformedPt); | |
+ if (mPathBuilder->CurrentPoint() == aPoint) { | |
+ return; | |
} | |
+ mPathBuilder->LineTo(aPoint); | |
} | |
void BezierTo(const mozilla::gfx::Point& aCP1, | |
const mozilla::gfx::Point& aCP2, | |
const mozilla::gfx::Point& aCP3) { | |
if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) { | |
return; | |
} | |
- if (mPathBuilder) { | |
- if (aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 && | |
- aCP1 == aCP3) { | |
- return; | |
- } | |
- mPathBuilder->BezierTo(aCP1, aCP2, aCP3); | |
- } else { | |
- mozilla::gfx::Matrix transform = mTarget->GetTransform(); | |
- mozilla::gfx::Point transformedPos = transform.TransformPoint(aCP1); | |
- if (transformedPos == mDSPathBuilder->CurrentPoint() && aCP1 == aCP2 && | |
- aCP1 == aCP3) { | |
- return; | |
- } | |
- mDSPathBuilder->BezierTo(transformedPos, transform.TransformPoint(aCP2), | |
- transform.TransformPoint(aCP3)); | |
+ if (aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 && | |
+ aCP1 == aCP3) { | |
+ return; | |
} | |
+ mPathBuilder->BezierTo(aCP1, aCP2, aCP3); | |
} | |
virtual UniquePtr<uint8_t[]> GetImageBuffer( | |
int32_t* out_format, gfx::IntSize* out_imageSize) override; | |
virtual void OnShutdown(); | |
/** | |
* Update CurrentState().filter with the filter description for | |
* CurrentState().filterChain. | |
@@ -666,21 +626,21 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, | |
void EnsureWritablePath(); | |
// Ensures a path in UserSpace is available. | |
void EnsureUserSpacePath( | |
const CanvasWindingRule& aWinding = CanvasWindingRule::Nonzero); | |
/** | |
* Needs to be called before updating the transform. This makes a call to | |
* EnsureTarget() so you don't have to. | |
*/ | |
- void TransformWillUpdate(); | |
+ void TransformWillUpdate(const mozilla::gfx::Matrix& aTransformToNew); | |
// Report the fillRule has changed. | |
void FillRuleChanged(); | |
/** | |
* Create the backing surfacing, if it doesn't exist. If there is an error | |
* in creating the target then it will put sErrorTarget in place. If there | |
* is in turn an error in creating the sErrorTarget then they would both | |
* be null so IsTargetValid() would still return null. | |
* | |
@@ -862,24 +822,21 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, | |
* occur correctly. | |
* | |
* There's never both a device space path builder and a user space path | |
* builder present at the same time. There is also never a path and a | |
* path builder present at the same time. When writing proceeds on an | |
* existing path the Path is cleared and a new builder is created. | |
* | |
* mPath is always in user-space. | |
*/ | |
RefPtr<mozilla::gfx::Path> mPath; | |
- RefPtr<mozilla::gfx::PathBuilder> mDSPathBuilder; | |
RefPtr<mozilla::gfx::PathBuilder> mPathBuilder; | |
- bool mPathTransformWillUpdate; | |
- mozilla::gfx::Matrix mPathToDS; | |
/** | |
* Number of times we've invalidated before calling redraw | |
*/ | |
uint32_t mInvalidateCount; | |
static const uint32_t kCanvasMaxInvalidateCount = 100; | |
mozilla::intl::Bidi mBidiEngine; | |
/** | |
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h | |
index 3f3192b8419b6..0390b86f65762 100644 | |
--- a/gfx/2d/2D.h | |
+++ b/gfx/2d/2D.h | |
@@ -1537,20 +1537,33 @@ class DrawTarget : public external::AtomicRefCounted<DrawTarget> { | |
* @param aStart Starting point of the line | |
* @param aEnd End point of the line | |
* @param aPattern Pattern that forms the source of this stroking operation | |
* @param aOptions Options that are applied to this operation | |
*/ | |
virtual void StrokeLine(const Point& aStart, const Point& aEnd, | |
const Pattern& aPattern, | |
const StrokeOptions& aStrokeOptions = StrokeOptions(), | |
const DrawOptions& aOptions = DrawOptions()) = 0; | |
+ /** | |
+ * Stroke a circle on the DrawTarget with a certain source pattern. | |
+ * | |
+ * @param aCircle the parameters of the circle | |
+ * @param aPattern Pattern that forms the source of this stroking operation | |
+ * @param aOptions Options that are applied to this operation | |
+ */ | |
+ virtual void StrokeCircle(const Point& aOrigin, | |
+ float radius, | |
+ const Pattern& aPattern, | |
+ const StrokeOptions& aStrokeOptions = StrokeOptions(), | |
+ const DrawOptions& aOptions = DrawOptions()); | |
+ | |
/** | |
* Stroke a path on the draw target with a certain source pattern. | |
* | |
* @param aPath Path that is to be stroked | |
* @param aPattern Pattern that should be used for the stroke | |
* @param aStrokeOptions Stroke options used for this operation | |
* @param aOptions Draw options used for this operation | |
*/ | |
virtual void Stroke(const Path* aPath, const Pattern& aPattern, | |
const StrokeOptions& aStrokeOptions = StrokeOptions(), | |
diff --git a/gfx/2d/DrawTarget.cpp b/gfx/2d/DrawTarget.cpp | |
index 11a7ca5afb883..bd206a53ddf9e 100644 | |
--- a/gfx/2d/DrawTarget.cpp | |
+++ b/gfx/2d/DrawTarget.cpp | |
@@ -172,20 +172,29 @@ void DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects, | |
SetTransform(oldTransform); | |
} | |
void DrawTarget::FillRoundedRect(const RoundedRect& aRect, | |
const Pattern& aPattern, | |
const DrawOptions& aOptions) { | |
RefPtr<Path> path = MakePathForRoundedRect(*this, aRect.rect, aRect.corners); | |
Fill(path, aPattern, aOptions); | |
} | |
+void DrawTarget::StrokeCircle(const Point& aOrigin, | |
+ float radius, | |
+ const Pattern& aPattern, | |
+ const StrokeOptions& aStrokeOptions, | |
+ const DrawOptions& aOptions) { | |
+ RefPtr<Path> path = MakePathForCircle(*this, aOrigin, radius); | |
+ Stroke(path, aPattern, aStrokeOptions, aOptions); | |
+} | |
+ | |
void DrawTarget::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, | |
const Pattern& aPattern, | |
const StrokeOptions& aStrokeOptions, | |
const DrawOptions& aOptions) { | |
RefPtr<Path> path = aFont->GetPathForGlyphs(aBuffer, this); | |
Stroke(path, aPattern, aStrokeOptions, aOptions); | |
} | |
already_AddRefed<SourceSurface> DrawTarget::IntoLuminanceSource( | |
LuminanceType aMaskType, float aOpacity) { | |
diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp | |
index d6279004a9072..210df31e68317 100644 | |
--- a/gfx/2d/DrawTargetRecording.cpp | |
+++ b/gfx/2d/DrawTargetRecording.cpp | |
@@ -348,20 +348,31 @@ void DrawTargetRecording::MaskSurface(const Pattern& aSource, | |
EnsurePatternDependenciesStored(aSource); | |
EnsureSurfaceStoredRecording(mRecorder, aMask, "MaskSurface"); | |
mRecorder->RecordEvent( | |
RecordedMaskSurface(this, aSource, aMask, aOffset, aOptions)); | |
} | |
void DrawTargetRecording::Stroke(const Path* aPath, const Pattern& aPattern, | |
const StrokeOptions& aStrokeOptions, | |
const DrawOptions& aOptions) { | |
+ | |
+ if (aPath->GetBackendType() == BackendType::RECORDING) { | |
+ const PathRecording* path = | |
+ static_cast<const PathRecording*>(aPath); | |
+ auto circle = path->AsCircle(); | |
+ if (circle) { | |
+ EnsurePatternDependenciesStored(aPattern); | |
+ mRecorder->RecordEvent(RecordedStrokeCircle(this, circle.value(), aPattern, aStrokeOptions, aOptions)); | |
+ } | |
+ } | |
+ | |
RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); | |
EnsurePatternDependenciesStored(aPattern); | |
mRecorder->RecordEvent( | |
RecordedStroke(this, pathRecording, aPattern, aStrokeOptions, aOptions)); | |
} | |
already_AddRefed<SourceSurface> DrawTargetRecording::Snapshot() { | |
RefPtr<SourceSurface> retSurf = | |
new SourceSurfaceRecording(mRect.Size(), mFormat, mRecorder); | |
diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h | |
index 22befa91db9af..5fd6d45934cb6 100644 | |
--- a/gfx/2d/PathHelpers.h | |
+++ b/gfx/2d/PathHelpers.h | |
@@ -251,20 +251,29 @@ GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder, | |
const Size& aDimensions); | |
inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget, | |
const Point& aCenter, | |
const Size& aDimensions) { | |
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); | |
AppendEllipseToPath(builder, aCenter, aDimensions); | |
return builder->Finish(); | |
} | |
+inline already_AddRefed<Path> MakePathForCircle(const DrawTarget& aDrawTarget, | |
+ const Point& aCenter, | |
+ float aRadius) { | |
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); | |
+ builder->Arc(aCenter, aRadius, 0.0f, Float(2.0 * M_PI)); | |
+ builder->Close(); | |
+ return builder->Finish(); | |
+} | |
+ | |
/** | |
* If aDrawTarget's transform only contains a translation, and if this line is | |
* a horizontal or vertical line, this function will snap the line's vertices | |
* to align with the device pixel grid so that stroking the line with a one | |
* pixel wide stroke will result in a crisp line that is not antialiased over | |
* two pixels across its width. | |
* | |
* @return Returns true if this function snaps aRect's vertices, else returns | |
* false. | |
*/ | |
diff --git a/gfx/2d/PathRecording.cpp b/gfx/2d/PathRecording.cpp | |
index 6b499c6d0c084..a5d2a9495d95b 100644 | |
--- a/gfx/2d/PathRecording.cpp | |
+++ b/gfx/2d/PathRecording.cpp | |
@@ -105,20 +105,46 @@ PathOps PathOps::TransformedCopy(const Matrix& aTransform) const { | |
newPathOps.Close(); | |
break; | |
default: | |
MOZ_CRASH("We control mOpTypes, so this should never happen."); | |
} | |
} | |
return newPathOps; | |
} | |
+Maybe<Circle> PathOps::AsCircle() const { | |
+ if (mPathData.empty()) { | |
+ return Nothing(); | |
+ } | |
+ | |
+ const uint8_t* nextByte = mPathData.data(); | |
+ const uint8_t* end = nextByte + mPathData.size(); | |
+ const OpType opType = *reinterpret_cast<const OpType*>(nextByte); | |
+ nextByte += sizeof(OpType); | |
+ if (opType == OpType::OP_ARC) { | |
+ NEXT_PARAMS(ArcParams) | |
+ if (fabs(params.startAngle - params.endAngle) == 2 * M_PI) { | |
+ const OpType nextOpType = *reinterpret_cast<const OpType*>(nextByte); | |
+ nextByte += sizeof(OpType); | |
+ if (nextOpType == OpType::OP_CLOSE) { | |
+ if (nextByte == end) { | |
+ return Some(Circle { params.origin, params.radius }); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ return Nothing(); | |
+} | |
+ | |
+ | |
#undef NEXT_PARAMS | |
size_t PathOps::NumberOfOps() const { | |
size_t size = 0; | |
const uint8_t* nextByte = mPathData.data(); | |
const uint8_t* end = nextByte + mPathData.size(); | |
while (nextByte < end) { | |
size++; | |
const OpType opType = *reinterpret_cast<const OpType*>(nextByte); | |
nextByte += sizeof(OpType); | |
diff --git a/gfx/2d/PathRecording.h b/gfx/2d/PathRecording.h | |
index 9be4b1713ed9a..e45eb99545322 100644 | |
--- a/gfx/2d/PathRecording.h | |
+++ b/gfx/2d/PathRecording.h | |
@@ -10,20 +10,25 @@ | |
#include "2D.h" | |
#include <vector> | |
#include <ostream> | |
#include "PathHelpers.h" | |
#include "RecordingTypes.h" | |
namespace mozilla { | |
namespace gfx { | |
+ struct Circle { | |
+ Point origin; | |
+ float radius; | |
+ }; | |
+ | |
class PathOps { | |
public: | |
PathOps() = default; | |
template <class S> | |
explicit PathOps(S& aStream); | |
PathOps(const PathOps& aOther) = default; | |
PathOps& operator=(const PathOps&) = delete; // assign using std::move()! | |
@@ -56,20 +61,22 @@ class PathOps { | |
AppendPathOp(OpType::OP_ARC, ArcParams{aOrigin, aRadius, aStartAngle, | |
aEndAngle, aAntiClockwise}); | |
} | |
void Close() { | |
size_t oldSize = mPathData.size(); | |
mPathData.resize(oldSize + sizeof(OpType)); | |
*reinterpret_cast<OpType*>(mPathData.data() + oldSize) = OpType::OP_CLOSE; | |
} | |
+ Maybe<Circle> AsCircle() const; | |
+ | |
private: | |
enum class OpType : uint32_t { | |
OP_MOVETO = 0, | |
OP_LINETO, | |
OP_BEZIERTO, | |
OP_QUADRATICBEZIERTO, | |
OP_ARC, | |
OP_CLOSE, | |
OP_INVALID | |
}; | |
@@ -199,20 +206,24 @@ class PathRecording final : public Path { | |
const Matrix& aTransform = Matrix()) const final { | |
EnsurePath(); | |
return mPath->GetStrokedBounds(aStrokeOptions, aTransform); | |
} | |
Maybe<Rect> AsRect() const final { | |
EnsurePath(); | |
return mPath->AsRect(); | |
} | |
+ Maybe<Circle> AsCircle() const { | |
+ return mPathOps.AsCircle(); | |
+ } | |
+ | |
void StreamToSink(PathSink* aSink) const final { | |
mPathOps.StreamToSink(*aSink); | |
} | |
FillRule GetFillRule() const final { return mFillRule; } | |
private: | |
friend class DrawTargetWrapAndRecord; | |
friend class DrawTargetRecording; | |
friend class RecordedPathCreation; | |
diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h | |
index 17516436e2c2f..5c697342d0bfc 100644 | |
--- a/gfx/2d/RecordedEvent.h | |
+++ b/gfx/2d/RecordedEvent.h | |
@@ -354,20 +354,21 @@ class EventStream { | |
}; | |
class RecordedEvent { | |
public: | |
enum EventType { | |
DRAWTARGETCREATION = 0, | |
DRAWTARGETDESTRUCTION, | |
FILLRECT, | |
STROKERECT, | |
STROKELINE, | |
+ STROKECIRCLE, | |
CLEARRECT, | |
COPYSURFACE, | |
SETTRANSFORM, | |
PUSHCLIP, | |
PUSHCLIPRECT, | |
POPCLIP, | |
FILL, | |
FILLGLYPHS, | |
MASK, | |
STROKE, | |
diff --git a/gfx/2d/RecordedEventImpl.h b/gfx/2d/RecordedEventImpl.h | |
index 995d63101c57c..166f2c603f9c5 100644 | |
--- a/gfx/2d/RecordedEventImpl.h | |
+++ b/gfx/2d/RecordedEventImpl.h | |
@@ -299,20 +299,55 @@ class RecordedStrokeLine : public RecordedDrawingEvent<RecordedStrokeLine> { | |
template <class S> | |
MOZ_IMPLICIT RecordedStrokeLine(S& aStream); | |
Point mBegin; | |
Point mEnd; | |
PatternStorage mPattern; | |
StrokeOptions mStrokeOptions; | |
DrawOptions mOptions; | |
}; | |
+class RecordedStrokeCircle : public RecordedDrawingEvent<RecordedStrokeCircle> { | |
+ public: | |
+ RecordedStrokeCircle(DrawTarget* aDT, Circle aCircle, | |
+ const Pattern& aPattern, | |
+ const StrokeOptions& aStrokeOptions, | |
+ const DrawOptions& aOptions) | |
+ : RecordedDrawingEvent(STROKECIRCLE, aDT), | |
+ mCircle(aCircle), | |
+ mPattern(), | |
+ mStrokeOptions(aStrokeOptions), | |
+ mOptions(aOptions) { | |
+ StorePattern(mPattern, aPattern); | |
+ } | |
+ | |
+ bool PlayEvent(Translator* aTranslator) const override; | |
+ | |
+ template <class S> | |
+ void Record(S& aStream) const; | |
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; | |
+ | |
+ std::string GetName() const override { return "StrokeCircle"; } | |
+ | |
+ private: | |
+ friend class RecordedEvent; | |
+ | |
+ template <class S> | |
+ MOZ_IMPLICIT RecordedStrokeCircle(S& aStream); | |
+ | |
+ Circle mCircle; | |
+ PatternStorage mPattern; | |
+ StrokeOptions mStrokeOptions; | |
+ DrawOptions mOptions; | |
+}; | |
+ | |
+ | |
class RecordedFill : public RecordedDrawingEvent<RecordedFill> { | |
public: | |
RecordedFill(DrawTarget* aDT, ReferencePtr aPath, const Pattern& aPattern, | |
const DrawOptions& aOptions) | |
: RecordedDrawingEvent(FILL, aDT), | |
mPath(aPath), | |
mPattern(), | |
mOptions(aOptions) { | |
StorePattern(mPattern, aPattern); | |
} | |
@@ -2337,20 +2372,57 @@ RecordedStrokeLine::RecordedStrokeLine(S& aStream) | |
} | |
inline void RecordedStrokeLine::OutputSimpleEventInfo( | |
std::stringstream& aStringStream) const { | |
aStringStream << "[" << mDT << "] StrokeLine (" << mBegin.x << ", " | |
<< mBegin.y << " - " << mEnd.x << ", " << mEnd.y | |
<< ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; | |
OutputSimplePatternInfo(mPattern, aStringStream); | |
} | |
+inline bool RecordedStrokeCircle::PlayEvent(Translator* aTranslator) const { | |
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT); | |
+ if (!dt) { | |
+ return false; | |
+ } | |
+ | |
+ dt->StrokeCircle(mCircle.origin, mCircle.radius, *GenericPattern(mPattern, aTranslator), | |
+ mStrokeOptions, mOptions); | |
+ return true; | |
+} | |
+ | |
+template <class S> | |
+void RecordedStrokeCircle::Record(S& aStream) const { | |
+ RecordedDrawingEvent::Record(aStream); | |
+ WriteElement(aStream, mCircle); | |
+ WriteElement(aStream, mOptions); | |
+ RecordPatternData(aStream, mPattern); | |
+ RecordStrokeOptions(aStream, mStrokeOptions); | |
+} | |
+ | |
+template <class S> | |
+RecordedStrokeCircle::RecordedStrokeCircle(S& aStream) | |
+ : RecordedDrawingEvent(STROKELINE, aStream) { | |
+ ReadElement(aStream, mCircle); | |
+ ReadDrawOptions(aStream, mOptions); | |
+ ReadPatternData(aStream, mPattern); | |
+ ReadStrokeOptions(aStream, mStrokeOptions); | |
+} | |
+ | |
+inline void RecordedStrokeCircle::OutputSimpleEventInfo( | |
+ std::stringstream& aStringStream) const { | |
+ aStringStream << "[" << mDT << "] StrokeCircle (" << mCircle.origin.x << ", " | |
+ << mCircle.origin.y << " - " << mCircle.radius | |
+ << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; | |
+ OutputSimplePatternInfo(mPattern, aStringStream); | |
+} | |
+ | |
inline bool RecordedFill::PlayEvent(Translator* aTranslator) const { | |
DrawTarget* dt = aTranslator->LookupDrawTarget(mDT); | |
if (!dt) { | |
return false; | |
} | |
dt->Fill(aTranslator->LookupPath(mPath), | |
*GenericPattern(mPattern, aTranslator), mOptions); | |
return true; | |
} | |
@@ -3986,20 +4058,21 @@ inline void RecordedDestination::OutputSimpleEventInfo( | |
std::stringstream& aStringStream) const { | |
aStringStream << "Destination [" << mDestination << " @ " << mPoint << "]"; | |
} | |
#define FOR_EACH_EVENT(f) \ | |
f(DRAWTARGETCREATION, RecordedDrawTargetCreation); \ | |
f(DRAWTARGETDESTRUCTION, RecordedDrawTargetDestruction); \ | |
f(FILLRECT, RecordedFillRect); \ | |
f(STROKERECT, RecordedStrokeRect); \ | |
f(STROKELINE, RecordedStrokeLine); \ | |
+ f(STROKECIRCLE, RecordedStrokeCircle); \ | |
f(CLEARRECT, RecordedClearRect); \ | |
f(COPYSURFACE, RecordedCopySurface); \ | |
f(SETTRANSFORM, RecordedSetTransform); \ | |
f(PUSHCLIPRECT, RecordedPushClipRect); \ | |
f(PUSHCLIP, RecordedPushClip); \ | |
f(POPCLIP, RecordedPopClip); \ | |
f(FILL, RecordedFill); \ | |
f(FILLGLYPHS, RecordedFillGlyphs); \ | |
f(MASK, RecordedMask); \ | |
f(STROKE, RecordedStroke); \ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment