LibreOffice Module svgio (master) 1
svgtextpathnode.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <svgtextpathnode.hxx>
22#include <svgpathnode.hxx>
23#include <svgdocument.hxx>
30#include <o3tl/string_view.hxx>
31
32namespace svgio::svgreader
33{
34 namespace {
35
36 class pathTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
37 {
38 private:
40 const double mfBasegfxPathLength;
41 double mfPosition;
43
44 const sal_uInt32 mnMaxIndex;
45 sal_uInt32 mnIndex;
47 std::unique_ptr<basegfx::B2DCubicBezierHelper> mpB2DCubicBezierHelper;
50
51 protected:
54 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
55
56 void freeB2DCubicBezierHelper();
57 basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper();
58 void advanceToPosition(double fNewPosition);
59
60 public:
61 pathTextBreakupHelper(
63 const basegfx::B2DPolygon& rPolygon,
64 const double fBasegfxPathLength,
65 double fPosition,
66 const basegfx::B2DPoint& rTextStart);
67 virtual ~pathTextBreakupHelper() override;
68
69 // read access to evtl. advanced position
70 double getPosition() const { return mfPosition; }
71 };
72
73 }
74
75 void pathTextBreakupHelper::freeB2DCubicBezierHelper()
76 {
78 }
79
80 basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper()
81 {
83 {
85 }
86
87 return mpB2DCubicBezierHelper.get();
88 }
89
90 void pathTextBreakupHelper::advanceToPosition(double fNewPosition)
91 {
93 {
95 mnIndex++;
96
98 {
99 freeB2DCubicBezierHelper();
102 mfCurrentSegmentLength = getB2DCubicBezierHelper()
103 ? getB2DCubicBezierHelper()->getLength()
105 }
106 }
107
108 mfPosition = fNewPosition;
109 }
110
111 pathTextBreakupHelper::pathTextBreakupHelper(
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),
126 {
129
130 advanceToPosition(fPosition);
131 }
132
133 pathTextBreakupHelper::~pathTextBreakupHelper()
134 {
135 freeB2DCubicBezierHelper();
136 }
137
138 bool pathTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength)
139 {
140 bool bRetval(false);
141
143 {
144 const double fSnippetWidth(
145 getTextLayouter().getTextWidth(
146 getSource().getText(),
147 nIndex,
148 nLength));
149
150 if(basegfx::fTools::more(fSnippetWidth, 0.0))
151 {
152 const OUString aText(getSource().getText());
153 const std::u16string_view aTrimmedChars(o3tl::trim(aText.subView(nIndex, nLength)));
154 const double fEndPos(mfPosition + fSnippetWidth);
155
156 if(!aTrimmedChars.empty() && (mfPosition < mfBasegfxPathLength || fEndPos > 0.0))
157 {
158 const double fHalfSnippetWidth(fSnippetWidth * 0.5);
159
160 advanceToPosition(mfPosition + fHalfSnippetWidth);
161
162 // create representation for this snippet
163 bRetval = true;
164
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);
168
169 if(mfPosition < 0.0)
170 {
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));
175 }
177 {
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));
182 }
183 else
184 {
185 // snippet center inside segment, interpolate
186 double fBezierDistance(mfPosition - mfSegmentStartPosition);
187
188 if(getB2DCubicBezierHelper())
189 {
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);
193 }
194 else
195 {
196 // linear relationship, make relative to segment length
197 fBezierDistance = fBezierDistance / mfCurrentSegmentLength;
198 }
199
200 aPosition = maCurrentSegment.interpolatePoint(fBezierDistance);
201 aTangent = maCurrentSegment.getTangent(fBezierDistance);
202 aTangent.normalize();
203 }
204
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);
208
209 if(!basegfx::fTools::equalZero(aOffset.getY()))
210 {
211 // ...and apply
212 aPosition.setY(aPosition.getY() + aOffset.getY());
213 }
214
215 // move target position from snippet center to left text start
216 aPosition -= fHalfSnippetWidth * aTangent;
217
218 // remove current translation
219 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
220
221 // rotate due to tangent
222 rNewTransform.rotate(atan2(aTangent.getY(), aTangent.getX()));
223
224 // add new translation
225 rNewTransform.translate(aPosition.getX(), aPosition.getY());
226 }
227
228 // advance to end
229 advanceToPosition(fEndPos);
230 }
231 }
232
233 return bRetval;
234 }
235
236} // end of namespace svgio::svgreader
237
238
239namespace svgio::svgreader
240{
242 SvgDocument& rDocument,
243 SvgNode* pParent)
244 : SvgNode(SVGToken::TextPath, rDocument, pParent),
245 maSvgStyleAttributes(*this)
246 {
247 }
248
250 {
251 }
252
254 {
255 return &maSvgStyleAttributes;
256 }
257
258 void SvgTextPathNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent)
259 {
260 // call parent
261 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
262
263 // read style attributes
264 maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
265
266 // parse own
267 switch(aSVGToken)
268 {
269 case SVGToken::Style:
270 {
271 readLocalCssStyle(aContent);
272 break;
273 }
275 {
276 SvgNumber aNum;
277
278 if(readSingleNumber(aContent, aNum))
279 {
280 if(aNum.isPositive())
281 {
282 maStartOffset = aNum;
283 }
284 }
285 break;
286 }
287 case SVGToken::Method:
288 {
289 break;
290 }
292 {
293 break;
294 }
295 case SVGToken::Href:
297 {
298 readLocalLink(aContent, maXLink);
299 break;
300 }
301 default:
302 {
303 break;
304 }
305 }
306 }
307
309 {
310 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
311
312 if(!pSvgPathNode)
313 {
314 return false;
315 }
316
317 const std::optional<basegfx::B2DPolyPolygon>& pPolyPolyPath = pSvgPathNode->getPath();
318
319 if(!pPolyPolyPath || !pPolyPolyPath->count())
320 {
321 return false;
322 }
323
324 const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
325
326 if(!aPolygon.count())
327 {
328 return false;
329 }
330
331 const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
332
333 return !basegfx::fTools::equalZero(fBasegfxPathLength);
334 }
335
339 const basegfx::B2DPoint& rTextStart) const
340 {
341 if(rPathContent.empty())
342 return;
343
344 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
345
346 if(!pSvgPathNode)
347 return;
348
349 const std::optional<basegfx::B2DPolyPolygon>& pPolyPolyPath = pSvgPathNode->getPath();
350
351 if(!(pPolyPolyPath && pPolyPolyPath->count()))
352 return;
353
354 basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
355
356 if(pSvgPathNode->getTransform())
357 {
358 aPolygon.transform(*pSvgPathNode->getTransform());
359 }
360
361 const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
362
363 if(basegfx::fTools::equalZero(fBasegfxPathLength))
364 return;
365
366 double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user
367
368 if(pSvgPathNode->getPathLength().isSet())
369 {
370 const double fUserLength(pSvgPathNode->getPathLength().solve(*this));
371
372 if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength))
373 {
374 fUserToBasegfx = fUserLength / fBasegfxPathLength;
375 }
376 }
377
378 double fPosition(0.0);
379
380 if(getStartOffset().isSet())
381 {
382 if (SvgUnit::percent == getStartOffset().getUnit())
383 {
384 // percent are relative to path length
385 fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength;
386 }
387 else
388 {
389 fPosition = getStartOffset().solve(*this) * fUserToBasegfx;
390 }
391 }
392
393 if(fPosition < 0.0)
394 return;
395
396 const sal_Int32 nLength(rPathContent.size());
397 sal_Int32 nCurrent(0);
398
399 while(fPosition < fBasegfxPathLength && nCurrent < nLength)
400 {
402 const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]);
403
404 if(xReference.is())
405 {
406 pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get());
407 }
408
409 if(pCandidate)
410 {
411 pathTextBreakupHelper aPathTextBreakupHelper(
412 *pCandidate,
413 aPolygon,
414 fBasegfxPathLength,
415 fPosition,
416 rTextStart);
417
419 aPathTextBreakupHelper.extractResult();
420
421 if(!aResult.empty())
422 {
423 rTarget.append(std::move(aResult));
424 }
425
426 // advance position to consumed
427 fPosition = aPathTextBreakupHelper.getPosition();
428 }
429
430 nCurrent++;
431 }
432 }
433
434} // end of namespace svgio
435
436/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
TextPath
const B2DPoint & getStartPoint() const
double getLength(double fDeviation=0.01) const
B2DPoint interpolatePoint(double t) const
const B2DPoint & getEndPoint() const
B2DVector getTangent(double t) const
void rotate(double fRadiant)
void translate(double fX, double fY)
void transform(const basegfx::B2DHomMatrix &rMatrix)
void getBezierSegment(sal_uInt32 nIndex, B2DCubicBezier &rTarget) const
sal_uInt32 count() const
B2DVector & normalize()
const SvgNode * findSvgNodeById(const OUString &rStr) const
find a node by its Id
Definition: svgdocument.cxx:56
virtual void parseAttribute(const OUString &rTokenName, SVGToken aSVGToken, const OUString &aContent)
Definition: svgnode.cxx:534
void readLocalCssStyle(std::u16string_view aContent)
scan helper to read and interpret a local CssStyle to mpLocalCssStyle
Definition: svgnode.cxx:412
const SvgDocument & getDocument() const
Definition: svgnode.hxx:157
double solve(const InfoProvider &rInfoProvider, NumberType aNumberType=NumberType::length) const
Definition: SvgNumber.cxx:69
double getNumber() const
Definition: SvgNumber.hxx:85
const std::optional< basegfx::B2DPolyPolygon > & getPath() const
path content, set if found in current context
Definition: svgpathnode.hxx:54
const SvgNumber & getPathLength() const
PathLength content.
Definition: svgpathnode.hxx:62
const std::optional< basegfx::B2DHomMatrix > & getTransform() const
transform content, set if found in current context
Definition: svgpathnode.hxx:58
void parseStyleAttribute(SVGToken aSVGToken, const OUString &rContent)
local attribute scanner
SvgTextPathNode(SvgDocument &rDocument, SvgNode *pParent)
SvgStyleAttributes maSvgStyleAttributes
use styles
virtual const SvgStyleAttributes * getSvgStyleAttributes() const override
const SvgNumber & getStartOffset() const
StartOffset content.
SvgNumber maStartOffset
variable scan values, dependent of given XAttributeList
OUString maXLink
link to path content.
void decomposePathNode(const drawinglayer::primitive2d::Primitive2DContainer &rPathContent, drawinglayer::primitive2d::Primitive2DContainer &rTarget, const basegfx::B2DPoint &rTextStart) const
virtual void parseAttribute(const OUString &rTokenName, SVGToken aSVGToken, const OUString &aContent) override
FilterGroup & rTarget
bool more(const T &rfValA, const T &rfValB)
bool equalZero(const T &rfVal)
bool equal(T const &rfValA, T const &rfValB)
double getLength(const B2DPolygon &rCandidate)
std::basic_string_view< charT, traits > trim(std::basic_string_view< charT, traits > str)
bool readLocalLink(std::u16string_view rCandidate, OUString &rURL)
Definition: svgtools.cxx:1084
bool readSingleNumber(std::u16string_view rCandidate, SvgNumber &aNum)
Definition: svgtools.cxx:1076
const sal_uInt32 mnMaxIndex
const double mfBasegfxPathLength
double mfPosition
double mfSegmentStartPosition
basegfx::B2DCubicBezier maCurrentSegment
sal_uInt32 mnIndex
const basegfx::B2DPolygon & mrPolygon
std::unique_ptr< basegfx::B2DCubicBezierHelper > mpB2DCubicBezierHelper
double mfCurrentSegmentLength
const basegfx::B2DPoint & mrTextStart
sal_Int32 nLength