|
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/. */ |
|
5 |
|
6 #include "PathCairo.h" |
|
7 #include <math.h> |
|
8 #include "DrawTargetCairo.h" |
|
9 #include "Logging.h" |
|
10 #include "PathHelpers.h" |
|
11 #include "HelpersCairo.h" |
|
12 |
|
13 namespace mozilla { |
|
14 namespace gfx { |
|
15 |
|
16 PathBuilderCairo::PathBuilderCairo(FillRule aFillRule) |
|
17 : mFillRule(aFillRule) |
|
18 { |
|
19 } |
|
20 |
|
21 void |
|
22 PathBuilderCairo::MoveTo(const Point &aPoint) |
|
23 { |
|
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); |
|
31 |
|
32 mBeginPoint = mCurrentPoint = aPoint; |
|
33 } |
|
34 |
|
35 void |
|
36 PathBuilderCairo::LineTo(const Point &aPoint) |
|
37 { |
|
38 cairo_path_data_t data; |
|
39 data.header.type = CAIRO_PATH_LINE_TO; |
|
40 data.header.length = 2; |
|
41 mPathData.push_back(data); |
|
42 data.point.x = aPoint.x; |
|
43 data.point.y = aPoint.y; |
|
44 mPathData.push_back(data); |
|
45 |
|
46 mCurrentPoint = aPoint; |
|
47 } |
|
48 |
|
49 void |
|
50 PathBuilderCairo::BezierTo(const Point &aCP1, |
|
51 const Point &aCP2, |
|
52 const Point &aCP3) |
|
53 { |
|
54 cairo_path_data_t data; |
|
55 data.header.type = CAIRO_PATH_CURVE_TO; |
|
56 data.header.length = 4; |
|
57 mPathData.push_back(data); |
|
58 data.point.x = aCP1.x; |
|
59 data.point.y = aCP1.y; |
|
60 mPathData.push_back(data); |
|
61 data.point.x = aCP2.x; |
|
62 data.point.y = aCP2.y; |
|
63 mPathData.push_back(data); |
|
64 data.point.x = aCP3.x; |
|
65 data.point.y = aCP3.y; |
|
66 mPathData.push_back(data); |
|
67 |
|
68 mCurrentPoint = aCP3; |
|
69 } |
|
70 |
|
71 void |
|
72 PathBuilderCairo::QuadraticBezierTo(const Point &aCP1, |
|
73 const Point &aCP2) |
|
74 { |
|
75 // We need to elevate the degree of this quadratic Bézier to cubic, so we're |
|
76 // going to add an intermediate control point, and recompute control point 1. |
|
77 // The first and last control points remain the same. |
|
78 // This formula can be found on http://fontforge.sourceforge.net/bezier.html |
|
79 Point CP0 = CurrentPoint(); |
|
80 Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; |
|
81 Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; |
|
82 Point CP3 = aCP2; |
|
83 |
|
84 cairo_path_data_t data; |
|
85 data.header.type = CAIRO_PATH_CURVE_TO; |
|
86 data.header.length = 4; |
|
87 mPathData.push_back(data); |
|
88 data.point.x = CP1.x; |
|
89 data.point.y = CP1.y; |
|
90 mPathData.push_back(data); |
|
91 data.point.x = CP2.x; |
|
92 data.point.y = CP2.y; |
|
93 mPathData.push_back(data); |
|
94 data.point.x = CP3.x; |
|
95 data.point.y = CP3.y; |
|
96 mPathData.push_back(data); |
|
97 |
|
98 mCurrentPoint = aCP2; |
|
99 } |
|
100 |
|
101 void |
|
102 PathBuilderCairo::Close() |
|
103 { |
|
104 cairo_path_data_t data; |
|
105 data.header.type = CAIRO_PATH_CLOSE_PATH; |
|
106 data.header.length = 1; |
|
107 mPathData.push_back(data); |
|
108 |
|
109 mCurrentPoint = mBeginPoint; |
|
110 } |
|
111 |
|
112 void |
|
113 PathBuilderCairo::Arc(const Point &aOrigin, float aRadius, float aStartAngle, |
|
114 float aEndAngle, bool aAntiClockwise) |
|
115 { |
|
116 ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise); |
|
117 } |
|
118 |
|
119 Point |
|
120 PathBuilderCairo::CurrentPoint() const |
|
121 { |
|
122 return mCurrentPoint; |
|
123 } |
|
124 |
|
125 TemporaryRef<Path> |
|
126 PathBuilderCairo::Finish() |
|
127 { |
|
128 return new PathCairo(mFillRule, mPathData, mCurrentPoint); |
|
129 } |
|
130 |
|
131 PathCairo::PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t> &aPathData, const Point &aCurrentPoint) |
|
132 : mFillRule(aFillRule) |
|
133 , mContainingContext(nullptr) |
|
134 , mCurrentPoint(aCurrentPoint) |
|
135 { |
|
136 mPathData.swap(aPathData); |
|
137 } |
|
138 |
|
139 PathCairo::PathCairo(cairo_t *aContext) |
|
140 : mFillRule(FillRule::FILL_WINDING) |
|
141 , mContainingContext(nullptr) |
|
142 { |
|
143 cairo_path_t *path = cairo_copy_path(aContext); |
|
144 |
|
145 // XXX - mCurrentPoint is not properly set here, the same is true for the |
|
146 // D2D Path code, we never require current point when hitting this codepath |
|
147 // but this should be fixed. |
|
148 for (int i = 0; i < path->num_data; i++) { |
|
149 mPathData.push_back(path->data[i]); |
|
150 } |
|
151 |
|
152 cairo_path_destroy(path); |
|
153 } |
|
154 |
|
155 PathCairo::~PathCairo() |
|
156 { |
|
157 if (mContainingContext) { |
|
158 cairo_destroy(mContainingContext); |
|
159 } |
|
160 } |
|
161 |
|
162 TemporaryRef<PathBuilder> |
|
163 PathCairo::CopyToBuilder(FillRule aFillRule) const |
|
164 { |
|
165 RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); |
|
166 |
|
167 builder->mPathData = mPathData; |
|
168 builder->mCurrentPoint = mCurrentPoint; |
|
169 |
|
170 return builder; |
|
171 } |
|
172 |
|
173 TemporaryRef<PathBuilder> |
|
174 PathCairo::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const |
|
175 { |
|
176 RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); |
|
177 |
|
178 AppendPathToBuilder(builder, &aTransform); |
|
179 builder->mCurrentPoint = aTransform * mCurrentPoint; |
|
180 |
|
181 return builder; |
|
182 } |
|
183 |
|
184 bool |
|
185 PathCairo::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const |
|
186 { |
|
187 Matrix inverse = aTransform; |
|
188 inverse.Invert(); |
|
189 Point transformed = inverse * aPoint; |
|
190 |
|
191 EnsureContainingContext(); |
|
192 |
|
193 return cairo_in_fill(mContainingContext, transformed.x, transformed.y); |
|
194 } |
|
195 |
|
196 bool |
|
197 PathCairo::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, |
|
198 const Point &aPoint, |
|
199 const Matrix &aTransform) const |
|
200 { |
|
201 Matrix inverse = aTransform; |
|
202 inverse.Invert(); |
|
203 Point transformed = inverse * aPoint; |
|
204 |
|
205 EnsureContainingContext(); |
|
206 |
|
207 SetCairoStrokeOptions(mContainingContext, aStrokeOptions); |
|
208 |
|
209 return cairo_in_stroke(mContainingContext, transformed.x, transformed.y); |
|
210 } |
|
211 |
|
212 Rect |
|
213 PathCairo::GetBounds(const Matrix &aTransform) const |
|
214 { |
|
215 EnsureContainingContext(); |
|
216 |
|
217 double x1, y1, x2, y2; |
|
218 |
|
219 cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2); |
|
220 Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1)); |
|
221 return aTransform.TransformBounds(bounds); |
|
222 } |
|
223 |
|
224 Rect |
|
225 PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions, |
|
226 const Matrix &aTransform) const |
|
227 { |
|
228 EnsureContainingContext(); |
|
229 |
|
230 double x1, y1, x2, y2; |
|
231 |
|
232 SetCairoStrokeOptions(mContainingContext, aStrokeOptions); |
|
233 |
|
234 cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2); |
|
235 Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1)); |
|
236 return aTransform.TransformBounds(bounds); |
|
237 } |
|
238 |
|
239 void |
|
240 PathCairo::StreamToSink(PathSink *aSink) const |
|
241 { |
|
242 for (size_t i = 0; i < mPathData.size(); i++) { |
|
243 switch (mPathData[i].header.type) { |
|
244 case CAIRO_PATH_MOVE_TO: |
|
245 i++; |
|
246 aSink->MoveTo(Point(mPathData[i].point.x, mPathData[i].point.y)); |
|
247 break; |
|
248 case CAIRO_PATH_LINE_TO: |
|
249 i++; |
|
250 aSink->LineTo(Point(mPathData[i].point.x, mPathData[i].point.y)); |
|
251 break; |
|
252 case CAIRO_PATH_CURVE_TO: |
|
253 aSink->BezierTo(Point(mPathData[i + 1].point.x, mPathData[i + 1].point.y), |
|
254 Point(mPathData[i + 2].point.x, mPathData[i + 2].point.y), |
|
255 Point(mPathData[i + 3].point.x, mPathData[i + 3].point.y)); |
|
256 i += 3; |
|
257 break; |
|
258 case CAIRO_PATH_CLOSE_PATH: |
|
259 aSink->Close(); |
|
260 break; |
|
261 default: |
|
262 // Corrupt path data! |
|
263 MOZ_ASSERT(false); |
|
264 } |
|
265 } |
|
266 } |
|
267 |
|
268 void |
|
269 PathCairo::EnsureContainingContext() const |
|
270 { |
|
271 if (mContainingContext) { |
|
272 return; |
|
273 } |
|
274 |
|
275 mContainingContext = cairo_create(DrawTargetCairo::GetDummySurface()); |
|
276 |
|
277 SetPathOnContext(mContainingContext); |
|
278 } |
|
279 |
|
280 void |
|
281 PathCairo::SetPathOnContext(cairo_t *aContext) const |
|
282 { |
|
283 // Needs the correct fill rule set. |
|
284 cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule)); |
|
285 |
|
286 cairo_new_path(aContext); |
|
287 |
|
288 if (mPathData.size()) { |
|
289 cairo_path_t path; |
|
290 path.data = const_cast<cairo_path_data_t*>(&mPathData.front()); |
|
291 path.num_data = mPathData.size(); |
|
292 path.status = CAIRO_STATUS_SUCCESS; |
|
293 cairo_append_path(aContext, &path); |
|
294 } |
|
295 } |
|
296 |
|
297 void |
|
298 PathCairo::AppendPathToBuilder(PathBuilderCairo *aBuilder, const Matrix *aTransform) const |
|
299 { |
|
300 if (aTransform) { |
|
301 size_t i = 0; |
|
302 while (i < mPathData.size()) { |
|
303 uint32_t pointCount = mPathData[i].header.length - 1; |
|
304 aBuilder->mPathData.push_back(mPathData[i]); |
|
305 i++; |
|
306 for (uint32_t c = 0; c < pointCount; c++) { |
|
307 cairo_path_data_t data; |
|
308 Point newPoint = *aTransform * Point(mPathData[i].point.x, mPathData[i].point.y); |
|
309 data.point.x = newPoint.x; |
|
310 data.point.y = newPoint.y; |
|
311 aBuilder->mPathData.push_back(data); |
|
312 i++; |
|
313 } |
|
314 } |
|
315 } else { |
|
316 for (size_t i = 0; i < mPathData.size(); i++) { |
|
317 aBuilder->mPathData.push_back(mPathData[i]); |
|
318 } |
|
319 } |
|
320 } |
|
321 |
|
322 } |
|
323 } |