PlantUML CJK / Emoji / special-character rendering foot-guns and font setup

puml.online

PlantUML’s default font DejaVu Sans only ships Latin glyphs. Chinese / Japanese / Korean / Emoji either render as boxes or vanish entirely. This is the font configuration field guide + CI environment setup + multi-language mixing.

Three common symptoms

Symptom 1: Chinese renders as □□□

1
2
3
4
@startuml
skinparam defaultFontName "DejaVu Sans"
Alice -> Bob: 你好世界
@enduml

The SVG shows Chinese as three empty boxes. DejaVu Sans has no CJK glyphs.

Symptom 2: Emoji displays as black-and-white text

1
2
3
@startuml
Alice -> Bob: 🚀 deploy success 🎉
@enduml

Emoji becomes characters like < > instead of colored icons. DejaVu Sans has no Emoji font.

Symptom 3: Japanese kana garbled

1
2
3
@startuml
note left: こんにちは世界
@enduml

Some kana renders, some doesn’t — the font only ships partial Japanese character sets (e.g. only Hiragana, no Katakana).

Fix 1: switch fonts (server / Docker side)

Install CJK fonts on Linux server

1
2
3
4
5
6
7
8
# Debian/Ubuntu
sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra

# RHEL/CentOS
sudo yum install -y google-noto-sans-cjk-fonts

# Alpine (common in slim images)
apk add --no-cache font-noto-cjk

After installing, restart plantuml server to refresh the font cache.

Install fonts inside Docker image

Modify Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
FROM plantuml/plantuml-server:tomcat

USER root

