include/svx/svdtrans.hxx                            |    2 
 oox/inc/drawingml/customshapeproperties.hxx         |    4 
 oox/inc/drawingml/textbodyproperties.hxx            |    5 
 oox/inc/drawingml/transform2dcontext.hxx            |    5 
 oox/qa/unit/data/tdf149551_TextRot.pptx             |binary
 oox/qa/unit/data/tdf149551_vert270AndTextRot.pptx   |binary
 oox/qa/unit/data/tdf149551_vert_and_padding.pptx    |binary
 oox/qa/unit/drawingml.cxx                           |   63 ++++
 oox/qa/unit/export.cxx                              |   22 +
 oox/source/drawingml/chart/axisconverter.cxx        |    4 
 oox/source/drawingml/chart/objectformatter.cxx      |    2 
 oox/source/drawingml/customshapeproperties.cxx      |    4 
 oox/source/drawingml/diagram/diagramlayoutatoms.cxx |    4 
 oox/source/drawingml/shape.cxx                      |   42 +--
 oox/source/drawingml/shapecontext.cxx               |    7 
 oox/source/drawingml/textbodyproperties.cxx         |   12 
 oox/source/drawingml/textbodypropertiescontext.cxx  |   11 
 oox/source/drawingml/transform2dcontext.cxx         |  267 +++++++++++++++++---
 oox/source/export/drawingml.cxx                     |  206 +++++++++------
 oox/source/shape/WpsContext.cxx                     |   18 -
 sc/qa/unit/subsequent_export_test2.cxx              |   11 
 sd/qa/unit/import-tests-smartart.cxx                |    2 
 sd/qa/unit/import-tests.cxx                         |   12 
 sw/qa/extras/ooxmlimport/ooxmlimport.cxx            |    2 
 24 files changed, 528 insertions(+), 177 deletions(-)

New commits:
commit 7e23cbdbb6ec0247a29ed8a8f744c01e10963ea0
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Sat Jun 25 00:07:43 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Jul 20 08:16:03 2022 +0200

    tdf#149551 separate TextRotateAngle from TextPreRotateAngle
    
    The import filter had only one member for both and had it mapped to
    TextPreRotateAngle. That resulted in lost text area rotation when a
    shape had both types of rotations, and sheared text when only text
    area rotation existed.
    
    The patch introduces a new 'moTextAreaRotation' member for the 'rot'
    attribute of the <bodyPr> element. It is mapped to 'TextRotateAngle'
    property. It becomes the 'draw:text-rotate-angle' attribute of the
    <draw:enhanced-geometry> element for a shape and 'style:rotate-angle'
    for chart elements in ODF. It must also be used to simulate 'upright'
    and is used for the 'rot' attribute in <txXfrm> of diagram shapes.
    
    The 'moRotation' member is now only used for the 'TextPreRotateAngle'
    property. That angle describes a rotation of the text without changing
    the text area. Valid values are multiples of 90deg. It is used for
    simulating vertical writing modes that are not yet implemented. It has
    no corresponding attribute in ODF. To make the meaning clear in code,
    the member is renamed to 'moTextPreRotation'.
    
    MS Office recalutes a diagram on file opening from layout.xml and
    data.xml. That is not yet implemented in LO, but we use drawing.xml,
    which gives the state of the diagram at time of saving. The patch
    handles the 'rot' attribute of <txXfrm> element as well. It has to be
    mapped to moTextAreaRotation, because it might contain angles, which
    are not multiples of 90deg, for example diagram 'Gear'.
    
    The <off> and <ext> child elements of <txXfrm> describe the actual used
    text area rectangle. The existing import calculates the difference of
    the actual used text area rectangle to the predefined one and
    incorporates it into the TextArea*Distance attributes. The patch adds
    calculating the current values of the text area rectangle as it would
    be using the preset markup. At that time in import there is no
    SdrObjCustomShape object, thus we cannot use its GetTextBounds() method.
    So it is down manually, covering most of those types, which are used in
    diagrams of MS Office.
    
    Remarks to unit tests:
    Now the rotation introduced by txXfrm is no longer in
    TextPreRotateAngle, but in TextRotateAngle. According changes are in
    SdImportTest::testN86510_2
    Test, testFdo87488
    
    Now the correct preset text area rectangles are used. That requires
    adaption of the needed distances. Done in
    SdImportTest::testBnc870237()
    SdImportTest::testTdf93830()
    SdImportTestSmartArt::testTdf134221()
    
    The buggy 'upright' export is fixed. Adaption in
    ScExportTest2::testTdf137000_handle_upright()
    
    Change-Id: I79df1559f88b76e96988fe745304dc4162de6316
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136447
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/include/svx/svdtrans.hxx b/include/svx/svdtrans.hxx
index 67741e77728b..0087f5407f81 100644
--- a/include/svx/svdtrans.hxx
+++ b/include/svx/svdtrans.hxx
@@ -148,7 +148,7 @@ inline double GetCrookAngle(Point& rPnt, const Point& 
rCenter, const Point& rRad
  */
 SVXCORE_DLLPUBLIC Degree100 GetAngle(const Point& rPnt);
 
-Degree100 NormAngle18000(Degree100 a); /// Normalize angle to -180.00..179.99
+SVXCORE_DLLPUBLIC Degree100 NormAngle18000(Degree100 a); /// Normalize angle 
to -180.00..179.99
 
 SVXCORE_DLLPUBLIC Degree100 NormAngle36000(Degree100 a); /// Normalize angle 
to 0.00..359.99
 
diff --git a/oox/inc/drawingml/customshapeproperties.hxx 
b/oox/inc/drawingml/customshapeproperties.hxx
index 74369c5286d6..2a6baad662ad 100644
--- a/oox/inc/drawingml/customshapeproperties.hxx
+++ b/oox/inc/drawingml/customshapeproperties.hxx
@@ -117,6 +117,7 @@ public:
     void                                setMirroredY( bool bMirroredY ) { 
mbMirroredY = bMirroredY; };
     void                                setTextRotateAngle( sal_Int32 nAngle ) 
{ mnTextRotateAngle = nAngle; };
     void                                setTextCameraZRotateAngle( sal_Int32 
nAngle ) { mnTextCameraZRotateAngle = nAngle; };
+    void                                setTextAreaRotateAngle(sal_Int32 
nAngle) { moTextAreaRotateAngle = nAngle; };
 
     static sal_Int32 SetCustomShapeGuideValue( std::vector< CustomShapeGuide 
>& rGuideList, const CustomShapeGuide& rGuide );
     static sal_Int32 GetCustomShapeGuideValue( const std::vector< 
CustomShapeGuide >& rGuideList, std::u16string_view rFormulaName );
@@ -144,8 +145,9 @@ private:
                                     maSegments;
     bool                            mbMirroredX;
     bool                            mbMirroredY;
-    sal_Int32                       mnTextRotateAngle;
+    sal_Int32                       mnTextRotateAngle; // TextPreRotateAngle
     sal_Int32                       mnTextCameraZRotateAngle;
+    std::optional< sal_Int32 >      moTextAreaRotateAngle; // TextRotateAngle
 
     typedef std::unordered_map< sal_Int32, PropertyMap > PresetDataMap;
 
diff --git a/oox/inc/drawingml/textbodyproperties.hxx 
b/oox/inc/drawingml/textbodyproperties.hxx
index 8d115848f6ec..bc1d9508daea 100644
--- a/oox/inc/drawingml/textbodyproperties.hxx
+++ b/oox/inc/drawingml/textbodyproperties.hxx
@@ -34,7 +34,10 @@ namespace oox::drawingml {
 struct TextBodyProperties
 {
     PropertyMap                                     maPropertyMap;
-    std::optional< sal_Int32 >                      moRotation;
+    // TextPreRotateAngle. Used for simulating writing modes.
+    std::optional< sal_Int32 >                      moTextPreRotation;
+    // TextRotateAngle. ODF draw:text-rotate-angle, OOXML 'rot' attribute in 
<bodyPr> element
+    std::optional< sal_Int32 >                      moTextAreaRotation;
     bool                                            mbAnchorCtr;
     std::optional< sal_Int32 >                      moVert;
     bool                                            moUpright = false;
diff --git a/oox/inc/drawingml/transform2dcontext.hxx 
b/oox/inc/drawingml/transform2dcontext.hxx
index 984ce0ed18cb..494bc579d82e 100644
--- a/oox/inc/drawingml/transform2dcontext.hxx
+++ b/oox/inc/drawingml/transform2dcontext.hxx
@@ -20,6 +20,8 @@
 #ifndef INCLUDED_OOX_DRAWINGML_TRANSFORM2DCONTEXT_HXX
 #define INCLUDED_OOX_DRAWINGML_TRANSFORM2DCONTEXT_HXX
 
+#include <optional>
+
 #include <oox/core/contexthandler2.hxx>
 
 namespace oox::drawingml {
@@ -37,6 +39,9 @@ public:
 private:
     Shape&              mrShape;
     bool                mbtxXfrm;
+    std::optional<sal_Int32> mno_txXfrmRot;
+    std::optional<sal_Int32> mno_txXfrmOffX;
+    std::optional<sal_Int32> mno_txXfrmOffY;
 };
 
 } // namespace oox::drawingml
diff --git a/oox/qa/unit/data/tdf149551_TextRot.pptx 
b/oox/qa/unit/data/tdf149551_TextRot.pptx
new file mode 100644
index 000000000000..98c547530c99
Binary files /dev/null and b/oox/qa/unit/data/tdf149551_TextRot.pptx differ
diff --git a/oox/qa/unit/data/tdf149551_vert270AndTextRot.pptx 
b/oox/qa/unit/data/tdf149551_vert270AndTextRot.pptx
new file mode 100644
index 000000000000..b90a8fe91750
Binary files /dev/null and b/oox/qa/unit/data/tdf149551_vert270AndTextRot.pptx 
differ
diff --git a/oox/qa/unit/data/tdf149551_vert_and_padding.pptx 
b/oox/qa/unit/data/tdf149551_vert_and_padding.pptx
new file mode 100644
index 000000000000..02b4855ff938
Binary files /dev/null and b/oox/qa/unit/data/tdf149551_vert_and_padding.pptx 
differ
diff --git a/oox/qa/unit/drawingml.cxx b/oox/qa/unit/drawingml.cxx
index 4dc066f98039..2317f411ec7e 100644
--- a/oox/qa/unit/drawingml.cxx
+++ b/oox/qa/unit/drawingml.cxx
@@ -31,6 +31,7 @@
 #include <com/sun/star/text/XTextRange.hpp>
 #include <com/sun/star/table/XCellRange.hpp>
 
+#include <comphelper/sequenceashashmap.hxx>
 #include <unotools/mediadescriptor.hxx>
 #include <unotools/tempfile.hxx>
 
@@ -541,6 +542,68 @@ CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, testThemeTint)
     CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(-1), nFillColorTheme);
 }
 
+CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, testVert270AndTextRot)
+{
+    // tdf##149551. The document contains a shape with attributes 
'rot="720000"' and 'vert="vert270"'
+    // of the <bodyPr> element. Without the fix the simulation of vert270 had 
overwritten the text
+    // rotation angle and thus 'rot'="720000" was lost.
+    OUString aURL
+        = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"tdf149551_vert270AndTextRot.pptx";
+    load(aURL);
+
+    uno::Reference<drawing::XDrawPagesSupplier> 
xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+    uno::Reference<drawing::XDrawPage> 
xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+                                                 uno::UNO_QUERY);
+    uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), 
uno::UNO_QUERY);
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue("CustomShapeGeometry") >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+
+    // Without the fix the property "TextRotateAngle" does not exist.
+    comphelper::SequenceAsHashMap::iterator it = 
aGeoPropMap.find("TextRotateAngle");
+    CPPUNIT_ASSERT(it != aGeoPropMap.end());
+    sal_Int32 nAngle;
+    // MS 720000 clockwise -> ODF -12deg counter-clockwise
+    it->second >>= nAngle;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(-12), nAngle);
+}
+
+CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, testTextRot)
+{
+    // tdf#149551 The document contains a shape with attribute 'rot="720000"' 
of the <bodyPr> element.
+    // Without fix, the text rotation angle was saved in "TextPreRotateAngle" 
instead of
+    // "TextRotateAngle". That resulted in unrotated but sheared text.
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"tdf149551_TextRot.pptx";
+    load(aURL);
+
+    uno::Reference<drawing::XDrawPagesSupplier> 
xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+    uno::Reference<drawing::XDrawPage> 
xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+                                                 uno::UNO_QUERY);
+    uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), 
uno::UNO_QUERY);
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue("CustomShapeGeometry") >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+
+    // Without the fix the property "TextRotateAngle" does not exist.
+    comphelper::SequenceAsHashMap::iterator it = 
aGeoPropMap.find("TextRotateAngle");
+    CPPUNIT_ASSERT(it != aGeoPropMap.end());
+    sal_Int32 nAngle;
+    // MS 720000 clockwise -> ODF -12deg counter-clockwise
+    it->second >>= nAngle;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(-12), nAngle);
+
+    // Because writing mode is LR_TB, the property "TextPreRotateAngle" may 
missing, or in case it
+    // exists, its value must be 0. Without fix it had value -12.
+    it = aGeoPropMap.find("TextPreRotateAngle");
+    if (it != aGeoPropMap.end())
+    {
+        it->second >>= nAngle;
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nAngle);
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx
index 90089a994fa2..be4fb81fa0bf 100644
--- a/oox/qa/unit/export.cxx
+++ b/oox/qa/unit/export.cxx
@@ -770,6 +770,28 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf148784StretchCommandVW)
               .toInt32();
     CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchY", nHalfHeight, nHR);
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf149551VertPadding)
+{
+    // The document has shape[1] with attribute vert="vert270" and shape[2] 
with vert="vert". The text
+    // has paddings lIns="720000"=2cm, tIns="360000"=1cm, rIns="0" and 
bIns="0".
+    // After load and save the paddings were rotated and a 90deg text rotation 
was added.
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"tdf149551_vert_and_padding.pptx";
+    loadAndSave(aURL, "Impress Office Open XML");
+
+    // Verify the markup. The values must be the same as in the original file.
+    std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), 
"ppt/slides/slide1.xml");
+    xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get());
+    for (sal_Int32 i = 1; i <= 2; i++)
+    {
+        OString sElement = "//p:spTree/p:sp[" + OString::number(i) + 
"]/p:txBody/a:bodyPr";
+        assertXPath(pXmlDoc, sElement, "lIns", "720000");
+        assertXPath(pXmlDoc, sElement, "tIns", "360000");
+        assertXPath(pXmlDoc, sElement, "rIns", "0");
+        assertXPath(pXmlDoc, sElement, "bIns", "0");
+        assertXPathNoAttribute(pXmlDoc, sElement, "rot");
+    }
+}
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/oox/source/drawingml/chart/axisconverter.cxx 
b/oox/source/drawingml/chart/axisconverter.cxx
index ba22dc5e028e..9e749d6b54c0 100644
--- a/oox/source/drawingml/chart/axisconverter.cxx
+++ b/oox/source/drawingml/chart/axisconverter.cxx
@@ -274,9 +274,9 @@ void AxisConverter::convertFromModel(const 
Reference<XCoordinateSystem>& rxCoord
                     // do not overlap text unless the rotation is 0 in xml
                     bool bTextOverlap = false;
                     if (mrModel.mxTextProp.is()
-                        && 
mrModel.mxTextProp->getTextProperties().moRotation.has_value())
+                        && 
mrModel.mxTextProp->getTextProperties().moTextAreaRotation.has_value())
                         bTextOverlap
-                            = 
mrModel.mxTextProp->getTextProperties().moRotation.value() == 0;
+                            = 
mrModel.mxTextProp->getTextProperties().moTextAreaRotation.value() == 0;
                     aAxisProp.setProperty(PROP_TextOverlap, bTextOverlap);
                     /* do not break text into several lines unless the 
rotation is 0 degree,
                        or the rotation is 90 degree and the inner size of the 
chart is not fixed,
diff --git a/oox/source/drawingml/chart/objectformatter.cxx 
b/oox/source/drawingml/chart/objectformatter.cxx
index f716ece56f2c..14c96d166a03 100644
--- a/oox/source/drawingml/chart/objectformatter.cxx
+++ b/oox/source/drawingml/chart/objectformatter.cxx
@@ -1067,7 +1067,7 @@ void ObjectFormatter::convertTextRotation( PropertySet& 
rPropSet, const ModelRef
 
     /*  Chart2 expects rotation angle as double value in range of [0,360).
         OOXML counts clockwise, Chart2 counts counterclockwise. */
-    double fAngle = static_cast< double >( bStacked ? 0 : 
rxTextProp->getTextProperties().moRotation.value_or( nDefaultRotation ) );
+    double fAngle = static_cast< double >( bStacked ? 0 : 
rxTextProp->getTextProperties().moTextAreaRotation.value_or( nDefaultRotation ) 
);
     // MS Office UI allows values only in range of [-90,90].
     if ( fAngle < -5400000.0 || fAngle > 5400000.0 )
     {
diff --git a/oox/source/drawingml/customshapeproperties.cxx 
b/oox/source/drawingml/customshapeproperties.cxx
index b0b6d34d23fe..2c3204405c2b 100644
--- a/oox/source/drawingml/customshapeproperties.cxx
+++ b/oox/source/drawingml/customshapeproperties.cxx
@@ -127,6 +127,8 @@ void CustomShapeProperties::pushToPropSet(
         aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
         aPropertyMap.setProperty( PROP_TextPreRotateAngle, mnTextRotateAngle );
         aPropertyMap.setProperty( PROP_TextCameraZRotateAngle, 
mnTextCameraZRotateAngle );
+        if (moTextAreaRotateAngle.has_value())
+            aPropertyMap.setProperty(PROP_TextRotateAngle, 
moTextAreaRotateAngle.value());
         Sequence< PropertyValue > aSeq = 
aPropertyMap.makePropertyValueSequence();
         aPropSet.setProperty( PROP_CustomShapeGeometry, aSeq );
 
@@ -189,6 +191,8 @@ void CustomShapeProperties::pushToPropSet(
         aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
         if( mnTextRotateAngle )
             aPropertyMap.setProperty( PROP_TextPreRotateAngle, 
mnTextRotateAngle );
+        if (moTextAreaRotateAngle.has_value())
+            aPropertyMap.setProperty(PROP_TextRotateAngle, 
moTextAreaRotateAngle.value());
         // Note 1: If Equations are defined - they are processed using 
internal div by 360 coordinates
         // while if they are not, standard ooxml coordinates are used.
         // This size specifically affects scaling.
diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx 
b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
index c38ea30e5d6f..225c0a1e6e3c 100644
--- a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
+++ b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
@@ -1757,13 +1757,13 @@ void AlgAtom::layoutShape(const ShapePtr& rShape, const 
std::vector<Constraint>&
                         n90x = -2;
                     else if (nShapeRot > 45 * PER_DEGREE)
                         n90x = -1;
-                    pTextBody->getTextProperties().moRotation = n90x * 90 * 
PER_DEGREE;
+                    pTextBody->getTextProperties().moTextPreRotation = n90x * 
90 * PER_DEGREE;
                 }
                 break;
                 case XML_grav:
                 {
                     if (nShapeRot > (90 * PER_DEGREE) && nShapeRot < (270 * 
PER_DEGREE))
-                        pTextBody->getTextProperties().moRotation = -180 * 
PER_DEGREE;
+                        pTextBody->getTextProperties().moTextPreRotation = 
-180 * PER_DEGREE;
                 }
                 break;
                 case XML_none:
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index 20564b584f76..a04a5725308f 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -1437,6 +1437,7 @@ Reference< XShape > const & Shape::createAndInsert(
             }
             else if (mbTextBox)
             {
+                // ToDo: TextBox has no rotated text, so indroduce it only if 
really needed. tdf#82627
                 aShapeProps.setProperty(PROP_TextBox, true);
             }
 
@@ -1688,30 +1689,35 @@ Reference< XShape > const & Shape::createAndInsert(
                 sal_Int32 nTextCameraZRotation = 
getTextBody()->get3DProperties().maCameraRotation.mnRevolution.value_or(0);
                 mpCustomShapePropertiesPtr->setTextCameraZRotateAngle( 
nTextCameraZRotation / 60000 );
 
-                sal_Int32 nTextRotateAngle = static_cast< sal_Int32 >( 
getTextBody()->getTextProperties().moRotation.value_or( 0 ) );
+                // TextPreRotateAngle. Text rotates inside the text area.
+                sal_Int32 nTextPreRotateAngle = static_cast< sal_Int32 >( 
getTextBody()->getTextProperties().moTextPreRotation.value_or( 0 ) );
 
-                nTextRotateAngle -= mnDiagramRotation;
-                /* OOX measures text rotation clockwise in 1/60000th degrees,
-                   relative to the containing shape. setTextRotateAngle wants 
degrees anticlockwise. */
-                nTextRotateAngle = -1 * nTextRotateAngle / 60000;
+                nTextPreRotateAngle -= mnDiagramRotation;
 
+                // TextRotateAngle. The text area rotates.
+                sal_Int32 nTextAreaRotateAngle = 
getTextBody()->getTextProperties().moTextAreaRotation.value_or(0);
                 if (getTextBody()->getTextProperties().moUpright)
                 {
-                    // When upright is set, we want the text without any 
rotation.
-                    // But if we set 0 here, the text is still rotated if the
-                    // shape containing it is rotated.
-                    // Hence, we rotate the text into the opposite direction of
-                    // the rotation of the shape, by as much as the shape was 
rotated.
-                    mpCustomShapePropertiesPtr->setTextRotateAngle((mnRotation 
/ 60000) + nTextRotateAngle);
-                    // Also put the initial angles away in a GrabBag.
+                    // When upright is set, any text area transformation and 
shape rotation is ignored
+                    // in MS Office. To simulate this behaviour, we rotate the 
text area into the
+                    // opposite direction of the shape rotation by as much as 
the shape was rotated
+                    // and so compensate the shape rotation, which is added in 
rendering.
+                    nTextAreaRotateAngle = -mnRotation;
+                    // If 45° <= shape rotation < 135° or 225° <= shape 
rotation < 315°,
+                    // then MS Office adds an additional 90° rotation to the 
text area.
+                    const sal_Int32 nDeg(mnRotation / 60000);
+                    if ((nDeg >= 45 && nDeg < 135) || (nDeg >= 225 && nDeg < 
315))
+                    {
+                        nTextAreaRotateAngle += 5400000;
+                        nTextPreRotateAngle -= 5400000; // compensate the 
addional text area rotation
+                    }
                     putPropertyToGrabBag("Upright", Any(true));
-                    putPropertyToGrabBag("nShapeRotationAtImport", 
Any(mnRotation / 60000));
-                    putPropertyToGrabBag("nTextRotationAtImport", 
Any(nTextRotateAngle));
-                }
-                else
-                {
-                    
mpCustomShapePropertiesPtr->setTextRotateAngle(nTextRotateAngle);
                 }
+                /* OOX measures text rotation clockwise in 1/60000th degrees,
+                   relative to the containing shape. set*Angle wants degrees 
counter-clockwise. */
+                
mpCustomShapePropertiesPtr->setTextRotateAngle(-nTextPreRotateAngle / 60000);
+                if (nTextAreaRotateAngle != 0)
+                    
mpCustomShapePropertiesPtr->setTextAreaRotateAngle(-nTextAreaRotateAngle / 
60000);
 
                 auto sHorzOverflow = 
getTextBody()->getTextProperties().msHorzOverflow;
                 if (!sHorzOverflow.isEmpty())
diff --git a/oox/source/drawingml/shapecontext.cxx 
b/oox/source/drawingml/shapecontext.cxx
index 10dc25b82c5c..92adac196c71 100644
--- a/oox/source/drawingml/shapecontext.cxx
+++ b/oox/source/drawingml/shapecontext.cxx
@@ -32,6 +32,7 @@
 #include <oox/token/namespaces.hxx>
 #include <oox/token/tokens.hxx>
 #include <sal/log.hxx>
+#include <drawingml/transform2dcontext.hxx>
 
 using namespace oox::core;
 using namespace ::com::sun::star;
@@ -95,13 +96,13 @@ ContextHandlerRef ShapeContext::onCreateContext( sal_Int32 
aElementToken, const
             mpShapePtr->setTextBody( std::make_shared<TextBody>() );
         return new TextBodyContext( *this, mpShapePtr );
     }
-    case XML_txXfrm:
+    case XML_txXfrm: // diagram shape. [MS-ODRAWXML]
     {
         const TextBodyPtr& rShapePtr = mpShapePtr->getTextBody();
         if (rShapePtr)
-            rShapePtr->getTextProperties().moRotation = rAttribs.getInteger( 
XML_rot );
-        return nullptr;
+            return new oox::drawingml::Transform2DContext( *this, rAttribs, 
*mpShapePtr, true );
     }
+        break;
     case XML_cNvSpPr:
         break;
     case XML_spLocks:
diff --git a/oox/source/drawingml/textbodyproperties.cxx 
b/oox/source/drawingml/textbodyproperties.cxx
index e7cad96fb235..5cea05256462 100644
--- a/oox/source/drawingml/textbodyproperties.cxx
+++ b/oox/source/drawingml/textbodyproperties.cxx
@@ -65,7 +65,7 @@ void TextBodyProperties::pushVertSimulation()
         maPropertyMap.setProperty( PROP_TextHorizontalAdjust, 
TextHorizontalAdjust_CENTER);
 }
 
-/* Push text distances / insets, taking into consideration Shape Rotation */
+/* Push text distances / insets, taking into consideration text rotation */
 void TextBodyProperties::pushTextDistances(Size const& rTextAreaSize)
 {
     for (auto & rValue : maTextDistanceValues)
@@ -79,7 +79,7 @@ void TextBodyProperties::pushTextDistances(Size const& 
rTextAreaSize)
         PROP_TextLowerDistance
     };
 
-    switch (moRotation.value_or(0))
+    switch (moTextPreRotation.value_or(0))
     {
         case 90*1*60000: nOff = 3; break;
         case 90*2*60000: nOff = 2; break;
@@ -87,6 +87,9 @@ void TextBodyProperties::pushTextDistances(Size const& 
rTextAreaSize)
         default: break;
     }
 
+    if (moVert && moVert.value() == XML_eaVert)
+        nOff = (nOff + 3) % aProps.size();
+
     for (size_t i = 0; i < aProps.size(); i++)
     {
         sal_Int32 nVal = 0;
@@ -100,17 +103,12 @@ void TextBodyProperties::pushTextDistances(Size const& 
rTextAreaSize)
         if (nOff == 1 && moTextOffUpper)
             nVal = *moTextOffUpper;
 
-
         if (nOff == 2 && moTextOffRight)
             nVal = *moTextOffRight;
 
         if (nOff == 3 && moTextOffLower)
             nVal = *moTextOffLower;
 
-
-        if( nVal < 0 )
-            nVal = 0;
-
         sal_Int32 nTextOffsetValue = nVal;
 
         if (moInsets[i])
diff --git a/oox/source/drawingml/textbodypropertiescontext.cxx 
b/oox/source/drawingml/textbodypropertiescontext.cxx
index d1e5a7669227..fa3314ada35a 100644
--- a/oox/source/drawingml/textbodypropertiescontext.cxx
+++ b/oox/source/drawingml/textbodypropertiescontext.cxx
@@ -102,7 +102,8 @@ TextBodyPropertiesContext::TextBodyPropertiesContext( 
ContextHandler2Helper cons
     }
 
     // ST_Angle
-    mrTextBodyProp.moRotation = rAttribs.getInteger( XML_rot );
+    if (rAttribs.getInteger(XML_rot).has_value())
+        mrTextBodyProp.moTextAreaRotation = 
rAttribs.getInteger(XML_rot).value();
 
 //   bool bRtlCol = rAttribs.getBool( XML_rtlCol, false );
     // ST_PositiveCoordinate
@@ -117,10 +118,12 @@ TextBodyPropertiesContext::TextBodyPropertiesContext( 
ContextHandler2Helper cons
     if( rAttribs.hasAttribute( XML_vert ) ) {
         mrTextBodyProp.moVert = rAttribs.getToken( XML_vert );
         sal_Int32 tVert = mrTextBodyProp.moVert.value_or( XML_horz );
-        if (tVert == XML_vert || tVert == XML_eaVert || tVert == 
XML_mongolianVert)
-            mrTextBodyProp.moRotation = 5400000;
+        if (tVert == XML_eaVert)
+            mrTextBodyProp.maPropertyMap.setProperty(PROP_TextWritingMode, 
WritingMode_TB_RL);
+        else if (tVert == XML_vert || tVert == XML_mongolianVert)
+            mrTextBodyProp.moTextPreRotation = 5400000;
         else if (tVert == XML_vert270)
-            mrTextBodyProp.moRotation = 5400000 * 3;
+            mrTextBodyProp.moTextPreRotation = 5400000 * 3;
         else {
             bool bRtl = rAttribs.getBool( XML_rtl, false );
             mrTextBodyProp.maPropertyMap.setProperty( PROP_TextWritingMode,
diff --git a/oox/source/drawingml/transform2dcontext.cxx 
b/oox/source/drawingml/transform2dcontext.cxx
index b73e3d7c8a62..d583c8f32c3b 100644
--- a/oox/source/drawingml/transform2dcontext.cxx
+++ b/oox/source/drawingml/transform2dcontext.cxx
@@ -17,6 +17,8 @@
  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
  */
 
+#include <cmath>
+
 #include <drawingml/transform2dcontext.hxx>
 #include <oox/helper/attributelist.hxx>
 #include <oox/drawingml/shape.hxx>
@@ -24,6 +26,8 @@
 #include <drawingml/textbody.hxx>
 #include <oox/token/namespaces.hxx>
 
+#include <com/sun/star/awt/Rectangle.hpp>
+
 using namespace ::com::sun::star;
 using ::oox::core::ContextHandlerRef;
 
@@ -42,56 +46,243 @@ Transform2DContext::Transform2DContext( 
ContextHandler2Helper const & rParent, c
     }
     else
     {
-        if( rAttribs.hasAttribute( XML_rot ) )
-            mrShape.getTextBody()->getTextProperties().moRotation = 
rAttribs.getInteger( XML_rot, 0 );
+        if (rAttribs.hasAttribute(XML_rot))
+        {
+            mno_txXfrmRot = rAttribs.getInteger(XML_rot, 0);
+            sal_Int32 nTextAreaRot = 
mrShape.getTextBody()->getTextProperties().moTextAreaRotation.value_or(0);
+            mrShape.getTextBody()->getTextProperties().moTextAreaRotation = 
mno_txXfrmRot.value() + nTextAreaRot;
+        }
     }
 }
 
+namespace
+{
+bool ConstructPresetTextRectangle(Shape& rShape, awt::Rectangle& rRect)
+{
+    // When we are here, we have neither xShape nor a SdrObject. So need to 
manually calc the text
+    // area rectangle defined in the preset in OOXML standard, but only for 
those types of shapes
+    // where we know, that MS Office SmartArt presets do not use the default 
text area rectangle.
+    const sal_Int32 nType = 
rShape.getCustomShapeProperties()->getShapePresetType();
+    switch (nType)
+    {
+        case XML_ellipse:
+            // The preset text rectangle touches the perimeter of the ellipse 
at 45deg.
+            rRect.X = rShape.getPosition().X + rShape.getSize().Width * ((1.0 
- M_SQRT1_2) / 2.0);
+            rRect.Y = rShape.getPosition().Y + rShape.getSize().Height * ((1.0 
- M_SQRT1_2) / 2.0);
+            rRect.Width = rShape.getSize().Width * M_SQRT1_2;
+            rRect.Height = rShape.getSize().Height * M_SQRT1_2;
+            return true;
+        case XML_roundRect:
+        case XML_round2SameRect:
+        {
+            // Second handle of round2SameRect used in preset diagrams has 
value 0.
+            auto aAdjGdList = 
rShape.getCustomShapeProperties()->getAdjustmentGuideList();
+            double fAdj = aAdjGdList.empty() ? 16667 : 
aAdjGdList[0].maFormula.toDouble();
+            sal_Int32 nWidth = rShape.getSize().Width;
+            sal_Int32 nHeight = rShape.getSize().Height;
+            if (nWidth == 0 || nHeight == 0)
+                return false;
+            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
+            std::clamp<double>(fAdj, 0, fMaxAdj);
+            sal_Int32 nTextLeft = std::min(nWidth, nHeight) * fAdj / 100000.0 
* 0.29289;
+            sal_Int32 nTextTop = nTextLeft;
+            rRect.X = rShape.getPosition().X + nTextLeft;
+            rRect.Y = rShape.getPosition().Y + nTextTop;
+            rRect.Width = nWidth - 2 * nTextLeft;
+            rRect.Height = nHeight - (nType == XML_roundRect ? 2 : 1) * 
nTextTop;
+            return true;
+        }
+        case XML_trapezoid:
+        {
+            auto aAdjGdList = 
rShape.getCustomShapeProperties()->getAdjustmentGuideList();
+            double fAdj = aAdjGdList.empty() ? 25000 : 
aAdjGdList[0].maFormula.toDouble();
+            sal_Int32 nWidth = rShape.getSize().Width;
+            sal_Int32 nHeight = rShape.getSize().Height;
+            if (nWidth == 0 || nHeight == 0)
+                return false;
+            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
+            std::clamp<double>(fAdj, 0, fMaxAdj);
+            sal_Int32 nTextLeft = nWidth / 3.0 * fAdj / fMaxAdj;
+            sal_Int32 nTextTop = nHeight / 3.0 * fAdj / fMaxAdj;
+            rRect.X = rShape.getPosition().X + nTextLeft;
+            rRect.Y = rShape.getPosition().Y + nTextTop;
+            rRect.Width = nWidth - 2 * nTextLeft;
+            rRect.Height = nHeight - 2 * nTextTop;
+            return true;
+        }
+        case XML_flowChartManualOperation:
+        {
+            sal_Int32 nWidth = rShape.getSize().Width;
+            sal_Int32 nTextLeft = nWidth / 5;
+            rRect.X = rShape.getPosition().X + nTextLeft;
+            rRect.Y = rShape.getPosition().Y;
+            rRect.Width = nWidth - 2 * nTextLeft;
+            rRect.Height = rShape.getSize().Height;
+            return true;
+        }
+        case XML_pie:
+        case XML_rect:
+        case XML_wedgeRectCallout:
+        {
+            // When tdf#149918 is fixed, pie will need its own case
+            rRect.X = rShape.getPosition().X;
+            rRect.Y = rShape.getPosition().Y;
+            rRect.Width = rShape.getSize().Width;
+            rRect.Height = rShape.getSize().Height;
+            return true;
+        }
+        case XML_gear6:
+        {
+            // The identifiers here reflect the guides name value in 
presetShapeDefinitions.xml
+            double w = rShape.getSize().Width;
+            double h = rShape.getSize().Height;
+            if (w <= 0 || h <= 0)
+                return false;
+            double a1(15000.0);
+            double a2(3526.0);
+            auto aAdjGdList = 
rShape.getCustomShapeProperties()->getAdjustmentGuideList();
+            if (aAdjGdList.size() == 2)
+            {
+                a1 = aAdjGdList[0].maFormula.toDouble();
+                a2 = aAdjGdList[1].maFormula.toDouble();
+                std::clamp<double>(a1, 0, 20000);
+                std::clamp<double>(a2, 0, 5358);
+            }
+            double th = std::min(w, h) * a1 / 100000.0;
+            double l2 = std::min(w, h) * a2 / 100000.0 / 2.0;
+            double l3 = th / 2.0 + l2;
+
+            double rh = h / 2.0 - th;
+            double rw = w / 2.0 - th;
+
+            double maxr = std::min(rw, rh);
+            double ha = atan2(l3, maxr);
+
+            double aA1 = basegfx::deg2rad(330) - ha;
+            double ta11 = rw * cos(aA1);
+            double ta12 = rh * sin(aA1);
+            double bA1 = atan2(ta12, ta11);
+            double cta1 = rh * cos(bA1);
+            double sta1 = rw * sin(bA1);
+            double ma1 = std::hypot(cta1, sta1);
+            double na1 = rw * rh / ma1;
+            double dxa1 = na1 * cos(bA1);
+            double dya1 = na1 * sin(bA1);
+
+            double xA1 = w / 2.0 + dxa1; // r
+            double yA1 = h / 2.0 + dya1; // t
+            double yD2 = h - yA1; // b
+            double xD5 = w - xA1; // l
+
+            rRect.X = rShape.getPosition().X + xD5;
+            rRect.Y = rShape.getPosition().Y + yA1;
+            rRect.Width = xA1 - xD5;
+            rRect.Height = yD2 - yA1;
+            return true;
+        }
+        case XML_hexagon:
+        {
+            auto aAdjGdList = 
rShape.getCustomShapeProperties()->getAdjustmentGuideList();
+            double fAdj = aAdjGdList.empty() ? 25000 : 
aAdjGdList[0].maFormula.toDouble();
+            sal_Int32 nWidth = rShape.getSize().Width;
+            sal_Int32 nHeight = rShape.getSize().Height;
+            if (nWidth == 0 || nHeight == 0)
+                return false;
+            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
+            std::clamp<double>(fAdj, 0, fMaxAdj);
+            double fFactor = fAdj/fMaxAdj/6.0 + 1.0/12.0;
+            sal_Int32 nTextLeft = nWidth * fFactor;
+            sal_Int32 nTextTop = nHeight * fFactor;
+            rRect.X = rShape.getPosition().X + nTextLeft;
+            rRect.Y = rShape.getPosition().Y + nTextTop;
+            rRect.Width = nWidth - 2 * nTextLeft;
+            rRect.Height = nHeight - 2 * nTextTop;
+            return true;
+        }
+        default:
+            return false;
+    }
+}
+}
+
 ContextHandlerRef Transform2DContext::onCreateContext( sal_Int32 
aElementToken, const AttributeList& rAttribs )
 {
     if( mbtxXfrm )
     {
-        // Workaround: only for rectangles
-        const sal_Int32 nType = 
mrShape.getCustomShapeProperties()->getShapePresetType();
-        if( nType == XML_rect || nType == XML_roundRect || nType == 
XML_ellipse )
+        // The child elements <a:off> and <a:ext> of a <dsp:txXfrm> element 
describe the position and
+        // size of the text area rectangle. We cannot change the text area 
rectangle directly, because
+        // currently we depend on the geometry definition of the preset. As 
workaround we change the
+        // indents to move and scale the text block. The needed shifts are 
calculated here as moTextOff
+        // and used in TextBodyProperties::pushTextDistances().
+        awt::Rectangle aPresetTextRectangle;
+        if (!ConstructPresetTextRectangle(mrShape, aPresetTextRectangle))
+            return nullptr; // faulty shape or corrections from txXfrm not 
needed.
+
+        switch (aElementToken)
         {
-            switch( aElementToken )
+            case A_TOKEN(off):
             {
-                case A_TOKEN( off ):
-                    {
-                        const OUString sXValue = rAttribs.getStringDefaulted( 
XML_x );
-                        const OUString sYValue = rAttribs.getStringDefaulted( 
XML_y );
-
-                        if( !sXValue.isEmpty() && nType != XML_ellipse )
-                            
mrShape.getTextBody()->getTextProperties().moTextOffLeft = GetCoordinate( 
sXValue.toInt32() - mrShape.getPosition().X );
-                        if( !sYValue.isEmpty() )
-                            
mrShape.getTextBody()->getTextProperties().moTextOffUpper = GetCoordinate( 
sYValue.toInt32() - mrShape.getPosition().Y );
-                    }
-                    break;
-                case A_TOKEN( ext ):
-                    {
-                        const OUString sXValue = rAttribs.getStringDefaulted( 
XML_cx );
-                        const OUString sYValue = rAttribs.getStringDefaulted( 
XML_cy );
-
-                        if( !sXValue.isEmpty() && nType == XML_rect )
-                        {
-                            
mrShape.getTextBody()->getTextProperties().moTextOffRight = 
GetCoordinate(mrShape.getSize().Width - sXValue.toInt32());
-                            if( 
mrShape.getTextBody()->getTextProperties().moTextOffLeft )
-                               
*mrShape.getTextBody()->getTextProperties().moTextOffRight -=  
*mrShape.getTextBody()->getTextProperties().moTextOffLeft;
-                        }
-                        if( !sYValue.isEmpty() )
-                        {
-                            
mrShape.getTextBody()->getTextProperties().moTextOffLower = 
GetCoordinate(mrShape.getSize().Height - sYValue.toInt32());
-                            if( 
mrShape.getTextBody()->getTextProperties().moTextOffUpper )
-                               
*mrShape.getTextBody()->getTextProperties().moTextOffLower -=  
*mrShape.getTextBody()->getTextProperties().moTextOffUpper;
-
-                        }
-                    }
-                    break;
+                // need <a:ext> too, so only save values here.
+                const OUString sXValue = rAttribs.getStringDefaulted(XML_x);
+                const OUString sYValue = rAttribs.getStringDefaulted(XML_y);
+                if (!sXValue.isEmpty() && !sYValue.isEmpty())
+                {
+                    mno_txXfrmOffX = sXValue.toInt32();
+                    mno_txXfrmOffY = sYValue.toInt32();
+                }
             }
+            break;
+            case A_TOKEN(ext):
+            {
+                // Build text frame from txXfrm element
+                awt::Rectangle aUnrotatedTxXfrm = aPresetTextRectangle; // 
dummy initialize
+                const OUString sCXValue = rAttribs.getStringDefaulted(XML_cx);
+                const OUString sCYValue = rAttribs.getStringDefaulted(XML_cy);
+                if (!sCXValue.isEmpty() && !sCYValue.isEmpty() && 
mno_txXfrmOffX.has_value()
+                    && mno_txXfrmOffY.has_value())
+                {
+                    sal_Int32 ntxXfrmWidth = sCXValue.toInt32();
+                    sal_Int32 ntxXfrmHeight = sCYValue.toInt32();
+                    aUnrotatedTxXfrm.X = mno_txXfrmOffX.value();
+                    aUnrotatedTxXfrm.Y = mno_txXfrmOffY.value();
+                    aUnrotatedTxXfrm.Width = ntxXfrmWidth;
+                    aUnrotatedTxXfrm.Height = ntxXfrmHeight;
+                }
+                else if (mno_txXfrmOffX.has_value() && 
mno_txXfrmOffY.has_value()) // can it happen?
+                {
+                    aUnrotatedTxXfrm.X = mno_txXfrmOffX.value();
+                    aUnrotatedTxXfrm.Y = mno_txXfrmOffY.value();
+                }
+                else if (!sCXValue.isEmpty() && !sCYValue.isEmpty()) // can it 
happen?
+                {
+                    aUnrotatedTxXfrm.Width = sCXValue.toInt32();
+                    aUnrotatedTxXfrm.Height = sCYValue.toInt32();
+                }
+                // Calculate indent offsets
+                sal_Int32 nOffsetLeft = aUnrotatedTxXfrm.X - 
aPresetTextRectangle.X;
+                sal_Int32 nOffsetTop = aUnrotatedTxXfrm.Y - 
aPresetTextRectangle.Y;
+                sal_Int32 nOffsetRight
+                    = aPresetTextRectangle.Width - aUnrotatedTxXfrm.Width - 
nOffsetLeft;
+                sal_Int32 nOffsetBottom
+                    = aPresetTextRectangle.Height - aUnrotatedTxXfrm.Height - 
nOffsetTop;
+
+                if (nOffsetLeft)
+                    mrShape.getTextBody()->getTextProperties().moTextOffLeft
+                        = GetCoordinate(nOffsetLeft);
+                if (nOffsetTop)
+                    mrShape.getTextBody()->getTextProperties().moTextOffUpper
+                        = GetCoordinate(nOffsetTop);
+                if (nOffsetRight)
+                    mrShape.getTextBody()->getTextProperties().moTextOffRight
+                        = GetCoordinate(nOffsetRight);
+                if (nOffsetBottom)
+                    mrShape.getTextBody()->getTextProperties().moTextOffLower
+                        = GetCoordinate(nOffsetBottom);
+            }
+            break;
         }
         return nullptr;
-    }
+    } // end of case mbtxXfrm
 
     switch( aElementToken )
     {
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index fe6635dc23f2..df9b039640d4 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -125,6 +125,7 @@
 #include <editeng/unonrule.hxx>
 #include <svx/svdoashp.hxx>
 #include <svx/svdomedia.hxx>
+#include <svx/svdtrans.hxx>
 #include <svx/unoshape.hxx>
 #include <svx/EnhancedCustomShape2d.hxx>
 #include <drawingml/presetgeometrynames.hxx>
@@ -3278,9 +3279,6 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
     uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY);
     uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY);
 
-    sal_Int32 nTextPreRotateAngle = 0;
-    double nTextRotateAngle = 0;
-
     constexpr const sal_Int32 constDefaultLeftRightInset = 254;
     constexpr const sal_Int32 constDefaultTopBottomInset = 127;
     sal_Int32 nLeft = constDefaultLeftRightInset;
@@ -3326,12 +3324,11 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
         mAny >>= eVerticalAlignment;
     sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment);
 
-    const char* sWritingMode = nullptr;
+    std::optional<OString> sWritingMode;
     bool bVertical = false;
     if (GetProperty(rXPropSet, "TextWritingMode"))
     {
         WritingMode eMode;
-
         if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL )
         {
             sWritingMode = "eaVert";
@@ -3339,13 +3336,14 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
         }
     }
 
-    bool bIsFontworkShape(IsFontworkShape(rXPropSet));
+    // read values from CustomShapeGeometry
     Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq;
     uno::Sequence<beans::PropertyValue> aTextPathSeq;
     bool bScaleX(false);
     OUString sShapeType("non-primitive");
-    // ToDo move to InteropGrabBag
     OUString sMSWordPresetTextWarp;
+    sal_Int32 nTextPreRotateAngle = 0; // degree
+    std::optional<Degree100> nTextRotateAngleDeg100; // text area rotation
 
     if (GetProperty(rXPropSet, "CustomShapeGeometry"))
     {
@@ -3354,23 +3352,16 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
         {
             for ( const auto& rProp : std::as_const(aProps) )
             {
-                if ( rProp.Name == "TextPreRotateAngle" && ( rProp.Value >>= 
nTextPreRotateAngle ) )
-                {
-                    if ( nTextPreRotateAngle == -90 )
-                    {
-                        sWritingMode = "vert";
-                        bVertical = true;
-                    }
-                    else if ( nTextPreRotateAngle == -270 )
-                    {
-                        sWritingMode = "vert270";
-                        bVertical = true;
-                    }
-                }
+                if (rProp.Name == "TextPreRotateAngle")
+                    rProp.Value >>= nTextPreRotateAngle;
                 else if (rProp.Name == "AdjustmentValues")
                     rProp.Value >>= aAdjustmentSeq;
-                else if( rProp.Name == "TextRotateAngle" )
-                    rProp.Value >>= nTextRotateAngle;
+                else if (rProp.Name == "TextRotateAngle")
+                {
+                    double fTextRotateAngle; // degree
+                    rProp.Value >>= fTextRotateAngle;
+                    nTextRotateAngleDeg100 = 
Degree100(std::lround(fTextRotateAngle * 100.0));
+                }
                 else if (rProp.Name == "Type")
                     rProp.Value >>= sShapeType;
                 else if (rProp.Name == "TextPath")
@@ -3419,6 +3410,39 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
             }
         }
     }
+
+    // read InteropGrabBag if any
+    std::optional<OUString> sHorzOverflow;
+    std::optional<OUString> sVertOverflow;
+    bool bUpright = false;
+    std::optional<OString> isUpright;
+    if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
+    {
+        uno::Sequence<beans::PropertyValue> aGrabBag;
+        rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
+        for (const auto& aProp : std::as_const(aGrabBag))
+        {
+            if (aProp.Name == "Upright")
+            {
+                aProp.Value >>= bUpright;
+                isUpright = OString(bUpright ? "1" : "0");
+            }
+            else if (aProp.Name == "horzOverflow")
+            {
+                OUString sValue;
+                aProp.Value >>= sValue;
+                sHorzOverflow = sValue;
+            }
+            else if (aProp.Name == "vertOverflow")
+            {
+                OUString sValue;
+                aProp.Value >>= sValue;
+                sVertOverflow = sValue;
+            }
+        }
+    }
+
+    bool bIsFontworkShape(IsFontworkShape(rXPropSet));
     OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType));
     // ODF may have user defined TextPath, use "textPlain" as ersatz.
     if (sPresetWarp.isEmpty())
@@ -3428,6 +3452,85 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
                         && ( sPresetWarp == "textArchDown" || sPresetWarp == 
"textArchUp"
                             || sPresetWarp == "textButton" || sPresetWarp == 
"textCircle");
 
+
+    if (bUpright)
+    {
+        Degree100 nShapeRotateAngleDeg100(0_deg100);
+        if (GetProperty(rXPropSet, "RotateAngle"))
+            nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>());
+        // Depending on shape rotation, the import has made 90deg changes to 
properties
+        // "TextPreRotateAngle" and "TextRotateAngle". Revert it.
+        if ((nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 
<= 13500_deg100)
+            || (nShapeRotateAngleDeg100 > 22500_deg100 && 
nShapeRotateAngleDeg100 <= 31500_deg100))
+        {
+            nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) 
+ 9000_deg100;
+            nTextPreRotateAngle -= 90;
+        }
+        // If text is no longer upright, user has changed something. Do not 
write 'upright' then.
+        Degree100 nAngleSum = nShapeRotateAngleDeg100 + 
nTextRotateAngleDeg100.value_or(0_deg100);
+        if (abs(NormAngle18000(nAngleSum)) >= 100_deg100) // consider 
inaccuracy from rounding
+        {
+            isUpright.reset();
+        }
+    }
+
+    // Evaluate "TextPreRotateAngle". It is used to simulate not yet 
implemented writing modes and it
+    // is used to simulate the 'rot' attribute from diagram <dgm:txXfrm> 
element. When we are here
+    // and export a diagram it is in fact an export of a group of shapes. For 
them a text rotation
+    // inside the text area rectangle is only possible by the "vert" attribute.
+    if (nTextPreRotateAngle != 0 && !sWritingMode)
+    {
+        if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270)
+        {
+            sWritingMode = "vert";
+            bVertical = true;
+            // Our TextPreRotation includes padding, MSO vert does not include 
padding. Therefore set
+            // padding so, that is looks the same in MSO.
+            sal_Int32 nHelp = nLeft;
+            nLeft = nBottom;
+            nBottom = nRight;
+            nRight = nTop;
+            nTop = nHelp;
+        }
+        else if (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90)
+        {
+            sWritingMode = "vert270";
+            bVertical = true;
+            sal_Int32 nHelp = nLeft;
+            nLeft = nTop;
+            nTop = nRight;
+            nRight = nBottom;
+            nBottom = nHelp;
+        }
+        else if (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180)
+        {
+            nTextRotateAngleDeg100
+                = NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 
18000_deg100);
+            // ToDo: Examine insets. They might need rotation too.
+        }
+        else
+            SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << 
nTextPreRotateAngle);
+    }
+    else if (nTextPreRotateAngle == 0 && sWritingMode && sWritingMode.value() 
== "eaVert")
+    {
+        sal_Int32 nHelp = nLeft;
+        nLeft = nBottom;
+        nBottom = nRight;
+        nRight = nTop;
+        nTop = nHelp;
+    }
+    else if (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() 
== "eaVert")
+    {
+        // ToDo: eaVert plus 270deg clockwise rotation has to be written with 
vert="horz"
+        // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element.
+    }
+    // else nothing to do
+
+    std::optional<OString> sTextRotateAngleMSUnit;
+    if (nTextRotateAngleDeg100.has_value())
+        sTextRotateAngleMSUnit
+            = 
oox::drawingml::calcRotationValue(nTextRotateAngleDeg100.value().get());
+
     TextHorizontalAdjust eHorizontalAlignment( TextHorizontalAdjust_CENTER );
     bool bHorizontalCenter = false;
     if (GetProperty(rXPropSet, "TextHorizontalAdjust"))
@@ -3459,9 +3562,6 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
                 pWrap = "square";
         }
 
-        std::optional<OUString> sHorzOverflow;
-        std::optional<OUString> sVertOverflow;
-        sal_Int32 nShapeRotateAngle = 
rXPropSet->getPropertyValue("RotateAngle").get<sal_Int32>() / 300;
         sal_Int16 nCols = 0;
         sal_Int32 nColSpacing = -1;
         if (GetProperty(rXPropSet, "TextColumns"))
@@ -3478,59 +3578,6 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
             }
         }
 
-        std::optional<OString> isUpright;
-        if (GetProperty(rXPropSet, "InteropGrabBag"))
-        {
-            if 
(rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
-            {
-                bool bUpright = false;
-                sal_Int32 nOldShapeRotation = 0;
-                sal_Int32 nOldTextRotation = 0;
-                uno::Sequence<beans::PropertyValue> aGrabBag;
-                rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
-                for (const auto& aProp : std::as_const(aGrabBag))
-                {
-                    if (aProp.Name == "Upright")
-                    {
-                        aProp.Value >>= bUpright;
-                        isUpright = OString(bUpright ? "1" : "0");
-                    }
-                    else if (aProp.Name == "horzOverflow")
-                    {
-                        OUString sValue;
-                        aProp.Value >>= sValue;
-                        sHorzOverflow = sValue;
-                    }
-                    else if (aProp.Name == "vertOverflow")
-                    {
-                        OUString sValue;
-                        aProp.Value >>= sValue;
-                        sVertOverflow = sValue;
-                    }
-                }
-                if (bUpright)
-                {
-                    for (const auto& aProp : std::as_const(aGrabBag))
-                    {
-                        if (aProp.Name == "nShapeRotationAtImport")
-                            aProp.Value >>= nOldShapeRotation;
-                        else if (aProp.Name == "nTextRotationAtImport")
-                            aProp.Value >>= nOldTextRotation;
-                    }
-                    // So our shape with the textbox in it was not rotated.
-                    // Keep upright and make the preRotateAngle 0, it is an 
attribute
-                    // of textBodyPr and must be 0 when upright is true, 
otherwise
-                    // bad rotation happens in MSO.
-                    if (nShapeRotateAngle == nOldShapeRotation && 
nShapeRotateAngle == nOldTextRotation)
-                        nTextPreRotateAngle = 0;
-                    // So we rotated the shape, in this case lose upright and 
do
-                    // as LO normally does.
-                    else
-                        isUpright.reset();
-                }
-            }
-        }
-
         mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), 
