sc/source/ui/view/output2.cxx                |   16 ++-
 vcl/qa/cppunit/pdfexport/data/tdf160786.fods |  134 +++++++++++++++++++++++++++
 vcl/qa/cppunit/pdfexport/pdfexport2.cxx      |   49 +++++++++
 3 files changed, 196 insertions(+), 3 deletions(-)

New commits:
commit 981c5780da8ec6b6878de58909b7cc7e736ad33a
Author:     Jonathan Clark <jonat...@libreoffice.org>
AuthorDate: Tue Aug 6 06:01:57 2024 -0600
Commit:     Jonathan Clark <jonat...@libreoffice.org>
CommitDate: Tue Aug 6 21:57:10 2024 +0200

    tdf#160786 sc: Fix repeat char format code inaccuracy
    
    Number format codes allow arbitrary repeat characters. These repeat
    characters are inserted while formatting numbers to pad strings to the
    width of the containing cell. For example, this is used in some default
    format codes to left-align currency symbols with right-aligned digits.
    
    Previously, Calc determined the number of repeat characters to insert
    based on the assumption that the pixel width of a string containing N of
    the repeat character would be N times the pixel width of a string
    containing 1 of them. This is not a safe assumption.
    
    This change updates the algorithm to instead look at the average width
    of a repeat character when used in context as part of a padding string.
    This approach is still not entirely correct - it doesn't account for
    ligatures around the padding sequence, for example - but in practice it
    is a reasonable compromise between cost and accuracy.
    
    Change-Id: I240cf38ec0376ee2d2896e7a7da6760a374b3034
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171548
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <jonat...@libreoffice.org>

diff --git a/sc/source/ui/view/output2.cxx b/sc/source/ui/view/output2.cxx
index 1a8d1356a4a6..cab30c900136 100644
--- a/sc/source/ui/view/output2.cxx
+++ b/sc/source/ui/view/output2.cxx
@@ -573,8 +573,17 @@ void ScDrawStringsVars::RepeatToFill( tools::Long 
nColWidth )
     if ( nRepeatPos == -1 || nRepeatPos > aString.getLength() )
         return;
 
-    tools::Long nCharWidth = GetFmtTextWidth(OUString(nRepeatChar));
+    // Measuring a string containing a single copy of the repeat char is 
inaccurate.
+    // To increase accuracy, start with a representative sample of a padding 
sequence.
+    constexpr sal_Int32 nSampleSize = 20;
+    OUStringBuffer aFill(nSampleSize);
+    comphelper::string::padToLength(aFill, nSampleSize, nRepeatChar);
 
+    tools::Long nSampleWidth = GetFmtTextWidth(aFill.makeStringAndClear());
+    double nAvgCharWidth = static_cast<double>(nSampleWidth) / 
static_cast<double>(nSampleSize);
+
+    // Intentionally truncate to round toward zero
+    auto nCharWidth = static_cast<tools::Long>(nAvgCharWidth);
     if ( nCharWidth < 1 || (bPixelToLogic && nCharWidth < 
pOutput->mpRefDevice->PixelToLogic(Size(1,0)).Width()) )
         return;
 
@@ -590,8 +599,9 @@ void ScDrawStringsVars::RepeatToFill( tools::Long nColWidth 
)
     if ( nSpaceToFill <= nCharWidth )
         return;
 
-    sal_Int32 nCharsToInsert = nSpaceToFill / nCharWidth;
-    OUStringBuffer aFill(nCharsToInsert);
+    // Intentionally truncate to round toward zero
+    auto nCharsToInsert = 
static_cast<sal_Int32>(static_cast<double>(nSpaceToFill) / nAvgCharWidth);
+    aFill.ensureCapacity(nCharsToInsert);
     comphelper::string::padToLength(aFill, nCharsToInsert, nRepeatChar);
     aString = aString.replaceAt( nRepeatPos, 0, aFill );
     TextChanged();
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf160786.fods 
b/vcl/qa/cppunit/pdfexport/data/tdf160786.fods
new file mode 100644
index 000000000000..acb279ee52fc
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf160786.fods
@@ -0,0 +1,134 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<office:document 
xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" 
xmlns:css3t="http://www.w3.org/TR/css3-text/"; 
xmlns:grddl="http://www.w3.org/2003/g/data-view#"; 
xmlns:xhtml="http://www.w3.org/1999/xhtml"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"; 
xmlns:xforms="http://www.w3.org/2002/xforms"; 
xmlns:dom="http://www.w3.org/2001/xml-events"; 
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" 
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" 
xmlns:math="http://www.w3.org/1998/Math/MathML"; 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:ooo="http://openoffice.org/2004/office"; 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" 
xmlns:ooow="http://openoffice.org/2004/writer"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:drawooo="http://openoffice.org/2010/draw"; xmlns:oooc="http://o
 penoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/"; 
xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext: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:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" 
xmlns:tableooo="http://openoffice.org/2009/table"; 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" 
xmlns:rpt="http://openoffice.org/2005/report"; 
xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"
 xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" 
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" 
xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" 
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" 
xmlns:meta="urn:oasis:name
 s:tc:opendocument:xmlns:meta:1.0" 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
 office:version="1.3" 
office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
+ 
<office:meta><meta:creation-date>2024-08-06T05:16:57.794291032</meta:creation-date><meta:editing-duration>P0D</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:generator>LibreOfficeDev/25.2.0.0.alpha0$Linux_X86_64
 
LibreOffice_project/900af9c53788d5d274900f796e3aee14926abf2d</meta:generator><meta:print-date>2024-08-06T05:52:40.231662987</meta:print-date><meta:printed-by>PDF
 files</meta:printed-by><meta:document-statistic meta:table-count="1" 
meta:cell-count="2" meta:object-count="0"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation 
Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
+  <style:font-face style:name="Liberation Sans1" svg:font-family="'Liberation 
Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Microsoft YaHei" svg:font-family="'Microsoft 
YaHei'" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Serif" svg:font-family="'Noto Serif'" 
style:font-family-generic="roman" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="table-cell">
+   <style:paragraph-properties style:tab-stop-distance="0.5in"/>
+   <style:text-properties style:font-name="Liberation Sans" 
fo:font-size="10pt" fo:language="en" fo:country="US" 
style:font-name-asian="Microsoft YaHei" style:font-size-asian="10pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="Liberation Sans1" style:font-size-complex="10pt" 
style:language-complex="he" style:country-complex="IL"/>
+  </style:default-style>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" 
draw:fill-color="#729fcf" fo:wrap-option="no-wrap" 
draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" 
style:writing-mode="page"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" 
style:punctuation-wrap="simple" style:line-break="strict" 
loext:tab-stop-distance="0in" style:writing-mode="page" 
style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" fo:font-family="'Liberation Serif'" 
style:font-family-generic="roman" style:font-pitch="variable" 
fo:font-size="12pt" fo:language="en" fo:country="US" 
style:letter-kerning="true" style:font-family-asian="'Segoe UI'" 
style:font-family-generic-asian="system" style:font-pitch-asian="variable" 
style:font-size-asian="12pt" style:language-asian="zh" style:country-asian="CN" 
style:font-family-complex="Tahoma" style:font-family-generic-complex="system" 
style:font-pitch-complex="variable" style:font-size-complex="12pt" 
style:language-complex="he" style:country-complex="IL"/>
+  </style:default-style>
+  <number:number-style style:name="N0">
+   <number:number number:min-integer-digits="1"/>
+  </number:number-style>
+  <number:currency-style style:name="N132P0" style:volatile="true">
+   <number:currency-symbol number:language="en" 
number:country="US">$</number:currency-symbol>
+   <number:fill-character>f</number:fill-character>
+   <number:number number:decimal-places="2" number:min-decimal-places="2" 
number:min-integer-digits="1" number:grouping="true"/>
+  </number:currency-style>
+  <number:currency-style style:name="N132">
+   <number:text>-</number:text>
+   <number:currency-symbol number:language="en" 
number:country="US">$</number:currency-symbol>
+   <number:fill-character> </number:fill-character>
+   <number:number number:decimal-places="2" number:min-decimal-places="2" 
number:min-integer-digits="1" number:grouping="true"/>
+   <style:map style:condition="value()&gt;=0" style:apply-style-name="N132P0"/>
+  </number:currency-style>
+  <style:style style:name="Default" style:family="table-cell"/>
+  </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="co1" style:family="table-column">
+   <style:table-column-properties fo:break-before="auto" 
style:column-width="0.5071in"/>
+  </style:style>
+  <style:style style:name="co2" style:family="table-column">
+   <style:table-column-properties fo:break-before="auto" 
style:column-width="6.1126in"/>
+  </style:style>
+  <style:style style:name="co3" style:family="table-column">
+   <style:table-column-properties fo:break-before="auto" 
style:column-width="0.889in"/>
+  </style:style>
+  <style:style style:name="ro1" style:family="table-row">
+   <style:table-row-properties style:row-height="0.178in" 
fo:break-before="auto" style:use-optimal-row-height="true"/>
+  </style:style>
+  <style:style style:name="ta1" style:family="table" 
style:master-page-name="Default">
+   <style:table-properties table:display="true" style:writing-mode="lr-tb"/>
+  </style:style>
+  <number:number-style style:name="N2">
+   <number:number number:decimal-places="2" number:min-decimal-places="2" 
number:min-integer-digits="1"/>
+  </number:number-style>
+  <style:style style:name="ce1" style:family="table-cell" 
style:parent-style-name="Default">
+   <style:table-cell-properties style:text-align-source="fix" 
style:repeat-content="false"/>
+   <style:paragraph-properties fo:text-align="end" fo:margin-left="0in"/>
+   <style:text-properties style:font-name="Noto Serif" fo:font-size="10.5pt" 
style:font-size-asian="10.5pt" style:font-size-complex="10.5pt"/>
+  </style:style>
+  <style:style style:name="ce3" style:family="table-cell" 
style:parent-style-name="Default" style:data-style-name="N132">
+   <style:table-cell-properties style:text-align-source="value-type" 
style:repeat-content="false"/>
+   <style:paragraph-properties fo:margin-left="0in"/>
+   <style:text-properties style:font-name="Noto Serif" fo:font-size="10.5pt" 
style:font-size-asian="10.5pt" style:font-size-complex="10.5pt"/>
+  </style:style>
+  <style:style style:name="ce2" style:family="table-cell" 
style:parent-style-name="Default">
+   <style:table-cell-properties style:text-align-source="fix" 
style:repeat-content="false"/>
+   <style:paragraph-properties fo:text-align="end" fo:margin-left="0in"/>
+  </style:style>
+  <style:style style:name="ce5" style:family="table-cell" 
style:parent-style-name="Default">
+   <style:table-cell-properties style:text-align-source="value-type" 
style:repeat-content="false"/>
+   <style:paragraph-properties fo:margin-left="0in"/>
+   <style:text-properties style:font-name="Liberation Sans"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties style:writing-mode="lr-tb"/>
+   <style:header-style>
+    <style:header-footer-properties fo:min-height="0.2953in" 
fo:margin-left="0in" fo:margin-right="0in" fo:margin-bottom="0.0984in"/>
+   </style:header-style>
+   <style:footer-style>
+    <style:header-footer-properties fo:min-height="0.2953in" 
fo:margin-left="0in" fo:margin-right="0in" fo:margin-top="0.0984in"/>
+   </style:footer-style>
+  </style:page-layout>
+  </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Default" style:page-layout-name="pm1">
+   <style:header>
+    <text:p><text:sheet-name>???</text:sheet-name></text:p>
+   </style:header>
+   <style:header-left style:display="false"/>
+   <style:header-first style:display="false"/>
+   <style:footer>
+    <text:p>Page <text:page-number>1</text:page-number></text:p>
+   </style:footer>
+   <style:footer-left style:display="false"/>
+   <style:footer-first style:display="false"/>
+  </style:master-page>
+  </office:master-styles>
+ <office:body>
+  <office:spreadsheet>
+   <table:calculation-settings table:automatic-find-labels="false" 
table:use-regular-expressions="false" table:use-wildcards="true"/>
+   <table:table table:name="Sheet1" table:style-name="ta1">
+    <table:table-column table:style-name="co1" 
table:default-cell-style-name="Default"/>
+    <table:table-column table:style-name="co2" 
table:default-cell-style-name="Default"/>
+    <table:table-column table:style-name="co3" 
table:default-cell-style-name="Default"/>
+    <table:table-column table:style-name="co3" 
table:default-cell-style-name="ce5"/>
+    <table:table-row table:style-name="ro1">
+     <table:table-cell table:style-name="ce1" office:value-type="string" 
calcext:value-type="string">
+      <text:p>A</text:p>
+     </table:table-cell>
+     <table:table-cell table:style-name="ce3" office:value-type="currency" 
office:currency="USD" office:value="12345.67" calcext:value-type="currency">
+      <text:p>$12,345.67</text:p>
+     </table:table-cell>
+     <table:table-cell table:style-name="ce2"/>
+     <table:table-cell table:style-name="Default"/>
+    </table:table-row>
+    <table:table-row table:style-name="ro1" table:number-rows-repeated="7">
+     <table:table-cell table:number-columns-repeated="2"/>
+     <table:table-cell table:style-name="ce2"/>
+     <table:table-cell/>
+    </table:table-row>
+    <table:table-row table:style-name="ro1" 
table:number-rows-repeated="1048567">
+     <table:table-cell table:number-columns-repeated="4"/>
+    </table:table-row>
+    <table:table-row table:style-name="ro1">
+     <table:table-cell table:number-columns-repeated="4"/>
+    </table:table-row>
+   </table:table>
+   <table:named-expressions/>
+  </office:spreadsheet>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx 
b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
index 702b71db7eff..9b08b1fa047a 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -5429,6 +5429,55 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, 
testTdf162194SoftHyphen)
     CPPUNIT_ASSERT_EQUAL(u"fle"_ustr, aText.at(3).trim());
 }
 
