summaryrefslogtreecommitdiff
path: root/sw/source/filter/ww8/docxsdrexport.cxx
diff options
context:
space:
mode:
authorRegina Henschel <rb.henschel@t-online.de>2021-05-15 23:40:34 +0200
committerMiklos Vajna <vmiklos@collabora.com>2021-06-28 09:49:00 +0200
commit3262fc5ef3bde5b158909d11ccb008161ea95519 (patch)
treeaa2b4926146eaddaa60878b920da77bd5871f67d /sw/source/filter/ww8/docxsdrexport.cxx
parent2ffdd37067926ddb841c6055205f267b96706945 (diff)
tdf#142304 a.o. Improve wrap margins in docx filters
LO uses the bounding box of the shape in case of type 'Parallel'. Word uses in the corresponding wrap 'square' a box based on the full size of the shape. That will be very different in some cases, e.g. for an arc. And Word exchanges width and height in case of rotation angle in [45°;135°[ and [225°; 315°[. To get the same appearance as in Word, the wrap margins are suitable expanded on import. Word puts the additional space needed for fat strokes into effectExtent in case of wrap 'inline', so there is no need to add the half strokes width in addition. Word determines the area for the shape depending on rotation angle. Both are now considered. Total same appearance is not possible because it would need negative vertical wrap margins, which are currently faulty in LO, see tdf#141880. Patch solves in addition tdf#142486, tdf#142305 The export to Word would require negative values in effectExtent in some cases. They are allowed in OOXML but not supported in Word. My idea is to switch to wrap mode 'Tight' if needed. But export of wrap has so many bad parts, that it needs separate work and is not included here. Handling of border width for export of own frames is missing. Unittest changes ---------------- testDmlTextshapeB and TestDmlTextshape in ooxmlexport6.cxx are set to current values. Import and Export still have large errors with these shapes and correct value from file is unknown. So an exact value is pointless. Only the original problem needs to be still fixed, which is the case. testWpsOnly in ooxmlexport10.cxx. I have removed the test for LeftMargin equals 0. The test makes no sense, because the original file has distL=114300. testTdf124600 in ooxmlimport2.cxx. I have added a tolerance. It would fail with Expected: 2029, Actual:2028, likely a rounding problem somewhere. testTdf124600 in ooxmlimport2.cxx Word refers to outer edge of the border for align='left', LO aligns at snap rectangle. The different intepretations become visible if a thick line is used. LO needs a margin to get the same rendering as in Word. So an expected value of 0 is wrong and I have disabled the test for now. ToDo: tdf#142798. Get the correct margin and activate the test then. testTextframeGradient in ooxmlexport2.cxx. I didn't find any reference for a default value. The test is not reliable, I get both 316 and 318 as actual value. Handling of shadow in VML shapes is buggy, the values for margin and shadow are wrong anyway. Reports are e.g. tdf#142486, tdf#142558. For now I have added a tolerance of 2. testDMLGroupShapeChildPosition in ooxmlexport6.cxx. The accuracy has become better. After reload we get the same values as before. testEffectExtent in ooxmlexport.cxx. tdf#142805. I have disabled the test, because the image is not loaded at all, and therefore it makes no sense to test a margin of it. And you can only test the sum of distL and effectExtent l, because LO has only one property 'LeftMargin' for it. testEffectExtentInline in ooxmlexport.cxx. To get the same vertical alignment as in Word, it would be necessary to have negative margins, but those are not implemented yet. tdf#141880. Currently the patch contains a heuristic to detect unchanged objects and write the grab-Bag values then. testEffectExtentLineWidth in ooxmlexport16.cxx. I have changed the expected value from 508 to 506. That is still away from the to be fixed faulty 561. It is a VML shape and import of connectors in VML shapes is faulty. testRelorientation in ooxmlexport4.cxx. Changed to 8662, the original problem is still fixed. I don't know the reason for the difference to the values from file. Change-Id: I28f156637f6ae64975cf2917f0e5cc89e689aff5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115668 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Diffstat (limited to 'sw/source/filter/ww8/docxsdrexport.cxx')
-rw-r--r--sw/source/filter/ww8/docxsdrexport.cxx476
1 files changed, 352 insertions, 124 deletions
diff --git a/sw/source/filter/ww8/docxsdrexport.cxx b/sw/source/filter/ww8/docxsdrexport.cxx
index 9b734b9bbeb8..1416c4838904 100644
--- a/sw/source/filter/ww8/docxsdrexport.cxx
+++ b/sw/source/filter/ww8/docxsdrexport.cxx
@@ -17,6 +17,7 @@
#include <editeng/opaqitem.hxx>
#include <editeng/boxitem.hxx>
#include <svx/svdogrp.hxx>
+#include <svx/svdobjkind.hxx>
#include <oox/token/namespaces.hxx>
#include <textboxhelper.hxx>
#include <fmtanchr.hxx>
@@ -38,6 +39,7 @@
#include <tools/diagnose_ex.h>
#include <svx/xlnwtit.hxx>
+#include <svx/svdtrans.hxx>
using namespace com::sun::star;
using namespace oox;
@@ -87,29 +89,40 @@ void lclMovePositionWithRotation(awt::Point& aPos, const Size& rSize, Degree100
{
// code from ImplEESdrWriter::ImplFlipBoundingBox (filter/source/msfilter/eschesdo.cxx)
// TODO: refactor
-
+ // MSO uses left|top of the unrotated object rectangle as position. When you rotate that retangle
+ // around its center and build a snap rectangle S from it, then left|top of S has to be the
+ // position used in LO. This method converts LOs aPos to the position used by MSO.
+
+ // rSize has to be size of the logicRect of the object. For calculating the diff, we build a
+ // rectangle with left|top A = (-fWidthHalf | -fHeightHalf) and
+ // right|top B = (fWidthHalf | -fHeightHalf). The rotation matrix R is here
+ // fcos fsin
+ // -fsin fcos
+ // Left of rectangle S = X-coord of R * A, Top of rectangle S = Y-coord of R * B
+
+ // Use nRotation in [0;9000], for to have only one and not four cases.
if (nRotation100 == 0_deg100)
return;
sal_Int64 nRotation = nRotation100.get();
if (nRotation < 0)
nRotation = (36000 + nRotation) % 36000;
if (nRotation % 18000 == 0)
- nRotation = 0;
+ nRotation = 0; // prevents endless loop
while (nRotation > 9000)
nRotation = (18000 - (nRotation % 18000));
double fVal = static_cast<double>(nRotation) * F_PI18000;
- double fCos = cos(fVal);
+ double fCos = (nRotation == 9000) ? 0.0 : cos(fVal);
double fSin = sin(fVal);
- double nWidthHalf = static_cast<double>(rSize.Width()) / 2;
- double nHeightHalf = static_cast<double>(rSize.Height()) / 2;
+ double fWidthHalf = static_cast<double>(rSize.Width()) / 2.0;
+ double fHeightHalf = static_cast<double>(rSize.Height()) / 2.0;
- double nXDiff = fSin * nHeightHalf + fCos * nWidthHalf - nWidthHalf;
- double nYDiff = fSin * nWidthHalf + fCos * nHeightHalf - nHeightHalf;
+ double fXDiff = fSin * fHeightHalf + fCos * fWidthHalf - fWidthHalf;
+ double fYDiff = fSin * fWidthHalf + fCos * fHeightHalf - fHeightHalf;
- aPos.X += nXDiff;
- aPos.Y += nYDiff;
+ aPos.X += fXDiff + 0.5;
+ aPos.Y += fYDiff + 0.5;
}
/// Determines if the anchor is inside a paragraph.
@@ -118,8 +131,140 @@ bool IsAnchorTypeInsideParagraph(const ww8::Frame* pFrame)
const SwFormatAnchor& rAnchor = pFrame->GetFrameFormat().GetAttrSet().GetAnchor();
return rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PAGE;
}
+
+bool lcl_IsRotateAngleValid(const SdrObject& rObj)
+{
+ // Some shape types report a rotation angle but are not actually rotated, because all rotation
+ // have been incorporated.
+ switch (rObj.GetObjIdentifier())
+ {
+ case OBJ_GRUP:
+ case OBJ_LINE:
+ case OBJ_PATHLINE:
+ case OBJ_PATHFILL:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void lcl_calculateMSOBaseRectangle(const SdrObject& rObj, double& rfMSOLeft, double& rfMSORight,
+ double& rfMSOTop, double& rfMSOBottom)
+{
+ // Word rotates around shape center, LO around left/top. Thus logic rectangle of LO is not
+ // directly usable as 'base rectangle'.
+ double fCenterX = (rObj.GetSnapRect().Left() + rObj.GetSnapRect().Right()) / 2.0;
+ double fCenterY = (rObj.GetSnapRect().Top() + rObj.GetSnapRect().Bottom()) / 2.0;
+ double fHalfWidth = rObj.GetLogicRect().getWidth() / 2.0;
+ double fHalfHeight = rObj.GetLogicRect().getHeight() / 2.0;
+
+ // MSO swaps width and height depending on rotation angle.
+ double fRotation
+ = lcl_IsRotateAngleValid(rObj) ? toDegrees(NormAngle36000(rObj.GetRotateAngle())) : 0.0;
+ if ((fRotation > 45.0 && fRotation <= 135.0) || (fRotation > 225.0 && fRotation <= 315.0))
+ {
+ rfMSOLeft = fCenterX - fHalfHeight;
+ rfMSORight = fCenterX + fHalfHeight;
+ rfMSOTop = fCenterY - fHalfWidth;
+ rfMSOBottom = fCenterY + fHalfWidth;
+ }
+ else
+ {
+ rfMSOLeft = fCenterX - fHalfWidth;
+ rfMSORight = fCenterX + fHalfWidth;
+ rfMSOTop = fCenterY - fHalfHeight;
+ rfMSOBottom = fCenterY + fHalfHeight;
+ }
+}
+
+void lcl_calculateRawEffectExtent(sal_Int32& rLeft, sal_Int32& rRight, sal_Int32& rTop,
+ sal_Int32& rBottom, const SdrObject& rObj,
+ const bool bUseBoundRect)
+{
+ // This method calculates the extent needed, to let Word use the same outer area for the object
+ // as LO. Word uses as 'base rectangle' the unrotated shape rectangle, maybe having swapped width
+ // and height depending on rotation angle.
+ double fMSOLeft;
+ double fMSORight;
+ double fMSOTop;
+ double fMSOBottom;
+ lcl_calculateMSOBaseRectangle(rObj, fMSOLeft, fMSORight, fMSOTop, fMSOBottom);
+
+ tools::Rectangle aLORect = bUseBoundRect ? rObj.GetCurrentBoundRect() : rObj.GetSnapRect();
+ rLeft = fMSOLeft - aLORect.Left();
+ rRight = aLORect.Right() - fMSORight;
+ rTop = fMSOTop - aLORect.Top();
+ rBottom = aLORect.Bottom() - fMSOBottom;
+ // Result values might be negative, e.g for a custom shape 'Arc'.
+ return;
+}
+
+bool lcl_makeSingleDistAndEffectExtentNonNegative(sal_Int64& rDist, sal_Int32& rExt)
+{
+ // A negative effectExtent is allowed in OOXML, but Word cannot handle it (bug in Word). It
+ // might occur, if the BoundRect in LO is smaller than the base rect in Word.
+ // A negative wrap distance from text is allowed in ODF. LO can currently only handle left and
+ // right negative values, see bug tdf#141880. Dist must be non-negative in OOXML.
+ // We try to compensate Dist vs effectExtent to get similar visual appearance.
+ if (rExt >= 0 && rDist >= 0)
+ return true;
+ if (rExt < 0 && rDist < 0)
+ {
+ rExt = 0;
+ rDist = 0;
+ return false;
+ }
+ if (rDist + static_cast<sal_Int64>(rExt) < 0) // different sign, so no overflow
+ {
+ rExt = 0;
+ rDist = 0;
+ return false;
+ }
+ // rDist + rExt >= 0
+ rDist += rExt;
+ rExt = 0;
+ return true;
+}
+
+bool lcl_makeDistAndExtentNonNegative(sal_Int64& rDistT, sal_Int64& rDistB, sal_Int64& rDistL,
+ sal_Int64& rDistR, sal_Int32& rLeftExt, sal_Int32& rTopExt,
+ sal_Int32& rRightExt, sal_Int32& rBottomExt)
+{
+ bool bLeft = lcl_makeSingleDistAndEffectExtentNonNegative(rDistL, rLeftExt);
+ bool bTop = lcl_makeSingleDistAndEffectExtentNonNegative(rDistT, rTopExt);
+ bool bRight = lcl_makeSingleDistAndEffectExtentNonNegative(rDistR, rRightExt);
+ bool bBottom = lcl_makeSingleDistAndEffectExtentNonNegative(rDistB, rBottomExt);
+ return bLeft && bTop && bRight && bBottom;
}
+void lcl_makeSingleDistZeroAndExtentNonNegative(sal_Int64& rDist, sal_Int32& rExt)
+{
+ if (static_cast<double>(rDist) + static_cast<double>(rExt)
+ >= static_cast<double>(SAL_MAX_INT32))
+ rExt = SAL_MAX_INT32;
+ else if (static_cast<double>(rDist) + static_cast<double>(rExt) <= 0)
+ rExt = 0;
+ else // 0 < rDist + rExt < SAL_MAX_INT32
+ {
+ rExt = static_cast<sal_Int32>(rDist + rExt);
+ if (rExt < 0)
+ rExt = 0;
+ }
+ rDist = 0;
+}
+
+void lcl_makeDistZeroAndExtentNonNegative(sal_Int64& rDistT, sal_Int64& rDistB, sal_Int64& rDistL,
+ sal_Int64& rDistR, sal_Int32& rLeftExt,
+ sal_Int32& rTopExt, sal_Int32& rRightExt,
+ sal_Int32& rBottomExt)
+{
+ lcl_makeSingleDistZeroAndExtentNonNegative(rDistL, rLeftExt);
+ lcl_makeSingleDistZeroAndExtentNonNegative(rDistT, rTopExt);
+ lcl_makeSingleDistZeroAndExtentNonNegative(rDistR, rRightExt);
+ lcl_makeSingleDistZeroAndExtentNonNegative(rDistB, rBottomExt);
+}
+} // end anonymous namespace
+
ExportDataSaveRestore::ExportDataSaveRestore(DocxExport& rExport, sal_uLong nStt, sal_uLong nEnd,
ww8::Frame const* pParentFrame)
: m_rExport(rExport)
@@ -354,17 +499,20 @@ void DocxSdrExport::setFlyWrapAttrList(
void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, const Size& rSize)
{
+ // Word uses size excluding right edge. Caller writeDMLDrawing and writeDiagram are changed for
+ // now. ToDo: Look whether the other callers give the size this way.
m_pImpl->setDrawingOpen(true);
m_pImpl->setParagraphHasDrawing(true);
m_pImpl->getSerializer()->startElementNS(XML_w, XML_drawing);
+ const SdrObject* pObj = pFrameFormat->FindRealSdrObject();
- // tdf#135047: It must be allowed to find in parents too, but default value of bInP parameter
- // for GetLRSpace() and GetULSpace() is true, so no direct setting is required.
- const SvxLRSpaceItem& aLRSpaceItem = pFrameFormat->GetLRSpace();
- const SvxULSpaceItem& aULSpaceItem = pFrameFormat->GetULSpace();
-
- bool isAnchor;
+ // LO determines the place needed for the object from wrap type, wrap margin ('distance to text'),
+ // object type and anchor type. Word uses dist* for user set margins and effectExtent for place
+ // needed for effects like shadow and glow, for fat stroke and for rotation. We map the LO values
+ // to values needed by Word so that the appearance is the same as far as possible.
+ // All values in Twips, change to EMU is done immediately before writing out.
+ bool isAnchor; // true XML_anchor, false XML_inline
if (m_pImpl->getFlyFrameGraphic())
{
isAnchor = false; // make Graphic object inside DMLTextFrame & VMLTextFrame as Inline
@@ -374,55 +522,95 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
isAnchor = pFrameFormat->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR;
}
- // Count effectExtent values, their value is needed before dist{T,B,L,R} is written.
- SvxShadowItem aShadowItem = pFrameFormat->GetShadow();
- sal_Int32 nLeftExt = 0;
- sal_Int32 nRightExt = 0;
- sal_Int32 nTopExt = 0;
- sal_Int32 nBottomExt = 0;
- if (aShadowItem.GetLocation() != SvxShadowLocation::NONE)
+ // tdf#135047: It must be allowed to find in parents too, but default value of bInP parameter
+ // for GetLRSpace() and GetULSpace() is true, so no direct setting is required.
+ const SvxLRSpaceItem& aLRSpaceItem = pFrameFormat->GetLRSpace();
+ const SvxULSpaceItem& aULSpaceItem = pFrameFormat->GetULSpace();
+ sal_Int64 nDistT = aULSpaceItem.GetUpper();
+ sal_Int64 nDistB = aULSpaceItem.GetLower();
+ sal_Int64 nDistL = aLRSpaceItem.GetLeft();
+ sal_Int64 nDistR = aLRSpaceItem.GetRight();
+
+ // LibreOffice behaves different for frames and drawing objects, but MS Office treats frames
+ // as drawing objects too. Therefore we transform the values from frame so as if they come
+ // from a drawing object.
+ sal_Int32 nWidthDiff(0);
+ sal_Int32 nHeightDiff(0);
+ sal_Int32 nPosXDiff(0);
+ sal_Int32 nPosYDiff(0);
+ sal_Int32 nLeftExt(0);
+ sal_Int32 nRightExt(0);
+ sal_Int32 nTopExt(0);
+ sal_Int32 nBottomExt(0);
+
+ if ((!pObj) || (pObj && (pObj->GetObjIdentifier() == SwFlyDrawObjIdentifier)))
{
- sal_Int32 nShadowWidth(TwipsToEMU(aShadowItem.GetWidth()));
- switch (aShadowItem.GetLocation())
+ // Frame objects have a restricted shadow and no further effects. They have border instead of
+ // stroke. LO includes shadow and border in the object size, but Word not.
+ SvxShadowItem aShadowItem = pFrameFormat->GetShadow();
+ if (aShadowItem.GetLocation() != SvxShadowLocation::NONE)
{
- case SvxShadowLocation::TopLeft:
- nTopExt = nLeftExt = nShadowWidth;
- break;
- case SvxShadowLocation::TopRight:
- nTopExt = nRightExt = nShadowWidth;
- break;
- case SvxShadowLocation::BottomLeft:
- nBottomExt = nLeftExt = nShadowWidth;
- break;
- case SvxShadowLocation::BottomRight:
- nBottomExt = nRightExt = nShadowWidth;
- break;
- case SvxShadowLocation::NONE:
- case SvxShadowLocation::End:
- break;
+ sal_Int32 nShadowWidth(aShadowItem.GetWidth());
+ switch (aShadowItem.GetLocation())
+ {
+ case SvxShadowLocation::TopLeft:
+ nTopExt = nLeftExt = nShadowWidth;
+ nPosXDiff = nLeftExt; // actual move is postponed
+ nPosYDiff = nTopExt;
+ nWidthDiff = -nLeftExt; // actual size extent is postponed
+ nHeightDiff = -nTopExt;
+ break;
+ case SvxShadowLocation::TopRight:
+ nTopExt = nRightExt = nShadowWidth;
+ nPosYDiff = nTopExt;
+ nWidthDiff = -nRightExt;
+ nHeightDiff = -nTopExt;
+ break;
+ case SvxShadowLocation::BottomLeft:
+ nBottomExt = nLeftExt = nShadowWidth;
+ nPosXDiff = nLeftExt;
+ nWidthDiff = -nLeftExt;
+ nHeightDiff = -nBottomExt;
+ break;
+ case SvxShadowLocation::BottomRight:
+ nBottomExt = nRightExt = nShadowWidth;
+ nWidthDiff = -nRightExt;
+ nHeightDiff = -nBottomExt;
+ break;
+ case SvxShadowLocation::NONE:
+ case SvxShadowLocation::End:
+ break;
+ }
}
+ // ToDo: Position refers to outer edge of border in LO, but to center of border in Word.
+ // Adaption is missing here. Frames in LO have no stroke but border. The current conversion
+ // from border to line treats borders like table borders. That might give wrong values
+ // for drawing frames.
}
- else if (const SdrObject* pObject = pFrameFormat->FindRealSdrObject())
+ else // other objects than frames. pObj exists.
{
- // No shadow, but we have an idea what was the original effectExtent.
- uno::Any aAny;
- pObject->GetGrabBagItem(aAny);
- comphelper::SequenceAsHashMap aGrabBag(aAny);
- auto it = aGrabBag.find("CT_EffectExtent");
- if (it != aGrabBag.end())
+ // Word cannot handle negative EffectExtent although allowed in OOXML, the 'dist' attributes
+ // may not be negative. Take care of that.
+ if (isAnchor)
{
- comphelper::SequenceAsHashMap aEffectExtent(it->second);
- for (const std::pair<const OUString, uno::Any>& rDirection : aEffectExtent)
- {
- if (rDirection.first == "l" && rDirection.second.has<sal_Int32>())
- nLeftExt = rDirection.second.get<sal_Int32>();
- else if (rDirection.first == "t" && rDirection.second.has<sal_Int32>())
- nTopExt = rDirection.second.get<sal_Int32>();
- else if (rDirection.first == "r" && rDirection.second.has<sal_Int32>())
- nRightExt = rDirection.second.get<sal_Int32>();
- else if (rDirection.first == "b" && rDirection.second.has<sal_Int32>())
- nBottomExt = rDirection.second.get<sal_Int32>();
- }
+ lcl_calculateRawEffectExtent(nLeftExt, nRightExt, nTopExt, nBottomExt, *pObj, true);
+ // We have calculated the effectExtent from boundRect, therefore half stroke width is
+ // already contained.
+ // ToDo: The other half of the strokeWidth needs to be subtracted from padding.
+ // Where is that?
+ // ToDo: bool bCompansated = ... to be later able to switch from wrapSquare to wrapTight,
+ // if wrapSquare would require negative effectExtent.
+ lcl_makeDistAndExtentNonNegative(nDistT, nDistB, nDistL, nDistR, nLeftExt, nTopExt,
+ nRightExt, nBottomExt);
+ }
+ else
+ {
+ lcl_calculateRawEffectExtent(nLeftExt, nRightExt, nTopExt, nBottomExt, *pObj, false);
+ // nDistT,... contain the needed distances from import or set by user. But Word
+ // ignores Dist attributes of inline shapes. So we move all needed distances to
+ // effectExtent and force effectExtent to non-negative.
+ lcl_makeDistZeroAndExtentNonNegative(nDistT, nDistB, nDistL, nDistR, nLeftExt, nTopExt,
+ nRightExt, nBottomExt);
}
}
@@ -430,12 +618,9 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
{
rtl::Reference<sax_fastparser::FastAttributeList> attrList
= sax_fastparser::FastSerializerHelper::createAttrList();
+
bool bOpaque = pFrameFormat->GetOpaque().GetValue();
- awt::Point aPos(pFrameFormat->GetHoriOrient().GetPos(),
- pFrameFormat->GetVertOrient().GetPos());
- const SdrObject* pObj = pFrameFormat->FindRealSdrObject();
- Degree100 nRotation(0);
- if (pObj != nullptr)
+ if (pObj)
{
// SdrObjects know their layer, consider that instead of the frame format.
bOpaque = pObj->GetLayer()
@@ -444,46 +629,17 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
!= pFrameFormat->GetDoc()
->getIDocumentDrawModelAccess()
.GetInvisibleHellId();
-
- // Do not do this with lines.
- if (pObj->GetObjIdentifier() != OBJ_LINE)
- {
- nRotation = pObj->GetRotateAngle();
- lclMovePositionWithRotation(aPos, rSize, nRotation);
- }
}
attrList->add(XML_behindDoc, bOpaque ? "0" : "1");
- sal_Int32 nLineWidth = 0;
- if (const SdrObject* pObject = pFrameFormat->FindRealSdrObject())
- {
- nLineWidth = pObject->GetMergedItem(XATTR_LINEWIDTH).GetValue();
- }
- // Extend distance with the effect extent if the shape is not rotated, which is the opposite
- // of the mapping done at import time.
- // The type of dist* attributes is unsigned, so make sure no negative value is written.
- sal_Int64 nTopExtDist = nRotation ? 0 : nTopExt;
- nTopExtDist -= TwipsToEMU(nLineWidth / 2);
- sal_Int64 nDistT = std::max(static_cast<sal_Int64>(0),
- TwipsToEMU(aULSpaceItem.GetUpper()) - nTopExtDist);
- attrList->add(XML_distT, OString::number(nDistT).getStr());
- sal_Int64 nBottomExtDist = nRotation ? 0 : nBottomExt;
- nBottomExtDist -= TwipsToEMU(nLineWidth / 2);
- sal_Int64 nDistB = std::max(static_cast<sal_Int64>(0),
- TwipsToEMU(aULSpaceItem.GetLower()) - nBottomExtDist);
- attrList->add(XML_distB, OString::number(nDistB).getStr());
- sal_Int64 nLeftExtDist = nRotation ? 0 : nLeftExt;
- nLeftExtDist -= TwipsToEMU(nLineWidth / 2);
- sal_Int64 nDistL = std::max(static_cast<sal_Int64>(0),
- TwipsToEMU(aLRSpaceItem.GetLeft()) - nLeftExtDist);
- attrList->add(XML_distL, OString::number(nDistL).getStr());
- sal_Int64 nRightExtDist = nRotation ? 0 : nRightExt;
- nRightExtDist -= TwipsToEMU(nLineWidth / 2);
- sal_Int64 nDistR = std::max(static_cast<sal_Int64>(0),
- TwipsToEMU(aLRSpaceItem.GetRight()) - nRightExtDist);
- attrList->add(XML_distR, OString::number(nDistR).getStr());
+ attrList->add(XML_distT, OString::number(TwipsToEMU(nDistT)).getStr());
+ attrList->add(XML_distB, OString::number(TwipsToEMU(nDistB)).getStr());
+ attrList->add(XML_distL, OString::number(TwipsToEMU(nDistL)).getStr());
+ attrList->add(XML_distR, OString::number(TwipsToEMU(nDistR)).getStr());
+
attrList->add(XML_simplePos, "0");
attrList->add(XML_locked, "0");
+
bool bLclInTabCell = true;
if (pObj)
{
@@ -497,24 +653,40 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
attrList->add(XML_layoutInCell, "1");
else
attrList->add(XML_layoutInCell, "0");
+
bool bAllowOverlap = pFrameFormat->GetWrapInfluenceOnObjPos().GetAllowOverlap();
attrList->add(XML_allowOverlap, bAllowOverlap ? "1" : "0");
- if (pObj != nullptr)
+
+ if (pObj)
// It seems 0 and 1 have special meaning: just start counting from 2 to avoid issues with that.
attrList->add(XML_relativeHeight, OString::number(pObj->GetOrdNum() + 2));
else
// relativeHeight is mandatory attribute, if value is not present, we must write default value
attrList->add(XML_relativeHeight, "0");
- if (pObj != nullptr)
+
+ if (pObj)
{
OUString sAnchorId = lclGetAnchorIdFromGrabBag(pObj);
if (!sAnchorId.isEmpty())
attrList->addNS(XML_wp14, XML_anchorId,
OUStringToOString(sAnchorId, RTL_TEXTENCODING_UTF8));
}
+
m_pImpl->getSerializer()->startElementNS(XML_wp, XML_anchor, attrList);
+
m_pImpl->getSerializer()->singleElementNS(XML_wp, XML_simplePos, XML_x, "0", XML_y,
"0"); // required, unused
+
+ // Position is either determined by coordinates aPos or alignment keywords like 'center'.
+ // First prepare them.
+ awt::Point aPos(pFrameFormat->GetHoriOrient().GetPos(),
+ pFrameFormat->GetVertOrient().GetPos());
+
+ aPos.X += nPosXDiff; // Make the postponed position move of frames.
+ aPos.Y += nPosYDiff;
+ if (pObj && lcl_IsRotateAngleValid(*pObj))
+ lclMovePositionWithRotation(aPos, rSize, pObj->GetRotateAngle());
+
const char* relativeFromH;
const char* relativeFromV;
const char* alignH = nullptr;
@@ -611,8 +783,11 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
default:
break;
}
+
+ // write out horizontal position
m_pImpl->getSerializer()->startElementNS(XML_wp, XML_positionH, XML_relativeFrom,
relativeFromH);
+
/**
* Sizes of integral types
* climits header defines constants with the limits of integral types for the specific system and compiler implementation used.
@@ -620,6 +795,7 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
**/
const sal_Int64 MAX_INTEGER_VALUE = SAL_MAX_INT32;
const sal_Int64 MIN_INTEGER_VALUE = SAL_MIN_INT32;
+
if (alignH != nullptr)
{
m_pImpl->getSerializer()->startElementNS(XML_wp, XML_align);
@@ -654,9 +830,10 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
m_pImpl->getSerializer()->endElementNS(XML_wp, XML_posOffset);
}
m_pImpl->getSerializer()->endElementNS(XML_wp, XML_positionH);
+
+ // write out vertical position
m_pImpl->getSerializer()->startElementNS(XML_wp, XML_positionV, XML_relativeFrom,
relativeFromV);
-
sal_Int64 nPosYEMU = TwipsToEMU(aPos.Y);
// tdf#93675, 0 below line/paragraph and/or top line/paragraph with
@@ -693,16 +870,16 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
}
m_pImpl->getSerializer()->endElementNS(XML_wp, XML_positionV);
}
- else
+ else // inline
{
+ // nDist is forced to zero above and ignored by Word anyway, so write 0 directly.
rtl::Reference<sax_fastparser::FastAttributeList> aAttrList
= sax_fastparser::FastSerializerHelper::createAttrList();
- aAttrList->add(XML_distT, OString::number(TwipsToEMU(aULSpaceItem.GetUpper())).getStr());
- aAttrList->add(XML_distB, OString::number(TwipsToEMU(aULSpaceItem.GetLower())).getStr());
- aAttrList->add(XML_distL, OString::number(TwipsToEMU(aLRSpaceItem.GetLeft())).getStr());
- aAttrList->add(XML_distR, OString::number(TwipsToEMU(aLRSpaceItem.GetRight())).getStr());
- const SdrObject* pObj = pFrameFormat->FindRealSdrObject();
- if (pObj != nullptr)
+ aAttrList->add(XML_distT, OString::number(0).getStr());
+ aAttrList->add(XML_distB, OString::number(0).getStr());
+ aAttrList->add(XML_distL, OString::number(0).getStr());
+ aAttrList->add(XML_distR, OString::number(0).getStr());
+ if (pObj)
{
OUString sAnchorId = lclGetAnchorIdFromGrabBag(pObj);
if (!sAnchorId.isEmpty())
@@ -712,8 +889,7 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
m_pImpl->getSerializer()->startElementNS(XML_wp, XML_inline, aAttrList);
}
- // now the common parts
- // extent of the image
+ // now the common parts 'extent' and 'effectExtent'
/**
* Extent width is of type long ( i.e cx & cy ) as
*
@@ -731,26 +907,78 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
* 2147483647( MAX_INTEGER_VALUE ).
* Therefore changing the following accordingly so that LO sync's up with MSO.
**/
- sal_uInt64 cx
- = TwipsToEMU(std::clamp(rSize.Width(), tools::Long(0), tools::Long(SAL_MAX_INT32)));
+ sal_uInt64 cx = TwipsToEMU(
+ std::clamp(rSize.Width() + nWidthDiff, tools::Long(0), tools::Long(SAL_MAX_INT32)));
OString aWidth(OString::number(std::min(cx, sal_uInt64(SAL_MAX_INT32))));
- sal_uInt64 cy
- = TwipsToEMU(std::clamp(rSize.Height(), tools::Long(0), tools::Long(SAL_MAX_INT32)));
+ sal_uInt64 cy = TwipsToEMU(
+ std::clamp(rSize.Height() + nHeightDiff, tools::Long(0), tools::Long(SAL_MAX_INT32)));
OString aHeight(OString::number(std::min(cy, sal_uInt64(SAL_MAX_INT32))));
m_pImpl->getSerializer()->singleElementNS(XML_wp, XML_extent, XML_cx, aWidth, XML_cy, aHeight);
- // effectExtent, extent including the effect (shadow only for now)
+ // XML_effectExtent, includes effects, fat stroke and rotation
+ // FixMe: tdf141880. Because LibreOffice currently cannot handle negative vertical margins, they
+ // were forced to zero on import. Especially bottom margin of inline anchored rotated objects are
+ // affected. If the object was not changed, it would be better to export the original values
+ // from grab-Bag. Unfortulately there exists no marker for "not changed", so a heuristic is used
+ // here: If current left, top and right margins do not differ more than 1Hmm = 635EMU from the
+ // values in grab-Bag, it is likely, that the object was not changed and we restore the values
+ // from grab-Bag.
+ sal_Int64 nLeftExtEMU = TwipsToEMU(nLeftExt);
+ sal_Int64 nTopExtEMU = TwipsToEMU(nTopExt);
+ sal_Int64 nRightExtEMU = TwipsToEMU(nRightExt);
+ sal_Int64 nBottomExtEMU = TwipsToEMU(nBottomExt);
+ if (pObj)
+ {
+ uno::Any aAny;
+ pObj->GetGrabBagItem(aAny);
+ comphelper::SequenceAsHashMap aGrabBag(aAny);
+ auto it = aGrabBag.find("CT_EffectExtent");
+ if (it != aGrabBag.end())
+ {
+ comphelper::SequenceAsHashMap aEffectExtent(it->second);
+ sal_Int64 nLeftExtGrabBag(0);
+ sal_Int64 nTopExtGrabBag(0);
+ sal_Int64 nRightExtGrabBag(0);
+ sal_Int64 nBottomExtGrabBag(0);
+ for (const std::pair<const OUString, uno::Any>& rDirection : aEffectExtent)
+ {
+ if (rDirection.first == "l" && rDirection.second.has<sal_Int32>())
+ nLeftExtGrabBag = rDirection.second.get<sal_Int32>();
+ else if (rDirection.first == "t" && rDirection.second.has<sal_Int32>())
+ nTopExtGrabBag = rDirection.second.get<sal_Int32>();
+ else if (rDirection.first == "r" && rDirection.second.has<sal_Int32>())
+ nRightExtGrabBag = rDirection.second.get<sal_Int32>();
+ else if (rDirection.first == "b" && rDirection.second.has<sal_Int32>())
+ nBottomExtGrabBag = rDirection.second.get<sal_Int32>();
+ }
+ if (abs(nLeftExtEMU - nLeftExtGrabBag) <= 635 && abs(nTopExtEMU - nTopExtGrabBag) <= 635
+ && abs(nRightExtEMU - nRightExtGrabBag) <= 635)
+ {
+ nLeftExtEMU = nLeftExtGrabBag;
+ nTopExtEMU = nTopExtGrabBag;
+ nRightExtEMU = nRightExtGrabBag;
+ nBottomExtEMU = nBottomExtGrabBag;
+ }
+ }
+ }
m_pImpl->getSerializer()->singleElementNS(
- XML_wp, XML_effectExtent, XML_l, OString::number(nLeftExt), XML_t, OString::number(nTopExt),
- XML_r, OString::number(nRightExt), XML_b, OString::number(nBottomExt));
+ XML_wp, XML_effectExtent, XML_l, OString::number(nLeftExtEMU), XML_t,
+ OString::number(nTopExtEMU), XML_r, OString::number(nRightExtEMU), XML_b,
+ OString::number(nBottomExtEMU));
+
+ if (!isAnchor)
+ return; // OOXML 'inline' has not wrap type at all
+
+ // XML_anchor has exact one of types wrapNone, wrapSquare, wrapTight, wrapThrough and
+ // WrapTopAndBottom. Map our own types to them as far as possible.
+ sal_Int32 nWrapToken = 0; // 0 indicates that no wrap type is yet determined.
// See if we know the exact wrap type from grab-bag.
- sal_Int32 nWrapToken = 0;
- if (const SdrObject* pObject = pFrameFormat->FindRealSdrObject())
+ if (pObj)
{
uno::Any aAny;
- pObject->GetGrabBagItem(aAny);
+ pObj->GetGrabBagItem(aAny);
comphelper::SequenceAsHashMap aGrabBag(aAny);
auto it = aGrabBag.find("EG_WrapType");
if (it != aGrabBag.end())
@@ -891,10 +1119,10 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons
}
}
- // No? Then just approximate based on what we have.
- if (!isAnchor || nWrapToken)
+ if (nWrapToken)
return;
+ // No wrap type determined yet? Then just approximate based on what we have.
switch (pFrameFormat->GetSurround().GetValue())
{
case css::text::WrapTextMode_NONE:
@@ -973,7 +1201,7 @@ void DocxSdrExport::writeDMLDrawing(const SdrObject* pSdrObject, const SwFrameFo
m_pImpl->getExport().DocxAttrOutput().GetSdtEndBefore(pSdrObject);
sax_fastparser::FSHelperPtr pFS = m_pImpl->getSerializer();
- Size aSize(pSdrObject->GetLogicRect().GetWidth(), pSdrObject->GetLogicRect().GetHeight());
+ Size aSize(pSdrObject->GetLogicRect().getWidth(), pSdrObject->GetLogicRect().getHeight());
startDMLAnchorInline(pFrameFormat, aSize);
rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList
@@ -1242,7 +1470,7 @@ void DocxSdrExport::writeDiagram(const SdrObject* sdrObject, const SwFrameFormat
uno::UNO_QUERY);
// write necessary tags to document.xml
- Size aSize(sdrObject->GetSnapRect().GetWidth(), sdrObject->GetSnapRect().GetHeight());
+ Size aSize(sdrObject->GetSnapRect().getWidth(), sdrObject->GetSnapRect().getHeight());
startDMLAnchorInline(&rFrameFormat, aSize);
m_pImpl->getDrawingML()->WriteDiagram(xShape, nDiagramId);