no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / gfx / 2d / PathD2D.cpp
blobde01b410a9bff0ddd96ab196f6fcb7249d5b7b1e
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/. */
7 #include "PathD2D.h"
8 #include "HelpersD2D.h"
9 #include <math.h>
10 #include "DrawTargetD2D1.h"
11 #include "Logging.h"
12 #include "PathHelpers.h"
14 namespace mozilla {
15 namespace gfx {
17 already_AddRefed<PathBuilder> PathBuilderD2D::Create(FillRule aFillRule) {
18 RefPtr<ID2D1PathGeometry> path;
19 HRESULT hr =
20 DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
22 if (FAILED(hr)) {
23 gfxWarning() << "Failed to create Direct2D Path Geometry. Code: "
24 << hexa(hr);
25 return nullptr;
28 RefPtr<ID2D1GeometrySink> sink;
29 hr = path->Open(getter_AddRefs(sink));
30 if (FAILED(hr)) {
31 gfxWarning() << "Failed to access Direct2D Path Geometry. Code: "
32 << hexa(hr);
33 return nullptr;
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
47 // continued.
48 class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink {
49 public:
50 explicit OpeningGeometrySink(ID2D1SimplifiedGeometrySink* aSink)
51 : mSink(aSink), mNeedsFigureEnded(false) {}
53 HRESULT STDMETHODCALLTYPE QueryInterface(const IID& aIID, void** aPtr) {
54 if (!aPtr) {
55 return E_POINTER;
58 if (aIID == IID_IUnknown) {
59 *aPtr = static_cast<IUnknown*>(this);
60 return S_OK;
61 } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
62 *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
63 return S_OK;
66 return E_NOINTERFACE;
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) {
75 EnsureFigureEnded();
76 return;
78 STDMETHOD_(void, BeginFigure)
79 (D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) {
80 EnsureFigureEnded();
81 return mSink->BeginFigure(aPoint, aBegin);
83 STDMETHOD_(void, AddLines)(const D2D1_POINT_2F* aLines, UINT aCount) {
84 EnsureFigureEnded();
85 return mSink->AddLines(aLines, aCount);
87 STDMETHOD_(void, AddBeziers)
88 (const D2D1_BEZIER_SEGMENT* aSegments, UINT aCount) {
89 EnsureFigureEnded();
90 return mSink->AddBeziers(aSegments, aCount);
92 STDMETHOD(Close)() { /* Should never be called! */
93 return S_OK;
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);
105 } else {
106 mNeedsFigureEnded = true;
110 private:
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) {
125 if (mFigureActive) {
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,
142 const Point& aCP3) {
143 EnsureActive(aCP1);
144 mSink->AddBezier(
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) {
152 EnsureActive(aCP1);
153 mSink->AddQuadraticBezier(
154 D2D1::QuadraticBezierSegment(D2DPoint(aCP1), D2DPoint(aCP2)));
156 mCurrentPoint = aCP2;
157 mFigureEmpty = false;
160 void PathBuilderD2D::Close() {
161 if (mFigureActive) {
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) {
192 fullCircle = true;
193 aEndAngle = Float(aStartAngle + M_PI * 1.9999);
194 } else if (aStartAngle - aEndAngle >= 1.9999 * M_PI) {
195 fullCircle = true;
196 aStartAngle = Float(aEndAngle + M_PI * 1.9999);
199 Point startPoint;
200 startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
201 startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
203 if (!mFigureActive) {
204 EnsureActive(startPoint);
205 } else {
206 mSink->AddLine(D2DPoint(startPoint));
209 Point endPoint;
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) >
223 kEpsilon)) {
224 if (aAntiClockwise) {
225 if (aStartAngle - aEndAngle > M_PI) {
226 arcSize = D2D1_ARC_SIZE_LARGE;
228 } else {
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));
237 } else {
238 // our first workaround attempt didn't work, so instead draw the circle as
239 // two half-circles
240 Float midAngle = aEndAngle > aStartAngle ? Float(aStartAngle + M_PI)
241 : Float(aEndAngle + M_PI);
242 Point midPoint;
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() {
270 if (mFigureActive) {
271 mSink->EndFigure(D2D1_FIGURE_END_OPEN);
274 HRESULT hr = mSink->Close();
275 if (FAILED(hr)) {
276 gfxCriticalNote << "Failed to close PathSink. Code: " << hexa(hr);
277 return nullptr;
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;
291 HRESULT hr =
292 DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
294 if (FAILED(hr)) {
295 gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr);
296 return nullptr;
299 RefPtr<ID2D1GeometrySink> sink;
300 hr = path->Open(getter_AddRefs(sink));
301 if (FAILED(hr)) {
302 gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr);
303 return nullptr;
306 if (aFillRule == FillRule::FILL_WINDING) {
307 sink->SetFillMode(D2D1_FILL_MODE_WINDING);
310 if (mEndedActive) {
311 OpeningGeometrySink wrapSink(sink);
312 hr = mGeometry->Simplify(
313 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
314 D2DMatrix(aTransform), &wrapSink);
315 } else {
316 hr = mGeometry->Simplify(
317 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
318 D2DMatrix(aTransform), sink);
320 if (FAILED(hr)) {
321 gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: "
322 << hexa(hr) << " Active: " << mEndedActive;
323 return nullptr;
326 RefPtr<PathBuilderD2D> pathBuilder =
327 new PathBuilderD2D(sink, path, aFillRule, mBackendType);
329 pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint);
331 if (mEndedActive) {
332 pathBuilder->mFigureActive = true;
335 return pathBuilder.forget();
338 void PathD2D::StreamToSink(PathSink* aSink) const {
339 HRESULT hr;
341 StreamingGeometrySink sink(aSink);
343 hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
344 D2D1::IdentityMatrix(), &sink);
346 if (FAILED(hr)) {
347 gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr);
348 return;
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.
356 return false;
359 BOOL result;
361 HRESULT hr = mGeometry->FillContainsPoint(
362 D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
364 if (FAILED(hr)) {
365 // Log
366 return false;
369 return !!result;
372 bool PathD2D::StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
373 const Point& aPoint,
374 const Matrix& aTransform) const {
375 if (!aTransform.Determinant()) {
376 // If the transform is not invertible, then don't consider point inside.
377 return false;
380 BOOL result;
382 RefPtr<ID2D1StrokeStyle> strokeStyle =
383 CreateStrokeStyleForOptions(aStrokeOptions);
384 HRESULT hr = mGeometry->StrokeContainsPoint(
385 D2DPoint(aPoint), aStrokeOptions.mLineWidth, strokeStyle,
386 D2DMatrix(aTransform), &result);
388 if (FAILED(hr)) {
389 // Log
390 return false;
393 return !!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);
404 return Rect();
407 return bounds;
410 Rect PathD2D::GetStrokedBounds(const StrokeOptions& aStrokeOptions,
411 const Matrix& aTransform) const {
412 D2D1_RECT_F d2dBounds;
414 RefPtr<ID2D1StrokeStyle> strokeStyle =
415 CreateStrokeStyleForOptions(aStrokeOptions);
416 HRESULT hr =
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);
423 return Rect();
426 return bounds;
429 } // namespace gfx
430 } // namespace mozilla