@simple-mark/canvas 0.3.3 → 0.3.5
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/index.d.ts +19 -2
- package/index.js +79 -17
- package/package.json +3 -3
package/index.d.ts
CHANGED
|
@@ -46,23 +46,40 @@ interface TextRun {
|
|
|
46
46
|
lineFont: string;
|
|
47
47
|
fontSize: number;
|
|
48
48
|
color: string;
|
|
49
|
-
/** Behind this text only (inline code). */
|
|
49
|
+
/** Behind this text only (inline code); neighbouring runs with the same one share a box. */
|
|
50
50
|
background?: string;
|
|
51
|
+
/** Corner radius of that box. */
|
|
52
|
+
radius?: number;
|
|
51
53
|
strike?: boolean;
|
|
52
54
|
underline?: boolean;
|
|
53
55
|
/** Draw character by character on a grid of this column width (tables), wide ones in two columns. */
|
|
54
56
|
column?: number;
|
|
55
57
|
}
|
|
56
|
-
/** A line's full-width background or
|
|
58
|
+
/** A line's full-width background, bar or lines (code blocks, table rows, quotes, headings, rules). */
|
|
57
59
|
interface LineDecoration {
|
|
58
60
|
top: number;
|
|
59
61
|
height: number;
|
|
60
62
|
background?: string;
|
|
63
|
+
/** Corner radii of the background: top (first line of a code block) and bottom (last line). */
|
|
64
|
+
radius?: {
|
|
65
|
+
top: number;
|
|
66
|
+
bottom: number;
|
|
67
|
+
};
|
|
61
68
|
/** A bar along the left edge: its color and width. */
|
|
62
69
|
bar?: {
|
|
63
70
|
color: string;
|
|
64
71
|
width: number;
|
|
65
72
|
};
|
|
73
|
+
/** A line along the bottom edge (under h1, h2). */
|
|
74
|
+
border?: {
|
|
75
|
+
color: string;
|
|
76
|
+
width: number;
|
|
77
|
+
};
|
|
78
|
+
/** A line across the middle (horizontal rules). */
|
|
79
|
+
rule?: {
|
|
80
|
+
color: string;
|
|
81
|
+
width: number;
|
|
82
|
+
};
|
|
66
83
|
}
|
|
67
84
|
interface ImageBox {
|
|
68
85
|
kind: 'image';
|
package/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { charWidth, defaultTheme, markdownParser, markdownStyles, previewBlocks, resolveLineLook, resolveLook } from "@simple-mark/model";
|
|
1
|
+
import { STROKES, charWidth, defaultTheme, markdownParser, markdownStyles, previewBlocks, resolveLineLook, resolveLook } from "@simple-mark/model";
|
|
2
2
|
//#region packages/canvas/src/layout.ts
|
|
3
3
|
/** Editor sizes in em, so a canvas text at any font size looks like the editor at 15px. */
|
|
4
4
|
const EM = {
|
|
5
5
|
previewTop: 4 / 15,
|
|
6
6
|
previewBottom: 8 / 15,
|
|
7
7
|
imageMaxHeight: 320 / 15,
|
|
8
|
-
imageGap: 8 / 15
|
|
9
|
-
quoteBar: 3 / 15,
|
|
10
|
-
quoteIndent: 12 / 15
|
|
8
|
+
imageGap: 8 / 15
|
|
11
9
|
};
|
|
12
10
|
const ERROR_COLOR = "#cf222e";
|
|
13
11
|
/** Characters a line may break between, like CJK in the browser (no spaces needed). */
|
|
@@ -54,11 +52,19 @@ function layoutMarkdown(source, options) {
|
|
|
54
52
|
}
|
|
55
53
|
return lo;
|
|
56
54
|
};
|
|
57
|
-
const lineBlocks = lineStarts.map(() =>
|
|
55
|
+
const lineBlocks = lineStarts.map(() => ({
|
|
56
|
+
styles: /* @__PURE__ */ new Set(),
|
|
57
|
+
first: /* @__PURE__ */ new Set(),
|
|
58
|
+
last: /* @__PURE__ */ new Set()
|
|
59
|
+
}));
|
|
58
60
|
for (const block of blocks) {
|
|
59
61
|
let end = block.to;
|
|
60
62
|
if (end > block.from && isLineStart.has(end)) end--;
|
|
61
|
-
|
|
63
|
+
const first = lineOf(block.from);
|
|
64
|
+
const last = lineOf(end);
|
|
65
|
+
for (let n = first; n <= last; n++) lineBlocks[n].styles.add(block.style);
|
|
66
|
+
lineBlocks[first].first.add(block.style);
|
|
67
|
+
lineBlocks[last].last.add(block.style);
|
|
62
68
|
}
|
|
63
69
|
const previews = /* @__PURE__ */ new Map();
|
|
64
70
|
for (const preview of previewBlocks(tree, text)) {
|
|
@@ -79,12 +85,12 @@ function layoutMarkdown(source, options) {
|
|
|
79
85
|
for (let n = 0; n < lineStarts.length; n++) {
|
|
80
86
|
const from = lineStarts[n];
|
|
81
87
|
const to = lineEndOf(n);
|
|
82
|
-
const styles = [...lineBlocks[n]];
|
|
83
|
-
const lineLook = resolveLineLook(styles, theme);
|
|
88
|
+
const styles = [...lineBlocks[n].styles];
|
|
89
|
+
const lineLook = resolveLineLook(styles, theme, lineBlocks[n]);
|
|
84
90
|
const scale = lineLook.fontScale ?? 1;
|
|
85
91
|
const lineHeight = style.lineHeight * style.fontSize * scale;
|
|
86
92
|
const textTop = (lineHeight - style.fontSize * scale) / 2;
|
|
87
|
-
const indent = lineLook.
|
|
93
|
+
const indent = (lineLook.indent ?? 0) * style.fontSize;
|
|
88
94
|
const lineFont = fontOf(resolveLook(styles, [], theme), style, theme, scale);
|
|
89
95
|
const table = styles.includes("table");
|
|
90
96
|
const available = wrapWidth - indent;
|
|
@@ -158,7 +164,7 @@ function layoutMarkdown(source, options) {
|
|
|
158
164
|
visual.at(-1).push(piece);
|
|
159
165
|
x += piece.width;
|
|
160
166
|
}
|
|
161
|
-
for (const row of visual) {
|
|
167
|
+
for (const [r, row] of visual.entries()) {
|
|
162
168
|
const first = runs.length;
|
|
163
169
|
let rx = indent;
|
|
164
170
|
for (const piece of row) {
|
|
@@ -181,6 +187,7 @@ function layoutMarkdown(source, options) {
|
|
|
181
187
|
fontSize: style.fontSize * scale,
|
|
182
188
|
color: look.color ?? style.color,
|
|
183
189
|
...look.background ? { background: look.background } : {},
|
|
190
|
+
...look.background && look.radius ? { radius: look.radius * style.fontSize * scale } : {},
|
|
184
191
|
...look.strike ? { strike: true } : {},
|
|
185
192
|
...look.underline ? { underline: true } : {},
|
|
186
193
|
...table ? { column: measure("0", font) } : {}
|
|
@@ -195,13 +202,28 @@ function layoutMarkdown(source, options) {
|
|
|
195
202
|
rx
|
|
196
203
|
]);
|
|
197
204
|
maxWidth = Math.max(maxWidth, rx);
|
|
198
|
-
|
|
205
|
+
const radiusTop = r === 0 ? (lineLook.radiusTop ?? 0) * style.fontSize : 0;
|
|
206
|
+
const radiusBottom = r === visual.length - 1 ? (lineLook.radiusBottom ?? 0) * style.fontSize : 0;
|
|
207
|
+
const border = r === visual.length - 1 ? lineLook.borderBottom : void 0;
|
|
208
|
+
if (lineLook.lineBackground || lineLook.borderLeft || border || lineLook.rule) lines.push({
|
|
199
209
|
top: y,
|
|
200
210
|
height: lineHeight,
|
|
201
211
|
...lineLook.lineBackground ? { background: lineLook.lineBackground } : {},
|
|
212
|
+
...lineLook.lineBackground && (radiusTop || radiusBottom) ? { radius: {
|
|
213
|
+
top: radiusTop,
|
|
214
|
+
bottom: radiusBottom
|
|
215
|
+
} } : {},
|
|
202
216
|
...lineLook.borderLeft ? { bar: {
|
|
203
217
|
color: lineLook.borderLeft,
|
|
204
|
-
width:
|
|
218
|
+
width: STROKES.bar * style.fontSize
|
|
219
|
+
} } : {},
|
|
220
|
+
...border ? { border: {
|
|
221
|
+
color: border,
|
|
222
|
+
width: STROKES.border * style.fontSize
|
|
223
|
+
} } : {},
|
|
224
|
+
...lineLook.rule ? { rule: {
|
|
225
|
+
color: lineLook.rule,
|
|
226
|
+
width: STROKES.rule * style.fontSize
|
|
205
227
|
} } : {}
|
|
206
228
|
});
|
|
207
229
|
y += lineHeight;
|
|
@@ -381,27 +403,67 @@ function baseline(ctx, run) {
|
|
|
381
403
|
alphabetic: true
|
|
382
404
|
};
|
|
383
405
|
}
|
|
406
|
+
/** A filled rectangle with rounded corners (top-left, top-right, bottom-right, bottom-left). */
|
|
407
|
+
function fillBox(ctx, x, y, width, height, radius) {
|
|
408
|
+
const radii = typeof radius === "number" ? [
|
|
409
|
+
radius,
|
|
410
|
+
radius,
|
|
411
|
+
radius,
|
|
412
|
+
radius
|
|
413
|
+
] : radius;
|
|
414
|
+
if (!radii.some((r) => r > 0) || !ctx.roundRect) {
|
|
415
|
+
ctx.fillRect(x, y, width, height);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
ctx.beginPath();
|
|
419
|
+
ctx.roundRect(x, y, width, height, radii.map((r) => Math.min(r, width / 2, height / 2)));
|
|
420
|
+
ctx.fill();
|
|
421
|
+
}
|
|
384
422
|
function paintMarkdown(ctx, layout, sources = {}, color = "#1f2328") {
|
|
385
423
|
ctx.save();
|
|
386
424
|
ctx.textAlign = "left";
|
|
387
425
|
for (const line of layout.lines) {
|
|
388
426
|
if (line.background) {
|
|
389
427
|
ctx.fillStyle = line.background;
|
|
390
|
-
ctx
|
|
428
|
+
fillBox(ctx, 0, line.top, layout.width, line.height, line.radius ? [
|
|
429
|
+
line.radius.top,
|
|
430
|
+
line.radius.top,
|
|
431
|
+
line.radius.bottom,
|
|
432
|
+
line.radius.bottom
|
|
433
|
+
] : 0);
|
|
434
|
+
}
|
|
435
|
+
if (line.rule) {
|
|
436
|
+
ctx.fillStyle = line.rule.color;
|
|
437
|
+
ctx.fillRect(0, line.top + (line.height - line.rule.width) / 2, layout.width, line.rule.width);
|
|
391
438
|
}
|
|
392
439
|
if (line.bar) {
|
|
393
440
|
ctx.fillStyle = line.bar.color;
|
|
394
441
|
ctx.fillRect(0, line.top, line.bar.width, line.height);
|
|
395
442
|
}
|
|
443
|
+
if (line.border) {
|
|
444
|
+
ctx.fillStyle = line.border.color;
|
|
445
|
+
ctx.fillRect(0, line.top + line.height - line.border.width, layout.width, line.border.width);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (let i = 0; i < layout.runs.length;) {
|
|
449
|
+
const run = layout.runs[i];
|
|
450
|
+
let end = run.x + run.width;
|
|
451
|
+
let j = i + 1;
|
|
452
|
+
if (run.background) {
|
|
453
|
+
for (; j < layout.runs.length; j++) {
|
|
454
|
+
const next = layout.runs[j];
|
|
455
|
+
if (next.background !== run.background || next.top !== run.top || Math.abs(next.x - end) > .5) break;
|
|
456
|
+
end = next.x + next.width;
|
|
457
|
+
}
|
|
458
|
+
ctx.fillStyle = run.background;
|
|
459
|
+
fillBox(ctx, run.x, run.textTop - run.fontSize * .1, end - run.x, run.fontSize * 1.2, run.radius ?? 0);
|
|
460
|
+
}
|
|
461
|
+
i = j;
|
|
396
462
|
}
|
|
397
463
|
for (const run of layout.runs) {
|
|
398
464
|
const { y, alphabetic } = baseline(ctx, run);
|
|
399
465
|
ctx.font = run.font;
|
|
400
466
|
ctx.textBaseline = alphabetic ? "alphabetic" : "top";
|
|
401
|
-
if (run.background) {
|
|
402
|
-
ctx.fillStyle = run.background;
|
|
403
|
-
ctx.fillRect(run.x, run.textTop - run.fontSize * .1, run.width, run.fontSize * 1.2);
|
|
404
|
-
}
|
|
405
467
|
ctx.fillStyle = run.color;
|
|
406
468
|
if (run.column) {
|
|
407
469
|
let x = run.x;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simple-mark/canvas",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Draws Markdown on a <canvas> without hiding its syntax: layout (no DOM) and Canvas 2D painting.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@simple-mark/math": "^0.3.
|
|
27
|
-
"@simple-mark/model": "^0.3.
|
|
26
|
+
"@simple-mark/math": "^0.3.5",
|
|
27
|
+
"@simple-mark/model": "^0.3.5"
|
|
28
28
|
}
|
|
29
29
|
}
|