Skip to main content

pdfxml/
lib.rs

1//! 这个文件可以理解成“库的总入口”。
2//!
3//! 别的 Rust 项目接入 `pdfxml` 时,最先接触到的通常就是这里。
4//! 因为这里决定了两件事:
5//! - 哪些模块对外公开
6//! - 外部最常用的类型和函数有哪些
7//!
8//! 如果你只是想会用这个库,不想一下子钻进所有实现细节,
9//! 那直接从这个文件看就够了。
10//!
11//! 你可以把这里当成“对外使用说明 + API 门面”:
12//! - 想读 XFDF:看 [`load_xfdf`]
13//! - 想从 PDF 读注释:看 [`load_annotations_from_pdf`]
14//! - 想把注释导出成 PDF:看 [`export_annotations`]
15//! - 想自己细调导出行为:看 [`PdfAnnotationExporter`]
16//!
17//! 建议阅读顺序:
18//! 1. 先看下面的 `pub mod ...`,知道项目分成哪几块
19//! 2. 再看 `pub use ...`,知道外部能直接拿到哪些 API
20//! 3. 最后看几个顶层函数,理解最常见调用流程
21
22/// 注释数据结构模块。
23///
24/// 这里定义了文本注释、高亮、线条、图章等 Rust 类型。
25/// 如果你想在代码里直接查看或修改注释内容,通常会用到这里导出的类型。
26pub mod annotation;
27
28/// 统一错误类型模块。
29///
30/// 读取文件、解析 XML、处理 PDF 失败时,
31/// 最后都会尽量统一收口到这里定义的错误类型。
32pub mod error;
33
34/// PDF 读写模块。
35///
36/// 这里放的是把注释真正写进 PDF、或者从 PDF 里读回注释的核心逻辑。
37pub mod pdf;
38
39/// XFDF/XML 解析模块。
40///
41/// 它负责把 XFDF 字符串解析成 Rust 结构,
42/// 也负责把 Rust 结构重新写回 XFDF 字符串。
43pub mod xfdf;
44
45pub use annotation::*;
46pub use error::{PdfXmlError, Result};
47pub use pdf::PdfAnnotationExporter;
48pub use xfdf::{XfdfDocument, XfdfField};
49
50use std::fs;
51use std::path::Path;
52
53/// 从磁盘读取一个 XFDF 文件,并解析成 [`XfdfDocument`]。
54///
55/// 可以把它理解成“两步合成一步”:
56/// 1. 先把文件内容读出来
57/// 2. 再把读到的 XML 解析成程序能直接使用的数据结构
58///
59/// # 示例
60///
61/// ```no_run
62/// use pdfxml::load_xfdf;
63///
64/// let doc = load_xfdf("examples/sample.xfdf")?;
65/// assert!(!doc.annotations.is_empty());
66/// # Ok::<(), pdfxml::PdfXmlError>(())
67/// ```
68pub fn load_xfdf(path: impl AsRef<Path>) -> Result<XfdfDocument> {
69    let content = fs::read_to_string(path)
70        .map_err(|e| PdfXmlError::PdfProcessing(format!("读取 XFDF 文件失败: {}", e)))?;
71    XfdfDocument::parse(&content)
72}
73
74/// 从 PDF 中读取注释,并转换成项目统一使用的 [`XfdfDocument`]。
75///
76/// 这样做的好处是:
77/// - 后面可以继续复用同一套数据结构
78/// - 测试和导出 XFDF 的逻辑也都能继续复用
79///
80/// # 示例
81///
82/// ```no_run
83/// use pdfxml::load_annotations_from_pdf;
84///
85/// let doc = load_annotations_from_pdf("annotated.pdf")?;
86/// println!("{}", doc.annotations.len());
87/// # Ok::<(), pdfxml::PdfXmlError>(())
88/// ```
89pub fn load_annotations_from_pdf(path: impl AsRef<Path>) -> Result<XfdfDocument> {
90    let mut exporter = PdfAnnotationExporter::new();
91    exporter.load_annotations_from_pdf(path.as_ref())
92}
93
94/// 把 PDF 里的注释直接导出成一个标准 XFDF 文件。
95///
96/// # 示例
97///
98/// ```no_run
99/// use pdfxml::export_pdf_annotations_to_xfdf;
100///
101/// export_pdf_annotations_to_xfdf("annotated.pdf", "exported.xfdf")?;
102/// # Ok::<(), pdfxml::PdfXmlError>(())
103/// ```
104pub fn export_pdf_annotations_to_xfdf(
105    input_pdf: impl AsRef<Path>,
106    output_xfdf: impl AsRef<Path>,
107) -> Result<()> {
108    let xfdf_doc = load_annotations_from_pdf(input_pdf)?;
109    let xml = xfdf_doc.to_xfdf_string()?;
110    fs::write(output_xfdf, xml)
111        .map_err(|e| PdfXmlError::PdfProcessing(format!("写入 XFDF 文件失败: {}", e)))?;
112    Ok(())
113}
114
115/// 把已经解析好的注释导出成 PDF。
116///
117/// 规则很简单:
118/// - 如果给了 `target_pdf`,就把注释合并进已有 PDF
119/// - 如果没有给 `target_pdf`,就新建一个 PDF 来放这些注释
120///
121/// 这个函数适合直接给 SDK 调用方使用。
122/// 如果你需要更细的控制,也可以直接使用 [`PdfAnnotationExporter`]。
123///
124/// # 示例
125///
126/// ```no_run
127/// use pdfxml::{export_annotations, XfdfDocument};
128///
129/// let doc = XfdfDocument::parse(r#"
130/// <?xml version="1.0" encoding="UTF-8" ?>
131/// <xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
132///   <annots>
133///     <text page="0" rect="100,700,250,730">Hello</text>
134///   </annots>
135/// </xfdf>
136/// "#)?;
137/// export_annotations(&doc, Option::<&str>::None, "output.pdf")?;
138/// # Ok::<(), pdfxml::PdfXmlError>(())
139/// ```
140pub fn export_annotations(
141    xfdf_doc: &XfdfDocument,
142    target_pdf: Option<impl AsRef<Path>>,
143    output_path: impl AsRef<Path>,
144) -> Result<()> {
145    let mut exporter = PdfAnnotationExporter::new();
146    match target_pdf {
147        Some(target_pdf) => {
148            exporter.export_to_existing_pdf(xfdf_doc, target_pdf.as_ref(), output_path.as_ref())
149        }
150        None => exporter.export_to_new_pdf(xfdf_doc, output_path.as_ref()),
151    }
152}