@ugfoundation/swaralipi-js 0.6.1 → 0.7.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/README.md +36 -1
- package/dist/{batch-E76HM4GM.js → batch-6AINAZJS.js} +4 -4
- package/dist/{batch-E76HM4GM.js.map → batch-6AINAZJS.js.map} +1 -1
- package/dist/{chunk-52EPQY47.js → chunk-7QBLGKJC.js} +3 -3
- package/dist/{chunk-52EPQY47.js.map → chunk-7QBLGKJC.js.map} +1 -1
- package/dist/{chunk-MSVF43WQ.js → chunk-ZOCFJL5A.js} +3 -3
- package/dist/{chunk-MSVF43WQ.js.map → chunk-ZOCFJL5A.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/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 +353 -131
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +101 -14
- package/dist/viewer/index.d.ts +101 -14
- package/dist/viewer/index.js +348 -131
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -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;
|
|
152
|
+
}
|
|
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 };
|
|
116
190
|
}
|
|
117
|
-
function resolveArcs(cells
|
|
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
|
|
@@ -206,20 +277,43 @@ function cutGlyphs(marks, ctx) {
|
|
|
206
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);
|
|
207
278
|
}
|
|
208
279
|
function cellMatra(cell) {
|
|
209
|
-
const
|
|
210
|
-
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);
|
|
211
283
|
const whole = Math.floor(total + EPS);
|
|
212
284
|
return { matras: half ? whole : Math.max(1, whole), half };
|
|
213
285
|
}
|
|
214
286
|
function cellPitchedOpts(cell) {
|
|
215
287
|
const { matras, half } = cellMatra(cell);
|
|
216
|
-
const
|
|
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);
|
|
217
295
|
const aakars = marks !== void 0 && !marks.aakar ? 0 : matras;
|
|
218
|
-
return cell.notes.map((
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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;
|
|
223
317
|
}
|
|
224
318
|
function renderPitchedInner(note, opts, ctx) {
|
|
225
319
|
let inner = bracketGlyphs(note, "open");
|
|
@@ -297,64 +391,56 @@ function renderAnnotation(notes, ctx) {
|
|
|
297
391
|
function lyricText(s, ctx) {
|
|
298
392
|
return s.split("\u09F9").map((part) => part === "" ? "" : ctx.tr(part)).join(ctx.marks.melisma);
|
|
299
393
|
}
|
|
300
|
-
|
|
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) {
|
|
301
415
|
const cells = groupRowIntoCells(row.notes);
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
for (const cell of cells) {
|
|
308
|
-
const firstCol = cell.start + 1;
|
|
309
|
-
const lastCol = cell.start + cell.notes.length;
|
|
310
|
-
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
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;
|
|
311
421
|
const opts = cell.kind === "group" ? cellPitchedOpts(cell) : [];
|
|
312
|
-
cell.
|
|
313
|
-
|
|
422
|
+
const barCls = cell.kind === "bar" ? " sw-bar-cell" : "";
|
|
423
|
+
const notes = cell.notes.map((note, j) => {
|
|
314
424
|
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
lyric.push(
|
|
335
|
-
`<span class="sw-lyr${j === 0 ? " sw-cs" : ""}" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
if (note.above !== void 0 && note.above.length > 0) {
|
|
339
|
-
above.push(`<span class="sw-above" style="${cellSpan}">${renderAnnotation(note.above, ctx)}</span>`);
|
|
340
|
-
}
|
|
341
|
-
const nonArc = (note.below ?? []).filter((b) => !isArcRaw(b.raw));
|
|
342
|
-
if (nonArc.length > 0) {
|
|
343
|
-
below.push(`<span class="sw-below" style="${cellSpan}">${renderAnnotation(nonArc, ctx)}</span>`);
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
for (const arc of arcs) {
|
|
348
|
-
const cls = [
|
|
349
|
-
"sw-arc",
|
|
350
|
-
arc.openLeft ? "sw-arc--openl" : "",
|
|
351
|
-
arc.openRight ? "sw-arc--openr" : "",
|
|
352
|
-
arc.line === true ? "sw-arc--line" : ""
|
|
353
|
-
].filter(Boolean).join(" ");
|
|
354
|
-
below.push(`<span class="${cls}" style="grid-column:${arc.startCol} / ${arc.endCol + 1}"></span>`);
|
|
355
|
-
}
|
|
356
|
-
const label = row.label !== void 0 ? `<span class="sw-label">${escapeHtml(ctx.tr(row.label))}</span>` : "";
|
|
357
|
-
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>`;
|
|
358
444
|
}
|
|
359
445
|
function renderHeader(meta, ctx) {
|
|
360
446
|
return renderMetaHeader(meta, ctx.lang, ctx.tr);
|
|
@@ -377,19 +463,52 @@ function renderToHtml(doc, opts = {}) {
|
|
|
377
463
|
const lang = opts.lang === "hi" || opts.lang === "en" ? opts.lang : "bn";
|
|
378
464
|
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
379
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(calc(var(--sw-matra, 1.4em) * var(--sw-tight, 1)),max-content))`;
|
|
380
468
|
const out = [`<div class="sw-sheet" lang="${lang}">`];
|
|
381
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;
|
|
382
472
|
doc.parts.forEach((part, p) => {
|
|
383
473
|
out.push('<section class="sw-part">');
|
|
384
474
|
if (part.name !== void 0)
|
|
385
475
|
out.push(`<h3 class="sw-part-name">${escapeHtml(ctx.tr(part.name))}</h3>`);
|
|
386
|
-
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)));
|
|
387
477
|
out.push("</section>");
|
|
388
478
|
});
|
|
389
|
-
out.push("</div>");
|
|
479
|
+
out.push("</div></div></div>");
|
|
390
480
|
return out.join("");
|
|
391
481
|
}
|
|
392
482
|
|
|
483
|
+
// src/viewer/fit.ts
|
|
484
|
+
function fitSwaralipi(root, minScale = 0, zoom = 1, minSpacing = 0.35, minSize = 12) {
|
|
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
|
+
sheet.style.removeProperty("--sw-tight");
|
|
492
|
+
const available = scroll.clientWidth;
|
|
493
|
+
let natural = block.scrollWidth;
|
|
494
|
+
if (available === 0 || natural === 0) continue;
|
|
495
|
+
for (const step of [0.75, 0.5, 0.25, 0]) {
|
|
496
|
+
if (natural <= available || minSpacing >= 1) break;
|
|
497
|
+
sheet.style.setProperty("--sw-tight", String(minSpacing + (1 - minSpacing) * step));
|
|
498
|
+
natural = block.scrollWidth;
|
|
499
|
+
}
|
|
500
|
+
const base = parseFloat(getComputedStyle(sheet).fontSize);
|
|
501
|
+
let fit = natural > available ? Math.max(minScale, available / natural) : 1;
|
|
502
|
+
if (base > 0 && minSize > 0) fit = Math.max(fit, Math.min(1, minSize / base));
|
|
503
|
+
const scale = fit * zoom;
|
|
504
|
+
if (scale === 1) continue;
|
|
505
|
+
sheet.style.setProperty("--sw-size", `${(base * scale).toFixed(3)}px`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
var MIN_ZOOM = 0.5;
|
|
509
|
+
var MAX_ZOOM = 3;
|
|
510
|
+
var ZOOM_STEP = 1.2;
|
|
511
|
+
|
|
393
512
|
// src/viewer/styles.ts
|
|
394
513
|
var NOTATION_FONT_URLS = {
|
|
395
514
|
Swarabitan: "https://lipighor.pages.dev/swarabitan/Swarabitan.ttf",
|
|
@@ -419,7 +538,10 @@ ${FONT_FACES}
|
|
|
419
538
|
margin: 0 0 0.75em;
|
|
420
539
|
padding: 0 0 0.5em;
|
|
421
540
|
border-bottom: 1px solid var(--sw-rule, rgba(128, 128, 128, 0.35));
|
|
422
|
-
|
|
541
|
+
/* Scales with the sheet, but never below the floor: fitSwaralipi shrinks
|
|
542
|
+
.sw-sheet's font-size to fit the notation, and the metadata is prose \u2014
|
|
543
|
+
it has no columns to align and must stay readable on a phone. */
|
|
544
|
+
font-size: max(0.95em, var(--sw-header-min, 0.95rem));
|
|
423
545
|
}
|
|
424
546
|
.sw-meta-row { display: flex; gap: 0.3em; }
|
|
425
547
|
.sw-meta-label { font-weight: 600; min-width: 4.5em; }
|
|
@@ -427,30 +549,42 @@ ${FONT_FACES}
|
|
|
427
549
|
|
|
428
550
|
.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }
|
|
429
551
|
|
|
430
|
-
/* ----
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
552
|
+
/* ---- block grid: ONE column set for the whole sheet ----
|
|
553
|
+
* NLTR emits a song as a single html table, which is why its lines align
|
|
554
|
+
* down the page. Same here: .sw-block owns the tracks, every .sw-row
|
|
555
|
+
* borrows them with subgrid, and .sw-part is display:contents so section
|
|
556
|
+
* headings sit in the same grid instead of breaking it. */
|
|
557
|
+
.sw-scroll { overflow-x: auto; }
|
|
558
|
+
.sw-block {
|
|
559
|
+
display: grid;
|
|
560
|
+
column-gap: calc(var(--sw-cell-gap, 0.4em) * var(--sw-tight, 1));
|
|
561
|
+
align-items: end;
|
|
562
|
+
width: max-content;
|
|
436
563
|
}
|
|
437
|
-
.sw-
|
|
438
|
-
.sw-
|
|
564
|
+
.sw-part { display: contents; }
|
|
565
|
+
.sw-row {
|
|
566
|
+
grid-column: 1 / -1;
|
|
439
567
|
display: grid;
|
|
568
|
+
grid-template-columns: subgrid;
|
|
440
569
|
grid-template-rows: auto auto auto auto;
|
|
441
|
-
grid-auto-columns: max-content;
|
|
442
570
|
align-items: end;
|
|
571
|
+
padding: 0.4em 0;
|
|
443
572
|
}
|
|
444
|
-
.sw-
|
|
445
|
-
|
|
573
|
+
.sw-label { grid-column: 1; grid-row: 2; padding-right: 0.5em; font-weight: 600; }
|
|
574
|
+
|
|
575
|
+
/* One column per cell; sub-notes are equal slices of it (min-width:0 kills
|
|
576
|
+
* flex's min-content floor, else a wide sub-note steals width from its
|
|
577
|
+
* neighbours while the syllable below it does not, and the two bands drift). */
|
|
578
|
+
.sw-cell, .sw-lyr { display: flex; justify-content: center; }
|
|
579
|
+
.sw-cell > .sw-n { white-space: nowrap; }
|
|
580
|
+
.sw-cell > .sw-n.sw-p,
|
|
581
|
+
.sw-lyr > span { flex: 1 1 0; min-width: 0; text-align: center; white-space: nowrap; }
|
|
582
|
+
.sw-cell { grid-row: 2; }
|
|
583
|
+
.sw-above { grid-row: 1; font-size: 0.62em; justify-self: center; white-space: nowrap; }
|
|
446
584
|
.sw-arc,
|
|
447
585
|
.sw-below { grid-row: 3; }
|
|
448
|
-
.sw-lyr { grid-row: 4;
|
|
449
|
-
.sw-below { font-size: 0.62em; justify-self:
|
|
450
|
-
|
|
451
|
-
/* inter-cell gap as padding on each cell's first notation item, so arcs
|
|
452
|
-
* and above-spans crossing cells stay continuous */
|
|
453
|
-
.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }
|
|
586
|
+
.sw-lyr { grid-row: 4; padding-top: 0.3em; }
|
|
587
|
+
.sw-below { font-size: 0.62em; justify-self: center; white-space: nowrap; }
|
|
454
588
|
|
|
455
589
|
/* ---- notation clusters & marks ---- */
|
|
456
590
|
.sw-cl { position: relative; display: inline-block; }
|
|
@@ -519,31 +653,41 @@ ${FONT_FACES}
|
|
|
519
653
|
opacity: 0.55;
|
|
520
654
|
}
|
|
521
655
|
|
|
522
|
-
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs ---- */
|
|
523
|
-
/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014
|
|
524
|
-
* never reset it with a padding shorthand here */
|
|
656
|
+
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs, all langs ---- */
|
|
525
657
|
.sw-bar {
|
|
526
|
-
grid-row: 2;
|
|
527
658
|
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
528
659
|
white-space: nowrap;
|
|
529
|
-
padding-right: 0.1em;
|
|
530
660
|
}
|
|
531
661
|
.sw-bar--quad { letter-spacing: 0.15em; }
|
|
662
|
+
/* a bar owns a whole column but needs no matra of width */
|
|
663
|
+
.sw-bar-cell { min-width: 0; }
|
|
532
664
|
|
|
533
|
-
/* ---- under-arcs (
|
|
665
|
+
/* ---- \u09AE\u09C0\u09A1\u09BC under-arcs (from w/x segments and spec meend) ----
|
|
666
|
+
* Swarabitan draws the arc as three pieces \u2014 'w' the opening hook, 'y' a
|
|
667
|
+
* flat fill, 'x' the closing hook. We use the font's own hooks and stretch
|
|
668
|
+
* the fill as a CSS bar (the 'y' glyph IS a plain rectangle), so one arc
|
|
669
|
+
* spans any number of columns. An open side = the arc continues onto the
|
|
670
|
+
* next system: drop that hook and leave the fill squared off. */
|
|
534
671
|
.sw-arc {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
.sw-arc
|
|
672
|
+
display: flex;
|
|
673
|
+
align-items: baseline;
|
|
674
|
+
line-height: 1;
|
|
675
|
+
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
676
|
+
font-size: 0.9em;
|
|
677
|
+
}
|
|
678
|
+
.sw-arc-l, .sw-arc-r { flex: none; font-style: normal; }
|
|
679
|
+
/* The fill's geometry is measured off the font's own 'y' glyph: ink 0.11em
|
|
680
|
+
tall whose bottom sits 0.01em above the baseline. An empty inline-block
|
|
681
|
+
takes its bottom margin edge as its baseline, so -0.01em lifts the bar to
|
|
682
|
+
exactly where the hooks' flat stroke runs \u2014 no seam at either join. */
|
|
683
|
+
.sw-arc-m {
|
|
684
|
+
flex: 1 1 0;
|
|
685
|
+
min-width: 0;
|
|
686
|
+
align-self: baseline;
|
|
687
|
+
height: 0.11em;
|
|
688
|
+
margin-bottom: -0.01em;
|
|
689
|
+
background: currentColor;
|
|
690
|
+
}
|
|
547
691
|
|
|
548
692
|
/* ---- annotation content ---- */
|
|
549
693
|
.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }
|
|
@@ -587,8 +731,8 @@ var SWARALIPI_PRINT_CSS = `
|
|
|
587
731
|
@page { margin: 16mm; }
|
|
588
732
|
@media print {
|
|
589
733
|
.sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
|
|
590
|
-
.sw-
|
|
591
|
-
.sw-
|
|
734
|
+
.sw-scroll { overflow: visible !important; }
|
|
735
|
+
.sw-row { break-inside: avoid; page-break-inside: avoid; }
|
|
592
736
|
.sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
|
|
593
737
|
.sw-hl { background: none !important; }
|
|
594
738
|
/* Keep each engraved staff system whole across a page break (the abcjs line
|
|
@@ -887,12 +1031,17 @@ ${SWARALIPI_STAFF_CSS}
|
|
|
887
1031
|
${SWARALIPI_PRINT_CSS}
|
|
888
1032
|
${watermarkCss(watermark)}
|
|
889
1033
|
html, body { margin: 0; }
|
|
890
|
-
|
|
1034
|
+
/* The printable width of A4 at the @page margin above. The sheet is one grid
|
|
1035
|
+
with one natural width, so the fit script needs a real page-sized box to
|
|
1036
|
+
measure against \u2014 the iframe's own width is not it. */
|
|
1037
|
+
body { width: 178mm; color: #111; background: #fff; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
891
1038
|
.sw-sheet { max-width: 100%; }
|
|
892
1039
|
.sw-footer { margin-top: 12px; padding-top: 6px; border-top: 1px solid #ddd; font-size: 12px; color: #555; text-align: right; }
|
|
893
1040
|
</style>
|
|
894
1041
|
</head>
|
|
895
|
-
<body>${top}${bodyHtml}${bottom}
|
|
1042
|
+
<body>${top}${bodyHtml}${bottom}
|
|
1043
|
+
<script>window.__swFit=function(){(${fitSwaralipi.toString()})(document,0.45,1,undefined,0)};window.__swFit()</script>
|
|
1044
|
+
</body>
|
|
896
1045
|
</html>`;
|
|
897
1046
|
}
|
|
898
1047
|
function renderPrintDocument(doc, opts = {}) {
|
|
@@ -935,6 +1084,7 @@ function printHtmlInIframe(html) {
|
|
|
935
1084
|
if (printed) return;
|
|
936
1085
|
printed = true;
|
|
937
1086
|
try {
|
|
1087
|
+
win.__swFit?.();
|
|
938
1088
|
win.focus();
|
|
939
1089
|
win.print();
|
|
940
1090
|
} catch {
|
|
@@ -981,7 +1131,7 @@ function ensureNotationFonts() {
|
|
|
981
1131
|
var sharedSheet;
|
|
982
1132
|
function createElementClass() {
|
|
983
1133
|
return class SwaralipiSheet extends HTMLElement {
|
|
984
|
-
static observedAttributes = ["src", "lang", "hide-header"];
|
|
1134
|
+
static observedAttributes = ["src", "lang", "hide-header", "min-spacing", "min-size"];
|
|
985
1135
|
#doc;
|
|
986
1136
|
#root;
|
|
987
1137
|
#adopted = false;
|
|
@@ -990,6 +1140,9 @@ function createElementClass() {
|
|
|
990
1140
|
#translitLang;
|
|
991
1141
|
#renderSeq = 0;
|
|
992
1142
|
#staffOptions = {};
|
|
1143
|
+
#resize;
|
|
1144
|
+
#fittedAt = -1;
|
|
1145
|
+
#zoom = 1;
|
|
993
1146
|
validate = false;
|
|
994
1147
|
constructor() {
|
|
995
1148
|
super();
|
|
@@ -1033,15 +1186,77 @@ function createElementClass() {
|
|
|
1033
1186
|
if (newValue !== null) void this.#load(newValue);
|
|
1034
1187
|
return;
|
|
1035
1188
|
}
|
|
1189
|
+
if (name === "min-spacing" || name === "min-size") {
|
|
1190
|
+
this.fit();
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1036
1193
|
if (name === "lang") this.#ensureTransliterator();
|
|
1037
1194
|
this.#render();
|
|
1038
1195
|
}
|
|
1039
1196
|
connectedCallback() {
|
|
1040
1197
|
this.#ensureTransliterator();
|
|
1041
1198
|
this.#render();
|
|
1199
|
+
if (this.#resize === void 0 && typeof ResizeObserver !== "undefined") {
|
|
1200
|
+
this.#resize = new ResizeObserver(([entry]) => {
|
|
1201
|
+
const w = entry?.contentRect.width ?? -1;
|
|
1202
|
+
if (w === this.#fittedAt) return;
|
|
1203
|
+
this.#fittedAt = w;
|
|
1204
|
+
this.#fit();
|
|
1205
|
+
});
|
|
1206
|
+
this.#resize.observe(this);
|
|
1207
|
+
}
|
|
1042
1208
|
}
|
|
1043
1209
|
disconnectedCallback() {
|
|
1044
1210
|
this.#abort?.abort();
|
|
1211
|
+
this.#resize?.disconnect();
|
|
1212
|
+
this.#resize = void 0;
|
|
1213
|
+
}
|
|
1214
|
+
/** Scale the notation to fit the available width, times the zoom factor. */
|
|
1215
|
+
#fit() {
|
|
1216
|
+
fitSwaralipi(this.#root, 0, this.#zoom, this.minSpacing, this.minSize);
|
|
1217
|
+
}
|
|
1218
|
+
/** Numeric attribute, or undefined for fitSwaralipi's own default. */
|
|
1219
|
+
#attrNum(name) {
|
|
1220
|
+
const value = parseFloat(this.getAttribute(name) ?? "");
|
|
1221
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1222
|
+
}
|
|
1223
|
+
#setAttrNum(name, value) {
|
|
1224
|
+
if (value === void 0) this.removeAttribute(name);
|
|
1225
|
+
else this.setAttribute(name, String(value));
|
|
1226
|
+
}
|
|
1227
|
+
get minSpacing() {
|
|
1228
|
+
return this.#attrNum("min-spacing");
|
|
1229
|
+
}
|
|
1230
|
+
set minSpacing(value) {
|
|
1231
|
+
this.#setAttrNum("min-spacing", value);
|
|
1232
|
+
}
|
|
1233
|
+
get minSize() {
|
|
1234
|
+
return this.#attrNum("min-size");
|
|
1235
|
+
}
|
|
1236
|
+
set minSize(value) {
|
|
1237
|
+
this.#setAttrNum("min-size", value);
|
|
1238
|
+
}
|
|
1239
|
+
/** Re-fit the current render (public for hosts that resize the sheet
|
|
1240
|
+
* themselves, e.g. a tab that was hidden while the document loaded). */
|
|
1241
|
+
fit() {
|
|
1242
|
+
this.#fittedAt = -1;
|
|
1243
|
+
this.#fit();
|
|
1244
|
+
}
|
|
1245
|
+
get zoom() {
|
|
1246
|
+
return this.#zoom;
|
|
1247
|
+
}
|
|
1248
|
+
set zoom(factor) {
|
|
1249
|
+
this.#zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, factor));
|
|
1250
|
+
this.#fit();
|
|
1251
|
+
}
|
|
1252
|
+
zoomIn() {
|
|
1253
|
+
this.zoom = this.#zoom * ZOOM_STEP;
|
|
1254
|
+
}
|
|
1255
|
+
zoomOut() {
|
|
1256
|
+
this.zoom = this.#zoom / ZOOM_STEP;
|
|
1257
|
+
}
|
|
1258
|
+
resetZoom() {
|
|
1259
|
+
this.zoom = 1;
|
|
1045
1260
|
}
|
|
1046
1261
|
async #load(url) {
|
|
1047
1262
|
this.#abort?.abort();
|
|
@@ -1073,6 +1288,7 @@ function createElementClass() {
|
|
|
1073
1288
|
}
|
|
1074
1289
|
#render() {
|
|
1075
1290
|
const seq = ++this.#renderSeq;
|
|
1291
|
+
this.#zoom = 1;
|
|
1076
1292
|
if (this.#doc === void 0) {
|
|
1077
1293
|
this.#root.innerHTML = this.#styleFallback();
|
|
1078
1294
|
return;
|
|
@@ -1091,6 +1307,7 @@ function createElementClass() {
|
|
|
1091
1307
|
transliterate: this.#translitLang === lang ? this.#translit : void 0
|
|
1092
1308
|
});
|
|
1093
1309
|
this.#root.innerHTML = this.#styleFallback() + html;
|
|
1310
|
+
this.#fit();
|
|
1094
1311
|
}
|
|
1095
1312
|
#renderError(message) {
|
|
1096
1313
|
const div = document.createElement("div");
|
|
@@ -1135,6 +1352,6 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
|
|
|
1135
1352
|
return ctor;
|
|
1136
1353
|
}
|
|
1137
1354
|
|
|
1138
|
-
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 };
|
|
1355
|
+
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 };
|
|
1139
1356
|
//# sourceMappingURL=index.js.map
|
|
1140
1357
|
//# sourceMappingURL=index.js.map
|