1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <svgio/svgreader/svgcharacternode.hxx>
21 #include <svgio/svgreader/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>
29 //////////////////////////////////////////////////////////////////////////////
35 SvgTextPositions::SvgTextPositions()
46 void SvgTextPositions::parseTextPositionAttributes(const rtl::OUString
& /*rTokenName*/, SVGToken aSVGToken
, const rtl::OUString
& aContent
)
53 if(aContent
.getLength())
55 SvgNumberVector aVector
;
57 if(readSvgNumberVector(aContent
, aVector
))
66 if(aContent
.getLength())
68 SvgNumberVector aVector
;
70 if(readSvgNumberVector(aContent
, aVector
))
79 if(aContent
.getLength())
81 SvgNumberVector aVector
;
83 if(readSvgNumberVector(aContent
, aVector
))
92 if(aContent
.getLength())
94 SvgNumberVector aVector
;
96 if(readSvgNumberVector(aContent
, aVector
))
105 if(aContent
.getLength())
107 SvgNumberVector aVector
;
109 if(readSvgNumberVector(aContent
, aVector
))
116 case SVGTokenTextLength
:
120 if(readSingleNumber(aContent
, aNum
))
122 if(aNum
.isPositive())
129 case SVGTokenLengthAdjust
:
131 if(aContent
.getLength())
133 static rtl::OUString
aStrSpacing(rtl::OUString::createFromAscii("spacing"));
134 static rtl::OUString
aStrSpacingAndGlyphs(rtl::OUString::createFromAscii("spacingAndGlyphs"));
136 if(aContent
.match(aStrSpacing
))
138 setLengthAdjust(true);
140 else if(aContent
.match(aStrSpacingAndGlyphs
))
142 setLengthAdjust(false);
154 } // end of namespace svgreader
155 } // end of namespace svgio
157 //////////////////////////////////////////////////////////////////////////////
163 class localTextBreakupHelper
: public drawinglayer::primitive2d::TextBreakupHelper
166 SvgTextPosition
& mrSvgTextPosition
;
169 /// allow user callback to allow changes to the new TextTransformation. Default
171 virtual bool allowChange(sal_uInt32 nCount
, basegfx::B2DHomMatrix
& rNewTransform
, sal_uInt32 nIndex
, sal_uInt32 nLength
);
174 localTextBreakupHelper(
175 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D
& rSource
,
176 SvgTextPosition
& rSvgTextPosition
)
177 : drawinglayer::primitive2d::TextBreakupHelper(rSource
),
178 mrSvgTextPosition(rSvgTextPosition
)
183 bool localTextBreakupHelper::allowChange(sal_uInt32
/*nCount*/, basegfx::B2DHomMatrix
& rNewTransform
, sal_uInt32
/*nIndex*/, sal_uInt32
/*nLength*/)
185 const double fRotation(mrSvgTextPosition
.consumeRotation());
189 const basegfx::B2DPoint
aBasePoint(rNewTransform
* basegfx::B2DPoint(0.0, 0.0));
191 rNewTransform
.translate(-aBasePoint
.getX(), -aBasePoint
.getY());
192 rNewTransform
.rotate(fRotation
);
193 rNewTransform
.translate(aBasePoint
.getX(), aBasePoint
.getY());
199 } // end of namespace svgreader
200 } // end of namespace svgio
202 //////////////////////////////////////////////////////////////////////////////
208 SvgCharacterNode::SvgCharacterNode(
209 SvgDocument
& rDocument
,
211 const rtl::OUString
& rText
)
212 : SvgNode(SVGTokenCharacter
, rDocument
, pParent
),
217 SvgCharacterNode::~SvgCharacterNode()
221 const SvgStyleAttributes
* SvgCharacterNode::getSvgStyleAttributes() const
223 // no own style, use parent's
226 return getParent()->getSvgStyleAttributes();
234 drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* SvgCharacterNode::createSimpleTextPrimitive(
235 SvgTextPosition
& rSvgTextPosition
,
236 const SvgStyleAttributes
& rSvgStyleAttributes
) const
238 // prepare retval, index and length
239 drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* pRetval
= 0;
240 sal_uInt32
nIndex(0);
241 sal_uInt32
nLength(getText().getLength());
245 // prepare FontAttribute
246 const rtl::OUString aFontFamily
= rSvgStyleAttributes
.getFontFamily().empty() ?
247 rtl::OUString(rtl::OUString::createFromAscii("Times New Roman")) :
248 rSvgStyleAttributes
.getFontFamily()[0];
249 const ::FontWeight
nFontWeight(getVclFontWeight(rSvgStyleAttributes
.getFontWeight()));
251 bool bVertical(false);
252 bool bItalic(FontStyle_italic
== rSvgStyleAttributes
.getFontStyle() || FontStyle_oblique
== rSvgStyleAttributes
.getFontStyle());
253 bool bMonospaced(false);
254 bool bOutline(false);
256 bool bBiDiStrong(false);
258 const drawinglayer::attribute::FontAttribute
aFontAttribute(
271 double fFontWidth(rSvgStyleAttributes
.getFontSize().solve(*this, length
));
272 double fFontHeight(fFontWidth
);
275 ::com::sun::star::lang::Locale aLocale
;
277 // prepare TextLayouterDevice
278 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice
;
279 aTextLayouterDevice
.setFontAttribute(aFontAttribute
, fFontWidth
, fFontHeight
, aLocale
);
282 ::std::vector
< double > aTextArray(rSvgTextPosition
.getX());
284 if(!aTextArray
.empty() && aTextArray
.size() < nLength
)
286 const sal_uInt32
nArray(aTextArray
.size());
292 if(rSvgTextPosition
.getParent() && rSvgTextPosition
.getParent()->getAbsoluteX())
294 fStartX
= rSvgTextPosition
.getParent()->getPosition().getX();
298 fStartX
= aTextArray
[nArray
- 1];
301 ::std::vector
< double > aExtendArray(aTextLayouterDevice
.getTextArray(getText(), nArray
, nLength
- nArray
));
302 aTextArray
.reserve(nLength
);
304 for(sal_uInt32
a(0); a
< aExtendArray
.size(); a
++)
306 aTextArray
.push_back(aExtendArray
[a
] + fStartX
);
311 // get current TextPosition and TextWidth in units
312 basegfx::B2DPoint
aPosition(rSvgTextPosition
.getPosition());
313 double fTextWidth(aTextLayouterDevice
.getTextWidth(getText(), nIndex
, nLength
));
315 // check for user-given TextLength
316 if(0.0 != rSvgTextPosition
.getTextLength()
317 && !basegfx::fTools::equal(fTextWidth
, rSvgTextPosition
.getTextLength()))
319 const double fFactor(rSvgTextPosition
.getTextLength() / fTextWidth
);
321 if(rSvgTextPosition
.getLengthAdjust())
323 // spacing, need to create and expand TextArray
324 if(aTextArray
.empty())
326 aTextArray
= aTextLayouterDevice
.getTextArray(getText(), nIndex
, nLength
);
329 for(sal_uInt32
a(0); a
< aTextArray
.size(); a
++)
331 aTextArray
[a
] *= fFactor
;
336 // spacing and glyphs, just apply to FontWidth
337 fFontWidth
*= fFactor
;
340 fTextWidth
= rSvgTextPosition
.getTextLength();
344 TextAlign
aTextAlign(rSvgStyleAttributes
.getTextAlign());
346 // map TextAnchor to TextAlign, there seems not to be a difference
347 if(TextAnchor_notset
!= rSvgStyleAttributes
.getTextAnchor())
349 switch(rSvgStyleAttributes
.getTextAnchor())
351 case TextAnchor_start
:
353 aTextAlign
= TextAlign_left
;
356 case TextAnchor_middle
:
358 aTextAlign
= TextAlign_center
;
363 aTextAlign
= TextAlign_right
;
376 case TextAlign_right
:
378 aPosition
.setX(aPosition
.getX() - fTextWidth
);
381 case TextAlign_center
:
383 aPosition
.setX(aPosition
.getX() - (fTextWidth
* 0.5));
386 case TextAlign_notset
:
388 case TextAlign_justify
:
390 // TextAlign_notset, TextAlign_left: nothing to do
391 // TextAlign_justify is not clear currently; handle as TextAlign_left
397 const basegfx::BColor
aFill(rSvgStyleAttributes
.getFill()
398 ? *rSvgStyleAttributes
.getFill()
399 : basegfx::BColor(0.0, 0.0, 0.0));
401 // prepare TextTransformation
402 basegfx::B2DHomMatrix aTextTransform
;
404 aTextTransform
.scale(fFontWidth
, fFontHeight
);
405 aTextTransform
.translate(aPosition
.getX(), aPosition
.getY());
407 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
408 const TextDecoration
aDeco(rSvgStyleAttributes
.getTextDecoration());
410 if(TextDecoration_underline
== aDeco
411 || TextDecoration_overline
== aDeco
412 || TextDecoration_line_through
== aDeco
)
414 // get the fill for decroation as described by SVG. We cannot
415 // have different stroke colors/definitions for those, though
416 const SvgStyleAttributes
* pDecoDef
= rSvgStyleAttributes
.getTextDecorationDefiningSvgStyleAttributes();
417 const basegfx::BColor
aDecoColor(pDecoDef
&& pDecoDef
->getFill() ? *pDecoDef
->getFill() : aFill
);
419 // create decorated text primitive
420 pRetval
= new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
430 // extra props for decorated
433 TextDecoration_overline
== aDeco
? drawinglayer::primitive2d::TEXT_LINE_SINGLE
: drawinglayer::primitive2d::TEXT_LINE_NONE
,
434 TextDecoration_underline
== aDeco
? drawinglayer::primitive2d::TEXT_LINE_SINGLE
: drawinglayer::primitive2d::TEXT_LINE_NONE
,
436 TextDecoration_line_through
== aDeco
? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE
: drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE
,
438 drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE
,
441 drawinglayer::primitive2d::TEXT_RELIEF_NONE
,
446 // create text primitive
447 pRetval
= new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
458 // advance current TextPosition
459 rSvgTextPosition
.setPosition(rSvgTextPosition
.getPosition() + basegfx::B2DVector(fTextWidth
, 0.0));
465 void SvgCharacterNode::decomposeTextWithStyle(
466 drawinglayer::primitive2d::Primitive2DSequence
& rTarget
,
467 SvgTextPosition
& rSvgTextPosition
,
468 const SvgStyleAttributes
& rSvgStyleAttributes
) const
470 const drawinglayer::primitive2d::Primitive2DReference
xRef(
471 createSimpleTextPrimitive(
473 rSvgStyleAttributes
));
477 if(!rSvgTextPosition
.isRotated())
479 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget
, xRef
);
483 // need to apply rotations to each character as given
484 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* pCandidate
=
485 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* >(xRef
.get());
489 const localTextBreakupHelper
alocalTextBreakupHelper(*pCandidate
, rSvgTextPosition
);
490 const drawinglayer::primitive2d::Primitive2DSequence
aResult(
491 alocalTextBreakupHelper
.getResult(drawinglayer::primitive2d::BreakupUnit_character
));
493 if(aResult
.hasElements())
495 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget
, aResult
);
498 // also consume for the implied single space
499 rSvgTextPosition
.consumeRotation();
503 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
509 void SvgCharacterNode::whiteSpaceHandling()
511 if(XmlSpace_default
== getXmlSpace())
513 maText
= whiteSpaceHandlingDefault(maText
);
517 maText
= whiteSpaceHandlingPreserve(maText
);
521 void SvgCharacterNode::addGap()
523 maText
+= rtl::OUString(sal_Unicode(' '));
526 void SvgCharacterNode::concatenate(const rtl::OUString
& rText
)
531 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence
& rTarget
, SvgTextPosition
& rSvgTextPosition
) const
533 if(getText().getLength())
535 const SvgStyleAttributes
* pSvgStyleAttributes
= getSvgStyleAttributes();
537 if(pSvgStyleAttributes
)
539 decomposeTextWithStyle(rTarget
, rSvgTextPosition
, *pSvgStyleAttributes
);
544 } // end of namespace svgreader
545 } // end of namespace svgio
547 //////////////////////////////////////////////////////////////////////////////
553 SvgTextPosition::SvgTextPosition(
554 SvgTextPosition
* pParent
,
555 const InfoProvider
& rInfoProvider
,
556 const SvgTextPositions
& rSvgTextPositions
)
558 maX(), // computed below
559 maY(), // computed below
560 maRotate(solveSvgNumberVector(rSvgTextPositions
.getRotate(), rInfoProvider
, length
)),
562 maPosition(), // computed below
564 mbLengthAdjust(rSvgTextPositions
.getLengthAdjust()),
568 // get TextLength if provided
569 if(rSvgTextPositions
.getTextLength().isSet())
571 mfTextLength
= rSvgTextPositions
.getTextLength().solve(rInfoProvider
, length
);
574 // SVG does not really define in which units a ‘rotate’ for Text/TSpan is given,
575 // but it seems to be degrees. Convert here to radians
576 if(!maRotate
.empty())
578 const double fFactor(F_PI
/ 180.0);
580 for(sal_uInt32
a(0); a
< maRotate
.size(); a
++)
582 maRotate
[a
] *= fFactor
;
586 // get text positions X
587 const sal_uInt32
nSizeX(rSvgTextPositions
.getX().size());
591 // we have absolute positions, get first one as current text position X
592 maPosition
.setX(rSvgTextPositions
.getX()[0].solve(rInfoProvider
, xcoordinate
));
597 // fill deltas to maX
600 for(sal_uInt32
a(1); a
< nSizeX
; a
++)
602 maX
.push_back(rSvgTextPositions
.getX()[a
].solve(rInfoProvider
, xcoordinate
) - maPosition
.getX());
608 // no absolute position, get from parent
611 maPosition
.setX(pParent
->getPosition().getX());
614 const sal_uInt32
nSizeDx(rSvgTextPositions
.getDx().size());
618 // relative positions given, translate position derived from parent
619 maPosition
.setX(maPosition
.getX() + rSvgTextPositions
.getDx()[0].solve(rInfoProvider
, xcoordinate
));
623 // fill deltas to maX
624 maX
.reserve(nSizeDx
);
626 for(sal_uInt32
a(1); a
< nSizeDx
; a
++)
628 maX
.push_back(rSvgTextPositions
.getDx()[a
].solve(rInfoProvider
, xcoordinate
));
634 // get text positions Y
635 const sal_uInt32
nSizeY(rSvgTextPositions
.getY().size());
639 // we have absolute positions, get first one as current text position Y
640 maPosition
.setY(rSvgTextPositions
.getY()[0].solve(rInfoProvider
, ycoordinate
));
645 // fill deltas to maY
648 for(sal_uInt32
a(1); a
< nSizeY
; a
++)
650 maY
.push_back(rSvgTextPositions
.getY()[a
].solve(rInfoProvider
, ycoordinate
) - maPosition
.getY());
656 // no absolute position, get from parent
659 maPosition
.setY(pParent
->getPosition().getY());
662 const sal_uInt32
nSizeDy(rSvgTextPositions
.getDy().size());
666 // relative positions given, translate position derived from parent
667 maPosition
.setY(maPosition
.getY() + rSvgTextPositions
.getDy()[0].solve(rInfoProvider
, ycoordinate
));
671 // fill deltas to maY
672 maY
.reserve(nSizeDy
);
674 for(sal_uInt32
a(1); a
< nSizeDy
; a
++)
676 maY
.push_back(rSvgTextPositions
.getDy()[a
].solve(rInfoProvider
, ycoordinate
));
683 bool SvgTextPosition::isRotated() const
689 return getParent()->isRotated();
702 double SvgTextPosition::consumeRotation()
710 fRetval
= mpParent
->consumeRotation();
719 const sal_uInt32
nSize(maRotate
.size());
721 if(mnRotationIndex
< nSize
)
723 fRetval
= maRotate
[mnRotationIndex
++];
727 fRetval
= maRotate
[nSize
- 1];
734 } // end of namespace svgreader
735 } // end of namespace svgio
737 //////////////////////////////////////////////////////////////////////////////
740 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */