WPF

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.

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.

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.

<jetsonpdf:Document
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
    PageSize="Letter" Title="Invoice #123" Author="Acme">
  <jetsonpdf:Document.Pages>
    <jetsonpdf:Page>
      <Grid Margin="48">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="*"/>
          <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Border Grid.Row="0" Background="#213F85" Padding="20,12">
          <TextBlock Text="Invoice #123"
                     FontSize="28" FontWeight="Bold"
                     Foreground="White"/>
        </Border>

        <StackPanel Grid.Row="1" Margin="0,24,0,0">
          <TextBlock FontSize="14" Text="Bill to: Wile E. Coyote"/>
          <TextBlock FontSize="14" Text="123 Desert Rd."/>
        </StackPanel>

        <TextBlock Grid.Row="2" HorizontalAlignment="Right" FontSize="11">
          <Run Text="Page "/><Run Text="{jetsonpdf:PageNumber}"/>
          <Run Text=" of "/><Run Text="{jetsonpdf:PageCount}"/>
        </TextBlock>
      </Grid>
    </jetsonpdf:Page>
  </jetsonpdf:Document.Pages>
</jetsonpdf:Document>

Then convert — on an STA thread, since WPF requires it:

using JetsonPDF.Wpf.Authoring;

string xaml = File.ReadAllText("invoice.xaml");
byte[] pdfBytes = XamlToPdfConverter.Convert(xaml);
File.WriteAllBytes("invoice.pdf", pdfBytes);

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.

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>

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.

<TextBox jetsonpdf:Form.FieldName="email"
         jetsonpdf:Form.MaxLength="120"
         Width="320" Height="24"/>

<CheckBox jetsonpdf:Form.FieldName="subscribe" Content="Subscribe"/>

<ComboBox jetsonpdf:Form.FieldName="country">
  <ComboBoxItem Content="Canada"/>
  <ComboBoxItem Content="USA"/>
</ComboBox>

<Button jetsonpdf:Form.FieldName="submit"
        jetsonpdf:Form.Action="SubmitForm"
        Content="Send"/>

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 JetsonPageContext DataContext, so the page-context markup extensions resolve per-page:

<jetsonpdf:Document Title="Multi-page report" PageSize="Letter">
  <jetsonpdf:Document.Pages>
    <jetsonpdf:Page>
      <TextBlock Text="Cover" FontSize="48"/>
    </jetsonpdf:Page>

    <jetsonpdf:Page Landscape="True">
      <TextBlock FontSize="11">
        <Run Text="Page "/><Run Text="{jetsonpdf:PageNumber}"/>
        <Run Text=" of "/><Run Text="{jetsonpdf:PageCount}"/>
      </TextBlock>
    </jetsonpdf:Page>
  </jetsonpdf:Document.Pages>
</jetsonpdf:Document>

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

See the full feature matrix →