Updated core
[LibreOffice.git] / svgio / source / svgreader / svgcharacternode.cxx
blob7b8e4cb252e824df77ea1b58a4696e3759e18eb7
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 <svgio/svgreader/svgcharacternode.hxx>
21 #include <svgio/svgreader/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(const OUString& /*rTokenName*/, 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
154 namespace svgio
156 namespace svgreader
158 class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
160 private:
161 SvgTextPosition& mrSvgTextPosition;
163 protected:
164 /// allow user callback to allow changes to the new TextTransformation. Default
165 /// does nothing.
166 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) SAL_OVERRIDE;
168 public:
169 localTextBreakupHelper(
170 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
171 SvgTextPosition& rSvgTextPosition)
172 : drawinglayer::primitive2d::TextBreakupHelper(rSource),
173 mrSvgTextPosition(rSvgTextPosition)
178 bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
180 const double fRotation(mrSvgTextPosition.consumeRotation());
182 if(0.0 != fRotation)
184 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
186 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
187 rNewTransform.rotate(fRotation);
188 rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
191 return true;
194 } // end of namespace svgreader
195 } // end of namespace svgio
199 namespace svgio
201 namespace svgreader
203 SvgCharacterNode::SvgCharacterNode(
204 SvgDocument& rDocument,
205 SvgNode* pParent,
206 const OUString& rText)
207 : SvgNode(SVGTokenCharacter, rDocument, pParent),
208 maText(rText)
212 SvgCharacterNode::~SvgCharacterNode()
216 const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
218 // no own style, use parent's
219 if(getParent())
221 return getParent()->getSvgStyleAttributes();
223 else
225 return 0;
229 drawinglayer::primitive2d::TextSimplePortionPrimitive2D* SvgCharacterNode::createSimpleTextPrimitive(
230 SvgTextPosition& rSvgTextPosition,
231 const SvgStyleAttributes& rSvgStyleAttributes) const
233 // prepare retval, index and length
234 drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pRetval = 0;
235 sal_uInt32 nIndex(0);
236 sal_uInt32 nLength(getText().getLength());
238 if(nLength)
240 // prepare FontAttribute
241 OUString aFontFamily = rSvgStyleAttributes.getFontFamily().empty() ?
242 OUString("Times New Roman") :
243 rSvgStyleAttributes.getFontFamily()[0];
245 // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
246 // of a SVG export with font embedding. Remove this to make font matching work. This
247 // is pretty safe since there should be no font family names ending on ' embedded'.
248 // Remove again when FontEmbedding is implemented in SVG import
249 if(aFontFamily.endsWith(" embedded"))
251 aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9);
254 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
255 bool bSymbol(false);
256 bool bVertical(false);
257 bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle());
258 bool bMonospaced(false);
259 bool bOutline(false);
260 bool bRTL(false);
261 bool bBiDiStrong(false);
263 const drawinglayer::attribute::FontAttribute aFontAttribute(
264 aFontFamily,
265 OUString(),
266 nFontWeight,
267 bSymbol,
268 bVertical,
269 bItalic,
270 bMonospaced,
271 bOutline,
272 bRTL,
273 bBiDiStrong);
275 // prepare FontSize
276 double fFontWidth(rSvgStyleAttributes.getFontSize().solve(*this, length));
277 double fFontHeight(fFontWidth);
279 // prepare locale
280 ::com::sun::star::lang::Locale aLocale;
282 // prepare TextLayouterDevice
283 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
284 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
286 // prepare TextArray
287 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
289 if(!aTextArray.empty() && aTextArray.size() < nLength)
291 const sal_uInt32 nArray(aTextArray.size());
293 if(nArray < nLength)
295 double fStartX(0.0);
297 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
299 fStartX = rSvgTextPosition.getParent()->getPosition().getX();
301 else
303 fStartX = aTextArray[nArray - 1];
306 ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
307 aTextArray.reserve(nLength);
309 for(sal_uInt32 a(0); a < aExtendArray.size(); a++)
311 aTextArray.push_back(aExtendArray[a] + fStartX);
316 // get current TextPosition and TextWidth in units
317 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
318 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
320 // check for user-given TextLength
321 if(0.0 != rSvgTextPosition.getTextLength()
322 && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
324 const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
326 if(rSvgTextPosition.getLengthAdjust())
328 // spacing, need to create and expand TextArray
329 if(aTextArray.empty())
331 aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
334 for(sal_uInt32 a(0); a < aTextArray.size(); a++)
336 aTextArray[a] *= fFactor;
339 else
341 // spacing and glyphs, just apply to FontWidth
342 fFontWidth *= fFactor;
345 fTextWidth = rSvgTextPosition.getTextLength();
348 // get TextAlign
349 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
351 // map TextAnchor to TextAlign, there seems not to be a difference
352 if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor())
354 switch(rSvgStyleAttributes.getTextAnchor())
356 case TextAnchor_start:
358 aTextAlign = TextAlign_left;
359 break;
361 case TextAnchor_middle:
363 aTextAlign = TextAlign_center;
364 break;
366 case TextAnchor_end:
368 aTextAlign = TextAlign_right;
369 break;
371 default:
373 break;
378 // apply TextAlign
379 switch(aTextAlign)
381 case TextAlign_right:
383 aPosition.setX(aPosition.getX() - fTextWidth);
384 break;
386 case TextAlign_center:
388 aPosition.setX(aPosition.getX() - (fTextWidth * 0.5));
389 break;
391 case TextAlign_notset:
392 case TextAlign_left:
393 case TextAlign_justify:
395 // TextAlign_notset, TextAlign_left: nothing to do
396 // TextAlign_justify is not clear currently; handle as TextAlign_left
397 break;
401 // get BaselineShift
402 const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
404 // apply BaselineShift
405 switch(aBaselineShift)
407 case BaselineShift_Sub:
409 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
410 break;
412 case BaselineShift_Super:
414 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
415 break;
417 case BaselineShift_Percentage:
418 case BaselineShift_Length:
420 const SvgNumber aNumber(rSvgStyleAttributes.getBaselineShiftNumber());
421 const double mfBaselineShift(aNumber.solve(*this, length));
423 aPosition.setY(aPosition.getY() + mfBaselineShift);
424 break;
426 default: // BaselineShift_Baseline
428 // nothing to do
429 break;
433 // get fill color
434 const basegfx::BColor aFill(rSvgStyleAttributes.getFill()
435 ? *rSvgStyleAttributes.getFill()
436 : basegfx::BColor(0.0, 0.0, 0.0));
438 // prepare TextTransformation
439 basegfx::B2DHomMatrix aTextTransform;
441 aTextTransform.scale(fFontWidth, fFontHeight);
442 aTextTransform.translate(aPosition.getX(), aPosition.getY());
444 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
445 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
447 if(TextDecoration_underline == aDeco
448 || TextDecoration_overline == aDeco
449 || TextDecoration_line_through == aDeco)
451 // get the fill for decroation as described by SVG. We cannot
452 // have different stroke colors/definitions for those, though
453 const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
454 const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill);
456 // create decorated text primitive
457 pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
458 aTextTransform,
459 getText(),
460 nIndex,
461 nLength,
462 aTextArray,
463 aFontAttribute,
464 aLocale,
465 aFill,
466 COL_TRANSPARENT,
468 // extra props for decorated
469 aDecoColor,
470 aDecoColor,
471 TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
472 TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
473 false,
474 TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE,
475 false,
476 drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE,
477 true,
478 false,
479 drawinglayer::primitive2d::TEXT_RELIEF_NONE,
480 false);
482 else
484 // create text primitive
485 pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
486 aTextTransform,
487 getText(),
488 nIndex,
489 nLength,
490 aTextArray,
491 aFontAttribute,
492 aLocale,
493 aFill);
496 // advance current TextPosition
497 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
500 return pRetval;
503 void SvgCharacterNode::decomposeTextWithStyle(
504 drawinglayer::primitive2d::Primitive2DSequence& rTarget,
505 SvgTextPosition& rSvgTextPosition,
506 const SvgStyleAttributes& rSvgStyleAttributes) const
508 const drawinglayer::primitive2d::Primitive2DReference xRef(
509 createSimpleTextPrimitive(
510 rSvgTextPosition,
511 rSvgStyleAttributes));
513 if(xRef.is())
515 if(!rSvgTextPosition.isRotated())
517 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef);
519 else
521 // need to apply rotations to each character as given
522 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate =
523 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get());
525 if(pCandidate)
527 const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
528 const drawinglayer::primitive2d::Primitive2DSequence aResult(
529 alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character));
531 if(aResult.hasElements())
533 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult);
536 // also consume for the implied single space
537 rSvgTextPosition.consumeRotation();
539 else
541 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
547 void SvgCharacterNode::whiteSpaceHandling()
549 if(XmlSpace_default == getXmlSpace())
551 maText = whiteSpaceHandlingDefault(maText);
553 else
555 maText = whiteSpaceHandlingPreserve(maText);
559 void SvgCharacterNode::addGap()
561 maText += OUString(' ');
564 void SvgCharacterNode::concatenate(const OUString& rText)
566 maText += rText;
569 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const
571 if(!getText().isEmpty())
573 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
575 if(pSvgStyleAttributes)
577 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
582 } // end of namespace svgreader
583 } // end of namespace svgio
587 namespace svgio
589 namespace svgreader
591 SvgTextPosition::SvgTextPosition(
592 SvgTextPosition* pParent,
593 const InfoProvider& rInfoProvider,
594 const SvgTextPositions& rSvgTextPositions)
595 : mpParent(pParent),
596 maX(), // computed below
597 maY(), // computed below
598 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)),
599 mfTextLength(0.0),
600 maPosition(), // computed below
601 mnRotationIndex(0),
602 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()),
603 mbAbsoluteX(false),
604 mbAbsoluteY(false)
606 // get TextLength if provided
607 if(rSvgTextPositions.getTextLength().isSet())
609 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length);
612 // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
613 // but it seems to be degrees. Convert here to radians
614 if(!maRotate.empty())
616 const double fFactor(F_PI / 180.0);
618 for(sal_uInt32 a(0); a < maRotate.size(); a++)
620 maRotate[a] *= fFactor;
624 // get text positions X
625 const sal_uInt32 nSizeX(rSvgTextPositions.getX().size());
627 if(nSizeX)
629 // we have absolute positions, get first one as current text position X
630 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate));
631 mbAbsoluteX = true;
633 if(nSizeX > 1)
635 // fill deltas to maX
636 maX.reserve(nSizeX);
638 for(sal_uInt32 a(1); a < nSizeX; a++)
640 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX());
644 else
646 // no absolute position, get from parent
647 if(pParent)
649 maPosition.setX(pParent->getPosition().getX());
652 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size());
654 if(nSizeDx)
656 // relative positions given, translate position derived from parent
657 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate));
659 if(nSizeDx > 1)
661 // fill deltas to maX
662 maX.reserve(nSizeDx);
664 for(sal_uInt32 a(1); a < nSizeDx; a++)
666 maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate));
672 // get text positions Y
673 const sal_uInt32 nSizeY(rSvgTextPositions.getY().size());
675 if(nSizeY)
677 // we have absolute positions, get first one as current text position Y
678 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate));
679 mbAbsoluteX = true;
681 if(nSizeY > 1)
683 // fill deltas to maY
684 maY.reserve(nSizeY);
686 for(sal_uInt32 a(1); a < nSizeY; a++)
688 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY());
692 else
694 // no absolute position, get from parent
695 if(pParent)
697 maPosition.setY(pParent->getPosition().getY());
700 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size());
702 if(nSizeDy)
704 // relative positions given, translate position derived from parent
705 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate));
707 if(nSizeDy > 1)
709 // fill deltas to maY
710 maY.reserve(nSizeDy);
712 for(sal_uInt32 a(1); a < nSizeDy; a++)
714 maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate));
721 bool SvgTextPosition::isRotated() const
723 if(maRotate.empty())
725 if(getParent())
727 return getParent()->isRotated();
729 else
731 return false;
734 else
736 return true;
740 double SvgTextPosition::consumeRotation()
742 double fRetval(0.0);
744 if(maRotate.empty())
746 if(getParent())
748 fRetval = mpParent->consumeRotation();
750 else
752 fRetval = 0.0;
755 else
757 const sal_uInt32 nSize(maRotate.size());
759 if(mnRotationIndex < nSize)
761 fRetval = maRotate[mnRotationIndex++];
763 else
765 fRetval = maRotate[nSize - 1];
769 return fRetval;
772 } // end of namespace svgreader
773 } // end of namespace svgio
775 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */