FontDNA
FontDNA is an ONNX model for recognizing typography from word-image crops.
It returns a font embedding plus word-local typography predictions. Font family is not a fixed classifier: compare the embedding against embeddings of your own reference fonts.
Model I/O
Inputs
imgโfloat32, shape(B, 1, 40, W)colsโint64, shape(B,); real crop width divided by8
Word crops are grayscale, resized to height 40 while preserving aspect ratio. Width is aligned to multiples of 8 (max 320). Scale pixels to [0,1], then normalize each crop with:
x = (x - x.mean()) / (x.std() + 1e-4)
Outputs, in order
| # | Output | Decode as |
|---|---|---|
| 0 | font embedding | L2-normalize, then use cosine similarity |
| 1 | style logits [4] |
sigmoid โ bold, italic, underline, strikethrough |
| 2 | category logits [5] |
softmax โ serif, sans-serif, display, handwriting, monospace |
| 3 | confidence logit | sigmoid |
| 4 | script logits [14] |
softmax โ mapping below |
Script indices:
0 latin 5 hangul 10 tamil
1 cyrillic 6 arabic 11 thai
2 greek 7 hebrew 12 armenian
3 han 8 devanagari 13 georgian
4 kana 9 bengali
Styles are per word. For example, one underlined word can still share the same font-family embedding/section as the surrounding regular words.
Raw ONNX Runtime example
import numpy as np
import onnxruntime as ort
session = ort.InferenceSession("glyphdna.onnx")
# img: preprocessed float32 array shaped (B, 1, 40, W)
# cols: int64 array shaped (B,), where cols[i] = real_width // 8
embedding, style_logits, category_logits, confidence_logits, script_logits = (
session.run(None, {"img": img, "cols": cols})
)
sigmoid = lambda x: 1 / (1 + np.exp(-np.clip(x, -60, 60)))
softmax = lambda x: np.exp(x - x.max(-1, keepdims=True)) / np.exp(
x - x.max(-1, keepdims=True)
).sum(-1, keepdims=True)
embedding = embedding / np.maximum(
np.linalg.norm(embedding, axis=1, keepdims=True), 1e-8
)
styles = sigmoid(style_logits)
categories = softmax(category_logits)
confidence = sigmoid(confidence_logits).reshape(-1)
scripts = softmax(script_logits)
# Example: first word
print("bold:", styles[0, 0])
print("italic:", styles[0, 1])
print("underline:", styles[0, 2])
print("strikethrough:", styles[0, 3])
print("category index:", categories[0].argmax())
print("script index:", scripts[0].argmax())
If your exported ONNX uses different tensor names, inspect them with:
print([x.name for x in session.get_inputs()])
print([x.name for x in session.get_outputs()])
Font matching
To recognize a font family, embed reference word renders from each candidate font with the same model and preprocessing. L2-normalize the vectors and rank candidates by cosine similarity. Combining several neighboring word embeddings is usually more reliable than trusting a single word, because some words contain few font-discriminative glyphs.
A standalone Python library for indexing fonts, segmenting images, clustering same-font sections, and preserving per-word formatting is planned separately and is expected to be released in a few weeks.
Notes
- This model is not OCR and does not transcribe text.
- Exact font names come from your own reference/index collection.
- Bold, italic, underline, strikethrough, category, and script are predicted per word.
- Precompiled font indexes must use the same model weights and preprocessing.
License
MIT.