diff --git a/papers/aesthetic-eye.mjs b/papers/aesthetic-eye.mjs index 8de3863880..11f93f9f8f 100644 --- a/papers/aesthetic-eye.mjs +++ b/papers/aesthetic-eye.mjs @@ -7,7 +7,7 @@ import { createHash } from "node:crypto"; import { execFile } from "node:child_process"; -import { access, mkdir, readFile, readdir } from "node:fs/promises"; +import { access, mkdir, readFile, readdir, rm } from "node:fs/promises"; import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; @@ -40,6 +40,11 @@ export function countEvidenceFigures(sourceText) { ); } +export function renderedPageNumber(name) { + const match = String(name || "").match(/^page-(\d+)\.png$/); + return match ? Number(match[1]) : null; +} + function resolveInputs(input, manifestArg) { const absolute = resolve(input); const paperDir = extname(absolute).toLowerCase() === ".pdf" ? dirname(absolute) : absolute; @@ -226,11 +231,17 @@ async function prepare(input, manifestArg, outputArg) { const outputDir = outputArg ? resolve(outputArg) : join(review.paperDir, ".aesthetic-eye"); await mkdir(outputDir, { recursive: true }); const pagePrefix = join(outputDir, "page"); + for (const name of await readdir(outputDir)) { + if (renderedPageNumber(name) != null) await rm(join(outputDir, name)); + } await exec("pdftoppm", ["-png", "-r", "144", review.pdfPath, pagePrefix], { maxBuffer: 8 * 1024 * 1024 }); - const pagePaths = (await readdir(outputDir)) - .filter((name) => /^page-\d+\.png$/.test(name)) - .sort((a, b) => Number(a.match(/\d+/)?.[0]) - Number(b.match(/\d+/)?.[0])) - .map((name) => join(outputDir, name)); + const pageEntries = (await readdir(outputDir)) + .map((name) => ({ name, page: renderedPageNumber(name) })) + .filter((entry) => entry.page != null) + .sort((a, b) => a.page - b.page) + .map((entry) => ({ ...entry, path: join(outputDir, entry.name) })); + const pagePaths = pageEntries.map((entry) => entry.path); + const pagePathByNumber = new Map(pageEntries.map((entry) => [entry.page, entry.path])); if (pagePaths.length) { const pagesContactPath = join(outputDir, "pages-contact.png"); @@ -246,8 +257,8 @@ async function prepare(input, manifestArg, outputArg) { async function renderCrops(items, kind) { const crops = []; for (const item of items || []) { - const pagePath = `${pagePrefix}-${item.page}.png`; - if (!(await exists(pagePath))) throw new Error(`${item.id}: rendered page ${item.page} is missing`); + const pagePath = pagePathByNumber.get(item.page); + if (!pagePath || !(await exists(pagePath))) throw new Error(`${item.id}: rendered page ${item.page} is missing`); const { stdout } = await exec("magick", ["identify", "-format", "%w %h", pagePath]); const [pageWidth, pageHeight] = stdout.trim().split(/\s+/).map(Number); const [x, y, width, height] = item.crop; diff --git a/papers/test/aesthetic-eye.test.mjs b/papers/test/aesthetic-eye.test.mjs index a433595d0f..84d81d70d7 100644 --- a/papers/test/aesthetic-eye.test.mjs +++ b/papers/test/aesthetic-eye.test.mjs @@ -1,7 +1,7 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { countEvidenceFigures, validateManifest } from "../aesthetic-eye.mjs"; +import { countEvidenceFigures, renderedPageNumber, validateManifest } from "../aesthetic-eye.mjs"; const baseManifest = { schema: 1, @@ -42,6 +42,12 @@ test("counts every embedded evidence image but not a TikZ diagram", () => { assert.equal(countEvidenceFigures(source), 3); }); +test("resolves padded and unpadded rendered page filenames", () => { + assert.equal(renderedPageNumber("page-5.png"), 5); + assert.equal(renderedPageNumber("page-05.png"), 5); + assert.equal(renderedPageNumber("page-contact.png"), null); +}); + test("fails when source evidence is absent from the Aesthetic Eye manifest", () => { const verdict = validateManifest(baseManifest, "current-hash", 1); assert.equal(verdict.pass, false);