@flighthq/textshaper 0.1.0
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.
- package/dist/_textShaperHooks.d.ts +4 -0
- package/dist/_textShaperHooks.d.ts.map +1 -0
- package/dist/_textShaperHooks.js +5 -0
- package/dist/_textShaperHooks.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/textShaper.d.ts +5 -0
- package/dist/textShaper.d.ts.map +1 -0
- package/dist/textShaper.js +28 -0
- package/dist/textShaper.js.map +1 -0
- package/dist/textShaperCache.d.ts +9 -0
- package/dist/textShaperCache.d.ts.map +1 -0
- package/dist/textShaperCache.js +54 -0
- package/dist/textShaperCache.js.map +1 -0
- package/dist/textShaperCluster.d.ts +5 -0
- package/dist/textShaperCluster.d.ts.map +1 -0
- package/dist/textShaperCluster.js +73 -0
- package/dist/textShaperCluster.js.map +1 -0
- package/dist/textShaperItemize.d.ts +4 -0
- package/dist/textShaperItemize.d.ts.map +1 -0
- package/dist/textShaperItemize.js +161 -0
- package/dist/textShaperItemize.js.map +1 -0
- package/dist/textShaperPool.d.ts +4 -0
- package/dist/textShaperPool.d.ts.map +1 -0
- package/dist/textShaperPool.js +24 -0
- package/dist/textShaperPool.js.map +1 -0
- package/dist/textShaperRun.d.ts +15 -0
- package/dist/textShaperRun.d.ts.map +1 -0
- package/dist/textShaperRun.js +123 -0
- package/dist/textShaperRun.js.map +1 -0
- package/dist/textShaperSignals.d.ts +5 -0
- package/dist/textShaperSignals.d.ts.map +1 -0
- package/dist/textShaperSignals.js +27 -0
- package/dist/textShaperSignals.js.map +1 -0
- package/package.json +38 -0
- package/src/_textShaperHooks.test.ts +69 -0
- package/src/textShaper.test.ts +42 -0
- package/src/textShaperCache.test.ts +161 -0
- package/src/textShaperCluster.test.ts +113 -0
- package/src/textShaperItemize.test.ts +114 -0
- package/src/textShaperPool.test.ts +36 -0
- package/src/textShaperRun.test.ts +363 -0
- package/src/textShaperSignals.test.ts +84 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TextShaperBackend } from '@flighthq/types';
|
|
2
|
+
export declare let _textShaperBackendHook: ((backend: TextShaperBackend | null) => void) | null;
|
|
3
|
+
export declare function _setTextShaperBackendHook(hook: ((backend: TextShaperBackend | null) => void) | null): void;
|
|
4
|
+
//# sourceMappingURL=_textShaperHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_textShaperHooks.d.ts","sourceRoot":"","sources":["../src/_textShaperHooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,eAAO,IAAI,sBAAsB,EAAE,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAE/F,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE1G"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_textShaperHooks.js","sourceRoot":"","sources":["../src/_textShaperHooks.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,IAAI,sBAAsB,GAAyD,IAAI,CAAC;AAE/F,MAAM,UAAU,yBAAyB,CAAC,IAA0D;IAClG,sBAAsB,GAAG,IAAI,CAAC;AAChC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './textShaper';
|
|
2
|
+
export * from './textShaperCache';
|
|
3
|
+
export * from './textShaperCluster';
|
|
4
|
+
export * from './textShaperItemize';
|
|
5
|
+
export * from './textShaperPool';
|
|
6
|
+
export * from './textShaperRun';
|
|
7
|
+
export * from './textShaperSignals';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './textShaper';
|
|
2
|
+
export * from './textShaperCache';
|
|
3
|
+
export * from './textShaperCluster';
|
|
4
|
+
export * from './textShaperItemize';
|
|
5
|
+
export * from './textShaperPool';
|
|
6
|
+
export * from './textShaperRun';
|
|
7
|
+
export * from './textShaperSignals';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TextFormat, TextShaperBackend } from '@flighthq/types';
|
|
2
|
+
export declare function getTextShaperBackend(): TextShaperBackend | null;
|
|
3
|
+
export declare function measureText(text: string, format: Readonly<TextFormat>): number;
|
|
4
|
+
export declare function setTextShaperBackend(backend: TextShaperBackend | null): void;
|
|
5
|
+
//# sourceMappingURL=textShaper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaper.d.ts","sourceRoot":"","sources":["../src/textShaper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAWrE,wBAAgB,oBAAoB,IAAI,iBAAiB,GAAG,IAAI,CAE/D;AAKD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,CAG9E;AAKD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAG5E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { _textShaperBackendHook } from './_textShaperHooks';
|
|
2
|
+
// Returns the active text-shaper backend, or null when none has been registered. Unlike the
|
|
3
|
+
// always-on platform capabilities (clipboard, storage), shaping has no light web default that lives
|
|
4
|
+
// here: the canvas backend needs DOM + font-string computation, so it ships in
|
|
5
|
+
// @flighthq/textshaper-canvas and is installed via setTextShaperBackend. This mirrors text-layout's
|
|
6
|
+
// historical measure-provider, which was null until a renderer registered one — callers fall back to
|
|
7
|
+
// leaving text unmeasured until a backend exists. A future @flighthq/textshaper-harfbuzz registers
|
|
8
|
+
// the same way.
|
|
9
|
+
export function getTextShaperBackend() {
|
|
10
|
+
return _backend;
|
|
11
|
+
}
|
|
12
|
+
// Measures `text` in `format` to its horizontal advance, in pixels, via the active backend. Returns
|
|
13
|
+
// the sentinel -1 when no backend is registered (expected before setup), so callers can distinguish
|
|
14
|
+
// "unmeasurable" from a real zero-width advance.
|
|
15
|
+
export function measureText(text, format) {
|
|
16
|
+
if (_backend === null)
|
|
17
|
+
return -1;
|
|
18
|
+
return _backend.measureText(text, format);
|
|
19
|
+
}
|
|
20
|
+
// Installs a text-shaper backend; pass null to clear it. Last write wins — registering over an
|
|
21
|
+
// existing backend replaces it, which is how a host swaps the canvas default for HarfBuzz. Never
|
|
22
|
+
// throws on re-registration.
|
|
23
|
+
export function setTextShaperBackend(backend) {
|
|
24
|
+
_backend = backend;
|
|
25
|
+
_textShaperBackendHook?.(backend);
|
|
26
|
+
}
|
|
27
|
+
let _backend = null;
|
|
28
|
+
//# sourceMappingURL=textShaper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaper.js","sourceRoot":"","sources":["../src/textShaper.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,4FAA4F;AAC5F,oGAAoG;AACpG,+EAA+E;AAC/E,oGAAoG;AACpG,qGAAqG;AACrG,mGAAmG;AACnG,gBAAgB;AAChB,MAAM,UAAU,oBAAoB;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,oGAAoG;AACpG,oGAAoG;AACpG,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAA4B;IACpE,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC,CAAC;IACjC,OAAO,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,+FAA+F;AAC/F,iGAAiG;AACjG,6BAA6B;AAC7B,MAAM,UAAU,oBAAoB,CAAC,OAAiC;IACpE,QAAQ,GAAG,OAAO,CAAC;IACnB,sBAAsB,EAAE,CAAC,OAAO,CAAC,CAAC;AACpC,CAAC;AAED,IAAI,QAAQ,GAA6B,IAAI,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShapedRun, ShapeRunOptions, TextFormat } from '@flighthq/types';
|
|
2
|
+
export interface TextShaperCache {
|
|
3
|
+
readonly _entries: Map<string, ShapedRun>;
|
|
4
|
+
}
|
|
5
|
+
export declare function clearTextShaperCache(cache: TextShaperCache): void;
|
|
6
|
+
export declare function createTextShaperCache(): TextShaperCache;
|
|
7
|
+
export declare function disposeTextShaperCache(cache: TextShaperCache): void;
|
|
8
|
+
export declare function shapeTextRunCached(cache: TextShaperCache, text: string, format: Readonly<TextFormat>, options?: Readonly<ShapeRunOptions>): ShapedRun | null;
|
|
9
|
+
//# sourceMappingURL=textShaperCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperCache.d.ts","sourceRoot":"","sources":["../src/textShaperCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI9E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAC3C;AAKD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAEjE;AAGD,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAKD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAEnE;AAMD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC5B,OAAO,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,GAClC,SAAS,GAAG,IAAI,CAOlB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { shapeTextRun } from './textShaperRun';
|
|
2
|
+
// Clears all cached ShapedRuns from `cache`, releasing their references. The cache remains valid
|
|
3
|
+
// for continued use after clearing. Does not release the cache object itself (use
|
|
4
|
+
// `disposeTextShaperCache` for that).
|
|
5
|
+
export function clearTextShaperCache(cache) {
|
|
6
|
+
cache._entries.clear();
|
|
7
|
+
}
|
|
8
|
+
// Allocates a new, empty TextShaperCache.
|
|
9
|
+
export function createTextShaperCache() {
|
|
10
|
+
return { _entries: new Map() };
|
|
11
|
+
}
|
|
12
|
+
// Releases the `cache` object and all cached ShapedRuns. The cache must not be used after this
|
|
13
|
+
// call. Prefer `clearTextShaperCache` when you want to reuse the cache object; use
|
|
14
|
+
// `disposeTextShaperCache` only when the cache lifetime is over.
|
|
15
|
+
export function disposeTextShaperCache(cache) {
|
|
16
|
+
cache._entries.clear();
|
|
17
|
+
}
|
|
18
|
+
// Shapes `text` in `format` with `options`, returning a cached ShapedRun when an equivalent call
|
|
19
|
+
// was made earlier in this cache's lifetime. Calls `shapeTextRun` on miss and stores the result.
|
|
20
|
+
// Returns null when no backend is registered or the backend is advances-only (same as
|
|
21
|
+
// `shapeTextRun`); null results are NOT cached so a later backend registration can succeed.
|
|
22
|
+
export function shapeTextRunCached(cache, text, format, options) {
|
|
23
|
+
const key = _makeCacheKey(text, format, options);
|
|
24
|
+
const existing = cache._entries.get(key);
|
|
25
|
+
if (existing !== undefined)
|
|
26
|
+
return existing;
|
|
27
|
+
const result = shapeTextRun(text, format, options);
|
|
28
|
+
if (result !== null)
|
|
29
|
+
cache._entries.set(key, result);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
// Builds a stable cache key from the shaping parameters. The key encodes all fields that affect
|
|
33
|
+
// the shaped output so distinct inputs do not collide. Format fields that are undefined are
|
|
34
|
+
// omitted; options fields that are undefined are omitted.
|
|
35
|
+
function _makeCacheKey(text, format, options) {
|
|
36
|
+
// Format fields that affect shaping: font identity, size, bold, italic, kerning, letterSpacing.
|
|
37
|
+
const fmt = [
|
|
38
|
+
format.font ?? '',
|
|
39
|
+
format.size ?? 12,
|
|
40
|
+
format.bold ? 1 : 0,
|
|
41
|
+
format.italic ? 1 : 0,
|
|
42
|
+
format.kerning ? 1 : 0,
|
|
43
|
+
format.letterSpacing ?? 0,
|
|
44
|
+
].join('\x01');
|
|
45
|
+
// Options: direction, script.
|
|
46
|
+
let opts = '';
|
|
47
|
+
if (options !== undefined) {
|
|
48
|
+
const dir = options.direction ?? '';
|
|
49
|
+
const script = options.script ?? '';
|
|
50
|
+
opts = `${dir}\x01${script}`;
|
|
51
|
+
}
|
|
52
|
+
return `${text}\x00${fmt}\x00${opts}`;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=textShaperCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperCache.js","sourceRoot":"","sources":["../src/textShaperCache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAM/C,iGAAiG;AACjG,kFAAkF;AAClF,sCAAsC;AACtC,MAAM,UAAU,oBAAoB,CAAC,KAAsB;IACzD,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,qBAAqB;IACnC,OAAO,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;AACjC,CAAC;AAED,+FAA+F;AAC/F,mFAAmF;AACnF,iEAAiE;AACjE,MAAM,UAAU,sBAAsB,CAAC,KAAsB;IAC3D,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,iGAAiG;AACjG,iGAAiG;AACjG,sFAAsF;AACtF,4FAA4F;AAC5F,MAAM,UAAU,kBAAkB,CAChC,KAAsB,EACtB,IAAY,EACZ,MAA4B,EAC5B,OAAmC;IAEnC,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACnD,IAAI,MAAM,KAAK,IAAI;QAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gGAAgG;AAChG,4FAA4F;AAC5F,0DAA0D;AAC1D,SAAS,aAAa,CAAC,IAAY,EAAE,MAA4B,EAAE,OAAmC;IACpG,gGAAgG;IAChG,MAAM,GAAG,GAAG;QACV,MAAM,CAAC,IAAI,IAAI,EAAE;QACjB,MAAM,CAAC,IAAI,IAAI,EAAE;QACjB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,CAAC,aAAa,IAAI,CAAC;KAC1B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACf,8BAA8B;IAC9B,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QACpC,IAAI,GAAG,GAAG,GAAG,OAAO,MAAM,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO,GAAG,IAAI,OAAO,GAAG,OAAO,IAAI,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ShapedRun } from '@flighthq/types';
|
|
2
|
+
export declare function getCaretPositionsForRun(run: Readonly<ShapedRun>): number[];
|
|
3
|
+
export declare function getClusterForIndex(run: Readonly<ShapedRun>, stringIndex: number): number;
|
|
4
|
+
export declare function getIndexRangeForCluster(run: Readonly<ShapedRun>, cluster: number, stringLength?: number): readonly [number, number] | null;
|
|
5
|
+
//# sourceMappingURL=textShaperCluster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperCluster.d.ts","sourceRoot":"","sources":["../src/textShaperCluster.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAUjD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,EAAE,CAW1E;AAQD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAYxF;AAUD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,EACxB,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAqBlC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Returns the per-grapheme caret x-positions for the run, in visual order. Each position is the
|
|
2
|
+
// x-coordinate of the caret insertion point before the corresponding glyph, measured in pixels
|
|
3
|
+
// from the left edge of the run. The returned array has `glyphCount + 1` entries: index 0 is the
|
|
4
|
+
// left edge (0.0), and the last entry is the total advance width of the run.
|
|
5
|
+
//
|
|
6
|
+
// Unlike summing per-character widths, this respects per-glyph xOffset (mark attachment, kerning
|
|
7
|
+
// corrections) and ligature clusters — the caret jumps at cluster boundaries, not character
|
|
8
|
+
// boundaries, which is correct for editing into ligatures and composed characters.
|
|
9
|
+
export function getCaretPositionsForRun(run) {
|
|
10
|
+
const glyphs = run.glyphs;
|
|
11
|
+
const count = run.glyphCount;
|
|
12
|
+
const positions = new Array(count + 1);
|
|
13
|
+
positions[0] = 0;
|
|
14
|
+
let x = 0;
|
|
15
|
+
for (let i = 0; i < count; i++) {
|
|
16
|
+
x += glyphs[i].xAdvance;
|
|
17
|
+
positions[i + 1] = x;
|
|
18
|
+
}
|
|
19
|
+
return positions;
|
|
20
|
+
}
|
|
21
|
+
// Returns the cluster index (UTF-16 code-unit offset into the shaped string) for the glyph that
|
|
22
|
+
// covers `stringIndex`. If no glyph covers the given index exactly, returns the cluster of the
|
|
23
|
+
// nearest preceding glyph. Returns -1 for an empty run or when `stringIndex` is out of range.
|
|
24
|
+
//
|
|
25
|
+
// This replaces per-character advance summing for caret movement: instead of iterating characters,
|
|
26
|
+
// callers locate the correct cluster and use `getCaretPositionsForRun` for sub-cluster positioning.
|
|
27
|
+
export function getClusterForIndex(run, stringIndex) {
|
|
28
|
+
if (run.glyphCount === 0)
|
|
29
|
+
return -1;
|
|
30
|
+
if (stringIndex < 0)
|
|
31
|
+
return -1;
|
|
32
|
+
const glyphs = run.glyphs;
|
|
33
|
+
// Find the last glyph whose cluster value is <= stringIndex.
|
|
34
|
+
let best = -1;
|
|
35
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
36
|
+
if (glyphs[i].cluster <= stringIndex) {
|
|
37
|
+
best = glyphs[i].cluster;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return best;
|
|
41
|
+
}
|
|
42
|
+
// Returns the [start, end) UTF-16 string index range that the given cluster occupies. `cluster`
|
|
43
|
+
// is a cluster value as returned by `getClusterForIndex`. Returns null when the cluster is not
|
|
44
|
+
// found in the run, or when the run is empty.
|
|
45
|
+
//
|
|
46
|
+
// The range is derived from the cluster values of adjacent glyphs: the end of a cluster is the
|
|
47
|
+
// cluster value of the next distinct cluster (or the run's total string length, inferred from the
|
|
48
|
+
// last glyph's cluster + 1, which is a conservative estimate). Callers that know the source
|
|
49
|
+
// string length should pass it as `stringLength` to get an exact end for the final cluster.
|
|
50
|
+
export function getIndexRangeForCluster(run, cluster, stringLength) {
|
|
51
|
+
if (run.glyphCount === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const glyphs = run.glyphs;
|
|
54
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
55
|
+
if (glyphs[i].cluster === cluster) {
|
|
56
|
+
// Find the next distinct cluster value to determine the end of this cluster's range.
|
|
57
|
+
let end;
|
|
58
|
+
for (let j = i + 1; j < glyphs.length; j++) {
|
|
59
|
+
if (glyphs[j].cluster !== cluster) {
|
|
60
|
+
end = glyphs[j].cluster;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (end === undefined) {
|
|
65
|
+
// Last cluster in the run: end is either the supplied string length or cluster + 1.
|
|
66
|
+
end = stringLength !== undefined ? stringLength : cluster + 1;
|
|
67
|
+
}
|
|
68
|
+
return [cluster, end];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=textShaperCluster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperCluster.js","sourceRoot":"","sources":["../src/textShaperCluster.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ShapedRun, TextFormat, TextItem, TextShaperOptions } from '@flighthq/types';
|
|
2
|
+
export declare function itemizeText(text: string, _format: Readonly<TextFormat>, options?: Readonly<TextShaperOptions>): readonly TextItem[];
|
|
3
|
+
export declare function shapeTextRuns(text: string, format: Readonly<TextFormat>, options?: Readonly<TextShaperOptions>): readonly ShapedRun[];
|
|
4
|
+
//# sourceMappingURL=textShaperItemize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperItemize.d.ts","sourceRoot":"","sources":["../src/textShaperItemize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAa1F,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EAGZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC7B,OAAO,CAAC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GACpC,SAAS,QAAQ,EAAE,CAgDrB;AAQD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC5B,OAAO,CAAC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GACpC,SAAS,SAAS,EAAE,CAiBtB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { shapeTextRun } from './textShaperRun';
|
|
2
|
+
// Splits `text` into contiguous runs that share a single script and direction. Each item carries
|
|
3
|
+
// start/end UTF-16 code-unit offsets into the original string, the inferred ISO 15924 script tag,
|
|
4
|
+
// and the text direction.
|
|
5
|
+
//
|
|
6
|
+
// This is a built-in Unicode-property fallback covering Latin/RTL detection and major script
|
|
7
|
+
// splits. For full bidi correctness (Unicode Bidirectional Algorithm), complex-script joining, and
|
|
8
|
+
// language-specific alternates, use a full-glyph backend such as @flighthq/textshaper-harfbuzz.
|
|
9
|
+
//
|
|
10
|
+
// The base direction from `options.direction` is used when no strong bidi character is found.
|
|
11
|
+
export function itemizeText(text,
|
|
12
|
+
// Itemization splits on Unicode script/bidi properties alone and does not consult the font; the
|
|
13
|
+
// parameter is kept for call-site symmetry with shapeTextRuns (both take text, format, options).
|
|
14
|
+
_format, options) {
|
|
15
|
+
if (text.length === 0)
|
|
16
|
+
return [];
|
|
17
|
+
const baseDirection = options?.direction ?? 'LeftToRight';
|
|
18
|
+
const items = [];
|
|
19
|
+
let runStart = 0;
|
|
20
|
+
let runScript = '';
|
|
21
|
+
let runDirection = baseDirection;
|
|
22
|
+
for (let i = 0; i < text.length;) {
|
|
23
|
+
const codePoint = text.codePointAt(i) ?? 0;
|
|
24
|
+
const charLen = codePoint > 0xffff ? 2 : 1;
|
|
25
|
+
const charScript = getCodePointScript(codePoint);
|
|
26
|
+
const charBidi = getCodePointBidiClass(codePoint);
|
|
27
|
+
// Determine effective direction for this code point.
|
|
28
|
+
let charDirection;
|
|
29
|
+
if (charBidi === 'rtl') {
|
|
30
|
+
charDirection = 'RightToLeft';
|
|
31
|
+
}
|
|
32
|
+
else if (charBidi === 'ltr') {
|
|
33
|
+
charDirection = 'LeftToRight';
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
charDirection = runDirection; // inherit neutral chars into current run
|
|
37
|
+
}
|
|
38
|
+
// Resolve script: neutral/common characters inherit the current run's script.
|
|
39
|
+
const effectiveScript = charScript === 'Zyyy' ? runScript || 'Latn' : charScript;
|
|
40
|
+
if (i === 0) {
|
|
41
|
+
// Initialize first run.
|
|
42
|
+
runScript = effectiveScript;
|
|
43
|
+
runDirection = charDirection;
|
|
44
|
+
}
|
|
45
|
+
else if (effectiveScript !== runScript || charDirection !== runDirection) {
|
|
46
|
+
// Script or direction changed: close current run, open a new one.
|
|
47
|
+
if (i > runStart) {
|
|
48
|
+
items.push({ direction: runDirection, end: i, script: runScript, start: runStart });
|
|
49
|
+
}
|
|
50
|
+
runStart = i;
|
|
51
|
+
runScript = effectiveScript;
|
|
52
|
+
runDirection = charDirection;
|
|
53
|
+
}
|
|
54
|
+
i += charLen;
|
|
55
|
+
}
|
|
56
|
+
// Close the final run.
|
|
57
|
+
if (text.length > runStart) {
|
|
58
|
+
items.push({
|
|
59
|
+
direction: runDirection,
|
|
60
|
+
end: text.length,
|
|
61
|
+
script: runScript,
|
|
62
|
+
start: runStart,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return items;
|
|
66
|
+
}
|
|
67
|
+
// Itemizes `text` into script/direction runs and then shapes each run through the active backend,
|
|
68
|
+
// returning an array of ShapedRuns in logical order. Returns an empty array when no backend is
|
|
69
|
+
// registered or the backend is advances-only (canvas tier).
|
|
70
|
+
//
|
|
71
|
+
// This is the primary convenience entry point for multi-script strings: `itemizeText` handles the
|
|
72
|
+
// script/direction split, then `shapeTextRun` produces glyph ids and positions for each sub-run.
|
|
73
|
+
export function shapeTextRuns(text, format, options) {
|
|
74
|
+
if (text.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
const items = itemizeText(text, format, options);
|
|
77
|
+
const result = [];
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
const sub = text.slice(item.start, item.end);
|
|
80
|
+
const runOptions = {
|
|
81
|
+
...options,
|
|
82
|
+
// ShapeRunOptions models only horizontal direction; vertical (TopToBottom) maps to undefined
|
|
83
|
+
// (unspecified) and the shaper falls back to its default.
|
|
84
|
+
direction: item.direction === 'TopToBottom' ? undefined : item.direction,
|
|
85
|
+
script: item.script,
|
|
86
|
+
};
|
|
87
|
+
const run = shapeTextRun(sub, format, runOptions);
|
|
88
|
+
if (run !== null)
|
|
89
|
+
result.push(run);
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
// Returns the Unicode bidi category for a single code point. The result is a simplified category
|
|
94
|
+
// sufficient for first-level itemization (RTL/strong-RTL detection). Does not implement the full
|
|
95
|
+
// Unicode Bidirectional Algorithm — that requires a full ICU or unicode-bidi implementation which
|
|
96
|
+
// belongs in a future @flighthq/textshaper-harfbuzz or @flighthq/textshaper-icu backend.
|
|
97
|
+
function getCodePointBidiClass(codePoint) {
|
|
98
|
+
// Arabic (U+0600–U+06FF, U+0750–U+077F, U+08A0–U+08FF, U+FB50–U+FDFF, U+FE70–U+FEFF)
|
|
99
|
+
if ((codePoint >= 0x0600 && codePoint <= 0x06ff) ||
|
|
100
|
+
(codePoint >= 0x0750 && codePoint <= 0x077f) ||
|
|
101
|
+
(codePoint >= 0x08a0 && codePoint <= 0x08ff) ||
|
|
102
|
+
(codePoint >= 0xfb50 && codePoint <= 0xfdff) ||
|
|
103
|
+
(codePoint >= 0xfe70 && codePoint <= 0xfeff))
|
|
104
|
+
return 'rtl';
|
|
105
|
+
// Hebrew (U+0590–U+05FF, U+FB1D–U+FB4F)
|
|
106
|
+
if ((codePoint >= 0x0590 && codePoint <= 0x05ff) || (codePoint >= 0xfb1d && codePoint <= 0xfb4f))
|
|
107
|
+
return 'rtl';
|
|
108
|
+
// Thaana (U+0780–U+07BF), N'Ko (U+07C0–U+07FF), Samaritan (U+0800–U+083F)
|
|
109
|
+
if (codePoint >= 0x0780 && codePoint <= 0x083f)
|
|
110
|
+
return 'rtl';
|
|
111
|
+
// Syriac (U+0700–U+074F), Mandaic (U+0840–U+085F)
|
|
112
|
+
if ((codePoint >= 0x0700 && codePoint <= 0x074f) || (codePoint >= 0x0840 && codePoint <= 0x085f))
|
|
113
|
+
return 'rtl';
|
|
114
|
+
// ASCII letters/digits: LTR
|
|
115
|
+
if ((codePoint >= 0x0041 && codePoint <= 0x005a) ||
|
|
116
|
+
(codePoint >= 0x0061 && codePoint <= 0x007a) ||
|
|
117
|
+
(codePoint >= 0x0030 && codePoint <= 0x0039))
|
|
118
|
+
return 'ltr';
|
|
119
|
+
// Latin extended, Greek, Cyrillic, CJK: LTR
|
|
120
|
+
if (codePoint >= 0x00c0 && codePoint <= 0x02ff)
|
|
121
|
+
return 'ltr';
|
|
122
|
+
if (codePoint >= 0x0370 && codePoint <= 0x04ff)
|
|
123
|
+
return 'ltr';
|
|
124
|
+
if (codePoint >= 0x4e00 && codePoint <= 0x9fff)
|
|
125
|
+
return 'ltr';
|
|
126
|
+
// Everything else: neutral (punctuation, whitespace, symbols)
|
|
127
|
+
return 'neutral';
|
|
128
|
+
}
|
|
129
|
+
// Returns a simplified ISO 15924 script tag for a code point. This is not a complete Unicode
|
|
130
|
+
// script property lookup; it covers the most common scripts for first-pass itemization. A full
|
|
131
|
+
// implementation belongs in a HarfBuzz/ICU-backed shaper.
|
|
132
|
+
function getCodePointScript(codePoint) {
|
|
133
|
+
if (codePoint >= 0x0041 && codePoint <= 0x007a)
|
|
134
|
+
return 'Latn';
|
|
135
|
+
if (codePoint >= 0x00c0 && codePoint <= 0x024f)
|
|
136
|
+
return 'Latn';
|
|
137
|
+
if (codePoint >= 0x0370 && codePoint <= 0x03ff)
|
|
138
|
+
return 'Grek';
|
|
139
|
+
if (codePoint >= 0x0400 && codePoint <= 0x04ff)
|
|
140
|
+
return 'Cyrl';
|
|
141
|
+
if (codePoint >= 0x0590 && codePoint <= 0x05ff)
|
|
142
|
+
return 'Hebr';
|
|
143
|
+
if (codePoint >= 0x0600 && codePoint <= 0x06ff)
|
|
144
|
+
return 'Arab';
|
|
145
|
+
if (codePoint >= 0x0700 && codePoint <= 0x074f)
|
|
146
|
+
return 'Syrc';
|
|
147
|
+
if (codePoint >= 0x0900 && codePoint <= 0x097f)
|
|
148
|
+
return 'Deva';
|
|
149
|
+
if (codePoint >= 0x0e00 && codePoint <= 0x0e7f)
|
|
150
|
+
return 'Thai';
|
|
151
|
+
if (codePoint >= 0x3040 && codePoint <= 0x309f)
|
|
152
|
+
return 'Hira';
|
|
153
|
+
if (codePoint >= 0x30a0 && codePoint <= 0x30ff)
|
|
154
|
+
return 'Kana';
|
|
155
|
+
if (codePoint >= 0x4e00 && codePoint <= 0x9fff)
|
|
156
|
+
return 'Hans';
|
|
157
|
+
if (codePoint >= 0xac00 && codePoint <= 0xd7af)
|
|
158
|
+
return 'Hang';
|
|
159
|
+
return 'Zyyy'; // Common script
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=textShaperItemize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperItemize.js","sourceRoot":"","sources":["../src/textShaperItemize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperPool.d.ts","sourceRoot":"","sources":["../src/textShaperPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAUjD,wBAAgB,gBAAgB,IAAI,SAAS,CAG5C;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAIrD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createShapedRun } from './textShaperRun';
|
|
2
|
+
// Acquires a ShapedRun from the pool, allocating a new one when the pool is empty.
|
|
3
|
+
// Must be paired with a matching `releaseShapedRun` call. Treat as paired brackets:
|
|
4
|
+
// every `acquireShapedRun` call must have exactly one `releaseShapedRun` in its lifetime.
|
|
5
|
+
//
|
|
6
|
+
// The returned run is in an unspecified state — always populate it before use (e.g. via
|
|
7
|
+
// `shapeTextRunInto`).
|
|
8
|
+
export function acquireShapedRun() {
|
|
9
|
+
if (_pool.length > 0)
|
|
10
|
+
return _pool.pop();
|
|
11
|
+
return createShapedRun();
|
|
12
|
+
}
|
|
13
|
+
// Returns a ShapedRun to the pool. The run must not be used after release. Pairs with
|
|
14
|
+
// `acquireShapedRun`. Runs released beyond the pool capacity are silently discarded (GC-collected).
|
|
15
|
+
export function releaseShapedRun(run) {
|
|
16
|
+
if (_pool.length < _POOL_MAX_SIZE) {
|
|
17
|
+
_pool.push(run);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Maximum number of ShapedRuns to retain in the pool before discarding on release. Keeps
|
|
21
|
+
// memory bounded in cases where burst shaping produces many runs.
|
|
22
|
+
const _POOL_MAX_SIZE = 64;
|
|
23
|
+
const _pool = [];
|
|
24
|
+
//# sourceMappingURL=textShaperPool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperPool.js","sourceRoot":"","sources":["../src/textShaperPool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,mFAAmF;AACnF,oFAAoF;AACpF,0FAA0F;AAC1F,EAAE;AACF,wFAAwF;AACxF,uBAAuB;AACvB,MAAM,UAAU,gBAAgB;IAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,EAAG,CAAC;IAC1C,OAAO,eAAe,EAAE,CAAC;AAC3B,CAAC;AAED,sFAAsF;AACtF,oGAAoG;AACpG,MAAM,UAAU,gBAAgB,CAAC,GAAc;IAC7C,IAAI,KAAK,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,kEAAkE;AAClE,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,KAAK,GAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FontMetrics, GlyphExtents, ShapedRun, ShapeRunOptions, TextFormat } from '@flighthq/types';
|
|
2
|
+
export declare function clearShapedRun(run: ShapedRun): ShapedRun;
|
|
3
|
+
export declare function createShapedRun(): ShapedRun;
|
|
4
|
+
export declare function getCodePointForGlyph(glyphId: number, _format: Readonly<TextFormat>): number;
|
|
5
|
+
export declare function getFontMetrics(format: Readonly<TextFormat>): FontMetrics | null;
|
|
6
|
+
export declare function getFontMetricsInto(format: Readonly<TextFormat>, out: FontMetrics): boolean;
|
|
7
|
+
export declare function getFontUnitScale(format: Readonly<TextFormat>): number;
|
|
8
|
+
export declare function getGlyphExtents(glyphId: number, _format: Readonly<TextFormat>): GlyphExtents | null;
|
|
9
|
+
export declare function getGlyphExtentsBatch(glyphIds: ReadonlyArray<number>, _format: Readonly<TextFormat>, out: GlyphExtents[]): number;
|
|
10
|
+
export declare function getGlyphExtentsInto(glyphId: number, _format: Readonly<TextFormat>, out: GlyphExtents): boolean;
|
|
11
|
+
export declare function getGlyphIndexForCodePoint(codePoint: number, _format: Readonly<TextFormat>): number;
|
|
12
|
+
export declare function getGlyphName(glyphId: number, _format: Readonly<TextFormat>): string;
|
|
13
|
+
export declare function shapeTextRun(text: string, format: Readonly<TextFormat>, options?: ShapeRunOptions): ShapedRun | null;
|
|
14
|
+
export declare function shapeTextRunInto(text: string, format: Readonly<TextFormat>, out: ShapedRun, options?: ShapeRunOptions): boolean;
|
|
15
|
+
//# sourceMappingURL=textShaperRun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textShaperRun.d.ts","sourceRoot":"","sources":["../src/textShaperRun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIzG,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,CAQxD;AAED,wBAAgB,eAAe,IAAI,SAAS,CAS3C;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,CAI3F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,IAAI,CAI/E;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAY1F;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,CAKrE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY,GAAG,IAAI,CAInG;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC7B,GAAG,EAAE,YAAY,EAAE,GAClB,MAAM,CAcR;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAQ9G;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,CAIlG;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,CAInF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAIpH;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC5B,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAgBT"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getTextShaperBackend } from './textShaper';
|
|
2
|
+
export function clearShapedRun(run) {
|
|
3
|
+
run.advanceWidth = 0;
|
|
4
|
+
run.direction = 'LeftToRight';
|
|
5
|
+
run.font = null;
|
|
6
|
+
run.glyphCount = 0;
|
|
7
|
+
run.glyphs.length = 0;
|
|
8
|
+
run.script = '';
|
|
9
|
+
return run;
|
|
10
|
+
}
|
|
11
|
+
export function createShapedRun() {
|
|
12
|
+
return {
|
|
13
|
+
advanceWidth: 0,
|
|
14
|
+
direction: 'LeftToRight',
|
|
15
|
+
font: null,
|
|
16
|
+
glyphCount: 0,
|
|
17
|
+
glyphs: [],
|
|
18
|
+
script: '',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function getCodePointForGlyph(glyphId, _format) {
|
|
22
|
+
const backend = getTextShaperBackend();
|
|
23
|
+
if (backend === null || !backend.getCodePointForGlyph)
|
|
24
|
+
return -1;
|
|
25
|
+
return backend.getCodePointForGlyph(glyphId);
|
|
26
|
+
}
|
|
27
|
+
export function getFontMetrics(format) {
|
|
28
|
+
const backend = getTextShaperBackend();
|
|
29
|
+
if (backend === null || !backend.getFontMetrics)
|
|
30
|
+
return null;
|
|
31
|
+
return backend.getFontMetrics(format);
|
|
32
|
+
}
|
|
33
|
+
export function getFontMetricsInto(format, out) {
|
|
34
|
+
const metrics = getFontMetrics(format);
|
|
35
|
+
if (metrics === null)
|
|
36
|
+
return false;
|
|
37
|
+
out.ascent = metrics.ascent;
|
|
38
|
+
out.capHeight = metrics.capHeight;
|
|
39
|
+
out.descent = metrics.descent;
|
|
40
|
+
out.lineGap = metrics.lineGap;
|
|
41
|
+
out.underlinePosition = metrics.underlinePosition;
|
|
42
|
+
out.underlineThickness = metrics.underlineThickness;
|
|
43
|
+
out.unitsPerEm = metrics.unitsPerEm;
|
|
44
|
+
out.xHeight = metrics.xHeight;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
export function getFontUnitScale(format) {
|
|
48
|
+
const metrics = getFontMetrics(format);
|
|
49
|
+
if (metrics === null)
|
|
50
|
+
return -1;
|
|
51
|
+
const size = format.size ?? 12;
|
|
52
|
+
return size / metrics.unitsPerEm;
|
|
53
|
+
}
|
|
54
|
+
export function getGlyphExtents(glyphId, _format) {
|
|
55
|
+
const backend = getTextShaperBackend();
|
|
56
|
+
if (backend === null || !backend.getGlyphExtents)
|
|
57
|
+
return null;
|
|
58
|
+
return backend.getGlyphExtents(glyphId);
|
|
59
|
+
}
|
|
60
|
+
export function getGlyphExtentsBatch(glyphIds, _format, out) {
|
|
61
|
+
const backend = getTextShaperBackend();
|
|
62
|
+
if (backend === null || !backend.getGlyphExtents)
|
|
63
|
+
return 0;
|
|
64
|
+
let resolved = 0;
|
|
65
|
+
for (let i = 0; i < glyphIds.length; i++) {
|
|
66
|
+
const extents = backend.getGlyphExtents(glyphIds[i]);
|
|
67
|
+
if (extents !== null) {
|
|
68
|
+
out[i] = extents;
|
|
69
|
+
resolved++;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
out[i] = { height: 0, width: 0, xBearing: 0, yBearing: 0 };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
export function getGlyphExtentsInto(glyphId, _format, out) {
|
|
78
|
+
const extents = getGlyphExtents(glyphId, _format);
|
|
79
|
+
if (extents === null)
|
|
80
|
+
return false;
|
|
81
|
+
out.height = extents.height;
|
|
82
|
+
out.width = extents.width;
|
|
83
|
+
out.xBearing = extents.xBearing;
|
|
84
|
+
out.yBearing = extents.yBearing;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
export function getGlyphIndexForCodePoint(codePoint, _format) {
|
|
88
|
+
const backend = getTextShaperBackend();
|
|
89
|
+
if (backend === null || !backend.getGlyphIndexForCodePoint)
|
|
90
|
+
return -1;
|
|
91
|
+
return backend.getGlyphIndexForCodePoint(codePoint);
|
|
92
|
+
}
|
|
93
|
+
export function getGlyphName(glyphId, _format) {
|
|
94
|
+
const backend = getTextShaperBackend();
|
|
95
|
+
if (backend === null || !backend.getGlyphName)
|
|
96
|
+
return '';
|
|
97
|
+
return backend.getGlyphName(glyphId);
|
|
98
|
+
}
|
|
99
|
+
export function shapeTextRun(text, format, options) {
|
|
100
|
+
const backend = getTextShaperBackend();
|
|
101
|
+
if (backend === null || !backend.shapeRun)
|
|
102
|
+
return null;
|
|
103
|
+
return backend.shapeRun(text, format, options);
|
|
104
|
+
}
|
|
105
|
+
export function shapeTextRunInto(text, format, out, options) {
|
|
106
|
+
const backend = getTextShaperBackend();
|
|
107
|
+
if (backend === null || !backend.shapeRun)
|
|
108
|
+
return false;
|
|
109
|
+
const result = backend.shapeRun(text, format, options);
|
|
110
|
+
const glyphs = out.glyphs;
|
|
111
|
+
out.advanceWidth = result.advanceWidth;
|
|
112
|
+
out.direction = result.direction;
|
|
113
|
+
out.font = result.font;
|
|
114
|
+
out.glyphCount = result.glyphCount;
|
|
115
|
+
out.script = result.script;
|
|
116
|
+
glyphs.length = 0;
|
|
117
|
+
for (let i = 0; i < result.glyphs.length; i++) {
|
|
118
|
+
glyphs.push(result.glyphs[i]);
|
|
119
|
+
}
|
|
120
|
+
out.glyphs = glyphs;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=textShaperRun.js.map
|