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 <svgcharacternode.hxx>
21 #include <svgstyleattributes.hxx>
22 #include <drawinglayer/attribute/fontattribute.hxx>
23 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
24 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
25 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
26 #include <drawinglayer/primitive2d/groupprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
33 SvgTextPositions::SvgTextPositions()
44 void SvgTextPositions::parseTextPositionAttributes(SVGToken aSVGToken
, const OUString
& aContent
)
51 if(!aContent
.isEmpty())
53 SvgNumberVector aVector
;
55 if(readSvgNumberVector(aContent
, aVector
))
64 if(!aContent
.isEmpty())
66 SvgNumberVector aVector
;
68 if(readSvgNumberVector(aContent
, aVector
))
77 if(!aContent
.isEmpty())
79 SvgNumberVector aVector
;
81 if(readSvgNumberVector(aContent
, aVector
))
90 if(!aContent
.isEmpty())
92 SvgNumberVector aVector
;
94 if(readSvgNumberVector(aContent
, aVector
))
103 if(!aContent
.isEmpty())
105 SvgNumberVector aVector
;
107 if(readSvgNumberVector(aContent
, aVector
))
114 case SVGTokenTextLength
:
118 if(readSingleNumber(aContent
, aNum
))
120 if(aNum
.isPositive())
127 case SVGTokenLengthAdjust
:
129 if(!aContent
.isEmpty())
131 if(aContent
.startsWith("spacing"))
133 setLengthAdjust(true);
135 else if(aContent
.startsWith("spacingAndGlyphs"))
137 setLengthAdjust(false);
149 } // end of namespace svgreader
150 } // end of namespace svgio
157 class localTextBreakupHelper
: public drawinglayer::primitive2d::TextBreakupHelper
160 SvgTextPosition
& mrSvgTextPosition
;
163 /// allow user callback to allow changes to the new TextTransformation. Default
165 virtual bool allowChange(sal_uInt32 nCount
, basegfx::B2DHomMatrix
& rNewTransform
, sal_uInt32 nIndex
, sal_uInt32 nLength
) override
;
168 localTextBreakupHelper(
169 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D
& rSource
,
170 SvgTextPosition
& rSvgTextPosition
)
171 : drawinglayer::primitive2d::TextBreakupHelper(rSource
),
172 mrSvgTextPosition(rSvgTextPosition
)
177 bool localTextBreakupHelper::allowChange(sal_uInt32
/*nCount*/, basegfx::B2DHomMatrix
& rNewTransform
, sal_uInt32
/*nIndex*/, sal_uInt32
/*nLength*/)
179 const double fRotation(mrSvgTextPosition
.consumeRotation());
183 const basegfx::B2DPoint
aBasePoint(rNewTransform
* basegfx::B2DPoint(0.0, 0.0));
185 rNewTransform
.translate(-aBasePoint
.getX(), -aBasePoint
.getY());
186 rNewTransform
.rotate(fRotation
);
187 rNewTransform
.translate(aBasePoint
.getX(), aBasePoint
.getY());
193 } // end of namespace svgreader
194 } // end of namespace svgio
201 SvgCharacterNode::SvgCharacterNode(
202 SvgDocument
& rDocument
,
204 const OUString
& rText
)
205 : SvgNode(SVGTokenCharacter
, rDocument
, pParent
),
210 SvgCharacterNode::~SvgCharacterNode()
214 const SvgStyleAttributes
* SvgCharacterNode::getSvgStyleAttributes() const
216 // no own style, use parent's
219 return getParent()->getSvgStyleAttributes();
227 drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* SvgCharacterNode::createSimpleTextPrimitive(
228 SvgTextPosition
& rSvgTextPosition
,
229 const SvgStyleAttributes
& rSvgStyleAttributes
) const
231 // prepare retval, index and length
232 drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* pRetval
= nullptr;
233 sal_uInt32
nIndex(0);
234 sal_uInt32
nLength(getText().getLength());
238 // prepare FontAttribute
239 const SvgStringVector
& rFontFamilyVector
= rSvgStyleAttributes
.getFontFamily();
240 OUString aFontFamily
= rFontFamilyVector
.empty() ?
241 OUString("Times New Roman") :
242 rFontFamilyVector
[0];
244 // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
245 // of a SVG export with font embedding. Remove this to make font matching work. This
246 // is pretty safe since there should be no font family names ending on ' embedded'.
247 // Remove again when FontEmbedding is implemented in SVG import
248 if(aFontFamily
.endsWith(" embedded"))
250 aFontFamily
= aFontFamily
.copy(0, aFontFamily
.getLength() - 9);
253 const ::FontWeight
nFontWeight(getVclFontWeight(rSvgStyleAttributes
.getFontWeight()));
254 bool bItalic(FontStyle_italic
== rSvgStyleAttributes
.getFontStyle() || FontStyle_oblique
== rSvgStyleAttributes
.getFontStyle());
256 const drawinglayer::attribute::FontAttribute
aFontAttribute(
263 false/*bMonospaced*/,
266 false/*bBiDiStrong*/);
268 // prepare FontSizeNumber
269 double fFontWidth(rSvgStyleAttributes
.getFontSizeNumber().solve(*this));
270 double fFontHeight(fFontWidth
);
273 css::lang::Locale aLocale
;
275 // prepare TextLayouterDevice
276 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice
;
277 aTextLayouterDevice
.setFontAttribute(aFontAttribute
, fFontWidth
, fFontHeight
, aLocale
);
280 ::std::vector
< double > aTextArray(rSvgTextPosition
.getX());
282 if(!aTextArray
.empty() && aTextArray
.size() < nLength
)
284 const sal_uInt32
nArray(aTextArray
.size());
290 if(rSvgTextPosition
.getParent() && rSvgTextPosition
.getParent()->getAbsoluteX())
292 fStartX
= rSvgTextPosition
.getParent()->getPosition().getX();
296 fStartX
= aTextArray
[nArray
- 1];
299 ::std::vector
< double > aExtendArray(aTextLayouterDevice
.getTextArray(getText(), nArray
, nLength
- nArray
));
300 aTextArray
.reserve(nLength
);
302 for(size_t a(0); a
< aExtendArray
.size(); a
++)
304 aTextArray
.push_back(aExtendArray
[a
] + fStartX
);
309 // get current TextPosition and TextWidth in units
310 basegfx::B2DPoint
aPosition(rSvgTextPosition
.getPosition());
311 double fTextWidth(aTextLayouterDevice
.getTextWidth(getText(), nIndex
, nLength
));
313 // check for user-given TextLength
314 if(0.0 != rSvgTextPosition
.getTextLength()
315 && !basegfx::fTools::equal(fTextWidth
, rSvgTextPosition
.getTextLength()))
317 const double fFactor(rSvgTextPosition
.getTextLength() / fTextWidth
);
319 if(rSvgTextPosition
.getLengthAdjust())
321 // spacing, need to create and expand TextArray
322 if(aTextArray
.empty())
324 aTextArray
= aTextLayouterDevice
.getTextArray(getText(), nIndex
, nLength
);
327 for(size_t a(0); a
< aTextArray
.size(); a
++)
329 aTextArray
[a
] *= fFactor
;
334 // spacing and glyphs, just apply to FontWidth
335 fFontWidth
*= fFactor
;
338 fTextWidth
= rSvgTextPosition
.getTextLength();
342 TextAlign
aTextAlign(rSvgStyleAttributes
.getTextAlign());
344 // map TextAnchor to TextAlign, there seems not to be a difference
345 if(TextAnchor_notset
!= rSvgStyleAttributes
.getTextAnchor())
347 switch(rSvgStyleAttributes
.getTextAnchor())
349 case TextAnchor_start
:
351 aTextAlign
= TextAlign_left
;
354 case TextAnchor_middle
:
356 aTextAlign
= TextAlign_center
;
361 aTextAlign
= TextAlign_right
;
374 case TextAlign_right
:
376 aPosition
.setX(aPosition
.getX() - fTextWidth
);
379 case TextAlign_center
:
381 aPosition
.setX(aPosition
.getX() - (fTextWidth
* 0.5));
384 case TextAlign_notset
:
386 case TextAlign_justify
:
388 // TextAlign_notset, TextAlign_left: nothing to do
389 // TextAlign_justify is not clear currently; handle as TextAlign_left
395 const BaselineShift
aBaselineShift(rSvgStyleAttributes
.getBaselineShift());
397 // apply BaselineShift
398 switch(aBaselineShift
)
400 case BaselineShift_Sub
:
402 aPosition
.setY(aPosition
.getY() + aTextLayouterDevice
.getUnderlineOffset());
405 case BaselineShift_Super
:
407 aPosition
.setY(aPosition
.getY() + aTextLayouterDevice
.getOverlineOffset());
410 case BaselineShift_Percentage
:
411 case BaselineShift_Length
:
413 const SvgNumber
aNumber(rSvgStyleAttributes
.getBaselineShiftNumber());
414 const double mfBaselineShift(aNumber
.solve(*this));
416 aPosition
.setY(aPosition
.getY() + mfBaselineShift
);
419 default: // BaselineShift_Baseline
427 const basegfx::BColor
aFill(rSvgStyleAttributes
.getFill()
428 ? *rSvgStyleAttributes
.getFill()
429 : basegfx::BColor(0.0, 0.0, 0.0));
431 // prepare TextTransformation
432 basegfx::B2DHomMatrix aTextTransform
;
434 aTextTransform
.scale(fFontWidth
, fFontHeight
);
435 aTextTransform
.translate(aPosition
.getX(), aPosition
.getY());
437 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
438 const TextDecoration
aDeco(rSvgStyleAttributes
.getTextDecoration());
440 if(TextDecoration_underline
== aDeco
441 || TextDecoration_overline
== aDeco
442 || TextDecoration_line_through
== aDeco
)
444 // get the fill for decoration as described by SVG. We cannot
445 // have different stroke colors/definitions for those, though
446 const SvgStyleAttributes
* pDecoDef
= rSvgStyleAttributes
.getTextDecorationDefiningSvgStyleAttributes();
447 const basegfx::BColor
aDecoColor(pDecoDef
&& pDecoDef
->getFill() ? *pDecoDef
->getFill() : aFill
);
449 // create decorated text primitive
450 pRetval
= new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
461 // extra props for decorated
464 TextDecoration_overline
== aDeco
? drawinglayer::primitive2d::TEXT_LINE_SINGLE
: drawinglayer::primitive2d::TEXT_LINE_NONE
,
465 TextDecoration_underline
== aDeco
? drawinglayer::primitive2d::TEXT_LINE_SINGLE
: drawinglayer::primitive2d::TEXT_LINE_NONE
,
467 TextDecoration_line_through
== aDeco
? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE
: drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE
,
469 drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE
,
472 drawinglayer::primitive2d::TEXT_RELIEF_NONE
,
477 // create text primitive
478 pRetval
= new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
489 // advance current TextPosition
490 rSvgTextPosition
.setPosition(rSvgTextPosition
.getPosition() + basegfx::B2DVector(fTextWidth
, 0.0));
496 void SvgCharacterNode::decomposeTextWithStyle(
497 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
,
498 SvgTextPosition
& rSvgTextPosition
,
499 const SvgStyleAttributes
& rSvgStyleAttributes
) const
501 const drawinglayer::primitive2d::Primitive2DReference
xRef(
502 createSimpleTextPrimitive(
504 rSvgStyleAttributes
));
506 if(xRef
.is() && (Visibility_visible
== rSvgStyleAttributes
.getVisibility()))
508 if(!rSvgTextPosition
.isRotated())
510 rTarget
.push_back(xRef
);
514 // need to apply rotations to each character as given
515 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* pCandidate
=
516 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D
* >(xRef
.get());
520 const localTextBreakupHelper
alocalTextBreakupHelper(*pCandidate
, rSvgTextPosition
);
521 const drawinglayer::primitive2d::Primitive2DContainer
& aResult(
522 alocalTextBreakupHelper
.getResult());
526 rTarget
.append(aResult
);
529 // also consume for the implied single space
530 rSvgTextPosition
.consumeRotation();
534 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
540 void SvgCharacterNode::whiteSpaceHandling()
542 if(XmlSpace_default
== getXmlSpace())
544 maText
= whiteSpaceHandlingDefault(maText
);
548 maText
= whiteSpaceHandlingPreserve(maText
);
552 void SvgCharacterNode::addGap()
557 void SvgCharacterNode::concatenate(const OUString
& rText
)
562 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DContainer
& rTarget
, SvgTextPosition
& rSvgTextPosition
) const
564 if(!getText().isEmpty())
566 const SvgStyleAttributes
* pSvgStyleAttributes
= getSvgStyleAttributes();
568 if(pSvgStyleAttributes
)
570 decomposeTextWithStyle(rTarget
, rSvgTextPosition
, *pSvgStyleAttributes
);
575 } // end of namespace svgreader
576 } // end of namespace svgio
583 SvgTextPosition::SvgTextPosition(
584 SvgTextPosition
* pParent
,
585 const InfoProvider
& rInfoProvider
,
586 const SvgTextPositions
& rSvgTextPositions
)
588 maX(), // computed below
589 maY(), // computed below
590 maRotate(solveSvgNumberVector(rSvgTextPositions
.getRotate(), rInfoProvider
)),
592 maPosition(), // computed below
594 mbLengthAdjust(rSvgTextPositions
.getLengthAdjust()),
597 // get TextLength if provided
598 if(rSvgTextPositions
.getTextLength().isSet())
600 mfTextLength
= rSvgTextPositions
.getTextLength().solve(rInfoProvider
);
603 // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
604 // but it seems to be degrees. Convert here to radians
605 if(!maRotate
.empty())
607 for (double& f
: maRotate
)
609 f
= basegfx::deg2rad(f
);
613 // get text positions X
614 const sal_uInt32
nSizeX(rSvgTextPositions
.getX().size());
618 // we have absolute positions, get first one as current text position X
619 maPosition
.setX(rSvgTextPositions
.getX()[0].solve(rInfoProvider
, xcoordinate
));
624 // fill deltas to maX
627 for(sal_uInt32
a(1); a
< nSizeX
; a
++)
629 maX
.push_back(rSvgTextPositions
.getX()[a
].solve(rInfoProvider
, xcoordinate
) - maPosition
.getX());
635 // no absolute position, get from parent
638 maPosition
.setX(pParent
->getPosition().getX());
641 const sal_uInt32
nSizeDx(rSvgTextPositions
.getDx().size());
645 // relative positions given, translate position derived from parent
646 maPosition
.setX(maPosition
.getX() + rSvgTextPositions
.getDx()[0].solve(rInfoProvider
, xcoordinate
));
650 // fill deltas to maX
651 maX
.reserve(nSizeDx
);
653 for(sal_uInt32
a(1); a
< nSizeDx
; a
++)
655 maX
.push_back(rSvgTextPositions
.getDx()[a
].solve(rInfoProvider
, xcoordinate
));
661 // get text positions Y
662 const sal_uInt32
nSizeY(rSvgTextPositions
.getY().size());
666 // we have absolute positions, get first one as current text position Y
667 maPosition
.setY(rSvgTextPositions
.getY()[0].solve(rInfoProvider
, ycoordinate
));
672 // fill deltas to maY
675 for(sal_uInt32
a(1); a
< nSizeY
; a
++)
677 maY
.push_back(rSvgTextPositions
.getY()[a
].solve(rInfoProvider
, ycoordinate
) - maPosition
.getY());
683 // no absolute position, get from parent
686 maPosition
.setY(pParent
->getPosition().getY());
689 const sal_uInt32
nSizeDy(rSvgTextPositions
.getDy().size());
693 // relative positions given, translate position derived from parent
694 maPosition
.setY(maPosition
.getY() + rSvgTextPositions
.getDy()[0].solve(rInfoProvider
, ycoordinate
));
698 // fill deltas to maY
699 maY
.reserve(nSizeDy
);
701 for(sal_uInt32
a(1); a
< nSizeDy
; a
++)
703 maY
.push_back(rSvgTextPositions
.getDy()[a
].solve(rInfoProvider
, ycoordinate
));
710 bool SvgTextPosition::isRotated() const
716 return getParent()->isRotated();
729 double SvgTextPosition::consumeRotation()
737 fRetval
= mpParent
->consumeRotation();
746 const sal_uInt32
nSize(maRotate
.size());
748 if(mnRotationIndex
< nSize
)
750 fRetval
= maRotate
[mnRotationIndex
++];
754 fRetval
= maRotate
[nSize
- 1];
761 } // end of namespace svgreader
762 } // end of namespace svgio
764 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */