@flighthq/textshaper 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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/README.md CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/textshaper`. The `@f
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/textshaper@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/textshaper)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/textshaper@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/textshaper)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
@@ -1,5 +1,7 @@
1
- import type { TextFormat, TextShaperBackend } from '@flighthq/types/contract';
1
+ import type { BackendOperationExplanation, TextFormat, TextShaperBackend, TextShaperOperation } from '@flighthq/types/contract';
2
+ export declare function explainTextShaperOperation(operation: TextShaperOperation): BackendOperationExplanation;
2
3
  export declare function getTextShaperBackend(): TextShaperBackend | null;
4
+ export declare function hasTextShaperOperation(operation: TextShaperOperation): boolean;
3
5
  export declare function measureText(text: string, format: Readonly<TextFormat>): number;
4
6
  export declare function setTextShaperBackend(backend: TextShaperBackend | null): void;
5
7
  //# sourceMappingURL=textShaper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"textShaper.d.ts","sourceRoot":"","sources":["../src/textShaper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAW9E,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"}
1
+ {"version":3,"file":"textShaper.d.ts","sourceRoot":"","sources":["../src/textShaper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,UAAU,EACV,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAelC,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,mBAAmB,GAAG,2BAA2B,CAKtG;AAED,wBAAgB,oBAAoB,IAAI,iBAAiB,GAAG,IAAI,CAE/D;AAID,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAE9E;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"}
@@ -6,9 +6,24 @@ import { _textShaperBackendHook } from './_textShaperHooks';
6
6
  // historical measure-provider, which was null until a renderer registered one — callers fall back to
7
7
  // leaving text unmeasured until a backend exists. A future @flighthq/textshaper-harfbuzz registers
8
8
  // the same way.
9
+ // Which layer implements `operation`. This capability is a SINGLE NULLABLE SLOT — there is no host layer
10
+ // and no sentinel — so the two honest answers are `'custom'` (a backend is installed and provides it) and
11
+ // `'none'` (no backend is installed at all). Reporting `'sentinel'` here would name a fall-through object
12
+ // that does not exist, and inventing a host layer would describe a precedence this package does not have.
13
+ export function explainTextShaperOperation(operation) {
14
+ if (_backend !== null && typeof _backend[operation] === 'function') {
15
+ return { implemented: true, layer: 'custom', operation };
16
+ }
17
+ return { implemented: false, layer: 'none', operation };
18
+ }
9
19
  export function getTextShaperBackend() {
10
20
  return _backend;
11
21
  }
22
+ // Whether an installed backend implements `operation`. False when nothing is installed, which for this
23
+ // capability is also what the getter reports by returning null.
24
+ export function hasTextShaperOperation(operation) {
25
+ return explainTextShaperOperation(operation).implemented;
26
+ }
12
27
  // Measures `text` in `format` to its horizontal advance, in pixels, via the active backend. Returns
13
28
  // the sentinel -1 when no backend is registered (expected before setup), so callers can distinguish
14
29
  // "unmeasurable" from a real zero-width advance.
@@ -1 +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"}
1
+ {"version":3,"file":"textShaper.js","sourceRoot":"","sources":["../src/textShaper.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,4FAA4F;AAC5F,oGAAoG;AACpG,+EAA+E;AAC/E,oGAAoG;AACpG,qGAAqG;AACrG,mGAAmG;AACnG,gBAAgB;AAChB,yGAAyG;AACzG,0GAA0G;AAC1G,0GAA0G;AAC1G,0GAA0G;AAC1G,MAAM,UAAU,0BAA0B,CAAC,SAA8B;IACvE,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,CAAC,SAAS,CAAC,KAAK,UAAU,EAAE,CAAC;QACnE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC3D,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,uGAAuG;AACvG,gEAAgE;AAChE,MAAM,UAAU,sBAAsB,CAAC,SAA8B;IACnE,OAAO,0BAA0B,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC;AAC3D,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/textshaper",
3
- "version": "0.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.638.d4dbd4e",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,9 +38,9 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/log": "0.5.0-next.1944.cd6b6e7",
42
- "@flighthq/signals": "0.5.0-next.1944.cd6b6e7",
43
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/log": "0.5.1-edge.638.d4dbd4e",
42
+ "@flighthq/signals": "0.5.1-edge.638.d4dbd4e",
43
+ "@flighthq/types": "0.5.1-edge.638.d4dbd4e"
44
44
  },
45
45
  "devDependencies": {
46
46
  "typescript": "^5.3.0"
@@ -1,17 +1,77 @@
1
- import type { TextShaperBackend } from '@flighthq/types/contract';
1
+ import type { TextShaperBackend, TextShaperOperation } from '@flighthq/types/contract';
2
2
 
3
- import { getTextShaperBackend, measureText, setTextShaperBackend } from './textShaper';
3
+ import {
4
+ explainTextShaperOperation,
5
+ getTextShaperBackend,
6
+ hasTextShaperOperation,
7
+ measureText,
8
+ setTextShaperBackend,
9
+ } from './textShaper';
4
10
 
5
11
  afterEach(() => {
6
12
  setTextShaperBackend(null);
7
13
  });
8
14
 
15
+ describe('explainTextShaperOperation', () => {
16
+ afterEach(() => {
17
+ setTextShaperBackend(null);
18
+ });
19
+
20
+ // ★ With nothing installed, the getter returns null,
21
+ // so a query resolving through the getter would report every operation implemented. It must not.
22
+ it('reports none and no implementation when nothing is installed', () => {
23
+ setTextShaperBackend(null);
24
+ for (const operation of OPTIONAL_OPERATIONS) {
25
+ expect(explainTextShaperOperation(operation)).toEqual({ implemented: false, layer: 'none', operation });
26
+ }
27
+ });
28
+
29
+ it('reports a custom backend as implementing only what it provides', () => {
30
+ setTextShaperBackend(partialBackend());
31
+ for (const operation of OPTIONAL_OPERATIONS) {
32
+ expect(hasTextShaperOperation(operation)).toBe(false);
33
+ }
34
+ expect(explainTextShaperOperation(OPTIONAL_OPERATIONS[0]).layer).toBe('none');
35
+ });
36
+
37
+ it('reports an operation the backend does provide', () => {
38
+ const operation = OPTIONAL_OPERATIONS[0];
39
+ setTextShaperBackend({ ...partialBackend(), [operation]: () => undefined } as TextShaperBackend);
40
+ expect(explainTextShaperOperation(operation)).toEqual({ implemented: true, layer: 'custom', operation });
41
+ });
42
+ });
43
+
9
44
  describe('getTextShaperBackend', () => {
10
45
  it('returns null before a backend is set', () => {
11
46
  expect(getTextShaperBackend()).toBeNull();
12
47
  });
13
48
  });
14
49
 
50
+ describe('hasTextShaperOperation', () => {
51
+ afterEach(() => {
52
+ setTextShaperBackend(null);
53
+ });
54
+
55
+ it('agrees with explainTextShaperOperation for every optional operation', () => {
56
+ setTextShaperBackend(partialBackend());
57
+ for (const operation of OPTIONAL_OPERATIONS) {
58
+ expect(hasTextShaperOperation(operation)).toBe(explainTextShaperOperation(operation).implemented);
59
+ }
60
+ });
61
+ });
62
+
63
+ // Per-operation availability for TextShaperBackend. The operations below are the ones the interface declares
64
+ // OPTIONAL, so a host that omits them is compliant rather than broken — that is the absence-of-an-export
65
+ // ruling, and this is the query that makes it observable.
66
+ const OPTIONAL_OPERATIONS: readonly TextShaperOperation[] = [
67
+ 'getCodePointForGlyph',
68
+ 'getFontMetrics',
69
+ 'getGlyphExtents',
70
+ 'getGlyphIndexForCodePoint',
71
+ 'getGlyphName',
72
+ 'shapeRun',
73
+ ];
74
+
15
75
  describe('measureText', () => {
16
76
  it('returns -1 when no backend is registered', () => {
17
77
  expect(measureText('hello', {})).toBe(-1);
@@ -40,3 +100,10 @@ describe('setTextShaperBackend', () => {
40
100
  expect(getTextShaperBackend()).toBe(second);
41
101
  });
42
102
  });
103
+
104
+ // A host implementing only the REQUIRED members — partial support declared by absence.
105
+ function partialBackend(): TextShaperBackend {
106
+ return {
107
+ measureText: (() => undefined) as never,
108
+ } as TextShaperBackend;
109
+ }