PdfSpace
Read, edit and review PDFs — locally, in the browser or on desktop
PdfSpace is a local-first PDF application built in C# with Uno Platform and SkiaSharp. The browser runs a real Uno WebAssembly app with the same document, editing and rendering libraries as the desktop host, and an Acrobat-inspired shell brings together tabs, comments, form preparation, native object and original-text editing, on-device OCR, AES-256 protection, redaction and page organization. No document or password is sent to a processing service.

What makes PdfSpace special
Familiar if you know Acrobat-style PDF editors — but original, local‑first and written end‑to‑end in C#.
Genuine Uno WebAssembly
The browser build is a real Uno/Skia application sharing its engines with the desktop host — not a PDF.js viewer or HTML canvas app.
Review without flattening
Highlights, notes, replies, ink, shapes, stamps and links are written back as native PDF annotation objects.
Prepare and fill forms
Create text, check-box and dropdown fields, fill imported forms, and import or export values as XFDF or JSON.
Edit native objects
Select native text, paths, images and Form groups on the page, then move, resize, align, group, crop or restyle them with one-step undo.
Real original-text edits
Replaces actual PDF text-showing operands instead of covering them with a white rectangle; unsupported cases are rejected.
Local Scan & OCR
Recognize image-only PDFs in English, Polish or German, review words against the scan, and export a searchable PDF.
Protect and redact a copy
Export AES-256 PDFs with separate opening and owner passwords, or build a confirmed image-only redacted copy.
Indexed performance
Indexed page geometry, bounded search indexes and bounded picture/source caches reduce repeated layout and parsing work on large documents.
33 capabilities, 7 areas
Read and navigate
- Multiple document tabs for PDFs and editable workspaces
- Search and copy text; zoom, pan and Ctrl+wheel zoom around the pointer
- Continuous, single-page and two-page layouts with virtualized thumbnails
- Original PDF outlines alongside editable workspace bookmarks
- Local named destinations and internal links that follow page reorganization
Review and comment
- Text, highlight, underline, strikethrough, notes and threaded replies
- Ink, shapes, arrows, stamps and links
- Move, resize and recolor annotations; resolve comments; undo and redo
- Supported annotations and replies export as native PDF objects
Forms
- Create text, check-box and dropdown fields directly on a page
- Fill imported text fields, check boxes, radio groups and single-select choices
- Edit tooltips, defaults, read-only/required/multiline flags, font size and maximum length
- Import/export plain field values as XFDF or versioned JSON in one undoable transaction
- Check required fields and Tab / Shift+Tab between supported widgets
Edit existing content
- Replace supported original text, including text inside nested Form XObjects
- Select, move, resize, rotate, flip, replace, insert or delete native image placements
- Align, distribute, crop, stack, group and copy/paste objects across documents
- Edit real path geometry, control points and solid fill/stroke
- Native opacity, blending, stroke caps/joins, miter and dash editing
- Shift constrains movement and aspect ratio; Alt resizes from the center
Scan & OCR
- Local recognition in English, Polish or German at 150, 200 or 300 DPI
- Recognize image-only PDFs or import PNG/JPEG scans honoring EXIF orientation
- Review low-confidence words against the original scan and correct them
- Export a searchable PDF with an invisible Unicode text layer
- Browser self-hosts pinned Tesseract.js/WASM models; native hosts use Tesseract 5
Organize and export
- Reorder, rotate, duplicate, delete, crop, combine, extract and split pages
- Export native PDF or a separately labeled flattened visual PDF
- Download PNG, text, form data or an editable .pdfspace workspace
- Browser recovery in IndexedDB; atomic temporary-file recovery on desktop
Protect and redact
- AES-256 revision 6 output with distinct opening and owner passwords
- Print, copy and edit permission flags; owner authentication before re-editing
- Browser encryption runs in an isolated, hash-pinned QPDF WASM worker
- Raster redaction rebuilds every page as an image with burned-in marks
Run PdfSpace right here
Open the live WebAssembly build and try every workflow on the included original sample documents — no upload of private files needed. Switch viewports, go fullscreen and tick off the guided tour as you explore.
Try PdfSpace live
Open the live WebAssembly build and try every workflow on the included original sample documents — no upload of private files needed.
Real C# · Uno WebAssembly · runs locally in your browser12 libraries you can embed
PdfSpace is assembled from independently reusable libraries, each published on NuGet.org. Hover the graph to trace dependencies; click a node to jump to its package, and open a card for install and usage.
PdfSpace.Core
Workspace, page, annotation, field and geometry models with validated JSON
PdfSpace.Layout
Crop/rotation transforms and viewport/page layouts without UI dependencies
PdfSpace.Editing
Transactional editing, shared field state and bounded undo/redo
PdfSpace.Documents
PdfPig import, text extraction, search, page ranges and XFDF/JSON form data
PdfSpace.Rendering.Skia
Source-pinned Apache-2.0 PdfPig Skia renderer with image-alpha, dash and miter fixes
PdfSpace.Skia
Cached rendering, overlays, visual/PNG export and raster redaction
PdfSpace.Pdf
PDFsharp native annotation/form writer, source-text editing and security-provider contract
PdfSpace.Ocr
Local OCR contracts, raster preparation, bounded TSV decoding and snapshot batches
PdfSpace.Controls
Custom Uno icons, command buttons, tabs, panels, palettes and dialogs
PdfSpace.Viewer
Embeddable document viewport with annotation editing, form input and thumbnails
PdfSpace.Storage
Platform-neutral file, recovery, clipboard and print contracts
PdfSpace.Workbench
Complete multi-document shell and PDF workflows
12 packages on NuGet.org
PdfSpace ships as twelve packages, all versioned together. Eleven are MIT-licensed; PdfSpace.Rendering.Skia is Apache-2.0, a derivative of an upstream renderer (see its subsection). Nine target plain net10.0 and have no UI dependency: PdfSpace.Core, Layout, Editing and Storage are pure .NET, Documents adds PdfPig, Rendering.Skia, Skia and Ocr add SkiaSharp, and Pdf adds PDFsharp. The three Uno Platform packages (Controls, Viewer, Workbench) target net10.0-desktop and net10.0-browserwasm. Symbols are published to nuget.org as .snupkg with SourceLink. The application itself is a thin browser/desktop host and is not packaged; see also the embedding guide.
dotnet add package PdfSpace.Core --prerelease<PackageReference Include="PdfSpace.Core" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Layout" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Editing" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Storage" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Documents" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Rendering.Skia" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Skia" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Pdf" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Ocr" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Controls" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Viewer" Version="0.5.5-alpha.1" />
<PackageReference Include="PdfSpace.Workbench" Version="0.5.5-alpha.1" /><!-- Directory.Packages.props -->
<ItemGroup>
<PackageVersion Include="PdfSpace.Core" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Layout" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Editing" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Storage" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Documents" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Rendering.Skia" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Skia" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Pdf" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Ocr" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Controls" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Viewer" Version="0.5.5-alpha.1" />
<PackageVersion Include="PdfSpace.Workbench" Version="0.5.5-alpha.1" />
</ItemGroup> Published with NuGet Trusted Publishing from GitHub Actions, with symbols (.snupkg) and SourceLink. Release v0.5.5-alpha.1 ↗
Dependencies (from project references): Core ← Layout, Core ← Editing, Core ← Documents; Documents + Layout + Rendering.Skia ← Skia ← Pdf, Skia ← Ocr; Controls + Editing + Skia ← Viewer; Pdf + Viewer + Storage + Ocr ← Workbench. Storage, Controls and Rendering.Skia have no PdfSpace dependencies.
The immutable document model shared by every package: a PdfWorkspace holds the original source bytes plus per-page state (rotation, crop, bookmarks, annotations with replies, form fields and OCR layers), so edits stay reversible until export. Includes validated .pdfspace JSON and page selection/composition helpers. No dependencies and no UI.
dotnet add package PdfSpace.Core --prereleaseKey types
PdfWorkspace–Sources,Pages,UpdatePage,IsSensitive,AnnotationCount,FieldCountPdfPageState,PdfSource– page geometry/state and original PDF bytesAnnotation/AnnotationKind,PdfFormFieldState/PdfFieldKind– review and form modelsWorkspaceJson–Save,Load(bounded, validated),ValidateWorkspacePages.Select,WorkspaceComposition.Append,PdfFormData.FromWorkspaceRectD,PointD– page-space geometry in PDF points
Basic usage
using PdfSpace.Core;
var page = new PdfPageState { Width = 612, Height = 792 };
var note = new Annotation { Kind = AnnotationKind.Note, Bounds = new RectD(72, 72, 24, 24), Text = "Check totals" };
var workspace = new PdfWorkspace { Title = "Review.pdf", Pages = [page with { Annotations = [note] }] };
workspace = workspace.UpdatePage(page.Id, p => p with { Rotation = 90, Bookmark = "Summary" });
var firstPageOnly = WorkspacePages.Select(workspace, [0]);
string json = WorkspaceJson.Save(workspace); // .pdfspace workspace
var restored = WorkspaceJson.Load(json); // bounded + validated
Console.WriteLine($"{restored.Pages.Length} page(s), {restored.AnnotationCount} annotation(s)");Renderer-independent page geometry: page ↔ display transforms under crop and rotation, an indexed continuous/single/two-page layout with visible-page queries and hit testing, a spatial bounds index for picking dense pages, object snapping (move, resize, points) and selection transforms. Depends on PdfSpace.Core; no UI.
dotnet add package PdfSpace.Layout --prereleaseKey types
PageLayoutIndex–Visible,Place,HitTest,NearestPage,TotalHeightforPageLayoutModePageGeometry–ToDisplay,ToPage,DisplayBounds;PagePlacement.ToPage/ToScreenSpatialBoundsIndex–QueryandHitTestover many rectanglesObjectSnapIndex–Snap,SnapResize,SnapPointwith guidesSelectionTransform,SelectionRotation– constrained move/resize/create and rotation deltas
Basic usage
using PdfSpace.Core;
using PdfSpace.Layout;
var pages = Enumerable.Range(0, 50).Select(_ => new PdfPageState()).ToArray();
var index = new PageLayoutIndex(pages, PageLayoutMode.TwoPage);
double width = 1280, zoom = 1.25, scroll = 0, pan = 0;
foreach (PagePlacement placement in index.Visible(width, height: 900, zoom, scroll, pan))
Console.WriteLine($"page {placement.Index + 1} at {placement.Bounds}");
var screen = new PointD(400, 300);
int hit = index.HitTest(screen, width, zoom, scroll, pan);
if (hit >= 0)
{
PointD pagePoint = index.Place(hit, width, zoom, scroll, pan).ToPage(pages[hit], screen, zoom);
Console.WriteLine($"page {hit + 1}, PDF point {pagePoint}");
}
RectD rotatedBox = PageGeometry.DisplayBounds(pages[0] with { Rotation = 90 }, new RectD(72, 72, 200, 50));EditorSession applies every change as an immutable workspace transaction with bounded undo/redo (entry count and retained source bytes): annotations and replies, page rotate/crop/insert/duplicate/delete/move/combine, form-field creation, filling and validated XFDF/JSON imports, and OCR corrections. Custom commands go through Execute. Depends on PdfSpace.Core; no UI.
dotnet add package PdfSpace.Editing --prereleaseKey types
EditorSession–Document,CurrentPage,Execute(label, command),Undo/Redo,IsDirty,Changed- Annotations and pages –
AddAnnotation,UpdateAnnotation,Reply,RotatePage,CropPage,MovePage,Combine - Forms –
AddField,UpdateField,SetFieldValue,PrepareFormDataImport/ApplyFormDataImport EditorHistoryOptions–MaximumEntries,MaximumSourceBytes;ClearHistory()
Basic usage
using PdfSpace.Core;
using PdfSpace.Editing;
var session = new EditorSession(new PdfWorkspace(), new EditorHistoryOptions { MaximumEntries = 50 });
session.Changed += (_, _) => Console.WriteLine($"{session.UndoLabel} (dirty: {session.IsDirty})");
session.AddAnnotation(new Annotation
{
Kind = AnnotationKind.Rectangle,
Bounds = new RectD(72, 120, 180, 64),
Color = 0xFF1473E6,
StrokeWidth = 2
});
session.RotatePage();
session.DuplicatePage();
session.Execute("Bookmark pages", doc => doc with
{
Pages = doc.Pages.Select((p, i) => p with { Bookmark = $"Page {i + 1}" }).ToArray()
});
session.Undo();
File.WriteAllText("review.pdfspace", WorkspaceJson.Save(session.Document));The host contract the workbench uses for everything platform-specific: opening documents, images and form data, saving/downloading, local recovery, clipboard text and printing. Implement it once per platform (the app ships browser and desktop implementations). No dependencies and no UI.
dotnet add package PdfSpace.Storage --prereleaseKey types
IWorkspaceStorage–OpenAsync,SaveAsync,Read/Write/ClearRecoveryAsync,CopyTextAsync,PrintAsync- Optional members with defaults –
OpenImageAsync,OpenFormDataAsync WorkspaceFile– name + bytes
Basic usage
using PdfSpace.Storage;
public sealed class FolderStorage(string root) : IWorkspaceStorage
{
private string Recovery => Path.Combine(root, "recovery.pdfspace");
public Task<WorkspaceFile?> OpenAsync() => Task.FromResult<WorkspaceFile?>(null); // show a file picker here
public Task SaveAsync(string name, byte[] bytes, string contentType) => File.WriteAllBytesAsync(Path.Combine(root, name), bytes);
public async Task<string?> ReadRecoveryAsync() => File.Exists(Recovery) ? await File.ReadAllTextAsync(Recovery) : null;
public Task WriteRecoveryAsync(string workspace) => File.WriteAllTextAsync(Recovery, workspace);
public Task ClearRecoveryAsync() { File.Delete(Recovery); return Task.CompletedTask; }
public Task CopyTextAsync(string text) => Task.CompletedTask; // host clipboard
public Task PrintAsync(string name, byte[] pdf) => Task.CompletedTask; // host print dialog
}Reads PDFs into a PdfWorkspace with PdfPig: page sizes, words with bounds, full-text extraction and search (including a byte-bounded per-document index), one-based page-range parsing, and plain-field form data as XFDF or versioned JSON. Depends on PdfSpace.Core and PdfPig; no UI.
dotnet add package PdfSpace.Documents --prereleaseKey types
PdfReader–Open(bytes, name),Words,Find,ExtractTextPdfSearchIndex– bounded cached search withFind(document, query, matchCase, wholeWord)PdfTextReader– reusable per-workspace word readerPageRange.Parse("1-3, 5", pageCount)– zero-based indicesPdfFormDataCodec–Read,WriteXfdf,WriteJson
Basic usage
using PdfSpace.Core;
using PdfSpace.Documents;
var workspace = PdfReader.Open(File.ReadAllBytes("report.pdf"), "report.pdf");
Console.WriteLine(PdfReader.ExtractText(workspace));
var search = new PdfSearchIndex();
foreach (SearchResult hit in search.Find(workspace, "revenue", wholeWord: true).Take(10))
Console.WriteLine($"p.{hit.PageIndex + 1}: {hit.Text} at {hit.Bounds}");
int[] pages = PageRange.Parse("1-3, 5", workspace.Pages.Length); // zero-based indices
File.WriteAllBytes("form.xfdf", PdfFormDataCodec.WriteXfdf(PdfFormData.FromWorkspace(workspace)));
PdfFormData answers = PdfFormDataCodec.Read(File.ReadAllBytes("answers.json"), "answers.json");License: Apache-2.0 (not MIT). This package is a derivative work: a source-pinned adaptation of BobLd/PdfPig.Rendering.Skia 0.1.16.4, moved into the isolated PdfSpace.Rendering.Skia namespace, with fixes for image alpha, odd-length dash patterns and miter limits. The package retains the upstream LICENSE.txt and NOTICE.txt plus UPSTREAM.json (original source hashes) and ADAPTATIONS.md (every modification); keep those notices when you redistribute it. Use it directly to rasterize PDF pages with PdfPig and SkiaSharp; PdfSpace.Skia references it for you. It depends on PdfPig (with its DCT, JBIG2 and JPX filter packages), SkiaSharp and SkiaSharp.HarfBuzz; no UI.
dotnet add package PdfSpace.Rendering.Skia --prereleaseKey types
PdfPigExtensions–AddSkiaPageFactory,GetPageSize,GetPageAsSKPicture,GetPageAsSKBitmap,GetPageAsPngSkiaRenderingParsingOptions.Instance– lenient PdfPig parsing with the image filter providerPdfPageSize–Width,Height,PageNumberSkiaPageFactory,SkiaRenderingFilterProvider– the PdfPig factory/filter plumbing
Basic usage
using PdfSpace.Rendering.Skia;
using SkiaSharp;
using UglyToad.PdfPig;
using var document = PdfDocument.Open(File.ReadAllBytes("input.pdf"), SkiaRenderingParsingOptions.Instance);
document.AddSkiaPageFactory();
for (var page = 1; page <= document.NumberOfPages; page++)
{
PdfPageSize size = document.GetPageSize(page);
using var png = document.GetPageAsPng(page, scale: 2);
File.WriteAllBytes($"page-{page}.png", png.ToArray());
Console.WriteLine($"page {page}: {size.Width} x {size.Height} pt");
}
using SKPicture picture = document.GetPageAsSKPicture(1); // replay onto any SKCanvasDraws workspace pages: cached source pictures from the Apache-2.0 renderer, then crop/rotation, annotations and form widgets on top. Exports PNG pages and an explicitly flattened visual PDF, rebuilds image-only redacted copies, imports PNG/JPEG scans as pages, and generates the original sample documents. For native annotations and interactive forms use PdfSpace.Pdf instead. Depends on PdfSpace.Documents, PdfSpace.Layout, PdfSpace.Rendering.Skia and SkiaSharp; no UI framework.
dotnet add package PdfSpace.Skia --prereleaseKey types
PdfRenderer–DrawPage,ExportPng,ExportPdf(flattened),Typeface,CacheCapacityRasterRedactor.Export– image-only reconstruction with burned-in redaction marksPdfImageImporter–Opena PNG/JPEG as a one-page workspaceAnnotationPainter,FormFieldPainter– overlay drawing on anySKCanvasSampleDocument.Create(typeface)– original synthetic demo workspace
Basic usage
using PdfSpace.Documents;
using PdfSpace.Skia;
using SkiaSharp;
var workspace = PdfReader.Open(File.ReadAllBytes("input.pdf"), "input.pdf");
using var typeface = SKTypeface.FromFile("fonts/Inter.ttf"); // annotation fallback text
using var renderer = new PdfRenderer { CacheCapacity = 8, Typeface = typeface }; // disposed before the typeface
File.WriteAllBytes("first-page.png", renderer.ExportPng(workspace, pageIndex: 0, scale: 2));
File.WriteAllBytes("flattened.pdf", renderer.ExportPdf(workspace)); // visual copy, not native
File.WriteAllBytes("redacted.pdf", RasterRedactor.Export(workspace, renderer)); // image-only rebuild
using var surface = SKSurface.Create(new SKImageInfo(612, 792));
renderer.DrawPage(surface.Canvas, workspace, workspace.Pages[0]);Structured PDF output and native editing with PDFsharp: save workspaces with real annotation and AcroForm objects, edit supported original text runs, read/move/replace native images, discover and transform native page objects (text, paths, images, shadings, Form groups), read outlines, inspect documents, and encrypt/unlock with AES-256 through IPdfSecurityProvider. Depends on PdfSpace.Skia and PDFsharp; no UI.
dotnet add package PdfSpace.Pdf --prereleaseKey types
PdfDocumentEngine–Open(with optional password),Save→PdfWriteResult,Inspect,CanPreserveCatalogPdfTextEditor–Read,Replace,ReplaceWithFont;PdfImageEditor– native image occurrencesPdfObjectEditor–Read,Transform,Align,Distribute,Group,Copy/Paste,SetAppearanceIPdfSecurityProvider/NativePdfSecurityProvider–EncryptAsync,UnlockAsync;PdfProtectionOptionsPdfNavigation.ReadBookmarks,PdfAffineMatrix
Basic usage
using PdfSpace.Pdf;
using SkiaSharp;
using var typeface = SKTypeface.FromFile("fonts/Inter.ttf"); // font for new annotation/form text
var workspace = PdfDocumentEngine.Open(File.ReadAllBytes("input.pdf"), "input.pdf");
PdfTextRun run = PdfTextEditor.Read(workspace, pageIndex: 0).First(r => r.Editable);
workspace = PdfTextEditor.Replace(workspace, run, "Updated heading");
var paths = PdfObjectEditor.Read(workspace, 0).Where(o => o.Kind == PdfPageObjectKind.Path && o.Editable).ToArray();
workspace = PdfObjectEditor.Transform(workspace, paths, PdfAffineMatrix.Translate(12, 6));
PdfWriteResult result = PdfDocumentEngine.Save(workspace, typeface); // native annotations + AcroForms
File.WriteAllBytes("native-copy.pdf", result.Bytes);
foreach (var warning in result.Warnings) Console.WriteLine(warning);
IPdfSecurityProvider security = new NativePdfSecurityProvider(); // desktop; not for AES on browserwasm
byte[] encrypted = await security.EncryptAsync(result.Bytes,
new PdfProtectionOptions(openingPassword, ownerPassword, AllowCopy: false));Local OCR orchestration: rasterize selected pages, send them to an IOcrEngine, decode bounded Tesseract TSV into word boxes, and apply the result as one undoable OCR layer that PdfSpace.Pdf exports as an invisible, searchable text layer. TesseractProcessEngine runs an installed Tesseract 5 executable; browser hosts inject their own engine. Nothing is sent to a service. Depends on PdfSpace.Skia; no UI.
dotnet add package PdfSpace.Ocr --prereleaseKey types
OcrBatch–RecognizeAsync(snapshot, renderer, engine, pages, language, dpi, progress, token),Apply,WordCountIOcrEngine–RecognizeTsvAsync(image, language, token);TesseractProcessEngine,UnavailableOcrEngineTesseractTsv.Decode– bounded TSV →PdfOcrLayerOcrImage,OcrProgress
Basic usage
using PdfSpace.Ocr;
using PdfSpace.Skia;
var scan = PdfImageImporter.Open(File.ReadAllBytes("scan.png"), "scan.png"); // PNG/JPEG → one-page workspace
using var renderer = new PdfRenderer();
IOcrEngine engine = new TesseractProcessEngine(); // requires Tesseract 5 on PATH
var progress = new Progress<OcrProgress>(p => Console.WriteLine($"{p.Completed}/{p.Total} {p.Stage}"));
OcrBatch batch = await OcrBatch.RecognizeAsync(scan, renderer, engine, pages: [0], language: "eng", progress: progress);
var searchable = batch.Apply(scan); // adds the recognized OCR layer
Console.WriteLine($"{batch.WordCount} words on {batch.RecognizedPages} page(s)");Original, Skia-drawn Uno building blocks for PDF tools: vector icons, command buttons, document tabs, tool panels, a color palette, an in-window dialog host (prompt/secret/confirm) and styled text fields, plus PdfTheme helpers and PdfResources styles. Compose them into your own shell without the workbench. Depends only on Uno Platform (Skia renderer).
dotnet add package PdfSpace.Controls --prereleaseKey types
PdfIcon/PdfIconKind,PdfCommandButton,PdfTextFieldPdfToolPanel–Heading,Description,Add(label, icon, action),ItemsPdfDocumentTab,PdfColorPalette(ColorChanged)PdfDialogHost–PromptAsync,SecretAsync,ConfirmAsyncPdfTheme,PdfResources,PdfInputFocus.ActivateWhenLoaded
Basic usage
using Microsoft.UI.Xaml.Controls;
using PdfSpace.Controls;
var status = PdfTheme.Text("Ready");
var panel = new PdfToolPanel("Review");
panel.Heading("Annotate");
panel.Add("Highlight", PdfIconKind.Highlight, () => status.Text = "Highlight");
panel.Add("Add note", PdfIconKind.Comment, () => status.Text = "Note");
var palette = new PdfColorPalette();
palette.ColorChanged += argb => status.Text = $"#{argb:X8}";
panel.Items.Children.Add(palette);
panel.Items.Children.Add(status);
var dialogs = new PdfDialogHost();
var root = new Grid { Children = { panel, dialogs } };
window.Content = root;
string? title = await dialogs.PromptAsync("Rename", "New document title", initial: "Report.pdf");The embeddable document viewport: Skia-rendered pages with continuous/single/two-page layouts, zoom/pan, text selection, direct annotation creation and editing for the session's current tool, in-place form filling, search highlighting and native object/image manipulation surfaces, plus virtualized PdfThumbnailView. Navigation and zoom are view operations; document changes belong to EditorSession. Depends on PdfSpace.Controls, PdfSpace.Editing, PdfSpace.Skia and Uno Platform.
dotnet add package PdfSpace.Viewer --prereleaseKey types
PdfViewport–PdfViewport(session),Renderer,Navigate,FitPage,ZoomTo,SetLayout,HighlightSearchPdfViewportevents –StatusChanged,NoteRequested,FieldRequested,LinkRequested,ContextRequestedPdfThumbnailView–PdfThumbnailView(viewport),PageActivated,OrganizeModeNativeObjectTarget,NativeImageTarget– detached geometry the host supplies for object editing
Basic usage
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using PdfSpace.Core;
using PdfSpace.Documents;
using PdfSpace.Editing;
using PdfSpace.Viewer;
var session = new EditorSession(PdfReader.Open(pdfBytes, "input.pdf"));
var viewport = new PdfViewport(session);
var thumbnails = new PdfThumbnailView(viewport) { Width = 180 };
session.SetTool(PdfTool.Highlight);
viewport.NoteRequested += (pageIndex, point) => session.AddAnnotation(
new Annotation { Kind = AnnotationKind.Note, Bounds = new RectD(point.X, point.Y, 24, 24), Text = "New note" }, pageIndex);
viewport.Loaded += (_, _) => viewport.FitPage(); // needs a nonzero arranged size
var root = new Grid { ColumnDefinitions = { new ColumnDefinition { Width = GridLength.Auto }, new ColumnDefinition() } };
Grid.SetColumn(viewport, 1);
root.Children.Add(thumbnails);
root.Children.Add(viewport);
window.Content = root;
window.Closed += (_, _) => viewport.Dispose();The complete Acrobat-style shell used by the app: document tabs, quick tools, comments, form preparation, original-text and native object editing, page organization, search, export, protection, redaction, OCR and recovery. Platform services come from IWorkspaceStorage; security and OCR providers are optional constructor arguments. The host owns the injected typeface; the workbench owns the viewports it creates. Depends on PdfSpace.Pdf, PdfSpace.Viewer, PdfSpace.Storage, PdfSpace.Ocr and Uno Platform.
dotnet add package PdfSpace.Workbench --prereleaseKey types
PdfWorkbench–PdfWorkbench(initial, storage, typeface, security?, ocr?),AddDocument,OpenFileAsync,OfferRecoveryAsync- State –
Session,Viewport,DocumentCount,HasUnsavedChanges,StateChanged,ShowStatus
Basic usage
using PdfSpace.Ocr;
using PdfSpace.Pdf;
using PdfSpace.Skia;
using PdfSpace.Storage;
using PdfSpace.Workbench;
using SkiaSharp;
using var fontData = SKData.CreateCopy(fontBytes);
var typeface = SKTypeface.FromData(fontData); // host-owned UI/annotation font
IWorkspaceStorage storage = new FolderStorage(appData); // see PdfSpace.Storage above
var workbench = new PdfWorkbench(SampleDocument.Create(typeface), storage, typeface,
security: new NativePdfSecurityProvider(), ocr: new TesseractProcessEngine()); // desktop providers
window.Content = workbench;
await workbench.OfferRecoveryAsync();
window.Closed += (_, _) => { workbench.Dispose(); typeface.Dispose(); };From engine to workbench
Layers build bottom‑up: each one depends only on those beneath it.
Immutable workspace, annotation and field models, layout transforms and transactional undo — free of Uno, rendering and storage.
PdfSpace.CorePdfSpace.LayoutPdfSpace.EditingPdfPig import and search feed a source-pinned Skia renderer with cached pictures, overlays and visual export.
PdfSpace.DocumentsPdfSpace.Rendering.SkiaPdfSpace.SkiaA PDFsharp-backed structured writer, pluggable security provider and local OCR pipeline.
PdfSpace.PdfPdfSpace.OcrCustom Uno controls and an embeddable Skia viewport for pointer editing, forms and thumbnails.
PdfSpace.ControlsPdfSpace.ViewerThe multi-document shell composes the workflows; hosts inject browser or desktop storage contracts.
PdfSpace.WorkbenchPdfSpace.StorageEmbed, script, extend
git clone https://github.com/wieslawsoltes/PdfSpace.git
cd PdfSpace
python3 scripts/fetch-assets.py
dotnet workload install wasm-tools
# Native engine and PDF round-trip tests.
dotnet run --project tests/PdfSpace.Tests -c Release
# Desktop host.
dotnet run --project src/PdfSpace.App -f net10.0-desktop -p:PdfSpaceDesktopOnly=true
# Browser publish and static-root collection.
dotnet publish src/PdfSpace.App -f net10.0-browserwasm -c Release \
-o artifacts/publish -p:WasmShellWebAppBasePath=/PdfSpace/
python3 scripts/collect-site.py artifacts/publish artifacts/site
python3 scripts/serve-site.py --directory artifacts/site --port 4173File formats
Keyboard essentials
Continuously verified
Each push runs through GitHub Actions. The public browser build is deployed only from successful pipelines.
Toolchain
Third‑party NuGet packages
git clone https://github.com/wieslawsoltes/PdfSpace.git
cd PdfSpace
dotnet workload install wasm-tools
dotnet run --project src/PdfSpace.App -f net10.0-browserwasmWhat it doesn’t claim
The project documents its limits as carefully as its features.
- Functional PDF workflows, not complete or pixel-identical Adobe Acrobat compatibility
- Export PDF rewrites the document; it is not an incremental or guaranteed lossless save
- No XFA, form scripts/calculations, certificate signatures, full tagging/compliance or Office conversion
- Text editing has no paragraph reflow or complex-script shaping; unsupported encodings are rejected
- Redacted output is image-only; .pdfspace workspaces are unencrypted and keep the full original