JetsonPDF.Wpf
1.1.0
dotnet add package JetsonPDF.Wpf --version 1.1.0
NuGet\Install-Package JetsonPDF.Wpf -Version 1.1.0
<PackageReference Include="JetsonPDF.Wpf" Version="1.1.0" />
<PackageVersion Include="JetsonPDF.Wpf" Version="1.1.0" />
<PackageReference Include="JetsonPDF.Wpf" />
paket add JetsonPDF.Wpf --version 1.1.0
#r "nuget: JetsonPDF.Wpf, 1.1.0"
#:package JetsonPDF.Wpf@1.1.0
#addin nuget:?package=JetsonPDF.Wpf&version=1.1.0
#tool nuget:?package=JetsonPDF.Wpf&version=1.1.0
JetsonPDF.Wpf
WPF integration for JetsonPDF. One package, two pipelines:
- Authoring (XAML → PDF) — write a
<jetsonpdf:XamlDocument>tree of WPF panels and controls; the real WPF layout engine (XamlReader.ParseplusMeasure/Arrange) lays it out and JetsonPDF emits a PDF. Types live underJetsonPDF.Wpf.Authoring. - Viewer (PDF → XAML) — feed a parsed
ReadDocument(fromJetsonPDF.Reader) intoPdfToXamlConverterand get back WPF XAML thatXamlReader.Parseturns into a live visual tree. No native deps, no filesystem cache. Types live underJetsonPDF.Wpf.
Authoring example:
using JetsonPDF.Wpf.Authoring;
byte[] pdf = XamlToPdfConverter.Convert("""
<jetsonpdf:XamlDocument
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
PageSize="Letter" Title="Hello" Author="Me">
<StackPanel Margin="40">
<TextBlock Text="Hello, PDF" FontSize="32" FontWeight="Bold"/>
<TextBlock Text="Built with JetsonPDF.Wpf authoring." Margin="0,8,0,0"/>
</StackPanel>
</jetsonpdf:XamlDocument>
""");
File.WriteAllBytes("hello.pdf", pdf);
Viewer example:
using System.Windows.Markup;
using JetsonPDF.Reading;
using JetsonPDF.Wpf;
ReadDocument doc = PdfReader.Read(File.ReadAllBytes("hello.pdf"));
// PdfToXamlConverter is async because the shared code path also serves
// OpenSilver (which decodes JPEG via a JS promise). On WPF every
// await completes synchronously, so blocking the Task is safe.
string xaml = PdfToXamlConverter.ConvertAsync(doc).GetAwaiter().GetResult();
var visual = (UIElement)XamlReader.Parse(xaml);
// Hand the visual to any WPF host — Window.Content, ScrollViewer.Content,
// HwndHost child, etc.
Windows-only (net8.0-windows + WPF). For OpenSilver in the browser, use
JetsonPDF.OpenSilver,
which shares the same authoring dialect and viewer-emitter source files.
Contents
Overview
JetsonPDF.Wpf is the WPF-bound high-level wrapper around JetsonPDF.Writer
and JetsonPDF.Reader. Two independent pipelines ship in the same package:
| Pipeline | Direction | Entry point | Namespace |
|---|---|---|---|
| Authoring | XAML → PDF bytes | XamlToPdfConverter.Convert(xaml) |
JetsonPDF.Wpf.Authoring |
| Viewer | ReadDocument → XAML string |
PdfToXamlConverter.ConvertAsync(doc) |
JetsonPDF.Wpf |
Authoring relies on WPF's own layout engine — XamlReader.Parse instantiates
the tree, the converter calls Measure/Arrange at the chosen page
dimensions, then walks the arranged visuals and emits PDF drawing operations.
All the relative-layout machinery (Grid rows/columns, StackPanel stacking,
DockPanel docking, Border padding) comes for free because WPF runs it. The
walker only has to read the arranged rectangles and the per-element brush /
font / geometry data.
The viewer pipeline is the inverse: a parsed ReadDocument becomes a string
of XAML that any XamlReader.Parse call turns back into a live visual tree.
The output is a top-level StackPanel containing one Canvas per page with
absolute-positioned TextBlock, Image, Path, and form-widget children.
Shared source files live under JetsonPDF.XamlToPdfConverter.Authoring,
JetsonPDF.XamlToPdfConverter.Core, and JetsonPDF.PdfToXamlConverter.Shared;
the same XAML dialect and emitter logic also drives the OpenSilver package.
Authoring (XAML → PDF)
<jetsonpdf:XamlDocument> and <jetsonpdf:XamlPage>
XamlDocument is the preferred root. It carries PDF-wide metadata (page size,
landscape, title, author) and exposes collections for outline / named
destinations / page labels / layers / conformance. Any other
FrameworkElement works too — the converter will size it against the
options' default page size when its Width/Height are unset.
<jetsonpdf:XamlDocument
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
PageSize="A4" Landscape="False"
Title="Quarterly Report" Author="Acme Co.">
<Grid Margin="50">
<TextBlock Text="Hello, A4 world" FontSize="28"/>
</Grid>
</jetsonpdf:XamlDocument>
| Property | Type | Default | Notes |
|---|---|---|---|
PageSize |
JetsonPageSize |
Letter |
Letter, Legal, Tabloid, A3, A4, A5. |
Landscape |
bool |
False |
Swaps width and height of the named size. |
PageWidth / PageHeight |
double (DIP) |
NaN |
Custom dimensions; override PageSize when both are set. |
Title / Author |
string? |
null |
Emitted as /Info /Title and /Info /Author. |
Pages |
ObservableCollection<XamlPage> |
empty | Populate for explicit multi-page. |
Outline |
Collection<XamlOutlineItem> |
empty | Bookmark tree (/Outlines). |
NamedDestinations |
Collection<NamedDestination> |
empty | /Names /Dests. |
PageLabels |
Collection<XamlPageLabel> |
empty | /PageLabels (§12.4.2). |
Layers |
Collection<Layer> |
empty | Optional content groups (§8.11). |
Conformance |
Conformance |
None |
PDF/A or PDF/UA declaration. |
XamlPage is a ContentControl that you drop into XamlDocument.Pages. It
re-declares the four size properties via AddOwner, so a per-page override
wins over the document-wide setting (locally set values via
ReadLocalValue — unset properties inherit):
<jetsonpdf:XamlDocument PageSize="Letter">
<jetsonpdf:XamlDocument.Pages>
<jetsonpdf:XamlPage PageSize="A3" Landscape="True">
<Grid><TextBlock Text="Cover" FontSize="96"/></Grid>
</jetsonpdf:XamlPage>
<jetsonpdf:XamlPage>
<StackPanel Margin="50">
<TextBlock Text="Body" FontSize="20"/>
</StackPanel>
</jetsonpdf:XamlPage>
</jetsonpdf:XamlDocument.Pages>
</jetsonpdf:XamlDocument>
Layout panels
Anything WPF can lay out, the converter can emit. Layout is run by WPF; the emitter only reads the arranged geometry.
| Panel | Supported usage |
|---|---|
Canvas |
Absolute positioning via Canvas.Left / Canvas.Top. |
StackPanel |
Vertical or horizontal stack with Orientation. |
Grid |
Full ColumnDefinitions / RowDefinitions including *, Auto, fixed. |
DockPanel |
Standard DockPanel.Dock="Left/Right/Top/Bottom" plus LastChildFill. |
Border |
Padding, border thickness, border brush, background, corner radius. |
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Padding="8" Background="#EEEEEE" CornerRadius="4">
<TextBlock Text="Label"/>
</Border>
<Border Grid.Column="1" Padding="8" BorderBrush="#888888" BorderThickness="1">
<TextBlock Text="Value"/>
</Border>
</Grid>
Shapes
Rectangle, Ellipse, Line, and Path map to PDF path operators. Stroke,
fill, stroke thickness, dash array, and dash offset all round-trip.
<Canvas>
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="120" Height="60"
Stroke="Navy" StrokeThickness="2" Fill="#FFFAEA"/>
<Ellipse Canvas.Left="200" Canvas.Top="50" Width="80" Height="80"
Stroke="#FF5050" StrokeThickness="1.5"/>
<Line X1="50" Y1="160" X2="280" Y2="160" Stroke="#999999" StrokeThickness="1"/>
<Path Stroke="DarkGreen" StrokeThickness="2" Fill="#E0F2E5"
Data="M 50,200 L 100,180 L 150,210 C 170,250 200,250 230,210 Z"/>
</Canvas>
Path.Data supports the full WPF Path mini-language plus all PathSegment
subclasses: LineSegment, BezierSegment, QuadraticBezierSegment,
PolyLineSegment, PolyBezierSegment, PolyQuadraticBezierSegment, and
ArcSegment (flattened to cubic Béziers by the path flattener).
Text
TextBlock maps to PDF text-showing operators. Font family resolution falls
back through the registered standard 14 (Helvetica / Times-Roman / Courier /
Symbol / ZapfDingbats and their style variants) when the requested family
isn't available as a TrueType / OpenType file on the system.
<StackPanel>
<TextBlock Text="Heading"
FontFamily="Times New Roman" FontSize="24"
FontWeight="Bold" Foreground="#1A2C5C"/>
<TextBlock FontFamily="Helvetica" FontSize="11" Margin="0,8,0,0">
<Run Text="Mixed " FontWeight="Bold"/>
<Run Text="runs " FontStyle="Italic"/>
<Run Text="with per-run styling."/>
</TextBlock>
</StackPanel>
Supported on TextBlock / Run: FontFamily, FontSize, FontWeight,
FontStyle, Foreground, TextAlignment, LineHeight, TextWrapping.
Images
<Image> accepts any WPF ImageSource. The most common shapes:
<Image Source="C:\art\logo.png" Width="120"/>
<Image Source="pack://application:,,,/Resources/seal.png" Width="80"/>
<Image xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
Source="{jetsonpdf:Base64Image Data=iVBORw0KGgo...}"
Width="48" Height="48"/>
Base64ImageExtension decodes the base-64 string into a frozen BitmapImage
at parse time. WPF's built-in ImageSourceConverter doesn't accept data:
URIs, so this extension fills that gap without touching the filesystem.
Annotations
Annotations live in JetsonPDF.Wpf.Authoring and derive from the abstract
AnnotationElement (which is a FrameworkElement, so it participates in WPF
layout and you can place it via Canvas.Left/Top + Width/Height).
| Element | PDF subtype | Notes |
|---|---|---|
LinkAnnotation |
/Link |
Uri, NamedDestination, or Target (x:Reference to an element). |
TextMarkupAnnotation |
/Highlight / /Underline / /StrikeOut / /Squiggly |
Target to a TextBlock builds quads via FormattedText.BuildHighlightGeometry. |
FreeTextAnnotation |
/FreeText |
Self-contained text note with font / colour / alignment. |
StampAnnotation |
/Stamp |
StampName (e.g. Approved, Draft, Confidential). |
SquareAnnotation / CircleAnnotation |
/Square / /Circle |
Rect-based with stroke / fill / border width. |
LineAnnotation |
/Line |
X1/Y1/X2/Y2 plus optional StartEnding / EndEnding. |
PolygonAnnotation / PolyLineAnnotation |
/Polygon / /PolyLine |
Vertices PointCollection. |
InkAnnotation |
/Ink |
Strokes — collection of PointCollection per stroke. |
<Canvas>
<TextBlock x:Name="Para1" Canvas.Left="50" Canvas.Top="100" Width="400"
TextWrapping="Wrap" FontSize="11"
Text="The quick brown fox jumps over the lazy dog."/>
<jetsonpdf:TextMarkupAnnotation
Kind="Highlight" Color="#80FFEA00"
Target="{x:Reference Para1}" StartIndex="4" Length="5"/>
<jetsonpdf:LinkAnnotation Canvas.Left="50" Canvas.Top="160"
Width="200" Height="18"
Uri="https://example.com"/>
<jetsonpdf:StampAnnotation Canvas.Left="350" Canvas.Top="50"
Width="120" Height="40" StampName="Approved"/>
<jetsonpdf:InkAnnotation StrokeColor="#0066CC" BorderWidth="1.5">
<jetsonpdf:InkAnnotation.Strokes>
<jetsonpdf:Points Data="50,300 80,290 120,310 160,295"/>
<jetsonpdf:Points Data="50,330 200,330"/>
</jetsonpdf:InkAnnotation.Strokes>
</jetsonpdf:InkAnnotation>
</Canvas>
The <jetsonpdf:Points> markup extension wraps a delimited point list and
materialises a PointCollection. It exists for parity with OpenSilver, whose
XAML parser doesn't accept multi-point literals inside a <PointCollection>
element directly — using <jetsonpdf:Points> makes the same XAML render on
both runtimes.
Form widgets via Form.* attached properties
Standard WPF input controls become AcroForm widgets when you set
jetsonpdf:Form.FieldName on them. The converter reads the arranged bounds
and skips the control's visual children, so the PDF carries the widget rect
and not the rendered chrome underneath.
| Host control | Field type | /Ff flags honoured |
|---|---|---|
TextBox |
/FT /Tx |
MaxLength, IsMultiline, IsPassword (and WPF's AcceptsReturn, IsReadOnly). |
CheckBox |
/FT /Btn |
Checked = Yes, unchecked = Off. |
ComboBox |
/FT /Ch (Combo) |
IsEditable ↔ /Edit flag, Items, SelectedValue. |
ListBox |
/FT /Ch |
SelectionMode, Items, SelectedItems. |
Button |
/FT /Btn (Pushbutton) |
Form.Action → PDF action (URI / Named / ResetForm). |
<StackPanel Margin="50" xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025">
<TextBlock Text="Name"/>
<TextBox Width="280" Height="22"
jetsonpdf:Form.FieldName="applicant.name"
jetsonpdf:Form.MaxLength="64"/>
<TextBlock Text="Comments" Margin="0,10,0,0"/>
<TextBox Width="280" Height="80" AcceptsReturn="True"
jetsonpdf:Form.FieldName="applicant.comments"
jetsonpdf:Form.IsMultiline="True"/>
<CheckBox Content="I agree" Margin="0,10,0,0"
jetsonpdf:Form.FieldName="applicant.agreed" IsChecked="True"/>
<ComboBox Width="160" Margin="0,10,0,0"
jetsonpdf:Form.FieldName="applicant.region">
<ComboBoxItem Content="NA"/>
<ComboBoxItem Content="EMEA"/>
<ComboBoxItem Content="APAC"/>
</ComboBox>
<Button Content="Print" Width="80" Height="24" Margin="0,16,0,0"
jetsonpdf:Form.FieldName="actions.print"
jetsonpdf:Form.Action="Print"/>
<Button Content="Clear" Width="80" Height="24" Margin="0,4,0,0"
jetsonpdf:Form.FieldName="actions.reset"
jetsonpdf:Form.Action="Reset"/>
</StackPanel>
Form.Action recognised shorthands:
| Value | PDF action |
|---|---|
"Print" |
NamedAction("Print") — viewer print dialog. |
"Reset" |
ResetFormAction — clears every field. |
http://…, https://…, mailto:… |
UriAction. |
| anything else | NamedAction(value). |
Page-context markup extensions
Inside any element the converter installs a per-page JetsonPageContext on
DataContext. Two markup extensions resolve to bindings against it:
| Extension | Returns |
|---|---|
{jetsonpdf:PageNumber} |
1-based index of the current page. |
{jetsonpdf:PageCount} |
Total page count for the whole document. |
<DockPanel LastChildFill="True">
<Grid DockPanel.Dock="Bottom" Margin="40,0,40,20">
<TextBlock TextAlignment="Right" Foreground="#666666">
<Run Text="XamlPage "/>
<Run Text="{jetsonpdf:PageNumber}"/>
<Run Text=" of "/>
<Run Text="{jetsonpdf:PageCount}"/>
</TextBlock>
</Grid>
<ContentControl></ContentControl>
</DockPanel>
Both extensions support StringFormat:
<TextBlock Text="{jetsonpdf:PageNumber StringFormat='XamlPage {0}'}"/>
Multi-page documents
Populate XamlDocument.Pages and the converter emits one PDF page per entry.
Each page is Measured and Arranged at its own dimensions, so per-page
overrides work cleanly. The converter sets a fresh JetsonPageContext on
each page so {jetsonpdf:PageNumber} / {jetsonpdf:PageCount} resolve to
the right values.
<jetsonpdf:XamlDocument PageSize="Letter" Author="Acme">
<jetsonpdf:XamlDocument.Pages>
<jetsonpdf:XamlPage>
<StackPanel Margin="50">
<TextBlock Text="XamlPage 1 — Introduction" FontSize="20"/>
</StackPanel>
</jetsonpdf:XamlPage>
<jetsonpdf:XamlPage>
<StackPanel Margin="50">
<TextBlock Text="XamlPage 2 — Details" FontSize="20"/>
<TextBlock Text="{jetsonpdf:PageNumber StringFormat='Page {0}'}"
Margin="0,40,0,0"/>
</StackPanel>
</jetsonpdf:XamlPage>
</jetsonpdf:XamlDocument.Pages>
</jetsonpdf:XamlDocument>
PaginatedTable
<jetsonpdf:PaginatedTable> is a flowing table primitive: a header row plus
an arbitrarily long sequence of rows bound to ItemsSource. When the row
count exceeds the arranged slot, the converter expands the host
XamlPage into N PDF pages, re-parses the XAML once per slice so
{jetsonpdf:PageNumber} bindings stay live, and repeats the header on every
spawned page.
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Top" Text="Order #1042" FontSize="24"
Margin="40,40,40,8"
jetsonpdf:Pagination.HideOnOverflow="True"/>
<Border DockPanel.Dock="Bottom" Margin="40,8,40,20" Height="30">
<TextBlock TextAlignment="Right">
<Run Text="Page "/><Run Text="{jetsonpdf:PageNumber}"/>
<Run Text=" of "/><Run Text="{jetsonpdf:PageCount}"/>
</TextBlock>
</Border>
<jetsonpdf:PaginatedTable Margin="40,0,40,0"
ItemsSource="{Binding LineItems}"
HeaderHeight="28" RowHeight="22"
HeaderBackground="#1A2C5C" HeaderForeground="White"
BorderBrush="#888888" BorderThickness="0.5"
FontFamily="Helvetica" FontSize="11">
<jetsonpdf:PaginatedTable.Columns>
<jetsonpdf:PaginatedColumn Header="SKU" Width="120" Binding="Sku"/>
<jetsonpdf:PaginatedColumn Header="Name" Width="*" Binding="Name"/>
<jetsonpdf:PaginatedColumn Header="Qty" Width="80" Binding="Qty"
TextAlignment="Right" HeaderAlignment="Right"/>
<jetsonpdf:PaginatedColumn Header="Price" Width="100" Binding="Price"
TextAlignment="Right" HeaderAlignment="Right"/>
</jetsonpdf:PaginatedTable.Columns>
</jetsonpdf:PaginatedTable>
</DockPanel>
jetsonpdf:Pagination.HideOnOverflow="True" on any descendant collapses
that element on overflow pages so its space is reclaimed by the table — the
lead page sees the element as authored, every subsequent slice doesn't.
Outline, named destinations, page labels, layers, conformance
XamlDocument exposes five collections that round-trip into the matching
catalog entries.
<jetsonpdf:XamlDocument PageSize="Letter" Conformance="PdfA1b">
<jetsonpdf:XamlDocument.NamedDestinations>
<jetsonpdf:NamedDestination Name="intro" PageIndex="0"/>
<jetsonpdf:NamedDestination Name="results" PageIndex="3" Mode="Xyz" Left="40" Top="100" Zoom="1.0"/>
</jetsonpdf:XamlDocument.NamedDestinations>
<jetsonpdf:XamlDocument.Outline>
<jetsonpdf:XamlOutlineItem Title="Introduction" NamedDestination="intro" Bold="True" IsExpanded="True">
<jetsonpdf:XamlOutlineItem.Children>
<jetsonpdf:XamlOutlineItem Title="Why this report" PageIndex="0"/>
</jetsonpdf:XamlOutlineItem.Children>
</jetsonpdf:XamlOutlineItem>
<jetsonpdf:XamlOutlineItem Title="Results" NamedDestination="results"/>
</jetsonpdf:XamlDocument.Outline>
<jetsonpdf:XamlDocument.PageLabels>
<jetsonpdf:XamlPageLabel StartPage="0" Style="LowerRoman"/>
<jetsonpdf:XamlPageLabel StartPage="4" Style="DecimalArabic"/>
</jetsonpdf:XamlDocument.PageLabels>
<jetsonpdf:XamlDocument.Layers>
<jetsonpdf:Layer Name="Design" VisibleByDefault="True" Intent="Design"/>
<jetsonpdf:Layer Name="Notes" VisibleByDefault="False"/>
</jetsonpdf:XamlDocument.Layers>
<StackPanel jetsonpdf:XamlDocument.Layer="Notes">
<TextBlock Text="Reviewer notes" FontStyle="Italic"/>
</StackPanel>
</jetsonpdf:XamlDocument>
Conformance accepts every flag the writer supports (PdfA1b, PdfA2a,
PdfA2b, PdfA2u, PdfA3a, PdfA3b, PdfA3u, PdfUa1, PdfUa2).
Setting it stamps the XMP packet and catalog entries but does not
validate the rendered content — pair with ConformanceValidator.Validate(doc)
or rely on Document.Save with ThrowOnConformanceError to enforce it.
Conversion options
XamlToPdfConverter.Convert(xaml, options) accepts an XamlToPdfOptions:
| Property | Default | Purpose |
|---|---|---|
DefaultPageSize |
PageSize.Letter |
Fallback page size when the root has neither explicit dimensions nor a XamlDocument size. |
Title / Author |
null |
/Info entries; overridden by XamlDocument.Title / Author when both are present. |
DefaultFontFamily |
FontFamily.Helvetica |
Used when a TextBlock has no FontFamily or specifies an unrecognised family. |
DefaultFontSizeDip |
16.0 |
Used when TextBlock has no FontSize (16 DIP ≈ 12 pt, matching WPF). |
DefaultTextColor |
Color.Black |
Used when Foreground is unset. |
RasterizeEffectsDpi |
192.0 |
Rendering DPI for elements with a UIElement.Effect (DropShadow, Blur, custom ShaderEffect). PDF has no native primitive for these, so the subtree is rasterised. |
Convenience overloads cover the common shapes:
byte[] bytes = XamlToPdfConverter.Convert(xaml);
byte[] bytes = XamlToPdfConverter.Convert(xaml, new XamlToPdfOptions { Title = "Q1" });
XamlToPdfConverter.Convert(xaml, fileStream);
XamlToPdfConverter.Convert(xaml, fileStream, new XamlToPdfOptions { DefaultPageSize = PageSize.A4 });
STA threading requirement
WPF object creation and layout are single-threaded-apartment only — they
will throw if you call XamlReader.Parse or Measure/Arrange from a
non-STA thread. The same applies to the authoring converter: it must run on
an STA thread.
Console apps and ASP.NET background workers default to MTA. Wrap the call in an STA thread:
static byte[] BuildPdfOnSta(string xaml)
{
byte[] result = null!;
var thread = new Thread(() => result = XamlToPdfConverter.Convert(xaml));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return result;
}
A WPF app's UI dispatcher is already STA, so calling from there is fine —
just remember the converter blocks the dispatcher (no await inside, layout
runs synchronously). Use Task.Run only after switching to an STA worker.
Viewer (PDF → XAML)
PdfToXamlConverter
using System.Windows.Markup;
using JetsonPDF.Reading;
using JetsonPDF.Wpf;
// 1. Read the PDF into the JetsonPDF object model.
ReadDocument doc = PdfReader.Read(File.ReadAllBytes("report.pdf"));
// 2. Convert to a XAML string.
string xaml = PdfToXamlConverter.ConvertAsync(doc).GetAwaiter().GetResult();
// 3. Parse and host.
var pages = (UIElement)XamlReader.Parse(xaml);
window.Content = new ScrollViewer { Content = pages };
ConvertAsync returns a top-level StackPanel of Canvas pages with
absolute-positioned children — TextBlock for text, Image for raster
content (PNG / JPEG / TIFF / JBIG2 / JPX decoded by the reader), Path for
vectors, Line / Rectangle / Ellipse for primitives, Glyphs for
embedded fonts when available, and form widgets (TextBox, CheckBox,
ComboBox, ListBox, Button) for AcroForm fields. Annotation links emit
transparent Rectangles with the WidgetActions.Action attached property
set.
PdfToXamlConverter.ConvertPageAsync(page) produces a standalone Canvas
for a single page — useful for paginated UI that virtualises pages on
demand.
ReadPage page = doc.Pages[3];
string xaml = await PdfToXamlConverter.ConvertPageAsync(page);
Options on PdfToXamlOptions:
| Property | Purpose |
|---|---|
ShowFormFields |
Render AcroForm widgets as live WPF controls (default true). |
HideContentUnderWidgets |
When ShowFormFields is on, clip widget rects out of the content layer so producer-painted placeholder borders don't show through. |
EmbedFonts |
Emit embedded fonts as in-memory GlyphTypeface references so Glyphs elements render exactly. |
In-memory contract
The viewer pipeline never touches the filesystem. Images are embedded as
base-64 BitmapImages via Base64ImageExtension; fonts are loaded from the
parsed ReadDocument's in-memory byte buffers. Everything you need to host
the output is in the returned XAML string plus the ResourceCache the
converter built internally.
This matters for:
- Server-side rendering where the process has no writable disk.
- Sandboxed environments where temp paths are off-limits.
- Cases where you want to ship the XAML to another process / machine and parse it there.
WidgetActions attached behavior
The viewer emits an Action payload on every widget / link with a PDF
action. To wire up dispatch, set WidgetActions.Enabled="True" on any
ancestor of the parsed XAML:
<ContentControl xmlns:jetsonpdf="clr-namespace:JetsonPDF.Wpf;assembly=JetsonPDF.Wpf"
jetsonpdf:WidgetActions.Enabled="True">
</ContentControl>
Or in code:
WidgetActions.SetEnabled(root, true);
root.AddHandler(WidgetActions.ActionInvokedEvent,
new EventHandler<WidgetActionEventArgs>((s, e) =>
{
if (e.Action is GoToAction goTo)
{
// Custom in-document navigation — viewer-specific.
ScrollTo(e.FieldName, goTo);
e.Handled = true;
}
}));
Default dispatch covers:
| Action | Default behaviour |
|---|---|
UriAction |
Opens the URI in the OS default browser via Process.Start. |
NamedAction("Print") |
Shows PrintDialog, prints the root visual. |
ResetFormAction |
Clears descendant TextBox / PasswordBox / CheckBox / RadioButton / ComboBox / ListBox controls. Honours FieldNames + ExcludeFields. |
SubmitFormAction / JavaScriptAction / GoToAction / other named |
Bubbles the ActionInvokedEvent without acting. Consumers handle these — they need viewer-specific context. |
Triggers wired automatically:
| Control | Event |
|---|---|
ButtonBase (Button, CheckBox, RadioButton) |
Click |
TextBox / PasswordBox |
LostFocus (matches Acrobat's "on blur" semantics for text fields with /A dicts). |
ComboBox / ListBox |
SelectionChanged (matches PDF "calculate" semantics). |
Bare UIElement (e.g. link rectangle) |
MouseLeftButtonUp |
The ap-204 PDF's "Print" and "Clear" buttons exercise the
NamedAction("Print") and ResetFormAction paths respectively — both work
without any consumer code beyond setting Enabled="True".
Glyphs and negative-advance handling
Embedded fonts round-trip through <Glyphs> elements with explicit per-glyph
advances and offsets. WPF's Glyphs control crashes on negative advances
that come up in some CJK and complex-script subsets; the viewer pipeline
clamps those to a tiny positive epsilon so the page still renders.
PDF → TIFF
To rasterise a PDF to a multipage TIFF, add the separate
JetsonPDF.PdfToTiffConverter
package. It builds on this package's viewer pipeline — each page is laid
out by WPF, captured with RenderTargetBitmap, and encoded by the managed
JetsonPDF.Tiff writer (no GDI+):
using JetsonPDF.Tiff;
PdfToTiffConverter.ConvertToFile("input.pdf", "output.tif",
new PdfToTiffOptions { Dpi = 200 });
Like the authoring pipeline, it must run on an STA thread. See that package's README for the full option surface, per-page output, and progress reporting.
Limitations
- STA-only. Both pipelines require an STA thread — they use WPF object creation and layout under the hood. Console apps must spin up an STA worker (see STA threading requirement).
UIElement.Effectis rasterised. PDF has no native primitive forDropShadowEffect/BlurEffect/ customShaderEffect. The affected subtree is rendered to a bitmap atRasterizeEffectsDpi(default 192) and embedded. Text selection and vector fidelity are lost inside that subtree.- One
PaginatedTableperXamlPage(v1). If you need multiple flowing tables per page, split them across separateXamlPages for now. PaginatedColumn.Width="Auto"falls back to1*. Column-width measurement against cell contents is deferred. Use explicit DIP widths or star weights.- Custom font discovery. The converter resolves
FontFamilyagainst the installed system fonts and the standard 14 PDF fonts. Pack-URI fonts (pack://...#FamilyName) work; arbitrary stream-loaded fonts that aren't referenced through a normal WPFFontFamilymay not embed correctly. - Viewer round-trip is read-then-render.
PdfToXamlConverterproduces XAML for display, not for re-editing back throughXamlToPdfConverter. The two pipelines target different schemas (authoring grammar vs. flat Canvas-of-primitives) and the trip is lossy. - Annotation preview in WPF.
AnnotationElementsubclasses have no defaultOnRender— they're authoring metadata, not visible content. If you want a live preview of annotation rectangles in the WPF tree, subclass and overrideOnRender.
Targets
net8.0-windows
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows7.0 is compatible. net9.0-windows was computed. net10.0-windows was computed. |
-
net8.0-windows7.0
- JetsonPDF.Common (>= 1.1.0)
- JetsonPDF.Reader (>= 1.1.0)
- JetsonPDF.Writer (>= 1.1.0)
- JetsonPDF.XamlToPdfConverter.Core (>= 1.1.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on JetsonPDF.Wpf:
| Package | Downloads |
|---|---|
|
JetsonPDF.PdfToTiffConverter
Rasterizes a parsed JetsonPDF document to a multipage TIFF. Reuses the JetsonPDF.Wpf PdfToXamlConverter pipeline (XamlReader.Parse + WPF Measure/Arrange) for layout, captures each page via RenderTargetBitmap, and encodes the multipage TIFF through JetsonPDF.Tiff's managed TiffWriter (no GDI+ dependency). Windows-only because of WPF rasterisation (net8.0-windows + WPF); must be invoked from an STA thread. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.0 | 121 | 6/6/2026 |
| 1.0.0 | 114 | 5/23/2026 |
| 0.2.0-preview | 105 | 5/23/2026 |
| 0.1.0-preview | 110 | 5/17/2026 |