package pdf import ( "fmt" "path/filepath" "github.com/jung-kurt/gofpdf" ) // Generator handles PDF generation for documents type Generator struct { pdf *gofpdf.Fpdf outputDir string headerText string footerText string docRef string currentPage int } // NewGenerator creates a new PDF generator func NewGenerator(outputDir string) *Generator { pdf := gofpdf.New("P", "mm", "A4", "") return &Generator{ pdf: pdf, outputDir: outputDir, headerText: "CMC TECHNOLOGIES", footerText: "Copyright © %d CMC Technologies. All rights reserved.", } } // AddPage adds a new page to the PDF func (g *Generator) AddPage() { g.pdf.AddPage() g.currentPage++ } // Page1Header adds the standard header for page 1 func (g *Generator) Page1Header() { g.pdf.SetY(10) // Set text color to blue g.pdf.SetTextColor(0, 0, 152) // Add logo if available (assuming logo is in static directory) // logoPath := filepath.Join("static", "images", "cmclogosmall.png") // Try to add logo, but don't fail if it doesn't exist or isn't a proper image // g.pdf.ImageOptions(logoPath, 10, 10, 0, 28, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "http://www.cmctechnologies.com.au") // Company name g.pdf.SetFont("Helvetica", "B", 30) g.pdf.SetX(40) g.pdf.CellFormat(0, 0, g.headerText, "", 1, "C", false, 0, "") // Company details g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetY(22) g.pdf.SetX(40) g.pdf.CellFormat(0, 0, "PTY LIMITED ACN: 085 991 224 ABN: 47 085 991 224", "", 1, "C", false, 0, "") // Draw horizontal line g.pdf.SetDrawColor(0, 0, 0) g.pdf.Line(43, 24, 200, 24) // Contact details g.pdf.SetTextColor(0, 0, 0) g.pdf.SetY(32) // Left column - labels g.pdf.SetX(45) g.pdf.MultiCell(30, 5, "Phone:\nFax:\nEmail:\nWeb Site:", "", "L", false) // Middle column - values g.pdf.SetY(32) g.pdf.SetX(65) g.pdf.SetFont("Helvetica", "", 10) g.pdf.MultiCell(55, 5, "+61 2 9669 4000\n+61 2 9669 4111\nsales@cmctechnologies.com.au\nwww.cmctechnologies.net.au", "", "L", false) // Right column - address g.pdf.SetY(32) g.pdf.SetX(150) g.pdf.MultiCell(52, 5, "Unit 19, 77 Bourke Rd\nAlexandria NSW 2015\nAUSTRALIA", "", "L", false) // Engineering text g.pdf.SetTextColor(0, 0, 152) g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetY(37) g.pdf.SetX(10) g.pdf.MultiCell(30, 5, "Engineering &\nIndustrial\nInstrumentation", "", "L", false) } // Page1Footer adds the standard footer func (g *Generator) Page1Footer() { g.pdf.SetY(-20) // Footer line g.pdf.SetDrawColor(0, 0, 0) g.pdf.Line(10, g.pdf.GetY(), 200, g.pdf.GetY()) // Footer text g.pdf.SetFont("Helvetica", "", 9) g.pdf.SetY(-18) g.pdf.CellFormat(0, 5, "CMC TECHNOLOGIES Provides Solutions in the Following Fields", "", 1, "C", false, 0, "") // Color-coded services g.pdf.SetY(-13) g.pdf.SetX(10) // First line of services services := []struct { text string r, g, b int }{ {"EXPLOSION PREVENTION AND PROTECTION", 153, 0, 10}, {"—", 0, 0, 0}, {"FIRE PROTECTION", 255, 153, 0}, {"—", 0, 0, 0}, {"PRESSURE RELIEF", 255, 0, 25}, {"—", 0, 0, 0}, {"VISION IN THE PROCESS", 0, 128, 30}, } x := 15.0 for _, service := range services { g.pdf.SetTextColor(service.r, service.g, service.b) width := g.pdf.GetStringWidth(service.text) g.pdf.SetX(x) g.pdf.CellFormat(width, 5, service.text, "", 0, "L", false, 0, "") x += width + 1 } // Second line of services g.pdf.SetY(-8) g.pdf.SetX(60) g.pdf.SetTextColor(47, 75, 224) g.pdf.CellFormat(0, 5, "FLOW MEASUREMENT", "", 0, "L", false, 0, "") g.pdf.SetX(110) g.pdf.SetTextColor(171, 49, 248) g.pdf.CellFormat(0, 5, "—PROCESS INSTRUMENTATION", "", 0, "L", false, 0, "") } // DetailsBox adds the document details box (quote/invoice details) func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromName, fromEmail, refNumber, yourRef, issueDate string) { g.pdf.SetY(60) g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetTextColor(0, 0, 0) // Create details table lineHeight := 5.0 col1Width := 40.0 col2Width := 60.0 col3Width := 40.0 col4Width := 50.0 // Row 1 g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col1Width, lineHeight, "COMPANY NAME:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col2Width, lineHeight, companyName, "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col3Width, lineHeight, docType+" NO.:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col4Width, lineHeight, refNumber, "1", 1, "L", false, 0, "") // Row 2 g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col1Width, lineHeight, "EMAIL TO:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col2Width, lineHeight, emailTo, "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col3Width, lineHeight, "YOUR REFERENCE:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col4Width, lineHeight, yourRef, "1", 1, "L", false, 0, "") // Row 3 g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col1Width, lineHeight, "ATTENTION:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col2Width, lineHeight, attention, "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col3Width, lineHeight, "ISSUE DATE:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col4Width, lineHeight, issueDate, "1", 1, "L", false, 0, "") // Row 4 g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col1Width, lineHeight, "FROM:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col2Width, lineHeight, fromName, "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col3Width, lineHeight, "PAGES:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) pageText := fmt.Sprintf("%d of {nb}", g.pdf.PageNo()) g.pdf.CellFormat(col4Width, lineHeight, pageText, "1", 1, "L", false, 0, "") // Row 5 g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(col1Width, lineHeight, "EMAIL:", "1", 0, "L", false, 0, "") g.pdf.SetFont("Helvetica", "", 10) g.pdf.CellFormat(col2Width, lineHeight, fromEmail, "1", 0, "L", false, 0, "") g.pdf.CellFormat(col3Width+col4Width, lineHeight, "", "1", 1, "L", false, 0, "") } // AddContent adds HTML content to the PDF func (g *Generator) AddContent(content string) { g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetTextColor(0, 0, 0) g.pdf.SetY(g.pdf.GetY() + 10) // Basic HTML to PDF conversion // Note: gofpdf has limited HTML support, so we'll need to parse and convert manually // For now, we'll just add the content as plain text g.pdf.MultiCell(0, 5, content, "", "L", false) } // AddLineItemsTable adds a table of line items func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, showGST bool) { g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetTextColor(0, 0, 0) // Column widths itemWidth := 15.0 qtyWidth := 15.0 descWidth := 100.0 unitWidth := 30.0 totalWidth := 30.0 // Table header g.pdf.CellFormat(itemWidth, 7, "ITEM", "1", 0, "C", false, 0, "") g.pdf.CellFormat(qtyWidth, 7, "QTY", "1", 0, "C", false, 0, "") g.pdf.CellFormat(descWidth, 7, "DESCRIPTION", "1", 0, "C", false, 0, "") g.pdf.CellFormat(unitWidth, 7, "UNIT PRICE", "1", 0, "C", false, 0, "") g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "") // Table rows g.pdf.SetFont("Helvetica", "", 9) subtotal := 0.0 for _, item := range items { // Check if we need a new page if g.pdf.GetY() > 250 { g.AddPage() g.pdf.SetFont("Helvetica", "B", 10) g.pdf.CellFormat(itemWidth, 7, "ITEM", "1", 0, "C", false, 0, "") g.pdf.CellFormat(qtyWidth, 7, "QTY", "1", 0, "C", false, 0, "") g.pdf.CellFormat(descWidth, 7, "DESCRIPTION", "1", 0, "C", false, 0, "") g.pdf.CellFormat(unitWidth, 7, "UNIT PRICE", "1", 0, "C", false, 0, "") g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "") g.pdf.SetFont("Helvetica", "", 9) } g.pdf.CellFormat(itemWidth, 6, item.ItemNumber, "1", 0, "C", false, 0, "") g.pdf.CellFormat(qtyWidth, 6, item.Quantity, "1", 0, "C", false, 0, "") g.pdf.CellFormat(descWidth, 6, item.Title, "1", 0, "L", false, 0, "") g.pdf.CellFormat(unitWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.UnitPrice), "1", 0, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.TotalPrice), "1", 1, "R", false, 0, "") subtotal += item.TotalPrice } // Totals g.pdf.SetFont("Helvetica", "B", 10) // Subtotal g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.CellFormat(unitWidth, 6, "SUBTOTAL:", "1", 0, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, subtotal), "1", 1, "R", false, 0, "") // GST if applicable if showGST { gst := subtotal * 0.10 g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.CellFormat(unitWidth, 6, "GST (10%):", "1", 0, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, gst), "1", 1, "R", false, 0, "") // Total total := subtotal + gst g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.CellFormat(unitWidth, 6, "TOTAL DUE:", "1", 0, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, total), "1", 1, "R", false, 0, "") } else { // Total without GST g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.CellFormat(unitWidth, 6, "TOTAL:", "1", 0, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, subtotal), "1", 1, "R", false, 0, "") } } // Save saves the PDF to a file func (g *Generator) Save(filename string) error { g.pdf.AliasNbPages("") outputPath := filepath.Join(g.outputDir, filename) fmt.Printf("Generator.Save: Saving PDF to path: %s\n", outputPath) err := g.pdf.OutputFileAndClose(outputPath) if err != nil { fmt.Printf("Generator.Save: Error saving PDF: %v\n", err) } else { fmt.Printf("Generator.Save: PDF saved successfully to: %s\n", outputPath) } return err } // LineItem represents a line item for the PDF type LineItem struct { ItemNumber string Quantity string Title string UnitPrice float64 TotalPrice float64 }