109dbbe93SAndrew Rist /************************************************************** 2cdf0e10cSrcweir * 309dbbe93SAndrew Rist * Licensed to the Apache Software Foundation (ASF) under one 409dbbe93SAndrew Rist * or more contributor license agreements. See the NOTICE file 509dbbe93SAndrew Rist * distributed with this work for additional information 609dbbe93SAndrew Rist * regarding copyright ownership. The ASF licenses this file 709dbbe93SAndrew Rist * to you under the Apache License, Version 2.0 (the 809dbbe93SAndrew Rist * "License"); you may not use this file except in compliance 909dbbe93SAndrew Rist * with the License. You may obtain a copy of the License at 1009dbbe93SAndrew Rist * 1109dbbe93SAndrew Rist * http://www.apache.org/licenses/LICENSE-2.0 1209dbbe93SAndrew Rist * 1309dbbe93SAndrew Rist * Unless required by applicable law or agreed to in writing, 1409dbbe93SAndrew Rist * software distributed under the License is distributed on an 1509dbbe93SAndrew Rist * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 1609dbbe93SAndrew Rist * KIND, either express or implied. See the License for the 1709dbbe93SAndrew Rist * specific language governing permissions and limitations 1809dbbe93SAndrew Rist * under the License. 1909dbbe93SAndrew Rist * 2009dbbe93SAndrew Rist *************************************************************/ 2109dbbe93SAndrew Rist 2209dbbe93SAndrew Rist 23cdf0e10cSrcweir 24cdf0e10cSrcweir // MARKER(update_precomp.py): autogen include statement, do not remove 25cdf0e10cSrcweir #include "precompiled_basegfx.hxx" 26cdf0e10cSrcweir 27cdf0e10cSrcweir #include <basegfx/polygon/b2dpolygontools.hxx> 28cdf0e10cSrcweir #include <basegfx/polygon/b2dpolypolygontools.hxx> 29cdf0e10cSrcweir #include <basegfx/polygon/b2dpolygontools.hxx> 30cdf0e10cSrcweir #include <basegfx/polygon/b2dpolypolygon.hxx> 31cdf0e10cSrcweir #include <basegfx/matrix/b2dhommatrix.hxx> 32cdf0e10cSrcweir #include <basegfx/matrix/b2dhommatrixtools.hxx> 33cdf0e10cSrcweir #include <rtl/ustring.hxx> 34cdf0e10cSrcweir #include <rtl/math.hxx> 351f882ec4SArmin Le Grand #include <stringconversiontools.hxx> 36cdf0e10cSrcweir 37cdf0e10cSrcweir namespace basegfx 38cdf0e10cSrcweir { 391f882ec4SArmin Le Grand namespace tools 401f882ec4SArmin Le Grand { operator <(const PointIndex & rComp) const411f882ec4SArmin Le Grand bool PointIndex::operator<(const PointIndex& rComp) const 42cdf0e10cSrcweir { 431f882ec4SArmin Le Grand if(rComp.getPolygonIndex() == getPolygonIndex()) 44cdf0e10cSrcweir { 451f882ec4SArmin Le Grand return rComp.getPointIndex() < getPointIndex(); 46cdf0e10cSrcweir } 47cdf0e10cSrcweir 481f882ec4SArmin Le Grand return rComp.getPolygonIndex() < getPolygonIndex(); 49cdf0e10cSrcweir } 50cdf0e10cSrcweir importFromSvgD(B2DPolyPolygon & o_rPolyPolygon,const::rtl::OUString & rSvgDStatement,bool bHandleRelativeNextPointCompatible,PointIndexSet * pHelpPointIndexSet)511f882ec4SArmin Le Grand bool importFromSvgD( 521f882ec4SArmin Le Grand B2DPolyPolygon& o_rPolyPolygon, 531f882ec4SArmin Le Grand const ::rtl::OUString& rSvgDStatement, 541f882ec4SArmin Le Grand bool bHandleRelativeNextPointCompatible, 551f882ec4SArmin Le Grand PointIndexSet* pHelpPointIndexSet) 56cdf0e10cSrcweir { 57cdf0e10cSrcweir o_rPolyPolygon.clear(); 58cdf0e10cSrcweir const sal_Int32 nLen(rSvgDStatement.getLength()); 59cdf0e10cSrcweir sal_Int32 nPos(0); 60cdf0e10cSrcweir double nLastX( 0.0 ); 61cdf0e10cSrcweir double nLastY( 0.0 ); 621f882ec4SArmin Le Grand B2DPolygon aCurrPoly; 63cdf0e10cSrcweir 641f882ec4SArmin Le Grand // skip initial whitespace 651f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 66cdf0e10cSrcweir 67cdf0e10cSrcweir while(nPos < nLen) 68cdf0e10cSrcweir { 69cdf0e10cSrcweir bool bRelative(false); 70cdf0e10cSrcweir const sal_Unicode aCurrChar(rSvgDStatement[nPos]); 71cdf0e10cSrcweir 721f882ec4SArmin Le Grand if(o_rPolyPolygon.count() && !aCurrPoly.count() && !('m' == aCurrChar || 'M' == aCurrChar)) 731f882ec4SArmin Le Grand { 741f882ec4SArmin Le Grand // we have a new sub-polygon starting, but without a 'moveto' command. 751f882ec4SArmin Le Grand // this requires to add the current point as start point to the polygon 761f882ec4SArmin Le Grand // (see SVG1.1 8.3.3 The "closepath" command) 771f882ec4SArmin Le Grand aCurrPoly.append(B2DPoint(nLastX, nLastY)); 781f882ec4SArmin Le Grand } 791f882ec4SArmin Le Grand 80cdf0e10cSrcweir switch(aCurrChar) 81cdf0e10cSrcweir { 82cdf0e10cSrcweir case 'z' : 83cdf0e10cSrcweir case 'Z' : 84cdf0e10cSrcweir { 851f882ec4SArmin Le Grand // consume CurrChar and whitespace 86cdf0e10cSrcweir nPos++; 871f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 881f882ec4SArmin Le Grand 891f882ec4SArmin Le Grand // create closed polygon and reset import values 901f882ec4SArmin Le Grand if(aCurrPoly.count()) 911f882ec4SArmin Le Grand { 921f882ec4SArmin Le Grand if(!bHandleRelativeNextPointCompatible) 931f882ec4SArmin Le Grand { 941f882ec4SArmin Le Grand // SVG defines that "the next subpath starts at the 951f882ec4SArmin Le Grand // same initial point as the current subpath", so set the 961f882ec4SArmin Le Grand // current point if we do not need to be compatible 971f882ec4SArmin Le Grand nLastX = aCurrPoly.getB2DPoint(0).getX(); 981f882ec4SArmin Le Grand nLastY = aCurrPoly.getB2DPoint(0).getY(); 991f882ec4SArmin Le Grand } 1001f882ec4SArmin Le Grand 1011f882ec4SArmin Le Grand aCurrPoly.setClosed(true); 1021f882ec4SArmin Le Grand o_rPolyPolygon.append(aCurrPoly); 1031f882ec4SArmin Le Grand aCurrPoly.clear(); 1041f882ec4SArmin Le Grand } 105cdf0e10cSrcweir 106cdf0e10cSrcweir break; 107cdf0e10cSrcweir } 1081f882ec4SArmin Le Grand 109cdf0e10cSrcweir case 'm' : 110cdf0e10cSrcweir case 'M' : 111cdf0e10cSrcweir { 1121f882ec4SArmin Le Grand // create non-closed polygon and reset import values 1131f882ec4SArmin Le Grand if(aCurrPoly.count()) 1141f882ec4SArmin Le Grand { 1151f882ec4SArmin Le Grand o_rPolyPolygon.append(aCurrPoly); 1161f882ec4SArmin Le Grand aCurrPoly.clear(); 1171f882ec4SArmin Le Grand } 1181f882ec4SArmin Le Grand 1191f882ec4SArmin Le Grand // FALLTHROUGH intended to add coordinate data as 1st point of new polygon 120cdf0e10cSrcweir } 121cdf0e10cSrcweir case 'l' : 122cdf0e10cSrcweir case 'L' : 123cdf0e10cSrcweir { 124cdf0e10cSrcweir if('m' == aCurrChar || 'l' == aCurrChar) 125cdf0e10cSrcweir { 1261f882ec4SArmin Le Grand bRelative = true; 127cdf0e10cSrcweir } 128cdf0e10cSrcweir 1291f882ec4SArmin Le Grand // consume CurrChar and whitespace 130cdf0e10cSrcweir nPos++; 1311f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 132cdf0e10cSrcweir 1331f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 134cdf0e10cSrcweir { 135cdf0e10cSrcweir double nX, nY; 136cdf0e10cSrcweir 1371f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 1381f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 139cdf0e10cSrcweir 140cdf0e10cSrcweir if(bRelative) 141cdf0e10cSrcweir { 142cdf0e10cSrcweir nX += nLastX; 143cdf0e10cSrcweir nY += nLastY; 144cdf0e10cSrcweir } 145cdf0e10cSrcweir 146cdf0e10cSrcweir // set last position 147cdf0e10cSrcweir nLastX = nX; 148cdf0e10cSrcweir nLastY = nY; 1491f882ec4SArmin Le Grand 150cdf0e10cSrcweir // add point 151cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 152cdf0e10cSrcweir } 153cdf0e10cSrcweir break; 154cdf0e10cSrcweir } 155cdf0e10cSrcweir 156cdf0e10cSrcweir case 'h' : 157cdf0e10cSrcweir { 158cdf0e10cSrcweir bRelative = true; 159cdf0e10cSrcweir // FALLTHROUGH intended 160cdf0e10cSrcweir } 161cdf0e10cSrcweir case 'H' : 162cdf0e10cSrcweir { 163cdf0e10cSrcweir nPos++; 1641f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 165cdf0e10cSrcweir 1661f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 167cdf0e10cSrcweir { 168cdf0e10cSrcweir double nX, nY(nLastY); 169cdf0e10cSrcweir 1701f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 171cdf0e10cSrcweir 172cdf0e10cSrcweir if(bRelative) 1731f882ec4SArmin Le Grand { 174cdf0e10cSrcweir nX += nLastX; 1751f882ec4SArmin Le Grand } 176cdf0e10cSrcweir 177cdf0e10cSrcweir // set last position 178cdf0e10cSrcweir nLastX = nX; 1791f882ec4SArmin Le Grand 180cdf0e10cSrcweir // add point 181cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 182cdf0e10cSrcweir } 183cdf0e10cSrcweir break; 184cdf0e10cSrcweir } 1851f882ec4SArmin Le Grand 186cdf0e10cSrcweir case 'v' : 187cdf0e10cSrcweir { 188cdf0e10cSrcweir bRelative = true; 189cdf0e10cSrcweir // FALLTHROUGH intended 190cdf0e10cSrcweir } 191cdf0e10cSrcweir case 'V' : 192cdf0e10cSrcweir { 193cdf0e10cSrcweir nPos++; 1941f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 195cdf0e10cSrcweir 1961f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 197cdf0e10cSrcweir { 198cdf0e10cSrcweir double nX(nLastX), nY; 199cdf0e10cSrcweir 2001f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 201cdf0e10cSrcweir 202cdf0e10cSrcweir if(bRelative) 2031f882ec4SArmin Le Grand { 204cdf0e10cSrcweir nY += nLastY; 2051f882ec4SArmin Le Grand } 206cdf0e10cSrcweir 207cdf0e10cSrcweir // set last position 208cdf0e10cSrcweir nLastY = nY; 2091f882ec4SArmin Le Grand 210cdf0e10cSrcweir // add point 211cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 212cdf0e10cSrcweir } 213cdf0e10cSrcweir break; 214cdf0e10cSrcweir } 2151f882ec4SArmin Le Grand 216cdf0e10cSrcweir case 's' : 217cdf0e10cSrcweir { 218cdf0e10cSrcweir bRelative = true; 219cdf0e10cSrcweir // FALLTHROUGH intended 220cdf0e10cSrcweir } 221cdf0e10cSrcweir case 'S' : 222cdf0e10cSrcweir { 223cdf0e10cSrcweir nPos++; 2241f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 225cdf0e10cSrcweir 2261f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 227cdf0e10cSrcweir { 228cdf0e10cSrcweir double nX, nY; 229cdf0e10cSrcweir double nX2, nY2; 230cdf0e10cSrcweir 2311f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; 2321f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; 2331f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 2341f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 235cdf0e10cSrcweir 236cdf0e10cSrcweir if(bRelative) 237cdf0e10cSrcweir { 238cdf0e10cSrcweir nX2 += nLastX; 239cdf0e10cSrcweir nY2 += nLastY; 240cdf0e10cSrcweir nX += nLastX; 241cdf0e10cSrcweir nY += nLastY; 242cdf0e10cSrcweir } 243cdf0e10cSrcweir 244*07a3d7f1SPedro Giffuni // ensure existence of start point 2451f882ec4SArmin Le Grand if(!aCurrPoly.count()) 2461f882ec4SArmin Le Grand { 247cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 2481f882ec4SArmin Le Grand } 249cdf0e10cSrcweir 2501f882ec4SArmin Le Grand // get first control point. It's the reflection of the PrevControlPoint 2511f882ec4SArmin Le Grand // of the last point. If not existent, use current point (see SVG) 2521f882ec4SArmin Le Grand B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); 2531f882ec4SArmin Le Grand const sal_uInt32 nIndex(aCurrPoly.count() - 1); 254cdf0e10cSrcweir 2551f882ec4SArmin Le Grand if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) 2561f882ec4SArmin Le Grand { 2571f882ec4SArmin Le Grand const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); 2581f882ec4SArmin Le Grand const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); 259cdf0e10cSrcweir 2601f882ec4SArmin Le Grand // use mirrored previous control point 2611f882ec4SArmin Le Grand aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); 2621f882ec4SArmin Le Grand aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); 2631f882ec4SArmin Le Grand } 264cdf0e10cSrcweir 2651f882ec4SArmin Le Grand // append curved edge 2661f882ec4SArmin Le Grand aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY)); 267cdf0e10cSrcweir 268cdf0e10cSrcweir // set last position 269cdf0e10cSrcweir nLastX = nX; 270cdf0e10cSrcweir nLastY = nY; 271cdf0e10cSrcweir } 272cdf0e10cSrcweir break; 273cdf0e10cSrcweir } 2741f882ec4SArmin Le Grand 275cdf0e10cSrcweir case 'c' : 276cdf0e10cSrcweir { 277cdf0e10cSrcweir bRelative = true; 278cdf0e10cSrcweir // FALLTHROUGH intended 279cdf0e10cSrcweir } 280cdf0e10cSrcweir case 'C' : 281cdf0e10cSrcweir { 282cdf0e10cSrcweir nPos++; 2831f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 284cdf0e10cSrcweir 2851f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 286cdf0e10cSrcweir { 287cdf0e10cSrcweir double nX, nY; 288cdf0e10cSrcweir double nX1, nY1; 289cdf0e10cSrcweir double nX2, nY2; 290cdf0e10cSrcweir 2911f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; 2921f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; 2931f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; 2941f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; 2951f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 2961f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 297cdf0e10cSrcweir 298cdf0e10cSrcweir if(bRelative) 299cdf0e10cSrcweir { 300cdf0e10cSrcweir nX1 += nLastX; 301cdf0e10cSrcweir nY1 += nLastY; 302cdf0e10cSrcweir nX2 += nLastX; 303cdf0e10cSrcweir nY2 += nLastY; 304cdf0e10cSrcweir nX += nLastX; 305cdf0e10cSrcweir nY += nLastY; 306cdf0e10cSrcweir } 307cdf0e10cSrcweir 308*07a3d7f1SPedro Giffuni // ensure existence of start point 3091f882ec4SArmin Le Grand if(!aCurrPoly.count()) 3101f882ec4SArmin Le Grand { 311cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 3121f882ec4SArmin Le Grand } 313cdf0e10cSrcweir 3141f882ec4SArmin Le Grand // append curved edge 3151f882ec4SArmin Le Grand aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY)); 316cdf0e10cSrcweir 317cdf0e10cSrcweir // set last position 318cdf0e10cSrcweir nLastX = nX; 319cdf0e10cSrcweir nLastY = nY; 320cdf0e10cSrcweir } 321cdf0e10cSrcweir break; 322cdf0e10cSrcweir } 3231f882ec4SArmin Le Grand 324cdf0e10cSrcweir // #100617# quadratic beziers are imported as cubic ones 325cdf0e10cSrcweir case 'q' : 326cdf0e10cSrcweir { 327cdf0e10cSrcweir bRelative = true; 328cdf0e10cSrcweir // FALLTHROUGH intended 329cdf0e10cSrcweir } 330cdf0e10cSrcweir case 'Q' : 331cdf0e10cSrcweir { 332cdf0e10cSrcweir nPos++; 3331f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 334cdf0e10cSrcweir 3351f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 336cdf0e10cSrcweir { 337cdf0e10cSrcweir double nX, nY; 338cdf0e10cSrcweir double nX1, nY1; 339cdf0e10cSrcweir 3401f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; 3411f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; 3421f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 3431f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 344cdf0e10cSrcweir 345cdf0e10cSrcweir if(bRelative) 346cdf0e10cSrcweir { 347cdf0e10cSrcweir nX1 += nLastX; 348cdf0e10cSrcweir nY1 += nLastY; 349cdf0e10cSrcweir nX += nLastX; 350cdf0e10cSrcweir nY += nLastY; 351cdf0e10cSrcweir } 352cdf0e10cSrcweir 353cdf0e10cSrcweir // calculate the cubic bezier coefficients from the quadratic ones 354cdf0e10cSrcweir const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0); 355cdf0e10cSrcweir const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0); 356cdf0e10cSrcweir const double nX2Prime((nX1 * 2.0 + nX) / 3.0); 357cdf0e10cSrcweir const double nY2Prime((nY1 * 2.0 + nY) / 3.0); 358cdf0e10cSrcweir 359*07a3d7f1SPedro Giffuni // ensure existence of start point 3601f882ec4SArmin Le Grand if(!aCurrPoly.count()) 3611f882ec4SArmin Le Grand { 362cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 3631f882ec4SArmin Le Grand } 364cdf0e10cSrcweir 3651f882ec4SArmin Le Grand // append curved edge 3661f882ec4SArmin Le Grand aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); 367cdf0e10cSrcweir 368cdf0e10cSrcweir // set last position 369cdf0e10cSrcweir nLastX = nX; 370cdf0e10cSrcweir nLastY = nY; 371cdf0e10cSrcweir } 372cdf0e10cSrcweir break; 373cdf0e10cSrcweir } 3741f882ec4SArmin Le Grand 375cdf0e10cSrcweir // #100617# relative quadratic beziers are imported as cubic 376cdf0e10cSrcweir case 't' : 377cdf0e10cSrcweir { 378cdf0e10cSrcweir bRelative = true; 379cdf0e10cSrcweir // FALLTHROUGH intended 380cdf0e10cSrcweir } 381cdf0e10cSrcweir case 'T' : 382cdf0e10cSrcweir { 383cdf0e10cSrcweir nPos++; 3841f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 385cdf0e10cSrcweir 3861f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 387cdf0e10cSrcweir { 388cdf0e10cSrcweir double nX, nY; 389cdf0e10cSrcweir 3901f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 3911f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 392cdf0e10cSrcweir 393cdf0e10cSrcweir if(bRelative) 394cdf0e10cSrcweir { 395cdf0e10cSrcweir nX += nLastX; 396cdf0e10cSrcweir nY += nLastY; 397cdf0e10cSrcweir } 398cdf0e10cSrcweir 399*07a3d7f1SPedro Giffuni // ensure existence of start point 4001f882ec4SArmin Le Grand if(!aCurrPoly.count()) 4011f882ec4SArmin Le Grand { 402cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 4031f882ec4SArmin Le Grand } 4041f882ec4SArmin Le Grand 4051f882ec4SArmin Le Grand // get first control point. It's the reflection of the PrevControlPoint 4061f882ec4SArmin Le Grand // of the last point. If not existent, use current point (see SVG) 4071f882ec4SArmin Le Grand B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); 4081f882ec4SArmin Le Grand const sal_uInt32 nIndex(aCurrPoly.count() - 1); 4091f882ec4SArmin Le Grand const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); 4101f882ec4SArmin Le Grand 4111f882ec4SArmin Le Grand if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) 4121f882ec4SArmin Le Grand { 4131f882ec4SArmin Le Grand const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); 4141f882ec4SArmin Le Grand 4151f882ec4SArmin Le Grand // use mirrored previous control point 4161f882ec4SArmin Le Grand aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); 4171f882ec4SArmin Le Grand aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); 4181f882ec4SArmin Le Grand } 4191f882ec4SArmin Le Grand 4201f882ec4SArmin Le Grand if(!aPrevControl.equal(aPrevPoint)) 4211f882ec4SArmin Le Grand { 4221f882ec4SArmin Le Grand // there is a prev control point, and we have the already mirrored one 4231f882ec4SArmin Le Grand // in aPrevControl. We also need the quadratic control point for this 4241f882ec4SArmin Le Grand // new quadratic segment to calculate the 2nd cubic control point 4251f882ec4SArmin Le Grand const B2DPoint aQuadControlPoint( 4261f882ec4SArmin Le Grand ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0, 4271f882ec4SArmin Le Grand ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0); 4281f882ec4SArmin Le Grand 4291f882ec4SArmin Le Grand // calculate the cubic bezier coefficients from the quadratic ones. 4301f882ec4SArmin Le Grand const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0); 4311f882ec4SArmin Le Grand const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0); 4321f882ec4SArmin Le Grand 4331f882ec4SArmin Le Grand // append curved edge, use mirrored cubic control point directly 4341f882ec4SArmin Le Grand aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); 4351f882ec4SArmin Le Grand } 4361f882ec4SArmin Le Grand else 4371f882ec4SArmin Le Grand { 4381f882ec4SArmin Le Grand // when no previous control, SVG says to use current point -> straight line. 4391f882ec4SArmin Le Grand // Just add end point 4401f882ec4SArmin Le Grand aCurrPoly.append(B2DPoint(nX, nY)); 4411f882ec4SArmin Le Grand } 442cdf0e10cSrcweir 443cdf0e10cSrcweir // set last position 444cdf0e10cSrcweir nLastX = nX; 445cdf0e10cSrcweir nLastY = nY; 446cdf0e10cSrcweir } 447cdf0e10cSrcweir break; 448cdf0e10cSrcweir } 449cdf0e10cSrcweir 450cdf0e10cSrcweir case 'a' : 451cdf0e10cSrcweir { 452cdf0e10cSrcweir bRelative = true; 453cdf0e10cSrcweir // FALLTHROUGH intended 454cdf0e10cSrcweir } 455cdf0e10cSrcweir case 'A' : 456cdf0e10cSrcweir { 457cdf0e10cSrcweir nPos++; 4581f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 459cdf0e10cSrcweir 4601f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 461cdf0e10cSrcweir { 462cdf0e10cSrcweir double nX, nY; 463cdf0e10cSrcweir double fRX, fRY, fPhi; 464cdf0e10cSrcweir sal_Int32 bLargeArcFlag, bSweepFlag; 465cdf0e10cSrcweir 4661f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false; 4671f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false; 4681f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false; 4691f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false; 4701f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false; 4711f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 4721f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 473cdf0e10cSrcweir 474cdf0e10cSrcweir if(bRelative) 475cdf0e10cSrcweir { 476cdf0e10cSrcweir nX += nLastX; 477cdf0e10cSrcweir nY += nLastY; 478cdf0e10cSrcweir } 479cdf0e10cSrcweir 4801f882ec4SArmin Le Grand const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1)); 481cdf0e10cSrcweir 482cdf0e10cSrcweir if( nX == nLastX && nY == nLastY ) 483cdf0e10cSrcweir continue; // start==end -> skip according to SVG spec 484cdf0e10cSrcweir 485cdf0e10cSrcweir if( fRX == 0.0 || fRY == 0.0 ) 486cdf0e10cSrcweir { 487cdf0e10cSrcweir // straight line segment according to SVG spec 488cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 489cdf0e10cSrcweir } 490cdf0e10cSrcweir else 491cdf0e10cSrcweir { 492cdf0e10cSrcweir // normalize according to SVG spec 493cdf0e10cSrcweir fRX=fabs(fRX); fRY=fabs(fRY); 494cdf0e10cSrcweir 495cdf0e10cSrcweir // from the SVG spec, appendix F.6.4 496cdf0e10cSrcweir 497cdf0e10cSrcweir // |x1'| |cos phi sin phi| |(x1 - x2)/2| 498cdf0e10cSrcweir // |y1'| = |-sin phi cos phi| |(y1 - y2)/2| 499cdf0e10cSrcweir const B2DPoint p1(nLastX, nLastY); 500cdf0e10cSrcweir const B2DPoint p2(nX, nY); 501cdf0e10cSrcweir B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180)); 502cdf0e10cSrcweir 503cdf0e10cSrcweir const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) ); 504cdf0e10cSrcweir 505cdf0e10cSrcweir // ______________________________________ rx y1' 506cdf0e10cSrcweir // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry 507cdf0e10cSrcweir // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1' 508cdf0e10cSrcweir // rx 509cdf0e10cSrcweir // chose + if f_A != f_S 510cdf0e10cSrcweir // chose - if f_A = f_S 511cdf0e10cSrcweir B2DPoint aCenter_prime; 512cdf0e10cSrcweir const double fRadicant( 513cdf0e10cSrcweir (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/ 514cdf0e10cSrcweir (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX())); 515cdf0e10cSrcweir if( fRadicant < 0.0 ) 516cdf0e10cSrcweir { 517cdf0e10cSrcweir // no solution - according to SVG 518cdf0e10cSrcweir // spec, scale up ellipse 519cdf0e10cSrcweir // uniformly such that it passes 520cdf0e10cSrcweir // through end points (denominator 521cdf0e10cSrcweir // of radicant solved for fRY, 522cdf0e10cSrcweir // with s=fRX/fRY) 523cdf0e10cSrcweir const double fRatio(fRX/fRY); 524cdf0e10cSrcweir const double fRadicant2( 525cdf0e10cSrcweir p1_prime.getY()*p1_prime.getY() + 526cdf0e10cSrcweir p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio)); 527cdf0e10cSrcweir if( fRadicant2 < 0.0 ) 528cdf0e10cSrcweir { 529cdf0e10cSrcweir // only trivial solution, one 530cdf0e10cSrcweir // of the axes 0 -> straight 531cdf0e10cSrcweir // line segment according to 532cdf0e10cSrcweir // SVG spec 533cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 534cdf0e10cSrcweir continue; 535cdf0e10cSrcweir } 536cdf0e10cSrcweir 537cdf0e10cSrcweir fRY=sqrt(fRadicant2); 538cdf0e10cSrcweir fRX=fRatio*fRY; 539cdf0e10cSrcweir 540cdf0e10cSrcweir // keep center_prime forced to (0,0) 541cdf0e10cSrcweir } 542cdf0e10cSrcweir else 543cdf0e10cSrcweir { 544cdf0e10cSrcweir const double fFactor( 545cdf0e10cSrcweir (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) * 546cdf0e10cSrcweir sqrt(fRadicant)); 547cdf0e10cSrcweir 548cdf0e10cSrcweir // actually calculate center_prime 549cdf0e10cSrcweir aCenter_prime = B2DPoint( 550cdf0e10cSrcweir fFactor*fRX*p1_prime.getY()/fRY, 551cdf0e10cSrcweir -fFactor*fRY*p1_prime.getX()/fRX); 552cdf0e10cSrcweir } 553cdf0e10cSrcweir 554cdf0e10cSrcweir // + u - v 555cdf0e10cSrcweir // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx)) 556cdf0e10cSrcweir // - ||u|| ||v|| 557cdf0e10cSrcweir 558cdf0e10cSrcweir // 1 | (x1' - cx')/rx | 559cdf0e10cSrcweir // theta1 = angle(( ), | | ) 560cdf0e10cSrcweir // 0 | (y1' - cy')/ry | 561cdf0e10cSrcweir const B2DPoint aRadii(fRX,fRY); 562cdf0e10cSrcweir double fTheta1( 563cdf0e10cSrcweir B2DVector(1.0,0.0).angle( 564cdf0e10cSrcweir (p1_prime-aCenter_prime)/aRadii)); 565cdf0e10cSrcweir 566cdf0e10cSrcweir // |1| | (-x1' - cx')/rx | 567cdf0e10cSrcweir // theta2 = angle( | | , | | ) 568cdf0e10cSrcweir // |0| | (-y1' - cy')/ry | 569cdf0e10cSrcweir double fTheta2( 570cdf0e10cSrcweir B2DVector(1.0,0.0).angle( 571cdf0e10cSrcweir (-p1_prime-aCenter_prime)/aRadii)); 572cdf0e10cSrcweir 573cdf0e10cSrcweir // map both angles to [0,2pi) 574cdf0e10cSrcweir fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI); 575cdf0e10cSrcweir fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI); 576cdf0e10cSrcweir 577cdf0e10cSrcweir // make sure the large arc is taken 578cdf0e10cSrcweir // (since 579cdf0e10cSrcweir // createPolygonFromEllipseSegment() 580cdf0e10cSrcweir // normalizes to e.g. cw arc) 581ddde725dSArmin Le Grand 582ddde725dSArmin Le Grand // ALG: In my opinion flipping the segment only 583ddde725dSArmin Le Grand // depends on the sweep flag. At least, this gives 584ddde725dSArmin Le Grand // correct results forthe SVG example (see SVG doc 8.3.8 ff) 585ddde725dSArmin Le Grand // 586ddde725dSArmin Le Grand //const bool bFlipSegment( (bLargeArcFlag!=0) == 587ddde725dSArmin Le Grand // (fmod(fTheta2+2*M_PI-fTheta1, 588ddde725dSArmin Le Grand // 2*M_PI)<M_PI) ); 589ddde725dSArmin Le Grand const bool bFlipSegment(!bSweepFlag); 590ddde725dSArmin Le Grand 591cdf0e10cSrcweir if( bFlipSegment ) 592cdf0e10cSrcweir std::swap(fTheta1,fTheta2); 593cdf0e10cSrcweir 594cdf0e10cSrcweir // finally, create bezier polygon from this 595cdf0e10cSrcweir B2DPolygon aSegment( 596cdf0e10cSrcweir tools::createPolygonFromUnitEllipseSegment( 597cdf0e10cSrcweir fTheta1, fTheta2 )); 598cdf0e10cSrcweir 599cdf0e10cSrcweir // transform ellipse by rotation & move to final center 600cdf0e10cSrcweir aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY); 601cdf0e10cSrcweir aTransform.translate(aCenter_prime.getX(), 602cdf0e10cSrcweir aCenter_prime.getY()); 603cdf0e10cSrcweir aTransform.rotate(fPhi*M_PI/180); 604cdf0e10cSrcweir const B2DPoint aOffset((p1+p2)/2.0); 605cdf0e10cSrcweir aTransform.translate(aOffset.getX(), 606cdf0e10cSrcweir aOffset.getY()); 607cdf0e10cSrcweir aSegment.transform(aTransform); 608cdf0e10cSrcweir 609cdf0e10cSrcweir // createPolygonFromEllipseSegment() 610cdf0e10cSrcweir // always creates arcs that are 611cdf0e10cSrcweir // positively oriented - flip polygon 612cdf0e10cSrcweir // if we swapped angles above 613cdf0e10cSrcweir if( bFlipSegment ) 614cdf0e10cSrcweir aSegment.flip(); 6151f882ec4SArmin Le Grand 6161f882ec4SArmin Le Grand // remember PointIndex of evtl. added pure helper points 6171f882ec4SArmin Le Grand sal_uInt32 nPointIndex(aCurrPoly.count() + 1); 618cdf0e10cSrcweir aCurrPoly.append(aSegment); 6191f882ec4SArmin Le Grand 6201f882ec4SArmin Le Grand // if asked for, mark pure helper points by adding them to the index list of 6211f882ec4SArmin Le Grand // helper points 6221f882ec4SArmin Le Grand if(pHelpPointIndexSet && aCurrPoly.count() > 1) 6231f882ec4SArmin Le Grand { 6241f882ec4SArmin Le Grand const sal_uInt32 nPolyIndex(o_rPolyPolygon.count()); 6251f882ec4SArmin Le Grand 6261f882ec4SArmin Le Grand for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++) 6271f882ec4SArmin Le Grand { 6281f882ec4SArmin Le Grand pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex)); 6291f882ec4SArmin Le Grand } 6301f882ec4SArmin Le Grand } 631cdf0e10cSrcweir } 632cdf0e10cSrcweir 633cdf0e10cSrcweir // set last position 634cdf0e10cSrcweir nLastX = nX; 635cdf0e10cSrcweir nLastY = nY; 636cdf0e10cSrcweir } 637cdf0e10cSrcweir break; 638cdf0e10cSrcweir } 639cdf0e10cSrcweir 640cdf0e10cSrcweir default: 641cdf0e10cSrcweir { 642cdf0e10cSrcweir OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!"); 643cdf0e10cSrcweir OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar); 644cdf0e10cSrcweir ++nPos; 645cdf0e10cSrcweir break; 646cdf0e10cSrcweir } 647cdf0e10cSrcweir } 648cdf0e10cSrcweir } 649cdf0e10cSrcweir 6501f882ec4SArmin Le Grand // if there is polygon data, create non-closed polygon 651cdf0e10cSrcweir if(aCurrPoly.count()) 652cdf0e10cSrcweir { 6531f882ec4SArmin Le Grand o_rPolyPolygon.append(aCurrPoly); 654cdf0e10cSrcweir } 655cdf0e10cSrcweir 656cdf0e10cSrcweir return true; 657cdf0e10cSrcweir } 658cdf0e10cSrcweir importFromSvgPoints(B2DPolygon & o_rPoly,const::rtl::OUString & rSvgPointsAttribute)659cdf0e10cSrcweir bool importFromSvgPoints( B2DPolygon& o_rPoly, 660cdf0e10cSrcweir const ::rtl::OUString& rSvgPointsAttribute ) 661cdf0e10cSrcweir { 662cdf0e10cSrcweir o_rPoly.clear(); 663cdf0e10cSrcweir const sal_Int32 nLen(rSvgPointsAttribute.getLength()); 664cdf0e10cSrcweir sal_Int32 nPos(0); 665cdf0e10cSrcweir double nX, nY; 666cdf0e10cSrcweir 667cdf0e10cSrcweir // skip initial whitespace 6681f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); 669cdf0e10cSrcweir 670cdf0e10cSrcweir while(nPos < nLen) 671cdf0e10cSrcweir { 6721f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false; 6731f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false; 674cdf0e10cSrcweir 675cdf0e10cSrcweir // add point 676cdf0e10cSrcweir o_rPoly.append(B2DPoint(nX, nY)); 677cdf0e10cSrcweir 678cdf0e10cSrcweir // skip to next number, or finish 6791f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); 680cdf0e10cSrcweir } 681cdf0e10cSrcweir 682cdf0e10cSrcweir return true; 683cdf0e10cSrcweir } 684cdf0e10cSrcweir exportToSvgPoints(const B2DPolygon & rPoly)6851f882ec4SArmin Le Grand ::rtl::OUString exportToSvgPoints( const B2DPolygon& rPoly ) 6861f882ec4SArmin Le Grand { 6871f882ec4SArmin Le Grand OSL_ENSURE(!rPoly.areControlPointsUsed(), "exportToSvgPoints: Only non-bezier polygons allowed (!)"); 6881f882ec4SArmin Le Grand const sal_uInt32 nPointCount(rPoly.count()); 6891f882ec4SArmin Le Grand ::rtl::OUStringBuffer aResult; 6901f882ec4SArmin Le Grand 6911f882ec4SArmin Le Grand for(sal_uInt32 a(0); a < nPointCount; a++) 6921f882ec4SArmin Le Grand { 6931f882ec4SArmin Le Grand const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a)); 6941f882ec4SArmin Le Grand 6951f882ec4SArmin Le Grand if(a) 6961f882ec4SArmin Le Grand { 6971f882ec4SArmin Le Grand aResult.append(sal_Unicode(' ')); 6981f882ec4SArmin Le Grand } 6991f882ec4SArmin Le Grand 7001f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getX()); 7011f882ec4SArmin Le Grand aResult.append(sal_Unicode(',')); 7021f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getY()); 7031f882ec4SArmin Le Grand } 7041f882ec4SArmin Le Grand 7051f882ec4SArmin Le Grand return aResult.makeStringAndClear(); 7061f882ec4SArmin Le Grand } 7071f882ec4SArmin Le Grand exportToSvgD(const B2DPolyPolygon & rPolyPolygon,bool bUseRelativeCoordinates,bool bDetectQuadraticBeziers,bool bHandleRelativeNextPointCompatible)708cdf0e10cSrcweir ::rtl::OUString exportToSvgD( 7091f882ec4SArmin Le Grand const B2DPolyPolygon& rPolyPolygon, 7101f882ec4SArmin Le Grand bool bUseRelativeCoordinates, 7111f882ec4SArmin Le Grand bool bDetectQuadraticBeziers, 7121f882ec4SArmin Le Grand bool bHandleRelativeNextPointCompatible) 713cdf0e10cSrcweir { 714cdf0e10cSrcweir const sal_uInt32 nCount(rPolyPolygon.count()); 715cdf0e10cSrcweir ::rtl::OUStringBuffer aResult; 716cdf0e10cSrcweir B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point 717cdf0e10cSrcweir 718cdf0e10cSrcweir for(sal_uInt32 i(0); i < nCount; i++) 719cdf0e10cSrcweir { 720cdf0e10cSrcweir const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i)); 721cdf0e10cSrcweir const sal_uInt32 nPointCount(aPolygon.count()); 722cdf0e10cSrcweir 7231f882ec4SArmin Le Grand if(nPointCount) 7241f882ec4SArmin Le Grand { 7251f882ec4SArmin Le Grand const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed()); 7261f882ec4SArmin Le Grand const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1); 7271f882ec4SArmin Le Grand sal_Unicode aLastSVGCommand(' '); // last SVG command char 7281f882ec4SArmin Le Grand B2DPoint aLeft, aRight; // for quadratic bezier test 7291f882ec4SArmin Le Grand 7301f882ec4SArmin Le Grand // handle polygon start point 7311f882ec4SArmin Le Grand B2DPoint aEdgeStart(aPolygon.getB2DPoint(0)); 7321f882ec4SArmin Le Grand bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates); 7331f882ec4SArmin Le Grand 7341f882ec4SArmin Le Grand if(bHandleRelativeNextPointCompatible) 7351f882ec4SArmin Le Grand { 7361f882ec4SArmin Le Grand // To get around the error that the start point for the next polygon is the 7371f882ec4SArmin Le Grand // start point of the current one (and not the last as it was handled up to now) 7381f882ec4SArmin Le Grand // do force to write an absolute 'M' command as start for the next polygon 7391f882ec4SArmin Le Grand bUseRelativeCoordinatesForFirstPoint = false; 7401f882ec4SArmin Le Grand } 7411f882ec4SArmin Le Grand 7421f882ec4SArmin Le Grand // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto' 7431f882ec4SArmin Le Grand aResult.append(::basegfx::internal::lcl_getCommand('M', 'm', bUseRelativeCoordinatesForFirstPoint)); 7441f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint); 7451f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint); 7461f882ec4SArmin Le Grand aLastSVGCommand = ::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinatesForFirstPoint); 7471f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeStart; 7481f882ec4SArmin Le Grand 7491f882ec4SArmin Le Grand for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++) 7501f882ec4SArmin Le Grand { 7511f882ec4SArmin Le Grand // prepare access to next point 7521f882ec4SArmin Le Grand const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); 7531f882ec4SArmin Le Grand const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex)); 7541f882ec4SArmin Le Grand 7551f882ec4SArmin Le Grand // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex) 7561f882ec4SArmin Le Grand const bool bEdgeIsBezier(bPolyUsesControlPoints 7571f882ec4SArmin Le Grand && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex))); 7581f882ec4SArmin Le Grand 7591f882ec4SArmin Le Grand if(bEdgeIsBezier) 7601f882ec4SArmin Le Grand { 7611f882ec4SArmin Le Grand // handle bezier edge 7621f882ec4SArmin Le Grand const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex)); 7631f882ec4SArmin Le Grand const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex)); 7641f882ec4SArmin Le Grand bool bIsQuadraticBezier(false); 7651f882ec4SArmin Le Grand 7661f882ec4SArmin Le Grand // check continuity at current edge's start point. For SVG, do NOT use an 7671f882ec4SArmin Le Grand // existing continuity since no 'S' or 's' statement should be written. At 7681f882ec4SArmin Le Grand // import, that 'previous' control vector is not available. SVG documentation 7691f882ec4SArmin Le Grand // says for interpretation: 7701f882ec4SArmin Le Grand // 7711f882ec4SArmin Le Grand // "(If there is no previous command or if the previous command was 7721f882ec4SArmin Le Grand // not an C, c, S or s, assume the first control point is coincident 7731f882ec4SArmin Le Grand // with the current point.)" 7741f882ec4SArmin Le Grand // 7751f882ec4SArmin Le Grand // That's what is done from our import, so avoid exporting it as first statement 7761f882ec4SArmin Le Grand // is necessary. 7771f882ec4SArmin Le Grand const bool bSymmetricAtEdgeStart( 7781f882ec4SArmin Le Grand 0 != nIndex 7791f882ec4SArmin Le Grand && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex)); 7801f882ec4SArmin Le Grand 7811f882ec4SArmin Le Grand if(bDetectQuadraticBeziers) 7821f882ec4SArmin Le Grand { 7831f882ec4SArmin Le Grand // check for quadratic beziers - that's 7841f882ec4SArmin Le Grand // the case if both control points are in 7851f882ec4SArmin Le Grand // the same place when they are prolonged 7861f882ec4SArmin Le Grand // to the common quadratic control point 7871f882ec4SArmin Le Grand // 7881f882ec4SArmin Le Grand // Left: P = (3P1 - P0) / 2 7891f882ec4SArmin Le Grand // Right: P = (3P2 - P3) / 2 7901f882ec4SArmin Le Grand aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0); 7911f882ec4SArmin Le Grand aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0); 7921f882ec4SArmin Le Grand bIsQuadraticBezier = aLeft.equal(aRight); 7931f882ec4SArmin Le Grand } 7941f882ec4SArmin Le Grand 7951f882ec4SArmin Le Grand if(bIsQuadraticBezier) 7961f882ec4SArmin Le Grand { 7971f882ec4SArmin Le Grand // approximately equal, export as quadratic bezier 7981f882ec4SArmin Le Grand if(bSymmetricAtEdgeStart) 7991f882ec4SArmin Le Grand { 8001f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('T', 't', bUseRelativeCoordinates)); 8011f882ec4SArmin Le Grand 8021f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 8031f882ec4SArmin Le Grand { 804cdf0e10cSrcweir aResult.append(aCommand); 8051f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8061f882ec4SArmin Le Grand } 807cdf0e10cSrcweir 8081f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8091f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8101f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8111f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 8121f882ec4SArmin Le Grand } 8131f882ec4SArmin Le Grand else 8141f882ec4SArmin Le Grand { 8151f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('Q', 'q', bUseRelativeCoordinates)); 8161f882ec4SArmin Le Grand 8171f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 8181f882ec4SArmin Le Grand { 819cdf0e10cSrcweir aResult.append(aCommand); 8201f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8211f882ec4SArmin Le Grand } 822cdf0e10cSrcweir 8231f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8241f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8251f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8261f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8271f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8281f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 8291f882ec4SArmin Le Grand } 8301f882ec4SArmin Le Grand } 8311f882ec4SArmin Le Grand else 8321f882ec4SArmin Le Grand { 8331f882ec4SArmin Le Grand // export as cubic bezier 8341f882ec4SArmin Le Grand if(bSymmetricAtEdgeStart) 8351f882ec4SArmin Le Grand { 8361f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('S', 's', bUseRelativeCoordinates)); 8371f882ec4SArmin Le Grand 8381f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 8391f882ec4SArmin Le Grand { 840cdf0e10cSrcweir aResult.append(aCommand); 8411f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8421f882ec4SArmin Le Grand } 843cdf0e10cSrcweir 8441f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8451f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8461f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8471f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8481f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8491f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 8501f882ec4SArmin Le Grand } 8511f882ec4SArmin Le Grand else 8521f882ec4SArmin Le Grand { 8531f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('C', 'c', bUseRelativeCoordinates)); 8541f882ec4SArmin Le Grand 8551f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 8561f882ec4SArmin Le Grand { 857cdf0e10cSrcweir aResult.append(aCommand); 8581f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8591f882ec4SArmin Le Grand } 860cdf0e10cSrcweir 8611f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8621f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8631f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8641f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8651f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 8661f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 8671f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8681f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 8691f882ec4SArmin Le Grand } 8701f882ec4SArmin Le Grand } 8711f882ec4SArmin Le Grand } 8721f882ec4SArmin Le Grand else 8731f882ec4SArmin Le Grand { 8741f882ec4SArmin Le Grand // straight edge 8751f882ec4SArmin Le Grand if(0 == nNextIndex) 8761f882ec4SArmin Le Grand { 8771f882ec4SArmin Le Grand // it's a closed polygon's last edge and it's not a bezier edge, so there is 8781f882ec4SArmin Le Grand // no need to write it 8791f882ec4SArmin Le Grand } 8801f882ec4SArmin Le Grand else 8811f882ec4SArmin Le Grand { 8821f882ec4SArmin Le Grand const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX()); 8831f882ec4SArmin Le Grand const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY()); 8841f882ec4SArmin Le Grand 8851f882ec4SArmin Le Grand if(bXEqual && bYEqual) 8861f882ec4SArmin Le Grand { 8871f882ec4SArmin Le Grand // point is a double point; do not export at all 8881f882ec4SArmin Le Grand } 8891f882ec4SArmin Le Grand else if(bXEqual) 8901f882ec4SArmin Le Grand { 8911f882ec4SArmin Le Grand // export as vertical line 8921f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('V', 'v', bUseRelativeCoordinates)); 8931f882ec4SArmin Le Grand 8941f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 8951f882ec4SArmin Le Grand { 896cdf0e10cSrcweir aResult.append(aCommand); 8971f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 8981f882ec4SArmin Le Grand } 8991f882ec4SArmin Le Grand 9001f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 9011f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 9021f882ec4SArmin Le Grand } 9031f882ec4SArmin Le Grand else if(bYEqual) 9041f882ec4SArmin Le Grand { 9051f882ec4SArmin Le Grand // export as horizontal line 9061f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('H', 'h', bUseRelativeCoordinates)); 9071f882ec4SArmin Le Grand 9081f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 9091f882ec4SArmin Le Grand { 910cdf0e10cSrcweir aResult.append(aCommand); 9111f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 9121f882ec4SArmin Le Grand } 9131f882ec4SArmin Le Grand 9141f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 9151f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 9161f882ec4SArmin Le Grand } 9171f882ec4SArmin Le Grand else 9181f882ec4SArmin Le Grand { 9191f882ec4SArmin Le Grand // export as line 9201f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinates)); 9211f882ec4SArmin Le Grand 9221f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 9231f882ec4SArmin Le Grand { 924cdf0e10cSrcweir aResult.append(aCommand); 9251f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 9261f882ec4SArmin Le Grand } 9271f882ec4SArmin Le Grand 9281f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 9291f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 9301f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 9311f882ec4SArmin Le Grand } 9321f882ec4SArmin Le Grand } 9331f882ec4SArmin Le Grand } 9341f882ec4SArmin Le Grand 9351f882ec4SArmin Le Grand // prepare edge start for next loop step 9361f882ec4SArmin Le Grand aEdgeStart = aEdgeEnd; 9371f882ec4SArmin Le Grand } 9381f882ec4SArmin Le Grand 9391f882ec4SArmin Le Grand // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched) 9401f882ec4SArmin Le Grand if(aPolygon.isClosed()) 9411f882ec4SArmin Le Grand { 9421f882ec4SArmin Le Grand aResult.append(::basegfx::internal::lcl_getCommand('Z', 'z', bUseRelativeCoordinates)); 9431f882ec4SArmin Le Grand } 9441f882ec4SArmin Le Grand 9451f882ec4SArmin Le Grand if(!bHandleRelativeNextPointCompatible) 9461f882ec4SArmin Le Grand { 9471f882ec4SArmin Le Grand // SVG defines that "the next subpath starts at the same initial point as the current subpath", 9481f882ec4SArmin Le Grand // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path 9491f882ec4SArmin Le Grand aCurrentSVGPosition = aPolygon.getB2DPoint(0); 9501f882ec4SArmin Le Grand } 9511f882ec4SArmin Le Grand } 952cdf0e10cSrcweir } 953cdf0e10cSrcweir 954cdf0e10cSrcweir return aResult.makeStringAndClear(); 955cdf0e10cSrcweir } 956cdf0e10cSrcweir } 957cdf0e10cSrcweir } 958cdf0e10cSrcweir 959cdf0e10cSrcweir // eof 960