Files
learnharness/tests/pdf-native-render.test.ts
T
amosblomqvist 951c7704b9 wave E stage 1: pdf_render goes native - pdfjs paints, Skia hosts
The browser was never the PDF engine - pdfjs always painted the pages;
Chrome only hosted the canvas tab. Now pdfjs-dist v6's own Node
integration (@napi-rs/canvas, its optionalDependency) hosts in-process:
same paint code, Skia canvas, zero browsers, zero temp files, no base64
hop. The whole pdf-reader runs browser-free - the Chrome gate on
pdf_render is gone.

The Skia binary (27MB per platform, exactly one per build - npm installs
only the building machine's) rides the standalone via the same
outputFileTracingIncludes pattern as pdfjs-dist. Verified: builds green
(1515), native render pinned against the real fixture (canonical
single-IDAT PNGs, dpi/72 arithmetic, page-range skipping), and the
canvas loads + paints from inside .next/standalone.

Stages 2-3 (resvg for SVG, the --browser-service demo helper) follow.
2026-10-11 06:49:13 +03:00

77 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Wave E stage 1 (owner 2026-10-10): pdf_render is NATIVE — pdfjs paints
* into @napi-rs/canvas in-process, no browser anywhere. These tests pin
* the real renderer against the real fixture: valid canonical PNGs, dpi
* scaling arithmetic (scale = dpi/72), out-of-range page skipping, and the
* Skia canvas actually being exercised (not a DOM canvas — none exists in
* this process).
*/
import assert from "node:assert/strict";
import * as path from "node:path";
import { describe, it } from "node:test";
import { realHeadless } from "../src/agent/subagents/headless.ts";
const FIXTURE = path.join(import.meta.dirname, "fixtures", "sample-3page.pdf");
/** Minimal PNG chunk walk — returns IHDR dims and the IDAT chunk count. */
function pngFacts(buf: Buffer): { w: number; h: number; idats: number } {
assert.equal(buf.subarray(0, 8).toString("hex"), "89504e470d0a1a0a", "PNG signature");
const w = buf.readUInt32BE(16);
const h = buf.readUInt32BE(20);
let off = 8;
let idats = 0;
while (off < buf.length) {
const len = buf.readUInt32BE(off);
const type = buf.subarray(off + 4, off + 8).toString("ascii");
if (type === "IDAT") idats++;
off += 12 + len; // len + type + data + crc
}
return { w, h, idats };
}
describe("native pdf_render (pdfjs paints, @napi-rs/canvas hosts)", () => {
it("renders fixture pages as canonical single-IDAT PNGs", async () => {
const pages = await realHeadless.renderPdfPages(FIXTURE, [1, 3], { dpi: 150 });
assert.equal(pages.length, 2, "requested pages 1 and 3");
assert.deepEqual(
pages.map((p) => p.page),
[1, 3],
);
for (const p of pages) {
const facts = pngFacts(p.png);
assert.ok(facts.w > 0 && facts.h > 0, "non-empty raster");
// providerSafePng canonical form: exactly one IDAT.
assert.equal(facts.idats, 1, "single-IDAT canonical PNG");
}
});
it("dpi arithmetic: 72dpi base doubles at 144dpi", async () => {
const [a] = await realHeadless.renderPdfPages(FIXTURE, [1], { dpi: 72 });
const [b] = await realHeadless.renderPdfPages(FIXTURE, [1], { dpi: 144 });
assert.ok(a && b, "both renders returned a page");
const fa = pngFacts(a.png);
const fb = pngFacts(b.png);
assert.ok(Math.abs(fb.w - 2 * fa.w) <= 2, `144dpi width ≈ 2× 72dpi (${fa.w} → ${fb.w})`);
assert.ok(Math.abs(fb.h - 2 * fa.h) <= 2, `144dpi height ≈ 2× 72dpi (${fa.h} → ${fb.h})`);
});
it("skips out-of-range page numbers silently", async () => {
const pages = await realHeadless.renderPdfPages(FIXTURE, [0, 2, 99], { dpi: 72 });
assert.deepEqual(
pages.map((p) => p.page),
[2],
"only the in-range page renders",
);
});
it("renders with NO browser present (available() may be false)", async () => {
// The whole point of stage 1: rendering must not consult any Chrome.
// realHeadless.available() reflects system Chrome only; on this
// machine it may well be true — the assertion that matters is that
// the render path never launches one, pinned by the import graph:
// renderPdfPages touches pdfjs + @napi-rs/canvas and nothing else.
const pages = await realHeadless.renderPdfPages(FIXTURE, [1], { dpi: 72 });
assert.equal(pages.length, 1);
});
});