From 6ee21829844ca2e97eaf46e570ee60137648b17f315c24aea27995e23b43f8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Thu, 6 Apr 2017 15:12:12 +0000 Subject: [PATCH 1/4] - Version update to 5.3.2.2: * RC2 for the 5.3.2 release OBS-URL: https://build.opensuse.org/package/show/LibreOffice:Factory/libreoffice?expand=0&rev=480 --- libreoffice-5.3.2.1.tar.xz | 3 --- libreoffice-5.3.2.2.tar.xz | 3 +++ libreoffice-help-5.3.2.1.tar.xz | 3 --- libreoffice-help-5.3.2.2.tar.xz | 3 +++ libreoffice-translations-5.3.2.1.tar.xz | 3 --- libreoffice-translations-5.3.2.2.tar.xz | 3 +++ libreoffice.changes | 6 ++++++ libreoffice.spec | 2 +- 8 files changed, 16 insertions(+), 10 deletions(-) delete mode 100644 libreoffice-5.3.2.1.tar.xz create mode 100644 libreoffice-5.3.2.2.tar.xz delete mode 100644 libreoffice-help-5.3.2.1.tar.xz create mode 100644 libreoffice-help-5.3.2.2.tar.xz delete mode 100644 libreoffice-translations-5.3.2.1.tar.xz create mode 100644 libreoffice-translations-5.3.2.2.tar.xz diff --git a/libreoffice-5.3.2.1.tar.xz b/libreoffice-5.3.2.1.tar.xz deleted file mode 100644 index c85e537..0000000 --- a/libreoffice-5.3.2.1.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07052dccc52e427c2898bc5768c7f55d181aa0f79ffcd827dc8dd1f17e844e55 -size 189746408 diff --git a/libreoffice-5.3.2.2.tar.xz b/libreoffice-5.3.2.2.tar.xz new file mode 100644 index 0000000..530e991 --- /dev/null +++ b/libreoffice-5.3.2.2.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8811a95eb9298cc89603bccd22bcf7196dcef8543b5e66dae5aa47263a0f9ead +size 189737884 diff --git a/libreoffice-help-5.3.2.1.tar.xz b/libreoffice-help-5.3.2.1.tar.xz deleted file mode 100644 index e789f6d..0000000 --- a/libreoffice-help-5.3.2.1.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a462e6f1a575eb9185dfeffac37f77878454d5bee5bf9eb7ce5f6b6ae38dc622 -size 2127012 diff --git a/libreoffice-help-5.3.2.2.tar.xz b/libreoffice-help-5.3.2.2.tar.xz new file mode 100644 index 0000000..9b28930 --- /dev/null +++ b/libreoffice-help-5.3.2.2.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29b24202ee6ca0e1f6cd81a3c1ad98fa226225e7f2d6d4162936943c01656087 +size 2127516 diff --git a/libreoffice-translations-5.3.2.1.tar.xz b/libreoffice-translations-5.3.2.1.tar.xz deleted file mode 100644 index 2926cf4..0000000 --- a/libreoffice-translations-5.3.2.1.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b6770dc70698ac75dd02d27909c6731b40ef4c6a78cc5f90927e49011f83a7a -size 140855860 diff --git a/libreoffice-translations-5.3.2.2.tar.xz b/libreoffice-translations-5.3.2.2.tar.xz new file mode 100644 index 0000000..7577c38 --- /dev/null +++ b/libreoffice-translations-5.3.2.2.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e5719f2f56e8f79e4925bf011dd4a39001880ea0285f0a9f31514d8d2fda95c +size 140917912 diff --git a/libreoffice.changes b/libreoffice.changes index b4292bd..c4ce4df 100644 --- a/libreoffice.changes +++ b/libreoffice.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Apr 6 15:10:59 UTC 2017 - tchvatal@suse.com + +- Version update to 5.3.2.2: + * RC2 for the 5.3.2 release + ------------------------------------------------------------------- Thu Mar 30 12:57:25 UTC 2017 - tchvatal@suse.com diff --git a/libreoffice.spec b/libreoffice.spec index dac2e62..f856ef5 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -35,7 +35,7 @@ %bcond_with firebird %endif Name: libreoffice -Version: 5.3.2.1 +Version: 5.3.2.2 Release: 0 Summary: A Free Office Suite (Framework) License: Apache-2.0 and Artistic-1.0 and BSD-3-Clause and BSD-4-Clause and GPL-2.0+ and LPPL-1.3c and LGPL-2.1+ and LGPL-3.0 and MPL-1.1 and MIT and SUSE-Public-Domain and W3C From 153b1a5be0647110706751f1de5fc7ac27a1df1add4368dec8f226355b3b90d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Tue, 18 Apr 2017 10:49:52 +0000 Subject: [PATCH 2/4] Accepting request 489047 from LibreOffice:5.3 OBS-URL: https://build.opensuse.org/request/show/489047 OBS-URL: https://build.opensuse.org/package/show/LibreOffice:Factory/libreoffice?expand=0&rev=482 --- ...ment-text-rotation-for-Impress-table.patch | 1788 +++++++++++++++++ libreoffice.changes | 12 +- libreoffice.spec | 7 +- 3 files changed, 1802 insertions(+), 5 deletions(-) create mode 100644 bnc959926-Implement-text-rotation-for-Impress-table.patch diff --git a/bnc959926-Implement-text-rotation-for-Impress-table.patch b/bnc959926-Implement-text-rotation-for-Impress-table.patch new file mode 100644 index 0000000..ea36a8d --- /dev/null +++ b/bnc959926-Implement-text-rotation-for-Impress-table.patch @@ -0,0 +1,1788 @@ +From c0cb16c07b18f2298ff4e1f832802e72dc5bab8a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tam=C3=A1s=20Zolnai?= +Date: Tue, 28 Mar 2017 19:24:49 +0200 +Subject: [PATCH] tdf#105286: Implement text rotation for Impress tables +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Typo: TopToBotton -> TopToBottom + +Change-Id: I1b4d3ab9ec1d1383d76a56c9662ffeeb9fe69655 +Reviewed-on: https://gerrit.libreoffice.org/36014 +Tested-by: Jenkins +Reviewed-by: Tamás Zolnai +(cherry picked from commit 334e6e2f1ad3da319be0849ec426aa64b18cb599) + +Add SvxTextRotateItem inherited from SvxCharRotatItem + +I will be use it later for text rotation inside a table. + +Change-Id: I4cbaf05953b0e71331d2f3fdb45b7c4254a2b8cc +Reviewed-on: https://gerrit.libreoffice.org/36021 +Reviewed-by: Tamás Zolnai +Tested-by: Tamás Zolnai +(cherry picked from commit 1e30d2aface12a31687e5a27126e2061efd4b0cd) + +Introduce text rotation for Impress tables + +* Introduce new table property for text rotation +* Support only two rotation angle (270° and 90°) +* Implement editing and rendering of 270° rotated +text (90° rotation was already implemented) + +(cherry picked from commit c671094154ecec6f3ba5beea9d26ff0d2d4d86ad) + +Change-Id: Ifc2e0f171e9c840e86b365e9af2c30aa97ecd92e + +Implement RotateAngle API property for Impress table cells + +(cherry picked from commit a0755ab8772d01797f4945016a978a2bbd8fdf20) + +Change-Id: I01379c0fc21e8fe294bc882bf824f64502863ff4 + +tdf#100926: PPTX import of table with rotated text + +(cherry picked from commit 2436cf17304f25c7d34da52a321d6da0e9011d19) + +Change-Id: I05a8e979ac11b179e15784023032a56edc5b569b + +ODF import / export of rotated text in Impress table + +(cherry picked from commit bcb371b1a830442610ad7fda476eda5271427a50) + +Change-Id: I57136e32ed2db5e405a45e8e4bad1b8d459b7ae8 + +Fix vertical text and bitmap bullet rendering + +Change-Id: I881fce0511c81b164516d68f72c7e750687d4e0e +(cherry picked from commit 15ac3f9f4dc65fc0c6020284064e3725956f5d0a) +--- + cui/source/tabpages/chardlg.cxx | 4 +- + editeng/source/editeng/editdoc.cxx | 3 +- + editeng/source/editeng/editdoc.hxx | 5 +- + editeng/source/editeng/editeng.cxx | 21 +++- + editeng/source/editeng/editobj.cxx | 40 +++++-- + editeng/source/editeng/editobj2.hxx | 6 +- + editeng/source/editeng/impedit.cxx | 82 +++++++++++--- + editeng/source/editeng/impedit.hxx | 4 +- + editeng/source/editeng/impedit3.cxx | 161 ++++++++++++++++++++++------ + editeng/source/editeng/impedit4.cxx | 4 +- + editeng/source/items/svxitems.src | 8 ++ + editeng/source/items/textitem.cxx | 119 +++++++++++++++++--- + editeng/source/outliner/outlin2.cxx | 9 +- + editeng/source/outliner/outliner.cxx | 29 +++-- + editeng/source/outliner/outlobj.cxx | 12 ++- + include/editeng/charrotateitem.hxx | 57 ++++++++-- + include/editeng/editeng.hxx | 3 +- + include/editeng/editobj.hxx | 3 +- + include/editeng/editrids.hrc | 2 + + include/editeng/outliner.hxx | 3 +- + include/editeng/outlobj.hxx | 3 +- + include/svx/svddef.hxx | 3 +- + oox/source/drawingml/table/tablecell.cxx | 5 + + sd/qa/unit/data/pptx/tdf100926.pptx | Bin 0 -> 32382 bytes + sd/qa/unit/data/pptx/tdf100926_ODP.pptx | Bin 0 -> 32382 bytes + sd/qa/unit/export-tests.cxx | 31 ++++++ + sd/qa/unit/import-tests.cxx | 28 +++++ + sd/source/ui/func/futext.cxx | 4 +- + svx/source/svdraw/svdattr.cxx | 2 + + svx/source/svdraw/svdotext.cxx | 2 +- + svx/source/svdraw/svdotextdecomposition.cxx | 24 +++-- + svx/source/table/cell.cxx | 67 +++++++++++- + svx/source/table/svdotable.cxx | 4 +- + svx/source/unodraw/unoshtxt.cxx | 2 +- + xmloff/inc/xmlsdtypes.hxx | 2 + + xmloff/source/draw/sdpropls.cxx | 64 ++++++++++- + xmloff/source/table/XMLTableExport.cxx | 14 +++ + xmloff/source/table/XMLTableImport.cxx | 1 + + xmloff/source/table/table.hxx | 1 + + 39 files changed, 712 insertions(+), 120 deletions(-) + create mode 100755 sd/qa/unit/data/pptx/tdf100926.pptx + create mode 100755 sd/qa/unit/data/pptx/tdf100926_ODP.pptx + +diff --git a/cui/source/tabpages/chardlg.cxx b/cui/source/tabpages/chardlg.cxx +index b606199..fcaa7b7 100644 +--- a/cui/source/tabpages/chardlg.cxx ++++ b/cui/source/tabpages/chardlg.cxx +@@ -2954,7 +2954,7 @@ void SvxCharPositionPage::Reset( const SfxItemSet* rSet ) + static_cast( rSet->Get( nWhich )); + if (rItem.IsBottomToTop()) + m_p90degRB->Check(); +- else if (rItem.IsTopToBotton()) ++ else if (rItem.IsTopToBottom()) + m_p270degRB->Check(); + else + { +@@ -3111,7 +3111,7 @@ bool SvxCharPositionPage::FillItemSet( SfxItemSet* rSet ) + if (m_p90degRB->IsChecked()) + aItem.SetBottomToTop(); + else if (m_p270degRB->IsChecked()) +- aItem.SetTopToBotton(); ++ aItem.SetTopToBottom(); + rSet->Put( aItem ); + bModified = true; + } +diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx +index a7a73ce..8763b07 100644 +--- a/editeng/source/editeng/editdoc.cxx ++++ b/editeng/source/editeng/editdoc.cxx +@@ -1982,6 +1982,7 @@ EditDoc::EditDoc( SfxItemPool* pPool ) : + pItemPool(pPool ? pPool : new EditEngineItemPool(false)), + nDefTab(DEFTAB), + bIsVertical(false), ++ bIsTopToBottomVert(false), + bIsFixedCellHeight(false), + bOwnerOfPool(pPool == nullptr), + bModified(false) +@@ -2116,7 +2117,7 @@ void EditDoc::CreateDefFont( bool bUseStyles ) + SfxItemSet aTmpSet( GetItemPool(), EE_PARA_START, EE_CHAR_END ); + CreateFont( aDefFont, aTmpSet ); + aDefFont.SetVertical( IsVertical() ); +- aDefFont.SetOrientation( IsVertical() ? 2700 : 0 ); ++ aDefFont.SetOrientation( IsVertical() ? (IsTopToBottom() ? 2700 : 900) : 0 ); + + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { +diff --git a/editeng/source/editeng/editdoc.hxx b/editeng/source/editeng/editdoc.hxx +index 3bdfa01..c25ed6c 100644 +--- a/editeng/source/editeng/editdoc.hxx ++++ b/editeng/source/editeng/editdoc.hxx +@@ -739,6 +739,7 @@ private: + SvxFont aDefFont; //faster than ever from the pool!! + sal_uInt16 nDefTab; + bool bIsVertical:1; ++ bool bIsTopToBottomVert : 1; + bool bIsFixedCellHeight:1; + + bool bOwnerOfPool:1; +@@ -765,8 +766,10 @@ public: + void SetDefTab( sal_uInt16 nTab ) { nDefTab = nTab ? nTab : DEFTAB; } + sal_uInt16 GetDefTab() const { return nDefTab; } + +- void SetVertical( bool bVertical ) { bIsVertical = bVertical; } ++ void SetVertical( bool bVertical, bool bTopToBottom = true ) ++ { bIsVertical = bVertical; bIsTopToBottomVert = bVertical && bTopToBottom; } + bool IsVertical() const { return bIsVertical; } ++ bool IsTopToBottom() const { return bIsTopToBottomVert; } + + void SetFixedCellHeight( bool bUseFixedCellHeight ) { bIsFixedCellHeight = bUseFixedCellHeight; } + bool IsFixedCellHeight() const { return bIsFixedCellHeight; } +diff --git a/editeng/source/editeng/editeng.cxx b/editeng/source/editeng/editeng.cxx +index bde286a..5f88c0e 100644 +--- a/editeng/source/editeng/editeng.cxx ++++ b/editeng/source/editeng/editeng.cxx +@@ -429,9 +429,9 @@ const Size& EditEngine::GetPaperSize() const + return pImpEditEngine->GetPaperSize(); + } + +-void EditEngine::SetVertical( bool bVertical ) ++void EditEngine::SetVertical( bool bVertical, bool bTopToBottom ) + { +- pImpEditEngine->SetVertical( bVertical ); ++ pImpEditEngine->SetVertical( bVertical, bTopToBottom); + } + + bool EditEngine::IsVertical() const +@@ -439,6 +439,11 @@ bool EditEngine::IsVertical() const + return pImpEditEngine->IsVertical(); + } + ++bool EditEngine::IsTopToBottom() const ++{ ++ return pImpEditEngine->IsTopToBottom(); ++} ++ + void EditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) + { + pImpEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +@@ -1767,8 +1772,16 @@ void EditEngine::StripPortions() + Rectangle aBigRect( Point( 0, 0 ), Size( 0x7FFFFFFF, 0x7FFFFFFF ) ); + if ( IsVertical() ) + { +- aBigRect.Right() = 0; +- aBigRect.Left() = -0x7FFFFFFF; ++ if( IsTopToBottom() ) ++ { ++ aBigRect.Right() = 0; ++ aBigRect.Left() = -0x7FFFFFFF; ++ } ++ else ++ { ++ aBigRect.Top() = -0x7FFFFFFF; ++ aBigRect.Bottom() = 0; ++ } + } + pImpEditEngine->Paint( aTmpDev.get(), aBigRect, Point(), true ); + } +diff --git a/editeng/source/editeng/editobj.cxx b/editeng/source/editeng/editobj.cxx +index 5a37083..e64793c 100644 +--- a/editeng/source/editeng/editobj.cxx ++++ b/editeng/source/editeng/editobj.cxx +@@ -355,9 +355,14 @@ bool EditTextObject::IsVertical() const + return mpImpl->IsVertical(); + } + +-void EditTextObject::SetVertical( bool bVertical ) ++bool EditTextObject::IsTopToBottom() const + { +- return mpImpl->SetVertical(bVertical); ++ return mpImpl->IsTopToBottom(); ++} ++ ++void EditTextObject::SetVertical( bool bVertical, bool bTopToBottom ) ++{ ++ return mpImpl->SetVertical(bVertical, bTopToBottom); + } + + SvtScriptType EditTextObject::GetScriptType() const +@@ -562,6 +567,7 @@ EditTextObjectImpl::EditTextObjectImpl( EditTextObject* pFront, SfxItemPool* pP + } + + bVertical = false; ++ bIsTopToBottomVert = false; + bStoreUnicodeStrings = false; + nScriptType = SvtScriptType::NONE; + } +@@ -574,6 +580,7 @@ EditTextObjectImpl::EditTextObjectImpl( EditTextObject* pFront, const EditTextOb + nUserType = r.nUserType; + nObjSettings = r.nObjSettings; + bVertical = r.bVertical; ++ bIsTopToBottomVert = r.bIsTopToBottomVert; + nScriptType = r.nScriptType; + pPortionInfo = nullptr; // Do not copy PortionInfo + bStoreUnicodeStrings = false; +@@ -655,12 +662,22 @@ std::vector EditTextObjectImpl::GetSharedStrings() const + return aSSs; + } + ++bool EditTextObjectImpl::IsVertical() const ++{ ++ return bVertical; ++} ++ ++bool EditTextObjectImpl::IsTopToBottom() const ++{ ++ return bIsTopToBottomVert; ++} + +-void EditTextObjectImpl::SetVertical( bool b ) ++void EditTextObjectImpl::SetVertical( bool bVert, bool bTopToBottom) + { +- if ( b != bVertical ) ++ if (bVert != bVertical || bTopToBottom != (bVert && bIsTopToBottomVert)) + { +- bVertical = b; ++ bVertical = bVert; ++ bIsTopToBottomVert = bVert && bTopToBottom; + ClearPortionInfo(); + } + } +@@ -1090,7 +1107,7 @@ public: + + void EditTextObjectImpl::StoreData( SvStream& rOStream ) const + { +- sal_uInt16 nVer = 602; ++ sal_uInt16 nVer = 603; + rOStream.WriteUInt16( nVer ); + + rOStream.WriteBool( bOwnerOfPool ); +@@ -1229,6 +1246,7 @@ void EditTextObjectImpl::StoreData( SvStream& rOStream ) const + rOStream.WriteUInt32( nObjSettings ); + + rOStream.WriteBool( bVertical ); ++ rOStream.WriteBool( bIsTopToBottomVert ); + rOStream.WriteUInt16( static_cast(nScriptType) ); + + rOStream.WriteBool( bStoreUnicodeStrings ); +@@ -1481,6 +1499,13 @@ void EditTextObjectImpl::CreateData( SvStream& rIStream ) + bVertical = bTmp; + } + ++ if (nVersion >= 603) ++ { ++ bool bTmp(false); ++ rIStream.ReadCharAsBool(bTmp); ++ bIsTopToBottomVert = bTmp; ++ } ++ + if ( nVersion >= 602 ) + { + sal_uInt16 aTmp16; +@@ -1571,7 +1596,8 @@ bool EditTextObjectImpl::operator==( const EditTextObjectImpl& rCompare ) const + ( nMetric != rCompare.nMetric ) || + ( nUserType!= rCompare.nUserType ) || + ( nScriptType != rCompare.nScriptType ) || +- ( bVertical != rCompare.bVertical ) ) ++ ( bVertical != rCompare.bVertical ) || ++ ( bIsTopToBottomVert != rCompare.bIsTopToBottomVert ) ) + return false; + + for (size_t i = 0, n = aContents.size(); i < n; ++i) +diff --git a/editeng/source/editeng/editobj2.hxx b/editeng/source/editeng/editobj2.hxx +index 9aaef1d..7466354 100644 +--- a/editeng/source/editeng/editobj2.hxx ++++ b/editeng/source/editeng/editobj2.hxx +@@ -182,6 +182,7 @@ private: + + bool bOwnerOfPool:1; + bool bVertical:1; ++ bool bIsTopToBottomVert : 1; + bool bStoreUnicodeStrings:1; + + bool ImpChangeStyleSheets( const OUString& rOldName, SfxStyleFamily eOldFamily, +@@ -204,8 +205,9 @@ public: + void NormalizeString( svl::SharedStringPool& rPool ); + std::vector GetSharedStrings() const; + +- bool IsVertical() const { return bVertical;} +- void SetVertical( bool b ); ++ bool IsVertical() const; ++ bool IsTopToBottom() const; ++ void SetVertical( bool bVert, bool bTopToBottom = true); + + SvtScriptType GetScriptType() const { return nScriptType;} + void SetScriptType( SvtScriptType nType ); +diff --git a/editeng/source/editeng/impedit.cxx b/editeng/source/editeng/impedit.cxx +index e4eb615..a8af19d 100644 +--- a/editeng/source/editeng/impedit.cxx ++++ b/editeng/source/editeng/impedit.cxx +@@ -513,6 +513,11 @@ bool ImpEditView::IsVertical() const + return pEditEngine->pImpEditEngine->IsVertical(); + } + ++bool ImpEditView::IsTopToBottom() const ++{ ++ return pEditEngine->pImpEditEngine->IsTopToBottom(); ++} ++ + Rectangle ImpEditView::GetVisDocArea() const + { + return Rectangle( GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom() ); +@@ -530,8 +535,16 @@ Point ImpEditView::GetDocPos( const Point& rWindowPos ) const + } + else + { +- aPoint.X() = rWindowPos.Y() - aOutArea.Top() + GetVisDocLeft(); +- aPoint.Y() = aOutArea.Right() - rWindowPos.X() + GetVisDocTop(); ++ if (pEditEngine->pImpEditEngine->IsTopToBottom()) ++ { ++ aPoint.X() = rWindowPos.Y() - aOutArea.Top() + GetVisDocLeft(); ++ aPoint.Y() = aOutArea.Right() - rWindowPos.X() + GetVisDocTop(); ++ } ++ else ++ { ++ aPoint.X() = aOutArea.Bottom() - rWindowPos.Y() + GetVisDocLeft(); ++ aPoint.Y() = rWindowPos.X() - aOutArea.Left() + GetVisDocTop(); ++ } + } + + return aPoint; +@@ -549,8 +562,16 @@ Point ImpEditView::GetWindowPos( const Point& rDocPos ) const + } + else + { +- aPoint.X() = aOutArea.Right() - rDocPos.Y() + GetVisDocTop(); +- aPoint.Y() = rDocPos.X() + aOutArea.Top() - GetVisDocLeft(); ++ if (pEditEngine->pImpEditEngine->IsTopToBottom()) ++ { ++ aPoint.X() = aOutArea.Right() - rDocPos.Y() + GetVisDocTop(); ++ aPoint.Y() = rDocPos.X() + aOutArea.Top() - GetVisDocLeft(); ++ } ++ else ++ { ++ aPoint.X() = aOutArea.Left() + rDocPos.Y() - GetVisDocTop(); ++ aPoint.Y() = aOutArea.Bottom() - rDocPos.X() + GetVisDocLeft(); ++ } + } + + return aPoint; +@@ -916,8 +937,8 @@ void ImpEditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor ) + + if ( nDocDiffX | nDocDiffY ) + { +- long nDiffX = !IsVertical() ? nDocDiffX : -nDocDiffY; +- long nDiffY = !IsVertical() ? nDocDiffY : nDocDiffX; ++ long nDiffX = !IsVertical() ? nDocDiffX : (IsTopToBottom() ? -nDocDiffY : nDocDiffY); ++ long nDiffY = !IsVertical() ? nDocDiffY : (IsTopToBottom() ? nDocDiffX : -nDocDiffX); + + // Negative: Back to the top or left edge + if ( ( std::abs( nDiffY ) > pEditEngine->GetOnePixelInRef() ) && DoBigScroll() ) +@@ -993,7 +1014,7 @@ void ImpEditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor ) + aCursorSz.Width() = aOldSz.Height(); + aCursorSz.Height() = aOldSz.Width(); + GetCursor()->SetPos( aCursorRect.TopRight() ); +- GetCursor()->SetOrientation( 2700 ); ++ GetCursor()->SetOrientation( IsTopToBottom() ? 2700 : 900 ); + } + else + // #i32593# Reset correct orientation in horizontal layout +@@ -1087,8 +1108,16 @@ Pair ImpEditView::Scroll( long ndX, long ndY, ScrollRangeCheck nRangeCheck ) + } + else + { +- aNewVisArea.Top() += ndX; +- aNewVisArea.Bottom() += ndX; ++ if( IsTopToBottom() ) ++ { ++ aNewVisArea.Top() += ndX; ++ aNewVisArea.Bottom() += ndX; ++ } ++ else ++ { ++ aNewVisArea.Top() -= ndX; ++ aNewVisArea.Bottom() -= ndX; ++ } + } + if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Bottom() > (long)pEditEngine->pImpEditEngine->GetTextHeight() ) ) + { +@@ -1107,8 +1136,16 @@ Pair ImpEditView::Scroll( long ndX, long ndY, ScrollRangeCheck nRangeCheck ) + } + else + { +- aNewVisArea.Left() -= ndY; +- aNewVisArea.Right() -= ndY; ++ if (IsTopToBottom()) ++ { ++ aNewVisArea.Left() -= ndY; ++ aNewVisArea.Right() -= ndY; ++ } ++ else ++ { ++ aNewVisArea.Left() += ndY; ++ aNewVisArea.Right() += ndY; ++ } + } + if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Right() > (long)pEditEngine->pImpEditEngine->CalcTextWidth( false ) ) ) + { +@@ -1119,8 +1156,8 @@ Pair ImpEditView::Scroll( long ndX, long ndY, ScrollRangeCheck nRangeCheck ) + aNewVisArea.Move( -aNewVisArea.Left(), 0 ); + + // The difference must be alignt on pixel (due to scroll!) +- long nDiffX = !IsVertical() ? ( GetVisDocLeft() - aNewVisArea.Left() ) : -( GetVisDocTop() - aNewVisArea.Top() ); +- long nDiffY = !IsVertical() ? ( GetVisDocTop() - aNewVisArea.Top() ) : ( GetVisDocLeft() - aNewVisArea.Left() ); ++ long nDiffX = !IsVertical() ? ( GetVisDocLeft() - aNewVisArea.Left() ) : (IsTopToBottom() ? -( GetVisDocTop() - aNewVisArea.Top() ) : (GetVisDocTop() - aNewVisArea.Top())); ++ long nDiffY = !IsVertical() ? ( GetVisDocTop() - aNewVisArea.Top() ) : (IsTopToBottom() ? (GetVisDocLeft() - aNewVisArea.Left()) : -(GetVisDocTop() - aNewVisArea.Top())); + + Size aDiffs( nDiffX, nDiffY ); + aDiffs = pOutWin->LogicToPixel( aDiffs ); +@@ -1139,7 +1176,12 @@ Pair ImpEditView::Scroll( long ndX, long ndY, ScrollRangeCheck nRangeCheck ) + if ( !IsVertical() ) + aVisDocStartPos.Move( -nRealDiffX, -nRealDiffY ); + else +- aVisDocStartPos.Move( -nRealDiffY, nRealDiffX ); ++ { ++ if (IsTopToBottom()) ++ aVisDocStartPos.Move(-nRealDiffY, nRealDiffX); ++ else ++ aVisDocStartPos.Move(nRealDiffY, -nRealDiffX); ++ } + // Das Move um den allignten Wert ergibt nicht unbedingt ein + // alligntes Rechteck... + // Aligned value of the move does not necessarily result in aligned +@@ -2117,8 +2159,16 @@ void ImpEditView::dragOver(const css::datatransfer::dnd::DropTargetDragEvent& rD + } + else + { +- aEditCursor.Left()--; +- aEditCursor.Right()++; ++ if( IsTopToBottom() ) ++ { ++ aEditCursor.Left()--; ++ aEditCursor.Right()++; ++ } ++ else ++ { ++ aEditCursor.Left()++; ++ aEditCursor.Right()--; ++ } + } + aEditCursor = GetWindow()->PixelToLogic( aEditCursor ); + } +diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx +index 291a95a..0958617 100644 +--- a/editeng/source/editeng/impedit.hxx ++++ b/editeng/source/editeng/impedit.hxx +@@ -295,6 +295,7 @@ public: + const Rectangle& GetOutputArea() const { return aOutArea; } + + bool IsVertical() const; ++ bool IsTopToBottom() const; + + bool PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window* pFrameWin ); + +@@ -720,8 +721,9 @@ public: + const Size& GetPaperSize() const { return aPaperSize; } + void SetPaperSize( const Size& rSz ) { aPaperSize = rSz; } + +- void SetVertical( bool bVertical ); ++ void SetVertical( bool bVertical, bool bTopToBottom = true); + bool IsVertical() const { return GetEditDoc().IsVertical(); } ++ bool IsTopToBottom() const { return GetEditDoc().IsTopToBottom(); } + + bool IsPageOverflow( ) const; + +diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx +index 9334293..dee5f65 100644 +--- a/editeng/source/editeng/impedit3.cxx ++++ b/editeng/source/editeng/impedit3.cxx +@@ -2580,11 +2580,11 @@ void ImpEditEngine::SetTextRanger( TextRanger* pRanger ) + } + } + +-void ImpEditEngine::SetVertical( bool bVertical ) ++void ImpEditEngine::SetVertical( bool bVertical, bool bTopToBottom) + { +- if ( IsVertical() != bVertical ) ++ if ( IsVertical() != bVertical || IsTopToBottom() != (bVertical && bTopToBottom)) + { +- GetEditDoc().SetVertical( bVertical ); ++ GetEditDoc().SetVertical( bVertical, bTopToBottom); + bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + GetEditDoc().CreateDefFont( bUseCharAttribs ); + if ( IsFormatted() ) +@@ -2963,7 +2963,8 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + sal_Int32 nIndex = 0; + if ( pPortion->IsVisible() && ( + ( !IsVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) || +- ( IsVertical() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) ) ) ++ ( IsVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) || ++ ( IsVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) ) + + { + +@@ -2977,7 +2978,12 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + if ( !IsVertical() ) + aStartPos.Y() += pPortion->GetFirstLineOffset(); + else +- aStartPos.X() -= pPortion->GetFirstLineOffset(); ++ { ++ if( IsTopToBottom() ) ++ aStartPos.X() -= pPortion->GetFirstLineOffset(); ++ else ++ aStartPos.X() += pPortion->GetFirstLineOffset(); ++ } + + Point aParaStart( aStartPos ); + +@@ -3002,15 +3008,27 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + } + else + { +- aTmpPos.Y() += pLine->GetStartPosX(); +- aTmpPos.X() -= pLine->GetMaxAscent(); +- aStartPos.X() -= pLine->GetHeight(); +- if (nLine != nLastLine) +- aStartPos.X() -= nVertLineSpacing; ++ if ( IsTopToBottom() ) ++ { ++ aTmpPos.Y() += pLine->GetStartPosX(); ++ aTmpPos.X() -= pLine->GetMaxAscent(); ++ aStartPos.X() -= pLine->GetHeight(); ++ if (nLine != nLastLine) ++ aStartPos.X() -= nVertLineSpacing; ++ } ++ else ++ { ++ aTmpPos.Y() -= pLine->GetStartPosX(); ++ aTmpPos.X() += pLine->GetMaxAscent(); ++ aStartPos.X() += pLine->GetHeight(); ++ if (nLine != nLastLine) ++ aStartPos.X() += nVertLineSpacing; ++ } + } + + if ( ( !IsVertical() && ( aStartPos.Y() > aClipRect.Top() ) ) +- || ( IsVertical() && aStartPos.X() < aClipRect.Right() ) ) ++ || ( IsVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() ) ++ || ( IsVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) ) + { + bPaintBullet = false; + +@@ -3048,9 +3066,18 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + } + else + { +- aTmpPos.Y() = aStartPos.Y() + nPortionXOffset; +- if ( aTmpPos.Y() > aClipRect.Bottom() ) +- break; // No further output in line necessary ++ if( IsTopToBottom() ) ++ { ++ aTmpPos.Y() = aStartPos.Y() + nPortionXOffset; ++ if ( aTmpPos.Y() > aClipRect.Bottom() ) ++ break; // No further output in line necessary ++ } ++ else ++ { ++ aTmpPos.Y() = aStartPos.Y() - nPortionXOffset; ++ if (aTmpPos.Y() < aClipRect.Top()) ++ break; // No further output in line necessary ++ } + } + + switch ( rTextPortion.GetKind() ) +@@ -3141,8 +3168,16 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + } + else + { +- aTopLeftRectPos.Y() += nAdvanceX; +- aTopLeftRectPos.X() -= nAdvanceY; ++ if( IsTopToBottom() ) ++ { ++ aTopLeftRectPos.Y() -= nAdvanceX; ++ aTopLeftRectPos.X() += nAdvanceY; ++ } ++ else ++ { ++ aTopLeftRectPos.Y() += nAdvanceX; ++ aTopLeftRectPos.X() -= nAdvanceY; ++ } + } + + Point aBottomRightRectPos( aTopLeftRectPos ); +@@ -3153,8 +3188,16 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + } + else + { +- aBottomRightRectPos.X() -= pLine->GetHeight(); +- aBottomRightRectPos.Y() += 2 * nHalfBlankWidth; ++ if (IsTopToBottom()) ++ { ++ aBottomRightRectPos.X() += pLine->GetHeight(); ++ aBottomRightRectPos.Y() -= 2 * nHalfBlankWidth; ++ } ++ else ++ { ++ aBottomRightRectPos.X() -= pLine->GetHeight(); ++ aBottomRightRectPos.Y() += 2 * nHalfBlankWidth; ++ } + } + + pOutDev->Push( PushFlags::FILLCOLOR ); +@@ -3187,7 +3230,10 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + } + else + { +- aSlashPos.Y() = aTopLeftRectPos.Y() + nAddX; ++ if (IsTopToBottom()) ++ aSlashPos.Y() = aTopLeftRectPos.Y() + nAddX; ++ else ++ aSlashPos.Y() = aTopLeftRectPos.Y() - nAddX; + } + + aTmpFont.QuickDrawText( pOutDev, aSlashPos, aSlash, 0, 1 ); +@@ -3226,8 +3272,16 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + } + else + { +- aTmpPos.X() -= pLine->GetMaxAscent(); +- aStartPos.X() -= pLine->GetHeight(); ++ if (IsTopToBottom()) ++ { ++ aTmpPos.X() -= pLine->GetMaxAscent(); ++ aStartPos.X() -= pLine->GetHeight(); ++ } ++ else ++ { ++ aTmpPos.X() += pLine->GetMaxAscent(); ++ aStartPos.X() += pLine->GetHeight(); ++ } + } + } + ::std::vector< sal_Int32 >::iterator curIt = itSubLines; +@@ -3404,7 +3458,12 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + if ( !IsVertical() ) + aOutPos.Y() -= nDiff; + else +- aOutPos.X() += nDiff; ++ { ++ if (IsTopToBottom()) ++ aOutPos.X() += nDiff; ++ else ++ aOutPos.X() -= nDiff; ++ } + aRedLineTmpPos = aOutPos; + aTmpFont.SetEscapement( 0 ); + } +@@ -3534,7 +3593,10 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + if( !IsVertical() ) + aRedLineTmpPos.Y() -= nShift; + else +- aRedLineTmpPos.X() += nShift; ++ if (IsTopToBottom()) ++ aRedLineTmpPos.X() += nShift; ++ else ++ aRedLineTmpPos.X() -= nShift; + } + } + Color aOldColor( pOutDev->GetLineColor() ); +@@ -3651,13 +3713,20 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + if ( !IsVertical() ) + aStartPos.Y() += nSBL; + else +- aStartPos.X() -= nSBL; ++ { ++ if( IsTopToBottom() ) ++ aStartPos.X() -= nSBL; ++ else ++ aStartPos.X() += nSBL; ++ } + } + + // no more visible actions? + if ( !IsVertical() && ( aStartPos.Y() >= aClipRect.Bottom() ) ) + break; +- else if ( IsVertical() && ( aStartPos.X() <= aClipRect.Left() ) ) ++ else if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() <= aClipRect.Left() ) ) ++ break; ++ else if (IsVertical() && !IsTopToBottom() && (aStartPos.X() >= aClipRect.Right())) + break; + } + +@@ -3668,7 +3737,12 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + if ( !IsVertical() ) + aStartPos.Y() += nUL; + else +- aStartPos.X() -= nUL; ++ { ++ if (IsTopToBottom()) ++ aStartPos.X() -= nUL; ++ else ++ aStartPos.X() += nUL; ++ } + } + + // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion +@@ -3697,7 +3771,12 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + if ( !IsVertical() ) + aStartPos.Y() += nParaHeight; + else +- aStartPos.X() -= nParaHeight; ++ { ++ if (IsTopToBottom()) ++ aStartPos.X() -= nParaHeight; ++ else ++ aStartPos.X() += nParaHeight; ++ } + } + + if ( pPDFExtOutDevData ) +@@ -3706,7 +3785,9 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, Rectangle aClipRect, Point aSt + // no more visible actions? + if ( !IsVertical() && ( aStartPos.Y() > aClipRect.Bottom() ) ) + break; +- if ( IsVertical() && ( aStartPos.X() < aClipRect.Left() ) ) ++ if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() < aClipRect.Left() ) ) ++ break; ++ if (IsVertical() && !IsTopToBottom() && ( aStartPos.X() > aClipRect.Right() ) ) + break; + } + if ( aStatus.DoRestoreFont() ) +@@ -3735,9 +3816,18 @@ void ImpEditEngine::Paint( ImpEditView* pView, const Rectangle& rRect, OutputDev + } + else + { +- aStartPos = pView->GetOutputArea().TopRight(); +- aStartPos.X() += pView->GetVisDocTop(); +- aStartPos.Y() -= pView->GetVisDocLeft(); ++ if( IsTopToBottom() ) ++ { ++ aStartPos = pView->GetOutputArea().TopRight(); ++ aStartPos.X() += pView->GetVisDocTop(); ++ aStartPos.Y() -= pView->GetVisDocLeft(); ++ } ++ else ++ { ++ aStartPos = pView->GetOutputArea().BottomLeft(); ++ aStartPos.X() -= pView->GetVisDocTop(); ++ aStartPos.Y() += pView->GetVisDocLeft(); ++ } + } + + // If Doc-width < Output Area,Width and not wrapped fields, +@@ -4029,8 +4119,13 @@ long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const + return 0; + + if (IsVertical()) +- // Shift the text to the right for the asian layout mode. +- rStartPos.X() += nTotalSpace; ++ { ++ if( IsTopToBottom() ) ++ // Shift the text to the right for the asian layout mode. ++ rStartPos.X() += nTotalSpace; ++ else ++ rStartPos.X() -= nTotalSpace; ++ } + + return nTotalSpace / (nTotalLineCount-1); + } +diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx +index dde54ad..7cf098b 100644 +--- a/editeng/source/editeng/impedit4.cxx ++++ b/editeng/source/editeng/impedit4.cxx +@@ -1026,7 +1026,7 @@ EditTextObject* ImpEditEngine::CreateTextObject(const EditSelection& rSel) + EditTextObject* ImpEditEngine::CreateTextObject( EditSelection aSel, SfxItemPool* pPool, bool bAllowBigObjects, sal_Int32 nBigObjectStart ) + { + EditTextObject* pTxtObj = new EditTextObject(pPool); +- pTxtObj->SetVertical( IsVertical() ); ++ pTxtObj->SetVertical( IsVertical(), IsTopToBottom()); + MapUnit eMapUnit = aEditDoc.GetItemPool().GetMetric( DEF_METRIC ); + pTxtObj->mpImpl->SetMetric( (sal_uInt16) eMapUnit ); + if ( pTxtObj->mpImpl->IsOwnerOfPool() ) +@@ -1184,7 +1184,7 @@ void ImpEditEngine::SetText( const EditTextObject& rTextObject ) + EnableUndo( false ); + + InsertText( rTextObject, EditSelection( aPaM, aPaM ) ); +- SetVertical( rTextObject.IsVertical() ); ++ SetVertical( rTextObject.IsVertical(), rTextObject.IsTopToBottom()); + + DBG_ASSERT( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "From where comes the Undo in SetText ?!" ); + SetUpdateMode( _bUpdate ); +diff --git a/editeng/source/items/svxitems.src b/editeng/source/items/svxitems.src +index e173867..c39eb0dc 100644 +--- a/editeng/source/items/svxitems.src ++++ b/editeng/source/items/svxitems.src +@@ -943,6 +943,14 @@ String RID_SVXITEMS_CHARROTATE_FITLINE + { + Text [ en-US ] = "Fit to line"; + }; ++String RID_SVXITEMS_TEXTROTATE_OFF ++{ ++ Text [ en-US ] = "Text is not rotated"; ++}; ++String RID_SVXITEMS_TEXTROTATE ++{ ++ Text [ en-US ] = "Text is rotated by $(ARG1)°"; ++}; + String RID_SVXITEMS_CHARSCALE + { + Text [ en-US ] = "Characters scaled $(ARG1)%"; +diff --git a/editeng/source/items/textitem.cxx b/editeng/source/items/textitem.cxx +index 1e01793..2bc2b9d 100644 +--- a/editeng/source/items/textitem.cxx ++++ b/editeng/source/items/textitem.cxx +@@ -2878,13 +2878,114 @@ sal_uInt16 SvxTwoLinesItem::GetVersion( sal_uInt16 nFFVer ) const + + + /************************************************************************* ++|* class SvxTextRotateItem ++*************************************************************************/ ++ ++SvxTextRotateItem::SvxTextRotateItem(sal_uInt16 nValue, const sal_uInt16 nW) ++ : SfxUInt16Item(nW, nValue) ++{ ++} ++ ++SfxPoolItem* SvxTextRotateItem::Clone(SfxItemPool*) const ++{ ++ return new SvxTextRotateItem(GetValue(), Which()); ++} ++ ++SfxPoolItem* SvxTextRotateItem::Create(SvStream& rStrm, sal_uInt16) const ++{ ++ sal_uInt16 nVal; ++ rStrm.ReadUInt16(nVal); ++ return new SvxTextRotateItem(nVal, Which()); ++} ++ ++SvStream& SvxTextRotateItem::Store(SvStream & rStrm, sal_uInt16) const ++{ ++ rStrm.WriteUInt16(GetValue()); ++ return rStrm; ++} ++ ++sal_uInt16 SvxTextRotateItem::GetVersion(sal_uInt16 nFFVer) const ++{ ++ return SOFFICE_FILEFORMAT_50 > nFFVer ? USHRT_MAX : 0; ++} ++ ++bool SvxTextRotateItem::GetPresentation( ++ SfxItemPresentation /*ePres*/, ++ MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, ++ OUString &rText, const IntlWrapper*) const ++{ ++ if (!GetValue()) ++ rText = EE_RESSTR(RID_SVXITEMS_TEXTROTATE_OFF); ++ else ++ { ++ rText = EE_RESSTR(RID_SVXITEMS_TEXTROTATE); ++ rText = rText.replaceFirst("$(ARG1)", ++ OUString::number(GetValue() / 10)); ++ } ++ return true; ++} ++ ++bool SvxTextRotateItem::QueryValue(css::uno::Any& rVal, ++ sal_uInt8 nMemberId) const ++{ ++ nMemberId &= ~CONVERT_TWIPS; ++ bool bRet = true; ++ switch (nMemberId) ++ { ++ case MID_ROTATE: ++ rVal <<= (sal_Int16)GetValue(); ++ break; ++ default: ++ bRet = false; ++ break; ++ } ++ return bRet; ++} ++ ++bool SvxTextRotateItem::PutValue(const css::uno::Any& rVal, sal_uInt8 nMemberId) ++{ ++ nMemberId &= ~CONVERT_TWIPS; ++ bool bRet = true; ++ switch (nMemberId) ++ { ++ case MID_ROTATE: ++ { ++ sal_Int16 nVal = 0; ++ if ((rVal >>= nVal) && (0 == nVal || 900 == nVal || 2700 == nVal)) ++ SetValue((sal_uInt16)nVal); ++ else ++ bRet = false; ++ break; ++ } ++ default: ++ bRet = false; ++ } ++ return bRet; ++} ++ ++bool SvxTextRotateItem::operator==(const SfxPoolItem& rItem) const ++{ ++ assert(SfxPoolItem::operator==(rItem)); ++ return SfxUInt16Item::operator==(rItem); ++} ++ ++void SvxTextRotateItem::dumpAsXml(xmlTextWriterPtr pWriter) const ++{ ++ xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTextRotateItem")); ++ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); ++ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue()).getStr())); ++ xmlTextWriterEndElement(pWriter); ++} ++ ++ ++/************************************************************************* + |* class SvxCharRotateItem + *************************************************************************/ + + SvxCharRotateItem::SvxCharRotateItem( sal_uInt16 nValue, + bool bFitIntoLine, + const sal_uInt16 nW ) +- : SfxUInt16Item( nW, nValue ), bFitToLine( bFitIntoLine ) ++ : SvxTextRotateItem(nValue, nW), bFitToLine( bFitIntoLine ) + { + } + +@@ -2934,12 +3035,11 @@ bool SvxCharRotateItem::GetPresentation( + bool SvxCharRotateItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const + { +- nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; +- switch( nMemberId ) ++ switch(nMemberId & ~CONVERT_TWIPS) + { + case MID_ROTATE: +- rVal <<= (sal_Int16)GetValue(); ++ SvxTextRotateItem::QueryValue(rVal, nMemberId); + break; + case MID_FITTOLINE: + rVal = css::uno::makeAny( IsFitToLine() ); +@@ -2954,17 +3054,12 @@ bool SvxCharRotateItem::QueryValue( css::uno::Any& rVal, + bool SvxCharRotateItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) + { +- nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; +- switch( nMemberId ) ++ switch(nMemberId & ~CONVERT_TWIPS) + { + case MID_ROTATE: + { +- sal_Int16 nVal = 0; +- if((rVal >>= nVal) && (0 == nVal || 900 == nVal || 2700 == nVal)) +- SetValue( (sal_uInt16)nVal ); +- else +- bRet = false; ++ bRet = SvxTextRotateItem::PutValue(rVal, nMemberId); + break; + } + +@@ -2980,7 +3075,7 @@ bool SvxCharRotateItem::PutValue( const css::uno::Any& rVal, + bool SvxCharRotateItem::operator==( const SfxPoolItem& rItem ) const + { + assert(SfxPoolItem::operator==(rItem)); +- return SfxUInt16Item::operator==( rItem ) && ++ return SvxTextRotateItem::operator==( rItem ) && + IsFitToLine() == static_cast(rItem).IsFitToLine(); + } + +diff --git a/editeng/source/outliner/outlin2.cxx b/editeng/source/outliner/outlin2.cxx +index 3248ba3..c89a89f 100644 +--- a/editeng/source/outliner/outlin2.cxx ++++ b/editeng/source/outliner/outlin2.cxx +@@ -526,9 +526,9 @@ const EditEngine& Outliner::GetEditEngine() const + return *pEditEngine; + } + +-void Outliner::SetVertical( bool b ) ++void Outliner::SetVertical( bool bVertical, bool bTopToBottom) + { +- pEditEngine->SetVertical( b ); ++ pEditEngine->SetVertical(bVertical, bTopToBottom); + } + + bool Outliner::IsVertical() const +@@ -536,6 +536,11 @@ bool Outliner::IsVertical() const + return pEditEngine->IsVertical(); + } + ++bool Outliner::IsTopToBottom() const ++{ ++ return pEditEngine->IsTopToBottom(); ++} ++ + void Outliner::SetFixedCellHeight( bool bUseFixedCellHeight ) + { + pEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +diff --git a/editeng/source/outliner/outliner.cxx b/editeng/source/outliner/outliner.cxx +index a77e881..9943efd 100644 +--- a/editeng/source/outliner/outliner.cxx ++++ b/editeng/source/outliner/outliner.cxx +@@ -861,7 +861,7 @@ vcl::Font Outliner::ImpCalcBulletFont( sal_Int32 nPara ) const + aBulletFont.SetFontSize( Size( 0, nScaledLineHeight ) ); + bool bVertical = IsVertical(); + aBulletFont.SetVertical( bVertical ); +- aBulletFont.SetOrientation( bVertical ? 2700 : 0 ); ++ aBulletFont.SetOrientation( bVertical ? (IsTopToBottom() ? 2700 : 900) : 0 ); + + Color aColor( COL_AUTO ); + if( !pEditEngine->IsFlatMode() && !( pEditEngine->GetControlWord() & EEControlBits::NOCOLORS ) ) +@@ -890,6 +890,7 @@ void Outliner::PaintBullet( sal_Int32 nPara, const Point& rStartPos, + if (bDrawBullet && ImplHasNumberFormat(nPara)) + { + bool bVertical = IsVertical(); ++ bool bTopToBottom = IsTopToBottom(); + + bool bRightToLeftPara = pEditEngine->IsRightToLeft( nPara ); + +@@ -927,9 +928,17 @@ void Outliner::PaintBullet( sal_Int32 nPara, const Point& rStartPos, + } + else + { +-// aTextPos.X() = rStartPos.X() - aBulletArea.Bottom(); +- aTextPos.X() = rStartPos.X() - ( bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent ); +- aTextPos.Y() = rStartPos.Y() + aBulletArea.Left(); ++ if (bTopToBottom) ++ { ++// aTextPos.X() = rStartPos.X() - aBulletArea.Bottom(); ++ aTextPos.X() = rStartPos.X() - (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent); ++ aTextPos.Y() = rStartPos.Y() + aBulletArea.Left(); ++ } ++ else ++ { ++ aTextPos.X() = rStartPos.X() + (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent); ++ aTextPos.Y() = rStartPos.Y() + aBulletArea.Left(); ++ } + } + + if ( nOrientation ) +@@ -998,8 +1007,16 @@ void Outliner::PaintBullet( sal_Int32 nPara, const Point& rStartPos, + } + else + { +- aBulletPos.X() = rStartPos.X() - aBulletArea.Bottom(); +- aBulletPos.Y() = rStartPos.Y() + aBulletArea.Left(); ++ if (bTopToBottom) ++ { ++ aBulletPos.X() = rStartPos.X() - aBulletArea.Bottom(); ++ aBulletPos.Y() = rStartPos.Y() + aBulletArea.Left(); ++ } ++ else ++ { ++ aBulletPos.X() = rStartPos.X() + aBulletArea.Top(); ++ aBulletPos.Y() = rStartPos.Y() - aBulletArea.Right(); ++ } + } + + if(bStrippingPortions) +diff --git a/editeng/source/outliner/outlobj.cxx b/editeng/source/outliner/outlobj.cxx +index 3eba88e..a993b82 100644 +--- a/editeng/source/outliner/outlobj.cxx ++++ b/editeng/source/outliner/outlobj.cxx +@@ -131,12 +131,18 @@ bool OutlinerParaObject::IsVertical() const + return mpImpl->mpEditTextObject->IsVertical(); + } + +-void OutlinerParaObject::SetVertical(bool bNew) ++bool OutlinerParaObject::IsTopToBottom() const ++{ ++ return mpImpl->mpEditTextObject->IsTopToBottom(); ++} ++ ++void OutlinerParaObject::SetVertical(bool bNew, bool bTopToBottom) + { + const ::o3tl::cow_wrapper< OutlinerParaObjData >* pImpl = &mpImpl; +- if ( ( *pImpl )->mpEditTextObject->IsVertical() != bNew ) ++ if ( ( *pImpl )->mpEditTextObject->IsVertical() != bNew || ++ (*pImpl)->mpEditTextObject->IsTopToBottom() != (bNew && bTopToBottom)) + { +- mpImpl->mpEditTextObject->SetVertical(bNew); ++ mpImpl->mpEditTextObject->SetVertical(bNew, bTopToBottom); + } + } + +diff --git a/include/editeng/charrotateitem.hxx b/include/editeng/charrotateitem.hxx +index 2ab87f2..32a84a5 100644 +--- a/include/editeng/charrotateitem.hxx ++++ b/include/editeng/charrotateitem.hxx +@@ -22,6 +22,55 @@ + #include + #include + ++ // class SvxTextRotateItem ---------------------------------------------- ++ ++ /* [Description] ++ ++ This item defines a text rotation value. Currently ++ text can only be rotated 90,0 and 270,0 degrees. ++ ++ */ ++ ++class EDITENG_DLLPUBLIC SvxTextRotateItem : public SfxUInt16Item ++{ ++public: ++ static SfxPoolItem* CreateDefault(); ++ ++ SvxTextRotateItem(sal_uInt16 nValue, const sal_uInt16 nId); ++ ++ virtual SfxPoolItem* Clone(SfxItemPool *pPool = nullptr) const override; ++ virtual SfxPoolItem* Create(SvStream &, sal_uInt16) const override; ++ virtual SvStream& Store(SvStream & rStrm, sal_uInt16 nIVer) const override; ++ virtual sal_uInt16 GetVersion(sal_uInt16 nFileVersion) const override; ++ ++ virtual bool GetPresentation(SfxItemPresentation ePres, ++ MapUnit eCoreMetric, ++ MapUnit ePresMetric, ++ OUString &rText, ++ const IntlWrapper * = nullptr) const override; ++ ++ virtual bool QueryValue(css::uno::Any& rVal, sal_uInt8 nMemberId = 0) const override; ++ virtual bool PutValue(const css::uno::Any& rVal, sal_uInt8 nMemberId) override; ++ ++ SvxTextRotateItem& operator=(const SvxTextRotateItem& rItem) ++ { ++ SetValue(rItem.GetValue()); ++ return *this; ++ } ++ ++ virtual bool operator==(const SfxPoolItem&) const override; ++ ++ // our currently only degree values ++ void SetTopToBottom() { SetValue(2700); } ++ void SetBottomToTop() { SetValue(900); } ++ bool IsTopToBottom() const { return 2700 == GetValue(); } ++ bool IsBottomToTop() const { return 900 == GetValue(); } ++ bool IsVertical() const { return IsTopToBottom() || IsBottomToTop(); } ++ ++ void dumpAsXml(struct _xmlTextWriter* pWriter) const override; ++}; ++ ++ + // class SvxCharRotateItem ---------------------------------------------- + + /* [Description] +@@ -33,7 +82,7 @@ + + */ + +-class EDITENG_DLLPUBLIC SvxCharRotateItem : public SfxUInt16Item ++class EDITENG_DLLPUBLIC SvxCharRotateItem : public SvxTextRotateItem + { + bool bFitToLine; + public: +@@ -66,12 +115,6 @@ public: + + virtual bool operator==( const SfxPoolItem& ) const override; + +- // our currently only degree values +- void SetTopToBotton() { SetValue( 2700 ); } +- void SetBottomToTop() { SetValue( 900 ); } +- bool IsTopToBotton() const { return 2700 == GetValue(); } +- bool IsBottomToTop() const { return 900 == GetValue(); } +- + bool IsFitToLine() const { return bFitToLine; } + void SetFitToLine( bool b ) { bFitToLine = b; } + +diff --git a/include/editeng/editeng.hxx b/include/editeng/editeng.hxx +index d8be7c6..4d77107 100644 +--- a/include/editeng/editeng.hxx ++++ b/include/editeng/editeng.hxx +@@ -237,8 +237,9 @@ public: + void SetPaperSize( const Size& rSize ); + const Size& GetPaperSize() const; + +- void SetVertical( bool bVertical ); ++ void SetVertical( bool bVertical, bool bTopToBottom = true ); + bool IsVertical() const; ++ bool IsTopToBottom() const; + + void SetFixedCellHeight( bool bUseFixedCellHeight ); + +diff --git a/include/editeng/editobj.hxx b/include/editeng/editobj.hxx +index da35f32..aba1c86 100644 +--- a/include/editeng/editobj.hxx ++++ b/include/editeng/editobj.hxx +@@ -87,7 +87,8 @@ public: + void SetUserType( OutlinerMode n ); + + bool IsVertical() const; +- void SetVertical( bool bVertical ); ++ bool IsTopToBottom() const; ++ void SetVertical( bool bVertical, bool bTopToBottom = true); + + SvtScriptType GetScriptType() const; + +diff --git a/include/editeng/editrids.hrc b/include/editeng/editrids.hrc +index cff44d4..522d858 100644 +--- a/include/editeng/editrids.hrc ++++ b/include/editeng/editrids.hrc +@@ -113,6 +113,8 @@ + #define RID_SVXITEMS_CHARROTATE_OFF (RID_EDIT_START + 81) + #define RID_SVXITEMS_CHARROTATE (RID_EDIT_START + 82) + #define RID_SVXITEMS_CHARROTATE_FITLINE (RID_EDIT_START + 83) ++#define RID_SVXITEMS_TEXTROTATE_OFF (RID_EDIT_START + 84) ++#define RID_SVXITEMS_TEXTROTATE (RID_EDIT_START + 116) + + #define RID_SVXITEMS_RELIEF_BEGIN (RID_EDIT_START + 85) + #define RID_SVXITEMS_RELIEF_NONE (RID_EDIT_START + 85) +diff --git a/include/editeng/outliner.hxx b/include/editeng/outliner.hxx +index dad3d11..ae04cd0 100644 +--- a/include/editeng/outliner.hxx ++++ b/include/editeng/outliner.hxx +@@ -686,8 +686,9 @@ public: + void Init( OutlinerMode nOutlinerMode ); + OutlinerMode GetMode() const { return nOutlinerMode; } + +- void SetVertical( bool bVertical ); ++ void SetVertical( bool bVertical, bool bTopToBottom = true); + bool IsVertical() const; ++ bool IsTopToBottom() const; + + void SetFixedCellHeight( bool bUseFixedCellHeight ); + +diff --git a/include/editeng/outlobj.hxx b/include/editeng/outlobj.hxx +index 6bde289..b34cf00 100644 +--- a/include/editeng/outlobj.hxx ++++ b/include/editeng/outlobj.hxx +@@ -80,7 +80,8 @@ public: + + // vertical access + bool IsVertical() const; +- void SetVertical(bool bNew); ++ bool IsTopToBottom() const; ++ void SetVertical(bool bNew, bool bTopToBottom = true); + + // data read access + sal_Int32 Count() const; +diff --git a/include/svx/svddef.hxx b/include/svx/svddef.hxx +index 0f93472..0d889fe 100644 +--- a/include/svx/svddef.hxx ++++ b/include/svx/svddef.hxx +@@ -296,8 +296,9 @@ + #define SDRATTR_TABLE_BORDER_INNER (SDRATTR_TABLE_FIRST+1) + #define SDRATTR_TABLE_BORDER_TLBR (SDRATTR_TABLE_FIRST+2) + #define SDRATTR_TABLE_BORDER_BLTR (SDRATTR_TABLE_FIRST+3) ++#define SDRATTR_TABLE_TEXT_ROTATION (SDRATTR_TABLE_FIRST+4) + +-#define SDRATTR_TABLE_LAST (SDRATTR_TABLE_BORDER_BLTR) ++#define SDRATTR_TABLE_LAST (SDRATTR_TABLE_TEXT_ROTATION) + + #define SDRATTR_END SDRATTR_TABLE_LAST /* 1357 */ /* 1333 V4+++*/ /* 1243 V4+++*/ /*1213*/ /*1085*/ /*1040*/ /*Pool V2: 1123,V1: 1065 */ + +diff --git a/oox/source/drawingml/table/tablecell.cxx b/oox/source/drawingml/table/tablecell.cxx +index 1c0d086..ebc2ed7 100644 +--- a/oox/source/drawingml/table/tablecell.cxx ++++ b/oox/source/drawingml/table/tablecell.cxx +@@ -467,6 +467,11 @@ void TableCell::pushToXCell( const ::oox::core::XmlFilterBase& rFilterBase, cons + } + + getTextBody()->insertAt( rFilterBase, xText, xAt, aTextStyleProps, pMasterTextListStyle ); ++ ++ if (getVertToken() == XML_vert) ++ xPropSet->setPropertyValue("RotateAngle", Any(short(27000))); ++ else if (getVertToken() == XML_vert270) ++ xPropSet->setPropertyValue("RotateAngle", Any(short(9000))); + } + + } } } +diff --git a/sd/source/ui/func/futext.cxx b/sd/source/ui/func/futext.cxx +index 37fb65c..cb9bdc3 100644 +--- a/sd/source/ui/func/futext.cxx ++++ b/sd/source/ui/func/futext.cxx +@@ -1055,7 +1055,9 @@ void FuText::SetInEditMode(const MouseEvent& rMEvt, bool bQuickDrag) + if( pTextObj ) + { + OutlinerParaObject* pOPO = pTextObj->GetOutlinerParaObject(); +- if( ( pOPO && pOPO->IsVertical() ) || (nSlotId == SID_ATTR_CHAR_VERTICAL) || (nSlotId == SID_TEXT_FITTOSIZE_VERTICAL) ) ++ if( pOPO && pOPO->IsVertical() ) ++ pOutl->SetVertical( true, pOPO->IsTopToBottom()); ++ else if (nSlotId == SID_ATTR_CHAR_VERTICAL || nSlotId == SID_TEXT_FITTOSIZE_VERTICAL) + pOutl->SetVertical( true ); + + if( pTextObj->getTextCount() > 1 ) +diff --git a/svx/source/svdraw/svdattr.cxx b/svx/source/svdraw/svdattr.cxx +index 196a344..c3519a3 100644 +--- a/svx/source/svdraw/svdattr.cxx ++++ b/svx/source/svdraw/svdattr.cxx +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -319,6 +320,7 @@ SdrItemPool::SdrItemPool( + mppLocalPoolDefaults[ SDRATTR_TABLE_BORDER_INNER - SDRATTR_START ] = pBoxInfoItem; + mppLocalPoolDefaults[ SDRATTR_TABLE_BORDER_TLBR - SDRATTR_START ] = new SvxLineItem( SDRATTR_TABLE_BORDER_TLBR ); + mppLocalPoolDefaults[ SDRATTR_TABLE_BORDER_BLTR - SDRATTR_START ] = new SvxLineItem( SDRATTR_TABLE_BORDER_BLTR ); ++ mppLocalPoolDefaults[SDRATTR_TABLE_TEXT_ROTATION - SDRATTR_START] = new SvxTextRotateItem(0, SDRATTR_TABLE_TEXT_ROTATION); + + // set own ItemInfos + mpLocalItemInfos[SDRATTR_SHADOW-SDRATTR_START]._nSID=SID_ATTR_FILL_SHADOW; +diff --git a/svx/source/svdraw/svdotext.cxx b/svx/source/svdraw/svdotext.cxx +index f052d69..6824c46 100644 +--- a/svx/source/svdraw/svdotext.cxx ++++ b/svx/source/svdraw/svdotext.cxx +@@ -1384,7 +1384,7 @@ void SdrTextObj::NbcSetOutlinerParaObjectForText( OutlinerParaObject* pTextObjec + + if (pText && pText->GetOutlinerParaObject()) + { +- SvxWritingModeItem aWritingMode(pText->GetOutlinerParaObject()->IsVertical() ++ SvxWritingModeItem aWritingMode(pText->GetOutlinerParaObject()->IsVertical() && pText->GetOutlinerParaObject()->IsTopToBottom() + ? css::text::WritingMode_TB_RL + : css::text::WritingMode_LR_TB, + SDRATTR_TEXTDIRECTION); +diff --git a/svx/source/svdraw/svdotextdecomposition.cxx b/svx/source/svdraw/svdotextdecomposition.cxx +index c567477..e97b77cf 100644 +--- a/svx/source/svdraw/svdotextdecomposition.cxx ++++ b/svx/source/svdraw/svdotextdecomposition.cxx +@@ -759,6 +759,7 @@ void SdrTextObj::impDecomposeAutoFitTextPrimitive( + const OutlinerParaObject* pOutlinerParaObject = rSdrAutofitTextPrimitive.getSdrText()->GetOutlinerParaObject(); + OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)"); + const bool bVerticalWriting(pOutlinerParaObject->IsVertical()); ++ const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if((rSdrAutofitTextPrimitive.getWordWrap() || IsTextFrame())) +@@ -829,8 +830,9 @@ void SdrTextObj::impDecomposeAutoFitTextPrimitive( + // translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner +- const double fStartInX(bVerticalWriting ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); +- aNewTransformA.translate(fStartInX, aAdjustTranslate.getY()); ++ const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); ++ const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); ++ aNewTransformA.translate(fStartInX, fStartInY); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. +@@ -922,6 +924,7 @@ void SdrTextObj::impDecomposeBlockTextPrimitive( + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1L)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1L)); + const bool bVerticalWriting(rSdrBlockTextPrimitive.getOutlinerParaObject().IsVertical()); ++ const bool bTopToBottom(rSdrBlockTextPrimitive.getOutlinerParaObject().IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(bIsCell) +@@ -1082,8 +1085,9 @@ void SdrTextObj::impDecomposeBlockTextPrimitive( + // Translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner +- const double fStartInX(bVerticalWriting ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); +- const basegfx::B2DTuple aAdjOffset(fStartInX, aAdjustTranslate.getY()); ++ const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); ++ const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); ++ const basegfx::B2DTuple aAdjOffset(fStartInX, fStartInY); + basegfx::B2DHomMatrix aNewTransformA(basegfx::tools::createTranslateB2DHomMatrix(aAdjOffset.getX(), aAdjOffset.getY())); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, +@@ -1162,10 +1166,14 @@ void SdrTextObj::impDecomposeStretchTextPrimitive( + // needs to translate the text initially around object width to orient + // it relative to the topper right instead of the topper left + const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsVertical()); ++ const bool bTopToBottom(rSdrStretchTextPrimitive.getOutlinerParaObject().IsTopToBottom()); + + if(bVertical) + { +- aNewTransformA.translate(aScale.getX(), 0.0); ++ if(bTopToBottom) ++ aNewTransformA.translate(aScale.getX(), 0.0); ++ else ++ aNewTransformA.translate(0.0, aScale.getY()); + } + + // calculate global char stretching scale parameters. Use non-mirrored sizes +@@ -1517,6 +1525,7 @@ void SdrTextObj::impDecomposeChainedTextPrimitive( + OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)"); + + const bool bVerticalWriting(pOutlinerParaObject->IsVertical()); ++ const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(IsTextFrame()) +@@ -1593,8 +1602,9 @@ void SdrTextObj::impDecomposeChainedTextPrimitive( + // translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner +- const double fStartInX(bVerticalWriting ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); +- aNewTransformA.translate(fStartInX, aAdjustTranslate.getY()); ++ const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); ++ const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); ++ aNewTransformA.translate(fStartInX, fStartInY); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. +diff --git a/svx/source/table/cell.cxx b/svx/source/table/cell.cxx +index 5eaa3f9..3f484d3 100644 +--- a/svx/source/table/cell.cxx ++++ b/svx/source/table/cell.cxx +@@ -51,6 +51,7 @@ + #include "svx/unoshape.hxx" + #include "editeng/editobj.hxx" + #include "editeng/boxitem.hxx" ++#include + #include "svx/xflbstit.hxx" + #include "svx/xflbmtit.hxx" + #include +@@ -89,6 +90,7 @@ static const SvxItemPropertySet* ImplGetSvxCellPropertySet() + { OUString("BottomBorder"), SDRATTR_TABLE_BORDER, cppu::UnoType::get(), 0, BOTTOM_BORDER }, \ + { OUString("LeftBorder"), SDRATTR_TABLE_BORDER, cppu::UnoType::get(), 0, LEFT_BORDER }, \ + { OUString("RightBorder"), SDRATTR_TABLE_BORDER, cppu::UnoType::get(), 0, RIGHT_BORDER }, \ ++ { OUString("RotateAngle"), SDRATTR_TABLE_TEXT_ROTATION, cppu::UnoType::get(), 0, 0 }, \ + + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_CHAR_PROPERTIES, +@@ -291,7 +293,6 @@ namespace sdr + + OutlinerParaObject* pTemp = pOutliner->CreateParaObject(0, nParaCount); + pOutliner->Clear(); +- + mxCell->SetOutlinerParaObject(pTemp); + } + +@@ -314,8 +315,7 @@ namespace sdr + bool bVertical(css::text::WritingMode_TB_RL == static_cast(pNewItem)->GetValue()); + + sdr::table::SdrTableObj& rObj = static_cast(GetSdrObject()); +- if( rObj.IsVerticalWriting() != bVertical ) +- rObj.SetVerticalWriting(bVertical); ++ rObj.SetVerticalWriting(bVertical); + + // Set a cell vertical property + OutlinerParaObject* pParaObj = mxCell->GetEditOutlinerParaObject(); +@@ -334,6 +334,50 @@ namespace sdr + } + } + ++ if (pNewItem && (SDRATTR_TABLE_TEXT_ROTATION == nWhich)) ++ { ++ const SvxTextRotateItem* pRotateItem = static_cast(pNewItem); ++ ++ // Set a cell vertical property ++ OutlinerParaObject* pParaObj = mxCell->GetEditOutlinerParaObject(); ++ ++ const bool bOwnParaObj = pParaObj != nullptr; ++ ++ if (pParaObj == nullptr) ++ pParaObj = mxCell->GetOutlinerParaObject(); ++ ++ if (pParaObj) ++ { ++ pParaObj->SetVertical(pRotateItem->IsVertical(), pRotateItem->IsTopToBottom()); ++ ++ if (bOwnParaObj) ++ delete pParaObj; ++ } ++ ++ // Change autogrow direction ++ SdrTextObj& rObj = static_cast(GetSdrObject()); ++ ++ // rescue object size ++ Rectangle aObjectRect = rObj.GetSnapRect(); ++ ++ const SfxItemSet& rSet = rObj.GetObjectItemSet(); ++ bool bAutoGrowWidth = static_cast(rSet.Get(SDRATTR_TEXT_AUTOGROWWIDTH)).GetValue(); ++ bool bAutoGrowHeight = static_cast(rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT)).GetValue(); ++ ++ // prepare ItemSet to set exchanged width and height items ++ SfxItemSet aNewSet(*rSet.GetPool(), ++ SDRATTR_TEXT_AUTOGROWHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT, ++ 0, 0); ++ ++ aNewSet.Put(rSet); ++ aNewSet.Put(makeSdrTextAutoGrowWidthItem(bAutoGrowHeight)); ++ aNewSet.Put(makeSdrTextAutoGrowHeightItem(bAutoGrowWidth)); ++ rObj.SetObjectItemSet(aNewSet); ++ ++ // restore object size ++ rObj.SetSnapRect(aObjectRect); ++ } ++ + // call parent + AttributeProperties::ItemChange( nWhich, pNewItem ); + } +@@ -1068,6 +1112,18 @@ void SAL_CALL Cell::setPropertyValue( const OUString& rPropertyName, const Any& + mpProperties->SetObjectItem( XFillBmpTileItem( eMode == BitmapMode_REPEAT ) ); + return; + } ++ case SDRATTR_TABLE_TEXT_ROTATION: ++ { ++ sal_Int32 nRotVal = 0; ++ if (!(rValue >>= nRotVal)) ++ throw IllegalArgumentException(); ++ ++ if (nRotVal != 27000 && nRotVal != 9000 && nRotVal != 0) ++ throw IllegalArgumentException(); ++ ++ mpProperties->SetObjectItem(SvxTextRotateItem(nRotVal/10, SDRATTR_TABLE_TEXT_ROTATION)); ++ return; ++ } + default: + { + SfxItemSet aSet( GetModel()->GetItemPool(), pMap->nWID, pMap->nWID); +@@ -1183,6 +1239,11 @@ Any SAL_CALL Cell::getPropertyValue( const OUString& PropertyName ) throw(Unknow + return Any( BitmapMode_NO_REPEAT ); + } + } ++ case SDRATTR_TABLE_TEXT_ROTATION: ++ { ++ const SvxTextRotateItem& rTextRotate = static_cast(mpProperties->GetItem(SDRATTR_TABLE_TEXT_ROTATION)); ++ return Any(sal_Int32(rTextRotate.GetValue() * 10)); ++ } + default: + { + SfxItemSet aSet( GetModel()->GetItemPool(), pMap->nWID, pMap->nWID); +diff --git a/svx/source/table/svdotable.cxx b/svx/source/table/svdotable.cxx +index d53ddff..a4374b9 100644 +--- a/svx/source/table/svdotable.cxx ++++ b/svx/source/table/svdotable.cxx +@@ -1950,9 +1950,9 @@ bool SdrTableObj::IsVerticalWriting() const + } + + +-void SdrTableObj::SetVerticalWriting(bool bVertical ) ++void SdrTableObj::SetVerticalWriting(bool bVertical) + { +- if( bVertical != IsVerticalWriting() ) ++ if(bVertical != IsVerticalWriting() ) + { + SvxWritingModeItem aModeItem( css::text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION ); + SetObjectItem( aModeItem ); +diff --git a/svx/source/unodraw/unoshtxt.cxx b/svx/source/unodraw/unoshtxt.cxx +index 3a5705f..b7bd9a66 100644 +--- a/svx/source/unodraw/unoshtxt.cxx ++++ b/svx/source/unodraw/unoshtxt.cxx +@@ -636,7 +636,7 @@ SvxTextForwarder* SvxTextEditSourceImpl::GetBackgroundTextForwarder() + mpOutliner->SetStyleSheet( 0, pStyleSheet ); + + if( bVertical ) +- mpOutliner->SetVertical( true ); ++ mpOutliner->SetVertical( true, pOutlinerParaObject->IsTopToBottom()); + } + + // maybe we have to set the border attributes +diff --git a/xmloff/inc/xmlsdtypes.hxx b/xmloff/inc/xmlsdtypes.hxx +index 8e388cf..86e81ba 100644 +--- a/xmloff/inc/xmlsdtypes.hxx ++++ b/xmloff/inc/xmlsdtypes.hxx +@@ -117,6 +117,8 @@ + + ////////////////////////////////////////////////////////////////////////////// + ++#define XML_SD_TYPE_CELL_ROTATION_ANGLE (XML_SD_TYPES_START + 79 ) ++ + #define CTF_NUMBERINGRULES 1000 + #define CTF_CONTROLWRITINGMODE 1001 + #define CTF_WRITINGMODE 1002 +diff --git a/xmloff/source/draw/sdpropls.cxx b/xmloff/source/draw/sdpropls.cxx +index 72d6315..3afbf33 100644 +--- a/xmloff/source/draw/sdpropls.cxx ++++ b/xmloff/source/draw/sdpropls.cxx +@@ -32,6 +32,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -62,6 +63,7 @@ + #include "XMLPercentOrMeasurePropertyHandler.hxx" + #include "animations.hxx" + #include ++#include "xmlsdtypes.hxx" + + #include "sdxmlexp_impl.hxx" + +@@ -853,6 +855,52 @@ bool XMLSdHeaderFooterVisibilityTypeHdl::exportXML( + return bRet; + } + ++class XMLSdRotationAngleTypeHdl : public XMLPropertyHandler ++{ ++public: ++ virtual bool importXML(const OUString& rStrImpValue, css::uno::Any& rValue, const SvXMLUnitConverter& rUnitConverter) const override; ++ virtual bool exportXML(OUString& rStrExpValue, const css::uno::Any& rValue, const SvXMLUnitConverter& rUnitConverter) const override; ++}; ++ ++bool XMLSdRotationAngleTypeHdl::importXML( ++ const OUString& rStrImpValue, ++ css::uno::Any& rValue, ++ const SvXMLUnitConverter&) const ++{ ++ sal_Int32 nValue; ++ bool const bRet = ::sax::Converter::convertNumber(nValue, rStrImpValue); ++ if (bRet) ++ { ++ nValue = (nValue % 360); ++ if (nValue < 0) ++ nValue = 360 + nValue; ++ sal_Int32 nAngle; ++ if (nValue < 45 || nValue > 315) ++ nAngle = 0; ++ else if (nValue < 180) ++ nAngle = 9000; ++ else /* if nValalue <= 315 ) */ ++ nAngle = 27000; ++ ++ rValue <<= nAngle; ++ } ++ return bRet; ++} ++ ++bool XMLSdRotationAngleTypeHdl::exportXML( ++ OUString& rStrExpValue, ++ const Any& rValue, ++ const SvXMLUnitConverter&) const ++{ ++ sal_Int32 nAngle; ++ bool bRet = (rValue >>= nAngle) && nAngle != 0; ++ if (bRet) ++ { ++ rStrExpValue = OUString::number(nAngle / 100); ++ } ++ return bRet; ++} ++ + XMLSdPropHdlFactory::XMLSdPropHdlFactory( uno::Reference< frame::XModel > const & xModel, SvXMLImport& rImport ) + : mxModel( xModel ), mpExport(nullptr), mpImport( &rImport ) + { +@@ -1154,6 +1202,9 @@ const XMLPropertyHandler* XMLSdPropHdlFactory::GetPropertyHandler( sal_Int32 nTy + case XML_SD_TYPE_HEADER_FOOTER_VISIBILITY_TYPE: + pHdl = new XMLSdHeaderFooterVisibilityTypeHdl(); + break; ++ case XML_SD_TYPE_CELL_ROTATION_ANGLE: ++ pHdl = new XMLSdRotationAngleTypeHdl; ++ break; + } + + if(pHdl) +@@ -1276,7 +1327,18 @@ void XMLShapeExportPropertyMapper::ContextFilter( + pControlWritingMode = property; + break; + case CTF_TEXTWRITINGMODE: +- pTextWritingMode = property; ++ { ++ pTextWritingMode = property; ++ sal_Int32 eWritingMode; ++ if (property->maValue >>= eWritingMode) ++ { ++ if (text::WritingMode2::LR_TB == eWritingMode) ++ { ++ property->mnIndex = -1; ++ pTextWritingMode = nullptr; ++ } ++ } ++ } + break; + case CTF_REPEAT_OFFSET_X: + pRepeatOffsetX = property; +diff --git a/xmloff/source/table/XMLTableExport.cxx b/xmloff/source/table/XMLTableExport.cxx +index 4a47857..12d7590 100644 +--- a/xmloff/source/table/XMLTableExport.cxx ++++ b/xmloff/source/table/XMLTableExport.cxx +@@ -44,6 +44,7 @@ + #include + #include + #include ++#include "xmlsdtypes.hxx" + #include + #include + #include +@@ -62,6 +63,7 @@ using namespace ::com::sun::star::style; + #define MAP_(name,prefix,token,type,context) { name, sizeof(name)-1, prefix, token, type, context, SvtSaveOptions::ODFVER_010, false } + #define CMAP(name,prefix,token,type,context) MAP_(name,prefix,token,type|XML_TYPE_PROP_TABLE_COLUMN,context) + #define RMAP(name,prefix,token,type,context) MAP_(name,prefix,token,type|XML_TYPE_PROP_TABLE_ROW,context) ++#define CELLMAP(name,prefix,token,type,context) MAP_(name,prefix,token,type|XML_TYPE_PROP_TABLE_CELL,context) + #define MAP_END { nullptr, 0, 0, XML_EMPTY, 0, 0, SvtSaveOptions::ODFVER_010, false } + + const XMLPropertyMapEntry* getColumnPropertiesMap() +@@ -89,6 +91,17 @@ const XMLPropertyMapEntry* getRowPropertiesMap() + return &aXMLRowProperties[0]; + } + ++const XMLPropertyMapEntry* getCellPropertiesMap() ++{ ++ static const XMLPropertyMapEntry aXMLCellProperties[] = ++ { ++ CELLMAP( "RotateAngle", XML_NAMESPACE_STYLE, XML_ROTATION_ANGLE, XML_SD_TYPE_CELL_ROTATION_ANGLE, 0), ++ MAP_END ++ }; ++ ++ return &aXMLCellProperties[0]; ++} ++ + class StringStatisticHelper + { + private: +@@ -167,6 +180,7 @@ XMLTableExport::XMLTableExport(SvXMLExport& rExp, const rtl::Reference< SvXMLExp + { + mxCellExportPropertySetMapper = xExportPropertyMapper; + mxCellExportPropertySetMapper->ChainExportMapper(XMLTextParagraphExport::CreateParaExtPropMapper(rExp)); ++ mxCellExportPropertySetMapper->ChainExportMapper(new SvXMLExportPropertyMapper(new XMLPropertySetMapper(getCellPropertiesMap(), xFactoryRef.get(), true))); + } + + mxRowExportPropertySetMapper = new SvXMLExportPropertyMapper( new XMLPropertySetMapper( getRowPropertiesMap(), xFactoryRef.get(), true ) ); +diff --git a/xmloff/source/table/XMLTableImport.cxx b/xmloff/source/table/XMLTableImport.cxx +index d844d2f..4a4d6d9 100644 +--- a/xmloff/source/table/XMLTableImport.cxx ++++ b/xmloff/source/table/XMLTableImport.cxx +@@ -219,6 +219,7 @@ XMLTableImport::XMLTableImport( SvXMLImport& rImport, const rtl::Reference< XMLP + { + mxCellImportPropertySetMapper = new SvXMLImportPropertyMapper( xCellPropertySetMapper.get(), rImport ); + mxCellImportPropertySetMapper->ChainImportMapper(XMLTextImportHelper::CreateParaExtPropMapper(rImport)); ++ mxCellImportPropertySetMapper->ChainImportMapper(new SvXMLImportPropertyMapper(new XMLPropertySetMapper(getCellPropertiesMap(), xFactoryRef.get(), true), rImport)); + } + + rtl::Reference < XMLPropertySetMapper > xRowMapper( new XMLPropertySetMapper( getRowPropertiesMap(), xFactoryRef.get(), false ) ); +diff --git a/xmloff/source/table/table.hxx b/xmloff/source/table/table.hxx +index cb00e7c..2eddd38 100644 +--- a/xmloff/source/table/table.hxx ++++ b/xmloff/source/table/table.hxx +@@ -34,6 +34,7 @@ extern const TableStyleElement* getTableStyleMap(); + extern const TableStyleElement* getWriterSpecificTableStyleMap(); + extern const XMLPropertyMapEntry* getColumnPropertiesMap(); + extern const XMLPropertyMapEntry* getRowPropertiesMap(); ++extern const XMLPropertyMapEntry* getCellPropertiesMap(); + + #endif + +-- +2.8.3 + diff --git a/libreoffice.changes b/libreoffice.changes index c4ce4df..866cb30 100644 --- a/libreoffice.changes +++ b/libreoffice.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Apr 18 06:18:51 UTC 2017 - andras.timar@collabora.com + +- Fixed bnc#959926 LO-L3: Text not rotated properly in LO Impress tables + * bnc959926-Implement-text-rotation-for-Impress-table.patch + ------------------------------------------------------------------- Thu Apr 6 15:10:59 UTC 2017 - tchvatal@suse.com @@ -8,7 +14,7 @@ Thu Apr 6 15:10:59 UTC 2017 - tchvatal@suse.com Thu Mar 30 12:57:25 UTC 2017 - tchvatal@suse.com - Version update to 5.3.2.1: - * RC1 of the 5.3.2 release + * RC1 of the 5.3.2 release ------------------------------------------------------------------- Sat Mar 18 19:58:29 UTC 2017 - tchvatal@suse.com @@ -63,7 +69,7 @@ Wed Feb 1 10:53:18 UTC 2017 - tchvatal@suse.com Fri Jan 20 12:03:38 UTC 2017 - tchvatal@suse.com - Version update to 5.3.0.2: - * RC2 of the 5.3.0 target, stabilizing the lokit + * RC2 of the 5.3.0 target, stabilizing the lokit ------------------------------------------------------------------- Wed Jan 18 21:21:04 UTC 2017 - tchvatal@suse.com @@ -80,7 +86,7 @@ Tue Jan 17 12:38:47 UTC 2017 - tchvatal@suse.com Fri Jan 13 09:21:39 UTC 2017 - tchvatal@suse.com - Version update to 5.3.0.1: - * RC candidate stabilizing ont he beta1 bump + * RC candidate stabilizing ont he beta1 bump ------------------------------------------------------------------- Thu Jan 5 15:08:29 UTC 2017 - tchvatal@suse.com diff --git a/libreoffice.spec b/libreoffice.spec index f856ef5..13ff670 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -80,6 +80,8 @@ Patch5: mediawiki-no-broken-help.diff Patch16: libreoffice-hotfix-disablebrokenshapetest.patch # PATCH-FIX-UPSTREAM: merged in 5.4 Patch17: gi-annotation-syntax.patch +# LO-L3: Text not rotated properly in LO Impress tables +Patch18: bnc959926-Implement-text-rotation-for-Impress-table.patch # try to save space by using hardlinks Patch990: install-with-hardlinks.diff BuildRequires: %{name}-share-linker @@ -644,10 +646,10 @@ appearance and behavior. %package base-drivers-mysql Summary: MySQL Database Driver for LibreOffice -# This mysql thing is just dlopened -# WARNING: the soname might change! License: GPL-2.0 and LGPL-3.0 Group: Productivity/Office/Suite +# This mysql thing is just dlopened +# WARNING: the soname might change! Requires: libmysqlclient_r18 Requires: libreoffice-base = %{version} Requires(pre): libreoffice = %{version} @@ -947,6 +949,7 @@ Provides additional %{langname} translations and resources for %{project}. \ %patch5 %patch16 -p1 %patch17 -p1 +%patch18 -p1 %patch990 -p1 # Do not generate doxygen timestamp From 5850eff28a800f11742c6814f196bcdc87550aa7d267c8a9b6e861e9ec59a498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Wed, 19 Apr 2017 09:33:54 +0000 Subject: [PATCH 3/4] * bsc#1034329 CVE-2017-7882 * Fixes bsc#1034568 CVE-2017-7870 * Fixes bsc#1034192 CVE-2016-10327 OBS-URL: https://build.opensuse.org/package/show/LibreOffice:Factory/libreoffice?expand=0&rev=483 --- libreoffice.changes | 3 +++ libreoffice.spec | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libreoffice.changes b/libreoffice.changes index 866cb30..c349b70 100644 --- a/libreoffice.changes +++ b/libreoffice.changes @@ -8,6 +8,7 @@ Tue Apr 18 06:18:51 UTC 2017 - andras.timar@collabora.com Thu Apr 6 15:10:59 UTC 2017 - tchvatal@suse.com - Version update to 5.3.2.2: + * bsc#1034329 CVE-2017-7882 * RC2 for the 5.3.2 release ------------------------------------------------------------------- @@ -63,6 +64,8 @@ Wed Feb 1 13:10:25 UTC 2017 - adam.majer@suse.de Wed Feb 1 10:53:18 UTC 2017 - tchvatal@suse.com - Version update to 5.3.0.3: + * Fixes bsc#1034568 CVE-2017-7870 + * Fixes bsc#1034192 CVE-2016-10327 * RC3 of the 5.3 targets with additional fixes ------------------------------------------------------------------- diff --git a/libreoffice.spec b/libreoffice.spec index 13ff670..6d254ee 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -646,10 +646,10 @@ appearance and behavior. %package base-drivers-mysql Summary: MySQL Database Driver for LibreOffice -License: GPL-2.0 and LGPL-3.0 -Group: Productivity/Office/Suite # This mysql thing is just dlopened # WARNING: the soname might change! +License: GPL-2.0 and LGPL-3.0 +Group: Productivity/Office/Suite Requires: libmysqlclient_r18 Requires: libreoffice-base = %{version} Requires(pre): libreoffice = %{version} From ef2b015064bd23518ad46436093becd67ab2a083c322bc10d282557907ff6010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Mon, 24 Apr 2017 10:00:40 +0000 Subject: [PATCH 4/4] - Provide and obsolete the help packages wrt bsc#1035087 OBS-URL: https://build.opensuse.org/package/show/LibreOffice:Factory/libreoffice?expand=0&rev=484 --- libreoffice.changes | 5 +++++ libreoffice.spec | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libreoffice.changes b/libreoffice.changes index c349b70..b06b395 100644 --- a/libreoffice.changes +++ b/libreoffice.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Mon Apr 24 09:59:01 UTC 2017 - tchvatal@suse.com + +- Provide and obsolete the help packages wrt bsc#1035087 + ------------------------------------------------------------------- Tue Apr 18 06:18:51 UTC 2017 - andras.timar@collabora.com diff --git a/libreoffice.spec b/libreoffice.spec index 6d254ee..9301e78 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -834,7 +834,6 @@ Obsoletes: %{name}-l10n-%{-p*} < %{version} \ Provides: %{name}-l10n-%{-q*} = %{version} \ Obsoletes: %{name}-l10n-%{-q*} < %{version} \ } \ -%{-T: \ Provides: %{name}-help-%{lang} = %{version} \ Obsoletes: %{name}-help-%{lang} < %{version} \ %{-L: \ @@ -849,7 +848,6 @@ Obsoletes: %{name}-help-%{-p*} < %{version} \ Provides: %{name}-help-%{-q*} = %{version} \ Obsoletes: %{name}-help-%{-q*} < %{version} \ } \ -} \ \ %description %{pkgname} \ Provides additional %{langname} translations and resources for %{project}. \