Bug 1685225 - Use state bits to determine the color for non-native theme meter chunks...
[gecko.git] / gfx / 2d / PathD2D.cpp
blob654a6b0ae4ccad05fe50e407b27af279e034ecba
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"
13 namespace mozilla {
14 namespace gfx {
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 public:
22 explicit OpeningGeometrySink(ID2D1SimplifiedGeometrySink* aSink)
23 : mSink(aSink), mNeedsFigureEnded(false) {}
25 HRESULT STDMETHODCALLTYPE QueryInterface(const IID& aIID, void** aPtr) {
26 if (!aPtr) {
27 return E_POINTER;
30 if (aIID == IID_IUnknown) {
31 *aPtr = static_cast<IUnknown*>(this);
32 return S_OK;
33 } else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
34 *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
35 return S_OK;
38 return E_NOINTERFACE;
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) {
47 EnsureFigureEnded();
48 return;
50 STDMETHOD_(void, BeginFigure)
51 (D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) {
52 EnsureFigureEnded();
53 return mSink->BeginFigure(aPoint, aBegin);
55 STDMETHOD_(void, AddLines)(const D2D1_POINT_2F* aLines, UINT aCount) {
56 EnsureFigureEnded();
57 return mSink->AddLines(aLines, aCount);
59 STDMETHOD_(void, AddBeziers)
60 (const D2D1_BEZIER_SEGMENT* aSegments, UINT aCount) {
61 EnsureFigureEnded();
62 return mSink->AddBeziers(aSegments, aCount);
64 STDMETHOD(Close)() { /* Should never be called! */
65 return S_OK;
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);
77 } else {
78 mNeedsFigureEnded = true;
82 private:
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) {
97 if (mFigureActive) {
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,
113 const Point& aCP3) {
114 EnsureActive(aCP1);
115 mSink->AddBezier(
116 D2D1::BezierSegment(D2DPoint(aCP1), D2DPoint(aCP2), D2DPoint(aCP3)));
118 mCurrentPoint = aCP3;
121 void PathBuilderD2D::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
122 EnsureActive(aCP1);
123 mSink->AddQuadraticBezier(
124 D2D1::QuadraticBezierSegment(D2DPoint(aCP1), D2DPoint(aCP2)));
126 mCurrentPoint = aCP2;
129 void PathBuilderD2D::Close() {
130 if (mFigureActive) {
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) {
158 fullCircle = true;
159 aEndAngle = Float(aStartAngle + M_PI * 1.9999);
160 } else if (aStartAngle - aEndAngle >= 2 * M_PI) {
161 fullCircle = true;
162 aStartAngle = Float(aEndAngle + M_PI * 1.9999);
165 Point startPoint;
166 startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
167 startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
169 if (!mFigureActive) {
170 EnsureActive(startPoint);
171 } else {
172 mSink->AddLine(D2DPoint(startPoint));
175 Point endPoint;
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) >
189 kEpsilon)) {
190 if (aAntiClockwise) {
191 if (aStartAngle - aEndAngle > M_PI) {
192 arcSize = D2D1_ARC_SIZE_LARGE;
194 } else {
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));
203 } else {
204 // our first workaround attempt didn't work, so instead draw the circle as
205 // two half-circles
206 Float midAngle = aEndAngle > aStartAngle ? Float(aStartAngle + M_PI)
207 : Float(aEndAngle + M_PI);
208 Point midPoint;
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() {
235 if (mFigureActive) {
236 mSink->EndFigure(D2D1_FIGURE_END_OPEN);
239 HRESULT hr = mSink->Close();
240 if (FAILED(hr)) {
241 gfxCriticalNote << "Failed to close PathSink. Code: " << hexa(hr);
242 return nullptr;
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;
256 HRESULT hr =
257 DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
259 if (FAILED(hr)) {
260 gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr);
261 return nullptr;
264 RefPtr<ID2D1GeometrySink> sink;
265 hr = path->Open(getter_AddRefs(sink));
266 if (FAILED(hr)) {
267 gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr);
268 return nullptr;
271 if (aFillRule == FillRule::FILL_WINDING) {
272 sink->SetFillMode(D2D1_FILL_MODE_WINDING);
275 if (mEndedActive) {
276 OpeningGeometrySink wrapSink(sink);
277 hr = mGeometry->Simplify(
278 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
279 D2DMatrix(aTransform), &wrapSink);
280 } else {
281 hr = mGeometry->Simplify(
282 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
283 D2DMatrix(aTransform), sink);
285 if (FAILED(hr)) {
286 gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: "
287 << hexa(hr) << " Active: " << mEndedActive;
288 return nullptr;
291 RefPtr<PathBuilderD2D> pathBuilder =
292 new PathBuilderD2D(sink, path, aFillRule, mBackendType);
294 pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint);
296 if (mEndedActive) {
297 pathBuilder->mFigureActive = true;
300 return pathBuilder.forget();
303 void PathD2D::StreamToSink(PathSink* aSink) const {
304 HRESULT hr;
306 StreamingGeometrySink sink(aSink);
308 hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
309 D2D1::IdentityMatrix(), &sink);
311 if (FAILED(hr)) {
312 gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr);
313 return;
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.
321 return false;
324 BOOL result;
326 HRESULT hr = mGeometry->FillContainsPoint(
327 D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
329 if (FAILED(hr)) {
330 // Log
331 return false;
334 return !!result;
337 bool PathD2D::StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
338 const Point& aPoint,
339 const Matrix& aTransform) const {
340 if (!aTransform.Determinant()) {
341 // If the transform is not invertible, then don't consider point inside.
342 return false;
345 BOOL result;
347 RefPtr<ID2D1StrokeStyle> strokeStyle =
348 CreateStrokeStyleForOptions(aStrokeOptions);
349 HRESULT hr = mGeometry->StrokeContainsPoint(
350 D2DPoint(aPoint), aStrokeOptions.mLineWidth, strokeStyle,
351 D2DMatrix(aTransform), &result);
353 if (FAILED(hr)) {
354 // Log
355 return false;
358 return !!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);
369 return Rect();
372 return bounds;
375 Rect PathD2D::GetStrokedBounds(const StrokeOptions& aStrokeOptions,
376 const Matrix& aTransform) const {
377 D2D1_RECT_F d2dBounds;
379 RefPtr<ID2D1StrokeStyle> strokeStyle =
380 CreateStrokeStyleForOptions(aStrokeOptions);
381 HRESULT hr =
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);
388 return Rect();
391 return bounds;
394 } // namespace gfx
395 } // namespace mozilla