From 959fc726b7c617a68ce20e497d30fcd9eeb9aad6 Mon Sep 17 00:00:00 2001 From: Montagnard <928786745@qq.com> Date: Mon, 22 Dec 2025 01:55:59 +0800 Subject: [PATCH 1/3] Add support for TikzJax. --- .eleventy.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 1 + package.json | 1 + 3 files changed, 67 insertions(+) diff --git a/.eleventy.js b/.eleventy.js index 2c381874e..57399831c 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -7,6 +7,7 @@ const tocPlugin = require("eleventy-plugin-nesting-toc"); const { parse } = require("node-html-parser"); const htmlMinifier = require("html-minifier-terser"); const pluginRss = require("@11ty/eleventy-plugin-rss"); +const tex2svg = require("node-tikzjax").default; const { headerToId, namedHeadingsFilter } = require("./src/helpers/utils"); const { @@ -202,6 +203,11 @@ module.exports = function (eleventyConfig) { )}`; return res } + if (token.info === "tikz") { + const code = token.content.trim(); + const codeEscaped = md.utils.escapeHtml(code); + return `
${codeEscaped}
`; + } // Other languages return origFenceRule(tokens, idx, options, env, slf); @@ -526,6 +532,65 @@ module.exports = function (eleventyConfig) { return content; }); + eleventyConfig.addTransform("render-tikzjax", (() => { + let tikzQueue = Promise.resolve(); + + // https://github.com/artisticat1/obsidian-tikzjax/blob/main/main.ts + function tidyTikzSource(src) { + if (!src) return src; + return src + .replaceAll(" ", "") // Remove non-breaking space characters, otherwise we get errors + .replace(/\u00A0/g, "") + .replace(/\r\n/g, "\n") // Normalize line endings & Split into lines + .split("\n") + .map((line) => line.trim()) // Trim whitespace that is inserted when pasting in code, otherwise TikZJax complains + .filter((line) => line.length > 0) // Remove empty lines + .join("\n"); + } + + return async function (content, outputPath) { + if (!outputPath || !outputPath.endsWith(".html")) return content; + if (!content || !content.includes('class="block-language-tikz"')) return content; + + const root = parse(content); + const blocks = root.querySelectorAll("div.block-language-tikz"); + if (!blocks.length) return content; + + for (const block of blocks) { + const src = block.text || ""; + const texSource = tidyTikzSource(src); + try { + const run = tikzQueue + .catch(() => {}) + .then(() => + tex2svg(texSource, { + // SvgOptions + embedFontCss: true, // Whether to embed the font CSS file in the SVG. + // TeXOptions + showConsole: false, // Print log of TeX engine to console. + texPackages: {}, // Additional TeX packages to load. e.g. texPackages: { pgfplots: '', amsmath: 'intlimits' }, + tikzLibraries: '', // Additional TikZ libraries to load. e.g. tikzLibraries: 'arrows.meta,calc' + }) + ); + tikzQueue = run.catch(() => {}); + const svg = await run; + + const svgElement = parse(svg) + .querySelector("svg") // after zooming some pictures could go wrong, + .setAttribute("style", "margin:auto; display:block;"); // e.g. oleeskild/obsidian-digital-garden#667 + block.replaceWith(svgElement); + } catch (e) { + console.warn("\n[TikZJax] render failed at:", outputPath); + console.warn("[TikZJax] TeX source (first 400 chars):\n", texSource.slice(0, 400)); + console.warn("[TikZJax] Warn:", e); + block.replaceWith(`
TikZ render failed. See build log.\n\n${(texSource)}
`); + } + } + + return root.toString(); + }; + })()); + eleventyConfig.addPassthroughCopy("src/site/img"); eleventyConfig.addPassthroughCopy("src/site/scripts"); eleventyConfig.addPassthroughCopy("src/site/styles/_theme.*.css"); diff --git a/package-lock.json b/package-lock.json index 995776466..a257d9de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "markdown-it-mathjax3": "^4.3.1", "markdown-it-plantuml": "^1.4.1", "markdown-it-task-checkbox": "^1.0.6", + "node-tikzjax": "^1.0.3", "npm-run-all": "^4.1.5", "rimraf": "^4.4.1" }, diff --git a/package.json b/package.json index 584bbd56d..1763de96c 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "markdown-it-mathjax3": "^4.3.1", "markdown-it-plantuml": "^1.4.1", "markdown-it-task-checkbox": "^1.0.6", + "node-tikzjax": "^1.0.3", "npm-run-all": "^4.1.5", "rimraf": "^4.4.1" } From 3b79f881c8100e779151d504b2af166f2535a9d8 Mon Sep 17 00:00:00 2001 From: Montagnard <928786745@qq.com> Date: Mon, 22 Dec 2025 02:58:53 +0800 Subject: [PATCH 2/3] Use Base64 to store TikZJax code. --- .eleventy.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.eleventy.js b/.eleventy.js index 57399831c..ea73b1014 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -205,8 +205,8 @@ module.exports = function (eleventyConfig) { } if (token.info === "tikz") { const code = token.content.trim(); - const codeEscaped = md.utils.escapeHtml(code); - return `
${codeEscaped}
`; + const b64 = Buffer.from(code, "utf8").toString("base64"); // Encode into Base64 to avoid issues with special characters in TeX code + return `
${b64}
`; } // Other languages @@ -557,8 +557,8 @@ module.exports = function (eleventyConfig) { if (!blocks.length) return content; for (const block of blocks) { - const src = block.text || ""; - const texSource = tidyTikzSource(src); + const srcB64 = block.text || ""; + const texSource = tidyTikzSource(Buffer.from(srcB64, "base64").toString("utf8")); try { const run = tikzQueue .catch(() => {}) From 3a3176e61c234740f0875524dbde745d32e1d271 Mon Sep 17 00:00:00 2001 From: Montagnard <928786745@qq.com> Date: Sun, 1 Mar 2026 23:55:43 +0800 Subject: [PATCH 3/3] Fix the TikzJax support according to comments. --- .eleventy.js | 75 ++++++++++++++++++++++++++++------------------- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/.eleventy.js b/.eleventy.js index ea73b1014..b5717efb2 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -7,7 +7,6 @@ const tocPlugin = require("eleventy-plugin-nesting-toc"); const { parse } = require("node-html-parser"); const htmlMinifier = require("html-minifier-terser"); const pluginRss = require("@11ty/eleventy-plugin-rss"); -const tex2svg = require("node-tikzjax").default; const { headerToId, namedHeadingsFilter } = require("./src/helpers/utils"); const { @@ -507,32 +506,12 @@ module.exports = function (eleventyConfig) { return str && parsed.innerHTML; }); - eleventyConfig.addTransform("htmlMinifier", async (content, outputPath) => { - if ( - (process.env.NODE_ENV === "production" || process.env.ELEVENTY_ENV === "prod") && - outputPath && - outputPath.endsWith(".html") - ) { - try { - return await htmlMinifier.minify(content, { - useShortDoctype: true, - removeComments: true, - collapseWhitespace: true, - conservativeCollapse: true, - preserveLineBreaks: true, - minifyCSS: true, - minifyJS: true, - keepClosingSlash: true, - }); - } catch { - // If the html minifying fails for some reason due to some malformed text, just return the content as is. - return content; - } - } - return content; - }); - eleventyConfig.addTransform("render-tikzjax", (() => { + // Lazy loading to save resources. + const tex2svg = require("node-tikzjax").default; + + // Serialize renderings. + // Running node-tikzjax instances concurrently is problematic. See: https://github.com/prinsss/node-tikzjax/blob/main/README.md let tikzQueue = Promise.resolve(); // https://github.com/artisticat1/obsidian-tikzjax/blob/main/main.ts @@ -576,14 +555,25 @@ module.exports = function (eleventyConfig) { const svg = await run; const svgElement = parse(svg) - .querySelector("svg") // after zooming some pictures could go wrong, - .setAttribute("style", "margin:auto; display:block;"); // e.g. oleeskild/obsidian-digital-garden#667 - block.replaceWith(svgElement); + .querySelector("svg"); + if (svgElement) { + // Zooming would result in other TikZ diagrams being overlapped or partly invisible. + svgElement.setAttribute("style", "margin:auto; display:block;"); // e.g. oleeskild/obsidian-digital-garden#667 + block.replaceWith(svgElement); + } } catch (e) { console.warn("\n[TikZJax] render failed at:", outputPath); console.warn("[TikZJax] TeX source (first 400 chars):\n", texSource.slice(0, 400)); console.warn("[TikZJax] Warn:", e); - block.replaceWith(`
TikZ render failed. See build log.\n\n${(texSource)}
`); + // Escape texSource to be interpreted as HTML properly. + // https://stackoverflow.com/a/7382028 + const texSource_escaped = texSource + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); + block.replaceWith(`
TikZ render failed. See build log.\n\n${(texSource_escaped)}
`); } } @@ -591,6 +581,31 @@ module.exports = function (eleventyConfig) { }; })()); + eleventyConfig.addTransform("htmlMinifier", async (content, outputPath) => { + if ( + (process.env.NODE_ENV === "production" || process.env.ELEVENTY_ENV === "prod") && + outputPath && + outputPath.endsWith(".html") + ) { + try { + return await htmlMinifier.minify(content, { + useShortDoctype: true, + removeComments: true, + collapseWhitespace: true, + conservativeCollapse: true, + preserveLineBreaks: true, + minifyCSS: true, + minifyJS: true, + keepClosingSlash: true, + }); + } catch { + // If the html minifying fails for some reason due to some malformed text, just return the content as is. + return content; + } + } + return content; + }); + eleventyConfig.addPassthroughCopy("src/site/img"); eleventyConfig.addPassthroughCopy("src/site/scripts"); eleventyConfig.addPassthroughCopy("src/site/styles/_theme.*.css"); diff --git a/package-lock.json b/package-lock.json index a257d9de2..2a604ae94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "markdown-it-mathjax3": "^4.3.1", "markdown-it-plantuml": "^1.4.1", "markdown-it-task-checkbox": "^1.0.6", - "node-tikzjax": "^1.0.3", + "node-tikzjax": "^1.0.5", "npm-run-all": "^4.1.5", "rimraf": "^4.4.1" }, diff --git a/package.json b/package.json index 1763de96c..119d2e6fc 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "markdown-it-mathjax3": "^4.3.1", "markdown-it-plantuml": "^1.4.1", "markdown-it-task-checkbox": "^1.0.6", - "node-tikzjax": "^1.0.3", + "node-tikzjax": "^1.0.5", "npm-run-all": "^4.1.5", "rimraf": "^4.4.1" }