If you've ever tried to generate a PDF with jsPDF that contains Cyrillic (or really any non-Latin text), you've probably seen this: instead of your text, you get garbage characters, question marks, or nothing at all.
I hit this exact wall while building an invoicing app that needed to output quotes in both English and Bulgarian. Here's how I solved it properly.
Why it happens
jsPDF ships with the standard PDF fonts (Helvetica, Times, Courier). These fonts simply don't contain Cyrillic glyphs. So when you ask jsPDF to render "Оферта", it has nothing to draw.
The common "fix" you'll see online is transliteration — converting "Оферта" to "Oferta". But that's not a fix. Your Bulgarian client gets a document in mangled Latin letters, and it looks unprofessional.
The real solution: embed a Unicode font
The proper way is to embed a font that actually contains Cyrillic glyphs. I used DejaVu Sans — it's free, open source, and covers Latin + Cyrillic in one file.
The steps:
- Convert the
.ttffont file to a base64 string - Register it with jsPDF using
addFileToVFSandaddFont - Set it as the active font before writing text
Here's the registration part:
import { jsPDF } from "jspdf";
export function registerCyrillicFont(doc) {
doc.addFileToVFS("DejaVuSans.ttf", DejaVuSansBase64);
doc.addFont("DejaVuSans.ttf", "DejaVuSans", "normal");
doc.addFileToVFS("DejaVuSans-Bold.ttf", DejaVuSansBoldBase64);
doc.addFont("DejaVuSans-Bold.ttf", "DejaVuSans", "bold");
}
Then before writing any text:
const doc = new jsPDF();
registerCyrillicFont(doc);
doc.setFont("DejaVuSans", "normal");
doc.text("Оферта за строителни материали", 20, 20); // renders perfectly
Converting the font to base64
You can do this in a few lines of Python:
import base64
with open("DejaVuSans.ttf", "rb") as f:
encoded = base64.b64encode(f.read()).decode("ascii")
# save `encoded` into a .js file as a string constant
The base64 string is large (~1MB for the regular weight), so keep it in a separate file and import it — don't inline it in your main component.
A gotcha: bold weight
If you use doc.setFont("DejaVuSans", "bold") but only registered the regular weight, jsPDF silently falls back to a font that has no Cyrillic — and you're back to broken text. Register both the regular and bold .ttf files, as shown above.
The result
Clean, professional PDFs in both languages, from the same codebase — no transliteration, no broken characters.
I actually built this into a full invoicing & quote app (React + Capacitor). If you want to see the whole thing in action, there's a demo here: https://youtu.be/uxR4yrXYjV8
And the full source code is available if you'd rather not build invoicing from scratch: https://antovtodor1.gumroad.com/l/quote-studio
Hope this saves someone the hours I spent figuring it out. Happy to answer questions in the comments!
javascript, react, webdev, tutorial
Top comments (1)
Great tutorial, thanks for sharing 👍🏼