@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.
@@ -2,49 +2,42 @@ import { O as Octave, a as SvaraLetter, N as Note, S as Swaralipi } from '../sch
2
2
  import 'zod';
3
3
 
4
4
  /**
5
- * Per-language glyph tables + Unicode cluster composition for the viewer.
5
+ * Per-language notation encoding for the viewer.
6
6
  *
7
- * The akarmatrik system is script-portable: Bengali and Devanagari use the
8
- * same letters-with-distinct-komal-forms convention (legend: কোমল র—ঋ,
9
- * কোমল গ—জ্ঞ, কড়ি ম—ক্ষ, কোমল ধ—দ, কোমল ন—ণ), the same আ-কার matra carrier,
10
- * the same reph-above for taar and understroke for mandra. `en` has no
11
- * native convention, so it uses S R G M P D N with CSS marks (komal
12
- * underline, tivra prime, octave dots) and no matra carrier.
7
+ * Every language renders its notation through a dedicated, purpose-built
8
+ * font whose glyphs ARE the akarmatrik/Bhatkhande marks — so composeCluster
9
+ * emits that font's ASCII encoding and the font does all the hard work
10
+ * (komal/tivra forms, octave reph/dots, matra). This is why the viewer no
11
+ * longer touches Unicode cluster composition at all: the old reph-over-
12
+ * vowel, hasanta-before-matra and dotted-circle hazards simply don't exist
13
+ * when the font supplies precomposed glyphs keyed by plain ASCII.
13
14
  *
14
- * Unicode hazards owned here (see composeCluster):
15
- * - mandra can NEVER be a real hasanta: letter + ্ + া is an invalid
16
- * cluster (virama before vowel sign → dotted circle). It is always a
17
- * CSS understroke (`sw-mandra`).
18
- * - ঋ/ऋ (komal re) is an independent VOWEL letter: both র্+ঋ (reph over a
19
- * vowel) and ঋ+া (vowel sign on a vowel) are invalid clusters. taar-ঋ
20
- * falls back to a CSS reph (`sw-reph-css`); a matra following ঋ must be
21
- * emitted as a separate isolated-matra span (`matraSeparate`).
22
- * - Isolated dependent signs (া, ঃ) need a base: U+00A0 NBSP is the
23
- * Unicode-sanctioned placeholder base and avoids dotted circles.
15
+ * - bn → NLTR's **Swarabitan** (the akarmatrik font this project decodes).
16
+ * Letters s r g m p q n = স র গ ম প ধ ন (q=ধ); komal v t d u = ঋ জ্ঞ দ ণ;
17
+ * tivra ma k = the authentic কড়ি-ম conjunct (ম-based — has NO Unicode
18
+ * form, which is what forced this whole approach); suffix f = taar reph,
19
+ * h = mandra hasanta; trailing a = the আ-কার matra carrier. See codec.ts.
20
+ * - hi/en → **Ome Bhatkhande** Hindi/English (SIL OFL 1.1, © Omenad —
21
+ * github.com/omenad/fonts). Same clean keymap for both: lowercase
22
+ * s r g m p d n = the seven shuddha swaras, uppercase R G M D N = komal
23
+ * Re/Ga, tivra Ma, komal Dha/Ni; octave suffix u/l = taar/mandra dot,
24
+ * U/L = ati (two dots). Bhatkhande has no আ-কার, so no matra is appended.
25
+ *
26
+ * The font is applied by CSS keyed on `.sw-sheet`'s `lang` (see styles.ts);
27
+ * composeCluster only chooses the ASCII. The single residual CSS mark is the
28
+ * bn ati-octave extra dot (Swarabitan's f/h are single reph/hasanta only).
24
29
  */
25
30
 
26
31
  type ViewerLang = 'bn' | 'hi' | 'en';
27
- interface LetterForms {
28
- shuddha: string;
29
- /** Distinct komal glyph (bn/hi); absent for en (CSS underline instead). */
30
- komal?: string;
31
- /** Distinct tivra glyph (bn/hi ক্ষ/क्ष); absent for en (CSS prime). */
32
- tivra?: string;
33
- }
34
- declare const LETTERS: Record<ViewerLang, Record<SvaraLetter, LetterForms>>;
35
32
  /** Digits 0-9 in the target script (beat labels, avartan). */
