The minimum: one page, one line of text, one file on disk.
using JetsonPDF;
var doc = new Document { Title = "Hello" };
var page = doc.AddPage(PageSize.Letter);
page.DrawText("Hello, PDF!",
new Font(FontFamily.Helvetica, 24),
x: 72, y: 700);
doc.Save("hello.pdf");
PDF coordinates start at the bottom-left, in points (1 point = 1/72 inch). A US Letter page is 612×792 points; y: 700 is near the top.
2Text, image, and a clickable link
Drop in a JPEG, draw a heading, and make the heading itself clickable.
using JetsonPDF;
var doc = new Document();
var page = doc.AddPage(PageSize.Letter);
var heading = new Font(FontFamily.Helvetica, 22, FontStyle.Bold);
page.DrawText("Annotated page", heading, x: 50, y: 750);
// Make the heading itself clickable.
page.AddLink(x: 50, y: 745, width: 280, height: 28,
uri: "https://jetsonpdf.com/");
// JPEG / PNG auto-detected from magic bytes.
var photo = Image.FromFile("photo.jpg");
page.DrawImage(photo, x: 50, y: 580, width: 300, height: 150);
doc.Save("page.pdf");
Image is created via the FromFile / FromJpeg / FromPng factories — the same instance can be drawn on many pages and is deduped to a single XObject in the output.
3Embedded TrueType + Unicode
Embed a font file once and use it like any standard font. Subsetting happens automatically on save.
using JetsonPDF;
var doc = new Document
{
Title = "Unicode demo",
PdfVersion = "2.0",
};
var calibri = EmbeddedFontFace.FromFile(@"C:\Windows\Fonts\calibri.ttf");
var font = new Font(calibri, 14);
var page = doc.AddPage(PageSize.Letter);
page.DrawText("English — Français — Español",
font, x: 72, y: 720);
page.DrawText("中文 — 日本語 — 한국어",
font, x: 72, y: 690);
doc.Save("unicode.pdf");
For CJK content, JetsonPDF emits a Type 0 / CIDFontType2 font with a /ToUnicode CMap so text is still selectable and copyable.
4Vector graphics + gradients
Paths, fills, and a clipped axial gradient. Set page.TransparencyGroup if you need PDF 2.0 transparency compositing for the whole page.
using JetsonPDF;
var doc = new Document();
var page = doc.AddPage(PageSize.Letter);
// Opt the page into an isolated transparency group (ISO 32000-2 §11.6.6).
page.TransparencyGroup = new TransparencyGroup();
// Solid fill + stroke.
page.DrawRectangle(72, 600, 200, 120,
fill: Color.Rgb(0.13, 0.31, 0.52),
stroke: Color.Black,
strokeWidth: 1.5);
// A 2-stop axial gradient, clipped to a rectangle.
var shading = new AxialShading(
x0: 72, y0: 480, x1: 272, y1: 480,
Color.Rgb(1.0, 0.85, 0.20),
Color.Rgb(0.90, 0.30, 0.30));
page.FillRectangleWithShading(x: 72, y: 460, width: 200, height: 80, shading);
doc.Save("vectors.pdf");
5AcroForm widgets
Add an interactive form — text fields, a checkbox, and a Print button — from C#.
using JetsonPDF;
using JetsonPDF.Forms;
var doc = new Document { Title = "Signup" };
var page = doc.AddPage(PageSize.Letter);
var label = new Font(FontFamily.Helvetica, 11);
page.DrawText("Name:", label, x: 72, y: 720);
page.AddTextField("name", x: 130, y: 712, width: 280, height: 20, label);
page.DrawText("Email:", label, x: 72, y: 690);
var email = page.AddTextField("email", x: 130, y: 682, width: 280, height: 20, label);
email.MaxLength = 120;
var subscribe = page.AddCheckBox("subscribe", x: 130, y: 650, width: 14, height: 14);
subscribe.IsChecked = true;
page.DrawText("Send me occasional updates", label, x: 152, y: 651);
var print = page.AddPushButton("printBtn",
x: 130, y: 600, width: 90, height: 24, label, caption: "Print");
print.Action = new NamedAction("Print");
doc.Save("signup.pdf");
6Fill an existing AcroForm
Open a PDF that already has form fields, discover them, mutate them, and save with a single-layer incremental update — original bytes preserved, digital signatures untouched.
using JetsonPDF;
using JetsonPDF.Forms;
using var form = Form.Open("input.pdf");
// Discover what's in the file. `Options` exposes /Opt as (Export, Display) pairs.
foreach (var f in form.Fields)
Console.WriteLine($"{f.Name}: {f.Type} = {f.Value}");
// Type-safe mutations: wrong type throws InvalidOperationException with
// the field name in the message.
form.Fields["Name"].SetText("Jordan");
form.Fields["IsActive"].SetChecked(true);
form.Fields["State"].SetChoice("CA");
// Stamp an image into a text field. ReadOnly is set by default so Adobe
// Acrobat renders the appearance immediately (it otherwise treats an
// empty-/V text widget's /AP /N as a stale cache and shows nothing until
// the user clicks).
form.Fields["Photo"].SetImage(Image.FromFile("signature.jpg"));
// Stamp a visual into an unsigned /FT /Sig widget. ReadOnly is never
// applied to signature widgets — the user must remain able to
// invoke Acrobat's Sign action.
if (!form.Fields["SignHere"].IsSigned)
form.Fields["SignHere"].SetImage(Image.FromFile("signature.png"));
form.Fields["OldValue"].Clear(); // per-field reset
// form.Clear(); // reset every value-bearing field
form.Save("output.pdf");
Repeated Save() calls within one Form instance are byte-identical — mutations are re-applied against the original source bytes rather than stacked on the previous save, so files don't grow with each pass. Encrypted PDFs round-trip with no re-keying: new objects are encrypted with the source file's existing key.
7AES-256 encryption with permissions
Encrypt the document and limit what viewers can do without the owner password.
using JetsonPDF;
var doc = new Document
{
Title = "Confidential",
PdfVersion = "2.0",
Encryption = new EncryptionOptions
{
Algorithm = EncryptionAlgorithm.Aes256,
UserPassword = "open-me",
OwnerPassword = "owner-only",
Permissions = Permissions.Print | Permissions.CopyContent,
},
};
doc.AddPage(PageSize.Letter)
.DrawText("Confidential.",
new Font(FontFamily.Helvetica, 24),
x: 72, y: 700);
doc.Save("encrypted.pdf");
AES-256 uses Standard Security Handler V5 (algorithms 2.B / 8 / 9 / 10 in PDF 2.0). Acrobat X+ and every modern viewer read it; for older viewers, drop down to Aes128.
8Detached PKCS#7 signature
Sign the rendered bytes with a certificate that has a private key. Multi-signing works via incremental update.
using System.Security.Cryptography.X509Certificates;
using JetsonPDF;
// 1. Build the document as usual.
var doc = new Document { Title = "Signed report" };
doc.AddPage(PageSize.Letter)
.DrawText("Signed.", new Font(FontFamily.Helvetica, 24), 72, 700);
using var ms = new MemoryStream();
doc.Save(ms);
byte[] unsigned = ms.ToArray();
// 2. Sign in-memory with a PFX certificate.
var cert = new X509Certificate2("signer.pfx", "pfx-password",
X509KeyStorageFlags.Exportable);
var signed = Signer.Sign(unsigned, new SignatureOptions(cert)
{
Reason = "Approved",
Location = "HQ",
FieldName = "Signature1",
});
File.WriteAllBytes("signed.pdf", signed);
For PAdES B-LT/B-LTA, follow up with Timestamper.AddDocumentTimestamp and Dss.AddSecurityStore to embed the validation material.
9Fluent invoice with header, footer, and table
The JetsonPDF.Fluent namespace exposes a QuestPDF-style layout DSL. Header, footer, and content slots; rows; tables; per-run rich text; live page numbers.
using JetsonPDF;
using JetsonPDF.Fluent;
var doc = FluentDocument.Create(d =>
{
d.Page(p =>
{
p.Size(PageSize.Letter);
p.Margin(50);
p.DefaultTextStyle(s => s.FontSize = 10);
// Header — repeats on every page.
p.Header().Row(row =>
{
row.RelativeItem().Text("INVOICE", s =>
{
s.FontSize = 24;
s.FontStyle = FontStyle.Bold;
s.FontColor = Colors.IndigoDark;
});
row.ConstantItem(120).AlignRight().Text(t =>
{
t.AlignRight();
t.Span("Page ");
t.CurrentPageNumber().Bold();
t.Span(" / ");
t.TotalPages().Bold();
});
});
// Footer — repeats on every page.
p.Footer().AlignCenter().Text("https://jetsonpdf.com");
// Content — auto-paginates on overflow.
p.Content().PaddingVertical(12).Column(col =>
{
col.Spacing(14);
col.Item().Text("Bill to: Acme Corporation",
s => s.FontStyle = FontStyle.Bold);
col.Item().Table(t =>
{
t.ColumnsDefinition(c =>
{
c.RelativeColumn(3); // Description
c.ConstantColumn(50); // Qty
c.ConstantColumn(70); // Price
c.ConstantColumn(80); // Amount
});
// Header row — repeats per page.
t.Header(h =>
{
h.Cell().Padding(6).Background(Colors.IndigoDark)
.Text("Description", s => s.FontColor = Colors.White);
h.Cell().Padding(6).Background(Colors.IndigoDark).AlignRight()
.Text("Qty", s => s.FontColor = Colors.White);
h.Cell().Padding(6).Background(Colors.IndigoDark).AlignRight()
.Text("Price", s => s.FontColor = Colors.White);
h.Cell().Padding(6).Background(Colors.IndigoDark).AlignRight()
.Text("Amount", s => s.FontColor = Colors.White);
});
foreach (var (name, qty, price) in lineItems)
{
t.Cell().Padding(6).Text(name);
t.Cell().Padding(6).AlignRight().Text(qty.ToString());
t.Cell().Padding(6).AlignRight().Text($"${price:F2}");
t.Cell().Padding(6).AlignRight().Text($"${qty * price:F2}");
}
// Footer row — only on the last page; spans columns.
t.Footer(f =>
{
f.Cell(columnSpan: 3).Padding(6).AlignRight()
.Text("Total", s => s.FontStyle = FontStyle.Bold);
f.Cell().Padding(6).Background(Colors.IndigoDark).AlignRight()
.Text($"${total:F2}", s =>
{ s.FontStyle = FontStyle.Bold; s.FontColor = Colors.White; });
});
});
});
});
}).WithMetadata(title: "Invoice #INV-2026-001", author: "Acme");
doc.GeneratePdf("invoice.pdf");
Any leaf slot becomes an AcroForm widget. .Section(anchor) registers a named destination; .SectionLink(anchor) links to it.
using JetsonPDF;
using JetsonPDF.Fluent;
var doc = FluentDocument.Create(d => d.Page(p =>
{
p.Size(PageSize.Letter);
p.Margin(50);
p.Content().Column(col =>
{
col.Spacing(12);
// A named destination.
col.Item().Section("top").Text("Customer acknowledgement",
s => s.FontStyle = FontStyle.Bold);
// Text field — Acrobat draws the chrome at the arranged bounds.
col.Item().Row(row =>
{
row.ConstantItem(100).AlignMiddle().Text("Signed name:");
row.RelativeItem().Height(18).AsTextField("ack.name");
});
// Check box.
col.Item().Row(row =>
{
row.ConstantItem(15).Height(15).AsCheckBox("ack.agreed");
row.RelativeItem().PaddingLeft(6).AlignMiddle()
.Text("I agree to the payment terms above.");
});
// Combo box with options.
col.Item().Row(row =>
{
row.ConstantItem(100).AlignMiddle().Text("Region:");
row.ConstantItem(160).Height(18).AsComboBox("ack.region",
new[] { "North America", "EMEA", "APAC", "LATAM" });
});
// Push button — combine .Link(uri) with .AsPushButton.
col.Item().Width(120).Height(22)
.Link("https://jetsonpdf.com/pay")
.AsPushButton("btnPayNow", "Pay now");
// Internal-link button to the named destination above.
col.Item().SectionLink("top").Width(60).Height(14)
.Background(Colors.Blue).AlignCenter().AlignMiddle()
.Text("Top", s => s.FontColor = Colors.White);
});
}));
doc.GeneratePdf("acknowledgement.pdf");
11Word-style report with Flow
JetsonPDF.Flow is a Word-like retained-mode DOM. Build a tree of Section/Paragraph/Table, mutate it freely, then Save. Auto-paginates over the Fluent renderer.
using JetsonPDF;
using JetsonPDF.Flow;
var doc = new FlowDocument
{
Title = "Q3 Quarterly Report",
Author = "Acme Corp",
DefaultRunProperties = new RunProperties
{
FontFamily = "Helvetica",
FontSize = 11,
Color = Color.Black,
},
Hyphenator = JetsonPDF.Flow.Hyphenation.Hyphenator.EnglishDefault(),
};
// Named style with a basedOn graph.
doc.AddStyle("Callout", configure: s =>
{
s.RunProperties.Italic = true;
s.RunProperties.FontSize = 10;
s.ParagraphProperties.LeftIndent = 24;
s.ParagraphProperties.RightIndent = 24;
});
var section = doc.AddSection();
section.PageSize = PageSize.Letter;
section.PageMargins = new Margins(72);
// Footer with live page numbers.
section.Footer.Body.Add(new Paragraph(p =>
{
p.AddText("Page ");
p.AddPageNumber();
p.AddText(" of ");
p.AddPageCount();
}) { Alignment = TextAlignment.Center });
// Headings auto-bookmark — TableOfContents picks them up.
section.Body.Add(new Paragraph("Q3 Quarterly Report")
{ Style = ParagraphStyle.Heading1, Alignment = TextAlignment.Center });
section.Body.Add(new Paragraph("Contents") { Style = ParagraphStyle.Heading2 });
section.Body.Add(new TableOfContents());
section.Body.Add(new Paragraph("Introduction")
{ Style = ParagraphStyle.Heading1, PageBreakBefore = true });
// Rich inline runs: bold, italic, link, footnote, drop cap, justified.
section.Body.Add(new Paragraph(p =>
{
p.AddText("This report describes ");
p.AddText("quarterly performance").Bold();
p.AddText(" across our three primary business lines. ");
p.AddText("All comparisons reference the prior fiscal quarter");
p.AddFootnote("Constant-currency rates are locked at the start of FY25.");
p.AddText(". For methodology see ");
p.AddText("our public docs").Link("https://www.example.com/methodology");
p.AddText(".");
})
{
DropCap = new DropCap { LineSpan = 3 },
Alignment = TextAlignment.Justify,
});
// Numbered list — auto-counter, hanging indent.
section.Body.Add(new Paragraph("Total revenue grew 12% year over year.")
{ ListMarker = new ListMarker(ListKind.Number) });
section.Body.Add(new Paragraph("Hardware margin expanded by 180 bps.")
{ ListMarker = new ListMarker(ListKind.Number) });
// Named-style callout.
section.Body.Add(new Paragraph("Forward-looking statements may change.")
{ StyleName = "Callout" });
// Table with repeating header.
var table = new Table
{
Columns = { 2.0, 1.0, 1.0 },
RepeatHeader = true,
CellBorderWidth = 0.5,
CellPadding = 5,
};
var hdr = new TableRow();
hdr.Cells.Add(new TableCell(new Paragraph("Region")
{ Style = ParagraphStyle.Heading3 }));
hdr.Cells.Add(new TableCell(new Paragraph("Q3")
{ Style = ParagraphStyle.Heading3, Alignment = TextAlignment.Right }));
hdr.Cells.Add(new TableCell(new Paragraph("Δ")
{ Style = ParagraphStyle.Heading3, Alignment = TextAlignment.Right }));
table.Header.Add(hdr);
foreach (var (region, q3, delta) in new[]
{
("North America", "$53.6M", "+11.2%"),
("EMEA", "$34.0M", "+9.3%"),
("APAC", "$25.9M", "+14.1%"),
})
{
var row = new TableRow();
row.Cells.Add(new TableCell(region));
row.Cells.Add(new TableCell(new Paragraph(q3)
{ Alignment = TextAlignment.Right }));
row.Cells.Add(new TableCell(new Paragraph(delta)
{ Alignment = TextAlignment.Right }));
table.Body.Add(row);
}
section.Body.Add(table);
// Endnote list — auto-collected from the body.
section.Body.Add(new Paragraph("Notes")
{ Style = ParagraphStyle.Heading2, PageBreakBefore = true });
section.Body.Add(new EndnoteList());
doc.Save("FlowReport.pdf");
Download the rendered PDF for the full report (cover, footnotes, two-column section, track changes, anchored images, comments, segment table). See the Flow API reference for the complete tree.
12Author a PDF in XAML
Compose with the WPF layout engine you already know — Grid, StackPanel, Border, data-bound text. JetsonPDF runs Measure/Arrange and walks the resulting visual tree.
Add jetsonpdf:Form.FieldName="email" to a TextBox and that XAML control becomes a real AcroForm widget at the arranged bounds. See the WPF guide for the full authoring surface.
13Read a PDF and walk its content
Open a file, list its outlines, and dump the text of every page.
using JetsonPDF.Reading;
var doc = Reader.Load("input.pdf");
Console.WriteLine($"Title: {doc.Info.GetValueOrDefault(\"Title\", \"(none)\")}");
Console.WriteLine($"Pages: {doc.Pages.Count}");
Console.WriteLine($"Encrypted: {doc.IsEncrypted}");
foreach (var item in doc.Outlines)
Console.WriteLine($" * {item.Title}");
for (int i = 0; i < doc.Pages.Count; i++)
{
Console.WriteLine($"--- page {i + 1} ---");
foreach (var content in doc.Pages[i].Items.OfType<PageTextItem>())
Console.WriteLine(content.Text);
}
Need to read an encrypted file? Pass the password as the second argument to Load: Reader.Load("input.pdf", "open-me").
14Merge and extract pages
The JetsonPDF.Composition package assembles documents at the page level — PageExtractor pulls a subset of pages into a new PDF, and Merger concatenates whole PDFs into one. Both are a lossless COS object-graph copy: content, fonts, images, and annotations are carried over in their original encoded form, never re-rendered.
using JetsonPDF.Composition;
// --- Extract pages ------------------------------------------------------
// Page numbers are 1-based, and the output keeps them in the order given —
// so the same call also reorders and duplicates pages.
byte[] report = File.ReadAllBytes("report.pdf");
byte[] summary = PageExtractor.Extract(report, 1, 3, 5); // pick pages 1, 3, 5
byte[] chapter = PageExtractor.ExtractRange(report, 4, 9); // inclusive range 4..9
File.WriteAllBytes("summary.pdf", summary);
// File-to-file overload — move page 8 to the front, then keep 1–3.
PageExtractor.Extract("report.pdf", "reordered.pdf", 8, 1, 2, 3);
// Encrypted source? Pass the password; the output is not encrypted.
byte[] unlocked = PageExtractor.Extract(
File.ReadAllBytes("locked.pdf"), password: "secret", 1, 2);
// --- Merge documents ----------------------------------------------------
// Concatenates in order. Outlines, named destinations, and AcroForm fields
// merge across sources; cross-document name collisions are suffixed
// (e.g. "signature" + "signature_2") so each field stays independent.
byte[] combined = Merger.Merge(
File.ReadAllBytes("cover.pdf"),
File.ReadAllBytes("body.pdf"),
File.ReadAllBytes("appendix.pdf"));
File.WriteAllBytes("combined.pdf", combined);
// Stream and file-list overloads too — merge a whole folder in name order.
Merger.Merge(
Directory.EnumerateFiles("chapters", "*.pdf").OrderBy(p => p),
"book.pdf");
Both types are static and thread-safe, depend only on the Reader (not the Writer), and target net8.0 / netstandard2.0 / net462. Merge sources must be decrypted first — extract an encrypted file with its password, then merge the result. See the Composition guide for what carries over and what doesn't.
15Render any PDF as a WPF view
Convert ReadDocument straight into XAML and host it in 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");
// ConvertAsync's Task completes synchronously on WPF (no JS bridge), so blocking 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. See the WPF guide for more on the viewer.
16Rasterize a PDF to a multipage TIFF
The JetsonPDF.PdfToTiffConverter package renders every page through the WPF pipeline and encodes the frames with the managed JetsonPDF.Tiff writer — no GDI+. It must run on an STA thread.
using System.Threading;
using JetsonPDF.Tiff;
// PdfToTiffConverter drives the WPF render path, so the call must be made
// on an STA thread. A console app marks Main with [STAThread]; from any
// other context, marshal onto a dedicated STA worker thread:
var worker = new Thread(() =>
{
var options = new PdfToTiffOptions
{
Dpi = 200,
ColorMode = TiffColorMode.Grayscale,
Compression = TiffCompression.Deflate,
// Render a page subset; omit Pages for the whole document.
Pages = PdfToTiffOptions.ParsePageRange("1-3,5"),
};
// Optional progress sink — fires per stage so a UI can show a bar.
var progress = new Progress<TiffConversionProgress>(
p => Console.WriteLine($"{p.Description} ({p.Fraction:P0})"));
PdfToTiffConverter.ConvertToFile("input.pdf", "output.tif", options, progress);
});
worker.SetApartmentState(ApartmentState.STA);
worker.Start();
worker.Join();
Set MultipageStrategy = TiffMultipageStrategy.OneTiffPerPage to emit one TIFF file per page instead of a single multi-frame file. For browser-hosted rasterization, JetsonPDF.OpenSilver's PdfToTiffBrowserConverter does the same job entirely in WebAssembly — see the OpenSilver guide.
17Decode and re-encode TIFFs with the managed codec
The JetsonPDF.Tiff package is a standalone codec — netstandard2.0, no native dependencies, no GDI+, no System.Drawing. Use it directly when you have TIFF bytes that didn't come from a PDF.
using JetsonPDF.Tiff;
// --- Decode -------------------------------------------------------------
using var fs = File.OpenRead("scan.tiff");
TiffImage image = TiffImage.Decode(fs);
Console.WriteLine($"Frames: {image.Frames.Count}");
for (int i = 0; i < image.Frames.Count; i++)
{
TiffFrame f = image.Frames[i];
Console.WriteLine($" page {i + 1}: {f.Width}x{f.Height} " +
$"{f.BitsPerSample}-bit {f.Photometric} " +
$"({f.Compression})");
// Browser-friendly export: encodes to PNG (or returns the original
// JPEG bytes if the frame is a JPEG passthrough).
File.WriteAllBytes($"page-{i + 1}.png", f.EncodeImage());
}
// --- Re-encode ----------------------------------------------------------
// Build new frames from raw RGBA buffers and chain them into one multi-page
// TIFF. The writer never touches GDI+ — all formatting is managed C#.
var encoded = new[]
{
new TiffEncodeFrame(width: 850, height: 1100, rgba8888: pageABuffer),
new TiffEncodeFrame(width: 850, height: 1100, rgba8888: pageBBuffer),
};
byte[] tiff = TiffWriter.Encode(encoded, new TiffWriteOptions
{
Compression = TiffCompression.Deflate,
PixelFormat = TiffEncodePixelFormat.Rgb,
DpiX = 200, DpiY = 200,
RowsPerStrip = 64,
});
File.WriteAllBytes("output.tiff", tiff);
Set PixelFormat = TiffEncodePixelFormat.BlackAndWhite with a threshold via BlackAndWhiteThreshold for 1-bit fax-style output. TiffFrame.ToDataUri() returns a data:image/png;base64,... string — drop it straight into an <img> tag in any browser. See the Tiff API reference for the full encoder / decoder surface.
18View and produce TIFFs in the browser (OpenSilver)
Browsers can't render TIFFs natively. JetsonPDF.OpenSilver.TiffViewer decodes them in WebAssembly via the managed codec and stacks each frame as a base64-PNG <Image>. PdfToTiffBrowserConverter rasterises a PDF through the browser DOM and returns multi-page TIFF bytes — pair them for an end-to-end PDF → TIFF → view pipeline running entirely client-side.
<!-- Page.xaml -->
<StackPanel xmlns:jp="clr-namespace:JetsonPDF.OpenSilver;assembly=JetsonPDF.OpenSilver">
<!-- Live (parented) scratch host the converter needs to mount each page. -->
<Grid x:Name="RasterHost" Visibility="Collapsed"/>
<!-- Decoded TIFF view: stacks every page; binds to a byte[] DP. -->
<jp:TiffViewer x:Name="Viewer"
Source="{Binding TiffBytes}"
FitToWidth="True"/>
</StackPanel>
using JetsonPDF.OpenSilver;
using JetsonPDF.Tiff;
// 1. Pick up the PDF — uploaded file, server fetch, embedded resource, ...
byte[] pdfBytes = await ResourceLoader.LoadAsync("report.pdf");
// 2. Optional: surface stage-weighted progress to a label or progress bar.
var progress = new Progress<TiffConversionProgress>(p =>
StatusLabel.Content = $"{p.Description} ({p.Fraction:P0})");
// 3. Rasterise via html2canvas + the managed TiffWriter — no server hop,
// no native image library, no GDI+. RasterHost must already be in the
// live visual tree.
byte[] tiff = await PdfToTiffBrowserConverter.ConvertAsync(
pdfBytes,
host: RasterHost,
options: new TiffWriteOptions
{
Compression = TiffCompression.Deflate,
PixelFormat = TiffEncodePixelFormat.Rgb,
},
progress: progress);
// 4. Hand the bytes to TiffViewer — every frame decodes to a base64-PNG
// the browser can render natively.
Viewer.Source = tiff;
If you already have TIFF bytes (and don't need the PDF → TIFF rasterisation), assign them directly to TiffViewer.Source. To plug in a managed JPEG 2000 decoder (ImageSharp, SkiaSharp, etc.) so JPX-encoded PDF pages render with full fidelity, set OpenSilverImageDecoders.Jpx at app startup — see the OpenSilver guide.
19Render PDFML markup with data binding
PDFML is JetsonPDF's declarative XML for PDFs. Write the document as markup with WPF-style {Binding} expressions, then render it against a plain data object — no WPF, browser, or STA thread required.
using JetsonPDF.Pdfml;
string pdfml = """
<Document xmlns="https://schemas.jetsonpdf.com/pdfml/1.0"
Title="{Binding Number, StringFormat='Invoice {0}'}">
<Flow Margin="2cm">
<TextBlock FontSize="22pt" FontWeight="Bold" Text="{Binding Title}" />
<TextBlock Text="{Binding Customer.Name}" />
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid CellPadding="4pt">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Description}" />
<TextBlock Grid.Column="1" TextAlignment="Right" Text="{Binding Amount, StringFormat=C}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock TextAlignment="Right" FontWeight="Bold" Text="{Binding Total, StringFormat='Total: {0:C}'}" />
</Flow>
</Document>
""";
var data = new
{
Number = "INV-2026-001",
Title = "Invoice",
Customer = new { Name = "Acme Corporation" },
Lines = new[]
{
new { Description = "Design", Amount = 1200m },
new { Description = "Development", Amount = 4800m },
},
Total = 6000m,
};
byte[] pdf = new PdfmlRenderer().Render(pdfml, data);
File.WriteAllBytes("invoice.pdf", pdf);
To host PDFML live in a WPF or OpenSilver app — re-rendering as a bound model changes — drop a <jp:PdfmlView Markup="..." Model="..."/> onto the form. See the PDFML guide for the language, dialects, and the control.