PDFsharp provides native support for embedding vector-based QR codes in PDF documents. However, its built-in barcode format support remains limited, lacking compatibility with commonly used standards like Code 93 and various 2D matrix codes.
ZXing offers extensive barcode generation capabilities but does not provide direct methods for outputting vecter graphics that integrate seamlessly with PDFsharp's rendering pipeline.
The following extension bridges this gap by converting ZXing's bitmap output into PDFsharp-compatible vector paths.
Implementation
using PdfSharp.Drawing;
using PdfSharp.Pdf;
using ZXing;
using ZXing.Common;
using Extensions;
var document = new PdfDocument();
var page = document.AddPage();
var ctx = XGraphics.FromPdfPage(page);
var generator = new BarcodeWriterGeneric
{
Format = BarcodeFormat.CODE_93,
Options = new EncodingOptions { Height = 80, Width = 200 }
};
var matrix = generator.Encode("24341873");
ctx.RenderBarcode(matrix, new XPoint(100, 100));
document.Save("output.pdf");
document.Close();
Extension Module: XGraphicsExtensions.cs
using PdfSharp.Drawing;
using ZXing.Common;
namespace Extensions
{
public static class XGraphicsExtensions
{
public static void RenderBarcode(this XGraphics ctx, BitMatrix data, XPoint origin)
{
if (data == null)
return;
int cols = data.Width;
int rows = data.Height;
var visited = new BitMatrix(cols, rows);
bool inBlock = false;
int blockStartX = 0;
int blockStartY = 0;
for (int col = 0; col < cols; col++)
{
int blockEndX;
for (int row = 0; row < rows; row++)
{
if (visited[col, row])
continue;
visited[col, row] = true;
if (data[col, row])
{
if (!inBlock)
{
blockStartX = col;
blockStartY = row;
inBlock = true;
}
}
else
{
if (inBlock)
{
ExtendBlock(data, visited, blockStartX, blockStartY, row, out blockEndX);
var region = new XRect(
blockStartX + origin.X,
blockStartY + origin.Y,
blockEndX - blockStartX + 1,
row - blockStartY);
ctx.DrawRectangle(XBrushes.Black, region);
inBlock = false;
}
}
}
if (inBlock)
{
ExtendBlock(data, visited, blockStartX, blockStartY, rows, out blockEndX);
var region = new XRect(
blockStartX + origin.X,
blockStartY + origin.Y,
blockEndX - blockStartX + 1,
rows - blockStartY);
ctx.DrawRectangle(XBrushes.Black, region);
inBlock = false;
}
}
}
private static void ExtendBlock(
BitMatrix data,
BitMatrix processed,
int startCol,
int startRow,
int endRow,
out int endCol)
{
endCol = startCol;
for (int col = startCol + 1; col < data.Width; col++)
{
for (int row = startRow; row < endRow; row++)
{
if (!data[col, row])
return;
}
endCol = col;
for (int row = startRow; row < endRow; row++)
{
processed[col, row] = true;
}
}
}
}
}
How It Works
The extension processses the bit matrix row by row, identifying consecutive black pixels. Instead of rendering individual pixels—which would produce rasterized output—it calculates maximum rectangular regions of contiguous black pixels and draws them as filled rectangles. This approach ensures the output remains in vector format while minimizing the total number of drawing operations.
The algorithm tracks visited pixels to prevent redundant rendering and progressively expands horizontal blocks until no further extension is posible within the current row segment.