bidi-js: Unicode Bidirectional Algorithm
The `bidi-js` package provides a pure JavaScript implementation of the Unicode Bidirectional Algorithm (UAX #9) version 13.0.0. It aims for correctness, small bundle size, and performance, having passed all Unicode conformance tests. Currently at version 1.0.3, it offers a stable API for calculating embedding levels, reordering segments, and identifying mirrored characters in mixed-direction text. It ships as ES5 compatible and has no external dependencies, making it suitable for both browser and Node.js environments. The library differentiates itself by providing a factory function for module initialization, allowing for flexible deployment, such as within web workers, and ensuring a self-contained module without closure dependencies.
Common errors
-
TypeError: bidi-js is not a function
cause Attempting to use the default import `bidiFactory` directly as an object or calling a method on it without first invoking it as a function.fixEnsure you call the factory function to get the bidi object: `const bidiFactory = require('bidi-js'); const bidi = bidiFactory();` or `import bidiFactory from 'bidi-js'; const bidi = bidiFactory();` -
TypeError: bidi.getEmbeddingLevels is not a function
cause This error occurs when `bidi` is not the object returned by the factory function, meaning `bidiFactory()` was likely not called, or `bidi` was incorrectly assigned.fixVerify that `bidiFactory()` has been correctly invoked and its return value assigned to the `bidi` variable: `const bidi = bidiFactory()`.
Warnings
- gotcha The `bidi-js` package's only export is a factory function that *must* be invoked to obtain the bidi processing object. Forgetting to call `bidiFactory()` will lead to errors when attempting to access methods.
- gotcha When applying character reorderings from `getReorderSegments`, the returned array of `flips` contains ranges that *must* be applied sequentially in the order they are provided to ensure correct visual representation.
- gotcha For line-wrapped text, `getReorderSegments` should be called for each individual line by providing the `start` and `end` parameters corresponding to that line. This handles special cases for trailing whitespace within line segments.
- gotcha While the library ensures conformance to Unicode Bidirectional Algorithm version 13.0.0, future Unicode standard updates (new versions of UAX #9) may introduce changes. The library's current implementation adheres strictly to the specified version.
Install
-
npm install bidi-js -
yarn add bidi-js -
pnpm add bidi-js
Imports
- bidiFactory
import { bidiFactory } from 'bidi-js'import bidiFactory from 'bidi-js'
- bidi object
const bidi = bidiFactory
const bidi = bidiFactory()
- getEmbeddingLevels
getEmbeddingLevels(text, explicitDirection)
bidi.getEmbeddingLevels(text, explicitDirection)
Quickstart
import bidiFactory from 'bidi-js';
// In a Node.js environment or a browser with ESM support
async function runBidiExample() {
// 1. Initialize the bidi object by invoking the factory function
const bidi = bidiFactory();
// Example text with mixed directions (English and Hebrew)
const text = "Hello אֲנִי World, this is a test."; // Hebrew for "I"
const explicitDirection = "ltr"; // Optional, if you want to force base direction
console.log("Original Text:", text);
// 2. Calculate bidi embedding levels for each character
const embeddingLevels = bidi.getEmbeddingLevels(text, explicitDirection);
const { levels, paragraphs } = embeddingLevels;
console.log("Embedding Levels (Uint8Array):");
console.log(levels.join(', '));
console.log("Paragraphs:", paragraphs);
// 3. Calculate character reorderings (segments to reverse)
const flips = bidi.getReorderSegments(text, embeddingLevels);
console.log("Reorder Flips (ranges [start, end] to reverse):", flips);
// 4. Apply reordering to get the visual order of characters
let reorderedTextArray = Array.from(text);
flips.forEach(range => {
const [start, end] = range;
// Reverse the segment in place
let segment = reorderedTextArray.slice(start, end + 1);
segment.reverse();
reorderedTextArray.splice(start, segment.length, ...segment);
});
console.log("Reordered Text (visual order):
", reorderedTextArray.join(''));
// 5. Identify characters that need to be mirrored (e.g., parentheses)
const mirroredCharactersMap = bidi.getMirroredCharactersMap(text, embeddingLevels);
console.log("Mirrored Characters Map (index -> replacement):", Array.from(mirroredCharactersMap.entries()));
}
runBidiExample();