nss: upgrade to release 3.73
[LibreOffice.git] / svgio / source / svgreader / svgcharacternode.cxx
blob419a887cc55f02f14b16ce98cc5adcd9b1d74dc1
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 SVGTokenX:
48 if(!aContent.isEmpty())
50 SvgNumberVector aVector;
52 if(readSvgNumberVector(aContent, aVector))
54 setX(aVector);
57 break;
59 case SVGTokenY:
61 if(!aContent.isEmpty())
63 SvgNumberVector aVector;
65 if(readSvgNumberVector(aContent, aVector))
67 setY(aVector);
70 break;
72 case SVGTokenDx:
74 if(!aContent.isEmpty())
76 SvgNumberVector aVector;
78 if(readSvgNumberVector(aContent, aVector))
80 setDx(aVector);
83 break;
85 case SVGTokenDy:
87 if(!aContent.isEmpty())
89 SvgNumberVector aVector;
91 if(readSvgNumberVector(aContent, aVector))
93 setDy(aVector);
96 break;
98 case SVGTokenRotate:
100 if(!aContent.isEmpty())
102 SvgNumberVector aVector;
104 if(readSvgNumberVector(aContent, aVector))
106 setRotate(aVector);
109 break;
111 case SVGTokenTextLength:
113 SvgNumber aNum;
115 if(readSingleNumber(aContent, aNum))
117 if(aNum.isPositive())
119 setTextLength(aNum);
122 break;
124 case SVGTokenLengthAdjust:
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 } // end of namespace svgio::svgreader
149 namespace svgio::svgreader
151 namespace {
153 class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
155 private:
156 SvgTextPosition& mrSvgTextPosition;
158 protected:
159 /// allow user callback to allow changes to the new TextTransformation. Default
160 /// does nothing.
161 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
163 public:
164 localTextBreakupHelper(
165 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
166 SvgTextPosition& rSvgTextPosition)
167 : drawinglayer::primitive2d::TextBreakupHelper(rSource),
168 mrSvgTextPosition(rSvgTextPosition)
175 bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
177 const double fRotation(mrSvgTextPosition.consumeRotation());
179 if(0.0 != fRotation)
181 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
183 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
184 rNewTransform.rotate(fRotation);
185 rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
188 return true;
191 } // end of namespace svgio::svgreader
194 namespace svgio::svgreader
196 SvgCharacterNode::SvgCharacterNode(
197 SvgDocument& rDocument,
198 SvgNode* pParent,
199 const OUString& rText)
200 : SvgNode(SVGTokenCharacter, rDocument, pParent),
201 maText(rText)
205 SvgCharacterNode::~SvgCharacterNode()
209 const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
211 // no own style, use parent's
212 if(getParent())
214 return getParent()->getSvgStyleAttributes();
216 else
218 return nullptr;
222 drawinglayer::primitive2d::TextSimplePortionPrimitive2D* SvgCharacterNode::createSimpleTextPrimitive(
223 SvgTextPosition& rSvgTextPosition,
224 const SvgStyleAttributes& rSvgStyleAttributes) const
226 // prepare retval, index and length
227 drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pRetval = nullptr;
228 sal_uInt32 nLength(getText().getLength());
230 if(nLength)
232 sal_uInt32 nIndex(0);
233 // prepare FontAttribute
234 const SvgStringVector& rFontFamilyVector = rSvgStyleAttributes.getFontFamily();
235 OUString aFontFamily = rFontFamilyVector.empty() ?
236 OUString("Times New Roman") :
237 rFontFamilyVector[0];
239 // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
240 // of a SVG export with font embedding. Remove this to make font matching work. This
241 // is pretty safe since there should be no font family names ending on ' embedded'.
242 // Remove again when FontEmbedding is implemented in SVG import
243 if(aFontFamily.endsWith(" embedded"))
245 aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9);
248 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
249 bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle());
251 const drawinglayer::attribute::FontAttribute aFontAttribute(
252 aFontFamily,
253 OUString(),
254 nFontWeight,
255 false/*bSymbol*/,
256 false/*bVertical*/,
257 bItalic,
258 false/*bMonospaced*/,
259 false/*bOutline*/,
260 false/*bRTL*/,
261 false/*bBiDiStrong*/);
263 // prepare FontSizeNumber
264 double fFontWidth(rSvgStyleAttributes.getFontSizeNumber().solve(*this));
265 double fFontHeight(fFontWidth);
267 // prepare locale
268 css::lang::Locale aLocale;
270 // prepare TextLayouterDevice
271 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
272 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
274 // prepare TextArray
275 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
277 if(!aTextArray.empty() && aTextArray.size() < nLength)
279 const sal_uInt32 nArray(aTextArray.size());
281 if(nArray < nLength)
283 double fStartX(0.0);
285 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
287 fStartX = rSvgTextPosition.getParent()->getPosition().getX();
289 else
291 fStartX = aTextArray[nArray - 1];
294 ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
295 aTextArray.reserve(nLength);
297 for(size_t a(0); a < aExtendArray.size(); a++)
299 aTextArray.push_back(aExtendArray[a] + fStartX);
304 // get current TextPosition and TextWidth in units
305 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
306 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
308 // check for user-given TextLength
309 if(0.0 != rSvgTextPosition.getTextLength()
310 && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
312 const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
314 if(rSvgTextPosition.getLengthAdjust())
316 // spacing, need to create and expand TextArray
317 if(aTextArray.empty())
319 aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
322 for(size_t a(0); a < aTextArray.size(); a++)
324 aTextArray[a] *= fFactor;
327 else
329 // spacing and glyphs, just apply to FontWidth
330 fFontWidth *= fFactor;
333 fTextWidth = rSvgTextPosition.getTextLength();
336 // get TextAlign
337 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
339 // map TextAnchor to TextAlign, there seems not to be a difference
340 if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor())
342 switch(rSvgStyleAttributes.getTextAnchor())
344 case TextAnchor_start:
346 aTextAlign = TextAlign_left;
347 break;
349 case TextAnchor_middle:
351 aTextAlign = TextAlign_center;
352 break;
354 case TextAnchor_end:
356 aTextAlign = TextAlign_right;
357 break;
359 default:
361 break;
366 // apply TextAlign
367 switch(aTextAlign)
369 case TextAlign_right:
371 aPosition.setX(aPosition.getX() - fTextWidth);
372 break;
374 case TextAlign_center:
376 aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
377 break;
379 case TextAlign_notset:
380 case TextAlign_left:
381 case TextAlign_justify:
383 // TextAlign_notset, TextAlign_left: nothing to do
384 // TextAlign_justify is not clear currently; handle as TextAlign_left
385 break;
389 // get BaselineShift
390 const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
392 // apply BaselineShift
393 switch(aBaselineShift)
395 case BaselineShift_Sub:
397 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
398 break;
400 case BaselineShift_Super:
402 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
403 break;
405 case BaselineShift_Percentage:
406 case BaselineShift_Length:
408 const SvgNumber aNumber(rSvgStyleAttributes.getBaselineShiftNumber());
409 const double mfBaselineShift(aNumber.solve(*this));
411 aPosition.setY(aPosition.getY() + mfBaselineShift);
412 break;
414 default: // BaselineShift_Baseline
416 // nothing to do
417 break;
421 // get fill color
422 const basegfx::BColor aFill(rSvgStyleAttributes.getFill()
423 ? *rSvgStyleAttributes.getFill()
424 : basegfx::BColor(0.0, 0.0, 0.0));
426 // prepare TextTransformation
427 basegfx::B2DHomMatrix aTextTransform;
429 aTextTransform.scale(fFontWidth, fFontHeight);
430 aTextTransform.translate(aPosition.getX(), aPosition.getY());
432 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
433 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
435 if(TextDecoration_underline == aDeco
436 || TextDecoration_overline == aDeco
437 || TextDecoration_line_through == aDeco)
439 // get the fill for decoration as described by SVG. We cannot
440 // have different stroke colors/definitions for those, though
441 const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
442 const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill);
444 // create decorated text primitive
445 pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
446 aTextTransform,
447 getText(),
448 nIndex,
449 nLength,
450 aTextArray,
451 aFontAttribute,
452 aLocale,
453 aFill,
454 COL_TRANSPARENT,
456 // extra props for decorated
457 aDecoColor,
458 aDecoColor,
459 TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
460 TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
461 false,
462 TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
463 false,
464 drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE,
465 true,
466 false,
467 drawinglayer::primitive2d::TEXT_RELIEF_NONE,
468 false);
470 else
472 // create text primitive
473 pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
474 aTextTransform,
475 getText(),
476 nIndex,
477 nLength,
478 aTextArray,
479 aFontAttribute,
480 aLocale,
481 aFill);
484 // advance current TextPosition
485 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
488 return pRetval;
491 void SvgCharacterNode::decomposeTextWithStyle(
492 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
493 SvgTextPosition& rSvgTextPosition,
494 const SvgStyleAttributes& rSvgStyleAttributes) const
496 const drawinglayer::primitive2d::Primitive2DReference xRef(
497 createSimpleTextPrimitive(
498 rSvgTextPosition,
499 rSvgStyleAttributes));
501 if(!(xRef.is() && (Visibility_visible == rSvgStyleAttributes.getVisibility())))
502 return;
504 if(!rSvgTextPosition.isRotated())
506 rTarget.push_back(xRef);
508 else
510 // need to apply rotations to each character as given
511 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate =
512 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get());
514 if(pCandidate)
516 const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
517 const drawinglayer::primitive2d::Primitive2DContainer& aResult(
518 alocalTextBreakupHelper.getResult());
520 if(!aResult.empty())
522 rTarget.append(aResult);
525 // also consume for the implied single space
526 rSvgTextPosition.consumeRotation();
528 else
530 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
535 void SvgCharacterNode::whiteSpaceHandling()
537 if(XmlSpace_default == getXmlSpace())
539 maText = whiteSpaceHandlingDefault(maText);
541 else
543 maText = whiteSpaceHandlingPreserve(maText);
547 void SvgCharacterNode::addGap()
549 maText += " ";
552 void SvgCharacterNode::concatenate(const OUString& rText)
554 maText += rText;
557 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const
559 if(!getText().isEmpty())
561 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
563 if(pSvgStyleAttributes)
565 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
570 } // end of namespace svgio::svgreader
573 namespace svgio::svgreader
575 SvgTextPosition::SvgTextPosition(
576 SvgTextPosition* pParent,
577 const InfoProvider& rInfoProvider,
578 const SvgTextPositions& rSvgTextPositions)
579 : mpParent(pParent),
580 maX(), // computed below
581 maY(), // computed below
582 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider)),
583 mfTextLength(0.0),
584 maPosition(), // computed below
585 mnRotationIndex(0),
586 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
587 mbAbsoluteX(false)
589 // get TextLength if provided
590 if(rSvgTextPositions.getTextLength().isSet())
592 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider);
595 // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
596 // but it seems to be degrees. Convert here to radians
597 if(!maRotate.empty())
599 for (double& f : maRotate)
601 f = basegfx::deg2rad(f);
605 // get text positions X
606 const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
608 if(nSizeX)
610 // we have absolute positions, get first one as current text position X
611 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
612 mbAbsoluteX = true;
614 if(nSizeX > 1)
616 // fill deltas to maX
617 maX.reserve(nSizeX);
619 for(sal_uInt32 a(1); a < nSizeX; a++)
621 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
625 else
627 // no absolute position, get from parent
628 if(pParent)
630 maPosition.setX(pParent->getPosition().getX());
633 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
635 if(nSizeDx)
637 // relative positions given, translate position derived from parent
638 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
640 if(nSizeDx > 1)
642 // fill deltas to maX
643 maX.reserve(nSizeDx);
645 for(sal_uInt32 a(1); a < nSizeDx; a++)
647 maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate));
653 // get text positions Y
654 const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
656 if(nSizeY)
658 // we have absolute positions, get first one as current text position Y
659 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
660 mbAbsoluteX = true;
662 if(nSizeY > 1)
664 // fill deltas to maY
665 maY.reserve(nSizeY);
667 for(sal_uInt32 a(1); a < nSizeY; a++)
669 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
673 else
675 // no absolute position, get from parent
676 if(pParent)
678 maPosition.setY(pParent->getPosition().getY());
681 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
683 if(nSizeDy)
685 // relative positions given, translate position derived from parent
686 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
688 if(nSizeDy > 1)
690 // fill deltas to maY
691 maY.reserve(nSizeDy);
693 for(sal_uInt32 a(1); a < nSizeDy; a++)
695 maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate));
702 bool SvgTextPosition::isRotated() const
704 if(maRotate.empty())
706 if(getParent())
708 return getParent()->isRotated();
710 else
712 return false;
715 else
717 return true;
721 double SvgTextPosition::consumeRotation()
723 double fRetval(0.0);
725 if(maRotate.empty())
727 if(getParent())
729 fRetval = mpParent->consumeRotation();
731 else
733 fRetval = 0.0;
736 else
738 const sal_uInt32 nSize(maRotate.size());
740 if(mnRotationIndex < nSize)
742 fRetval = maRotate[mnRotationIndex++];
744 else
746 fRetval = maRotate[nSize - 1];
750 return fRetval;
753 } // end of namespace svgio
755 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */