@ugfoundation/swaralipi-js 0.1.6 → 0.2.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.
@@ -1,4 +1,4 @@
1
- export { SPEC_VERSION, barSchema, bracketSchema, metaSchema, microtoneSchema, noteSchema, octaveSchema, parseSwaralipi, partSchema, rowSchema, safeParseSwaralipi, svaraLetterSchema, svaraSchema, swaralipiSchema, taalSchema } from '../chunk-QXDQAPE3.js';
1
+ export { SPEC_VERSION, barSchema, bracketSchema, metaSchema, microtoneSchema, noteSchema, octaveSchema, parseSwaralipi, partSchema, rowSchema, safeParseSwaralipi, svaraLetterSchema, svaraSchema, swaralipiSchema, taalSchema } from '../chunk-KYNCQ7OF.js';
2
2
  export { TAALS } from '../chunk-H6HC44PZ.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -179,50 +179,46 @@ var init_spec = __esm({
179
179
  init_schema();
180
180
  init_schema();
181
181
  init_taals();
182
- SPEC_VERSION = "0.1.6";
182
+ SPEC_VERSION = "0.2.1";
183
183
  }
184
184
  });
185
185
 
186
186
  // src/viewer/glyphs.ts
187
- var LETTERS = {
188
- bn: {
189
- s: { shuddha: "\u09B8" },
190
- r: { shuddha: "\u09B0", komal: "\u098B" },
191
- g: { shuddha: "\u0997", komal: "\u099C\u09CD\u099E" },
192
- m: { shuddha: "\u09AE", tivra: "\u0995\u09CD\u09B7" },
193
- p: { shuddha: "\u09AA" },
194
- d: { shuddha: "\u09A7", komal: "\u09A6" },
195
- n: { shuddha: "\u09A8", komal: "\u09A3" }
196
- },
197
- hi: {
198
- s: { shuddha: "\u0938" },
199
- r: { shuddha: "\u0930", komal: "\u090B" },
200
- g: { shuddha: "\u0917", komal: "\u091C\u094D\u091E" },
201
- m: { shuddha: "\u092E", tivra: "\u0915\u094D\u0937" },
202
- p: { shuddha: "\u092A" },
203
- d: { shuddha: "\u0927", komal: "\u0926" },
204
- n: { shuddha: "\u0928", komal: "\u0923" }
205
- },
206
- en: {
207
- s: { shuddha: "S" },
208
- r: { shuddha: "R" },
209
- g: { shuddha: "G" },
210
- m: { shuddha: "M" },
211
- p: { shuddha: "P" },
212
- d: { shuddha: "D" },
213
- n: { shuddha: "N" }
214
- }
187
+ var SWARABITAN_SHUDDHA = {
188
+ s: "s",
189
+ r: "r",
190
+ g: "g",
191
+ m: "m",
192
+ p: "p",
193
+ d: "q",
194
+ n: "n"
195
+ // q = ধ
196
+ };
197
+ var SWARABITAN_KOMAL = {
198
+ r: "v",
199
+ g: "t",
200
+ d: "d",
201
+ n: "u"
202
+ // ঋ জ্ঞ দ ণ
203
+ };
204
+ var SWARABITAN_TIVRA = { m: "k" };
205
+ var BHATKHANDE_OCTAVE = {
206
+ madhyam: "",
207
+ taar: "u",
208
+ mandra: "l",
209
+ "ati-taar": "U",
210
+ "ati-mandra": "L"
215
211
  };
212
+ var BHATKHANDE_HAS_KOMAL = /* @__PURE__ */ new Set(["r", "g", "d", "n"]);
216
213
  var DIGITS = {
217
214
  bn: "\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF",
218
215
  hi: "\u0966\u0967\u0968\u0969\u096A\u096B\u096C\u096D\u096E\u096F",
219
216
  en: "0123456789"
220
217
  };
221
- var VOWEL_LETTER_GLYPHS = /* @__PURE__ */ new Set(["\u098B", "\u090B"]);
222
218
  var MARKS = {
223
- bn: { matra: "\u09BE", hyphen: "-", melisma: "\u09F9", cut: "\u0983", reph: "\u09B0\u09CD" },
224
- hi: { matra: "\u093E", hyphen: "-", melisma: "\u0970", cut: "\u0903", reph: "\u0930\u094D" },
225
- en: { matra: "", hyphen: "-", melisma: "\xB7", cut: ":", reph: "" }
219
+ bn: { hyphen: "-", melisma: "\u09F9", cut: "\u0983" },
220
+ hi: { hyphen: "-", melisma: "\u0970", cut: "\u0903" },
221
+ en: { hyphen: "-", melisma: "\xB7", cut: ":" }
226
222
  };
227
223
  var LABELS = {
228
224
  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" },
@@ -230,40 +226,20 @@ var LABELS = {
230
226
  en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Avartan", notationBy: "Notation" }
231
227
  };
232
228
  function composeCluster(letter, opts) {
233
- const forms = LETTERS[opts.lang][letter];
234
- const marks = MARKS[opts.lang];
235
229
  const classes = [];
236
- let base = forms.shuddha;
237
- if (opts.komal) {
238
- if (forms.komal !== void 0) base = forms.komal;
239
- else classes.push("sw-komal");
240
- }
241
- if (opts.tivra) {
242
- if (forms.tivra !== void 0) base = forms.tivra;
243
- else classes.push("sw-tivra");
244
- }
245
- const isVowelLetter = VOWEL_LETTER_GLYPHS.has(base);
246
- let text = base;
247
- switch (opts.octave) {
248
- case "taar":
249
- case "ati-taar":
250
- if (marks.reph === "") classes.push("sw-dot-above");
251
- else if (isVowelLetter) classes.push("sw-reph-css");
252
- else text = marks.reph + text;
253
- if (opts.octave === "ati-taar") classes.push("sw-ati-taar");
254
- break;
255
- case "mandra":
256
- case "ati-mandra":
257
- classes.push(marks.reph === "" ? "sw-dot-below" : "sw-mandra");
258
- if (opts.octave === "ati-mandra") classes.push("sw-ati-mandra");
259
- break;
230
+ if (opts.lang === "bn") {
231
+ let text = opts.tivra && SWARABITAN_TIVRA[letter] || opts.komal && SWARABITAN_KOMAL[letter] || SWARABITAN_SHUDDHA[letter];
232
+ if (opts.octave === "taar" || opts.octave === "ati-taar") text += "f";
233
+ else if (opts.octave === "mandra" || opts.octave === "ati-mandra") text += "h";
234
+ if (opts.octave === "ati-taar") classes.push("sw-ati-taar");
235
+ if (opts.octave === "ati-mandra") classes.push("sw-ati-mandra");
236
+ if (opts.withMatra) text += "a";
237
+ return { text, classes };
260
238
  }
261
- let matraSeparate = false;
262
- if (opts.withMatra && marks.matra !== "") {
263
- if (isVowelLetter) matraSeparate = true;
264
- else text += marks.matra;
265
- }
266
- return { text, classes, matraSeparate };
239
+ let base = letter;
240
+ if (opts.tivra && letter === "m") base = "M";
241
+ else if (opts.komal && BHATKHANDE_HAS_KOMAL.has(letter)) base = base.toUpperCase();
242
+ return { text: base + BHATKHANDE_OCTAVE[opts.octave], classes };
267
243
  }
268
244
  function localizeDigits(s, lang) {
269
245
  const digits = DIGITS[lang];
@@ -386,11 +362,8 @@ function bracketGlyphs(note, edge) {
386
362
  if (!note.brackets) return "";
387
363
  return note.brackets.filter((b) => b.edge === edge).map((b) => `<span class="sw-br">${BRACKET_GLYPHS[edge][b.kind]}</span>`).join("");
388
364
  }
389
- function isoMatra(ctx) {
390
- return ctx.marks.matra === "" ? "" : `<span class="sw-iso">${NBSP}${ctx.marks.matra}</span>`;
391
- }
392
- function restGlyph(ctx) {
393
- return ctx.marks.matra === "" ? '<span class="sw-iso">\u2013</span>' : isoMatra(ctx);
365
+ function matraGlyph(ctx) {
366
+ return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
394
367
  }
395
368
  function renderKan(note, ctx) {
396
369
  if (!note.kan) return "";
@@ -412,26 +385,24 @@ function renderPitchedInner(note, opts, ctx) {
412
385
  lang: ctx.lang
413
386
  });
414
387
  inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
415
- if (c.matraSeparate) inner += isoMatra(ctx);
416
388
  } else if (note.rest === true) {
417
- inner += restGlyph(ctx);
389
+ inner += matraGlyph(ctx);
418
390
  } else if (note.sustain === true && opts.withMatra) {
419
- inner += isoMatra(ctx);
391
+ inner += matraGlyph(ctx);
420
392
  }
421
393
  if (note.microtone !== void 0) {
422
- const mark = note.microtone === "atikomal" ? "\u21CA" : "\u2193";
394
+ const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
423
395
  inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
424
396
  }
425
397
  const d = note.durationMatra ?? 1;
426
- if (isIntegral(d) && d > 1 + EPS) inner += isoMatra(ctx).repeat(Math.round(d) - 1);
398
+ if (isIntegral(d) && d > 1 + EPS) inner += matraGlyph(ctx).repeat(Math.round(d) - 1);
427
399
  if (opts.cuts > 0) inner += `<span class="sw-cut">${NBSP}${ctx.marks.cut}</span>`.repeat(opts.cuts);
428
400
  inner += bracketGlyphs(note, "close");
429
401
  return inner;
430
402
  }
431
- var BAR_STROKES = { dari: 1, single: 1, double: 2, quad: 4 };
403
+ var BAR_GLYPHS = { dari: "A", single: "l", double: "L", quad: "LL" };
432
404
  function renderBarInner(note) {
433
- const strokes = "<i></i>".repeat(BAR_STROKES[note.bar]);
434
- return `${bracketGlyphs(note, "open")}${strokes}${bracketGlyphs(note, "close")}`;
405
+ return `${bracketGlyphs(note, "open")}${BAR_GLYPHS[note.bar]}${bracketGlyphs(note, "close")}`;
435
406
  }
436
407
  function renderAnnotation(notes, ctx) {
437
408
  const parts = [];
@@ -510,7 +481,7 @@ function renderRow(row, partIdx, rowIdx, ctx) {
510
481
  }
511
482
  if (note.lyric !== void 0) {
512
483
  lyric.push(
513
- `<span class="sw-lyr" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
484
+ `<span class="sw-lyr${j === 0 ? " sw-cs" : ""}" style="grid-column:${col}">${escapeHtml(lyricText(note.lyric, ctx))}</span>`
514
485
  );
515
486
  }
516
487
  if (note.above !== void 0 && note.above.length > 0) {
@@ -566,13 +537,29 @@ function renderToHtml(doc, opts = {}) {
566
537
  }
567
538
 
568
539
  // src/viewer/styles.ts
540
+ var NOTATION_FONT_URLS = {
541
+ Swarabitan: "https://cdn.daserve.in/webfonts/swarabitan/Swarabitan.ttf",
542
+ ome_bhatkhande_hindi: "https://cdn.daserve.in/webfonts/ome-bhatkhande/OmeBhatkhandeHindi.ttf",
543
+ ome_bhatkhande_english: "https://cdn.daserve.in/webfonts/ome-bhatkhande/OmeBhatkhandeEnglish.ttf"
544
+ };
545
+ NOTATION_FONT_URLS.Swarabitan;
546
+ var FONT_FACES = Object.entries(NOTATION_FONT_URLS).map(
547
+ ([family, url]) => `@font-face { font-family: ${family}; src: url('${url}') format('truetype'); font-display: block; }`
548
+ ).join("\n");
569
549
  var SWARALIPI_CSS = `
550
+ ${FONT_FACES}
551
+
570
552
  .sw-sheet {
571
553
  font-family: var(--sw-font, 'Noto Sans Bengali', 'Kohinoor Bangla', 'Bangla Sangam MN', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif);
572
554
  font-size: var(--sw-size, 1.125rem);
573
555
  line-height: 1.5;
574
556
  }
575
557
 
558
+ /* ---- notation font, per language (the note clusters + grace notes) ---- */
559
+ .sw-sheet[lang="bn"] .sw-cl, .sw-sheet[lang="bn"] .sw-kan { font-family: Swarabitan, var(--sw-font, sans-serif); }
560
+ .sw-sheet[lang="hi"] .sw-cl, .sw-sheet[lang="hi"] .sw-kan { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
561
+ .sw-sheet[lang="en"] .sw-cl, .sw-sheet[lang="en"] .sw-kan { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
562
+
576
563
  /* ---- header ---- */
577
564
  .sw-header {
578
565
  margin: 0 0 0.75em;
@@ -601,10 +588,10 @@ var SWARALIPI_CSS = `
601
588
  align-items: end;
602
589
  }
603
590
  .sw-above { grid-row: 1; font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }
604
- .sw-n { grid-row: 2; white-space: nowrap; }
591
+ .sw-n { grid-row: 2; justify-self: center; white-space: nowrap; }
605
592
  .sw-arc,
606
593
  .sw-below { grid-row: 3; }
607
- .sw-lyr { grid-row: 4; white-space: nowrap; padding-top: 0.3em; }
594
+ .sw-lyr { grid-row: 4; justify-self: center; white-space: nowrap; padding-top: 0.3em; }
608
595
  .sw-below { font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }
609
596
 
610
597
  /* inter-cell gap as padding on each cell's first notation item, so arcs
@@ -614,71 +601,21 @@ var SWARALIPI_CSS = `
614
601
  /* ---- notation clusters & marks ---- */
615
602
  .sw-cl { position: relative; display: inline-block; }
616
603
 
617
- /* mandra: hasanta-shaped understroke (a real \u09CD before \u09BE is invalid Unicode) */
618
- .sw-mandra::after {
619
- content: '';
620
- position: absolute;
621
- left: 0.1em;
622
- bottom: -0.08em;
623
- width: 0.45em;
624
- height: 0.09em;
625
- background: currentColor;
626
- transform: rotate(-32deg);
627
- }
628
- /* ati-mandra: an extra dot below */
604
+ /* bn ati-octave: Swarabitan's f/h give a single reph/hasanta, so ati adds
605
+ * one extra dot above (ati-taar) / below (ati-mandra). bn-only \u2014 hi/en get
606
+ * the two-dot glyph natively from the U/L octave suffix. */
607
+ .sw-ati-taar::before,
629
608
  .sw-ati-mandra::before {
630
609
  content: '';
631
610
  position: absolute;
632
611
  left: 0.25em;
633
- bottom: -0.35em;
634
612
  width: 0.16em;
635
613
  height: 0.16em;
636
614
  border-radius: 50%;
637
615
  background: currentColor;
638
616
  }
639
- /* ati-taar (reph already in the text): an extra dot above */
640
- .sw-ati-taar::before {
641
- content: '';
642
- position: absolute;
643
- left: 0.25em;
644
- top: -0.3em;
645
- width: 0.16em;
646
- height: 0.16em;
647
- border-radius: 50%;
648
- background: currentColor;
649
- }
650
- /* CSS reph fallback for vowel letters \u098B/\u090B that reject a real reph */
651
- .sw-reph-css::before {
652
- content: '';
653
- position: absolute;
654
- top: -0.12em;
655
- left: 0.12em;
656
- width: 0.3em;
657
- height: 0.3em;
658
- border: 0.09em solid currentColor;
659
- border-right: none;
660
- border-bottom: none;
661
- border-top-left-radius: 0.3em;
662
- }
663
- /* en octave dots (Bhatkhande-style) */
664
- .sw-dot-above::before,
665
- .sw-dot-below::before {
666
- content: '';
667
- position: absolute;
668
- left: 50%;
669
- margin-left: -0.07em;
670
- width: 0.14em;
671
- height: 0.14em;
672
- border-radius: 50%;
673
- background: currentColor;
674
- }
675
- .sw-dot-above::before { top: -0.25em; }
676
- .sw-dot-below::before { bottom: -0.2em; }
677
- .sw-ati-taar.sw-dot-above::before,
678
- .sw-ati-mandra.sw-dot-below::before { box-shadow: 0.24em 0 0 currentColor; margin-left: -0.19em; }
679
- /* en accidentals (bn/hi use distinct letters instead) */
680
- .sw-komal { text-decoration: underline; text-underline-offset: 0.18em; }
681
- .sw-tivra::after { content: '\\2032'; font-size: 0.7em; vertical-align: super; }
617
+ .sw-ati-taar::before { top: -0.3em; }
618
+ .sw-ati-mandra::before { bottom: -0.35em; }
682
619
 
683
620
  /* kan grace: small raised cluster left of the main note */
684
621
  .sw-kan {
@@ -695,29 +632,25 @@ var SWARALIPI_CSS = `
695
632
  .sw-br { opacity: 0.8; padding: 0 0.06em; }
696
633
  .sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
697
634
 
698
- /* raw fallback: never dropped, visibly non-notation */
635
+ /* raw fallback: never dropped, visibly non-notation. Swarabitan first \u2014
636
+ * raw IS that font's encoding, so undecoded cells show NLTR's own glyphs
637
+ * (dimmed: semantics unverified); monospace ASCII when the font is out. */
699
638
  .sw-raw {
700
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
639
+ font-family: Swarabitan, ui-monospace, SFMono-Regular, Menlo, monospace;
701
640
  font-size: 0.72em;
702
641
  opacity: 0.55;
703
642
  }
704
643
 
705
- /* ---- bars: dari (thin) and danda strokes, notation-row height ---- */
644
+ /* ---- bars: dari (A) and danda (l/L) Swarabitan glyphs ---- */
706
645
  /* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014
707
646
  * never reset it with a padding shorthand here */
708
647
  .sw-bar {
709
648
  grid-row: 2;
710
- align-self: stretch;
711
- display: inline-flex;
712
- align-items: stretch;
713
- gap: 0.16em;
649
+ font-family: Swarabitan, var(--sw-font, sans-serif);
650
+ white-space: nowrap;
714
651
  padding-right: 0.1em;
715
652
  }
716
- .sw-bar i { display: inline-block; width: 0.14em; min-height: 1.25em; background: currentColor; }
717
- .sw-bar--dari i { width: 0.05em; opacity: 0.9; }
718
- .sw-bar--quad i:nth-child(3) { margin-left: 0.18em; }
719
- .sw-above .sw-bar, .sw-below .sw-bar { display: inline-flex; vertical-align: baseline; }
720
- .sw-above .sw-bar i, .sw-below .sw-bar i { min-height: 1em; }
653
+ .sw-bar--quad { letter-spacing: 0.15em; }
721
654
 
722
655
  /* ---- under-arcs (slurs decoded from w/x segments and meend) ---- */
723
656
  .sw-arc {
@@ -826,7 +759,10 @@ function printSwaralipi(doc, opts = {}) {
826
759
  }
827
760
  const cleanup = () => setTimeout(() => iframe.remove(), 1e3);
828
761
  win.addEventListener("afterprint", cleanup, { once: true });
762
+ let printed = false;
829
763
  const go = () => {
764
+ if (printed) return;
765
+ printed = true;
830
766
  try {
831
767
  win.focus();
832
768
  win.print();
@@ -834,8 +770,14 @@ function printSwaralipi(doc, opts = {}) {
834
770
  }
835
771
  };
836
772
  const fonts = iframe.contentDocument?.fonts;
837
- if (fonts?.ready) void fonts.ready.then(go).catch(go);
838
- else setTimeout(go, 250);
773
+ if (fonts && typeof fonts.forEach === "function") {
774
+ const loads = [Promise.resolve(fonts.ready)];
775
+ fonts.forEach((face) => loads.push(face.load().catch(() => void 0)));
776
+ void Promise.all(loads).then(go);
777
+ setTimeout(go, 3e3);
778
+ } else {
779
+ setTimeout(go, 250);
780
+ }
839
781
  };
840
782
  iframe.srcdoc = html;
841
783
  document.body.appendChild(iframe);
@@ -856,6 +798,15 @@ async function loadLyricTransliterator(lang) {
856
798
 
857
799
  // src/viewer/element.ts
858
800
  var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en"]);
801
+ function ensureNotationFonts() {
802
+ if (typeof FontFace === "undefined" || typeof document === "undefined") return;
803
+ const present = /* @__PURE__ */ new Set();
804
+ for (const f of document.fonts) present.add(f.family.replace(/['"]/g, ""));
805
+ for (const [family, url] of Object.entries(NOTATION_FONT_URLS)) {
806
+ if (present.has(family)) continue;
807
+ document.fonts.add(new FontFace(family, `url(${url})`, { display: "block" }));
808
+ }
809
+ }
859
810
  var sharedSheet;
860
811
  function createElementClass() {
861
812
  return class SwaralipiSheet extends HTMLElement {
@@ -988,6 +939,7 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
988
939
  }
989
940
  const existing = customElements.get(tagName);
990
941
  if (existing) return existing;
942
+ ensureNotationFonts();
991
943
  const ctor = registered.get(tagName) ?? createElementClass();
992
944
  registered.set(tagName, ctor);
993
945
  customElements.define(tagName, ctor);
@@ -996,8 +948,8 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
996
948
 
997
949
  exports.DIGITS = DIGITS;
998
950
  exports.LABELS = LABELS;
999
- exports.LETTERS = LETTERS;
1000
951
  exports.MARKS = MARKS;
952
+ exports.NOTATION_FONT_URLS = NOTATION_FONT_URLS;
1001
953
  exports.SWARALIPI_CSS = SWARALIPI_CSS;
1002
954
  exports.SWARALIPI_PRINT_CSS = SWARALIPI_PRINT_CSS;
1003
955
  exports.composeCluster = composeCluster;