pdf_engine/xfa.rs
1//! XFA feature surface for `pdf-engine`.
2//!
3//! This module is compiled only with the `xfa` feature and re-exports the
4//! high-level helpers needed to extract and flatten XFA content without making
5//! the base crate depend on the XFA stack by default.
6
7// Copyright (c) 2026 Innovation Trigger B.V.
8//
9// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
10// the PDFluent Commercial Licence. See the LICENSE file in this repository --
11// that file travels with the copy you received, which a URL does not.
12
13use crate::PdfDocument;
14
15pub use pdf_xfa::error::XfaError;
16pub use pdf_xfa::extract::XfaPackets;
17pub use pdf_xfa::session::{
18 XfaCommitOutcome, XfaFieldModel, XfaFieldOption, XfaFieldType, XfaRect, XfaScriptError,
19 XfaScriptErrorKind, XfaScriptReport, XfaScriptsSkipped, XfaSession, XfaSetOutcome, XfaWidget,
20 XfaWriteValue, XfaWritebackReport,
21};
22pub use pdf_xfa::PresenceChange;
23pub use xfa_json::{export_schema, form_tree_to_json, form_tree_to_value, FormData, FormSchema};
24pub use xfa_layout_engine::form::{FormNodeId, FormTree};
25
26/// Extract the XFA packets embedded in a document.
27pub fn extract_packets(document: &PdfDocument) -> Result<XfaPackets, XfaError> {
28 pdf_xfa::extract::extract_xfa(document.pdf())
29}
30
31/// Returns `true` if the document has an XFA template packet.
32///
33/// Only an active `/AcroForm` `/XFA` entry counts here. Orphaned XFA streams
34/// left behind after flattening should not trigger another flatten/render pass.
35/// XFA PDFs without a template are treated as non-XFA (they have no content to
36/// flatten), so this returns `false` for those documents.
37pub fn has_xfa(document: &PdfDocument) -> bool {
38 pdf_xfa::extract::extract_xfa_from_acroform(document.pdf())
39 .is_some_and(|packets| packets.template().is_some())
40}
41
42/// Flatten an XFA document into static PDF bytes.
43pub fn flatten(document: &PdfDocument) -> Result<Vec<u8>, XfaError> {
44 pdf_xfa::flatten_xfa_to_pdf(document.pdf().data().as_ref())
45}
46
47/// [`flatten`] through the process-wide content-addressed cache (#468).
48///
49/// Returns the same bytes as [`flatten`] for the same document; only the cost
50/// differs. The cache key is a hash of the document bytes, so a document that
51/// was mutated (any changed byte) is flattened again and an unchanged one is
52/// not. This is what rendering uses: before it, every `render_page` call on an
53/// XFA form paid the full flatten (117 to 729 ms on the corpus forms).
54pub fn flatten_cached(document: &PdfDocument) -> Result<std::sync::Arc<Vec<u8>>, XfaError> {
55 pdf_xfa::flatten_cache::flatten_cached(document.pdf().data().as_ref())
56}
57
58/// Counters of the flatten cache: hits, misses, entries and bytes held.
59pub use pdf_xfa::flatten_cache::FlattenCacheStats;
60
61/// Counters of the process-wide flatten cache.
62pub fn flatten_cache_stats() -> FlattenCacheStats {
63 pdf_xfa::flatten_cache::stats()
64}
65
66/// Drop every cached flatten. Never needed for correctness; releases memory.
67pub fn clear_flatten_cache() {
68 pdf_xfa::flatten_cache::clear();
69}
70
71/// Flatten an encrypted XFA document, using `password` to open it.
72///
73/// [`flatten`] tries the empty password only, which covers owner-only
74/// encryption and nothing else. See #149.
75pub fn flatten_with_password(document: &PdfDocument, password: &str) -> Result<Vec<u8>, XfaError> {
76 pdf_xfa::flatten::flatten_xfa_to_pdf_with_password(document.pdf().data().as_ref(), password)
77}