36
33
  declare const DIGITS: Record<ViewerLang, string>;
37
34
  interface LangMarks {
38
- /** আ-কার matra carrier appended to a group-final cluster ('' for en). */
39
- matra: string;
40
35
  /** Melisma / no-new-attack dash before a sustained cluster. */
41
36
  hyphen: string;
42
37
  /** Lyric continuation mark shown under sustains (the source uses ৹). */
43
38
  melisma: string;
44
39
  /** ছেদ cut/articulation mark (two stacked rings, ঃ-shaped). */
45
40
  cut: string;
46
- /** Real-text reph prefix for taar ('' for en → CSS dot instead). */
47
- reph: string;
48
41
  }
49
42
  declare const MARKS: Record<ViewerLang, LangMarks>;
50
43
  /** Header labels (NLTR prints নাম / তাল / আবর্তন …). */
@@ -55,28 +48,24 @@ declare const LABELS: Record<ViewerLang, {
55
48
  avartan: string;
56
49
  notationBy: string;
57
50
  }>;
58
- /** A composed notation cluster: text plus the CSS classes for marks the
59
- * text itself cannot carry (mandra understroke, ati dots, en komal/tivra,
60
- * CSS-reph fallback). */
51
+ /** A composed notation cluster: the font's ASCII text plus any residual CSS
52
+ * classes the font itself cannot express (only the bn ati-octave dot). */
61
53
  interface Cluster {
62
54
  text: string;
63
55
  classes: string[];
64
- /** Set when the matra could not join the cluster (ঋ/ऋ, or en where the
65
- * carrier doesn't exist) and must be emitted as an isolated-matra span. */
66
- matraSeparate: boolean;
67
56
  }
68
57
  interface ComposeOptions {
69
58
  komal?: boolean;
70
59
  tivra?: boolean;
71
60
  octave: Octave;
72
- /** Append the আ-কার matra carrier (group-final notes only). */
61
+ /** Append the আ-কার matra carrier (bn group-final notes only). */
73
62
  withMatra: boolean;
74
63
  lang: ViewerLang;
75
64
  }
76
65
  /**
77
- * Compose one svara cluster. The single place that knows Unicode
78
- * composition order: reph? + base + matra?, with the ঋ/mandra/en fallbacks
79
- * documented in the file header.
66
+ * Compose one svara into its notation-font ASCII. bn = Swarabitan
67
+ * (letter + f/h octave + a matra); hi/en = Ome Bhatkhande (case-encoded
68
+ * letter + u/l/U/L octave, no matra). The font renders the actual glyph.
80
69
  */
81
70
  declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Cluster;
82
71
  /** Map ASCII digits in `s` to the target script's digits. */
@@ -97,8 +86,8 @@ declare function localizeDigits(s: string, lang: ViewerLang): string;
97
86
  * Rendering is spec-driven (semantics, not `raw`): grouping derives from
98
87
  * `durationMatra` accumulation so hand-authored documents without `raw`
99
88
  * render identically. `raw` is consulted only for (a) raw-only fallback
100
- * notes (never dropped — dimmed monospace), (b) the ছেদ cut mark and beat
101
- * labels, whose grammar lives in raw (see src/convert/codec.ts).
89
+ * notes (never dropped — dimmed, in Swarabitan when loaded), (b) the ছেদ
90
+ * cut mark and beat labels, whose grammar lives in raw (see codec.ts).
102
91
  */
103
92
 
