33个版本

0.7.0 2024年1月24日
0.6.0 2023年9月13日
0.5.3 2022年8月20日
0.5.2 2022年3月10日
0.1.1 2017年7月20日

#42 in 文本处理

Download history 2132/week @ 2024-04-21 1962/week @ 2024-04-28 2153/week @ 2024-05-05 2519/week @ 2024-05-12 2207/week @ 2024-05-19 2511/week @ 2024-05-26 2380/week @ 2024-06-02 2197/week @ 2024-06-09 2151/week @ 2024-06-16 2014/week @ 2024-06-23 1875/week @ 2024-06-30 1983/week @ 2024-07-07 2633/week @ 2024-07-14 2422/week @ 2024-07-21 4188/week @ 2024-07-28 3582/week @ 2024-08-04

12,946 每月下载量
36 个crates中使用 (27 直接使用)

MIT 协议

4MB
5.5K SLoC

printpdf

Travis CI Appveyor Dependencies

printpdf 是一个用于创建可打印PDF文档的库。

Crates.io | 文档

[dependencies]
printpdf = "0.5.0"

功能

目前,printpdf只能写入文档,不能读取。

  • 页面生成
  • 图层(类似于Illustrator的图层)
  • 图形(线条、形状、贝塞尔曲线)
  • 图像(目前仅支持BMP/PNG/JPG或生成自己的图像)
  • 嵌入字体(TTF和OTF)并支持Unicode
  • 高级图形 - 印刷控制、混合模式等
  • 高级排版 - 字符缩放、字符间距、上标、下标、轮廓等
  • PDF图层(您应该能够在Illustrator中打开PDF,并看到图层出现)

入门

编写PDF

简单页面

use printpdf::*;
use std::fs::File;
use std::io::BufWriter;

let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
let (page2, layer1) = doc.add_page(Mm(10.0), Mm(250.0),"Page 2, Layer 1");

doc.save(&mut BufWriter::new(File::create("test_working.pdf").unwrap())).unwrap();

添加图形形状

use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
use std::iter::FromIterator;

let (doc, page1, layer1) = PdfDocument::new("printpdf graphics test", Mm(297.0), Mm(210.0), "Layer 1");
let current_layer = doc.get_page(page1).get_layer(layer1);

// Quadratic shape. The "false" determines if the next (following)
// point is a bezier handle (for curves)
// If you want holes, simply reorder the winding of the points to be
// counterclockwise instead of clockwise.
let points1 = 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 the shape stroked? Is the shape closed? Is the shape filled?
let line1 = Line {
    points: points1,
    is_closed: true,
    has_fill: true,
    has_stroke: true,
    is_clipping_path: false,
};

// Triangle shape
// Note: Line is invisible by default, the previous method of
// constructing a line is recommended!
let mut line2 = Line::from_iter(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)]);

line2.set_stroke(true);
line2.set_closed(false);
line2.set_fill(false);
line2.set_as_clipping_path(false);

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);

current_layer.set_fill_color(fill_color);
current_layer.set_outline_color(outline_color);
current_layer.set_outline_thickness(10.0);

// Draw first line
current_layer.add_shape(line1);

let fill_color_2 = Color::Cmyk(Cmyk::new(0.0, 0.0, 0.0, 0.0, None));
let outline_color_2 = Color::Greyscale(Greyscale::new(0.45, None));

// More advanced graphical options
current_layer.set_overprint_stroke(true);
current_layer.set_blend_mode(BlendMode::Seperable(SeperableBlendMode::Multiply));
current_layer.set_line_dash_pattern(dash_pattern);
current_layer.set_line_cap_style(LineCapStyle::Round);

current_layer.set_fill_color(fill_color_2);
current_layer.set_outline_color(outline_color_2);
current_layer.set_outline_thickness(15.0);

// draw second line
current_layer.add_shape(line2);

添加图像

注意:图像仅在发布模式下进行压缩。在调试模式下,您可能会得到非常大的PDF文件(6MB或更多)。在发布模式下,压缩会使这些文件变得更小(约100-200KB)。

为了使此过程更快,请使用 BufReader 而不是直接从文件读取。图像目前不是最高优先级。

图像缩放隐式地以适应每像素 = 300 dpi下的一个点。

extern crate printpdf;

// imports the `image` library with the exact version that we are using
use printpdf::*;

use std::convert::From;
use std::fs::File;

fn main() {
    let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
    let current_layer = doc.get_page(page1).get_layer(layer1);

    // currently, the only reliable file formats are bmp/jpeg/png
    // this is an issue of the image library, not a fault of printpdf
    let mut image_file = File::open("assets/img/BMP_test.bmp").unwrap();
    let image = Image::try_from(image_crate::codecs::bmp::BmpDecoder::new(&mut image_file).unwrap()).unwrap();

    // translate x, translate y, rotate, scale x, scale y
    // by default, an image is optimized to 300 DPI (if scale is None)
    // rotations and translations are always in relation to the lower left corner
    image.add_to_layer(current_layer.clone(), ImageTransform::default());

    // you can also construct images manually from your data:
    let mut image_file_2 = ImageXObject {
        width: Px(200),
        height: Px(200),
        color_space: ColorSpace::Greyscale,
        bits_per_component: ColorBits::Bit8,
        interpolate: true,
        /* put your bytes here. Make sure the total number of bytes =
           width * height * (bytes per component * number of components)
           (e.g. 2 (bytes) x 3 (colors) for RGB 16bit) */
        image_data: Vec::new(),
        image_filter: None, /* does not work yet */
        clipping_bbox: None, /* doesn't work either, untested */
    };

    let image2 = Image::from(image_file_2);
}

