Skip to content

Repository files navigation

Document Viewer

A native C++ / Qt Widgets PDF and EPUB reader with multiple document tabs, continuous vertical scrolling, and zoom from 10% to 500%.

Licensed under the MIT License. Qt, PDFium, and other bundled third-party components retain their respective licenses.

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 document opens in its own closable, reorderable tab and retains its scroll position and zoom. Select PDFs and EPUBs 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.

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. 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.

Build and run on this machine

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 -Package

The 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.

Controls

Action Control
Open PDF or EPUB Open button, Ctrl+O, or drop a local file
Scroll continuously Mouse wheel, trackpad, scrollbar, Page Up / Page Down
Zoom − / + buttons, percentage field, or Ctrl+wheel
Zoom shortcuts Ctrl+−, Ctrl++, Ctrl+=
Fit width Toolbar button or Ctrl+0
Fit page Zoom dropdown
Navigate Page number, arrow buttons, Alt+Left / Alt+Right
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

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.

PDF rendering dependency

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 support

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 interactive book navigation 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.

Validation

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. EPUB tests cover versions 2 and 3, spine order, relative image/CSS resources, substring search, zoom/session recovery, and missing-chapter error recovery.

GitHub Actions

.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 stage

The 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.

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.

Single-file distribution

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.

Releases

Push a version tag to build, test, and publish a GitHub Release:

git tag v1.0.0
git push origin v1.0.0

The 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.

About

A cross-platform fast document viewer

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages