Use "= default" to denote default constructor or destructor
[WebKit-https.git] / Source / WebCore / platform / graphics / win / PathDirect2D.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "Path.h"
28
29 #if USE(DIRECT2D)
30
31 #include "AffineTransform.h"
32 #include "COMPtr.h"
33 #include "FloatRect.h"
34 #include "GraphicsContext.h"
35 #include "IntRect.h"
36 #include "NotImplemented.h"
37 #include "StrokeStyleApplier.h"
38 #include <d2d1.h>
39 #include <wtf/MathExtras.h>
40 #include <wtf/RetainPtr.h>
41 #include <wtf/text/WTFString.h>
42
43 namespace WebCore {
44
45 static inline ID2D1RenderTarget* scratchRenderTarget()
46 {
47     static COMPtr<ID2D1RenderTarget> renderTarget = adoptCOM(GraphicsContext::defaultRenderTarget());
48     return renderTarget.get();
49 }
50
51 Path Path::polygonPathFromPoints(const Vector<FloatPoint>& points)
52 {
53     Path path;
54     if (points.size() < 2)
55         return path;
56
57     Vector<D2D1_POINT_2F, 32> d2dPoints;
58     d2dPoints.reserveInitialCapacity(points.size() - 1);
59     for (auto point : points)
60         d2dPoints.uncheckedAppend(point);
61
62     path.moveTo(points.first());
63
64     ASSERT(path.activePath());
65
66     path.activePath()->AddLines(d2dPoints.data(), d2dPoints.size());
67     path.closeSubpath();
68
69     return path;
70 }
71
72 Path::Path() = default;
73
74 Path::~Path() = default;
75
76 PlatformPathPtr Path::ensurePlatformPath()
77 {
78     if (!m_path) {
79         HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(D2D1_FILL_MODE_WINDING, nullptr, 0, &m_path);
80         ASSERT(SUCCEEDED(hr));
81         if (FAILED(hr))
82             return nullptr;
83     }
84
85     return m_path.get();
86 }
87
88 void Path::appendGeometry(ID2D1Geometry* geometry)
89 {
90     unsigned geometryCount = m_path ? m_path->GetSourceGeometryCount() : 0;
91     Vector<ID2D1Geometry*> geometries(geometryCount, nullptr);
92
93     // Note: 'GetSourceGeometries' returns geometries that have a +1 ref count.
94     // so they must be released before we return.
95     if (geometryCount)
96         m_path->GetSourceGeometries(geometries.data(), geometryCount);
97
98     geometry->AddRef();
99     geometries.append(geometry);
100
101     auto fillMode = m_path ? m_path->GetFillMode() : D2D1_FILL_MODE_WINDING;
102
103     COMPtr<ID2D1GeometryGroup> protectedPath = m_path;
104     m_path = nullptr;
105
106     HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(fillMode, geometries.data(), geometries.size(), &m_path);
107     RELEASE_ASSERT(SUCCEEDED(hr));
108
109     for (auto entry : geometries)
110         entry->Release();
111 }
112
113 void Path::createGeometryWithFillMode(WindRule webkitFillMode, COMPtr<ID2D1GeometryGroup>& path) const
114 {
115     RELEASE_ASSERT(m_path);
116
117     auto fillMode = (webkitFillMode == RULE_EVENODD) ? D2D1_FILL_MODE_ALTERNATE : D2D1_FILL_MODE_WINDING;
118
119     if (fillMode == m_path->GetFillMode()) {
120         path = m_path;
121         return;
122     }
123
124     unsigned geometryCount = m_path->GetSourceGeometryCount();
125
126     Vector<ID2D1Geometry*> geometries(geometryCount, nullptr);
127     ASSERT(geometryCount);
128
129     // Note: 'GetSourceGeometries' returns geometries that have a +1 ref count.
130     // so they must be released before we return.
131     m_path->GetSourceGeometries(geometries.data(), geometryCount);
132
133     HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(fillMode, geometries.data(), geometries.size(), &path);
134     RELEASE_ASSERT(SUCCEEDED(hr));
135
136     for (auto entry : geometries)
137         entry->Release();
138 }
139
140 Path::Path(const Path& other)
141 {
142     if (other.platformPath() && other.activePath()) {
143         auto otherPath = other.platformPath();
144
145         unsigned geometryCount = otherPath->GetSourceGeometryCount();
146
147         Vector<ID2D1Geometry*> geometries(geometryCount, nullptr);
148         ASSERT(geometryCount);
149
150         // Note: 'GetSourceGeometries' returns geometries that have a +1 ref count.
151         // so they must be released before we return.
152         otherPath->GetSourceGeometries(geometries.data(), geometryCount);
153
154         HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(other.m_path->GetFillMode(), geometries.data(), geometryCount, &m_path);
155         RELEASE_ASSERT(SUCCEEDED(hr));
156
157         for (auto entry : geometries)
158             entry->Release();
159     }
160 }
161
162 Path& Path::operator=(const Path& other)
163 {
164     m_path = other.m_path;
165     m_activePath = other.m_activePath;
166     m_activePathGeometry = other.m_activePathGeometry;
167
168     return *this;
169 }
170
171 HRESULT Path::initializePathState()
172 {
173     m_path = nullptr;
174     m_activePath = nullptr;
175     m_activePathGeometry = nullptr;
176
177     GraphicsContext::systemFactory()->CreatePathGeometry(&m_activePathGeometry);
178
179     Vector<ID2D1Geometry*> geometries;
180     geometries.append(m_activePathGeometry.get());
181
182     HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(D2D1_FILL_MODE_WINDING, geometries.data(), geometries.size(), &m_path);
183     if (FAILED(hr))
184         return hr;
185
186     return m_activePathGeometry->Open(&m_activePath);
187 }
188
189 void Path::drawDidComplete() const
190 {
191     FloatPoint currentPoint = this->currentPoint();
192
193     // To maintain proper semantics with CG, we need to clear our Direct2D
194     // path objects when a draw has finished.
195     HRESULT hr = const_cast<Path*>(this)->initializePathState();
196
197     if (!SUCCEEDED(hr))
198         return;
199
200     m_activePath->SetFillMode(D2D1_FILL_MODE_WINDING);
201
202     m_activePath->BeginFigure(currentPoint, D2D1_FIGURE_BEGIN_FILLED);
203 }
204
205 bool Path::contains(const FloatPoint& point, WindRule rule) const
206 {
207     if (isNull())
208         return false;
209
210     if (!fastBoundingRect().contains(point))
211         return false;
212
213     BOOL contains;
214     if (!SUCCEEDED(m_path->FillContainsPoint(D2D1::Point2F(point.x(), point.y()), nullptr, &contains)))
215         return false;
216
217     return contains;
218 }
219
220 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
221 {
222     if (isNull())
223         return false;
224
225     ASSERT(applier);
226
227     GraphicsContext scratchContext(scratchRenderTarget());
228     applier->strokeStyle(&scratchContext);
229
230     BOOL containsPoint = false;
231     HRESULT hr = m_path->StrokeContainsPoint(point, scratchContext.strokeThickness(), scratchContext.platformStrokeStyle(), nullptr, 1.0f, &containsPoint);
232     if (!SUCCEEDED(hr))
233         return false;
234
235     return containsPoint;
236 }
237
238 void Path::translate(const FloatSize& size)
239 {
240     transform(AffineTransform(1, 0, 0, 1, size.width(), size.height()));
241 }
242
243 void Path::transform(const AffineTransform& transform)
244 {
245     if (transform.isIdentity() || isEmpty())
246         return;
247
248     bool pathIsActive = false;
249     if (m_activePath) {
250         m_activePath->Close();
251         m_activePath = nullptr;
252         m_activePathGeometry = nullptr;
253         pathIsActive = true;
254     }
255
256     const D2D1_MATRIX_3X2_F& d2dTransform = static_cast<const D2D1_MATRIX_3X2_F>(transform);
257     COMPtr<ID2D1TransformedGeometry> transformedPath;
258     if (!SUCCEEDED(GraphicsContext::systemFactory()->CreateTransformedGeometry(m_path.get(), d2dTransform, &transformedPath)))
259         return;
260
261     Vector<ID2D1Geometry*> geometries;
262     geometries.append(transformedPath.get());
263
264     if (pathIsActive) {
265         GraphicsContext::systemFactory()->CreatePathGeometry(&m_activePathGeometry);
266         m_activePathGeometry->Open(&m_activePath);
267         geometries.append(m_activePathGeometry.get());
268     }
269
270     auto fillMode = m_path->GetFillMode();
271
272     m_path = nullptr;
273
274     HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(fillMode, geometries.data(), geometries.size(), &m_path);
275     RELEASE_ASSERT(SUCCEEDED(hr));
276 }
277
278 FloatRect Path::boundingRect() const
279 {
280     if (isNull())
281         return FloatRect();
282
283     D2D1_RECT_F bounds = { };
284     if (!SUCCEEDED(m_path->GetBounds(nullptr, &bounds)))
285         return FloatRect();
286
287     return bounds;
288 }
289
290 FloatRect Path::fastBoundingRect() const
291 {
292     if (isNull())
293         return FloatRect();
294
295     D2D1_RECT_F bounds = { };
296     if (!SUCCEEDED(m_path->GetBounds(nullptr, &bounds)))
297         return FloatRect();
298
299     return bounds;
300 }
301
302 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const
303 {
304     if (isNull())
305         return FloatRect();
306
307     if (!applier)
308         return boundingRect();
309
310     UNUSED_PARAM(applier);
311     notImplemented();
312
313     // Just return regular bounding rect for now.
314     return boundingRect();
315 }
316
317 void Path::moveTo(const FloatPoint& point)
318 {
319     if (!m_activePath) {
320         GraphicsContext::systemFactory()->CreatePathGeometry(&m_activePathGeometry);
321
322         appendGeometry(m_activePathGeometry.get());
323
324         if (!SUCCEEDED(m_activePathGeometry->Open(&m_activePath)))
325             return;
326
327         m_activePath->SetFillMode(D2D1_FILL_MODE_WINDING);
328     }
329
330     m_activePath->BeginFigure(point, D2D1_FIGURE_BEGIN_FILLED);
331 }
332
333 void Path::addLineTo(const FloatPoint& point)
334 {
335     ASSERT(m_activePath.get());
336
337     m_activePath->AddLine(point);
338 }
339
340 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p)
341 {
342     ASSERT(m_activePath.get());
343
344     m_activePath->AddQuadraticBezier(D2D1::QuadraticBezierSegment(cp, p));
345 }
346
347 void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p)
348 {
349     ASSERT(m_activePath.get());
350
351     FloatPoint beforePoint = currentPoint();
352
353     m_activePath->AddBezier(D2D1::BezierSegment(cp1, cp2, p));
354 }
355
356 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
357 {
358     UNUSED_PARAM(p1);
359     UNUSED_PARAM(p2);
360     UNUSED_PARAM(radius);
361     notImplemented();
362 }
363
364 static bool equalRadiusWidths(const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
365 {
366     return topLeftRadius.width() == topRightRadius.width()
367         && topRightRadius.width() == bottomLeftRadius.width()
368         && bottomLeftRadius.width() == bottomRightRadius.width();
369 }
370
371 static bool equalRadiusHeights(const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
372 {
373     return topLeftRadius.height() == bottomLeftRadius.height()
374         && bottomLeftRadius.height() == topRightRadius.height()
375         && topRightRadius.height() == bottomRightRadius.height();
376 }
377
378 void Path::platformAddPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
379 {
380     bool equalWidths = equalRadiusWidths(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
381     bool equalHeights = equalRadiusHeights(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
382
383     if (equalWidths && equalHeights) {
384         // Ensure that CG can render the rounded rect.
385         float radiusWidth = topLeftRadius.width();
386         float radiusHeight = topLeftRadius.height();
387         auto rectToDraw = D2D1::RectF(rect.x(), rect.y(), rect.maxX(), rect.maxY());
388
389         COMPtr<ID2D1RoundedRectangleGeometry> roundRect;
390         HRESULT hr = GraphicsContext::systemFactory()->CreateRoundedRectangleGeometry(D2D1::RoundedRect(rectToDraw, radiusWidth, radiusHeight), &roundRect);
391         RELEASE_ASSERT(SUCCEEDED(hr));
392         appendGeometry(roundRect.get());
393         return;
394     }
395
396     addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
397 }
398
399 void Path::closeSubpath()
400 {
401     if (isNull())
402         return;
403
404     ASSERT(m_activePath.get());
405     m_activePath->EndFigure(D2D1_FIGURE_END_OPEN);
406 }
407
408 static FloatPoint arcStart(const FloatPoint& center, float radius, float startAngle)
409 {
410     FloatPoint startingPoint = center;
411     float startX = radius * std::cos(startAngle);
412     float startY = radius * std::sin(startAngle);
413     startingPoint.move(startX, startY);
414     return startingPoint;
415 }
416
417 static void drawArcSection(ID2D1GeometrySink* sink, const FloatPoint& center, float radius, float startAngle, float endAngle, bool clockwise)
418 {
419     // Direct2D wants us to specify the end point of the arc, not the center. It will be drawn from
420     // whatever the current point in the 'm_activePath' is.
421     FloatPoint p = center;
422     float endX = radius * std::cos(endAngle);
423     float endY = radius * std::sin(endAngle);
424     p.move(endX, endY);
425
426     float arcDeg = rad2deg(endAngle - startAngle);
427     D2D1_SWEEP_DIRECTION direction = clockwise ? D2D1_SWEEP_DIRECTION_CLOCKWISE : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
428     sink->AddArc(D2D1::ArcSegment(p, D2D1::SizeF(radius, radius), arcDeg, direction, D2D1_ARC_SIZE_SMALL));
429 }
430
431 void Path::addArc(const FloatPoint& center, float radius, float startAngle, float endAngle, bool clockwise)
432 {
433     auto arcStartPoint = arcStart(center, radius, startAngle);
434     if (!m_activePath)
435         moveTo(arcStartPoint);
436     else if (!areEssentiallyEqual(currentPoint(), arcStartPoint)) {
437         // If the arc defined by the center and radius does not intersect the current position,
438         // we need to draw a line from the current position to the starting point of the arc.
439         addLineTo(arcStartPoint);
440     }
441
442     // Direct2D has problems drawing large arcs. It gets confused if drawing a complete (or
443     // nearly complete) circle in the counter-clockwise direction. So, draw any arcs larger
444     // than 180 degrees in two pieces.
445     float fullSweep = endAngle - startAngle;
446     float negate = fullSweep < 0 ? -1.0f : 1.0f;
447     float maxSweep = negate * std::min(std::abs(fullSweep), piFloat);
448     float firstArcEnd = startAngle + maxSweep;
449     drawArcSection(m_activePath.get(), center, radius, startAngle, firstArcEnd, clockwise);
450
451     if (WTF::areEssentiallyEqual(firstArcEnd, endAngle))
452         return;
453
454     drawArcSection(m_activePath.get(), center, radius, firstArcEnd, endAngle, clockwise);
455 }
456
457 void Path::addRect(const FloatRect& r)
458 {
459     COMPtr<ID2D1RectangleGeometry> rectangle;
460     HRESULT hr = GraphicsContext::systemFactory()->CreateRectangleGeometry(r, &rectangle);
461     RELEASE_ASSERT(SUCCEEDED(hr));
462     appendGeometry(rectangle.get());
463 }
464
465 void Path::addEllipse(FloatPoint p, float radiusX, float radiusY, float rotation, float startAngle, float endAngle, bool anticlockwise)
466 {
467     AffineTransform transform;
468     transform.translate(p.x(), p.y()).rotate(rad2deg(rotation)).scale(radiusX, radiusY);
469
470     notImplemented();
471 }
472
473 void Path::addEllipse(const FloatRect& r)
474 {
475     COMPtr<ID2D1EllipseGeometry> ellipse;
476     HRESULT hr = GraphicsContext::systemFactory()->CreateEllipseGeometry(D2D1::Ellipse(r.center(), r.width(), r.height()), &ellipse);
477     RELEASE_ASSERT(SUCCEEDED(hr));
478     appendGeometry(ellipse.get());
479 }
480
481 void Path::addPath(const Path& path, const AffineTransform& transform)
482 {
483     if (!path.platformPath())
484         return;
485
486     if (!transform.isInvertible())
487         return;
488
489     notImplemented();
490 }
491
492
493 void Path::clear()
494 {
495     if (isNull())
496         return;
497
498     m_path = nullptr;
499     m_activePath = nullptr;
500     m_activePathGeometry = nullptr;
501 }
502
503 bool Path::isEmpty() const
504 {
505     if (isNull())
506         return true;
507
508     if (!m_path->GetSourceGeometryCount())
509         return true;
510
511     return false;
512 }
513
514 bool Path::hasCurrentPoint() const
515 {
516     return !isEmpty();
517 }
518     
519 FloatPoint Path::currentPoint() const 
520 {
521     if (isNull())
522         return FloatPoint();
523
524     float length = 0;
525     HRESULT hr = m_path->ComputeLength(nullptr, &length);
526     if (!SUCCEEDED(hr))
527         return FloatPoint();
528
529     D2D1_POINT_2F point = { };
530     D2D1_POINT_2F tangent = { };
531     hr = m_path->ComputePointAtLength(length, nullptr, &point, &tangent);
532     if (!SUCCEEDED(hr))
533         return FloatPoint();
534
535     return point;
536 }
537
538 float Path::length() const
539 {
540     float length = 0;
541     HRESULT hr = m_path->ComputeLength(nullptr, &length);
542     if (!SUCCEEDED(hr))
543         return 0;
544
545     return length;
546 }
547
548 void Path::apply(const PathApplierFunction& function) const
549 {
550     if (isNull())
551         return;
552
553     notImplemented();
554 }
555
556 }
557
558 #endif // USE(DIRECT2D)