sw/CppunitTest_sw_globalfilter.mk | 1 sw/inc/ToxLinkProcessor.hxx | 2 - sw/qa/core/test_ToxLinkProcessor.cxx | 14 +++---- sw/qa/extras/globalfilter/data/SimpleTOC.odt |binary sw/qa/extras/globalfilter/globalfilter.cxx | 50 +++++++++++++++++++++++++++ sw/source/core/tox/ToxLinkProcessor.cxx | 3 + sw/source/core/tox/ToxTextGenerator.cxx | 8 +++- 7 files changed, 67 insertions(+), 11 deletions(-)
New commits: commit c468fab6f0095941a966f044729094ce8af0f3b7 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Fri Jul 11 17:08:00 2025 +0200 Commit: Tomaž Vajngerl <[email protected]> CommitDate: Sat Jul 26 12:47:57 2025 +0200 sw: test for tdf#167409 - check PDF Annotation /Contents Change-Id: I7f24868387672f4237a9333f91acada1d33b81d8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187737 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Michael Stahl <[email protected]> diff --git a/sw/CppunitTest_sw_globalfilter.mk b/sw/CppunitTest_sw_globalfilter.mk index e6d136efbe3a..819f233fcb00 100644 --- a/sw/CppunitTest_sw_globalfilter.mk +++ b/sw/CppunitTest_sw_globalfilter.mk @@ -42,6 +42,7 @@ $(eval $(call gb_CppunitTest_use_externals,sw_globalfilter,\ $(eval $(call gb_CppunitTest_set_include,sw_globalfilter,\ -I$(SRCDIR)/sw/inc \ -I$(SRCDIR)/sw/source/core/inc \ + -I$(SRCDIR)/sw/source/uibase/inc \ -I$(SRCDIR)/sw/qa/inc \ $$(INCLUDE) \ )) diff --git a/sw/qa/extras/globalfilter/data/SimpleTOC.odt b/sw/qa/extras/globalfilter/data/SimpleTOC.odt new file mode 100644 index 000000000000..fbcf101dbb24 Binary files /dev/null and b/sw/qa/extras/globalfilter/data/SimpleTOC.odt differ diff --git a/sw/qa/extras/globalfilter/globalfilter.cxx b/sw/qa/extras/globalfilter/globalfilter.cxx index 19ffaaee929c..ed4a4b7ae761 100644 --- a/sw/qa/extras/globalfilter/globalfilter.cxx +++ b/sw/qa/extras/globalfilter/globalfilter.cxx @@ -32,6 +32,7 @@ #include <ndtxt.hxx> #include <ndindex.hxx> #include <pam.hxx> +#include <wrtsh.hxx> #include <xmloff/odffields.hxx> #include <IDocumentMarkAccess.hxx> #include <IMark.hxx> @@ -1649,6 +1650,55 @@ CPPUNIT_TEST_FIXTURE(Test, testListLabelPDFExport) CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nL)>(1), nL); } +CPPUNIT_TEST_FIXTURE(Test, testTableOfContentLinksHaveContentSet) +{ + // Test for tdf#167409 + + // TOC is expected to have alt. text set (written to /Contents key), PDF/UA conformance tests + // will fail. TOC links can't be set by the user. + + createSwDoc("SimpleTOC.odt"); + + // Let's update TOC first + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pWrtShell->GetTOXCount()); + const SwTOXBase* pTOX = pWrtShell->GetTOX(0); + CPPUNIT_ASSERT(pTOX); + pWrtShell->UpdateTableOf(*pTOX); + + // Export as PDF + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_pdf_Export"_ustr; + // Enable PDF/UA + uno::Sequence<beans::PropertyValue> aFilterData( + comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); + aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData; + css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW); + xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); + + // Parse the export result with pdfium. + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport(); + + // Non-NULL pPdfDocument means pdfium is available. + if (!pPdfDocument) + return; + + // The document has one page. + CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); + std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); + CPPUNIT_ASSERT(pPdfPage); + + // The page has one annotation. + CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount()); + std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPdfPage->getAnnotation(0); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Link, pAnnotation->getSubType()); + CPPUNIT_ASSERT(pAnnotation->hasKey("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, + pAnnotation->getValueType("Contents"_ostr)); + OUString aContent = pAnnotation->getString("Contents"_ostr); + CPPUNIT_ASSERT_EQUAL(u"Heading 1"_ustr, aContent); +} + CPPUNIT_TEST_FIXTURE(Test, testTdf143311) { createSwDoc("tdf143311-1.docx"); commit 46b8d64c5098aaa9af51a15acb7cc170ee6a8405 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Wed Jul 9 10:13:55 2025 +0200 Commit: Tomaž Vajngerl <[email protected]> CommitDate: Sat Jul 26 12:47:48 2025 +0200 tdf#167409 set alt. text to the internal hyperlinks in a TOC The alt. text that is set is the TOC entry text. Alt. text is required when exporting as PDF/UA and the user can't set it for TOC, so we have to set this. Change-Id: I8a9012cd7a6155f0672e0e8019ec1183d01cdcb6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187556 Reviewed-by: Tomaž Vajngerl <[email protected]> Tested-by: Jenkins (cherry picked from commit e2be650d97765945538614463b9269acb164947c) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187599 Reviewed-by: Michael Stahl <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> diff --git a/sw/inc/ToxLinkProcessor.hxx b/sw/inc/ToxLinkProcessor.hxx index 967e658d4970..0714b691a0cb 100644 --- a/sw/inc/ToxLinkProcessor.hxx +++ b/sw/inc/ToxLinkProcessor.hxx @@ -41,7 +41,7 @@ public: * STR_POOLCHR_TOXJUMP. */ void - CloseLink(sal_Int32 endPosition, const OUString& url, bool bRelative); + CloseLink(sal_Int32 endPosition, const OUString& url, const OUString& sText, bool bRelative); /** Insert the found links as attributes to a text node */ void diff --git a/sw/qa/core/test_ToxLinkProcessor.cxx b/sw/qa/core/test_ToxLinkProcessor.cxx index a47762912c6c..64d1583c5d50 100644 --- a/sw/qa/core/test_ToxLinkProcessor.cxx +++ b/sw/qa/core/test_ToxLinkProcessor.cxx @@ -56,11 +56,11 @@ ToxLinkProcessorTest::NoExceptionIsThrownIfTooManyLinksAreClosed() { ToxLinkProcessor sut; sut.StartNewLink(0, STYLE_NAME_1); - sut.CloseLink(1, URL_1, /*bRelative=*/true); + sut.CloseLink(1, URL_1, OUString(), /*bRelative=*/true); // fdo#85872 actually it turns out the UI does something like this // so an exception must not be thrown! // should not succeed either (for backward compatibility) - sut.CloseLink(2, URL_1, /*bRelative=*/true); + sut.CloseLink(2, URL_1, OUString(), /*bRelative=*/true); CPPUNIT_ASSERT_EQUAL(1u, static_cast<unsigned>(sut.m_ClosedLinks.size())); CPPUNIT_ASSERT_EQUAL(1u, static_cast<unsigned>(sut.m_ClosedLinks.at(0)->mEndTextPos)); CPPUNIT_ASSERT_MESSAGE("no links are open", !sut.m_oStartedLink); @@ -72,10 +72,10 @@ ToxLinkProcessorTest::AddingAndClosingTwoOverlappingLinksResultsInOneClosedLink( ToxLinkProcessor sut; sut.StartNewLink(0, STYLE_NAME_1); sut.StartNewLink(0, STYLE_NAME_2); - sut.CloseLink(1, URL_1, /*bRelative=*/true); + sut.CloseLink(1, URL_1, OUString(), /*bRelative=*/true); // this should not cause an error, and should not succeed either // (for backward compatibility) - sut.CloseLink(1, URL_2, /*bRelative=*/true); + sut.CloseLink(1, URL_2, OUString(), /*bRelative=*/true); CPPUNIT_ASSERT_EQUAL(1u, static_cast<unsigned>(sut.m_ClosedLinks.size())); CPPUNIT_ASSERT_MESSAGE("no links are open", !sut.m_oStartedLink); // backward compatibility: the last start is closed by the first end @@ -108,7 +108,7 @@ ToxLinkProcessorTest::LinkIsCreatedCorrectly() ToxLinkProcessorWithOverriddenObtainPoolId sut; sut.StartNewLink(0, STYLE_NAME_1); - sut.CloseLink(1, URL_1, /*bRelative=*/true); + sut.CloseLink(1, URL_1, OUString(), /*bRelative=*/true); CPPUNIT_ASSERT_EQUAL_MESSAGE("Style is stored correctly in link", STYLE_NAME_1, sut.m_ClosedLinks.at(0)->mINetFormat.GetVisitedFormat()); CPPUNIT_ASSERT_EQUAL_MESSAGE("Url is stored correctly in link", URL_1, sut.m_ClosedLinks.at(0)->mINetFormat.GetValue()); @@ -122,9 +122,9 @@ ToxLinkProcessorTest::LinkSequenceIsPreserved() ToxLinkProcessorWithOverriddenObtainPoolId sut; sut.StartNewLink(0, STYLE_NAME_2); - sut.CloseLink(1, URL_2, /*bRelative=*/true); + sut.CloseLink(1, URL_2, OUString(), /*bRelative=*/true); sut.StartNewLink(1, STYLE_NAME_1); - sut.CloseLink(2, URL_1, /*bRelative=*/true); + sut.CloseLink(2, URL_1, OUString(), /*bRelative=*/true); // check first closed element CPPUNIT_ASSERT_EQUAL_MESSAGE("Style is stored correctly in link", diff --git a/sw/source/core/tox/ToxLinkProcessor.cxx b/sw/source/core/tox/ToxLinkProcessor.cxx index 6d431e997651..f3c9c47a540e 100644 --- a/sw/source/core/tox/ToxLinkProcessor.cxx +++ b/sw/source/core/tox/ToxLinkProcessor.cxx @@ -24,7 +24,7 @@ ToxLinkProcessor::StartNewLink(sal_Int32 startPosition, const OUString& characte m_oStartedLink.emplace(startPosition, characterStyle); } -void ToxLinkProcessor::CloseLink(sal_Int32 endPosition, const OUString& url, bool bRelative) +void ToxLinkProcessor::CloseLink(sal_Int32 endPosition, const OUString& url, const OUString& sAltText, bool bRelative) { if (!m_oStartedLink) { @@ -58,6 +58,7 @@ void ToxLinkProcessor::CloseLink(sal_Int32 endPosition, const OUString& url, boo sal_uInt16 poolId = ObtainPoolId(characterStyle); pClosedLink->mINetFormat.SetVisitedFormatAndId(characterStyle, poolId); pClosedLink->mINetFormat.SetINetFormatAndId(characterStyle, poolId); + pClosedLink->mINetFormat.SetName(sAltText); m_ClosedLinks.push_back(std::move(pClosedLink)); m_oStartedLink.reset(); diff --git a/sw/source/core/tox/ToxTextGenerator.cxx b/sw/source/core/tox/ToxTextGenerator.cxx index 32d18d1c8087..9bc9307b5e24 100644 --- a/sw/source/core/tox/ToxTextGenerator.cxx +++ b/sw/source/core/tox/ToxTextGenerator.cxx @@ -178,6 +178,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, // FIXME this operates directly on the node text OUString & rText = const_cast<OUString&>(pTOXNd->GetText()); rText.clear(); + OUString rAltText; for(sal_uInt16 nIndex = indexOfEntryToProcess; nIndex < indexOfEntryToProcess + numberOfEntriesToProcess; nIndex++) { if(nIndex > indexOfEntryToProcess) @@ -213,6 +214,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, case TOKEN_ENTRY_TEXT: { HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout); ApplyHandledTextToken(htt, *pTOXNd); + rAltText += htt.text; } break; @@ -222,6 +224,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, rText += GetNumStringOfFirstNode(rBase, true, MAXLEVEL, pLayout); HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout); ApplyHandledTextToken(htt, *pTOXNd); + rAltText += htt.text; } break; @@ -247,6 +250,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, case TOKEN_LINK_START: mLinkProcessor->StartNewLink(rText.getLength(), aToken.sCharStyleName); + rAltText = ""; break; case TOKEN_LINK_END: @@ -259,7 +263,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, ++iter->second; url = "#" + OUString::number(iter->second) + url; } - mLinkProcessor->CloseLink(rText.getLength(), url, /*bRelative=*/true); + mLinkProcessor->CloseLink(rText.getLength(), url, rAltText, /*bRelative=*/true); } break; @@ -280,7 +284,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, OUString aURL = SwTOXAuthority::GetSourceURL( rAuthority.GetText(AUTH_FIELD_URL, pLayout)); - mLinkProcessor->CloseLink(rText.getLength(), aURL, /*bRelative=*/false); + mLinkProcessor->CloseLink(rText.getLength(), aURL, rAltText, /*bRelative=*/false); } } break;
