过年那两天尝试在网站上放一个蛇引理的交换图热热手, 结果发现 MathJax 没有把 Pgf 和 TikZ 写进去, 所以不能用 tikz-cd 画交换图. 自带的 ams-cd 又只能画水平和竖直的箭头, 约等于什么都画不了. 那怎么办呢?
上网搜了一下, The Stacks Project 用的是 XY-pic, 香蕉空间用基于 MediaWiki, 都不是我们想要的. 但是找到了一个过滤器 hexo-filter-tikzjax
1, 看起来不错. 在此之前我又安装了 hexo-filter-mathjax
并把 NexT 主题配置文件中的 math.mathjax.enable
改回了 false
. 直接把香蕉空间蛇引理2的源码拔过来, 放在一个 tikz
代码块里, 试用出来的结果如下:
有两个问题: 第一个是太小了, 第二个是颜色不对. 还好这两个问题都不难解决: 第一个问题只需把 svg
文件的 width
和 height
都变成原来的 1.5 倍即可, 不合适的做法是直接在头部的 <style>.tikzjax{...}</style>
中添加 transform: scale(1.5)
, 也就是直接修改 hexo-filter-tikzjax/dist/common.js
中的 export.defaultConfig.inline_style
(或者主文件夹 _config.yml
中的 tikzjax
设置? ), 因为这会造成一些不好的显示问题. 第二个问题查看 svg
文件可以发现: 前者的 stroke
和 fill
都是 currentColor
, 这里是 #555
; 而后者的 stroke
是纯黑色 #000
. 这个不难改, 但是直接在开头改完发现 fill="#000"
. 这个应该只需要把所有的 #000
替换成 currentColor
就行.
为了还能显示原来的效果, 我把原来识别 tikz
代码块的部分改成了识别 tikz
或 tikzcd
代码块, 使用的占位符也做了修改, 从而可以被后续插入 svg
部分的正则表达式认出来不同, 进而应用上面的变化. 具体来讲, 将 hexo-filter-tikzjax/dist/render-tikzjax.js
修改为:
1 | ; |
再把 insert-svg.js
修改为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 ;
Object.defineProperty(exports, "__esModule", { value: true });
exports.insertSvg = void 0;
const common_1 = require("./common");
/**
* Insert generated SVGs into HTML of the post as inline tags.
*
* We separate this function from `renderTikzjax` and run them in different filters,
* since we need the Markdown source to render TikZ graphics (in `before_post_render`).
* But insert SVGs into Markdown source will cause problems, so we wait until the Markdown
* source is rendered into HTML, then insert SVGs into HTML (in `after_render:html`).
*
* Since we need to process archive/tags/categories pages too, if they contains posts which
* have TikZ graphs in it, the `after_post_render` filter is not sufficient.
*/
function insertSvg(html, locals) {
const config = this.config.tikzjax;
const page = locals.page;
const indexContains = page.__index && page.posts.toArray().find((post) => post.tikzjax);
// Only process if a post contains TikZ, or it's an index page and one of the posts contains TikZ.
if (!page.tikzjax && !config.every_page && !indexContains) {
return;
}
// Prepend CSS for TikZJax.
html = html.replace(/<head>(?!<\/head>).+?<\/head>/s, (str) => str.replace('</head>', `<link rel="stylesheet" type="text/css" href="${config.font_css_url}" />` +
(config.inline_style ? `<style>${config.inline_style}</style>` : '') +
'</head>'));
// Find all TikZ placeholders inserted by `renderTikzjax`.
const regex = /<!-- tikzjax-(cd-)?placeholder-(\w+?) -->/g;
const matches = html.matchAll(regex);
const debug = (...args) => this.log.debug('[hexo-filter-tikzjax]', ...args);
for (const match of matches) {
const hash = match[2]?.trim();
if (!hash) {
continue;
}
const svg = common_1.localStorage.getItem(hash);
debug('Looking for SVG in cache...', hash);
if (svg) {
if (match[1]) {
const svg_cd = svg.replace('g stroke="#000"', 'g stroke="#000" fill="#000"')
.replaceAll('#000', 'currentColor')
.replace(/\b(width|height)=["']([\d.]+)["']/g, (match, attr, value) => {
const scaledValue = parseFloat(value) * 1.5;
return `${attr}="${scaledValue.toFixed(3)}"`;
})
html = html.replace(match[0], `<p><span class="tikzjax">${svg_cd}</span></p>`);
debug('SVG commutative diagram inserted!', hash);
}
else {
html = html.replace(match[0], `<p><span class="tikzjax">${svg}</span></p>`);
debug('SVG inserted!', hash);
}
}
else {
debug('SVG not found in cache. Skipped.', hash);
}
}
return html;
}
exports.insertSvg = insertSvg;
于是现在只需要使用 tikzcd
代码块就能画出想要的交换图:
本来以为事情到这里就结束了, 结果经过仔细观察, 这字符的间距是不是不太对? 它好像把我的 ker 拆成了 k-er, 把我的 coker 拆成了 cok-er, 所以 k 和 e 的间距变小了一点. 这是不能容忍的, 必须要出重拳!
查看 hexo-filter-tikzjax
的源代码, 发现生成部分主要调用的是作者的下层包 node-tikzjax.default
, 查看 node-tikzjax/disc/index.js
:1
2
3
4
5
6
7
8
9
10// row 18:
const dvi2svg_1 = require("./dvi2svg");
// row 24-30:
async function tex2svg(input, options) {
await (0, bootstrap_1.load)();
const dvi = await (0, bootstrap_1.tex)(input, options);
const svg = await (0, dvi2svg_1.dvi2svg)(dvi, options);
return svg;
}
exports.default = tex2svg;
去 dvi2svg.js
里看了看:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// row 5:
const dvi2html_1 = require("@prinsss/dvi2html");
// row 16-27:
let html = '';
const dom = new jsdom_1.JSDOM(`<!DOCTYPE html>`);
const document = dom.window.document;
async function* streamBuffer() {
yield Buffer.from(dvi);
return;
}
await (0, dvi2html_1.dvi2html)(streamBuffer(), {
write(chunk) {
html = html + chunk.toString();
},
});
核心又是另一个库 @prinsss/dvi2html
中的 dvi2html
, 去看 /lib/index.js
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// row 59-75
function dvi2html(dviStream, htmlStream) {
return __awaiter(this, void 0, void 0, function () {
var parser, machine;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
parser = papersize_1.default(svg_1.default(psfile_1.default(ps_1.default(color_1.default(parser_1.mergeText(parser_1.dviParser(dviStream)))))));
machine = new html_1.default(htmlStream);
return [4 /*yield*/, parser_1.execute(parser, machine)];
case 1:
_a.sent();
return [2 /*return*/, machine];
}
});
});
}
exports.dvi2html = dvi2html;
其中连续调用了一长串函数, 看着怪吓人的.
直接编译 tex
得到的 dvi
确实把 ker 和 coker 拆开了, 所以是 parser_1.mergeText
的问题吗? 再往后的代码实在看不懂了, 对 dvi
也不熟, 就这样吧.
1. https://github.com/prinsss/hexo-filter-tikzjax ↩
2. https://www.bananaspace.org/wiki/%E8%9B%87%E5%BD%A2%E5%BC%95%E7%90%86 ↩