update credits
[LibreOffice.git] / svgio / source / svgreader / svgtextpathnode.cxx
blobe01c70b495a3543d872efe6be37161a2b78e8abf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svgio/svgreader/svgtextpathnode.hxx>
21 #include <svgio/svgreader/svgstyleattributes.hxx>
22 #include <svgio/svgreader/svgpathnode.hxx>
23 #include <svgio/svgreader/svgdocument.hxx>
24 #include <svgio/svgreader/svgtrefnode.hxx>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <basegfx/polygon/b2dpolygontools.hxx>
27 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
28 #include <drawinglayer/primitive2d/groupprimitive2d.hxx>
29 #include <basegfx/curve/b2dcubicbezier.hxx>
30 #include <basegfx/curve/b2dbeziertools.hxx>
32 //////////////////////////////////////////////////////////////////////////////
34 namespace svgio
36 namespace svgreader
38 class pathTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
40 private:
41 const basegfx::B2DPolygon& mrPolygon;
42 const double mfBasegfxPathLength;
43 double mfPosition;
44 const basegfx::B2DPoint& mrTextStart;
46 const sal_uInt32 mnMaxIndex;
47 sal_uInt32 mnIndex;
48 basegfx::B2DCubicBezier maCurrentSegment;
49 basegfx::B2DCubicBezierHelper* mpB2DCubicBezierHelper;
50 double mfCurrentSegmentLength;
51 double mfSegmentStartPosition;
53 protected:
54 /// allow user callback to allow changes to the new TextTransformation. Default
55 /// does nothing.
56 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength);
58 void freeB2DCubicBezierHelper();
59 basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper();
60 void advanceToPosition(double fNewPosition);
62 public:
63 pathTextBreakupHelper(
64 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
65 const basegfx::B2DPolygon& rPolygon,
66 const double fBasegfxPathLength,
67 double fPosition,
68 const basegfx::B2DPoint& rTextStart);
69 virtual ~pathTextBreakupHelper();
71 // read access to evtl. advanced position
72 double getPosition() const { return mfPosition; }
75 void pathTextBreakupHelper::freeB2DCubicBezierHelper()
77 if(mpB2DCubicBezierHelper)
79 delete mpB2DCubicBezierHelper;
80 mpB2DCubicBezierHelper = 0;
84 basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper()
86 if(!mpB2DCubicBezierHelper && maCurrentSegment.isBezier())
88 mpB2DCubicBezierHelper = new basegfx::B2DCubicBezierHelper(maCurrentSegment);
91 return mpB2DCubicBezierHelper;
94 void pathTextBreakupHelper::advanceToPosition(double fNewPosition)
96 while(mfSegmentStartPosition + mfCurrentSegmentLength < fNewPosition && mnIndex < mnMaxIndex)
98 mfSegmentStartPosition += mfCurrentSegmentLength;
99 mnIndex++;
101 if(mnIndex < mnMaxIndex)
103 freeB2DCubicBezierHelper();
104 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
105 maCurrentSegment.testAndSolveTrivialBezier();
106 mfCurrentSegmentLength = getB2DCubicBezierHelper()
107 ? getB2DCubicBezierHelper()->getLength()
108 : maCurrentSegment.getLength();
112 mfPosition = fNewPosition;
115 pathTextBreakupHelper::pathTextBreakupHelper(
116 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
117 const basegfx::B2DPolygon& rPolygon,
118 const double fBasegfxPathLength,
119 double fPosition,
120 const basegfx::B2DPoint& rTextStart)
121 : drawinglayer::primitive2d::TextBreakupHelper(rSource),
122 mrPolygon(rPolygon),
123 mfBasegfxPathLength(fBasegfxPathLength),
124 mfPosition(0.0),
125 mrTextStart(rTextStart),
126 mnMaxIndex(rPolygon.isClosed() ? rPolygon.count() : rPolygon.count() - 1),
127 mnIndex(0),
128 maCurrentSegment(),
129 mpB2DCubicBezierHelper(0),
130 mfCurrentSegmentLength(0.0),
131 mfSegmentStartPosition(0.0)
133 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
134 mfCurrentSegmentLength = maCurrentSegment.getLength();
136 advanceToPosition(fPosition);
139 pathTextBreakupHelper::~pathTextBreakupHelper()
141 freeB2DCubicBezierHelper();
144 bool pathTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength)
146 bool bRetval(false);
148 if(mfPosition < mfBasegfxPathLength && nLength && mnIndex < mnMaxIndex)
150 const double fSnippetWidth(
151 getTextLayouter().getTextWidth(
152 getSource().getText(),
153 nIndex,
154 nLength));
156 if(basegfx::fTools::more(fSnippetWidth, 0.0))
158 const OUString aText(getSource().getText());
159 const OUString aTrimmedChars(aText.copy(nIndex, nLength).trim());
160 const double fEndPos(mfPosition + fSnippetWidth);
162 if(aTrimmedChars.getLength() && (mfPosition < mfBasegfxPathLength || fEndPos > 0.0))
164 const double fHalfSnippetWidth(fSnippetWidth * 0.5);
166 advanceToPosition(mfPosition + fHalfSnippetWidth);
168 // create representation for this snippet
169 bRetval = true;
171 // get target position and tangent in that pint
172 basegfx::B2DPoint aPosition(0.0, 0.0);
173 basegfx::B2DVector aTangent(0.0, 1.0);
175 if(mfPosition < 0.0)
177 // snippet center is left of first segment, but right edge is on it (SVG allows that)
178 aTangent = maCurrentSegment.getTangent(0.0);
179 aTangent.normalize();
180 aPosition = maCurrentSegment.getStartPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
182 else if(mfPosition > mfBasegfxPathLength)
184 // snippet center is right of last segment, but left edge is on it (SVG allows that)
185 aTangent = maCurrentSegment.getTangent(1.0);
186 aTangent.normalize();
187 aPosition = maCurrentSegment.getEndPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
189 else
191 // snippet center inside segment, interpolate
192 double fBezierDistance(mfPosition - mfSegmentStartPosition);
194 if(getB2DCubicBezierHelper())
196 // use B2DCubicBezierHelper to bridge the non-linear gap between
197 // length and bezier distances (if it's a bezier segment)
198 fBezierDistance = getB2DCubicBezierHelper()->distanceToRelative(fBezierDistance);
200 else
202 // linear relationship, make relative to segment length
203 fBezierDistance = fBezierDistance / mfCurrentSegmentLength;
206 aPosition = maCurrentSegment.interpolatePoint(fBezierDistance);
207 aTangent = maCurrentSegment.getTangent(fBezierDistance);
208 aTangent.normalize();
211 // detect evtl. hor/ver translations (depends on text direction)
212 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
213 const basegfx::B2DVector aOffset(aBasePoint - mrTextStart);
215 if(!basegfx::fTools::equalZero(aOffset.getY()))
217 // ...and apply
218 aPosition.setY(aPosition.getY() + aOffset.getY());
221 // move target position from snippet center to left text start
222 aPosition -= fHalfSnippetWidth * aTangent;
224 // remove current translation
225 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
227 // rotate due to tangent
228 rNewTransform.rotate(atan2(aTangent.getY(), aTangent.getX()));
230 // add new translation
231 rNewTransform.translate(aPosition.getX(), aPosition.getY());
234 // advance to end
235 advanceToPosition(fEndPos);
239 return bRetval;
242 } // end of namespace svgreader
243 } // end of namespace svgio
245 //////////////////////////////////////////////////////////////////////////////
247 namespace svgio
249 namespace svgreader
251 SvgTextPathNode::SvgTextPathNode(
252 SvgDocument& rDocument,
253 SvgNode* pParent)
254 : SvgNode(SVGTokenTextPath, rDocument, pParent),
255 maSvgStyleAttributes(*this),
256 maXLink(),
257 maStartOffset(),
258 mbMethod(true),
259 mbSpacing(false)
263 SvgTextPathNode::~SvgTextPathNode()
267 const SvgStyleAttributes* SvgTextPathNode::getSvgStyleAttributes() const
269 return &maSvgStyleAttributes;
272 void SvgTextPathNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent)
274 // call parent
275 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
277 // read style attributes
278 maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent);
280 // parse own
281 switch(aSVGToken)
283 case SVGTokenStyle:
285 maSvgStyleAttributes.readStyle(aContent);
286 break;
288 case SVGTokenStartOffset:
290 SvgNumber aNum;
292 if(readSingleNumber(aContent, aNum))
294 if(aNum.isPositive())
296 setStartOffset(aNum);
299 break;
301 case SVGTokenMethod:
303 if(aContent.getLength())
305 static OUString aStrAlign(OUString::createFromAscii("align"));
306 static OUString aStrStretch(OUString::createFromAscii("stretch"));
308 if(aContent.match(aStrAlign))
310 setMethod(true);
312 else if(aContent.match(aStrStretch))
314 setMethod(false);
317 break;
319 case SVGTokenSpacing:
321 if(aContent.getLength())
323 static OUString aStrAuto(OUString::createFromAscii("auto"));
324 static OUString aStrExact(OUString::createFromAscii("exact"));
326 if(aContent.match(aStrAuto))
328 setSpacing(true);
330 else if(aContent.match(aStrExact))
332 setSpacing(false);
335 break;
337 case SVGTokenXlinkHref:
339 const sal_Int32 nLen(aContent.getLength());
341 if(nLen && sal_Unicode('#') == aContent[0])
343 maXLink = aContent.copy(1);
345 break;
347 default:
349 break;
354 bool SvgTextPathNode::isValid() const
356 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
358 if(!pSvgPathNode)
360 return false;
363 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath();
365 if(!pPolyPolyPath || !pPolyPolyPath->count())
367 return false;
370 const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
372 if(!aPolygon.count())
374 return false;
377 const double fBasegfxPathLength(basegfx::tools::getLength(aPolygon));
379 if(basegfx::fTools::equalZero(fBasegfxPathLength))
381 return false;
384 return true;
387 void SvgTextPathNode::decomposePathNode(
388 const drawinglayer::primitive2d::Primitive2DSequence& rPathContent,
389 drawinglayer::primitive2d::Primitive2DSequence& rTarget,
390 const basegfx::B2DPoint& rTextStart) const
392 if(rPathContent.hasElements())
394 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
396 if(pSvgPathNode)
398 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath();
400 if(pPolyPolyPath && pPolyPolyPath->count())
402 basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
404 if(pSvgPathNode->getTransform())
406 aPolygon.transform(*pSvgPathNode->getTransform());
409 const double fBasegfxPathLength(basegfx::tools::getLength(aPolygon));
411 if(!basegfx::fTools::equalZero(fBasegfxPathLength))
413 double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user
415 if(pSvgPathNode->getPathLength().isSet())
417 const double fUserLength(pSvgPathNode->getPathLength().solve(*this, length));
419 if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength))
421 fUserToBasegfx = fUserLength / fBasegfxPathLength;
425 double fPosition(0.0);
427 if(getStartOffset().isSet())
429 if(Unit_percent == getStartOffset().getUnit())
431 // percent are relative to path length
432 fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength;
434 else
436 fPosition = getStartOffset().solve(*this, length) * fUserToBasegfx;
440 if(fPosition >= 0.0)
442 const sal_Int32 nLength(rPathContent.getLength());
443 sal_Int32 nCurrent(0);
445 while(fPosition < fBasegfxPathLength && nCurrent < nLength)
447 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = 0;
448 const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]);
450 if(xReference.is())
452 pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get());
455 if(pCandidate)
457 const pathTextBreakupHelper aPathTextBreakupHelper(
458 *pCandidate,
459 aPolygon,
460 fBasegfxPathLength,
461 fPosition,
462 rTextStart);
464 const drawinglayer::primitive2d::Primitive2DSequence aResult(
465 aPathTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character));
467 if(aResult.hasElements())
469 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult);
472 // advance position to consumed
473 fPosition = aPathTextBreakupHelper.getPosition();
476 nCurrent++;
485 } // end of namespace svgreader
486 } // end of namespace svgio
488 //////////////////////////////////////////////////////////////////////////////
489 // eof
491 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */