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 cf25b30b2a200466d5ef1260320ce9a622f4f1c3
Author:     Jaume Pujantell <jaume.pujant...@collabora.com>
AuthorDate: Wed Jun 25 23:24:03 2025 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Aug 20 09:07:18 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/+/189920
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/include/vcl/filter/PDFiumLibrary.hxx 
b/include/vcl/filter/PDFiumLibrary.hxx
index 073cf88b42d3..c91c0cef6a77 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 390ab675c065..743fd68eebc0 100644
--- a/include/vcl/pdfread.hxx
+++ b/include/vcl/pdfread.hxx
@@ -80,19 +80,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 07165959eff9..c923e6e6a5f4 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 50fb74852175..123b39d4f1d1 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 4739e72b7145..6daa750132c9 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
@@ -908,6 +932,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;
                     }
                 }
@@ -975,7 +1001,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 10a46e70228e..40abf61e1dd4 100644
--- a/vcl/source/filter/ipdf/pdfread.cxx
+++ b/vcl/source/filter/ipdf/pdfread.cxx
@@ -337,6 +337,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)
@@ -391,8 +416,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 04359dd1cad5..c2ce375aa866 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;
@@ -1040,6 +1056,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);
@@ -1656,6 +1684,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