How should SVG fonts be designed for Typst?

I cannot find any font that produces this result in SVG in Typst: noto-emoji/svg/emoji_u1f4d6.svg at main · googlefonts/noto-emoji · GitHub.

I tried building my own font (made up of that single SVG) with GitHub - Sumandora/svg2font: A simple svg to ttf converter that uses fontforge and automatically scales objects · GitHub, but after producing the file Typst did not recognise the character at all.

Therefore I was thinking: how does Typst want fonts to be formatted? Because It’s been very hit and miss with a lot of these color emojis to get them in SVG.

Could you give a screenshot of your result? Are you using the font shipped on typst.app, or compiling locally with a font that you have downloaded on your own? Are you exporting to SVG, PNG, or PDF?

Noto Color Emoji should just work with Typst v0.14.2. It was once broken, but it has been fixed since v0.14.0-rc.1.

#set text(5em, font: ("Libertinus Serif", "Noto Color Emoji"))
#"\u{1f4d6}"

it works for me too, but is PNG not SVG, that’s all

Here is a complete, technically accurate explanation you can post on the forum to help others who run into the same issue. It explains exactly what happened with svg2font, how Typst handles color fonts, and the two best ways to actually solve it.


Why your custom SVG font failed in Typst (and how to fix it)

If you are trying to convert colored SVGs (like the Google Noto Emoji set) into a custom font for Typst and getting either “tofu” (missing character boxes) or solid black silhouettes, you are running into a mix of Unicode mapping issues and font format limitations.

Here is exactly why the standard svg2font tools fail, and the two ways to correctly get complex SVG icons into Typst.


Why svg2font (and FontForge) Failed

1. The Unicode Mapping is Wrong (\uE000 problem) Most automated svg2font tools map your first SVG to \uE000 (a Private Use Area block in Unicode). However, when you type the book emoji (📖) in Typst, it searches the font for the standard Unicode codepoint U+1F4D6. Because the tool mapped your book to \uE000 instead, Typst finds nothing and renders a missing character “tofu” box.

2. FontForge Destroys Colors and Gradients svg2font uses FontForge under the hood. FontForge is designed for standard, single-color TrueType outlines. If your SVG contains hex colors (fill:#C62828), gradients (<linearGradient>), or opacities (<g style="opacity:0.5;">), FontForge completely strips them. It merges everything into a single, flat, solid black silhouette.


How to actually fix it

Depending on whether you need a real font or just the graphic, here are the two solutions.

Solution 1: The Native Typst Way (Best for perfect gradients/opacities)

If your SVG has complex gradients, opacities, or strokes (like the Adobe Illustrator export you are using), do not turn it into a font.

Currently, the only OpenType font format that supports internal gradients is COLRv1. While Typst’s text engine can read COLRv1, its PDF exporter cannot natively embed COLRv1 gradients yet. If you force a COLRv1 font into Typst, the PDF exporter panics and falls back to rendering a solid black box.

Instead, Typst natively supports embedding raw SVGs inline with text. This perfectly preserves all Adobe Illustrator gradients and vector scaling.

Just save your file as book.svg and inject it into your text flow using a box with a shifted baseline (so it aligns with your text perfectly):

typst

#let book-icon = box(baseline: 15%, image("book.svg", height: 1em))

I am reading a #book-icon today.

Solution 2: The Font Way (If you absolutely must have a .ttf)

If you are building a massive icon font and must compile it to a .ttf, you have to throw away svg2font and use Google’s nanoemoji Python compiler. It is specifically designed to parse colored SVGs and build OpenType color tables.

However, because Typst cannot export COLRv1 gradients to PDF, you must tell nanoemoji to compile an older COLRv0 font. COLRv0 supports solid colors perfectly, but it will flatten any gradients into solid color blocks.

Steps to build the font:

  1. Install the compiler: pip install nanoemoji
  2. Crucial: Rename your SVG file from book.svg to its exact Unicode hex value: 1f4d6.svg. nanoemoji reads the filename to map it to the correct character, fixing the \uE000 problem.
  3. Compile to COLRv0:

bash

nanoemoji --color_format=glyf_colr_0 1f4d6.svg
  1. This outputs a Font.ttf (named "An Emoji Family" by default) in a build/ folder.

Include this font in your Typst project and set up your fallback rules correctly so Typst knows to use it:

typst

// Load your system font first, then fallback to the custom emoji font
#set text(font: ("Libertinus Serif", "An Emoji Family"))

I am reading a 📖 today.

This will successfully render the colored vector from the font directly in your PDF.

So where did the name “An Emoji Family” come from?

It did not come from the SVG at all. The SVG file knows absolutely nothing about fonts, family names, or typography. It is purely a vector drawing.

The name "An Emoji Family" came from the Python compiler (nanoemoji) itself.

When you run nanoemoji, the software has to create a brand new TrueType (.ttf) font container from scratch. Every TrueType font is required by the OpenType specification to have an internal “Family Name” written into its metadata tables (the name table).

Because you didn’t explicitly tell nanoemoji what you wanted the font to be called, it fell back to its hardcoded default. If you look at the source code for the nanoemoji tool on GitHub, its default font name setting is literally programmed as "An Emoji Family".

When nanoemoji finished building the Font.ttf file, it injected "An Emoji Family" into the font’s internal metadata.

Then, when the Typst compiler scanned the folder, it opened Font.ttf, read the internal metadata, and registered that name. That is why we had to type #set text(font: "An Emoji Family") to use it!

#set page(width: 15cm, height: auto, margin: 1cm)

= The Font Correction (via nanoemoji)

#let example-box(title, body) = block(
  fill: luma(245),
  inset: 10pt,
  radius: 4pt,
  width: 100%,
)[
  #text(weight: "bold", size: 11pt, title)\
  #v(4pt)
  #body
]

