include/vcl/filter/PDFiumLibrary.hxx |    9 +++++
 include/vcl/pdfread.hxx              |    9 ++++-
 sd/inc/sdpage.hxx                    |   13 +++++++
 sd/source/filter/pdf/sdpdffilter.cxx |    1 
 sd/source/ui/view/drviews1.cxx       |   29 +++++++++++++++++
 vcl/source/filter/ipdf/pdfread.cxx   |   30 +++++++++++++++++
 vcl/source/pdf/PDFiumLibrary.cxx     |   59 +++++++++++++++++++++++++++++++++++
 7 files changed, 148 insertions(+), 2 deletions(-)

New commits:
commit b307d3f997eee3014697734f04ad99b4f3ef50ae
Author:     Jaume Pujantell <jaume.pujant...@collabora.com>
AuthorDate: Wed Jun 25 23:24:03 2025 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Jul 9 13:54:42 2025 +0200

    lokit pdfium: send link annotations data
    
    On read only pdfs opened with PDFium send the data (position and uri) of
    link annotations in each page so the behavior of having clickable
    links even in a read only document can be emulated.
    
    Change-Id: I40bb22b9f6b2f9f9cd8be13c0639faec48ce82d1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187010
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/include/vcl/filter/PDFiumLibrary.hxx 
b/include/vcl/filter/PDFiumLibrary.hxx
index 1f20beab475f..6dbc1cfece70 100644
--- a/include/vcl/filter/PDFiumLibrary.hxx
+++ b/include/vcl/filter/PDFiumLibrary.hxx
@@ -120,6 +120,14 @@ public:
     virtual int getOptionCount(PDFiumDocument* pDoc) = 0;
 };
 
+class VCL_DLLPUBLIC PDFiumLink
+{
+public:
+    virtual ~PDFiumLink() = default;
+    virtual basegfx::B2DRectangle getRectangle() = 0;
+    virtual OUString getURIPath() = 0;
+};
+
 class PDFiumTextPage;
 
 class VCL_DLLPUBLIC PDFiumPathSegment
@@ -236,6 +244,7 @@ public:
     virtual bool hasTransparency() = 0;
 
     virtual bool hasLinks() = 0;
+    virtual std::unique_ptr<PDFiumLink> enumerateLink(int* nStartIndex, 
PDFiumDocument* pDoc) = 0;
 
     virtual void onAfterLoadPage(PDFiumDocument* pDoc) = 0;
 };
diff --git a/include/vcl/pdfread.hxx b/include/vcl/pdfread.hxx
index 4a78aa68b156..d819f1dff2f2 100644
--- a/include/vcl/pdfread.hxx
+++ b/include/vcl/pdfread.hxx
@@ -82,19 +82,26 @@ class PDFGraphicResult
     Size maSize;
 
     std::vector<PDFGraphicAnnotation> maAnnotations;
+    std::vector<std::pair<basegfx::B2DRectangle, OUString>> maLinksInfo;
 
 public:
     PDFGraphicResult(Graphic aGraphic, Size const& rSize,
-                     std::vector<PDFGraphicAnnotation> aAnnotations)
+                     std::vector<PDFGraphicAnnotation> aAnnotations,
+                     std::vector<std::pair<basegfx::B2DRectangle, OUString>> 
aLinks)
         : maGraphic(std::move(aGraphic))
         , maSize(rSize)
         , maAnnotations(std::move(aAnnotations))
+        , maLinksInfo(std::move(aLinks))
     {
     }
 
     const Graphic& GetGraphic() const { return maGraphic; }
     const Size& GetSize() const { return maSize; }
     const std::vector<PDFGraphicAnnotation>& GetAnnotations() const { return 
maAnnotations; }
+    const std::vector<std::pair<basegfx::B2DRectangle, OUString>>& 
GetLinksInfo() const
+    {
+        return maLinksInfo;
+    }
 };
 
 /// Import PDF as Graphic images (1 per page), but not loaded yet.
