JetsonPDF.Writer
1.1.0
dotnet add package JetsonPDF.Writer --version 1.1.0
NuGet\Install-Package JetsonPDF.Writer -Version 1.1.0
<PackageReference Include="JetsonPDF.Writer" Version="1.1.0" />
<PackageVersion Include="JetsonPDF.Writer" Version="1.1.0" />
<PackageReference Include="JetsonPDF.Writer" />
paket add JetsonPDF.Writer --version 1.1.0
#r "nuget: JetsonPDF.Writer, 1.1.0"
#:package JetsonPDF.Writer@1.1.0
#addin nuget:?package=JetsonPDF.Writer&version=1.1.0
#tool nuget:?package=JetsonPDF.Writer&version=1.1.0
JetsonPDF.Writer
The low-level PDF authoring API. Builds PDF documents byte by byte against ISO 32000-2 (PDF 2.0). Headless, no native dependencies, with explicit control over fonts, colour spaces, content-stream operators, encryption, conformance, and signatures.
using JetsonPDF;
var doc = new Document
{
Title = "Hello",
Author = "Me",
};
var page = doc.AddPage(PageSize.Letter);
page.DrawText("Hello, world!", new Font(FontFamily.Helvetica, 24), x: 72, y: 720);
page.DrawRectangle(72, 600, 200, 80, stroke: Color.Black, fill: Color.RedLight);
doc.Save("hello.pdf");
If you'd rather think in containers, decorators, and retained DOM, look at JetsonPDF.Fluent (QuestPDF-style) or JetsonPDF.Flow (Word-like retained DOM). Both compose on top of this writer.
Contents
- Overview
- Quick start
- Core concepts
- Document setup
- Text and fonts
- Vector graphics
- Images
- Colour and colour spaces
- Patterns and shadings
- Form XObjects
- Annotations
- AcroForm widgets
- Encryption
- Document metadata
- Tagged PDF / structure tree
- Measurements and viewports
- Conformance
- Edit mode and incremental updates
- Linearization
- Digital signatures
- Save overloads
- Limitations
- Targets
- License
Overview
JetsonPDF.Writer is the engine that produces PDF bytes. Everything in the
JetsonPDF stack — the JetsonPDF.Fluent container API, the JetsonPDF.Flow
retained DOM, the WPF / OpenSilver authoring converters — ends up calling
into this assembly to actually emit a content stream.
The model is direct: a Document owns a list of Pages, each Page has a
content stream you populate with draw calls (DrawText, DrawRectangle,
DrawPath, DrawImage, DrawShading, …) plus collections of widgets,
annotations, layers, and structure elements. When you call Save, the
writer serialises objects, builds the cross-reference table, optionally
packs into object streams (PDF 2.0 layout), and emits the file.
The same instance can also open an existing PDF for incremental editing
(Document.Open(path)), letting you replace page contents, stamp Form
XObjects, mutate the page tree, or update form values without rewriting
unchanged objects.
Quick start
using JetsonPDF;
var doc = new Document
{
Title = "First Document",
Author = "You",
PdfVersion = "2.0", // emit a PDF 2.0 header
UseObjectStreams = true, // §7.5.7 / §7.5.8 — compact PDF 2.0 layout
};
var page = doc.AddPage(PageSize.A4);
var font = new Font(FontFamily.Helvetica, 12);
page.DrawText("Hello, A4 world!", font, x: 72, y: 800);
page.DrawLine(72, 790, 523, 790, Color.Black);
page.DrawRectangle(72, 700, 200, 60,
stroke: Color.Black, fill: Color.FromBytes(255, 240, 200));
doc.Save("first.pdf");
Core concepts
| Type | Role |
|---|---|
Document |
Top-level builder. Carries metadata, catalog entries, and the list of Pages. Has AddPage / Save / Open. |
Page |
One page in the document. Content-stream draw calls plus per-page collections (widgets, annotations, layers, structure elements). |
PageSize |
Width / height in points. Static factories (PageSize.Letter, PageSize.A4, …) and a (double w, double h) constructor. |
Font |
Family + style + size — built either from the standard 14 (FontFamily.Helvetica, …) or from an EmbeddedFontFace. |
EmbeddedFontFace |
A loaded TrueType / OpenType face. FromFile / FromBytes. Embedded as a CID composite (Identity-H) with full Unicode coverage. |
Color |
Stroke / fill colour. Device factories (Rgb / Gray / Cmyk), FromBytes, InSpace(colorSpace, …) for non-device. |
ColorSpace |
Device singletons plus CIE-based and special-space factories (CalGray, CalRgb, Lab, Separation, DeviceN, NChannel). |
Path |
A built-up vector path — MoveTo, LineTo, CurveTo, AddRectangle, AddEllipse, AddCircle, Close. |
Image |
A raster image XObject. FromFile / FromPng / FromJpeg / FromInlineItem. |
ImageMask |
A 1-bit stencil drawn with a caller-supplied colour. |
FormXObject |
A reusable content stream (§8.10). Build once, paint many times via Page.DrawForm. |
TilingPattern |
A repeating pattern cell (§8.7.3). Coloured (PaintType 1) or uncoloured stencil (PaintType 2). |
Shading |
Axial / radial / function-based gradients (§8.7.4). |
Annotation |
Base class for every PDF annotation (link, text-markup, FreeText, geometric, …). |
Widget (in JetsonPDF.Forms) |
Base class for AcroForm field widgets — TextField, CheckBox, RadioGroup, PushButton, ComboBox, ListBox, SignatureField, BarcodeField. |
Every draw call on Page wraps itself in a q / Q save-restore so
graphics state (fill colour, line width, transforms) does not leak between
calls. Explicit graphics-state management is also exposed via
PushGraphicsState / PopGraphicsState / Transform.
Document setup
var doc = new Document
{
Title = "Annual Report",
Author = "Acme Inc.",
Subject = "FY2025 results",
Keywords = "annual, finance",
Creator = "report-generator",
Producer = "JetsonPDF",
PdfVersion = "2.0",
UseObjectStreams = true, // ObjStm + XRef stream layout (§7.5.7)
Language = "en-US", // /Lang on the catalog
PageLayout = PageLayout.TwoColumnLeft,
PageMode = PageMode.UseOutlines,
ViewerPreferences = new ViewerPreferences
{
CenterWindow = true,
DisplayDocTitle = true,
Direction = ReadingDirection.LeftToRight,
},
OpenAction = Destination.FitEntire(pageIndex: 0),
};
PdfVersion controls only the header banner. UseObjectStreams switches
between classic xref / trailer syntax and the PDF 2.0 compact
cross-reference-stream layout.
Text and fonts
Font takes a family (one of the 14 standard PostScript fonts) or an
EmbeddedFontFace loaded from a TTF/OTF file, plus a point size. Standard
fonts use WinAnsi (Windows-1252) encoding; embedded fonts use Identity-H
composite encoding so any Unicode codepoint the font supplies renders.
// One of the 14 standard fonts — no embedding, available in every viewer.
var helv = new Font(FontFamily.Helvetica, 12);
var bold = new Font(FontFamily.Helvetica, 14, FontStyle.Bold);
var mono = new Font(FontFamily.Courier, 10);
// Embedded TrueType with full Unicode coverage.
var inter = EmbeddedFontFace.FromFile(@"C:\fonts\Inter-Regular.ttf");
var face = new Font(inter, 12);
page.DrawText("Standard 14 Helvetica", helv, x: 72, y: 750);
page.DrawText("Inter with Unicode → ← arrows", face, x: 72, y: 730);
// Coloured text — applied inside a q/Q scope.
page.DrawText("Tagline", bold, 72, 700, Color.Rgb(0.1, 0.3, 0.7));
// Measure: real metrics from AFM (standard 14) or hmtx (embedded TTF).
double width = helv.MeasureString("How wide am I?");
// Layout a wrapping paragraph in a column.
page.DrawTextBlock(
"A long block of text that should wrap at word boundaries within " +
"the given column width. Paragraph breaks use \\n.",
helv, x: 72, y: 670, width: 450,
alignment: TextAlignment.Justify, lineHeight: 14);
For custom glyph shapes that don't warrant a full TrueType embed, use a
Type3Font:
var t3 = new Type3Font();
t3.DefineGlyph((byte)'A', advance: 600,
g => g.DrawRectangle(0, 0, 500, 600, fill: Color.Black));
var iconFont = new Font(t3, 12);
page.DrawText("A", iconFont, 100, 700);
For text that doesn't round-trip cleanly to Unicode (ligatures, custom
symbols), wrap the visible glyphs in an /ActualText span so extractors
and assistive tech see the canonical string:
page.DrawTextWithActualText(visibleText: "ffi", actualText: "ffi",
font: helv, x: 72, y: 640);
Vector graphics
All vector drawing goes through PDF's path-construction operators (m l c re h) and one of the paint operators (f f* S B B*). The high-level
Page API wraps these:
// Stroked line.
page.DrawLine(72, 700, 540, 700, Color.Black, strokeWidth: 0.5);
// Stroked + filled rectangle.
page.DrawRectangle(72, 600, 200, 60,
stroke: Color.Black, fill: Color.RedLight, strokeWidth: 1.0);
// Ellipse / circle (inscribed in the bounding box).
page.DrawEllipse(cx: 300, cy: 500, rx: 80, ry: 40,
stroke: Color.Blue, fill: Color.FromBytes(220, 230, 255));
// Hand-built path with fill rule.
var star = new Path()
.MoveTo(300, 720)
.LineTo(320, 770)
.LineTo(370, 770)
.LineTo(330, 800)
.LineTo(345, 850)
.LineTo(300, 820)
.Close();
page.DrawPath(star, stroke: Color.Black, fill: Color.Rgb(1, 0.85, 0),
fillRule: FillRule.EvenOdd);
Direct CTM control for transforms the wrappers don't cover (rotation, shear, scale around an arbitrary origin):
page.PushGraphicsState();
page.Transform(Math.Cos(0.4), Math.Sin(0.4),
-Math.Sin(0.4), Math.Cos(0.4),
300, 400); // rotate ~23° around (300, 400)
page.DrawRectangle(0, 0, 100, 40, fill: Color.Black);
page.PopGraphicsState();
Images
// Auto-detect format from magic bytes.
var logo = Image.FromFile("logo.png");
// Explicit format. Both accept an optional ICC profile.
var photo = Image.FromJpeg(File.ReadAllBytes("photo.jpg"));
var alpha = Image.FromPng(File.ReadAllBytes("alpha.png"));
page.DrawImage(logo, x: 72, y: 700, width: 100, height: 100);
page.DrawImage(photo, x: 200, y: 700, width: 200, height: 150);
PNG decoding covers all bit depths (1/2/4/8/16-bit), Adam7 interlacing, an
embedded ICC profile, and per-pixel alpha via a soft mask. JPEG is
embedded verbatim (the JPEG bytes are the DCT-encoded payload PDF expects).
JBIG2 and CCITT G3/G4 images can be embedded via the reader-side inline
image bridge (Image.FromInlineItem).
A 1-bit stencil mask is painted by supplying the colour at paint time:
var mask = new ImageMask(width: 32, height: 32, maskBits: bits);
page.DrawImageMask(mask, x: 100, y: 100, width: 32, height: 32,
color: Color.Rgb(0.2, 0.6, 0.2));
Colour and colour spaces
Color covers the three device families plus arbitrary CIE-based or
N-colorant spaces.
Color.Black; Color.White; Color.Red; Color.Green; Color.Blue;
Color.Gray(0.5); // DeviceGray
Color.Rgb(0.2, 0.4, 0.6); // DeviceRGB
Color.Cmyk(0, 0.4, 0.7, 0.1); // DeviceCMYK
Color.FromBytes(255, 240, 200); // 8-bit RGB shorthand
// Non-device — pass a ColorSpace plus components.
var cal = ColorSpace.CalRgb(gamma: new[] { 2.2, 2.2, 2.2 });
Color.InSpace(cal, 0.4, 0.5, 0.6);
var lab = ColorSpace.Lab();
Color.InSpace(lab, 50, 20, -10); // L* = 50, a* = 20, b* = -10
// Single-colorant spot (PANTONE etc.).
var pantone485 = ColorSpace.Separation(
"PANTONE 485 C",
alternate: ColorSpace.DeviceCmyk,
tintAtFullStrength: new[] { 0.0, 0.95, 1.0, 0.0 });
Color.InSpace(pantone485, 0.85); // 85% strength
// Multi-colorant DeviceN / NChannel.
var hexachrome = ColorSpace.DeviceN(
colorantNames: new[] { "Cyan", "Magenta", "Yellow", "Orange", "Green" },
alternate: ColorSpace.DeviceCmyk,
tintsAtFullStrength: new[]
{
new[] { 1.0, 0, 0, 0 }, new[] { 0, 1.0, 0, 0 }, new[] { 0, 0, 1.0, 0 },
new[] { 0, 0.5, 1.0, 0 }, new[] { 1.0, 0, 1.0, 0 },
});
When a draw call sees a non-device Color, the writer registers the
colour space under /Resources/ColorSpace on the page and emits the
appropriate /CSn cs … scn operator sequence.
Patterns and shadings
Tiling patterns build a small content stream that the viewer repeats:
var pattern = new TilingPattern(
bboxWidth: 40, bboxHeight: 40,
xStep: 40, yStep: 40,
paintType: PaintType.Colored);
pattern.DrawLine(0, 0, 40, 40, Color.Rgb(0.7, 0.7, 0.7), strokeWidth: 1);
pattern.DrawLine(40, 0, 0, 40, Color.Rgb(0.7, 0.7, 0.7), strokeWidth: 1);
// Repeat across a rectangle on the page.
page.FillRectangleWithPattern(72, 400, 450, 200, pattern);
Uncoloured patterns (PaintType 2) supply the paint colour at fill time — the cell renders as a stencil:
var dots = new TilingPattern(20, 20, 20, 20, PaintType.Uncolored);
dots.DrawEllipse(10, 10, 4, 4, fill: Color.Black);
page.FillRectangleWithPattern(72, 300, 450, 60, dots, Color.Rgb(0.2, 0.5, 0.8));
Smooth gradients use Shading. Axial, radial, and function-based (Type 1,
2, 3) are supported on write; mesh shadings (4–7) are read-side only.
// Two-stop axial gradient diagonally across a rectangle.
var grad = Shading.Axial(
x0: 72, y0: 200, x1: 522, y1: 100,
startColor: Color.Rgb(0.1, 0.3, 0.8),
endColor: Color.Rgb(0.9, 0.9, 0.2));
page.FillRectangleWithShading(72, 100, 450, 100, grad);
// Multi-stop radial gradient.
var spot = Shading.Radial(
x0: 300, y0: 600, r0: 0,
x1: 300, y1: 600, r1: 80,
stops: new[]
{
new KeyValuePair<double, Color>(0.0, Color.White),
new KeyValuePair<double, Color>(0.5, Color.Rgb(1, 0.5, 0)),
new KeyValuePair<double, Color>(1.0, Color.Black),
});
page.DrawShading(spot);
Form XObjects
A FormXObject is a reusable content stream — perfect for repeated
watermarks, logos, or page headers. The same instance shared across pages
becomes a single XObject in the output.
var stamp = new FormXObject(width: 200, height: 60);
stamp.DrawRectangle(0, 0, 200, 60, fill: Color.Rgb(0.95, 0.95, 0.95));
stamp.DrawText("DRAFT", new Font(FontFamily.Helvetica, 24, FontStyle.Bold),
x: 20, y: 18, Color.Rgb(0.8, 0.1, 0.1));
foreach (var p in doc.Pages)
p.DrawForm(stamp, x: 200, y: 700, scaleX: 1.0, scaleY: 1.0);
Annotations
Annotations live in the page's /Annots array alongside form widgets.
Every subtype derives from Annotation (in JetsonPDF.Annotations).
using JetsonPDF.Annotations;
// Clickable URL.
page.AddLink(x: 72, y: 700, width: 100, height: 16,
uri: "https://example.com");
// Highlight over text drawn at (72, 670).
var quad = Quad.OverText(helv, x: 72, baselineY: 670, text: "highlighted");
page.AddAnnotation(new TextMarkupAnnotation(TextMarkupKind.Highlight,
new[] { quad }) { Color = Color.Rgb(1, 1, 0) });
// FreeText sticky-note style annotation.
page.AddAnnotation(new FreeTextAnnotation(
x: 400, y: 700, width: 130, height: 40,
text: "Reviewer comment", font: helv));
// Stamp annotation with a Form XObject appearance.
page.AddAnnotation(new StampAnnotation(72, 600, 100, 30,
name: "Approved", appearance: stamp));
// Geometric markups — Line / Square / Circle / Polygon / PolyLine / Ink.
page.AddAnnotation(new LineAnnotation(x1: 72, y1: 500, x2: 200, y2: 500)
{
StrokeColor = Color.Red,
BorderWidth = 2,
StartEnding = LineEnding.Circle,
EndEnding = LineEnding.OpenArrow,
});
page.AddAnnotation(new PolygonAnnotation(new (double, double)[]
{
(300, 500), (350, 540), (400, 500), (350, 460),
}) { FillColor = Color.FromBytes(220, 230, 255) });
// File attachment — embed a payload as an indirect file spec.
var ef = new EmbeddedFile("data.json", File.ReadAllBytes("data.json"));
page.AddAnnotation(new FileAttachmentAnnotation(72, 400, 16, 16, ef)
{
Contents = "Source data",
});
// Other supported subtypes: CaretAnnotation, PopupAnnotation,
// SoundAnnotation, RedactionAnnotation, InkAnnotation.
AcroForm widgets
Form fields are added through Page factories and surface as both the
page's /Annots entries and the document's /AcroForm /Fields array.
using JetsonPDF.Forms;
var page = doc.AddPage(PageSize.Letter);
var font = new Font(FontFamily.Helvetica, 11);
// Text field with several common knobs.
var name = page.AddTextField("name", x: 72, y: 700, width: 300, height: 20, font);
name.MaxLength = 60;
name.Value = "Jane Doe";
name.Alignment = FieldAlignment.Left;
name.IsMultiline = false;
name.IsRequired = true;
// Check box.
page.AddCheckBox("agree", x: 72, y: 670, width: 14, height: 14)
.IsChecked = true;
// Radio group — share a single field name across mutually-exclusive options.
var radio = page.AddRadioGroup("size");
radio.Add("S", x: 72, y: 640, width: 14, height: 14);
radio.Add("M", x: 92, y: 640, width: 14, height: 14);
radio.Add("L", x: 112, y: 640, width: 14, height: 14);
radio.SelectedValue = "M";
// Combo box / list box.
var region = page.AddComboBox("region", 72, 600, 200, 18, font);
region.AddOption("NA"); region.AddOption("EMEA"); region.AddOption("APAC");
region.SelectedValue = "NA";
// Push button with a Named action.
var print = page.AddPushButton("print", 72, 560, 80, 22, font, caption: "Print");
print.Action = new NamedAction("Print");
// Signature field — empty placeholder for the Signer to fill.
page.AddSignatureField("sig", x: 380, y: 560, width: 160, height: 40);
// 2D barcode — bring your own encoder (ZXing, QRCoder, …) for the matrix.
bool[,] modules = MyEncoder.QrCode("https://example.com/order/123");
page.AddBarcodeField("barcode", 380, 480, 60, 60, BarcodeSymbology.QrCode, modules);
Widget-level extras: per-field /AA triggers via widget.AdditionalActions
(keystroke, format, validate, calculate for Tx; mouse / focus for any
annotation), widget.FieldLock for static post-sign field locking, and
SignatureField.SeedValue for /SV seed values. For fields that compute
from other fields, push the dependents into doc.CalculationOrder in
order — the AcroForm dictionary's /CO will reflect that.
Encryption
doc.Encryption = new EncryptionOptions
{
UserPassword = "openme",
OwnerPassword = "fullcontrol",
Algorithm = EncryptionAlgorithm.Aes256, // PDF 2.0 strength
Permissions = Permissions.AllowPrinting // bitwise OR of allowed ops
| Permissions.AllowCopying,
};
Supported algorithms cover the full standard-security-handler spectrum:
| Algorithm | Notes |
|---|---|
RC4_40 |
Legacy V=1, R=2. 40-bit RC4. |
RC4_128 |
V=2, R=3. 128-bit RC4. |
Aes128 |
V=4, R=4. AES-128 with CBC + PKCS#5. Default. |
Aes256 |
V=5, R=6. AES-256 — PDF 2.0 strength. |
Encryption is incompatible with object streams at present; the writer
will fall back to classic xref / trailer syntax when both are set.
Document metadata
Outlines (bookmarks)
var intro = doc.Outlines.Add("Introduction", Destination.FitEntire(0));
intro.Bold = true; intro.Expanded = true;
intro.AddChild("Background", Destination.Xyz(0, top: 720));
intro.AddChild("Scope", Destination.Xyz(0, top: 480));
doc.Outlines.Add("Chapter 1", Destination.FitEntire(1));
doc.Outlines.Add("Chapter 2", Destination.FitEntire(3));
Named destinations
doc.NamedDestinations["intro"] = Destination.FitEntire(0);
doc.NamedDestinations["data-table"] = Destination.Xyz(2, left: 72, top: 720);
// Outline items and link annotations can reference them by name.
doc.Outlines.Add("Jump to data", "data-table");
Page labels
doc.PageLabels.Add(new PageLabelRange(startPageIndex: 0,
style: PageLabelStyle.LowerRoman)); // i, ii, iii, iv
doc.PageLabels.Add(new PageLabelRange(startPageIndex: 4,
style: PageLabelStyle.DecimalArabic)); // 1, 2, 3, …
doc.PageLabels.Add(new PageLabelRange(startPageIndex: 48,
style: PageLabelStyle.DecimalArabic, prefix: "A-")); // A-1, A-2, …
Optional content / layers
var design = new OptionalContentGroup("Design") { VisibleByDefault = true };
var notes = new OptionalContentGroup("Reviewer notes") { VisibleByDefault = false };
doc.OptionalContentGroups.Add(design);
doc.OptionalContentGroups.Add(notes);
using (page.BeginLayer(design)) page.DrawText("Visible by default", helv, 72, 500);
using (page.BeginLayer(notes)) page.DrawText("Hidden by default", helv, 72, 480);
Embedded files
var spec = new EmbeddedFile("invoice.xml",
bytes: File.ReadAllBytes("invoice.xml"),
mimeType: "text/xml");
doc.AssociatedFiles.Add(new AssociatedFile(spec, AfRelationship.Source));
Document-level /AF is the load-bearing entry for Factur-X / ZUGFeRD
PDF/A-3 invoices.
Raw XMP
doc.XmpMetadata = File.ReadAllText("custom-xmp.xml");
When Conformance is set and XmpMetadata is left null, the writer
auto-builds a packet with the appropriate pdfaid / pdfuaid namespaces.
Piece info
doc.PieceInfo["MyApp"] = new PieceInfoEntry
{
LastModified = DateTimeOffset.UtcNow,
Private = new Dictionary<string, string>
{
["DesignerVersion"] = "1.4.2",
},
};
Tagged PDF / structure tree
Set MarkAsTagged = true and wrap content in BeginTag blocks. Nested
calls produce nested structure elements; the document gains a real
/StructTreeRoot.
doc.MarkAsTagged = true;
doc.Language = "en-US";
doc.RoleMap["MyHeading"] = "H1";
using (var section = page.BeginTag("Sect"))
{
section.Title = "Introduction";
section.Language = "en-US";
using (var h = page.BeginTag("H1"))
{
h.ActualText = "Introduction";
page.DrawText("Introduction", boldFont, 72, 750);
}
using (var p = page.BeginTag("P"))
page.DrawTextBlock(introBody, helv, 72, 720, width: 450);
using (var fig = page.BeginTag("Figure"))
{
fig.Alt = "Sales by region";
page.DrawImage(chart, 72, 500, 450, 200);
}
}
Element-level attributes go through PdfStructureAttributes. Shared
attribute bundles can live in doc.ClassMap and be referenced from
element.ClassNames instead of repeating the same attrs on every element.
Annotations can be wired into the tree as /OBJR children:
var link = page.AddLink(72, 400, 100, 16, "https://example.com");
using (var p = page.BeginTag("P"))
p.AnnotationRefs.Add(link);
Measurements and viewports
Geometric annotations and page-level viewports can carry real-world unit metadata so a viewer can show measurements on hover.
// Page-level viewport (e.g. a map covering part of the page).
var rl = PdfMeasure.Rectilinear(
scaleRatio: "1 in = 10 ft",
numberFormats: new[] { PdfNumberFormat.Linear(unitLabel: "ft", precision: 1) });
page.Viewports.Add(new Viewport(
bbox: (x: 72, y: 100, width: 450, height: 400),
name: "Site plan",
measure: rl));
// Per-annotation /Measure (length on a line).
var line = new LineAnnotation(72, 50, 450, 50) { Measure = rl };
page.AddAnnotation(line);
Geospatial measurement (EPSG-coded or WKT-coded coordinate systems) goes
through PdfCoordinateSystem and PdfMeasure.Geospatial.
Conformance
doc.Conformance = Conformance.PdfA2a | Conformance.PdfUA1;
doc.Language = "en-US";
doc.OutputIntent = OutputIntent.SrgbIec61966();
// Find issues without throwing.
foreach (var issue in doc.Validate())
Console.WriteLine($"{issue.Severity} [{issue.Code}] {issue.Message}");
// Or have Save throw on any error.
doc.ThrowOnConformanceError = true;
doc.Save("compliant.pdf");
Supported conformance declarations:
| Flag | Standard |
|---|---|
PdfA1b |
ISO 19005-1 Level B |
PdfA2b / PdfA2a / PdfA2u |
ISO 19005-2 Level B / A / U |
PdfA3b / PdfA3a / PdfA3u |
ISO 19005-3 Level B / A / U |
PdfUA1 |
ISO 14289-1 (accessible PDF, PDF 1.7) |
PdfUA2 |
ISO 14289-2 (accessible PDF, PDF 2.0) |
Setting a flag controls the catalog flag, the XMP identifier namespaces,
and viewer-pref auto-population (DisplayDocTitle for PDF/UA). Content
validation is opt-in via Validate() — it covers high-impact rules
(embedded fonts, /Lang, tag tree, output intent, …) without claiming
veraPDF parity.
Edit mode and incremental updates
Document.Open wraps an existing parsed PDF. Save then emits an
incremental update (§7.5.6) — the original bytes are preserved verbatim
and a trailing section appends whatever you mutated.
var doc = Document.Open("input.pdf");
// Patch metadata.
doc.Title = "New title";
doc.Author = "Editor";
// Fill form values by fully-qualified field name.
doc.Forms["customer.name"] = "Jane Doe";
doc.Forms["agree"] = "Yes";
// Page-tree mutations.
doc.RotatePage(0, degrees: 90);
doc.MovePage(fromIndex: 2, toIndex: 0);
doc.RemovePage(5);
// Stamp a Form XObject on an existing page.
doc.StampPage(pageIndex: 0, form: watermark, x: 200, y: 700);
// Replace a page's content stream while preserving its /MediaBox + /Annots.
doc.ReplacePageContents(pageIndex: 1, draw: page =>
{
page.DrawText("Replaced page", new Font(FontFamily.Helvetica, 24), 72, 700);
});
// Append new pages.
var added = doc.AddPage(PageSize.Letter);
added.DrawText("Appended page", new Font(FontFamily.Helvetica, 12), 72, 720);
doc.Save("input-edited.pdf");
For metadata-only updates, the standalone IncrementalUpdater is a thinner
entry point that doesn't need a full Document:
var upd = IncrementalUpdater.Open("input.pdf");
upd.Title = "Final";
upd.Producer = "MyApp 2.0";
upd.Save("input-meta.pdf");
Edit mode also exposes doc.Source — the full reader DOM — so you can
walk page items and combine reads with writes (echo + replace, find +
highlight, etc.):
var read = doc.Source!.Pages[0];
foreach (var item in read.Items.OfType<JetsonPDF.Reading.PageTextItem>())
{
if (item.Text == "OLD")
doc.ReplacePageContents(0, p => p.EchoTextItem(item, "NEW", helv));
}
Linearization
Linearizer.Linearize(bytes) rewrites a saved PDF into the Annex F
fast-web-view layout — first-page bytes up front, then a Primary Hint
Stream carrying real Page Offset and Shared Object hint tables, then the
remaining pages, then shared objects, with a trailing /Prev-chained
xref.
byte[] saved = doc.Save(); // in-memory
byte[] linear = Linearizer.Linearize(saved);
File.WriteAllBytes("fast-web.pdf", linear);
Caveats: classic xref / trailer output only (xref-stream input is
accepted), and encrypted input is not supported (the linearizer needs
plaintext object bodies).
Digital signatures
Signer.Sign produces an invisible PKCS#7 detached signature via an
incremental update. The signature widget is appended to the document's
/AcroForm. Re-running on the output produces a second incremental
update — multi-signature documents work by chaining.
using var cert = new X509Certificate2("signer.pfx", "password");
byte[] saved = doc.Save();
byte[] signed = Signer.Sign(saved, new SignatureOptions(cert)
{
Reason = "I am the author",
Location = "Seattle, WA",
ContactInfo = "hello@example.com",
HashAlgorithm = HashAlgorithmName.SHA256,
DocMdpPermission = DocMdpPermission.FormFilling, // certification sig
});
File.WriteAllBytes("signed.pdf", signed);
For long-term-validation (PAdES-LTA) flows, layer a document timestamp and
a /DSS Document Security Store on top:
// 1. RFC 3161 timestamp via a TSA client delegate (HTTP factory provided).
byte[] withTs = Timestamper.AddDocumentTimestamp(signed,
new TimestampOptions
{
Client = Timestamper.CreateHttpClient(tsaUrl: "https://freetsa.org/tsr"),
HashAlgorithm = HashAlgorithmName.SHA256,
});
// 2. Bundle the validation material (signer certs, CRLs, OCSP responses)
// into a /DSS dict + per-signature /VRI sub-dicts.
var dss = new DocumentSecurityStore();
dss.Certificates.Add(signerCertBytes);
dss.Certificates.Add(intermediateCertBytes);
dss.OcspResponses.Add(ocspBytes);
dss.Vri[signatureHex] = new VriEntry { /* … */ };
byte[] lta = Dss.AddSecurityStore(withTs, dss);
File.WriteAllBytes("signed-lta.pdf", lta);
SignatureOptions.FieldMdpRestriction and widget.FieldLock /
SignatureField.SeedValue give the field-MDP / /Lock / /SV
machinery for signature constraints (§12.7 / §12.8).
Save overloads
doc.Save("out.pdf"); // path
doc.Save(stream); // any writable Stream
byte[] bytes = doc.Save(); // in-memory
In edit mode, every overload emits the original bytes plus an incremental
section. In new-document mode, the writer builds the full file from
scratch — object stream + xref stream layout when UseObjectStreams, the
classic layout otherwise.
ThrowOnConformanceError runs ConformanceValidator first and throws a
ConformanceException before writing anything when an error-severity
issue is found.
Limitations
- Encryption + object streams are mutually exclusive. Setting
EncryptionwhileUseObjectStreams = truefalls back to classicxref/traileroutput. PDF 2.0 strictly allows the combination via encrypted xref streams; that wiring is queued. - Linearizer accepts xref streams but emits classic xref output. Encrypted input isn't supported — the linearizer needs plaintext object bodies for its BFS closure and hint tables.
- Standard 14 fonts use WinAnsi (Windows-1252) encoding. Anything
outside CP1252 (
↑↓→›‹arrows, em dashes outside CP1252, non-Latin scripts) renders as?. Embed a TrueType face for full Unicode. - Mesh shadings (types 4–7) are read-side only. Writer-side mesh
emission is deferred; use Coons / tensor patches via
PatchTessellatorif you need a discrete tessellation, or stick to axial / radial / function-based for smooth fills. - Signature appearances are invisible. A signature widget is added to
the AcroForm but has no
/APstream — viewers show their default "signed" chrome. Custom appearance is aFormXObjectaway but isn't wired intoSigneryet. - Conformance is declarative, not constructive.
Validate()covers high-impact rules but doesn't claim veraPDF parity; the writer trusts the caller to keep within the declared subset.
Targets
net8.0netstandard2.0net462
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 is compatible. 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 is compatible. 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. |
-
.NETFramework 4.6.2
- JetsonPDF.Common (>= 1.1.0)
- JetsonPDF.Reader (>= 1.1.0)
- Microsoft.Bcl.HashCode (>= 6.0.0)
- System.Buffers (>= 4.5.1)
- System.Memory (>= 4.5.5)
- System.Security.Cryptography.Pkcs (>= 8.0.0)
- System.Text.Encoding.CodePages (>= 8.0.0)
- System.ValueTuple (>= 4.5.0)
-
.NETStandard 2.0
- JetsonPDF.Common (>= 1.1.0)
- JetsonPDF.Reader (>= 1.1.0)
- Microsoft.Bcl.HashCode (>= 6.0.0)
- System.Buffers (>= 4.5.1)
- System.Memory (>= 4.5.5)
- System.Security.Cryptography.Pkcs (>= 8.0.0)
- System.Text.Encoding.CodePages (>= 8.0.0)
-
net8.0
- JetsonPDF.Common (>= 1.1.0)
- JetsonPDF.Reader (>= 1.1.0)
- System.Security.Cryptography.Pkcs (>= 8.0.0)
- System.Text.Encoding.CodePages (>= 8.0.0)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on JetsonPDF.Writer:
| Package | Downloads |
|---|---|
|
JetsonPDF.XamlToPdfConverter.Core
Runtime-neutral core for the XAML-to-PDF authoring pipeline. Owns the snapshot model, PDF emission, pagination, font/Std14 mapping, and path/geometry flattening. Consumed by the WPF and OpenSilver tree-walker adapters. |
|
|
JetsonPDF.Fluent
Headless, code-first fluent API for building PDFs with JetsonPDF — QuestPDF-style containers, auto-pagination, dynamic page numbers. |
|
|
JetsonPDF.Wpf
WPF integration for JetsonPDF. Bundles the XAML-to-PDF authoring pipeline (xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025" dialect, driven by real WPF Measure/Arrange) and the PDF-to-XAML viewer-emitter (turns a parsed ReadDocument into WPF XAML that XamlReader.Parse renders directly). Authoring types live under JetsonPDF.Wpf.Authoring; viewer/markup types live under JetsonPDF.Wpf. Windows-only (net8.0-windows + WPF). |
|
|
JetsonPDF.Flow
Word-like retained-mode DOM (Section / Paragraph / Run / Table) with auto-pagination, layered on JetsonPDF.Fluent. |
|
|
JetsonPDF.OpenSilver
OpenSilver integration for JetsonPDF. Bundles both directions of the XAML pipeline. Authoring (XAML→PDF): the same xmlns:jetsonpdf="http://schemas.jetsonpdf.com/authoring/2025" dialect as the WPF integration, driven by the OpenSilver runtime so the same XAML compiles to a PDF from inside a WebAssembly app, an Edge WebView2 simulator, or a Playwright-driven Chromium CLI. Viewer (PDF→XAML): emits XAML that OpenSilver's XamlReader.Load renders in the browser — text via vector paths (no font cache), images via base64 data URIs, AcroForm widgets as live OpenSilver controls. Walker + image encoder + widget-action dispatch are OpenSilver-specific; emission code, snapshot model, and PDF emission are shared with the WPF flavour. Authoring types live under JetsonPDF.OpenSilver.Authoring; viewer/markup types under JetsonPDF.OpenSilver. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.0 | 52 | 6/6/2026 |
| 1.0.0 | 314 | 5/23/2026 |
| 0.2.0-preview | 315 | 5/23/2026 |
| 0.1.0-preview | 312 | 5/17/2026 |