bump product version to 7.2.5.1
[LibreOffice.git] / svgio / source / svgreader / svgtextpathnode.cxx
blob210d674b09ac5c94721749de221426f18389f0cf
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 <svgtextpathnode.hxx>
21 #include <svgstyleattributes.hxx>
22 #include <svgpathnode.hxx>
23 #include <svgdocument.hxx>
24 #include <basegfx/polygon/b2dpolygon.hxx>
25 #include <basegfx/polygon/b2dpolygontools.hxx>
26 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
27 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
28 #include <basegfx/curve/b2dcubicbezier.hxx>
29 #include <basegfx/curve/b2dbeziertools.hxx>
31 namespace svgio::svgreader
33 namespace {
35 class pathTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
37 private:
38 const basegfx::B2DPolygon& mrPolygon;
39 const double mfBasegfxPathLength;
40 double mfPosition;
41 const basegfx::B2DPoint& mrTextStart;
43 const sal_uInt32 mnMaxIndex;
44 sal_uInt32 mnIndex;
45 basegfx::B2DCubicBezier maCurrentSegment;
46 std::unique_ptr<basegfx::B2DCubicBezierHelper> mpB2DCubicBezierHelper;
47 double mfCurrentSegmentLength;
48 double mfSegmentStartPosition;
50 protected:
51 /// allow user callback to allow changes to the new TextTransformation. Default
52 /// does nothing.
53 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
55 void freeB2DCubicBezierHelper();
56 basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper();
57 void advanceToPosition(double fNewPosition);
59 public:
60 pathTextBreakupHelper(
61 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
62 const basegfx::B2DPolygon& rPolygon,
63 const double fBasegfxPathLength,
64 double fPosition,
65 const basegfx::B2DPoint& rTextStart);
66 virtual ~pathTextBreakupHelper() override;
68 // read access to evtl. advanced position
69 double getPosition() const { return mfPosition; }
74 void pathTextBreakupHelper::freeB2DCubicBezierHelper()
76 mpB2DCubicBezierHelper.reset();
79 basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper()
81 if(!mpB2DCubicBezierHelper && maCurrentSegment.isBezier())
83 mpB2DCubicBezierHelper.reset(new basegfx::B2DCubicBezierHelper(maCurrentSegment));
86 return mpB2DCubicBezierHelper.get();
89 void pathTextBreakupHelper::advanceToPosition(double fNewPosition)
91 while(mfSegmentStartPosition + mfCurrentSegmentLength < fNewPosition && mnIndex < mnMaxIndex)
93 mfSegmentStartPosition += mfCurrentSegmentLength;
94 mnIndex++;
96 if(mnIndex < mnMaxIndex)
98 freeB2DCubicBezierHelper();
99 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
100 maCurrentSegment.testAndSolveTrivialBezier();
101 mfCurrentSegmentLength = getB2DCubicBezierHelper()
102 ? getB2DCubicBezierHelper()->getLength()
103 : maCurrentSegment.getLength();
107 mfPosition = fNewPosition;
110 pathTextBreakupHelper::pathTextBreakupHelper(
111 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
112 const basegfx::B2DPolygon& rPolygon,
113 const double fBasegfxPathLength,
114 double fPosition,
115 const basegfx::B2DPoint& rTextStart)
116 : drawinglayer::primitive2d::TextBreakupHelper(rSource),
117 mrPolygon(rPolygon),
118 mfBasegfxPathLength(fBasegfxPathLength),
119 mfPosition(0.0),
120 mrTextStart(rTextStart),
121 mnMaxIndex(rPolygon.isClosed() ? rPolygon.count() : rPolygon.count() - 1),
122 mnIndex(0),
123 maCurrentSegment(),
124 mfCurrentSegmentLength(0.0),
125 mfSegmentStartPosition(0.0)
127 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
128 mfCurrentSegmentLength = maCurrentSegment.getLength();
130 advanceToPosition(fPosition);
133 pathTextBreakupHelper::~pathTextBreakupHelper()
135 freeB2DCubicBezierHelper();
138 bool pathTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength)
140 bool bRetval(false);
142 if(mfPosition < mfBasegfxPathLength && nLength && mnIndex < mnMaxIndex)
144 const double fSnippetWidth(
145 getTextLayouter().getTextWidth(
146 getSource().getText(),
147 nIndex,
148 nLength));
150 if(basegfx::fTools::more(fSnippetWidth, 0.0))
152 const OUString aText(getSource().getText());
153 const OUString aTrimmedChars(aText.copy(nIndex, nLength).trim());
154 const double fEndPos(mfPosition + fSnippetWidth);
156 if(!aTrimmedChars.isEmpty() && (mfPosition < mfBasegfxPathLength || fEndPos > 0.0))
158 const double fHalfSnippetWidth(fSnippetWidth * 0.5);
160 advanceToPosition(mfPosition + fHalfSnippetWidth);
162 // create representation for this snippet
163 bRetval = true;
165 // get target position and tangent in that point
166 basegfx::B2DPoint aPosition(0.0, 0.0);
167 basegfx::B2DVector aTangent(0.0, 1.0);
169 if(mfPosition < 0.0)
171 // snippet center is left of first segment, but right edge is on it (SVG allows that)
172 aTangent = maCurrentSegment.getTangent(0.0);
173 aTangent.normalize();
174 aPosition = maCurrentSegment.getStartPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
176 else if(mfPosition > mfBasegfxPathLength)
178 // snippet center is right of last segment, but left edge is on it (SVG allows that)
179 aTangent = maCurrentSegment.getTangent(1.0);
180 aTangent.normalize();
181 aPosition = maCurrentSegment.getEndPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
183 else
185 // snippet center inside segment, interpolate
186 double fBezierDistance(mfPosition - mfSegmentStartPosition);
188 if(getB2DCubicBezierHelper())
190 // use B2DCubicBezierHelper to bridge the non-linear gap between
191 // length and bezier distances (if it's a bezier segment)
192 fBezierDistance = getB2DCubicBezierHelper()->distanceToRelative(fBezierDistance);
194 else
196 // linear relationship, make relative to segment length
197 fBezierDistance = fBezierDistance / mfCurrentSegmentLength;
200 aPosition = maCurrentSegment.interpolatePoint(fBezierDistance);
201 aTangent = maCurrentSegment.getTangent(fBezierDistance);
202 aTangent.normalize();
205 // detect evtl. hor/ver translations (depends on text direction)
206 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
207 const basegfx::B2DVector aOffset(aBasePoint - mrTextStart);
209 if(!basegfx::fTools::equalZero(aOffset.getY()))
211 // ...and apply
212 aPosition.setY(aPosition.getY() + aOffset.getY());
215 // move target position from snippet center to left text start
216 aPosition -= fHalfSnippetWidth * aTangent;
218 // remove current translation
219 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
221 // rotate due to tangent
222 rNewTransform.rotate(atan2(aTangent.getY(), aTangent.getX()));
224 // add new translation
225 rNewTransform.translate(aPosition.getX(), aPosition.getY());
228 // advance to end
229 advanceToPosition(fEndPos);
233 return bRetval;
236 } // end of namespace svgio::svgreader
239 namespace svgio::svgreader
241 SvgTextPathNode::SvgTextPathNode(
242 SvgDocument& rDocument,
243 SvgNode* pParent)
244 : SvgNode(SVGToken::TextPath, rDocument, pParent),
245 maSvgStyleAttributes(*this),
246 maXLink(),
247 maStartOffset()
251 SvgTextPathNode::~SvgTextPathNode()
255 const SvgStyleAttributes* SvgTextPathNode::getSvgStyleAttributes() const
257 return &maSvgStyleAttributes;
260 void SvgTextPathNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent)
262 // call parent
263 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
265 // read style attributes
266 maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent, false);
268 // parse own
269 switch(aSVGToken)
271 case SVGToken::Style:
273 readLocalCssStyle(aContent);
274 break;
276 case SVGToken::StartOffset:
278 SvgNumber aNum;
280 if(readSingleNumber(aContent, aNum))
282 if(aNum.isPositive())
284 maStartOffset = aNum;
287 break;
289 case SVGToken::Method:
291 break;
293 case SVGToken::Spacing:
295 break;
297 case SVGToken::XlinkHref:
299 const sal_Int32 nLen(aContent.getLength());
301 if(nLen && '#' == aContent[0])
303 maXLink = aContent.copy(1);
305 break;
307 default:
309 break;
314 bool SvgTextPathNode::isValid() const
316 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
318 if(!pSvgPathNode)
320 return false;
323 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath();
325 if(!pPolyPolyPath || !pPolyPolyPath->count())
327 return false;
330 const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
332 if(!aPolygon.count())
334 return false;
337 const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
339 return !basegfx::fTools::equalZero(fBasegfxPathLength);
342 void SvgTextPathNode::decomposePathNode(
343 const drawinglayer::primitive2d::Primitive2DContainer& rPathContent,
344 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
345 const basegfx::B2DPoint& rTextStart) const
347 if(rPathContent.empty())
348 return;
350 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
352 if(!pSvgPathNode)
353 return;
355 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath();
357 if(!(pPolyPolyPath && pPolyPolyPath->count()))
358 return;
360 basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
362 if(pSvgPathNode->getTransform())
364 aPolygon.transform(*pSvgPathNode->getTransform());
367 const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
369 if(basegfx::fTools::equalZero(fBasegfxPathLength))
370 return;
372 double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user
374 if(pSvgPathNode->getPathLength().isSet())
376 const double fUserLength(pSvgPathNode->getPathLength().solve(*this));
378 if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength))
380 fUserToBasegfx = fUserLength / fBasegfxPathLength;
384 double fPosition(0.0);
386 if(getStartOffset().isSet())
388 if (SvgUnit::percent == getStartOffset().getUnit())
390 // percent are relative to path length
391 fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength;
393 else
395 fPosition = getStartOffset().solve(*this) * fUserToBasegfx;
399 if(fPosition < 0.0)
400 return;
402 const sal_Int32 nLength(rPathContent.size());
403 sal_Int32 nCurrent(0);
405 while(fPosition < fBasegfxPathLength && nCurrent < nLength)
407 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = nullptr;
408 const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]);
410 if(xReference.is())
412 pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get());
415 if(pCandidate)
417 const pathTextBreakupHelper aPathTextBreakupHelper(
418 *pCandidate,
419 aPolygon,
420 fBasegfxPathLength,
421 fPosition,
422 rTextStart);
424 const drawinglayer::primitive2d::Primitive2DContainer& aResult(
425 aPathTextBreakupHelper.getResult());
427 if(!aResult.empty())
429 rTarget.append(aResult);
432 // advance position to consumed
433 fPosition = aPathTextBreakupHelper.getPosition();
436 nCurrent++;
440 } // end of namespace svgio
442 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */