michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "PathD2D.h" michael@0: #include "HelpersD2D.h" michael@0: #include michael@0: #include "DrawTargetD2D.h" michael@0: #include "Logging.h" michael@0: #include "mozilla/Constants.h" michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: // This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows michael@0: // a geometry to be duplicated into a geometry sink, while removing the final michael@0: // figure end and thus allowing a figure that was implicitly closed to be michael@0: // continued. michael@0: class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink michael@0: { michael@0: public: michael@0: OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink) michael@0: : mSink(aSink) michael@0: , mNeedsFigureEnded(false) michael@0: { michael@0: } michael@0: michael@0: HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) michael@0: { michael@0: if (!aPtr) { michael@0: return E_POINTER; michael@0: } michael@0: michael@0: if (aIID == IID_IUnknown) { michael@0: *aPtr = static_cast(this); michael@0: return S_OK; michael@0: } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { michael@0: *aPtr = static_cast(this); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: ULONG STDMETHODCALLTYPE AddRef() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: ULONG STDMETHODCALLTYPE Release() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: // We ignore SetFillMode, the copier will decide. michael@0: STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) michael@0: { EnsureFigureEnded(); return; } michael@0: STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) michael@0: { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); } michael@0: STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) michael@0: { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); } michael@0: STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) michael@0: { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); } michael@0: STDMETHOD(Close)() michael@0: { /* Should never be called! */ return S_OK; } michael@0: STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) michael@0: { return mSink->SetSegmentFlags(aFlags); } michael@0: michael@0: // This function is special - it's the reason this class exists. michael@0: // It needs to intercept the very last endfigure. So that a user can michael@0: // continue writing to this sink as if they never stopped. michael@0: STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) michael@0: { michael@0: if (aEnd == D2D1_FIGURE_END_CLOSED) { michael@0: return mSink->EndFigure(aEnd); michael@0: } else { michael@0: mNeedsFigureEnded = true; michael@0: } michael@0: } michael@0: private: michael@0: void EnsureFigureEnded() michael@0: { michael@0: if (mNeedsFigureEnded) { michael@0: mSink->EndFigure(D2D1_FIGURE_END_OPEN); michael@0: mNeedsFigureEnded = false; michael@0: } michael@0: } michael@0: michael@0: ID2D1SimplifiedGeometrySink *mSink; michael@0: bool mNeedsFigureEnded; michael@0: }; michael@0: michael@0: class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink michael@0: { michael@0: public: michael@0: StreamingGeometrySink(PathSink *aSink) michael@0: : mSink(aSink) michael@0: { michael@0: } michael@0: michael@0: HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) michael@0: { michael@0: if (!aPtr) { michael@0: return E_POINTER; michael@0: } michael@0: michael@0: if (aIID == IID_IUnknown) { michael@0: *aPtr = static_cast(this); michael@0: return S_OK; michael@0: } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { michael@0: *aPtr = static_cast(this); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: ULONG STDMETHODCALLTYPE AddRef() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: ULONG STDMETHODCALLTYPE Release() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: // We ignore SetFillMode, this depends on the destination sink. michael@0: STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) michael@0: { return; } michael@0: STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) michael@0: { mSink->MoveTo(ToPoint(aPoint)); } michael@0: STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) michael@0: { for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } } michael@0: STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) michael@0: { michael@0: for (UINT i = 0; i < aCount; i++) { michael@0: mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); michael@0: } michael@0: } michael@0: STDMETHOD(Close)() michael@0: { /* Should never be called! */ return S_OK; } michael@0: STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) michael@0: { /* Should never be called! */ } michael@0: michael@0: STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) michael@0: { michael@0: if (aEnd == D2D1_FIGURE_END_CLOSED) { michael@0: return mSink->Close(); michael@0: } michael@0: } michael@0: private: michael@0: michael@0: PathSink *mSink; michael@0: }; michael@0: michael@0: PathBuilderD2D::~PathBuilderD2D() michael@0: { michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::MoveTo(const Point &aPoint) michael@0: { michael@0: if (mFigureActive) { michael@0: mSink->EndFigure(D2D1_FIGURE_END_OPEN); michael@0: mFigureActive = false; michael@0: } michael@0: EnsureActive(aPoint); michael@0: mCurrentPoint = aPoint; michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::LineTo(const Point &aPoint) michael@0: { michael@0: EnsureActive(aPoint); michael@0: mSink->AddLine(D2DPoint(aPoint)); michael@0: michael@0: mCurrentPoint = aPoint; michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::BezierTo(const Point &aCP1, michael@0: const Point &aCP2, michael@0: const Point &aCP3) michael@0: { michael@0: EnsureActive(aCP1); michael@0: mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1), michael@0: D2DPoint(aCP2), michael@0: D2DPoint(aCP3))); michael@0: michael@0: mCurrentPoint = aCP3; michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::QuadraticBezierTo(const Point &aCP1, michael@0: const Point &aCP2) michael@0: { michael@0: EnsureActive(aCP1); michael@0: mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1), michael@0: D2DPoint(aCP2))); michael@0: michael@0: mCurrentPoint = aCP2; michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::Close() michael@0: { michael@0: if (mFigureActive) { michael@0: mSink->EndFigure(D2D1_FIGURE_END_CLOSED); michael@0: michael@0: mFigureActive = false; michael@0: michael@0: EnsureActive(mBeginPoint); michael@0: } michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, michael@0: Float aEndAngle, bool aAntiClockwise) michael@0: { michael@0: if (aAntiClockwise && aStartAngle < aEndAngle) { michael@0: // D2D does things a little differently, and draws the arc by specifying an michael@0: // beginning and an end point. This means the circle will be the wrong way michael@0: // around if the start angle is smaller than the end angle. It might seem michael@0: // tempting to invert aAntiClockwise but that would change the sweeping michael@0: // direction of the arc to instead we exchange start/begin. michael@0: Float oldStart = aStartAngle; michael@0: aStartAngle = aEndAngle; michael@0: aEndAngle = oldStart; michael@0: } michael@0: michael@0: // XXX - Workaround for now, D2D does not appear to do the desired thing when michael@0: // the angle sweeps a complete circle. michael@0: if (aEndAngle - aStartAngle >= 2 * M_PI) { michael@0: aEndAngle = Float(aStartAngle + M_PI * 1.9999); michael@0: } else if (aStartAngle - aEndAngle >= 2 * M_PI) { michael@0: aStartAngle = Float(aEndAngle + M_PI * 1.9999); michael@0: } michael@0: michael@0: Point startPoint; michael@0: startPoint.x = aOrigin.x + aRadius * cos(aStartAngle); michael@0: startPoint.y = aOrigin.y + aRadius * sin(aStartAngle); michael@0: michael@0: if (!mFigureActive) { michael@0: EnsureActive(startPoint); michael@0: } else { michael@0: mSink->AddLine(D2DPoint(startPoint)); michael@0: } michael@0: michael@0: Point endPoint; michael@0: endPoint.x = aOrigin.x + aRadius * cos(aEndAngle); michael@0: endPoint.y = aOrigin.y + aRadius * sin(aEndAngle); michael@0: michael@0: D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL; michael@0: michael@0: if (aAntiClockwise) { michael@0: if (aStartAngle - aEndAngle > M_PI) { michael@0: arcSize = D2D1_ARC_SIZE_LARGE; michael@0: } michael@0: } else { michael@0: if (aEndAngle - aStartAngle > M_PI) { michael@0: arcSize = D2D1_ARC_SIZE_LARGE; michael@0: } michael@0: } michael@0: michael@0: mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint), michael@0: D2D1::SizeF(aRadius, aRadius), michael@0: 0.0f, michael@0: aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : michael@0: D2D1_SWEEP_DIRECTION_CLOCKWISE, michael@0: arcSize)); michael@0: michael@0: mCurrentPoint = endPoint; michael@0: } michael@0: michael@0: Point michael@0: PathBuilderD2D::CurrentPoint() const michael@0: { michael@0: return mCurrentPoint; michael@0: } michael@0: michael@0: void michael@0: PathBuilderD2D::EnsureActive(const Point &aPoint) michael@0: { michael@0: if (!mFigureActive) { michael@0: mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED); michael@0: mBeginPoint = aPoint; michael@0: mFigureActive = true; michael@0: } michael@0: } michael@0: michael@0: TemporaryRef michael@0: PathBuilderD2D::Finish() michael@0: { michael@0: if (mFigureActive) { michael@0: mSink->EndFigure(D2D1_FIGURE_END_OPEN); michael@0: } michael@0: michael@0: HRESULT hr = mSink->Close(); michael@0: if (FAILED(hr)) { michael@0: gfxDebug() << "Failed to close PathSink. Code: " << hr; michael@0: return nullptr; michael@0: } michael@0: michael@0: return new PathD2D(mGeometry, mFigureActive, mCurrentPoint, mFillRule); michael@0: } michael@0: michael@0: TemporaryRef michael@0: PathD2D::CopyToBuilder(FillRule aFillRule) const michael@0: { michael@0: return TransformedCopyToBuilder(Matrix(), aFillRule); michael@0: } michael@0: michael@0: TemporaryRef michael@0: PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const michael@0: { michael@0: RefPtr path; michael@0: HRESULT hr = DrawTargetD2D::factory()->CreatePathGeometry(byRef(path)); michael@0: michael@0: if (FAILED(hr)) { michael@0: gfxWarning() << "Failed to create PathGeometry. Code: " << hr; michael@0: return nullptr; michael@0: } michael@0: michael@0: RefPtr sink; michael@0: hr = path->Open(byRef(sink)); michael@0: if (FAILED(hr)) { michael@0: gfxWarning() << "Failed to open Geometry for writing. Code: " << hr; michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aFillRule == FillRule::FILL_WINDING) { michael@0: sink->SetFillMode(D2D1_FILL_MODE_WINDING); michael@0: } michael@0: michael@0: if (mEndedActive) { michael@0: OpeningGeometrySink wrapSink(sink); michael@0: mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, michael@0: D2DMatrix(aTransform), michael@0: &wrapSink); michael@0: } else { michael@0: mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, michael@0: D2DMatrix(aTransform), michael@0: sink); michael@0: } michael@0: michael@0: RefPtr pathBuilder = new PathBuilderD2D(sink, path, aFillRule); michael@0: michael@0: pathBuilder->mCurrentPoint = aTransform * mEndPoint; michael@0: michael@0: if (mEndedActive) { michael@0: pathBuilder->mFigureActive = true; michael@0: } michael@0: michael@0: return pathBuilder; michael@0: } michael@0: michael@0: void michael@0: PathD2D::StreamToSink(PathSink *aSink) const michael@0: { michael@0: HRESULT hr; michael@0: michael@0: StreamingGeometrySink sink(aSink); michael@0: michael@0: hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, michael@0: D2D1::IdentityMatrix(), &sink); michael@0: michael@0: if (FAILED(hr)) { michael@0: gfxWarning() << "Failed to stream D2D path to sink. Code: " << hr; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const michael@0: { michael@0: BOOL result; michael@0: michael@0: HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result); michael@0: michael@0: if (FAILED(hr)) { michael@0: // Log michael@0: return false; michael@0: } michael@0: michael@0: return !!result; michael@0: } michael@0: michael@0: bool michael@0: PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, michael@0: const Point &aPoint, michael@0: const Matrix &aTransform) const michael@0: { michael@0: BOOL result; michael@0: michael@0: RefPtr strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); michael@0: HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint), michael@0: aStrokeOptions.mLineWidth, michael@0: strokeStyle, michael@0: D2DMatrix(aTransform), michael@0: &result); michael@0: michael@0: if (FAILED(hr)) { michael@0: // Log michael@0: return false; michael@0: } michael@0: michael@0: return !!result; michael@0: } michael@0: michael@0: Rect michael@0: PathD2D::GetBounds(const Matrix &aTransform) const michael@0: { michael@0: D2D1_RECT_F d2dBounds; michael@0: michael@0: HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds); michael@0: michael@0: Rect bounds = ToRect(d2dBounds); michael@0: if (FAILED(hr) || !bounds.IsFinite()) { michael@0: gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr; michael@0: return Rect(); michael@0: } michael@0: michael@0: return bounds; michael@0: } michael@0: michael@0: Rect michael@0: PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions, michael@0: const Matrix &aTransform) const michael@0: { michael@0: D2D1_RECT_F d2dBounds; michael@0: michael@0: RefPtr strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); michael@0: HRESULT hr = michael@0: mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle, michael@0: D2DMatrix(aTransform), &d2dBounds); michael@0: michael@0: Rect bounds = ToRect(d2dBounds); michael@0: if (FAILED(hr) || !bounds.IsFinite()) { michael@0: gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr; michael@0: return Rect(); michael@0: } michael@0: michael@0: return bounds; michael@0: } michael@0: michael@0: } michael@0: }