【printpdf】readme.md文件详解

printpdf 是一个用于创建、读取、写入和渲染 PDF 文档的 Rust 库。

Website | Crates.io | Documentation | Donate

!IMPORTANT

HTML 到 PDF 的渲染功能仍处于实验阶段(存根 API),并且正在开发中。

目前,您需要手动定位 PDF 元素。

特性

  • 页面、书签、链接注释(读取 / 写入)
  • 图层(读取 / 写入)
  • 图形:线条、形状、贝塞尔曲线、SVG 内容(读取 / 写入)
  • 图像编码 / 解码(读取支持:实验性 / 写入支持需使用 image 库)
  • 嵌入式字体、Unicode 支持(读取支持:实验性 / 写入支持)
  • 最小化文件大小(自动子集化字体)
  • 高级图形 - 叠印、混合等
  • 高级排版 - 字符 / 单词缩放和间距、上标、下标等
  • 嵌入 SVG(内部使用 svg2pdf 库)

实验性特性:

  • 将 PDF 页面渲染为 SVG(每页一个独立的 SVG 文件)
    • 您可以使用生成的 SVG,通过 resvg 将 PDF 页面渲染为图像
  • 从 PDF 页面提取文本(自动解码 Unicode、换行符和文本位置)
  • 用于简单页面布局的最小化 XHTML 布局(使用 azul-layout + kuchiki HTML 解析器)
    • 目前这仅是一个存根 API:它可以编译,但不会产生可用的输出
    • azul-layout 获得更好的 HTML 求解器时,HTML 到 PDF 的管道将得到改进

不支持的功能:

  • 渐变
  • 图案
  • 文件附件
  • 开放印前接口
  • 半色调图像
  • 各种 PDF 标准的符合性 / 错误检查
  • 嵌入式 JavaScript

实时 WASM32 演示请参见:https://fschutt.github.io/printpdf

编写 PDF

基础示例

rust 复制代码
use printpdf::*;

fn main() {
    let mut doc = PdfDocument::new("My first PDF");
    let page1_contents = vec![Op::Marker { id: "debugging-marker".to_string() }];
    let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents);
    let pdf_bytes: Vec<u8> = doc
        .with_pages(vec![page1])
        .save(&PdfSaveOptions::default());
}

图形

rust 复制代码
use printpdf::*;

fn main() {
    let mut doc = PdfDocument::new("My first PDF");

    let line = Line {
        // 二次形状。"false" 决定下一个(后续)点是否是贝塞尔控制点(用于曲线)
        // 如果您想要孔洞,只需将点的缠绕顺序从顺时针重新排序为逆时针。
        points: vec![
            (Point::new(Mm(100.0), Mm(100.0)), false),
            (Point::new(Mm(100.0), Mm(200.0)), false),
            (Point::new(Mm(300.0), Mm(200.0)), false),
            (Point::new(Mm(300.0), Mm(100.0)), false),
        ],
        is_closed: true,
    };
    
    // 三角形形状
    let polygon = Polygon {
        rings: vec![vec![
            (Point::new(Mm(150.0), Mm(150.0)), false),
            (Point::new(Mm(150.0), Mm(250.0)), false),
            (Point::new(Mm(350.0), Mm(250.0)), false),
        ]],
        mode: PaintMode::FillStroke,
        winding_order: WindingOrder::NonZero,
    };
    
    // 图形配置
    let fill_color = Color::Cmyk(Cmyk::new(0.0, 0.23, 0.0, 0.0, None));
    let outline_color = Color::Rgb(Rgb::new(0.75, 1.0, 0.64, None));
    let mut dash_pattern = LineDashPattern::default();
    dash_pattern.dash_1 = Some(20);

    let extgstate = ExtendedGraphicsStateBuilder::new()
        .with_overprint_stroke(true)
        .with_blend_mode(BlendMode::multiply())
        .build();
    
    let page1_contents = vec![
        // 添加 line1(正方形)
        Op::SetOutlineColor { col: Color::Rgb(Rgb::new(0.75, 1.0, 0.64, None)) },
        Op::SetOutlineThickness { pt: Pt(10.0) },
        Op::DrawLine { line: line },
    
        // 添加 line2(三角形)
        Op::SaveGraphicsState,
        Op::LoadGraphicsState { gs: doc.add_graphics_state(extgstate) },
        Op::SetLineDashPattern { dash: dash_pattern },
        Op::SetLineJoinStyle { join: LineJoinStyle::Round },
        Op::SetLineCapStyle { cap: LineCapStyle::Round },
        Op::SetFillColor { col: fill_color },
        Op::SetOutlineThickness { pt: Pt(15.0) },
        Op::SetOutlineColor { col: outline_color },
        Op::DrawPolygon { polygon: polygon },
        Op::RestoreGraphicsState,
    ];
    
    let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents);
    let pdf_bytes: Vec<u8> = doc
        .with_pages(vec![page1])
        .save(&PdfSaveOptions::default());
}

图像

  • 图像仅在发布模式下被压缩。在调试模式下,您可能会得到巨大的 PDF(6 MB 或更多)。
  • 为了使这个过程更快,请使用 BufReader 而不是直接从文件读取。
  • 图像的缩放是隐式完成的,以适应 300 dpi 下的一像素等于一点。
rust 复制代码
use printpdf::*;

fn main() {
    let mut doc = PdfDocument::new("My first PDF");
    let image_bytes = include_bytes!("assets/img/BMP_test.bmp");
    let image = RawImage::decode_from_bytes(image_bytes).unwrap(); // 需要 --feature bmp
    
    // 在 PDF 中,图像是一个 `XObject`,由唯一的 `ImageId` 标识
    let image_xobject_id = doc.add_image(image);

    let page1_contents = vec![
        Op::UseXobject { 
            id: image_xobject_id.clone(), 
            transform: XObjectTransform::default() 
        }
    ];

    let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents);
    let pdf_bytes: Vec<u8> = doc
        .with_pages(vec![page1])
        .save(&PdfSaveOptions::default());
}

字体

rust 复制代码
use printpdf::*;

fn main() {
    let mut doc = PdfDocument::new("My first PDF");

    let roboto_bytes = include_bytes!("assets/fonts/RobotoMedium.ttf").unwrap()
    let font_index = 0;
    let mut warnings = Vec::new();
    let font = ParsedFont::from_bytes(&roboto_bytes, font_index, &mut warnings).unwrap();

    // 如果您需要自定义文本整形(内部使用 `allsorts` 字体整形器)
    // let glyphs = font.shape(text);

    // printpdf 自动跟踪 PDF 中使用的字体
    let font_id = doc.add_font(&font);

    let text_pos = Point {
        x: Mm(10.0).into(),
        y: Mm(100.0).into(),
    }; // 从左下角开始

    let page1_contents = vec![
        Op::SetLineHeight { lh: Pt(33.0) },
        Op::SetWordSpacing { pt: Pt(33.0) },
        Op::SetCharacterSpacing { multiplier: 10.0 },
        Op::SetTextCursor { pos: text_pos },
        // Op::WriteCodepoints { ... }
        // Op::WriteCodepointsWithKerning { ... }
        Op::WriteText {
            items: vec![TextItem::Text("Lorem ipsum".to_string())],
            font: font_id.clone(),
        },
        Op::AddLineBreak,
        Op::WriteText {
            items: vec![TextItem::Text("dolor sit amet".to_string())],
            font: font_id.clone(),
        },
        Op::AddLineBreak,
    ];

    let save_options = PdfSaveOptions {
        subset_fonts: true, // 保存时自动子集化字体
        ..Default::default()
    };

    let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents);
    let mut warnings = Vec::new();
    let pdf_bytes: Vec<u8> = doc
        .with_pages(vec![page1])
        .save(&save_options, &mut warnings);
}

