1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "HelpersD2D.h"
10 #include "DrawTargetD2D1.h"
16 // This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows
17 // a geometry to be duplicated into a geometry sink, while removing the final
18 // figure end and thus allowing a figure that was implicitly closed to be
20 class OpeningGeometrySink
: public ID2D1SimplifiedGeometrySink
{
22 explicit OpeningGeometrySink(ID2D1SimplifiedGeometrySink
* aSink
)
23 : mSink(aSink
), mNeedsFigureEnded(false) {}
25 HRESULT STDMETHODCALLTYPE
QueryInterface(const IID
& aIID
, void** aPtr
) {
30 if (aIID
== IID_IUnknown
) {
31 *aPtr
= static_cast<IUnknown
*>(this);
33 } else if (aIID
== IID_ID2D1SimplifiedGeometrySink
) {
34 *aPtr
= static_cast<ID2D1SimplifiedGeometrySink
*>(this);
41 ULONG STDMETHODCALLTYPE
AddRef() { return 1; }
43 ULONG STDMETHODCALLTYPE
Release() { return 1; }
45 // We ignore SetFillMode, the copier will decide.
46 STDMETHOD_(void, SetFillMode
)(D2D1_FILL_MODE aMode
) {
50 STDMETHOD_(void, BeginFigure
)
51 (D2D1_POINT_2F aPoint
, D2D1_FIGURE_BEGIN aBegin
) {
53 return mSink
->BeginFigure(aPoint
, aBegin
);
55 STDMETHOD_(void, AddLines
)(const D2D1_POINT_2F
* aLines
, UINT aCount
) {
57 return mSink
->AddLines(aLines
, aCount
);
59 STDMETHOD_(void, AddBeziers
)
60 (const D2D1_BEZIER_SEGMENT
* aSegments
, UINT aCount
) {
62 return mSink
->AddBeziers(aSegments
, aCount
);
64 STDMETHOD(Close
)() { /* Should never be called! */
67 STDMETHOD_(void, SetSegmentFlags
)(D2D1_PATH_SEGMENT aFlags
) {
68 return mSink
->SetSegmentFlags(aFlags
);
71 // This function is special - it's the reason this class exists.
72 // It needs to intercept the very last endfigure. So that a user can
73 // continue writing to this sink as if they never stopped.
74 STDMETHOD_(void, EndFigure
)(D2D1_FIGURE_END aEnd
) {
75 if (aEnd
== D2D1_FIGURE_END_CLOSED
) {
76 return mSink
->EndFigure(aEnd
);
78 mNeedsFigureEnded
= true;
83 void EnsureFigureEnded() {
84 if (mNeedsFigureEnded
) {
85 mSink
->EndFigure(D2D1_FIGURE_END_OPEN
);
86 mNeedsFigureEnded
= false;
90 ID2D1SimplifiedGeometrySink
* mSink
;
91 bool mNeedsFigureEnded
;
94 PathBuilderD2D::~PathBuilderD2D() {}
96 void PathBuilderD2D::MoveTo(const Point
& aPoint
) {
98 mSink
->EndFigure(D2D1_FIGURE_END_OPEN
);
99 mFigureActive
= false;
101 EnsureActive(aPoint
);
102 mCurrentPoint
= aPoint
;
105 void PathBuilderD2D::LineTo(const Point
& aPoint
) {
106 EnsureActive(aPoint
);
107 mSink
->AddLine(D2DPoint(aPoint
));
109 mCurrentPoint
= aPoint
;
112 void PathBuilderD2D::BezierTo(const Point
& aCP1
, const Point
& aCP2
,
116 D2D1::BezierSegment(D2DPoint(aCP1
), D2DPoint(aCP2
), D2DPoint(aCP3
)));
118 mCurrentPoint
= aCP3
;
121 void PathBuilderD2D::QuadraticBezierTo(const Point
& aCP1
, const Point
& aCP2
) {
123 mSink
->AddQuadraticBezier(
124 D2D1::QuadraticBezierSegment(D2DPoint(aCP1
), D2DPoint(aCP2
)));
126 mCurrentPoint
= aCP2
;
129 void PathBuilderD2D::Close() {
131 mSink
->EndFigure(D2D1_FIGURE_END_CLOSED
);
133 mFigureActive
= false;
135 EnsureActive(mBeginPoint
);
139 void PathBuilderD2D::Arc(const Point
& aOrigin
, Float aRadius
, Float aStartAngle
,
140 Float aEndAngle
, bool aAntiClockwise
) {
141 MOZ_ASSERT(aRadius
>= 0);
143 if (aAntiClockwise
&& aStartAngle
< aEndAngle
) {
144 // D2D does things a little differently, and draws the arc by specifying an
145 // beginning and an end point. This means the circle will be the wrong way
146 // around if the start angle is smaller than the end angle. It might seem
147 // tempting to invert aAntiClockwise but that would change the sweeping
148 // direction of the arc so instead we exchange start/begin.
149 Float oldStart
= aStartAngle
;
150 aStartAngle
= aEndAngle
;
151 aEndAngle
= oldStart
;
154 // XXX - Workaround for now, D2D does not appear to do the desired thing when
155 // the angle sweeps a complete circle.
156 bool fullCircle
= false;
157 if (aEndAngle
- aStartAngle
>= 2 * M_PI
) {
159 aEndAngle
= Float(aStartAngle
+ M_PI
* 1.9999);
160 } else if (aStartAngle
- aEndAngle
>= 2 * M_PI
) {
162 aStartAngle
= Float(aEndAngle
+ M_PI
* 1.9999);
166 startPoint
.x
= aOrigin
.x
+ aRadius
* cos(aStartAngle
);
167 startPoint
.y
= aOrigin
.y
+ aRadius
* sin(aStartAngle
);
169 if (!mFigureActive
) {
170 EnsureActive(startPoint
);
172 mSink
->AddLine(D2DPoint(startPoint
));
176 endPoint
.x
= aOrigin
.x
+ aRadius
* cosf(aEndAngle
);
177 endPoint
.y
= aOrigin
.y
+ aRadius
* sinf(aEndAngle
);
179 D2D1_ARC_SIZE arcSize
= D2D1_ARC_SIZE_SMALL
;
180 D2D1_SWEEP_DIRECTION direction
= aAntiClockwise
181 ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE
182 : D2D1_SWEEP_DIRECTION_CLOCKWISE
;
184 // if startPoint and endPoint of our circle are too close there are D2D issues
185 // with drawing the circle as a single arc
186 const Float kEpsilon
= 1e-5f
;
187 if (!fullCircle
|| (std::abs(startPoint
.x
- endPoint
.x
) +
188 std::abs(startPoint
.y
- endPoint
.y
) >
190 if (aAntiClockwise
) {
191 if (aStartAngle
- aEndAngle
> M_PI
) {
192 arcSize
= D2D1_ARC_SIZE_LARGE
;
195 if (aEndAngle
- aStartAngle
> M_PI
) {
196 arcSize
= D2D1_ARC_SIZE_LARGE
;
200 mSink
->AddArc(D2D1::ArcSegment(D2DPoint(endPoint
),
201 D2D1::SizeF(aRadius
, aRadius
), 0.0f
,
202 direction
, arcSize
));
204 // our first workaround attempt didn't work, so instead draw the circle as
206 Float midAngle
= aEndAngle
> aStartAngle
? Float(aStartAngle
+ M_PI
)
207 : Float(aEndAngle
+ M_PI
);
209 midPoint
.x
= aOrigin
.x
+ aRadius
* cosf(midAngle
);
210 midPoint
.y
= aOrigin
.y
+ aRadius
* sinf(midAngle
);
212 mSink
->AddArc(D2D1::ArcSegment(D2DPoint(midPoint
),
213 D2D1::SizeF(aRadius
, aRadius
), 0.0f
,
214 direction
, arcSize
));
216 // if the adjusted endPoint computed above is used here and endPoint !=
217 // startPoint then this half of the circle won't render...
218 mSink
->AddArc(D2D1::ArcSegment(D2DPoint(startPoint
),
219 D2D1::SizeF(aRadius
, aRadius
), 0.0f
,
220 direction
, arcSize
));
223 mCurrentPoint
= endPoint
;
226 void PathBuilderD2D::EnsureActive(const Point
& aPoint
) {
227 if (!mFigureActive
) {
228 mSink
->BeginFigure(D2DPoint(aPoint
), D2D1_FIGURE_BEGIN_FILLED
);
229 mBeginPoint
= aPoint
;
230 mFigureActive
= true;
234 already_AddRefed
<Path
> PathBuilderD2D::Finish() {
236 mSink
->EndFigure(D2D1_FIGURE_END_OPEN
);
239 HRESULT hr
= mSink
->Close();
241 gfxCriticalNote
<< "Failed to close PathSink. Code: " << hexa(hr
);
245 return MakeAndAddRef
<PathD2D
>(mGeometry
, mFigureActive
, mCurrentPoint
,
246 mFillRule
, mBackendType
);
249 already_AddRefed
<PathBuilder
> PathD2D::CopyToBuilder(FillRule aFillRule
) const {
250 return TransformedCopyToBuilder(Matrix(), aFillRule
);
253 already_AddRefed
<PathBuilder
> PathD2D::TransformedCopyToBuilder(
254 const Matrix
& aTransform
, FillRule aFillRule
) const {
255 RefPtr
<ID2D1PathGeometry
> path
;
257 DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path
));
260 gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr
);
264 RefPtr
<ID2D1GeometrySink
> sink
;
265 hr
= path
->Open(getter_AddRefs(sink
));
267 gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr
);
271 if (aFillRule
== FillRule::FILL_WINDING
) {
272 sink
->SetFillMode(D2D1_FILL_MODE_WINDING
);
276 OpeningGeometrySink
wrapSink(sink
);
277 hr
= mGeometry
->Simplify(
278 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES
,
279 D2DMatrix(aTransform
), &wrapSink
);
281 hr
= mGeometry
->Simplify(
282 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES
,
283 D2DMatrix(aTransform
), sink
);
286 gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: "
287 << hexa(hr
) << " Active: " << mEndedActive
;
291 RefPtr
<PathBuilderD2D
> pathBuilder
=
292 new PathBuilderD2D(sink
, path
, aFillRule
, mBackendType
);
294 pathBuilder
->mCurrentPoint
= aTransform
.TransformPoint(mEndPoint
);
297 pathBuilder
->mFigureActive
= true;
300 return pathBuilder
.forget();
303 void PathD2D::StreamToSink(PathSink
* aSink
) const {
306 StreamingGeometrySink
sink(aSink
);
308 hr
= mGeometry
->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES
,
309 D2D1::IdentityMatrix(), &sink
);
312 gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr
);
317 bool PathD2D::ContainsPoint(const Point
& aPoint
,
318 const Matrix
& aTransform
) const {
319 if (!aTransform
.Determinant()) {
320 // If the transform is not invertible, then don't consider point inside.
326 HRESULT hr
= mGeometry
->FillContainsPoint(
327 D2DPoint(aPoint
), D2DMatrix(aTransform
), 0.001f
, &result
);
337 bool PathD2D::StrokeContainsPoint(const StrokeOptions
& aStrokeOptions
,
339 const Matrix
& aTransform
) const {
340 if (!aTransform
.Determinant()) {
341 // If the transform is not invertible, then don't consider point inside.
347 RefPtr
<ID2D1StrokeStyle
> strokeStyle
=
348 CreateStrokeStyleForOptions(aStrokeOptions
);
349 HRESULT hr
= mGeometry
->StrokeContainsPoint(
350 D2DPoint(aPoint
), aStrokeOptions
.mLineWidth
, strokeStyle
,
351 D2DMatrix(aTransform
), &result
);
361 Rect
PathD2D::GetBounds(const Matrix
& aTransform
) const {
362 D2D1_RECT_F d2dBounds
;
364 HRESULT hr
= mGeometry
->GetBounds(D2DMatrix(aTransform
), &d2dBounds
);
366 Rect bounds
= ToRect(d2dBounds
);
367 if (FAILED(hr
) || !bounds
.IsFinite()) {
368 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr
);
375 Rect
PathD2D::GetStrokedBounds(const StrokeOptions
& aStrokeOptions
,
376 const Matrix
& aTransform
) const {
377 D2D1_RECT_F d2dBounds
;
379 RefPtr
<ID2D1StrokeStyle
> strokeStyle
=
380 CreateStrokeStyleForOptions(aStrokeOptions
);
382 mGeometry
->GetWidenedBounds(aStrokeOptions
.mLineWidth
, strokeStyle
,
383 D2DMatrix(aTransform
), &d2dBounds
);
385 Rect bounds
= ToRect(d2dBounds
);
386 if (FAILED(hr
) || !bounds
.IsFinite()) {
387 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr
);
395 } // namespace mozilla