summaryrefslogtreecommitdiffstats
path: root/sw/qa/extras/globalfilter/globalfilter.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/qa/extras/globalfilter/globalfilter.cxx')
-rw-r--r--sw/qa/extras/globalfilter/globalfilter.cxx2267
1 files changed, 2267 insertions, 0 deletions
diff --git a/sw/qa/extras/globalfilter/globalfilter.cxx b/sw/qa/extras/globalfilter/globalfilter.cxx
new file mode 100644
index 0000000000..9d7c28b83f
--- /dev/null
+++ b/sw/qa/extras/globalfilter/globalfilter.cxx
@@ -0,0 +1,2267 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <swmodeltestbase.hxx>
+
+#include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/graphic/GraphicType.hpp>
+#include <com/sun/star/text/ControlCharacter.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/text/XDocumentIndex.hpp>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <tools/zcodec.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <unotxdoc.hxx>
+#include <docsh.hxx>
+#include <editsh.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentContentOperations.hxx>
+#include <doc.hxx>
+#include <ndgrf.hxx>
+#include <ndtxt.hxx>
+#include <ndindex.hxx>
+#include <pam.hxx>
+#include <unotools/fltrcfg.hxx>
+#include <xmloff/odffields.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <IMark.hxx>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <unotools/mediadescriptor.hxx>
+
+class Test : public SwModelTestBase
+{
+public:
+ Test() : SwModelTestBase("/sw/qa/extras/globalfilter/data/") {}
+
+ void testEmbeddedGraphicRoundtrip();
+ void testLinkedGraphicRT();
+ void testImageWithSpecialID();
+ void testGraphicShape();
+ void testMultipleIdenticalGraphics();
+ void testCharHighlight();
+ void testCharHighlightODF();
+ void testCharHighlightBody();
+ void testCharStyleHighlight();
+ void testMSCharBackgroundEditing();
+ void testCharBackgroundToHighlighting();
+#if !defined(_WIN32)
+ void testSkipImages();
+#endif
+ void testNestedFieldmark();
+ void verifyText13(char const*);
+ void testODF13();
+ void testRedlineFlags();
+ void testBulletAsImage();
+ void testTextFormField();
+ void testCheckBoxFormField();
+ void testDropDownFormField();
+ void testDateFormField();
+ void testDateFormFieldCharacterFormatting();
+ void testSvgImageSupport();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testEmbeddedGraphicRoundtrip);
+ CPPUNIT_TEST(testLinkedGraphicRT);
+ CPPUNIT_TEST(testImageWithSpecialID);
+ CPPUNIT_TEST(testGraphicShape);
+ CPPUNIT_TEST(testMultipleIdenticalGraphics);
+ CPPUNIT_TEST(testCharHighlight);
+ CPPUNIT_TEST(testCharHighlightODF);
+ CPPUNIT_TEST(testMSCharBackgroundEditing);
+ CPPUNIT_TEST(testCharBackgroundToHighlighting);
+#if !defined(_WIN32)
+ CPPUNIT_TEST(testSkipImages);
+#endif
+ CPPUNIT_TEST(testNestedFieldmark);
+ CPPUNIT_TEST(testODF13);
+ CPPUNIT_TEST(testRedlineFlags);
+ CPPUNIT_TEST(testBulletAsImage);
+ CPPUNIT_TEST(testTextFormField);
+ CPPUNIT_TEST(testCheckBoxFormField);
+ CPPUNIT_TEST(testDropDownFormField);
+ CPPUNIT_TEST(testDateFormField);
+ CPPUNIT_TEST(testDateFormFieldCharacterFormatting);
+ CPPUNIT_TEST(testSvgImageSupport);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void Test::testEmbeddedGraphicRoundtrip()
+{
+ OUString aFilterNames[] = {
+ "writer8",
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ // Check whether the export code swaps in the image which was swapped out before by auto mechanism
+
+ createSwDoc("document_with_two_images.odt");
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check whether graphic exported well after it was swapped out
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
+
+ // First image
+ uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
+ uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
+
+ // Check graphic, size
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
+ uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
+ }
+
+ // Second Image
+ xImage.set(getShape(2), uno::UNO_QUERY);
+ XPropSet.set( xImage, uno::UNO_QUERY_THROW );
+
+ // Check graphic, size
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
+ uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
+ }
+ }
+}
+
+void Test::testLinkedGraphicRT()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+// "Rich Text Format", Note: picture is there, but SwGrfNode is not found?
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("document_with_linked_graphic.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
+ SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pDoc);
+ SwNodes& aNodes = pDoc->GetNodes();
+
+ // Find the image
+ bool bImageFound = false;
+ Graphic aGraphic;
+ for (SwNodeOffset nIndex(0); nIndex < aNodes.Count(); ++nIndex)
+ {
+ if (aNodes[nIndex]->IsGrfNode())
+ {
+ SwGrfNode* pGrfNode = aNodes[nIndex]->GetGrfNode();
+ CPPUNIT_ASSERT(pGrfNode);
+
+ const GraphicObject& rGraphicObj = pGrfNode->GetGrfObj(true);
+ aGraphic = rGraphicObj.GetGraphic();
+ bImageFound = true;
+ }
+ }
+
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bImageFound);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_uLong(864900), aGraphic.GetSizeBytes());
+
+ // Check if linked graphic is registered in LinkManager
+ sfx2::LinkManager& rLinkManager = pTextDoc->GetDocShell()->GetDoc()->GetEditShell()->GetLinkManager();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(1), rLinkManager.GetLinks().size());
+ const tools::SvRef<sfx2::SvBaseLink> & rLink = rLinkManager.GetLinks()[0];
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), rLink->GetLinkSourceName().indexOf("linked_graphic.jpg") >= 0);
+ }
+}
+
+void Test::testImageWithSpecialID()
+{
+ // Check how LO handles when the imported graphic's ID is different from that one
+ // which is generated by LO.
+
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("images_with_special_IDs.odt");
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check whether graphic exported well
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
+
+ uno::Reference<drawing::XShape> xImage = getShape(1);
+ uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
+
+ // Check graphic, size
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
+ uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
+ }
+
+ // Second Image
+ xImage.set(getShape(2), uno::UNO_QUERY);
+ XPropSet.set( xImage, uno::UNO_QUERY_THROW );
+
+ // Check graphic, size
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
+ uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
+ }
+ }
+}
+
+/// Gives the first embedded or linked image in a document.
+static uno::Reference<drawing::XShape> lcl_getShape(const uno::Reference<lang::XComponent>& xComponent, bool bEmbedded)
+{
+ uno::Reference<drawing::XShape> xShape;
+
+ uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
+ for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
+ {
+ uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
+ uno::Reference<graphic::XGraphic> xGraphic;
+ xShapeProperties->getPropertyValue("Graphic") >>= xGraphic;
+ if (xGraphic.is())
+ {
+ Graphic aGraphic(xGraphic);
+
+ if (bEmbedded == aGraphic.getOriginURL().isEmpty())
+ {
+ xShape.set(xShapeProperties, uno::UNO_QUERY);
+ return xShape;
+ }
+ }
+ }
+
+ return xShape;
+}
+
+void Test::testGraphicShape()
+{
+ // There are two kind of images in Writer: 1) Writer specific handled by SwGrfNode and
+ // 2) graphic shape handled by SdrGrafObj (e.g. after copy&paste from Impress).
+
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("graphic_shape.odt");
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check whether graphic exported well
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
+
+ uno::Reference<drawing::XShape> xImage = lcl_getShape(mxComponent, true);
+ CPPUNIT_ASSERT_MESSAGE("Couldn't load the shape/image", xImage.is());
+ uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY );
+ // First image is embedded
+ // Check size
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+ uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height );
+ }
+
+ // MS filters make this kind of linked images broken !?
+ if (rFilterName != "writer8")
+ return;
+
+ // Second image is a linked one
+ xImage = lcl_getShape(mxComponent, false);
+ XPropSet.set(xImage, uno::UNO_QUERY);
+ const OString sFailedImageLoad = OString::Concat("Couldn't load the shape/image for ") + rFilterName.toUtf8();
+ CPPUNIT_ASSERT_MESSAGE(sFailedImageLoad.getStr(), xImage.is());
+
+ // Check size
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+
+ Graphic aGraphic(xGraphic);
+ OUString sURL = aGraphic.getOriginURL();
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), sURL.endsWith("linked_graphic.jpg"));
+
+ uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(620), xBitmap->getSize().Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(465), xBitmap->getSize().Height);
+ }
+ }
+}
+
+namespace
+{
+
+std::vector<uno::Reference<graphic::XGraphic>>
+ lcl_getGraphics(const uno::Reference<lang::XComponent>& xComponent)
+{
+ std::vector<uno::Reference<graphic::XGraphic>> aGraphics;
+
+ uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
+ for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
+ {
+ uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
+ uno::Reference<graphic::XGraphic> xGraphic;
+ xShapeProperties->getPropertyValue("Graphic") >>= xGraphic;
+ if (xGraphic.is())
+ {
+ aGraphics.push_back(xGraphic);
+ }
+ }
+
+ return aGraphics;
+}
+
+}
+
+void Test::testMultipleIdenticalGraphics()
+{
+ // We have multiple identical graphics. When we save them we want
+ // them to be saved de-duplicated and the same should still be true
+ // after loading them again. This test check that the de-duplication
+ // works as expected.
+
+ const OUString aFilterNames[] {
+ "writer8",
+ //"Rich Text Format", // doesn't work correctly for now
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("multiple_identical_graphics.odt");
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check whether graphic exported well
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+ auto aGraphics = lcl_getGraphics(mxComponent);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(5), aGraphics.size());
+
+ // Get all GfxLink addresses, we expect all of them to be the same
+ // indicating we use the same graphic instance for all shapes
+ std::vector<sal_Int64> aGfxLinkAddresses;
+ for (auto const & rxGraphic : aGraphics)
+ {
+ GfxLink* pLink = Graphic(rxGraphic).GetSharedGfxLink().get();
+ aGfxLinkAddresses.emplace_back(reinterpret_cast<sal_Int64>(pLink));
+ }
+
+ // Check all addresses are the same
+ bool bResult = std::equal(aGfxLinkAddresses.begin() + 1, aGfxLinkAddresses.end(), aGfxLinkAddresses.begin());
+ const OString sGraphicNotTheSameFailedMessage = OString::Concat("Graphics not the same for filter: '") +
+ rFilterName.toUtf8() + OString::Concat("'");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sGraphicNotTheSameFailedMessage.getStr(), true, bResult);
+ }
+}
+
+void Test::testCharHighlightBody()
+{
+ // MS Word has two kind of character backgrounds called character shading and highlighting
+ // MS filters handle these attributes separately, but ODF export merges them into one background attribute
+
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("char_highlight.docx");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ const uno::Reference< text::XTextRange > xPara = getParagraph(1);
+ // Both highlight and background
+ const Color nBackColor(0x4F81BD);
+ for( int nRun = 1; nRun <= 16; ++nRun )
+ {
+ const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
+ Color nHighlightColor;
+ switch( nRun )
+ {
+ case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
+ case 2: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
+ case 3: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
+ case 4: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
+ case 5: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
+ case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
+ case 7: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
+ case 8: nHighlightColor = COL_WHITE; break; //white 0xffffff
+ case 9: nHighlightColor = COL_BLUE; break;//blue 0x000080
+ case 10: nHighlightColor = COL_CYAN; break; //cyan 0x008080
+ case 11: nHighlightColor = COL_GREEN; break; //green 0x008000
+ case 12: nHighlightColor = COL_MAGENTA; break; //magenta 0x800080
+ case 13: nHighlightColor = COL_RED; break; //red 0x800000
+ case 14: nHighlightColor = COL_BROWN; break; //brown 0x808000
+ case 15: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
+ case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
+ }
+
+ if (rFilterName == "writer8")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ else // MS filters
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ }
+
+ // Only highlight
+ {
+ const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,18), uno::UNO_QUERY);
+ if (rFilterName == "writer8")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ }
+
+ // Only background
+ {
+ const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,19), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ }
+}
+
+void Test::testCharStyleHighlight()
+{
+ // MS Word has two kind of character backgrounds called character shading and highlighting.
+ // However, their character style can only accept shading. It ignores the highlighting value.
+
+ const OUString aFilterNames[] = {
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("tdf138345_charstyle_highlight.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ uno::Reference<beans::XPropertySet> xCharStyle;
+ getStyles("CharacterStyles")->getByName("charBackground") >>= xCharStyle;
+ const Color nBackColor(0xFFDBB6); //orange-y
+
+ // Always export character style's background colour as shading, never as highlighting.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xCharStyle,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xCharStyle,"CharBackColor"));
+ }
+}
+
+void Test::testCharHighlight()
+{
+ SvtFilterOptions& rOpt = SvtFilterOptions::Get();
+ rOpt.SetCharBackground2Shading();
+
+ testCharHighlightBody();
+ testCharStyleHighlight();
+
+ rOpt.SetCharBackground2Highlighting();
+
+ testCharHighlightBody();
+ testCharStyleHighlight();
+}
+
+void Test::testCharHighlightODF()
+{
+ createSwDoc("char_background_editing.docx");
+
+ // don't check import, testMSCharBackgroundEditing already does that
+
+ uno::Reference<text::XTextRange> xPara = getParagraph(1);
+ for (int i = 1; i <= 4; ++i)
+ {
+ uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
+ switch (i)
+ {
+ case 1: // non-transparent highlight
+ xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
+ xRun->setPropertyValue("CharBackTransparent", uno::Any(true));
+ xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(64)));
+ break;
+
+ case 2: // transparent backcolor
+ xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
+ xRun->setPropertyValue("CharBackTransparent", uno::Any(true));
+ xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
+ break;
+
+ case 3: // non-transparent backcolor
+ xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
+ xRun->setPropertyValue("CharBackTransparent", uno::Any(false));
+ xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
+ break;
+
+ case 4: // non-transparent highlight again
+ xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
+ xRun->setPropertyValue("CharBackTransparent", uno::Any(false));
+ xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(64)));
+ break;
+ }
+ }
+
+ saveAndReload("writer8");
+
+ xPara.set(getParagraph(1));
+ for (int i = 1; i <= 4; ++i)
+ {
+ uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(COL_TRANSPARENT), getProperty<sal_Int32>(xRun, "CharHighlight"));
+ switch (i)
+ {
+ case 1: // non-transparent highlight
+ CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, "CharBackColor"));
+ CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
+ break;
+ case 2: // transparent backcolor
+ CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, getProperty<Color>(xRun, "CharBackColor"));
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xRun, "CharBackTransparent"));
+ break;
+ case 3: // non-transparent backcolor
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, getProperty<Color>(xRun, "CharBackColor"));
+ CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
+ break;
+ case 4: // non-transparent highlight again
+ CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, "CharBackColor"));
+ CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
+ break;
+ }
+ }
+}
+
+void Test::testMSCharBackgroundEditing()
+{
+ // Simulate the editing process of imported MSO character background attributes
+ // and check how export behaves.
+
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("char_background_editing.docx");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Check whether import was done on the right way
+ uno::Reference< text::XTextRange > xPara = getParagraph(1);
+ {
+ uno::Reference<beans::XPropertySet> xRun(getRun(xPara,1), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
+
+ xRun.set(getRun(xPara,2), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
+
+ xRun.set(getRun(xPara,3), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
+
+ xRun.set(getRun(xPara,4), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
+ }
+
+ // Simulate editing
+ for( int i = 1; i <= 4; ++i )
+ {
+ uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
+ // Change background
+ Color nBackColor;
+ switch( i )
+ {
+ case 1: nBackColor = COL_BLACK; break; //black 0x000000
+ case 2: nBackColor = COL_LIGHTCYAN; break; //cyan 0x00ffff
+ case 3: nBackColor = COL_LIGHTGREEN; break; //green 0x00ff00
+ case 4: nBackColor = COL_LIGHTMAGENTA; break; //magenta 0xff00ff
+ }
+ xRun->setPropertyValue("CharBackColor", uno::Any(nBackColor));
+ // Remove highlighting
+ xRun->setPropertyValue("CharHighlight", uno::Any(COL_TRANSPARENT));
+ // Remove shading marker
+ uno::Sequence<beans::PropertyValue> aGrabBag = getProperty<uno::Sequence<beans::PropertyValue> >(xRun,"CharInteropGrabBag");
+ for (beans::PropertyValue& rProp : asNonConstRange(aGrabBag))
+ {
+ if (rProp.Name == "CharShadingMarker")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), true, rProp.Value.get<bool>());
+ rProp.Value <<= false;
+ }
+ }
+ xRun->setPropertyValue("CharInteropGrabBag", uno::Any(aGrabBag));
+ }
+
+ SvtFilterOptions& rOpt = SvtFilterOptions::Get();
+ rOpt.SetCharBackground2Highlighting();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check whether background was exported as highlighting
+ xPara.set(getParagraph(1));
+ for( int i = 1; i <= 4; ++i )
+ {
+ Color nBackColor;
+ switch( i )
+ {
+ case 1: nBackColor = COL_BLACK; break; //black 0x000000
+ case 2: nBackColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
+ case 3: nBackColor = COL_LIGHTGREEN; break; //light green 0x00ff00
+ case 4: nBackColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
+ }
+ const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
+ if (rFilterName == "writer8")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharHighlight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
+ }
+ }
+ }
+}
+
+void Test::testCharBackgroundToHighlighting()
+{
+ // MSO highlighting has less kind of values so let's see how LO character background is converted
+ // to these values
+
+ const OUString aFilterNames[] = {
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ createSwDoc("char_background.odt");
+
+ OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+
+ SvtFilterOptions& rOpt = SvtFilterOptions::Get();
+ rOpt.SetCharBackground2Highlighting();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check highlight color
+ const uno::Reference< text::XTextRange > xPara = getParagraph(1);
+ for( int nRun = 1; nRun <= 19; ++nRun )
+ {
+ const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
+ Color nHighlightColor;
+ switch( nRun )
+ {
+ case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
+ case 2: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
+ case 3: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
+ case 4: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
+ case 5: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
+ case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
+ case 7: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
+ case 8: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
+ case 9: nHighlightColor = COL_GREEN; break; //dark green 0x008000
+ case 10: nHighlightColor = COL_MAGENTA; break; //dark magenta 0x800080
+ case 11: nHighlightColor = COL_BLUE; break; //dark blue 0x000080
+ case 12: nHighlightColor = COL_BROWN; break; //brown 0x808000
+ case 13: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
+ case 14: nHighlightColor = COL_BLACK; break; //black 0x000000
+ case 15: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
+ case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
+ case 17: nHighlightColor = COL_RED; break; //dark red 0x800000
+ case 18: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
+ case 19: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
+ }
+ const OString sMessage = sFailedMessage +". Index of run with unmatched color: " + OString::number(nRun);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharHighlight"));
+ }
+ }
+}
+
+#if !defined(_WIN32)
+void Test::testSkipImages()
+{
+ // Check how LO skips image loading (but not texts of textboxes and custom shapes)
+ // during DOC and DOCX import, using the "SkipImages" FilterOptions.
+
+ std::pair<OUString, OUString> aFilterNames[] = {
+ { "skipimages.doc", "" },
+ { "skipimages.doc", "SkipImages" },
+ { "skipimages.docx", "" },
+ { "skipimages.docx", "SkipImages" }
+ };
+
+ for (auto const & rFilterNamePair : aFilterNames)
+ {
+ bool bSkipImages = !rFilterNamePair.second.isEmpty();
+ OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterNamePair.first.toUtf8();
+
+ setImportFilterOptions(rFilterNamePair.second);
+ createSwDoc(rFilterNamePair.first.toUtf8().getStr());
+ sFailedMessage += " - " + rFilterNamePair.second.toUtf8();
+
+ // Check shapes (images, textboxes, custom shapes)
+ uno::Reference<drawing::XShape> xShape;
+ uno::Reference<graphic::XGraphic> xGraphic;
+ uno::Reference< beans::XPropertySet > XPropSet;
+ uno::Reference<awt::XBitmap> xBitmap;
+
+ bool bHasTextboxText = false;
+ bool bHasCustomShapeText = false;
+ sal_Int32 nImageCount = 0;
+
+ for (int i = 1; i<= getShapes(); i++)
+ {
+ xShape = getShape(i);
+ XPropSet.set( xShape, uno::UNO_QUERY_THROW );
+ try
+ {
+ XPropSet->getPropertyValue("Graphic") >>= xGraphic;
+ xBitmap.set(xGraphic, uno::UNO_QUERY);
+ if (xBitmap.is())
+ nImageCount++;
+ }
+ catch (beans::UnknownPropertyException &)
+ { /* ignore */ }
+
+ uno::Reference<text::XTextRange> xText(xShape, uno::UNO_QUERY);
+ if (xText.is())
+ {
+ OUString shapeText = xText->getString();
+ if (shapeText.startsWith("Lorem ipsum"))
+ bHasTextboxText = true;
+ else if (shapeText.startsWith("Nam pretium"))
+ bHasCustomShapeText = true;
+ }
+ }
+
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasTextboxText);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasCustomShapeText);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(bSkipImages ? 0 : 3), nImageCount );
+ }
+}
+#endif
+
+static auto verifyNestedFieldmark(OUString const& rTestName,
+ uno::Reference<lang::XComponent> const& xComponent) -> void
+{
+ SwDoc const*const pDoc(dynamic_cast<SwXTextDocument&>(*xComponent).GetDocShell()->GetDoc());
+ IDocumentMarkAccess const& rIDMA(*pDoc->getIDocumentMarkAccess());
+
+ // no spurious bookmarks have been created
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
+ sal_Int32(0), rIDMA.getBookmarksCount());
+
+ // check inner fieldmark
+ SwNodeIndex const node1(*pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), +2);
+ SwPosition const innerPos(*node1.GetNode().GetTextNode(),
+ node1.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
+ sal_Int32(1), innerPos.GetContentIndex());
+ ::sw::mark::IFieldmark *const pInner(rIDMA.getFieldmarkAt(innerPos));
+ CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pInner);
+ OUString const innerString(SwPaM(pInner->GetMarkPos(), pInner->GetOtherMarkPos()).GetText());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
+ OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), innerString);
+
+ // check outer fieldmark
+ SwNodeIndex const node2(node1, -1);
+ SwPosition const outerPos(*node2.GetNode().GetTextNode(),
+ node2.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
+ sal_Int32(0), outerPos.GetContentIndex());
+ ::sw::mark::IFieldmark const*const pOuter(rIDMA.getFieldmarkAt(outerPos));
+ CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pOuter);
+ OUString const outerString(SwPaM(pOuter->GetMarkPos(), pOuter->GetOtherMarkPos()).GetText());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
+ OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" " + OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND) + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" foo " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz" + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
+ + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), outerString);
+
+ // must return innermost mark
+ CPPUNIT_ASSERT_EQUAL(pInner, rIDMA.getInnerFieldmarkFor(innerPos));
+}
+
+void Test::testNestedFieldmark()
+{
+ // experimental config setting
+ Resetter resetter(
+ [] () {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(false, pBatch);
+ return pBatch->commit();
+ });
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(true, pBatch);
+ pBatch->commit();
+
+ std::pair<OUString, OUString> const aFilterNames[] = {
+ {"writer8", "fieldmark_QUOTE_nest.fodt"},
+ {"Office Open XML Text", "fieldmark_QUOTE_nest.docx"},
+ {"Rich Text Format", "fieldmark_QUOTE_nest.rtf"},
+ };
+
+ for (auto const & rFilterName : aFilterNames)
+ {
+ createSwDoc(rFilterName.second.toUtf8().getStr());
+
+ verifyNestedFieldmark(rFilterName.first + ", load", mxComponent);
+
+ // Export the document and import again
+ saveAndReload(rFilterName.first);
+
+ verifyNestedFieldmark(rFilterName.first + " exported-reload", mxComponent);
+ }
+}
+
+auto Test::verifyText13(char const*const pTestName) -> void
+{
+ // OFFICE-3789 style:header-first/style:footer-first
+ uno::Reference<beans::XPropertySet> xPageStyle;
+ getStyles("PageStyles")->getByName("Standard") >>= xPageStyle;
+ uno::Reference<text::XText> xHF(getProperty<uno::Reference<text::XText>>(xPageStyle, "HeaderTextFirst"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Header first"), xHF->getString());
+ uno::Reference<text::XText> xFF(getProperty<uno::Reference<text::XText>>(xPageStyle, "FooterTextFirst"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Footer first"), xFF->getString());
+ // OFFICE-3767 text:contextual-spacing
+ uno::Reference<text::XTextRange> xPara(getParagraph(1));
+ CPPUNIT_ASSERT_MESSAGE(pTestName, getProperty<bool>(xPara, "ParaContextMargin"));
+ // OFFICE-3776 meta:creator-initials
+ uno::Reference<text::XTextRange> xRun(getRun(xPara, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Annotation"), getProperty<OUString>(xRun, "TextPortionType"));
+ uno::Reference<beans::XPropertySet> xComment(getProperty<uno::Reference<beans::XPropertySet>>(xRun, "TextField"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("dj"), getProperty<OUString>(xComment, "Initials"));
+ // OFFICE-3941 text:index-entry-link-start/text:index-entry-link-end
+ uno::Reference<text::XDocumentIndexesSupplier> xDIS(mxComponent, uno::UNO_QUERY);
+ uno::Reference<container::XIndexAccess> xIndexes(xDIS->getDocumentIndexes());
+ uno::Reference<text::XDocumentIndex> xIndex(xIndexes->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<container::XIndexReplace> xLevels(getProperty<uno::Reference<container::XIndexReplace>>(xIndex, "LevelFormat"));
+ uno::Sequence<beans::PropertyValues> format;
+ xLevels->getByIndex(1) >>= format; // 1-based?
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenType"), format[0][0].Name);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenHyperlinkStart"), format[0][0].Value.get<OUString>());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenType"), format[4][0].Name);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenHyperlinkEnd"), format[4][0].Value.get<OUString>());
+}
+
+// test ODF 1.3 new text document features
+void Test::testODF13()
+{
+ // import
+ createSwDoc("text13e.odt");
+
+ // check model
+ verifyText13("import");
+
+ Resetter _([]() {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ return pBatch->commit();
+ });
+
+ {
+ // export ODF 1.3
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch);
+ pBatch->commit();
+
+ saveAndReload("writer8");
+
+ // check XML
+ xmlDocUniquePtr pContentXml = parseExport("content.xml");
+ assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@style:contextual-spacing='true']"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-start"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-start"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-end"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-end"_ostr, 0);
+ xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first"_ostr);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first"_ostr, 0);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first"_ostr);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first"_ostr, 0);
+
+ // check model
+ verifyText13("1.3 reload");
+ }
+ {
+ // export ODF 1.2 extended
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(9, pBatch);
+ pBatch->commit();
+
+ // FIXME: it's not possible to use 'reload' here because the validation fails with
+ // Error: unexpected attribute "loext:contextual-spacing"
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer8");
+
+ uno::Reference<frame::XStorable> const xStorable(mxComponent, uno::UNO_QUERY);
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // check XML
+ xmlDocUniquePtr pContentXml = parseExport("content.xml");
+ assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@loext:contextual-spacing='true']"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-start"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-start"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-end"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-end"_ostr, 0);
+ xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first"_ostr);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first"_ostr, 0);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first"_ostr);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first"_ostr, 0);
+
+ // reload
+ mxComponent->dispose();
+ mxComponent = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.text.TextDocument");
+
+ // check model
+ verifyText13("1.2 Extended reload");
+ }
+ {
+ // export ODF 1.2
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(4, pBatch);
+ pBatch->commit();
+
+ // don't reload - no point
+ save("writer8");
+
+ // check XML
+ xmlDocUniquePtr pContentXml = parseExport("content.xml");
+ assertXPathNoAttribute(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties"_ostr, "contextual-spacing"_ostr);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-start"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-start"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-end"_ostr, 0);
+ assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-end"_ostr, 0);
+ xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first"_ostr, 0);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first"_ostr, 0);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first"_ostr, 0);
+ assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first"_ostr, 0);
+ }
+}
+
+void Test::testRedlineFlags()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Rich Text Format",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ createSwDoc();
+ SwDoc* pDoc = getSwDoc();
+
+ SwPaM pam(SwPosition(pDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1)));
+ pDoc->getIDocumentContentOperations().InsertString(pam, "foo bar baz");
+
+ IDocumentRedlineAccess & rIDRA(pDoc->getIDocumentRedlineAccess());
+ // enable change tracking
+ rIDRA.SetRedlineFlags(rIDRA.GetRedlineFlags()
+ | RedlineFlags::On | RedlineFlags::ShowDelete);
+
+ // need a delete redline to trigger mode switching
+ pam.Move(fnMoveForward, GoInDoc);
+ pam.SetMark();
+ pam.Move(fnMoveBackward, GoInDoc);
+ pDoc->getIDocumentContentOperations().DeleteAndJoin(pam);
+
+ // hide delete redlines
+ RedlineFlags const nRedlineFlags =
+ rIDRA.GetRedlineFlags() & ~RedlineFlags::ShowDelete;
+ rIDRA.SetRedlineFlags(nRedlineFlags);
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ // export the document
+ save(rFilterName);
+
+ // tdf#97103 check that redline mode is properly restored
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ OString(OString::Concat("redline mode not restored in ") + rFilterName.toUtf8()).getStr(),
+ static_cast<int>(nRedlineFlags), static_cast<int>(rIDRA.GetRedlineFlags()));
+ }
+}
+
+void Test::testBulletAsImage()
+{
+ OUString aFilterNames[] = {
+ "writer8",
+ "MS Word 97",
+ "Office Open XML Text",
+ "Rich Text Format",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ createSwDoc("BulletAsImage.odt");
+
+ // Check if import was successful
+ {
+ uno::Reference<text::XTextRange> xPara(getParagraph(1));
+ uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
+ uno::Reference<container::XIndexAccess> xLevels;
+ xLevels.set(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aProperties;
+ xLevels->getByIndex(0) >>= aProperties;
+ uno::Reference<awt::XBitmap> xBitmap;
+ awt::Size aSize;
+ sal_Int16 nNumberingType = -1;
+
+ for (beans::PropertyValue const & rProperty : std::as_const(aProperties))
+ {
+ if (rProperty.Name == "NumberingType")
+ {
+ nNumberingType = rProperty.Value.get<sal_Int16>();
+ }
+ else if (rProperty.Name == "GraphicBitmap")
+ {
+ if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
+ {
+ xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
+ }
+ }
+ else if (rProperty.Name == "GraphicSize")
+ {
+ aSize = rProperty.Value.get<awt::Size>();
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
+
+ // Graphic Bitmap
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
+
+ // Graphic Size
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
+ }
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ {
+ uno::Reference<text::XTextRange> xPara(getParagraph(1));
+ uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
+ uno::Reference<container::XIndexAccess> xLevels;
+ xLevels.set(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aProperties;
+ xLevels->getByIndex(0) >>= aProperties;
+ uno::Reference<awt::XBitmap> xBitmap;
+ awt::Size aSize;
+ sal_Int16 nNumberingType = -1;
+
+ for (beans::PropertyValue const & rProperty : std::as_const(aProperties))
+ {
+ if (rProperty.Name == "NumberingType")
+ {
+ nNumberingType = rProperty.Value.get<sal_Int16>();
+ }
+ else if (rProperty.Name == "GraphicBitmap")
+ {
+ if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
+ {
+ xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
+ }
+ }
+ else if (rProperty.Name == "GraphicSize")
+ {
+ aSize = rProperty.Value.get<awt::Size>();
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
+
+ // Graphic Bitmap
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
+ Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
+
+ // Graphic Size
+ if (rFilterName == "write8") // ODT is correct
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
+ }
+ // FIXME: MS Filters don't work correctly for graphic bullet size
+ else if (rFilterName == "Office Open XML Text" || rFilterName == "Rich Text Format")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Height);
+ }
+ else if (rFilterName == "MS Word 97")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Height);
+ }
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testListLabelPDFExport)
+{
+ createSwDoc();
+
+ uno::Reference<text::XTextDocument> xDoc(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<text::XText> xText(xDoc->getText());
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexReplace> xNumRule(
+ xFactory->createInstance("com.sun.star.text.NumberingRules"),
+ uno::UNO_QUERY_THROW);
+ OUString listFormat;
+ for (sal_Int32 i = 0; i < xNumRule->getCount(); ++i)
+ {
+ uno::Sequence<beans::PropertyValue> format;
+ format.getArray();
+ xNumRule->getByIndex(i) >>= format;
+ {
+ auto it(::std::find_if(format.begin(), format.end(),
+ [](auto const& r) { return r.Name == "NumberingType"; }));
+ // need something RTL
+ const_cast<uno::Any&>(it->Value) <<= style::NumberingType::CHARS_ARABIC;
+ }
+ {
+#if 0
+ // this doesn't work any more
+ auto it(::std::find_if(format.begin(), format.end(),
+ [](auto const& r) { return r.Name == "ParentNumbering"; }));
+ const_cast<uno::Any&>(it->Value) <<= sal_Int16(i + 1);
+#endif
+ listFormat += "%" + OUString::number(i+1) + "%.";
+ auto it(::std::find_if(format.begin(), format.end(),
+ [](auto const& r) { return r.Name == "ListFormat"; }));
+ const_cast<uno::Any&>(it->Value) <<= listFormat;
+ }
+ xNumRule->replaceByIndex(i, uno::Any(format));
+ }
+ uno::Reference<beans::XPropertySet>(getParagraph(1), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingRules", uno::Any(xNumRule));
+ xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
+ uno::Reference<beans::XPropertySet>(getParagraph(2), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingLevel", uno::Any(sal_Int16(1)));
+ xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
+ uno::Reference<beans::XPropertySet>(getParagraph(3), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingLevel", uno::Any(sal_Int16(2)));
+
+ // check PDF export of the list items (label in particular)
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result with pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ // Non-NULL pPdfDocument means pdfium is available.
+ if (pPdfDocument != nullptr)
+ {
+ // The document has one page.
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
+ CPPUNIT_ASSERT(pPdfPage);
+
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pPdfTextPage);
+
+ int nChars = pPdfTextPage->countChars();
+ CPPUNIT_ASSERT_EQUAL(22, nChars);
+
+ // Check that the label strings were exported correctly
+ std::vector<sal_uInt32> aChars(nChars);
+ for (int i = 0; i < nChars; i++)
+ aChars[i] = pPdfTextPage->getUnicode(i);
+ OUString aText(aChars.data(), aChars.size());
+ CPPUNIT_ASSERT_EQUAL(u"\u0623\r\n.\r\n\u0623.\u0623\r\n.\r\n\u0623.\u0623.\u0623\r\n."_ustr, aText);
+ }
+
+ // Parse the document again to get its raw content
+ // TODO: get the content from PDFiumPage somehow
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* const pEnd = pStart + aUncompressed.GetSize();
+
+ enum
+ {
+ Default,
+ Lbl,
+ LblFoundText
+ } state
+ = Default;
+
+ auto nLine(0);
+ auto nLbl(0);
+ auto nLblTj(0);
+ auto nLblTJ(0);
+ std::vector<int> mcids;
+ while (true)
+ {
+ ++nLine;
+ auto const pLine = ::std::find(pStart, pEnd, '\n');
+ if (pLine == pEnd)
+ {
+ break;
+ }
+ std::string_view const line(pStart, pLine - pStart);
+ pStart = pLine + 1;
+ if (!line.empty() && line[0] != '%')
+ {
+ ::std::cerr << nLine << ": " << line << "\n";
+ if (o3tl::starts_with(line, "/Lbl<</MCID") && o3tl::ends_with(line, ">>BDC"))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ mcids.push_back(o3tl::toInt32(line.substr(12)));
+ state = Lbl;
+ ++nLbl;
+ }
+ else if (state == Lbl)
+ {
+ auto const endj(line.find(">Tj"));
+ if (endj != ::std::string_view::npos)
+ {
+ state = LblFoundText;
+ ++nLblTj;
+ }
+ else
+ {
+ auto const endJ(line.find("]TJ"));
+ if (endJ != ::std::string_view::npos)
+ {
+ state = LblFoundText;
+ ++nLblTJ;
+ }
+ }
+ }
+ else if (state != Default && line == "EMC")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("missing text", LblFoundText, state);
+ state = Default;
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
+ // ideally there should be 3 but apparently every text portion gets its own
+ // tag - this should not be a problem if these are grouped in the structure
+ // tree into 3 Lbl.
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLbl);
+ // these are quite arbitrary?
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLblTJ + nLblTj);
+
+ auto nL(0);
+ for (const auto& rDocElement : aDocument.GetElements())
+ {
+ auto pObject0 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
+ if (!pObject0)
+ continue;
+ auto pType0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("Type"_ostr));
+ if (!pType0 || pType0->GetValue() != "StructElem")
+ {
+ continue;
+ }
+ auto pS0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("S"_ostr));
+ if (!pS0 || pS0->GetValue() != "Document")
+ {
+ continue;
+ }
+ auto pKids0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject0->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids0);
+
+ for (const auto& pKid0 : pKids0->GetElements())
+ {
+ auto pRefKid0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid0);
+ CPPUNIT_ASSERT(pRefKid0);
+ auto pObject1 = pRefKid0->LookupObject();
+ CPPUNIT_ASSERT(pObject1);
+ auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType1);
+
+ if (pType1 && pType1->GetValue() == "StructElem")
+ {
+ auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
+ if (pS1 && pS1->GetValue() == "L")
+ {
+ ++nL;
+ auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1);
+ // this is purely structural so there should be 1 child
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1->GetElements().size());
+
+ auto pRefKid11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid11);
+ auto pObject11 = pRefKid11->LookupObject();
+ CPPUNIT_ASSERT(pObject11);
+ auto pType11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType11);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11->GetValue());
+ auto pS11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS11);
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11->GetValue());
+ // LI has 2 children: Lbl and LBody
+ auto pKids11 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids11);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11->GetElements().size());
+
+ auto pRefKid111 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid111);
+ auto pObject111 = pRefKid111->LookupObject();
+ CPPUNIT_ASSERT(pObject111);
+ auto pType111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType111);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType111->GetValue());
+ auto pS111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS111);
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS111->GetValue());
+ // Lbl has 2 children: the first 2 mcids (in order)
+ auto pKids111 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject111->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids111);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids111->GetElements().size());
+
+ auto pRefKid1111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid1111);
+ CPPUNIT_ASSERT_EQUAL(mcids[0], int(pRefKid1111->GetValue()));
+ auto pRefKid1112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid1112);
+ CPPUNIT_ASSERT_EQUAL(mcids[1], int(pRefKid1112->GetValue()));
+
+ auto pRefKid112 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid112);
+ auto pObject112 = pRefKid112->LookupObject();
+ CPPUNIT_ASSERT(pObject112);
+ auto pType112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType112);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112->GetValue());
+ auto pS112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS112);
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112->GetValue());
+ // LBody has 2 children: paragraph and nested L (in order)
+ auto pKids112 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids112);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112->GetElements().size());
+
+ auto pRefKid1121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid1121);
+ auto pObject1121 = pRefKid1121->LookupObject();
+ CPPUNIT_ASSERT(pObject1121);
+ auto pType1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType1121);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1121->GetValue());
+ auto pS1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS1121);
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1121->GetValue());
+
+ auto pRefKid1122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid1122);
+ auto pObject1122 = pRefKid1122->LookupObject();
+ CPPUNIT_ASSERT(pObject1122);
+ auto pType1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType1122);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122->GetValue());
+ auto pS1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS1122);
+ CPPUNIT_ASSERT_EQUAL("L"_ostr, pS1122->GetValue());
+ auto pKids1122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1122);
+ // this is purely structural so there should be 1 child
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122->GetElements().size());
+
+ auto pRefKid11221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid11221);
+ auto pObject11221 = pRefKid11221->LookupObject();
+ CPPUNIT_ASSERT(pObject11221);
+ auto pType11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType11221);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11221->GetValue());
+ auto pS11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS11221);
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11221->GetValue());
+ // LI has 2 children: Lbl and LBody
+ auto pKids11221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids11221);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221->GetElements().size());
+
+ auto pRefKid112211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid112211);
+ auto pObject112211 = pRefKid112211->LookupObject();
+ CPPUNIT_ASSERT(pObject112211);
+ auto pType112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType112211);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112211->GetValue());
+ auto pS112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS112211);
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS112211->GetValue());
+ // Lbl has 2 children: the first 2 mcids (in order)
+ auto pKids112211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112211->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids112211);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112211->GetElements().size());
+
+ auto pRefKid1122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid1122111);
+ CPPUNIT_ASSERT_EQUAL(mcids[2], int(pRefKid1122111->GetValue()));
+ auto pRefKid1122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid1122112);
+ CPPUNIT_ASSERT_EQUAL(mcids[3], int(pRefKid1122112->GetValue()));
+
+ auto pRefKid112212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid112212);
+ auto pObject112212 = pRefKid112212->LookupObject();
+ CPPUNIT_ASSERT(pObject112212);
+ auto pType112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType112212);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212->GetValue());
+ auto pS112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS112212);
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112212->GetValue());
+ // LBody has 2 children: paragraph and nested L (in order)
+ auto pKids112212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids112212);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212->GetElements().size());
+
+ auto pRefKid1122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid1122121);
+ auto pObject1122121 = pRefKid1122121->LookupObject();
+ CPPUNIT_ASSERT(pObject1122121);
+ auto pType1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType1122121);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122121->GetValue());
+ auto pS1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS1122121);
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1122121->GetValue());
+
+ auto pRefKid1122122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid1122122);
+ auto pObject1122122 = pRefKid1122122->LookupObject();
+ CPPUNIT_ASSERT(pObject1122122);
+ auto pType1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType1122122);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122122->GetValue());
+ auto pS1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS1122122);
+ CPPUNIT_ASSERT_EQUAL("L"_ostr, pS1122122->GetValue());
+ auto pKids1122122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122122->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids1122122);
+ // this is purely structural so there should be 1 child
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122122->GetElements().size());
+
+ auto pRefKid11221221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122122->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid11221221);
+ auto pObject11221221 = pRefKid11221221->LookupObject();
+ CPPUNIT_ASSERT(pObject11221221);
+ auto pType11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType11221221);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11221221->GetValue());
+ auto pS11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS11221221);
+ CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11221221->GetValue());
+ // LI has 2 children: Lbl and LBody
+ auto pKids11221221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221221->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids11221221);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221221->GetElements().size());
+
+ auto pRefKid112212211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid112212211);
+ auto pObject112212211 = pRefKid112212211->LookupObject();
+ CPPUNIT_ASSERT(pObject112212211);
+ auto pType112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType112212211);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212211->GetValue());
+ auto pS112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS112212211);
+ CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS112212211->GetValue());
+ // Lbl has 2 children: the first 2 mcids (in order)
+ auto pKids112212211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212211->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids112212211);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212211->GetElements().size());
+
+ auto pRefKid1122122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid1122122111);
+ CPPUNIT_ASSERT_EQUAL(mcids[4], int(pRefKid1122122111->GetValue()));
+ auto pRefKid1122122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid1122122112);
+ CPPUNIT_ASSERT_EQUAL(mcids[5], int(pRefKid1122122112->GetValue()));
+
+ auto pRefKid112212212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[1]);
+ CPPUNIT_ASSERT(pRefKid112212212);
+ auto pObject112212212 = pRefKid112212212->LookupObject();
+ CPPUNIT_ASSERT(pObject112212212);
+ auto pType112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType112212212);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212212->GetValue());
+ auto pS112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS112212212);
+ CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112212212->GetValue());
+ // inner LBody has 1 children: paragraph
+ auto pKids112212212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212212->Lookup("K"_ostr));
+ CPPUNIT_ASSERT(pKids112212212);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pKids112212212->GetElements().size());
+
+ auto pRefKid1122122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212212->GetElements()[0]);
+ CPPUNIT_ASSERT(pRefKid1122122121);
+ auto pObject1122122121 = pRefKid1122122121->LookupObject();
+ CPPUNIT_ASSERT(pObject1122122121);
+ auto pType1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("Type"_ostr));
+ CPPUNIT_ASSERT(pType1122122121);
+ CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122122121->GetValue());
+ auto pS1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("S"_ostr));
+ CPPUNIT_ASSERT(pS1122122121);
+ CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1122122121->GetValue());
+ }
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nL)>(1), nL);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf143311)
+{
+ createSwDoc("tdf143311-1.docx");
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
+ {
+ // add another one that's a SdrObject
+ uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(
+ xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ xShapeProps->setPropertyValue("Decorative", uno::Any(true));
+ uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPageSupplier->getDrawPage());
+ xDrawPage->add(xShape);
+ }
+ // check DOCX filters
+ saveAndReload("Office Open XML Text");
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), "Decorative"));
+ {
+ // tdf#153925 not imported - check default and set it to test ODF filters
+ uno::Reference<beans::XPropertySet> const xStyle(getStyles("FrameStyles")->getByName("Formula"), uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xStyle, "Decorative"));
+ xStyle->setPropertyValue("Decorative", uno::Any(true));
+ }
+ // check ODF filters
+ saveAndReload("writer8");
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), "Decorative"));
+ CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getStyles("FrameStyles")->getByName("Formula"), "Decorative"));
+
+ // check PDF export
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ // Enable PDF/UA
+ uno::Sequence<beans::PropertyValue> aFilterData(
+ comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
+ aMediaDescriptor["FilterData"] <<= aFilterData;
+ css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ vcl::filter::PDFDocument aDocument;
+ SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(aDocument.Read(aStream));
+
+ // The document has one page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+
+ vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
+ CPPUNIT_ASSERT(pContents);
+ vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ // Uncompress it.
+ SvMemoryStream aUncompressed;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ rObjectStream.Seek(0);
+ aZCodec.Decompress(rObjectStream, aUncompressed);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+
+ auto pStart = static_cast<const char*>(aUncompressed.GetData());
+ const char* const pEnd = pStart + aUncompressed.GetSize();
+
+ enum
+ {
+ Default,
+ Artifact,
+ Tagged
+ } state
+ = Default;
+
+ auto nLine(0);
+ auto nTagged(0);
+ auto nArtifacts(0);
+ while (true)
+ {
+ ++nLine;
+ auto const pLine = ::std::find(pStart, pEnd, '\n');
+ if (pLine == pEnd)
+ {
+ break;
+ }
+ std::string_view const line(pStart, pLine - pStart);
+ pStart = pLine + 1;
+ if (!line.empty() && line[0] != '%')
+ {
+ ::std::cerr << nLine << ": " << line << "\n";
+ if (line == "/Artifact BMC")
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Artifact;
+ ++nArtifacts;
+ }
+ else if (o3tl::starts_with(line, "/Standard<</MCID") && o3tl::ends_with(line, ">>BDC"))
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
+ state = Tagged;
+ ++nTagged;
+ }
+ else if (line == "EMC")
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
+ state = Default;
+ }
+ else if (nLine > 1) // first line is expected "0.1 w"
+ {
+ CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
+ }
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
+ CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(25), nTagged); // text in body
+ // 1 decorative image + 1 decorative shape + 1 pre-existing rectangle border or something
+ CPPUNIT_ASSERT(nArtifacts >= 3);
+}
+
+void Test::testTextFormField()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (const OUString& rFilterName : aFilterNames)
+ {
+ createSwDoc("text_form_field.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check the document after round trip
+ SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
+ SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+ IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
+
+ // We have two text form fields
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
+
+ // Check whether all fieldmarks are text form fields
+ for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
+ {
+ ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMTEXT, pFieldmark->GetFieldname());
+ }
+
+ // In the first paragraph we have an empty text form field with the placeholder spaces
+ const uno::Reference< text::XTextRange > xPara = getParagraph(1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldStart"), getProperty<OUString>(getRun(xPara, 1), "TextPortionType"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldSeparator"), getProperty<OUString>(getRun(xPara, 2), "TextPortionType"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("Text"), getProperty<OUString>(getRun(xPara, 3), "TextPortionType"));
+ sal_Unicode vEnSpaces[5] = {8194, 8194, 8194, 8194, 8194};
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), getRun(xPara, 3)->getString());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldEnd"), getProperty<OUString>(getRun(xPara, 4), "TextPortionType"));
+
+ // In the second paragraph we have a set text
+ const uno::Reference< text::XTextRange > xPara2 = getParagraph(2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldStart"), getProperty<OUString>(getRun(xPara2, 1), "TextPortionType"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldSeparator"), getProperty<OUString>(getRun(xPara2, 2), "TextPortionType"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("Text"), getProperty<OUString>(getRun(xPara2, 3), "TextPortionType"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("xxxxx"), getRun(xPara2, 3)->getString());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldEnd"), getProperty<OUString>(getRun(xPara2, 4), "TextPortionType"));
+ }
+}
+
+void Test::testCheckBoxFormField()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (const OUString& rFilterName : aFilterNames)
+ {
+ createSwDoc("checkbox_form_field.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check the document after round trip
+ SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
+ SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+ IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
+
+ // We have two check box form fields
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
+
+ int nIndex = 0;
+ for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
+ {
+ ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
+
+ if(rFilterName == "Office Open XML Text") // OOXML import also generates bookmarks
+ {
+ if(!pFieldmark)
+ continue;
+ }
+
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMCHECKBOX, pFieldmark->GetFieldname());
+ ::sw::mark::ICheckboxFieldmark* pCheckBox = dynamic_cast< ::sw::mark::ICheckboxFieldmark* >(pFieldmark);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox);
+
+ // The first one is unchecked, the other one is checked
+ if(nIndex == 0)
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !pCheckBox->IsChecked());
+ else
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox->IsChecked());
+ ++nIndex;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
+ }
+}
+
+void Test::testDropDownFormField()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+ "MS Word 97",
+ "Office Open XML Text",
+ };
+
+ for (const OUString& rFilterName : aFilterNames)
+ {
+ createSwDoc("dropdown_form_field.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check the document after round trip
+ SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
+ SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+ IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
+
+ int nIndex = 0;
+ for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
+ {
+ ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
+
+ if(!pFieldmark)
+ continue;
+
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDROPDOWN, pFieldmark->GetFieldname());
+
+ // Check drop down field's parameters.
+ const sw::mark::IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
+ css::uno::Sequence<OUString> vListEntries;
+ sal_Int32 nSelection = -1;
+ auto pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
+ if (pListEntries != pParameters->end())
+ {
+ pListEntries->second >>= vListEntries;
+
+ if(vListEntries.hasElements())
+ {
+ auto pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= nSelection;
+ }
+ }
+ }
+
+ // The first one is empty
+ if(nIndex == 0)
+ {
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !vListEntries.hasElements());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(-1), nSelection);
+ }
+ else // The second one has list and also a selected item
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(4), vListEntries.getLength());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), nSelection);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("1000"), vListEntries[0]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2000"), vListEntries[1]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("3000"), vListEntries[2]);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("4000"), vListEntries[3]);
+ }
+ ++nIndex;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
+ }
+}
+
+void Test::testDateFormField()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Office Open XML Text",
+ };
+
+ for (const OUString& rFilterName : aFilterNames)
+ {
+ createSwDoc("date_form_field.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check the document after round trip
+ if (rFilterName == "writer8")
+ {
+ SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
+ SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+ IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pMarkAccess->getAllMarksCount());
+
+ int nIndex = 0;
+ for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
+ {
+ ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*aIter);
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDATE, pFieldmark->GetFieldname());
+
+ // Check date form field's parameters.
+ const sw::mark::IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
+ OUString sDateFormat;
+ auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sDateFormat;
+ }
+
+ OUString sLang;
+ pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sLang;
+ }
+
+ OUString sCurrentDate = pFieldmark->GetContent();
+
+ // The first one has the default field content
+ if(nIndex == 0)
+ {
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
+ sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), sCurrentDate);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pFieldmark->GetMarkStart().GetContentIndex());
+ }
+ else if (nIndex == 1) // The second has the default format
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("06/12/19"), sCurrentDate);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(20), pFieldmark->GetMarkStart().GetContentIndex());
+ }
+ else if (nIndex == 2) // The third one has special format
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("hu-HU"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2019. febr. 12."), sCurrentDate);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(40), pFieldmark->GetMarkStart().GetContentIndex());
+
+ }
+ else if (nIndex == 3) // The fourth one has placeholder text
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("D, MMM YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("bm-ML"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[select date]"), sCurrentDate);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(62), pFieldmark->GetMarkStart().GetContentIndex());
+
+ }
+ else // The last one is really empty
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(""), sCurrentDate);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(82), pFieldmark->GetMarkStart().GetContentIndex());
+
+ }
+ ++nIndex;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
+ }
+ else
+ {
+ // Import from DOCX, so the fieldmark is now a content control.
+ uno::Reference<container::XEnumerationAccess> xEnumAccess(getParagraph(1), uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xTextPortions = xEnumAccess->createEnumeration();
+
+ int nIndex = 0;
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aPortionType;
+ xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
+ if (aPortionType != "ContentControl")
+ {
+ continue;
+ }
+
+ uno::Reference<text::XTextContent> xContentControl;
+ xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
+ uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
+
+ bool bDate{};
+ xContentControlProps->getPropertyValue("Date") >>= bDate;
+ CPPUNIT_ASSERT(bDate);
+
+ // Check date form field's parameters.
+ OUString sDateFormat;
+ xContentControlProps->getPropertyValue("DateFormat") >>= sDateFormat;
+
+ OUString sLang;
+ xContentControlProps->getPropertyValue("DateLanguage") >>= sLang;
+
+ uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
+ uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xContentControlEnum
+ = xContentControlEnumAccess->createEnumeration();
+ uno::Reference<text::XTextRange> xContentControlTextPortion(xContentControlEnum->nextElement(), uno::UNO_QUERY);
+ OUString sCurrentDate = xContentControlTextPortion->getString();
+
+ // The first one has the default field content
+ if(nIndex == 0)
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
+ sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), sCurrentDate);
+ }
+ else if (nIndex == 1) // The second has the default format
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("06/12/19"), sCurrentDate);
+ }
+ else if (nIndex == 2) // The third one has special format
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("hu-HU"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2019. febr. 12."), sCurrentDate);
+ }
+ else if (nIndex == 3) // The fourth one has placeholder text
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("D, MMM YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("bm-ML"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[select date]"), sCurrentDate);
+ }
+ else // The last one is really empty
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(""), sCurrentDate);
+ }
+ ++nIndex;
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
+ }
+ }
+}
+
+void Test::testDateFormFieldCharacterFormatting()
+{
+ const OUString aFilterNames[] = {
+ "writer8",
+ "Office Open XML Text",
+ };
+
+ for (const OUString& rFilterName : aFilterNames)
+ {
+ createSwDoc("date_form_field_char_formatting.odt");
+
+ const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
+
+ // Export the document and import again for a check
+ saveAndReload(rFilterName);
+
+ // Check the document after round trip
+ if (rFilterName == "writer8")
+ {
+ SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
+ SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+ IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
+
+ // Check that we have the field at the right place
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), pMarkAccess->getAllMarksCount());
+ ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*pMarkAccess->getAllMarksBegin());
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDATE, pFieldmark->GetFieldname());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(0), pFieldmark->GetMarkStart().GetContentIndex());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(11), pFieldmark->GetMarkEnd().GetContentIndex());
+
+ // We have one date field, first half of the field has bold character weight and second part has red character color
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(getRun(getParagraph(1), 3), "CharWeight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(getRun(getParagraph(1), 3), "CharColor"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(getRun(getParagraph(1), 4), "CharWeight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), Color(0xff0000), getProperty<Color>(getRun(getParagraph(1), 4), "CharColor"));
+ }
+ else
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(getRun(getParagraph(1), 1), uno::UNO_QUERY);
+ OUString aPortionType;
+ xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
+ CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aPortionType);
+
+ uno::Reference<text::XTextContent> xContentControl;
+ xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
+ uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
+ bool bDate{};
+ xContentControlProps->getPropertyValue("Date") >>= bDate;
+ CPPUNIT_ASSERT(bDate);
+
+ uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
+ uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xContentControlEnum
+ = xContentControlEnumAccess->createEnumeration();
+ xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(xTextPortion, "CharWeight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(xTextPortion, "CharColor"));
+ xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(xTextPortion, "CharWeight"));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), Color(0xff0000), getProperty<Color>(xTextPortion, "CharColor"));
+ }
+ }
+}
+
+void Test::testSvgImageSupport()
+{
+ OUString aFilterNames[] = {
+ "writer8",
+ "Office Open XML Text",
+ };
+
+ for (OUString const & rFilterName : aFilterNames)
+ {
+ // Use case to import a document containing a SVG image, export in target format, import and check if the
+ // SVG image is present and as expected in the document
+
+ // Import ODT file
+ createSwDoc("SvgImageTest.odt");
+
+ // Export the document in target format and import again
+ saveAndReload(rFilterName);
+
+ // Prepare fail message (writing which import/export filter was used)
+ const OString sFailedMessage = "Failed on filter: "_ostr + rFilterName.toUtf8();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 1, getShapes());
+
+ // Get the image
+ uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xPropertySet(xImage, uno::UNO_QUERY_THROW);
+
+ // Convert to a XGraphic
+ uno::Reference<graphic::XGraphic> xGraphic;
+ xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
+
+ // Access the Graphic
+ Graphic aGraphic(xGraphic);
+
+ // Check if it contian a VectorGraphicData struct
+ auto pVectorGraphic = aGraphic.getVectorGraphicData();
+ CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pVectorGraphic);
+
+ // Which should be of type SVG, which means we have a SVG file
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), VectorGraphicDataType::Svg, pVectorGraphic->getType());
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */