
Tegaki is a JavaScript handwriting animation library that turns font glyphs into stroke-by-stroke text animations.
It ships a vanilla JavaScript engine plus adapters for React, Vue, Nuxt, Svelte, SolidJS, Astro, and Web Components.
The renderer builds handwriting from font data and follows the chosen typeface directly. Hand-authored SVG strokes are unnecessary.
Features:
- Stroke-by-stroke handwriting generated from font data.
- 13 built-in font bundles for Latin, Hebrew, Arabic, Devanagari, Japanese, Korean, Bengali, and Simplified Chinese text.
- Vanilla JavaScript plus React, Vue, Nuxt, Svelte, SolidJS, Astro, and Web Component integrations.
- Uncontrolled, controlled, and CSS-driven time modes.
- Streaming text playback with configurable catch-up behavior.
- Pressure control and ordered plugins for glow, taper, gradients, variation, line boil, and annotations.
- Deterministic seeded rendering for repeatable output.
- Animated and static SVG export from the renderer or command line.
- Optional HarfBuzz shaping for ligatures, Arabic joining, Indic scripts, and complex text layout.
- Custom bundles from Google Fonts or uploaded TTF/OTF files in Tegaki Studio.
See It In Action:
Use Cases:
- Signature-style logos and hero headings that need to match an existing typeface.
- Remotion titles whose handwriting follows video time frame by frame.
- Streaming chat responses that keep writing as new chunks arrive.
- Self-drawing SVG text for READMEs, slides, and static pages.
How To Use It:
1. Install Tegaki
# npm npm install tegaki # yarn yarn add tegaki # pnpm pnpm add tegaki # bun bun add tegaki
2. Use the Web Component from a CDN
Your static page can load the Web Component and a font bundle through ESM imports.
<tegaki-renderer
font="Caveat"
text="Hello, world!"
style="font-size: 56px"
></tegaki-renderer>
<script type="module">
import {
registerTegakiElement,
TegakiEngine,
} from 'https://esm.sh/tegaki/wc';
import caveat from 'https://esm.sh/tegaki/fonts/caveat';
TegakiEngine.registerBundle(caveat);
registerTegakiElement();
</script>3. Render Handwriting with Vanilla JavaScript
Import TegakiEngine from tegaki/core and pass a font bundle to the constructor.
<div id="handwriting" style="font-size: 56px"></div>
<script type="module">
import { TegakiEngine } from 'tegaki/core';
import italianno from 'tegaki/fonts/italianno';
const engine = new TegakiEngine(
document.getElementById('handwriting'),
{
text: 'Welcome Back',
font: italianno,
time: { mode: 'uncontrolled', speed: 1, loop: false },
}
);
</script>4. Render Handwriting in React
Import the React renderer from tegaki and pass a font bundle.
import { TegakiRenderer } from 'tegaki';
import caveat from 'tegaki/fonts/caveat';
function SignatureHero() {
return (
<TegakiRenderer
font={caveat}
time={{ mode: 'uncontrolled', speed: 1, loop: true }}
style={{ fontSize: 48 }}
>
Your Name Here
</TegakiRenderer>
);
}5. Framework Entry Points
| Environment | Entry Point | Requirement |
|---|---|---|
| React | import { TegakiRenderer } from 'tegaki'; | React 18+ |
| Next.js | Use the React adapter from tegaki in a Client Component. | React 18+ |
| Svelte | import { TegakiRenderer } from 'tegaki/svelte'; | Svelte 5+ |
| Vue | import { TegakiRenderer } from 'tegaki/vue'; | Vue 3+ |
| Nuxt | Register tegaki/nuxt in modules. | Nuxt 3 or 4 |
| SolidJS | import { TegakiRenderer } from 'tegaki/solid'; | SolidJS 1+ |
| Astro | import TegakiRenderer from 'tegaki/astro'; | Astro 4+ |
| Web Components | import { registerTegakiElement } from 'tegaki/wc'; | No framework dependency. |
| Vanilla JavaScript | import { TegakiEngine } from 'tegaki/core'; | No framework dependency. |
6. Configure Nuxt
The Nuxt module auto-imports <TegakiRenderer> and wires the Vue adapter into SSR and client rendering.
export default defineNuxtConfig({
modules: ['tegaki/nuxt'],
});7. Style the Handwriting with CSS
Tegaki reads font-size, line-height, color, and letter-spacing from the renderer container.
#handwriting {
font-size: 64px;
line-height: normal;
color: #242424;
letter-spacing: 0.03em;
}8. Style the Ink with Plugins
import {
glowPlugin,
strokeGradientPlugin,
taperPlugin,
variationPlugin,
} from 'tegaki/core';
const plugins = [
taperPlugin(),
variationPlugin(),
strokeGradientPlugin(),
glowPlugin({ radius: 0.1, color: '#00ccff' }),
];
const engine = new TegakiEngine(container, {
text: 'Handwritten',
font: caveat,
pressure: 0.8,
plugins,
});9. Control the Animation from JavaScript
Pass seconds directly to time, use a percentage string for progress, or pass a controlled-time object when another timeline owns playback.
const engine = new TegakiEngine(container, {
text: 'Scrub me',
font: caveat,
time: 0,
});
// Seek to a time in seconds.
engine.update({ time: 1.25 });
// Or seek by progress.
engine.update({ time: '50%' });
// Equivalent controlled object.
engine.update({
time: {
mode: 'controlled',
value: 0.75,
unit: 'progress',
},
});10. Read the Timeline Duration
Use computeTimeline() when external playback needs the natural duration before a renderer starts.
import { computeTimeline } from 'tegaki';
import caveat from 'tegaki/fonts/caveat';
const { totalDuration } = computeTimeline('Handcrafted', caveat);11. Drive the Animation with CSS
Set time to 'css' and animate --tegaki-progress from 0 to 1. This works with normal CSS animation timelines, including scroll-driven timelines in browsers that implement them.
<tegaki-renderer class="scroll-writing" font="Caveat" text="Scroll to write" time="css" ></tegaki-renderer>
@keyframes tegaki-progress {
from { --tegaki-progress: 0; }
to { --tegaki-progress: 1; }
}
.scroll-writing {
animation-name: tegaki-progress;
animation-duration: 1s;
animation-fill-mode: both;
animation-timeline: view();
animation-range: cover 10% cover 70%;
}12. Animate Streaming Text
Update text as chunks arrive. catchUp increases playback speed when buffered text grows.
const writer = new TegakiEngine(container, {
font: caveat,
time: {
mode: 'uncontrolled',
speed: 4,
catchUp: 0.5,
},
});
let response = '';
function appendChunk(chunk) {
response += chunk;
writer.update({ text: response });
}13. Register a Font Bundle by Name
Register a bundle once when later renderers need to reference it by its family name.
import { TegakiEngine } from 'tegaki/core';
import caveat from 'tegaki/fonts/caveat';
TegakiEngine.registerBundle(caveat);
const engine = new TegakiEngine(container, {
text: 'Thank you',
font: 'Caveat',
});14. Pre-render Tegaki Markup for SSR
TegakiEngine.renderElements() creates the renderer tree for a server renderer. The browser can adopt that pre-rendered content with adopt: true.
const { rootProps, content } = TegakiEngine.renderElements(
{ text: 'Server Rendered Note', font: caveat },
createElement
);
// Render rootProps on the container and content inside it
// with your server renderer.
// In the browser:
const engine = new TegakiEngine(container, {
adopt: true,
text: 'Server Rendered Note',
font: caveat,
});15. Generate an Animated SVG from the Command Line
The CLI writes SVG only and uses bundled HarfBuzz shaping. Tegaki Studio exports PNG, GIF, and WebM when raster or video output is required.
npx tegaki "Tegaki is awesome"
npx tegaki "Hello World" --font tangerine --mode once -o hello.svg
npx tegaki "Glow" --plugins '[["glow", {"radius": 0.12, "color": "#f0a"}]]'Renderer Options:
| Option | Type / Default | Description |
|---|---|---|
text | string, default '' | Text to render. The React adapter can also read string or number children. |
font | TegakiBundle | string | Font bundle object or a family name registered with registerBundle(). |
time | TimeControlProp | Controls playback through uncontrolled, controlled, or CSS time. |
reducedMotion | 'never' | 'user' | 'always', default 'never' | Controls reduced-motion behavior for uncontrolled playback. |
pressure | number, default 1 | Sets how closely stroke width follows pressure data stored in the bundle. Use values from 0 to 1. |
timing | TimelineConfig | Configures glyph gaps, word gaps, line gaps, easing, dot deferral, and staggered scheduling. |
quality | TegakiQuality | Configures pixel ratio, stroke subdivision, text clipping, and smoothing. |
plugins | readonly TegakiPluginSpec[] | Ordered plugin objects or registered plugin names that reshape, retime, paint, or decorate the handwriting. |
seed | number | 'random', default 0 | Controls deterministic random variation used by plugins. |
showOverlay | boolean, default false | Shows Tegaki’s debug text overlay. |
onComplete | () => void | Runs when uncontrolled playback reaches the end. |
onChangeTimeline | (timeline) => void | Runs after Tegaki recomputes the active timeline. |
direction | 'ltr' | 'rtl' | Sets text direction explicitly. Omit it to use automatic paragraph direction. |
fallbackFont | string | CSS font-family list for characters that have no stroke data in the active bundle. |
shaper | boolean, default true | Lets the renderer use a globally registered text shaper. Set it to false for one renderer instance. |
Time Control Options
| Option | Default | Description |
|---|---|---|
time: number | – | Controlled time in seconds. |
time: '50%' | – | Controlled progress shorthand. |
time: 'css' | – | CSS-time shorthand that reads --tegaki-progress. |
controlled.value | Required | Current controlled time or progress value. |
controlled.unit | 'seconds' | 'seconds' or 'progress'. |
uncontrolled.initialTime | 0 | Initial time in seconds. |
uncontrolled.playing | true | Initial play or pause state. |
uncontrolled.loop | false | Restarts playback after completion. |
uncontrolled.delay | 0 | Delay in seconds before playback begins. |
uncontrolled.loopGap | 0 | Pause in seconds between loop iterations. |
uncontrolled.speed | 1 | Playback speed multiplier. |
uncontrolled.catchUp | 0 | Increases playback speed when buffered text has advanced ahead of the pen. |
uncontrolled.duration | – | Sets one iteration to a fixed number of seconds. It is mutually exclusive with speed and catchUp. |
uncontrolled.easing | – | Maps linear playback progress to displayed progress. |
uncontrolled.onTimeChange | – | Runs on each frame with the current eased time. |
Timeline Options
| Option | Default | Description |
|---|---|---|
glyphGap | 0.1 | Pause between glyphs in seconds. |
wordGap | 0.15 | Pause after a space in seconds. |
lineGap | 0.3 | Pause after a line break in seconds. |
unknownDuration | 0.2 | Duration assigned to characters that have no glyph stroke data. |
strokeEasing | Ease-out exponential | Easing function applied to each stroke’s draw progress. |
glyphEasing | Linear | Easing function applied to each glyph’s local time. |
deferDots | true | Defers priority-tagged marks and script-specific finishing strokes until body strokes in the word are complete. |
stagger.advance | Required when stagger is used | Delay between glyph starts in seconds or as a percentage string. |
stagger.duration | 'auto' | Uses bundled glyph timing or scales each glyph to a fixed number of seconds. |
Quality Options
| Option | Default | Description |
|---|---|---|
pixelRatio | 1 | Internal supersampling factor applied on top of the device pixel ratio. |
segmentSize | 2 px when subdivision is active | Maximum drawn segment length in CSS pixels for variable-width or reshaped strokes. |
clipText | false | Clips handwriting strokes to the filled text shape. A positive number also scales stroke widths before clipping. |
smoothing | false | Smooths stroke polylines with a centripetal Catmull-Rom spline. |
Nuxt Module Options
| Option | Default | Description |
|---|---|---|
autoImport | true | Registers <TegakiRenderer> as a global Nuxt component. |
prefix | '' | Prepends a string to the auto-imported component name. |
Built-in Plugins:
Built-in plugin factories come from tegaki/core. Plugin order affects the final render. Register plugin factories when configuration must use plain names in HTML attributes, Astro props, or CLI data.
glowPlugin / glow
| Option | Default | Description |
|---|---|---|
radius | 0.1 | Glow radius. Bare numbers use em units. A string like '8px' uses a fixed unit. |
color | '' | Glow color. An empty value uses the text color. |
offsetX | 0 | Horizontal glow offset. |
offsetY | 0 | Vertical glow offset. |
taperPlugin / taper
| Option | Default | Description |
|---|---|---|
startLength | 0.15 | Share of a stroke that thins toward its start. |
endLength | 0.15 | Share of a stroke that thins toward its end. |
strokeGradientPlugin / strokeGradient
| Option | Default | Description |
|---|---|---|
colors | [] | Gradient colors. An empty array creates the rainbow behavior. |
saturation | 80 | Rainbow saturation in percent. |
lightness | 55 | Rainbow lightness in percent. |
globalGradientPlugin / globalGradient
| Option | Default | Description |
|---|---|---|
colors | ['#ff0000', '#0000ff'] | Color stops across the complete text. |
angle | 0 | Gradient direction in degrees. |
variationPlugin / variation
Presets: Subtle, Loose, and Signature.
| Option | Default | Description |
|---|---|---|
amount | 1 | Scales all variation settings. |
size | 0.05 | Glyph size variation. |
slant | 4 | Slant variation in degrees. |
rotation | 2 | Rotation variation in degrees. |
drift | 0.02 | Position drift as a length value. |
warp | 0.015 | Glyph bend as a length value. |
width | 0.12 | Stroke-width variation. |
boilPlugin / boil
Presets: Cartoon, Nervous, and Sketchy.
| Option | Default | Description |
|---|---|---|
drawings | 3 | Number of alternate drawings in the cycle. |
fps | 12 | Drawing changes per second. |
amount | 0.012 | Line displacement as a length value. |
detail | 0.3 | Controls slow bends versus finer jitter. |
width | 0.08 | Stroke-width variation. |
idle | false | Keeps the boil cycle active after writing finishes or during a pause. |
annotatePlugin / annotate
| Option | Default | Description |
|---|---|---|
mark | 'underline' | 'underline', 'double', 'highlight', 'strike', 'circle', 'box', or 'cross'. |
target | 'text' | 'text', 'lines', or 'words'. |
pick | 0 | Selects one item by one-based index. 0 marks every eligible item. |
when | 'after' | 'after' draws marks after the text. 'each' draws a mark after its associated text unit. |
color | '#e5484d' | Annotation color. |
width | 0.045 | Annotation stroke width as a length value. |
padding | 0.12 | Distance between the annotation and handwriting. |
roughness | 0.5 | Controls how loosely the annotation is drawn. |
duration | 0.5 | Seconds used to draw each mark. |
delay | 0.25 | Seconds before each mark begins. |
Custom Plugin Hooks:
| Hook | Purpose |
|---|---|
geometry(path, ctx) | Reshapes stroke points and widths when layout changes. |
timing(ctx) | Changes stroke start times and durations. |
paint(stroke, next) | Changes how each stroke is painted. |
ink(ctx) | Post-processes the finished ink for glow, shadows, or filters. |
underlay(ctx) | Paints beneath the handwriting. |
overlay(ctx) | Paints above the handwriting. |
onFrame(frame, prev) | Runs after every rendered frame for side effects or synchronization. |
bounds(ctx) | Expands drawing bounds for plugin output beyond the ink. |
outline(contour, ctx) | Reshapes glyph outlines used by text clipping. |
svg(ctx) | Supplies plugin-specific SVG markup or styling during SVG export. |
attach(ctx) | Sets up plugin resources and can return a cleanup function. |
steps | Defines a drawing cycle with count, fps, optional idle, and optional paintOnly. |
TegakiEngine API:
Constructor
| Signature | Description |
|---|---|
new TegakiEngine(container, options?) | Creates an engine in an HTML container. Constructor options use TegakiEngineOptions plus adopt?: boolean for pre-rendered content. |
Static Methods
| Method | Description |
|---|---|
TegakiEngine.registerBundle(bundle) | Registers a font bundle by family name. |
TegakiEngine.getBundle(name) | Returns a registered font bundle. |
TegakiEngine.registerPlugin(...factories) | Registers plugin factories for name-based plugin configuration. |
TegakiEngine.getPlugin(name) | Returns a registered plugin factory. |
TegakiEngine.registerShaper(factory) | Registers a global text shaper. Pass null to unregister it. |
TegakiEngine.preload(bundle) | Preloads font faces and the registered shaper for a bundle. |
TegakiEngine.renderElements(options, createElement) | Returns { rootProps, content } for server rendering and later client adoption. |
Instance Properties
| Property | Description |
|---|---|
currentTime | Current rendered timeline time in seconds. |
duration | Total active timeline duration. |
seed | Resolved numeric seed used by the renderer. |
timeline | Current computed timeline. |
strokes | Read-only stroke list with start times and durations. |
isPlaying | Current uncontrolled playback state. |
isComplete | Reports completion for the active timeline. |
element | Renderer root element. |
canvas | Backing canvas containing the latest rendered frame. |
Instance Methods
| Method | Description |
|---|---|
update(options) | Updates any subset of engine options. |
computeTimeline(text) | Computes a timeline with the engine’s loaded font, timing configuration, and resolved shaper. |
frameAt(time?) | Returns stroke state, progress, geometry, and pen-head data at a timeline time. |
toSVG(options?) | Serializes the current render to an SVG string. |
exportSVG(options?) | Exports SVG asynchronously and embeds font data required by SVG text. |
play() | Resumes uncontrolled playback. |
pause() | Pauses uncontrolled playback. |
seek(time) | Moves uncontrolled playback to seconds or a percentage string. |
restart() | Returns uncontrolled playback to the beginning and applies its configured delay. |
destroy() | Stops observers, loops, plugin attachments, and engine-owned DOM content. |
SVG Export Options
| Option | Default | Description |
|---|---|---|
animated | true | Exports self-drawing SVG animation. Set false for finished artwork. |
loop | false | Uses a repeating CSS-keyframe SVG animation. |
speed | Engine speed or 1 | Playback speed used by the exported SVG. |
loopHold | 1.5 | Seconds the completed text is held before a looping SVG repeats. |
crop | true | Crops the SVG viewBox to the rendered ink. |
fontFaces | – | toSVG() can embed supplied font-face data. exportSVG() builds the required font data automatically. |
Additional Public Core APIs:
| API | Description |
|---|---|
BUNDLE_VERSION | Current public font-bundle format version. |
textToSvg(text, bundle, options?) | Creates animated or static SVG markup in headless code. |
drawGlyph(ctx, glyph, pos, time, options?) | Draws one glyph on a canvas context at a specific local time. |
computeTimeline(text, font, timing?, shaper?) | Computes text timing outside an engine instance. |
StrokePath | Stroke polyline class with length, pointAt(), slice(), bounds(), and map(). |
paintStroke() | Default stroke painter used at the end of a plugin paint chain. |
offsetPath() | Creates a parallel stroke path. |
inkEdge() | Builds an offset distance that follows the ink edge. |
clearance() | Measures distance from a point to the nearest ink edge. |
unionBoxes() | Returns one box that contains a list of boxes. |
expandBox() | Expands a box by a pixel amount on each side. |
groupStrokes() | Groups strokes by complete text, lines, or words. |
seededRandom() | Creates deterministic random values from a seed and key. |
createBundle() | Creates a font bundle from font and glyph data supplied by application code. |
createPlugin() | Defines a typed plugin factory with parameters, defaults, ranges, and presets. |
lengthToPx() | Converts a plugin length value to CSS pixels for a given font size. |
parseLength() | Parses a Tegaki plugin length value. |
registerBundle() / getBundle() | Function exports for the global bundle registry. |
registerPlugin() / getPlugin() | Function exports for the global plugin registry. |
parsePluginSpecs() | Parses name-based plugin configuration. |
paintsDrawnOnly() | Marks a plugin factory whose paint hook only restyles strokes that the pen has reached. |
Built-in Font Bundles:
Most built-in bundles can load their source font as fallback text when a character has no stroke data. LXGW WenKai ships the generated subset only; configure fallbackFont for characters outside that subset.
| Font | Import | Writing System |
|---|---|---|
| Caveat | tegaki/fonts/caveat | Latin |
| Italianno | tegaki/fonts/italianno | Latin |
| Tangerine | tegaki/fonts/tangerine | Latin |
| Parisienne | tegaki/fonts/parisienne | Latin |
| Suez One | tegaki/fonts/suez-one | Hebrew and Latin |
| Amiri | tegaki/fonts/amiri | Arabic and Latin |
| Tillana | tegaki/fonts/tillana | Devanagari and Latin |
| Klee One | tegaki/fonts/klee-one | Japanese kana, Kyōiku grade 1-2 kanji, and Latin |
| Nanum Pen Script | tegaki/fonts/nanum-pen-script | Korean Hangul, jamo, and Latin |
| Atma | tegaki/fonts/atma | Bengali and Latin |
| LXGW WenKai | tegaki/fonts/lxgw-wenkai | Simplified Chinese, 1000 common hanzi, punctuation, and Latin |
| Hershey Script | tegaki/fonts/hershey-script | Latin single-line stroke font |
| EMS Allure | tegaki/fonts/ems-allure | Latin single-line stroke font |
Custom Font Bundles:
Tegaki Studio accepts Google Fonts and uploaded .ttf or .otf files. Download the generated bundle and import its bundle.ts file in place of a built-in font import.
import customFont from './output/my-font/bundle';
Text Shaping:
Complex scripts and OpenType features can use the optional HarfBuzz shaper. Install harfbuzzjs before importing tegaki/shaper-harfbuzz.
npm install harfbuzzjs
import { TegakiEngine } from 'tegaki/core';
import harfbuzzShaper from 'tegaki/shaper-harfbuzz';
TegakiEngine.registerShaper(harfbuzzShaper);Migrating from the Old effects API:
The stable 1.x API removes effects. Existing effect configuration must move to plugins and pressure.
| Old API | Current API |
|---|---|
effects: { glow: { radius: 8 } } | plugins: [glowPlugin({ radius: '8px' })] |
effects: { wobble: { ... } } | Use variationPlugin(), boilPlugin(), or a custom wobble plugin. |
effects: { taper: true } | plugins: [taperPlugin()] |
effects: { strokeGradient: { colors: 'rainbow' } } | plugins: [strokeGradientPlugin()] |
effects: { globalGradient: { colors, angle } } | plugins: [globalGradientPlugin({ colors, angle })] |
effects: { pressureWidth: false } | pressure: 0 |
effects: { pressureWidth: { strength: 0.5 } } | pressure: 0.5 |
--effects in the CLI | --plugins |
Alternatives & Related Resources:
- Latest Free Text Animations In Pure JavaScript And CSS
- Create Handwritten SVG Text Animations in JavaScript – Khoshnus.js
- Animate Text Using SVG Font – svg-text-animate.js
- 10 Best Typewriter Text Animation JavaScript Libraries (2026 Update)
- JavaScript Library For Beautiful Smooth Text Animations – TxtAnime.js
FAQs:
Q: Why does Tegaki lay out text in Vite but fail to draw the handwriting?
A: Vite 7 and earlier can pre-bundle Tegaki in a way that breaks the relative font URL. Exclude tegaki from dependency optimization and restart the dev server.
export default defineConfig({
optimizeDeps: {
exclude: ['tegaki'],
},
});Q: What does Astro need for server-rendered font bundles?
A: Use the Astro integration in astro.config.mjs. It lets Vite emit bundled fonts as assets and resolves their public URLs during server rendering.
import { defineConfig } from 'astro/config';
import tegaki from 'tegaki/astro/integration';
export default defineConfig({
integrations: [tegaki()],
});Q: Why does a registered plugin name fail to resolve?
A: Name-based plugins are not registered automatically in framework renderers. Register each factory with TegakiEngine.registerPlugin() before relying on names like 'glow' or 'taper'.
Changelog:
v1.1.0 (10/12/2026)
- Bundles with several font weights
v1.0.3 (10/07/2026)
- Nib stamps keep their size below full pressure
v1.0.2 (10/02/2026)
- Idle plugin loops pause off screen
v1.0.1 (10/01/2026)
- Unified Vue, Nuxt, Svelte, SolidJS, and Astro adapters on
tegaki/coreto fix plugin and bundle registry compatibility.
v1.0.0 (09/30/2026)
- Established the stable semver renderer, plugin, and bundle APIs.
- Replaced the old
effectsconfiguration with plugins and thepressureoption. - Shipped LXGW WenKai for Simplified Chinese plus the Hershey Script and EMS Allure single-line bundles.
- Introduced deterministic
seed,fallbackFont,reducedMotion, plugin registration, and improved SVG export. - Regenerated bundled fonts with improved stroke extraction and stroke order.
- Shipped
annotatePluginand expanded the public plugin API.
v0.22.2 (09/23/2026)
- Continued rendering when a bundle font file fails to load.
v0.22.1 (07/13/2026)
- Shipped the
tegaki/fonts/atmabuild output.
v0.22.0 (07/12/2026)
- Introduced Bengali support through the Atma font bundle.
v0.19.0 (06/29/2026)
- Introduced Korean Hangul support and the Nanum Pen Script bundle.







