Version 4.0.0.1, tag libreoffice-4.0.0.1
[LibreOffice.git] / svgio / source / svgreader / svgtextpathnode.cxx
blobc06b6b7ca08ce895d9b97a5944ea3ca315d87355
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 const double mfUserToBasegfx;
44 double mfPosition;
45 const basegfx::B2DPoint& mrTextStart;
47 const sal_uInt32 mnMaxIndex;
48 sal_uInt32 mnIndex;
49 basegfx::B2DCubicBezier maCurrentSegment;
50 basegfx::B2DCubicBezierHelper* mpB2DCubicBezierHelper;
51 double mfCurrentSegmentLength;
52 double mfSegmentStartPosition;
54 protected:
55 /// allow user callback to allow changes to the new TextTransformation. Default
56 /// does nothing.
57 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength);
59 void freeB2DCubicBezierHelper();
60 basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper();
61 void advanceToPosition(double fNewPosition);
63 public:
64 pathTextBreakupHelper(
65 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
66 const basegfx::B2DPolygon& rPolygon,
67 const double fBasegfxPathLength,
68 const double fUserToBasegfx,
69 double fPosition,
70 const basegfx::B2DPoint& rTextStart);
71 virtual ~pathTextBreakupHelper();
73 // read access to evtl. advanced position
74 double getPosition() const { return mfPosition; }
77 void pathTextBreakupHelper::freeB2DCubicBezierHelper()
79 if(mpB2DCubicBezierHelper)
81 delete mpB2DCubicBezierHelper;
82 mpB2DCubicBezierHelper = 0;
86 basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper()
88 if(!mpB2DCubicBezierHelper && maCurrentSegment.isBezier())
90 mpB2DCubicBezierHelper = new basegfx::B2DCubicBezierHelper(maCurrentSegment);
93 return mpB2DCubicBezierHelper;
96 void pathTextBreakupHelper::advanceToPosition(double fNewPosition)
98 while(mfSegmentStartPosition + mfCurrentSegmentLength < fNewPosition && mnIndex < mnMaxIndex)
100 mfSegmentStartPosition += mfCurrentSegmentLength;
101 mnIndex++;
103 if(mnIndex < mnMaxIndex)
105 freeB2DCubicBezierHelper();
106 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
107 maCurrentSegment.testAndSolveTrivialBezier();
108 mfCurrentSegmentLength = getB2DCubicBezierHelper()
109 ? getB2DCubicBezierHelper()->getLength()
110 : maCurrentSegment.getLength();
114 mfPosition = fNewPosition;
117 pathTextBreakupHelper::pathTextBreakupHelper(
118 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
119 const basegfx::B2DPolygon& rPolygon,
120 const double fBasegfxPathLength,
121 const double fUserToBasegfx,
122 double fPosition,
123 const basegfx::B2DPoint& rTextStart)
124 : drawinglayer::primitive2d::TextBreakupHelper(rSource),
125 mrPolygon(rPolygon),
126 mfBasegfxPathLength(fBasegfxPathLength),
127 mfUserToBasegfx(fUserToBasegfx),
128 mfPosition(0.0),
129 mrTextStart(rTextStart),
130 mnMaxIndex(rPolygon.isClosed() ? rPolygon.count() : rPolygon.count() - 1),
131 mnIndex(0),
132 maCurrentSegment(),
133 mpB2DCubicBezierHelper(0),
134 mfCurrentSegmentLength(0.0),
135 mfSegmentStartPosition(0.0)
137 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
138 mfCurrentSegmentLength = maCurrentSegment.getLength();
140 advanceToPosition(fPosition);
143 pathTextBreakupHelper::~pathTextBreakupHelper()
145 freeB2DCubicBezierHelper();
148 bool pathTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength)
150 bool bRetval(false);
152 if(mfPosition < mfBasegfxPathLength && nLength && mnIndex < mnMaxIndex)
154 const double fSnippetWidth(
155 getTextLayouter().getTextWidth(
156 getSource().getText(),
157 nIndex,
158 nLength));
160 if(basegfx::fTools::more(fSnippetWidth, 0.0))
162 const ::rtl::OUString aText(getSource().getText());
163 const ::rtl::OUString aTrimmedChars(aText.copy(nIndex, nLength).trim());
164 const double fEndPos(mfPosition + fSnippetWidth);
166 if(aTrimmedChars.getLength() && (mfPosition < mfBasegfxPathLength || fEndPos > 0.0))
168 const double fHalfSnippetWidth(fSnippetWidth * 0.5);
170 advanceToPosition(mfPosition + fHalfSnippetWidth);
172 // create representation for this snippet
173 bRetval = true;
175 // get target position and tangent in that pint
176 basegfx::B2DPoint aPosition(0.0, 0.0);
177 basegfx::B2DVector aTangent(0.0, 1.0);
179 if(mfPosition < 0.0)
181 // snippet center is left of first segment, but right edge is on it (SVG allows that)
182 aTangent = maCurrentSegment.getTangent(0.0);
183 aTangent.normalize();
184 aPosition = maCurrentSegment.getStartPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
186 else if(mfPosition > mfBasegfxPathLength)
188 // snippet center is right of last segment, but left edge is on it (SVG allows that)
189 aTangent = maCurrentSegment.getTangent(1.0);
190 aTangent.normalize();
191 aPosition = maCurrentSegment.getEndPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
193 else
195 // snippet center inside segment, interpolate
196 double fBezierDistance(mfPosition - mfSegmentStartPosition);
198 if(getB2DCubicBezierHelper())
200 // use B2DCubicBezierHelper to bridge the non-linear gap between
201 // length and bezier distances (if it's a bezier segment)
202 fBezierDistance = getB2DCubicBezierHelper()->distanceToRelative(fBezierDistance);
204 else
206 // linear relationship, make relative to segment length
207 fBezierDistance = fBezierDistance / mfCurrentSegmentLength;
210 aPosition = maCurrentSegment.interpolatePoint(fBezierDistance);
211 aTangent = maCurrentSegment.getTangent(fBezierDistance);
212 aTangent.normalize();
215 // detect evtl. hor/ver translations (depends on text direction)
216 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
217 const basegfx::B2DVector aOffset(aBasePoint - mrTextStart);
219 if(!basegfx::fTools::equalZero(aOffset.getY()))
221 // ...and apply
222 aPosition.setY(aPosition.getY() + aOffset.getY());
225 // move target position from snippet center to left text start
226 aPosition -= fHalfSnippetWidth * aTangent;
228 // remove current translation
229 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
231 // rotate due to tangent
232 rNewTransform.rotate(atan2(aTangent.getY(), aTangent.getX()));
234 // add new translation
235 rNewTransform.translate(aPosition.getX(), aPosition.getY());
238 // advance to end
239 advanceToPosition(fEndPos);
243 return bRetval;
246 } // end of namespace svgreader
247 } // end of namespace svgio
249 //////////////////////////////////////////////////////////////////////////////
251 namespace svgio
253 namespace svgreader
255 SvgTextPathNode::SvgTextPathNode(
256 SvgDocument& rDocument,
257 SvgNode* pParent)
258 : SvgNode(SVGTokenTextPath, rDocument, pParent),
259 maSvgStyleAttributes(*this),
260 maXLink(),
261 maStartOffset(),
262 mbMethod(true),
263 mbSpacing(false)
267 SvgTextPathNode::~SvgTextPathNode()
271 const SvgStyleAttributes* SvgTextPathNode::getSvgStyleAttributes() const
273 return &maSvgStyleAttributes;
276 void SvgTextPathNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent)
278 // call parent
279 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
281 // read style attributes
282 maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent);
284 // parse own
285 switch(aSVGToken)
287 case SVGTokenStyle:
289 maSvgStyleAttributes.readStyle(aContent);
290 break;
292 case SVGTokenStartOffset:
294 SvgNumber aNum;
296 if(readSingleNumber(aContent, aNum))
298 if(aNum.isPositive())
300 setStartOffset(aNum);
303 break;
305 case SVGTokenMethod:
307 if(aContent.getLength())
309 static rtl::OUString aStrAlign(rtl::OUString::createFromAscii("align"));
310 static rtl::OUString aStrStretch(rtl::OUString::createFromAscii("stretch"));
312 if(aContent.match(aStrAlign))
314 setMethod(true);
316 else if(aContent.match(aStrStretch))
318 setMethod(false);
321 break;
323 case SVGTokenSpacing:
325 if(aContent.getLength())
327 static rtl::OUString aStrAuto(rtl::OUString::createFromAscii("auto"));
328 static rtl::OUString aStrExact(rtl::OUString::createFromAscii("exact"));
330 if(aContent.match(aStrAuto))
332 setSpacing(true);
334 else if(aContent.match(aStrExact))
336 setSpacing(false);
339 break;
341 case SVGTokenXlinkHref:
343 const sal_Int32 nLen(aContent.getLength());
345 if(nLen && sal_Unicode('#') == aContent[0])
347 maXLink = aContent.copy(1);
349 break;
351 default:
353 break;
358 bool SvgTextPathNode::isValid() const
360 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
362 if(!pSvgPathNode)
364 return false;
367 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath();
369 if(!pPolyPolyPath || !pPolyPolyPath->count())
371 return false;
374 const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
376 if(!aPolygon.count())
378 return false;
381 const double fBasegfxPathLength(basegfx::tools::getLength(aPolygon));
383 if(basegfx::fTools::equalZero(fBasegfxPathLength))
385 return false;
388 return true;
391 void SvgTextPathNode::decomposePathNode(
392 const drawinglayer::primitive2d::Primitive2DSequence& rPathContent,
393 drawinglayer::primitive2d::Primitive2DSequence& rTarget,
394 const basegfx::B2DPoint& rTextStart) const
396 if(rPathContent.hasElements())
398 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
400 if(pSvgPathNode)
402 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath();
404 if(pPolyPolyPath && pPolyPolyPath->count())
406 basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
408 if(pSvgPathNode->getTransform())
410 aPolygon.transform(*pSvgPathNode->getTransform());
413 const double fBasegfxPathLength(basegfx::tools::getLength(aPolygon));
415 if(!basegfx::fTools::equalZero(fBasegfxPathLength))
417 double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user
419 if(pSvgPathNode->getPathLength().isSet())
421 const double fUserLength(pSvgPathNode->getPathLength().solve(*this, length));
423 if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength))
425 fUserToBasegfx = fUserLength / fBasegfxPathLength;
429 double fPosition(0.0);
431 if(getStartOffset().isSet())
433 if(Unit_percent == getStartOffset().getUnit())
435 // percent are relative to path length
436 fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength;
438 else
440 fPosition = getStartOffset().solve(*this, length) * fUserToBasegfx;
444 if(fPosition >= 0.0)
446 const sal_Int32 nLength(rPathContent.getLength());
447 sal_Int32 nCurrent(0);
449 while(fPosition < fBasegfxPathLength && nCurrent < nLength)
451 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = 0;
452 const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]);
454 if(xReference.is())
456 pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get());
459 if(pCandidate)
461 const pathTextBreakupHelper aPathTextBreakupHelper(
462 *pCandidate,
463 aPolygon,
464 fBasegfxPathLength,
465 fUserToBasegfx,
466 fPosition,
467 rTextStart);
469 const drawinglayer::primitive2d::Primitive2DSequence aResult(
470 aPathTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character));
472 if(aResult.hasElements())
474 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult);
477 // advance position to consumed
478 fPosition = aPathTextBreakupHelper.getPosition();
481 nCurrent++;
490 } // end of namespace svgreader
491 } // end of namespace svgio
493 //////////////////////////////////////////////////////////////////////////////
494 // eof
496 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */