PDF.js 源码解读: encodings
2026年10月27日 · StreamPDF Team

前言
上一篇讲 fonts_utils.js,它负责把「字符码 + 名字数组」变成 glyph id。但名字数组从哪来、字符码对应哪个字形名,还没有交代。答案就在 src/core/encodings.js(301 行):这里存放 PDF 规范预定义的单字节编码表,把 0 到 255 的字符码翻译成字形名。
它在整条链路里位置很靠前:evaluator.js 从字体字典读出 /Encoding 与 /Differences,决定基准编码名;getEncoding 把名字换成表;fonts.js、type1_font.js、cff_font.js 再用这张表建立字符码到 glyph id 的映射。没有这张表,「Tj 里这个字节画哪个字形」就无从谈起。
用一句话概括:它是字体层的编码表层,只负责「code 到 glyph name」这一跳,不解析字形轮廓,也不决定 Unicode。
模块总览
encodings.js 没有导入,也没有类,全部内容可以切成五块:
| 区块 | 关键成员 | 职责 |
|---|---|---|
| 西文基础 | StandardEncoding、MacRomanEncoding、WinAnsiEncoding | 三种最常用的单字节编码 |
| Expert | ExpertEncoding、MacExpertEncoding | 小型大写、旧式数字、上下标与连字 |
| 符号 | SymbolSetEncoding、ZapfDingbatsEncoding | Symbol 与 ZapfDingbats 的内置编码 |
| 分发 | getEncoding | 把编码名换成对应的 256 项数组 |
| 导出 | export 块 | 七张表里只公开六张,MacExpertEncoding 私有 |
它在字体链路里的位置:
// 字体字典 /Encoding、/Differences
// → evaluator.js 解析出 baseEncodingName 与 differences
// → encodings.js getEncoding(名字) → 256 项编码表 (本篇)
// → fonts_utils.js type1FontGlyphMapping(表 + glyphNames) → glyph id
// → fonts.js buildToFontChar(表 + glyphlist) → Unicode
// → font_renderer.js 按 glyph id 取轮廓版本说明:本篇按当前仓库这一版
encodings.js讲解。有两点与旧资料不同。第一,本版没有PDFDocEncoding,PDF 文本串的解码由string_utils.js的PDFStringTranslateTable承担,getEncoding("PDFDocEncoding")会返回null。第二,旧版常见的getExtendedEncoding与「基础表 + 扩展表工厂」的写法在本版不存在:七张表全都是字面量 256 项数组,0x80 到 0x9F 的高位区直接写在数组里。另外,MacExpertEncoding虽然定义在文件中,却没有出现在export块里,只能通过getEncoding("MacExpertEncoding")取得。
编码表在字体链路中的位置
PDF 里的字符码有三条不同的解释路径,encodings.js 只负责第一条:
| 路径 | 输入 | 中间结果 | 最终结果 | 使用场景 |
|---|---|---|---|---|
| 单字节编码 | 1 字节 code | 字形名 | glyph id | Type1、TrueType、Type3 |
| CMap | 1 或 2 字节 code | CID | glyph id | CIDFont、中文日文韩文字体 |
| ToUnicode | 1 或 2 字节 code | Unicode | 文本字符 | 文本提取、复制、搜索 |
三条路径互不替代。一个 CID 字体可以没有 encodings.js 里的任何表,却仍然正常显示;一个简单字体可以没有 /ToUnicode,却仍然能画出字形,只是复制出来的文字可能是乱码。encodings.js 的职责边界就在这里:它只描述「这个字节在视觉上对应哪个字形名」,不描述这个字形名最终念什么。
表的形状:256 项、下标即码位
七张表都是普通的 JavaScript 数组,长度固定 256,下标就是字符码:
// prettier-ignore
const StandardEncoding = [
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
// ...
"space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
"ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus",
"comma", "hyphen", "period", "slash", "zero", "one", "two", "three",
// ...
"asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
// ...
"exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section",
"currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
// ...
"Lslash", "Oslash", "OE", "ordmasculine", "", "", "", "", "", "ae",
"", "", "", "dotlessi", "", "", "lslash", "oslash", "oe", "germandbls", "",
"", "", ""];空字符串 "" 表示该码位在这个编码里没有定义。用空串而不是 undefined 或 null,有两个直接好处:数组长度始终是 256,任何下标都能安全读取;调用方可以统一用 !== "" 判断「这个码位有没有字形名」,不需要区分「数组太短」与「值缺失」。
// prettier-ignore 不是装饰。七张表都是逐行手工排布的数据,Prettier 会按最大行宽重新折行,破坏「同一段码位排在一起」的可读性;关掉格式化,能让码位区间一眼可辨。
按码位区间看,所有表的结构一致:
| 区间 | 含义 | 典型内容 |
|---|---|---|
| 0x00-0x1F | 控制字符 | 全部为空串 |
| 0x20-0x7E | ASCII 可打印区 | 字母、数字、标点 |
| 0x7F-0x9F | 高位区,差异最大 | WinAnsi 放 Euro 与排版符号,MacRoman 放重音字母 |
| 0xA0-0xFF | 拉丁扩展、货币与符号 | 重音字母、dagger、florin、germandbls |
三种西文编码
StandardEncoding、MacRomanEncoding、WinAnsiEncoding 覆盖同样的 ASCII 区,差异集中在少数标点和整个高位区。先看 WinAnsi 的高位段:
"asciitilde", "bullet", "Euro", "bullet", "quotesinglbase", "florin",
"quotedblbase", "ellipsis", "dagger", "daggerdbl", "circumflex",
"perthousand", "Scaron", "guilsinglleft", "OE", "bullet", "Zcaron",
"bullet", "bullet", "quoteleft", "quoteright", "quotedblleft",
"quotedblright", "bullet", "endash", "emdash", "tilde", "trademark",
"scaron", "guilsinglright", "oe", "bullet", "zcaron", "Ydieresis", "space",这段从 0x7E 开始。0x80 是 Euro,0x82 是 quotesinglbase,0x85 是 ellipsis,0x91 到 0x94 是弯引号。中间反复出现的 bullet 不是真实字形,而是 Windows 代码页里未定义位置的占位符;PDF.js 照抄这套映射,是为了和 Acrobat、Word 生成的 PDF 保持一致。
MacRoman 在同一区间放的是重音字母:
"", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis",
"Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde",
"aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis",
// ...
"Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot",
"radical", "florin", "approxequal", "Delta", "guillemotleft",
"guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde", "OE",
// ...
"Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple",
"Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex",注意 0xCA 位置的 space 与 0xF0 位置的 apple:MacRoman 用 space 表示不换行空格,apple 是苹果标志字形。这些名字只在 Mac 字体里有意义,放到别的字体上通常落回 .notdef。
把三个表放在同一批码位上对比,差异更直观:
| 码位 | StandardEncoding | MacRomanEncoding | WinAnsiEncoding |
|---|---|---|---|
| 0x27 | quoteright | quotesingle | quotesingle |
| 0x60 | quoteleft | grave | grave |
| 0x7F | 空 | 空 | bullet |
| 0x80 | 空 | Adieresis | Euro |
| 0xA0 | 空 | dagger | space |
| 0xB5 | 空 | mu | mu |
| 0xCA | ring | space | Ecircumflex |
| 0xF0 | 空 | apple | eth |
这张表解释了一个常见疑问:为什么同一段 Word 导出的文字在 PDF.js 里复制出来会变成弯引号。因为 WinAnsi 把 0x91 到 0x94 定义成排版引号,而 StandardEncoding 在同样位置没有定义;编码选错,名字就不同,最终 Unicode 也不同。
设计要点:编码表只描述「code 到字形名」,不描述「名字到 Unicode」。Unicode 解析交给
glyphlist.js,于是同一张表可以同时服务字形定位(名字到 glyph id)与文本提取(名字到 Unicode),不需要为两条路径维护两份数据。代价是名字必须足够规范,遇到uniXXXX之类的非标准名还得靠recoverGlyphName兜底。
符号与 Expert 编码
后四张表服务特殊字体,命名风格与西文表明显不同。Symbol 字体用的是希腊字母与数学符号:
"space", "exclam", "universal", "numbersign", "existential", "percent",
"ampersand", "suchthat", "parenleft", "parenright", "asteriskmath", "plus",
"comma", "minus", "period", "slash", "zero", "one", "two", "three", "four",
// ...
"congruent", "Alpha", "Beta", "Chi", "Delta", "Epsilon", "Phi", "Gamma",
"Eta", "Iota", "theta1", "Kappa", "Lambda", "Mu", "Nu", "Omicron", "Pi",
"Theta", "Rho", "Sigma", "Tau", "Upsilon", "sigma1", "Omega", "Xi", "Psi",
"Zeta", "bracketleft", "therefore", "bracketright", "perpendicular",
// ...
"Euro", "Upsilon1", "minute", "lessequal", "fraction", "infinity", "florin",
"club", "diamond", "heart", "spade", "arrowboth", "arrowleft", "arrowup",
"arrowright", "arrowdown", "degree", "plusminus", "second", "greaterequal",universal、existential、suchthat、therefore 是数学逻辑符号的标准字形名;theta1、sigma1、Upsilon1 里的一后缀表示「同一字母的另一种字形」,不是数字 1。club、diamond、heart、spade 是扑克花色,说明这张表覆盖的不只是数学。
fonts.js 还用它反向构造 Symbol 字体的 glyph id 编码:
const getSymbolGlyphIdEncoding = getLookupTableFactory(t => {
let glyphId = 3;
for (const [firstCharCode, lastCharCode] of [
[0x20, 0x7e],
[0xa1, 0xfe],
]) {
for (let charCode = firstCharCode; charCode <= lastCharCode; charCode++) {
t[glyphId++] = SymbolSetEncoding[charCode];
}
}
}, /* useArray = */ true);这段代码从 0x20 到 0x7E、0xA1 到 0xFE 顺序遍历,把每个码位的字形名写到 glyphId 递增的位置。它利用了 Symbol 字体「字形按码位顺序排列」这一事实,同时跳过 0x7F 到 0xA0 的空洞,以及 glyph id 0 到 2 的 .notdef、.null、nonmarkingreturn。同一张表在这里被当成「码位顺序」使用,下标不再是 glyph id,而是由外部计数器维护。
ZapfDingbats 更彻底:字形名就是 Adobe 的编号,没有语义:
"space", "a1", "a2", "a202", "a3", "a4", "a5", "a119", "a118", "a117",
"a11", "a12", "a13", "a14", "a15", "a16", "a105", "a17", "a18", "a19",
// ...
"a57", "a58", "a59", "a60", "a61", "a62", "a63", "a64", "a65", "a66",
"a67", "a68", "a69", "a70", "a71", "a72", "a73", "a74", "a203", "a75",编号并不按码位递增,a202 出现在 a2 后面,a203 夹在 a74 与 a75 之间。这些编号必须原样保留,因为 ZapfDingbats 字体的 post 表与内置编码就是用它们对齐的。
这些 aNN 名字也不在普通字形表里。glyphlist.js 为此单独导出了 getDingbatsGlyphsUnicode,与 getGlyphsUnicode 并列;fonts.js 在建立 ZapfDingbats 的 Unicode 映射时使用前者。编码表与字形表必须成对使用,拿错表会把 a1 解析成空值,文本提取随之丢字。
Expert 系列面向专业排版,装的是小型大写、旧式数字、上下标与连字:
"space", "exclamsmall", "Hungarumlautsmall", "", "dollaroldstyle",
"dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior",
"parenrightsuperior", "twodotenleader", "onedotenleader", "comma",
"hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
// ...
"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall",
"Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall",
// ...
"questiondownsmall", "oneeighth", "threeeighths", "fiveeighths",
"seveneighths", "onethird", "twothirds", "", "", "zerosuperior",ExpertEncoding 与 MacExpertEncoding 覆盖的内容相似,码位安排不同。MacExpertEncoding 在 0x23 位置是 centoldstyle,而 ExpertEncoding 在同一位置是空串:
"space", "exclamsmall", "Hungarumlautsmall", "centoldstyle",
"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
"parenleftsuperior", "parenrightsuperior", "twodotenleader",
"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",Ethsmall 等名字在两张表里也落在不同码位。两张表都不能靠猜,必须按名字查。
| 表 | 码位范围 | 命名风格 | 示例 |
|---|---|---|---|
SymbolSetEncoding | 0x20-0xFE | 数学、希腊、箭头 | universal、theta1、arrowboth |
ZapfDingbatsEncoding | 0x20-0xFE | Adobe 编号 | a1、a202、a191 |
ExpertEncoding | 0x20-0xFF | 小型大写与旧式数字 | Asmall、zerooldstyle、oneeighth |
MacExpertEncoding | 0x20-0xFF | 类似 Expert,码位不同 | centoldstyle、Ethsmall |
编码名从哪里来:/Encoding 的三种形态
编码表本身不会出现在 PDF 里。PDF 里出现的是 /Encoding 条目,它可能是名字,也可能是字典。evaluator.js 负责把这几种形态收敛成「基准编码名 + 差异数组」:
if (dict.has('Encoding')) {
encoding = dict.get('Encoding');
if (encoding instanceof Dict) {
baseEncodingName = encoding.get('BaseEncoding');
baseEncodingName = baseEncodingName instanceof Name ? baseEncodingName.name : null;
// Load the differences between the base and original
if (encoding.has('Differences')) {
const diffEncoding = encoding.get('Differences');
let index = 0;
for (const entry of diffEncoding) {
const data = xref.fetchIfRef(entry);
if (typeof data === 'number') {
index = data;
} else if (data instanceof Name) {
differences[index++] = data.name;
} else {
throw new FormatError(`Invalid entry in 'Differences' array: ${data}`);
}
}
}
} else if (encoding instanceof Name) {
baseEncodingName = encoding.name;
} else {
// ...
}
// According to table 114 if the encoding is a named encoding it must be
// one of these predefined encodings.
if (
baseEncodingName !== 'MacRomanEncoding' &&
baseEncodingName !== 'MacExpertEncoding' &&
baseEncodingName !== 'WinAnsiEncoding'
) {
baseEncodingName = null;
}
}/Differences 的解析采用「数字重置下标、名字写入并自增」的稀疏数组写法:数组里的数字项表示下一个名字要写到的码位,名字项写入后下标加一。因此 120 /a /b 表示 120 放 a、121 放 b,中间不需要写满 256 项。
最后那段白名单最容易被忽略:即使 /Encoding 是 /StandardEncoding 这样的合法名字,只要不在 MacRomanEncoding、MacExpertEncoding、WinAnsiEncoding 三者之内,baseEncodingName 也会被置空。最终形态只有四种:
/Encoding 形态 | baseEncodingName | differences | 后续处理 |
|---|---|---|---|
| 名字,且在三个白名单内 | 对应名字 | 空 | 直接作为默认编码 |
字典,/BaseEncoding 在白名单内 | 对应名字 | 可能有 | 默认编码加差异覆盖 |
字典,/BaseEncoding 缺失或不在白名单 | null | 可能有 | 走符号/非符号默认规则 |
| 其它类型 | null | 空 | 忽略错误或抛 FormatError |
这也解释了 getEncoding 为什么需要七个分支、而 evaluator.js 实际只会传三个名字:另外四张表由字体程序解析器(type1_parser.js、cff_parser.js)直接引用,或由 fonts.js 在 TrueType 分支按显式白名单取用。分发函数服务所有调用方,白名单只约束其中一条路径。
getEncoding:七个名字的分发
七张表通过一个很短的 switch 暴露给外部:
function getEncoding(encodingName) {
switch (encodingName) {
case 'WinAnsiEncoding':
return WinAnsiEncoding;
case 'StandardEncoding':
return StandardEncoding;
case 'MacRomanEncoding':
return MacRomanEncoding;
case 'SymbolSetEncoding':
return SymbolSetEncoding;
case 'ZapfDingbatsEncoding':
return ZapfDingbatsEncoding;
case 'ExpertEncoding':
return ExpertEncoding;
case 'MacExpertEncoding':
return MacExpertEncoding;
default:
return null;
}
}它的契约只有两条:名字精确匹配,未知名字返回 null。不做大小写折叠,不做别名,也不做前缀匹配。PDF 规范里这些名字是固定的,拼错就是拼错,返回 null 比猜一个近似值更安全。
调用方对 null 的处理并不统一,这一点读源码时要留意:
| 调用位置 | 传入的名字来源 | 对 null 的处理 |
|---|---|---|
evaluator.js 的 defaultEncoding | /BaseEncoding 或 /Encoding 名 | 存入属性,后续按假值判断回退 |
fonts.js 的 TrueType 分支 | 仅 MacRomanEncoding 与 WinAnsiEncoding | 不会传其它名字,因此不会得到 null |
type1_parser.js 的内置编码 | 字体程序里的 /Encoding 名 | 交给后续映射,未知名等同没有编码 |
cff_parser.js 的预定义 charset | 不使用名字 | 直接引用 ExpertEncoding 与 StandardEncoding |
导出面比定义面小,是这张表最容易被忽略的细节:
| 名字 | getEncoding 返回 | 能否直接导入 |
|---|---|---|
WinAnsiEncoding | 是 | 是 |
StandardEncoding | 是 | 是 |
MacRomanEncoding | 是 | 是 |
SymbolSetEncoding | 是 | 是 |
ZapfDingbatsEncoding | 是 | 是 |
ExpertEncoding | 是 | 是 |
MacExpertEncoding | 是 | 否,模块内私有 |
cff_parser.js 需要 ExpertEncoding 时直接导入;没有任何文件导入 MacExpertEncoding,它只作为 switch 的一个分支存在。这种不对称不是疏漏:CFF 的预定义 charset 只用 Expert 与 Standard 两种,MacExpert 只在字体字典声明时经 getEncoding 取用,减少导出面可以让依赖关系更清楚。
消费方:默认编码、内置编码与 ToUnicode
evaluator.js 把编码名变成属性,真正的选择留到字形映射阶段:
if (baseEncodingName) {
properties.defaultEncoding = getEncoding(baseEncodingName);
} else {
let isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
const isNonsymbolicFont = !!(properties.flags & FontFlags.Nonsymbolic);
// ...
// According to "Table 114" in section "9.6.6.1 General" (under
// "9.6.6 Character Encoding") of the PDF specification, a Nonsymbolic
// font should use the `StandardEncoding` if no encoding is specified.
encoding = StandardEncoding;这段代码对应规范里的一条默认规则:非符号字体没有 /Encoding 时使用 StandardEncoding。properties.defaultEncoding 只是「候选基准」,后面 type1FontGlyphMapping 还会结合 /Differences 与字体程序内置编码再决定最终映射。
fonts.js 则把编码表转成 Unicode 表,供文本提取使用:
function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
const toFontChar = [];
let unicode;
for (let i = 0, ii = encoding.length; i < ii; i++) {
unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
if (unicode !== -1) {
toFontChar[i] = unicode;
}
}
for (const charCode in differences) {
unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
if (unicode !== -1) {
toFontChar[+charCode] = unicode;
}
}
return toFontChar;
}同一个 encoding 数组,在 type1FontGlyphMapping 里被拿去 glyphNames.indexOf,在这里被拿去查 glyphsUnicodeMap。两个消费者共享数据,却不共享结果:前者得到 glyph id,后者得到 Unicode。
CFF 的预定义 charset 也直接使用这两张表,但方向相反:
if (pos === 0 || pos === 1) {
predefined = true;
format = pos;
const baseEncoding = pos ? ExpertEncoding : StandardEncoding;
for (i = 0, ii = charset.length; i < ii; i++) {
const index = baseEncoding.indexOf(charset[i]);
if (index !== -1) {
encoding[index] = i;
}
}
}CFF 的 charset 是「按 glyph id 排列的名字数组」,这里拿 indexOf 反查码位,再把该码位映射到当前 glyph id。也就是说,同一张表既能正着用(码位取名字),也能反着用(名字找码位)。
文本提取与字形定位是两条平行链路,不要混成一条优先级:
| 维度 | 主要输入 | 输出 | 缺失时的回退 |
|---|---|---|---|
| 字形定位 | /Encoding、/Differences、字体内置编码 | glyph id | StandardEncoding 或 .notdef |
| 文本提取 | /ToUnicode CMap | Unicode | 编码表加 glyphlist 的字形名反查 |
/ToUnicode 存在时,文本提取直接用它,encodings.js 完全不参与;只有没有 /ToUnicode 时,才用编码表把码位变成字形名,再查 glyphlist 得到 Unicode。这就是为什么删掉 /ToUnicode 不会影响显示,却会让复制出的文字变乱。
设计要点:编码表刻意不包含「code 到 Unicode」的对应关系。Unicode 是可变的解释结果,同一个字形在不同字体的
ToUnicode里可能给出不同码位;字形名是稳定的中间层。把这一层单独抽出来,使ToUnicode可以覆盖它,而不用修改任何一张预定义表。
总结
encodings.js 是字体层的第一跳翻译表:字节先变成字形名,之后才可能变成 glyph id 或 Unicode。它不解析字体程序,也不参与绘制,却决定了「同一个字节到底该长什么样」。
它在整条链路中的位置
| 阶段 | 模块 | 输入 | 输出 |
|---|---|---|---|
| 读字典 | evaluator.js | /Encoding、/Differences | 基准编码名与差异数组 |
| 取表 | encodings.js | 编码名 | 256 项编码表 |
| 建映射 | fonts.js、type1_font.js、cff_font.js | 编码表、字形名数组 | 字符码到 glyph id |
| 文本 | fonts.js 的 buildToFontChar | 编码表、glyphlist | 字符码到 Unicode |
| 绘制 | font_renderer.js | glyph id | 字形轮廓 |
用一句话串起来:evaluator 选出编码名,encodings.js 把名字变成表,fonts.js 再把表变成 glyph id 或 Unicode,绘制层只认 glyph id。
五条设计原则
- 数据与逻辑分离:301 行里绝大部分是数组字面量,唯一的函数是
getEncoding。表可以逐字节对照规范检查,逻辑只有一个switch,没有隐藏分支。 - 下标就是码位:不建
Map,不做字符串键查找,table[charCode]一次寻址完成。单字节编码的取值范围固定,数组是最贴合的容器。 - 空串是合法占位:未定义码位写
"",数组长度始终 256。调用方用!== ""判断有无名字,不需要额外处理越界。 - 名字是唯一外部契约:
getEncoding只认七个精确名字,未知输入返回null。拼错编码名不会悄悄命中近似表,而是让上层明确走默认回退。 - 一层只做一件事:这里不解析 CMap,不做 Unicode 映射,也不决定
/Differences的优先级。编码表只回答「这个字节对应哪个字形名」,其余判断交给fonts_utils.js与fonts.js。
理解了 encodings.js,再看 fonts_utils.js 的 type1FontGlyphMapping,就能把「基准编码 + /Differences + 内置编码」三条来源串起来;再往上看 fonts.js 的 buildToFontChar,也能明白为什么同一个码位在不同字体里会得到不同的 Unicode。下一篇可以继续往字体格式内部走,看 Type1 的 charstring 是如何被解释成轮廓的。
StreamPDF 的设计原则: 能前置处理的绝对不放到解析的时候才做, 由服务端一次解决所有性能问题. 浏览器只获取&渲染必须数据.
相关文章
- PDF.js 从 demo 深入浅出 10: CanvasGraphics 与 operatorList 绘制2026年10月27日
- PDF.js 从 demo 深入浅出 9: 网络流与 Range 请求2026年10月27日
- PDF.js 从 demo 深入浅出 8: PDFWorker.create 与真假 Worker 握手2026年10月27日
- PDF.js 从 demo 深入浅出 7: WorkerTransport 初始化2026年10月27日
- PDF.js 从 demo 深入浅出 6: MessageHandler 跨线程通讯2026年10月27日
- PDF.js 从 demo 深入浅出 5: render 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 4: getViewport 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 3: getPage 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 2: getDocument 方法全貌2026年10月27日
- PDF.js 源码解读: TextLayer2026年10月27日
- PDF.js 源码解读: font_loader2026年10月27日
- PDF.js 源码解读: obj_bin_transform_display2026年10月27日
- PDF.js 源码解读: PDFObjects2026年10月27日
- PDF.js 源码解读: 网络流2026年10月27日
- PDF.js 源码解读: internal_evt.js2026年10月27日
- PDF.js 源码解读: math_clamp.js2026年10月27日
- PDF.js 源码解读: css_utils.js2026年10月27日
- PDF.js 源码解读: scripting_utils2026年10月27日
- PDF.js 源码解读: MurmurHash3_642026年10月27日
- PDF.js 源码解读: obj_bin_transform_utils2026年10月27日
- PDF.js 源码解读: image_utils.js2026年10月27日
- PDF.js 源码解读: BasePDFStream2026年10月27日
- PDF.js 源码解读: 共享工具层2026年10月27日
- PDF.js 源码解读: MessageHandler2026年10月27日
- PDF.js 源码解读: api2026年10月27日
- PDF.js 源码解读: fonts2026年10月27日
- PDF.js 源码解读: glyphlist2026年10月27日
- PDF.js 源码解读: binary_cmap2026年10月27日
- PDF.js 源码解读: cmap2026年10月27日
- PDF.js 源码解读: metrics2026年10月27日
- PDF.js 源码解读: standard_fonts2026年10月27日
- PDF.js 源码解读: fonts_utils2026年10月27日
- PDF.js 源码解读: default_appearance2026年10月27日
- PDF.js 源码解读: metadata_parser2026年10月27日
- PDF.js 源码解读: name_number_tree2026年10月27日
- PDF.js 源码解读: struct_tree2026年10月27日
- PDF.js 源码解读: catalog2026年10月27日
- PDF.js 源码解读: pdf_manager2026年10月27日
- PDF.js 源码解读: object_loader2026年10月27日
- PDF.js 源码解读: obj_bin_transform_core2026年10月27日
- PDF.js 源码解读: crypto2026年10月27日
- PDF.js 源码解读: core_utils2026年10月27日
- PDF.js 源码解读: primitives2026年10月27日
- PDF.js 源码解读: worker_stream2026年10月27日
- PDF.js 源码解读: ccitt_stream2026年10月27日
- PDF.js 源码解读: jpx_stream2026年10月27日
- PDF.js 源码解读: jbig2_stream2026年10月27日
- PDF.js 源码解读: jpeg_stream2026年10月27日
- PDF.js 源码解读: decrypt_stream2026年10月27日
- PDF.js 源码解读: brotli_stream2026年10月27日
- PDF.js 源码解读: run_length_stream2026年10月27日
- PDF.js 源码解读: ascii_hex_stream2026年10月27日
- PDF.js 源码解读: ascii_85_stream2026年10月27日
- PDF.js 源码解读: lzw_stream2026年10月27日
- PDF.js 源码解读: predictor_stream2026年10月27日
- PDF.js 源码解读: flate_stream2026年10月27日
- PDF.js 源码解读: decode_stream2026年10月27日
- PDF.js 源码解读: chunked_stream2026年10月27日
- PDF.js 源码解读: stream2026年10月27日
- PDF.js 源码解读: base_stream2026年10月27日
- PDF.js 深入浅出 11: Parser2026年10月26日
- PDF.js 深入浅出 10: XRef2026年10月26日
- PDF.js 深入浅出 9: CanvasGraphics2026年10月26日
- PDF.js 深入浅出 8: Document 与 Page2026年10月26日
- AI 时代, 我们到底应该做什么才能活下去(StreamPDF 创立有感)2026年10月26日
- PDF.js 深入浅出 7: PartialEvaluator2026年10月25日
- PDF.js 深入浅出 6: PDFManager.initDocument2026年10月25日
- PDF.js 深入浅出 5: PDFFetchStream 和 PDFWorkerStream2026年10月25日
- PDF.js 深入浅出 4: MessageHandler2026年10月22日
- PDF.js 深入浅出 3: getViewport & render2026年10月22日
- PDF.js 从 demo 深入浅出 1: 自定义 viewer 渲染 PDF2026年10月19日
- PDF.js 深入浅出 2: getDocument & getPage2026年10月19日
- PDF.js 深入浅出 1: PDF.js 的架构设计2026年10月16日