summaryrefslogtreecommitdiffstats
path: root/gfx/2d/PathD2D.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/2d/PathD2D.cpp')
-rw-r--r--gfx/2d/PathD2D.cpp523
1 files changed, 523 insertions, 0 deletions
diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp
new file mode 100644
index 000000000..b10e456e1
--- /dev/null
+++ b/gfx/2d/PathD2D.cpp
@@ -0,0 +1,523 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PathD2D.h"
+#include "HelpersD2D.h"
+#include <math.h>
+#include "DrawTargetD2D1.h"
+#include "Logging.h"
+
+namespace mozilla {
+namespace gfx {
+
+// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows
+// a geometry to be duplicated into a geometry sink, while removing the final
+// figure end and thus allowing a figure that was implicitly closed to be
+// continued.
+class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink
+{
+public:
+ OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink)
+ : mSink(aSink)
+ , mNeedsFigureEnded(false)
+ {
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
+ {
+ if (!aPtr) {
+ return E_POINTER;
+ }
+
+ if (aIID == IID_IUnknown) {
+ *aPtr = static_cast<IUnknown*>(this);
+ return S_OK;
+ } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
+ *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return 1;
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return 1;
+ }
+
+ // We ignore SetFillMode, the copier will decide.
+ STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
+ { EnsureFigureEnded(); return; }
+ STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
+ { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); }
+ STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
+ { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); }
+ STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
+ { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); }
+ STDMETHOD(Close)()
+ { /* Should never be called! */ return S_OK; }
+ STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
+ { return mSink->SetSegmentFlags(aFlags); }
+
+ // This function is special - it's the reason this class exists.
+ // It needs to intercept the very last endfigure. So that a user can
+ // continue writing to this sink as if they never stopped.
+ STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
+ {
+ if (aEnd == D2D1_FIGURE_END_CLOSED) {
+ return mSink->EndFigure(aEnd);
+ } else {
+ mNeedsFigureEnded = true;
+ }
+ }
+private:
+ void EnsureFigureEnded()
+ {
+ if (mNeedsFigureEnded) {
+ mSink->EndFigure(D2D1_FIGURE_END_OPEN);
+ mNeedsFigureEnded = false;
+ }
+ }
+
+ ID2D1SimplifiedGeometrySink *mSink;
+ bool mNeedsFigureEnded;
+};
+
+class MOZ_STACK_CLASS AutoRestoreFP
+{
+public:
+ AutoRestoreFP()
+ {
+ // save the current floating point control word
+ _controlfp_s(&savedFPSetting, 0, 0);
+ UINT unused;
+ // set the floating point control word to its default value
+ _controlfp_s(&unused, _CW_DEFAULT, MCW_PC);
+ }
+ ~AutoRestoreFP()
+ {
+ UINT unused;
+ // restore the saved floating point control word
+ _controlfp_s(&unused, savedFPSetting, MCW_PC);
+ }
+private:
+ UINT savedFPSetting;
+};
+
+// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may
+// get called from D2D with nonstandard floating point settings (see comments in
+// bug 1134549) - use AutoRestoreFP to reset the floating point control word to
+// what we expect
+class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink
+{
+public:
+ StreamingGeometrySink(PathSink *aSink)
+ : mSink(aSink)
+ {
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
+ {
+ if (!aPtr) {
+ return E_POINTER;
+ }
+
+ if (aIID == IID_IUnknown) {
+ *aPtr = static_cast<IUnknown*>(this);
+ return S_OK;
+ } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
+ *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return 1;
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return 1;
+ }
+
+ // We ignore SetFillMode, this depends on the destination sink.
+ STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
+ { return; }
+ STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
+ {
+ AutoRestoreFP resetFloatingPoint;
+ mSink->MoveTo(ToPoint(aPoint));
+ }
+ STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
+ {
+ AutoRestoreFP resetFloatingPoint;
+ for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); }
+ }
+ STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
+ {
+ AutoRestoreFP resetFloatingPoint;
+ for (UINT i = 0; i < aCount; i++) {
+ mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3));
+ }
+ }
+ STDMETHOD(Close)()
+ { /* Should never be called! */ return S_OK; }
+ STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
+ { /* Should never be called! */ }
+
+ STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
+ {
+ AutoRestoreFP resetFloatingPoint;
+ if (aEnd == D2D1_FIGURE_END_CLOSED) {
+ return mSink->Close();
+ }
+ }
+private:
+
+ PathSink *mSink;
+};
+
+PathBuilderD2D::~PathBuilderD2D()
+{
+}
+
+void
+PathBuilderD2D::MoveTo(const Point &aPoint)
+{
+ if (mFigureActive) {
+ mSink->EndFigure(D2D1_FIGURE_END_OPEN);
+ mFigureActive = false;
+ }
+ EnsureActive(aPoint);
+ mCurrentPoint = aPoint;
+}
+
+void
+PathBuilderD2D::LineTo(const Point &aPoint)
+{
+ EnsureActive(aPoint);
+ mSink->AddLine(D2DPoint(aPoint));
+
+ mCurrentPoint = aPoint;
+}
+
+void
+PathBuilderD2D::BezierTo(const Point &aCP1,
+ const Point &aCP2,
+ const Point &aCP3)
+ {
+ EnsureActive(aCP1);
+ mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1),
+ D2DPoint(aCP2),
+ D2DPoint(aCP3)));
+
+ mCurrentPoint = aCP3;
+}
+
+void
+PathBuilderD2D::QuadraticBezierTo(const Point &aCP1,
+ const Point &aCP2)
+{
+ EnsureActive(aCP1);
+ mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1),
+ D2DPoint(aCP2)));
+
+ mCurrentPoint = aCP2;
+}
+
+void
+PathBuilderD2D::Close()
+{
+ if (mFigureActive) {
+ mSink->EndFigure(D2D1_FIGURE_END_CLOSED);
+
+ mFigureActive = false;
+
+ EnsureActive(mBeginPoint);
+ }
+}
+
+void
+PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
+ Float aEndAngle, bool aAntiClockwise)
+{
+ MOZ_ASSERT(aRadius >= 0);
+
+ if (aAntiClockwise && aStartAngle < aEndAngle) {
+ // D2D does things a little differently, and draws the arc by specifying an
+ // beginning and an end point. This means the circle will be the wrong way
+ // around if the start angle is smaller than the end angle. It might seem
+ // tempting to invert aAntiClockwise but that would change the sweeping
+ // direction of the arc so instead we exchange start/begin.
+ Float oldStart = aStartAngle;
+ aStartAngle = aEndAngle;
+ aEndAngle = oldStart;
+ }
+
+ // XXX - Workaround for now, D2D does not appear to do the desired thing when
+ // the angle sweeps a complete circle.
+ bool fullCircle = false;
+ if (aEndAngle - aStartAngle >= 2 * M_PI) {
+ fullCircle = true;
+ aEndAngle = Float(aStartAngle + M_PI * 1.9999);
+ } else if (aStartAngle - aEndAngle >= 2 * M_PI) {
+ fullCircle = true;
+ aStartAngle = Float(aEndAngle + M_PI * 1.9999);
+ }
+
+ Point startPoint;
+ startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
+ startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
+
+ if (!mFigureActive) {
+ EnsureActive(startPoint);
+ } else {
+ mSink->AddLine(D2DPoint(startPoint));
+ }
+
+ Point endPoint;
+ endPoint.x = aOrigin.x + aRadius * cosf(aEndAngle);
+ endPoint.y = aOrigin.y + aRadius * sinf(aEndAngle);
+
+ D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL;
+ D2D1_SWEEP_DIRECTION direction =
+ aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE :
+ D2D1_SWEEP_DIRECTION_CLOCKWISE;
+
+ // if startPoint and endPoint of our circle are too close there are D2D issues
+ // with drawing the circle as a single arc
+ const Float kEpsilon = 1e-5f;
+ if (!fullCircle ||
+ (std::abs(startPoint.x - endPoint.x) +
+ std::abs(startPoint.y - endPoint.y) > kEpsilon)) {
+
+ if (aAntiClockwise) {
+ if (aStartAngle - aEndAngle > M_PI) {
+ arcSize = D2D1_ARC_SIZE_LARGE;
+ }
+ } else {
+ if (aEndAngle - aStartAngle > M_PI) {
+ arcSize = D2D1_ARC_SIZE_LARGE;
+ }
+ }
+
+ mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint),
+ D2D1::SizeF(aRadius, aRadius),
+ 0.0f,
+ direction,
+ arcSize));
+ }
+ else {
+ // our first workaround attempt didn't work, so instead draw the circle as
+ // two half-circles
+ Float midAngle = aEndAngle > aStartAngle ?
+ Float(aStartAngle + M_PI) : Float(aEndAngle + M_PI);
+ Point midPoint;
+ midPoint.x = aOrigin.x + aRadius * cosf(midAngle);
+ midPoint.y = aOrigin.y + aRadius * sinf(midAngle);
+
+ mSink->AddArc(D2D1::ArcSegment(D2DPoint(midPoint),
+ D2D1::SizeF(aRadius, aRadius),
+ 0.0f,
+ direction,
+ arcSize));
+
+ // if the adjusted endPoint computed above is used here and endPoint !=
+ // startPoint then this half of the circle won't render...
+ mSink->AddArc(D2D1::ArcSegment(D2DPoint(startPoint),
+ D2D1::SizeF(aRadius, aRadius),
+ 0.0f,
+ direction,
+ arcSize));
+ }
+
+ mCurrentPoint = endPoint;
+}
+
+Point
+PathBuilderD2D::CurrentPoint() const
+{
+ return mCurrentPoint;
+}
+
+void
+PathBuilderD2D::EnsureActive(const Point &aPoint)
+{
+ if (!mFigureActive) {
+ mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED);
+ mBeginPoint = aPoint;
+ mFigureActive = true;
+ }
+}
+
+already_AddRefed<Path>
+PathBuilderD2D::Finish()
+{
+ if (mFigureActive) {
+ mSink->EndFigure(D2D1_FIGURE_END_OPEN);
+ }
+
+ HRESULT hr = mSink->Close();
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to close PathSink. Code: " << hexa(hr);
+ return nullptr;
+ }
+
+ return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mCurrentPoint, mFillRule, mBackendType);
+}
+
+already_AddRefed<PathBuilder>
+PathD2D::CopyToBuilder(FillRule aFillRule) const
+{
+ return TransformedCopyToBuilder(Matrix(), aFillRule);
+}
+
+already_AddRefed<PathBuilder>
+PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
+{
+ RefPtr<ID2D1PathGeometry> path;
+ HRESULT hr = DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr);
+ return nullptr;
+ }
+
+ RefPtr<ID2D1GeometrySink> sink;
+ hr = path->Open(getter_AddRefs(sink));
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr);
+ return nullptr;
+ }
+
+ if (aFillRule == FillRule::FILL_WINDING) {
+ sink->SetFillMode(D2D1_FILL_MODE_WINDING);
+ }
+
+ if (mEndedActive) {
+ OpeningGeometrySink wrapSink(sink);
+ hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
+ D2DMatrix(aTransform),
+ &wrapSink);
+ } else {
+ hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
+ D2DMatrix(aTransform),
+ sink);
+ }
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: " << hexa(hr) << " Active: " << mEndedActive;
+ return nullptr;
+ }
+
+ RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule, mBackendType);
+
+ pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint);
+
+ if (mEndedActive) {
+ pathBuilder->mFigureActive = true;
+ }
+
+ return pathBuilder.forget();
+}
+
+void
+PathD2D::StreamToSink(PathSink *aSink) const
+{
+ HRESULT hr;
+
+ StreamingGeometrySink sink(aSink);
+
+ hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
+ D2D1::IdentityMatrix(), &sink);
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr);
+ return;
+ }
+}
+
+bool
+PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
+{
+ BOOL result;
+
+ HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
+
+ if (FAILED(hr)) {
+ // Log
+ return false;
+ }
+
+ return !!result;
+}
+
+bool
+PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
+ const Point &aPoint,
+ const Matrix &aTransform) const
+{
+ BOOL result;
+
+ RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
+ HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint),
+ aStrokeOptions.mLineWidth,
+ strokeStyle,
+ D2DMatrix(aTransform),
+ &result);
+
+ if (FAILED(hr)) {
+ // Log
+ return false;
+ }
+
+ return !!result;
+}
+
+Rect
+PathD2D::GetBounds(const Matrix &aTransform) const
+{
+ D2D1_RECT_F d2dBounds;
+
+ HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds);
+
+ Rect bounds = ToRect(d2dBounds);
+ if (FAILED(hr) || !bounds.IsFinite()) {
+ gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr);
+ return Rect();
+ }
+
+ return bounds;
+}
+
+Rect
+PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
+ const Matrix &aTransform) const
+{
+ D2D1_RECT_F d2dBounds;
+
+ RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
+ HRESULT hr =
+ mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle,
+ D2DMatrix(aTransform), &d2dBounds);
+
+ Rect bounds = ToRect(d2dBounds);
+ if (FAILED(hr) || !bounds.IsFinite()) {
+ gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr);
+ return Rect();
+ }
+
+ return bounds;
+}
+
+}
+}