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) {