openfont 1.0.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,31 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - 'v*'
8
+
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ registry-url: 'https://registry.npmjs.org'
23
+
24
+ - name: Install dependencies
25
+ run: npm ci
26
+
27
+ - name: Build
28
+ run: npm run build
29
+
30
+ - name: Publish to npm
31
+ run: npm publish --provenance --access public
package/README.md ADDED
@@ -0,0 +1,392 @@
1
+ # OpenFont
2
+
3
+ A lightweight, TypeScript-based library for extracting detailed information from font files in the browser and Node.js.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/openfont.svg)](https://www.npmjs.com/package/openfont)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - 🔍 Extract comprehensive font metadata (weight, style, family name, etc.)
11
+ - 🌐 Load fonts from URLs or ArrayBuffer
12
+ - ⚡ Async/await API for seamless integration
13
+ - 📦 Works in both browser and Node.js environments
14
+ - 💪 Full TypeScript support with type definitions
15
+ - 🎨 Perfect for font management tools, design systems, and typography applications
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install openfont
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Load from URL
26
+
27
+ ```typescript
28
+ import Font from 'openfont';
29
+
30
+ const font = new Font('https://example.com/path/to/font.ttf');
31
+ const info = await font.info();
32
+
33
+ console.log(info);
34
+ // {
35
+ // familyName: "Roboto",
36
+ // styleName: "Bold Italic",
37
+ // weight: "Bold",
38
+ // isItalic: true,
39
+ // isBold: true,
40
+ // fullName: "Roboto Bold Italic",
41
+ // postScriptName: "Roboto-BoldItalic",
42
+ // version: "Version 2.137",
43
+ // ...
44
+ // }
45
+ ```
46
+
47
+ ### Load from File Upload
48
+
49
+ ```typescript
50
+ import Font from 'openfont';
51
+
52
+ async function handleFileUpload(file: File) {
53
+ const arrayBuffer = await file.arrayBuffer();
54
+ const font = new Font(arrayBuffer);
55
+
56
+ const weight = await font.getWeight();
57
+ const isItalic = await font.isItalic();
58
+
59
+ console.log(`Weight: ${weight}, Italic: ${isItalic}`);
60
+ }
61
+ ```
62
+
63
+ ## API Reference
64
+
65
+ ### Constructor
66
+
67
+ #### `new Font(fontSource: string | ArrayBuffer)`
68
+
69
+ Creates a new Font instance.
70
+
71
+ - **fontSource**: Either a URL string pointing to a font file, or an ArrayBuffer containing font data
72
+
73
+ ```typescript
74
+ // From URL
75
+ const font1 = new Font('https://fonts.example.com/myfont.ttf');
76
+
77
+ // From ArrayBuffer
78
+ const buffer = await fetch('/font.ttf').then(r => r.arrayBuffer());
79
+ const font2 = new Font(buffer);
80
+ ```
81
+
82
+ ### Methods
83
+
84
+ #### `info(): Promise<FontInfo>`
85
+
86
+ Returns comprehensive information about the font.
87
+
88
+ ```typescript
89
+ const info = await font.info();
90
+ ```
91
+
92
+ **Returns:**
93
+ ```typescript
94
+ {
95
+ familyName: string; // e.g., "Roboto"
96
+ styleName: string; // e.g., "Bold Italic"
97
+ weight: number | string; // e.g., 700 or "Bold"
98
+ isItalic: boolean;
99
+ isBold: boolean;
100
+ fullName: string; // e.g., "Roboto Bold Italic"
101
+ postScriptName: string; // e.g., "Roboto-BoldItalic"
102
+ version: string;
103
+ copyright: string;
104
+ trademark: string;
105
+ manufacturer: string;
106
+ designer: string;
107
+ description: string;
108
+ unitsPerEm: number;
109
+ ascender: number;
110
+ descender: number;
111
+ }
112
+ ```
113
+
114
+ #### `getWeight(): Promise<number | string>`
115
+
116
+ Returns the font weight. Returns a number (100-900) or a string name (e.g., "Bold", "Light").
117
+
118
+ ```typescript
119
+ const weight = await font.getWeight(); // 700 or "Bold"
120
+ ```
121
+
122
+ #### `isItalic(): Promise<boolean>`
123
+
124
+ Checks if the font is italic or oblique.
125
+
126
+ ```typescript
127
+ const italic = await font.isItalic(); // true or false
128
+ ```
129
+
130
+ #### `isBold(): Promise<boolean>`
131
+
132
+ Checks if the font is bold (weight >= 700).
133
+
134
+ ```typescript
135
+ const bold = await font.isBold(); // true or false
136
+ ```
137
+
138
+ #### `getFamilyName(): Promise<string>`
139
+
140
+ Returns the font family name.
141
+
142
+ ```typescript
143
+ const family = await font.getFamilyName(); // "Roboto"
144
+ ```
145
+
146
+ #### `getStyleName(): Promise<string>`
147
+
148
+ Returns the font style name.
149
+
150
+ ```typescript
151
+ const style = await font.getStyleName(); // "Bold Italic"
152
+ ```
153
+
154
+ #### `getFullName(): Promise<string>`
155
+
156
+ Returns the full font name.
157
+
158
+ ```typescript
159
+ const fullName = await font.getFullName(); // "Roboto Bold Italic"
160
+ ```
161
+
162
+ #### `getPostScriptName(): Promise<string>`
163
+
164
+ Returns the PostScript name.
165
+
166
+ ```typescript
167
+ const psName = await font.getPostScriptName(); // "Roboto-BoldItalic"
168
+ ```
169
+
170
+ #### `getVersion(): Promise<string>`
171
+
172
+ Returns the font version string.
173
+
174
+ ```typescript
175
+ const version = await font.getVersion(); // "Version 2.137"
176
+ ```
177
+
178
+ #### `getCopyright(): Promise<string>`
179
+
180
+ Returns the copyright notice.
181
+
182
+ ```typescript
183
+ const copyright = await font.getCopyright();
184
+ ```
185
+
186
+ #### `getDesigner(): Promise<string>`
187
+
188
+ Returns the font designer's name.
189
+
190
+ ```typescript
191
+ const designer = await font.getDesigner();
192
+ ```
193
+
194
+ #### `getRawFont(): opentype.Font | null`
195
+
196
+ Returns the underlying opentype.js Font object for advanced use cases.
197
+
198
+ ```typescript
199
+ const opentypeFont = font.getRawFont();
200
+ ```
201
+
202
+ ## React Example
203
+
204
+ ```typescript
205
+ import React, { useState } from 'react';
206
+ import Font from 'openfont';
207
+
208
+ function FontAnalyzer() {
209
+ const [fontInfo, setFontInfo] = useState(null);
210
+ const [loading, setLoading] = useState(false);
211
+
212
+ const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
213
+ const file = e.target.files?.[0];
214
+ if (!file) return;
215
+
216
+ setLoading(true);
217
+ try {
218
+ const arrayBuffer = await file.arrayBuffer();
219
+ const font = new Font(arrayBuffer);
220
+ const info = await font.info();
221
+ setFontInfo(info);
222
+ } catch (error) {
223
+ console.error('Failed to analyze font:', error);
224
+ } finally {
225
+ setLoading(false);
226
+ }
227
+ };
228
+
229
+ const analyzeWebFont = async () => {
230
+ setLoading(true);
231
+ try {
232
+ const font = new Font('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2');
233
+ const info = await font.info();
234
+ setFontInfo(info);
235
+ } catch (error) {
236
+ console.error('Failed to analyze font:', error);
237
+ } finally {
238
+ setLoading(false);
239
+ }
240
+ };
241
+
242
+ return (
243
+ <div>
244
+ <h1>Font Analyzer</h1>
245
+
246
+ <input
247
+ type="file"
248
+ onChange={handleFileUpload}
249
+ accept=".ttf,.otf,.woff,.woff2"
250
+ disabled={loading}
251
+ />
252
+
253
+ <button onClick={analyzeWebFont} disabled={loading}>
254
+ Analyze Web Font
255
+ </button>
256
+
257
+ {loading && <p>Loading...</p>}
258
+
259
+ {fontInfo && (
260
+ <div>
261
+ <h2>Font Information</h2>
262
+ <table>
263
+ <tbody>
264
+ <tr>
265
+ <td><strong>Family:</strong></td>
266
+ <td>{fontInfo.familyName}</td>
267
+ </tr>
268
+ <tr>
269
+ <td><strong>Style:</strong></td>
270
+ <td>{fontInfo.styleName}</td>
271
+ </tr>
272
+ <tr>
273
+ <td><strong>Weight:</strong></td>
274
+ <td>{fontInfo.weight}</td>
275
+ </tr>
276
+ <tr>
277
+ <td><strong>Italic:</strong></td>
278
+ <td>{fontInfo.isItalic ? 'Yes' : 'No'}</td>
279
+ </tr>
280
+ <tr>
281
+ <td><strong>Bold:</strong></td>
282
+ <td>{fontInfo.isBold ? 'Yes' : 'No'}</td>
283
+ </tr>
284
+ <tr>
285
+ <td><strong>Designer:</strong></td>
286
+ <td>{fontInfo.designer}</td>
287
+ </tr>
288
+ <tr>
289
+ <td><strong>Version:</strong></td>
290
+ <td>{fontInfo.version}</td>
291
+ </tr>
292
+ </tbody>
293
+ </table>
294
+ </div>
295
+ )}
296
+ </div>
297
+ );
298
+ }
299
+
300
+ export default FontAnalyzer;
301
+ ```
302
+
303
+ ## Node.js Example
304
+
305
+ ```typescript
306
+ import Font from 'openfont';
307
+ import fs from 'fs/promises';
308
+
309
+ async function analyzeFontFile(filePath: string) {
310
+ const buffer = await fs.readFile(filePath);
311
+ const font = new Font(buffer.buffer);
312
+
313
+ const info = await font.info();
314
+ console.log('Font Family:', info.familyName);
315
+ console.log('Weight:', info.weight);
316
+ console.log('Is Italic:', info.isItalic);
317
+ console.log('Designer:', info.designer);
318
+ }
319
+
320
+ analyzeFontFile('./fonts/MyFont.ttf');
321
+ ```
322
+
323
+ ## Supported Font Formats
324
+
325
+ - TrueType (.ttf)
326
+ - OpenType (.otf)
327
+ - WOFF (.woff)
328
+ - WOFF2 (.woff2)
329
+
330
+ ## Browser Compatibility
331
+
332
+ OpenFont works in all modern browsers that support ES2020 and the Fetch API:
333
+
334
+ - Chrome 80+
335
+ - Firefox 75+
336
+ - Safari 14+
337
+ - Edge 80+
338
+
339
+ ## TypeScript Support
340
+
341
+ OpenFont is written in TypeScript and includes full type definitions out of the box. No need for `@types` packages.
342
+
343
+ ```typescript
344
+ import Font, { FontInfo } from 'openfont';
345
+
346
+ const font = new Font('path/to/font.ttf');
347
+ const info: FontInfo = await font.info();
348
+ ```
349
+
350
+ ## Error Handling
351
+
352
+ All async methods can throw errors. Wrap them in try-catch blocks:
353
+
354
+ ```typescript
355
+ try {
356
+ const font = new Font('https://example.com/font.ttf');
357
+ const info = await font.info();
358
+ } catch (error) {
359
+ console.error('Failed to load or parse font:', error);
360
+ }
361
+ ```
362
+
363
+ ## Use Cases
364
+
365
+ - **Design Systems**: Automatically catalog and validate fonts in your design system
366
+ - **Font Managers**: Build font browsing and management applications
367
+ - **Typography Tools**: Create tools for font analysis and comparison
368
+ - **Web Font Optimization**: Analyze web fonts to optimize loading strategies
369
+ - **Font Validation**: Verify font files meet specific requirements
370
+ - **Documentation**: Auto-generate font specimen sheets
371
+
372
+ ## How It Works
373
+
374
+ OpenFont is built on top of [opentype.js](https://github.com/opentypejs/opentype.js), a powerful font parser. It provides a simplified, promise-based API for common font analysis tasks.
375
+
376
+ ## Contributing
377
+
378
+ Contributions are welcome! Please feel free to submit a Pull Request.
379
+
380
+ ## License
381
+
382
+ MIT © [Miles Low](https://github.com/mileslow)
383
+
384
+ ## Links
385
+
386
+ - [GitHub Repository](https://github.com/mileslow/OpenFont)
387
+ - [npm Package](https://www.npmjs.com/package/openfont)
388
+ - [Report Issues](https://github.com/mileslow/OpenFont/issues)
389
+
390
+ ## Acknowledgments
391
+
392
+ Built with [opentype.js](https://github.com/opentypejs/opentype.js) - a powerful JavaScript parser and writer for TrueType and OpenType fonts.
@@ -0,0 +1,41 @@
1
+ import opentype from 'opentype.js';
2
+ export interface FontInfo {
3
+ familyName: string;
4
+ styleName: string;
5
+ weight: number | string;
6
+ isItalic: boolean;
7
+ isBold: boolean;
8
+ fullName: string;
9
+ postScriptName: string;
10
+ version: string;
11
+ copyright: string;
12
+ trademark: string;
13
+ manufacturer: string;
14
+ designer: string;
15
+ description: string;
16
+ unitsPerEm: number;
17
+ ascender: number;
18
+ descender: number;
19
+ }
20
+ export declare class Font {
21
+ private font;
22
+ private loadPromise;
23
+ constructor(fontSource: string | ArrayBuffer);
24
+ private loadFromUrl;
25
+ private loadFromBuffer;
26
+ private ensureLoaded;
27
+ private getNameEntry;
28
+ info(): Promise<FontInfo>;
29
+ getWeight(): Promise<number | string>;
30
+ isItalic(): Promise<boolean>;
31
+ isBold(): Promise<boolean>;
32
+ getFamilyName(): Promise<string>;
33
+ getStyleName(): Promise<string>;
34
+ getFullName(): Promise<string>;
35
+ getPostScriptName(): Promise<string>;
36
+ getVersion(): Promise<string>;
37
+ getCopyright(): Promise<string>;
38
+ getDesigner(): Promise<string>;
39
+ getRawFont(): opentype.Font | null;
40
+ }
41
+ export default Font;
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Font = void 0;
7
+ const opentype_js_1 = __importDefault(require("opentype.js"));
8
+ class Font {
9
+ constructor(fontSource) {
10
+ this.font = null;
11
+ this.loadPromise = null;
12
+ if (typeof fontSource === 'string') {
13
+ this.loadPromise = this.loadFromUrl(fontSource);
14
+ }
15
+ else {
16
+ this.loadFromBuffer(fontSource);
17
+ }
18
+ }
19
+ async loadFromUrl(url) {
20
+ return new Promise((resolve, reject) => {
21
+ opentype_js_1.default.load(url, (err, font) => {
22
+ if (err) {
23
+ reject(new Error(`Failed to load font: ${err.message}`));
24
+ }
25
+ else if (font) {
26
+ this.font = font;
27
+ resolve();
28
+ }
29
+ else {
30
+ reject(new Error('Failed to load font: Unknown error'));
31
+ }
32
+ });
33
+ });
34
+ }
35
+ loadFromBuffer(buffer) {
36
+ this.font = opentype_js_1.default.parse(buffer);
37
+ }
38
+ async ensureLoaded() {
39
+ if (this.loadPromise) {
40
+ await this.loadPromise;
41
+ }
42
+ if (!this.font) {
43
+ throw new Error('Font not loaded');
44
+ }
45
+ }
46
+ getNameEntry(nameId) {
47
+ if (!this.font)
48
+ return '';
49
+ const names = this.font.names;
50
+ const nameRecord = names[nameId.toString()];
51
+ return nameRecord?.en || '';
52
+ }
53
+ async info() {
54
+ await this.ensureLoaded();
55
+ if (!this.font) {
56
+ throw new Error('Font not loaded');
57
+ }
58
+ const weight = await this.getWeight();
59
+ const isItalic = await this.isItalic();
60
+ return {
61
+ familyName: this.getNameEntry(1) || '',
62
+ styleName: this.getNameEntry(2) || '',
63
+ weight,
64
+ isItalic,
65
+ isBold: await this.isBold(),
66
+ fullName: this.getNameEntry(4) || '',
67
+ postScriptName: this.getNameEntry(6) || '',
68
+ version: this.getNameEntry(5) || '',
69
+ copyright: this.getNameEntry(0) || '',
70
+ trademark: this.getNameEntry(7) || '',
71
+ manufacturer: this.getNameEntry(8) || '',
72
+ designer: this.getNameEntry(9) || '',
73
+ description: this.getNameEntry(10) || '',
74
+ unitsPerEm: this.font.unitsPerEm,
75
+ ascender: this.font.ascender,
76
+ descender: this.font.descender
77
+ };
78
+ }
79
+ async getWeight() {
80
+ await this.ensureLoaded();
81
+ if (!this.font) {
82
+ throw new Error('Font not loaded');
83
+ }
84
+ const os2Table = this.font.tables.os2;
85
+ if (os2Table && 'usWeightClass' in os2Table) {
86
+ const weightValue = os2Table.usWeightClass;
87
+ const weightNames = {
88
+ 100: 'Thin',
89
+ 200: 'Extra Light',
90
+ 300: 'Light',
91
+ 400: 'Normal',
92
+ 500: 'Medium',
93
+ 600: 'Semi Bold',
94
+ 700: 'Bold',
95
+ 800: 'Extra Bold',
96
+ 900: 'Black'
97
+ };
98
+ return weightNames[weightValue] || weightValue;
99
+ }
100
+ const styleName = this.getNameEntry(2).toLowerCase();
101
+ if (styleName.includes('bold'))
102
+ return 700;
103
+ if (styleName.includes('light'))
104
+ return 300;
105
+ if (styleName.includes('black'))
106
+ return 900;
107
+ return 400;
108
+ }
109
+ async isItalic() {
110
+ await this.ensureLoaded();
111
+ if (!this.font) {
112
+ throw new Error('Font not loaded');
113
+ }
114
+ const post = this.font.tables.post;
115
+ if (post && 'italicAngle' in post) {
116
+ const angle = post.italicAngle;
117
+ if (angle !== 0)
118
+ return true;
119
+ }
120
+ const os2Table = this.font.tables.os2;
121
+ if (os2Table && 'fsSelection' in os2Table) {
122
+ const fsSelection = os2Table.fsSelection;
123
+ if (fsSelection & 0x01)
124
+ return true;
125
+ }
126
+ const styleName = this.getNameEntry(2).toLowerCase();
127
+ return styleName.includes('italic') || styleName.includes('oblique');
128
+ }
129
+ async isBold() {
130
+ await this.ensureLoaded();
131
+ const weight = await this.getWeight();
132
+ const weightValue = typeof weight === 'number' ? weight : 400;
133
+ return weightValue >= 700;
134
+ }
135
+ async getFamilyName() {
136
+ await this.ensureLoaded();
137
+ return this.getNameEntry(1);
138
+ }
139
+ async getStyleName() {
140
+ await this.ensureLoaded();
141
+ return this.getNameEntry(2);
142
+ }
143
+ async getFullName() {
144
+ await this.ensureLoaded();
145
+ return this.getNameEntry(4);
146
+ }
147
+ async getPostScriptName() {
148
+ await this.ensureLoaded();
149
+ return this.getNameEntry(6);
150
+ }
151
+ async getVersion() {
152
+ await this.ensureLoaded();
153
+ return this.getNameEntry(5);
154
+ }
155
+ async getCopyright() {
156
+ await this.ensureLoaded();
157
+ return this.getNameEntry(0);
158
+ }
159
+ async getDesigner() {
160
+ await this.ensureLoaded();
161
+ return this.getNameEntry(9);
162
+ }
163
+ getRawFont() {
164
+ return this.font;
165
+ }
166
+ }
167
+ exports.Font = Font;
168
+ exports.default = Font;
package/example.ts ADDED
@@ -0,0 +1,81 @@
1
+ import Font from './src/index';
2
+
3
+ // Example 1: Load from URL (in browser/React)
4
+ async function analyzeFromUrl() {
5
+ const font = new Font('https://example.com/path/to/font.ttf');
6
+
7
+ const info = await font.info();
8
+ console.log('Font Info:', info);
9
+ // {
10
+ // familyName: "Roboto",
11
+ // styleName: "Bold Italic",
12
+ // weight: "Bold" or 700,
13
+ // isItalic: true,
14
+ // isBold: true,
15
+ // fullName: "Roboto Bold Italic",
16
+ // ...
17
+ // }
18
+
19
+ const weight = await font.getWeight();
20
+ console.log('Weight:', weight); // 700 or "Bold"
21
+
22
+ const isItalic = await font.isItalic();
23
+ console.log('Is Italic:', isItalic); // true
24
+
25
+ const isBold = await font.isBold();
26
+ console.log('Is Bold:', isBold); // true
27
+ }
28
+
29
+ // Example 2: Load from ArrayBuffer (e.g., from file upload)
30
+ async function analyzeFromFile(file: File) {
31
+ const arrayBuffer = await file.arrayBuffer();
32
+ const font = new Font(arrayBuffer);
33
+
34
+ const info = await font.info();
35
+ console.log('Font Info:', info);
36
+ }
37
+
38
+ // Example 3: React component usage
39
+ /*
40
+ import React, { useState } from 'react';
41
+ import Font from 'openfont';
42
+
43
+ function FontAnalyzer() {
44
+ const [fontInfo, setFontInfo] = useState(null);
45
+
46
+ const handleFileUpload = async (e) => {
47
+ const file = e.target.files[0];
48
+ if (file) {
49
+ const arrayBuffer = await file.arrayBuffer();
50
+ const font = new Font(arrayBuffer);
51
+ const info = await font.info();
52
+ setFontInfo(info);
53
+ }
54
+ };
55
+
56
+ const analyzeRemoteFont = async () => {
57
+ const font = new Font('https://example.com/font.ttf');
58
+ const info = await font.info();
59
+ setFontInfo(info);
60
+ };
61
+
62
+ return (
63
+ <div>
64
+ <input type="file" onChange={handleFileUpload} accept=".ttf,.otf,.woff,.woff2" />
65
+ <button onClick={analyzeRemoteFont}>Analyze Remote Font</button>
66
+
67
+ {fontInfo && (
68
+ <div>
69
+ <h3>Font Information</h3>
70
+ <p>Family: {fontInfo.familyName}</p>
71
+ <p>Weight: {fontInfo.weight}</p>
72
+ <p>Italic: {fontInfo.isItalic ? 'Yes' : 'No'}</p>
73
+ <p>Bold: {fontInfo.isBold ? 'Yes' : 'No'}</p>
74
+ </div>
75
+ )}
76
+ </div>
77
+ );
78
+ }
79
+ */
80
+
81
+ export { analyzeFromUrl, analyzeFromFile };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "openfont",
3
+ "version": "1.0.0",
4
+ "description": "A library for extracting font information from font files",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "font",
13
+ "opentype",
14
+ "truetype",
15
+ "typography",
16
+ "font-parser"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "opentype.js": "^1.3.4"
22
+ },
23
+ "devDependencies": {
24
+ "@types/opentype.js": "^1.3.8",
25
+ "typescript": "^5.3.3"
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,205 @@
1
+ import opentype from 'opentype.js';
2
+
3
+ export interface FontInfo {
4
+ familyName: string;
5
+ styleName: string;
6
+ weight: number | string;
7
+ isItalic: boolean;
8
+ isBold: boolean;
9
+ fullName: string;
10
+ postScriptName: string;
11
+ version: string;
12
+ copyright: string;
13
+ trademark: string;
14
+ manufacturer: string;
15
+ designer: string;
16
+ description: string;
17
+ unitsPerEm: number;
18
+ ascender: number;
19
+ descender: number;
20
+ }
21
+
22
+ export class Font {
23
+ private font: opentype.Font | null = null;
24
+ private loadPromise: Promise<void> | null = null;
25
+
26
+ constructor(fontSource: string | ArrayBuffer) {
27
+ if (typeof fontSource === 'string') {
28
+ this.loadPromise = this.loadFromUrl(fontSource);
29
+ } else {
30
+ this.loadFromBuffer(fontSource);
31
+ }
32
+ }
33
+
34
+ private async loadFromUrl(url: string): Promise<void> {
35
+ return new Promise((resolve, reject) => {
36
+ opentype.load(url, (err, font) => {
37
+ if (err) {
38
+ reject(new Error(`Failed to load font: ${err.message}`));
39
+ } else if (font) {
40
+ this.font = font;
41
+ resolve();
42
+ } else {
43
+ reject(new Error('Failed to load font: Unknown error'));
44
+ }
45
+ });
46
+ });
47
+ }
48
+
49
+ private loadFromBuffer(buffer: ArrayBuffer): void {
50
+ this.font = opentype.parse(buffer);
51
+ }
52
+
53
+ private async ensureLoaded(): Promise<void> {
54
+ if (this.loadPromise) {
55
+ await this.loadPromise;
56
+ }
57
+ if (!this.font) {
58
+ throw new Error('Font not loaded');
59
+ }
60
+ }
61
+
62
+ private getNameEntry(nameId: number): string {
63
+ if (!this.font) return '';
64
+ const names = this.font.names;
65
+ const nameRecord = (names as unknown as Record<string, { en?: string }>)[nameId.toString()];
66
+ return nameRecord?.en || '';
67
+ }
68
+
69
+ async info(): Promise<FontInfo> {
70
+ await this.ensureLoaded();
71
+
72
+ if (!this.font) {
73
+ throw new Error('Font not loaded');
74
+ }
75
+
76
+ const weight = await this.getWeight();
77
+ const isItalic = await this.isItalic();
78
+
79
+ return {
80
+ familyName: this.getNameEntry(1) || '',
81
+ styleName: this.getNameEntry(2) || '',
82
+ weight,
83
+ isItalic,
84
+ isBold: await this.isBold(),
85
+ fullName: this.getNameEntry(4) || '',
86
+ postScriptName: this.getNameEntry(6) || '',
87
+ version: this.getNameEntry(5) || '',
88
+ copyright: this.getNameEntry(0) || '',
89
+ trademark: this.getNameEntry(7) || '',
90
+ manufacturer: this.getNameEntry(8) || '',
91
+ designer: this.getNameEntry(9) || '',
92
+ description: this.getNameEntry(10) || '',
93
+ unitsPerEm: this.font.unitsPerEm,
94
+ ascender: this.font.ascender,
95
+ descender: this.font.descender
96
+ };
97
+ }
98
+
99
+ async getWeight(): Promise<number | string> {
100
+ await this.ensureLoaded();
101
+
102
+ if (!this.font) {
103
+ throw new Error('Font not loaded');
104
+ }
105
+
106
+ const os2Table = this.font.tables.os2;
107
+ if (os2Table && 'usWeightClass' in os2Table) {
108
+ const weightValue = os2Table.usWeightClass as number;
109
+
110
+ const weightNames: Record<number, string> = {
111
+ 100: 'Thin',
112
+ 200: 'Extra Light',
113
+ 300: 'Light',
114
+ 400: 'Normal',
115
+ 500: 'Medium',
116
+ 600: 'Semi Bold',
117
+ 700: 'Bold',
118
+ 800: 'Extra Bold',
119
+ 900: 'Black'
120
+ };
121
+
122
+ return weightNames[weightValue] || weightValue;
123
+ }
124
+
125
+ const styleName = this.getNameEntry(2).toLowerCase();
126
+ if (styleName.includes('bold')) return 700;
127
+ if (styleName.includes('light')) return 300;
128
+ if (styleName.includes('black')) return 900;
129
+
130
+ return 400;
131
+ }
132
+
133
+ async isItalic(): Promise<boolean> {
134
+ await this.ensureLoaded();
135
+
136
+ if (!this.font) {
137
+ throw new Error('Font not loaded');
138
+ }
139
+
140
+ const post = this.font.tables.post;
141
+ if (post && 'italicAngle' in post) {
142
+ const angle = post.italicAngle as number;
143
+ if (angle !== 0) return true;
144
+ }
145
+
146
+ const os2Table = this.font.tables.os2;
147
+ if (os2Table && 'fsSelection' in os2Table) {
148
+ const fsSelection = os2Table.fsSelection as number;
149
+ if (fsSelection & 0x01) return true;
150
+ }
151
+
152
+ const styleName = this.getNameEntry(2).toLowerCase();
153
+ return styleName.includes('italic') || styleName.includes('oblique');
154
+ }
155
+
156
+ async isBold(): Promise<boolean> {
157
+ await this.ensureLoaded();
158
+
159
+ const weight = await this.getWeight();
160
+ const weightValue = typeof weight === 'number' ? weight : 400;
161
+
162
+ return weightValue >= 700;
163
+ }
164
+
165
+ async getFamilyName(): Promise<string> {
166
+ await this.ensureLoaded();
167
+ return this.getNameEntry(1);
168
+ }
169
+
170
+ async getStyleName(): Promise<string> {
171
+ await this.ensureLoaded();
172
+ return this.getNameEntry(2);
173
+ }
174
+
175
+ async getFullName(): Promise<string> {
176
+ await this.ensureLoaded();
177
+ return this.getNameEntry(4);
178
+ }
179
+
180
+ async getPostScriptName(): Promise<string> {
181
+ await this.ensureLoaded();
182
+ return this.getNameEntry(6);
183
+ }
184
+
185
+ async getVersion(): Promise<string> {
186
+ await this.ensureLoaded();
187
+ return this.getNameEntry(5);
188
+ }
189
+
190
+ async getCopyright(): Promise<string> {
191
+ await this.ensureLoaded();
192
+ return this.getNameEntry(0);
193
+ }
194
+
195
+ async getDesigner(): Promise<string> {
196
+ await this.ensureLoaded();
197
+ return this.getNameEntry(9);
198
+ }
199
+
200
+ getRawFont(): opentype.Font | null {
201
+ return this.font;
202
+ }
203
+ }
204
+
205
+ export default Font;
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020", "DOM"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "moduleResolution": "node",
14
+ "resolveJsonModule": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }