using GB5Shared.Export.NativePdf; using GB5Shared.QRCode; using PdfSharpCore.Drawing; using PdfSharpCore.Fonts; using PdfSharpCore.Pdf; namespace GB5Shared.ScanIdentifier { // One PDF page per selected code, page-sized exactly to the chosen label's real physical // dimensions (MLABELTEMPLATE) — not a report layout, so this is its own small renderer // rather than reusing GB5Shared/Export/NativePdf/NativePdfRenderer.cs (built for tabular // reports). Reuses that same module's LatoFontResolver/PdfSharpCore stack (the actual live // PDF pipeline in this codebase — see NativePdfRenderer's own doc comment) rather than // pulling in a second PDF library. LabelsPerSheet is metadata only here — WidthMm/HeightMm // in MLABELTEMPLATE are already per-label, so each PDF page is exactly one physical label // regardless of how many sit on one physical sheet/roll. public static class ScanLabelPdfRenderer { private const double MmToPoints = 72.0 / 25.4; // Global, process-wide PdfSharpCore state — GlobalFontSettings.FontResolver can only be // set once per process ("must not change font resolver after it was once used"), so this // checks the actual shared property rather than a private flag, to coexist correctly with // NativePdfRenderer's own registration regardless of which one runs first. private static readonly object FontResolverLock = new(); private static void EnsureFontResolverRegistered() { if (GlobalFontSettings.FontResolver is not null) return; lock (FontResolverLock) { GlobalFontSettings.FontResolver ??= new LatoFontResolver(); } } public static byte[] RenderBatch( IReadOnlyList<(GeneratedScanIdentifierDTO Identifier, string DisplayCode, string DisplayName)> rows, LabelTemplateDTO template) { EnsureFontResolverRegistered(); var widthPt = (double)template.WidthMm * MmToPoints; var heightPt = (double)template.HeightMm * MmToPoints; var margin = Math.Min(widthPt, heightPt) * 0.08; var qrSize = Math.Min(heightPt - margin * 2, widthPt * 0.4); using var doc = new PdfDocument(); var titleFont = new XFont("Lato", Math.Max(6, heightPt * 0.14), XFontStyle.Bold); var bodyFont = new XFont("Lato", Math.Max(5, heightPt * 0.10), XFontStyle.Regular); foreach (var (identifier, displayCode, displayName) in rows) { var page = doc.AddPage(); page.Width = XUnit.FromPoint(widthPt); page.Height = XUnit.FromPoint(heightPt); using var gfx = XGraphics.FromPdfPage(page); var qrBytes = QrCodeGenerator.GenerateQrPng(identifier.IdentifierValue, 10); using var qrImage = XImage.FromStream(() => new MemoryStream(qrBytes)); gfx.DrawImage(qrImage, margin, margin, qrSize, qrSize); var textX = margin * 2 + qrSize; var textWidth = widthPt - textX - margin; var textRect1 = new XRect(textX, margin, textWidth, heightPt * 0.4); var textRect2 = new XRect(textX, margin + heightPt * 0.4, textWidth, heightPt * 0.3); var textRect3 = new XRect(textX, margin + heightPt * 0.65, textWidth, heightPt * 0.3); gfx.DrawString(displayCode, titleFont, XBrushes.Black, textRect1, XStringFormats.TopLeft); gfx.DrawString(displayName, bodyFont, XBrushes.Black, textRect2, XStringFormats.TopLeft); gfx.DrawString(identifier.IdentifierValue, bodyFont, XBrushes.Black, textRect3, XStringFormats.TopLeft); } using var ms = new MemoryStream(); doc.Save(ms, false); return ms.ToArray(); } } }