77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
/**
|
|
* Minimal PDF builder for fixtures. Produces a syntactically valid PDF 1.4
|
|
* with one Helvetica text object per page — small enough to build in-memory
|
|
* and ship through pdfjs-dist's loader the same way an uploaded file would.
|
|
*/
|
|
|
|
export function buildSimplePdf(pageTexts: string[]): Buffer {
|
|
const parts: Buffer[] = [];
|
|
const offsets: number[] = [];
|
|
let totalLen = 0;
|
|
|
|
function emit(s: string | Buffer) {
|
|
const buf = typeof s === 'string' ? Buffer.from(s, 'binary') : s;
|
|
parts.push(buf);
|
|
totalLen += buf.length;
|
|
}
|
|
function recordObj() {
|
|
offsets.push(totalLen);
|
|
}
|
|
|
|
emit('%PDF-1.4\n');
|
|
emit('%\xE2\xE3\xCF\xD3\n'); // 4-byte binary marker so readers treat it as binary
|
|
|
|
recordObj();
|
|
emit('1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n');
|
|
|
|
const numPages = pageTexts.length;
|
|
const pageObjIds: number[] = [];
|
|
const contentObjIds: number[] = [];
|
|
for (let i = 0; i < numPages; i++) {
|
|
pageObjIds.push(3 + i * 2);
|
|
contentObjIds.push(4 + i * 2);
|
|
}
|
|
|
|
recordObj();
|
|
emit(`2 0 obj\n<< /Type /Pages /Kids [${pageObjIds
|
|
.map((id) => `${id} 0 R`)
|
|
.join(' ')}] /Count ${numPages} >>\nendobj\n`);
|
|
|
|
for (let i = 0; i < numPages; i++) {
|
|
const text = escapePdfString(pageTexts[i]);
|
|
const streamContent = `BT /F1 24 Tf 72 720 Td (${text}) Tj ET`;
|
|
const streamLen = Buffer.byteLength(streamContent, 'binary');
|
|
|
|
recordObj();
|
|
emit(
|
|
`${pageObjIds[i]} 0 obj\n` +
|
|
`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents ${contentObjIds[i]} 0 R ` +
|
|
`/Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >> >>\n` +
|
|
`endobj\n`,
|
|
);
|
|
|
|
recordObj();
|
|
emit(
|
|
`${contentObjIds[i]} 0 obj\n` +
|
|
`<< /Length ${streamLen} >>\nstream\n${streamContent}\nendstream\nendobj\n`,
|
|
);
|
|
}
|
|
|
|
const xrefStart = totalLen;
|
|
const totalObjs = 1 + offsets.length;
|
|
emit(`xref\n0 ${totalObjs}\n`);
|
|
emit('0000000000 65535 f \n');
|
|
for (const offset of offsets) {
|
|
emit(`${offset.toString().padStart(10, '0')} 00000 n \n`);
|
|
}
|
|
emit(`trailer\n<< /Size ${totalObjs} /Root 1 0 R >>\n`);
|
|
emit(`startxref\n${xrefStart}\n`);
|
|
emit('%%EOF\n');
|
|
|
|
return Buffer.concat(parts);
|
|
}
|
|
|
|
function escapePdfString(s: string): string {
|
|
return s.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
|
|
}
|