XML_bodyPr,
                                XML_numCol, 
sax_fastparser::UseIf(OString::number(nCols), nCols > 0),
                                XML_spcCol, 
sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)),
 nCols > 0 && nColSpacing >= 0),
@@ -3546,7 +3593,8 @@ void DrawingML::WriteText(const Reference<XInterface>& 
rXIface, bool bBodyPr, bo
                                XML_anchorCtr, sax_fastparser::UseIf("1", 
bHorizontalCenter),
                                XML_vert, sWritingMode,
                                XML_upright, isUpright,
-                               XML_rot, 
sax_fastparser::UseIf(oox::drawingml::calcRotationValue((nTextPreRotateAngle + 
nTextRotateAngle) * 100), (nTextPreRotateAngle + nTextRotateAngle) != 0));
+                               XML_rot, sTextRotateAngleMSUnit);
+
         if (bIsFontworkShape)
         {
             if (aAdjustmentSeq.hasElements())
diff --git a/oox/source/shape/WpsContext.cxx b/oox/source/shape/WpsContext.cxx
index 98d3375f7a1c..79fcf1c9dc81 100644
--- a/oox/source/shape/WpsContext.cxx
+++ b/oox/source/shape/WpsContext.cxx
@@ -64,6 +64,8 @@ oox::core::ContextHandlerRef 
WpsContext::onCreateContext(sal_Int32 nElementToken
         case XML_bodyPr:
             if (mxShape.is())
             {
+                // no evaluation of attribute XML_rot, because Word ignores 
it, as of 2022-07.
+
                 uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, 
uno::UNO_QUERY);
                 uno::Reference<beans::XPropertySet> xPropertySet(mxShape, 
uno::UNO_QUERY);
                 sal_Int32 nVert = rAttribs.getToken(XML_vert, XML_horz);
@@ -74,6 +76,12 @@ oox::core::ContextHandlerRef 
WpsContext::onCreateContext(sal_Int32 nElementToken
                 }
                 else if (nVert != XML_horz)
                 {
+                    // The UI of Word has only 'vert' and 'vert270'. Further 
values would be
+                    // 'mongolianVert', 'wordArtVert' and 'wordArtVertRtl'.
+                    const sal_Int32 nRotation = nVert == XML_vert270 ? -270 : 
-90;
+
+                    // Workaround for tdf#87924, produces bug tdf#149809 as of 
2022-07
+                    // If the text is not rotated the way the shape wants it 
already, set the angle.
                     // Get the existing rotation of the shape.
                     drawing::HomogenMatrix3 aMatrix;
                     xPropertySet->getPropertyValue("Transformation") >>= 
aMatrix;
@@ -81,11 +89,11 @@ oox::core::ContextHandlerRef 
WpsContext::onCreateContext(sal_Int32 nElementToken
                     aTransformation.set(0, 0, aMatrix.Line1.Column1);
                     aTransformation.set(0, 1, aMatrix.Line1.Column2);
                     aTransformation.set(0, 2, aMatrix.Line1.Column3);
-                    aTransformation.set(1, 0, aMatrix.Line1.Column1);
+                    aTransformation.set(1, 0, aMatrix.Line2.Column1);
                     aTransformation.set(1, 1, aMatrix.Line2.Column2);
-                    aTransformation.set(1, 2, aMatrix.Line3.Column3);
-                    aTransformation.set(2, 0, aMatrix.Line1.Column1);
-                    aTransformation.set(2, 1, aMatrix.Line2.Column2);
+                    aTransformation.set(1, 2, aMatrix.Line2.Column3);
+                    aTransformation.set(2, 0, aMatrix.Line3.Column1);
+                    aTransformation.set(2, 1, aMatrix.Line3.Column2);
                     aTransformation.set(2, 2, aMatrix.Line3.Column3);
                     basegfx::B2DTuple aScale;
                     basegfx::B2DTuple aTranslate;
@@ -93,8 +101,6 @@ oox::core::ContextHandlerRef 
WpsContext::onCreateContext(sal_Int32 nElementToken
                     double fShearX = 0;
                     aTransformation.decompose(aScale, aTranslate, fRotate, 
fShearX);
 
-                    // If the text is not rotated the way the shape wants it 
already, set the angle.
-                    const sal_Int32 nRotation = nVert == XML_vert270 ? -270 : 
-90;
                     if (static_cast<sal_Int32>(basegfx::rad2deg(fRotate))
                         != NormAngle36000(Degree100(nRotation * 100)).get() / 
100)
                     {
diff --git a/sc/qa/unit/subsequent_export_test2.cxx 
b/sc/qa/unit/subsequent_export_test2.cxx
index a49cb97f8376..11d069c632b1 100644
--- a/sc/qa/unit/subsequent_export_test2.cxx
+++ b/sc/qa/unit/subsequent_export_test2.cxx
@@ -2203,10 +2203,10 @@ void 
ScExportTest2::testTdf91251_missingOverflowRoundtrip()
 
 void ScExportTest2::testTdf137000_handle_upright()
 {
-    // tdf#106197 When exporting the "upright" attribute, we must set
-    // TextPreRotateAngle to 0.
-    // (Upright is an xml attribute of xdr:txBody/a:bodyPr. It is set when
-    // in a textbox menu we choose: do not rotate this element.)
+    // Upright is an xml attribute of xdr:txBody/a:bodyPr. It is set when in a 
textbox menu we
+    // choose, 'do not rotate this element'. Implementations are in tdf#106197 
with followup
+    // tdf#137000. tdf#149538, tdf#149551 improve the implementation to export 
'upright' instead
+    // of workaround 'rot'.
     ScDocShellRef xShell = loadDoc(u"tdf137000_export_upright.", FORMAT_XLSX);
 
     std::shared_ptr<utl::TempFile> pXPathFile = 
ScBootstrapFixture::exportTo(*xShell, FORMAT_XLSX);
@@ -2214,8 +2214,7 @@ void ScExportTest2::testTdf137000_handle_upright()
         = XPathHelper::parseExport(pXPathFile, m_xSFactory, 
"xl/drawings/drawing1.xml");
     CPPUNIT_ASSERT(pDrawing);
 
-    assertXPath(pDrawing, 
"/xdr:wsDr/xdr:twoCellAnchor/xdr:sp/xdr:txBody/a:bodyPr", "rot",
-                "-5400000");
+    assertXPath(pDrawing, 
"/xdr:wsDr/xdr:twoCellAnchor/xdr:sp/xdr:txBody/a:bodyPr", "upright", "1");
 }
 
 void ScExportTest2::testTdf126305_DataValidatyErrorAlert()
diff --git a/sd/qa/unit/import-tests-smartart.cxx 
b/sd/qa/unit/import-tests-smartart.cxx
index 371d8d81511b..e41a635dff8b 100644
--- a/sd/qa/unit/import-tests-smartart.cxx
+++ b/sd/qa/unit/import-tests-smartart.cxx
@@ -1689,7 +1689,7 @@ void SdImportTestSmartArt::testTdf134221()
     CPPUNIT_ASSERT(xShapeB.is());
     uno::Reference<beans::XPropertySet> xTxtProps(xShapeB, 
uno::UNO_QUERY_THROW);
 
-    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(736),
+    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-248),
                          
xTxtProps->getPropertyValue("TextUpperDistance").get<sal_Int32>());
 
     xDocShRef->DoClose();
diff --git a/sd/qa/unit/import-tests.cxx b/sd/qa/unit/import-tests.cxx
index 68161536da93..6c2d3084fb23 100644
--- a/sd/qa/unit/import-tests.cxx
+++ b/sd/qa/unit/import-tests.cxx
@@ -839,7 +839,7 @@ void SdImportTest::testN862510_2()
         CPPUNIT_ASSERT( pGrpObj );
         SdrObjCustomShape *pObj = dynamic_cast<SdrObjCustomShape *>( 
pGrpObj->GetSubList()->GetObj( 1 ) );
         CPPUNIT_ASSERT( pObj );
-        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong Text Rotation!", 90.0, 
pObj->GetExtraTextRotation( true ) );
+        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong Text Rotation!", 90.0, 
pObj->GetExtraTextRotation( false ) );
     }
 
     xDocShRef->DoClose();
@@ -1231,10 +1231,10 @@ void SdImportTest::testBnc870237()
     const SdrObjGroup* pGroupObj = dynamic_cast<SdrObjGroup*>( pPage->GetObj( 
0 ) );
     const SdrObject* pObj = pGroupObj->GetSubList()->GetObj( 1 );
     CPPUNIT_ASSERT_MESSAGE( "no object", pObj != nullptr);
-    CPPUNIT_ASSERT_EQUAL( sal_Int32(0), 
pObj->GetMergedItem(SDRATTR_TEXT_UPPERDIST).GetValue());
-    CPPUNIT_ASSERT_EQUAL( sal_Int32(9919), 
pObj->GetMergedItem(SDRATTR_TEXT_LOWERDIST).GetValue());
-    CPPUNIT_ASSERT_EQUAL( sal_Int32(0), 
pObj->GetMergedItem(SDRATTR_TEXT_RIGHTDIST).GetValue());
-    CPPUNIT_ASSERT_EQUAL( sal_Int32(0), 
pObj->GetMergedItem(SDRATTR_TEXT_LEFTDIST).GetValue());
+    CPPUNIT_ASSERT_EQUAL( sal_Int32(-158), 
pObj->GetMergedItem(SDRATTR_TEXT_UPPERDIST).GetValue());
+    CPPUNIT_ASSERT_EQUAL( sal_Int32(9760), 
pObj->GetMergedItem(SDRATTR_TEXT_LOWERDIST).GetValue());
+    CPPUNIT_ASSERT_EQUAL( sal_Int32(-158), 
pObj->GetMergedItem(SDRATTR_TEXT_RIGHTDIST).GetValue());
+    CPPUNIT_ASSERT_EQUAL( sal_Int32(-158), 
pObj->GetMergedItem(SDRATTR_TEXT_LEFTDIST).GetValue());
 
     xDocShRef->DoClose();
 }
