SDL Reader is a lightweight, cross-platform document viewer built with SDL2 and MuPDF. It supports viewing PDF, CBZ/ZIP & CBR/RAR comic archives, EPUB books, MOBI e-books, and plain text files with intuitive navigation, zooming, rotation, and mirroring features. Optimized for embedded devices like the TrimUI Brick and TrimUI Smart Pro running NextUI. It also runs on desktop platforms including macOS, Linux, and is a work in progress as Wii U homebrew.
- Features
- Supported Document Types
- TrimUI Control Scheme
- Build Instructions
- Usage
- TG5040 Deployment
- User Inputs
- Project Structure
- Architecture
- View PDF documents, comic book archives (CBZ/ZIP & CBR/RAR), EPUB books, and MOBI e-books.
- Built-in heads-up display with page, zoom, edge-turn, minimap, and error indicators — now including an optional Document Minimap Overlay that mirrors your zoomed-in viewport (toggleable in the font/reading style panel).
- Integrated Nuklear-powered file browser (
--browse) with:- Controller support
- Persistent last directory
- Asynchronous multi-threaded thumbnail grid (toggle with X)
- Color-coded tile badges for file types
- Smarter auto-scroll and restore behavior for faster navigation
- TrimUI Brick/Smart Pro-friendly layout
- Stateful Home Directory Usage: runtime assets (config, fonts list, and
reading_history.json) now live under$SDL_READER_STATE_DIR(default$HOME) so settings persist across firmware/app updates and can be redirected per device. - Custom font picker with reading style themes, MuPDF-backed CSS injection, controller-friendly navigation, hold-to-scroll, inline tooltips, info glyphs, and persistent highlights.
- On-screen number pad for page jumps when navigating with a controller.
- Automatic reading history tracking with resume-on-open for the last 50 documents.
- Page navigation (next/previous page) with Smart Edge Navigation: when zoomed ≥100% & at a page edge, holding the D-pad for 300 ms flips pages with a progress indicator.
- Quick page jumping (±10 pages) and arbitrary page entry.
- Zoom in/out, fit-to-width, high-maximum zoom levels, optimized downsampling paths, and improved caching for smoother zoom/pan performance (notably on TrimUI Brick/Smart Pro devices).
- Page rotation (90° increments) and horizontal/vertical mirroring.
- Smooth scrolling within pages (if zoomed in or if the page is larger than the viewport).
- Toggle fullscreen mode (desktop platforms).
- TrimUI Brick/Smart Pro power button integration with a dedicated handler, fake sleep fallback, resume cleanup, and on-screen messaging for console-like behavior.
![]() |
![]() |
![]() |
![]() |
- PDF (
.pdf) - Comic Book Archive (
.cbz,.cbr,.rar,.zipcontaining images)- Enhanced WebP Support: Supports WebP images within comic book archives
- EPUB (
.epub) - MOBI (
.mobi) - Plain Text (
.txt)
This project supports multiple platforms with a unified build system.
- TG5040 - TrimUI Brick and TrimUI Smart Pro - Embedded Linux devices (default)
- macOS - Desktop development and testing
- Wii U - Nintendo Wii U homebrew (requires devkitPro)
- Linux - Desktop Linux distributions (tested on Ubuntu 24.04)
# Build for default platform (TG5040)
make
# Build for specific platform
make tg5040 # TG5040 embedded device (TrimUI Brick and Smart Pro)
make mac # macOS
make wiiu # Wii U (requires devkitPro environment)
make linux # Linux desktop
# Export TG5040 distribution bundle
make export-tg5040 # Build and create complete TG5040 package
# List available platforms
make list-platforms
# Clean all build artifacts
make clean
# Show help
make help- Prebuilt cross-compilation toolchain container:
ghcr.io/loveretro/tg5040-toolchain - Docker environment available (see
ports/tg5040/for Compose/Makefile helpers) - Limited external dependencies: MuPDF & libarchive built automatically for optimal bundle size
- A C++17 compatible compiler (e.g., g++, clang++)
SDL2andSDL2_ttfdevelopment librarieslibarchivedevelopment library (for CBR support)libwebpdevelopment library (for WebP image support)git(for automatic MuPDF download)pkg-config
Install dependencies using Homebrew:
brew install sdl2 sdl2_ttf libarchive webp libarchive git pkg-configNote: MuPDF is now built automatically from source with CBR and WebP support - no system MuPDF packages needed.
- A C++17 compatible compiler (e.g., g++, clang++)
SDL2andSDL2_ttfdevelopment librarieslibarchivedevelopment library (for CBR support)libwebpdevelopment library (for WebP image support)git(for automatic MuPDF download)pkg-config
Tested on Ubuntu 24.04. Other distributions may require different packages.
Install dependencies (Ubuntu/Debian):
sudo apt install build-essential pkg-config libsdl2-dev libsdl2-ttf-dev libfreetype6-dev libharfbuzz-dev libjpeg-dev libopenjp2-7-dev libjbig2dec0-dev libgumbo-dev libmujs-dev libarchive-dev libwebp-dev gitNote: MuPDF is now built automatically from source with CBR and WebP support - no system MuPDF packages needed.
Or use the automated installer:
cd ports/linux && make install-deps- devkitPro toolchain with WUT (Wii U Toolchain)
- See
ports/wiiu/for Wii U-specific build instructions - Note: WebP support may be limited due to devkitPro library availability
All make targets download MuPDF 1.26.7 and apply webp-upstream-697749.patch (sourced from KOReader) to enable modern WebP decoding and fix upstream regressions. Platform exports embed Nuklear for UI rendering. See each port README for platform-specific details.
- Advanced Power Management: NextUI-compatible power button integration
- Short press: Intelligent sleep with fallback to fake sleep mode
- Fake sleep: Black screen with input blocking when hardware sleep unavailable
- Automatic deep sleep attempts in background during fake sleep
- Long press (2+ seconds): Safe shutdown
- Smart error handling: No immediate errors, 30-second timeout before user notification
- Event flushing on wake to prevent phantom button presses
- Device-specific Input:
/dev/input/event1monitoring for power events - System Integration: NextUI-compatible suspend/shutdown scripts
- Bundle Export: Complete distribution package creation
- Self-contained bundle with all dependencies
- Automated library bundling and RPATH setup
- Includes utilities (jq, minui-list) and resources
- Ready for device deployment via
make export-tg5040
- Desktop Environment: Standard desktop window management
- Development Platform: Full debugging and development tools available
- Cross-platform Testing: Verify functionality before deployment
- Desktop Environment: Standard Linux desktop window management
- Package Management: Easy dependency installation via system package managers
- Development Platform: Full debugging and development tools available
- Tested Platform: Ubuntu 24.04 (other distributions may require additional setup)
- Homebrew Environment: Nintendo Wii U specific adaptations
- Custom Input Handling: Gamepad and touch screen support
After building, you can either launch straight into a document or drop into the integrated browser:
# Open a specific file directly
./bin/sdl_reader_cli path/to/your_document.pdf
./bin/sdl_reader_cli path/to/your_comic.cbz
./bin/sdl_reader_cli path/to/your_book.epub
./bin/sdl_reader_cli path/to/your_ebook.mobi
# Launch the controller-friendly file browser (saves last directory)
./bin/sdl_reader_cli --browseWhen using --browse, SDL Reader will remember the last directory you visited (stored in config.json) and automatically resume the last page you read for each document (stored in reading_history.json). Both files live in the reader state directory ($SDL_READER_STATE_DIR, defaulting to $HOME).
- Press
X(or the controllerXbutton) to toggle a high-performance thumbnail grid that previews covers and caches results in the background. - Thumbnails are generated asynchronously so scrolling stays responsive even on large folders.
- The browser respects
SDL_READER_DEFAULT_DIR; set this environment variable to confine browsing to a specific root directory.
SDL Reader uses a config.json file (stored under $SDL_READER_STATE_DIR, defaulting to $HOME/config.json) for customizing font settings and display options.
-
Copy the example configuration file:
cp config.json.example "$HOME/config.json" -
Edit
config.jsonto customize settings:{ "fontPath": "./fonts/JetBrainsMono-Bold.ttf", "fontName": "JetBrains Mono Bold", "fontSize": 16, "zoomStep": 10, "readingStyle": 0, "disableEdgeProgressBar": false, "showDocumentMinimap": true, "lastBrowseDirectory": "/path/to/library" }
- fontPath: Path to the TTF/OTF font file to use for documents that support CSS/reflow (EPUB/MOBI/TXT)
- fontName: Display name for the font (shown in the font menu)
- fontSize: Default font size in points applied by the CSS generator
- zoomStep: Percentage increment for zoom operations and controller zoom buttons
- readingStyle: Numeric identifier for the active reading theme (see table below)
- lastBrowseDirectory: Directory the file browser should open by default when launched with
--browse - disableEdgeProgressBar: When
true, panning at page edges changes pages instantly without the 300ms delay and progress bar. Whenfalse(default), the edge nudge progress bar is shown. - showDocumentMinimap: Toggle the zoomed-in minimap overlay; set to
falseto hide it. - State directory override: Set
SDL_READER_STATE_DIRto relocateconfig.json,reading_history.json, and other runtime assets. Defaults to your$HOMEdirectory. - Environment override: Set
SDL_READER_DEFAULT_DIRto control the starting directory for the browser. If unset, the reader defaults to$HOME.
readingStyle |
Theme | Background | Text Color |
|---|---|---|---|
| 0 | Document Default | Unchanged | Unchanged |
| 1 | Sepia | #f4ecd8 | #5c4a3a |
| 2 | Dark Mode | #1e1e1e | #d4d4d4 |
| 3 | High Contrast | #ffffff | #000000 |
| 4 | Paper Texture | #faf8f3 | #2c2c2c |
| 5 | Soft Gray | #e8e8e8 | #333333 |
| 6 | Night Mode | #0d0d0d | #c9c9c9 |
All configuration values are saved automatically when you apply changes from the in-app font menu.
Note: Runtime config.json files are stored outside the repository (in the reader state directory) and are ignored by Git so you can personalize settings without affecting the repo.
Adding new fonts: Drop any .ttf or .otf files into the top-level fonts/ directory (either on desktop or inside a TG5040 bundle). The Options → Font & Reading Style menu will automatically discover them, let you preview the typography, and persist your selection for EPUB/MOBI/TXT documents.
The packaged builds include curated open fonts:
- Inter (Regular, Bold) — SIL Open Font License 1.1
- JetBrains Mono (Regular, Bold) — SIL Open Font License 1.1
- Noto Serif Condensed (Regular, Bold) — SIL Open Font License 1.1
- Roboto (Regular, Bold) — Apache License 2.0
Full license texts are provided in fonts/LICENSES.md. Keep these notices with any redistributed bundle.
SDL Reader keeps a lightweight reading_history.json file in the reader state directory ($SDL_READER_STATE_DIR, defaulting to $HOME/reading_history.json). Every time you change pages, the current document path and page number are persisted so the next launch resumes automatically. The history remembers the most recent 50 documents. Delete the file if you want to reset all progress.
For TG5040 (Trimui Brick) deployment, use the bundle export system:
# Build and create complete distribution bundle
make export-tg5040
# Or build first, then export
make tg5040
make export-tg5040The exported bundle at ports/tg5040/pak/ contains:
- bin/:
sdl_reader_cli(legacy utilities such asjqandminui-listare preserved if you copy them in before exporting) - lib/: All shared library dependencies with proper RPATH setup
- fonts/: All bundled font files ready for the runtime picker
- res/: Optional resources (e.g., documentation PDFs)
- launch.sh: Main launcher script that boots straight into the Nuklear file browser
- README.md / pak.json: Copied for reference inside the bundle
- Copy the entire
ports/tg5040/pak/directory to your TG5040 device - Ensure the package has executable permissions
- Run via the launch script or execute binaries directly
The bundle is completely self-contained and includes all necessary dependencies for the TG5040 platform.
The SDL Reader supports the following keyboard, mouse, and game controller inputs:
| Button/Input | Action |
|---|---|
| D-Pad | |
D-Pad Up/Down/Left/Right |
Scroll/Pan in direction |
D-Pad (at page edge) |
Hold for 300ms to turn page (with progress indicator) |
| Shoulder Buttons | |
L1 (Left Shoulder) |
Previous page |
R1 (Right Shoulder) |
Next page |
L1 + R1 (Both Shoulders) |
Toggle font & reading style menu |
L2 (Left Trigger) |
Jump back 10 pages |
R2 (Right Trigger) |
Jump forward 10 pages |
L2 + R2 (Both Triggers) |
Reset page view |
| Face Buttons | |
B |
Fit page to width |
A |
Zoom out |
X |
Zoom in |
Y |
Rotate page clockwise (90°) |
| Function Buttons | |
F1 |
Reset page view |
F2 |
Zoom to 200% |
| System Buttons | |
Start |
Toggle horizontal mirror |
Select |
Toggle vertical mirror |
Menu (button 10) |
Toggle font & reading style menu |
Home (Guide) |
Quit application |
| Input | Action |
|---|---|
Q or Esc |
Quit application |
Right Arrow |
Scroll right |
Left Arrow |
Scroll left |
Up Arrow |
Scroll up |
Down Arrow |
Scroll down |
Page Down |
Go to next page |
Page Up |
Go to previous page |
+ (Plus) |
Zoom in |
- (Minus) |
Zoom out |
Numpad + / Numpad - |
Zoom in/out (numpad support) |
F |
Toggle Fullscreen |
G |
Jump to Page |
M |
Toggle font & reading style menu |
W |
Fit page to width |
R |
Reset page view |
Shift + R |
Rotate page clockwise (90°) |
H |
Toggle horizontal mirror |
V |
Toggle vertical mirror |
[ (Left Bracket) |
Jump back 10 pages |
] (Right Bracket) |
Jump forward 10 pages |
| Input | Action |
|---|---|
Mouse Wheel Up |
Scroll up |
Mouse Wheel Down |
Scroll down |
Ctrl + Mouse Wheel Up |
Zoom in |
Ctrl + Mouse Wheel Down |
Zoom out |
Left Click + Drag |
Pan/Scroll |
| Button/Input | Action |
|---|---|
| D-Pad | |
D-Pad Up/Down/Left/Right |
Scroll/Pan in direction |
D-Pad (zoomed ≥ 100% & at page edge) |
Hold for 300ms to turn page (with progress indicator) |
| Shoulder Buttons | |
L1 (Left Shoulder) |
Previous page |
R1 (Right Shoulder) |
Next page |
L1 + R1 (Both Shoulders) |
Toggle font & reading style menu |
L2 (Left Trigger) |
Jump back 10 pages |
R2 (Right Trigger) |
Jump forward 10 pages |
L2 + R2 (Both Triggers) |
Reset page view |
| Face Buttons | |
A |
Fit page to width |
B |
Zoom out |
X |
Rotate page clockwise (90°) |
Y |
Zoom in |
| System Buttons | |
Start |
Toggle font & reading style menu |
Back/Select |
Toggle vertical mirror |
Guide/Menu |
Quit application |
| Analog Sticks | |
Left/Right Stick X-Axis |
Scroll horizontally |
Left/Right Stick Y-Axis |
Scroll vertically |
| Button/Input | Action |
|---|---|
| D-Pad | |
D-Pad Up/Down |
Move selection up/down |
D-Pad Left/Right (List View) |
Jump pages in file list |
D-Pad Left/Right (Thumbnail View) |
Move selection horizontally in grid |
| Shoulder Buttons | |
L1 (Left Shoulder) |
Jump selection backward by first letter |
R1 (Right Shoulder) |
Jump selection forward by first letter |
| Face Buttons | |
A |
Select/Open file or directory |
B |
Go to parent directory |
X |
Toggle between list and thumbnail view |
| System Buttons | |
Guide/Menu |
Quit application |
Note: Controller button names may vary depending on your controller type (Xbox, PlayStation, etc.). The mappings above use SDL2's standardized button names.
Note 2: When using an Xbox controller, D-Pad Left & Right appear to trigger the Brick F1 & F2 functionality in addition to nudging.
SDL Reader includes an intelligent edge navigation system for smooth page turning with game controllers:
- Edge Detection: When zoomed in ≥ 100%, using D-pad controls and reaching a page edge (left, right, top, or bottom), the system detects you're at the boundary
- Hold to Turn: Continue holding the D-pad direction for 300ms to initiate page turning (configurable - see below)
- Visual Feedback: A progress bar appears showing:
- Direction of pending page change (e.g., "Next Page", "Previous Page")
- Progress indicator that fills as you approach the 300ms threshold
- Color transitions from yellow to green as the timer progresses
- Immediate Cancellation: Release the D-pad before 300ms to stay on the current page
- Seamless Transition: After page change, you appear at the appropriate edge of the new page for continuous navigation
You can disable the 300ms delay and progress bar for instant page turns:
- Via Settings Menu: Open Settings (Menu button on TG5040,
Mkey on desktop) → Page Navigation section → Check "Disable Edge Progress Bar" - Via config.json: Set
"disableEdgeProgressBar": truein your configuration file - When enabled, panning at page edges will change pages immediately without any delay or visual indicator
- Fully Zoomed Out: When the page fits entirely within the window
- At Scroll Limits: When zoomed in and you've reached the maximum scroll position in any direction
- D-pad Only: This feature works with game controller D-pads, not keyboard arrow keys
- Prevents Accidental Page Changes: No more accidentally flipping pages when trying to scroll
- Intuitive Control: Natural feel for gaming device users
- Visual Clarity: Always know when a page change is about to happen
SDLReader-brick/
├── Makefile # Main multi-platform build system
├── src/ # Shared source code
├── include/ # Shared header files
├── cli/ # Command-line interface
├── fonts/ # Font files (available to the font picker)
├── config.json.example # Sample runtime configuration
├── reading_history.json # Runtime cache (stored in the reader state directory)
└── ports/ # Platform-specific builds
├── tg5040/ # TG5040 embedded device
│ ├── Makefile # TG5040 build configuration
│ ├── Makefile.docker # Docker environment (pulls ghcr.io/loveretro/tg5040-toolchain)
│ ├── docker-compose.yml # Docker Compose setup (prebuilt toolchain)
│ ├── Dockerfile # Thin wrapper for the GHCR toolchain image
│ ├── export_bundle.sh # Bundle export script
│ ├── make_bundle2.sh # Library dependency bundler
│ ├── pak/ # Distribution bundle (created by export)
│ │ ├── bin/ # Executables (sdl_reader_cli + optional utilities)
│ │ ├── lib/ # Shared libraries and dependencies
│ │ ├── fonts/ # Font files
│ │ ├── res/ # Other resources (if any)
│ │ └── launch.sh # Main launcher script
│ ├── include/ # TG5040-specific headers
│ │ └── power_handler.h # Power management for TG5040
│ └── src/ # TG5040-specific source code
│ └── power_handler.cpp # Power button handling implementation
├── mac/ # macOS desktop
│ └── Makefile # macOS build configuration
├── linux/ # Linux desktop
│ ├── Makefile # Linux build configuration
│ └── README.md # Linux-specific instructions
└── wiiu/ # Nintendo Wii U homebrew
└── Makefile # Wii U build configuration
- Cross-platform code: Modify files in
src/,include/,cli/ - Platform-specific code: Use conditional compilation (
#ifdef TG5040_PLATFORM) or create port-specific implementations inports/{platform}/ - Power management: TG5040 includes hardware-specific power button handling and suspend/resume functionality
- Build system: Each platform has its own Makefile in
ports/{platform}/ - Docker development: Use
ports/tg5040/docker-compose.ymlto pullghcr.io/loveretro/tg5040-toolchain(workspace mounts at/workspace) - TG5040 distribution: Use
make export-tg5040to create complete deployment package
For detailed information about the multi-platform design, conditional compilation strategies, and guidelines for adding new platforms, see ARCHITECTURE.md.
Key architectural highlights:
- Clean separation between shared and platform-specific code
- Conditional compilation for platform-specific features
- Port-specific implementations for hardware integration (e.g., TG5040 power management)
- Unified build system with platform-specific Makefiles
- Scalable design for easy addition of new platforms
hito16, for starting the original SDl Reader project.- rofl0r/SDLBook, for inspiring hito16.
- LoveRetro/NextUI, for creating an excellent OS for the TrimUI Brick.
josegonzalez, for minui-list and countless other tools.- UncleJunVIP/nextui-pak-store for the Pak Store
- Immediate-Mode-UI/Nuklear, for the Nuklear UI framework powering the overlay, browser, and font menus.
- koreader/koreader, for the MuPDF WebP Patch.
- Claude.ai, for creating Sonnet 4. I’m not a C++ programmer, but Sonnet gave me a fighting chance at getting this done in a reasonable timeframe.
In order to comply with the MuPDF license, this project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the LICENSE file for details.





