This guide explains how to create custom themes and add custom fonts to Papyrix.
Papyrix supports user-customizable themes stored on the SD card. Themes control colors, layout options, and fonts.
Theme files are stored in the /config/themes/ directory on the SD card:
/config/themes/
├── light.theme # Default light theme
├── dark.theme # Default dark theme
└── my-custom.theme # Your custom theme
When you first use the device, default light.theme and dark.theme files are created automatically.
- Copy example.theme or an existing theme file from your device
- Rename it (e.g.,
my-custom.theme) — use only letters, digits, hyphens, and underscores - Edit the file with any text editor
- Place it in
/config/themes/on your SD card - Restart the device and select your theme in Settings > Reader > Theme
- Maximum themes: 16 themes can be displayed in the Settings UI
- Theme name length: Maximum 31 characters
- Filename format: Theme filenames must contain only letters, digits, hyphens, and underscores (e.g.,
my-custom.theme,dark_v2.theme). Files with other characters are ignored. - Themes beyond the limit are ignored with a log warning (alphabetical order by filename)
- If a theme file is invalid or fails to parse, the device skips it and logs a warning
Theme files use a simple INI format:
# Papyrix Theme Configuration
# Edit values and restart device to apply
[theme]
name = My Custom Theme # Display name shown in Settings UI (optional)
[colors]
inverted_mode = false # true = dark mode, false = light mode
background = white # white or black
[selection]
fill_color = black # Selection highlight color
text_color = white # Text on selection
[text]
primary_color = black # Normal text color
secondary_color = black # Secondary/dimmed text color
[layout]
margin_top = 9 # Top margin in pixels
margin_side = 3 # Side margin in pixels
item_height = 30 # Menu item height
item_spacing = 0 # Space between menu items
[fonts]
reader_font_small = # Reader font for small size (empty = builtin)
reader_font_medium = # Reader font for medium size (empty = builtin)
reader_font_large = # Reader font for large size (empty = builtin)Optional metadata for the theme:
- name - Display name shown in the Settings UI
- If not specified, the filename (without extension) is used
- Example:
name = Dark Noto Serif
- inverted_mode - Enable dark mode (inverted colors)
- Values:
trueorfalse
- Values:
- background - Screen background color
- Values:
whiteorblack
- Values:
- fill_color - Highlight color for selected items
- Values:
whiteorblack
- Values:
- text_color - Text color on selected items
- Values:
whiteorblack
- Values:
- primary_color - Main text color
- Values:
whiteorblack
- Values:
- secondary_color - Secondary/dimmed text color
- Values:
whiteorblack
- Values:
- margin_top - Top screen margin in pixels
- Default:
9
- Default:
- margin_side - Side screen margins in pixels
- Default:
3
- Default:
- item_height - Height of menu items in pixels
- Default:
30 - Minimum:
1(values of 0 will cause errors) - Affects file browser and menu navigation (including long-press page skip)
- Note: Chapter selection screens use automatic 2-line item heights based on font size
- Default:
- item_spacing - Vertical space between items in pixels
- Default:
0
- Default:
Note: Front button layout (B/C/L/R vs L/R/B/C) and side button layout are now configured in Settings > Device instead of the theme file.
- reader_font_small - Custom reader font for small size (14pt)
- Leave empty to use builtin font
- reader_font_medium - Custom reader font for medium size (16pt)
- Leave empty to use builtin font
- reader_font_large - Custom reader font for large size (18pt)
- Leave empty to use builtin font
[colors]
inverted_mode = true
background = black
[selection]
fill_color = white
text_color = black
[text]
primary_color = white
secondary_color = white
[layout]
margin_top = 9
margin_side = 3
item_height = 30
item_spacing = 0
[fonts]
reader_font_small =
reader_font_medium =
reader_font_large =[colors]
inverted_mode = false
background = white
[selection]
fill_color = black
text_color = white
[text]
primary_color = black
secondary_color = black
[layout]
margin_top = 5
margin_side = 5
item_height = 25
item_spacing = 2
[fonts]
reader_font_small =
reader_font_medium =
reader_font_large =[colors]
inverted_mode = false
background = white
[selection]
fill_color = black
text_color = white
[text]
primary_color = black
secondary_color = black
[layout]
margin_top = 9
margin_side = 3
item_height = 30
item_spacing = 0
[fonts]
reader_font_small = noto-serif-14
reader_font_medium = noto-serif-16
reader_font_large = noto-serif-18This theme uses custom fonts:
- Reader (small):
/config/fonts/noto-serif-14/ - Reader (medium):
/config/fonts/noto-serif-16/ - Reader (large):
/config/fonts/noto-serif-18/
If any font directory doesn't exist, the device falls back to the builtin font for that size.
Papyrix supports loading custom fonts from the SD card. Fonts must be pre-converted to the .epdfont binary format.
Custom fonts are stored in the /config/fonts/ directory, organized by font family:
/config/fonts/
├── my-font/
│ ├── regular.epdfont
│ └── bold.epdfont # optional
└── another-font/
└── regular.epdfont
Each font family is a subdirectory containing style variants. Only regular.epdfont is required. Bold is loaded on demand when first encountered. Italic text renders using the regular variant. If the font is not found on SD card, the built-in font is used (with native italic support).
To create .epdfont files from TTF/OTF fonts, use the fontconvert.py script included in the firmware source code (scripts/fontconvert.py).
- Python 3.12+
- uv package manager (dependencies are handled automatically via inline script metadata)
Convert a font family with bold:
uv run scripts/fontconvert.py my-font \
-r MyFont-Regular.ttf \
-b MyFont-Bold.ttf \
--2bit \
-o /path/to/output/Convert only the regular style:
uv run scripts/fontconvert.py my-font -r MyFont-Regular.ttf --2bit -o /tmp/fonts/- -r, --regular - Path to regular style font (required for binary mode)
- -b, --bold - Path to bold style font
- -i, --italic - Path to italic style font
- -o, --output - Output directory (default: current directory)
- -s, --size-opt - Font size in points (default: 16)
- --2bit - Generate 2-bit grayscale (smoother but larger)
- --all-sizes - Generate all reader sizes (14, 16, 18pt)
- --header - Output C header instead of binary .epdfont
- --thai - Include Thai script (U+0E00-0E7F)
- --arabic - Include Arabic script (U+0600-06FF, Presentation Forms)
- --additional-intervals - Additional Unicode intervals as min,max (can be repeated)
# Convert with custom size
uv run scripts/fontconvert.py my-font -r Font.ttf --2bit -s 14 -o /tmp/fonts/
# Output directly to SD card
uv run scripts/fontconvert.py my-font -r Font.ttf --2bit -o /Volumes/SDCARD/config/fonts/
# Generate all sizes for reader font (14, 16, 18pt)
uv run scripts/fontconvert.py my-font -r Font.ttf --2bit --all-sizes -o /tmp/fonts/
# Include Thai script support
uv run scripts/fontconvert.py my-font -r NotoSansThai-Regular.ttf --2bit --thai -o /tmp/fonts/
# Generate C header for builtin fonts (original mode, outputs to stdout)
uv run scripts/fontconvert.py my_font 16 Font.ttf --2bit > my_font_16_2b.hThe script creates a font family directory structure:
my-font/
├── regular.epdfont
└── bold.epdfont # optional
With --all-sizes, separate directories are created for each size:
my-font-14/
├── regular.epdfont
└── bold.epdfont
my-font-16/
├── ...
my-font-18/
├── ...
Copy the entire folder(s) to /config/fonts/ on your SD card.
- Reader font (Small setting): 14pt
- Reader font (Normal setting): 16pt
- Reader font (Large setting): 18pt
- UI font: 14-16pt
Once you've created your font files, reference them in your theme configuration:
[fonts]
reader_font_small = my-font-14
reader_font_medium = my-font-16
reader_font_large = my-font-18Each font family name must match a directory name under /config/fonts/. You can use the same font for all sizes, or different fonts for each size.
By default, the font converter includes:
- Basic Latin (ASCII) - letters, digits, punctuation
- Latin-1 Supplement - Western European accented characters
- Latin Extended-A/B - Eastern European languages
- Latin Extended Additional - Vietnamese characters
- General punctuation - smart quotes, dashes, ellipsis
- Common currency symbols
- Cyrillic characters
- Combining diacritical marks
- Math operators and arrows
The built-in fonts include Vietnamese diacritics natively, so no custom font is needed. If you prefer a different typeface, Vietnamese fonts work with standard .epdfont format since they use Latin script with additional diacritics.
The built-in fonts include Thai script natively, so no custom font is needed. If you prefer a different typeface, Thai fonts can be generated using the --thai flag:
# Thai font with Thai script support
uv run scripts/fontconvert.py noto-sans-thai -r NotoSansThai-Regular.ttf --2bit --thai -o /tmp/fonts/The built-in fonts include Arabic script natively, so no custom font is needed. Arabic text in books is automatically shaped (contextual letter forms, Lam-Alef ligatures) and rendered right-to-left. Arabic support is available in reader mode for book text only (not in the UI). If you prefer a different typeface, Arabic fonts can be generated using the --arabic flag:
# Arabic font with Arabic script support
uv run scripts/fontconvert.py noto-sans-arabic -r NotoSansArabic-Regular.ttf -b NotoSansArabic-Bold.ttf --2bit --arabic -o /tmp/fonts/The ESP32-C3 has limited RAM (~380KB), so CJK fonts require external .bin format which streams glyphs from SD card. CJK fonts are supported for book text (reading view) only — UI elements (home screen, status bar, book title overlay) use built-in fonts without CJK glyphs. Pre-converted CJK fonts are available in the docs/examples/fonts/ directory. To convert your own CJK fonts, use scripts/gen_cjk_theme.sh (auto-downloads the converter binary) — see the Fonts Guide: CJK section for details.
If a custom font file is missing, corrupted, or exceeds size limits:
- The device automatically falls back to built-in fonts
- Console shows which font failed and why
Size limits:
.epdfontfiles: max 512KB bitmap data.binexternal fonts: max 32MB file size, max 64x64 pixel glyphs
Built-in fonts are always available:
- Reader - Reader font (3 sizes) with Latin, Cyrillic, Vietnamese, Thai, Greek, and Arabic coverage
- UI - UI font with Latin, Cyrillic, Vietnamese, Thai, Greek, and Arabic coverage
- Small - Small text
Note: Custom font loading is optional. The device works perfectly with built-in fonts if no custom fonts are configured.
Here's the complete SD card structure for customization:
/
├── config/
│ ├── calibre.ini
│ ├── themes/
│ │ ├── light.theme
│ │ ├── dark.theme
│ │ └── custom.theme
│ └── fonts/
│ ├── my-reader-font/
│ │ ├── regular.epdfont
│ │ └── bold.epdfont # optional
│ └── my-ui-font/
│ └── regular.epdfont
├── sleep.bmp # Custom sleep image (optional)
└── sleep/ # Multiple sleep images (optional)
├── image1.bmp
└── image2.bmp
The repository includes example theme and font files in docs/examples/:
Themes:
light-noto-serif.theme- Light theme with Noto Serif reader fonts (Latin script)light-noto-sans.theme- Light theme with Noto Sans reader fontslight-pt-serif.theme- Light theme with PT Serif reader fontslight-literata.theme- Light theme with Literata reader fontslight-roboto.theme- Light theme with Roboto reader fontslight-opendyslexic.theme- Light theme with OpenDyslexic reader fontslight-thai.theme- Light theme with Noto Sans Thai fontslight-vietnamese.theme- Light theme with Noto Serif Vietnamese fontslight-arabic.theme- Light theme with Noto Sans Arabic fontslight-noto-sans-sc.theme- Light theme with Noto Sans SC (Simplified Chinese) CJK fontlight-noto-sans-jp.theme- Light theme with Noto Sans JP (Japanese) CJK font
Fonts:
fonts/noto-serif-*/- Noto Serif at 14pt, 16pt, 18pt (Latin script)fonts/noto-sans-*/- Noto Sans at 14pt, 16pt, 18ptfonts/pt-serif-*/- PT Serif at 14pt, 16pt, 18ptfonts/literata-*/- Literata at 14pt, 16pt, 18ptfonts/roboto-*/- Roboto at 14pt, 16pt, 18ptfonts/opendyslexic-*/- OpenDyslexic at 14pt, 16pt, 18ptfonts/noto-sans-thai-*/- Noto Sans Thai at 14pt, 16pt, 18ptfonts/noto-serif-vn-*/- Noto Serif Vietnamese at 14pt, 16pt, 18ptfonts/noto-sans-arabic-*/- Noto Sans Arabic at 12pt, 14pt, 16pt, 18ptfonts/*.bin- CJK external fonts (Source Han Sans CN, KingHwaOldSong)
To use a theme:
- Copy the
.themefile to/config/themes/on your SD card - Copy the corresponding font folders to
/config/fonts/on your SD card - Select the theme in Settings > Reader > Theme
The example fonts use:
- Noto Serif from Google Fonts (SIL OFL)
- Noto Sans Thai from Google Fonts (SIL OFL)
- Noto Sans Arabic from Google Fonts (SIL OFL)
- Source Han Sans CN from Adobe (SIL OFL)
- KingHwaOldSong (traditional Chinese font)
All fonts are licensed under the SIL Open Font License (OFL).