sw/CppunitTest_sw_core_layout.mk              |    1 
 sw/qa/core/layout/calcmove.cxx                |   44 ++++++++++++++++++++++++++
 sw/qa/core/layout/data/ignore-top-margin.docx |binary
 sw/source/core/inc/frame.hxx                  |    3 +
 sw/source/core/layout/calcmove.cxx            |   32 ++++++++++++++++++
 sw/source/core/layout/flowfrm.cxx             |    5 ++
 6 files changed, 85 insertions(+)

New commits:
commit 6200d89b905d51776ff4f3c8a84f338655ffaa7f
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue May 7 08:13:37 2024 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue May 7 09:18:06 2024 +0200

    tdf#160952 sw: ignore top margin of para on non-first pages with newer DOCX
    
    The 2nd page of the bugdoc has a single paragraph, with a non-zero top
    margin. This is ignored in Word, but wasn't ignored in Writer.
    
    Experimenting with the document, it looks like old Word files also don't
    ignore this top margin: it started when the compat mode is upgraded
    (from binary DOC or Word 2010) to Word 2023 or newer. Also the top
    margin is only ignored for the first paragraph on the page, and only in
    case it's not on the first page.
    
    Fix the problem by introducing a new SwFrame::IsCollapseUpper() function
    to decide if the upper margin should be collapsed or not, and then by
    using it in SwFlowFrame::CalcUpperSpace() at one place where we read the
    top margin from the doc model. Take advantage of the fact that we have
    related, existing compat flags that tell us if we're in "Word >= 2013"
    compat mode: see e.g. GetFlyAnchorBottom(), which explains
    DocumentSettingId::TAB_OVER_MARGIN is a good indicator that this is a
    "Word <= 2010" document. Also, DocumentSettingId::TAB_OVER_SPACING is an
    indicator that this is a Word document, so we want the "TabOverSpacing
    && !TabOverMargin" case.
    
    This doesn't change all reads of the upper spacing of a text node, but
    is enough to avoid the unwanted top spacing, as demonstrated by the
    bugdoc.
    
    Change-Id: Ibdebdf5f0555256a0b6ed85d07079f14ef69a576
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167252
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/CppunitTest_sw_core_layout.mk b/sw/CppunitTest_sw_core_layout.mk
index 5eb874400d53..d64a43f7d3fb 100644
--- a/sw/CppunitTest_sw_core_layout.mk
+++ b/sw/CppunitTest_sw_core_layout.mk
@@ -14,6 +14,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,sw_core_layout))
 $(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_core_layout))
 
 $(eval $(call gb_CppunitTest_add_exception_objects,sw_core_layout, \
+    sw/qa/core/layout/calcmove \
     sw/qa/core/layout/fly \
     sw/qa/core/layout/flycnt \
     sw/qa/core/layout/frmtool \
diff --git a/sw/qa/core/layout/calcmove.cxx b/sw/qa/core/layout/calcmove.cxx
new file mode 100644
index 000000000000..3e4deec52ae8
--- /dev/null
+++ b/sw/qa/core/layout/calcmove.cxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <swmodeltestbase.hxx>
+
+#include <test/xmldocptr.hxx>
+
+namespace
+{
+/// Covers sw/source/core/layout/calcmove.cxx fixes.
+class Test : public SwModelTestBase
+{
+public:
+    Test()
+        : SwModelTestBase("/sw/qa/core/layout/data/")
+    {
+    }
+};
+
+CPPUNIT_TEST_FIXTURE(Test, testIgnoreTopMargin)
+{
+    // Given a DOCX (>= Word 2013) file, with 2 pages:
+    // When loading that document:
+    createSwDoc("ignore-top-margin.docx");
+
+    // Then make sure that the paragraph on the 2nd page has no top margin:
+    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+    sal_Int32 nParaTopMargin
+        = getXPath(pXmlDoc, "/root/page[2]/body/txt/infos/prtBounds"_ostr, 
"top"_ostr).toInt32();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 0
+    // - Actual  : 2400
+    // i.e. the top margin in the first para of a non-first page wasn't 
ignored, like in Word.
+    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), nParaTopMargin);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/core/layout/data/ignore-top-margin.docx 
b/sw/qa/core/layout/data/ignore-top-margin.docx
new file mode 100644
index 000000000000..d05a1358db1e
Binary files /dev/null and b/sw/qa/core/layout/data/ignore-top-margin.docx 
differ
diff --git a/sw/source/core/inc/frame.hxx b/sw/source/core/inc/frame.hxx
index eac4adff7ce6..f0b7439bd283 100644
--- a/sw/source/core/inc/frame.hxx
+++ b/sw/source/core/inc/frame.hxx
@@ -951,6 +951,9 @@ public:
     virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const;
     void dumpChildrenAsXml(xmlTextWriterPtr writer) const;
     bool IsCollapse() const;
+
+    /// Determines if the upper margin of this frame should be ignored.
+    bool IsCollapseUpper() const;
 };
 
 inline bool SwFrame::IsInDocBody() const
diff --git a/sw/source/core/layout/calcmove.cxx 
b/sw/source/core/layout/calcmove.cxx
index 428c1defe8c2..50dd45579e4b 100644
--- a/sw/source/core/layout/calcmove.cxx
+++ b/sw/source/core/layout/calcmove.cxx
@@ -1076,6 +1076,38 @@ bool SwFrame::IsCollapse() const
     return pTextFrame->GetText().isEmpty() && pTextNode && 
pTextNode->IsCollapse();
 }
 
+bool SwFrame::IsCollapseUpper() const
+{
+    const SwTextFrame* pTextFrame = DynCastTextFrame();
+    if (!pTextFrame)
+    {
+        return false;
+    }
+
+    const SwDoc& rDoc = pTextFrame->GetDoc();
+    const IDocumentSettingAccess& rIDSA = rDoc.getIDocumentSettingAccess();
+    if (!rIDSA.get(DocumentSettingId::TAB_OVER_SPACING) || 
rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN))
+    {
+        // Writer or Word Word <= 2010 style: upper margin is never ignored.
+        return false;
+    }
+
+    // Word >= 2013 style: when we're at the top of the page, but not on the 
first page, then ignore
+    // the upper margin for paragraphs.
+    if (GetPrev())
+    {
+        return false;
+    }
+
+    const SwPageFrame* pPageFrame = FindPageFrame();
+    if (!pPageFrame || !pPageFrame->GetPrev())
+    {
+        return false;
+    }
+
+    return true;
+}
+
 void SwContentFrame::MakePrtArea( const SwBorderAttrs &rAttrs )
 {
     if ( isFramePrintAreaValid() )
diff --git a/sw/source/core/layout/flowfrm.cxx 
b/sw/source/core/layout/flowfrm.cxx
index 88158161c530..37fd20b323d7 100644
--- a/sw/source/core/layout/flowfrm.cxx
+++ b/sw/source/core/layout/flowfrm.cxx
@@ -1658,6 +1658,11 @@ SwTwips SwFlowFrame::CalcUpperSpace( const SwBorderAttrs 
*pAttrs,
                   CastFlowFrame( pOwn )->HasParaSpaceAtPages( 
m_rThis.IsSctFrame() ) )
         {
             nUpper = pAttrs->GetULSpace().GetUpper();
+
+            if (m_rThis.IsCollapseUpper())
+            {
+                nUpper = 0;
+            }
         }
     }
 

Reply via email to