From 5d1b2baaa96fce2cc283e4d383bcfca70651b2da Mon Sep 17 00:00:00 2001 From: Jonathan Clark Date: Tue, 21 Oct 2025 02:14:26 -0600 Subject: [PATCH] tdf#118350 [API CHANGE] Implement start and end paragraph alignment Implements support for ODF fo:text-align start and end in paragraph styles. The new alignment options can be selected in the paragraph dialog. Further user interface changes are tracked as separate bugs. This change also adds START and END to css.style.ParagraphAdjust, which is flagged as an incompatible UNO API change. Change-Id: I2ee57f0029de7b794bc1ecd3eddbbd90af5dfdb9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192932 Tested-by: Jenkins Reviewed-by: Jonathan Clark --- cui/source/inc/paragrph.hxx | 2 + cui/source/tabpages/paragrph.cxx | 100 ++++++--- cui/uiconfig/ui/paragalignpage.ui | 49 ++++- editeng/source/editeng/impedit2.cxx | 25 ++- editeng/source/items/paraitem.cxx | 10 +- editeng/source/uno/unonrule.cxx | 2 + include/editeng/adjustitem.hxx | 8 + include/editeng/editrids.hrc | 2 + include/editeng/svxenum.hxx | 2 + include/xmloff/xmlimp.hxx | 3 + offapi/com/sun/star/style/ParagraphAdjust.idl | 13 +- offapi/type_reference/offapi.idl | 4 +- .../data/tdf118350-start-end-preserved.fodt | 150 ++++++++++++++ sw/qa/extras/odfexport/odfexport4.cxx | 31 ++- sw/qa/extras/uiwriter/data/tdf143176.fodt | 2 +- sw/source/core/text/itrcrsr.cxx | 26 ++- sw/source/core/unocore/unosett.cxx | 2 + .../data/tdf118350-start-end-para-align.fodt | 192 ++++++++++++++++++ vcl/qa/cppunit/pdfexport/pdfexport2.cxx | 91 +++++++++ xmloff/qa/unit/uxmloff.cxx | 5 +- xmloff/source/core/xmlimp.cxx | 12 ++ xmloff/source/style/adjushdl.cxx | 4 +- xmloff/source/style/xmlimppr.cxx | 29 +++ 23 files changed, 705 insertions(+), 59 deletions(-) create mode 100644 sw/qa/extras/odfexport/data/tdf118350-start-end-preserved.fodt create mode 100644 vcl/qa/cppunit/pdfexport/data/tdf118350-start-end-para-align.fodt diff --git a/cui/source/inc/paragrph.hxx b/cui/source/inc/paragrph.hxx index 856daffcf776..ff6482526ed3 100644 --- a/cui/source/inc/paragrph.hxx +++ b/cui/source/inc/paragrph.hxx @@ -128,7 +128,9 @@ class SvxParaAlignTabPage : public SfxTabPage // alignment std::unique_ptr m_xLeft; + std::unique_ptr m_xStart; std::unique_ptr m_xRight; + std::unique_ptr m_xEnd; std::unique_ptr m_xCenter; std::unique_ptr m_xJustify; std::unique_ptr m_xLeftBottom; diff --git a/cui/source/tabpages/paragrph.cxx b/cui/source/tabpages/paragrph.cxx index 3d285f514e84..978c4811b8cf 100644 --- a/cui/source/tabpages/paragrph.cxx +++ b/cui/source/tabpages/paragrph.cxx @@ -1265,7 +1265,9 @@ SvxParaAlignTabPage::SvxParaAlignTabPage(weld::Container* pPage, weld::DialogCon : SfxTabPage(pPage, pController, u"cui/ui/paragalignpage.ui"_ustr, u"ParaAlignPage"_ustr, &rSet) , m_bSdrVertAlign(false) , m_xLeft(m_xBuilder->weld_radio_button(u"radioBTN_LEFTALIGN"_ustr)) + , m_xStart(m_xBuilder->weld_radio_button(u"radioBTN_STARTALIGN"_ustr)) , m_xRight(m_xBuilder->weld_radio_button(u"radioBTN_RIGHTALIGN"_ustr)) + , m_xEnd(m_xBuilder->weld_radio_button(u"radioBTN_ENDALIGN"_ustr)) , m_xCenter(m_xBuilder->weld_radio_button(u"radioBTN_CENTERALIGN"_ustr)) , m_xJustify(m_xBuilder->weld_radio_button(u"radioBTN_JUSTIFYALIGN"_ustr)) , m_xLeftBottom(m_xBuilder->weld_label(u"labelST_LEFTALIGN_ASIAN"_ustr)) @@ -1321,7 +1323,9 @@ SvxParaAlignTabPage::SvxParaAlignTabPage(weld::Container* pPage, weld::DialogCon Link aLink = LINK( this, SvxParaAlignTabPage, AlignHdl_Impl ); m_xLeft->connect_toggled(aLink); + m_xStart->connect_toggled(aLink); m_xRight->connect_toggled(aLink); + m_xEnd->connect_toggled(aLink); m_xCenter->connect_toggled(aLink); m_xJustify->connect_toggled(aLink); m_xLastLineLB->connect_changed(LINK(this, SvxParaAlignTabPage, LastLineHdl_Impl)); @@ -1366,11 +1370,21 @@ bool SvxParaAlignTabPage::FillItemSet( SfxItemSet* rOutSet ) eAdjust = SvxAdjust::Left; bAdj = m_xLeft->get_saved_state() == TRISTATE_FALSE; } + else if (m_xStart->get_active()) + { + eAdjust = SvxAdjust::ParaStart; + bAdj = m_xStart->get_saved_state() == TRISTATE_FALSE; + } else if (m_xRight->get_active()) { eAdjust = SvxAdjust::Right; bAdj = m_xRight->get_saved_state() == TRISTATE_FALSE; } + else if (m_xEnd->get_active()) + { + eAdjust = SvxAdjust::ParaEnd; + bAdj = m_xEnd->get_saved_state() == TRISTATE_FALSE; + } else if (m_xCenter->get_active()) { eAdjust = SvxAdjust::Center; @@ -1466,8 +1480,12 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) { case SvxAdjust::Left: m_xLeft->set_active(true); break; + case SvxAdjust::ParaStart: m_xStart->set_active(true); break; + case SvxAdjust::Right: m_xRight->set_active(true); break; + case SvxAdjust::ParaEnd: m_xEnd->set_active(true); break; + case SvxAdjust::Center: m_xCenter->set_active(true); break; case SvxAdjust::Block: m_xJustify->set_active(true); break; @@ -1533,7 +1551,9 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) else { m_xLeft->set_active(false); + m_xStart->set_active(false); m_xRight->set_active(false); + m_xEnd->set_active(false); m_xCenter->set_active(false); m_xJustify->set_active(false); m_xLabelWordSpacing->set_sensitive(false); @@ -1601,7 +1621,9 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xSnapToGridCB->save_state(); m_xVertAlignLB->save_value(); m_xLeft->save_state(); + m_xStart->save_state(); m_xRight->save_state(); + m_xEnd->save_state(); m_xCenter->save_state(); m_xJustify->save_state(); m_xLastLineLB->save_value(); @@ -1623,7 +1645,9 @@ void SvxParaAlignTabPage::ChangesApplied() m_xSnapToGridCB->save_state(); m_xVertAlignLB->save_value(); m_xLeft->save_state(); + m_xStart->save_state(); m_xRight->save_state(); + m_xEnd->save_state(); m_xCenter->save_state(); m_xJustify->save_state(); m_xLastLineLB->save_value(); @@ -1714,50 +1738,64 @@ IMPL_LINK_NOARG(SvxParaAlignTabPage, WordSpacingMaximumHdl_Impl, weld::MetricSpi void SvxParaAlignTabPage::UpdateExample_Impl() { + bool bParaIsRtl = [&] + { + SvxFrameDirection eDir = m_xTextDirectionLB->get_active_id(); + switch (eDir) + { + case SvxFrameDirection::Environment: + return AllSettings::GetLayoutRTL(); + + case SvxFrameDirection::Horizontal_RL_TB: + return true; + + default: + case SvxFrameDirection::Horizontal_LR_TB: + return false; + } + }(); + if (m_xLeft->get_active()) { m_aExampleWin.EnableRTL(false); m_aExampleWin.SetAdjust(SvxAdjust::Left); m_aExampleWin.SetLastLine(SvxAdjust::Left); } + else if (m_xStart->get_active()) + { + m_aExampleWin.EnableRTL(bParaIsRtl); + m_aExampleWin.SetAdjust(SvxAdjust::Left); + m_aExampleWin.SetLastLine(SvxAdjust::Left); + } else if (m_xRight->get_active()) { m_aExampleWin.EnableRTL(true); m_aExampleWin.SetAdjust(SvxAdjust::Left); m_aExampleWin.SetLastLine(SvxAdjust::Left); } - else + else if (m_xEnd->get_active()) { - SvxFrameDirection eDir = m_xTextDirectionLB->get_active_id(); - switch ( eDir ) - { - case SvxFrameDirection::Environment : - if ( !m_xRight->get_active() ) - m_aExampleWin.EnableRTL( AllSettings::GetLayoutRTL() ); - break; - case SvxFrameDirection::Horizontal_RL_TB : - if ( !m_xLeft->get_active() ) - m_aExampleWin.EnableRTL( true ); - break; - case SvxFrameDirection::Horizontal_LR_TB : - if ( !m_xRight->get_active() ) - m_aExampleWin.EnableRTL( false ); - break; - default: ; //prevent warning - } - if (m_xCenter->get_active()) - m_aExampleWin.SetAdjust( SvxAdjust::Center ); - else if (m_xJustify->get_active()) - { - m_aExampleWin.SetAdjust( SvxAdjust::Block ); - int nLBPos = m_xLastLineLB->get_active(); - if (nLBPos == 0) - m_aExampleWin.SetLastLine(SvxAdjust::Left); - else if (nLBPos == 1) - m_aExampleWin.SetLastLine(SvxAdjust::Center); - else if (nLBPos == 2) - m_aExampleWin.SetLastLine(SvxAdjust::Block); - } + m_aExampleWin.EnableRTL(!bParaIsRtl); + m_aExampleWin.SetAdjust(SvxAdjust::Left); + m_aExampleWin.SetLastLine(SvxAdjust::Left); + } + else if (m_xCenter->get_active()) + { + m_aExampleWin.EnableRTL(bParaIsRtl); + m_aExampleWin.SetAdjust(SvxAdjust::Center); + } + else if (m_xJustify->get_active()) + { + m_aExampleWin.EnableRTL(bParaIsRtl); + m_aExampleWin.SetAdjust(SvxAdjust::Block); + + int nLBPos = m_xLastLineLB->get_active(); + if (nLBPos == 0) + m_aExampleWin.SetLastLine(SvxAdjust::Left); + else if (nLBPos == 1) + m_aExampleWin.SetLastLine(SvxAdjust::Center); + else if (nLBPos == 2) + m_aExampleWin.SetLastLine(SvxAdjust::Block); } m_aExampleWin.Invalidate(); diff --git a/cui/uiconfig/ui/paragalignpage.ui b/cui/uiconfig/ui/paragalignpage.ui index 92824780cf41..58da274533f8 100644 --- a/cui/uiconfig/ui/paragalignpage.ui +++ b/cui/uiconfig/ui/paragalignpage.ui @@ -110,7 +110,7 @@ 0 none - + True False @@ -174,7 +174,7 @@ 0 - 3 + 5 @@ -189,7 +189,7 @@ 0 - 5 + 7 @@ -204,7 +204,7 @@ 0 - 6 + 8 @@ -250,7 +250,7 @@ 0 - 7 + 9 @@ -293,11 +293,41 @@ + + 0 + 6 + + + + + _End + True + True + False + True + True + radioBTN_LEFTALIGN + 0 4 + + + S_tart + True + True + False + True + True + radioBTN_LEFTALIGN + + + 0 + 3 + + @@ -445,7 +475,7 @@ 0 none - + True False @@ -657,7 +687,12 @@ 3 - + + + + + + diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx index 5a617b0e6146..c72239dedc11 100644 --- a/editeng/source/editeng/impedit2.cxx +++ b/editeng/source/editeng/impedit2.cxx @@ -2070,12 +2070,29 @@ SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const { eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust(); - if ( IsRightToLeft( nPara ) ) + // Convert paragraph adjustment into an internal start/end value. + switch (eJustification) { - if ( eJustification == SvxAdjust::Left ) - eJustification = SvxAdjust::Right; - else if ( eJustification == SvxAdjust::Right ) + case SvxAdjust::ParaStart: + // Internally, the paragraph start is always the left margin: eJustification = SvxAdjust::Left; + break; + + case SvxAdjust::ParaEnd: + // Internally, the paragraph end is always the right margin: + eJustification = SvxAdjust::Right; + break; + + case SvxAdjust::Left: + eJustification = IsRightToLeft(nPara) ? SvxAdjust::Right : SvxAdjust::Left; + break; + + case SvxAdjust::Right: + eJustification = IsRightToLeft(nPara) ? SvxAdjust::Left : SvxAdjust::Right; + break; + + default: + break; } } return eJustification; diff --git a/editeng/source/items/paraitem.cxx b/editeng/source/items/paraitem.cxx index 58fd5be3cc60..9c16c3f78dca 100644 --- a/editeng/source/items/paraitem.cxx +++ b/editeng/source/items/paraitem.cxx @@ -409,7 +409,7 @@ bool SvxAdjustItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) { sal_Int32 eVal = - 1; ::cppu::enum2int(eVal,rVal); - if(eVal >= 0 && eVal <= 4) + if(eVal >= 0 && eVal <= 6) { SvxAdjust eAdjust = static_cast(eVal); if(MID_LAST_LINE_ADJUST == nMemberId && @@ -512,10 +512,12 @@ OUString SvxAdjustItem::GetValueTextByPos( sal_uInt16 nPos ) RID_SVXITEMS_ADJUST_RIGHT, RID_SVXITEMS_ADJUST_BLOCK, RID_SVXITEMS_ADJUST_CENTER, - RID_SVXITEMS_ADJUST_BLOCKLINE + RID_SVXITEMS_ADJUST_BLOCKLINE, + RID_SVXITEMS_ADJUST_PARASTART, + RID_SVXITEMS_ADJUST_PARAEND }; - static_assert(std::size(RID_SVXITEMS_ADJUST) - 1 == static_cast(SvxAdjust::BlockLine), "unexpected size"); - assert(nPos <= sal_uInt16(SvxAdjust::BlockLine) && "enum overflow!"); + static_assert(std::size(RID_SVXITEMS_ADJUST) - 1 == static_cast(SvxAdjust::ParaEnd), "unexpected size"); + assert(nPos <= sal_uInt16(SvxAdjust::ParaEnd) && "enum overflow!"); return EditResId(RID_SVXITEMS_ADJUST[nPos]); } diff --git a/editeng/source/uno/unonrule.cxx b/editeng/source/uno/unonrule.cxx index c3ba8276551c..a508d6a7544c 100644 --- a/editeng/source/uno/unonrule.cxx +++ b/editeng/source/uno/unonrule.cxx @@ -72,6 +72,8 @@ const o3tl::enumarray aSvxToUnoAdjust text::HoriOrientation::FULL, text::HoriOrientation::CENTER, text::HoriOrientation::FULL, + text::HoriOrientation::LEFT, + text::HoriOrientation::RIGHT, text::HoriOrientation::LEFT }; diff --git a/include/editeng/adjustitem.hxx b/include/editeng/adjustitem.hxx index c1acd36a4113..0a57417bc073 100644 --- a/include/editeng/adjustitem.hxx +++ b/include/editeng/adjustitem.hxx @@ -37,6 +37,8 @@ class EDITENG_DLLPUBLIC SvxAdjustItem final : public SfxPoolItem bool bRight : 1; bool bCenter : 1; bool bBlock : 1; + bool bStart : 1; + bool bEnd : 1; // only active when bBlock bool bOneBlock : 1; @@ -103,6 +105,8 @@ public: bRight = eType == SvxAdjust::Right; bCenter = eType == SvxAdjust::Center; bBlock = eType == SvxAdjust::Block; + bStart = eType == SvxAdjust::ParaStart; + bEnd = eType == SvxAdjust::ParaEnd; } SvxAdjust GetLastBlock() const @@ -135,6 +139,10 @@ public: eRet = SvxAdjust::Center; else if ( bBlock ) eRet = SvxAdjust::Block; + else if ( bStart ) + eRet = SvxAdjust::ParaStart; + else if ( bEnd ) + eRet = SvxAdjust::ParaEnd; return eRet; } diff --git a/include/editeng/editrids.hrc b/include/editeng/editrids.hrc index 23c8ec932fd0..fa82e0d90f33 100644 --- a/include/editeng/editrids.hrc +++ b/include/editeng/editrids.hrc @@ -151,6 +151,8 @@ #define RID_SVXITEMS_ADJUST_BLOCK NC_("RID_SVXITEMS_ADJUST_BLOCK", "Justify") #define RID_SVXITEMS_ADJUST_CENTER NC_("RID_SVXITEMS_ADJUST_CENTER", "Centered") #define RID_SVXITEMS_ADJUST_BLOCKLINE NC_("RID_SVXITEMS_ADJUST_BLOCKLINE", "Justify") +#define RID_SVXITEMS_ADJUST_PARASTART NC_("RID_SVXITEMS_ADJUST_PARASTART", "Start") +#define RID_SVXITEMS_ADJUST_PARAEND NC_("RID_SVXITEMS_ADJUST_PARAEND", "End") #define RID_SOLID NC_("RID_SOLID", "Single, solid") #define RID_DOTTED NC_("RID_DOTTED", "Single, dotted") #define RID_DASHED NC_("RID_DASHED", "Single, dashed") diff --git a/include/editeng/svxenum.hxx b/include/editeng/svxenum.hxx index ba77a2a3c65a..84362e18c975 100644 --- a/include/editeng/svxenum.hxx +++ b/include/editeng/svxenum.hxx @@ -83,6 +83,8 @@ enum class SvxAdjust Block, Center, BlockLine, + ParaStart, + ParaEnd, End, LAST = End }; diff --git a/include/xmloff/xmlimp.hxx b/include/xmloff/xmlimp.hxx index 352c61224f74..2a0660272250 100644 --- a/include/xmloff/xmlimp.hxx +++ b/include/xmloff/xmlimp.hxx @@ -555,6 +555,9 @@ public: static const sal_uInt16 LO_76 = 76 | LO_flag; static const sal_uInt16 LO_242 = 80 | LO_flag; static const sal_uInt16 LO_248 = 81 | LO_flag; + static const sal_uInt16 LO_252 = 82 | LO_flag; + static const sal_uInt16 LO_258 = 83 | LO_flag; + static const sal_uInt16 LO_262 = 84 | LO_flag; static const sal_uInt16 LO_New = 100 | LO_flag; static const sal_uInt16 ProductVersionUnknown = SAL_MAX_UINT16; diff --git a/offapi/com/sun/star/style/ParagraphAdjust.idl b/offapi/com/sun/star/style/ParagraphAdjust.idl index 367202bfa87a..248eec51dca3 100644 --- a/offapi/com/sun/star/style/ParagraphAdjust.idl +++ b/offapi/com/sun/star/style/ParagraphAdjust.idl @@ -43,8 +43,19 @@ published enum ParagraphAdjust /** adjusted to both borders / stretched, including last line */ - STRETCH + STRETCH, + /** equivalent to LEFT for LTR paragraphs, RIGHT for RTL paragraphs + + @since LibreOffice 26.2 + */ + START, + + /** equivalent to RIGHT for LTR paragraphs, LEFT for RTL paragraphs + + @since LibreOffice 26.2 + */ + END }; diff --git a/offapi/type_reference/offapi.idl b/offapi/type_reference/offapi.idl index 471183f20005..369ca0550221 100644 --- a/offapi/type_reference/offapi.idl +++ b/offapi/type_reference/offapi.idl @@ -3585,7 +3585,9 @@ module com { RIGHT = 1, BLOCK = 2, CENTER = 3, - STRETCH = 4 + STRETCH = 4, + START = 5, + END = 6 }; published enum TabAlign { LEFT = 0, diff --git a/sw/qa/extras/odfexport/data/tdf118350-start-end-preserved.fodt b/sw/qa/extras/odfexport/data/tdf118350-start-end-preserved.fodt new file mode 100644 index 000000000000..2859bf0cddae --- /dev/null +++ b/sw/qa/extras/odfexport/data/tdf118350-start-end-preserved.fodt @@ -0,0 +1,150 @@ + + + 2025-10-22T05:31:47.251184918LibreOfficeDev/26.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/d7e24df067ba6af0043e70750ad2b97e21201fa92025-10-22T05:36:27.933616336PT4M39S3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This paragraph is LTR aligned left. + + This paragraph is LTR aligned start. + + This paragraph is LTR aligned right. + + This paragraph is LTR aligned end. + + This paragraph is RTL aligned left. + + This paragraph is RTL aligned start. + + This paragraph is RTL aligned right. + + This paragraph is RTL aligned end. + + + + diff --git a/sw/qa/extras/odfexport/odfexport4.cxx b/sw/qa/extras/odfexport/odfexport4.cxx index d75937da016c..087e19731391 100644 --- a/sw/qa/extras/odfexport/odfexport4.cxx +++ b/sw/qa/extras/odfexport/odfexport4.cxx @@ -1645,8 +1645,9 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf168980) CPPUNIT_TEST_FIXTURE(Test, testTdf37128ConvertStartEndOnResave) { - // LO doesn't support start/end fo:text-align. These are always loaded as - // left/right, respectively. They should be resaved as left/right, too. + // Older versions of LO always stored fo:text-align left/right as + // start/end, respectively. Make sure documents produced by older + // versions of LO convert these values to left/right. loadAndReload("tdf37128-start-end-to-left-right.fodt"); CPPUNIT_ASSERT_EQUAL(1, getPages()); xmlDocUniquePtr pXmlDoc = parseExport(u"styles.xml"_ustr); @@ -1669,6 +1670,32 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf37128ConvertStartEndOnResave) "text-align", u"right"); } +CPPUNIT_TEST_FIXTURE(Test, testTdf118350StartEndPreserved) +{ + // Check that fo:text-align start/end round-trip correctly for + // documents made by newer versions of LO. + loadAndReload("tdf118350-start-end-preserved.fodt"); + CPPUNIT_ASSERT_EQUAL(1, getPages()); + xmlDocUniquePtr pXmlDoc = parseExport(u"styles.xml"_ustr); + + assertXPath(pXmlDoc, "//style:style[@style:name='LTRLeft']/style:paragraph-properties", + "text-align", u"left"); + assertXPath(pXmlDoc, "//style:style[@style:name='LTRStart']/style:paragraph-properties", + "text-align", u"start"); + assertXPath(pXmlDoc, "//style:style[@style:name='LTRRight']/style:paragraph-properties", + "text-align", u"right"); + assertXPath(pXmlDoc, "//style:style[@style:name='LTREnd']/style:paragraph-properties", + "text-align", u"end"); + assertXPath(pXmlDoc, "//style:style[@style:name='RTLLeft']/style:paragraph-properties", + "text-align", u"left"); + assertXPath(pXmlDoc, "//style:style[@style:name='RTLStart']/style:paragraph-properties", + "text-align", u"start"); + assertXPath(pXmlDoc, "//style:style[@style:name='RTLRight']/style:paragraph-properties", + "text-align", u"right"); + assertXPath(pXmlDoc, "//style:style[@style:name='RTLEnd']/style:paragraph-properties", + "text-align", u"end"); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/uiwriter/data/tdf143176.fodt b/sw/qa/extras/uiwriter/data/tdf143176.fodt index fdd89c38da3c..994f133447d4 100644 --- a/sw/qa/extras/uiwriter/data/tdf143176.fodt +++ b/sw/qa/extras/uiwriter/data/tdf143176.fodt @@ -8,7 +8,7 @@ - + diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx index 1a8c500ef92f..bff160bffba8 100644 --- a/sw/source/core/text/itrcrsr.cxx +++ b/sw/source/core/text/itrcrsr.cxx @@ -348,13 +348,29 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p const SvxAdjustItem& rAdjust = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust(); mnAdjust = rAdjust.GetAdjust(); - // left is left and right is right - if ( m_pFrame->IsRightToLeft() ) + // Convert paragraph adjustment into an internal start/end value. + switch (mnAdjust) { - if ( SvxAdjust::Left == mnAdjust ) - mnAdjust = SvxAdjust::Right; - else if ( SvxAdjust::Right == mnAdjust ) + case SvxAdjust::ParaStart: + // Internally, the paragraph start is always the left margin: mnAdjust = SvxAdjust::Left; + break; + + case SvxAdjust::ParaEnd: + // Internally, the paragraph end is always the right margin: + mnAdjust = SvxAdjust::Right; + break; + + case SvxAdjust::Left: + mnAdjust = m_pFrame->IsRightToLeft() ? SvxAdjust::Right : SvxAdjust::Left; + break; + + case SvxAdjust::Right: + mnAdjust = m_pFrame->IsRightToLeft() ? SvxAdjust::Left : SvxAdjust::Right; + break; + + default: + break; } m_bOneBlock = rAdjust.GetOneWord() == SvxAdjust::Block; diff --git a/sw/source/core/unocore/unosett.cxx b/sw/source/core/unocore/unosett.cxx index cce13036c8d5..cd4d5020f13f 100644 --- a/sw/source/core/unocore/unosett.cxx +++ b/sw/source/core/unocore/unosett.cxx @@ -244,6 +244,8 @@ const o3tl::enumarray aSvxToUnoAdjust sal_Int16(-1), text::HoriOrientation::CENTER, //2 sal_Int16(-1), + sal_Int16(-1), + sal_Int16(-1), sal_Int16(-1) }; diff --git a/vcl/qa/cppunit/pdfexport/data/tdf118350-start-end-para-align.fodt b/vcl/qa/cppunit/pdfexport/data/tdf118350-start-end-para-align.fodt new file mode 100644 index 000000000000..ad0b5e132659 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/tdf118350-start-end-para-align.fodt @@ -0,0 +1,192 @@ + + + 2025-10-23T06:15:16.5370825422025-10-23T06:21:45.657634279PT4M33S5LibreOfficeDev/26.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/faccef0e7b37c40cb2efc276ea5d914f9496665e + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This paragraph is LTR aligned left. + + This paragraph is LTR aligned start. + + This paragraph is LTR aligned right. + + This paragraph is LTR aligned end. + + This paragraph is RTL aligned left. + + This paragraph is RTL aligned start. + + This paragraph is RTL aligned right. + + This paragraph is RTL aligned end. + + + + This paragraph is LTR aligned left. + + This paragraph is LTR aligned start. + + This paragraph is LTR aligned right. + + This paragraph is LTR aligned end. + + This paragraph is RTL aligned left. + + This paragraph is RTL aligned start. + + This paragraph is RTL aligned right. + + This paragraph is RTL aligned end. + + + + + + \ No newline at end of file diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx index b160e67b0ab0..61075c4d3bf9 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx @@ -6348,6 +6348,97 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf166044ContFootnoteOnlyOnePgNum) CPPUNIT_ASSERT_EQUAL(u"2"_ustr, pPgNumObject->getText(pTextPage)); } +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf118350StartEndParaAlign) +{ + saveAsPDF(u"tdf118350-start-end-para-align.fodt"); + + 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(24, nPageObjectCount); + + std::vector aText; + std::vector aRect; + + 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()); + } + } + + CPPUNIT_ASSERT_EQUAL(size_t(24), aText.size()); + + // Lines from the Writer portion + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned left."_ustr, aText.at(0).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(0).getMinX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned start."_ustr, aText.at(1).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(1).getMinX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned right."_ustr, aText.at(2).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(2).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned end."_ustr, aText.at(3).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(3).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned left"_ustr, aText.at(4).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(5).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(5).getMinX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned start"_ustr, aText.at(6).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(7).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(6).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned right"_ustr, aText.at(8).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(9).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(8).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned end"_ustr, aText.at(10).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(11).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(11).getMinX(), /*delta*/ 10.0); + + // Lines from the Edit Engine portion + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned left."_ustr, aText.at(12).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(12).getMinX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned start."_ustr, aText.at(13).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(13).getMinX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned right."_ustr, aText.at(14).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(14).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned end."_ustr, aText.at(15).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(15).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned left"_ustr, aText.at(16).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(17).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(17).getMinX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned start"_ustr, aText.at(18).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(19).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(18).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned right"_ustr, aText.at(20).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(21).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(20).getMaxX(), /*delta*/ 10.0); + + CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned end"_ustr, aText.at(22).trim()); + CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(23).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(23).getMinX(), /*delta*/ 10.0); +} + } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/xmloff/qa/unit/uxmloff.cxx b/xmloff/qa/unit/uxmloff.cxx index cf888baa93fd..7f51e193f284 100644 --- a/xmloff/qa/unit/uxmloff.cxx +++ b/xmloff/qa/unit/uxmloff.cxx @@ -201,7 +201,10 @@ void Test::testMetaGenerator() { "CIB_OfficeDev/6.4.0.19$Linux_X86_64 LibreOffice_project/2e04f804b5f82770435f250873f07b3384d95504", ";6.4.0.19", SvXMLImport::LO_63x }, { "LibreOfficeDev/24.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/b81e7b6f3c71fb3ade1cb665444ac730dac0a9a9", ";24.2.0.0.", SvXMLImport::LO_242 }, { "LibreOfficeDev/24.8.6.2$Linux_X86_64 LibreOffice_project/dc6216c3d2b4ec3bcdba950f1e6ee1d013adb2d6", ";24.8.6.2", SvXMLImport::LO_248 }, - { "LibreOfficeDev/25.8.0.0.alpha0$Linux_X86_64 LibreOffice_project/308705574842e0c36f7385f73fc47da9a4542367", ";25.8.0.0.", SvXMLImport::LO_New } + { "LibreOffice/25.2.6.2$Linux_X86_64 LibreOffice_project/520$Build-2", ";25.2.6.2", SvXMLImport::LO_252 }, + { "LibreOfficeDev/25.8.0.0.alpha0$Linux_X86_64 LibreOffice_project/308705574842e0c36f7385f73fc47da9a4542367", ";25.8.0.0.", SvXMLImport::LO_258 }, + { "LibreOfficeDev/26.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/d7e24df067ba6af0043e70750ad2b97e21201fa9", ";26.2.0.0.", SvXMLImport::LO_262 }, + { "LibreOfficeDev/26.8.0.0.alpha0$Linux_X86_64 LibreOffice_project/d7e24df067ba6af0043e70750ad2b97e21201fa9", ";26.8.0.0.", SvXMLImport::LO_New } }; for (auto const[pGenerator, pBuildId, nResult] : tests) diff --git a/xmloff/source/core/xmlimp.cxx b/xmloff/source/core/xmlimp.cxx index 3e1c26fced76..36aae86dc200 100644 --- a/xmloff/source/core/xmlimp.cxx +++ b/xmloff/source/core/xmlimp.cxx @@ -243,6 +243,18 @@ public: { mnGeneratorVersion = SvXMLImport::LO_248; } + else if (25 == year && month == 2) + { + mnGeneratorVersion = SvXMLImport::LO_252; + } + else if (25 == year && month == 8) + { + mnGeneratorVersion = SvXMLImport::LO_258; + } + else if (26 == year && month == 2) + { + mnGeneratorVersion = SvXMLImport::LO_262; + } else if (0 < year) { mnGeneratorVersion = SvXMLImport::LO_New; diff --git a/xmloff/source/style/adjushdl.cxx b/xmloff/source/style/adjushdl.cxx index 61878c720504..a37243c5e052 100644 --- a/xmloff/source/style/adjushdl.cxx +++ b/xmloff/source/style/adjushdl.cxx @@ -33,8 +33,8 @@ SvXMLEnumMapEntry const pXML_Para_Adjust_Enum[] = { { XML_LEFT, style::ParagraphAdjust_LEFT }, { XML_RIGHT, style::ParagraphAdjust_RIGHT }, - { XML_START, style::ParagraphAdjust_LEFT }, - { XML_END, style::ParagraphAdjust_RIGHT }, + { XML_START, style::ParagraphAdjust_START }, + { XML_END, style::ParagraphAdjust_END }, { XML_CENTER, style::ParagraphAdjust_CENTER }, { XML_JUSTIFY, style::ParagraphAdjust_BLOCK }, { XML_JUSTIFIED, style::ParagraphAdjust_BLOCK }, // obsolete diff --git a/xmloff/source/style/xmlimppr.cxx b/xmloff/source/style/xmlimppr.cxx index ef4b2bb24513..5626da2e3c99 100644 --- a/xmloff/source/style/xmlimppr.cxx +++ b/xmloff/source/style/xmlimppr.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -231,6 +232,34 @@ void SvXMLImportPropertyMapper::importXMLAttribute( // let the XMLPropertySetMapper decide how to import the value bSet = maPropMapper->importXML( sValue, aNewProperty, rUnitConverter ); + + // tdf#37128: Older versions of LO stored fo:text-align incorrectly. + // Remap the buggy values during parsing. + if (bSet + && GetImport().isGeneratorVersionOlderThan(SvXMLImport::AOO_4x, + SvXMLImport::LO_262) + && rAttrName == u"fo:text-align"_ustr) + { + if (sal_Int16 nTmp; aNewProperty.maValue >>= nTmp) + { + css::style::ParagraphAdjust eAdjust{ nTmp }; + switch (eAdjust) + { + case css::style::ParagraphAdjust_START: + eAdjust = css::style::ParagraphAdjust_LEFT; + break; + + case css::style::ParagraphAdjust_END: + eAdjust = css::style::ParagraphAdjust_RIGHT; + break; + + default: + break; + } + + aNewProperty.maValue <<= static_cast(eAdjust); + } + } } else {