A native C++ / Qt Widgets PDF, EPUB, and DjVu reader with multiple document tabs, continuous vertical scrolling, and zoom from 10% to 500%.
The window title shows the release tag, for example Document Viewer v1.0.2.
Builds between releases also show the commit distance and hash; builds without Git
tags show dev. Set -DDOCUMENT_VIEWER_VERSION=v1.0.2 when building from a source
archive to supply the version explicitly.
Viewer source is licensed under the MIT License. Qt, PDFium, and other bundled third-party components retain their respective licenses. The DjVu-enabled application includes GPL-2.0-or-later DjVuLibre; distribution of the combined application must comply with that license. See third-party notices.
Only one instance runs per Windows login session (per user on Linux/macOS). A
second launch forwards its file paths to the running window, then exits. For example,
DocumentViewer first.pdf followed by DocumentViewer second.epub opens both
documents in the same window. Multiple paths per command are supported; quote
paths containing spaces. Relative paths use the launching terminal?s directory. Instance
locks are released or recovered after exit or a crash.
Each new document opens with its widest page filling 80% of the viewer width, in its own closable, reorderable tab that retains its scroll position and zoom. Restored documents keep their saved zoom. Select PDFs, EPUBs, and DjVu documents in the open dialog, drop multiple files, or pass multiple file paths at startup. Closing the last tab returns to the welcome view. Opening a file that is already open switches to its existing tab, preserving its position, zoom, and search. Relative paths and symbolic links to the same document are recognized; separate files with the same name still get separate tabs.
The app automatically saves the tab order, active tab, each document's horizontal and vertical scroll positions, zoom percentage and fit mode, and window geometry on close and every five seconds. The next launch restores the session before opening any files supplied on the command line. Checkpoints use atomic file replacement so an interrupted write preserves the previous saved session. After a crash, the latest completed checkpoint is restored.
Right-click and choose Register file types to add this installation to Open With
for PDF, EPUB, and DjVu files. Windows also lists it in Default Apps for the current user,
without administrator access. Linux registers the executable or AppImage in your user
applications; on macOS, run the installed DocumentViewer.app. Choose the default
viewer through your system settings. Register again after moving the application.
Right-click the document area and choose Update to latest version to download the matching package from the latest stable GitHub release. The updater checks the download size and SHA-256 checksum, saves the reading session, replaces the application, and restarts it with the same documents, active tab, zoom, and scroll positions. Downloads can be canceled before installation.
Automatic updates support the portable Windows EXE, the Linux AppImage, and an
installed macOS app bundle. The application folder must be writable. Windows uses
the system curl and PowerShell; Linux and macOS require curl. Development builds
and unpacked Windows deployment folders are not replaced by the updater.
Successful updates remove their .dv-update-* staging folder, including downloads,
logs, and the previous app. If replacement or startup fails, the previous app is
restored and diagnostic files are retained in that folder. Windows waits for the
portable launcher to exit before replacing its EXE.
Session data is stored in %LOCALAPPDATA%\Document Viewer\session.json on Windows,
~/.local/share/Document Viewer/session.json on Linux (or $XDG_DATA_HOME), and
~/Library/Application Support/Document Viewer/session.json on macOS.
Missing or unreadable documents are skipped with a status message. Passwords are never
saved; protected PDFs request their password again during restoration. Closing all
tabs and exiting saves an empty session.
Features: open dialog, drag and drop, password-protected documents, page navigation, fit width, fit page, editable zoom percentage, and keyboard shortcuts. Clicking the scrollbar track moves the slider to the pointer; keep the mouse button held to drag it. Pages are rendered as visible 512-pixel tiles with a 64 MiB cache, including high-DPI support. An unsuccessful open keeps the previous document available.
Click a document link to jump to its PDF destination or EPUB chapter/anchor. Use the mouse Back/Forward buttons, Alt+Left/Alt+Right, or dedicated Back/Forward keys to revisit reading positions before and after jumps. Each tab keeps its own history of link, page, and search jumps, including position and zoom. Scrolling does not add history entries; a new jump after going back clears forward history. History lasts until the document is closed. Web and email links open in the system's default browser or mail application. PDFs and EPUBs also recognize web addresses and email addresses printed as plain text, even when the document has no link annotation. Supported links show a hand cursor; dragging across a link does not activate it. PDF launch actions, links to other local files, and script links are not executed.
Uses the installed Qt 6.11.2 MinGW kit, MinGW 13.1, CMake, and Ninja under C:\Qt.
No Python is used.
.\build.cmd -Test
.\build\DocumentViewer.exe
# Optionally open a document at startup:
.\build\DocumentViewer.exe 'C:\documents\example.pdf'The build script deploys the Qt runtime beside the executable. In Qt Creator,
open CMakeLists.txt and select the installed Qt 6.11.2 MinGW 64-bit kit.
CMakePresets.json contains the local paths; adjust them for another installation.
The .cmd launcher permits the build script for that process only, so it also
works with this machine's default PowerShell script policy.
To compile Qt itself with the installed MSVC x64 toolchain and LTO, then build and package the viewer against it:
.\build-qt-msvc.cmd
.\build-msvc.cmd -PackageThe Qt build script fetches pinned Qt 6.11.2 Base and SVG sources into C:\Qt\src,
builds under C:\Qt\build, and installs to C:\Qt\6.11.2\msvc_lto_64.
It uses shared release libraries with -ltcg -optimize-size; Qt examples and
Qt's own test suite are excluded. Visual Studio's C++ workload, Python 3, Git,
and the local CMake/Ninja tools are required. Both scripts accept -Jobs (default 8).
The MSVC viewer uses build/msvc, and its portable launcher uses
build/portable-msvc, keeping compiler caches separate from MinGW.
build-msvc.cmd -Test builds and tests; -Package also tests, deploys, and creates
dist/dv-windows-x64.exe, then smoke-tests that portable executable.
The package includes the MSVC runtime DLLs beside the application, without
requiring users to run a separate redistributable installer.
The local-msvc-lto preset can also be used from an MSVC developer shell.
GitHub Actions uses the same Qt source build and runtime deployment scripts for
both Windows x64 and ARM64. Linux and macOS continue to use prebuilt Qt kits.
| Action | Control |
|---|---|
| Open PDF, EPUB, or DjVu | Open button, Ctrl+O, or drop a local file |
| Scroll continuously | Mouse wheel, trackpad, scrollbar, Page Up / Page Down |
| Select text | Drag over text, or double-click a word; drag near an edge to scroll |
| Copy selected text | Ctrl+C (Command+C on macOS), or right-click → Copy |
| Zoom | − / + buttons, percentage field, or Ctrl+wheel |
| Zoom shortcuts | Ctrl+−, Ctrl++, Ctrl+= |
| Fit width | Zoom dropdown or Ctrl+0 |
| Fit page | Zoom dropdown |
| Navigate pages | Page number, arrow buttons, or document links |
| Back / Forward in history | Mouse Back / Forward buttons, Alt+Left / Alt+Right, or Back / Forward keys |
| Switch tabs | Click a tab, Ctrl+Tab, or Ctrl+Shift+Tab |
| Close tab | Tab close button or Ctrl+W |
| Exit and save session | Escape |
| Find substring | Find button or Ctrl+F |
| Next / previous match | Enter or F3 / Shift+F3, or search bar buttons |
Text selection works across pages in PDFs and EPUBs, and selects OCR words in DjVu documents. Selections remain highlighted when zooming or switching tabs; click elsewhere in the document to clear them. Image-only scans need an existing text layer for selection and copying; the app does not perform OCR.
Search is case-insensitive, includes partial words and overlapping matches, and wraps at the first/last result. Navigation is available as soon as matches are found, while the remaining pages are still being searched. Next/previous wraps among the matches found so far; new results and search completion preserve the selected match and scroll position. All results are highlighted in yellow, with the selected result in orange. Each tab keeps its own query while the app is open. Clear the query to remove highlights. Search scans one page per event-loop turn; an unusually complex page can still briefly delay input. Image-only scans need an existing OCR text layer to be searchable; the app does not perform OCR.
The installed Qt kit does not include Qt PDF. The interface uses local Qt Widgets;
rendering uses PDFium through the
PDFium binary distribution.
CMake downloads the matching platform/architecture release chromium/8044 at first configure and verifies
its SHA-256 checksum. Subsequent builds use the cached dependency. PDF files stay
local. PDFium's license and third-party notices are included in the build output.
Build targets include Windows x64/ARM64, Linux x64, and macOS x64/ARM64. Text selection, editing, and printing are outside the current scope. Rendering runs on the UI thread, so unusually complex pages can briefly delay input. The file is kept in memory while open; rendered tiles have a separate bounded cache.
EPUB 2 and EPUB 3 books are read directly from their ZIP container without extracting files. Chapters follow the package spine reading order. Qt lays out text, images, tables, and supported CSS into A4 pages in memory, displayed through the existing viewer. Continuous scrolling, zoom, substring search, mixed PDF/EPUB tabs, and session recovery work for books too. The original EPUB is unchanged.
This is a basic paginated EPUB reader: complex CSS, fixed-layout fidelity, inline SVG, embedded fonts, scripts, multimedia, and advanced interactive content are not fully supported. Fonts use installed fallbacks. DRM-encrypted books are rejected. Opening a large book can take a moment while pages are laid out; zoom scales these pages rather than reflowing the book. External network/file resources are not loaded.
Archive reading uses the installed Qt 6.11.2 CorePrivate ZIP reader. Rebuild with matching private headers when upgrading Qt; distribute the matching deployed Qt DLLs.
Files ending in .djvu or .djv (case-insensitive) open alongside PDFs and EPUBs.
Single-page, bundled multipage, and indirect books are supported. Indirect books
need their component files in the same directory or its subdirectories.
The viewer renders visible tiles directly with DjVuLibre, preserving page rotation,
continuous scrolling, zoom, navigation history, and session recovery. Embedded OCR
text supports case-insensitive substring search, including overlapping matches;
highlights cover the matching words. Image-only pages have no searchable text.
DjVu hyperlinks and annotations are not currently interactive.
CMake fetches the pinned upstream DjVuLibre 3.5.30.1 source revision and builds it
with the application's compiler. No separate DjVu installation is needed. The
installed package includes the decoder source and its license under sources/
and licenses/. Legacy JPEG-encoded DjVu backgrounds are not supported by this
build; standard IW44, JB2, and MMR pages are supported. Like PDF rendering, page
decoding runs synchronously and complex pages can briefly delay input.
build.cmd -Test runs Qt Test offscreen against a generated three-page PDF. It
checks page rendering, continuous scroll range, navigation, zoom limits,
Ctrl+wheel, resize-to-fit, invalid-file recovery, Unicode paths, and reopening.
It also checks independent tab state, toolbar synchronization, keyboard tab
switching and closing, and reopening after the last document is closed.
Session tests cover periodic checkpoints without a close event, immediate saves
on close, restored tab state, missing documents, empty sessions, and corrupt JSON.
DjVu tests cover generated single/multipage books, all four rotations, tile rendering,
OCR search, Unicode paths, invalid-file recovery, mixed tabs, and session recovery.
EPUB tests cover versions 2 and 3, spine order, relative image/CSS resources,
substring search, zoom/session recovery, and missing-chapter error recovery.
Link tests cover internal and named PDF destinations, destination zoom, rotated
pages, web links, drag cancellation, and EPUB chapter anchors.
.github/workflows/build.yml runs on pushes, pull requests, and manual dispatch:
| Runner | Toolchain / architecture |
|---|---|
windows-latest |
MSVC 2026, x64 |
windows-11-vs2026-arm |
MSVC 2026, ARM64 |
ubuntu-latest |
GCC, x64 |
macos-latest |
Apple Clang, ARM64 |
Windows jobs compile pinned Qt 6.11.2 Base and SVG sources with MSVC, LTO, and size optimization. The installed Qt kits are cached by architecture, compiler, Windows SDK, and build-script hash. The first build after a cache change takes longer. Windows packages bundle the matching MSVC runtime DLLs directly, omitting the redistributable installer. Portable smoke tests exclude the Qt installation from the environment.
Linux and macOS jobs install prebuilt Qt 6.11.2 with matching private headers. Each job configures CMake/Ninja, builds, runs the offscreen Qt tests, and deploys the application with its Qt and PDFium dependencies. Platform-specific PDFium archives are pinned by SHA-256. Failed jobs upload test logs. Successful jobs upload a platform artifact:
| Artifact | Contents |
|---|---|
dv-windows-x64.exe |
dv-windows-x64.exe |
dv-windows-arm64.exe |
dv-windows-arm64.exe |
dv-linux-x64.AppImage |
Direct AppImage download |
dv-macos-arm64.dmg |
Direct disk image download |
Workflow artifacts are uploaded directly with archive: false, without ZIP
wrappers. For Linux, run chmod +x dv-linux-x64.AppImage after downloading the
AppImage. Failed-job logs are uploaded as uncompressed tar files. Windows files launch directly;
open the macOS disk image and launch or copy
DocumentViewer.app.
The macOS app is not Developer ID signed or notarized.
Linux artifacts target the runner's distribution/runtime generation.
For a portable local configure with Qt available in CMAKE_PREFIX_PATH:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DBUILD_TESTING=ON
cmake --build build --parallel 2
ctest --test-dir build --output-on-failure
cmake --install build --prefix stageThe existing local-qt preset and build.cmd remain specific to this Windows
machine. Offscreen tests load an installed font; set DOCUMENT_VIEWER_TEST_FONT
to a font file to override the platform default. On an already deployed Windows
build, use the test preset or set QT_QPA_PLATFORM_PLUGIN_PATH to the Qt kit's
plugins/platforms directory so the offscreen plugin can be found.
On an interactive Windows desktop, run
py tests/windows_launch_tests.py build/DocumentViewer.exe
to check repeated file opens, tab selection, minimized
window restoration, and foreground activation. Add a portable executable path
to test that build as well. Hosted ARM64 CI uses --no-foreground because desktop
focus is not reliably available there; all other launch checks still run.
Windows x64 CI also checks foreground activation.
Link-time optimization (LTO/IPO) is enabled by default for Release, RelWithDebInfo,
and MinSizeRel builds, including the Windows portable launcher. CMake checks
compiler/linker support at configure time; use -DDOCUMENT_VIEWER_ENABLE_LTO=OFF
to disable it. Prebuilt Qt and PDFium libraries are not rebuilt with LTO; the
build-qt-msvc.cmd path and Windows CI compile Qt itself with LTO.
Local presets, CI, and the portable launcher use MinSizeRel to optimize for size
(-Os with GCC/Clang, /O1 with MSVC) while retaining LTO. GNU-linked size builds
strip symbols; Apple and MSVC size builds remove unused code. Windows payloads use
maximum ZIP compression. Most of the portable file still consists of the prebuilt
Qt/PDFium dependencies. Prebuilt dependencies retain their upstream compilation
settings; the optional local MSVC Qt build enables size optimization and LTO.
Run package.cmd on this machine to build, test, deploy, and generate
dist/dv-windows-x64.exe. This is the file to copy to another Windows x64 machine;
it embeds the application, Qt plugins, PDFium, compiler runtime, and notices.
The portable launcher uses Windows' built-in tar.exe (Windows 10 1803+ / Windows
11) to unpack into a unique temporary directory, forwards command-line arguments,
waits for the viewer to exit, and then deletes that directory. A forced termination
of the launcher can leave temporary files behind. Session settings remain in the
normal user data directory, so they persist across launches and updates.
This uses self-extraction rather than static Qt/PDFium linking. Linux AppImages
similarly contain their runtime dependencies. A macOS .dmg is a single distribution
file containing the native .app bundle. CI smoke-tests the packaged Windows and
Linux launchers and the deployed macOS bundle with --smoke-test, which initializes
the UI without loading or changing the reading session.
packaging/package.py packages an existing CMake install tree. On Windows it builds
a small native launcher with a statically linked compiler runtime. Linux packaging
uses checksum-verified appimagetool 1.9.1; macOS uses the built-in hdiutil tool.
Push a version tag to build, test, and publish a GitHub Release:
git tag v1.0.0
git push origin v1.0.0The release job runs only for v* tags, after all four platform builds and tests
succeed. Branch builds and pull requests do not publish releases. You can also
dispatch the workflow for an existing version tag with
gh workflow run build.yml --ref v1.0.0.
Tags containing a hyphen, such as v1.1.0-rc.1, produce prereleases.
Release assets are direct downloads: dv-windows-x64.exe,
dv-windows-arm64.exe, dv-macos-arm64.dmg, and dv-linux-x64.AppImage.
For Linux, run chmod +x dv-linux-x64.AppImage after downloading.
No ZIP wrapper is added to release downloads.
Release notes are generated automatically. All assets are uploaded to a draft before publication. Existing releases are never overwritten; if an upload fails, delete the incomplete draft before rerunning the failed release job. Published versions should use new tags for subsequent changes.