postext 1.53.0 → 1.54.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/dist/__tests__/bundle/balloonPlacementPin.test.js +6 -5
- package/dist/__tests__/bundle/balloonPlacementPin.test.js.map +1 -1
- package/dist/__tests__/bundle/designOverflowPin.test.js +1 -1
- package/dist/__tests__/bundle/gridBalancingPin.test.js +6 -5
- package/dist/__tests__/bundle/gridBalancingPin.test.js.map +1 -1
- package/dist/__tests__/bundle/gridBreakRulesPin.test.js +8 -7
- package/dist/__tests__/bundle/gridBreakRulesPin.test.js.map +1 -1
- package/dist/__tests__/bundle/looseOrderPin.test.js +6 -5
- package/dist/__tests__/bundle/looseOrderPin.test.js.map +1 -1
- package/dist/__tests__/bundle/markRadiusPin.test.js +1 -1
- package/dist/__tests__/bundle/refLabelBreaksPin.test.js +6 -5
- package/dist/__tests__/bundle/refLabelBreaksPin.test.js.map +1 -1
- package/dist/__tests__/bundle/scriptContextPin.test.js +6 -5
- package/dist/__tests__/bundle/scriptContextPin.test.js.map +1 -1
- package/dist/__tests__/bundle/shapingLanguagePin.test.js +3 -3
- package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
- package/dist/__tests__/bundle.test.js +1 -1
- package/dist/__tests__/codeBlocks.test.js +1 -1
- package/dist/__tests__/composedTemplates.test.js +1 -1
- package/dist/__tests__/exports.test.js +17 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/hardBreaks.test.js +1 -1
- package/dist/__tests__/headingInlineMarks.test.js +1 -1
- package/dist/__tests__/mathSizeMigration.test.js +2 -2
- package/dist/__tests__/musicEngine.test.d.ts +2 -0
- package/dist/__tests__/musicEngine.test.d.ts.map +1 -0
- package/dist/__tests__/musicEngine.test.js +102 -0
- package/dist/__tests__/musicEngine.test.js.map +1 -0
- package/dist/__tests__/musicLayout.test.d.ts +2 -0
- package/dist/__tests__/musicLayout.test.d.ts.map +1 -0
- package/dist/__tests__/musicLayout.test.js +146 -0
- package/dist/__tests__/musicLayout.test.js.map +1 -0
- package/dist/__tests__/pipeline/boxParagraphStyles.test.js +2 -2
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
- package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
- package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
- package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
- package/dist/__tests__/slashBreaks.test.js +1 -1
- package/dist/__tests__/verseLines.test.js +1 -1
- package/dist/bundle/codec.d.ts.map +1 -1
- package/dist/bundle/codec.js +10 -0
- package/dist/bundle/codec.js.map +1 -1
- package/dist/bundle/configVersion.d.ts +12 -2
- package/dist/bundle/configVersion.d.ts.map +1 -1
- package/dist/bundle/configVersion.js +32 -2
- package/dist/bundle/configVersion.js.map +1 -1
- package/dist/bundle/manifest.d.ts.map +1 -1
- package/dist/bundle/manifest.js +9 -2
- package/dist/bundle/manifest.js.map +1 -1
- package/dist/bundle/types.d.ts +6 -1
- package/dist/bundle/types.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +17 -2
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +7 -0
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/canvas-backend/scoreRender.d.ts +10 -0
- package/dist/canvas-backend/scoreRender.d.ts.map +1 -0
- package/dist/canvas-backend/scoreRender.js +74 -0
- package/dist/canvas-backend/scoreRender.js.map +1 -0
- package/dist/composed/sourceMap.js +1 -1
- package/dist/composed/sourceMap.js.map +1 -1
- package/dist/defaults/index.d.ts +2 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +17 -1
- package/dist/defaults/index.js.map +1 -1
- package/dist/defaults/resourceTypes.d.ts +5 -0
- package/dist/defaults/resourceTypes.d.ts.map +1 -1
- package/dist/defaults/resourceTypes.js +34 -4
- package/dist/defaults/resourceTypes.js.map +1 -1
- package/dist/defaults/scoreStyle.d.ts +15 -0
- package/dist/defaults/scoreStyle.d.ts.map +1 -0
- package/dist/defaults/scoreStyle.js +91 -0
- package/dist/defaults/scoreStyle.js.map +1 -0
- package/dist/design/elementKeys.d.ts +2 -0
- package/dist/design/elementKeys.d.ts.map +1 -1
- package/dist/design/elementKeys.js +1 -1
- package/dist/design/elementKeys.js.map +1 -1
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +47 -0
- package/dist/html-backend.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/music/engine.d.ts +21 -0
- package/dist/music/engine.d.ts.map +1 -0
- package/dist/music/engine.js +309 -0
- package/dist/music/engine.js.map +1 -0
- package/dist/music/flatten.d.ts +21 -0
- package/dist/music/flatten.d.ts.map +1 -0
- package/dist/music/flatten.js +302 -0
- package/dist/music/flatten.js.map +1 -0
- package/dist/music/index.d.ts +4 -0
- package/dist/music/index.d.ts.map +1 -0
- package/dist/music/index.js +3 -0
- package/dist/music/index.js.map +1 -0
- package/dist/music/types.d.ts +88 -0
- package/dist/music/types.d.ts.map +1 -0
- package/dist/music/types.js +35 -0
- package/dist/music/types.js.map +1 -0
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +21 -1
- package/dist/parse/blockParser.js.map +1 -1
- package/dist/parse/types.d.ts +30 -1
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +40 -7
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
- package/dist/pipeline/buildBlockKind.js +9 -0
- package/dist/pipeline/buildBlockKind.js.map +1 -1
- package/dist/pipeline/buildMeasurement.d.ts.map +1 -1
- package/dist/pipeline/buildMeasurement.js +22 -0
- package/dist/pipeline/buildMeasurement.js.map +1 -1
- package/dist/pipeline/calloutLayout.d.ts.map +1 -1
- package/dist/pipeline/calloutLayout.js +14 -3
- package/dist/pipeline/calloutLayout.js.map +1 -1
- package/dist/pipeline/codeBlocks.d.ts.map +1 -1
- package/dist/pipeline/codeBlocks.js +4 -2
- package/dist/pipeline/codeBlocks.js.map +1 -1
- package/dist/pipeline/columnBalancing.d.ts.map +1 -1
- package/dist/pipeline/columnBalancing.js +7 -1
- package/dist/pipeline/columnBalancing.js.map +1 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +3 -1
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/frameLayout.js +1 -1
- package/dist/pipeline/frameLayout.js.map +1 -1
- package/dist/pipeline/lineNumbers.js +1 -1
- package/dist/pipeline/lineNumbers.js.map +1 -1
- package/dist/pipeline/resourceLayout.d.ts.map +1 -1
- package/dist/pipeline/resourceLayout.js +9 -0
- package/dist/pipeline/resourceLayout.js.map +1 -1
- package/dist/pipeline/scoreBlocks.d.ts +44 -0
- package/dist/pipeline/scoreBlocks.d.ts.map +1 -0
- package/dist/pipeline/scoreBlocks.js +172 -0
- package/dist/pipeline/scoreBlocks.js.map +1 -0
- package/dist/pipeline/tashkil.js +1 -1
- package/dist/pipeline/tashkil.js.map +1 -1
- package/dist/react/createLayout.d.ts.map +1 -1
- package/dist/react/createLayout.js +4 -0
- package/dist/react/createLayout.js.map +1 -1
- package/dist/types.d.ts +76 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +43 -4
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/dist/worker/layout.worker.js +10 -0
- package/dist/worker/layout.worker.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { flattenVerovioSvg, itemsToSvg } from './flatten.js';
|
|
2
|
+
import { measureTextWidth } from '../measure/canvas.js';
|
|
3
|
+
import { buildFontString } from '../measure/font.js';
|
|
4
|
+
let toolkit = null;
|
|
5
|
+
let initPromise = null;
|
|
6
|
+
const initListeners = new Set();
|
|
7
|
+
let warnedWithoutEngine = false;
|
|
8
|
+
export function isMusicReady() {
|
|
9
|
+
return toolkit !== null;
|
|
10
|
+
}
|
|
11
|
+
export function onMusicReady(fn) {
|
|
12
|
+
if (toolkit !== null) {
|
|
13
|
+
fn();
|
|
14
|
+
return () => { };
|
|
15
|
+
}
|
|
16
|
+
initListeners.add(fn);
|
|
17
|
+
return () => { initListeners.delete(fn); };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load Verovio and start the score engraver. Await it before laying out a
|
|
21
|
+
* document with scores on the main thread (the layout worker calls it
|
|
22
|
+
* itself): until it resolves, every score is set as a grey placeholder box.
|
|
23
|
+
* Idempotent; a failed start can be retried.
|
|
24
|
+
*/
|
|
25
|
+
export async function initMusicEngine() {
|
|
26
|
+
if (toolkit !== null)
|
|
27
|
+
return;
|
|
28
|
+
if (initPromise)
|
|
29
|
+
return initPromise;
|
|
30
|
+
initPromise = (async () => {
|
|
31
|
+
try {
|
|
32
|
+
const [{ default: createVerovioModule }, { VerovioToolkit }] = await Promise.all([
|
|
33
|
+
import('verovio/wasm-hum'),
|
|
34
|
+
import('verovio/esm'),
|
|
35
|
+
]);
|
|
36
|
+
const module = await createVerovioModule();
|
|
37
|
+
toolkit = new VerovioToolkit(module);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
initPromise = null;
|
|
41
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
42
|
+
throw new Error(`[postext] the music engine failed to start: ${reason}`, { cause: err });
|
|
43
|
+
}
|
|
44
|
+
for (const fn of initListeners)
|
|
45
|
+
fn();
|
|
46
|
+
initListeners.clear();
|
|
47
|
+
})();
|
|
48
|
+
return initPromise;
|
|
49
|
+
}
|
|
50
|
+
/** Called when the layout sets a score with no engine running. Warns once,
|
|
51
|
+
* and only when nobody asked for the engine. */
|
|
52
|
+
export function noteMusicWithoutEngine() {
|
|
53
|
+
if (toolkit !== null || initPromise !== null || warnedWithoutEngine)
|
|
54
|
+
return;
|
|
55
|
+
warnedWithoutEngine = true;
|
|
56
|
+
console.warn('[postext] This document contains music scores, but the music engine is not running, so every score is laid out as a grey placeholder box. '
|
|
57
|
+
+ 'Call `await initMusicEngine()` before `buildDocument` (the layout worker does it for you).');
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// LRU cache
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
const MAX_CACHE = 512;
|
|
63
|
+
const cache = new Map();
|
|
64
|
+
function cacheKey(source, format, widthPx, o) {
|
|
65
|
+
return [
|
|
66
|
+
format, widthPx.toFixed(2), o.staffSizePx.toFixed(3), o.font, o.textFont ?? '', o.spacing,
|
|
67
|
+
o.barNumbers, o.systemSpacing, source,
|
|
68
|
+
].join('|');
|
|
69
|
+
}
|
|
70
|
+
export function clearMusicCache() {
|
|
71
|
+
cache.clear();
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Rendering
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
/** Verovio's staff height at scale 100, in px: 4 staff spaces of 18 px. */
|
|
77
|
+
const STAFF_PX_AT_100 = 72;
|
|
78
|
+
/** The value of the first ABC header line `field:` (`T:` title, `C:`
|
|
79
|
+
* composer), read line by line. */
|
|
80
|
+
function abcField(source, field) {
|
|
81
|
+
for (const line of source.split('\n')) {
|
|
82
|
+
if (line.startsWith(`${field}:`)) {
|
|
83
|
+
const value = line.slice(field.length + 1).trim();
|
|
84
|
+
if (value)
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/** The text of the first element whose opening tag starts with one of
|
|
91
|
+
* `openings` (`<work-title`, `<creator type="composer"`), found with plain
|
|
92
|
+
* string scans. */
|
|
93
|
+
function elementText(source, openings) {
|
|
94
|
+
const lower = source.toLowerCase();
|
|
95
|
+
for (const opening of openings) {
|
|
96
|
+
const at = lower.indexOf(opening);
|
|
97
|
+
if (at < 0)
|
|
98
|
+
continue;
|
|
99
|
+
const next = source[at + opening.length];
|
|
100
|
+
if (next !== '>' && next !== ' ' && next !== '\t' && next !== '\n')
|
|
101
|
+
continue;
|
|
102
|
+
const open = source.indexOf('>', at);
|
|
103
|
+
const close = open < 0 ? -1 : source.indexOf('<', open + 1);
|
|
104
|
+
if (close < 0)
|
|
105
|
+
continue;
|
|
106
|
+
const text = source.slice(open + 1, close).trim();
|
|
107
|
+
if (text)
|
|
108
|
+
return text;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
function readMetadata(source, format) {
|
|
113
|
+
if (format === 'abc')
|
|
114
|
+
return { title: abcField(source, 'T'), composer: abcField(source, 'C') };
|
|
115
|
+
return {
|
|
116
|
+
title: elementText(source, ['<work-title', '<movement-title', '<title']),
|
|
117
|
+
composer: elementText(source, ['<creator type="composer"', '<persname role="composer"', '<composer']),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function verovioOptions(widthPx, o, leftMarginPx = 0) {
|
|
121
|
+
// Engrave at scale 100 on a page as wide as the measure at that scale; the
|
|
122
|
+
// slices are then scaled to the measure exactly (Verovio's scale is an
|
|
123
|
+
// integer percentage, too coarse for a staff size in px).
|
|
124
|
+
const toScale100 = STAFF_PX_AT_100 / o.staffSizePx;
|
|
125
|
+
return {
|
|
126
|
+
scale: 100,
|
|
127
|
+
pageWidth: Math.max(100, Math.round(widthPx * toScale100)),
|
|
128
|
+
// Room for a brace or bracket left of the staves (see `renderScore`).
|
|
129
|
+
pageMarginLeft: Math.round(leftMarginPx * toScale100),
|
|
130
|
+
// One system per page: each page is one breakable slice of the score.
|
|
131
|
+
pageHeight: 100,
|
|
132
|
+
adjustPageHeight: true,
|
|
133
|
+
breaks: 'auto',
|
|
134
|
+
header: 'none',
|
|
135
|
+
footer: 'none',
|
|
136
|
+
pageMarginTop: 0,
|
|
137
|
+
pageMarginBottom: 0,
|
|
138
|
+
pageMarginRight: 0,
|
|
139
|
+
spacingSystem: o.systemSpacing,
|
|
140
|
+
spacingLinear: 0.25 * o.spacing,
|
|
141
|
+
spacingNonLinear: 0.6,
|
|
142
|
+
mnumInterval: o.barNumbers === 'every' ? 1 : 0,
|
|
143
|
+
// Room between the words of a lyric line: at the engraver's default
|
|
144
|
+
// (1.2) the words of a narrow measure touch ("nigh,sha-").
|
|
145
|
+
lyricWordSpace: 2.5,
|
|
146
|
+
font: o.font,
|
|
147
|
+
svgViewBox: true,
|
|
148
|
+
svgRemoveXlink: true,
|
|
149
|
+
smuflTextFont: 'none',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const COORD_RUN_RE = /[MLCQST]([^A-Za-z]*)/g;
|
|
153
|
+
/** The leftmost x a system paints at (its paths' points; text is never
|
|
154
|
+
* left of the staves). */
|
|
155
|
+
function itemsMinX(items) {
|
|
156
|
+
let min = Infinity;
|
|
157
|
+
for (const it of items) {
|
|
158
|
+
if (it.type !== 'path')
|
|
159
|
+
continue;
|
|
160
|
+
for (const m of it.d.matchAll(COORD_RUN_RE)) {
|
|
161
|
+
const nums = m[1].trim().split(/[\s,]+/).filter(Boolean).map(Number);
|
|
162
|
+
for (let k = 0; k < nums.length; k += 2)
|
|
163
|
+
if (nums[k] < min)
|
|
164
|
+
min = nums[k];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return Number.isFinite(min) ? min : 0;
|
|
168
|
+
}
|
|
169
|
+
/** The face Verovio measures words in when it spaces the notes. */
|
|
170
|
+
const ENGRAVER_TEXT_FONT = 'Times New Roman';
|
|
171
|
+
/**
|
|
172
|
+
* A word of several runs (a chord symbol and its raised figure) as one item
|
|
173
|
+
* per run, each at its size and baseline, laid side by side by their widths
|
|
174
|
+
* in `textFont` (about half an em per character where nothing measures).
|
|
175
|
+
*/
|
|
176
|
+
function layOutRuns(items, textFont) {
|
|
177
|
+
if (!items.some((it) => it.type === 'text' && it.runs))
|
|
178
|
+
return items;
|
|
179
|
+
const widthOf = (text, size, bold, italic) => {
|
|
180
|
+
try {
|
|
181
|
+
const w = measureTextWidth(text, buildFontString(textFont, size, bold ? 'bold' : 'normal', italic ? 'italic' : 'normal'));
|
|
182
|
+
if (w > 0)
|
|
183
|
+
return w;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// No measuring context: the estimate below.
|
|
187
|
+
}
|
|
188
|
+
return text.length * size * 0.5;
|
|
189
|
+
};
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const it of items) {
|
|
192
|
+
if (it.type !== 'text' || !it.runs) {
|
|
193
|
+
out.push(it);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const widths = it.runs.map((r) => r.dx + widthOf(r.text, r.sizePx, r.bold ?? it.bold, r.italic ?? it.italic));
|
|
197
|
+
const total = widths.reduce((a, b) => a + b, 0);
|
|
198
|
+
let x = it.anchor === 'middle' ? it.x - total / 2 : it.anchor === 'end' ? it.x - total : it.x;
|
|
199
|
+
it.runs.forEach((r, i) => {
|
|
200
|
+
x += r.dx;
|
|
201
|
+
out.push({ type: 'text', x, y: it.y + r.rise, text: r.text, sizePx: r.sizePx, bold: r.bold ?? it.bold, italic: r.italic ?? it.italic });
|
|
202
|
+
x += widths[i] - r.dx;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Words set in `textFont` at the room the engraver gave them: it spaces
|
|
209
|
+
* lyrics and directions by its own (Times) metrics, so a wider face would
|
|
210
|
+
* run one syllable into the next. A word wider in `textFont` than in Times
|
|
211
|
+
* is set that much smaller. Without a measuring context (a bare Node
|
|
212
|
+
* process) the words are left as they are.
|
|
213
|
+
*/
|
|
214
|
+
function fitWords(items, textFont) {
|
|
215
|
+
if (textFont === ENGRAVER_TEXT_FONT)
|
|
216
|
+
return items;
|
|
217
|
+
try {
|
|
218
|
+
return items.map((it) => {
|
|
219
|
+
if (it.type !== 'text')
|
|
220
|
+
return it;
|
|
221
|
+
const weight = it.bold ? 'bold' : 'normal';
|
|
222
|
+
const style = it.italic ? 'italic' : 'normal';
|
|
223
|
+
const planned = measureTextWidth(it.text, buildFontString(ENGRAVER_TEXT_FONT, 100, weight, style));
|
|
224
|
+
const actual = measureTextWidth(it.text, buildFontString(textFont, 100, weight, style));
|
|
225
|
+
if (!(planned > 0) || !(actual > planned * 1.01))
|
|
226
|
+
return it;
|
|
227
|
+
return { ...it, sizePx: it.sizePx * (planned / actual) };
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return items;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const DEFAULT_TEXT_FONT = 'Times New Roman, serif';
|
|
235
|
+
function toSystem(svg, o, widthPx) {
|
|
236
|
+
const flat = flattenVerovioSvg(svg, widthPx, o.barNumbers === 'none' ? { skipClasses: ['mNum'] } : undefined);
|
|
237
|
+
if (!flat)
|
|
238
|
+
return null;
|
|
239
|
+
const textFont = o.textFont || DEFAULT_TEXT_FONT;
|
|
240
|
+
const items = fitWords(layOutRuns(flat.items, textFont), textFont);
|
|
241
|
+
return {
|
|
242
|
+
widthPx,
|
|
243
|
+
heightPx: flat.heightPx,
|
|
244
|
+
items,
|
|
245
|
+
// Every backend paints the same words: HTML, PDF and EPUB from this SVG.
|
|
246
|
+
svg: itemsToSvg(items, widthPx, flat.heightPx, 'currentColor', textFont),
|
|
247
|
+
textFont,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Engrave a score at a measure `widthPx` wide. Returns one slice per system;
|
|
252
|
+
* an empty list (with `error`) when the source does not load or the engine
|
|
253
|
+
* is not running.
|
|
254
|
+
*/
|
|
255
|
+
export function renderScore(source, format, widthPx, options) {
|
|
256
|
+
const key = cacheKey(source, format, widthPx, options);
|
|
257
|
+
const hit = cache.get(key);
|
|
258
|
+
if (hit) {
|
|
259
|
+
cache.delete(key);
|
|
260
|
+
cache.set(key, hit);
|
|
261
|
+
return hit;
|
|
262
|
+
}
|
|
263
|
+
const meta = readMetadata(source, format);
|
|
264
|
+
if (toolkit === null) {
|
|
265
|
+
noteMusicWithoutEngine();
|
|
266
|
+
return { systems: [], error: 'music engine not running', ...meta };
|
|
267
|
+
}
|
|
268
|
+
const engrave = (leftMarginPx) => {
|
|
269
|
+
toolkit.setOptions(verovioOptions(widthPx, options, leftMarginPx));
|
|
270
|
+
if (!toolkit.loadData(source))
|
|
271
|
+
return null;
|
|
272
|
+
// Bar numbers need numbered measures: ABC and Humdrum import theirs
|
|
273
|
+
// without `@n`. Numbered in order, and read again as MEI.
|
|
274
|
+
if (options.barNumbers !== 'none') {
|
|
275
|
+
const mei = toolkit.getMEI();
|
|
276
|
+
if (/<measure\b(?![^>]*\bn=)/.test(mei)) {
|
|
277
|
+
let n = 0;
|
|
278
|
+
if (!toolkit.loadData(mei.replace(/<measure\b(?![^>]*\bn=)/g, () => `<measure n="${++n}"`)))
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const systems = [];
|
|
283
|
+
const pages = toolkit.getPageCount();
|
|
284
|
+
for (let p = 1; p <= pages; p++) {
|
|
285
|
+
const slice = toSystem(toolkit.renderToSVG(p), options, widthPx);
|
|
286
|
+
if (slice)
|
|
287
|
+
systems.push(slice);
|
|
288
|
+
}
|
|
289
|
+
return systems;
|
|
290
|
+
};
|
|
291
|
+
let systems = engrave(0);
|
|
292
|
+
// A brace or bracket joining staves stands left of them, past the
|
|
293
|
+
// measure's edge: engraved again with a left margin that takes it, the
|
|
294
|
+
// staves indented as far.
|
|
295
|
+
const overhang = systems ? -Math.min(0, ...systems.map((s) => itemsMinX(s.items))) : 0;
|
|
296
|
+
if (systems && overhang > 0.5)
|
|
297
|
+
systems = engrave(Math.ceil(overhang + 1));
|
|
298
|
+
const result = systems
|
|
299
|
+
? { systems, ...meta }
|
|
300
|
+
: { systems: [], error: toolkit.getLog().trim() || `the ${format} source did not load`, ...meta };
|
|
301
|
+
cache.set(key, result);
|
|
302
|
+
if (cache.size > MAX_CACHE) {
|
|
303
|
+
const first = cache.keys().next().value;
|
|
304
|
+
if (first !== undefined)
|
|
305
|
+
cache.delete(first);
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/music/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ScoreItem } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Flatten a Verovio page SVG into items in px of a slice `widthPx` wide.
|
|
4
|
+
* Returns the items and the slice height, or null when the markup is not
|
|
5
|
+
* Verovio's.
|
|
6
|
+
*/
|
|
7
|
+
export declare function flattenVerovioSvg(svg: string, widthPx: number,
|
|
8
|
+
/** Groups left out by class (`mNum`: bar numbers). */
|
|
9
|
+
options?: {
|
|
10
|
+
skipClasses?: readonly string[];
|
|
11
|
+
}): {
|
|
12
|
+
items: ScoreItem[];
|
|
13
|
+
heightPx: number;
|
|
14
|
+
} | null;
|
|
15
|
+
/**
|
|
16
|
+
* Plain SVG for a flattened system: paths and text, `currentColor` ink, the
|
|
17
|
+
* text in `textFont`. HTML inlines it; the PDF backend draws it with its SVG
|
|
18
|
+
* vector subset.
|
|
19
|
+
*/
|
|
20
|
+
export declare function itemsToSvg(items: readonly ScoreItem[], widthPx: number, heightPx: number, color: string, textFont: string): string;
|
|
21
|
+
//# sourceMappingURL=flatten.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../src/music/flatten.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAwI5C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM;AACf,sDAAsD;AACtD,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC5C;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA8IjD;AAQD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYlI"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Flatten one Verovio system (SVG) into painted items: filled or stroked
|
|
2
|
+
// paths with every transform baked in, and text runs, all in px of the
|
|
3
|
+
// slice. Canvas paints the items; HTML and PDF take the plain SVG written
|
|
4
|
+
// back from them (`itemsToSvg`), which needs no stylesheet, no `<use>` and
|
|
5
|
+
// no nested viewport.
|
|
6
|
+
//
|
|
7
|
+
// Verovio's markup is well-formed and regular — glyphs in `<defs>` drawn by
|
|
8
|
+
// `<use>`, staff lines as stroked paths, stems and bar lines as rects, beams
|
|
9
|
+
// as polygons, dots as ellipses, words as `<text>`/`<tspan>` — so a small
|
|
10
|
+
// tag scanner does.
|
|
11
|
+
import { multiply, parseSvgTransform, scale as scaleMatrix, transformPath, } from '../math/pathTransform.js';
|
|
12
|
+
const ATTR_RE = /([\w:-]+)\s*=\s*"([^"]*)"/g;
|
|
13
|
+
function decode(s) {
|
|
14
|
+
return s
|
|
15
|
+
.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'")
|
|
16
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)))
|
|
17
|
+
.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)))
|
|
18
|
+
.replace(/&/g, '&');
|
|
19
|
+
}
|
|
20
|
+
function parse(svg) {
|
|
21
|
+
const root = { name: '#root', attrs: {}, children: [], text: '' };
|
|
22
|
+
const stack = [root];
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < svg.length) {
|
|
25
|
+
const lt = svg.indexOf('<', i);
|
|
26
|
+
const top = stack[stack.length - 1];
|
|
27
|
+
if (lt < 0)
|
|
28
|
+
break;
|
|
29
|
+
if (lt > i)
|
|
30
|
+
top.text += decode(svg.slice(i, lt));
|
|
31
|
+
if (svg.startsWith('<!--', lt)) {
|
|
32
|
+
const end = svg.indexOf('-->', lt);
|
|
33
|
+
i = end < 0 ? svg.length : end + 3;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const gt = svg.indexOf('>', lt);
|
|
37
|
+
if (gt < 0)
|
|
38
|
+
break;
|
|
39
|
+
const tag = svg.slice(lt + 1, gt);
|
|
40
|
+
i = gt + 1;
|
|
41
|
+
if (tag.startsWith('?') || tag.startsWith('!'))
|
|
42
|
+
continue;
|
|
43
|
+
if (tag.startsWith('/')) {
|
|
44
|
+
if (stack.length > 1)
|
|
45
|
+
stack.pop();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const selfClosing = tag.endsWith('/');
|
|
49
|
+
const body = selfClosing ? tag.slice(0, -1) : tag;
|
|
50
|
+
const sp = body.search(/\s/);
|
|
51
|
+
const name = (sp < 0 ? body : body.slice(0, sp)).replace(/^.*:/, '');
|
|
52
|
+
const attrs = {};
|
|
53
|
+
for (const m of body.matchAll(ATTR_RE))
|
|
54
|
+
attrs[m[1].replace(/^xlink:/, '')] = decode(m[2]);
|
|
55
|
+
const el = { name, attrs, children: [], text: '' };
|
|
56
|
+
top.children.push(el);
|
|
57
|
+
if (!selfClosing)
|
|
58
|
+
stack.push(el);
|
|
59
|
+
}
|
|
60
|
+
return root.children.find((c) => c.name === 'svg') ?? null;
|
|
61
|
+
}
|
|
62
|
+
function num(v, fallback = 0) {
|
|
63
|
+
if (v === undefined)
|
|
64
|
+
return fallback;
|
|
65
|
+
const n = parseFloat(v);
|
|
66
|
+
return Number.isFinite(n) ? n : fallback;
|
|
67
|
+
}
|
|
68
|
+
function rectPath(x, y, w, h) {
|
|
69
|
+
return `M${x} ${y}L${x + w} ${y}L${x + w} ${y + h}L${x} ${y + h}Z`;
|
|
70
|
+
}
|
|
71
|
+
/** An ellipse as four cubic Béziers (κ = 0.5523). */
|
|
72
|
+
function ellipsePath(cx, cy, rx, ry) {
|
|
73
|
+
const k = 0.5522847498;
|
|
74
|
+
const ox = rx * k;
|
|
75
|
+
const oy = ry * k;
|
|
76
|
+
return `M${cx - rx} ${cy}`
|
|
77
|
+
+ `C${cx - rx} ${cy - oy} ${cx - ox} ${cy - ry} ${cx} ${cy - ry}`
|
|
78
|
+
+ `C${cx + ox} ${cy - ry} ${cx + rx} ${cy - oy} ${cx + rx} ${cy}`
|
|
79
|
+
+ `C${cx + rx} ${cy + oy} ${cx + ox} ${cy + ry} ${cx} ${cy + ry}`
|
|
80
|
+
+ `C${cx - ox} ${cy + ry} ${cx - rx} ${cy + oy} ${cx - rx} ${cy}Z`;
|
|
81
|
+
}
|
|
82
|
+
function pointsPath(points) {
|
|
83
|
+
const n = points.trim().split(/[\s,]+/).map(Number);
|
|
84
|
+
let d = '';
|
|
85
|
+
for (let k = 0; k + 1 < n.length; k += 2)
|
|
86
|
+
d += `${k === 0 ? 'M' : 'L'}${n[k]} ${n[k + 1]}`;
|
|
87
|
+
return d ? `${d}Z` : '';
|
|
88
|
+
}
|
|
89
|
+
/** Mean scale of a matrix: what a stroke width or font size is multiplied by. */
|
|
90
|
+
function lengthScale(m) {
|
|
91
|
+
return Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
92
|
+
}
|
|
93
|
+
/** A path of straight segments only: a line, never a filled shape. */
|
|
94
|
+
const LINE_ONLY_RE = /^[\sMLHVmlhv\d.,eE+-]*$/;
|
|
95
|
+
const BOLD_CLASSES = new Set(['ending', 'fing', 'reh', 'tempo']);
|
|
96
|
+
const ITALIC_CLASSES = new Set(['dir', 'dynam', 'mNum']);
|
|
97
|
+
/** The words of a `<text>`, one run per `<tspan>` that holds any, with the
|
|
98
|
+
* size, style and baseline shift each is set at: a figure raised or
|
|
99
|
+
* lowered beside a chord symbol (V₇, the figures of a figured bass) is a
|
|
100
|
+
* smaller run with a `dy` of its own. */
|
|
101
|
+
function leafRuns(el, inherited, at) {
|
|
102
|
+
const size = el.attrs['font-size'] !== undefined ? num(el.attrs['font-size'], inherited) : inherited;
|
|
103
|
+
const dx = num(el.attrs.dx);
|
|
104
|
+
at.rise += num(el.attrs.dy);
|
|
105
|
+
const own = el.text.replace(/\s+/g, ' ');
|
|
106
|
+
const out = [];
|
|
107
|
+
if (own.trim())
|
|
108
|
+
out.push({ text: own.trim(), size, style: el.attrs['font-style'], weight: el.attrs['font-weight'], rise: at.rise, dx });
|
|
109
|
+
let first = out.length === 0;
|
|
110
|
+
for (const c of el.children) {
|
|
111
|
+
if (c.name !== 'tspan')
|
|
112
|
+
continue;
|
|
113
|
+
for (const r of leafRuns(c, size, at)) {
|
|
114
|
+
out.push({ ...r, style: r.style ?? el.attrs['font-style'], weight: r.weight ?? el.attrs['font-weight'], dx: r.dx + (first ? dx : 0) });
|
|
115
|
+
first = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Flatten a Verovio page SVG into items in px of a slice `widthPx` wide.
|
|
122
|
+
* Returns the items and the slice height, or null when the markup is not
|
|
123
|
+
* Verovio's.
|
|
124
|
+
*/
|
|
125
|
+
export function flattenVerovioSvg(svg, widthPx,
|
|
126
|
+
/** Groups left out by class (`mNum`: bar numbers). */
|
|
127
|
+
options) {
|
|
128
|
+
const skip = new Set(options?.skipClasses ?? []);
|
|
129
|
+
const root = parse(svg);
|
|
130
|
+
if (!root)
|
|
131
|
+
return null;
|
|
132
|
+
const inner = root.children.find((c) => c.name === 'svg') ?? root;
|
|
133
|
+
const vb = (inner.attrs.viewBox ?? '').trim().split(/[\s,]+/).map(Number);
|
|
134
|
+
if (vb.length !== 4 || !(vb[2] > 0) || !(vb[3] > 0))
|
|
135
|
+
return null;
|
|
136
|
+
const s = widthPx / vb[2];
|
|
137
|
+
const base = multiply(scaleMatrix(s, s), [1, 0, 0, 1, -vb[0], -vb[1]]);
|
|
138
|
+
const heightPx = vb[3] * s;
|
|
139
|
+
const defs = new Map();
|
|
140
|
+
const collectDefs = (el) => {
|
|
141
|
+
for (const c of el.children) {
|
|
142
|
+
if (c.name === 'defs') {
|
|
143
|
+
for (const d of c.children)
|
|
144
|
+
if (d.attrs.id)
|
|
145
|
+
defs.set(d.attrs.id, d);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
collectDefs(c);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
collectDefs(root);
|
|
153
|
+
const items = [];
|
|
154
|
+
const pushPath = (d, ctx, el) => {
|
|
155
|
+
if (!d)
|
|
156
|
+
return;
|
|
157
|
+
const fillAttr = el.attrs.fill;
|
|
158
|
+
const sw = el.attrs['stroke-width'] !== undefined ? num(el.attrs['stroke-width']) : ctx.strokeWidth;
|
|
159
|
+
const path = transformPath(d, ctx.matrix);
|
|
160
|
+
const stroked = sw !== undefined && sw > 0;
|
|
161
|
+
const filled = fillAttr !== 'none';
|
|
162
|
+
// Verovio strokes staff lines, ledger lines and bar lines with the
|
|
163
|
+
// stylesheet's `stroke: currentColor`; their `fill` is left to the
|
|
164
|
+
// default black, which on a path of straight lines paints nothing. Slurs
|
|
165
|
+
// and ties are curves both filled and stroked.
|
|
166
|
+
if (filled && !(stroked && el.name === 'path' && LINE_ONLY_RE.test(d))) {
|
|
167
|
+
items.push({ type: 'path', d: path, fill: true });
|
|
168
|
+
}
|
|
169
|
+
if (stroked) {
|
|
170
|
+
items.push({
|
|
171
|
+
type: 'path',
|
|
172
|
+
d: path,
|
|
173
|
+
fill: false,
|
|
174
|
+
strokeWidth: sw * lengthScale(ctx.matrix),
|
|
175
|
+
...(el.attrs['stroke-linecap'] ? { lineCap: el.attrs['stroke-linecap'] } : {}),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const walk = (el, parent) => {
|
|
180
|
+
if (el.name === 'defs' || el.name === 'style' || el.name === 'desc' || el.name === 'title')
|
|
181
|
+
return;
|
|
182
|
+
let matrix = parent.matrix;
|
|
183
|
+
if (el.attrs.transform)
|
|
184
|
+
matrix = multiply(matrix, parseSvgTransform(el.attrs.transform));
|
|
185
|
+
const classes = (el.attrs.class ?? '').split(/\s+/);
|
|
186
|
+
if (classes.some((c) => skip.has(c)))
|
|
187
|
+
return;
|
|
188
|
+
const ctx = {
|
|
189
|
+
matrix,
|
|
190
|
+
bold: parent.bold || classes.some((c) => BOLD_CLASSES.has(c)),
|
|
191
|
+
italic: parent.italic || classes.some((c) => ITALIC_CLASSES.has(c)),
|
|
192
|
+
strokeWidth: el.attrs['stroke-width'] !== undefined ? num(el.attrs['stroke-width']) : parent.strokeWidth,
|
|
193
|
+
};
|
|
194
|
+
switch (el.name) {
|
|
195
|
+
case 'svg':
|
|
196
|
+
if (el !== inner && el !== root) {
|
|
197
|
+
// A nested viewport (Verovio uses none inside a system; skip safely).
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
case 'g':
|
|
202
|
+
break;
|
|
203
|
+
case 'use': {
|
|
204
|
+
const ref = defs.get((el.attrs.href ?? '').replace(/^#/, ''));
|
|
205
|
+
if (!ref)
|
|
206
|
+
return;
|
|
207
|
+
const m = multiply(ctx.matrix, [1, 0, 0, 1, num(el.attrs.x), num(el.attrs.y)]);
|
|
208
|
+
walk(ref, { ...ctx, matrix: m });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
case 'path':
|
|
212
|
+
pushPath(el.attrs.d ?? '', ctx, el);
|
|
213
|
+
return;
|
|
214
|
+
case 'rect':
|
|
215
|
+
pushPath(rectPath(num(el.attrs.x), num(el.attrs.y), num(el.attrs.width), num(el.attrs.height)), ctx, el);
|
|
216
|
+
return;
|
|
217
|
+
case 'polygon':
|
|
218
|
+
pushPath(pointsPath(el.attrs.points ?? ''), ctx, el);
|
|
219
|
+
return;
|
|
220
|
+
case 'polyline': {
|
|
221
|
+
const d = pointsPath(el.attrs.points ?? '').replace(/Z$/, '');
|
|
222
|
+
pushPath(d, { ...ctx, strokeWidth: ctx.strokeWidth ?? 1 }, { ...el, attrs: { ...el.attrs, fill: 'none' } });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
case 'ellipse':
|
|
226
|
+
case 'circle': {
|
|
227
|
+
const rx = num(el.attrs.rx ?? el.attrs.r);
|
|
228
|
+
const ry = num(el.attrs.ry ?? el.attrs.r);
|
|
229
|
+
pushPath(ellipsePath(num(el.attrs.cx), num(el.attrs.cy), rx, ry), ctx, el);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
case 'text': {
|
|
233
|
+
const runs = leafRuns(el, num(el.attrs['font-size'], 0), { rise: 0 }).filter((r) => r.size > 0);
|
|
234
|
+
const text = runs.map((r) => r.text).join('');
|
|
235
|
+
const size = runs[0]?.size ?? 0;
|
|
236
|
+
if (!text || !(size > 0))
|
|
237
|
+
return;
|
|
238
|
+
const [x, y] = [num(el.attrs.x), num(el.attrs.y)];
|
|
239
|
+
const m = ctx.matrix;
|
|
240
|
+
const k = lengthScale(m);
|
|
241
|
+
const anchor = el.attrs['text-anchor'];
|
|
242
|
+
// Runs of one size on one baseline read as one word; others keep
|
|
243
|
+
// their sizes and shifts, laid side by side when the engine can
|
|
244
|
+
// measure them (`layOutRuns`).
|
|
245
|
+
const plain = runs.every((r) => r.size === size && r.rise === 0 && r.dx === 0);
|
|
246
|
+
items.push({
|
|
247
|
+
type: 'text',
|
|
248
|
+
x: m[0] * x + m[2] * y + m[4],
|
|
249
|
+
y: m[1] * x + m[3] * y + m[5],
|
|
250
|
+
text,
|
|
251
|
+
sizePx: size * k,
|
|
252
|
+
bold: ctx.bold || runs.some((r) => r.weight === 'bold'),
|
|
253
|
+
italic: ctx.italic || runs.some((r) => r.style === 'italic'),
|
|
254
|
+
...(anchor === 'middle' || anchor === 'end' ? { anchor } : {}),
|
|
255
|
+
...(plain ? {} : {
|
|
256
|
+
runs: runs.map((r) => ({
|
|
257
|
+
text: r.text,
|
|
258
|
+
sizePx: r.size * k,
|
|
259
|
+
rise: r.rise * k,
|
|
260
|
+
dx: r.dx * k,
|
|
261
|
+
...(r.weight === 'bold' ? { bold: true } : {}),
|
|
262
|
+
...(r.style === 'italic' ? { italic: true } : {}),
|
|
263
|
+
})),
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
default:
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
for (const c of el.children)
|
|
272
|
+
walk(c, ctx);
|
|
273
|
+
};
|
|
274
|
+
const start = { matrix: base, bold: false, italic: false, strokeWidth: undefined };
|
|
275
|
+
for (const c of inner.children)
|
|
276
|
+
walk(c, start);
|
|
277
|
+
return { items, heightPx };
|
|
278
|
+
}
|
|
279
|
+
function esc(s) {
|
|
280
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
281
|
+
}
|
|
282
|
+
const r2 = (n) => String(Math.round(n * 100) / 100);
|
|
283
|
+
/**
|
|
284
|
+
* Plain SVG for a flattened system: paths and text, `currentColor` ink, the
|
|
285
|
+
* text in `textFont`. HTML inlines it; the PDF backend draws it with its SVG
|
|
286
|
+
* vector subset.
|
|
287
|
+
*/
|
|
288
|
+
export function itemsToSvg(items, widthPx, heightPx, color, textFont) {
|
|
289
|
+
const out = [];
|
|
290
|
+
for (const it of items) {
|
|
291
|
+
if (it.type === 'path') {
|
|
292
|
+
out.push(it.fill
|
|
293
|
+
? `<path d="${it.d}" fill="${color}"/>`
|
|
294
|
+
: `<path d="${it.d}" fill="none" stroke="${color}" stroke-width="${r2(it.strokeWidth ?? 1)}"${it.lineCap ? ` stroke-linecap="${it.lineCap}"` : ''}/>`);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
out.push(`<text x="${r2(it.x)}" y="${r2(it.y)}" font-size="${r2(it.sizePx)}"${it.bold ? ' font-weight="bold"' : ''}${it.italic ? ' font-style="italic"' : ''}${it.anchor ? ` text-anchor="${it.anchor}"` : ''} fill="${color}">${esc(it.text)}</text>`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${r2(widthPx)}" height="${r2(heightPx)}" viewBox="0 0 ${r2(widthPx)} ${r2(heightPx)}" font-family="${esc(textFont)}">${out.join('')}</svg>`;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=flatten.js.map
|