Ordered stroke-centreline templates for Japanese kana and Han characters, as JSON. Each character is a list of strokes, each stroke a polyline of points, in the order the character is written.
6,333 templates: 153 kana, 2,136 jōyō kanji, the 2,500 most frequent simplified hanzi, 1,011 traditional hanzi, and 533 Korean hanja.
neighbours.json sits beside them: for each character, the eight characters
most like it, nearest first. It is measured from templates.json rather than
authored, so the two are published together and only mean anything together.
This repository exists to satisfy Arphic Public License §2(b), which
requires that modifications to Arphic-licensed material be made "Freely
Available as a whole to all third parties under the terms of this License … by
offering access to copy the modifications from a designated place." This is that
designated place. The data here is free for you to copy, modify and redistribute
under the Arphic Public License — see ARPHICPL.TXT, which §1
requires accompany every copy you make.
Derived from AnimCJK:
AnimCJK project — Copyright 2016–2026 — FM&SH
AnimCJK is itself derived from the Arphic PL KaitiM GB and Arphic PL KaitiM Big5 fonts by Arphic Technology Co., Ltd., and from Make Me a Hanzi.
script is one of:
| Value | |
|---|---|
hiragana, katakana |
Japanese kana |
kanji |
Han, as written in Japan |
simplifiedHanzi |
Han, PRC simplified standard |
traditionalHanzi |
Han, traditional standard |
hanja |
Han, as written in Korea |
The four Han values name a tradition as well as a script, because that is the only thing that distinguishes them. Unicode unifies Han — 学 is one codepoint whether read as kanji or hanzi — so the same character appears once per tradition, with different stroke order in each.
Coordinates sit in a 1024-unit em box in screen orientation — Y increases downward, unlike the font-space source. Strokes are in writing order, and points within a stroke follow the direction the stroke is drawn. Nothing is resampled or normalized; do that at load time so you can pick your own point count.
Filter on script rather than merging across it. 一 appears four times, once
per tradition, and the four disagree about stroke order — which is the point.
Merging them would give a matcher several mutually contradictory templates for
the same character.
Which characters resemble which — ソ against ツ, 未 against 末 — worked out once here so that nothing has to load the templates and compare geometry to find out.
{
"version": 1,
"neighbourCount": 8,
"method": "…stroke order ignored, optimal stroke assignment, 16 resampled points…",
"templates": { "count": 6333, "generated": "2026-08-04", "digest": "fnv1a64:91d4ef56230767de" },
"provenance": { … },
"scripts": [
{
"script": "katakana",
"entries": [
{
"character": "ソ",
"neighbours": ["ツ", "ノ", "メ", "ゾ", …],
"distances": [0.2198, 0.2367, 0.3305, 0.401, …]
}
]
}
]
}neighbours and distances are parallel: distances[i] belongs to
neighbours[i]. They are split rather than paired into an object per neighbour
because the object form more than triples the file, and this is data a phone
parses at launch.
Neighbours never cross scripts — Han is written in a different order in each tradition, so 学 as kanji and 学 as simplified hanzi have separate entries — and they are drawn only from characters whose stroke count is within ±2, which is the window a recognizer would consider anyway. Nothing outside it is a plausible confusion. So a list can be short, and one is empty: 鬱 is 29 strokes and nothing else in the set comes within two of it. That is an answer rather than a gap.
distances are the raw geometric distances the generator measured. They are
comparable to each other within this file and are not probabilities or
similarity percentages; what they are good for is separating "genuinely
confusable" from "merely the closest of a distant field".
templates.digest fingerprints the templates.json these were computed from.
Regenerate or edit the templates and this file no longer describes them —
without the digest there would be no symptom, since every answer it gives still
looks reasonable.
Recorded here and in each file's provenance block, as §2(a) requires.
- Extracted the
medianscentreline polylines; the glyph outlines in AnimCJK'sstrokesfield were discarded. - Dropped duplicate strokes produced by median extraction on self-intersecting strokes. Skeletonizing a stroke that crosses itself branches, so looping characters — あ お の は ま る among them — arrive with one stroke too many. The phantom is identified by the exact shared tail it has with the stroke it duplicates.
- Reflected Y about the em ascender (
y' = 900 - y), converting font coordinates to screen coordinates. - Discarded entries whose codepoint is not a kana or Han character, and strokes of fewer than two points.
- Excluded small kana (ぁぃぅ ァィゥ …), which are the same glyph as their full-size forms at a smaller size and therefore indistinguishable once scale is normalized away.
- Skipped CJK Compatibility Ideographs, which are canonically equivalent to a unified character already present.
- Restricted output to a subset of characters where noted in
provenance. - Derived
neighbours.jsonfrom the finished templates: the eight most similar characters per character within the same script, by the same stroke distance a recognizer uses. It reproduces no glyph geometry — the file is characters and distances — but it is published under this licence with everything else, since being wrong about that in the cautious direction costs nothing.
Arphic Public License — ARPHICPL.TXT.
The generator that produces these files is a separate, independently licensed project. Under §2 of the Arphic PL, identifiable sections not derived from the Font, which can reasonably be considered independent works, fall outside the licence; mere aggregation does not bring them under it. That separation runs one way only — the data in this repository is Arphic PL, and §5 forbids anyone imposing further restrictions on your rights to it.
{ "version": 2, "provenance": { "source": …, "license": …, "generated": …, "modifications": [ … ] }, "templates": [ { "character": "三", "script": "kanji", "strokes": [ [ {"x": 137, "y": 258}, … ], … ] } ] }