a house in the woods

Hi, nice to meet you.

  1. 1. 前言
  2. 2. 正文
    1. 2.1. 谈谈PDF格式
    2. 2.2. 插入图片(jspdf.addImage)
    3. 2.3. 插入文字
      1. 2.3.1. 添加文字的操作
    4. 2.4. 字体文件过大
      1. 2.4.1. 生产中的解决办法
  3. 3. 结语

前言

因为在自己的简历上写了用过jspdf来生成pdf格式的纸质试卷,所以前两天面试的时候就被问到了是否对其有了解。

当时确实很无助呀,因为对于这类文件规范编码的库,是不太感兴趣的,因为无非就是根据文档所约定的方式进行编解码,它的内容还是挺无趣的。

但既然会在面试中被问到,同时也在这次面试中明白了提升技术深度的一个方式就是要对用到的开源库的原理也应有一定的了解,所以就写下探索的过程吧。

正文

谈谈PDF格式

在没看spec前,我一直以为pdf大概跟xlsx那样是xmlgzip的二进制文件。

但在开发者工具的network里看到,请求返回的结果竟然是一个带有一点二进制的纯文本文件

先说说文件中常见的语法:

  1. 结构:由<< ... >>所包裹。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <<
    /Type /XObject
    /Subtype /Image
    /Width 213
    /Height 256
    /BitsPerComponent 8
    /ColorSpace /DeviceRGB
    /Length 11 0 R
    /Filter /DCTDecode
    >>

    像上面这个用YAML表示就是

    1
    2
    3
    4
    5
    6
    Type: XObject
    Subtype: Image
    Width: 213
    ...
    Length: '11 0 R'
    ...

    keyvalue用空格分开,如果是嵌套的结构,那value那里可以用<< ... >>包裹

  2. 数组:用[]包裹,空格隔开各个元素,元素也可以是一个数组或者结构

    1
    2
    3
    <<
    /MediaBox [0 0 595 842]
    >>
  3. obj声明indexgeneration都是numberindex表示obj的序号(唯一),generation一般是版本,通常为0

    下面...省略的部分可以为任何数据类型,结构,数组,数字等等,所以有点像是变量声明。

    1
    2
    3
    index generation obj
    ...
    endobj
  4. obj引用:在结构的例子中,可以看到这串(11 0 R)字符,其实前面两个数字就代表了obj声明的两个数字,最后的字母R则是reference的意思。

  1. stream:在pdf中是为content stream,用来表示pdf实际渲染的数据(如图片数据)或者生成内容的指令(普通或二进制文本),放在obj声明中,所以stream存在于obj声明中时,如果其上方有结构,那该结构可看作是stream的属性定义。
    1
    2
    3
    stream
    ...
    endstream

插入图片(jspdf.addImage

  1. 需先定义一个图片obj,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    19 0 obj
    <<
    /Type /XObject
    /Subtype /Image
    /Width 184
    /Height 60
    /ColorSpace /DeviceRGB
    /BitsPerComponent 8
    /DecodeParms <</Colors 3 /BitsPerComponent 8 /Columns 184>>
    /SMask 20 0 R
    /Length 33120
    >>
    stream
    ...
    endstream
    endobj
  2. 把图片obj注册到资源obj上,如下面的I0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    2 0 obj
    <<
    /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
    /Font <<
    /F1 5 0 R
    /F2 6 0 R
    /F3 7 0 R
    ...
    >>
    /XObject <<
    /I0 19 0 R
    >>
    >>
    endobj
  3. 资源obj会被page objResources属性引用,而Contents属性,这里是4 0 obj则是page实际要渲染的内容了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    3 0 obj
    <</Type /Page
    /Parent 1 0 R
    /Resources 2 0 R
    /MediaBox [0 0 793.3333333333332575 1122.6666666666665151]
    /Contents 4 0 R
    >>
    ...
    endobj
  4. page有对应的图片资源后,就可以在绘图操作中引用,q对应context.saveQ对应context.restorecmcurrent matrix表示位置大小变换,Do则是展示图片的操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    4 0 obj
    ...
    stream
    ...
    q
    245.3333333333333144 0 0 80. 274. 1042.6666666666665151 cm
    /I0 Do
    Q
    endstream
    endobj

插入文字

插入文字首先需要添加字体,如果是英文字符,则无需添加额外的字体,因为pdf本身已经内置一些标准字体,但如果是中文字符,则需要通过addFont来添加字体资源,性质跟图片资源是一致的,同样是在Resources注册和Contents引用。

添加文字的操作

BT: begin text

EF: end text

Td: 文字的位置偏移,相对于当前行

Tj: 显示文字

Tf: 文字大小

TL: 行高

Tj左边的操作数为hex格式的字符在字体文件中的序号,如果不是自定义字体,则是实际的字符如(hello) Tj

1
2
3
4
5
6
7
BT
/F15 21 Tf
24.1499999999999986 TL
0. 0. 0. rg
322. 990.9333292643227651 Td
<036b> Tj
ET

字符到unicode的映射:如下就表示序号为3的字符对应的unicode
0x0020,也就是空格。beginbfchar左边的数字,表示映射的数量。
begincodespacerange表示映射的序号范围。

1
2
3
4
5
6
7
1 begincodespacerange
<0000><ffff>
endcodespacerange
19 beginbfchar
<0003><0020>
...
endbfchar

字体文件过大

虽然jspdf支持添加字体,但字体文件实在太大了,在浏览器中生成的pdf的时候再去加载完整的字体文件不太现实。

实际上字体文件可以动态生成,只保留有限字符的字形(glyph)数据来缩小字体文件和生成的pdf的大小。

动态生成的方案:

  1. 客户端发送所需的文字请求服务器即时生成
  2. 浏览器在闲时缓存完整的字体文件,再用fonteditor-core库去筛出所需的字形

在实际使用自定义字体时,发现pdf是不能像html那样自行切换font-weightfont-style,而是需要分别提供各个变形的字体。

生产中的解决办法

直接把html转成图片,这样就不用考虑字体的问题了,但问题是pdf的体积会偏大,而且会缺失文字的信息(因为是图片

误解:一开始我是看到jspdfhtml这个方法,来把html转成pdf,粗看源码发现它其实用的是html2canvas这个库,于是以为它是先把html转成图片后再添加到pdf上的,所以我就没去使用该方法,而是手动使用html2canvas来转换成图片。

现在重新看源码后,发现jspdf是在context2d模块中模拟了canvasapi,所以它能够接入html2canvas,把绘制转换成相应的pdf操作。

结语

以上便是我对pdfjspdf的大概认识,其实关于pdf和字体文件(ttf)还有很多问题没有了解,例如ttf里面包含的各个table,将来再去把这部分的知识补上b吧。

This article was last updated on days ago, and the information described in the article may have changed.