添加字体

注意:字体在页面之间是共享的。这意味着它们首先添加到文档中,然后可以将对该对象的引用传递到多个页面。这与图像不同,例如,图像只能在其创建的页面上使用一次(因为这是最常见的用例)。

use printpdf::*;
use std::fs::File;

let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
let current_layer = doc.get_page(page1).get_layer(layer1);

let text = "Lorem ipsum";
let text2 = "unicode: стуфхfцчшщъыьэюя";

let font = doc.add_external_font(File::open("assets/fonts/RobotoMedium.ttf").unwrap()).unwrap();
let font2 = doc.add_external_font(File::open("assets/fonts/RobotoMedium.ttf").unwrap()).unwrap();

// text, font size, x from left edge, y from bottom edge, font
current_layer.use_text(text, 48.0, Mm(200.0), Mm(200.0), &font);

// For more complex layout of text, you can use functions
// defined on the PdfLayerReference
// Make sure to wrap your commands
// in a `begin_text_section()` and `end_text_section()` wrapper
current_layer.begin_text_section();

    // setup the general fonts.
    // see the docs for these functions for details
    current_layer.set_font(&font2, 33.0);
    current_layer.set_text_cursor(Mm(10.0), Mm(10.0));
    current_layer.set_line_height(33.0);
    current_layer.set_word_spacing(3000.0);
    current_layer.set_character_spacing(10.0);
    current_layer.set_text_rendering_mode(TextRenderingMode::Stroke);

    // write two lines (one line break)
    current_layer.write_text(text.clone(), &font2);
    current_layer.add_line_break();
    current_layer.write_text(text2.clone(), &font2);
    current_layer.add_line_break();

    // write one line, but write text2 in superscript
    current_layer.write_text(text.clone(), &font2);
    current_layer.set_line_offset(10.0);
    current_layer.write_text(text2.clone(), &font2);

current_layer.end_text_section();

变更日志

查看CHANGELOG.md文件。

进一步阅读

PdfDocument 被隐藏在 PdfDocumentReference 后面,它通过外观封装来锁定你可以做的事情。几乎所有的函数都操作 PdfLayerReference,所以你应该在那里寻找现有的函数或者实现新函数。 PdfDocumentReference 是一个引用计数的文档。它使用页面和层进行内部可变性,因为我遇到了文档的借用问题。 重要: 所有会改变文档状态的函数,“借用”文档以可变方式在函数期间使用。确保不要两次借用文档(如果你这样做,你的程序将会崩溃)。我已尽可能地阻止这种情况发生,通过使文档只对该crate公开,你不能从库外锁定它。

在使用之前,必须将图像添加到页面资源中。这意味着,你只能在添加图像的页面上使用它。否则,你可能会得到一个损坏的PDF。

字体通过 freetype 嵌入。该仓库中有一个 rusttype 分支,但 rusttype 无法正确获取未缩放的字体的高度,因此你现在必须使用 freetype

如果有任何问题,请报告,尤其是如果你看到 BorrowMut 错误(它们不应该发生)。由于 freetyperusttype 都无法可靠地读取字距调整数据,因此目前没有执行字距调整。然而,“正确”的字距调整/放置需要一个完整的字体形状引擎等。这将是一个完全不同的项目。

要了解PDF实际上是如何制作的,请阅读wiki(目前尚未完全完成)。当我开始制作这个库时,这些资源在别处都不可用,因此我希望帮助其他人了解这些主题。阅读wiki是想要为这个库做出贡献的必要条件。

目标和路线图

printpdf 的目标是成为一个通用PDF库,类似于 libharu 或类似库。由 printpdf 生成的PDF应始终遵循PDF标准,除非你将其关闭。目前,仅覆盖标准 PDF/X-3:2002(即根据Adobe Acrobat有效的PDF)。随着时间的推移,将支持更多的标准。目前对PDF进行错误检查的功能只是一个占位符。

计划的功能 / 未完成

以下功能尚未实现,大多数

  • 剪裁
  • 对齐/布局文本
  • Open Prepress Interface
  • 半色调图像、渐变、图案
  • SVG / 实例化内容
  • 表单、注释
  • 书签/目录
  • 各种PDF标准的符合性/错误检查
  • 嵌入式JavaScript
  • 读取PDF
  • 完成 printpdf wiki

测试

当前的测试几乎不存在,因为PDF很难测试。这应该随着时间的推移而改变:测试应分两个阶段进行。首先,测试单个PDF对象,如果将它们转换为PDF对象是正确的。第二阶段是通过Adobe Preflight手动检查PDF对象。

将第一阶段测试放在 /tests/mod.rs 中。第二阶段测试最好在插件 mod.rs 文件中处理。printpdf 高度依赖于 lopdf,因此您可以选择针对实际类型或您序列化类型的调试字符串构建测试对象。两种方式都可以 - 您只需确保测试对象符合 PDF 的期望。

以下是我在编写这个库时找到的一些资源

PDFXPlorer,显示 PDF 的 DOM 树,需要 .NET 2.0

官方 PDF 1.7 参考

[德语] 如何在 PDF 中嵌入 Unicode 字体

PDF X/1-a 验证器

PDF X/3 技术笔记

依赖项

~17MB
~232K SLoC