@turntrout/subfont 1.6.0 → 1.7.1

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.
Files changed (39) hide show
  1. package/README.md +43 -43
  2. package/lib/FontTracerPool.d.ts +37 -0
  3. package/lib/FontTracerPool.d.ts.map +1 -0
  4. package/lib/FontTracerPool.js +212 -173
  5. package/lib/FontTracerPool.js.map +1 -0
  6. package/lib/HeadlessBrowser.js +11 -3
  7. package/lib/cli.d.ts +3 -0
  8. package/lib/cli.d.ts.map +1 -0
  9. package/lib/cli.js +15 -12
  10. package/lib/cli.js.map +1 -0
  11. package/lib/collectTextsByPage.js +425 -352
  12. package/lib/escapeJsStringLiteral.js +13 -0
  13. package/lib/extractVisibleText.js +6 -2
  14. package/lib/fontConverter.js +25 -0
  15. package/lib/fontConverterWorker.js +16 -0
  16. package/lib/fontFaceHelpers.js +16 -4
  17. package/lib/gatherStylesheetsWithPredicates.js +4 -5
  18. package/lib/normalizeFontPropertyValue.js +1 -1
  19. package/lib/sfntCache.js +10 -7
  20. package/lib/subfont.d.ts +33 -0
  21. package/lib/subfont.d.ts.map +1 -0
  22. package/lib/subfont.js +533 -591
  23. package/lib/subfont.js.map +1 -0
  24. package/lib/subsetFontWithGlyphs.d.ts +17 -0
  25. package/lib/subsetFontWithGlyphs.d.ts.map +1 -0
  26. package/lib/subsetFontWithGlyphs.js +231 -253
  27. package/lib/subsetFontWithGlyphs.js.map +1 -0
  28. package/lib/subsetFonts.d.ts +59 -0
  29. package/lib/subsetFonts.d.ts.map +1 -0
  30. package/lib/subsetFonts.js +921 -1180
  31. package/lib/subsetFonts.js.map +1 -0
  32. package/lib/subsetGeneration.d.ts +39 -0
  33. package/lib/subsetGeneration.d.ts.map +1 -0
  34. package/lib/subsetGeneration.js +294 -324
  35. package/lib/subsetGeneration.js.map +1 -0
  36. package/lib/unquote.js +9 -4
  37. package/lib/warnAboutMissingGlyphs.js +36 -25
  38. package/lib/wasmQueue.js +6 -2
  39. package/package.json +11 -3
