Line data Source code
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 "mozilla/DebugOnly.h"
8 :
9 : #include "SVGPathSegListSMILType.h"
10 : #include "nsSMILValue.h"
11 : #include "SVGPathSegUtils.h"
12 : #include "SVGPathData.h"
13 :
14 : // Indices of boolean flags within 'arc' segment chunks in path-data arrays
15 : // (where '0' would correspond to the index of the encoded segment type):
16 : #define LARGE_ARC_FLAG_IDX 4
17 : #define SWEEP_FLAG_IDX 5
18 :
19 : namespace mozilla {
20 :
21 : //----------------------------------------------------------------------
22 : // nsISMILType implementation
23 :
24 : void
25 0 : SVGPathSegListSMILType::Init(nsSMILValue &aValue) const
26 : {
27 0 : MOZ_ASSERT(aValue.IsNull(), "Unexpected value type");
28 0 : aValue.mU.mPtr = new SVGPathDataAndInfo();
29 0 : aValue.mType = this;
30 0 : }
31 :
32 : void
33 0 : SVGPathSegListSMILType::Destroy(nsSMILValue& aValue) const
34 : {
35 0 : NS_PRECONDITION(aValue.mType == this, "Unexpected SMIL value type");
36 0 : delete static_cast<SVGPathDataAndInfo*>(aValue.mU.mPtr);
37 0 : aValue.mU.mPtr = nullptr;
38 0 : aValue.mType = nsSMILNullType::Singleton();
39 0 : }
40 :
41 : nsresult
42 0 : SVGPathSegListSMILType::Assign(nsSMILValue& aDest,
43 : const nsSMILValue& aSrc) const
44 : {
45 0 : NS_PRECONDITION(aDest.mType == aSrc.mType, "Incompatible SMIL types");
46 0 : NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL value");
47 :
48 : const SVGPathDataAndInfo* src =
49 0 : static_cast<const SVGPathDataAndInfo*>(aSrc.mU.mPtr);
50 : SVGPathDataAndInfo* dest =
51 0 : static_cast<SVGPathDataAndInfo*>(aDest.mU.mPtr);
52 :
53 0 : return dest->CopyFrom(*src);
54 : }
55 :
56 : bool
57 0 : SVGPathSegListSMILType::IsEqual(const nsSMILValue& aLeft,
58 : const nsSMILValue& aRight) const
59 : {
60 0 : NS_PRECONDITION(aLeft.mType == aRight.mType, "Incompatible SMIL types");
61 0 : NS_PRECONDITION(aLeft.mType == this, "Unexpected type for SMIL value");
62 :
63 0 : return *static_cast<const SVGPathDataAndInfo*>(aLeft.mU.mPtr) ==
64 0 : *static_cast<const SVGPathDataAndInfo*>(aRight.mU.mPtr);
65 : }
66 :
67 : static bool
68 0 : ArcFlagsDiffer(SVGPathDataAndInfo::const_iterator aPathData1,
69 : SVGPathDataAndInfo::const_iterator aPathData2)
70 : {
71 0 : MOZ_ASSERT
72 : (SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])),
73 : "ArcFlagsDiffer called with non-arc segment");
74 0 : MOZ_ASSERT
75 : (SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2[0])),
76 : "ArcFlagsDiffer called with non-arc segment");
77 :
78 0 : return aPathData1[LARGE_ARC_FLAG_IDX] != aPathData2[LARGE_ARC_FLAG_IDX] ||
79 0 : aPathData1[SWEEP_FLAG_IDX] != aPathData2[SWEEP_FLAG_IDX];
80 : }
81 :
82 : enum PathInterpolationResult {
83 : eCannotInterpolate,
84 : eRequiresConversion,
85 : eCanInterpolate
86 : };
87 :
88 : static PathInterpolationResult
89 0 : CanInterpolate(const SVGPathDataAndInfo& aStart,
90 : const SVGPathDataAndInfo& aEnd)
91 : {
92 0 : if (aStart.IsIdentity()) {
93 0 : return eCanInterpolate;
94 : }
95 :
96 0 : if (aStart.Length() != aEnd.Length()) {
97 0 : return eCannotInterpolate;
98 : }
99 :
100 0 : PathInterpolationResult result = eCanInterpolate;
101 :
102 0 : SVGPathDataAndInfo::const_iterator pStart = aStart.begin();
103 0 : SVGPathDataAndInfo::const_iterator pEnd = aEnd.begin();
104 0 : SVGPathDataAndInfo::const_iterator pStartDataEnd = aStart.end();
105 0 : SVGPathDataAndInfo::const_iterator pEndDataEnd = aEnd.end();
106 :
107 0 : while (pStart < pStartDataEnd && pEnd < pEndDataEnd) {
108 0 : uint32_t startType = SVGPathSegUtils::DecodeType(*pStart);
109 0 : uint32_t endType = SVGPathSegUtils::DecodeType(*pEnd);
110 :
111 0 : if (SVGPathSegUtils::IsArcType(startType) &&
112 0 : SVGPathSegUtils::IsArcType(endType) &&
113 0 : ArcFlagsDiffer(pStart, pEnd)) {
114 0 : return eCannotInterpolate;
115 : }
116 :
117 0 : if (startType != endType) {
118 0 : if (!SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType)) {
119 0 : return eCannotInterpolate;
120 : }
121 :
122 0 : result = eRequiresConversion;
123 : }
124 :
125 0 : pStart += 1 + SVGPathSegUtils::ArgCountForType(startType);
126 0 : pEnd += 1 + SVGPathSegUtils::ArgCountForType(endType);
127 : }
128 :
129 0 : MOZ_ASSERT(pStart <= pStartDataEnd && pEnd <= pEndDataEnd,
130 : "Iterated past end of buffer! (Corrupt path data?)");
131 :
132 0 : if (pStart != pStartDataEnd || pEnd != pEndDataEnd) {
133 0 : return eCannotInterpolate;
134 : }
135 :
136 0 : return result;
137 : }
138 :
139 : enum RelativenessAdjustmentType {
140 : eAbsoluteToRelative,
141 : eRelativeToAbsolute
142 : };
143 :
144 : static inline void
145 0 : AdjustSegmentForRelativeness(RelativenessAdjustmentType aAdjustmentType,
146 : const SVGPathDataAndInfo::iterator& aSegmentToAdjust,
147 : const SVGPathTraversalState& aState)
148 : {
149 0 : if (aAdjustmentType == eAbsoluteToRelative) {
150 0 : aSegmentToAdjust[0] -= aState.pos.x;
151 0 : aSegmentToAdjust[1] -= aState.pos.y;
152 : } else {
153 0 : aSegmentToAdjust[0] += aState.pos.x;
154 0 : aSegmentToAdjust[1] += aState.pos.y;
155 : }
156 0 : }
157 :
158 : /**
159 : * Helper function for AddWeightedPathSegLists, to add multiples of two
160 : * path-segments of the same type.
161 : *
162 : * NOTE: |aSeg1| is allowed to be nullptr, so we use |aSeg2| as the
163 : * authoritative source of things like segment-type and boolean arc flags.
164 : *
165 : * @param aCoeff1 The coefficient to use on the first segment.
166 : * @param aSeg1 An iterator pointing to the first segment. This can be
167 : * null, which is treated as identity (zero).
168 : * @param aCoeff2 The coefficient to use on the second segment.
169 : * @param aSeg2 An iterator pointing to the second segment.
170 : * @param [out] aResultSeg An iterator pointing to where we should write the
171 : * result of this operation.
172 : */
173 : static inline void
174 0 : AddWeightedPathSegs(double aCoeff1,
175 : SVGPathDataAndInfo::const_iterator& aSeg1,
176 : double aCoeff2,
177 : SVGPathDataAndInfo::const_iterator& aSeg2,
178 : SVGPathDataAndInfo::iterator& aResultSeg)
179 : {
180 0 : MOZ_ASSERT(aSeg2, "2nd segment must be non-null");
181 0 : MOZ_ASSERT(aResultSeg, "result segment must be non-null");
182 :
183 0 : uint32_t segType = SVGPathSegUtils::DecodeType(aSeg2[0]);
184 0 : MOZ_ASSERT(!aSeg1 || SVGPathSegUtils::DecodeType(*aSeg1) == segType,
185 : "unexpected segment type");
186 :
187 : // FIRST: Directly copy the arguments that don't make sense to add.
188 0 : aResultSeg[0] = aSeg2[0]; // encoded segment type
189 :
190 0 : bool isArcType = SVGPathSegUtils::IsArcType(segType);
191 0 : if (isArcType) {
192 : // Copy boolean arc flags.
193 0 : MOZ_ASSERT(!aSeg1 || !ArcFlagsDiffer(aSeg1, aSeg2),
194 : "Expecting arc flags to match");
195 0 : aResultSeg[LARGE_ARC_FLAG_IDX] = aSeg2[LARGE_ARC_FLAG_IDX];
196 0 : aResultSeg[SWEEP_FLAG_IDX] = aSeg2[SWEEP_FLAG_IDX];
197 : }
198 :
199 : // SECOND: Add the arguments that are supposed to be added.
200 : // (The 1's below are to account for segment type)
201 0 : uint32_t numArgs = SVGPathSegUtils::ArgCountForType(segType);
202 0 : for (uint32_t i = 1; i < 1 + numArgs; ++i) {
203 : // Need to skip arc flags for arc-type segments. (already handled them)
204 0 : if (!(isArcType && (i == LARGE_ARC_FLAG_IDX || i == SWEEP_FLAG_IDX))) {
205 0 : aResultSeg[i] = (aSeg1 ? aCoeff1 * aSeg1[i] : 0.0) + aCoeff2 * aSeg2[i];
206 : }
207 : }
208 :
209 : // FINALLY: Shift iterators forward. ("1+" is to include seg-type)
210 0 : if (aSeg1) {
211 0 : aSeg1 += 1 + numArgs;
212 : }
213 0 : aSeg2 += 1 + numArgs;
214 0 : aResultSeg += 1 + numArgs;
215 0 : }
216 :
217 : /**
218 : * Helper function for Add & Interpolate, to add multiples of two path-segment
219 : * lists.
220 : *
221 : * NOTE: aList1 and aList2 are assumed to have their segment-types and
222 : * segment-count match exactly (unless aList1 is an identity value).
223 : *
224 : * NOTE: aResult, the output list, is expected to either be an identity value
225 : * (in which case we'll grow it) *or* to already have the exactly right length
226 : * (e.g. in cases where aList1 and aResult are actually the same list).
227 : *
228 : * @param aCoeff1 The coefficient to use on the first path segment list.
229 : * @param aList1 The first path segment list. Allowed to be identity.
230 : * @param aCoeff2 The coefficient to use on the second path segment list.
231 : * @param aList2 The second path segment list.
232 : * @param [out] aResultSeg The resulting path segment list. Allowed to be
233 : * identity, in which case we'll grow it to the right
234 : * size. Also allowed to be the same list as aList1.
235 : */
236 : static nsresult
237 0 : AddWeightedPathSegLists(double aCoeff1, const SVGPathDataAndInfo& aList1,
238 : double aCoeff2, const SVGPathDataAndInfo& aList2,
239 : SVGPathDataAndInfo& aResult)
240 : {
241 0 : MOZ_ASSERT(aCoeff1 >= 0.0 && aCoeff2 >= 0.0,
242 : "expecting non-negative coefficients");
243 0 : MOZ_ASSERT(!aList2.IsIdentity(),
244 : "expecting 2nd list to be non-identity");
245 0 : MOZ_ASSERT(aList1.IsIdentity() || aList1.Length() == aList2.Length(),
246 : "expecting 1st list to be identity or to have same "
247 : "length as 2nd list");
248 0 : MOZ_ASSERT(aResult.IsIdentity() || aResult.Length() == aList2.Length(),
249 : "expecting result list to be identity or to have same "
250 : "length as 2nd list");
251 :
252 : SVGPathDataAndInfo::const_iterator iter1, end1;
253 0 : if (aList1.IsIdentity()) {
254 0 : iter1 = end1 = nullptr; // indicate that this is an identity list
255 : } else {
256 0 : iter1 = aList1.begin();
257 0 : end1 = aList1.end();
258 : }
259 0 : SVGPathDataAndInfo::const_iterator iter2 = aList2.begin();
260 0 : SVGPathDataAndInfo::const_iterator end2 = aList2.end();
261 :
262 : // Grow |aResult| if necessary. (NOTE: It's possible that aResult and aList1
263 : // are the same list, so this may implicitly resize aList1. That's fine,
264 : // because in that case, we will have already set iter1 to nullptr above, to
265 : // record that our first operand is an identity value.)
266 0 : if (aResult.IsIdentity()) {
267 0 : if (!aResult.SetLength(aList2.Length())) {
268 0 : return NS_ERROR_OUT_OF_MEMORY;
269 : }
270 0 : aResult.SetElement(aList2.Element()); // propagate target element info!
271 : }
272 :
273 0 : SVGPathDataAndInfo::iterator resultIter = aResult.begin();
274 :
275 0 : while ((!iter1 || iter1 != end1) &&
276 0 : iter2 != end2) {
277 : AddWeightedPathSegs(aCoeff1, iter1,
278 : aCoeff2, iter2,
279 0 : resultIter);
280 : }
281 0 : MOZ_ASSERT((!iter1 || iter1 == end1) &&
282 : iter2 == end2 &&
283 : resultIter == aResult.end(),
284 : "Very, very bad - path data corrupt");
285 0 : return NS_OK;
286 : }
287 :
288 : static void
289 0 : ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart,
290 : SVGPathDataAndInfo::const_iterator& aEnd,
291 : SVGPathDataAndInfo::iterator& aResult,
292 : SVGPathTraversalState& aState)
293 : {
294 0 : uint32_t startType = SVGPathSegUtils::DecodeType(*aStart);
295 0 : uint32_t endType = SVGPathSegUtils::DecodeType(*aEnd);
296 :
297 : uint32_t segmentLengthIncludingType =
298 0 : 1 + SVGPathSegUtils::ArgCountForType(startType);
299 :
300 0 : SVGPathDataAndInfo::const_iterator pResultSegmentBegin = aResult;
301 :
302 0 : if (startType == endType) {
303 : // No conversion need, just directly copy aStart.
304 0 : aEnd += segmentLengthIncludingType;
305 0 : while (segmentLengthIncludingType) {
306 0 : *aResult++ = *aStart++;
307 0 : --segmentLengthIncludingType;
308 : }
309 0 : SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState);
310 0 : return;
311 : }
312 :
313 0 : MOZ_ASSERT
314 : (SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType),
315 : "Incompatible path segment types passed to ConvertPathSegmentData!");
316 :
317 : RelativenessAdjustmentType adjustmentType =
318 0 : SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute
319 0 : : eAbsoluteToRelative;
320 :
321 0 : MOZ_ASSERT
322 : (segmentLengthIncludingType ==
323 : 1 + SVGPathSegUtils::ArgCountForType(endType),
324 : "Compatible path segment types for interpolation had different lengths!");
325 :
326 0 : aResult[0] = aEnd[0];
327 :
328 0 : switch (endType) {
329 : case PATHSEG_LINETO_HORIZONTAL_ABS:
330 : case PATHSEG_LINETO_HORIZONTAL_REL:
331 0 : aResult[1] = aStart[1] +
332 0 : (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x;
333 0 : break;
334 : case PATHSEG_LINETO_VERTICAL_ABS:
335 : case PATHSEG_LINETO_VERTICAL_REL:
336 0 : aResult[1] = aStart[1] +
337 0 : (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y;
338 0 : break;
339 : case PATHSEG_ARC_ABS:
340 : case PATHSEG_ARC_REL:
341 0 : aResult[1] = aStart[1];
342 0 : aResult[2] = aStart[2];
343 0 : aResult[3] = aStart[3];
344 0 : aResult[4] = aStart[4];
345 0 : aResult[5] = aStart[5];
346 0 : aResult[6] = aStart[6];
347 0 : aResult[7] = aStart[7];
348 0 : AdjustSegmentForRelativeness(adjustmentType, aResult + 6, aState);
349 0 : break;
350 : case PATHSEG_CURVETO_CUBIC_ABS:
351 : case PATHSEG_CURVETO_CUBIC_REL:
352 0 : aResult[5] = aStart[5];
353 0 : aResult[6] = aStart[6];
354 0 : AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState);
355 : MOZ_FALLTHROUGH;
356 : case PATHSEG_CURVETO_QUADRATIC_ABS:
357 : case PATHSEG_CURVETO_QUADRATIC_REL:
358 : case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
359 : case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
360 0 : aResult[3] = aStart[3];
361 0 : aResult[4] = aStart[4];
362 0 : AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState);
363 : MOZ_FALLTHROUGH;
364 : case PATHSEG_MOVETO_ABS:
365 : case PATHSEG_MOVETO_REL:
366 : case PATHSEG_LINETO_ABS:
367 : case PATHSEG_LINETO_REL:
368 : case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
369 : case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
370 0 : aResult[1] = aStart[1];
371 0 : aResult[2] = aStart[2];
372 0 : AdjustSegmentForRelativeness(adjustmentType, aResult + 1, aState);
373 0 : break;
374 : }
375 :
376 0 : SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState);
377 0 : aStart += segmentLengthIncludingType;
378 0 : aEnd += segmentLengthIncludingType;
379 0 : aResult += segmentLengthIncludingType;
380 : }
381 :
382 : static void
383 0 : ConvertAllPathSegmentData(SVGPathDataAndInfo::const_iterator aStart,
384 : SVGPathDataAndInfo::const_iterator aStartDataEnd,
385 : SVGPathDataAndInfo::const_iterator aEnd,
386 : SVGPathDataAndInfo::const_iterator aEndDataEnd,
387 : SVGPathDataAndInfo::iterator aResult)
388 : {
389 0 : SVGPathTraversalState state;
390 0 : state.mode = SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos;
391 0 : while (aStart < aStartDataEnd && aEnd < aEndDataEnd) {
392 0 : ConvertPathSegmentData(aStart, aEnd, aResult, state);
393 : }
394 0 : MOZ_ASSERT(aStart == aStartDataEnd && aEnd == aEndDataEnd,
395 : "Failed to convert all path segment data! (Corrupt?)");
396 0 : }
397 :
398 : nsresult
399 0 : SVGPathSegListSMILType::Add(nsSMILValue& aDest,
400 : const nsSMILValue& aValueToAdd,
401 : uint32_t aCount) const
402 : {
403 0 : NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL type");
404 0 : NS_PRECONDITION(aValueToAdd.mType == this, "Incompatible SMIL type");
405 :
406 : SVGPathDataAndInfo& dest =
407 0 : *static_cast<SVGPathDataAndInfo*>(aDest.mU.mPtr);
408 : const SVGPathDataAndInfo& valueToAdd =
409 0 : *static_cast<const SVGPathDataAndInfo*>(aValueToAdd.mU.mPtr);
410 :
411 0 : if (valueToAdd.IsIdentity()) { // Adding identity value - no-op
412 0 : return NS_OK;
413 : }
414 :
415 0 : if (!dest.IsIdentity()) {
416 : // Neither value is identity; make sure they're compatible.
417 0 : MOZ_ASSERT(dest.Element() == valueToAdd.Element(),
418 : "adding values from different elements...?");
419 :
420 0 : PathInterpolationResult check = CanInterpolate(dest, valueToAdd);
421 0 : if (check == eCannotInterpolate) {
422 : // SVGContentUtils::ReportToConsole - can't add path segment lists with
423 : // different numbers of segments, with arcs that have different flag
424 : // values, or with incompatible segment types.
425 0 : return NS_ERROR_FAILURE;
426 : }
427 0 : if (check == eRequiresConversion) {
428 : // Convert dest, in-place, to match the types in valueToAdd:
429 0 : ConvertAllPathSegmentData(dest.begin(), dest.end(),
430 : valueToAdd.begin(), valueToAdd.end(),
431 0 : dest.begin());
432 : }
433 : }
434 :
435 0 : return AddWeightedPathSegLists(1.0, dest, aCount, valueToAdd, dest);
436 : }
437 :
438 : nsresult
439 0 : SVGPathSegListSMILType::ComputeDistance(const nsSMILValue& aFrom,
440 : const nsSMILValue& aTo,
441 : double& aDistance) const
442 : {
443 0 : NS_PRECONDITION(aFrom.mType == this, "Unexpected SMIL type");
444 0 : NS_PRECONDITION(aTo.mType == this, "Incompatible SMIL type");
445 :
446 : // See https://bugzilla.mozilla.org/show_bug.cgi?id=522306#c18
447 :
448 : // SVGContentUtils::ReportToConsole
449 0 : return NS_ERROR_NOT_IMPLEMENTED;
450 : }
451 :
452 : nsresult
453 0 : SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
454 : const nsSMILValue& aEndVal,
455 : double aUnitDistance,
456 : nsSMILValue& aResult) const
457 : {
458 0 : NS_PRECONDITION(aStartVal.mType == aEndVal.mType,
459 : "Trying to interpolate different types");
460 0 : NS_PRECONDITION(aStartVal.mType == this,
461 : "Unexpected types for interpolation");
462 0 : NS_PRECONDITION(aResult.mType == this, "Unexpected result type");
463 :
464 : const SVGPathDataAndInfo& start =
465 0 : *static_cast<const SVGPathDataAndInfo*>(aStartVal.mU.mPtr);
466 : const SVGPathDataAndInfo& end =
467 0 : *static_cast<const SVGPathDataAndInfo*>(aEndVal.mU.mPtr);
468 : SVGPathDataAndInfo& result =
469 0 : *static_cast<SVGPathDataAndInfo*>(aResult.mU.mPtr);
470 0 : MOZ_ASSERT(result.IsIdentity(),
471 : "expecting outparam to start out as identity");
472 :
473 0 : PathInterpolationResult check = CanInterpolate(start, end);
474 :
475 0 : if (check == eCannotInterpolate) {
476 : // SVGContentUtils::ReportToConsole - can't interpolate path segment lists with
477 : // different numbers of segments, with arcs with different flag values, or
478 : // with incompatible segment types.
479 0 : return NS_ERROR_FAILURE;
480 : }
481 :
482 0 : const SVGPathDataAndInfo* startListToUse = &start;
483 0 : if (check == eRequiresConversion) {
484 : // Can't convert |start| in-place, since it's const. Instead, we copy it
485 : // into |result|, converting the types as we go, and use that as our start.
486 0 : if (!result.SetLength(end.Length())) {
487 0 : return NS_ERROR_OUT_OF_MEMORY;
488 : }
489 0 : result.SetElement(end.Element()); // propagate target element info!
490 :
491 0 : ConvertAllPathSegmentData(start.begin(), start.end(),
492 : end.begin(), end.end(),
493 0 : result.begin());
494 0 : startListToUse = &result;
495 : }
496 :
497 0 : return AddWeightedPathSegLists(1.0 - aUnitDistance, *startListToUse,
498 0 : aUnitDistance, end, result);
499 : }
500 :
501 : } // namespace mozilla
|