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 <svgcharacternode.hxx>
21 #include <svgstyleattributes.hxx>
22 #include <drawinglayer/attribute/fontattribute.hxx>
23 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
24 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
25 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
26 #include <drawinglayer/primitive2d/groupprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
28 
29 namespace svgio
30 {
31     namespace svgreader
32     {
SvgTextPositions()33         SvgTextPositions::SvgTextPositions()
34         :   maX(),
35             maY(),
36             maDx(),
37             maDy(),
38             maRotate(),
39             maTextLength(),
40             mbLengthAdjust(true)
41         {
42         }
43 
parseTextPositionAttributes(SVGToken aSVGToken,const OUString & aContent)44         void SvgTextPositions::parseTextPositionAttributes(SVGToken aSVGToken, const OUString& aContent)
45         {
46             // parse own
47             switch(aSVGToken)
48             {
49                 case SVGTokenX:
50                 {
51                     if(!aContent.isEmpty())
52                     {
53                         SvgNumberVector aVector;
54 
55                         if(readSvgNumberVector(aContent, aVector))
56                         {
57                             setX(aVector);
58                         }
59                     }
60                     break;
61                 }
62                 case SVGTokenY:
63                 {
64                     if(!aContent.isEmpty())
65                     {
66                         SvgNumberVector aVector;
67 
68                         if(readSvgNumberVector(aContent, aVector))
69                         {
70                             setY(aVector);
71                         }
72                     }
73                     break;
74                 }
75                 case SVGTokenDx:
76                 {
77                     if(!aContent.isEmpty())
78                     {
79                         SvgNumberVector aVector;
80 
81                         if(readSvgNumberVector(aContent, aVector))
82                         {
83                             setDx(aVector);
84                         }
85                     }
86                     break;
87                 }
88                 case SVGTokenDy:
89                 {
90                     if(!aContent.isEmpty())
91                     {
92                         SvgNumberVector aVector;
93 
94                         if(readSvgNumberVector(aContent, aVector))
95                         {
96                             setDy(aVector);
97                         }
98                     }
99                     break;
100                 }
101                 case SVGTokenRotate:
102                 {
103                     if(!aContent.isEmpty())
104                     {
105                         SvgNumberVector aVector;
106 
107                         if(readSvgNumberVector(aContent, aVector))
108                         {
109                             setRotate(aVector);
110                         }
111                     }
112                     break;
113                 }
114                 case SVGTokenTextLength:
115                 {
116                     SvgNumber aNum;
117 
118                     if(readSingleNumber(aContent, aNum))
119                     {
120                         if(aNum.isPositive())
121                         {
122                             setTextLength(aNum);
123                         }
124                     }
125                     break;
126                 }
127                 case SVGTokenLengthAdjust:
128                 {
129                     if(!aContent.isEmpty())
130                     {
131                         if(aContent.startsWith("spacing"))
132                         {
133                             setLengthAdjust(true);
134                         }
135                         else if(aContent.startsWith("spacingAndGlyphs"))
136                         {
137                             setLengthAdjust(false);
138                         }
139                     }
140                     break;
141                 }
142                 default:
143                 {
144                     break;
145                 }
146             }
147         }
148 
149     } // end of namespace svgreader
150 } // end of namespace svgio
151 
152 
153 namespace svgio
154 {
155     namespace svgreader
156     {
157         class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
158         {
159         private:
160             SvgTextPosition&                    mrSvgTextPosition;
161 
162         protected:
163             /// allow user callback to allow changes to the new TextTransformation. Default
164             /// does nothing.
165             virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
166 
167         public:
localTextBreakupHelper(const drawinglayer::primitive2d::TextSimplePortionPrimitive2D & rSource,SvgTextPosition & rSvgTextPosition)168             localTextBreakupHelper(
169                 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
170                 SvgTextPosition& rSvgTextPosition)
171             :   drawinglayer::primitive2d::TextBreakupHelper(rSource),
172                 mrSvgTextPosition(rSvgTextPosition)
173             {
174             }
175         };
176 
allowChange(sal_uInt32,basegfx::B2DHomMatrix & rNewTransform,sal_uInt32,sal_uInt32)177         bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
178         {
179             const double fRotation(mrSvgTextPosition.consumeRotation());
180 
181             if(0.0 != fRotation)
182             {
183                 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
184 
185                 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
186                 rNewTransform.rotate(fRotation);
187                 rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
188             }
189 
190             return true;
191         }
192 
193     } // end of namespace svgreader
194 } // end of namespace svgio
195 
196 
197 namespace svgio
198 {
199     namespace svgreader
200     {
SvgCharacterNode(SvgDocument & rDocument,SvgNode * pParent,const OUString & rText)201         SvgCharacterNode::SvgCharacterNode(
202             SvgDocument& rDocument,
203             SvgNode* pParent,
204             const OUString& rText)
205         :   SvgNode(SVGTokenCharacter, rDocument, pParent),
206             maText(rText)
207         {
208         }
209 
~SvgCharacterNode()210         SvgCharacterNode::~SvgCharacterNode()
211         {
212         }
213 
getSvgStyleAttributes() const214         const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
215         {
216             // no own style, use parent's
217             if(getParent())
218             {
219                 return getParent()->getSvgStyleAttributes();
220             }
221             else
222             {
223                 return nullptr;
224             }
225         }
226 
createSimpleTextPrimitive(SvgTextPosition & rSvgTextPosition,const SvgStyleAttributes & rSvgStyleAttributes) const227         drawinglayer::primitive2d::TextSimplePortionPrimitive2D* SvgCharacterNode::createSimpleTextPrimitive(
228             SvgTextPosition& rSvgTextPosition,
229             const SvgStyleAttributes& rSvgStyleAttributes) const
230         {
231             // prepare retval, index and length
232             drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pRetval = nullptr;
233             sal_uInt32 nIndex(0);
234             sal_uInt32 nLength(getText().getLength());
235 
236             if(nLength)
237             {
238                 // prepare FontAttribute
239                 const SvgStringVector& rFontFamilyVector = rSvgStyleAttributes.getFontFamily();
240                 OUString aFontFamily = rFontFamilyVector.empty() ?
241                     OUString("Times New Roman") :
242                     rFontFamilyVector[0];
243 
244                 // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
245                 // of a SVG export with font embedding. Remove this to make font matching work. This
246                 // is pretty safe since there should be no font family names ending on ' embedded'.
247                 // Remove again when FontEmbedding is implemented in SVG import
248                 if(aFontFamily.endsWith(" embedded"))
249                 {
250                     aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9);
251                 }
252 
253                 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
254                 bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle());
255 
256                 const drawinglayer::attribute::FontAttribute aFontAttribute(
257                     aFontFamily,
258                     OUString(),
259                     nFontWeight,
260                     false/*bSymbol*/,
261                     false/*bVertical*/,
262                     bItalic,
263                     false/*bMonospaced*/,
264                     false/*bOutline*/,
265                     false/*bRTL*/,
266                     false/*bBiDiStrong*/);
267 
268                 // prepare FontSizeNumber
269                 double fFontWidth(rSvgStyleAttributes.getFontSizeNumber().solve(*this));
270                 double fFontHeight(fFontWidth);
271 
272                 // prepare locale
273                 css::lang::Locale aLocale;
274 
275                 // prepare TextLayouterDevice
276                 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
277                 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
278 
279                 // prepare TextArray
280                 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
281 
282                 if(!aTextArray.empty() && aTextArray.size() < nLength)
283                 {
284                     const sal_uInt32 nArray(aTextArray.size());
285 
286                     if(nArray < nLength)
287                     {
288                         double fStartX(0.0);
289 
290                         if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
291                         {
292                             fStartX = rSvgTextPosition.getParent()->getPosition().getX();
293                         }
294                         else
295                         {
296                             fStartX = aTextArray[nArray - 1];
297                         }
298 
299                         ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
300                         aTextArray.reserve(nLength);
301 
302                         for(size_t a(0); a < aExtendArray.size(); a++)
303                         {
304                             aTextArray.push_back(aExtendArray[a] + fStartX);
305                         }
306                     }
307                 }
308 
309                 // get current TextPosition and TextWidth in units
310                 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
311                 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
312 
313                 // check for user-given TextLength
314                 if(0.0 != rSvgTextPosition.getTextLength()
315                     && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
316                 {
317                     const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
318 
319                     if(rSvgTextPosition.getLengthAdjust())
320                     {
321                         // spacing, need to create and expand TextArray
322                         if(aTextArray.empty())
323                         {
324                             aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
325                         }
326 
327                         for(size_t a(0); a < aTextArray.size(); a++)
328                         {
329                             aTextArray[a] *= fFactor;
330                         }
331                     }
332                     else
333                     {
334                         // spacing and glyphs, just apply to FontWidth
335                         fFontWidth *= fFactor;
336                     }
337 
338                     fTextWidth = rSvgTextPosition.getTextLength();
339                 }
340 
341                 // get TextAlign
342                 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
343 
344                 // map TextAnchor to TextAlign, there seems not to be a difference
345                 if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor())
346                 {
347                     switch(rSvgStyleAttributes.getTextAnchor())
348                     {
349                         case TextAnchor_start:
350                         {
351                             aTextAlign = TextAlign_left;
352                             break;
353                         }
354                         case TextAnchor_middle:
355                         {
356                             aTextAlign = TextAlign_center;
357                             break;
358                         }
359                         case TextAnchor_end:
360                         {
361                             aTextAlign = TextAlign_right;
362                             break;
363                         }
364                         default:
365                         {
366                             break;
367                         }
368                     }
369                 }
370 
371                 // apply TextAlign
372                 switch(aTextAlign)
373                 {
374                     case TextAlign_right:
375                     {
376                         aPosition.setX(aPosition.getX() - fTextWidth);
377                         break;
378                     }
379                     case TextAlign_center:
380                     {
381                         aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
382                         break;
383                     }
384                     case TextAlign_notset:
385                     case TextAlign_left:
386                     case TextAlign_justify:
387                     {
388                         // TextAlign_notset, TextAlign_left: nothing to do
389                         // TextAlign_justify is not clear currently; handle as TextAlign_left
390                         break;
391                     }
392                 }
393 
394                 // get BaselineShift
395                 const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
396 
397                 // apply BaselineShift
398                 switch(aBaselineShift)
399                 {
400                     case BaselineShift_Sub:
401                     {
402                         aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
403                         break;
404                     }
405                     case BaselineShift_Super:
406                     {
407                         aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
408                         break;
409                     }
410                     case BaselineShift_Percentage:
411                     case BaselineShift_Length:
412                     {
413                         const SvgNumber aNumber(rSvgStyleAttributes.getBaselineShiftNumber());
414                         const double mfBaselineShift(aNumber.solve(*this));
415 
416                         aPosition.setY(aPosition.getY() + mfBaselineShift);
417                         break;
418                     }
419                     default: // BaselineShift_Baseline
420                     {
421                         // nothing to do
422                         break;
423                     }
424                 }
425 
426                 // get fill color
427                 const basegfx::BColor aFill(rSvgStyleAttributes.getFill()
428                     ? *rSvgStyleAttributes.getFill()
429                     : basegfx::BColor(0.0, 0.0, 0.0));
430 
431                 // prepare TextTransformation
432                 basegfx::B2DHomMatrix aTextTransform;
433 
434                 aTextTransform.scale(fFontWidth, fFontHeight);
435                 aTextTransform.translate(aPosition.getX(), aPosition.getY());
436 
437                 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
438                 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
439 
440                 if(TextDecoration_underline == aDeco
441                     || TextDecoration_overline == aDeco
442                     || TextDecoration_line_through == aDeco)
443                 {
444                     // get the fill for decoration as described by SVG. We cannot
445                     // have different stroke colors/definitions for those, though
446                     const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
447                     const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill);
448 
449                     // create decorated text primitive
450                     pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
451                         aTextTransform,
452                         getText(),
453                         nIndex,
454                         nLength,
455                         aTextArray,
456                         aFontAttribute,
457                         aLocale,
458                         aFill,
459                         COL_TRANSPARENT,
460 
461                         // extra props for decorated
462                         aDecoColor,
463                         aDecoColor,
464                         TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
465                         TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
466                         false,
467                         TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
468                         false,
469                         drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE,
470                         true,
471                         false,
472                         drawinglayer::primitive2d::TEXT_RELIEF_NONE,
473                         false);
474                 }
475                 else
476                 {
477                     // create text primitive
478                     pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
479                         aTextTransform,
480                         getText(),
481                         nIndex,
482                         nLength,
483                         aTextArray,
484                         aFontAttribute,
485                         aLocale,
486                         aFill);
487                 }
488 
489                 // advance current TextPosition
490                 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
491             }
492 
493             return pRetval;
494         }
495 
decomposeTextWithStyle(drawinglayer::primitive2d::Primitive2DContainer & rTarget,SvgTextPosition & rSvgTextPosition,const SvgStyleAttributes & rSvgStyleAttributes) const496         void SvgCharacterNode::decomposeTextWithStyle(
497             drawinglayer::primitive2d::Primitive2DContainer& rTarget,
498             SvgTextPosition& rSvgTextPosition,
499             const SvgStyleAttributes& rSvgStyleAttributes) const
500         {
501             const drawinglayer::primitive2d::Primitive2DReference xRef(
502                 createSimpleTextPrimitive(
503                     rSvgTextPosition,
504                     rSvgStyleAttributes));
505 
506             if(!(xRef.is() && (Visibility_visible == rSvgStyleAttributes.getVisibility())))
507                 return;
508 
509             if(!rSvgTextPosition.isRotated())
510             {
511                 rTarget.push_back(xRef);
512             }
513             else
514             {
515                 // need to apply rotations to each character as given
516                 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate =
517                     dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get());
518 
519                 if(pCandidate)
520                 {
521                     const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
522                     const drawinglayer::primitive2d::Primitive2DContainer& aResult(
523                         alocalTextBreakupHelper.getResult());
524 
525                     if(!aResult.empty())
526                     {
527                         rTarget.append(aResult);
528                     }
529 
530                     // also consume for the implied single space
531                     rSvgTextPosition.consumeRotation();
532                 }
533                 else
534                 {
535                     OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
536                 }
537             }
538         }
539 
whiteSpaceHandling()540         void SvgCharacterNode::whiteSpaceHandling()
541         {
542             if(XmlSpace_default == getXmlSpace())
543             {
544                 maText = whiteSpaceHandlingDefault(maText);
545             }
546             else
547             {
548                 maText = whiteSpaceHandlingPreserve(maText);
549             }
550         }
551 
addGap()552         void SvgCharacterNode::addGap()
553         {
554             maText += " ";
555         }
556 
concatenate(const OUString & rText)557         void SvgCharacterNode::concatenate(const OUString& rText)
558         {
559             maText += rText;
560         }
561 
decomposeText(drawinglayer::primitive2d::Primitive2DContainer & rTarget,SvgTextPosition & rSvgTextPosition) const562         void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const
563         {
564             if(!getText().isEmpty())
565             {
566                 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
567 
568                 if(pSvgStyleAttributes)
569                 {
570                     decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
571                 }
572             }
573         }
574 
575     } // end of namespace svgreader
576 } // end of namespace svgio
577 
578 
579 namespace svgio
580 {
581     namespace svgreader
582     {
SvgTextPosition(SvgTextPosition * pParent,const InfoProvider & rInfoProvider,const SvgTextPositions & rSvgTextPositions)583         SvgTextPosition::SvgTextPosition(
584             SvgTextPosition* pParent,
585             const InfoProvider& rInfoProvider,
586             const SvgTextPositions& rSvgTextPositions)
587         :   mpParent(pParent),
588             maX(), // computed below
589             maY(), // computed below
590             maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider)),
591             mfTextLength(0.0),
592             maPosition(), // computed below
593             mnRotationIndex(0),
594             mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
595             mbAbsoluteX(false)
596         {
597             // get TextLength if provided
598             if(rSvgTextPositions.getTextLength().isSet())
599             {
600                 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider);
601             }
602 
603             // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
604             // but it seems to be degrees. Convert here to radians
605             if(!maRotate.empty())
606             {
607                 for (double& f : maRotate)
608                 {
609                     f = basegfx::deg2rad(f);
610                 }
611             }
612 
613             // get text positions X
614             const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
615 
616             if(nSizeX)
617             {
618                 // we have absolute positions, get first one as current text position X
619                 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
620                 mbAbsoluteX = true;
621 
622                 if(nSizeX > 1)
623                 {
624                     // fill deltas to maX
625                     maX.reserve(nSizeX);
626 
627                     for(sal_uInt32 a(1); a < nSizeX; a++)
628                     {
629                         maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
630                     }
631                 }
632             }
633             else
634             {
635                 // no absolute position, get from parent
636                 if(pParent)
637                 {
638                     maPosition.setX(pParent->getPosition().getX());
639                 }
640 
641                 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
642 
643                 if(nSizeDx)
644                 {
645                     // relative positions given, translate position derived from parent
646                     maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
647 
648                     if(nSizeDx > 1)
649                     {
650                         // fill deltas to maX
651                         maX.reserve(nSizeDx);
652 
653                         for(sal_uInt32 a(1); a < nSizeDx; a++)
654                         {
655                             maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate));
656                         }
657                     }
658                 }
659             }
660 
661             // get text positions Y
662             const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
663 
664             if(nSizeY)
665             {
666                 // we have absolute positions, get first one as current text position Y
667                 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
668                 mbAbsoluteX = true;
669 
670                 if(nSizeY > 1)
671                 {
672                     // fill deltas to maY
673                     maY.reserve(nSizeY);
674 
675                     for(sal_uInt32 a(1); a < nSizeY; a++)
676                     {
677                         maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
678                     }
679                 }
680             }
681             else
682             {
683                 // no absolute position, get from parent
684                 if(pParent)
685                 {
686                     maPosition.setY(pParent->getPosition().getY());
687                 }
688 
689                 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
690 
691                 if(nSizeDy)
692                 {
693                     // relative positions given, translate position derived from parent
694                     maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
695 
696                     if(nSizeDy > 1)
697                     {
698                         // fill deltas to maY
699                         maY.reserve(nSizeDy);
700 
701                         for(sal_uInt32 a(1); a < nSizeDy; a++)
702                         {
703                             maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate));
704                         }
705                     }
706                 }
707             }
708         }
709 
isRotated() const710         bool SvgTextPosition::isRotated() const
711         {
712             if(maRotate.empty())
713             {
714                 if(getParent())
715                 {
716                     return getParent()->isRotated();
717                 }
718                 else
719                 {
720                     return false;
721                 }
722             }
723             else
724             {
725                 return true;
726             }
727         }
728 
consumeRotation()729         double SvgTextPosition::consumeRotation()
730         {
731             double fRetval(0.0);
732 
733             if(maRotate.empty())
734             {
735                 if(getParent())
736                 {
737                     fRetval = mpParent->consumeRotation();
738                 }
739                 else
740                 {
741                     fRetval = 0.0;
742                 }
743             }
744             else
745             {
746                 const sal_uInt32 nSize(maRotate.size());
747 
748                 if(mnRotationIndex < nSize)
749                 {
750                     fRetval = maRotate[mnRotationIndex++];
751                 }
752                 else
753                 {
754                     fRetval = maRotate[nSize - 1];
755                 }
756             }
757 
758             return fRetval;
759         }
760 
761     } // end of namespace svgreader
762 } // end of namespace svgio
763 
764 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
765