1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <sal/config.h>
11 #include <config_oox.h>
13 #include <test/unoapi_test.hxx>
15 #include <com/sun/star/frame/Desktop.hpp>
16 #include <com/sun/star/frame/XStorable.hpp>
17 #include <com/sun/star/lang/XComponent.hpp>
18 #include <com/sun/star/sheet/XSpreadsheet.hpp>
19 #include <com/sun/star/table/XCellRange.hpp>
20 #include <com/sun/star/view/XSelectionSupplier.hpp>
21 #include <comphelper/propertysequence.hxx>
22 #include <unotools/tempfile.hxx>
24 #include <editutil.hxx>
25 #include <editeng/eeitem.hxx>
26 #include <editeng/fontitem.hxx>
27 #include <osl/file.hxx>
28 #include <comphelper/processfactory.hxx>
29 #include <comphelper/propertyvalue.hxx>
31 #include <vcl/filter/PDFiumLibrary.hxx>
37 using namespace css::lang
;
38 using namespace ::com::sun::star
;
39 using namespace ::com::sun::star::uno
;
41 class ScPDFExportTest
: public UnoApiTest
49 void exportToPDF(const uno::Reference
<frame::XModel
>& xModel
, const ScRange
& range
);
51 void exportToPDFWithUnoCommands(const OUString
& rRange
);
53 bool hasTextInPdf(const char* sText
, bool& bFound
);
55 void setFont(ScFieldEditEngine
& rEE
, sal_Int32 nStart
, sal_Int32 nEnd
,
56 const OUString
& rFontName
);
60 void testExportRange_Tdf120161();
61 void testExportFitToPage_Tdf103516();
62 void testUnoCommands_Tdf120161();
63 void testTdf64703_hiddenPageBreak();
67 void testForcepoint97();
69 CPPUNIT_TEST_SUITE(ScPDFExportTest
);
70 CPPUNIT_TEST(testExportRange_Tdf120161
);
71 CPPUNIT_TEST(testExportFitToPage_Tdf103516
);
72 CPPUNIT_TEST(testUnoCommands_Tdf120161
);
73 CPPUNIT_TEST(testTdf64703_hiddenPageBreak
);
74 CPPUNIT_TEST(testTdf143978
);
75 CPPUNIT_TEST(testTdf84012
);
76 CPPUNIT_TEST(testTdf78897
);
77 CPPUNIT_TEST(testForcepoint97
);
78 CPPUNIT_TEST_SUITE_END();
81 ScPDFExportTest::ScPDFExportTest()
82 : UnoApiTest("sc/qa/extras/testdocuments/")
86 ScPDFExportTest::~ScPDFExportTest()
93 bool ScPDFExportTest::hasTextInPdf(const char* sText
, bool& bFound
)
95 SvStream
* pStream
= maTempFile
.GetStream(StreamMode::STD_READ
);
96 CPPUNIT_ASSERT(pStream
);
99 const std::size_t nFileSize
= pStream
->TellEnd();
104 char* pBuffer
= new char[nFileSize
];
105 pStream
->Seek(STREAM_SEEK_TO_BEGIN
);
106 const std::size_t nRead
= pStream
->ReadBytes(pBuffer
, nFileSize
);
107 if (nRead
== nFileSize
)
109 const std::string
haystack(pBuffer
, pBuffer
+ nFileSize
);
110 const std::string
needle(sText
);
111 const std::size_t n
= haystack
.find(needle
);
112 bFound
= (n
!= std::string::npos
);
116 // close and return the status
118 maTempFile
.CloseStream();
119 return (nRead
== nFileSize
);
122 void ScPDFExportTest::exportToPDF(const uno::Reference
<frame::XModel
>& xModel
, const ScRange
& range
)
125 uno::Reference
<sheet::XSpreadsheetDocument
> xDoc(xModel
, uno::UNO_QUERY_THROW
);
126 uno::Reference
<sheet::XSpreadsheets
> xSheets(xDoc
->getSheets(), UNO_SET_THROW
);
127 uno::Reference
<container::XIndexAccess
> xIndex(xSheets
, uno::UNO_QUERY_THROW
);
128 uno::Reference
<sheet::XSpreadsheet
> rSheet(xIndex
->getByIndex(0), UNO_QUERY_THROW
);
130 // select requested cells to print
131 // query for the XCellRange interface
132 uno::Reference
<table::XCellRange
> xCellRange
= rSheet
->getCellRangeByPosition(
133 range
.aStart
.Col(), range
.aStart
.Row(), range
.aEnd
.Col(), range
.aEnd
.Row());
135 uno::Reference
<frame::XController
> xController
= xModel
->getCurrentController();
136 CPPUNIT_ASSERT(xController
.is());
138 uno::Reference
<view::XSelectionSupplier
> xSelection(xController
, uno::UNO_QUERY_THROW
);
139 CPPUNIT_ASSERT(xSelection
.is());
141 uno::Any
rCellRangeAny(xCellRange
);
142 xSelection
->select(rCellRangeAny
);
145 // init special pdf export params
146 css::uno::Sequence
<css::beans::PropertyValue
> aFilterData
{
147 comphelper::makePropertyValue("Selection", xCellRange
),
148 comphelper::makePropertyValue("Printing", sal_Int32(2)),
149 comphelper::makePropertyValue("ViewPDFAfterExport", true)
152 // init set of params for storeToURL() call
153 css::uno::Sequence
<css::beans::PropertyValue
> seqArguments
{
154 comphelper::makePropertyValue("FilterData", aFilterData
),
155 comphelper::makePropertyValue("FilterName", OUString("calc_pdf_Export")),
156 comphelper::makePropertyValue("URL", maTempFile
.GetURL())
160 uno::Reference
<lang::XComponent
> xComponent(mxComponent
, UNO_SET_THROW
);
161 uno::Reference
<css::frame::XStorable
> xStorable(xComponent
, UNO_QUERY
);
162 xStorable
->storeToURL(maTempFile
.GetURL(), seqArguments
);
165 void ScPDFExportTest::exportToPDFWithUnoCommands(const OUString
& rRange
)
167 uno::Sequence
<beans::PropertyValue
> aArgs
168 = comphelper::InitPropertySequence({ { "ToPoint", uno::Any(rRange
) } });
169 dispatchCommand(mxComponent
, ".uno:GoToCell", aArgs
);
171 dispatchCommand(mxComponent
, ".uno:DefinePrintArea", {});
173 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
174 { { "ViewPDFAfterExport", uno::Any(true) }, { "Printing", uno::Any(sal_Int32(2)) } }));
176 uno::Sequence
<beans::PropertyValue
> aDescriptor(
177 comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("calc_pdf_Export")) },
178 { "FilterData", uno::Any(aFilterData
) },
179 { "URL", uno::Any(maTempFile
.GetURL()) } }));
181 dispatchCommand(mxComponent
, ".uno:ExportToPDF", aDescriptor
);
184 void ScPDFExportTest::setFont(ScFieldEditEngine
& rEE
, sal_Int32 nStart
, sal_Int32 nEnd
,
185 const OUString
& rFontName
)
188 aSel
.nStartPara
= aSel
.nEndPara
= 0;
189 aSel
.nStartPos
= nStart
;
192 SfxItemSet aItemSet
= rEE
.GetEmptyItemSet();
193 SvxFontItem
aItem(FAMILY_MODERN
, rFontName
, "", PITCH_VARIABLE
, RTL_TEXTENCODING_UTF8
,
196 rEE
.QuickSetAttribs(aItemSet
, aSel
);
199 // Selection was not taken into account during export into PDF
200 void ScPDFExportTest::testExportRange_Tdf120161()
202 // create test document
203 mxComponent
= loadFromDesktop("private:factory/scalc");
204 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
205 uno::Reference
<sheet::XSpreadsheetDocument
> xDoc(xModel
, uno::UNO_QUERY_THROW
);
206 uno::Reference
<sheet::XSpreadsheets
> xSheets(xDoc
->getSheets(), UNO_SET_THROW
);
207 uno::Reference
<container::XIndexAccess
> xIndex(xSheets
, uno::UNO_QUERY_THROW
);
208 xSheets
->insertNewByName("First Sheet", 0);
209 uno::Reference
<sheet::XSpreadsheet
> rSheet(xIndex
->getByIndex(0), UNO_QUERY_THROW
);
213 SfxObjectShell
* pFoundShell
= SfxObjectShell::GetShellFromComponent(mxComponent
);
214 CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell
);
215 ScDocShellRef xDocSh
= dynamic_cast<ScDocShell
*>(pFoundShell
);
216 CPPUNIT_ASSERT(xDocSh
);
218 // put some content into the first row with default font
219 ScDocument
& rDoc
= xDocSh
->GetDocument();
220 for (unsigned int r
= 0; r
< 1; ++r
)
221 for (unsigned int c
= 0; c
< 14; ++c
)
222 rDoc
.SetValue(ScAddress(c
, r
, 0), (r
+ 1) * (c
+ 1));
224 // set "Text" to H1 cell with "DejaVuSans" font
225 ScFieldEditEngine
& rEE
= rDoc
.GetEditEngine();
227 rEE
.SetTextCurrentDefaults("Text");
228 setFont(rEE
, 0, 4, "DejaVuSans"); // set font for first 4 chars
229 rDoc
.SetEditText(ScAddress(7, 0, 0), rEE
.CreateTextObject());
234 ScRange
range1(0, 0, 0, 6, 0, 0);
235 exportToPDF(xModel
, range1
);
237 CPPUNIT_ASSERT(hasTextInPdf("DejaVuSans", bFound
));
238 CPPUNIT_ASSERT_EQUAL(false, bFound
);
243 ScRange
range1(6, 0, 0, 7, 0, 0);
244 exportToPDF(xModel
, range1
);
246 CPPUNIT_ASSERT(hasTextInPdf("DejaVuSans", bFound
));
247 CPPUNIT_ASSERT_EQUAL(true, bFound
);
252 ScRange
range1(7, 0, 0, 8, 0, 0);
253 exportToPDF(xModel
, range1
);
255 CPPUNIT_ASSERT(hasTextInPdf("DejaVuSans", bFound
));
256 CPPUNIT_ASSERT_EQUAL(true, bFound
);
260 void ScPDFExportTest::testExportFitToPage_Tdf103516()
262 // create test document
263 mxComponent
= loadFromDesktop("private:factory/scalc");
264 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
265 uno::Reference
<sheet::XSpreadsheetDocument
> xDoc(xModel
, uno::UNO_QUERY_THROW
);
266 uno::Reference
<sheet::XSpreadsheets
> xSheets(xDoc
->getSheets(), UNO_SET_THROW
);
267 uno::Reference
<container::XIndexAccess
> xIndex(xSheets
, uno::UNO_QUERY_THROW
);
268 xSheets
->insertNewByName("First Sheet", 0);
269 uno::Reference
<sheet::XSpreadsheet
> rSheet(xIndex
->getByIndex(0), UNO_QUERY_THROW
);
273 SfxObjectShell
* pFoundShell
= SfxObjectShell::GetShellFromComponent(mxComponent
);
274 CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell
);
275 ScDocShellRef xDocSh
= dynamic_cast<ScDocShell
*>(pFoundShell
);
276 CPPUNIT_ASSERT(xDocSh
);
278 // put some content into the table
279 ScDocument
& rDoc
= xDocSh
->GetDocument();
280 for (unsigned int r
= 0; r
< 80; ++r
)
281 for (unsigned int c
= 0; c
< 12; ++c
)
282 rDoc
.SetValue(ScAddress(c
, r
, 0), (r
+ 1) * (c
+ 1));
285 // A1:G50: 2-page export
287 ScRange
range1(0, 0, 0, 6, 49, 0);
288 exportToPDF(xModel
, range1
);
290 CPPUNIT_ASSERT(hasTextInPdf("/Count 2>>", bFound
));
291 CPPUNIT_ASSERT_EQUAL(true, bFound
);
294 // A1:L80: 4-page export
296 ScRange
range1(0, 0, 0, 11, 79, 0);
297 exportToPDF(xModel
, range1
);
299 CPPUNIT_ASSERT(hasTextInPdf("/Count 4>>", bFound
));
300 CPPUNIT_ASSERT_EQUAL(true, bFound
);
303 // set fit to page: width=1 page, height=0 (automatic)
304 uno::Reference
<style::XStyleFamiliesSupplier
> xStyleFamSupp(xDoc
, UNO_QUERY_THROW
);
305 uno::Reference
<container::XNameAccess
> xStyleFamiliesNames(xStyleFamSupp
->getStyleFamilies(),
307 uno::Reference
<container::XNameAccess
> xPageStyles(xStyleFamiliesNames
->getByName("PageStyles"),
309 uno::Any aDefaultStyle
= xPageStyles
->getByName("Default");
310 uno::Reference
<beans::XPropertySet
> xProp(aDefaultStyle
, UNO_QUERY_THROW
);
312 uno::Any aScaleX
, aScaleY
;
314 aScaleX
<<= static_cast<sal_Int16
>(1);
315 xProp
->setPropertyValue("ScaleToPagesX", aScaleX
);
316 aScaleX
= xProp
->getPropertyValue("ScaleToPagesX");
318 CPPUNIT_ASSERT_EQUAL(sal_Int16(1), nScale
);
320 aScaleY
= xProp
->getPropertyValue("ScaleToPagesY");
322 CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nScale
);
324 // A1:G50 with fit to page width=1: slightly smaller zoom results only 1-page export
326 ScRange
range1(0, 0, 0, 6, 49, 0);
327 exportToPDF(xModel
, range1
);
329 CPPUNIT_ASSERT(hasTextInPdf("/Count 1>>", bFound
));
330 CPPUNIT_ASSERT_EQUAL(true, bFound
);
333 // A1:L80 with fit to page width=1: slightly smaller zoom results only 1-page export
335 ScRange
range1(0, 0, 0, 11, 79, 0);
336 exportToPDF(xModel
, range1
);
338 CPPUNIT_ASSERT(hasTextInPdf("/Count 1>>", bFound
));
339 CPPUNIT_ASSERT_EQUAL(true, bFound
);
343 void ScPDFExportTest::testUnoCommands_Tdf120161()
345 loadFromURL(u
"tdf120161.ods");
349 exportToPDFWithUnoCommands("A1:G1");
351 CPPUNIT_ASSERT(hasTextInPdf("DejaVuSans", bFound
));
352 CPPUNIT_ASSERT_EQUAL(false, bFound
);
357 exportToPDFWithUnoCommands("G1:H1");
359 CPPUNIT_ASSERT(hasTextInPdf("DejaVuSans", bFound
));
360 CPPUNIT_ASSERT_EQUAL(true, bFound
);
365 exportToPDFWithUnoCommands("H1:I1");
367 CPPUNIT_ASSERT(hasTextInPdf("DejaVuSans", bFound
));
368 CPPUNIT_ASSERT_EQUAL(true, bFound
);
372 void ScPDFExportTest::testTdf64703_hiddenPageBreak()
374 loadFromURL(u
"tdf64703_hiddenPageBreak.ods");
376 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
378 // A1:A11: 4-page export
380 ScRange
range1(0, 0, 0, 0, 10, 0);
381 exportToPDF(xModel
, range1
);
383 CPPUNIT_ASSERT(hasTextInPdf("/Count 4>>", bFound
));
384 CPPUNIT_ASSERT_EQUAL(true, bFound
);
388 void ScPDFExportTest::testTdf143978()
390 std::shared_ptr
<vcl::pdf::PDFium
> pPDFium
= vcl::pdf::PDFiumLibrary::get();
396 loadFromURL(u
"tdf143978.ods");
397 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
400 ScRange
range1(0, 0, 0, 0, 1, 0);
401 exportToPDF(xModel
, range1
);
402 // Parse the export result with pdfium.
403 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
= parsePDFExport();
404 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument
->getPageCount());
406 // Get the first page
407 std::unique_ptr
<vcl::pdf::PDFiumPage
> pPdfPage
= pPdfDocument
->openPage(/*nIndex=*/0);
408 CPPUNIT_ASSERT(pPdfPage
);
409 std::unique_ptr
<vcl::pdf::PDFiumTextPage
> pTextPage
= pPdfPage
->getTextPage();
411 int nPageObjectCount
= pPdfPage
->getObjectCount();
412 CPPUNIT_ASSERT_EQUAL(2, nPageObjectCount
);
414 // Without the fix in place, this test would have failed with
415 // - Expected: Dies ist viel zu viel Text
416 // - Actual : Dies ist vie
417 std::unique_ptr
<vcl::pdf::PDFiumPageObject
> pPageObject1
= pPdfPage
->getObject(0);
418 OUString sText1
= pPageObject1
->getText(pTextPage
);
419 CPPUNIT_ASSERT_EQUAL(OUString("Dies ist viel zu viel Text"), sText1
);
421 // and it would also have failed with
422 // - Expected: 2021-11-17
424 std::unique_ptr
<vcl::pdf::PDFiumPageObject
> pPageObject2
= pPdfPage
->getObject(1);
425 OUString sText2
= pPageObject2
->getText(pTextPage
);
426 CPPUNIT_ASSERT_EQUAL(OUString("2021-11-17"), sText2
);
429 void ScPDFExportTest::testTdf84012()
431 std::shared_ptr
<vcl::pdf::PDFium
> pPDFium
= vcl::pdf::PDFiumLibrary::get();
437 loadFromURL(u
"tdf84012.ods");
438 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
441 ScRange
range1(0, 0, 0, 0, 0, 0);
442 exportToPDF(xModel
, range1
);
443 // Parse the export result with pdfium.
444 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
= parsePDFExport();
445 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument
->getPageCount());
447 // Get the first page
448 std::unique_ptr
<vcl::pdf::PDFiumPage
> pPdfPage
= pPdfDocument
->openPage(/*nIndex=*/0);
449 CPPUNIT_ASSERT(pPdfPage
);
450 std::unique_ptr
<vcl::pdf::PDFiumTextPage
> pTextPage
= pPdfPage
->getTextPage();
452 int nChars
= pTextPage
->countChars();
453 std::vector
<sal_uInt32
> aChars(nChars
);
454 for (int i
= 0; i
< nChars
; i
++)
455 aChars
[i
] = pTextPage
->getUnicode(i
);
456 OUString
aActualText(aChars
.data(), aChars
.size());
458 // Without the fix in place, this test would have failed with
459 // - Expected: Blah blah (blah, blah)
460 // - Actual : Blah blah
461 CPPUNIT_ASSERT_EQUAL(OUString("Blah blah (blah, blah)"), aActualText
);
464 void ScPDFExportTest::testTdf78897()
466 std::shared_ptr
<vcl::pdf::PDFium
> pPDFium
= vcl::pdf::PDFiumLibrary::get();
472 loadFromURL(u
"tdf78897.xls");
473 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
476 ScRange
range1(2, 2, 0, 3, 2, 0);
477 exportToPDF(xModel
, range1
);
478 // Parse the export result with pdfium.
479 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
= parsePDFExport();
480 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument
->getPageCount());
482 // Get the first page
483 std::unique_ptr
<vcl::pdf::PDFiumPage
> pPdfPage
= pPdfDocument
->openPage(/*nIndex=*/0);
484 CPPUNIT_ASSERT(pPdfPage
);
485 std::unique_ptr
<vcl::pdf::PDFiumTextPage
> pTextPage
= pPdfPage
->getTextPage();
487 int nChars
= pTextPage
->countChars();
488 std::vector
<sal_uInt32
> aChars(nChars
);
489 for (int i
= 0; i
< nChars
; i
++)
490 aChars
[i
] = pTextPage
->getUnicode(i
);
491 OUString
aActualText(aChars
.data(), aChars
.size());
493 // Without the fix in place, this test would have failed with
494 // - Expected: 11.00 11.00
495 // - Actual : 11.00 ###
496 CPPUNIT_ASSERT_EQUAL(OUString(" 11.00 11.00 "), aActualText
);
499 // just needs to not crash on export to pdf
500 void ScPDFExportTest::testForcepoint97()
502 loadFromURL(u
"forcepoint97.xlsx");
503 uno::Reference
<frame::XModel
> xModel(mxComponent
, uno::UNO_QUERY
);
506 ScRange
range1(0, 0, 0, 7, 81, 0);
507 exportToPDF(xModel
, range1
);
510 CPPUNIT_TEST_SUITE_REGISTRATION(ScPDFExportTest
);
511 CPPUNIT_PLUGIN_IMPLEMENT();
513 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */