From 4896d67700590a4a890afff629fe82a6e6497995 Mon Sep 17 00:00:00 2001 From: FAL Date: Mon, 17 Nov 2025 17:31:59 +0900 Subject: [PATCH 01/20] fix: rename logErrors() -> logWarnings() and other relevant names to line up with the actual behavior --- MODIFICATIONS.md | 19 ++++++++++--------- src/TTFFont.js | 8 ++++---- src/base.js | 10 +++++----- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/MODIFICATIONS.md b/MODIFICATIONS.md index c389ff21..d0c1ce41 100644 --- a/MODIFICATIONS.md +++ b/MODIFICATIONS.md @@ -4,15 +4,16 @@ - Remove WOFF format support - Also removing the `tiny-inflate` dependency (the indirect dependency may remain) -- Remove WOFF2 format support - - Also removing the `brotli` dependency -- Remove DFont format support -- Simplify the published TypeScript types by inlining the concrete format exports (`TTFFont`/`TrueTypeCollection`) in place of the old aliases (`Font`/`FontCollection`). -- Remove the dependencies below by replacing them with new internal helpers (no API changes): - - `clone` (used by `TTFSubset`) - - `fast-deep-equal` (used by `CFFDict`) - -## [2.0.4-mod.2025.2] +- Remove WOFF2 format support + - Also removing the `brotli` dependency +- Remove DFont format support +- Simplify the published TypeScript types by inlining the concrete format exports (`TTFFont`/`TrueTypeCollection`) in place of the old aliases (`Font`/`FontCollection`). +- Remove the dependencies below by replacing them with new internal helpers (no API changes): + - `clone` (used by `TTFSubset`) + - `fast-deep-equal` (used by `CFFDict`) +- Rename table decode logging helpers from `logErrors`/`isLoggingErrors` to `logWarnings`/`isLoggingWarnings` and downgrade emitted messages to warnings + +## [2.0.4-mod.2025.2] - Improve performance of `CmapProcessor#lookupNonDefaultUVS` by caching variation selector records from `cmap` format 14 subtable diff --git a/src/TTFFont.js b/src/TTFFont.js index 24d3c89d..a4eba3a3 100644 --- a/src/TTFFont.js +++ b/src/TTFFont.js @@ -1,6 +1,6 @@ import * as r from 'restructure'; import { cache } from './decorators'; -import { isLoggingErrors, getDefaultLanguage as getGlobalDefaultLanguage } from './base'; +import { isLoggingWarnings, getDefaultLanguage as getGlobalDefaultLanguage } from './base'; import Directory from './tables/directory'; import tables from './tables/index'; import CmapProcessor from './CmapProcessor'; @@ -81,9 +81,9 @@ export default class TTFFont { try { this._tables[table.tag] = this._decodeTable(table); } catch (e) { - if (isLoggingErrors()) { - console.error(`Error decoding table ${table.tag}`); - console.error(e.stack); + if (isLoggingWarnings()) { + console.warn(`Failed to decode table ${table.tag}`); + console.warn(e.stack); } } } diff --git a/src/base.js b/src/base.js index b2a7a70d..aa859608 100644 --- a/src/base.js +++ b/src/base.js @@ -5,17 +5,17 @@ import { DecodeStream } from 'restructure'; // ----------------------------------------------------------------------------- -let loggingErrors = false; +let loggingWarnings = false; -export function isLoggingErrors() { - return loggingErrors; +export function isLoggingWarnings() { + return loggingWarnings; } /** * @param {boolean} flag */ -export function logErrors(flag) { - loggingErrors = flag; +export function logWarnings(flag) { + loggingWarnings = flag; } // ----------------------------------------------------------------------------- From e1b48e405cbdd7556bdcde449872215041c6bae0 Mon Sep 17 00:00:00 2001 From: FAL Date: Mon, 17 Nov 2025 22:40:16 +0900 Subject: [PATCH 02/20] =?UTF-8?q?fix:=20=E6=9C=AA=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E3=81=AE=20GSUB=20lookupType=20=E3=81=AB=E3=81=A4=E3=81=84?= =?UTF-8?q?=E3=81=A6=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E5=87=BA=E3=81=99?= =?UTF-8?q?=E9=9A=9B=E3=81=AB=E3=80=81=E6=9C=AA=E5=AF=BE=E5=BF=9C=E3=81=AE?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=A8=E4=B8=8D=E6=AD=A3=E3=81=AA=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=A8=E3=82=92=E5=8C=BA=E5=88=A5=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/opentype/GSUBProcessor.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/opentype/GSUBProcessor.js b/src/opentype/GSUBProcessor.js index f5672d5d..77c9f7bf 100644 --- a/src/opentype/GSUBProcessor.js +++ b/src/opentype/GSUBProcessor.js @@ -185,8 +185,11 @@ export default class GSUBProcessor extends OTProcessor { case 7: // Extension Substitution return this.applyLookup(table.lookupType, table.extension); + case 8: // Reverse Chaining Contextual Single Substitution + throw new Error(`GSUB lookupType 8 is not supported`); + default: - throw new Error(`GSUB lookupType ${lookupType} is not supported`); + throw new Error(`Unknown GSUB lookupType: ${lookupType}`); } } } From e3ead97ac3fd1823edbbde6c7a54c446b6d4c71e Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 00:40:54 +0900 Subject: [PATCH 03/20] =?UTF-8?q?fix:=20CMAP=20format=202=20=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E3=81=97=E3=81=A6=20Unknown=20=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E3=81=AA=E3=81=8F=20Unsupported=20=E3=81=A8=E3=81=AE=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=82=92=E5=87=BA=E3=81=99=E3=82=88=E3=81=86?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CmapProcessor.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/CmapProcessor.js b/src/CmapProcessor.js index 390cc944..cbcdbe00 100644 --- a/src/CmapProcessor.js +++ b/src/CmapProcessor.js @@ -73,6 +73,10 @@ export default class CmapProcessor { case 0: return cmap.codeMap.get(codepoint) || 0; + case 2: + // Microsoft OpenType spec says "This format is not commonly used today." + throw new Error('Unsupported cmap format 2'); + case 4: { let min = 0; let max = cmap.segCount - 1; @@ -179,6 +183,10 @@ export default class CmapProcessor { case 0: return range(0, cmap.codeMap.length); + case 2: + // Microsoft OpenType spec says "This format is not commonly used today." + throw new Error('Unsupported cmap format 2'); + case 4: { let res = []; let endCodes = cmap.endCode.toArray(); From 21aefd1503d9ef4a4e57373fca2699100a6ff42e Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 00:45:44 +0900 Subject: [PATCH 04/20] =?UTF-8?q?fix:=20CMAP=20format=208=20=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E3=81=99=E3=82=8B=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CmapProcessor.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/CmapProcessor.js b/src/CmapProcessor.js index cbcdbe00..82b8e07d 100644 --- a/src/CmapProcessor.js +++ b/src/CmapProcessor.js @@ -109,7 +109,8 @@ export default class CmapProcessor { } case 8: - throw new Error('TODO: cmap format 8'); + // TODO: support format 8 + throw new Error('Unsupported cmap format 8'); case 6: case 10: @@ -200,7 +201,8 @@ export default class CmapProcessor { } case 8: - throw new Error('TODO: cmap format 8'); + // TODO: support format 8 + throw new Error('Unsupported cmap format 8'); case 6: case 10: From 4006bf6c0b96701e7606f7e89364afe5231dd636 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 01:18:18 +0900 Subject: [PATCH 05/20] chore: add JSDoc @abstract tags --- src/opentype/OTProcessor.js | 6 ++++++ src/subset/Subset.js | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/opentype/OTProcessor.js b/src/opentype/OTProcessor.js index 1442b43d..a186324b 100644 --- a/src/opentype/OTProcessor.js +++ b/src/opentype/OTProcessor.js @@ -3,6 +3,9 @@ import * as Script from '../layout/Script'; const DEFAULT_SCRIPTS = ['DFLT', 'dflt', 'latn']; +/** + * @abstract + */ export default class OTProcessor { constructor(font, table) { this.font = font; @@ -218,6 +221,9 @@ export default class OTProcessor { } } + /** + * @abstract + */ applyLookup(lookup, table) { throw new Error('applyLookup must be implemented by subclasses'); } diff --git a/src/subset/Subset.js b/src/subset/Subset.js index a80ab599..2d7ce7a4 100644 --- a/src/subset/Subset.js +++ b/src/subset/Subset.js @@ -1,5 +1,8 @@ // @ts-check +/** + * @abstract + */ export default class Subset { /** * @type {('TTF' | 'CFF' | 'UNKNOWN')} @@ -47,7 +50,7 @@ export default class Subset { } /** - * @returns {Uint8Array} + * @abstract */ encode() { throw new Error('Not implemented'); From 4f77494d6e12a0be463f331378380d67f58f0762 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 01:38:51 +0900 Subject: [PATCH 06/20] chore: remove unused imports --- src/TrueTypeCollection.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/TrueTypeCollection.js b/src/TrueTypeCollection.js index 90540049..5d4a9f08 100644 --- a/src/TrueTypeCollection.js +++ b/src/TrueTypeCollection.js @@ -1,7 +1,5 @@ import * as r from 'restructure'; import TTFFont from './TTFFont'; -import Directory from './tables/directory'; -import tables from './tables'; import { asciiDecoder } from './utils/decode'; let TTCHeader = new r.VersionedStruct(r.uint32, { From f7d5de22ec691ce5da7750c99915e08e09db0a8e Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 02:17:39 +0900 Subject: [PATCH 07/20] fix: throw dedicated errors for unsupported and unexpected cmap formats in codePointsForGlyph() --- src/CmapProcessor.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/CmapProcessor.js b/src/CmapProcessor.js index 82b8e07d..1e9546f6 100644 --- a/src/CmapProcessor.js +++ b/src/CmapProcessor.js @@ -327,6 +327,12 @@ export default class CmapProcessor { return res; } + case 2: + case 6: + case 8: + case 10: + throw new Error(`Unsupported cmap format ${cmap.version}`); + case 12: { let res = []; for (let group of cmap.groups.toArray()) { @@ -349,6 +355,10 @@ export default class CmapProcessor { return res; } + case 14: + // Format 14 is handled separately via the uvs property + throw new Error('Unexpected cmap format 14'); + default: throw new Error(`Unknown cmap format ${cmap.version}`); } From 2596dfb4c44ae03b670113e06da021ded97c17f2 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 03:17:12 +0900 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=E4=B8=8D=E6=AD=A3=E3=81=AA=20GPOS?= =?UTF-8?q?=20lookupType=20=E3=81=AB=E5=AF=BE=E3=81=99=E3=82=8B=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=82=92=20Unsupported=20->=20Unknown=20=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/opentype/GPOSProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opentype/GPOSProcessor.js b/src/opentype/GPOSProcessor.js index c0852450..d5777fd4 100644 --- a/src/opentype/GPOSProcessor.js +++ b/src/opentype/GPOSProcessor.js @@ -278,7 +278,7 @@ export default class GPOSProcessor extends OTProcessor { return this.applyLookup(table.lookupType, table.extension); default: - throw new Error(`Unsupported GPOS table: ${lookupType}`); + throw new Error(`Unknown GPOS lookupType: ${lookupType}`); } } From 34d4be332be727860cfe4ca26a980446de96584d Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 14:33:57 +0900 Subject: [PATCH 09/20] =?UTF-8?q?fix:=20=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E5=BE=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aat/AATMorxProcessor.js | 2 +- src/cff/CFFIndex.js | 4 ++-- src/glyph/GlyphVariationProcessor.js | 2 +- src/glyph/TTFGlyph.js | 2 +- src/subset/CFFSubset.js | 2 +- src/subset/Subset.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/aat/AATMorxProcessor.js b/src/aat/AATMorxProcessor.js index 94744e1a..7aeedbdd 100644 --- a/src/aat/AATMorxProcessor.js +++ b/src/aat/AATMorxProcessor.js @@ -297,7 +297,7 @@ export default class AATMorxProcessor { let reverse = !!(subtable.coverage & REVERSE_DIRECTION); if (reverse) { - throw new Error('Reverse subtable, not supported.'); + throw new Error('Reverse MORX subtable not supported.'); } this.subtable = subtable; diff --git a/src/cff/CFFIndex.js b/src/cff/CFFIndex.js index fea5eaf3..62f4b606 100644 --- a/src/cff/CFFIndex.js +++ b/src/cff/CFFIndex.js @@ -90,7 +90,7 @@ export default class CFFIndex { } else if (offset <= 0xffffffff) { offsetType = r.uint32; } else { - throw new Error("Bad offset in CFFIndex"); + throw new Error('CFFIndex size overflow'); } size += 1 + offsetType.size() * (arr.length + 1); @@ -126,7 +126,7 @@ export default class CFFIndex { } else if (offset <= 0xffffffff) { offsetType = r.uint32; } else { - throw new Error("Bad offset in CFFIndex"); + throw new Error('CFFIndex encode offset overflow'); } // write offset size diff --git a/src/glyph/GlyphVariationProcessor.js b/src/glyph/GlyphVariationProcessor.js index 71c36878..3dca7277 100644 --- a/src/glyph/GlyphVariationProcessor.js +++ b/src/glyph/GlyphVariationProcessor.js @@ -105,7 +105,7 @@ export default class GlyphVariationProcessor { } else { if ((tupleIndex & TUPLE_INDEX_MASK) >= gvar.globalCoordCount) { - throw new Error('Invalid gvar table'); + throw new Error('gvar tuple references invalid shared coordinate index'); } var tupleCoords = gvar.globalCoords[tupleIndex & TUPLE_INDEX_MASK]; diff --git a/src/glyph/TTFGlyph.js b/src/glyph/TTFGlyph.js index 8dc1c75b..af8e820a 100644 --- a/src/glyph/TTFGlyph.js +++ b/src/glyph/TTFGlyph.js @@ -376,7 +376,7 @@ export default class TTFGlyph extends Glyph { var curvePt = null; } else { - throw new Error("Unknown TTF path state"); + throw new Error('Inconsistent on/off-curve sequence in glyf contour'); } } diff --git a/src/subset/CFFSubset.js b/src/subset/CFFSubset.js index 1c865cab..325ce410 100644 --- a/src/subset/CFFSubset.js +++ b/src/subset/CFFSubset.js @@ -14,7 +14,7 @@ export default class CFFSubset extends Subset { this.cff = this.font['CFF ']; if (!this.cff) { - throw new Error('Not a CFF Font'); + throw new Error('CFFSubset requires a font with a CFF table'); } } diff --git a/src/subset/Subset.js b/src/subset/Subset.js index 2d7ce7a4..287d739d 100644 --- a/src/subset/Subset.js +++ b/src/subset/Subset.js @@ -53,6 +53,6 @@ export default class Subset { * @abstract */ encode() { - throw new Error('Not implemented'); + throw new Error('Subset.encode() must be overridden by subclasses'); } } From 4264176e8e7cb0b20f9a9440b2ad746ea983fc49 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 14:46:48 +0900 Subject: [PATCH 10/20] chore: remove unused import --- src/subset/CFFSubset.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/subset/CFFSubset.js b/src/subset/CFFSubset.js index 325ce410..ad14ef7a 100644 --- a/src/subset/CFFSubset.js +++ b/src/subset/CFFSubset.js @@ -1,6 +1,5 @@ import Subset from './Subset'; import CFFTop from '../cff/CFFTop'; -import CFFPrivateDict from '../cff/CFFPrivateDict'; import standardStrings from '../cff/CFFStandardStrings'; export default class CFFSubset extends Subset { From e57afcdf49d6f9252817ca9fc718e4e3f39565c8 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 15:02:01 +0900 Subject: [PATCH 11/20] chore: add comments --- src/TrueTypeCollection.js | 1 + src/opentype/OTLayoutEngine.js | 3 +++ src/subset/CFFSubset.js | 1 + 3 files changed, 5 insertions(+) diff --git a/src/TrueTypeCollection.js b/src/TrueTypeCollection.js index 5d4a9f08..08622f03 100644 --- a/src/TrueTypeCollection.js +++ b/src/TrueTypeCollection.js @@ -29,6 +29,7 @@ export default class TrueTypeCollection { constructor(stream) { this.stream = stream; if (stream.readString(4) !== 'ttcf') { + // Should be unreachable: probe() verifies the TTC tag before construction. throw new Error('Not a TrueType collection'); } diff --git a/src/opentype/OTLayoutEngine.js b/src/opentype/OTLayoutEngine.js index 51d8b68c..db0ed179 100644 --- a/src/opentype/OTLayoutEngine.js +++ b/src/opentype/OTLayoutEngine.js @@ -63,6 +63,7 @@ export default class OTLayoutEngine { */ substitute(glyphRun) { if (this.glyphInfos == null || this.plan == null) { + // Internal guard: shapers invoke substitute() only after setup(). throw new Error('setup() must be called before substitute()'); } @@ -80,6 +81,7 @@ export default class OTLayoutEngine { */ position(glyphRun) { if (this.glyphInfos == null || this.plan == null || this.shaper == null) { + // Internal guard: setup() must run before any positioning work. throw new Error('setup() must be called before position()'); } @@ -116,6 +118,7 @@ export default class OTLayoutEngine { */ zeroMarkAdvances(positions) { if (this.glyphInfos == null) { + // Internal guard: zeroing advances only happens after setup(). throw new Error('setup() must be called before zeroMarkAdvances()'); } diff --git a/src/subset/CFFSubset.js b/src/subset/CFFSubset.js index ad14ef7a..38971ae1 100644 --- a/src/subset/CFFSubset.js +++ b/src/subset/CFFSubset.js @@ -13,6 +13,7 @@ export default class CFFSubset extends Subset { this.cff = this.font['CFF ']; if (!this.cff) { + // Subset constructors are only called after format probing, so this flags an invariant breach. throw new Error('CFFSubset requires a font with a CFF table'); } } From 1cc06a35b4f23200f7d5fdacd3badcae5ab72b56 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 15:05:08 +0900 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E5=BE=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base.js b/src/base.js index aa859608..38cd2bcc 100644 --- a/src/base.js +++ b/src/base.js @@ -45,7 +45,7 @@ export function create(buffer, postscriptName) { } } - throw new Error('Unknown font format'); + throw new Error('Unsupported font file format: no registered reader recognized the data'); } // ----------------------------------------------------------------------------- From 223708fb474628d4ff8b607a239474d8bd05e41b Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 21:16:38 +0900 Subject: [PATCH 13/20] =?UTF-8?q?build:=20tsconfig=E9=96=A2=E9=80=A3?= =?UTF-8?q?=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jsconfig.json | 7 ------- tsconfig-types.json | 8 ++++---- tsconfig.json | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 11 deletions(-) delete mode 100644 jsconfig.json create mode 100644 tsconfig.json diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 546bcbe9..00000000 --- a/jsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "strictNullChecks": true, - "target": "es2015", - "experimentalDecorators": true - } -} diff --git a/tsconfig-types.json b/tsconfig-types.json index 7fa57e1e..e40ef199 100644 --- a/tsconfig-types.json +++ b/tsconfig-types.json @@ -1,13 +1,13 @@ { + "extends": "./tsconfig.json", "compilerOptions": { + "noEmit": false, "declaration": true, "emitDeclarationOnly": true, - "allowJs": true, - "target": "ESNext", - "outDir": "types", + "outDir": "types" }, "include": [ "src/index.ts", - "src/node.ts", + "src/node.ts" ] } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..fe7eb799 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "experimentalDecorators": true, + "allowJs": true, + "checkJs": false, + "strictNullChecks": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js" + ] +} From 11d9c8bf11c50180c955540e1d4fa829bd65120c Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 21:42:56 +0900 Subject: [PATCH 14/20] =?UTF-8?q?chore:=20FontkitError=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=81=8A=E3=82=88=E3=81=B3=E5=90=84=E7=A8=AE=E3=82=B5?= =?UTF-8?q?=E3=83=96=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/errors.ts | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/errors.ts diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 00000000..7ccf2c8e --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,95 @@ +/** + * Error type codes recognized by `@denkiyagi/fontkit`. + */ +export const FontkitErrorTypes = { + /** @see {@link UnsupportedFontFileFormatError} */ + UNSUPPORTED_FONT_FILE_FORMAT: 'UNSUPPORTED_FONT_FILE_FORMAT', + + /** @see {@link UnsupportedFontDataError} */ + UNSUPPORTED_FONT_DATA: 'UNSUPPORTED_FONT_DATA', + + /** @see {@link InvalidFontDataError} */ + INVALID_FONT_DATA: 'INVALID_FONT_DATA', + + /** @see {@link InvalidCallerInputError} */ + INVALID_CALLER_INPUT: 'INVALID_CALLER_INPUT', + + /** @see {@link AssertionError} */ + ASSERTION: 'ASSERTION', +} as const; + +/** + * Error type code recognized by `@denkiyagi/fontkit`. + */ +export type FontkitErrorType = typeof FontkitErrorTypes[keyof typeof FontkitErrorTypes]; + +/** + * Error explicitly thrown by `@denkiyagi/fontkit`. + */ +export class FontkitError extends Error { + /** + * Machine-readable classification for the failure. + */ + readonly type: FontkitErrorType; + + constructor(type: FontkitErrorType, message: string, options?: ErrorOptions) { + super(message, options); + + this.name = new.target.name; + this.type = type; + } +} + +/** + * Buffer cannot be recognized as any supported font container, + * or represents a packaging variant we intentionally do not handle. + */ +export class UnsupportedFontFileFormatError extends FontkitError { + constructor(message: string, options?: ErrorOptions) { + super(FontkitErrorTypes.UNSUPPORTED_FONT_FILE_FORMAT, message, options); + } +} + +/** + * The font decodes successfully but requests optional tables/features that + * our engine has not implemented yet (e.g., cmap format 8, GSUB lookup type 8). + */ +export class UnsupportedFontDataError extends FontkitError { + constructor(message: string, options?: ErrorOptions) { + super(FontkitErrorTypes.UNSUPPORTED_FONT_DATA, message, options); + } +} + +/** + * Encoded data violates the applicable specification or is structurally + * inconsistent (bad offsets, illegal operators, corrupt tuples, ...). + */ +export class InvalidFontDataError extends FontkitError { + constructor(message: string, options?: ErrorOptions) { + super(FontkitErrorTypes.INVALID_FONT_DATA, message, options); + } +} + +/** + * Public API or shared helper received parameters outside its contract + * (wrong types, missing setup call, unsupported descriptors). + * Caller may be user code or an upstream subsystem. + * + * Scenarios that can only be triggered by internal misuse (no external entry point) + * are classified as `AssertionError` instead. + */ +export class InvalidCallerInputError extends FontkitError { + constructor(message: string, options?: ErrorOptions) { + super(FontkitErrorTypes.INVALID_CALLER_INPUT, message, options); + } +} + +/** + * Invariant breach that can only originate from an internal bug; + * the scenario should never happen when inputs are valid. + */ +export class AssertionError extends FontkitError { + constructor(message: string, options?: ErrorOptions) { + super(FontkitErrorTypes.ASSERTION, message, options); + } +} From 44aa00d38afec9a0d8de94168c76aa7905bb6277 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 21:52:27 +0900 Subject: [PATCH 15/20] chore: minor fix --- src/types.ts | 114 +++++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/types.ts b/src/types.ts index 16d05f69..9fbf1032 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,59 +1,59 @@ -import type TTFFont from './TTFFont.js'; +import type TTFFont from './TTFFont.js'; import type GlyphInfo from './opentype/GlyphInfo.js'; import type ShapingPlan from './opentype/ShapingPlan.js'; - -export type { GlyphInfo, ShapingPlan }; - -/** - * Advanced parameters for `TTFFont#layout` and `LayoutEngine#layout`. - */ -export type LayoutAdvancedParams = { - /** - * If not provided, `fontkit` attempts to detect the script from the string. - */ - script?: string; - - /** - * If not provided, `fontkit` uses the default language of the script. - */ - language?: string; - - /** - * If not provided, `fontkit` uses the default direction of the script. - */ - direction?: 'ltr' | 'rtl'; - - /** - * If not provided, `fontkit` chooses its own prepared `Shaper` based on the script. - */ - shaper?: Shaper; - - /** - * Set to `true` to skip position adjustment for each individual glyph. - * This results in `GlyphRun#positions` being `null`. - */ - skipPerGlyphPositioning?: boolean; -}; - -export interface Shaper { - zeroMarkWidths?: 'NONE' | 'BEFORE_GPOS' | 'AFTER_GPOS'; - - plan( - plan: ShapingPlan, - glyphs: GlyphInfo[], - userFeatures: string[] | Record - ): void; - - assignFeatures(plan: ShapingPlan, glyphs: GlyphInfo[]): void; -} - -/** - * Element of `ShapingPlan#stages` that is either an array of feature tags or a single `ShapingPlanStageFunction`. - */ -export type ShapingPlanStage = string[] | ShapingPlanStageFunction; - -export type ShapingPlanStageFunction = ( - font: TTFFont, - glyphs: GlyphInfo[], - plan: ShapingPlan -) => void; + +export type { GlyphInfo, ShapingPlan }; + +/** + * Advanced parameters for `TTFFont#layout` and `LayoutEngine#layout`. + */ +export type LayoutAdvancedParams = { + /** + * If not provided, `fontkit` attempts to detect the script from the string. + */ + script?: string; + + /** + * If not provided, `fontkit` uses the default language of the script. + */ + language?: string; + + /** + * If not provided, `fontkit` uses the default direction of the script. + */ + direction?: 'ltr' | 'rtl'; + + /** + * If not provided, `fontkit` chooses its own prepared `Shaper` based on the script. + */ + shaper?: Shaper; + + /** + * Set to `true` to skip position adjustment for each individual glyph. + * This results in `GlyphRun#positions` being `null`. + */ + skipPerGlyphPositioning?: boolean; +}; + +export interface Shaper { + zeroMarkWidths?: 'NONE' | 'BEFORE_GPOS' | 'AFTER_GPOS'; + + plan( + plan: ShapingPlan, + glyphs: GlyphInfo[], + userFeatures: string[] | Record + ): void; + + assignFeatures(plan: ShapingPlan, glyphs: GlyphInfo[]): void; +} + +/** + * Element of `ShapingPlan#stages` that is either an array of feature tags or a single `ShapingPlanStageFunction`. + */ +export type ShapingPlanStage = string[] | ShapingPlanStageFunction; + +export type ShapingPlanStageFunction = ( + font: TTFFont, + glyphs: GlyphInfo[], + plan: ShapingPlan +) => void; From 9993f4bad0c7cb4b19e3433b836660e27c374125 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 23:16:16 +0900 Subject: [PATCH 16/20] test: npm install tsx --- package-lock.json | 528 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- 2 files changed, 531 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0d0e6af..77bf8fe5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "npm-run-all": "^4.1.5", "parcel": "2.0.0-canary.1713", "shx": "^0.3.4", + "tsx": "^4.20.6", "typescript": "^5.9.3" } }, @@ -141,6 +142,448 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -3149,6 +3592,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -3339,6 +3824,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -5246,6 +5744,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restructure": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", @@ -5895,6 +6403,26 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", diff --git a/package.json b/package.json index 738b597a..f9d31b5f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ ], "scripts": { "test": "run-s build mocha", - "mocha": "mocha \"test/**/*.js\"", + "mocha": "mocha --node-option import=tsx \"test/**/*.js\"", "build": "run-s build:*", "build:js": "parcel build", "build:types": "tsc --project tsconfig-types.json", @@ -22,7 +22,7 @@ "trie:use": "node src/opentype/shapers/gen-use.js", "trie:indic": "node src/opentype/shapers/gen-indic.js", "clean": "shx rm -rf src/opentype/shapers/data.trie src/opentype/shapers/use.trie src/opentype/shapers/use.json src/opentype/shapers/indic.trie src/opentype/shapers/indic.json dist types", - "coverage": "c8 mocha \"test/**/*.js\"" + "coverage": "c8 mocha --node-option import=tsx \"test/**/*.js\"" }, "type": "module", "main": "dist/main.cjs", @@ -97,6 +97,7 @@ "npm-run-all": "^4.1.5", "parcel": "2.0.0-canary.1713", "shx": "^0.3.4", + "tsx": "^4.20.6", "typescript": "^5.9.3" } } From 225022828af0f97428ec4f64a9f8afa95fd78489 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 23:18:08 +0900 Subject: [PATCH 17/20] fix!: Replace generic `Error`s with new specific `FontkitError` subclasses --- src/CmapProcessor.js | 25 +++++++++++++------------ src/TTFFont.js | 23 ++++++++++++++--------- src/TrueTypeCollection.js | 3 ++- src/aat/AATLookupTable.js | 5 +++-- src/aat/AATMorxProcessor.js | 7 ++++--- src/base.js | 5 ++++- src/cff/CFFDict.js | 3 ++- src/cff/CFFFont.js | 5 +++-- src/cff/CFFIndex.js | 7 ++++--- src/glyph/CFFGlyph.js | 11 ++++++----- src/glyph/GlyphVariationProcessor.js | 4 +++- src/glyph/TTFGlyph.js | 3 ++- src/index.ts | 2 ++ src/layout/KernProcessor.js | 5 +++-- src/node.ts | 2 ++ src/opentype/GPOSProcessor.js | 3 ++- src/opentype/GSUBProcessor.js | 5 +++-- src/opentype/OTLayoutEngine.js | 7 ++++--- src/opentype/OTProcessor.js | 3 ++- src/opentype/ShapingPlan.js | 8 +++++--- src/subset/CFFSubset.js | 3 ++- src/subset/Subset.js | 4 +++- src/utils/clone.js | 4 +++- src/utils/deep-equal.js | 4 +++- test/index.js | 7 +++++-- 25 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/CmapProcessor.js b/src/CmapProcessor.js index 1e9546f6..c5d7d301 100644 --- a/src/CmapProcessor.js +++ b/src/CmapProcessor.js @@ -1,6 +1,7 @@ import { binarySearch, range } from './utils/arrays'; import { encodingExists, getEncoding, getEncodingMapping } from './encodings'; import { cache } from './decorators'; +import { AssertionError, InvalidFontDataError, UnsupportedFontDataError } from './errors'; export default class CmapProcessor { constructor(cmapTable) { @@ -33,7 +34,7 @@ export default class CmapProcessor { } if (!this.cmap) { - throw new Error("Could not find a supported cmap table"); + throw new UnsupportedFontDataError('Could not find a supported cmap table'); } this.uvs = this.findSubtable(cmapTable, [[0, 5]]); @@ -75,7 +76,7 @@ export default class CmapProcessor { case 2: // Microsoft OpenType spec says "This format is not commonly used today." - throw new Error('Unsupported cmap format 2'); + throw new UnsupportedFontDataError('Unsupported cmap format 2'); case 4: { let min = 0; @@ -110,7 +111,7 @@ export default class CmapProcessor { case 8: // TODO: support format 8 - throw new Error('Unsupported cmap format 8'); + throw new UnsupportedFontDataError('Unsupported cmap format 8'); case 6: case 10: @@ -142,10 +143,10 @@ export default class CmapProcessor { case 14: // Format 14 is handled separately via the uvs property - throw new Error('Unexpected cmap format 14'); + throw new AssertionError('Unexpected cmap format 14'); default: - throw new Error(`Unknown cmap format ${cmap.version}`); + throw new InvalidFontDataError(`Unknown cmap format ${cmap.version}`); } } @@ -186,7 +187,7 @@ export default class CmapProcessor { case 2: // Microsoft OpenType spec says "This format is not commonly used today." - throw new Error('Unsupported cmap format 2'); + throw new UnsupportedFontDataError('Unsupported cmap format 2'); case 4: { let res = []; @@ -202,7 +203,7 @@ export default class CmapProcessor { case 8: // TODO: support format 8 - throw new Error('Unsupported cmap format 8'); + throw new UnsupportedFontDataError('Unsupported cmap format 8'); case 6: case 10: @@ -220,10 +221,10 @@ export default class CmapProcessor { case 14: // Format 14 is handled separately via the uvs property - throw new Error('Unexpected cmap format 14'); + throw new AssertionError('Unexpected cmap format 14'); default: - throw new Error(`Unknown cmap format ${cmap.version}`); + throw new InvalidFontDataError(`Unknown cmap format ${cmap.version}`); } } @@ -331,7 +332,7 @@ export default class CmapProcessor { case 6: case 8: case 10: - throw new Error(`Unsupported cmap format ${cmap.version}`); + throw new UnsupportedFontDataError(`Unsupported cmap format ${cmap.version}`); case 12: { let res = []; @@ -357,10 +358,10 @@ export default class CmapProcessor { case 14: // Format 14 is handled separately via the uvs property - throw new Error('Unexpected cmap format 14'); + throw new AssertionError('Unexpected cmap format 14'); default: - throw new Error(`Unknown cmap format ${cmap.version}`); + throw new InvalidFontDataError(`Unknown cmap format ${cmap.version}`); } } } diff --git a/src/TTFFont.js b/src/TTFFont.js index a4eba3a3..aa29dbd0 100644 --- a/src/TTFFont.js +++ b/src/TTFFont.js @@ -14,6 +14,7 @@ import TTFSubset from './subset/TTFSubset'; import CFFSubset from './subset/CFFSubset'; import BBox from './glyph/BBox'; import { asciiDecoder } from './utils/decode'; +import { InvalidCallerInputError } from './errors'; /** * This is the base class for all SFNT-based font formats in fontkit. @@ -102,7 +103,7 @@ export default class TTFFont { } _decodeDirectory() { - return this.directory = Directory.decode(this.stream, {_startOffset: 0}); + return this.directory = Directory.decode(this.stream, { _startOffset: 0 }); } _decodeTable(table) { @@ -125,12 +126,12 @@ export default class TTFFont { if (record) { // Attempt to retrieve the entry, depending on which translation is available: return ( - record[lang] - || record[this.defaultLanguage] - || record[getGlobalDefaultLanguage()] - || record['en'] - || record[Object.keys(record)[0]] // Seriously, ANY language would be fine - || null + record[lang] + || record[this.defaultLanguage] + || record[getGlobalDefaultLanguage()] + || record['en'] + || record[Object.keys(record)[0]] // Seriously, ANY language would be fine + || null ); } @@ -561,7 +562,9 @@ export default class TTFFont { */ getVariation(settings) { if (!(this.directory.tables.fvar && ((this.directory.tables.gvar && this.directory.tables.glyf) || this.directory.tables.CFF2))) { - throw new Error('Variations require a font with the fvar, gvar and glyf, or CFF2 tables.'); + throw new InvalidCallerInputError( + 'Variations require a font with the fvar, gvar and glyf, or CFF2 tables.' + ); } if (typeof settings === 'string') { @@ -569,7 +572,9 @@ export default class TTFFont { } if (typeof settings !== 'object') { - throw new Error('Variation settings must be either a variation name or settings object.'); + throw new InvalidCallerInputError( + 'Variation settings must be either a variation name or settings object.' + ); } // normalize the coordinates diff --git a/src/TrueTypeCollection.js b/src/TrueTypeCollection.js index 08622f03..1a40bc8c 100644 --- a/src/TrueTypeCollection.js +++ b/src/TrueTypeCollection.js @@ -1,6 +1,7 @@ import * as r from 'restructure'; import TTFFont from './TTFFont'; import { asciiDecoder } from './utils/decode'; +import { AssertionError } from './errors'; let TTCHeader = new r.VersionedStruct(r.uint32, { 0x00010000: { @@ -30,7 +31,7 @@ export default class TrueTypeCollection { this.stream = stream; if (stream.readString(4) !== 'ttcf') { // Should be unreachable: probe() verifies the TTC tag before construction. - throw new Error('Not a TrueType collection'); + throw new AssertionError('Not a TrueType collection'); } this.header = TTCHeader.decode(stream); diff --git a/src/aat/AATLookupTable.js b/src/aat/AATLookupTable.js index 636a4166..d0cc9854 100644 --- a/src/aat/AATLookupTable.js +++ b/src/aat/AATLookupTable.js @@ -1,5 +1,6 @@ import {cache} from '../decorators'; import {range} from '../utils/arrays'; +import { InvalidFontDataError } from '../errors'; export default class AATLookupTable { constructor(table) { @@ -70,7 +71,7 @@ export default class AATLookupTable { return this.table.values[glyph - this.table.firstGlyph]; default: - throw new Error(`Unknown lookup table format: ${this.table.version}`); + throw new InvalidFontDataError(`Unknown lookup table format: ${this.table.version}`); } } @@ -117,7 +118,7 @@ export default class AATLookupTable { } default: - throw new Error(`Unknown lookup table format: ${this.table.version}`); + throw new InvalidFontDataError(`Unknown lookup table format: ${this.table.version}`); } return res; diff --git a/src/aat/AATMorxProcessor.js b/src/aat/AATMorxProcessor.js index 7aeedbdd..3cadc69d 100644 --- a/src/aat/AATMorxProcessor.js +++ b/src/aat/AATMorxProcessor.js @@ -1,6 +1,7 @@ import AATStateMachine from './AATStateMachine'; import AATLookupTable from './AATLookupTable'; import {cache} from '../decorators'; +import { InvalidFontDataError, UnsupportedFontDataError } from '../errors'; // indic replacement flags const MARK_FIRST = 0x8000; @@ -122,7 +123,7 @@ export default class AATMorxProcessor { case 5: return this.processGlyphInsertion; default: - throw new Error(`Invalid morx subtable type: ${this.subtable.type}`); + throw new InvalidFontDataError(`Invalid morx subtable type: ${this.subtable.type}`); } } @@ -297,7 +298,7 @@ export default class AATMorxProcessor { let reverse = !!(subtable.coverage & REVERSE_DIRECTION); if (reverse) { - throw new Error('Reverse MORX subtable not supported.'); + throw new UnsupportedFontDataError('Reverse MORX subtable not supported.'); } this.subtable = subtable; @@ -425,6 +426,6 @@ function reorderGlyphs(glyphs, verb, firstGlyph, lastGlyph) { return swap(glyphs, [firstGlyph, 2], [lastGlyph, 2], true, true); default: - throw new Error(`Unknown verb: ${verb}`); + throw new InvalidFontDataError(`Unknown verb: ${verb}`); } } diff --git a/src/base.js b/src/base.js index 38cd2bcc..ebbdd0d9 100644 --- a/src/base.js +++ b/src/base.js @@ -2,6 +2,7 @@ // @ts-ignore import { DecodeStream } from 'restructure'; +import { UnsupportedFontFileFormatError } from './errors'; // ----------------------------------------------------------------------------- @@ -45,7 +46,9 @@ export function create(buffer, postscriptName) { } } - throw new Error('Unsupported font file format: no registered reader recognized the data'); + throw new UnsupportedFontFileFormatError( + 'Unsupported font file format: no registered reader recognized the data' + ); } // ----------------------------------------------------------------------------- diff --git a/src/cff/CFFDict.js b/src/cff/CFFDict.js index 66fc0855..82bb2656 100644 --- a/src/cff/CFFDict.js +++ b/src/cff/CFFDict.js @@ -1,6 +1,7 @@ import CFFOperand from './CFFOperand'; import { PropertyDescriptor } from 'restructure'; import { equalArray } from '../utils/deep-equal'; +import { InvalidFontDataError } from '../errors'; export default class CFFDict { constructor(ops = []) { @@ -73,7 +74,7 @@ export default class CFFDict { let field = this.fields[b]; if (!field) { - throw new Error(`Unknown operator ${b}`); + throw new InvalidFontDataError(`Unknown operator ${b}`); } let val = this.decodeOperands(field[2], stream, ret, operands); diff --git a/src/cff/CFFFont.js b/src/cff/CFFFont.js index c6c6809b..84f48aa8 100644 --- a/src/cff/CFFFont.js +++ b/src/cff/CFFFont.js @@ -3,6 +3,7 @@ import CFFIndex from './CFFIndex'; import CFFTop from './CFFTop'; import CFFPrivateDict from './CFFPrivateDict'; import standardStrings from './CFFStandardStrings'; +import { InvalidFontDataError } from '../errors'; class CFFFont { constructor(stream) { @@ -24,7 +25,7 @@ class CFFFont { if (this.version < 2) { if (this.topDictIndex.length !== 1) { - throw new Error("Only a single font is allowed in CFF"); + throw new InvalidFontDataError('Only a single font is allowed in CFF'); } this.topDict = this.topDictIndex[0]; @@ -134,7 +135,7 @@ class CFFFont { } } default: - throw new Error(`Unknown FDSelect version: ${this.topDict.FDSelect.version}`); + throw new InvalidFontDataError(`Unknown FDSelect version: ${this.topDict.FDSelect.version}`); } } diff --git a/src/cff/CFFIndex.js b/src/cff/CFFIndex.js index 62f4b606..dddcaa15 100644 --- a/src/cff/CFFIndex.js +++ b/src/cff/CFFIndex.js @@ -1,4 +1,5 @@ import * as r from 'restructure'; +import { AssertionError, InvalidFontDataError } from '../errors'; export default class CFFIndex { constructor(type) { @@ -34,7 +35,7 @@ export default class CFFIndex { } else if (offSize === 4) { offsetType = r.uint32; } else { - throw new Error(`Bad offset size in CFFIndex: ${offSize} ${stream.pos}`); + throw new InvalidFontDataError(`Bad offset size in CFFIndex: ${offSize} ${stream.pos}`); } let ret = []; @@ -90,7 +91,7 @@ export default class CFFIndex { } else if (offset <= 0xffffffff) { offsetType = r.uint32; } else { - throw new Error('CFFIndex size overflow'); + throw new AssertionError('CFFIndex size overflow'); } size += 1 + offsetType.size() * (arr.length + 1); @@ -126,7 +127,7 @@ export default class CFFIndex { } else if (offset <= 0xffffffff) { offsetType = r.uint32; } else { - throw new Error('CFFIndex encode offset overflow'); + throw new AssertionError('CFFIndex encode offset overflow'); } // write offset size diff --git a/src/glyph/CFFGlyph.js b/src/glyph/CFFGlyph.js index 1ac182f1..d45f2913 100644 --- a/src/glyph/CFFGlyph.js +++ b/src/glyph/CFFGlyph.js @@ -1,5 +1,6 @@ import Glyph from './Glyph'; import Path from './Path'; +import { InvalidFontDataError } from '../errors'; /** * Represents an OpenType PostScript glyph, in the Compact Font Format. @@ -180,7 +181,7 @@ export default class CFFGlyph extends Glyph { case 15: { // vsindex if (cff.version < 2) { - throw new Error('vsindex operator not supported in CFF v1'); + throw new InvalidFontDataError('vsindex operator not supported in CFF v1'); } vsindex = stack.pop(); @@ -189,11 +190,11 @@ export default class CFFGlyph extends Glyph { case 16: { // blend if (cff.version < 2) { - throw new Error('blend operator not supported in CFF v1'); + throw new InvalidFontDataError('blend operator not supported in CFF v1'); } if (!variationProcessor) { - throw new Error('blend operator in non-variation font'); + throw new InvalidFontDataError('blend operator in non-variation font'); } let blendVector = variationProcessor.getBlendVector(vstore, vsindex); @@ -571,12 +572,12 @@ export default class CFFGlyph extends Glyph { break; default: - throw new Error(`Unknown op: 12 ${op}`); + throw new InvalidFontDataError(`Unknown op: 12 ${op}`); } break; default: - throw new Error(`Unknown op: ${op}`); + throw new InvalidFontDataError(`Unknown op: ${op}`); } } else if (op < 247) { diff --git a/src/glyph/GlyphVariationProcessor.js b/src/glyph/GlyphVariationProcessor.js index 3dca7277..1c19fdae 100644 --- a/src/glyph/GlyphVariationProcessor.js +++ b/src/glyph/GlyphVariationProcessor.js @@ -1,3 +1,5 @@ +import { InvalidFontDataError } from '../errors'; + const TUPLES_SHARE_POINT_NUMBERS = 0x8000; const TUPLE_COUNT_MASK = 0x0fff; const EMBEDDED_TUPLE_COORD = 0x8000; @@ -105,7 +107,7 @@ export default class GlyphVariationProcessor { } else { if ((tupleIndex & TUPLE_INDEX_MASK) >= gvar.globalCoordCount) { - throw new Error('gvar tuple references invalid shared coordinate index'); + throw new InvalidFontDataError('gvar tuple references invalid shared coordinate index'); } var tupleCoords = gvar.globalCoords[tupleIndex & TUPLE_INDEX_MASK]; diff --git a/src/glyph/TTFGlyph.js b/src/glyph/TTFGlyph.js index af8e820a..bb458b69 100644 --- a/src/glyph/TTFGlyph.js +++ b/src/glyph/TTFGlyph.js @@ -2,6 +2,7 @@ import Glyph from './Glyph'; import Path from './Path'; import BBox from './BBox'; import * as r from 'restructure'; +import { InvalidFontDataError } from '../errors'; // The header for both simple and composite glyphs let GlyfHeader = new r.Struct({ @@ -376,7 +377,7 @@ export default class TTFGlyph extends Glyph { var curvePt = null; } else { - throw new Error('Inconsistent on/off-curve sequence in glyf contour'); + throw new InvalidFontDataError('Inconsistent on/off-curve sequence in glyf contour'); } } diff --git a/src/index.ts b/src/index.ts index 04c29161..a5cbf4de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,8 @@ registerFormat(TrueTypeCollection); export * from './base'; export { DefaultShaper } from './base'; // Explicit export for preventing tree-shaking +export * from './errors'; + export type { default as TTFFont } from './TTFFont'; export type { default as TrueTypeCollection } from './TrueTypeCollection'; export type { default as Glyph } from './glyph/Glyph'; diff --git a/src/layout/KernProcessor.js b/src/layout/KernProcessor.js index 075062a6..f284c421 100644 --- a/src/layout/KernProcessor.js +++ b/src/layout/KernProcessor.js @@ -1,5 +1,6 @@ // @ts-check +import { UnsupportedFontDataError } from '../errors'; import { binarySearch } from '../utils/arrays'; export default class KernProcessor { @@ -52,7 +53,7 @@ export default class KernProcessor { break; default: - throw new Error(`Unsupported kerning table version ${table.version}`); + throw new UnsupportedFontDataError(`Unsupported kerning table version ${table.version}`); } let val = 0; @@ -94,7 +95,7 @@ export default class KernProcessor { break; default: - throw new Error(`Unsupported kerning sub-table format ${table.format}`); + throw new UnsupportedFontDataError(`Unsupported kerning sub-table format ${table.format}`); } // Microsoft supports the override flag, which resets the result diff --git a/src/node.ts b/src/node.ts index d34de7e9..34681279 100644 --- a/src/node.ts +++ b/src/node.ts @@ -9,6 +9,8 @@ registerFormat(TrueTypeCollection); export * from './base'; export { DefaultShaper } from './base'; // Explicit export for preventing tree-shaking +export * from './errors'; + export type { default as TTFFont } from './TTFFont'; export type { default as TrueTypeCollection } from './TrueTypeCollection'; export type { default as Glyph } from './glyph/Glyph'; diff --git a/src/opentype/GPOSProcessor.js b/src/opentype/GPOSProcessor.js index d5777fd4..8c704936 100644 --- a/src/opentype/GPOSProcessor.js +++ b/src/opentype/GPOSProcessor.js @@ -1,5 +1,6 @@ import GlyphPosition from '../layout/GlyphPosition'; import OTProcessor from './OTProcessor'; +import { InvalidFontDataError } from '../errors'; /** * Null object for `GlyphPosition`. @@ -278,7 +279,7 @@ export default class GPOSProcessor extends OTProcessor { return this.applyLookup(table.lookupType, table.extension); default: - throw new Error(`Unknown GPOS lookupType: ${lookupType}`); + throw new InvalidFontDataError(`Unknown GPOS lookupType: ${lookupType}`); } } diff --git a/src/opentype/GSUBProcessor.js b/src/opentype/GSUBProcessor.js index 77c9f7bf..eaed2096 100644 --- a/src/opentype/GSUBProcessor.js +++ b/src/opentype/GSUBProcessor.js @@ -1,5 +1,6 @@ import OTProcessor from './OTProcessor'; import GlyphInfo from './GlyphInfo'; +import { InvalidFontDataError, UnsupportedFontDataError } from '../errors'; export default class GSUBProcessor extends OTProcessor { applyLookup(lookupType, table) { @@ -186,10 +187,10 @@ export default class GSUBProcessor extends OTProcessor { return this.applyLookup(table.lookupType, table.extension); case 8: // Reverse Chaining Contextual Single Substitution - throw new Error(`GSUB lookupType 8 is not supported`); + throw new UnsupportedFontDataError('GSUB lookupType 8 is not supported'); default: - throw new Error(`Unknown GSUB lookupType: ${lookupType}`); + throw new InvalidFontDataError(`Unknown GSUB lookupType: ${lookupType}`); } } } diff --git a/src/opentype/OTLayoutEngine.js b/src/opentype/OTLayoutEngine.js index db0ed179..0eb2c160 100644 --- a/src/opentype/OTLayoutEngine.js +++ b/src/opentype/OTLayoutEngine.js @@ -5,6 +5,7 @@ import * as Shapers from './shapers/index'; import GlyphInfo from './GlyphInfo'; import GSUBProcessor from './GSUBProcessor'; import GPOSProcessor from './GPOSProcessor'; +import { AssertionError } from '../errors'; export default class OTLayoutEngine { /** @@ -64,7 +65,7 @@ export default class OTLayoutEngine { substitute(glyphRun) { if (this.glyphInfos == null || this.plan == null) { // Internal guard: shapers invoke substitute() only after setup(). - throw new Error('setup() must be called before substitute()'); + throw new AssertionError('setup() must be called before substitute()'); } if (this.GSUBProcessor) { @@ -82,7 +83,7 @@ export default class OTLayoutEngine { position(glyphRun) { if (this.glyphInfos == null || this.plan == null || this.shaper == null) { // Internal guard: setup() must run before any positioning work. - throw new Error('setup() must be called before position()'); + throw new AssertionError('setup() must be called before position()'); } let appliedFeatures = null; @@ -119,7 +120,7 @@ export default class OTLayoutEngine { zeroMarkAdvances(positions) { if (this.glyphInfos == null) { // Internal guard: zeroing advances only happens after setup(). - throw new Error('setup() must be called before zeroMarkAdvances()'); + throw new AssertionError('setup() must be called before zeroMarkAdvances()'); } for (let i = 0; i < this.glyphInfos.length; i++) { diff --git a/src/opentype/OTProcessor.js b/src/opentype/OTProcessor.js index a186324b..5e5ef397 100644 --- a/src/opentype/OTProcessor.js +++ b/src/opentype/OTProcessor.js @@ -1,5 +1,6 @@ import GlyphIterator from './GlyphIterator'; import * as Script from '../layout/Script'; +import { AssertionError } from '../errors'; const DEFAULT_SCRIPTS = ['DFLT', 'dflt', 'latn']; @@ -225,7 +226,7 @@ export default class OTProcessor { * @abstract */ applyLookup(lookup, table) { - throw new Error('applyLookup must be implemented by subclasses'); + throw new AssertionError('applyLookup must be implemented by subclasses'); } applyLookupList(lookupRecords) { diff --git a/src/opentype/ShapingPlan.js b/src/opentype/ShapingPlan.js index 9717022d..71de4ff2 100644 --- a/src/opentype/ShapingPlan.js +++ b/src/opentype/ShapingPlan.js @@ -1,5 +1,7 @@ // @ts-check +import { AssertionError, InvalidCallerInputError } from '../errors'; + /** * ShapingPlans are used by the OpenType shapers to store which * features should by applied, and in what order to apply them. @@ -51,7 +53,7 @@ export default class ShapingPlan { } } } else { - throw new Error('Invalid data type of stage in ShapingPlan#stages'); + throw new AssertionError('Invalid data type of stage in ShapingPlan#stages'); } } @@ -76,7 +78,7 @@ export default class ShapingPlan { this._addFeatures(arg.global || [], true); this._addFeatures(arg.local || [], false); } else { - throw new Error('Unsupported argument to ShapingPlan#add'); + throw new InvalidCallerInputError('Unsupported argument to ShapingPlan#add'); } } @@ -112,7 +114,7 @@ export default class ShapingPlan { delete this.allFeatures[tag]; delete this.globalFeatures[tag]; } else { - throw new Error('Invalid data type of stage in ShapingPlan#stages'); + throw new AssertionError('Invalid data type of stage in ShapingPlan#stages'); } } } diff --git a/src/subset/CFFSubset.js b/src/subset/CFFSubset.js index 38971ae1..19837794 100644 --- a/src/subset/CFFSubset.js +++ b/src/subset/CFFSubset.js @@ -1,6 +1,7 @@ import Subset from './Subset'; import CFFTop from '../cff/CFFTop'; import standardStrings from '../cff/CFFStandardStrings'; +import { AssertionError } from '../errors'; export default class CFFSubset extends Subset { /** @@ -14,7 +15,7 @@ export default class CFFSubset extends Subset { this.cff = this.font['CFF ']; if (!this.cff) { // Subset constructors are only called after format probing, so this flags an invariant breach. - throw new Error('CFFSubset requires a font with a CFF table'); + throw new AssertionError('CFFSubset requires a font with a CFF table'); } } diff --git a/src/subset/Subset.js b/src/subset/Subset.js index 287d739d..5d0d5704 100644 --- a/src/subset/Subset.js +++ b/src/subset/Subset.js @@ -1,5 +1,7 @@ // @ts-check +import { AssertionError } from '../errors'; + /** * @abstract */ @@ -53,6 +55,6 @@ export default class Subset { * @abstract */ encode() { - throw new Error('Subset.encode() must be overridden by subclasses'); + throw new AssertionError('Subset.encode() must be overridden by subclasses'); } } diff --git a/src/utils/clone.js b/src/utils/clone.js index a7da6ccc..802d1d83 100644 --- a/src/utils/clone.js +++ b/src/utils/clone.js @@ -1,5 +1,6 @@ // @ts-check +import { AssertionError } from '../errors'; import { isPrimitive } from './primitive.js'; /** @@ -41,7 +42,8 @@ function cloneValue(value, seen) { return cloneObject(value, seen); } - throw new TypeError('cloneDeep only supports primitives, arrays, and plain objects'); + // Internal misuse if we reach here + throw new AssertionError('cloneDeep only supports primitives, arrays, and plain objects'); } /** diff --git a/src/utils/deep-equal.js b/src/utils/deep-equal.js index 483df2ce..d0658570 100644 --- a/src/utils/deep-equal.js +++ b/src/utils/deep-equal.js @@ -1,5 +1,6 @@ // @ts-check +import { AssertionError } from '../errors'; import { isPrimitive } from './primitive.js'; /** @@ -46,5 +47,6 @@ export function equalArray(left, right) { } } - throw new TypeError('equalArray only supports primitives and arrays'); + // Internal misuse if we reach here + throw new AssertionError('equalArray only supports primitives and arrays'); } diff --git a/test/index.js b/test/index.js index 73e29390..2b577864 100644 --- a/test/index.js +++ b/test/index.js @@ -34,12 +34,15 @@ describe('fontkit', function () { it('should error when opening an invalid font asynchronously', async function () { assert.rejects( fontkit.open(new URL(import.meta.url)), - 'Unknown font format' + fontkit.UnsupportedFontFileFormatError ); }); it('should error when opening an invalid font synchronously', function () { - assert.throws(() => fontkit.openSync(new URL(import.meta.url)), /Unknown font format/); + assert.throws( + () => fontkit.openSync(new URL(import.meta.url)), + fontkit.UnsupportedFontFileFormatError + ); }); it('should get collection objects for ttc fonts', function () { From 890adc11bb9af1a66a6ef585bfabd96b060fe712 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 23:24:26 +0900 Subject: [PATCH 18/20] =?UTF-8?q?test:=20=E9=9D=9E=E5=90=8C=E6=9C=9F?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=8C=E5=AE=9F=E8=A3=85=E3=83=9F=E3=82=B9=E3=81=A7=E5=B8=B8?= =?UTF-8?q?=E3=81=ABpass=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index 2b577864..73a5ac1e 100644 --- a/test/index.js +++ b/test/index.js @@ -32,7 +32,7 @@ describe('fontkit', function () { }); it('should error when opening an invalid font asynchronously', async function () { - assert.rejects( + await assert.rejects( fontkit.open(new URL(import.meta.url)), fontkit.UnsupportedFontFileFormatError ); From 337f0f1d8b9e8d86ed4995e20a80828412cf5f30 Mon Sep 17 00:00:00 2001 From: FAL Date: Tue, 18 Nov 2025 23:26:27 +0900 Subject: [PATCH 19/20] docs: update changelog --- MODIFICATIONS.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/MODIFICATIONS.md b/MODIFICATIONS.md index d0c1ce41..5c3b1759 100644 --- a/MODIFICATIONS.md +++ b/MODIFICATIONS.md @@ -2,21 +2,33 @@ ## [Unreleased] +### Feature/Dependency Removals + - Remove WOFF format support - Also removing the `tiny-inflate` dependency (the indirect dependency may remain) -- Remove WOFF2 format support - - Also removing the `brotli` dependency -- Remove DFont format support -- Simplify the published TypeScript types by inlining the concrete format exports (`TTFFont`/`TrueTypeCollection`) in place of the old aliases (`Font`/`FontCollection`). -- Remove the dependencies below by replacing them with new internal helpers (no API changes): - - `clone` (used by `TTFSubset`) - - `fast-deep-equal` (used by `CFFDict`) -- Rename table decode logging helpers from `logErrors`/`isLoggingErrors` to `logWarnings`/`isLoggingWarnings` and downgrade emitted messages to warnings - -## [2.0.4-mod.2025.2] +- Remove WOFF2 format support + - Also removing the `brotli` dependency +- Remove DFont format support +- Remove the dependencies below by replacing them with new internal helpers (no API changes): + - `clone` (used by `TTFSubset`) + - `fast-deep-equal` (used by `CFFDict`) + +### Error Improvements + +- Replace generic `Error`s with new specific `FontkitError` subclasses +- Fix some error messages +- Rename table decode logging helpers from `logErrors`/`isLoggingErrors` to `logWarnings`/`isLoggingWarnings` and downgrade emitted messages to warnings + +### Other Changes + +- Simplify the published TypeScript types by inlining the concrete format exports (`TTFFont`/`TrueTypeCollection`) in place of the old aliases (`Font`/`FontCollection`). + + +## [2.0.4-mod.2025.2] - Improve performance of `CmapProcessor#lookupNonDefaultUVS` by caching variation selector records from `cmap` format 14 subtable + ## [2.0.4-mod.2025.1] - Fix glyph mapping using the cmap format 14 subtable, improving support for UVS in methods like `TTFFont#glyphsForString` From 6903faed06e202f490b251f41f16102aa075ec4b Mon Sep 17 00:00:00 2001 From: FAL Date: Wed, 19 Nov 2025 00:01:57 +0900 Subject: [PATCH 20/20] docs: update AGENTS.md --- AGENTS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index abbcff54..4a2201c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,7 @@ - Better Unicode variation selector (UVS) handling - Richer shaping controls - Vertical metrics accuracy + - Better error reporting - Predictable type definitions for consumers ## Repository Orientation @@ -18,6 +19,9 @@ ## Toolchain & Commands - Use Node 20+ and npm (repo ships `package-lock.json`). Install with `npm install`. +- Two TypeScript configs ship with the repo: + - `tsconfig.json` configures editor tooling for the codebase. It enables `allowJs`, `strictNullChecks`, and decorators, but keeps `checkJs` disabled so files opt in via `// @ts-check`. + - `tsconfig-types.json` extends the base config and only emits declarations for `src/index.ts` and `src/node.ts` when running `npm run build:types`. - Fast feedback loop: - `npm run build:js` → Parcel build into `dist/`. - `npm run build:types` → `tsc --project tsconfig-types.json` (follows `src/index.ts` & `src/node.ts`, pulls in JS via `allowJs`).