font-probe 0.1.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/font-probe.d.ts +48 -0
- package/dist/font-probe.d.ts.map +1 -0
- package/dist/font-probe.js +188 -0
- package/dist/font-probe.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# font-probe
|
|
2
|
+
|
|
3
|
+
Browser font probing utility that uses canvas typography signatures to estimate whether a requested font is actually rendering.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install font-probe
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { FontProbe } from "font-probe";
|
|
15
|
+
|
|
16
|
+
new FontProbe();
|
|
17
|
+
|
|
18
|
+
const stack = 'Inter, "Segoe UI", Arial, sans-serif';
|
|
19
|
+
const candidates = FontProbe.splitFontFamilyList(stack);
|
|
20
|
+
|
|
21
|
+
for (const name of candidates) {
|
|
22
|
+
const signal = FontProbe.getFontDistinctSignal(name);
|
|
23
|
+
console.log(name, signal.label);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Possible labels:
|
|
28
|
+
|
|
29
|
+
- `available`
|
|
30
|
+
- `generic`
|
|
31
|
+
- `not rendering`
|
|
32
|
+
- `not found`
|
|
33
|
+
|
|
34
|
+
## Ready example
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run example
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then open the URL printed by `http-server` (usually `http://127.0.0.1:8080`) and use `index.html`.
|
|
41
|
+
|
|
42
|
+
Do not open `index.html` directly with `file://` from desktop: browsers block module loading there and you'll see CORS/module errors.
|
|
43
|
+
|
|
44
|
+
If using VS Code Live Server:
|
|
45
|
+
|
|
46
|
+
1. Run `npm run build` first (so `dist/` exists).
|
|
47
|
+
2. Start Live Server from the project root.
|
|
48
|
+
3. Open `/index.html` via `http://127.0.0.1:<port>/index.html`.
|
|
49
|
+
|
|
50
|
+
The example page:
|
|
51
|
+
|
|
52
|
+
- accepts a comma-separated list of font names,
|
|
53
|
+
- applies that full list to preview text,
|
|
54
|
+
- probes every candidate with `FontProbe`,
|
|
55
|
+
- groups output into `available` (with text samples) and `others` (`generic`, `not rendering`, `not found`).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare class FontProbe {
|
|
2
|
+
private static readonly FONT_ONE_VARIANTS;
|
|
3
|
+
private static readonly FONT_TWO_VARIANTS;
|
|
4
|
+
private static readonly METRIC_SAMPLE;
|
|
5
|
+
private static readonly GENERIC_FONT_FAMILIES;
|
|
6
|
+
static measureCanvas: HTMLCanvasElement | null;
|
|
7
|
+
static font1: {
|
|
8
|
+
name: string;
|
|
9
|
+
xHeight: number;
|
|
10
|
+
capHeight: number;
|
|
11
|
+
emWidth: number;
|
|
12
|
+
normalWidth: number;
|
|
13
|
+
} | null;
|
|
14
|
+
static font2: {
|
|
15
|
+
name: string;
|
|
16
|
+
xHeight: number;
|
|
17
|
+
capHeight: number;
|
|
18
|
+
emWidth: number;
|
|
19
|
+
normalWidth: number;
|
|
20
|
+
} | null;
|
|
21
|
+
constructor();
|
|
22
|
+
static getMeasureContext(): CanvasRenderingContext2D | null;
|
|
23
|
+
/** Finding 2 distinct fonts that are not fallbacks and override each other if the order is swapped */
|
|
24
|
+
private defineCheckerFonts;
|
|
25
|
+
static getFontDistinctSignal(fontName: string): {
|
|
26
|
+
label: string;
|
|
27
|
+
className: string;
|
|
28
|
+
};
|
|
29
|
+
/** If in both cases returned signature is the same, and we know that checker fonts are different
|
|
30
|
+
* then we know that the engine didn't fall back to the checker fonts and fontName is rendering.
|
|
31
|
+
*/
|
|
32
|
+
static runDualBaselineFontTest(fontName: string): boolean;
|
|
33
|
+
static measureTypographySignature(context: CanvasRenderingContext2D, fontFamily: string): {
|
|
34
|
+
xHeight: number;
|
|
35
|
+
capHeight: number;
|
|
36
|
+
emWidth: number;
|
|
37
|
+
normalWidth: number;
|
|
38
|
+
} | null;
|
|
39
|
+
private static areSignaturesClose;
|
|
40
|
+
static measureTextMetrics(context: CanvasRenderingContext2D, fontFamily: string, sample: string): {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
} | null;
|
|
44
|
+
static splitFontFamilyList(fontStack: string): string[];
|
|
45
|
+
static cleanFontCandidate(candidate: string): string;
|
|
46
|
+
static checkFontAvailability(fontCandidate: string): boolean | null;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=font-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-probe.d.ts","sourceRoot":"","sources":["../font-probe.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAS;IAElB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAmE;IAC5G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAyE;IAClH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAKnC;IACF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAc1C;IAEH,OAAc,aAAa,EAAE,iBAAiB,GAAG,IAAI,CAAQ;IAC7D,OAAc,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAC9H,OAAc,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;;WAMhH,iBAAiB,IAAI,wBAAwB,GAAG,IAAI;IAOlE,sGAAsG;IACtG,OAAO,CAAC,kBAAkB;WA0BZ,qBAAqB,CAC/B,QAAQ,EAAE,MAAM,GACjB;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;KAAE;IAkBxC;;MAEE;WACY,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;WAkBlD,0BAA0B,CACpC,OAAO,EAAE,wBAAwB,EACjC,UAAU,EAAE,MAAM,GACnB;QACC,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACvB,GAAG,IAAI;IAuBR,OAAO,CAAC,MAAM,CAAC,kBAAkB;WAmBnB,kBAAkB,CAC5B,OAAO,EAAE,wBAAwB,EACjC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;WAsB7B,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;WAsChD,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;WAI7C,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;CAc7E"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
export class FontProbe {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.defineCheckerFonts();
|
|
4
|
+
}
|
|
5
|
+
static getMeasureContext() {
|
|
6
|
+
if (!FontProbe.measureCanvas) {
|
|
7
|
+
FontProbe.measureCanvas = document.createElement("canvas");
|
|
8
|
+
}
|
|
9
|
+
return FontProbe.measureCanvas.getContext("2d");
|
|
10
|
+
}
|
|
11
|
+
/** Finding 2 distinct fonts that are not fallbacks and override each other if the order is swapped */
|
|
12
|
+
defineCheckerFonts() {
|
|
13
|
+
const fonts_one = FontProbe.FONT_ONE_VARIANTS;
|
|
14
|
+
const fonts_two = FontProbe.FONT_TWO_VARIANTS;
|
|
15
|
+
const context = FontProbe.getMeasureContext();
|
|
16
|
+
if (!context) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < fonts_one.length; i++) {
|
|
20
|
+
const fontName1 = FontProbe.cleanFontCandidate(fonts_one[i]);
|
|
21
|
+
for (let j = 0; j < fonts_two.length; j++) {
|
|
22
|
+
const fontName2 = FontProbe.cleanFontCandidate(fonts_two[j]);
|
|
23
|
+
const signature1 = FontProbe.measureTypographySignature(context, `"${fontName1}", "${fontName2}"`);
|
|
24
|
+
const signature2 = FontProbe.measureTypographySignature(context, `"${fontName2}", "${fontName1}"`);
|
|
25
|
+
if (signature1 && signature2 && !FontProbe.areSignaturesClose(signature1, signature2)) {
|
|
26
|
+
FontProbe.font1 = { name: fontName1, ...signature1 };
|
|
27
|
+
FontProbe.font2 = { name: fontName2, ...signature2 };
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (FontProbe.font1 && FontProbe.font2) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static getFontDistinctSignal(fontName) {
|
|
37
|
+
// Generic is not the name of the font
|
|
38
|
+
if (FontProbe.GENERIC_FONT_FAMILIES.has(fontName.toLowerCase())) {
|
|
39
|
+
return { label: "generic", className: "is-generic" };
|
|
40
|
+
}
|
|
41
|
+
// In case it works in some environments or will work in the future
|
|
42
|
+
const available = FontProbe.checkFontAvailability(fontName);
|
|
43
|
+
if (available === false) {
|
|
44
|
+
return { label: "not found", className: "is-missing" };
|
|
45
|
+
}
|
|
46
|
+
if (FontProbe.runDualBaselineFontTest(fontName)) {
|
|
47
|
+
return { label: "available", className: "is-distinct" };
|
|
48
|
+
}
|
|
49
|
+
return { label: "not rendering", className: "is-unknown" };
|
|
50
|
+
}
|
|
51
|
+
/** If in both cases returned signature is the same, and we know that checker fonts are different
|
|
52
|
+
* then we know that the engine didn't fall back to the checker fonts and fontName is rendering.
|
|
53
|
+
*/
|
|
54
|
+
static runDualBaselineFontTest(fontName) {
|
|
55
|
+
const context = FontProbe.getMeasureContext();
|
|
56
|
+
if (!context || !this.font1 || !this.font2) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const testOne = FontProbe.measureTypographySignature(context, `"${fontName}", ${this.font1.name}`);
|
|
60
|
+
const testTwo = FontProbe.measureTypographySignature(context, `"${fontName}", ${this.font2.name}`);
|
|
61
|
+
return FontProbe.areSignaturesClose(testOne, testTwo);
|
|
62
|
+
}
|
|
63
|
+
static measureTypographySignature(context, fontFamily) {
|
|
64
|
+
const xMetrics = this.measureTextMetrics(context, fontFamily, FontProbe.METRIC_SAMPLE.xHeight);
|
|
65
|
+
const capMetrics = this.measureTextMetrics(context, fontFamily, FontProbe.METRIC_SAMPLE.capHeight);
|
|
66
|
+
const emMetrics = this.measureTextMetrics(context, fontFamily, FontProbe.METRIC_SAMPLE.emWidth);
|
|
67
|
+
const normalMetrics = this.measureTextMetrics(context, fontFamily, FontProbe.METRIC_SAMPLE.normalWidth);
|
|
68
|
+
if (!xMetrics || !capMetrics || !emMetrics || !normalMetrics) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const xCount = FontProbe.METRIC_SAMPLE.xHeight.length;
|
|
72
|
+
const capCount = FontProbe.METRIC_SAMPLE.capHeight.length;
|
|
73
|
+
const emCount = FontProbe.METRIC_SAMPLE.emWidth.length;
|
|
74
|
+
const normalCount = FontProbe.METRIC_SAMPLE.normalWidth.length;
|
|
75
|
+
return {
|
|
76
|
+
xHeight: xMetrics.height,
|
|
77
|
+
capHeight: capMetrics.height,
|
|
78
|
+
emWidth: emMetrics.width / emCount,
|
|
79
|
+
normalWidth: normalMetrics.width / normalCount,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
static areSignaturesClose(first, second) {
|
|
83
|
+
if (!first || !second) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const maxHeightBase = Math.max(1, second.capHeight);
|
|
87
|
+
const maxWidthBase = Math.max(1, second.emWidth);
|
|
88
|
+
const xHeightDelta = Math.abs(first.xHeight - second.xHeight) / maxHeightBase;
|
|
89
|
+
const capHeightDelta = Math.abs(first.capHeight - second.capHeight) / maxHeightBase;
|
|
90
|
+
const emWidthDelta = Math.abs(first.emWidth - second.emWidth) / maxWidthBase;
|
|
91
|
+
const normalWidthDelta = Math.abs(first.normalWidth - second.normalWidth) / maxWidthBase;
|
|
92
|
+
const aggregateDelta = (xHeightDelta + capHeightDelta + emWidthDelta + normalWidthDelta) / 4;
|
|
93
|
+
return aggregateDelta <= 0.02;
|
|
94
|
+
}
|
|
95
|
+
static measureTextMetrics(context, fontFamily, sample) {
|
|
96
|
+
context.font = `32px ${fontFamily}`;
|
|
97
|
+
const metrics = context.measureText(sample);
|
|
98
|
+
const ascent = Number.isFinite(metrics.actualBoundingBoxAscent)
|
|
99
|
+
? metrics.actualBoundingBoxAscent
|
|
100
|
+
: 0;
|
|
101
|
+
const descent = Number.isFinite(metrics.actualBoundingBoxDescent)
|
|
102
|
+
? metrics.actualBoundingBoxDescent
|
|
103
|
+
: 0;
|
|
104
|
+
const height = Math.max(1, ascent + descent);
|
|
105
|
+
if (!Number.isFinite(metrics.width) || metrics.width <= 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
width: metrics.width,
|
|
110
|
+
height,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
static splitFontFamilyList(fontStack) {
|
|
114
|
+
if (!fontStack.trim()) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const parts = [];
|
|
118
|
+
let current = "";
|
|
119
|
+
let quote = null;
|
|
120
|
+
for (let index = 0; index < fontStack.length; index++) {
|
|
121
|
+
const character = fontStack[index];
|
|
122
|
+
if ((character === '"' || character === "'") && (!quote || quote === character)) {
|
|
123
|
+
quote = quote ? null : character;
|
|
124
|
+
current += character;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (character === "," && !quote) {
|
|
128
|
+
const cleaned = this.cleanFontCandidate(current);
|
|
129
|
+
if (cleaned) {
|
|
130
|
+
parts.push(cleaned);
|
|
131
|
+
}
|
|
132
|
+
current = "";
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
current += character;
|
|
136
|
+
}
|
|
137
|
+
const cleaned = this.cleanFontCandidate(current);
|
|
138
|
+
if (cleaned) {
|
|
139
|
+
parts.push(cleaned);
|
|
140
|
+
}
|
|
141
|
+
return parts;
|
|
142
|
+
}
|
|
143
|
+
static cleanFontCandidate(candidate) {
|
|
144
|
+
return candidate.trim().replace(/^['"]|['"]$/g, "").trim();
|
|
145
|
+
}
|
|
146
|
+
static checkFontAvailability(fontCandidate) {
|
|
147
|
+
if (!document.fonts?.check) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const normalized = FontProbe.cleanFontCandidate(fontCandidate);
|
|
151
|
+
if (!normalized) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (FontProbe.GENERIC_FONT_FAMILIES.has(normalized.toLowerCase())) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
const escaped = normalized.replace(/"/g, '\\"');
|
|
158
|
+
return document.fonts.check(`16px "${escaped}"`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Baseline fonts should be commonly available, but measurably distinct, we need a pair to compare against to detect fallbacks
|
|
162
|
+
FontProbe.FONT_ONE_VARIANTS = ["Arial", "Verdana", "Times New Roman", "Palatino", "Helvetica"];
|
|
163
|
+
FontProbe.FONT_TWO_VARIANTS = ["Courier New", "Courier", "Lucida Console", "Lucida Sans Typewriter"];
|
|
164
|
+
FontProbe.METRIC_SAMPLE = {
|
|
165
|
+
xHeight: "xxxxxxxxxxxx",
|
|
166
|
+
capHeight: "XXXXXXXXXXXX",
|
|
167
|
+
emWidth: "mmmmmmmmmmmm",
|
|
168
|
+
normalWidth: "nnnnnnnnnnnn",
|
|
169
|
+
};
|
|
170
|
+
FontProbe.GENERIC_FONT_FAMILIES = new Set([
|
|
171
|
+
"serif",
|
|
172
|
+
"sans-serif",
|
|
173
|
+
"monospace",
|
|
174
|
+
"cursive",
|
|
175
|
+
"fantasy",
|
|
176
|
+
"system-ui",
|
|
177
|
+
"emoji",
|
|
178
|
+
"math",
|
|
179
|
+
"fangsong",
|
|
180
|
+
"ui-serif",
|
|
181
|
+
"ui-sans-serif",
|
|
182
|
+
"ui-monospace",
|
|
183
|
+
"ui-rounded",
|
|
184
|
+
]);
|
|
185
|
+
FontProbe.measureCanvas = null;
|
|
186
|
+
FontProbe.font1 = null;
|
|
187
|
+
FontProbe.font2 = null;
|
|
188
|
+
//# sourceMappingURL=font-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-probe.js","sourceRoot":"","sources":["../font-probe.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "font-probe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser font availability probe using canvas typography signatures.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"example": "npm run build && npx --yes http-server . -c-1",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"font",
|
|
28
|
+
"font-detection",
|
|
29
|
+
"canvas",
|
|
30
|
+
"typography",
|
|
31
|
+
"browser"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.9.2"
|
|
37
|
+
}
|
|
38
|
+
}
|