Kanject.Core.Qr
A first-party QR Code (ISO/IEC 18004) encoder with styled SVG and PNG rendering — all 40 versions, multi-block Reed-Solomon, penalty-based masking. BCL-only and AOT-clean: no System.Drawing, no third-party packages, no native binaries in your Lambda bundle.
Install
dotnet add package Kanject.Core.Qr# Optional: compile-time [QrTemplate] / [QrSource] projectionsdotnet add package Kanject.Core.Qr.Annotations Register
using Kanject.Core.Qr.Extensions;// Registers IQrEncoder, IQrSvgRenderer, IQrPngRenderer, and// IQrScannabilityVerifier as singletons — via TryAdd, so registering// your own implementation first overrides any of them.builder.Services.AddQrCodes(); Encode and render
Encode auto-selects the densest mode the payload allows (numeric → alphanumeric → byte) and the smallest version that fits it. Styling — colours, gradients, rounded or dot modules, finder-eye styling, a centre logo — goes through QrRenderOptions, shared by both renderers:
public class TicketService(IQrEncoder encoder, IQrPngRenderer png){ public byte[] RenderTicketQr(Ticket ticket) { var matrix = encoder.Encode($"https://acme.io/t/{ticket.Code}", new QrEncodeOptions { ErrorCorrection = QrErrorCorrection.H }); return png.Render(matrix, new QrRenderOptions { Logo = new QrLogo { DataUri = acmeLogoPng }, VerifyBeforeReturn = true, // refuse to emit a dead ticket }); }} The output, live
This is not an illustration. The QR below is the actual SVG that QrSvgRenderer produced for this exact call — checked into the docs verbatim (regenerate it with scripts/gen-core-qr-figure):
var matrix = new QrEncoder().Encode( "https://www.kanject.com/docs/core-qr/", new QrEncodeOptions { ErrorCorrection = QrErrorCorrection.H });var svg = new QrSvgRenderer().Render(matrix, new QrRenderOptions{ ModuleShape = QrModuleShape.Rounded, DarkColor = "#221635", GradientColor = "#6d28d9", EyeStyle = new QrFinderEyeStyle { Color = "#6d28d9", Rounded = true }, VerifyBeforeReturn = true,}); Scannability, verified
Styling is where QR codes quietly die — a logo that eats too many codewords, grey-on-grey brand colours, an overlay across a finder pattern. The built-in verifier checks contrast, per-block logo error budgets, and reserved-pattern occlusion as pure arithmetic on the matrix. Set VerifyBeforeReturn = true (or use QrRenderOptions.VerifiedDefault) and the renderer throws a coded QrUnscannableException instead of returning a symbol that won't scan.
Compile-time payloads
With Kanject.Core.Qr.Annotations, mark a partial POCO with [QrTemplate] — each {PropertyName} placeholder binds to a public property, URL-escaped — or put [QrSource] on a single string property to use its value verbatim. The generator emits the payload and encode/render helpers; misuse is a compile-time KANQR diagnostic, not a runtime surprise:
[QrTemplate("https://acme.io/t/{Code}", Ecc = QrErrorCorrection.H)]public sealed partial class TicketQr{ public required string Code { get; init; }}// Generated: ToQrPayload(), ToQrMatrix(…), ToQrSvg(…), ToQrPng(…)var svg = new TicketQr { Code = "1042" }.ToQrSvg(encoder, svgRenderer); What ships with it
- Full ISO/IEC 18004 encoder — numeric / alphanumeric / byte modes, versions 1–40, error-correction levels L / M / Q / H
IQrSvgRenderer(SVG string) andIQrPngRenderer(PNG bytes) with one sharedQrRenderOptions- Logo overlays with per-Reed-Solomon-block error budgeting, gradients, module shapes, finder-eye styling
IQrScannabilityVerifier— contrast, logo budget, and reserved-pattern checks, no camera loop required[QrTemplate]/[QrSource]source generator — reflection-free, AOT-clean payload projectionsKANQR###diagnostic codes on every refusal: 001–049 compile-time, 050–099 runtime