diff --git a/sd/inc/sdpage.hxx b/sd/inc/sdpage.hxx
index ace5daa0e478..71b47e4b7cc9 100644
--- a/sd/inc/sdpage.hxx
+++ b/sd/inc/sdpage.hxx
@@ -123,6 +123,9 @@ friend class sd::UndoAttrObject;
     sal_uInt16  mnPaperBin;               ///< PaperBin
     SdPageLink* mpPageLink;               ///< Page link (at left sides only)
 
+    // PDF link annotations for read-only pdfium
+    std::vector<std::pair<basegfx::B2DRectangle, OUString>> maLinkAnnotations;
+
     /** holds the smil animation sequences for this page */
     css::uno::Reference< css::animations::XAnimationNode > mxAnimationNode;
 
@@ -373,6 +376,16 @@ public:
     SD_DLLPUBLIC void 
removeAnnotation(rtl::Reference<sdr::annotation::Annotation> const& 
xAnnotation) override;
     void removeAnnotationNoNotify(rtl::Reference<sdr::annotation::Annotation> 
const& xAnnotation) override;
 
+    void setLinkAnnotations(std::vector<std::pair<basegfx::B2DRectangle, 
OUString>> aLinks)
+    {
+        maLinkAnnotations = aLinks;
+    }
+    const std::vector<std::pair<basegfx::B2DRectangle, OUString>>& 
getLinkAnnotations() const
+    {
+        return maLinkAnnotations;
+    }
+    bool hasLinkAnnotations() const { return !maLinkAnnotations.empty(); }
+
     bool Equals(const SdPage&) const;
     virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override;
     sal_uInt16 getPageId() const { return mnPageId; }
diff --git a/sd/source/filter/pdf/sdpdffilter.cxx 
b/sd/source/filter/pdf/sdpdffilter.cxx
index 068aca1fcce9..84c3e9bb01a9 100644
--- a/sd/source/filter/pdf/sdpdffilter.cxx
+++ b/sd/source/filter/pdf/sdpdffilter.cxx
@@ -227,6 +227,7 @@ bool SdPdfFilter::Import()
 
             pPage->addAnnotation(xAnnotation, -1);
         }
+        pPage->setLinkAnnotations(rPDFGraphicResult.GetLinksInfo());
     }
     mrDocument.setLock(bWasLocked);
     mrDocument.EnableUndo(bSavedUndoEnabled);
diff --git a/sd/source/ui/view/drviews1.cxx b/sd/source/ui/view/drviews1.cxx
index e77211fc3496..e7d5f387da1e 100644
--- a/sd/source/ui/view/drviews1.cxx
+++ b/sd/source/ui/view/drviews1.cxx
@@ -799,6 +799,30 @@ bool DrawViewShell::IsSelected(sal_uInt16 nPage)
     return false;
 }
 
