@ugfoundation/swaralipi-js 0.6.0 → 0.7.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/README.md +16 -0
- package/dist/{batch-RUZGM2NR.js → batch-UG26T3IE.js} +4 -4
- package/dist/{batch-RUZGM2NR.js.map → batch-UG26T3IE.js.map} +1 -1
- package/dist/{chunk-ULK7NVKH.js → chunk-KMIW7ZBA.js} +3 -3
- package/dist/{chunk-ULK7NVKH.js.map → chunk-KMIW7ZBA.js.map} +1 -1
- package/dist/{chunk-ZNVTOY7J.js → chunk-NFKKXN7S.js} +3 -3
- package/dist/chunk-NFKKXN7S.js.map +1 -0
- 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/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 +354 -148
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +80 -14
- package/dist/viewer/index.d.ts +80 -14
- package/dist/viewer/index.js +349 -148
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZNVTOY7J.js.map +0 -1
package/dist/viewer/index.js
CHANGED
|
@@ -112,49 +112,120 @@ function groupRowIntoCells(notes) {
|
|
|
112
112
|
if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
|
|
113
113
|
});
|
|
114
114
|
close();
|
|
115
|
-
return cells;
|
|
115
|
+
return mergeStructural(absorbBrackets(cells));
|
|
116
|
+
}
|
|
117
|
+
var isStructural = (c) => c.kind === "bar" || c.kind === "carrier";
|
|
118
|
+
function bracketEdges(cell) {
|
|
119
|
+
if (cell.kind !== "carrier") return void 0;
|
|
120
|
+
let open = false;
|
|
121
|
+
let close = false;
|
|
122
|
+
for (const note of cell.notes) {
|
|
123
|
+
if (note.raw !== void 0 && !/^[{}()[\]\s]*$/.test(note.raw)) return void 0;
|
|
124
|
+
for (const b of note.brackets ?? []) {
|
|
125
|
+
if (b.edge === "open") open = true;
|
|
126
|
+
else close = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return open || close ? { open, close } : void 0;
|
|
130
|
+
}
|
|
131
|
+
function absorbBrackets(cells) {
|
|
132
|
+
const out = [];
|
|
133
|
+
for (let i = 0; i < cells.length; i++) {
|
|
134
|
+
const cell = cells[i];
|
|
135
|
+
const edges = bracketEdges(cell);
|
|
136
|
+
if (edges !== void 0 && edges.open !== edges.close) {
|
|
137
|
+
const prev = out[out.length - 1];
|
|
138
|
+
const next = cells[i + 1];
|
|
139
|
+
if (edges.close && prev?.kind === "group") {
|
|
140
|
+
prev.notes = [...prev.notes, ...cell.notes];
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (edges.open && next?.kind === "group") {
|
|
144
|
+
next.notes = [...cell.notes, ...next.notes];
|
|
145
|
+
next.start = cell.start;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
out.push(cell);
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
116
152
|
}
|
|
117
|
-
function
|
|
153
|
+
function mergeStructural(cells) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const cell of cells) {
|
|
156
|
+
const prev = out[out.length - 1];
|
|
157
|
+
if (prev !== void 0 && isStructural(prev) && isStructural(cell)) {
|
|
158
|
+
prev.notes = [...prev.notes, ...cell.notes];
|
|
159
|
+
if (cell.kind === "bar") prev.kind = "bar";
|
|
160
|
+
} else out.push(cell);
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
var SECTION_BARS = /* @__PURE__ */ new Set(["double", "quad"]);
|
|
165
|
+
var isBarCell = (cell) => cell?.kind === "bar";
|
|
166
|
+
var endsSection = (cell) => isBarCell(cell) && SECTION_BARS.has(cell.notes[cell.notes.length - 1].bar);
|
|
167
|
+
function alignRows(rows) {
|
|
168
|
+
const counts = /* @__PURE__ */ new Map();
|
|
169
|
+
for (const cells of rows)
|
|
170
|
+
if (isBarCell(cells[0])) counts.set(cells.length, (counts.get(cells.length) ?? 0) + 1);
|
|
171
|
+
let width = 0;
|
|
172
|
+
let best = 0;
|
|
173
|
+
for (const [len, n] of counts)
|
|
174
|
+
if (n > best || n === best && len > width) {
|
|
175
|
+
width = len;
|
|
176
|
+
best = n;
|
|
177
|
+
}
|
|
178
|
+
if (width === 0) for (const cells of rows) width = Math.max(width, cells.length);
|
|
179
|
+
const offsets = rows.map((cells, i) => {
|
|
180
|
+
if (cells.length === 0 || cells.length >= width) return 0;
|
|
181
|
+
const prev = rows[i - 1];
|
|
182
|
+
const phraseStart = !isBarCell(cells[0]) || i === 0 || endsSection(prev?.[prev.length - 1]);
|
|
183
|
+
if (!phraseStart) return 0;
|
|
184
|
+
return Math.max(0, width - cells.length - (isBarCell(cells[cells.length - 1]) ? 0 : 1));
|
|
185
|
+
});
|
|
186
|
+
rows.forEach((cells, i) => {
|
|
187
|
+
width = Math.max(width, offsets[i] + cells.length);
|
|
188
|
+
});
|
|
189
|
+
return { width, offsets };
|
|
190
|
+
}
|
|
191
|
+
function resolveArcs(cells) {
|
|
118
192
|
const arcs = [];
|
|
119
193
|
let openStart;
|
|
120
194
|
let sawEnds = false;
|
|
121
|
-
const
|
|
122
|
-
const openAt = (
|
|
195
|
+
const fillCells = [];
|
|
196
|
+
const openAt = (i) => {
|
|
123
197
|
sawEnds = true;
|
|
124
|
-
if (openStart === void 0) openStart =
|
|
198
|
+
if (openStart === void 0) openStart = i;
|
|
125
199
|
};
|
|
126
|
-
const closeAt = (
|
|
200
|
+
const closeAt = (i) => {
|
|
127
201
|
sawEnds = true;
|
|
128
202
|
if (openStart !== void 0) {
|
|
129
|
-
arcs.push({
|
|
203
|
+
arcs.push({ start: openStart, end: i, openLeft: false, openRight: false });
|
|
130
204
|
openStart = void 0;
|
|
131
205
|
} else {
|
|
132
|
-
arcs.push({
|
|
206
|
+
arcs.push({ start: 0, end: i, openLeft: true, openRight: false });
|
|
133
207
|
}
|
|
134
208
|
};
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const col = cell.start + j + 1;
|
|
140
|
-
if (note.meend === "start") openAt(col);
|
|
141
|
-
else if (note.meend === "end") closeAt(col);
|
|
209
|
+
cells.forEach((cell, i) => {
|
|
210
|
+
for (const note of cell.notes) {
|
|
211
|
+
if (note.meend === "start") openAt(i);
|
|
212
|
+
else if (note.meend === "end") closeAt(i);
|
|
142
213
|
for (const b of note.below ?? []) {
|
|
143
214
|
if (!isArcRaw(b.raw)) continue;
|
|
144
215
|
for (const ch of b.raw) {
|
|
145
|
-
if (ch === "w" || ch === "W") openAt(
|
|
146
|
-
else if (ch === "x" || ch === "X") closeAt(
|
|
147
|
-
else
|
|
216
|
+
if (ch === "w" || ch === "W") openAt(i);
|
|
217
|
+
else if (ch === "x" || ch === "X") closeAt(i);
|
|
218
|
+
else fillCells.push(i);
|
|
148
219
|
}
|
|
149
220
|
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
152
223
|
if (openStart !== void 0) {
|
|
153
|
-
arcs.push({
|
|
154
|
-
} else if (!sawEnds &&
|
|
224
|
+
arcs.push({ start: openStart, end: Math.max(cells.length - 1, 0), openLeft: false, openRight: true });
|
|
225
|
+
} else if (!sawEnds && fillCells.length > 0) {
|
|
155
226
|
arcs.push({
|
|
156
|
-
|
|
157
|
-
|
|
227
|
+
start: Math.min(...fillCells),
|
|
228
|
+
end: Math.max(...fillCells),
|
|
158
229
|
openLeft: true,
|
|
159
230
|
openRight: true,
|
|
160
231
|
line: true
|
|
@@ -162,9 +233,22 @@ function resolveArcs(cells, noteCount) {
|
|
|
162
233
|
}
|
|
163
234
|
return arcs;
|
|
164
235
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
236
|
+
var SVARA_CHAR = /[srgmpqndtkuv]/;
|
|
237
|
+
function rawMarks(raw, count) {
|
|
238
|
+
if (raw === void 0) return void 0;
|
|
239
|
+
const marks = { aakar: false, pre: "", post: new Array(count).fill("") };
|
|
240
|
+
const leads = SVARA_CHAR.test(raw);
|
|
241
|
+
let seen = 0;
|
|
242
|
+
for (const ch of raw) {
|
|
243
|
+
if (SVARA_CHAR.test(ch)) seen = Math.min(seen + 1, count);
|
|
244
|
+
else if (ch === "a") marks.aakar = true;
|
|
245
|
+
else if (ch === "i" || ch === ":") {
|
|
246
|
+
if (seen > 0) marks.post[seen - 1] += ch;
|
|
247
|
+
else if (leads) marks.pre += ch;
|
|
248
|
+
else marks.post[0] += ch;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return marks;
|
|
168
252
|
}
|
|
169
253
|
function parseBeatLabel(raw) {
|
|
170
254
|
const m = raw.match(/^([0-9]{1,2})([fF])?:?$/);
|
|
@@ -188,16 +272,54 @@ function renderGrace(svara, cls, ctx) {
|
|
|
188
272
|
const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
|
|
189
273
|
return `<span class="${[cls, ...c.classes].join(" ")}">${c.text}</span>`;
|
|
190
274
|
}
|
|
275
|
+
function cutGlyphs(marks, ctx) {
|
|
276
|
+
if (marks === "") return "";
|
|
277
|
+
return ctx.lang === "bn" ? `<span class="sw-cl" title="${ctx.marks.cut}">${marks}</span>` : `<span class="sw-cut" title="${ctx.marks.cut}"></span>`.repeat(marks.length);
|
|
278
|
+
}
|
|
191
279
|
function cellMatra(cell) {
|
|
192
|
-
const
|
|
193
|
-
const
|
|
280
|
+
const pitched = cell.notes.filter(isPitched);
|
|
281
|
+
const total = pitched.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
|
|
282
|
+
const half = pitched.some((n) => n.halfMatra === true);
|
|
194
283
|
const whole = Math.floor(total + EPS);
|
|
195
284
|
return { matras: half ? whole : Math.max(1, whole), half };
|
|
196
285
|
}
|
|
286
|
+
function cellPitchedOpts(cell) {
|
|
287
|
+
const { matras, half } = cellMatra(cell);
|
|
288
|
+
const pitched = [];
|
|
289
|
+
cell.notes.forEach((n, j) => {
|
|
290
|
+
if (isPitched(n)) pitched.push(j);
|
|
291
|
+
});
|
|
292
|
+
const first = pitched[0];
|
|
293
|
+
const last = pitched[pitched.length - 1];
|
|
294
|
+
const marks = rawMarks(first === void 0 ? void 0 : cell.notes[first].raw, pitched.length);
|
|
295
|
+
const aakars = marks !== void 0 && !marks.aakar ? 0 : matras;
|
|
296
|
+
return cell.notes.map((note, j) => {
|
|
297
|
+
if (!isPitched(note)) return { matras: 0, pre: "", post: "" };
|
|
298
|
+
return {
|
|
299
|
+
matras: j === last ? aakars : 0,
|
|
300
|
+
pre: j === first ? marks?.pre ?? "" : "",
|
|
301
|
+
post: marks !== void 0 ? marks.post[pitched.indexOf(j)] : half && j === last ? "i" : ""
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function cellLyricSlices(cell) {
|
|
306
|
+
if (!cell.notes.some((n) => n.lyric !== void 0)) return void 0;
|
|
307
|
+
const pitched = cell.notes.filter(isPitched);
|
|
308
|
+
if (pitched.length === 0) return cell.notes.map((n) => n.lyric ?? "");
|
|
309
|
+
const slices = new Array(pitched.length).fill("");
|
|
310
|
+
let k = 0;
|
|
311
|
+
for (const note of cell.notes) {
|
|
312
|
+
const at = Math.min(k, pitched.length - 1);
|
|
313
|
+
slices[at] += note.lyric ?? "";
|
|
314
|
+
if (isPitched(note)) k++;
|
|
315
|
+
}
|
|
316
|
+
return slices;
|
|
317
|
+
}
|
|
197
318
|
function renderPitchedInner(note, opts, ctx) {
|
|
198
319
|
let inner = bracketGlyphs(note, "open");
|
|
199
320
|
inner += renderGrace(note.kan, "sw-kan", ctx);
|
|
200
321
|
if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
|
|
322
|
+
inner += cutGlyphs(opts.pre, ctx);
|
|
201
323
|
if (note.svara !== void 0) {
|
|
202
324
|
const [octave, letter] = note.svara;
|
|
203
325
|
const c = composeCluster(letter, {
|
|
@@ -223,8 +345,7 @@ function renderPitchedInner(note, opts, ctx) {
|
|
|
223
345
|
if (note.svara !== void 0 && opts.matras > 1) {
|
|
224
346
|
inner += matraGlyph(ctx).repeat(opts.matras - 1);
|
|
225
347
|
}
|
|
226
|
-
|
|
227
|
-
if (opts.cuts > 0) inner += `<span class="sw-cut" title="${ctx.marks.cut}"></span>`.repeat(opts.cuts);
|
|
348
|
+
inner += cutGlyphs(opts.post, ctx);
|
|
228
349
|
inner += bracketGlyphs(note, "close");
|
|
229
350
|
return inner;
|
|
230
351
|
}
|
|
@@ -241,12 +362,9 @@ function renderAnnotation(notes, ctx) {
|
|
|
241
362
|
continue;
|
|
242
363
|
}
|
|
243
364
|
if (cell.kind === "group") {
|
|
244
|
-
const
|
|
245
|
-
const cuts = half ? 0 : cutMarkCount(cell.notes[0].raw);
|
|
365
|
+
const opts = cellPitchedOpts(cell);
|
|
246
366
|
cell.notes.forEach((note2, j) => {
|
|
247
|
-
|
|
248
|
-
const opts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
|
|
249
|
-
parts.push(`<span class="sw-n">${renderPitchedInner(note2, opts, ctx)}</span>`);
|
|
367
|
+
parts.push(`<span class="sw-n">${renderPitchedInner(note2, opts[j], ctx)}</span>`);
|
|
250
368
|
});
|
|
251
369
|
continue;
|
|
252
370
|
}
|
|
@@ -273,67 +391,56 @@ function renderAnnotation(notes, ctx) {
|
|
|
273
391
|
function lyricText(s, ctx) {
|
|
274
392
|
return s.split("\u09F9").map((part) => part === "" ? "" : ctx.tr(part)).join(ctx.marks.melisma);
|
|
275
393
|
}
|
|
276
|
-
|
|
394
|
+
var COL_BASE = 2;
|
|
395
|
+
function renderNote(note, kind, opts, ctx) {
|
|
396
|
+
if (note.bar !== void 0) return renderBarInner(note);
|
|
397
|
+
if (kind === "group" && opts !== void 0) return renderPitchedInner(note, opts, ctx);
|
|
398
|
+
const raw = rawDisplay(note);
|
|
399
|
+
return bracketGlyphs(note, "open") + (raw !== void 0 ? `<span class="sw-raw" title="${escapeHtml(raw)}">${escapeHtml(raw)}</span>` : "") + bracketGlyphs(note, "close");
|
|
400
|
+
}
|
|
401
|
+
function renderArc(arc, offset) {
|
|
402
|
+
const open = arc.line === true;
|
|
403
|
+
const cls = [
|
|
404
|
+
"sw-arc",
|
|
405
|
+
arc.openLeft || open ? "sw-arc--openl" : "",
|
|
406
|
+
arc.openRight || open ? "sw-arc--openr" : ""
|
|
407
|
+
].filter(Boolean).join(" ");
|
|
408
|
+
const left = open || arc.openLeft ? "" : '<i class="sw-arc-l">w</i>';
|
|
409
|
+
const right = open || arc.openRight ? "" : '<i class="sw-arc-r">x</i>';
|
|
410
|
+
const from = offset + arc.start + COL_BASE;
|
|
411
|
+
const to = offset + arc.end + COL_BASE + 1;
|
|
412
|
+
return `<span class="${cls}" style="grid-column:${from} / ${to}">${left}<i class="sw-arc-m"></i>${right}</span>`;
|
|
413
|
+
}
|
|
414
|
+
function renderRow(row, partIdx, rowIdx, offset, ctx) {
|
|
277
415
|
const cells = groupRowIntoCells(row.notes);
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
287
|
-
const { matras, half } = cellMatra(cell);
|
|
288
|
-
const cuts = cell.kind === "group" && !half ? cutMarkCount(cell.notes[0].raw) : 0;
|
|
289
|
-
cell.notes.forEach((note, j) => {
|
|
290
|
-
const col = cell.start + j + 1;
|
|
416
|
+
const out = [];
|
|
417
|
+
if (row.label !== void 0)
|
|
418
|
+
out.push(`<span class="sw-label">${escapeHtml(ctx.tr(row.label))}</span>`);
|
|
419
|
+
cells.forEach((cell, ci) => {
|
|
420
|
+
const col = offset + ci + COL_BASE;
|
|
421
|
+
const opts = cell.kind === "group" ? cellPitchedOpts(cell) : [];
|
|
422
|
+
const barCls = cell.kind === "bar" ? " sw-bar-cell" : "";
|
|
423
|
+
const notes = cell.notes.map((note, j) => {
|
|
291
424
|
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
if (note.lyric !== void 0) {
|
|
313
|
-
lyric.push(
|
|
314
|
-
`<span class="sw-lyr${j === 0 ? " sw-cs" : ""}" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
if (note.above !== void 0 && note.above.length > 0) {
|
|
318
|
-
above.push(`<span class="sw-above" style="${cellSpan}">${renderAnnotation(note.above, ctx)}</span>`);
|
|
319
|
-
}
|
|
320
|
-
const nonArc = (note.below ?? []).filter((b) => !isArcRaw(b.raw));
|
|
321
|
-
if (nonArc.length > 0) {
|
|
322
|
-
below.push(`<span class="sw-below" style="${cellSpan}">${renderAnnotation(nonArc, ctx)}</span>`);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
for (const arc of arcs) {
|
|
327
|
-
const cls = [
|
|
328
|
-
"sw-arc",
|
|
329
|
-
arc.openLeft ? "sw-arc--openl" : "",
|
|
330
|
-
arc.openRight ? "sw-arc--openr" : "",
|
|
331
|
-
arc.line === true ? "sw-arc--line" : ""
|
|
332
|
-
].filter(Boolean).join(" ");
|
|
333
|
-
below.push(`<span class="${cls}" style="grid-column:${arc.startCol} / ${arc.endCol + 1}"></span>`);
|
|
334
|
-
}
|
|
335
|
-
const label = row.label !== void 0 ? `<span class="sw-label">${escapeHtml(ctx.tr(row.label))}</span>` : "";
|
|
336
|
-
return `<div class="sw-row">${label}<div class="sw-grid">${above.join("")}${notation.join("")}${below.join("")}${lyric.join("")}</div></div>`;
|
|
425
|
+
const cls = isPitched(note) ? " sw-p" : "";
|
|
426
|
+
const bar = note.bar !== void 0 ? ` sw-bar sw-bar--${note.bar}` : "";
|
|
427
|
+
return `<span class="sw-n${cls}${bar}" data-note="${path}">${renderNote(note, cell.kind, opts[j], ctx)}</span>`;
|
|
428
|
+
}).join("");
|
|
429
|
+
out.push(`<span class="sw-cell${barCls}" style="grid-column:${col}">${notes}</span>`);
|
|
430
|
+
const slices = cellLyricSlices(cell);
|
|
431
|
+
if (slices !== void 0) {
|
|
432
|
+
const html = slices.map((t) => `<span>${escapeHtml(lyricText(t, ctx))}</span>`).join("");
|
|
433
|
+
out.push(`<span class="sw-lyr" style="grid-column:${col}">${html}</span>`);
|
|
434
|
+
}
|
|
435
|
+
const above = cell.notes.flatMap((n) => n.above ?? []);
|
|
436
|
+
if (above.length > 0)
|
|
437
|
+
out.push(`<span class="sw-above" style="grid-column:${col}">${renderAnnotation(above, ctx)}</span>`);
|
|
438
|
+
const below = cell.notes.flatMap((n) => (n.below ?? []).filter((b) => !isArcRaw(b.raw)));
|
|
439
|
+
if (below.length > 0)
|
|
440
|
+
out.push(`<span class="sw-below" style="grid-column:${col}">${renderAnnotation(below, ctx)}</span>`);
|
|
441
|
+
});
|
|
442
|
+
for (const arc of resolveArcs(cells)) out.push(renderArc(arc, offset));
|
|
443
|
+
return `<div class="sw-row">${out.join("")}</div>`;
|
|
337
444
|
}
|
|
338
445
|
function renderHeader(meta, ctx) {
|
|
339
446
|
return renderMetaHeader(meta, ctx.lang, ctx.tr);
|
|
@@ -356,19 +463,45 @@ function renderToHtml(doc, opts = {}) {
|
|
|
356
463
|
const lang = opts.lang === "hi" || opts.lang === "en" ? opts.lang : "bn";
|
|
357
464
|
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
358
465
|
const ctx = { lang, marks: MARKS[lang], tr };
|
|
466
|
+
const layout = alignRows(doc.parts.flatMap((p) => p.rows.map((r) => groupRowIntoCells(r.notes))));
|
|
467
|
+
const tracks = `grid-template-columns:max-content repeat(${layout.width},minmax(var(--sw-matra, 1.4em),max-content))`;
|
|
359
468
|
const out = [`<div class="sw-sheet" lang="${lang}">`];
|
|
360
469
|
if (opts.showHeader !== false) out.push(renderHeader(doc.meta, ctx));
|
|
470
|
+
out.push(`<div class="sw-scroll"><div class="sw-block" style="${tracks}">`);
|
|
471
|
+
let n = 0;
|
|
361
472
|
doc.parts.forEach((part, p) => {
|
|
362
473
|
out.push('<section class="sw-part">');
|
|
363
474
|
if (part.name !== void 0)
|
|
364
475
|
out.push(`<h3 class="sw-part-name">${escapeHtml(ctx.tr(part.name))}</h3>`);
|
|
365
|
-
part.rows.forEach((row, r) => out.push(renderRow(row, p, r, ctx)));
|
|
476
|
+
part.rows.forEach((row, r) => out.push(renderRow(row, p, r, layout.offsets[n++], ctx)));
|
|
366
477
|
out.push("</section>");
|
|
367
478
|
});
|
|
368
|
-
out.push("</div>");
|
|
479
|
+
out.push("</div></div></div>");
|
|
369
480
|
return out.join("");
|
|
370
481
|
}
|
|
371
482
|
|
|
483
|
+
// src/viewer/fit.ts
|
|
484
|
+
function fitSwaralipi(root, minScale = 0, zoom = 1) {
|
|
485
|
+
const sheets = root.querySelectorAll(".sw-sheet");
|
|
486
|
+
for (const sheet of sheets) {
|
|
487
|
+
const scroll = sheet.querySelector(".sw-scroll");
|
|
488
|
+
const block = sheet.querySelector(".sw-block");
|
|
489
|
+
if (scroll === null || block === null) continue;
|
|
490
|
+
sheet.style.removeProperty("--sw-size");
|
|
491
|
+
const available = scroll.clientWidth;
|
|
492
|
+
const natural = block.scrollWidth;
|
|
493
|
+
if (available === 0 || natural === 0) continue;
|
|
494
|
+
const fit = natural > available ? Math.max(minScale, available / natural) : 1;
|
|
495
|
+
const scale = fit * zoom;
|
|
496
|
+
if (scale === 1) continue;
|
|
497
|
+
const base = parseFloat(getComputedStyle(sheet).fontSize);
|
|
498
|
+
sheet.style.setProperty("--sw-size", `${(base * scale).toFixed(3)}px`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
var MIN_ZOOM = 0.5;
|
|
502
|
+
var MAX_ZOOM = 3;
|
|
503
|
+
var ZOOM_STEP = 1.2;
|
|
504
|
+
|
|
372
505
|
// src/viewer/styles.ts
|
|
373
506
|
var NOTATION_FONT_URLS = {
|
|
374
507
|
Swarabitan: "https://lipighor.pages.dev/swarabitan/Swarabitan.ttf",
|
|
@@ -406,30 +539,42 @@ ${FONT_FACES}
|
|
|
406
539
|
|
|
407
540
|
.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }
|
|
408
541
|
|
|
409
|
-
/* ----
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
542
|
+
/* ---- block grid: ONE column set for the whole sheet ----
|
|
543
|
+
* NLTR emits a song as a single html table, which is why its lines align
|
|
544
|
+
* down the page. Same here: .sw-block owns the tracks, every .sw-row
|
|
545
|
+
* borrows them with subgrid, and .sw-part is display:contents so section
|
|
546
|
+
* headings sit in the same grid instead of breaking it. */
|
|
547
|
+
.sw-scroll { overflow-x: auto; }
|
|
548
|
+
.sw-block {
|
|
549
|
+
display: grid;
|
|
550
|
+
column-gap: var(--sw-cell-gap, 0.4em);
|
|
551
|
+
align-items: end;
|
|
552
|
+
width: max-content;
|
|
415
553
|
}
|
|
416
|
-
.sw-
|
|
417
|
-
.sw-
|
|
554
|
+
.sw-part { display: contents; }
|
|
555
|
+
.sw-row {
|
|
556
|
+
grid-column: 1 / -1;
|
|
418
557
|
display: grid;
|
|
558
|
+
grid-template-columns: subgrid;
|
|
419
559
|
grid-template-rows: auto auto auto auto;
|
|
420
|
-
grid-auto-columns: max-content;
|
|
421
560
|
align-items: end;
|
|
561
|
+
padding: 0.4em 0;
|
|
422
562
|
}
|
|
423
|
-
.sw-
|
|
424
|
-
|
|
563
|
+
.sw-label { grid-column: 1; grid-row: 2; padding-right: 0.5em; font-weight: 600; }
|
|
564
|
+
|
|
565
|
+
/* One column per cell; sub-notes are equal slices of it (min-width:0 kills
|
|
566
|
+
* flex's min-content floor, else a wide sub-note steals width from its
|
|
567
|
+
* neighbours while the syllable below it does not, and the two bands drift). */
|
|
568
|
+
.sw-cell, .sw-lyr { display: flex; justify-content: center; }
|
|
569
|
+
.sw-cell > .sw-n { white-space: nowrap; }
|
|
570
|
+
.sw-cell > .sw-n.sw-p,
|
|
571
|
+
.sw-lyr > span { flex: 1 1 0; min-width: 0; text-align: center; white-space: nowrap; }
|
|
572
|
+
.sw-cell { grid-row: 2; }
|
|
573
|
+
.sw-above { grid-row: 1; font-size: 0.62em; justify-self: center; white-space: nowrap; }
|
|
425
574
|
.sw-arc,
|
|
426
575
|
.sw-below { grid-row: 3; }
|
|
427
|
-
.sw-lyr { grid-row: 4;
|
|
428
|
-
.sw-below { font-size: 0.62em; justify-self:
|
|
429
|
-
|
|
430
|
-
/* inter-cell gap as padding on each cell's first notation item, so arcs
|
|
431
|
-
* and above-spans crossing cells stay continuous */
|
|
432
|
-
.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }
|
|
576
|
+
.sw-lyr { grid-row: 4; padding-top: 0.3em; }
|
|
577
|
+
.sw-below { font-size: 0.62em; justify-self: center; white-space: nowrap; }
|
|
433
578
|
|
|
434
579
|
/* ---- notation clusters & marks ---- */
|
|
435
580
|
.sw-cl { position: relative; display: inline-block; }
|
|
@@ -463,21 +608,19 @@ ${FONT_FACES}
|
|
|
463
608
|
|
|
464
609
|
.sw-hy { padding-right: 0.02em; }
|
|
465
610
|
.sw-iso { display: inline-block; margin-left: -0.18em; }
|
|
466
|
-
/* The \u0985\u09B0\u09CD\u09A7\u09AE\u09BE\u09A4\u09CD\u09B0\u09BE / \u099B\u09C7\u09A6 mark: two stacked rings (legend p. \u09E7\u09E9\u09EB)
|
|
467
|
-
*
|
|
468
|
-
* a dotted circle (an NBSP carrier
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
.sw-cut
|
|
472
|
-
.sw-half {
|
|
611
|
+
/* The \u0985\u09B0\u09CD\u09A7\u09AE\u09BE\u09A4\u09CD\u09B0\u09BE / \u099B\u09C7\u09A6 mark: two stacked rings (legend p. \u09E7\u09E9\u09EB). bn types
|
|
612
|
+
* Swarabitan's own glyph (the ASCII i key) \u2014 an isolated Unicode \u0983 has no base and
|
|
613
|
+
* every Bengali text font shapes it with a dotted circle (an NBSP carrier
|
|
614
|
+
* does not help), but the notation font has the mark itself. hi/en fall back
|
|
615
|
+
* to these drawn rings: Bhatkhande has no such sign. */
|
|
616
|
+
.sw-cut {
|
|
473
617
|
position: relative;
|
|
474
618
|
display: inline-block;
|
|
475
619
|
width: 0.3em;
|
|
476
620
|
height: 1em;
|
|
477
621
|
vertical-align: baseline;
|
|
478
622
|
}
|
|
479
|
-
.sw-cut::before, .sw-cut::after
|
|
480
|
-
.sw-half::before, .sw-half::after {
|
|
623
|
+
.sw-cut::before, .sw-cut::after {
|
|
481
624
|
content: '';
|
|
482
625
|
position: absolute;
|
|
483
626
|
left: 0.08em;
|
|
@@ -486,8 +629,8 @@ ${FONT_FACES}
|
|
|
486
629
|
border-radius: 50%;
|
|
487
630
|
background: currentColor;
|
|
488
631
|
}
|
|
489
|
-
.sw-cut::before
|
|
490
|
-
.sw-cut::after
|
|
632
|
+
.sw-cut::before { top: 0.2em; }
|
|
633
|
+
.sw-cut::after { top: 0.55em; }
|
|
491
634
|
.sw-br { opacity: 0.8; padding: 0 0.06em; }
|
|
492
635
|
.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
|
|
493
636
|
|
|
@@ -500,31 +643,41 @@ ${FONT_FACES}
|
|
|
500
643
|
opacity: 0.55;
|
|
501
644
|
}
|
|
502
645
|
|
|
503
|
-
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs ---- */
|
|
504
|
-
/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014
|
|
505
|
-
* never reset it with a padding shorthand here */
|
|
646
|
+
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs, all langs ---- */
|
|
506
647
|
.sw-bar {
|
|
507
|
-
grid-row: 2;
|
|
508
648
|
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
509
649
|
white-space: nowrap;
|
|
510
|
-
padding-right: 0.1em;
|
|
511
650
|
}
|
|
512
651
|
.sw-bar--quad { letter-spacing: 0.15em; }
|
|
652
|
+
/* a bar owns a whole column but needs no matra of width */
|
|
653
|
+
.sw-bar-cell { min-width: 0; }
|
|
513
654
|
|
|
514
|
-
/* ---- under-arcs (
|
|
655
|
+
/* ---- \u09AE\u09C0\u09A1\u09BC under-arcs (from w/x segments and spec meend) ----
|
|
656
|
+
* Swarabitan draws the arc as three pieces \u2014 'w' the opening hook, 'y' a
|
|
657
|
+
* flat fill, 'x' the closing hook. We use the font's own hooks and stretch
|
|
658
|
+
* the fill as a CSS bar (the 'y' glyph IS a plain rectangle), so one arc
|
|
659
|
+
* spans any number of columns. An open side = the arc continues onto the
|
|
660
|
+
* next system: drop that hook and leave the fill squared off. */
|
|
515
661
|
.sw-arc {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
.sw-arc
|
|
662
|
+
display: flex;
|
|
663
|
+
align-items: baseline;
|
|
664
|
+
line-height: 1;
|
|
665
|
+
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
666
|
+
font-size: 0.9em;
|
|
667
|
+
}
|
|
668
|
+
.sw-arc-l, .sw-arc-r { flex: none; font-style: normal; }
|
|
669
|
+
/* The fill's geometry is measured off the font's own 'y' glyph: ink 0.11em
|
|
670
|
+
tall whose bottom sits 0.01em above the baseline. An empty inline-block
|
|
671
|
+
takes its bottom margin edge as its baseline, so -0.01em lifts the bar to
|
|
672
|
+
exactly where the hooks' flat stroke runs \u2014 no seam at either join. */
|
|
673
|
+
.sw-arc-m {
|
|
674
|
+
flex: 1 1 0;
|
|
675
|
+
min-width: 0;
|
|
676
|
+
align-self: baseline;
|
|
677
|
+
height: 0.11em;
|
|
678
|
+
margin-bottom: -0.01em;
|
|
679
|
+
background: currentColor;
|
|
680
|
+
}
|
|
528
681
|
|
|
529
682
|
/* ---- annotation content ---- */
|
|
530
683
|
.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }
|
|
@@ -568,8 +721,8 @@ var SWARALIPI_PRINT_CSS = `
|
|
|
568
721
|
@page { margin: 16mm; }
|
|
569
722
|
@media print {
|
|
570
723
|
.sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
|
|
571
|
-
.sw-
|
|
572
|
-
.sw-
|
|
724
|
+
.sw-scroll { overflow: visible !important; }
|
|
725
|
+
.sw-row { break-inside: avoid; page-break-inside: avoid; }
|
|
573
726
|
.sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
|
|
574
727
|
.sw-hl { background: none !important; }
|
|
575
728
|
/* Keep each engraved staff system whole across a page break (the abcjs line
|
|
@@ -868,12 +1021,17 @@ ${SWARALIPI_STAFF_CSS}
|
|
|
868
1021
|
${SWARALIPI_PRINT_CSS}
|
|
869
1022
|
${watermarkCss(watermark)}
|
|
870
1023
|
html, body { margin: 0; }
|
|
871
|
-
|
|
1024
|
+
/* The printable width of A4 at the @page margin above. The sheet is one grid
|
|
1025
|
+
with one natural width, so the fit script needs a real page-sized box to
|
|
1026
|
+
measure against \u2014 the iframe's own width is not it. */
|
|
1027
|
+
body { width: 178mm; color: #111; background: #fff; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
872
1028
|
.sw-sheet { max-width: 100%; }
|
|
873
1029
|
.sw-footer { margin-top: 12px; padding-top: 6px; border-top: 1px solid #ddd; font-size: 12px; color: #555; text-align: right; }
|
|
874
1030
|
</style>
|
|
875
1031
|
</head>
|
|
876
|
-
<body>${top}${bodyHtml}${bottom}
|
|
1032
|
+
<body>${top}${bodyHtml}${bottom}
|
|
1033
|
+
<script>window.__swFit=function(){(${fitSwaralipi.toString()})(document,0.45)};window.__swFit()</script>
|
|
1034
|
+
</body>
|
|
877
1035
|
</html>`;
|
|
878
1036
|
}
|
|
879
1037
|
function renderPrintDocument(doc, opts = {}) {
|
|
@@ -916,6 +1074,7 @@ function printHtmlInIframe(html) {
|
|
|
916
1074
|
if (printed) return;
|
|
917
1075
|
printed = true;
|
|
918
1076
|
try {
|
|
1077
|
+
win.__swFit?.();
|
|
919
1078
|
win.focus();
|
|
920
1079
|
win.print();
|
|
921
1080
|
} catch {
|
|
@@ -971,6 +1130,9 @@ function createElementClass() {
|
|
|
971
1130
|
#translitLang;
|
|
972
1131
|
#renderSeq = 0;
|
|
973
1132
|
#staffOptions = {};
|
|
1133
|
+
#resize;
|
|
1134
|
+
#fittedAt = -1;
|
|
1135
|
+
#zoom = 1;
|
|
974
1136
|
validate = false;
|
|
975
1137
|
constructor() {
|
|
976
1138
|
super();
|
|
@@ -1020,9 +1182,46 @@ function createElementClass() {
|
|
|
1020
1182
|
connectedCallback() {
|
|
1021
1183
|
this.#ensureTransliterator();
|
|
1022
1184
|
this.#render();
|
|
1185
|
+
if (this.#resize === void 0 && typeof ResizeObserver !== "undefined") {
|
|
1186
|
+
this.#resize = new ResizeObserver(([entry]) => {
|
|
1187
|
+
const w = entry?.contentRect.width ?? -1;
|
|
1188
|
+
if (w === this.#fittedAt) return;
|
|
1189
|
+
this.#fittedAt = w;
|
|
1190
|
+
this.#fit();
|
|
1191
|
+
});
|
|
1192
|
+
this.#resize.observe(this);
|
|
1193
|
+
}
|
|
1023
1194
|
}
|
|
1024
1195
|
disconnectedCallback() {
|
|
1025
1196
|
this.#abort?.abort();
|
|
1197
|
+
this.#resize?.disconnect();
|
|
1198
|
+
this.#resize = void 0;
|
|
1199
|
+
}
|
|
1200
|
+
/** Scale the notation to fit the available width, times the zoom factor. */
|
|
1201
|
+
#fit() {
|
|
1202
|
+
fitSwaralipi(this.#root, 0, this.#zoom);
|
|
1203
|
+
}
|
|
1204
|
+
/** Re-fit the current render (public for hosts that resize the sheet
|
|
1205
|
+
* themselves, e.g. a tab that was hidden while the document loaded). */
|
|
1206
|
+
fit() {
|
|
1207
|
+
this.#fittedAt = -1;
|
|
1208
|
+
this.#fit();
|
|
1209
|
+
}
|
|
1210
|
+
get zoom() {
|
|
1211
|
+
return this.#zoom;
|
|
1212
|
+
}
|
|
1213
|
+
set zoom(factor) {
|
|
1214
|
+
this.#zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, factor));
|
|
1215
|
+
this.#fit();
|
|
1216
|
+
}
|
|
1217
|
+
zoomIn() {
|
|
1218
|
+
this.zoom = this.#zoom * ZOOM_STEP;
|
|
1219
|
+
}
|
|
1220
|
+
zoomOut() {
|
|
1221
|
+
this.zoom = this.#zoom / ZOOM_STEP;
|
|
1222
|
+
}
|
|
1223
|
+
resetZoom() {
|
|
1224
|
+
this.zoom = 1;
|
|
1026
1225
|
}
|
|
1027
1226
|
async #load(url) {
|
|
1028
1227
|
this.#abort?.abort();
|
|
@@ -1054,6 +1253,7 @@ function createElementClass() {
|
|
|
1054
1253
|
}
|
|
1055
1254
|
#render() {
|
|
1056
1255
|
const seq = ++this.#renderSeq;
|
|
1256
|
+
this.#zoom = 1;
|
|
1057
1257
|
if (this.#doc === void 0) {
|
|
1058
1258
|
this.#root.innerHTML = this.#styleFallback();
|
|
1059
1259
|
return;
|
|
@@ -1072,6 +1272,7 @@ function createElementClass() {
|
|
|
1072
1272
|
transliterate: this.#translitLang === lang ? this.#translit : void 0
|
|
1073
1273
|
});
|
|
1074
1274
|
this.#root.innerHTML = this.#styleFallback() + html;
|
|
1275
|
+
this.#fit();
|
|
1075
1276
|
}
|
|
1076
1277
|
#renderError(message) {
|
|
1077
1278
|
const div = document.createElement("div");
|
|
@@ -1116,6 +1317,6 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
|
|
|
1116
1317
|
return ctor;
|
|
1117
1318
|
}
|
|
1118
1319
|
|
|
1119
|
-
export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, abcDuration, abcToSvg, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
|
|
1320
|
+
export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, LABELS, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
|
|
1120
1321
|
//# sourceMappingURL=index.js.map
|
|
1121
1322
|
//# sourceMappingURL=index.js.map
|