JetsonPDF.OpenSilver 1.0.0

dotnet add package JetsonPDF.OpenSilver --version 1.0.0
                    
NuGet\Install-Package JetsonPDF.OpenSilver -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JetsonPDF.OpenSilver" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JetsonPDF.OpenSilver" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="JetsonPDF.OpenSilver" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add JetsonPDF.OpenSilver --version 1.0.0
                    
#r "nuget: JetsonPDF.OpenSilver, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package JetsonPDF.OpenSilver@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=JetsonPDF.OpenSilver&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=JetsonPDF.OpenSilver&version=1.0.0
                    
Install as a Cake Tool

JetsonPDF.OpenSilver

OpenSilver integration for JetsonPDF. Authors the same xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025" dialect as the WPF integration, but the layout pass runs on the OpenSilver runtime so the same XAML can compile to a PDF from inside a WebAssembly app, an Edge WebView2 simulator, or a Playwright-driven Chromium CLI.

using JetsonPDF.OpenSilver.Authoring;

string xaml = """
<jetsonpdf:Document xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
                    Title="OpenSilver demo">
  <jetsonpdf:Document.Pages>
    <jetsonpdf:Page Width="612" Height="792">
      <Canvas>
        <Rectangle Canvas.Left="72" Canvas.Top="72" Width="144" Height="72"
                   Fill="#1E88E5"/>
        <TextBlock Canvas.Left="72" Canvas.Top="160" Text="Hello from OpenSilver"
                   FontFamily="Helvetica" FontSize="18"/>
      </Canvas>
    </jetsonpdf:Page>
  </jetsonpdf:Document.Pages>
</jetsonpdf:Document>
""";

byte[] pdf = await XamlToPdfConverter.ConvertAsync(xaml);
// or: await XamlToPdfConverter.ConvertAsync(xaml, outputStream, options);

The result is identical (snapshot-level) to what JetsonPDF.Wpf produces from the same XAML — both walkers feed the same runtime-neutral DocumentSnapshot into JetsonPDF.XamlToPdfConverter.Core.ConverterCore.

Public API

Member Purpose
JetsonPDF.OpenSilver.Authoring.XamlToPdfConverter.ConvertAsync(string xaml, XamlToPdfCoreOptions? options = null) → Task<byte[]> Parse, layout, walk, emit PDF bytes.
JetsonPDF.OpenSilver.Authoring.XamlToPdfConverter.ConvertAsync(string xaml, Stream output, XamlToPdfCoreOptions? options = null) → Task Same, but writes to a stream.
JetsonPDF.OpenSilver.Authoring.OpenSilverTreeWalker Walker (implements IXamlTreeWalker). Set HostContainer to a hosted Panel for accurate layout in a Wasm host (see Hosting).
JetsonPDF.OpenSilver.Base64ImageExtension {jetsonpdf:Base64Image Data='…'} markup extension that wraps the payload in a data: URI so OpenSilver's browser-backed BitmapImage can decode it natively.
JetsonPDF.OpenSilver.IJpegPixelDecoder / IJpxPixelDecoder Optional hooks for decoding JPEG / JPEG 2000 bytes to RGB pixels. Register one when you need JPEG+SMask alpha compositing (see JPEG + SMask alpha).
JetsonPDF.OpenSilver.OpenSilverImageDecoders Static slots (.Jpeg, .Jpx) where consumers plug in the decoders above.
JetsonPDF.OpenSilver.PdfToTiffBrowserConverter.ConvertAsync(byte[] pdfBytes, Panel host, TiffWriteOptions? options = null, IProgress<TiffConversionProgress>? progress = null) → Task<byte[]> Rasterise every page of a PDF to a multipage TIFF, entirely in the browser (see Browser-side PDF → TIFF).

The XAML namespace is registered as xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025" and resolves both JetsonPDF.OpenSilver.Authoring (containers/document/page types from the shared authoring projitems) and JetsonPDF.OpenSilver (the OpenSilver-side Base64ImageExtension).

Supported XAML surface

The OpenSilver walker handles the full authoring dialect that the WPF integration does (with two caveats noted below). All emission goes through runtime-neutral snapshots, so anything the core emitter knows how to draw is reachable from OpenSilver:

  • Document structure<jetsonpdf:Document> root with multiple <jetsonpdf:Page> children, per-page width/height/landscape, document metadata (Title/Author), named destinations, page labels.
  • ShapesRectangle (with corner radii), Ellipse, Line, Border, Path (LineGeometry / RectangleGeometry / EllipseGeometry / PathGeometry with LineSegment, BezierSegment, QuadraticBezier, PolyLineSegment, PolyBezierSegment, PolyQuadraticBezierSegment, ArcSegment), Image.
  • TextTextBlock with FontFamily/FontSize/FontStyle/FontWeight/Foreground, TextWrapping, TextAlignment, 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 prepass that records every element's PDF-space coordinates before the walk.
  • Form widgetsTextBox, CheckBox, ComboBox, ListBox, Button become AcroForm fields when tagged with jetsonpdf:Form.FieldName="...". Children of form-tagged controls are skipped so the viewer's default chrome doesn't double-paint.
  • Page-context markup extensions{jetsonpdf:PageNumber} / {jetsonpdf:PageCount} resolve against JetsonPageContext, which the walker stamps onto each page's DataContext before Measure. Run.Text (not a DP in OpenSilver) uses a sentinel-substitution pass before Measure so layout reflects the final string.
  • UIElement.Effect (DropShadow / Blur / custom shader) — the subtree is rasterised via WriteableBitmap.Render and embedded as a PNG image (descendants are skipped so the baked pixels aren't double-painted).
  • Base64 images{jetsonpdf:Base64Image Data='…'} produces a BitmapImage from a data: URI (MIME sniffed from the first base64 chars: PNG / JPEG / GIF).

The two practical differences from the WPF integration:

  1. Async. ConvertAsync is asynchronous because OpenSilver's BitmapImage loading goes through data: / HttpClient and resolves asynchronously. The WPF integration is synchronous because PNG encoding happens in-process on raw pixel buffers.
  2. WriteableBitmap.Render needs a live DOM. Image rasterisation (and the effects-rasterisation path) relies on WriteableBitmap.Render, which in the Wasm runtime needs the element parented to a live host Canvas. In the Simulator or in unit tests that don't supply a host, rasterisation soft-fails and the image is omitted from the snapshot — layout still completes.

Hosting

OpenSilver's layout is partly DOM-driven; calling Measure/Arrange on a detached element resolves to zero metrics in the Wasm runtime. Production hosts parent the parsed authoring tree to a hidden host Canvas for the duration of the walk:

// In your OpenSilver page's Loaded handler:
OpenSilverTreeWalker.HostContainer = HostCanvas;

After HostContainer is set, the walker attaches each parsed root, runs Measure/Arrange/UpdateLayout, walks the visual tree, and detaches before returning. If HostContainer is left null (Simulator / detached test), the walker falls back to detached layout — some metrics may resolve to zero, but the API still works.

The companion host scaffold lives at ../JetsonPDF.OpenSilver.Sample.Host/: a UserControl-based shell with .Browser (WebAssembly) and .Simulator (WebView2) flavours, plus a [JSInvokable] JS bridge that exposes window.__jetsonpdfConvert(xaml) to Playwright-style callers.

For a runnable example that drives the converter through 15 authoring-XAML demos, see samples/JetsonPDF.OpenSilver.Showcase/.

JPEG + SMask alpha

PDFs can pair a JPEG image with a soft-mask (/SMask) alpha channel. The browser's <img> element decodes the JPEG natively but ignores the external alpha, so the alpha has to be merged into the pixels before the image is handed to the renderer. The WPF adapter does this via JpegBitmapDecoder; OpenSilver targets netstandard2.0 and has no managed JPEG decoder in the BCL — even in .NET 8/9 there is none — so we bridge to the browser's own JPEG decoder via createImageBitmap and an offscreen canvas.

OpenSilverImageDecoders.Jpeg defaults to DefaultBrowserJpegDecoder.Instance, so JPEG+SMask alpha composites correctly out of the box inside any modern browser host (WebAssembly, Edge WebView2, Playwright Chromium). In non-browser hosts (the OpenSilver Simulator before the JS bridge is up, unit tests, server-side conversion) the default decoder catches the missing-Interop exception, emits a one-time stderr warning, and returns null — so the previous alpha-drop pass-through kicks in. No exceptions, no behavior regression.

To swap in a managed decoder (better throughput when a document has many large JPEG+SMask images — the default round-trips ~33 MB of base64 RGB through the JS bridge for a 4K image):

using JetsonPDF.OpenSilver;

OpenSilverImageDecoders.Jpeg = new MyJpegDecoder();
OpenSilverImageDecoders.Jpx  = new MyJpxDecoder();   // JPEG 2000, same pattern; no default

internal sealed class MyJpegDecoder : IJpegPixelDecoder
{
    public Task<JpegPixelResult?> TryDecodeRgb24Async(byte[] jpegBytes, CancellationToken ct = default)
    {
        // Wrap your decoder of choice (SkiaSharp, ImageSharp, libjpeg port, …).
        // Output: width*height*3 bytes of R,G,B,R,G,B,…. Return null on failure.
        return Task.FromResult<JpegPixelResult?>(new JpegPixelResult(rgb, width, height));
    }
}

To disable JPEG+SMask compositing entirely (and accept the alpha-drop pass-through):

OpenSilverImageDecoders.Jpeg = null;

JPX (JPEG 2000) has no default decoder because browsers don't decode JP2 portably (Safari only). Register your own if you need it.

Async API note

Because the default decoder bridges through a JS Promise, IJpegPixelDecoder.TryDecodeRgb24Async is async and PdfToXamlConverter.Convert is now PdfToXamlConverter.ConvertAsync(...) → Task<string>. The WPF flavour returns a synchronously-completed Task (no JS bridge involved), so blocking on the result via .GetAwaiter().GetResult() is safe in WPF; in OpenSilver, await it.

Browser-side PDF → TIFF

PdfToTiffBrowserConverter rasterises every page of a PDF to a multipage TIFF without a server round-trip or a native image library — the whole pipeline runs in WebAssembly. It reads the PDF with JetsonPDF.Reader, turns each page into XAML via PdfToXamlConverter, mounts that into a hidden host Panel, snapshots the resulting DOM with html2canvas (fetched from a CDN on first use), reads the RGBA pixels back through getImageData, and encodes the frames through JetsonPDF.Tiff's managed TiffWriter.

using JetsonPDF.OpenSilver;
using JetsonPDF.Tiff;

// HostCanvas is an empty Panel already parented in the live visual tree.
byte[] tiff = await PdfToTiffBrowserConverter.ConvertAsync(
    pdfBytes,
    HostCanvas,
    new TiffWriteOptions { Compression = TiffCompression.Deflate },
    progress: new Progress<TiffConversionProgress>(p => StatusText.Text = p.Description));
  • The host panel must already be in Application.Current's visual tree. OpenSilver's layout is DOM-driven; a detached panel resolves to zero metrics and the converter would emit blank pages. The converter sizes the panel per page, mounts the parsed XAML, snapshots, and clears it (restoring the panel's prior state even if an exception unwinds).
  • Raster images are composited onto the captured canvas directly, because html2canvas can't reliably snapshot OpenSilver's asynchronously-loaded <Image> elements.
  • Progress — the optional IProgress<TiffConversionProgress> sink reports each TiffConversionStage (StartingConvertingXaml / Rendering per page → Encoding per frame → Completed), suitable for driving a ProgressBar.

Browsers can't natively display TIFF, so pair this with JetsonPDF.Tiff's TiffImage.Decode + ToDataUri() to show the result in an <Image>. See samples/JetsonPDF.OpenSilver.Showcase/'s TiffViewerPage for a round-trip. For the Windows desktop equivalent, use the separate JetsonPDF.PdfToTiffConverter package.

Architecture

  • Imports the shared JetsonPDF.XamlToPdfConverter.Authoring.projitems — same authoring types (Document, Page, Form, annotation classes, PageContextSentinel, …) as the WPF integration; under OPENSILVER, those types compile into the JetsonPDF.OpenSilver.Authoring namespace.
  • Walker (OpenSilverTreeWalker) is OpenSilver-specific (it uses VisualTreeHelper.GetChildrenCount against DependencyObject because Silverlight has no System.Windows.Media.Visual, and it has to manage host parenting).
  • Snapshot types and PDF emission live in JetsonPDF.XamlToPdfConverter.Core — runtime-neutral.

Targets

  • netstandard2.0
  • OpenSilver 3.2+
  • Defines OPENSILVER for code that conditionally specialises.

NuGet

dotnet add package JetsonPDF.OpenSilver

License

MIT.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 92 5/23/2026
0.2.0-preview 93 5/23/2026
0.1.0-preview 88 5/17/2026