Chrome保存为PDF时改变CJK字符

9

当我们尝试使用Chrome的打印选项将包含CJK字符的网页保存为PDF时,我们遇到了问题。

在PDF中,Chrome渲染的字符在视觉上看起来相同,但Unicode不同。

以下是一个基本的HTML。

<HTML>

<HEAD>
  Test Character
</HEAD>

<BODY></BODY>

</HTML>

HTML中的字符在Chrome中打开为:https://graphemica.com/%E5%AD%90,但在PDF中对应的字符为:https://graphemica.com/%E2%BC%A6。HTML和PDF的链接为:https://1drv.ms/f/s!Aq5YnvMOo4V8iVzdRyjmX3X5L0TD。首先需要了解为什么会出现这种情况,然后才能找到解决方法。是否有任何实用程序可以将我的字符转换为Chrome在PDF中呈现的方式?操作系统版本为:MacOS 10.13.6 (17G65),Chrome版本为:75.0.3770.100 (Official Build) (64-bit)。

对应的Chromium问题: https://bugs.chromium.org/p/chromium/issues/detail?id=981259 - Abhishek Garg
3
我为我的粗鲁用语道歉。我已经测试了所有我能找到的PDF阅读器/渲染器/工具,它们的反应不一。坦率地说,我认为这很奇怪。 U+5B50: okular 1.7.2, xreader 1.8.5, Firefox 67 U+2F26: qpdfview 0.4.16, pdfgrep 2.0.1, Chromium 75, pdftotext/poppler-tools 0.72.0 - daxim
1
U+5B50是U+2F26的Unicode规范兼容分解形式(NFKD),但反之不成立。因此,如果您从U+5B50开始,任何Unicode规范化形式都不能将其更改为U+2F26,所以将其更改为U+2F26是奇怪的。 - Mark Tolonen
1
Unicode规范化参考(主要针对Javascript):https://withblue.ink/2019/03/11/why-you-need-to-normalize-unicode-strings.html - ecc521
2
这里是Skia开发人员。 跟踪错误在这里:http://crbug.com/738643 - Hal Canary
显示剩余10条评论
1个回答

4
我的理解是PDF实际上并不包含文档呈现时看到的字符序列,而是字形序列和支持查找表的组合,这些表将这些字形映射回字符代码。在OP的测试用例中,在macOS上使用的CJK字符字体是STSongti-SC-Regular,其字形ID为十六进制0436
我只能在macOS上重现OP的行为。在Linux和Windows上,我看到的字形映射到最初在html文件中的字符:U+5B50。下面是peepdf实用程序的输出示例比较:

enter image description here

字符到字形和字形到字符的转换操作分别在skia的SkFontHost_mac.cpp文件中的onCharsToGlyphs()populate_glyph_to_unicode()方法中完成。在macOS上,这两个方法都依赖于Core Text库中的CTFontGetGlyphsForCharacters()调用,迭代每个可能的字符以构建映射表。
我将这种方法简化为以下测试代码,打印出给定字体的每个字形ID和相应的字符代码:
NSString *fontName = @"STSongti-SC-Regular";
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)fontName, 10.0, NULL);

CFDataRef bitmap = CFCharacterSetCreateBitmapRepresentation(kCFAllocatorDefault, CTFontCopyCharacterSet(fontRef));
CFIndex length = CFDataGetLength(bitmap);

const UInt8* bits = CFDataGetBytePtr(bitmap);

for (int i = 0; i < length; i++) {
    int mask = bits[i];
    if (!mask)
        continue;
    for (int j = 0; j < 8; j++) {
        CGGlyph glyph;
        UniChar unichar = (UniChar)((i << 3) + j);
        if (mask & (1 << j) && CTFontGetGlyphsForCharacters(fontRef, &unichar, &glyph, 1)) {
            NSLog(@"%04x %04x", glyph, unichar);
        }
    }
}

浏览输出结果,我们的字形代码有两个字符编码:

0436 2f26
0436 5b50

首先遇到的是 2f26,这很重要,因为在构建查找表时,如果字形已经有一个字符编码被确定(并且它的值 >= 0x20),则不会被覆盖:

if (CTFontGetGlyphsForCharacters(ctFont, utf16, glyphs, count)) {
    // ...
    if (glyphToUnicode[glyphs[0]] < 0x20) {
        glyphToUnicode[glyphs[0]] = codepoint;
    }
}

因此,我相信最终发生的是:

  1. Chrome 正确确定了 5B50STSongti-SC-Regular 字形 id 为 0436。它在 pdf 中使用这个字形来表示 cjk 字符。
  2. 然后,它通过遍历所有可能的字符来构建 STSongti-SC-Regular 的字形到字符码查找表。由于 0436 映射到两个代码,并且先遇到 2f26,所以记录的就是这个值,而这也是从文档中复制和粘贴时返回的值。

1
为了完成循环,如果有人试图以编程方式读取这些PDF文件。Python有一个normalize函数,可以将字符转换为标准值。https://docs.python.org/2/library/unicodedata.html#unicodedata.normalize对于上述字符,形式值应为“NFKD”。 - Abhishek Garg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接