Table of Contents

Printing to a physical printer

The combit.ListLabel31.CrossPlatform.Printing.Pdf package extends List & Label Cross Platform with the ability to send PDF reports directly to a physical printer — on Windows, Linux, and macOS — without requiring a graphical desktop environment.

This is distinct from the export functionality built into LLCP. The printing assembly takes a PDF file (or stream) produced by LLCP and delivers it to a named printer using platform-appropriate mechanisms.


When to use this package

Use the printing assembly when your server-side application needs to:

  • Submit reports to a physical or network printer automatically
  • Integrate print dispatching into a background service or batch workflow
  • Print LLCP-generated PDF documents without a desktop UI
Note

This assembly handles delivery to a printer. Report generation (export to PDF) is handled separately using the core LLCP package. A typical workflow is: export to PDF with LLCP, then pass the result to PdfPrintService.

Note

This assembly does not provide interactive print dialogs or preview windows. It is designed for headless, automated printing in server-side environments.


Installation

Add the printing package to your project via NuGet:

dotnet add package combit.ListLabel31.CrossPlatform.Printing.Pdf

This package targets net8.0 and net10.0 and depends on PDFium for page rasterization.


Discovering available printers

Use PrinterDiscoveryService to enumerate printers and query their capabilities before submitting a print job.

List all printers

using combit.Reporting.Printing.Pdf;

using var discovery = new PrinterDiscoveryService();
IReadOnlyList<PrinterInfo> printers = await discovery.GetPrintersAsync();

foreach (PrinterInfo printer in printers)
{
    Console.WriteLine($"{printer.Name} (default: {printer.IsDefault}, available: {printer.IsAvailable})");
}

Look up a specific printer

PrinterInfo? printer = await discovery.GetPrinterAsync("Office Printer");
if (printer is null)
{
    Console.Error.WriteLine("Printer not found.");
    return;
}

Query printer capabilities

PrinterCapabilities? caps = await discovery.GetCapabilitiesAsync("Office Printer");
if (caps is not null)
{
    Console.WriteLine($"Supports PDF passthrough: {caps.SupportsPdfPassthrough}");
    Console.WriteLine($"Supports duplex:          {caps.SupportsDuplex}");
    Console.WriteLine($"Supports color:           {caps.SupportsColor}");
    Console.WriteLine($"Paper sizes: {string.Join(", ", caps.SupportedPaperSizes)}");
}

PrinterCapabilities exposes the set of media types and paper sizes the printer accepts, along with flags for duplex support, color output, and PDF passthrough support.


Printing a PDF report

PdfPrintService accepts a PDF file path or stream and submits it to the specified printer.

using combit.Reporting.Printing.Pdf;

using var printService = new PdfPrintService();

PrintResult result = await printService.PrintAsync(
    pdfFilePath: "output/report.pdf",
    printerName: "Office Printer");

if (!result.Success)
{
    Console.Error.WriteLine($"Print failed: {result.ErrorMessage}");
    Console.Error.WriteLine(result.DiagnosticLog);
}

Use the stream overload to print directly from an in-memory or piped PDF without writing a temporary file to disk:

using combit.Reporting.Printing.Pdf;

await using Stream pdfStream = File.OpenRead("output/report.pdf");

using var printService = new PdfPrintService();

PrintResult result = await printService.PrintAsync(
    pdfStream: pdfStream,
    printerName: "Office Printer");

Combining export and print

A typical end-to-end workflow exports a report to PDF using LLCP, then prints the result:

using combit.Reporting;
using combit.Reporting.Printing.Pdf;

// Step 1: export the report to PDF
string pdfPath = Path.GetTempFileName() + ".pdf";

ListLabel listLabel = new ListLabel
{
    DataSource = GetDataSource(),
    AutoProjectFile = "reports/invoice.json",
    LicensingInfo = "..."
};

ExportConfiguration export = new ExportConfiguration(LlExportTarget.Pdf, pdfPath, "reports/invoice.json");
listLabel.Export(export);

// Step 2: print the exported PDF
using var printService = new PdfPrintService();

PrintResult result = await printService.PrintAsync(pdfPath, printerName: "Office Printer");

if (result.Success)
{
    Console.WriteLine($"Printed using {result.MethodUsed}.");
}
else
{
    Console.Error.WriteLine($"Print failed: {result.ErrorMessage}");
}

Configuring print job options

Pass a PrintJobOptions instance to control how the document is printed.

using combit.Reporting.Printing.Pdf;

var options = new PrintJobOptions
{
    Copies          = 2,
    Duplex          = DuplexMode.LongEdge,
    PaperSize       = "A4",
    ScalingMode     = PrintScalingMode.FitToPage,
    RasterDpi       = 300,
    JobName         = "Monthly Invoice",
    PreferPdfPassthrough = true
};

using var printService = new PdfPrintService();
PrintResult result = await printService.PrintAsync("output/report.pdf", "Office Printer", options);
Property Default Description
Copies 1 Number of copies to print. Must be at least 1.
Duplex None Duplex mode: None, LongEdge (portrait binding), or ShortEdge (landscape binding).
PaperSize null Paper size name (for example, "A4" or "Letter"). When null, no paper size override is applied and the printer selects the closest supported paper size automatically.
ScalingMode FitToPage Whether to scale pages to fit the paper (FitToPage) or print at actual size (ActualSize).
RasterDpi 300 Resolution used when rasterizing PDF pages. Minimum value is 72.
JobName null Job name visible in the print spooler. Derived from the file name when null.
PreferPdfPassthrough true Whether to attempt sending raw PDF data directly (passthrough) before falling back to rasterization.

