Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / basegfx / source / polygon / b2dsvgpolypolygon.cxx
blob2fdacad3653ff0d05dfa98a29e382599fce0a68b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <basegfx/polygon/b2dpolygontools.hxx>
21 #include <basegfx/polygon/b2dpolypolygontools.hxx>
22 #include <basegfx/polygon/b2dpolypolygon.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
24 #include <basegfx/matrix/b2dhommatrixtools.hxx>
26 #include <rtl/ustring.hxx>
27 #include <sal/log.hxx>
28 #include <rtl/math.hxx>
29 #include <rtl/character.hxx>
30 #include <stringconversiontools.hxx>
32 namespace
35 void putCommandChar(OUStringBuffer& rBuffer,sal_Unicode& rLastSVGCommand, sal_Unicode aChar, bool bToLower,bool bVerbose)
37 const sal_Unicode aCommand = bToLower ? rtl::toAsciiLowerCase(aChar) : aChar;
39 if (bVerbose && rBuffer.getLength())
40 rBuffer.append(' ');
42 if (bVerbose || rLastSVGCommand != aCommand)
44 rBuffer.append(aCommand);
45 rLastSVGCommand = aCommand;
49 void putNumberChar(OUStringBuffer& rStr,double fValue, double fOldValue, bool bUseRelativeCoordinates,bool bVerbose)
51 if (bUseRelativeCoordinates)
52 fValue -= fOldValue;
54 const sal_Int32 aLen(rStr.getLength());
55 if (bVerbose || (aLen && basegfx::internal::isOnNumberChar(rStr[aLen - 1], false) && fValue >= 0.0))
56 rStr.append(' ');
58 rStr.append(fValue);
63 namespace basegfx
65 namespace utils
67 bool PointIndex::operator<(const PointIndex& rComp) const
69 if(rComp.getPolygonIndex() == getPolygonIndex())
71 return rComp.getPointIndex() < getPointIndex();
74 return rComp.getPolygonIndex() < getPolygonIndex();
77 bool importFromSvgD(
78 B2DPolyPolygon& o_rPolyPolygon,
79 const OUString& rSvgDStatement,
80 bool bHandleRelativeNextPointCompatible,
81 PointIndexSet* pHelpPointIndexSet)
83 o_rPolyPolygon.clear();
84 const sal_Int32 nLen(rSvgDStatement.getLength());
85 sal_Int32 nPos(0);
86 double nLastX( 0.0 );
87 double nLastY( 0.0 );
88 B2DPolygon aCurrPoly;
90 // skip initial whitespace
91 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
93 while(nPos < nLen)
95 bool bRelative(false);
96 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
98 if(o_rPolyPolygon.count() && !aCurrPoly.count() && !(aCurrChar == 'm' || aCurrChar == 'M'))
100 // we have a new sub-polygon starting, but without a 'moveto' command.
101 // this requires to add the current point as start point to the polygon
102 // (see SVG1.1 8.3.3 The "closepath" command)
103 aCurrPoly.append(B2DPoint(nLastX, nLastY));
106 switch(aCurrChar)
108 case 'z' :
109 case 'Z' :
111 // consume CurrChar and whitespace
112 nPos++;
113 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
115 // create closed polygon and reset import values
116 if(aCurrPoly.count())
118 if(!bHandleRelativeNextPointCompatible)
120 // SVG defines that "the next subpath starts at the
121 // same initial point as the current subpath", so set the
122 // current point if we do not need to be compatible
123 nLastX = aCurrPoly.getB2DPoint(0).getX();
124 nLastY = aCurrPoly.getB2DPoint(0).getY();
127 aCurrPoly.setClosed(true);
128 o_rPolyPolygon.append(aCurrPoly);
129 aCurrPoly.clear();
132 break;
135 case 'm' :
136 case 'M' :
138 // create non-closed polygon and reset import values
139 if(aCurrPoly.count())
141 o_rPolyPolygon.append(aCurrPoly);
142 aCurrPoly.clear();
144 [[fallthrough]]; // to add coordinate data as 1st point of new polygon
146 case 'l' :
147 case 'L' :
149 if(aCurrChar == 'm' || aCurrChar == 'l')
151 bRelative = true;
154 // consume CurrChar and whitespace
155 nPos++;
156 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
158 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
160 double nX, nY;
162 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
163 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
165 if(bRelative)
167 nX += nLastX;
168 nY += nLastY;
171 // set last position
172 nLastX = nX;
173 nLastY = nY;
175 // add point
176 aCurrPoly.append(B2DPoint(nX, nY));
178 break;
181 case 'h' :
183 bRelative = true;
184 [[fallthrough]];
186 case 'H' :
188 nPos++;
189 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
191 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
193 double nX, nY(nLastY);
195 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
197 if(bRelative)
199 nX += nLastX;
202 // set last position
203 nLastX = nX;
205 // add point
206 aCurrPoly.append(B2DPoint(nX, nY));
208 break;
211 case 'v' :
213 bRelative = true;
214 [[fallthrough]];
216 case 'V' :
218 nPos++;
219 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
221 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
223 double nX(nLastX), nY;
225 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
227 if(bRelative)
229 nY += nLastY;
232 // set last position
233 nLastY = nY;
235 // add point
236 aCurrPoly.append(B2DPoint(nX, nY));
238 break;
241 case 's' :
243 bRelative = true;
244 [[fallthrough]];
246 case 'S' :
248 nPos++;
249 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
251 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
253 double nX, nY;
254 double nX2, nY2;
256 if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
257 if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
258 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
259 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
261 if(bRelative)
263 nX2 += nLastX;
264 nY2 += nLastY;
265 nX += nLastX;
266 nY += nLastY;
269 // ensure existence of start point
270 if(!aCurrPoly.count())
272 aCurrPoly.append(B2DPoint(nLastX, nLastY));
275 // get first control point. It's the reflection of the PrevControlPoint
276 // of the last point. If not existent, use current point (see SVG)
277 B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
278 const sal_uInt32 nIndex(aCurrPoly.count() - 1);
280 if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
282 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
283 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
285 // use mirrored previous control point
286 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
287 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
290 // append curved edge
291 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
293 // set last position
294 nLastX = nX;
295 nLastY = nY;
297 break;
300 case 'c' :
302 bRelative = true;
303 [[fallthrough]];
305 case 'C' :
307 nPos++;
308 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
310 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
312 double nX, nY;
313 double nX1, nY1;
314 double nX2, nY2;
316 if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
317 if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
318 if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
319 if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
320 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
321 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
323 if(bRelative)
325 nX1 += nLastX;
326 nY1 += nLastY;
327 nX2 += nLastX;
328 nY2 += nLastY;
329 nX += nLastX;
330 nY += nLastY;
333 // ensure existence of start point
334 if(!aCurrPoly.count())
336 aCurrPoly.append(B2DPoint(nLastX, nLastY));
339 // append curved edge
340 aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
342 // set last position
343 nLastX = nX;
344 nLastY = nY;
346 break;
349 // #100617# quadratic beziers are imported as cubic ones
350 case 'q' :
352 bRelative = true;
353 [[fallthrough]];
355 case 'Q' :
357 nPos++;
358 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
360 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
362 double nX, nY;
363 double nX1, nY1;
365 if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
366 if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
367 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
368 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
370 if(bRelative)
372 nX1 += nLastX;
373 nY1 += nLastY;
374 nX += nLastX;
375 nY += nLastY;
378 // ensure existence of start point
379 if(!aCurrPoly.count())
381 aCurrPoly.append(B2DPoint(nLastX, nLastY));
384 // append curved edge
385 aCurrPoly.appendQuadraticBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX, nY));
387 // set last position
388 nLastX = nX;
389 nLastY = nY;
391 break;
394 // #100617# relative quadratic beziers are imported as cubic
395 case 't' :
397 bRelative = true;
398 [[fallthrough]];
400 case 'T' :
402 nPos++;
403 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
405 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
407 double nX, nY;
409 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
410 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
412 if(bRelative)
414 nX += nLastX;
415 nY += nLastY;
418 // ensure existence of start point
419 if(!aCurrPoly.count())
421 aCurrPoly.append(B2DPoint(nLastX, nLastY));
424 // get first control point. It's the reflection of the PrevControlPoint
425 // of the last point. If not existent, use current point (see SVG)
426 B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
427 const sal_uInt32 nIndex(aCurrPoly.count() - 1);
428 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
430 if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
432 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
434 // use mirrored previous control point
435 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
436 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
439 if(!aPrevControl.equal(aPrevPoint))
441 // there is a prev control point, and we have the already mirrored one
442 // in aPrevControl. We also need the quadratic control point for this
443 // new quadratic segment to calculate the 2nd cubic control point
444 const B2DPoint aQuadControlPoint(
445 ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
446 ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
448 // calculate the cubic bezier coefficients from the quadratic ones.
449 const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
450 const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
452 // append curved edge, use mirrored cubic control point directly
453 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
455 else
457 // when no previous control, SVG says to use current point -> straight line.
458 // Just add end point
459 aCurrPoly.append(B2DPoint(nX, nY));
462 // set last position
463 nLastX = nX;
464 nLastY = nY;
466 break;
469 case 'a' :
471 bRelative = true;
472 [[fallthrough]];
474 case 'A' :
476 nPos++;
477 basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
479 while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
481 double nX, nY;
482 double fRX, fRY, fPhi;
483 sal_Int32 bLargeArcFlag, bSweepFlag;
485 if(!basegfx::internal::importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
486 if(!basegfx::internal::importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
487 if(!basegfx::internal::importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
488 if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
489 if(!basegfx::internal::importFlagAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
490 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
491 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
493 if(bRelative)
495 nX += nLastX;
496 nY += nLastY;
499 if( rtl::math::approxEqual(nX, nLastX) && rtl::math::approxEqual(nY, nLastY) )
500 continue; // start==end -> skip according to SVG spec
502 if( fRX == 0.0 || fRY == 0.0 )
504 // straight line segment according to SVG spec
505 aCurrPoly.append(B2DPoint(nX, nY));
507 else
509 // normalize according to SVG spec
510 fRX=fabs(fRX); fRY=fabs(fRY);
512 // from the SVG spec, appendix F.6.4
514 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
515 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
516 const B2DPoint p1(nLastX, nLastY);
517 const B2DPoint p2(nX, nY);
518 B2DHomMatrix aTransform(basegfx::utils::createRotateB2DHomMatrix(
519 -deg2rad(fPhi)));
521 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
523 // ______________________________________ rx y1'
524 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
525 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
526 // rx
527 // chose + if f_A != f_S
528 // chose - if f_A = f_S
529 B2DPoint aCenter_prime;
530 const double fRadicant(
531 (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
532 (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
533 if( fRadicant < 0.0 )
535 // no solution - according to SVG
536 // spec, scale up ellipse
537 // uniformly such that it passes
538 // through end points (denominator
539 // of radicant solved for fRY,
540 // with s=fRX/fRY)
541 const double fRatio(fRX/fRY);
542 const double fRadicant2(
543 p1_prime.getY()*p1_prime.getY() +
544 p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
545 if( fRadicant2 < 0.0 )
547 // only trivial solution, one
548 // of the axes 0 -> straight
549 // line segment according to
550 // SVG spec
551 aCurrPoly.append(B2DPoint(nX, nY));
552 continue;
555 fRY=sqrt(fRadicant2);
556 fRX=fRatio*fRY;
558 // keep center_prime forced to (0,0)
560 else
562 const double fFactor(
563 (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
564 sqrt(fRadicant));
566 // actually calculate center_prime
567 aCenter_prime = B2DPoint(
568 fFactor*fRX*p1_prime.getY()/fRY,
569 -fFactor*fRY*p1_prime.getX()/fRX);
572 // + u - v
573 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
574 // - ||u|| ||v||
576 // 1 | (x1' - cx')/rx |
577 // theta1 = angle(( ), | | )
578 // 0 | (y1' - cy')/ry |
579 const B2DPoint aRadii(fRX,fRY);
580 double fTheta1(
581 B2DVector(1.0,0.0).angle(
582 (p1_prime-aCenter_prime)/aRadii));
584 // |1| | (-x1' - cx')/rx |
585 // theta2 = angle( | | , | | )
586 // |0| | (-y1' - cy')/ry |
587 double fTheta2(
588 B2DVector(1.0,0.0).angle(
589 (-p1_prime-aCenter_prime)/aRadii));
591 // map both angles to [0,2pi)
592 fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
593 fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
595 // make sure the large arc is taken
596 // (since
597 // createPolygonFromEllipseSegment()
598 // normalizes to e.g. cw arc)
599 if( !bSweepFlag )
600 std::swap(fTheta1,fTheta2);
602 // finally, create bezier polygon from this
603 B2DPolygon aSegment(
604 utils::createPolygonFromUnitEllipseSegment(
605 fTheta1, fTheta2 ));
607 // transform ellipse by rotation & move to final center
608 aTransform = basegfx::utils::createScaleB2DHomMatrix(fRX, fRY);
609 aTransform.translate(aCenter_prime.getX(),
610 aCenter_prime.getY());
611 aTransform.rotate(deg2rad(fPhi));
612 const B2DPoint aOffset((p1+p2)/2.0);
613 aTransform.translate(aOffset.getX(),
614 aOffset.getY());
615 aSegment.transform(aTransform);
617 // createPolygonFromEllipseSegment()
618 // always creates arcs that are
619 // positively oriented - flip polygon
620 // if we swapped angles above
621 if( !bSweepFlag )
622 aSegment.flip();
624 // remember PointIndex of evtl. added pure helper points
625 sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
626 aCurrPoly.append(aSegment);
628 // if asked for, mark pure helper points by adding them to the index list of
629 // helper points
630 if(pHelpPointIndexSet && aCurrPoly.count() > 1)
632 const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
634 for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
636 pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
641 // set last position
642 nLastX = nX;
643 nLastY = nY;
645 break;
648 default:
650 SAL_WARN("basegfx", "importFromSvgD(): skipping tags in svg:d element (unknown: \""
651 << aCurrChar
652 << "\")!");
653 ++nPos;
654 break;
659 // if there is polygon data, create non-closed polygon
660 if(aCurrPoly.count())
662 o_rPolyPolygon.append(aCurrPoly);
665 return true;
668 bool importFromSvgPoints( B2DPolygon& o_rPoly,
669 const OUString& rSvgPointsAttribute )
671 o_rPoly.clear();
672 const sal_Int32 nLen(rSvgPointsAttribute.getLength());
673 sal_Int32 nPos(0);
674 double nX, nY;
676 // skip initial whitespace
677 basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
679 while(nPos < nLen)
681 if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
682 if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
684 // add point
685 o_rPoly.append(B2DPoint(nX, nY));
687 // skip to next number, or finish
688 basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
691 return true;
694 OUString exportToSvgPoints( const B2DPolygon& rPoly )
696 SAL_WARN_IF(rPoly.areControlPointsUsed(), "basegfx", "exportToSvgPoints: Only non-bezier polygons allowed (!)");
697 const sal_uInt32 nPointCount(rPoly.count());
698 OUStringBuffer aResult;
700 for(sal_uInt32 a(0); a < nPointCount; a++)
702 const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
704 if(a)
706 aResult.append(' ');
709 aResult.append(aPoint.getX());
710 aResult.append(',');
711 aResult.append(aPoint.getY());
714 return aResult.makeStringAndClear();
717 OUString exportToSvgD(
718 const B2DPolyPolygon& rPolyPolygon,
719 bool bUseRelativeCoordinates,
720 bool bDetectQuadraticBeziers,
721 bool bHandleRelativeNextPointCompatible,
722 bool bOOXMLMotionPath)
724 const sal_uInt32 nCount(rPolyPolygon.count());
725 sal_uInt32 nCombinedPointCount = 0;
726 for(sal_uInt32 i(0); i < nCount; i++)
728 const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
729 nCombinedPointCount += aPolygon.count();
732 OUStringBuffer aResult(std::max<int>(nCombinedPointCount * 32,512));
733 B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
735 for(sal_uInt32 i(0); i < nCount; i++)
737 const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
738 const sal_uInt32 nPointCount(aPolygon.count());
740 if(nPointCount)
742 const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
743 const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
744 sal_Unicode aLastSVGCommand(' '); // last SVG command char
745 B2DPoint aLeft, aRight; // for quadratic bezier test
747 // handle polygon start point
748 B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
749 bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
751 if(bHandleRelativeNextPointCompatible)
753 // To get around the error that the start point for the next polygon is the
754 // start point of the current one (and not the last as it was handled up to now)
755 // do force to write an absolute 'M' command as start for the next polygon
756 bUseRelativeCoordinatesForFirstPoint = false;
759 // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
760 putCommandChar(aResult, aLastSVGCommand, 'M', bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
761 putNumberChar(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
762 putNumberChar(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
763 aLastSVGCommand = bUseRelativeCoordinatesForFirstPoint ? 'l' : 'L';
764 aCurrentSVGPosition = aEdgeStart;
766 for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
768 // prepare access to next point
769 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
770 const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
772 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
773 const bool bEdgeIsBezier(bPolyUsesControlPoints
774 && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
776 if(bEdgeIsBezier)
778 // handle bezier edge
779 const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
780 const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
781 bool bIsQuadraticBezier(false);
783 // check continuity at current edge's start point. For SVG, do NOT use an
784 // existing continuity since no 'S' or 's' statement should be written. At
785 // import, that 'previous' control vector is not available. SVG documentation
786 // says for interpretation:
788 // "(If there is no previous command or if the previous command was
789 // not a C, c, S or s, assume the first control point is coincident
790 // with the current point.)"
792 // That's what is done from our import, so avoid exporting it as first statement
793 // is necessary.
794 const bool bSymmetricAtEdgeStart(
795 !bOOXMLMotionPath && nIndex != 0
796 && aPolygon.getContinuityInPoint(nIndex) == B2VectorContinuity::C2);
798 if(bDetectQuadraticBeziers)
800 // check for quadratic beziers - that's
801 // the case if both control points are in
802 // the same place when they are prolonged
803 // to the common quadratic control point
805 // Left: P = (3P1 - P0) / 2
806 // Right: P = (3P2 - P3) / 2
807 aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
808 aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
809 bIsQuadraticBezier = aLeft.equal(aRight);
812 if(bIsQuadraticBezier)
814 // approximately equal, export as quadratic bezier
815 if(bSymmetricAtEdgeStart)
817 putCommandChar(aResult, aLastSVGCommand, 'T', bUseRelativeCoordinates, bOOXMLMotionPath);
819 putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
820 putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
821 aCurrentSVGPosition = aEdgeEnd;
823 else
825 putCommandChar(aResult, aLastSVGCommand, 'Q', bUseRelativeCoordinates, bOOXMLMotionPath);
827 putNumberChar(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
828 putNumberChar(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
829 putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
830 putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
831 aCurrentSVGPosition = aEdgeEnd;
834 else
836 // export as cubic bezier
837 if(bSymmetricAtEdgeStart)
839 putCommandChar(aResult, aLastSVGCommand, 'S', bUseRelativeCoordinates, bOOXMLMotionPath);
841 putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
842 putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
843 putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
844 putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
845 aCurrentSVGPosition = aEdgeEnd;
847 else
849 putCommandChar(aResult, aLastSVGCommand, 'C', bUseRelativeCoordinates, bOOXMLMotionPath);
851 putNumberChar(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
852 putNumberChar(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
853 putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
854 putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
855 putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
856 putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
857 aCurrentSVGPosition = aEdgeEnd;
861 else
863 // straight edge
864 if(nNextIndex == 0)
866 // it's a closed polygon's last edge and it's not a bezier edge, so there is
867 // no need to write it
869 else
871 const bool bXEqual(rtl::math::approxEqual(aEdgeStart.getX(), aEdgeEnd.getX()));
872 const bool bYEqual(rtl::math::approxEqual(aEdgeStart.getY(), aEdgeEnd.getY()));
874 if(bXEqual && bYEqual)
876 // point is a double point; do not export at all
878 else if(bXEqual && !bOOXMLMotionPath)
880 // export as vertical line
881 putCommandChar(aResult, aLastSVGCommand, 'V', bUseRelativeCoordinates, bOOXMLMotionPath);
883 putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
884 aCurrentSVGPosition = aEdgeEnd;
886 else if(bYEqual && !bOOXMLMotionPath)
888 // export as horizontal line
889 putCommandChar(aResult, aLastSVGCommand, 'H', bUseRelativeCoordinates, bOOXMLMotionPath);
891 putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
892 aCurrentSVGPosition = aEdgeEnd;
894 else
896 // export as line
897 putCommandChar(aResult, aLastSVGCommand, 'L', bUseRelativeCoordinates, bOOXMLMotionPath);
899 putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
900 putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
901 aCurrentSVGPosition = aEdgeEnd;
906 // prepare edge start for next loop step
907 aEdgeStart = aEdgeEnd;
910 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
911 if(aPolygon.isClosed())
913 putCommandChar(aResult, aLastSVGCommand, 'Z', bUseRelativeCoordinates, bOOXMLMotionPath);
915 else if (bOOXMLMotionPath)
917 putCommandChar(aResult, aLastSVGCommand, 'E', bUseRelativeCoordinates, bOOXMLMotionPath);
920 if(!bHandleRelativeNextPointCompatible)
922 // SVG defines that "the next subpath starts at the same initial point as the current subpath",
923 // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
924 aCurrentSVGPosition = aPolygon.getB2DPoint(0);
929 return aResult.makeStringAndClear();
934 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */