Simple local PDF reader for Windows 10+. WPF · .NET 8 · no ribbons, AI, or cloud.
UI mocks (locked look): docs/mocks/ — empty/open, reader, password modal.
Grab the latest installer from Releases (current: v1.2.0):
- Download
ClearPDF-Setup.exe - Run it (SmartScreen may show Unknown publisher once — More info → Run anyway; the build is unsigned)
- Optional: check “Set ClearPDF as default for .pdf”
Self-contained ~166 MB on disk after install. No .NET install required.
Docnet.Core (NuGet) — managed PDFium bindings. Chosen over PdfiumViewer because it targets modern .NET, restores cleanly with net8.0-windows, and keeps the app on a thin WPF shell instead of WinForms hosts.
| Piece | License |
|---|---|
| ClearPDF app code | MIT (see LICENSE) |
| Docnet.Core | Apache-2.0 |
| PDFium (native) | Apache-2.0 / BSD-style (Google PDFium) |
Passwords are used for view unlock only and are never stored.
- Windows 10 or 11
- .NET 8 SDK (includes Windows Desktop / WPF)
- Optional: Visual Studio 2022 with “.NET desktop development”
This repo was scaffolded on a Linux box. WPF does not run there. Build and run on Windows.
cd ClearPDF
dotnet restore
dotnet build
dotnet run --project src\ClearPDF\ClearPDF.csprojTests (Core helpers — runs on Linux / any OS with .NET 8 SDK):
dotnet test
# equivalent: dotnet test tests/ClearPDF.Tests/ClearPDF.Tests.csprojOn non-Windows, the WPF project is a no-op stub so solution-level dotnet test only exercises ClearPDF.Core.
ClearPDF opens the first existing .pdf argument after the exe (spaces/quoted paths OK). No-arg launch still shows the empty Open screen.
ClearPDF.exe "C:\path\to\file.pdf"Code QA smoke: associate or “Open with” ClearPDF → double-click a PDF → document renders (not the empty Open screen). Drag-drop a PDF onto the window also works. Helper: CommandLinePdfArgs in Core (covered by CommandLinePdfArgsTests).
Docnet.Core needs pdfium.dll (and related natives) at runtime on Windows.
dotnet restore/dotnet buildpulls Docnet.Core. Depending on package version, natives may arrive via runtime assets or a companion package.- If the app starts but shows the stub page banner (“PDFium natives not loaded”), copy
pdfium.dllnext toClearPDF.exe(usuallysrc\ClearPDF\bin\Debug\net8.0-windows\). - Official / community native builds: bblanchon/pdfium-binaries (pick the Windows x64 build matching your RID).
- On non-Windows scaffold machines, the factory probes for natives and falls back to
StubPdfDocumentso the UI shell still opens. Real render/find/print quality needs Win10 + natives.
Factory entry point: DocnetPdfDocumentFactory in src/ClearPDF/Services/DocnetPdfDocument.cs.
Local file logs only (%LocalAppData%\ClearPDF\logs\clearpdf-yyyyMMdd.log): open failures, password errors, unhandled exceptions — no network/Sentry/telemetry.
Image-heavy PDFs (brochures, full-bleed photos) used to hitch because GetImage ran on the UI thread and every zoom rebuilt every page via fixed PageDimensions(2.0) + TransformedBitmap.
Current architecture:
| Piece | Behavior |
|---|---|
| Target DPI | RenderPage opens a short-lived Docnet reader at PdfRenderScale.CapPixelsPerPoint(zoom, pageW, pageH) — no TransformedBitmap thrash |
| Caps | Longest edge ≤ 4096px, ≤ ~8M pixels/page |
| Cache | PageRasterCache / PageBitmapCache keyed by (pageIndex, quantizedZoom), capped at 24 page rasters; thumbs are a separate map |
| Threads | Background raster → Dispatcher swaps Image.Source; last frame stays visible |
| Zoom | Debounced settle (120ms); pan/scroll does not re-Docnet or rebuild thumbs |
| Scroll | Chrome updates immediately; sharp window is debounced (80ms) and does not cancel in-flight neighbors |
| Virtualize | Sharp bitmaps for current ±2 pages; far pages keep low-res preview |
| Thumbnails | Sequential low-res fill at open (separate cache); scroll/zoom only update selection chrome |
| Password | Unlock once into memory bytes for re-raster; password never retained/stored |
| Page size | Still PDF points via baseline reader (PdfPageSize) |
Helpers: PdfRenderScale, PageRenderPlanner, PageRasterCache<T> in Core (unit-tested). Details: docs/render-perf.md.
UI-free logic lives in src/ClearPDF.Core/ (net8.0): ZoomHelper, FindHelper, RecentFilesService, PdfPageSize, RecentFileEntry, CommandLinePdfArgs, PageRenderPlanner, ScrollPagePicker, PageRasterCache<T>, PrintPageRange, PdfUnlockPolicy, PageSelection, PdfPageExtract. ClearPDF.Tests references Core only, so dotnet test runs on Linux. The WPF app (net8.0-windows) references Core and still requires Windows to build/run the shell.
In
- Open PDF (file dialog + recent list + command-line / Explorer “Open with” + window drag-drop)
- Render pages (page hero + left thumbnails)
- Scroll / PageUp·PageDown / Home·End
- Zoom in/out, fit width, fit page
- Find next/prev (extracted text)
- Print, Save As (copy file)
- v1.2 page-selection (ship version 1.2.0): multi-select thumbs (click / Ctrl-toggle / Shift-range;
#5A7FA6border + check beside the gray page number); Save pages as... (toolbar, overflow, tiny 2-line thumb menu) writes a new PDF via Docnet Split; Print Selection is the current page when size is 1, the selected set when multi; Save As with a multi-select shows a quiet in-window card (Selected pages (n) | Entire document) — no title-bar modal. Steal:docs/mocks/v12-STEAL.md - Password prompt for encrypted PDFs (unlock for viewing only)
- Error banner UI
- Bookmarks/outline when the engine exposes them (stubbed TODO if Docnet has no outline API)
Out
- Forms, annotations, DRM/IRM beyond open-password
- JavaScript, OCR, sync/cloud accounts
- Ribbons / AI features
- Proper outline/bookmarks panel
- Text selection / copy
- Continuous vs single-page modes polish
- Tile / progressive decode for very large pages
- Optional dark canvas
| Screen | Mock | Code |
|---|---|---|
| Empty + Recent | docs/mocks/chrome-arc-empty.png (v1.1) / 01-empty-open.png |
EmptyView in MainWindow.xaml |
| Reader | docs/mocks/chrome-arc-reader.png (v1.1) / 02-reader.png |
ReaderView + one icon toolbar + status bar |
| v1.2 thumbs / extract chrome | docs/mocks/v12-01-multiselect.png, v12-02-context-menu.png, v12-03-save-as-choice.png |
Thumbnail multi-select, 2-line thumb menu, Save As one-liner |
| Password | docs/mocks/03-password.png |
Views/PasswordDialog.xaml |
Accent: #5A7FA6 (one muted blue — Open CTA pill, selected thumb, find hit). Canvas: #F1F2F7 (sampled from chrome-arc-reader.png so the white page pops). Toolbar: one ~36px icon strip (Segoe MDL2), labels on hover. No ribbons.
Deferred (later 1.1.x): first-open Welcome card (docs/mocks/chrome-arc-intro.png) is not in this polish pass. Empty-state stays Open + Recent.
Graphics/Steve empty-state gate: docs/qa.md.
ClearPDF/
ClearPDF.sln
LICENSE
README.md
.gitignore
docs/mocks/
docs/qa.md # Graphics/Steve standing QA gates
docs/test-output.txt # captured `dotnet test` on Linux
src/ClearPDF.Core/ # net8.0 — ZoomHelper, FindHelper, RecentFiles, PdfPageSize, PdfRenderScale, CommandLinePdfArgs, PageRenderPlanner, PrintPageRange, PdfUnlockPolicy, PageSelection, PdfPageExtract
src/ClearPDF/ # net8.0-windows WPF shell (refs Core)
ClearPDF.csproj
App.xaml
MainWindow.xaml
Models/ BookmarkItem
Services/ Docnet + stub, AppLog (local file logs)
Views/ PasswordDialog
tests/ClearPDF.Tests/ # net8.0 → Core only