svgio/inc/svgcharacternode.hxx                |    7 ++
 svgio/qa/cppunit/SvgImportTest.cxx            |   86 ++++++++++++++++++++++++
 svgio/qa/cppunit/data/tdf151103.svg           |   18 +++++
 svgio/source/svgreader/svgcharacternode.cxx   |    6 +
 svgio/source/svgreader/svgdocumenthandler.cxx |   91 +++++++++++++++++++++++++-
 5 files changed, 203 insertions(+), 5 deletions(-)

New commits:
commit 2795a230464aea3a792e67b5625fce2a0c01d547
Author:     Xisco Fauli <[email protected]>
AuthorDate: Thu Aug 3 17:47:36 2023 +0200
Commit:     Xisco Fauli <[email protected]>
CommitDate: Thu Aug 3 18:36:56 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 <[email protected]>

diff --git a/svgio/inc/svgcharacternode.hxx b/svgio/inc/svgcharacternode.hxx
index 8861055f8e65..c74f881df468 100644
--- a/svgio/inc/svgcharacternode.hxx
+++ b/svgio/inc/svgcharacternode.hxx
@@ -127,6 +127,9 @@ namespace svgio::svgreader
             // keep a copy of string data before space handling
             OUString           maTextBeforeSpaceHandling;
 
+            // The whole text line of which this SvgCharacterNode is parted of
+            OUString           maWholeTextLine;
+
             /// local helpers
             rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> 
createSimpleTextPrimitive(
                 SvgTextPosition& rSvgTextPosition,
@@ -153,6 +156,10 @@ namespace svgio::svgreader
             const OUString& getText() const { return maText; }
 
             const OUString& getTextBeforeSpaceHandling() const { return 
maTextBeforeSpaceHandling; }
+
+            void setWholeTextLine(const OUString& rWholeTextLine) { 
maWholeTextLine = rWholeTextLine; }
+
+            const OUString& getWholeTextLine() const { return maWholeTextLine; 
}
         };
 
 } // end of namespace svgio::svgreader
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx 
b/svgio/qa/cppunit/SvgImportTest.cxx
index 24a05ea27075..80234e8b1f56 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -1458,6 +1458,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..bfd17c6e0d58 100644
--- a/svgio/source/svgreader/svgcharacternode.cxx
+++ b/svgio/source/svgreader/svgcharacternode.cxx
@@ -359,17 +359,19 @@ namespace svgio::svgreader
                     }
                 }
 
+                // Use the whole text line to calculate the align position
+                double 
fWholeTextLineWidth(aTextLayouterDevice.getTextWidth(getWholeTextLine(), 0, 
getWholeTextLine().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:
diff --git a/svgio/source/svgreader/svgdocumenthandler.cxx 
b/svgio/source/svgreader/svgdocumenthandler.cxx
index 5c600a86dce1..b0bdfbabe7fb 100644
--- a/svgio/source/svgreader/svgdocumenthandler.cxx
+++ b/svgio/source/svgreader/svgdocumenthandler.cxx
@@ -145,6 +145,87 @@ namespace
 
         return pLast;
     }
+
+    OUString getWholeTextLine(svgio::svgreader::SvgNode const * pNode)
+    {
+        OUString sText;
+        if (pNode)
+        {
+            const auto& rChilds = pNode->getChildren();
+            const sal_uInt32 nCount(rChilds.size());
+
+            for(sal_uInt32 a(0); a < nCount; a++)
+            {
+                svgio::svgreader::SvgNode* pCandidate = rChilds[a].get();
+
+                if(pCandidate)
+                {
+                    switch(pCandidate->getType())
+                    {
+                        case SVGToken::Character:
+                        {
+                            svgio::svgreader::SvgCharacterNode* pCharNode = 
static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
+                            sText += pCharNode->getText();
+                            break;
+                        }
+                        case SVGToken::Tspan:
+                        case SVGToken::TextPath:
+                        case SVGToken::Tref:
+                        {
+                            sText += getWholeTextLine(pCandidate);
+                            break;
+                        }
+                        default:
+                        {
+                            OSL_ENSURE(false, "Unexpected token inside 
SVGTokenText (!)");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return sText;
+    }
+
+    void setWholeTextLine(svgio::svgreader::SvgNode const * pNode, const 
OUString& rText)
+    {
+        if (pNode)
+        {
+            const auto& rChilds = pNode->getChildren();
+            const sal_uInt32 nCount(rChilds.size());
+
+            for(sal_uInt32 a(0); a < nCount; a++)
+            {
+                svgio::svgreader::SvgNode* pCandidate = rChilds[a].get();
+
+                if(pCandidate)
+                {
+                    switch(pCandidate->getType())
+                    {
+                        case SVGToken::Character:
+                        {
+                            svgio::svgreader::SvgCharacterNode* pCharNode = 
static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
+                            pCharNode->setWholeTextLine(rText);
+                            break;
+                        }
+                        case SVGToken::Tspan:
+                        case SVGToken::TextPath:
+                        case SVGToken::Tref:
+                        {
+                            setWholeTextLine(pCandidate, rText);
+                            break;
+                        }
+                        default:
+                        {
+                            OSL_ENSURE(false, "Unexpected token inside 
SVGTokenText (!)");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
 } // end anonymous namespace
 
         SvgDocHdl::SvgDocHdl(const OUString& aAbsolutePath)
@@ -468,7 +549,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);
 
@@ -597,10 +678,14 @@ namespace
                 }
             }
 
-            if(pWhitespaceCheck)
+            if(pTextNode)
             {
                 // cleanup read strings
-                whiteSpaceHandling(pWhitespaceCheck, nullptr);
+                whiteSpaceHandling(pTextNode, nullptr);
+
+                // Iterate over all the nodes in this text element to get the 
whole text line
+                OUString sWholeTextLine = getWholeTextLine(pTextNode);
+                setWholeTextLine(pTextNode, sWholeTextLine);
             }
         }
 

Reply via email to