gfx/2d/PathD2D.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     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"
    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 {
    22 public:
    23   OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink)
    24     : mSink(aSink)
    25     , mNeedsFigureEnded(false)
    26   {
    27   }
    29   HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
    30   {
    31     if (!aPtr) {
    32       return E_POINTER;
    33     }
    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     }
    43     return E_NOINTERFACE;
    44   }
    46   ULONG STDMETHODCALLTYPE AddRef()
    47   {
    48     return 1;
    49   }
    51   ULONG STDMETHODCALLTYPE Release()
    52   {
    53     return 1;
    54   }
    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); }
    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   }
    90   ID2D1SimplifiedGeometrySink *mSink;
    91   bool mNeedsFigureEnded;
    92 };
    94 class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink
    95 {
    96 public:
    97   StreamingGeometrySink(PathSink *aSink)
    98     : mSink(aSink)
    99   {
   100   }
   102   HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
   103   {
   104     if (!aPtr) {
   105       return E_POINTER;
   106     }
   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     }
   116     return E_NOINTERFACE;
   117   }
   119   ULONG STDMETHODCALLTYPE AddRef()
   120   {
   121     return 1;
   122   }
   124   ULONG STDMETHODCALLTYPE Release()
   125   {
   126     return 1;
   127   }
   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! */ }
   147   STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
   148   {
   149     if (aEnd == D2D1_FIGURE_END_CLOSED) {
   150       return mSink->Close();
   151     }
   152   }
   153 private:
   155   PathSink *mSink;
   156 };
   158 PathBuilderD2D::~PathBuilderD2D()
   159 {
   160 }
   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 }
   173 void
   174 PathBuilderD2D::LineTo(const Point &aPoint)
   175 {
   176   EnsureActive(aPoint);
   177   mSink->AddLine(D2DPoint(aPoint));
   179   mCurrentPoint = aPoint;
   180 }
   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)));
   192   mCurrentPoint = aCP3;
   193 }
   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)));
   203   mCurrentPoint = aCP2;
   204 }
   206 void
   207 PathBuilderD2D::Close()
   208 {
   209   if (mFigureActive) {
   210     mSink->EndFigure(D2D1_FIGURE_END_CLOSED);
   212     mFigureActive = false;
   214     EnsureActive(mBeginPoint);
   215   }
   216 }
   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   }
   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   }
   241   Point startPoint;
   242   startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
   243   startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
   245   if (!mFigureActive) {
   246     EnsureActive(startPoint);
   247   } else {
   248     mSink->AddLine(D2DPoint(startPoint));
   249   }
   251   Point endPoint;
   252   endPoint.x = aOrigin.x + aRadius * cos(aEndAngle);
   253   endPoint.y = aOrigin.y + aRadius * sin(aEndAngle);
   255   D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL;
   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   }
   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));
   274   mCurrentPoint = endPoint;
   275 }
   277 Point
   278 PathBuilderD2D::CurrentPoint() const
   279 {
   280   return mCurrentPoint;
   281 }
   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 }
   293 TemporaryRef<Path>
   294 PathBuilderD2D::Finish()
   295 {
   296   if (mFigureActive) {
   297     mSink->EndFigure(D2D1_FIGURE_END_OPEN);
   298   }
   300   HRESULT hr = mSink->Close();
   301   if (FAILED(hr)) {
   302     gfxDebug() << "Failed to close PathSink. Code: " << hr;
   303     return nullptr;
   304   }
   306   return new PathD2D(mGeometry, mFigureActive, mCurrentPoint, mFillRule);
   307 }
   309 TemporaryRef<PathBuilder>
   310 PathD2D::CopyToBuilder(FillRule aFillRule) const
   311 {
   312   return TransformedCopyToBuilder(Matrix(), aFillRule);
   313 }
   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));
   321   if (FAILED(hr)) {
   322     gfxWarning() << "Failed to create PathGeometry. Code: " << hr;
   323     return nullptr;
   324   }
   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   }
   333   if (aFillRule == FillRule::FILL_WINDING) {
   334     sink->SetFillMode(D2D1_FILL_MODE_WINDING);
   335   }
   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   }
   348   RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule);
   350   pathBuilder->mCurrentPoint = aTransform * mEndPoint;
   352   if (mEndedActive) {
   353     pathBuilder->mFigureActive = true;
   354   }
   356   return pathBuilder;
   357 }
   359 void
   360 PathD2D::StreamToSink(PathSink *aSink) const
   361 {
   362   HRESULT hr;
   364   StreamingGeometrySink sink(aSink);
   366   hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
   367                            D2D1::IdentityMatrix(), &sink);
   369   if (FAILED(hr)) {
   370     gfxWarning() << "Failed to stream D2D path to sink. Code: " << hr;
   371     return;
   372   }
   373 }
   375 bool
   376 PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
   377 {
   378   BOOL result;
   380   HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
   382   if (FAILED(hr)) {
   383     // Log
   384     return false;
   385   }
   387   return !!result;
   388 }
   390 bool
   391 PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
   392                              const Point &aPoint,
   393                              const Matrix &aTransform) const
   394 {
   395   BOOL result;
   397   RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
   398   HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint),
   399                                               aStrokeOptions.mLineWidth,
   400                                               strokeStyle,
   401                                               D2DMatrix(aTransform),
   402                                               &result);
   404   if (FAILED(hr)) {
   405     // Log
   406     return false;
   407   }
   409   return !!result;
   410 }
   412 Rect
   413 PathD2D::GetBounds(const Matrix &aTransform) const
   414 {
   415   D2D1_RECT_F d2dBounds;
   417   HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds);
   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   }
   425   return bounds;
   426 }
   428 Rect
   429 PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
   430                           const Matrix &aTransform) const
   431 {
   432   D2D1_RECT_F d2dBounds;
   434   RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
   435   HRESULT hr =
   436     mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle,
   437                                 D2DMatrix(aTransform), &d2dBounds);
   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   }
   445   return bounds;
   446 }
   448 }
   449 }

mercurial