@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.cjs
CHANGED
|
@@ -185,7 +185,7 @@ var init_spec = __esm({
|
|
|
185
185
|
init_schema();
|
|
186
186
|
init_schema();
|
|
187
187
|
init_taals();
|
|
188
|
-
SPEC_VERSION = "0.
|
|
188
|
+
SPEC_VERSION = "0.7.1";
|
|
189
189
|
}
|
|
190
190
|
});
|
|
191
191
|
|
|
@@ -460,49 +460,120 @@ function groupRowIntoCells(notes) {
|
|
|
460
460
|
if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
|
|
461
461
|
});
|
|
462
462
|
close();
|
|
463
|
-
return cells;
|
|
463
|
+
return mergeStructural(absorbBrackets(cells));
|
|
464
|
+
}
|
|
465
|
+
var isStructural = (c) => c.kind === "bar" || c.kind === "carrier";
|
|
466
|
+
function bracketEdges(cell) {
|
|
467
|
+
if (cell.kind !== "carrier") return void 0;
|
|
468
|
+
let open = false;
|
|
469
|
+
let close = false;
|
|
470
|
+
for (const note of cell.notes) {
|
|
471
|
+
if (note.raw !== void 0 && !/^[{}()[\]\s]*$/.test(note.raw)) return void 0;
|
|
472
|
+
for (const b of note.brackets ?? []) {
|
|
473
|
+
if (b.edge === "open") open = true;
|
|
474
|
+
else close = true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return open || close ? { open, close } : void 0;
|
|
478
|
+
}
|
|
479
|
+
function absorbBrackets(cells) {
|
|
480
|
+
const out = [];
|
|
481
|
+
for (let i = 0; i < cells.length; i++) {
|
|
482
|
+
const cell = cells[i];
|
|
483
|
+
const edges = bracketEdges(cell);
|
|
484
|
+
if (edges !== void 0 && edges.open !== edges.close) {
|
|
485
|
+
const prev = out[out.length - 1];
|
|
486
|
+
const next = cells[i + 1];
|
|
487
|
+
if (edges.close && prev?.kind === "group") {
|
|
488
|
+
prev.notes = [...prev.notes, ...cell.notes];
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (edges.open && next?.kind === "group") {
|
|
492
|
+
next.notes = [...cell.notes, ...next.notes];
|
|
493
|
+
next.start = cell.start;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
out.push(cell);
|
|
498
|
+
}
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
function mergeStructural(cells) {
|
|
502
|
+
const out = [];
|
|
503
|
+
for (const cell of cells) {
|
|
504
|
+
const prev = out[out.length - 1];
|
|
505
|
+
if (prev !== void 0 && isStructural(prev) && isStructural(cell)) {
|
|
506
|
+
prev.notes = [...prev.notes, ...cell.notes];
|
|
507
|
+
if (cell.kind === "bar") prev.kind = "bar";
|
|
508
|
+
} else out.push(cell);
|
|
509
|
+
}
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
512
|
+
var SECTION_BARS = /* @__PURE__ */ new Set(["double", "quad"]);
|
|
513
|
+
var isBarCell = (cell) => cell?.kind === "bar";
|
|
514
|
+
var endsSection = (cell) => isBarCell(cell) && SECTION_BARS.has(cell.notes[cell.notes.length - 1].bar);
|
|
515
|
+
function alignRows(rows) {
|
|
516
|
+
const counts = /* @__PURE__ */ new Map();
|
|
517
|
+
for (const cells of rows)
|
|
518
|
+
if (isBarCell(cells[0])) counts.set(cells.length, (counts.get(cells.length) ?? 0) + 1);
|
|
519
|
+
let width = 0;
|
|
520
|
+
let best = 0;
|
|
521
|
+
for (const [len, n] of counts)
|
|
522
|
+
if (n > best || n === best && len > width) {
|
|
523
|
+
width = len;
|
|
524
|
+
best = n;
|
|
525
|
+
}
|
|
526
|
+
if (width === 0) for (const cells of rows) width = Math.max(width, cells.length);
|
|
527
|
+
const offsets = rows.map((cells, i) => {
|
|
528
|
+
if (cells.length === 0 || cells.length >= width) return 0;
|
|
529
|
+
const prev = rows[i - 1];
|
|
530
|
+
const phraseStart = !isBarCell(cells[0]) || i === 0 || endsSection(prev?.[prev.length - 1]);
|
|
531
|
+
if (!phraseStart) return 0;
|
|
532
|
+
return Math.max(0, width - cells.length - (isBarCell(cells[cells.length - 1]) ? 0 : 1));
|
|
533
|
+
});
|
|
534
|
+
rows.forEach((cells, i) => {
|
|
535
|
+
width = Math.max(width, offsets[i] + cells.length);
|
|
536
|
+
});
|
|
537
|
+
return { width, offsets };
|
|
464
538
|
}
|
|
465
|
-
function resolveArcs(cells
|
|
539
|
+
function resolveArcs(cells) {
|
|
466
540
|
const arcs = [];
|
|
467
541
|
let openStart;
|
|
468
542
|
let sawEnds = false;
|
|
469
|
-
const
|
|
470
|
-
const openAt = (
|
|
543
|
+
const fillCells = [];
|
|
544
|
+
const openAt = (i) => {
|
|
471
545
|
sawEnds = true;
|
|
472
|
-
if (openStart === void 0) openStart =
|
|
546
|
+
if (openStart === void 0) openStart = i;
|
|
473
547
|
};
|
|
474
|
-
const closeAt = (
|
|
548
|
+
const closeAt = (i) => {
|
|
475
549
|
sawEnds = true;
|
|
476
550
|
if (openStart !== void 0) {
|
|
477
|
-
arcs.push({
|
|
551
|
+
arcs.push({ start: openStart, end: i, openLeft: false, openRight: false });
|
|
478
552
|
openStart = void 0;
|
|
479
553
|
} else {
|
|
480
|
-
arcs.push({
|
|
554
|
+
arcs.push({ start: 0, end: i, openLeft: true, openRight: false });
|
|
481
555
|
}
|
|
482
556
|
};
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const col = cell.start + j + 1;
|
|
488
|
-
if (note.meend === "start") openAt(col);
|
|
489
|
-
else if (note.meend === "end") closeAt(col);
|
|
557
|
+
cells.forEach((cell, i) => {
|
|
558
|
+
for (const note of cell.notes) {
|
|
559
|
+
if (note.meend === "start") openAt(i);
|
|
560
|
+
else if (note.meend === "end") closeAt(i);
|
|
490
561
|
for (const b of note.below ?? []) {
|
|
491
562
|
if (!isArcRaw(b.raw)) continue;
|
|
492
563
|
for (const ch of b.raw) {
|
|
493
|
-
if (ch === "w" || ch === "W") openAt(
|
|
494
|
-
else if (ch === "x" || ch === "X") closeAt(
|
|
495
|
-
else
|
|
564
|
+
if (ch === "w" || ch === "W") openAt(i);
|
|
565
|
+
else if (ch === "x" || ch === "X") closeAt(i);
|
|
566
|
+
else fillCells.push(i);
|
|
496
567
|
}
|
|
497
568
|
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
500
571
|
if (openStart !== void 0) {
|
|
501
|
-
arcs.push({
|
|
502
|
-
} else if (!sawEnds &&
|
|
572
|
+
arcs.push({ start: openStart, end: Math.max(cells.length - 1, 0), openLeft: false, openRight: true });
|
|
573
|
+
} else if (!sawEnds && fillCells.length > 0) {
|
|
503
574
|
arcs.push({
|
|
504
|
-
|
|
505
|
-
|
|
575
|
+
start: Math.min(...fillCells),
|
|
576
|
+
end: Math.max(...fillCells),
|
|
506
577
|
openLeft: true,
|
|
507
578
|
openRight: true,
|
|
508
579
|
line: true
|
|
@@ -554,20 +625,43 @@ function cutGlyphs(marks, ctx) {
|
|
|
554
625
|
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);
|
|
555
626
|
}
|
|
556
627
|
function cellMatra(cell) {
|
|
557
|
-
const
|
|
558
|
-
const
|
|
628
|
+
const pitched = cell.notes.filter(isPitched);
|
|
629
|
+
const total = pitched.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
|
|
630
|
+
const half = pitched.some((n) => n.halfMatra === true);
|
|
559
631
|
const whole = Math.floor(total + EPS);
|
|
560
632
|
return { matras: half ? whole : Math.max(1, whole), half };
|
|
561
633
|
}
|
|
562
634
|
function cellPitchedOpts(cell) {
|
|
563
635
|
const { matras, half } = cellMatra(cell);
|
|
564
|
-
const
|
|
636
|
+
const pitched = [];
|
|
637
|
+
cell.notes.forEach((n, j) => {
|
|
638
|
+
if (isPitched(n)) pitched.push(j);
|
|
639
|
+
});
|
|
640
|
+
const first = pitched[0];
|
|
641
|
+
const last = pitched[pitched.length - 1];
|
|
642
|
+
const marks = rawMarks(first === void 0 ? void 0 : cell.notes[first].raw, pitched.length);
|
|
565
643
|
const aakars = marks !== void 0 && !marks.aakar ? 0 : matras;
|
|
566
|
-
return cell.notes.map((
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
644
|
+
return cell.notes.map((note, j) => {
|
|
645
|
+
if (!isPitched(note)) return { matras: 0, pre: "", post: "" };
|
|
646
|
+
return {
|
|
647
|
+
matras: j === last ? aakars : 0,
|
|
648
|
+
pre: j === first ? marks?.pre ?? "" : "",
|
|
649
|
+
post: marks !== void 0 ? marks.post[pitched.indexOf(j)] : half && j === last ? "i" : ""
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
function cellLyricSlices(cell) {
|
|
654
|
+
if (!cell.notes.some((n) => n.lyric !== void 0)) return void 0;
|
|
655
|
+
const pitched = cell.notes.filter(isPitched);
|
|
656
|
+
if (pitched.length === 0) return cell.notes.map((n) => n.lyric ?? "");
|
|
657
|
+
const slices = new Array(pitched.length).fill("");
|
|
658
|
+
let k = 0;
|
|
659
|
+
for (const note of cell.notes) {
|
|
660
|
+
const at = Math.min(k, pitched.length - 1);
|
|
661
|
+
slices[at] += note.lyric ?? "";
|
|
662
|
+
if (isPitched(note)) k++;
|
|
663
|
+
}
|
|
664
|
+
return slices;
|
|
571
665
|
}
|
|
572
666
|
function renderPitchedInner(note, opts, ctx) {
|
|
573
667
|
let inner = bracketGlyphs(note, "open");
|
|
@@ -645,64 +739,56 @@ function renderAnnotation(notes, ctx) {
|
|
|
645
739
|
function lyricText(s, ctx) {
|
|
646
740
|
return s.split("\u09F9").map((part) => part === "" ? "" : ctx.tr(part)).join(ctx.marks.melisma);
|
|
647
741
|
}
|
|
648
|
-
|
|
742
|
+
var COL_BASE = 2;
|
|
743
|
+
function renderNote(note, kind, opts, ctx) {
|
|
744
|
+
if (note.bar !== void 0) return renderBarInner(note);
|
|
745
|
+
if (kind === "group" && opts !== void 0) return renderPitchedInner(note, opts, ctx);
|
|
746
|
+
const raw = rawDisplay(note);
|
|
747
|
+
return bracketGlyphs(note, "open") + (raw !== void 0 ? `<span class="sw-raw" title="${escapeHtml(raw)}">${escapeHtml(raw)}</span>` : "") + bracketGlyphs(note, "close");
|
|
748
|
+
}
|
|
749
|
+
function renderArc(arc, offset) {
|
|
750
|
+
const open = arc.line === true;
|
|
751
|
+
const cls = [
|
|
752
|
+
"sw-arc",
|
|
753
|
+
arc.openLeft || open ? "sw-arc--openl" : "",
|
|
754
|
+
arc.openRight || open ? "sw-arc--openr" : ""
|
|
755
|
+
].filter(Boolean).join(" ");
|
|
756
|
+
const left = open || arc.openLeft ? "" : '<i class="sw-arc-l">w</i>';
|
|
757
|
+
const right = open || arc.openRight ? "" : '<i class="sw-arc-r">x</i>';
|
|
758
|
+
const from = offset + arc.start + COL_BASE;
|
|
759
|
+
const to = offset + arc.end + COL_BASE + 1;
|
|
760
|
+
return `<span class="${cls}" style="grid-column:${from} / ${to}">${left}<i class="sw-arc-m"></i>${right}</span>`;
|
|
761
|
+
}
|
|
762
|
+
function renderRow(row, partIdx, rowIdx, offset, ctx) {
|
|
649
763
|
const cells = groupRowIntoCells(row.notes);
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
for (const cell of cells) {
|
|
656
|
-
const firstCol = cell.start + 1;
|
|
657
|
-
const lastCol = cell.start + cell.notes.length;
|
|
658
|
-
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
764
|
+
const out = [];
|
|
765
|
+
if (row.label !== void 0)
|
|
766
|
+
out.push(`<span class="sw-label">${escapeHtml(ctx.tr(row.label))}</span>`);
|
|
767
|
+
cells.forEach((cell, ci) => {
|
|
768
|
+
const col = offset + ci + COL_BASE;
|
|
659
769
|
const opts = cell.kind === "group" ? cellPitchedOpts(cell) : [];
|
|
660
|
-
cell.
|
|
661
|
-
|
|
770
|
+
const barCls = cell.kind === "bar" ? " sw-bar-cell" : "";
|
|
771
|
+
const notes = cell.notes.map((note, j) => {
|
|
662
772
|
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
lyric.push(
|
|
683
|
-
`<span class="sw-lyr${j === 0 ? " sw-cs" : ""}" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
if (note.above !== void 0 && note.above.length > 0) {
|
|
687
|
-
above.push(`<span class="sw-above" style="${cellSpan}">${renderAnnotation(note.above, ctx)}</span>`);
|
|
688
|
-
}
|
|
689
|
-
const nonArc = (note.below ?? []).filter((b) => !isArcRaw(b.raw));
|
|
690
|
-
if (nonArc.length > 0) {
|
|
691
|
-
below.push(`<span class="sw-below" style="${cellSpan}">${renderAnnotation(nonArc, ctx)}</span>`);
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
for (const arc of arcs) {
|
|
696
|
-
const cls = [
|
|
697
|
-
"sw-arc",
|
|
698
|
-
arc.openLeft ? "sw-arc--openl" : "",
|
|
699
|
-
arc.openRight ? "sw-arc--openr" : "",
|
|
700
|
-
arc.line === true ? "sw-arc--line" : ""
|
|
701
|
-
].filter(Boolean).join(" ");
|
|
702
|
-
below.push(`<span class="${cls}" style="grid-column:${arc.startCol} / ${arc.endCol + 1}"></span>`);
|
|
703
|
-
}
|
|
704
|
-
const label = row.label !== void 0 ? `<span class="sw-label">${escapeHtml(ctx.tr(row.label))}</span>` : "";
|
|
705
|
-
return `<div class="sw-row">${label}<div class="sw-grid">${above.join("")}${notation.join("")}${below.join("")}${lyric.join("")}</div></div>`;
|
|
773
|
+
const cls = isPitched(note) ? " sw-p" : "";
|
|
774
|
+
const bar = note.bar !== void 0 ? ` sw-bar sw-bar--${note.bar}` : "";
|
|
775
|
+
return `<span class="sw-n${cls}${bar}" data-note="${path}">${renderNote(note, cell.kind, opts[j], ctx)}</span>`;
|
|
776
|
+
}).join("");
|
|
777
|
+
out.push(`<span class="sw-cell${barCls}" style="grid-column:${col}">${notes}</span>`);
|
|
778
|
+
const slices = cellLyricSlices(cell);
|
|
779
|
+
if (slices !== void 0) {
|
|
780
|
+
const html = slices.map((t) => `<span>${escapeHtml(lyricText(t, ctx))}</span>`).join("");
|
|
781
|
+
out.push(`<span class="sw-lyr" style="grid-column:${col}">${html}</span>`);
|
|
782
|
+
}
|
|
783
|
+
const above = cell.notes.flatMap((n) => n.above ?? []);
|
|
784
|
+
if (above.length > 0)
|
|
785
|
+
out.push(`<span class="sw-above" style="grid-column:${col}">${renderAnnotation(above, ctx)}</span>`);
|
|
786
|
+
const below = cell.notes.flatMap((n) => (n.below ?? []).filter((b) => !isArcRaw(b.raw)));
|
|
787
|
+
if (below.length > 0)
|
|
788
|
+
out.push(`<span class="sw-below" style="grid-column:${col}">${renderAnnotation(below, ctx)}</span>`);
|
|
789
|
+
});
|
|
790
|
+
for (const arc of resolveArcs(cells)) out.push(renderArc(arc, offset));
|
|
791
|
+
return `<div class="sw-row">${out.join("")}</div>`;
|
|
706
792
|
}
|
|
707
793
|
function renderHeader(meta, ctx) {
|
|
708
794
|
return renderMetaHeader(meta, ctx.lang, ctx.tr);
|
|
@@ -725,19 +811,52 @@ function renderToHtml(doc, opts = {}) {
|
|
|
725
811
|
const lang = opts.lang === "hi" || opts.lang === "en" ? opts.lang : "bn";
|
|
726
812
|
const tr = lang !== "bn" && opts.transliterate ? opts.transliterate : (s) => s;
|
|
727
813
|
const ctx = { lang, marks: MARKS[lang], tr };
|
|
814
|
+
const layout = alignRows(doc.parts.flatMap((p) => p.rows.map((r) => groupRowIntoCells(r.notes))));
|
|
815
|
+
const tracks = `grid-template-columns:max-content repeat(${layout.width},minmax(calc(var(--sw-matra, 1.4em) * var(--sw-tight, 1)),max-content))`;
|
|
728
816
|
const out = [`<div class="sw-sheet" lang="${lang}">`];
|
|
729
817
|
if (opts.showHeader !== false) out.push(renderHeader(doc.meta, ctx));
|
|
818
|
+
out.push(`<div class="sw-scroll"><div class="sw-block" style="${tracks}">`);
|
|
819
|
+
let n = 0;
|
|
730
820
|
doc.parts.forEach((part, p) => {
|
|
731
821
|
out.push('<section class="sw-part">');
|
|
732
822
|
if (part.name !== void 0)
|
|
733
823
|
out.push(`<h3 class="sw-part-name">${escapeHtml(ctx.tr(part.name))}</h3>`);
|
|
734
|
-
part.rows.forEach((row, r) => out.push(renderRow(row, p, r, ctx)));
|
|
824
|
+
part.rows.forEach((row, r) => out.push(renderRow(row, p, r, layout.offsets[n++], ctx)));
|
|
735
825
|
out.push("</section>");
|
|
736
826
|
});
|
|
737
|
-
out.push("</div>");
|
|
827
|
+
out.push("</div></div></div>");
|
|
738
828
|
return out.join("");
|
|
739
829
|
}
|
|
740
830
|
|
|
831
|
+
// src/viewer/fit.ts
|
|
832
|
+
function fitSwaralipi(root, minScale = 0, zoom = 1, minSpacing = 0.35, minSize = 12) {
|
|
833
|
+
const sheets = root.querySelectorAll(".sw-sheet");
|
|
834
|
+
for (const sheet of sheets) {
|
|
835
|
+
const scroll = sheet.querySelector(".sw-scroll");
|
|
836
|
+
const block = sheet.querySelector(".sw-block");
|
|
837
|
+
if (scroll === null || block === null) continue;
|
|
838
|
+
sheet.style.removeProperty("--sw-size");
|
|
839
|
+
sheet.style.removeProperty("--sw-tight");
|
|
840
|
+
const available = scroll.clientWidth;
|
|
841
|
+
let natural = block.scrollWidth;
|
|
842
|
+
if (available === 0 || natural === 0) continue;
|
|
843
|
+
for (const step of [0.75, 0.5, 0.25, 0]) {
|
|
844
|
+
if (natural <= available || minSpacing >= 1) break;
|
|
845
|
+
sheet.style.setProperty("--sw-tight", String(minSpacing + (1 - minSpacing) * step));
|
|
846
|
+
natural = block.scrollWidth;
|
|
847
|
+
}
|
|
848
|
+
const base = parseFloat(getComputedStyle(sheet).fontSize);
|
|
849
|
+
let fit = natural > available ? Math.max(minScale, available / natural) : 1;
|
|
850
|
+
if (base > 0 && minSize > 0) fit = Math.max(fit, Math.min(1, minSize / base));
|
|
851
|
+
const scale = fit * zoom;
|
|
852
|
+
if (scale === 1) continue;
|
|
853
|
+
sheet.style.setProperty("--sw-size", `${(base * scale).toFixed(3)}px`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
var MIN_ZOOM = 0.5;
|
|
857
|
+
var MAX_ZOOM = 3;
|
|
858
|
+
var ZOOM_STEP = 1.2;
|
|
859
|
+
|
|
741
860
|
// src/viewer/styles.ts
|
|
742
861
|
var NOTATION_FONT_URLS = {
|
|
743
862
|
Swarabitan: "https://lipighor.pages.dev/swarabitan/Swarabitan.ttf",
|
|
@@ -767,7 +886,10 @@ ${FONT_FACES}
|
|
|
767
886
|
margin: 0 0 0.75em;
|
|
768
887
|
padding: 0 0 0.5em;
|
|
769
888
|
border-bottom: 1px solid var(--sw-rule, rgba(128, 128, 128, 0.35));
|
|
770
|
-
|
|
889
|
+
/* Scales with the sheet, but never below the floor: fitSwaralipi shrinks
|
|
890
|
+
.sw-sheet's font-size to fit the notation, and the metadata is prose \u2014
|
|
891
|
+
it has no columns to align and must stay readable on a phone. */
|
|
892
|
+
font-size: max(0.95em, var(--sw-header-min, 0.95rem));
|
|
771
893
|
}
|
|
772
894
|
.sw-meta-row { display: flex; gap: 0.3em; }
|
|
773
895
|
.sw-meta-label { font-weight: 600; min-width: 4.5em; }
|
|
@@ -775,30 +897,42 @@ ${FONT_FACES}
|
|
|
775
897
|
|
|
776
898
|
.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }
|
|
777
899
|
|
|
778
|
-
/* ----
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
900
|
+
/* ---- block grid: ONE column set for the whole sheet ----
|
|
901
|
+
* NLTR emits a song as a single html table, which is why its lines align
|
|
902
|
+
* down the page. Same here: .sw-block owns the tracks, every .sw-row
|
|
903
|
+
* borrows them with subgrid, and .sw-part is display:contents so section
|
|
904
|
+
* headings sit in the same grid instead of breaking it. */
|
|
905
|
+
.sw-scroll { overflow-x: auto; }
|
|
906
|
+
.sw-block {
|
|
907
|
+
display: grid;
|
|
908
|
+
column-gap: calc(var(--sw-cell-gap, 0.4em) * var(--sw-tight, 1));
|
|
909
|
+
align-items: end;
|
|
910
|
+
width: max-content;
|
|
784
911
|
}
|
|
785
|
-
.sw-
|
|
786
|
-
.sw-
|
|
912
|
+
.sw-part { display: contents; }
|
|
913
|
+
.sw-row {
|
|
914
|
+
grid-column: 1 / -1;
|
|
787
915
|
display: grid;
|
|
916
|
+
grid-template-columns: subgrid;
|
|
788
917
|
grid-template-rows: auto auto auto auto;
|
|
789
|
-
grid-auto-columns: max-content;
|
|
790
918
|
align-items: end;
|
|
919
|
+
padding: 0.4em 0;
|
|
791
920
|
}
|
|
792
|
-
.sw-
|
|
793
|
-
|
|
921
|
+
.sw-label { grid-column: 1; grid-row: 2; padding-right: 0.5em; font-weight: 600; }
|
|
922
|
+
|
|
923
|
+
/* One column per cell; sub-notes are equal slices of it (min-width:0 kills
|
|
924
|
+
* flex's min-content floor, else a wide sub-note steals width from its
|
|
925
|
+
* neighbours while the syllable below it does not, and the two bands drift). */
|
|
926
|
+
.sw-cell, .sw-lyr { display: flex; justify-content: center; }
|
|
927
|
+
.sw-cell > .sw-n { white-space: nowrap; }
|
|
928
|
+
.sw-cell > .sw-n.sw-p,
|
|
929
|
+
.sw-lyr > span { flex: 1 1 0; min-width: 0; text-align: center; white-space: nowrap; }
|
|
930
|
+
.sw-cell { grid-row: 2; }
|
|
931
|
+
.sw-above { grid-row: 1; font-size: 0.62em; justify-self: center; white-space: nowrap; }
|
|
794
932
|
.sw-arc,
|
|
795
933
|
.sw-below { grid-row: 3; }
|
|
796
|
-
.sw-lyr { grid-row: 4;
|
|
797
|
-
.sw-below { font-size: 0.62em; justify-self:
|
|
798
|
-
|
|
799
|
-
/* inter-cell gap as padding on each cell's first notation item, so arcs
|
|
800
|
-
* and above-spans crossing cells stay continuous */
|
|
801
|
-
.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }
|
|
934
|
+
.sw-lyr { grid-row: 4; padding-top: 0.3em; }
|
|
935
|
+
.sw-below { font-size: 0.62em; justify-self: center; white-space: nowrap; }
|
|
802
936
|
|
|
803
937
|
/* ---- notation clusters & marks ---- */
|
|
804
938
|
.sw-cl { position: relative; display: inline-block; }
|
|
@@ -867,31 +1001,41 @@ ${FONT_FACES}
|
|
|
867
1001
|
opacity: 0.55;
|
|
868
1002
|
}
|
|
869
1003
|
|
|
870
|
-
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs ---- */
|
|
871
|
-
/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014
|
|
872
|
-
* never reset it with a padding shorthand here */
|
|
1004
|
+
/* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs, all langs ---- */
|
|
873
1005
|
.sw-bar {
|
|
874
|
-
grid-row: 2;
|
|
875
1006
|
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
876
1007
|
white-space: nowrap;
|
|
877
|
-
padding-right: 0.1em;
|
|
878
1008
|
}
|
|
879
1009
|
.sw-bar--quad { letter-spacing: 0.15em; }
|
|
1010
|
+
/* a bar owns a whole column but needs no matra of width */
|
|
1011
|
+
.sw-bar-cell { min-width: 0; }
|
|
880
1012
|
|
|
881
|
-
/* ---- under-arcs (
|
|
1013
|
+
/* ---- \u09AE\u09C0\u09A1\u09BC under-arcs (from w/x segments and spec meend) ----
|
|
1014
|
+
* Swarabitan draws the arc as three pieces \u2014 'w' the opening hook, 'y' a
|
|
1015
|
+
* flat fill, 'x' the closing hook. We use the font's own hooks and stretch
|
|
1016
|
+
* the fill as a CSS bar (the 'y' glyph IS a plain rectangle), so one arc
|
|
1017
|
+
* spans any number of columns. An open side = the arc continues onto the
|
|
1018
|
+
* next system: drop that hook and leave the fill squared off. */
|
|
882
1019
|
.sw-arc {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
.sw-arc
|
|
1020
|
+
display: flex;
|
|
1021
|
+
align-items: baseline;
|
|
1022
|
+
line-height: 1;
|
|
1023
|
+
font-family: Swarabitan, var(--sw-font, sans-serif);
|
|
1024
|
+
font-size: 0.9em;
|
|
1025
|
+
}
|
|
1026
|
+
.sw-arc-l, .sw-arc-r { flex: none; font-style: normal; }
|
|
1027
|
+
/* The fill's geometry is measured off the font's own 'y' glyph: ink 0.11em
|
|
1028
|
+
tall whose bottom sits 0.01em above the baseline. An empty inline-block
|
|
1029
|
+
takes its bottom margin edge as its baseline, so -0.01em lifts the bar to
|
|
1030
|
+
exactly where the hooks' flat stroke runs \u2014 no seam at either join. */
|
|
1031
|
+
.sw-arc-m {
|
|
1032
|
+
flex: 1 1 0;
|
|
1033
|
+
min-width: 0;
|
|
1034
|
+
align-self: baseline;
|
|
1035
|
+
height: 0.11em;
|
|
1036
|
+
margin-bottom: -0.01em;
|
|
1037
|
+
background: currentColor;
|
|
1038
|
+
}
|
|
895
1039
|
|
|
896
1040
|
/* ---- annotation content ---- */
|
|
897
1041
|
.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }
|
|
@@ -935,8 +1079,8 @@ var SWARALIPI_PRINT_CSS = `
|
|
|
935
1079
|
@page { margin: 16mm; }
|
|
936
1080
|
@media print {
|
|
937
1081
|
.sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
|
|
938
|
-
.sw-
|
|
939
|
-
.sw-
|
|
1082
|
+
.sw-scroll { overflow: visible !important; }
|
|
1083
|
+
.sw-row { break-inside: avoid; page-break-inside: avoid; }
|
|
940
1084
|
.sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
|
|
941
1085
|
.sw-hl { background: none !important; }
|
|
942
1086
|
/* Keep each engraved staff system whole across a page break (the abcjs line
|
|
@@ -1354,12 +1498,17 @@ ${SWARALIPI_STAFF_CSS}
|
|
|
1354
1498
|
${SWARALIPI_PRINT_CSS}
|
|
1355
1499
|
${watermarkCss(watermark)}
|
|
1356
1500
|
html, body { margin: 0; }
|
|
1357
|
-
|
|
1501
|
+
/* The printable width of A4 at the @page margin above. The sheet is one grid
|
|
1502
|
+
with one natural width, so the fit script needs a real page-sized box to
|
|
1503
|
+
measure against \u2014 the iframe's own width is not it. */
|
|
1504
|
+
body { width: 178mm; color: #111; background: #fff; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
1358
1505
|
.sw-sheet { max-width: 100%; }
|
|
1359
1506
|
.sw-footer { margin-top: 12px; padding-top: 6px; border-top: 1px solid #ddd; font-size: 12px; color: #555; text-align: right; }
|
|
1360
1507
|
</style>
|
|
1361
1508
|
</head>
|
|
1362
|
-
<body>${top}${bodyHtml}${bottom}
|
|
1509
|
+
<body>${top}${bodyHtml}${bottom}
|
|
1510
|
+
<script>window.__swFit=function(){(${fitSwaralipi.toString()})(document,0.45,1,undefined,0)};window.__swFit()</script>
|
|
1511
|
+
</body>
|
|
1363
1512
|
</html>`;
|
|
1364
1513
|
}
|
|
1365
1514
|
function renderPrintDocument(doc, opts = {}) {
|
|
@@ -1402,6 +1551,7 @@ function printHtmlInIframe(html) {
|
|
|
1402
1551
|
if (printed) return;
|
|
1403
1552
|
printed = true;
|
|
1404
1553
|
try {
|
|
1554
|
+
win.__swFit?.();
|
|
1405
1555
|
win.focus();
|
|
1406
1556
|
win.print();
|
|
1407
1557
|
} catch {
|
|
@@ -1448,7 +1598,7 @@ function ensureNotationFonts() {
|
|
|
1448
1598
|
var sharedSheet;
|
|
1449
1599
|
function createElementClass() {
|
|
1450
1600
|
return class SwaralipiSheet extends HTMLElement {
|
|
1451
|
-
static observedAttributes = ["src", "lang", "hide-header"];
|
|
1601
|
+
static observedAttributes = ["src", "lang", "hide-header", "min-spacing", "min-size"];
|
|
1452
1602
|
#doc;
|
|
1453
1603
|
#root;
|
|
1454
1604
|
#adopted = false;
|
|
@@ -1457,6 +1607,9 @@ function createElementClass() {
|
|
|
1457
1607
|
#translitLang;
|
|
1458
1608
|
#renderSeq = 0;
|
|
1459
1609
|
#staffOptions = {};
|
|
1610
|
+
#resize;
|
|
1611
|
+
#fittedAt = -1;
|
|
1612
|
+
#zoom = 1;
|
|
1460
1613
|
validate = false;
|
|
1461
1614
|
constructor() {
|
|
1462
1615
|
super();
|
|
@@ -1500,15 +1653,77 @@ function createElementClass() {
|
|
|
1500
1653
|
if (newValue !== null) void this.#load(newValue);
|
|
1501
1654
|
return;
|
|
1502
1655
|
}
|
|
1656
|
+
if (name === "min-spacing" || name === "min-size") {
|
|
1657
|
+
this.fit();
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1503
1660
|
if (name === "lang") this.#ensureTransliterator();
|
|
1504
1661
|
this.#render();
|
|
1505
1662
|
}
|
|
1506
1663
|
connectedCallback() {
|
|
1507
1664
|
this.#ensureTransliterator();
|
|
1508
1665
|
this.#render();
|
|
1666
|
+
if (this.#resize === void 0 && typeof ResizeObserver !== "undefined") {
|
|
1667
|
+
this.#resize = new ResizeObserver(([entry]) => {
|
|
1668
|
+
const w = entry?.contentRect.width ?? -1;
|
|
1669
|
+
if (w === this.#fittedAt) return;
|
|
1670
|
+
this.#fittedAt = w;
|
|
1671
|
+
this.#fit();
|
|
1672
|
+
});
|
|
1673
|
+
this.#resize.observe(this);
|
|
1674
|
+
}
|
|
1509
1675
|
}
|
|
1510
1676
|
disconnectedCallback() {
|
|
1511
1677
|
this.#abort?.abort();
|
|
1678
|
+
this.#resize?.disconnect();
|
|
1679
|
+
this.#resize = void 0;
|
|
1680
|
+
}
|
|
1681
|
+
/** Scale the notation to fit the available width, times the zoom factor. */
|
|
1682
|
+
#fit() {
|
|
1683
|
+
fitSwaralipi(this.#root, 0, this.#zoom, this.minSpacing, this.minSize);
|
|
1684
|
+
}
|
|
1685
|
+
/** Numeric attribute, or undefined for fitSwaralipi's own default. */
|
|
1686
|
+
#attrNum(name) {
|
|
1687
|
+
const value = parseFloat(this.getAttribute(name) ?? "");
|
|
1688
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1689
|
+
}
|
|
1690
|
+
#setAttrNum(name, value) {
|
|
1691
|
+
if (value === void 0) this.removeAttribute(name);
|
|
1692
|
+
else this.setAttribute(name, String(value));
|
|
1693
|
+
}
|
|
1694
|
+
get minSpacing() {
|
|
1695
|
+
return this.#attrNum("min-spacing");
|
|
1696
|
+
}
|
|
1697
|
+
set minSpacing(value) {
|
|
1698
|
+
this.#setAttrNum("min-spacing", value);
|
|
1699
|
+
}
|
|
1700
|
+
get minSize() {
|
|
1701
|
+
return this.#attrNum("min-size");
|
|
1702
|
+
}
|
|
1703
|
+
set minSize(value) {
|
|
1704
|
+
this.#setAttrNum("min-size", value);
|
|
1705
|
+
}
|
|
1706
|
+
/** Re-fit the current render (public for hosts that resize the sheet
|
|
1707
|
+
* themselves, e.g. a tab that was hidden while the document loaded). */
|
|
1708
|
+
fit() {
|
|
1709
|
+
this.#fittedAt = -1;
|
|
1710
|
+
this.#fit();
|
|
1711
|
+
}
|
|
1712
|
+
get zoom() {
|
|
1713
|
+
return this.#zoom;
|
|
1714
|
+
}
|
|
1715
|
+
set zoom(factor) {
|
|
1716
|
+
this.#zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, factor));
|
|
1717
|
+
this.#fit();
|
|
1718
|
+
}
|
|
1719
|
+
zoomIn() {
|
|
1720
|
+
this.zoom = this.#zoom * ZOOM_STEP;
|
|
1721
|
+
}
|
|
1722
|
+
zoomOut() {
|
|
1723
|
+
this.zoom = this.#zoom / ZOOM_STEP;
|
|
1724
|
+
}
|
|
1725
|
+
resetZoom() {
|
|
1726
|
+
this.zoom = 1;
|
|
1512
1727
|
}
|
|
1513
1728
|
async #load(url) {
|
|
1514
1729
|
this.#abort?.abort();
|
|
@@ -1540,6 +1755,7 @@ function createElementClass() {
|
|
|
1540
1755
|
}
|
|
1541
1756
|
#render() {
|
|
1542
1757
|
const seq = ++this.#renderSeq;
|
|
1758
|
+
this.#zoom = 1;
|
|
1543
1759
|
if (this.#doc === void 0) {
|
|
1544
1760
|
this.#root.innerHTML = this.#styleFallback();
|
|
1545
1761
|
return;
|
|
@@ -1558,6 +1774,7 @@ function createElementClass() {
|
|
|
1558
1774
|
transliterate: this.#translitLang === lang ? this.#translit : void 0
|
|
1559
1775
|
});
|
|
1560
1776
|
this.#root.innerHTML = this.#styleFallback() + html;
|
|
1777
|
+
this.#fit();
|
|
1561
1778
|
}
|
|
1562
1779
|
#renderError(message) {
|
|
1563
1780
|
const div = document.createElement("div");
|
|
@@ -1607,18 +1824,23 @@ exports.DEFAULT_WATERMARK = DEFAULT_WATERMARK;
|
|
|
1607
1824
|
exports.DIGITS = DIGITS;
|
|
1608
1825
|
exports.LABELS = LABELS;
|
|
1609
1826
|
exports.MARKS = MARKS;
|
|
1827
|
+
exports.MAX_ZOOM = MAX_ZOOM;
|
|
1828
|
+
exports.MIN_ZOOM = MIN_ZOOM;
|
|
1610
1829
|
exports.NOTATION_FONT_URLS = NOTATION_FONT_URLS;
|
|
1611
1830
|
exports.SWARALIPI_CSS = SWARALIPI_CSS;
|
|
1612
1831
|
exports.SWARALIPI_PRINT_CSS = SWARALIPI_PRINT_CSS;
|
|
1613
1832
|
exports.SWARALIPI_STAFF_CSS = SWARALIPI_STAFF_CSS;
|
|
1833
|
+
exports.ZOOM_STEP = ZOOM_STEP;
|
|
1614
1834
|
exports.abcDuration = abcDuration;
|
|
1615
1835
|
exports.abcToSvg = abcToSvg;
|
|
1836
|
+
exports.alignRows = alignRows;
|
|
1616
1837
|
exports.buildAbc = buildAbc;
|
|
1617
1838
|
exports.buildTaalAbc = buildTaalAbc;
|
|
1618
1839
|
exports.cellMatra = cellMatra;
|
|
1619
1840
|
exports.composeCluster = composeCluster;
|
|
1620
1841
|
exports.defineSwaralipiSheet = defineSwaralipiSheet;
|
|
1621
1842
|
exports.docToAbc = docToAbc;
|
|
1843
|
+
exports.fitSwaralipi = fitSwaralipi;
|
|
1622
1844
|
exports.groupRowIntoCells = groupRowIntoCells;
|
|
1623
1845
|
exports.loadLyricTransliterator = loadLyricTransliterator;
|
|
1624
1846
|
exports.localizeDigits = localizeDigits;
|