tdf#129532 tdf#98839 fixes for mirror of custom shapes
tdf#98839 In case a sheared custom shape was mirrored, the shear angle in draw:transform had a wrong sign in the saved file. tdf#129532 Mirroring given in draw:transform in file or via macro was wrongly applied. Errors: 1)Mirroring from draw:transform attribute had overwritten already existing mirroring in the enhanced-geometry. 2)Mirroring from draw:transform attribute was set in enhanced- geometry attributes but not really applied. Change-Id: Ifa52f3606b5a33e6492a02d6e19c883d28752da8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/85670 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
This commit is contained in:
committed by
Tomaž Vajngerl
parent
79d396a2a6
commit
b4a6977e05
BIN
sd/qa/unit/data/tdf98839_ShearVFlipH.odg
Normal file
BIN
sd/qa/unit/data/tdf98839_ShearVFlipH.odg
Normal file
Binary file not shown.
@@ -53,7 +53,8 @@
|
||||
#include <vcl/window.hxx>
|
||||
#include <vcl/event.hxx>
|
||||
#include <vcl/keycodes.hxx>
|
||||
|
||||
#include <svx/svdoashp.hxx>
|
||||
#include <tools/gen.hxx>
|
||||
|
||||
using namespace ::com::sun::star;
|
||||
|
||||
@@ -76,6 +77,7 @@ public:
|
||||
void testTdf67248();
|
||||
void testTdf119956();
|
||||
void testTdf120527();
|
||||
void testTdf98839_ShearVFlipH();
|
||||
|
||||
CPPUNIT_TEST_SUITE(SdMiscTest);
|
||||
CPPUNIT_TEST(testTdf96206);
|
||||
@@ -93,6 +95,7 @@ public:
|
||||
CPPUNIT_TEST(testTdf67248);
|
||||
CPPUNIT_TEST(testTdf119956);
|
||||
CPPUNIT_TEST(testTdf120527);
|
||||
CPPUNIT_TEST(testTdf98839_ShearVFlipH);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
virtual void registerNamespaces(xmlXPathContextPtr& pXmlXPathCtx) override
|
||||
@@ -773,6 +776,32 @@ void SdMiscTest::testTdf119956()
|
||||
xDocShRef->DoClose();
|
||||
}
|
||||
|
||||
void SdMiscTest::testTdf98839_ShearVFlipH()
|
||||
{
|
||||
// Loads a document with a sheared shape and mirrors it
|
||||
const OUString sURL = "sd/qa/unit/data/tdf98839_ShearVFlipH.odg";
|
||||
sd::DrawDocShellRef xDocShRef = Load(m_directories.getURLFromSrc(sURL), ODG);
|
||||
sd::GraphicViewShell* pViewShell = static_cast<sd::GraphicViewShell*>(xDocShRef->GetViewShell());
|
||||
SdPage* pPage = pViewShell->GetActualPage();
|
||||
SdrObjCustomShape* pShape = static_cast<SdrObjCustomShape*>(pPage->GetObj(0));
|
||||
pShape->Mirror(Point(4000, 2000), Point(4000, 10000));
|
||||
|
||||
// Save and examine attribute draw:transform
|
||||
utl::TempFile aTempFile;
|
||||
aTempFile.EnableKillingFile();
|
||||
save(xDocShRef.get(), getFormat(ODG), aTempFile);
|
||||
xmlDocPtr pXmlDoc = parseExport(aTempFile, "content.xml");
|
||||
CPPUNIT_ASSERT_MESSAGE("Failed to get 'content.xml'", pXmlDoc);
|
||||
const OString sPathStart("/office:document-content/office:body/office:drawing/draw:page");
|
||||
assertXPath(pXmlDoc, sPathStart);
|
||||
const OUString sTransform = getXPath(pXmlDoc, sPathStart + "/draw:custom-shape","transform");
|
||||
|
||||
// Error was, that the shear angle had a wrong sign.
|
||||
CPPUNIT_ASSERT_MESSAGE("expected: draw:transform='skewX (-0.64350...)", sTransform.startsWith("skewX (-"));
|
||||
|
||||
xDocShRef->DoClose();
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(SdMiscTest);
|
||||
|
||||
CPPUNIT_PLUGIN_IMPLEMENT();
|
||||
|
@@ -631,6 +631,46 @@ CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf128413_tbrlOnOff)
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), sError);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf129532_MatrixFlipV)
|
||||
{
|
||||
// The document contains two rotated shapes with the same geometry. For one of them
|
||||
// "matrix(1 0 0 -1 0cm 0cm)" was manually added to the value of the draw:transform
|
||||
// attribute. That should result in mirroring on the x-axis. Error was, that the lines
|
||||
// which are drawn on the shape rectangle were mirrored, but not the rectangle itself.
|
||||
// The rectangle was only shifted.
|
||||
const OUString sFileName("tdf129532_MatrixFlipV.odg");
|
||||
OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + sFileName;
|
||||
mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
OUString sErrors; // sErrors collects the errors and should be empty in case all is OK.
|
||||
|
||||
uno::Reference<drawing::XShape> xShape0(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShape0Props(xShape0, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShape0Props.is());
|
||||
awt::Rectangle aBoundRect0;
|
||||
xShape0Props->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect0;
|
||||
|
||||
uno::Reference<drawing::XShape> xShape1(getShape(1));
|
||||
uno::Reference<beans::XPropertySet> xShape1Props(xShape1, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShape1Props.is());
|
||||
awt::Rectangle aBoundRect1;
|
||||
xShape1Props->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect1;
|
||||
|
||||
// The size of the two BoundRect rectangles are the same in case of correct
|
||||
// vertical mirroring.
|
||||
if (aBoundRect0.Width != aBoundRect1.Width)
|
||||
{
|
||||
sErrors += "\n Width expected: " + OUString::number(aBoundRect1.Width)
|
||||
+ " actual: " + OUString::number(aBoundRect0.Width);
|
||||
}
|
||||
if (aBoundRect0.Height != aBoundRect1.Height)
|
||||
{
|
||||
sErrors += "\n Height expected: " + OUString::number(aBoundRect1.Height)
|
||||
+ " actual: " + OUString::number(aBoundRect0.Height);
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL(OUString(), sErrors);
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
BIN
svx/qa/unit/data/tdf129532_MatrixFlipV.odg
Normal file
BIN
svx/qa/unit/data/tdf129532_MatrixFlipV.odg
Normal file
Binary file not shown.
@@ -2987,21 +2987,29 @@ void SdrObjCustomShape::AdjustToMaxRect(const tools::Rectangle& rMaxRect, bool b
|
||||
|
||||
void SdrObjCustomShape::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/)
|
||||
{
|
||||
// The shape might have already flipping in its enhanced geometry. LibreOffice applies
|
||||
// such after all transformations. We remove it, but remember it to apply them later.
|
||||
bool bIsMirroredX = IsMirroredX();
|
||||
bool bIsMirroredY = IsMirroredY();
|
||||
if (bIsMirroredX || bIsMirroredY)
|
||||
{
|
||||
Point aCurrentCenter = GetSnapRect().Center();
|
||||
if (bIsMirroredX) // mirror on the y-axis
|
||||
{
|
||||
Mirror(aCurrentCenter, Point(aCurrentCenter.X(), aCurrentCenter.Y() + 1000));
|
||||
}
|
||||
if (bIsMirroredY) // mirror on the x-axis
|
||||
{
|
||||
Mirror(aCurrentCenter, Point(aCurrentCenter.X() + 1000, aCurrentCenter.Y()));
|
||||
}
|
||||
}
|
||||
|
||||
// break up matrix
|
||||
basegfx::B2DTuple aScale;
|
||||
basegfx::B2DTuple aTranslate;
|
||||
double fRotate, fShearX;
|
||||
rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
|
||||
|
||||
// #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings
|
||||
// in X and Y which equal a 180 degree rotation. Recognize it and react accordingly
|
||||
if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
|
||||
{
|
||||
aScale.setX(fabs(aScale.getX()));
|
||||
aScale.setY(fabs(aScale.getY()));
|
||||
fRotate = fmod(fRotate + F_PI, F_2PI);
|
||||
}
|
||||
|
||||
// reset object shear and rotations
|
||||
fObjectRotation = 0.0;
|
||||
aGeo.nRotationAngle = 0;
|
||||
@@ -3018,14 +3026,19 @@ void SdrObjCustomShape::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix,
|
||||
}
|
||||
}
|
||||
|
||||
// build and set BaseRect (use scale)
|
||||
Size aSize(FRound(aScale.getX()), FRound(aScale.getY()));
|
||||
// scale
|
||||
Size aSize(FRound(fabs(aScale.getX())), FRound(fabs(aScale.getY())));
|
||||
// fdo#47434 We need a valid rectangle here
|
||||
if( !aSize.Height() ) aSize.setHeight( 1 );
|
||||
if( !aSize.Width() ) aSize.setWidth( 1 );
|
||||
|
||||
tools::Rectangle aBaseRect(Point(), aSize);
|
||||
SetSnapRect(aBaseRect);
|
||||
SetLogicRect(aBaseRect);
|
||||
|
||||
// Apply flipping from Matrix, which is a transformation relative to origin
|
||||
if (basegfx::fTools::less(aScale.getX(), 0.0))
|
||||
Mirror(Point(0, 0), Point(0, 1000)); // mirror on the y-axis
|
||||
if (basegfx::fTools::less(aScale.getY(), 0.0))
|
||||
Mirror(Point(0, 0), Point(1000, 0)); // mirror on the x-axis
|
||||
|
||||
// shear?
|
||||
if(!basegfx::fTools::equalZero(fShearX))
|
||||
@@ -3057,6 +3070,30 @@ void SdrObjCustomShape::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix,
|
||||
{
|
||||
Move(Size(FRound(aTranslate.getX()), FRound(aTranslate.getY())));
|
||||
}
|
||||
|
||||
// Apply flipping from enhanced geometry at center of the shape.
|
||||
if (bIsMirroredX || bIsMirroredY)
|
||||
{
|
||||
// create mathematically matrix for the applied transformations
|
||||
// aScale was in most cases built from a rectangle including edge
|
||||
// and is therefore mathematically too large by 1
|
||||
if (aScale.getX() > 2.0 && aScale.getY() > 2.0)
|
||||
aScale -= basegfx::B2DTuple(1.0, 1.0);
|
||||
basegfx::B2DHomMatrix aMathMat = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
|
||||
aScale, -fShearX, basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate,
|
||||
aTranslate);
|
||||
// Use matrix to get current center
|
||||
basegfx::B2DPoint aCenter(0.5,0.5);
|
||||
aCenter = aMathMat * aCenter;
|
||||
double fCenterX = aCenter.getX();
|
||||
double fCenterY = aCenter.getY();
|
||||
if (bIsMirroredX) // vertical axis
|
||||
Mirror(Point(FRound(fCenterX),FRound(fCenterY)),
|
||||
Point(FRound(fCenterX), FRound(fCenterY + 1000.0)));
|
||||
if (bIsMirroredY) // horizontal axis
|
||||
Mirror(Point(FRound(fCenterX),FRound(fCenterY)),
|
||||
Point(FRound(fCenterX + 1000.0), FRound(fCenterY)));
|
||||
}
|
||||
}
|
||||
|
||||
// taking fObjectRotation instead of aGeo.nAngle
|
||||
@@ -3078,6 +3115,7 @@ bool SdrObjCustomShape::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegf
|
||||
|
||||
if ( bMirroredX )
|
||||
{
|
||||
fShearX = -fShearX;
|
||||
tools::Polygon aPol = Rect2Poly(maRect, aNewGeo);
|
||||
tools::Rectangle aBoundRect( aPol.GetBoundRect() );
|
||||
|
||||
@@ -3100,6 +3138,7 @@ bool SdrObjCustomShape::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegf
|
||||
}
|
||||
if ( bMirroredY )
|
||||
{
|
||||
fShearX = -fShearX;
|
||||
tools::Polygon aPol( Rect2Poly( aRectangle, aNewGeo ) );
|
||||
tools::Rectangle aBoundRect( aPol.GetBoundRect() );
|
||||
|
||||
|
@@ -3755,11 +3755,9 @@ void SdXMLCustomShapeContext::StartElement( const uno::Reference< xml::sax::XAtt
|
||||
|
||||
void SdXMLCustomShapeContext::EndElement()
|
||||
{
|
||||
// for backward compatibility, the above SetTransformation() may already have
|
||||
// applied a call to SetMirroredX/SetMirroredY. This is not yet added to the
|
||||
// beans::PropertyValues in maCustomShapeGeometry. When applying these now, this
|
||||
// would be lost again.
|
||||
// TTTT: Remove again after aw080
|
||||
// Customshapes remember mirror state in its enhanced geometry.
|
||||
// SetTransformation() in StartElement() may have applied mirroring, but that is not yet
|
||||
// contained. Merge that information here before writing the property.
|
||||
if(!maUsedTransformation.isIdentity())
|
||||
{
|
||||
basegfx::B2DVector aScale, aTranslate;
|
||||
@@ -3767,43 +3765,58 @@ void SdXMLCustomShapeContext::EndElement()
|
||||
|
||||
maUsedTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
|
||||
|
||||
bool bFlippedX(aScale.getX() < 0.0);
|
||||
bool bFlippedY(aScale.getY() < 0.0);
|
||||
|
||||
if(bFlippedX && bFlippedY)
|
||||
if (aScale.getX() < 0.0)
|
||||
{
|
||||
// when both are used it is the same as 180 degree rotation; reset
|
||||
bFlippedX = bFlippedY = false;
|
||||
}
|
||||
|
||||
if(bFlippedX || bFlippedY)
|
||||
{
|
||||
OUString sName;
|
||||
|
||||
if(bFlippedX)
|
||||
sName = "MirroredX";
|
||||
else
|
||||
sName = "MirroredY";
|
||||
|
||||
//fdo#84043 overwrite the property if it already exists, otherwise append it
|
||||
beans::PropertyValue* pItem;
|
||||
const OUString sName("MirroredX");
|
||||
//fdo#84043 Merge, if property exists, otherwise append it
|
||||
auto aI = std::find_if(maCustomShapeGeometry.begin(), maCustomShapeGeometry.end(),
|
||||
[&sName](beans::PropertyValue& rValue) { return rValue.Name == sName; });
|
||||
if (aI != maCustomShapeGeometry.end())
|
||||
{
|
||||
beans::PropertyValue& rItem = *aI;
|
||||
pItem = &rItem;
|
||||
bool bMirroredX;
|
||||
rItem.Value >>= bMirroredX;
|
||||
rItem.Value <<= !bMirroredX;
|
||||
rItem.Handle = -1;
|
||||
rItem.State = beans::PropertyState_DIRECT_VALUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
beans::PropertyValue* pItem;
|
||||
maCustomShapeGeometry.emplace_back();
|
||||
pItem = &maCustomShapeGeometry.back();
|
||||
pItem->Name = sName;
|
||||
pItem->Handle = -1;
|
||||
pItem->Value <<= true;
|
||||
pItem->State = beans::PropertyState_DIRECT_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
pItem->Name = sName;
|
||||
pItem->Handle = -1;
|
||||
pItem->Value <<= true;
|
||||
pItem->State = beans::PropertyState_DIRECT_VALUE;
|
||||
if (aScale.getY() < 0.0)
|
||||
{
|
||||
const OUString sName("MirroredY");
|
||||
//fdo#84043 Merge, if property exists, otherwise append it
|
||||
auto aI = std::find_if(maCustomShapeGeometry.begin(), maCustomShapeGeometry.end(),
|
||||
[&sName](beans::PropertyValue& rValue) { return rValue.Name == sName; });
|
||||
if (aI != maCustomShapeGeometry.end())
|
||||
{
|
||||
beans::PropertyValue& rItem = *aI;
|
||||
bool bMirroredY;
|
||||
rItem.Value >>= bMirroredY;
|
||||
rItem.Value <<= !bMirroredY;
|
||||
rItem.Handle = -1;
|
||||
rItem.State = beans::PropertyState_DIRECT_VALUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
beans::PropertyValue* pItem;
|
||||
maCustomShapeGeometry.emplace_back();
|
||||
pItem = &maCustomShapeGeometry.back();
|
||||
pItem->Name = sName;
|
||||
pItem->Handle = -1;
|
||||
pItem->Value <<= true;
|
||||
pItem->State = beans::PropertyState_DIRECT_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user