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