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/. */
9 #include "DrawTargetCairo.h"
11 #include "PathHelpers.h"
12 #include "HelpersCairo.h"
17 already_AddRefed
<PathBuilder
> PathBuilderCairo::Create(FillRule aFillRule
) {
18 return MakeAndAddRef
<PathBuilderCairo
>(aFillRule
);
21 PathBuilderCairo::PathBuilderCairo(FillRule aFillRule
) : mFillRule(aFillRule
) {}
23 void PathBuilderCairo::MoveTo(const Point
& aPoint
) {
24 cairo_path_data_t data
;
25 data
.header
.type
= CAIRO_PATH_MOVE_TO
;
26 data
.header
.length
= 2;
27 mPathData
.push_back(data
);
28 data
.point
.x
= aPoint
.x
;
29 data
.point
.y
= aPoint
.y
;
30 mPathData
.push_back(data
);
32 mBeginPoint
= mCurrentPoint
= aPoint
;
35 void PathBuilderCairo::LineTo(const Point
& aPoint
) {
36 cairo_path_data_t data
;
37 data
.header
.type
= CAIRO_PATH_LINE_TO
;
38 data
.header
.length
= 2;
39 mPathData
.push_back(data
);
40 data
.point
.x
= aPoint
.x
;
41 data
.point
.y
= aPoint
.y
;
42 mPathData
.push_back(data
);
44 mCurrentPoint
= aPoint
;
47 void PathBuilderCairo::BezierTo(const Point
& aCP1
, const Point
& aCP2
,
49 cairo_path_data_t data
;
50 data
.header
.type
= CAIRO_PATH_CURVE_TO
;
51 data
.header
.length
= 4;
52 mPathData
.push_back(data
);
53 data
.point
.x
= aCP1
.x
;
54 data
.point
.y
= aCP1
.y
;
55 mPathData
.push_back(data
);
56 data
.point
.x
= aCP2
.x
;
57 data
.point
.y
= aCP2
.y
;
58 mPathData
.push_back(data
);
59 data
.point
.x
= aCP3
.x
;
60 data
.point
.y
= aCP3
.y
;
61 mPathData
.push_back(data
);
66 void PathBuilderCairo::QuadraticBezierTo(const Point
& aCP1
, const Point
& aCP2
) {
67 // We need to elevate the degree of this quadratic Bézier to cubic, so we're
68 // going to add an intermediate control point, and recompute control point 1.
69 // The first and last control points remain the same.
70 // This formula can be found on http://fontforge.sourceforge.net/bezier.html
71 Point CP0
= CurrentPoint();
72 Point CP1
= (CP0
+ aCP1
* 2.0) / 3.0;
73 Point CP2
= (aCP2
+ aCP1
* 2.0) / 3.0;
76 cairo_path_data_t data
;
77 data
.header
.type
= CAIRO_PATH_CURVE_TO
;
78 data
.header
.length
= 4;
79 mPathData
.push_back(data
);
82 mPathData
.push_back(data
);
85 mPathData
.push_back(data
);
88 mPathData
.push_back(data
);
93 void PathBuilderCairo::Close() {
94 cairo_path_data_t data
;
95 data
.header
.type
= CAIRO_PATH_CLOSE_PATH
;
96 data
.header
.length
= 1;
97 mPathData
.push_back(data
);
99 mCurrentPoint
= mBeginPoint
;
102 void PathBuilderCairo::Arc(const Point
& aOrigin
, float aRadius
,
103 float aStartAngle
, float aEndAngle
,
104 bool aAntiClockwise
) {
105 ArcToBezier(this, aOrigin
, Size(aRadius
, aRadius
), aStartAngle
, aEndAngle
,
109 already_AddRefed
<Path
> PathBuilderCairo::Finish() {
110 return MakeAndAddRef
<PathCairo
>(mFillRule
, mPathData
, mCurrentPoint
,
114 PathCairo::PathCairo(FillRule aFillRule
,
115 std::vector
<cairo_path_data_t
>& aPathData
,
116 const Point
& aCurrentPoint
, const Point
& aBeginPoint
)
117 : mFillRule(aFillRule
),
118 mContainingContext(nullptr),
119 mCurrentPoint(aCurrentPoint
),
120 mBeginPoint(aBeginPoint
) {
121 mPathData
.swap(aPathData
);
124 PathCairo::PathCairo(cairo_t
* aContext
)
125 : mFillRule(FillRule::FILL_WINDING
), mContainingContext(nullptr) {
126 cairo_path_t
* path
= cairo_copy_path(aContext
);
128 // XXX - mCurrentPoint is not properly set here, the same is true for the
129 // D2D Path code, we never require current point when hitting this codepath
130 // but this should be fixed.
131 for (int i
= 0; i
< path
->num_data
; i
++) {
132 mPathData
.push_back(path
->data
[i
]);
135 cairo_path_destroy(path
);
138 PathCairo::~PathCairo() {
139 if (mContainingContext
) {
140 cairo_destroy(mContainingContext
);
144 already_AddRefed
<PathBuilder
> PathCairo::CopyToBuilder(
145 FillRule aFillRule
) const {
146 RefPtr
<PathBuilderCairo
> builder
= new PathBuilderCairo(aFillRule
);
148 builder
->mPathData
= mPathData
;
149 builder
->mCurrentPoint
= mCurrentPoint
;
150 builder
->mBeginPoint
= mBeginPoint
;
152 return builder
.forget();
155 already_AddRefed
<PathBuilder
> PathCairo::TransformedCopyToBuilder(
156 const Matrix
& aTransform
, FillRule aFillRule
) const {
157 RefPtr
<PathBuilderCairo
> builder
= new PathBuilderCairo(aFillRule
);
159 AppendPathToBuilder(builder
, &aTransform
);
160 builder
->mCurrentPoint
= aTransform
.TransformPoint(mCurrentPoint
);
161 builder
->mBeginPoint
= aTransform
.TransformPoint(mBeginPoint
);
163 return builder
.forget();
166 bool PathCairo::ContainsPoint(const Point
& aPoint
,
167 const Matrix
& aTransform
) const {
168 Matrix inverse
= aTransform
;
170 Point transformed
= inverse
.TransformPoint(aPoint
);
172 EnsureContainingContext(aTransform
);
174 return cairo_in_fill(mContainingContext
, transformed
.x
, transformed
.y
);
177 bool PathCairo::StrokeContainsPoint(const StrokeOptions
& aStrokeOptions
,
179 const Matrix
& aTransform
) const {
180 Matrix inverse
= aTransform
;
182 Point transformed
= inverse
.TransformPoint(aPoint
);
184 EnsureContainingContext(aTransform
);
186 SetCairoStrokeOptions(mContainingContext
, aStrokeOptions
);
188 return cairo_in_stroke(mContainingContext
, transformed
.x
, transformed
.y
);
191 Rect
PathCairo::GetBounds(const Matrix
& aTransform
) const {
192 EnsureContainingContext(aTransform
);
194 double x1
, y1
, x2
, y2
;
196 cairo_path_extents(mContainingContext
, &x1
, &y1
, &x2
, &y2
);
197 Rect
bounds(Float(x1
), Float(y1
), Float(x2
- x1
), Float(y2
- y1
));
198 return aTransform
.TransformBounds(bounds
);
201 Rect
PathCairo::GetStrokedBounds(const StrokeOptions
& aStrokeOptions
,
202 const Matrix
& aTransform
) const {
203 EnsureContainingContext(aTransform
);
205 double x1
, y1
, x2
, y2
;
207 SetCairoStrokeOptions(mContainingContext
, aStrokeOptions
);
209 cairo_stroke_extents(mContainingContext
, &x1
, &y1
, &x2
, &y2
);
210 Rect
bounds((Float
)x1
, (Float
)y1
, (Float
)(x2
- x1
), (Float
)(y2
- y1
));
211 return aTransform
.TransformBounds(bounds
);
214 void PathCairo::StreamToSink(PathSink
* aSink
) const {
215 for (size_t i
= 0; i
< mPathData
.size(); i
++) {
216 switch (mPathData
[i
].header
.type
) {
217 case CAIRO_PATH_MOVE_TO
:
219 aSink
->MoveTo(Point(mPathData
[i
].point
.x
, mPathData
[i
].point
.y
));
221 case CAIRO_PATH_LINE_TO
:
223 aSink
->LineTo(Point(mPathData
[i
].point
.x
, mPathData
[i
].point
.y
));
225 case CAIRO_PATH_CURVE_TO
:
227 Point(mPathData
[i
+ 1].point
.x
, mPathData
[i
+ 1].point
.y
),
228 Point(mPathData
[i
+ 2].point
.x
, mPathData
[i
+ 2].point
.y
),
229 Point(mPathData
[i
+ 3].point
.x
, mPathData
[i
+ 3].point
.y
));
232 case CAIRO_PATH_CLOSE_PATH
:
236 // Corrupt path data!
242 bool PathCairo::IsEmpty() const {
243 for (size_t i
= 0; i
< mPathData
.size(); i
++) {
244 switch (mPathData
[i
].header
.type
) {
245 case CAIRO_PATH_MOVE_TO
:
247 case CAIRO_PATH_CLOSE_PATH
:
256 void PathCairo::EnsureContainingContext(const Matrix
& aTransform
) const {
257 if (mContainingContext
) {
258 if (mContainingTransform
.ExactlyEquals(aTransform
)) {
262 mContainingContext
= cairo_create(DrawTargetCairo::GetDummySurface());
265 mContainingTransform
= aTransform
;
268 GfxMatrixToCairoMatrix(mContainingTransform
, mat
);
269 cairo_set_matrix(mContainingContext
, &mat
);
271 SetPathOnContext(mContainingContext
);
274 void PathCairo::SetPathOnContext(cairo_t
* aContext
) const {
275 // Needs the correct fill rule set.
276 cairo_set_fill_rule(aContext
, GfxFillRuleToCairoFillRule(mFillRule
));
278 cairo_new_path(aContext
);
280 if (!mPathData
.empty()) {
282 path
.data
= const_cast<cairo_path_data_t
*>(&mPathData
.front());
283 path
.num_data
= mPathData
.size();
284 path
.status
= CAIRO_STATUS_SUCCESS
;
285 cairo_append_path(aContext
, &path
);
289 void PathCairo::AppendPathToBuilder(PathBuilderCairo
* aBuilder
,
290 const Matrix
* aTransform
) const {
293 while (i
< mPathData
.size()) {
294 uint32_t pointCount
= mPathData
[i
].header
.length
- 1;
295 aBuilder
->mPathData
.push_back(mPathData
[i
]);
297 for (uint32_t c
= 0; c
< pointCount
; c
++) {
298 cairo_path_data_t data
;
299 Point newPoint
= aTransform
->TransformPoint(
300 Point(mPathData
[i
].point
.x
, mPathData
[i
].point
.y
));
301 data
.point
.x
= newPoint
.x
;
302 data
.point
.y
= newPoint
.y
;
303 aBuilder
->mPathData
.push_back(data
);
308 for (size_t i
= 0; i
< mPathData
.size(); i
++) {
309 aBuilder
->mPathData
.push_back(mPathData
[i
]);
315 } // namespace mozilla