How to convert truetype or opentype to a legacy svg font
- Step 1Drop your font onto the converter — Drag a `.ttf`, `.otf`, `.woff`, or `.woff2` file onto the drop zone above, or click to browse. The free tier accepts files up to **5 MB**; Pro raises this to 50 MB and Developer to 1 GB. A file that isn't one of those four extensions is rejected with `not a supported font (TTF/OTF/WOFF/WOFF2)`.
- Step 2Let the converter normalise to sfnt — If you uploaded a WOFF2, it's Brotli-decompressed in-browser via the wawoff2 WASM module; a WOFF is zlib-decompressed table-by-table via pako and rebuilt into a flat sfnt. TTF and OTF are used as-is. You don't choose this — it's automatic based on the 4-byte magic number.
- Step 3Click Process — opentype.js parses the sfnt, reads `unitsPerEm`, ascender, and descender, then iterates glyph indices 0..min(glyphCount, 5000). Glyph 0 becomes the `<missing-glyph>`; every other glyph that has a Unicode codepoint becomes a `<glyph unicode="…">`. Glyphs with no codepoint (ligatures, alternates, components) are silently skipped — see the [edge cases](#edge-cases-and-what-actually-happens).
- Step 4Read the result metrics — The result panel shows three numbers: **Glyphs exported** (how many made it into the file), **UPM** (the units-per-em carried through), and **Family** (the resolved family name). Compare Glyphs exported against the source glyph count — a big gap means many glyphs lacked codepoints or you hit the 5,000 cap.
- Step 5Download or copy the SVG — Click **Download SVG** to save `<stem>.svg`, or **Copy to clipboard** to paste the XML straight into another tool. The output is a complete `<svg><defs><font>…</font></defs></svg>` document — the `<font>` lives inside `<defs>` exactly as the SVG Fonts spec requires.
- Step 6Feed it to your downstream engine — Point your PDF generator, e-reader build, or vector pipeline at the `.svg`. If the engine instead wants modern web fonts, you almost certainly want [TTF → WOFF2](/font-tools/ttf-to-woff2) instead — SVG Fonts will not render in any current browser. To inspect individual outlines before converting, use the [Glyph Inspector](/font-tools/glyph-inspector).
Input formats and how they're handled
All four formats route through the same opentype.js glyph walk; the only difference is the decompression step that turns the upload into a flat sfnt buffer first.
| Upload format | 4-byte magic | Pre-processing | Result |
|---|---|---|---|
TTF (.ttf) | 0x00010000 (or true) | None — already an sfnt | Parsed directly |
OTF (.otf) | 0x4F54544F ("OTTO") | None — CFF sfnt parsed directly | CFF outlines converted to SVG paths |
WOFF (.woff) | 0x774F4646 ("wOFF") | pako zlib-inflate per table → rebuilt sfnt | Parsed after unwrap |
WOFF2 (.woff2) | 0x774F4632 ("wOF2") | wawoff2 WASM Brotli decompress → sfnt | Parsed after unwrap |
| TTC / unknown | 0x74746366 / other | Rejected — Unsupported font format | No output |
What ends up in the SVG document
The exact element structure the converter emits. Every attribute below is populated from the source font's tables — nothing is invented or defaulted unless the source omits it.
| Element / attribute | Source | Notes |
|---|---|---|
<font id horiz-adv-x> | Family name + unitsPerEm | horiz-adv-x on <font> is the global default advance (= UPM) |
<font-face units-per-em ascent descent> | unitsPerEm, font.ascender, font.descender | Falls back to UPM×0.8 / UPM×-0.2 if the font omits them |
<font-face font-weight> | Subfamily name | bold only if subfamily contains "bold"; otherwise normal |
<missing-glyph horiz-adv-x d> | Glyph index 0 | Always emitted from the .notdef glyph |
<glyph unicode glyph-name horiz-adv-x d> | Each codepointed glyph | glyph-name only present if the font names the glyph |
d="…" | getPath(0,0,UPM).toPathData(2) | Path data at 2-decimal precision, UPM-scaled |
Limits and what triggers them
The converter has no UI controls; everything below is enforced in code. Numbers are the real constants from lib/font/font-utils.ts and the SVG-font handler.
| Limit | Value | Effect when hit |
|---|---|---|
| Free file size | 5 MB | exceeds the free tier per-job limit — upgrade or compress first |
| Pro / Developer file size | 50 MB / 1 GB | Higher ceilings for large CJK or icon fonts |
| Glyph export cap | 5,000 glyphs | Indices beyond 5,000 are never iterated — output is partial, silently |
| Glyph must have a codepoint | — | Glyphs with unicode == null (except index 0) are skipped |
| Batch size | 1 file | No multi-file mode — convert one font at a time |
Cookbook
Concrete before/after for the conversion. The SVG snippets below are trimmed for readability — real output carries the full path data. If you need modern web fonts instead, stop here and use TTF → WOFF2.
A Latin TTF in, an SVG Font out
ExampleThe everyday case: a small Latin TTF with a few hundred glyphs, all codepointed. Every glyph survives the conversion and the metrics panel confirms the count.
Input: MyBrand-Regular.ttf (412 glyphs, UPM 1000)
Process → Glyphs exported: 412 | UPM: 1000 | Family: MyBrand
Output (MyBrand-Regular.svg, trimmed):
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<font id="MyBrand" horiz-adv-x="1000">
<font-face font-family="MyBrand" font-weight="normal"
units-per-em="1000" ascent="800" descent="-200"/>
<missing-glyph horiz-adv-x="1000" d="…"/>
<glyph unicode="A" glyph-name="A" horiz-adv-x="680" d="M…Z"/>
<glyph unicode="a" glyph-name="a" horiz-adv-x="540" d="M…Z"/>
</font>
</defs>
</svg>Uploading a WOFF2 (auto-decompressed)
ExampleYou don't have to convert a web font back to TTF first. Drop the WOFF2 directly — the converter Brotli-decompresses it to sfnt internally before walking the glyphs. Output is identical to converting the equivalent TTF.
Input: Inter-Regular.woff2 (woff2 magic 0x774F4632) → wawoff2.decompress → sfnt buffer → opentype.parse → glyph walk Process → Glyphs exported: 938 | UPM: 2048 | Family: Inter Download → Inter-Regular.svg
A bold weight resolves font-weight="bold"
ExampleThe converter reads the subfamily from the name table. If it contains the word "bold", the <font-face> gets font-weight="bold". Any other subfamily (Regular, Light, Medium, Black) maps to normal — the converter does not parse numeric weight classes.
Input: Roboto-Bold.ttf (subfamily "Bold") Output <font-face>: font-family="Roboto" font-weight="bold" units-per-em="2048" … Input: Roboto-Black.ttf (subfamily "Black") Output <font-face>: font-family="Roboto" font-weight="normal" ← Black ≠ "bold"
An icon font where glyphs lack codepoints
ExampleIcon fonts that map symbols only via ligatures (and leave the .notdef-style glyphs without a unicode) lose those glyphs in conversion — only codepointed glyphs are exported. PUA-mapped icon fonts (each icon at U+E000+) convert fine.
Input: MaterialIcons.ttf (ligature-mapped, ~1,100 glyphs) Process → Glyphs exported: 3 ← only the 3 codepointed glyphs survive Input: MaterialIcons-PUA.ttf (each icon at U+E000…) Process → Glyphs exported: 1,100 ← all PUA-codepointed glyphs export
A large CJK font hits the 5,000-glyph cap
ExampleThe converter iterates at most 5,000 glyph indices. A 20,000-glyph CJK font produces an SVG containing the first 5,000 — usable for a subset but not the whole repertoire. Subset first if you need specific characters.
Input: NotoSansSC.otf (~20,000 glyphs) Process → Glyphs exported: 5,000 ← capped, silently Fix: subset to the characters you need FIRST, then convert: Font Subsetter → keep your text's glyphs → small TTF → Font-to-SVG-Font → complete, small SVG
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Output is much larger than the source font
By designSVG Fonts are uncompressed XML — every glyph is a verbose <glyph> element with a path string at 2-decimal precision. A 50 KB TTF routinely produces a 200–400 KB SVG. There is no table compression and no Brotli/zlib wrapper. This is inherent to the format, not a converter bug; if size matters and you're targeting browsers, use TTF → WOFF2 instead.
Glyphs without a Unicode codepoint vanish
Skipped — by designThe glyph walk skips any glyph where unicode == null (except index 0, which becomes <missing-glyph>). That means standalone ligature glyphs, stylistic alternates, small-caps variants, and component glyphs reached only via GSUB are NOT exported. The SVG Fonts conversion is purely a cmap-driven outline dump; it does not flatten OpenType substitutions.
CJK or large icon font exceeds 5,000 glyphs
Capped — silentThe loop runs Math.min(font.glyphs.length, 5000). Glyph indices past 5,000 are never serialised, and there is no warning in the result panel — only the Glyphs exported number reveals the truncation. Subset the font down to the characters you actually need with the Font Subsetter before converting.
Kerning, ligatures, and OpenType features are dropped
Not exportedThe converter emits glyph outlines and advance widths only. It does not write <hkern>/<vkern> pairs, does not export GSUB ligatures, and ignores GPOS positioning entirely. The SVG Fonts spec allows <hkern>, but this tool does not generate them. If your pipeline needs kerning, keep the original TTF/OTF and inspect pairs with the Kerning Pair Auditor.
File over the tier size limit
413 / rejectedA file larger than your tier ceiling (5 MB free, 50 MB Pro, 1 GB Developer) is rejected before processing with exceeds the <tier> tier per-job limit. The block is recorded for usage stats. Either compress the font (a WOFF2 of the same TTF is far smaller, and the converter accepts WOFF2 directly) or upgrade the tier.
Wrong extension or a TrueType Collection
Rejected — invalidOnly .ttf, .otf, .woff, .woff2 are accepted; anything else fails the isSupportedFontFile check with not a supported font. A TrueType Collection (.ttc, magic ttcf) sniffs as ttc and throws Unsupported font format inside the sfnt step — extract a single face from the collection first.
Font has no name table family
Preserved (fallback)If the font's name table lacks an English fontFamily, the converter falls back to the file's stem (Inter-Regular.woff2 → Inter-Regular) for both the <font id> and font-family. The output is still valid; the family name just mirrors your filename instead of the embedded metadata.
Missing ascender/descender metrics
Preserved (fallback)When font.ascender or font.descender is undefined, the converter substitutes unitsPerEm × 0.8 for ascent and unitsPerEm × -0.2 for descent. These are reasonable defaults but won't match a font's true vertical metrics — if precise line metrics matter downstream, verify them with the Font Metrics Analyzer.
Special characters in glyph names or family
Preserved (escaped)Family names, glyph names, and the unicode attribute are XML-escaped (&, <, >, ", ' → entities) before serialisation, so a glyph named quotedbl or a family with an ampersand won't break the XML. The escaping is applied automatically; you don't need to sanitise the font first.
Expecting a browser-renderable web font
Won't render in browsersSVG Fonts were removed from every shipping browser — Chrome dropped them in 2015, and only old Safari ever rendered them inline. The output of this tool is for non-browser consumers. For the web, convert to WOFF2 with TTF → WOFF2 and confirm the format of any mystery file with the Font Format Identifier.
Frequently asked questions
What formats can I upload?
TTF, OTF, WOFF, and WOFF2. WOFF and WOFF2 are decompressed to a flat sfnt in your browser first (pako for WOFF's zlib tables, the wawoff2 WASM module for WOFF2's Brotli), then walked the same way as a TTF. TrueType Collections (.ttc) and anything else are rejected.
Are there any conversion options?
No. The tool has a single file drop zone and a Process button — no charset picker, no precision slider, no kerning toggle. It always exports every codepointed glyph (up to 5,000) at 2-decimal path precision with the source units-per-em. If you need a subset, run the Font Subsetter first.
Why is the SVG so much bigger than my TTF?
SVG Fonts are plain XML with no compression — each glyph is a <glyph> element holding a full path string. A 50 KB TTF commonly becomes 200–400 KB of SVG. That's normal for the format. If small file size is the goal and you're targeting the web, use WOFF2 instead; it's an order of magnitude smaller.
How many glyphs will it export?
Up to 5,000 codepointed glyphs. The converter iterates glyph indices 0 through min(glyphCount, 5000), exports glyph 0 as <missing-glyph>, and exports every other glyph that has a Unicode codepoint. Glyphs without a codepoint are skipped. The Glyphs exported metric tells you the actual count.
Does it preserve kerning and ligatures?
No. The converter writes glyph outlines and horizontal advance widths only. It does not emit <hkern> pairs, does not export GSUB ligatures, and ignores GPOS. If your downstream needs kerning or shaping, keep the original OpenType file and use a tool that consumes GSUB/GPOS directly.
Will my font be uploaded anywhere?
No. Conversion runs entirely in your browser with opentype.js. The font binary never reaches a server on the free tier — the result panel even shows a 0 bytes uploaded badge. Only a single usage counter (no file content) is recorded for signed-in dashboard stats. Paid tiers can optionally route the job to the local @jadapps/runner, which is still on your own machine.
Can I convert an SVG Font back to TTF here?
No. This is a one-way TTF/OTF/WOFF/WOFF2 → SVG-Font converter. Reconstructing a TTF from an SVG Font would require rebuilding cmap, kerning, OpenType layout, and metric tables that the SVG output doesn't carry. Keep your original source font for any round-trip needs.
My icon font exported almost nothing — why?
Ligature-mapped icon fonts (Material Icons classic, for example) leave most glyphs without a unicode codepoint — they're reached via ligature substitution. The converter only exports codepointed glyphs, so those icons are skipped. Use a PUA-codepointed build (each icon at U+E000+) and every icon will convert.
Does it handle OTF (CFF) fonts, not just TrueType?
Yes. OTF files with PostScript/CFF outlines (magic "OTTO") are parsed by opentype.js and their CFF outlines are converted to SVG path data exactly like TrueType outlines. The output element structure is identical regardless of outline type.
What precision are the path coordinates?
Two decimal places. The converter calls getPath(0, 0, unitsPerEm).toPathData(2), which rounds each coordinate to 2 decimals in the units-per-em coordinate system. That keeps file size reasonable while staying sub-pixel accurate at any realistic render size.
Why does my bold font say font-weight="normal"?
The converter sets font-weight="bold" only when the font's subfamily name literally contains the word "bold". Subfamilies like Black, Heavy, Semibold-without-a-space-match, or Medium map to normal. It's a coarse two-state mapping, not a numeric weight parse. Edit the attribute by hand if your consumer needs the exact weight.
I just want a web font — is this the right tool?
Probably not. SVG Fonts don't render in any current browser. For the web, convert to WOFF2 with TTF → WOFF2, and if you only know you have "some font file", identify it first with the Font Format Identifier. Use this SVG-Font converter only for non-browser engines that specifically require the legacy format.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.