1 | /* |
2 | * Copyright (C) Research In Motion Limited 2010, 2011. All rights reserved. |
3 | * Copyright (C) 2015 Apple Inc. All rights reserved. |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public License |
16 | * along with this library; see the file COPYING.LIB. If not, write to |
17 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
18 | * Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #include "config.h" |
22 | #include "SVGPathBlender.h" |
23 | |
24 | #include "AnimationUtilities.h" |
25 | #include "SVGPathSeg.h" |
26 | #include "SVGPathSource.h" |
27 | #include <wtf/SetForScope.h> |
28 | |
29 | namespace WebCore { |
30 | |
31 | bool SVGPathBlender::addAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, unsigned repeatCount) |
32 | { |
33 | SVGPathBlender blender(fromSource, toSource, &consumer); |
34 | return blender.addAnimatedPath(repeatCount); |
35 | } |
36 | |
37 | bool SVGPathBlender::blendAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, float progress) |
38 | { |
39 | SVGPathBlender blender(fromSource, toSource, &consumer); |
40 | return blender.blendAnimatedPath(progress); |
41 | } |
42 | |
43 | bool SVGPathBlender::canBlendPaths(SVGPathSource& fromSource, SVGPathSource& toSource) |
44 | { |
45 | SVGPathBlender blender(fromSource, toSource); |
46 | return blender.canBlendPaths(); |
47 | } |
48 | |
49 | SVGPathBlender::SVGPathBlender(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer* consumer) |
50 | : m_fromSource(fromSource) |
51 | , m_toSource(toSource) |
52 | , m_consumer(consumer) |
53 | { |
54 | } |
55 | |
56 | // Helper functions |
57 | static inline FloatPoint blendFloatPoint(const FloatPoint& a, const FloatPoint& b, float progress) |
58 | { |
59 | return FloatPoint(blend(a.x(), b.x(), progress), blend(a.y(), b.y(), progress)); |
60 | } |
61 | |
62 | float SVGPathBlender::blendAnimatedDimensonalFloat(float from, float to, FloatBlendMode blendMode, float progress) |
63 | { |
64 | if (m_addTypesCount) { |
65 | ASSERT(m_fromMode == m_toMode); |
66 | return from + to * m_addTypesCount; |
67 | } |
68 | |
69 | if (m_fromMode == m_toMode) |
70 | return blend(from, to, progress); |
71 | |
72 | float fromValue = blendMode == BlendHorizontal ? m_fromCurrentPoint.x() : m_fromCurrentPoint.y(); |
73 | float toValue = blendMode == BlendHorizontal ? m_toCurrentPoint.x() : m_toCurrentPoint.y(); |
74 | |
75 | // Transform toY to the coordinate mode of fromY |
76 | float animValue = blend(from, m_fromMode == AbsoluteCoordinates ? to + toValue : to - toValue, progress); |
77 | |
78 | if (m_isInFirstHalfOfAnimation) |
79 | return animValue; |
80 | |
81 | // Transform the animated point to the coordinate mode, needed for the current progress. |
82 | float currentValue = blend(fromValue, toValue, progress); |
83 | return m_toMode == AbsoluteCoordinates ? animValue + currentValue : animValue - currentValue; |
84 | } |
85 | |
86 | FloatPoint SVGPathBlender::blendAnimatedFloatPoint(const FloatPoint& fromPoint, const FloatPoint& toPoint, float progress) |
87 | { |
88 | if (m_addTypesCount) { |
89 | ASSERT(m_fromMode == m_toMode); |
90 | FloatPoint repeatedToPoint = toPoint; |
91 | repeatedToPoint.scale(m_addTypesCount); |
92 | return fromPoint + repeatedToPoint; |
93 | } |
94 | |
95 | if (m_fromMode == m_toMode) |
96 | return blendFloatPoint(fromPoint, toPoint, progress); |
97 | |
98 | // Transform toPoint to the coordinate mode of fromPoint |
99 | FloatPoint animatedPoint = toPoint; |
100 | if (m_fromMode == AbsoluteCoordinates) |
101 | animatedPoint += m_toCurrentPoint; |
102 | else |
103 | animatedPoint.move(-m_toCurrentPoint.x(), -m_toCurrentPoint.y()); |
104 | |
105 | animatedPoint = blendFloatPoint(fromPoint, animatedPoint, progress); |
106 | |
107 | if (m_isInFirstHalfOfAnimation) |
108 | return animatedPoint; |
109 | |
110 | // Transform the animated point to the coordinate mode, needed for the current progress. |
111 | FloatPoint currentPoint = blendFloatPoint(m_fromCurrentPoint, m_toCurrentPoint, progress); |
112 | if (m_toMode == AbsoluteCoordinates) |
113 | return animatedPoint + currentPoint; |
114 | |
115 | animatedPoint.move(-currentPoint.x(), -currentPoint.y()); |
116 | return animatedPoint; |
117 | } |
118 | |
119 | bool SVGPathBlender::blendMoveToSegment(float progress) |
120 | { |
121 | FloatPoint fromTargetPoint; |
122 | FloatPoint toTargetPoint; |
123 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseMoveToSegment(fromTargetPoint)) |
124 | || !m_toSource.parseMoveToSegment(toTargetPoint)) |
125 | return false; |
126 | |
127 | if (!m_consumer) |
128 | return true; |
129 | |
130 | m_consumer->moveTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), false, m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
131 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
132 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
133 | return true; |
134 | } |
135 | |
136 | bool SVGPathBlender::blendLineToSegment(float progress) |
137 | { |
138 | FloatPoint fromTargetPoint; |
139 | FloatPoint toTargetPoint; |
140 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseLineToSegment(fromTargetPoint)) |
141 | || !m_toSource.parseLineToSegment(toTargetPoint)) |
142 | return false; |
143 | |
144 | if (!m_consumer) |
145 | return true; |
146 | |
147 | m_consumer->lineTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
148 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
149 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
150 | return true; |
151 | } |
152 | |
153 | bool SVGPathBlender::blendLineToHorizontalSegment(float progress) |
154 | { |
155 | float fromX = 0; |
156 | float toX = 0; |
157 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseLineToHorizontalSegment(fromX)) |
158 | || !m_toSource.parseLineToHorizontalSegment(toX)) |
159 | return false; |
160 | |
161 | if (!m_consumer) |
162 | return true; |
163 | |
164 | m_consumer->lineToHorizontal(blendAnimatedDimensonalFloat(fromX, toX, BlendHorizontal, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
165 | m_fromCurrentPoint.setX(m_fromMode == AbsoluteCoordinates ? fromX : m_fromCurrentPoint.x() + fromX); |
166 | m_toCurrentPoint.setX(m_toMode == AbsoluteCoordinates ? toX : m_toCurrentPoint.x() + toX); |
167 | return true; |
168 | } |
169 | |
170 | bool SVGPathBlender::blendLineToVerticalSegment(float progress) |
171 | { |
172 | float fromY = 0; |
173 | float toY = 0; |
174 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseLineToVerticalSegment(fromY)) |
175 | || !m_toSource.parseLineToVerticalSegment(toY)) |
176 | return false; |
177 | |
178 | if (!m_consumer) |
179 | return true; |
180 | |
181 | m_consumer->lineToVertical(blendAnimatedDimensonalFloat(fromY, toY, BlendVertical, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
182 | m_fromCurrentPoint.setY(m_fromMode == AbsoluteCoordinates ? fromY : m_fromCurrentPoint.y() + fromY); |
183 | m_toCurrentPoint.setY(m_toMode == AbsoluteCoordinates ? toY : m_toCurrentPoint.y() + toY); |
184 | return true; |
185 | } |
186 | |
187 | bool SVGPathBlender::blendCurveToCubicSegment(float progress) |
188 | { |
189 | FloatPoint fromTargetPoint; |
190 | FloatPoint fromPoint1; |
191 | FloatPoint fromPoint2; |
192 | FloatPoint toTargetPoint; |
193 | FloatPoint toPoint1; |
194 | FloatPoint toPoint2; |
195 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToCubicSegment(fromPoint1, fromPoint2, fromTargetPoint)) |
196 | || !m_toSource.parseCurveToCubicSegment(toPoint1, toPoint2, toTargetPoint)) |
197 | return false; |
198 | |
199 | if (!m_consumer) |
200 | return true; |
201 | |
202 | m_consumer->curveToCubic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress), |
203 | blendAnimatedFloatPoint(fromPoint2, toPoint2, progress), |
204 | blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), |
205 | m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
206 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
207 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
208 | return true; |
209 | } |
210 | |
211 | bool SVGPathBlender::blendCurveToCubicSmoothSegment(float progress) |
212 | { |
213 | FloatPoint fromTargetPoint; |
214 | FloatPoint fromPoint2; |
215 | FloatPoint toTargetPoint; |
216 | FloatPoint toPoint2; |
217 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToCubicSmoothSegment(fromPoint2, fromTargetPoint)) |
218 | || !m_toSource.parseCurveToCubicSmoothSegment(toPoint2, toTargetPoint)) |
219 | return false; |
220 | |
221 | if (!m_consumer) |
222 | return true; |
223 | |
224 | m_consumer->curveToCubicSmooth(blendAnimatedFloatPoint(fromPoint2, toPoint2, progress), |
225 | blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), |
226 | m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
227 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
228 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
229 | return true; |
230 | } |
231 | |
232 | bool SVGPathBlender::blendCurveToQuadraticSegment(float progress) |
233 | { |
234 | FloatPoint fromTargetPoint; |
235 | FloatPoint fromPoint1; |
236 | FloatPoint toTargetPoint; |
237 | FloatPoint toPoint1; |
238 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToQuadraticSegment(fromPoint1, fromTargetPoint)) |
239 | || !m_toSource.parseCurveToQuadraticSegment(toPoint1, toTargetPoint)) |
240 | return false; |
241 | |
242 | if (!m_consumer) |
243 | return true; |
244 | |
245 | m_consumer->curveToQuadratic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress), |
246 | blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), |
247 | m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
248 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
249 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
250 | return true; |
251 | } |
252 | |
253 | bool SVGPathBlender::blendCurveToQuadraticSmoothSegment(float progress) |
254 | { |
255 | FloatPoint fromTargetPoint; |
256 | FloatPoint toTargetPoint; |
257 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToQuadraticSmoothSegment(fromTargetPoint)) |
258 | || !m_toSource.parseCurveToQuadraticSmoothSegment(toTargetPoint)) |
259 | return false; |
260 | |
261 | if (!m_consumer) |
262 | return true; |
263 | |
264 | m_consumer->curveToQuadraticSmooth(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
265 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
266 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
267 | return true; |
268 | } |
269 | |
270 | bool SVGPathBlender::blendArcToSegment(float progress) |
271 | { |
272 | float fromRx = 0; |
273 | float fromRy = 0; |
274 | float fromAngle = 0; |
275 | bool fromLargeArc = false; |
276 | bool fromSweep = false; |
277 | FloatPoint fromTargetPoint; |
278 | float toRx = 0; |
279 | float toRy = 0; |
280 | float toAngle = 0; |
281 | bool toLargeArc = false; |
282 | bool toSweep = false; |
283 | FloatPoint toTargetPoint; |
284 | if ((m_fromSource.hasMoreData() && !m_fromSource.parseArcToSegment(fromRx, fromRy, fromAngle, fromLargeArc, fromSweep, fromTargetPoint)) |
285 | || !m_toSource.parseArcToSegment(toRx, toRy, toAngle, toLargeArc, toSweep, toTargetPoint)) |
286 | return false; |
287 | |
288 | if (!m_consumer) |
289 | return true; |
290 | |
291 | if (m_addTypesCount) { |
292 | ASSERT(m_fromMode == m_toMode); |
293 | FloatPoint scaledToTargetPoint = toTargetPoint; |
294 | scaledToTargetPoint.scale(m_addTypesCount); |
295 | m_consumer->arcTo(fromRx + toRx * m_addTypesCount, |
296 | fromRy + toRy * m_addTypesCount, |
297 | fromAngle + toAngle * m_addTypesCount, |
298 | fromLargeArc || toLargeArc, |
299 | fromSweep || toSweep, |
300 | fromTargetPoint + scaledToTargetPoint, |
301 | m_fromMode); |
302 | } else { |
303 | m_consumer->arcTo(blend(fromRx, toRx, progress), |
304 | blend(fromRy, toRy, progress), |
305 | blend(fromAngle, toAngle, progress), |
306 | m_isInFirstHalfOfAnimation ? fromLargeArc : toLargeArc, |
307 | m_isInFirstHalfOfAnimation ? fromSweep : toSweep, |
308 | blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), |
309 | m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); |
310 | } |
311 | m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; |
312 | m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; |
313 | return true; |
314 | } |
315 | |
316 | static inline PathCoordinateMode coordinateModeOfCommand(const SVGPathSegType& type) |
317 | { |
318 | if (type < PathSegMoveToAbs) |
319 | return AbsoluteCoordinates; |
320 | |
321 | // Odd number = relative command |
322 | if (type % 2) |
323 | return RelativeCoordinates; |
324 | |
325 | return AbsoluteCoordinates; |
326 | } |
327 | |
328 | static inline bool isSegmentEqual(const SVGPathSegType& fromType, const SVGPathSegType& toType, const PathCoordinateMode& fromMode, const PathCoordinateMode& toMode) |
329 | { |
330 | if (fromType == toType && (fromType == PathSegUnknown || fromType == PathSegClosePath)) |
331 | return true; |
332 | |
333 | unsigned short from = fromType; |
334 | unsigned short to = toType; |
335 | if (fromMode == toMode) |
336 | return from == to; |
337 | if (fromMode == AbsoluteCoordinates) |
338 | return from == to - 1; |
339 | return to == from - 1; |
340 | } |
341 | |
342 | bool SVGPathBlender::addAnimatedPath(unsigned repeatCount) |
343 | { |
344 | SetForScope<unsigned> change(m_addTypesCount, repeatCount); |
345 | return blendAnimatedPath(0); |
346 | } |
347 | |
348 | bool SVGPathBlender::canBlendPaths() |
349 | { |
350 | float progress = 0.5; |
351 | bool fromSourceHadData = m_fromSource.hasMoreData(); |
352 | while (m_toSource.hasMoreData()) { |
353 | SVGPathSegType fromCommand; |
354 | SVGPathSegType toCommand; |
355 | if ((fromSourceHadData && !m_fromSource.parseSVGSegmentType(fromCommand)) || !m_toSource.parseSVGSegmentType(toCommand)) |
356 | return false; |
357 | |
358 | m_toMode = coordinateModeOfCommand(toCommand); |
359 | m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode; |
360 | if (m_fromMode != m_toMode && m_addTypesCount) |
361 | return false; |
362 | |
363 | if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode)) |
364 | return false; |
365 | |
366 | switch (toCommand) { |
367 | case PathSegMoveToRel: |
368 | case PathSegMoveToAbs: |
369 | if (!blendMoveToSegment(progress)) |
370 | return false; |
371 | break; |
372 | case PathSegLineToRel: |
373 | case PathSegLineToAbs: |
374 | if (!blendLineToSegment(progress)) |
375 | return false; |
376 | break; |
377 | case PathSegLineToHorizontalRel: |
378 | case PathSegLineToHorizontalAbs: |
379 | if (!blendLineToHorizontalSegment(progress)) |
380 | return false; |
381 | break; |
382 | case PathSegLineToVerticalRel: |
383 | case PathSegLineToVerticalAbs: |
384 | if (!blendLineToVerticalSegment(progress)) |
385 | return false; |
386 | break; |
387 | case PathSegClosePath: |
388 | break; |
389 | case PathSegCurveToCubicRel: |
390 | case PathSegCurveToCubicAbs: |
391 | if (!blendCurveToCubicSegment(progress)) |
392 | return false; |
393 | break; |
394 | case PathSegCurveToCubicSmoothRel: |
395 | case PathSegCurveToCubicSmoothAbs: |
396 | if (!blendCurveToCubicSmoothSegment(progress)) |
397 | return false; |
398 | break; |
399 | case PathSegCurveToQuadraticRel: |
400 | case PathSegCurveToQuadraticAbs: |
401 | if (!blendCurveToQuadraticSegment(progress)) |
402 | return false; |
403 | break; |
404 | case PathSegCurveToQuadraticSmoothRel: |
405 | case PathSegCurveToQuadraticSmoothAbs: |
406 | if (!blendCurveToQuadraticSmoothSegment(progress)) |
407 | return false; |
408 | break; |
409 | case PathSegArcRel: |
410 | case PathSegArcAbs: |
411 | if (!blendArcToSegment(progress)) |
412 | return false; |
413 | break; |
414 | case PathSegUnknown: |
415 | return false; |
416 | } |
417 | |
418 | if (!fromSourceHadData) |
419 | continue; |
420 | if (m_fromSource.hasMoreData() != m_toSource.hasMoreData()) |
421 | return false; |
422 | if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData()) |
423 | return true; |
424 | } |
425 | |
426 | return true; |
427 | } |
428 | |
429 | bool SVGPathBlender::blendAnimatedPath(float progress) |
430 | { |
431 | m_isInFirstHalfOfAnimation = progress < 0.5f; |
432 | |
433 | bool fromSourceHadData = m_fromSource.hasMoreData(); |
434 | while (m_toSource.hasMoreData()) { |
435 | SVGPathSegType fromCommand; |
436 | SVGPathSegType toCommand; |
437 | if ((fromSourceHadData && !m_fromSource.parseSVGSegmentType(fromCommand)) || !m_toSource.parseSVGSegmentType(toCommand)) |
438 | return false; |
439 | |
440 | m_toMode = coordinateModeOfCommand(toCommand); |
441 | m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode; |
442 | if (m_fromMode != m_toMode && m_addTypesCount) |
443 | return false; |
444 | |
445 | if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode)) |
446 | return false; |
447 | |
448 | switch (toCommand) { |
449 | case PathSegMoveToRel: |
450 | case PathSegMoveToAbs: |
451 | if (!blendMoveToSegment(progress)) |
452 | return false; |
453 | break; |
454 | case PathSegLineToRel: |
455 | case PathSegLineToAbs: |
456 | if (!blendLineToSegment(progress)) |
457 | return false; |
458 | break; |
459 | case PathSegLineToHorizontalRel: |
460 | case PathSegLineToHorizontalAbs: |
461 | if (!blendLineToHorizontalSegment(progress)) |
462 | return false; |
463 | break; |
464 | case PathSegLineToVerticalRel: |
465 | case PathSegLineToVerticalAbs: |
466 | if (!blendLineToVerticalSegment(progress)) |
467 | return false; |
468 | break; |
469 | case PathSegClosePath: |
470 | m_consumer->closePath(); |
471 | break; |
472 | case PathSegCurveToCubicRel: |
473 | case PathSegCurveToCubicAbs: |
474 | if (!blendCurveToCubicSegment(progress)) |
475 | return false; |
476 | break; |
477 | case PathSegCurveToCubicSmoothRel: |
478 | case PathSegCurveToCubicSmoothAbs: |
479 | if (!blendCurveToCubicSmoothSegment(progress)) |
480 | return false; |
481 | break; |
482 | case PathSegCurveToQuadraticRel: |
483 | case PathSegCurveToQuadraticAbs: |
484 | if (!blendCurveToQuadraticSegment(progress)) |
485 | return false; |
486 | break; |
487 | case PathSegCurveToQuadraticSmoothRel: |
488 | case PathSegCurveToQuadraticSmoothAbs: |
489 | if (!blendCurveToQuadraticSmoothSegment(progress)) |
490 | return false; |
491 | break; |
492 | case PathSegArcRel: |
493 | case PathSegArcAbs: |
494 | if (!blendArcToSegment(progress)) |
495 | return false; |
496 | break; |
497 | case PathSegUnknown: |
498 | return false; |
499 | } |
500 | |
501 | if (!fromSourceHadData) |
502 | continue; |
503 | if (m_fromSource.hasMoreData() != m_toSource.hasMoreData()) |
504 | return false; |
505 | if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData()) |
506 | return true; |
507 | } |
508 | |
509 | return true; |
510 | } |
511 | |
512 | } |
513 | |