svgio/Library_svgio.mk | 1 svgio/inc/svgcharacternode.hxx | 98 ------- svgio/inc/svgtextnode.hxx | 11 svgio/inc/svgtextposition.hxx | 72 +++++ svgio/inc/svgtspannode.hxx | 51 +++- svgio/qa/cppunit/SvgImportTest.cxx | 140 ++++++++++- svgio/qa/cppunit/data/tdf151103.svg | 18 + svgio/qa/cppunit/data/tdf156616.svg | 29 ++ svgio/source/svgreader/svgcharacternode.cxx | 322 +++----------------------- svgio/source/svgreader/svgdocumenthandler.cxx | 92 ++----- svgio/source/svgreader/svgtextnode.cxx | 25 -- svgio/source/svgreader/svgtextposition.cxx | 200 ++++++++++++++++ svgio/source/svgreader/svgtspannode.cxx | 85 ++++++ 13 files changed, 668 insertions(+), 476 deletions(-)
New commits: commit 87e0ac2b92f7a7aac16bf66d2379688238496a75 Author: Xisco Fauli <xiscofa...@libreoffice.org> AuthorDate: Sat Aug 12 02:28:02 2023 +0200 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Aug 15 19:01:22 2023 +0200 tdf#156616: check if character's parent has x or y if so, only concatenate the characters that are in the same line so the alignment will be calculated based on the line's width Change-Id: I704370c0a470f8b4cff97c51ad9863158118ee8a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155636 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155657 diff --git a/svgio/inc/svgcharacternode.hxx b/svgio/inc/svgcharacternode.hxx index 391c4029e46c..059aa9ece1fd 100644 --- a/svgio/inc/svgcharacternode.hxx +++ b/svgio/inc/svgcharacternode.hxx @@ -40,7 +40,7 @@ namespace svgio::svgreader // keep a copy of string data before space handling OUString maTextBeforeSpaceHandling; - SvgTextNode* mpTextParent; + SvgTspanNode* mpParentLine; /// local helpers rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> createSimpleTextPrimitive( @@ -68,7 +68,7 @@ namespace svgio::svgreader /// Text content const OUString& getText() const { return maText; } - void setTextParent(SvgTextNode* pTextParent) { mpTextParent = pTextParent; } + void setParentLine(SvgTspanNode* pParentLine) { mpParentLine = pParentLine; } }; } // end of namespace svgio::svgreader diff --git a/svgio/inc/svgtextnode.hxx b/svgio/inc/svgtextnode.hxx index 2d5f98ec18fc..787687977e11 100644 --- a/svgio/inc/svgtextnode.hxx +++ b/svgio/inc/svgtextnode.hxx @@ -33,10 +33,6 @@ namespace svgio::svgreader std::optional<basegfx::B2DHomMatrix> mpaTransform; - // The text line composed by the different SvgCharacterNode children - // it will be used to calculate their alignment - OUString maTextLine; - /// local helpers void DecomposeChild( const SvgNode& rCandidate, @@ -59,9 +55,6 @@ namespace svgio::svgreader /// transform content, set if found in current context const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; } void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; } - - void concatenateTextLine(std::u16string_view rText) {maTextLine += rText;} - const OUString& getTextLine() const { return maTextLine; } }; } // end of namespace svgio::svgreader diff --git a/svgio/inc/svgtspannode.hxx b/svgio/inc/svgtspannode.hxx index d5d86c5a7c1a..92ed8319c628 100644 --- a/svgio/inc/svgtspannode.hxx +++ b/svgio/inc/svgtspannode.hxx @@ -39,6 +39,10 @@ namespace svgio::svgreader bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs + // The text line composed by the different SvgCharacterNode children + // it will be used to calculate their alignment + OUString maTextLine; + public: SvgTspanNode( SVGToken aType, @@ -78,6 +82,9 @@ namespace svgio::svgreader /// LengthAdjust content bool getLengthAdjust() const { return mbLengthAdjust; } void setLengthAdjust(bool bNew) { mbLengthAdjust = bNew; } + + void concatenateTextLine(std::u16string_view rText) {maTextLine += rText;} + const OUString& getTextLine() const { return maTextLine; } }; } // end of namespace svgio::svgreader diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx index c52e20594b10..42cb98c4b6be 100644 --- a/svgio/qa/cppunit/SvgImportTest.cxx +++ b/svgio/qa/cppunit/SvgImportTest.cxx @@ -740,7 +740,45 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf85770) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", " End"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "height", "11"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "familyname", "Times New Roman"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf156616) +{ + Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156616.svg"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + + CPPUNIT_ASSERT (pDocument); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", "First"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "x", "114"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "y", "103"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", " line"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "x", "142"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "y", "103"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", "Second line"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", "114"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "y", "122"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "text", "First"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "x", "86"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "y", "153"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "text", " line"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "x", "114"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "y", "153"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "text", "Second line"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "x", "77"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "y", "172"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "text", "First"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "x", "59"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "y", "203"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "text", " line"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "x", "87"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "y", "203"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "text", "Second line"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "x", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "y", "222"); } CPPUNIT_TEST_FIXTURE(Test, testTdf79163) diff --git a/svgio/qa/cppunit/data/tdf156616.svg b/svgio/qa/cppunit/data/tdf156616.svg new file mode 100644 index 000000000000..6b3bb3c6c6a5 --- /dev/null +++ b/svgio/qa/cppunit/data/tdf156616.svg @@ -0,0 +1,29 @@ +<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg"> + <text + style="text-anchor:start" + x="114" + y="103"><tspan + x="114" + y="103"><tspan>First </tspan>line </tspan><tspan + x="114" + y="122">Second line</tspan> + </text> + <text + style="text-anchor:middle" + x="114" + y="153"><tspan + x="114" + y="153"><tspan>First </tspan>line </tspan><tspan + x="114" + y="172">Second line</tspan> + </text> + <text + style="text-anchor:end" + x="114" + y="203"><tspan + x="114" + y="203"><tspan>First </tspan>line </tspan><tspan + x="114" + y="222">Second line</tspan> + </text> +</svg> diff --git a/svgio/source/svgreader/svgcharacternode.cxx b/svgio/source/svgreader/svgcharacternode.cxx index 2b88944aa8d0..9ba70ffb3ef5 100644 --- a/svgio/source/svgreader/svgcharacternode.cxx +++ b/svgio/source/svgreader/svgcharacternode.cxx @@ -79,7 +79,7 @@ namespace svgio::svgreader OUString aText) : SvgNode(SVGToken::Character, rDocument, pParent), maText(std::move(aText)), - mpTextParent(nullptr) + mpParentLine(nullptr) { } @@ -251,7 +251,7 @@ namespace svgio::svgreader } // Use the whole text line to calculate the align position - double fWholeTextLineWidth(aTextLayouterDevice.getTextWidth(mpTextParent->getTextLine(), 0, mpTextParent->getTextLine().getLength())); + double fWholeTextLineWidth(aTextLayouterDevice.getTextWidth(mpParentLine->getTextLine(), 0, mpParentLine->getTextLine().getLength())); // apply TextAlign switch(aTextAlign) { @@ -482,6 +482,10 @@ namespace svgio::svgreader if(pPreviousCharacterNode->maTextBeforeSpaceHandling[nLastLength - 1] != ' ' && maTextBeforeSpaceHandling[0] != ' ') bAddGap = false; + // Do not add a gap if this node and last node are in different lines + if(pPreviousCharacterNode->mpParentLine != mpParentLine) + bAddGap = false; + // With this option a baseline shift between two char parts ('words') // will not add a space 'gap' to the end of the (non-last) word. This // seems to be the standard behaviour, see last bugdoc attached #122524# diff --git a/svgio/source/svgreader/svgdocumenthandler.cxx b/svgio/source/svgreader/svgdocumenthandler.cxx index 45213997144c..d552db8a0d19 100644 --- a/svgio/source/svgreader/svgdocumenthandler.cxx +++ b/svgio/source/svgreader/svgdocumenthandler.cxx @@ -62,7 +62,7 @@ namespace svgio::svgreader namespace { - svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgTextNode* pText, svgio::svgreader::SvgCharacterNode* pLast) + svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgTspanNode* pParentLine, svgio::svgreader::SvgCharacterNode* pLast) { if(pNode) { @@ -82,19 +82,31 @@ namespace // clean whitespace in text span svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate); + pCharNode->setParentLine(pParentLine); + pCharNode->whiteSpaceHandling(); pLast = pCharNode->addGap(pLast); - pCharNode->setTextParent(pText); - pText->concatenateTextLine(pCharNode->getText()); + pParentLine->concatenateTextLine(pCharNode->getText()); break; } case SVGToken::Tspan: + { + svgio::svgreader::SvgTspanNode* pTspanNode = static_cast< svgio::svgreader::SvgTspanNode* >(pCandidate); + + // If x or y exist it means it's a new line of text + if(!pTspanNode->getX().empty() || !pTspanNode->getY().empty()) + pParentLine = pTspanNode; + + // recursively clean whitespaces in subhierarchy + pLast = whiteSpaceHandling(pCandidate, pParentLine, pLast); + break; + } case SVGToken::TextPath: case SVGToken::Tref: { // recursively clean whitespaces in subhierarchy - pLast = whiteSpaceHandling(pCandidate, pText, pLast); + pLast = whiteSpaceHandling(pCandidate, pParentLine, pLast); break; } default: @@ -552,7 +564,7 @@ namespace if(pTextNode) { // cleanup read strings - whiteSpaceHandling(pTextNode, static_cast< SvgTextNode*>(pTextNode), nullptr); + whiteSpaceHandling(pTextNode, static_cast< SvgTspanNode*>(pTextNode), nullptr); } } commit 00871b79164d7dfb50820cdcae05b9050639a014 Author: Xisco Fauli <xiscofa...@libreoffice.org> AuthorDate: Thu Aug 3 17:47:36 2023 +0200 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Aug 15 19:01:16 2023 +0200 tdf#151103: Use the whole text line to calculate the align position Change-Id: I7ecd41c422afbf028101924972c47a510834ba5a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155314 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> This commit also contains the following commits: svgio: remove recurrent checks Change-Id: I26c37e6b58e7c54e2bdc2c77543896daceb638d3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155520 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> svgio: get rid of SvgTextPositions and make SvgText inherit from SvgTspan Change-Id: Ief25e52ba2a493936f82f1674f73168ed5647278 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155521 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> svgio: avoid dynamic_cast Change-Id: I9a2e2c4341476a59ffb001c42e7812cb8249c856 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155548 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> svgio: handle addGap internally inside SvgCharacterNode Also add the gap at the beginning of the current node, not at the end of the previous one Change-Id: I6583059b4a7418010ac2af459e00fb0d02d39605 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155552 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> svgio: move SvgTextPosition to its own file In order to avoid a circular dependency in a follow-up commit Change-Id: Ib7b16e73282dfa6f3ca87aab1044cb92df72b6bf Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155555 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> related: tdf#151103: simplify code Keep the text line in the SvgTextNode and not in each SvgCharacterNode Change-Id: Ia33e46cc974a39a915e7b933337b4c529e6eeca5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155558 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> cid#1539807 Uninitialized pointer field Change-Id: I500c5d9f15c6a57622a28ea7cbf3b5f90761b5c0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155582 Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com> Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155656 Tested-by: Jenkins diff --git a/svgio/Library_svgio.mk b/svgio/Library_svgio.mk index c25077ed94d3..edd83ed57251 100644 --- a/svgio/Library_svgio.mk +++ b/svgio/Library_svgio.mk @@ -85,6 +85,7 @@ $(eval $(call gb_Library_add_exception_objects,svgio,\ svgio/source/svgreader/svgsvgnode \ svgio/source/svgreader/svgsymbolnode \ svgio/source/svgreader/svgtextnode \ + svgio/source/svgreader/svgtextposition \ svgio/source/svgreader/svgtitledescnode \ svgio/source/svgreader/svgtoken \ svgio/source/svgreader/svgtrefnode \ diff --git a/svgio/inc/svgcharacternode.hxx b/svgio/inc/svgcharacternode.hxx index 8861055f8e65..391c4029e46c 100644 --- a/svgio/inc/svgcharacternode.hxx +++ b/svgio/inc/svgcharacternode.hxx @@ -24,100 +24,13 @@ #include <string_view> -#include "svgnode.hxx" +#include "svgtextnode.hxx" +#include "svgtextposition.hxx" namespace drawinglayer::primitive2d { class TextSimplePortionPrimitive2D; } namespace svgio::svgreader { - class SvgTextPositions - { - private: - SvgNumberVector maX; - SvgNumberVector maY; - SvgNumberVector maDx; - SvgNumberVector maDy; - SvgNumberVector maRotate; - SvgNumber maTextLength; - - bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs - - public: - SvgTextPositions(); - - void parseTextPositionAttributes(SVGToken aSVGToken, std::u16string_view aContent); - - /// X content - const SvgNumberVector& getX() const { return maX; } - void setX(SvgNumberVector&& aX) { maX = std::move(aX); } - - /// Y content - const SvgNumberVector& getY() const { return maY; } - void setY(SvgNumberVector&& aY) { maY = std::move(aY); } - - /// Dx content - const SvgNumberVector& getDx() const { return maDx; } - void setDx(SvgNumberVector&& aDx) { maDx = std::move(aDx); } - - /// Dy content - const SvgNumberVector& getDy() const { return maDy; } - void setDy(SvgNumberVector&& aDy) { maDy = std::move(aDy); } - - /// Rotate content - const SvgNumberVector& getRotate() const { return maRotate; } - void setRotate(SvgNumberVector&& aRotate) { maRotate = std::move(aRotate); } - - /// TextLength content - const SvgNumber& getTextLength() const { return maTextLength; } - void setTextLength(const SvgNumber& rTextLength) { maTextLength = rTextLength; } - - /// LengthAdjust content - bool getLengthAdjust() const { return mbLengthAdjust; } - void setLengthAdjust(bool bNew) { mbLengthAdjust = bNew; } - }; - - class SvgTextPosition - { - private: - SvgTextPosition* mpParent; - ::std::vector< double > maX; - ::std::vector< double > maY; - ::std::vector< double > maDx; - ::std::vector< double > maRotate; - double mfTextLength; - - // absolute, current, advancing position - basegfx::B2DPoint maPosition; - - // advancing rotation index - sal_uInt32 mnRotationIndex; - - bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs - bool mbAbsoluteX : 1; - - public: - SvgTextPosition( - SvgTextPosition* pParent, - const InfoProvider& rInfoProvider, - const SvgTextPositions& rSvgTextPositions); - - // data read access - const SvgTextPosition* getParent() const { return mpParent; } - const ::std::vector< double >& getX() const { return maX; } - const ::std::vector< double >& getDx() const { return maDx; } - double getTextLength() const { return mfTextLength; } - bool getLengthAdjust() const { return mbLengthAdjust; } - bool getAbsoluteX() const { return mbAbsoluteX; } - - // get/set absolute, current, advancing position - const basegfx::B2DPoint& getPosition() const { return maPosition; } - void setPosition(const basegfx::B2DPoint& rNew) { maPosition = rNew; } - - // rotation handling - bool isRotated() const; - double consumeRotation(); - }; - class SvgCharacterNode final : public SvgNode { private: @@ -127,6 +40,8 @@ namespace svgio::svgreader // keep a copy of string data before space handling OUString maTextBeforeSpaceHandling; + SvgTextNode* mpTextParent; + /// local helpers rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> createSimpleTextPrimitive( SvgTextPosition& rSvgTextPosition, @@ -144,15 +59,16 @@ namespace svgio::svgreader virtual ~SvgCharacterNode() override; virtual const SvgStyleAttributes* getSvgStyleAttributes() const override; + void decomposeText(drawinglayer::primitive2d::Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const; void whiteSpaceHandling(); - void addGap(); + SvgCharacterNode* addGap(SvgCharacterNode* pPreviousCharacterNode); void concatenate(std::u16string_view rText); /// Text content const OUString& getText() const { return maText; } - const OUString& getTextBeforeSpaceHandling() const { return maTextBeforeSpaceHandling; } + void setTextParent(SvgTextNode* pTextParent) { mpTextParent = pTextParent; } }; } // end of namespace svgio::svgreader diff --git a/svgio/inc/svgtextnode.hxx b/svgio/inc/svgtextnode.hxx index 0cc78f130aed..2d5f98ec18fc 100644 --- a/svgio/inc/svgtextnode.hxx +++ b/svgio/inc/svgtextnode.hxx @@ -19,23 +19,23 @@ #pragma once -#include "svgnode.hxx" #include "svgstyleattributes.hxx" -#include "svgcharacternode.hxx" +#include "svgtextposition.hxx" +#include "svgtspannode.hxx" #include <basegfx/matrix/b2dhommatrix.hxx> namespace svgio::svgreader { - class SvgTextNode final : public SvgNode + class SvgTextNode final : public SvgTspanNode { private: - /// use styles - SvgStyleAttributes maSvgStyleAttributes; - /// variable scan values, dependent of given XAttributeList std::optional<basegfx::B2DHomMatrix> mpaTransform; - SvgTextPositions maSvgTextPositions; + + // The text line composed by the different SvgCharacterNode children + // it will be used to calculate their alignment + OUString maTextLine; /// local helpers void DecomposeChild( @@ -53,13 +53,15 @@ namespace svgio::svgreader SvgNode* pParent); virtual ~SvgTextNode() override; - virtual const SvgStyleAttributes* getSvgStyleAttributes() const override; virtual void parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent) override; virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override; /// transform content, set if found in current context const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; } void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; } + + void concatenateTextLine(std::u16string_view rText) {maTextLine += rText;} + const OUString& getTextLine() const { return maTextLine; } }; } // end of namespace svgio::svgreader diff --git a/svgio/inc/svgtextposition.hxx b/svgio/inc/svgtextposition.hxx new file mode 100644 index 000000000000..df6adc16ab1e --- /dev/null +++ b/svgio/inc/svgtextposition.hxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> +#include <rtl/ref.hxx> + +#include <string_view> + +#include "svgtspannode.hxx" + +namespace svgio::svgreader +{ +class SvgTextPosition +{ +private: + SvgTextPosition* mpParent; + ::std::vector<double> maX; + ::std::vector<double> maY; + ::std::vector<double> maDx; + ::std::vector<double> maRotate; + double mfTextLength; + + // absolute, current, advancing position + basegfx::B2DPoint maPosition; + + // advancing rotation index + sal_uInt32 mnRotationIndex; + + bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs + bool mbAbsoluteX : 1; + +public: + SvgTextPosition(SvgTextPosition* pParent, const SvgTspanNode& rSvgCharacterNode); + + // data read access + const SvgTextPosition* getParent() const { return mpParent; } + const ::std::vector<double>& getX() const { return maX; } + const ::std::vector<double>& getDx() const { return maDx; } + double getTextLength() const { return mfTextLength; } + bool getLengthAdjust() const { return mbLengthAdjust; } + bool getAbsoluteX() const { return mbAbsoluteX; } + + // get/set absolute, current, advancing position + const basegfx::B2DPoint& getPosition() const { return maPosition; } + void setPosition(const basegfx::B2DPoint& rNew) { maPosition = rNew; } + + // rotation handling + bool isRotated() const; + double consumeRotation(); +}; + +} // end of namespace svgio::svgreader + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svgio/inc/svgtspannode.hxx b/svgio/inc/svgtspannode.hxx index 10a7b7ee16a9..d5d86c5a7c1a 100644 --- a/svgio/inc/svgtspannode.hxx +++ b/svgio/inc/svgtspannode.hxx @@ -19,22 +19,29 @@ #pragma once -#include "svgcharacternode.hxx" +#include "svgnode.hxx" #include "svgstyleattributes.hxx" namespace svgio::svgreader { - class SvgTspanNode final : public SvgNode + class SvgTspanNode : public SvgNode { private: /// use styles SvgStyleAttributes maSvgStyleAttributes; - /// variable scan values, dependent of given XAttributeList - SvgTextPositions maSvgTextPositions; + SvgNumberVector maX; + SvgNumberVector maY; + SvgNumberVector maDx; + SvgNumberVector maDy; + SvgNumberVector maRotate; + SvgNumber maTextLength; + + bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs public: SvgTspanNode( + SVGToken aType, SvgDocument& rDocument, SvgNode* pParent); virtual ~SvgTspanNode() override; @@ -44,8 +51,33 @@ namespace svgio::svgreader double getCurrentFontSize() const; - /// access to SvgTextPositions - const SvgTextPositions& getSvgTextPositions() const { return maSvgTextPositions; } + /// X content + const SvgNumberVector& getX() const { return maX; } + void setX(SvgNumberVector&& aX) { maX = std::move(aX); } + + /// Y content + const SvgNumberVector& getY() const { return maY; } + void setY(SvgNumberVector&& aY) { maY = std::move(aY); } + + /// Dx content + const SvgNumberVector& getDx() const { return maDx; } + void setDx(SvgNumberVector&& aDx) { maDx = std::move(aDx); } + + /// Dy content + const SvgNumberVector& getDy() const { return maDy; } + void setDy(SvgNumberVector&& aDy) { maDy = std::move(aDy); } + + /// Rotate content + const SvgNumberVector& getRotate() const { return maRotate; } + void setRotate(SvgNumberVector&& aRotate) { maRotate = std::move(aRotate); } + + /// TextLength content + const SvgNumber& getTextLength() const { return maTextLength; } + void setTextLength(const SvgNumber& rTextLength) { maTextLength = rTextLength; } + + /// LengthAdjust content + bool getLengthAdjust() const { return mbLengthAdjust; } + void setLengthAdjust(bool bNew) { mbLengthAdjust = bNew; } }; } // end of namespace svgio::svgreader diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx index 7a8ba6e82ec0..c52e20594b10 100644 --- a/svgio/qa/cppunit/SvgImportTest.cxx +++ b/svgio/qa/cppunit/SvgImportTest.cxx @@ -733,11 +733,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf85770) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "height", "11"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "familyname", "Times New Roman"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "fontcolor", "#000000"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", "Start "); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", "Start"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "height", "11"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "familyname", "Times New Roman"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "fontcolor", "#000000"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", "End"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", " End"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "height", "11"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "familyname", "Times New Roman"); @@ -1159,12 +1159,12 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf156251) // Without the fix in place, this test would have failed with // - Expected: 'You are ' // - Actual : 'You are' - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", "You are "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", "not "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", "a banana!"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "text", "You are "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "text", "not "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "text", "a banana!"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", "You are"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", " not"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", " a banana!"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "text", "You are"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "text", " not"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "text", " a banana!"); } CPPUNIT_TEST_FIXTURE(Test, testMaskText) @@ -1454,6 +1454,92 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf95400) assertXPathNoAttribute(pDocument, "/primitive2D/transform/textsimpleportion[2]", "dx0"); } +CPPUNIT_TEST_FIXTURE(Test, testTextAnchor) +{ + Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf151103.svg"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + + CPPUNIT_ASSERT (pDocument); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "x", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "y", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "x", "43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "y", "50"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", "26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "y", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "x", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "y", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "x", "43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "y", "50"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "x", "26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "y", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "x", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "y", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "x", "43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "y", "50"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "x", "26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "y", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "text", "ABC"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]", "x", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]", "y", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]", "text", "A"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[11]", "x", "72"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[11]", "y", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[11]", "text", "B"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[12]", "x", "83"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[12]", "y", "40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[12]", "text", "C"); + + // Without the fix in place, this test would have failed with + // - Expected: 43 + // - Actual : 54 + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "x", "43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "y", "50"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "text", "A"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]", "x", "55"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]", "y", "50"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]", "text", "B"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "x", "66"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "y", "50"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "text", "C"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "x", "26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "y", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "text", "A"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "x", "38"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "y", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "text", "B"); + + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "x", "49"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "y", "60"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "text", "C"); +} + CPPUNIT_TEST_FIXTURE(Test, testTdf156577) { Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156577.svg"); diff --git a/svgio/qa/cppunit/data/tdf151103.svg b/svgio/qa/cppunit/data/tdf151103.svg new file mode 100644 index 000000000000..664253f8df06 --- /dev/null +++ b/svgio/qa/cppunit/data/tdf151103.svg @@ -0,0 +1,18 @@ +<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"> + <text text-anchor="start" x="60" y="40">ABC</text> + <text text-anchor="middle" x="60" y="50">ABC</text> + <text text-anchor="end" x="60" y="60">ABC</text> + + <text><tspan text-anchor="start" x="60" y="40">ABC</tspan></text> + <text><tspan text-anchor="middle" x="60" y="50">ABC</tspan></text> + <text><tspan text-anchor="end" x="60" y="60">ABC</tspan></text> + + <text text-anchor="start" x="60" y="40"><tspan>ABC</tspan></text> + <text text-anchor="middle" x="60" y="50"><tspan>ABC</tspan></text> + <text text-anchor="end" x="60" y="60"><tspan>ABC</tspan></text> + + <text text-anchor="start" x="60" y="40">A<tspan>B</tspan>C</text> + <text text-anchor="middle" x="60" y="50">A<tspan>B</tspan>C</text> + <text text-anchor="end" x="60" y="60">A<tspan>B</tspan>C</text> +</svg> + diff --git a/svgio/source/svgreader/svgcharacternode.cxx b/svgio/source/svgreader/svgcharacternode.cxx index 8a6610c91d25..2b88944aa8d0 100644 --- a/svgio/source/svgreader/svgcharacternode.cxx +++ b/svgio/source/svgreader/svgcharacternode.cxx @@ -33,116 +33,6 @@ using namespace drawinglayer::primitive2d; namespace svgio::svgreader { - SvgTextPositions::SvgTextPositions() - : mbLengthAdjust(true) - { - } - - void SvgTextPositions::parseTextPositionAttributes(SVGToken aSVGToken, std::u16string_view aContent) - { - // parse own - switch(aSVGToken) - { - case SVGToken::X: - { - if(!aContent.empty()) - { - SvgNumberVector aVector; - - if(readSvgNumberVector(aContent, aVector)) - { - setX(std::move(aVector)); - } - } - break; - } - case SVGToken::Y: - { - if(!aContent.empty()) - { - SvgNumberVector aVector; - - if(readSvgNumberVector(aContent, aVector)) - { - setY(std::move(aVector)); - } - } - break; - } - case SVGToken::Dx: - { - if(!aContent.empty()) - { - SvgNumberVector aVector; - - if(readSvgNumberVector(aContent, aVector)) - { - setDx(std::move(aVector)); - } - } - break; - } - case SVGToken::Dy: - { - if(!aContent.empty()) - { - SvgNumberVector aVector; - - if(readSvgNumberVector(aContent, aVector)) - { - setDy(std::move(aVector)); - } - } - break; - } - case SVGToken::Rotate: - { - if(!aContent.empty()) - { - SvgNumberVector aVector; - - if(readSvgNumberVector(aContent, aVector)) - { - setRotate(std::move(aVector)); - } - } - break; - } - case SVGToken::TextLength: - { - SvgNumber aNum; - - if(readSingleNumber(aContent, aNum)) - { - if(aNum.isPositive()) - { - setTextLength(aNum); - } - } - break; - } - case SVGToken::LengthAdjust: - { - if(!aContent.empty()) - { - if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"spacing")) - { - setLengthAdjust(true); - } - else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"spacingAndGlyphs")) - { - setLengthAdjust(false); - } - } - break; - } - default: - { - break; - } - } - } - namespace { class localTextBreakupHelper : public TextBreakupHelper @@ -188,7 +78,8 @@ namespace svgio::svgreader SvgNode* pParent, OUString aText) : SvgNode(SVGToken::Character, rDocument, pParent), - maText(std::move(aText)) + maText(std::move(aText)), + mpTextParent(nullptr) { } @@ -359,17 +250,19 @@ namespace svgio::svgreader } } + // Use the whole text line to calculate the align position + double fWholeTextLineWidth(aTextLayouterDevice.getTextWidth(mpTextParent->getTextLine(), 0, mpTextParent->getTextLine().getLength())); // apply TextAlign switch(aTextAlign) { case TextAlign::right: { - aPosition.setX(aPosition.getX() - fTextWidth); + aPosition.setX(aPosition.getX() - fWholeTextLineWidth); break; } case TextAlign::center: { - aPosition.setX(aPosition.getX() - (fTextWidth * 0.5)); + aPosition.setX(aPosition.getX() - (fWholeTextLineWidth * 0.5)); break; } case TextAlign::notset: @@ -573,197 +466,64 @@ namespace svgio::svgreader } } - void SvgCharacterNode::addGap() - { - maText += " "; - } - - void SvgCharacterNode::concatenate(std::u16string_view rText) - { - maText += rText; - } - - void SvgCharacterNode::decomposeText(Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const + SvgCharacterNode* SvgCharacterNode::addGap(SvgCharacterNode* pPreviousCharacterNode) { - if(!getText().isEmpty()) + // maText may have lost all text. If that's the case, ignore as invalid character node + // Also ignore if maTextBeforeSpaceHandling just have spaces + if(!maText.isEmpty() && !o3tl::trim(maTextBeforeSpaceHandling).empty()) { - const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes(); - - if(pSvgStyleAttributes) + if(pPreviousCharacterNode) { - decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes); - } - } - } + bool bAddGap(true); + // Do not add a gap if last node doesn't end with a space and + // current note doesn't start with a space + const sal_uInt32 nLastLength(pPreviousCharacterNode->maTextBeforeSpaceHandling.getLength()); + if(pPreviousCharacterNode->maTextBeforeSpaceHandling[nLastLength - 1] != ' ' && maTextBeforeSpaceHandling[0] != ' ') + bAddGap = false; - SvgTextPosition::SvgTextPosition( - SvgTextPosition* pParent, - const InfoProvider& rInfoProvider, - const SvgTextPositions& rSvgTextPositions) - : mpParent(pParent), - maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider)), - mfTextLength(0.0), - mnRotationIndex(0), - mbLengthAdjust(rSvgTextPositions.getLengthAdjust()), - mbAbsoluteX(false) - { - // get TextLength if provided - if(rSvgTextPositions.getTextLength().isSet()) - { - mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider); - } - - // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given, - // but it seems to be degrees. Convert here to radians - if(!maRotate.empty()) - { - for (double& f : maRotate) - { - f = basegfx::deg2rad(f); - } - } - - // get text positions X - const sal_uInt32 nSizeX(rSvgTextPositions.getX().size()); - - if(nSizeX) - { - // we have absolute positions, get first one as current text position X - maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, NumberType::xcoordinate)); - mbAbsoluteX = true; - } - else - { - // no absolute position, get from parent - if(pParent) - { - maPosition.setX(pParent->getPosition().getX()); - } - } - - const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size()); - if(nSizeDx) - { - // relative positions given, translate position derived from parent - maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, NumberType::xcoordinate)); - } - - // fill deltas to maX - maX.reserve(nSizeX); + // With this option a baseline shift between two char parts ('words') + // will not add a space 'gap' to the end of the (non-last) word. This + // seems to be the standard behaviour, see last bugdoc attached #122524# + const SvgStyleAttributes* pStyleLast = pPreviousCharacterNode->getSvgStyleAttributes(); + const SvgStyleAttributes* pStyleCurrent = getSvgStyleAttributes(); - for(sal_uInt32 a(1); a < std::max(nSizeX, nSizeDx); ++a) - { - if (a < nSizeX) - { - double nPos = rSvgTextPositions.getX()[a].solve(rInfoProvider, NumberType::xcoordinate) - maPosition.getX(); - - if(a < nSizeDx) + if(pStyleLast && pStyleCurrent && pStyleLast->getBaselineShift() != pStyleCurrent->getBaselineShift()) { - nPos += rSvgTextPositions.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate); + bAddGap = false; } - maX.push_back(nPos); - } - else - { - // Apply them later since it also needs the character width to calculate - // the final character position - maDx.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate)); + // add in-between whitespace (single space) to last + // known character node + if(bAddGap) + { + maText = " " + maText; + } } - } - - // get text positions Y - const sal_uInt32 nSizeY(rSvgTextPositions.getY().size()); - if(nSizeY) - { - // we have absolute positions, get first one as current text position Y - maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, NumberType::ycoordinate)); - mbAbsoluteX = true; + // this becomes the previous character node + return this; } - else - { - // no absolute position, get from parent - if(pParent) - { - maPosition.setY(pParent->getPosition().getY()); - } - } - - const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size()); - if(nSizeDy) - { - // relative positions given, translate position derived from parent - maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, NumberType::ycoordinate)); - } - - // fill deltas to maY - maY.reserve(nSizeY); - - for(sal_uInt32 a(1); a < nSizeY; a++) - { - double nPos = rSvgTextPositions.getY()[a].solve(rInfoProvider, NumberType::ycoordinate) - maPosition.getY(); - - if(a < nSizeDy) - { - nPos += rSvgTextPositions.getDy()[a].solve(rInfoProvider, NumberType::ycoordinate); - } - - maY.push_back(nPos); - } + return pPreviousCharacterNode; } - bool SvgTextPosition::isRotated() const + void SvgCharacterNode::concatenate(std::u16string_view rText) { - if(maRotate.empty()) - { - if(getParent()) - { - return getParent()->isRotated(); - } - else - { - return false; - } - } - else - { - return true; - } + maText += rText; } - double SvgTextPosition::consumeRotation() + void SvgCharacterNode::decomposeText(Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const { - double fRetval(0.0); - - if(maRotate.empty()) - { - if(getParent()) - { - fRetval = mpParent->consumeRotation(); - } - else - { - fRetval = 0.0; - } - } - else + if(!getText().isEmpty()) { - const sal_uInt32 nSize(maRotate.size()); + const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes(); - if(mnRotationIndex < nSize) - { - fRetval = maRotate[mnRotationIndex++]; - } - else + if(pSvgStyleAttributes) { - fRetval = maRotate[nSize - 1]; + decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes); } } - - return fRetval; } } // end of namespace svgio diff --git a/svgio/source/svgreader/svgdocumenthandler.cxx b/svgio/source/svgreader/svgdocumenthandler.cxx index 2e7785f2626d..45213997144c 100644 --- a/svgio/source/svgreader/svgdocumenthandler.cxx +++ b/svgio/source/svgreader/svgdocumenthandler.cxx @@ -54,7 +54,6 @@ #include <svgtitledescnode.hxx> #include <sal/log.hxx> #include <osl/diagnose.h> -#include <o3tl/string_view.hxx> using namespace com::sun::star; @@ -63,7 +62,7 @@ namespace svgio::svgreader namespace { - svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgCharacterNode* pLast) + svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgTextNode* pText, svgio::svgreader::SvgCharacterNode* pLast) { if(pNode) { @@ -84,45 +83,10 @@ namespace svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate); pCharNode->whiteSpaceHandling(); + pLast = pCharNode->addGap(pLast); - // pCharNode may have lost all text. If that's the case, ignore - // as invalid character node - // Also ignore if textBeforeSpaceHandling just have spaces - if(!pCharNode->getText().isEmpty() && !o3tl::trim(pCharNode->getTextBeforeSpaceHandling()).empty()) - { - if(pLast) - { - bool bAddGap(true); - - // Do not add a gap if last node doesn't end with a space and - // current note doesn't start with a space - const sal_uInt32 nLastLength(pLast->getTextBeforeSpaceHandling().getLength()); - if(pLast->getTextBeforeSpaceHandling()[nLastLength - 1] != ' ' && pCharNode->getTextBeforeSpaceHandling()[0] != ' ') - bAddGap = false; - - // With this option a baseline shift between two char parts ('words') - // will not add a space 'gap' to the end of the (non-last) word. This - // seems to be the standard behaviour, see last bugdoc attached #122524# - const svgio::svgreader::SvgStyleAttributes* pStyleLast = pLast->getSvgStyleAttributes(); - const svgio::svgreader::SvgStyleAttributes* pStyleCurrent = pCandidate->getSvgStyleAttributes(); - - if(pStyleLast && pStyleCurrent && pStyleLast->getBaselineShift() != pStyleCurrent->getBaselineShift()) - { - bAddGap = false; - } - - // add in-between whitespace (single space) to last - // known character node - if(bAddGap) - { - pLast->addGap(); - } - } - - // remember new last corrected character node - pLast = pCharNode; - } - + pCharNode->setTextParent(pText); + pText->concatenateTextLine(pCharNode->getText()); break; } case SVGToken::Tspan: @@ -130,7 +94,7 @@ namespace case SVGToken::Tref: { // recursively clean whitespaces in subhierarchy - pLast = whiteSpaceHandling(pCandidate, pLast); + pLast = whiteSpaceHandling(pCandidate, pText, pLast); break; } default: @@ -145,6 +109,7 @@ namespace return pLast; } + } // end anonymous namespace SvgDocHdl::SvgDocHdl(const OUString& aAbsolutePath) @@ -323,7 +288,7 @@ namespace } case SVGToken::Tspan: { - mpTarget = new SvgTspanNode(maDocument, mpTarget); + mpTarget = new SvgTspanNode(aSVGToken, maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } @@ -463,7 +428,7 @@ namespace return; const SVGToken aSVGToken(StrToSVGToken(aName, false)); - SvgNode* pWhitespaceCheck(SVGToken::Text == aSVGToken ? mpTarget : nullptr); + SvgNode* pTextNode(SVGToken::Text == aSVGToken ? mpTarget : nullptr); SvgStyleNode* pCssStyle(SVGToken::Style == aSVGToken ? static_cast< SvgStyleNode* >(mpTarget) : nullptr); SvgTitleDescNode* pSvgTitleDescNode(SVGToken::Title == aSVGToken || SVGToken::Desc == aSVGToken ? static_cast< SvgTitleDescNode* >(mpTarget) : nullptr); @@ -584,10 +549,10 @@ namespace } } - if(pWhitespaceCheck) + if(pTextNode) { // cleanup read strings - whiteSpaceHandling(pWhitespaceCheck, nullptr); + whiteSpaceHandling(pTextNode, static_cast< SvgTextNode*>(pTextNode), nullptr); } } @@ -605,24 +570,23 @@ namespace case SVGToken::TextPath: { const auto& rChilds = mpTarget->getChildren(); - SvgCharacterNode* pTarget = nullptr; if(!rChilds.empty()) { - pTarget = dynamic_cast< SvgCharacterNode* >(rChilds[rChilds.size() - 1].get()); - } + SvgNode* pChild = rChilds[rChilds.size() - 1].get(); + if ( pChild->getType() == SVGToken::Character ) + { + SvgCharacterNode& rSvgCharacterNode = static_cast< SvgCharacterNode& >(*pChild); - if(pTarget) - { - // concatenate to current character span - pTarget->concatenate(aChars); - } - else - { - // add character span as simplified tspan (no arguments) - // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode - new SvgCharacterNode(maDocument, mpTarget, aChars); + // concatenate to current character span + rSvgCharacterNode.concatenate(aChars); + break; + } } + + // add character span as simplified tspan (no arguments) + // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode + new SvgCharacterNode(maDocument, mpTarget, aChars); break; } case SVGToken::Style: diff --git a/svgio/source/svgreader/svgtextnode.cxx b/svgio/source/svgreader/svgtextnode.cxx index 5b8cc3187070..bd8a334c5e11 100644 --- a/svgio/source/svgreader/svgtextnode.cxx +++ b/svgio/source/svgreader/svgtextnode.cxx @@ -30,8 +30,7 @@ namespace svgio::svgreader SvgTextNode::SvgTextNode( SvgDocument& rDocument, SvgNode* pParent) - : SvgNode(SVGToken::Text, rDocument, pParent), - maSvgStyleAttributes(*this) + : SvgTspanNode(SVGToken::Text, rDocument, pParent) { } @@ -39,30 +38,14 @@ namespace svgio::svgreader { } - const SvgStyleAttributes* SvgTextNode::getSvgStyleAttributes() const - { - return checkForCssStyle(maSvgStyleAttributes); - } - void SvgTextNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent) { // call parent - SvgNode::parseAttribute(rTokenName, aSVGToken, aContent); - - // read style attributes - maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent); - - // read text position attributes - maSvgTextPositions.parseTextPositionAttributes(aSVGToken, aContent); + SvgTspanNode::parseAttribute(rTokenName, aSVGToken, aContent); // parse own switch(aSVGToken) { - case SVGToken::Style: - { - readLocalCssStyle(aContent); - break; - } case SVGToken::Transform: { const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this)); @@ -159,7 +142,7 @@ namespace svgio::svgreader if(nCount) { - SvgTextPosition aSvgTextPosition(&rSvgTextPosition, rSvgTspanNode, rSvgTspanNode.getSvgTextPositions()); + SvgTextPosition aSvgTextPosition(&rSvgTextPosition, rSvgTspanNode); drawinglayer::primitive2d::Primitive2DContainer aNewTarget; for(sal_uInt32 a(0); a < nCount; a++) @@ -229,7 +212,7 @@ namespace svgio::svgreader if(fOpacity <= 0.0) return; - SvgTextPosition aSvgTextPosition(nullptr, *this, maSvgTextPositions); + SvgTextPosition aSvgTextPosition(nullptr, *this); drawinglayer::primitive2d::Primitive2DContainer aNewTarget; const auto& rChildren = getChildren(); const sal_uInt32 nCount(rChildren.size()); diff --git a/svgio/source/svgreader/svgtextposition.cxx b/svgio/source/svgreader/svgtextposition.cxx new file mode 100644 index 000000000000..50a896ba2204 --- /dev/null +++ b/svgio/source/svgreader/svgtextposition.cxx @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svgtextposition.hxx> + +using namespace drawinglayer::primitive2d; + +namespace svgio::svgreader +{ +SvgTextPosition::SvgTextPosition(SvgTextPosition* pParent, const SvgTspanNode& rSvgTspanNode) + : mpParent(pParent) + , maRotate(solveSvgNumberVector(rSvgTspanNode.getRotate(), rSvgTspanNode)) + , mfTextLength(0.0) + , mnRotationIndex(0) + , mbLengthAdjust(rSvgTspanNode.getLengthAdjust()) + , mbAbsoluteX(false) +{ + const InfoProvider& rInfoProvider(rSvgTspanNode); + + // get TextLength if provided + if (rSvgTspanNode.getTextLength().isSet()) + { + mfTextLength = rSvgTspanNode.getTextLength().solve(rInfoProvider); + } + + // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given, + // but it seems to be degrees. Convert here to radians + if (!maRotate.empty()) + { + for (double& f : maRotate) + { + f = basegfx::deg2rad(f); + } + } + + // get text positions X + const sal_uInt32 nSizeX(rSvgTspanNode.getX().size()); + + if (nSizeX) + { + // we have absolute positions, get first one as current text position X + maPosition.setX(rSvgTspanNode.getX()[0].solve(rInfoProvider, NumberType::xcoordinate)); + mbAbsoluteX = true; + } + else + { + // no absolute position, get from parent + if (pParent) + { + maPosition.setX(pParent->getPosition().getX()); + } + } + + const sal_uInt32 nSizeDx(rSvgTspanNode.getDx().size()); + if (nSizeDx) + { + // relative positions given, translate position derived from parent + maPosition.setX(maPosition.getX() + + rSvgTspanNode.getDx()[0].solve(rInfoProvider, NumberType::xcoordinate)); + } + + // fill deltas to maX + maX.reserve(nSizeX); + + for (sal_uInt32 a(1); a < std::max(nSizeX, nSizeDx); ++a) + { + if (a < nSizeX) + { + double nPos = rSvgTspanNode.getX()[a].solve(rInfoProvider, NumberType::xcoordinate) + - maPosition.getX(); + + if (a < nSizeDx) + { + nPos += rSvgTspanNode.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate); + } + + maX.push_back(nPos); + } + else + { + // Apply them later since it also needs the character width to calculate + // the final character position + maDx.push_back(rSvgTspanNode.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate)); + } + } + + // get text positions Y + const sal_uInt32 nSizeY(rSvgTspanNode.getY().size()); + + if (nSizeY) + { + // we have absolute positions, get first one as current text position Y + maPosition.setY(rSvgTspanNode.getY()[0].solve(rInfoProvider, NumberType::ycoordinate)); + mbAbsoluteX = true; + } + else + { + // no absolute position, get from parent + if (pParent) + { + maPosition.setY(pParent->getPosition().getY()); + } + } + + const sal_uInt32 nSizeDy(rSvgTspanNode.getDy().size()); + + if (nSizeDy) + { + // relative positions given, translate position derived from parent + maPosition.setY(maPosition.getY() + + rSvgTspanNode.getDy()[0].solve(rInfoProvider, NumberType::ycoordinate)); + } + + // fill deltas to maY + maY.reserve(nSizeY); + + for (sal_uInt32 a(1); a < nSizeY; a++) + { + double nPos = rSvgTspanNode.getY()[a].solve(rInfoProvider, NumberType::ycoordinate) + - maPosition.getY(); + + if (a < nSizeDy) + { + nPos += rSvgTspanNode.getDy()[a].solve(rInfoProvider, NumberType::ycoordinate); + } + + maY.push_back(nPos); + } +} + +bool SvgTextPosition::isRotated() const +{ + if (maRotate.empty()) + { + if (getParent()) + { + return getParent()->isRotated(); + } + else + { + return false; + } + } + else + { + return true; + } +} + +double SvgTextPosition::consumeRotation() +{ + double fRetval(0.0); + + if (maRotate.empty()) + { + if (getParent()) + { + fRetval = mpParent->consumeRotation(); + } + else + { + fRetval = 0.0; + } + } + else + { + const sal_uInt32 nSize(maRotate.size()); + + if (mnRotationIndex < nSize) + { + fRetval = maRotate[mnRotationIndex++]; + } + else + { + fRetval = maRotate[nSize - 1]; + } + } + + return fRetval; +} + +} // end of namespace svgio + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svgio/source/svgreader/svgtspannode.cxx b/svgio/source/svgreader/svgtspannode.cxx index df5e440080f8..4472b88ab3ad 100644 --- a/svgio/source/svgreader/svgtspannode.cxx +++ b/svgio/source/svgreader/svgtspannode.cxx @@ -18,14 +18,17 @@ */ #include <svgtspannode.hxx> +#include <o3tl/string_view.hxx> namespace svgio::svgreader { SvgTspanNode::SvgTspanNode( + SVGToken aType, SvgDocument& rDocument, SvgNode* pParent) - : SvgNode(SVGToken::Tspan, rDocument, pParent), - maSvgStyleAttributes(*this) + : SvgNode(aType, rDocument, pParent), + maSvgStyleAttributes(*this), + mbLengthAdjust(true) { } @@ -47,9 +50,6 @@ namespace svgio::svgreader // read style attributes maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent); - // read text position attributes - maSvgTextPositions.parseTextPositionAttributes(aSVGToken, aContent); - // parse own switch(aSVGToken) { @@ -58,6 +58,81 @@ namespace svgio::svgreader readLocalCssStyle(aContent); break; } + case SVGToken::X: + { + SvgNumberVector aVector; + + if(readSvgNumberVector(aContent, aVector)) + { + setX(std::move(aVector)); + } + break; + } + case SVGToken::Y: + { + SvgNumberVector aVector; + + if(readSvgNumberVector(aContent, aVector)) + { + setY(std::move(aVector)); + } + break; + } + case SVGToken::Dx: + { + SvgNumberVector aVector; + + if(readSvgNumberVector(aContent, aVector)) + { + setDx(std::move(aVector)); + } + break; + } + case SVGToken::Dy: + { + SvgNumberVector aVector; + + if(readSvgNumberVector(aContent, aVector)) + { + setDy(std::move(aVector)); + } + break; + } + case SVGToken::Rotate: + { + SvgNumberVector aVector; + + if(readSvgNumberVector(aContent, aVector)) + { + setRotate(std::move(aVector)); + } + break; + } + case SVGToken::TextLength: + { + SvgNumber aNum; + + if(readSingleNumber(aContent, aNum)) + { + if(aNum.isPositive()) + { + setTextLength(aNum); + } + } + break; + } + case SVGToken::LengthAdjust: + { + if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"spacing")) + { + setLengthAdjust(true); + } + else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"spacingAndGlyphs")) + { + setLengthAdjust(false); + } + break; + } default: { break;