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.
- package/.github/workflows/publish.yml +31 -0
- package/README.md +392 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +168 -0
- package/example.ts +81 -0
- package/package.json +27 -0
- package/src/index.ts +205 -0
- package/tsconfig.json +18 -0
|
@@ -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
|
+
[](https://www.npmjs.com/package/openfont)
|
|
6
|
+
[](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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|