bekesh
v0.2.0
Published
Browser-first Persian and Arabic kashida justification
Downloads
0
Maintainers
Readme
Bekesh
Bekesh is an opinionated ESM-only browser library that fits a line of Persian or Arabic text set in a naskh-style font to a target width. It inserts U+0640 ARABIC TATWEEL at contextually appropriate elongation points, then returns CSS word spacing for the remaining width.
Bekesh measures the requested font in the browser and verifies its result against DOM layout. Its candidate rules use letter families, positional forms, joined-run length, and explicit naskh prohibitions. If a word has no suitable elongation point, Bekesh leaves that width to word spacing instead of using an arbitrary connection.
Install
pnpm add bekeshOr: npm install bekesh
Usage
import { justifyWithKashida } from "bekesh";
const sourceText = "توانا بود هر که دانا بود";
const font = '32px "Scheherazade New"';
const lang = "fa";
const result = await justifyWithKashida({
text: sourceText,
targetWidth: 420,
font,
lang,
});
element.textContent = result.displayText;
element.lang = lang;
element.style.font = font;
element.style.wordSpacing = `${result.wordSpacing}px`;
element.style.direction = "rtl";
element.style.whiteSpace = "pre";Pass clean source text on every call. displayText contains presentation characters and should not replace the original text in application state. targetWidth is a CSS-pixel content width. Make sure to render with the same language, font, and RTL direction used for measurement! white-space: pre preserves spaces and keeps the text on one line.
An existing bare U+0640 is treated as an author-selected elongation point and receives the highest candidate priority. A tatweel carrying a combining mark remains a mark seat and is not treated as an elongation signal.
justifyWithKashida() waits for the requested font through the CSS Font Loading API. It uses Canvas for candidate search, verifies the fitted text and word spacing in a hidden DOM element, and backs off when the browser's inline layout would exceed the target.
API
function justifyWithKashida(options: JustifyOptions): Promise<JustificationResult>;JustifyOptions contains:
text: clean source text; it is not mutated or normalizedtargetWidth: desired inline width in CSS pixelsfont: a valid CSSfontshorthand, including the font sizelang: optional shaping language,"fa"(default) or"ar"tolerance: optional permitted overshoot in CSS pixels; defaults to zero
The result includes the source and display strings, measured widths, width remaining after tatweels, per-space wordSpacing, inserted tatweel edits, and diagnostic strings. If the clean source text already exceeds the target width, Bekesh returns it unchanged with the source-overflows-target diagnostic.
diagnostics contains values from the exported JustificationDiagnostic type:
source-overflows-target: the clean source is already too wide and is returned unchangedno-adjustable-spaces: residual width remains, but the source contains noU+0020spaces to which word spacing can be appliediteration-safety-limit-reached: the pure solver reached its bounded iteration limitdom-verification-adjusted: DOM verification reduced the Canvas-selected tatweels or word spacingdom-verification-fallback: bounded DOM refitting could not find a verified edited result, so the fitting step fell back to clean source text
The package also exports measureDomText(text, font, lang?) for synchronous DOM measurement, plus the JustifyOptions, JustificationResult, JustificationDiagnostic, and TatweelEdit types. Language defaults to "fa". measureDomText() does not load fonts; wait for the relevant face before calling this function when the font may not be ready:
await document.fonts.load(font, text);
const width = measureDomText(text, font, "ar");Browser and layout requirements
Bekesh requires a modern browser with ES modules, the DOM, Canvas 2D, document.fonts, and Intl.Segmenter support. It has no runtime dependencies.
Measurement currently models the CSS font shorthand, the selected language, RTL direction, and returned word spacing. Font features, variation settings, letter spacing, transforms, fallback selection, and other shaping inputs are not API options. If those differ between measurement and rendering, the final element can have a different width. Padding and borders are likewise outside targetWidth.
Bekesh fits one line at a time. It does not break paragraphs into lines, shrink overlong source text, or implement calligraphic glyph elongation. Bekesh sets the measurement language to Persian or Arabic and the direction to RTL; the browser handles bidi, shaping, and font fallback. This library's rules are designed for Persian and Arabic text in naskh-style fonts, not as a universal Arabic-script justification model. Candidate choice remains heuristic, so please review it with the fonts and texts that your application supports.
Research and prior art
This repository also maintains the research that informed the implementation:
- Research synthesis – the original survey and proposed architecture
- Prior-art catalog – a map of projects and standards
- Implementation comparison – responsibilities and tradeoffs side by side
- Source notes – notes tied to specific revisions
- Regression corpus and
cases.json– test categories and starter cases
Research notes prefer primary sources and pin code observations to revisions where possible. U+0640 output is treated as a reversible presentation artifact, not source text.
Development
pnpm install
pnpm check
pnpm test:browsertest:browser is an opt-in integration suite. It expects a system Playwright installation with Chromium, Firefox, and WebKit, and downloads a pinned Scheherazade New font into the operating system's temporary directory. The normal test and check commands do not require Playwright or network access.
One way to provide the system browser tooling is:
volta install playwright
playwright install chromium firefox webkitIf Playwright is installed elsewhere, set BEKESH_PLAYWRIGHT_PATH to its package directory.
License
Bekesh is available under the MIT License. See third-party notices for the provenance and terms of the compact Unicode joining-property data.