Understanding print results

PrintResult describes the outcome of a print operation.

Property Description
Success true when the document was delivered to the printer.
MethodUsed The PrintMethod used: Passthrough, Rasterized, or None.
ErrorMessage A human-readable error description when Success is false.
DiagnosticLog Step-by-step log of the print pipeline, useful for troubleshooting.

Passthrough vs. rasterized printing

PdfPrintService automatically selects the best delivery method:

  1. Passthrough — the raw PDF bytes are sent directly to the printer. This is faster and preserves vector quality. Only attempted when PreferPdfPassthrough = true and the printer advertises PDF support.
  2. Rasterized — each PDF page is rendered to a bitmap at RasterDpi resolution and then sent to the printer. This works universally but is slower and uses more memory for high DPI values.

When passthrough is attempted but fails, the service automatically retries using rasterization. In this case, result.Success is still true but result.MethodUsed is Rasterized and result.DiagnosticLog records the fallback path.

Tip

Check result.DiagnosticLog whenever MethodUsed is Rasterized but you expected passthrough. The log records the exact reason passthrough was skipped or failed.


Logging

Pass a logger to PdfPrintService or PrinterDiscoveryService to capture structured diagnostics:

using Microsoft.Extensions.Logging;
using combit.Reporting.Printing.Pdf;

using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
    builder.AddConsole().SetMinimumLevel(LogLevel.Debug));

ILogger<PdfPrintService> logger = loggerFactory.CreateLogger<PdfPrintService>();

using var printService = new PdfPrintService(logger);

When no logger is supplied, logging is disabled. For production scenarios, supplying a logger is recommended to capture print pipeline diagnostics for troubleshooting.


Platform support

The printing assembly supports Windows, Linux, and macOS. Platform-specific behavior:

Platform Passthrough mechanism Rasterized mechanism
Windows Win32 Print Spooler (winspool.drv) GDI printer device context (StretchDIBits)
Linux IPP over HTTP (application/pdf) IPP over HTTP (PWG Raster)
macOS lpr command lpr with rasterized multi-page PDF
Tip

On Linux, printing uses the Internet Printing Protocol (IPP) directly over HTTP. The printer must be reachable via its IPP URI and must accept jobs sent over the network. Network-attached printers accessible via CUPS are the typical target.

Tip

On macOS, the lpr utility must be available on the system path. Printers must be configured in the macOS print system (CUPS) before they can be used.

Important

Printing across different operating systems, printer models, and driver implementations is inherently complex and may exhibit inconsistent behavior.

While the printing assembly aims to provide reliable cross-platform output, it ultimately depends on the capabilities and correctness of the underlying print subsystem, drivers, and device firmware. Variations in these components can lead to differences in rendering, layout, color reproduction, or job handling.

As a result, printing functionality is provided “as is” without guarantees that all documents will produce identical or expected results in every environment. Thorough testing with the target printers, drivers, and operating systems is strongly recommended, especially for production scenarios.


Complete example

The following example discovers the default printer, exports a report to PDF, and prints it with duplex enabled:

using combit.Reporting;
using combit.Reporting.Printing.Pdf;
using Microsoft.Extensions.Logging;

// Set up logging
using ILoggerFactory loggerFactory = LoggerFactory.Create(b =>
    b.AddConsole().SetMinimumLevel(LogLevel.Information));

// Discover printers and find the default
using var discovery = new PrinterDiscoveryService(loggerFactory.CreateLogger<PrinterDiscoveryService>());
IReadOnlyList<PrinterInfo> printers = await discovery.GetPrintersAsync();
PrinterInfo? defaultPrinter = printers.FirstOrDefault(p => p.IsDefault);

if (defaultPrinter is null)
{
    Console.Error.WriteLine("No default printer found.");
    return;
}

Console.WriteLine($"Printing to: {defaultPrinter.Name}");

// Export the report to PDF
string pdfPath = Path.Combine(Path.GetTempPath(), "report.pdf");

ListLabel listLabel = new ListLabel
{
    DataSource = GetDataSource(),
    AutoProjectFile = "reports/invoice.json",
    LicensingInfo = "..."
};

ExportConfiguration export = new ExportConfiguration(LlExportTarget.Pdf, pdfPath, "reports/invoice.json");
listLabel.Export(export);

// Print with duplex on long edge
var options = new PrintJobOptions
{
    Duplex      = DuplexMode.LongEdge,
    ScalingMode = PrintScalingMode.FitToPage,
    JobName     = "Invoice Report"
};

using var printService = new PdfPrintService(loggerFactory.CreateLogger<PdfPrintService>());
PrintResult result = await printService.PrintAsync(pdfPath, defaultPrinter.Name, options);

if (result.Success)
{
    Console.WriteLine($"Print job submitted. Method used: {result.MethodUsed}.");
}
else
{
    Console.Error.WriteLine($"Print failed: {result.ErrorMessage}");
    Console.Error.WriteLine(result.DiagnosticLog);
}