JetsonPDF as a first-class WPF citizen. The JetsonPDF.Wpf assembly ships two adapters that share one xmlns:jetsonpdf authoring dialect: XamlToPdfConverter parses XAML, lets WPF run Measure / Arrange, and walks the visual tree to emit a PDF; PdfToXamlConverter goes the other way, turning any ReadDocument into a XAML tree you can drop into a ContentControl.
Two adapters, one dialect.
Authoring — XamlToPdfConverter.Convert(xaml). Parses authoring-XAML, runs live WPF layout, walks the visual tree, emits PDF bytes. STA thread required.
Viewer — await PdfToXamlConverter.ConvertAsync(readDoc). Emits a StackPanel of Canvas pages with absolute-positioned TextBlock, Image, Path, and Glyphs elements. No filesystem access — images come back as base64 inline. WPF's underlying decoder is sync, so the returned Task completes inline; .GetAwaiter().GetResult() is safe from sync contexts.
Same XAML — both sides speak xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025". A document round-trips through PDF and back into an equivalent visual tree.
API reference:jetsonpdf-wpf-api.pdf (PDF→XAML viewer, XAML→PDF authoring, XamlDocument/XamlPage, PaginatedTable, form widgets, page-number markup) · jetsonpdf-tiff-api.pdf (the PdfToTiffConverter rasteriser ships in this project too).
Overview
The WPF adapter is the original platform integration for the authoring-XAML pipeline. It runs the parsed XAML through XamlReader.Parse, calls Measure / Arrange on the resulting tree, then walks the visual tree with VisualTreeHelper to translate each rendered element into Writer calls. Grid, StackPanel, DockPanel, Border, data-bound TextBlocks — everything WPF already knows how to lay out — "just works", because WPF is doing the layout.
The viewer side feeds a ReadDocument's content items back into the same authoring dialect. The result is a normal WPF visual that integrates with ScrollViewer, hit testing, printing, and the rest of the WPF surface.
STA thread — the authoring path needs WPF's dispatcher. Most desktop apps already run on STA; for console / unit-test contexts use [STAThread] on Main or start a dedicated STA thread.
No filesystem — the viewer never touches disk. Images come in via the {jetsonpdf:Base64Image} markup extension; there's no font / image cache on disk.
Quick start
Add the package to a WPF application project:
dotnet add package JetsonPDF.Wpf
Write the document as XAML. The root is <jetsonpdf:Document> with one or more <jetsonpdf:Page> children; anything inside a page is whatever WPF can lay out.
There's also a Stream overload that writes directly without buffering the whole document.
Supported XAML surface
Anything WPF lays out, the walker handles. The list below is the authoring-specific surface on top of that.
Document structure — <jetsonpdf:Document> root with multiple <jetsonpdf:Page> children, per-page width / height / landscape, Title / Author metadata, named destinations, page labels.
Live WPF layout — Grid, StackPanel, DockPanel, Border, Canvas, WrapPanel, UniformGrid — WPF runs Measure / Arrange and the walker reads the arranged bounds.
Shapes — Rectangle (with corner radii), Ellipse, Line, Path (LineGeometry, RectangleGeometry, EllipseGeometry, PathGeometry with all segment types including arcs), Image.
Text — TextBlock with FontFamily, FontSize, FontStyle, FontWeight, Foreground, TextWrapping, TextAlignment, and mixed-style <Run> inlines.
Annotations — Link, TextMarkup (Highlight / Underline / StrikeOut / Squiggly with Target binding to a TextBlock for AFM quad derivation), FreeText, Stamp, Square, Circle, Line, Polygon, PolyLine, Ink. Cross-page Target references resolve through a coordinate prepass before the walk.
Page-context markup — {jetsonpdf:PageNumber} and {jetsonpdf:PageCount} resolve against a per-page DataContext.
UIElement.Effect (DropShadow / Blur / shader) — rasterised via RenderTargetBitmap and embedded as a PNG (descendants are skipped so baked pixels don't double-paint).
Base64 images — {jetsonpdf:Base64Image Data='...'} for inline image payloads without filesystem access.
PDF → XAML viewer
Convert any ReadDocument straight into XAML and host it inside a WPF window. No filesystem access; images come back as base64 inline.
using JetsonPDF.Reading;
using JetsonPDF.Wpf;
using System.Windows.Markup;
var read = Reader.Load("input.pdf");
// WPF's path is synchronous under the hood; the Task returned from ConvertAsync
// is already-completed, so .GetAwaiter().GetResult() is safe.
string xaml = PdfToXamlConverter.ConvertAsync(read).GetAwaiter().GetResult();
// Parse the XAML and bind it into the host control.
var visual = (FrameworkElement)XamlReader.Parse(xaml);
PdfHost.Content = visual;
The output is a StackPanel — one Canvas per page — with absolute-positioned TextBlock, Image, Path, and Glyphs elements. Wrap it in a ScrollViewer for paging. Everything is real WPF, so hit testing, selection, and printing work the way they would for any other visual tree.
PdfmlView — live data-bound PDFML
JetsonPDF.Wpf.PdfmlView is a drop-in control for PDFML markup. Set Markup to a .pdfml document and Model to a data object; it renders the resulting PDF as a vector visual tree and re-renders automatically when the markup or model changes — ideal for previews that track a live view model (a timesheet date range, an invoice total).
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:jp="clr-namespace:JetsonPDF.Wpf;assembly=JetsonPDF.Wpf">
<!-- Markup is the .pdfml text; Model is the {Binding} source
(falls back to the control's DataContext when unset). -->
<jp:PdfmlView Markup="{Binding Template}" Model="{Binding Timesheet}"/>
</Window>
Pipeline — Markup + Model → PdfmlRenderer → PDF → Reader → PdfToXamlConverter → a vector tree in a ScrollViewer. It reuses the viewer above; nothing is rasterised.
Auto-refresh — the control subscribes to INotifyPropertyChanged on the model and re-renders on a short debounce. Changing Markup or swapping Model also refreshes.
Surface — read-only PageCount / RenderError; Rendered, RenderFailed, and WidgetActionInvoked events; live AcroForm widgets dispatch through the emitted controls.
Cross-platform — the same control ships in JetsonPDF.OpenSilver for the browser. A runnable WPF demo is at samples/PdfmlViewDemo.
PDF → TIFF rasterization
The separate JetsonPDF.PdfToTiffConverter package rasterizes a PDF into a multipage TIFF. It reuses the same viewer pipeline: each page is turned into a XAML Canvas by PdfToXamlConverter, laid out by WPF, captured with a RenderTargetBitmap, and encoded by the managed JetsonPDF.Tiff writer — no GDI+ or System.Drawing dependency.
dotnet add package JetsonPDF.PdfToTiffConverter
using JetsonPDF.Tiff;
PdfToTiffConverter.ConvertToFile("input.pdf", "output.tif",
new PdfToTiffOptions
{
Dpi = 200,
ColorMode = TiffColorMode.Grayscale,
Compression = TiffCompression.Deflate,
Pages = PdfToTiffOptions.ParsePageRange("1-3,5"),
});
There's an awaitable ConvertToFileAsync, a stream-based ConvertAsync(ReadDocument, Stream, ...), and RenderPageAsync(ReadPage) which returns a single page as a BitmapSource for a custom encoder. Set MultipageStrategy = TiffMultipageStrategy.OneTiffPerPage to emit one file per page, and pass an IProgress<TiffConversionProgress> to drive a progress bar.
Because it drives the WPF render path, calls must be made from an STA thread — the same constraint as the authoring pipeline. In a console host, mark Main with [STAThread] or marshal onto a dedicated STA thread. For browser-hosted rasterization see the OpenSilver guide.
AcroForm widgets in XAML
Tag a standard WPF control with jetsonpdf:Form.FieldName="..." and the walker turns it into a real PDF widget at the arranged bounds — Acrobat draws the actual chrome.
Supported controls: TextBox (text field), CheckBox, ComboBox, ListBox, Button (push button). Tuning attributes: Form.MaxLength, Form.IsMultiline, Form.IsPassword, Form.Action, Form.FieldAlignment. Form-marked controls are leaves — their descendant WPF chrome is skipped so the widget rect stays clean.
Multi-page authoring
<jetsonpdf:Document> carries a Pages collection of <jetsonpdf:Page> children. Each page can override Width, Height, and Landscape independently. The converter assigns each page a JetsonPageContextDataContext, so the page-context markup extensions resolve per-page:
Per-page sizes work through AddOwner DPs with ReadLocalValue-based inheritance from the document.
PaginatedTable
A multi-page-aware table that overflows row-by-row across page breaks, with a repeating header row on every page and an opt-in jetsonpdf:Pagination.HideOnOverflow for content that should only appear on the lead page (think "Notes:" headings, lead-page-only callouts).
If you'd rather drive layout from C#, the same multi-page table behaviour is also available through the Fluent API's .Table(...) primitive.
Caveats
Windows-only.JetsonPDF.Wpf targets net8.0-windows because it depends on PresentationFramework / PresentationCore. For browser-hosted authoring see OpenSilver; for headless layout see Fluent or Flow.
STA thread required. The authoring path uses XamlReader.Parse + WPF dispatcher. Use [STAThread] on Main, or start a dedicated STA worker thread, before calling XamlToPdfConverter.Convert.
Standard-14 font metrics by default. Built-in FontFamily values (Helvetica / Times / Courier) emit Standard-14 fonts in the PDF. To embed a non-standard face, register a TrueType through the Writer's EmbeddedFontFace.FromFile and reference it from your XAML by name.