Examples

Self-contained snippets you can paste into a fresh project. Each one runs against the current JetsonPDF API.

Reference PDFs. Every public namespace has a generated, paginated reference document: Writer · Reader · Fluent · Flow · Forms · Composition · Tiff · Wpf · OpenSilver.
Rendered output. Open these PDFs to see what the code below actually produces: FluentInvoice.pdf · FluentMeetingNotes.pdf · FluentShowcase.pdf · FlowReport.pdf.

1Hello world

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.

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");

Download the rendered PDF to see this in full (with stripes, callout box, and form widgets). See the Fluent API reference for every container method.

10Form widgets and internal nav with Fluent

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.

<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 in C# — 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);

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.