2025-09-02 15:32:24 +01:00
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 < string > logLines = File . Exists ( logFilePath ) ? File . ReadAllLines ( logFilePath ) . ToList ( ) : new List < string > ( ) ;
List < string > errorLines = logLines . Where ( l = > l . Contains ( "[ERROR]" ) ) . ToList ( ) ;
List < string > 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 < string , int > errorCounts = [ ] ;
Dictionary < string , int > 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 < string , int > 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 < string , int > 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
2025-11-26 14:39:07 +00:00
string SaveDir = TestRecordDir + CamSoak . Model ;
if ( ! Directory . Exists ( TestRecordDir ) )
{
SaveDir = Path . Combine ( LDS . MAVPath , "SoakTestRecords" ) ; // Gets local folder if the main TestRecordDir is not found (no google drive).
if ( ! Directory . Exists ( SaveDir ) )
Directory . CreateDirectory ( SaveDir ) ;
}
string fullPath = Path . Combine ( SaveDir , $"SoakTestReport_{CamSoak.Model}_{CamSoak.Serial}_{pcTime:dd-MM-yyyy_HH-mm-ss}" + ( CamSoak . RMANum ! = 0 ? $" RMA{CamSoak.RMANum}" : "" ) + ".pdf" ) ;
//string fullPath = TestRecordDir + CamSoak.Model + $"\\SoakTestReport_{CamSoak.Model}_{CamSoak.Serial}_{pcTime:dd-MM-yyyy_HH-mm-ss}" + (CamSoak.RMANum != 0 ? $" RMA{CamSoak.RMANum}" : "") + ".pdf";
2025-09-02 15:32:24 +01:00
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 ) ;
}
}
}
}