calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / svgio / source / svgreader / svgcharacternode.cxx
blob4ffc46a483db927cc0efb37b3e5c3c3f38fe7e3c
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>
27 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
28 #include <utility>
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)
43 // parse own
44 switch(aSVGToken)
46 case SVGToken::X:
48 if(!aContent.empty())
50 SvgNumberVector aVector;
52 if(readSvgNumberVector(aContent, aVector))
54 setX(std::move(aVector));
57 break;
59 case SVGToken::Y:
61 if(!aContent.empty())
63 SvgNumberVector aVector;
65 if(readSvgNumberVector(aContent, aVector))
67 setY(std::move(aVector));
70 break;
72 case SVGToken::Dx:
74 if(!aContent.empty())
76 SvgNumberVector aVector;
78 if(readSvgNumberVector(aContent, aVector))
80 setDx(std::move(aVector));
83 break;
85 case SVGToken::Dy:
87 if(!aContent.empty())
89 SvgNumberVector aVector;
91 if(readSvgNumberVector(aContent, aVector))
93 setDy(std::move(aVector));
96 break;
98 case SVGToken::Rotate:
100 if(!aContent.empty())
102 SvgNumberVector aVector;
104 if(readSvgNumberVector(aContent, aVector))
106 setRotate(std::move(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.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);
137 break;
139 default:
141 break;
146 namespace {
148 class localTextBreakupHelper : public 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 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());
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 OUString aText)
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
202 if(getParent())
204 return getParent()->getSvgStyleAttributes();
206 else
208 return nullptr;
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());
220 if(nLength)
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(
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 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(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)
314 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 basegfx::BColor aFill(0, 0, 0);
413 if(rSvgStyleAttributes.getFill())
414 aFill = *rSvgStyleAttributes.getFill();
416 // get fill opacity
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(
458 aTextTransform,
459 getText(),
460 nIndex,
461 nLength,
462 std::move(aTextArray),
464 aFontAttribute,
465 aLocale,
466 aFill,
467 COL_TRANSPARENT,
469 // extra props for decorated
470 aDecoColor,
471 aDecoColor,
472 eFontOverline,
473 eFontUnderline,
474 false,
475 eTextStrikeout,
476 false,
477 TEXT_FONT_EMPHASIS_MARK_NONE,
478 true,
479 false,
480 TEXT_RELIEF_NONE,
481 false);
483 else
485 // create text primitive
486 pRetval = new TextSimplePortionPrimitive2D(
487 aTextTransform,
488 getText(),
489 nIndex,
490 nLength,
491 std::move(aTextArray),
493 aFontAttribute,
494 aLocale,
495 aFill);
498 if (fFillOpacity != 1.0)
500 pRetval = new UnifiedTransparencePrimitive2D(
501 drawinglayer::primitive2d::Primitive2DContainer{ pRetval },
502 1.0 - fFillOpacity);
505 // advance current TextPosition
506 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
509 return pRetval;
512 void SvgCharacterNode::decomposeTextWithStyle(
513 Primitive2DContainer& rTarget,
514 SvgTextPosition& rSvgTextPosition,
515 const SvgStyleAttributes& rSvgStyleAttributes) const
517 const Primitive2DReference xRef(
518 createSimpleTextPrimitive(
519 rSvgTextPosition,
520 rSvgStyleAttributes));
522 if(!(xRef.is() && (Visibility::visible == rSvgStyleAttributes.getVisibility())))
523 return;
525 if(!rSvgTextPosition.isRotated())
527 rTarget.push_back(xRef);
529 else
531 // need to apply rotations to each character as given
532 const TextSimplePortionPrimitive2D* pCandidate =
533 dynamic_cast< const TextSimplePortionPrimitive2D* >(xRef.get());
535 if(pCandidate)
537 localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
538 Primitive2DContainer aResult = alocalTextBreakupHelper.extractResult();
540 if(!aResult.empty())
542 rTarget.append(std::move(aResult));
545 // also consume for the implied single space
546 rSvgTextPosition.consumeRotation();
548 else
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" ");
562 if(bIsDefault)
564 // strip of all leading and trailing spaces
565 // and consolidate contiguous space
566 maText = consolidateContiguousSpace(maText.trim());
570 void SvgCharacterNode::addGap()
572 maText += " ";
575 void SvgCharacterNode::concatenate(std::u16string_view rText)
577 maText += 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)
598 : mpParent(pParent),
599 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider)),
600 mfTextLength(0.0),
601 mnRotationIndex(0),
602 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
603 mbAbsoluteX(false)
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());
624 if(nSizeX)
626 // we have absolute positions, get first one as current text position X
627 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, NumberType::xcoordinate));
628 mbAbsoluteX = true;
630 if(nSizeX > 1)
632 // fill deltas to maX
633 maX.reserve(nSizeX);
635 for(sal_uInt32 a(1); a < nSizeX; a++)
637 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, NumberType::xcoordinate) - maPosition.getX());
641 else
643 // no absolute position, get from parent
644 if(pParent)
646 maPosition.setX(pParent->getPosition().getX());
649 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
651 if(nSizeDx)
653 // relative positions given, translate position derived from parent
654 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, NumberType::xcoordinate));
656 if(nSizeDx > 1)
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());
672 if(nSizeY)
674 // we have absolute positions, get first one as current text position Y
675 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, NumberType::ycoordinate));
676 mbAbsoluteX = true;
678 if(nSizeY > 1)
680 // fill deltas to maY
681 maY.reserve(nSizeY);
683 for(sal_uInt32 a(1); a < nSizeY; a++)
685 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, NumberType::ycoordinate) - maPosition.getY());
689 else
691 // no absolute position, get from parent
692 if(pParent)
694 maPosition.setY(pParent->getPosition().getY());
697 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
699 if(nSizeDy)
701 // relative positions given, translate position derived from parent
702 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, NumberType::ycoordinate));
704 if(nSizeDy > 1)
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
720 if(maRotate.empty())
722 if(getParent())
724 return getParent()->isRotated();
726 else
728 return false;
731 else
733 return true;
737 double SvgTextPosition::consumeRotation()
739 double fRetval(0.0);
741 if(maRotate.empty())
743 if(getParent())
745 fRetval = mpParent->consumeRotation();
747 else
749 fRetval = 0.0;
752 else
754 const sal_uInt32 nSize(maRotate.size());
756 if(mnRotationIndex < nSize)
758 fRetval = maRotate[mnRotationIndex++];
760 else
762 fRetval = maRotate[nSize - 1];
766 return fRetval;
769 } // end of namespace svgio
771 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */