filter/qa/unit/data/fit-to-size-text.fodg |   35 ++++++++++++++++++++++++++++++
 filter/qa/unit/svg.cxx                    |   16 +++++++++++++
 filter/source/svg/svgwriter.cxx           |   29 ++++++++++++++++++------
 filter/source/svg/svgwriter.hxx           |    4 +--
 4 files changed, 74 insertions(+), 10 deletions(-)

New commits:
commit 08eb258f957baa4bf39ac2da4f417946995568b9
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Fri May 30 14:46:22 2025 +0500
Commit:     Michael Stahl <michael.st...@allotropia.de>
CommitDate: Mon Jun 2 09:15:08 2025 +0200

    tdf#166789: use text width from DXArray in MetaActionType::TEXTARRAY
    
    DXArray gives much more reliable data compared to font width, which
    is known to require complex corrections (see tdf#127471 and commit
    3d33e4ce3987ea17e73a72e84f7f0df7af8101a6).
    
    This fix uses tspan's `lengthAdjust` and `textLength` attributes for
    automatic justification, which are already documented as early as in
    SVG 1.0 ( https://www.w3.org/TR/SVG10/text.html#TSpanElement ). This
    works in LibreOffice and in Chrome, but is not supported in Firefox
    yet ( see https://bugzilla.mozilla.org/show_bug.cgi?id=890692 ).
    
    I considered using these attributes on `text` element level, which
    is supported in both browsers - but that breaks text elements with
    multiple tspans. The `transform` attribute can't be used with tspan
    both in Chrome and in Firefox, which creates the same problem, and
    has an additional drawback that tspan offsets need tweaks.
    
    Change-Id: I7d4bbb7542a9d60e53bdf6f6cac7fae0729e6e48
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186033
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    (cherry picked from commit a6eb3588d9717eb01c03bdc100565eb692362ded)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186048
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>

diff --git a/filter/qa/unit/data/fit-to-size-text.fodg 
b/filter/qa/unit/data/fit-to-size-text.fodg
new file mode 100644
index 000000000000..04e988b48971
--- /dev/null
+++ b/filter/qa/unit/data/fit-to-size-text.fodg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
office:version="1.4" 
office:mimetype="application/vnd.oasis.opendocument.graphics">
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Sans" 
svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="roman" 
style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:style style:name="standard" style:family="graphic">
+   <style:graphic-properties draw:stroke="solid" svg:stroke-color="#000000" 
svg:stroke-width="5" draw:fill="none" fo:padding-top="0" fo:padding-bottom="0" 
fo:padding-left="0" fo:padding-right="0"/>
+   <style:text-properties style:font-name="Liberation Sans" 
fo:font-size="18pt" fo:font-variant="normal" fo:language="zxx" 
fo:country="none" fo:font-style="normal" style:letter-kerning="true"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:page-layout style:name="PM0">
+   <style:page-layout-properties fo:margin-top="0" fo:margin-bottom="0" 
fo:margin-left="0" fo:margin-right="0" fo:page-width="8cm" 
fo:page-height="15mm"/>
+  </style:page-layout>
+  <style:style style:name="gr1" style:family="graphic" 
style:parent-style-name="standard">
+   <style:graphic-properties draw:auto-grow-height="false" 
draw:auto-grow-width="false" draw:fit-to-size="true" 
style:shrink-to-fit="false"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="PM0"/>
+ </office:master-styles>
+ <office:body>
+  <office:drawing>
+   <draw:page draw:name="page1" draw:master-page-name="Standard">
+    <draw:frame draw:style-name="gr1" svg:width="7cm" svg:height="5mm" 
svg:x="5mm" svg:y="5mm">
+     <draw:text-box>
+      <text:p>= Foo(Bar).method&lt;templateArg&gt;(arg1, arg2, ...)</text:p>
+     </draw:text-box>
+    </draw:frame>
+   </draw:page>
+  </office:drawing>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx
index 366102fd964c..4fab96055822 100644
--- a/filter/qa/unit/svg.cxx
+++ b/filter/qa/unit/svg.cxx
@@ -20,6 +20,7 @@
 #include <com/sun/star/beans/XPropertySet.hpp>
 
 #include <comphelper/propertyvalue.hxx>
+#include <o3tl/string_view.hxx>
 #include <unotools/streamwrap.hxx>
 #include <unotools/mediadescriptor.hxx>
 #include <tools/stream.hxx>
@@ -379,6 +380,21 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, testTdf91315)
     // - Actual  : 0
 }
 
+CPPUNIT_TEST_FIXTURE(SvgFilterTest, testTdf166789)
+{
+    // A fit-to-size text
+    loadFromFile(u"fit-to-size-text.fodg");
+
+    save(u"impress_svg_Export"_ustr);
+
+    xmlDocUniquePtr pXmlDoc = parseExportedFile();
+
+    // Without the accompanying fix, the text wasn't adjusted to the given 
width
+    OUString length = getXPath(pXmlDoc, 
"//svg:text//svg:tspan[@lengthAdjust='spacingAndGlyphs']",
+                               "textLength");
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(7000, length.toInt32(), 70); // allow 1% for 
rounding errors
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
index 00c3f1eb0e40..d5e23f1ec35e 100644
--- a/filter/source/svg/svgwriter.cxx
+++ b/filter/source/svg/svgwriter.cxx
@@ -1623,8 +1623,7 @@ void SVGTextWriter::implWriteEmbeddedBitmaps()
 }
 
 
-void SVGTextWriter::writeTextPortion( const Point& rPos,
-                                      const OUString& rText )
+void SVGTextWriter::writeTextPortion(const Point& rPos, const OUString& rText, 
tools::Long nWidth)
 {
     if( rText.isEmpty() )
         return;
@@ -1703,7 +1702,7 @@ void SVGTextWriter::writeTextPortion( const Point& rPos,
         // to be implemented
     }
 #else
-    implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() );
+    implWriteTextPortion( rPos, rText, mpVDev->GetTextColor(), nWidth );
 #endif
 
     if( bStandAloneTextPortion )
@@ -1713,9 +1712,8 @@ void SVGTextWriter::writeTextPortion( const Point& rPos,
 }
 
 
-void SVGTextWriter::implWriteTextPortion( const Point& rPos,
-                                          const OUString& rText,
-                                          Color aTextColor )
+void SVGTextWriter::implWriteTextPortion(const Point& rPos, const OUString& 
rText, Color aTextColor,
+                                         tools::Long nWidth)
 {
     Point                                   aPos;
     Point                                   aBaseLinePos( rPos );
@@ -1808,6 +1806,18 @@ void SVGTextWriter::implWriteTextPortion( const Point& 
rPos,
 
     addFontAttributes( /* isTexTContainer: */ false );
 
+    tools::Long nTextWidth;
+    if (nWidth)
+    {
+        Size size;
+        implMap(Size(nWidth, 0), size);
+        nTextWidth = size.Width();
+        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"lengthAdjust"_ustr, 
u"spacingAndGlyphs"_ustr);
+        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"textLength"_ustr, 
OUString::number(nTextWidth));
+    }
+    else
+        nTextWidth = mpVDev->GetTextWidth(rText);
+
     if (!maTextOpacity.isEmpty())
     {
         mrExport.AddAttribute(XML_NAMESPACE_NONE, u"fill-opacity"_ustr, 
maTextOpacity);
@@ -1843,7 +1853,7 @@ void SVGTextWriter::implWriteTextPortion( const Point& 
rPos,
         mrExport.GetDocHandler()->characters( rText );
     }
 
-    mnTextWidth += mpVDev->GetTextWidth( rText );
+    mnTextWidth += nTextWidth;
 }
 
 
@@ -4013,7 +4023,10 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
                         }
                         else
                         {
-                            maTextWriter.writeTextPortion( pA->GetPoint(), 
aText );
+                            tools::Long nWidth = 0;
+                            if (pA->GetDXArray().size() >= 
o3tl::make_unsigned(aText.getLength()))
+                                nWidth = 
std::round(pA->GetDXArray()[aText.getLength() - 1]);
+                            maTextWriter.writeTextPortion(pA->GetPoint(), 
aText, nWidth);
                         }
                     }
                 }
diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx
index a798b8b74a78..d6f3b8c9bf74 100644
--- a/filter/source/svg/svgwriter.hxx
+++ b/filter/source/svg/svgwriter.hxx
@@ -264,9 +264,9 @@ class SVGTextWriter final
     template< typename MetaBitmapActionType >
     void writeBitmapPlaceholder( const MetaBitmapActionType* pAction );
     void implWriteEmbeddedBitmaps();
-    void writeTextPortion( const Point& rPos, const OUString& rText );
+    void writeTextPortion(const Point& rPos, const OUString& rText, 
tools::Long nWidth = 0);
     void implWriteTextPortion( const Point& rPos, const OUString& rText,
-                               Color aTextColor );
+                               Color aTextColor, tools::Long nWidth );
 
     void setVirtualDevice( VirtualDevice* pVDev, MapMode& rTargetMapMode )
     {

Reply via email to