@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.
Files changed (37) hide show
  1. package/dist/{batch-CIAXVKP5.js → batch-QPSHVNDS.js} +4 -4
  2. package/dist/{batch-CIAXVKP5.js.map → batch-QPSHVNDS.js.map} +1 -1
  3. package/dist/{chunk-2NBNF6JP.js → chunk-4YTOLN3C.js} +3 -3
  4. package/dist/{chunk-2NBNF6JP.js.map → chunk-4YTOLN3C.js.map} +1 -1
  5. package/dist/chunk-V5ZSUJCA.js +354 -0
  6. package/dist/chunk-V5ZSUJCA.js.map +1 -0
  7. package/dist/{chunk-SH2INDZ2.js → chunk-VCRWS3F3.js} +3 -3
  8. package/dist/{chunk-SH2INDZ2.js.map → chunk-VCRWS3F3.js.map} +1 -1
  9. package/dist/cli.cjs +1 -1
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +2 -2
  12. package/dist/convert/index.cjs +1 -1
  13. package/dist/convert/index.cjs.map +1 -1
  14. package/dist/convert/index.js +2 -2
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/player/index.cjs +58 -2
  19. package/dist/player/index.cjs.map +1 -1
  20. package/dist/player/index.d.cts +12 -158
  21. package/dist/player/index.d.ts +12 -158
  22. package/dist/player/index.js +31 -324
  23. package/dist/player/index.js.map +1 -1
  24. package/dist/schedule-DhO_Xzz4.d.cts +167 -0
  25. package/dist/schedule-PTHUONrA.d.ts +167 -0
  26. package/dist/spec/index.cjs +1 -1
  27. package/dist/spec/index.cjs.map +1 -1
  28. package/dist/spec/index.d.cts +1 -1
  29. package/dist/spec/index.d.ts +1 -1
  30. package/dist/spec/index.js +1 -1
  31. package/dist/viewer/index.cjs +684 -202
  32. package/dist/viewer/index.cjs.map +1 -1
  33. package/dist/viewer/index.d.cts +217 -64
  34. package/dist/viewer/index.d.ts +217 -64
  35. package/dist/viewer/index.js +421 -177
  36. package/dist/viewer/index.js.map +1 -1
  37. package/package.json +7 -2
@@ -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 LETTERS = {
3
- bn: {
4
- s: { shuddha: "\u09B8" },
5
- r: { shuddha: "\u09B0", komal: "\u098B" },
6
- g: { shuddha: "\u0997", komal: "\u099C\u09CD\u099E" },
7
- m: { shuddha: "\u09AE", tivra: "\u0995\u09CD\u09B7" },
8
- p: { shuddha: "\u09AA" },
9
- d: { shuddha: "\u09A7", komal: "\u09A6" },
10
- n: { shuddha: "\u09A8", komal: "\u09A3" }
11
- },
12
- hi: {
13
- s: { shuddha: "\u0938" },
14
- r: { shuddha: "\u0930", komal: "\u090B" },
15
- g: { shuddha: "\u0917", komal: "\u091C\u094D\u091E" },
16
- m: { shuddha: "\u092E", tivra: "\u0915\u094D\u0937" },
17
- p: { shuddha: "\u092A" },
18
- d: { shuddha: "\u0927", komal: "\u0926" },
19
- n: { shuddha: "\u0928", komal: "\u0923" }
20
- },
21
- en: {
22
- s: { shuddha: "S" },
23
- r: { shuddha: "R" },
24
- g: { shuddha: "G" },
25
- m: { shuddha: "M" },
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: { matra: "\u09BE", hyphen: "-", melisma: "\u09F9", cut: "\u0983", reph: "\u09B0\u09CD" },
39
- hi: { matra: "\u093E", hyphen: "-", melisma: "\u0970", cut: "\u0903", reph: "\u0930\u094D" },
40
- en: { matra: "", hyphen: "-", melisma: "\xB7", cut: ":", reph: "" }
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: "Notation" }
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
- let base = forms.shuddha;
52
- if (opts.komal) {
53
- if (forms.komal !== void 0) base = forms.komal;
54
- else classes.push("sw-komal");
55
- }
56
- if (opts.tivra) {
57
- if (forms.tivra !== void 0) base = forms.tivra;
58
- else classes.push("sw-tivra");
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
- return { text, classes, matraSeparate };
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 isoMatra(ctx) {
205
- return ctx.marks.matra === "" ? "" : `<span class="sw-iso">${NBSP}${ctx.marks.matra}</span>`;
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 += restGlyph(ctx);
207
+ inner += matraGlyph(ctx);
233
208
  } else if (note.sustain === true && opts.withMatra) {
234
- inner += isoMatra(ctx);
209
+ inner += matraGlyph(ctx);
235
210
  }
236
211
  if (note.microtone !== void 0) {
237
- const mark = note.microtone === "atikomal" ? "\u21CA" : "\u2193";
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 += isoMatra(ctx).repeat(Math.round(d) - 1);
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 BAR_STROKES = { dari: 1, single: 1, double: 2, quad: 4 };
221
+ var BAR_GLYPHS = { dari: "A", single: "l", double: "L", quad: "LL" };
247
222
  function renderBarInner(note) {
248
- const strokes = "<i></i>".repeat(BAR_STROKES[note.bar]);
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
- const L = LABELS[ctx.lang];
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, ctx.tr(meta.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, ctx.tr(taal));
361
- if (meta.raag !== void 0) add(L.raag, ctx.tr(meta.raag));
362
- if (meta.avartan !== void 0) add(L.avartan, localizeDigits(String(meta.avartan), ctx.lang));
363
- if (meta.notationBy !== void 0) add(L.notationBy, ctx.tr(meta.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 ?? "bn";
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
- /* mandra: hasanta-shaped understroke (a real \u09CD before \u09BE is invalid Unicode) */
433
- .sw-mandra::after {
434
- content: '';
435
- position: absolute;
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
- /* CSS reph fallback for vowel letters \u098B/\u090B that reject a real reph */
466
- .sw-reph-css::before {
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 (thin) and danda strokes, notation-row height ---- */
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
- align-self: stretch;
526
- display: inline-flex;
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 i { display: inline-block; width: 0.14em; min-height: 1.25em; background: currentColor; }
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
601
768
  }
602
- function renderPrintDocument(doc, opts = {}) {
603
- const lang = opts.lang ?? "bn";
604
- const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
605
- const title = opts.title ?? tr(doc.meta.title);
606
- const body = renderToHtml(doc, {
607
- lang,
608
- showHeader: opts.showHeader,
609
- transliterate: opts.transliterate
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>${body}</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
- const html = renderPrintDocument(doc, opts);
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?.ready) void fonts.ready.then(go).catch(go);
653
- else setTimeout(go, 250);
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, LETTERS, MARKS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
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