officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs | 6 +++ sw/inc/IDocumentSettingAccess.hxx | 2 + sw/qa/extras/ooxmlexport/ooxmlexport24.cxx | 17 +++++++--- sw/qa/extras/ooxmlexport/ooxmlexport25.cxx | 7 ++++ sw/source/core/doc/DocumentSettingManager.cxx | 13 +++++++ sw/source/core/inc/DocumentSettingManager.hxx | 1 sw/source/core/layout/tabfrm.cxx | 12 +++++-- sw/source/filter/ww8/ww8par.cxx | 3 + sw/source/filter/ww8/ww8scan.cxx | 2 - sw/source/filter/ww8/ww8scan.hxx | 3 + sw/source/uibase/uno/SwXDocumentSettings.cxx | 15 ++++++++ sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 4 ++ sw/source/writerfilter/dmapper/SettingsTable.cxx | 10 +++++ sw/source/writerfilter/dmapper/SettingsTable.hxx | 2 + 14 files changed, 88 insertions(+), 9 deletions(-)
New commits: commit 10b9d5200e382db5affaefc035ebe4d6b35a92fb Author: Justin Luth <jl...@mail.com> AuthorDate: Thu May 22 14:28:33 2025 -0400 Commit: Justin Luth <jl...@mail.com> CommitDate: Wed May 28 11:42:00 2025 +0200 tdf#37153 tdf#165478 sw: add mso-compat flag doNotVertAlignCellWithSp This patch depends on NFC tdf#37153 ww8import: replace 'unknown' with compatibility names The problem was that the cell vertical orientation should not ALWAYS be forced to top when the cell contains an floating shape. This replaces my 6.0 commit 7a9fb40cb07de8c2ea33f92735be5008d30d6704 which incorrectly applied to all MSO documents. All DOC files - yes, but it should only affect DOCX with compat flag doNotVertAlignCellWithSp (Don't vertically align table cells with shapes). It appears that DOC format should ALWAYS be considered as having this flag enabled, even if it is set to OFF (and it IS set to off somehow in the unit tests). At least that is how MS Word 2010 seems to deal with it, and MS Word 2003 (which doesn't detail these layout flags) also works the same way (even for the DOCX - which reinforces all DOC). - open the unit test tdf37153_considerWrapOnObjPos.doc -- see the image/paragraph marker at the top of the cell. - delete the image -- see the paragraph marker drop to the bottom of the cell. make CppunitTest_sw_ww8export2 CPPUNIT_TEST_NAME=testTdf37153 make CppunitTest_sw_ooxmlexport25 \ CPPUNIT_TEST_NAME=testTdf165478_bottomAligned For ooxmlexport24's tdf37153_considerWrapOnObjPos.docx, I probably created that unit test after testing in Word 2003. There it behaved identically to the DOC version, and that becomes no surprise when I now find out that doNotVertAlignCellWithSp is a flag introduced for Word 2007. Since it was testing for an ancient layout - I changed it. Change-Id: Id1ef078a19605532bbb694532cbe7f3cf82fb565 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185695 Reviewed-by: Justin Luth <jl...@mail.com> Tested-by: Jenkins diff --git a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs index b667e24e5172..d22a2c465191 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Compatibility.xcs @@ -188,6 +188,12 @@ </info> <value>false</value> </prop> + <prop oor:name="ForceTopAlignmentInCellWithFloatingAnchor" oor:type="xs:boolean" oor:nillable="false"> + <info> + <desc>Use old Word-compatible layout, forcing cell contents to be top-aligned if the cell contains a floating shape</desc> + </info> + <value>false</value> + </prop> </group> </templates> <component> diff --git a/sw/inc/IDocumentSettingAccess.hxx b/sw/inc/IDocumentSettingAccess.hxx index f523a51af65d..902f23b72a87 100644 --- a/sw/inc/IDocumentSettingAccess.hxx +++ b/sw/inc/IDocumentSettingAccess.hxx @@ -145,6 +145,8 @@ enum class DocumentSettingId MS_WORD_UL_TRAIL_SPACE, // tdf#88908 optionally adjust normal spaces in CJK context to halfwidth BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, + // tdf#37153 Word-compatibility: force top-alignment for cells containing a floating shape + FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR, }; /** Provides access to settings of a document diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport24.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport24.cxx index cacc795e54a5..bfcd33955090 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport24.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport24.cxx @@ -82,6 +82,14 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf99227) DECLARE_OOXMLEXPORT_TEST(testTdf37153, "tdf37153_considerWrapOnObjPos.docx") { + // NOTE: this is now known to have nothing to do with considerWrapOnObjPos... + + // Given a Word 2007 file WITHOUT compat flag doNotVertAlignCellWithSp + // the fly should be at the bottom of the cell (since the cell calls for bottom-align). + // Because the fly is layout-in-cell (although it is also wrap-through), + // it SHOULD try to stay within the cell (although it can escape if necessary). + // TODO: Since it is not necessary to escape, it should push the paragraph marker upwards... + CPPUNIT_ASSERT_EQUAL(text::WrapTextMode_THROUGH, getProperty<text::WrapTextMode>(getShape(1), u"Surround"_ustr)); @@ -93,12 +101,11 @@ DECLARE_OOXMLEXPORT_TEST(testTdf37153, "tdf37153_considerWrapOnObjPos.docx") text::VertOrientation::BOTTOM, getProperty<sal_Int16>(xTable->getCellByName(u"A1"_ustr), u"VertOrient"_ustr)); - //For MSO compatibility, the textbox should be at the top of the cell, not at the bottom - despite VertOrientation::BOTTOM xmlDocUniquePtr pXmlDoc = parseLayoutDump(); - sal_Int32 nFlyTop - = getXPath(pXmlDoc, "/root/page/body/tab/row/cell[1]/txt/anchored/fly/infos/bounds", "top") - .toInt32(); - CPPUNIT_ASSERT_MESSAGE("FlyTop should be 2865, not 5649", nFlyTop < sal_Int32(3000)); + // For MSO compatibility, the textbox should be at the bottom of the cell, not below the cell + // sal_Int32 nFlyBottom = getXPath(pXmlDoc, "//cell[1]//fly/infos/bounds", "bottom").toInt32(); + // CPPUNIT_ASSERT_MESSAGE("FlyBottom should be ~ 5810, not 6331", sal_Int32(5810), nFlyBottom); + sal_Int32 nTextTop = getXPath(pXmlDoc, "/root/page/body/tab/row/cell[2]/txt[1]/infos/bounds", "top").toInt32(); CPPUNIT_ASSERT_MESSAGE("TextTop should be 3856", nTextTop > 3000); diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx index 88769ef361ac..65e778abe675 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx @@ -67,6 +67,13 @@ DECLARE_OOXMLEXPORT_TEST(testTdf165478_bottomAligned, "tdf165478_bottomAligned.d // Without the fix, the text was at the top of the cell (2002) instead of at the bottom (4423) CPPUNIT_ASSERT_EQUAL(nCellBottom, nTextBottom); + + // The image is inside of the table + sal_Int32 nFlyTop = getXPath(pXmlDoc, "//cell[2]//fly/infos/bounds", "top").toInt32(); + sal_Int32 nCellTop = getXPath(pXmlDoc, "//cell[2]/infos/bounds", "top").toInt32(); + // Without the fix, the image was above the table (284) instead of inside the cell (1887) + CPPUNIT_ASSERT_GREATER(nCellTop, nFlyTop); // image is below the cell top + CPPUNIT_ASSERT_EQUAL(sal_Int32(1887), nFlyTop); } CPPUNIT_TEST_FIXTURE(Test, testTdf166620) diff --git a/sw/source/core/doc/DocumentSettingManager.cxx b/sw/source/core/doc/DocumentSettingManager.cxx index 7943810e8fc9..bd2dba7f7454 100644 --- a/sw/source/core/doc/DocumentSettingManager.cxx +++ b/sw/source/core/doc/DocumentSettingManager.cxx @@ -284,6 +284,8 @@ bool sw::DocumentSettingManager::get(/*[in]*/ DocumentSettingId id) const case DocumentSettingId::MS_WORD_UL_TRAIL_SPACE: return mbMsWordUlTrailSpace; case DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES: return mbBalanceSpacesAndIdeographicSpaces; + case DocumentSettingId::FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR: + return mbForceTopAlignmentInCellWithFloatingAnchor; default: OSL_FAIL("Invalid setting id"); } @@ -621,6 +623,9 @@ void sw::DocumentSettingManager::set(/*[in]*/ DocumentSettingId id, /*[in]*/ boo case DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES: mbBalanceSpacesAndIdeographicSpaces = value; break; + case DocumentSettingId::FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR: + mbForceTopAlignmentInCellWithFloatingAnchor = value; + break; default: OSL_FAIL("Invalid setting id"); } @@ -804,6 +809,7 @@ void sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSetti mbNoClippingWithWrapPolygon = rSource.mbNoClippingWithWrapPolygon; mbMsWordUlTrailSpace = rSource.mbMsWordUlTrailSpace; mbBalanceSpacesAndIdeographicSpaces = rSource.mbBalanceSpacesAndIdeographicSpaces; + mbForceTopAlignmentInCellWithFloatingAnchor = rSource.mbForceTopAlignmentInCellWithFloatingAnchor; } sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions1() const @@ -1211,6 +1217,13 @@ void sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const BAD_CAST(OString::boolean(mbBalanceSpacesAndIdeographicSpaces).getStr())); (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, + BAD_CAST("mbForceTopAlignmentInCellWithFloatingAnchor")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbForceTopAlignmentInCellWithFloatingAnchor).getStr())); + (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterEndElement(pWriter); } diff --git a/sw/source/core/inc/DocumentSettingManager.hxx b/sw/source/core/inc/DocumentSettingManager.hxx index e91d0a095528..bb2d9ddf83a9 100644 --- a/sw/source/core/inc/DocumentSettingManager.hxx +++ b/sw/source/core/inc/DocumentSettingManager.hxx @@ -192,6 +192,7 @@ class DocumentSettingManager final : bool mbNoClippingWithWrapPolygon : 1; // tdf#161233 bool mbMsWordUlTrailSpace : 1 = false; bool mbBalanceSpacesAndIdeographicSpaces : 1 = false; // tdf#88908 + bool mbForceTopAlignmentInCellWithFloatingAnchor : 1 = false; // tdf#37153 public: diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index b7632cbab074..5d4cdd0e2503 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -6186,7 +6186,6 @@ void SwCellFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorder // #i43913# - no vertical alignment, if wrapping // style influence is considered on object positioning and // an object is anchored inside the cell. - const bool bConsiderWrapOnObjPos( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ); // No alignment if fly with wrap overlaps the cell. if ( pPg->GetSortedObjs() ) { @@ -6198,8 +6197,17 @@ void SwCellFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorder { const SwFrameFormat* pAnchoredObjFrameFormat = pAnchoredObj->GetFrameFormat(); const SwFormatSurround &rSur = pAnchoredObjFrameFormat->GetSurround(); + const IDocumentSettingAccess& rIDSA = GetFormat()->getIDocumentSettingAccess(); - if ( bConsiderWrapOnObjPos || css::text::WrapTextMode_THROUGH != rSur.GetSurround() ) + // Note: I think that technically, bConsiderWrapOnObjPos should not apply here. + // However, Word's UI strongly encourages vertical offsets that match + // the layout that results from this use of bConsiderWrapOnObjPos. tdf#166710 + const bool bConsiderWrapOnObjPos + = rIDSA.get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION); + const bool bForceTopVAlign = bConsiderWrapOnObjPos && rIDSA.get( + DocumentSettingId::FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR); + + if (bForceTopVAlign || css::text::WrapTextMode_THROUGH != rSur.GetSurround()) { // frames, which the cell is a lower of, aren't relevant if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) diff --git a/sw/source/filter/ww8/ww8par.cxx b/sw/source/filter/ww8/ww8par.cxx index fb9f4d3f7d85..d60a3418f86f 100644 --- a/sw/source/filter/ww8/ww8par.cxx +++ b/sw/source/filter/ww8/ww8par.cxx @@ -1976,6 +1976,9 @@ void SwWW8ImplReader::ImportDop() rIDSA.set(DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES, true); } + if (m_xWDop->fDontVertAlignCellWithSp) + rIDSA.set(DocumentSettingId::FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR, true); + // COMPATIBILITY FLAGS END // Import magic doptypography information, if it's there diff --git a/sw/source/filter/ww8/ww8scan.cxx b/sw/source/filter/ww8/ww8scan.cxx index 61c9b53fd5f9..477ed7180bd2 100644 --- a/sw/source/filter/ww8/ww8scan.cxx +++ b/sw/source/filter/ww8/ww8scan.cxx @@ -7992,7 +7992,7 @@ void WW8Dop::SetCompatibilityOptions2(sal_uInt32 a32Bit) fUnderlineTabInNumList = ( a32Bit & 0x02000800 ) >> 25 ; fHangulWidthLikeWW11 = ( a32Bit & 0x04000800 ) >> 26 ; fSplitPgBreakAndParaMark = ( a32Bit & 0x08000800 ) >> 27 ; - fDontVertAlignCellWithSp = ( a32Bit & 0x10000800 ) >> 28 ; + fDontVertAlignCellWithSp = true; // always true = ( a32Bit & 0x10000800 ) >> 28 ; fDontBreakConstrainedForcedTables = ( a32Bit & 0x20000800 ) >> 29 ; fDontVertAlignInTxbx = ( a32Bit & 0x40000800 ) >> 30 ; fWord11KerningPairs = ( a32Bit & 0x80000000 ) >> 31 ; diff --git a/sw/source/filter/ww8/ww8scan.hxx b/sw/source/filter/ww8/ww8scan.hxx index 8e7099f54da6..0df40f268032 100644 --- a/sw/source/filter/ww8/ww8scan.hxx +++ b/sw/source/filter/ww8/ww8scan.hxx @@ -1812,7 +1812,8 @@ public: bool fUnderlineTabInNumList : 1 = false; bool fHangulWidthLikeWW11 : 1 = false; bool fSplitPgBreakAndParaMark : 1 = false; - bool fDontVertAlignCellWithSp : 1 = false; + /// Don't vertically align table cells with shapes + bool fDontVertAlignCellWithSp : 1 = true; // tdf#37153 bool fDontBreakConstrainedForcedTables : 1 = false; bool fDontVertAlignInTxbx : 1 = false; bool fWord11KerningPairs : 1 = false; diff --git a/sw/source/uibase/uno/SwXDocumentSettings.cxx b/sw/source/uibase/uno/SwXDocumentSettings.cxx index 62fb62380f7d..89cdb980007c 100644 --- a/sw/source/uibase/uno/SwXDocumentSettings.cxx +++ b/sw/source/uibase/uno/SwXDocumentSettings.cxx @@ -169,6 +169,7 @@ enum SwDocumentSettingsPropertyHandles HANDLE_NO_CLIPPING_WITH_WRAP_POLYGON, HANDLE_MS_WORD_UL_TRAIL_SPACE, HANDLE_BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, + HANDLE_FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR, }; } @@ -284,6 +285,7 @@ static rtl::Reference<MasterPropertySetInfo> lcl_createSettingsInfo() { u"NoClippingWithWrapPolygon"_ustr, HANDLE_NO_CLIPPING_WITH_WRAP_POLYGON, cppu::UnoType<bool>::get(), 0 }, { u"MsWordUlTrailSpace"_ustr, HANDLE_MS_WORD_UL_TRAIL_SPACE, cppu::UnoType<bool>::get(), 0 }, { u"BalanceSpacesAndIdeographicSpaces"_ustr, HANDLE_BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, cppu::UnoType<bool>::get(), 0 }, + { u"ForceTopAlignmentInCellWithFloatingAnchor"_ustr, HANDLE_FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR, cppu::UnoType<bool>::get(), 0 }, /* * As OS said, we don't have a view when we need to set this, so I have to @@ -1231,6 +1233,13 @@ void SwXDocumentSettings::_setSingleValue( const comphelper::PropertyInfo & rInf DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES, bTmp); } break; + case HANDLE_FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR: + if (bool bTmp; rValue >>= bTmp) + { + mpDoc->getIDocumentSettingAccess().set( + DocumentSettingId::FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR, bTmp); + } + break; default: throw UnknownPropertyException(OUString::number(rInfo.mnHandle)); } @@ -1854,6 +1863,12 @@ void SwXDocumentSettings::_getSingleValue( const comphelper::PropertyInfo & rInf DocumentSettingId::BALANCE_SPACES_AND_IDEOGRAPHIC_SPACES); } break; + case HANDLE_FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR: + { + rValue <<= mpDoc->getIDocumentSettingAccess().get( + DocumentSettingId::FORCE_TOP_ALIGNMENT_IN_CELL_WITH_FLOATING_ANCHOR); + } + break; default: throw UnknownPropertyException(OUString::number(rInfo.mnHandle)); } diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index a903bbaa0a86..6b2476e666e7 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -9997,6 +9997,10 @@ void DomainMapper_Impl::ApplySettingsTable() xSettings->setPropertyValue(u"ModifyPasswordInfo"_ustr, uno::Any(aWriteProtection)); if (m_pSettingsTable->GetMsWordUlTrailSpace()) xSettings->setPropertyValue(u"MsWordUlTrailSpace"_ustr, uno::Any(true)); + if (m_pSettingsTable->GetDoNotVertAlignCellWithSp()) + xSettings->setPropertyValue(u"ForceTopAlignmentInCellWithFloatingAnchor"_ustr, + uno::Any(true)); + } catch(const uno::Exception&) { diff --git a/sw/source/writerfilter/dmapper/SettingsTable.cxx b/sw/source/writerfilter/dmapper/SettingsTable.cxx index 9983efa22871..fcd24b6d53f8 100644 --- a/sw/source/writerfilter/dmapper/SettingsTable.cxx +++ b/sw/source/writerfilter/dmapper/SettingsTable.cxx @@ -128,6 +128,8 @@ struct SettingsTable_Impl bool m_bAllowTextAfterFloatingTableBreak = false; /// Endnotes at section end, not at document end. bool m_bEndnoteIsCollectAtSectionEnd = false; + /// Don't vertically align table cells containing shapes + bool m_bDoNotVertAlignCellWithSp = false; // tdf#37153 SettingsTable_Impl() : m_nDefaultTabStop( 720 ) //default is 1/2 in @@ -465,6 +467,9 @@ void SettingsTable::lcl_sprm(Sprm& rSprm) case NS_ooxml::LN_CT_Compat_ulTrailSpace: m_pImpl->m_bMsWordUlTrailSpace = true; break; + case NS_ooxml::LN_CT_Compat_doNotVertAlignCellWithSp: + m_pImpl->m_bDoNotVertAlignCellWithSp = nIntValue != 0; + break; default: { #ifdef DBG_UTIL @@ -864,6 +869,11 @@ bool SettingsTable::GetEndnoteIsCollectAtSectionEnd() const return m_pImpl->m_bEndnoteIsCollectAtSectionEnd; } +bool SettingsTable::GetDoNotVertAlignCellWithSp() const +{ + return m_pImpl->m_bDoNotVertAlignCellWithSp; +} + }//namespace dmapper } //namespace writerfilter diff --git a/sw/source/writerfilter/dmapper/SettingsTable.hxx b/sw/source/writerfilter/dmapper/SettingsTable.hxx index 4df8f30e258e..76db5aa85dc5 100644 --- a/sw/source/writerfilter/dmapper/SettingsTable.hxx +++ b/sw/source/writerfilter/dmapper/SettingsTable.hxx @@ -108,6 +108,8 @@ public: bool GetEndnoteIsCollectAtSectionEnd() const; + bool GetDoNotVertAlignCellWithSp() const; + private: // Properties virtual void lcl_attribute(Id Name, const Value& val) override;