1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/2d/PathD2D.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,449 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "PathD2D.h" 1.10 +#include "HelpersD2D.h" 1.11 +#include <math.h> 1.12 +#include "DrawTargetD2D.h" 1.13 +#include "Logging.h" 1.14 +#include "mozilla/Constants.h" 1.15 + 1.16 +namespace mozilla { 1.17 +namespace gfx { 1.18 + 1.19 +// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows 1.20 +// a geometry to be duplicated into a geometry sink, while removing the final 1.21 +// figure end and thus allowing a figure that was implicitly closed to be 1.22 +// continued. 1.23 +class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink 1.24 +{ 1.25 +public: 1.26 + OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink) 1.27 + : mSink(aSink) 1.28 + , mNeedsFigureEnded(false) 1.29 + { 1.30 + } 1.31 + 1.32 + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) 1.33 + { 1.34 + if (!aPtr) { 1.35 + return E_POINTER; 1.36 + } 1.37 + 1.38 + if (aIID == IID_IUnknown) { 1.39 + *aPtr = static_cast<IUnknown*>(this); 1.40 + return S_OK; 1.41 + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { 1.42 + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); 1.43 + return S_OK; 1.44 + } 1.45 + 1.46 + return E_NOINTERFACE; 1.47 + } 1.48 + 1.49 + ULONG STDMETHODCALLTYPE AddRef() 1.50 + { 1.51 + return 1; 1.52 + } 1.53 + 1.54 + ULONG STDMETHODCALLTYPE Release() 1.55 + { 1.56 + return 1; 1.57 + } 1.58 + 1.59 + // We ignore SetFillMode, the copier will decide. 1.60 + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) 1.61 + { EnsureFigureEnded(); return; } 1.62 + STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) 1.63 + { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); } 1.64 + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) 1.65 + { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); } 1.66 + STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) 1.67 + { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); } 1.68 + STDMETHOD(Close)() 1.69 + { /* Should never be called! */ return S_OK; } 1.70 + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) 1.71 + { return mSink->SetSegmentFlags(aFlags); } 1.72 + 1.73 + // This function is special - it's the reason this class exists. 1.74 + // It needs to intercept the very last endfigure. So that a user can 1.75 + // continue writing to this sink as if they never stopped. 1.76 + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) 1.77 + { 1.78 + if (aEnd == D2D1_FIGURE_END_CLOSED) { 1.79 + return mSink->EndFigure(aEnd); 1.80 + } else { 1.81 + mNeedsFigureEnded = true; 1.82 + } 1.83 + } 1.84 +private: 1.85 + void EnsureFigureEnded() 1.86 + { 1.87 + if (mNeedsFigureEnded) { 1.88 + mSink->EndFigure(D2D1_FIGURE_END_OPEN); 1.89 + mNeedsFigureEnded = false; 1.90 + } 1.91 + } 1.92 + 1.93 + ID2D1SimplifiedGeometrySink *mSink; 1.94 + bool mNeedsFigureEnded; 1.95 +}; 1.96 + 1.97 +class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink 1.98 +{ 1.99 +public: 1.100 + StreamingGeometrySink(PathSink *aSink) 1.101 + : mSink(aSink) 1.102 + { 1.103 + } 1.104 + 1.105 + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) 1.106 + { 1.107 + if (!aPtr) { 1.108 + return E_POINTER; 1.109 + } 1.110 + 1.111 + if (aIID == IID_IUnknown) { 1.112 + *aPtr = static_cast<IUnknown*>(this); 1.113 + return S_OK; 1.114 + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { 1.115 + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); 1.116 + return S_OK; 1.117 + } 1.118 + 1.119 + return E_NOINTERFACE; 1.120 + } 1.121 + 1.122 + ULONG STDMETHODCALLTYPE AddRef() 1.123 + { 1.124 + return 1; 1.125 + } 1.126 + 1.127 + ULONG STDMETHODCALLTYPE Release() 1.128 + { 1.129 + return 1; 1.130 + } 1.131 + 1.132 + // We ignore SetFillMode, this depends on the destination sink. 1.133 + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) 1.134 + { return; } 1.135 + STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) 1.136 + { mSink->MoveTo(ToPoint(aPoint)); } 1.137 + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) 1.138 + { for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } } 1.139 + STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) 1.140 + { 1.141 + for (UINT i = 0; i < aCount; i++) { 1.142 + mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); 1.143 + } 1.144 + } 1.145 + STDMETHOD(Close)() 1.146 + { /* Should never be called! */ return S_OK; } 1.147 + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) 1.148 + { /* Should never be called! */ } 1.149 + 1.150 + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) 1.151 + { 1.152 + if (aEnd == D2D1_FIGURE_END_CLOSED) { 1.153 + return mSink->Close(); 1.154 + } 1.155 + } 1.156 +private: 1.157 + 1.158 + PathSink *mSink; 1.159 +}; 1.160 + 1.161 +PathBuilderD2D::~PathBuilderD2D() 1.162 +{ 1.163 +} 1.164 + 1.165 +void 1.166 +PathBuilderD2D::MoveTo(const Point &aPoint) 1.167 +{ 1.168 + if (mFigureActive) { 1.169 + mSink->EndFigure(D2D1_FIGURE_END_OPEN); 1.170 + mFigureActive = false; 1.171 + } 1.172 + EnsureActive(aPoint); 1.173 + mCurrentPoint = aPoint; 1.174 +} 1.175 + 1.176 +void 1.177 +PathBuilderD2D::LineTo(const Point &aPoint) 1.178 +{ 1.179 + EnsureActive(aPoint); 1.180 + mSink->AddLine(D2DPoint(aPoint)); 1.181 + 1.182 + mCurrentPoint = aPoint; 1.183 +} 1.184 + 1.185 +void 1.186 +PathBuilderD2D::BezierTo(const Point &aCP1, 1.187 + const Point &aCP2, 1.188 + const Point &aCP3) 1.189 + { 1.190 + EnsureActive(aCP1); 1.191 + mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1), 1.192 + D2DPoint(aCP2), 1.193 + D2DPoint(aCP3))); 1.194 + 1.195 + mCurrentPoint = aCP3; 1.196 +} 1.197 + 1.198 +void 1.199 +PathBuilderD2D::QuadraticBezierTo(const Point &aCP1, 1.200 + const Point &aCP2) 1.201 +{ 1.202 + EnsureActive(aCP1); 1.203 + mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1), 1.204 + D2DPoint(aCP2))); 1.205 + 1.206 + mCurrentPoint = aCP2; 1.207 +} 1.208 + 1.209 +void 1.210 +PathBuilderD2D::Close() 1.211 +{ 1.212 + if (mFigureActive) { 1.213 + mSink->EndFigure(D2D1_FIGURE_END_CLOSED); 1.214 + 1.215 + mFigureActive = false; 1.216 + 1.217 + EnsureActive(mBeginPoint); 1.218 + } 1.219 +} 1.220 + 1.221 +void 1.222 +PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, 1.223 + Float aEndAngle, bool aAntiClockwise) 1.224 +{ 1.225 + if (aAntiClockwise && aStartAngle < aEndAngle) { 1.226 + // D2D does things a little differently, and draws the arc by specifying an 1.227 + // beginning and an end point. This means the circle will be the wrong way 1.228 + // around if the start angle is smaller than the end angle. It might seem 1.229 + // tempting to invert aAntiClockwise but that would change the sweeping 1.230 + // direction of the arc to instead we exchange start/begin. 1.231 + Float oldStart = aStartAngle; 1.232 + aStartAngle = aEndAngle; 1.233 + aEndAngle = oldStart; 1.234 + } 1.235 + 1.236 + // XXX - Workaround for now, D2D does not appear to do the desired thing when 1.237 + // the angle sweeps a complete circle. 1.238 + if (aEndAngle - aStartAngle >= 2 * M_PI) { 1.239 + aEndAngle = Float(aStartAngle + M_PI * 1.9999); 1.240 + } else if (aStartAngle - aEndAngle >= 2 * M_PI) { 1.241 + aStartAngle = Float(aEndAngle + M_PI * 1.9999); 1.242 + } 1.243 + 1.244 + Point startPoint; 1.245 + startPoint.x = aOrigin.x + aRadius * cos(aStartAngle); 1.246 + startPoint.y = aOrigin.y + aRadius * sin(aStartAngle); 1.247 + 1.248 + if (!mFigureActive) { 1.249 + EnsureActive(startPoint); 1.250 + } else { 1.251 + mSink->AddLine(D2DPoint(startPoint)); 1.252 + } 1.253 + 1.254 + Point endPoint; 1.255 + endPoint.x = aOrigin.x + aRadius * cos(aEndAngle); 1.256 + endPoint.y = aOrigin.y + aRadius * sin(aEndAngle); 1.257 + 1.258 + D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL; 1.259 + 1.260 + if (aAntiClockwise) { 1.261 + if (aStartAngle - aEndAngle > M_PI) { 1.262 + arcSize = D2D1_ARC_SIZE_LARGE; 1.263 + } 1.264 + } else { 1.265 + if (aEndAngle - aStartAngle > M_PI) { 1.266 + arcSize = D2D1_ARC_SIZE_LARGE; 1.267 + } 1.268 + } 1.269 + 1.270 + mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint), 1.271 + D2D1::SizeF(aRadius, aRadius), 1.272 + 0.0f, 1.273 + aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : 1.274 + D2D1_SWEEP_DIRECTION_CLOCKWISE, 1.275 + arcSize)); 1.276 + 1.277 + mCurrentPoint = endPoint; 1.278 +} 1.279 + 1.280 +Point 1.281 +PathBuilderD2D::CurrentPoint() const 1.282 +{ 1.283 + return mCurrentPoint; 1.284 +} 1.285 + 1.286 +void 1.287 +PathBuilderD2D::EnsureActive(const Point &aPoint) 1.288 +{ 1.289 + if (!mFigureActive) { 1.290 + mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED); 1.291 + mBeginPoint = aPoint; 1.292 + mFigureActive = true; 1.293 + } 1.294 +} 1.295 + 1.296 +TemporaryRef<Path> 1.297 +PathBuilderD2D::Finish() 1.298 +{ 1.299 + if (mFigureActive) { 1.300 + mSink->EndFigure(D2D1_FIGURE_END_OPEN); 1.301 + } 1.302 + 1.303 + HRESULT hr = mSink->Close(); 1.304 + if (FAILED(hr)) { 1.305 + gfxDebug() << "Failed to close PathSink. Code: " << hr; 1.306 + return nullptr; 1.307 + } 1.308 + 1.309 + return new PathD2D(mGeometry, mFigureActive, mCurrentPoint, mFillRule); 1.310 +} 1.311 + 1.312 +TemporaryRef<PathBuilder> 1.313 +PathD2D::CopyToBuilder(FillRule aFillRule) const 1.314 +{ 1.315 + return TransformedCopyToBuilder(Matrix(), aFillRule); 1.316 +} 1.317 + 1.318 +TemporaryRef<PathBuilder> 1.319 +PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const 1.320 +{ 1.321 + RefPtr<ID2D1PathGeometry> path; 1.322 + HRESULT hr = DrawTargetD2D::factory()->CreatePathGeometry(byRef(path)); 1.323 + 1.324 + if (FAILED(hr)) { 1.325 + gfxWarning() << "Failed to create PathGeometry. Code: " << hr; 1.326 + return nullptr; 1.327 + } 1.328 + 1.329 + RefPtr<ID2D1GeometrySink> sink; 1.330 + hr = path->Open(byRef(sink)); 1.331 + if (FAILED(hr)) { 1.332 + gfxWarning() << "Failed to open Geometry for writing. Code: " << hr; 1.333 + return nullptr; 1.334 + } 1.335 + 1.336 + if (aFillRule == FillRule::FILL_WINDING) { 1.337 + sink->SetFillMode(D2D1_FILL_MODE_WINDING); 1.338 + } 1.339 + 1.340 + if (mEndedActive) { 1.341 + OpeningGeometrySink wrapSink(sink); 1.342 + mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, 1.343 + D2DMatrix(aTransform), 1.344 + &wrapSink); 1.345 + } else { 1.346 + mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, 1.347 + D2DMatrix(aTransform), 1.348 + sink); 1.349 + } 1.350 + 1.351 + RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule); 1.352 + 1.353 + pathBuilder->mCurrentPoint = aTransform * mEndPoint; 1.354 + 1.355 + if (mEndedActive) { 1.356 + pathBuilder->mFigureActive = true; 1.357 + } 1.358 + 1.359 + return pathBuilder; 1.360 +} 1.361 + 1.362 +void 1.363 +PathD2D::StreamToSink(PathSink *aSink) const 1.364 +{ 1.365 + HRESULT hr; 1.366 + 1.367 + StreamingGeometrySink sink(aSink); 1.368 + 1.369 + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, 1.370 + D2D1::IdentityMatrix(), &sink); 1.371 + 1.372 + if (FAILED(hr)) { 1.373 + gfxWarning() << "Failed to stream D2D path to sink. Code: " << hr; 1.374 + return; 1.375 + } 1.376 +} 1.377 + 1.378 +bool 1.379 +PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const 1.380 +{ 1.381 + BOOL result; 1.382 + 1.383 + HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result); 1.384 + 1.385 + if (FAILED(hr)) { 1.386 + // Log 1.387 + return false; 1.388 + } 1.389 + 1.390 + return !!result; 1.391 +} 1.392 + 1.393 +bool 1.394 +PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, 1.395 + const Point &aPoint, 1.396 + const Matrix &aTransform) const 1.397 +{ 1.398 + BOOL result; 1.399 + 1.400 + RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); 1.401 + HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint), 1.402 + aStrokeOptions.mLineWidth, 1.403 + strokeStyle, 1.404 + D2DMatrix(aTransform), 1.405 + &result); 1.406 + 1.407 + if (FAILED(hr)) { 1.408 + // Log 1.409 + return false; 1.410 + } 1.411 + 1.412 + return !!result; 1.413 +} 1.414 + 1.415 +Rect 1.416 +PathD2D::GetBounds(const Matrix &aTransform) const 1.417 +{ 1.418 + D2D1_RECT_F d2dBounds; 1.419 + 1.420 + HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds); 1.421 + 1.422 + Rect bounds = ToRect(d2dBounds); 1.423 + if (FAILED(hr) || !bounds.IsFinite()) { 1.424 + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr; 1.425 + return Rect(); 1.426 + } 1.427 + 1.428 + return bounds; 1.429 +} 1.430 + 1.431 +Rect 1.432 +PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions, 1.433 + const Matrix &aTransform) const 1.434 +{ 1.435 + D2D1_RECT_F d2dBounds; 1.436 + 1.437 + RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); 1.438 + HRESULT hr = 1.439 + mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle, 1.440 + D2DMatrix(aTransform), &d2dBounds); 1.441 + 1.442 + Rect bounds = ToRect(d2dBounds); 1.443 + if (FAILED(hr) || !bounds.IsFinite()) { 1.444 + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hr; 1.445 + return Rect(); 1.446 + } 1.447 + 1.448 + return bounds; 1.449 +} 1.450 + 1.451 +} 1.452 +}