@flighthq/font 0.3.0-next.906.07cea63 → 0.3.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/_fontFaceLoad.d.ts +6 -0
- package/dist/_fontFaceLoad.d.ts.map +1 -0
- package/dist/_fontFaceLoad.js +28 -0
- package/dist/_fontFaceLoad.js.map +1 -0
- package/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -0
- package/dist/contract.js.map +1 -1
- package/dist/fontFrom.d.ts +13 -0
- package/dist/fontFrom.d.ts.map +1 -1
- package/dist/fontFrom.js +18 -18
- package/dist/fontFrom.js.map +1 -1
- package/dist/fontResourceFrom.d.ts +16 -0
- package/dist/fontResourceFrom.d.ts.map +1 -1
- package/dist/fontResourceFrom.js +21 -18
- package/dist/fontResourceFrom.js.map +1 -1
- package/dist/glyphOutlineSource.d.ts +3 -0
- package/dist/glyphOutlineSource.d.ts.map +1 -0
- package/dist/glyphOutlineSource.js +107 -0
- package/dist/glyphOutlineSource.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/src/_fontFaceLoad.test.ts +98 -0
- package/src/fontResourceFrom.test.ts +180 -35
- package/src/glyphOutlineSource.test.ts +80 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FontUrl } from '@flighthq/types/contract';
|
|
2
|
+
export declare function _loadFontFaceFromBytes(family: string, bytes: Uint8Array): Promise<FontFace>;
|
|
3
|
+
export declare function _loadFontFaceFromUrl(family: string, url: string): Promise<FontFace>;
|
|
4
|
+
export declare function _loadFontFaceFromUrls(family: string, sources: readonly FontUrl[]): Promise<FontFace>;
|
|
5
|
+
export declare function _loadFontFacesFromName(family: string): Promise<FontFace[]>;
|
|
6
|
+
//# sourceMappingURL=_fontFaceLoad.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_fontFaceLoad.d.ts","sourceRoot":"","sources":["../src/_fontFaceLoad.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAKxD,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAGjG;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEnF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQpG;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAE1E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { inferFontFormatFromUrl } from './fontFormat';
|
|
2
|
+
import { getFontShorthand } from './fontShorthand';
|
|
3
|
+
export async function _loadFontFaceFromBytes(family, bytes) {
|
|
4
|
+
const source = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
5
|
+
return loadAndRegisterFontFace(family, source);
|
|
6
|
+
}
|
|
7
|
+
export function _loadFontFaceFromUrl(family, url) {
|
|
8
|
+
return loadAndRegisterFontFace(family, `url(${url})`);
|
|
9
|
+
}
|
|
10
|
+
export function _loadFontFaceFromUrls(family, sources) {
|
|
11
|
+
const source = sources
|
|
12
|
+
.map(({ url, format }) => {
|
|
13
|
+
const resolvedFormat = format ?? inferFontFormatFromUrl(url);
|
|
14
|
+
return resolvedFormat !== null ? `url(${url}) format('${resolvedFormat}')` : `url(${url})`;
|
|
15
|
+
})
|
|
16
|
+
.join(', ');
|
|
17
|
+
return loadAndRegisterFontFace(family, source);
|
|
18
|
+
}
|
|
19
|
+
export function _loadFontFacesFromName(family) {
|
|
20
|
+
return document.fonts.load(getFontShorthand(family));
|
|
21
|
+
}
|
|
22
|
+
async function loadAndRegisterFontFace(family, source) {
|
|
23
|
+
const face = new FontFace(family, source);
|
|
24
|
+
await face.load();
|
|
25
|
+
document.fonts.add(face);
|
|
26
|
+
return face;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=_fontFaceLoad.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_fontFaceLoad.js","sourceRoot":"","sources":["../src/_fontFaceLoad.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAc,EAAE,KAAiB;IAC5E,MAAM,MAAM,GAAI,KAAK,CAAC,MAAsB,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1G,OAAO,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,GAAW;IAC9D,OAAO,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,OAA2B;IAC/E,MAAM,MAAM,GAAG,OAAO;SACnB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE;QACvB,MAAM,cAAc,GAAG,MAAM,IAAI,sBAAsB,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,aAAa,cAAc,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC;IAC7F,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,KAAK,UAAU,uBAAuB,CAAC,MAAc,EAAE,MAA4B;IACjF,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IAClB,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
package/dist/fontFrom.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import type { Font, FontUrl } from '@flighthq/types/contract';
|
|
2
|
+
/**
|
|
3
|
+
* Loads a font face from bytes and returns its family handle. If loading rejects, the face is not
|
|
4
|
+
* registered and no `Font` is returned.
|
|
5
|
+
*/
|
|
2
6
|
export declare function loadFontFromBytes(bytes: Uint8Array, family: string): Promise<Font>;
|
|
7
|
+
/** Looks up a registered family and returns its handle. If the lookup rejects, no `Font` is returned. */
|
|
3
8
|
export declare function loadFontFromName(name: string): Promise<Font>;
|
|
9
|
+
/**
|
|
10
|
+
* Loads a font face from one URL and returns its family handle. If loading rejects, the face is not
|
|
11
|
+
* registered and no `Font` is returned.
|
|
12
|
+
*/
|
|
4
13
|
export declare function loadFontFromUrl(url: string, family: string): Promise<Font>;
|
|
14
|
+
/**
|
|
15
|
+
* Loads a font face from fallback URLs and returns its family handle. If loading rejects, the face is
|
|
16
|
+
* not registered and no `Font` is returned.
|
|
17
|
+
*/
|
|
5
18
|
export declare function loadFontFromUrls(sources: FontUrl[], family: string): Promise<Font>;
|
|
6
19
|
//# sourceMappingURL=fontFrom.d.ts.map
|
package/dist/fontFrom.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontFrom.d.ts","sourceRoot":"","sources":["../src/fontFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"fontFrom.d.ts","sourceRoot":"","sources":["../src/fontFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAU9D;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxF;AAED,yGAAyG;AACzG,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxF"}
|
package/dist/fontFrom.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
+
import { _loadFontFaceFromBytes, _loadFontFaceFromUrl, _loadFontFaceFromUrls, _loadFontFacesFromName, } from './_fontFaceLoad';
|
|
1
2
|
import { createFont } from './font';
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Loads a font face from bytes and returns its family handle. If loading rejects, the face is not
|
|
5
|
+
* registered and no `Font` is returned.
|
|
6
|
+
*/
|
|
4
7
|
export async function loadFontFromBytes(bytes, family) {
|
|
5
|
-
|
|
6
|
-
await face.load();
|
|
7
|
-
document.fonts.add(face);
|
|
8
|
+
await _loadFontFaceFromBytes(family, bytes);
|
|
8
9
|
return createFont(family);
|
|
9
10
|
}
|
|
11
|
+
/** Looks up a registered family and returns its handle. If the lookup rejects, no `Font` is returned. */
|
|
10
12
|
export async function loadFontFromName(name) {
|
|
11
|
-
await
|
|
13
|
+
await _loadFontFacesFromName(name);
|
|
12
14
|
return createFont(name);
|
|
13
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Loads a font face from one URL and returns its family handle. If loading rejects, the face is not
|
|
18
|
+
* registered and no `Font` is returned.
|
|
19
|
+
*/
|
|
14
20
|
export async function loadFontFromUrl(url, family) {
|
|
15
|
-
|
|
16
|
-
await face.load();
|
|
17
|
-
document.fonts.add(face);
|
|
21
|
+
await _loadFontFaceFromUrl(family, url);
|
|
18
22
|
return createFont(family);
|
|
19
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Loads a font face from fallback URLs and returns its family handle. If loading rejects, the face is
|
|
26
|
+
* not registered and no `Font` is returned.
|
|
27
|
+
*/
|
|
20
28
|
export async function loadFontFromUrls(sources, family) {
|
|
21
|
-
|
|
22
|
-
.map(({ url, format }) => {
|
|
23
|
-
const fmt = format ?? inferFontFormatFromUrl(url);
|
|
24
|
-
return fmt !== null ? `url(${url}) format('${fmt}')` : `url(${url})`;
|
|
25
|
-
})
|
|
26
|
-
.join(', ');
|
|
27
|
-
const face = new FontFace(family, src);
|
|
28
|
-
await face.load();
|
|
29
|
-
document.fonts.add(face);
|
|
29
|
+
await _loadFontFaceFromUrls(family, sources);
|
|
30
30
|
return createFont(family);
|
|
31
31
|
}
|
|
32
32
|
//# sourceMappingURL=fontFrom.js.map
|
package/dist/fontFrom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontFrom.js","sourceRoot":"","sources":["../src/fontFrom.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"fontFrom.js","sourceRoot":"","sources":["../src/fontFrom.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAiB,EAAE,MAAc;IACvE,MAAM,sBAAsB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,yGAAyG;AACzG,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY;IACjD,MAAM,sBAAsB,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,MAAc;IAC/D,MAAM,oBAAoB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAkB,EAAE,MAAc;IACvE,MAAM,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { FontResource, FontUrl } from '@flighthq/types/contract';
|
|
2
|
+
/**
|
|
3
|
+
* Loads a face from bytes into `out`. If loading rejects, no face is registered and `out.face` retains
|
|
4
|
+
* its previous value, so a failed reload does not blank a working resource.
|
|
5
|
+
*/
|
|
2
6
|
export declare function loadFontResourceFromBytes(out: FontResource, bytes: Uint8Array): Promise<FontResource>;
|
|
7
|
+
/**
|
|
8
|
+
* Looks up an existing family and attaches its first face to `out`. If the lookup rejects or returns no
|
|
9
|
+
* faces, `out.face` retains its previous value.
|
|
10
|
+
*/
|
|
3
11
|
export declare function loadFontResourceFromName(out: FontResource): Promise<FontResource>;
|
|
12
|
+
/**
|
|
13
|
+
* Loads a face from one URL into `out`. If loading rejects, no face is registered and `out.face`
|
|
14
|
+
* retains its previous value, so a failed reload does not blank a working resource.
|
|
15
|
+
*/
|
|
4
16
|
export declare function loadFontResourceFromUrl(out: FontResource, url: string): Promise<FontResource>;
|
|
17
|
+
/**
|
|
18
|
+
* Loads a face from fallback URLs into `out`. If loading rejects, no face is registered and `out.face`
|
|
19
|
+
* retains its previous value, so a failed reload does not blank a working resource.
|
|
20
|
+
*/
|
|
5
21
|
export declare function loadFontResourceFromUrls(out: FontResource, sources: FontUrl[]): Promise<FontResource>;
|
|
6
22
|
//# sourceMappingURL=fontResourceFrom.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontResourceFrom.d.ts","sourceRoot":"","sources":["../src/fontResourceFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"fontResourceFrom.d.ts","sourceRoot":"","sources":["../src/fontResourceFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAStE;;;GAGG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAI3G;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAIvF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAInG;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAI3G"}
|
package/dist/fontResourceFrom.js
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { _loadFontFaceFromBytes, _loadFontFaceFromUrl, _loadFontFaceFromUrls, _loadFontFacesFromName, } from './_fontFaceLoad';
|
|
2
|
+
/**
|
|
3
|
+
* Loads a face from bytes into `out`. If loading rejects, no face is registered and `out.face` retains
|
|
4
|
+
* its previous value, so a failed reload does not blank a working resource.
|
|
5
|
+
*/
|
|
3
6
|
export async function loadFontResourceFromBytes(out, bytes) {
|
|
4
|
-
const face =
|
|
5
|
-
await face.load();
|
|
6
|
-
document.fonts.add(face);
|
|
7
|
+
const face = await _loadFontFaceFromBytes(out.family, bytes);
|
|
7
8
|
out.face = face;
|
|
8
9
|
return out;
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Looks up an existing family and attaches its first face to `out`. If the lookup rejects or returns no
|
|
13
|
+
* faces, `out.face` retains its previous value.
|
|
14
|
+
*/
|
|
10
15
|
export async function loadFontResourceFromName(out) {
|
|
11
|
-
const faces = await
|
|
16
|
+
const faces = await _loadFontFacesFromName(out.family);
|
|
12
17
|
if (faces.length > 0)
|
|
13
18
|
out.face = faces[0];
|
|
14
19
|
return out;
|
|
15
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Loads a face from one URL into `out`. If loading rejects, no face is registered and `out.face`
|
|
23
|
+
* retains its previous value, so a failed reload does not blank a working resource.
|
|
24
|
+
*/
|
|
16
25
|
export async function loadFontResourceFromUrl(out, url) {
|
|
17
|
-
const face =
|
|
18
|
-
await face.load();
|
|
19
|
-
document.fonts.add(face);
|
|
26
|
+
const face = await _loadFontFaceFromUrl(out.family, url);
|
|
20
27
|
out.face = face;
|
|
21
28
|
return out;
|
|
22
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Loads a face from fallback URLs into `out`. If loading rejects, no face is registered and `out.face`
|
|
32
|
+
* retains its previous value, so a failed reload does not blank a working resource.
|
|
33
|
+
*/
|
|
23
34
|
export async function loadFontResourceFromUrls(out, sources) {
|
|
24
|
-
const
|
|
25
|
-
.map(({ url, format }) => {
|
|
26
|
-
const fmt = format ?? inferFontFormatFromUrl(url);
|
|
27
|
-
return fmt !== null ? `url(${url}) format('${fmt}')` : `url(${url})`;
|
|
28
|
-
})
|
|
29
|
-
.join(', ');
|
|
30
|
-
const face = new FontFace(out.family, src);
|
|
31
|
-
await face.load();
|
|
32
|
-
document.fonts.add(face);
|
|
35
|
+
const face = await _loadFontFaceFromUrls(out.family, sources);
|
|
33
36
|
out.face = face;
|
|
34
37
|
return out;
|
|
35
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontResourceFrom.js","sourceRoot":"","sources":["../src/fontResourceFrom.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"fontResourceFrom.js","sourceRoot":"","sources":["../src/fontResourceFrom.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AAEzB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAiB,EAAE,KAAiB;IAClF,MAAM,IAAI,GAAG,MAAM,sBAAsB,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,GAAiB;IAC9D,MAAM,KAAK,GAAG,MAAM,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,GAAiB,EAAE,GAAW;IAC1E,MAAM,IAAI,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzD,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,GAAiB,EAAE,OAAkB;IAClF,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glyphOutlineSource.d.ts","sourceRoot":"","sources":["../src/glyphOutlineSource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,kBAAkB,EAGlB,sBAAsB,EAEvB,MAAM,0BAA0B,CAAC;AAOlC,wBAAgB,kDAAkD,CAAC,MAAM,EAAE,kBAAkB,GAAG,sBAAsB,CAgBrH"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createPath, flattenPath, getPathBounds } from '@flighthq/path/contract';
|
|
2
|
+
// Adapts an index-keyed vector font into the codepoint-keyed rasterizer consumed by glyphatlas. The
|
|
3
|
+
// adapter is bound to one source, so callers can install it on one GlyphAtlas through
|
|
4
|
+
// `GlyphAtlasOptions.rasterizerBackend` without changing the process-wide backend. Rasterization is a
|
|
5
|
+
// portable 4x4 coverage scan over flattened contours: it needs no DOM/canvas and therefore works for
|
|
6
|
+
// imported fonts in browser, worker, native-host, and headless environments alike.
|
|
7
|
+
export function createGlyphRasterizerBackendFromGlyphOutlineSource(source) {
|
|
8
|
+
return {
|
|
9
|
+
measureMetrics(options) {
|
|
10
|
+
const metrics = source.getGlyphOutlineMetrics();
|
|
11
|
+
const scale = resolveGlyphOutlineScale(metrics.unitsPerEm, options.fontSize);
|
|
12
|
+
if (scale === null)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
ascent: metrics.ascent * scale,
|
|
16
|
+
descent: metrics.descent * scale,
|
|
17
|
+
lineGap: metrics.lineGap * scale,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
rasterize(codePoint, options) {
|
|
21
|
+
return rasterizeGlyphOutlineSource(source, codePoint, options);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function rasterizeGlyphOutlineSource(source, codePoint, options) {
|
|
26
|
+
const glyphIndex = source.getGlyphOutlineIndexForCodePoint(codePoint);
|
|
27
|
+
if (glyphIndex < 0)
|
|
28
|
+
return null;
|
|
29
|
+
const metrics = source.getGlyphOutlineMetrics();
|
|
30
|
+
const scale = resolveGlyphOutlineScale(metrics.unitsPerEm, options.fontSize);
|
|
31
|
+
if (scale === null)
|
|
32
|
+
return null;
|
|
33
|
+
const path = createPath();
|
|
34
|
+
if (!source.getGlyphOutline(path, glyphIndex))
|
|
35
|
+
return null;
|
|
36
|
+
const advance = source.getGlyphOutlineAdvance(glyphIndex) * scale;
|
|
37
|
+
if (!Number.isFinite(advance))
|
|
38
|
+
return null;
|
|
39
|
+
const bounds = { height: 0, width: 0, x: 0, y: 0 };
|
|
40
|
+
if (!getPathBounds(path, bounds) || bounds.width === 0 || bounds.height === 0) {
|
|
41
|
+
return { advance, bearingX: 0, bearingY: 0, height: 0, pixels: new Uint8ClampedArray(), width: 0 };
|
|
42
|
+
}
|
|
43
|
+
const left = Math.floor(bounds.x * scale) - RASTER_GUARD;
|
|
44
|
+
const top = Math.floor(bounds.y * scale) - RASTER_GUARD;
|
|
45
|
+
const right = Math.ceil((bounds.x + bounds.width) * scale) + RASTER_GUARD;
|
|
46
|
+
const bottom = Math.ceil((bounds.y + bounds.height) * scale) + RASTER_GUARD;
|
|
47
|
+
const width = right - left;
|
|
48
|
+
const height = bottom - top;
|
|
49
|
+
if (width <= 0 || height <= 0)
|
|
50
|
+
return null;
|
|
51
|
+
// Flatten once at quarter-pixel precision in design units. Sampling the Path helper directly would
|
|
52
|
+
// flatten every curve again for every subpixel and turn raster cost into pixels × path decoding.
|
|
53
|
+
const contours = flattenPath(path, 0.25 / scale);
|
|
54
|
+
const pixels = new Uint8ClampedArray(width * height * 4);
|
|
55
|
+
for (let pixelY = 0; pixelY < height; pixelY++) {
|
|
56
|
+
for (let pixelX = 0; pixelX < width; pixelX++) {
|
|
57
|
+
let covered = 0;
|
|
58
|
+
for (let sampleY = 0; sampleY < RASTER_SAMPLE_AXIS; sampleY++) {
|
|
59
|
+
const y = (top + pixelY + (sampleY + 0.5) / RASTER_SAMPLE_AXIS) / scale;
|
|
60
|
+
for (let sampleX = 0; sampleX < RASTER_SAMPLE_AXIS; sampleX++) {
|
|
61
|
+
const x = (left + pixelX + (sampleX + 0.5) / RASTER_SAMPLE_AXIS) / scale;
|
|
62
|
+
if (containsFlattenedGlyphOutlinePoint(contours, path.winding, x, y))
|
|
63
|
+
covered++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (covered === 0)
|
|
67
|
+
continue;
|
|
68
|
+
const offset = (pixelY * width + pixelX) * 4;
|
|
69
|
+
pixels[offset] = 0xff;
|
|
70
|
+
pixels[offset + 1] = 0xff;
|
|
71
|
+
pixels[offset + 2] = 0xff;
|
|
72
|
+
pixels[offset + 3] = Math.round((covered * 0xff) / RASTER_SAMPLE_COUNT);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { advance, bearingX: left, bearingY: -top, height, pixels, width };
|
|
76
|
+
}
|
|
77
|
+
function containsFlattenedGlyphOutlinePoint(contours, winding, x, y) {
|
|
78
|
+
let crossings = 0;
|
|
79
|
+
for (const contour of contours) {
|
|
80
|
+
const pointCount = (contour.length / 2) | 0;
|
|
81
|
+
if (pointCount < 2)
|
|
82
|
+
continue;
|
|
83
|
+
let fromX = contour[(pointCount - 1) * 2];
|
|
84
|
+
let fromY = contour[(pointCount - 1) * 2 + 1];
|
|
85
|
+
for (let pointIndex = 0; pointIndex < pointCount; pointIndex++) {
|
|
86
|
+
const toX = contour[pointIndex * 2];
|
|
87
|
+
const toY = contour[pointIndex * 2 + 1];
|
|
88
|
+
if ((fromY <= y && toY > y) || (toY <= y && fromY > y)) {
|
|
89
|
+
const crossingX = fromX + ((y - fromY) * (toX - fromX)) / (toY - fromY);
|
|
90
|
+
if (x < crossingX)
|
|
91
|
+
crossings += toY > fromY ? 1 : -1;
|
|
92
|
+
}
|
|
93
|
+
fromX = toX;
|
|
94
|
+
fromY = toY;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return winding === 'evenOdd' ? (Math.abs(crossings) & 1) !== 0 : crossings !== 0;
|
|
98
|
+
}
|
|
99
|
+
function resolveGlyphOutlineScale(unitsPerEm, fontSize) {
|
|
100
|
+
if (!(unitsPerEm > 0) || !(fontSize > 0) || !Number.isFinite(unitsPerEm) || !Number.isFinite(fontSize))
|
|
101
|
+
return null;
|
|
102
|
+
return fontSize / unitsPerEm;
|
|
103
|
+
}
|
|
104
|
+
const RASTER_GUARD = 1;
|
|
105
|
+
const RASTER_SAMPLE_AXIS = 4;
|
|
106
|
+
const RASTER_SAMPLE_COUNT = RASTER_SAMPLE_AXIS * RASTER_SAMPLE_AXIS;
|
|
107
|
+
//# sourceMappingURL=glyphOutlineSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glyphOutlineSource.js","sourceRoot":"","sources":["../src/glyphOutlineSource.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createFont, createFontResource, detectFontFormat, getFontShorthand, inferFontFormatFromUrl, isFontLoaded, loadFontFromBytes, loadFontFromName, loadFontFromUrl, loadFontFromUrls, loadFontResourceFromBytes, loadFontResourceFromName, loadFontResourceFromUrl, loadFontResourceFromUrls, whenFontsReady, } from './contract';
|
|
1
|
+
export { createFont, createFontResource, createGlyphRasterizerBackendFromGlyphOutlineSource, detectFontFormat, getFontShorthand, inferFontFormatFromUrl, isFontLoaded, loadFontFromBytes, loadFontFromName, loadFontFromUrl, loadFontFromUrls, loadFontResourceFromBytes, loadFontResourceFromName, loadFontResourceFromUrl, loadFontResourceFromUrls, whenFontsReady, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,kDAAkD,EAClD,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createFont, createFontResource, detectFontFormat, getFontShorthand, inferFontFormatFromUrl, isFontLoaded, loadFontFromBytes, loadFontFromName, loadFontFromUrl, loadFontFromUrls, loadFontResourceFromBytes, loadFontResourceFromName, loadFontResourceFromUrl, loadFontResourceFromUrls, whenFontsReady, } from './contract';
|
|
1
|
+
export { createFont, createFontResource, createGlyphRasterizerBackendFromGlyphOutlineSource, detectFontFormat, getFontShorthand, inferFontFormatFromUrl, isFontLoaded, loadFontFromBytes, loadFontFromName, loadFontFromUrl, loadFontFromUrls, loadFontResourceFromBytes, loadFontResourceFromName, loadFontResourceFromUrl, loadFontResourceFromUrls, whenFontsReady, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,kDAAkD,EAClD,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/font",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": "Joshua Granick and other contributors",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,8 +38,9 @@
|
|
|
36
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@flighthq/entity": "0.3.0
|
|
40
|
-
"@flighthq/
|
|
41
|
+
"@flighthq/entity": "0.3.0",
|
|
42
|
+
"@flighthq/path": "0.3.0",
|
|
43
|
+
"@flighthq/types": "0.3.0"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"typescript": "^5.3.0"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
_loadFontFaceFromBytes,
|
|
3
|
+
_loadFontFaceFromUrl,
|
|
4
|
+
_loadFontFaceFromUrls,
|
|
5
|
+
_loadFontFacesFromName,
|
|
6
|
+
} from './_fontFaceLoad';
|
|
7
|
+
|
|
8
|
+
interface FontFaceConstruction {
|
|
9
|
+
family: string;
|
|
10
|
+
source: string | ArrayBuffer;
|
|
11
|
+
instance: MockFontFace;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let constructions: FontFaceConstruction[];
|
|
15
|
+
let addMock: ReturnType<typeof vi.fn>;
|
|
16
|
+
let loadMock: ReturnType<typeof vi.fn>;
|
|
17
|
+
|
|
18
|
+
class MockFontFace {
|
|
19
|
+
load = vi.fn().mockResolvedValue(undefined);
|
|
20
|
+
constructor(family: string, source: string | ArrayBuffer) {
|
|
21
|
+
constructions.push({ family, source, instance: this });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let originalFonts: PropertyDescriptor | undefined;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
constructions = [];
|
|
28
|
+
addMock = vi.fn();
|
|
29
|
+
loadMock = vi.fn().mockResolvedValue([]);
|
|
30
|
+
vi.stubGlobal('FontFace', MockFontFace);
|
|
31
|
+
originalFonts = Object.getOwnPropertyDescriptor(document, 'fonts');
|
|
32
|
+
Object.defineProperty(document, 'fonts', {
|
|
33
|
+
value: { add: addMock, load: loadMock },
|
|
34
|
+
configurable: true,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
if (originalFonts) {
|
|
40
|
+
Object.defineProperty(document, 'fonts', originalFonts);
|
|
41
|
+
} else {
|
|
42
|
+
delete (document as unknown as { fonts?: unknown }).fonts;
|
|
43
|
+
}
|
|
44
|
+
vi.unstubAllGlobals();
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('_loadFontFaceFromBytes', () => {
|
|
49
|
+
it('loads and registers a face using only the bytes inside the supplied view', async () => {
|
|
50
|
+
const backing = new Uint8Array([1, 2, 3, 4, 5, 6]);
|
|
51
|
+
const face = await _loadFontFaceFromBytes('Family', backing.subarray(1, 5));
|
|
52
|
+
|
|
53
|
+
expect(face).toBe(constructions[0].instance);
|
|
54
|
+
expect(new Uint8Array(constructions[0].source as ArrayBuffer)).toEqual(new Uint8Array([2, 3, 4, 5]));
|
|
55
|
+
expect(constructions[0].instance.load).toHaveBeenCalledOnce();
|
|
56
|
+
expect(addMock).toHaveBeenCalledWith(face);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('_loadFontFaceFromUrl', () => {
|
|
61
|
+
it('loads and registers the single URL source', async () => {
|
|
62
|
+
await _loadFontFaceFromUrl('Family', 'font.woff2');
|
|
63
|
+
expect(constructions[0].source).toBe('url(font.woff2)');
|
|
64
|
+
expect(addMock).toHaveBeenCalledWith(constructions[0].instance);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('_loadFontFaceFromUrls', () => {
|
|
69
|
+
it('composes explicit, inferred, and absent format hints once for both public loader families', async () => {
|
|
70
|
+
await _loadFontFaceFromUrls('Family', [{ url: 'a.woff2', format: 'woff2' }, { url: 'b.ttf' }, { url: 'c.bin' }]);
|
|
71
|
+
|
|
72
|
+
expect(constructions[0].source).toBe("url(a.woff2) format('woff2'), url(b.ttf) format('truetype'), url(c.bin)");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('does not register a face when loading rejects', async () => {
|
|
76
|
+
const failing = new MockFontFace('Family', '');
|
|
77
|
+
failing.load.mockRejectedValue(new Error('bad font'));
|
|
78
|
+
vi.stubGlobal(
|
|
79
|
+
'FontFace',
|
|
80
|
+
vi.fn(function () {
|
|
81
|
+
return failing;
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
await expect(_loadFontFaceFromUrls('Family', [{ url: 'font.woff2' }])).rejects.toThrow('bad font');
|
|
86
|
+
expect(addMock).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('_loadFontFacesFromName', () => {
|
|
91
|
+
it('uses the escaped shorthand and returns the document faces', async () => {
|
|
92
|
+
const faces = [{ family: 'Family' } as unknown as FontFace];
|
|
93
|
+
loadMock.mockResolvedValue(faces);
|
|
94
|
+
|
|
95
|
+
await expect(_loadFontFacesFromName("Family's Name")).resolves.toBe(faces);
|
|
96
|
+
expect(loadMock).toHaveBeenCalledWith("1em 'Family\\'s Name'");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -6,86 +6,231 @@ import {
|
|
|
6
6
|
loadFontResourceFromUrls,
|
|
7
7
|
} from './fontResourceFrom';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
interface FontFaceConstruction {
|
|
10
|
+
family: string;
|
|
11
|
+
source: string | ArrayBuffer;
|
|
12
|
+
instance: MockFontFace;
|
|
13
|
+
}
|
|
10
14
|
|
|
15
|
+
let constructions: FontFaceConstruction[];
|
|
16
|
+
let addMock: ReturnType<typeof vi.fn>;
|
|
17
|
+
let loadMock: ReturnType<typeof vi.fn>;
|
|
18
|
+
|
|
19
|
+
class MockFontFace {
|
|
20
|
+
load = vi.fn().mockResolvedValue(undefined);
|
|
21
|
+
constructor(family: string, source: string | ArrayBuffer) {
|
|
22
|
+
constructions.push({ family, source, instance: this });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Mirrors the harness in fontFrom.test.ts, including its restore discipline: the document.fonts
|
|
27
|
+
// descriptor is saved and put back per test rather than left overwritten, so under a shared
|
|
28
|
+
// (isolate:false) worker the patch cannot leak into a sibling file.
|
|
29
|
+
let originalFonts: PropertyDescriptor | undefined;
|
|
11
30
|
beforeEach(() => {
|
|
12
|
-
|
|
13
|
-
vi.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}),
|
|
18
|
-
);
|
|
31
|
+
constructions = [];
|
|
32
|
+
addMock = vi.fn();
|
|
33
|
+
loadMock = vi.fn().mockResolvedValue([]);
|
|
34
|
+
vi.stubGlobal('FontFace', MockFontFace);
|
|
35
|
+
originalFonts = Object.getOwnPropertyDescriptor(document, 'fonts');
|
|
19
36
|
Object.defineProperty(document, 'fonts', {
|
|
20
|
-
value: { add:
|
|
37
|
+
value: { add: addMock, load: loadMock },
|
|
21
38
|
configurable: true,
|
|
22
|
-
writable: true,
|
|
23
39
|
});
|
|
24
|
-
vi.spyOn(document.fonts, 'add').mockImplementation(() => document.fonts);
|
|
25
|
-
vi.spyOn(document.fonts, 'load').mockResolvedValue([mockFace as unknown as FontFace]);
|
|
26
40
|
});
|
|
27
41
|
|
|
28
42
|
afterEach(() => {
|
|
43
|
+
if (originalFonts) {
|
|
44
|
+
Object.defineProperty(document, 'fonts', originalFonts);
|
|
45
|
+
} else {
|
|
46
|
+
delete (document as unknown as { fonts?: unknown }).fonts;
|
|
47
|
+
}
|
|
29
48
|
vi.unstubAllGlobals();
|
|
30
49
|
vi.restoreAllMocks();
|
|
31
50
|
});
|
|
32
51
|
|
|
33
52
|
describe('loadFontResourceFromBytes', () => {
|
|
34
|
-
it('
|
|
53
|
+
it('constructs the face under the resource family, loads it, and registers it', async () => {
|
|
35
54
|
const resource = createFontResource('TestFont');
|
|
36
55
|
const result = await loadFontResourceFromBytes(resource, new Uint8Array(8));
|
|
37
56
|
expect(result).toBe(resource);
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
57
|
+
expect(constructions).toHaveLength(1);
|
|
58
|
+
expect(constructions[0].family).toBe('TestFont');
|
|
59
|
+
expect(constructions[0].instance.load).toHaveBeenCalledOnce();
|
|
60
|
+
expect(addMock).toHaveBeenCalledWith(constructions[0].instance);
|
|
61
|
+
expect(resource.face).toBe(constructions[0].instance);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('slices only the view bytes out of a larger backing buffer', async () => {
|
|
65
|
+
// This integration assertion pins that the resource wrapper forwards its view to the canonical
|
|
66
|
+
// byte loader rather than widening it back to the whole buffer.
|
|
67
|
+
const backing = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
68
|
+
await loadFontResourceFromBytes(createFontResource('TestFont'), backing.subarray(2, 6));
|
|
69
|
+
expect(new Uint8Array(constructions[0].source as ArrayBuffer)).toEqual(new Uint8Array([3, 4, 5, 6]));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('propagates a load failure and does not register the face', async () => {
|
|
73
|
+
const resource = createFontResource('TestFont');
|
|
74
|
+
const failing = new MockFontFace('TestFont', new ArrayBuffer(0));
|
|
75
|
+
failing.load.mockRejectedValue(new Error('bad font'));
|
|
76
|
+
vi.stubGlobal(
|
|
77
|
+
'FontFace',
|
|
78
|
+
vi.fn(function () {
|
|
79
|
+
return failing;
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
await expect(loadFontResourceFromBytes(resource, new Uint8Array(4))).rejects.toThrow('bad font');
|
|
83
|
+
expect(addMock).not.toHaveBeenCalled();
|
|
84
|
+
expect(resource.face).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('leaves a previously loaded face attached when a reload fails', async () => {
|
|
88
|
+
// The contract this pins: `face` is assigned only after the load resolves, so a failed reload
|
|
89
|
+
// rejects and leaves the resource on the font it already had rather than blanking it. A caller
|
|
90
|
+
// that ignores the rejection keeps rendering with a working font instead of losing one.
|
|
91
|
+
const resource = createFontResource('TestFont');
|
|
92
|
+
await loadFontResourceFromBytes(resource, new Uint8Array(4));
|
|
93
|
+
const loaded = resource.face;
|
|
94
|
+
expect(loaded).not.toBeNull();
|
|
95
|
+
|
|
96
|
+
const failing = new MockFontFace('TestFont', new ArrayBuffer(0));
|
|
97
|
+
failing.load.mockRejectedValue(new Error('boom'));
|
|
98
|
+
vi.stubGlobal(
|
|
99
|
+
'FontFace',
|
|
100
|
+
vi.fn(function () {
|
|
101
|
+
return failing;
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
await expect(loadFontResourceFromBytes(resource, new Uint8Array(4))).rejects.toThrow('boom');
|
|
105
|
+
expect(resource.face).toBe(loaded);
|
|
106
|
+
expect(addMock).toHaveBeenCalledTimes(1);
|
|
41
107
|
});
|
|
42
108
|
});
|
|
43
109
|
|
|
44
110
|
describe('loadFontResourceFromName', () => {
|
|
45
|
-
it('
|
|
111
|
+
it('queries the family via the escaped font shorthand', async () => {
|
|
112
|
+
await loadFontResourceFromName(createFontResource('MyFont'));
|
|
113
|
+
expect(loadMock).toHaveBeenCalledWith("1em 'MyFont'");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('escapes a single quote in the family name so the shorthand stays valid', async () => {
|
|
117
|
+
await loadFontResourceFromName(createFontResource("Josh's Font"));
|
|
118
|
+
expect(loadMock).toHaveBeenCalledWith("1em 'Josh\\'s Font'");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('attaches the first face when the document returns several', async () => {
|
|
122
|
+
const first = { family: 'first' } as unknown as FontFace;
|
|
123
|
+
const second = { family: 'second' } as unknown as FontFace;
|
|
124
|
+
loadMock.mockResolvedValue([first, second]);
|
|
46
125
|
const resource = createFontResource('TestFont');
|
|
47
126
|
const result = await loadFontResourceFromName(resource);
|
|
48
127
|
expect(result).toBe(resource);
|
|
49
|
-
expect(resource.face).toBe(
|
|
128
|
+
expect(resource.face).toBe(first);
|
|
50
129
|
});
|
|
51
130
|
|
|
52
131
|
it('leaves face null when no registered faces are found', async () => {
|
|
53
|
-
vi.spyOn(document.fonts, 'load').mockResolvedValue([]);
|
|
54
132
|
const resource = createFontResource('UnknownFont');
|
|
55
133
|
await loadFontResourceFromName(resource);
|
|
56
134
|
expect(resource.face).toBeNull();
|
|
57
135
|
});
|
|
136
|
+
|
|
137
|
+
it('registers nothing — a name lookup finds an existing face rather than adding one', async () => {
|
|
138
|
+
loadMock.mockResolvedValue([{ family: 'x' } as unknown as FontFace]);
|
|
139
|
+
await loadFontResourceFromName(createFontResource('TestFont'));
|
|
140
|
+
expect(addMock).not.toHaveBeenCalled();
|
|
141
|
+
expect(constructions).toHaveLength(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('propagates a lookup failure', async () => {
|
|
145
|
+
loadMock.mockRejectedValue(new Error('no such font'));
|
|
146
|
+
await expect(loadFontResourceFromName(createFontResource('MyFont'))).rejects.toThrow('no such font');
|
|
147
|
+
});
|
|
58
148
|
});
|
|
59
149
|
|
|
60
150
|
describe('loadFontResourceFromUrl', () => {
|
|
61
|
-
it('
|
|
62
|
-
const resource = createFontResource('
|
|
151
|
+
it('builds a url() source under the resource family and registers the face', async () => {
|
|
152
|
+
const resource = createFontResource('MyFont');
|
|
63
153
|
const result = await loadFontResourceFromUrl(resource, 'https://example.com/font.woff2');
|
|
64
154
|
expect(result).toBe(resource);
|
|
65
|
-
expect(
|
|
66
|
-
expect(
|
|
155
|
+
expect(constructions[0].family).toBe('MyFont');
|
|
156
|
+
expect(constructions[0].source).toBe('url(https://example.com/font.woff2)');
|
|
157
|
+
expect(addMock).toHaveBeenCalledWith(constructions[0].instance);
|
|
158
|
+
expect(resource.face).toBe(constructions[0].instance);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('adds no format() hint — a single url carries none, unlike the multi-source form', async () => {
|
|
162
|
+
await loadFontResourceFromUrl(createFontResource('MyFont'), 'font.woff2');
|
|
163
|
+
expect(constructions[0].source).toBe('url(font.woff2)');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('propagates a load failure and does not register the face', async () => {
|
|
167
|
+
const resource = createFontResource('MyFont');
|
|
168
|
+
const failing = new MockFontFace('MyFont', '');
|
|
169
|
+
failing.load.mockRejectedValue(new Error('bad url'));
|
|
170
|
+
vi.stubGlobal(
|
|
171
|
+
'FontFace',
|
|
172
|
+
vi.fn(function () {
|
|
173
|
+
return failing;
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
await expect(loadFontResourceFromUrl(resource, 'font.woff2')).rejects.toThrow('bad url');
|
|
177
|
+
expect(addMock).not.toHaveBeenCalled();
|
|
178
|
+
expect(resource.face).toBeNull();
|
|
67
179
|
});
|
|
68
180
|
});
|
|
69
181
|
|
|
70
182
|
describe('loadFontResourceFromUrls', () => {
|
|
71
|
-
it('
|
|
72
|
-
|
|
183
|
+
it('composes a comma-joined src with explicit and inferred format() hints', async () => {
|
|
184
|
+
// The block this replaces was titled "builds a multi-source src string" and asserted nothing
|
|
185
|
+
// about the string — only that a face was attached. The composed src is the whole point of this
|
|
186
|
+
// function, and it was the one thing not checked.
|
|
187
|
+
const resource = createFontResource('MyFont');
|
|
73
188
|
const result = await loadFontResourceFromUrls(resource, [
|
|
74
189
|
{ url: 'font.woff2', format: 'woff2' },
|
|
75
190
|
{ url: 'font.ttf' },
|
|
76
191
|
]);
|
|
77
192
|
expect(result).toBe(resource);
|
|
78
|
-
expect(
|
|
79
|
-
expect(
|
|
193
|
+
expect(constructions[0].family).toBe('MyFont');
|
|
194
|
+
expect(constructions[0].source).toBe("url(font.woff2) format('woff2'), url(font.ttf) format('truetype')");
|
|
195
|
+
expect(addMock).toHaveBeenCalledWith(constructions[0].instance);
|
|
80
196
|
});
|
|
81
197
|
|
|
82
|
-
it('infers format from file extension when
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await loadFontResourceFromUrls(
|
|
89
|
-
expect(
|
|
198
|
+
it('infers the format from the file extension when none is given', async () => {
|
|
199
|
+
await loadFontResourceFromUrls(createFontResource('MyFont'), [{ url: 'font.otf' }]);
|
|
200
|
+
expect(constructions[0].source).toBe("url(font.otf) format('opentype')");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('omits the format() hint when it cannot be inferred and none is given', async () => {
|
|
204
|
+
await loadFontResourceFromUrls(createFontResource('MyFont'), [{ url: 'font.bin' }]);
|
|
205
|
+
expect(constructions[0].source).toBe('url(font.bin)');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('mixes inferred, explicit, and absent hints across one src list', async () => {
|
|
209
|
+
await loadFontResourceFromUrls(createFontResource('MyFont'), [
|
|
210
|
+
{ url: 'a.woff2', format: 'woff2' },
|
|
211
|
+
{ url: 'b.ttf' },
|
|
212
|
+
{ url: 'c.bin' },
|
|
213
|
+
]);
|
|
214
|
+
expect(constructions[0].source).toBe("url(a.woff2) format('woff2'), url(b.ttf) format('truetype'), url(c.bin)");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('builds an empty src for an empty source list rather than throwing', async () => {
|
|
218
|
+
await loadFontResourceFromUrls(createFontResource('MyFont'), []);
|
|
219
|
+
expect(constructions[0].source).toBe('');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('propagates a load failure and does not register the face', async () => {
|
|
223
|
+
const resource = createFontResource('MyFont');
|
|
224
|
+
const failing = new MockFontFace('MyFont', '');
|
|
225
|
+
failing.load.mockRejectedValue(new Error('bad url'));
|
|
226
|
+
vi.stubGlobal(
|
|
227
|
+
'FontFace',
|
|
228
|
+
vi.fn(function () {
|
|
229
|
+
return failing;
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
await expect(loadFontResourceFromUrls(resource, [{ url: 'font.woff2' }])).rejects.toThrow('bad url');
|
|
233
|
+
expect(addMock).not.toHaveBeenCalled();
|
|
234
|
+
expect(resource.face).toBeNull();
|
|
90
235
|
});
|
|
91
236
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { appendPathClose, appendPathLineTo, appendPathMoveTo } from '@flighthq/path/contract';
|
|
2
|
+
import type { GlyphOutlineSource, Path } from '@flighthq/types/contract';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createGlyphRasterizerBackendFromGlyphOutlineSource } from './glyphOutlineSource';
|
|
6
|
+
|
|
7
|
+
describe('createGlyphRasterizerBackendFromGlyphOutlineSource', () => {
|
|
8
|
+
it('maps codepoints to glyph indices and rasterizes design-unit outlines at the requested em size', () => {
|
|
9
|
+
const backend = createGlyphRasterizerBackendFromGlyphOutlineSource(createTestGlyphOutlineSource());
|
|
10
|
+
const raster = backend.rasterize(0x41, { fontFamily: 'embedded', fontSize: 20 });
|
|
11
|
+
|
|
12
|
+
expect(raster).not.toBeNull();
|
|
13
|
+
expect(raster!.advance).toBe(12);
|
|
14
|
+
expect(raster!.bearingX).toBe(-3);
|
|
15
|
+
expect(raster!.bearingY).toBe(17);
|
|
16
|
+
expect(raster!.width).toBe(14);
|
|
17
|
+
expect(raster!.height).toBe(22);
|
|
18
|
+
expect(raster!.pixels.some((value, index) => index % 4 === 3 && value === 0xff)).toBe(true);
|
|
19
|
+
expect(raster!.pixels[3]).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns scaled outline metrics to glyphatlas', () => {
|
|
23
|
+
const backend = createGlyphRasterizerBackendFromGlyphOutlineSource(createTestGlyphOutlineSource());
|
|
24
|
+
|
|
25
|
+
expect(backend.measureMetrics?.({ fontFamily: 'embedded', fontSize: 20 })).toEqual({
|
|
26
|
+
ascent: 16,
|
|
27
|
+
descent: 4,
|
|
28
|
+
lineGap: 2,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns a zero-area raster with an advance for an empty glyph', () => {
|
|
33
|
+
const backend = createGlyphRasterizerBackendFromGlyphOutlineSource(createTestGlyphOutlineSource());
|
|
34
|
+
|
|
35
|
+
expect(backend.rasterize(0x20, { fontFamily: 'embedded', fontSize: 20 })).toEqual({
|
|
36
|
+
advance: 5,
|
|
37
|
+
bearingX: 0,
|
|
38
|
+
bearingY: 0,
|
|
39
|
+
height: 0,
|
|
40
|
+
pixels: new Uint8ClampedArray(),
|
|
41
|
+
width: 0,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('sentinels for an unmapped codepoint or invalid em scale', () => {
|
|
46
|
+
const backend = createGlyphRasterizerBackendFromGlyphOutlineSource(createTestGlyphOutlineSource());
|
|
47
|
+
|
|
48
|
+
expect(backend.rasterize(0x42, { fontFamily: 'embedded', fontSize: 20 })).toBeNull();
|
|
49
|
+
expect(backend.rasterize(0x41, { fontFamily: 'embedded', fontSize: 0 })).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function createTestGlyphOutlineSource(): GlyphOutlineSource {
|
|
54
|
+
return {
|
|
55
|
+
getGlyphOutline(out: Path, glyphIndex: number): boolean {
|
|
56
|
+
out.commands.length = 0;
|
|
57
|
+
out.data.length = 0;
|
|
58
|
+
out.winding = 'nonZero';
|
|
59
|
+
if (glyphIndex === 1) return true;
|
|
60
|
+
if (glyphIndex !== 0) return false;
|
|
61
|
+
appendPathMoveTo(out, -100, -800);
|
|
62
|
+
appendPathLineTo(out, 500, -800);
|
|
63
|
+
appendPathLineTo(out, 500, 200);
|
|
64
|
+
appendPathLineTo(out, -100, 200);
|
|
65
|
+
appendPathClose(out);
|
|
66
|
+
return true;
|
|
67
|
+
},
|
|
68
|
+
getGlyphOutlineAdvance(glyphIndex: number): number {
|
|
69
|
+
return glyphIndex === 1 ? 250 : 600;
|
|
70
|
+
},
|
|
71
|
+
getGlyphOutlineIndexForCodePoint(codePoint: number): number {
|
|
72
|
+
if (codePoint === 0x41) return 0;
|
|
73
|
+
if (codePoint === 0x20) return 1;
|
|
74
|
+
return -1;
|
|
75
|
+
},
|
|
76
|
+
getGlyphOutlineMetrics() {
|
|
77
|
+
return { ascent: 800, descent: 200, lineGap: 100, unitsPerEm: 1000 };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|