AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79560847
Accepted
gettalong
gettalong
Asked: 2025-04-08 05:50:42 +0800 CST2025-04-08 05:50:42 +0800 CST 2025-04-08 05:50:42 +0800 CST

仅在 macOS 预览版中出现 /ToUnicode 映射问题

  • 772

我遇到了一个奇怪的问题,PDF 及其包含的/ToUnicodeCMap 只影响 macOS 预览版,其他测试过的查看器都运行正常。问题是我不知道是包含的/ToUnicodeCMap 有问题还是预览版有问题。

这是有问题的 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 中完美运行(即复制和粘贴有效)。

  • 完全删除/ToUnicodeCMap 会导致文本无法复制。这意味着 macOS 预览版确实使用了此 CMap,并且它很可能是罪魁祸首。

  • 添加虚拟条目<0000><0000>不起作用。

  • <000D><0044>向CMap添加虚拟条目/ToUnicode不起作用。

  • 如果字符代码不是从 1 开始而是从 14 开始,则会导致前 13 个字符无效,即使情况变得更糟。

  • 在阅读了 PDF 规范和“5014 Adob​​e CMap 和 CIDFont 文件规范”的各个部分后,我认为/ToUnicode上面两个链接文件中的 CMap 是正确的。

/ToUnicode如果您能提供任何关于生成的CMap 是否无效或是否是 macOS Preview 的错误见解,我们将不胜感激!

pdf
  • 1 1 个回答
  • 53 Views

1 个回答

  • Voted
  1. Best Answer
    KenS
    2025-04-08T22:34:37+08:002025-04-08T22:34:37+08:00

    我相信我现在明白了问题所在,并且我有理由相信这是 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 的重要部分如下所示:

    1 begincodespacerange
    <0000> <FFFF>
    endcodespacerange
    2 begincidrange
    <0001><000C> 1
    <000E><001D> 13
    endcidrange
    

    因此,代码空间(有效值)的范围是 0 到 0xFFFF,CMap 定义了两个数字范围。第一个范围是 0x01 到 0x0C,映射到 CID 1(因此 0x01 = 1,0x02 = 2,依此类推)。第二个范围是 0x0E 到 0x1D,映射到从 13 开始的 CID。

    到目前为止一切顺利。但这怎么能让我们复制粘贴呢?答案是不行。有一个可选的表,叫做 ToUnicode CMap。它可能存在,也可能不存在。如果存在,PDF 用户就能可靠地找到给定字符代码映射到哪个 Unicode 码位。如果不存在,那就只能靠猜测了。在这种使用自定义映射和 susbset 字体的文件中,根本无法确定 Unicode 值。

    幸运的是,有一个 ToUnicode CMap:

    1 begincodespacerange
    <0000> <FFFF>
    endcodespacerange
    24 beginbfchar
    <0001><0048>
    <0002><0065>
    <0003><006c>
    <0004><006f>
    <0005><0020>
    <0006><0066>
    <0007><0072>
    <0008><006d>
    <0009><0078>
    <000A><0061>
    <000B><0050>
    <000C><0044>
    <000E><0046>
    <000F><002e>
    <0010><0054>
    <0015><0076>
    <0016><0079>
    <0017><0063>
    <0018><0070>
    <0019><0026>
    <001A><0075>
    <001B><0077>
    <001C><006e>
    <001D><0021>
    endbfchar
    2 beginbfrange
    <0011><0012><0068>
    <0013><0014><0073>
    endbfrange
    

    这基本上和之前的 CMap 一样。从中可以看出,字符代码 1 映射到 Unicode 码位 U+0048。这是一个大写的“H”。正如我在开头提到的,字符代码是根据使用情况分配的,第一个字符是“H”,它被分配给字符代码 1。

    因此,PDF 文件中的“文本”实际上是以二进制数据的形式存储的(正如 KJ 所说)。由于该文件的生成方式避免了使用转义字符,因此我们避免使用 0x0D,这意味着“文本”如下所示:

    0001 0002 0003 0003 0004 0005 0006 0007 0004 0008 0005 0001 0002 0009 000A 000B 000C 000E
    

    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 都能正常工作。

    • 1

相关问题

  • 将参数从 GhostScript CLI 传递到 PostScript

  • PDF 中的文档尾部 ID:为什么它由两个字符串组成以及如何通过命令行工具提取它

  • SVG 透明度破坏了 PDF/A 兼容性

  • 使用旧版 iText-2.1.7 将符号添加到 PDF

  • Adobe DRM 无法获取图像 pdf 的 acsm 文件

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve