Skip to main content

pdfium/
action.rs

1// PDFium-rs -- Modern Rust interface to PDFium, the PDF library from Google
2//
3// Copyright (c) 2025-2026 Martin van der Werff <github (at) newinnovations.nl>
4//
5// This file is part of PDFium-rs.
6//
7// PDFium-rs is free software: you can redistribute it and/or modify it under the terms of
8// the GNU General Public License as published by the Free Software Foundation, either version 3
9// of the License, or (at your option) any later version.
10//
11// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
12// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
13// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
15// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20use crate::{
21    PdfiumDestination, PdfiumDocument,
22    error::{PdfiumError, PdfiumResult},
23    lib,
24    pdfium_types::{ActionHandle, FPDF_ACTION, Handle},
25};
26
27/// Action type returned by FPDFAction_GetType
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum PdfiumActionType {
30    /// Unsupported action type.
31    #[default]
32    Unsupported,
33    /// Go to a destination within current document.
34    Goto,
35    /// Go to a destination within another document.
36    RemoteGoto,
37    /// URI, including web pages and other Internet resources.
38    #[allow(clippy::upper_case_acronyms)] // PDFium API uses uppercase URI
39    URI,
40    /// Launch an application or open a file.
41    Launch,
42    /// Go to a destination in an embedded file.
43    EmbeddedGoto,
44}
45
46/// # Rust interface to FPDF_ACTION
47#[derive(Debug, Clone)]
48pub struct PdfiumAction {
49    handle: ActionHandle,
50}
51
52impl PdfiumAction {
53    pub(crate) fn new_from_handle(handle: FPDF_ACTION) -> PdfiumResult<Self> {
54        if handle.is_null() {
55            Err(PdfiumError::NullHandle)
56        } else {
57            Ok(Self {
58                handle: Handle::new(handle, None), // TODO: check close is not needed
59            })
60        }
61    }
62
63    /// Returns the type of this action.
64    pub fn r#type(&self) -> PdfiumActionType {
65        match lib().FPDFAction_GetType(self) {
66            0 => PdfiumActionType::Unsupported,
67            1 => PdfiumActionType::Goto,
68            2 => PdfiumActionType::RemoteGoto,
69            3 => PdfiumActionType::URI,
70            4 => PdfiumActionType::Launch,
71            5 => PdfiumActionType::EmbeddedGoto,
72            _ => PdfiumActionType::Unsupported,
73        }
74    }
75
76    /// Returns the destination associated with this action, if any.
77    ///
78    /// Returns an error if the action is not a Goto or RemoteGoto action.
79    ///
80    /// Note: In the case of [PdfiumActionType::RemoteGoto], you must first call [PdfiumAction::file_path()],
81    /// then load the document at that path, then pass the document handle from that document as
82    /// |document| to [PdfiumAction::dest()].
83    pub fn dest(&self, document: &PdfiumDocument) -> PdfiumResult<PdfiumDestination> {
84        lib().FPDFAction_GetDest(document, self)
85    }
86
87    /// Returns the file path associated with this action, if any.
88    ///
89    /// Returns an error if the action is not a RemoteGoto or Launch action.
90    pub fn file_path(&self) -> PdfiumResult<String> {
91        let lib = lib();
92        let buf_len = lib.FPDFAction_GetFilePath(self, None, 0);
93        if buf_len == 0 {
94            // Same as the length checking below
95            return Err(PdfiumError::InvokationFailed);
96        }
97        let mut buffer = vec![0u8; buf_len as usize];
98        // Note from the library: Regardless of the platform, the buffer is always in UTF-8 encoding.
99        let actual_len = lib.FPDFAction_GetFilePath(self, Some(&mut buffer), buf_len);
100        if actual_len == 0 {
101            // PDFium returns 0 on error
102            return Err(PdfiumError::InvokationFailed);
103        }
104        let content_len = (actual_len as usize).saturating_sub(1);
105        buffer.truncate(content_len);
106        Ok(String::from_utf8_lossy(&buffer).into_owned())
107    }
108
109    /// Returns the URI Path of the action, in raw bytes.
110    ///
111    /// Note: the returned raw bytes represents a String, but we do not know what encoding the PDF
112    /// file adopts. If you believe the String is encoded in UTF-8, you can simply call
113    /// String::from_utf8() to convert the raw bytes into a String.
114    pub fn uri_path(&self, doc: &PdfiumDocument) -> PdfiumResult<Vec<u8>> {
115        let lib = lib();
116        let buf_len = lib.FPDFAction_GetURIPath(doc, self, None, 0);
117        if buf_len == 0 {
118            return Err(PdfiumError::InvokationFailed);
119        }
120        let mut buffer = vec![0u8; buf_len as usize];
121        let actual_len = lib.FPDFAction_GetURIPath(doc, self, Some(&mut buffer), buf_len);
122        if actual_len == 0 {
123            return Err(PdfiumError::InvokationFailed);
124        }
125        let content_len = (actual_len as usize).saturating_sub(1);
126        buffer.truncate(content_len); // Truncate the last null terminator before we returns
127        Ok(buffer)
128    }
129}
130
131impl From<&PdfiumAction> for FPDF_ACTION {
132    fn from(action: &PdfiumAction) -> Self {
133        action.handle.handle()
134    }
135}