RUN apt-get update && \
apt-get install -y --no-install-recommends \
fonts-noto-cjk \
fonts-noto-color-emoji && \
rm -rf /var/lib/apt/lists/*

USER plantuml
1
docker build -t my-plantuml:cn .

PlantUML: specify font

1
2
3
4
5
6
@startuml
skinparam defaultFontName "Noto Sans CJK SC"
skinparam defaultFontSize 14

Alice -> Bob: 你好世界
@enduml

Or via CLI:

1
plantuml -SdefaultFontName="Noto Sans CJK SC" diagram.puml

Fix 2: Emoji font

Install Noto Color Emoji

1
2
3
4
5
6
# Debian/Ubuntu
sudo apt-get install -y fonts-noto-color-emoji

# verify
fc-list | grep -i emoji
# /usr/share/fonts/truetype/noto/NotoColorEmoji.ttf: Noto Color Emoji:style=Regular

PlantUML configuration

PlantUML uses <fontconfig> for automatic matching, but you must declare intent in the diagram:

1
2
3
4
5
6
7
@startuml
skinparam defaultFontName "Noto Sans CJK SC"
skinparam sequenceMessageAlign center

Alice -> Bob: 🚀 deploy success 🎉
note right: Chinese note with Noto Color Emoji
@enduml

Emoji uses Noto Color Emoji, Chinese uses Noto Sans CJK SC. Two fonts coexist, fontconfig falls back automatically.

Foot-gun: PlantUML does not support per-character font switching — if a single line contains both Chinese and Emoji, the Emoji may get rendered by the CJK font (which has no glyph) → Emoji shows as empty box.

Fix — isolate with <font> tags:

1
Alice -> Bob: <font color=red>🚀</font> deploy success <font color=green>🎉</font>

Or just separate into multiple lines:

1
2
3
Alice -> Bob: 🚀
Alice -> Bob: deploy success
Alice -> Bob: 🎉

Fix 3: multi-language mixing

When mixing Chinese / English / Japanese / Korean / Emoji / Arabic / Latin, the font fallback chain is critical.

Linux fontconfig configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- /etc/fonts/conf.d/99-cjk.conf -->
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>sans-serif</family>
<prefer>
<family>Noto Sans CJK SC</family>
<family>Noto Sans CJK TC</family>
<family>Noto Sans CJK JP</family>
<family>Noto Sans CJK KR</family>
<family>Noto Color Emoji</family>
<family>DejaVu Sans</family>
</prefer>
</alias>
</fontconfig>

Fonts in <prefer> are tried in priority order: Chinese goes to SC, Emoji to Color Emoji, Latin to DejaVu.

PlantUML multi-font fallback

PlantUML 1.2024+ supports <font> nesting:

1
2
3
4
@startuml
skinparam defaultFontName "Noto Sans CJK SC,DejaVu Sans"
Alice -> Bob: 你好 Hello world 🚀
@enduml

Comma-separated font names, PlantUML uses fontconfig to find the first font that can render the glyph.

Fix 4: Mac / Windows local rendering

When rendering locally (e.g. hexo generate with plantuml CLI), fonts must also be installed:

1
2
3
4
5
# macOS — PingFang SC is system default
fc-list | grep -i "pingfang"

# Windows — install Source Han Sans / Noto CJK
winget install Noto.Sans.CJK

hexo-renderer-plantuml invokes plantuml which uses <system> fonts by default — macOS uses PingFang, Windows uses Microsoft YaHei, automatically.

CI environment

GitHub Actions default ubuntu-latest has no CJK fonts. Install them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .github/workflows/diagrams.yml
jobs:
render:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install CJK fonts
run: |
sudo apt-get update
sudo apt-get install -y fonts-noto-cjk fonts-noto-color-emoji
- name: Render diagrams
run: |
docker run --rm -v $(pwd):/data \
-e LANG=en_US.UTF-8 \
plantuml/plantuml \
-tsvg -failfast2 /data/docs/diagrams/*.puml

LANG=en_US.UTF-8 makes plantuml use UTF-8 charset — otherwise it may default to ASCII and garble Chinese.

Font subsetting (smaller image)

Full Noto Sans CJK SC is ~20MB. Pushing that into a Docker image is heavy.

Subset to only used characters:

1
2
3
4
5
6
7
# extract subset with fonttools
pip install fonttools brotli
pyftsubset NotoSansCJKsc-Regular.otf \
--text="$(cat docs/diagrams/*.puml | grep -oP '[\u4e00-\u9fff]+' | sort -u | tr -d '\n')" \
--output-file=NotoSansCJKsc-SC-subset.otf

# typically drops from 20MB to 200KB-2MB

Dockerfile installs subset:

1
2
3
4
FROM plantuml/plantuml-server:tomcat

COPY NotoSansCJKsc-SC-subset.otf /usr/share/fonts/truetype/noto/
RUN fc-cache -fv

Font size / line-height tuning

CJK character width differs from Latin width — PlantUML’s default line height causes misalignment in mixed text.

1
2
3
4
5
6
7
8
9
10
11
@startuml
skinparam defaultFontName "Noto Sans CJK SC"
skinparam defaultFontSize 14
skinparam dpi 150
skinparam Padding 5

note left
This is a Chinese note
Mixed 中英文 line
end note
@enduml

Padding 5 makes box inner padding enough for Chinese characters, dpi 150 produces sharper output (default 96).

Debugging flow

1
2
3
4
5
6
7
8
9
10
11
# 1. verify fonts installed
fc-list | grep -i "noto sans cjk"

# 2. verify plantuml recognizes the font
echo 'alice -> bob: 你好' | plantuml -pipe -tsvg > /tmp/test.svg
grep -o "Noto Sans CJK SC\|DejaVu Sans" /tmp/test.svg | sort -u
# should print Noto Sans CJK SC

# 3. verify fontconfig fallback chain
fc-match "Noto Sans CJK SC"
# /usr/share/fonts/.../NotoSansCJKsc-Regular.otf: "Noto Sans CJK SC" "Regular"

If fc-match returns the wrong font → fontconfig config is wrong. Check the priority of files under /etc/fonts/conf.d/.

Summary

Scenario Fix
Chinese/Japanese as boxes Install fonts-noto-cjk, PlantUML specify defaultFontName
Emoji displays as text Install fonts-noto-color-emoji, alongside CJK fonts
Mixed CJK/Latin misaligned Padding 5, dpi 150
CI renders Chinese garbled apt install fonts + LANG=en_US.UTF-8
Docker image too large pyftsubset extract the characters actually used by .puml
Multi-language mix fontconfig <prefer> chain, fonts tried by priority

Minimum setup — install fonts-noto-cjk + fonts-noto-color-emoji on the server, restart plantuml, covers 95% of multi-language needs.

  • Title: PlantUML CJK / Emoji / special-character rendering foot-guns and font setup
  • Author: puml.online
  • Created at : 2026-07-30 16:50:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-cjk-emoji-encoding-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.