rehype-matvis 0.1.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/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # rehype-matvis
2
+
3
+ A [rehype](https://github.com/rehypejs/rehype) plugin that turns fenced ` ```matvis ` code blocks into inline SVG diagrams for explaining ML internals: tensor shape/strides/byte-offsets, tiled matmul, and broadcasting. No external services, no rasterized images — just SVG spliced directly into your HTML at build time.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install rehype-matvis
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Drop `rehypeMatvis` into your existing `unified`/rehype pipeline, **immediately after `remark-rehype` and before any syntax highlighter** (e.g. `rehype-shiki`, `rehype-pretty-code`). If it runs after the highlighter, the highlighter will try (and fail) to tokenize the `matvis` language block.
14
+
15
+ ```js
16
+ import rehypeMatvis from "rehype-matvis";
17
+ import { unified } from "unified";
18
+ import remarkParse from "remark-parse";
19
+ import remarkRehype from "remark-rehype";
20
+ import rehypeStringify from "rehype-stringify";
21
+
22
+ const processor = unified()
23
+ .use(remarkParse)
24
+ .use(remarkRehype)
25
+ .use(rehypeMatvis) // <- here, before any syntax highlighter
26
+ .use(rehypeStringify);
27
+ ```
28
+
29
+ `rehype-matvis` splices real hast element nodes into the tree (not a `raw` node), so it works as a drop-in step — you do **not** need `rehype-raw` anywhere in your pipeline. The ` ```matvis ` code-fence language tag stays `matvis` regardless of the package name.
30
+
31
+ Then in a markdown file:
32
+
33
+ ````markdown
34
+ ```matvis
35
+ type: strides
36
+ shape: [4, 4]
37
+ strides: [4, 1]
38
+ ```
39
+ ````
40
+
41
+ ## DSL grammar
42
+
43
+ Every diagram is a YAML mapping with a required `type` discriminator (`strides`, `matmul`, or `broadcast`) plus type-specific fields. Shared optional fields: `title`, `dtype`, `width`.
44
+
45
+ ### `type: strides` — shape / strides / byte-offset grid
46
+
47
+ ```yaml
48
+ type: strides
49
+ title: "Row-major 4x4 float32 matrix"
50
+ dtype: float32 # affects byte-offset math; default 4 bytes if omitted/unknown
51
+ shape: [4, 4] # 1 or 2 dimensions
52
+ strides: [4, 1] # in elements, not bytes
53
+ highlight:
54
+ - cells: [[0, 0], [0, 1]]
55
+ color: accent # accent | warn | info | muted
56
+ label: "stride[1] = 1 elem"
57
+ byte_offsets: true # show computed byte offset per cell
58
+ order: row-major # row-major | col-major (informational, for the caption)
59
+ ```
60
+
61
+ ### `type: matmul` — tiled matmul (`A[M,K] @ B[K,N] = C[M,N]`)
62
+
63
+ ```yaml
64
+ type: matmul
65
+ title: "C = A @ B, tiled 2x2"
66
+ dims: { M: 8, K: 4, N: 8 }
67
+ tile: { M: 2, K: 4, N: 2 } # must evenly divide dims
68
+ highlight_tile: [0, 0] # which output tile to spotlight, and its contributing A/B strips
69
+ labels: { A: "A [M,K]", B: "B [K,N]", C: "C [M,N]" }
70
+ ```
71
+
72
+ ### `type: broadcast` — elementwise broadcasting
73
+
74
+ ```yaml
75
+ type: broadcast
76
+ title: "Broadcasting (3,1) against (1,4)"
77
+ operands:
78
+ - shape: [3, 1]
79
+ label: "A"
80
+ - shape: [1, 4]
81
+ label: "B"
82
+ result_label: "A + B -> (3,4)"
83
+ ```
84
+
85
+ The result shape and which axes stretch are computed automatically — you only describe the operands.
86
+
87
+ ## Theming
88
+
89
+ Every generated SVG ships an inline `<style>` block that reads CSS custom properties, with light-mode fallback colors baked in:
90
+
91
+ | Variable | Purpose | Default |
92
+ |---|---|---|
93
+ | `--matvis-bg` | cell fill | `#ffffff` |
94
+ | `--matvis-fg` | text/stroke (also falls back to `currentColor`) | `currentColor` |
95
+ | `--matvis-border` | cell border | `currentColor` |
96
+ | `--matvis-accent` | highlight color 1 | `#6366f1` |
97
+ | `--matvis-warn` | highlight color 2 | `#f59e0b` |
98
+ | `--matvis-info` | highlight color 3 | `#0ea5e9` |
99
+ | `--matvis-muted` | highlight color 4 / brackets | `#9ca3af` |
100
+
101
+ To support dark mode, define these variables once under your site's dark-mode selector (e.g. `[data-theme="dark"]`) — every diagram re-themes automatically, no JS required.
102
+
103
+ ## Known limitations (v1)
104
+
105
+ - `strides` and `broadcast` operands support at most 2 dimensions.
106
+ - Text that doesn't fit a cell shrinks its font size once; it does not wrap or relocate outside the grid.
107
+ - Highlight groups that share a corner cell (e.g. showing both a row-stride and a column-stride bracket from the same origin cell) can crowd their labels — pick non-overlapping highlight groups where legibility matters.
108
+ - Layout is deterministic grid arithmetic (no collision-avoidance engine), matching a diagram-authoring tool rather than a general graph layout tool.
109
+
110
+ ## Development
111
+
112
+ ```sh
113
+ npm install
114
+ npm test # vitest: DSL parsing, IR/snapshot, and full-pipeline integration tests
115
+ npm run build # tsup -> dist/
116
+ npm run preview # renders preview/posts/*.md through the same pipeline shape as a real blog, to preview/dist/*.html
117
+ ```
118
+
119
+ `preview/dist/index.html` links to one rendered page per diagram type, with a light/dark theme toggle button, for visual QA without needing a real blog.
@@ -0,0 +1,732 @@
1
+ // src/core/errors.ts
2
+ var MatvisParseError = class extends Error {
3
+ line;
4
+ column;
5
+ constructor(message, options) {
6
+ super(message);
7
+ this.name = "MatvisParseError";
8
+ this.line = options?.line;
9
+ this.column = options?.column;
10
+ }
11
+ };
12
+
13
+ // src/core/parse-dsl.ts
14
+ import { parse as parseYaml } from "yaml";
15
+
16
+ // src/core/dsl-schema.ts
17
+ var COLOR_TOKENS = ["default", "accent", "warn", "info", "muted"];
18
+ function fail(message) {
19
+ throw new MatvisParseError(message);
20
+ }
21
+ function isPlainObject(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function expectString(value, field) {
25
+ if (typeof value !== "string") fail(`expected "${field}" to be a string`);
26
+ return value;
27
+ }
28
+ function expectOptionalString(value, field) {
29
+ if (value === void 0) return void 0;
30
+ return expectString(value, field);
31
+ }
32
+ function expectNumber(value, field) {
33
+ if (typeof value !== "number" || !Number.isFinite(value)) {
34
+ fail(`expected "${field}" to be a number`);
35
+ }
36
+ return value;
37
+ }
38
+ function expectNumberArray(value, field) {
39
+ if (!Array.isArray(value) || value.length === 0) {
40
+ fail(`expected "${field}" to be a non-empty array of numbers`);
41
+ }
42
+ return value.map((v, i) => expectNumber(v, `${field}[${i}]`));
43
+ }
44
+ function expectColorToken(value, field) {
45
+ const str = expectString(value, field);
46
+ if (!COLOR_TOKENS.includes(str)) {
47
+ fail(`"${field}" must be one of ${COLOR_TOKENS.join(", ")}, got "${str}"`);
48
+ }
49
+ return str;
50
+ }
51
+ function expectCellCoord(value, field) {
52
+ if (!Array.isArray(value) || value.length !== 2) {
53
+ fail(`expected "${field}" to be a [row, col] pair`);
54
+ }
55
+ return [expectNumber(value[0], `${field}[0]`), expectNumber(value[1], `${field}[1]`)];
56
+ }
57
+ function validateHighlightGroups(value, field) {
58
+ if (value === void 0) return void 0;
59
+ if (!Array.isArray(value)) fail(`expected "${field}" to be an array`);
60
+ return value.map((group, i) => {
61
+ if (!isPlainObject(group)) fail(`expected "${field}[${i}]" to be an object`);
62
+ const cellsRaw = group.cells;
63
+ if (!Array.isArray(cellsRaw) || cellsRaw.length === 0) {
64
+ fail(`expected "${field}[${i}].cells" to be a non-empty array of [row, col] pairs`);
65
+ }
66
+ const cells = cellsRaw.map((c, j) => expectCellCoord(c, `${field}[${i}].cells[${j}]`));
67
+ const color = group.color === void 0 ? void 0 : expectColorToken(group.color, `${field}[${i}].color`);
68
+ const label = expectOptionalString(group.label, `${field}[${i}].label`);
69
+ return { cells, color, label };
70
+ });
71
+ }
72
+ function validateBase(raw) {
73
+ return {
74
+ title: expectOptionalString(raw.title, "title"),
75
+ dtype: expectOptionalString(raw.dtype, "dtype"),
76
+ width: raw.width === void 0 ? void 0 : expectNumber(raw.width, "width")
77
+ };
78
+ }
79
+ function validateStridesConfig(raw) {
80
+ const base = validateBase(raw);
81
+ const shape = expectNumberArray(raw.shape, "shape");
82
+ const strides = expectNumberArray(raw.strides, "strides");
83
+ if (strides.length !== shape.length) {
84
+ fail(`"strides" must have the same length as "shape" (${shape.length}), got ${strides.length}`);
85
+ }
86
+ const order = raw.order === void 0 ? "row-major" : expectString(raw.order, "order");
87
+ if (order !== "row-major" && order !== "col-major") {
88
+ fail(`"order" must be "row-major" or "col-major", got "${order}"`);
89
+ }
90
+ return {
91
+ type: "strides",
92
+ ...base,
93
+ shape,
94
+ strides,
95
+ highlight: validateHighlightGroups(raw.highlight, "highlight"),
96
+ byte_offsets: raw.byte_offsets === void 0 ? false : Boolean(raw.byte_offsets),
97
+ order
98
+ };
99
+ }
100
+ function expectMatmulDims(value, field) {
101
+ if (!isPlainObject(value)) fail(`expected "${field}" to have M, K, N keys`);
102
+ return {
103
+ M: expectNumber(value.M, `${field}.M`),
104
+ K: expectNumber(value.K, `${field}.K`),
105
+ N: expectNumber(value.N, `${field}.N`)
106
+ };
107
+ }
108
+ function validateMatmulConfig(raw) {
109
+ const base = validateBase(raw);
110
+ const dims = expectMatmulDims(raw.dims, "dims");
111
+ const tile = expectMatmulDims(raw.tile, "tile");
112
+ for (const axis of ["M", "K", "N"]) {
113
+ if (dims[axis] % tile[axis] !== 0) {
114
+ fail(`"tile.${axis}" (${tile[axis]}) must evenly divide "dims.${axis}" (${dims[axis]})`);
115
+ }
116
+ }
117
+ let highlightTile;
118
+ if (raw.highlight_tile !== void 0) {
119
+ highlightTile = expectCellCoord(raw.highlight_tile, "highlight_tile");
120
+ }
121
+ let labels;
122
+ if (raw.labels !== void 0) {
123
+ if (!isPlainObject(raw.labels)) fail(`expected "labels" to be an object`);
124
+ labels = {
125
+ A: expectOptionalString(raw.labels.A, "labels.A"),
126
+ B: expectOptionalString(raw.labels.B, "labels.B"),
127
+ C: expectOptionalString(raw.labels.C, "labels.C")
128
+ };
129
+ }
130
+ return {
131
+ type: "matmul",
132
+ ...base,
133
+ dims,
134
+ tile,
135
+ highlight_tile: highlightTile,
136
+ labels
137
+ };
138
+ }
139
+ function validateOperand(value, field) {
140
+ if (!isPlainObject(value)) fail(`expected "${field}" to be an object`);
141
+ return {
142
+ shape: expectNumberArray(value.shape, `${field}.shape`),
143
+ label: expectOptionalString(value.label, `${field}.label`)
144
+ };
145
+ }
146
+ function validateBroadcastConfig(raw) {
147
+ const base = validateBase(raw);
148
+ if (!Array.isArray(raw.operands) || raw.operands.length < 2) {
149
+ fail(`expected "operands" to be an array of at least 2 shapes`);
150
+ }
151
+ const operands = raw.operands.map((op, i) => validateOperand(op, `operands[${i}]`));
152
+ return {
153
+ type: "broadcast",
154
+ ...base,
155
+ operands,
156
+ result_label: expectOptionalString(raw.result_label, "result_label")
157
+ };
158
+ }
159
+ function validateDiagramConfig(raw) {
160
+ if (!isPlainObject(raw)) fail("diagram source must be a YAML mapping");
161
+ const type = raw.type;
162
+ if (type === "strides") return validateStridesConfig(raw);
163
+ if (type === "matmul") return validateMatmulConfig(raw);
164
+ if (type === "broadcast") return validateBroadcastConfig(raw);
165
+ fail(`unknown or missing "type" (expected "strides", "matmul", or "broadcast"), got ${JSON.stringify(type)}`);
166
+ }
167
+
168
+ // src/core/parse-dsl.ts
169
+ function parseDSL(source) {
170
+ let raw;
171
+ try {
172
+ raw = parseYaml(source);
173
+ } catch (err) {
174
+ const linePos = err.linePos;
175
+ throw new MatvisParseError(`invalid YAML: ${err.message}`, {
176
+ line: linePos?.[0]?.line,
177
+ column: linePos?.[0]?.col
178
+ });
179
+ }
180
+ return validateDiagramConfig(raw);
181
+ }
182
+
183
+ // src/core/layout/shared.ts
184
+ var BASE_CELL_SIZE = 40;
185
+ var MIN_CELL_SIZE = 14;
186
+ var MAX_GRID_SPAN = 480;
187
+ var GUTTER = 40;
188
+ var PADDING = 24;
189
+ var LABEL_MARGIN = 20;
190
+ function clampCellSize(cols, rows) {
191
+ const maxDim = Math.max(cols, rows);
192
+ const bounded = MAX_GRID_SPAN / maxDim;
193
+ return Math.max(MIN_CELL_SIZE, Math.min(BASE_CELL_SIZE, bounded));
194
+ }
195
+ var HIGHLIGHT_PALETTE = ["accent", "warn", "info", "muted"];
196
+ function paletteColor(index) {
197
+ return HIGHLIGHT_PALETTE[index % HIGHLIGHT_PALETTE.length];
198
+ }
199
+ function cellOrigin(grid, row, col) {
200
+ const cellSize = grid.cellSize ?? BASE_CELL_SIZE;
201
+ return { x: grid.origin.x + col * cellSize, y: grid.origin.y + row * cellSize };
202
+ }
203
+ function cellCenter(grid, row, col) {
204
+ const cellSize = grid.cellSize ?? BASE_CELL_SIZE;
205
+ const o = cellOrigin(grid, row, col);
206
+ return { x: o.x + cellSize / 2, y: o.y + cellSize / 2 };
207
+ }
208
+ function resolveEndpoint(grids, endpoint) {
209
+ if ("x" in endpoint && "y" in endpoint) return endpoint;
210
+ const grid = grids.find((g) => g.id === endpoint.gridId);
211
+ if (!grid) throw new Error(`arrow references unknown grid "${endpoint.gridId}"`);
212
+ return cellCenter(grid, endpoint.row, endpoint.col);
213
+ }
214
+ var DTYPE_BYTE_WIDTHS = {
215
+ float64: 8,
216
+ int64: 8,
217
+ uint64: 8,
218
+ float32: 4,
219
+ int32: 4,
220
+ uint32: 4,
221
+ float16: 2,
222
+ bfloat16: 2,
223
+ int16: 2,
224
+ uint16: 2,
225
+ int8: 1,
226
+ uint8: 1,
227
+ bool: 1
228
+ };
229
+ function dtypeByteWidth(dtype) {
230
+ if (!dtype) return 4;
231
+ return DTYPE_BYTE_WIDTHS[dtype] ?? 4;
232
+ }
233
+
234
+ // src/core/layout/broadcast.ts
235
+ function to2D(shape) {
236
+ if (shape.length === 1) return [1, shape[0]];
237
+ if (shape.length === 2) return [shape[0], shape[1]];
238
+ throw new MatvisParseError(`broadcast operands support at most 2 dimensions, got shape with ${shape.length} dimensions`);
239
+ }
240
+ function broadcastShape(shapes2D) {
241
+ const rows = broadcastAxis(shapes2D.map(([r]) => r));
242
+ const cols = broadcastAxis(shapes2D.map(([, c]) => c));
243
+ return [rows, cols];
244
+ }
245
+ function broadcastAxis(sizes) {
246
+ let result = 1;
247
+ for (const size of sizes) {
248
+ if (size === result || size === 1) {
249
+ result = Math.max(result, size);
250
+ } else if (result === 1) {
251
+ result = size;
252
+ } else {
253
+ throw new MatvisParseError(`shapes are not broadcastable: incompatible sizes ${sizes.join(", ")}`);
254
+ }
255
+ }
256
+ return result;
257
+ }
258
+ function makeCells(rows, cols, stretchedRows, stretchedCols) {
259
+ const cells = [];
260
+ for (let r = 0; r < rows; r++) {
261
+ for (let c = 0; c < cols; c++) {
262
+ cells.push({ row: r, col: c, opacity: stretchedRows || stretchedCols ? 0.5 : void 0 });
263
+ }
264
+ }
265
+ return cells;
266
+ }
267
+ function layoutBroadcast(config) {
268
+ const { operands, result_label, title } = config;
269
+ const shapes2D = operands.map((op) => to2D(op.shape));
270
+ const [resultRows, resultCols] = broadcastShape(shapes2D);
271
+ const maxDim = Math.max(resultRows, resultCols, ...shapes2D.flat());
272
+ const cellSize = clampCellSize(maxDim, maxDim);
273
+ const maxRows = Math.max(resultRows, ...shapes2D.map(([r]) => r));
274
+ const grids = [];
275
+ const arrows = [];
276
+ const labels = [];
277
+ let currentX = PADDING;
278
+ let previousGridId;
279
+ shapes2D.forEach(([rows, cols], i) => {
280
+ const id = `operand-${i}`;
281
+ const origin = { x: currentX, y: PADDING + (maxRows - rows) / 2 * cellSize };
282
+ grids.push({
283
+ id,
284
+ origin,
285
+ rows,
286
+ cols,
287
+ cellSize,
288
+ cells: makeCells(rows, cols, rows === 1 && resultRows > 1, cols === 1 && resultCols > 1)
289
+ });
290
+ labels.push({ at: { x: origin.x, y: origin.y - 8 }, text: operands[i].label ?? id, size: "sm" });
291
+ if (previousGridId) {
292
+ labels.push({
293
+ at: { x: currentX - GUTTER / 2, y: PADDING + maxRows * cellSize / 2 },
294
+ text: "\u2295",
295
+ size: "lg",
296
+ align: "middle"
297
+ });
298
+ }
299
+ previousGridId = id;
300
+ currentX += cols * cellSize + GUTTER;
301
+ });
302
+ const resultOrigin = { x: currentX, y: PADDING + (maxRows - resultRows) / 2 * cellSize };
303
+ const resultId = "result";
304
+ grids.push({
305
+ id: resultId,
306
+ origin: resultOrigin,
307
+ rows: resultRows,
308
+ cols: resultCols,
309
+ cellSize,
310
+ cells: makeCells(resultRows, resultCols, false, false)
311
+ });
312
+ labels.push({ at: { x: resultOrigin.x, y: resultOrigin.y - 8 }, text: "result", size: "sm" });
313
+ if (previousGridId) {
314
+ arrows.push({
315
+ from: { gridId: previousGridId, row: Math.floor(shapes2D[shapes2D.length - 1][0] / 2), col: shapes2D[shapes2D.length - 1][1] - 1 },
316
+ to: { gridId: resultId, row: Math.floor(resultRows / 2), col: 0 },
317
+ style: "arrow"
318
+ });
319
+ }
320
+ labels.push({
321
+ at: { x: PADDING, y: PADDING + maxRows * cellSize + 20 },
322
+ text: result_label ?? `-> (${resultRows}, ${resultCols})`,
323
+ size: "sm"
324
+ });
325
+ return { title, grids, arrows, labels };
326
+ }
327
+
328
+ // src/core/layout/matmul.ts
329
+ function stripCells(rows, cols, highlightRows, highlightCols) {
330
+ const cells = [];
331
+ for (let r = 0; r < rows; r++) {
332
+ for (let c = 0; c < cols; c++) {
333
+ const inRowRange = highlightRows ? r >= highlightRows[0] && r <= highlightRows[1] : false;
334
+ const inColRange = highlightCols ? c >= highlightCols[0] && c <= highlightCols[1] : false;
335
+ const highlighted = highlightRows && highlightCols ? inRowRange && inColRange : inRowRange || inColRange;
336
+ cells.push({ row: r, col: c, color: highlighted ? "info" : void 0 });
337
+ }
338
+ }
339
+ return cells;
340
+ }
341
+ function layoutMatmul(config) {
342
+ const { dims, tile, highlight_tile, labels, title } = config;
343
+ const maxDim = Math.max(dims.M, dims.K, dims.N);
344
+ const cellSize = clampCellSize(maxDim, maxDim);
345
+ let tileRowIdx = 0;
346
+ let tileColIdx = 0;
347
+ if (highlight_tile) {
348
+ [tileRowIdx, tileColIdx] = highlight_tile;
349
+ const tileRowCount = dims.M / tile.M;
350
+ const tileColCount = dims.N / tile.N;
351
+ if (tileRowIdx < 0 || tileRowIdx >= tileRowCount) {
352
+ throw new MatvisParseError(`"highlight_tile[0]" (${tileRowIdx}) out of range [0, ${tileRowCount - 1}]`);
353
+ }
354
+ if (tileColIdx < 0 || tileColIdx >= tileColCount) {
355
+ throw new MatvisParseError(`"highlight_tile[1]" (${tileColIdx}) out of range [0, ${tileColCount - 1}]`);
356
+ }
357
+ }
358
+ const rowRange = [tileRowIdx * tile.M, tileRowIdx * tile.M + tile.M - 1];
359
+ const colRange = [tileColIdx * tile.N, tileColIdx * tile.N + tile.N - 1];
360
+ const rowMid = Math.round((rowRange[0] + rowRange[1]) / 2);
361
+ const colMid = Math.round((colRange[0] + colRange[1]) / 2);
362
+ const cOrigin = {
363
+ x: PADDING + dims.K * cellSize + GUTTER,
364
+ y: PADDING + dims.K * cellSize + GUTTER
365
+ };
366
+ const aOrigin = { x: PADDING, y: cOrigin.y };
367
+ const bOrigin = { x: cOrigin.x, y: PADDING };
368
+ const gridA = {
369
+ id: "A",
370
+ origin: aOrigin,
371
+ rows: dims.M,
372
+ cols: dims.K,
373
+ cellSize,
374
+ cells: stripCells(dims.M, dims.K, rowRange, void 0),
375
+ gridlineEvery: { rows: tile.M, cols: tile.K }
376
+ };
377
+ const gridB = {
378
+ id: "B",
379
+ origin: bOrigin,
380
+ rows: dims.K,
381
+ cols: dims.N,
382
+ cellSize,
383
+ cells: stripCells(dims.K, dims.N, void 0, colRange),
384
+ gridlineEvery: { rows: tile.K, cols: tile.N }
385
+ };
386
+ const gridC = {
387
+ id: "C",
388
+ origin: cOrigin,
389
+ rows: dims.M,
390
+ cols: dims.N,
391
+ cellSize,
392
+ cells: stripCells(dims.M, dims.N, rowRange, colRange).map((cell) => ({
393
+ ...cell,
394
+ color: cell.color ? "accent" : void 0
395
+ })),
396
+ gridlineEvery: { rows: tile.M, cols: tile.N }
397
+ };
398
+ const arrows = [
399
+ {
400
+ from: { gridId: "A", row: rowMid, col: dims.K - 1 },
401
+ to: { gridId: "C", row: rowMid, col: colRange[0] },
402
+ style: "arrow",
403
+ color: "info"
404
+ },
405
+ {
406
+ from: { gridId: "B", row: dims.K - 1, col: colMid },
407
+ to: { gridId: "C", row: rowRange[0], col: colMid },
408
+ style: "arrow",
409
+ color: "info"
410
+ }
411
+ ];
412
+ const textLabels = [
413
+ { at: { x: gridA.origin.x, y: gridA.origin.y - 8 }, text: labels?.A ?? "A", size: "sm" },
414
+ { at: { x: gridB.origin.x, y: gridB.origin.y - 8 }, text: labels?.B ?? "B", size: "sm" },
415
+ { at: { x: gridC.origin.x, y: gridC.origin.y - 8 }, text: labels?.C ?? "C", size: "sm" },
416
+ {
417
+ at: { x: gridA.origin.x, y: gridC.origin.y + dims.M * cellSize + 20 },
418
+ text: `M=${dims.M} K=${dims.K} N=${dims.N} tile=[${tile.M}, ${tile.K}, ${tile.N}]`,
419
+ size: "sm"
420
+ }
421
+ ];
422
+ return { title, grids: [gridA, gridB, gridC], arrows, labels: textLabels };
423
+ }
424
+
425
+ // src/core/layout/strides.ts
426
+ function layoutStrides(config) {
427
+ const { shape, strides, highlight = [], byte_offsets = false, order = "row-major", dtype, title } = config;
428
+ if (shape.length > 2) {
429
+ throw new MatvisParseError(
430
+ `"strides" diagrams support at most 2 dimensions, got shape with ${shape.length} dimensions`
431
+ );
432
+ }
433
+ const rows = shape.length === 1 ? 1 : shape[0];
434
+ const cols = shape.length === 1 ? shape[0] : shape[1];
435
+ const rowStride = shape.length === 1 ? 0 : strides[0];
436
+ const colStride = shape.length === 1 ? strides[0] : strides[1];
437
+ const byteWidth = dtypeByteWidth(dtype);
438
+ const colorMap = /* @__PURE__ */ new Map();
439
+ highlight.forEach((group, i) => {
440
+ const color = group.color ?? paletteColor(i);
441
+ group.cells.forEach(([r, c]) => colorMap.set(`${r},${c}`, color));
442
+ });
443
+ const cells = [];
444
+ for (let r = 0; r < rows; r++) {
445
+ for (let c = 0; c < cols; c++) {
446
+ const offsetElems = r * rowStride + c * colStride;
447
+ cells.push({
448
+ row: r,
449
+ col: c,
450
+ text: byte_offsets ? String(offsetElems * byteWidth) : void 0,
451
+ color: colorMap.get(`${r},${c}`)
452
+ });
453
+ }
454
+ }
455
+ const showRowLabels = rows > 1 && rows <= 12;
456
+ const showColLabels = cols <= 12;
457
+ const cellSize = clampCellSize(cols, rows);
458
+ const origin = {
459
+ x: PADDING + (showRowLabels ? LABEL_MARGIN : 0),
460
+ y: PADDING + (showColLabels ? LABEL_MARGIN : 0)
461
+ };
462
+ const grid = {
463
+ id: "matrix",
464
+ origin,
465
+ rows,
466
+ cols,
467
+ cellSize,
468
+ cells,
469
+ rowLabels: showRowLabels ? Array.from({ length: rows }, (_, i) => String(i)) : void 0,
470
+ colLabels: showColLabels ? Array.from({ length: cols }, (_, i) => String(i)) : void 0
471
+ };
472
+ const arrows = [];
473
+ highlight.forEach((group, i) => {
474
+ if (!group.label) return;
475
+ const color = group.color ?? paletteColor(i);
476
+ const first = group.cells[0];
477
+ const last = group.cells[group.cells.length - 1];
478
+ arrows.push({
479
+ from: { gridId: grid.id, row: first[0], col: first[1] },
480
+ to: { gridId: grid.id, row: last[0], col: last[1] },
481
+ label: group.label,
482
+ style: "bracket",
483
+ color
484
+ });
485
+ });
486
+ const captionParts = [`shape=[${shape.join(", ")}]`, `strides=[${strides.join(", ")}]`, `(${order})`];
487
+ const labels = [
488
+ {
489
+ at: { x: origin.x, y: origin.y + rows * cellSize + 20 },
490
+ text: captionParts.join(" "),
491
+ size: "sm",
492
+ align: "start"
493
+ }
494
+ ];
495
+ return { title, grids: [grid], arrows, labels };
496
+ }
497
+
498
+ // src/core/render/primitives.ts
499
+ function escapeXml(value) {
500
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
501
+ }
502
+ function rect(x, y, width, height, opts = {}) {
503
+ const rx = opts.rx ?? 0;
504
+ const className = opts.className ?? "";
505
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${rx}" class="${className}" />`;
506
+ }
507
+ function text(x, y, content, opts = {}) {
508
+ const anchor = opts.align ?? "middle";
509
+ const fontSize = opts.fontSize ?? 12;
510
+ const className = opts.className ?? "";
511
+ return `<text x="${x}" y="${y}" text-anchor="${anchor}" font-size="${fontSize}" class="${className}">${escapeXml(
512
+ content
513
+ )}</text>`;
514
+ }
515
+ function line(x1, y1, x2, y2, opts = {}) {
516
+ const className = opts.className ?? "";
517
+ const marker = opts.markerEnd ? ` marker-end="url(#${opts.markerEnd})"` : "";
518
+ return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" class="${className}"${marker} />`;
519
+ }
520
+ function path(d, opts = {}) {
521
+ const className = opts.className ?? "";
522
+ return `<path d="${d}" class="${className}" fill="none" />`;
523
+ }
524
+
525
+ // src/core/render/theme.ts
526
+ var ARROWHEAD_DEFS = `<defs>
527
+ <marker id="matvis-arrowhead" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
528
+ <path d="M0,0 L10,5 L0,10 z" class="matvis-arrow-head" />
529
+ </marker>
530
+ </defs>`;
531
+ var THEME_STYLE = `<style>
532
+ .matvis-diagram { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
533
+ .matvis-cell { fill: var(--matvis-bg, #ffffff); stroke: var(--matvis-border, currentColor); stroke-width: 1; opacity: 0.9; }
534
+ .matvis-cell-accent { fill: var(--matvis-accent, #6366f1); opacity: 0.35; }
535
+ .matvis-cell-warn { fill: var(--matvis-warn, #f59e0b); opacity: 0.35; }
536
+ .matvis-cell-info { fill: var(--matvis-info, #0ea5e9); opacity: 0.35; }
537
+ .matvis-cell-muted { fill: var(--matvis-muted, #9ca3af); opacity: 0.35; }
538
+ .matvis-text { fill: var(--matvis-fg, currentColor); }
539
+ .matvis-title { fill: var(--matvis-fg, currentColor); font-weight: 600; }
540
+ .matvis-grid-line-heavy { stroke: var(--matvis-fg, currentColor); stroke-width: 2; }
541
+ .matvis-arrow { stroke: var(--matvis-fg, currentColor); stroke-width: 1.5; }
542
+ .matvis-arrow-accent { stroke: var(--matvis-accent, #6366f1); stroke-width: 1.5; }
543
+ .matvis-arrow-warn { stroke: var(--matvis-warn, #f59e0b); stroke-width: 1.5; }
544
+ .matvis-arrow-info { stroke: var(--matvis-info, #0ea5e9); stroke-width: 1.5; }
545
+ .matvis-arrow-muted { stroke: var(--matvis-muted, #9ca3af); stroke-width: 1.5; }
546
+ .matvis-arrow-head { fill: var(--matvis-fg, currentColor); }
547
+ .matvis-bracket { stroke: var(--matvis-muted, #9ca3af); stroke-width: 1; stroke-dasharray: 3 3; }
548
+ .matvis-error-text { fill: var(--matvis-warn, #dc2626); }
549
+ .matvis-error-box { fill: none; stroke: var(--matvis-warn, #dc2626); stroke-width: 1; stroke-dasharray: 4 2; }
550
+ </style>`;
551
+
552
+ // src/core/render/svg.ts
553
+ var TITLE_HEIGHT = 32;
554
+ function renderGridCells(grid) {
555
+ const cellSize = grid.cellSize ?? 40;
556
+ const parts = [];
557
+ for (const cell of grid.cells) {
558
+ const { x, y } = cellOrigin(grid, cell.row, cell.col);
559
+ const className = cell.color ? `matvis-cell matvis-cell-${cell.color}` : "matvis-cell";
560
+ parts.push(rect(x, y, cellSize, cellSize, { className }));
561
+ if (cell.text) {
562
+ const fontSize = Math.min(12, cellSize * 0.32);
563
+ parts.push(text(x + cellSize / 2, y + cellSize / 2 + fontSize / 3, cell.text, { className: "matvis-text", fontSize }));
564
+ }
565
+ }
566
+ return parts.join("");
567
+ }
568
+ function renderGridlines(grid) {
569
+ if (!grid.gridlineEvery) return "";
570
+ const cellSize = grid.cellSize ?? 40;
571
+ const parts = [];
572
+ const { rows: everyRows, cols: everyCols } = grid.gridlineEvery;
573
+ if (everyCols) {
574
+ for (let c = everyCols; c < grid.cols; c += everyCols) {
575
+ const x = grid.origin.x + c * cellSize;
576
+ parts.push(line(x, grid.origin.y, x, grid.origin.y + grid.rows * cellSize, { className: "matvis-grid-line-heavy" }));
577
+ }
578
+ }
579
+ if (everyRows) {
580
+ for (let r = everyRows; r < grid.rows; r += everyRows) {
581
+ const y = grid.origin.y + r * cellSize;
582
+ parts.push(line(grid.origin.x, y, grid.origin.x + grid.cols * cellSize, y, { className: "matvis-grid-line-heavy" }));
583
+ }
584
+ }
585
+ return parts.join("");
586
+ }
587
+ function renderGridLabels(grid) {
588
+ const cellSize = grid.cellSize ?? 40;
589
+ const parts = [];
590
+ grid.colLabels?.forEach((label, c) => {
591
+ const cx = grid.origin.x + c * cellSize + cellSize / 2;
592
+ parts.push(text(cx, grid.origin.y - 6, label, { className: "matvis-text", fontSize: 10 }));
593
+ });
594
+ grid.rowLabels?.forEach((label, r) => {
595
+ const cy = grid.origin.y + r * cellSize + cellSize / 2 + 4;
596
+ parts.push(text(grid.origin.x - 8, cy, label, { className: "matvis-text", fontSize: 10, align: "end" }));
597
+ });
598
+ return parts.join("");
599
+ }
600
+ function renderArrow(arrow, grids) {
601
+ const from = resolveEndpoint(grids, arrow.from);
602
+ const to = resolveEndpoint(grids, arrow.to);
603
+ if (arrow.style === "bracket") {
604
+ return path(`M${from.x},${from.y} L${to.x},${to.y}`, { className: "matvis-bracket" });
605
+ }
606
+ const className = arrow.color ? `matvis-arrow-${arrow.color}` : "matvis-arrow";
607
+ return line(from.x, from.y, to.x, to.y, { className, markerEnd: "matvis-arrowhead" });
608
+ }
609
+ var LEGEND_SWATCH_WIDTH = 16;
610
+ var LEGEND_LINE_HEIGHT = 18;
611
+ function legendSwatchClass(arrow) {
612
+ if (arrow.style === "bracket") return "matvis-bracket";
613
+ return arrow.color ? `matvis-arrow-${arrow.color}` : "matvis-arrow";
614
+ }
615
+ function renderLegend(arrows, originX, originY) {
616
+ const labeled = arrows.filter((arrow) => arrow.label);
617
+ const parts = [];
618
+ labeled.forEach((arrow, i) => {
619
+ const y = originY + i * LEGEND_LINE_HEIGHT;
620
+ parts.push(line(originX, y, originX + LEGEND_SWATCH_WIDTH, y, { className: legendSwatchClass(arrow) }));
621
+ parts.push(text(originX + LEGEND_SWATCH_WIDTH + 6, y + 4, arrow.label, { className: "matvis-text", fontSize: 11, align: "start" }));
622
+ });
623
+ return parts.join("");
624
+ }
625
+ function renderLabel(label) {
626
+ const fontSize = label.size === "lg" ? 16 : label.size === "sm" ? 11 : 13;
627
+ return text(label.at.x, label.at.y, label.text, { className: "matvis-text", fontSize, align: label.align ?? "start" });
628
+ }
629
+ function computeGridBounds(grids) {
630
+ let maxX = 0;
631
+ let maxY = 0;
632
+ for (const grid of grids) {
633
+ const cellSize = grid.cellSize ?? 40;
634
+ maxX = Math.max(maxX, grid.origin.x + grid.cols * cellSize);
635
+ maxY = Math.max(maxY, grid.origin.y + grid.rows * cellSize);
636
+ }
637
+ return { maxX, maxY };
638
+ }
639
+ function computeBounds(ir, legendX, legendCount) {
640
+ const { maxX: gridMaxX, maxY: gridMaxY } = computeGridBounds(ir.grids);
641
+ let maxX = gridMaxX;
642
+ let maxY = gridMaxY;
643
+ for (const arrow of ir.arrows) {
644
+ const from = resolveEndpoint(ir.grids, arrow.from);
645
+ const to = resolveEndpoint(ir.grids, arrow.to);
646
+ maxX = Math.max(maxX, from.x, to.x);
647
+ maxY = Math.max(maxY, from.y, to.y);
648
+ }
649
+ for (const label of ir.labels) {
650
+ maxX = Math.max(maxX, label.at.x + label.text.length * 7);
651
+ maxY = Math.max(maxY, label.at.y);
652
+ }
653
+ if (legendCount > 0) {
654
+ const legendLabelWidth = Math.max(
655
+ ...ir.arrows.filter((a) => a.label).map((a) => LEGEND_SWATCH_WIDTH + 6 + a.label.length * 6.5)
656
+ );
657
+ maxX = Math.max(maxX, legendX + legendLabelWidth);
658
+ maxY = Math.max(maxY, PADDING + legendCount * LEGEND_LINE_HEIGHT);
659
+ }
660
+ return { width: maxX + PADDING, height: maxY + PADDING };
661
+ }
662
+ function renderSVG(ir) {
663
+ const titleHeight = ir.title ? TITLE_HEIGHT : 0;
664
+ const { maxX: gridMaxX } = computeGridBounds(ir.grids);
665
+ const legendX = gridMaxX + GUTTER;
666
+ const labeledArrows = ir.arrows.filter((arrow) => arrow.label);
667
+ const bounds = computeBounds(ir, legendX, labeledArrows.length);
668
+ const totalWidth = bounds.width;
669
+ const totalHeight = bounds.height + titleHeight;
670
+ const body = [];
671
+ for (const grid of ir.grids) {
672
+ body.push(renderGridCells(grid));
673
+ body.push(renderGridlines(grid));
674
+ body.push(renderGridLabels(grid));
675
+ }
676
+ for (const arrow of ir.arrows) {
677
+ body.push(renderArrow(arrow, ir.grids));
678
+ }
679
+ body.push(renderLegend(ir.arrows, legendX, PADDING));
680
+ for (const label of ir.labels) {
681
+ body.push(renderLabel(label));
682
+ }
683
+ const titleSVG = ir.title ? text(PADDING, 20, ir.title, { className: "matvis-title", fontSize: 14, align: "start" }) : "";
684
+ const displayWidth = ir.width ?? totalWidth;
685
+ const displayHeight = ir.width ? ir.width / totalWidth * totalHeight : totalHeight;
686
+ return [
687
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${totalWidth} ${totalHeight}" width="${displayWidth}" height="${displayHeight}" class="matvis-diagram">`,
688
+ ARROWHEAD_DEFS,
689
+ THEME_STYLE,
690
+ titleSVG,
691
+ `<g transform="translate(0, ${titleHeight})">`,
692
+ body.join(""),
693
+ `</g>`,
694
+ `</svg>`
695
+ ].join("");
696
+ }
697
+
698
+ // src/core/render-diagram.ts
699
+ function buildIR(config) {
700
+ if (config.type === "strides") return layoutStrides(config);
701
+ if (config.type === "matmul") return layoutMatmul(config);
702
+ return layoutBroadcast(config);
703
+ }
704
+ function renderErrorSVG(message) {
705
+ const width = Math.max(240, message.length * 6.5 + 24);
706
+ const height = 60;
707
+ return [
708
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" class="matvis-diagram">`,
709
+ THEME_STYLE,
710
+ rect(4, 4, width - 8, height - 8, { className: "matvis-error-box", rx: 4 }),
711
+ text(width / 2, height / 2 + 4, `matvis: ${message}`, { className: "matvis-error-text", fontSize: 12 }),
712
+ `</svg>`
713
+ ].join("");
714
+ }
715
+ function renderDiagram(source) {
716
+ try {
717
+ const config = parseDSL(source);
718
+ const ir = buildIR(config);
719
+ return { svg: renderSVG(ir) };
720
+ } catch (err) {
721
+ const message = err instanceof MatvisParseError ? err.line !== void 0 ? `${err.message} (line ${err.line})` : err.message : err instanceof Error ? err.message : String(err);
722
+ return { svg: renderErrorSVG(message), error: message };
723
+ }
724
+ }
725
+
726
+ export {
727
+ MatvisParseError,
728
+ parseDSL,
729
+ buildIR,
730
+ renderDiagram
731
+ };
732
+ //# sourceMappingURL=chunk-KZ3XONBA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/errors.ts","../src/core/parse-dsl.ts","../src/core/dsl-schema.ts","../src/core/layout/shared.ts","../src/core/layout/broadcast.ts","../src/core/layout/matmul.ts","../src/core/layout/strides.ts","../src/core/render/primitives.ts","../src/core/render/theme.ts","../src/core/render/svg.ts","../src/core/render-diagram.ts"],"sourcesContent":["export class MatvisParseError extends Error {\n readonly line?: number;\n readonly column?: number;\n\n constructor(message: string, options?: { line?: number; column?: number }) {\n super(message);\n this.name = \"MatvisParseError\";\n this.line = options?.line;\n this.column = options?.column;\n }\n}\n","import { parse as parseYaml } from \"yaml\";\nimport { validateDiagramConfig } from \"./dsl-schema.js\";\nimport { MatvisParseError } from \"./errors.js\";\nimport type { DiagramConfig } from \"./types.js\";\n\nexport function parseDSL(source: string): DiagramConfig {\n let raw: unknown;\n try {\n raw = parseYaml(source);\n } catch (err) {\n const linePos = (err as { linePos?: Array<{ line: number; col: number }> }).linePos;\n throw new MatvisParseError(`invalid YAML: ${(err as Error).message}`, {\n line: linePos?.[0]?.line,\n column: linePos?.[0]?.col,\n });\n }\n return validateDiagramConfig(raw);\n}\n","import { MatvisParseError } from \"./errors.js\";\nimport type {\n BroadcastConfig,\n BroadcastOperand,\n ColorToken,\n DiagramConfig,\n HighlightGroup,\n MatmulConfig,\n MatmulDims,\n Order,\n StridesConfig,\n} from \"./types.js\";\n\nconst COLOR_TOKENS: ColorToken[] = [\"default\", \"accent\", \"warn\", \"info\", \"muted\"];\n\nfunction fail(message: string): never {\n throw new MatvisParseError(message);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction expectString(value: unknown, field: string): string {\n if (typeof value !== \"string\") fail(`expected \"${field}\" to be a string`);\n return value;\n}\n\nfunction expectOptionalString(value: unknown, field: string): string | undefined {\n if (value === undefined) return undefined;\n return expectString(value, field);\n}\n\nfunction expectNumber(value: unknown, field: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n fail(`expected \"${field}\" to be a number`);\n }\n return value as number;\n}\n\nfunction expectNumberArray(value: unknown, field: string): number[] {\n if (!Array.isArray(value) || value.length === 0) {\n fail(`expected \"${field}\" to be a non-empty array of numbers`);\n }\n return value.map((v, i) => expectNumber(v, `${field}[${i}]`));\n}\n\nfunction expectColorToken(value: unknown, field: string): ColorToken {\n const str = expectString(value, field);\n if (!COLOR_TOKENS.includes(str as ColorToken)) {\n fail(`\"${field}\" must be one of ${COLOR_TOKENS.join(\", \")}, got \"${str}\"`);\n }\n return str as ColorToken;\n}\n\nfunction expectCellCoord(value: unknown, field: string): [number, number] {\n if (!Array.isArray(value) || value.length !== 2) {\n fail(`expected \"${field}\" to be a [row, col] pair`);\n }\n return [expectNumber(value[0], `${field}[0]`), expectNumber(value[1], `${field}[1]`)];\n}\n\nfunction validateHighlightGroups(value: unknown, field: string): HighlightGroup[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) fail(`expected \"${field}\" to be an array`);\n return value.map((group, i) => {\n if (!isPlainObject(group)) fail(`expected \"${field}[${i}]\" to be an object`);\n const cellsRaw = group.cells;\n if (!Array.isArray(cellsRaw) || cellsRaw.length === 0) {\n fail(`expected \"${field}[${i}].cells\" to be a non-empty array of [row, col] pairs`);\n }\n const cells = cellsRaw.map((c, j) => expectCellCoord(c, `${field}[${i}].cells[${j}]`));\n const color = group.color === undefined ? undefined : expectColorToken(group.color, `${field}[${i}].color`);\n const label = expectOptionalString(group.label, `${field}[${i}].label`);\n return { cells, color, label };\n });\n}\n\nfunction validateBase(raw: Record<string, unknown>) {\n return {\n title: expectOptionalString(raw.title, \"title\"),\n dtype: expectOptionalString(raw.dtype, \"dtype\"),\n width: raw.width === undefined ? undefined : expectNumber(raw.width, \"width\"),\n };\n}\n\nexport function validateStridesConfig(raw: Record<string, unknown>): StridesConfig {\n const base = validateBase(raw);\n const shape = expectNumberArray(raw.shape, \"shape\");\n const strides = expectNumberArray(raw.strides, \"strides\");\n if (strides.length !== shape.length) {\n fail(`\"strides\" must have the same length as \"shape\" (${shape.length}), got ${strides.length}`);\n }\n const order: Order = raw.order === undefined ? \"row-major\" : (expectString(raw.order, \"order\") as Order);\n if (order !== \"row-major\" && order !== \"col-major\") {\n fail(`\"order\" must be \"row-major\" or \"col-major\", got \"${order}\"`);\n }\n return {\n type: \"strides\",\n ...base,\n shape,\n strides,\n highlight: validateHighlightGroups(raw.highlight, \"highlight\"),\n byte_offsets: raw.byte_offsets === undefined ? false : Boolean(raw.byte_offsets),\n order,\n };\n}\n\nfunction expectMatmulDims(value: unknown, field: string): MatmulDims {\n if (!isPlainObject(value)) fail(`expected \"${field}\" to have M, K, N keys`);\n return {\n M: expectNumber(value.M, `${field}.M`),\n K: expectNumber(value.K, `${field}.K`),\n N: expectNumber(value.N, `${field}.N`),\n };\n}\n\nexport function validateMatmulConfig(raw: Record<string, unknown>): MatmulConfig {\n const base = validateBase(raw);\n const dims = expectMatmulDims(raw.dims, \"dims\");\n const tile = expectMatmulDims(raw.tile, \"tile\");\n for (const axis of [\"M\", \"K\", \"N\"] as const) {\n if (dims[axis] % tile[axis] !== 0) {\n fail(`\"tile.${axis}\" (${tile[axis]}) must evenly divide \"dims.${axis}\" (${dims[axis]})`);\n }\n }\n let highlightTile: [number, number] | undefined;\n if (raw.highlight_tile !== undefined) {\n highlightTile = expectCellCoord(raw.highlight_tile, \"highlight_tile\");\n }\n let labels: MatmulConfig[\"labels\"];\n if (raw.labels !== undefined) {\n if (!isPlainObject(raw.labels)) fail(`expected \"labels\" to be an object`);\n labels = {\n A: expectOptionalString(raw.labels.A, \"labels.A\"),\n B: expectOptionalString(raw.labels.B, \"labels.B\"),\n C: expectOptionalString(raw.labels.C, \"labels.C\"),\n };\n }\n return {\n type: \"matmul\",\n ...base,\n dims,\n tile,\n highlight_tile: highlightTile,\n labels,\n };\n}\n\nfunction validateOperand(value: unknown, field: string): BroadcastOperand {\n if (!isPlainObject(value)) fail(`expected \"${field}\" to be an object`);\n return {\n shape: expectNumberArray(value.shape, `${field}.shape`),\n label: expectOptionalString(value.label, `${field}.label`),\n };\n}\n\nexport function validateBroadcastConfig(raw: Record<string, unknown>): BroadcastConfig {\n const base = validateBase(raw);\n if (!Array.isArray(raw.operands) || raw.operands.length < 2) {\n fail(`expected \"operands\" to be an array of at least 2 shapes`);\n }\n const operands = raw.operands.map((op, i) => validateOperand(op, `operands[${i}]`));\n return {\n type: \"broadcast\",\n ...base,\n operands,\n result_label: expectOptionalString(raw.result_label, \"result_label\"),\n };\n}\n\nexport function validateDiagramConfig(raw: unknown): DiagramConfig {\n if (!isPlainObject(raw)) fail(\"diagram source must be a YAML mapping\");\n const type = raw.type;\n if (type === \"strides\") return validateStridesConfig(raw);\n if (type === \"matmul\") return validateMatmulConfig(raw);\n if (type === \"broadcast\") return validateBroadcastConfig(raw);\n fail(`unknown or missing \"type\" (expected \"strides\", \"matmul\", or \"broadcast\"), got ${JSON.stringify(type)}`);\n}\n","import type { ArrowEndpoint, ColorToken, Grid, Point } from \"../types.js\";\n\nexport const BASE_CELL_SIZE = 40;\nexport const MIN_CELL_SIZE = 14;\nexport const MAX_GRID_SPAN = 480;\nexport const GUTTER = 40;\nexport const PADDING = 24;\nexport const LABEL_MARGIN = 20;\n\nexport function clampCellSize(cols: number, rows: number): number {\n const maxDim = Math.max(cols, rows);\n const bounded = MAX_GRID_SPAN / maxDim;\n return Math.max(MIN_CELL_SIZE, Math.min(BASE_CELL_SIZE, bounded));\n}\n\nconst HIGHLIGHT_PALETTE: ColorToken[] = [\"accent\", \"warn\", \"info\", \"muted\"];\n\nexport function paletteColor(index: number): ColorToken {\n return HIGHLIGHT_PALETTE[index % HIGHLIGHT_PALETTE.length];\n}\n\nexport function gridPixelSize(grid: Grid): { width: number; height: number } {\n const cellSize = grid.cellSize ?? BASE_CELL_SIZE;\n return { width: grid.cols * cellSize, height: grid.rows * cellSize };\n}\n\nexport function cellOrigin(grid: Grid, row: number, col: number): Point {\n const cellSize = grid.cellSize ?? BASE_CELL_SIZE;\n return { x: grid.origin.x + col * cellSize, y: grid.origin.y + row * cellSize };\n}\n\nexport function cellCenter(grid: Grid, row: number, col: number): Point {\n const cellSize = grid.cellSize ?? BASE_CELL_SIZE;\n const o = cellOrigin(grid, row, col);\n return { x: o.x + cellSize / 2, y: o.y + cellSize / 2 };\n}\n\nexport function resolveEndpoint(grids: Grid[], endpoint: ArrowEndpoint): Point {\n if (\"x\" in endpoint && \"y\" in endpoint) return endpoint;\n const grid = grids.find((g) => g.id === endpoint.gridId);\n if (!grid) throw new Error(`arrow references unknown grid \"${endpoint.gridId}\"`);\n return cellCenter(grid, endpoint.row, endpoint.col);\n}\n\nexport function estimateTextWidth(text: string, fontSize: number): number {\n return text.length * fontSize * 0.62;\n}\n\nexport function fitsInCell(text: string, cellSize: number, fontSize: number): boolean {\n return estimateTextWidth(text, fontSize) < cellSize - 4;\n}\n\nconst DTYPE_BYTE_WIDTHS: Record<string, number> = {\n float64: 8,\n int64: 8,\n uint64: 8,\n float32: 4,\n int32: 4,\n uint32: 4,\n float16: 2,\n bfloat16: 2,\n int16: 2,\n uint16: 2,\n int8: 1,\n uint8: 1,\n bool: 1,\n};\n\nexport function dtypeByteWidth(dtype: string | undefined): number {\n if (!dtype) return 4;\n return DTYPE_BYTE_WIDTHS[dtype] ?? 4;\n}\n","import { MatvisParseError } from \"../errors.js\";\nimport type { Arrow, BroadcastConfig, Cell, DiagramIR, Grid, TextLabel } from \"../types.js\";\nimport { clampCellSize, GUTTER, PADDING } from \"./shared.js\";\n\nfunction to2D(shape: number[]): [number, number] {\n if (shape.length === 1) return [1, shape[0]];\n if (shape.length === 2) return [shape[0], shape[1]];\n throw new MatvisParseError(`broadcast operands support at most 2 dimensions, got shape with ${shape.length} dimensions`);\n}\n\nfunction broadcastShape(shapes2D: Array<[number, number]>): [number, number] {\n const rows = broadcastAxis(shapes2D.map(([r]) => r));\n const cols = broadcastAxis(shapes2D.map(([, c]) => c));\n return [rows, cols];\n}\n\nfunction broadcastAxis(sizes: number[]): number {\n let result = 1;\n for (const size of sizes) {\n if (size === result || size === 1) {\n result = Math.max(result, size);\n } else if (result === 1) {\n result = size;\n } else {\n throw new MatvisParseError(`shapes are not broadcastable: incompatible sizes ${sizes.join(\", \")}`);\n }\n }\n return result;\n}\n\nfunction makeCells(rows: number, cols: number, stretchedRows: boolean, stretchedCols: boolean): Cell[] {\n const cells: Cell[] = [];\n for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n cells.push({ row: r, col: c, opacity: stretchedRows || stretchedCols ? 0.5 : undefined });\n }\n }\n return cells;\n}\n\nexport function layoutBroadcast(config: BroadcastConfig): DiagramIR {\n const { operands, result_label, title } = config;\n const shapes2D = operands.map((op) => to2D(op.shape));\n const [resultRows, resultCols] = broadcastShape(shapes2D);\n\n const maxDim = Math.max(resultRows, resultCols, ...shapes2D.flat());\n const cellSize = clampCellSize(maxDim, maxDim);\n const maxRows = Math.max(resultRows, ...shapes2D.map(([r]) => r));\n\n const grids: Grid[] = [];\n const arrows: Arrow[] = [];\n const labels: TextLabel[] = [];\n\n let currentX = PADDING;\n let previousGridId: string | undefined;\n\n shapes2D.forEach(([rows, cols], i) => {\n const id = `operand-${i}`;\n const origin = { x: currentX, y: PADDING + ((maxRows - rows) / 2) * cellSize };\n grids.push({\n id,\n origin,\n rows,\n cols,\n cellSize,\n cells: makeCells(rows, cols, rows === 1 && resultRows > 1, cols === 1 && resultCols > 1),\n });\n labels.push({ at: { x: origin.x, y: origin.y - 8 }, text: operands[i].label ?? id, size: \"sm\" });\n\n if (previousGridId) {\n labels.push({\n at: { x: currentX - GUTTER / 2, y: PADDING + (maxRows * cellSize) / 2 },\n text: \"⊕\",\n size: \"lg\",\n align: \"middle\",\n });\n }\n previousGridId = id;\n currentX += cols * cellSize + GUTTER;\n });\n\n const resultOrigin = { x: currentX, y: PADDING + ((maxRows - resultRows) / 2) * cellSize };\n const resultId = \"result\";\n grids.push({\n id: resultId,\n origin: resultOrigin,\n rows: resultRows,\n cols: resultCols,\n cellSize,\n cells: makeCells(resultRows, resultCols, false, false),\n });\n labels.push({ at: { x: resultOrigin.x, y: resultOrigin.y - 8 }, text: \"result\", size: \"sm\" });\n\n if (previousGridId) {\n arrows.push({\n from: { gridId: previousGridId, row: Math.floor(shapes2D[shapes2D.length - 1][0] / 2), col: shapes2D[shapes2D.length - 1][1] - 1 },\n to: { gridId: resultId, row: Math.floor(resultRows / 2), col: 0 },\n style: \"arrow\",\n });\n }\n\n labels.push({\n at: { x: PADDING, y: PADDING + maxRows * cellSize + 20 },\n text: result_label ?? `-> (${resultRows}, ${resultCols})`,\n size: \"sm\",\n });\n\n return { title, grids, arrows, labels };\n}\n","import { MatvisParseError } from \"../errors.js\";\nimport type { Arrow, Cell, DiagramIR, Grid, MatmulConfig, TextLabel } from \"../types.js\";\nimport { clampCellSize, GUTTER, PADDING } from \"./shared.js\";\n\nfunction stripCells(rows: number, cols: number, highlightRows?: [number, number], highlightCols?: [number, number]): Cell[] {\n const cells: Cell[] = [];\n for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n const inRowRange = highlightRows ? r >= highlightRows[0] && r <= highlightRows[1] : false;\n const inColRange = highlightCols ? c >= highlightCols[0] && c <= highlightCols[1] : false;\n const highlighted = highlightRows && highlightCols ? inRowRange && inColRange : inRowRange || inColRange;\n cells.push({ row: r, col: c, color: highlighted ? \"info\" : undefined });\n }\n }\n return cells;\n}\n\nexport function layoutMatmul(config: MatmulConfig): DiagramIR {\n const { dims, tile, highlight_tile, labels, title } = config;\n const maxDim = Math.max(dims.M, dims.K, dims.N);\n const cellSize = clampCellSize(maxDim, maxDim);\n\n let tileRowIdx = 0;\n let tileColIdx = 0;\n if (highlight_tile) {\n [tileRowIdx, tileColIdx] = highlight_tile;\n const tileRowCount = dims.M / tile.M;\n const tileColCount = dims.N / tile.N;\n if (tileRowIdx < 0 || tileRowIdx >= tileRowCount) {\n throw new MatvisParseError(`\"highlight_tile[0]\" (${tileRowIdx}) out of range [0, ${tileRowCount - 1}]`);\n }\n if (tileColIdx < 0 || tileColIdx >= tileColCount) {\n throw new MatvisParseError(`\"highlight_tile[1]\" (${tileColIdx}) out of range [0, ${tileColCount - 1}]`);\n }\n }\n\n const rowRange: [number, number] = [tileRowIdx * tile.M, tileRowIdx * tile.M + tile.M - 1];\n const colRange: [number, number] = [tileColIdx * tile.N, tileColIdx * tile.N + tile.N - 1];\n const rowMid = Math.round((rowRange[0] + rowRange[1]) / 2);\n const colMid = Math.round((colRange[0] + colRange[1]) / 2);\n\n const cOrigin = {\n x: PADDING + dims.K * cellSize + GUTTER,\n y: PADDING + dims.K * cellSize + GUTTER,\n };\n const aOrigin = { x: PADDING, y: cOrigin.y };\n const bOrigin = { x: cOrigin.x, y: PADDING };\n\n const gridA: Grid = {\n id: \"A\",\n origin: aOrigin,\n rows: dims.M,\n cols: dims.K,\n cellSize,\n cells: stripCells(dims.M, dims.K, rowRange, undefined),\n gridlineEvery: { rows: tile.M, cols: tile.K },\n };\n\n const gridB: Grid = {\n id: \"B\",\n origin: bOrigin,\n rows: dims.K,\n cols: dims.N,\n cellSize,\n cells: stripCells(dims.K, dims.N, undefined, colRange),\n gridlineEvery: { rows: tile.K, cols: tile.N },\n };\n\n const gridC: Grid = {\n id: \"C\",\n origin: cOrigin,\n rows: dims.M,\n cols: dims.N,\n cellSize,\n cells: stripCells(dims.M, dims.N, rowRange, colRange).map((cell) => ({\n ...cell,\n color: cell.color ? \"accent\" : undefined,\n })),\n gridlineEvery: { rows: tile.M, cols: tile.N },\n };\n\n const arrows: Arrow[] = [\n {\n from: { gridId: \"A\", row: rowMid, col: dims.K - 1 },\n to: { gridId: \"C\", row: rowMid, col: colRange[0] },\n style: \"arrow\",\n color: \"info\",\n },\n {\n from: { gridId: \"B\", row: dims.K - 1, col: colMid },\n to: { gridId: \"C\", row: rowRange[0], col: colMid },\n style: \"arrow\",\n color: \"info\",\n },\n ];\n\n const textLabels: TextLabel[] = [\n { at: { x: gridA.origin.x, y: gridA.origin.y - 8 }, text: labels?.A ?? \"A\", size: \"sm\" },\n { at: { x: gridB.origin.x, y: gridB.origin.y - 8 }, text: labels?.B ?? \"B\", size: \"sm\" },\n { at: { x: gridC.origin.x, y: gridC.origin.y - 8 }, text: labels?.C ?? \"C\", size: \"sm\" },\n {\n at: { x: gridA.origin.x, y: gridC.origin.y + dims.M * cellSize + 20 },\n text: `M=${dims.M} K=${dims.K} N=${dims.N} tile=[${tile.M}, ${tile.K}, ${tile.N}]`,\n size: \"sm\",\n },\n ];\n\n return { title, grids: [gridA, gridB, gridC], arrows, labels: textLabels };\n}\n","import { MatvisParseError } from \"../errors.js\";\nimport type { Arrow, Cell, ColorToken, DiagramIR, Grid, StridesConfig, TextLabel } from \"../types.js\";\nimport { clampCellSize, dtypeByteWidth, LABEL_MARGIN, paletteColor, PADDING } from \"./shared.js\";\n\nexport function layoutStrides(config: StridesConfig): DiagramIR {\n const { shape, strides, highlight = [], byte_offsets = false, order = \"row-major\", dtype, title } = config;\n\n if (shape.length > 2) {\n throw new MatvisParseError(\n `\"strides\" diagrams support at most 2 dimensions, got shape with ${shape.length} dimensions`\n );\n }\n\n const rows = shape.length === 1 ? 1 : shape[0];\n const cols = shape.length === 1 ? shape[0] : shape[1];\n const rowStride = shape.length === 1 ? 0 : strides[0];\n const colStride = shape.length === 1 ? strides[0] : strides[1];\n const byteWidth = dtypeByteWidth(dtype);\n\n const colorMap = new Map<string, ColorToken>();\n highlight.forEach((group, i) => {\n const color = group.color ?? paletteColor(i);\n group.cells.forEach(([r, c]) => colorMap.set(`${r},${c}`, color));\n });\n\n const cells: Cell[] = [];\n for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n const offsetElems = r * rowStride + c * colStride;\n cells.push({\n row: r,\n col: c,\n text: byte_offsets ? String(offsetElems * byteWidth) : undefined,\n color: colorMap.get(`${r},${c}`),\n });\n }\n }\n\n const showRowLabels = rows > 1 && rows <= 12;\n const showColLabels = cols <= 12;\n const cellSize = clampCellSize(cols, rows);\n const origin = {\n x: PADDING + (showRowLabels ? LABEL_MARGIN : 0),\n y: PADDING + (showColLabels ? LABEL_MARGIN : 0),\n };\n\n const grid: Grid = {\n id: \"matrix\",\n origin,\n rows,\n cols,\n cellSize,\n cells,\n rowLabels: showRowLabels ? Array.from({ length: rows }, (_, i) => String(i)) : undefined,\n colLabels: showColLabels ? Array.from({ length: cols }, (_, i) => String(i)) : undefined,\n };\n\n const arrows: Arrow[] = [];\n highlight.forEach((group, i) => {\n if (!group.label) return;\n const color = group.color ?? paletteColor(i);\n const first = group.cells[0];\n const last = group.cells[group.cells.length - 1];\n arrows.push({\n from: { gridId: grid.id, row: first[0], col: first[1] },\n to: { gridId: grid.id, row: last[0], col: last[1] },\n label: group.label,\n style: \"bracket\",\n color,\n });\n });\n\n const captionParts = [`shape=[${shape.join(\", \")}]`, `strides=[${strides.join(\", \")}]`, `(${order})`];\n const labels: TextLabel[] = [\n {\n at: { x: origin.x, y: origin.y + rows * cellSize + 20 },\n text: captionParts.join(\" \"),\n size: \"sm\",\n align: \"start\",\n },\n ];\n\n return { title, grids: [grid], arrows, labels };\n}\n","export function escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n\nexport interface RectOptions {\n className?: string;\n rx?: number;\n}\n\nexport function rect(x: number, y: number, width: number, height: number, opts: RectOptions = {}): string {\n const rx = opts.rx ?? 0;\n const className = opts.className ?? \"\";\n return `<rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" rx=\"${rx}\" class=\"${className}\" />`;\n}\n\nexport interface TextOptions {\n className?: string;\n align?: \"start\" | \"middle\" | \"end\";\n fontSize?: number;\n}\n\nexport function text(x: number, y: number, content: string, opts: TextOptions = {}): string {\n const anchor = opts.align ?? \"middle\";\n const fontSize = opts.fontSize ?? 12;\n const className = opts.className ?? \"\";\n return `<text x=\"${x}\" y=\"${y}\" text-anchor=\"${anchor}\" font-size=\"${fontSize}\" class=\"${className}\">${escapeXml(\n content\n )}</text>`;\n}\n\nexport interface LineOptions {\n className?: string;\n markerEnd?: string;\n}\n\nexport function line(x1: number, y1: number, x2: number, y2: number, opts: LineOptions = {}): string {\n const className = opts.className ?? \"\";\n const marker = opts.markerEnd ? ` marker-end=\"url(#${opts.markerEnd})\"` : \"\";\n return `<line x1=\"${x1}\" y1=\"${y1}\" x2=\"${x2}\" y2=\"${y2}\" class=\"${className}\"${marker} />`;\n}\n\nexport interface PathOptions {\n className?: string;\n}\n\nexport function path(d: string, opts: PathOptions = {}): string {\n const className = opts.className ?? \"\";\n return `<path d=\"${d}\" class=\"${className}\" fill=\"none\" />`;\n}\n","export const ARROWHEAD_DEFS = `<defs>\n <marker id=\"matvis-arrowhead\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n <path d=\"M0,0 L10,5 L0,10 z\" class=\"matvis-arrow-head\" />\n </marker>\n</defs>`;\n\nexport const THEME_STYLE = `<style>\n .matvis-diagram { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n .matvis-cell { fill: var(--matvis-bg, #ffffff); stroke: var(--matvis-border, currentColor); stroke-width: 1; opacity: 0.9; }\n .matvis-cell-accent { fill: var(--matvis-accent, #6366f1); opacity: 0.35; }\n .matvis-cell-warn { fill: var(--matvis-warn, #f59e0b); opacity: 0.35; }\n .matvis-cell-info { fill: var(--matvis-info, #0ea5e9); opacity: 0.35; }\n .matvis-cell-muted { fill: var(--matvis-muted, #9ca3af); opacity: 0.35; }\n .matvis-text { fill: var(--matvis-fg, currentColor); }\n .matvis-title { fill: var(--matvis-fg, currentColor); font-weight: 600; }\n .matvis-grid-line-heavy { stroke: var(--matvis-fg, currentColor); stroke-width: 2; }\n .matvis-arrow { stroke: var(--matvis-fg, currentColor); stroke-width: 1.5; }\n .matvis-arrow-accent { stroke: var(--matvis-accent, #6366f1); stroke-width: 1.5; }\n .matvis-arrow-warn { stroke: var(--matvis-warn, #f59e0b); stroke-width: 1.5; }\n .matvis-arrow-info { stroke: var(--matvis-info, #0ea5e9); stroke-width: 1.5; }\n .matvis-arrow-muted { stroke: var(--matvis-muted, #9ca3af); stroke-width: 1.5; }\n .matvis-arrow-head { fill: var(--matvis-fg, currentColor); }\n .matvis-bracket { stroke: var(--matvis-muted, #9ca3af); stroke-width: 1; stroke-dasharray: 3 3; }\n .matvis-error-text { fill: var(--matvis-warn, #dc2626); }\n .matvis-error-box { fill: none; stroke: var(--matvis-warn, #dc2626); stroke-width: 1; stroke-dasharray: 4 2; }\n</style>`;\n","import { cellOrigin, GUTTER, PADDING, resolveEndpoint } from \"../layout/shared.js\";\nimport type { Arrow, DiagramIR, Grid, TextLabel } from \"../types.js\";\nimport { line, path, rect, text } from \"./primitives.js\";\nimport { ARROWHEAD_DEFS, THEME_STYLE } from \"./theme.js\";\n\nconst TITLE_HEIGHT = 32;\n\nfunction renderGridCells(grid: Grid): string {\n const cellSize = grid.cellSize ?? 40;\n const parts: string[] = [];\n for (const cell of grid.cells) {\n const { x, y } = cellOrigin(grid, cell.row, cell.col);\n const className = cell.color ? `matvis-cell matvis-cell-${cell.color}` : \"matvis-cell\";\n parts.push(rect(x, y, cellSize, cellSize, { className }));\n if (cell.text) {\n const fontSize = Math.min(12, cellSize * 0.32);\n parts.push(text(x + cellSize / 2, y + cellSize / 2 + fontSize / 3, cell.text, { className: \"matvis-text\", fontSize }));\n }\n }\n return parts.join(\"\");\n}\n\nfunction renderGridlines(grid: Grid): string {\n if (!grid.gridlineEvery) return \"\";\n const cellSize = grid.cellSize ?? 40;\n const parts: string[] = [];\n const { rows: everyRows, cols: everyCols } = grid.gridlineEvery;\n if (everyCols) {\n for (let c = everyCols; c < grid.cols; c += everyCols) {\n const x = grid.origin.x + c * cellSize;\n parts.push(line(x, grid.origin.y, x, grid.origin.y + grid.rows * cellSize, { className: \"matvis-grid-line-heavy\" }));\n }\n }\n if (everyRows) {\n for (let r = everyRows; r < grid.rows; r += everyRows) {\n const y = grid.origin.y + r * cellSize;\n parts.push(line(grid.origin.x, y, grid.origin.x + grid.cols * cellSize, y, { className: \"matvis-grid-line-heavy\" }));\n }\n }\n return parts.join(\"\");\n}\n\nfunction renderGridLabels(grid: Grid): string {\n const cellSize = grid.cellSize ?? 40;\n const parts: string[] = [];\n grid.colLabels?.forEach((label, c) => {\n const cx = grid.origin.x + c * cellSize + cellSize / 2;\n parts.push(text(cx, grid.origin.y - 6, label, { className: \"matvis-text\", fontSize: 10 }));\n });\n grid.rowLabels?.forEach((label, r) => {\n const cy = grid.origin.y + r * cellSize + cellSize / 2 + 4;\n parts.push(text(grid.origin.x - 8, cy, label, { className: \"matvis-text\", fontSize: 10, align: \"end\" }));\n });\n return parts.join(\"\");\n}\n\nfunction renderArrow(arrow: Arrow, grids: Grid[]): string {\n const from = resolveEndpoint(grids, arrow.from);\n const to = resolveEndpoint(grids, arrow.to);\n if (arrow.style === \"bracket\") {\n return path(`M${from.x},${from.y} L${to.x},${to.y}`, { className: \"matvis-bracket\" });\n }\n const className = arrow.color ? `matvis-arrow-${arrow.color}` : \"matvis-arrow\";\n return line(from.x, from.y, to.x, to.y, { className, markerEnd: \"matvis-arrowhead\" });\n}\n\nconst LEGEND_SWATCH_WIDTH = 16;\nconst LEGEND_LINE_HEIGHT = 18;\n\nfunction legendSwatchClass(arrow: Arrow): string {\n if (arrow.style === \"bracket\") return \"matvis-bracket\";\n return arrow.color ? `matvis-arrow-${arrow.color}` : \"matvis-arrow\";\n}\n\n/** Labels live in a legend column to the right of the grids, never on top of the diagram. */\nfunction renderLegend(arrows: Arrow[], originX: number, originY: number): string {\n const labeled = arrows.filter((arrow) => arrow.label);\n const parts: string[] = [];\n labeled.forEach((arrow, i) => {\n const y = originY + i * LEGEND_LINE_HEIGHT;\n parts.push(line(originX, y, originX + LEGEND_SWATCH_WIDTH, y, { className: legendSwatchClass(arrow) }));\n parts.push(text(originX + LEGEND_SWATCH_WIDTH + 6, y + 4, arrow.label!, { className: \"matvis-text\", fontSize: 11, align: \"start\" }));\n });\n return parts.join(\"\");\n}\n\nfunction renderLabel(label: TextLabel): string {\n const fontSize = label.size === \"lg\" ? 16 : label.size === \"sm\" ? 11 : 13;\n return text(label.at.x, label.at.y, label.text, { className: \"matvis-text\", fontSize, align: label.align ?? \"start\" });\n}\n\nfunction computeGridBounds(grids: Grid[]): { maxX: number; maxY: number } {\n let maxX = 0;\n let maxY = 0;\n for (const grid of grids) {\n const cellSize = grid.cellSize ?? 40;\n maxX = Math.max(maxX, grid.origin.x + grid.cols * cellSize);\n maxY = Math.max(maxY, grid.origin.y + grid.rows * cellSize);\n }\n return { maxX, maxY };\n}\n\nfunction computeBounds(ir: DiagramIR, legendX: number, legendCount: number): { width: number; height: number } {\n const { maxX: gridMaxX, maxY: gridMaxY } = computeGridBounds(ir.grids);\n let maxX = gridMaxX;\n let maxY = gridMaxY;\n for (const arrow of ir.arrows) {\n const from = resolveEndpoint(ir.grids, arrow.from);\n const to = resolveEndpoint(ir.grids, arrow.to);\n maxX = Math.max(maxX, from.x, to.x);\n maxY = Math.max(maxY, from.y, to.y);\n }\n for (const label of ir.labels) {\n maxX = Math.max(maxX, label.at.x + label.text.length * 7);\n maxY = Math.max(maxY, label.at.y);\n }\n if (legendCount > 0) {\n const legendLabelWidth = Math.max(\n ...ir.arrows.filter((a) => a.label).map((a) => LEGEND_SWATCH_WIDTH + 6 + a.label!.length * 6.5)\n );\n maxX = Math.max(maxX, legendX + legendLabelWidth);\n maxY = Math.max(maxY, PADDING + legendCount * LEGEND_LINE_HEIGHT);\n }\n return { width: maxX + PADDING, height: maxY + PADDING };\n}\n\nexport function renderSVG(ir: DiagramIR): string {\n const titleHeight = ir.title ? TITLE_HEIGHT : 0;\n const { maxX: gridMaxX } = computeGridBounds(ir.grids);\n const legendX = gridMaxX + GUTTER;\n const labeledArrows = ir.arrows.filter((arrow) => arrow.label);\n const bounds = computeBounds(ir, legendX, labeledArrows.length);\n const totalWidth = bounds.width;\n const totalHeight = bounds.height + titleHeight;\n\n const body: string[] = [];\n for (const grid of ir.grids) {\n body.push(renderGridCells(grid));\n body.push(renderGridlines(grid));\n body.push(renderGridLabels(grid));\n }\n for (const arrow of ir.arrows) {\n body.push(renderArrow(arrow, ir.grids));\n }\n body.push(renderLegend(ir.arrows, legendX, PADDING));\n for (const label of ir.labels) {\n body.push(renderLabel(label));\n }\n\n const titleSVG = ir.title ? text(PADDING, 20, ir.title, { className: \"matvis-title\", fontSize: 14, align: \"start\" }) : \"\";\n\n const displayWidth = ir.width ?? totalWidth;\n const displayHeight = ir.width ? (ir.width / totalWidth) * totalHeight : totalHeight;\n\n return [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${totalWidth} ${totalHeight}\" width=\"${displayWidth}\" height=\"${displayHeight}\" class=\"matvis-diagram\">`,\n ARROWHEAD_DEFS,\n THEME_STYLE,\n titleSVG,\n `<g transform=\"translate(0, ${titleHeight})\">`,\n body.join(\"\"),\n `</g>`,\n `</svg>`,\n ].join(\"\");\n}\n","import { MatvisParseError } from \"./errors.js\";\nimport { layoutBroadcast } from \"./layout/broadcast.js\";\nimport { layoutMatmul } from \"./layout/matmul.js\";\nimport { layoutStrides } from \"./layout/strides.js\";\nimport { parseDSL } from \"./parse-dsl.js\";\nimport { rect, text } from \"./render/primitives.js\";\nimport { renderSVG } from \"./render/svg.js\";\nimport { THEME_STYLE } from \"./render/theme.js\";\nimport type { DiagramConfig, DiagramIR } from \"./types.js\";\n\nexport function buildIR(config: DiagramConfig): DiagramIR {\n if (config.type === \"strides\") return layoutStrides(config);\n if (config.type === \"matmul\") return layoutMatmul(config);\n return layoutBroadcast(config);\n}\n\nfunction renderErrorSVG(message: string): string {\n const width = Math.max(240, message.length * 6.5 + 24);\n const height = 60;\n return [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${width} ${height}\" width=\"${width}\" height=\"${height}\" class=\"matvis-diagram\">`,\n THEME_STYLE,\n rect(4, 4, width - 8, height - 8, { className: \"matvis-error-box\", rx: 4 }),\n text(width / 2, height / 2 + 4, `matvis: ${message}`, { className: \"matvis-error-text\", fontSize: 12 }),\n `</svg>`,\n ].join(\"\");\n}\n\nexport interface RenderResult {\n svg: string;\n error?: string;\n}\n\nexport function renderDiagram(source: string): RenderResult {\n try {\n const config = parseDSL(source);\n const ir = buildIR(config);\n return { svg: renderSVG(ir) };\n } catch (err) {\n const message =\n err instanceof MatvisParseError\n ? err.line !== undefined\n ? `${err.message} (line ${err.line})`\n : err.message\n : err instanceof Error\n ? err.message\n : String(err);\n return { svg: renderErrorSVG(message), error: message };\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,124 @@
1
+ type ColorToken = "default" | "accent" | "warn" | "info" | "muted";
2
+ interface Point {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /** A single grid cell, in grid-coordinate space (row, col), not pixels. */
7
+ interface Cell {
8
+ row: number;
9
+ col: number;
10
+ text?: string;
11
+ color?: ColorToken;
12
+ border?: ColorToken;
13
+ opacity?: number;
14
+ }
15
+ type ArrowEndpoint = Point | {
16
+ gridId: string;
17
+ row: number;
18
+ col: number;
19
+ };
20
+ /** One rectangular grid of cells, positioned by an anchor point + explicit rows/cols. */
21
+ interface Grid {
22
+ id: string;
23
+ origin: Point;
24
+ rows: number;
25
+ cols: number;
26
+ cellSize?: number;
27
+ cells: Cell[];
28
+ rowLabels?: string[];
29
+ colLabels?: string[];
30
+ gridlineEvery?: {
31
+ rows?: number;
32
+ cols?: number;
33
+ };
34
+ }
35
+ /** Arrow/bracket annotation connecting two points or two grid regions. */
36
+ interface Arrow {
37
+ from: ArrowEndpoint;
38
+ to: ArrowEndpoint;
39
+ label?: string;
40
+ style?: "arrow" | "bracket";
41
+ color?: ColorToken;
42
+ }
43
+ interface TextLabel {
44
+ at: Point;
45
+ text: string;
46
+ size?: "sm" | "md" | "lg";
47
+ align?: "start" | "middle" | "end";
48
+ }
49
+ /** The single normalized shape every diagram type reduces to before rendering. */
50
+ interface DiagramIR {
51
+ title?: string;
52
+ grids: Grid[];
53
+ arrows: Arrow[];
54
+ labels: TextLabel[];
55
+ width?: number;
56
+ height?: number;
57
+ }
58
+ type DiagramType = "strides" | "matmul" | "broadcast";
59
+ interface BaseDiagramConfig {
60
+ type: DiagramType;
61
+ title?: string;
62
+ dtype?: string;
63
+ width?: number;
64
+ }
65
+ type Order = "row-major" | "col-major";
66
+ interface HighlightGroup {
67
+ cells: Array<[number, number]>;
68
+ color?: ColorToken;
69
+ label?: string;
70
+ }
71
+ interface StridesConfig extends BaseDiagramConfig {
72
+ type: "strides";
73
+ shape: number[];
74
+ strides: number[];
75
+ highlight?: HighlightGroup[];
76
+ byte_offsets?: boolean;
77
+ order?: Order;
78
+ }
79
+ interface MatmulDims {
80
+ M: number;
81
+ K: number;
82
+ N: number;
83
+ }
84
+ interface MatmulConfig extends BaseDiagramConfig {
85
+ type: "matmul";
86
+ dims: MatmulDims;
87
+ tile: MatmulDims;
88
+ highlight_tile?: [number, number];
89
+ labels?: {
90
+ A?: string;
91
+ B?: string;
92
+ C?: string;
93
+ };
94
+ }
95
+ interface BroadcastOperand {
96
+ shape: number[];
97
+ label?: string;
98
+ }
99
+ interface BroadcastConfig extends BaseDiagramConfig {
100
+ type: "broadcast";
101
+ operands: BroadcastOperand[];
102
+ result_label?: string;
103
+ }
104
+ type DiagramConfig = StridesConfig | MatmulConfig | BroadcastConfig;
105
+
106
+ declare function buildIR(config: DiagramConfig): DiagramIR;
107
+ interface RenderResult {
108
+ svg: string;
109
+ error?: string;
110
+ }
111
+ declare function renderDiagram(source: string): RenderResult;
112
+
113
+ declare function parseDSL(source: string): DiagramConfig;
114
+
115
+ declare class MatvisParseError extends Error {
116
+ readonly line?: number;
117
+ readonly column?: number;
118
+ constructor(message: string, options?: {
119
+ line?: number;
120
+ column?: number;
121
+ });
122
+ }
123
+
124
+ export { type BroadcastConfig, type BroadcastOperand, type ColorToken, type DiagramConfig, type DiagramIR, type DiagramType, type HighlightGroup, type MatmulConfig, type MatmulDims, MatvisParseError, type RenderResult, type StridesConfig, buildIR, parseDSL, renderDiagram };
@@ -0,0 +1,13 @@
1
+ import {
2
+ MatvisParseError,
3
+ buildIR,
4
+ parseDSL,
5
+ renderDiagram
6
+ } from "../chunk-KZ3XONBA.js";
7
+ export {
8
+ MatvisParseError,
9
+ buildIR,
10
+ parseDSL,
11
+ renderDiagram
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,5 @@
1
+ import { Root } from 'hast';
2
+
3
+ declare function rehypeMatvis(): (tree: Root) => void;
4
+
5
+ export { rehypeMatvis as default };
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ import {
2
+ renderDiagram
3
+ } from "./chunk-KZ3XONBA.js";
4
+
5
+ // src/plugin/rehype-matvis.ts
6
+ import { visit } from "unist-util-visit";
7
+
8
+ // src/plugin/hast-splice.ts
9
+ import { fromHtmlIsomorphic } from "hast-util-from-html-isomorphic";
10
+ function svgStringToHast(svg) {
11
+ const root = fromHtmlIsomorphic(svg, { fragment: true });
12
+ const svgElement = root.children.find((node) => node.type === "element");
13
+ if (!svgElement) {
14
+ throw new Error("matvis: failed to parse generated SVG into a hast element");
15
+ }
16
+ return svgElement;
17
+ }
18
+
19
+ // src/plugin/rehype-matvis.ts
20
+ function isElement(node) {
21
+ return typeof node === "object" && node !== null && node.type === "element";
22
+ }
23
+ function rehypeMatvis() {
24
+ return (tree) => {
25
+ visit(tree, "element", (node, index, parent) => {
26
+ if (node.tagName !== "pre" || !parent || index == null) return;
27
+ const codeNode = node.children.find((child) => isElement(child) && child.tagName === "code");
28
+ if (!codeNode) return;
29
+ const className = codeNode.properties?.className ?? [];
30
+ if (!className.includes("language-matvis")) return;
31
+ const source = codeNode.children.filter((child) => child.type === "text").map((child) => child.value).join("");
32
+ const { svg } = renderDiagram(source);
33
+ const svgHast = svgStringToHast(svg);
34
+ parent.children.splice(index, 1, svgHast);
35
+ return index;
36
+ });
37
+ };
38
+ }
39
+ export {
40
+ rehypeMatvis as default
41
+ };
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/rehype-matvis.ts","../src/plugin/hast-splice.ts"],"sourcesContent":["import type { Element, Root } from \"hast\";\nimport { visit } from \"unist-util-visit\";\nimport { renderDiagram } from \"../core/render-diagram.js\";\nimport { svgStringToHast } from \"./hast-splice.js\";\n\nfunction isElement(node: unknown): node is Element {\n return typeof node === \"object\" && node !== null && (node as { type?: string }).type === \"element\";\n}\n\nexport default function rehypeMatvis() {\n return (tree: Root) => {\n visit(tree, \"element\", (node: Element, index, parent) => {\n if (node.tagName !== \"pre\" || !parent || index == null) return;\n\n const codeNode = node.children.find((child) => isElement(child) && child.tagName === \"code\") as\n | Element\n | undefined;\n if (!codeNode) return;\n\n const className = (codeNode.properties?.className ?? []) as unknown[];\n if (!className.includes(\"language-matvis\")) return;\n\n const source = codeNode.children\n .filter((child) => child.type === \"text\")\n .map((child) => (child as { value: string }).value)\n .join(\"\");\n\n const { svg } = renderDiagram(source);\n const svgHast = svgStringToHast(svg);\n parent.children.splice(index, 1, svgHast);\n return index;\n });\n };\n}\n","import { fromHtmlIsomorphic } from \"hast-util-from-html-isomorphic\";\nimport type { Element } from \"hast\";\n\nexport function svgStringToHast(svg: string): Element {\n const root = fromHtmlIsomorphic(svg, { fragment: true });\n const svgElement = root.children.find((node) => node.type === \"element\") as Element | undefined;\n if (!svgElement) {\n throw new Error(\"matvis: failed to parse generated SVG into a hast element\");\n }\n return svgElement;\n}\n"],"mappings":";;;;;AACA,SAAS,aAAa;;;ACDtB,SAAS,0BAA0B;AAG5B,SAAS,gBAAgB,KAAsB;AACpD,QAAM,OAAO,mBAAmB,KAAK,EAAE,UAAU,KAAK,CAAC;AACvD,QAAM,aAAa,KAAK,SAAS,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS;AACvE,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,2DAA2D;AAAA,EAC7E;AACA,SAAO;AACT;;;ADLA,SAAS,UAAU,MAAgC;AACjD,SAAO,OAAO,SAAS,YAAY,SAAS,QAAS,KAA2B,SAAS;AAC3F;AAEe,SAAR,eAAgC;AACrC,SAAO,CAAC,SAAe;AACrB,UAAM,MAAM,WAAW,CAAC,MAAe,OAAO,WAAW;AACvD,UAAI,KAAK,YAAY,SAAS,CAAC,UAAU,SAAS,KAAM;AAExD,YAAM,WAAW,KAAK,SAAS,KAAK,CAAC,UAAU,UAAU,KAAK,KAAK,MAAM,YAAY,MAAM;AAG3F,UAAI,CAAC,SAAU;AAEf,YAAM,YAAa,SAAS,YAAY,aAAa,CAAC;AACtD,UAAI,CAAC,UAAU,SAAS,iBAAiB,EAAG;AAE5C,YAAM,SAAS,SAAS,SACrB,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAW,MAA4B,KAAK,EACjD,KAAK,EAAE;AAEV,YAAM,EAAE,IAAI,IAAI,cAAc,MAAM;AACpC,YAAM,UAAU,gBAAgB,GAAG;AACnC,aAAO,SAAS,OAAO,OAAO,GAAG,OAAO;AACxC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "rehype-matvis",
3
+ "version": "0.1.0",
4
+ "description": "Rehype plugin for rendering ML tensor/memory diagrams (shape/strides, matmul tiling, broadcasting) as inline SVG",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./core": {
14
+ "types": "./dist/core/index.d.ts",
15
+ "import": "./dist/core/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "preview": "tsx preview/build.ts"
28
+ },
29
+ "keywords": [
30
+ "rehype",
31
+ "remark",
32
+ "markdown",
33
+ "svg",
34
+ "tensor",
35
+ "matrix",
36
+ "ml"
37
+ ],
38
+ "author": "notnotharsh",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/notnotharsh/rehype-matvis.git"
43
+ },
44
+ "homepage": "https://github.com/notnotharsh/rehype-matvis#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/notnotharsh/rehype-matvis/issues"
47
+ },
48
+ "dependencies": {
49
+ "yaml": "^2.5.0",
50
+ "unist-util-visit": "^5.0.0",
51
+ "hast-util-from-html-isomorphic": "^2.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/hast": "^3.0.4",
55
+ "@types/node": "^22.0.0",
56
+ "rehype-stringify": "^10.0.0",
57
+ "remark-parse": "^11.0.0",
58
+ "remark-rehype": "^11.1.0",
59
+ "tsup": "^8.0.0",
60
+ "tsx": "^4.0.0",
61
+ "typescript": "^5.5.0",
62
+ "unified": "^11.0.0",
63
+ "vitest": "^2.0.0"
64
+ }
65
+ }