通过 HTML 与 CSS3 制作有逼格的 PDF 书籍

2017-04-14 15:18 #旧文章

前言

说到印刷物排版,我觉得大多数人都会想到用 Word 来完成任务,但是对于我来说一点都不友好,第一就是它不能绑定快捷键来控制光标,第二就是它不能跨平台,所以我在高一时就开始使用标记语言进行笔记的书写,那时候我用的是 Markdown,现在用的是 Textile。

通过转换器,我们可以将这些标记语言转化成标准的 HTML 文档,通常我都是用 Chrome 打开生成的 HTML 文件然后进行打印。虽然目前的 CSS 规范已经为打印提出了一系列的属性标准,但是看起来没有哪个浏览器很好地支持。所以想输出通过 HTML 输出 PDF 书籍还是一个难题。直到我发现了另一个输出 PDF 的好方法:Prince,它对于非商业用途的使用是免费的。

通过 Prince,我得到了这样的文档:

既然 CSS 为打印提供了一系列的属性,那我们就先来看看这些属性。

规范

你所知道的绝大部分 CSS 属性都可以用在打印输出上。对于打印,我们有 CSS Paged Media Module Level 3CSS Generated Content for Paged Media Module 两个规范,我们下面来看看这两个规范主要有什么。

@page

你可以通过 @page 来指定打印纸张的大小等属性。比如我想用 A6 的纸张来印刷我上面的那本书,我可以在 CSS 中这么写:

@page {
size: 105mm 148mm;
margin-top: 11mm;
margin-bottom: 11mm;
margin-inside: 18mm;
margin-outside: 10mm;
}
size
指定纸张大小,第一个值表示宽,第二个值表示高。
margin-top
指定纸张顶部的页边距。
margin-bottom
指定纸张底部的页边距。
margin-inside
指定纸张装订一侧的页边距。
对于奇数页来说,装订侧表示左侧。
对于偶数页来说,装订侧表示右侧。
margin-outside
指定装订侧对边的页边距。

对于 size 属性来说,你还可以这么写:

@page {
size: A4 landscape;
}

第一个值是纸张名称,第二个值表示横向。

页边盒结构

在打印纸边缘定义有 16 个盒,这些盒可以用来容纳页眉等内容。

Prince 文档

页边盒结构示意图
页边盒结构示意图

@page :first

第一页是封面,不需要页边距?没问题,这个属性可以单独指定第一页的属性。

@page :first {
margin: 0;
}

强制换页

一节内容结束了,我们不想让下一节的标题跟上,这时候我们可以通过一套属性来完成:

div.newpage {
page-break-after: always;
}

这样我们就可以通过在 HTML 中加入一个 <div class="newpage"></div> 来强制换页。同时,我们还可以防止在标题后换页:

h1 {
page-break-after: avoid;
}

另外,我们也不希望表格和图片被分开印在两页中:

table,
img {
page-break-inside: avoid;
}

内容生成

我们可以通过 content 属性来生成页眉等:

@page {
@top-right {
content: "我是页眉";
}
}

页码生成

规范中定义了一个页码计数器,我们可以通过它生成页码:

@page {
@bottom-left {
content: counter(page);
}
}

字符串设置

我们不希望页眉永远都是那一串文字,我们想让页面显示出当前页码对应的节标题,我已我们可以这么设置:

h1 {
string-set: title content(); /*设置title变量的值为当前元素的内容*/
}
@page {
@top-right {
content: string(title); /*并在页眉出显示出来*/
}
}

分别设置左右页码样式

我们不希望左右两页的布局都一样,所以 @page 规范中定义有 :left:right 状态选择器。

所以最后的设置是这样的:

@page {
size: 105mm 148mm;
margin-top: 11mm;
margin-bottom: 11mm;
margin-inside: 18mm;
margin-outside: 10mm;
}
@page :first {
margin: 0;
}
@page :left {
@top-left {
margin: 1mm 0 1mm 0;
content: string(subtitle);
font-size: 9pt;
font-family: "AR PL UKai CN";
}
@bottom-left {
margin: 1mm 0 1mm 0;
border-top: 0.25pt solid #666;
content: counter(page);
font-size: 9pt;
font-family: "Droid Sans Fallback";
}
}
@page :right {
@bottom-right {
margin: 1mm 0 1mm 0;
border-top: 0.25pt solid #666;
content: counter(page);
font-size: 9pt;
}
@top-right {
content: string(title);
margin: 2mm 0 2mm 0;
font-size: 9pt;
font-family: "AR PL UKai CN";
}
}

目录生成

想自动生成目录吗?好像没有多简便的方法。所以我们来手动编写目录:

<div id="catalog">
<h1>目录</h1>
<ul>
<li><a href="#A">A</a></li>
<li><a href="#B">B</a></li>
<li><a href="#C">C</a></li>
<li><a href="#D">D</a></li>
<li><a href="#E">E</a></li>
<li><a href="#F">F</a></li>
<li><a href="#G">G</a></li>
<li><a href="#H">H</a></li>
</ul>
</div>

这样还不够,我们希望做出图片中那样的效果。
我们先把列表前面的圈圈去掉:

#catalog ul {
list-style: none;
margin: 0;
padding: 0;
}

然后把超链接的样式清除:

#catalog ul li a {
text-decoration: none;
}

最后生成页码数字并插入 .

#catalog ul a::after {
content: leader(".") target-counter(attr(href), page);
}

到此为止,一本比较像样的书就做好了。

生成 PDF

我们已经将 HTML 文档做好了,接下来我们使用 Prince 生成 PDF,执行如下命令:

Terminal window
prince book.html book.pdf

完成。