@@ -0,0 +1,13 @@
1
+ // Escape a value for safe inclusion in any JS string context (single-quoted,
2
+ // double-quoted, or template literal). Uses JSON.stringify for robust escaping
3
+ // of backslashes, quotes, newlines, U+2028, U+2029, etc.
4
+ // The < escape prevents </script> from closing an inline script tag.
5
+ function escapeJsStringLiteral(str) {
6
+ return JSON.stringify(str)
7
+ .slice(1, -1)
8
+ .replace(/'/g, "\\'")
9
+ .replace(/`/g, '\\x60')
10
+ .replace(/</g, '\\x3c');
11
+ }
12
+
13
+ module.exports = escapeJsStringLiteral;
@@ -81,8 +81,12 @@ const namedEntities = {
81
81
  const entityRe = /&(?:#x([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g;
82
82
  function decodeEntities(str) {
83
83
  return str.replace(entityRe, (match, hex, dec, name) => {
84
- if (hex) return String.fromCodePoint(parseInt(hex, 16));
85
- if (dec) return String.fromCodePoint(parseInt(dec, 10));
84
+ try {
85
+ if (hex) return String.fromCodePoint(parseInt(hex, 16));
86
+ if (dec) return String.fromCodePoint(parseInt(dec, 10));
87
+ } catch {
88
+ return match;
89
+ }
86
90
  if (name && namedEntities[name.toLowerCase()] !== undefined) {
87
91
  return namedEntities[name.toLowerCase()];
88
92
  }
@@ -0,0 +1,25 @@
1
+ const pathModule = require('path');
2
+ const { Worker } = require('worker_threads');
3
+
4
+ const workerPath = pathModule.join(__dirname, 'fontConverterWorker.js');
5
+
6
+ function convert(buffer, targetFormat, sourceFormat) {
7
+ return new Promise((resolve, reject) => {
8
+ const worker = new Worker(workerPath);
9
+ worker.on('message', (msg) => {
10
+ worker.terminate();
11
+ if (msg.type === 'result') {
12
+ resolve(Buffer.from(msg.buffer));
13
+ } else {
14
+ reject(new Error(msg.error));
15
+ }
16
+ });
17
+ worker.on('error', (err) => {
18
+ worker.terminate();
19
+ reject(err);
20
+ });
21
+ worker.postMessage({ buffer, targetFormat, sourceFormat });
22
+ });
23
+ }
24
+
25
+ module.exports = { convert };
@@ -0,0 +1,16 @@
1
+ const { parentPort } = require('worker_threads');
2
+ const fontverter = require('fontverter');
3
+
4
+ parentPort.on('message', async (msg) => {
5
+ try {
6
+ const buffer = Buffer.from(msg.buffer);
7
+ const result = await fontverter.convert(
8
+ buffer,
9
+ msg.targetFormat,
10
+ msg.sourceFormat
11
+ );
12
+ parentPort.postMessage({ type: 'result', buffer: result });
13
+ } catch (err) {
14
+ parentPort.postMessage({ type: 'error', error: err.message });
15
+ }
16
+ });
@@ -75,15 +75,27 @@ function getFontFaceDeclarationText(node, relations) {
75
75
 
76
76
  const fontOrder = ['woff2', 'woff', 'truetype'];
77
77
 
78
- function getFontFaceForFontUsage(fontUsage) {
79
- const subsets = fontOrder
80
- .filter((format) => fontUsage.subsets[format])
78
+ // Cache base64-encoded data URIs keyed by the underlying Buffer. Subset
79
+ // buffers are shared across pages (propagated from the canonical fontUsage),
80
+ // so without this every page re-encodes the same multi-hundred-KB buffer.
81
+ const subsetDataUrlCache = new WeakMap();
82
+ function getSubsetDataUrls(subsetsObj) {
83
+ let cached = subsetDataUrlCache.get(subsetsObj);
84
+ if (cached) return cached;
85
+ cached = fontOrder
86
+ .filter((format) => subsetsObj[format])
81
87
  .map((format) => ({
82
88
  format,
83
- url: `data:${contentTypeByFontFormat[format]};base64,${fontUsage.subsets[
89
+ url: `data:${contentTypeByFontFormat[format]};base64,${subsetsObj[
84
90
  format
85
91
  ].toString('base64')}`,
86
92
  }));
93
+ subsetDataUrlCache.set(subsetsObj, cached);
94
+ return cached;
95
+ }
96
+
97
+ function getFontFaceForFontUsage(fontUsage) {
98
+ const subsets = getSubsetDataUrls(fontUsage.subsets);
87
99
 
88
100
  const resultString = ['@font-face {'];
89
101
 
@@ -3,18 +3,17 @@ module.exports = function gatherStylesheetsWithPredicates(
3
3
  htmlAsset,
4
4
  relationIndex
5
5
  ) {
6
- const assetStack = [];
6
+ const visiting = new Set();
7
7
  const incomingMedia = [];
8
8
  const conditionalCommentConditionStack = [];
9
9
  const result = [];
10
10
  (function traverse(asset, isWithinNotIeConditionalComment, isWithinNoscript) {
11
- if (assetStack.includes(asset)) {
12
- // Cycle detected
11
+ if (visiting.has(asset)) {
13
12
  return;
14
13
  } else if (!asset.isLoaded) {
15
14
  return;
16
15
  }
17
- assetStack.push(asset);
16
+ visiting.add(asset);
18
17
  // Use pre-built index if available, otherwise fall back to findRelations
19
18
  const relations = relationIndex
20
19
  ? relationIndex.get(asset) || []
@@ -60,7 +59,7 @@ module.exports = function gatherStylesheetsWithPredicates(
60
59
  }
61
60
  }
62
61
  }
63
- assetStack.pop();
62
+ visiting.delete(asset);
64
63
  if (asset.type === 'Css') {
65
64
  const predicates = {};
66
65
  for (const incomingMedium of incomingMedia) {
@@ -34,7 +34,7 @@ function isValidWeight(weight) {
34
34
  function normalizeFontPropertyValue(propName, value) {
35
35
  const propNameLowerCase = propName.toLowerCase();
36
36
  if (value === undefined) {
37
- return initialValueByProp[propName];
37
+ return initialValueByProp[propNameLowerCase];
38
38
  }
39
39
  if (propNameLowerCase === 'font-family') {
40
40
  return unquote(value);
package/lib/sfntCache.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const fontverter = require('fontverter');
2
+ const { convert } = require('./fontConverter');
2
3
 
3
4
  const sfntPromiseByBuffer = new WeakMap();
4
5
 
@@ -9,13 +10,15 @@ function toSfnt(buffer) {
9
10
  let promise;
10
11
  try {
11
12
  const format = fontverter.detectFormat(buffer);
12
- promise =
13
- format === 'sfnt'
14
- ? Promise.resolve(buffer)
15
- : fontverter.convert(buffer, 'sfnt');
16
- } catch (err) {
17
- // Unrecognized format — don't cache so retries work
18
- return fontverter.convert(buffer, 'sfnt');
13
+ if (format === 'sfnt') {
14
+ promise = Promise.resolve(buffer);
15
+ } else if (format === 'woff2') {
16
+ promise = convert(buffer, 'sfnt');
17
+ } else {
18
+ promise = fontverter.convert(buffer, 'sfnt');
19
+ }
20
+ } catch {
21
+ promise = convert(buffer, 'sfnt');
19
22
  }
20
23
  // Evict on rejection so retries with the same buffer aren't stuck
21
24
  promise.catch(() => sfntPromiseByBuffer.delete(buffer));
@@ -0,0 +1,33 @@
1
+ import AssetGraph = require('assetgraph');
2
+ declare class UsageError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ interface SubfontOptions {
6
+ root?: string;
7
+ canonicalRoot?: string;
8
+ output?: string;
9
+ debug?: boolean;
10
+ dryRun?: boolean;
11
+ silent?: boolean;
12
+ inlineCss?: boolean;
13
+ fontDisplay?: string;
14
+ inPlace?: boolean;
15
+ inputFiles?: Array<string | URL>;
16
+ recursive?: boolean;
17
+ relativeUrls?: boolean;
18
+ dynamic?: boolean;
19
+ fallbacks?: boolean;
20
+ text?: string;
21
+ sourceMaps?: boolean;
22
+ concurrency?: number;
23
+ chromeFlags?: string[];
24
+ cache?: boolean | string;
25
+ strict?: boolean;
26
+ }
27
+ interface SubfontFn {
28
+ (options: SubfontOptions, console?: Console): Promise<InstanceType<typeof AssetGraph>>;
29
+ UsageError: typeof UsageError;
30
+ }
31
+ declare const subfont: SubfontFn;
32
+ export = subfont;
33
+ //# sourceMappingURL=subfont.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subfont.d.ts","sourceRoot":"","sources":["../src/subfont.ts"],"names":[],"mappings":"AAKA,OAAO,UAAU,GAAG,QAAQ,YAAY,CAAC,CAAC;AAQ1C,cAAM,UAAW,SAAQ,KAAK;gBAChB,OAAO,EAAE,MAAM;CAI5B;AAED,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqBD,UAAU,SAAS;IACjB,CACE,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC;IAC5C,UAAU,EAAE,OAAO,UAAU,CAAC;CAC/B;AAED,QAAA,MAAM,OAAO,EA6pBR,SAAS,CAAC;AAIf,SAAS,OAAO,CAAC"}