1pub mod acroform;
46pub mod annotations;
47pub mod arc;
48pub mod attachments;
49pub mod decrypt;
50pub mod encrypt;
51pub mod error;
52pub mod info;
53pub mod linearize;
54pub mod objects;
55pub mod operators;
56pub mod outline;
57pub mod page;
58pub mod pubsec;
59pub mod reader;
60pub mod resources;
61pub mod sig;
62pub mod writer;
63mod zlib;
64
65pub use acroform::{
66 write_pdf_with_form, FieldJustification, FormField, FormFieldCheckbox, FormFieldChoice,
67 FormFieldRadioGroup, FormFieldSignature, FormFieldText, RadioOption,
68};
69pub use annotations::{
70 write_pdf_with_annotations, Annotation, AnnotationKind as WriterAnnotationKind, CaretSymbol,
71 FixedPrintSpec, FreeTextQuadding, SoundEncoding,
72};
73pub use attachments::{
74 write_pdf_with_annotations_and_attachments, write_pdf_with_attachments, AfRelationship,
75 Attachment,
76};
77pub use error::PdfError;
78pub use outline::{LinkAnnotationSpec, LinkTarget, OutlineDestination, OutlineSpec};
79pub use pubsec::{
80 open_with_certificate, open_with_certificate_and_trust_store,
81 open_with_certificate_and_trust_store_with_permissions, open_with_certificate_with_permissions,
82 CertRef, KariRecipient, PubSecCfGroup, PubSecCredential, PubSecEncoderConfig,
83 PubSecEncryptionState, PubSecKariConfig, PubSecMatch, PubSecMultiCfConfig, PubSecRecipient,
84 PubSecSubFilter, TrustStore,
85};
86pub use reader::doc_timestamps as read_pdf_doc_timestamps;
87pub use reader::{
88 actions as read_pdf_actions, annotations as read_pdf_annotations,
89 attachments as read_pdf_attachments, extract_text as extract_pdf_text,
90 extract_text_marked as extract_pdf_text_marked, image_xobjects as read_pdf_image_xobjects,
91 inline_images as read_pdf_inline_images, links as read_pdf_links, outline as read_pdf_outline,
92 parse_linearization_dict, pdfa_signals as read_pdf_pdfa_signals,
93 read_in_logical_order as read_pdf_in_logical_order, read_pdf_to_scene,
94 read_pdf_to_scene_with_certificate, read_pdf_to_scene_with_certificate_and_trust_store,
95 read_pdf_to_scene_with_password, signatures as read_pdf_signatures,
96 signed_bytes as pdf_signed_bytes, verify_hierarchy as verify_pdf_hierarchy, ActionKind,
97 ActionTrigger, AnnotationAppearance, AnnotationKind, ColorSpace, FixedPrint, HierarchyIssue,
98 HierarchyReport, InlineImageFilter, IssueSeverity, LayoutMode, LinearizationParams,
99 MarkedTextRun, MovieActivation, OutlineNode, PdfACatalogSignals, PdfAConformance, PdfAction,
100 PdfAnnotation, PdfAttachment, PdfDocTimestamp, PdfImageXObject, PdfInlineImage, PdfLink,
101 PdfLinkTarget, PdfMarkedTextExtraction, PdfOutline, PdfSignature, PdfTextExtraction,
102 ReadingOrderText, TextMarkupVariant, TextRenderMode, TextRun, ThreeDActivation,
103 ThreeDViewSelector, XmpPacket,
104};
105pub use sig::{
106 add_document_timestamp, build_tst_info, pkcs7_wrap_signed_data, sign_pdf_from_scene,
107 wrap_tst_in_signed_data, EcdsaP256Sha256Signer, MessageImprint, MockTsaSigner,
108 RsaPkcs1v15Sha256Signer, SigWriter, Signer, SignerIdentity, SigningAlgorithm, TsaSigner,
109 OID_CT_TST_INFO,
110};
111pub use writer::{
112 write_pdf, write_pdf_from_scene, write_pdf_from_scene_encrypted,
113 write_pdf_from_scene_linearized, write_pdf_from_scene_object_stream,
114 write_pdf_from_scene_object_stream_encrypted, write_pdf_from_scene_pubsec_encrypted,
115 write_pdf_from_scene_pubsec_kari, write_pdf_from_scene_pubsec_multi_cf,
116 write_pdf_from_scene_with_outlines, write_pdf_from_scene_with_outlines_and_links,
117 write_pdf_from_scene_with_xmp, write_pdf_from_scene_xref_stream, write_pdf_incremental_update,
118};
119
120use oxideav_core::{
121 CodecCapabilities, CodecId, CodecInfo, CodecParameters, CodecRegistry, ContainerRegistry,
122 Encoder, Error, Frame, MediaType, Muxer, Packet, Result, RuntimeContext, StreamInfo, TimeBase,
123 WriteSeek,
124};
125
126pub const CODEC_ID_STR: &str = "pdf";
130
131struct PdfEncoder {
134 output_params: CodecParameters,
135 pending: Option<Vec<u8>>,
136 eof: bool,
137}
138
139fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
140 let mut output_params = params.clone();
141 output_params.media_type = MediaType::Video;
142 output_params.codec_id = CodecId::new(CODEC_ID_STR);
143 Ok(Box::new(PdfEncoder {
144 output_params,
145 pending: None,
146 eof: false,
147 }))
148}
149
150impl Encoder for PdfEncoder {
151 fn codec_id(&self) -> &CodecId {
152 &self.output_params.codec_id
153 }
154
155 fn output_params(&self) -> &CodecParameters {
156 &self.output_params
157 }
158
159 fn send_frame(&mut self, frame: &Frame) -> Result<()> {
160 match frame {
161 Frame::Vector(v) => {
162 let bytes = write_pdf(v).map_err(Error::from)?;
163 self.pending = Some(bytes);
164 Ok(())
165 }
166 _ => Err(Error::invalid(
167 "PDF encoder: only vector frames are accepted (Frame::Vector)",
168 )),
169 }
170 }
171
172 fn receive_packet(&mut self) -> Result<Packet> {
173 if let Some(bytes) = self.pending.take() {
174 let mut pkt = Packet::new(0, TimeBase::new(1, 1), bytes);
175 pkt.flags.keyframe = true;
176 return Ok(pkt);
177 }
178 if self.eof {
179 return Err(Error::Eof);
180 }
181 Err(Error::NeedMore)
182 }
183
184 fn flush(&mut self) -> Result<()> {
185 self.eof = true;
186 Ok(())
187 }
188}
189
190struct PdfMuxer {
193 output: Box<dyn WriteSeek>,
194 written_packet: bool,
195}
196
197fn open_muxer(output: Box<dyn WriteSeek>, _streams: &[StreamInfo]) -> Result<Box<dyn Muxer>> {
198 Ok(Box::new(PdfMuxer {
199 output,
200 written_packet: false,
201 }))
202}
203
204impl Muxer for PdfMuxer {
205 fn format_name(&self) -> &str {
206 "pdf"
207 }
208
209 fn write_header(&mut self) -> Result<()> {
210 Ok(())
214 }
215
216 fn write_packet(&mut self, packet: &Packet) -> Result<()> {
217 if self.written_packet {
218 return Err(Error::invalid(
219 "PDF muxer: round-1 supports a single page; got more than one packet",
220 ));
221 }
222 use std::io::Write;
223 self.output.write_all(&packet.data)?;
224 self.written_packet = true;
225 Ok(())
226 }
227
228 fn write_trailer(&mut self) -> Result<()> {
229 Ok(())
231 }
232}
233
234pub fn register_codecs(reg: &mut CodecRegistry) {
238 let caps = CodecCapabilities::video("pdf_sw")
239 .with_intra_only(true)
240 .with_lossless(true);
241 reg.register(
242 CodecInfo::new(CodecId::new(CODEC_ID_STR))
243 .capabilities(caps)
244 .encoder(make_encoder),
245 );
246}
247
248pub fn register_containers(reg: &mut ContainerRegistry) {
250 reg.register_muxer("pdf", open_muxer);
251 reg.register_extension("pdf", "pdf");
252}
253
254pub fn register(ctx: &mut RuntimeContext) {
261 register_codecs(&mut ctx.codecs);
262 register_containers(&mut ctx.containers);
263}
264
265oxideav_core::register!("pdf", register);
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn registration_adds_encoder_and_muxer() {
273 let mut ctx = RuntimeContext::new();
274 register(&mut ctx);
275 assert!(ctx.codecs.has_encoder(&CodecId::new(CODEC_ID_STR)));
276 assert!(ctx.containers.muxer_names().any(|n| n == "pdf"));
277 }
278
279 #[test]
280 fn register_via_runtime_context_installs_both_sides() {
281 let mut ctx = RuntimeContext::new();
282 register(&mut ctx);
283 let id = CodecId::new(CODEC_ID_STR);
284 assert!(
285 ctx.codecs.has_encoder(&id),
286 "PDF encoder factory not installed via RuntimeContext"
287 );
288 assert_eq!(
289 ctx.containers.container_for_extension("pdf"),
290 Some("pdf"),
291 "PDF container extension not installed via RuntimeContext"
292 );
293 }
294}