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                 default:
153                 {
154                     break;
155                 }
156             }
157         }
158 
159     } // end of namespace svgreader
160 } // end of namespace svgio
161 
162 //////////////////////////////////////////////////////////////////////////////
163 
164 namespace svgio
165 {
166     namespace svgreader
167     {
168         class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
169         {
170         private:
171             SvgTextPosition&                    mrSvgTextPosition;
172 
173         protected:
174             /// allow user callback to allow changes to the new TextTransformation. Default
175             /// does nothing.
176             virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength);
177 
178         public:
179             localTextBreakupHelper(
180                 const drawinglayer::primitive2d::Primitive2DReference& rxSource,
181                 SvgTextPosition& rSvgTextPosition)
182             :   drawinglayer::primitive2d::TextBreakupHelper(rxSource),
183                 mrSvgTextPosition(rSvgTextPosition)
184             {
185             }
186         };
187 
188         bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
189         {
190             const double fRotation(mrSvgTextPosition.consumeRotation());
191 
192             if(0.0 != fRotation)
193             {
194                 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
195 
196                 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
197                 rNewTransform.rotate(fRotation);
198                 rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
199             }
200 
201             return true;
202         }
203 
204     } // end of namespace svgreader
205 } // end of namespace svgio
206 
207 //////////////////////////////////////////////////////////////////////////////
208 
209 namespace svgio
210 {
211     namespace svgreader
212     {
213         SvgCharacterNode::SvgCharacterNode(
214             SvgDocument& rDocument,
215             SvgNode* pParent,
216             const rtl::OUString& rText)
217         :   SvgNode(SVGTokenCharacter, rDocument, pParent),
218             maText(rText)
219         {
220         }
221 
222         SvgCharacterNode::~SvgCharacterNode()
223         {
224         }
225 
226         const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
227         {
228             // no own style, use parent's
229             if(getParent())
230             {
231                 return getParent()->getSvgStyleAttributes();
232             }
233             else
234             {
235                 return 0;
236             }
237         }
238 
239         drawinglayer::primitive2d::TextSimplePortionPrimitive2D* SvgCharacterNode::createSimpleTextPrimitive(
240             SvgTextPosition& rSvgTextPosition,
241             const SvgStyleAttributes& rSvgStyleAttributes) const
242         {
243             // prepare retval, index and length
244             drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pRetval = 0;
245             sal_uInt32 nIndex(0);
246             sal_uInt32 nLength(getText().getLength());
247 
248             if(nLength)
249             {
250                 // prepare FontAttribute
251                 const rtl::OUString aFontFamily = rSvgStyleAttributes.getFontFamily().empty() ?
252                     rtl::OUString(rtl::OUString::createFromAscii("Times New Roman")) :
253                     rSvgStyleAttributes.getFontFamily()[0];
254                 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
255                 bool bSymbol(false);
256                 bool bVertical(false);
257                 bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle());
258                 bool bMonospaced(false);
259                 bool bOutline(false);
260                 bool bRTL(false);
261                 bool bBiDiStrong(false);
262 
263                 const drawinglayer::attribute::FontAttribute aFontAttribute(
264                     aFontFamily,
265                     rtl::OUString(),
266                     nFontWeight,
267                     bSymbol,
268                     bVertical,
269                     bItalic,
270                     bMonospaced,
271                     bOutline,
272                     bRTL,
273                     bBiDiStrong);
274 
275                 // prepare FontSize
276                 double fFontWidth(rSvgStyleAttributes.getFontSize().solve(*this, length));
277                 double fFontHeight(fFontWidth);
278 
279                 // prepare locale
280                 ::com::sun::star::lang::Locale aLocale;
281 
282                 // prepare TextLayouterDevice
283                 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
284                 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
285 
286                 // prepare TextArray
287                 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
288 
289                 if(!aTextArray.empty() && aTextArray.size() < nLength)
290                 {
291                     const sal_uInt32 nArray(aTextArray.size());
292 
293                     if(nArray < nLength)
294                     {
295                         double fStartX(0.0);
296 
297                         if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
298                         {
299                             fStartX = rSvgTextPosition.getParent()->getPosition().getX();
300                         }
301                         else
302                         {
303                             fStartX = aTextArray[nArray - 1];
304                         }
305 
306                         ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
307                         aTextArray.reserve(nLength);
308 
309                         for(sal_uInt32 a(0); a < aExtendArray.size(); a++)
310                         {
311                             aTextArray.push_back(aExtendArray[a] + fStartX);
312                         }
313                     }
314                 }
315 
316                 // get current TextPosition and TextWidth in units
317                 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
318                 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
319 
320                 // check for user-given TextLength
321                 if(0.0 != rSvgTextPosition.getTextLength()
322                     && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
323                 {
324                     const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
325 
326                     if(rSvgTextPosition.getLengthAdjust())
327                     {
328                         // spacing, need to create and expand TextArray
329                         if(aTextArray.empty())
330                         {
331                             aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
332                         }
333 
334                         for(sal_uInt32 a(0); a < aTextArray.size(); a++)
335                         {
336                             aTextArray[a] *= fFactor;
337                         }
338                     }
339                     else
340                     {
341                         // spacing and glyphs, just apply to FontWidth
342                         fFontWidth *= fFactor;
343                     }
344 
345                     fTextWidth = rSvgTextPosition.getTextLength();
346                 }
347 
348                 // get TextAlign
349                 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
350 
351                 // map TextAnchor to TextAlign, there seems not to be a difference
352                 if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor())
353                 {
354                     switch(rSvgStyleAttributes.getTextAnchor())
355                     {
356                         case TextAnchor_start:
357                         {
358                             aTextAlign = TextAlign_left;
359                             break;
360                         }
361                         case TextAnchor_middle:
362                         {
363                             aTextAlign = TextAlign_center;
364                             break;
365                         }
366                         case TextAnchor_end:
367                         {
368                             aTextAlign = TextAlign_right;
369                             break;
370                         }
371                         default:
372                         {
373                             break;
374                         }
375                     }
376                 }
377 
378                 // apply TextAlign
379                 switch(aTextAlign)
380                 {
381                     case TextAlign_right:
382                     {
383                         aPosition.setX(aPosition.getX() - fTextWidth);
384                         break;
385                     }
386                     case TextAlign_center:
387                     {
388                         aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
389                         break;
390                     }
391                     case TextAlign_notset:
392                     case TextAlign_left:
393                     case TextAlign_justify:
394                     {
395                         // TextAlign_notset, TextAlign_left: nothing to do
396                         // TextAlign_justify is not clear currently; handle as TextAlign_left
397                         break;
398                     }
399                 }
400 
401                 // get fill color
402                 const basegfx::BColor aFill(rSvgStyleAttributes.getFill()
403                     ? *rSvgStyleAttributes.getFill()
404                     : basegfx::BColor(0.0, 0.0, 0.0));
405 
406                 // prepare TextTransformation
407                 basegfx::B2DHomMatrix aTextTransform;
408 
409                 aTextTransform.scale(fFontWidth, fFontHeight);
410                 aTextTransform.translate(aPosition.getX(), aPosition.getY());
411 
412                 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
413                 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
414 
415                 if(TextDecoration_underline == aDeco
416                     || TextDecoration_overline == aDeco
417                     || TextDecoration_line_through == aDeco)
418                 {
419                     // get the fill for decroation as described by SVG. We cannot
420                     // have different stroke colors/definitions for those, though
421                     const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
422                     const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill);
423 
424                     // create decorated text primitive
425                     pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
426                         aTextTransform,
427                         getText(),
428                         nIndex,
429                         nLength,
430                         aTextArray,
431                         aFontAttribute,
432                         aLocale,
433                         aFill,
434 
435                         // extra props for decorated
436                         aDecoColor,
437                         aDecoColor,
438                         TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
439                         TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
440                         false,
441                         TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
442                         false,
443                         drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE,
444                         true,
445                         false,
446                         drawinglayer::primitive2d::TEXT_RELIEF_NONE,
447                         false);
448                 }
449                 else
450                 {
451                     // create text primitive
452                     pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
453                         aTextTransform,
454                         getText(),
455                         nIndex,
456                         nLength,
457                         aTextArray,
458                         aFontAttribute,
459                         aLocale,
460                         aFill);
461                 }
462 
463                 // advance current TextPosition
464                 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
465             }
466 
467             return pRetval;
468         }
469 
470         void SvgCharacterNode::decomposeTextWithStyle(
471             drawinglayer::primitive2d::Primitive2DSequence& rTarget,
472             SvgTextPosition& rSvgTextPosition,
473             const SvgStyleAttributes& rSvgStyleAttributes) const
474         {
475             const drawinglayer::primitive2d::Primitive2DReference xRef(
476                 createSimpleTextPrimitive(
477                     rSvgTextPosition,
478                     rSvgStyleAttributes));
479 
480             if(xRef.is())
481             {
482                 if(!rSvgTextPosition.isRotated())
483                 {
484                     drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef);
485                 }
486                 else
487                 {
488                     // need to apply rotations to each character as given
489                     localTextBreakupHelper alocalTextBreakupHelper(xRef, rSvgTextPosition);
490                     const drawinglayer::primitive2d::Primitive2DSequence aResult(
491                         alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character));
492 
493                     if(aResult.hasElements())
494                     {
495                         drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult);
496                     }
497 
498                     // also consume for the implied single space
499                     rSvgTextPosition.consumeRotation();
500                 }
501             }
502         }
503 
504         void SvgCharacterNode::whiteSpaceHandling()
505         {
506             if(XmlSpace_default == getXmlSpace())
507             {
508                 maText = whiteSpaceHandlingDefault(maText);
509             }
510             else
511             {
512                 maText = whiteSpaceHandlingPreserve(maText);
513             }
514         }
515 
516         void SvgCharacterNode::addGap()
517         {
518             maText += rtl::OUString(sal_Unicode(' '));
519         }
520 
521         void SvgCharacterNode::concatenate(const rtl::OUString& rText)
522         {
523             maText += rText;
524         }
525 
526         void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const
527         {
528             if(getText().getLength())
529             {
530                 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
531 
532                 if(pSvgStyleAttributes)
533                 {
534                     decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
535                 }
536             }
537         }
538 
539     } // end of namespace svgreader
540 } // end of namespace svgio
541 
542 //////////////////////////////////////////////////////////////////////////////
543 
544 namespace svgio
545 {
546     namespace svgreader
547     {
548         SvgTextPosition::SvgTextPosition(
549             SvgTextPosition* pParent,
550             const InfoProvider& rInfoProvider,
551             const SvgTextPositions& rSvgTextPositions)
552         :   mpParent(pParent),
553             maX(), // computed below
554             maY(), // computed below
555             maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)),
556             mfTextLength(0.0),
557             maPosition(), // computed below
558             mnRotationIndex(0),
559             mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
560             mbAbsoluteX(false),
561             mbAbsoluteY(false)
562         {
563             // get TextLength if provided
564             if(rSvgTextPositions.getTextLength().isSet())
565             {
566                 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length);
567             }
568 
569             // SVG does not really define in which units a �rotate� for Text/TSpan is given,
570             // but it seems to be degrees. Convert here to radians
571             if(!maRotate.empty())
572             {
573                 const double fFactor(F_PI / 180.0);
574 
575                 for(sal_uInt32 a(0); a < maRotate.size(); a++)
576                 {
577                     maRotate[a] *= fFactor;
578                 }
579             }
580 
581             // get text positions X
582             const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
583 
584             if(nSizeX)
585             {
586                 // we have absolute positions, get first one as current text position X
587                 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
588                 mbAbsoluteX = true;
589 
590                 if(nSizeX > 1)
591                 {
592                     // fill deltas to maX
593                     maX.reserve(nSizeX);
594 
595                     for(sal_uInt32 a(1); a < nSizeX; a++)
596                     {
597                         maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
598                     }
599                 }
600             }
601             else
602             {
603                 // no absolute position, get from parent
604                 if(pParent)
605                 {
606                     maPosition.setX(pParent->getPosition().getX());
607                 }
608 
609                 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
610 
611                 if(nSizeDx)
612                 {
613                     // relative positions given, translate position derived from parent
614                     maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
615 
616                     if(nSizeDx > 1)
617                     {
618                         // fill deltas to maX
619                         maX.reserve(nSizeDx);
620 
621                         for(sal_uInt32 a(1); a < nSizeDx; a++)
622                         {
623                             maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate));
624                         }
625                     }
626                 }
627             }
628 
629             // get text positions Y
630             const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
631 
632             if(nSizeY)
633             {
634                 // we have absolute positions, get first one as current text position Y
635                 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
636                 mbAbsoluteX = true;
637 
638                 if(nSizeY > 1)
639                 {
640                     // fill deltas to maY
641                     maY.reserve(nSizeY);
642 
643                     for(sal_uInt32 a(1); a < nSizeY; a++)
644                     {
645                         maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
646                     }
647                 }
648             }
649             else
650             {
651                 // no absolute position, get from parent
652                 if(pParent)
653                 {
654                     maPosition.setY(pParent->getPosition().getY());
655                 }
656 
657                 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
658 
659                 if(nSizeDy)
660                 {
661                     // relative positions given, translate position derived from parent
662                     maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
663 
664                     if(nSizeDy > 1)
665                     {
666                         // fill deltas to maY
667                         maY.reserve(nSizeDy);
668 
669                         for(sal_uInt32 a(1); a < nSizeDy; a++)
670                         {
671                             maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate));
672                         }
673                     }
674                 }
675             }
676         }
677 
678         bool SvgTextPosition::isRotated() const
679         {
680             if(maRotate.empty())
681             {
682                 if(getParent())
683                 {
684                     return getParent()->isRotated();
685                 }
686                 else
687                 {
688                     return false;
689                 }
690             }
691             else
692             {
693                 return true;
694             }
695         }
696 
697         double SvgTextPosition::consumeRotation()
698         {
699             double fRetval(0.0);
700 
701             if(maRotate.empty())
702             {
703                 if(getParent())
704                 {
705                     fRetval = mpParent->consumeRotation();
706                 }
707                 else
708                 {
709                     fRetval = 0.0;
710                 }
711             }
712             else
713             {
714                 const sal_uInt32 nSize(maRotate.size());
715 
716                 if(mnRotationIndex < nSize)
717                 {
718                     fRetval = maRotate[mnRotationIndex++];
719                 }
720                 else
721                 {
722                     fRetval = maRotate[nSize - 1];
723                 }
724             }
725 
726             return fRetval;
727         }
728 
729     } // end of namespace svgreader
730 } // end of namespace svgio
731 
732 //////////////////////////////////////////////////////////////////////////////
733 // eof
734