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}