bump product version to 7.2.5.1
[LibreOffice.git] / svgio / source / svgreader / svgcharacternode.cxx
blobeb7037d17f2c8dc38c5b8f8be083faa6bda18bc7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
28 namespace svgio::svgreader
30 SvgTextPositions::SvgTextPositions()
31 : maX(),
32 maY(),
33 maDx(),
34 maDy(),
35 maRotate(),
36 maTextLength(),
37 mbLengthAdjust(true)
41 void SvgTextPositions::parseTextPositionAttributes(SVGToken aSVGToken, const OUString& aContent)
43 // parse own
44 switch(aSVGToken)
46 case SVGToken::X:
48 if(!aContent.isEmpty())
50 SvgNumberVector aVector;
52 if(readSvgNumberVector(aContent, aVector))
54 setX(aVector);
57 break;
59 case SVGToken::Y:
61 if(!aContent.isEmpty())
63 SvgNumberVector aVector;
65 if(readSvgNumberVector(aContent, aVector))
67 setY(aVector);
70 break;
72 case SVGToken::Dx:
74 if(!aContent.isEmpty())
76 SvgNumberVector aVector;
78 if(readSvgNumberVector(aContent, aVector))
80 setDx(aVector);
83 break;
85 case SVGToken::Dy:
87 if(!aContent.isEmpty())
89 SvgNumberVector aVector;
91 if(readSvgNumberVector(aContent, aVector))
93 setDy(aVector);
96 break;
98 case SVGToken::Rotate:
100 if(!aContent.isEmpty())
102 SvgNumberVector aVector;
104 if(readSvgNumberVector(aContent, aVector))
106 setRotate(aVector);
109 break;
111 case SVGToken::TextLength:
113 SvgNumber aNum;
115 if(readSingleNumber(aContent, aNum))
117 if(aNum.isPositive())
119 setTextLength(aNum);
122 break;
124 case SVGToken::LengthAdjust:
126 if(!aContent.isEmpty())
128 if(aContent.startsWith("spacing"))
130 setLengthAdjust(true);
132 else if(aContent.startsWith("spacingAndGlyphs"))
134 setLengthAdjust(false);
137 break;
139 default:
141 break;
146 namespace {
148 class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
150 private:
151 SvgTextPosition& mrSvgTextPosition;
153 protected:
154 /// allow user callback to allow changes to the new TextTransformation. Default
155 /// does nothing.
156 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
158 public:
159 localTextBreakupHelper(
160 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
161 SvgTextPosition& rSvgTextPosition)
162 : drawinglayer::primitive2d::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());
174 if(0.0 != fRotation)
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());
183 return true;
186 SvgCharacterNode::SvgCharacterNode(
187 SvgDocument& rDocument,
188 SvgNode* pParent,
189 const OUString& rText)
190 : SvgNode(SVGToken::Character, rDocument, pParent),
191 maText(rText)
195 SvgCharacterNode::~SvgCharacterNode()
199 const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
201 // no own style, use parent's
202 if(getParent())
204 return getParent()->getSvgStyleAttributes();
206 else
208 return nullptr;
212 rtl::Reference<drawinglayer::primitive2d::TextSimplePortionPrimitive2D> SvgCharacterNode::createSimpleTextPrimitive(
213 SvgTextPosition& rSvgTextPosition,
214 const SvgStyleAttributes& rSvgStyleAttributes) const
216 // prepare retval, index and length
217 rtl::Reference<drawinglayer::primitive2d::TextSimplePortionPrimitive2D> pRetval;
218 sal_uInt32 nLength(getText().getLength());
220 if(nLength)
222 sal_uInt32 nIndex(0);
223 // prepare FontAttribute
224 const SvgStringVector& rFontFamilyVector = rSvgStyleAttributes.getFontFamily();
225 OUString aFontFamily = rFontFamilyVector.empty() ?
226 OUString("Times New Roman") :
227 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(
242 aFontFamily,
243 OUString(),
244 nFontWeight,
245 false/*bSymbol*/,
246 false/*bVertical*/,
247 bItalic,
248 false/*bMonospaced*/,
249 false/*bOutline*/,
250 false/*bRTL*/,
251 false/*bBiDiStrong*/);
253 // prepare FontSizeNumber
254 double fFontWidth(rSvgStyleAttributes.getFontSizeNumber().solve(*this));
255 double fFontHeight(fFontWidth);
257 // prepare locale
258 css::lang::Locale aLocale;
260 // prepare TextLayouterDevice
261 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
262 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
264 // prepare TextArray
265 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
267 if(!aTextArray.empty() && aTextArray.size() < nLength)
269 const sal_uInt32 nArray(aTextArray.size());
271 if(nArray < nLength)
273 double fStartX(0.0);
275 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
277 fStartX = rSvgTextPosition.getParent()->getPosition().getX();
279 else
281 fStartX = aTextArray[nArray - 1];
284 ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
285 aTextArray.reserve(nLength);
287 for(size_t a(0); a < aExtendArray.size(); a++)
289 aTextArray.push_back(aExtendArray[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(size_t a(0); a < aTextArray.size(); a++)
314 aTextArray[a] *= fFactor;
317 else
319 // spacing and glyphs, just apply to FontWidth
320 fFontWidth *= fFactor;
323 fTextWidth = rSvgTextPosition.getTextLength();
326 // get TextAlign
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;
337 break;
339 case TextAnchor::middle:
341 aTextAlign = TextAlign::center;
342 break;
344 case TextAnchor::end:
346 aTextAlign = TextAlign::right;
347 break;
349 default:
351 break;
356 // apply TextAlign
357 switch(aTextAlign)
359 case TextAlign::right:
361 aPosition.setX(aPosition.getX() - fTextWidth);
362 break;
364 case TextAlign::center:
366 aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
367 break;
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
375 break;
379 // get BaselineShift
380 const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
382 // apply BaselineShift
383 switch(aBaselineShift)
385 case BaselineShift::Sub:
387 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
388 break;
390 case BaselineShift::Super:
392 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
393 break;
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);
402 break;
404 default: // BaselineShift::Baseline
406 // nothing to do
407 break;
411 // get fill color
412 const basegfx::BColor aFill(rSvgStyleAttributes.getFill()
413 ? *rSvgStyleAttributes.getFill()
414 : basegfx::BColor(0.0, 0.0, 0.0));
416 // prepare TextTransformation
417 basegfx::B2DHomMatrix aTextTransform;
419 aTextTransform.scale(fFontWidth, fFontHeight);
420 aTextTransform.translate(aPosition.getX(), aPosition.getY());
422 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
423 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
425 if(TextDecoration::underline == aDeco
426 || TextDecoration::overline == aDeco
427 || TextDecoration::line_through == aDeco)
429 // get the fill for decoration as described by SVG. We cannot
430 // have different stroke colors/definitions for those, though
431 const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
432 const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill);
434 // create decorated text primitive
435 pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
436 aTextTransform,
437 getText(),
438 nIndex,
439 nLength,
440 aTextArray,
441 aFontAttribute,
442 aLocale,
443 aFill,
444 COL_TRANSPARENT,
446 // extra props for decorated
447 aDecoColor,
448 aDecoColor,
449 TextDecoration::overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
450 TextDecoration::underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
451 false,
452 TextDecoration::line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
453 false,
454 drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE,
455 true,
456 false,
457 drawinglayer::primitive2d::TEXT_RELIEF_NONE,
458 false);
460 else
462 // create text primitive
463 pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
464 aTextTransform,
465 getText(),
466 nIndex,
467 nLength,
468 aTextArray,
469 aFontAttribute,
470 aLocale,
471 aFill);
474 // advance current TextPosition
475 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
478 return pRetval;
481 void SvgCharacterNode::decomposeTextWithStyle(
482 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
483 SvgTextPosition& rSvgTextPosition,
484 const SvgStyleAttributes& rSvgStyleAttributes) const
486 const drawinglayer::primitive2d::Primitive2DReference xRef(
487 createSimpleTextPrimitive(
488 rSvgTextPosition,
489 rSvgStyleAttributes));
491 if(!(xRef.is() && (Visibility::visible == rSvgStyleAttributes.getVisibility())))
492 return;
494 if(!rSvgTextPosition.isRotated())
496 rTarget.push_back(xRef);
498 else
500 // need to apply rotations to each character as given
501 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate =
502 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get());
504 if(pCandidate)
506 const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
507 const drawinglayer::primitive2d::Primitive2DContainer& aResult(
508 alocalTextBreakupHelper.getResult());
510 if(!aResult.empty())
512 rTarget.append(aResult);
515 // also consume for the implied single space
516 rSvgTextPosition.consumeRotation();
518 else
520 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
525 void SvgCharacterNode::whiteSpaceHandling()
527 if (XmlSpace::Default == getXmlSpace())
529 maText = whiteSpaceHandlingDefault(maText);
531 else
533 maText = whiteSpaceHandlingPreserve(maText);
537 void SvgCharacterNode::addGap()
539 maText += " ";
542 void SvgCharacterNode::concatenate(std::u16string_view rText)
544 maText += rText;
547 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const
549 if(!getText().isEmpty())
551 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
553 if(pSvgStyleAttributes)
555 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
561 SvgTextPosition::SvgTextPosition(
562 SvgTextPosition* pParent,
563 const InfoProvider& rInfoProvider,
564 const SvgTextPositions& rSvgTextPositions)
565 : mpParent(pParent),
566 maX(), // computed below
567 maY(), // computed below
568 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider)),
569 mfTextLength(0.0),
570 maPosition(), // computed below
571 mnRotationIndex(0),
572 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
573 mbAbsoluteX(false)
575 // get TextLength if provided
576 if(rSvgTextPositions.getTextLength().isSet())
578 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider);
581 // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
582 // but it seems to be degrees. Convert here to radians
583 if(!maRotate.empty())
585 for (double& f : maRotate)
587 f = basegfx::deg2rad(f);
591 // get text positions X
592 const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
594 if(nSizeX)
596 // we have absolute positions, get first one as current text position X
597 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, NumberType::xcoordinate));
598 mbAbsoluteX = true;
600 if(nSizeX > 1)
602 // fill deltas to maX
603 maX.reserve(nSizeX);
605 for(sal_uInt32 a(1); a < nSizeX; a++)
607 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, NumberType::xcoordinate) - maPosition.getX());
611 else
613 // no absolute position, get from parent
614 if(pParent)
616 maPosition.setX(pParent->getPosition().getX());
619 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
621 if(nSizeDx)
623 // relative positions given, translate position derived from parent
624 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, NumberType::xcoordinate));
626 if(nSizeDx > 1)
628 // fill deltas to maX
629 maX.reserve(nSizeDx);
631 for(sal_uInt32 a(1); a < nSizeDx; a++)
633 maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate));
639 // get text positions Y
640 const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
642 if(nSizeY)
644 // we have absolute positions, get first one as current text position Y
645 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, NumberType::ycoordinate));
646 mbAbsoluteX = true;
648 if(nSizeY > 1)
650 // fill deltas to maY
651 maY.reserve(nSizeY);
653 for(sal_uInt32 a(1); a < nSizeY; a++)
655 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, NumberType::ycoordinate) - maPosition.getY());
659 else
661 // no absolute position, get from parent
662 if(pParent)
664 maPosition.setY(pParent->getPosition().getY());
667 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
669 if(nSizeDy)
671 // relative positions given, translate position derived from parent
672 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, NumberType::ycoordinate));
674 if(nSizeDy > 1)
676 // fill deltas to maY
677 maY.reserve(nSizeDy);
679 for(sal_uInt32 a(1); a < nSizeDy; a++)
681 maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, NumberType::ycoordinate));
688 bool SvgTextPosition::isRotated() const
690 if(maRotate.empty())
692 if(getParent())
694 return getParent()->isRotated();
696 else
698 return false;
701 else
703 return true;
707 double SvgTextPosition::consumeRotation()
709 double fRetval(0.0);
711 if(maRotate.empty())
713 if(getParent())
715 fRetval = mpParent->consumeRotation();
717 else
719 fRetval = 0.0;
722 else
724 const sal_uInt32 nSize(maRotate.size());
726 if(mnRotationIndex < nSize)
728 fRetval = maRotate[mnRotationIndex++];
730 else
732 fRetval = maRotate[nSize - 1];
736 return fRetval;
739 } // end of namespace svgio
741 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */