@markmap/plugin-math 0.0.0-stage → 1.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Gerald
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
- # Temporary Holding Version
1
+ # @markmap/plugin-math
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TeX math in Markmap labels, rendered with MathJax.
4
+
5
+ See the [Markmap README](https://github.com/markmap/markmap#readme) for how the packages fit together.
@@ -0,0 +1,23 @@
1
+ import type { ContentPlugin } from '@markmap/core';
2
+ import type { MathSvg } from './mathjax';
3
+ /** Parses `$…$` (and `$$…$$`) into math nodes. Pass it to `parse(md, { plugins })`. */
4
+ export { default as remarkMath } from 'remark-math';
5
+ export interface MathContentOptions {
6
+ /**
7
+ * Load the TeX converter. Defaults to a dynamic import of MathJax, so it stays out of
8
+ * the initial bundle and loads the first time a formula is drawn.
9
+ */
10
+ load?: () => Promise<{
11
+ texToSvg(tex: string, display: boolean): MathSvg;
12
+ }>;
13
+ /** Size of 1ex as a fraction of the font size. */
14
+ exFactor?: number;
15
+ }
16
+ /**
17
+ * Draws `$…$` in labels as MathJax SVG. Formulas are measured from the TeX source until
18
+ * MathJax has loaded and converted them, then the view renders again with the real
19
+ * size. Invalid TeX keeps showing its source.
20
+ */
21
+ export declare function createMathContent(options?: MathContentOptions): ContentPlugin;
22
+ /** Math in labels, with MathJax loaded on first use. */
23
+ export declare const mathContent: ContentPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,186 @@
1
+ import { n as withColor } from "./shapes-Bx9ID-CD.js";
2
+ import remarkMath from "remark-math";
3
+ // src/index.ts
4
+ /** Placeholder and error look: the TeX source in the code font. */
5
+ function sourceStyle(style, ctx) {
6
+ return {
7
+ font: {
8
+ ...style.font,
9
+ family: ctx.theme.codeFontFamily,
10
+ size: style.font.size * .875
11
+ },
12
+ fill: "var(--mm-code-text)"
13
+ };
14
+ }
15
+ function sourceSize(tex, style, ctx) {
16
+ const { font } = sourceStyle(style, ctx);
17
+ return {
18
+ width: ctx.measurer.measure(tex, font),
19
+ height: style.font.lineHeight,
20
+ baseline: (style.font.lineHeight + style.font.size * .7) / 2
21
+ };
22
+ }
23
+ /** Fit a `viewBox` in `box`, centered and keeping its aspect ratio, like SVG does. */
24
+ function fitViewBox(viewBox, box) {
25
+ const [minX, minY, width, height] = viewBox.split(/\s+/).map(Number);
26
+ const k = Math.min(box.width / width, box.height / height);
27
+ return [
28
+ k,
29
+ 0,
30
+ 0,
31
+ k,
32
+ box.x + (box.width - width * k) / 2 - minX * k,
33
+ box.y + (box.height - height * k) / 2 - minY * k
34
+ ];
35
+ }
36
+ var escapeAttr = (value) => value.replace(/[&"<>]/g, (c) => `&#${c.charCodeAt(0)};`);
37
+ var isMathCode = (node) => node?.type === "element" && node.tagName === "code" && Array.isArray(node.properties.className) && node.properties.className.includes("language-math");
38
+ var textOf = (node) => node.children.map((child) => child.type === "text" ? child.value : "").join("");
39
+ /** Standalone HTML `<svg>` for a popover, sized in ex so it scales with the text. */
40
+ function popoverSvg(tex, svg) {
41
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${svg.width}ex" height="${svg.height}ex" viewBox="${escapeAttr(svg.viewBox)}" style="vertical-align:${svg.verticalAlign}ex" role="img" focusable="false" aria-label="${escapeAttr(tex)}">${svg.body}</svg>`;
42
+ }
43
+ /**
44
+ * Draws `$…$` in labels as MathJax SVG. Formulas are measured from the TeX source until
45
+ * MathJax has loaded and converted them, then the view renders again with the real
46
+ * size. Invalid TeX keeps showing its source.
47
+ */
48
+ function createMathContent(options = {}) {
49
+ const load = options.load ?? (() => import("./mathjax-ClZ0hICZ.js"));
50
+ const exFactor = options.exFactor ?? .5;
51
+ let converter;
52
+ /** Converted formulas by TeX; an `Error` when the TeX is invalid. */
53
+ const formulas = /* @__PURE__ */ new Map();
54
+ const converting = /* @__PURE__ */ new Map();
55
+ const convert = (tex, ctx) => {
56
+ let task = converting.get(tex);
57
+ if (!task) {
58
+ converter ??= load();
59
+ task = converter.then(({ texToSvg }) => {
60
+ try {
61
+ formulas.set(tex, texToSvg(tex, false));
62
+ } catch (error) {
63
+ formulas.set(tex, error instanceof Error ? error : new Error(String(error)));
64
+ }
65
+ }).catch((error) => {
66
+ converter = void 0;
67
+ formulas.set(tex, error instanceof Error ? error : new Error(String(error)));
68
+ }).finally(() => converting.delete(tex));
69
+ converting.set(tex, task);
70
+ }
71
+ ctx.defer?.(task);
72
+ };
73
+ /** Replace `$…$` and `$$…$$` (code with `language-math` after sanitizing) with SVG. */
74
+ const popover = async (tree) => {
75
+ const found = [];
76
+ const visit = (parent) => {
77
+ parent.children.forEach((child, index) => {
78
+ if (child.type !== "element") return;
79
+ const [only] = child.children;
80
+ if (child.tagName === "pre" && child.children.length === 1 && isMathCode(only)) found.push({
81
+ parent,
82
+ index,
83
+ tex: textOf(only),
84
+ display: true
85
+ });
86
+ else if (isMathCode(child)) found.push({
87
+ parent,
88
+ index,
89
+ tex: textOf(child),
90
+ display: false
91
+ });
92
+ else visit(child);
93
+ });
94
+ };
95
+ visit(tree);
96
+ if (!found.length) return;
97
+ let texToSvg;
98
+ try {
99
+ converter ??= load();
100
+ ({texToSvg} = await converter);
101
+ } catch {
102
+ converter = void 0;
103
+ return;
104
+ }
105
+ for (const { parent, index, tex, display } of found) {
106
+ let svg;
107
+ try {
108
+ svg = texToSvg(tex, display);
109
+ } catch {
110
+ continue;
111
+ }
112
+ const markup = popoverSvg(tex, svg);
113
+ const raw = {
114
+ type: "raw",
115
+ value: display ? `<div class="markmap-math-display" style="margin:0.5em 0;text-align:center;overflow-x:auto">${markup}</div>` : markup
116
+ };
117
+ parent.children[index] = raw;
118
+ }
119
+ };
120
+ const formula = (inline, ctx) => {
121
+ const found = formulas.get(inline.value);
122
+ if (!found) convert(inline.value, ctx);
123
+ return found instanceof Error ? void 0 : found;
124
+ };
125
+ return {
126
+ name: "math",
127
+ popover,
128
+ atoms: { inlineMath: {
129
+ measure(inline, style, ctx) {
130
+ const svg = formula(inline, ctx);
131
+ if (!svg) return sourceSize(inline.value, style, ctx);
132
+ const ex = style.font.size * exFactor;
133
+ const height = svg.height * ex;
134
+ return {
135
+ width: svg.width * ex,
136
+ height,
137
+ baseline: height + svg.verticalAlign * ex
138
+ };
139
+ },
140
+ render(inline, box, style, ctx) {
141
+ const svg = formulas.get(inline.value);
142
+ const sourceText = () => {
143
+ const source = sourceStyle(style, ctx);
144
+ const { baseline } = sourceSize(inline.value, style, ctx);
145
+ const run = {
146
+ text: inline.value,
147
+ x: 0,
148
+ y: baseline,
149
+ ...source
150
+ };
151
+ return {
152
+ kind: "text",
153
+ x: box.x,
154
+ y: box.y,
155
+ runs: [run]
156
+ };
157
+ };
158
+ if (!svg || svg instanceof Error) return [sourceText()];
159
+ const attrs = [
160
+ `x="${box.x}"`,
161
+ `y="${box.y}"`,
162
+ `width="${box.width}"`,
163
+ `height="${box.height}"`,
164
+ `viewBox="${escapeAttr(svg.viewBox)}"`,
165
+ `style="color:${escapeAttr(style.fill)};overflow:visible"`
166
+ ].join(" ");
167
+ return [{
168
+ kind: "custom",
169
+ backends: ["svg"],
170
+ svg: () => `<svg ${attrs}>${svg.body}</svg>`
171
+ }, svg.shapes ? {
172
+ kind: "group",
173
+ backends: ["canvas"],
174
+ transform: fitViewBox(svg.viewBox, box),
175
+ children: withColor(svg.shapes, style.fill)
176
+ } : {
177
+ ...sourceText(),
178
+ backends: ["canvas"]
179
+ }];
180
+ }
181
+ } }
182
+ };
183
+ }
184
+ /** Math in labels, with MathJax loaded on first use. */
185
+ var mathContent = createMathContent();
186
+ export { createMathContent, mathContent, remarkMath };
@@ -0,0 +1,122 @@
1
+ import { t as svgToPrimitives } from "./shapes-Bx9ID-CD.js";
2
+ import "@mathjax/src/js/input/tex/base/BaseConfiguration.js";
3
+ import "@mathjax/src/js/input/tex/action/ActionConfiguration.js";
4
+ import "@mathjax/src/js/input/tex/ams/AmsConfiguration.js";
5
+ import "@mathjax/src/js/input/tex/amscd/AmsCdConfiguration.js";
6
+ import "@mathjax/src/js/input/tex/bbox/BboxConfiguration.js";
7
+ import "@mathjax/src/js/input/tex/boldsymbol/BoldsymbolConfiguration.js";
8
+ import "@mathjax/src/js/input/tex/braket/BraketConfiguration.js";
9
+ import "@mathjax/src/js/input/tex/bussproofs/BussproofsConfiguration.js";
10
+ import "@mathjax/src/js/input/tex/cancel/CancelConfiguration.js";
11
+ import "@mathjax/src/js/input/tex/cases/CasesConfiguration.js";
12
+ import "@mathjax/src/js/input/tex/centernot/CenternotConfiguration.js";
13
+ import "@mathjax/src/js/input/tex/color/ColorConfiguration.js";
14
+ import "@mathjax/src/js/input/tex/colortbl/ColortblConfiguration.js";
15
+ import "@mathjax/src/js/input/tex/empheq/EmpheqConfiguration.js";
16
+ import "@mathjax/src/js/input/tex/enclose/EncloseConfiguration.js";
17
+ import "@mathjax/src/js/input/tex/extpfeil/ExtpfeilConfiguration.js";
18
+ import "@mathjax/src/js/input/tex/gensymb/GensymbConfiguration.js";
19
+ import "@mathjax/src/js/input/tex/mathtools/MathtoolsConfiguration.js";
20
+ import "@mathjax/src/js/input/tex/mhchem/MhchemConfiguration.js";
21
+ import "@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js";
22
+ import "@mathjax/src/js/input/tex/noerrors/NoErrorsConfiguration.js";
23
+ import "@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js";
24
+ import "@mathjax/src/js/input/tex/physics/PhysicsConfiguration.js";
25
+ import "@mathjax/src/js/input/tex/tagformat/TagFormatConfiguration.js";
26
+ import "@mathjax/src/js/input/tex/textcomp/TextcompConfiguration.js";
27
+ import "@mathjax/src/js/input/tex/textmacros/TextMacrosConfiguration.js";
28
+ import "@mathjax/src/js/input/tex/unicode/UnicodeConfiguration.js";
29
+ import "@mathjax/src/js/input/tex/upgreek/UpgreekConfiguration.js";
30
+ import "@mathjax/src/js/input/tex/verb/VerbConfiguration.js";
31
+ import { MathJaxTexFont } from "@mathjax/mathjax-tex-font/js/svg.js";
32
+ import { liteAdaptor } from "@mathjax/src/js/adaptors/liteAdaptor.js";
33
+ import { RegisterHTMLHandler } from "@mathjax/src/js/handlers/html.js";
34
+ import { TeX } from "@mathjax/src/js/input/tex.js";
35
+ import { mathjax } from "@mathjax/src/js/mathjax.js";
36
+ import { SVG } from "@mathjax/src/js/output/svg.js";
37
+ // src/mathjax.ts
38
+ /**
39
+ * TeX packages that only lay out math. The rest, such as `html`, `texhtml`, `require`,
40
+ * `autoload`, `setoptions` and `configmacros`, can reach outside the formula or change
41
+ * the converter, so Markdown cannot use them.
42
+ */
43
+ var SAFE_PACKAGES = [
44
+ "base",
45
+ "action",
46
+ "ams",
47
+ "amscd",
48
+ "bbox",
49
+ "boldsymbol",
50
+ "braket",
51
+ "bussproofs",
52
+ "cancel",
53
+ "cases",
54
+ "centernot",
55
+ "color",
56
+ "colortbl",
57
+ "empheq",
58
+ "enclose",
59
+ "extpfeil",
60
+ "gensymb",
61
+ "mathtools",
62
+ "mhchem",
63
+ "newcommand",
64
+ "noerrors",
65
+ "noundefined",
66
+ "physics",
67
+ "tagformat",
68
+ "textcomp",
69
+ "textmacros",
70
+ "unicode",
71
+ "upgreek",
72
+ "verb"
73
+ ];
74
+ var adaptor = liteAdaptor();
75
+ RegisterHTMLHandler(adaptor);
76
+ var doc = mathjax.document("", {
77
+ InputJax: new TeX({ packages: SAFE_PACKAGES }),
78
+ OutputJax: new SVG({
79
+ fontData: MathJaxTexFont,
80
+ fontCache: "none",
81
+ linebreaks: { inline: false }
82
+ })
83
+ });
84
+ var ex = (value) => Number.parseFloat(value ?? "") || 0;
85
+ /** MathJax's element tree in the form {@link svgToPrimitives} takes. */
86
+ function toSvgNode(node) {
87
+ const attrs = {};
88
+ for (const { name, value } of adaptor.allAttributes(node)) attrs[name] = value;
89
+ const children = adaptor.childNodes(node).map((child) => {
90
+ const tag = adaptor.kind(child);
91
+ return tag.startsWith("#") ? {
92
+ tag,
93
+ attrs: {},
94
+ children: []
95
+ } : toSvgNode(child);
96
+ });
97
+ return {
98
+ tag: adaptor.kind(node),
99
+ attrs,
100
+ children
101
+ };
102
+ }
103
+ /** Convert TeX to SVG. Throws on TeX errors. */
104
+ function texToSvg(tex, display) {
105
+ const container = doc.convert(tex, { display });
106
+ const svg = adaptor.firstChild(container);
107
+ const body = adaptor.innerHTML(svg).replace(/\sdata-latex(?:-item)?="[^"]*"/g, "");
108
+ if (body.includes("data-mjx-error")) {
109
+ const message = /data-mjx-error="([^"]*)"/.exec(body)?.[1] ?? "TeX error";
110
+ throw new Error(message);
111
+ }
112
+ const style = adaptor.getAttribute(svg, "style") ?? "";
113
+ return {
114
+ body: body.replace(/<\/?(?:a|script|foreignObject)\b[^>]*>/gi, "").replace(/\shref="[^"]*"/gi, ""),
115
+ viewBox: adaptor.getAttribute(svg, "viewBox") ?? "0 0 0 0",
116
+ width: ex(adaptor.getAttribute(svg, "width")),
117
+ height: ex(adaptor.getAttribute(svg, "height")),
118
+ verticalAlign: ex(/vertical-align:\s*([-\d.]+)ex/.exec(style)?.[1]),
119
+ shapes: svgToPrimitives(toSvgNode(svg).children)
120
+ };
121
+ }
122
+ export { texToSvg };
@@ -0,0 +1,48 @@
1
+ import '@mathjax/src/js/input/tex/base/BaseConfiguration.js';
2
+ import '@mathjax/src/js/input/tex/action/ActionConfiguration.js';
3
+ import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js';
4
+ import '@mathjax/src/js/input/tex/amscd/AmsCdConfiguration.js';
5
+ import '@mathjax/src/js/input/tex/bbox/BboxConfiguration.js';
6
+ import '@mathjax/src/js/input/tex/boldsymbol/BoldsymbolConfiguration.js';
7
+ import '@mathjax/src/js/input/tex/braket/BraketConfiguration.js';
8
+ import '@mathjax/src/js/input/tex/bussproofs/BussproofsConfiguration.js';
9
+ import '@mathjax/src/js/input/tex/cancel/CancelConfiguration.js';
10
+ import '@mathjax/src/js/input/tex/cases/CasesConfiguration.js';
11
+ import '@mathjax/src/js/input/tex/centernot/CenternotConfiguration.js';
12
+ import '@mathjax/src/js/input/tex/color/ColorConfiguration.js';
13
+ import '@mathjax/src/js/input/tex/colortbl/ColortblConfiguration.js';
14
+ import '@mathjax/src/js/input/tex/empheq/EmpheqConfiguration.js';
15
+ import '@mathjax/src/js/input/tex/enclose/EncloseConfiguration.js';
16
+ import '@mathjax/src/js/input/tex/extpfeil/ExtpfeilConfiguration.js';
17
+ import '@mathjax/src/js/input/tex/gensymb/GensymbConfiguration.js';
18
+ import '@mathjax/src/js/input/tex/mathtools/MathtoolsConfiguration.js';
19
+ import '@mathjax/src/js/input/tex/mhchem/MhchemConfiguration.js';
20
+ import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js';
21
+ import '@mathjax/src/js/input/tex/noerrors/NoErrorsConfiguration.js';
22
+ import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js';
23
+ import '@mathjax/src/js/input/tex/physics/PhysicsConfiguration.js';
24
+ import '@mathjax/src/js/input/tex/tagformat/TagFormatConfiguration.js';
25
+ import '@mathjax/src/js/input/tex/textcomp/TextcompConfiguration.js';
26
+ import '@mathjax/src/js/input/tex/textmacros/TextMacrosConfiguration.js';
27
+ import '@mathjax/src/js/input/tex/unicode/UnicodeConfiguration.js';
28
+ import '@mathjax/src/js/input/tex/upgreek/UpgreekConfiguration.js';
29
+ import '@mathjax/src/js/input/tex/verb/VerbConfiguration.js';
30
+ import type { Primitive } from '@markmap/core';
31
+ /** A formula as self-contained SVG, measured in ex. */
32
+ export interface MathSvg {
33
+ /** Markup inside the formula's `<svg>` element: paths only, no fonts or links. */
34
+ body: string;
35
+ viewBox: string;
36
+ width: number;
37
+ height: number;
38
+ /** Distance of the baseline below the bottom edge (usually negative), from `vertical-align`. */
39
+ verticalAlign: number;
40
+ /**
41
+ * The formula as primitives in `viewBox` coordinates, for backends that cannot use SVG
42
+ * markup, with `currentColor` standing for the text color. `undefined` when the
43
+ * formula uses something that has no primitive.
44
+ */
45
+ shapes?: Primitive[];
46
+ }
47
+ /** Convert TeX to SVG. Throws on TeX errors. */
48
+ export declare function texToSvg(tex: string, display: boolean): MathSvg;
@@ -0,0 +1,217 @@
1
+ import { multiply, translate } from "@markmap/core";
2
+ // src/shapes.ts
3
+ /** Paint that stands for the label's text color until a formula is drawn. */
4
+ var CURRENT_COLOR = "currentColor";
5
+ var Unsupported = class extends Error {};
6
+ var COMMON_ATTRS = [
7
+ "transform",
8
+ "fill",
9
+ "stroke",
10
+ "stroke-width"
11
+ ];
12
+ var ALLOWED_ATTRS = {
13
+ g: COMMON_ATTRS,
14
+ path: [...COMMON_ATTRS, "d"],
15
+ rect: [
16
+ ...COMMON_ATTRS,
17
+ "x",
18
+ "y",
19
+ "width",
20
+ "height"
21
+ ],
22
+ line: [
23
+ ...COMMON_ATTRS,
24
+ "x1",
25
+ "y1",
26
+ "x2",
27
+ "y2"
28
+ ],
29
+ ellipse: [
30
+ ...COMMON_ATTRS,
31
+ "cx",
32
+ "cy",
33
+ "rx",
34
+ "ry"
35
+ ],
36
+ svg: [
37
+ "x",
38
+ "y",
39
+ "width",
40
+ "height",
41
+ "viewBox"
42
+ ]
43
+ };
44
+ function number(value, fallback) {
45
+ if (value == null && fallback != null) return fallback;
46
+ const result = Number(value);
47
+ if (value == null || value.trim() === "" || !Number.isFinite(result)) throw new Unsupported();
48
+ return result;
49
+ }
50
+ var numbers = (value) => value.trim().split(/[\s,]+/).map((part) => number(part));
51
+ function parseTransform(value) {
52
+ let matrix = [
53
+ 1,
54
+ 0,
55
+ 0,
56
+ 1,
57
+ 0,
58
+ 0
59
+ ];
60
+ if (value.replace(/(\w+)\(([^)]*)\)/g, (_, name, args) => {
61
+ const v = numbers(args);
62
+ let next;
63
+ if (name === "translate" && v.length <= 2) next = translate(v[0], v[1] ?? 0);
64
+ else if (name === "scale" && v.length <= 2) next = [
65
+ v[0],
66
+ 0,
67
+ 0,
68
+ v[1] ?? v[0],
69
+ 0,
70
+ 0
71
+ ];
72
+ else if (name === "matrix" && v.length === 6) next = v;
73
+ else throw new Unsupported();
74
+ matrix = multiply(matrix, next);
75
+ return "";
76
+ }).trim()) throw new Unsupported();
77
+ return matrix;
78
+ }
79
+ var inheritPaint = (value, current) => value == null ? current : value === "none" ? void 0 : value;
80
+ function inherit(attrs, paints) {
81
+ return {
82
+ fill: inheritPaint(attrs.fill, paints.fill),
83
+ stroke: inheritPaint(attrs.stroke, paints.stroke),
84
+ strokeWidth: number(attrs["stroke-width"], paints.strokeWidth)
85
+ };
86
+ }
87
+ /** A shape's paints; a stroke of width 0 draws nothing. */
88
+ var paintsOf = ({ fill, stroke, strokeWidth }) => ({
89
+ fill,
90
+ ...stroke && strokeWidth > 0 ? {
91
+ stroke,
92
+ strokeWidth
93
+ } : {}
94
+ });
95
+ function shape(node, paints) {
96
+ const { attrs } = node;
97
+ switch (node.tag) {
98
+ case "path":
99
+ if (!attrs.d) throw new Unsupported();
100
+ return {
101
+ kind: "path",
102
+ d: attrs.d,
103
+ ...paintsOf(paints)
104
+ };
105
+ case "rect": return {
106
+ kind: "rect",
107
+ x: number(attrs.x, 0),
108
+ y: number(attrs.y, 0),
109
+ w: number(attrs.width),
110
+ h: number(attrs.height),
111
+ ...paintsOf(paints)
112
+ };
113
+ case "line": {
114
+ const [x1, y1] = [number(attrs.x1, 0), number(attrs.y1, 0)];
115
+ const [x2, y2] = [number(attrs.x2, 0), number(attrs.y2, 0)];
116
+ return {
117
+ kind: "path",
118
+ d: `M${x1},${y1}L${x2},${y2}`,
119
+ ...paintsOf(paints)
120
+ };
121
+ }
122
+ case "ellipse": {
123
+ const [cx, cy] = [number(attrs.cx, 0), number(attrs.cy, 0)];
124
+ const [rx, ry] = [number(attrs.rx), number(attrs.ry)];
125
+ const arc = `A${rx},${ry} 0 1 0`;
126
+ return {
127
+ kind: "path",
128
+ d: `M${cx - rx},${cy}${arc} ${cx + rx},${cy}${arc} ${cx - rx},${cy}Z`,
129
+ ...paintsOf(paints)
130
+ };
131
+ }
132
+ default: throw new Unsupported();
133
+ }
134
+ }
135
+ /**
136
+ * What a nested `<svg>` does to its content: place it, fit its view box to its size, and
137
+ * hide what overflows. MathJax relies on that to cut stretched rules to length.
138
+ */
139
+ function nestedView({ x, y, width, height, viewBox }) {
140
+ const [minX, minY, boxWidth, boxHeight] = numbers(viewBox ?? "");
141
+ const [w, h] = [number(width), number(height)];
142
+ if (!(boxWidth > 0) || !(boxHeight > 0)) throw new Unsupported();
143
+ const k = Math.min(w / boxWidth, h / boxHeight);
144
+ const left = minX - (w / k - boxWidth) / 2;
145
+ const top = minY - (h / k - boxHeight) / 2;
146
+ return {
147
+ transform: [
148
+ k,
149
+ 0,
150
+ 0,
151
+ k,
152
+ number(x, 0) - left * k,
153
+ number(y, 0) - top * k
154
+ ],
155
+ clip: {
156
+ x: left,
157
+ y: top,
158
+ width: w / k,
159
+ height: h / k
160
+ }
161
+ };
162
+ }
163
+ function convert(node, outer) {
164
+ const allowed = ALLOWED_ATTRS[node.tag];
165
+ if (!allowed) throw new Unsupported();
166
+ for (const name of Object.keys(node.attrs)) if (!allowed.includes(name) && !name.startsWith("data-")) throw new Unsupported();
167
+ const paints = inherit(node.attrs, outer);
168
+ const isContainer = node.tag === "g" || node.tag === "svg";
169
+ if (!isContainer && node.children.length) throw new Unsupported();
170
+ const content = isContainer ? node.children.map((child) => convert(child, paints)) : [shape(node, paints)];
171
+ if (node.tag === "svg") return {
172
+ kind: "group",
173
+ ...nestedView(node.attrs),
174
+ children: content
175
+ };
176
+ const { transform } = node.attrs;
177
+ if (!isContainer && !transform) return content[0];
178
+ return {
179
+ kind: "group",
180
+ ...transform ? { transform: parseTransform(transform) } : {},
181
+ children: content
182
+ };
183
+ }
184
+ /**
185
+ * Turn MathJax's SVG into primitives, in its view box coordinates, for backends that
186
+ * cannot use SVG markup. Only what MathJax is known to emit is accepted: anything else
187
+ * returns `undefined`, so a formula is never drawn wrong.
188
+ */
189
+ function svgToPrimitives(nodes) {
190
+ try {
191
+ const paints = {
192
+ fill: CURRENT_COLOR,
193
+ strokeWidth: 1
194
+ };
195
+ return nodes.map((node) => convert(node, paints));
196
+ } catch (error) {
197
+ if (error instanceof Unsupported) return void 0;
198
+ throw error;
199
+ }
200
+ }
201
+ /** A copy of `primitives` with the text color filled in. */
202
+ function withColor(primitives, color) {
203
+ const paint = (value) => value === CURRENT_COLOR ? color : value;
204
+ return primitives.map((p) => {
205
+ if (p.kind === "group") return {
206
+ ...p,
207
+ children: withColor(p.children, color)
208
+ };
209
+ if (p.kind === "rect" || p.kind === "path") return {
210
+ ...p,
211
+ fill: paint(p.fill),
212
+ stroke: paint(p.stroke)
213
+ };
214
+ return p;
215
+ });
216
+ }
217
+ export { withColor as n, svgToPrimitives as t };
@@ -0,0 +1,15 @@
1
+ import { type Primitive } from '@markmap/core';
2
+ /** An element of MathJax's SVG output. */
3
+ export interface SvgNode {
4
+ tag: string;
5
+ attrs: Readonly<Record<string, string>>;
6
+ children: readonly SvgNode[];
7
+ }
8
+ /**
9
+ * Turn MathJax's SVG into primitives, in its view box coordinates, for backends that
10
+ * cannot use SVG markup. Only what MathJax is known to emit is accepted: anything else
11
+ * returns `undefined`, so a formula is never drawn wrong.
12
+ */
13
+ export declare function svgToPrimitives(nodes: readonly SvgNode[]): Primitive[] | undefined;
14
+ /** A copy of `primitives` with the text color filled in. */
15
+ export declare function withColor(primitives: readonly Primitive[], color: string): Primitive[];
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@markmap/plugin-math",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0-next.0",
4
+ "description": "TeX math in Markmap labels, rendered with MathJax",
5
+ "homepage": "https://github.com/markmap/markmap#readme",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/markmap/markmap.git",
10
+ "directory": "packages/plugin-math"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "@markmap/core": "1.0.0-next.0",
28
+ "@mathjax/mathjax-tex-font": "^4.1.3",
29
+ "@mathjax/src": "^4.1.3",
30
+ "mdast-util-math": "^3.0.0",
31
+ "remark-math": "^6.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@markmap/parse": "1.0.0-next.0",
35
+ "@markmap/render": "1.0.0-next.0",
36
+ "@types/hast": "^3.0.5",
37
+ "@types/mdast": "^4.0.4"
38
+ },
39
+ "scripts": {
40
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
41
+ "build:types": "tsc -b",
42
+ "build:js": "vite build"
43
+ }
6
44
  }