@ugfoundation/swaralipi-js 0.1.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/LICENSE +347 -0
- package/README.md +257 -0
- package/dist/batch-NF2Q2CFS.js +47 -0
- package/dist/batch-NF2Q2CFS.js.map +1 -0
- package/dist/chunk-C6O7DYU5.js +115 -0
- package/dist/chunk-C6O7DYU5.js.map +1 -0
- package/dist/chunk-GWXI2HJA.js +14 -0
- package/dist/chunk-GWXI2HJA.js.map +1 -0
- package/dist/chunk-N3NNWAOW.js +593 -0
- package/dist/chunk-N3NNWAOW.js.map +1 -0
- package/dist/cli.cjs +913 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +91 -0
- package/dist/cli.js.map +1 -0
- package/dist/convert/index.cjs +720 -0
- package/dist/convert/index.cjs.map +1 -0
- package/dist/convert/index.d.cts +270 -0
- package/dist/convert/index.d.ts +270 -0
- package/dist/convert/index.js +5 -0
- package/dist/convert/index.js.map +1 -0
- package/dist/index.cjs +143 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/player/index.cjs +775 -0
- package/dist/player/index.cjs.map +1 -0
- package/dist/player/index.d.cts +285 -0
- package/dist/player/index.d.ts +285 -0
- package/dist/player/index.js +743 -0
- package/dist/player/index.js.map +1 -0
- package/dist/schema-gxhG45OK.d.cts +467 -0
- package/dist/schema-gxhG45OK.d.ts +467 -0
- package/dist/spec/index.cjs +143 -0
- package/dist/spec/index.cjs.map +1 -0
- package/dist/spec/index.d.cts +70 -0
- package/dist/spec/index.d.ts +70 -0
- package/dist/spec/index.js +4 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/taals-DguYW0wf.d.cts +60 -0
- package/dist/taals-DguYW0wf.d.ts +60 -0
- package/dist/viewer/index.cjs +998 -0
- package/dist/viewer/index.cjs.map +1 -0
- package/dist/viewer/index.d.cts +285 -0
- package/dist/viewer/index.d.ts +285 -0
- package/dist/viewer/index.js +814 -0
- package/dist/viewer/index.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
// 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
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var DIGITS = {
|
|
32
|
+
bn: "\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF",
|
|
33
|
+
hi: "\u0966\u0967\u0968\u0969\u096A\u096B\u096C\u096D\u096E\u096F",
|
|
34
|
+
en: "0123456789"
|
|
35
|
+
};
|
|
36
|
+
var VOWEL_LETTER_GLYPHS = /* @__PURE__ */ new Set(["\u098B", "\u090B"]);
|
|
37
|
+
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: "" }
|
|
41
|
+
};
|
|
42
|
+
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" }
|
|
46
|
+
};
|
|
47
|
+
function composeCluster(letter, opts) {
|
|
48
|
+
const forms = LETTERS[opts.lang][letter];
|
|
49
|
+
const marks = MARKS[opts.lang];
|
|
50
|
+
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;
|
|
80
|
+
}
|
|
81
|
+
return { text, classes, matraSeparate };
|
|
82
|
+
}
|
|
83
|
+
function localizeDigits(s, lang) {
|
|
84
|
+
const digits = DIGITS[lang];
|
|
85
|
+
return s.replace(/[0-9]/g, (d) => digits[d.charCodeAt(0) - 48]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/viewer/render.ts
|
|
89
|
+
var NBSP = "\xA0";
|
|
90
|
+
var EPS = 1e-6;
|
|
91
|
+
var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS;
|
|
92
|
+
function escapeHtml(s) {
|
|
93
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
94
|
+
}
|
|
95
|
+
var isPitched = (n) => n.svara !== void 0 || n.rest === true || n.sustain === true;
|
|
96
|
+
function rawDisplay(note) {
|
|
97
|
+
if (note.raw === void 0) return void 0;
|
|
98
|
+
if ((note.brackets?.length ?? 0) > 0 && /^[{}()[\]]*$/.test(note.raw)) return void 0;
|
|
99
|
+
return note.raw;
|
|
100
|
+
}
|
|
101
|
+
function groupRowIntoCells(notes) {
|
|
102
|
+
const cells = [];
|
|
103
|
+
let cur;
|
|
104
|
+
let acc = 0;
|
|
105
|
+
const close = () => {
|
|
106
|
+
if (cur) {
|
|
107
|
+
cells.push(cur);
|
|
108
|
+
cur = void 0;
|
|
109
|
+
}
|
|
110
|
+
acc = 0;
|
|
111
|
+
};
|
|
112
|
+
notes.forEach((note, i) => {
|
|
113
|
+
if (note.bar !== void 0) {
|
|
114
|
+
close();
|
|
115
|
+
cells.push({ kind: "bar", start: i, notes: [note] });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!isPitched(note)) {
|
|
119
|
+
close();
|
|
120
|
+
cells.push({ kind: rawDisplay(note) !== void 0 ? "raw" : "carrier", start: i, notes: [note] });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const d = note.durationMatra ?? 1;
|
|
124
|
+
if (isIntegral(d)) {
|
|
125
|
+
close();
|
|
126
|
+
cells.push({ kind: "group", start: i, notes: [note] });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
cur ??= { kind: "group", start: i, notes: [] };
|
|
130
|
+
cur.notes.push(note);
|
|
131
|
+
acc += d;
|
|
132
|
+
if (acc > EPS && isIntegral(acc)) close();
|
|
133
|
+
});
|
|
134
|
+
close();
|
|
135
|
+
return cells;
|
|
136
|
+
}
|
|
137
|
+
var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
|
|
138
|
+
var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
|
|
139
|
+
function resolveArcs(cells, noteCount) {
|
|
140
|
+
const arcs = [];
|
|
141
|
+
let openStart;
|
|
142
|
+
let sawEnds = false;
|
|
143
|
+
const fillCols = [];
|
|
144
|
+
const openAt = (col) => {
|
|
145
|
+
sawEnds = true;
|
|
146
|
+
if (openStart === void 0) openStart = col;
|
|
147
|
+
};
|
|
148
|
+
const closeAt = (col) => {
|
|
149
|
+
sawEnds = true;
|
|
150
|
+
if (openStart !== void 0) {
|
|
151
|
+
arcs.push({ startCol: openStart, endCol: col, openLeft: false, openRight: false });
|
|
152
|
+
openStart = void 0;
|
|
153
|
+
} else {
|
|
154
|
+
arcs.push({ startCol: 1, endCol: col, openLeft: true, openRight: false });
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
for (const cell of cells) {
|
|
158
|
+
const firstCol = cell.start + 1;
|
|
159
|
+
const lastCol = cell.start + cell.notes.length;
|
|
160
|
+
cell.notes.forEach((note, j) => {
|
|
161
|
+
const col = cell.start + j + 1;
|
|
162
|
+
if (note.meend === "start") openAt(col);
|
|
163
|
+
else if (note.meend === "end") closeAt(col);
|
|
164
|
+
for (const b of note.below ?? []) {
|
|
165
|
+
if (!isArcRaw(b.raw)) continue;
|
|
166
|
+
for (const ch of b.raw) {
|
|
167
|
+
if (ch === "w" || ch === "W") openAt(firstCol);
|
|
168
|
+
else if (ch === "x" || ch === "X") closeAt(lastCol);
|
|
169
|
+
else fillCols.push(firstCol, lastCol);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (openStart !== void 0) {
|
|
175
|
+
arcs.push({ startCol: openStart, endCol: Math.max(noteCount, 1), openLeft: false, openRight: true });
|
|
176
|
+
} else if (!sawEnds && fillCols.length > 0) {
|
|
177
|
+
arcs.push({
|
|
178
|
+
startCol: Math.min(...fillCols),
|
|
179
|
+
endCol: Math.max(...fillCols),
|
|
180
|
+
openLeft: true,
|
|
181
|
+
openRight: true,
|
|
182
|
+
line: true
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return arcs;
|
|
186
|
+
}
|
|
187
|
+
function cutMarkCount(raw) {
|
|
188
|
+
if (raw === void 0) return 0;
|
|
189
|
+
return (raw.match(/[i:]/g) ?? []).length;
|
|
190
|
+
}
|
|
191
|
+
function parseBeatLabel(raw) {
|
|
192
|
+
const m = raw.match(/^([0-9]{1,2})([fF])?:?$/);
|
|
193
|
+
if (!m) return void 0;
|
|
194
|
+
return { digits: m[1], sam: m[2] !== void 0 };
|
|
195
|
+
}
|
|
196
|
+
var BRACKET_GLYPHS = {
|
|
197
|
+
open: { repeat: "{", skip: "(", alt: "[" },
|
|
198
|
+
close: { repeat: "}", skip: ")", alt: "]" }
|
|
199
|
+
};
|
|
200
|
+
function bracketGlyphs(note, edge) {
|
|
201
|
+
if (!note.brackets) return "";
|
|
202
|
+
return note.brackets.filter((b) => b.edge === edge).map((b) => `<span class="sw-br">${BRACKET_GLYPHS[edge][b.kind]}</span>`).join("");
|
|
203
|
+
}
|
|
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);
|
|
209
|
+
}
|
|
210
|
+
function renderKan(note, ctx) {
|
|
211
|
+
if (!note.kan) return "";
|
|
212
|
+
const [octave, letter] = note.kan;
|
|
213
|
+
const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
|
|
214
|
+
return `<span class="${["sw-kan", ...c.classes].join(" ")}">${c.text}</span>`;
|
|
215
|
+
}
|
|
216
|
+
function renderPitchedInner(note, opts, ctx) {
|
|
217
|
+
let inner = bracketGlyphs(note, "open");
|
|
218
|
+
inner += renderKan(note, ctx);
|
|
219
|
+
if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
|
|
220
|
+
if (note.svara !== void 0) {
|
|
221
|
+
const [octave, letter] = note.svara;
|
|
222
|
+
const c = composeCluster(letter, {
|
|
223
|
+
komal: note.komal,
|
|
224
|
+
tivra: note.tivra,
|
|
225
|
+
octave,
|
|
226
|
+
withMatra: opts.withMatra,
|
|
227
|
+
lang: ctx.lang
|
|
228
|
+
});
|
|
229
|
+
inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
|
|
230
|
+
if (c.matraSeparate) inner += isoMatra(ctx);
|
|
231
|
+
} else if (note.rest === true) {
|
|
232
|
+
inner += restGlyph(ctx);
|
|
233
|
+
} else if (note.sustain === true && opts.withMatra) {
|
|
234
|
+
inner += isoMatra(ctx);
|
|
235
|
+
}
|
|
236
|
+
if (note.microtone !== void 0) {
|
|
237
|
+
const mark = note.microtone === "atikomal" ? "\u21CA" : "\u2193";
|
|
238
|
+
inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
|
|
239
|
+
}
|
|
240
|
+
const d = note.durationMatra ?? 1;
|
|
241
|
+
if (isIntegral(d) && d > 1 + EPS) inner += isoMatra(ctx).repeat(Math.round(d) - 1);
|
|
242
|
+
if (opts.cuts > 0) inner += `<span class="sw-cut">${NBSP}${ctx.marks.cut}</span>`.repeat(opts.cuts);
|
|
243
|
+
inner += bracketGlyphs(note, "close");
|
|
244
|
+
return inner;
|
|
245
|
+
}
|
|
246
|
+
var BAR_STROKES = { dari: 1, single: 1, double: 2, quad: 4 };
|
|
247
|
+
function renderBarInner(note) {
|
|
248
|
+
const strokes = "<i></i>".repeat(BAR_STROKES[note.bar]);
|
|
249
|
+
return `${bracketGlyphs(note, "open")}${strokes}${bracketGlyphs(note, "close")}`;
|
|
250
|
+
}
|
|
251
|
+
function renderAnnotation(notes, ctx) {
|
|
252
|
+
const parts = [];
|
|
253
|
+
for (const cell of groupRowIntoCells(notes)) {
|
|
254
|
+
if (cell.kind === "bar") {
|
|
255
|
+
const note2 = cell.notes[0];
|
|
256
|
+
parts.push(`<span class="sw-bar sw-bar--${note2.bar}">${renderBarInner(note2)}</span>`);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (cell.kind === "group") {
|
|
260
|
+
const cuts = cutMarkCount(cell.notes[0].raw);
|
|
261
|
+
cell.notes.forEach((note2, j) => {
|
|
262
|
+
const last = j === cell.notes.length - 1;
|
|
263
|
+
parts.push(
|
|
264
|
+
`<span class="sw-n">${renderPitchedInner(note2, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const note = cell.notes[0];
|
|
270
|
+
const raw = rawDisplay(note);
|
|
271
|
+
if (raw !== void 0) {
|
|
272
|
+
const beat = parseBeatLabel(raw);
|
|
273
|
+
if (beat) {
|
|
274
|
+
parts.push(
|
|
275
|
+
`<span class="sw-beat${beat.sam ? " sw-sam" : ""}">${localizeDigits(beat.digits, ctx.lang)}</span>`
|
|
276
|
+
);
|
|
277
|
+
} else if (/^\|+$/.test(raw)) {
|
|
278
|
+
parts.push(`<span class="sw-stop">${"<i></i>".repeat(raw.length)}</span>`);
|
|
279
|
+
} else {
|
|
280
|
+
parts.push(`<span class="sw-raw" title="${escapeHtml(raw)}">${escapeHtml(raw)}</span>`);
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const brackets = bracketGlyphs(note, "open") + bracketGlyphs(note, "close");
|
|
285
|
+
if (brackets !== "") parts.push(`<span class="sw-carrier">${brackets}</span>`);
|
|
286
|
+
}
|
|
287
|
+
return parts.join("");
|
|
288
|
+
}
|
|
289
|
+
function lyricText(s, ctx) {
|
|
290
|
+
return s.split("\u09F9").map((part) => part === "" ? "" : ctx.tr(part)).join(ctx.marks.melisma);
|
|
291
|
+
}
|
|
292
|
+
function renderRow(row, partIdx, rowIdx, ctx) {
|
|
293
|
+
const cells = groupRowIntoCells(row.notes);
|
|
294
|
+
const arcs = resolveArcs(cells, row.notes.length);
|
|
295
|
+
const above = [];
|
|
296
|
+
const notation = [];
|
|
297
|
+
const below = [];
|
|
298
|
+
const lyric = [];
|
|
299
|
+
for (const cell of cells) {
|
|
300
|
+
const firstCol = cell.start + 1;
|
|
301
|
+
const lastCol = cell.start + cell.notes.length;
|
|
302
|
+
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
303
|
+
const cuts = cell.kind === "group" ? cutMarkCount(cell.notes[0].raw) : 0;
|
|
304
|
+
cell.notes.forEach((note, j) => {
|
|
305
|
+
const col = cell.start + j + 1;
|
|
306
|
+
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
307
|
+
const cellEdges = `${j === 0 ? " sw-cs" : ""}${j === cell.notes.length - 1 ? " sw-ce" : ""}`;
|
|
308
|
+
if (cell.kind === "bar") {
|
|
309
|
+
notation.push(
|
|
310
|
+
`<span class="sw-bar sw-bar--${note.bar}${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderBarInner(note)}</span>`
|
|
311
|
+
);
|
|
312
|
+
} else if (cell.kind === "group") {
|
|
313
|
+
const last = j === cell.notes.length - 1;
|
|
314
|
+
notation.push(
|
|
315
|
+
`<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
|
|
316
|
+
);
|
|
317
|
+
} else {
|
|
318
|
+
const raw = rawDisplay(note);
|
|
319
|
+
const inner = bracketGlyphs(note, "open") + (raw !== void 0 ? `<span class="sw-raw" title="${escapeHtml(raw)}">${escapeHtml(raw)}</span>` : "") + bracketGlyphs(note, "close");
|
|
320
|
+
if (inner !== "") {
|
|
321
|
+
notation.push(
|
|
322
|
+
`<span class="sw-n sw-carrier${cellEdges}" data-note="${path}" style="grid-column:${col}">${inner}</span>`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (note.lyric !== void 0) {
|
|
327
|
+
lyric.push(
|
|
328
|
+
`<span class="sw-lyr" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (note.above !== void 0 && note.above.length > 0) {
|
|
332
|
+
above.push(`<span class="sw-above" style="${cellSpan}">${renderAnnotation(note.above, ctx)}</span>`);
|
|
333
|
+
}
|
|
334
|
+
const nonArc = (note.below ?? []).filter((b) => !isArcRaw(b.raw));
|
|
335
|
+
if (nonArc.length > 0) {
|
|
336
|
+
below.push(`<span class="sw-below" style="${cellSpan}">${renderAnnotation(nonArc, ctx)}</span>`);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
for (const arc of arcs) {
|
|
341
|
+
const cls = [
|
|
342
|
+
"sw-arc",
|
|
343
|
+
arc.openLeft ? "sw-arc--openl" : "",
|
|
344
|
+
arc.openRight ? "sw-arc--openr" : "",
|
|
345
|
+
arc.line === true ? "sw-arc--line" : ""
|
|
346
|
+
].filter(Boolean).join(" ");
|
|
347
|
+
below.push(`<span class="${cls}" style="grid-column:${arc.startCol} / ${arc.endCol + 1}"></span>`);
|
|
348
|
+
}
|
|
349
|
+
const label = row.label !== void 0 ? `<span class="sw-label">${escapeHtml(ctx.tr(row.label))}</span>` : "";
|
|
350
|
+
return `<div class="sw-row">${label}<div class="sw-grid">${above.join("")}${notation.join("")}${below.join("")}${lyric.join("")}</div></div>`;
|
|
351
|
+
}
|
|
352
|
+
function renderHeader(meta, ctx) {
|
|
353
|
+
const L = LABELS[ctx.lang];
|
|
354
|
+
const rows = [];
|
|
355
|
+
const add = (label, value) => rows.push(
|
|
356
|
+
`<div class="sw-meta-row"><span class="sw-meta-label">${escapeHtml(label)}</span><span class="sw-meta-value">${escapeHtml(value)}</span></div>`
|
|
357
|
+
);
|
|
358
|
+
add(L.title, ctx.tr(meta.title));
|
|
359
|
+
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));
|
|
364
|
+
return `<header class="sw-header">${rows.join("")}</header>`;
|
|
365
|
+
}
|
|
366
|
+
function renderToHtml(doc, opts = {}) {
|
|
367
|
+
const lang = opts.lang ?? "bn";
|
|
368
|
+
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
369
|
+
const ctx = { lang, marks: MARKS[lang], tr };
|
|
370
|
+
const out = [`<div class="sw-sheet" lang="${lang}">`];
|
|
371
|
+
if (opts.showHeader !== false) out.push(renderHeader(doc.meta, ctx));
|
|
372
|
+
doc.parts.forEach((part, p) => {
|
|
373
|
+
out.push('<section class="sw-part">');
|
|
374
|
+
if (part.name !== void 0)
|
|
375
|
+
out.push(`<h3 class="sw-part-name">${escapeHtml(ctx.tr(part.name))}</h3>`);
|
|
376
|
+
part.rows.forEach((row, r) => out.push(renderRow(row, p, r, ctx)));
|
|
377
|
+
out.push("</section>");
|
|
378
|
+
});
|
|
379
|
+
out.push("</div>");
|
|
380
|
+
return out.join("");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/viewer/styles.ts
|
|
384
|
+
var SWARALIPI_CSS = `
|
|
385
|
+
.sw-sheet {
|
|
386
|
+
font-family: var(--sw-font, 'Noto Sans Bengali', 'Kohinoor Bangla', 'Bangla Sangam MN', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif);
|
|
387
|
+
font-size: var(--sw-size, 1.125rem);
|
|
388
|
+
line-height: 1.5;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* ---- header ---- */
|
|
392
|
+
.sw-header {
|
|
393
|
+
margin: 0 0 0.75em;
|
|
394
|
+
padding: 0 0 0.5em;
|
|
395
|
+
border-bottom: 1px solid var(--sw-rule, rgba(128, 128, 128, 0.35));
|
|
396
|
+
font-size: 0.95em;
|
|
397
|
+
}
|
|
398
|
+
.sw-meta-row { display: flex; gap: 0.3em; }
|
|
399
|
+
.sw-meta-label { font-weight: 600; min-width: 4.5em; }
|
|
400
|
+
.sw-meta-label::after { content: ':'; font-weight: 400; margin-left: 0.15em; }
|
|
401
|
+
|
|
402
|
+
.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }
|
|
403
|
+
|
|
404
|
+
/* ---- row grid: above / notation / below(arcs) / lyric ---- */
|
|
405
|
+
.sw-row {
|
|
406
|
+
display: flex;
|
|
407
|
+
align-items: flex-start;
|
|
408
|
+
overflow-x: auto;
|
|
409
|
+
padding: 0.4em 0;
|
|
410
|
+
}
|
|
411
|
+
.sw-label { flex: none; margin-right: 0.8em; font-weight: 600; }
|
|
412
|
+
.sw-grid {
|
|
413
|
+
display: grid;
|
|
414
|
+
grid-template-rows: auto auto auto auto;
|
|
415
|
+
grid-auto-columns: max-content;
|
|
416
|
+
align-items: end;
|
|
417
|
+
}
|
|
418
|
+
.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; }
|
|
420
|
+
.sw-arc,
|
|
421
|
+
.sw-below { grid-row: 3; }
|
|
422
|
+
.sw-lyr { grid-row: 4; white-space: nowrap; padding-top: 0.3em; }
|
|
423
|
+
.sw-below { font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }
|
|
424
|
+
|
|
425
|
+
/* inter-cell gap as padding on each cell's first notation item, so arcs
|
|
426
|
+
* and above-spans crossing cells stay continuous */
|
|
427
|
+
.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }
|
|
428
|
+
|
|
429
|
+
/* ---- notation clusters & marks ---- */
|
|
430
|
+
.sw-cl { position: relative; display: inline-block; }
|
|
431
|
+
|
|
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 */
|
|
444
|
+
.sw-ati-mandra::before {
|
|
445
|
+
content: '';
|
|
446
|
+
position: absolute;
|
|
447
|
+
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
|
+
width: 0.16em;
|
|
461
|
+
height: 0.16em;
|
|
462
|
+
border-radius: 50%;
|
|
463
|
+
background: currentColor;
|
|
464
|
+
}
|
|
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; }
|
|
497
|
+
|
|
498
|
+
/* kan grace: small raised cluster left of the main note */
|
|
499
|
+
.sw-kan {
|
|
500
|
+
position: relative;
|
|
501
|
+
display: inline-block;
|
|
502
|
+
font-size: 0.58em;
|
|
503
|
+
vertical-align: 0.75em;
|
|
504
|
+
margin-right: 0.08em;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.sw-hy { padding-right: 0.02em; }
|
|
508
|
+
.sw-iso { display: inline-block; margin-left: -0.18em; }
|
|
509
|
+
.sw-cut { font-size: 0.8em; display: inline-block; margin-left: -0.15em; }
|
|
510
|
+
.sw-br { opacity: 0.8; padding: 0 0.06em; }
|
|
511
|
+
.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
|
|
512
|
+
|
|
513
|
+
/* raw fallback: never dropped, visibly non-notation */
|
|
514
|
+
.sw-raw {
|
|
515
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
516
|
+
font-size: 0.72em;
|
|
517
|
+
opacity: 0.55;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* ---- bars: dari (thin) and danda strokes, notation-row height ---- */
|
|
521
|
+
/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014
|
|
522
|
+
* never reset it with a padding shorthand here */
|
|
523
|
+
.sw-bar {
|
|
524
|
+
grid-row: 2;
|
|
525
|
+
align-self: stretch;
|
|
526
|
+
display: inline-flex;
|
|
527
|
+
align-items: stretch;
|
|
528
|
+
gap: 0.16em;
|
|
529
|
+
padding-right: 0.1em;
|
|
530
|
+
}
|
|
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; }
|
|
536
|
+
|
|
537
|
+
/* ---- under-arcs (slurs decoded from w/x segments and meend) ---- */
|
|
538
|
+
.sw-arc {
|
|
539
|
+
align-self: start;
|
|
540
|
+
height: 0.4em;
|
|
541
|
+
margin: 0.05em 0.25em 0;
|
|
542
|
+
border: 0.09em solid currentColor;
|
|
543
|
+
border-top: none;
|
|
544
|
+
border-bottom-left-radius: 0.6em;
|
|
545
|
+
border-bottom-right-radius: 0.6em;
|
|
546
|
+
opacity: 0.85;
|
|
547
|
+
}
|
|
548
|
+
.sw-arc--openl { border-left: none; border-bottom-left-radius: 0; }
|
|
549
|
+
.sw-arc--openr { border-right: none; border-bottom-right-radius: 0; }
|
|
550
|
+
.sw-arc--line { border-left: none; border-right: none; border-radius: 0; height: 0.22em; }
|
|
551
|
+
|
|
552
|
+
/* ---- annotation content ---- */
|
|
553
|
+
.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }
|
|
554
|
+
.sw-sam::after {
|
|
555
|
+
content: '';
|
|
556
|
+
position: absolute;
|
|
557
|
+
top: -0.45em;
|
|
558
|
+
left: 50%;
|
|
559
|
+
width: 0.1em;
|
|
560
|
+
height: 0.4em;
|
|
561
|
+
background: currentColor;
|
|
562
|
+
transform: rotate(14deg);
|
|
563
|
+
}
|
|
564
|
+
.sw-stop i {
|
|
565
|
+
display: inline-block;
|
|
566
|
+
width: 0.07em;
|
|
567
|
+
height: 1em;
|
|
568
|
+
margin: 0 0.07em;
|
|
569
|
+
background: currentColor;
|
|
570
|
+
vertical-align: text-bottom;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* ---- player sync (P2.3) ---- */
|
|
574
|
+
.sw-hl { background: var(--sw-hl, rgba(255, 196, 64, 0.45)); border-radius: 0.15em; }
|
|
575
|
+
|
|
576
|
+
/* ---- element error box ---- */
|
|
577
|
+
.sw-error {
|
|
578
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
579
|
+
font-size: 0.85em;
|
|
580
|
+
padding: 0.6em 0.8em;
|
|
581
|
+
border: 1px solid rgba(200, 64, 64, 0.6);
|
|
582
|
+
border-radius: 0.3em;
|
|
583
|
+
color: #b33;
|
|
584
|
+
background: rgba(200, 64, 64, 0.08);
|
|
585
|
+
}
|
|
586
|
+
`;
|
|
587
|
+
var SWARALIPI_PRINT_CSS = `
|
|
588
|
+
@page { margin: 16mm; }
|
|
589
|
+
@media print {
|
|
590
|
+
.sw-sheet { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
|
|
591
|
+
.sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }
|
|
592
|
+
.sw-part { break-inside: auto; }
|
|
593
|
+
.sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
|
|
594
|
+
.sw-hl { background: none !important; }
|
|
595
|
+
}
|
|
596
|
+
`;
|
|
597
|
+
|
|
598
|
+
// src/viewer/print.ts
|
|
599
|
+
function escapeHtml2(s) {
|
|
600
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
601
|
+
}
|
|
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
|
+
});
|
|
611
|
+
return `<!doctype html>
|
|
612
|
+
<html lang="${escapeHtml2(lang)}">
|
|
613
|
+
<head>
|
|
614
|
+
<meta charset="utf-8">
|
|
615
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
616
|
+
<title>${escapeHtml2(title)}</title>
|
|
617
|
+
<style>
|
|
618
|
+
${SWARALIPI_CSS}
|
|
619
|
+
${SWARALIPI_PRINT_CSS}
|
|
620
|
+
html, body { margin: 0; }
|
|
621
|
+
body { color: #111; background: #fff; padding: 16px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
622
|
+
.sw-sheet { max-width: 100%; }
|
|
623
|
+
</style>
|
|
624
|
+
</head>
|
|
625
|
+
<body>${body}</body>
|
|
626
|
+
</html>`;
|
|
627
|
+
}
|
|
628
|
+
function printSwaralipi(doc, opts = {}) {
|
|
629
|
+
if (typeof document === "undefined") {
|
|
630
|
+
throw new Error("printSwaralipi requires a DOM; use renderPrintDocument() for server-side PDF");
|
|
631
|
+
}
|
|
632
|
+
const html = renderPrintDocument(doc, opts);
|
|
633
|
+
const iframe = document.createElement("iframe");
|
|
634
|
+
iframe.setAttribute("aria-hidden", "true");
|
|
635
|
+
iframe.style.cssText = "position:fixed; left:-10000px; top:0; width:800px; height:600px; border:0;";
|
|
636
|
+
iframe.onload = () => {
|
|
637
|
+
const win = iframe.contentWindow;
|
|
638
|
+
if (win === null) {
|
|
639
|
+
iframe.remove();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const cleanup = () => setTimeout(() => iframe.remove(), 1e3);
|
|
643
|
+
win.addEventListener("afterprint", cleanup, { once: true });
|
|
644
|
+
const go = () => {
|
|
645
|
+
try {
|
|
646
|
+
win.focus();
|
|
647
|
+
win.print();
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
const fonts = iframe.contentDocument?.fonts;
|
|
652
|
+
if (fonts?.ready) void fonts.ready.then(go).catch(go);
|
|
653
|
+
else setTimeout(go, 250);
|
|
654
|
+
};
|
|
655
|
+
iframe.srcdoc = html;
|
|
656
|
+
document.body.appendChild(iframe);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/viewer/transliterate.ts
|
|
660
|
+
var TARGET = {
|
|
661
|
+
hi: "devanagari",
|
|
662
|
+
en: "iast"
|
|
663
|
+
};
|
|
664
|
+
async function loadLyricTransliterator(lang) {
|
|
665
|
+
if (lang === "bn") return void 0;
|
|
666
|
+
const mod = await import('@indic-transliteration/sanscript');
|
|
667
|
+
const sanscript = mod.default ?? mod;
|
|
668
|
+
const target = TARGET[lang];
|
|
669
|
+
return (text) => sanscript.t(text, "bengali", target);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/viewer/element.ts
|
|
673
|
+
var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en"]);
|
|
674
|
+
var sharedSheet;
|
|
675
|
+
function createElementClass() {
|
|
676
|
+
return class SwaralipiSheet extends HTMLElement {
|
|
677
|
+
static observedAttributes = ["src", "lang", "hide-header"];
|
|
678
|
+
#doc;
|
|
679
|
+
#root;
|
|
680
|
+
#adopted = false;
|
|
681
|
+
#abort;
|
|
682
|
+
#translit;
|
|
683
|
+
#translitLang;
|
|
684
|
+
validate = false;
|
|
685
|
+
constructor() {
|
|
686
|
+
super();
|
|
687
|
+
this.#root = this.attachShadow({ mode: "open" });
|
|
688
|
+
if (typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && "adoptedStyleSheets" in this.#root) {
|
|
689
|
+
if (!sharedSheet) {
|
|
690
|
+
sharedSheet = new CSSStyleSheet();
|
|
691
|
+
sharedSheet.replaceSync(SWARALIPI_CSS);
|
|
692
|
+
}
|
|
693
|
+
this.#root.adoptedStyleSheets = [...this.#root.adoptedStyleSheets, sharedSheet];
|
|
694
|
+
this.#adopted = true;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
get doc() {
|
|
698
|
+
return this.#doc;
|
|
699
|
+
}
|
|
700
|
+
set doc(value) {
|
|
701
|
+
this.#doc = value;
|
|
702
|
+
if (value !== void 0 && this.validate) {
|
|
703
|
+
void import('../spec/index.js').then(({ safeParseSwaralipi }) => {
|
|
704
|
+
const result = safeParseSwaralipi(value);
|
|
705
|
+
if (!result.success) this.#renderError(result.error.message);
|
|
706
|
+
}).catch(() => void 0);
|
|
707
|
+
}
|
|
708
|
+
this.#render();
|
|
709
|
+
}
|
|
710
|
+
get #lang() {
|
|
711
|
+
const attr = this.getAttribute("lang");
|
|
712
|
+
return attr !== null && LANGS.has(attr) ? attr : "bn";
|
|
713
|
+
}
|
|
714
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
715
|
+
if (oldValue === newValue) return;
|
|
716
|
+
if (name === "src") {
|
|
717
|
+
if (newValue !== null) void this.#load(newValue);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (name === "lang") this.#ensureTransliterator();
|
|
721
|
+
this.#render();
|
|
722
|
+
}
|
|
723
|
+
connectedCallback() {
|
|
724
|
+
this.#ensureTransliterator();
|
|
725
|
+
this.#render();
|
|
726
|
+
}
|
|
727
|
+
disconnectedCallback() {
|
|
728
|
+
this.#abort?.abort();
|
|
729
|
+
}
|
|
730
|
+
async #load(url) {
|
|
731
|
+
this.#abort?.abort();
|
|
732
|
+
const abort = new AbortController();
|
|
733
|
+
this.#abort = abort;
|
|
734
|
+
try {
|
|
735
|
+
const res = await fetch(url, { signal: abort.signal });
|
|
736
|
+
if (!res.ok) throw new Error(`fetch ${url}: HTTP ${res.status}`);
|
|
737
|
+
this.doc = await res.json();
|
|
738
|
+
} catch (err) {
|
|
739
|
+
if (abort.signal.aborted) return;
|
|
740
|
+
this.#renderError(err instanceof Error ? err.message : String(err));
|
|
741
|
+
this.dispatchEvent(new ErrorEvent("error", { error: err, message: String(err) }));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/** Load (once per lang) the lyric transliterator, then re-render. */
|
|
745
|
+
#ensureTransliterator() {
|
|
746
|
+
const lang = this.#lang;
|
|
747
|
+
if (lang === "bn" || this.#translitLang === lang) return;
|
|
748
|
+
void loadLyricTransliterator(lang).then((t) => {
|
|
749
|
+
if (t === void 0 || this.#lang !== lang) return;
|
|
750
|
+
this.#translit = t;
|
|
751
|
+
this.#translitLang = lang;
|
|
752
|
+
this.#render();
|
|
753
|
+
}).catch(() => void 0);
|
|
754
|
+
}
|
|
755
|
+
#styleFallback() {
|
|
756
|
+
return this.#adopted ? "" : `<style>${SWARALIPI_CSS}</style>`;
|
|
757
|
+
}
|
|
758
|
+
#render() {
|
|
759
|
+
if (this.#doc === void 0) {
|
|
760
|
+
this.#root.innerHTML = this.#styleFallback();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const lang = this.#lang;
|
|
764
|
+
const html = renderToHtml(this.#doc, {
|
|
765
|
+
lang,
|
|
766
|
+
showHeader: !this.hasAttribute("hide-header"),
|
|
767
|
+
transliterate: this.#translitLang === lang ? this.#translit : void 0
|
|
768
|
+
});
|
|
769
|
+
this.#root.innerHTML = this.#styleFallback() + html;
|
|
770
|
+
}
|
|
771
|
+
#renderError(message) {
|
|
772
|
+
const div = document.createElement("div");
|
|
773
|
+
div.className = "sw-error";
|
|
774
|
+
div.textContent = message;
|
|
775
|
+
this.#root.innerHTML = this.#styleFallback();
|
|
776
|
+
this.#root.appendChild(div);
|
|
777
|
+
}
|
|
778
|
+
highlight(path) {
|
|
779
|
+
if (!/^\d+\.\d+\.\d+$/.test(path)) return null;
|
|
780
|
+
const el = this.#root.querySelector(`[data-note="${path}"]`);
|
|
781
|
+
el?.classList.add("sw-hl");
|
|
782
|
+
return el;
|
|
783
|
+
}
|
|
784
|
+
clearHighlights() {
|
|
785
|
+
for (const el of this.#root.querySelectorAll(".sw-hl")) el.classList.remove("sw-hl");
|
|
786
|
+
}
|
|
787
|
+
print(options = {}) {
|
|
788
|
+
if (this.#doc === void 0) return;
|
|
789
|
+
const lang = this.#lang;
|
|
790
|
+
printSwaralipi(this.#doc, {
|
|
791
|
+
lang,
|
|
792
|
+
showHeader: !this.hasAttribute("hide-header"),
|
|
793
|
+
transliterate: this.#translitLang === lang ? this.#translit : void 0,
|
|
794
|
+
...options
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
var registered = /* @__PURE__ */ new Map();
|
|
800
|
+
function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
|
|
801
|
+
if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") {
|
|
802
|
+
throw new Error("swaralipi-sheet requires a DOM; use renderToHtml() for server-side rendering");
|
|
803
|
+
}
|
|
804
|
+
const existing = customElements.get(tagName);
|
|
805
|
+
if (existing) return existing;
|
|
806
|
+
const ctor = registered.get(tagName) ?? createElementClass();
|
|
807
|
+
registered.set(tagName, ctor);
|
|
808
|
+
customElements.define(tagName, ctor);
|
|
809
|
+
return ctor;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export { DIGITS, LABELS, LETTERS, MARKS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
|
|
813
|
+
//# sourceMappingURL=index.js.map
|
|
814
|
+
//# sourceMappingURL=index.js.map
|