PDF.js Source Walkthrough: name_number_tree
October 27, 2026 · StreamPDF Team

Introduction
This is the article in the PDF.js source walkthrough series about name and number trees. It covers src/core/name_number_tree.js (169 lines).
In the PDF specification, a lot of data that is looked up by name or by number is not stored in an ordinary dictionary but in a hierarchical, ordered tree that can reference objects across the file. Typical examples:
- document-level named destinations (
/Destsunder/Names), mapping names used by bookmarks to page positions; - attachments (
/EmbeddedFiles), mapping file names to file streams; - document-level JavaScript (
/JavaScript), mapping script names to action dictionaries; - page labels (
/PageLabels), mapping page numbers to label styles; - the parent tree in a structure tree (
/ParentTree), mapping structure element numbers to arrays of content items.
These structures come in two kinds: those whose keys are strings are called NameTree, and those whose keys are integers are called NumberTree. The specification treats them in two sections (7.9.6 and 7.9.7), but their node shapes are nearly identical, so PDF.js writes a single base class NameOrNumberTree and then two subclasses of about ten lines each that just fix the key field name.
In one sentence, the module's job is: flatten an ordered tree that may be spread across several indirect objects into a single Map, or perform one binary search for a single key.
Module Overview
name_number_tree.js is short and splits into four parts:
| Block | Key members | Responsibility |
|---|---|---|
| Base class | NameOrNumberTree constructor | Store the root, the XRef and the key field name |
| Bulk read | getAll | Traverse the whole tree breadth-first, return a Map |
| Single-key read | getRaw, get | Binary-search down Limits, then inside the leaf |
| Concrete trees | NameTree, NumberTree | Decide whether the key field is Names or Nums |
Where it sits in the chain:
Catalog / StructTreeRoot / PDFEditor
→ new NameTree(root, xref)
→ getAll() (flatten the whole tree into a Map)
→ get(key) / getRaw(key) (single-key binary search)
→ XRef.fetchIfRef (turn a Ref into the real object)
→ Dict / array / page destinationVersion note: This article follows exactly the version of
name_number_tree.jsin the current repository. The constructor isconstructor(root, xref, type)and has only three fields,root,xrefand_type; there are nodict,parentorrefparameters, no private#map, nohas()method and no "build once lazily, reuse afterwards" cache.getAlltraverses again and builds a newMapon every call. Theparent/refcycle detection common in older versions is replaced here by theRefSetinsidegetAlland the depth cap insidegetRaw. Do not apply APIs from other versions you remember when reading the source.
One Tree, Two Kinds of Keys
Start with the tree itself. NameTree and NumberTree nodes come in only two shapes:
- Interior nodes: they carry a
Kidsarray whose elements are indirect references or dictionaries pointing at child nodes; - Leaf nodes: they carry a
NamesorNumsarray laid out as key, value, key, value.
A leaf is just an ordinary array in the source, so a key-value pair is two adjacent elements rather than an object:
// NameTree leaf (illustrative)
{
Names: ['Alpha', 12, 'Beta', 13];
}
// NumberTree leaf (illustrative)
{
Nums: [0, 12, 1, 13];
}The PDF specification requires keys to be ordered: NameTree by the byte order of the string in ascending order, NumberTree by numeric value in ascending order. This ordering is not decoration; it is the precondition that lets getRaw perform a binary search.
| Dimension | NameTree | NumberTree |
|---|---|---|
| Key type | String (a PDF string, not a name) | Integer |
| Leaf field | Names | Nums |
| Sort order | Ascending byte order | Ascending numeric value |
Base _type | "Names" | "Nums" |
| Typical uses | Named destinations, attachments, JavaScript | Page labels, structure tree parent tree |
Both trees share the same node structure; they differ only in the name of the key field. The base class therefore writes all the logic once and each subclass passes in a single string.
The Constructor
The base constructor is only a dozen lines long, but it does two things: refuse direct instantiation and store three fields.
class NameOrNumberTree {
constructor(root, xref, type) {
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
this.constructor === NameOrNumberTree
) {
unreachable("Cannot initialize NameOrNumberTree.");
}
this.root = root;
this.xref = xref;
this._type = type;
}The fields are easy to list:
| Field | Type | Role |
|---|---|---|
root | Dict or Ref | The tree root. It may be a plain dictionary or an indirect reference |
xref | XRef | Dereferencing and object retrieval. Every Ref goes through it |
_type | String | The leaf field name. "Names" or "Nums", supplied by the subclass |
root is allowed to be a Ref, which matters. Callers usually pull the root out of a parent dictionary with getRaw, for example obj.getRaw("Dests"); what comes back may be 12 0 R rather than an already expanded dictionary. The base class does not rush to dereference it, keeping the Ref as is and handing it to xref.fetchIfRef only during traversal. Construction therefore performs no I/O and cannot throw MissingDataException just because the root lives in a network chunk that has not arrived.
The constructor's self-check only takes effect in test builds: when PDFJSDev.test("TESTING") is true and this.constructor is exactly the base class, it calls unreachable to throw. In production builds that check is preprocessed away, leaving no runtime cost. Its purpose is not to defend against users but to remind maintainers: the base class is not a usable tree, and NameTree or NumberTree must supply _type.
Design note: The constructor parses nothing.
rootmay be aRefthat has not been fetched, andxrefis merely stored. The real object retrieval is deferred intogetAllandgetRaw. As a resultnew NameTree(...)is a pure in-memory operation that can be created safely at any stage, and exceptions from missing network chunks only surface when data is actually read, where the caller can retry.
Reading Interfaces
The getAll Method
getAll has a straightforward goal: no matter how many levels the tree has or whether its nodes are scattered through object streams, it returns a single Map from key to value.
getAll(isRaw = false) {
const map = new Map();
if (!this.root) {
return map;
}
const xref = this.xref;
// Reading Name/Number tree.
const processed = new RefSet();
if (this.root instanceof Ref) {
processed.put(this.root);
}
const queue = [this.root];
for (const node of queue) {
const obj = xref.fetchIfRef(node);
if (!(obj instanceof Dict)) {
continue;
}
if (obj.has("Kids")) {
const kids = obj.get("Kids");
if (!Array.isArray(kids)) {
continue;
}
for (const kid of kids) {
if (kid instanceof Ref) {
if (processed.has(kid)) {
throw new FormatError(`Duplicate entry in "${this._type}" tree.`);
}
processed.put(kid);
}
queue.push(kid);
}
continue;
}
const entries = obj.get(this._type);
if (!Array.isArray(entries)) {
continue;
}
for (let i = 0, ii = entries.length; i < ii; i += 2) {
map.set(
isRaw ? entries[i] : xref.fetchIfRef(entries[i]),
isRaw ? entries[i + 1] : xref.fetchIfRef(entries[i + 1])
);
}
}
return map;
}There are several details in this code worth expanding.
Breadth-first, not recursion
queue is an ordinary array and for (const node of queue) uses the array iterator. The array iterator re-reads length on every step, so nodes appended with queue.push(kid) inside the loop body are visited in later iterations. This is a queue that grows while it is traversed, and it needs no extra index variable.
The traversal order is breadth-first: root, then all of the root's children, then their children. Order does not matter to getAll because the result is a Map, but it does matter to duplicate detection: a Ref that appears a second time along any path is caught by the same processed set.
processed guards duplicates, not cycles
processed is a RefSet and records only nodes that are Refs. If the root is a Ref it is added first; each kid that is a Ref is checked and then added. The second encounter with the same reference throws a FormatError whose message includes _type, so it is easy to tell whether the NameTree or the NumberTree is at fault.
There is no notion of a current path here, only a global set. So it detects not just cycles but also DAG shapes where two different parents point at the same child. The specification forbids sharing tree nodes, and the source chooses to reject it strictly rather than tolerate it and deduplicate afterwards.
If the root is a plain Dict it is not recorded; if some child points back to that root dictionary through a reference, the reference itself is still recorded and checked. Only a pure dictionary cycle with no references at all can slip through, and such a structure cannot be formed through object references in a PDF, so it does not occur in practice.
Interior nodes and leaves
The decision is based on obj.has("Kids"):
| Node situation | What getAll does |
|---|---|
Has Kids and it is an array | Enqueue all children, then continue without reading this node's entries |
Has Kids but it is not an array | Skip the whole node |
No Kids, has Names / Nums and it is an array | Write into the Map with a step of 2 |
No Kids, key field is not an array | Skip the whole node |
fetchIfRef result is not a dictionary | Skip the whole node |
The specification's convention is that a node with Kids is always an interior node. The source does not check for malformed nodes that have both Kids and Names; it simply gives Kids priority. That way, even if a file writes an extra leaf field, no entries are collected twice.
The two meanings of isRaw
isRaw decides whether keys and values are dereferenced:
isRaw | Key | Value | Use case |
|---|---|---|---|
false | xref.fetchIfRef(key) | xref.fetchIfRef(value) | Normal read; the target object is returned |
true | The raw key (may be a Ref) | The raw value (may be a Ref) | The editor needs the original reference structure when saving |
Catalog uses the default false for attachments, destinations and JavaScript; PDFEditor uses true when reading ParentTree and IDTree because it needs to carry references into the edit data unchanged, and the hierarchy must not be lost when the file is written back.
Duplicate keys do not raise an error
processed only checks node references, not the keys inside leaves. If two leaves contain the same key, Map.set silently overwrites and the value visited later wins. The specification forbids duplicate keys, but PDF.js chooses the lenient policy here to avoid paying for an extra Set lookup. Callers that truly need strictness handle it in their own upper layer.
Design note:
getAll's fault-tolerance policy is to skip the bad node and keep traversing; only a duplicate reference aborts. It does not try to repair a damaged tree, only to read as much as it can. For a PDF viewer that may receive files from arbitrary producers, that is more practical than invalidating the whole tree because of one bad spot.
The getRaw Method
getRaw handles single-key lookup. It builds no table; instead it performs two layers of binary search along the tree's Limits: first locate the child within Kids that may contain the target key, then find the exact match in the leaf's key-value array.
Layer one: descend along Kids
getRaw(key) {
if (!this.root) {
return null;
}
const xref = this.xref;
let kidsOrEntries = xref.fetchIfRef(this.root);
let loopCount = 0;
const MAX_LEVELS = 10;
// Perform a binary search to quickly find the entry that
// contains the key we are looking for.
while (kidsOrEntries.has("Kids")) {
if (++loopCount > MAX_LEVELS) {
warn(`Search depth limit reached for "${this._type}" tree.`);
return null;
}
const kids = kidsOrEntries.get("Kids");
if (!Array.isArray(kids)) {
return null;
}
let l = 0,
r = kids.length - 1;
while (l <= r) {
const m = (l + r) >> 1;
const kid = xref.fetchIfRef(kids[m]);
const limits = kid.get("Limits");
if (key < xref.fetchIfRef(limits[0])) {
r = m - 1;
} else if (key > xref.fetchIfRef(limits[1])) {
l = m + 1;
} else {
kidsOrEntries = kid;
break;
}
}
if (l > r) {
return null;
}
}Limits is an array every non-root node must carry, holding the smallest and largest key of its subtree. The source turns the question of whether the key could be in this child into two comparisons:
| Comparison result | Action |
|---|---|
| Target key is less than the left bound | r = m - 1, look in the left half |
| Target key is greater than the right bound | l = m + 1, look in the right half |
| Falls between the bounds (inclusive) | Select this child and break out |
After selecting a child, kidsOrEntries = kid and the outer while checks whether that child still has Kids. If it is already a leaf, the loop ends naturally.
MAX_LEVELS = 10 is the depth cap. A specification tree can be arbitrarily deep, but real files are only two or three levels; more than ten levels is almost certainly corruption or a malicious cycle. Unlike getAll, this path does not track visited nodes with a RefSet; it falls back to a fixed limit. The cost is that a legal but unusually deep file returns null; the benefit is that no set has to be maintained on every descent.
After the loop there is an easily overlooked check:
if (l > r) {
return null;
}A binary search leaves l and r in a normal relationship only when it found the child and breaks. If the loop ended because l passed r, no child's Limits covered the target key, and kidsOrEntries still points at the previous level, where searching its key-value array would be wrong. This check separates a miss from a hit.
Layer two: binary search inside the leaf
// If we get here, then we have found the right entry. Now go through the
// entries in the dictionary until we find the key we're looking for.
const entries = kidsOrEntries.get(this._type);
if (Array.isArray(entries)) {
// Perform a binary search to reduce the lookup time.
let l = 0,
r = entries.length - 2;
while (l <= r) {
// Check only even indices (0, 2, 4, ...) because the
// odd indices contain the actual data.
const tmp = (l + r) >> 1,
m = tmp + (tmp & 1);
const currentKey = xref.fetchIfRef(entries[m]);
if (key < currentKey) {
r = m - 2;
} else if (key > currentKey) {
l = m + 2;
} else {
return entries[m + 1];
}
}
}
return null;
}The leaf is a flat array with keys at even indices and values at odd indices. The binary search must run on key indices, so the source first computes a midpoint tmp and then rounds an odd midpoint up to an even index with tmp & 1:
| Step | Expression | Effect |
|---|---|---|
| Coarse midpoint | (l + r) >> 1 | Take a midpoint within the key indices |
| Align to even | tmp + (tmp & 1) | Odd becomes even plus one, even stays |
| Compare | entries[m] | Compare only against the key, not the value |
| Hit | entries[m + 1] | Return the raw value, possibly a Ref |
r starts at entries.length - 2, the index of the last key. Shrinking left uses r = m - 2 and shrinking right uses l = m + 2, each time stepping over a key and value pair so that l always lands on an even index.
The return value is the raw entries[m + 1], not dereferenced. That is exactly the dividing line between getRaw and get.
Fault-tolerance differences from getAll
getRaw leaves several spots undefended along its path:
| Location | Assumption | Result when violated |
|---|---|---|
kidsOrEntries.has | The root or child is always a dictionary | Calling it on a non-dictionary throws TypeError |
kid.get("Limits") | Every non-root node carries Limits | limits is undefined, then indexing it throws |
Elements of limits | Always directly comparable values | The comparison is unreliable |
entries | A flat array of even length | The lone final key is never compared |
getAll skips bad nodes; getRaw leaves exceptions to the caller. That division is deliberate: bulk reading aims to get as much as possible, single-key reading aims to answer precisely, and the latter must not quietly return a wrong answer. Callers usually handle exceptions in their own try, for example Catalog catches and logs a warning when reading page labels.
Design note: One layer uses
Limitsto rule out an entire subtree, the other uses the key array to rule out half a leaf. Both are binary searches, but they compare different things: the upper layer compares interval bounds, the lower layer compares individual keys.getRawtherefore never needs to load the whole tree into memory, and query cost is logarithmic in tree depth and fan-out.
The get Method
get is a single line; it passes the result of getRaw to XRef for dereferencing:
get(key) {
return this.xref.fetchIfRef(this.getRaw(key));
}This design separates lookup from object retrieval. Callers that need to keep the Ref use getRaw, for example the structure tree checking whether the parent tree holds a reference; callers that need the real object use get, for example the structure tree reading the array in ParentTree.
If getRaw returns null, fetchIfRef returns null unchanged, so get's miss value is also null and callers need not distinguish a missing key from an empty value.
The NameTree and NumberTree Subclasses
Together the two subclasses are only ten lines:
class NameTree extends NameOrNumberTree {
constructor(root, xref) {
super(root, xref, 'Names');
}
}
class NumberTree extends NameOrNumberTree {
constructor(root, xref) {
super(root, xref, 'Nums');
}
}They do one thing: fix the leaf field name. All traversal, binary search and fault tolerance live in the base class. This one-base-class-plus-two-constant-subclasses style is clearer than a boolean switch in the base class: _type is used both to fetch the leaf field and to build error messages, with no extra branching.
| Subclass | Third argument | Leaf field | Key comparison |
|---|---|---|---|
NameTree | "Names" | Names | JavaScript string comparison |
NumberTree | "Nums" | Nums | JavaScript numeric comparison |
Key Ordering Semantics: Strings and Numbers
Every comparison in getRaw is written as key < currentKey or key > currentKey. In JavaScript these operators choose a comparison mode based on operand type:
| Key type | Actual comparison | Matches the PDF specification |
|---|---|---|
| String | UTF-16 code units compared one by one | For single-byte strings this equals byte order; UTF-16BE strings sort by code unit |
| Number | Numeric magnitude | Yes |
NameTree keys are PDF strings, not Name objects. In memory a PDF string may already have been decoded into a JavaScript string: each character of a single-byte encoding is the original byte value, and each character of UTF-16BE is a 16-bit code unit. For the former, code-unit comparison is exactly byte comparison; for the latter, code-unit comparison sorts by 16-bit units rather than by raw bytes.
This creates an edge case: if a producer sorts UTF-16BE strings by byte order while PDF.js compares by code-unit order, the binary search can take the wrong branch. In practice the vast majority of NameTree keys in real files are ASCII or PDFDocEncoding, so this rarely triggers; even if it does, callers usually have a fallback path that traverses the whole tree and builds a table, as Catalog.destinations does by calling getAll first.
NumberTree has no such problem: its keys are integers, and JavaScript numeric comparison is exactly the comparison the specification requires. Negative numbers and zero sort correctly too.
Design note: The source does not implement its own byte comparison function; it relies directly on JavaScript's
<and>. That is correct for nearly every PDF and avoids an encoding conversion per key. In cases where strict byte order really matters, the producer should guarantee the sorting, not the reader re-encode on every comparison.
Cycles and Fault Tolerance
Collecting every guard in the module produces a complete fault-tolerance table:
| Guard | Location | Trigger | Result |
|---|---|---|---|
| Empty root | getAll | root is null or undefined | Return an empty Map |
| Empty root | getRaw | Same as above | Return null |
| Non-dictionary node | getAll | fetchIfRef result is not a Dict | Skip that node |
Kids is not an array | getAll | obj.get("Kids") is not an array | Skip that node |
| Duplicate reference | getAll | The same Ref enters the queue a second time | Throw FormatError |
| Entry field is not an array | getAll | Names / Nums is not an array | Skip that leaf |
| Depth exceeded | getRaw | More than 10 descents | Warn and return null |
Kids is not an array | getRaw | An interior node's Kids is not an array | Return null |
| Bounds do not cover | getRaw | l is greater than r after the search | Return null |
| Leaf has no entry field | getRaw | entries is not an array | Return null |
Missing Limits | getRaw | A non-root node has no Limits | Throw TypeError to the caller |
This table makes an important point: the two read paths in the same module do not use the same fault-tolerance policy. getAll swallows errors where it can; getRaw exposes them where it can. By choosing a method, the caller also chooses complete or precise.
getAll uses RefSet for global deduplication and getRaw uses a depth cap for local protection. The former can detect one child referenced by two parents; the latter can detect descending forever without reaching a leaf. Neither handles out-of-order keys inside a leaf - that is the producer's responsibility, and the reader only performs a binary search on the assumption that keys are ordered.
Where It Sits in the Chain
NameTree and NumberTree are not used directly for page rendering; they serve document-level index structures. The main call sites in the current repository are:
| Caller | Tree type | Root source | Method used | Purpose |
|---|---|---|---|---|
Catalog.destinations | NameTree | Dests under /Names | getAll | Named destinations |
Catalog.getDestination | NameTree | Same as above | get, then bulk lookup on a miss | A single named destination |
Catalog.attachments | NameTree | EmbeddedFiles under /Names | getAll | Attachment list |
Catalog.#collectJavaScript | NameTree | JavaScript under /Names | getAll | Document-level scripts |
Catalog.pageLabels | NumberTree | /PageLabels | getAll | Page number to label mapping |
StructTreeRoot | NumberTree | /ParentTree | get / getRaw | Structure element to content items |
PDFEditor | Both | ParentTree / IDTree | getAll(true) | Preserve original references when saving |
Taking Catalog.destinations as an example, the chain looks like this:
Catalog.#readDests()
→ new NameTree(namesDict.getRaw("Dests"), xref)
→ nameTree.getAll()
→ Map<string, destination array or dictionary>
→ fetchDest(value)
→ page index + coordinatesNote that getRaw("Dests") returns the raw value, which may be a Ref or a dictionary. The NameTree constructor accepts either, and fetchIfRef inside getAll handles them uniformly. That is also why the constructor does not rush to dereference: the shape the caller receives is not fixed.
Conclusion
name_number_tree.js is PDF.js's ordered index layer: downward it dereferences through XRef, upward it offers Catalog, StructTreeRoot and PDFEditor two ways to read, flatten the whole tree and look up a single key.
Where It Sits in the Overall Chain
| Stage | Module | Input | Output |
|---|---|---|---|
| Root retrieval | Catalog / StructTreeRoot | The parent dictionary's getRaw | Dict or Ref |
| Construction | NameTree / NumberTree | root, xref, type string | A lightweight tree handle |
| Bulk read | getAll | The whole tree | Map from key to value |
| Single-key read | getRaw / get | One key | A raw value or a dereferenced object |
| Upstream use | Catalog / StructTreeRoot | A Map or a single value | Destinations, attachments, page labels, structure nodes |
In one sentence: the parent dictionary provides the root, the base class stores it, getAll flattens the tree into a table, getRaw walks Limits down to a leaf, and get finally turns the reference into an object.
Four Design Principles
- One base class, two constant subclasses. NameTree and NumberTree have exactly the same node shape and differ only in the leaf field name. The difference collapses into one string in the constructor, so traversal and lookup are written once and error messages can include the type directly.
- Bulk and single-key reads tolerate faults differently.
getAllskips bad nodes and reports only duplicate references, aiming to read as much as possible;getRawdoes no extra defending, aiming either to give an exact answer or to let the caller know it failed. The two policies match two use cases and are not forced into one. - The ordering assumption is used for lookup, not for table building.
getAlldoes not depend on key order at all; breadth-first collection is enough.getRawdepends on orderedLimitsand key arrays and uses two binary searches to reduce lookup to logarithmic time. Table building and lookup each pick the algorithm that suits them. - References are preserved until the last moment. The constructor stores the raw
root,getRawreturns raw values, and onlygetcallsfetchIfRef. Editors that need the reference structure usegetAll(true)orgetRaw; catalogs that need real objects usegetor the defaultgetAll. The same tree data can serve both consumers.
NameTree and NumberTree produce no page content themselves, but they decide whether many document-level features can be found: bookmark jumps, attachment downloads, page labels, accessibility structure. The next article leaves these index structures and returns to the content stream side to see how a default appearance string is parsed into font size and color.
StreamPDF design principle: anything that can be handled ahead of time is absolutely not deferred to parse time; the server solves all performance problems once. The browser only fetches and renders the data it must.
Related articles
- PDF.js Deep Dive from Demo 10: CanvasGraphics and operatorList RenderingOctober 27, 2026
- PDF.js Deep Dive from Demo 9: Network Streams and Range RequestsOctober 27, 2026
- PDF.js Deep Dive from Demo 8: PDFWorker.create and the Real/Fake Worker HandshakeOctober 27, 2026
- PDF.js Deep Dive from Demo 7: WorkerTransport InitializationOctober 27, 2026
- PDF.js Deep Dive from Demo 6: MessageHandler Cross-Thread CommunicationOctober 27, 2026
- PDF.js Deep Dive from Demo 5: renderOctober 27, 2026
- PDF.js Deep Dive from Demo 4: getViewportOctober 27, 2026
- PDF.js Deep Dive from Demo 3: getPageOctober 27, 2026
- PDF.js Deep Dive from Demo 2: getDocumentOctober 27, 2026
- PDF.js Source Walkthrough: TextLayerOctober 27, 2026
- PDF.js Source Walkthrough: font_loaderOctober 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_displayOctober 27, 2026
- PDF.js Source Walkthrough: PDFObjectsOctober 27, 2026
- PDF.js Source Walkthrough: Network StreamsOctober 27, 2026
- PDF.js Source Walkthrough: internal_evt.jsOctober 27, 2026
- PDF.js Source Walkthrough: math_clamp.jsOctober 27, 2026
- PDF.js Source Walkthrough: css_utils.jsOctober 27, 2026
- PDF.js Source Walkthrough: scripting_utilsOctober 27, 2026
- PDF.js Source Walkthrough: MurmurHash3_64October 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_utilsOctober 27, 2026
- PDF.js Source Walkthrough: image_utils.jsOctober 27, 2026
- PDF.js Source Walkthrough: BasePDFStreamOctober 27, 2026
- PDF.js Source Walkthrough: Shared UtilitiesOctober 27, 2026
- PDF.js Source Walkthrough: MessageHandlerOctober 27, 2026
- PDF.js Source Walkthrough: apiOctober 27, 2026
- PDF.js Source Walkthrough: fontsOctober 27, 2026
- PDF.js Source Walkthrough: glyphlistOctober 27, 2026
- PDF.js Source Walkthrough: binary_cmapOctober 27, 2026
- PDF.js Source Walkthrough: cmapOctober 27, 2026
- PDF.js Source Walkthrough: encodingsOctober 27, 2026
- PDF.js Source Walkthrough: metricsOctober 27, 2026
- PDF.js Source Walkthrough: standard_fontsOctober 27, 2026
- PDF.js Source Walkthrough: fonts_utilsOctober 27, 2026
- PDF.js Source Walkthrough: default_appearanceOctober 27, 2026
- PDF.js Source Walkthrough: metadata_parserOctober 27, 2026
- PDF.js Source Walkthrough: struct_treeOctober 27, 2026
- PDF.js Source Walkthrough: catalogOctober 27, 2026
- PDF.js Source Walkthrough: pdf_managerOctober 27, 2026
- PDF.js Source Walkthrough: object_loaderOctober 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_coreOctober 27, 2026
- PDF.js Source Walkthrough: cryptoOctober 27, 2026
- PDF.js Source Walkthrough: core_utilsOctober 27, 2026
- PDF.js Source Walkthrough: primitivesOctober 27, 2026
- PDF.js Source Walkthrough: worker_streamOctober 27, 2026
- PDF.js Source Walkthrough: ccitt_streamOctober 27, 2026
- PDF.js Source Walkthrough: jpx_streamOctober 27, 2026
- PDF.js Source Walkthrough: jbig2_streamOctober 27, 2026
- PDF.js Source Walkthrough: jpeg_streamOctober 27, 2026
- PDF.js Source Walkthrough: decrypt_streamOctober 27, 2026
- PDF.js Source Walkthrough: brotli_streamOctober 27, 2026
- PDF.js Source Walkthrough: run_length_streamOctober 27, 2026
- PDF.js Source Walkthrough: ascii_hex_streamOctober 27, 2026
- PDF.js Source Walkthrough: ascii_85_streamOctober 27, 2026
- PDF.js Source Walkthrough: lzw_streamOctober 27, 2026
- PDF.js Source Walkthrough: predictor_streamOctober 27, 2026
- PDF.js Source Walkthrough: flate_streamOctober 27, 2026
- PDF.js Source Walkthrough: decode_streamOctober 27, 2026
- PDF.js Source Walkthrough: chunked_streamOctober 27, 2026
- PDF.js Source Walkthrough: streamOctober 27, 2026
- PDF.js Source Walkthrough: base_streamOctober 27, 2026
- PDF.js Deep Dive 11: ParserOctober 26, 2026
- PDF.js Deep Dive 10: XRefOctober 26, 2026
- PDF.js Deep Dive 9: CanvasGraphicsOctober 26, 2026
- PDF.js Deep Dive 8: PDFDocumentOctober 26, 2026
- The AI Era: What Should We Do to Survive? (Reflections on Founding StreamPDF)October 26, 2026
- PDF.js Deep Dive 7: PartialEvaluatorOctober 25, 2026
- PDF.js Deep Dive 6: PDFManager.initDocumentOctober 25, 2026
- PDF.js Deep Dive 5: PDFFetchStream and PDFWorkerStreamOctober 25, 2026
- PDF.js Deep Dive 4: MessageHandlerOctober 22, 2026
- PDF.js Deep Dive 3: getViewport & renderOctober 22, 2026
- PDF.js Deep Dive from Demo 1: Rendering a PDF with a Custom ViewerOctober 19, 2026
- PDF.js Deep Dive 2: getDocument & getPageOctober 19, 2026
- PDF.js Deep Dive 1: The Architecture of PDF.jsOctober 16, 2026