From f0b9b7d32e942a86f4d75cc7446a2e4dc5f81a99 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Tue, 18 Mar 2014 08:03:06 -0400 Subject: [PATCH] Bug 830734 - Implement Path primitives. r=roc --- .../canvas/src/CanvasRenderingContext2D.cpp | 276 ++++++++++++++++++ content/canvas/src/CanvasRenderingContext2D.h | 54 ++++ dom/bindings/Bindings.conf | 5 + dom/webidl/CanvasRenderingContext2D.webidl | 12 +- modules/libpref/src/init/all.js | 2 + 5 files changed, 346 insertions(+), 3 deletions(-) diff --git a/content/canvas/src/CanvasRenderingContext2D.cpp b/content/canvas/src/CanvasRenderingContext2D.cpp index 2631b6b01f2..1d5df63a7d4 100755 --- a/content/canvas/src/CanvasRenderingContext2D.cpp +++ b/content/canvas/src/CanvasRenderingContext2D.cpp @@ -1691,6 +1691,29 @@ CanvasRenderingContext2D::Fill(const CanvasWindingRule& winding) Redraw(); } +void CanvasRenderingContext2D::Fill(const CanvasPath& path, const CanvasWindingRule& winding) +{ + EnsureTarget(); + + RefPtr gfxpath = path.GetPath(winding, mTarget); + + if (!gfxpath) { + return; + } + + mgfx::Rect bounds; + + if (NeedToDrawShadow()) { + bounds = gfxpath->GetBounds(mTarget->GetTransform()); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + Fill(gfxpath, CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), + DrawOptions(CurrentState().globalAlpha, UsedOperation())); + + Redraw(); +} + void CanvasRenderingContext2D::Stroke() { @@ -1720,6 +1743,37 @@ CanvasRenderingContext2D::Stroke() Redraw(); } +void +CanvasRenderingContext2D::Stroke(const CanvasPath& path) +{ + EnsureTarget(); + + RefPtr gfxpath = path.GetPath(CanvasWindingRule::Nonzero, mTarget); + + if (!gfxpath) { + return; + } + + const ContextState &state = CurrentState(); + + StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), state.dash.Elements(), + state.dashOffset); + + mgfx::Rect bounds; + if (NeedToDrawShadow()) { + bounds = + gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + Stroke(gfxpath, CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); + + Redraw(); +} + void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement) { EnsureUserSpacePath(); @@ -1800,6 +1854,21 @@ CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding) CurrentState().clipsPushed.push_back(mPath); } +void +CanvasRenderingContext2D::Clip(const CanvasPath& path, const CanvasWindingRule& winding) +{ + EnsureTarget(); + + RefPtr gfxpath = path.GetPath(winding, mTarget); + + if (!gfxpath) { + return; + } + + mTarget->PushClip(gfxpath); + CurrentState().clipsPushed.push_back(gfxpath); +} + void CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, double y2, double radius, @@ -4226,5 +4295,212 @@ CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight)); } +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(CanvasPath) + +CanvasPath::CanvasPath(nsCOMPtr aParent) : mParent(aParent) +{ + SetIsDOMBinding(); + + mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); +} + +CanvasPath::CanvasPath(nsCOMPtr aParent, RefPtr aPathBuilder): mParent(aParent), mPathBuilder(aPathBuilder) +{ + SetIsDOMBinding(); + + if (!mPathBuilder) { + mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); + } +} + +JSObject* +CanvasPath::WrapObject(JSContext* aCx, JS::Handle aScope) +{ + return Path2DBinding::Wrap(aCx, aScope, this); +} + +already_AddRefed +CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) +{ + nsRefPtr path = new CanvasPath(aGlobal.GetAsSupports()); + return path.forget(); +} + +already_AddRefed +CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv) +{ + RefPtr tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero, + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()); + + nsRefPtr path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); + return path.forget(); +} + +void +CanvasPath::ClosePath() +{ + mPathBuilder->Close(); +} + +void +CanvasPath::MoveTo(double x, double y) +{ + mPathBuilder->MoveTo(Point(ToFloat(x), ToFloat(y))); +} + +void +CanvasPath::LineTo(double x, double y) +{ + mPathBuilder->LineTo(Point(ToFloat(x), ToFloat(y))); +} + +void +CanvasPath::QuadraticCurveTo(double cpx, double cpy, double x, double y) +{ + mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(cpx), ToFloat(cpy)), + gfx::Point(ToFloat(x), ToFloat(y))); +} + +void +CanvasPath::BezierCurveTo(double cp1x, double cp1y, + double cp2x, double cp2y, + double x, double y) +{ + BezierTo(gfx::Point(ToFloat(cp1x), ToFloat(cp1y)), + gfx::Point(ToFloat(cp2x), ToFloat(cp2y)), + gfx::Point(ToFloat(x), ToFloat(y))); +} + +void +CanvasPath::ArcTo(double x1, double y1, double x2, double y2, double radius, + ErrorResult& error) +{ + if (radius < 0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + // Current point in user space! + Point p0 = mPathBuilder->CurrentPoint(); + Point p1(x1, y1); + Point p2(x2, y2); + + // Execute these calculations in double precision to avoid cumulative + // rounding errors. + double dir, a2, b2, c2, cosx, sinx, d, anx, any, + bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; + bool anticlockwise; + + if (p0 == p1 || p1 == p2 || radius == 0) { + LineTo(p1.x, p1.y); + return; + } + + // Check for colinearity + dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); + if (dir == 0) { + LineTo(p1.x, p1.y); + return; + } + + + // XXX - Math for this code was already available from the non-azure code + // and would be well tested. Perhaps converting to bezier directly might + // be more efficient longer run. + a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); + b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); + c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); + cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); + + sinx = sqrt(1 - cosx*cosx); + d = radius / ((1 - cosx) / sinx); + + anx = (x1-p0.x) / sqrt(a2); + any = (y1-p0.y) / sqrt(a2); + bnx = (x1-x2) / sqrt(b2); + bny = (y1-y2) / sqrt(b2); + x3 = x1 - anx*d; + y3 = y1 - any*d; + x4 = x1 - bnx*d; + y4 = y1 - bny*d; + anticlockwise = (dir < 0); + cx = x3 + any*radius*(anticlockwise ? 1 : -1); + cy = y3 - anx*radius*(anticlockwise ? 1 : -1); + angle0 = atan2((y3-cy), (x3-cx)); + angle1 = atan2((y4-cy), (x4-cx)); + + + LineTo(x3, y3); + + Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); +} + +void +CanvasPath::Rect(double x, double y, double w, double h) +{ + MoveTo(x, y); + LineTo(x + w, y); + LineTo(x + w, y + h); + LineTo(x, y + h); + ClosePath(); +} + +void +CanvasPath::Arc(double x, double y, double radius, + double startAngle, double endAngle, bool anticlockwise, + ErrorResult& error) +{ + if (radius < 0.0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + ArcToBezier(this, Point(x, y), Size(radius, radius), startAngle, endAngle, anticlockwise); +} + +void +CanvasPath::LineTo(const gfx::Point& aPoint) +{ + mPathBuilder->LineTo(aPoint); +} + +void +CanvasPath::BezierTo(const gfx::Point& aCP1, + const gfx::Point& aCP2, + const gfx::Point& aCP3) +{ + mPathBuilder->BezierTo(aCP1, aCP2, aCP3); +} + +RefPtr +CanvasPath::GetPath(const CanvasWindingRule& winding, const mozilla::RefPtr& mTarget) const +{ + FillRule fillRule = FillRule::FILL_WINDING; + if (winding == CanvasWindingRule::Evenodd) { + fillRule = FillRule::FILL_EVEN_ODD; + } + + RefPtr mTempPath = mPathBuilder->Finish(); + if (!mTempPath) + return mTempPath; + + // retarget our backend if we're used with a different backend + if (mTempPath->GetBackendType() != mTarget->GetType()) { + mPathBuilder = mTarget->CreatePathBuilder(fillRule); + mTempPath->StreamToSink(mPathBuilder); + mTempPath = mPathBuilder->Finish(); + } else if (mTempPath->GetFillRule() != fillRule) { + mPathBuilder = mTempPath->CopyToBuilder(fillRule); + mTempPath = mPathBuilder->Finish(); + } + + mPathBuilder = mTempPath->CopyToBuilder(); + + return mTempPath; +} + } } diff --git a/content/canvas/src/CanvasRenderingContext2D.h b/content/canvas/src/CanvasRenderingContext2D.h index fcec5b21a61..c7d2fa76629 100644 --- a/content/canvas/src/CanvasRenderingContext2D.h +++ b/content/canvas/src/CanvasRenderingContext2D.h @@ -45,6 +45,57 @@ extern const mozilla::gfx::Float SIGMA_MAX; template class Optional; +class CanvasPath : + public nsWrapperCache +{ +public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CanvasPath) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CanvasPath) + + nsCOMPtr GetParentObject() { return mParent; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle aScope); + + static already_AddRefed Constructor(const GlobalObject& aGlobal, + ErrorResult& rv); + static already_AddRefed Constructor(const GlobalObject& aGlobal, + CanvasPath& aCanvasPath, + ErrorResult& rv); + + void ClosePath(); + void MoveTo(double x, double y); + void LineTo(double x, double y); + void QuadraticCurveTo(double cpx, double cpy, double x, double y); + void BezierCurveTo(double cp1x, double cp1y, + double cp2x, double cp2y, + double x, double y); + void ArcTo(double x1, double y1, double x2, double y2, double radius, + ErrorResult& error); + void Rect(double x, double y, double w, double h); + void Arc(double x, double y, double radius, + double startAngle, double endAngle, bool anticlockwise, + ErrorResult& error); + + void LineTo(const gfx::Point& aPoint); + void BezierTo(const gfx::Point& aCP1, + const gfx::Point& aCP2, + const gfx::Point& aCP3); + + mozilla::RefPtr GetPath(const CanvasWindingRule& winding, + const mozilla::RefPtr& mTarget) const; + + CanvasPath(nsCOMPtr aParent); + CanvasPath(nsCOMPtr aParent, RefPtr mPathBuilder); + virtual ~CanvasPath() {} + +private: + + nsCOMPtr mParent; + static gfx::Float ToFloat(double aValue) { return gfx::Float(aValue); } + + mutable RefPtr mPathBuilder; +}; + struct CanvasBidiProcessor; class CanvasRenderingContext2DUserData; @@ -172,10 +223,13 @@ public: void StrokeRect(double x, double y, double w, double h); void BeginPath(); void Fill(const CanvasWindingRule& winding); + void Fill(const CanvasPath& path, const CanvasWindingRule& winding); void Stroke(); + void Stroke(const CanvasPath& path); void DrawFocusIfNeeded(mozilla::dom::Element& element); bool DrawCustomFocusRing(mozilla::dom::Element& element); void Clip(const CanvasWindingRule& winding); + void Clip(const CanvasPath& path, const CanvasWindingRule& winding); bool IsPointInPath(double x, double y, const CanvasWindingRule& winding); bool IsPointInStroke(double x, double y); void FillText(const nsAString& text, double x, double y, diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index ca9656d2a49..ba274fd485d 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -858,6 +858,11 @@ DOMInterfaces = { 'resultNotAddRefed': [ 'coneGain', 'distanceGain' ], }, +'Path2D': { + 'nativeType': 'mozilla::dom::CanvasPath', + 'headerFile': 'CanvasRenderingContext2D.h' +}, + 'PeerConnectionImpl': { 'nativeType': 'sipcc::PeerConnectionImpl', 'headerFile': 'PeerConnectionImpl.h', diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index 6b1b375c562..885991a249e 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -84,9 +84,9 @@ interface CanvasRenderingContext2D { // path API (see also CanvasPathMethods) void beginPath(); void fill(optional CanvasWindingRule winding = "nonzero"); -// NOT IMPLEMENTED void fill(Path path); + void fill(Path2D path, optional CanvasWindingRule winding = "nonzero"); void stroke(); -// NOT IMPLEMENTED void stroke(Path path); + void stroke(Path2D path); [Pref="canvas.focusring.enabled"] void drawFocusIfNeeded(Element element); // NOT IMPLEMENTED void drawSystemFocusRing(Path path, HTMLElement element); [Pref="canvas.customfocusring.enabled"] boolean drawCustomFocusRing(Element element); @@ -94,7 +94,7 @@ interface CanvasRenderingContext2D { // NOT IMPLEMENTED void scrollPathIntoView(); // NOT IMPLEMENTED void scrollPathIntoView(Path path); void clip(optional CanvasWindingRule winding = "nonzero"); -// NOT IMPLEMENTED void clip(Path path); + void clip(Path2D path, optional CanvasWindingRule winding = "nonzero"); // NOT IMPLEMENTED void resetClip(); boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasWindingRule winding = "nonzero"); // NOT IMPLEMENTED boolean isPointInPath(Path path, unrestricted double x, unrestricted double y); @@ -313,3 +313,9 @@ interface TextMetrics { }; +[Pref="canvas.path.enabled", + Constructor, + Constructor(Path2D other)] +interface Path2D +{}; +Path2D implements CanvasPathMethods; diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 9b3179c5c5a..7ba7aead230 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -460,6 +460,8 @@ pref("accessibility.tabfocus_applies_to_xul", true); pref("canvas.focusring.enabled", false); pref("canvas.customfocusring.enabled", false); pref("canvas.hitregions.enabled", false); +// Add support for canvas path objects +pref("canvas.path.enabled", false); // We want the ability to forcibly disable platform a11y, because // some non-a11y-related components attempt to bring it up. See bug