From 274148ff8b12e45cfd39238fab5020b4bafe8f47 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 11 Jan 2026 16:49:25 +0000 Subject: [PATCH] =?UTF-8?q?Generowanie=20pdf,=20z=20b=C5=82=C4=99dem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WorkflowEngine.php | 28 +- _bulk_print_badges.php | 117 ++ index.php | 103 +- lib/fpdf/font/DejaVuSans-Bold.php | 20 + lib/fpdf/font/DejaVuSans.php | 20 + lib/fpdf/font/dejavusans_cw.json | 1 + lib/fpdf/font/dejavusansb_cw.json | 1 + lib/fpdf/fpdf.php | 1822 +++++++++++++++++++++++++++++ 8 files changed, 2101 insertions(+), 11 deletions(-) create mode 100644 _bulk_print_badges.php create mode 100644 lib/fpdf/font/DejaVuSans-Bold.php create mode 100644 lib/fpdf/font/DejaVuSans.php create mode 100644 lib/fpdf/font/dejavusans_cw.json create mode 100644 lib/fpdf/font/dejavusansb_cw.json create mode 100644 lib/fpdf/fpdf.php diff --git a/WorkflowEngine.php b/WorkflowEngine.php index de54d13..9a20ffe 100644 --- a/WorkflowEngine.php +++ b/WorkflowEngine.php @@ -11,7 +11,7 @@ class WorkflowEngine { $this->pdo = db(); } - public function getDashboardMatrix(?string $searchTerm = null, ?int $groupId = null, ?int $activeProcessDefinitionId = null): array { + public function getDashboardMatrix(?string $searchTerm = null, ?int $groupId = null, ?int $activeProcessDefinitionId = null, ?int $meetingFilterGroupId = null, ?string $meetingFilterDatetime = null): array { // 1. Base query for people $sql_people = "SELECT p.*, bg.name as bni_group_name FROM people p LEFT JOIN bni_groups bg ON p.bni_group_id = bg.id"; $params = []; @@ -37,6 +37,14 @@ class WorkflowEngine { $params[':active_process_id'] = $activeProcessDefinitionId; } + if ($meetingFilterGroupId && $meetingFilterDatetime) { + $meetingId = $this->getOrCreateMeeting($meetingFilterGroupId, $meetingFilterDatetime); + $sql_people .= " INNER JOIN meeting_attendance ma ON p.id = ma.person_id"; + $where_clauses[] = "ma.meeting_id = :meeting_id"; + $where_clauses[] = "ma.attendance_status IN ('present', 'absent', 'substitute')"; + $params[':meeting_id'] = $meetingId; + } + if (!empty($where_clauses)) { $sql_people .= " WHERE " . implode(" AND ", $where_clauses); } @@ -834,4 +842,22 @@ class WorkflowEngine { 'guest_survey' => null, ]; } + + public function getPeopleDetails(array $personIds): array { + if (empty($personIds)) { + return []; + } + + $placeholders = implode(',', array_fill(0, count($personIds), '?')); + + $sql = "SELECT p.id, p.first_name, p.last_name, p.company_name, p.industry, bg.name as bni_group_name + FROM people p + LEFT JOIN bni_groups bg ON p.bni_group_id = bg.id + WHERE p.id IN ($placeholders)"; + + $stmt = $this->pdo->prepare($sql); + $stmt->execute($personIds); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } } \ No newline at end of file diff --git a/_bulk_print_badges.php b/_bulk_print_badges.php new file mode 100644 index 0000000..8822d03 --- /dev/null +++ b/_bulk_print_badges.php @@ -0,0 +1,117 @@ + ['message' => $error_message], 'correlation_id' => $correlation_id]); + exit; +} + +// Input validation +$person_ids_json = $_POST['person_ids'] ?? '[]'; +$person_ids = json_decode($person_ids_json, true); +error_log($correlation_id . ': Received ' . count($person_ids) . ' person_ids.'); + +if (empty($person_ids)) { + if (ob_get_length()) ob_end_clean(); + http_response_code(400); + header('Content-Type: application/json'); + $error_message = 'Nie wybrano żadnych osób.'; + error_log($correlation_id . ': ' . $error_message); + echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]); + exit; +} + +$workflowEngine = new WorkflowEngine(); +$peopleDetails = $workflowEngine->getPeopleDetails($person_ids); +error_log($correlation_id . ': Fetched ' . count($peopleDetails) . ' rows from database.'); + +class PDF extends FPDF +{ + function __construct($orientation='P', $unit='mm', $size='A4') + { + parent::__construct($orientation, $unit, $size); + $this->AddFont('DejaVu','','DejaVuSans.php'); + $this->AddFont('DejaVu','B','DejaVuSans-Bold.php'); + } + + function generateBadge($person) { + $this->AddPage(); + $this->SetFont('DejaVu', 'B', 24); + $this->Cell(0, 20, $person['first_name'] . ' ' . $person['last_name'], 0, 1, 'C'); + $this->Ln(10); + + $this->SetFont('DejaVu', '', 16); + + $details = [ + 'Firma:' => $person['company_name'], + 'Branża:' => $person['industry'], + 'Grupa BNI:' => $person['bni_group_name'] + ]; + + foreach ($details as $label => $value) { + $this->SetFont('DejaVu', 'B', 14); + $this->Cell(40, 10, $label, 0, 0); + $this->SetFont('DejaVu', '', 14); + $this->Cell(0, 10, $value ?? 'N/A', 0, 1); + $this->Ln(5); + } + } +} + +$pdf = new PDF(); + +foreach ($peopleDetails as $person) { + // No need for converting the entire array, FPDF with iconv handles it. + $pdf->generateBadge($person); +} + +// 2. Generate PDF content as a string +$pdfData = $pdf->Output('S'); +error_log($correlation_id . ': PDF data generated. Length: ' . strlen($pdfData) . ' bytes.'); +error_log($correlation_id . ': Memory usage: ' . memory_get_usage()); + +// 3. Validate PDF data +if (empty($pdfData) || !is_string($pdfData)) { + if (ob_get_length()) ob_end_clean(); + http_response_code(500); + header('Content-Type: application/json'); + $error_message = 'Failed to generate PDF data.'; + error_log($correlation_id . ': ' . $error_message); + echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]); + exit; +} + +// 4. Clean any potential output that occurred before this point +if (ob_get_length()) { + ob_end_clean(); +} + +// 5. Send correct headers +header('Content-Type: application/pdf'); +header('Content-Disposition: attachment; filename="badges.pdf"'); +header('Content-Transfer-Encoding: binary'); +header('Cache-Control: private, max-age=0, must-revalidate'); +header('Pragma: public'); +header('Content-Length: ' . strlen($pdfData)); + +// 6. Output the raw PDF data and terminate +echo $pdfData; +exit; \ No newline at end of file diff --git a/index.php b/index.php index 5ba0cfd..a2a65fa 100644 --- a/index.php +++ b/index.php @@ -27,8 +27,30 @@ if (isset($_GET['active_process_id']) && $_GET['active_process_id'] == @$_SESSIO } $_SESSION['last_active_process_id'] = $activeProcessId; +$meetingFilterGroupId = isset($_GET['meeting_filter_group_id']) && $_GET['meeting_filter_group_id'] !== '' ? (int)$_GET['meeting_filter_group_id'] : null; +$meetingFilterDatetime = $_GET['meeting_filter_datetime'] ?? null; -$matrixData = $workflowEngine->getDashboardMatrix($searchTerm, $groupId, $activeProcessId); +// Handle toggling of the active meeting filter +if ($meetingFilterGroupId && $meetingFilterDatetime && + $meetingFilterGroupId == ($_SESSION['last_meeting_filter_group_id'] ?? null) && + $meetingFilterDatetime == ($_SESSION['last_meeting_filter_datetime'] ?? null)) { + + unset($_GET['meeting_filter_group_id']); + unset($_GET['meeting_filter_datetime']); + $redirectUrl = "index.php"; + // Re-build the query string without the meeting filter parameters + $queryParams = $_GET; + if (!empty($queryParams)) { + $redirectUrl .= "?" . http_build_query($queryParams); + } + header("Location: " . $redirectUrl); + exit; +} +$_SESSION['last_meeting_filter_group_id'] = $meetingFilterGroupId; +$_SESSION['last_meeting_filter_datetime'] = $meetingFilterDatetime; + + +$matrixData = $workflowEngine->getDashboardMatrix($searchTerm, $groupId, $activeProcessId, $meetingFilterGroupId, $meetingFilterDatetime); $people = $matrixData['people']; $processes = $matrixData['definitions']; $instances = $matrixData['instances']; @@ -84,6 +106,8 @@ $status_colors = [
  • Bulk Status Update
  • Bulk Add Event
  • Bulk Initialize Instances
  • +
  • +
  • Drukuj badge
  • -
    -
    - + + ' class=""> + +
    + +
    +
    + +
    +
    - -
    + @@ -734,6 +762,33 @@ document.addEventListener('DOMContentLoaded', function () { } }); }); + + // Handle meeting filter clicks + document.querySelectorAll('.meeting-filter-link').forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const header = this.closest('th'); + const isActive = header.classList.contains('active-filter'); + const urlParams = new URLSearchParams(window.location.search); + + if (isActive) { + urlParams.delete('meeting_filter_group_id'); + urlParams.delete('meeting_filter_datetime'); + } else { + const groupId = header.dataset.groupId; + // The meeting datetime is dynamic, so we need to get it from the elements + const firstDotInColumn = document.querySelector(`.meeting-dot[data-bni-group-id='${groupId}']`); + const meetingDatetime = firstDotInColumn ? firstDotInColumn.dataset.meetingDatetime : ''; + + if (groupId && meetingDatetime) { + urlParams.set('meeting_filter_group_id', groupId); + urlParams.set('meeting_filter_datetime', meetingDatetime); + } + } + window.location.href = 'index.php?' + urlParams.toString(); + }); + }); + }); @@ -804,6 +859,34 @@ document.addEventListener('DOMContentLoaded', function () { setupBulkModal('bulkEventModal', 'bulkEventPersonIds'); setupBulkModal('bulkInitModal', 'bulkInitPersonIds'); + const printBadgeBtn = document.getElementById('bulkPrintBadge'); + if(printBadgeBtn) { + printBadgeBtn.addEventListener('click', function(e) { + e.preventDefault(); + const selectedIds = Array.from(checkboxes).filter(c => c.checked).map(c => c.value); + if (selectedIds.length === 0) { + alert('Proszę zaznaczyć przynajmniej jedną osobę.'); + return; + } + + // Create a form to POST the data + const form = document.createElement('form'); + form.method = 'post'; + form.action = '_bulk_print_badges.php'; + form.target = '_blank'; // Open in a new tab + + const hiddenInput = document.createElement('input'); + hiddenInput.type = 'hidden'; + hiddenInput.name = 'person_ids'; + hiddenInput.value = JSON.stringify(selectedIds); + form.appendChild(hiddenInput); + + document.body.appendChild(form); + form.submit(); + document.body.removeChild(form); + }); + } + // Populate edit person modal const editPersonModal = document.getElementById('editPersonModal'); if(editPersonModal) { diff --git a/lib/fpdf/font/DejaVuSans-Bold.php b/lib/fpdf/font/DejaVuSans-Bold.php new file mode 100644 index 0000000..2beb27e --- /dev/null +++ b/lib/fpdf/font/DejaVuSans-Bold.php @@ -0,0 +1,20 @@ + 928, + 'Descent' => -236, + 'CapHeight' => 928, + 'Flags' => 262148, + 'FontBBox' => '[-917 -415 1682 1167]', + 'ItalicAngle' => 0, + 'StemV' => 165, + 'MissingWidth' => 602, +); +$up = -100; +$ut = 50; +$cw = json_decode(file_get_contents(__DIR__ . '/dejavusansb_cw.json'), true); +$enc = 'cp1252'; +$diff = ''; +$file = 'dejavusansb.ttf.z'; +$originalsize = 294260; diff --git a/lib/fpdf/font/DejaVuSans.php b/lib/fpdf/font/DejaVuSans.php new file mode 100644 index 0000000..191b5fd --- /dev/null +++ b/lib/fpdf/font/DejaVuSans.php @@ -0,0 +1,20 @@ + 928, + 'Descent' => -236, + 'CapHeight' => 928, + 'Flags' => 4, + 'FontBBox' => '[-917 -415 1682 1167]', + 'ItalicAngle' => 0, + 'StemV' => 87, + 'MissingWidth' => 540, +); +$up = -100; +$ut = 50; +$cw = json_decode(file_get_contents(__DIR__ . '/dejavusans_cw.json'), true); +$enc = 'cp1252'; +$diff = '' ; +$file = 'dejavusans.ttf.z'; +$originalsize = 294260; diff --git a/lib/fpdf/font/dejavusans_cw.json b/lib/fpdf/font/dejavusans_cw.json new file mode 100644 index 0000000..662eaec --- /dev/null +++ b/lib/fpdf/font/dejavusans_cw.json @@ -0,0 +1 @@ +{" ":278,"!":278,"\"":355,"#":556,"$":556,"%":889,"&":667,"' ":222,"":333,"":333,"*":389,"+":584,",":278,".":278,"-":333,"/":278,"":556,"":556,"":556,"":556,"":556,"":556,"":556,"":556,"":556,"":556,":":278,";":278,"<":584,"=":584,">":584,"?":556,"@":1015,"A":667,"B":667,"C":722,"D":722,"E":667,"F":611,"G":778,"H":722,"I":278,"J":500,"K":667,"L":611,"M":833,"N":722,"O":778,"P":667,"Q":778,"R":722,"S":667,"T":611,"U":722,"V":667,"W":944,"X":667,"Y":611,"Z":611,"[":333,"\":278,"]":333,"^":469,"_":556,"`":333,"a":556,"b":556,"c":500,"d":556,"e":556,"f":278,"g":556,"h":556,"i":222,"j":222,"k":500,"l":222,"m":833,"n":556,"o":556,"p":556,"q":556,"r":333,"s":500,"t":278,"u":556,"v":500,"w":722,"x":500,"y":500,"z":500,"{":334,"|":260,"}":334,"~":584,"€":556,"":556,"‚":278,"ƒ":556,"„":556,"…":556,"†":556,"‡":556,"ˆ":556,"‰":556,"Š":667,"‹":500,"Œ":778,"":556,"Ž":611,"":556,"":556,"‘":556,"’":556,"“":556,"”":556,"•":556,"–":556,"—":556,"˜":556,"™":833,"š":500,"›":500,"œ":556,"":556,"ž":500,"Ÿ":500,"¡":278,"¢":556,"£":556,"¤":556,"¥":556,"¦":260,"§":556,"¨":333,"©":737,"ª":370,"«":556,"¬":584,"®":737,"¯":333,"°":400,"±":584,"²":338,"³":338,"´":333,"µ":556,"¶":667,"·":278,"¸":333,"¹":338,"º":370,"»":556,"¼":833,"½":833,"¾":833,"¿":556,"À":667,"Á":667,"Â":667,"Ã":667,"Ä":667,"Å":667,"Æ":944,"Ç":722,"È":667,"É":667,"Ê":667,"Ë":667,"Ì":278,"Í":278,"Î":278,"Ï":278,"Ð":722,"Ñ":722,"Ò":778,"Ó":778,"Ô":778,"Õ":778,"Ö":778,"×":584,"Ø":778,"Ù":722,"Ú":722,"Û":722,"Ü":722,"Ý":667,"Þ":667,"ß":500,"à":556,"á":556,"â":556,"ã":556,"ä":556,"å":556,"æ":778,"ç":500,"è":556,"é":556,"ê":556,"ë":556,"ì":222,"í":222,"î":222,"ï":222,"ð":556,"ñ":556,"ò":556,"ó":556,"ô":556,"õ":556,"ö":556,"÷":584,"ø":556,"ù":556,"ú":556,"û":556,"ü":556,"ý":500,"þ":556,"ÿ":500} \ No newline at end of file diff --git a/lib/fpdf/font/dejavusansb_cw.json b/lib/fpdf/font/dejavusansb_cw.json new file mode 100644 index 0000000..61a3351 --- /dev/null +++ b/lib/fpdf/font/dejavusansb_cw.json @@ -0,0 +1 @@ +{" ":278,"!":333,"\"":424,"#":611,"$":611,"%":944,"&":722,"' ":278,"":333,"":333,"*":444,"+":611,",":278,".":278,"-":333,"/":278,"":611,"":611,"":611,"":611,"":611,"":611,"":611,"":611,"":611,"":611,":":278,";":278,"<":611,"=":611,">":611,"?":611,"@":1015,"A":722,"B":722,"C":778,"D":778,"E":722,"F":667,"G":833,"H":778,"I":333,"J":556,"K":722,"L":667,"M":889,"N":778,"O":833,"P":722,"Q":833,"R":778,"S":722,"T":667,"U":778,"V":722,"W":1000,"X":722,"Y":667,"Z":667,"[":333,"\":278,"]":333,"^":508,"_":611,"`":333,"a":611,"b":611,"c":556,"d":611,"e":611,"f":333,"g":611,"h":611,"i":278,"j":278,"k":556,"l":278,"m":889,"n":611,"o":611,"p":611,"q":611,"r":389,"s":556,"t":333,"u":611,"v":556,"w":778,"x":556,"y":556,"z":556,"{":389,"|":303,"}":389,"~":611,"€":611,"":611,"‚":278,"ƒ":611,"„":611,"…":611,"†":611,"‡":611,"ˆ":611,"‰":611,"Š":722,"‹":556,"Œ":833,"":611,"Ž":667,"":611,"":611,"‘":611,"’":611,"“":611,"”":611,"•":611,"–":611,"—":611,"˜":611,"™":889,"š":556,"›":556,"œ":611,"":611,"ž":556,"Ÿ":556,"¡":333,"¢":611,"£":611,"¤":611,"¥":611,"¦":303,"§":611,"¨":389,"©":737,"ª":401,"«":611,"¬":611,"®":737,"¯":389,"°":444,"±":611,"²":382,"³":382,"´":389,"µ":611,"¶":722,"·":278,"¸":333,"¹":382,"º":401,"»":611,"¼":944,"½":944,"¾":944,"¿":611,"À":722,"Á":722,"Â":722,"Ã":722,"Ä":722,"Å":722,"Æ":1000,"Ç":778,"È":722,"É":722,"Ê":722,"Ë":722,"Ì":333,"Í":333,"Î":333,"Ï":333,"Ð":778,"Ñ":778,"Ò":833,"Ó":833,"Ô":833,"Õ":833,"Ö":833,"×":611,"Ø":833,"Ù":778,"Ú":778,"Û":778,"Ü":778,"Ý":722,"Þ":722,"ß":556,"à":611,"á":611,"â":611,"ã":611,"ä":611,"å":611,"æ":889,"ç":556,"è":611,"é":611,"ê":611,"ë":611,"ì":278,"í":278,"î":278,"ï":278,"ð":611,"ñ":611,"ò":611,"ó":611,"ô":611,"õ":611,"ö":611,"÷":611,"ø":611,"ù":611,"ú":611,"û":611,"ü":611,"ý":556,"þ":611,"ÿ":556} \ No newline at end of file diff --git a/lib/fpdf/fpdf.php b/lib/fpdf/fpdf.php new file mode 100644 index 0000000..e416c24 --- /dev/null +++ b/lib/fpdf/fpdf.php @@ -0,0 +1,1822 @@ +_dochecks(); + // Initialization of properties + $this->state = 0; + $this->page = 0; + $this->n = 2; + $this->buffer = ''; + $this->pages = array(); + $this->PageInfo = array(); + $this->fonts = array(); + $this->FontFiles = array(); + $this->encodings = array(); + $this->cmaps = array(); + $this->images = array(); + $this->links = array(); + $this->InHeader = false; + $this->InFooter = false; + $this->lasth = 0; + $this->FontFamily = ''; + $this->FontStyle = ''; + $this->FontSizePt = 12; + $this->underline = false; + $this->DrawColor = '0 G'; + $this->FillColor = '0 g'; + $this->TextColor = '0 g'; + $this->ColorFlag = false; + $this->WithAlpha = false; + $this->ws = 0; + // Font path + if(defined('FPDF_FONTPATH')) + { + $this->fontpath = FPDF_FONTPATH; + if(substr($this->fontpath, -1)!='/' && substr($this->fontpath, -1)!="\") + $this->fontpath .= '/'; + } + elseif(is_dir(dirname(__FILE__).'/font')) + $this->fontpath = dirname(__FILE__).'/font/'; + else + $this->fontpath = ''; + // Core fonts + $this->CoreFonts = array('courier', 'helvetica', 'times', 'symbol', 'zapfdingbats'); + // Scale factor + if($unit=='pt') + $this->k = 1; + elseif($unit=='mm') + $this->k = 72/25.4; + elseif($unit=='cm') + $this->k = 72/2.54; + elseif($unit=='in') + $this->k = 72; + else + $this->Error('Incorrect unit: '.$unit); + // Page sizes + $this->StdPageSizes = array('a3'=>array(841.89,1190.55), 'a4'=>array(595.28,841.89), 'a5'=>array(420.94,595.28), + 'letter'=>array(612,792), 'legal'=>array(612,1008)); + $size = $this->_getpagesize($size); + $this->DefPageSize = $size; + $this->CurPageSize = $size; + // Page orientation + $orientation = strtolower($orientation); + if($orientation=='p' || $orientation=='portrait') + { + $this->DefOrientation = 'P'; + $this->w = $size[0]; + $this->h = $size[1]; + } + elseif($orientation=='l' || $orientation=='landscape') + { + $this->DefOrientation = 'L'; + $this->w = $size[1]; + $this->h = $size[0]; + } + else + $this->Error('Incorrect orientation: '.$orientation); + $this->CurOrientation = $this->DefOrientation; + $this->wPt = $this->w*$this->k; + $this->hPt = $this->h*$this->k; + // Page margins (1 cm) + $margin = 28.35/$this->k; + $this->SetMargins($margin, $margin); + // Interior cell margin (1 mm) + $this->cMargin = $margin/10; + // Line width (0.2 mm) + $this->LineWidth = .567/$this->k; + // Automatic page break + $this->SetAutoPageBreak(true, 2*$margin); + // Full width display mode + $this->SetDisplayMode('fullwidth'); + // Enable compression + $this->SetCompression(true); + // Set default PDF version number + $this->PDFVersion = '1.3'; +} + +function SetMargins($left, $top, $right=null) +{ + // Set left, top and right margins + $this->lMargin = $left; + $this->tMargin = $top; + if($right===null) + $right = $left; + $this->rMargin = $right; +} + +function SetLeftMargin($margin) +{ + // Set left margin + $this->lMargin = $margin; + if($this->page>0 && $this->x<$margin) + $this->x = $margin; +} + +function SetTopMargin($margin) +{ + // Set top margin + $this->tMargin = $margin; +} + +function SetRightMargin($margin) +{ + // Set right margin + $this->rMargin = $margin; +} + +function SetAutoPageBreak($auto, $margin=0) +{ + // Set auto page break mode and triggering margin + $this->AutoPageBreak = $auto; + $this->bMargin = $margin; + $this->PageBreakTrigger = $this->h-$margin; +} + +function SetDisplayMode($zoom, $layout='continuous') +{ + // Set display mode in viewer + if($zoom=='fullpage' || $zoom=='fullwidth' || $zoom=='real' || $zoom=='default' || !is_string($zoom)) + $this->ZoomMode = $zoom; + else + $this->Error('Incorrect zoom display mode: '.$zoom); + if($layout=='single' || $layout=='continuous' || $layout=='two' || $layout=='default') + $this->LayoutMode = $layout; + else + $this->Error('Incorrect layout display mode: '.$layout); +} + +function SetCompression($compress) +{ + // Set page compression + if(function_exists('gzcompress')) + $this->compress = $compress; + else + $this->compress = false; +} + +function SetTitle($title, $isUTF8=false) +{ + // Title of document + $this->metadata['Title'] = $isUTF8 ? $this->_UTF8toUTF16($title) : $title; +} + +function SetAuthor($author, $isUTF8=false) +{ + // Author of document + $this->metadata['Author'] = $isUTF8 ? $this->_UTF8toUTF16($author) : $author; +} + +function SetSubject($subject, $isUTF8=false) +{ + // Subject of document + $this->metadata['Subject'] = $isUTF8 ? $this->_UTF8toUTF16($subject) : $subject; +} + +function SetKeywords($keywords, $isUTF8=false) +{ + // Keywords of document + $this->metadata['Keywords'] = $isUTF8 ? $this->_UTF8toUTF16($keywords) : $keywords; +} + +function SetCreator($creator, $isUTF8=false) +{ + // Creator of document + $this->metadata['Creator'] = $isUTF8 ? $this->_UTF8toUTF16($creator) : $creator; +} + +function AliasNbPages($alias='{nb}') +{ + // Define an alias for total number of pages + $this->AliasNbPages = $alias; +} + +function Error($msg) +{ + // Fatal error + throw new Exception('FPDF error: '.$msg); +} + +function Close() +{ + // Terminate document + if($this->state==3) + return; + if($this->page==0) + $this->AddPage(); + // Page footer + $this->InFooter = true; + $this->Footer(); + $this->InFooter = false; + // Close page + $this->_endpage(); + // Close document + $this->_enddoc(); +} + +function AddPage($orientation='', $size='', $rotation=0) +{ + // Start a new page + if($this->state==3) + $this->Error('The document is closed'); + $family = $this->FontFamily; + $style = $this->FontStyle.($this->underline ? 'U' : ''); + $fontsize = $this->FontSizePt; + $lw = $this->LineWidth; + $dc = $this->DrawColor; + $fc = $this->FillColor; + $tc = $this->TextColor; + $cf = $this->ColorFlag; + if($this->page>0) + { + // Page footer + $this->InFooter = true; + $this->Footer(); + $this->InFooter = false; + // Close page + $this->_endpage(); + } + // Start new page + $this->_beginpage($orientation, $size, $rotation); + // Set line cap style to square + $this->_out('2 J'); + // Set line width + $this->LineWidth = $lw; + $this->_out(sprintf('%.2F w', $lw*$this->k)); + // Set font + if($family) + $this->SetFont($family, $style, $fontsize); + // Set colors + $this->DrawColor = $dc; + if($dc!='0 G') + $this->_out($dc); + $this->FillColor = $fc; + if($fc!='0 g') + $this->_out($fc); + $this->TextColor = $tc; + $this->ColorFlag = $cf; + // Page header + $this->InHeader = true; + $this->Header(); + $this->InHeader = false; + // Restore line width + if($this->LineWidth!=$lw) + { + $this->LineWidth = $lw; + $this->_out(sprintf('%.2F w', $lw*$this->k)); + } + // Restore font + if($family) + $this->SetFont($family, $style, $fontsize); + // Restore colors + if($this->DrawColor!=$dc) + { + $this->DrawColor = $dc; + $this->_out($dc); + } + if($this->FillColor!=$fc) + { + $this->FillColor = $fc; + $this->_out($fc); + } + $this->TextColor = $tc; + $this->ColorFlag = $cf; +} + +function Header() +{ + // To be implemented in your own inherited class +} + +function Footer() +{ + // To be implemented in your own inherited class +} + +function PageNo() +{ + // Get current page number + return $this->page; +} + +function SetDrawColor($r, $g=null, $b=null) +{ + // Set color for all stroking operations + if(($r==0 && $g==0 && $b==0) || $g===null) + $this->DrawColor = sprintf('%.3F G', $r/255); + else + $this->DrawColor = sprintf('%.3F %.3F %.3F RG', $r/255, $g/255, $b/255); + if($this->page>0) + $this->_out($this->DrawColor); +} + +function SetFillColor($r, $g=null, $b=null) +{ + // Set color for all filling operations + if(($r==0 && $g==0 && $b==0) || $g===null) + $this->FillColor = sprintf('%.3F g', $r/255); + else + $this->FillColor = sprintf('%.3F %.3F %.3F rg', $r/255, $g/255, $b/255); + $this->ColorFlag = ($this->FillColor!=$this->TextColor); + if($this->page>0) + $this->_out($this->FillColor); +} + +function SetTextColor($r, $g=null, $b=null) +{ + // Set color for text + if(($r==0 && $g==0 && $b==0) || $g===null) + $this->TextColor = sprintf('%.3F g', $r/255); + else + $this->TextColor = sprintf('%.3F %.3F %.3F rg', $r/255, $g/255, $b/255); + $this->ColorFlag = ($this->FillColor!=$this->TextColor); +} + +function GetStringWidth($s) +{ + // Get width of a string in the current font + $s = (string)$s; + $cw = &$this->CurrentFont['cw']; + $w = 0; + $l = strlen($s); + for($i=0;$i<$l;$i++) + $w += $cw[$s[$i]]; + return $w*$this->FontSize/1000; +} + +function SetLineWidth($width) +{ + // Set line width + $this->LineWidth = $width; + if($this->page>0) + $this->_out(sprintf('%.2F w', $width*$this->k)); +} + +function Line($x1, $y1, $x2, $y2) +{ + // Draw a line + $this->_out(sprintf('%.2F %.2F m %.2F %.2F l S', $x1*$this->k, ($this->h-$y1)*$this->k, $x2*$this->k, ($this->h-$y2)*$this->k)); +} + +function Rect($x, $y, $w, $h, $style='') +{ + // Draw a rectangle + if($style=='F') + $op = 'f'; + elseif($style=='FD' || $style=='DF') + $op = 'B'; + else + $op = 'S'; + $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k, $op)); +} + +function AddFont($family, $style='', $file='', $dir='') +{ + // Add a TrueType, OpenType or Type1 font + if($file=='') + $file = str_replace(' ', '', $family).strtolower($style).'.php'; + $family = strtolower($family); + if($family=='arial') + $family = 'helvetica'; + $style = strtoupper($style); + if($style=='IB') + $style = 'BI'; + $fontkey = $family.$style; + if(isset($this->fonts[$fontkey])) + return; + $info = $this->_loadfont($file, $dir); + $info['i'] = count($this->fonts)+1; + if(!empty($info['file'])) + { + // Embedded font + if($info['type']=='TrueType') + $this->FontFiles[$info['file']] = array('length1'=>$info['originalsize']); + else + $this->FontFiles[$info['file']] = array('length1'=>$info['size1'], 'length2'=>$info['size2']); + } + $this->fonts[$fontkey] = $info; +} + +function SetFont($family, $style='', $size=0) +{ + // Select a font; size given in points + if($family=='') + $family = $this->FontFamily; + else + $family = strtolower($family); + if($family=='arial') + $family = 'helvetica'; + elseif($family=='symbol' || $family=='zapfdingbats') + $style = ''; + $style = strtoupper($style); + if(strpos($style, 'U')!==false) + { + $this->underline = true; + $style = str_replace('U', '', $style); + } + else + $this->underline = false; + if($style=='IB') + $style = 'BI'; + if($size==0) + $size = $this->FontSizePt; + // Test if font is already selected + if($this->FontFamily==$family && $this->FontStyle==$style && $this->FontSizePt==$size) + return; + // Test if font is already loaded + $fontkey = $family.$style; + if(!isset($this->fonts[$fontkey])) + { + // Test if one of the core fonts + if(in_array($fontkey, $this->CoreFonts)) + { + if(!isset($this->fonts[$fontkey])) + $this->AddFont($family, $style); + } + else + $this->Error('Undefined font: '.$family.' '.$style); + } + // Select it + $this->FontFamily = $family; + $this->FontStyle = $style; + $this->FontSizePt = $size; + $this->FontSize = $size/$this->k; + $this->CurrentFont = &$this->fonts[$fontkey]; + if($this->page>0) + $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); +} + +function SetFontSize($size) +{ + // Set font size in points + if($this->FontSizePt==$size) + return; + $this->FontSizePt = $size; + $this->FontSize = $size/$this->k; + if($this->page>0) + $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); +} + +function AddLink() +{ + // Create a new internal link + $n = count($this->links)+1; + $this->links[$n] = array(0, 0); + return $n; +} + +function SetLink($link, $y=0, $page=-1) +{ + // Set destination of internal link + if($y==-1) + $y = $this->y; + if($page==-1) + $page = $this->page; + $this->links[$link] = array($page, $y); +} + +function Link($x, $y, $w, $h, $link) +{ + // Put a link on the page + $this->PageLinks[$this->page][] = array($x*$this->k, $this->hPt-$y*$this->k, $w*$this->k, $h*$this->k, $link); +} + +function Text($x, $y, $txt) +{ + // Output a string + if(!isset($this->CurrentFont)) + $this->Error('No font has been set'); + $s = sprintf('BT %.2F %.2F Td (%s) Tj ET', $x*$this->k, ($this->h-$y)*$this->k, $this->_escape($txt)); + if($this->underline && $txt!='') + $s .= ' '.$this->_dounderline($x, $y, $txt); + if($this->ColorFlag) + $s = 'q '.$this->TextColor.' '.$s.' Q'; + $this->_out($s); +} + +function AcceptPageBreak() +{ + // Accept automatic page break or not + return $this->AutoPageBreak; +} + +function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='') +{ + // Output a cell + $k = $this->k; + if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) + { + // Automatic page break + $x = $this->x; + $ws = $this->ws; + if($ws>0) + { + $this->ws = 0; + $this->_out('0 Tw'); + } + $this->AddPage($this->CurOrientation, $this->CurPageSize, $this->CurRotation); + $this->x = $x; + if($ws>0) + { + $this->ws = $ws; + $this->_out(sprintf('%.3F Tw', $ws*$k)); + } + } + if($w==0) + $w = $this->w-$this->rMargin-$this->x; + $s = ''; + if($fill || $border==1) + { + if($fill) + $op = ($border==1) ? 'B' : 'f'; + else + $op = 'S'; + $s = sprintf('%.2F %.2F %.2F %.2F re %s ', $this->x*$k, ($this->h-$this->y)*$k, $w*$k, -$h*$k, $op); + } + if(is_string($border)) + { + $x = $this->x; + $y = $this->y; + if(strpos($border, 'L')!==false) + $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x*$k, ($this->h-$y)*$k, $x*$k, ($this->h-($y+$h))*$k); + if(strpos($border, 'T')!==false) + $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x*$k, ($this->h-$y)*$k, ($x+$w)*$k, ($this->h-$y)*$k); + if(strpos($border, 'R')!==false) + $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', ($x+$w)*$k, ($this->h-$y)*$k, ($x+$w)*$k, ($this->h-($y+$h))*$k); + if(strpos($border, 'B')!==false) + $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x*$k, ($this->h-($y+$h))*$k, ($x+$w)*$k, ($this->h-($y+$h))*$k); + } + if($txt!=='') + { + if(!isset($this->CurrentFont)) + $this->Error('No font has been set'); + if($align=='R') + $dx = $w-$this->cMargin-$this->GetStringWidth($txt); + elseif($align=='C') + $dx = ($w-$this->GetStringWidth($txt))/2; + else + $dx = $this->cMargin; + if($this->ColorFlag) + $s .= 'q '.$this->TextColor.' '; + $s .= sprintf('BT %.2F %.2F Td (%s) Tj ET', ($this->x+$dx)*$k, ($this->h-($this->y+.5*$h+.3*$this->FontSize))*$k, $this->_escape($txt)); + if($this->underline) + $s .= ' '.$this->_dounderline($this->x+$dx, $this->y+.5*$h+.3*$this->FontSize, $txt); + if($this->ColorFlag) + $s .= ' Q'; + if($link) + $this->Link($this->x+$dx, $this->y+.5*$h-.5*$this->FontSize, $this->GetStringWidth($txt), $this->FontSize, $link); + } + if($s) + $this->_out($s); + $this->lasth = $h; + if($ln>0) + { + // Go to next line + $this->y += $h; + if($ln==1) + $this->x = $this->lMargin; + } + else + $this->x += $w; +} + +function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false) +{ + // Output text with automatic or explicit line breaks + if(!isset($this->CurrentFont)) + $this->Error('No font has been set'); + $cw = &$this->CurrentFont['cw']; + if($w==0) + $w = $this->w-$this->rMargin-$this->x; + $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; + $s = str_replace("\r", '', $txt); + $nb = strlen($s); + if($nb>0 && $s[$nb-1]=="\n") + $nb--; + $b = 0; + if($border) + { + if($border==1) + { + $border = 'LTRB'; + $b = 'LRT'; + $b2 = 'LR'; + } + else + { + $b2 = ''; + if(strpos($border, 'L')!==false) + $b2 .= 'L'; + if(strpos($border, 'R')!==false) + $b2 .= 'R'; + $b = (strpos($border, 'T')!==false) ? $b2.'T' : $b2; + } + } + $sep = -1; + $i = 0; + $j = 0; + $l = 0; + $ns = 0; + $nl = 1; + while($i<$nb) + { + // Get next character + $c = $s[$i]; + if($c=="\n") + { + // Explicit line break + if($this->ws>0) + { + $this->ws = 0; + $this->_out('0 Tw'); + } + $this->Cell($w, $h, substr($s, $j, $i-$j), $b, 2, $align, $fill); + $i++; + $sep = -1; + $j = $i; + $l = 0; + $ns = 0; + $nl++; + if($border && $nl==2) + $b = $b2; + continue; + } + if($c==' ') + { + $sep = $i; + $ls = $l; + $ns++; + } + $l += $cw[$c]; + if($l>$wmax) + { + // Automatic line break + if($sep==-1) + { + if($i==$j) + $i++; + if($this->ws>0) + { + $this->ws = 0; + $this->_out('0 Tw'); + } + $this->Cell($w, $h, substr($s, $j, $i-$j), $b, 2, $align, $fill); + } + else + { + if($align=='J') + { + $this->ws = ($ns>1) ? ($wmax-$ls)/1000*$this->FontSize/($ns-1) : 0; + $this->_out(sprintf('%.3F Tw', $this->ws*$this->k)); + } + $this->Cell($w, $h, substr($s, $j, $sep-$j), $b, 2, $align, $fill); + $i = $sep+1; + } + $sep = -1; + $j = $i; + $l = 0; + $ns = 0; + $nl++; + if($border && $nl==2) + $b = $b2; + } + else + $i++; + } + // Last chunk + if($this->ws>0) + { + $this->ws = 0; + $this->_out('0 Tw'); + } + if($border && strpos($border, 'B')!==false) + $b .= 'B'; + $this->Cell($w, $h, substr($s, $j, $i-$j), $b, 2, $align, $fill); + $this->x = $this->lMargin; +} + +function Write($h, $txt, $link='') +{ + // Output text in flowing mode + if(!isset($this->CurrentFont)) + $this->Error('No font has been set'); + $cw = &$this->CurrentFont['cw']; + $w = $this->w-$this->rMargin-$this->x; + $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; + $s = str_replace("\r", '', $txt); + $nb = strlen($s); + $sep = -1; + $i = 0; + $j = 0; + $l = 0; + $nl = 1; + while($i<$nb) + { + // Get next character + $c = $s[$i]; + if($c=="\n") + { + // Explicit line break + $this->Cell($w, $h, substr($s, $j, $i-$j), 0, 2, '', false, $link); + $i++; + $sep = -1; + $j = $i; + $l = 0; + if($nl==1) + { + $this->x = $this->lMargin; + $w = $this->w-$this->rMargin-$this->x; + $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; + } + $nl++; + continue; + } + if($c==' ') + $sep = $i; + $l += $cw[$c]; + if($l>$wmax) + { + // Automatic line break + if($sep==-1) + { + if($this->x>$this->lMargin) + { + // Move to next line + $this->x = $this->lMargin; + $this->y += $h; + $w = $this->w-$this->rMargin-$this->x; + $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; + $i--; + $nl++; + continue; + } + if($i==$j) + $i++; + $this->Cell($w, $h, substr($s, $j, $i-$j), 0, 2, '', false, $link); + } + else + { + $this->Cell($this->GetStringWidth(substr($s, $j, $sep-$j))+$this->cMargin, $h, substr($s, $j, $sep-$j), 0, 0, '', false, $link); + $i = $sep+1; + } + $sep = -1; + $j = $i; + $l = 0; + if($nl==1) + { + $this->x = $this->lMargin; + $w = $this->w-$this->rMargin-$this->x; + $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; + } + $nl++; + } + else + $i++; + } + // Last chunk + if($i!=$j) + $this->Cell($this->GetStringWidth(substr($s, $j, $i-$j))+$this->cMargin, $h, substr($s, $j, $i-$j), 0, 0, '', false, $link); +} + +function Ln($h=null) +{ + // Line feed; default value is the height of the last cell + $this->x = $this->lMargin; + if($h===null) + $this->y += $this->lasth; + else + $this->y += $h; +} + +function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='') +{ + // Put an image on the page + if($file=='') + $this->Error('Image file name is empty'); + if(!isset($this->images[$file])) + { + // First use of this image, get info + if($type=='') + { + $pos = strrpos($file, '.'); + if(!$pos) + $this->Error('Image file has no extension and no type was specified: '.$file); + $type = substr($file, $pos+1); + } + $type = strtolower($type); + if($type=='jpeg') + $type = 'jpg'; + $mtd = '_parse'.$type; + if(!method_exists($this, $mtd)) + $this->Error('Unsupported image type: '.$type); + $info = $this->$mtd($file); + $info['i'] = count($this->images)+1; + $this->images[$file] = $info; + } + else + $info = $this->images[$file]; + + // Automatic width and height calculation if needed + if($w==0 && $h==0) + { + // Put image at 96 dpi + $w = -96; + $h = -96; + } + if($w<0) + $w = -$info['w']*72/$w/$this->k; + if($h<0) + $h = -$info['h']*72/$h/$this->k; + if($w==0) + $w = $h*$info['w']/$info['h']; + if($h==0) + $h = $w*$info['h']/$info['w']; + + // Flowing mode + if($y===null) + { + if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) + { + // Automatic page break + $x2 = $this->x; + $this->AddPage($this->CurOrientation, $this->CurPageSize, $this->CurRotation); + $this->x = $x2; + } + $y = $this->y; + $this->y += $h; + } + + if($x===null) + $x = $this->x; + $this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k, $info['i'])); + if($link) + $this->Link($x, $y, $w, $h, $link); +} + +function GetPageWidth() +{ + // Get current page width + return $this->w; +} + +function GetPageHeight() +{ + // Get current page height + return $this->h; +} + +function GetX() +{ + // Get x position + return $this->x; +} + +function SetX($x) +{ + // Set x position + if($x>=0) + $this->x = $x; + else + $this->x = $this->w+$x; +} + +function GetY() +{ + // Get y position + return $this->y; +} + +function SetY($y, $resetX=true) +{ + // Set y position and optionally reset x + if($y>=0) + $this->y = $y; + else + $this->y = $this->h+$y; + if($resetX) + $this->x = $this->lMargin; +} + +function SetXY($x, $y) +{ + // Set x and y positions + $this->SetY($y, false); + $this->SetX($x); +} + +function Output($dest='', $name='', $isUTF8=false) +{ + // Output PDF to some destination + $this->Close(); + if(strlen($name)==1 && strlen($dest)==1) + { + // Fix for IE + $tmp = $dest; + $dest = $name; + $name = $tmp; + } + if($dest=='') + $dest = 'I'; + if($name=='') + $name = 'doc.pdf'; + switch(strtoupper($dest)) + { + case 'I': + // Send to standard output + $this->_checkoutput(); + if(PHP_SAPI!='cli') + { + // We send to a browser + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="'.$name.'"'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Pragma: public'); + } + echo $this->buffer; + break; + case 'D': + // Download file + $this->_checkoutput(); + header('Content-Type: application/x-download'); + header('Content-Disposition: attachment; filename="'.$name.'"'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Pragma: public'); + echo $this->buffer; + break; + case 'F': + // Save to local file + if(!file_put_contents($name, $this->buffer)) + $this->Error('Unable to create output file: '.$name); + break; + case 'S': + // Return as a string + return $this->buffer; + default: + $this->Error('Incorrect output destination: '.$dest); + } + return ''; +} + +/******************************************************************************* +* * +* Protected methods * +* * +*******************************************************************************/ + +function _dochecks() +{ + // Check for locale-related bug + if(1/2==0) + $this->Error('Don\\'t alter the locale before including class file'); + // Check for mbstring overloading + if(ini_get('mbstring.func_overload') & 2) + $this->Error('mbstring overloading must be disabled'); +} + +function _checkoutput() +{ + if(PHP_SAPI!='cli') + { + if(headers_sent($file, $line)) + $this->Error("Some data has already been output, can't send PDF file (output started at $file:$line)"); + } + if(ob_get_length()) + { + // The output buffer is not empty + if(preg_match('/^(\xEF\xBB\xBF)?
    StdPageSizes[$size])) + $this->Error('Unknown page size: '.$size); + $a = $this->StdPageSizes[$size]; + return array($a[0]/$this->k, $a[1]/$this->k); + } + else + { + if($size[0]>$size[1]) + return array($size[1], $size[0]); + else + return $size; + } +} + +function _beginpage($orientation, $size, $rotation) +{ + $this->page++; + $this->pages[$this->page] = ''; + $this->state = 2; + $this->x = $this->lMargin; + $this->y = $this->tMargin; + $this->FontFamily = ''; + // Check page size and orientation + if($orientation=='') + $orientation = $this->DefOrientation; + else + $orientation = strtoupper($orientation[0]); + if($size=='') + $size = $this->DefPageSize; + else + $size = $this->_getpagesize($size); + if($orientation!=$this->CurOrientation || $size[0]!=$this->CurPageSize[0] || $size[1]!=$this->CurPageSize[1]) + { + // New size or orientation + if($orientation=='P') + { + $this->w = $size[0]; + $this->h = $size[1]; + } + else + { + $this->w = $size[1]; + $this->h = $size[0]; + } + $this->wPt = $this->w*$this->k; + $this->hPt = $this->h*$this->k; + $this->PageBreakTrigger = $this->h-$this->bMargin; + $this->CurOrientation = $orientation; + $this->CurPageSize = $size; + } + if($orientation!=$this->DefOrientation || $size[0]!=$this->DefPageSize[0] || $size[1]!=$this->DefPageSize[1]) + $this->CurPageFormat = array('w'=>$this->wPt, 'h'=>$this->hPt); + else + $this->CurPageFormat = null; + if($rotation!=0) + { + if($rotation%90!=0) + $this->Error('Incorrect rotation value: '.$rotation); + if(!isset($this->CurPageFormat)) + $this->CurPageFormat = array('w'=>$this->wPt, 'h'=>$this->hPt); + $this->CurPageFormat['Rotate'] = $rotation; + } + $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); + if(isset($this->CurPageFormat)) + $this->PageInfo[$this->page]['format'] = $this->CurPageFormat; +} + +function _endpage() +{ + $this->state = 1; +} + +function _loadfont($font, $dir) +{ + // Load a font definition file from the font directory + if($dir=='') + $dir = $this->fontpath; + if(substr($dir, -1)!='/' && substr($dir, -1)!='\') + $dir .= '/'; + @include($dir.$font); + if(!isset($name)) + $this->Error('Could not include font definition file'); + if(!isset($cw)) + $this->Error('Missing character widths array'); + return get_defined_vars(); +} + +function _UTF8toUTF16($s) +{ + // Convert UTF-8 string to UTF-16BE + $res = ""; + $nb = strlen($s); + $i = 0; + while($i<$nb) + { + $c1 = ord($s[$i++]); + if($c1>=224) + { + // 3-byte character + $c2 = ord($s[$i++]); + $c3 = ord($s[$i++]); + $res .= chr((($c1 & 0x0F) << 4) + (($c2 & 0x3C) >> 2)); + $res .= chr((($c2 & 0x03) << 6) + ($c3 & 0x3F)); + } + elseif($c1>=192) + { + // 2-byte character + $c2 = ord($s[$i++]); + $res .= chr(($c1 & 0x1C) >> 2); + $res .= chr((($c1 & 0x03) << 6) + ($c2 & 0x3F)); + } + else + { + // Single-byte character + $res .= "\0".chr($c1); + } + } + return $res; +} + +function _escape($s) +{ + // Escape special characters in strings + $s = str_replace('\\', '\\\\', $s); // Escape backslashes first + $s = str_replace('(', '\(', $s); + $s = str_replace(')', '\)', $s); + $s = str_replace("\r", '\r', $s); // Escape carriage return + return $s; +} + +function _textstring($s) +{ + // Format a text string + if(!$this->_isascii($s)) + $s = $this->_UTF8toUTF16($s); + return '('.$this->_escape($s).')'; +} + +function _isascii($s) +{ + $nb = strlen($s); + for($i=0;$i<$nb;$i++) + { + if(ord($s[$i])>127) + return false; + } + return true; +} + +function _dounderline($x, $y, $txt) +{ + // Underline text + $up = $this->CurrentFont['up']; + $ut = $this->CurrentFont['ut']; + $w = $this->GetStringWidth($txt)+$this->ws*substr_count($txt, ' '); + return sprintf('%.2F %.2F %.2F %.2F re f', $x*$this->k, ($this->h-($y-$up/1000*$this->FontSize))*$this->k, $w*$this->k, -$ut/1000*$this->FontSizePt); +} + +function _parsejpg($file) +{ + // Extract info from a JPEG file + $a = getimagesize($file); + if(!$a) + $this->Error('Missing or incorrect image file: '.$file); + if($a[2]!=2) + $this->Error('Not a JPEG file: '.$file); + if(!isset($a['channels']) || $a['channels']==3) + $colspace = 'DeviceRGB'; + elseif($a['channels']==4) + $colspace = 'DeviceCMYK'; + else + $colspace = 'DeviceGray'; + $bpc = isset($a['bits']) ? $a['bits'] : 8; + $data = file_get_contents($file); + return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$data); +} + +function _parsepng($file) +{ + // Extract info from a PNG file + $f = fopen($file, 'rb'); + if(!$f) + $this->Error('Can\'t open image file: '.$file); + $info = $this->_parsepngstream($f, $file); + fclose($f); + return $info; +} + +function _parsepngstream($f, $file) +{ + // Check signature + if($this->_readstream($f, 8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) + $this->Error('Not a PNG file: '.$file); + + // Read header chunk + $this->_readstream($f, 4); + if($this->_readstream($f, 4)!='IHDR') + $this->Error('Incorrect PNG file: '.$file); + $w = $this->_readint($f); + $h = $this->_readint($f); + $bpc = ord($this->_readstream($f, 1)); + if($bpc>8) + $this->Error('16-bit depth not supported: '.$file); + $ct = ord($this->_readstream($f, 1)); + if($ct==0 || $ct==4) + $colspace = 'DeviceGray'; + elseif($ct==2 || $ct==6) + $colspace = 'DeviceRGB'; + elseif($ct==3) + $colspace = 'Indexed'; + else + $this->Error('Unknown color type: '.$file); + if(ord($this->_readstream($f, 1))!=0) + $this->Error('Unknown compression method: '.$file); + if(ord($this->_readstream($f, 1))!=0) + $this->Error('Unknown filter method: '.$file); + if(ord($this->_readstream($f, 1))!=0) + $this->Error('Interlacing not supported: '.$file); + $this->_readstream($f, 4); + $dp = '/Predictor 15 /Colors '.($colspace=='DeviceRGB' ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w; + + // Scan chunks looking for palette, transparency and image data + $pal = ''; + $trns = ''; + $data = ''; + do + { + $n = $this->_readint($f); + $type = $this->_readstream($f, 4); + if($type=='PLTE') + { + // Read palette + $pal = $this->_readstream($f, $n); + $this->_readstream($f, 4); + } + elseif($type=='tRNS') + { + // Read transparency info + $t = $this->_readstream($f, $n); + if($ct==0) + $trns = array(ord(substr($t, 1, 1))); + elseif($ct==2) + $trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1))); + else + { + $pos = strpos($t, chr(0)); + if($pos!==false) + $trns = array($pos); + } + $this->_readstream($f, 4); + } + elseif($type=='IDAT') + { + // Read image data block + $data .= $this->_readstream($f, $n); + $this->_readstream($f, 4); + } + elseif($type=='IEND') + break; + else + $this->_readstream($f, $n+4); + } + while($n); + + if($colspace=='Indexed' && empty($pal)) + $this->Error('Missing palette in '.$file); + if($ct==6 && $bpc==8) + $this->WithAlpha = true; + $info = array('w'=>$w, 'h'=>$h, 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'FlateDecode', 'dp'=>$dp, 'pal'=>$pal, 'trns'=>$trns); + if($this->compress) + { + $data = gzuncompress($data); + if($ct==6) + { + // Separate alpha channel + $color = ''; + $alpha = ''; + for($i=0;$i<$h;$i++) + { + $pos = (1+$w*4)*$i; + $color .= $data[$pos]; + $alpha .= $data[$pos]; + for($j=0;$j<$w;$j++) + { + $color .= substr($data, $pos+1+4*$j, 3); + $alpha .= $data[$pos+4+4*$j]; + } + } + $data = gzcompress($color); + $info['smask'] = gzcompress($alpha); + if($this->PDFVersion<'1.4') + $this->PDFVersion = '1.4'; + } + $info['data'] = $data; + } + else + { + $info['data'] = $data; + if($ct==6) + $this->Error('PNG compression required for alpha channel'); + } + return $info; +} + +function _readstream($f, $n) +{ + // Read n bytes from stream + $res = ''; + while($n>0 && !feof($f)) + { + $s = fread($f, $n); + if($s===false) + $this->Error('Error while reading stream'); + $n -= strlen($s); + $res .= $s; + } + if($n>0) + $this->Error('Unexpected end of stream'); + return $res; +} + +function _readint($f) +{ + // Read a 4-byte integer from stream + $a = unpack('Ni', $this->_readstream($f, 4)); + return $a['i']; +} + +function _parsegif($file) +{ + // Extract info from a GIF file (via PNG conversion) + if(!function_exists('imagepng')) + $this->Error('GD extension is required for GIF support'); + if(!function_exists('imagecreatefromgif')) + $this->Error('GD extension is required for GIF support'); + $im = imagecreatefromgif($file); + if(!$im) + $this->Error('Missing or incorrect image file: '.$file); + imageinterlace($im, 0); + ob_start(); + imagepng($im); + $data = ob_get_clean(); + imagedestroy($im); + $f = fopen('php://temp', 'rb+'); + if(!$f) + $this->Error('Unable to create temporary file'); + fwrite($f, $data); + rewind($f); + $info = $this->_parsepngstream($f, $file); + fclose($f); + return $info; +} + +function _out($s) +{ + // Add a line to the document + if($this->state==2) + $this->pages[$this->page] .= $s."\n"; + elseif($this->state==1) + $this->_put($s); + elseif($this->state==0) + $this->Error('No page has been added yet'); + elseif($this->state==3) + $this->Error('The document is closed'); +} + +function _put($s) +{ + $this->buffer .= $s."\n"; +} + +function _getoffset() +{ + return strlen($this->buffer); +} + +function _newobj($n=null) +{ + // Begin a new object + if($n===null) + $n = ++$this->n; + $this->offsets[$n] = $this->_getoffset(); + $this->_put($n.' 0 obj'); +} + +function _putstream($data) +{ + $this->_put('stream'); + $this->_put($data); + $this->_put('endstream'); +} + +function _putstreamobject($data) +{ + if($this->compress) + { + $entries = '/Filter /FlateDecode '; + $data = gzcompress($data); + } + else + $entries = ''; + $entries .= '/Length '.strlen($data); + $this->_newobj(); + $this->_put('<<'.$entries.'>>'); + $this->_putstream($data); + $this->_put('endobj'); +} + +function _putpage($n) +{ + $this->_newobj(); + $this->_put('<_put('/Parent 1 0 R'); + $this->_put('/Resources 2 0 R'); + $format = $this->PageInfo[$n]['size']; + if(isset($this->PageInfo[$n]['format'])) + { + $format = $this->PageInfo[$n]['format']; + if(isset($format['Rotate'])) + $format['MediaBox'] = array(0, 0, $this->PageInfo[$n]['size'][1], $this->PageInfo[$n]['size'][0]); + } + $this->_put('/MediaBox [0 0 '.sprintf('%.2F %.2F', $format['w'], $format['h']).']'); + if(isset($format['Rotate'])) + $this->_put('/Rotate '.$format['Rotate']); + $this->_put('/Contents '.($this->n+1).' 0 R'); + $this->_put('>>'); + $this->_put('endobj'); + // Page content + $p = $this->pages[$n]; + $this->_putstreamobject($p); +} + +function _putpages() +{ + $nb = $this->page; + if(!empty($this->AliasNbPages)) + { + // Replace number of pages + for($n=1;$n<=$nb;$n++) + $this->pages[$n] = str_replace($this->AliasNbPages, $nb, $this->pages[$n]); + } + // Pages root + $this->_newobj(1); + $this->_put('<_putpage($n); + $kids .= (2+2*$n).' 0 R '; + } + $this->_put($kids.']'); + $this->_put('/Count '.$nb); + if(isset($this->PageInfo[1]['format'])) + $format = $this->PageInfo[1]['format']; + else + $format = $this->PageInfo[1]['size']; + $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]', $format['w'], $format['h'])); + $this->_put('>>'); + $this->_put('endobj'); +} + +function _putfonts() +{ + foreach($this->FontFiles as $file=>$info) + { + // Font file embedding + $this->_newobj(); + $this->FontFiles[$file]['n'] = $this->n; + $font = file_get_contents($this->fontpath.$file, true); + if(!$font) + $this->Error('Font file not found: '.$file); + $compressed = (substr($file, -2)=='.z'); + if(!$compressed && isset($info['length2'])) + $font = substr($font, 6, $info['length1']).substr($font, 6+$info['length1']+6, $info['length2']); + $this->_put('<_put('/Filter /FlateDecode'); + $this->_put('/Length1 '.$info['length1']); + if(isset($info['length2'])) + $this->_put('/Length2 '.$info['length2']); + $this->_put('>>'); + $this->_putstream($font); + $this->_put('endobj'); + } + foreach($this->fonts as $k=>$font) + { + // Font objects + $this->fonts[$k]['n'] = $this->n+1; + $type = $font['type']; + $name = $font['name']; + if($type=='Core') + { + // Core font + $this->_newobj(); + $this->_put('<_put('/BaseFont /'.$name); + $this->_put('/Subtype /Type1'); + if($name!='Symbol' && $name!='ZapfDingbats') + $this->_put('/Encoding /WinAnsiEncoding'); + $this->_put('>>'); + $this->_put('endobj'); + } + elseif($type=='Type1' || $type=='TrueType') + { + // Additional Type1 or TrueType/OpenType font + $this->_newobj(); + $this->_put('<_put('/BaseFont /'.$name); + $this->_put('/Subtype /'.$type); + $this->_put('/FirstChar 32'); + $this->_put('/LastChar 255'); + $this->_put('/Widths '.($this->n+1).' 0 R'); + $this->_put('/FontDescriptor '.($this->n+2).' 0 R'); + if($font['enc']) + { + if(isset($font['diff'])) + $this->_put('/Encoding '.($this->n+4).' 0 R'); + else + $this->_put('/Encoding /WinAnsiEncoding'); + } + $this->_put('>>'); + $this->_put('endobj'); + // Widths + $this->_newobj(); + $cw = &$font['cw']; + $s = '['; + for($i=32;$i<=255;$i++) + $s .= $cw[chr($i)].' '; + $this->_put($s.']'); + $this->_put('endobj'); + // Descriptor + $this->_newobj(); + $s = '<$v) + $s .= ' /'.$k2.' '.$v; + if(!empty($font['file'])) + $s .= ' /FontFile'.($type=='Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R'; + $this->_put($s.'>>'); + $this->_put('endobj'); + // Differences + if(isset($font['diff'])) + { + $this->_newobj(); + $this->_put('<>'); + $this->_put('endobj'); + } + } + elseif($type=='CMap') + { + // CJK font + $this->_newobj(); + $this->_put('<_put('/Subtype /Type0'); + $this->_put('/BaseFont /'.$name); + $this->_put('/Encoding /'.$font['enc']); + $this->_put('/DescendantFonts ['.($this->n+1).' 0 R]'); + $this->_put('>>'); + $this->_put('endobj'); + $this->_newobj(); + $this->_put('<_put('/Subtype /CIDFontType0'); + $this->_put('/BaseFont /'.$name); + $this->_put('/CIDSystemInfo '.($this->n+1).' 0 R'); + $this->_put('/FontDescriptor '.($this->n+2).' 0 R'); + if(isset($font['dw'])) + $this->_put('/DW '.$font['dw']); + if(isset($font['w'])) + $this->_put('/W '.($this->n+5).' 0 R'); + if(isset($font['cid_info'])) + $this->_put('/CIDToGIDMap /'.$font['cid_info']); + $this->_put('>>'); + $this->_put('endobj'); + $this->_newobj(); + $this->_put('<>'); + $this->_put('endobj'); + // Descriptor + $this->_newobj(); + $s = '<$v) + $s .= ' /'.$k2.' '.$v; + if(!empty($font['file'])) + $s .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R'; + $this->_put($s.'>>'); + $this->_put('endobj'); + // Widths + if(isset($font['w'])) + { + $this->_newobj(); + $this->_put('['.$font['w'].']'); + $this->_put('endobj'); + } + } + else + $this->Error('Unsupported font type: '.$type); + } +} + +function _putimages() +{ + foreach($this->images as $file=>$info) + { + $this->_newobj(); + $this->_put('<_put('/Subtype /Image'); + $this->_put('/Width '.$info['w']); + $this->_put('/Height '.$info['h']); + if($info['cs']=='Indexed') + $this->_put('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]'); + else + { + $this->_put('/ColorSpace /'.$info['cs']); + if($info['cs']=='DeviceCMYK') + $this->_put('/Decode [1 0 1 0 1 0 1 0]'); + } + $this->_put('/BitsPerComponent '.$info['bpc']); + if(isset($info['f'])) + $this->_put('/Filter /'.$info['f']); + if(isset($info['dp'])) + $this->_put('/DecodeParms <<'.$info['dp'].'>>'); + if(isset($info['trns']) && is_array($info['trns'])) + { + $trns = ''; + for($i=0;$i_put('/Mask ['.$trns.']'); + } + if(isset($info['smask'])) + $this->_put('/SMask '.($this->n+1).' 0 R'); + $this->_put('/Length '.strlen($info['data']).'>>'); + $this->_putstream($info['data']); + $this->_put('endobj'); + // Soft mask + if(isset($info['smask'])) + { + $dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns '.$info['w']; + $smask = array('w'=>$info['w'], 'h'=>$info['h'], 'cs'=>'DeviceGray', 'bpc'=>8, 'f'=>'FlateDecode', 'dp'=>$dp, 'data'=>$info['smask']); + $this->_putimage($smask); + } + // Palette + if($info['cs']=='Indexed') + $this->_putstreamobject($info['pal']); + } +} + +function _putimage(&$info) +{ + $this->_newobj(); + $this->_put('<_put('/Subtype /Image'); + $this->_put('/Width '.$info['w']); + $this->_put('/Height '.$info['h']); + $this->_put('/ColorSpace /'.$info['cs']); + $this->_put('/BitsPerComponent '.$info['bpc']); + $this->_put('/Filter /'.$info['f']); + $this->_put('/DecodeParms <<'.$info['dp'].'>>'); + $this->_put('/Length '.strlen($info['data']).'>>'); + $this->_putstream($info['data']); + $this->_put('endobj'); +} + +function _putxobjectdict() +{ + foreach($this->images as $file=>$info) + $this->_put('/I'.$info['i'].' '.($info['n']).' 0 R'); +} + +function _putresourcedict() +{ + $this->_put('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + $this->_put('/Font <<'); + foreach($this->fonts as $font) + $this->_put('/F'.$font['i'].' '.$font['n'].' 0 R'); + $this->_put('>>'); + $this->_put('/XObject <<'); + $this->_putxobjectdict(); + $this->_put('>>'); +} + +function _putresources() +{ + $this->_putfonts(); + $this->_putimages(); + // Resource dictionary + $this->_newobj(2); + $this->_put('<<'); + $this->_putresourcedict(); + $this->_put('>>'); + $this->_put('endobj'); +} + +function _putinfo() +{ + if(empty($this->metadata)) + return; + $this->_newobj(); + $this->_put('<<'); + foreach($this->metadata as $key=>$value) + $this->_put('/'.$key.' '.$this->_textstring($value)); + $this->_put('>>'); + $this->_put('endobj'); +} + +function _putcatalog() +{ + $this->_newobj(); + $this->_put('<_put('/Pages 1 0 R'); + if($this->ZoomMode=='fullpage') + $this->_put('/OpenAction [3 0 R /Fit]'); + elseif($this->ZoomMode=='fullwidth') + $this->put('/OpenAction [3 0 R /FitH null]'); + elseif($this->ZoomMode=='real') + $this->_put('/OpenAction [3 0 R /XYZ null null 1]'); + elseif(!is_string($this->ZoomMode)) + $this->_put('/OpenAction [3 0 R /XYZ null null '.sprintf('%.2F', $this->ZoomMode/100).']'); + if($this->LayoutMode=='single') + $this->_put('/PageLayout /SinglePage'); + elseif($this->LayoutMode=='continuous') + $this->put('/PageLayout /OneColumn'); + elseif($this->LayoutMode=='two') + $this->_put('/PageLayout /TwoColumnLeft'); + $this->_put('>>'); + $this->_put('endobj'); +} + +function _putheader() +{ + $this->_put('%PDF-'.$this->PDFVersion); +} + +function _puttrailer() +{ + $this->_put('trailer'); + $this->_put('<<'); + $this->_put('/Size '.($this->n+1)); + $this->_put('/Root '.($this->n).' 0 R'); + if(!empty($this->metadata)) + $this->_put('/Info '.($this->n-1).' 0 R'); + $this->_put('>>'); +} + +function _enddoc() +{ + $this->_putheader(); + $this->_putpages(); + $this->_putresources(); + $this->_putinfo(); + $this->_putcatalog(); + $offset = $this->_getoffset(); + $this->_puttrailer(); + $this->_put('startxref'); + $this->_put($offset); + $this->_put('%%EOF'); + $this->state = 3; +} +} \ No newline at end of file