Repository navigation
Should extractContent() or some other call give enough details to fully reconstruct the page? #1794
Replies: 3 comments
|
One thing first, because it decides where your roadmap question goes: What you're seeing is by design in 2.11.1. A text asset has exactly
Your third question already has an answer: the same fork exports the parser it uses. const fs = require('fs');
const {
PDFDocument, PDFArray, PDFRawStream, PDFContentStream, PDFName,
parseContentStream, decodePDFRawStream,
} = require('@cantoo/pdf-lib');
// Operand -> PDF syntax (inverse of parseContentStream)
const ser = (a) => {
if (typeof a === 'number') return String(+a.toFixed(6));
if (Array.isArray(a)) return '[' + a.map(ser).join(' ') + ']';
if (a.type === 'name') return '/' + a.value;
if (a.type === 'hexString') return '<' + Buffer.from(a.bytes).toString('hex') + '>';
if (a.type === 'string')
return '(' + Buffer.from(a.bytes).toString('latin1').replace(/[\\()]/g, '\\$&') + ')';
return '<<' + Object.entries(a).map(([k, v]) => '/' + k + ' ' + ser(v)).join(' ') + '>>';
};
// All content streams of a page, decoded and parsed into [{ name, args }]
const readOps = (page) => {
const c = page.node.Contents();
const streams = c instanceof PDFArray
? c.asArray().map((r) => page.doc.context.lookup(r)) : [c];
const parts = streams.map((s) => Buffer.from(
s instanceof PDFRawStream ? decodePDFRawStream(s).decode()
: s instanceof PDFContentStream ? s.getUnencodedContents() : s.getContents()));
return parseContentStream(Buffer.concat(parts.flatMap((b) => [b, Buffer.from('\n')])));
};
const writeOps = (page, ops) => {
const ctx = page.doc.context;
const body = ops.map((o) => [...o.args.map(ser), o.name].join(' ')).join('\n');
page.node.set(PDFName.of('Contents'), ctx.register(ctx.flateStream(body)));
};
(async () => {
const doc = await PDFDocument.load(fs.readFileSync(process.argv[2]));
const page = doc.getPage(0);
const ops = readOps(page);
let inText = false;
for (const o of ops) {
if (o.name === 'BT') inText = true;
if (o.name === 'ET') inText = false;
if (inText && o.name === 'rg') o.args = [0.5, 0, 0.5]; // text fill -> purple
if (o.name === 'Tj' && o.args[0].bytes) { // edit the shown string
const s = Buffer.from(o.args[0].bytes).toString('latin1').replace('Hello', 'Howdy');
o.args[0] = { type: 'string', bytes: Buffer.from(s, 'latin1') };
}
}
writeOps(page, ops);
fs.writeFileSync(process.argv[3], await doc.save());
})();I ran this on a page drawn with Caveats before you rely on it:
To get a page into another document, don't redraw it from extracted assets. Copy it: Tested with Disclosure: I build a hosted HTML→PDF API. It has nothing to do with this and isn't linked. This answer was written with AI assistance (Claude Code), and every snippet and number above was run as shown. |
|
the cantoo repo links to this repo's discussions in various md files as well as in the new issue "wizard" or whassname |
|
"To get a page into another document..." well that's a black box approach, what I ultimately want was to understand and change some elements. In particular I have a PDF with tiling gap artifacts that I want to automatically fix, but as a more general long term goal any actual edit should be possible for a library that dares to claim it can "edit" and not just "overlay" PDFs. |
Uh oh!
There was an error while loading. Please reload this page.
Right now extractContent() gives reasonably detailed info to accurately redraw images on a new document/page (actually it still lacks skew/rotation but those are rarely used I guess).
But it gives very few details about text - lacking render mode, colors, skew, max width, scale and rotation (btw that wasn't in the drawText arguments for some reason), as well as only giving the font name but with no way to actually extract the font and add it to another document/page.
It also is not accurate about SVG/shapes, though I can't really point out in what ways since I only did very little testing. All I know it trying to repeat them with drawSVG on a new page made a complete mess with everything being offset randomly.
Is extractContent() planned to be more detailed and accurate eventually, and is just a work in progress for now? Will it ever allow actual editing of PDF docs, as in getting the current objects, changing some in some ways, and redrawing them? It's a real bummer how the current claims about "editing" capabilities are really nothing more than overlaying stuff on top, I don't think that counts at all.
If extractContent() is not intended to be as detailed as I hoped, could the library get some lower level, not object-level but say pdf operator list level or raw decompressed text get/set functionality that will allow editing, even if with more effort and requiring knowledge of more PDF internals?
All reactions