@ugfoundation/swaralipi-js 0.4.0 → 0.6.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.
Files changed (47) hide show
  1. package/README.md +8 -8
  2. package/dist/{batch-V4X3GN4N.js → batch-RUZGM2NR.js} +4 -4
  3. package/dist/{batch-V4X3GN4N.js.map → batch-RUZGM2NR.js.map} +1 -1
  4. package/dist/{chunk-6QW7MSJO.js → chunk-GHWURC4U.js} +98 -6
  5. package/dist/chunk-GHWURC4U.js.map +1 -0
  6. package/dist/{chunk-RTKWFJFI.js → chunk-ULK7NVKH.js} +8 -6
  7. package/dist/chunk-ULK7NVKH.js.map +1 -0
  8. package/dist/{chunk-ZS43BDP7.js → chunk-ZNVTOY7J.js} +7 -6
  9. package/dist/chunk-ZNVTOY7J.js.map +1 -0
  10. package/dist/cli.cjs +11 -8
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +2 -2
  13. package/dist/convert/index.cjs +10 -7
  14. package/dist/convert/index.cjs.map +1 -1
  15. package/dist/convert/index.d.cts +4 -3
  16. package/dist/convert/index.d.ts +4 -3
  17. package/dist/convert/index.js +2 -2
  18. package/dist/index.cjs +6 -4
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/player/index.cjs +222 -23
  24. package/dist/player/index.cjs.map +1 -1
  25. package/dist/player/index.d.cts +16 -4
  26. package/dist/player/index.d.ts +16 -4
  27. package/dist/player/index.js +120 -22
  28. package/dist/player/index.js.map +1 -1
  29. package/dist/{schedule-CAns78TD.d.ts → schedule-8WFUBJqB.d.ts} +82 -7
  30. package/dist/{schedule-D3JyEhuO.d.cts → schedule-CjPcD-Hf.d.cts} +82 -7
  31. package/dist/{schema-D3wyNRqM.d.ts → schema-D_86x4oV.d.cts} +21 -6
  32. package/dist/{schema-D3wyNRqM.d.cts → schema-D_86x4oV.d.ts} +21 -6
  33. package/dist/spec/index.cjs +6 -4
  34. package/dist/spec/index.cjs.map +1 -1
  35. package/dist/spec/index.d.cts +3 -3
  36. package/dist/spec/index.d.ts +3 -3
  37. package/dist/spec/index.js +1 -1
  38. package/dist/viewer/index.cjs +253 -172
  39. package/dist/viewer/index.cjs.map +1 -1
  40. package/dist/viewer/index.d.cts +16 -23
  41. package/dist/viewer/index.d.ts +16 -23
  42. package/dist/viewer/index.js +88 -47
  43. package/dist/viewer/index.js.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/chunk-6QW7MSJO.js.map +0 -1
  46. package/dist/chunk-RTKWFJFI.js.map +0 -1
  47. package/dist/chunk-ZS43BDP7.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D3wyNRqM.cjs';
2
- import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-D3JyEhuO.cjs';
1
+ import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D_86x4oV.cjs';
2
+ import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-CjPcD-Hf.cjs';
3
3
  import 'zod';
4
4
  import 'tone';
5
5
  import '../taals-CjzBuZ35.cjs';
@@ -77,25 +77,6 @@ declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Clus
77
77
  /** Map ASCII digits in `s` to the target script's digits. */
78
78
  declare function localizeDigits(s: string, lang: GridLang): string;
79
79
 
80
- /**
81
- * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
82
- * (Astro/static sites emit `renderToHtml(doc)` next to a
83
- * `<style>${SWARALIPI_CSS}</style>`) and unit-testable in plain Node.
84
- *
85
- * Layout model (mirrors NLTR's 4-row systems): each spec `Row` becomes one
86
- * CSS grid with **one column per top-level note** and four grid rows —
87
- * above-annotations / notation / below (slur arcs, marks) / lyric. Source
88
- * cells (groups of sub-notes sharing one matra) become column *spans*, so
89
- * per-sub-note lyric alignment and P2.3's per-note highlight targets fall
90
- * out of the column model for free.
91
- *
92
- * Rendering is spec-driven (semantics, not `raw`): grouping derives from
93
- * `durationMatra` accumulation so hand-authored documents without `raw`
94
- * render identically. `raw` is consulted only for (a) raw-only fallback
95
- * notes (never dropped — dimmed, in Swarabitan when loaded), (b) the ছেদ
96
- * cut mark and beat labels, whose grammar lives in raw (see codec.ts).
97
- */
98
-
99
80
  interface RenderOptions {
100
81
  /** Notation + UI language. Default `bn`. */
101
82
  lang?: ViewerLang;
@@ -119,7 +100,9 @@ interface Cell {
119
100
  * - bar / raw-only / carrier notes force-close and form singleton cells;
120
101
  * - full-matra notes are singleton groups;
121
102
  * - fractional notes accumulate until the sum reaches an integer matra
122
- * boundary (epsilon for 1/3, 0.2 chains), then the group closes.
103
+ * boundary (epsilon for 1/3, 0.2 chains), then the group closes;
104
+ * - `halfMatra` (the অর্ধমাত্রা ঃ) closes the cell on a half boundary — the
105
+ * only case durations alone cannot resolve (`সঃ গঃ` vs `সরা`).
123
106
  * The converter emits k notes × 1/k per source cell, so groups coincide
124
107
  * with source cells; an incomplete trailing group still closes at row end
125
108
  * (the matra carrier lands on its last note).
@@ -147,6 +130,16 @@ interface Arc {
147
130
  * A row with fill segments but no ends at all renders one straight line.
148
131
  */
149
132
  declare function resolveArcs(cells: Cell[], noteCount: number): Arc[];
133
+ /**
134
+ * The matra marks a cell's final note carries: its total duration as whole
135
+ * আ-কার matras plus an optional অর্ধমাত্রা ঃ (legend `রাঃ` = 1½, `সঃ` = ½).
136
+ * Only a `halfMatra` cell may show zero matras — every other cell keeps at
137
+ * least one, so an incomplete trailing group still gets its carrier.
138
+ */
139
+ declare function cellMatra(cell: Cell): {
140
+ matras: number;
141
+ half: boolean;
142
+ };
150
143
  /**
151
144
  * The নাম / তাল / রাগ / আবর্তন / স্বরলিপিকার header block, shared by the
152
145
  * akarmatrik grid and the staff view so both show the same metadata (localized
@@ -467,4 +460,4 @@ type Transliterator = (bengaliText: string) => string;
467
460
  */
468
461
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
469
462
 
470
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
463
+ export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
@@ -1,5 +1,5 @@
1
- import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D3wyNRqM.js';
2
- import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-CAns78TD.js';
1
+ import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D_86x4oV.js';
2
+ import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-8WFUBJqB.js';
3
3
  import 'zod';
4
4
  import 'tone';
5
5
  import '../taals-CjzBuZ35.js';
@@ -77,25 +77,6 @@ declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Clus
77
77
  /** Map ASCII digits in `s` to the target script's digits. */
78
78
  declare function localizeDigits(s: string, lang: GridLang): string;
79
79
 
80
- /**
81
- * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
82
- * (Astro/static sites emit `renderToHtml(doc)` next to a
83
- * `<style>${SWARALIPI_CSS}</style>`) and unit-testable in plain Node.
84
- *
85
- * Layout model (mirrors NLTR's 4-row systems): each spec `Row` becomes one
86
- * CSS grid with **one column per top-level note** and four grid rows —
87
- * above-annotations / notation / below (slur arcs, marks) / lyric. Source
88
- * cells (groups of sub-notes sharing one matra) become column *spans*, so
89
- * per-sub-note lyric alignment and P2.3's per-note highlight targets fall
90
- * out of the column model for free.
91
- *
92
- * Rendering is spec-driven (semantics, not `raw`): grouping derives from
93
- * `durationMatra` accumulation so hand-authored documents without `raw`
94
- * render identically. `raw` is consulted only for (a) raw-only fallback
95
- * notes (never dropped — dimmed, in Swarabitan when loaded), (b) the ছেদ
96
- * cut mark and beat labels, whose grammar lives in raw (see codec.ts).
97
- */
98
-
99
80
  interface RenderOptions {
100
81
  /** Notation + UI language. Default `bn`. */
101
82
  lang?: ViewerLang;
@@ -119,7 +100,9 @@ interface Cell {
119
100
  * - bar / raw-only / carrier notes force-close and form singleton cells;
120
101
  * - full-matra notes are singleton groups;
121
102
  * - fractional notes accumulate until the sum reaches an integer matra
122
- * boundary (epsilon for 1/3, 0.2 chains), then the group closes.
103
+ * boundary (epsilon for 1/3, 0.2 chains), then the group closes;
104
+ * - `halfMatra` (the অর্ধমাত্রা ঃ) closes the cell on a half boundary — the
105
+ * only case durations alone cannot resolve (`সঃ গঃ` vs `সরা`).
123
106
  * The converter emits k notes × 1/k per source cell, so groups coincide
124
107
  * with source cells; an incomplete trailing group still closes at row end
125
108
  * (the matra carrier lands on its last note).
@@ -147,6 +130,16 @@ interface Arc {
147
130
  * A row with fill segments but no ends at all renders one straight line.
148
131
  */
149
132
  declare function resolveArcs(cells: Cell[], noteCount: number): Arc[];
133
+ /**
134
+ * The matra marks a cell's final note carries: its total duration as whole
135
+ * আ-কার matras plus an optional অর্ধমাত্রা ঃ (legend `রাঃ` = 1½, `সঃ` = ½).
136
+ * Only a `halfMatra` cell may show zero matras — every other cell keeps at
137
+ * least one, so an incomplete trailing group still gets its carrier.
138
+ */
139
+ declare function cellMatra(cell: Cell): {
140
+ matras: number;
141
+ half: boolean;
142
+ };
150
143
  /**
151
144
  * The নাম / তাল / রাগ / আবর্তন / স্বরলিপিকার header block, shared by the
152
145
  * akarmatrik grid and the staff view so both show the same metadata (localized
@@ -467,4 +460,4 @@ type Transliterator = (bengaliText: string) => string;
467
460
  */
468
461
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
469
462
 
470
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
463
+ export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
@@ -1,4 +1,4 @@
1
- import { buildSchedule, drumPattern } from '../chunk-6QW7MSJO.js';
1
+ import { isArcRaw, glideTargets, buildSchedule, drumPattern } from '../chunk-GHWURC4U.js';
2
2
  import { TAALS } from '../chunk-6ETPFHZ6.js';
3
3
 
4
4
  // src/viewer/glyphs.ts
@@ -41,7 +41,9 @@ var MARKS = {
41
41
  var LABELS = {
42
42
  bn: { title: "\u09A8\u09BE\u09AE", taal: "\u09A4\u09BE\u09B2", raag: "\u09B0\u09BE\u0997", avartan: "\u0986\u09AC\u09B0\u09CD\u09A4\u09A8", notationBy: "\u09B8\u09CD\u09AC\u09B0\u09B2\u09BF\u09AA\u09BF\u0995\u09BE\u09B0" },
43
43
  hi: { title: "\u0928\u093E\u092E", taal: "\u0924\u093E\u0932", raag: "\u0930\u093E\u0917", avartan: "\u0906\u0935\u0930\u094D\u0924\u0928", notationBy: "\u0938\u094D\u0935\u0930\u0932\u093F\u092A\u093F\u0915\u093E\u0930" },
44
- en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Avartan", notationBy: "Notator" }
44
+ // "Cycle", not "Avartan": the English sheet is read by people who do not know the word, and
45
+ // one taal cycle is exactly what the number counts.
46
+ en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Cycles", notationBy: "Notator" }
45
47
  };
46
48
  function composeCluster(letter, opts) {
47
49
  const classes = [];
@@ -65,7 +67,6 @@ function localizeDigits(s, lang) {
65
67
  }
66
68
 
67
69
  // src/viewer/render.ts
68
- var NBSP = "\xA0";
69
70
  var EPS = 1e-6;
70
71
  var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS;
71
72
  function escapeHtml(s) {
@@ -100,7 +101,7 @@ function groupRowIntoCells(notes) {
100
101
  return;
101
102
  }
102
103
  const d = note.durationMatra ?? 1;
103
- if (isIntegral(d)) {
104
+ if (isIntegral(d) && note.halfMatra !== true) {
104
105
  close();
105
106
  cells.push({ kind: "group", start: i, notes: [note] });
106
107
  return;
@@ -108,13 +109,11 @@ function groupRowIntoCells(notes) {
108
109
  cur ??= { kind: "group", start: i, notes: [] };
109
110
  cur.notes.push(note);
110
111
  acc += d;
111
- if (acc > EPS && isIntegral(acc)) close();
112
+ if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
112
113
  });
113
114
  close();
114
115
  return cells;
115
116
  }
116
- var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
117
- var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
118
117
  function resolveArcs(cells, noteCount) {
119
118
  const arcs = [];
120
119
  let openStart;
@@ -183,15 +182,21 @@ function bracketGlyphs(note, edge) {
183
182
  function matraGlyph(ctx) {
184
183
  return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
185
184
  }
186
- function renderKan(note, ctx) {
187
- if (!note.kan) return "";
188
- const [octave, letter] = note.kan;
185
+ function renderGrace(svara, cls, ctx) {
186
+ if (svara === void 0) return "";
187
+ const [octave, letter] = svara;
189
188
  const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
190
- return `<span class="${["sw-kan", ...c.classes].join(" ")}">${c.text}</span>`;
189
+ return `<span class="${[cls, ...c.classes].join(" ")}">${c.text}</span>`;
190
+ }
191
+ function cellMatra(cell) {
192
+ const total = cell.notes.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
193
+ const half = cell.notes.some((n) => n.halfMatra === true);
194
+ const whole = Math.floor(total + EPS);
195
+ return { matras: half ? whole : Math.max(1, whole), half };
191
196
  }
192
197
  function renderPitchedInner(note, opts, ctx) {
193
198
  let inner = bracketGlyphs(note, "open");
194
- inner += renderKan(note, ctx);
199
+ inner += renderGrace(note.kan, "sw-kan", ctx);
195
200
  if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
196
201
  if (note.svara !== void 0) {
197
202
  const [octave, letter] = note.svara;
@@ -199,22 +204,27 @@ function renderPitchedInner(note, opts, ctx) {
199
204
  komal: note.komal,
200
205
  tivra: note.tivra,
201
206
  octave,
202
- withMatra: opts.withMatra,
207
+ // The first matra is composed into the cluster itself (bn: the font's
208
+ // trailing `a`); any further ones are separate glyphs below.
209
+ withMatra: opts.matras >= 1,
203
210
  lang: ctx.lang
204
211
  });
205
212
  inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
206
213
  } else if (note.rest === true) {
207
- inner += matraGlyph(ctx);
208
- } else if (note.sustain === true && opts.withMatra) {
209
- inner += matraGlyph(ctx);
214
+ inner += matraGlyph(ctx).repeat(Math.max(1, opts.matras));
215
+ } else if (note.sustain === true) {
216
+ inner += matraGlyph(ctx).repeat(opts.matras);
210
217
  }
211
218
  if (note.microtone !== void 0) {
212
219
  const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
213
220
  inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
214
221
  }
215
- const d = note.durationMatra ?? 1;
216
- if (isIntegral(d) && d > 1 + EPS) inner += matraGlyph(ctx).repeat(Math.round(d) - 1);
217
- if (opts.cuts > 0) inner += `<span class="sw-cut">${NBSP}${ctx.marks.cut}</span>`.repeat(opts.cuts);
222
+ inner += renderGrace(note.resh, "sw-resh", ctx);
223
+ if (note.svara !== void 0 && opts.matras > 1) {
224
+ inner += matraGlyph(ctx).repeat(opts.matras - 1);
225
+ }
226
+ if (opts.half) inner += `<span class="sw-half" title="${ctx.marks.cut}"></span>`;
227
+ if (opts.cuts > 0) inner += `<span class="sw-cut" title="${ctx.marks.cut}"></span>`.repeat(opts.cuts);
218
228
  inner += bracketGlyphs(note, "close");
219
229
  return inner;
220
230
  }
@@ -231,12 +241,12 @@ function renderAnnotation(notes, ctx) {
231
241
  continue;
232
242
  }
233
243
  if (cell.kind === "group") {
234
- const cuts = cutMarkCount(cell.notes[0].raw);
244
+ const { matras, half } = cellMatra(cell);
245
+ const cuts = half ? 0 : cutMarkCount(cell.notes[0].raw);
235
246
  cell.notes.forEach((note2, j) => {
236
247
  const last = j === cell.notes.length - 1;
237
- parts.push(
238
- `<span class="sw-n">${renderPitchedInner(note2, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
239
- );
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>`);
240
250
  });
241
251
  continue;
242
252
  }
@@ -274,7 +284,8 @@ function renderRow(row, partIdx, rowIdx, ctx) {
274
284
  const firstCol = cell.start + 1;
275
285
  const lastCol = cell.start + cell.notes.length;
276
286
  const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
277
- const cuts = cell.kind === "group" ? cutMarkCount(cell.notes[0].raw) : 0;
287
+ const { matras, half } = cellMatra(cell);
288
+ const cuts = cell.kind === "group" && !half ? cutMarkCount(cell.notes[0].raw) : 0;
278
289
  cell.notes.forEach((note, j) => {
279
290
  const col = cell.start + j + 1;
280
291
  const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
@@ -285,8 +296,9 @@ function renderRow(row, partIdx, rowIdx, ctx) {
285
296
  );
286
297
  } else if (cell.kind === "group") {
287
298
  const last = j === cell.notes.length - 1;
299
+ const pOpts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
288
300
  notation.push(
289
- `<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
301
+ `<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, pOpts, ctx)}</span>`
290
302
  );
291
303
  } else {
292
304
  const raw = rawDisplay(note);
@@ -359,9 +371,9 @@ function renderToHtml(doc, opts = {}) {
359
371
 
360
372
  // src/viewer/styles.ts
361
373
  var NOTATION_FONT_URLS = {
362
- Swarabitan: "https://webfonts-4qq.pages.dev/swarabitan/Swarabitan.ttf",
363
- ome_bhatkhande_hindi: "https://webfonts-4qq.pages.dev/ome-bhatkhande/OmeBhatkhandeHindi.ttf",
364
- ome_bhatkhande_english: "https://webfonts-4qq.pages.dev/ome-bhatkhande/OmeBhatkhandeEnglish.ttf"
374
+ Swarabitan: "https://lipighor.pages.dev/swarabitan/Swarabitan.ttf",
375
+ ome_bhatkhande_hindi: "https://lipighor.pages.dev/ome-bhatkhande/OmeBhatkhandeHindi.ttf",
376
+ ome_bhatkhande_english: "https://lipighor.pages.dev/ome-bhatkhande/OmeBhatkhandeEnglish.ttf"
365
377
  };
366
378
  NOTATION_FONT_URLS.Swarabitan;
367
379
  var FONT_FACES = Object.entries(NOTATION_FONT_URLS).map(
@@ -377,9 +389,9 @@ ${FONT_FACES}
377
389
  }
378
390
 
379
391
  /* ---- notation font, per language (the note clusters + grace notes) ---- */
380
- .sw-sheet[lang="bn"] .sw-cl, .sw-sheet[lang="bn"] .sw-kan { font-family: Swarabitan, var(--sw-font, sans-serif); }
381
- .sw-sheet[lang="hi"] .sw-cl, .sw-sheet[lang="hi"] .sw-kan { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
382
- .sw-sheet[lang="en"] .sw-cl, .sw-sheet[lang="en"] .sw-kan { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
392
+ .sw-sheet[lang="bn"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: Swarabitan, var(--sw-font, sans-serif); }
393
+ .sw-sheet[lang="hi"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
394
+ .sw-sheet[lang="en"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
383
395
 
384
396
  /* ---- header ---- */
385
397
  .sw-header {
@@ -438,18 +450,44 @@ ${FONT_FACES}
438
450
  .sw-ati-taar::before { top: -0.3em; }
439
451
  .sw-ati-mandra::before { bottom: -0.35em; }
440
452
 
441
- /* kan grace: small raised cluster left of the main note */
442
- .sw-kan {
453
+ /* grace notes: small raised clusters \u2014 \u0995\u09A3 before the note, \u09B0\u09C7\u09B6 after it */
454
+ .sw-kan,
455
+ .sw-resh {
443
456
  position: relative;
444
457
  display: inline-block;
445
458
  font-size: 0.58em;
446
459
  vertical-align: 0.75em;
447
- margin-right: 0.08em;
448
460
  }
461
+ .sw-kan { margin-right: 0.08em; }
462
+ .sw-resh { margin-left: 0.08em; }
449
463
 
450
464
  .sw-hy { padding-right: 0.02em; }
451
465
  .sw-iso { display: inline-block; margin-left: -0.18em; }
452
- .sw-cut { font-size: 0.8em; display: inline-block; margin-left: -0.15em; }
466
+ /* The \u0985\u09B0\u09CD\u09A7\u09AE\u09BE\u09A4\u09CD\u09B0\u09BE / \u099B\u09C7\u09A6 mark: two stacked rings (legend p. \u09E7\u09E9\u09EB), drawn rather
467
+ * than typed \u2014 an isolated \u0983 has no base, so every Bengali font shapes it with
468
+ * a dotted circle (an NBSP carrier does not help). sw-half = semantic
469
+ * (halfMatra), sw-cut = decoded from a source cell's raw: one look, two
470
+ * provenances. */
471
+ .sw-cut,
472
+ .sw-half {
473
+ position: relative;
474
+ display: inline-block;
475
+ width: 0.3em;
476
+ height: 1em;
477
+ vertical-align: baseline;
478
+ }
479
+ .sw-cut::before, .sw-cut::after,
480
+ .sw-half::before, .sw-half::after {
481
+ content: '';
482
+ position: absolute;
483
+ left: 0.08em;
484
+ width: 0.15em;
485
+ height: 0.15em;
486
+ border-radius: 50%;
487
+ background: currentColor;
488
+ }
489
+ .sw-cut::before, .sw-half::before { top: 0.2em; }
490
+ .sw-cut::after, .sw-half::after { top: 0.55em; }
453
491
  .sw-br { opacity: 0.8; padding: 0 0.06em; }
454
492
  .sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
455
493
 
@@ -606,20 +644,23 @@ function buildAbc(schedule, lyricOf, opts) {
606
644
  barPos -= beatsPerBar;
607
645
  }
608
646
  };
609
- for (const ev of schedule.events) {
647
+ schedule.events.forEach((ev, i) => {
610
648
  if (ev.midi === null) {
611
649
  if (ev.kind === "rest") {
612
650
  tokens.push(`z${abcDuration(ev.durationBeats)}`);
613
651
  words.push("*");
614
652
  advance(ev.durationBeats);
615
653
  }
616
- continue;
654
+ return;
617
655
  }
618
- tokens.push(abcNote(ev));
656
+ const more = glideTargets(schedule.events, i).length > 0;
657
+ const slurOpen = ev.glide !== true && more ? "(" : "";
658
+ const slurClose = ev.glide === true && !more ? ")" : "";
659
+ tokens.push(`${slurOpen}${abcNote(ev)}${slurClose}`);
619
660
  const lyr = lyricOf(ev);
620
661
  words.push(lyr ? abcText(lyr).replace(/\s+/g, "~") : "*");
621
662
  advance(ev.durationBeats);
622
- }
663
+ });
623
664
  if (tokens.length && tokens[tokens.length - 1] !== "|") tokens.push("|]");
624
665
  else if (tokens.length) tokens[tokens.length - 1] = "|]";
625
666
  const meter = ratioMeter(beatsPerBar);
@@ -790,26 +831,26 @@ function escapeHtml2(s) {
790
831
  }
791
832
  function watermarkCss(text) {
792
833
  if (!text) return "";
834
+ const span = 92;
835
+ const size = Math.min(18, span / (0.62 * text.length));
836
+ const len = Math.min(span, 0.62 * text.length * size);
837
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"><text x="50" y="50" dy=".35em" text-anchor="middle" font-family="sans-serif" font-weight="700" fill="rgba(0,0,0,0.07)" font-size="${size.toFixed(2)}" textLength="${len.toFixed(2)}" lengthAdjust="spacingAndGlyphs" transform="rotate(-35 50 50)">${escapeHtml2(text)}</text></svg>`;
793
838
  return `
794
839
  .sw-watermark {
795
840
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
796
- display: flex; align-items: center; justify-content: center;
797
- overflow: hidden;
841
+ background: url("data:image/svg+xml,${encodeURIComponent(svg)}") center / contain no-repeat;
798
842
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
799
843
  }
800
- .sw-watermark span {
801
- font-size: 11vw; font-weight: 700; letter-spacing: .08em;
802
- color: rgba(0,0,0,0.07); transform: rotate(-35deg); white-space: nowrap;
803
- }
804
844
  body > .sw-sheet, body > .sw-footer { position: relative; z-index: 1; }
805
845
  `;
806
846
  }
807
847
  function chrome(text, notator) {
808
- const top = text ? `<div class="sw-watermark" aria-hidden="true"><span>${escapeHtml2(text)}</span></div>` : "";
848
+ const top = text ? '<div class="sw-watermark" aria-hidden="true"></div>' : "";
809
849
  const bottom = notator ? `<footer class="sw-footer">${escapeHtml2(notator)}</footer>` : "";
810
850
  return { top, bottom };
811
851
  }
812
852
  function resolveNotator(doc, opts) {
853
+ if (opts.notator === "") return "";
813
854
  return doc.meta.notationBy ?? opts.notator ?? DEFAULT_NOTATOR;
814
855
  }
815
856
  function pageHtml(lang, title, bodyHtml, opts, doc) {
@@ -1075,6 +1116,6 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
1075
1116
  return ctor;
1076
1117
  }
1077
1118
 
1078
- export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, abcDuration, abcToSvg, buildAbc, buildTaalAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
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 };
1079
1120
  //# sourceMappingURL=index.js.map
1080
1121
  //# sourceMappingURL=index.js.map