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="'Liberation Sans'" 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<templateArg>(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 ) {