Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
65 changes: 65 additions & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Owner

Choose a reason for hiding this comment

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

Consider lazy-loading this inside the transform on first use, so the TeX engine isn't loaded on builds with no TikZ blocks.

Copy link
Author

Choose a reason for hiding this comment

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


const { headerToId, namedHeadingsFilter } = require("./src/helpers/utils");
const {
Expand Down Expand Up @@ -202,6 +203,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 @@ -526,6 +532,65 @@ module.exports = function (eleventyConfig) {
return content;
});

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.

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") // after zooming some pictures could go wrong,
Copy link
Owner

Choose a reason for hiding this comment

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

querySelector("svg") can return null if tex2svg produces unexpected output. Add a null check before calling .setAttribute().

Copy link
Author

Choose a reason for hiding this comment

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

.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(`<pre><code class="language-tikz">TikZ render failed. See build log.\n\n${(texSource)}</code></pre>`);
Copy link
Owner

Choose a reason for hiding this comment

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

texSource is inserted into HTML unescaped. If the TeX source contains < or & and rendering fails, they'll be interpreted as HTML. Either escape it, or log it to the console instead.

Copy link
Author

Choose a reason for hiding this comment

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

}
}

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

eleventyConfig.addPassthroughCopy("src/site/img");
eleventyConfig.addPassthroughCopy("src/site/scripts");
eleventyConfig.addPassthroughCopy("src/site/styles/_theme.*.css");
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.3",
"npm-run-all": "^4.1.5",
"rimraf": "^4.4.1"
}
Expand Down