Version 4.2.0.1, tag libreoffice-4.2.0.1
[LibreOffice.git] / basegfx / source / polygon / b2dsvgpolypolygon.cxx
blob62e70144a3aa807b72a4f0db45df4483b8d12e43
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>
25 #include <rtl/ustring.hxx>
26 #include <rtl/math.hxx>
27 #include <stringconversiontools.hxx>
29 namespace basegfx
31 namespace tools
33 bool PointIndex::operator<(const PointIndex& rComp) const
35 if(rComp.getPolygonIndex() == getPolygonIndex())
37 return rComp.getPointIndex() < getPointIndex();
40 return rComp.getPolygonIndex() < getPolygonIndex();
43 bool importFromSvgD(
44 B2DPolyPolygon& o_rPolyPolygon,
45 const OUString& rSvgDStatement,
46 bool bHandleRelativeNextPointCompatible,
47 PointIndexSet* pHelpPointIndexSet)
49 o_rPolyPolygon.clear();
50 const sal_Int32 nLen(rSvgDStatement.getLength());
51 sal_Int32 nPos(0);
52 double nLastX( 0.0 );
53 double nLastY( 0.0 );
54 B2DPolygon aCurrPoly;
56 // skip initial whitespace
57 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
59 while(nPos < nLen)
61 bool bRelative(false);
62 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
64 if(o_rPolyPolygon.count() && !aCurrPoly.count() && !('m' == aCurrChar || 'M' == aCurrChar))
66 // we have a new sub-polygon starting, but without a 'moveto' command.
67 // this requires to add the current point as start point to the polygon
68 // (see SVG1.1 8.3.3 The "closepath" command)
69 aCurrPoly.append(B2DPoint(nLastX, nLastY));
72 switch(aCurrChar)
74 case 'z' :
75 case 'Z' :
77 // consume CurrChar and whitespace
78 nPos++;
79 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
81 // create closed polygon and reset import values
82 if(aCurrPoly.count())
84 if(!bHandleRelativeNextPointCompatible)
86 // SVG defines that "the next subpath starts at the
87 // same initial point as the current subpath", so set the
88 // current point if we do not need to be compatible
89 nLastX = aCurrPoly.getB2DPoint(0).getX();
90 nLastY = aCurrPoly.getB2DPoint(0).getY();
93 aCurrPoly.setClosed(true);
94 o_rPolyPolygon.append(aCurrPoly);
95 aCurrPoly.clear();
98 break;
101 case 'm' :
102 case 'M' :
104 // create non-closed polygon and reset import values
105 if(aCurrPoly.count())
107 o_rPolyPolygon.append(aCurrPoly);
108 aCurrPoly.clear();
111 // FALLTHROUGH intended to add coordinate data as 1st point of new polygon
113 case 'l' :
114 case 'L' :
116 if('m' == aCurrChar || 'l' == aCurrChar)
118 bRelative = true;
121 // consume CurrChar and whitespace
122 nPos++;
123 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
125 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
127 double nX, nY;
129 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
130 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
132 if(bRelative)
134 nX += nLastX;
135 nY += nLastY;
138 // set last position
139 nLastX = nX;
140 nLastY = nY;
142 // add point
143 aCurrPoly.append(B2DPoint(nX, nY));
145 break;
148 case 'h' :
150 bRelative = true;
151 // FALLTHROUGH intended
153 case 'H' :
155 nPos++;
156 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
158 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
160 double nX, nY(nLastY);
162 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
164 if(bRelative)
166 nX += nLastX;
169 // set last position
170 nLastX = nX;
172 // add point
173 aCurrPoly.append(B2DPoint(nX, nY));
175 break;
178 case 'v' :
180 bRelative = true;
181 // FALLTHROUGH intended
183 case 'V' :
185 nPos++;
186 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
188 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
190 double nX(nLastX), nY;
192 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
194 if(bRelative)
196 nY += nLastY;
199 // set last position
200 nLastY = nY;
202 // add point
203 aCurrPoly.append(B2DPoint(nX, nY));
205 break;
208 case 's' :
210 bRelative = true;
211 // FALLTHROUGH intended
213 case 'S' :
215 nPos++;
216 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
218 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
220 double nX, nY;
221 double nX2, nY2;
223 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
224 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
225 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
226 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
228 if(bRelative)
230 nX2 += nLastX;
231 nY2 += nLastY;
232 nX += nLastX;
233 nY += nLastY;
236 // ensure existance of start point
237 if(!aCurrPoly.count())
239 aCurrPoly.append(B2DPoint(nLastX, nLastY));
242 // get first control point. It's the reflection of the PrevControlPoint
243 // of the last point. If not existent, use current point (see SVG)
244 B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
245 const sal_uInt32 nIndex(aCurrPoly.count() - 1);
247 if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
249 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
250 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
252 // use mirrored previous control point
253 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
254 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
257 // append curved edge
258 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
260 // set last position
261 nLastX = nX;
262 nLastY = nY;
264 break;
267 case 'c' :
269 bRelative = true;
270 // FALLTHROUGH intended
272 case 'C' :
274 nPos++;
275 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
277 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
279 double nX, nY;
280 double nX1, nY1;
281 double nX2, nY2;
283 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
284 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
285 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
286 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
287 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
288 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
290 if(bRelative)
292 nX1 += nLastX;
293 nY1 += nLastY;
294 nX2 += nLastX;
295 nY2 += nLastY;
296 nX += nLastX;
297 nY += nLastY;
300 // ensure existance of start point
301 if(!aCurrPoly.count())
303 aCurrPoly.append(B2DPoint(nLastX, nLastY));
306 // append curved edge
307 aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
309 // set last position
310 nLastX = nX;
311 nLastY = nY;
313 break;
316 // #100617# quadratic beziers are imported as cubic ones
317 case 'q' :
319 bRelative = true;
320 // FALLTHROUGH intended
322 case 'Q' :
324 nPos++;
325 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
327 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
329 double nX, nY;
330 double nX1, nY1;
332 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
333 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
334 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
335 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
337 if(bRelative)
339 nX1 += nLastX;
340 nY1 += nLastY;
341 nX += nLastX;
342 nY += nLastY;
345 // calculate the cubic bezier coefficients from the quadratic ones
346 const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
347 const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
348 const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
349 const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
351 // ensure existance of start point
352 if(!aCurrPoly.count())
354 aCurrPoly.append(B2DPoint(nLastX, nLastY));
357 // append curved edge
358 aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
360 // set last position
361 nLastX = nX;
362 nLastY = nY;
364 break;
367 // #100617# relative quadratic beziers are imported as cubic
368 case 't' :
370 bRelative = true;
371 // FALLTHROUGH intended
373 case 'T' :
375 nPos++;
376 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
378 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
380 double nX, nY;
382 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
383 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
385 if(bRelative)
387 nX += nLastX;
388 nY += nLastY;
391 // ensure existance of start point
392 if(!aCurrPoly.count())
394 aCurrPoly.append(B2DPoint(nLastX, nLastY));
397 // get first control point. It's the reflection of the PrevControlPoint
398 // of the last point. If not existent, use current point (see SVG)
399 B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
400 const sal_uInt32 nIndex(aCurrPoly.count() - 1);
401 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
403 if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
405 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
407 // use mirrored previous control point
408 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
409 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
412 if(!aPrevControl.equal(aPrevPoint))
414 // there is a prev control point, and we have the already mirrored one
415 // in aPrevControl. We also need the quadratic control point for this
416 // new quadratic segment to calculate the 2nd cubic control point
417 const B2DPoint aQuadControlPoint(
418 ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
419 ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
421 // calculate the cubic bezier coefficients from the quadratic ones.
422 const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
423 const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
425 // append curved edge, use mirrored cubic control point directly
426 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
428 else
430 // when no previous control, SVG says to use current point -> straight line.
431 // Just add end point
432 aCurrPoly.append(B2DPoint(nX, nY));
435 // set last position
436 nLastX = nX;
437 nLastY = nY;
439 break;
442 case 'a' :
444 bRelative = true;
445 // FALLTHROUGH intended
447 case 'A' :
449 nPos++;
450 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
452 while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
454 double nX, nY;
455 double fRX, fRY, fPhi;
456 sal_Int32 bLargeArcFlag, bSweepFlag;
458 if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
459 if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
460 if(!::basegfx::internal::lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
461 if(!::basegfx::internal::lcl_importFlagAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
462 if(!::basegfx::internal::lcl_importFlagAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
463 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
464 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
466 if(bRelative)
468 nX += nLastX;
469 nY += nLastY;
472 if( nX == nLastX && nY == nLastY )
473 continue; // start==end -> skip according to SVG spec
475 if( fRX == 0.0 || fRY == 0.0 )
477 // straight line segment according to SVG spec
478 aCurrPoly.append(B2DPoint(nX, nY));
480 else
482 // normalize according to SVG spec
483 fRX=fabs(fRX); fRY=fabs(fRY);
485 // from the SVG spec, appendix F.6.4
487 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
488 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
489 const B2DPoint p1(nLastX, nLastY);
490 const B2DPoint p2(nX, nY);
491 B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180));
493 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
495 // ______________________________________ rx y1'
496 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
497 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
498 // rx
499 // chose + if f_A != f_S
500 // chose - if f_A = f_S
501 B2DPoint aCenter_prime;
502 const double fRadicant(
503 (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
504 (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
505 if( fRadicant < 0.0 )
507 // no solution - according to SVG
508 // spec, scale up ellipse
509 // uniformly such that it passes
510 // through end points (denominator
511 // of radicant solved for fRY,
512 // with s=fRX/fRY)
513 const double fRatio(fRX/fRY);
514 const double fRadicant2(
515 p1_prime.getY()*p1_prime.getY() +
516 p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
517 if( fRadicant2 < 0.0 )
519 // only trivial solution, one
520 // of the axes 0 -> straight
521 // line segment according to
522 // SVG spec
523 aCurrPoly.append(B2DPoint(nX, nY));
524 continue;
527 fRY=sqrt(fRadicant2);
528 fRX=fRatio*fRY;
530 // keep center_prime forced to (0,0)
532 else
534 const double fFactor(
535 (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
536 sqrt(fRadicant));
538 // actually calculate center_prime
539 aCenter_prime = B2DPoint(
540 fFactor*fRX*p1_prime.getY()/fRY,
541 -fFactor*fRY*p1_prime.getX()/fRX);
544 // + u - v
545 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
546 // - ||u|| ||v||
548 // 1 | (x1' - cx')/rx |
549 // theta1 = angle(( ), | | )
550 // 0 | (y1' - cy')/ry |
551 const B2DPoint aRadii(fRX,fRY);
552 double fTheta1(
553 B2DVector(1.0,0.0).angle(
554 (p1_prime-aCenter_prime)/aRadii));
556 // |1| | (-x1' - cx')/rx |
557 // theta2 = angle( | | , | | )
558 // |0| | (-y1' - cy')/ry |
559 double fTheta2(
560 B2DVector(1.0,0.0).angle(
561 (-p1_prime-aCenter_prime)/aRadii));
563 // map both angles to [0,2pi)
564 fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
565 fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
567 // make sure the large arc is taken
568 // (since
569 // createPolygonFromEllipseSegment()
570 // normalizes to e.g. cw arc)
571 if( !bSweepFlag )
572 std::swap(fTheta1,fTheta2);
574 // finally, create bezier polygon from this
575 B2DPolygon aSegment(
576 tools::createPolygonFromUnitEllipseSegment(
577 fTheta1, fTheta2 ));
579 // transform ellipse by rotation & move to final center
580 aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY);
581 aTransform.translate(aCenter_prime.getX(),
582 aCenter_prime.getY());
583 aTransform.rotate(fPhi*M_PI/180);
584 const B2DPoint aOffset((p1+p2)/2.0);
585 aTransform.translate(aOffset.getX(),
586 aOffset.getY());
587 aSegment.transform(aTransform);
589 // createPolygonFromEllipseSegment()
590 // always creates arcs that are
591 // positively oriented - flip polygon
592 // if we swapped angles above
593 if( !bSweepFlag )
594 aSegment.flip();
596 // remember PointIndex of evtl. added pure helper points
597 sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
598 aCurrPoly.append(aSegment);
600 // if asked for, mark pure helper points by adding them to the index list of
601 // helper points
602 if(pHelpPointIndexSet && aCurrPoly.count() > 1)
604 const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
606 for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
608 pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
613 // set last position
614 nLastX = nX;
615 nLastY = nY;
617 break;
620 default:
622 OSL_FAIL("importFromSvgD(): skipping tags in svg:d element (unknown)!");
623 OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar);
624 ++nPos;
625 break;
630 // if there is polygon data, create non-closed polygon
631 if(aCurrPoly.count())
633 o_rPolyPolygon.append(aCurrPoly);
636 return true;
639 bool importFromSvgPoints( B2DPolygon& o_rPoly,
640 const OUString& rSvgPointsAttribute )
642 o_rPoly.clear();
643 const sal_Int32 nLen(rSvgPointsAttribute.getLength());
644 sal_Int32 nPos(0);
645 double nX, nY;
647 // skip initial whitespace
648 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
650 while(nPos < nLen)
652 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
653 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
655 // add point
656 o_rPoly.append(B2DPoint(nX, nY));
658 // skip to next number, or finish
659 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
662 return true;
665 OUString exportToSvgPoints( const B2DPolygon& rPoly )
667 OSL_ENSURE(!rPoly.areControlPointsUsed(), "exportToSvgPoints: Only non-bezier polygons allowed (!)");
668 const sal_uInt32 nPointCount(rPoly.count());
669 OUStringBuffer aResult;
671 for(sal_uInt32 a(0); a < nPointCount; a++)
673 const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
675 if(a)
677 aResult.append(' ');
680 ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getX());
681 aResult.append(',');
682 ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getY());
685 return aResult.makeStringAndClear();
688 OUString exportToSvgD(
689 const B2DPolyPolygon& rPolyPolygon,
690 bool bUseRelativeCoordinates,
691 bool bDetectQuadraticBeziers,
692 bool bHandleRelativeNextPointCompatible)
694 const sal_uInt32 nCount(rPolyPolygon.count());
695 OUStringBuffer aResult;
696 B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
698 for(sal_uInt32 i(0); i < nCount; i++)
700 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
701 const sal_uInt32 nPointCount(aPolygon.count());
703 if(nPointCount)
705 const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
706 const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
707 sal_Unicode aLastSVGCommand(' '); // last SVG command char
708 B2DPoint aLeft, aRight; // for quadratic bezier test
710 // handle polygon start point
711 B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
712 bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
714 if(bHandleRelativeNextPointCompatible)
716 // To get around the error that the start point for the next polygon is the
717 // start point of the current one (and not the last as it was handled up to now)
718 // do force to write an absolute 'M' command as start for the next polygon
719 bUseRelativeCoordinatesForFirstPoint = false;
722 // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
723 aResult.append(::basegfx::internal::lcl_getCommand('M', 'm', bUseRelativeCoordinatesForFirstPoint));
724 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint);
725 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint);
726 aLastSVGCommand = ::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinatesForFirstPoint);
727 aCurrentSVGPosition = aEdgeStart;
729 for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
731 // prepare access to next point
732 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
733 const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
735 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
736 const bool bEdgeIsBezier(bPolyUsesControlPoints
737 && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
739 if(bEdgeIsBezier)
741 // handle bezier edge
742 const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
743 const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
744 bool bIsQuadraticBezier(false);
746 // check continuity at current edge's start point. For SVG, do NOT use an
747 // existing continuity since no 'S' or 's' statement should be written. At
748 // import, that 'previous' control vector is not available. SVG documentation
749 // says for interpretation:
751 // "(If there is no previous command or if the previous command was
752 // not an C, c, S or s, assume the first control point is coincident
753 // with the current point.)"
755 // That's what is done from our import, so avoid exporting it as first statement
756 // is necessary.
757 const bool bSymmetricAtEdgeStart(
758 0 != nIndex
759 && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex));
761 if(bDetectQuadraticBeziers)
763 // check for quadratic beziers - that's
764 // the case if both control points are in
765 // the same place when they are prolonged
766 // to the common quadratic control point
768 // Left: P = (3P1 - P0) / 2
769 // Right: P = (3P2 - P3) / 2
770 aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
771 aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
772 bIsQuadraticBezier = aLeft.equal(aRight);
775 if(bIsQuadraticBezier)
777 // approximately equal, export as quadratic bezier
778 if(bSymmetricAtEdgeStart)
780 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('T', 't', bUseRelativeCoordinates));
782 if(aLastSVGCommand != aCommand)
784 aResult.append(aCommand);
785 aLastSVGCommand = aCommand;
788 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
789 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
790 aLastSVGCommand = aCommand;
791 aCurrentSVGPosition = aEdgeEnd;
793 else
795 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
797 if(aLastSVGCommand != aCommand)
799 aResult.append(aCommand);
800 aLastSVGCommand = aCommand;
803 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
804 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
805 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
806 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
807 aLastSVGCommand = aCommand;
808 aCurrentSVGPosition = aEdgeEnd;
811 else
813 // export as cubic bezier
814 if(bSymmetricAtEdgeStart)
816 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('S', 's', bUseRelativeCoordinates));
818 if(aLastSVGCommand != aCommand)
820 aResult.append(aCommand);
821 aLastSVGCommand = aCommand;
824 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
825 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
826 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
827 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
828 aLastSVGCommand = aCommand;
829 aCurrentSVGPosition = aEdgeEnd;
831 else
833 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('C', 'c', bUseRelativeCoordinates));
835 if(aLastSVGCommand != aCommand)
837 aResult.append(aCommand);
838 aLastSVGCommand = aCommand;
841 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
842 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
843 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
844 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
845 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
846 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
847 aLastSVGCommand = aCommand;
848 aCurrentSVGPosition = aEdgeEnd;
852 else
854 // straight edge
855 if(0 == nNextIndex)
857 // it's a closed polygon's last edge and it's not a bezier edge, so there is
858 // no need to write it
860 else
862 const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
863 const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
865 if(bXEqual && bYEqual)
867 // point is a double point; do not export at all
869 else if(bXEqual)
871 // export as vertical line
872 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('V', 'v', bUseRelativeCoordinates));
874 if(aLastSVGCommand != aCommand)
876 aResult.append(aCommand);
877 aLastSVGCommand = aCommand;
880 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
881 aCurrentSVGPosition = aEdgeEnd;
883 else if(bYEqual)
885 // export as horizontal line
886 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('H', 'h', bUseRelativeCoordinates));
888 if(aLastSVGCommand != aCommand)
890 aResult.append(aCommand);
891 aLastSVGCommand = aCommand;
894 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
895 aCurrentSVGPosition = aEdgeEnd;
897 else
899 // export as line
900 const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinates));
902 if(aLastSVGCommand != aCommand)
904 aResult.append(aCommand);
905 aLastSVGCommand = aCommand;
908 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
909 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
910 aCurrentSVGPosition = aEdgeEnd;
915 // prepare edge start for next loop step
916 aEdgeStart = aEdgeEnd;
919 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
920 if(aPolygon.isClosed())
922 aResult.append(::basegfx::internal::lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
925 if(!bHandleRelativeNextPointCompatible)
927 // SVG defines that "the next subpath starts at the same initial point as the current subpath",
928 // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
929 aCurrentSVGPosition = aPolygon.getB2DPoint(0);
934 return aResult.makeStringAndClear();
939 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */