Puppeteer 生成的 PDF Copy/Paste 文本产生奇怪的字符
Puppeteer generated PDF Copy/Paste text produce weird characters
我正在使用最新版本的 puppeteer 来生成附件中的 PDF。当我在 Adobe Acrobat 中打开它并尝试复制和粘贴文本时,它变成
This is a test string.
进入
Țħįș įș ǻ țěșț șțřįňģ.
这是我生成 PDF 的代码。
const puppeteer = require('puppeteer');
const argv = require('minimist')(process.argv.slice(2));
const fileName = argv.fileName || "page";
const timeout = 90;
(async () => {
var pageUrl = "my-url-here"
const browser = await puppeteer.launch();
const page = await browser.newPage();
function onTimeout() {
console.log("Timed out waiting for data after " + timeout + " seconds.");
process.exit();
}
console.log("Opening " + pageUrl);
await page.goto(pageUrl, {waitUntil: 'networkidle2'});
console.log("Waiting for page to load...");
console.log("Waiting for data to load...");
await page.waitForSelector('#print-report-loaded', {timeout:timeout*1000}).catch(onTimeout);
var fileFullName = fileName + ".pdf";
console.log("Saving PDF as " + fileFullName);
await page.pdf({path: fileFullName});
console.log("PDF saved successfully as " + fileFullName);
await browser.close();
})();
Here is link to the generated PDF
如能知道如何修复,我们将不胜感激!
Acrobat 不会更改 文本,它只是复制为这些字体存储的 Unicode 字符。您看到的 'characters' 是 Type 3 轮廓,它们具有 形式 的 "regular" 字符,但它们相关的 Unicode 代码点确实是重音字符的代码点。
从AcrobatReader和官方PDF规范来看,一切都是"working as designed"。
让我们看看您的 PDF。
为了不必要地使事情复杂化,人们会认为这只需要一种字体,但您的工具生成了 两种 字体:F0
,它将字符代码映射到以下 Unicode代码
<(01)> <( )>
<(0D)> <(.)>
<(26)> <(Ț)>
<(32)> <(ǻ)>
<(35)> <(ě)>
<(37)> <(ģ)>
<(38)> <(ħ)>
<(39)> <(į)>
<(3E)> <(ň)>
<(42)> <(ț)>
和 F1
映射到
<(15)> <(ř)>
<(16)> <(ș)>
字符代码以字符串的形式写出,一次一个字符(中间有一些命令;此处省略,因为不是很相关):
<26><38><39>{16}<01><39>{16}<01><32><01><42><35>{16}<42><01>{16}<42>{15}<39><3E><37><0D>
我指出 <..>
内配对的十六进制代码来自字体 F0
,{..}
来自 F1
。现在,如果你用 Unicode 字符一个一个地替换字符索引,你确实得到了 Unicode 字符串
Țħįș įș ǻ țěșț șțřįňģ.
此处使用的 "fonts" 是 Type 3 PostScript 字体,完全嵌入 PDF 中。例如,字体 #0 定义为
8 0 obj @ 1059 % "F0"
<<
/Type /Font
/Subtype /Type3
/CIDToGIDMap /Identity
/CharProcs
<<
/g0 11 0 R % -> stream
/g1 12 0 R % -> stream
/g26 14 0 R % -> stream
/g32 15 0 R % -> stream
/g35 16 0 R % -> stream
/g37 17 0 R % -> stream
/g38 18 0 R % -> stream
/g39 19 0 R % -> stream
/g3E 20 0 R % -> stream
/g42 21 0 R % -> stream
/gD 13 0 R % -> stream
>>
/Encoding
<<
/Type /Encoding
/Differences [ 0 /g0 /g1 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /gD /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0
/g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g26 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0
/g0 /g32 /g0 /g0 /g35 /g0 /g37 /g38 /g39 /g0 /g0 /g0 /g0 /g3E /g0 /g0 /g0 /g42 ]
>>
/FirstChar 0
/FontBBox [ -1 202 598 -801 ]
/FontDescriptor 10 0 R % -> FontDescriptor (Font)
/FontMatrix [ 0.082254 0 0 -0.082254 0 0 ]
/LastChar 66
/ToUnicode 9 0 R % -> stream
/Widths [ 500 300 0 0 0 0 0 0 0 0 0 0 0 244 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 641 0 0 0 0 0 0 0 0 0 0 0 579 0
0 592 0 664 616 263 0 0 0 0 616 0 0 0 404 ]
>>
endobj
大部分都不重要,除了 编码数组 ,它告诉 字符索引 与什么字形名称相关联,以及CharProcs
数组,它将编码数组中的 names 与实际绘图指令相关联。这是从 "font name plus character index" 显示字符串到 "character index in encoding" 的路由,后者又使用 ToUnicode
数组查找 actual(已报告)每个字符的 Unicode 值。
每个角色本身的绘图指令(对每个 /gX
流的引用)是一系列常规的 move
、line
和 fill
指令 – 再次, 没有什么不寻常的,尽管其他 PDF 引擎更经常包含原始字体,而不仅仅是文字绘图说明。
但是 ToUnicode
table 把复制的东西弄乱了。它没有声明 "character 16#26
maps to Unicode U+0054 'Latin Capital T'",而是指向 "U+021A Latin Capital T with Comma Below"——而且没有明显的原因!这肯定不是 random 翻译,但我找不到 any 合理的解释来解释为什么有人会故意这样编码纯文本......除非有人在那里满足地笑着想,"yes, this is what I had in mind" – 在这种情况下,这将是 故意混淆。
Github 处的 Puppeteer 代码似乎无法处理 PDF 本身,因此它必须由 Chromium 处理,Chromium 在内部使用 Skia PDF 引擎(据报道但由 PDF 二进制 header 证实) , 当最高位被清零时,读取 "D3 EB E9 E1" – "Skia"。它已被报告为错误 as early as 2012,但有 2017 年的报告,这似乎表明这不是他们需要解决的紧迫问题。
我正在使用最新版本的 puppeteer 来生成附件中的 PDF。当我在 Adobe Acrobat 中打开它并尝试复制和粘贴文本时,它变成
This is a test string.
进入
Țħįș įș ǻ țěșț șțřįňģ.
这是我生成 PDF 的代码。
const puppeteer = require('puppeteer');
const argv = require('minimist')(process.argv.slice(2));
const fileName = argv.fileName || "page";
const timeout = 90;
(async () => {
var pageUrl = "my-url-here"
const browser = await puppeteer.launch();
const page = await browser.newPage();
function onTimeout() {
console.log("Timed out waiting for data after " + timeout + " seconds.");
process.exit();
}
console.log("Opening " + pageUrl);
await page.goto(pageUrl, {waitUntil: 'networkidle2'});
console.log("Waiting for page to load...");
console.log("Waiting for data to load...");
await page.waitForSelector('#print-report-loaded', {timeout:timeout*1000}).catch(onTimeout);
var fileFullName = fileName + ".pdf";
console.log("Saving PDF as " + fileFullName);
await page.pdf({path: fileFullName});
console.log("PDF saved successfully as " + fileFullName);
await browser.close();
})();
Here is link to the generated PDF
如能知道如何修复,我们将不胜感激!
Acrobat 不会更改 文本,它只是复制为这些字体存储的 Unicode 字符。您看到的 'characters' 是 Type 3 轮廓,它们具有 形式 的 "regular" 字符,但它们相关的 Unicode 代码点确实是重音字符的代码点。
从AcrobatReader和官方PDF规范来看,一切都是"working as designed"。
让我们看看您的 PDF。
为了不必要地使事情复杂化,人们会认为这只需要一种字体,但您的工具生成了 两种 字体:F0
,它将字符代码映射到以下 Unicode代码
<(01)> <( )>
<(0D)> <(.)>
<(26)> <(Ț)>
<(32)> <(ǻ)>
<(35)> <(ě)>
<(37)> <(ģ)>
<(38)> <(ħ)>
<(39)> <(į)>
<(3E)> <(ň)>
<(42)> <(ț)>
和 F1
映射到
<(15)> <(ř)>
<(16)> <(ș)>
字符代码以字符串的形式写出,一次一个字符(中间有一些命令;此处省略,因为不是很相关):
<26><38><39>{16}<01><39>{16}<01><32><01><42><35>{16}<42><01>{16}<42>{15}<39><3E><37><0D>
我指出 <..>
内配对的十六进制代码来自字体 F0
,{..}
来自 F1
。现在,如果你用 Unicode 字符一个一个地替换字符索引,你确实得到了 Unicode 字符串
Țħįș įș ǻ țěșț șțřįňģ.
此处使用的 "fonts" 是 Type 3 PostScript 字体,完全嵌入 PDF 中。例如,字体 #0 定义为
8 0 obj @ 1059 % "F0"
<<
/Type /Font
/Subtype /Type3
/CIDToGIDMap /Identity
/CharProcs
<<
/g0 11 0 R % -> stream
/g1 12 0 R % -> stream
/g26 14 0 R % -> stream
/g32 15 0 R % -> stream
/g35 16 0 R % -> stream
/g37 17 0 R % -> stream
/g38 18 0 R % -> stream
/g39 19 0 R % -> stream
/g3E 20 0 R % -> stream
/g42 21 0 R % -> stream
/gD 13 0 R % -> stream
>>
/Encoding
<<
/Type /Encoding
/Differences [ 0 /g0 /g1 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /gD /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0
/g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g26 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0
/g0 /g32 /g0 /g0 /g35 /g0 /g37 /g38 /g39 /g0 /g0 /g0 /g0 /g3E /g0 /g0 /g0 /g42 ]
>>
/FirstChar 0
/FontBBox [ -1 202 598 -801 ]
/FontDescriptor 10 0 R % -> FontDescriptor (Font)
/FontMatrix [ 0.082254 0 0 -0.082254 0 0 ]
/LastChar 66
/ToUnicode 9 0 R % -> stream
/Widths [ 500 300 0 0 0 0 0 0 0 0 0 0 0 244 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 641 0 0 0 0 0 0 0 0 0 0 0 579 0
0 592 0 664 616 263 0 0 0 0 616 0 0 0 404 ]
>>
endobj
大部分都不重要,除了 编码数组 ,它告诉 字符索引 与什么字形名称相关联,以及CharProcs
数组,它将编码数组中的 names 与实际绘图指令相关联。这是从 "font name plus character index" 显示字符串到 "character index in encoding" 的路由,后者又使用 ToUnicode
数组查找 actual(已报告)每个字符的 Unicode 值。
每个角色本身的绘图指令(对每个 /gX
流的引用)是一系列常规的 move
、line
和 fill
指令 – 再次, 没有什么不寻常的,尽管其他 PDF 引擎更经常包含原始字体,而不仅仅是文字绘图说明。
但是 ToUnicode
table 把复制的东西弄乱了。它没有声明 "character 16#26
maps to Unicode U+0054 'Latin Capital T'",而是指向 "U+021A Latin Capital T with Comma Below"——而且没有明显的原因!这肯定不是 random 翻译,但我找不到 any 合理的解释来解释为什么有人会故意这样编码纯文本......除非有人在那里满足地笑着想,"yes, this is what I had in mind" – 在这种情况下,这将是 故意混淆。
Github 处的 Puppeteer 代码似乎无法处理 PDF 本身,因此它必须由 Chromium 处理,Chromium 在内部使用 Skia PDF 引擎(据报道但由 PDF 二进制 header 证实) , 当最高位被清零时,读取 "D3 EB E9 E1" – "Skia"。它已被报告为错误 as early as 2012,但有 2017 年的报告,这似乎表明这不是他们需要解决的紧迫问题。