Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
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 :
26 : #include <osl/diagnose.h>
27 : #include <rtl/ustring.hxx>
28 : #include <rtl/math.hxx>
29 : #include <stringconversiontools.hxx>
30 :
31 : namespace basegfx
32 : {
33 : namespace tools
34 : {
35 0 : bool PointIndex::operator<(const PointIndex& rComp) const
36 : {
37 0 : if(rComp.getPolygonIndex() == getPolygonIndex())
38 : {
39 0 : return rComp.getPointIndex() < getPointIndex();
40 : }
41 :
42 0 : return rComp.getPolygonIndex() < getPolygonIndex();
43 : }
44 :
45 2763 : bool importFromSvgD(
46 : B2DPolyPolygon& o_rPolyPolygon,
47 : const OUString& rSvgDStatement,
48 : bool bHandleRelativeNextPointCompatible,
49 : PointIndexSet* pHelpPointIndexSet)
50 : {
51 2763 : o_rPolyPolygon.clear();
52 2763 : const sal_Int32 nLen(rSvgDStatement.getLength());
53 2763 : sal_Int32 nPos(0);
54 2763 : double nLastX( 0.0 );
55 2763 : double nLastY( 0.0 );
56 2763 : B2DPolygon aCurrPoly;
57 :
58 : // skip initial whitespace
59 2763 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
60 :
61 54079 : while(nPos < nLen)
62 : {
63 48553 : bool bRelative(false);
64 48553 : const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
65 :
66 48553 : if(o_rPolyPolygon.count() && !aCurrPoly.count() && !('m' == aCurrChar || 'M' == aCurrChar))
67 : {
68 : // we have a new sub-polygon starting, but without a 'moveto' command.
69 : // this requires to add the current point as start point to the polygon
70 : // (see SVG1.1 8.3.3 The "closepath" command)
71 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
72 : }
73 :
74 48553 : switch(aCurrChar)
75 : {
76 : case 'z' :
77 : case 'Z' :
78 : {
79 : // consume CurrChar and whitespace
80 4469 : nPos++;
81 4469 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
82 :
83 : // create closed polygon and reset import values
84 4469 : if(aCurrPoly.count())
85 : {
86 4469 : if(!bHandleRelativeNextPointCompatible)
87 : {
88 : // SVG defines that "the next subpath starts at the
89 : // same initial point as the current subpath", so set the
90 : // current point if we do not need to be compatible
91 4463 : nLastX = aCurrPoly.getB2DPoint(0).getX();
92 4463 : nLastY = aCurrPoly.getB2DPoint(0).getY();
93 : }
94 :
95 4469 : aCurrPoly.setClosed(true);
96 4469 : o_rPolyPolygon.append(aCurrPoly);
97 4469 : aCurrPoly.clear();
98 : }
99 :
100 4469 : break;
101 : }
102 :
103 : case 'm' :
104 : case 'M' :
105 : {
106 : // create non-closed polygon and reset import values
107 4680 : if(aCurrPoly.count())
108 : {
109 44 : o_rPolyPolygon.append(aCurrPoly);
110 44 : aCurrPoly.clear();
111 : }
112 :
113 : // FALLTHROUGH intended to add coordinate data as 1st point of new polygon
114 : }
115 : case 'l' :
116 : case 'L' :
117 : {
118 20326 : if('m' == aCurrChar || 'l' == aCurrChar)
119 : {
120 10561 : bRelative = true;
121 : }
122 :
123 : // consume CurrChar and whitespace
124 20326 : nPos++;
125 20326 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
126 :
127 61834 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
128 : {
129 : double nX, nY;
130 :
131 21182 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
132 21182 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
133 :
134 21182 : if(bRelative)
135 : {
136 11397 : nX += nLastX;
137 11397 : nY += nLastY;
138 : }
139 :
140 : // set last position
141 21182 : nLastX = nX;
142 21182 : nLastY = nY;
143 :
144 : // add point
145 21182 : aCurrPoly.append(B2DPoint(nX, nY));
146 : }
147 20326 : break;
148 : }
149 :
150 : case 'h' :
151 : {
152 3296 : bRelative = true;
153 : // FALLTHROUGH intended
154 : }
155 : case 'H' :
156 : {
157 4581 : nPos++;
158 4581 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
159 :
160 13798 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
161 : {
162 4636 : double nX, nY(nLastY);
163 :
164 4636 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
165 :
166 4636 : if(bRelative)
167 : {
168 3351 : nX += nLastX;
169 : }
170 :
171 : // set last position
172 4636 : nLastX = nX;
173 :
174 : // add point
175 4636 : aCurrPoly.append(B2DPoint(nX, nY));
176 : }
177 4581 : break;
178 : }
179 :
180 : case 'v' :
181 : {
182 2497 : bRelative = true;
183 : // FALLTHROUGH intended
184 : }
185 : case 'V' :
186 : {
187 3581 : nPos++;
188 3581 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
189 :
190 10788 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
191 : {
192 3626 : double nX(nLastX), nY;
193 :
194 3626 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
195 :
196 3626 : if(bRelative)
197 : {
198 2542 : nY += nLastY;
199 : }
200 :
201 : // set last position
202 3626 : nLastY = nY;
203 :
204 : // add point
205 3626 : aCurrPoly.append(B2DPoint(nX, nY));
206 : }
207 3581 : break;
208 : }
209 :
210 : case 's' :
211 : {
212 766 : bRelative = true;
213 : // FALLTHROUGH intended
214 : }
215 : case 'S' :
216 : {
217 888 : nPos++;
218 888 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
219 :
220 2802 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
221 : {
222 : double nX, nY;
223 : double nX2, nY2;
224 :
225 1026 : if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
226 1026 : if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
227 1026 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
228 1026 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
229 :
230 1026 : if(bRelative)
231 : {
232 904 : nX2 += nLastX;
233 904 : nY2 += nLastY;
234 904 : nX += nLastX;
235 904 : nY += nLastY;
236 : }
237 :
238 : // ensure existence of start point
239 1026 : if(!aCurrPoly.count())
240 : {
241 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
242 : }
243 :
244 : // get first control point. It's the reflection of the PrevControlPoint
245 : // of the last point. If not existent, use current point (see SVG)
246 1026 : B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
247 1026 : const sal_uInt32 nIndex(aCurrPoly.count() - 1);
248 :
249 1026 : if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
250 : {
251 822 : const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
252 1644 : const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
253 :
254 : // use mirrored previous control point
255 822 : aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
256 1644 : aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
257 : }
258 :
259 : // append curved edge
260 1026 : aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
261 :
262 : // set last position
263 1026 : nLastX = nX;
264 1026 : nLastY = nY;
265 1026 : }
266 888 : break;
267 : }
268 :
269 : case 'c' :
270 : {
271 12496 : bRelative = true;
272 : // FALLTHROUGH intended
273 : }
274 : case 'C' :
275 : {
276 14575 : nPos++;
277 14575 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
278 :
279 44740 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
280 : {
281 : double nX, nY;
282 : double nX1, nY1;
283 : double nX2, nY2;
284 :
285 15590 : if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
286 15590 : if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
287 15590 : if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
288 15590 : if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
289 15590 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
290 15590 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
291 :
292 15590 : if(bRelative)
293 : {
294 13499 : nX1 += nLastX;
295 13499 : nY1 += nLastY;
296 13499 : nX2 += nLastX;
297 13499 : nY2 += nLastY;
298 13499 : nX += nLastX;
299 13499 : nY += nLastY;
300 : }
301 :
302 : // ensure existence of start point
303 15590 : if(!aCurrPoly.count())
304 : {
305 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
306 : }
307 :
308 : // append curved edge
309 15590 : aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
310 :
311 : // set last position
312 15590 : nLastX = nX;
313 15590 : nLastY = nY;
314 : }
315 14575 : break;
316 : }
317 :
318 : // #100617# quadratic beziers are imported as cubic ones
319 : case 'q' :
320 : {
321 10 : bRelative = true;
322 : // FALLTHROUGH intended
323 : }
324 : case 'Q' :
325 : {
326 10 : nPos++;
327 10 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
328 :
329 30 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
330 : {
331 : double nX, nY;
332 : double nX1, nY1;
333 :
334 10 : if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
335 10 : if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
336 10 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
337 10 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
338 :
339 10 : if(bRelative)
340 : {
341 10 : nX1 += nLastX;
342 10 : nY1 += nLastY;
343 10 : nX += nLastX;
344 10 : nY += nLastY;
345 : }
346 :
347 : // calculate the cubic bezier coefficients from the quadratic ones
348 10 : const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
349 10 : const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
350 10 : const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
351 10 : const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
352 :
353 : // ensure existence of start point
354 10 : if(!aCurrPoly.count())
355 : {
356 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
357 : }
358 :
359 : // append curved edge
360 10 : aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
361 :
362 : // set last position
363 10 : nLastX = nX;
364 10 : nLastY = nY;
365 : }
366 10 : break;
367 : }
368 :
369 : // #100617# relative quadratic beziers are imported as cubic
370 : case 't' :
371 : {
372 0 : bRelative = true;
373 : // FALLTHROUGH intended
374 : }
375 : case 'T' :
376 : {
377 0 : nPos++;
378 0 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
379 :
380 0 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
381 : {
382 : double nX, nY;
383 :
384 0 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
385 0 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
386 :
387 0 : if(bRelative)
388 : {
389 0 : nX += nLastX;
390 0 : nY += nLastY;
391 : }
392 :
393 : // ensure existence of start point
394 0 : if(!aCurrPoly.count())
395 : {
396 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
397 : }
398 :
399 : // get first control point. It's the reflection of the PrevControlPoint
400 : // of the last point. If not existent, use current point (see SVG)
401 0 : B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
402 0 : const sal_uInt32 nIndex(aCurrPoly.count() - 1);
403 0 : const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
404 :
405 0 : if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
406 : {
407 0 : const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
408 :
409 : // use mirrored previous control point
410 0 : aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
411 0 : aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
412 : }
413 :
414 0 : if(!aPrevControl.equal(aPrevPoint))
415 : {
416 : // there is a prev control point, and we have the already mirrored one
417 : // in aPrevControl. We also need the quadratic control point for this
418 : // new quadratic segment to calculate the 2nd cubic control point
419 : const B2DPoint aQuadControlPoint(
420 0 : ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
421 0 : ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
422 :
423 : // calculate the cubic bezier coefficients from the quadratic ones.
424 0 : const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
425 0 : const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
426 :
427 : // append curved edge, use mirrored cubic control point directly
428 0 : aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
429 : }
430 : else
431 : {
432 : // when no previous control, SVG says to use current point -> straight line.
433 : // Just add end point
434 0 : aCurrPoly.append(B2DPoint(nX, nY));
435 : }
436 :
437 : // set last position
438 0 : nLastX = nX;
439 0 : nLastY = nY;
440 0 : }
441 0 : break;
442 : }
443 :
444 : case 'a' :
445 : {
446 0 : bRelative = true;
447 : // FALLTHROUGH intended
448 : }
449 : case 'A' :
450 : {
451 123 : nPos++;
452 123 : basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
453 :
454 369 : while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
455 : {
456 : double nX, nY;
457 : double fRX, fRY, fPhi;
458 : sal_Int32 bLargeArcFlag, bSweepFlag;
459 :
460 123 : if(!basegfx::internal::importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
461 123 : if(!basegfx::internal::importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
462 123 : if(!basegfx::internal::importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
463 123 : if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
464 123 : if(!basegfx::internal::importFlagAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
465 123 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
466 123 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
467 :
468 123 : if(bRelative)
469 : {
470 0 : nX += nLastX;
471 0 : nY += nLastY;
472 : }
473 :
474 123 : if( nX == nLastX && nY == nLastY )
475 0 : continue; // start==end -> skip according to SVG spec
476 :
477 123 : if( fRX == 0.0 || fRY == 0.0 )
478 : {
479 : // straight line segment according to SVG spec
480 0 : aCurrPoly.append(B2DPoint(nX, nY));
481 : }
482 : else
483 : {
484 : // normalize according to SVG spec
485 123 : fRX=fabs(fRX); fRY=fabs(fRY);
486 :
487 : // from the SVG spec, appendix F.6.4
488 :
489 : // |x1'| |cos phi sin phi| |(x1 - x2)/2|
490 : // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
491 123 : const B2DPoint p1(nLastX, nLastY);
492 246 : const B2DPoint p2(nX, nY);
493 246 : B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180));
494 :
495 246 : const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
496 :
497 : // ______________________________________ rx y1'
498 : // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
499 : // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
500 : // rx
501 : // chose + if f_A != f_S
502 : // chose - if f_A = f_S
503 246 : B2DPoint aCenter_prime;
504 : const double fRadicant(
505 123 : (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
506 123 : (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
507 123 : if( fRadicant < 0.0 )
508 : {
509 : // no solution - according to SVG
510 : // spec, scale up ellipse
511 : // uniformly such that it passes
512 : // through end points (denominator
513 : // of radicant solved for fRY,
514 : // with s=fRX/fRY)
515 16 : const double fRatio(fRX/fRY);
516 : const double fRadicant2(
517 16 : p1_prime.getY()*p1_prime.getY() +
518 16 : p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
519 16 : if( fRadicant2 < 0.0 )
520 : {
521 : // only trivial solution, one
522 : // of the axes 0 -> straight
523 : // line segment according to
524 : // SVG spec
525 0 : aCurrPoly.append(B2DPoint(nX, nY));
526 0 : continue;
527 : }
528 :
529 16 : fRY=sqrt(fRadicant2);
530 16 : fRX=fRatio*fRY;
531 :
532 : // keep center_prime forced to (0,0)
533 : }
534 : else
535 : {
536 : const double fFactor(
537 107 : (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
538 107 : sqrt(fRadicant));
539 :
540 : // actually calculate center_prime
541 321 : aCenter_prime = B2DPoint(
542 107 : fFactor*fRX*p1_prime.getY()/fRY,
543 214 : -fFactor*fRY*p1_prime.getX()/fRX);
544 : }
545 :
546 : // + u - v
547 : // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
548 : // - ||u|| ||v||
549 :
550 : // 1 | (x1' - cx')/rx |
551 : // theta1 = angle(( ), | | )
552 : // 0 | (y1' - cy')/ry |
553 246 : const B2DPoint aRadii(fRX,fRY);
554 : double fTheta1(
555 : B2DVector(1.0,0.0).angle(
556 123 : (p1_prime-aCenter_prime)/aRadii));
557 :
558 : // |1| | (-x1' - cx')/rx |
559 : // theta2 = angle( | | , | | )
560 : // |0| | (-y1' - cy')/ry |
561 : double fTheta2(
562 : B2DVector(1.0,0.0).angle(
563 123 : (-p1_prime-aCenter_prime)/aRadii));
564 :
565 : // map both angles to [0,2pi)
566 123 : fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
567 123 : fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
568 :
569 : // make sure the large arc is taken
570 : // (since
571 : // createPolygonFromEllipseSegment()
572 : // normalizes to e.g. cw arc)
573 123 : if( !bSweepFlag )
574 53 : std::swap(fTheta1,fTheta2);
575 :
576 : // finally, create bezier polygon from this
577 : B2DPolygon aSegment(
578 : tools::createPolygonFromUnitEllipseSegment(
579 246 : fTheta1, fTheta2 ));
580 :
581 : // transform ellipse by rotation & move to final center
582 123 : aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY);
583 : aTransform.translate(aCenter_prime.getX(),
584 123 : aCenter_prime.getY());
585 123 : aTransform.rotate(fPhi*M_PI/180);
586 246 : const B2DPoint aOffset((p1+p2)/2.0);
587 : aTransform.translate(aOffset.getX(),
588 123 : aOffset.getY());
589 123 : aSegment.transform(aTransform);
590 :
591 : // createPolygonFromEllipseSegment()
592 : // always creates arcs that are
593 : // positively oriented - flip polygon
594 : // if we swapped angles above
595 123 : if( !bSweepFlag )
596 53 : aSegment.flip();
597 :
598 : // remember PointIndex of evtl. added pure helper points
599 123 : sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
600 123 : aCurrPoly.append(aSegment);
601 :
602 : // if asked for, mark pure helper points by adding them to the index list of
603 : // helper points
604 123 : if(pHelpPointIndexSet && aCurrPoly.count() > 1)
605 : {
606 0 : const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
607 :
608 0 : for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
609 : {
610 0 : pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
611 : }
612 123 : }
613 : }
614 :
615 : // set last position
616 123 : nLastX = nX;
617 123 : nLastY = nY;
618 : }
619 123 : break;
620 : }
621 :
622 : default:
623 : {
624 : OSL_FAIL("importFromSvgD(): skipping tags in svg:d element (unknown)!");
625 : OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar);
626 0 : ++nPos;
627 0 : break;
628 : }
629 : }
630 : }
631 :
632 : // if there is polygon data, create non-closed polygon
633 2763 : if(aCurrPoly.count())
634 : {
635 167 : o_rPolyPolygon.append(aCurrPoly);
636 : }
637 :
638 2763 : return true;
639 : }
640 :
641 720 : bool importFromSvgPoints( B2DPolygon& o_rPoly,
642 : const OUString& rSvgPointsAttribute )
643 : {
644 720 : o_rPoly.clear();
645 720 : const sal_Int32 nLen(rSvgPointsAttribute.getLength());
646 720 : sal_Int32 nPos(0);
647 : double nX, nY;
648 :
649 : // skip initial whitespace
650 720 : basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
651 :
652 5530 : while(nPos < nLen)
653 : {
654 4090 : if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
655 4090 : if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
656 :
657 : // add point
658 4090 : o_rPoly.append(B2DPoint(nX, nY));
659 :
660 : // skip to next number, or finish
661 4090 : basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
662 : }
663 :
664 720 : return true;
665 : }
666 :
667 4 : OUString exportToSvgPoints( const B2DPolygon& rPoly )
668 : {
669 : OSL_ENSURE(!rPoly.areControlPointsUsed(), "exportToSvgPoints: Only non-bezier polygons allowed (!)");
670 4 : const sal_uInt32 nPointCount(rPoly.count());
671 4 : OUStringBuffer aResult;
672 :
673 20 : for(sal_uInt32 a(0); a < nPointCount; a++)
674 : {
675 16 : const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
676 :
677 16 : if(a)
678 : {
679 12 : aResult.append(' ');
680 : }
681 :
682 16 : aResult.append(aPoint.getX());
683 16 : aResult.append(',');
684 16 : aResult.append(aPoint.getY());
685 16 : }
686 :
687 4 : return aResult.makeStringAndClear();
688 : }
689 :
690 95 : OUString exportToSvgD(
691 : const B2DPolyPolygon& rPolyPolygon,
692 : bool bUseRelativeCoordinates,
693 : bool bDetectQuadraticBeziers,
694 : bool bHandleRelativeNextPointCompatible)
695 : {
696 95 : const sal_uInt32 nCount(rPolyPolygon.count());
697 95 : OUStringBuffer aResult;
698 190 : B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
699 :
700 323 : for(sal_uInt32 i(0); i < nCount; i++)
701 : {
702 228 : const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
703 228 : const sal_uInt32 nPointCount(aPolygon.count());
704 :
705 228 : if(nPointCount)
706 : {
707 228 : const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
708 228 : const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
709 228 : sal_Unicode aLastSVGCommand(' '); // last SVG command char
710 456 : B2DPoint aLeft, aRight; // for quadratic bezier test
711 :
712 : // handle polygon start point
713 456 : B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
714 228 : bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
715 :
716 228 : if(bHandleRelativeNextPointCompatible)
717 : {
718 : // To get around the error that the start point for the next polygon is the
719 : // start point of the current one (and not the last as it was handled up to now)
720 : // do force to write an absolute 'M' command as start for the next polygon
721 19 : bUseRelativeCoordinatesForFirstPoint = false;
722 : }
723 :
724 : // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
725 228 : aResult.append(basegfx::internal::getCommand('M', 'm', bUseRelativeCoordinatesForFirstPoint));
726 228 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint);
727 228 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint);
728 228 : aLastSVGCommand = basegfx::internal::getCommand('L', 'l', bUseRelativeCoordinatesForFirstPoint);
729 228 : aCurrentSVGPosition = aEdgeStart;
730 :
731 2014 : for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
732 : {
733 : // prepare access to next point
734 1786 : const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
735 1786 : const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
736 :
737 : // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
738 : const bool bEdgeIsBezier(bPolyUsesControlPoints
739 1786 : && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
740 :
741 1786 : if(bEdgeIsBezier)
742 : {
743 : // handle bezier edge
744 141 : const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
745 282 : const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
746 141 : bool bIsQuadraticBezier(false);
747 :
748 : // check continuity at current edge's start point. For SVG, do NOT use an
749 : // existing continuity since no 'S' or 's' statement should be written. At
750 : // import, that 'previous' control vector is not available. SVG documentation
751 : // says for interpretation:
752 :
753 : // "(If there is no previous command or if the previous command was
754 : // not an C, c, S or s, assume the first control point is coincident
755 : // with the current point.)"
756 :
757 : // That's what is done from our import, so avoid exporting it as first statement
758 : // is necessary.
759 : const bool bSymmetricAtEdgeStart(
760 : 0 != nIndex
761 141 : && B2VectorContinuity::C2 == aPolygon.getContinuityInPoint(nIndex));
762 :
763 141 : if(bDetectQuadraticBeziers)
764 : {
765 : // check for quadratic beziers - that's
766 : // the case if both control points are in
767 : // the same place when they are prolonged
768 : // to the common quadratic control point
769 :
770 : // Left: P = (3P1 - P0) / 2
771 : // Right: P = (3P2 - P3) / 2
772 141 : aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
773 141 : aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
774 141 : bIsQuadraticBezier = aLeft.equal(aRight);
775 : }
776 :
777 141 : if(bIsQuadraticBezier)
778 : {
779 : // approximately equal, export as quadratic bezier
780 10 : if(bSymmetricAtEdgeStart)
781 : {
782 0 : const sal_Unicode aCommand(basegfx::internal::getCommand('T', 't', bUseRelativeCoordinates));
783 :
784 0 : if(aLastSVGCommand != aCommand)
785 : {
786 0 : aResult.append(aCommand);
787 0 : aLastSVGCommand = aCommand;
788 : }
789 :
790 0 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
791 0 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
792 0 : aLastSVGCommand = aCommand;
793 0 : aCurrentSVGPosition = aEdgeEnd;
794 : }
795 : else
796 : {
797 10 : const sal_Unicode aCommand(basegfx::internal::getCommand('Q', 'q', bUseRelativeCoordinates));
798 :
799 10 : if(aLastSVGCommand != aCommand)
800 : {
801 10 : aResult.append(aCommand);
802 10 : aLastSVGCommand = aCommand;
803 : }
804 :
805 10 : basegfx::internal::putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
806 10 : basegfx::internal::putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
807 10 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
808 10 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
809 10 : aLastSVGCommand = aCommand;
810 10 : aCurrentSVGPosition = aEdgeEnd;
811 : }
812 : }
813 : else
814 : {
815 : // export as cubic bezier
816 131 : if(bSymmetricAtEdgeStart)
817 : {
818 21 : const sal_Unicode aCommand(basegfx::internal::getCommand('S', 's', bUseRelativeCoordinates));
819 :
820 21 : if(aLastSVGCommand != aCommand)
821 : {
822 17 : aResult.append(aCommand);
823 17 : aLastSVGCommand = aCommand;
824 : }
825 :
826 21 : basegfx::internal::putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
827 21 : basegfx::internal::putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
828 21 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
829 21 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
830 21 : aLastSVGCommand = aCommand;
831 21 : aCurrentSVGPosition = aEdgeEnd;
832 : }
833 : else
834 : {
835 110 : const sal_Unicode aCommand(basegfx::internal::getCommand('C', 'c', bUseRelativeCoordinates));
836 :
837 110 : if(aLastSVGCommand != aCommand)
838 : {
839 63 : aResult.append(aCommand);
840 63 : aLastSVGCommand = aCommand;
841 : }
842 :
843 110 : basegfx::internal::putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
844 110 : basegfx::internal::putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
845 110 : basegfx::internal::putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
846 110 : basegfx::internal::putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
847 110 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
848 110 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
849 110 : aLastSVGCommand = aCommand;
850 110 : aCurrentSVGPosition = aEdgeEnd;
851 : }
852 141 : }
853 : }
854 : else
855 : {
856 : // straight edge
857 1645 : if(0 == nNextIndex)
858 : {
859 : // it's a closed polygon's last edge and it's not a bezier edge, so there is
860 : // no need to write it
861 : }
862 : else
863 : {
864 1465 : const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
865 1465 : const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
866 :
867 1465 : if(bXEqual && bYEqual)
868 : {
869 : // point is a double point; do not export at all
870 : }
871 1433 : else if(bXEqual)
872 : {
873 : // export as vertical line
874 469 : const sal_Unicode aCommand(basegfx::internal::getCommand('V', 'v', bUseRelativeCoordinates));
875 :
876 469 : if(aLastSVGCommand != aCommand)
877 : {
878 448 : aResult.append(aCommand);
879 448 : aLastSVGCommand = aCommand;
880 : }
881 :
882 469 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
883 469 : aCurrentSVGPosition = aEdgeEnd;
884 : }
885 964 : else if(bYEqual)
886 : {
887 : // export as horizontal line
888 445 : const sal_Unicode aCommand(basegfx::internal::getCommand('H', 'h', bUseRelativeCoordinates));
889 :
890 445 : if(aLastSVGCommand != aCommand)
891 : {
892 415 : aResult.append(aCommand);
893 415 : aLastSVGCommand = aCommand;
894 : }
895 :
896 445 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
897 445 : aCurrentSVGPosition = aEdgeEnd;
898 : }
899 : else
900 : {
901 : // export as line
902 519 : const sal_Unicode aCommand(basegfx::internal::getCommand('L', 'l', bUseRelativeCoordinates));
903 :
904 519 : if(aLastSVGCommand != aCommand)
905 : {
906 27 : aResult.append(aCommand);
907 27 : aLastSVGCommand = aCommand;
908 : }
909 :
910 519 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
911 519 : basegfx::internal::putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
912 519 : aCurrentSVGPosition = aEdgeEnd;
913 : }
914 : }
915 : }
916 :
917 : // prepare edge start for next loop step
918 1786 : aEdgeStart = aEdgeEnd;
919 1786 : }
920 :
921 : // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
922 228 : if(aPolygon.isClosed())
923 : {
924 180 : aResult.append(basegfx::internal::getCommand('Z', 'z', bUseRelativeCoordinates));
925 : }
926 :
927 228 : if(!bHandleRelativeNextPointCompatible)
928 : {
929 : // SVG defines that "the next subpath starts at the same initial point as the current subpath",
930 : // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
931 209 : aCurrentSVGPosition = aPolygon.getB2DPoint(0);
932 228 : }
933 : }
934 228 : }
935 :
936 190 : return aResult.makeStringAndClear();
937 : }
938 : }
939 : }
940 :
941 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|