Bhowra.Ink 1.0.7

dotnet add package Bhowra.Ink --version 1.0.7
                    
NuGet\Install-Package Bhowra.Ink -Version 1.0.7
                    
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="Bhowra.Ink" Version="1.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Bhowra.Ink" Version="1.0.7" />
                    
Directory.Packages.props
<PackageReference Include="Bhowra.Ink" />
                    
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 Bhowra.Ink --version 1.0.7
                    
#r "nuget: Bhowra.Ink, 1.0.7"
                    
#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 Bhowra.Ink@1.0.7
                    
#: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=Bhowra.Ink&version=1.0.7
                    
Install as a Cake Addin
#tool nuget:?package=Bhowra.Ink&version=1.0.7
                    
Install as a Cake Tool

Bhowra.Ink — HTML to PDF for .NET, C# & Azure Functions

NuGet NuGet Downloads

Serverless HTML-to-PDF for .NET — without a browser. Bhowra.Ink is a zero-dependency, pure-C# engine that converts modern HTML5 + CSS (Grid, flexbox, var()/calc(), gradients, border-radius, box-shadow, transforms, SVG) straight to PDF, in-process — with no Chromium, no headless browser, and no native binaries.

A fully managed alternative to headless-browser (Chromium-based) converters and AGPL-licensed PDF libraries. No AGPL. No cold start. Zero third-party dependencies.

Use it for HTML-to-PDF in C#, ASP.NET Core, Blazor, Azure Functions, and AWS Lambda — invoices, hybrid e-invoices (Factur-X / ZUGFeRD, ZATCA), payslips, statements, reports, and contracts — where launching a headless browser per request is too slow, too heavy, or simply not allowed.


Install

dotnet add package Bhowra.Ink

Targets .NET 8. One managed DLL (under 1 MB — 880 KB, fonts embedded). Works on Windows and Linux, including Azure Functions / AWS Lambda / containers.

Quickstart

using Bhowra.Ink;

string html = "<h1 style='color:teal'>Hello, PDF</h1><p>Rendered in pure .NET.</p>";

// Simplest: straight to a file
HtmlConverter.ConvertToFile(html, "out.pdf");

// Or get bytes + a result you can inspect
ConversionResult result = HtmlConverter.Convert(html, new ConverterProperties { Title = "Demo" });
Console.WriteLine($"{result.PageCount} page(s), {result.SizeBytes} bytes, {result.ConversionTimeMs} ms");
result.SaveToFile("out.pdf");

Output options: ConvertToFile, ConvertToBytes, ConvertToStream, ConvertFileToFile, and Convert (returns a ConversionResult with bytes, page count, timing, and diagnostics).

Convert HTML to PDF in Azure Functions (serverless)

Because Bhowra.Ink runs in-process with no Chromium and no native binaries, it works on Azure Functions, AWS Lambda, and Linux containers with no cold-start browser launch and nothing to install on the host — just add the package and call HtmlConverter:

[Function("InvoicePdf")]
public async Task<HttpResponseData> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
    string html = await new StreamReader(req.Body).ReadToEndAsync();
    byte[] pdf   = HtmlConverter.ConvertToBytes(html);

    var res = req.CreateResponse(System.Net.HttpStatusCode.OK);
    res.Headers.Add("Content-Type", "application/pdf");
    await res.Body.WriteAsync(pdf);
    return res;
}

The same code runs unchanged on Windows Consumption plans, Linux, AWS Lambda, and Docker — there are no per-platform native builds to manage.


Why Bhowra.Ink? (vs headless-browser converters & AGPL/commercial PDF libraries)

Concern Chromium / headless-browser converters AGPL or commercial PDF libraries Bhowra.Ink
Cold start (serverless) 5–15 s to launch a browser varies in-process, no browser launch
Dependencies Chromium + native binaries AGPL or per-seat commercial zero — pure .NET 8 BCL
Deployment size 150–400 MB tens of MB under 1 MB (880 KB), one DLL
Linux / Windows often native-only varies cross-platform managed
Modern CSS (Grid, gradients, SVG) yes frequently weak yes
License often AGPL or costly commercial; watermark-on-trial

What it renders

CSS & layout

  • Full CSS cascade — specificity, inheritance, shorthand expansion, !important, inline styles
  • External stylesheets<link rel="stylesheet"> and @import resolved into the cascade in document order (secure-by-default; see External stylesheets)
  • Selectors: type, class, id, attribute, descendant/child/adjacent/sibling, :nth-child, :not, ::before/::after (incl. counters)
  • Flexbox and CSS Grid (incl. repeat(), minmax(), auto-fit/auto-fill)
  • var(), calc(), position (relative/absolute/fixed + z-index), float, multi-column
  • Gradients (linear / radial / conic), border-radius, box-shadow, filter: blur, transforms (rotate/scale/translate), opacity & alpha compositing
  • Tables (colspan, rowspan, border-collapse, repeating headers), lists, nested markers

Documents & paging

  • Automatic pagination, page-break-*, @page size/margins
  • Running headers/footers (HeaderHtml/FooterHtml with {{page}} / {{pages}})
  • Bookmarks/outlines from headings, internal links, link annotations
  • Interactive AcroForm fields and embedded file attachments

Graphics & fonts

  • SVG: paths, shapes, gradients, clipPath, <use>/<symbol>, transforms
  • Images: PNG (incl. alpha/soft-mask + 16-bit), JPEG (incl. CMYK + Exif orientation), GIF, BMP, WebP (lossless), data: URIs
  • Base-14 fonts (no files needed) + TrueType/OpenType embedding (Identity-H), WOFF/WOFF2, subsetting, @font-face
  • Right-to-left & complex scripts — Arabic (OpenType cursive joining + GPOS harakat) and Hebrew with bidirectional (mixed LTR/RTL) text, plus Devanagari and Thai — bundled Noto fonts, no setup

Output targets

  • Encryption: passwords with RC4-128 or AES-256
  • PDF/A-2b and PDF/A-3b (archival — all fonts embedded automatically), PDF/UA-1 + Tagged PDF (accessibility), XMP metadata
  • Hybrid e-invoices — PDF/A-3 with embedded associated files (/AFRelationship), EU Factur-X / ZUGFeRD, and a built-in QR-code generator (see E-invoicing)
  • Configurable diagonal watermark

E-invoicing — Factur-X / ZUGFeRD, ZATCA hybrid, and QR

Produce the hybrid invoice PDF that modern e-invoicing flows expect: a human-readable PDF/A-3 with the structured XML embedded inside it, plus a scannable QR — all in pure .NET, no extra packages.

PDF/A-3 with an embedded associated file

using Bhowra.Ink;

byte[] xml = File.ReadAllBytes("invoice.xml");

var props = new ConverterProperties { PdfA = PdfAConformance.PdfA3B }
    .AttachFile("invoice.xml", xml, "application/xml", afRelationship: "Alternative");

byte[] pdf = HtmlConverter.ConvertToBytes(invoiceHtml, props);

EU Factur-X / ZUGFeRDSetFacturX embeds the Cross-Industry Invoice XML under the mandated filename (factur-x.xml, or xrechnung.xml for XRECHNUNG), writes the fx: XMP schema with its required PDF/A extension declaration, and applies a profile-correct /AFRelationship:

byte[] cii = File.ReadAllBytes("factur-x.xml");

var props = new ConverterProperties { PdfA = PdfAConformance.PdfA3B }
    .SetFacturX(cii, FacturXProfile.En16931);

byte[] pdf = HtmlConverter.ConvertToBytes(invoiceHtml, props);

Profiles: Minimum, BasicWL, Basic, En16931, Extended, XRechnung.

This fits enterprise EDI, ERP, and SAP S/4HANA flows that need a PDF/A-3 hybrid invoice carrier: Bhowra.Ink builds the document layer (rendered invoice PDF + embedded CII XML + Factur-X/XRechnung metadata). It is not an EDI network, validation service, or SAP connector; generate and validate the business XML in your platform, then pass it to SetFacturX.

Built-in QR — a zero-dependency ISO/IEC 18004 generator (byte mode, versions 1–40, error-correction L/M/Q/H), drawn as a crisp vector with the mandatory quiet zone — sized for payloads up to the ~700-character range (e.g. a Saudi TLV string).

For Saudi invoice QR data, ZatcaTlv builds the TLV payload: typed helpers for tags 1-5, and raw caller-supplied bytes for Phase 2 tags 6-9 (invoice hash, signature, public key, stamp signature). It does not generate, sign, stamp, validate, or submit the invoice XML.

using Bhowra.Ink;
using Bhowra.Ink.QrCode;

byte[] tlvBytes = ZatcaTlv.BuildPhase1Bytes(
    seller: "Najd Trading Company",
    vatNo: "311111111100003",
    ts: DateTimeOffset.Parse("2026-06-28T10:15:00Z"),
    total: "2472.50",
    vat: "322.50");

var props = new ConverterProperties
{
    QrCode =
    {
        Data             = tlvBytes,       // any bytes; null/empty = no QR (output unchanged)
        X                = 60f,            // points from the page's left edge
        Y                = 70f,            // points from the page's bottom edge
        Size             = 120f,           // points (includes the quiet zone)
        Page             = 1,              // 1-based; 0 = every page
        EcLevel          = QrEcLevel.M,    // L / M / Q / H
        QuietZoneModules = 4,              // ISO minimum
    },
};

Configuration

ConverterProperties is the single config object (every member is documented for IntelliSense):

var props = new ConverterProperties
{
    Title  = "Invoice 2026-001",
    Author = "Acme Corp",

    WatermarkText    = "CONFIDENTIAL",   // diagonal; always honoured, licensed or not
    WatermarkOpacity = 0.15f,
    WatermarkColor   = "#BFBFBF",

    UserPassword = "secret",             // encrypts the PDF
    Aes256       = true,                 // AES-256 instead of the default RC4-128

    PdfA   = PdfAConformance.PdfA2B,     // archival conformance (fonts auto-embedded)
    Tagged = true,                       // accessible structure (PDF/UA)

    MissingGlyphBehavior = MissingGlyphBehavior.Diagnostic, // never silently drop glyphs
}
.SetPageA4()        // or SetPageLetter() / SetPageLegal() / SetLandscape()
.SetMargins(36f);   // points (1 pt = 1/72")

byte[] pdf = HtmlConverter.ConvertToBytes(html, props);

Page size/margins can also be set explicitly via PageWidth/PageHeight and MarginTop/Right/Bottom/Left (all in points). Defaults: A4 portrait, 36 pt margins.


The engine resolves external CSS — <link rel="stylesheet"> and the @imports those sheets (and inline <style> blocks) contain — and cascades it with embedded styles in correct document order. It is secure-by-default: local stylesheets under BaseUri resolve out of the box; remote (http/https) fetching is opt-in.

// Local stylesheets (the common case) — resolved relative to BaseUri, no extra setup:
var props = new ConverterProperties { BaseUri = templateDir };   // <link href="css/site.css"> just works

// Opt in to remote stylesheets (CDN), keeping every guard on:
props.ExternalCss.EnableRemote();          // or .Mode = ExternalCssMode.LocalAndRemote

props.ExternalCss (an ExternalCssOptions) carries the policy and limits:

Setting Default Purpose
Mode LocalOnly Disabled / LocalOnly / LocalAndRemote
AllowHttp false allow plain http:// (otherwise HTTPS-only)
AllowPrivateNetwork false the SSRF guard — blocks private/loopback/link-local IPs and the cloud metadata endpoint
MaxBytesPerFile / MaxBytesTotal 2 MiB / 8 MiB size caps (DoS guard)
MaxStylesheets / MaxImportDepth 30 / 5 count + @import recursion caps
TimeoutSeconds / MaxRedirects 10 / 5 per-fetch timeout; each redirect is SSRF-re-validated
FailOnError false fail-open (skip a bad sheet + record a diagnostic) vs. fail-closed
AllowedHosts / CssFetcher optional host allow-list; optional custom fetcher (e.g. Managed Identity)

Behaviour matches how a strict print renderer treats CSS: the print media is the target, so media="screen" links are ignored and media="print"/all apply; @import cycles are detected (ancestor-chain) and depth-bounded; charset follows BOM → HTTP charset@charset → UTF-8. Skipped or blocked stylesheets are reported in ConversionResult.ImageDiagnostics.


Images: remote & local sources (secure-by-default)

Out of the box an <img> is embedded only from a data: URI. Both remote and local file sources are opt-in, so converting HTML you don't fully trust makes no outbound network calls and reads no local files — no SSRF and no local-file disclosure by default.

<img src> Default Enable
data: URI embedded
Remote http(s):// not fetched props.AllowRemoteImages = true (or supply an ImageFetcher). Fetches then pass the SSRF guard — private/loopback/link-local IPs and the cloud metadata endpoint blocked, redirect-to-internal and DNS-rebind defeated, 64 MiB response cap.
Local file path not read props.AllowLocalFiles = trueonly for trusted HTML. Off by default so an untrusted <img src="/etc/passwd"> can't embed an arbitrary local file.
// Trusted template with local logo assets + CDN images:
var props = new ConverterProperties { AllowLocalFiles = true, AllowRemoteImages = true };

Skipped or blocked images are reported in ConversionResult.ImageDiagnostics.


Image → PDF

Turn one or more images into a PDF — one image per page — with ImageToPdf. It routes through the same renderer, so it inherits the full image stack: JPEG embedded losslessly (DCTDecode passthrough), PNG/WebP/BMP/GIF decoding, RGB/Gray/CMYK color spaces, EXIF auto-orientation, alpha (/SMask), and image de-duplication.

// One image, fit to A4 (default), preserving aspect ratio:
byte[] pdf = ImageToPdf.ToBytes("scan.jpg");

// Several images → multi-page, page sized to each image (no margins):
ImageToPdf.ToFile(
    new[] { ImageInput.FromFile("p1.png"), ImageInput.FromBytes(jpegBytes) },
    "album.pdf",
    new ImageToPdfOptions { PageSize = ImagePageSize.FitToImage });

// A4 with margins, centered, cropped to fill, at print resolution:
var opts = new ImageToPdfOptions
{
    PageSize  = ImagePageSize.A4,     // or Letter / Legal / Custom / FitToImage
    Fit       = ImageFit.Cover,       // Contain (default) / Cover / Stretch / None
    MarginPt  = 36,
    Dpi       = 300,                  // px→pt for FitToImage (300 = print scans)
    Background = "#ffffff",
};
byte[] album = ImageToPdf.ToBytes(new[] { ImageInput.FromFile("photo.jpg") }, opts);

Inputs are files, byte[], or Stream (ImageInput.FromFile/FromBytes/FromStream). ImagePageSize.FitToImage sizes the page to the image; with multiple differently-sized images the page is sized to the largest and the rest are centered (one PDF uses a single page size).


Fonts & non-Latin text

props.RegisterFont("Inter", File.ReadAllBytes("Inter.ttf"));   // embed a custom family

Bundled Noto fonts (Arabic, Hebrew, Thai, Devanagari) ship inside the DLL, so common non-Latin scripts render with no extra setup. Anything a registered or bundled font can't draw is reported — never silently dropped:

var result = HtmlConverter.Convert(html, props);
foreach (var d in result.FontDiagnostics)
    Console.WriteLine(d);   // e.g. characters with no available glyph + how to fix

Licensing & trial

Bhowra.Ink is commercial software that runs fully without a key — it never expires, never imposes a page limit, and never throws based on license state. Unlicensed output simply carries a horizontal "TRIAL VERSION" mark (plus a clickable licensing banner). Activating a valid license removes that trial mark; any caller-configured watermark is independent and unaffected. A free, time-limited evaluation key for clean output is available on request.

See LICENSE.txt for the full agreement. Licensing & sales: sales@bhowra.com.


Architecture (in brief)

HtmlConverterHtmlParser (DOM) + CssParser/CssCascade (computed styles) → LayoutTreeBuilder (typed layout tree) → PdfWriter (paginate, render, emit PDF bytes). Everything — the HTML parser, CSS engine, font/image decoders, and the PDF writer — is implemented from scratch on the .NET 8 base class library, with no third-party packages.


Known limitations

Area Status
PDF/A (2b / 3b) + watermark / form fields Not combinable — watermarks, the trial mark, and form fields draw with non-embeddable standard-14 fonts, so PDF/A throws a clear error. Plain text + registered fonts embed automatically; PDF/A needs a licensed build (no trial mark).
Persian/Urdu extended Arabic letters Beyond Modern Standard Arabic — not yet cursively joined
CJK scripts Require a font via RegisterFont(...) or @font-face (not bundled)
JavaScript / SPAs / live web pages Out of scope — renders HTML/CSS, does not execute scripts
Remote @font-face URLs Not fetched (local files and data: URIs supported)
TIFF / JPEG-2000 images No decoder — surfaced via result.ImageDiagnostics (never silently dropped, no black box)
Lossy / animated WebP Only lossless (VP8L) WebP is decoded
border-collapse shared-border dedup Borders drawn per cell (no dedup yet)
Cross-host font fallback Text in base-14 and the bundled Noto/Roboto fonts (Latin, Arabic, Hebrew, Thai, Devanagari) renders identically everywhere; glyphs outside that set fall back to host system fonts, so their metrics can differ across environments (e.g. Windows Arial vs Linux DejaVu). Bundle or RegisterFont(...) a font for byte-identical output on every host.
Gradient as a page background Element gradients (linear / radial / conic on a box) render as real PDF shadings; a gradient set on body/html is not yet painted as the full-page background (a solid page colour is)
backdrop-filter Not applied — the blur of content behind a translucent box is a no-op (the box's own background, borders and shadows still render)

License

Commercial license — see LICENSE.txt. Bundled Noto fonts are licensed under the SIL Open Font License 1.1 (see OFL.txt) and remain governed by the OFL.

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

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.7 93 7/11/2026
1.0.6 108 7/7/2026
1.0.5 105 7/5/2026
1.0.4 172 7/4/2026
1.0.3 115 7/3/2026
1.0.2 111 6/28/2026
1.0.1 113 6/27/2026
1.0.0 123 6/25/2026

1.0.7 — ZATCA QR TLV builder and QR options grouping. New ZatcaTlv builds the Base64-encoded Tag-Length-Value payload for the Saudi ZATCA invoice QR: typed helpers for tags 1-5 (seller name as UTF-8, VAT number, ISO-8601 UTC timestamp, invoice total, VAT total) and raw caller-supplied bytes for Phase 2 tags 6-9 (invoice hash, ECDSA signature, public key, stamp signature), with a one-byte length cap and BuildPhase1/BuildPhase2 convenience helpers. It PACKS values only — it does not compute the hash or signature, generate the UBL/CII invoice XML, cryptographically stamp, or submit to the FATOORA portal; those remain the caller's responsibility (this is the presentation/document layer, not a compliance platform). QR configuration is now grouped into a QrCodeOptions object accessed via ConverterProperties.QrCode (Data/X/Y/Size/Page/EcLevel/QuietZoneModules), mirroring the ExternalCss options; the former flat ConverterProperties.QrCode* properties remain as source-compatible [Obsolete] forwarding shims over it. Internal/quality: the QR round-trip decoder that proves the encoder is correct was moved out of the shipped assembly into the test project, so no test-only code ships in the library. Output is byte-for-byte unchanged for documents that do not use the QR/e-invoicing features. 1.0.6 — Encryption hardening, table-layout fixes, and internal modularization (output byte-for-byte unchanged for unencrypted documents). Encryption: password-protected PDFs now encrypt EVERY dictionary-level string — form-field names/values and the /DA default-appearance, link /URI actions, GoTo destination names, the names tree, embedded-file names/descriptions, /Alt text and /Lang — plus the text-field appearance streams; previously several of these were emitted as plaintext, so a conforming viewer would decrypt them to garbage (broken links, unreadable field values). Radio buttons sharing a name are now grouped under one parent field with per-widget export states, so selecting one clears its name-mates; non-ASCII radio export values are UTF-8/byte-correct in PDF names. The RC4 owner password is now honoured (was silently ignored), encrypted content streams are Flate-compressed (were left uncompressed), and requesting PDF/A together with a password now fails loud (PDF/A forbids encryption) instead of quietly dropping it. Table layout: a rowspan cell beside an auto-width column no longer leaves a blank unpainted gap — row-height and column-width measurement now resolve each cell to its true column across active rowspans (matching the render pass); and word-break:break-all no longer inflates its column's minimum width and starves the sibling columns. Layout: aspect-ratio on a definite width now recomputes the derived height when a render-time max-width clamp narrows the box, and browser-style fit-to-page scaling no longer compounds across the two-pass target-counter (cross-reference) render. Image security: a crafted PNG chunk length can no longer overflow the 32-bit bounds check (long arithmetic), and IDAT decompression is capped to the size the image header declares — rejecting a decompression bomb before it can exhaust memory. Internals/quality: the large PDF writer is split into focused partial-class files, and the PNG decoder and the PDF Standard-Security cryptographic primitives (RC4 / AES-256 R6 Hash2B) are now standalone types with direct unit tests, including an automated AES-256 decrypt round-trip. 1.0.5 — Browser-parity rendering fixes and a golden-image regression harness. Four layout/text defects fixed, each locked by a test that fails on the prior code: an inline <svg> with width/height="100%" now resolves against its container (with a viewBox aspect-ratio fallback for auto height) instead of a fixed default, so it no longer overflows its box; grid-column: 1 / -1 now spans the full column set (negative grid lines resolve to the last line) instead of collapsing to a single track; an absolutely-positioned box with width:auto now shrinks to fit its content (CSS 2.1 §10.3.7) instead of stretching to the containing block; and Arabic-Indic numerals (U+0660–0669 / U+06F0–06F9) now render left-to-right as numbers instead of being reversed as if RTL, so an Arabic-Indic price such as 45.00 no longer renders as 00.54. Also adds a golden-image regression harness (rasterizes generated PDFs to fingerprints and fails the build on structural drift) and a documented capability contract for in-scope vs out-of-scope features. Security (behavior change): remote (http/https) <img> fetching is now OFF by default — opt in via ConverterProperties.AllowRemoteImages (or by supplying an ImageFetcher), matching the existing external-CSS LocalOnly policy; when enabled, image fetches go through the same SSRF guard (private/link-local/cloud-metadata addresses blocked, redirect-to-internal and DNS rebind defeated, 64 MiB response cap). Callers that relied on remote images rendering by default must now enable them. Plus code-review CSS-correctness fixes: grid-column / grid-area negative line numbers (e.g. 1 / -1) span to the last track, non-rectangular grid-template-areas are rejected per spec, a ::before/::after counter-reset no longer leaks its counter scope, and a definite-zero percentage-height parent resolves the child to 0 rather than auto. Security (behavior change): reading a LOCAL (non-data, non-http) <img> file is now OFF by default — opt in via ConverterProperties.AllowLocalFiles for TRUSTED HTML only, so converting untrusted HTML can no longer disclose an arbitrary local file (e.g. <img src="/etc/passwd">) into the PDF; callers that render local image assets must enable it. More browser-parity fixes, each locked by a test: aspect-ratio on a PERCENTAGE width now derives the box height (a responsive width:100% + aspect-ratio box is no longer zero-height); a uniform border-radius percentage resolves against the element's OWN width instead of a fixed default (a %/auto-width card is no longer over-rounded); a <select>'s <option value> export values are preserved in the AcroForm /Opt array instead of being collapsed to the display label; StyleApplier is now idempotent, so font-variant:small-caps no longer compounds its 0.8x shrink when a style is re-applied (e.g. via a legacy table bgcolor attribute); and a pathologically deep DOM is depth-bounded so it degrades gracefully instead of overflowing the stack. Hardening: Azure SAS signatures are redacted from image diagnostics and the image-download response is disposed. 1.0.4 — CSS Grid, flexbox, and browser-style fit-to-page. New unified CSS Grid engine: rows now stretch and distribute height (fr/minmax/auto tracks, align-content) the way columns do, track sizes honour every CSS unit (px/%/em/rem/…), a span grows the implicit grid, and a grid with no grid-template-columns (implicit columns, or columns from grid-template-areas) now sizes its columns from content instead of collapsing to zero width and rendering invisible. Grid item text no longer disappears under the grid container's own background (paint-order fix). New fit-to-page scaling (ConverterProperties.PageScaling, default ShrinkToFit): a design wider than the page is laid out at its natural width and the whole page is scaled to fit — with link, bookmark and destination coordinates scaled to match — instead of collapsing or paginating; PageScaling.None keeps the previous behaviour. Flexbox: a nested flex row is measured at its true (cross-axis) height rather than a block stack; justify-content space-between/around/evenly/center/end now distributes on a stretched or min-height column (e.g. a card pinning its footer to the bottom); and such a column no longer spreads its items below the page bottom when it cannot fit (falls back to top-packing, no content loss). Plus min-height/min-width and nested grid/flex measurement fixes. 1.0.3 — External stylesheets, Image-to-PDF, and rendering-correctness fixes. New external CSS resolution: <link rel="stylesheet"> and @import are fetched and folded into the cascade in document order, secure by default — local-only unless remote is explicitly enabled, with SSRF protection (private/link-local/cloud-metadata address blocking, per-redirect re-validation, connect-time IP pinning), per-file and total byte caps, a stylesheet-count cap, @import cycle and depth guards, and BOM/header/@charset-correct decoding. New Image-to-PDF API (ImageToPdf): turn one or more images (JPEG/PNG/WebP/BMP/GIF) into a PDF, one image per page, reusing the engine's image pipeline (JPEG DCTDecode passthrough, DeviceRGB/Gray/CMYK, Adobe inverted-CMYK, EXIF orientation, alpha to soft-mask, SHA-256 dedup, Flate compression) with Contain/Cover/Stretch/None fit modes and fixed or fit-to-image page sizing. Rendering correctness: line-height:normal now derives per font from the font's own hhea metrics (fixes overlapping wrapped bold lines); nested non-table block content and long paragraphs now paginate across pages instead of overflowing off-page; a box's background and borders are sliced across page breaks (box-decoration-break: slice); position:fixed/absolute elements anchor to the CSS page area so running headers/footers land correctly on every page; percentage heights resolve against the containing block's height; text placed directly inside a flex or grid box inherits the container's font-size, weight and colour; undefined utility class names no longer synthesize styles in author-styled documents. 1.0.2 — Hybrid invoices, QR, and expanded search tags. New PDF/A-3b conformance (PdfAConformance.PdfA3B) embeds arbitrary files (e.g. an invoice XML) as associated files with a per-file /AFRelationship. New EU Factur-X / ZUGFeRD support (SetFacturX): the structured CII XML is embedded under the mandated filename with the fx: XMP schema and its required PDF/A extension-schema declaration, and a profile-correct /AFRelationship. This is also the PDF/A-3 hybrid that Saudi ZATCA permits as the human-readable buyer copy (the signed XML, cryptographic stamp and FATOORA submission remain the caller's responsibility — this is the document layer, not a compliance platform). New zero-dependency QR generator (QrCodeData): byte mode, versions 1–40, all four EC levels, drawn as a crisp vector Form XObject with the mandatory quiet zone — sized for the ~500–700-char ZATCA TLV payload. Also expanded NuGet search tags. 1.0.1 — PDF/A-2b now embeds all fonts automatically, including the default font-less path (a small OFL font is bundled), so archival output is conformant out of the box; watermarks and form fields under PDF/A now fail fast with a clear error instead of emitting a non-conformant file. Zero-dependency, pure-.NET HTML-to-PDF: modern CSS (flexbox, grid, gradients, transforms), SVG, PDF/A-2b and PDF/UA-1, RC4-128/AES-256 encryption, and right-to-left scripts (Arabic with GSUB cursive joining + GPOS harakat, and Hebrew).