/src/mozilla-central/dom/svg/SVGPathData.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "SVGPathData.h" |
8 | | |
9 | | #include "gfx2DGlue.h" |
10 | | #include "gfxPlatform.h" |
11 | | #include "mozilla/gfx/2D.h" |
12 | | #include "mozilla/gfx/Types.h" |
13 | | #include "mozilla/gfx/Point.h" |
14 | | #include "mozilla/RefPtr.h" |
15 | | #include "nsError.h" |
16 | | #include "nsString.h" |
17 | | #include "nsSVGPathDataParser.h" |
18 | | #include <stdarg.h> |
19 | | #include "nsStyleConsts.h" |
20 | | #include "SVGContentUtils.h" |
21 | | #include "SVGGeometryElement.h" // for nsSVGMark |
22 | | #include "SVGPathSegUtils.h" |
23 | | #include <algorithm> |
24 | | |
25 | | using namespace mozilla; |
26 | | using namespace mozilla::dom::SVGPathSeg_Binding; |
27 | | using namespace mozilla::gfx; |
28 | | |
29 | | static inline bool IsMoveto(uint16_t aSegType) |
30 | 0 | { |
31 | 0 | return aSegType == PATHSEG_MOVETO_ABS || |
32 | 0 | aSegType == PATHSEG_MOVETO_REL; |
33 | 0 | } |
34 | | |
35 | | static inline bool |
36 | | IsMoveto(StylePathCommand::Tag aSegType) |
37 | 0 | { |
38 | 0 | return aSegType == StylePathCommand::Tag::MoveTo; |
39 | 0 | } |
40 | | |
41 | | static inline bool |
42 | | IsValidType(uint16_t aSegType) |
43 | 0 | { |
44 | 0 | return SVGPathSegUtils::IsValidType(aSegType); |
45 | 0 | } |
46 | | |
47 | | static inline bool |
48 | | IsValidType(StylePathCommand::Tag aSegType) |
49 | 0 | { |
50 | 0 | return aSegType != StylePathCommand::Tag::Unknown; |
51 | 0 | } |
52 | | |
53 | | static inline bool |
54 | 0 | IsClosePath(uint16_t aSegType) { |
55 | 0 | return aSegType == PATHSEG_CLOSEPATH; |
56 | 0 | } |
57 | | |
58 | | static inline bool |
59 | | IsClosePath(StylePathCommand::Tag aSegType) |
60 | 0 | { |
61 | 0 | return aSegType == StylePathCommand::Tag::ClosePath; |
62 | 0 | } |
63 | | |
64 | | nsresult |
65 | | SVGPathData::CopyFrom(const SVGPathData& rhs) |
66 | 0 | { |
67 | 0 | if (!mData.Assign(rhs.mData, fallible)) { |
68 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
69 | 0 | } |
70 | 0 | return NS_OK; |
71 | 0 | } |
72 | | |
73 | | void |
74 | | SVGPathData::GetValueAsString(nsAString& aValue) const |
75 | 0 | { |
76 | 0 | // we need this function in DidChangePathSegList |
77 | 0 | aValue.Truncate(); |
78 | 0 | if (!Length()) { |
79 | 0 | return; |
80 | 0 | } |
81 | 0 | uint32_t i = 0; |
82 | 0 | for (;;) { |
83 | 0 | nsAutoString segAsString; |
84 | 0 | SVGPathSegUtils::GetValueAsString(&mData[i], segAsString); |
85 | 0 | // We ignore OOM, since it's not useful for us to return an error. |
86 | 0 | aValue.Append(segAsString); |
87 | 0 | i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); |
88 | 0 | if (i >= mData.Length()) { |
89 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
90 | 0 | return; |
91 | 0 | } |
92 | 0 | aValue.Append(' '); |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | | nsresult |
97 | | SVGPathData::SetValueFromString(const nsAString& aValue) |
98 | 0 | { |
99 | 0 | // We don't use a temp variable since the spec says to parse everything up to |
100 | 0 | // the first error. We still return any error though so that callers know if |
101 | 0 | // there's a problem. |
102 | 0 |
|
103 | 0 | nsSVGPathDataParser pathParser(aValue, this); |
104 | 0 | return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR; |
105 | 0 | } |
106 | | |
107 | | nsresult |
108 | | SVGPathData::AppendSeg(uint32_t aType, ...) |
109 | 0 | { |
110 | 0 | uint32_t oldLength = mData.Length(); |
111 | 0 | uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType); |
112 | 0 | if (!mData.SetLength(newLength, fallible)) { |
113 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
114 | 0 | } |
115 | 0 | |
116 | 0 | mData[oldLength] = SVGPathSegUtils::EncodeType(aType); |
117 | 0 | va_list args; |
118 | 0 | va_start(args, aType); |
119 | 0 | for (uint32_t i = oldLength + 1; i < newLength; ++i) { |
120 | 0 | // NOTE! 'float' is promoted to 'double' when passed through '...'! |
121 | 0 | mData[i] = float(va_arg(args, double)); |
122 | 0 | } |
123 | 0 | va_end(args); |
124 | 0 | return NS_OK; |
125 | 0 | } |
126 | | |
127 | | float |
128 | | SVGPathData::GetPathLength() const |
129 | 0 | { |
130 | 0 | SVGPathTraversalState state; |
131 | 0 |
|
132 | 0 | uint32_t i = 0; |
133 | 0 | while (i < mData.Length()) { |
134 | 0 | SVGPathSegUtils::TraversePathSegment(&mData[i], state); |
135 | 0 | i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); |
136 | 0 | } |
137 | 0 |
|
138 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
139 | 0 |
|
140 | 0 | return state.length; |
141 | 0 | } |
142 | | |
143 | | #ifdef DEBUG |
144 | | uint32_t |
145 | | SVGPathData::CountItems() const |
146 | | { |
147 | | uint32_t i = 0, count = 0; |
148 | | |
149 | | while (i < mData.Length()) { |
150 | | i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); |
151 | | count++; |
152 | | } |
153 | | |
154 | | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
155 | | |
156 | | return count; |
157 | | } |
158 | | #endif |
159 | | |
160 | | bool |
161 | | SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const |
162 | 0 | { |
163 | 0 | aLengths->Clear(); |
164 | 0 | SVGPathTraversalState state; |
165 | 0 |
|
166 | 0 | uint32_t i = 0; |
167 | 0 | while (i < mData.Length()) { |
168 | 0 | state.length = 0.0; |
169 | 0 | SVGPathSegUtils::TraversePathSegment(&mData[i], state); |
170 | 0 | if (!aLengths->AppendElement(state.length)) { |
171 | 0 | aLengths->Clear(); |
172 | 0 | return false; |
173 | 0 | } |
174 | 0 | i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); |
175 | 0 | } |
176 | 0 |
|
177 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
178 | 0 |
|
179 | 0 | return true; |
180 | 0 | } |
181 | | |
182 | | bool |
183 | | SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(FallibleTArray<double> *aOutput) const |
184 | 0 | { |
185 | 0 | SVGPathTraversalState state; |
186 | 0 |
|
187 | 0 | aOutput->Clear(); |
188 | 0 |
|
189 | 0 | uint32_t i = 0; |
190 | 0 | while (i < mData.Length()) { |
191 | 0 | uint32_t segType = SVGPathSegUtils::DecodeType(mData[i]); |
192 | 0 | SVGPathSegUtils::TraversePathSegment(&mData[i], state); |
193 | 0 |
|
194 | 0 | // We skip all moveto commands except an initial moveto. See the text 'A |
195 | 0 | // "move to" command does not count as an additional point when dividing up |
196 | 0 | // the duration...': |
197 | 0 | // |
198 | 0 | // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement |
199 | 0 | // |
200 | 0 | // This is important in the non-default case of calcMode="linear". In |
201 | 0 | // this case an equal amount of time is spent on each path segment, |
202 | 0 | // except on moveto segments which are jumped over immediately. |
203 | 0 |
|
204 | 0 | if (i == 0 || (segType != PATHSEG_MOVETO_ABS && |
205 | 0 | segType != PATHSEG_MOVETO_REL)) { |
206 | 0 | if (!aOutput->AppendElement(state.length, fallible)) { |
207 | 0 | return false; |
208 | 0 | } |
209 | 0 | } |
210 | 0 | i += 1 + SVGPathSegUtils::ArgCountForType(segType); |
211 | 0 | } |
212 | 0 |
|
213 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?"); |
214 | 0 |
|
215 | 0 | return true; |
216 | 0 | } |
217 | | |
218 | | uint32_t |
219 | | SVGPathData::GetPathSegAtLength(float aDistance) const |
220 | 0 | { |
221 | 0 | // TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or |
222 | 0 | // 'aDistance' > the length of the path, or the seg list is empty. |
223 | 0 | // Return -1? Throwing would better help authors avoid tricky bugs (DOM |
224 | 0 | // could do that if we return -1). |
225 | 0 |
|
226 | 0 | uint32_t i = 0, segIndex = 0; |
227 | 0 | SVGPathTraversalState state; |
228 | 0 |
|
229 | 0 | while (i < mData.Length()) { |
230 | 0 | SVGPathSegUtils::TraversePathSegment(&mData[i], state); |
231 | 0 | if (state.length >= aDistance) { |
232 | 0 | return segIndex; |
233 | 0 | } |
234 | 0 | i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); |
235 | 0 | segIndex++; |
236 | 0 | } |
237 | 0 |
|
238 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
239 | 0 |
|
240 | 0 | return std::max(1U, segIndex) - 1; // -1 because while loop takes us 1 too far |
241 | 0 | } |
242 | | |
243 | | /** |
244 | | * The SVG spec says we have to paint stroke caps for zero length subpaths: |
245 | | * |
246 | | * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes |
247 | | * |
248 | | * Cairo only does this for |stroke-linecap: round| and not for |
249 | | * |stroke-linecap: square| (since that's what Adobe Acrobat has always done). |
250 | | * Most likely the other backends that DrawTarget uses have the same behavior. |
251 | | * |
252 | | * To help us conform to the SVG spec we have this helper function to draw an |
253 | | * approximation of square caps for zero length subpaths. It does this by |
254 | | * inserting a subpath containing a single user space axis aligned straight |
255 | | * line that is as small as it can be while minimizing the risk of it being |
256 | | * thrown away by the DrawTarget's backend for being too small to affect |
257 | | * rendering. The idea is that we'll then get stroke caps drawn for this axis |
258 | | * aligned line, creating an axis aligned rectangle that approximates the |
259 | | * square that would ideally be drawn. |
260 | | * |
261 | | * Since we don't have any information about transforms from user space to |
262 | | * device space, we choose the length of the small line that we insert by |
263 | | * making it a small percentage of the stroke width of the path. This should |
264 | | * hopefully allow us to make the line as long as possible (to avoid rounding |
265 | | * issues in the backend resulting in the backend seeing it as having zero |
266 | | * length) while still avoiding the small rectangle being noticeably different |
267 | | * from a square. |
268 | | * |
269 | | * Note that this function inserts a subpath into the current gfx path that |
270 | | * will be present during both fill and stroke operations. |
271 | | */ |
272 | | static void |
273 | | ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB, |
274 | | const Point& aPoint, |
275 | | Float aStrokeWidth) |
276 | 0 | { |
277 | 0 | // Note that caps are proportional to stroke width, so if stroke width is |
278 | 0 | // zero it's actually fine for |tinyLength| below to end up being zero. |
279 | 0 | // However, it would be a waste to inserting a LineTo in that case, so better |
280 | 0 | // not to. |
281 | 0 | MOZ_ASSERT(aStrokeWidth > 0.0f, |
282 | 0 | "Make the caller check for this, or check it here"); |
283 | 0 |
|
284 | 0 | // The fraction of the stroke width that we choose for the length of the |
285 | 0 | // line is rather arbitrary, other than being chosen to meet the requirements |
286 | 0 | // described in the comment above. |
287 | 0 |
|
288 | 0 | Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR; |
289 | 0 |
|
290 | 0 | aPB->LineTo(aPoint + Point(tinyLength, 0)); |
291 | 0 | aPB->MoveTo(aPoint); |
292 | 0 | } |
293 | | |
294 | | #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \ |
295 | 0 | do { \ |
296 | 0 | if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \ |
297 | 0 | subpathContainsNonMoveTo && \ |
298 | 0 | IsValidType(prevSegType) && \ |
299 | 0 | (!IsMoveto(prevSegType) || IsClosePath(segType))) { \ |
300 | 0 | ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, aStrokeWidth);\ |
301 | 0 | } \ |
302 | 0 | } while(0) |
303 | | |
304 | | already_AddRefed<Path> |
305 | | SVGPathData::BuildPath(PathBuilder* aBuilder, |
306 | | uint8_t aStrokeLineCap, |
307 | | Float aStrokeWidth) const |
308 | 0 | { |
309 | 0 | if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) { |
310 | 0 | return nullptr; // paths without an initial moveto are invalid |
311 | 0 | } |
312 | 0 | |
313 | 0 | bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT; |
314 | 0 | bool subpathHasLength = false; // visual length |
315 | 0 | bool subpathContainsNonMoveTo = false; |
316 | 0 |
|
317 | 0 | uint32_t segType = PATHSEG_UNKNOWN; |
318 | 0 | uint32_t prevSegType = PATHSEG_UNKNOWN; |
319 | 0 | Point pathStart(0.0, 0.0); // start point of [sub]path |
320 | 0 | Point segStart(0.0, 0.0); |
321 | 0 | Point segEnd; |
322 | 0 | Point cp1, cp2; // previous bezier's control points |
323 | 0 | Point tcp1, tcp2; // temporaries |
324 | 0 |
|
325 | 0 | // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, |
326 | 0 | // then cp2 is its second control point. If the previous segment was a |
327 | 0 | // quadratic curve, then cp1 is its (only) control point. |
328 | 0 |
|
329 | 0 | uint32_t i = 0; |
330 | 0 | while (i < mData.Length()) { |
331 | 0 | segType = SVGPathSegUtils::DecodeType(mData[i++]); |
332 | 0 | uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType); |
333 | 0 |
|
334 | 0 | switch (segType) |
335 | 0 | { |
336 | 0 | case PATHSEG_CLOSEPATH: |
337 | 0 | // set this early to allow drawing of square caps for "M{x},{y} Z": |
338 | 0 | subpathContainsNonMoveTo = true; |
339 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
340 | 0 | segEnd = pathStart; |
341 | 0 | aBuilder->Close(); |
342 | 0 | break; |
343 | 0 |
|
344 | 0 | case PATHSEG_MOVETO_ABS: |
345 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
346 | 0 | pathStart = segEnd = Point(mData[i], mData[i+1]); |
347 | 0 | aBuilder->MoveTo(segEnd); |
348 | 0 | subpathHasLength = false; |
349 | 0 | break; |
350 | 0 |
|
351 | 0 | case PATHSEG_MOVETO_REL: |
352 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
353 | 0 | pathStart = segEnd = segStart + Point(mData[i], mData[i+1]); |
354 | 0 | aBuilder->MoveTo(segEnd); |
355 | 0 | subpathHasLength = false; |
356 | 0 | break; |
357 | 0 |
|
358 | 0 | case PATHSEG_LINETO_ABS: |
359 | 0 | segEnd = Point(mData[i], mData[i+1]); |
360 | 0 | if (segEnd != segStart) { |
361 | 0 | subpathHasLength = true; |
362 | 0 | aBuilder->LineTo(segEnd); |
363 | 0 | } |
364 | 0 | break; |
365 | 0 |
|
366 | 0 | case PATHSEG_LINETO_REL: |
367 | 0 | segEnd = segStart + Point(mData[i], mData[i+1]); |
368 | 0 | if (segEnd != segStart) { |
369 | 0 | subpathHasLength = true; |
370 | 0 | aBuilder->LineTo(segEnd); |
371 | 0 | } |
372 | 0 | break; |
373 | 0 |
|
374 | 0 | case PATHSEG_CURVETO_CUBIC_ABS: |
375 | 0 | cp1 = Point(mData[i], mData[i+1]); |
376 | 0 | cp2 = Point(mData[i+2], mData[i+3]); |
377 | 0 | segEnd = Point(mData[i+4], mData[i+5]); |
378 | 0 | if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { |
379 | 0 | subpathHasLength = true; |
380 | 0 | aBuilder->BezierTo(cp1, cp2, segEnd); |
381 | 0 | } |
382 | 0 | break; |
383 | 0 |
|
384 | 0 | case PATHSEG_CURVETO_CUBIC_REL: |
385 | 0 | cp1 = segStart + Point(mData[i], mData[i+1]); |
386 | 0 | cp2 = segStart + Point(mData[i+2], mData[i+3]); |
387 | 0 | segEnd = segStart + Point(mData[i+4], mData[i+5]); |
388 | 0 | if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { |
389 | 0 | subpathHasLength = true; |
390 | 0 | aBuilder->BezierTo(cp1, cp2, segEnd); |
391 | 0 | } |
392 | 0 | break; |
393 | 0 |
|
394 | 0 | case PATHSEG_CURVETO_QUADRATIC_ABS: |
395 | 0 | cp1 = Point(mData[i], mData[i+1]); |
396 | 0 | // Convert quadratic curve to cubic curve: |
397 | 0 | tcp1 = segStart + (cp1 - segStart) * 2 / 3; |
398 | 0 | segEnd = Point(mData[i+2], mData[i+3]); // set before setting tcp2! |
399 | 0 | tcp2 = cp1 + (segEnd - cp1) / 3; |
400 | 0 | if (segEnd != segStart || segEnd != cp1) { |
401 | 0 | subpathHasLength = true; |
402 | 0 | aBuilder->BezierTo(tcp1, tcp2, segEnd); |
403 | 0 | } |
404 | 0 | break; |
405 | 0 |
|
406 | 0 | case PATHSEG_CURVETO_QUADRATIC_REL: |
407 | 0 | cp1 = segStart + Point(mData[i], mData[i+1]); |
408 | 0 | // Convert quadratic curve to cubic curve: |
409 | 0 | tcp1 = segStart + (cp1 - segStart) * 2 / 3; |
410 | 0 | segEnd = segStart + Point(mData[i+2], mData[i+3]); // set before setting tcp2! |
411 | 0 | tcp2 = cp1 + (segEnd - cp1) / 3; |
412 | 0 | if (segEnd != segStart || segEnd != cp1) { |
413 | 0 | subpathHasLength = true; |
414 | 0 | aBuilder->BezierTo(tcp1, tcp2, segEnd); |
415 | 0 | } |
416 | 0 | break; |
417 | 0 |
|
418 | 0 | case PATHSEG_ARC_ABS: |
419 | 0 | case PATHSEG_ARC_REL: |
420 | 0 | { |
421 | 0 | Point radii(mData[i], mData[i+1]); |
422 | 0 | segEnd = Point(mData[i+5], mData[i+6]); |
423 | 0 | if (segType == PATHSEG_ARC_REL) { |
424 | 0 | segEnd += segStart; |
425 | 0 | } |
426 | 0 | if (segEnd != segStart) { |
427 | 0 | subpathHasLength = true; |
428 | 0 | if (radii.x == 0.0f || radii.y == 0.0f) { |
429 | 0 | aBuilder->LineTo(segEnd); |
430 | 0 | } else { |
431 | 0 | nsSVGArcConverter converter(segStart, segEnd, radii, mData[i+2], |
432 | 0 | mData[i+3] != 0, mData[i+4] != 0); |
433 | 0 | while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { |
434 | 0 | aBuilder->BezierTo(cp1, cp2, segEnd); |
435 | 0 | } |
436 | 0 | } |
437 | 0 | } |
438 | 0 | break; |
439 | 0 | } |
440 | 0 |
|
441 | 0 | case PATHSEG_LINETO_HORIZONTAL_ABS: |
442 | 0 | segEnd = Point(mData[i], segStart.y); |
443 | 0 | if (segEnd != segStart) { |
444 | 0 | subpathHasLength = true; |
445 | 0 | aBuilder->LineTo(segEnd); |
446 | 0 | } |
447 | 0 | break; |
448 | 0 |
|
449 | 0 | case PATHSEG_LINETO_HORIZONTAL_REL: |
450 | 0 | segEnd = segStart + Point(mData[i], 0.0f); |
451 | 0 | if (segEnd != segStart) { |
452 | 0 | subpathHasLength = true; |
453 | 0 | aBuilder->LineTo(segEnd); |
454 | 0 | } |
455 | 0 | break; |
456 | 0 |
|
457 | 0 | case PATHSEG_LINETO_VERTICAL_ABS: |
458 | 0 | segEnd = Point(segStart.x, mData[i]); |
459 | 0 | if (segEnd != segStart) { |
460 | 0 | subpathHasLength = true; |
461 | 0 | aBuilder->LineTo(segEnd); |
462 | 0 | } |
463 | 0 | break; |
464 | 0 |
|
465 | 0 | case PATHSEG_LINETO_VERTICAL_REL: |
466 | 0 | segEnd = segStart + Point(0.0f, mData[i]); |
467 | 0 | if (segEnd != segStart) { |
468 | 0 | subpathHasLength = true; |
469 | 0 | aBuilder->LineTo(segEnd); |
470 | 0 | } |
471 | 0 | break; |
472 | 0 |
|
473 | 0 | case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: |
474 | 0 | cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart; |
475 | 0 | cp2 = Point(mData[i], mData[i+1]); |
476 | 0 | segEnd = Point(mData[i+2], mData[i+3]); |
477 | 0 | if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { |
478 | 0 | subpathHasLength = true; |
479 | 0 | aBuilder->BezierTo(cp1, cp2, segEnd); |
480 | 0 | } |
481 | 0 | break; |
482 | 0 |
|
483 | 0 | case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: |
484 | 0 | cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart; |
485 | 0 | cp2 = segStart + Point(mData[i], mData[i+1]); |
486 | 0 | segEnd = segStart + Point(mData[i+2], mData[i+3]); |
487 | 0 | if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { |
488 | 0 | subpathHasLength = true; |
489 | 0 | aBuilder->BezierTo(cp1, cp2, segEnd); |
490 | 0 | } |
491 | 0 | break; |
492 | 0 |
|
493 | 0 | case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: |
494 | 0 | cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart; |
495 | 0 | // Convert quadratic curve to cubic curve: |
496 | 0 | tcp1 = segStart + (cp1 - segStart) * 2 / 3; |
497 | 0 | segEnd = Point(mData[i], mData[i+1]); // set before setting tcp2! |
498 | 0 | tcp2 = cp1 + (segEnd - cp1) / 3; |
499 | 0 | if (segEnd != segStart || segEnd != cp1) { |
500 | 0 | subpathHasLength = true; |
501 | 0 | aBuilder->BezierTo(tcp1, tcp2, segEnd); |
502 | 0 | } |
503 | 0 | break; |
504 | 0 |
|
505 | 0 | case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: |
506 | 0 | cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart; |
507 | 0 | // Convert quadratic curve to cubic curve: |
508 | 0 | tcp1 = segStart + (cp1 - segStart) * 2 / 3; |
509 | 0 | segEnd = segStart + Point(mData[i], mData[i+1]); // changed before setting tcp2! |
510 | 0 | tcp2 = cp1 + (segEnd - cp1) / 3; |
511 | 0 | if (segEnd != segStart || segEnd != cp1) { |
512 | 0 | subpathHasLength = true; |
513 | 0 | aBuilder->BezierTo(tcp1, tcp2, segEnd); |
514 | 0 | } |
515 | 0 | break; |
516 | 0 |
|
517 | 0 | default: |
518 | 0 | MOZ_ASSERT_UNREACHABLE("Bad path segment type"); |
519 | 0 | return nullptr; // according to spec we'd use everything up to the bad seg anyway |
520 | 0 | } |
521 | 0 |
|
522 | 0 | subpathContainsNonMoveTo = segType != PATHSEG_MOVETO_ABS && |
523 | 0 | segType != PATHSEG_MOVETO_REL; |
524 | 0 | i += argCount; |
525 | 0 | prevSegType = segType; |
526 | 0 | segStart = segEnd; |
527 | 0 | } |
528 | 0 |
|
529 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
530 | 0 | MOZ_ASSERT(prevSegType == segType, |
531 | 0 | "prevSegType should be left at the final segType"); |
532 | 0 |
|
533 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
534 | 0 |
|
535 | 0 | return aBuilder->Finish(); |
536 | 0 | } |
537 | | |
538 | | already_AddRefed<Path> |
539 | | SVGPathData::BuildPathForMeasuring() const |
540 | 0 | { |
541 | 0 | // Since the path that we return will not be used for painting it doesn't |
542 | 0 | // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want |
543 | 0 | // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as |
544 | 0 | // aStrokeLineCap to avoid the insertion of extra little lines (by |
545 | 0 | // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we |
546 | 0 | // pass as aStrokeWidth doesn't matter (since it's only used to determine the |
547 | 0 | // length of those extra little lines). |
548 | 0 |
|
549 | 0 | RefPtr<DrawTarget> drawTarget = |
550 | 0 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); |
551 | 0 | RefPtr<PathBuilder> builder = |
552 | 0 | drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); |
553 | 0 | return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0); |
554 | 0 | } |
555 | | |
556 | | // We could simplify this function because this is only used by CSS motion path |
557 | | // and clip-path, which don't render the SVG Path. i.e. The returned path is |
558 | | // used as a reference. |
559 | | /* static */ already_AddRefed<Path> |
560 | | SVGPathData::BuildPath(const nsTArray<StylePathCommand>& aPath, |
561 | | PathBuilder* aBuilder, |
562 | | uint8_t aStrokeLineCap, |
563 | | Float aStrokeWidth, |
564 | | float aZoomFactor) |
565 | 0 | { |
566 | 0 | if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) { |
567 | 0 | return nullptr; // paths without an initial moveto are invalid |
568 | 0 | } |
569 | 0 | |
570 | 0 | auto toGfxPoint = [](const StyleCoordPair& aPair) { |
571 | 0 | return Point(aPair._0, aPair._1); |
572 | 0 | }; |
573 | 0 |
|
574 | 0 | auto isCubicType = [](StylePathCommand::Tag aType) { |
575 | 0 | return aType == StylePathCommand::Tag::CurveTo || |
576 | 0 | aType == StylePathCommand::Tag::SmoothCurveTo; |
577 | 0 | }; |
578 | 0 |
|
579 | 0 | auto isQuadraticType = [](StylePathCommand::Tag aType) { |
580 | 0 | return aType == StylePathCommand::Tag::QuadBezierCurveTo || |
581 | 0 | aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo; |
582 | 0 | }; |
583 | 0 |
|
584 | 0 | bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT; |
585 | 0 | bool subpathHasLength = false; // visual length |
586 | 0 | bool subpathContainsNonMoveTo = false; |
587 | 0 |
|
588 | 0 | StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown; |
589 | 0 | StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown; |
590 | 0 | Point pathStart(0.0, 0.0); // start point of [sub]path |
591 | 0 | Point segStart(0.0, 0.0); |
592 | 0 | Point segEnd; |
593 | 0 | Point cp1, cp2; // previous bezier's control points |
594 | 0 | Point tcp1, tcp2; // temporaries |
595 | 0 |
|
596 | 0 | auto scale = [aZoomFactor](const Point& p) { |
597 | 0 | return Point(p.x * aZoomFactor, p.y * aZoomFactor); |
598 | 0 | }; |
599 | 0 |
|
600 | 0 | // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, |
601 | 0 | // then cp2 is its second control point. If the previous segment was a |
602 | 0 | // quadratic curve, then cp1 is its (only) control point. |
603 | 0 |
|
604 | 0 | for (const StylePathCommand& cmd: aPath) { |
605 | 0 | segType = cmd.tag; |
606 | 0 | switch (segType) { |
607 | 0 | case StylePathCommand::Tag::ClosePath: |
608 | 0 | // set this early to allow drawing of square caps for "M{x},{y} Z": |
609 | 0 | subpathContainsNonMoveTo = true; |
610 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
611 | 0 | segEnd = pathStart; |
612 | 0 | aBuilder->Close(); |
613 | 0 | break; |
614 | 0 | case StylePathCommand::Tag::MoveTo: { |
615 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
616 | 0 | const Point& p = toGfxPoint(cmd.move_to.point); |
617 | 0 | pathStart = segEnd = cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; |
618 | 0 | aBuilder->MoveTo(scale(segEnd)); |
619 | 0 | subpathHasLength = false; |
620 | 0 | break; |
621 | 0 | } |
622 | 0 | case StylePathCommand::Tag::LineTo: { |
623 | 0 | const Point& p = toGfxPoint(cmd.line_to.point); |
624 | 0 | segEnd = cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; |
625 | 0 | if (segEnd != segStart) { |
626 | 0 | subpathHasLength = true; |
627 | 0 | aBuilder->LineTo(scale(segEnd)); |
628 | 0 | } |
629 | 0 | break; |
630 | 0 | } |
631 | 0 | case StylePathCommand::Tag::CurveTo: |
632 | 0 | cp1 = toGfxPoint(cmd.curve_to.control1); |
633 | 0 | cp2 = toGfxPoint(cmd.curve_to.control2); |
634 | 0 | segEnd = toGfxPoint(cmd.curve_to.point); |
635 | 0 |
|
636 | 0 | if (cmd.curve_to.absolute == StyleIsAbsolute::No) { |
637 | 0 | cp1 += segStart; |
638 | 0 | cp2 += segStart; |
639 | 0 | segEnd += segStart; |
640 | 0 | } |
641 | 0 |
|
642 | 0 | if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { |
643 | 0 | subpathHasLength = true; |
644 | 0 | aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); |
645 | 0 | } |
646 | 0 | break; |
647 | 0 |
|
648 | 0 | case StylePathCommand::Tag::QuadBezierCurveTo: |
649 | 0 | cp1 = toGfxPoint(cmd.quad_bezier_curve_to.control1); |
650 | 0 | segEnd = toGfxPoint(cmd.quad_bezier_curve_to.point); |
651 | 0 |
|
652 | 0 | if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) { |
653 | 0 | cp1 += segStart; |
654 | 0 | segEnd += segStart; // set before setting tcp2! |
655 | 0 | } |
656 | 0 |
|
657 | 0 | // Convert quadratic curve to cubic curve: |
658 | 0 | tcp1 = segStart + (cp1 - segStart) * 2 / 3; |
659 | 0 | tcp2 = cp1 + (segEnd - cp1) / 3; |
660 | 0 |
|
661 | 0 | if (segEnd != segStart || segEnd != cp1) { |
662 | 0 | subpathHasLength = true; |
663 | 0 | aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); |
664 | 0 | } |
665 | 0 | break; |
666 | 0 |
|
667 | 0 | case StylePathCommand::Tag::EllipticalArc: { |
668 | 0 | const auto& arc = cmd.elliptical_arc; |
669 | 0 | Point radii(arc.rx, arc.ry); |
670 | 0 | segEnd = toGfxPoint(arc.point); |
671 | 0 | if (arc.absolute == StyleIsAbsolute::No) { |
672 | 0 | segEnd += segStart; |
673 | 0 | } |
674 | 0 | if (segEnd != segStart) { |
675 | 0 | subpathHasLength = true; |
676 | 0 | if (radii.x == 0.0f || radii.y == 0.0f) { |
677 | 0 | aBuilder->LineTo(scale(segEnd)); |
678 | 0 | } else { |
679 | 0 | nsSVGArcConverter converter(segStart, segEnd, radii, arc.angle, |
680 | 0 | arc.large_arc_flag._0, |
681 | 0 | arc.sweep_flag._0); |
682 | 0 | while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { |
683 | 0 | aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); |
684 | 0 | } |
685 | 0 | } |
686 | 0 | } |
687 | 0 | break; |
688 | 0 | } |
689 | 0 | case StylePathCommand::Tag::HorizontalLineTo: |
690 | 0 | if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) { |
691 | 0 | segEnd = Point(cmd.horizontal_line_to.x, segStart.y); |
692 | 0 | } else { |
693 | 0 | segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f); |
694 | 0 | } |
695 | 0 |
|
696 | 0 | if (segEnd != segStart) { |
697 | 0 | subpathHasLength = true; |
698 | 0 | aBuilder->LineTo(scale(segEnd)); |
699 | 0 | } |
700 | 0 | break; |
701 | 0 |
|
702 | 0 | case StylePathCommand::Tag::VerticalLineTo: |
703 | 0 | if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) { |
704 | 0 | segEnd = Point(segStart.x, cmd.vertical_line_to.y); |
705 | 0 | } else { |
706 | 0 | segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y); |
707 | 0 | } |
708 | 0 |
|
709 | 0 | if (segEnd != segStart) { |
710 | 0 | subpathHasLength = true; |
711 | 0 | aBuilder->LineTo(scale(segEnd)); |
712 | 0 | } |
713 | 0 | break; |
714 | 0 |
|
715 | 0 | case StylePathCommand::Tag::SmoothCurveTo: |
716 | 0 | cp1 = isCubicType(prevSegType) ? segStart * 2 - cp2 : segStart; |
717 | 0 | cp2 = toGfxPoint(cmd.smooth_curve_to.control2); |
718 | 0 | segEnd = toGfxPoint(cmd.smooth_curve_to.point); |
719 | 0 |
|
720 | 0 | if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) { |
721 | 0 | cp2 += segStart; |
722 | 0 | segEnd += segStart; |
723 | 0 | } |
724 | 0 |
|
725 | 0 | if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { |
726 | 0 | subpathHasLength = true; |
727 | 0 | aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); |
728 | 0 | } |
729 | 0 | break; |
730 | 0 |
|
731 | 0 | case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { |
732 | 0 | cp1 = isQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart; |
733 | 0 | // Convert quadratic curve to cubic curve: |
734 | 0 | tcp1 = segStart + (cp1 - segStart) * 2 / 3; |
735 | 0 |
|
736 | 0 | const Point& p = toGfxPoint(cmd.smooth_quad_bezier_curve_to.point); |
737 | 0 | // set before setting tcp2! |
738 | 0 | segEnd = |
739 | 0 | cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; |
740 | 0 | tcp2 = cp1 + (segEnd - cp1) / 3; |
741 | 0 |
|
742 | 0 | if (segEnd != segStart || segEnd != cp1) { |
743 | 0 | subpathHasLength = true; |
744 | 0 | aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); |
745 | 0 | } |
746 | 0 | break; |
747 | 0 | } |
748 | 0 | case StylePathCommand::Tag::Unknown: |
749 | 0 | MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type"); |
750 | 0 | return nullptr; |
751 | 0 | } |
752 | 0 |
|
753 | 0 | subpathContainsNonMoveTo = !IsMoveto(segType); |
754 | 0 | prevSegType = segType; |
755 | 0 | segStart = segEnd; |
756 | 0 | } |
757 | 0 |
|
758 | 0 | MOZ_ASSERT(prevSegType == segType, |
759 | 0 | "prevSegType should be left at the final segType"); |
760 | 0 |
|
761 | 0 | MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; |
762 | 0 |
|
763 | 0 | return aBuilder->Finish(); |
764 | 0 | } |
765 | | |
766 | | static double |
767 | | AngleOfVector(const Point& aVector) |
768 | 0 | { |
769 | 0 | // C99 says about atan2 "A domain error may occur if both arguments are |
770 | 0 | // zero" and "On a domain error, the function returns an implementation- |
771 | 0 | // defined value". In the case of atan2 the implementation-defined value |
772 | 0 | // seems to commonly be zero, but it could just as easily be a NaN value. |
773 | 0 | // We specifically want zero in this case, hence the check: |
774 | 0 |
|
775 | 0 | return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0; |
776 | 0 | } |
777 | | |
778 | | static float |
779 | | AngleOfVector(const Point& cp1, const Point& cp2) |
780 | 0 | { |
781 | 0 | return static_cast<float>(AngleOfVector(cp1 - cp2)); |
782 | 0 | } |
783 | | |
784 | | void |
785 | | SVGPathData::GetMarkerPositioningData(nsTArray<nsSVGMark> *aMarks) const |
786 | 0 | { |
787 | 0 | // This code should assume that ANY type of segment can appear at ANY index. |
788 | 0 | // It should also assume that segments such as M and Z can appear in weird |
789 | 0 | // places, and repeat multiple times consecutively. |
790 | 0 |
|
791 | 0 | // info on current [sub]path (reset every M command): |
792 | 0 | Point pathStart(0.0, 0.0); |
793 | 0 | float pathStartAngle = 0.0f; |
794 | 0 |
|
795 | 0 | // info on previous segment: |
796 | 0 | uint16_t prevSegType = PATHSEG_UNKNOWN; |
797 | 0 | Point prevSegEnd(0.0, 0.0); |
798 | 0 | float prevSegEndAngle = 0.0f; |
799 | 0 | Point prevCP; // if prev seg was a bezier, this was its last control point |
800 | 0 |
|
801 | 0 | uint32_t i = 0; |
802 | 0 | while (i < mData.Length()) { |
803 | 0 |
|
804 | 0 | // info on current segment: |
805 | 0 | uint16_t segType = |
806 | 0 | SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args |
807 | 0 | Point& segStart = prevSegEnd; |
808 | 0 | Point segEnd; |
809 | 0 | float segStartAngle, segEndAngle; |
810 | 0 |
|
811 | 0 | switch (segType) // to find segStartAngle, segEnd and segEndAngle |
812 | 0 | { |
813 | 0 | case PATHSEG_CLOSEPATH: |
814 | 0 | segEnd = pathStart; |
815 | 0 | segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); |
816 | 0 | break; |
817 | 0 |
|
818 | 0 | case PATHSEG_MOVETO_ABS: |
819 | 0 | case PATHSEG_MOVETO_REL: |
820 | 0 | if (segType == PATHSEG_MOVETO_ABS) { |
821 | 0 | segEnd = Point(mData[i], mData[i+1]); |
822 | 0 | } else { |
823 | 0 | segEnd = segStart + Point(mData[i], mData[i+1]); |
824 | 0 | } |
825 | 0 | pathStart = segEnd; |
826 | 0 | // If authors are going to specify multiple consecutive moveto commands |
827 | 0 | // with markers, me might as well make the angle do something useful: |
828 | 0 | segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); |
829 | 0 | i += 2; |
830 | 0 | break; |
831 | 0 |
|
832 | 0 | case PATHSEG_LINETO_ABS: |
833 | 0 | case PATHSEG_LINETO_REL: |
834 | 0 | if (segType == PATHSEG_LINETO_ABS) { |
835 | 0 | segEnd = Point(mData[i], mData[i+1]); |
836 | 0 | } else { |
837 | 0 | segEnd = segStart + Point(mData[i], mData[i+1]); |
838 | 0 | } |
839 | 0 | segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); |
840 | 0 | i += 2; |
841 | 0 | break; |
842 | 0 |
|
843 | 0 | case PATHSEG_CURVETO_CUBIC_ABS: |
844 | 0 | case PATHSEG_CURVETO_CUBIC_REL: |
845 | 0 | { |
846 | 0 | Point cp1, cp2; // control points |
847 | 0 | if (segType == PATHSEG_CURVETO_CUBIC_ABS) { |
848 | 0 | cp1 = Point(mData[i], mData[i+1]); |
849 | 0 | cp2 = Point(mData[i+2], mData[i+3]); |
850 | 0 | segEnd = Point(mData[i+4], mData[i+5]); |
851 | 0 | } else { |
852 | 0 | cp1 = segStart + Point(mData[i], mData[i+1]); |
853 | 0 | cp2 = segStart + Point(mData[i+2], mData[i+3]); |
854 | 0 | segEnd = segStart + Point(mData[i+4], mData[i+5]); |
855 | 0 | } |
856 | 0 | prevCP = cp2; |
857 | 0 | segStartAngle = |
858 | 0 | AngleOfVector(cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); |
859 | 0 | segEndAngle = |
860 | 0 | AngleOfVector(segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); |
861 | 0 | i += 6; |
862 | 0 | break; |
863 | 0 | } |
864 | 0 |
|
865 | 0 | case PATHSEG_CURVETO_QUADRATIC_ABS: |
866 | 0 | case PATHSEG_CURVETO_QUADRATIC_REL: |
867 | 0 | { |
868 | 0 | Point cp1; // control point |
869 | 0 | if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) { |
870 | 0 | cp1 = Point(mData[i], mData[i+1]); |
871 | 0 | segEnd = Point(mData[i+2], mData[i+3]); |
872 | 0 | } else { |
873 | 0 | cp1 = segStart + Point(mData[i], mData[i+1]); |
874 | 0 | segEnd = segStart + Point(mData[i+2], mData[i+3]); |
875 | 0 | } |
876 | 0 | prevCP = cp1; |
877 | 0 | segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); |
878 | 0 | segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); |
879 | 0 | i += 4; |
880 | 0 | break; |
881 | 0 | } |
882 | 0 |
|
883 | 0 | case PATHSEG_ARC_ABS: |
884 | 0 | case PATHSEG_ARC_REL: |
885 | 0 | { |
886 | 0 | double rx = mData[i]; |
887 | 0 | double ry = mData[i+1]; |
888 | 0 | double angle = mData[i+2]; |
889 | 0 | bool largeArcFlag = mData[i+3] != 0.0f; |
890 | 0 | bool sweepFlag = mData[i+4] != 0.0f; |
891 | 0 | if (segType == PATHSEG_ARC_ABS) { |
892 | 0 | segEnd = Point(mData[i+5], mData[i+6]); |
893 | 0 | } else { |
894 | 0 | segEnd = segStart + Point(mData[i+5], mData[i+6]); |
895 | 0 | } |
896 | 0 |
|
897 | 0 | // See section F.6 of SVG 1.1 for details on what we're doing here: |
898 | 0 | // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes |
899 | 0 |
|
900 | 0 | if (segStart == segEnd) { |
901 | 0 | // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical, |
902 | 0 | // then this is equivalent to omitting the elliptical arc segment |
903 | 0 | // entirely." We take that very literally here, not adding a mark, and |
904 | 0 | // not even setting any of the 'prev' variables so that it's as if this |
905 | 0 | // arc had never existed; note the difference this will make e.g. if |
906 | 0 | // the arc is proceeded by a bezier curve and followed by a "smooth" |
907 | 0 | // bezier curve of the same degree! |
908 | 0 | i += 7; |
909 | 0 | continue; |
910 | 0 | } |
911 | 0 | |
912 | 0 | // Below we have funny interleaving of F.6.6 (Correction of out-of-range |
913 | 0 | // radii) and F.6.5 (Conversion from endpoint to center parameterization) |
914 | 0 | // which is designed to avoid some unnecessary calculations. |
915 | 0 | |
916 | 0 | if (rx == 0.0 || ry == 0.0) { |
917 | 0 | // F.6.6 step 1 - straight line or coincidental points |
918 | 0 | segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); |
919 | 0 | i += 7; |
920 | 0 | break; |
921 | 0 | } |
922 | 0 | rx = fabs(rx); // F.6.6.1 |
923 | 0 | ry = fabs(ry); |
924 | 0 |
|
925 | 0 | // F.6.5.1: |
926 | 0 | angle = angle * M_PI/180.0; |
927 | 0 | double x1p = cos(angle) * (segStart.x - segEnd.x) / 2.0 |
928 | 0 | + sin(angle) * (segStart.y - segEnd.y) / 2.0; |
929 | 0 | double y1p = -sin(angle) * (segStart.x - segEnd.x) / 2.0 |
930 | 0 | + cos(angle) * (segStart.y - segEnd.y) / 2.0; |
931 | 0 |
|
932 | 0 | // This is the root in F.6.5.2 and the numerator under that root: |
933 | 0 | double root; |
934 | 0 | double numerator = rx*rx*ry*ry - rx*rx*y1p*y1p - ry*ry*x1p*x1p; |
935 | 0 |
|
936 | 0 | if (numerator >= 0.0) { |
937 | 0 | root = sqrt(numerator/(rx*rx*y1p*y1p + ry*ry*x1p*x1p)); |
938 | 0 | if (largeArcFlag == sweepFlag) |
939 | 0 | root = -root; |
940 | 0 | } else { |
941 | 0 | // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result |
942 | 0 | // of F.6.6.2 (lamedh) being greater than one. What we have here is |
943 | 0 | // ellipse radii that are too small for the ellipse to reach between |
944 | 0 | // segStart and segEnd. We scale the radii up uniformly so that the |
945 | 0 | // ellipse is just big enough to fit (i.e. to the point where there is |
946 | 0 | // exactly one solution). |
947 | 0 |
|
948 | 0 | double lamedh = 1.0 - numerator/(rx*rx*ry*ry); // equiv to eqn F.6.6.2 |
949 | 0 | double s = sqrt(lamedh); |
950 | 0 | rx *= s; // F.6.6.3 |
951 | 0 | ry *= s; |
952 | 0 | root = 0.0; |
953 | 0 | } |
954 | 0 |
|
955 | 0 | double cxp = root * rx * y1p / ry; // F.6.5.2 |
956 | 0 | double cyp = -root * ry * x1p / rx; |
957 | 0 |
|
958 | 0 | double theta, delta; |
959 | 0 | theta = AngleOfVector(Point((x1p-cxp)/rx, (y1p-cyp)/ry)); // F.6.5.5 |
960 | 0 | delta = AngleOfVector(Point((-x1p-cxp)/rx, (-y1p-cyp)/ry)) - // F.6.5.6 |
961 | 0 | theta; |
962 | 0 | if (!sweepFlag && delta > 0) |
963 | 0 | delta -= 2.0 * M_PI; |
964 | 0 | else if (sweepFlag && delta < 0) |
965 | 0 | delta += 2.0 * M_PI; |
966 | 0 |
|
967 | 0 | double tx1, ty1, tx2, ty2; |
968 | 0 | tx1 = -cos(angle)*rx*sin(theta) - sin(angle)*ry*cos(theta); |
969 | 0 | ty1 = -sin(angle)*rx*sin(theta) + cos(angle)*ry*cos(theta); |
970 | 0 | tx2 = -cos(angle)*rx*sin(theta+delta) - sin(angle)*ry*cos(theta+delta); |
971 | 0 | ty2 = -sin(angle)*rx*sin(theta+delta) + cos(angle)*ry*cos(theta+delta); |
972 | 0 |
|
973 | 0 | if (delta < 0.0f) { |
974 | 0 | tx1 = -tx1; |
975 | 0 | ty1 = -ty1; |
976 | 0 | tx2 = -tx2; |
977 | 0 | ty2 = -ty2; |
978 | 0 | } |
979 | 0 |
|
980 | 0 | segStartAngle = static_cast<float>(atan2(ty1, tx1)); |
981 | 0 | segEndAngle = static_cast<float>(atan2(ty2, tx2)); |
982 | 0 | i += 7; |
983 | 0 | break; |
984 | 0 | } |
985 | 0 |
|
986 | 0 | case PATHSEG_LINETO_HORIZONTAL_ABS: |
987 | 0 | case PATHSEG_LINETO_HORIZONTAL_REL: |
988 | 0 | if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) { |
989 | 0 | segEnd = Point(mData[i++], segStart.y); |
990 | 0 | } else { |
991 | 0 | segEnd = segStart + Point(mData[i++], 0.0f); |
992 | 0 | } |
993 | 0 | segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); |
994 | 0 | break; |
995 | 0 |
|
996 | 0 | case PATHSEG_LINETO_VERTICAL_ABS: |
997 | 0 | case PATHSEG_LINETO_VERTICAL_REL: |
998 | 0 | if (segType == PATHSEG_LINETO_VERTICAL_ABS) { |
999 | 0 | segEnd = Point(segStart.x, mData[i++]); |
1000 | 0 | } else { |
1001 | 0 | segEnd = segStart + Point(0.0f, mData[i++]); |
1002 | 0 | } |
1003 | 0 | segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); |
1004 | 0 | break; |
1005 | 0 |
|
1006 | 0 | case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: |
1007 | 0 | case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: |
1008 | 0 | { |
1009 | 0 | Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? |
1010 | 0 | segStart * 2 - prevCP : segStart; |
1011 | 0 | Point cp2; |
1012 | 0 | if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) { |
1013 | 0 | cp2 = Point(mData[i], mData[i+1]); |
1014 | 0 | segEnd = Point(mData[i+2], mData[i+3]); |
1015 | 0 | } else { |
1016 | 0 | cp2 = segStart + Point(mData[i], mData[i+1]); |
1017 | 0 | segEnd = segStart + Point(mData[i+2], mData[i+3]); |
1018 | 0 | } |
1019 | 0 | prevCP = cp2; |
1020 | 0 | segStartAngle = |
1021 | 0 | AngleOfVector(cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); |
1022 | 0 | segEndAngle = |
1023 | 0 | AngleOfVector(segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); |
1024 | 0 | i += 4; |
1025 | 0 | break; |
1026 | 0 | } |
1027 | 0 |
|
1028 | 0 | case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: |
1029 | 0 | case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: |
1030 | 0 | { |
1031 | 0 | Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? |
1032 | 0 | segStart * 2 - prevCP : segStart; |
1033 | 0 | if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) { |
1034 | 0 | segEnd = Point(mData[i], mData[i+1]); |
1035 | 0 | } else { |
1036 | 0 | segEnd = segStart + Point(mData[i], mData[i+1]); |
1037 | 0 | } |
1038 | 0 | prevCP = cp1; |
1039 | 0 | segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); |
1040 | 0 | segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); |
1041 | 0 | i += 2; |
1042 | 0 | break; |
1043 | 0 | } |
1044 | 0 |
|
1045 | 0 | default: |
1046 | 0 | // Leave any existing marks in aMarks so we have a visual indication of |
1047 | 0 | // when things went wrong. |
1048 | 0 | MOZ_ASSERT(false, "Unknown segment type - path corruption?"); |
1049 | 0 | return; |
1050 | 0 | } |
1051 | 0 |
|
1052 | 0 | // Set the angle of the mark at the start of this segment: |
1053 | 0 | if (aMarks->Length()) { |
1054 | 0 | nsSVGMark &mark = aMarks->LastElement(); |
1055 | 0 | if (!IsMoveto(segType) && IsMoveto(prevSegType)) { |
1056 | 0 | // start of new subpath |
1057 | 0 | pathStartAngle = mark.angle = segStartAngle; |
1058 | 0 | } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) { |
1059 | 0 | // end of a subpath |
1060 | 0 | if (prevSegType != PATHSEG_CLOSEPATH) |
1061 | 0 | mark.angle = prevSegEndAngle; |
1062 | 0 | } else { |
1063 | 0 | if (!(segType == PATHSEG_CLOSEPATH && |
1064 | 0 | prevSegType == PATHSEG_CLOSEPATH)) |
1065 | 0 | mark.angle = SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle); |
1066 | 0 | } |
1067 | 0 | } |
1068 | 0 |
|
1069 | 0 | // Add the mark at the end of this segment, and set its position: |
1070 | 0 | if (!aMarks->AppendElement(nsSVGMark(static_cast<float>(segEnd.x), |
1071 | 0 | static_cast<float>(segEnd.y), |
1072 | 0 | 0.0f, |
1073 | 0 | nsSVGMark::eMid))) { |
1074 | 0 | aMarks->Clear(); // OOM, so try to free some |
1075 | 0 | return; |
1076 | 0 | } |
1077 | 0 | |
1078 | 0 | if (segType == PATHSEG_CLOSEPATH && |
1079 | 0 | prevSegType != PATHSEG_CLOSEPATH) { |
1080 | 0 | aMarks->LastElement().angle = |
1081 | 0 | //aMarks->ElementAt(pathStartIndex).angle = |
1082 | 0 | SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle); |
1083 | 0 | } |
1084 | 0 |
|
1085 | 0 | prevSegType = segType; |
1086 | 0 | prevSegEnd = segEnd; |
1087 | 0 | prevSegEndAngle = segEndAngle; |
1088 | 0 | } |
1089 | 0 |
|
1090 | 0 | MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); |
1091 | 0 |
|
1092 | 0 | if (aMarks->Length()) { |
1093 | 0 | if (prevSegType != PATHSEG_CLOSEPATH) { |
1094 | 0 | aMarks->LastElement().angle = prevSegEndAngle; |
1095 | 0 | } |
1096 | 0 | aMarks->LastElement().type = nsSVGMark::eEnd; |
1097 | 0 | aMarks->ElementAt(0).type = nsSVGMark::eStart; |
1098 | 0 | } |
1099 | 0 | } |
1100 | | |
1101 | | size_t |
1102 | | SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
1103 | 0 | { |
1104 | 0 | return mData.ShallowSizeOfExcludingThis(aMallocSizeOf); |
1105 | 0 | } |
1106 | | |
1107 | | size_t |
1108 | | SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
1109 | 0 | { |
1110 | 0 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
1111 | 0 | } |
1112 | | |