Find the font in an image, in your browser. Crop a line of text from a screenshot or a design and get the closest font families, with weight and style, from Google Fonts and other free catalogs.
- Runs on the user's device, on WebGPU or the CPU: no server, no API key, the image is never uploaded.
- Names the face, not only the family: Bold Italic, Light and so on.
- Searches 2,004 Google Fonts families, every weight and italic, or one of seven other catalogs: DaFont, Adobe Fonts, GitHub font repositories, Debian, Font Library, WhatFontIs and Other, 102,726 distinct families in all.
- Kinds of one family with identical letters fold into one row, in every catalog: IBM Plex Sans KR and IBM Plex Sans Arabic show under IBM Plex Sans, Khmer OS Fasthand with Khmer OS Freehand. A different font that borrows the letters, like Parastoo with Lora's Latin, keeps its own row.
- Finds a family by name anywhere in the ranking, at its rank for the crop.
It is experimental: on rendered crops of every Google Fonts family, on text it never trained on, the right family is in the top five 93% of the time and first 73%.
npm install gpu-fontimport { createMatcher } from 'gpu-font'
const matcher = await createMatcher() // Google Fonts
// imageData: a crop of text, such as canvas.getContext('2d').getImageData(x, y, width, height)
const [best] = await matcher.match(imageData)
best.face.family // 'Playfair Display'
best.face.styleName // 'Regular'
best.score // 0.87match returns families best first, or [] when the image holds no text. A crop of several lines is read line by line; a line that is not already two-tone is read as it stands and cleaned (its ink found by colour and drawn black on white), and a line set steeply on a slant is read turned level as well; each family keeps its best reading. Each match has:
face: the closest face:family,styleName,weight(100–900),style(normaloritalic), andsourceUrloutside Google Fonts.score: similarity from -1 to 1, higher is closer.siblings: names of same-named families with the same letters, folded into this one.
matcher.threshold is a score below which the crop's font is probably in no catalog, when the model ships one; it is null now. Whether a photographed font is in the catalog turned out not to be readable from its match (measured): the closest design comes back either way, and a low score means a far design, not an absent one.
matcher.destroy() releases the GPU.
The crop's own words rank better than any reference line. createReader() loads a 156 KB line reader; rerank sets the first 30 matches in a text, drawn by a function you supply for the faces you can load, and sorts them by the catalog score plus the match in that text (measured: first place 26% → 44% on photographs):
import { createMatcher, createReader, rerank } from 'gpu-font'
const matcher = await createMatcher(), reader = await createReader()
const matches = await matcher.match(imageData)
const text = reader.read(imageData)?.text ?? '' // 'HUGGING'; or let the person type it
const ranked = await rerank(matches, text, { embedding: await matcher.embed(imageData), encoder: matcher.encoder, draw })
// draw(match, text, size) → ImageData of the match's face drawing `text` at `size` px, or null when you cannot load that faceWithout a bundler, import it from a CDN:
<script type="module">
import { createMatcher } from 'https://cdn.jsdelivr.net/npm/gpu-font@0.4/src/match.mjs'
</script>In Node, pass raw RGBA pixels, for example from sharp:
import sharp from 'sharp'
const { data, info } = await sharp('crop.png').ensureAlpha().raw().toBuffer({ resolveWithObject: true })
const matches = await matcher.match({ data, width: info.width, height: info.height })A matcher searches one catalog. A catalog holds font names, links and style vectors, not font files: get the fonts from their source.
import { createMatcher, catalogs } from 'gpu-font'
const matcher = await createMatcher(catalogs.debian)catalogs. |
Families | |
|---|---|---|
google-fonts (default) |
2,004 | Google Fonts |
debian |
2,165 | Debian's font packages |
fontlibrary |
1,221 | Font Library |
dafont |
96,212 | DaFont, from its preview images: one face a family, weight and style unknown |
adobe-fonts |
4,784 | Adobe Fonts, from its specimen pages: one face a family |
github |
3,564 | Openly licensed font repositories on GitHub, from their files |
whatfontis |
226 | WhatFontIs, from its own renders: the fonts of its benchmark no other catalog holds, one face a family |
pangram-pangram |
201 | Pangram Pangram, from its font files, by the foundry's permission: every cut and its variable twin, weight and style known; the page draws each face from the web fonts the foundry's site serves, for its preview and the second stage |
other |
576 | Sources under 100 families: Uncut, Use & Modify, Velvetyne, Fontshare, Fontsource, Bye Bye Binary, Collletttivo, Tunera, Latin Modern, TeX Gyre, Paper Mono and more |
DaFont, Adobe Fonts and WhatFontIs are indexed from the images their sites show, not from font files, and ship as names, links and style vectors, the first two by the maintainer's decision, to which Adobe has since said by email it has no issues; Pangram Pangram ships the same way from its files, under the permission it gave to train on its library. Their terms are recorded in bench/foundries.json, and any of these catalogs comes down at its source's request.
A catalog built for this model also works from its URL: createMatcher('https://example.com/my-catalog.json'). To build one from your own font files, index them on the Catalogs page and press Download JSON.
A catalog fits only the model that built it: createMatcher refuses one whose encoderSha256 differs. Each new model comes in a new minor version (0.5, 0.6…) with the shipped catalogs rebuilt, and the page always runs the latest. npm's default range, ^0.4.0, stays within 0.4, so your catalog keeps working until you upgrade; then build it again.
Everything the matcher needs ships in the package: the model, the catalogs and the modules, read from the package's own files, so createMatcher() works with no network once installed, in Node or bundled. The page needs the network only for previews, which it draws with fonts from Google Fonts or shows as small pictures, stored with the site or, for DaFont, loaded from DaFont; matching itself runs in the browser and sends nothing.
The page's address holds what is on show. A font sample is its font, ?sample=, and its text, ?text=; any other source is ?style=, the crop's style, the numbers the model reads from it, never its pixels; ?catalog= is the catalog searched. Copy the address, or press Link under the matches, to share them. A link made with another model says so over the page's own sample.
?catalog= takes all, google-fonts, dafont, adobe-fonts, github, debian, fontlibrary, other, my-fonts, or one source inside Other, such as collletttivo or fontshare. With embed, the page shows the matcher alone, searching only that catalog, to put on your own site:
<iframe src="https://dy.github.io/gpu-font/?catalog=collletttivo&embed" allow="clipboard-write" style="width: 100%; height: 960px; border: 0"></iframe>On rendered crops of 5–10 characters from every Google Fonts family, on text the model never trained on (method and full results):
| Right family in the top five | 93% |
| Right family first | 73% |
| Top five: Latin, other scripts, Chinese | 94%, 85%, 68% |
| Top five at 16 px, at 32 px and larger | 88%, 95% |
| Weight of the matched face, 100–900 | off by 6 on average |
| Italic or upright | 99.8% right |
Families the model never trained on are found more often than Google's own: 751 held-out open families, searched with all nine catalogs at once (110,953 families) and scored on the exact family, 74% top five, against 71% for Google Fonts families under the same search (other catalogs).
On photographs of one word printed or painted on real surfaces (WhatFontIs-Bench), searched across all nine catalogs (111,000 families), 597 of the set's 600 fonts indexed: 49% top five and 68% top twenty on the 8,955 images of 448 fonts the model never trained on, 26% first, each crop read as the page reads it: line by line, each line as it stands and cleaned, a steep line turned level as well; WhatFontIs itself reads 93% on the set, which was built from its catalogue. On DaFont forum requests, cropped by hand, 48% top five, with two thirds of the forum's confirmed answers indexed. On lines cut from the top websites' homepages, labelled by the font the browser drew them with and never trained on, 29% top five, 40% for web fonts.
On 5,546 real "what font is this?" requests from the DaFont forum, held out from training (forum threads): 18.7% top five and 10.5% first, with seven answers in ten DaFont fonts the catalog knows by one preview line.
On Pangram Pangram's own 667 product pictures (photos.md), the design is in the first ten for 63% of whole pictures and 62% of their largest lines, from 11% and 17%: the catalogs hold every named weight and style of a variable font and a cover line "Aa" for every face, and a picture's lines are read one by one.
With the crop's own words (reader.md): on 400 of those photographs, held-out Google families, the page's second stage lifts first place from 31% to 46% and the top five from 56% to 72%, the words read by a 156 KB line reader (character error 11%) or typed.
Not yet: hand-drawn letters, icons, symbols and emoji, and matching capitals when a catalog holds only lowercase (57% top five).
createMatcher()downloads about 1.2 MB, compressed: the model (6-bit weights, 0.34 MB) and the Google Fonts catalog (4-bit vectors, 0.88 MB). DaFont's catalog adds 5.8 MB, GitHub 1.0 MB, Adobe Fonts 0.84 MB, Debian 0.37 MB, Font Library 0.32 MB, Pangram Pangram 0.31 MB, Other 0.18 MB and WhatFontIs 0.03 MB; the page's All downloads them all, about 9.7 MB.- With WebGPU, a match takes 0.02 to 0.06 s by the machine's load. Reading a photograph's lines as they stand and cleaned adds about 0.2 s; a screenshot, two-tone, about 0.05. Without it, it runs on the CPU: about 1.1 s in Chromium and 1.1–1.7 s in Node. All measured on an Apple M4 Max.
- The line reader is 0.16 MB compressed and reads a line in about 0.07 s on the CPU; setting the first 30 matches in its text takes about 0.24 s on WebGPU once their fonts are loaded. Without WebGPU the page shows the words read but keeps the catalog's order: on the CPU the thirty faces at three sizes would take half a minute.
Running the demo, training the model and building catalogs: development.md.
gpu-lexer, gpu-time, gpu-query.
MIT. This work is also offered in the spirit described by the Krishnized license, which does not alter the MIT terms.
