calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / svgio / source / svgreader / svgtextpathnode.cxx
blob2c5a823566edc3c83c5bfdb11a4f4ccdc9a37440
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>
30 #include <o3tl/string_view.hxx>
32 namespace svgio::svgreader
34 namespace {
36 class pathTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
38 private:
39 const basegfx::B2DPolygon& mrPolygon;
40 const double mfBasegfxPathLength;
41 double mfPosition;
42 const basegfx::B2DPoint& mrTextStart;
44 const sal_uInt32 mnMaxIndex;
45 sal_uInt32 mnIndex;
46 basegfx::B2DCubicBezier maCurrentSegment;
47 std::unique_ptr<basegfx::B2DCubicBezierHelper> mpB2DCubicBezierHelper;
48 double mfCurrentSegmentLength;
49 double mfSegmentStartPosition;
51 protected:
52 /// allow user callback to allow changes to the new TextTransformation. Default
53 /// does nothing.
54 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
56 void freeB2DCubicBezierHelper();
57 basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper();
58 void advanceToPosition(double fNewPosition);
60 public:
61 pathTextBreakupHelper(
62 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
63 const basegfx::B2DPolygon& rPolygon,
64 const double fBasegfxPathLength,
65 double fPosition,
66 const basegfx::B2DPoint& rTextStart);
67 virtual ~pathTextBreakupHelper() override;
69 // read access to evtl. advanced position
70 double getPosition() const { return mfPosition; }
75 void pathTextBreakupHelper::freeB2DCubicBezierHelper()
77 mpB2DCubicBezierHelper.reset();
80 basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper()
82 if(!mpB2DCubicBezierHelper && maCurrentSegment.isBezier())
84 mpB2DCubicBezierHelper.reset(new basegfx::B2DCubicBezierHelper(maCurrentSegment));
87 return mpB2DCubicBezierHelper.get();
90 void pathTextBreakupHelper::advanceToPosition(double fNewPosition)
92 while(mfSegmentStartPosition + mfCurrentSegmentLength < fNewPosition && mnIndex < mnMaxIndex)
94 mfSegmentStartPosition += mfCurrentSegmentLength;
95 mnIndex++;
97 if(mnIndex < mnMaxIndex)
99 freeB2DCubicBezierHelper();
100 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
101 maCurrentSegment.testAndSolveTrivialBezier();
102 mfCurrentSegmentLength = getB2DCubicBezierHelper()
103 ? getB2DCubicBezierHelper()->getLength()
104 : maCurrentSegment.getLength();
108 mfPosition = fNewPosition;
111 pathTextBreakupHelper::pathTextBreakupHelper(
112 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
113 const basegfx::B2DPolygon& rPolygon,
114 const double fBasegfxPathLength,
115 double fPosition,
116 const basegfx::B2DPoint& rTextStart)
117 : drawinglayer::primitive2d::TextBreakupHelper(rSource),
118 mrPolygon(rPolygon),
119 mfBasegfxPathLength(fBasegfxPathLength),
120 mfPosition(0.0),
121 mrTextStart(rTextStart),
122 mnMaxIndex(rPolygon.isClosed() ? rPolygon.count() : rPolygon.count() - 1),
123 mnIndex(0),
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 std::u16string_view aTrimmedChars(o3tl::trim(aText.subView(nIndex, nLength)));
154 const double fEndPos(mfPosition + fSnippetWidth);
156 if(!aTrimmedChars.empty() && (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)
249 SvgTextPathNode::~SvgTextPathNode()
253 const SvgStyleAttributes* SvgTextPathNode::getSvgStyleAttributes() const
255 return &maSvgStyleAttributes;
258 void SvgTextPathNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent)
260 // call parent
261 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
263 // read style attributes
264 maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
266 // parse own
267 switch(aSVGToken)
269 case SVGToken::Style:
271 readLocalCssStyle(aContent);
272 break;
274 case SVGToken::StartOffset:
276 SvgNumber aNum;
278 if(readSingleNumber(aContent, aNum))
280 if(aNum.isPositive())
282 maStartOffset = aNum;
285 break;
287 case SVGToken::Method:
289 break;
291 case SVGToken::Spacing:
293 break;
295 case SVGToken::Href:
296 case SVGToken::XlinkHref:
298 readLocalLink(aContent, maXLink);
299 break;
301 default:
303 break;
308 bool SvgTextPathNode::isValid() const
310 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
312 if(!pSvgPathNode)
314 return false;
317 const std::optional<basegfx::B2DPolyPolygon>& pPolyPolyPath = pSvgPathNode->getPath();
319 if(!pPolyPolyPath || !pPolyPolyPath->count())
321 return false;
324 const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
326 if(!aPolygon.count())
328 return false;
331 const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
333 return !basegfx::fTools::equalZero(fBasegfxPathLength);
336 void SvgTextPathNode::decomposePathNode(
337 const drawinglayer::primitive2d::Primitive2DContainer& rPathContent,
338 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
339 const basegfx::B2DPoint& rTextStart) const
341 if(rPathContent.empty())
342 return;
344 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
346 if(!pSvgPathNode)
347 return;
349 const std::optional<basegfx::B2DPolyPolygon>& pPolyPolyPath = pSvgPathNode->getPath();
351 if(!(pPolyPolyPath && pPolyPolyPath->count()))
352 return;
354 basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
356 if(pSvgPathNode->getTransform())
358 aPolygon.transform(*pSvgPathNode->getTransform());
361 const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
363 if(basegfx::fTools::equalZero(fBasegfxPathLength))
364 return;
366 double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user
368 if(pSvgPathNode->getPathLength().isSet())
370 const double fUserLength(pSvgPathNode->getPathLength().solve(*this));
372 if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength))
374 fUserToBasegfx = fUserLength / fBasegfxPathLength;
378 double fPosition(0.0);
380 if(getStartOffset().isSet())
382 if (SvgUnit::percent == getStartOffset().getUnit())
384 // percent are relative to path length
385 fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength;
387 else
389 fPosition = getStartOffset().solve(*this) * fUserToBasegfx;
393 if(fPosition < 0.0)
394 return;
396 const sal_Int32 nLength(rPathContent.size());
397 sal_Int32 nCurrent(0);
399 while(fPosition < fBasegfxPathLength && nCurrent < nLength)
401 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = nullptr;
402 const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]);
404 if(xReference.is())
406 pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get());
409 if(pCandidate)
411 pathTextBreakupHelper aPathTextBreakupHelper(
412 *pCandidate,
413 aPolygon,
414 fBasegfxPathLength,
415 fPosition,
416 rTextStart);
418 drawinglayer::primitive2d::Primitive2DContainer aResult =
419 aPathTextBreakupHelper.extractResult();
421 if(!aResult.empty())
423 rTarget.append(std::move(aResult));
426 // advance position to consumed
427 fPosition = aPathTextBreakupHelper.getPosition();
430 nCurrent++;
434 } // end of namespace svgio
436 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */