using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Tables; using MigraDoc.Rendering; using PdfSharp.Pdf; using PdfSharp.Pdf.IO; using Image = MigraDoc.DocumentObjectModel.Shapes.Image; namespace AiQ_GUI { internal class PDF { public const string TestRecordDir = "G:\\Shared drives\\MAV Production Test Records\\AiQ\\"; public const string ImageDir = $"{GoogleAPI.DrivePath}AiQ\\GUI's\\"; public static void CreateFinalTestReport(Camera CamOnTest, string UserName, string fulltestvalues, DateTime PCTime) { // Create a new PDF document Document document = new(); Section section = document.AddSection(); // Create a table with two columns Table latable = section.AddTable(); latable.Borders.Visible = false; // No visible borders latable.AddColumn(Unit.FromCentimeter(13)); // Left column for MAV logo latable.AddColumn(Unit.FromCentimeter(3)); // Right column for AiQ logo // Add a row for the logos Row larow = latable.AddRow(); Cell cellMAV = larow.Cells[0]; Cell cellAiQ = larow.Cells[1]; // Add MAV logo to the left cell Image imageMAV = cellMAV.AddImage($"{ImageDir}MAV-Logo.png"); imageMAV.LockAspectRatio = true; imageMAV.Height = Unit.FromCentimeter(1); // Set height to 1 cm // Add AiQ logo to the right cell Image imageAiQ = cellAiQ.AddImage($"{ImageDir}AiQ-Logo.png"); imageAiQ.LockAspectRatio = true; imageAiQ.Height = Unit.FromCentimeter(1.25); // Set height to 1 cm // Add spacing below the table Paragraph tableParagraph = section.AddParagraph(); tableParagraph.Format.SpaceAfter = "0.5cm"; // Add Title Paragraph title = section.AddParagraph("Certificate of Conformity"); title.Format.Font.Size = 20; title.Format.Font.Bold = true; title.Format.Alignment = ParagraphAlignment.Center; title.Format.SpaceAfter = "0.15cm"; // Add SubTitle string Subtitle = $"AiQ {CamOnTest.Model} {CamOnTest.Serial}" + (CamOnTest.RMANum != 0 ? $" (RMA {CamOnTest.RMANum})" : ""); Paragraph subtitle = section.AddParagraph(Subtitle); subtitle.Format.Font.Size = 14; subtitle.Format.Alignment = ParagraphAlignment.Center; subtitle.Format.SpaceAfter = "0.5cm"; // Add Date Paragraph date = section.AddParagraph($"Date: {PCTime}{Environment.NewLine}Engineer: {UserName}{Environment.NewLine}Tested in accordance with MAV.146.060.010.01"); date.Format.Font.Size = 10; date.Format.SpaceAfter = "0.5cm"; // Add Test Values header Paragraph valuesHeader = section.AddParagraph("Test Values:"); valuesHeader.Format.Font.Size = 14; valuesHeader.Format.Font.Bold = true; valuesHeader.Format.SpaceAfter = "0.25cm"; // Add Test Values Paragraph FTV = section.AddParagraph(fulltestvalues); FTV.Format.Font.Size = 10; FTV.Format.SpaceAfter = "0.5cm"; // Add Images Paragraph imagesHeader = section.AddParagraph("Images:"); imagesHeader.Format.Font.Size = 14; imagesHeader.Format.Font.Bold = true; imagesHeader.Format.SpaceAfter = "0.25cm"; // Create a table with two columns Table table = section.AddTable(); table.Borders.Width = 0; // No border table.AddColumn(Unit.FromCentimeter(6)); // Column 1 width table.AddColumn(Unit.FromCentimeter(6)); // Column 2 width table.AddColumn(Unit.FromCentimeter(6)); // Column 3 width table.Rows.Alignment = RowAlignment.Center; Row row = table.AddRow(); // Add IR F16 to the first column Cell cell1 = row.Cells[0]; cell1.Format.Alignment = ParagraphAlignment.Center; // Center horizontally cell1.AddParagraph("Infrared - F16.0"); Image imageIR = cell1.AddImage(LDS.MAVPath + LDS.IRTightsavePath); imageIR.LockAspectRatio = true; // Maintain aspect ratio imageIR.Width = Unit.FromCentimeter(6); // Scale as needed // Add IR F2 to the second column Cell cell2 = row.Cells[1]; cell2.Format.Alignment = ParagraphAlignment.Center; // Center horizontally cell2.AddParagraph("Infrared - F2.0"); Image imageIR17 = cell2.AddImage(LDS.MAVPath + LDS.IROpensavePath); imageIR17.LockAspectRatio = true; // Maintain aspect ratio imageIR17.Width = Unit.FromCentimeter(6); // Scale as needed // Add Image OV to the third column Cell cell3 = row.Cells[2]; cell3.Format.Alignment = ParagraphAlignment.Center; // Center horizontally cell3.AddParagraph("Overview"); Image imageOV = cell3.AddImage(LDS.MAVPath + LDS.OVsavePath); imageOV.LockAspectRatio = true; // Maintain aspect ratio imageOV.Width = Unit.FromCentimeter(6); // Scale as needed section.AddParagraph().Format.SpaceAfter = "0.5cm"; // Add signiture Paragraph Signiture = section.AddParagraph("This unit is approved for shipment by:"); Signiture.Format.Font.Size = 14; Signiture.Format.Alignment = ParagraphAlignment.Right; Paragraph paragraph = section.AddParagraph(); Image image = paragraph.AddImage($"{ImageDir}RP-Sig.jpg"); image.Height = Unit.FromCentimeter(2); image.LockAspectRatio = true; paragraph.Format.Alignment = ParagraphAlignment.Right; Paragraph PostSig = section.AddParagraph($"Richard Porter{Environment.NewLine}Head of Engineering{Environment.NewLine}MAV Systems Ltd"); PostSig.Format.Font.Size = 14; PostSig.Format.Alignment = ParagraphAlignment.Right; try { // Render PDF PdfDocumentRenderer renderer = new() { Document = document }; renderer.RenderDocument(); renderer.PdfDocument.Options.Layout = PdfWriterLayout.Compact; // Adds RMA number to file namme if there is one string saveLoc = $"{TestRecordDir}{CamOnTest.Model}\\FinalTestReport_{CamOnTest.Model}_{CamOnTest.Serial}_{PCTime:dd-MM-yyyy_HH-mm-ss}" + (CamOnTest.RMANum != 0 ? $" RMA{CamOnTest.RMANum}" : "") + ".pdf"; if (!Directory.Exists($"{TestRecordDir}{CamOnTest.Model}\\")) // Does the model directory exist? { Directory.CreateDirectory($"{TestRecordDir}{CamOnTest.Model}\\"); // if not then create the directory now } renderer.PdfDocument.Save(saveLoc); Logging.LogMessage("Final Test PDF saved to " + saveLoc); } catch (Exception ex) { // Show a message box to inform the user that PDF creation failed, displaying the exception message. MainForm.Instance.AddToActionsList($"Failed to create PDF:\n{ex.Message}"); } } public static string CreateSoakTestReport(Camera CamSoak, string userName, DateTime pcTime, string logFilePath) { if (!File.Exists(logFilePath)) { MainForm.Instance.AddToActionsList("Soak log file not found. Cannot create Soak Test Report."); return null; } List logLines = File.Exists(logFilePath) ? File.ReadAllLines(logFilePath).ToList() : new List(); List errorLines = logLines.Where(l => l.Contains("[ERROR]")).ToList(); List warningLines = logLines.Where(l => l.Contains("[WARNING]")).ToList(); // Create PDF document Document document = new(); Section section = document.AddSection(); // Header table with logos Table logoTable = section.AddTable(); logoTable.Borders.Visible = false; logoTable.AddColumn(Unit.FromCentimeter(13)); // Left column logoTable.AddColumn(Unit.FromCentimeter(3)); // Right column Row logoRow = logoTable.AddRow(); Cell cellMAV = logoRow.Cells[0]; Cell cellAiQ = logoRow.Cells[1]; Image mavLogo = cellMAV.AddImage($"{ImageDir}MAV-Logo.png"); mavLogo.LockAspectRatio = true; mavLogo.Height = Unit.FromCentimeter(1); Image aiqLogo = cellAiQ.AddImage($"{ImageDir}AiQ-Logo.png"); aiqLogo.LockAspectRatio = true; aiqLogo.Height = Unit.FromCentimeter(1.25); section.AddParagraph().Format.SpaceAfter = "0.5cm"; // Title Paragraph title = section.AddParagraph("Soak Test Certificate"); title.Format.Font.Size = 20; title.Format.Font.Bold = true; title.Format.Alignment = ParagraphAlignment.Center; title.Format.SpaceAfter = "0.15cm"; // Subtitle with RMA number if available string subtitleText = $"AiQ {CamSoak.Model} {CamSoak.Serial}" + (CamSoak.RMANum != 0 ? $" (RMA {CamSoak.RMANum})" : ""); Paragraph subtitle = section.AddParagraph(subtitleText); subtitle.Format.Font.Size = 14; subtitle.Format.Alignment = ParagraphAlignment.Center; subtitle.Format.SpaceAfter = "0.5cm"; // Date and engineer info Paragraph datePara = section.AddParagraph($"Date: {pcTime:dd/MM/yyyy HH:mm:ss}{Environment.NewLine}Engineer: {userName}{Environment.NewLine}Tested in accordance with MAV.146.060.020.01"); datePara.Format.Font.Size = 10; datePara.Format.SpaceAfter = "0.5cm"; // === Add warning/error summary with counts and color === if (warningLines.Count > 0 || errorLines.Count > 0) { Paragraph logSummary = section.AddParagraph("Log Summary:"); logSummary.Format.Font.Size = 12; logSummary.Format.Font.Bold = true; logSummary.Format.SpaceAfter = "0.2cm"; Paragraph totalCounts = section.AddParagraph(); totalCounts.Format.Font.Size = 10; totalCounts.AddText("Total Errors: "); FormattedText errorCountText = totalCounts.AddFormattedText(errorLines.Count.ToString()); errorCountText.Font.Color = Colors.Red; totalCounts.AddText("\nTotal Warnings: "); FormattedText warningCountText = totalCounts.AddFormattedText(warningLines.Count.ToString()); warningCountText.Font.Color = Colors.Orange; totalCounts.Format.SpaceAfter = "0.2cm"; Dictionary errorCounts = []; Dictionary warningCounts = []; foreach (string line in errorLines) { string message = ExtractLogMessageContent(line); if (errorCounts.TryGetValue(message, out int value)) errorCounts[message] = ++value; else errorCounts[message] = 1; } foreach (string line in warningLines) { string message = ExtractLogMessageContent(line); if (warningCounts.TryGetValue(message, out int value)) warningCounts[message] = ++value; else warningCounts[message] = 1; } foreach (KeyValuePair ErrorCounter in errorCounts) // Itterates through the dictionary and Adds Errors and how many times that warning has appeared { Paragraph errorPara = section.AddParagraph(); errorPara.Format.Font.Size = 9; errorPara.AddFormattedText("[", TextFormat.NotBold); FormattedText redText = errorPara.AddFormattedText("ERROR", TextFormat.NotBold); redText.Font.Color = Colors.Red; errorPara.AddFormattedText($"] {ErrorCounter.Key} (x{ErrorCounter.Value})"); } foreach (KeyValuePair WarningCounter in warningCounts) // Itterates through the dictionary and Adds warning and how many times that warning has appeared { Paragraph warnPara = section.AddParagraph(); warnPara.Format.Font.Size = 9; warnPara.AddFormattedText("[", TextFormat.NotBold); FormattedText orangeText = warnPara.AddFormattedText("WARNING", TextFormat.NotBold); orangeText.Font.Color = Colors.Orange; warnPara.AddFormattedText($"] {WarningCounter.Key} (x{WarningCounter.Value})"); } section.AddParagraph().Format.SpaceAfter = "0.5cm"; } // Signature if (errorLines.Count == 0) { Paragraph approval = section.AddParagraph("This unit is approved for shipment by:"); approval.Format.Font.Size = 14; approval.Format.Alignment = ParagraphAlignment.Right; Paragraph sigPara = section.AddParagraph(); Image sigImage = sigPara.AddImage($"{ImageDir}RP-Sig.jpg"); sigImage.Height = Unit.FromCentimeter(2); sigImage.LockAspectRatio = true; sigPara.Format.Alignment = ParagraphAlignment.Right; Paragraph signoff = section.AddParagraph($"Richard Porter{Environment.NewLine}Head of Engineering{Environment.NewLine}MAV Systems Ltd"); signoff.Format.Font.Size = 14; signoff.Format.Alignment = ParagraphAlignment.Right; } // New page with full log Section logSection = document.AddSection(); Paragraph fullLogTitle = logSection.AddParagraph("Full Log Output:"); fullLogTitle.Format.Font.Size = 12; fullLogTitle.Format.Font.Bold = true; fullLogTitle.Format.SpaceAfter = "0.2cm"; foreach (string line in logLines) { Paragraph logLine = logSection.AddParagraph(); logLine.Format.Font.Size = 8; if (line.Contains("[ERROR]")) { int index = line.IndexOf("[ERROR]"); // You get rid of error only to add it back a few lines later? // this is because its difficukt to change the colour of text your importing its easier to remove and then make red logLine.AddText(line[..index]); logLine.AddFormattedText("ERROR", TextFormat.NotBold).Font.Color = Colors.Red; logLine.AddText(line[(index + "[ERROR]".Length)..]); } else if (line.Contains("[WARNING]")) { int index = line.IndexOf("[WARNING]"); // You get rid of warning only to add it back a few lines later? // Same here logLine.AddText(line[..index]); logLine.AddFormattedText("WARNING", TextFormat.NotBold).Font.Color = Colors.Orange; logLine.AddText(line[(index + "[WARNING]".Length)..]); } else { logLine.AddText(line); } } try { // Render PDF PdfDocumentRenderer renderer = new() { Document = document }; renderer.RenderDocument(); renderer.PdfDocument.Options.Layout = PdfWriterLayout.Compact; // Construct save path string fullPath = TestRecordDir + CamSoak.Model + $"\\SoakTestReport_{CamSoak.Model}_{CamSoak.Serial}_{pcTime:dd-MM-yyyy_HH-mm-ss}" + (CamSoak.RMANum != 0 ? $" RMA{CamSoak.RMANum}" : "") + ".pdf"; renderer.PdfDocument.Save(fullPath); Logging.LogMessage("Soak Test PDF saved to " + fullPath); return fullPath; } catch (Exception ex) { // Show a message box to inform the user that PDF creation failed, displaying the exception message. MainForm.Instance.AddToActionsList($"Failed to create PDF:\n{ex.Message}"); return null; } } private static string ExtractLogMessageContent(string line) { int tagEnd = line.IndexOf("] "); if (tagEnd != -1) { return line[(tagEnd + 2)..].Trim(); } return line.Trim(); } public static bool LinkPDFs(string pdf1Path, string pdf2Path, string outputPath) { bool hasError = false; if (!File.Exists(pdf1Path)) { MainForm.Instance.AddToActionsList($"{pdf1Path} does not exist."); hasError = true; } if (!File.Exists(pdf2Path)) { MainForm.Instance.AddToActionsList($"{pdf2Path} does not exist."); hasError = true; } if (hasError) return false; using PdfDocument outputDocument = new(); // Create a new PDF document AppendPdf(outputDocument, pdf1Path); AppendPdf(outputDocument, pdf2Path); outputDocument.Save(outputPath); // Save the output document return true; } public static void AppendPdf(PdfDocument target, string sourcePath) { using PdfDocument inputDocument = PdfReader.Open(sourcePath, PdfDocumentOpenMode.Import); for (int idx = 0; idx < inputDocument.PageCount; idx++) // Iterate through all pages and add them to the output { PdfPage page = inputDocument.Pages[idx]; target.AddPage(page); } } } }