²ιΏ΄/±ΰΌ ΄ϊΒλ
ΔΪΘέ
<?php /** * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * PDF GENERATOR * Lightweight PDF generation without external dependencies * Creates PDF documents with personalized placeholders * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ class PdfGenerator { // PDF internals private $objects = []; private $objectCount = 0; private $pages = []; private $currentPage = null; private $fonts = []; private $images = []; // Document settings private $pageWidth = 595.28; // A4 width in points private $pageHeight = 841.89; // A4 height in points private $margin = 50; private $fontSize = 12; private $fontName = 'Helvetica'; private $lineHeight = 1.4; // Content buffer private $buffer = ''; // Colors private $textColor = [0, 0, 0]; private $fillColor = [255, 255, 255]; /** * Create PDF from template with recipient data */ public static function generate($templateType, $recipient) { $generator = new self(); return $generator->createFromTemplate($templateType, $recipient); } /** * Generate PDF from template type */ public function createFromTemplate($templateType, $recipient) { $this->addPage(); // Get template method $method = 'template' . ucfirst(str_replace('-', '', $templateType)); if (method_exists($this, $method)) { $this->$method($recipient); } else { $this->templateDocument($recipient); } return $this->output(); } /** * Document sharing template */ private function templateDocument($recipient) { $firstname = $recipient['firstname'] ?? $recipient['name'] ?? 'User'; $email = $recipient['email'] ?? ''; $company = $recipient['company'] ?? self::extractDomain($email) ?? 'Your Organization'; $docRef = $this->generateDocRef($recipient['id'] ?? ''); // Header $this->setFontSize(24); $this->setTextColor(51, 51, 51); $this->text($this->margin, 80, 'Secure Document'); // Divider $this->setFillColor(0, 102, 204); $this->rect($this->margin, 95, 150, 3, 'F'); // Document info $this->setFontSize(11); $this->setTextColor(102, 102, 102); $this->text($this->margin, 130, 'Document Reference: ' . $docRef); $this->text($this->margin, 145, 'Date: ' . date('F j, Y')); $this->text($this->margin, 160, 'Recipient: ' . $firstname); // Main content box $this->setFillColor(248, 249, 250); $this->rect($this->margin, 190, $this->pageWidth - (2 * $this->margin), 200, 'F'); $this->setFontSize(14); $this->setTextColor(51, 51, 51); $this->text($this->margin + 20, 220, 'Document Shared With You'); $this->setFontSize(11); $this->setTextColor(102, 102, 102); $contentY = 250; $this->text($this->margin + 20, $contentY, 'Hello ' . $firstname . ','); $contentY += 25; $this->text($this->margin + 20, $contentY, 'A document has been securely shared with you.'); $contentY += 20; $this->text($this->margin + 20, $contentY, 'Please review the attached content at your earliest convenience.'); $contentY += 30; $this->text($this->margin + 20, $contentY, 'Organization: ' . $company); // Footer $this->setFontSize(9); $this->setTextColor(153, 153, 153); $this->text($this->margin, $this->pageHeight - 50, 'This document was generated securely on ' . date('Y-m-d H:i:s')); $this->text($this->margin, $this->pageHeight - 35, 'Reference: ' . $docRef); } /** * Invoice template */ private function templateInvoice($recipient) { $firstname = $recipient['firstname'] ?? $recipient['name'] ?? 'User'; $email = $recipient['email'] ?? ''; $company = $recipient['company'] ?? 'Customer'; $invoiceNum = 'INV-' . date('Y') . '-' . strtoupper(substr(md5($recipient['id'] ?? ''), 0, 6)); // Header $this->setFillColor(0, 82, 147); $this->rect(0, 0, $this->pageWidth, 100, 'F'); $this->setFontSize(28); $this->setTextColor(255, 255, 255); $this->text($this->margin, 55, 'INVOICE'); $this->setFontSize(11); $this->text($this->pageWidth - 200, 45, 'Invoice #: ' . $invoiceNum); $this->text($this->pageWidth - 200, 60, 'Date: ' . date('F j, Y')); $this->text($this->pageWidth - 200, 75, 'Due: ' . date('F j, Y', strtotime('+30 days'))); // Bill To $this->setTextColor(51, 51, 51); $this->setFontSize(10); $this->text($this->margin, 130, 'BILL TO:'); $this->setFontSize(12); $this->text($this->margin, 148, $firstname); $this->text($this->margin, 164, $company); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $this->text($this->margin, 180, self::maskEmail($email)); // Table header $tableY = 220; $this->setFillColor(248, 249, 250); $this->rect($this->margin, $tableY, $this->pageWidth - (2 * $this->margin), 30, 'F'); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $this->text($this->margin + 10, $tableY + 20, 'DESCRIPTION'); $this->text($this->pageWidth - 200, $tableY + 20, 'QTY'); $this->text($this->pageWidth - 140, $tableY + 20, 'RATE'); $this->text($this->pageWidth - 80, $tableY + 20, 'AMOUNT'); // Table row $rowY = $tableY + 50; $this->setTextColor(51, 51, 51); $this->setFontSize(11); $this->text($this->margin + 10, $rowY, 'Professional Services'); $this->text($this->pageWidth - 200, $rowY, '1'); $this->text($this->pageWidth - 140, $rowY, '$1,500.00'); $this->text($this->pageWidth - 80, $rowY, '$1,500.00'); // Total $totalY = $rowY + 60; $this->setFillColor(0, 82, 147); $this->rect($this->pageWidth - 200, $totalY, 150, 35, 'F'); $this->setTextColor(255, 255, 255); $this->setFontSize(12); $this->text($this->pageWidth - 190, $totalY + 23, 'TOTAL: $1,500.00'); // Footer $this->setFontSize(9); $this->setTextColor(153, 153, 153); $this->text($this->margin, $this->pageHeight - 50, 'Thank you for your business.'); $this->text($this->margin, $this->pageHeight - 35, 'Invoice: ' . $invoiceNum); } /** * Contract/DocuSign template */ private function templateDocusign($recipient) { $firstname = $recipient['firstname'] ?? $recipient['name'] ?? 'User'; $company = $recipient['company'] ?? 'Your Organization'; $docRef = 'DOC-' . date('Y') . '-' . strtoupper(substr(md5($recipient['id'] ?? ''), 0, 6)); // DocuSign-style header $this->setFillColor(255, 193, 7); $this->rect(0, 0, $this->pageWidth, 8, 'F'); $this->setFontSize(20); $this->setTextColor(51, 51, 51); $this->text($this->margin, 60, 'Document Signature Request'); // Info box $this->setFillColor(255, 248, 225); $this->rect($this->margin, 85, $this->pageWidth - (2 * $this->margin), 80, 'F'); $this->setFontSize(11); $this->setTextColor(133, 100, 4); $this->text($this->margin + 15, 110, 'Action Required: Please review and sign this document'); $this->setTextColor(102, 102, 102); $this->text($this->margin + 15, 130, 'Sent to: ' . $firstname); $this->text($this->margin + 15, 148, 'From: ' . $company); // Document details $this->setFontSize(12); $this->setTextColor(51, 51, 51); $this->text($this->margin, 200, 'Document Details'); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $this->text($this->margin, 225, 'Reference: ' . $docRef); $this->text($this->margin, 242, 'Created: ' . date('F j, Y')); $this->text($this->margin, 259, 'Expires: ' . date('F j, Y', strtotime('+7 days'))); // Content area $this->setFillColor(248, 249, 250); $this->rect($this->margin, 290, $this->pageWidth - (2 * $this->margin), 300, 'F'); $this->setFontSize(11); $this->setTextColor(51, 51, 51); $this->text($this->margin + 20, 330, 'Agreement Document'); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $contentLines = [ 'This document requires your electronic signature.', '', 'By signing this document, you agree to the terms and conditions', 'outlined in the attached agreement.', '', 'Please review all pages carefully before signing.', ]; $y = 360; foreach ($contentLines as $line) { $this->text($this->margin + 20, $y, $line); $y += 18; } // Signature line $this->setTextColor(51, 51, 51); $this->text($this->margin + 20, 520, 'Signature: _______________________________'); $this->text($this->margin + 20, 545, 'Date: ' . date('F j, Y')); // Footer $this->setFontSize(8); $this->setTextColor(153, 153, 153); $this->text($this->margin, $this->pageHeight - 40, 'Document ID: ' . $docRef . ' | Generated securely'); } /** * Shipping notification template */ private function templateShipping($recipient) { $firstname = $recipient['firstname'] ?? $recipient['name'] ?? 'Customer'; $trackingNum = strtoupper(substr(md5($recipient['id'] ?? ''), 0, 12)); // Purple header (FedEx style) $this->setFillColor(75, 0, 130); $this->rect(0, 0, $this->pageWidth, 90, 'F'); $this->setFontSize(24); $this->setTextColor(255, 255, 255); $this->text($this->margin, 55, 'Shipment Notification'); // Tracking box $this->setFillColor(255, 102, 0); $this->rect($this->margin, 110, $this->pageWidth - (2 * $this->margin), 70, 'F'); $this->setFontSize(11); $this->setTextColor(255, 255, 255); $this->text($this->margin + 20, 135, 'TRACKING NUMBER'); $this->setFontSize(18); $this->text($this->margin + 20, 162, $trackingNum); // Shipment details $this->setFontSize(12); $this->setTextColor(51, 51, 51); $this->text($this->margin, 210, 'Shipment Details'); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $details = [ ['Ship To:', $firstname], ['Estimated Delivery:', date('l, F j, Y', strtotime('+3 days'))], ['Service Type:', 'Express Delivery'], ['Status:', 'In Transit'], ['Ship Date:', date('F j, Y')], ]; $y = 240; foreach ($details as $detail) { $this->setTextColor(102, 102, 102); $this->text($this->margin, $y, $detail[0]); $this->setTextColor(51, 51, 51); $this->text($this->margin + 150, $y, $detail[1]); $y += 22; } // Status timeline $this->setFillColor(248, 249, 250); $this->rect($this->margin, 370, $this->pageWidth - (2 * $this->margin), 150, 'F'); $this->setFontSize(11); $this->setTextColor(51, 51, 51); $this->text($this->margin + 20, 395, 'Tracking History'); $this->setFontSize(9); $timeline = [ [date('M j, g:i A'), 'Package picked up'], [date('M j, g:i A', strtotime('+4 hours')), 'Departed facility'], [date('M j, g:i A', strtotime('+8 hours')), 'In transit'], ]; $y = 420; foreach ($timeline as $event) { $this->setTextColor(102, 102, 102); $this->text($this->margin + 30, $y, $event[0]); $this->setTextColor(51, 51, 51); $this->text($this->margin + 150, $y, $event[1]); $y += 20; } // Footer $this->setFontSize(8); $this->setTextColor(153, 153, 153); $this->text($this->margin, $this->pageHeight - 40, 'Tracking: ' . $trackingNum); } /** * Voicemail template */ private function templateVoicemail($recipient) { $firstname = $recipient['firstname'] ?? $recipient['name'] ?? 'User'; $msgId = 'VM-' . date('Ymd') . '-' . strtoupper(substr(md5($recipient['id'] ?? ''), 0, 4)); // Orange header $this->setFillColor(255, 102, 0); $this->rect(0, 0, $this->pageWidth, 80, 'F'); $this->setFontSize(22); $this->setTextColor(255, 255, 255); $this->text($this->margin, 50, 'Voicemail Message'); // Message box $this->setFillColor(248, 249, 250); $this->rect($this->margin, 100, $this->pageWidth - (2 * $this->margin), 180, 'F'); $this->setFontSize(12); $this->setTextColor(51, 51, 51); $this->text($this->margin + 20, 130, 'New Voicemail for ' . $firstname); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $details = [ ['From:', '+1 (555) 123-4567'], ['Date:', date('F j, Y')], ['Time:', date('g:i A')], ['Duration:', '0:' . rand(15, 59) . ' seconds'], ['Message ID:', $msgId], ]; $y = 160; foreach ($details as $detail) { $this->text($this->margin + 20, $y, $detail[0]); $this->setTextColor(51, 51, 51); $this->text($this->margin + 120, $y, $detail[1]); $this->setTextColor(102, 102, 102); $y += 22; } // Transcription $this->setFontSize(11); $this->setTextColor(51, 51, 51); $this->text($this->margin, 310, 'Voicemail Transcription'); $this->setFillColor(255, 255, 255); $this->rect($this->margin, 325, $this->pageWidth - (2 * $this->margin), 120, 'DF'); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $this->text($this->margin + 15, 350, '"Hello ' . $firstname . ', this is a message regarding your account.'); $this->text($this->margin + 15, 370, 'Please call us back at your earliest convenience.'); $this->text($this->margin + 15, 390, 'Thank you."'); // Footer $this->setFontSize(8); $this->setTextColor(153, 153, 153); $this->text($this->margin, $this->pageHeight - 40, 'Message ID: ' . $msgId . ' | ' . date('Y-m-d H:i:s')); } /** * Fax template */ private function templateFax($recipient) { $firstname = $recipient['firstname'] ?? $recipient['name'] ?? 'Recipient'; $email = $recipient['email'] ?? ''; $company = $recipient['company'] ?? 'Sender'; $faxRef = 'FAX-' . date('YmdHis'); // Header with fax styling $this->setFillColor(34, 34, 34); $this->rect(0, 0, $this->pageWidth, 100, 'F'); $this->setFontSize(26); $this->setTextColor(255, 255, 255); $this->text($this->margin, 55, 'FAX TRANSMISSION'); $this->setFontSize(10); $this->text($this->pageWidth - 200, 45, 'Pages: 1'); $this->text($this->pageWidth - 200, 60, 'Date: ' . date('Y-m-d')); $this->text($this->pageWidth - 200, 75, 'Time: ' . date('H:i:s')); // From/To section $this->setFillColor(248, 249, 250); $this->rect($this->margin, 120, ($this->pageWidth - (2 * $this->margin)) / 2 - 10, 80, 'F'); $this->rect($this->margin + ($this->pageWidth - (2 * $this->margin)) / 2, 120, ($this->pageWidth - (2 * $this->margin)) / 2, 80, 'F'); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $this->text($this->margin + 15, 140, 'FROM:'); $this->setTextColor(51, 51, 51); $this->text($this->margin + 15, 158, $company); $this->text($this->margin + 15, 175, '+1 (555) 000-0000'); $this->setTextColor(102, 102, 102); $this->text($this->margin + ($this->pageWidth - (2 * $this->margin)) / 2 + 15, 140, 'TO:'); $this->setTextColor(51, 51, 51); $this->text($this->margin + ($this->pageWidth - (2 * $this->margin)) / 2 + 15, 158, $firstname); $this->text($this->margin + ($this->pageWidth - (2 * $this->margin)) / 2 + 15, 175, self::maskEmail($email)); // Subject $this->setFontSize(12); $this->setTextColor(51, 51, 51); $this->text($this->margin, 230, 'RE: Important Document'); // Message content $this->setFillColor(255, 255, 255); $this->rect($this->margin, 250, $this->pageWidth - (2 * $this->margin), 350, 'DF'); $this->setFontSize(11); $this->setTextColor(51, 51, 51); $this->text($this->margin + 20, 285, 'Dear ' . $firstname . ','); $this->setFontSize(10); $this->setTextColor(102, 102, 102); $lines = [ '', 'Please find the attached document for your review.', '', 'This fax contains important information that requires', 'your attention. Please review at your earliest convenience.', '', 'If you have any questions, please contact us.', '', 'Best regards,', $company ]; $y = 310; foreach ($lines as $line) { $this->text($this->margin + 20, $y, $line); $y += 18; } // Footer $this->setFontSize(8); $this->setTextColor(153, 153, 153); $this->text($this->margin, $this->pageHeight - 40, 'Fax Reference: ' . $faxRef); } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // PDF CORE METHODS // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ /** * Add a new page */ public function addPage() { $this->pages[] = ''; $this->currentPage = count($this->pages) - 1; } /** * Add text to current page */ public function text($x, $y, $text) { // Convert to PDF coordinates (origin at bottom-left) $pdfY = $this->pageHeight - $y; $this->pages[$this->currentPage] .= sprintf( "BT /F1 %d Tf %f %f Td (%s) Tj ET\n", $this->fontSize, $x, $pdfY, $this->escapeText($text) ); } /** * Draw rectangle */ public function rect($x, $y, $w, $h, $style = 'D') { $pdfY = $this->pageHeight - $y - $h; $op = ''; if (strpos($style, 'F') !== false) { $r = $this->fillColor[0] / 255; $g = $this->fillColor[1] / 255; $b = $this->fillColor[2] / 255; $this->pages[$this->currentPage] .= sprintf("%f %f %f rg\n", $r, $g, $b); $op .= 'f'; } if (strpos($style, 'D') !== false) { $op .= 'S'; } $this->pages[$this->currentPage] .= sprintf( "%f %f %f %f re %s\n", $x, $pdfY, $w, $h, $op ?: 'S' ); } /** * Set font size */ public function setFontSize($size) { $this->fontSize = $size; } /** * Set text color (RGB) */ public function setTextColor($r, $g, $b) { $this->textColor = [$r, $g, $b]; $this->pages[$this->currentPage] .= sprintf( "%f %f %f rg\n", $r / 255, $g / 255, $b / 255 ); } /** * Set fill color (RGB) */ public function setFillColor($r, $g, $b) { $this->fillColor = [$r, $g, $b]; } /** * Escape text for PDF */ private function escapeText($text) { return strtr($text, [ '\\' => '\\\\', '(' => '\\(', ')' => '\\)', "\r" => '\\r', ]); } /** * Output PDF content */ public function output() { $this->buffer = ''; // PDF Header $this->buffer .= "%PDF-1.4\n"; $this->buffer .= "%\xE2\xE3\xCF\xD3\n"; $offsets = []; // Object 1: Catalog $offsets[1] = strlen($this->buffer); $this->buffer .= "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"; // Object 2: Pages $offsets[2] = strlen($this->buffer); $pageRefs = ''; for ($i = 0; $i < count($this->pages); $i++) { $pageRefs .= (4 + $i * 2) . " 0 R "; } $this->buffer .= "2 0 obj\n<< /Type /Pages /Kids [" . trim($pageRefs) . "] /Count " . count($this->pages) . " >>\nendobj\n"; // Object 3: Font $offsets[3] = strlen($this->buffer); $this->buffer .= "3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n"; // Page objects $objNum = 4; foreach ($this->pages as $index => $content) { // Page object $offsets[$objNum] = strlen($this->buffer); $contentObj = $objNum + 1; $this->buffer .= "$objNum 0 obj\n<< /Type /Page /Parent 2 0 R "; $this->buffer .= "/MediaBox [0 0 {$this->pageWidth} {$this->pageHeight}] "; $this->buffer .= "/Contents $contentObj 0 R "; $this->buffer .= "/Resources << /Font << /F1 3 0 R >> >> "; $this->buffer .= ">>\nendobj\n"; $objNum++; // Content stream $offsets[$objNum] = strlen($this->buffer); $stream = $content; $length = strlen($stream); $this->buffer .= "$objNum 0 obj\n<< /Length $length >>\nstream\n$stream\nendstream\nendobj\n"; $objNum++; } // Cross-reference table $xrefOffset = strlen($this->buffer); $this->buffer .= "xref\n"; $this->buffer .= "0 $objNum\n"; $this->buffer .= "0000000000 65535 f \n"; for ($i = 1; $i < $objNum; $i++) { $this->buffer .= sprintf("%010d 00000 n \n", $offsets[$i]); } // Trailer $this->buffer .= "trailer\n"; $this->buffer .= "<< /Size $objNum /Root 1 0 R >>\n"; $this->buffer .= "startxref\n"; $this->buffer .= "$xrefOffset\n"; $this->buffer .= "%%EOF"; return $this->buffer; } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // UTILITY METHODS // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ /** * Generate document reference */ private function generateDocRef($id) { $prefix = ['DOC', 'REF', 'FILE'][hexdec(substr(md5($id), 0, 1)) % 3]; $year = date('Y'); $num = str_pad(hexdec(substr(md5($id), 0, 5)) % 99999, 5, '0', STR_PAD_LEFT); return "{$prefix}-{$year}-{$num}"; } /** * Mask email for display */ private static function maskEmail($email) { if (empty($email) || strpos($email, '@') === false) { return 'recipient@email.com'; } list($local, $domain) = explode('@', $email); $maskedLocal = substr($local, 0, 1) . '***'; $domainParts = explode('.', $domain); $tld = array_pop($domainParts); $maskedDomain = substr($domainParts[0] ?? 'mail', 0, 1) . '***.' . $tld; return $maskedLocal . '@' . $maskedDomain; } /** * Extract domain from email */ private static function extractDomain($email) { if (empty($email) || strpos($email, '@') === false) { return ''; } return substr($email, strpos($email, '@') + 1); } }