lok: vcl: fix multiple floatwin removal case more robustly.
[LibreOffice.git] / svgio / source / svgreader / svgcharacternode.cxx
blob70e99384b4402c993b3ad893ff807cf0767fec33
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/groupprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
29 namespace svgio
31 namespace svgreader
33 SvgTextPositions::SvgTextPositions()
34 : maX(),
35 maY(),
36 maDx(),
37 maDy(),
38 maRotate(),
39 maTextLength(),
40 mbLengthAdjust(true)
44 void SvgTextPositions::parseTextPositionAttributes(SVGToken aSVGToken, const OUString& aContent)
46 // parse own
47 switch(aSVGToken)
49 case SVGTokenX:
51 if(!aContent.isEmpty())
53 SvgNumberVector aVector;
55 if(readSvgNumberVector(aContent, aVector))
57 setX(aVector);
60 break;
62 case SVGTokenY:
64 if(!aContent.isEmpty())
66 SvgNumberVector aVector;
68 if(readSvgNumberVector(aContent, aVector))
70 setY(aVector);
73 break;
75 case SVGTokenDx:
77 if(!aContent.isEmpty())
79 SvgNumberVector aVector;
81 if(readSvgNumberVector(aContent, aVector))
83 setDx(aVector);
86 break;
88 case SVGTokenDy:
90 if(!aContent.isEmpty())
92 SvgNumberVector aVector;
94 if(readSvgNumberVector(aContent, aVector))
96 setDy(aVector);
99 break;
101 case SVGTokenRotate:
103 if(!aContent.isEmpty())
105 SvgNumberVector aVector;
107 if(readSvgNumberVector(aContent, aVector))
109 setRotate(aVector);
112 break;
114 case SVGTokenTextLength:
116 SvgNumber aNum;
118 if(readSingleNumber(aContent, aNum))
120 if(aNum.isPositive())
122 setTextLength(aNum);
125 break;
127 case SVGTokenLengthAdjust:
129 if(!aContent.isEmpty())
131 if(aContent.startsWith("spacing"))
133 setLengthAdjust(true);
135 else if(aContent.startsWith("spacingAndGlyphs"))
137 setLengthAdjust(false);
140 break;
142 default:
144 break;
149 } // end of namespace svgreader
150 } // end of namespace svgio
153 namespace svgio
155 namespace svgreader
157 class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
159 private:
160 SvgTextPosition& mrSvgTextPosition;
162 protected:
163 /// allow user callback to allow changes to the new TextTransformation. Default
164 /// does nothing.
165 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
167 public:
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());
181 if(0.0 != fRotation)
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());
190 return true;
193 } // end of namespace svgreader
194 } // end of namespace svgio
197 namespace svgio
199 namespace svgreader
201 SvgCharacterNode::SvgCharacterNode(
202 SvgDocument& rDocument,
203 SvgNode* pParent,
204 const OUString& rText)
205 : SvgNode(SVGTokenCharacter, rDocument, pParent),
206 maText(rText)
210 SvgCharacterNode::~SvgCharacterNode()
214 const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
216 // no own style, use parent's
217 if(getParent())
219 return getParent()->getSvgStyleAttributes();
221 else
223 return nullptr;
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());
236 if(nLength)
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(
257 aFontFamily,
258 OUString(),
259 nFontWeight,
260 false/*bSymbol*/,
261 false/*bVertical*/,
262 bItalic,
263 false/*bMonospaced*/,
264 false/*bOutline*/,
265 false/*bRTL*/,
266 false/*bBiDiStrong*/);
268 // prepare FontSizeNumber
269 double fFontWidth(rSvgStyleAttributes.getFontSizeNumber().solve(*this));
270 double fFontHeight(fFontWidth);
272 // prepare locale
273 css::lang::Locale aLocale;
275 // prepare TextLayouterDevice
276 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
277 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
279 // prepare TextArray
280 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
282 if(!aTextArray.empty() && aTextArray.size() < nLength)
284 const sal_uInt32 nArray(aTextArray.size());
286 if(nArray < nLength)
288 double fStartX(0.0);
290 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
292 fStartX = rSvgTextPosition.getParent()->getPosition().getX();
294 else
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;
332 else
334 // spacing and glyphs, just apply to FontWidth
335 fFontWidth *= fFactor;
338 fTextWidth = rSvgTextPosition.getTextLength();
341 // get TextAlign
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;
352 break;
354 case TextAnchor_middle:
356 aTextAlign = TextAlign_center;
357 break;
359 case TextAnchor_end:
361 aTextAlign = TextAlign_right;
362 break;
364 default:
366 break;
371 // apply TextAlign
372 switch(aTextAlign)
374 case TextAlign_right:
376 aPosition.setX(aPosition.getX() - fTextWidth);
377 break;
379 case TextAlign_center:
381 aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
382 break;
384 case TextAlign_notset:
385 case TextAlign_left:
386 case TextAlign_justify:
388 // TextAlign_notset, TextAlign_left: nothing to do
389 // TextAlign_justify is not clear currently; handle as TextAlign_left
390 break;
394 // get BaselineShift
395 const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
397 // apply BaselineShift
398 switch(aBaselineShift)
400 case BaselineShift_Sub:
402 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
403 break;
405 case BaselineShift_Super:
407 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
408 break;
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);
417 break;
419 default: // BaselineShift_Baseline
421 // nothing to do
422 break;
426 // get fill color
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(
451 aTextTransform,
452 getText(),
453 nIndex,
454 nLength,
455 aTextArray,
456 aFontAttribute,
457 aLocale,
458 aFill,
459 COL_TRANSPARENT,
461 // extra props for decorated
462 aDecoColor,
463 aDecoColor,
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,
466 false,
467 TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
468 false,
469 drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE,
470 true,
471 false,
472 drawinglayer::primitive2d::TEXT_RELIEF_NONE,
473 false);
475 else
477 // create text primitive
478 pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
479 aTextTransform,
480 getText(),
481 nIndex,
482 nLength,
483 aTextArray,
484 aFontAttribute,
485 aLocale,
486 aFill);
489 // advance current TextPosition
490 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
493 return pRetval;
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(
503 rSvgTextPosition,
504 rSvgStyleAttributes));
506 if(xRef.is() && (Visibility_visible == rSvgStyleAttributes.getVisibility()))
508 if(!rSvgTextPosition.isRotated())
510 rTarget.push_back(xRef);
512 else
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());
518 if(pCandidate)
520 const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
521 const drawinglayer::primitive2d::Primitive2DContainer& aResult(
522 alocalTextBreakupHelper.getResult());
524 if(!aResult.empty())
526 rTarget.append(aResult);
529 // also consume for the implied single space
530 rSvgTextPosition.consumeRotation();
532 else
534 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
540 void SvgCharacterNode::whiteSpaceHandling()
542 if(XmlSpace_default == getXmlSpace())
544 maText = whiteSpaceHandlingDefault(maText);
546 else
548 maText = whiteSpaceHandlingPreserve(maText);
552 void SvgCharacterNode::addGap()
554 maText += " ";
557 void SvgCharacterNode::concatenate(const OUString& rText)
559 maText += 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
579 namespace svgio
581 namespace svgreader
583 SvgTextPosition::SvgTextPosition(
584 SvgTextPosition* pParent,
585 const InfoProvider& rInfoProvider,
586 const SvgTextPositions& rSvgTextPositions)
587 : mpParent(pParent),
588 maX(), // computed below
589 maY(), // computed below
590 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider)),
591 mfTextLength(0.0),
592 maPosition(), // computed below
593 mnRotationIndex(0),
594 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
595 mbAbsoluteX(false)
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());
616 if(nSizeX)
618 // we have absolute positions, get first one as current text position X
619 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
620 mbAbsoluteX = true;
622 if(nSizeX > 1)
624 // fill deltas to maX
625 maX.reserve(nSizeX);
627 for(sal_uInt32 a(1); a < nSizeX; a++)
629 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
633 else
635 // no absolute position, get from parent
636 if(pParent)
638 maPosition.setX(pParent->getPosition().getX());
641 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
643 if(nSizeDx)
645 // relative positions given, translate position derived from parent
646 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
648 if(nSizeDx > 1)
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());
664 if(nSizeY)
666 // we have absolute positions, get first one as current text position Y
667 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
668 mbAbsoluteX = true;
670 if(nSizeY > 1)
672 // fill deltas to maY
673 maY.reserve(nSizeY);
675 for(sal_uInt32 a(1); a < nSizeY; a++)
677 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
681 else
683 // no absolute position, get from parent
684 if(pParent)
686 maPosition.setY(pParent->getPosition().getY());
689 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
691 if(nSizeDy)
693 // relative positions given, translate position derived from parent
694 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
696 if(nSizeDy > 1)
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
712 if(maRotate.empty())
714 if(getParent())
716 return getParent()->isRotated();
718 else
720 return false;
723 else
725 return true;
729 double SvgTextPosition::consumeRotation()
731 double fRetval(0.0);
733 if(maRotate.empty())
735 if(getParent())
737 fRetval = mpParent->consumeRotation();
739 else
741 fRetval = 0.0;
744 else
746 const sal_uInt32 nSize(maRotate.size());
748 if(mnRotationIndex < nSize)
750 fRetval = maRotate[mnRotationIndex++];
752 else
754 fRetval = maRotate[nSize - 1];
758 return fRetval;
761 } // end of namespace svgreader
762 } // end of namespace svgio
764 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */