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); } } }