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