続Vivliostyle, VFM で図が使いたい
Vivliostyle VFM でも github と同じように mermaid の図を埋め込みたかったので、ハックしてできるようにしたのが前回。あれから問題が見つかり、対応したのが今回。
dosvg.js
kroki で svg を取得する大まかな流れは変更なし。使い方も変更なし。前回は html 中に直接 svg を埋め込んでいたけれども、今回は img タグにして、data:
スキームで画像として埋め込んでいる。
img タグにしたので、サイズ調整の CSS も要変更。svg
の代わりに img.kroki
に対して行えば ok。
const hostname = process.env.KROKIHOST || "kroki.io";
const langs = process.env.KROKILANGS || "mermaid";
const https = require("node:https");
const deasync = require("deasync");
const p = require("prismjs/components/prism-core");
const klangs = langs.split(",");
p.hooks.add("wrap", function (env) {
if (env.type == "base64") {
env.tag = "img";
env.classes.push("kroki");
env.attributes = {
src: `data:image/svg+xml;base64,${env.content.value}`
};
env.content.value = "";
}
});
p.hooks.add("after-tokenize", function (env) {
if (klangs.includes(env.language)) {
env.tokens = [new p.Token("base64", env.code, null, null)];
}
});
p.hooks.add("before-tokenize", function (env) {
if (klangs.includes(env.language)) {
env.grammar = { raw: { pattern: /\A.*\z/m } };
const rb = {
diagram_source: env.code,
diagram_type: env.language,
output_format: "svg"
};
const p = (resolve) => {
const req = https.request({
hostname: hostname,
port: 443,
path: "/",
method: "POST",
}, res => {
let resp = [];
res.on("data", (c) => { resp.push(c); });
res.on("end", () => {
env.code = Buffer.concat(resp).toString("base64");
resolve();
});
});
req.write(JSON.stringify(rb));
req.end();
};
deasync(p)();
};
});
何が問題だったか
svg は html に直接置くことができる。できるのだけれど、svg は XML なので、html に置くと、html と xml の構文が混じった状態になる。html でなく xhtml なら問題ないかもしれなけれども、html5 は xhtml から離れる方向に進んだので、文法が異なる。
vfm の処理は mdast から hast へ、hast から html へと変換を行う。hast では parse5 が使われている。ただし parse5はXML をサポートしておらず、全て html として解釈する。
mdast の段階で svg を埋め込むと、hast で svg がパースされる。svg は XML なので内部に xhtml が埋め込まれているけれども、parse5 で html として処理される。結果 svg 中の xhtml <br />
が html のようなもの <br></br>
に変換されて svg に再び埋め込まれる。Chrome は svg 内の xhtml <br></br>
が処理できずに空白になる、ということが起こっていた。
data:
スキームを使うと、データが混ざらないので、上記の現象を回避できる。
Discussion