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/textdecoratedprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
29 #include <o3tl/string_view.hxx>
30 #include <osl/diagnose.h>
32 using namespace drawinglayer::primitive2d
;
34 namespace svgio::svgreader
36 SvgTextPositions::SvgTextPositions()
37 : mbLengthAdjust(true)
41 void SvgTextPositions::parseTextPositionAttributes(SVGToken aSVGToken
, std::u16string_view aContent
)
50 SvgNumberVector aVector
;
52 if(readSvgNumberVector(aContent
, aVector
))
54 setX(std::move(aVector
));
63 SvgNumberVector aVector
;
65 if(readSvgNumberVector(aContent
, aVector
))
67 setY(std::move(aVector
));
76 SvgNumberVector aVector
;
78 if(readSvgNumberVector(aContent
, aVector
))
80 setDx(std::move(aVector
));
89 SvgNumberVector aVector
;
91 if(readSvgNumberVector(aContent
, aVector
))
93 setDy(std::move(aVector
));
98 case SVGToken::Rotate
:
100 if(!aContent
.empty())
102 SvgNumberVector aVector
;
104 if(readSvgNumberVector(aContent
, aVector
))
106 setRotate(std::move(aVector
));
111 case SVGToken::TextLength
:
115 if(readSingleNumber(aContent
, aNum
))
117 if(aNum
.isPositive())
124 case SVGToken::LengthAdjust
:
126 if(!aContent
.empty())
128 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"spacing"))
130 setLengthAdjust(true);
132 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent
), u
"spacingAndGlyphs"))
134 setLengthAdjust(false);
148 class localTextBreakupHelper
: public TextBreakupHelper
151 SvgTextPosition
& mrSvgTextPosition
;
154 /// allow user callback to allow changes to the new TextTransformation. Default
156 virtual bool allowChange(sal_uInt32 nCount
, basegfx::B2DHomMatrix
& rNewTransform
, sal_uInt32 nIndex
, sal_uInt32 nLength
) override
;
159 localTextBreakupHelper(
160 const TextSimplePortionPrimitive2D
& rSource
,
161 SvgTextPosition
& rSvgTextPosition
)
162 : TextBreakupHelper(rSource
),
163 mrSvgTextPosition(rSvgTextPosition
)
170 bool localTextBreakupHelper::allowChange(sal_uInt32
/*nCount*/, basegfx::B2DHomMatrix
& rNewTransform
, sal_uInt32
/*nIndex*/, sal_uInt32
/*nLength*/)
172 const double fRotation(mrSvgTextPosition
.consumeRotation());
176 const basegfx::B2DPoint
aBasePoint(rNewTransform
* basegfx::B2DPoint(0.0, 0.0));
178 rNewTransform
.translate(-aBasePoint
.getX(), -aBasePoint
.getY());
179 rNewTransform
.rotate(fRotation
);
180 rNewTransform
.translate(aBasePoint
.getX(), aBasePoint
.getY());
186 SvgCharacterNode::SvgCharacterNode(
187 SvgDocument
& rDocument
,
190 : SvgNode(SVGToken::Character
, rDocument
, pParent
),
191 maText(std::move(aText
))
195 SvgCharacterNode::~SvgCharacterNode()
199 const SvgStyleAttributes
* SvgCharacterNode::getSvgStyleAttributes() const
201 // no own style, use parent's
204 return getParent()->getSvgStyleAttributes();
212 rtl::Reference
<BasePrimitive2D
> SvgCharacterNode::createSimpleTextPrimitive(
213 SvgTextPosition
& rSvgTextPosition
,
214 const SvgStyleAttributes
& rSvgStyleAttributes
) const
216 // prepare retval, index and length
217 rtl::Reference
<BasePrimitive2D
> pRetval
;
218 sal_uInt32
nLength(getText().getLength());
222 sal_uInt32
nIndex(0);
223 // prepare FontAttribute
224 const SvgStringVector
& rFontFamilyVector
= rSvgStyleAttributes
.getFontFamily();
225 OUString
aFontFamily("Times New Roman");
226 if(!rFontFamilyVector
.empty())
227 aFontFamily
=rFontFamilyVector
[0];
229 // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
230 // of a SVG export with font embedding. Remove this to make font matching work. This
231 // is pretty safe since there should be no font family names ending on ' embedded'.
232 // Remove again when FontEmbedding is implemented in SVG import
233 if(aFontFamily
.endsWith(" embedded"))
235 aFontFamily
= aFontFamily
.copy(0, aFontFamily
.getLength() - 9);
238 const ::FontWeight
nFontWeight(getVclFontWeight(rSvgStyleAttributes
.getFontWeight()));
239 bool bItalic(FontStyle::italic
== rSvgStyleAttributes
.getFontStyle() || FontStyle::oblique
== rSvgStyleAttributes
.getFontStyle());
241 const drawinglayer::attribute::FontAttribute
aFontAttribute(
248 false/*bMonospaced*/,
251 false/*bBiDiStrong*/);
253 // prepare FontSizeNumber
254 double fFontWidth(rSvgStyleAttributes
.getFontSizeNumber().solve(*this));
255 double fFontHeight(fFontWidth
);
258 css::lang::Locale aLocale
;
260 // prepare TextLayouterDevice
261 TextLayouterDevice aTextLayouterDevice
;
262 aTextLayouterDevice
.setFontAttribute(aFontAttribute
, fFontWidth
, fFontHeight
, aLocale
);
265 ::std::vector
< double > aTextArray(rSvgTextPosition
.getX());
267 if(!aTextArray
.empty() && aTextArray
.size() < nLength
)
269 const sal_uInt32
nArray(aTextArray
.size());
275 if(rSvgTextPosition
.getParent() && rSvgTextPosition
.getParent()->getAbsoluteX())
277 fStartX
= rSvgTextPosition
.getParent()->getPosition().getX();
281 fStartX
= aTextArray
[nArray
- 1];
284 ::std::vector
< double > aExtendArray(aTextLayouterDevice
.getTextArray(getText(), nArray
, nLength
- nArray
));
285 aTextArray
.reserve(nLength
);
287 for(const auto &a
: aExtendArray
)
289 aTextArray
.push_back(a
+ fStartX
);
294 // get current TextPosition and TextWidth in units
295 basegfx::B2DPoint
aPosition(rSvgTextPosition
.getPosition());
296 double fTextWidth(aTextLayouterDevice
.getTextWidth(getText(), nIndex
, nLength
));
298 // check for user-given TextLength
299 if(0.0 != rSvgTextPosition
.getTextLength()
300 && !basegfx::fTools::equal(fTextWidth
, rSvgTextPosition
.getTextLength()))
302 const double fFactor(rSvgTextPosition
.getTextLength() / fTextWidth
);
304 if(rSvgTextPosition
.getLengthAdjust())
306 // spacing, need to create and expand TextArray
307 if(aTextArray
.empty())
309 aTextArray
= aTextLayouterDevice
.getTextArray(getText(), nIndex
, nLength
);
312 for(auto &a
: aTextArray
)
319 // spacing and glyphs, just apply to FontWidth
320 fFontWidth
*= fFactor
;
323 fTextWidth
= rSvgTextPosition
.getTextLength();
327 TextAlign
aTextAlign(rSvgStyleAttributes
.getTextAlign());
329 // map TextAnchor to TextAlign, there seems not to be a difference
330 if(TextAnchor::notset
!= rSvgStyleAttributes
.getTextAnchor())
332 switch(rSvgStyleAttributes
.getTextAnchor())
334 case TextAnchor::start
:
336 aTextAlign
= TextAlign::left
;
339 case TextAnchor::middle
:
341 aTextAlign
= TextAlign::center
;
344 case TextAnchor::end
:
346 aTextAlign
= TextAlign::right
;
359 case TextAlign::right
:
361 aPosition
.setX(aPosition
.getX() - fTextWidth
);
364 case TextAlign::center
:
366 aPosition
.setX(aPosition
.getX() - (fTextWidth
* 0.5));
369 case TextAlign::notset
:
370 case TextAlign::left
:
371 case TextAlign::justify
:
373 // TextAlign::notset, TextAlign::left: nothing to do
374 // TextAlign::justify is not clear currently; handle as TextAlign::left
380 const BaselineShift
aBaselineShift(rSvgStyleAttributes
.getBaselineShift());
382 // apply BaselineShift
383 switch(aBaselineShift
)
385 case BaselineShift::Sub
:
387 aPosition
.setY(aPosition
.getY() + aTextLayouterDevice
.getUnderlineOffset());
390 case BaselineShift::Super
:
392 aPosition
.setY(aPosition
.getY() + aTextLayouterDevice
.getOverlineOffset());
395 case BaselineShift::Percentage
:
396 case BaselineShift::Length
:
398 const SvgNumber
aNumber(rSvgStyleAttributes
.getBaselineShiftNumber());
399 const double mfBaselineShift(aNumber
.solve(*this));
401 aPosition
.setY(aPosition
.getY() + mfBaselineShift
);
404 default: // BaselineShift::Baseline
412 basegfx::BColor
aFill(0, 0, 0);
413 if(rSvgStyleAttributes
.getFill())
414 aFill
= *rSvgStyleAttributes
.getFill();
417 double fFillOpacity
= 1.0;
418 if (rSvgStyleAttributes
.getFillOpacity().isSet())
420 fFillOpacity
= rSvgStyleAttributes
.getFillOpacity().getNumber();
423 // prepare TextTransformation
424 basegfx::B2DHomMatrix aTextTransform
;
426 aTextTransform
.scale(fFontWidth
, fFontHeight
);
427 aTextTransform
.translate(aPosition
.getX(), aPosition
.getY());
429 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
430 const TextDecoration
aDeco(rSvgStyleAttributes
.getTextDecoration());
432 if(TextDecoration::underline
== aDeco
433 || TextDecoration::overline
== aDeco
434 || TextDecoration::line_through
== aDeco
)
436 // get the fill for decoration as described by SVG. We cannot
437 // have different stroke colors/definitions for those, though
438 const SvgStyleAttributes
* pDecoDef
= rSvgStyleAttributes
.getTextDecorationDefiningSvgStyleAttributes();
440 basegfx::BColor
aDecoColor(aFill
);
441 if(pDecoDef
&& pDecoDef
->getFill())
442 aDecoColor
= *pDecoDef
->getFill();
444 TextLine eFontOverline
= TEXT_LINE_NONE
;
445 if(TextDecoration::overline
== aDeco
)
446 eFontOverline
= TEXT_LINE_SINGLE
;
448 TextLine eFontUnderline
= TEXT_LINE_NONE
;
449 if(TextDecoration::underline
== aDeco
)
450 eFontUnderline
= TEXT_LINE_SINGLE
;
452 TextStrikeout eTextStrikeout
= TEXT_STRIKEOUT_NONE
;
453 if(TextDecoration::line_through
== aDeco
)
454 eTextStrikeout
= TEXT_STRIKEOUT_SINGLE
;
456 // create decorated text primitive
457 pRetval
= new TextDecoratedPortionPrimitive2D(
462 std::move(aTextArray
),
469 // extra props for decorated
477 TEXT_FONT_EMPHASIS_MARK_NONE
,
485 // create text primitive
486 pRetval
= new TextSimplePortionPrimitive2D(
491 std::move(aTextArray
),
498 if (fFillOpacity
!= 1.0)
500 pRetval
= new UnifiedTransparencePrimitive2D(
501 drawinglayer::primitive2d::Primitive2DContainer
{ pRetval
},
505 // advance current TextPosition
506 rSvgTextPosition
.setPosition(rSvgTextPosition
.getPosition() + basegfx::B2DVector(fTextWidth
, 0.0));
512 void SvgCharacterNode::decomposeTextWithStyle(
513 Primitive2DContainer
& rTarget
,
514 SvgTextPosition
& rSvgTextPosition
,
515 const SvgStyleAttributes
& rSvgStyleAttributes
) const
517 const Primitive2DReference
xRef(
518 createSimpleTextPrimitive(
520 rSvgStyleAttributes
));
522 if(!(xRef
.is() && (Visibility::visible
== rSvgStyleAttributes
.getVisibility())))
525 if(!rSvgTextPosition
.isRotated())
527 rTarget
.push_back(xRef
);
531 // need to apply rotations to each character as given
532 const TextSimplePortionPrimitive2D
* pCandidate
=
533 dynamic_cast< const TextSimplePortionPrimitive2D
* >(xRef
.get());
537 localTextBreakupHelper
alocalTextBreakupHelper(*pCandidate
, rSvgTextPosition
);
538 Primitive2DContainer aResult
= alocalTextBreakupHelper
.extractResult();
542 rTarget
.append(std::move(aResult
));
545 // also consume for the implied single space
546 rSvgTextPosition
.consumeRotation();
550 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
555 void SvgCharacterNode::whiteSpaceHandling()
557 bool bIsDefault(XmlSpace::Default
== getXmlSpace());
558 // if xml:space="default" then remove all newline characters, otherwise convert them to space
559 // convert tab to space too
560 maText
= maTextBeforeSpaceHandling
= maText
.replaceAll(u
"\n", bIsDefault
? u
"" : u
" ").replaceAll(u
"\t", u
" ");
564 // strip of all leading and trailing spaces
565 // and consolidate contiguous space
566 maText
= consolidateContiguousSpace(maText
.trim());
570 void SvgCharacterNode::addGap()
575 void SvgCharacterNode::concatenate(std::u16string_view rText
)
580 void SvgCharacterNode::decomposeText(Primitive2DContainer
& rTarget
, SvgTextPosition
& rSvgTextPosition
) const
582 if(!getText().isEmpty())
584 const SvgStyleAttributes
* pSvgStyleAttributes
= getSvgStyleAttributes();
586 if(pSvgStyleAttributes
)
588 decomposeTextWithStyle(rTarget
, rSvgTextPosition
, *pSvgStyleAttributes
);
594 SvgTextPosition::SvgTextPosition(
595 SvgTextPosition
* pParent
,
596 const InfoProvider
& rInfoProvider
,
597 const SvgTextPositions
& rSvgTextPositions
)
599 maRotate(solveSvgNumberVector(rSvgTextPositions
.getRotate(), rInfoProvider
)),
602 mbLengthAdjust(rSvgTextPositions
.getLengthAdjust()),
605 // get TextLength if provided
606 if(rSvgTextPositions
.getTextLength().isSet())
608 mfTextLength
= rSvgTextPositions
.getTextLength().solve(rInfoProvider
);
611 // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
612 // but it seems to be degrees. Convert here to radians
613 if(!maRotate
.empty())
615 for (double& f
: maRotate
)
617 f
= basegfx::deg2rad(f
);
621 // get text positions X
622 const sal_uInt32
nSizeX(rSvgTextPositions
.getX().size());
626 // we have absolute positions, get first one as current text position X
627 maPosition
.setX(rSvgTextPositions
.getX()[0].solve(rInfoProvider
, NumberType::xcoordinate
));
632 // fill deltas to maX
635 for(sal_uInt32
a(1); a
< nSizeX
; a
++)
637 maX
.push_back(rSvgTextPositions
.getX()[a
].solve(rInfoProvider
, NumberType::xcoordinate
) - maPosition
.getX());
643 // no absolute position, get from parent
646 maPosition
.setX(pParent
->getPosition().getX());
649 const sal_uInt32
nSizeDx(rSvgTextPositions
.getDx().size());
653 // relative positions given, translate position derived from parent
654 maPosition
.setX(maPosition
.getX() + rSvgTextPositions
.getDx()[0].solve(rInfoProvider
, NumberType::xcoordinate
));
658 // fill deltas to maX
659 maX
.reserve(nSizeDx
);
661 for(sal_uInt32
a(1); a
< nSizeDx
; a
++)
663 maX
.push_back(rSvgTextPositions
.getDx()[a
].solve(rInfoProvider
, NumberType::xcoordinate
));
669 // get text positions Y
670 const sal_uInt32
nSizeY(rSvgTextPositions
.getY().size());
674 // we have absolute positions, get first one as current text position Y
675 maPosition
.setY(rSvgTextPositions
.getY()[0].solve(rInfoProvider
, NumberType::ycoordinate
));
680 // fill deltas to maY
683 for(sal_uInt32
a(1); a
< nSizeY
; a
++)
685 maY
.push_back(rSvgTextPositions
.getY()[a
].solve(rInfoProvider
, NumberType::ycoordinate
) - maPosition
.getY());
691 // no absolute position, get from parent
694 maPosition
.setY(pParent
->getPosition().getY());
697 const sal_uInt32
nSizeDy(rSvgTextPositions
.getDy().size());
701 // relative positions given, translate position derived from parent
702 maPosition
.setY(maPosition
.getY() + rSvgTextPositions
.getDy()[0].solve(rInfoProvider
, NumberType::ycoordinate
));
706 // fill deltas to maY
707 maY
.reserve(nSizeDy
);
709 for(sal_uInt32
a(1); a
< nSizeDy
; a
++)
711 maY
.push_back(rSvgTextPositions
.getDy()[a
].solve(rInfoProvider
, NumberType::ycoordinate
));
718 bool SvgTextPosition::isRotated() const
724 return getParent()->isRotated();
737 double SvgTextPosition::consumeRotation()
745 fRetval
= mpParent
->consumeRotation();
754 const sal_uInt32
nSize(maRotate
.size());
756 if(mnRotationIndex
< nSize
)
758 fRetval
= maRotate
[mnRotationIndex
++];
762 fRetval
= maRotate
[nSize
- 1];
769 } // end of namespace svgio
771 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */