tdf#121509 DOCX export: fix corrupt shape anchoring in textbox
MSO doesn't support shapes anchored to character in a textbox.
Convert these shapes by re-anchoring them to the anchor
point of the textbox (also recalculating their positions in
simple cases), so Word can now open the exported document.
Change-Id: I28b244975981d69c50e5d4a46249918af089bae5
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117163
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/extras/layout/data/Tdf121509.odt b/sw/qa/extras/layout/data/Tdf121509.odt
new file mode 100644
index 0000000..856f60c
--- /dev/null
+++ b/sw/qa/extras/layout/data/Tdf121509.odt
Binary files differ
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index 72cdc90..1db5f1e 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -49,6 +49,9 @@
#include <svx/svdpage.hxx>
#include <svx/svdotext.hxx>
#include <dcontact.hxx>
#include <frameformats.hxx>
#include <fmtcntnt.hxx>
#include <unotextrange.hxx>
constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/extras/layout/data/";
@@ -1442,6 +1445,61 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf127118)
assertXPath(pXmlDoc, "/root/page[2]/body/tab/row[1]/cell[1]/txt[1]", "WritingMode", "VertBTLR");
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf121509)
{
auto pDoc = createSwDoc(DATA_DIRECTORY, "Tdf121509.odt");
CPPUNIT_ASSERT(pDoc);
// Get all shape/frame formats
auto vFrameFormats = pDoc->GetSpzFrameFormats();
// Get the textbox
auto xTextFrame = SwTextBoxHelper::getUnoTextFrame(getShape(1));
// Get The triangle
auto pTriangleShapeFormat = vFrameFormats->GetFormat(2);
CPPUNIT_ASSERT(xTextFrame);
CPPUNIT_ASSERT(pTriangleShapeFormat);
// Get the position inside the textbox
auto xTextContentStart = xTextFrame->getText()->getStart();
SwUnoInternalPaM aCursor(*pDoc);
CPPUNIT_ASSERT(sw::XTextRangeToSwPaM(aCursor, xTextContentStart));
// Put the triangle into the textbox
SwFormatAnchor aNewAnch(pTriangleShapeFormat->GetAnchor());
aNewAnch.SetAnchor(aCursor.Start());
CPPUNIT_ASSERT(pTriangleShapeFormat->SetFormatAttr(aNewAnch));
// Reload (docx)
auto aTemp = utl::TempFile();
save("Office Open XML Text", aTemp);
// The second part: check if the reloaded doc has flys inside a fly
uno::Reference<lang::XComponent> xComponent
= loadFromDesktop(aTemp.GetURL(), "com.sun.star.text.TextDocument");
uno::Reference<text::XTextDocument> xTextDoc(xComponent, uno::UNO_QUERY);
auto pTextDoc = dynamic_cast<SwXTextDocument*>(xTextDoc.get());
CPPUNIT_ASSERT(pTextDoc);
auto pSecondDoc = pTextDoc->GetDocShell()->GetDoc();
auto pSecondFormats = pSecondDoc->GetSpzFrameFormats();
bool bFlyInFlyFound = false;
for (auto secondformat : *pSecondFormats)
{
auto& pNd = secondformat->GetAnchor().GetContentAnchor()->nNode.GetNode();
if (pNd.FindFlyStartNode())
{
// So there is a fly inside another -> problem.
bFlyInFlyFound = true;
break;
}
}
// Drop the tempfile
aTemp.CloseStream();
// With the fix this cannot be true, if it is, that means Word unable to read the file..
CPPUNIT_ASSERT_MESSAGE("Corrupt exported docx file!", !bFlyInFlyFound);
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf134685)
{
createSwDoc(DATA_DIRECTORY, "tdf134685.docx");
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index 7d185f5..19dc42a 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -6082,7 +6082,7 @@ void DocxAttributeOutput::WritePostponedDMLDrawing()
m_pPostponedOLEs = std::move(pPostponedOLEs);
}
void DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame &rFrame, const Point& /*rNdTopLeft*/ )
void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame)
{
m_pSerializer->mark(Tag_OutputFlyFrame);
@@ -6254,6 +6254,71 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame &rFrame, const P
m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame);
}
void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/)
{
/// The old OutputFlyFrame_Impl() moved to WriteFlyFrame().
/// Now if a frame anchored inside another frame, it will
/// not be exported immediately, because OOXML does not
/// support that feature, instead it postponed and exported
/// later when the original shape closed.
if (rFrame.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR
|| rFrame.IsInline())
{
m_nEmbedFlyLevel++;
WriteFlyFrame(rFrame);
m_nEmbedFlyLevel--;
return;
}
if (m_nEmbedFlyLevel == 0)
{
if (m_vPostponedFlys.empty())
{
m_nEmbedFlyLevel++;
WriteFlyFrame(rFrame);
m_nEmbedFlyLevel--;
}
else
for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();)
{
m_nEmbedFlyLevel++;
WriteFlyFrame(*it);
it = m_vPostponedFlys.erase(it);
m_nEmbedFlyLevel--;
}
}
else
{
bool bFound = false;
for (const auto& i : m_vPostponedFlys)
{
if (i.RefersToSameFrameAs(rFrame))
{
bFound = true;
break;
}
}
if (!bFound)
{
if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat())
{
auto aHori(rFrame.GetFrameFormat().GetHoriOrient());
aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos());
auto aVori(rFrame.GetFrameFormat().GetVertOrient());
aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos());
const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori);
const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori);
const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor());
m_vPostponedFlys.push_back(rFrame);
}
}
}
}
void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
{
const EditTextObject& rEditObj = rParaObj.GetTextObject();
@@ -9993,6 +10058,7 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr
m_sFieldBkm( ),
m_nNextBookmarkId( 0 ),
m_nNextAnnotationMarkId( 0 ),
m_nEmbedFlyLevel(0),
m_pCurrentFrame( nullptr ),
m_bParagraphOpened( false ),
m_bParagraphFrameOpen( false ),
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx
index b068818..7f880cc 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -718,6 +718,7 @@ private:
void WritePostponedOLE();
void WritePostponedDMLDrawing();
void WritePostponedCustomShape();
void WriteFlyFrame(const ww8::Frame& rFrame);
void WriteSdtBlock(sal_Int32& nSdtPrToken,
rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrTokenChildren,
@@ -802,6 +803,14 @@ private:
OUString m_sRawText;
/// The first frame (anchored to the main text) is 0.
/// The second frame what is anchored to the previous in, is 1
/// The third anchored inside the second is the 2 etc.
sal_uInt32 m_nEmbedFlyLevel;
/// Stores the flys what are anchored inside a fly
std::vector<ww8::Frame> m_vPostponedFlys;
/// Bookmarks to output
std::vector<OUString> m_rBookmarksStart;
std::vector<OUString> m_rBookmarksEnd;