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 {