@ugfoundation/swaralipi-js 0.2.0 → 0.3.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/{batch-CIAXVKP5.js → batch-QPSHVNDS.js} +4 -4
- package/dist/{batch-CIAXVKP5.js.map → batch-QPSHVNDS.js.map} +1 -1
- package/dist/{chunk-2NBNF6JP.js → chunk-4YTOLN3C.js} +3 -3
- package/dist/{chunk-2NBNF6JP.js.map → chunk-4YTOLN3C.js.map} +1 -1
- package/dist/chunk-V5ZSUJCA.js +354 -0
- package/dist/chunk-V5ZSUJCA.js.map +1 -0
- package/dist/{chunk-SH2INDZ2.js → chunk-VCRWS3F3.js} +3 -3
- package/dist/{chunk-SH2INDZ2.js.map → chunk-VCRWS3F3.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/player/index.cjs +58 -2
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +12 -158
- package/dist/player/index.d.ts +12 -158
- package/dist/player/index.js +31 -324
- package/dist/player/index.js.map +1 -1
- package/dist/schedule-DhO_Xzz4.d.cts +167 -0
- package/dist/schedule-PTHUONrA.d.ts +167 -0
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +1 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +684 -202
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +217 -64
- package/dist/viewer/index.d.ts +217 -64
- package/dist/viewer/index.js +421 -177
- package/dist/viewer/index.js.map +1 -1
- package/package.json +7 -2
package/dist/viewer/index.js
CHANGED
|
@@ -1,84 +1,63 @@
|
|
|
1
|
+
import { buildSchedule, drumPattern } from '../chunk-V5ZSUJCA.js';
|
|
2
|
+
import { TAALS } from '../chunk-H6HC44PZ.js';
|
|
3
|
+
|
|
1
4
|
// src/viewer/glyphs.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
p: { shuddha: "P" },
|
|
27
|
-
d: { shuddha: "D" },
|
|
28
|
-
n: { shuddha: "N" }
|
|
29
|
-
}
|
|
5
|
+
var SWARABITAN_SHUDDHA = {
|
|
6
|
+
s: "s",
|
|
7
|
+
r: "r",
|
|
8
|
+
g: "g",
|
|
9
|
+
m: "m",
|
|
10
|
+
p: "p",
|
|
11
|
+
d: "q",
|
|
12
|
+
n: "n"
|
|
13
|
+
// q = ধ
|
|
14
|
+
};
|
|
15
|
+
var SWARABITAN_KOMAL = {
|
|
16
|
+
r: "v",
|
|
17
|
+
g: "t",
|
|
18
|
+
d: "d",
|
|
19
|
+
n: "u"
|
|
20
|
+
// ঋ জ্ঞ দ ণ
|
|
21
|
+
};
|
|
22
|
+
var SWARABITAN_TIVRA = { m: "k" };
|
|
23
|
+
var BHATKHANDE_OCTAVE = {
|
|
24
|
+
madhyam: "",
|
|
25
|
+
taar: "u",
|
|
26
|
+
mandra: "l",
|
|
27
|
+
"ati-taar": "U",
|
|
28
|
+
"ati-mandra": "L"
|
|
30
29
|
};
|
|
30
|
+
var BHATKHANDE_HAS_KOMAL = /* @__PURE__ */ new Set(["r", "g", "d", "n"]);
|
|
31
31
|
var DIGITS = {
|
|
32
32
|
bn: "\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF",
|
|
33
33
|
hi: "\u0966\u0967\u0968\u0969\u096A\u096B\u096C\u096D\u096E\u096F",
|
|
34
34
|
en: "0123456789"
|
|
35
35
|
};
|
|
36
|
-
var VOWEL_LETTER_GLYPHS = /* @__PURE__ */ new Set(["\u098B", "\u090B"]);
|
|
37
36
|
var MARKS = {
|
|
38
|
-
bn: {
|
|
39
|
-
hi: {
|
|
40
|
-
en: {
|
|
37
|
+
bn: { hyphen: "-", melisma: "\u09F9", cut: "\u0983" },
|
|
38
|
+
hi: { hyphen: "-", melisma: "\u0970", cut: "\u0903" },
|
|
39
|
+
en: { hyphen: "-", melisma: "\xB7", cut: ":" }
|
|
41
40
|
};
|
|
42
41
|
var LABELS = {
|
|
43
|
-
bn: { title: "\u09A8\u09BE\u09AE", taal: "\u09A4\u09BE\u09B2", raag: "\u09B0\u09BE\u0997", avartan: "\u0986\u09AC\u09B0\u09CD\u09A4\u09A8", notationBy: "\u09B8\u09CD\u09AC\u09B0\u09B2\u09BF\u09AA\u09BF" },
|
|
44
|
-
hi: { title: "\u0928\u093E\u092E", taal: "\u0924\u093E\u0932", raag: "\u0930\u093E\u0917", avartan: "\u0906\u0935\u0930\u094D\u0924\u0928", notationBy: "\u0938\u094D\u0935\u0930\u0932\u093F\u092A\u093F" },
|
|
45
|
-
en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Avartan", notationBy: "
|
|
42
|
+
bn: { title: "\u09A8\u09BE\u09AE", taal: "\u09A4\u09BE\u09B2", raag: "\u09B0\u09BE\u0997", avartan: "\u0986\u09AC\u09B0\u09CD\u09A4\u09A8", notationBy: "\u09B8\u09CD\u09AC\u09B0\u09B2\u09BF\u09AA\u09BF\u0995\u09BE\u09B0" },
|
|
43
|
+
hi: { title: "\u0928\u093E\u092E", taal: "\u0924\u093E\u0932", raag: "\u0930\u093E\u0917", avartan: "\u0906\u0935\u0930\u094D\u0924\u0928", notationBy: "\u0938\u094D\u0935\u0930\u0932\u093F\u092A\u093F\u0915\u093E\u0930" },
|
|
44
|
+
en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Avartan", notationBy: "Notator" }
|
|
46
45
|
};
|
|
47
46
|
function composeCluster(letter, opts) {
|
|
48
|
-
const forms = LETTERS[opts.lang][letter];
|
|
49
|
-
const marks = MARKS[opts.lang];
|
|
50
47
|
const classes = [];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
else
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const isVowelLetter = VOWEL_LETTER_GLYPHS.has(base);
|
|
61
|
-
let text = base;
|
|
62
|
-
switch (opts.octave) {
|
|
63
|
-
case "taar":
|
|
64
|
-
case "ati-taar":
|
|
65
|
-
if (marks.reph === "") classes.push("sw-dot-above");
|
|
66
|
-
else if (isVowelLetter) classes.push("sw-reph-css");
|
|
67
|
-
else text = marks.reph + text;
|
|
68
|
-
if (opts.octave === "ati-taar") classes.push("sw-ati-taar");
|
|
69
|
-
break;
|
|
70
|
-
case "mandra":
|
|
71
|
-
case "ati-mandra":
|
|
72
|
-
classes.push(marks.reph === "" ? "sw-dot-below" : "sw-mandra");
|
|
73
|
-
if (opts.octave === "ati-mandra") classes.push("sw-ati-mandra");
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
let matraSeparate = false;
|
|
77
|
-
if (opts.withMatra && marks.matra !== "") {
|
|
78
|
-
if (isVowelLetter) matraSeparate = true;
|
|
79
|
-
else text += marks.matra;
|
|
48
|
+
if (opts.lang === "bn") {
|
|
49
|
+
let text = opts.tivra && SWARABITAN_TIVRA[letter] || opts.komal && SWARABITAN_KOMAL[letter] || SWARABITAN_SHUDDHA[letter];
|
|
50
|
+
if (opts.octave === "taar" || opts.octave === "ati-taar") text += "f";
|
|
51
|
+
else if (opts.octave === "mandra" || opts.octave === "ati-mandra") text += "h";
|
|
52
|
+
if (opts.octave === "ati-taar") classes.push("sw-ati-taar");
|
|
53
|
+
if (opts.octave === "ati-mandra") classes.push("sw-ati-mandra");
|
|
54
|
+
if (opts.withMatra) text += "a";
|
|
55
|
+
return { text, classes };
|
|
80
56
|
}
|
|
81
|
-
|
|
57
|
+
let base = letter;
|
|
58
|
+
if (opts.tivra && letter === "m") base = "M";
|
|
59
|
+
else if (opts.komal && BHATKHANDE_HAS_KOMAL.has(letter)) base = base.toUpperCase();
|
|
60
|
+
return { text: base + BHATKHANDE_OCTAVE[opts.octave], classes };
|
|
82
61
|
}
|
|
83
62
|
function localizeDigits(s, lang) {
|
|
84
63
|
const digits = DIGITS[lang];
|
|
@@ -201,11 +180,8 @@ function bracketGlyphs(note, edge) {
|
|
|
201
180
|
if (!note.brackets) return "";
|
|
202
181
|
return note.brackets.filter((b) => b.edge === edge).map((b) => `<span class="sw-br">${BRACKET_GLYPHS[edge][b.kind]}</span>`).join("");
|
|
203
182
|
}
|
|
204
|
-
function
|
|
205
|
-
return ctx.
|
|
206
|
-
}
|
|
207
|
-
function restGlyph(ctx) {
|
|
208
|
-
return ctx.marks.matra === "" ? '<span class="sw-iso">\u2013</span>' : isoMatra(ctx);
|
|
183
|
+
function matraGlyph(ctx) {
|
|
184
|
+
return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
|
|
209
185
|
}
|
|
210
186
|
function renderKan(note, ctx) {
|
|
211
187
|
if (!note.kan) return "";
|
|
@@ -227,26 +203,24 @@ function renderPitchedInner(note, opts, ctx) {
|
|
|
227
203
|
lang: ctx.lang
|
|
228
204
|
});
|
|
229
205
|
inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
|
|
230
|
-
if (c.matraSeparate) inner += isoMatra(ctx);
|
|
231
206
|
} else if (note.rest === true) {
|
|
232
|
-
inner +=
|
|
207
|
+
inner += matraGlyph(ctx);
|
|
233
208
|
} else if (note.sustain === true && opts.withMatra) {
|
|
234
|
-
inner +=
|
|
209
|
+
inner += matraGlyph(ctx);
|
|
235
210
|
}
|
|
236
211
|
if (note.microtone !== void 0) {
|
|
237
|
-
const mark = note.microtone === "atikomal" ? "
|
|
212
|
+
const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
|
|
238
213
|
inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
|
|
239
214
|
}
|
|
240
215
|
const d = note.durationMatra ?? 1;
|
|
241
|
-
if (isIntegral(d) && d > 1 + EPS) inner +=
|
|
216
|
+
if (isIntegral(d) && d > 1 + EPS) inner += matraGlyph(ctx).repeat(Math.round(d) - 1);
|
|
242
217
|
if (opts.cuts > 0) inner += `<span class="sw-cut">${NBSP}${ctx.marks.cut}</span>`.repeat(opts.cuts);
|
|
243
218
|
inner += bracketGlyphs(note, "close");
|
|
244
219
|
return inner;
|
|
245
220
|
}
|
|
246
|
-
var
|
|
221
|
+
var BAR_GLYPHS = { dari: "A", single: "l", double: "L", quad: "LL" };
|
|
247
222
|
function renderBarInner(note) {
|
|
248
|
-
|
|
249
|
-
return `${bracketGlyphs(note, "open")}${strokes}${bracketGlyphs(note, "close")}`;
|
|
223
|
+
return `${bracketGlyphs(note, "open")}${BAR_GLYPHS[note.bar]}${bracketGlyphs(note, "close")}`;
|
|
250
224
|
}
|
|
251
225
|
function renderAnnotation(notes, ctx) {
|
|
252
226
|
const parts = [];
|
|
@@ -325,7 +299,7 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
325
299
|
}
|
|
326
300
|
if (note.lyric !== void 0) {
|
|
327
301
|
lyric.push(
|
|
328
|
-
`<span class="sw-lyr" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
|
|
302
|
+
`<span class="sw-lyr${j === 0 ? " sw-cs" : ""}" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
|
|
329
303
|
);
|
|
330
304
|
}
|
|
331
305
|
if (note.above !== void 0 && note.above.length > 0) {
|
|
@@ -350,21 +324,24 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
350
324
|
return `<div class="sw-row">${label}<div class="sw-grid">${above.join("")}${notation.join("")}${below.join("")}${lyric.join("")}</div></div>`;
|
|
351
325
|
}
|
|
352
326
|
function renderHeader(meta, ctx) {
|
|
353
|
-
|
|
327
|
+
return renderMetaHeader(meta, ctx.lang, ctx.tr);
|
|
328
|
+
}
|
|
329
|
+
function renderMetaHeader(meta, lang, tr = (s) => s) {
|
|
330
|
+
const L = LABELS[lang];
|
|
354
331
|
const rows = [];
|
|
355
332
|
const add = (label, value) => rows.push(
|
|
356
333
|
`<div class="sw-meta-row"><span class="sw-meta-label">${escapeHtml(label)}</span><span class="sw-meta-value">${escapeHtml(value)}</span></div>`
|
|
357
334
|
);
|
|
358
|
-
add(L.title,
|
|
335
|
+
add(L.title, tr(meta.title));
|
|
359
336
|
const taal = meta.taalName ?? meta.taal?.id;
|
|
360
|
-
if (taal !== void 0) add(L.taal,
|
|
361
|
-
if (meta.raag !== void 0) add(L.raag,
|
|
362
|
-
if (meta.avartan !== void 0) add(L.avartan, localizeDigits(String(meta.avartan),
|
|
363
|
-
if (meta.notationBy !== void 0) add(L.notationBy,
|
|
337
|
+
if (taal !== void 0) add(L.taal, tr(taal));
|
|
338
|
+
if (meta.raag !== void 0) add(L.raag, tr(meta.raag));
|
|
339
|
+
if (meta.avartan !== void 0) add(L.avartan, localizeDigits(String(meta.avartan), lang));
|
|
340
|
+
if (meta.notationBy !== void 0) add(L.notationBy, tr(meta.notationBy));
|
|
364
341
|
return `<header class="sw-header">${rows.join("")}</header>`;
|
|
365
342
|
}
|
|
366
343
|
function renderToHtml(doc, opts = {}) {
|
|
367
|
-
const lang = opts.lang
|
|
344
|
+
const lang = opts.lang === "hi" || opts.lang === "en" ? opts.lang : "bn";
|
|
368
345
|
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
369
346
|
const ctx = { lang, marks: MARKS[lang], tr };
|
|
370
347
|
const out = [`<div class="sw-sheet" lang="${lang}">`];
|
|
@@ -381,13 +358,29 @@ function renderToHtml(doc, opts = {}) {
|
|
|
381
358
|
}
|
|
382
359
|
|
|
383
360
|
// src/viewer/styles.ts
|
|
361
|
+
var NOTATION_FONT_URLS = {
|
|
362
|
+
Swarabitan: "https://webfonts-4qq.pages.dev/swarabitan/Swarabitan.ttf",
|
|
363
|
+
ome_bhatkhande_hindi: "https://webfonts-4qq.pages.dev/ome-bhatkhande/OmeBhatkhandeHindi.ttf",
|
|
364
|
+
ome_bhatkhande_english: "https://webfonts-4qq.pages.dev/ome-bhatkhande/OmeBhatkhandeEnglish.ttf"
|
|
365
|
+
};
|
|
366
|
+
NOTATION_FONT_URLS.Swarabitan;
|
|
367
|
+
var FONT_FACES = Object.entries(NOTATION_FONT_URLS).map(
|
|
368
|
+
([family, url]) => `@font-face { font-family: ${family}; src: url('${url}') format('truetype'); font-display: block; }`
|
|
369
|
+
).join("\n");
|
|
384
370
|
var SWARALIPI_CSS = `
|
|
371
|
+
${FONT_FACES}
|
|
372
|
+
|
|
385
373
|
.sw-sheet {
|
|
386
374
|
font-family: var(--sw-font, 'Noto Sans Bengali', 'Kohinoor Bangla', 'Bangla Sangam MN', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif);
|
|
387
375
|
font-size: var(--sw-size, 1.125rem);
|
|
388
376
|
line-height: 1.5;
|
|
389
377
|
}
|
|
390
378
|
|
|
379
|
+
/* ---- notation font, per language (the note clusters + grace notes) ---- */
|
|
380
|
+
.sw-sheet[lang="bn"] .sw-cl, .sw-sheet[lang="bn"] .sw-kan { font-family: Swarabitan, var(--sw-font, sans-serif); }
|
|
381
|
+
.sw-sheet[lang="hi"] .sw-cl, .sw-sheet[lang="hi"] .sw-kan { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
|
|
382
|
+
.sw-sheet[lang="en"] .sw-cl, .sw-sheet[lang="en"] .sw-kan { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
|
|
383
|
+
|
|
391
384
|
/* ---- header ---- */
|
|
392
385
|
.sw-header {
|
|
393
386
|
margin: 0 0 0.75em;
|
|
@@ -416,10 +409,10 @@ var SWARALIPI_CSS = `
|
|
|
416
409
|
align-items: end;
|
|
417
410
|
}
|
|
418
411
|
.sw-above { grid-row: 1; font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }
|
|
419
|
-
.sw-n { grid-row: 2; white-space: nowrap; }
|
|
412
|
+
.sw-n { grid-row: 2; justify-self: center; white-space: nowrap; }
|
|
420
413
|
.sw-arc,
|
|
421
414
|
.sw-below { grid-row: 3; }
|
|
422
|
-
.sw-lyr { grid-row: 4; white-space: nowrap; padding-top: 0.3em; }
|
|
415
|
+
.sw-lyr { grid-row: 4; justify-self: center; white-space: nowrap; padding-top: 0.3em; }
|
|
423
416
|
.sw-below { font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }
|
|
424
417
|
|
|
425
418
|
/* inter-cell gap as padding on each cell's first notation item, so arcs
|
|
@@ -429,71 +422,21 @@ var SWARALIPI_CSS = `
|
|
|
429
422
|
/* ---- notation clusters & marks ---- */
|
|
430
423
|
.sw-cl { position: relative; display: inline-block; }
|
|
431
424
|
|
|
432
|
-
/*
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
left: 0.1em;
|
|
437
|
-
bottom: -0.08em;
|
|
438
|
-
width: 0.45em;
|
|
439
|
-
height: 0.09em;
|
|
440
|
-
background: currentColor;
|
|
441
|
-
transform: rotate(-32deg);
|
|
442
|
-
}
|
|
443
|
-
/* ati-mandra: an extra dot below */
|
|
425
|
+
/* bn ati-octave: Swarabitan's f/h give a single reph/hasanta, so ati adds
|
|
426
|
+
* one extra dot above (ati-taar) / below (ati-mandra). bn-only \u2014 hi/en get
|
|
427
|
+
* the two-dot glyph natively from the U/L octave suffix. */
|
|
428
|
+
.sw-ati-taar::before,
|
|
444
429
|
.sw-ati-mandra::before {
|
|
445
430
|
content: '';
|
|
446
431
|
position: absolute;
|
|
447
432
|
left: 0.25em;
|
|
448
|
-
bottom: -0.35em;
|
|
449
|
-
width: 0.16em;
|
|
450
|
-
height: 0.16em;
|
|
451
|
-
border-radius: 50%;
|
|
452
|
-
background: currentColor;
|
|
453
|
-
}
|
|
454
|
-
/* ati-taar (reph already in the text): an extra dot above */
|
|
455
|
-
.sw-ati-taar::before {
|
|
456
|
-
content: '';
|
|
457
|
-
position: absolute;
|
|
458
|
-
left: 0.25em;
|
|
459
|
-
top: -0.3em;
|
|
460
433
|
width: 0.16em;
|
|
461
434
|
height: 0.16em;
|
|
462
435
|
border-radius: 50%;
|
|
463
436
|
background: currentColor;
|
|
464
437
|
}
|
|
465
|
-
|
|
466
|
-
.sw-
|
|
467
|
-
content: '';
|
|
468
|
-
position: absolute;
|
|
469
|
-
top: -0.12em;
|
|
470
|
-
left: 0.12em;
|
|
471
|
-
width: 0.3em;
|
|
472
|
-
height: 0.3em;
|
|
473
|
-
border: 0.09em solid currentColor;
|
|
474
|
-
border-right: none;
|
|
475
|
-
border-bottom: none;
|
|
476
|
-
border-top-left-radius: 0.3em;
|
|
477
|
-
}
|
|
478
|
-
/* en octave dots (Bhatkhande-style) */
|
|
479
|
-
.sw-dot-above::before,
|
|
480
|
-
.sw-dot-below::before {
|
|
481
|
-
content: '';
|
|
482
|
-
position: absolute;
|
|
483
|
-
left: 50%;
|
|
484
|
-
margin-left: -0.07em;
|
|
485
|
-
width: 0.14em;
|
|
486
|
-
height: 0.14em;
|
|
487
|
-
border-radius: 50%;
|
|
488
|
-
background: currentColor;
|
|
489
|
-
}
|
|
490
|
-
.sw-dot-above::before { top: -0.25em; }
|
|
491
|
-
.sw-dot-below::before { bottom: -0.2em; }
|
|
492
|
-
.sw-ati-taar.sw-dot-above::before,
|
|
493
|
-
.sw-ati-mandra.sw-dot-below::before { box-shadow: 0.24em 0 0 currentColor; margin-left: -0.19em; }
|
|
494
|
-
/* en accidentals (bn/hi use distinct letters instead) */
|
|
495
|
-
.sw-komal { text-decoration: underline; text-underline-offset: 0.18em; }
|
|
496
|
-
.sw-tivra::after { content: '\\2032'; font-size: 0.7em; vertical-align: super; }
|
|
438
|
+
.sw-ati-taar::before { top: -0.3em; }
|
|
439
|
+
.sw-ati-mandra::before { bottom: -0.35em; }
|
|
497
440
|
|
|
498
441
|
/* kan grace: small raised cluster left of the main note */
|
|
499
442
|
.sw-kan {
|
|
@@ -510,29 +453,25 @@ var SWARALIPI_CSS = `
|
|
|
510
453
|
.sw-br { opacity: 0.8; padding: 0 0.06em; }
|
|
511
454
|
.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
|
|
512
455
|
|
|
513
|
-
/* raw fallback: never dropped, visibly non-notation
|
|
456
|
+
/* raw fallback: never dropped, visibly non-notation. Swarabitan first \u2014
|
|
457
|
+
* raw IS that font's encoding, so undecoded cells show NLTR's own glyphs
|
|
458
|
+
* (dimmed: semantics unverified); monospace ASCII when the font is out. */
|
|
514
459
|
.sw-raw {
|
|
515
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
460
|
+
font-family: Swarabitan, ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
516
461
|
font-size: 0.72em;
|
|
517
462
|
opacity: 0.55;
|
|
518
463
|
}
|
|
519
464
|
|
|
520
|
-
/* ---- bars: dari (
|
|
465
|
+
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs ---- */
|
|
521
466
|
/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014
|
|
522
467
|
* never reset it with a padding shorthand here */
|
|
523
468
|
.sw-bar {
|
|
524
469
|
grid-row: 2;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
align-items: stretch;
|
|
528
|
-
gap: 0.16em;
|
|
470
|
+
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
471
|
+
white-space: nowrap;
|
|
529
472
|
padding-right: 0.1em;
|
|
530
473
|
}
|
|
531
|
-
.sw-bar
|
|
532
|
-
.sw-bar--dari i { width: 0.05em; opacity: 0.9; }
|
|
533
|
-
.sw-bar--quad i:nth-child(3) { margin-left: 0.18em; }
|
|
534
|
-
.sw-above .sw-bar, .sw-below .sw-bar { display: inline-flex; vertical-align: baseline; }
|
|
535
|
-
.sw-above .sw-bar i, .sw-below .sw-bar i { min-height: 1em; }
|
|
474
|
+
.sw-bar--quad { letter-spacing: 0.15em; }
|
|
536
475
|
|
|
537
476
|
/* ---- under-arcs (slurs decoded from w/x segments and meend) ---- */
|
|
538
477
|
.sw-arc {
|
|
@@ -572,6 +511,9 @@ var SWARALIPI_CSS = `
|
|
|
572
511
|
|
|
573
512
|
/* ---- player sync (P2.3) ---- */
|
|
574
513
|
.sw-hl { background: var(--sw-hl, rgba(255, 196, 64, 0.45)); border-radius: 0.15em; }
|
|
514
|
+
/* Staff notes are SVG <g> \u2014 background doesn't paint there, so recolour the
|
|
515
|
+
glyph fill/stroke instead (abcjs draws note heads/stems as filled paths). */
|
|
516
|
+
.sw-staff .sw-hl, .sw-staff .sw-hl * { fill: var(--sw-hl-note, #d68400); stroke: var(--sw-hl-note, #d68400); }
|
|
575
517
|
|
|
576
518
|
/* ---- element error box ---- */
|
|
577
519
|
.sw-error {
|
|
@@ -587,27 +529,270 @@ var SWARALIPI_CSS = `
|
|
|
587
529
|
var SWARALIPI_PRINT_CSS = `
|
|
588
530
|
@page { margin: 16mm; }
|
|
589
531
|
@media print {
|
|
590
|
-
.sw-sheet { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
|
|
532
|
+
.sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
|
|
591
533
|
.sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }
|
|
592
534
|
.sw-part { break-inside: auto; }
|
|
593
535
|
.sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
|
|
594
536
|
.sw-hl { background: none !important; }
|
|
537
|
+
/* Keep each engraved staff system whole across a page break (the abcjs line
|
|
538
|
+
is a <div> wrapping the <svg>, so the break rule must sit on the div). */
|
|
539
|
+
.sw-staff-lines > div { break-inside: avoid; page-break-inside: avoid; }
|
|
540
|
+
.sw-staff-meta { break-after: avoid-page; page-break-after: avoid; }
|
|
595
541
|
}
|
|
596
542
|
`;
|
|
543
|
+
var SWARALIPI_STAFF_CSS = `
|
|
544
|
+
.sw-staff { color: currentColor; }
|
|
545
|
+
.sw-staff-meta { display: flex; gap: 1.25em; align-items: baseline; margin: 0 0 .5em; font-size: .95em; opacity: .85; }
|
|
546
|
+
.sw-staff-instrument { font-weight: 600; }
|
|
547
|
+
.sw-staff-drums { opacity: .75; }
|
|
548
|
+
.sw-staff-tempo { font-variant-numeric: tabular-nums; }
|
|
549
|
+
/* abcjs (oneSvgPerLine) wraps the music into one <div><svg> per system; stack
|
|
550
|
+
them as short, swaralipi-style lines. abcjs sets a fixed pixel height +
|
|
551
|
+
overflow:hidden on each wrapper div \u2014 neutralise it so the viewBox'd SVG can
|
|
552
|
+
scale to the container (and to the print page) instead of being clipped. */
|
|
553
|
+
.sw-staff-lines > div { margin: 0 0 .35em; height: auto !important; overflow: visible !important; }
|
|
554
|
+
/* Fill up to the engraved (staffwidth) size \u2014 shrink to fit a narrow container,
|
|
555
|
+
but never stretch past natural width (abcToSvg pins per-SVG max-width in px),
|
|
556
|
+
else a short last system balloons. */
|
|
557
|
+
.sw-staff-lines svg { display: block; width: 100%; height: auto; }
|
|
558
|
+
.sw-staff-missing { padding: 1em; border: 1px dashed currentColor; border-radius: 6px; opacity: .8; font-size: .9em; }
|
|
559
|
+
.sw-staff-title { font-size: 1.15em; font-weight: 700; }
|
|
560
|
+
.sw-staff-subtitle { opacity: .8; font-size: .95em; margin-top: .15em; }
|
|
561
|
+
`;
|
|
562
|
+
|
|
563
|
+
// src/viewer/staff.ts
|
|
564
|
+
var ABC_SHARP = ["C", "^C", "D", "^D", "E", "F", "^F", "G", "^G", "A", "^A", "B"];
|
|
565
|
+
function midiToAbc(midi) {
|
|
566
|
+
const pc = (midi % 12 + 12) % 12;
|
|
567
|
+
const octave = Math.floor(midi / 12) - 1;
|
|
568
|
+
const token = ABC_SHARP[pc];
|
|
569
|
+
const acc = token[0] === "^" ? "^" : "";
|
|
570
|
+
const letter = acc ? token[1] : token[0];
|
|
571
|
+
return octave >= 5 ? acc + letter.toLowerCase() + "'".repeat(octave - 5) : acc + letter + ",".repeat(4 - octave);
|
|
572
|
+
}
|
|
573
|
+
function abcDuration(beats) {
|
|
574
|
+
if (Math.abs(beats - 1) < 1e-6) return "";
|
|
575
|
+
if (Math.abs(beats - Math.round(beats)) < 1e-6) return String(Math.round(beats));
|
|
576
|
+
const inv = 1 / beats;
|
|
577
|
+
if (Math.abs(inv - Math.round(inv)) < 1e-6) return `/${Math.round(inv)}`;
|
|
578
|
+
const sixteenths = Math.max(1, Math.round(beats * 4));
|
|
579
|
+
return `${sixteenths}/4`;
|
|
580
|
+
}
|
|
581
|
+
function abcNote(ev) {
|
|
582
|
+
const grace = ev.kan !== null ? `{${midiToAbc(ev.kan)}}` : "";
|
|
583
|
+
return `${grace}${midiToAbc(ev.midi)}${abcDuration(ev.durationBeats)}`;
|
|
584
|
+
}
|
|
585
|
+
function eventPaths(schedule) {
|
|
586
|
+
const paths = [];
|
|
587
|
+
for (const ev of schedule.events) {
|
|
588
|
+
if (ev.midi !== null || ev.kind === "rest") paths.push(ev.path);
|
|
589
|
+
}
|
|
590
|
+
return paths;
|
|
591
|
+
}
|
|
592
|
+
function abcText(s) {
|
|
593
|
+
return s.replace(/[\r\n]+/g, " ").trim();
|
|
594
|
+
}
|
|
595
|
+
function buildAbc(schedule, lyricOf, opts) {
|
|
596
|
+
const beatsPerBar = opts.beatsPerBar > 0 ? opts.beatsPerBar : 4;
|
|
597
|
+
const tokens = [];
|
|
598
|
+
const words = [];
|
|
599
|
+
let barPos = 0;
|
|
600
|
+
const EPS2 = 1e-6;
|
|
601
|
+
const advance = (dur) => {
|
|
602
|
+
barPos += dur;
|
|
603
|
+
while (barPos >= beatsPerBar - EPS2) {
|
|
604
|
+
tokens.push("|");
|
|
605
|
+
words.push("|");
|
|
606
|
+
barPos -= beatsPerBar;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
for (const ev of schedule.events) {
|
|
610
|
+
if (ev.midi === null) {
|
|
611
|
+
if (ev.kind === "rest") {
|
|
612
|
+
tokens.push(`z${abcDuration(ev.durationBeats)}`);
|
|
613
|
+
words.push("*");
|
|
614
|
+
advance(ev.durationBeats);
|
|
615
|
+
}
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
tokens.push(abcNote(ev));
|
|
619
|
+
const lyr = lyricOf(ev);
|
|
620
|
+
words.push(lyr ? abcText(lyr).replace(/\s+/g, "~") : "*");
|
|
621
|
+
advance(ev.durationBeats);
|
|
622
|
+
}
|
|
623
|
+
if (tokens.length && tokens[tokens.length - 1] !== "|") tokens.push("|]");
|
|
624
|
+
else if (tokens.length) tokens[tokens.length - 1] = "|]";
|
|
625
|
+
const meter = ratioMeter(beatsPerBar);
|
|
626
|
+
const head = ["X:1", `M:${meter}`, "L:1/4", `Q:1/4=${Math.round(opts.bpm)}`];
|
|
627
|
+
if (opts.drums) head.push(...percMapLines(opts.drums));
|
|
628
|
+
head.push("K:C treble");
|
|
629
|
+
const melodyBody = tokens.length ? tokens.join(" ") : "z |]";
|
|
630
|
+
const lines = [...head];
|
|
631
|
+
if (opts.drums) {
|
|
632
|
+
lines.push("V:1", melodyBody);
|
|
633
|
+
if (opts.lyrics && words.some((w) => w !== "*" && w !== "|")) lines.push(`w: ${words.join(" ")}`);
|
|
634
|
+
lines.push("V:2 clef=perc stem=up", buildDrumVoice(schedule.totalBeats, beatsPerBar, opts.drums));
|
|
635
|
+
} else {
|
|
636
|
+
lines.push(melodyBody);
|
|
637
|
+
if (opts.lyrics && words.some((w) => w !== "*" && w !== "|")) lines.push(`w: ${words.join(" ")}`);
|
|
638
|
+
}
|
|
639
|
+
return lines.join("\n");
|
|
640
|
+
}
|
|
641
|
+
function ratioMeter(beats) {
|
|
642
|
+
return `${Math.max(1, Math.round(beats))}/4`;
|
|
643
|
+
}
|
|
644
|
+
var DRUM_ABC = {
|
|
645
|
+
kick: { note: "F,", gm: "bass-drum-1" },
|
|
646
|
+
snare: { note: "C", gm: "acoustic-snare" },
|
|
647
|
+
hihat: { note: "g", gm: "closed-hi-hat" },
|
|
648
|
+
tom1: { note: "E", gm: "high-tom" },
|
|
649
|
+
tom2: { note: "A,", gm: "low-mid-tom" },
|
|
650
|
+
tom3: { note: "D,", gm: "low-floor-tom" }
|
|
651
|
+
};
|
|
652
|
+
function percMapLines(voices) {
|
|
653
|
+
const seen = /* @__PURE__ */ new Set();
|
|
654
|
+
const lines = [];
|
|
655
|
+
for (const v of voices) {
|
|
656
|
+
if (seen.has(v)) continue;
|
|
657
|
+
seen.add(v);
|
|
658
|
+
const { note, gm } = DRUM_ABC[v];
|
|
659
|
+
lines.push(`%%percmap ${note} ${gm}`);
|
|
660
|
+
}
|
|
661
|
+
return lines;
|
|
662
|
+
}
|
|
663
|
+
function buildDrumVoice(totalBeats, beatsPerBar, pattern) {
|
|
664
|
+
const n = Math.max(1, Math.ceil(totalBeats));
|
|
665
|
+
const tokens = [];
|
|
666
|
+
for (let beat = 0; beat < n; beat++) {
|
|
667
|
+
tokens.push(DRUM_ABC[pattern[beat % pattern.length]].note);
|
|
668
|
+
if ((beat + 1) % beatsPerBar === 0) tokens.push("|");
|
|
669
|
+
}
|
|
670
|
+
if (tokens.length === 0 || tokens[tokens.length - 1] !== "|") tokens.push("|]");
|
|
671
|
+
else tokens[tokens.length - 1] = "|]";
|
|
672
|
+
return tokens.join(" ");
|
|
673
|
+
}
|
|
674
|
+
function lyricLookup(doc) {
|
|
675
|
+
return (ev) => {
|
|
676
|
+
const [p, r, i] = ev.path.split(".").map(Number);
|
|
677
|
+
return doc.parts[p]?.rows[r]?.notes[i]?.lyric;
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function resolveDrumTaal(doc) {
|
|
681
|
+
const id = doc.meta.taal?.id;
|
|
682
|
+
return id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 };
|
|
683
|
+
}
|
|
684
|
+
function docToAbc(doc, opts = {}) {
|
|
685
|
+
const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave });
|
|
686
|
+
const bpm = opts.bpm ?? doc.meta.bpm ?? 90;
|
|
687
|
+
const beatsPerBar = opts.beatsPerBar ?? doc.meta.taal?.numBeats ?? 4;
|
|
688
|
+
const drums = opts.drums ? drumPattern(resolveDrumTaal(doc)) : void 0;
|
|
689
|
+
return buildAbc(schedule, lyricLookup(doc), { bpm, lyrics: opts.lyrics !== false, beatsPerBar, drums });
|
|
690
|
+
}
|
|
691
|
+
async function abcToSvg(abc, opts = {}, notePaths) {
|
|
692
|
+
if (typeof document === "undefined") {
|
|
693
|
+
throw new Error("abcToSvg requires a DOM; render staff notation in the browser or print iframe");
|
|
694
|
+
}
|
|
695
|
+
let ABCJS;
|
|
696
|
+
try {
|
|
697
|
+
const mod = await import('abcjs');
|
|
698
|
+
ABCJS = mod.default ?? mod;
|
|
699
|
+
} catch {
|
|
700
|
+
return `<div class="sw-staff-missing">Staff notation needs the optional <code>abcjs</code> package.</div>`;
|
|
701
|
+
}
|
|
702
|
+
const host = document.createElement("div");
|
|
703
|
+
host.style.cssText = "position:fixed; left:-10000px; top:0; width:720px; visibility:hidden;";
|
|
704
|
+
const mount = document.body ?? document.documentElement;
|
|
705
|
+
mount.appendChild(host);
|
|
706
|
+
try {
|
|
707
|
+
ABCJS.renderAbc(host, abc, {
|
|
708
|
+
add_classes: true,
|
|
709
|
+
staffwidth: opts.staffwidth ?? 680,
|
|
710
|
+
scale: opts.scale ?? 1,
|
|
711
|
+
// Reflow into short lines. staffwidth is the width budget;
|
|
712
|
+
// preferredMeasuresPerLine keeps systems swaralipi-short.
|
|
713
|
+
// (Not `responsive: resize` — its absolute-positioned aspect-ratio wrapper
|
|
714
|
+
// overlaps the header and mis-sizes on the print page.)
|
|
715
|
+
wrap: { minSpacing: 1.8, maxSpacing: 2.7, preferredMeasuresPerLine: opts.measuresPerLine ?? 4 },
|
|
716
|
+
// CRUCIAL: put each wrapped system in its OWN <svg> (default is all systems
|
|
717
|
+
// stacked in one tall SVG, which is atomic and can't page-break — that was
|
|
718
|
+
// the near-blank-first-page / mid-staff-cutoff print bug). One SVG per line
|
|
719
|
+
// lets `break-inside: avoid` keep each staff whole while pages fill.
|
|
720
|
+
oneSvgPerLine: true
|
|
721
|
+
});
|
|
722
|
+
for (const svg of host.querySelectorAll("svg")) {
|
|
723
|
+
const w = parseFloat(svg.getAttribute("width") ?? "");
|
|
724
|
+
const h = parseFloat(svg.getAttribute("height") ?? "");
|
|
725
|
+
if (w > 0 && h > 0) {
|
|
726
|
+
if (!svg.getAttribute("viewBox")) svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
727
|
+
svg.setAttribute("preserveAspectRatio", "xMinYMin meet");
|
|
728
|
+
svg.removeAttribute("width");
|
|
729
|
+
svg.removeAttribute("height");
|
|
730
|
+
svg.setAttribute("style", `max-width:${w}px`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (notePaths) {
|
|
734
|
+
const melody = host.querySelectorAll(".abcjs-v0.abcjs-note, .abcjs-v0.abcjs-rest");
|
|
735
|
+
let i = 0;
|
|
736
|
+
for (const el of melody) {
|
|
737
|
+
const path = notePaths[i++];
|
|
738
|
+
if (path !== void 0) el.setAttribute("data-note", path);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return host.innerHTML;
|
|
742
|
+
} finally {
|
|
743
|
+
host.remove();
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function renderStaffHtml(doc, opts = {}) {
|
|
747
|
+
const bpm = opts.bpm ?? doc.meta.bpm ?? 90;
|
|
748
|
+
const abc = docToAbc(doc, { ...opts, bpm });
|
|
749
|
+
const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave });
|
|
750
|
+
const engraved = await abcToSvg(abc, opts, eventPaths(schedule));
|
|
751
|
+
const lines = engraved.includes("sw-staff-missing") ? engraved : `<div class="sw-staff-lines">${engraved}</div>`;
|
|
752
|
+
const header = opts.showHeader === false ? "" : renderMetaHeader(doc.meta, opts.headerLang ?? "en", opts.transliterate);
|
|
753
|
+
const metaParts = [];
|
|
754
|
+
if (opts.instrument) metaParts.push(`<span class="sw-staff-instrument">${escapeAttr(opts.instrument)}</span>`);
|
|
755
|
+
if (opts.drums) metaParts.push(`<span class="sw-staff-drums">+ drums</span>`);
|
|
756
|
+
const meta = metaParts.length ? `<div class="sw-staff-meta">${metaParts.join("")}</div>` : "";
|
|
757
|
+
return `<div class="sw-staff">${header}${meta}${lines}</div>`;
|
|
758
|
+
}
|
|
759
|
+
function escapeAttr(s) {
|
|
760
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
761
|
+
}
|
|
597
762
|
|
|
598
763
|
// src/viewer/print.ts
|
|
764
|
+
var DEFAULT_WATERMARK = "www.gitabitan.io";
|
|
765
|
+
var DEFAULT_NOTATOR = "gitabitan.io Team";
|
|
599
766
|
function escapeHtml2(s) {
|
|
600
767
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
601
768
|
}
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
769
|
+
function watermarkCss(text) {
|
|
770
|
+
if (!text) return "";
|
|
771
|
+
return `
|
|
772
|
+
.sw-watermark {
|
|
773
|
+
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
|
774
|
+
display: flex; align-items: center; justify-content: center;
|
|
775
|
+
overflow: hidden;
|
|
776
|
+
-webkit-print-color-adjust: exact; print-color-adjust: exact;
|
|
777
|
+
}
|
|
778
|
+
.sw-watermark span {
|
|
779
|
+
font-size: 11vw; font-weight: 700; letter-spacing: .08em;
|
|
780
|
+
color: rgba(0,0,0,0.07); transform: rotate(-35deg); white-space: nowrap;
|
|
781
|
+
}
|
|
782
|
+
body > .sw-sheet, body > .sw-footer { position: relative; z-index: 1; }
|
|
783
|
+
`;
|
|
784
|
+
}
|
|
785
|
+
function chrome(text, notator) {
|
|
786
|
+
const top = text ? `<div class="sw-watermark" aria-hidden="true"><span>${escapeHtml2(text)}</span></div>` : "";
|
|
787
|
+
const bottom = notator ? `<footer class="sw-footer">${escapeHtml2(notator)}</footer>` : "";
|
|
788
|
+
return { top, bottom };
|
|
789
|
+
}
|
|
790
|
+
function resolveNotator(doc, opts) {
|
|
791
|
+
return doc.meta.notationBy ?? opts.notator ?? DEFAULT_NOTATOR;
|
|
792
|
+
}
|
|
793
|
+
function pageHtml(lang, title, bodyHtml, opts, doc) {
|
|
794
|
+
const watermark = opts.watermark ?? DEFAULT_WATERMARK;
|
|
795
|
+
const { top, bottom } = chrome(watermark, resolveNotator(doc, opts));
|
|
611
796
|
return `<!doctype html>
|
|
612
797
|
<html lang="${escapeHtml2(lang)}">
|
|
613
798
|
<head>
|
|
@@ -616,20 +801,42 @@ function renderPrintDocument(doc, opts = {}) {
|
|
|
616
801
|
<title>${escapeHtml2(title)}</title>
|
|
617
802
|
<style>
|
|
618
803
|
${SWARALIPI_CSS}
|
|
804
|
+
${SWARALIPI_STAFF_CSS}
|
|
619
805
|
${SWARALIPI_PRINT_CSS}
|
|
806
|
+
${watermarkCss(watermark)}
|
|
620
807
|
html, body { margin: 0; }
|
|
621
808
|
body { color: #111; background: #fff; padding: 16px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
622
809
|
.sw-sheet { max-width: 100%; }
|
|
810
|
+
.sw-footer { margin-top: 12px; padding-top: 6px; border-top: 1px solid #ddd; font-size: 12px; color: #555; text-align: right; }
|
|
623
811
|
</style>
|
|
624
812
|
</head>
|
|
625
|
-
<body>${
|
|
813
|
+
<body>${top}${bodyHtml}${bottom}</body>
|
|
626
814
|
</html>`;
|
|
627
815
|
}
|
|
816
|
+
function renderPrintDocument(doc, opts = {}) {
|
|
817
|
+
const lang = opts.lang === "staff" ? "bn" : opts.lang ?? "bn";
|
|
818
|
+
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
819
|
+
const title = opts.title ?? tr(doc.meta.title);
|
|
820
|
+
const body = renderToHtml(doc, { lang, showHeader: opts.showHeader, transliterate: opts.transliterate });
|
|
821
|
+
return pageHtml(lang, title, body, opts, doc);
|
|
822
|
+
}
|
|
823
|
+
async function renderStaffPrintDocument(doc, opts = {}) {
|
|
824
|
+
if (opts.lang !== "staff") return renderPrintDocument(doc, opts);
|
|
825
|
+
const title = opts.title ?? doc.meta.title;
|
|
826
|
+
const staff = await renderStaffHtml(doc, opts);
|
|
827
|
+
return pageHtml("en", title, staff, opts, doc);
|
|
828
|
+
}
|
|
628
829
|
function printSwaralipi(doc, opts = {}) {
|
|
629
830
|
if (typeof document === "undefined") {
|
|
630
831
|
throw new Error("printSwaralipi requires a DOM; use renderPrintDocument() for server-side PDF");
|
|
631
832
|
}
|
|
632
|
-
|
|
833
|
+
if (opts.lang === "staff") {
|
|
834
|
+
void renderStaffPrintDocument(doc, opts).then(printHtmlInIframe);
|
|
835
|
+
} else {
|
|
836
|
+
printHtmlInIframe(renderPrintDocument(doc, opts));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function printHtmlInIframe(html) {
|
|
633
840
|
const iframe = document.createElement("iframe");
|
|
634
841
|
iframe.setAttribute("aria-hidden", "true");
|
|
635
842
|
iframe.style.cssText = "position:fixed; left:-10000px; top:0; width:800px; height:600px; border:0;";
|
|
@@ -641,7 +848,10 @@ function printSwaralipi(doc, opts = {}) {
|
|
|
641
848
|
}
|
|
642
849
|
const cleanup = () => setTimeout(() => iframe.remove(), 1e3);
|
|
643
850
|
win.addEventListener("afterprint", cleanup, { once: true });
|
|
851
|
+
let printed = false;
|
|
644
852
|
const go = () => {
|
|
853
|
+
if (printed) return;
|
|
854
|
+
printed = true;
|
|
645
855
|
try {
|
|
646
856
|
win.focus();
|
|
647
857
|
win.print();
|
|
@@ -649,8 +859,14 @@ function printSwaralipi(doc, opts = {}) {
|
|
|
649
859
|
}
|
|
650
860
|
};
|
|
651
861
|
const fonts = iframe.contentDocument?.fonts;
|
|
652
|
-
if (fonts
|
|
653
|
-
|
|
862
|
+
if (fonts && typeof fonts.forEach === "function") {
|
|
863
|
+
const loads = [Promise.resolve(fonts.ready)];
|
|
864
|
+
fonts.forEach((face) => loads.push(face.load().catch(() => void 0)));
|
|
865
|
+
void Promise.all(loads).then(go);
|
|
866
|
+
setTimeout(go, 3e3);
|
|
867
|
+
} else {
|
|
868
|
+
setTimeout(go, 250);
|
|
869
|
+
}
|
|
654
870
|
};
|
|
655
871
|
iframe.srcdoc = html;
|
|
656
872
|
document.body.appendChild(iframe);
|
|
@@ -662,7 +878,7 @@ var TARGET = {
|
|
|
662
878
|
en: "iast"
|
|
663
879
|
};
|
|
664
880
|
async function loadLyricTransliterator(lang) {
|
|
665
|
-
if (lang === "bn") return void 0;
|
|
881
|
+
if (lang === "bn" || lang === "staff") return void 0;
|
|
666
882
|
const mod = await import('@indic-transliteration/sanscript');
|
|
667
883
|
const sanscript = mod.default ?? mod;
|
|
668
884
|
const target = TARGET[lang];
|
|
@@ -670,7 +886,16 @@ async function loadLyricTransliterator(lang) {
|
|
|
670
886
|
}
|
|
671
887
|
|
|
672
888
|
// src/viewer/element.ts
|
|
673
|
-
var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en"]);
|
|
889
|
+
var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en", "staff"]);
|
|
890
|
+
function ensureNotationFonts() {
|
|
891
|
+
if (typeof FontFace === "undefined" || typeof document === "undefined") return;
|
|
892
|
+
const present = /* @__PURE__ */ new Set();
|
|
893
|
+
for (const f of document.fonts) present.add(f.family.replace(/['"]/g, ""));
|
|
894
|
+
for (const [family, url] of Object.entries(NOTATION_FONT_URLS)) {
|
|
895
|
+
if (present.has(family)) continue;
|
|
896
|
+
document.fonts.add(new FontFace(family, `url(${url})`, { display: "block" }));
|
|
897
|
+
}
|
|
898
|
+
}
|
|
674
899
|
var sharedSheet;
|
|
675
900
|
function createElementClass() {
|
|
676
901
|
return class SwaralipiSheet extends HTMLElement {
|
|
@@ -681,6 +906,8 @@ function createElementClass() {
|
|
|
681
906
|
#abort;
|
|
682
907
|
#translit;
|
|
683
908
|
#translitLang;
|
|
909
|
+
#renderSeq = 0;
|
|
910
|
+
#staffOptions = {};
|
|
684
911
|
validate = false;
|
|
685
912
|
constructor() {
|
|
686
913
|
super();
|
|
@@ -688,7 +915,7 @@ function createElementClass() {
|
|
|
688
915
|
if (typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && "adoptedStyleSheets" in this.#root) {
|
|
689
916
|
if (!sharedSheet) {
|
|
690
917
|
sharedSheet = new CSSStyleSheet();
|
|
691
|
-
sharedSheet.replaceSync(SWARALIPI_CSS);
|
|
918
|
+
sharedSheet.replaceSync(SWARALIPI_CSS + SWARALIPI_STAFF_CSS);
|
|
692
919
|
}
|
|
693
920
|
this.#root.adoptedStyleSheets = [...this.#root.adoptedStyleSheets, sharedSheet];
|
|
694
921
|
this.#adopted = true;
|
|
@@ -707,6 +934,13 @@ function createElementClass() {
|
|
|
707
934
|
}
|
|
708
935
|
this.#render();
|
|
709
936
|
}
|
|
937
|
+
get staffOptions() {
|
|
938
|
+
return this.#staffOptions;
|
|
939
|
+
}
|
|
940
|
+
set staffOptions(value) {
|
|
941
|
+
this.#staffOptions = value;
|
|
942
|
+
if (this.#lang === "staff") this.#render();
|
|
943
|
+
}
|
|
710
944
|
get #lang() {
|
|
711
945
|
const attr = this.getAttribute("lang");
|
|
712
946
|
return attr !== null && LANGS.has(attr) ? attr : "bn";
|
|
@@ -753,14 +987,22 @@ function createElementClass() {
|
|
|
753
987
|
}).catch(() => void 0);
|
|
754
988
|
}
|
|
755
989
|
#styleFallback() {
|
|
756
|
-
return this.#adopted ? "" : `<style>${SWARALIPI_CSS}</style>`;
|
|
990
|
+
return this.#adopted ? "" : `<style>${SWARALIPI_CSS}${SWARALIPI_STAFF_CSS}</style>`;
|
|
757
991
|
}
|
|
758
992
|
#render() {
|
|
993
|
+
const seq = ++this.#renderSeq;
|
|
759
994
|
if (this.#doc === void 0) {
|
|
760
995
|
this.#root.innerHTML = this.#styleFallback();
|
|
761
996
|
return;
|
|
762
997
|
}
|
|
763
998
|
const lang = this.#lang;
|
|
999
|
+
if (lang === "staff") {
|
|
1000
|
+
const doc = this.#doc;
|
|
1001
|
+
void renderStaffHtml(doc, this.staffOptions).then((html2) => {
|
|
1002
|
+
if (seq === this.#renderSeq) this.#root.innerHTML = this.#styleFallback() + html2;
|
|
1003
|
+
}).catch(() => void 0);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
764
1006
|
const html = renderToHtml(this.#doc, {
|
|
765
1007
|
lang,
|
|
766
1008
|
showHeader: !this.hasAttribute("hide-header"),
|
|
@@ -791,6 +1033,7 @@ function createElementClass() {
|
|
|
791
1033
|
lang,
|
|
792
1034
|
showHeader: !this.hasAttribute("hide-header"),
|
|
793
1035
|
transliterate: this.#translitLang === lang ? this.#translit : void 0,
|
|
1036
|
+
...lang === "staff" ? this.staffOptions : {},
|
|
794
1037
|
...options
|
|
795
1038
|
});
|
|
796
1039
|
}
|
|
@@ -803,12 +1046,13 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
|
|
|
803
1046
|
}
|
|
804
1047
|
const existing = customElements.get(tagName);
|
|
805
1048
|
if (existing) return existing;
|
|
1049
|
+
ensureNotationFonts();
|
|
806
1050
|
const ctor = registered.get(tagName) ?? createElementClass();
|
|
807
1051
|
registered.set(tagName, ctor);
|
|
808
1052
|
customElements.define(tagName, ctor);
|
|
809
1053
|
return ctor;
|
|
810
1054
|
}
|
|
811
1055
|
|
|
812
|
-
export { DIGITS, LABELS,
|
|
1056
|
+
export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, abcDuration, abcToSvg, buildAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderToHtml, resolveArcs };
|
|
813
1057
|
//# sourceMappingURL=index.js.map
|
|
814
1058
|
//# sourceMappingURL=index.js.map
|