Lightweight CJK glyph geometric coherence validator for Python.
A strict, pre-processing gateway that checks character geometry for structural coherence before data is passed downstream. Deterministic — no LLM, no network, no linguistic processing. Pure NumPy / OpenCV / Pillow mathematics.
For each CJK character (U+4E00–U+9FFF) found in an input string:
- Rasterize — renders the character to a 128×128 binary matrix via a reference font (Noto Sans CJK or equivalent).
- Transform — applies horizontal mirror, vertical mirror, and 180° rotation.
- Interference — computes
cv2.absdiff(original, transform)for each pair. - Evaluate — checks stroke density, tofu-box detection, connected-component count, and interference statistics against strict geometric thresholds.
Execution halts at the first anomalous character. Input is never forwarded downstream.
pip install glyph-validatorRequires a local CJK font (NotoSansCJK recommended):
# Debian/Ubuntu
sudo apt install fonts-noto-cjk
# macOS — PingFang.ttc is bundled with the OS
# Windows — msyh.ttc (Microsoft YaHei) is bundled with the OSfrom glyph_validator import validate_payload
# No CJK — immediate pass
validate_payload("Hello world")
# → {"status": "pass", "code": 200}
# Valid CJK ideographs
validate_payload("中文汉字")
# → {"status": "pass", "code": 200}
# Anomalous character (font fallback / missing glyph / degenerate geometry)
validate_payload("鿿")
# → {"status": "fail", "code": 403, "flagged_hex": "U+9FFF"}Evaluates each character against parametric coherence bounds:
| Check | What it catches |
|---|---|
Stroke density [0.03, 0.60] |
Empty renders, solid blocks |
Border fraction < 0.40 |
Missing-glyph tofu rectangles |
Component count ≤ 32 |
Noisy / fragmented renders |
Interference floor 0.002 |
Implausible tri-axis symmetry (circle fallbacks) |
Interference ceiling 0.48 |
Degenerate stroke geometry |
Build a reference profile once from your trusted font installation:
python3 -m glyph_validator --build-profiles profiles.npz
# renders all 20,992 CJK characters → ~8 s → 685 KB compressedLoad at runtime for pixel-exact comparison:
from glyph_validator import load_profiles, validate_payload
load_profiles("profiles.npz") # switches to strict mode automatically
validate_payload("中文") # → pass (matches stored fingerprints)
validate_payload("鿿") # → fail (zero-digest entry)Any pixel-level deviation from the reference — caused by font file substitution, render-parameter tampering, or injected fallback glyphs — triggers an immediate fail.
# Single string
glyph-validator "input text"
# Build reference profiles (one-time)
glyph-validator --build-profiles profiles.npz
# Strict mode
glyph-validator --profiles profiles.npz "input text"
# Batch mode — one JSON object per line in, one result per line out
echo '{"id": "r1", "text": "中文"}' | glyph-validator --batch
# Strict + batch
cat requests.jsonl | glyph-validator --profiles profiles.npz --batchBatch input/output format:
{"id": "r1", "text": "中文测试"}
{"id": "r2", "text": "Hello world"}{"status": "pass", "code": 200, "id": "r1"}
{"status": "pass", "code": 200, "id": "r2"}from glyph_validator import validate_payload, load_profiles, build_reference_profiles
# Core gateway — returns dict, never raises on bad input
result = validate_payload(input_string: str) -> dict
# {"status": "pass", "code": 200}
# {"status": "fail", "code": 403, "flagged_hex": "U+XXXX"}
# Strict mode setup
build_reference_profiles(output_path: str, verbose: bool = True) -> dict
load_profiles(profile_path: str) -> NoneEdit the module-level constants before importing:
import glyph_validator
glyph_validator.FONT_PATH = "/path/to/your/CJK.ttf"
glyph_validator.FONT_INDEX = 0 # TTC collection index
glyph_validator.FONT_SIZE = 96 # render point sizeDefault search order: FONT_PATH → NotoSansCJK fallbacks → NotoSerifCJK →
PingFang.ttc (macOS) → msyh.ttc (Windows).
numpy >= 1.21opencv-python-headless >= 4.5Pillow >= 8.0
No network access. No LLM layers. No external linguistic resources.
MIT — see LICENSE.