.gitignore | 1 Makefile.fetch | 1 Makefile.in | 1 bin/verapdf.sh.in | 2 config_host.mk.in | 1 configure.ac | 27 +++++++++ download.lst | 5 + include/test/unoapi_test.hxx | 7 +- test/source/unoapi_test.cxx | 87 +++++++++++++++++++------------- vcl/qa/cppunit/pdfexport/pdfexport.cxx | 3 + vcl/qa/cppunit/pdfexport/pdfexport2.cxx | 10 +++ 11 files changed, 109 insertions(+), 36 deletions(-)
New commits: commit 7039373704b2e9c78662c2e44ae81aea522b5f7b Author: Xisco Fauli <[email protected]> AuthorDate: Mon Feb 2 15:38:09 2026 +0100 Commit: Xisco Fauli <[email protected]> CommitDate: Fri Feb 20 08:31:47 2026 +0100 tdf#136822: Add VeraPDF as a PDF validator For now, only validate those tests that are conformant with the validator verapdf-cli-1.29.0.jar has been generated using https://github.com/veraPDF/veraPDF-apps/pull/415 and running 'mvn clean install' Change-Id: Ic172100a120dc0f43f6de526660961d34dc6b415 Change-Id: Icacc8ccbd938f779b506fb5e1820f10a539ab77b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198556 Reviewed-by: Xisco Fauli <[email protected]> Tested-by: Jenkins diff --git a/.gitignore b/.gitignore index ed69ef94f996..e621db8d34ae 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ /bin/bffvalidator.sh /bin/odfvalidator.sh /bin/officeotron.sh +/bin/verapdf.sh /hardened_runtime.xcent /lo.xcent /vs-code.code-workspace.template diff --git a/Makefile.fetch b/Makefile.fetch index caed43c085d5..29a9fc6e4b14 100644 --- a/Makefile.fetch +++ b/Makefile.fetch @@ -252,6 +252,7 @@ $(WORKDIR)/download: $(BUILDDIR)/config_$(gb_Side).mk $(SRCDIR)/download.lst $(S $(call fetch_Optional,OPENSYMBOL,OPENSYMBOL_TTF) \ $(call fetch_Optional,ODFVALIDATOR,ODFVALIDATOR_JAR) \ $(call fetch_Optional,OFFICEOTRON,OFFICEOTRON_JAR) \ + $(call fetch_Optional,VERAPDF,VERAPDF_JAR) \ ,$(call fetch_Download_item,https://dev-www.libreoffice.org/extern,$(item))) -@mkdir -p $(TARFILE_LOCATION)/cargo $(if $(call fetch_Optional,YRS,1),\ diff --git a/Makefile.in b/Makefile.in index 340ebf7321bd..f754c4c3ec76 100644 --- a/Makefile.in +++ b/Makefile.in @@ -226,6 +226,7 @@ distclean : clean compilerplugins-clean mac-app-store-package.clean $(BUILDDIR)/bin/bffvalidator.sh \ $(BUILDDIR)/bin/odfvalidator.sh \ $(BUILDDIR)/bin/officeotron.sh \ + $(BUILDDIR)/bin/verapdf.sh \ $(BUILDDIR)/config.Build.log \ $(BUILDDIR)/config.Build.warn \ $(BUILDDIR)/config.log \ diff --git a/bin/verapdf.sh.in b/bin/verapdf.sh.in new file mode 100644 index 000000000000..bc536eeb9e4e --- /dev/null +++ b/bin/verapdf.sh.in @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +java -jar @TARFILE_LOCATION_NATIVE@/@VERAPDF_JAR@ --nonpdfext "$@" diff --git a/config_host.mk.in b/config_host.mk.in index 2103814da8d0..45940b6b258b 100644 --- a/config_host.mk.in +++ b/config_host.mk.in @@ -766,6 +766,7 @@ export VCL_PLUGIN_INFO=@VCL_PLUGIN_INFO@ export VCTOOLSET=@VCTOOLSET@ export VCVER=@VCVER@ export DEVENV=@DEVENV@ +export VERAPDF=@VERAPDF@ export VISIO_CFLAGS=$(gb_SPACE)@VISIO_CFLAGS@ export VISIO_LIBS=$(gb_SPACE)@VISIO_LIBS@ export WGET=@WGET@ diff --git a/configure.ac b/configure.ac index ccfcb348c26e..2693dfde888b 100644 --- a/configure.ac +++ b/configure.ac @@ -6197,6 +6197,7 @@ if test "$cross_compiling" = "yes"; then bin/bffvalidator.sh.in \ bin/odfvalidator.sh.in \ bin/officeotron.sh.in \ + bin/verapdf.sh.in \ instsetoo_native/util/openoffice.lst.in \ config_host/*.in \ sysui/desktop/macosx/Info.plist.in \ @@ -9653,6 +9654,31 @@ if test "$with_export_validation" != "no"; then else OFFICEOTRON="sh $OFFICEOTRON" fi + + AC_PATH_PROGS(VERAPDF, verapdf) + if test -z "$VERAPDF"; then + # remember to download the verapdf with validator later + AC_MSG_NOTICE([no verapdf found, will download it]) + BUILD_TYPE="$BUILD_TYPE VERAPDF" + VERAPDF="$BUILDDIR/bin/verapdf.sh" + + # and fetch name of verapdf jar name from download.lst + VERAPDF_JAR=`$SED -n -e "s/^VERAPDF_JAR *:= *\(.*\) *//p" $SRC_ROOT/download.lst` + AC_SUBST(VERAPDF_JAR) + + if test -z "$VERAPDF_JAR"; then + AC_MSG_ERROR([cannot determine verapdf jar location (--with-export-validation)]) + fi + fi + if test "$build_os" = "cygwin"; then + # In case of Cygwin it will be executed from Windows, + # so we need to run bash and absolute path to validator + # so instead of "odfvalidator" it will be + # something like "bash.exe C:+ VERAPDF="bash.exe `cygpath -m "$VERAPDF"`" + else + VERAPDF="sh $VERAPDF" + fi fi AC_SUBST(OFFICEOTRON) else @@ -16274,6 +16300,7 @@ AC_CONFIG_FILES([ bin/bffvalidator.sh bin/odfvalidator.sh bin/officeotron.sh + bin/verapdf.sh instsetoo_native/util/openoffice.lst sysui/desktop/macosx/Info.plist sysui/desktop/macosx/LaunchConstraint.plist diff --git a/download.lst b/download.lst index 4c41f1d1292b..a4f21573ec82 100644 --- a/download.lst +++ b/download.lst @@ -539,6 +539,11 @@ OFFICEOTRON_JAR := officeotron-0.8.8.jar # three static lines # so that git cherry-pick # will not run into conflicts +VERAPDF_SHA256SUM := bdeef807f7e883fe3ff4e0a4712dc216064ca670d5c857fbc94408266719f0f4 +VERAPDF_JAR := verapdf-cli-1.29.0.jar +# three static lines +# so that git cherry-pick +# will not run into conflicts ONLINEUPDATE_SHA256SUM := 37206cf981e8409d048b59ac5839621ea107ff49af72beb9d7769a2f41da8d90 ONLINEUPDATE_TARBALL := onlineupdate-c003be8b9727672e7d30972983b375f4c200233f-2.tar.xz # three static lines diff --git a/include/test/unoapi_test.hxx b/include/test/unoapi_test.hxx index 82baa824d2b0..664d12994a71 100644 --- a/include/test/unoapi_test.hxx +++ b/include/test/unoapi_test.hxx @@ -28,7 +28,8 @@ enum ValidationFormat { OOXML, ODF, - MSBINARY + MSBINARY, + PDF }; enum class TestFilter @@ -188,9 +189,9 @@ protected: rtl::Reference<TestInteractionHandler> xInteractionHandler; -private: - void validate(const OUString& rURL, TestFilter eFilter) const; + void validate(TestFilter eFilter); +private: bool mbSkipValidation; OUString m_aBaseString; diff --git a/test/source/unoapi_test.cxx b/test/source/unoapi_test.cxx index 476005d89be2..ebd4a3f00cff 100644 --- a/test/source/unoapi_test.cxx +++ b/test/source/unoapi_test.cxx @@ -100,7 +100,7 @@ constexpr std::u16string_view grand_total = u"Grand total of errors in submitted } #endif -void UnoApiTest::validate(const OUString& rPath, TestFilter eFilter) const +void UnoApiTest::validate(TestFilter eFilter) { ValidationFormat eFormat = ValidationFormat::ODF; if (eFilter == TestFilter::XLSX) @@ -123,6 +123,8 @@ void UnoApiTest::validate(const OUString& rPath, TestFilter eFilter) const eFormat = ValidationFormat::MSBINARY; else if (eFilter == TestFilter::PPT) eFormat = ValidationFormat::MSBINARY; + else if (eFilter == TestFilter::PDF_WRITER) + eFormat = ValidationFormat::PDF; else { SAL_INFO("test", "UnoApiTest::validate: unknown filter"); @@ -139,6 +141,10 @@ void UnoApiTest::validate(const OUString& rPath, TestFilter eFilter) const { var = "ODFVALIDATOR"; } + else if (eFormat == ValidationFormat::PDF) + { + var = "VERAPDF"; + } else if (eFormat == ValidationFormat::MSBINARY) { #if HAVE_BFFVALIDATOR @@ -175,7 +181,7 @@ void UnoApiTest::validate(const OUString& rPath, TestFilter eFilter) const utl::TempFileNamed aOutput; aOutput.EnableKillingFile(); OUString aOutputFile = aOutput.GetFileName(); - OUString aCommand = aValidator + " " + rPath + " > " + aOutputFile + " 2>&1"; + OUString aCommand = aValidator + " " + maTempFile.GetFileName() + " > " + aOutputFile + " 2>&1"; #if !defined _WIN32 // For now, this is only needed by some Linux ASan builds, so keep it simply and disable it on @@ -199,45 +205,59 @@ void UnoApiTest::validate(const OUString& rPath, TestFilter eFilter) const SAL_INFO("test", "UnoApiTest::validate: executing '" << aCommand << "'"); int returnValue = system(OUStringToOString(aCommand, RTL_TEXTENCODING_UTF8).getStr()); - OString aContentString = loadFile(aOutput.GetURL()); - OUString aContentOUString = OStringToOUString(aContentString, RTL_TEXTENCODING_UTF8); - - if (eFormat == ValidationFormat::OOXML && !aContentOUString.isEmpty()) + if (eFormat == ValidationFormat::PDF) + { + SvMemoryStream aStream; + SvFileStream aFileStream(aOutput.GetURL(), StreamMode::READ); + aStream.WriteStream(aFileStream); + aStream.Seek(0); + xmlDocUniquePtr pXmlDoc = parseXmlStream(&aStream); + // Make sure the output is well-formed. + CPPUNIT_ASSERT(pXmlDoc); + assertXPath(pXmlDoc, "/report/jobs/job/validationReport", "isCompliant", u"true"); + } + else { - // check for validation errors here - sal_Int32 nIndex = aContentOUString.lastIndexOf(grand_total); - if (nIndex == -1) + OString aContentString = loadFile(aOutput.GetURL()); + OUString aContentOUString = OStringToOUString(aContentString, RTL_TEXTENCODING_UTF8); + + if (eFormat == ValidationFormat::OOXML && !aContentOUString.isEmpty()) { - SAL_WARN("test", "no summary line"); + // check for validation errors here + sal_Int32 nIndex = aContentOUString.lastIndexOf(grand_total); + if (nIndex == -1) + { + SAL_WARN("test", "no summary line"); + } + else + { + sal_Int32 nStartOfNumber = nIndex + grand_total.size(); + std::u16string_view aNumber = aContentOUString.subView(nStartOfNumber); + sal_Int32 nErrors = o3tl::toInt32(aNumber); + OString aMsg + = "validation error in OOXML export: Errors: " + OString::number(nErrors); + if (nErrors) + { + SAL_WARN("test", aContentOUString); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), sal_Int32(0), nErrors); + } } - else + else if (eFormat == ValidationFormat::ODF && !aContentOUString.isEmpty()) { - sal_Int32 nStartOfNumber = nIndex + grand_total.size(); - std::u16string_view aNumber = aContentOUString.subView(nStartOfNumber); - sal_Int32 nErrors = o3tl::toInt32(aNumber); - OString aMsg = "validation error in OOXML export: Errors: " + OString::number(nErrors); - if (nErrors) + if (aContentOUString.indexOf("Error") != -1 || aContentOUString.indexOf("Fatal") != -1) { SAL_WARN("test", aContentOUString); + CPPUNIT_FAIL(aContentString.getStr()); } - CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), sal_Int32(0), nErrors); - } - } - else if (eFormat == ValidationFormat::ODF && !aContentOUString.isEmpty()) - { - if (aContentOUString.indexOf("Error") != -1 || aContentOUString.indexOf("Fatal") != -1) - { - SAL_WARN("test", aContentOUString); - CPPUNIT_FAIL(aContentString.getStr()); } + CPPUNIT_ASSERT_EQUAL_MESSAGE( + OString("failed to execute: " + OUStringToOString(aCommand, RTL_TEXTENCODING_UTF8) + + " " + OUStringToOString(aContentOUString, RTL_TEXTENCODING_UTF8)) + .getStr(), + 0, returnValue); } - CPPUNIT_ASSERT_EQUAL_MESSAGE( - OString("failed to execute: " + OUStringToOString(aCommand, RTL_TEXTENCODING_UTF8) + " " - + OUStringToOString(aContentOUString, RTL_TEXTENCODING_UTF8)) - .getStr(), - 0, returnValue); #else - (void)rPath; (void)eFormat; #endif } @@ -347,8 +367,9 @@ void UnoApiTest::save(TestFilter eFilter, const uno::Sequence<beans::PropertyVal css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); - if (!mbSkipValidation) - validate(maTempFile.GetFileName(), eFilter); + // FIXME: Don't validate pdf files by default for now + if (!mbSkipValidation && eFilter != TestFilter::PDF_WRITER) + validate(eFilter); } void UnoApiTest::saveAndReload(TestFilter eFilter, diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 8a5c73c6f7c4..c41988e4d28f 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -2385,6 +2385,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) vcl::filter::PDFDocument aDocument; loadFromFile(u"tdf157816.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -2787,6 +2788,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) vcl::filter::PDFDocument aDocument; loadFromFile(u"LinkWithFly.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -3178,6 +3180,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) vcl::filter::PDFDocument aDocument; loadFromFile(u"LinkPages.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx index 4cf9cafce23a..efef680693b4 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx @@ -1473,6 +1473,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf139736) vcl::filter::PDFDocument aDocument; loadFromFile(u"tdf139736-1.odt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -1779,6 +1780,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf149140) vcl::filter::PDFDocument aDocument; loadFromFile(u"TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -1839,6 +1841,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testNestedSection) vcl::filter::PDFDocument aDocument; loadFromFile(u"nestedsection.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -2976,6 +2979,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf154982) vcl::filter::PDFDocument aDocument; loadFromFile(u"tdf154982.odt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -3386,6 +3390,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf135192) vcl::filter::PDFDocument aDocument; loadFromFile(u"tdf135192-1.fodp"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -3518,6 +3523,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf154955) vcl::filter::PDFDocument aDocument; loadFromFile(u"grouped-shape.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -3653,6 +3659,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf155190) vcl::filter::PDFDocument aDocument; loadFromFile(u"tdf155190.odt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -3743,6 +3750,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testMediaShapeAnnot) vcl::filter::PDFDocument aDocument; loadFromFile(u"vid.odt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -3883,6 +3891,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFlyFrameHyperlinkAnnot) vcl::filter::PDFDocument aDocument; loadFromFile(u"image-hyperlink-alttext.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); @@ -4019,6 +4028,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFormControlAnnot) vcl::filter::PDFDocument aDocument; loadFromFile(u"formcontrol.fodt"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); + validate(TestFilter::PDF_WRITER); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
