tdf#171225: don't lose camera rotation angle for mnShapePresetType < 0

For the case when mnShapePresetType >= 0, the code setting it was added
in commit c50e44b270 (tdf#126060 Handle
text camera z rotation while pptx import., 2019-10-16). But it omitted
the other case.

This change also improves positioning of the rotated text, initially
implemented in that commit. The rotation is now added to the block text
decomposition as a separate transform primitive. The text center is now
obtained directly from the decomposition. After the change, I see about
1-pixel difference compared to PowerPoint render at 100% zoom (before
that, the offset was tens of millimeters).

testTdf128212 had to be corrected, because the metafile now has a new
element in the parsed XML, and the final positioning has changed (it is
better now). Unfortunately, I don't know what would it now produce if
the fix is broken, so I had to remove respective comment.

This change does not fix tdf#128206.

Change-Id: I86cbd0710744b14e8b9b68a436affc5b08703a12
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201286
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
This commit is contained in:
Mike Kaganski
2026-03-10 07:01:48 +01:00
parent 779b6935c7
commit 04bc53cd7e
5 changed files with 88 additions and 22 deletions
@@ -227,6 +227,8 @@ void CustomShapeProperties::pushToPropSet(
aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
if( mnTextPreRotateAngle )
aPropertyMap.setProperty( PROP_TextPreRotateAngle, mnTextPreRotateAngle );
if (mnTextCameraZRotateAngle)
aPropertyMap.setProperty(PROP_TextCameraZRotateAngle, mnTextCameraZRotateAngle);
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
+19 -5
View File
@@ -105,11 +105,25 @@ CPPUNIT_TEST_FIXTURE(SdLayoutTest, testTdf128212)
createSdImpressDoc("pptx/tdf128212.pptx");
xmlDocUniquePtr pXmlDoc = parseLayout();
// Without the fix in place, this test would have failed with
// - Expected: 7795
// - Actual : 12068
assertXPath(pXmlDoc, "/metafile/push[1]/push[1]/textarray", "x", u"4523");
assertXPath(pXmlDoc, "/metafile/push[1]/push[1]/textarray", "y", u"7795");
// The position of the rotated text depends on the calculated text size. This depends on
// rendering, so it differs a bit on different platforms. Hence rather big delta here.
// translation
assertXPath(pXmlDoc, "//push[@flags='PushMapMode']", 1);
assertXPath(pXmlDoc, "//push[@flags='PushMapMode']/mapmode", "mapunit", u"MapRelative");
CPPUNIT_ASSERT_DOUBLES_EQUAL(
331.0, getXPath(pXmlDoc, "//push[@flags='PushMapMode']/mapmode", "x").toDouble(), 3.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(
9420.0, getXPath(pXmlDoc, "//push[@flags='PushMapMode']/mapmode", "y").toDouble(), 10.0);
// no scaling
assertXPath(pXmlDoc, "//push[@flags='PushMapMode']/mapmode", "scalex", u"(1/1)");
assertXPath(pXmlDoc, "//push[@flags='PushMapMode']/mapmode", "scaley", u"(1/1)");
// text position
CPPUNIT_ASSERT_DOUBLES_EQUAL(
4760.0, getXPath(pXmlDoc, "//push[@flags='PushMapMode']/textarray", "x").toDouble(), 3.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(
-2250.0, getXPath(pXmlDoc, "//push[@flags='PushMapMode']/textarray", "y").toDouble(), 3.0);
}
CPPUNIT_TEST_FIXTURE(SdLayoutTest, testColumnsLayout)
Binary file not shown.
+52
View File
@@ -17,6 +17,7 @@
#include <svx/sdr/contact/displayinfo.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/svdoashp.hxx>
#include <svx/svdpage.hxx>
#include <svx/unopage.hxx>
#include <vcl/virdev.hxx>
@@ -189,6 +190,57 @@ CPPUNIT_TEST_FIXTURE(SdrTest, testSlideBackground)
// i.e. the rendering did not find the bitmap.
assertXPath(pDocument, "//bitmap", 1);
}
CPPUNIT_TEST_FIXTURE(SdrTest, test3DRotatedText)
{
// The document contains a shape with text "Vertical" and a 3D scene camera rotation of 90 deg.
// The text should appear rotated and positioned ~at the top-left of the text frame.
loadFromFile(u"3d_rotated_text.pptx");
// verify the camera rotation was imported correctly
auto xDrawPages = mxComponent.queryThrow<drawing::XDrawPagesSupplier>()->getDrawPages();
auto xDrawPage = xDrawPages->getByIndex(0).queryThrow<drawing::XDrawPage>();
auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
CPPUNIT_ASSERT(pDrawPage);
auto* pSdrTextObj = static_cast<SdrTextObj*>(pDrawPage->GetSdrPage()->GetObj(0));
CPPUNIT_ASSERT(pSdrTextObj);
CPPUNIT_ASSERT_EQUAL(90.0, pSdrTextObj->GetCameraZRotation());
auto aPrimitives = renderPageToPrimitives(xDrawPage);
svx::ExtendedPrimitive2dXmlDump aDumper;
xmlDocUniquePtr pDocument = aDumper.dumpAndParse(aPrimitives);
CPPUNIT_ASSERT(pDocument);
assertXPath(pDocument, "//textsimpleportion", "text", u"Vertical");
// x/y are the unrotated text position (in page coordinates, before the transform)
assertXPath(pDocument, "//textsimpleportion", "x", u"7799");
assertXPath(pDocument, "//textsimpleportion", "y", u"6303");
assertXPath(pDocument, "//transform", 1);
double fXY11 = getXPath(pDocument, "//transform", "xy11").toDouble();
double fXY12 = getXPath(pDocument, "//transform", "xy12").toDouble();
double fXY13 = getXPath(pDocument, "//transform", "xy13").toDouble();
double fXY21 = getXPath(pDocument, "//transform", "xy21").toDouble();
double fXY22 = getXPath(pDocument, "//transform", "xy22").toDouble();
double fXY23 = getXPath(pDocument, "//transform", "xy23").toDouble();
basegfx::B2DHomMatrix aTransform(fXY11, fXY12, fXY13, fXY21, fXY22, fXY23);
basegfx::B2DTuple aScale, aTranslate;
double fRotate, fShearX;
aTransform.decompose(aScale, aTranslate, fRotate, fShearX);
// no scaling, no shear
CPPUNIT_ASSERT_EQUAL(1.0, aScale.getX());
CPPUNIT_ASSERT_EQUAL(1.0, aScale.getY());
CPPUNIT_ASSERT_EQUAL(0.0, fShearX);
// The text is rotated 90 degrees counterclockwise around its unrotated center.
CPPUNIT_ASSERT_EQUAL(-M_PI_2, fRotate);
// The translation values reflect current state; they may change a bit (the position of the
// rotated text is not pixel-perfect; it is about one pixel off compared to Powerpoint).
CPPUNIT_ASSERT_DOUBLES_EQUAL(2744.0, aTranslate.getX(), 10.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(14896.0, aTranslate.getY(), 10.0);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+15 -17
View File
@@ -51,6 +51,7 @@
#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
#include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <svx/unoapi.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <editeng/outlobj.hxx>
@@ -596,23 +597,6 @@ void SdrTextObj::impDecomposeBlockTextPrimitive(
const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
basegfx::B2DHomMatrix aNewTransformA(basegfx::utils::createTranslateB2DHomMatrix(fStartInX, fStartInY));
// Apply the camera rotation. It have to be applied after adjustment of
// the text (top, bottom, center, left, right).
if(GetCameraZRotation() != 0)
{
// First find the text rect.
basegfx::B2DRange aTextRectangle(/*x1=*/aTranslate.getX() + aAdjustTranslate.getX(),
/*y1=*/aTranslate.getY() + aAdjustTranslate.getY(),
/*x2=*/aTranslate.getX() + aOutlinerScale.getX() - aAdjustTranslate.getX(),
/*y2=*/aTranslate.getY() + aOutlinerScale.getY() - aAdjustTranslate.getY());
// Rotate the text from the center point.
basegfx::B2DVector aTranslateToCenter(aTextRectangle.getWidth() / 2, aTextRectangle.getHeight() / 2);
aNewTransformA.translate(-aTranslateToCenter.getX(), -aTranslateToCenter.getY());
aNewTransformA.rotate(basegfx::deg2rad(360.0 - GetCameraZRotation() ));
aNewTransformA.translate(aTranslateToCenter.getX(), aTranslateToCenter.getY());
}
// mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
// move the null point which was top left to bottom right.
const bool bMirrorX(aScale.getX() < 0.0);
@@ -639,6 +623,20 @@ void SdrTextObj::impDecomposeBlockTextPrimitive(
rOutliner.StripPortions(aBreakup);
rTarget = aBreakup.getTextPortionPrimitives();
// Apply 3D camera Z rotation as a post-processing step
if (GetCameraZRotation() != 0 && !rTarget.empty())
{
// Rotate around the center of the unrotated text content
const drawinglayer::geometry::ViewInformation2D aViewInfoLocal;
const basegfx::B2DRange aOrigBounds = rTarget.getB2DRange(aViewInfoLocal);
const basegfx::B2DHomMatrix aRotationMat(basegfx::utils::createRotateAroundPoint(
aOrigBounds.getCenter(), basegfx::deg2rad(360.0 - GetCameraZRotation())));
rtl::Reference pRotation(
new drawinglayer::primitive2d::TransformPrimitive2D(aRotationMat, std::move(rTarget)));
rTarget = drawinglayer::primitive2d::Primitive2DContainer{ pRotation };
}
// cleanup outliner
rOutliner.SetBackgroundColor(aOriginalBackColor);
rOutliner.Clear();