JetsonPDF.OpenSilver
1.1.0
dotnet add package JetsonPDF.OpenSilver --version 1.1.0
NuGet\Install-Package JetsonPDF.OpenSilver -Version 1.1.0
<PackageReference Include="JetsonPDF.OpenSilver" Version="1.1.0" />
<PackageVersion Include="JetsonPDF.OpenSilver" Version="1.1.0" />
<PackageReference Include="JetsonPDF.OpenSilver" />
paket add JetsonPDF.OpenSilver --version 1.1.0
#r "nuget: JetsonPDF.OpenSilver, 1.1.0"
#:package JetsonPDF.OpenSilver@1.1.0
#addin nuget:?package=JetsonPDF.OpenSilver&version=1.1.0
#tool nuget:?package=JetsonPDF.OpenSilver&version=1.1.0
JetsonPDF.OpenSilver
OpenSilver integration for JetsonPDF.
Authors the same xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
dialect as JetsonPDF.Wpf, 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. A second component,
PdfToTiffBrowserConverter, rasterises PDFs to multipage TIFFs entirely in
the browser — no server round-trip, no native image library.
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 stream-based:
// await XamlToPdfConverter.ConvertAsync(xaml, outputStream, options);
The result is snapshot-equivalent to what JetsonPDF.Wpf produces from the
same XAML — both walkers feed the same runtime-neutral DocumentSnapshot into
JetsonPDF.XamlToPdfConverter.Core.ConverterCore.
Contents
- Overview
- Public API
- Hosting
- Supported XAML surface
- Differences from JetsonPDF.Wpf
- JPEG + SMask alpha
- Browser-side PDF to TIFF
- Architecture
- Hosting projects
- Limitations
- Targets
- License
Overview
JetsonPDF.OpenSilver is the second runtime adapter on top of the shared
authoring pipeline. The first adapter, JetsonPDF.Wpf.Authoring, drives PDF
generation from a XAML tree through a live WPF layout pass — useful on
Windows desktop, tied to STA threads and net8.0-windows.
JetsonPDF.OpenSilver covers the browser / web / cross-platform scenario:
pure netstandard2.0, OpenSilver 3.2+, no WPF, no STA constraint.
Both adapters consume the same authoring XAML, share the same set of
authoring types (via the shared JetsonPDF.XamlToPdfConverter.Authoring
projitems), and produce the same DocumentSnapshot shape — so a PDF
generated by the WPF adapter and one generated by the OpenSilver adapter
from the same input XAML differ only in the runtime that walked the visual
tree. PDF emission lives in JetsonPDF.XamlToPdfConverter.Core and is
runtime-neutral.
The OpenSilver pipeline is asynchronous (Task<byte[]>-returning) because
OpenSilver's BitmapImage loading goes through data: URIs and
HttpClient resolved on the browser event loop; the WPF pipeline is
synchronous because PNG encoding runs in-process on raw pixel buffers.
Public API
| Member | Purpose |
|---|---|
JetsonPDF.OpenSilver.Authoring.XamlToPdfConverter.ConvertAsync(string xaml, XamlToPdfCoreOptions? options = null) -> Task<byte[]> |
Parse the XAML, run Measure/Arrange via the OpenSilver runtime, walk the visual tree into a snapshot, and feed it to ConverterCore.Convert. Returns PDF bytes. |
JetsonPDF.OpenSilver.Authoring.XamlToPdfConverter.ConvertAsync(string xaml, Stream output, XamlToPdfCoreOptions? options = null) -> Task |
Same pipeline; writes the bytes to a stream. Doesn't dispose the stream. |
JetsonPDF.OpenSilver.Authoring.OpenSilverTreeWalker |
Implements IXamlTreeWalker. Exposes the static HostContainer slot used to parent the parsed tree to a live Panel for accurate Measure/Arrange in a WASM host (see Hosting). |
JetsonPDF.XamlAuthoring.IXamlTreeWalker |
Shared adapter contract — DocumentSnapshot WalkToSnapshots(string xaml, XamlToPdfCoreOptions options). WPF has VisualTreeWalker; OpenSilver has OpenSilverTreeWalker. |
JetsonPDF.OpenSilver.Base64ImageExtension |
{jetsonpdf:Base64Image Data='…'} markup extension. 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 raw RGB pixels. Plug one in when you need JPEG+SMask alpha compositing (see JPEG + SMask alpha). |
JetsonPDF.OpenSilver.JpegPixelResult |
Decoder return shape: byte[] Rgb (R,G,B,…), int Width, int Height. |
JetsonPDF.OpenSilver.OpenSilverImageDecoders |
Process-wide registration: Jpeg (defaults to DefaultBrowserJpegDecoder.Instance) and Jpx (no default). |
JetsonPDF.OpenSilver.DefaultBrowserJpegDecoder.Instance |
Built-in JPEG decoder that bridges to the browser's createImageBitmap via the OpenSilver Interop layer. Falls back silently in non-browser hosts. |
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 WebAssembly (see Browser-side PDF to TIFF). |
JetsonPDF.OpenSilver.TiffViewer / TiffViewerSource |
Live UserControl + markup extension for displaying a TIFF byte array (or any frame) inside an OpenSilver page. |
JetsonPDF.OpenSilver.WidgetActions |
Attached behaviour that wires WidgetAction clicks to default browser behaviour (window.open, window.print, reset-form). |
The XAML namespace is registered as
xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025" and resolves
both:
JetsonPDF.OpenSilver.Authoring— containers,Document,Page,Form.*attached properties, annotation classes,PaginatedTable,JetsonPageContext,PageNumberExtension,PageCountExtension(shipped via the shared authoring projitems).JetsonPDF.OpenSilver— the runtime-side hooks:Base64ImageExtension, the image-decoder slots, the TIFF viewer.
Hosting
OpenSilver's layout is partly DOM-driven; calling Measure / Arrange on a
detached element resolves to zero metrics inside the WASM runtime. Production
hosts parent the parsed authoring tree to a hidden host Canvas for the
duration of the walk:
<Grid>
<Canvas x:Name="HostCanvas"
IsHitTestVisible="False"
Opacity="0"
Width="0" Height="0"/>
</Grid>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += (_, _) =>
{
// Wire the walker to a hidden Canvas in the live visual tree.
OpenSilverTreeWalker.HostContainer = HostCanvas;
};
}
private async Task<byte[]> BuildPdfAsync(string xaml)
{
return await XamlToPdfConverter.ConvertAsync(xaml);
}
}
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 without a
bridge, detached unit tests), the walker falls back to detached layout —
some metrics may resolve to zero, but the API still completes.
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.
Supported XAML surface
The OpenSilver walker handles the full authoring dialect that the WPF integration does — with two caveats noted under Differences from JetsonPDF.Wpf. All emission goes through runtime-neutral snapshots, so anything the core emitter knows how to draw is reachable from OpenSilver.
Document structure
<jetsonpdf:Document> is the multi-page root; <jetsonpdf:Page> carries
per-page width/height/landscape; document-level fields cover metadata,
named destinations, outline (bookmarks), page labels, OCG layers, and a
declarative conformance flag.
<jetsonpdf:Document xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025"
Title="Quarterly report"
Author="Contoso, Inc.">
<jetsonpdf:Document.NamedDestinations>
<jetsonpdf:NamedDestination Name="intro" PageIndex="0"/>
<jetsonpdf:NamedDestination Name="ch1" PageIndex="2"/>
</jetsonpdf:Document.NamedDestinations>
<jetsonpdf:Document.Outline>
<jetsonpdf:OutlineItem Title="Introduction" NamedDestination="intro" Bold="True"/>
<jetsonpdf:OutlineItem Title="Chapter 1" NamedDestination="ch1"/>
</jetsonpdf:Document.Outline>
<jetsonpdf:Document.PageLabels>
<jetsonpdf:PageLabelRange StartPage="0" Style="LowerRoman"/>
<jetsonpdf:PageLabelRange StartPage="2" Style="DecimalArabic"/>
</jetsonpdf:Document.PageLabels>
<jetsonpdf:Document.Pages>
<jetsonpdf:Page Width="612" Height="792">
</jetsonpdf:Page>
<jetsonpdf:Page Landscape="True">
</jetsonpdf:Page>
</jetsonpdf:Document.Pages>
</jetsonpdf:Document>
A bare-root flavour (Canvas / Grid as the document root) is also supported
for single-page documents; the walker pulls the page size from
XamlToPdfCoreOptions.DefaultPageSize (Letter by default).
Shapes
Pure-vector primitives: Rectangle (with RadiusX / RadiusY corners),
Ellipse, Line, Border, Image, and Path with the full geometry
zoo — LineGeometry, RectangleGeometry, EllipseGeometry, and
PathGeometry with LineSegment, BezierSegment, QuadraticBezierSegment,
PolyLineSegment, PolyBezierSegment, PolyQuadraticBezierSegment, and
ArcSegment.
<Canvas>
<Rectangle Canvas.Left="40" Canvas.Top="40"
Width="200" Height="80"
RadiusX="8" RadiusY="8"
Fill="#FF1E88E5" Stroke="#FF0D47A1" StrokeThickness="2"/>
<Ellipse Canvas.Left="280" Canvas.Top="40"
Width="80" Height="80"
Fill="#FFFFD600"/>
<Path Canvas.Left="40" Canvas.Top="160"
Stroke="Black" StrokeThickness="1.5" Fill="#22FF0000">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0" IsClosed="True">
<LineSegment Point="120,0"/>
<BezierSegment Point1="180,40" Point2="180,80" Point3="120,120"/>
<LineSegment Point="0,120"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
Brushes — SolidColorBrush, LinearGradientBrush (axial), and
RadialGradientBrush — all emit through the same shading-aware path as
WPF; pattern brushes are not supported in v1.
Text
TextBlock with FontFamily / FontSize / FontStyle / FontWeight /
Foreground, TextWrapping, TextAlignment, and mixed-style <Run>
inlines.
<TextBlock Canvas.Left="40" Canvas.Top="40"
Width="400"
FontFamily="Helvetica" FontSize="14"
TextWrapping="Wrap" TextAlignment="Justify">
This is a paragraph with
<Run FontStyle="Italic">mixed</Run>
<Run FontWeight="Bold">styles</Run> per run, wrapped at word
boundaries by OpenSilver's layout pass.
</TextBlock>
Glyph metrics come from the same AFM tables the WPF walker uses, so baseline / ascent / descent are identical across the two adapters. Text positions reflect the OpenSilver-computed line layout — the runtime is the source of truth.
Annotations
Ten annotation subtypes, each as a first-class authoring element:
Link— URI or named-destination jump, with the click rect derived from the element's arranged bounds (or from an explicitTargetreference to another element in the tree).TextMarkup(Highlight,Underline,StrikeOut,Squiggly) — whenTargetbinds to aTextBlockplus optionalStartIndex/Length, quads are derived fromFormattedText.BuildHighlightGeometryso the highlight tracks the actual rendered glyphs.FreeText— inline text annotation with its own font + colour.Stamp— named or custom stamp.Square,Circle,Line,Polygon,PolyLine,Ink— geometric annotations with stroke / fill / opacity.
<Canvas>
<TextBlock x:Name="HelloText"
Canvas.Left="40" Canvas.Top="40"
FontFamily="Helvetica" FontSize="16"
Text="Click here for more information."/>
<jetsonpdf:Link Target="{Binding ElementName=HelloText}"
Uri="https://example.com"/>
<jetsonpdf:TextMarkup Subtype="Highlight"
Target="{Binding ElementName=HelloText}"
StartIndex="6" Length="4"
Color="#80FFD600"/>
</Canvas>
Cross-page Target references resolve through a prepass that records every
element's PDF-space coordinates before the snapshot walk — a Link on page 1
can target a TextBlock on page 4 and the click rect lands in the right
place.
Form widgets
TextBox, CheckBox, ComboBox, ListBox, and Button become AcroForm
fields when tagged with jetsonpdf:Form.FieldName="…". The walker drops
the control's chrome children once it's marked — the viewer (Acrobat,
Foxit, the OpenSilver-rendered preview) paints the widget itself.
<Canvas>
<TextBlock Canvas.Left="40" Canvas.Top="40" Text="Name"/>
<TextBox Canvas.Left="40" Canvas.Top="60"
Width="240" Height="22"
jetsonpdf:Form.FieldName="name"
jetsonpdf:Form.MaxLength="50"/>
<CheckBox Canvas.Left="40" Canvas.Top="100"
Width="16" Height="16"
jetsonpdf:Form.FieldName="agree"
IsChecked="True"/>
<ComboBox Canvas.Left="40" Canvas.Top="130"
Width="160" Height="22"
jetsonpdf:Form.FieldName="region">
<ComboBoxItem Content="NA"/>
<ComboBoxItem Content="EMEA"/>
<ComboBoxItem Content="APAC"/>
</ComboBox>
<Button Canvas.Left="40" Canvas.Top="170"
Width="120" Height="24"
Content="Submit"
jetsonpdf:Form.FieldName="submit"
jetsonpdf:Form.Action="Print"/>
</Canvas>
Form.IsMultiline, Form.IsPassword, and Form.Action (URI / Print /
Reset) tune the emitted widget.
Page-context markup extensions
{jetsonpdf:PageNumber} and {jetsonpdf:PageCount} resolve against
JetsonPageContext, which the walker stamps onto each page's DataContext
before Measure. The bindings are OneWay so they re-evaluate per page
during multi-page rendering (relevant when PaginatedTable expands a
single source page into multiple PDF pages).
<jetsonpdf:Page Width="612" Height="792">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" Content="{Binding Body}"/>
<TextBlock Grid.Row="1"
HorizontalAlignment="Center"
FontFamily="Helvetica" FontSize="10">
<Run Text="Page "/>
<Run Text="{jetsonpdf:PageNumber}"/>
<Run Text=" of "/>
<Run Text="{jetsonpdf:PageCount}"/>
</TextBlock>
</Grid>
</jetsonpdf:Page>
Run.Text is not a DependencyProperty under OpenSilver, so the walker
runs a sentinel-substitution pre-pass before Measure that swaps in the
final string. Layout therefore reflects the actual page-number digits
(no worst-case width reservation).
UIElement.Effect rasterisation
UIElement.Effect (DropShadow, Blur, custom ShaderEffect) is rasterised
via WriteableBitmap.Render and embedded as a PNG image. The descendants
of the effected subtree are skipped after rasterisation so the baked
pixels aren't double-painted.
<Border Canvas.Left="40" Canvas.Top="40"
Width="240" Height="120"
Background="White" CornerRadius="8">
<Border.Effect>
<DropShadowEffect BlurRadius="8" ShadowDepth="4"
Color="#80000000"/>
</Border.Effect>
<TextBlock Margin="20" Text="Soft-shadowed card"/>
</Border>
WriteableBitmap.Render requires a live DOM under WASM — see
Differences from JetsonPDF.Wpf. In
detached / Simulator hosts without HostContainer wired, the
rasterisation soft-fails and the image is omitted from the snapshot
(layout still completes).
Base64 images
{jetsonpdf:Base64Image Data='…'} is the canonical way to embed an image
without going through OpenSilver's resource loader. The extension wraps
the payload in a data: URI with the MIME type sniffed from the first
base64 characters (PNG iVBORw0KGgo, JPEG /9j/, GIF R0lGOD); the
browser-backed BitmapImage decodes the URI natively.
<Image Canvas.Left="40" Canvas.Top="40"
Width="200" Height="120"
Source="{jetsonpdf:Base64Image Data='iVBORw0KGgoAAAANSUhEUg…'}"/>
Same XAML surface as the WPF Base64ImageExtension, so an authoring
document moves between adapters unchanged.
Differences from JetsonPDF.Wpf
The two adapters consume identical XAML and produce snapshot-equivalent PDFs, but the runtime constraints differ in two practical ways:
Async API.
XamlToPdfConverter.ConvertAsyncreturnsTask<byte[]>because OpenSilver image loading is asynchronous —BitmapImage.UriSourceresolves throughdata:URIs orHttpClienton the browser event loop, and the JPEG-bridge decoder bridges through a JSPromise. The WPF adapter'sConvertis synchronous becausePngBitmapEncoderworks on raw bitmap pixels in-process.WriteableBitmap.Renderneeds a live DOM. Effect rasterisation (DropShadow / Blur / ShaderEffect) and effect-driven Image baking go throughWriteableBitmap.Render, which under WASM needs the element parented to a live host Canvas. SetOpenSilverTreeWalker.HostContainerto a Canvas already inApplication.Current's visual tree. 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.
The shared authoring projitems guarantee that authoring types (Document,
Page, Form.*, the annotation classes, PageContextSentinel, …) are
literally the same code on both sides, just compiled into different
namespaces.
JPEG + SMask alpha
A PDF 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 .NET 8/9 doesn't ship one), so we
bridge to the browser's own JPEG decoder via createImageBitmap plus 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
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 — the previous
alpha-drop pass-through kicks in. No exceptions, no behaviour 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
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 — the pipeline will fall back to alpha-drop pass-through.
var (rgb, width, height) = DecodeJpeg(jpegBytes);
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 to 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:
JetsonPDF.Reader.Reader.Loadparses the PDF.- For each page,
PdfToXamlConverter.ConvertPageAsyncturns it into OpenSilver XAML. - The parsed root is mounted into a hidden host
Panelalready in the live visual tree. - The host element's DOM is snapshotted via
html2canvas(fetched from a CDN on first use). - Raster images are composited onto the canvas directly through an
overlay path, because
html2canvascan't reliably snapshot OpenSilver's asynchronously-loaded<Image>elements. - RGBA pixels are read back through
CanvasRenderingContext2D.getImageData. - Frames are encoded through
JetsonPDF.Tiff.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));
// Display via JetsonPDF.Tiff:
var decoded = TiffImage.Decode(tiff);
PreviewImage.Source = decoded.Frames[0].ToDataUri();
Key constraints:
- The
hostpanel must already be inApplication.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. The panel's prior width/height/opacity/hit-test state is restored even if an exception unwinds out of the loop. - Raster images are composited onto the captured canvas directly,
because
html2canvascan't reliably snapshot OpenSilver's asynchronously-loaded<Image>elements. The XAML emitted byPdfToXamlConverterhas<Image>elements stripped out before rendering — the overlay path is the single source of raster images. - ListBox option clipping is reproduced by capping each option's
MaxHeightso the option stack fits inside the widget rect.html2canvasdoesn't honour the live ScrollViewer clip. - Progress. The optional
IProgress<TiffConversionProgress>sink reports eachTiffConversionStage—Starting→ConvertingXaml/Renderingper page →Encodingper frame →Completed. Suitable for driving aProgressBar.
Browsers can't natively display TIFF, so pair this with
JetsonPDF.Tiff's TiffImage.Decode + ToDataUri() to show the result
in an <Image>.
Architecture
+---------------------------------------------------+
| Your OpenSilver app (Browser or Simulator) |
| |
| XamlToPdfConverter.ConvertAsync(xaml) |
| | |
| v |
| +-------------------------+ |
| | OpenSilverTreeWalker | IXamlTreeWalker |
| | - XamlReader.Load | |
| | - HostContainer attach | |
| | - Measure/Arrange | |
| | - VisualTreeHelper | |
| +-------------------------+ |
| | |
| | DocumentSnapshot |
| v |
| +-------------------------+ |
| | ConverterCore.Convert | runtime-neutral |
| | + PdfEmitter | (also used by WPF) |
| +-------------------------+ |
| | |
| v |
| byte[] PDF |
+---------------------------------------------------+
- The shared
JetsonPDF.XamlToPdfConverter.Authoring.projitemsships the authoring types (Document,Page,Form, the annotation classes,PageContextSentinel,PaginatedTable, the markup extensions, …) into bothJetsonPDF.OpenSilver.AuthoringandJetsonPDF.Wpf.Authoring. UnderOPENSILVER(defined in this project's csproj), they compile against OpenSilver primitives; otherwise against WPF. OpenSilverTreeWalkeris OpenSilver-specific: it usesVisualTreeHelper.GetChildrenCountagainstDependencyObjectbecause Silverlight has noSystem.Windows.Media.Visual, and it has to manage the host-container parenting required by WASM layout.- Snapshot types (
DocumentSnapshot,PageSnapshot, every*Snapshot) and PDF emission live inJetsonPDF.XamlToPdfConverter.Core. Runtime-neutral.
Hosting projects
src/JetsonPDF.OpenSilver.Sample.Host/
ships a reference shell for embedding the converter in your own app:
| Project | Purpose |
|---|---|
JetsonPDF.OpenSilver.Sample.Host |
A UserControl-based host. Sets up OpenSilverTreeWalker.HostContainer, exposes a [JSInvokable] JS bridge window.__jetsonpdfConvert(xaml) that Playwright (or any JS driver) can call to drive the converter from outside the page, and wires the WidgetActions defaults. |
JetsonPDF.OpenSilver.Sample.Host.Browser |
The WebAssembly host — produces a static-file bundle that you can serve from any web server (no .NET runtime on the server). |
JetsonPDF.OpenSilver.Sample.Host.Simulator |
The WebView2 simulator — for stepping through the host in Visual Studio. |
Copy the structure into your own solution to bootstrap a converter front
end; or call the converter directly from your app's Loaded handler if
you don't need the JS bridge.
Limitations
HostContaineris required for accurate layout. OpenSilver's WASM runtime computes Measure/Arrange against the DOM; an unparented element resolves to zero metrics. SetOpenSilverTreeWalker.HostContainerto a hiddenCanvas(or anyPanel) insideApplication.Current's visual tree before callingConvertAsync. Without it the API still completes, but text positions and image sizes may be zero.WriteableBitmap.Renderrequires a live DOM. Effect-rasterised subtrees (DropShadow / Blur / ShaderEffect) and effect-driven image baking soft-fail in detached / Simulator hosts. The PDF is still produced; the effected pixels are omitted from the snapshot.JPEG 2000 (JPX) has no default decoder. Browsers don't decode JP2 portably (Safari only). Register an
IJpxPixelDecoderviaOpenSilverImageDecoders.Jpxif your input PDFs use JPX images with alpha; otherwise the alpha-drop pass-through applies.JPEG+SMask alpha through the default decoder is bandwidth-heavy. The browser bridge round-trips ~33 MB of base64 RGB for a 4K image. Register a managed decoder via
OpenSilverImageDecoders.Jpegfor documents with many large JPEG+SMask pairs.PdfToTiffBrowserConverterfidelity ceiling ishtml2canvas. Text and vector shapes inherit OpenSilver's DOM rendering quality; complex CSS effects, custom shaders, or anything outsidehtml2canvas's supported subset won't reproduce. Raster images are composited via the overlay path so they're pixel-accurate.One
PaginatedTableper source page (v1). Multi-page expansion of a single sourceXamlPageis supported, but a page with multiplePaginatedTableinstances throws. The same restriction applies to WPF.Pattern brushes are not supported. Solid / axial gradient / radial gradient brushes round-trip; tiling-pattern brushes are not in the authoring surface.
Targets
netstandard2.0- OpenSilver 3.2+
- Defines
OPENSILVERfor code that conditionally specialises.
NuGet
dotnet add package JetsonPDF.OpenSilver
License
MIT.
| Product | Versions 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. |
-
.NETStandard 2.0
- JetsonPDF.Common (>= 1.1.0)
- JetsonPDF.Reader (>= 1.1.0)
- JetsonPDF.Tiff (>= 1.1.0)
- JetsonPDF.Writer (>= 1.1.0)
- JetsonPDF.XamlToPdfConverter.Core (>= 1.1.0)
- Microsoft.Bcl.HashCode (>= 6.0.0)
- OpenSilver (>= 3.2.0)
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.1.0 | 131 | 6/6/2026 |
| 1.0.0 | 106 | 5/23/2026 |
| 0.2.0-preview | 108 | 5/23/2026 |
| 0.1.0-preview | 102 | 5/17/2026 |