38 lines
1.9 KiB
TypeScript
38 lines
1.9 KiB
TypeScript
export function createPdf(pages: string[], encrypted = false): Uint8Array {
|
|
const fontId = 3 + pages.length * 2;
|
|
const objects = [
|
|
"<< /Type /Catalog /Pages 2 0 R >>",
|
|
`<< /Type /Pages /Kids [${pages.map((_, index) => `${3 + index * 2} 0 R`).join(" ")}] /Count ${pages.length} >>`,
|
|
];
|
|
for (let index = 0; index < pages.length; index += 1) {
|
|
const pageId = 3 + index * 2;
|
|
const contentId = pageId + 1;
|
|
const text = pages[index]!;
|
|
const operators = Array.from({ length: Math.ceil(text.length / 100) }, (_, partIndex) => {
|
|
const part = text.slice(partIndex * 100, (partIndex + 1) * 100);
|
|
const escaped = part.replaceAll("\\", "\\\\").replaceAll("(", "\\(").replaceAll(")", "\\)");
|
|
return `(${escaped}) Tj`;
|
|
});
|
|
const content = text ? `BT /F1 12 Tf 72 720 Td ${operators.join(" ")} ET` : "";
|
|
objects.push(
|
|
`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 ${fontId} 0 R >> >> /Contents ${contentId} 0 R >>`,
|
|
`<< /Length ${Buffer.byteLength(content)} >>\nstream\n${content}\nendstream`,
|
|
);
|
|
}
|
|
objects.push("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>");
|
|
const encryptionId = objects.length + 1;
|
|
if (encrypted) objects.push(`<< /Filter /Standard /V 1 /R 2 /O <${"00".repeat(32)}> /U <${"00".repeat(32)}> /P -4 >>`);
|
|
|
|
let output = "%PDF-1.4\n";
|
|
const offsets = [0];
|
|
objects.forEach((object, index) => {
|
|
offsets.push(Buffer.byteLength(output));
|
|
output += `${index + 1} 0 obj\n${object}\nendobj\n`;
|
|
});
|
|
const xrefOffset = Buffer.byteLength(output);
|
|
output += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
|
|
for (const offset of offsets.slice(1)) output += `${String(offset).padStart(10, "0")} 00000 n \n`;
|
|
output += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R${encrypted ? ` /Encrypt ${encryptionId} 0 R` : ""} >>\nstartxref\n${xrefOffset}\n%%EOF`;
|
|
return new TextEncoder().encode(output);
|
|
}
|