104
93
  interface RenderOptions {
@@ -170,12 +159,17 @@ declare function renderToHtml(doc: Swaralipi, opts?: RenderOptions): string;
170
159
  * --sw-rule header rule / muted line color
171
160
  *
172
161
  * All ink uses `currentColor`, so the sheet follows the host's text color
173
- * (dark mode included). Octave marks that Unicode cannot express in a valid
174
- * cluster (mandra understroke, ati dots, ঋ-reph, en octave dots) are drawn
175
- * with positioned pseudo-elements; offsets are em-based and tuned against
176
- * the NLTR reference rendering of song 4270.
162
+ * (dark mode included). The only octave mark drawn as a pseudo-element is
163
+ * the bn ati-octave extra dot (Swarabitan's f/h glyphs are a single
164
+ * reph/hasanta); everything else is a real glyph in the notation font.
177
165
  */
178
- declare const SWARALIPI_CSS = "\n.sw-sheet {\n font-family: var(--sw-font, 'Noto Sans Bengali', 'Kohinoor Bangla', 'Bangla Sangam MN', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif);\n font-size: var(--sw-size, 1.125rem);\n line-height: 1.5;\n}\n\n/* ---- header ---- */\n.sw-header {\n margin: 0 0 0.75em;\n padding: 0 0 0.5em;\n border-bottom: 1px solid var(--sw-rule, rgba(128, 128, 128, 0.35));\n font-size: 0.95em;\n}\n.sw-meta-row { display: flex; gap: 0.3em; }\n.sw-meta-label { font-weight: 600; min-width: 4.5em; }\n.sw-meta-label::after { content: ':'; font-weight: 400; margin-left: 0.15em; }\n\n.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }\n\n/* ---- row grid: above / notation / below(arcs) / lyric ---- */\n.sw-row {\n display: flex;\n align-items: flex-start;\n overflow-x: auto;\n padding: 0.4em 0;\n}\n.sw-label { flex: none; margin-right: 0.8em; font-weight: 600; }\n.sw-grid {\n display: grid;\n grid-template-rows: auto auto auto auto;\n grid-auto-columns: max-content;\n align-items: end;\n}\n.sw-above { grid-row: 1; font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }\n.sw-n { grid-row: 2; white-space: nowrap; }\n.sw-arc,\n.sw-below { grid-row: 3; }\n.sw-lyr { grid-row: 4; white-space: nowrap; padding-top: 0.3em; }\n.sw-below { font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }\n\n/* inter-cell gap as padding on each cell's first notation item, so arcs\n * and above-spans crossing cells stay continuous */\n.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }\n\n/* ---- notation clusters & marks ---- */\n.sw-cl { position: relative; display: inline-block; }\n\n/* mandra: hasanta-shaped understroke (a real \u09CD before \u09BE is invalid Unicode) */\n.sw-mandra::after {\n content: '';\n position: absolute;\n left: 0.1em;\n bottom: -0.08em;\n width: 0.45em;\n height: 0.09em;\n background: currentColor;\n transform: rotate(-32deg);\n}\n/* ati-mandra: an extra dot below */\n.sw-ati-mandra::before {\n content: '';\n position: absolute;\n left: 0.25em;\n bottom: -0.35em;\n width: 0.16em;\n height: 0.16em;\n border-radius: 50%;\n background: currentColor;\n}\n/* ati-taar (reph already in the text): an extra dot above */\n.sw-ati-taar::before {\n content: '';\n position: absolute;\n left: 0.25em;\n top: -0.3em;\n width: 0.16em;\n height: 0.16em;\n border-radius: 50%;\n background: currentColor;\n}\n/* CSS reph fallback for vowel letters \u098B/\u090B that reject a real reph */\n.sw-reph-css::before {\n content: '';\n position: absolute;\n top: -0.12em;\n left: 0.12em;\n width: 0.3em;\n height: 0.3em;\n border: 0.09em solid currentColor;\n border-right: none;\n border-bottom: none;\n border-top-left-radius: 0.3em;\n}\n/* en octave dots (Bhatkhande-style) */\n.sw-dot-above::before,\n.sw-dot-below::before {\n content: '';\n position: absolute;\n left: 50%;\n margin-left: -0.07em;\n width: 0.14em;\n height: 0.14em;\n border-radius: 50%;\n background: currentColor;\n}\n.sw-dot-above::before { top: -0.25em; }\n.sw-dot-below::before { bottom: -0.2em; }\n.sw-ati-taar.sw-dot-above::before,\n.sw-ati-mandra.sw-dot-below::before { box-shadow: 0.24em 0 0 currentColor; margin-left: -0.19em; }\n/* en accidentals (bn/hi use distinct letters instead) */\n.sw-komal { text-decoration: underline; text-underline-offset: 0.18em; }\n.sw-tivra::after { content: '\\2032'; font-size: 0.7em; vertical-align: super; }\n\n/* kan grace: small raised cluster left of the main note */\n.sw-kan {\n position: relative;\n display: inline-block;\n font-size: 0.58em;\n vertical-align: 0.75em;\n margin-right: 0.08em;\n}\n\n.sw-hy { padding-right: 0.02em; }\n.sw-iso { display: inline-block; margin-left: -0.18em; }\n.sw-cut { font-size: 0.8em; display: inline-block; margin-left: -0.15em; }\n.sw-br { opacity: 0.8; padding: 0 0.06em; }\n.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }\n\n/* raw fallback: never dropped, visibly non-notation */\n.sw-raw {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.72em;\n opacity: 0.55;\n}\n\n/* ---- bars: dari (thin) and danda strokes, notation-row height ---- */\n/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014\n * never reset it with a padding shorthand here */\n.sw-bar {\n grid-row: 2;\n align-self: stretch;\n display: inline-flex;\n align-items: stretch;\n gap: 0.16em;\n padding-right: 0.1em;\n}\n.sw-bar i { display: inline-block; width: 0.14em; min-height: 1.25em; background: currentColor; }\n.sw-bar--dari i { width: 0.05em; opacity: 0.9; }\n.sw-bar--quad i:nth-child(3) { margin-left: 0.18em; }\n.sw-above .sw-bar, .sw-below .sw-bar { display: inline-flex; vertical-align: baseline; }\n.sw-above .sw-bar i, .sw-below .sw-bar i { min-height: 1em; }\n\n/* ---- under-arcs (slurs decoded from w/x segments and meend) ---- */\n.sw-arc {\n align-self: start;\n height: 0.4em;\n margin: 0.05em 0.25em 0;\n border: 0.09em solid currentColor;\n border-top: none;\n border-bottom-left-radius: 0.6em;\n border-bottom-right-radius: 0.6em;\n opacity: 0.85;\n}\n.sw-arc--openl { border-left: none; border-bottom-left-radius: 0; }\n.sw-arc--openr { border-right: none; border-bottom-right-radius: 0; }\n.sw-arc--line { border-left: none; border-right: none; border-radius: 0; height: 0.22em; }\n\n/* ---- annotation content ---- */\n.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }\n.sw-sam::after {\n content: '';\n position: absolute;\n top: -0.45em;\n left: 50%;\n width: 0.1em;\n height: 0.4em;\n background: currentColor;\n transform: rotate(14deg);\n}\n.sw-stop i {\n display: inline-block;\n width: 0.07em;\n height: 1em;\n margin: 0 0.07em;\n background: currentColor;\n vertical-align: text-bottom;\n}\n\n/* ---- player sync (P2.3) ---- */\n.sw-hl { background: var(--sw-hl, rgba(255, 196, 64, 0.45)); border-radius: 0.15em; }\n\n/* ---- element error box ---- */\n.sw-error {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n padding: 0.6em 0.8em;\n border: 1px solid rgba(200, 64, 64, 0.6);\n border-radius: 0.3em;\n color: #b33;\n background: rgba(200, 64, 64, 0.08);\n}\n";
166
+ /** Notation fonts, self-hosted on the project CDN (CORS-enabled), keyed by
167
+ * the CSS `font-family` this stylesheet uses. bn = NLTR's Swarabitan; hi/en
168
+ * = Ome Bhatkhande (SIL OFL 1.1, © Omenad — github.com/omenad/fonts).
169
+ * Self-hosters: declare your own \`@font-face\` for any of these families
170
+ * before this stylesheet / before defineSwaralipiSheet() and it wins. */
171
+ declare const NOTATION_FONT_URLS: Record<string, string>;
172
+ declare const SWARALIPI_CSS: string;
179
173
  /**
180
174
  * Print stylesheet — pair with `SWARALIPI_CSS` (renderPrintDocument injects
181
175
  * both). Gated on `@media print`, so it also works if a consumer drops it into
@@ -282,4 +276,4 @@ type Transliterator = (bengaliText: string) => string;
282
276
  */
283
277
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
284
278
 
285
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, LETTERS, MARKS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, type SwaralipiSheetElement, type Transliterator, type ViewerLang, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
279
+ export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, type SwaralipiSheetElement, type Transliterator, type ViewerLang, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
@@ -2,49 +2,42 @@ import { O as Octave, a as SvaraLetter, N as Note, S as Swaralipi } from '../sch
2
2
  import 'zod';
3
3
 
4
4
  /**
5
- * Per-language glyph tables + Unicode cluster composition for the viewer.
5
+ * Per-language notation encoding for the viewer.
6
6
  *
7
- * The akarmatrik system is script-portable: Bengali and Devanagari use the
8
- * same letters-with-distinct-komal-forms convention (legend: কোমল র—ঋ,
9
- * কোমল গ—জ্ঞ, কড়ি ম—ক্ষ, কোমল ধ—দ, কোমল ন—ণ), the same আ-কার matra carrier,
10
- * the same reph-above for taar and understroke for mandra. `en` has no
11
- * native convention, so it uses S R G M P D N with CSS marks (komal
12
- * underline, tivra prime, octave dots) and no matra carrier.
7
+ * Every language renders its notation through a dedicated, purpose-built
8
+ * font whose glyphs ARE the akarmatrik/Bhatkhande marks — so composeCluster
9
+ * emits that font's ASCII encoding and the font does all the hard work
10
+ * (komal/tivra forms, octave reph/dots, matra). This is why the viewer no
11
+ * longer touches Unicode cluster composition at all: the old reph-over-
12
+ * vowel, hasanta-before-matra and dotted-circle hazards simply don't exist
13
+ * when the font supplies precomposed glyphs keyed by plain ASCII.
13
14
  *
14
- * Unicode hazards owned here (see composeCluster):
15
- * - mandra can NEVER be a real hasanta: letter + ্ + া is an invalid
16
- * cluster (virama before vowel sign → dotted circle). It is always a
17
- * CSS understroke (`sw-mandra`).
18
- * - ঋ/ऋ (komal re) is an independent VOWEL letter: both র্+ঋ (reph over a
19
- * vowel) and ঋ+া (vowel sign on a vowel) are invalid clusters. taar-ঋ
20
- * falls back to a CSS reph (`sw-reph-css`); a matra following ঋ must be
21
- * emitted as a separate isolated-matra span (`matraSeparate`).
22
- * - Isolated dependent signs (া, ঃ) need a base: U+00A0 NBSP is the
23
- * Unicode-sanctioned placeholder base and avoids dotted circles.
15
+ * - bn → NLTR's **Swarabitan** (the akarmatrik font this project decodes).
16
+ * Letters s r g m p q n = স র গ ম প ধ ন (q=ধ); komal v t d u = ঋ জ্ঞ দ ণ;
17
+ * tivra ma k = the authentic কড়ি-ম conjunct (ম-based — has NO Unicode
18
+ * form, which is what forced this whole approach); suffix f = taar reph,
19
+ * h = mandra hasanta; trailing a = the আ-কার matra carrier. See codec.ts.
20
+ * - hi/en → **Ome Bhatkhande** Hindi/English (SIL OFL 1.1, © Omenad —
21
+ * github.com/omenad/fonts). Same clean keymap for both: lowercase
22
+ * s r g m p d n = the seven shuddha swaras, uppercase R G M D N = komal
23
+ * Re/Ga, tivra Ma, komal Dha/Ni; octave suffix u/l = taar/mandra dot,
24
+ * U/L = ati (two dots). Bhatkhande has no আ-কার, so no matra is appended.
25
+ *
26
+ * The font is applied by CSS keyed on `.sw-sheet`'s `lang` (see styles.ts);
27
+ * composeCluster only chooses the ASCII. The single residual CSS mark is the
28
+ * bn ati-octave extra dot (Swarabitan's f/h are single reph/hasanta only).
24
29
  */
25
30
 
26
31
  type ViewerLang = 'bn' | 'hi' | 'en';
27
- interface LetterForms {
28
- shuddha: string;
29
- /** Distinct komal glyph (bn/hi); absent for en (CSS underline instead). */
30
- komal?: string;
31
- /** Distinct tivra glyph (bn/hi ক্ষ/क्ष); absent for en (CSS prime). */
32
- tivra?: string;
33
- }
34
- declare const LETTERS: Record<ViewerLang, Record<SvaraLetter, LetterForms>>;
35
32
  /** Digits 0-9 in the target script (beat labels, avartan). */
36
33
  declare const DIGITS: Record<ViewerLang, string>;
37
34
  interface LangMarks {
38
- /** আ-কার matra carrier appended to a group-final cluster ('' for en). */
39
- matra: string;
40
35
  /** Melisma / no-new-attack dash before a sustained cluster. */
41
36
  hyphen: string;
42
37
  /** Lyric continuation mark shown under sustains (the source uses ৹). */
43
38
  melisma: string;
44
39
  /** ছেদ cut/articulation mark (two stacked rings, ঃ-shaped). */
45
40
  cut: string;
46
- /** Real-text reph prefix for taar ('' for en → CSS dot instead). */
47
- reph: string;
48
41
  }
49
42
  declare const MARKS: Record<ViewerLang, LangMarks>;
50
43
  /** Header labels (NLTR prints নাম / তাল / আবর্তন …). */
@@ -55,28 +48,24 @@ declare const LABELS: Record<ViewerLang, {
55
48
  avartan: string;
56
49
  notationBy: string;
57
50
  }>;
58
- /** A composed notation cluster: text plus the CSS classes for marks the
59
- * text itself cannot carry (mandra understroke, ati dots, en komal/tivra,
60
- * CSS-reph fallback). */
51
+ /** A composed notation cluster: the font's ASCII text plus any residual CSS
52
+ * classes the font itself cannot express (only the bn ati-octave dot). */
61
53
  interface Cluster {
62
54
  text: string;
63
55
  classes: string[];
64
- /** Set when the matra could not join the cluster (ঋ/ऋ, or en where the
65
- * carrier doesn't exist) and must be emitted as an isolated-matra span. */
66
- matraSeparate: boolean;
67
56
  }
68
57
  interface ComposeOptions {
69
58
  komal?: boolean;
70
59
  tivra?: boolean;
71
60
  octave: Octave;
72
- /** Append the আ-কার matra carrier (group-final notes only). */
61
+ /** Append the আ-কার matra carrier (bn group-final notes only). */
73
62
  withMatra: boolean;
74
63
  lang: ViewerLang;
75
64
  }
76
65
  /**
77
- * Compose one svara cluster. The single place that knows Unicode
78
- * composition order: reph? + base + matra?, with the ঋ/mandra/en fallbacks
79
- * documented in the file header.
66
+ * Compose one svara into its notation-font ASCII. bn = Swarabitan
67
+ * (letter + f/h octave + a matra); hi/en = Ome Bhatkhande (case-encoded
68
+ * letter + u/l/U/L octave, no matra). The font renders the actual glyph.
80
69
  */
81
70
  declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Cluster;
82
71
  /** Map ASCII digits in `s` to the target script's digits. */
@@ -97,8 +86,8 @@ declare function localizeDigits(s: string, lang: ViewerLang): string;
97
86
  * Rendering is spec-driven (semantics, not `raw`): grouping derives from
98
87
  * `durationMatra` accumulation so hand-authored documents without `raw`
99
88
  * render identically. `raw` is consulted only for (a) raw-only fallback
100
- * notes (never dropped — dimmed monospace), (b) the ছেদ cut mark and beat
101
- * labels, whose grammar lives in raw (see src/convert/codec.ts).
89
+ * notes (never dropped — dimmed, in Swarabitan when loaded), (b) the ছেদ
90
+ * cut mark and beat labels, whose grammar lives in raw (see codec.ts).
102
91
  */
103
92
 
104
93
  interface RenderOptions {
@@ -170,12 +159,17 @@ declare function renderToHtml(doc: Swaralipi, opts?: RenderOptions): string;
170
159
  * --sw-rule header rule / muted line color
171
160
  *
172
161
  * All ink uses `currentColor`, so the sheet follows the host's text color
173
- * (dark mode included). Octave marks that Unicode cannot express in a valid
174
- * cluster (mandra understroke, ati dots, ঋ-reph, en octave dots) are drawn
175
- * with positioned pseudo-elements; offsets are em-based and tuned against
176
- * the NLTR reference rendering of song 4270.
162
+ * (dark mode included). The only octave mark drawn as a pseudo-element is
163
+ * the bn ati-octave extra dot (Swarabitan's f/h glyphs are a single
164
+ * reph/hasanta); everything else is a real glyph in the notation font.
177
165
  */
178
- declare const SWARALIPI_CSS = "\n.sw-sheet {\n font-family: var(--sw-font, 'Noto Sans Bengali', 'Kohinoor Bangla', 'Bangla Sangam MN', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif);\n font-size: var(--sw-size, 1.125rem);\n line-height: 1.5;\n}\n\n/* ---- header ---- */\n.sw-header {\n margin: 0 0 0.75em;\n padding: 0 0 0.5em;\n border-bottom: 1px solid var(--sw-rule, rgba(128, 128, 128, 0.35));\n font-size: 0.95em;\n}\n.sw-meta-row { display: flex; gap: 0.3em; }\n.sw-meta-label { font-weight: 600; min-width: 4.5em; }\n.sw-meta-label::after { content: ':'; font-weight: 400; margin-left: 0.15em; }\n\n.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }\n\n/* ---- row grid: above / notation / below(arcs) / lyric ---- */\n.sw-row {\n display: flex;\n align-items: flex-start;\n overflow-x: auto;\n padding: 0.4em 0;\n}\n.sw-label { flex: none; margin-right: 0.8em; font-weight: 600; }\n.sw-grid {\n display: grid;\n grid-template-rows: auto auto auto auto;\n grid-auto-columns: max-content;\n align-items: end;\n}\n.sw-above { grid-row: 1; font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }\n.sw-n { grid-row: 2; white-space: nowrap; }\n.sw-arc,\n.sw-below { grid-row: 3; }\n.sw-lyr { grid-row: 4; white-space: nowrap; padding-top: 0.3em; }\n.sw-below { font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }\n\n/* inter-cell gap as padding on each cell's first notation item, so arcs\n * and above-spans crossing cells stay continuous */\n.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }\n\n/* ---- notation clusters & marks ---- */\n.sw-cl { position: relative; display: inline-block; }\n\n/* mandra: hasanta-shaped understroke (a real \u09CD before \u09BE is invalid Unicode) */\n.sw-mandra::after {\n content: '';\n position: absolute;\n left: 0.1em;\n bottom: -0.08em;\n width: 0.45em;\n height: 0.09em;\n background: currentColor;\n transform: rotate(-32deg);\n}\n/* ati-mandra: an extra dot below */\n.sw-ati-mandra::before {\n content: '';\n position: absolute;\n left: 0.25em;\n bottom: -0.35em;\n width: 0.16em;\n height: 0.16em;\n border-radius: 50%;\n background: currentColor;\n}\n/* ati-taar (reph already in the text): an extra dot above */\n.sw-ati-taar::before {\n content: '';\n position: absolute;\n left: 0.25em;\n top: -0.3em;\n width: 0.16em;\n height: 0.16em;\n border-radius: 50%;\n background: currentColor;\n}\n/* CSS reph fallback for vowel letters \u098B/\u090B that reject a real reph */\n.sw-reph-css::before {\n content: '';\n position: absolute;\n top: -0.12em;\n left: 0.12em;\n width: 0.3em;\n height: 0.3em;\n border: 0.09em solid currentColor;\n border-right: none;\n border-bottom: none;\n border-top-left-radius: 0.3em;\n}\n/* en octave dots (Bhatkhande-style) */\n.sw-dot-above::before,\n.sw-dot-below::before {\n content: '';\n position: absolute;\n left: 50%;\n margin-left: -0.07em;\n width: 0.14em;\n height: 0.14em;\n border-radius: 50%;\n background: currentColor;\n}\n.sw-dot-above::before { top: -0.25em; }\n.sw-dot-below::before { bottom: -0.2em; }\n.sw-ati-taar.sw-dot-above::before,\n.sw-ati-mandra.sw-dot-below::before { box-shadow: 0.24em 0 0 currentColor; margin-left: -0.19em; }\n/* en accidentals (bn/hi use distinct letters instead) */\n.sw-komal { text-decoration: underline; text-underline-offset: 0.18em; }\n.sw-tivra::after { content: '\\2032'; font-size: 0.7em; vertical-align: super; }\n\n/* kan grace: small raised cluster left of the main note */\n.sw-kan {\n position: relative;\n display: inline-block;\n font-size: 0.58em;\n vertical-align: 0.75em;\n margin-right: 0.08em;\n}\n\n.sw-hy { padding-right: 0.02em; }\n.sw-iso { display: inline-block; margin-left: -0.18em; }\n.sw-cut { font-size: 0.8em; display: inline-block; margin-left: -0.15em; }\n.sw-br { opacity: 0.8; padding: 0 0.06em; }\n.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }\n\n/* raw fallback: never dropped, visibly non-notation */\n.sw-raw {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.72em;\n opacity: 0.55;\n}\n\n/* ---- bars: dari (thin) and danda strokes, notation-row height ---- */\n/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014\n * never reset it with a padding shorthand here */\n.sw-bar {\n grid-row: 2;\n align-self: stretch;\n display: inline-flex;\n align-items: stretch;\n gap: 0.16em;\n padding-right: 0.1em;\n}\n.sw-bar i { display: inline-block; width: 0.14em; min-height: 1.25em; background: currentColor; }\n.sw-bar--dari i { width: 0.05em; opacity: 0.9; }\n.sw-bar--quad i:nth-child(3) { margin-left: 0.18em; }\n.sw-above .sw-bar, .sw-below .sw-bar { display: inline-flex; vertical-align: baseline; }\n.sw-above .sw-bar i, .sw-below .sw-bar i { min-height: 1em; }\n\n/* ---- under-arcs (slurs decoded from w/x segments and meend) ---- */\n.sw-arc {\n align-self: start;\n height: 0.4em;\n margin: 0.05em 0.25em 0;\n border: 0.09em solid currentColor;\n border-top: none;\n border-bottom-left-radius: 0.6em;\n border-bottom-right-radius: 0.6em;\n opacity: 0.85;\n}\n.sw-arc--openl { border-left: none; border-bottom-left-radius: 0; }\n.sw-arc--openr { border-right: none; border-bottom-right-radius: 0; }\n.sw-arc--line { border-left: none; border-right: none; border-radius: 0; height: 0.22em; }\n\n/* ---- annotation content ---- */\n.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }\n.sw-sam::after {\n content: '';\n position: absolute;\n top: -0.45em;\n left: 50%;\n width: 0.1em;\n height: 0.4em;\n background: currentColor;\n transform: rotate(14deg);\n}\n.sw-stop i {\n display: inline-block;\n width: 0.07em;\n height: 1em;\n margin: 0 0.07em;\n background: currentColor;\n vertical-align: text-bottom;\n}\n\n/* ---- player sync (P2.3) ---- */\n.sw-hl { background: var(--sw-hl, rgba(255, 196, 64, 0.45)); border-radius: 0.15em; }\n\n/* ---- element error box ---- */\n.sw-error {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n padding: 0.6em 0.8em;\n border: 1px solid rgba(200, 64, 64, 0.6);\n border-radius: 0.3em;\n color: #b33;\n background: rgba(200, 64, 64, 0.08);\n}\n";
166
+ /** Notation fonts, self-hosted on the project CDN (CORS-enabled), keyed by
167
+ * the CSS `font-family` this stylesheet uses. bn = NLTR's Swarabitan; hi/en
168
+ * = Ome Bhatkhande (SIL OFL 1.1, © Omenad — github.com/omenad/fonts).
169
+ * Self-hosters: declare your own \`@font-face\` for any of these families
170
+ * before this stylesheet / before defineSwaralipiSheet() and it wins. */
171
+ declare const NOTATION_FONT_URLS: Record<string, string>;
172
+ declare const SWARALIPI_CSS: string;
179
173
  /**
180
174
  * Print stylesheet — pair with `SWARALIPI_CSS` (renderPrintDocument injects
181
175
  * both). Gated on `@media print`, so it also works if a consumer drops it into
@@ -282,4 +276,4 @@ type Transliterator = (bengaliText: string) => string;
282
276
  */
283
277
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
284
278
 
285
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, LETTERS, MARKS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, type SwaralipiSheetElement, type Transliterator, type ViewerLang, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
279
+ export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, type SwaralipiSheetElement, type Transliterator, type ViewerLang, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };