Why LLMs Are Good at Writing Code: A Complete Chinese Font Generation Workflow
This article explains where a large language model's coding strength actually comes from, then uses Chinese font generation as a concrete case study: building a usable font from a 20-year-old handwritten letter that yielded only 75 distinct characters, validating the pipeline on those 75 first, and then scaling to a 146-glyph first release and eventually 1,048 glyphs. It covers what actually lives inside a font file, why Chinese is an order of magnitude harder than Latin, a seven-step sample-to-font pipeline with example audit and component-reuse code, a checklist of common pitfalls, and where human judgment must stay in the loop.
1. First, the question: where does the model’s coding ability actually come from?
To understand why a model can take on font generation — a job that is mostly engineering — you have to look at the source of its capability instead of attributing everything to “a lot of parameters.”
1. Code is the most structured form of human expression. Natural language tolerates ambiguity; code does not. A function name, a parameter order, a boundary condition check — each of these is verifiable as right or wrong. The more structured an expression is, the easier it is to extract reusable patterns from large samples.
2. “Correct” can be verified objectively. Code has compilers, tests, and linters, so correctness is not decided by debate. That means training can incorporate large amounts of execution feedback: whether a snippet runs and whether tests pass is itself a high-quality signal. By comparison, “is this paragraph well written?” is very hard to score automatically.
3. Engineering work contains a lot of templated repetition. Reading and writing files, coordinate transforms, batch processing, format conversion, scaffolding and test code — these tasks have clear objectives, well-trodden paths, and are easy to compose. What models are good at is precisely “combining known patterns into something that runs.”
4. The capability boundary is just as clear. Aesthetic judgment, specification trade-offs, and long chains of state management (fine-tuning several hundred glyphs one by one) are not its strengths. The model delivers engineering throughput; decision rights and acceptance rights stay with people.
The case below draws that line — what the model is good at versus what only a human can do — unusually cleanly.
2. The scenario: from 75 handwritten characters to 1,048 glyphs
The sample was a handwritten letter from twenty years ago. After removing duplicates, only 75 distinct characters remained — but the goal was not to reproduce those 75 characters. It was to produce a font that could actually be used for typesetting. The task therefore split naturally into two phases: first drive the full loop (sample → outline → loadable font file) on 75 glyphs to prove the process works, then fill in common characters incrementally — a 146-glyph first version, and eventually 1,048 glyphs in total.
Get the loop working first, then scale horizontally. That is the only reliable order for this kind of work. Do it the other way around — pile up volume first — and every specification error gets amplified a thousandfold.
3. What is actually stored in a font file
Many people assume a font is just “a collection of pictures of characters.” The real structure is far more involved.
Outlines: vector descriptions, not pixels. Each glyph is drawn with a series of Bézier curves (quadratic or cubic, depending on the format), which is why it stays sharp at any size. Inside and outside contour direction distinguishes filled areas from counters (holes), and if the direction is reversed you get the unmistakable “hole filled in” artifact.
The em box: an invisible coordinate grid. Every glyph sits inside an invisible square, commonly divided into 1,000 or 2,048 units. How much of the box the strokes actually occupy (the ink ratio), how much space is left on each side, where the baseline sits, and where the next character starts — all of it is quantified in these units. Inconsistent ink ratios are the number-one cause of text that looks unevenly sized.
Layout rules: behavior written into the file. A font is not only a set of shapes; it also carries typographic features: commas and periods must move to the upper-right corner in vertical writing; a character that repeats consecutively may be allowed to switch to an alternate form; punctuation compression and line-breaking (no line may start with certain marks) rules and so on. These are interpreted by the operating system’s layout and text-shaping engines, not reimplemented by each application.
Strict validation: one bad spot and the whole file is rejected. A font file is organized as a set of internal data tables (outlines, metrics, encoding mappings, typographic features), and they constrain each other tightly. Any single non-conforming spot can make many applications refuse to load the entire file. So every generation round must pass format validation, then actually typeset a horizontal and a vertical sample with a layout engine and confirm the rendering visually.
4. Where the difficulty of Chinese fonts comes from
A completely different order of magnitude. A Latin font needs roughly a hundred glyphs — upper and lowercase, digits, common punctuation. Chinese is another scale entirely: GB 2312 covers 6,763 characters (3,755 in its first-level set), and the 1988 List of Frequently Used Characters in Modern Chinese concluded that at least 2,500 common characters are needed to cover roughly 97.97% of the corpus.
Component position determines form. The same 口 narrows on the left side of 叶, shrinks on top of 品, and must fill the frame when it is the outer box of 回. Move a component and its proportions, stroke lengths, and angles all have to change. That means you cannot simply reuse one component outline — you need explicit, structured deformation rules.
Ink distribution, optical center, and stroke weight rely on the trained eye. Consistency of the central area (the internal “cavity” left by the strokes), the vertical position of the visual center of gravity, and stroke weight is visual judgment that professional designers accumulate over years. Inspecting and adjusting several thousand glyphs one by one is why a Chinese font normally takes one to two years.
Handwriting fonts add a contradiction. They must look like they were written by the same person, on the same day, with the same pen: slight variation in size between characters reads as “handwritten,” but once stroke weight, slant angle, or connecting-stroke habits stop matching, the inconsistency is obvious as soon as the characters are set as a paragraph.
5. The seven-step pipeline from sample to font
Step 0 — Define the character set and acceptance criteria. Decide how many characters this version covers, which character set it targets, and what “accepted” means (loads correctly, renders correctly, visually consistent). The boundary of the character set determines all the work that follows.
Step 1 — Sample capture and cleaning. Scans and photos always carry background texture, paper creases, and uneven lighting. You need grayscale conversion, denoising, contrast stretching, and binarization to get clean per-character bitmaps. The quality of this step directly determines whether vectorization is usable later.
Step 2 — Character segmentation and encoding mapping. Use connected-component analysis together with projection-based splitting to isolate individual characters. The hard parts are connecting strokes and characters that touch each other — mis-segment one character and everything downstream is wrong. Every segmented glyph must be checked by a human and bound to the correct character encoding.
Step 3 — Vectorization. Extract contours, fit Bézier curves, and simplify redundant control points. Too many control points bloat the file and make curves wobble; too few lose stroke characteristics. This is the step that needs the most parameter tuning in the whole pipeline.
Step 4 — Metric normalization. Unify the baseline, the em box, and the unit system; align by optical center rather than by the geometric bounding box; choose component scaling ratios by structural type (left-right, top-bottom, enclosing); bring ink ratios into the target range. Handwritten samples are never positioned consistently, so normalization has to be modeled explicitly.
Step 5 — Consistency audit. Use statistics to find glyphs that deviate from the group: median stroke weight, slant angle, ink ratio, control-point count. Anything that is an outlier on any of these goes back to Step 3.
Step 6 — Typographic features and rendering regression. Add the layout rules (vertical punctuation and the rest), run format validation, then render horizontal and vertical test text with a layout engine and check mixed scripts, ligatures, and punctuation placement.
Step 7 — Versioning and incremental expansion. Keep every round’s output under version control, record the character-set range and known issues, then expand in batches. Growing to 1,048 glyphs was essentially repeating Steps 1–6, with scripts and statistics driving the marginal cost down.
6. Example code: turning “the trained eye” into measurable metrics
The most valuable thing the model produces in this pipeline is not the curve of any single glyph. It is scripts that turn a designer’s tacit judgment into checks that can be run in bulk.
# Illustrative pseudocode (language-agnostic): glyph consistency and quality auditdef audit_glyphs(font):report = {}for name, glyph in font.glyphs():contours = glyph.contours() # set of contoursbbox = glyph.bounding_box() # actual bounding box of the glyphem = font.units_per_em # em box units (e.g. 1000)widths = estimate_stroke_width(contours) # estimated width of each strokereport[name] = {"fill_ratio": bbox.area() / (em * em), # ink ratio"median_stroke": median(widths), # median stroke weight"stroke_std": stdev(widths), # weight variance (connection-stroke risk)"tilt_deg": estimate_tilt(contours), # dominant slant angle"n_points": total_control_points(contours),"self_intersect": has_self_intersection(contours),}return reportdef find_outliers(report, key, z=2.5):"""Find glyphs that deviate from the overall distribution on one metric, for human review"""values = {k: v[key] for k, v in report.items()}mu, sd = mean(values.values()), stdev(values.values())return [k for k, v in values.items() if sd and abs(v - mu) / sd > z]
The usage is straightforward. First pull one round of outliers by median_stroke and another by tilt_deg, and hand the candidate list to a human for visual inspection. Then use fill_ratio to equalize apparent size. self_intersect and reversed contour direction must be caught by the script, because these defects are often invisible to the eye yet will make the font fail to load.
Component reuse needs explicit rules rather than letting the model “guess” proportions:
# Illustrative pseudocode: apply different deformation rules to components by structural typeRULES = {"left_right": {"left": {"sx": 0.88, "sy": 1.00}, # narrow the left component"right": {"sx": 1.00, "sy": 1.00}},"top_bottom": {"top": {"sx": 0.92, "sy": 0.95}, # slightly shrink the top component"bottom":{"sx": 1.00, "sy": 1.00}},"surround": {"frame": {"sx": 1.00, "sy": 1.00}, # outer frame fills the em box"inner": {"sx": 0.85, "sy": 0.85}},}def compose(component, slot, structure):sx, sy = RULES[structure][slot].values()return transform(component, scale=(sx, sy), anchor=align_to_center_of_em())
The rules themselves come from the design specification. The model’s job is to implement them as code that can be run in bulk and verified by regression — which is exactly the part current models are best at.
7. Common pitfalls
- Reversed contour direction: counters get filled in. Format validation may not complain, but the rendering is clearly wrong.
- Inconsistent unit systems: mixing glyphs that came from sources with different
units_per_emvalues breaks the metrics across the board. - Redundant control points: the file balloons and the curves show subtle wobble.
- Aligning to the geometric bounding box: square and round characters end up visually different in size; you have to correct by optical center and ink ratio.
- Broken encoding mapping: the glyph is drawn correctly but bound to the wrong code point, which shows up the first time someone types it.
- Missing typographic features: horizontal text looks fine while vertical punctuation is misplaced, and only real typesetting reveals it.
- Passing validation but rendering wrong: format compliance is not visual compliance, and neither check can be skipped.
8. Human-AI division of labor
| Stage | Better handled by the model / scripts | Requires human judgment |
|---|---|---|
| Sample cleaning and segmentation | Batch processing, parameterized denoising | Manual review of connecting strokes and touching characters |
| Vectorization | Contour extraction, curve fitting, control-point simplification | Whether stroke characteristics are distorted |
| Metric normalization | Unifying units, optical-center alignment, batch transforms | Choosing the target ink ratio |
| Consistency checks | Statistical auditing, outlier selection | Acceptability on aesthetic grounds |
| Validation and regression | Format validation, horizontal/vertical rendering tests | Final acceptance and sign-off |
9. Wrap-up
Back to the original question: a model’s coding ability is strong where it can implement structured, verifiable, templated engineering work efficiently; it is weak at making specification trade-offs and aesthetic calls for you.
Font generation happens to be an ideal window for observing this. Starting from 75 handwritten glyphs, driving the loop closed first, then scaling to a 146-glyph first version and ultimately 1,048 glyphs — the scripts, transforms, statistics, and validation were accelerated by the model, while whether the glyphs look like they came from one person’s hand, where the ink ratio should land, and whether vertical punctuation reads well still came down to human eyes and a healthy respect for the craft. Parallelizable heavy work such as batch rendering and regression testing can run on general-purpose batch compute and object storage, keeping the pipeline repeatable and traceable.
For developers, the real takeaway is not a prompt trick. It is the methodology: make the domain expert’s tacit rules explicit, lock them into scripts, hold the line with validation, and let the model fill in the volume. The same path applies to any domain — design assets, data pipelines, format conversion.
