Skip to content

API ​

ts
import {
  renderSwarlipi,
  swarlipiWrapperClass,
  toUnicodeNotation,
  escapeHtml,
  MEEND_BAR_RATIO,
  type SwarlipiScript,
  type SwarlipiScriptInput,
  type SwarlipiRenderOptions,
} from 'swarlipi';
import 'swarlipi/style.css';
import { measureBridges, bridgeElement } from 'swarlipi/bridge';
import { SAMPLE_GROUPS, CROSS_BEAT_SAMPLES } from 'swarlipi/samples';
import { SYMBOL_GROUPS } from 'swarlipi/reference';

renderSwarlipi(notes, script, options?) ​

Renders one beat's notation as an HTML string. Never touches the DOM.

  • notes: string: the beat, in the Omenad keymap.

  • script: SwarlipiScriptInput: one of

    SwarlipiScriptLettersFace
    'gurmukhi'ਸ ਰੇ ਗ ਮ ਪ ਧ ਨੀNoto Sans Gurmukhi
    'devanagari'स रे ग म प ध नीNoto Sans Devanagari
    'bengali'স রে গ ম প ধ নীNoto Sans Bengali
    'gujarati'સ રે ગ મ પ ધ નીNoto Sans Gujarati
    'latin'S R G M P D NNoto Sans

    A script id names a letterform set, not a language — several languages share one. Marathi, Nepali and Konkani notation is 'devanagari', the very same letters Hindi uses, so a language belongs in your own language list mapped onto one of these.

    A script is here only when Bhatkhande notation is actually published in it.

    The original four ids 'punjabi' | 'hindi' | 'bangla' | 'english' are still accepted everywhere as aliases for gurmukhi | devanagari | bengali | latin, so existing code and stored cookies keep working. An unknown value degrades to 'latin' rather than throwing, since the value often comes from a cookie.

  • options.noteClass?: (swara: string, octave?: string) => string | undefined: extra class(es) for a note's .sl-n.

  • options.editing?: boolean: draw half-typed markers over empty slots.

Returns '' for an empty string.

swarlipiWrapperClass(script) ​

The class list the receiving element must carry: "sl-wrap sl-<script>". Where the script has an original-name alias, its class comes too ("sl-wrap sl-bengali sl-bangla"), so a stylesheet written against either name still applies.

toUnicodeNotation(notes, script) ​

The plain-text form: letters in the script plus Unicode combining marks. Marks are placed after the whole letter cluster, never between a consonant and its vowel sign, which would split the syllable in CoreText and Word.

ts
toUnicodeNotation('Rl', 'devanagari'); // "रे̱̣"  komal Re, mandra

What survives ​

InputBecomes
lettersthe script's letters, with their vowel signs
komal R G D NU+0331 combining macron below
tivra MU+030D combining vertical line above
octaves u U l LU+0307, U+0308, U+0323, U+0324
kan {p}, murki (r)parentheses around the notes
bols ; ' [ ] \their letters (; → ਦ / द / দ / દ / ದ)
digits 1 2 3the script's digits
- and _en dash and em dash

What is lost ​

This is a one-way export for reading, not a storage format. Notation cannot be recovered from the text it produces.

  • Chhand markers are dropped. @sr and sr give the same text, so the subdivision is gone.
  • Meend and ghaseet markers are dropped. qsre, QsrE and sre all give the same text. Nothing in Unicode draws a glide across letters.
  • A continuation-only beat becomes an empty string. w and W carry no letter, so a beat holding just a continuation exports as nothing. Join beats with a separator of your own if an empty beat would be ambiguous.
  • Kan and murki are indistinguishable, since both become parentheses.
  • Whether the marks are drawn depends on the destination font. The Swarlipi fonts exist for exactly this; in a font without the marks the letters still read and the marks are dropped or misplaced.

MEEND_BAR_RATIO ​

The thickness of a meend or ghaseet line as a fraction of its mark zone's height. swarlipi/bridge draws its bars from it; --sl-bar-ratio in the stylesheet mirrors it.

swarlipi/bridge ​

Joins the per-beat glide fragments across beats. Browser only: it measures the DOM.

measureBridges({ container, cellAt, spans, anchorPrefix }) ​

Returns a BridgeBar[], one per gap between consecutive fragments along each span, two where a span wraps to the next row.

  • container: HTMLElement: the positioned element the bars are placed in. The beat cells must be its descendants.
  • cellAt: (beatIndex) => Element | null | undefined: the beat cell for a flat beat index.
  • spans: { from: number; to: number }[]: which beats pair. Spans with to <= from are skipped; an in-beat pair draws itself.
  • anchorPrefix: string: a dashed-ident (--x) unique per container on the page. Each fragment along a span gets an inline anchor-name under it.

Every bar carries a measured fallback (calc(% + px) left and width, px top and height) and the anchor names of the zones it joins. The stylesheet's [data-sl-bar] rules pin it to those zones where the engine supports CSS anchor positioning, and the browser re-derives the geometry on every layout pass. That is what makes the bridges print: the print engine re-lays-out at paper width with print-media font sizes, which no screen measurement can predict. slabRatio is the band's height as a fraction of the box, scale-free for the same reason.

planBridges({ containerRect, zonesAt, spans }) ​

The geometry with no DOM: rectangles in, BridgeBar[] out. measureBridges is a thin shim over it that reads the page and writes the anchor names. Use it directly if you already hold the layout — a virtualised grid, a worker, a test.

zonesAt(beatIndex) returns that beat's fragments in document order, each as { name, rect, cellRect, slabRatio }: the anchor name you assigned, the fragment's own box, its beat cell's box (x is decomposed against the centre) and the ink fraction. Return undefined (or []) for a beat that is not rendered — a span running past the rendered window still bridges the fragments that are present, which is what a trimmed view needs.

It returns no bars when containerRect.width is 0, since an unlaid-out container reports every rectangle as 0 and there is nothing to measure against; call it again once the container has a size.

bridgeElement(bar, doc?) ​

A ready <div data-sl-bar> with the bar's style and ink. Append it to the container, after removing the bars from the previous measure: their geometry is stale, and the anchor names they reference may be gone.

bridgeStyle(bar), bridgeClass(bar), bridgeMarkup(bar) ​

The pieces of bridgeElement for a template layer: the inline style object (the measured fallback plus the --sl-t / --sl-a / --sl-b anchor names), the sl-bar-a / sl-bar-b classes that say which ends are zone-pinned, and the inner svg that draws the band. Put data-sl-bar on the element yourself.

BRIDGE_ZONE_SELECTOR, BRIDGE_OVERLAP ​

The fragment selector (.sl-mz), useful for deciding whether a DOM mutation needs a re-measure, and the fraction of a zone's height each bar runs into its zones so the junctions print seamless. A test keeps the stylesheet's anchor rules on the same value.

escapeHtml(value) ​

The escaper the renderer uses for text it passes through. Exported for hosts that build markup around the output.

swarlipi/style.css ​

The stylesheet the markup needs. Prefixed sl-; see the hooks table.

swarlipi/samples ​

SAMPLE_GROUPS: single-beat samples grouped by feature, each { title, note?, samples }. CROSS_BEAT_SAMPLES: runs of consecutive beats, { title, note?, beats }. The source of the Symbols.

swarlipi/reference ​

SYMBOL_GROUPS: the symbol mapping as data, grouped { id, title, entries } with each entry { key, name, example }. The source of the keymap table and of the notation app's in-editor guide sheet.

Code MIT · Fonts OFL 1.1 · Inspired by the Omenad Bhatkhande fonts