diff --git a/README.md b/README.md index 1bf163b..d09f09b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,26 @@ An [Emscripten] port of a subset of the functionality of [International Componen ## Using @mapwhit/rtl-text -@mapwhit/rtl-text exposes two functions: +```js +import rtlText from '@mapwhit/rtl-text'; +const {applyArabicShaping, processBidirectionalText} = await rtlText(); + +const arabicString = "سلام"; +const shapedArabicText = applyArabicShaping(arabicString); +const readyForDisplay = processBidirectionalText(shapedArabicText, []); +``` + +The default location of the compiled wasm file is the same folder as the javascript. If different it can be provided as a parameter to `rtlText`. + +```js +// in browser +const {applyArabicShaping, processBidirectionalText} = await rtlText('https://example.com/rtl/icu-123.wasm'); + +// in node +const {applyArabicShaping, processBidirectionalText} = await rtlText('/lib/rtl/icu-123.wasm'); +``` + +@mapwhit/rtl-text exposes following functions: ### `applyArabicShaping(unicodeInput)` @@ -19,6 +38,11 @@ Takes an input string in "logical order" (i.e. characters in the order they are Takes an input string with characters in "logical order", along with a set of chosen line break points, and applies the [Unicode Bidirectional Algorithm] to the string. Returns an ordered set of lines with characters in "visual order" (i.e. characters in the order they are displayed, left-to-right). The algorithm will insert mandatory line breaks (`\n` etc.) if they are not already included in `lineBreakPoints`. +### `processStyledBidirectionalText(unicodeInput, styleIndices, lineBreakPoints)` + +Takes an input string in logical order and applies the BiDi algorithm using the chosen line break points to generate a set of lines with the characters re-arranged into visual order. + +Also takes an array of "style indices" that specify different styling on the input characters (the styles are represented as integers here, the caller is responsible for the actual implementation of styling). BiDi can both reorder and add/remove characters from the input string, but this function copies style information from the "source" logical characters to their corresponding visual characters in the output. [tilerenderer]: https://npmjs.org/package/@mapwhit/tilerenderer [mapbox-gl-rtl-text]: https://npmjs.org/package/@mapbox/mapbox-gl-rtl-text diff --git a/package.json b/package.json index 5269727..78fe442 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "prepare": "make build" }, "devDependencies": { - "@biomejs/biome": "2.2.4" + "@biomejs/biome": "2.2.4", + "undici": "^7.19.1" }, "files": [ "src/*.js", diff --git a/src/browser.js b/src/browser.js index 1c18b95..3d9d425 100644 --- a/src/browser.js +++ b/src/browser.js @@ -1,14 +1,9 @@ function locateFile(file) { - return new URL(urlFromDataset(file) ?? file, document.baseURI); + return new URL(file, document.baseURI); } -function urlFromDataset(file) { - const el = document.getElementById('rtl-text'); - return el?.getAttribute(`data-${file.replace('.', '-')}`); -} - -export default async function instantiateAsync(imports) { - const url = locateFile('icu.wasm'); +export default async function instantiateAsync(imports, file = 'icu.wasm') { + const url = locateFile(file); const response = fetch(url, { mode: 'cors', credentials: 'omit' }); return await WebAssembly.instantiateStreaming(response, imports); } diff --git a/src/index.js b/src/index.js index 05f9c16..8611ef4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,8 @@ import instantiateAsync from '#instantiate'; import icu from './icu.js'; -export default async function makeModule() { - const Module = await icu({}, { instantiateAsync }); - +export default async function makeModule(file) { + const Module = await icu({}, { instantiateAsync: imports => instantiateAsync(imports, file) }); /** * Takes logical input and replaces Arabic characters with the "presentation form" * of their initial/medial/final forms, based on their order in the input. @@ -248,12 +247,6 @@ export default async function makeModule() { return lines; } - globalThis.registerRTLTextPlugin?.({ - applyArabicShaping, - processBidirectionalText, - processStyledBidirectionalText - }); - return { applyArabicShaping, processBidirectionalText, diff --git a/src/node.js b/src/node.js index 7c4a43d..aaae186 100644 --- a/src/node.js +++ b/src/node.js @@ -1,9 +1,7 @@ -import { readFile } from 'node:fs/promises'; +import fs from 'node:fs/promises'; import { resolve } from 'node:path'; -const filename = resolve(import.meta.dirname, './icu.wasm'); - -export default async function instantiate(imports) { - const buffer = await readFile(filename); +export default async function instantiateAsync(imports, file = './icu.wasm') { + const buffer = await fs.readFile(resolve(import.meta.dirname, file)); return await WebAssembly.instantiate(buffer, imports); } diff --git a/test/browser.js b/test/browser.js new file mode 100644 index 0000000..3c3c805 --- /dev/null +++ b/test/browser.js @@ -0,0 +1,32 @@ +import test from 'node:test'; +import { MockAgent, setGlobalDispatcher } from 'undici'; +import instantiateAsync from '../src/browser.js'; + +const url = 'http://example.com'; +const mockAgent = new MockAgent(); + +test('instantiate in browser', async t => { + t.before(() => { + global.document = {}; + global.document.baseURI = url; + setGlobalDispatcher(mockAgent); + mockAgent.disableNetConnect(); + }); + t.after(async () => { + await mockAgent.close(); + }); + + await t.test('default location', async t => { + mockAgent.get(url).intercept({ path: '/icu.wasm' }).reply(404); + + await t.assert.rejects(instantiateAsync({})); + t.assert.deepEqual(mockAgent.pendingInterceptors(), []); + }); + + await t.test('provided location', async t => { + mockAgent.get(url).intercept({ path: '/icu-123.wasm' }).reply(404); + + await t.assert.rejects(instantiateAsync({}, 'icu-123.wasm')); + t.assert.deepEqual(mockAgent.pendingInterceptors(), []); + }); +}); diff --git a/test.js b/test/index.js similarity index 98% rename from test.js rename to test/index.js index 1d8cfa4..1741970 100644 --- a/test.js +++ b/test/index.js @@ -1,7 +1,7 @@ import assert from 'node:assert'; import test from 'node:test'; -import rtlText from './src/index.js'; +import rtlText from '../src/index.js'; const { applyArabicShaping, processBidirectionalText, processStyledBidirectionalText } = await rtlText(); diff --git a/test/node.js b/test/node.js new file mode 100644 index 0000000..0476da3 --- /dev/null +++ b/test/node.js @@ -0,0 +1,24 @@ +import fs from 'node:fs/promises'; +import test from 'node:test'; +import instantiateAsync from '../src/node.js'; + +test('instantiate in node', async t => { + t.before(() => {}); + t.after(async () => {}); + + await t.test('default location', async t => { + t.mock.method(fs, 'readFile', () => Promise.reject()); + + await t.assert.rejects(instantiateAsync({})); + t.assert.equal(fs.readFile.mock.callCount(), 1); + t.assert.ok(fs.readFile.mock.calls[0].arguments[0].endsWith('/icu.wasm')); + }); + + await t.test('provided location', async t => { + t.mock.method(fs, 'readFile', () => Promise.reject()); + + await t.assert.rejects(instantiateAsync({}, './icu-123.wasm')); + t.assert.equal(fs.readFile.mock.callCount(), 1); + t.assert.ok(fs.readFile.mock.calls[0].arguments[0].endsWith('/icu-123.wasm')); + }); +});