/* Run with: node --test */
const test = require("node:test");
const assert = require("node:assert/strict");
const Pages = require("../pages.js");
const round = (n) => Math.round(n);
test("portrait images get a portrait A4 page", () => {
const page = Pages.pageSize(1200, 1800);
assert.equal(round(page.width), 595);
assert.equal(round(page.height), 842);
assert.equal(page.orientation, "p");
});
test("landscape images get a landscape A4 page", () => {
const page = Pages.pageSize(1800, 1200);
assert.equal(round(page.width), 842);
assert.equal(round(page.height), 595);
assert.equal(page.orientation, "l");
});
test("square images stay portrait", () => {
assert.equal(Pages.pageSize(1000, 1000).orientation, "p");
});
test("a batch can mix orientations", () => {
const orientations = [[400, 800], [800, 400], [600, 900]].map(
([w, h]) => Pages.pageSize(w, h).orientation
);
assert.deepEqual(orientations, ["p", "l", "p"]);
});
test("the image is centred on the page", () => {
const page = Pages.pageSize(1000, 1000);
const spot = Pages.placeImage(1000, 1000, page.width, page.height);
const rightGap = page.width - (spot.x + spot.width);
const bottomGap = page.height - (spot.y + spot.height);
assert.ok(Math.abs(spot.x - rightGap) < 0.001, "equal side margins");
assert.ok(Math.abs(spot.y - bottomGap) < 0.001, "equal top and bottom margins");
});
test("the image never crosses the margin", () => {
const page = Pages.pageSize(1200, 1800);
const spot = Pages.placeImage(1200, 1800, page.width, page.height);
assert.ok(spot.x >= Pages.MARGIN_PT - 0.001);
assert.ok(spot.y >= Pages.MARGIN_PT - 0.001);
assert.ok(spot.width <= page.width - Pages.MARGIN_PT * 2 + 0.001);
assert.ok(spot.height <= page.height - Pages.MARGIN_PT * 2 + 0.001);
});
test("aspect ratio survives placement", () => {
const page = Pages.pageSize(1600, 900);
const spot = Pages.placeImage(1600, 900, page.width, page.height);
assert.ok(Math.abs(spot.width / spot.height - 1600 / 900) < 0.001);
});
test("a very wide image is limited by width, not height", () => {
const page = Pages.pageSize(4000, 500);
const spot = Pages.placeImage(4000, 500, page.width, page.height);
assert.ok(Math.abs(spot.width - (page.width - Pages.MARGIN_PT * 2)) < 0.001);
assert.ok(spot.height < page.height - Pages.MARGIN_PT * 2);
});
test("a very tall image is limited by height, not width", () => {
const page = Pages.pageSize(500, 4000);
const spot = Pages.placeImage(500, 4000, page.width, page.height);
assert.ok(Math.abs(spot.height - (page.height - Pages.MARGIN_PT * 2)) < 0.001);
assert.ok(spot.width < page.width - Pages.MARGIN_PT * 2);
});
test("a small image is scaled up to fill the page", () => {
const page = Pages.pageSize(100, 150);
const spot = Pages.placeImage(100, 150, page.width, page.height);
assert.ok(spot.height > 700, "a thumbnail still fills an A4 page");
});
test("raster size follows the render density", () => {
const spot = { width: 288, height: 144 };
// 288pt = 4in; at 150 DPI that is 600px.
assert.deepEqual(Pages.rasterSize(spot), { width: 600, height: 300 });
assert.deepEqual(Pages.rasterSize(spot, 72), { width: 288, height: 144 });
});
test("raster size never collapses to zero", () => {
const raster = Pages.rasterSize({ width: 0.01, height: 0.01 });
assert.ok(raster.width >= 1 && raster.height >= 1);
});
test("an A4 content area stays a sane pixel size", () => {
const page = Pages.pageSize(6000, 8000);
const raster = Pages.rasterSize(Pages.placeImage(6000, 8000, page.width, page.height));
assert.ok(raster.width < 1300, `expected a downscale, got ${raster.width}px`);
});
test("byte formatting", () => {
assert.equal(Pages.formatBytes(512), "512 B");
assert.equal(Pages.formatBytes(2048), "2 KB");
assert.equal(Pages.formatBytes(5 * 1024 * 1024), "5.0 MB");
});
test("TIFF is detected from magic bytes in both byte orders", () => {
assert.equal(Pages.isTiff(new Uint8Array([0x49, 0x49, 0x2a, 0x00])), true, "II*\\0");
assert.equal(Pages.isTiff(new Uint8Array([0x4d, 0x4d, 0x00, 0x2a])), true, "MM\\0*");
});
test("other formats are not mistaken for TIFF", () => {
assert.equal(Pages.isTiff(new Uint8Array([0xff, 0xd8, 0xff, 0xe0])), false, "JPEG");
assert.equal(Pages.isTiff(new Uint8Array([0x89, 0x50, 0x4e, 0x47])), false, "PNG");
assert.equal(Pages.isTiff(new Uint8Array([0x52, 0x49, 0x46, 0x46])), false, "WebP");
assert.equal(Pages.isTiff(new Uint8Array([0x49, 0x49])), false, "truncated");
assert.equal(Pages.isTiff(null), false, "nothing");
});
test("image pages use embedded physical resolution instead of A4", () => {
const page = Pages.imagePage(1200, 600, 300, 300, 1);
assert.equal(page.width, 288);
assert.equal(page.height, 144);
assert.equal(page.capped, false);
});
test("images without usable DPI metadata fall back to 96 DPI", () => {
const page = Pages.imagePage(960, 480, undefined, 0, 1);
assert.equal(page.width, 720);
assert.equal(page.height, 360);
assert.equal(page.dpiX, 96);
assert.equal(page.dpiY, 96);
});
test("EXIF rotations swap pixel dimensions and resolution axes", () => {
const page = Pages.imagePage(1200, 600, 300, 150, 6);
assert.equal(page.pixelWidth, 600);
assert.equal(page.pixelHeight, 1200);
assert.equal(page.width, 288);
assert.equal(page.height, 288);
});
test("oversized physical pages are capped proportionally", () => {
const page = Pages.imagePage(40000, 20000, 1, 1, 1);
assert.equal(page.width, Pages.MAX_PAGE_PT);
assert.equal(page.height, Pages.MAX_PAGE_PT / 2);
assert.equal(page.capped, true);
assert.ok(page.scale < 1);
});
test("PNG pHYs metadata is converted from pixels per metre to DPI", () => {
const bytes = new Uint8Array(54);
bytes.set([137, 80, 78, 71, 13, 10, 26, 10]);
const view = new DataView(bytes.buffer);
view.setUint32(8, 13, false);
bytes.set([73, 72, 68, 82], 12);
view.setUint32(16, 1200, false);
view.setUint32(20, 600, false);
view.setUint32(33, 9, false);
bytes.set([112, 72, 89, 115], 37);
view.setUint32(41, 11811, false); // approximately 300 DPI
view.setUint32(45, 5906, false); // approximately 150 DPI
bytes[49] = 1;
const metadata = Pages.parseRasterMetadata(bytes);
assert.equal(metadata.width, 1200);
assert.equal(metadata.height, 600);
assert.ok(Math.abs(metadata.dpiX - 300) < 0.02);
assert.ok(Math.abs(metadata.dpiY - 150) < 0.02);
});
test("BMP pixels-per-metre metadata is read", () => {
const bytes = new Uint8Array(46);
bytes.set([66, 77]);
const view = new DataView(bytes.buffer);
view.setInt32(18, 800, true);
view.setInt32(22, 400, true);
view.setInt32(38, 3780, true);
view.setInt32(42, 3780, true);
const metadata = Pages.parseRasterMetadata(bytes);
assert.equal(metadata.width, 800);
assert.equal(metadata.height, 400);
assert.ok(Math.abs(metadata.dpiX - 96) < 0.02);
});
test("JPEG JFIF density and dimensions are read", () => {
const bytes = new Uint8Array([
0xff, 0xd8,
0xff, 0xe0, 0x00, 0x10,
0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c, 0x00, 0x96, 0x00, 0x00,
0xff, 0xc0, 0x00, 0x11, 0x08, 0x02, 0x58, 0x04, 0xb0,
0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00,
0xff, 0xd9,
]);
const metadata = Pages.parseRasterMetadata(bytes);
assert.equal(metadata.width, 1200);
assert.equal(metadata.height, 600);
assert.equal(metadata.dpiX, 300);
assert.equal(metadata.dpiY, 150);
});