@@ -1836,7 +1836,7 @@ void SdImportTest::testTdf93830()
 
     sal_Int32 nTextLeftDistance = 0;
     xPropSet->getPropertyValue( "TextLeftDistance" ) >>= nTextLeftDistance;
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(4152), nTextLeftDistance);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(4024), nTextLeftDistance);
 
     xDocShRef->DoClose();
 }
diff --git a/sw/qa/extras/ooxmlimport/ooxmlimport.cxx 
b/sw/qa/extras/ooxmlimport/ooxmlimport.cxx
index 6e22f8b9cb65..7cfdcb84a909 100644
--- a/sw/qa/extras/ooxmlimport/ooxmlimport.cxx
+++ b/sw/qa/extras/ooxmlimport/ooxmlimport.cxx
@@ -1361,7 +1361,7 @@ CPPUNIT_TEST_FIXTURE(Test, testFdo87488)
         CPPUNIT_ASSERT_EQUAL(props->getPropertyValue("RotateAngle"),
                              uno::Any(sal_Int32(270 * 100)));
         comphelper::SequenceAsHashMap 
geom(props->getPropertyValue("CustomShapeGeometry"));
-        CPPUNIT_ASSERT_EQUAL(sal_Int32(90), 
geom["TextPreRotateAngle"].get<sal_Int32>());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(90), 
geom["TextRotateAngle"].get<sal_Int32>());
     }
 }
 

Reply via email to