+// tdf#160786 - Tests that Calc format code with repeat char is measured 
correctly
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf160786)
+{
+    saveAsPDF(u"tdf160786.fods");
+
+    auto pPdfDocument = parsePDFExport();
+    CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+    auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
+    CPPUNIT_ASSERT(pPdfPage);
+    auto pTextPage = pPdfPage->getTextPage();
+    CPPUNIT_ASSERT(pTextPage);
+
+    int nPageObjectCount = pPdfPage->getObjectCount();
+    CPPUNIT_ASSERT_EQUAL(5, nPageObjectCount);
+
+    std::vector<OUString> aText;
+    std::vector<basegfx::B2DRectangle> aRect;
+
+    int nTextObjectCount = 0;
+    for (int i = 0; i < nPageObjectCount; ++i)
+    {
+        auto pPageObject = pPdfPage->getObject(i);
+        CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
+        if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
+        {
+            aText.push_back(pPageObject->getText(pTextPage));
+            aRect.push_back(pPageObject->getBounds());
+            ++nTextObjectCount;
+        }
+    }
+
+    CPPUNIT_ASSERT_EQUAL(5, nTextObjectCount);
+
+    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, aText.at(3).trim());
+
+    // The currency line is padded with an unknown number of 'f' characters. 
It doesn't matter how
+    // many are used, as long as the cell is padded to the expected width. 
Just verify that this
+    // text object is the expected one.
+    CPPUNIT_ASSERT(o3tl::trim(aText.at(4)).starts_with(u"$"));
+
+    // The currency cell must not overlap the adjacent cell
+    CPPUNIT_ASSERT_GREATEREQUAL(aRect.at(3).getMaxX(), aRect.at(4).getMinX());
+
+    // The currency cell must be padded to occupy its space reasonably well.
+    // As a heuristic, ensure the free space is no more than the width of "A"
+    CPPUNIT_ASSERT_LESS(aRect.at(3).getMaxX() + aRect.at(3).getWidth(), 
aRect.at(4).getMinX());
+}
+
 } // end anonymous namespace
 
 CPPUNIT_PLUGIN_IMPLEMENT();

Reply via email to