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.
Print from a file path
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);
}
Print from a stream
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);
Print job options reference
| 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:
- Passthrough — the raw PDF bytes are sent directly to the printer. This is faster and preserves vector quality. Only attempted when
PreferPdfPassthrough = trueand the printer advertises PDF support. - Rasterized — each PDF page is rendered to a bitmap at
RasterDpiresolution 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);
}
Related topics
- Exporting reports — How to export LLCP reports to PDF before printing
- Debugging and troubleshooting — General LLCP diagnostics and logging guidance
- Deployment — Running LLCP in containers and cloud environments