From 2ea31fe3a83f14f897a74efda5fef4646b0ff295 Mon Sep 17 00:00:00 2001 From: Richard Dern Date: Thu, 11 Sep 2025 00:12:40 +0200 Subject: [PATCH] tools/add_lego.js: set cover from largest image and omit it from body --- tools/add_lego.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/tools/add_lego.js b/tools/add_lego.js index d1c9e202..dce4df11 100644 --- a/tools/add_lego.js +++ b/tools/add_lego.js @@ -132,6 +132,58 @@ function escapeMarkdownAlt(text) { .replace(/\]/g, '\\]'); } +// Lightweight dimensions readers for PNG/JPEG/GIF. Fallback to file size. +async function getImageDimensions(filePath) { + const fd = await fs.open(filePath, 'r'); + try { + const stat = await fd.stat(); + const size = stat.size || 0; + const hdr = Buffer.alloc(64); + const { bytesRead } = await fd.read(hdr, 0, hdr.length, 0); + const b = hdr.slice(0, bytesRead); + // PNG + if (b.length >= 24 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4E && b[3] === 0x47) { + const width = b.readUInt32BE(16); + const height = b.readUInt32BE(20); + return { width, height, area: width * height, size }; + } + // GIF + if (b.length >= 10 && b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46) { + const width = b.readUInt16LE(6); + const height = b.readUInt16LE(8); + return { width, height, area: width * height, size }; + } + // JPEG: scan segments for SOF markers + if (b.length >= 4 && b[0] === 0xFF && b[1] === 0xD8) { + let pos = 2; + while (pos + 3 < size) { + const markerBuf = Buffer.alloc(4); + await fd.read(markerBuf, 0, 4, pos); + if (markerBuf[0] !== 0xFF) { pos += 1; continue; } + const marker = markerBuf[1]; + const hasLen = marker !== 0xD8 && marker !== 0xD9; + let segLen = 0; + if (hasLen) segLen = markerBuf.readUInt16BE(2); + // SOF0..SOF15 except DHT(0xC4), JPG(0xC8), DAC(0xCC) + if (marker >= 0xC0 && marker <= 0xCF && ![0xC4, 0xC8, 0xCC].includes(marker)) { + const segHdr = Buffer.alloc(7); + await fd.read(segHdr, 0, segHdr.length, pos + 4); + const height = segHdr.readUInt16BE(1); + const width = segHdr.readUInt16BE(3); + return { width, height, area: width * height, size }; + } + if (!hasLen || segLen < 2) { pos += 2; continue; } + pos += 2 + segLen; + } + } + return { width: 0, height: 0, area: 0, size }; + } catch (_) { + return { width: 0, height: 0, area: 0, size: 0 }; + } finally { + await fd.close(); + } +} + async function readYamlTitle(yamlPath) { try { const content = await fs.readFile(yamlPath, 'utf8'); @@ -327,6 +379,23 @@ async function main() { imageFiles = Array.from(downloadedImages); } + // Choose cover: prefer downloaded this run; fallback to all images + let coverFile = null; + const coverCandidates = (downloadedImages.size > 0) + ? Array.from(downloadedImages).filter((b) => imageFiles.includes(b)) + : imageFiles.slice(); + if (coverCandidates.length > 0) { + let best = null; + let bestScore = -1; + for (const base of coverCandidates) { + const full = path.join(imagesDir, base); + const dim = await getImageDimensions(full); + const score = (dim.area && dim.area > 0) ? dim.area : (dim.size || 0); + if (score > bestScore) { bestScore = score; best = base; } + } + coverFile = best; + } + let body = ''; if (imageFiles.length > 0) { const ordered = [ @@ -335,6 +404,7 @@ async function main() { ]; const lines = []; for (const base of ordered) { + if (coverFile && base === coverFile) continue; const nameNoExt = base.replace(/\.[^.]+$/, ''); const yamlPath = path.join(dataImagesDir, `${nameNoExt}.yaml`); const altRaw = (await readYamlTitle(yamlPath)) || `${pageTitle}`; @@ -345,13 +415,16 @@ async function main() { body = lines.join('\n') + '\n'; } + const coverLine = coverFile ? `cover: images/${coverFile}\n` : ''; const md = `---\n` + `title: "${pageTitle.replace(/"/g, '\\"')}"\n` + `date: ${date}\n` + + coverLine + `---\n\n` + body; await fs.writeFile(indexPath, md, 'utf8'); - console.log(`[create] ${path.relative(ROOT, indexPath)} with ${imageFiles.length} image reference(s)`); + const coverInfo = coverFile ? `, cover=images/${coverFile}` : ''; + console.log(`[create] ${path.relative(ROOT, indexPath)} with ${imageFiles.length} image reference(s)${coverInfo}`); } // Report downloaded images that are not referenced in an existing index.md