Skip to main content

rpdfium_edit/
object_ctx.rs

1// rpdfium-specific: no upstream equivalent.
2// This module resolves a PDFium API naming collision in Rust.
3
4//! Context types for namespaced page-object and annotation-object access.
5//!
6//! # Background — the naming collision
7//!
8//! PDFium's C API contains two groups of functions whose names differ only in
9//! the prefix that identifies the context (page vs. annotation):
10//!
11//! | PDFium function         | snake_case alias | Context |
12//! |-------------------------|------------------|---------|
13//! | `FPDFPage_GetObject`    | `get_object`     | page    |
14//! | `FPDFAnnot_GetObject`   | `get_object`     | annot   |
15//! | `FPDFPage_RemoveObject` | `remove_object`  | page    |
16//! | `FPDFAnnot_RemoveObject`| `remove_object`  | annot   |
17//!
18//! In C the disambiguation is implicit: the first argument type tells you
19//! which group you are calling.  In Rust we cannot define two methods with
20//! the same name on a single `EditDocument` struct, so we use *context
21//! types* instead.
22//!
23//! # Solution — context types
24//!
25//! Each context type is a lightweight wrapper that carries an immutable or
26//! mutable borrow of `EditDocument` together with the "which page / which
27//! annotation" key.  Once you have a context value you call the same
28//! upstream names (`get_object`, `remove_object`, …) on it, and the Rust
29//! type system selects the correct implementation.
30//!
31//! ```text
32//! // Page-object side
33//! let ctx = doc.page_objects(page_index);
34//! let bytes = ctx.get_object(obj_index)?;   // FPDFPage_GetObject
35//! let count = ctx.count_objects()?;         // FPDFPage_CountObjects
36//!
37//! let mut ctx = doc.page_objects_mut(page_index);
38//! ctx.remove_object(obj_index)?;            // FPDFPage_RemoveObject
39//! ctx.insert_object(new_obj)?;              // FPDFPage_InsertObject
40//!
41//! // Annotation-object side
42//! let ctx = doc.annot_objects(annot_id);
43//! let obj = ctx.get_object(0);             // FPDFAnnot_GetObject
44//! let n   = ctx.get_object_count();        // FPDFAnnot_GetObjectCount
45//!
46//! let mut ctx = doc.annot_objects_mut(annot_id);
47//! ctx.remove_object(0)?;                   // FPDFAnnot_RemoveObject
48//! ctx.append_object(new_obj)?;             // FPDFAnnot_AppendObject
49//! ```
50//!
51//! # ADR-019 alignment
52//!
53//! All methods named after upstream functions are ADR-019 Tier-2 aliases:
54//! they are `#[inline]`, accept no extra arguments, and delegate directly to
55//! the corresponding Tier-1 primary on `EditDocument`.  The context type
56//! itself supplies the context argument that upstream passes as a first
57//! parameter.
58
59use rpdfium_parser::object::ObjectId;
60
61use crate::document::EditDocument;
62use crate::error::EditError;
63use crate::page_object::PageObject;
64
65// ── Page-object context (immutable) ──────────────────────────────────────────
66
67/// Immutable context for accessing page objects on a single page.
68///
69/// Obtained via [`EditDocument::page_objects()`].
70///
71/// This type exists solely to provide correctly-namespaced ADR-019 T2 aliases
72/// for `FPDFPage_*` object functions, which otherwise collide with the
73/// `FPDFAnnot_*` aliases of the same snake_case name on `EditDocument`.
74///
75/// # Example
76/// ```no_run
77/// # use rpdfium_edit::document::EditDocument;
78/// # fn example(doc: &EditDocument) {
79/// let ctx = doc.page_objects(0);
80/// let count = ctx.count_objects().unwrap();
81/// let bytes = ctx.get_object(0).unwrap();
82/// # }
83/// ```
84pub struct PageObjectCtx<'doc> {
85    /// Borrowed document — all data lives in the document.
86    doc: &'doc EditDocument,
87    /// Which page these objects belong to.
88    page_index: usize,
89}
90
91impl<'doc> PageObjectCtx<'doc> {
92    // ── constructors (called by EditDocument entry points below) ─────────────
93
94    pub(crate) fn new(doc: &'doc EditDocument, page_index: usize) -> Self {
95        Self { doc, page_index }
96    }
97
98    // ── T2 aliases (upstream names) ───────────────────────────────────────────
99
100    /// Returns a copy of the raw content bytes for the page object at `index`.
101    ///
102    /// Each object inserted via [`EditDocument::insert_page_object()`] is
103    /// serialised as a `q … Q` (graphic-state save/restore) block.  This
104    /// method returns the bytes of the block at position `index`.
105    ///
106    /// Returns [`EditError::InvalidObjectIndex`] if `index` is out of bounds.
107    ///
108    /// ADR-019 T2 alias for `FPDFPage_GetObject`.  Namespaced here to avoid
109    /// collision with the `FPDFAnnot_GetObject` alias on `EditDocument`.
110    ///
111    /// See also [`AnnotObjectCtx::get_object()`] for the annotation side.
112    #[inline]
113    pub fn get_object(&self, index: usize) -> Result<Vec<u8>, EditError> {
114        self.doc.page_object_at(self.page_index, index)
115    }
116
117    /// Returns the number of page objects on this page.
118    ///
119    /// ADR-019 T2 alias for `FPDFPage_CountObjects`.
120    #[inline]
121    pub fn count_objects(&self) -> Result<usize, EditError> {
122        self.doc.page_object_count(self.page_index)
123    }
124}
125
126// ── Page-object context (mutable) ────────────────────────────────────────────
127
128/// Mutable context for accessing page objects on a single page.
129///
130/// Obtained via [`EditDocument::page_objects_mut()`].
131///
132/// Provides both read and write access.  Immutable methods delegate through
133/// an auto-reborrow so you can mix reads and writes without giving up the
134/// context.
135///
136/// # Example
137/// ```no_run
138/// # use rpdfium_edit::{document::EditDocument, page_object::PageObject};
139/// # fn example(doc: &mut EditDocument, new_obj: PageObject) {
140/// let mut ctx = doc.page_objects_mut(0);
141/// ctx.insert_object(new_obj).unwrap();
142/// ctx.remove_object(0).unwrap();
143/// # }
144/// ```
145pub struct PageObjectCtxMut<'doc> {
146    /// Mutably borrowed document.
147    doc: &'doc mut EditDocument,
148    /// Which page these objects belong to.
149    page_index: usize,
150}
151
152impl<'doc> PageObjectCtxMut<'doc> {
153    pub(crate) fn new(doc: &'doc mut EditDocument, page_index: usize) -> Self {
154        Self { doc, page_index }
155    }
156
157    // ── read-only aliases (borrow doc immutably through reborrow) ─────────────
158
159    /// Returns a copy of the raw content bytes for the page object at `index`.
160    ///
161    /// ADR-019 T2 alias for `FPDFPage_GetObject`.
162    #[inline]
163    pub fn get_object(&self, index: usize) -> Result<Vec<u8>, EditError> {
164        self.doc.page_object_at(self.page_index, index)
165    }
166
167    /// Returns the number of page objects on this page.
168    ///
169    /// ADR-019 T2 alias for `FPDFPage_CountObjects`.
170    #[inline]
171    pub fn count_objects(&self) -> Result<usize, EditError> {
172        self.doc.page_object_count(self.page_index)
173    }
174
175    // ── write aliases ─────────────────────────────────────────────────────────
176
177    /// Remove the page object at `index` from this page's content stream.
178    ///
179    /// ADR-019 T2 alias for `FPDFPage_RemoveObject`.  Namespaced here to
180    /// avoid collision with the `FPDFAnnot_RemoveObject` alias on
181    /// `EditDocument`.
182    ///
183    /// See also [`AnnotObjectCtxMut::remove_object()`] for the annotation side.
184    #[inline]
185    pub fn remove_object(&mut self, index: usize) -> Result<(), EditError> {
186        self.doc.remove_page_object(self.page_index, index)
187    }
188
189    /// Insert a page object into this page's content stream.
190    ///
191    /// ADR-019 T2 alias for `FPDFPage_InsertObject`.
192    #[inline]
193    pub fn insert_object(&mut self, object: PageObject) -> Result<(), EditError> {
194        self.doc.insert_page_object(self.page_index, object)
195    }
196}
197
198// ── Annotation-object context (immutable) ────────────────────────────────────
199
200/// Immutable context for accessing AP-stream objects on a single annotation.
201///
202/// Obtained via [`EditDocument::annot_objects()`].
203///
204/// This type mirrors [`PageObjectCtx`] on the annotation side, providing the
205/// same upstream-named methods for `FPDFAnnot_*`.
206///
207/// # Example
208/// ```no_run
209/// # use rpdfium_edit::document::EditDocument;
210/// # use rpdfium_parser::object::ObjectId;
211/// # fn example(doc: &EditDocument, annot_id: ObjectId) {
212/// let ctx = doc.annot_objects(annot_id);
213/// let count = ctx.get_object_count();
214/// let obj   = ctx.get_object(0);
215/// # }
216/// ```
217pub struct AnnotObjectCtx<'doc> {
218    /// Borrowed document — AP objects live in `EditDocument::ap_objects`.
219    doc: &'doc EditDocument,
220    /// Which annotation's AP-stream object list to access.
221    annot_id: ObjectId,
222}
223
224impl<'doc> AnnotObjectCtx<'doc> {
225    pub(crate) fn new(doc: &'doc EditDocument, annot_id: ObjectId) -> Self {
226        Self { doc, annot_id }
227    }
228
229    // ── T2 aliases ────────────────────────────────────────────────────────────
230
231    /// Returns a reference to the AP-stream object at `index`.
232    ///
233    /// Returns `None` if `index` is out of bounds or no objects have been
234    /// appended to this annotation.
235    ///
236    /// ADR-019 T2 alias for `FPDFAnnot_GetObject`.  Namespaced here to avoid
237    /// collision with the `FPDFPage_GetObject` alias on `EditDocument`.
238    ///
239    /// See also [`PageObjectCtx::get_object()`] for the page side.
240    #[inline]
241    pub fn get_object(&self, index: usize) -> Option<&'doc PageObject> {
242        self.doc.annotation_object_at(self.annot_id, index)
243    }
244
245    /// Returns the number of AP-stream objects for this annotation.
246    ///
247    /// ADR-019 T2 alias for `FPDFAnnot_GetObjectCount`.
248    #[inline]
249    pub fn get_object_count(&self) -> usize {
250        self.doc.annotation_object_count(self.annot_id)
251    }
252}
253
254// ── Annotation-object context (mutable) ──────────────────────────────────────
255
256/// Mutable context for accessing AP-stream objects on a single annotation.
257///
258/// Obtained via [`EditDocument::annot_objects_mut()`].
259///
260/// # Example
261/// ```no_run
262/// # use rpdfium_edit::{document::EditDocument, page_object::PageObject};
263/// # use rpdfium_parser::object::ObjectId;
264/// # fn example(doc: &mut EditDocument, annot_id: ObjectId, obj: PageObject) {
265/// let mut ctx = doc.annot_objects_mut(annot_id);
266/// ctx.append_object(obj).unwrap();
267/// ctx.remove_object(0).unwrap();
268/// # }
269/// ```
270pub struct AnnotObjectCtxMut<'doc> {
271    /// Mutably borrowed document.
272    doc: &'doc mut EditDocument,
273    /// Which annotation's AP-stream object list to modify.
274    annot_id: ObjectId,
275}
276
277impl<'doc> AnnotObjectCtxMut<'doc> {
278    pub(crate) fn new(doc: &'doc mut EditDocument, annot_id: ObjectId) -> Self {
279        Self { doc, annot_id }
280    }
281
282    // ── read-only aliases ─────────────────────────────────────────────────────
283
284    /// Returns a reference to the AP-stream object at `index`.
285    ///
286    /// ADR-019 T2 alias for `FPDFAnnot_GetObject`.
287    #[inline]
288    pub fn get_object(&self, index: usize) -> Option<&PageObject> {
289        self.doc.annotation_object_at(self.annot_id, index)
290    }
291
292    /// Returns the number of AP-stream objects for this annotation.
293    ///
294    /// ADR-019 T2 alias for `FPDFAnnot_GetObjectCount`.
295    #[inline]
296    pub fn get_object_count(&self) -> usize {
297        self.doc.annotation_object_count(self.annot_id)
298    }
299
300    // ── write aliases ─────────────────────────────────────────────────────────
301
302    /// Remove the AP-stream object at `index` from this annotation.
303    ///
304    /// ADR-019 T2 alias for `FPDFAnnot_RemoveObject`.  Namespaced here to
305    /// avoid collision with the `FPDFPage_RemoveObject` alias on
306    /// `EditDocument`.
307    ///
308    /// See also [`PageObjectCtxMut::remove_object()`] for the page side.
309    #[inline]
310    pub fn remove_object(&mut self, index: usize) -> Result<(), EditError> {
311        self.doc.remove_annotation_object(self.annot_id, index)
312    }
313
314    /// Append a page object to this annotation's AP-stream object list.
315    ///
316    /// ADR-019 T2 alias for `FPDFAnnot_AppendObject`.
317    #[inline]
318    pub fn append_object(&mut self, object: PageObject) -> Result<(), EditError> {
319        self.doc.append_annotation_object(self.annot_id, object)
320    }
321
322    /// Returns a mutable reference to the AP-stream object at `index`.
323    ///
324    /// Returns `None` if `index` is out of bounds.
325    ///
326    /// There is no upstream `FPDFAnnot_GetObjectMut`; mutable access is
327    /// Rust-specific.  This is the canonical way to modify an annotation's
328    /// AP-stream objects in-place.
329    #[inline]
330    pub fn get_object_mut(&mut self, index: usize) -> Option<&mut PageObject> {
331        self.doc.annotation_object_at_mut(self.annot_id, index)
332    }
333}
334
335// ── Entry points on EditDocument ─────────────────────────────────────────────
336
337impl EditDocument {
338    // ── Page-object side ──────────────────────────────────────────────────────
339
340    /// Return an immutable context for the page objects on page `page_index`.
341    ///
342    /// This is the preferred entry point for all `FPDFPage_*` object
343    /// operations.  The returned [`PageObjectCtx`] carries `page_index` so
344    /// you do not have to pass it to every call.
345    ///
346    /// # Why a context type?
347    ///
348    /// `FPDFPage_GetObject` and `FPDFAnnot_GetObject` both strip to the same
349    /// `get_object` name under ADR-019.  Rust cannot have two methods with
350    /// the same name on one struct, so we disambiguate via context types:
351    ///
352    /// ```text
353    /// doc.page_objects(i).get_object(j)   // → FPDFPage_GetObject
354    /// doc.annot_objects(id).get_object(j) // → FPDFAnnot_GetObject
355    /// ```
356    #[inline]
357    pub fn page_objects(&self, page_index: usize) -> PageObjectCtx<'_> {
358        PageObjectCtx::new(self, page_index)
359    }
360
361    /// Return a mutable context for the page objects on page `page_index`.
362    ///
363    /// See [`page_objects()`](Self::page_objects) for background.
364    #[inline]
365    pub fn page_objects_mut(&mut self, page_index: usize) -> PageObjectCtxMut<'_> {
366        PageObjectCtxMut::new(self, page_index)
367    }
368
369    // ── Annotation-object side ────────────────────────────────────────────────
370
371    /// Return an immutable context for the AP-stream objects on annotation
372    /// `annot_id`.
373    ///
374    /// This is the preferred entry point for `FPDFAnnot_*` object operations.
375    ///
376    /// ```text
377    /// doc.annot_objects(id).get_object(j)  // → FPDFAnnot_GetObject
378    /// doc.annot_objects(id).get_object_count() // → FPDFAnnot_GetObjectCount
379    /// ```
380    #[inline]
381    pub fn annot_objects(&self, annot_id: ObjectId) -> AnnotObjectCtx<'_> {
382        AnnotObjectCtx::new(self, annot_id)
383    }
384
385    /// Return a mutable context for the AP-stream objects on annotation
386    /// `annot_id`.
387    ///
388    /// See [`annot_objects()`](Self::annot_objects) for background.
389    #[inline]
390    pub fn annot_objects_mut(&mut self, annot_id: ObjectId) -> AnnotObjectCtxMut<'_> {
391        AnnotObjectCtxMut::new(self, annot_id)
392    }
393}