我遇到了一个奇怪的问题,PDF 及其包含的/ToUnicode
CMap 只影响 macOS 预览版,其他测试过的查看器都运行正常。问题是我不知道是包含的/ToUnicode
CMap 有问题还是预览版有问题。
这是有问题的 PDF:https://github.com/user-attachments/files/19538203/example.pdf以及出现此问题的Github 问题。
如果在 macOS 预览版中打开该 PDF,选中并复制文本,则“Hello from HexaPD”之后的所有内容都是错误的。其他查看者可以正常复制整个文本。
当前状态(已编辑):
生成 PDF 的库 HexaPDF 使用了一种优化方法
\r
,避免创建包含 ASCII 字符、(
和)
的字符代码\
。原因是,在序列化为 PDF 文字字符串时,需要对这些字符进行转义。如果关闭此优化,则生成的文件(参见https://github.com/user-attachments/files/19575820/example.pdf)可以在 macOS Preview 中完美运行(即复制和粘贴有效)。
完全删除
/ToUnicode
CMap 会导致文本无法复制。这意味着 macOS 预览版确实使用了此 CMap,并且它很可能是罪魁祸首。添加虚拟条目
<0000><0000>
不起作用。<000D><0044>
向CMap添加虚拟条目/ToUnicode
不起作用。如果字符代码不是从 1 开始而是从 14 开始,则会导致前 13 个字符无效,即使情况变得更糟。
在阅读了 PDF 规范和“5014 Adobe CMap 和 CIDFont 文件规范”的各个部分后,我认为
/ToUnicode
上面两个链接文件中的 CMap 是正确的。
/ToUnicode
如果您能提供任何关于生成的CMap 是否无效或是否是 macOS Preview 的错误见解,我们将不胜感激!
我相信我现在明白了问题所在,并且我有理由相信这是 Apple Preview 中的一个错误。
不幸的是,解释这一点很复杂......
PDF 文件使用嵌入的子集字体。通常情况下,该字体仅包含 PDF 文件使用的字形(字符形状的实际描述)。同样常见的是,“编码”方式是,第一个使用的字符获得字符代码 1,第二个字符获得字符代码 2,依此类推。
PDF 中的编码有点类似于 Windows 中的代码页或 ASCII;它们将数值映射到特定字符。
在这个文件中,字体实际上是一个 CIDFont,这使得事情变得复杂,因为这类编码的大小可能有所不同,就像 UTF-8 一样,代码所需的字节数也各不相同。幸运的是,在这种情况下,所有代码都是两个字节。
CMap 是将所有这些连接在一起的粘合剂;它决定了映射到特定字符需要多少字节的输入。CMap 获取字符代码并返回 CID;如果您使用的是 CIDFont,那么 CID 就是字体的“索引”,用于查找特定的字形程序。如果您的字体是 TrueType 字体(就像这里的情况一样),那么 CIDToGIDMap 会将 CID 转换为 GID(因为 TrueType 字体使用的是 GID)。同样幸运的是,CIDToGIDMap 是 /Identity。简洁明了。
现在 CMap 的重要部分如下所示:
因此,代码空间(有效值)的范围是 0 到 0xFFFF,CMap 定义了两个数字范围。第一个范围是 0x01 到 0x0C,映射到 CID 1(因此 0x01 = 1,0x02 = 2,依此类推)。第二个范围是 0x0E 到 0x1D,映射到从 13 开始的 CID。
到目前为止一切顺利。但这怎么能让我们复制粘贴呢?答案是不行。有一个可选的表,叫做 ToUnicode CMap。它可能存在,也可能不存在。如果存在,PDF 用户就能可靠地找到给定字符代码映射到哪个 Unicode 码位。如果不存在,那就只能靠猜测了。在这种使用自定义映射和 susbset 字体的文件中,根本无法确定 Unicode 值。
幸运的是,有一个 ToUnicode CMap:
这基本上和之前的 CMap 一样。从中可以看出,字符代码 1 映射到 Unicode 码位 U+0048。这是一个大写的“H”。正如我在开头提到的,字符代码是根据使用情况分配的,第一个字符是“H”,它被分配给字符代码 1。
因此,PDF 文件中的“文本”实际上是以二进制数据的形式存储的(正如 KJ 所说)。由于该文件的生成方式避免了使用转义字符,因此我们避免使用 0x0D,这意味着“文本”如下所示:
1 = H、2 = e、3 = l、4 = o、5 = ' '、6 = f 等。
因此,我们获取字符代码并查找 ToUnicode CMap。结果如下:
U+0048、U+0065、U+006C、U+006C 等等。重点是,我们使用字符代码来查找 ToUnicode CMap。
那么 Apple Preview 为何会出错呢?因为它“似乎是”将字体的 CMap 应用于字符代码以获取 CID,然后使用该CID查找 ToUnicode CMap。这样做的问题在于,CID 从 1 到 28 连续排列,但字符代码从 1 到 12,然后从 14 到 29。
这会导致两个错误:首先,CID 13 在 ToUnicode CMap 中没有条目;其次,所有超过 13 的 CID 都“偏离 1”。CID 14 获得了分配给字符代码 13 的 Unicode 代码点,依此类推。
我修改了原始失败的示例,使字体 CMap 能够正确映射 CID 以进行渲染,并修改了 ToUnicode CMap,使其在使用 CID 而不是字符代码进行查找时能够正确映射。该文件位于:
https://www.dropbox.com/scl/fi/ua6zzr8hr0hlazaf1f8yl/preview.pdf?rlkey=e6amwr722xjtrn2o9b44p6r7u&st=ifq46ngw&dl=0
该文件可以从 Apple Preview 正确复制/粘贴(嗯,在我老旧的 MacOS Big Sur 上确实可以)。但它无法从任何符合标准的 PDF 客户端正确复制/粘贴,因为显然 ToUnicode CMap 的设置是使用 CID 而不是字符代码。
简而言之,Apple Preview 的查找方式不正确。要让 Apple Preview 和符合标准的 PDF 用户都能获得正确结果,唯一的方法是确保字符代码和 CID 相同,这样无论使用字符代码还是 CID 进行查找,ToUnicode 都能正常工作。