表格、HTML

为了创建表格等,printpdf 使用了一个基本的布局系统,类似于 wkhtmltopdf(尽管在功能方面更有限)。它对于基本的页面布局、书籍渲染和报告 / 表单 / 等来说已经足够好。包括自动分页。

由于 printpdf 支持 WASM,因此有一个交互式演示在 https://fschutt.github.io/printpdf - 尝试使用 XML。

有关 XML 语法描述,请参见 SYNTAX.md

rust 复制代码
// 需要 --features="html"
use printpdf::*;

fn main() {

    // 参见 https://fschutt.github.io/printpdf 获取交互式 WASM 演示!

    let html = r#"
    <html>

        <!-- printpdf 自动将内容分页 -->
        <body style="padding:10mm">
            <p style="color: red; font-family: sans-serif;" data-chapter="1" data-subsection="First subsection">Hello!</p>
            <div style="width:200px;height:200px;background:red;" data-chapter="1" data-subsection="Second subsection">
                <p>World!</p>
            </div>
        </body>

        <!-- 为每个页面配置页眉和页脚 -->
        <head>
            <header>
                <h4 style="color: #2e2e2e;min-height: 8mm;">Chapter {attr:chapter} * {attr:subsection}</h4>
                <p style="position: absolute;top:5mm;left:5mm;">{builtin:pagenum}</p>
            </header>

            <footer>
                <hr/>
            <footer/>
        </head>
    </html>
    "#;

    let options = XmlRenderOptions {
        // 在 HTML 中使用的命名图像,例如 ["image1.png" => DecodedImage(image1_bytes)]
        images: BTreeMap::new(),
        // 在 HTML 中使用的命名字体,例如 ["Roboto" => DecodedImage(roboto_bytes)]
        fonts: BTreeMap::new(),
        // 默认页面宽度,printpdf 将自动分页
        page_width: Mm(210.0),
        // 默认页面高度
        page_height: Mm(297.0),
    };

    let pdf_bytes = PdfDocument::new("My PDF")
        .with_html(html, &options).unwrap()
        .save(&PdfSaveOptions::default());
}

路线图

printpdf 的目标是成为一个通用 PDF 库,例如 libharu 或类似的库。由 printpdf 生成的 PDF 应始终遵循 PDF 标准,除非您将其关闭。目前,仅涵盖了标准 PDF/X-3:2002(即根据 Adobe Acrobat 的有效 PDF)。随着时间的推移,将支持更多标准。

printpdf 维基实时更新于:https://github.com/fschutt/printpdf/wiki

以下是我在开发这个库时找到的一些资源:

许可证 / 支持

本库采用 MIT 许可证授权。

您可以在 https://github.com/sponsors/fschutt 进行捐赠(一次性或定期)。谢谢!

相关推荐
啊Q老师3 小时前
Rust:异步编程与并发安全的深度实践
rust·并发安全·异步编程·深度实践
RustFS3 小时前
K3s x RustFS,边缘场景下的云原生存储解决之道
rust
G_dou_3 小时前
rust:猜数字小游戏
rust
长存祈月心3 小时前
Rust HashSet 与 BTreeSet深度剖析
开发语言·后端·rust
长存祈月心3 小时前
Rust BTreeMap 红黑树
开发语言·后端·rust
颜颜yan_4 小时前
Rust impl块的组织方式:从基础到实践的深度探索
开发语言·后端·rust
代码改善世界4 小时前
Rust 入门基础:安全、并发与高性能的系统编程语言
开发语言·安全·rust
JMzz4 小时前
Rust 中的数据结构选择与性能影响:从算法复杂度到硬件特性 [特殊字符]
开发语言·数据结构·后端·算法·性能优化·rust
向上的车轮5 小时前
Actix Web 入门与实战
前端·rust·actix web