summaryrefslogtreecommitdiff
path: root/oox
diff options
context:
space:
mode:
authorRegina Henschel <rb.henschel@t-online.de>2022-06-25 00:07:43 +0200
committerMiklos Vajna <vmiklos@collabora.com>2022-07-20 08:16:03 +0200
commit7e23cbdbb6ec0247a29ed8a8f744c01e10963ea0 (patch)
tree4bcfb339a9763d475895990cde7be189c78b0312 /oox
parent65f1903921c448da61733ca0d47c2ffc862b201d (diff)
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 <vmiklos@collabora.com>
Diffstat (limited to 'oox')
-rw-r--r--oox/inc/drawingml/customshapeproperties.hxx4
-rw-r--r--oox/inc/drawingml/textbodyproperties.hxx5
-rw-r--r--oox/inc/drawingml/transform2dcontext.hxx5
-rw-r--r--oox/qa/unit/data/tdf149551_TextRot.pptxbin0 -> 15419 bytes
-rw-r--r--oox/qa/unit/data/tdf149551_vert270AndTextRot.pptxbin0 -> 15368 bytes
-rw-r--r--oox/qa/unit/data/tdf149551_vert_and_padding.pptxbin0 -> 25217 bytes
-rw-r--r--oox/qa/unit/drawingml.cxx63
-rw-r--r--oox/qa/unit/export.cxx22
-rw-r--r--oox/source/drawingml/chart/axisconverter.cxx4
-rw-r--r--oox/source/drawingml/chart/objectformatter.cxx2
-rw-r--r--oox/source/drawingml/customshapeproperties.cxx4
-rw-r--r--oox/source/drawingml/diagram/diagramlayoutatoms.cxx4
-rw-r--r--oox/source/drawingml/shape.cxx42
-rw-r--r--oox/source/drawingml/shapecontext.cxx7
-rw-r--r--oox/source/drawingml/textbodyproperties.cxx12
-rw-r--r--oox/source/drawingml/textbodypropertiescontext.cxx11
-rw-r--r--oox/source/drawingml/transform2dcontext.cxx267
-rw-r--r--oox/source/export/drawingml.cxx206
-rw-r--r--oox/source/shape/WpsContext.cxx18
19 files changed, 514 insertions, 162 deletions
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
--- /dev/null
+++ b/oox/qa/unit/data/tdf149551_TextRot.pptx
Binary files 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
--- /dev/null
+++ b/oox/qa/unit/data/tdf149551_vert270AndTextRot.pptx
Binary files 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
--- /dev/null
+++ b/oox/qa/unit/data/tdf149551_vert_and_padding.pptx
Binary files 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)
{