|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "PathD2D.h" |
|
7 #include "HelpersD2D.h" |
|
8 #include <math.h> |
|
9 #include "DrawTargetD2D.h" |
|
10 #include "Logging.h" |
|
11 #include "mozilla/Constants.h" |
|
12 |
|
13 namespace mozilla { |
|
14 namespace gfx { |
|
15 |
|
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 |
|
19 // continued. |
|
20 class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink |
|
21 { |
|
22 public: |
|
23 OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink) |
|
24 : mSink(aSink) |
|
25 , mNeedsFigureEnded(false) |
|
26 { |
|
27 } |
|
28 |
|
29 HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) |
|
30 { |
|
31 if (!aPtr) { |
|
32 return E_POINTER; |
|
33 } |
|
34 |
|
35 if (aIID == IID_IUnknown) { |
|
36 *aPtr = static_cast<IUnknown*>(this); |
|
37 return S_OK; |
|
38 } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { |
|
39 *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); |
|
40 return S_OK; |
|
41 } |
|
42 |
|
43 return E_NOINTERFACE; |
|
44 } |
|
45 |
|
46 ULONG STDMETHODCALLTYPE AddRef() |
|
47 { |
|
48 return 1; |
|
49 } |
|
50 |
|
51 ULONG STDMETHODCALLTYPE Release() |
|
52 { |
|
53 return 1; |
|
54 } |
|
55 |
|
56 // We ignore SetFillMode, the copier will decide. |
|
57 STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) |
|
58 { EnsureFigureEnded(); return; } |
|
59 STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) |
|
60 { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); } |
|
61 STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) |
|
62 { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); } |
|
63 STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) |
|
64 { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); } |
|
65 STDMETHOD(Close)() |
|
66 { /* Should never be called! */ return S_OK; } |
|
67 STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) |
|
68 { return mSink->SetSegmentFlags(aFlags); } |
|
69 |
|
70 // This function is special - it's the reason this class exists. |
|
71 // It needs to intercept the very last endfigure. So that a user can |
|
72 // continue writing to this sink as if they never stopped. |
|
73 STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) |
|
74 { |
|
75 if (aEnd == D2D1_FIGURE_END_CLOSED) { |
|
76 return mSink->EndFigure(aEnd); |
|
77 } else { |
|
78 mNeedsFigureEnded = true; |
|
79 } |
|
80 } |
|
81 private: |
|
82 void EnsureFigureEnded() |
|
83 { |
|
84 if (mNeedsFigureEnded) { |
|
85 mSink->EndFigure(D2D1_FIGURE_END_OPEN); |
|
86 mNeedsFigureEnded = false; |
|
87 } |
|
88 } |
|
89 |
|
90 ID2D1SimplifiedGeometrySink *mSink; |
|
91 bool mNeedsFigureEnded; |
|
92 }; |
|
93 |
|
94 class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink |
|
95 { |
|
96 public: |
|
97 StreamingGeometrySink(PathSink *aSink) |
|
98 : mSink(aSink) |
|
99 { |
|
100 } |
|
101 |
|
102 HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) |
|
103 { |
|
104 if (!aPtr) { |
|
105 return E_POINTER; |
|
106 } |
|
107 |
|
108 if (aIID == IID_IUnknown) { |
|
109 *aPtr = static_cast<IUnknown*>(this); |
|
110 return S_OK; |
|
111 } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { |
|
112 *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); |
|
113 return S_OK; |
|
114 } |
|
115 |
|
116 return E_NOINTERFACE; |
|
117 } |
|
118 |
|
119 ULONG STDMETHODCALLTYPE AddRef() |
|
120 { |
|
121 return 1; |
|
122 } |
|
123 |
|
124 ULONG STDMETHODCALLTYPE Release() |
|
125 { |
|
126 return 1; |
|
127 } |
|
128 |
|
129 // We ignore SetFillMode, this depends on the destination sink. |
|
130 STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) |
|
131 { return; } |
|
132 STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) |
|
133 { mSink->MoveTo(ToPoint(aPoint)); } |
|
134 STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) |
|
135 { for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } } |
|
136 STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) |
|
137 { |
|
138 for (UINT i = 0; i < aCount; i++) { |
|
139 mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); |
|
140 } |
|
141 } |
|
142 STDMETHOD(Close)() |
|
143 { /* Should never be called! */ return S_OK; } |
|
144 STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) |
|
145 { /* Should never be called! */ } |
|
146 |
|
147 STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) |
|
148 { |
|
149 if (aEnd == D2D1_FIGURE_END_CLOSED) { |
|
150 return mSink->Close(); |
|
151 } |
|
152 } |
|
153 private: |
|
154 |
|
155 PathSink *mSink; |
|
156 }; |
|
157 |
|
158 PathBuilderD2D::~PathBuilderD2D() |
|
159 { |
|
160 } |
|
161 |
|
162 void |
|
163 PathBuilderD2D::MoveTo(const Point &aPoint) |
|
164 { |
|
165 if (mFigureActive) { |
|
166 mSink->EndFigure(D2D1_FIGURE_END_OPEN); |
|
167 mFigureActive = false; |
|
168 } |
|
169 EnsureActive(aPoint); |
|
170 mCurrentPoint = aPoint; |
|
171 } |
|
172 |
|
173 void |
|
174 PathBuilderD2D::LineTo(const Point &aPoint) |
|
175 { |
|
176 EnsureActive(aPoint); |
|
177 mSink->AddLine(D2DPoint(aPoint)); |
|
178 |
|
179 mCurrentPoint = aPoint; |
|
180 } |
|
181 |
|
182 void |
|
183 PathBuilderD2D::BezierTo(const Point &aCP1, |
|
184 const Point &aCP2, |
|
185 const Point &aCP3) |
|
186 { |
|
187 EnsureActive(aCP1); |
|
188 mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1), |
|
189 D2DPoint(aCP2), |
|
190 D2DPoint(aCP3))); |
|
191 |
|
192 mCurrentPoint = aCP3; |
|
193 } |
|
194 |
|
195 void |
|
196 PathBuilderD2D::QuadraticBezierTo(const Point &aCP1, |
|
197 const Point &aCP2) |
|
198 { |
|
199 EnsureActive(aCP1); |
|
200 mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1), |
|
201 D2DPoint(aCP2))); |
|
202 |
|
203 mCurrentPoint = aCP2; |
|
204 } |
|
205 |
|
206 void |
|
207 PathBuilderD2D::Close() |
|
208 { |
|
209 if (mFigureActive) { |
|
210 mSink->EndFigure(D2D1_FIGURE_END_CLOSED); |
|
211 |
|
212 mFigureActive = false; |
|
213 |
|
214 EnsureActive(mBeginPoint); |
|
215 } |
|
216 } |
|
217 |
|
218 void |
|
219 PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, |
|
220 Float aEndAngle, bool aAntiClockwise) |
|
221 { |
|
222 if (aAntiClockwise && aStartAngle < aEndAngle) { |
|
223 // D2D does things a little differently, and draws the arc by specifying an |
|
224 // beginning and an end point. This means the circle will be the wrong way |
|
225 // around if the start angle is smaller than the end angle. It might seem |
|
226 // tempting to invert aAntiClockwise but that would change the sweeping |
|
227 // direction of the arc to instead we exchange start/begin. |
|
228 Float oldStart = aStartAngle; |
|
229 aStartAngle = aEndAngle; |
|
230 aEndAngle = oldStart; |
|
231 } |
|
232 |
|
233 // XXX - Workaround for now, D2D does not appear to do the desired thing when |
|
234 // the angle sweeps a complete circle. |
|
235 if (aEndAngle - aStartAngle >= 2 * M_PI) { |
|
236 aEndAngle = Float(aStartAngle + M_PI * 1.9999); |
|
237 } else if (aStartAngle - aEndAngle >= 2 * M_PI) { |
|
238 aStartAngle = Float(aEndAngle + M_PI * 1.9999); |
|
239 } |
|
240 |
|
241 Point startPoint; |
|
242 startPoint.x = aOrigin.x + aRadius * cos(aStartAngle); |
|
243 startPoint.y = aOrigin.y + aRadius * sin(aStartAngle); |
|
244 |
|
245 if (!mFigureActive) { |
|
246 EnsureActive(startPoint); |
|
247 } else { |
|
248 mSink->AddLine(D2DPoint(startPoint)); |
|
249 } |
|
250 |
|
251 Point endPoint; |
|
252 endPoint.x = aOrigin.x + aRadius * cos(aEndAngle); |
|
253 endPoint.y = aOrigin.y + aRadius * sin(aEndAngle); |
|
254 |
|
255 D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL; |
|
256 |
|
257 if (aAntiClockwise) { |
|
258 if (aStartAngle - aEndAngle > M_PI) { |
|
259 arcSize = D2D1_ARC_SIZE_LARGE; |
|
260 } |
|
261 } else { |
|
262 if (aEndAngle - aStartAngle > M_PI) { |
|
263 arcSize = D2D1_ARC_SIZE_LARGE; |
|
264 } |
|
265 } |
|
266 |
|
267 mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint), |
|
268 D2D1::SizeF(aRadius, aRadius), |
|
269 0.0f, |
|
270 aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : |
|
271 D2D1_SWEEP_DIRECTION_CLOCKWISE, |
|
272 arcSize)); |
|
273 |
|
274 mCurrentPoint = endPoint; |
|
275 } |
|
276 |
|
277 Point |
|
278 PathBuilderD2D::CurrentPoint() const |
|
279 { |
|
280 return mCurrentPoint; |
|
281 } |
|
282 |
|
283 void |
|
284 PathBuilderD2D::EnsureActive(const Point &aPoint) |
|
285 { |
|
286 if (!mFigureActive) { |
|
287 mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED); |
|
288 mBeginPoint = aPoint; |
|
289 mFigureActive = true; |
|
290 } |
|
291 } |
|
292 |
|
293 TemporaryRef<Path> |
|
294 PathBuilderD2D::Finish() |
|
295 { |
|
296 if (mFigureActive) { |
|
297 mSink->EndFigure(D2D1_FIGURE_END_OPEN); |
|
298 } |
|
299 |
|
300 HRESULT hr = mSink->Close(); |
|
301 if (FAILED(hr)) { |
|
302 gfxDebug() << "Failed to close PathSink. Code: " << hr; |
|
303 return nullptr; |
|
304 } |
|
305 |
|
306 return new PathD2D(mGeometry, mFigureActive, mCurrentPoint, mFillRule); |
|
307 } |
|
308 |
|
309 TemporaryRef<PathBuilder> |
|
310 PathD2D::CopyToBuilder(FillRule aFillRule) const |
|
311 { |
|
312 return TransformedCopyToBuilder(Matrix(), aFillRule); |
|
313 } |
|
314 |
|
315 TemporaryRef<PathBuilder> |
|
316 PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const |
|
317 { |
|
318 RefPtr<ID2D1PathGeometry> path; |
|
319 HRESULT hr = DrawTargetD2D::factory()->CreatePathGeometry(byRef(path)); |
|
320 |
|
321 if (FAILED(hr)) { |
|
322 gfxWarning() << "Failed to create PathGeometry. Code: " << hr; |
|
323 return nullptr; |
|
324 } |
|
325 |
|
326 RefPtr<ID2D1GeometrySink> sink; |
|
327 hr = path->Open(byRef(sink)); |
|
328 if (FAILED(hr)) { |
|
329 gfxWarning() << "Failed to open Geometry for writing. Code: " << hr; |
|
330 return nullptr; |
|
331 } |
|
332 |
|
333 if (aFillRule == FillRule::FILL_WINDING) { |
|
334 sink->SetFillMode(D2D1_FILL_MODE_WINDING); |
|
335 } |
|
336 |
|
337 if (mEndedActive) { |
|
338 OpeningGeometrySink wrapSink(sink); |
|
339 mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, |
|
340 D2DMatrix(aTransform), |
|
341 &wrapSink); |
|
342 } else { |
|
343 mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, |
|
344 D2DMatrix(aTransform), |
|
345 sink); |
|
346 } |
|
347 |
|
348 RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule); |
|
349 |
|
350 pathBuilder->mCurrentPoint = aTransform * mEndPoint; |
|
351 |
|
352 if (mEndedActive) { |
|
353 pathBuilder->mFigureActive = true; |
|
354 } |
|
355 |
|
356 return pathBuilder; |
|
357 } |
|
358 |
|
359 void |
|
360 PathD2D::StreamToSink(PathSink *aSink) const |
|
361 { |
|
362 HRESULT hr; |
|
363 |
|
364 StreamingGeometrySink sink(aSink); |
|
365 |
|
366 hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, |
|
367 D2D1::IdentityMatrix(), &sink); |
|
368 |
|
369 if (FAILED(hr)) { |
|
370 gfxWarning() << "Failed to stream D2D path to sink. Code: " << hr; |
|
371 return; |
|
372 } |
|
373 } |
|
374 |
|
375 bool |
|
376 PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const |
|
377 { |
|
378 BOOL result; |
|
379 |
|
380 HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result); |
|
381 |
|
382 if (FAILED(hr)) { |
|
383 // Log |
|
384 return false; |
|
385 } |
|
386 |
|
387 return !!result; |
|
388 } |
|
389 |
|
390 bool |
|
391 PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, |
|
392 const Point &aPoint, |
|
393 const Matrix &aTransform) const |
|
394 { |
|
395 BOOL result; |
|
396 |
|
397 RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); |
|
398 HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint), |
|
399 aStrokeOptions.mLineWidth, |
|
400 strokeStyle, |
|
401 D2DMatrix(aTransform), |
|
402 &result); |
|
403 |
|
404 if (FAILED(hr)) { |
|
405 // Log |
|
406 return false; |
|
407 } |
|
408 |
|
409 return !!result; |
|
410 } |
|
411 |
|
412 Rect |
|
413 PathD2D::GetBounds(const Matrix &aTransform) const |
|
414 { |
|
415 D2D1_RECT_F d2dBounds; |
|
416 |
|
417 HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds); |
|
418 |
|
419 Rect bounds = ToRect(d2dBounds); |
|
420 if (FAILED(hr) || !bounds.IsFinite()) { |
|
421 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr; |
|
422 return Rect(); |
|
423 } |
|
424 |
|
425 return bounds; |
|
426 } |
|
427 |
|
428 Rect |
|
429 PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions, |
|
430 const Matrix &aTransform) const |
|
431 { |
|
432 D2D1_RECT_F d2dBounds; |
|
433 |
|
434 RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); |
|
435 HRESULT hr = |
|
436 mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle, |
|
437 D2DMatrix(aTransform), &d2dBounds); |
|
438 |
|
439 Rect bounds = ToRect(d2dBounds); |
|
440 if (FAILED(hr) || !bounds.IsFinite()) { |
|
441 gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr; |
|
442 return Rect(); |
|
443 } |
|
444 |
|
445 return bounds; |
|
446 } |
|
447 |
|
448 } |
|
449 } |