using Microsoft.Extensions.Options;
using PrintService.Models;
using System.Drawing;
using System.Drawing.Printing;
namespace PrintService.Services;
///
/// Handles printing to Windows print queue with tray control
///
public class PrinterService
{
private readonly AppSettings _settings;
private readonly ILogger _logger;
public PrinterService(IOptions settings, ILogger logger)
{
_settings = settings.Value;
_logger = logger;
}
///
/// Print document using the new DocumentTypeConfig (printer-config.json).
/// Each PageConfig entry defines one output copy per original content page,
/// so a 4-page document with 4 PageConfig entries produces 16 output pages.
///
public void Print(string content, DocumentTypeConfig config, string originalFileName)
{
if (config.Pages.Count == 0)
{
throw new InvalidOperationException("No pages/trays defined for document type");
}
var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None);
var linesPerPage = CalculateLinesPerPage(config.FontSize);
var totalOriginalPages = Math.Max(1, (int)Math.Ceiling(lines.Length / (double)linesPerPage));
var originalPageIndex = 0;
var copyIndex = 0; // index into config.Pages (one entry per tray copy)
// All pages within one document type share the same printer in typical use,
// but PageConfig supports per-page overrides — use the first page's printer for
// the PrintDocument; tray (PaperSource) is set per page in the event handler.
var primaryPrinterName = config.Pages[0].PrinterName;
var printDoc = new PrintDocument
{
PrinterSettings = { PrinterName = primaryPrinterName }
};
// Handle PDF output if using Microsoft Print to PDF
if (primaryPrinterName.Equals("Microsoft Print to PDF", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(config.OutputPath))
{
Directory.CreateDirectory(config.OutputPath);
var pdfFileName = Path.GetFileNameWithoutExtension(originalFileName) + ".pdf";
var pdfPath = Path.Combine(config.OutputPath, pdfFileName);
printDoc.PrinterSettings.PrintToFile = true;
printDoc.PrinterSettings.PrintFileName = pdfPath;
_logger.LogInformation("PDF will be saved to: {Path}", pdfPath);
}
// Verify primary printer exists
if (!PrinterSettings.InstalledPrinters.Cast().Contains(primaryPrinterName))
{
throw new InvalidOperationException($"Printer '{primaryPrinterName}' not found");
}
printDoc.PrintPage += (sender, e) =>
{
if (e.Graphics == null || e.PageSettings == null)
return;
var pageConfig = config.Pages[copyIndex];
try
{
var paperSource = GetPaperSource(printDoc.PrinterSettings, pageConfig.TrayNumber);
if (paperSource != null)
{
e.PageSettings.PaperSource = paperSource;
if (_settings.VerboseLogging)
{
_logger.LogDebug(
"Output page {OutputPage}: original page {OriginalPage}, tray {Tray} ({Label}) ({Source})",
(originalPageIndex * config.Pages.Count) + copyIndex + 1,
originalPageIndex + 1,
pageConfig.TrayNumber,
pageConfig.TrayLabel ?? "?",
paperSource.SourceName);
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to set tray {Tray}, using default", pageConfig.TrayNumber);
}
// Render the same original page for every copy before moving to next original page.
RenderPage(e.Graphics, lines, originalPageIndex * linesPerPage, linesPerPage,
config.FontName, config.FontSize, config.HorizontalOffset, config.VerticalOffset);
copyIndex++;
if (copyIndex >= config.Pages.Count)
{
copyIndex = 0;
originalPageIndex++;
}
e.HasMorePages = originalPageIndex < totalOriginalPages;
};
_logger.LogInformation(
"Printing to {Printer}: {OriginalPages} original page(s) × {Copies} copies = {Total} output pages",
primaryPrinterName,
totalOriginalPages,
config.Pages.Count,
totalOriginalPages * config.Pages.Count);
printDoc.Print();
}
///
/// Render page content using Graphics API
///
private void RenderPage(Graphics graphics, string[] lines, int startLine, int linesPerPage,
string fontName, float fontSize, int horizontalOffset, int verticalOffset)
{
var font = new Font(fontName, fontSize);
var brush = Brushes.Black;
var lineHeight = font.GetHeight(graphics);
var x = (float)horizontalOffset;
var y = (float)verticalOffset;
var endLine = Math.Min(startLine + linesPerPage, lines.Length);
for (int i = startLine; i < endLine; i++)
{
if (i < lines.Length)
{
graphics.DrawString(lines[i], font, brush, x, y);
y += lineHeight;
}
}
}
///
/// Calculate lines per page based on font size
///
private int CalculateLinesPerPage(float fontSize)
{
// Estimate: standard letter size is 11 inches, at 10pt font ~66 lines
var estimatedLineHeight = fontSize * 1.2f; // points
var pageHeightInPoints = 11 * 72; // 11 inches * 72 points per inch
return (int)Math.Floor(pageHeightInPoints / estimatedLineHeight);
}
///
/// Get PaperSource by logical tray number
///
private PaperSource? GetPaperSource(PrinterSettings printerSettings, int trayNumber)
{
// Try to find by RawKind (tray number)
foreach (PaperSource source in printerSettings.PaperSources)
{
// Some printers use RawKind directly as tray number
if (source.RawKind == trayNumber ||
source.RawKind == (trayNumber + 256)) // Some drivers offset by 256
{
return source;
}
}
// Fallback: use by index if available
if (trayNumber > 0 && trayNumber <= printerSettings.PaperSources.Count)
{
return printerSettings.PaperSources[trayNumber - 1];
}
_logger.LogWarning("Could not map tray {Tray} to PaperSource", trayNumber);
return null;
}
///
/// List available paper sources for a printer (for debugging/configuration)
///
public void ListPaperSources(string printerName)
{
var printDoc = new PrintDocument { PrinterSettings = { PrinterName = printerName } };
_logger.LogInformation("Available paper sources for {Printer}:", printerName);
foreach (PaperSource source in printDoc.PrinterSettings.PaperSources)
{
_logger.LogInformation(" - {Name} (RawKind: {Kind})", source.SourceName, source.RawKind);
}
}
}