136 lines
4.0 KiB
JavaScript
136 lines
4.0 KiB
JavaScript
|
|
"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<number>} 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<Uint8Array>}
|
||
|
|
*/
|
||
|
|
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);
|
||
|
|
});
|