Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ module.exports = function (eleventyConfig) {
)}</div></div>`;
return res
}
if (token.info === "tikz") {
const code = token.content.trim();
const b64 = Buffer.from(code, "utf8").toString("base64"); // Encode into Base64 to avoid issues with special characters in TeX code
return `<div class="block-language-tikz">${b64}</div>`;
}

// Other languages
return origFenceRule(tokens, idx, options, env, slf);
Expand Down Expand Up @@ -501,6 +506,81 @@ module.exports = function (eleventyConfig) {
return str && parsed.innerHTML;
});

eleventyConfig.addTransform("render-tikzjax", (() => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is registered after htmlMinifier, so in production the minifier runs first. Consider moving it before htmlMinifier to avoid fragile ordering.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// 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();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment explaining why rendering is serialized (TeX engine likely isn't concurrency-safe), since the promise chain pattern is non-obvious.
SOmething like: // Serialize renders — the TeX engine doesn't support concurrent invocations

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// https://github.com/artisticat1/obsidian-tikzjax/blob/main/main.ts
function tidyTikzSource(src) {
if (!src) return src;
return src
.replaceAll("&nbsp;", "") // 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 srcB64 = block.text || "";
const texSource = tidyTikzSource(Buffer.from(srcB64, "base64").toString("utf8"));
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");
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);
// Escape texSource to be interpreted as HTML properly.
// https://stackoverflow.com/a/7382028
const texSource_escaped = texSource
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
block.replaceWith(`<pre><code class="language-tikz">TikZ render failed. See build log.\n\n${(texSource_escaped)}</code></pre>`);
}
}

return root.toString();
};
})());

eleventyConfig.addTransform("htmlMinifier", async (content, outputPath) => {
if (
(process.env.NODE_ENV === "production" || process.env.ELEVENTY_ENV === "prod") &&
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.5",
"npm-run-all": "^4.1.5",
"rimraf": "^4.4.1"
}
Expand Down