+namespace
+{
+void notifyLinkAnnotations(SfxViewShell* pViewShell, SdPage* pPage)
+{
+    if (!pViewShell || !pPage || !pPage->hasLinkAnnotations())
+        return;
+    ::tools::JsonWriter jsonWriter;
+    jsonWriter.put("commandName", "PageLinks");
+    {
+        auto jsonLinks = jsonWriter.startArray("links");
+        for (const auto& link : pPage->getLinkAnnotations())
+        {
+            auto jsonLink = jsonWriter.startStruct();
+            std::stringstream ss;
+            ss << link.first;
+            jsonWriter.put("rectangle", ss.str());
+            jsonWriter.put("uri", link.second);
+        }
+    }
+    OString aPayload = jsonWriter.finishAndGetAsOString();
+    pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, 
aPayload);
+}
+}
+
 /**
  * Switch to desired page.
  * nSelectPage refers to the current EditMode
@@ -904,6 +928,8 @@ bool DrawViewShell::SwitchPage(sal_uInt16 nSelectedPage, 
bool bAllowChangeFocus)
                         && 
maTabControl->GetPageText(maTabControl->GetPageId(nSelectedPage)) == 
pNewPage->GetName())
                     {
                         // this slide is already visible
+                        if (comphelper::LibreOfficeKit::isActive())
+                            notifyLinkAnnotations(GetViewShell(), 
mpActualPage);
                         return true;
                     }
                 }
@@ -971,7 +997,10 @@ bool DrawViewShell::SwitchPage(sal_uInt16 nSelectedPage, 
bool bAllowChangeFocus)
             // notify LibreOfficeKit about changed page
             OString aPayload = OString::number(nSelectedPage);
             if (SfxViewShell* pViewShell = GetViewShell())
+            {
                 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, 
aPayload);
+                notifyLinkAnnotations(pViewShell, mpActualPage);
+            }
         }
 
         rtl::Reference< sd::SlideShow > xSlideshow( SlideShow::GetSlideShow( 
GetDoc() ) );
diff --git a/vcl/source/filter/ipdf/pdfread.cxx 
b/vcl/source/filter/ipdf/pdfread.cxx
index 1a1bf2217f1a..3332b76d5254 100644
--- a/vcl/source/filter/ipdf/pdfread.cxx
+++ b/vcl/source/filter/ipdf/pdfread.cxx
@@ -346,6 +346,31 @@ findAnnotations(const 
std::unique_ptr<vcl::pdf::PDFiumPage>& pPage, basegfx::B2D
     return aPDFGraphicAnnotations;
 }
 
+std::vector<std::pair<basegfx::B2DRectangle, OUString>>
+findLinks(const std::unique_ptr<vcl::pdf::PDFiumPage>& pPage,
+          const std::unique_ptr<vcl::pdf::PDFiumDocument>& pDocument, 
basegfx::B2DSize aPageSize)
+{
+    std::vector<std::pair<basegfx::B2DRectangle, OUString>> aResult;
+    int nIndex = 0;
+    std::unique_ptr<vcl::pdf::PDFiumLink> pLink;
+    while ((pLink = pPage->enumerateLink(&nIndex, pDocument.get())))
+    {
+        if (!pLink->getURIPath().isEmpty())
+        {
+            basegfx::B2DRectangle rRectangle = pLink->getRectangle();
+            basegfx::B2DRectangle rRectangleHMM(
+                o3tl::convert(rRectangle.getMinX(), o3tl::Length::pt, 
o3tl::Length::twip),
+                o3tl::convert(aPageSize.getHeight() - rRectangle.getMinY(), 
o3tl::Length::pt,
+                              o3tl::Length::twip),
+                o3tl::convert(rRectangle.getMaxX(), o3tl::Length::pt, 
o3tl::Length::twip),
+                o3tl::convert(aPageSize.getHeight() - rRectangle.getMaxY(), 
o3tl::Length::pt,
+                              o3tl::Length::twip));
+            aResult.emplace_back(rRectangleHMM, pLink->getURIPath());
+        }
+    }
+    return aResult;
+}
+
 } // end anonymous namespace
 
 size_t ImportPDFUnloaded(const OUString& rURL, std::vector<PDFGraphicResult>& 
rGraphics)
@@ -399,8 +424,11 @@ size_t ImportPDFUnloaded(const OUString& rURL, 
std::vector<PDFGraphicResult>& rG
         std::vector<PDFGraphicAnnotation> aPDFGraphicAnnotations
             = findAnnotations(pPage, aPageSize);
 
+        std::vector<std::pair<basegfx::B2DRectangle, OUString>> aPDFLinksInfo
+            = findLinks(pPage, pPdfDocument, aPageSize);
+
         rGraphics.emplace_back(std::move(aGraphic), Size(nPageWidth, 
nPageHeight),
-                               aPDFGraphicAnnotations);
+                               aPDFGraphicAnnotations, aPDFLinksInfo);
     }
 
     return rGraphics.size();
diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx
index 9c13d5555aae..65e26e04d717 100644
--- a/vcl/source/pdf/PDFiumLibrary.cxx
+++ b/vcl/source/pdf/PDFiumLibrary.cxx
@@ -339,6 +339,20 @@ public:
     int getOptionCount(PDFiumDocument* pDoc) override;
 };
 
+class VCL_DLLPUBLIC PDFiumLinkImpl final : public PDFiumLink
+{
+    FPDF_LINK mpLink;
+    OUString maURI;
+
+    PDFiumLinkImpl(const PDFiumLinkImpl&) = delete;
+    PDFiumLinkImpl& operator=(const PDFiumLinkImpl&) = delete;
+
+public:
+    PDFiumLinkImpl(FPDF_DOCUMENT pDocument, FPDF_LINK pLink);
+    basegfx::B2DRectangle getRectangle() override;
+    OUString getURIPath() override;
+};
+
 class PDFiumStructureElementImpl final : public PDFiumStructureElement
 {
 private:
@@ -515,6 +529,7 @@ public:
     bool hasTransparency() override;
 
     bool hasLinks() override;
+    std::unique_ptr<PDFiumLink> enumerateLink(int* nStartIndex, 
PDFiumDocument* pDoc) override;
 
     void onAfterLoadPage(PDFiumDocument* pDoc) override;
 };
@@ -566,6 +581,7 @@ public:
     PDFiumDocumentImpl(FPDF_DOCUMENT pPdfDocument);
     ~PDFiumDocumentImpl() override;
     FPDF_FORMHANDLE getFormHandlePointer();
+    FPDF_DOCUMENT getPointer() { return mpPdfDocument; }
 
     // Page size in points
     basegfx::B2DSize getPageSize(int nIndex) override;
@@ -1026,6 +1042,18 @@ bool PDFiumPageImpl::hasLinks()
     return FPDFLink_Enumerate(mpPage, &nStartPos, &pLinkAnnot);
 }
 
+std::unique_ptr<PDFiumLink> PDFiumPageImpl::enumerateLink(int* nStartIndex, 
PDFiumDocument* pDoc)
+{
+    std::unique_ptr<PDFiumLink> pPDFiumLink;
+    FPDF_LINK pLinkAnnot = nullptr;
+    if (FPDFLink_Enumerate(mpPage, nStartIndex, &pLinkAnnot))
+    {
+        auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+        pPDFiumLink = std::make_unique<PDFiumLinkImpl>(pDocImpl->getPointer(), 
pLinkAnnot);
+    }
+    return pPDFiumLink;
+}
+
 void PDFiumPageImpl::onAfterLoadPage(PDFiumDocument* pDoc)
 {
     auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
@@ -1653,6 +1681,37 @@ std::unique_ptr<PDFiumPageObject> 
PDFiumAnnotationImpl::getObject(int nIndex)
     return pPDFiumPageObject;
 }
 
+PDFiumLinkImpl::PDFiumLinkImpl(FPDF_DOCUMENT pDocument, FPDF_LINK pLink)
+    : mpLink(pLink)
+{
+    FPDF_ACTION pAction = FPDFLink_GetAction(pLink);
+    int nType = FPDFAction_GetType(pAction);
+    if (nType != PDFACTION_URI)
+        return;
+
+    size_t nLen = FPDFAction_GetURIPath(pDocument, pAction, nullptr, 0);
+    if (nLen == 0)
+        return;
+
+    char* pBuffer = new char[nLen];
+    FPDFAction_GetURIPath(pDocument, pAction, pBuffer, nLen);
+    maURI = OUString::fromUtf8(std::string_view(pBuffer, nLen - 1));
+    delete[] pBuffer;
+}
+
+basegfx::B2DRectangle PDFiumLinkImpl::getRectangle()
+{
+    basegfx::B2DRectangle aB2DRectangle;
+    FS_RECTF aRect;
+    if (FPDFLink_GetAnnotRect(mpLink, &aRect))
+    {
+        aB2DRectangle = basegfx::B2DRectangle(aRect.left, aRect.top, 
aRect.right, aRect.bottom);
+    }
+    return aB2DRectangle;
+}
+
+OUString PDFiumLinkImpl::getURIPath() { return maURI; }
+
 PDFiumStructureElementImpl::PDFiumStructureElementImpl(FPDF_STRUCTELEMENT 
pStructureElement)
     : mpStructureElement(pStructureElement)
 {

Reply via email to