I Have pdf file with a pdf attachment file into it. How can I extract the attachment pdf-file from the existing "main" pdf with pdf-lib as a new pdf file?
Thanks
Hello @nachum37!
Here's an example script demonstrating how to do this with pdf-lib:
import fs from 'fs';
import fetch from 'node-fetch';
import {
PDFDocument,
PDFName,
PDFDict,
PDFArray,
PDFHexString,
PDFString,
PDFStream,
decodePDFRawStream,
PDFRawStream,
} from 'pdf-lib';
const extractRawAttachments = (pdfDoc: PDFDocument) => {
if (!pdfDoc.catalog.has(PDFName.of('Names'))) return [];
const Names = pdfDoc.catalog.lookup(PDFName.of('Names'), PDFDict);
if (!Names.has(PDFName.of('EmbeddedFiles'))) return [];
const EmbeddedFiles = Names.lookup(PDFName.of('EmbeddedFiles'), PDFDict);
if (!EmbeddedFiles.has(PDFName.of('Names'))) return [];
const EFNames = EmbeddedFiles.lookup(PDFName.of('Names'), PDFArray);
const rawAttachments = [];
for (let idx = 0, len = EFNames.size(); idx < len; idx += 2) {
const fileName = EFNames.lookup(idx) as PDFHexString | PDFString;
const fileSpec = EFNames.lookup(idx + 1, PDFDict);
rawAttachments.push({ fileName, fileSpec });
}
return rawAttachments;
};
const extractAttachments = (pdfDoc: PDFDocument) => {
const rawAttachments = extractRawAttachments(pdfDoc);
return rawAttachments.map(({ fileName, fileSpec }) => {
const stream = fileSpec
.lookup(PDFName.of('EF'), PDFDict)
.lookup(PDFName.of('F'), PDFStream) as PDFRawStream;
return {
name: fileName.decodeText(),
data: decodePDFRawStream(stream).decode(),
};
});
};
(async () => {
const pdfWithAttachments = await fetch(
'https://github.com/Hopding/pdf-lib/files/4963252/with_attachment.pdf',
).then((res) => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(pdfWithAttachments);
const attachments = extractAttachments(pdfDoc);
const csv = attachments.find((attachment) => attachment.name === 'cars.csv')!;
fs.writeFileSync('cars.csv', csv.data);
console.log('CSV file written to ./cars.csv');
const jpg = attachments.find((attachment) => attachment.name === 'mini.jpg')!;
fs.writeFileSync('mini.jpg', jpg.data);
console.log('JPG file written to ./mini.jpg');
})();
Note that this script is written for Node and will write the extracted attachments directly to the filesystem. However, the script will also work in a browser if you remove the fs.writeFileSync lines. You could, for example, replace them with file download prompts or send the attachments to an API.
I hope this helps!
I should note that it would be pretty neat to have a high level API for this:
const pdfDoc = await PDFDocument.load(...)
const attachments = pdfDoc.getAttachments()
const csv = attachments.find(({ name }) => name === 'cars.csv')
fs.writeFileSync(csv.name, csv.data)
If anybody would like to help out, it shouldn't be too difficult to implement with the example I created as a starting point.
Wow. @Hopding, Thanks for the great work and time you spent on writing this code.
There is a way to "read" (decrypt) password-locked PDF with pdf-lib right now?
My original goal was to decrypt my bank pdf mail attachments, they attach the original unlocked pdf to itself, so I need to decrypt it first. this is possible with some node CLI tools wrappers, like pdf-tk, not with vanilla js, so it is impossible to use them in Browser client-side, or Google Apps Script (my original idea). So I need to post them to AWS lambada layers, a way less simple than pdf-lib.
Most helpful comment
I should note that it would be pretty neat to have a high level API for this:
If anybody would like to help out, it shouldn't be too difficult to implement with the example I created as a starting point.