don't regenerate images that already exist
BIN
images/chapters/control/7fa216226582e3b0868e77b6fdfeee6d.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
images/chapters/control/9ef58ed62e5e9f2fd67cc004b8560a3d.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
images/chapters/control/a4bb3d892822ce42ec05c56ec437d22f.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 6.3 KiB |
BIN
images/chapters/explanation/fdea63696e525033c5ea74fa8f90009a.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 8.1 KiB |
BIN
images/chapters/whatis/b2e7a2bc650cbed9d750e8afc40a1aa3.png
Normal file
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 26 KiB |
16
index.html
@@ -499,7 +499,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\quadratic.png"
|
||||
src="images\chapters\introduction\8d7391688575c8c588ea741ada7b893e.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -515,7 +515,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\cubic.png"
|
||||
src="images\chapters\introduction\c0aa6126567320c80a3039a47edf994a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -585,7 +585,7 @@
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\whatis\interpolation.png"
|
||||
src="images\chapters\whatis\b2e7a2bc650cbed9d750e8afc40a1aa3.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -711,7 +711,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\explanation\circle.png"
|
||||
src="images\chapters\explanation\fdea63696e525033c5ea74fa8f90009a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -927,7 +927,7 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\lerp-quadratic.png"
|
||||
src="images\chapters\control\a4bb3d892822ce42ec05c56ec437d22f.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -943,7 +943,7 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\lerp-cubic.png"
|
||||
src="images\chapters\control\9ef58ed62e5e9f2fd67cc004b8560a3d.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -959,7 +959,7 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\lerp-fifteenth.png"
|
||||
src="images\chapters\control\7fa216226582e3b0868e77b6fdfeee6d.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -1015,7 +1015,7 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\cubic.png"
|
||||
src="images\chapters\introduction\c0aa6126567320c80a3039a47edf994a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
|
@@ -419,7 +419,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\quadratic.png"
|
||||
src="images\chapters\introduction\8d7391688575c8c588ea741ada7b893e.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -435,7 +435,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\cubic.png"
|
||||
src="images\chapters\introduction\c0aa6126567320c80a3039a47edf994a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -487,7 +487,7 @@
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\whatis\interpolation.png"
|
||||
src="images\chapters\whatis\b2e7a2bc650cbed9d750e8afc40a1aa3.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -565,7 +565,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\explanation\circle.png"
|
||||
src="images\chapters\explanation\fdea63696e525033c5ea74fa8f90009a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -743,7 +743,7 @@ function Bezier(3,t):
|
||||
</p>
|
||||
<img
|
||||
class="LaTeX SVG"
|
||||
src="images/latex/927ed9b2dbb2595f4c6c8ffd4e7bb024.svg"
|
||||
src="images/latex/c0d4dbc07b8ec7c0a18ea43c8a386935.svg"
|
||||
width="473px"
|
||||
height="40px"
|
||||
loading="lazy"
|
||||
|
@@ -1,84 +0,0 @@
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import { generateGraphicsModule } from "./generate-graphics-module.js";
|
||||
|
||||
const moduleURL = new URL(import.meta.url);
|
||||
const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``));
|
||||
const __root = path.join(__dirname, `..`, `..`, `..`);
|
||||
|
||||
/**
|
||||
* ...docs go here...
|
||||
*/
|
||||
async function generatePlaceHolders(localeStrings, markdown) {
|
||||
const locale = localeStrings.getCurrentLocale();
|
||||
|
||||
if (locale !== localeStrings.getDefaultLocale()) return;
|
||||
|
||||
let graphic = 0,
|
||||
pos = -1,
|
||||
data = markdown,
|
||||
elements = {},
|
||||
startmark = `<graphics-element`,
|
||||
endmark = `</graphics-element>`;
|
||||
|
||||
do {
|
||||
pos = data.indexOf(startmark);
|
||||
if (pos !== -1) {
|
||||
let endpos = data.indexOf(endmark, pos) + endmark.length;
|
||||
let key = `graphic${graphic++}`;
|
||||
elements[key] = data.substring(pos, endpos - endmark.length);
|
||||
data = `${data.slice(0, pos)}{{ ${key} }}${data.slice(endpos)}`;
|
||||
}
|
||||
} while (pos !== -1);
|
||||
|
||||
const keys = Object.keys(elements);
|
||||
const sourcePaths = keys.map(
|
||||
(key) => elements[key].match(/src="([^"]+)"/)[1]
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
sourcePaths.map(async (srcPath, i) => {
|
||||
try {
|
||||
// Get the sketch code
|
||||
const sourcePath = path.join(__root, srcPath);
|
||||
let code;
|
||||
try {
|
||||
code = fs.readFileSync(sourcePath).toString(`utf8`);
|
||||
} catch (e) {
|
||||
console.log(srcPath, sourcePath);
|
||||
throw e;
|
||||
}
|
||||
const width = elements[keys[i]].match(`width="([^"]+)"`)[1];
|
||||
const height = elements[keys[i]].match(`height="([^"]+)"`)[1];
|
||||
|
||||
// Convert this to a valid JS module code and write this to
|
||||
// a temporary file so we can import it.
|
||||
const nodeCode = generateGraphicsModule(code, width, height);
|
||||
const fileName = `./nodecode.${Date.now()}.${Math.random()}.js`;
|
||||
const tempFile = path.join(__dirname, fileName);
|
||||
fs.writeFileSync(tempFile, nodeCode, `utf8`);
|
||||
|
||||
// Import our entirely valid JS module, which will run the
|
||||
// sketch code and export a canvas instance that we can turn
|
||||
// into an actual image file.
|
||||
const { canvas } = await import(fileName);
|
||||
|
||||
fs.unlinkSync(tempFile);
|
||||
|
||||
const dataURI = canvas.toDataURL();
|
||||
const start = dataURI.indexOf(`base64,`) + 7;
|
||||
const imageData = Buffer.from(dataURI.substring(start), `base64`);
|
||||
const destPath = path.join(__root, `images`, srcPath);
|
||||
const filename = destPath.replace(`.js`, `.png`);
|
||||
|
||||
// console.log(`Writing placeholder to ${filename}`);
|
||||
fs.ensureDirSync(path.dirname(destPath));
|
||||
fs.writeFileSync(filename, imageData);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export { generatePlaceHolders };
|
@@ -9,7 +9,7 @@ nunjucks.configure(".", { autoescape: false });
|
||||
* ...docs go here...
|
||||
*/
|
||||
async function convertMarkDown(chapter, localeStrings, markdown) {
|
||||
markdown = preprocessGraphicsElement(chapter, localeStrings, markdown);
|
||||
markdown = await preprocessGraphicsElement(chapter, localeStrings, markdown);
|
||||
|
||||
// This yields the original markdown with all LaTeX blocked replaced with
|
||||
// uniquely named templating variables, referencing keys in the `latex` array.
|
||||
|
@@ -1,9 +1,16 @@
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import { createHash } from "crypto";
|
||||
import { generateGraphicsModule } from "./generate-graphics-module.js";
|
||||
|
||||
const moduleURL = new URL(import.meta.url);
|
||||
const __dirname = path.dirname(moduleURL.href.replace(`file:///`, ``));
|
||||
const __root = path.join(__dirname, `..`, `..`, `..`);
|
||||
|
||||
/**
|
||||
* ...docs go here...
|
||||
*/
|
||||
function preprocessGraphicsElement(chapter, localeStrings, markdown) {
|
||||
async function preprocessGraphicsElement(chapter, localeStrings, markdown) {
|
||||
const translate = localeStrings.translate;
|
||||
|
||||
let pos = -1,
|
||||
@@ -37,31 +44,31 @@ function preprocessGraphicsElement(chapter, localeStrings, markdown) {
|
||||
);
|
||||
|
||||
// Then add in the fallback code
|
||||
updated = updated.replace(
|
||||
/width="([^"]+)"\s+height="([^"]+)"\s+src="([^"]+)"\s*>/,
|
||||
(_, width, height, src) => {
|
||||
if (src.indexOf(`../`) === 0) src = `./chapters/${chapter}/${src}`;
|
||||
else {
|
||||
if (src[0] !== `.`) src = `./${src}`;
|
||||
src = src.replace(`./`, `./chapters/${chapter}/`);
|
||||
}
|
||||
|
||||
// TODO: generate a fallback image here, since this is where we need
|
||||
// to know what the code-hash is so we can properly link images.
|
||||
|
||||
let imageHash = generateFallbackImage(src);
|
||||
let img = path.join(
|
||||
path.dirname(src.replace(`./`, `./images/`)),
|
||||
`${imageHash}.png`
|
||||
);
|
||||
|
||||
return `width="${width}" height="${height}" src="${src}">
|
||||
<fallback-image>
|
||||
<img width="${width}px" height="${height}px" src="${img}" loading="lazy">
|
||||
${translate`disabledMessage`}
|
||||
</fallback-image>`;
|
||||
}
|
||||
const terms = updated.match(
|
||||
/width="([^"]+)"\s+height="([^"]+)"\s+src="([^"]+)"\s*>/
|
||||
);
|
||||
const [original, width, height] = terms;
|
||||
|
||||
let src = terms[3];
|
||||
if (src.indexOf(`../`) === 0) src = `./chapters/${chapter}/${src}`;
|
||||
else {
|
||||
if (src[0] !== `.`) src = `./${src}`;
|
||||
src = src.replace(`./`, `./chapters/${chapter}/`);
|
||||
}
|
||||
|
||||
let imageHash = await generateFallbackImage(src, width, height); // ← this is super fancy functionality.
|
||||
let imgUrl = path.join(
|
||||
path.dirname(src.replace(`./`, `./images/`)),
|
||||
`${imageHash}.png`
|
||||
);
|
||||
|
||||
const replacement = `width="${width}" height="${height}" src="${src}">
|
||||
<fallback-image>
|
||||
<img width="${width}px" height="${height}px" src="${imgUrl}" loading="lazy">
|
||||
${translate`disabledMessage`}
|
||||
</fallback-image>`;
|
||||
|
||||
updated = updated.replace(original, replacement);
|
||||
data = data.replace(slice, updated);
|
||||
pos += updated.length;
|
||||
}
|
||||
@@ -70,8 +77,53 @@ function preprocessGraphicsElement(chapter, localeStrings, markdown) {
|
||||
return data;
|
||||
}
|
||||
|
||||
function generateFallbackImage(src) {
|
||||
return path.basename(src).replace(`.js`, ``);
|
||||
/**
|
||||
*
|
||||
* @param {*} src
|
||||
*/
|
||||
async function generateFallbackImage(src, width, height) {
|
||||
// Get the sketch code
|
||||
const sourcePath = path.join(__root, src);
|
||||
let code;
|
||||
try {
|
||||
code = fs.readFileSync(sourcePath).toString(`utf8`);
|
||||
} catch (e) {
|
||||
console.log(`could not read file "${sourcePath}".`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Do we need to even generate a file here?
|
||||
const hash = createHash(`md5`).update(code).digest(`hex`);
|
||||
const destPath = path.dirname(path.join(__root, `images`, src));
|
||||
const filename = path.join(destPath, `${hash}.png`);
|
||||
if (fs.existsSync(filename)) return hash;
|
||||
|
||||
// If we get here, we need to actually run the magic: convert
|
||||
// this to a valid JS module code and write this to a temporary
|
||||
// file so we can import it.
|
||||
const nodeCode = generateGraphicsModule(code, width, height);
|
||||
const fileName = `./nodecode.${Date.now()}.${Math.random()}.js`;
|
||||
const tempFile = path.join(__dirname, fileName);
|
||||
fs.writeFileSync(tempFile, nodeCode, `utf8`);
|
||||
|
||||
// Then we import our entirely valid JS module, which will run
|
||||
// the sketch code and export a canvas instance that we can
|
||||
// turn into an actual image file.
|
||||
const { canvas } = await import(fileName);
|
||||
|
||||
fs.unlinkSync(tempFile);
|
||||
|
||||
// The canvas runs setup() + draw() as part of the module load, so
|
||||
// all we have to do now is get the image data and writ it to file.
|
||||
const dataURI = canvas.toDataURL();
|
||||
const start = dataURI.indexOf(`base64,`) + 7;
|
||||
const imageData = Buffer.from(dataURI.substring(start), `base64`);
|
||||
|
||||
fs.ensureDirSync(path.dirname(destPath));
|
||||
fs.writeFileSync(filename, imageData);
|
||||
console.log(`Generated fallback image for ${src}`);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
export default preprocessGraphicsElement;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import { convertMarkDown } from "./markdown/convert-markdown.js";
|
||||
import { generatePlaceHolders } from "./graphics/generate-placeholders.js";
|
||||
import nunjucks from "nunjucks";
|
||||
import toc from "../../chapters/toc.js";
|
||||
|
||||
@@ -53,9 +52,11 @@ async function processLocale(locale, localeStrings, chapterFiles) {
|
||||
localeFiles.map(async (file) => {
|
||||
const chapter = file.match(/chapters\/([^/]+)\/content./)[1];
|
||||
const markdown = fs.readFileSync(file).toString("utf8");
|
||||
const converted = await convertMarkDown(chapter, localeStrings, markdown);
|
||||
chapters[chapter] = converted;
|
||||
generatePlaceHolders(localeStrings, converted); // ← this is super fancy functionality.
|
||||
chapters[chapter] = await convertMarkDown(
|
||||
chapter,
|
||||
localeStrings,
|
||||
markdown
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
|
@@ -408,7 +408,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\quadratic.png"
|
||||
src="images\chapters\introduction\8d7391688575c8c588ea741ada7b893e.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -424,7 +424,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\introduction\cubic.png"
|
||||
src="images\chapters\introduction\c0aa6126567320c80a3039a47edf994a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -472,7 +472,7 @@
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\whatis\interpolation.png"
|
||||
src="images\chapters\whatis\b2e7a2bc650cbed9d750e8afc40a1aa3.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -548,7 +548,7 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\explanation\circle.png"
|
||||
src="images\chapters\explanation\fdea63696e525033c5ea74fa8f90009a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -724,7 +724,7 @@ function Bezier(3,t):
|
||||
</p>
|
||||
<img
|
||||
class="LaTeX SVG"
|
||||
src="images/latex/927ed9b2dbb2595f4c6c8ffd4e7bb024.svg"
|
||||
src="images/latex/c0d4dbc07b8ec7c0a18ea43c8a386935.svg"
|
||||
width="473px"
|
||||
height="40px"
|
||||
loading="lazy"
|
||||
|