Barcodes
Egami.Barcodes writes and reads barcodes. It draws them through Egami.Drawing's canvas, so a barcode stays vector on a PDF page or in SVG output, and reads them from rasters. It is fully managed.
It writes and reads QR Code, Data Matrix, PDF417 and Aztec Code, and the linear Code 128 (with GS1-128), Code 39, ITF and
ITF-14, EAN-13, EAN-8, UPC-A and UPC-E. Each has its own factory on Barcode with typed options; Barcode.Create takes a
format chosen at run time with the default options.
// Labels from a table that stores each barcode's format with its data.
(BarcodeFormat Format, string Data)[] rows =
[
(BarcodeFormat.Ean13, "590123412345"),
(BarcodeFormat.Code128, "SHIP-0042"),
(BarcodeFormat.Qr, "https://inexin.github.io/Egami/"),
];
foreach ((BarcodeFormat format, string data) in rows)
{
Barcode barcode = Barcode.Create(format, data);
using Raster<Gray8> image = barcode.ToRaster(pixelsPerModule: 3);
}
QR Code
Barcode.Qr writes a QR Code from text, bytes or GS1 element strings. Text is split into numeric, alphanumeric, byte and
Kanji segments that give the fewest bits. Text in ISO 8859-1 is written as it is, text that Shift JIS holds is written in
Shift JIS (its kanji in Kanji mode), and anything else as UTF-8 with an ECI that tells readers so.
By default the symbol is the smallest that holds the data at level M, and the level is then raised to Q or H while the
symbol stays the same size, so spare room goes to error correction. QrOptions fixes the level, the version (1 to 40) or the
mask instead.
// Level M, raised to Q or H when that fits in the same size; segments chosen for the fewest bits.
Barcode link = Barcode.Qr("https://inexin.github.io/Egami/");
// A fixed size and level, for a layout that must not change.
Barcode fixedSize = Barcode.Qr("ORDER-1180042", new QrOptions { Version = 3, ErrorCorrection = QrErrorCorrection.H });
// Bytes as they are, and GS1 element strings with FNC1.
Barcode bytes = Barcode.Qr([0x45, 0x67, 0x61, 0x6D, 0x69]);
Barcode gs1 = Barcode.Qr(Gs1.Parse("(01)09501101530003(10)AB12"));
using Raster<Gray8> image = link.ToRaster(pixelsPerModule: 4);
Data Matrix
Barcode.DataMatrix writes Data Matrix ECC 200 from text, bytes or GS1 element strings. The six encodation modes (ASCII,
C40, Text, X12, EDIFACT and Base 256) are switched for the fewest codewords, and the symbol is the smallest that holds them.
Text in ISO 8859-1 is written as it is, anything else as UTF-8 with an ECI.
Symbols are square by default. DataMatrixOptions.Shape allows rectangular symbols, or the smallest of either, and
AllowDmre adds the rectangular sizes of ISO/IEC 21471 (DMRE), which older readers may not read.
// Square by default; the encodation modes are switched for the fewest codewords.
Barcode part = Barcode.DataMatrix("PN 4711-0815 / LOT 2026-10");
// A narrow label: rectangular sizes, the DMRE ones too.
Barcode narrow = Barcode.DataMatrix("PN 4711-0815 / LOT 2026-10", new DataMatrixOptions
{
Shape = DataMatrixShape.Rectangle,
AllowDmre = true,
});
// GS1 Data Matrix, as on medicine packs.
Barcode pack = Barcode.DataMatrix(Gs1.Parse("(01)09501101530003(17)261231(10)AB12(21)XYZ"));
PDF417
Barcode.Pdf417 writes PDF417 from text or bytes, as on boarding passes and driving licences. Text, numeric and byte
compaction are chosen run by run. The error correction level follows the standard's recommended minimum for the amount of
data, and the columns are picked so the symbol is about three times as wide as tall; Pdf417Options sets either, or asks for
compact PDF417, which leaves out the right row indicators for a narrower symbol. Rows are 3 modules tall.
// About three times as wide as tall, error level from the standard's table for the amount of data.
Barcode ticket = Barcode.Pdf417("PASSENGER DOE/JANE FLIGHT EG1234 SEAT 12A GATE B17");
// Six data columns and error level 5, or the compact form for a narrow space.
Barcode wide = Barcode.Pdf417("PASSENGER DOE/JANE", new Pdf417Options { Columns = 6, ErrorLevel = 5 });
Barcode compact = Barcode.Pdf417("PASSENGER DOE/JANE", new Pdf417Options { Compact = true });
Aztec Code
Barcode.Aztec writes Aztec Code from text or bytes, as on rail and airline tickets. The text is written in the Upper,
Lower, Mixed, Punctuation and Digit modes and Binary Shift, switched for the fewest bits. The symbol is the smallest compact
(up to 4 layers) or full-range (up to 32 layers) one that leaves at least 23 % of its codewords, plus 3, for error correction;
AztecOptions raises that share or fixes the form and layers. Aztec Code finds itself by its bullseye, so it needs no quiet
zone. Barcode.AztecRune makes an Aztec Rune, an 11 x 11 core that holds one value from 0 to 255.
// The smallest compact or full-range symbol with at least 23 % error correction; no quiet zone needed.
Barcode ticket = Barcode.Aztec("EG1234 BER-MUC 2026-10-19 SEAT 12A DOE/JANE");
// More error correction for a ticket that gets folded, or a fixed compact size.
Barcode robust = Barcode.Aztec("EG1234 BER-MUC", new AztecOptions { ErrorCorrectionPercent = 50 });
Barcode compact = Barcode.Aztec("EG1234", new AztecOptions { Compact = true, Layers = 2 });
// A rune: one value from 0 to 255 in an 11 x 11 core.
Barcode rune = Barcode.AztecRune(42);
Linear barcodes
Code 128
Barcode.Code128 writes any text of characters 0 to 255. It picks the code sets A, B and C, and the changes and shifts
between them, that give the shortest symbol, so runs of digits go in pairs. Code128Options.Height sets the bar height in
modules when the barcode is drawn at a module size; drawn into a rectangle, the bars fill it.
// The shortest mix of code sets A, B and C; the digits go in pairs.
Barcode code = Barcode.Code128("ORDER-1180042", new Code128Options { Height = 40 });
using Raster<Gray8> image = code.ToRaster(pixelsPerModule: 2);
GS1-128 and GS1 element strings
Gs1.Parse reads GS1 element strings written with each Application Identifier in parentheses, such as
(01)09501101530003(17)261231, and checks each one against the GS1 General Specifications: the AI is defined, the
characters and lengths fit it, and check digits, dates and times are right. A bad field throws a FormatException that says
which AI is wrong and how; Gs1.TryParse returns false instead. Each Gs1ElementString has its Ai, Value and GS1's
Title for the AI. A parenthesis inside a value is written \(.
Barcode.Gs1Code128 encodes element strings as Code 128 with FNC1 first, and an FNC1 after each field whose AI has no
predefined length, unless it is the last.
// Each element string is checked: the AI, the length, the GTIN's check digit, the date.
IReadOnlyList<Gs1ElementString> elements = Gs1.Parse("(01)09501101530003(17)261231(10)AB12");
Barcode label = Barcode.Gs1Code128(elements);
// Mistakes say what is wrong.
if (!Gs1.TryParse("(01)09501101530004", out _))
{
Console.WriteLine("(01) has a wrong check digit");
}
The checks come from the GS1 Barcode Syntax Dictionary. Checks that need outside lists (country and currency codes, company prefixes) and the rules about which AIs go together are not applied.
Code 39
Barcode.Code39 writes digits, capital letters, space and - . $ / + %. Text with other ASCII characters, such as
lowercase letters, is written as Full ASCII, which spells them as pairs; Code39Options.FullAscii forces it on or off. A
reader has to be set to Full ASCII to read such text back. Code39Options.CheckDigit adds the mod 43 check character, and
WideRatio makes wide elements 2 (the default) or 3 times as wide as narrow ones.
// Capitals, digits and a few symbols; lowercase switches to Full ASCII on its own.
Barcode part = Barcode.Code39("PART-0042", new Code39Options { CheckDigit = true });
Console.WriteLine(part.HumanReadableText); // PART-0042 and its mod 43 check character
ITF and ITF-14
Barcode.Itf writes Interleaved 2 of 5 from digits; an odd number of digits gets a leading zero. Barcode.Itf14 writes a
GTIN-14 for cartons: 13 digits get their check digit added, 14 are checked, and bearer bars 5 modules thick frame the symbol
and its quiet zone. ItfOptions sets the wide ratio (3 by default), the bearer bars and the height.
// A GTIN-14 on a carton: the check digit is added, and bearer bars frame the symbol.
Barcode carton = Barcode.Itf14("1540014128876");
using Raster<Gray8> image = carton.ToRaster(pixelsPerModule: 3);
// Plain Interleaved 2 of 5 with a 2:1 wide ratio and no bearers.
Barcode itf = Barcode.Itf("0123456789", new ItfOptions { WideRatio = 2 });
EAN and UPC
Barcode.Ean13, Barcode.Ean8, Barcode.UpcA and Barcode.UpcE write retail barcodes. The check digit is added when it is
left out and checked when it is given; a wrong one throws. Barcode.UpcE also takes a UPC-A number and writes its
zero-suppressed form, or throws when the number has too few zeros. EanUpcOptions.AddOn adds a 2-digit or 5-digit add-on to
the right, such as a magazine's issue or a book's price.
With a font, the digits are drawn as on a product: EAN-13's first digit left of the symbol and the others between the guard bars, which reach down into the text line, and an add-on's digits above its bars. Bar heights default to the standards' nominal heights.
// A book: the ISBN as EAN-13, its check digit added, and the price as an EAN-5 add-on.
Barcode book = Barcode.Ean13("978020137962", new EanUpcOptions { AddOn = "52495" });
// Fitted into a rectangle, with the digits between the guard bars.
using Raster<Rgba8> label = Raster.Create(400, 200, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = label.CreateCanvas())
{
canvas.DrawBarcode(book, new RectF(10, 10, 380, 180), Rgba8.Black, new BarcodeDrawOptions { Font = font });
}
// A UPC-A number written in its short UPC-E form.
Barcode small = Barcode.UpcE("042100005264");
Reading barcodes
raster.ReadBarcodes() finds every barcode in an image and raster.ReadBarcode() the first. Reading barcodes
covers the options, photos, camera frames, GS1 data and speed.
// Every barcode in the image, of any format.
foreach (BarcodeResult result in scan.ReadBarcodes())
{
Console.WriteLine($"{result.Format}: {result.Text} at {result.Corners[0]}");
}
Drawing on a canvas
DrawBarcode, an extension method in CanvasBarcodeExtensions, draws on any canvas: a raster, a PDF or SVG page, a
recording. The whole symbol is one filled path, a rectangle for each run of dark modules in a row, so a QR code is a few
hundred rectangles rather than one per module, and neighbouring rectangles leave no seams.
Given a rectangle, the barcode is fitted into it and centered, with its quiet zone kept clear inside it. 2D symbols keep square modules; linear bars fill the rectangle's height. On a raster canvas with no rotation or skew, the module size snaps to whole pixels, the largest that still fits, so every edge is sharp.
using Raster<Rgba8> label = Raster.Create(300, 200, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = label.CreateCanvas())
{
// Fitted into the rectangle and centered, quiet zone inside it; on a raster, modules snap to whole pixels.
canvas.DrawBarcode(qr, new RectF(20, 20, 160, 160), Rgba8.Black);
}
Given a point and a module size instead, the barcode is drawn at that size, the X dimension of the standards, with the top-left
corner of its quiet zone at the point. Linear bars are then BarcodeModules.Height modules tall.
BarcodeDrawOptions changes how it is drawn:
QuietZonekeeps the quiet zone clear; it is on by default. Turn it off only when the space around the symbol is blank anyway.BarWidthReductiondraws each dark bar or module narrower by that much, half from each side, to make up for ink spreading on a printer. It changes drawing only, never the modules.Fontdraws the human-readable text under the bars, centered, in that font and size. Without a font no text is drawn; Egami.Barcodes ships no font of its own.
// A 0.33 mm module (the X dimension) in points, with the text under the bars and 0.05 mm taken off each bar
// for ink spread. The quiet zone's top-left corner goes at (36, 36).
float module = 0.33f / 25.4f * 72;
page.DrawBarcode(code, new Vector2(36, 36), module, Rgba8.Black, new BarcodeDrawOptions
{
Font = font,
BarWidthReduction = 0.05f / 25.4f * 72,
});
Paths and rasters
ToPath returns the symbol as one path, with the quiet zone's top-left corner at the origin, ready for Path.ToSvg or your own
drawing. It holds no text.
// One path, a rectangle per run of dark modules; the quiet zone's corner is at the origin.
Path path = qr.ToPath(moduleSize: 4);
string svg = path.ToSvg();
ToRaster renders black on white into a Raster<Gray8>, each module a square of whole pixels, with the quiet zone and, with a
font, the text.
// Black on white, 4 pixels a module, quiet zone included.
using Raster<Gray8> image = qr.ToRaster(pixelsPerModule: 4);
Barcodes from modules
A Barcode is one symbol: its BarcodeFormat, its Modules, the QuietZone its standard asks for in modules (4 for QR, 1 for
Data Matrix, 2 for PDF417, none for Aztec; at each end of a linear symbol, 10 for Code 128, Code 39 and ITF, 11 for EAN-13, 7
for EAN-8 and 9 for UPC) and the HumanReadableText printed under a linear symbol. Barcode.FromModules makes one from
modules you already have, such as from another encoder.
// Modules from another encoder become a barcode with the quiet zone of their standard: 4 modules for QR.
Barcode qr = Barcode.FromModules(BarcodeFormat.Qr, modules);
Console.WriteLine($"{qr.Format}: {qr.Modules.Width} modules plus {qr.QuietZone} on each side");
Modules
Every symbol is a BarcodeModules: a grid of dark and light modules for a 2D code, or a row of bars and spaces for a linear
one. Width and Height are in modules, the indexer says whether a module is dark, and a linear symbol also lists its bar and
space widths in Runs. A grid holds the symbol only, without its quiet zone.
BarcodeModules.Create and BarcodeModules.CreateLinear make one from modules you already have, for example from another
encoder.
// A 2D grid, row by row from the top; true is a dark module.
BarcodeModules grid = BarcodeModules.Create(3, 3,
[
true, false, true,
false, true, false,
true, false, true,
]);
// A linear symbol: bar and space widths in modules, starting and ending with a bar.
BarcodeModules bars = BarcodeModules.CreateLinear([2, 1, 1, 1, 3], height: 20);
for (int y = 0; y < grid.Height; y++)
{
for (int x = 0; x < grid.Width; x++)
{
Console.Write(grid[x, y] ? '#' : '.');
}
Console.WriteLine();
}
Console.WriteLine($"{bars.Width} modules wide, bars {string.Join(' ', bars.Runs.ToArray())}");
Recipes
Short answers to common tasks in this area. Each one compiles and runs in Egami's test run.
Put a QR code on a label
// A 600 x 300 label: a QR code on the left, the address beside it.
using Raster<Rgba8> label = Raster.Create(600, 300, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = label.CreateCanvas())
{
canvas.DrawBarcode(Barcode.Qr("https://example.com/track/4711"), new RectF(20, 20, 260, 260), Rgba8.Black);
canvas.DrawText("Parcel 4711", new Vector2(300, 80), font, Rgba8.Black);
canvas.DrawText("Track at example.com", new Vector2(300, 120), font, Rgba8.Black);
}
On a raster, the QR code's modules snap to whole pixels, so its edges stay sharp at any size.
Save a barcode as SVG
// The symbol as one path, 4 units a module, drawn on Egami.Svg's canvas over a white quiet zone.
Barcode barcode = Barcode.DataMatrix("PN 4711-0815");
Path path = barcode.ToPath(moduleSize: 4);
float width = (barcode.Modules.Width + (2 * barcode.QuietZone)) * 4, height = (barcode.Modules.Height + (2 * barcode.QuietZone)) * 4;
using (FileStream stream = File.Create(file))
using (var canvas = new Egami.Svg.SvgCanvas(stream, width, height))
{
canvas.FillRect(new RectF(0, 0, width, height), Rgba8.White);
canvas.FillPath(path, Rgba8.Black);
}
Barcode.ToPath gives the whole symbol as one path, so any canvas can take it; Egami.Svg's SvgCanvas writes it as one SVG
<path>. Egami.Barcodes itself does not depend on Egami.Svg.
Print a GS1 shipping label
// A 4 x 6 inch label in points: an SSCC as GS1-128 at 0.5 mm a module with its text, and a GS1 Data Matrix.
float module = 0.5f / 25.4f * 72;
Barcode sscc = Barcode.Gs1Code128("(00)106141411234567897");
page.DrawBarcode(sscc, new Vector2(18, 300), module, Rgba8.Black, new BarcodeDrawOptions { Font = font });
page.DrawBarcode(Barcode.DataMatrix(Gs1.Parse("(01)09501101530003(10)AB12")), new RectF(18, 18, 100, 100), Rgba8.Black);
At a module size in points, the barcode has the X dimension a scanner expects, on a PDF page or any other canvas.