"use strict"; /** * Generate the demo fixtures that test4node.js reads from the (gitignored) * `test/` directory: a source PDF for every rotation the manual demo walks * through, a stamp image, and a self-signed PKCS#12 certificate. * * Usage: node testutil/make-demo.js [pfxPassword] */ const m_fs = require("fs"); const m_path = require("path"); const m_zlib = require("zlib"); const Zga = require("../lib/zganode.js"); const {makeP12} = require("./fixtures.js"); /** @type {string} Output directory, matching the `workpath` of test4node.js. */ const outDir = m_path.join(__dirname, "..", "test"); /** @type {Array} Page rotations the manual demo iterates over. */ const ROTATIONS = [0, 90, 180, 270]; /** @type {string} */ const DEFAULT_PWD = "zgatest"; /** @type {number} CCN-STIC-221 requires at least 3000 bits. */ const RSA_BITS = 3072; /** * Build a one-page demo PDF with the given page rotation. * * @param {number} angle Page rotation in degrees. * @return {Promise} */ async function makePdf(angle){ const doc = await Zga.PDFLib.PDFDocument.create(); const font = await doc.embedFont(Zga.PDFLib.StandardFonts.Helvetica); const page = doc.addPage([595, 842]); page.drawText("zgapdfsigner demo document", {x: 60, y: 760, size: 18, font: font}); page.drawText("Page rotation: " + angle + " degrees", {x: 60, y: 730, size: 12, font: font}); page.drawText("Generated by testutil/make-demo.js", {x: 60, y: 710, size: 10, font: font}); if(angle){ page.setRotation(Zga.PDFLib.degrees(angle)); } return doc.save(); } /** * CRC-32 as specified by the PNG format. * * @param {Buffer} buf * @return {number} */ function crc32(buf){ let crc = 0xffffffff; for(let i = 0; i < buf.length; i++){ crc ^= buf[i]; for(let bit = 0; bit < 8; bit++){ crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1)); } } return (crc ^ 0xffffffff) >>> 0; } /** * Wrap a payload into a length-prefixed, CRC-suffixed PNG chunk. * * @param {string} type Four-character chunk type. * @param {Buffer} data * @return {Buffer} */ function pngChunk(type, data){ const head = Buffer.alloc(4); head.writeUInt32BE(data.length, 0); const body = Buffer.concat([Buffer.from(type, "ascii"), data]); const tail = Buffer.alloc(4); tail.writeUInt32BE(crc32(body), 0); return Buffer.concat([head, body, tail]); } /** * Build a small solid-color PNG, used as the signature stamp image. * * @return {Buffer} */ function makePng(){ const size = 64; const stride = 1 + size * 3; const raw = Buffer.alloc(size * stride); for(let y = 0; y < size; y++){ const row = y * stride; raw[row] = 0; // filter type: none for(let x = 0; x < size; x++){ const px = row + 1 + x * 3; const edge = x < 4 || y < 4 || x >= size - 4 || y >= size - 4; raw[px] = edge ? 0x1f : 0x8a; raw[px + 1] = edge ? 0x6f : 0xc8; raw[px + 2] = edge ? 0xd5 : 0xf0; } } const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4); ihdr[8] = 8; // bit depth ihdr[9] = 2; // color type: truecolor return Buffer.concat([ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), pngChunk("IHDR", ihdr), pngChunk("IDAT", m_zlib.deflateSync(raw)), pngChunk("IEND", Buffer.alloc(0)), ]); } async function main(){ const pwd = process.argv[2] || DEFAULT_PWD; m_fs.mkdirSync(outDir, {recursive: true}); for(const angle of ROTATIONS){ const name = "_test" + (angle ? "_" + angle : "") + ".pdf"; m_fs.writeFileSync(m_path.join(outDir, name), await makePdf(angle)); console.log("Wrote " + m_path.join(outDir, name)); } const pngPath = m_path.join(outDir, "_test.png"); m_fs.writeFileSync(pngPath, makePng()); console.log("Wrote " + pngPath); const pfxPath = m_path.join(outDir, "_test.pfx"); m_fs.writeFileSync(pfxPath, Buffer.from(makeP12(RSA_BITS, pwd), "latin1")); console.log("Wrote " + pfxPath + " (password: " + pwd + ")"); console.log("\nRun the manual demo with: npm run test:manual -- " + pwd); } main().catch(function(err){ console.error(err); process.exit(1); });