sw/qa/extras/layout/data/tdf167540.docx |binary
 sw/qa/extras/layout/layout5.cxx         |   86 ++++++++++++++++++++++++++++++++
 sw/source/core/text/frmpaint.cxx        |    9 ++-
 3 files changed, 93 insertions(+), 2 deletions(-)

New commits:
commit d680c2be8bafcac13fdd05c99c0cdcc06b55ce3d
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Thu Jul 17 20:44:23 2025 +0500
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Jul 18 09:27:20 2025 +0200

    tdf#167538, tdf#167540: better check if the line is dummy
    
    The check in SwTextFrame::PaintExtraData is made the same as in
    SwTextFormatter::CalcRealHeight (except for bNewLine, which was
    specific to the latter). It allows to recognize real dummy lines
    uniformly, and produce line numbering for trailing empty lines.
    
    Change-Id: If2bad4b998b0c48f8f79da52a081e6df7565eee3
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188016
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Tested-by: Jenkins
    (cherry picked from commit 5355b3ec3925ddd146a6e3dd6ba28a816b39c66c)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188022
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/qa/extras/layout/data/tdf167540.docx 
b/sw/qa/extras/layout/data/tdf167540.docx
new file mode 100644
index 000000000000..80aba5d98cf8
Binary files /dev/null and b/sw/qa/extras/layout/data/tdf167540.docx differ
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index f6e69743866e..21c022b10b16 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -1848,6 +1848,92 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf167526)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf167540)
+{
+    // Given a document with a paragraph with a hard line break followed by 
nothing (tdf#167538);
+    // and two floating tables with an empty paragraph between them 
(tdf#167540). The strings in
+    // paragraphs all have different lengths, to allow XPath matching:
+    createSwDoc("tdf167540.docx");
+
+    // check line numbering
+    auto verify_me = [this]() {
+        // Dump the rendering of the first page as an XML file.
+        SwDocShell* pShell = getSwDocShell();
+        std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+        MetafileXmlDump dumper;
+        xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+
+        // line 1
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=1][1]/text", u"1");
+        auto ln1_y = getXPath(pXmlDoc, "//textarray[@length=1][1]", "y");
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=4]/text", u"Text
");
+        auto text1_y = getXPath(pXmlDoc, "//textarray[@length=4]", "y");
+
+        // line numbering Y coordinate is the same as its line
+        CPPUNIT_ASSERT_EQUAL(text1_y, ln1_y);
+
+        // line 2 (empty)
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=1][2]/text", u"2");
+        auto ln2_y = getXPath(pXmlDoc, "//textarray[@length=1][2]", "y");
+
+        // line numbering for line 2 is indeed lower than for line 1
+        CPPUNIT_ASSERT_GREATER(ln1_y.toInt32(), ln2_y.toInt32());
+
+        // first floating table
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=20]/text", u"First 
floating table");
+        auto table1_y = getXPath(pXmlDoc, "//textarray[@length=20]", "y");
+
+        // the first floating table is indeed lower than line numbering for 
line 2
+        CPPUNIT_ASSERT_GREATER(ln2_y.toInt32(), table1_y.toInt32());
+
+        // line 3 (empty)
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=1][3]/text", u"3");
+        auto ln3_y = getXPath(pXmlDoc, "//textarray[@length=1][3]", "y");
+
+        // line numbering for line 3 is indeed lower than the first floating 
table
+        CPPUNIT_ASSERT_GREATER(table1_y.toInt32(), ln3_y.toInt32());
+
+        // second floating table
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=21]/text", u"Second 
floating table");
+        auto table2_y = getXPath(pXmlDoc, "//textarray[@length=21]", "y");
+
+        // the second floating table is indeed lower than line numbering for 
line 3
+        CPPUNIT_ASSERT_GREATER(ln3_y.toInt32(), table2_y.toInt32());
+
+        // Inline table
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=14]/text", u"A normal 
table");
+        auto table3_y = getXPath(pXmlDoc, "//textarray[@length=14]", "y");
+
+        // the inline table is indeed lower than second floating table
+        CPPUNIT_ASSERT_GREATER(table2_y.toInt32(), table3_y.toInt32());
+
+        // line 4
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=1][4]/text", u"4");
+        auto ln4_y = getXPath(pXmlDoc, "//textarray[@length=1][4]", "y");
+
+        assertXPathContent(pXmlDoc, "//textarray[@length=9]/text", u"More 
text");
+        auto text4_y = getXPath(pXmlDoc, "//textarray[@length=9]", "y");
+
+        // line numbering Y coordinate is the same as its line
+        CPPUNIT_ASSERT_EQUAL(text4_y, ln4_y);
+    };
+
+    verify_me();
+
+    // DOCX roundtrip:
+    saveAndReload(u"Office Open XML Text"_ustr);
+
+    verify_me();
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx
index 6d116cdbc156..f768d212a62f 100644
--- a/sw/source/core/text/frmpaint.cxx
+++ b/sw/source/core/text/frmpaint.cxx
@@ -361,7 +361,6 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) 
const
         aLayoutModeModifier.Modify( false );
 
         SwTextPainter  aLine( const_cast<SwTextFrame*>(this), &aInf );
-        bool bNoDummy = !aLine.GetNext(); // Only one empty line!
 
         while( aLine.Y() + aLine.GetLineHeight() <= rRect.Top() )
         {
@@ -381,7 +380,13 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) 
const
         const bool bIsShowChangesInMargin = 
pSh->GetViewOptions()->IsShowChangesInMargin();
         do
         {
-            if( bNoDummy || !aLine.GetCurr()->IsDummy() )
+            // A comment from SwTextFormatter::CalcRealHeight:
+            // The dummy flag is set on lines that only contain flyportions. 
Unfortunately an empty
+            // line can be at the end of a paragraph (empty paragraphs or 
behind a Shift-Return).
+            if (!aLine.GetCurr()->IsDummy()
+                || (!aLine.GetCurr()->GetNext()
+                    && aLine.GetStart()
+                           >= 
TextFrameIndex(aLine.GetTextFrame()->GetText().getLength())))
             {
                 bool bRed = bRedLine && aLine.GetCurr()->HasRedline();
                 if( rLineInf.IsCountBlankLines() || 
aLine.GetCurr()->HasContent() )

Reply via email to