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"
12 #include "PathHelpers.h"
17 already_AddRefed
<PathBuilder
> PathBuilderD2D::Create(FillRule aFillRule
) {
18 RefPtr
<ID2D1PathGeometry
> path
;
20 DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path
));
23 gfxWarning() << "Failed to create Direct2D Path Geometry. Code: "
28 RefPtr
<ID2D1GeometrySink
> sink
;
29 hr
= path
->Open(getter_AddRefs(sink
));
31 gfxWarning() << "Failed to access Direct2D Path Geometry. Code: "
36 if (aFillRule
== FillRule::FILL_WINDING
) {
37 sink
->SetFillMode(D2D1_FILL_MODE_WINDING
);
40 return MakeAndAddRef
<PathBuilderD2D
>(sink
, path
, aFillRule
,
41 BackendType::DIRECT2D1_1
);
44 // This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows
45 // a geometry to be duplicated into a geometry sink, while removing the final
46 // figure end and thus allowing a figure that was implicitly closed to be
48 class OpeningGeometrySink
: public ID2D1SimplifiedGeometrySink
{
50 explicit OpeningGeometrySink(ID2D1SimplifiedGeometrySink
* aSink
)
51 : mSink(aSink
), mNeedsFigureEnded(false) {}
53 HRESULT STDMETHODCALLTYPE
QueryInterface(const IID
& aIID
, void** aPtr
) {
58 if (aIID
== IID_IUnknown
) {
59 *aPtr
= static_cast<IUnknown
*>(this);
61 } else if (aIID
== IID_ID2D1SimplifiedGeometrySink
) {
62 *aPtr
= static_cast<ID2D1SimplifiedGeometrySink
*>(this);
69 ULONG STDMETHODCALLTYPE
AddRef() { return 1; }
71 ULONG STDMETHODCALLTYPE
Release() { return 1; }
73 // We ignore SetFillMode, the copier will decide.
74 STDMETHOD_(void, SetFillMode
)(D2D1_FILL_MODE aMode
) {
78 STDMETHOD_(void, BeginFigure
)
79 (D2D1_POINT_2F aPoint
, D2D1_FIGURE_BEGIN aBegin
) {
81 return mSink
->BeginFigure(aPoint
, aBegin
);
83 STDMETHOD_(void, AddLines
)(const D2D1_POINT_2F
* aLines
, UINT aCount
) {
85 return mSink
->AddLines(aLines
, aCount
);
87 STDMETHOD_(void, AddBeziers
)
88 (const D2D1_BEZIER_SEGMENT
* aSegments
, UINT aCount
) {
90 return mSink
->AddBeziers(aSegments
, aCount
);
92 STDMETHOD(Close
)() { /* Should never be called! */
95 STDMETHOD_(void, SetSegmentFlags
)(D2D1_PATH_SEGMENT aFlags
) {
96 return mSink
->SetSegmentFlags(aFlags
);
99 // This function is special - it's the reason this class exists.
100 // It needs to intercept the very last endfigure. So that a user can
101 // continue writing to this sink as if they never stopped.
102 STDMETHOD_(void, EndFigure
)(D2D1_FIGURE_END aEnd
) {
103 if (aEnd
== D2D1_FIGURE_END_CLOSED
) {
104 return mSink
->EndFigure(aEnd
);
106 mNeedsFigureEnded
= true;
111 void EnsureFigureEnded() {
112 if (mNeedsFigureEnded
) {
113 mSink
->EndFigure(D2D1_FIGURE_END_OPEN
);
114 mNeedsFigureEnded
= false;
118 ID2D1SimplifiedGeometrySink
* mSink
;
119 bool mNeedsFigureEnded
;
122 PathBuilderD2D::~PathBuilderD2D() {}
124 void PathBuilderD2D::MoveTo(const Point
& aPoint
) {
126 mSink
->EndFigure(D2D1_FIGURE_END_OPEN
);
127 mFigureActive
= false;
129 EnsureActive(aPoint
);
130 mCurrentPoint
= aPoint
;
133 void PathBuilderD2D::LineTo(const Point
& aPoint
) {
134 EnsureActive(aPoint
);
135 mSink
->AddLine(D2DPoint(aPoint
));
137 mCurrentPoint
= aPoint
;
138 mFigureEmpty
= false;
141 void PathBuilderD2D::BezierTo(const Point
& aCP1
, const Point
& aCP2
,
145 D2D1::BezierSegment(D2DPoint(aCP1
), D2DPoint(aCP2
), D2DPoint(aCP3
)));
147 mCurrentPoint
= aCP3
;
148 mFigureEmpty
= false;
151 void PathBuilderD2D::QuadraticBezierTo(const Point
& aCP1
, const Point
& aCP2
) {
153 mSink
->AddQuadraticBezier(
154 D2D1::QuadraticBezierSegment(D2DPoint(aCP1
), D2DPoint(aCP2
)));
156 mCurrentPoint
= aCP2
;
157 mFigureEmpty
= false;
160 void PathBuilderD2D::Close() {
162 mSink
->EndFigure(D2D1_FIGURE_END_CLOSED
);
164 mFigureActive
= false;
166 EnsureActive(mBeginPoint
);
170 void PathBuilderD2D::Arc(const Point
& aOrigin
, Float aRadius
, Float aStartAngle
,
171 Float aEndAngle
, bool aAntiClockwise
) {
172 MOZ_ASSERT(aRadius
>= 0);
174 // We want aEndAngle to come numerically after aStartAngle when taking into
175 // account the sweep direction so that our calculation of the arcSize below
176 // (large or small) works.
177 Float sweepDirection
= aAntiClockwise
? -1.0f
: 1.0f
;
179 Float arcSweepLeft
= (aEndAngle
- aStartAngle
) * sweepDirection
;
180 if (arcSweepLeft
< 0) {
181 // This calculation moves aStartAngle by a multiple of 2*Pi so that it is
182 // the closest it can be to aEndAngle and still be numerically before
183 // aEndAngle when taking into account sweepDirection.
184 arcSweepLeft
= Float(2.0f
* M_PI
) + fmodf(arcSweepLeft
, Float(2.0f
* M_PI
));
185 aStartAngle
= aEndAngle
- arcSweepLeft
* sweepDirection
;
188 // XXX - Workaround for now, D2D does not appear to do the desired thing when
189 // the angle sweeps a complete circle.
190 bool fullCircle
= false;
191 if (aEndAngle
- aStartAngle
>= 1.9999 * M_PI
) {
193 aEndAngle
= Float(aStartAngle
+ M_PI
* 1.9999);
194 } else if (aStartAngle
- aEndAngle
>= 1.9999 * M_PI
) {
196 aStartAngle
= Float(aEndAngle
+ M_PI
* 1.9999);
200 startPoint
.x
= aOrigin
.x
+ aRadius
* cos(aStartAngle
);
201 startPoint
.y
= aOrigin
.y
+ aRadius
* sin(aStartAngle
);
203 if (!mFigureActive
) {
204 EnsureActive(startPoint
);
206 mSink
->AddLine(D2DPoint(startPoint
));
210 endPoint
.x
= aOrigin
.x
+ aRadius
* cosf(aEndAngle
);
211 endPoint
.y
= aOrigin
.y
+ aRadius
* sinf(aEndAngle
);
213 D2D1_ARC_SIZE arcSize
= D2D1_ARC_SIZE_SMALL
;
214 D2D1_SWEEP_DIRECTION direction
= aAntiClockwise
215 ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE
216 : D2D1_SWEEP_DIRECTION_CLOCKWISE
;
218 // if startPoint and endPoint of our circle are too close there are D2D issues
219 // with drawing the circle as a single arc
220 const Float kEpsilon
= 1e-5f
;
221 if (!fullCircle
|| (std::abs(startPoint
.x
- endPoint
.x
) +
222 std::abs(startPoint
.y
- endPoint
.y
) >
224 if (aAntiClockwise
) {
225 if (aStartAngle
- aEndAngle
> M_PI
) {
226 arcSize
= D2D1_ARC_SIZE_LARGE
;
229 if (aEndAngle
- aStartAngle
> M_PI
) {
230 arcSize
= D2D1_ARC_SIZE_LARGE
;
234 mSink
->AddArc(D2D1::ArcSegment(D2DPoint(endPoint
),
235 D2D1::SizeF(aRadius
, aRadius
), 0.0f
,
236 direction
, arcSize
));
238 // our first workaround attempt didn't work, so instead draw the circle as
240 Float midAngle
= aEndAngle
> aStartAngle
? Float(aStartAngle
+ M_PI
)
241 : Float(aEndAngle
+ M_PI
);
243 midPoint
.x
= aOrigin
.x
+ aRadius
* cosf(midAngle
);
244 midPoint
.y
= aOrigin
.y
+ aRadius
* sinf(midAngle
);
246 mSink
->AddArc(D2D1::ArcSegment(D2DPoint(midPoint
),
247 D2D1::SizeF(aRadius
, aRadius
), 0.0f
,
248 direction
, arcSize
));
250 // if the adjusted endPoint computed above is used here and endPoint !=
251 // startPoint then this half of the circle won't render...
252 mSink
->AddArc(D2D1::ArcSegment(D2DPoint(startPoint
),
253 D2D1::SizeF(aRadius
, aRadius
), 0.0f
,
254 direction
, arcSize
));
257 mCurrentPoint
= endPoint
;
258 mFigureEmpty
= false;
261 void PathBuilderD2D::EnsureActive(const Point
& aPoint
) {
262 if (!mFigureActive
) {
263 mSink
->BeginFigure(D2DPoint(aPoint
), D2D1_FIGURE_BEGIN_FILLED
);
264 mBeginPoint
= aPoint
;
265 mFigureActive
= true;
269 already_AddRefed
<Path
> PathBuilderD2D::Finish() {
271 mSink
->EndFigure(D2D1_FIGURE_END_OPEN
);
274 HRESULT hr
= mSink
->Close();
276 gfxCriticalNote
<< "Failed to close PathSink. Code: " << hexa(hr
);
280 return MakeAndAddRef
<PathD2D
>(mGeometry
, mFigureActive
, mFigureEmpty
,
281 mCurrentPoint
, mFillRule
, mBackendType
);
284 already_AddRefed
<PathBuilder
> PathD2D::CopyToBuilder(FillRule aFillRule
) const {
285 return TransformedCopyToBuilder(Matrix(), aFillRule
);
288 already_AddRefed
<PathBuilder
> PathD2D::TransformedCopyToBuilder(
289 const Matrix
& aTransform
, FillRule aFillRule
) const {
290 RefPtr
<ID2D1PathGeometry
> path
;
292 DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path
));
295 gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr
);
299 RefPtr
<ID2D1GeometrySink
> sink
;
300 hr
= path
->Open(getter_AddRefs(sink
));
302 gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr
);
306 if (aFillRule
== FillRule::FILL_WINDING
) {
307 sink
->SetFillMode(D2D1_FILL_MODE_WINDING
);
311 OpeningGeometrySink
wrapSink(sink
);
312 hr
= mGeometry
->Simplify(
313 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES
,
314 D2DMatrix(aTransform
), &wrapSink
);
316 hr
= mGeometry
->Simplify(
317 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES
,
318 D2DMatrix(aTransform
), sink
);
321 gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: "
322 << hexa(hr
) << " Active: " << mEndedActive
;
326 RefPtr
<PathBuilderD2D
> pathBuilder
=
327 new PathBuilderD2D(sink
, path
, aFillRule
, mBackendType
);
329 pathBuilder
->mCurrentPoint
= aTransform
.TransformPoint(mEndPoint
);
332 pathBuilder
->mFigureActive
= true;
335 return pathBuilder
.forget();
338 void PathD2D::StreamToSink(PathSink
* aSink
) const {
341 StreamingGeometrySink
sink(aSink
);
343 hr
= mGeometry
->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES
,
344 D2D1::IdentityMatrix(), &sink
);
347 gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr
);
352 bool PathD2D::ContainsPoint(const Point
& aPoint
,
353 const Matrix
& aTransform
) const {
354 if (!aTransform
.Determinant()) {
355 // If the transform is not invertible, then don't consider point inside.
361 HRESULT hr
= mGeometry
->FillContainsPoint(
362 D2DPoint(aPoint
), D2DMatrix(aTransform
), 0.001f
, &result
);
372 bool PathD2D::StrokeContainsPoint(const StrokeOptions
& aStrokeOptions
,
374 const Matrix
& aTransform
) const {
375 if (!aTransform
.Determinant()) {
376 // If the transform is not invertible, then don't consider point inside.
382 RefPtr
<ID2D1StrokeStyle
> strokeStyle
=
383 CreateStrokeStyleForOptions(aStrokeOptions
);
384 HRESULT hr
= mGeometry
->StrokeContainsPoint(
385 D2DPoint(aPoint
), aStrokeOptions
.mLineWidth
, strokeStyle
,
386 D2DMatrix(aTransform
), &result
);
396 Rect
PathD2D::GetBounds(const Matrix
& aTransform
) const {
397 D2D1_RECT_F d2dBounds
;
399 HRESULT hr
= mGeometry
->GetBounds(D2DMatrix(aTransform
), &d2dBounds
);
401 Rect bounds
= ToRect(d2dBounds
);
402 if (FAILED(hr
) || !bounds
.IsFinite()) {
403 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr
);
410 Rect
PathD2D::GetStrokedBounds(const StrokeOptions
& aStrokeOptions
,
411 const Matrix
& aTransform
) const {
412 D2D1_RECT_F d2dBounds
;
414 RefPtr
<ID2D1StrokeStyle
> strokeStyle
=
415 CreateStrokeStyleForOptions(aStrokeOptions
);
417 mGeometry
->GetWidenedBounds(aStrokeOptions
.mLineWidth
, strokeStyle
,
418 D2DMatrix(aTransform
), &d2dBounds
);
420 Rect bounds
= ToRect(d2dBounds
);
421 if (FAILED(hr
) || !bounds
.IsFinite()) {
422 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr
);
430 } // namespace mozilla