@ugfoundation/swaralipi-js 0.2.1 → 0.3.1
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-SCSIUDB3.js → batch-TVEIT2OJ.js} +4 -4
- package/dist/{batch-SCSIUDB3.js.map → batch-TVEIT2OJ.js.map} +1 -1
- package/dist/{chunk-KYNCQ7OF.js → chunk-HDN3YZ6K.js} +3 -3
- package/dist/{chunk-KYNCQ7OF.js.map → chunk-HDN3YZ6K.js.map} +1 -1
- package/dist/chunk-V5ZSUJCA.js +354 -0
- package/dist/chunk-V5ZSUJCA.js.map +1 -0
- package/dist/{chunk-DINRYGRI.js → chunk-WBSIKYWB.js} +3 -3
- package/dist/{chunk-DINRYGRI.js.map → chunk-WBSIKYWB.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 +117 -20
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +24 -164
- package/dist/player/index.d.ts +24 -164
- package/dist/player/index.js +91 -343
- package/dist/player/index.js.map +1 -1
- package/dist/schedule-BhiP_maG.d.ts +167 -0
- package/dist/schedule-Dsb2paUE.d.cts +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 +584 -54
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +177 -18
- package/dist/viewer/index.d.ts +177 -18
- package/dist/viewer/index.js +322 -30
- package/dist/viewer/index.js.map +1 -1
- package/package.json +7 -2
package/dist/viewer/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { buildSchedule, drumPattern } from '../chunk-V5ZSUJCA.js';
|
|
2
|
+
import { TAALS } from '../chunk-H6HC44PZ.js';
|
|
3
|
+
|
|
1
4
|
// src/viewer/glyphs.ts
|
|
2
5
|
var SWARABITAN_SHUDDHA = {
|
|
3
6
|
s: "s",
|
|
@@ -36,9 +39,9 @@ var MARKS = {
|
|
|
36
39
|
en: { hyphen: "-", melisma: "\xB7", cut: ":" }
|
|
37
40
|
};
|
|
38
41
|
var LABELS = {
|
|
39
|
-
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" },
|
|
40
|
-
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" },
|
|
41
|
-
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" }
|
|
42
45
|
};
|
|
43
46
|
function composeCluster(letter, opts) {
|
|
44
47
|
const classes = [];
|
|
@@ -321,21 +324,24 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
321
324
|
return `<div class="sw-row">${label}<div class="sw-grid">${above.join("")}${notation.join("")}${below.join("")}${lyric.join("")}</div></div>`;
|
|
322
325
|
}
|
|
323
326
|
function renderHeader(meta, ctx) {
|
|
324
|
-
|
|
327
|
+
return renderMetaHeader(meta, ctx.lang, ctx.tr);
|
|
328
|
+
}
|
|
329
|
+
function renderMetaHeader(meta, lang, tr = (s) => s) {
|
|
330
|
+
const L = LABELS[lang];
|
|
325
331
|
const rows = [];
|
|
326
332
|
const add = (label, value) => rows.push(
|
|
327
333
|
`<div class="sw-meta-row"><span class="sw-meta-label">${escapeHtml(label)}</span><span class="sw-meta-value">${escapeHtml(value)}</span></div>`
|
|
328
334
|
);
|
|
329
|
-
add(L.title,
|
|
335
|
+
add(L.title, tr(meta.title));
|
|
330
336
|
const taal = meta.taalName ?? meta.taal?.id;
|
|
331
|
-
if (taal !== void 0) add(L.taal,
|
|
332
|
-
if (meta.raag !== void 0) add(L.raag,
|
|
333
|
-
if (meta.avartan !== void 0) add(L.avartan, localizeDigits(String(meta.avartan),
|
|
334
|
-
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));
|
|
335
341
|
return `<header class="sw-header">${rows.join("")}</header>`;
|
|
336
342
|
}
|
|
337
343
|
function renderToHtml(doc, opts = {}) {
|
|
338
|
-
const lang = opts.lang
|
|
344
|
+
const lang = opts.lang === "hi" || opts.lang === "en" ? opts.lang : "bn";
|
|
339
345
|
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
340
346
|
const ctx = { lang, marks: MARKS[lang], tr };
|
|
341
347
|
const out = [`<div class="sw-sheet" lang="${lang}">`];
|
|
@@ -353,9 +359,9 @@ function renderToHtml(doc, opts = {}) {
|
|
|
353
359
|
|
|
354
360
|
// src/viewer/styles.ts
|
|
355
361
|
var NOTATION_FONT_URLS = {
|
|
356
|
-
Swarabitan: "https://
|
|
357
|
-
ome_bhatkhande_hindi: "https://
|
|
358
|
-
ome_bhatkhande_english: "https://
|
|
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"
|
|
359
365
|
};
|
|
360
366
|
NOTATION_FONT_URLS.Swarabitan;
|
|
361
367
|
var FONT_FACES = Object.entries(NOTATION_FONT_URLS).map(
|
|
@@ -505,6 +511,9 @@ ${FONT_FACES}
|
|
|
505
511
|
|
|
506
512
|
/* ---- player sync (P2.3) ---- */
|
|
507
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); }
|
|
508
517
|
|
|
509
518
|
/* ---- element error box ---- */
|
|
510
519
|
.sw-error {
|
|
@@ -520,27 +529,270 @@ ${FONT_FACES}
|
|
|
520
529
|
var SWARALIPI_PRINT_CSS = `
|
|
521
530
|
@page { margin: 16mm; }
|
|
522
531
|
@media print {
|
|
523
|
-
.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; }
|
|
524
533
|
.sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }
|
|
525
534
|
.sw-part { break-inside: auto; }
|
|
526
535
|
.sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
|
|
527
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; }
|
|
528
541
|
}
|
|
529
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
|
+
}
|
|
530
762
|
|
|
531
763
|
// src/viewer/print.ts
|
|
764
|
+
var DEFAULT_WATERMARK = "www.gitabitan.io";
|
|
765
|
+
var DEFAULT_NOTATOR = "gitabitan.io Team";
|
|
532
766
|
function escapeHtml2(s) {
|
|
533
767
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
534
768
|
}
|
|
535
|
-
function
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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));
|
|
544
796
|
return `<!doctype html>
|
|
545
797
|
<html lang="${escapeHtml2(lang)}">
|
|
546
798
|
<head>
|
|
@@ -549,20 +801,42 @@ function renderPrintDocument(doc, opts = {}) {
|
|
|
549
801
|
<title>${escapeHtml2(title)}</title>
|
|
550
802
|
<style>
|
|
551
803
|
${SWARALIPI_CSS}
|
|
804
|
+
${SWARALIPI_STAFF_CSS}
|
|
552
805
|
${SWARALIPI_PRINT_CSS}
|
|
806
|
+
${watermarkCss(watermark)}
|
|
553
807
|
html, body { margin: 0; }
|
|
554
808
|
body { color: #111; background: #fff; padding: 16px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
555
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; }
|
|
556
811
|
</style>
|
|
557
812
|
</head>
|
|
558
|
-
<body>${
|
|
813
|
+
<body>${top}${bodyHtml}${bottom}</body>
|
|
559
814
|
</html>`;
|
|
560
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
|
+
}
|
|
561
829
|
function printSwaralipi(doc, opts = {}) {
|
|
562
830
|
if (typeof document === "undefined") {
|
|
563
831
|
throw new Error("printSwaralipi requires a DOM; use renderPrintDocument() for server-side PDF");
|
|
564
832
|
}
|
|
565
|
-
|
|
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) {
|
|
566
840
|
const iframe = document.createElement("iframe");
|
|
567
841
|
iframe.setAttribute("aria-hidden", "true");
|
|
568
842
|
iframe.style.cssText = "position:fixed; left:-10000px; top:0; width:800px; height:600px; border:0;";
|
|
@@ -604,7 +878,7 @@ var TARGET = {
|
|
|
604
878
|
en: "iast"
|
|
605
879
|
};
|
|
606
880
|
async function loadLyricTransliterator(lang) {
|
|
607
|
-
if (lang === "bn") return void 0;
|
|
881
|
+
if (lang === "bn" || lang === "staff") return void 0;
|
|
608
882
|
const mod = await import('@indic-transliteration/sanscript');
|
|
609
883
|
const sanscript = mod.default ?? mod;
|
|
610
884
|
const target = TARGET[lang];
|
|
@@ -612,7 +886,7 @@ async function loadLyricTransliterator(lang) {
|
|
|
612
886
|
}
|
|
613
887
|
|
|
614
888
|
// src/viewer/element.ts
|
|
615
|
-
var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en"]);
|
|
889
|
+
var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en", "staff"]);
|
|
616
890
|
function ensureNotationFonts() {
|
|
617
891
|
if (typeof FontFace === "undefined" || typeof document === "undefined") return;
|
|
618
892
|
const present = /* @__PURE__ */ new Set();
|
|
@@ -632,6 +906,8 @@ function createElementClass() {
|
|
|
632
906
|
#abort;
|
|
633
907
|
#translit;
|
|
634
908
|
#translitLang;
|
|
909
|
+
#renderSeq = 0;
|
|
910
|
+
#staffOptions = {};
|
|
635
911
|
validate = false;
|
|
636
912
|
constructor() {
|
|
637
913
|
super();
|
|
@@ -639,7 +915,7 @@ function createElementClass() {
|
|
|
639
915
|
if (typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && "adoptedStyleSheets" in this.#root) {
|
|
640
916
|
if (!sharedSheet) {
|
|
641
917
|
sharedSheet = new CSSStyleSheet();
|
|
642
|
-
sharedSheet.replaceSync(SWARALIPI_CSS);
|
|
918
|
+
sharedSheet.replaceSync(SWARALIPI_CSS + SWARALIPI_STAFF_CSS);
|
|
643
919
|
}
|
|
644
920
|
this.#root.adoptedStyleSheets = [...this.#root.adoptedStyleSheets, sharedSheet];
|
|
645
921
|
this.#adopted = true;
|
|
@@ -658,6 +934,13 @@ function createElementClass() {
|
|
|
658
934
|
}
|
|
659
935
|
this.#render();
|
|
660
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
|
+
}
|
|
661
944
|
get #lang() {
|
|
662
945
|
const attr = this.getAttribute("lang");
|
|
663
946
|
return attr !== null && LANGS.has(attr) ? attr : "bn";
|
|
@@ -704,14 +987,22 @@ function createElementClass() {
|
|
|
704
987
|
}).catch(() => void 0);
|
|
705
988
|
}
|
|
706
989
|
#styleFallback() {
|
|
707
|
-
return this.#adopted ? "" : `<style>${SWARALIPI_CSS}</style>`;
|
|
990
|
+
return this.#adopted ? "" : `<style>${SWARALIPI_CSS}${SWARALIPI_STAFF_CSS}</style>`;
|
|
708
991
|
}
|
|
709
992
|
#render() {
|
|
993
|
+
const seq = ++this.#renderSeq;
|
|
710
994
|
if (this.#doc === void 0) {
|
|
711
995
|
this.#root.innerHTML = this.#styleFallback();
|
|
712
996
|
return;
|
|
713
997
|
}
|
|
714
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
|
+
}
|
|
715
1006
|
const html = renderToHtml(this.#doc, {
|
|
716
1007
|
lang,
|
|
717
1008
|
showHeader: !this.hasAttribute("hide-header"),
|
|
@@ -742,6 +1033,7 @@ function createElementClass() {
|
|
|
742
1033
|
lang,
|
|
743
1034
|
showHeader: !this.hasAttribute("hide-header"),
|
|
744
1035
|
transliterate: this.#translitLang === lang ? this.#translit : void 0,
|
|
1036
|
+
...lang === "staff" ? this.staffOptions : {},
|
|
745
1037
|
...options
|
|
746
1038
|
});
|
|
747
1039
|
}
|
|
@@ -761,6 +1053,6 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
|
|
|
761
1053
|
return ctor;
|
|
762
1054
|
}
|
|
763
1055
|
|
|
764
|
-
export { DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, 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 };
|
|
765
1057
|
//# sourceMappingURL=index.js.map
|
|
766
1058
|
//# sourceMappingURL=index.js.map
|