跳转到主要内容

PDF

处理自定义 PDF 文件。

简介​

Netuno 通过 PDF 资源生成支持图像、表格、字体等功能的 PDF 文件,该资源为 Netuno 支持的各种编程语言提供了 iTextPDF 的低代码抽象。

Netuno 自带的 demo 应用(演示应用)包含大量各种语言可能实现的代码示例,其中包括通过位于以下位置的服务 export-pdf 导出 PDF 的演示:

  • netuno/apps/demo/server/services/samples/javascript/export-pdf.js

初始化​

首先需要创建包含正在创建的新文档上下文的对象,默认页面大小为 A4。

创建文档时会定义文档的生成位置。

如果未作任何定义,它将在服务的数据输出中生成,例如:

const pdfDocument = _pdf.newDocument()

要在应用的 docs 文件夹内生成 PDF 文件,可以这样做:

const pdfDocument = _pdf.newDocument(_app.file("docs/文件.pdf").output())

如果要在应用之外的新文件中生成文档,例如:

const pdfDocument = _pdf.newDocument(_os.file("/tmp/文件.pdf").output())

或者放在应用的 storage 中:

const pdfDocument = _pdf.newDocument(_storage.filesystem("server", "文件.pdf").output())

默认情况下,文档以 A4 页面格式创建。

页面格式​

要自定义页面格式,只需在创建文档时指定即可。

为服务的数据输出创建一个页面格式为 Letter 的新文档:

const pdfDocument = _pdf.newDocument(_pdf.pageSize("letter"))

为应用根目录中的文件创建一个页面格式为 B3 的新文档:

const pdfDocument = _pdf.newDocument(_app.file("文件.pdf").output(), _pdf.pageSize("B3"))

在应用的 storage 中创建一个页面格式为 Legal 的新文档:

const pdfDocument = _pdf.newDocument(_storage.filesystem("server", "文件.pdf").output(), _pdf.pageSize("legal"))

请参阅关于页面大小定义的文档。

PDF 文档也可以使用自定义页面大小创建,为此可使用 pageSize 并传入页面的宽度和高度值,例如:

_pdf.pageSize(50, 30)

HTTP 标头​

要创建一个在数据输出中返回 PDF 文件的 REST API 服务,需要在 header 中添加 HTTP 协议的 Content-Type 参数。

将内容定义为 PDF 的示例:

_header.contentType("pdf")

pdf 代码是以下内容的简写:

_header.contentType("application/pdf")

分页​

要创建新页面,可使用 areaBreak:

pdfDocument.add(
_pdf.areaBreak()
)

段落​

向文档中添加一个新段落:

pdfDocument.add(
_pdf.paragraph("我的文本...")
.setFontSize(15)
)

要在固定位置添加段落:

pdfDocument.add(
pdfDocument.add(
_pdf.paragraph("我的固定文本...")
.setFixedPosition(100, 500, 200)
.setFontSize(15)
)
)

在上面的代码中,setFixedPosition 方法的第一个参数是 X 位置(100),第二个参数是 Y 位置(500),第三个参数是宽度(200)。

注意: 如果 Y 定义为值 0,表示位于页面底部;因此要向上移动,需要增大 Y 值。Y 值越大,显示位置越靠顶部;但如果过高而超出页面长度,则不可见。

包含 iText 段落对象所有方法的文档。

字体​

要定义原生字体,可以这样做:

const helvetica = _pdf.font("helvetica")
const helveticaBold = _pdf.font("helvetica-bold")
const helveticaBoldOblique = _pdf.font("helvetica-boldoblique")
const helveticaOblique = _pdf.font("helvetica-oblique")

而要定义按需自定义的字体,可以使用 storage:

const viksiScript = _pdf.font(_storage.filesystem("server", "samples/export-pdf", "viksi-script.ttf"), true)

也可以使用应用内的文件;如本例所示,字体位于应用根目录下的 fonts 文件夹中:

const roboto = _pdf.font(_app.file("fonts/roboto.ttf"), true)

或者也可以使用系统任意路径中的文件;在这个示例中,字体位于系统临时文件夹内的 fonts 文件夹中:

const roboto = _pdf.font(_os.file("/tmp/fonts/roboto.ttf"), true)

包含 iText 字体对象所有方法的文档。

要在文本中使用字体,需要将字体常量传递给段落,如下所示:

pdfDocument.add(
_pdf.paragraph("使用粗体字体。")
.setFont(helveticaBold)
.setFontSize(15)
)

图像​

在以编程方式生成的 PDF 文档中插入图像的方式如下:

pdfDocument.add(
_pdf.image(_storage.filesystem("server", "samples/export-pdf", "logo.png"))
.scaleAbsolute(120, 36)
)

请注意,scaleAbsolute 方法对于重新调整尺寸和调整图像大小非常有用。

或者,我们也可以使用应用内的文件,如本例所示:

const imageLogo = _pdf.image(_app.file("public/images/logo.png")

或者也可以使用系统任意路径中的文件,如本例所示:

const imageLogo = _pdf.image(_os.file("/tmp/images/pdf-logo.png")

要在固定位置添加图像:

pdfDocument.add(
imageLogo
.scaleAbsolute(120, 36)
.setFixedPosition(100, 500)
)

其中在上面的 setFixedPosition 方法中,第一个参数是 X 位置(100),第二个参数是 Y 位置(500)。

注意: 如果 Y 定义为值 0,表示位于页面底部;因此要向上移动,需要增大 Y 值。Y 值越大,显示位置越靠顶部;但如果过高而超出页面长度,则不可见。

包含 iText 图像对象所有方法的文档。

表格​

可以创建表格,从而以更易读的方式呈现数据集。

表格的列数通过参数定义:_pdf.table(列数)

在下面的示例中,列数 为数字 1,即一列。

由于我们在表格中添加了两个单元格,因此第二个单元格会进入下一行。

请注意:

pdfDocument.add(
_pdf.table(1)
.addCell(
_pdf.cell()
.add(
_pdf.paragraph("人员")
.setFont(helvetica)
.setFontSize(10)
)
.setBorder(_pdf.border("solid", 2))
.setBackgroundColor(_pdf.colorRGB(1, 0, 0))
)
.addCell(
_pdf.cell()
.add(
_pdf.paragraph("妮可")
.setFont(helvetica)
.setFontSize(10)
)
.setBackgroundColor(_pdf.colorGray(0.5))
)
)

使用 addCell 方法可以插入单元格定义及其相应内容。

包含 iText 单元格对象所有方法的文档。

关闭并保存文档​

只有调用 close 方法时,文档才会被完全保存或生成,如下所示:

pdfDocument.close()

文档的生成位置在初始化时定义,因此我们在文档初始化时定义最终文档的生成位置:是在服务的数据输出(output)中,还是在文件中。

编辑文件​

可以编辑现有的 PDF 文档,例如:

const pdfDocument = _pdf.openDocument(_app.file("文件.pdf"))

生成的最终文档将发送到服务的数据输出中。

这对于将 PDF 用作基础(就像模板一样)非常有用。

编辑应用中 storage 内文档的示例:

const pdfDocument = _pdf.openDocument(_storage.filesystem('server', '文件.pdf'))

使用一个文档作为基础,但将更改保存到新的 PDF 文件中:

const pdfDocument = _pdf.openDocument(
_app.file("基础文档.pdf"),
_app.file("最终文档.pdf")
)

以 storage 中的文档为基础,但保存到另一个文档的示例:

const pdfDocument = _pdf.openDocument(
_storage.filesystem("server", "基础合同.pdf"),
_storage.filesystem("server", "最终合同.pdf")
)

加载文档后,我们可以追加内容;最后使用 close 方法关闭时,便会生成包含以编程方式添加的新内容的最终文档。

使用现有 PDF 作为模板来生成包含所添加动态内容的 PDF 文件输出的服务示例:

_header.contentType("pdf")

const pdfDocument = _pdf.openDocument(_app.file("模板-1.pdf"))

pdfDocument.add(
_pdf.paragraph("我的动态内容。")
.setFixedPosition(37, 730, 100)
.setFontSize(15)
)

pdfDocument.close()

读取文件​

读取 PDF 文档并提取文本信息。

我们使用 extract 方法,它会将 PDF 文件的所有数据提取到一个 JSON 对象中。

const pdfData = _pdf.extract(_app.file("文件-1.pdf"))

_out.json(pdfData)

返回的 JSON 中,content 键包含 PDF 文件的全部文本内容,metadata 键包含 PDF 文件的各种属性。

以下是 extract 返回的 JSON 示例:

{
"content": "PDF 文件的全部文本内容放在这里...",
"metadata":{
"Content-Type":"application/pdf",
"dc:format":"application/pdf; version=1.4",
"dc:title":"Linux 1",
"pdf:PDFVersion":"1.4",
"pdf:charsPerPage":"580",
"pdf:containsDamagedFont":"false",
"pdf:containsNonEmbeddedFont":"false",
"pdf:docinfo:producer":"Skia/PDF m125 Google Docs Renderer",
"pdf:docinfo:title":"文件 1",
"pdf:encrypted":"false",
"xmpTPg:NPages":"3",
...
}
}

metadata 中还会获取许多其他数据,但为了不让示例过长,已在 ... 中省略。

转换​

可以通过编程方式将 PDF 转换为文本,甚至转换为 HTML。

PDF 转文本​

我们可以使用以下方法将 PDF 文件的所有内容提取为连续文本:

_pdf.toText(...)

使用该方法读取应用根目录中文件的示例:

_out.print(_pdf.toText(_app.file("我的文档.pdf")))

或者也可以使用应用中 storage 内的文件:

_out.print(_pdf.toText(_storage.filesystem("server", "我的文档.pdf")))

PDF 转 HTML​

我们可以使用以下方法将 PDF 文件的所有内容提取为 HTML:

_pdf.toHTML(...)

使用该方法读取应用根目录中文件的示例:

_out.print(_pdf.toHTML(_app.file("我的文档.pdf")))

或者也可以使用应用中 storage 内的文件:

_out.print(_pdf.toHTML(_storage.filesystem("server", "我的文档.pdf")))

HTML 转 PDF​

可以使用以下方法从 HTML 代码生成 PDF 文件:

_pdf.fromHTML(...)

使用该方法在服务中生成下载文件的示例:

const icon = _app.file("public/images/icon.png");

const pdfFile = _pdf.fromHTML(`
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Limelight&family=Roboto" rel="stylesheet">
<style>
* {
font-family: Roboto;
}
div.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: 100px;
background: #1B5886;
}
div.sidebar > div {
position: absolute;
top: 350px;
transform: rotate(-90deg) translate(-50%, -34px);
font-family: Limelight;
font-size: 40px;
color: #89CFF1;
}
main {
padding-left: 150px;
}
h1 {
margin: 0 auto;
text-align: center;
font-family: Limelight;
color: #BA250E;
}
table {
margin: 0 auto;
border: 1px solid black;
border-spacing: 0;
border-collapse: collapse;
}
table > thead {
background: #40712A;
color: #fff;
}
table td, table th {
padding: 5px;
text-align: center;
}
table > tbody > tr:nth-child(even) {
background: #e1e1e1;
}
a {
font-family: Limelight;
font-size: 16px;
font-weight: bold;
text-decoration: none;
color: #1E5FB5;
}
</style>
</head>
<body>
<div class="sidebar">
<div>演示</div>
</div>
<main>
<h1>从 HTML 生成 PDF</h1>
<div style="text-align: center; margin: 30px 0;">
<img width="50" src="${icon.uri()}">
</div>
<p>
这是一个用于生成 PDF 文件的示例 HTML。
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam
tellus arcu, pharetra quis condimentum eget, dapibus convallis metus.
</p>
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
</thread>
<tbody>
<tr>
<td>玛丽亚·路易莎</td>
<td>23</td>
</tr>
<tr>
<td>若泽·马托斯</td>
<td>38</td>
</tr>
</tbody>
</table>
<hr style="margin-top: 50px; height: 1px; background: #50B6C6; border: none;"/>
<div>
<a href="https://netuno.org" target="_blank">Netuno.org</a>
</div>
</main>
</body>
</html>
`);

_header.contentTypePDF();
_out.copy(pdfFile.input());

视频​

以下是一些示例视频,演示如何以编程方式生成 PDF 文件: