Bhowra.Ink
1.0.11
dotnet add package Bhowra.Ink --version 1.0.11
NuGet\Install-Package Bhowra.Ink -Version 1.0.11
<PackageReference Include="Bhowra.Ink" Version="1.0.11" />
<PackageVersion Include="Bhowra.Ink" Version="1.0.11" />
<PackageReference Include="Bhowra.Ink" />
paket add Bhowra.Ink --version 1.0.11
#r "nuget: Bhowra.Ink, 1.0.11"
#:package Bhowra.Ink@1.0.11
#addin nuget:?package=Bhowra.Ink&version=1.0.11
#tool nuget:?package=Bhowra.Ink&version=1.0.11
Bhowra.Ink — Serverless HTML to PDF for .NET
Serverless HTML-to-PDF for .NET — lightweight, no browser, modern CSS rendering. Bhowra.Ink is a
zero-dependency, pure-C# engine in one DLL (just over 1 MB) that runs in-process on Azure
Functions (including the Consumption plan), AWS Lambda and containers — no Chromium, no headless
browser, no native binaries, no browser cold start. It renders modern HTML5 + CSS (Grid, flexbox,
multi-column, var()/calc(), gradients, border-radius, box-shadow, transforms incl. 3D, SVG)
to spec.
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 (just over 1 MB, 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 (Convert never throws — check Success)
ConversionResult result = HtmlConverter.Convert(html, new ConverterProperties { Title = "Demo" });
if (!result.Success) throw new InvalidOperationException(result.ErrorMessage);
Console.WriteLine($"{result.PageCount} page(s), {result.SizeBytes} bytes, {result.ConversionTimeMs} ms");
result.SaveToFile("out.pdf");
Output options: ConvertToFile, ConvertToBytes, ConvertToBytesAsync (fetches remote images
concurrently), ConvertToStream, ConvertFileToFile (uses the HTML file's folder as BaseUri), and
Convert. The Convert*To* methods throw on failure; Convert instead returns a ConversionResult with
Success/ErrorMessage, the bytes, page count, timing, and three diagnostic lists —
FontDiagnostics, ImageDiagnostics, and LayoutDiagnostics (structural recoveries such as an
inconsistent table grid or nesting-depth truncation). Forward all three to your logger.
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 | just over 1 MB, 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@importresolved into the cascade in document order (secure-by-default; see External stylesheets) - Selectors: type, class, id, attribute, descendant/child/adjacent/sibling,
:nth-child,:nth-of-type,:first-/:last-/:only-child,:first-/:last-of-type,:empty,:root,:not,:is,:where,::before/::after(incl. counters),::marker - Flexbox and CSS Grid (incl.
repeat(),minmax(),auto-fit/auto-fill);display: inline-blockon any element var(),calc(),position(relative/absolute/fixed + z-index),float- Multi-column —
columns,column-width,column-count,column-gap,column-fill, andcolumn-rule, paginated across pages - Gradients (linear / radial / conic) on boxes and as the page background (
body/html),border-radius,box-shadow,filter: blur, transforms (rotate/scale/translate, incl.calc()and%arguments), opacity & alpha compositing - Tables (colspan, rowspan,
border-collapse,border-spacing,empty-cells,caption-side, repeating headers), lists and nested markers (list-style-position,list-style-image) background-size/background-repeat,flex-flow,place-content, and logical properties (margin-inline-*,padding-inline-*,border-inline-*) that followdirection- Pair kerning from the font's OpenType
GPOStable, plusletter-spacing/word-spacing, for embedded fonts
Documents & paging
- Automatic pagination,
page-break-*/break-before/break-after(incl.avoid),break-inside: avoid/avoid-page @pagesize/margins, including named pages (@page wide { size: A4 landscape }+page: wide) — mixed page sizes in one PDF- Wide content is shrunk to fit the page by default (
PageScaling.ShrinkToFit); setPageScaling.Noneto lay out strictly at the page width - Running headers/footers (
HeaderHtml/FooterHtmlwith{{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, and<text>(shaped, bidi-aware, with font fallback) - 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) - Barcodes — 2D QR Code, PDF417, Aztec, and Data Matrix, and linear Code 128, GS1-128, EAN-13, EAN-8, and UPC-A, any number per document, placed by point coordinates or an HTML element anchor, plus an IATA BCBP boarding-pass builder (see Barcodes & boarding passes)
- 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 / ZUGFeRD — SetFacturX 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.
SetFacturX requires PdfA = PdfAConformance.PdfA3B and fails before fetching external CSS or images
when another PDF/A mode is selected.
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
},
};
Barcodes & boarding passes
Beyond the single invoice QR above, ConverterProperties.Barcodes draws any number of barcodes
on a document, mixing symbologies freely — 2D QR Code (ISO/IEC 18004), PDF417 (ISO/IEC 15438),
Aztec (ISO/IEC 24778), and Data Matrix ECC 200 (ISO/IEC 16022), and linear Code 128
(ISO/IEC 15417), GS1-128 (Code 128 with FNC1, for SSCC / GTIN logistics labels), EAN-13,
EAN-8, and UPC-A (ISO/IEC 15420). Each is a crisp vector Form
XObject — no raster, no fonts — so it stays sharp at any scale and is PDF/A-safe. All encoders are pure
.NET, zero third-party packages.
You supply the payload bytes; the engine renders them. Place each barcode either by explicit point
coordinates or by pointing AnchorId at an HTML element, letting the document's layout own placement:
using Bhowra.Ink;
using Bhowra.Ink.Barcodes;
using Bhowra.Ink.QrCode;
var props = new ConverterProperties();
// Placed by absolute point coordinates.
props.Barcodes.Add(new BarcodeOptions
{
Symbology = BarcodeSymbology.Pdf417,
Data = System.Text.Encoding.ASCII.GetBytes(bcbpString),
X = 40f, Y = 40f, Width = 220f, Height = 70f, // points; Height 0 = natural aspect
Page = 1, // 1-based; 0 = every page
Pdf417ErrorLevel = 5, // 0-8 (PDF417 only)
});
// Placed by an HTML element — the laid-out box of #boarding-code supplies X/Y/Width/Height.
props.Barcodes.Add(new BarcodeOptions
{
Symbology = BarcodeSymbology.Aztec,
Data = System.Text.Encoding.ASCII.GetBytes(bcbpString),
AnchorId = "boarding-code", // QR/Aztec/DataMatrix anchors must be square
AztecErrorPercent = 23, // ISO recommendation (Aztec only)
});
// Linear: a retail EAN-13 (12 digits = check digit computed; 13 = check digit verified).
// Linear anchors may be any aspect; the box's width and height set the symbol size.
props.Barcodes.Add(new BarcodeOptions
{
Symbology = BarcodeSymbology.Ean13,
Data = System.Text.Encoding.ASCII.GetBytes("978020137962"),
AnchorId = "product-code",
});
Per-symbology knobs (QrEcLevel, Pdf417ErrorLevel, Pdf417Columns, AztecErrorPercent) are ignored
by the others, and QuietZoneModules defaults to each spec's minimum (QR 4, PDF417 6, Data Matrix 1,
Aztec 0, Code 128 / GS1-128 10, EAN-13 11, UPC-A 9, EAN-8 7). An entry with no Data is skipped, so
the output is unchanged. Linear symbols draw bars only: put the human-readable digits in the HTML
beneath the anchored element, so they use your document's font and styling.
IATA boarding passes (BCBP) — BcbpPass builds the IATA Resolution 792 payload string for a mobile
or paper boarding pass, including multi-leg itineraries, ready to hand to any symbology above (Aztec and
QR for mobile, PDF417 for paper):
using Bhowra.Ink.Barcodes;
var pass = new BcbpPass { PassengerName = "DESMARAIS/LUC" };
pass.Legs.Add(new BcbpLeg
{
Pnr = "ABC123", From = "YUL", To = "FRA", Carrier = "AC", FlightNumber = "0834",
FlightDate = new DateOnly(2026, 6, 28), Cabin = 'J', Seat = "003A", Sequence = "0012",
});
byte[] payload = pass.BuildBytes(); // ASCII BCBP bytes → BarcodeOptions.Data
The library builds the document and barcode layer; it does not issue, validate, or check-in the pass against a departure-control system — supply the itinerary data your platform already holds.
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.
Other commonly used options:
| Option | Default | Purpose |
|---|---|---|
PageScaling |
ShrinkToFit |
scale a too-wide layout down to the page; None lays out strictly at page width |
HeaderHtml / FooterHtml |
— | running header/footer HTML; {{page}} / {{pages}} are substituted |
OwnerPassword |
user password | full-permission password when UserPassword encrypts |
WatermarkOnTop / WatermarkFontSize |
false / auto |
paint the watermark over content; force its size in points |
Subject / Keywords / CreatedOn |
— / now | document info metadata |
PdfUA |
false |
PDF/UA-1: forces Tagged and writes the PDF/UA identification |
Language |
en-US |
catalog /Lang for tagged output |
XmpMetadata |
false |
write an XMP packet (implied by PdfA / PdfUA) |
ValidateContent |
false |
check every visible HTML word reached the PDF; result in ConversionResult.ContentValidation |
EnableCompression |
true |
Flate-compress content streams (disable only for debugging) |
External stylesheets (<link rel="stylesheet"> and @import)
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 |
StrictContentType |
false |
accept only text/css / text/plain responses (default still rejects text/html and known binaries) |
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 = true — only 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 fonts ship inside the DLL — Noto Sans Arabic, Hebrew, Thai, Devanagari, Symbols and Symbols 2, plus Roboto (Latin) — so common non-Latin scripts and symbols (✓ ✗ ★ → ✈) render the same on every host 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 license for clean output is available on request.
Activating a license — pick one:
// 1. In code, once at startup (online activation; the signed token is cached and re-verified offline):
HtmlConverter.Initialize("YOUR-LICENSE-KEY"); // or await HtmlConverter.InitializeAsync(...)
- App setting / environment variable
BHOWRA_LICENSE_KEY— activates automatically on first conversion, no code change (the documented Azure App Service / Functions path). - Offline token — for hosts that must make no outbound calls: set the two environment variables
issued with the token,
BHOWRA_INSTALL_IDandBHOWRA_LICENSE_JWT. Verified locally, no network.
Licenses are bound to an installation ID. Read it with HtmlConverter.GetInstallId() (also on every
ConversionResult.InstallId) and send it with your order; on Azure it is derived from the site identity,
so all scaled-out instances of one app share it. Only the activation call itself can throw
(LicenseActivationException); conversion never throws on license state.
See LICENSE.txt for the full agreement. Licensing & sales: sales@bhowra.com.
Architecture (in brief)
HtmlConverter → HtmlParser (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 / password | Not combinable — a caller watermark and form fields draw with non-embeddable standard-14 fonts, and PDF/A forbids encryption, so PDF/A throws a clear error. Plain text, registered fonts and the trial mark embed automatically, so unlicensed builds produce PDF/A too. |
| 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 page background position | A body/html gradient paints over every page, positioned against the page box rather than the root element's box (identical for a one-page 100vh body) |
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. The bundled Noto and Roboto fonts are licensed
under the SIL Open Font License 1.1 (see Fonts/Bundled/OFL.txt in the package) and remain governed by the OFL.
Links
- Product & docs: https://bhowra.com/bhowra-ink.html
- Licensing & sales: sales@bhowra.com
| Product | Versions 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. |
-
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.
1.0.11 - CSS 3D transforms, multi-column and footnote layout, typography and layout-correctness fixes.
NEW: CSS 3D transforms (perspective, rotateX/Y/Z, translateZ, matrix3d, transform-style: preserve-3d, backface-visibility) as vector output. Footnotes (float: footnote) reserve a footnote area, so body text no longer runs under the notes. Inline sub/sup and vertical-align (sub, super, lengths, percentages, middle, text-top, text-bottom) on text. font-weight matching by number (CSS Fonts 4), synthetic oblique, Times and Courier italics, bolder/lighter against the parent. text-align-last, line-clamp, text-orientation, inline-size/block-size and their min/max forms.
FIXES: Multi-column: decorated paragraphs and block boxes split across columns with box-decoration-break slice/clone, bare text and inline content flow across columns, column-gap: normal is 1em. Parent/child margin collapsing, auto table column distribution, float shrink-to-fit insets and max-height on auto-height blocks follow CSS 2.1. Tall lines in a paragraph that crosses a page no longer run past the page bottom. Bidi brackets are paired for the whole paragraph. Link annotations are no longer recorded twice for table cells.
BEHAVIOUR CHANGES vs 1.0.10 - re-render and compare existing documents before upgrading: margins now collapse through a parent and its first/last child; sub/sup text is raised and smaller; column-gap: normal is 1em instead of 11.25pt; footnote areas shrink the page's body area; font-weight 500 is no longer bold.
1.0.10 - CSS correctness and linear barcodes.
NEW: Linear barcodes on ConverterProperties.Barcodes - Code 128, GS1-128, EAN-13, EAN-8 and UPC-A, alongside QR Code, PDF417, Aztec and Data Matrix (bars only; put the human-readable digits in your HTML). CSS that was previously parsed and silently ignored now renders: border-collapse, border-spacing, empty-cells, caption-side, list-style-position and flex-flow, plus background-size, background-repeat, place-content, list-style-image, and logical inline properties (margin/padding/border-inline-*) that follow direction. CSS-wide keywords inherit / initial / unset. GPOS pair kerning and letter-/word-spacing for embedded fonts. SVG text elements are drawn through the HTML text path (shaping, bidi, font fallback, rotation). A gradient set on body/html now paints as the page background. display:inline-block works on block elements (div, section, p), so they sit side by side. Transforms resolve calc(), percentages and font-relative lengths. LayoutDiagnostics now reports an inconsistent table column structure.
FIXES: List markers are sized from the widest marker the list draws, so item 10 and beyond no longer overlap their text, and a marker is never stranded at the foot of a page without its item. Floats that do not fit move to the next page; fixed-height boxes end on the right page; a forced break after empty content is honoured; flex lines paginate. Opacity applies to an element as one transparency group. Grid template-less and fit-content track sizing.
BEHAVIOUR CHANGES vs 1.0.9 - re-render and compare existing documents before upgrading: (1) Tables default to border-collapse:separate with 2px border-spacing (the CSS 2.1 initial value; 1.0.9 defaulted to collapse) - declare border-collapse:collapse to keep the previous look. (2) Line baselines use the font's ascent/descent with half-leading (CSS 2.1 10.8.1), so text sits about 0.13em lower. (3) Automatic shrink-to-fit never scales a page below 1/1.5 (about 67%). (4) img is an atomic inline-level box. (5) body margin/padding/border form a real box that offsets the content, and the html background paints the page canvas; the default body margin stays 0. (6) display:inline-block on block elements now lays out inline.
Also shipped in 1.0.9 but not listed in its notes: multi-column pagination with column-width, column-fill and column-rule; named @page sizes and margins; ::marker styling; break-inside:avoid-page and break-after:avoid; box-shadow fixes for #RGBA colours, mm/cm/in lengths and rgba() alpha; a width:100% table no longer collapses after a fixed-width sibling.
Previous 1.0.x notes remain in git history.