Line data Source code
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 0 : PathBuilderCairo::PathBuilderCairo(FillRule aFillRule)
17 0 : : mFillRule(aFillRule)
18 : {
19 0 : }
20 :
21 : void
22 0 : PathBuilderCairo::MoveTo(const Point &aPoint)
23 : {
24 : cairo_path_data_t data;
25 0 : data.header.type = CAIRO_PATH_MOVE_TO;
26 0 : data.header.length = 2;
27 0 : mPathData.push_back(data);
28 0 : data.point.x = aPoint.x;
29 0 : data.point.y = aPoint.y;
30 0 : mPathData.push_back(data);
31 :
32 0 : mBeginPoint = mCurrentPoint = aPoint;
33 0 : }
34 :
35 : void
36 0 : PathBuilderCairo::LineTo(const Point &aPoint)
37 : {
38 : cairo_path_data_t data;
39 0 : data.header.type = CAIRO_PATH_LINE_TO;
40 0 : data.header.length = 2;
41 0 : mPathData.push_back(data);
42 0 : data.point.x = aPoint.x;
43 0 : data.point.y = aPoint.y;
44 0 : mPathData.push_back(data);
45 :
46 0 : mCurrentPoint = aPoint;
47 0 : }
48 :
49 : void
50 0 : PathBuilderCairo::BezierTo(const Point &aCP1,
51 : const Point &aCP2,
52 : const Point &aCP3)
53 : {
54 : cairo_path_data_t data;
55 0 : data.header.type = CAIRO_PATH_CURVE_TO;
56 0 : data.header.length = 4;
57 0 : mPathData.push_back(data);
58 0 : data.point.x = aCP1.x;
59 0 : data.point.y = aCP1.y;
60 0 : mPathData.push_back(data);
61 0 : data.point.x = aCP2.x;
62 0 : data.point.y = aCP2.y;
63 0 : mPathData.push_back(data);
64 0 : data.point.x = aCP3.x;
65 0 : data.point.y = aCP3.y;
66 0 : mPathData.push_back(data);
67 :
68 0 : mCurrentPoint = aCP3;
69 0 : }
70 :
71 : void
72 0 : 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 0 : Point CP0 = CurrentPoint();
80 0 : Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
81 0 : Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
82 0 : Point CP3 = aCP2;
83 :
84 : cairo_path_data_t data;
85 0 : data.header.type = CAIRO_PATH_CURVE_TO;
86 0 : data.header.length = 4;
87 0 : mPathData.push_back(data);
88 0 : data.point.x = CP1.x;
89 0 : data.point.y = CP1.y;
90 0 : mPathData.push_back(data);
91 0 : data.point.x = CP2.x;
92 0 : data.point.y = CP2.y;
93 0 : mPathData.push_back(data);
94 0 : data.point.x = CP3.x;
95 0 : data.point.y = CP3.y;
96 0 : mPathData.push_back(data);
97 :
98 0 : mCurrentPoint = aCP2;
99 0 : }
100 :
101 : void
102 0 : PathBuilderCairo::Close()
103 : {
104 : cairo_path_data_t data;
105 0 : data.header.type = CAIRO_PATH_CLOSE_PATH;
106 0 : data.header.length = 1;
107 0 : mPathData.push_back(data);
108 :
109 0 : mCurrentPoint = mBeginPoint;
110 0 : }
111 :
112 : void
113 0 : PathBuilderCairo::Arc(const Point &aOrigin, float aRadius, float aStartAngle,
114 : float aEndAngle, bool aAntiClockwise)
115 : {
116 0 : ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise);
117 0 : }
118 :
119 : Point
120 0 : PathBuilderCairo::CurrentPoint() const
121 : {
122 0 : return mCurrentPoint;
123 : }
124 :
125 : already_AddRefed<Path>
126 0 : PathBuilderCairo::Finish()
127 : {
128 0 : return MakeAndAddRef<PathCairo>(mFillRule, mPathData, mCurrentPoint);
129 : }
130 :
131 0 : PathCairo::PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t> &aPathData, const Point &aCurrentPoint)
132 : : mFillRule(aFillRule)
133 : , mContainingContext(nullptr)
134 0 : , mCurrentPoint(aCurrentPoint)
135 : {
136 0 : mPathData.swap(aPathData);
137 0 : }
138 :
139 0 : PathCairo::PathCairo(cairo_t *aContext)
140 : : mFillRule(FillRule::FILL_WINDING)
141 0 : , mContainingContext(nullptr)
142 : {
143 0 : 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 0 : for (int i = 0; i < path->num_data; i++) {
149 0 : mPathData.push_back(path->data[i]);
150 : }
151 :
152 0 : cairo_path_destroy(path);
153 0 : }
154 :
155 0 : PathCairo::~PathCairo()
156 : {
157 0 : if (mContainingContext) {
158 0 : cairo_destroy(mContainingContext);
159 : }
160 0 : }
161 :
162 : already_AddRefed<PathBuilder>
163 0 : PathCairo::CopyToBuilder(FillRule aFillRule) const
164 : {
165 0 : RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule);
166 :
167 0 : builder->mPathData = mPathData;
168 0 : builder->mCurrentPoint = mCurrentPoint;
169 :
170 0 : return builder.forget();
171 : }
172 :
173 : already_AddRefed<PathBuilder>
174 0 : PathCairo::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
175 : {
176 0 : RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule);
177 :
178 0 : AppendPathToBuilder(builder, &aTransform);
179 0 : builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint);
180 :
181 0 : return builder.forget();
182 : }
183 :
184 : bool
185 0 : PathCairo::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
186 : {
187 0 : Matrix inverse = aTransform;
188 0 : inverse.Invert();
189 0 : Point transformed = inverse.TransformPoint(aPoint);
190 :
191 0 : EnsureContainingContext(aTransform);
192 :
193 0 : return cairo_in_fill(mContainingContext, transformed.x, transformed.y);
194 : }
195 :
196 : bool
197 0 : PathCairo::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
198 : const Point &aPoint,
199 : const Matrix &aTransform) const
200 : {
201 0 : Matrix inverse = aTransform;
202 0 : inverse.Invert();
203 0 : Point transformed = inverse.TransformPoint(aPoint);
204 :
205 0 : EnsureContainingContext(aTransform);
206 :
207 0 : SetCairoStrokeOptions(mContainingContext, aStrokeOptions);
208 :
209 0 : return cairo_in_stroke(mContainingContext, transformed.x, transformed.y);
210 : }
211 :
212 : Rect
213 0 : PathCairo::GetBounds(const Matrix &aTransform) const
214 : {
215 0 : EnsureContainingContext(aTransform);
216 :
217 : double x1, y1, x2, y2;
218 :
219 0 : cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2);
220 0 : Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1));
221 0 : return aTransform.TransformBounds(bounds);
222 : }
223 :
224 : Rect
225 0 : PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
226 : const Matrix &aTransform) const
227 : {
228 0 : EnsureContainingContext(aTransform);
229 :
230 : double x1, y1, x2, y2;
231 :
232 0 : SetCairoStrokeOptions(mContainingContext, aStrokeOptions);
233 :
234 0 : cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2);
235 0 : Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1));
236 0 : return aTransform.TransformBounds(bounds);
237 : }
238 :
239 : void
240 0 : PathCairo::StreamToSink(PathSink *aSink) const
241 : {
242 0 : for (size_t i = 0; i < mPathData.size(); i++) {
243 0 : switch (mPathData[i].header.type) {
244 : case CAIRO_PATH_MOVE_TO:
245 0 : i++;
246 0 : aSink->MoveTo(Point(mPathData[i].point.x, mPathData[i].point.y));
247 0 : break;
248 : case CAIRO_PATH_LINE_TO:
249 0 : i++;
250 0 : aSink->LineTo(Point(mPathData[i].point.x, mPathData[i].point.y));
251 0 : break;
252 : case CAIRO_PATH_CURVE_TO:
253 0 : aSink->BezierTo(Point(mPathData[i + 1].point.x, mPathData[i + 1].point.y),
254 0 : Point(mPathData[i + 2].point.x, mPathData[i + 2].point.y),
255 0 : Point(mPathData[i + 3].point.x, mPathData[i + 3].point.y));
256 0 : i += 3;
257 0 : break;
258 : case CAIRO_PATH_CLOSE_PATH:
259 0 : aSink->Close();
260 0 : break;
261 : default:
262 : // Corrupt path data!
263 0 : MOZ_ASSERT(false);
264 : }
265 : }
266 0 : }
267 :
268 : void
269 0 : PathCairo::EnsureContainingContext(const Matrix &aTransform) const
270 : {
271 0 : if (mContainingContext) {
272 0 : if (mContainingTransform.ExactlyEquals(aTransform)) {
273 0 : return;
274 : }
275 : } else {
276 0 : mContainingContext = cairo_create(DrawTargetCairo::GetDummySurface());
277 : }
278 :
279 0 : mContainingTransform = aTransform;
280 :
281 : cairo_matrix_t mat;
282 0 : GfxMatrixToCairoMatrix(mContainingTransform, mat);
283 0 : cairo_set_matrix(mContainingContext, &mat);
284 :
285 0 : SetPathOnContext(mContainingContext);
286 : }
287 :
288 : void
289 0 : PathCairo::SetPathOnContext(cairo_t *aContext) const
290 : {
291 : // Needs the correct fill rule set.
292 0 : cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule));
293 :
294 0 : cairo_new_path(aContext);
295 :
296 0 : if (mPathData.size()) {
297 : cairo_path_t path;
298 0 : path.data = const_cast<cairo_path_data_t*>(&mPathData.front());
299 0 : path.num_data = mPathData.size();
300 0 : path.status = CAIRO_STATUS_SUCCESS;
301 0 : cairo_append_path(aContext, &path);
302 : }
303 0 : }
304 :
305 : void
306 0 : PathCairo::AppendPathToBuilder(PathBuilderCairo *aBuilder, const Matrix *aTransform) const
307 : {
308 0 : if (aTransform) {
309 0 : size_t i = 0;
310 0 : while (i < mPathData.size()) {
311 0 : uint32_t pointCount = mPathData[i].header.length - 1;
312 0 : aBuilder->mPathData.push_back(mPathData[i]);
313 0 : i++;
314 0 : for (uint32_t c = 0; c < pointCount; c++) {
315 : cairo_path_data_t data;
316 0 : Point newPoint = aTransform->TransformPoint(Point(mPathData[i].point.x, mPathData[i].point.y));
317 0 : data.point.x = newPoint.x;
318 0 : data.point.y = newPoint.y;
319 0 : aBuilder->mPathData.push_back(data);
320 0 : i++;
321 : }
322 : }
323 : } else {
324 0 : for (size_t i = 0; i < mPathData.size(); i++) {
325 0 : aBuilder->mPathData.push_back(mPathData[i]);
326 : }
327 : }
328 0 : }
329 :
330 : } // namespace gfx
331 : } // namespace mozilla
|