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;

Reply via email to