cid#1640468 Dereference after null check
[LibreOffice.git] / sw / qa / extras / globalfilter / globalfilter.cxx
blob19ffaaee929c28e18e316de9b4d0eb2ac8738b26
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <swmodeltestbase.hxx>
12 #include <com/sun/star/awt/XBitmap.hpp>
13 #include <com/sun/star/graphic/XGraphic.hpp>
14 #include <com/sun/star/graphic/GraphicType.hpp>
15 #include <com/sun/star/text/ControlCharacter.hpp>
16 #include <com/sun/star/text/XText.hpp>
17 #include <com/sun/star/text/XDocumentIndex.hpp>
18 #include <o3tl/safeint.hxx>
19 #include <o3tl/string_view.hxx>
20 #include <officecfg/Office/Common.hxx>
21 #include <tools/zcodec.hxx>
22 #include <vcl/filter/pdfdocument.hxx>
23 #include <sfx2/linkmgr.hxx>
24 #include <comphelper/propertysequence.hxx>
25 #include <unotxdoc.hxx>
26 #include <docsh.hxx>
27 #include <editsh.hxx>
28 #include <IDocumentRedlineAccess.hxx>
29 #include <IDocumentContentOperations.hxx>
30 #include <doc.hxx>
31 #include <ndgrf.hxx>
32 #include <ndtxt.hxx>
33 #include <ndindex.hxx>
34 #include <pam.hxx>
35 #include <xmloff/odffields.hxx>
36 #include <IDocumentMarkAccess.hxx>
37 #include <IMark.hxx>
38 #include <com/sun/star/awt/FontWeight.hpp>
39 #include <unotools/mediadescriptor.hxx>
40 #include <unotools/saveopt.hxx>
42 namespace
44 class Test : public SwModelTestBase
46 public:
47 Test() : SwModelTestBase(u"/sw/qa/extras/globalfilter/data/"_ustr) {}
49 void testEmbeddedGraphicRoundtrip();
50 void testLinkedGraphicRT();
51 void testImageWithSpecialID();
52 void testGraphicShape();
53 void testMultipleIdenticalGraphics();
54 void testCharHighlight();
55 void testCharHighlightODF();
56 void testCharHighlightBody();
57 void testCharStyleHighlight();
58 void testMSCharBackgroundEditing();
59 void testCharBackgroundToHighlighting();
60 #if !defined(_WIN32)
61 void testSkipImages();
62 #endif
63 void testNestedFieldmark();
64 void verifyText13(char const*);
65 void testODF13();
66 void testRedlineFlags();
67 void testBulletAsImage();
68 void testTextFormField();
69 void testCheckBoxFormField();
70 void testDropDownFormField();
71 void testDateFormField();
72 void testDateFormFieldCharacterFormatting();
73 void testSvgImageSupport();
75 CPPUNIT_TEST_SUITE(Test);
76 CPPUNIT_TEST(testEmbeddedGraphicRoundtrip);
77 CPPUNIT_TEST(testLinkedGraphicRT);
78 CPPUNIT_TEST(testImageWithSpecialID);
79 CPPUNIT_TEST(testGraphicShape);
80 CPPUNIT_TEST(testMultipleIdenticalGraphics);
81 CPPUNIT_TEST(testCharHighlight);
82 CPPUNIT_TEST(testCharHighlightODF);
83 CPPUNIT_TEST(testMSCharBackgroundEditing);
84 CPPUNIT_TEST(testCharBackgroundToHighlighting);
85 #if !defined(_WIN32)
86 CPPUNIT_TEST(testSkipImages);
87 #endif
88 CPPUNIT_TEST(testNestedFieldmark);
89 CPPUNIT_TEST(testODF13);
90 CPPUNIT_TEST(testRedlineFlags);
91 CPPUNIT_TEST(testBulletAsImage);
92 CPPUNIT_TEST(testTextFormField);
93 CPPUNIT_TEST(testCheckBoxFormField);
94 CPPUNIT_TEST(testDropDownFormField);
95 CPPUNIT_TEST(testDateFormField);
96 CPPUNIT_TEST(testDateFormFieldCharacterFormatting);
97 CPPUNIT_TEST(testSvgImageSupport);
98 CPPUNIT_TEST_SUITE_END();
101 void Test::testEmbeddedGraphicRoundtrip()
103 OUString aFilterNames[] = {
104 u"writer8"_ustr,
105 u"Rich Text Format"_ustr,
106 u"MS Word 97"_ustr,
107 u"Office Open XML Text"_ustr,
110 for (OUString const & rFilterName : aFilterNames)
112 // Check whether the export code swaps in the image which was swapped out before by auto mechanism
114 createSwDoc("document_with_two_images.odt");
116 // Export the document and import again for a check
117 saveAndReload(rFilterName);
119 // Check whether graphic exported well after it was swapped out
120 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
121 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
123 // First image
124 uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
125 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
127 // Check graphic, size
129 uno::Reference<graphic::XGraphic> xGraphic;
130 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
131 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
132 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
133 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
134 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
135 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
136 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
139 // Second Image
140 xImage.set(getShape(2), uno::UNO_QUERY);
141 XPropSet.set( xImage, uno::UNO_QUERY_THROW );
143 // Check graphic, size
145 uno::Reference<graphic::XGraphic> xGraphic;
146 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
147 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
148 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
149 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
150 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
151 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
152 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
157 void Test::testLinkedGraphicRT()
159 const OUString aFilterNames[] = {
160 u"writer8"_ustr,
161 // "Rich Text Format", Note: picture is there, but SwGrfNode is not found?
162 u"MS Word 97"_ustr,
163 u"Office Open XML Text"_ustr,
166 for (OUString const & rFilterName : aFilterNames)
168 createSwDoc("document_with_linked_graphic.odt");
170 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
172 // Export the document and import again for a check
173 saveAndReload(rFilterName);
175 SwDoc* pDoc = getSwDoc();
176 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pDoc);
177 SwNodes& aNodes = pDoc->GetNodes();
179 // Find the image
180 bool bImageFound = false;
181 Graphic aGraphic;
182 for (SwNodeOffset nIndex(0); nIndex < aNodes.Count(); ++nIndex)
184 if (aNodes[nIndex]->IsGrfNode())
186 SwGrfNode* pGrfNode = aNodes[nIndex]->GetGrfNode();
187 CPPUNIT_ASSERT(pGrfNode);
189 const GraphicObject& rGraphicObj = pGrfNode->GetGrfObj(true);
190 aGraphic = rGraphicObj.GetGraphic();
191 bImageFound = true;
195 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bImageFound);
197 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
198 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_uLong(864900), aGraphic.GetSizeBytes());
200 // Check if linked graphic is registered in LinkManager
201 SwEditShell* const pEditShell(getSwDoc()->GetEditShell());
202 CPPUNIT_ASSERT(pEditShell);
203 sfx2::LinkManager& rLinkManager = pEditShell->GetLinkManager();
204 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(1), rLinkManager.GetLinks().size());
205 const tools::SvRef<sfx2::SvBaseLink> & rLink = rLinkManager.GetLinks()[0];
206 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), rLink->GetLinkSourceName().indexOf("linked_graphic.jpg") >= 0);
210 void Test::testImageWithSpecialID()
212 // Check how LO handles when the imported graphic's ID is different from that one
213 // which is generated by LO.
215 const OUString aFilterNames[] = {
216 u"writer8"_ustr,
217 u"Rich Text Format"_ustr,
218 u"MS Word 97"_ustr,
219 u"Office Open XML Text"_ustr,
222 for (OUString const & rFilterName : aFilterNames)
224 createSwDoc("images_with_special_IDs.odt");
226 // Export the document and import again for a check
227 saveAndReload(rFilterName);
229 // Check whether graphic exported well
230 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
231 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
233 uno::Reference<drawing::XShape> xImage = getShape(1);
234 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
236 // Check graphic, size
238 uno::Reference<graphic::XGraphic> xGraphic;
239 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
240 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
241 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
242 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
243 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
244 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
245 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
248 // Second Image
249 xImage.set(getShape(2), uno::UNO_QUERY);
250 XPropSet.set( xImage, uno::UNO_QUERY_THROW );
252 // Check graphic, size
254 uno::Reference<graphic::XGraphic> xGraphic;
255 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
256 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
257 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
258 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
259 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
260 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
261 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
266 /// Gives the first embedded or linked image in a document.
267 uno::Reference<drawing::XShape> lcl_getShape(const uno::Reference<lang::XComponent>& xComponent, bool bEmbedded)
269 uno::Reference<drawing::XShape> xShape;
271 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
272 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
273 for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
275 uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
276 uno::Reference<graphic::XGraphic> xGraphic;
277 xShapeProperties->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
278 if (xGraphic.is())
280 Graphic aGraphic(xGraphic);
282 if (bEmbedded == aGraphic.getOriginURL().isEmpty())
284 xShape.set(xShapeProperties, uno::UNO_QUERY);
285 return xShape;
290 return xShape;
293 void Test::testGraphicShape()
295 // There are two kind of images in Writer: 1) Writer specific handled by SwGrfNode and
296 // 2) graphic shape handled by SdrGrafObj (e.g. after copy&paste from Impress).
298 const OUString aFilterNames[] = {
299 u"writer8"_ustr,
300 u"Rich Text Format"_ustr,
301 u"MS Word 97"_ustr,
302 u"Office Open XML Text"_ustr,
305 for (OUString const & rFilterName : aFilterNames)
307 createSwDoc("graphic_shape.odt");
309 // Export the document and import again for a check
310 saveAndReload(rFilterName);
312 // Check whether graphic exported well
313 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
314 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
316 uno::Reference<drawing::XShape> xImage = lcl_getShape(mxComponent, true);
317 CPPUNIT_ASSERT_MESSAGE("Couldn't load the shape/image", xImage.is());
318 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY );
319 // First image is embedded
320 // Check size
322 uno::Reference<graphic::XGraphic> xGraphic;
323 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
324 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
325 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
326 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
327 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width );
328 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height );
331 // MS filters make this kind of linked images broken !?
332 if (rFilterName != "writer8")
333 return;
335 // Second image is a linked one
336 xImage = lcl_getShape(mxComponent, false);
337 XPropSet.set(xImage, uno::UNO_QUERY);
338 const OString sFailedImageLoad = OString::Concat("Couldn't load the shape/image for ") + rFilterName.toUtf8();
339 CPPUNIT_ASSERT_MESSAGE(sFailedImageLoad.getStr(), xImage.is());
341 // Check size
343 uno::Reference<graphic::XGraphic> xGraphic;
344 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
345 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
347 Graphic aGraphic(xGraphic);
348 OUString sURL = aGraphic.getOriginURL();
349 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), sURL.endsWith("linked_graphic.jpg"));
351 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
352 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
353 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(620), xBitmap->getSize().Width);
354 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(465), xBitmap->getSize().Height);
359 std::vector<uno::Reference<graphic::XGraphic>>
360 lcl_getGraphics(const uno::Reference<lang::XComponent>& xComponent)
362 std::vector<uno::Reference<graphic::XGraphic>> aGraphics;
364 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
365 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
366 for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
368 uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
369 uno::Reference<graphic::XGraphic> xGraphic;
370 xShapeProperties->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
371 if (xGraphic.is())
373 aGraphics.push_back(xGraphic);
377 return aGraphics;
380 void Test::testMultipleIdenticalGraphics()
382 // We have multiple identical graphics. When we save them we want
383 // them to be saved de-duplicated and the same should still be true
384 // after loading them again. This test check that the de-duplication
385 // works as expected.
387 const OUString aFilterNames[] {
388 u"writer8"_ustr,
389 //"Rich Text Format", // doesn't work correctly for now
390 u"MS Word 97"_ustr,
391 u"Office Open XML Text"_ustr,
394 for (OUString const & rFilterName : aFilterNames)
396 createSwDoc("multiple_identical_graphics.odt");
398 // Export the document and import again for a check
399 saveAndReload(rFilterName);
401 // Check whether graphic exported well
402 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
403 auto aGraphics = lcl_getGraphics(mxComponent);
405 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(5), aGraphics.size());
407 // Get all GfxLink addresses, we expect all of them to be the same
408 // indicating we use the same graphic instance for all shapes
409 std::vector<sal_Int64> aGfxLinkAddresses;
410 for (auto const & rxGraphic : aGraphics)
412 GfxLink* pLink = Graphic(rxGraphic).GetSharedGfxLink().get();
413 aGfxLinkAddresses.emplace_back(reinterpret_cast<sal_Int64>(pLink));
416 // Check all addresses are the same
417 bool bResult = std::equal(aGfxLinkAddresses.begin() + 1, aGfxLinkAddresses.end(), aGfxLinkAddresses.begin());
418 const OString sGraphicNotTheSameFailedMessage = OString::Concat("Graphics not the same for filter: '") +
419 rFilterName.toUtf8() + OString::Concat("'");
420 CPPUNIT_ASSERT_EQUAL_MESSAGE(sGraphicNotTheSameFailedMessage.getStr(), true, bResult);
424 void Test::testCharHighlightBody()
426 // MS Word has two kind of character backgrounds called character shading and highlighting
427 // MS filters handle these attributes separately, but ODF export merges them into one background attribute
429 const OUString aFilterNames[] = {
430 u"writer8"_ustr,
431 u"Rich Text Format"_ustr,
432 u"MS Word 97"_ustr,
433 u"Office Open XML Text"_ustr,
436 for (OUString const & rFilterName : aFilterNames)
438 createSwDoc("char_highlight.docx");
440 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
442 // Export the document and import again for a check
443 saveAndReload(rFilterName);
445 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
446 // Both highlight and background
447 const Color nBackColor(0x4F81BD);
448 for( int nRun = 1; nRun <= 16; ++nRun )
450 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
451 Color nHighlightColor;
452 switch( nRun )
454 case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
455 case 2: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
456 case 3: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
457 case 4: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
458 case 5: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
459 case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
460 case 7: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
461 case 8: nHighlightColor = COL_WHITE; break; //white 0xffffff
462 case 9: nHighlightColor = COL_BLUE; break;//blue 0x000080
463 case 10: nHighlightColor = COL_CYAN; break; //cyan 0x008080
464 case 11: nHighlightColor = COL_GREEN; break; //green 0x008000
465 case 12: nHighlightColor = COL_MAGENTA; break; //magenta 0x800080
466 case 13: nHighlightColor = COL_RED; break; //red 0x800000
467 case 14: nHighlightColor = COL_BROWN; break; //brown 0x808000
468 case 15: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
469 case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
472 if (rFilterName == "writer8")
474 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharHighlight"_ustr));
475 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,u"CharBackColor"_ustr));
477 else // MS filters
479 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,u"CharHighlight"_ustr));
480 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,u"CharBackColor"_ustr));
484 // Only highlight
486 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,18), uno::UNO_QUERY);
487 if (rFilterName == "writer8")
489 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharHighlight"_ustr));
490 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,u"CharBackColor"_ustr));
492 else
494 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,u"CharHighlight"_ustr));
495 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharBackColor"_ustr));
499 // Only background
501 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,19), uno::UNO_QUERY);
502 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharHighlight"_ustr));
503 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,u"CharBackColor"_ustr));
508 void Test::testCharStyleHighlight()
510 // MS Word has two kind of character backgrounds called character shading and highlighting.
511 // However, their character style can only accept shading. It ignores the highlighting value.
513 const OUString aFilterNames[] = {
514 u"Rich Text Format"_ustr,
515 u"MS Word 97"_ustr,
516 u"Office Open XML Text"_ustr,
519 for (OUString const & rFilterName : aFilterNames)
521 createSwDoc("tdf138345_charstyle_highlight.odt");
523 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
525 // Export the document and import again for a check
526 saveAndReload(rFilterName);
528 uno::Reference<beans::XPropertySet> xCharStyle;
529 getStyles(u"CharacterStyles"_ustr)->getByName(u"charBackground"_ustr) >>= xCharStyle;
530 const Color nBackColor(0xFFDBB6); //orange-y
532 // Always export character style's background colour as shading, never as highlighting.
533 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xCharStyle,u"CharHighlight"_ustr));
534 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xCharStyle,u"CharBackColor"_ustr));
538 void Test::testCharHighlight()
540 auto batch = comphelper::ConfigurationChanges::create();
541 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(false, batch);
542 batch->commit();
544 testCharHighlightBody();
545 testCharStyleHighlight();
547 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(true, batch);
548 batch->commit();
550 testCharHighlightBody();
551 testCharStyleHighlight();
554 void Test::testCharHighlightODF()
556 createSwDoc("char_background_editing.docx");
558 // don't check import, testMSCharBackgroundEditing already does that
560 uno::Reference<text::XTextRange> xPara = getParagraph(1);
561 for (int i = 1; i <= 4; ++i)
563 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
564 switch (i)
566 case 1: // non-transparent highlight
567 xRun->setPropertyValue(u"CharBackColor"_ustr, uno::Any(static_cast<sal_Int32>(128)));
568 xRun->setPropertyValue(u"CharBackTransparent"_ustr, uno::Any(true));
569 xRun->setPropertyValue(u"CharHighlight"_ustr, uno::Any(static_cast<sal_Int32>(64)));
570 break;
572 case 2: // transparent backcolor
573 xRun->setPropertyValue(u"CharBackColor"_ustr, uno::Any(static_cast<sal_Int32>(128)));
574 xRun->setPropertyValue(u"CharBackTransparent"_ustr, uno::Any(true));
575 xRun->setPropertyValue(u"CharHighlight"_ustr, uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
576 break;
578 case 3: // non-transparent backcolor
579 xRun->setPropertyValue(u"CharBackColor"_ustr, uno::Any(static_cast<sal_Int32>(128)));
580 xRun->setPropertyValue(u"CharBackTransparent"_ustr, uno::Any(false));
581 xRun->setPropertyValue(u"CharHighlight"_ustr, uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
582 break;
584 case 4: // non-transparent highlight again
585 xRun->setPropertyValue(u"CharBackColor"_ustr, uno::Any(static_cast<sal_Int32>(128)));
586 xRun->setPropertyValue(u"CharBackTransparent"_ustr, uno::Any(false));
587 xRun->setPropertyValue(u"CharHighlight"_ustr, uno::Any(static_cast<sal_Int32>(64)));
588 break;
592 saveAndReload(u"writer8"_ustr);
594 xPara.set(getParagraph(1));
595 for (int i = 1; i <= 4; ++i)
597 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
598 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(COL_TRANSPARENT), getProperty<sal_Int32>(xRun, u"CharHighlight"_ustr));
599 switch (i)
601 case 1: // non-transparent highlight
602 CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, u"CharBackColor"_ustr));
603 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, u"CharBackTransparent"_ustr));
604 break;
605 case 2: // transparent backcolor
606 CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, getProperty<Color>(xRun, u"CharBackColor"_ustr));
607 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xRun, u"CharBackTransparent"_ustr));
608 break;
609 case 3: // non-transparent backcolor
610 CPPUNIT_ASSERT_EQUAL(COL_BLUE, getProperty<Color>(xRun, u"CharBackColor"_ustr));
611 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, u"CharBackTransparent"_ustr));
612 break;
613 case 4: // non-transparent highlight again
614 CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, u"CharBackColor"_ustr));
615 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, u"CharBackTransparent"_ustr));
616 break;
621 void Test::testMSCharBackgroundEditing()
623 // Simulate the editing process of imported MSO character background attributes
624 // and check how export behaves.
626 const OUString aFilterNames[] = {
627 u"writer8"_ustr,
628 u"Rich Text Format"_ustr,
629 u"MS Word 97"_ustr,
630 u"Office Open XML Text"_ustr,
633 auto batch = comphelper::ConfigurationChanges::create();
634 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(true, batch);
635 batch->commit();
637 for (OUString const & rFilterName : aFilterNames)
639 createSwDoc("char_background_editing.docx");
641 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
643 // Check whether import was done on the right way
644 uno::Reference< text::XTextRange > xPara = getParagraph(1);
646 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,1), uno::UNO_QUERY);
647 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharHighlight"_ustr));
648 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,u"CharBackColor"_ustr));
650 xRun.set(getRun(xPara,2), uno::UNO_QUERY);
651 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,u"CharHighlight"_ustr));
652 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharBackColor"_ustr));
654 xRun.set(getRun(xPara,3), uno::UNO_QUERY);
655 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,u"CharHighlight"_ustr));
656 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,u"CharBackColor"_ustr));
658 xRun.set(getRun(xPara,4), uno::UNO_QUERY);
659 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharHighlight"_ustr));
660 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharBackColor"_ustr));
663 // Simulate editing
664 for( int i = 1; i <= 4; ++i )
666 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
667 // Change background
668 Color nBackColor;
669 switch( i )
671 case 1: nBackColor = COL_BLACK; break; //black 0x000000
672 case 2: nBackColor = COL_LIGHTCYAN; break; //cyan 0x00ffff
673 case 3: nBackColor = COL_LIGHTGREEN; break; //green 0x00ff00
674 case 4: nBackColor = COL_LIGHTMAGENTA; break; //magenta 0xff00ff
676 xRun->setPropertyValue(u"CharBackColor"_ustr, uno::Any(nBackColor));
677 // Remove highlighting
678 xRun->setPropertyValue(u"CharHighlight"_ustr, uno::Any(COL_TRANSPARENT));
679 // Remove shading marker
680 uno::Sequence<beans::PropertyValue> aGrabBag = getProperty<uno::Sequence<beans::PropertyValue> >(xRun,u"CharInteropGrabBag"_ustr);
681 for (beans::PropertyValue& rProp : asNonConstRange(aGrabBag))
683 if (rProp.Name == "CharShadingMarker")
685 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), true, rProp.Value.get<bool>());
686 rProp.Value <<= false;
689 xRun->setPropertyValue(u"CharInteropGrabBag"_ustr, uno::Any(aGrabBag));
692 // Export the document and import again for a check
693 saveAndReload(rFilterName);
695 // Check whether background was exported as highlighting
696 xPara.set(getParagraph(1));
697 for( int i = 1; i <= 4; ++i )
699 Color nBackColor;
700 switch( i )
702 case 1: nBackColor = COL_BLACK; break; //black 0x000000
703 case 2: nBackColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
704 case 3: nBackColor = COL_LIGHTGREEN; break; //light green 0x00ff00
705 case 4: nBackColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
707 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
708 if (rFilterName == "writer8")
710 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharHighlight"_ustr));
711 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,u"CharBackColor"_ustr));
713 else
715 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,u"CharHighlight"_ustr));
716 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,u"CharBackColor"_ustr));
722 void Test::testCharBackgroundToHighlighting()
724 // MSO highlighting has less kind of values so let's see how LO character background is converted
725 // to these values
727 const OUString aFilterNames[] = {
728 u"Rich Text Format"_ustr,
729 u"MS Word 97"_ustr,
730 u"Office Open XML Text"_ustr,
733 for (OUString const & rFilterName : aFilterNames)
735 createSwDoc("char_background.odt");
737 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
739 auto batch = comphelper::ConfigurationChanges::create();
740 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(true, batch);
741 batch->commit();
743 // Export the document and import again for a check
744 saveAndReload(rFilterName);
746 // Check highlight color
747 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
748 for( int nRun = 1; nRun <= 19; ++nRun )
750 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
751 Color nHighlightColor;
752 switch( nRun )
754 case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
755 case 2: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
756 case 3: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
757 case 4: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
758 case 5: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
759 case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
760 case 7: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
761 case 8: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
762 case 9: nHighlightColor = COL_GREEN; break; //dark green 0x008000
763 case 10: nHighlightColor = COL_MAGENTA; break; //dark magenta 0x800080
764 case 11: nHighlightColor = COL_BLUE; break; //dark blue 0x000080
765 case 12: nHighlightColor = COL_BROWN; break; //brown 0x808000
766 case 13: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
767 case 14: nHighlightColor = COL_BLACK; break; //black 0x000000
768 case 15: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
769 case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
770 case 17: nHighlightColor = COL_RED; break; //dark red 0x800000
771 case 18: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
772 case 19: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
774 const OString sMessage = sFailedMessage +". Index of run with unmatched color: " + OString::number(nRun);
775 CPPUNIT_ASSERT_EQUAL_MESSAGE(sMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,u"CharHighlight"_ustr));
780 #if !defined(_WIN32)
781 void Test::testSkipImages()
783 // Check how LO skips image loading (but not texts of textboxes and custom shapes)
784 // during DOC and DOCX import, using the "SkipImages" FilterOptions.
786 std::pair<OUString, OUString> aFilterNames[] = {
787 { "skipimages.doc", "" },
788 { "skipimages.doc", "SkipImages" },
789 { "skipimages.docx", "" },
790 { "skipimages.docx", "SkipImages" }
793 for (auto const & rFilterNamePair : aFilterNames)
795 bool bSkipImages = !rFilterNamePair.second.isEmpty();
796 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterNamePair.first.toUtf8();
798 setImportFilterOptions(rFilterNamePair.second);
799 createSwDoc(rFilterNamePair.first.toUtf8().getStr());
800 sFailedMessage += " - " + rFilterNamePair.second.toUtf8();
802 // Check shapes (images, textboxes, custom shapes)
803 uno::Reference<drawing::XShape> xShape;
804 uno::Reference<graphic::XGraphic> xGraphic;
805 uno::Reference< beans::XPropertySet > XPropSet;
806 uno::Reference<awt::XBitmap> xBitmap;
808 bool bHasTextboxText = false;
809 bool bHasCustomShapeText = false;
810 sal_Int32 nImageCount = 0;
812 for (int i = 1; i<= getShapes(); i++)
814 xShape = getShape(i);
815 XPropSet.set( xShape, uno::UNO_QUERY_THROW );
818 XPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
819 xBitmap.set(xGraphic, uno::UNO_QUERY);
820 if (xBitmap.is())
821 nImageCount++;
823 catch (beans::UnknownPropertyException &)
824 { /* ignore */ }
826 uno::Reference<text::XTextRange> xText(xShape, uno::UNO_QUERY);
827 if (xText.is())
829 OUString shapeText = xText->getString();
830 if (shapeText.startsWith("Lorem ipsum"))
831 bHasTextboxText = true;
832 else if (shapeText.startsWith("Nam pretium"))
833 bHasCustomShapeText = true;
837 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasTextboxText);
838 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasCustomShapeText);
839 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(bSkipImages ? 0 : 3), nImageCount );
842 #endif
844 void Test::testNestedFieldmark()
846 // experimental config setting
847 Resetter resetter(
848 [] () {
849 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
850 comphelper::ConfigurationChanges::create());
851 officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(false, pBatch);
852 return pBatch->commit();
854 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create());
855 officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(true, pBatch);
856 pBatch->commit();
858 auto verify = [this](OUString const& rTestName) {
859 SwDoc* pDoc = getSwDoc();
860 IDocumentMarkAccess const& rIDMA(*pDoc->getIDocumentMarkAccess());
862 // no spurious bookmarks have been created
863 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
864 sal_Int32(0), rIDMA.getBookmarksCount());
866 // check inner fieldmark
867 SwNodeIndex const node1(*pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), +2);
868 SwPosition const innerPos(*node1.GetNode().GetTextNode(),
869 node1.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
870 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
871 sal_Int32(1), innerPos.GetContentIndex());
872 ::sw::mark::Fieldmark *const pInner(rIDMA.getFieldmarkAt(innerPos));
873 CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pInner);
874 OUString const innerString(SwPaM(pInner->GetMarkPos(), pInner->GetOtherMarkPos()).GetText());
875 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
876 OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
877 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
878 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
879 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
880 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), innerString);
882 // check outer fieldmark
883 SwNodeIndex const node2(node1, -1);
884 SwPosition const outerPos(*node2.GetNode().GetTextNode(),
885 node2.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
886 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
887 sal_Int32(0), outerPos.GetContentIndex());
888 ::sw::mark::Fieldmark const*const pOuter(rIDMA.getFieldmarkAt(outerPos));
889 CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pOuter);
890 OUString const outerString(SwPaM(pOuter->GetMarkPos(), pOuter->GetOtherMarkPos()).GetText());
891 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
892 OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
893 + u" " + OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
894 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
895 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
896 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
897 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND) + OUStringChar(CH_TXTATR_NEWLINE)
898 + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
899 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
900 + u" foo " + OUStringChar(CH_TXTATR_NEWLINE)
901 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
902 + u"baz" + OUStringChar(CH_TXTATR_NEWLINE)
903 + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
904 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), outerString);
906 // must return innermost mark
907 CPPUNIT_ASSERT_EQUAL(pInner, rIDMA.getInnerFieldmarkFor(innerPos));
910 std::pair<OUString, OUString> const aFilterNames[] = {
911 {"writer8", "fieldmark_QUOTE_nest.fodt"},
912 {"Office Open XML Text", "fieldmark_QUOTE_nest.docx"},
913 {"Rich Text Format", "fieldmark_QUOTE_nest.rtf"},
916 for (auto const & rFilterName : aFilterNames)
918 createSwDoc(rFilterName.second.toUtf8().getStr());
920 verify(rFilterName.first + ", load");
922 // Export the document and import again
923 saveAndReload(rFilterName.first);
925 verify(rFilterName.first + " exported-reload");
929 auto Test::verifyText13(char const*const pTestName) -> void
931 // OFFICE-3789 style:header-first/style:footer-first
932 uno::Reference<beans::XPropertySet> xPageStyle;
933 getStyles(u"PageStyles"_ustr)->getByName(u"Standard"_ustr) >>= xPageStyle;
934 uno::Reference<text::XText> xHF(getProperty<uno::Reference<text::XText>>(xPageStyle, u"HeaderTextFirst"_ustr));
935 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"Header first"_ustr, xHF->getString());
936 uno::Reference<text::XText> xFF(getProperty<uno::Reference<text::XText>>(xPageStyle, u"FooterTextFirst"_ustr));
937 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"Footer first"_ustr, xFF->getString());
938 // OFFICE-3767 text:contextual-spacing
939 uno::Reference<text::XTextRange> xPara(getParagraph(1));
940 CPPUNIT_ASSERT_MESSAGE(pTestName, getProperty<bool>(xPara, u"ParaContextMargin"_ustr));
941 // OFFICE-3776 meta:creator-initials
942 uno::Reference<text::XTextRange> xRun(getRun(xPara, 1));
943 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"Annotation"_ustr, getProperty<OUString>(xRun, u"TextPortionType"_ustr));
944 uno::Reference<beans::XPropertySet> xComment(getProperty<uno::Reference<beans::XPropertySet>>(xRun, u"TextField"_ustr));
945 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"dj"_ustr, getProperty<OUString>(xComment, u"Initials"_ustr));
946 // OFFICE-3941 text:index-entry-link-start/text:index-entry-link-end
947 uno::Reference<text::XDocumentIndexesSupplier> xDIS(mxComponent, uno::UNO_QUERY);
948 uno::Reference<container::XIndexAccess> xIndexes(xDIS->getDocumentIndexes());
949 uno::Reference<text::XDocumentIndex> xIndex(xIndexes->getByIndex(0), uno::UNO_QUERY);
950 uno::Reference<container::XIndexReplace> xLevels(getProperty<uno::Reference<container::XIndexReplace>>(xIndex, u"LevelFormat"_ustr));
951 uno::Sequence<beans::PropertyValues> format;
952 xLevels->getByIndex(1) >>= format; // 1-based?
953 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"TokenType"_ustr, format[0][0].Name);
954 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"TokenHyperlinkStart"_ustr, format[0][0].Value.get<OUString>());
955 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"TokenType"_ustr, format[4][0].Name);
956 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, u"TokenHyperlinkEnd"_ustr, format[4][0].Value.get<OUString>());
959 // test ODF 1.3 new text document features
960 void Test::testODF13()
962 Resetter resetter([]() { SetODFDefaultVersion(SvtSaveOptions::ODFVER_LATEST); });
964 // import
965 createSwDoc("text13e.odt");
967 // check model
968 verifyText13("import");
971 // export ODF 1.3
972 SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_013);
974 saveAndReload(u"writer8"_ustr);
976 // check XML
977 xmlDocUniquePtr pContentXml = parseExport(u"content.xml"_ustr);
978 assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@style:contextual-spacing='true']");
979 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials");
980 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials", 0);
981 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");
982 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", 0);
983 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");
984 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", 0);
985 xmlDocUniquePtr pStylesXml = parseExport(u"styles.xml"_ustr);
986 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first");
987 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first", 0);
988 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first");
989 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first", 0);
991 // check model
992 verifyText13("1.3 reload");
995 // export ODF 1.2 extended
996 SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_012_EXTENDED);
998 // FIXME: it's not possible to use 'reload' here because the validation fails with
999 // Error: unexpected attribute "loext:contextual-spacing"
1000 utl::MediaDescriptor aMediaDescriptor;
1001 aMediaDescriptor[u"FilterName"_ustr] <<= u"writer8"_ustr;
1003 uno::Reference<frame::XStorable> const xStorable(mxComponent, uno::UNO_QUERY);
1004 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1006 // check XML
1007 xmlDocUniquePtr pContentXml = parseExport(u"content.xml"_ustr);
1008 assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@loext:contextual-spacing='true']");
1009 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials");
1010 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials", 0);
1011 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");
1012 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", 0);
1013 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");
1014 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", 0);
1015 xmlDocUniquePtr pStylesXml = parseExport(u"styles.xml"_ustr);
1016 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first");
1017 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first", 0);
1018 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first");
1019 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first", 0);
1021 // reload
1022 loadFromURL(maTempFile.GetURL());
1024 // check model
1025 verifyText13("1.2 Extended reload");
1028 // export ODF 1.2
1029 SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_012);
1031 // don't reload - no point
1032 save(u"writer8"_ustr);
1034 // check XML
1035 xmlDocUniquePtr pContentXml = parseExport(u"content.xml"_ustr);
1036 assertXPathNoAttribute(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties", "contextual-spacing");
1037 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials", 0);
1038 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials", 0);
1039 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", 0);
1040 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", 0);
1041 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", 0);
1042 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", 0);
1043 xmlDocUniquePtr pStylesXml = parseExport(u"styles.xml"_ustr);
1044 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first", 0);
1045 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first", 0);
1046 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first", 0);
1047 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first", 0);
1051 void Test::testRedlineFlags()
1053 const OUString aFilterNames[] = {
1054 u"writer8"_ustr,
1055 u"Rich Text Format"_ustr,
1056 u"MS Word 97"_ustr,
1057 u"Office Open XML Text"_ustr,
1060 createSwDoc();
1061 SwDoc* pDoc = getSwDoc();
1063 SwPaM pam(SwPosition(pDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1)));
1064 pDoc->getIDocumentContentOperations().InsertString(pam, u"foo bar baz"_ustr);
1066 IDocumentRedlineAccess & rIDRA(pDoc->getIDocumentRedlineAccess());
1067 // enable change tracking
1068 rIDRA.SetRedlineFlags(rIDRA.GetRedlineFlags()
1069 | RedlineFlags::On | RedlineFlags::ShowDelete);
1071 // need a delete redline to trigger mode switching
1072 pam.Move(fnMoveForward, GoInDoc);
1073 pam.SetMark();
1074 pam.Move(fnMoveBackward, GoInDoc);
1075 pDoc->getIDocumentContentOperations().DeleteAndJoin(pam);
1077 // hide delete redlines
1078 RedlineFlags const nRedlineFlags =
1079 rIDRA.GetRedlineFlags() & ~RedlineFlags::ShowDelete;
1080 rIDRA.SetRedlineFlags(nRedlineFlags);
1082 for (OUString const & rFilterName : aFilterNames)
1084 // export the document
1085 save(rFilterName);
1087 // tdf#97103 check that redline mode is properly restored
1088 CPPUNIT_ASSERT_EQUAL_MESSAGE(
1089 OString(OString::Concat("redline mode not restored in ") + rFilterName.toUtf8()).getStr(),
1090 static_cast<int>(nRedlineFlags), static_cast<int>(rIDRA.GetRedlineFlags()));
1094 void Test::testBulletAsImage()
1096 OUString aFilterNames[] = {
1097 u"writer8"_ustr,
1098 u"MS Word 97"_ustr,
1099 u"Office Open XML Text"_ustr,
1100 u"Rich Text Format"_ustr,
1103 for (OUString const & rFilterName : aFilterNames)
1105 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1107 createSwDoc("BulletAsImage.odt");
1109 // Check if import was successful
1111 uno::Reference<text::XTextRange> xPara(getParagraph(1));
1112 uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
1113 uno::Reference<container::XIndexAccess> xLevels;
1114 xLevels.set(xPropertySet->getPropertyValue(u"NumberingRules"_ustr), uno::UNO_QUERY);
1115 uno::Sequence<beans::PropertyValue> aProperties;
1116 xLevels->getByIndex(0) >>= aProperties;
1117 uno::Reference<awt::XBitmap> xBitmap;
1118 awt::Size aSize;
1119 sal_Int16 nNumberingType = -1;
1121 for (beans::PropertyValue const& rProperty : aProperties)
1123 if (rProperty.Name == "NumberingType")
1125 nNumberingType = rProperty.Value.get<sal_Int16>();
1127 else if (rProperty.Name == "GraphicBitmap")
1129 if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
1131 xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
1134 else if (rProperty.Name == "GraphicSize")
1136 aSize = rProperty.Value.get<awt::Size>();
1140 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
1142 // Graphic Bitmap
1143 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
1144 Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
1145 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
1146 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
1147 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
1148 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
1150 // Graphic Size
1151 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
1152 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
1155 // Export the document and import again for a check
1156 saveAndReload(rFilterName);
1159 uno::Reference<text::XTextRange> xPara(getParagraph(1));
1160 uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
1161 uno::Reference<container::XIndexAccess> xLevels;
1162 xLevels.set(xPropertySet->getPropertyValue(u"NumberingRules"_ustr), uno::UNO_QUERY);
1163 uno::Sequence<beans::PropertyValue> aProperties;
1164 xLevels->getByIndex(0) >>= aProperties;
1165 uno::Reference<awt::XBitmap> xBitmap;
1166 awt::Size aSize;
1167 sal_Int16 nNumberingType = -1;
1169 for (beans::PropertyValue const& rProperty : aProperties)
1171 if (rProperty.Name == "NumberingType")
1173 nNumberingType = rProperty.Value.get<sal_Int16>();
1175 else if (rProperty.Name == "GraphicBitmap")
1177 if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
1179 xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
1182 else if (rProperty.Name == "GraphicSize")
1184 aSize = rProperty.Value.get<awt::Size>();
1188 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
1190 // Graphic Bitmap
1191 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
1192 Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
1193 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
1194 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
1195 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
1196 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
1198 // Graphic Size
1199 if (rFilterName == "write8") // ODT is correct
1201 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
1202 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
1204 // FIXME: MS Filters don't work correctly for graphic bullet size
1205 else if (rFilterName == "Office Open XML Text" || rFilterName == "Rich Text Format")
1207 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Width);
1208 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Height);
1210 else if (rFilterName == "MS Word 97")
1212 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Width);
1213 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Height);
1219 CPPUNIT_TEST_FIXTURE(Test, testListLabelPDFExport)
1221 createSwDoc();
1223 uno::Reference<text::XTextDocument> xDoc(mxComponent, uno::UNO_QUERY_THROW);
1224 uno::Reference<text::XText> xText(xDoc->getText());
1225 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY_THROW);
1226 uno::Reference<container::XIndexReplace> xNumRule(
1227 xFactory->createInstance(u"com.sun.star.text.NumberingRules"_ustr),
1228 uno::UNO_QUERY_THROW);
1229 OUString listFormat;
1230 for (sal_Int32 i = 0; i < xNumRule->getCount(); ++i)
1232 uno::Sequence<beans::PropertyValue> format;
1233 format.getArray();
1234 xNumRule->getByIndex(i) >>= format;
1236 auto it(::std::find_if(format.begin(), format.end(),
1237 [](auto const& r) { return r.Name == "NumberingType"; }));
1238 // need something RTL
1239 const_cast<uno::Any&>(it->Value) <<= style::NumberingType::CHARS_ARABIC;
1242 #if 0
1243 // this doesn't work any more
1244 auto it(::std::find_if(format.begin(), format.end(),
1245 [](auto const& r) { return r.Name == "ParentNumbering"; }));
1246 const_cast<uno::Any&>(it->Value) <<= sal_Int16(i + 1);
1247 #endif
1248 listFormat += "%" + OUString::number(i+1) + "%.";
1249 auto it(::std::find_if(format.begin(), format.end(),
1250 [](auto const& r) { return r.Name == "ListFormat"; }));
1251 const_cast<uno::Any&>(it->Value) <<= listFormat;
1253 xNumRule->replaceByIndex(i, uno::Any(format));
1255 uno::Reference<beans::XPropertySet>(getParagraph(1), uno::UNO_QUERY_THROW)->setPropertyValue(u"NumberingRules"_ustr, uno::Any(xNumRule));
1256 xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
1257 uno::Reference<beans::XPropertySet>(getParagraph(2), uno::UNO_QUERY_THROW)->setPropertyValue(u"NumberingLevel"_ustr, uno::Any(sal_Int16(1)));
1258 xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
1259 uno::Reference<beans::XPropertySet>(getParagraph(3), uno::UNO_QUERY_THROW)->setPropertyValue(u"NumberingLevel"_ustr, uno::Any(sal_Int16(2)));
1261 // check PDF export of the list items (label in particular)
1262 utl::MediaDescriptor aMediaDescriptor;
1263 aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_pdf_Export"_ustr;
1264 // Enable PDF/UA
1265 uno::Sequence<beans::PropertyValue> aFilterData(
1266 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
1267 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
1268 css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
1269 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1271 // Parse the export result with pdfium.
1272 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1274 // Non-NULL pPdfDocument means pdfium is available.
1275 if (pPdfDocument != nullptr)
1277 // The document has one page.
1278 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1279 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1280 CPPUNIT_ASSERT(pPdfPage);
1282 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1283 CPPUNIT_ASSERT(pPdfTextPage);
1285 int nChars = pPdfTextPage->countChars();
1286 CPPUNIT_ASSERT_EQUAL(22, nChars);
1288 // Check that the label strings were exported correctly
1289 std::vector<sal_uInt32> aChars(nChars);
1290 for (int i = 0; i < nChars; i++)
1291 aChars[i] = pPdfTextPage->getUnicode(i);
1292 OUString aText(aChars.data(), aChars.size());
1293 CPPUNIT_ASSERT_EQUAL(u"\u0623\r\n.\r\n\u0623.\u0623\r\n.\r\n\u0623.\u0623.\u0623\r\n."_ustr, aText);
1296 // Parse the document again to get its raw content
1297 // TODO: get the content from PDFiumPage somehow
1298 vcl::filter::PDFDocument aDocument;
1299 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1300 CPPUNIT_ASSERT(aDocument.Read(aStream));
1302 // The document has one page.
1303 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1304 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1306 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
1307 CPPUNIT_ASSERT(pContents);
1308 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1309 CPPUNIT_ASSERT(pStream);
1310 SvMemoryStream& rObjectStream = pStream->GetMemory();
1311 // Uncompress it.
1312 SvMemoryStream aUncompressed;
1313 ZCodec aZCodec;
1314 aZCodec.BeginCompression();
1315 rObjectStream.Seek(0);
1316 aZCodec.Decompress(rObjectStream, aUncompressed);
1317 CPPUNIT_ASSERT(aZCodec.EndCompression());
1319 auto pStart = static_cast<const char*>(aUncompressed.GetData());
1320 const char* const pEnd = pStart + aUncompressed.GetSize();
1322 enum
1324 Default,
1325 Lbl,
1326 LblFoundText
1327 } state
1328 = Default;
1330 auto nLine(0);
1331 auto nLbl(0);
1332 auto nLblTj(0);
1333 auto nLblTJ(0);
1334 std::vector<int> mcids;
1335 while (true)
1337 ++nLine;
1338 auto const pLine = ::std::find(pStart, pEnd, '\n');
1339 if (pLine == pEnd)
1341 break;
1343 std::string_view const line(pStart, pLine - pStart);
1344 pStart = pLine + 1;
1345 if (!line.empty() && line[0] != '%')
1347 ::std::cerr << nLine << ": " << line << "\n";
1348 if (o3tl::starts_with(line, "/Lbl<</MCID") && o3tl::ends_with(line, ">>BDC"))
1350 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1351 mcids.push_back(o3tl::toInt32(line.substr(12)));
1352 state = Lbl;
1353 ++nLbl;
1355 else if (state == Lbl)
1357 auto const endj(line.find(">Tj"));
1358 if (endj != ::std::string_view::npos)
1360 state = LblFoundText;
1361 ++nLblTj;
1363 else
1365 auto const endJ(line.find("]TJ"));
1366 if (endJ != ::std::string_view::npos)
1368 state = LblFoundText;
1369 ++nLblTJ;
1373 else if (state != Default && line == "EMC")
1375 CPPUNIT_ASSERT_EQUAL_MESSAGE("missing text", LblFoundText, state);
1376 state = Default;
1380 CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
1381 // ideally there should be 3 but apparently every text portion gets its own
1382 // tag - this should not be a problem if these are grouped in the structure
1383 // tree into 3 Lbl.
1384 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLbl);
1385 // these are quite arbitrary?
1386 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLblTJ + nLblTj);
1388 auto nL(0);
1389 for (const auto& rDocElement : aDocument.GetElements())
1391 auto pObject0 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
1392 if (!pObject0)
1393 continue;
1394 auto pType0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("Type"_ostr));
1395 if (!pType0 || pType0->GetValue() != "StructElem")
1397 continue;
1399 auto pS0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("S"_ostr));
1400 if (!pS0 || pS0->GetValue() != "Document")
1402 continue;
1404 auto pKids0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject0->Lookup("K"_ostr));
1405 CPPUNIT_ASSERT(pKids0);
1407 for (const auto& pKid0 : pKids0->GetElements())
1409 auto pRefKid0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid0);
1410 CPPUNIT_ASSERT(pRefKid0);
1411 auto pObject1 = pRefKid0->LookupObject();
1412 CPPUNIT_ASSERT(pObject1);
1413 auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
1414 CPPUNIT_ASSERT(pType1);
1416 if (pType1 && pType1->GetValue() == "StructElem")
1418 auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
1419 if (pS1 && pS1->GetValue() == "L")
1421 ++nL;
1422 auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
1423 CPPUNIT_ASSERT(pKids1);
1424 // this is purely structural so there should be 1 child
1425 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1->GetElements().size());
1427 auto pRefKid11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1->GetElements()[0]);
1428 CPPUNIT_ASSERT(pRefKid11);
1429 auto pObject11 = pRefKid11->LookupObject();
1430 CPPUNIT_ASSERT(pObject11);
1431 auto pType11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("Type"_ostr));
1432 CPPUNIT_ASSERT(pType11);
1433 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11->GetValue());
1434 auto pS11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("S"_ostr));
1435 CPPUNIT_ASSERT(pS11);
1436 CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11->GetValue());
1437 // LI has 2 children: Lbl and LBody
1438 auto pKids11 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11->Lookup("K"_ostr));
1439 CPPUNIT_ASSERT(pKids11);
1440 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11->GetElements().size());
1442 auto pRefKid111 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[0]);
1443 CPPUNIT_ASSERT(pRefKid111);
1444 auto pObject111 = pRefKid111->LookupObject();
1445 CPPUNIT_ASSERT(pObject111);
1446 auto pType111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("Type"_ostr));
1447 CPPUNIT_ASSERT(pType111);
1448 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType111->GetValue());
1449 auto pS111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("S"_ostr));
1450 CPPUNIT_ASSERT(pS111);
1451 CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS111->GetValue());
1452 // Lbl has 2 children: the first 2 mcids (in order)
1453 auto pKids111 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject111->Lookup("K"_ostr));
1454 CPPUNIT_ASSERT(pKids111);
1455 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids111->GetElements().size());
1457 auto pRefKid1111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[0]);
1458 CPPUNIT_ASSERT(pRefKid1111);
1459 CPPUNIT_ASSERT_EQUAL(mcids[0], int(pRefKid1111->GetValue()));
1460 auto pRefKid1112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[1]);
1461 CPPUNIT_ASSERT(pRefKid1112);
1462 CPPUNIT_ASSERT_EQUAL(mcids[1], int(pRefKid1112->GetValue()));
1464 auto pRefKid112 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[1]);
1465 CPPUNIT_ASSERT(pRefKid112);
1466 auto pObject112 = pRefKid112->LookupObject();
1467 CPPUNIT_ASSERT(pObject112);
1468 auto pType112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("Type"_ostr));
1469 CPPUNIT_ASSERT(pType112);
1470 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112->GetValue());
1471 auto pS112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("S"_ostr));
1472 CPPUNIT_ASSERT(pS112);
1473 CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112->GetValue());
1474 // LBody has 2 children: paragraph and nested L (in order)
1475 auto pKids112 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112->Lookup("K"_ostr));
1476 CPPUNIT_ASSERT(pKids112);
1477 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112->GetElements().size());
1479 auto pRefKid1121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[0]);
1480 CPPUNIT_ASSERT(pRefKid1121);
1481 auto pObject1121 = pRefKid1121->LookupObject();
1482 CPPUNIT_ASSERT(pObject1121);
1483 auto pType1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("Type"_ostr));
1484 CPPUNIT_ASSERT(pType1121);
1485 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1121->GetValue());
1486 auto pS1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("S"_ostr));
1487 CPPUNIT_ASSERT(pS1121);
1488 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1121->GetValue());
1490 auto pRefKid1122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[1]);
1491 CPPUNIT_ASSERT(pRefKid1122);
1492 auto pObject1122 = pRefKid1122->LookupObject();
1493 CPPUNIT_ASSERT(pObject1122);
1494 auto pType1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("Type"_ostr));
1495 CPPUNIT_ASSERT(pType1122);
1496 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122->GetValue());
1497 auto pS1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("S"_ostr));
1498 CPPUNIT_ASSERT(pS1122);
1499 CPPUNIT_ASSERT_EQUAL("L"_ostr, pS1122->GetValue());
1500 auto pKids1122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122->Lookup("K"_ostr));
1501 CPPUNIT_ASSERT(pKids1122);
1502 // this is purely structural so there should be 1 child
1503 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122->GetElements().size());
1505 auto pRefKid11221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122->GetElements()[0]);
1506 CPPUNIT_ASSERT(pRefKid11221);
1507 auto pObject11221 = pRefKid11221->LookupObject();
1508 CPPUNIT_ASSERT(pObject11221);
1509 auto pType11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("Type"_ostr));
1510 CPPUNIT_ASSERT(pType11221);
1511 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11221->GetValue());
1512 auto pS11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("S"_ostr));
1513 CPPUNIT_ASSERT(pS11221);
1514 CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11221->GetValue());
1515 // LI has 2 children: Lbl and LBody
1516 auto pKids11221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221->Lookup("K"_ostr));
1517 CPPUNIT_ASSERT(pKids11221);
1518 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221->GetElements().size());
1520 auto pRefKid112211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[0]);
1521 CPPUNIT_ASSERT(pRefKid112211);
1522 auto pObject112211 = pRefKid112211->LookupObject();
1523 CPPUNIT_ASSERT(pObject112211);
1524 auto pType112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("Type"_ostr));
1525 CPPUNIT_ASSERT(pType112211);
1526 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112211->GetValue());
1527 auto pS112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("S"_ostr));
1528 CPPUNIT_ASSERT(pS112211);
1529 CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS112211->GetValue());
1530 // Lbl has 2 children: the first 2 mcids (in order)
1531 auto pKids112211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112211->Lookup("K"_ostr));
1532 CPPUNIT_ASSERT(pKids112211);
1533 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112211->GetElements().size());
1535 auto pRefKid1122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[0]);
1536 CPPUNIT_ASSERT(pRefKid1122111);
1537 CPPUNIT_ASSERT_EQUAL(mcids[2], int(pRefKid1122111->GetValue()));
1538 auto pRefKid1122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[1]);
1539 CPPUNIT_ASSERT(pRefKid1122112);
1540 CPPUNIT_ASSERT_EQUAL(mcids[3], int(pRefKid1122112->GetValue()));
1542 auto pRefKid112212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[1]);
1543 CPPUNIT_ASSERT(pRefKid112212);
1544 auto pObject112212 = pRefKid112212->LookupObject();
1545 CPPUNIT_ASSERT(pObject112212);
1546 auto pType112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("Type"_ostr));
1547 CPPUNIT_ASSERT(pType112212);
1548 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212->GetValue());
1549 auto pS112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("S"_ostr));
1550 CPPUNIT_ASSERT(pS112212);
1551 CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112212->GetValue());
1552 // LBody has 2 children: paragraph and nested L (in order)
1553 auto pKids112212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212->Lookup("K"_ostr));
1554 CPPUNIT_ASSERT(pKids112212);
1555 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212->GetElements().size());
1557 auto pRefKid1122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[0]);
1558 CPPUNIT_ASSERT(pRefKid1122121);
1559 auto pObject1122121 = pRefKid1122121->LookupObject();
1560 CPPUNIT_ASSERT(pObject1122121);
1561 auto pType1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("Type"_ostr));
1562 CPPUNIT_ASSERT(pType1122121);
1563 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122121->GetValue());
1564 auto pS1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("S"_ostr));
1565 CPPUNIT_ASSERT(pS1122121);
1566 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1122121->GetValue());
1568 auto pRefKid1122122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[1]);
1569 CPPUNIT_ASSERT(pRefKid1122122);
1570 auto pObject1122122 = pRefKid1122122->LookupObject();
1571 CPPUNIT_ASSERT(pObject1122122);
1572 auto pType1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("Type"_ostr));
1573 CPPUNIT_ASSERT(pType1122122);
1574 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122122->GetValue());
1575 auto pS1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("S"_ostr));
1576 CPPUNIT_ASSERT(pS1122122);
1577 CPPUNIT_ASSERT_EQUAL("L"_ostr, pS1122122->GetValue());
1578 auto pKids1122122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122122->Lookup("K"_ostr));
1579 CPPUNIT_ASSERT(pKids1122122);
1580 // this is purely structural so there should be 1 child
1581 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122122->GetElements().size());
1583 auto pRefKid11221221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122122->GetElements()[0]);
1584 CPPUNIT_ASSERT(pRefKid11221221);
1585 auto pObject11221221 = pRefKid11221221->LookupObject();
1586 CPPUNIT_ASSERT(pObject11221221);
1587 auto pType11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("Type"_ostr));
1588 CPPUNIT_ASSERT(pType11221221);
1589 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11221221->GetValue());
1590 auto pS11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("S"_ostr));
1591 CPPUNIT_ASSERT(pS11221221);
1592 CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11221221->GetValue());
1593 // LI has 2 children: Lbl and LBody
1594 auto pKids11221221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221221->Lookup("K"_ostr));
1595 CPPUNIT_ASSERT(pKids11221221);
1596 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221221->GetElements().size());
1598 auto pRefKid112212211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[0]);
1599 CPPUNIT_ASSERT(pRefKid112212211);
1600 auto pObject112212211 = pRefKid112212211->LookupObject();
1601 CPPUNIT_ASSERT(pObject112212211);
1602 auto pType112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("Type"_ostr));
1603 CPPUNIT_ASSERT(pType112212211);
1604 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212211->GetValue());
1605 auto pS112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("S"_ostr));
1606 CPPUNIT_ASSERT(pS112212211);
1607 CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS112212211->GetValue());
1608 // Lbl has 2 children: the first 2 mcids (in order)
1609 auto pKids112212211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212211->Lookup("K"_ostr));
1610 CPPUNIT_ASSERT(pKids112212211);
1611 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212211->GetElements().size());
1613 auto pRefKid1122122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[0]);
1614 CPPUNIT_ASSERT(pRefKid1122122111);
1615 CPPUNIT_ASSERT_EQUAL(mcids[4], int(pRefKid1122122111->GetValue()));
1616 auto pRefKid1122122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[1]);
1617 CPPUNIT_ASSERT(pRefKid1122122112);
1618 CPPUNIT_ASSERT_EQUAL(mcids[5], int(pRefKid1122122112->GetValue()));
1620 auto pRefKid112212212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[1]);
1621 CPPUNIT_ASSERT(pRefKid112212212);
1622 auto pObject112212212 = pRefKid112212212->LookupObject();
1623 CPPUNIT_ASSERT(pObject112212212);
1624 auto pType112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("Type"_ostr));
1625 CPPUNIT_ASSERT(pType112212212);
1626 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212212->GetValue());
1627 auto pS112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("S"_ostr));
1628 CPPUNIT_ASSERT(pS112212212);
1629 CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112212212->GetValue());
1630 // inner LBody has 1 children: paragraph
1631 auto pKids112212212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212212->Lookup("K"_ostr));
1632 CPPUNIT_ASSERT(pKids112212212);
1633 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids112212212->GetElements().size());
1635 auto pRefKid1122122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212212->GetElements()[0]);
1636 CPPUNIT_ASSERT(pRefKid1122122121);
1637 auto pObject1122122121 = pRefKid1122122121->LookupObject();
1638 CPPUNIT_ASSERT(pObject1122122121);
1639 auto pType1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("Type"_ostr));
1640 CPPUNIT_ASSERT(pType1122122121);
1641 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122122121->GetValue());
1642 auto pS1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("S"_ostr));
1643 CPPUNIT_ASSERT(pS1122122121);
1644 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1122122121->GetValue());
1649 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nL)>(1), nL);
1652 CPPUNIT_TEST_FIXTURE(Test, testTdf143311)
1654 createSwDoc("tdf143311-1.docx");
1655 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), u"Decorative"_ustr));
1657 // add another one that's a SdrObject
1658 uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
1659 uno::Reference<drawing::XShape> xShape(
1660 xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY);
1661 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
1662 xShapeProps->setPropertyValue(u"Decorative"_ustr, uno::Any(true));
1663 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
1664 uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPageSupplier->getDrawPage());
1665 xDrawPage->add(xShape);
1667 // check DOCX filters
1668 saveAndReload(u"Office Open XML Text"_ustr);
1669 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), u"Decorative"_ustr));
1670 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), u"Decorative"_ustr));
1672 // tdf#153925 not imported - check default and set it to test ODF filters
1673 uno::Reference<beans::XPropertySet> const xStyle(getStyles(u"FrameStyles"_ustr)->getByName(u"Formula"_ustr), uno::UNO_QUERY_THROW);
1674 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xStyle, u"Decorative"_ustr));
1675 xStyle->setPropertyValue(u"Decorative"_ustr, uno::Any(true));
1677 // check ODF filters
1678 saveAndReload(u"writer8"_ustr);
1679 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), u"Decorative"_ustr));
1680 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), u"Decorative"_ustr));
1681 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getStyles(u"FrameStyles"_ustr)->getByName(u"Formula"_ustr), u"Decorative"_ustr));
1683 // check PDF export
1684 utl::MediaDescriptor aMediaDescriptor;
1685 aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_pdf_Export"_ustr;
1686 // Enable PDF/UA
1687 uno::Sequence<beans::PropertyValue> aFilterData(
1688 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
1689 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
1690 css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
1691 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1693 vcl::filter::PDFDocument aDocument;
1694 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1695 CPPUNIT_ASSERT(aDocument.Read(aStream));
1697 // The document has one page.
1698 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1699 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1701 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
1702 CPPUNIT_ASSERT(pContents);
1703 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1704 CPPUNIT_ASSERT(pStream);
1705 SvMemoryStream& rObjectStream = pStream->GetMemory();
1706 // Uncompress it.
1707 SvMemoryStream aUncompressed;
1708 ZCodec aZCodec;
1709 aZCodec.BeginCompression();
1710 rObjectStream.Seek(0);
1711 aZCodec.Decompress(rObjectStream, aUncompressed);
1712 CPPUNIT_ASSERT(aZCodec.EndCompression());
1714 auto pStart = static_cast<const char*>(aUncompressed.GetData());
1715 const char* const pEnd = pStart + aUncompressed.GetSize();
1717 enum
1719 Default,
1720 Artifact,
1721 Tagged
1722 } state
1723 = Default;
1725 auto nLine(0);
1726 auto nTagged(0);
1727 auto nArtifacts(0);
1728 while (true)
1730 ++nLine;
1731 auto const pLine = ::std::find(pStart, pEnd, '\n');
1732 if (pLine == pEnd)
1734 break;
1736 std::string_view const line(pStart, pLine - pStart);
1737 pStart = pLine + 1;
1738 if (!line.empty() && line[0] != '%')
1740 ::std::cerr << nLine << ": " << line << "\n";
1741 if (line == "/Artifact BMC")
1743 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1744 state = Artifact;
1745 ++nArtifacts;
1747 else if (o3tl::starts_with(line, "/Standard<</MCID") && o3tl::ends_with(line, ">>BDC"))
1749 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1750 state = Tagged;
1751 ++nTagged;
1753 else if (line == "EMC")
1755 CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
1756 state = Default;
1758 else if (nLine > 1) // first line is expected "0.1 w"
1760 CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
1764 CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
1765 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(25), nTagged); // text in body
1766 // 1 decorative image + 1 decorative shape + 1 pre-existing rectangle border or something
1767 CPPUNIT_ASSERT(nArtifacts >= 3);
1770 void Test::testTextFormField()
1772 const OUString aFilterNames[] = {
1773 u"writer8"_ustr,
1774 u"MS Word 97"_ustr,
1775 u"Office Open XML Text"_ustr,
1778 for (const OUString& rFilterName : aFilterNames)
1780 createSwDoc("text_form_field.odt");
1782 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1784 // Export the document and import again for a check
1785 saveAndReload(rFilterName);
1787 // Check the document after round trip
1788 SwDoc* pDoc = getSwDoc();
1789 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1791 // We have two text form fields
1792 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1794 // Check whether all fieldmarks are text form fields
1795 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1797 ::sw::mark::Fieldmark* pFieldmark = dynamic_cast<::sw::mark::Fieldmark*>(*aIter);
1798 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1799 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMTEXT, pFieldmark->GetFieldname());
1802 // In the first paragraph we have an empty text form field with the placeholder spaces
1803 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
1804 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"TextFieldStart"_ustr, getProperty<OUString>(getRun(xPara, 1), u"TextPortionType"_ustr));
1805 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"TextFieldSeparator"_ustr, getProperty<OUString>(getRun(xPara, 2), u"TextPortionType"_ustr));
1806 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"Text"_ustr, getProperty<OUString>(getRun(xPara, 3), u"TextPortionType"_ustr));
1807 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), vEnSpaces, getRun(xPara, 3)->getString());
1808 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"TextFieldEnd"_ustr, getProperty<OUString>(getRun(xPara, 4), u"TextPortionType"_ustr));
1810 // In the second paragraph we have a set text
1811 const uno::Reference< text::XTextRange > xPara2 = getParagraph(2);
1812 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"TextFieldStart"_ustr, getProperty<OUString>(getRun(xPara2, 1), u"TextPortionType"_ustr));
1813 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"TextFieldSeparator"_ustr, getProperty<OUString>(getRun(xPara2, 2), u"TextPortionType"_ustr));
1814 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"Text"_ustr, getProperty<OUString>(getRun(xPara2, 3), u"TextPortionType"_ustr));
1815 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"xxxxx"_ustr, getRun(xPara2, 3)->getString());
1816 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"TextFieldEnd"_ustr, getProperty<OUString>(getRun(xPara2, 4), u"TextPortionType"_ustr));
1820 void Test::testCheckBoxFormField()
1822 const OUString aFilterNames[] = {
1823 u"writer8"_ustr,
1824 u"MS Word 97"_ustr,
1825 u"Office Open XML Text"_ustr,
1828 for (const OUString& rFilterName : aFilterNames)
1830 createSwDoc("checkbox_form_field.odt");
1832 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1834 // Export the document and import again for a check
1835 saveAndReload(rFilterName);
1837 // Check the document after round trip
1838 SwDoc* pDoc = getSwDoc();
1839 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1841 // We have two check box form fields
1842 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1844 int nIndex = 0;
1845 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1847 ::sw::mark::Fieldmark* pFieldmark = dynamic_cast<::sw::mark::Fieldmark*>(*aIter);
1849 if(rFilterName == "Office Open XML Text") // OOXML import also generates bookmarks
1851 if(!pFieldmark)
1852 continue;
1855 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1856 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMCHECKBOX, pFieldmark->GetFieldname());
1857 ::sw::mark::CheckboxFieldmark* pCheckBox = dynamic_cast< ::sw::mark::CheckboxFieldmark* >(pFieldmark);
1858 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox);
1860 // The first one is unchecked, the other one is checked
1861 if(nIndex == 0)
1862 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !pCheckBox->IsChecked());
1863 else
1864 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox->IsChecked());
1865 ++nIndex;
1867 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
1871 void Test::testDropDownFormField()
1873 const OUString aFilterNames[] = {
1874 u"writer8"_ustr,
1875 u"MS Word 97"_ustr,
1876 u"Office Open XML Text"_ustr,
1879 for (const OUString& rFilterName : aFilterNames)
1881 createSwDoc("dropdown_form_field.odt");
1883 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1885 // Export the document and import again for a check
1886 saveAndReload(rFilterName);
1888 // Check the document after round trip
1889 SwDoc* pDoc = getSwDoc();
1890 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1892 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1894 int nIndex = 0;
1895 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1897 ::sw::mark::Fieldmark* pFieldmark = dynamic_cast<::sw::mark::Fieldmark*>(*aIter);
1899 if(!pFieldmark)
1900 continue;
1902 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1903 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDROPDOWN, pFieldmark->GetFieldname());
1905 // Check drop down field's parameters.
1906 const sw::mark::Fieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
1907 css::uno::Sequence<OUString> vListEntries;
1908 sal_Int32 nSelection = -1;
1909 auto pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
1910 if (pListEntries != pParameters->end())
1912 pListEntries->second >>= vListEntries;
1914 if(vListEntries.hasElements())
1916 auto pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
1917 if (pResult != pParameters->end())
1919 pResult->second >>= nSelection;
1924 // The first one is empty
1925 if(nIndex == 0)
1927 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !vListEntries.hasElements());
1928 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(-1), nSelection);
1930 else // The second one has list and also a selected item
1932 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(4), vListEntries.getLength());
1933 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), nSelection);
1934 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"1000"_ustr, vListEntries[0]);
1935 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"2000"_ustr, vListEntries[1]);
1936 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"3000"_ustr, vListEntries[2]);
1937 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"4000"_ustr, vListEntries[3]);
1939 ++nIndex;
1941 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
1945 void Test::testDateFormField()
1947 const OUString aFilterNames[] = {
1948 u"writer8"_ustr,
1949 u"Office Open XML Text"_ustr,
1952 for (const OUString& rFilterName : aFilterNames)
1954 createSwDoc("date_form_field.odt");
1956 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1958 // Export the document and import again for a check
1959 saveAndReload(rFilterName);
1961 // Check the document after round trip
1962 if (rFilterName == "writer8")
1964 SwDoc* pDoc = getSwDoc();
1965 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1967 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pMarkAccess->getAllMarksCount());
1969 int nIndex = 0;
1970 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1972 ::sw::mark::DateFieldmark* pFieldmark = dynamic_cast<::sw::mark::DateFieldmark*>(*aIter);
1973 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1974 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDATE, pFieldmark->GetFieldname());
1976 // Check date form field's parameters.
1977 const sw::mark::Fieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
1978 OUString sDateFormat;
1979 auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
1980 if (pResult != pParameters->end())
1982 pResult->second >>= sDateFormat;
1985 OUString sLang;
1986 pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
1987 if (pResult != pParameters->end())
1989 pResult->second >>= sLang;
1992 OUString sCurrentDate = pFieldmark->GetContent();
1994 // The first one has the default field content
1995 if(nIndex == 0)
1998 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"MM/DD/YY"_ustr, sDateFormat);
1999 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"en-US"_ustr, sLang);
2000 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), vEnSpaces, sCurrentDate);
2002 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2003 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pFieldmark->GetMarkStart().GetContentIndex());
2005 else if (nIndex == 1) // The second has the default format
2007 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"MM/DD/YY"_ustr, sDateFormat);
2008 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"en-US"_ustr, sLang);
2009 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"06/12/19"_ustr, sCurrentDate);
2011 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2012 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(20), pFieldmark->GetMarkStart().GetContentIndex());
2014 else if (nIndex == 2) // The third one has special format
2016 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."_ustr, sDateFormat);
2017 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"hu-HU"_ustr, sLang);
2018 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"2019. febr. 12."_ustr, sCurrentDate);
2020 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2021 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(40), pFieldmark->GetMarkStart().GetContentIndex());
2024 else if (nIndex == 3) // The fourth one has placeholder text
2026 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"D, MMM YY"_ustr, sDateFormat);
2027 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"bm-ML"_ustr, sLang);
2028 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"[select date]"_ustr, sCurrentDate);
2030 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2031 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(62), pFieldmark->GetMarkStart().GetContentIndex());
2034 else // The last one is really empty
2036 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"MM/DD/YY"_ustr, sDateFormat);
2037 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"en-US"_ustr, sLang);
2038 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u""_ustr, sCurrentDate);
2040 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2041 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(82), pFieldmark->GetMarkStart().GetContentIndex());
2044 ++nIndex;
2046 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
2048 else
2050 // Import from DOCX, so the fieldmark is now a content control.
2051 uno::Reference<container::XEnumerationAccess> xEnumAccess(getParagraph(1), uno::UNO_QUERY);
2052 uno::Reference<container::XEnumeration> xTextPortions = xEnumAccess->createEnumeration();
2054 int nIndex = 0;
2055 while (xTextPortions->hasMoreElements())
2057 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
2058 OUString aPortionType;
2059 xTextPortion->getPropertyValue(u"TextPortionType"_ustr) >>= aPortionType;
2060 if (aPortionType != "ContentControl")
2062 continue;
2065 uno::Reference<text::XTextContent> xContentControl;
2066 xTextPortion->getPropertyValue(u"ContentControl"_ustr) >>= xContentControl;
2067 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
2069 bool bDate{};
2070 xContentControlProps->getPropertyValue(u"Date"_ustr) >>= bDate;
2071 CPPUNIT_ASSERT(bDate);
2073 // Check date form field's parameters.
2074 OUString sDateFormat;
2075 xContentControlProps->getPropertyValue(u"DateFormat"_ustr) >>= sDateFormat;
2077 OUString sLang;
2078 xContentControlProps->getPropertyValue(u"DateLanguage"_ustr) >>= sLang;
2080 uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
2081 uno::UNO_QUERY);
2082 uno::Reference<container::XEnumeration> xContentControlEnum
2083 = xContentControlEnumAccess->createEnumeration();
2084 uno::Reference<text::XTextRange> xContentControlTextPortion(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2085 OUString sCurrentDate = xContentControlTextPortion->getString();
2087 // The first one has the default field content
2088 if(nIndex == 0)
2090 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"MM/DD/YY"_ustr, sDateFormat);
2091 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"en-US"_ustr, sLang);
2092 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), vEnSpaces, sCurrentDate);
2094 else if (nIndex == 1) // The second has the default format
2096 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"MM/DD/YY"_ustr, sDateFormat);
2097 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"en-US"_ustr, sLang);
2098 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"06/12/19"_ustr, sCurrentDate);
2100 else if (nIndex == 2) // The third one has special format
2102 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."_ustr, sDateFormat);
2103 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"hu-HU"_ustr, sLang);
2104 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"2019. febr. 12."_ustr, sCurrentDate);
2106 else if (nIndex == 3) // The fourth one has placeholder text
2108 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"D, MMM YY"_ustr, sDateFormat);
2109 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"bm-ML"_ustr, sLang);
2110 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"[select date]"_ustr, sCurrentDate);
2112 else // The last one is really empty
2114 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"MM/DD/YY"_ustr, sDateFormat);
2115 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u"en-US"_ustr, sLang);
2116 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), u""_ustr, sCurrentDate);
2118 ++nIndex;
2120 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
2125 void Test::testDateFormFieldCharacterFormatting()
2127 const OUString aFilterNames[] = {
2128 u"writer8"_ustr,
2129 u"Office Open XML Text"_ustr,
2132 for (const OUString& rFilterName : aFilterNames)
2134 createSwDoc("date_form_field_char_formatting.odt");
2136 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
2138 // Export the document and import again for a check
2139 saveAndReload(rFilterName);
2141 // Check the document after round trip
2142 if (rFilterName == "writer8")
2144 SwDoc* pDoc = getSwDoc();
2145 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
2147 // Check that we have the field at the right place
2148 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), pMarkAccess->getAllMarksCount());
2149 ::sw::mark::DateFieldmark* pFieldmark = dynamic_cast<::sw::mark::DateFieldmark*>(*pMarkAccess->getAllMarksBegin());
2150 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
2151 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDATE, pFieldmark->GetFieldname());
2152 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(0), pFieldmark->GetMarkStart().GetContentIndex());
2153 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(11), pFieldmark->GetMarkEnd().GetContentIndex());
2155 // We have one date field, first half of the field has bold character weight and second part has red character color
2156 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(getRun(getParagraph(1), 3), u"CharWeight"_ustr));
2157 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(getRun(getParagraph(1), 3), u"CharColor"_ustr));
2158 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(getRun(getParagraph(1), 4), u"CharWeight"_ustr));
2159 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(getRun(getParagraph(1), 4), u"CharColor"_ustr));
2161 else
2163 uno::Reference<beans::XPropertySet> xTextPortion(getRun(getParagraph(1), 1), uno::UNO_QUERY);
2164 OUString aPortionType;
2165 xTextPortion->getPropertyValue(u"TextPortionType"_ustr) >>= aPortionType;
2166 CPPUNIT_ASSERT_EQUAL(u"ContentControl"_ustr, aPortionType);
2168 uno::Reference<text::XTextContent> xContentControl;
2169 xTextPortion->getPropertyValue(u"ContentControl"_ustr) >>= xContentControl;
2170 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
2171 bool bDate{};
2172 xContentControlProps->getPropertyValue(u"Date"_ustr) >>= bDate;
2173 CPPUNIT_ASSERT(bDate);
2175 uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
2176 uno::UNO_QUERY);
2177 uno::Reference<container::XEnumeration> xContentControlEnum
2178 = xContentControlEnumAccess->createEnumeration();
2179 xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2180 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(xTextPortion, u"CharWeight"_ustr));
2181 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(xTextPortion, u"CharColor"_ustr));
2182 xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2183 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(xTextPortion, u"CharWeight"_ustr));
2184 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xTextPortion, u"CharColor"_ustr));
2189 void Test::testSvgImageSupport()
2191 OUString aFilterNames[] = {
2192 u"writer8"_ustr,
2193 u"Office Open XML Text"_ustr,
2196 for (OUString const & rFilterName : aFilterNames)
2198 // Use case to import a document containing a SVG image, export in target format, import and check if the
2199 // SVG image is present and as expected in the document
2201 // Import ODT file
2202 createSwDoc("SvgImageTest.odt");
2204 // Export the document in target format and import again
2205 saveAndReload(rFilterName);
2207 // Prepare fail message (writing which import/export filter was used)
2208 const OString sFailedMessage = "Failed on filter: "_ostr + rFilterName.toUtf8();
2210 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 1, getShapes());
2212 // Get the image
2213 uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
2214 uno::Reference<beans::XPropertySet> xPropertySet(xImage, uno::UNO_QUERY_THROW);
2216 // Convert to a XGraphic
2217 uno::Reference<graphic::XGraphic> xGraphic;
2218 xPropertySet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
2219 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
2221 // Access the Graphic
2222 Graphic aGraphic(xGraphic);
2224 // Check if it contains a VectorGraphicData struct
2225 auto pVectorGraphic = aGraphic.getVectorGraphicData();
2226 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pVectorGraphic);
2228 // Which should be of type SVG, which means we have a SVG file
2229 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), VectorGraphicDataType::Svg, pVectorGraphic->getType());
2233 } // end of anonymous namespace
2234 CPPUNIT_TEST_SUITE_REGISTRATION(Test);
2236 CPPUNIT_PLUGIN_IMPLEMENT();
2238 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */