文字(Text)
本章我们会学习如何在页面上绘制文本。绘制文本是PDF中最复杂的部分,但也是PDF击败竞争对手成为今天国际标准的原因。当其他原始的查看器将文本转换为光栅图像或矢量路径(以保持视觉完整性)时,PDF的发明者知道用户需要可以搜索和复制的文本,而不是只能看的图片。凭借Adobe工程师对字体的丰富经验和理解,他们做到了把文本与视觉呈现相结合。
虽然PDF对文本的支持允许渲染任何语言的任何字体的任何字形,但PDF是在Unicode发明之前创建的。所以很多其他文件格式的开发人员认为理所当然的事情在PDF中必须手动完成,比如PDF中就做不到直接给出Unicode代码点(codepoin)然后让渲染器完成所有的绘制工作。
字体(Fonts)
我们已经学习了如何在页面上绘制矢量图形(或路径)以及光栅图形(图像)。这些类型的绘图操作相当简单,因为通常不需要额外的信息——只需要指令和(光栅图像)图像数据。文本则需要更多的数据,其中最重要的是字体。
字形(Glyphs)
字体,也叫字体程序(font program),就是一组独特的绘图指令——字形,的集合。这些字形和前几章里绘制的路径或光栅图像没有不同。不过字体中还包含一些关于各种字形的元数据(metadata),包括编码(encoding),编码提供了从已知字符集(例如ASCII、Unicode或Shift-JIS)到字形的映射。当然并不是每种字体都有字形能对应上编码中的某个值。
图 4-1 是不同编码中的三个常见值及其不同字体中的字形的示例。
图 4-1 不同编码中的三个常见值及其不同字体中的字形的示例
看着像方框的字形通常被称为“notdef”字形。Notdef(未定义的缩写)是所有字体中都存在的特殊字形,用于请求的字形不存在时进行填充。如果在PDF中发现这种字形,那一定是出了什么问题。(在上图中我们是为了举例故意显示的)。
虽然我们主要关注字形的形状和外观,但除此之外还有一组我们需要关注的值,叫字形度量(glyph metrics)。字形度量,有些是在字体中直接定义的,还有一些是计算出来的。字形度量主要被用来布局文本。比如要绘制“Hello”的时候,程序需要知道将“e”放在“H”之后的哪个位置。图 4-2 说明了需要将“e”放在下一个“字形原点”(glyph origin)处。
图 4-2 在字形周围的一些字形度量的值
字体类型(Font Types)
图形界面首次出现的时候,使用的字体被称为“位图字体(bitmapped fonts)”,因为每个字形都被描述为位图或光栅图像。虽然这对在屏幕上展示很有帮助,但是对于打印来说不够灵活,因此诞生了轮廓(基于路径)字体格式。PDF支持三种最常见的轮廓字体:
Type 1
TrueType
OpenType
对于这些字体类型,实际的字体程序是在单独的字体文件中定义的,该文件可以嵌入到PDF流对象中,也可以从外部源获取。
PDF还具有两种专门用于PDF的字体,它们的字形数据必须在PDF中定义:
Type 3
Type 0
字体字典(The Font Dictionary)
第1章里的我们定义的那个PDF文件,简单地在页面上绘制了“Hello World”。示例 4-1 显示了文件中相关的部分,我们现在看看里面的细节。
示例 4-1 “Hello World.pdf”的部分内容
1 0 obj
/Type /Page
/Parent 5 0 R
/MediaBox [ 0 0 612 792 ]
/Resources 3 0 R
/Contents 2 0 R
endobj
3 0 obj
/Font <</F1 4 0 R >>
endobj
4 0 obj
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont/Helvetica
endobj
在这个例子中,对象#1是页面,其字典里只有最低限度的必需键值:MediaBox(页面大小)、Contents(如何绘图)和Resources(Contents绘图需要)。资源字典中只有一个类型Font和一个条目F1,F1就是对象#4处的字体字典。
字体词典中应该包含字体的类型(Type 1或TrueType),字体的PostScript名称和encoding信息,以及当字体程序没有被嵌入PDF文件时可用的替代字体。
ISO 32000-1:2008, 9.6.2.2描述了标准字体列表(又叫Base 14),其中包括14种字体,每个PDF渲染器都需要了解并直接提供或通过替代品提供。而且渲染器应该知道它们的字体度量,这样我们就不需要在字体字典中提供。我们的示例中对象#4相对比较小,因为它只用了标准列表中的一种字体(Helvetica)。
标准字体的完整列表是:
Times-Roman
Times-Bold
Times-Italic
Times-BoldItalic
Helvetica
Helvetica-Bold
Helvetica-Oblique
Helvetica-BoldOblique
Courier
Courier-Bold
Courier-Oblique
Courier-BoldOblique
Symbol
ZapfDingbats
创建上面某一种标准字体的字体字典时,只需要三个键:Type(值为Font)、Subtype(值为Type1)和BaseName(上面字体列表中的值之一).
从列表中可以看出,一些字体实际上包含了样式(例如粗体或斜体)。所以如果希望Times字体以斜体显示,需要使用Times-Italic字体。示例 4-2 是“Hello World”的变体,使用了几种不同的字体。
示例 4-2 “Hello World”(四种不同的字体)
9 0 obj
/Type/Page
/Contents 24 0 R
/MediaBox[0 0 612 792]
/Parent 5 0 R
/Resources 13 0 R
endobj
13 0 obj
/Font <<
/F1 14 0 R
/F2 13 0 R
/F3 12 0 R
/F4 11 0 R
endobj
14 0 obj
/Type /Font
/Subtype /Type1
/BaseFont/Helvetica
endobj
13 0 obj
/Type /Font
/Subtype /Type1
/BaseFont/Symbol
endobj
12 0 obj
/Type /Font
/Subtype /Type1
/BaseFont/Courier-Bold
endobj
11 0 obj
/Type /Font
/Subtype /Type1
/BaseFont/Times-Italic
endobj
24 0 obj
<</Length 182>>
stream
/F1 48 Tf
1 0 0 1 10 100 Tm
(Hello World)Tj
0 50 Td
/F2 48 Tf
(Hello World)Tj
0 50 Td
/F3 48 Tf
(Hello World)Tj
0 50 Td
/F4 48 Tf
(Hello World)Tj
endstream
endobj
编码(Encodings)
到目前为止,示例中的文本都是简单的英文文本。PDF认为与标准的14种字体相关的文本都属于称为标准编码(StandardEncoding),是ISO 32000-1:2008, D.2中定义的ISO Latin-1(ISO 8859-1)的一个子集。但是很多情况下我们需要完整的Latin-1来表示其他基于罗马/拉丁语的语言(如法语、西班牙语或德语)。我们需要向字体字典中添加具体的编码类型才能使用完整的Latin-1。
下面是使用WinAnsiEncoding(也称为Windows code page 1252)以其他语言写出一些文本的示例。
示例 4-3 绘制其他语言的文本
14 0 obj
/Type /Font
/Subtype /Type1
/BaseFont/Helvetica
/Encoding/WinAnsiEncoding
endobj
24 0 obj
<</Length 182>>
stream
/F1 48 Tf
1 0 0 1 10 100 Tm
(English)Tj
0 50 Td
/F2 48 Tf
(Français)Tj
0 50 Td
/F3 48 Tf
(Español)Tj
endstream
endobj
为了支持其他非拉丁语系语言,必须使用嵌入字体。不过我们应该不会涉及到这个主题。
只使用非嵌入字体也可以支持PDF中的中文、日文和韩文(CJK),但这需要用户安装额外的字体。不推荐这样做。
文本状态(Text State)
我们已经了解了字体和字形,接下来我们看看怎么在页面上实际绘制文本(示例 4-4)。
示例 4-4 Hello World.pdf的部分内容
1 0 obj
/Type /Page
/Parent 5 0 R
/MediaBox [ 0 0 612 792 ]
/Resources 3 0 R
/Contents 2 0 R
endobj
2 0 obj
<< /Length 53 >>
stream
/F1 24 Tf
1 0 0 1 260 600 Tm
(Hello World)Tj
endstream
endobj
在对象#2的内容流里,有五个新的运算符。首先是BT,在第一行单独出现(没有操作数)。它代表“开始文本”,后面需要描述一系列与文本相关的指令。BT与ET(结束文本,同样没有操作数)运算符成对出现,ET在内容流的最后一行出现。
类似于图形状态(请参阅图形状态),PDF文件中也有包含了所有文本绘制相关属性的文本状态。 用BT作为开头声明一个新的文本状态,然后用ET作为结尾将文本状态清除,就像q和Q处理图形状态一样。只不过文本状态没有推送/弹出的概念,不允许嵌套出现BT/ET。
字体和大小(Font and Size)
文本状态有很多属性,其中最重要的三个是使用的字体、文本大小和文本的放置位置。
Tf运算符指定字体资源的名称——也就是资源字典的字体子字典中的某一条目。该条目的值是一个字体字典(请参阅字体字典),参考示例4-1中的F1。
PDF使用通用的“用户单位”概念来定义对象的大小和位置。用户空间中的标准字形大小为1个单位,而紧密间隔的文本行的标称高度也是1个单位。因此为了指定绘制字形的大小,我们需要指定比例因子(scale factor)。Tf运算符的第二个操作数就是比例因子,用来设置图形状态下的文本字体大小。示例 4-4 中,字体大小为24。
还有第二种设置字形比例因子的方法,类似于其他图形对象的缩放方式——使用变换矩阵。Tm运算符可以用于对于文本的转换,它的参数和第2章中的cm运算符相同的。示例 4-4 中没有进行额外的缩放,但文本被定位在(260,600)处。通常我们就用示例 4-4 中的方法给绘制的第一个字形设置缩放和位置。但是也可以通过这种方式进行:
/F1 1 Tf
24 0 0 24 260 600 Tm
(Hello World)Tj
这两种方法在现代系统中几乎没有差别。可能取决于字体的使用方式。如果我们只使用同一尺寸的文本(在给定页面上),那比例因子最好作为Tf的参数(示例 4-4),因为这样字体加载器将预先缩放字形。但如果我们在页面上用到了相同的字体的多种尺寸,那么以1个单位加载并每次使用Tm进行缩放可能更理想。前提是尺寸真的来回切换很多次,不然还是使用第二个Tf创建字体的第二个实例好一些,如示例 4-5 所示。
示例 4-5 用大文字和小文字绘画
/F1 24 Tf
1 0 0 1 100 150 Tm
(Big Text)Tj
/F1 12 Tf
1 0 0 1 100 100 Tm
(Small Text)Tj
渲染模式(Rendering Mode)
在图像一章,我们知道了路径的绘制根据运算符不同(f或S)可以分为填充、描边或既填充又描边。文本绘制不同于路径的是,可以使用单个运算符(Tr)来设置渲染模式。图 4-3 列出了Tr的值及其对文本绘制的影响。
结合渲染模式对图形状态属性的影响,我们可以创建示例 4-6。
示例 4-6 描边和填充文本
绘制文本(Drawing Text)
Tj运算符用于在页面上绘制文本(“显示”字符串)。原理很简单,操作符使PDF渲染器将第一个字形的“字形原点”与当前笔位置对齐并绘制字形。然后渲染器将笔按字形的宽度推进到“下一个字形原点”并绘制下一个字形,对整个字符串依此类推。
对于大多数文本渲染的情况,这是完全可以接受,也是大多数用户习惯看到的。但是对于那些需要更精确地控制字形定位的情况,我们将需要使用TJ运算符。
许多字体中包含如何更精确地控制字形之间距离的信息,称为字距调整(kerning)。但Tj运算符不支持此操作。所以我们需要从字体中主动获取该信息,然后使用TJ运算符来获得更具视觉吸引力的结果。
TJ运算符用数组不是将字符串作为操作数。数组由一个或多个字符串组成,字符串中间夹着数字,数字用于调整文本位置 (Tm)。数字单位是千分之一的用户单位,绘制的时候要从当前水平坐标中减去该值。
这意味着在默认坐标系中,正调整具有将绘制的下一个字形向左移动给定数量的效果,而负调整会将下一个字形向右移动。
示例 4-7 显示了使用Tj运算符绘制的单词,和通过TJ手动调整字距之后的单词。
示例 4-7 手动字距调整的文本
/F1 48 Tf
1 0 0 1 10 150 Tm
(AWAY)Tj
1 0 0 1 10 100 Tm
[ (A) 120 (W) 120 (A) 95 (Y) ] TJ
放置文本(Positioning Text)
之前的所有示例中,我们已经使用了Tm运算符明确了文本的位置。但是如果我们只是想沿一个方向移动笔(向下移动到下一行,或向右移动),那使用Tm运算符就太重了。对于笔的简单移动,应使用Td运算符。Td运算符需要两个参数,tx和ty,代表如何在X和Y方向移动笔。参数为0,那么笔就不会朝相应方向移动。示例 4-8 展示了使用Td绘制的4个数字。
示例 4-8 使用Td绘制的4个数字
/F1 48 Tf
1 0 0 1 10 700 Tm
(1)Tj
0 -50 Td
(2)Tj
50 50 Td
(3)Tj
0 -50 Td
(4)Tj
在PDF中,页面底部的y坐标为0。因此想要在页面下方绘制文本的话,我们需要从高处开始向下减。
下一步计划(What’s Next)
本章我们了解了字体和字形以及如何使用它们来绘制文本。下一章我们将学习PDF中的导航功能。