@nowline/export-core 0.5.1 → 0.6.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.
@@ -0,0 +1,10 @@
1
+ import type { ResolvedFontPair } from '../types.js';
2
+ /**
3
+ * Return the canonical bundled DejaVu font pair decoded with browser-compatible
4
+ * APIs (`atob`). Result is cached after the first call so the ~1 MB decode only
5
+ * runs once per page. Safe to call before wasm initialization.
6
+ */
7
+ export declare function loadBundledFontsForBrowser(): ResolvedFontPair;
8
+ /** Test seam: drop the cached pair so isolated tests start fresh. */
9
+ export declare function _clearBrowserFontsCache(): void;
10
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/fonts/browser.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAWpD;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,gBAAgB,CAY7D;AAED,qEAAqE;AACrE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C"}
@@ -0,0 +1,43 @@
1
+ // Browser-compatible bundled font loader.
2
+ //
3
+ // Same bundled DejaVu bytes as loadBundledSans/loadBundledMono (Node), but
4
+ // decoded with `atob()` instead of Node's `Buffer.from()` so the module
5
+ // bundles cleanly for the browser (the Free/Pro web apps, Playwright legs).
6
+ //
7
+ // Usage: `loadBundledFontsForBrowser()` returns a cached `ResolvedFontPair`
8
+ // synchronously after the first call. Call it once at module init or lazy-once
9
+ // inside your `loadWasm` / font-loading step.
10
+ //
11
+ // Node callers: use `loadBundledSans()` / `loadBundledMono()` from bundled.ts.
12
+ import { MONO_BASE64, SANS_BASE64 } from '../generated/bundled-fonts.js';
13
+ function b64ToBytes(b64) {
14
+ const binary = atob(b64);
15
+ const out = new Uint8Array(binary.length);
16
+ for (let i = 0; i < binary.length; i++)
17
+ out[i] = binary.charCodeAt(i);
18
+ return out;
19
+ }
20
+ let cachedFonts;
21
+ /**
22
+ * Return the canonical bundled DejaVu font pair decoded with browser-compatible
23
+ * APIs (`atob`). Result is cached after the first call so the ~1 MB decode only
24
+ * runs once per page. Safe to call before wasm initialization.
25
+ */
26
+ export function loadBundledFontsForBrowser() {
27
+ if (!cachedFonts) {
28
+ cachedFonts = {
29
+ sans: { name: 'DejaVu Sans', bytes: b64ToBytes(SANS_BASE64), source: 'bundled' },
30
+ mono: {
31
+ name: 'DejaVu Sans Mono',
32
+ bytes: b64ToBytes(MONO_BASE64),
33
+ source: 'bundled',
34
+ },
35
+ };
36
+ }
37
+ return cachedFonts;
38
+ }
39
+ /** Test seam: drop the cached pair so isolated tests start fresh. */
40
+ export function _clearBrowserFontsCache() {
41
+ cachedFonts = undefined;
42
+ }
43
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/fonts/browser.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,4EAA4E;AAC5E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,8CAA8C;AAC9C,EAAE;AACF,+EAA+E;AAE/E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAGzE,SAAS,UAAU,CAAC,GAAW;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,GAAG,CAAC;AACf,CAAC;AAED,IAAI,WAAyC,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,0BAA0B;IACtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACf,WAAW,GAAG;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE;YAChF,IAAI,EAAE;gBACF,IAAI,EAAE,kBAAkB;gBACxB,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC;gBAC9B,MAAM,EAAE,SAAS;aACpB;SACJ,CAAC;IACN,CAAC;IACD,OAAO,WAAW,CAAC;AACvB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,uBAAuB;IACnC,WAAW,GAAG,SAAS,CAAC;AAC5B,CAAC"}
@@ -1,5 +1,13 @@
1
1
  export declare const BUNDLED_SANS_PATH: string;
2
2
  export declare const BUNDLED_MONO_PATH: string;
3
+ /**
4
+ * Family names stamped on the bundled DejaVu faces. The resolver's
5
+ * `ResolvedFont.name`, the renderer's pinned `font-family`, the resvg family
6
+ * hints, and the live-preview `@font-face` must all use these exact strings
7
+ * so preview and raster export name the same face (the WYSIWYG contract).
8
+ */
9
+ export declare const BUNDLED_SANS_FAMILY = "DejaVu Sans";
10
+ export declare const BUNDLED_MONO_FAMILY = "DejaVu Sans Mono";
3
11
  export declare function loadBundledSans(): Promise<Uint8Array>;
4
12
  export declare function loadBundledMono(): Promise<Uint8Array>;
5
13
  /** Test seam: drop cached bytes so platform-mocked tests start fresh. */
@@ -1 +1 @@
1
- {"version":3,"file":"bundled.d.ts","sourceRoot":"","sources":["../../src/fonts/bundled.ts"],"names":[],"mappings":"AA+BA,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AACzE,eAAO,MAAM,iBAAiB,QAA8C,CAAC;AAU7E,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAI3D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAI3D;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
1
+ {"version":3,"file":"bundled.d.ts","sourceRoot":"","sources":["../../src/fonts/bundled.ts"],"names":[],"mappings":"AA8CA,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AACzE,eAAO,MAAM,iBAAiB,QAA8C,CAAC;AAE7E;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAUtD,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAI3D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAI3D;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
@@ -16,16 +16,40 @@
16
16
  import * as path from 'node:path';
17
17
  import { fileURLToPath } from 'node:url';
18
18
  import { MONO_BASE64, SANS_BASE64 } from '../generated/bundled-fonts.js';
19
- const HERE = path.dirname(fileURLToPath(import.meta.url));
20
- // dist/fonts/bundled.js ../../assets/fonts/
21
- // src/fonts/bundled.ts ../../assets/fonts/
22
- const ASSETS_DIR = path.resolve(HERE, '..', '..', 'assets', 'fonts');
19
+ // Resolve the on-disk assets/fonts directory from this module's location.
20
+ // `import.meta.url` is rewritten to `undefined` when this module is bundled
21
+ // into a CommonJS context — esbuild collapses `import.meta` to `{}` for the VS
22
+ // Code extension's `dist/extension.cjs`. The two exported paths below are
23
+ // informational only (the runtime decodes the embedded base64 above, never
24
+ // reads these files), so degrade to a bare relative directory instead of
25
+ // throwing `ERR_INVALID_ARG_TYPE` at module load — which would crash the
26
+ // extension before it can activate. See packages/vscode-extension.
27
+ function bundledFontsDir() {
28
+ try {
29
+ // dist/fonts/bundled.js → ../../assets/fonts/
30
+ // src/fonts/bundled.ts → ../../assets/fonts/
31
+ const here = path.dirname(fileURLToPath(import.meta.url));
32
+ return path.resolve(here, '..', '..', 'assets', 'fonts');
33
+ }
34
+ catch {
35
+ return path.join('assets', 'fonts');
36
+ }
37
+ }
38
+ const ASSETS_DIR = bundledFontsDir();
23
39
  // Informational: where the source-of-truth TTFs live on disk for users
24
40
  // running under Node. Not the runtime load source — `loadBundledSans` and
25
41
  // `loadBundledMono` decode from the embedded base64 instead. Inside a bun
26
42
  // --compile binary these paths are phantom (the assets are not on disk).
27
43
  export const BUNDLED_SANS_PATH = path.join(ASSETS_DIR, 'DejaVuSans.ttf');
28
44
  export const BUNDLED_MONO_PATH = path.join(ASSETS_DIR, 'DejaVuSansMono.ttf');
45
+ /**
46
+ * Family names stamped on the bundled DejaVu faces. The resolver's
47
+ * `ResolvedFont.name`, the renderer's pinned `font-family`, the resvg family
48
+ * hints, and the live-preview `@font-face` must all use these exact strings
49
+ * so preview and raster export name the same face (the WYSIWYG contract).
50
+ */
51
+ export const BUNDLED_SANS_FAMILY = 'DejaVu Sans';
52
+ export const BUNDLED_MONO_FAMILY = 'DejaVu Sans Mono';
29
53
  let cachedSans;
30
54
  let cachedMono;
31
55
  function decodeBase64(b64) {
@@ -1 +1 @@
1
- {"version":3,"file":"bundled.js","sourceRoot":"","sources":["../../src/fonts/bundled.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,6EAA6E;AAC7E,SAAS;AACT,EAAE;AACF,2DAA2D;AAC3D,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,qEAAqE;AACrE,2BAA2B;AAE3B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,8CAA8C;AAC9C,+CAA+C;AAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAErE,uEAAuE;AACvE,0EAA0E;AAC1E,0EAA0E;AAC1E,yEAAyE;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;AAE7E,IAAI,UAAkC,CAAC;AACvC,IAAI,UAAkC,CAAC;AAEvC,SAAS,YAAY,CAAC,GAAW;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACvC,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,UAAU,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACvC,OAAO,UAAU,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,UAAU,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACvC,OAAO,UAAU,CAAC;AACtB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,iBAAiB;IAC7B,UAAU,GAAG,SAAS,CAAC;IACvB,UAAU,GAAG,SAAS,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"bundled.js","sourceRoot":"","sources":["../../src/fonts/bundled.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,6EAA6E;AAC7E,SAAS;AACT,EAAE;AACF,2DAA2D;AAC3D,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,qEAAqE;AACrE,2BAA2B;AAE3B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEzE,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,0EAA0E;AAC1E,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,mEAAmE;AACnE,SAAS,eAAe;IACpB,IAAI,CAAC;QACD,8CAA8C;QAC9C,+CAA+C;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC;AAErC,uEAAuE;AACvE,0EAA0E;AAC1E,0EAA0E;AAC1E,yEAAyE;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAEtD,IAAI,UAAkC,CAAC;AACvC,IAAI,UAAkC,CAAC;AAEvC,SAAS,YAAY,CAAC,GAAW;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACvC,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,UAAU,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACvC,OAAO,UAAU,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,UAAU,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACvC,OAAO,UAAU,CAAC;AACtB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,iBAAiB;IAC7B,UAAU,GAAG,SAAS,CAAC;IACvB,UAAU,GAAG,SAAS,CAAC;AAC3B,CAAC"}
@@ -1,3 +1,4 @@
1
+ export { _clearBrowserFontsCache, loadBundledFontsForBrowser } from './browser.js';
1
2
  export { BUNDLED_MONO_PATH, BUNDLED_SANS_PATH, clearBundledCache, loadBundledMono, loadBundledSans, } from './bundled.js';
2
3
  export { ALIASES, aliasCandidate, type FontCandidate, isAlias, type PlatformProbe, probeListFor, } from './probe-list.js';
3
4
  export type { ResolveOptions, ResolveResult } from './resolve.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fonts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACH,OAAO,EACP,cAAc,EACd,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,aAAa,EAClB,YAAY,GACf,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fonts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EACH,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACH,OAAO,EACP,cAAc,EACd,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,aAAa,EAClB,YAAY,GACf,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
@@ -1,3 +1,4 @@
1
+ export { _clearBrowserFontsCache, loadBundledFontsForBrowser } from './browser.js';
1
2
  export { BUNDLED_MONO_PATH, BUNDLED_SANS_PATH, clearBundledCache, loadBundledMono, loadBundledSans, } from './bundled.js';
2
3
  export { ALIASES, aliasCandidate, isAlias, probeListFor, } from './probe-list.js';
3
4
  export { FontResolveError, resolveFonts } from './resolve.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fonts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACH,OAAO,EACP,cAAc,EAEd,OAAO,EAEP,YAAY,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fonts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EACH,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACH,OAAO,EACP,cAAc,EAEd,OAAO,EAEP,YAAY,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
@@ -5,9 +5,18 @@ export interface ResolveOptions {
5
5
  /** Path or alias for the mono role. */
6
6
  fontMono?: string;
7
7
  /**
8
- * Skip steps 4–5; go straight to step 5 (bundled DejaVu pair). Implied by
8
+ * Opt in to the platform font probe (step 4). Off by default: the
9
+ * resolver is bundled-first, so the default (no flag/env) is the bundled
10
+ * DejaVu pair on every OS. With this set the probe runs and the first
11
+ * STATIC system font wins (variable fonts are skipped), bundled if none.
12
+ */
13
+ useSystemFonts?: boolean;
14
+ /**
15
+ * Skip the probe; go straight to the bundled DejaVu pair. Implied by
9
16
  * `NOWLINE_HEADLESS=1` and by `CI=true` without a TTY (unless
10
- * `disableAutoHeadless` is set).
17
+ * `disableAutoHeadless` is set). Largely redundant now that bundled is the
18
+ * default, but retained so an explicit `--headless` still forces bundled
19
+ * even alongside `useSystemFonts`.
11
20
  */
12
21
  headless?: boolean;
13
22
  /** Disable the CI-no-TTY auto-headless heuristic. Defaults to false. */
@@ -22,11 +31,20 @@ export interface ResolveResult {
22
31
  sans: ResolvedFont;
23
32
  mono: ResolvedFont;
24
33
  /**
25
- * True when EITHER role landed at step 5 without explicit `--headless`.
26
- * Callers (CLI) emit a `--strict` warning on this.
34
+ * True when a role landed on bundled DejaVu *after an opted-in probe found
35
+ * nothing usable* (`useSystemFonts` set, no static system font present).
36
+ * The bundled-first default is the intended path, not a fallback, so it
37
+ * does NOT set this. Callers (CLI) emit a `--strict` warning on this.
27
38
  */
28
39
  sansFellBackToBundled: boolean;
29
40
  monoFellBackToBundled: boolean;
41
+ /**
42
+ * True when an explicitly requested font (flag / env / alias) was a
43
+ * variable font and was replaced by the bundled DejaVu (raster cannot
44
+ * render a VF). Callers warn; the CLI errors under `--strict`.
45
+ */
46
+ sansVariableFontSubstituted: boolean;
47
+ monoVariableFontSubstituted: boolean;
30
48
  }
31
49
  export declare function resolveFonts(options?: ResolveOptions): Promise<ResolveResult>;
32
50
  export declare class FontResolveError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/fonts/resolve.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAwB,YAAY,EAAE,MAAM,aAAa,CAAC;AAKtE,MAAM,WAAW,cAAc;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB;;;OAGG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;CAClC;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAgCvF;AAyID,qBAAa,gBAAiB,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI9B"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/fonts/resolve.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAwB,YAAY,EAAE,MAAM,aAAa,CAAC;AAUtE,MAAM,WAAW,cAAc;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB;;;;;OAKG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,2BAA2B,EAAE,OAAO,CAAC;IACrC,2BAA2B,EAAE,OAAO,CAAC;CACxC;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAqCvF;AA6KD,qBAAa,gBAAiB,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI9B"}
@@ -1,18 +1,32 @@
1
- // 5-step font resolver shared by PDF and PNG.
1
+ // Bundled-first font resolver shared by PDF and PNG.
2
2
  //
3
- // Spec: specs/handoffs/m2c.md § 10 "Font strategy — system first, one
4
- // bundled headless fallback".
3
+ // Spec: specs/handoffs/m2c.md § 10 "Font strategy — bundled first, system
4
+ // fonts opt-in".
5
+ //
6
+ // The resolver defaults to the bundled static DejaVu pair on every OS so that
7
+ // preview and raster export look identical everywhere and we never hand
8
+ // `@resvg/resvg-wasm` a variable font (which it cannot rasterize — see
9
+ // `sfns.ts`). System fonts are an explicit opt-in.
5
10
  //
6
11
  // Resolution order (run independently for sans / mono — first hit wins):
7
12
  // 1. Explicit flag — `--font-sans <path|alias>` / `--font-mono <path|alias>`
8
13
  // 2. Environment — NOWLINE_FONT_SANS / NOWLINE_FONT_MONO
9
- // 3. Headless override — `--headless`, NOWLINE_HEADLESS=1, or auto in CI
10
- // without a TTY
11
- // 4. Platform probe — first existing entry from `probe-list.ts`
12
- // 5. Bundled fallback DejaVuSans.ttf / DejaVuSansMono.ttf
14
+ // 3. Headless / default — `--headless`, NOWLINE_HEADLESS=1, auto in CI
15
+ // without a TTY, OR the plain default (no
16
+ // `useSystemFonts`): bundled DejaVu pair, no probe.
17
+ // 4. Platform probe opt-in via `useSystemFonts`; first existing STATIC
18
+ // entry from `probe-list.ts` (variable fonts skipped).
19
+ // 5. Bundled fallback — DejaVuSans.ttf / DejaVuSansMono.ttf (after an
20
+ // opted-in probe found nothing usable).
21
+ //
22
+ // Variable-font guard: an explicitly requested font (flag/env/alias) that
23
+ // turns out to be a variable font is substituted with the bundled DejaVu and
24
+ // flagged (`*VariableFontSubstituted`), because raster export cannot render a
25
+ // VF and there is no runtime instancer. On the opt-in probe path, variable
26
+ // candidates are skipped in favor of the next static system font.
13
27
  import { existsSync as defaultExistsSync, promises as fs } from 'node:fs';
14
28
  import * as path from 'node:path';
15
- import { loadBundledMono, loadBundledSans } from './bundled.js';
29
+ import { BUNDLED_MONO_FAMILY, BUNDLED_SANS_FAMILY, loadBundledMono, loadBundledSans, } from './bundled.js';
16
30
  import { aliasCandidate, isAlias, probeListFor } from './probe-list.js';
17
31
  import { isVariableFontBytes } from './sfns.js';
18
32
  export async function resolveFonts(options = {}) {
@@ -22,11 +36,13 @@ export async function resolveFonts(options = {}) {
22
36
  const readFileBytes = options.readFileBytes ?? defaultReadFileBytes;
23
37
  const probe = probeListFor(platform, env);
24
38
  const headlessRequested = isHeadlessRequested(options, env);
39
+ const useSystemFonts = options.useSystemFonts ?? false;
25
40
  const sans = await resolveRole({
26
41
  role: 'sans',
27
42
  flag: options.fontSans,
28
43
  envValue: env.NOWLINE_FONT_SANS,
29
44
  headless: headlessRequested,
45
+ useSystemFonts,
30
46
  probe,
31
47
  fileExists,
32
48
  readFileBytes,
@@ -36,15 +52,18 @@ export async function resolveFonts(options = {}) {
36
52
  flag: options.fontMono,
37
53
  envValue: env.NOWLINE_FONT_MONO,
38
54
  headless: headlessRequested,
55
+ useSystemFonts,
39
56
  probe,
40
57
  fileExists,
41
58
  readFileBytes,
42
59
  });
43
60
  return {
44
- sans,
45
- mono,
46
- sansFellBackToBundled: sans.source === 'bundled' && !headlessRequested,
47
- monoFellBackToBundled: mono.source === 'bundled' && !headlessRequested,
61
+ sans: sans.font,
62
+ mono: mono.font,
63
+ sansFellBackToBundled: sans.fellBack,
64
+ monoFellBackToBundled: mono.fellBack,
65
+ sansVariableFontSubstituted: sans.variableSubstituted,
66
+ monoVariableFontSubstituted: mono.variableSubstituted,
48
67
  };
49
68
  }
50
69
  function isHeadlessRequested(options, env) {
@@ -61,30 +80,58 @@ function isHeadlessRequested(options, env) {
61
80
  async function resolveRole(args) {
62
81
  // Step 1 — flag (path or alias)
63
82
  if (args.flag) {
64
- return loadFlag(args.flag, args.role, 'flag', args);
83
+ return guardExplicit(await loadFlag(args.flag, args.role, 'flag', args), args);
65
84
  }
66
85
  // Step 2 — environment
67
86
  if (args.envValue) {
68
- return loadFlag(args.envValue, args.role, 'env', args);
87
+ return guardExplicit(await loadFlag(args.envValue, args.role, 'env', args), args);
69
88
  }
70
- // Step 3 — headless: skip probe, go to bundled
89
+ // Step 3 — explicit headless: skip probe, go to bundled.
71
90
  if (args.headless) {
72
- return loadBundled(args.role, 'headless');
91
+ const font = await loadBundled(args.role, 'headless');
92
+ return { font, fellBack: false, variableSubstituted: false };
73
93
  }
74
- // Step 4platform probe
94
+ // Step 3 (default) bundled-first: with no explicit system opt-in the
95
+ // default is the bundled DejaVu pair on every OS. This is the intended
96
+ // path (not a fallback), so it does not set `fellBack`.
97
+ if (!args.useSystemFonts) {
98
+ const font = await loadBundled(args.role, 'bundled');
99
+ return { font, fellBack: false, variableSubstituted: false };
100
+ }
101
+ // Step 4 — platform probe (opt-in). Skip variable candidates so we land on
102
+ // the next STATIC system font (resvg/raster cannot render a VF).
75
103
  for (const candidate of args.probe[args.role]) {
76
- if (args.fileExists(candidate.path)) {
77
- const bytes = await args.readFileBytes(candidate.path);
78
- return decorate(bytes, {
104
+ if (!args.fileExists(candidate.path))
105
+ continue;
106
+ const bytes = await args.readFileBytes(candidate.path);
107
+ if (isVariableFontBytes(bytes))
108
+ continue;
109
+ return {
110
+ font: decorate(bytes, {
79
111
  name: candidate.name,
80
112
  source: 'probe',
81
113
  path: candidate.path,
82
114
  face: candidate.face,
83
- });
84
- }
115
+ }),
116
+ fellBack: false,
117
+ variableSubstituted: false,
118
+ };
119
+ }
120
+ // Step 5 — bundled fallback after an opted-in probe found nothing usable.
121
+ const font = await loadBundled(args.role, 'bundled');
122
+ return { font, fellBack: true, variableSubstituted: false };
123
+ }
124
+ /**
125
+ * Guard an explicitly resolved font (flag / env / alias). A variable font
126
+ * cannot be rasterized and we have no runtime instancer, so substitute the
127
+ * bundled DejaVu and flag it; the caller decides whether to warn or error.
128
+ */
129
+ async function guardExplicit(font, args) {
130
+ if (font.isVariableFont) {
131
+ const bundled = await loadBundled(args.role, 'bundled');
132
+ return { font: bundled, fellBack: false, variableSubstituted: true };
85
133
  }
86
- // Step 5 bundled fallback
87
- return loadBundled(args.role, 'bundled');
134
+ return { font, fellBack: false, variableSubstituted: false };
88
135
  }
89
136
  async function loadFlag(raw, role, source, args) {
90
137
  if (path.isAbsolute(raw) ||
@@ -128,7 +175,7 @@ async function loadFlag(raw, role, source, args) {
128
175
  async function loadBundled(role, source) {
129
176
  const bytes = role === 'sans' ? await loadBundledSans() : await loadBundledMono();
130
177
  return decorate(bytes, {
131
- name: role === 'sans' ? 'DejaVu Sans' : 'DejaVu Sans Mono',
178
+ name: role === 'sans' ? BUNDLED_SANS_FAMILY : BUNDLED_MONO_FAMILY,
132
179
  source,
133
180
  });
134
181
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/fonts/resolve.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/fonts/resolve.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sfns.d.ts","sourceRoot":"","sources":["../../src/fonts/sfns.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAkB9D"}
1
+ {"version":3,"file":"sfns.d.ts","sourceRoot":"","sources":["../../src/fonts/sfns.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAkB9D"}
@@ -1,11 +1,13 @@
1
- // SF Pro variable-font handling.
1
+ // Variable-font detection.
2
2
  //
3
- // Spec: specs/handoffs/m2c.md § 10 "Variable-font handling (SFNS.ttf)".
3
+ // Spec: specs/handoffs/m2c.md § 10 "Font strategy — bundled first, system
4
+ // fonts opt-in".
4
5
  //
5
- // Detection only — actual VF instancing for PDF embedding lives in
6
- // @nowline/export-pdf, which depends on fontkit directly. The resolver
7
- // surfaces `isVariableFont: true` so consumers know they may need to
8
- // pre-instance.
6
+ // Detection only — the resolver uses this to guard explicit VF requests
7
+ // (substituting the bundled DejaVu and setting `variableSubstituted`) and to
8
+ // skip VF candidates on the opt-in system-font probe. There is no runtime
9
+ // instancer; any VF that reaches export would rasterize as blank in
10
+ // `@resvg/resvg-wasm`.
9
11
  const FVAR_TAG = 0x66766172; // 'fvar'
10
12
  /**
11
13
  * Returns true when the given TTF/OTF byte buffer carries an `fvar` table —
@@ -1 +1 @@
1
- {"version":3,"file":"sfns.js","sourceRoot":"","sources":["../../src/fonts/sfns.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,wEAAwE;AACxE,EAAE;AACF,mEAAmE;AACnE,uEAAuE;AACvE,qEAAqE;AACrE,gBAAgB;AAEhB,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,SAAS;AAEtC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACjD,IAAI,KAAK,CAAC,UAAU,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACtC,uEAAuE;IACvE,MAAM,MAAM,GAAG,WAAW,KAAK,UAAU,IAAI,WAAW,KAAK,UAAU,CAAC;IACxE,MAAM,YAAY,GAAG,WAAW,KAAK,UAAU,CAAC,CAAC,8BAA8B;IAC/E,IAAI,CAAC,MAAM,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,YAAY;QAAE,OAAO,KAAK,CAAC,CAAC,gDAAgD;IAEhF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,YAAY,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACzC,IAAI,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"sfns.js","sourceRoot":"","sources":["../../src/fonts/sfns.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,EAAE;AACF,0EAA0E;AAC1E,iBAAiB;AACjB,EAAE;AACF,wEAAwE;AACxE,6EAA6E;AAC7E,0EAA0E;AAC1E,oEAAoE;AACpE,uBAAuB;AAEvB,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,SAAS;AAEtC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACjD,IAAI,KAAK,CAAC,UAAU,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACtC,uEAAuE;IACvE,MAAM,MAAM,GAAG,WAAW,KAAK,UAAU,IAAI,WAAW,KAAK,UAAU,CAAC;IACxE,MAAM,YAAY,GAAG,WAAW,KAAK,UAAU,CAAC,CAAC,8BAA8B;IAC/E,IAAI,CAAC,MAAM,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,YAAY;QAAE,OAAO,KAAK,CAAC,CAAC,gDAAgD;IAEhF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,YAAY,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACzC,IAAI,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { displayLabel, getProp, getProps, hasProp, type PropertyHost, roadmapTitle, } from './ast-helpers.js';
2
- export { BUNDLED_MONO_PATH, BUNDLED_SANS_PATH, clearBundledCache, loadBundledMono, loadBundledSans, } from './fonts/bundled.js';
2
+ export { _clearBrowserFontsCache, loadBundledFontsForBrowser } from './fonts/browser.js';
3
+ export { BUNDLED_MONO_FAMILY, BUNDLED_MONO_PATH, BUNDLED_SANS_FAMILY, BUNDLED_SANS_PATH, clearBundledCache, loadBundledMono, loadBundledSans, } from './fonts/bundled.js';
3
4
  export { ALIASES, aliasCandidate, type FontCandidate, isAlias, type PlatformProbe, probeListFor, } from './fonts/probe-list.js';
4
5
  export { FontResolveError, type ResolveOptions, type ResolveResult, resolveFonts, } from './fonts/resolve.js';
5
6
  export { isVariableFontBytes } from './fonts/sfns.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,KAAK,YAAY,EACjB,YAAY,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACH,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACH,OAAO,EACP,cAAc,EACd,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,aAAa,EAClB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACH,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,YAAY,GACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EACH,KAAK,YAAY,EACjB,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,KAAK,YAAY,EACjB,WAAW,EACX,cAAc,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACR,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,EACT,aAAa,EACb,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACH,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,GACjB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,KAAK,YAAY,EACjB,YAAY,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EACH,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACH,OAAO,EACP,cAAc,EACd,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,aAAa,EAClB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACH,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,YAAY,GACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EACH,KAAK,YAAY,EACjB,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,KAAK,YAAY,EACjB,WAAW,EACX,cAAc,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACR,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,EACT,aAAa,EACb,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACH,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,GACjB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { displayLabel, getProp, getProps, hasProp, roadmapTitle, } from './ast-helpers.js';
2
- export { BUNDLED_MONO_PATH, BUNDLED_SANS_PATH, clearBundledCache, loadBundledMono, loadBundledSans, } from './fonts/bundled.js';
2
+ export { _clearBrowserFontsCache, loadBundledFontsForBrowser } from './fonts/browser.js';
3
+ export { BUNDLED_MONO_FAMILY, BUNDLED_MONO_PATH, BUNDLED_SANS_FAMILY, BUNDLED_SANS_PATH, clearBundledCache, loadBundledMono, loadBundledSans, } from './fonts/bundled.js';
3
4
  export { ALIASES, aliasCandidate, isAlias, probeListFor, } from './fonts/probe-list.js';
4
5
  export { FontResolveError, resolveFonts, } from './fonts/resolve.js';
5
6
  export { isVariableFontBytes } from './fonts/sfns.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,OAAO,EAEP,YAAY,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACH,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACH,OAAO,EACP,cAAc,EAEd,OAAO,EAEP,YAAY,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACH,gBAAgB,EAGhB,YAAY,GACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAEH,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,WAAW,EAEX,WAAW,EACX,cAAc,GACjB,MAAM,eAAe,CAAC;AAavB,OAAO,EACH,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,GACjB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,OAAO,EAEP,YAAY,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EACH,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACH,OAAO,EACP,cAAc,EAEd,OAAO,EAEP,YAAY,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACH,gBAAgB,EAGhB,YAAY,GACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAEH,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,WAAW,EAEX,WAAW,EACX,cAAc,GACjB,MAAM,eAAe,CAAC;AAavB,OAAO,EACH,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,GACjB,MAAM,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowline/export-core",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Nowline export-core — shared types, font resolver, unit converter, PDF page-size types",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -34,8 +34,8 @@
34
34
  "assets/"
35
35
  ],
36
36
  "dependencies": {
37
- "@nowline/core": "0.5.1",
38
- "@nowline/layout": "0.5.1"
37
+ "@nowline/core": "0.6.0",
38
+ "@nowline/layout": "0.6.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.9.1",
@@ -0,0 +1,47 @@
1
+ // Browser-compatible bundled font loader.
2
+ //
3
+ // Same bundled DejaVu bytes as loadBundledSans/loadBundledMono (Node), but
4
+ // decoded with `atob()` instead of Node's `Buffer.from()` so the module
5
+ // bundles cleanly for the browser (the Free/Pro web apps, Playwright legs).
6
+ //
7
+ // Usage: `loadBundledFontsForBrowser()` returns a cached `ResolvedFontPair`
8
+ // synchronously after the first call. Call it once at module init or lazy-once
9
+ // inside your `loadWasm` / font-loading step.
10
+ //
11
+ // Node callers: use `loadBundledSans()` / `loadBundledMono()` from bundled.ts.
12
+
13
+ import { MONO_BASE64, SANS_BASE64 } from '../generated/bundled-fonts.js';
14
+ import type { ResolvedFontPair } from '../types.js';
15
+
16
+ function b64ToBytes(b64: string): Uint8Array {
17
+ const binary = atob(b64);
18
+ const out = new Uint8Array(binary.length);
19
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
20
+ return out;
21
+ }
22
+
23
+ let cachedFonts: ResolvedFontPair | undefined;
24
+
25
+ /**
26
+ * Return the canonical bundled DejaVu font pair decoded with browser-compatible
27
+ * APIs (`atob`). Result is cached after the first call so the ~1 MB decode only
28
+ * runs once per page. Safe to call before wasm initialization.
29
+ */
30
+ export function loadBundledFontsForBrowser(): ResolvedFontPair {
31
+ if (!cachedFonts) {
32
+ cachedFonts = {
33
+ sans: { name: 'DejaVu Sans', bytes: b64ToBytes(SANS_BASE64), source: 'bundled' },
34
+ mono: {
35
+ name: 'DejaVu Sans Mono',
36
+ bytes: b64ToBytes(MONO_BASE64),
37
+ source: 'bundled',
38
+ },
39
+ };
40
+ }
41
+ return cachedFonts;
42
+ }
43
+
44
+ /** Test seam: drop the cached pair so isolated tests start fresh. */
45
+ export function _clearBrowserFontsCache(): void {
46
+ cachedFonts = undefined;
47
+ }
@@ -19,11 +19,26 @@ import { fileURLToPath } from 'node:url';
19
19
 
20
20
  import { MONO_BASE64, SANS_BASE64 } from '../generated/bundled-fonts.js';
21
21
 
22
- const HERE = path.dirname(fileURLToPath(import.meta.url));
22
+ // Resolve the on-disk assets/fonts directory from this module's location.
23
+ // `import.meta.url` is rewritten to `undefined` when this module is bundled
24
+ // into a CommonJS context — esbuild collapses `import.meta` to `{}` for the VS
25
+ // Code extension's `dist/extension.cjs`. The two exported paths below are
26
+ // informational only (the runtime decodes the embedded base64 above, never
27
+ // reads these files), so degrade to a bare relative directory instead of
28
+ // throwing `ERR_INVALID_ARG_TYPE` at module load — which would crash the
29
+ // extension before it can activate. See packages/vscode-extension.
30
+ function bundledFontsDir(): string {
31
+ try {
32
+ // dist/fonts/bundled.js → ../../assets/fonts/
33
+ // src/fonts/bundled.ts → ../../assets/fonts/
34
+ const here = path.dirname(fileURLToPath(import.meta.url));
35
+ return path.resolve(here, '..', '..', 'assets', 'fonts');
36
+ } catch {
37
+ return path.join('assets', 'fonts');
38
+ }
39
+ }
23
40
 
24
- // dist/fonts/bundled.js ../../assets/fonts/
25
- // src/fonts/bundled.ts → ../../assets/fonts/
26
- const ASSETS_DIR = path.resolve(HERE, '..', '..', 'assets', 'fonts');
41
+ const ASSETS_DIR = bundledFontsDir();
27
42
 
28
43
  // Informational: where the source-of-truth TTFs live on disk for users
29
44
  // running under Node. Not the runtime load source — `loadBundledSans` and
@@ -32,6 +47,15 @@ const ASSETS_DIR = path.resolve(HERE, '..', '..', 'assets', 'fonts');
32
47
  export const BUNDLED_SANS_PATH = path.join(ASSETS_DIR, 'DejaVuSans.ttf');
33
48
  export const BUNDLED_MONO_PATH = path.join(ASSETS_DIR, 'DejaVuSansMono.ttf');
34
49
 
50
+ /**
51
+ * Family names stamped on the bundled DejaVu faces. The resolver's
52
+ * `ResolvedFont.name`, the renderer's pinned `font-family`, the resvg family
53
+ * hints, and the live-preview `@font-face` must all use these exact strings
54
+ * so preview and raster export name the same face (the WYSIWYG contract).
55
+ */
56
+ export const BUNDLED_SANS_FAMILY = 'DejaVu Sans';
57
+ export const BUNDLED_MONO_FAMILY = 'DejaVu Sans Mono';
58
+
35
59
  let cachedSans: Uint8Array | undefined;
36
60
  let cachedMono: Uint8Array | undefined;
37
61
 
@@ -1,3 +1,4 @@
1
+ export { _clearBrowserFontsCache, loadBundledFontsForBrowser } from './browser.js';
1
2
  export {
2
3
  BUNDLED_MONO_PATH,
3
4
  BUNDLED_SANS_PATH,
@@ -1,21 +1,40 @@
1
- // 5-step font resolver shared by PDF and PNG.
1
+ // Bundled-first font resolver shared by PDF and PNG.
2
2
  //
3
- // Spec: specs/handoffs/m2c.md § 10 "Font strategy — system first, one
4
- // bundled headless fallback".
3
+ // Spec: specs/handoffs/m2c.md § 10 "Font strategy — bundled first, system
4
+ // fonts opt-in".
5
+ //
6
+ // The resolver defaults to the bundled static DejaVu pair on every OS so that
7
+ // preview and raster export look identical everywhere and we never hand
8
+ // `@resvg/resvg-wasm` a variable font (which it cannot rasterize — see
9
+ // `sfns.ts`). System fonts are an explicit opt-in.
5
10
  //
6
11
  // Resolution order (run independently for sans / mono — first hit wins):
7
12
  // 1. Explicit flag — `--font-sans <path|alias>` / `--font-mono <path|alias>`
8
13
  // 2. Environment — NOWLINE_FONT_SANS / NOWLINE_FONT_MONO
9
- // 3. Headless override — `--headless`, NOWLINE_HEADLESS=1, or auto in CI
10
- // without a TTY
11
- // 4. Platform probe — first existing entry from `probe-list.ts`
12
- // 5. Bundled fallback DejaVuSans.ttf / DejaVuSansMono.ttf
14
+ // 3. Headless / default — `--headless`, NOWLINE_HEADLESS=1, auto in CI
15
+ // without a TTY, OR the plain default (no
16
+ // `useSystemFonts`): bundled DejaVu pair, no probe.
17
+ // 4. Platform probe opt-in via `useSystemFonts`; first existing STATIC
18
+ // entry from `probe-list.ts` (variable fonts skipped).
19
+ // 5. Bundled fallback — DejaVuSans.ttf / DejaVuSansMono.ttf (after an
20
+ // opted-in probe found nothing usable).
21
+ //
22
+ // Variable-font guard: an explicitly requested font (flag/env/alias) that
23
+ // turns out to be a variable font is substituted with the bundled DejaVu and
24
+ // flagged (`*VariableFontSubstituted`), because raster export cannot render a
25
+ // VF and there is no runtime instancer. On the opt-in probe path, variable
26
+ // candidates are skipped in favor of the next static system font.
13
27
 
14
28
  import { existsSync as defaultExistsSync, promises as fs } from 'node:fs';
15
29
  import * as path from 'node:path';
16
30
 
17
31
  import type { FontRole, FontSource, ResolvedFont } from '../types.js';
18
- import { loadBundledMono, loadBundledSans } from './bundled.js';
32
+ import {
33
+ BUNDLED_MONO_FAMILY,
34
+ BUNDLED_SANS_FAMILY,
35
+ loadBundledMono,
36
+ loadBundledSans,
37
+ } from './bundled.js';
19
38
  import { aliasCandidate, isAlias, type PlatformProbe, probeListFor } from './probe-list.js';
20
39
  import { isVariableFontBytes } from './sfns.js';
21
40
 
@@ -25,9 +44,18 @@ export interface ResolveOptions {
25
44
  /** Path or alias for the mono role. */
26
45
  fontMono?: string;
27
46
  /**
28
- * Skip steps 4–5; go straight to step 5 (bundled DejaVu pair). Implied by
47
+ * Opt in to the platform font probe (step 4). Off by default: the
48
+ * resolver is bundled-first, so the default (no flag/env) is the bundled
49
+ * DejaVu pair on every OS. With this set the probe runs and the first
50
+ * STATIC system font wins (variable fonts are skipped), bundled if none.
51
+ */
52
+ useSystemFonts?: boolean;
53
+ /**
54
+ * Skip the probe; go straight to the bundled DejaVu pair. Implied by
29
55
  * `NOWLINE_HEADLESS=1` and by `CI=true` without a TTY (unless
30
- * `disableAutoHeadless` is set).
56
+ * `disableAutoHeadless` is set). Largely redundant now that bundled is the
57
+ * default, but retained so an explicit `--headless` still forces bundled
58
+ * even alongside `useSystemFonts`.
31
59
  */
32
60
  headless?: boolean;
33
61
  /** Disable the CI-no-TTY auto-headless heuristic. Defaults to false. */
@@ -44,11 +72,20 @@ export interface ResolveResult {
44
72
  sans: ResolvedFont;
45
73
  mono: ResolvedFont;
46
74
  /**
47
- * True when EITHER role landed at step 5 without explicit `--headless`.
48
- * Callers (CLI) emit a `--strict` warning on this.
75
+ * True when a role landed on bundled DejaVu *after an opted-in probe found
76
+ * nothing usable* (`useSystemFonts` set, no static system font present).
77
+ * The bundled-first default is the intended path, not a fallback, so it
78
+ * does NOT set this. Callers (CLI) emit a `--strict` warning on this.
49
79
  */
50
80
  sansFellBackToBundled: boolean;
51
81
  monoFellBackToBundled: boolean;
82
+ /**
83
+ * True when an explicitly requested font (flag / env / alias) was a
84
+ * variable font and was replaced by the bundled DejaVu (raster cannot
85
+ * render a VF). Callers warn; the CLI errors under `--strict`.
86
+ */
87
+ sansVariableFontSubstituted: boolean;
88
+ monoVariableFontSubstituted: boolean;
52
89
  }
53
90
 
54
91
  export async function resolveFonts(options: ResolveOptions = {}): Promise<ResolveResult> {
@@ -59,11 +96,13 @@ export async function resolveFonts(options: ResolveOptions = {}): Promise<Resolv
59
96
  const probe = probeListFor(platform, env);
60
97
 
61
98
  const headlessRequested = isHeadlessRequested(options, env);
99
+ const useSystemFonts = options.useSystemFonts ?? false;
62
100
  const sans = await resolveRole({
63
101
  role: 'sans',
64
102
  flag: options.fontSans,
65
103
  envValue: env.NOWLINE_FONT_SANS,
66
104
  headless: headlessRequested,
105
+ useSystemFonts,
67
106
  probe,
68
107
  fileExists,
69
108
  readFileBytes,
@@ -73,15 +112,18 @@ export async function resolveFonts(options: ResolveOptions = {}): Promise<Resolv
73
112
  flag: options.fontMono,
74
113
  envValue: env.NOWLINE_FONT_MONO,
75
114
  headless: headlessRequested,
115
+ useSystemFonts,
76
116
  probe,
77
117
  fileExists,
78
118
  readFileBytes,
79
119
  });
80
120
  return {
81
- sans,
82
- mono,
83
- sansFellBackToBundled: sans.source === 'bundled' && !headlessRequested,
84
- monoFellBackToBundled: mono.source === 'bundled' && !headlessRequested,
121
+ sans: sans.font,
122
+ mono: mono.font,
123
+ sansFellBackToBundled: sans.fellBack,
124
+ monoFellBackToBundled: mono.fellBack,
125
+ sansVariableFontSubstituted: sans.variableSubstituted,
126
+ monoVariableFontSubstituted: mono.variableSubstituted,
85
127
  };
86
128
  }
87
129
 
@@ -99,38 +141,74 @@ interface RoleArgs {
99
141
  flag?: string;
100
142
  envValue?: string;
101
143
  headless: boolean;
144
+ useSystemFonts: boolean;
102
145
  probe: PlatformProbe;
103
146
  fileExists: (p: string) => boolean;
104
147
  readFileBytes: (p: string) => Promise<Uint8Array>;
105
148
  }
106
149
 
107
- async function resolveRole(args: RoleArgs): Promise<ResolvedFont> {
150
+ interface RoleResolution {
151
+ font: ResolvedFont;
152
+ /** Bundled because an opted-in probe found no usable system font. */
153
+ fellBack: boolean;
154
+ /** An explicit VF request was replaced by the bundled DejaVu. */
155
+ variableSubstituted: boolean;
156
+ }
157
+
158
+ async function resolveRole(args: RoleArgs): Promise<RoleResolution> {
108
159
  // Step 1 — flag (path or alias)
109
160
  if (args.flag) {
110
- return loadFlag(args.flag, args.role, 'flag', args);
161
+ return guardExplicit(await loadFlag(args.flag, args.role, 'flag', args), args);
111
162
  }
112
163
  // Step 2 — environment
113
164
  if (args.envValue) {
114
- return loadFlag(args.envValue, args.role, 'env', args);
165
+ return guardExplicit(await loadFlag(args.envValue, args.role, 'env', args), args);
115
166
  }
116
- // Step 3 — headless: skip probe, go to bundled
167
+ // Step 3 — explicit headless: skip probe, go to bundled.
117
168
  if (args.headless) {
118
- return loadBundled(args.role, 'headless');
169
+ const font = await loadBundled(args.role, 'headless');
170
+ return { font, fellBack: false, variableSubstituted: false };
171
+ }
172
+ // Step 3 (default) — bundled-first: with no explicit system opt-in the
173
+ // default is the bundled DejaVu pair on every OS. This is the intended
174
+ // path (not a fallback), so it does not set `fellBack`.
175
+ if (!args.useSystemFonts) {
176
+ const font = await loadBundled(args.role, 'bundled');
177
+ return { font, fellBack: false, variableSubstituted: false };
119
178
  }
120
- // Step 4 — platform probe
179
+ // Step 4 — platform probe (opt-in). Skip variable candidates so we land on
180
+ // the next STATIC system font (resvg/raster cannot render a VF).
121
181
  for (const candidate of args.probe[args.role]) {
122
- if (args.fileExists(candidate.path)) {
123
- const bytes = await args.readFileBytes(candidate.path);
124
- return decorate(bytes, {
182
+ if (!args.fileExists(candidate.path)) continue;
183
+ const bytes = await args.readFileBytes(candidate.path);
184
+ if (isVariableFontBytes(bytes)) continue;
185
+ return {
186
+ font: decorate(bytes, {
125
187
  name: candidate.name,
126
188
  source: 'probe',
127
189
  path: candidate.path,
128
190
  face: candidate.face,
129
- });
130
- }
191
+ }),
192
+ fellBack: false,
193
+ variableSubstituted: false,
194
+ };
195
+ }
196
+ // Step 5 — bundled fallback after an opted-in probe found nothing usable.
197
+ const font = await loadBundled(args.role, 'bundled');
198
+ return { font, fellBack: true, variableSubstituted: false };
199
+ }
200
+
201
+ /**
202
+ * Guard an explicitly resolved font (flag / env / alias). A variable font
203
+ * cannot be rasterized and we have no runtime instancer, so substitute the
204
+ * bundled DejaVu and flag it; the caller decides whether to warn or error.
205
+ */
206
+ async function guardExplicit(font: ResolvedFont, args: RoleArgs): Promise<RoleResolution> {
207
+ if (font.isVariableFont) {
208
+ const bundled = await loadBundled(args.role, 'bundled');
209
+ return { font: bundled, fellBack: false, variableSubstituted: true };
131
210
  }
132
- // Step 5 bundled fallback
133
- return loadBundled(args.role, 'bundled');
211
+ return { font, fellBack: false, variableSubstituted: false };
134
212
  }
135
213
 
136
214
  async function loadFlag(
@@ -187,7 +265,7 @@ async function loadFlag(
187
265
  async function loadBundled(role: FontRole, source: FontSource): Promise<ResolvedFont> {
188
266
  const bytes = role === 'sans' ? await loadBundledSans() : await loadBundledMono();
189
267
  return decorate(bytes, {
190
- name: role === 'sans' ? 'DejaVu Sans' : 'DejaVu Sans Mono',
268
+ name: role === 'sans' ? BUNDLED_SANS_FAMILY : BUNDLED_MONO_FAMILY,
191
269
  source,
192
270
  });
193
271
  }
package/src/fonts/sfns.ts CHANGED
@@ -1,11 +1,13 @@
1
- // SF Pro variable-font handling.
1
+ // Variable-font detection.
2
2
  //
3
- // Spec: specs/handoffs/m2c.md § 10 "Variable-font handling (SFNS.ttf)".
3
+ // Spec: specs/handoffs/m2c.md § 10 "Font strategy — bundled first, system
4
+ // fonts opt-in".
4
5
  //
5
- // Detection only — actual VF instancing for PDF embedding lives in
6
- // @nowline/export-pdf, which depends on fontkit directly. The resolver
7
- // surfaces `isVariableFont: true` so consumers know they may need to
8
- // pre-instance.
6
+ // Detection only — the resolver uses this to guard explicit VF requests
7
+ // (substituting the bundled DejaVu and setting `variableSubstituted`) and to
8
+ // skip VF candidates on the opt-in system-font probe. There is no runtime
9
+ // instancer; any VF that reaches export would rasterize as blank in
10
+ // `@resvg/resvg-wasm`.
9
11
 
10
12
  const FVAR_TAG = 0x66766172; // 'fvar'
11
13
 
package/src/index.ts CHANGED
@@ -6,8 +6,11 @@ export {
6
6
  type PropertyHost,
7
7
  roadmapTitle,
8
8
  } from './ast-helpers.js';
9
+ export { _clearBrowserFontsCache, loadBundledFontsForBrowser } from './fonts/browser.js';
9
10
  export {
11
+ BUNDLED_MONO_FAMILY,
10
12
  BUNDLED_MONO_PATH,
13
+ BUNDLED_SANS_FAMILY,
11
14
  BUNDLED_SANS_PATH,
12
15
  clearBundledCache,
13
16
  loadBundledMono,