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}