#example-box("Testing the Compiled Font")[
  #set text(font: ("Libertinus Serif", "An Emoji Family"), size: 14pt)
  
  This text is rendered using the `nanoemoji`-compiled TrueType font 
  loaded directly into Typst. We mapped it properly to `1f4d6.svg`, 
  so when we type the standard book emoji below, the font uses our vector graphic.

  *Result:* I am reading a 📖 today.
]
How the 📖 replaced with custom SVG?

The SVG graphic isn’t being “inserted” like an image file anymore. The 📖 character itself is literally triggering the font to draw your vector graphic.

Here is the exact step-by-step breakdown of how the 📖 symbol connects to your SVG code:

1. The 📖 is just a number

When you type 📖 on your keyboard, you aren’t actually typing a picture. You are typing a secret Unicode number: U+1F4D6. Every piece of text you type is just a stream of numbers. “A” is U+0041, and “:open_book:” is U+1F4D6.

2. The Filename was the secret link

Remember earlier when I said I renamed your book.svg file to 1f4d6.svg before compiling it?

When nanoemoji compiled the font, it looked at the filename (1f4d6), packed your SVG vector code into the TrueType file, and wrote a rule inside the font that said:

“If any program ever asks for character U+1F4D6, draw this specific vector graphic.”

3. How Typst reads the code

Now, look at the Typst code:

typst

#set text(font: ("Libertinus Serif", "An Emoji Family"))
*Result:* I am reading a 📖 today.

When Typst renders this sentence, it looks at every single letter one by one and asks the fonts for the drawing:

  1. Typst sees “I”: It asks the first font (Libertinus Serif), “Do you have ‘I’?” Libertinus says “Yes”, and draws a standard serif ‘I’.
  2. Typst sees " ": Libertinus draws a space.
  3. Typst sees “r e a d i n g”: Libertinus draws them.
  4. Typst sees 📖 (U+1F4D6): It asks Libertinus Serif, “Do you have a drawing for U+1F4D6?” Libertinus Serif says, “No, I’m just a text font. I don’t have emojis.”
  5. The Fallback kicks in: Because Typst failed to find it in Libertinus Serif, it moves to the second font in your list: "An Emoji Family".
  6. Typst asks the Emoji Font: “Do you have a drawing for U+1F4D6?” An Emoji Family says, “Yes! I was programmed to draw this specific colored vector shape whenever you ask for U+1F4D6!”

Typst then takes that vector shape (which is your exact Adobe Illustrator graphic, now living inside the font) and draws it perfectly in line with the text.

This is why fonts are so powerful: you aren’t manually placing images. You are just typing normal text, and the font is swapping the characters out for your custom vector drawings automatically!

USE