diff --git a/.gitignore b/.gitignore index e286330..a8bf98e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules examples run.sh screenshot* +output diff --git a/README.md b/README.md index f094b9d..568ff47 100644 --- a/README.md +++ b/README.md @@ -287,3 +287,8 @@ Ouput: ] ``` +It is also possible to make screenshots of each slide but removing the considered element. + +```sh +locator --output output-directory example.html +``` diff --git a/index.js b/index.js index de49a90..2650ea9 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const fs = require('fs').promises; const process = require('process'); const puppeteer = require('puppeteer'); const quality = require('./quality.js'); +const uuid = require('uuid').v4; // Size of the rendering of the web page const size = { width: 1280, height: 720 }; @@ -42,21 +43,36 @@ async function eprintln(data) { async function main() { - if (process.argv[2] === undefined) { - console.error('This program expects an argument.'); - console.error('USAGE: locator '); + let outputDir = null; + let filename = process.argv[2]; + + if (process.argv[2] === "-o" || process.argv[2] === "--output") { + outputDir = process.argv[3]; + filename = process.argv[4]; + } + + try { + await fs.mkdir(outputDir); + } catch (e) { + eprintln("Couldn't create directory " + outputDir + ": " + e); + process.exit(1); + } + + if (filename === undefined) { + eprintln('This program expects an argument.'); + eprintln('USAGE: locator '); process.exit(1); } // Path to the HTML file to analyse (given as relative path from current directory) // We need the full path so that puppeteer is able to access it - const path = process.argv[2].startsWith('/') ? process.argv[2] : process.cwd() + '/' + process.argv[2]; + const path = filename.startsWith('/') ? filename : process.cwd() + '/' + filename; // Check that the file exists try { await fs.access(path, fs.constants.F_OK); } catch (e) { - console.error('No such file: ' + path); + eprintln('No such file: ' + path); process.exit(1); } @@ -72,18 +88,25 @@ async function main() { // If there is no slide, try to run on HTML body if (root === null) { - console.error('Not a marp HTML file.'); + eprintln('Not a marp HTML file.'); process.exit(1); } + // Hide slides controls + await page.evaluate(() => { + for (let elt of document.getElementsByClassName('bespoke-marp-osc')) { + elt.style.visibility = "hidden"; + } + }); + // Take a first screenshot - await page.screenshot({path: __dirname + '/' + 'screenshot1.png'}); + await page.screenshot({path: (outputDir === null ? __dirname : outputDir) + '/' + 'screenshot1.png'}); // Edit the page to shrink elements in order to get better bounding boxes let withSpan = await addSpan(root); // Take another screenshot and check the modification we made didn't change the layout of the page - await page.screenshot({path: __dirname + '/' + 'screenshot2.png'}); + await page.screenshot({path: (outputDir === null ? __dirname : outputDir) + '/' + 'screenshot2.png'}); // Compare both screenshots let file1 = await fs.readFile(__dirname + '/' + 'screenshot1.png'); @@ -96,7 +119,7 @@ async function main() { // Crash if they're different if (psnr > 70) { - eprintln("\x1b[33mWarning: " + process.argv[2] + " produced slight diff: psnr = " + psnr + '\x1b[0m'); + eprintln("\x1b[33mWarning: " + filename + " produced slight diff: psnr = " + psnr + '\x1b[0m'); } else { await eprintln("\x1b[31mError: age edit changed the layout: psnr = " + psnr + '\x1b[0m'); process.exit(1); @@ -104,13 +127,18 @@ async function main() { } // Analyse the root and output the result - let analyse = await analyseElement(root); + let analyse = await analyseElement(root, page, outputDir); if (process.argv[3] === '--flatten') { analyse = flatten(analyse); } - console.log(JSON.stringify(analyse, undefined, 4)); + let json = JSON.stringify(analyse, undefined, 4); + if (outputDir === null) { + console.log(); + } else { + await fs.writeFile(outputDir + '/annotations.json', json); + } await browser.close(); @@ -143,7 +171,7 @@ async function addSpan(element) { // Recursive function to analyse an HTML element. // The output is written in hierarchy. -async function analyseElement(element) { +async function analyseElement(element, page, outputDir = null) { // Get some information on the element let tagAttr = await element.getProperty('tagName'); let tagName = await tagAttr.jsonValue(); @@ -159,6 +187,7 @@ async function analyseElement(element) { let analyse = {}; analyse.tag = tagName; analyse.class = className; + analyse.uuid = uuid(); analyse.box = box; box.x /= size.width; box.width /= size.width; @@ -166,6 +195,21 @@ async function analyseElement(element) { box.height /= size.height; analyse.children = []; + if (outputDir !== null) { + // Perform a screenshot where the element is hidden + let previousVisibility = await element.evaluate(el => { + let previousVisibility = el.style.visibility; + el.style.visibility = "hidden"; + return previousVisibility; + }); + + await page.screenshot({path: outputDir + '/' + analyse.uuid + '.png'}); + + await element.evaluate((el, previousVisibility) => { + el.style.visibility = previousVisibility; + }, [ previousVisibility ]); + } + // Extract the text content if it is a span (we made those spans by ourselves in the addSpan function) if (tagName === 'SPAN' && textContent !== "") { analyse.text = textContent; @@ -178,7 +222,7 @@ async function analyseElement(element) { for (let child of children) { // Recursively analyse the children - analyse.children.push(await analyseElement(child)); + analyse.children.push(await analyseElement(child, page, outputDir)); } return analyse; diff --git a/package-lock.json b/package-lock.json index 0024732..3545cf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "pngjs": "^7.0.0", - "puppeteer": "^19.7.1" + "puppeteer": "^19.7.1", + "uuid": "^9.0.0" }, "bin": { "locator": "index.js" @@ -742,6 +743,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 7489186..c983888 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "pngjs": "^7.0.0", - "puppeteer": "^19.7.1" + "puppeteer": "^19.7.1", + "uuid": "^9.0.0" } }