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::TextSimplePortionPrimitive2D& rSource,
181                 SvgTextPosition& rSvgTextPosition)
182             :   drawinglayer::primitive2d::TextBreakupHelper(rSource),
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                     const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate =
490                         dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get());
491 
492                     if(pCandidate)
493                     {
494                         const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
495                         const drawinglayer::primitive2d::Primitive2DSequence aResult(
496                             alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character));
497 
498                         if(aResult.hasElements())
499                         {
500                             drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult);
501                         }
502 
503                         // also consume for the implied single space
504                         rSvgTextPosition.consumeRotation();
505                     }
506                     else
507                     {
508                         OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
509                     }
510                 }
511             }
512         }
513 
514         void SvgCharacterNode::whiteSpaceHandling()
515         {
516             if(XmlSpace_default == getXmlSpace())
517             {
518                 maText = whiteSpaceHandlingDefault(maText);
519             }
520             else
521             {
522                 maText = whiteSpaceHandlingPreserve(maText);
523             }
524         }
525 
526         void SvgCharacterNode::addGap()
527         {
528             maText += rtl::OUString(sal_Unicode(' '));
529         }
530 
531         void SvgCharacterNode::concatenate(const rtl::OUString& rText)
532         {
533             maText += rText;
534         }
535 
536         void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const
537         {
538             if(getText().getLength())
539             {
540                 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
541 
542                 if(pSvgStyleAttributes)
543                 {
544                     decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
545                 }
546             }
547         }
548 
549     } // end of namespace svgreader
550 } // end of namespace svgio
551 
552 //////////////////////////////////////////////////////////////////////////////
553 
554 namespace svgio
555 {
556     namespace svgreader
557     {
558         SvgTextPosition::SvgTextPosition(
559             SvgTextPosition* pParent,
560             const InfoProvider& rInfoProvider,
561             const SvgTextPositions& rSvgTextPositions)
562         :   mpParent(pParent),
563             maX(), // computed below
564             maY(), // computed below
565             maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)),
566             mfTextLength(0.0),
567             maPosition(), // computed below
568             mnRotationIndex(0),
569             mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
570             mbAbsoluteX(false),
571             mbAbsoluteY(false)
572         {
573             // get TextLength if provided
574             if(rSvgTextPositions.getTextLength().isSet())
575             {
576                 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length);
577             }
578 
579             // SVG does not really define in which units a �rotate� for Text/TSpan is given,
580             // but it seems to be degrees. Convert here to radians
581             if(!maRotate.empty())
582             {
583                 const double fFactor(F_PI / 180.0);
584 
585                 for(sal_uInt32 a(0); a < maRotate.size(); a++)
586                 {
587                     maRotate[a] *= fFactor;
588                 }
589             }
590 
591             // get text positions X
592             const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
593 
594             if(nSizeX)
595             {
596                 // we have absolute positions, get first one as current text position X
597                 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
598                 mbAbsoluteX = true;
599 
600                 if(nSizeX > 1)
601                 {
602                     // fill deltas to maX
603                     maX.reserve(nSizeX);
604 
605                     for(sal_uInt32 a(1); a < nSizeX; a++)
606                     {
607                         maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
608                     }
609                 }
610             }
611             else
612             {
613                 // no absolute position, get from parent
614                 if(pParent)
615                 {
616                     maPosition.setX(pParent->getPosition().getX());
617                 }
618 
619                 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
620 
621                 if(nSizeDx)
622                 {
623                     // relative positions given, translate position derived from parent
624                     maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
625 
626                     if(nSizeDx > 1)
627                     {
628                         // fill deltas to maX
629                         maX.reserve(nSizeDx);
630 
631                         for(sal_uInt32 a(1); a < nSizeDx; a++)
632                         {
633                             maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate));
634                         }
635                     }
636                 }
637             }
638 
639             // get text positions Y
640             const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
641 
642             if(nSizeY)
643             {
644                 // we have absolute positions, get first one as current text position Y
645                 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
646                 mbAbsoluteX = true;
647 
648                 if(nSizeY > 1)
649                 {
650                     // fill deltas to maY
651                     maY.reserve(nSizeY);
652 
653                     for(sal_uInt32 a(1); a < nSizeY; a++)
654                     {
655                         maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
656                     }
657                 }
658             }
659             else
660             {
661                 // no absolute position, get from parent
662                 if(pParent)
663                 {
664                     maPosition.setY(pParent->getPosition().getY());
665                 }
666 
667                 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
668 
669                 if(nSizeDy)
670                 {
671                     // relative positions given, translate position derived from parent
672                     maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
673 
674                     if(nSizeDy > 1)
675                     {
676                         // fill deltas to maY
677                         maY.reserve(nSizeDy);
678 
679                         for(sal_uInt32 a(1); a < nSizeDy; a++)
680                         {
681                             maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate));
682                         }
683                     }
684                 }
685             }
686         }
687 
688         bool SvgTextPosition::isRotated() const
689         {
690             if(maRotate.empty())
691             {
692                 if(getParent())
693                 {
694                     return getParent()->isRotated();
695                 }
696                 else
697                 {
698                     return false;
699                 }
700             }
701             else
702             {
703                 return true;
704             }
705         }
706 
707         double SvgTextPosition::consumeRotation()
708         {
709             double fRetval(0.0);
710 
711             if(maRotate.empty())
712             {
713                 if(getParent())
714                 {
715                     fRetval = mpParent->consumeRotation();
716                 }
717                 else
718                 {
719                     fRetval = 0.0;
720                 }
721             }
722             else
723             {
724                 const sal_uInt32 nSize(maRotate.size());
725 
726                 if(mnRotationIndex < nSize)
727                 {
728                     fRetval = maRotate[mnRotationIndex++];
729                 }
730                 else
731                 {
732                     fRetval = maRotate[nSize - 1];
733                 }
734             }
735 
736             return fRetval;
737         }
738 
739     } // end of namespace svgreader
740 } // end of namespace svgio
741 
742 //////////////////////////////////////////////////////////////////////////////
743 // eof
744