【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 页面渲染为图像
- 您可以使用生成的 SVG,通过
- 从 PDF 页面提取文本(自动解码 Unicode、换行符和文本位置)
- 用于简单页面布局的最小化 XHTML 布局(使用
azul-layout+kuchikiHTML 解析器)- 目前这仅是一个存根 API:它可以编译,但不会产生可用的输出
- 当
azul-layout获得更好的 HTML 求解器时,HTML 到 PDF 的管道将得到改进
不支持的功能:
- 渐变
- 图案
- 文件附件
- 开放印前接口
- 半色调图像
- 各种 PDF 标准的符合性 / 错误检查
- 嵌入式 JavaScript
实时 WASM32 演示请参见:https://fschutt.github.io/printpdf
编写 PDF
基础示例
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());
}
图形
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 下的一像素等于一点。
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());
}
字体
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。
// 需要 --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
以下是我在开发这个库时找到的一些资源:
PDFXPlorer,显示 PDF 的 DOM 树,需要 .NET 2.0- 官方 PDF 1.7 参考
- [德语] 如何在 PDF 中嵌入 Unicode 字体
- PDF X/1-a 验证器
- PDF X/3 技术说明
许可证 / 支持
本库采用 MIT 许可证授权。
您可以在 https://github.com/sponsors/fschutt 进行捐赠(一次性或定期)。谢谢!
