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> 35*1f882ec4SArmin Le Grand #include <stringconversiontools.hxx> 36cdf0e10cSrcweir 37cdf0e10cSrcweir namespace basegfx 38cdf0e10cSrcweir { 39*1f882ec4SArmin Le Grand namespace tools 40*1f882ec4SArmin Le Grand { 41*1f882ec4SArmin Le Grand bool PointIndex::operator<(const PointIndex& rComp) const 42cdf0e10cSrcweir { 43*1f882ec4SArmin Le Grand if(rComp.getPolygonIndex() == getPolygonIndex()) 44cdf0e10cSrcweir { 45*1f882ec4SArmin Le Grand return rComp.getPointIndex() < getPointIndex(); 46cdf0e10cSrcweir } 47cdf0e10cSrcweir 48*1f882ec4SArmin Le Grand return rComp.getPolygonIndex() < getPolygonIndex(); 49cdf0e10cSrcweir } 50cdf0e10cSrcweir 51*1f882ec4SArmin Le Grand bool importFromSvgD( 52*1f882ec4SArmin Le Grand B2DPolyPolygon& o_rPolyPolygon, 53*1f882ec4SArmin Le Grand const ::rtl::OUString& rSvgDStatement, 54*1f882ec4SArmin Le Grand bool bHandleRelativeNextPointCompatible, 55*1f882ec4SArmin 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 ); 62*1f882ec4SArmin Le Grand B2DPolygon aCurrPoly; 63cdf0e10cSrcweir 64*1f882ec4SArmin Le Grand // skip initial whitespace 65*1f882ec4SArmin 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 72*1f882ec4SArmin Le Grand if(o_rPolyPolygon.count() && !aCurrPoly.count() && !('m' == aCurrChar || 'M' == aCurrChar)) 73*1f882ec4SArmin Le Grand { 74*1f882ec4SArmin Le Grand // we have a new sub-polygon starting, but without a 'moveto' command. 75*1f882ec4SArmin Le Grand // this requires to add the current point as start point to the polygon 76*1f882ec4SArmin Le Grand // (see SVG1.1 8.3.3 The "closepath" command) 77*1f882ec4SArmin Le Grand aCurrPoly.append(B2DPoint(nLastX, nLastY)); 78*1f882ec4SArmin Le Grand } 79*1f882ec4SArmin Le Grand 80cdf0e10cSrcweir switch(aCurrChar) 81cdf0e10cSrcweir { 82cdf0e10cSrcweir case 'z' : 83cdf0e10cSrcweir case 'Z' : 84cdf0e10cSrcweir { 85*1f882ec4SArmin Le Grand // consume CurrChar and whitespace 86cdf0e10cSrcweir nPos++; 87*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 88*1f882ec4SArmin Le Grand 89*1f882ec4SArmin Le Grand // create closed polygon and reset import values 90*1f882ec4SArmin Le Grand if(aCurrPoly.count()) 91*1f882ec4SArmin Le Grand { 92*1f882ec4SArmin Le Grand if(!bHandleRelativeNextPointCompatible) 93*1f882ec4SArmin Le Grand { 94*1f882ec4SArmin Le Grand // SVG defines that "the next subpath starts at the 95*1f882ec4SArmin Le Grand // same initial point as the current subpath", so set the 96*1f882ec4SArmin Le Grand // current point if we do not need to be compatible 97*1f882ec4SArmin Le Grand nLastX = aCurrPoly.getB2DPoint(0).getX(); 98*1f882ec4SArmin Le Grand nLastY = aCurrPoly.getB2DPoint(0).getY(); 99*1f882ec4SArmin Le Grand } 100*1f882ec4SArmin Le Grand 101*1f882ec4SArmin Le Grand aCurrPoly.setClosed(true); 102*1f882ec4SArmin Le Grand o_rPolyPolygon.append(aCurrPoly); 103*1f882ec4SArmin Le Grand aCurrPoly.clear(); 104*1f882ec4SArmin Le Grand } 105cdf0e10cSrcweir 106cdf0e10cSrcweir break; 107cdf0e10cSrcweir } 108*1f882ec4SArmin Le Grand 109cdf0e10cSrcweir case 'm' : 110cdf0e10cSrcweir case 'M' : 111cdf0e10cSrcweir { 112*1f882ec4SArmin Le Grand // create non-closed polygon and reset import values 113*1f882ec4SArmin Le Grand if(aCurrPoly.count()) 114*1f882ec4SArmin Le Grand { 115*1f882ec4SArmin Le Grand o_rPolyPolygon.append(aCurrPoly); 116*1f882ec4SArmin Le Grand aCurrPoly.clear(); 117*1f882ec4SArmin Le Grand } 118*1f882ec4SArmin Le Grand 119*1f882ec4SArmin 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 { 126*1f882ec4SArmin Le Grand bRelative = true; 127cdf0e10cSrcweir } 128cdf0e10cSrcweir 129*1f882ec4SArmin Le Grand // consume CurrChar and whitespace 130cdf0e10cSrcweir nPos++; 131*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 132cdf0e10cSrcweir 133*1f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 134cdf0e10cSrcweir { 135cdf0e10cSrcweir double nX, nY; 136cdf0e10cSrcweir 137*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 138*1f882ec4SArmin 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; 149*1f882ec4SArmin 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++; 164*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 165cdf0e10cSrcweir 166*1f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 167cdf0e10cSrcweir { 168cdf0e10cSrcweir double nX, nY(nLastY); 169cdf0e10cSrcweir 170*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 171cdf0e10cSrcweir 172cdf0e10cSrcweir if(bRelative) 173*1f882ec4SArmin Le Grand { 174cdf0e10cSrcweir nX += nLastX; 175*1f882ec4SArmin Le Grand } 176cdf0e10cSrcweir 177cdf0e10cSrcweir // set last position 178cdf0e10cSrcweir nLastX = nX; 179*1f882ec4SArmin Le Grand 180cdf0e10cSrcweir // add point 181cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 182cdf0e10cSrcweir } 183cdf0e10cSrcweir break; 184cdf0e10cSrcweir } 185*1f882ec4SArmin Le Grand 186cdf0e10cSrcweir case 'v' : 187cdf0e10cSrcweir { 188cdf0e10cSrcweir bRelative = true; 189cdf0e10cSrcweir // FALLTHROUGH intended 190cdf0e10cSrcweir } 191cdf0e10cSrcweir case 'V' : 192cdf0e10cSrcweir { 193cdf0e10cSrcweir nPos++; 194*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 195cdf0e10cSrcweir 196*1f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 197cdf0e10cSrcweir { 198cdf0e10cSrcweir double nX(nLastX), nY; 199cdf0e10cSrcweir 200*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 201cdf0e10cSrcweir 202cdf0e10cSrcweir if(bRelative) 203*1f882ec4SArmin Le Grand { 204cdf0e10cSrcweir nY += nLastY; 205*1f882ec4SArmin Le Grand } 206cdf0e10cSrcweir 207cdf0e10cSrcweir // set last position 208cdf0e10cSrcweir nLastY = nY; 209*1f882ec4SArmin Le Grand 210cdf0e10cSrcweir // add point 211cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nX, nY)); 212cdf0e10cSrcweir } 213cdf0e10cSrcweir break; 214cdf0e10cSrcweir } 215*1f882ec4SArmin Le Grand 216cdf0e10cSrcweir case 's' : 217cdf0e10cSrcweir { 218cdf0e10cSrcweir bRelative = true; 219cdf0e10cSrcweir // FALLTHROUGH intended 220cdf0e10cSrcweir } 221cdf0e10cSrcweir case 'S' : 222cdf0e10cSrcweir { 223cdf0e10cSrcweir nPos++; 224*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 225cdf0e10cSrcweir 226*1f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 227cdf0e10cSrcweir { 228cdf0e10cSrcweir double nX, nY; 229cdf0e10cSrcweir double nX2, nY2; 230cdf0e10cSrcweir 231*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; 232*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; 233*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 234*1f882ec4SArmin 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*1f882ec4SArmin Le Grand // ensure existance of start point 245*1f882ec4SArmin Le Grand if(!aCurrPoly.count()) 246*1f882ec4SArmin Le Grand { 247cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 248*1f882ec4SArmin Le Grand } 249cdf0e10cSrcweir 250*1f882ec4SArmin Le Grand // get first control point. It's the reflection of the PrevControlPoint 251*1f882ec4SArmin Le Grand // of the last point. If not existent, use current point (see SVG) 252*1f882ec4SArmin Le Grand B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); 253*1f882ec4SArmin Le Grand const sal_uInt32 nIndex(aCurrPoly.count() - 1); 254cdf0e10cSrcweir 255*1f882ec4SArmin Le Grand if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) 256*1f882ec4SArmin Le Grand { 257*1f882ec4SArmin Le Grand const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); 258*1f882ec4SArmin Le Grand const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); 259cdf0e10cSrcweir 260*1f882ec4SArmin Le Grand // use mirrored previous control point 261*1f882ec4SArmin Le Grand aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); 262*1f882ec4SArmin Le Grand aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); 263*1f882ec4SArmin Le Grand } 264cdf0e10cSrcweir 265*1f882ec4SArmin Le Grand // append curved edge 266*1f882ec4SArmin 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 } 274*1f882ec4SArmin Le Grand 275cdf0e10cSrcweir case 'c' : 276cdf0e10cSrcweir { 277cdf0e10cSrcweir bRelative = true; 278cdf0e10cSrcweir // FALLTHROUGH intended 279cdf0e10cSrcweir } 280cdf0e10cSrcweir case 'C' : 281cdf0e10cSrcweir { 282cdf0e10cSrcweir nPos++; 283*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 284cdf0e10cSrcweir 285*1f882ec4SArmin 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 291*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; 292*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; 293*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; 294*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; 295*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 296*1f882ec4SArmin 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*1f882ec4SArmin Le Grand // ensure existance of start point 309*1f882ec4SArmin Le Grand if(!aCurrPoly.count()) 310*1f882ec4SArmin Le Grand { 311cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 312*1f882ec4SArmin Le Grand } 313cdf0e10cSrcweir 314*1f882ec4SArmin Le Grand // append curved edge 315*1f882ec4SArmin 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 } 323*1f882ec4SArmin 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++; 333*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 334cdf0e10cSrcweir 335*1f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 336cdf0e10cSrcweir { 337cdf0e10cSrcweir double nX, nY; 338cdf0e10cSrcweir double nX1, nY1; 339cdf0e10cSrcweir 340*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; 341*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; 342*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 343*1f882ec4SArmin 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*1f882ec4SArmin Le Grand // ensure existance of start point 360*1f882ec4SArmin Le Grand if(!aCurrPoly.count()) 361*1f882ec4SArmin Le Grand { 362cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 363*1f882ec4SArmin Le Grand } 364cdf0e10cSrcweir 365*1f882ec4SArmin Le Grand // append curved edge 366*1f882ec4SArmin 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 } 374*1f882ec4SArmin 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++; 384*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 385cdf0e10cSrcweir 386*1f882ec4SArmin Le Grand while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos)) 387cdf0e10cSrcweir { 388cdf0e10cSrcweir double nX, nY; 389cdf0e10cSrcweir 390*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 391*1f882ec4SArmin 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*1f882ec4SArmin Le Grand // ensure existance of start point 400*1f882ec4SArmin Le Grand if(!aCurrPoly.count()) 401*1f882ec4SArmin Le Grand { 402cdf0e10cSrcweir aCurrPoly.append(B2DPoint(nLastX, nLastY)); 403*1f882ec4SArmin Le Grand } 404*1f882ec4SArmin Le Grand 405*1f882ec4SArmin Le Grand // get first control point. It's the reflection of the PrevControlPoint 406*1f882ec4SArmin Le Grand // of the last point. If not existent, use current point (see SVG) 407*1f882ec4SArmin Le Grand B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); 408*1f882ec4SArmin Le Grand const sal_uInt32 nIndex(aCurrPoly.count() - 1); 409*1f882ec4SArmin Le Grand const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); 410*1f882ec4SArmin Le Grand 411*1f882ec4SArmin Le Grand if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) 412*1f882ec4SArmin Le Grand { 413*1f882ec4SArmin Le Grand const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); 414*1f882ec4SArmin Le Grand 415*1f882ec4SArmin Le Grand // use mirrored previous control point 416*1f882ec4SArmin Le Grand aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); 417*1f882ec4SArmin Le Grand aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); 418*1f882ec4SArmin Le Grand } 419*1f882ec4SArmin Le Grand 420*1f882ec4SArmin Le Grand if(!aPrevControl.equal(aPrevPoint)) 421*1f882ec4SArmin Le Grand { 422*1f882ec4SArmin Le Grand // there is a prev control point, and we have the already mirrored one 423*1f882ec4SArmin Le Grand // in aPrevControl. We also need the quadratic control point for this 424*1f882ec4SArmin Le Grand // new quadratic segment to calculate the 2nd cubic control point 425*1f882ec4SArmin Le Grand const B2DPoint aQuadControlPoint( 426*1f882ec4SArmin Le Grand ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0, 427*1f882ec4SArmin Le Grand ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0); 428*1f882ec4SArmin Le Grand 429*1f882ec4SArmin Le Grand // calculate the cubic bezier coefficients from the quadratic ones. 430*1f882ec4SArmin Le Grand const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0); 431*1f882ec4SArmin Le Grand const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0); 432*1f882ec4SArmin Le Grand 433*1f882ec4SArmin Le Grand // append curved edge, use mirrored cubic control point directly 434*1f882ec4SArmin Le Grand aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); 435*1f882ec4SArmin Le Grand } 436*1f882ec4SArmin Le Grand else 437*1f882ec4SArmin Le Grand { 438*1f882ec4SArmin Le Grand // when no previous control, SVG says to use current point -> straight line. 439*1f882ec4SArmin Le Grand // Just add end point 440*1f882ec4SArmin Le Grand aCurrPoly.append(B2DPoint(nX, nY)); 441*1f882ec4SArmin 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++; 458*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen); 459cdf0e10cSrcweir 460*1f882ec4SArmin 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 466*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false; 467*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false; 468*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false; 469*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false; 470*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false; 471*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 472*1f882ec4SArmin 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 480*1f882ec4SArmin 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(); 615*1f882ec4SArmin Le Grand 616*1f882ec4SArmin Le Grand // remember PointIndex of evtl. added pure helper points 617*1f882ec4SArmin Le Grand sal_uInt32 nPointIndex(aCurrPoly.count() + 1); 618cdf0e10cSrcweir aCurrPoly.append(aSegment); 619*1f882ec4SArmin Le Grand 620*1f882ec4SArmin Le Grand // if asked for, mark pure helper points by adding them to the index list of 621*1f882ec4SArmin Le Grand // helper points 622*1f882ec4SArmin Le Grand if(pHelpPointIndexSet && aCurrPoly.count() > 1) 623*1f882ec4SArmin Le Grand { 624*1f882ec4SArmin Le Grand const sal_uInt32 nPolyIndex(o_rPolyPolygon.count()); 625*1f882ec4SArmin Le Grand 626*1f882ec4SArmin Le Grand for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++) 627*1f882ec4SArmin Le Grand { 628*1f882ec4SArmin Le Grand pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex)); 629*1f882ec4SArmin Le Grand } 630*1f882ec4SArmin 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 650*1f882ec4SArmin Le Grand // if there is polygon data, create non-closed polygon 651cdf0e10cSrcweir if(aCurrPoly.count()) 652cdf0e10cSrcweir { 653*1f882ec4SArmin Le Grand o_rPolyPolygon.append(aCurrPoly); 654cdf0e10cSrcweir } 655cdf0e10cSrcweir 656cdf0e10cSrcweir return true; 657cdf0e10cSrcweir } 658cdf0e10cSrcweir 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 668*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); 669cdf0e10cSrcweir 670cdf0e10cSrcweir while(nPos < nLen) 671cdf0e10cSrcweir { 672*1f882ec4SArmin Le Grand if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false; 673*1f882ec4SArmin 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 679*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); 680cdf0e10cSrcweir } 681cdf0e10cSrcweir 682cdf0e10cSrcweir return true; 683cdf0e10cSrcweir } 684cdf0e10cSrcweir 685*1f882ec4SArmin Le Grand ::rtl::OUString exportToSvgPoints( const B2DPolygon& rPoly ) 686*1f882ec4SArmin Le Grand { 687*1f882ec4SArmin Le Grand OSL_ENSURE(!rPoly.areControlPointsUsed(), "exportToSvgPoints: Only non-bezier polygons allowed (!)"); 688*1f882ec4SArmin Le Grand const sal_uInt32 nPointCount(rPoly.count()); 689*1f882ec4SArmin Le Grand ::rtl::OUStringBuffer aResult; 690*1f882ec4SArmin Le Grand 691*1f882ec4SArmin Le Grand for(sal_uInt32 a(0); a < nPointCount; a++) 692*1f882ec4SArmin Le Grand { 693*1f882ec4SArmin Le Grand const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a)); 694*1f882ec4SArmin Le Grand 695*1f882ec4SArmin Le Grand if(a) 696*1f882ec4SArmin Le Grand { 697*1f882ec4SArmin Le Grand aResult.append(sal_Unicode(' ')); 698*1f882ec4SArmin Le Grand } 699*1f882ec4SArmin Le Grand 700*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getX()); 701*1f882ec4SArmin Le Grand aResult.append(sal_Unicode(',')); 702*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getY()); 703*1f882ec4SArmin Le Grand } 704*1f882ec4SArmin Le Grand 705*1f882ec4SArmin Le Grand return aResult.makeStringAndClear(); 706*1f882ec4SArmin Le Grand } 707*1f882ec4SArmin Le Grand 708cdf0e10cSrcweir ::rtl::OUString exportToSvgD( 709*1f882ec4SArmin Le Grand const B2DPolyPolygon& rPolyPolygon, 710*1f882ec4SArmin Le Grand bool bUseRelativeCoordinates, 711*1f882ec4SArmin Le Grand bool bDetectQuadraticBeziers, 712*1f882ec4SArmin 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 723*1f882ec4SArmin Le Grand if(nPointCount) 724*1f882ec4SArmin Le Grand { 725*1f882ec4SArmin Le Grand const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed()); 726*1f882ec4SArmin Le Grand const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1); 727*1f882ec4SArmin Le Grand sal_Unicode aLastSVGCommand(' '); // last SVG command char 728*1f882ec4SArmin Le Grand B2DPoint aLeft, aRight; // for quadratic bezier test 729*1f882ec4SArmin Le Grand 730*1f882ec4SArmin Le Grand // handle polygon start point 731*1f882ec4SArmin Le Grand B2DPoint aEdgeStart(aPolygon.getB2DPoint(0)); 732*1f882ec4SArmin Le Grand bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates); 733*1f882ec4SArmin Le Grand 734*1f882ec4SArmin Le Grand if(bHandleRelativeNextPointCompatible) 735*1f882ec4SArmin Le Grand { 736*1f882ec4SArmin Le Grand // To get around the error that the start point for the next polygon is the 737*1f882ec4SArmin Le Grand // start point of the current one (and not the last as it was handled up to now) 738*1f882ec4SArmin Le Grand // do force to write an absolute 'M' command as start for the next polygon 739*1f882ec4SArmin Le Grand bUseRelativeCoordinatesForFirstPoint = false; 740*1f882ec4SArmin Le Grand } 741*1f882ec4SArmin Le Grand 742*1f882ec4SArmin Le Grand // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto' 743*1f882ec4SArmin Le Grand aResult.append(::basegfx::internal::lcl_getCommand('M', 'm', bUseRelativeCoordinatesForFirstPoint)); 744*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint); 745*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint); 746*1f882ec4SArmin Le Grand aLastSVGCommand = ::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinatesForFirstPoint); 747*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeStart; 748*1f882ec4SArmin Le Grand 749*1f882ec4SArmin Le Grand for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++) 750*1f882ec4SArmin Le Grand { 751*1f882ec4SArmin Le Grand // prepare access to next point 752*1f882ec4SArmin Le Grand const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); 753*1f882ec4SArmin Le Grand const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex)); 754*1f882ec4SArmin Le Grand 755*1f882ec4SArmin Le Grand // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex) 756*1f882ec4SArmin Le Grand const bool bEdgeIsBezier(bPolyUsesControlPoints 757*1f882ec4SArmin Le Grand && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex))); 758*1f882ec4SArmin Le Grand 759*1f882ec4SArmin Le Grand if(bEdgeIsBezier) 760*1f882ec4SArmin Le Grand { 761*1f882ec4SArmin Le Grand // handle bezier edge 762*1f882ec4SArmin Le Grand const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex)); 763*1f882ec4SArmin Le Grand const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex)); 764*1f882ec4SArmin Le Grand bool bIsQuadraticBezier(false); 765*1f882ec4SArmin Le Grand 766*1f882ec4SArmin Le Grand // check continuity at current edge's start point. For SVG, do NOT use an 767*1f882ec4SArmin Le Grand // existing continuity since no 'S' or 's' statement should be written. At 768*1f882ec4SArmin Le Grand // import, that 'previous' control vector is not available. SVG documentation 769*1f882ec4SArmin Le Grand // says for interpretation: 770*1f882ec4SArmin Le Grand // 771*1f882ec4SArmin Le Grand // "(If there is no previous command or if the previous command was 772*1f882ec4SArmin Le Grand // not an C, c, S or s, assume the first control point is coincident 773*1f882ec4SArmin Le Grand // with the current point.)" 774*1f882ec4SArmin Le Grand // 775*1f882ec4SArmin Le Grand // That's what is done from our import, so avoid exporting it as first statement 776*1f882ec4SArmin Le Grand // is necessary. 777*1f882ec4SArmin Le Grand const bool bSymmetricAtEdgeStart( 778*1f882ec4SArmin Le Grand 0 != nIndex 779*1f882ec4SArmin Le Grand && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex)); 780*1f882ec4SArmin Le Grand 781*1f882ec4SArmin Le Grand if(bDetectQuadraticBeziers) 782*1f882ec4SArmin Le Grand { 783*1f882ec4SArmin Le Grand // check for quadratic beziers - that's 784*1f882ec4SArmin Le Grand // the case if both control points are in 785*1f882ec4SArmin Le Grand // the same place when they are prolonged 786*1f882ec4SArmin Le Grand // to the common quadratic control point 787*1f882ec4SArmin Le Grand // 788*1f882ec4SArmin Le Grand // Left: P = (3P1 - P0) / 2 789*1f882ec4SArmin Le Grand // Right: P = (3P2 - P3) / 2 790*1f882ec4SArmin Le Grand aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0); 791*1f882ec4SArmin Le Grand aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0); 792*1f882ec4SArmin Le Grand bIsQuadraticBezier = aLeft.equal(aRight); 793*1f882ec4SArmin Le Grand } 794*1f882ec4SArmin Le Grand 795*1f882ec4SArmin Le Grand if(bIsQuadraticBezier) 796*1f882ec4SArmin Le Grand { 797*1f882ec4SArmin Le Grand // approximately equal, export as quadratic bezier 798*1f882ec4SArmin Le Grand if(bSymmetricAtEdgeStart) 799*1f882ec4SArmin Le Grand { 800*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('T', 't', bUseRelativeCoordinates)); 801*1f882ec4SArmin Le Grand 802*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 803*1f882ec4SArmin Le Grand { 804cdf0e10cSrcweir aResult.append(aCommand); 805*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 806*1f882ec4SArmin Le Grand } 807cdf0e10cSrcweir 808*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 809*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 810*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 811*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 812*1f882ec4SArmin Le Grand } 813*1f882ec4SArmin Le Grand else 814*1f882ec4SArmin Le Grand { 815*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('Q', 'q', bUseRelativeCoordinates)); 816*1f882ec4SArmin Le Grand 817*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 818*1f882ec4SArmin Le Grand { 819cdf0e10cSrcweir aResult.append(aCommand); 820*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 821*1f882ec4SArmin Le Grand } 822cdf0e10cSrcweir 823*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 824*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 825*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 826*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 827*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 828*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 829*1f882ec4SArmin Le Grand } 830*1f882ec4SArmin Le Grand } 831*1f882ec4SArmin Le Grand else 832*1f882ec4SArmin Le Grand { 833*1f882ec4SArmin Le Grand // export as cubic bezier 834*1f882ec4SArmin Le Grand if(bSymmetricAtEdgeStart) 835*1f882ec4SArmin Le Grand { 836*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('S', 's', bUseRelativeCoordinates)); 837*1f882ec4SArmin Le Grand 838*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 839*1f882ec4SArmin Le Grand { 840cdf0e10cSrcweir aResult.append(aCommand); 841*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 842*1f882ec4SArmin Le Grand } 843cdf0e10cSrcweir 844*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 845*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 846*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 847*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 848*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 849*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 850*1f882ec4SArmin Le Grand } 851*1f882ec4SArmin Le Grand else 852*1f882ec4SArmin Le Grand { 853*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('C', 'c', bUseRelativeCoordinates)); 854*1f882ec4SArmin Le Grand 855*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 856*1f882ec4SArmin Le Grand { 857cdf0e10cSrcweir aResult.append(aCommand); 858*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 859*1f882ec4SArmin Le Grand } 860cdf0e10cSrcweir 861*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 862*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 863*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 864*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 865*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 866*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 867*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 868*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 869*1f882ec4SArmin Le Grand } 870*1f882ec4SArmin Le Grand } 871*1f882ec4SArmin Le Grand } 872*1f882ec4SArmin Le Grand else 873*1f882ec4SArmin Le Grand { 874*1f882ec4SArmin Le Grand // straight edge 875*1f882ec4SArmin Le Grand if(0 == nNextIndex) 876*1f882ec4SArmin Le Grand { 877*1f882ec4SArmin Le Grand // it's a closed polygon's last edge and it's not a bezier edge, so there is 878*1f882ec4SArmin Le Grand // no need to write it 879*1f882ec4SArmin Le Grand } 880*1f882ec4SArmin Le Grand else 881*1f882ec4SArmin Le Grand { 882*1f882ec4SArmin Le Grand const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX()); 883*1f882ec4SArmin Le Grand const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY()); 884*1f882ec4SArmin Le Grand 885*1f882ec4SArmin Le Grand if(bXEqual && bYEqual) 886*1f882ec4SArmin Le Grand { 887*1f882ec4SArmin Le Grand // point is a double point; do not export at all 888*1f882ec4SArmin Le Grand } 889*1f882ec4SArmin Le Grand else if(bXEqual) 890*1f882ec4SArmin Le Grand { 891*1f882ec4SArmin Le Grand // export as vertical line 892*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('V', 'v', bUseRelativeCoordinates)); 893*1f882ec4SArmin Le Grand 894*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 895*1f882ec4SArmin Le Grand { 896cdf0e10cSrcweir aResult.append(aCommand); 897*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 898*1f882ec4SArmin Le Grand } 899*1f882ec4SArmin Le Grand 900*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 901*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 902*1f882ec4SArmin Le Grand } 903*1f882ec4SArmin Le Grand else if(bYEqual) 904*1f882ec4SArmin Le Grand { 905*1f882ec4SArmin Le Grand // export as horizontal line 906*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('H', 'h', bUseRelativeCoordinates)); 907*1f882ec4SArmin Le Grand 908*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 909*1f882ec4SArmin Le Grand { 910cdf0e10cSrcweir aResult.append(aCommand); 911*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 912*1f882ec4SArmin Le Grand } 913*1f882ec4SArmin Le Grand 914*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 915*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 916*1f882ec4SArmin Le Grand } 917*1f882ec4SArmin Le Grand else 918*1f882ec4SArmin Le Grand { 919*1f882ec4SArmin Le Grand // export as line 920*1f882ec4SArmin Le Grand const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinates)); 921*1f882ec4SArmin Le Grand 922*1f882ec4SArmin Le Grand if(aLastSVGCommand != aCommand) 923*1f882ec4SArmin Le Grand { 924cdf0e10cSrcweir aResult.append(aCommand); 925*1f882ec4SArmin Le Grand aLastSVGCommand = aCommand; 926*1f882ec4SArmin Le Grand } 927*1f882ec4SArmin Le Grand 928*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 929*1f882ec4SArmin Le Grand ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 930*1f882ec4SArmin Le Grand aCurrentSVGPosition = aEdgeEnd; 931*1f882ec4SArmin Le Grand } 932*1f882ec4SArmin Le Grand } 933*1f882ec4SArmin Le Grand } 934*1f882ec4SArmin Le Grand 935*1f882ec4SArmin Le Grand // prepare edge start for next loop step 936*1f882ec4SArmin Le Grand aEdgeStart = aEdgeEnd; 937*1f882ec4SArmin Le Grand } 938*1f882ec4SArmin Le Grand 939*1f882ec4SArmin Le Grand // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched) 940*1f882ec4SArmin Le Grand if(aPolygon.isClosed()) 941*1f882ec4SArmin Le Grand { 942*1f882ec4SArmin Le Grand aResult.append(::basegfx::internal::lcl_getCommand('Z', 'z', bUseRelativeCoordinates)); 943*1f882ec4SArmin Le Grand } 944*1f882ec4SArmin Le Grand 945*1f882ec4SArmin Le Grand if(!bHandleRelativeNextPointCompatible) 946*1f882ec4SArmin Le Grand { 947*1f882ec4SArmin Le Grand // SVG defines that "the next subpath starts at the same initial point as the current subpath", 948*1f882ec4SArmin Le Grand // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path 949*1f882ec4SArmin Le Grand aCurrentSVGPosition = aPolygon.getB2DPoint(0); 950*1f882ec4SArmin Le Grand } 951*1f882ec4SArmin Le Grand } 952cdf0e10cSrcweir } 953cdf0e10cSrcweir 954cdf0e10cSrcweir return aResult.makeStringAndClear(); 955cdf0e10cSrcweir } 956cdf0e10cSrcweir } 957cdf0e10cSrcweir } 958cdf0e10cSrcweir 959cdf0e10cSrcweir // eof 960