Generating Chinese Character Stroke Order Animations from Font Files
This article details a technical approach to automatically generating stroke order animation data for Chinese characters from standard font files, covering SVG rendering, coordinate transformations, deep learning-based stroke decomposition, and CSS animation techniques.
This article introduces a backend system designed to automatically generate stroke order animation data for Chinese characters from standard font files (WOFF, OTF, TTF). The system outputs JSON resources distributed via CDN, featuring manual intervention tools for stroke decomposition, direction adjustment, and scaling.
Rendering characters requires aligning the font coordinate system with SVG. Since TTF fonts use a baseline coordinate system while SVG uses a top-left origin, a transformation is applied: transform="scale(1, -1) translate(0, -900)" . This flips the Y-axis and shifts the glyph to the correct baseline position.
Animation effects are created by calculating the length of each stroke's median line and applying CSS keyframes. The stroke-dashoffset and stroke-dasharray properties simulate a drawing brush, while clip-path restricts the animation to the glyph's outline:
const animationStyle = `@keyframes ${keyframeId} {
0% { stroke: blue; stroke-dashoffset: ${animation.offset}; stroke-width: ${animation.width}; }
${animation.fraction}% { stroke: blue; stroke-dashoffset: 0; stroke-width: ${animation.width}; }
100% { stroke: var(--color-text-1); stroke-width: ${STANDARD_LEN}; }
}
#${animationId} { animation: ${keyframeId} ${duration}s linear ${delay}s both; }
`;Stroke decomposition begins by parsing TTF files with opentype.js to extract contour points. Corner points with tangent angle differences exceeding 18° are identified as potential stroke boundaries. A neural network ( convnetjs ) evaluates feature vectors between corners to generate matching scores:
const getFeatures = (ins, out) => {
const diff = out.subtract(ins);
const angle = Math.atan2(diff[1], diff[0]);
const distance = Math.sqrt(out.distance2(ins));
return [subtractAngle(angle, ins.angles[0]), subtractAngle(out.angles[1], angle), /* ... */];
};The Hungarian algorithm solves the maximum weight bipartite matching problem to connect corners with "bridges," effectively splitting the glyph into individual strokes. Straight bridge lines are then smoothed using cubic Bezier curves to preserve natural brush tips.
Finally, stroke order is determined by generating Voronoi diagrams from sampled stroke points to extract median lines. These medians are matched against a structural tree derived from Ideographic Description Characters (IDC). The system produces compact ~4 kB JSON files, though complex characters still require manual verification. Future work aims to integrate error-correction algorithms to minimize human intervention.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.