From efd543fe593537a291d015b6414ee8fdddcaf4dd Mon Sep 17 00:00:00 2001 From: basil <15813246678@163.com> Date: Tue, 3 Jun 2025 01:21:14 +0800 Subject: [PATCH] 1 --- .gitignore | 8 + 2.pdf | Bin 0 -> 121193 bytes Fpdi/CFpdf.php | 381 ++++ Fpdi/Fpdf.php | 1795 +++++++++++++++++ Fpdi/FpdfTpl.php | 21 + Fpdi/FpdfTplTrait.php | 470 +++++ Fpdi/Fpdi.php | 153 ++ Fpdi/FpdiException.php | 18 + Fpdi/FpdiTrait.php | 559 +++++ .../CrossReference/AbstractReader.php | 95 + .../CrossReference/CrossReference.php | 326 +++ .../CrossReferenceException.php | 79 + Fpdi/PdfParser/CrossReference/FixedReader.php | 199 ++ Fpdi/PdfParser/CrossReference/LineReader.php | 167 ++ .../CrossReference/ReaderInterface.php | 34 + Fpdi/PdfParser/Filter/Ascii85.php | 102 + Fpdi/PdfParser/Filter/Ascii85Exception.php | 27 + Fpdi/PdfParser/Filter/AsciiHex.php | 47 + Fpdi/PdfParser/Filter/FilterException.php | 23 + Fpdi/PdfParser/Filter/FilterInterface.php | 25 + Fpdi/PdfParser/Filter/Flate.php | 86 + Fpdi/PdfParser/Filter/FlateException.php | 27 + Fpdi/PdfParser/Filter/Lzw.php | 187 ++ Fpdi/PdfParser/Filter/LzwException.php | 22 + Fpdi/PdfParser/PdfParser.php | 381 ++++ Fpdi/PdfParser/PdfParserException.php | 49 + Fpdi/PdfParser/StreamReader.php | 471 +++++ Fpdi/PdfParser/Tokenizer.php | 154 ++ Fpdi/PdfParser/Type/PdfArray.php | 85 + Fpdi/PdfParser/Type/PdfBoolean.php | 42 + Fpdi/PdfParser/Type/PdfDictionary.php | 134 ++ Fpdi/PdfParser/Type/PdfHexString.php | 77 + Fpdi/PdfParser/Type/PdfIndirectObject.php | 103 + .../Type/PdfIndirectObjectReference.php | 52 + Fpdi/PdfParser/Type/PdfName.php | 82 + Fpdi/PdfParser/Type/PdfNull.php | 19 + Fpdi/PdfParser/Type/PdfNumeric.php | 43 + Fpdi/PdfParser/Type/PdfStream.php | 326 +++ Fpdi/PdfParser/Type/PdfString.php | 172 ++ Fpdi/PdfParser/Type/PdfToken.php | 43 + Fpdi/PdfParser/Type/PdfType.php | 78 + Fpdi/PdfParser/Type/PdfTypeException.php | 24 + Fpdi/PdfReader/DataStructure/Rectangle.php | 173 ++ Fpdi/PdfReader/Page.php | 271 +++ Fpdi/PdfReader/PageBoundaries.php | 94 + Fpdi/PdfReader/PdfReader.php | 234 +++ Fpdi/PdfReader/PdfReaderException.php | 34 + Fpdi/font/courier.php | 10 + Fpdi/font/courierb.php | 10 + Fpdi/font/courierbi.php | 10 + Fpdi/font/courieri.php | 10 + Fpdi/font/helvetica.php | 21 + Fpdi/font/helveticab.php | 21 + Fpdi/font/helveticabi.php | 21 + Fpdi/font/helveticai.php | 21 + Fpdi/font/symbol.php | 20 + Fpdi/font/times.php | 21 + Fpdi/font/timesb.php | 21 + Fpdi/font/timesbi.php | 21 + Fpdi/font/timesi.php | 21 + Fpdi/font/zapfdingbats.php | 20 + LICENSE | 21 + README.md | 19 + _test/111.jpeg | Bin 0 -> 108883 bytes _test/create.php | 28 + _test/test.pdf | Bin 0 -> 7137 bytes _test/test.php | 41 + autoload.php | 20 + composer.json | 27 + include.php | 14 + 70 files changed, 8410 insertions(+) create mode 100644 .gitignore create mode 100644 2.pdf create mode 100644 Fpdi/CFpdf.php create mode 100644 Fpdi/Fpdf.php create mode 100644 Fpdi/FpdfTpl.php create mode 100644 Fpdi/FpdfTplTrait.php create mode 100644 Fpdi/Fpdi.php create mode 100644 Fpdi/FpdiException.php create mode 100644 Fpdi/FpdiTrait.php create mode 100644 Fpdi/PdfParser/CrossReference/AbstractReader.php create mode 100644 Fpdi/PdfParser/CrossReference/CrossReference.php create mode 100644 Fpdi/PdfParser/CrossReference/CrossReferenceException.php create mode 100644 Fpdi/PdfParser/CrossReference/FixedReader.php create mode 100644 Fpdi/PdfParser/CrossReference/LineReader.php create mode 100644 Fpdi/PdfParser/CrossReference/ReaderInterface.php create mode 100644 Fpdi/PdfParser/Filter/Ascii85.php create mode 100644 Fpdi/PdfParser/Filter/Ascii85Exception.php create mode 100644 Fpdi/PdfParser/Filter/AsciiHex.php create mode 100644 Fpdi/PdfParser/Filter/FilterException.php create mode 100644 Fpdi/PdfParser/Filter/FilterInterface.php create mode 100644 Fpdi/PdfParser/Filter/Flate.php create mode 100644 Fpdi/PdfParser/Filter/FlateException.php create mode 100644 Fpdi/PdfParser/Filter/Lzw.php create mode 100644 Fpdi/PdfParser/Filter/LzwException.php create mode 100644 Fpdi/PdfParser/PdfParser.php create mode 100644 Fpdi/PdfParser/PdfParserException.php create mode 100644 Fpdi/PdfParser/StreamReader.php create mode 100644 Fpdi/PdfParser/Tokenizer.php create mode 100644 Fpdi/PdfParser/Type/PdfArray.php create mode 100644 Fpdi/PdfParser/Type/PdfBoolean.php create mode 100644 Fpdi/PdfParser/Type/PdfDictionary.php create mode 100644 Fpdi/PdfParser/Type/PdfHexString.php create mode 100644 Fpdi/PdfParser/Type/PdfIndirectObject.php create mode 100644 Fpdi/PdfParser/Type/PdfIndirectObjectReference.php create mode 100644 Fpdi/PdfParser/Type/PdfName.php create mode 100644 Fpdi/PdfParser/Type/PdfNull.php create mode 100644 Fpdi/PdfParser/Type/PdfNumeric.php create mode 100644 Fpdi/PdfParser/Type/PdfStream.php create mode 100644 Fpdi/PdfParser/Type/PdfString.php create mode 100644 Fpdi/PdfParser/Type/PdfToken.php create mode 100644 Fpdi/PdfParser/Type/PdfType.php create mode 100644 Fpdi/PdfParser/Type/PdfTypeException.php create mode 100644 Fpdi/PdfReader/DataStructure/Rectangle.php create mode 100644 Fpdi/PdfReader/Page.php create mode 100644 Fpdi/PdfReader/PageBoundaries.php create mode 100644 Fpdi/PdfReader/PdfReader.php create mode 100644 Fpdi/PdfReader/PdfReaderException.php create mode 100644 Fpdi/font/courier.php create mode 100644 Fpdi/font/courierb.php create mode 100644 Fpdi/font/courierbi.php create mode 100644 Fpdi/font/courieri.php create mode 100644 Fpdi/font/helvetica.php create mode 100644 Fpdi/font/helveticab.php create mode 100644 Fpdi/font/helveticabi.php create mode 100644 Fpdi/font/helveticai.php create mode 100644 Fpdi/font/symbol.php create mode 100644 Fpdi/font/times.php create mode 100644 Fpdi/font/timesb.php create mode 100644 Fpdi/font/timesbi.php create mode 100644 Fpdi/font/timesi.php create mode 100644 Fpdi/font/zapfdingbats.php create mode 100644 LICENSE create mode 100644 README.md create mode 100644 _test/111.jpeg create mode 100644 _test/create.php create mode 100644 _test/test.pdf create mode 100644 _test/test.php create mode 100644 autoload.php create mode 100644 composer.json create mode 100644 include.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3cb6a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.git +/.idea +/vendor +/Cache +/nbproject +/composer.lock +.DS_Store +cookie \ No newline at end of file diff --git a/2.pdf b/2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d35dba3528058201ba3b301d2d410f8ef297e0d2 GIT binary patch literal 121193 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 EKi| 250, '!' => 250, '"' => 408, '#' => 668, '$' => 490, '%' => 875, '&' => 698, '\'' => 250, + '(' => 240, ')' => 240, '*' => 417, '+' => 667, ',' => 250, '-' => 313, '.' => 250, '/' => 520, '0' => 500, '1' => 500, + '2' => 500, '3' => 500, '4' => 500, '5' => 500, '6' => 500, '7' => 500, '8' => 500, '9' => 500, ':' => 250, ';' => 250, + '<' => 667, '=' => 667, '>' => 667, '?' => 396, '@' => 921, 'A' => 677, 'B' => 615, 'C' => 719, 'D' => 760, 'E' => 625, + 'F' => 552, 'G' => 771, 'H' => 802, 'I' => 354, 'J' => 354, 'K' => 781, 'L' => 604, 'M' => 927, 'N' => 750, 'O' => 823, + 'P' => 563, 'Q' => 823, 'R' => 729, 'S' => 542, 'T' => 698, 'U' => 771, 'V' => 729, 'W' => 948, 'X' => 771, 'Y' => 677, + 'Z' => 635, '[' => 344, '\\' => 520, ']' => 344, '^' => 469, '_' => 500, '`' => 250, 'a' => 469, 'b' => 521, 'c' => 427, + 'd' => 521, 'e' => 438, 'f' => 271, 'g' => 469, 'h' => 531, 'i' => 250, 'j' => 250, 'k' => 458, 'l' => 240, 'm' => 802, + 'n' => 531, 'o' => 500, 'p' => 521, 'q' => 521, 'r' => 365, 's' => 333, 't' => 292, 'u' => 521, 'v' => 458, 'w' => 677, + 'x' => 479, 'y' => 458, 'z' => 427, '{' => 480, '|' => 496, '}' => 480, '~' => 667 + ); + + protected $GB_widths = array( + ' ' => 207, '!' => 270, '"' => 342, '#' => 467, '$' => 462, '%' => 797, '&' => 710, '\'' => 239, + '(' => 374, ')' => 374, '*' => 423, '+' => 605, ',' => 238, '-' => 375, '.' => 238, '/' => 334, '0' => 462, '1' => 462, + '2' => 462, '3' => 462, '4' => 462, '5' => 462, '6' => 462, '7' => 462, '8' => 462, '9' => 462, ':' => 238, ';' => 238, + '<' => 605, '=' => 605, '>' => 605, '?' => 344, '@' => 748, 'A' => 684, 'B' => 560, 'C' => 695, 'D' => 739, 'E' => 563, + 'F' => 511, 'G' => 729, 'H' => 793, 'I' => 318, 'J' => 312, 'K' => 666, 'L' => 526, 'M' => 896, 'N' => 758, 'O' => 772, + 'P' => 544, 'Q' => 772, 'R' => 628, 'S' => 465, 'T' => 607, 'U' => 753, 'V' => 711, 'W' => 972, 'X' => 647, 'Y' => 620, + 'Z' => 607, '[' => 374, '\\' => 333, ']' => 374, '^' => 606, '_' => 500, '`' => 239, 'a' => 417, 'b' => 503, 'c' => 427, + 'd' => 529, 'e' => 415, 'f' => 264, 'g' => 444, 'h' => 518, 'i' => 241, 'j' => 230, 'k' => 495, 'l' => 228, 'm' => 793, + 'n' => 527, 'o' => 524, 'p' => 524, 'q' => 504, 'r' => 338, 's' => 336, 't' => 277, 'u' => 517, 'v' => 450, 'w' => 652, + 'x' => 466, 'y' => 452, 'z' => 407, '{' => 370, '|' => 258, '}' => 370, '~' => 605 + ); + + public function AddCIDFont($family, $style, $name, $cw, $CMap, $registry) + { + $fontkey = strtolower($family) . strtoupper($style); + if (isset($this->fonts[$fontkey])) + $this->Error("Font already added: $family $style"); + $i = count($this->fonts) + 1; + $name = str_replace(' ', '', $name); + $this->fonts[$fontkey] = array('i' => $i, 'type' => 'Type0', 'name' => $name, 'up' => -130, 'ut' => 40, 'cw' => $cw, 'CMap' => $CMap, 'registry' => $registry); + } + + public function AddCIDFonts($family, $name, $cw, $CMap, $registry) + { + $this->AddCIDFont($family, '', $name, $cw, $CMap, $registry); + $this->AddCIDFont($family, 'B', $name . ',Bold', $cw, $CMap, $registry); + $this->AddCIDFont($family, 'I', $name . ',Italic', $cw, $CMap, $registry); + $this->AddCIDFont($family, 'BI', $name . ',BoldItalic', $cw, $CMap, $registry); + } + + public function AddBig5Font($family = 'Big5', $name = 'MSungStd-Light-Acro') + { + // Add Big5 font with proportional Latin + $cw = $this->Big5_widths; + $CMap = 'ETenms-B5-H'; + $registry = array('ordering' => 'CNS1', 'supplement' => 0); + $this->AddCIDFonts($family, $name, $cw, $CMap, $registry); + } + + public function AddBig5hwFont($family = 'Big5-hw', $name = 'MSungStd-Light-Acro') + { + // Add Big5 font with half-witdh Latin + for ($i = 32; $i <= 126; $i++) + $cw[chr($i)] = 500; + $CMap = 'ETen-B5-H'; + $registry = array('ordering' => 'CNS1', 'supplement' => 0); + $this->AddCIDFonts($family, $name, $cw, $CMap, $registry); + } + + public function AddGBFont($family = 'GB', $name = 'STSongStd-Light-Acro') + { + // Add GB font with proportional Latin + $cw = $this->GB_widths; + $CMap = 'GBKp-EUC-H'; + $registry = array('ordering' => 'GB1', 'supplement' => 2); + $this->AddCIDFonts($family, $name, $cw, $CMap, $registry); + } + + public function AddGBhwFont($family = 'GB-hw', $name = 'STSongStd-Light-Acro') + { + // Add GB font with half-width Latin + for ($i = 32; $i <= 126; $i++) + $cw[chr($i)] = 500; + $CMap = 'GBK-EUC-H'; + $registry = array('ordering' => 'GB1', 'supplement' => 2); + $this->AddCIDFonts($family, $name, $cw, $CMap, $registry); + } + + public function GetStringWidth($s) + { + if ($this->CurrentFont['type'] == 'Type0') + return $this->GetMBStringWidth($s); + else + return parent::GetStringWidth($s); + } + + public function GetMBStringWidth($s) + { + // Multi-byte version of GetStringWidth() + $l = 0; + $cw = &$this->CurrentFont['cw']; + $nb = strlen($s); + $i = 0; + while ($i < $nb) { + $c = $s[$i]; + if (ord($c) < 128) { + $l += $cw[$c]; + $i++; + } else { + $l += 1000; + $i += 2; + } + } + return $l * $this->FontSize / 1000; + } + + public function MultiCell($w, $h, $txt, $border = 0, $align = 'L', $fill = 0) + { + if ($this->CurrentFont['type'] == 'Type0') + $this->MBMultiCell($w, $h, $txt, $border, $align, $fill); + else + parent::MultiCell($w, $h, $txt, $border, $align, $fill); + } + + private function MBMultiCell($w, $h, $txt, $border = 0, $align = 'L', $fill = 0) + { + // Multi-byte version of MultiCell() + $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 (is_int(strpos($border, 'L'))) + $b2 .= 'L'; + if (is_int(strpos($border, 'R'))) + $b2 .= 'R'; + $b = is_int(strpos($border, 'T')) ? $b2 . 'T' : $b2; + } + } + $sep = -1; + $i = 0; + $j = 0; + $l = 0; + $nl = 1; + while ($i < $nb) { + // Get next character + $c = $s[$i]; + // Check if ASCII or MB + $ascii = (ord($c) < 128); + if ($c == "\n") { + // Explicit line break + $this->Cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill); + $i++; + $sep = -1; + $j = $i; + $l = 0; + $nl++; + if ($border && $nl == 2) + $b = $b2; + continue; + } + if (!$ascii) { + $sep = $i; + $ls = $l; + } elseif ($c == ' ') { + $sep = $i; + $ls = $l; + } + $l += $ascii ? $cw[$c] : 1000; + if ($l > $wmax) { + // Automatic line break + if ($sep == -1 || $i == $j) { + if ($i == $j) + $i += $ascii ? 1 : 2; + $this->Cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill); + } else { + $this->Cell($w, $h, substr($s, $j, $sep - $j), $b, 2, $align, $fill); + $i = ($s[$sep] == ' ') ? $sep + 1 : $sep; + } + $sep = -1; + $j = $i; + $l = 0; + $nl++; + if ($border && $nl == 2) + $b = $b2; + } else + $i += $ascii ? 1 : 2; + } + // Last chunk + if ($border && is_int(strpos($border, 'B'))) + $b .= 'B'; + $this->Cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill); + $this->x = $this->lMargin; + } + + public function Write($h, $txt, $link = '') + { + if ($this->CurrentFont['type'] == 'Type0') + $this->MBWrite($h, $txt, $link); + else + parent::Write($h, $txt, $link); + } + + private function MBWrite($h, $txt, $link) + { + // Multi-byte version of Write() + $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]; + // Check if ASCII or MB + $ascii = (ord($c) < 128); + if ($c == "\n") { + // Explicit line break + $this->Cell($w, $h, substr($s, $j, $i - $j), 0, 2, '', 0, $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 (!$ascii || $c == ' ') + $sep = $i; + $l += $ascii ? $cw[$c] : 1000; + if ($l > $wmax) { + // Automatic line break + if ($sep == -1 || $i == $j) { + 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 += $ascii ? 1 : 2; + $this->Cell($w, $h, substr($s, $j, $i - $j), 0, 2, '', 0, $link); + } else { + $this->Cell($w, $h, substr($s, $j, $sep - $j), 0, 2, '', 0, $link); + $i = ($s[$sep] == ' ') ? $sep + 1 : $sep; + } + $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 += $ascii ? 1 : 2; + } + // Last chunk + if ($i != $j) + $this->Cell($l / 1000 * $this->FontSize, $h, substr($s, $j, $i - $j), 0, 0, '', 0, $link); + } + + public function _putType0($font) + { + // Type0 + $this->_newobj(); + $this->_out('<_out('/Subtype /Type0'); + $this->_out('/BaseFont /' . $font['name'] . '-' . $font['CMap']); + $this->_out('/Encoding /' . $font['CMap']); + $this->_out('/DescendantFonts [' . ($this->n + 1) . ' 0 R]'); + $this->_out('>>'); + $this->_out('endobj'); + // CIDFont + $this->_newobj(); + $this->_out('<_out('/Subtype /CIDFontType0'); + $this->_out('/BaseFont /' . $font['name']); + $this->_out('/CIDSystemInfo <_textstring('Adobe') . ' /Ordering ' . $this->_textstring($font['registry']['ordering']) . ' /Supplement ' . $font['registry']['supplement'] . '>>'); + $this->_out('/FontDescriptor ' . ($this->n + 1) . ' 0 R'); + if ($font['CMap'] == 'ETen-B5-H') + $W = '13648 13742 500'; + elseif ($font['CMap'] == 'GBK-EUC-H') + $W = '814 907 500 7716 [500]'; + else + $W = '1 [' . implode(' ', $font['cw']) . ']'; + $this->_out('/W [' . $W . ']>>'); + $this->_out('endobj'); + // Font descriptor + $this->_newobj(); + $this->_out('<_out('/FontName /' . $font['name']); + $this->_out('/Flags 6'); + $this->_out('/FontBBox [0 -200 1000 900]'); + $this->_out('/ItalicAngle 0'); + $this->_out('/Ascent 800'); + $this->_out('/Descent -200'); + $this->_out('/CapHeight 800'); + $this->_out('/StemV 50'); + $this->_out('>>'); + $this->_out('endobj'); + } + + /** + * 旋转文字,角度为弧度 + * + * @param int $angle 角度 + * @return void + */ + private function Rotate($angle, $x = -1, $y = -1) + { + if ($x == -1) + $x = $this->x; + if ($y == -1) + $y = $this->y; + if ($this->angle != 0) + $this->_out('Q'); + $this->angle = $angle; + if ($angle != 0) { + $angle *= M_PI / 180; + $c = cos($angle); + $s = sin($angle); + $cx = $x * $this->k; + $cy = ($this->h - $y) * $this->k; + $this->_out(sprintf('q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm', $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy)); + } + } + public function _endpage() + { + if ($this->angle != 0) { + $this->angle = 0; + $this->_out('Q'); + } + parent::_endpage(); + } + public function RotatedText($x, $y, $txt, $angle) + { + //Text rotated around its origin + $this->Rotate($angle, $x, $y); + $this->Text($x, $y, $txt); + $this->Rotate(0); + } + + public function RotatedImage($file, $x, $y, $w, $h, $angle) + { + //Image rotated around its upper-left corner + $this->Rotate($angle, $x, $y); + $this->Image($file, $x, $y, $w, $h); + $this->Rotate(0); + } +} diff --git a/Fpdi/Fpdf.php b/Fpdi/Fpdf.php new file mode 100644 index 0000000..10bcc28 --- /dev/null +++ b/Fpdi/Fpdf.php @@ -0,0 +1,1795 @@ +_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 = ''; + */ + if (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 rotation + $this->CurRotation = 0; + // 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); + // Default display mode + $this->SetDisplayMode('default'); + // 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 = 'default') + { + // 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 ? $title : utf8_encode($title); + } + + function SetAuthor($author, $isUTF8 = false) + { + // Author of document + $this->metadata['Author'] = $isUTF8 ? $author : utf8_encode($author); + } + + function SetSubject($subject, $isUTF8 = false) + { + // Subject of document + $this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode($subject); + } + + function SetKeywords($keywords, $isUTF8 = false) + { + // Keywords of document + $this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode($keywords); + } + + function SetCreator($creator, $isUTF8 = false) + { + // Creator of document + $this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode($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 = '') + { + // Add a TrueType, OpenType or Type1 font + $family = strtolower($family); + if ($file == '') + $file = str_replace(' ', '', $family) . strtolower($style) . '.php'; + $style = strtoupper($style); + if ($style == 'IB') + $style = 'BI'; + $fontkey = $family . $style; + if (isset($this->fonts[$fontkey])) + return; + $info = $this->_loadfont($file); + $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); + $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 ($family == 'arial') + $family = 'helvetica'; + if (in_array($family, $this->CoreFonts)) { + if ($family == 'symbol' || $family == 'zapfdingbats') + $style = ''; + $fontkey = $family . $style; + 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($w, $h, substr($s, $j, $sep - $j), 0, 2, '', 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($l / 1000 * $this->FontSize, $h, substr($s, $j), 0, 0, '', false, $link); + } + + function Ln($h = null) + { + // Line feed; default value is the last cell height + $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->SetX($x); + $this->SetY($y, false); + } + + function Output($dest = '', $name = '', $isUTF8 = false) + { + // Output PDF to some destination + $this->Close(); + if (strlen($name) == 1 && strlen($dest) != 1) { + // Fix parameter order + $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; ' . $this->_httpencode('filename', $name, $isUTF8)); + 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; ' . $this->_httpencode('filename', $name, $isUTF8)); + 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 * + *******************************************************************************/ + + protected function _dochecks() + { + // Check mbstring overloading + if (ini_get('mbstring.func_overload') & 2) + $this->Error('mbstring overloading must be disabled'); + } + + protected 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)?\s*$/', ob_get_contents())) { + // It contains only a UTF-8 BOM and/or whitespace, let's clean it + ob_clean(); + } else + $this->Error("Some data has already been output, can't send PDF file"); + } + } + + protected function _getpagesize($size) + { + if (is_string($size)) { + $size = strtolower($size); + if (!isset($this->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; + } + } + + protected function _beginpage($orientation, $size, $rotation) + { + $this->page++; + $this->pages[$this->page] = ''; + $this->PageLinks[$this->page] = array(); + $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->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); + if ($rotation != 0) { + if ($rotation % 90 != 0) + $this->Error('Incorrect rotation value: ' . $rotation); + $this->CurRotation = $rotation; + $this->PageInfo[$this->page]['rotation'] = $rotation; + } + } + + protected function _endpage() + { + $this->state = 1; + } + + protected function _loadfont($font) + { + // Load a font definition file from the font directory + if (strpos($font, '/') !== false || strpos($font, "\\") !== false) + $this->Error('Incorrect font definition file name: ' . $font); + include($this->fontpath . $font); + if (!isset($name)) + $this->Error('Could not include font definition file'); + if (isset($enc)) + $enc = strtolower($enc); + if (!isset($subsetted)) + $subsetted = false; + return get_defined_vars(); + } + + protected function _isascii($s) + { + // Test if string is ASCII + $nb = strlen($s); + for ($i = 0; $i < $nb; $i++) { + if (ord($s[$i]) > 127) + return false; + } + return true; + } + + protected function _httpencode($param, $value, $isUTF8) + { + // Encode HTTP header field parameter + if ($this->_isascii($value)) + return $param . '="' . $value . '"'; + if (!$isUTF8) + $value = utf8_encode($value); + if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false) + return $param . '="' . rawurlencode($value) . '"'; + else + return $param . "*=UTF-8''" . rawurlencode($value); + } + + protected function _UTF8toUTF16($s) + { + // Convert UTF-8 to UTF-16BE with BOM + $res = "\xFE\xFF"; + $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; + } + + protected function _escape($s) + { + // Escape special characters + if (strpos($s, '(') !== false || strpos($s, ')') !== false || strpos($s, '\\') !== false || strpos($s, "\r") !== false) + return str_replace(array('\\', '(', ')', "\r"), array('\\\\', '\\(', '\\)', '\\r'), $s); + else + return $s; + } + + protected function _textstring($s) + { + // Format a text string + if (!$this->_isascii($s)) + $s = $this->_UTF8toUTF16($s); + return '(' . $this->_escape($s) . ')'; + } + + protected 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); + } + + protected 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); + } + + protected 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; + } + + protected 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); + $info = array('w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'dp' => $dp, 'pal' => $pal, 'trns' => $trns); + if ($ct >= 4) { + // Extract alpha channel + if (!function_exists('gzuncompress')) + $this->Error('Zlib not available, can\'t handle alpha channel: ' . $file); + $data = gzuncompress($data); + $color = ''; + $alpha = ''; + if ($ct == 4) { + // Gray image + $len = 2 * $w; + for ($i = 0; $i < $h; $i++) { + $pos = (1 + $len) * $i; + $color .= $data[$pos]; + $alpha .= $data[$pos]; + $line = substr($data, $pos + 1, $len); + $color .= preg_replace('/(.)./s', '$1', $line); + $alpha .= preg_replace('/.(.)/s', '$1', $line); + } + } else { + // RGB image + $len = 4 * $w; + for ($i = 0; $i < $h; $i++) { + $pos = (1 + $len) * $i; + $color .= $data[$pos]; + $alpha .= $data[$pos]; + $line = substr($data, $pos + 1, $len); + $color .= preg_replace('/(.{3})./s', '$1', $line); + $alpha .= preg_replace('/.{3}(.)/s', '$1', $line); + } + } + unset($data); + $data = gzcompress($color); + $info['smask'] = gzcompress($alpha); + $this->WithAlpha = true; + if ($this->PDFVersion < '1.4') + $this->PDFVersion = '1.4'; + } + $info['data'] = $data; + return $info; + } + + protected 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; + } + + protected function _readint($f) + { + // Read a 4-byte integer from stream + $a = unpack('Ni', $this->_readstream($f, 4)); + return $a['i']; + } + + protected 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 has no GIF read 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 memory stream'); + fwrite($f, $data); + rewind($f); + $info = $this->_parsepngstream($f, $file); + fclose($f); + return $info; + } + + protected 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'); + } + + protected function _put($s) + { + $this->buffer .= $s . "\n"; + } + + protected function _getoffset() + { + return strlen($this->buffer); + } + + protected function _newobj($n = null) + { + // Begin a new object + if ($n === null) + $n = ++$this->n; + $this->offsets[$n] = $this->_getoffset(); + $this->_put($n . ' 0 obj'); + } + + protected function _putstream($data) + { + $this->_put('stream'); + $this->_put($data); + $this->_put('endstream'); + } + + protected 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'); + } + + protected function _putpage($n) + { + $this->_newobj(); + $this->_put('<_put('/Parent 1 0 R'); + if (isset($this->PageInfo[$n]['size'])) + $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]', $this->PageInfo[$n]['size'][0], $this->PageInfo[$n]['size'][1])); + if (isset($this->PageInfo[$n]['rotation'])) + $this->_put('/Rotate ' . $this->PageInfo[$n]['rotation']); + $this->_put('/Resources 2 0 R'); + if (!empty($this->PageLinks[$n])) { + $s = '/Annots ['; + foreach ($this->PageLinks[$n] as $pl) + $s .= $pl[5] . ' 0 R '; + $s .= ']'; + $this->_put($s); + } + if ($this->WithAlpha) + $this->_put('/Group <>'); + $this->_put('/Contents ' . ($this->n + 1) . ' 0 R>>'); + $this->_put('endobj'); + // Page content + if (!empty($this->AliasNbPages)) + $this->pages[$n] = str_replace($this->AliasNbPages, $this->page, $this->pages[$n]); + $this->_putstreamobject($this->pages[$n]); + // Annotations + foreach ($this->PageLinks[$n] as $pl) { + $this->_newobj(); + $rect = sprintf('%.2F %.2F %.2F %.2F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3]); + $s = '<_textstring($pl[4]) . '>>>>'; + else { + $l = $this->links[$pl[4]]; + if (isset($this->PageInfo[$l[0]]['size'])) + $h = $this->PageInfo[$l[0]]['size'][1]; + else + $h = ($this->DefOrientation == 'P') ? $this->DefPageSize[1] * $this->k : $this->DefPageSize[0] * $this->k; + $s .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>', $this->PageInfo[$l[0]]['n'], $h - $l[1] * $this->k); + } + $this->_put($s); + $this->_put('endobj'); + } + } + + protected function _putpages() + { + $nb = $this->page; + $n = $this->n; + for ($i = 1; $i <= $nb; $i++) { + $this->PageInfo[$i]['n'] = ++$n; + $n++; + foreach ($this->PageLinks[$i] as &$pl) + $pl[5] = ++$n; + unset($pl); + } + for ($i = 1; $i <= $nb; $i++) + $this->_putpage($i); + // Pages root + $this->_newobj(1); + $this->_put('<PageInfo[$i]['n'] . ' 0 R '; + $kids .= ']'; + $this->_put($kids); + $this->_put('/Count ' . $nb); + if ($this->DefOrientation == 'P') { + $w = $this->DefPageSize[0]; + $h = $this->DefPageSize[1]; + } else { + $w = $this->DefPageSize[1]; + $h = $this->DefPageSize[0]; + } + $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]', $w * $this->k, $h * $this->k)); + $this->_put('>>'); + $this->_put('endobj'); + } + + protected 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'] . ' /Length3 0'); + $this->_put('>>'); + $this->_putstream($font); + $this->_put('endobj'); + } + foreach ($this->fonts as $k => $font) { + // Encoding + if (isset($font['diff'])) { + if (!isset($this->encodings[$font['enc']])) { + $this->_newobj(); + $this->_put('<>'); + $this->_put('endobj'); + $this->encodings[$font['enc']] = $this->n; + } + } + // ToUnicode CMap + if (isset($font['uv'])) { + if (isset($font['enc'])) + $cmapkey = $font['enc']; + else + $cmapkey = $font['name']; + if (!isset($this->cmaps[$cmapkey])) { + $cmap = $this->_tounicodecmap($font['uv']); + $this->_putstreamobject($cmap); + $this->cmaps[$cmapkey] = $this->n; + } + } + // Font object + $this->fonts[$k]['n'] = $this->n + 1; + $type = $font['type']; + $name = $font['name']; + if (isset($font['subsetted'])) + $name = 'AAAAAA+' . $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'); + if (isset($font['uv'])) + $this->_put('/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R'); + $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 /LastChar 255'); + $this->_put('/Widths ' . ($this->n + 1) . ' 0 R'); + $this->_put('/FontDescriptor ' . ($this->n + 2) . ' 0 R'); + if (isset($font['diff'])) + $this->_put('/Encoding ' . $this->encodings[$font['enc']] . ' 0 R'); + else + $this->_put('/Encoding /WinAnsiEncoding'); + if (isset($font['uv'])) + $this->_put('/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R'); + $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 .= ' /' . $k . ' ' . $v; + if (!empty($font['file'])) + $s .= ' /FontFile' . ($type == 'Type1' ? '' : '2') . ' ' . $this->FontFiles[$font['file']]['n'] . ' 0 R'; + $this->_put($s . '>>'); + $this->_put('endobj'); + } else { + // Allow for additional types + $mtd = '_put' . strtolower($type); + if (!method_exists($this, $mtd)) + $this->Error('Unsupported font type: ' . $type); + $this->$mtd($font); + } + } + } + + protected function _tounicodecmap($uv) + { + $ranges = ''; + $nbr = 0; + $chars = ''; + $nbc = 0; + foreach ($uv as $c => $v) { + if (is_array($v)) { + $ranges .= sprintf("<%02X> <%02X> <%04X>\n", $c, $c + $v[1] - 1, $v[0]); + $nbr++; + } else { + $chars .= sprintf("<%02X> <%04X>\n", $c, $v); + $nbc++; + } + } + $s = "/CIDInit /ProcSet findresource begin\n"; + $s .= "12 dict begin\n"; + $s .= "begincmap\n"; + $s .= "/CIDSystemInfo\n"; + $s .= "< 0) { + $s .= "$nbr beginbfrange\n"; + $s .= $ranges; + $s .= "endbfrange\n"; + } + if ($nbc > 0) { + $s .= "$nbc beginbfchar\n"; + $s .= $chars; + $s .= "endbfchar\n"; + } + $s .= "endcmap\n"; + $s .= "CMapName currentdict /CMap defineresource pop\n"; + $s .= "end\n"; + $s .= "end"; + return $s; + } + + protected function _putimages() + { + foreach (array_keys($this->images) as $file) { + $this->_putimage($this->images[$file]); + unset($this->images[$file]['data']); + unset($this->images[$file]['smask']); + } + } + + protected function _putimage(&$info) + { + $this->_newobj(); + $info['n'] = $this->n; + $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 < count($info['trns']); $i++) + $trns .= $info['trns'][$i] . ' ' . $info['trns'][$i] . ' '; + $this->_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' => $info['f'], 'dp' => $dp, 'data' => $info['smask']); + $this->_putimage($smask); + } + // Palette + if ($info['cs'] == 'Indexed') + $this->_putstreamobject($info['pal']); + } + + protected function _putxobjectdict() + { + foreach ($this->images as $image) + $this->_put('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); + } + + protected 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('>>'); + } + + protected function _putresources() + { + $this->_putfonts(); + $this->_putimages(); + // Resource dictionary + $this->_newobj(2); + $this->_put('<<'); + $this->_putresourcedict(); + $this->_put('>>'); + $this->_put('endobj'); + } + + protected function _putinfo() + { + $this->metadata['Producer'] = 'FPDF ' . FPDF_VERSION; + $this->metadata['CreationDate'] = 'D:' . @date('YmdHis'); + foreach ($this->metadata as $key => $value) + $this->_put('/' . $key . ' ' . $this->_textstring($value)); + } + + protected function _putcatalog() + { + $n = $this->PageInfo[1]['n']; + $this->_put('/Type /Catalog'); + $this->_put('/Pages 1 0 R'); + if ($this->ZoomMode == 'fullpage') + $this->_put('/OpenAction [' . $n . ' 0 R /Fit]'); + elseif ($this->ZoomMode == 'fullwidth') + $this->_put('/OpenAction [' . $n . ' 0 R /FitH null]'); + elseif ($this->ZoomMode == 'real') + $this->_put('/OpenAction [' . $n . ' 0 R /XYZ null null 1]'); + elseif (!is_string($this->ZoomMode)) + $this->_put('/OpenAction [' . $n . ' 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'); + } + + protected function _putheader() + { + $this->_put('%PDF-' . $this->PDFVersion); + } + + protected function _puttrailer() + { + $this->_put('/Size ' . ($this->n + 1)); + $this->_put('/Root ' . $this->n . ' 0 R'); + $this->_put('/Info ' . ($this->n - 1) . ' 0 R'); + } + + protected function _enddoc() + { + $this->_putheader(); + $this->_putpages(); + $this->_putresources(); + // Info + $this->_newobj(); + $this->_put('<<'); + $this->_putinfo(); + $this->_put('>>'); + $this->_put('endobj'); + // Catalog + $this->_newobj(); + $this->_put('<<'); + $this->_putcatalog(); + $this->_put('>>'); + $this->_put('endobj'); + // Cross-ref + $offset = $this->_getoffset(); + $this->_put('xref'); + $this->_put('0 ' . ($this->n + 1)); + $this->_put('0000000000 65535 f '); + for ($i = 1; $i <= $this->n; $i++) + $this->_put(sprintf('%010d 00000 n ', $this->offsets[$i])); + // Trailer + $this->_put('trailer'); + $this->_put('<<'); + $this->_puttrailer(); + $this->_put('>>'); + $this->_put('startxref'); + $this->_put($offset); + $this->_put('%%EOF'); + $this->state = 3; + } +} diff --git a/Fpdi/FpdfTpl.php b/Fpdi/FpdfTpl.php new file mode 100644 index 0000000..c4ce68f --- /dev/null +++ b/Fpdi/FpdfTpl.php @@ -0,0 +1,21 @@ +currentTemplateId !== null) { + throw new \BadMethodCallException('The page format cannot be changed when writing to a template.'); + } + + if (!\in_array($orientation, ['P', 'L'], true)) { + throw new \InvalidArgumentException(\sprintf( + 'Invalid page orientation "%s"! Only "P" and "L" are allowed!', + $orientation + )); + } + + $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; + + $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); + } + } + + /** + * Draws a template onto the page or another template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param array|float|int $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size + * @see FpdfTplTrait::getTemplateSize() + */ + public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (!isset($this->templates[$tpl])) { + throw new \InvalidArgumentException('Template does not exist!'); + } + + if (\is_array($x)) { + unset($x['tpl']); + \extract($x, EXTR_IF_EXISTS); + /** @noinspection NotOptimalIfConditionsInspection */ + /** @noinspection PhpConditionAlreadyCheckedInspection */ + if (\is_array($x)) { + $x = 0; + } + } + + $template = $this->templates[$tpl]; + + $originalSize = $this->getTemplateSize($tpl); + $newSize = $this->getTemplateSize($tpl, $width, $height); + if ($adjustPageSize) { + $this->setPageFormat($newSize, $newSize['orientation']); + } + + $this->_out( + // reset standard values, translate and scale + \sprintf( + 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q', + ($newSize['width'] / $originalSize['width']), + ($newSize['height'] / $originalSize['height']), + $x * $this->k, + ($this->h - $y - $newSize['height']) * $this->k, + $template['id'] + ) + ); + + return $newSize; + } + + /** + * Get the size of a template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getTemplateSize($tpl, $width = null, $height = null) + { + if (!isset($this->templates[$tpl])) { + return false; + } + + if ($width === null && $height === null) { + $width = $this->templates[$tpl]['width']; + $height = $this->templates[$tpl]['height']; + } elseif ($width === null) { + $width = $height * $this->templates[$tpl]['width'] / $this->templates[$tpl]['height']; + } + + if ($height === null) { + $height = $width * $this->templates[$tpl]['height'] / $this->templates[$tpl]['width']; + } + + if ($height <= 0. || $width <= 0.) { + throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.'); + } + + return [ + 'width' => $width, + 'height' => $height, + 0 => $width, + 1 => $height, + 'orientation' => $width > $height ? 'L' : 'P' + ]; + } + + /** + * Begins a new template. + * + * @param float|int|null $width The width of the template. If null, the current page width is used. + * @param float|int|null $height The height of the template. If null, the current page height is used. + * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). + * @return int A template identifier. + */ + public function beginTemplate($width = null, $height = null, $groupXObject = false) + { + if ($width === null) { + $width = $this->w; + } + + if ($height === null) { + $height = $this->h; + } + + $templateId = $this->getNextTemplateId(); + + // initiate buffer with current state of FPDF + $buffer = "2 J\n" + . \sprintf('%.2F w', $this->LineWidth * $this->k) . "\n"; + + if ($this->FontFamily) { + $buffer .= \sprintf("BT /F%d %.2F Tf ET\n", $this->CurrentFont['i'], $this->FontSizePt); + } + + if ($this->DrawColor !== '0 G') { + $buffer .= $this->DrawColor . "\n"; + } + if ($this->FillColor !== '0 g') { + $buffer .= $this->FillColor . "\n"; + } + + if ($groupXObject && \version_compare('1.4', $this->PDFVersion, '>')) { + $this->PDFVersion = '1.4'; + } + + $this->templates[$templateId] = [ + 'objectNumber' => null, + 'id' => 'TPL' . $templateId, + 'buffer' => $buffer, + 'width' => $width, + 'height' => $height, + 'groupXObject' => $groupXObject, + 'state' => [ + 'x' => $this->x, + 'y' => $this->y, + 'AutoPageBreak' => $this->AutoPageBreak, + 'bMargin' => $this->bMargin, + 'tMargin' => $this->tMargin, + 'lMargin' => $this->lMargin, + 'rMargin' => $this->rMargin, + 'h' => $this->h, + 'w' => $this->w, + 'FontFamily' => $this->FontFamily, + 'FontStyle' => $this->FontStyle, + 'FontSizePt' => $this->FontSizePt, + 'FontSize' => $this->FontSize, + 'underline' => $this->underline, + 'TextColor' => $this->TextColor, + 'DrawColor' => $this->DrawColor, + 'FillColor' => $this->FillColor, + 'ColorFlag' => $this->ColorFlag + ] + ]; + + $this->SetAutoPageBreak(false); + $this->currentTemplateId = $templateId; + + $this->h = $height; + $this->w = $width; + + $this->SetXY($this->lMargin, $this->tMargin); + $this->SetRightMargin($this->w - $width + $this->rMargin); + + return $templateId; + } + + /** + * Ends a template. + * + * @return bool|int|null A template identifier. + */ + public function endTemplate() + { + if ($this->currentTemplateId === null) { + return false; + } + + $templateId = $this->currentTemplateId; + $template = $this->templates[$templateId]; + + $state = $template['state']; + $this->SetXY($state['x'], $state['y']); + $this->tMargin = $state['tMargin']; + $this->lMargin = $state['lMargin']; + $this->rMargin = $state['rMargin']; + $this->h = $state['h']; + $this->w = $state['w']; + $this->SetAutoPageBreak($state['AutoPageBreak'], $state['bMargin']); + + $this->FontFamily = $state['FontFamily']; + $this->FontStyle = $state['FontStyle']; + $this->FontSizePt = $state['FontSizePt']; + $this->FontSize = $state['FontSize']; + + $this->TextColor = $state['TextColor']; + $this->DrawColor = $state['DrawColor']; + $this->FillColor = $state['FillColor']; + $this->ColorFlag = $state['ColorFlag']; + + $this->underline = $state['underline']; + + $fontKey = $this->FontFamily . $this->FontStyle; + if ($fontKey) { + $this->CurrentFont =& $this->fonts[$fontKey]; + } else { + unset($this->CurrentFont); + } + + $this->currentTemplateId = null; + + return $templateId; + } + + /** + * Get the next template id. + * + * @return int + */ + protected function getNextTemplateId() + { + return $this->templateId++; + } + + /* overwritten FPDF methods: */ + + /** + * @inheritdoc + */ + public function AddPage($orientation = '', $size = '', $rotation = 0) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('Pages cannot be added when writing to a template.'); + } + parent::AddPage($orientation, $size, $rotation); + } + + /** + * @inheritdoc + */ + public function Link($x, $y, $w, $h, $link) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('Links cannot be set when writing to a template.'); + } + parent::Link($x, $y, $w, $h, $link); + } + + /** + * @inheritdoc + */ + public function SetLink($link, $y = 0, $page = -1) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('Links cannot be set when writing to a template.'); + } + return parent::SetLink($link, $y, $page); + } + + /** + * @inheritdoc + */ + public function SetDrawColor($r, $g = null, $b = null) + { + parent::SetDrawColor($r, $g, $b); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out($this->DrawColor); + } + } + + /** + * @inheritdoc + */ + public function SetFillColor($r, $g = null, $b = null) + { + parent::SetFillColor($r, $g, $b); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out($this->FillColor); + } + } + + /** + * @inheritdoc + */ + public function SetLineWidth($width) + { + parent::SetLineWidth($width); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out(\sprintf('%.2F w', $width * $this->k)); + } + } + + /** + * @inheritdoc + */ + public function SetFont($family, $style = '', $size = 0) + { + parent::SetFont($family, $style, $size); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out(\sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); + } + } + + /** + * @inheritdoc + */ + public function SetFontSize($size) + { + parent::SetFontSize($size); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); + } + } + + /** + * @inheritdoc + */ + protected function _putimages() + { + parent::_putimages(); + + foreach ($this->templates as $key => $template) { + $this->_newobj(); + $this->templates[$key]['objectNumber'] = $this->n; + + $this->_put('<_put(\sprintf( + '/BBox[0 0 %.2F %.2F]', + $template['width'] * $this->k, + $template['height'] * $this->k + )); + $this->_put('/Resources 2 0 R'); // default resources dictionary of FPDF + + if ($this->compress) { + $buffer = \gzcompress($template['buffer']); + $this->_put('/Filter/FlateDecode'); + } else { + $buffer = $template['buffer']; + } + + $this->_put('/Length ' . \strlen($buffer)); + + if ($template['groupXObject']) { + $this->_put('/Group <>'); + } + + $this->_put('>>'); + $this->_putstream($buffer); + $this->_put('endobj'); + } + } + + /** + * @inheritdoc + */ + protected function _putxobjectdict() + { + foreach ($this->templates as $key => $template) { + $this->_put('/' . $template['id'] . ' ' . $template['objectNumber'] . ' 0 R'); + } + + parent::_putxobjectdict(); + } + + /** + * @inheritdoc + */ + public function _out($s) + { + if ($this->currentTemplateId !== null) { + $this->templates[$this->currentTemplateId]['buffer'] .= $s . "\n"; + } else { + parent::_out($s); + } + } +} diff --git a/Fpdi/Fpdi.php b/Fpdi/Fpdi.php new file mode 100644 index 0000000..dbbff28 --- /dev/null +++ b/Fpdi/Fpdi.php @@ -0,0 +1,153 @@ +cleanUp(); + } + + /** + * Draws an imported page or a template onto the page or another template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size + * @see Fpdi::getTemplateSize() + */ + public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (isset($this->importedPages[$tpl])) { + $size = $this->useImportedPage($tpl, $x, $y, $width, $height, $adjustPageSize); + if ($this->currentTemplateId !== null) { + $this->templates[$this->currentTemplateId]['resources']['templates']['importedPages'][$tpl] = $tpl; + } + return $size; + } + + return parent::useTemplate($tpl, $x, $y, $width, $height, $adjustPageSize); + } + + /** + * Get the size of an imported page or template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getTemplateSize($tpl, $width = null, $height = null) + { + $size = parent::getTemplateSize($tpl, $width, $height); + if ($size === false) { + return $this->getImportedPageSize($tpl, $width, $height); + } + + return $size; + } + + /** + * @inheritdoc + * @throws CrossReferenceException + * @throws PdfParserException + */ + protected function _putimages() + { + $this->currentReaderId = null; + parent::_putimages(); + + foreach ($this->importedPages as $key => $pageData) { + $this->_newobj(); + $this->importedPages[$key]['objectNumber'] = $this->n; + $this->currentReaderId = $pageData['readerId']; + $this->writePdfType($pageData['stream']); + $this->_put('endobj'); + } + + foreach (\array_keys($this->readers) as $readerId) { + $parser = $this->getPdfReader($readerId)->getParser(); + $this->currentReaderId = $readerId; + + while (($objectNumber = \array_pop($this->objectsToCopy[$readerId])) !== null) { + try { + $object = $parser->getIndirectObject($objectNumber); + } catch (CrossReferenceException $e) { + if ($e->getCode() === CrossReferenceException::OBJECT_NOT_FOUND) { + $object = PdfIndirectObject::create($objectNumber, 0, new PdfNull()); + } else { + throw $e; + } + } + + $this->writePdfType($object); + } + } + + $this->currentReaderId = null; + } + + /** + * @inheritdoc + */ + protected function _putxobjectdict() + { + foreach ($this->importedPages as $key => $pageData) { + $this->_put('/' . $pageData['id'] . ' ' . $pageData['objectNumber'] . ' 0 R'); + } + + parent::_putxobjectdict(); + } + + /** + * @inheritdoc + */ + protected function _put($s, $newLine = true) + { + if ($newLine) { + $this->buffer .= $s . "\n"; + } else { + $this->buffer .= $s; + } + } +} diff --git a/Fpdi/FpdiException.php b/Fpdi/FpdiException.php new file mode 100644 index 0000000..34e9fce --- /dev/null +++ b/Fpdi/FpdiException.php @@ -0,0 +1,18 @@ +readers) : $this->createdReaders; + foreach ($readers as $id) { + $this->readers[$id]->getParser()->getStreamReader()->cleanUp(); + unset($this->readers[$id]); + } + + $this->createdReaders = []; + } + + /** + * Set the minimal PDF version. + * + * @param string $pdfVersion + */ + protected function setMinPdfVersion($pdfVersion) + { + if (\version_compare($pdfVersion, $this->PDFVersion, '>')) { + $this->PDFVersion = $pdfVersion; + } + } + + /** @noinspection PhpUndefinedClassInspection */ + /** + * Get a new pdf parser instance. + * + * @param StreamReader $streamReader + * @return PdfParser|FpdiPdfParser + */ + protected function getPdfParserInstance(StreamReader $streamReader) + { + // note: if you get an exception here - turn off errors/warnings on not found for your autoloader. + // psr-4 (https://www.php-fig.org/psr/psr-4/) says: Autoloader implementations MUST NOT throw + // exceptions, MUST NOT raise errors of any level, and SHOULD NOT return a value. + /** @noinspection PhpUndefinedClassInspection */ + if (\class_exists(FpdiPdfParser::class)) { + /** @noinspection PhpUndefinedClassInspection */ + return new FpdiPdfParser($streamReader); + } + + return new PdfParser($streamReader); + } + + /** + * Get an unique reader id by the $file parameter. + * + * @param string|resource|PdfReader|StreamReader $file An open file descriptor, a path to a file, a PdfReader + * instance or a StreamReader instance. + * @return string + */ + protected function getPdfReaderId($file) + { + if (\is_resource($file)) { + $id = (string) $file; + } elseif (\is_string($file)) { + $id = \realpath($file); + if ($id === false) { + $id = $file; + } + } elseif (\is_object($file)) { + $id = \spl_object_hash($file); + } else { + throw new \InvalidArgumentException( + \sprintf('Invalid type in $file parameter (%s)', \gettype($file)) + ); + } + + /** @noinspection OffsetOperationsInspection */ + if (isset($this->readers[$id])) { + return $id; + } + + if (\is_resource($file)) { + $streamReader = new StreamReader($file); + } elseif (\is_string($file)) { + $streamReader = StreamReader::createByFile($file); + $this->createdReaders[] = $id; + } else { + $streamReader = $file; + } + + $reader = new PdfReader($this->getPdfParserInstance($streamReader)); + /** @noinspection OffsetOperationsInspection */ + $this->readers[$id] = $reader; + + return $id; + } + + /** + * Get a pdf reader instance by its id. + * + * @param string $id + * @return PdfReader + */ + protected function getPdfReader($id) + { + if (isset($this->readers[$id])) { + return $this->readers[$id]; + } + + throw new \InvalidArgumentException( + \sprintf('No pdf reader with the given id (%s) exists.', $id) + ); + } + + /** + * Set the source PDF file. + * + * @param string|resource|StreamReader $file Path to the file or a stream resource or a StreamReader instance. + * @return int The page count of the PDF document. + * @throws PdfParserException + */ + public function setSourceFile($file) + { + $this->currentReaderId = $this->getPdfReaderId($file); + $this->objectsToCopy[$this->currentReaderId] = []; + + $reader = $this->getPdfReader($this->currentReaderId); + $this->setMinPdfVersion($reader->getPdfVersion()); + + return $reader->getPageCount(); + } + + /** + * Imports a page. + * + * @param int $pageNumber The page number. + * @param string $box The page boundary to import. Default set to PageBoundaries::CROP_BOX. + * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). + * @return string A unique string identifying the imported page. + * @throws CrossReferenceException + * @throws FilterException + * @throws PdfParserException + * @throws PdfTypeException + * @throws PdfReaderException + * @see PageBoundaries + */ + public function importPage($pageNumber, $box = PageBoundaries::CROP_BOX, $groupXObject = true) + { + if (null === $this->currentReaderId) { + throw new \BadMethodCallException('No reader initiated. Call setSourceFile() first.'); + } + + $pageId = $this->currentReaderId; + + $pageNumber = (int)$pageNumber; + $pageId .= '|' . $pageNumber . '|' . ($groupXObject ? '1' : '0'); + + // for backwards compatibility with FPDI 1 + $box = \ltrim($box, '/'); + if (!PageBoundaries::isValidName($box)) { + throw new \InvalidArgumentException( + \sprintf('Box name is invalid: "%s"', $box) + ); + } + + $pageId .= '|' . $box; + + if (isset($this->importedPages[$pageId])) { + return $pageId; + } + + $reader = $this->getPdfReader($this->currentReaderId); + $page = $reader->getPage($pageNumber); + + $bbox = $page->getBoundary($box); + if ($bbox === false) { + throw new PdfReaderException( + \sprintf("Page doesn't have a boundary box (%s).", $box), + PdfReaderException::MISSING_DATA + ); + } + + $dict = new PdfDictionary(); + $dict->value['Type'] = PdfName::create('XObject'); + $dict->value['Subtype'] = PdfName::create('Form'); + $dict->value['FormType'] = PdfNumeric::create(1); + $dict->value['BBox'] = $bbox->toPdfArray(); + + if ($groupXObject) { + $this->setMinPdfVersion('1.4'); + $dict->value['Group'] = PdfDictionary::create([ + 'Type' => PdfName::create('Group'), + 'S' => PdfName::create('Transparency') + ]); + } + + $resources = $page->getAttribute('Resources'); + if ($resources !== null) { + $dict->value['Resources'] = $resources; + } + + list($width, $height) = $page->getWidthAndHeight($box); + + $a = 1; + $b = 0; + $c = 0; + $d = 1; + $e = -$bbox->getLlx(); + $f = -$bbox->getLly(); + + $rotation = $page->getRotation(); + + if ($rotation !== 0) { + $rotation *= -1; + $angle = $rotation * M_PI / 180; + $a = \cos($angle); + $b = \sin($angle); + $c = -$b; + $d = $a; + + switch ($rotation) { + case -90: + $e = -$bbox->getLly(); + $f = $bbox->getUrx(); + break; + case -180: + $e = $bbox->getUrx(); + $f = $bbox->getUry(); + break; + case -270: + $e = $bbox->getUry(); + $f = -$bbox->getLlx(); + break; + } + } + + // we need to rotate/translate + if ($a != 1 || $b != 0 || $c != 0 || $d != 1 || $e != 0 || $f != 0) { + $dict->value['Matrix'] = PdfArray::create([ + PdfNumeric::create($a), PdfNumeric::create($b), PdfNumeric::create($c), + PdfNumeric::create($d), PdfNumeric::create($e), PdfNumeric::create($f) + ]); + } + + // try to use the existing content stream + $pageDict = $page->getPageDictionary(); + + try { + $contentsObject = PdfType::resolve(PdfDictionary::get($pageDict, 'Contents'), $reader->getParser(), true); + $contents = PdfType::resolve($contentsObject, $reader->getParser()); + + // just copy the stream reference if it is only a single stream + if ( + ($contentsIsStream = ($contents instanceof PdfStream)) + || ($contents instanceof PdfArray && \count($contents->value) === 1) + ) { + if ($contentsIsStream) { + /** + * @var PdfIndirectObject $contentsObject + */ + $stream = $contents; + } else { + $stream = PdfType::resolve($contents->value[0], $reader->getParser()); + } + + $filter = PdfDictionary::get($stream->value, 'Filter'); + if (!$filter instanceof PdfNull) { + $dict->value['Filter'] = $filter; + } + $length = PdfType::resolve(PdfDictionary::get($stream->value, 'Length'), $reader->getParser()); + $dict->value['Length'] = $length; + $stream->value = $dict; + // otherwise extract it from the array and re-compress the whole stream + } else { + $streamContent = $this->compress + ? \gzcompress($page->getContentStream()) + : $page->getContentStream(); + + $dict->value['Length'] = PdfNumeric::create(\strlen($streamContent)); + if ($this->compress) { + $dict->value['Filter'] = PdfName::create('FlateDecode'); + } + + $stream = PdfStream::create($dict, $streamContent); + } + // Catch faulty pages and use an empty content stream + } catch (FpdiException $e) { + $dict->value['Length'] = PdfNumeric::create(0); + $stream = PdfStream::create($dict, ''); + } + + $this->importedPages[$pageId] = [ + 'objectNumber' => null, + 'readerId' => $this->currentReaderId, + 'id' => 'TPL' . $this->getNextTemplateId(), + 'width' => $width / $this->k, + 'height' => $height / $this->k, + 'stream' => $stream + ]; + + return $pageId; + } + + /** + * Draws an imported page onto the page. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $pageId The page id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size. + * @see Fpdi::getTemplateSize() + */ + public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (\is_array($x)) { + /** @noinspection OffsetOperationsInspection */ + unset($x['pageId']); + \extract($x, EXTR_IF_EXISTS); + /** @noinspection NotOptimalIfConditionsInspection */ + if (\is_array($x)) { + $x = 0; + } + } + + if (!isset($this->importedPages[$pageId])) { + throw new \InvalidArgumentException('Imported page does not exist!'); + } + + $importedPage = $this->importedPages[$pageId]; + + $originalSize = $this->getTemplateSize($pageId); + $newSize = $this->getTemplateSize($pageId, $width, $height); + if ($adjustPageSize) { + $this->setPageFormat($newSize, $newSize['orientation']); + } + + $this->_out( + // reset standard values, translate and scale + \sprintf( + 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q', + ($newSize['width'] / $originalSize['width']), + ($newSize['height'] / $originalSize['height']), + $x * $this->k, + ($this->h - $y - $newSize['height']) * $this->k, + $importedPage['id'] + ) + ); + + return $newSize; + } + + /** + * Get the size of an imported page. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getImportedPageSize($tpl, $width = null, $height = null) + { + if (isset($this->importedPages[$tpl])) { + $importedPage = $this->importedPages[$tpl]; + + if ($width === null && $height === null) { + $width = $importedPage['width']; + $height = $importedPage['height']; + } elseif ($width === null) { + $width = $height * $importedPage['width'] / $importedPage['height']; + } + + if ($height === null) { + $height = $width * $importedPage['height'] / $importedPage['width']; + } + + if ($height <= 0. || $width <= 0.) { + throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.'); + } + + return [ + 'width' => $width, + 'height' => $height, + 0 => $width, + 1 => $height, + 'orientation' => $width > $height ? 'L' : 'P' + ]; + } + + return false; + } + + /** + * Writes a PdfType object to the resulting buffer. + * + * @param PdfType $value + * @throws PdfTypeException + */ + protected function writePdfType(PdfType $value) + { + if ($value instanceof PdfNumeric) { + if (\is_int($value->value)) { + $this->_put($value->value . ' ', false); + } else { + $this->_put(\rtrim(\rtrim(\sprintf('%.5F', $value->value), '0'), '.') . ' ', false); + } + } elseif ($value instanceof PdfName) { + $this->_put('/' . $value->value . ' ', false); + } elseif ($value instanceof PdfString) { + $this->_put('(' . $value->value . ')', false); + } elseif ($value instanceof PdfHexString) { + $this->_put('<' . $value->value . '>'); + } elseif ($value instanceof PdfBoolean) { + $this->_put($value->value ? 'true ' : 'false ', false); + } elseif ($value instanceof PdfArray) { + $this->_put('[', false); + foreach ($value->value as $entry) { + $this->writePdfType($entry); + } + $this->_put(']'); + } elseif ($value instanceof PdfDictionary) { + $this->_put('<<', false); + foreach ($value->value as $name => $entry) { + $this->_put('/' . $name . ' ', false); + $this->writePdfType($entry); + } + $this->_put('>>'); + } elseif ($value instanceof PdfToken) { + $this->_put($value->value); + } elseif ($value instanceof PdfNull) { + $this->_put('null '); + } elseif ($value instanceof PdfStream) { + /** + * @var $value PdfStream + */ + $this->writePdfType($value->value); + $this->_put('stream'); + $this->_put($value->getStream()); + $this->_put('endstream'); + } elseif ($value instanceof PdfIndirectObjectReference) { + if (!isset($this->objectMap[$this->currentReaderId])) { + $this->objectMap[$this->currentReaderId] = []; + } + + if (!isset($this->objectMap[$this->currentReaderId][$value->value])) { + $this->objectMap[$this->currentReaderId][$value->value] = ++$this->n; + $this->objectsToCopy[$this->currentReaderId][] = $value->value; + } + + $this->_put($this->objectMap[$this->currentReaderId][$value->value] . ' 0 R ', false); + } elseif ($value instanceof PdfIndirectObject) { + /** + * @var PdfIndirectObject $value + */ + $n = $this->objectMap[$this->currentReaderId][$value->objectNumber]; + $this->_newobj($n); + $this->writePdfType($value->value); + $this->_put('endobj'); + } + } +} diff --git a/Fpdi/PdfParser/CrossReference/AbstractReader.php b/Fpdi/PdfParser/CrossReference/AbstractReader.php new file mode 100644 index 0000000..5e2b215 --- /dev/null +++ b/Fpdi/PdfParser/CrossReference/AbstractReader.php @@ -0,0 +1,95 @@ +parser = $parser; + $this->readTrailer(); + } + + /** + * Get the trailer dictionary. + * + * @return PdfDictionary + */ + public function getTrailer() + { + return $this->trailer; + } + + /** + * Read the trailer dictionary. + * + * @throws CrossReferenceException + * @throws PdfTypeException + */ + protected function readTrailer() + { + try { + $trailerKeyword = $this->parser->readValue(null, PdfToken::class); + if ($trailerKeyword->value !== 'trailer') { + throw new CrossReferenceException( + \sprintf( + 'Unexpected end of cross reference. "trailer"-keyword expected, got: %s.', + $trailerKeyword->value + ), + CrossReferenceException::UNEXPECTED_END + ); + } + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Unexpected end of cross reference. "trailer"-keyword expected, got an invalid object type.', + CrossReferenceException::UNEXPECTED_END, + $e + ); + } + + try { + $trailer = $this->parser->readValue(null, PdfDictionary::class); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Unexpected end of cross reference. Trailer not found.', + CrossReferenceException::UNEXPECTED_END, + $e + ); + } + + $this->trailer = $trailer; + } +} diff --git a/Fpdi/PdfParser/CrossReference/CrossReference.php b/Fpdi/PdfParser/CrossReference/CrossReference.php new file mode 100644 index 0000000..cfa725d --- /dev/null +++ b/Fpdi/PdfParser/CrossReference/CrossReference.php @@ -0,0 +1,326 @@ +parser = $parser; + $this->fileHeaderOffset = $fileHeaderOffset; + + $offset = $this->findStartXref(); + $reader = null; + /** @noinspection TypeUnsafeComparisonInspection */ + while ($offset != false) { // By doing an unsafe comparsion we ignore faulty references to byte offset 0 + try { + $reader = $this->readXref($offset + $this->fileHeaderOffset); + } catch (CrossReferenceException $e) { + // sometimes the file header offset is part of the byte offsets, so let's retry by resetting it to zero. + if ($e->getCode() === CrossReferenceException::INVALID_DATA && $this->fileHeaderOffset !== 0) { + $this->fileHeaderOffset = 0; + $reader = $this->readXref($offset + $this->fileHeaderOffset); + } else { + throw $e; + } + } + + $trailer = $reader->getTrailer(); + $this->checkForEncryption($trailer); + $this->readers[] = $reader; + + if (isset($trailer->value['Prev'])) { + $offset = $trailer->value['Prev']->value; + } else { + $offset = false; + } + } + + // fix faulty sub-section header + if ($reader instanceof FixedReader) { + /** + * @var FixedReader $reader + */ + $reader->fixFaultySubSectionShift(); + } + + if ($reader === null) { + throw new CrossReferenceException('No cross-reference found.', CrossReferenceException::NO_XREF_FOUND); + } + } + + /** + * Get the size of the cross reference. + * + * @return integer + */ + public function getSize() + { + return $this->getTrailer()->value['Size']->value; + } + + /** + * Get the trailer dictionary. + * + * @return PdfDictionary + */ + public function getTrailer() + { + return $this->readers[0]->getTrailer(); + } + + /** + * Get the cross reference readser instances. + * + * @return ReaderInterface[] + */ + public function getReaders() + { + return $this->readers; + } + + /** + * Get the offset by an object number. + * + * @param int $objectNumber + * @return integer|bool + */ + public function getOffsetFor($objectNumber) + { + foreach ($this->getReaders() as $reader) { + $offset = $reader->getOffsetFor($objectNumber); + if ($offset !== false) { + return $offset; + } + } + + return false; + } + + /** + * Get an indirect object by its object number. + * + * @param int $objectNumber + * @return PdfIndirectObject + * @throws CrossReferenceException + */ + public function getIndirectObject($objectNumber) + { + $offset = $this->getOffsetFor($objectNumber); + if ($offset === false) { + throw new CrossReferenceException( + \sprintf('Object (id:%s) not found.', $objectNumber), + CrossReferenceException::OBJECT_NOT_FOUND + ); + } + + $parser = $this->parser; + + $parser->getTokenizer()->clearStack(); + $parser->getStreamReader()->reset($offset + $this->fileHeaderOffset); + + try { + /** @var PdfIndirectObject $object */ + $object = $parser->readValue(null, PdfIndirectObject::class); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + \sprintf('Object (id:%s) not found at location (%s).', $objectNumber, $offset), + CrossReferenceException::OBJECT_NOT_FOUND, + $e + ); + } + + if ($object->objectNumber !== $objectNumber) { + throw new CrossReferenceException( + \sprintf('Wrong object found, got %s while %s was expected.', $object->objectNumber, $objectNumber), + CrossReferenceException::OBJECT_NOT_FOUND + ); + } + + return $object; + } + + /** + * Read the cross-reference table at a given offset. + * + * Internally the method will try to evaluate the best reader for this cross-reference. + * + * @param int $offset + * @return ReaderInterface + * @throws CrossReferenceException + * @throws PdfTypeException + */ + protected function readXref($offset) + { + $this->parser->getStreamReader()->reset($offset); + $this->parser->getTokenizer()->clearStack(); + $initValue = $this->parser->readValue(); + + return $this->initReaderInstance($initValue); + } + + /** + * Get a cross-reference reader instance. + * + * @param PdfToken|PdfIndirectObject $initValue + * @return ReaderInterface|bool + * @throws CrossReferenceException + * @throws PdfTypeException + */ + protected function initReaderInstance($initValue) + { + $position = $this->parser->getStreamReader()->getPosition() + + $this->parser->getStreamReader()->getOffset() + $this->fileHeaderOffset; + + if ($initValue instanceof PdfToken && $initValue->value === 'xref') { + try { + return new FixedReader($this->parser); + } catch (CrossReferenceException $e) { + $this->parser->getStreamReader()->reset($position); + $this->parser->getTokenizer()->clearStack(); + + return new LineReader($this->parser); + } + } + + if ($initValue instanceof PdfIndirectObject) { + try { + $stream = PdfStream::ensure($initValue->value); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Invalid object type at xref reference offset.', + CrossReferenceException::INVALID_DATA, + $e + ); + } + + $type = PdfDictionary::get($stream->value, 'Type'); + if ($type->value !== 'XRef') { + throw new CrossReferenceException( + 'The xref position points to an incorrect object type.', + CrossReferenceException::INVALID_DATA + ); + } + + $this->checkForEncryption($stream->value); + + throw new CrossReferenceException( + 'This PDF document probably uses a compression technique which is not supported by the ' . + 'free parser shipped with FPDI. (See https://www.setasign.com/fpdi-pdf-parser for more details)', + CrossReferenceException::COMPRESSED_XREF + ); + } + + throw new CrossReferenceException( + 'The xref position points to an incorrect object type.', + CrossReferenceException::INVALID_DATA + ); + } + + /** + * Check for encryption. + * + * @param PdfDictionary $dictionary + * @throws CrossReferenceException + */ + protected function checkForEncryption(PdfDictionary $dictionary) + { + if (isset($dictionary->value['Encrypt'])) { + throw new CrossReferenceException( + 'This PDF document is encrypted and cannot be processed with FPDI.', + CrossReferenceException::ENCRYPTED + ); + } + } + + /** + * Find the start position for the first cross-reference. + * + * @return int The byte-offset position of the first cross-reference. + * @throws CrossReferenceException + */ + protected function findStartXref() + { + $reader = $this->parser->getStreamReader(); + $reader->reset(-self::$trailerSearchLength, self::$trailerSearchLength); + + $buffer = $reader->getBuffer(false); + $pos = \strrpos($buffer, 'startxref'); + $addOffset = 9; + if ($pos === false) { + // Some corrupted documents uses startref, instead of startxref + $pos = \strrpos($buffer, 'startref'); + if ($pos === false) { + throw new CrossReferenceException( + 'Unable to find pointer to xref table', + CrossReferenceException::NO_STARTXREF_FOUND + ); + } + $addOffset = 8; + } + + $reader->setOffset($pos + $addOffset); + + try { + $value = $this->parser->readValue(null, PdfNumeric::class); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Invalid data after startxref keyword.', + CrossReferenceException::INVALID_DATA, + $e + ); + } + + return $value->value; + } +} diff --git a/Fpdi/PdfParser/CrossReference/CrossReferenceException.php b/Fpdi/PdfParser/CrossReference/CrossReferenceException.php new file mode 100644 index 0000000..52c7ee1 --- /dev/null +++ b/Fpdi/PdfParser/CrossReference/CrossReferenceException.php @@ -0,0 +1,79 @@ +reader = $parser->getStreamReader(); + $this->read(); + parent::__construct($parser); + } + + /** + * Get all subsection data. + * + * @return array + */ + public function getSubSections() + { + return $this->subSections; + } + + /** + * @inheritdoc + */ + public function getOffsetFor($objectNumber) + { + foreach ($this->subSections as $offset => list($startObject, $objectCount)) { + /** + * @var int $startObject + * @var int $objectCount + */ + if ($objectNumber >= $startObject && $objectNumber < ($startObject + $objectCount)) { + $position = $offset + 20 * ($objectNumber - $startObject); + $this->reader->ensure($position, 20); + $line = $this->reader->readBytes(20); + if ($line[17] === 'f') { + return false; + } + + return (int) \substr($line, 0, 10); + } + } + + return false; + } + + /** + * Read the cross-reference. + * + * This reader will only read the subsections in this method. The offsets were resolved individually by this + * information. + * + * @throws CrossReferenceException + */ + protected function read() + { + $subSections = []; + + $startObject = $entryCount = $lastLineStart = null; + $validityChecked = false; + while (($line = $this->reader->readLine(20)) !== false) { + if (\strpos($line, 'trailer') !== false) { + $this->reader->reset($lastLineStart); + break; + } + + // jump over if line content doesn't match the expected string + if (\sscanf($line, '%d %d', $startObject, $entryCount) !== 2) { + continue; + } + + $oldPosition = $this->reader->getPosition(); + $position = $oldPosition + $this->reader->getOffset(); + + if (!$validityChecked && $entryCount > 0) { + $nextLine = $this->reader->readBytes(21); + /* Check the next line for maximum of 20 bytes and not longer + * By catching 21 bytes and trimming the length should be still 21. + */ + if (\strlen(\trim($nextLine)) !== 21) { + throw new CrossReferenceException( + 'Cross-reference entries are larger than 20 bytes.', + CrossReferenceException::ENTRIES_TOO_LARGE + ); + } + + /* Check for less than 20 bytes: cut the line to 20 bytes and trim; have to result in exactly 18 bytes. + * If it would have less bytes the substring would get the first bytes of the next line which would + * evaluate to a 20 bytes long string after trimming. + */ + if (\strlen(\trim(\substr($nextLine, 0, 20))) !== 18) { + throw new CrossReferenceException( + 'Cross-reference entries are less than 20 bytes.', + CrossReferenceException::ENTRIES_TOO_SHORT + ); + } + + $validityChecked = true; + } + + $subSections[$position] = [$startObject, $entryCount]; + + $lastLineStart = $position + $entryCount * 20; + $this->reader->reset($lastLineStart); + } + + // reset after the last correct parsed line + $this->reader->reset($lastLineStart); + + if (\count($subSections) === 0) { + throw new CrossReferenceException( + 'No entries found in cross-reference.', + CrossReferenceException::NO_ENTRIES + ); + } + + $this->subSections = $subSections; + } + + /** + * Fixes an invalid object number shift. + * + * This method can be used to repair documents with an invalid subsection header: + * + * + * xref + * 1 7 + * 0000000000 65535 f + * 0000000009 00000 n + * 0000412075 00000 n + * 0000412172 00000 n + * 0000412359 00000 n + * 0000412417 00000 n + * 0000412468 00000 n + * + * + * It shall only be called on the first table. + * + * @return bool + */ + public function fixFaultySubSectionShift() + { + $subSections = $this->getSubSections(); + if (\count($subSections) > 1) { + return false; + } + + $subSection = \current($subSections); + if ($subSection[0] != 1) { + return false; + } + + if ($this->getOffsetFor(1) === false) { + foreach ($subSections as $offset => list($startObject, $objectCount)) { + $this->subSections[$offset] = [$startObject - 1, $objectCount]; + } + return true; + } + + return false; + } +} diff --git a/Fpdi/PdfParser/CrossReference/LineReader.php b/Fpdi/PdfParser/CrossReference/LineReader.php new file mode 100644 index 0000000..e557a91 --- /dev/null +++ b/Fpdi/PdfParser/CrossReference/LineReader.php @@ -0,0 +1,167 @@ + 20 bytes). + */ +class LineReader extends AbstractReader implements ReaderInterface +{ + /** + * The object offsets. + * + * @var array + */ + protected $offsets; + + /** + * LineReader constructor. + * + * @param PdfParser $parser + * @throws CrossReferenceException + */ + public function __construct(PdfParser $parser) + { + $this->read($this->extract($parser->getStreamReader())); + parent::__construct($parser); + } + + /** + * @inheritdoc + */ + public function getOffsetFor($objectNumber) + { + if (isset($this->offsets[$objectNumber])) { + return $this->offsets[$objectNumber][0]; + } + + return false; + } + + /** + * Get all found offsets. + * + * @return array + */ + public function getOffsets() + { + return $this->offsets; + } + + /** + * Extracts the cross reference data from the stream reader. + * + * @param StreamReader $reader + * @return string + * @throws CrossReferenceException + */ + protected function extract(StreamReader $reader) + { + $bytesPerCycle = 100; + $reader->reset(null, $bytesPerCycle); + + $cycles = 0; + do { + // 6 = length of "trailer" - 1 + $pos = \max(($bytesPerCycle * $cycles) - 6, 0); + $trailerPos = \strpos($reader->getBuffer(false), 'trailer', $pos); + $cycles++; + } while ($trailerPos === false && $reader->increaseLength($bytesPerCycle) !== false); + + if ($trailerPos === false) { + throw new CrossReferenceException( + 'Unexpected end of cross reference. "trailer"-keyword not found.', + CrossReferenceException::NO_TRAILER_FOUND + ); + } + + $xrefContent = \substr($reader->getBuffer(false), 0, $trailerPos); + $reader->reset($reader->getPosition() + $trailerPos); + + return $xrefContent; + } + + /** + * Read the cross-reference entries. + * + * @param string $xrefContent + * @throws CrossReferenceException + */ + protected function read($xrefContent) + { + // get eol markers in the first 100 bytes + \preg_match_all("/(\r\n|\n|\r)/", \substr($xrefContent, 0, 100), $m); + + if (\count($m[0]) === 0) { + throw new CrossReferenceException( + 'No data found in cross-reference.', + CrossReferenceException::INVALID_DATA + ); + } + + // count(array_count_values()) is faster then count(array_unique()) + // @see https://github.com/symfony/symfony/pull/23731 + // can be reverted for php7.2 + $differentLineEndings = \count(\array_count_values($m[0])); + if ($differentLineEndings > 1) { + $lines = \preg_split("/(\r\n|\n|\r)/", $xrefContent, -1, PREG_SPLIT_NO_EMPTY); + } else { + $lines = \explode($m[0][0], $xrefContent); + } + + unset($differentLineEndings, $m); + if (!\is_array($lines)) { + $this->offsets = []; + return; + } + + $start = 0; + $offsets = []; + + // trim all lines and remove empty lines + $lines = \array_filter(\array_map('\trim', $lines)); + foreach ($lines as $line) { + $pieces = \explode(' ', $line); + + switch (\count($pieces)) { + case 2: + $start = (int) $pieces[0]; + break; + + case 3: + switch ($pieces[2]) { + case 'n': + $offsets[$start] = [(int) $pieces[0], (int) $pieces[1]]; + $start++; + break 2; + case 'f': + $start++; + break 2; + } + // fall through if pieces doesn't match + + default: + throw new CrossReferenceException( + \sprintf('Unexpected data in xref table (%s)', \implode(' ', $pieces)), + CrossReferenceException::INVALID_DATA + ); + } + } + + $this->offsets = $offsets; + } +} diff --git a/Fpdi/PdfParser/CrossReference/ReaderInterface.php b/Fpdi/PdfParser/CrossReference/ReaderInterface.php new file mode 100644 index 0000000..4d3a929 --- /dev/null +++ b/Fpdi/PdfParser/CrossReference/ReaderInterface.php @@ -0,0 +1,34 @@ + + if ($ch === 126 && isset($data[$k + 1]) && (\ord($data[$k + 1]) & 0xFF) === 62) { + break; + } + + if ($ch === 122 /* z */ && $state === 0) { + $out .= \chr(0) . \chr(0) . \chr(0) . \chr(0); + continue; + } + + if ($ch < 33 /* ! */ || $ch > 117 /* u */) { + throw new Ascii85Exception( + 'Illegal character found while ASCII85 decode.', + Ascii85Exception::ILLEGAL_CHAR_FOUND + ); + } + + $chn[$state] = $ch - 33;/* ! */ + $state++; + + if ($state === 5) { + $state = 0; + $r = 0; + for ($j = 0; $j < 5; ++$j) { + /** @noinspection UnnecessaryCastingInspection */ + $r = (int)($r * 85 + $chn[$j]); + } + + $out .= \chr($r >> 24) + . \chr($r >> 16) + . \chr($r >> 8) + . \chr($r); + } + } + + if ($state === 1) { + throw new Ascii85Exception( + 'Illegal length while ASCII85 decode.', + Ascii85Exception::ILLEGAL_LENGTH + ); + } + + if ($state === 2) { + $r = $chn[0] * 85 * 85 * 85 * 85 + ($chn[1] + 1) * 85 * 85 * 85; + $out .= \chr($r >> 24); + } elseif ($state === 3) { + $r = $chn[0] * 85 * 85 * 85 * 85 + $chn[1] * 85 * 85 * 85 + ($chn[2] + 1) * 85 * 85; + $out .= \chr($r >> 24); + $out .= \chr($r >> 16); + } elseif ($state === 4) { + $r = $chn[0] * 85 * 85 * 85 * 85 + $chn[1] * 85 * 85 * 85 + $chn[2] * 85 * 85 + ($chn[3] + 1) * 85; + $out .= \chr($r >> 24); + $out .= \chr($r >> 16); + $out .= \chr($r >> 8); + } + + return $out; + } +} diff --git a/Fpdi/PdfParser/Filter/Ascii85Exception.php b/Fpdi/PdfParser/Filter/Ascii85Exception.php new file mode 100644 index 0000000..e9d27c5 --- /dev/null +++ b/Fpdi/PdfParser/Filter/Ascii85Exception.php @@ -0,0 +1,27 @@ +')); + if ((\strlen($data) % 2) === 1) { + $data .= '0'; + } + + return \pack('H*', $data); + } + + /** + * Converts a string into ASCII hexadecimal representation. + * + * @param string $data The input string + * @param boolean $leaveEOD + * @return string + */ + public function encode($data, $leaveEOD = false) + { + $t = \unpack('H*', $data); + return \current($t) + . ($leaveEOD ? '' : '>'); + } +} diff --git a/Fpdi/PdfParser/Filter/FilterException.php b/Fpdi/PdfParser/Filter/FilterException.php new file mode 100644 index 0000000..2a63753 --- /dev/null +++ b/Fpdi/PdfParser/Filter/FilterException.php @@ -0,0 +1,23 @@ +extensionLoaded()) { + $oData = $data; + $data = (($data !== '') ? @\gzuncompress($data) : ''); + if ($data === false) { + // let's try if the checksum is CRC32 + $fh = fopen('php://temp', 'w+b'); + fwrite($fh, "\x1f\x8b\x08\x00\x00\x00\x00\x00" . $oData); + stream_filter_append($fh, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 30]); + fseek($fh, 0); + $data = @stream_get_contents($fh); + fclose($fh); + + if ($data) { + return $data; + } + + // Try this fallback + $tries = 0; + + $oDataLen = strlen($oData); + while ($tries < 6 && ($data === false || (strlen($data) < ($oDataLen - $tries - 1)))) { + $data = @(gzinflate(substr($oData, $tries))); + $tries++; + } + + // let's use this fallback only if the $data is longer than the original data + if (strlen($data) > ($oDataLen - $tries - 1)) { + return $data; + } + + if (!$data) { + throw new FlateException( + 'Error while decompressing stream.', + FlateException::DECOMPRESS_ERROR + ); + } + } + } else { + throw new FlateException( + 'To handle FlateDecode filter, enable zlib support in PHP.', + FlateException::NO_ZLIB + ); + } + + return $data; + } +} diff --git a/Fpdi/PdfParser/Filter/FlateException.php b/Fpdi/PdfParser/Filter/FlateException.php new file mode 100644 index 0000000..c86b5db --- /dev/null +++ b/Fpdi/PdfParser/Filter/FlateException.php @@ -0,0 +1,27 @@ +initsTable(); + + $this->data = $data; + $this->dataLength = \strlen($data); + + // Initialize pointers + $this->bytePointer = 0; + + $this->nextData = 0; + $this->nextBits = 0; + + $oldCode = 0; + + $uncompData = ''; + + while (($code = $this->getNextCode()) !== 257) { + if ($code === 256) { + $this->initsTable(); + $code = $this->getNextCode(); + + if ($code === 257) { + break; + } + + $uncompData .= $this->sTable[$code]; + $oldCode = $code; + } else { + if ($code < $this->tIdx) { + $string = $this->sTable[$code]; + $uncompData .= $string; + + $this->addStringToTable($this->sTable[$oldCode], $string[0]); + $oldCode = $code; + } else { + $string = $this->sTable[$oldCode]; + $string .= $string[0]; + $uncompData .= $string; + + $this->addStringToTable($string); + $oldCode = $code; + } + } + } + + return $uncompData; + } + + /** + * Initialize the string table. + */ + protected function initsTable() + { + $this->sTable = []; + + for ($i = 0; $i < 256; $i++) { + $this->sTable[$i] = \chr($i); + } + + $this->tIdx = 258; + $this->bitsToGet = 9; + } + + /** + * Add a new string to the string table. + * + * @param string $oldString + * @param string $newString + */ + protected function addStringToTable($oldString, $newString = '') + { + $string = $oldString . $newString; + + // Add this new String to the table + $this->sTable[$this->tIdx++] = $string; + + if ($this->tIdx === 511) { + $this->bitsToGet = 10; + } elseif ($this->tIdx === 1023) { + $this->bitsToGet = 11; + } elseif ($this->tIdx === 2047) { + $this->bitsToGet = 12; + } + } + + /** + * Returns the next 9, 10, 11 or 12 bits. + * + * @return integer + */ + protected function getNextCode() + { + if ($this->bytePointer === $this->dataLength) { + return 257; + } + + $this->nextData = ($this->nextData << 8) | (\ord($this->data[$this->bytePointer++]) & 0xff); + $this->nextBits += 8; + + if ($this->nextBits < $this->bitsToGet) { + $this->nextData = ($this->nextData << 8) | (\ord($this->data[$this->bytePointer++]) & 0xff); + $this->nextBits += 8; + } + + $code = ($this->nextData >> ($this->nextBits - $this->bitsToGet)) & $this->andTable[$this->bitsToGet - 9]; + $this->nextBits -= $this->bitsToGet; + + return $code; + } +} diff --git a/Fpdi/PdfParser/Filter/LzwException.php b/Fpdi/PdfParser/Filter/LzwException.php new file mode 100644 index 0000000..e638adf --- /dev/null +++ b/Fpdi/PdfParser/Filter/LzwException.php @@ -0,0 +1,22 @@ +streamReader = $streamReader; + $this->tokenizer = new Tokenizer($streamReader); + } + + /** + * Removes cycled references. + * + * @internal + */ + public function cleanUp() + { + $this->xref = null; + } + + /** + * Get the stream reader instance. + * + * @return StreamReader + */ + public function getStreamReader() + { + return $this->streamReader; + } + + /** + * Get the tokenizer instance. + * + * @return Tokenizer + */ + public function getTokenizer() + { + return $this->tokenizer; + } + + /** + * Resolves the file header. + * + * @throws PdfParserException + * @return int + */ + protected function resolveFileHeader() + { + if ($this->fileHeader) { + return $this->fileHeaderOffset; + } + + $this->streamReader->reset(0); + $maxIterations = 1000; + while (true) { + $buffer = $this->streamReader->getBuffer(false); + $offset = \strpos($buffer, '%PDF-'); + if ($offset === false) { + if (!$this->streamReader->increaseLength(100) || (--$maxIterations === 0)) { + throw new PdfParserException( + 'Unable to find PDF file header.', + PdfParserException::FILE_HEADER_NOT_FOUND + ); + } + continue; + } + break; + } + + $this->fileHeaderOffset = $offset; + $this->streamReader->setOffset($offset); + + $this->fileHeader = \trim($this->streamReader->readLine()); + return $this->fileHeaderOffset; + } + + /** + * Get the cross reference instance. + * + * @return CrossReference + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getCrossReference() + { + if ($this->xref === null) { + $this->xref = new CrossReference($this, $this->resolveFileHeader()); + } + + return $this->xref; + } + + /** + * Get the PDF version. + * + * @return int[] An array of major and minor version. + * @throws PdfParserException + */ + public function getPdfVersion() + { + $this->resolveFileHeader(); + + if (\preg_match('/%PDF-(\d)\.(\d)/', $this->fileHeader, $result) === 0) { + throw new PdfParserException( + 'Unable to extract PDF version from file header.', + PdfParserException::PDF_VERSION_NOT_FOUND + ); + } + list(, $major, $minor) = $result; + + $catalog = $this->getCatalog(); + if (isset($catalog->value['Version'])) { + $versionParts = \explode( + '.', + PdfName::unescape(PdfType::resolve($catalog->value['Version'], $this)->value) + ); + if (count($versionParts) === 2) { + list($major, $minor) = $versionParts; + } + } + + return [(int) $major, (int) $minor]; + } + + /** + * Get the catalog dictionary. + * + * @return PdfDictionary + * @throws Type\PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getCatalog() + { + $trailer = $this->getCrossReference()->getTrailer(); + + $catalog = PdfType::resolve(PdfDictionary::get($trailer, 'Root'), $this); + + return PdfDictionary::ensure($catalog); + } + + /** + * Get an indirect object by its object number. + * + * @param int $objectNumber + * @param bool $cache + * @return PdfIndirectObject + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getIndirectObject($objectNumber, $cache = false) + { + $objectNumber = (int) $objectNumber; + if (isset($this->objects[$objectNumber])) { + return $this->objects[$objectNumber]; + } + + $object = $this->getCrossReference()->getIndirectObject($objectNumber); + + if ($cache) { + $this->objects[$objectNumber] = $object; + } + + return $object; + } + + /** + * Read a PDF value. + * + * @param null|bool|string $token + * @param null|string $expectedType + * @return false|PdfArray|PdfBoolean|PdfDictionary|PdfHexString|PdfIndirectObject|PdfIndirectObjectReference|PdfName|PdfNull|PdfNumeric|PdfStream|PdfString|PdfToken + * @throws Type\PdfTypeException + */ + public function readValue($token = null, $expectedType = null) + { + if ($token === null) { + $token = $this->tokenizer->getNextToken(); + } + + if ($token === false) { + if ($expectedType !== null) { + throw new Type\PdfTypeException('Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE); + } + return false; + } + + switch ($token) { + case '(': + $this->ensureExpectedType($token, $expectedType); + return PdfString::parse($this->streamReader); + + case '<': + if ($this->streamReader->getByte() === '<') { + $this->ensureExpectedType('<<', $expectedType); + $this->streamReader->addOffset(1); + return PdfDictionary::parse($this->tokenizer, $this->streamReader, $this); + } + + $this->ensureExpectedType($token, $expectedType); + return PdfHexString::parse($this->streamReader); + + case '/': + $this->ensureExpectedType($token, $expectedType); + return PdfName::parse($this->tokenizer, $this->streamReader); + + case '[': + $this->ensureExpectedType($token, $expectedType); + return PdfArray::parse($this->tokenizer, $this); + + default: + if (\is_numeric($token)) { + if (($token2 = $this->tokenizer->getNextToken()) !== false) { + if (\is_numeric($token2) && ($token3 = $this->tokenizer->getNextToken()) !== false) { + switch ($token3) { + case 'obj': + if ($expectedType !== null && $expectedType !== PdfIndirectObject::class) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', + Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + + return PdfIndirectObject::parse( + (int) $token, + (int) $token2, + $this, + $this->tokenizer, + $this->streamReader + ); + case 'R': + if ( + $expectedType !== null && + $expectedType !== PdfIndirectObjectReference::class + ) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', + Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + + return PdfIndirectObjectReference::create((int) $token, (int) $token2); + } + + $this->tokenizer->pushStack($token3); + } + + $this->tokenizer->pushStack($token2); + } + + if ($expectedType !== null && $expectedType !== PdfNumeric::class) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', + Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + return PdfNumeric::create($token + 0); + } + + if ($token === 'true' || $token === 'false') { + $this->ensureExpectedType($token, $expectedType); + return PdfBoolean::create($token === 'true'); + } + + if ($token === 'null') { + $this->ensureExpectedType($token, $expectedType); + return new PdfNull(); + } + + if ($expectedType !== null && $expectedType !== PdfToken::class) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', + Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + + $v = new PdfToken(); + $v->value = $token; + + return $v; + } + } + + /** + * Ensures that the token will evaluate to an expected object type (or not). + * + * @param string $token + * @param string|null $expectedType + * @return bool + * @throws Type\PdfTypeException + */ + private function ensureExpectedType($token, $expectedType) + { + static $mapping = [ + '(' => PdfString::class, + '<' => PdfHexString::class, + '<<' => PdfDictionary::class, + '/' => PdfName::class, + '[' => PdfArray::class, + 'true' => PdfBoolean::class, + 'false' => PdfBoolean::class, + 'null' => PdfNull::class + ]; + + if ($expectedType === null || $mapping[$token] === $expectedType) { + return true; + } + + throw new Type\PdfTypeException('Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE); + } +} diff --git a/Fpdi/PdfParser/PdfParserException.php b/Fpdi/PdfParser/PdfParserException.php new file mode 100644 index 0000000..e9f907a --- /dev/null +++ b/Fpdi/PdfParser/PdfParserException.php @@ -0,0 +1,49 @@ +stream = $stream; + $this->closeStream = $closeStream; + $this->reset(); + } + + /** + * The destructor. + */ + public function __destruct() + { + $this->cleanUp(); + } + + /** + * Closes the file handle. + */ + public function cleanUp() + { + if ($this->closeStream && is_resource($this->stream)) { + \fclose($this->stream); + } + } + + /** + * Returns the byte length of the buffer. + * + * @param bool $atOffset + * @return int + */ + public function getBufferLength($atOffset = false) + { + if ($atOffset === false) { + return $this->bufferLength; + } + + return $this->bufferLength - $this->offset; + } + + /** + * Get the current position in the stream. + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * Returns the current buffer. + * + * @param bool $atOffset + * @return string + */ + public function getBuffer($atOffset = true) + { + if ($atOffset === false) { + return $this->buffer; + } + + $string = \substr($this->buffer, $this->offset); + + return (string) $string; + } + + /** + * Gets a byte at a specific position in the buffer. + * + * If the position is invalid the method will return false. + * + * If the $position parameter is set to null the value of $this->offset will be used. + * + * @param int|null $position + * @return string|bool + */ + public function getByte($position = null) + { + $position = (int) ($position !== null ? $position : $this->offset); + if ( + $position >= $this->bufferLength + && (!$this->increaseLength() || $position >= $this->bufferLength) + ) { + return false; + } + + return $this->buffer[$position]; + } + + /** + * Returns a byte at a specific position, and set the offset to the next byte position. + * + * If the position is invalid the method will return false. + * + * If the $position parameter is set to null the value of $this->offset will be used. + * + * @param int|null $position + * @return string|bool + */ + public function readByte($position = null) + { + if ($position !== null) { + $position = (int) $position; + // check if needed bytes are available in the current buffer + if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) { + $this->reset($position); + $offset = $this->offset; + } else { + $offset = $position - $this->position; + } + } else { + $offset = $this->offset; + } + + if ( + $offset >= $this->bufferLength + && ((!$this->increaseLength()) || $offset >= $this->bufferLength) + ) { + return false; + } + + $this->offset = $offset + 1; + return $this->buffer[$offset]; + } + + /** + * Read bytes from the current or a specific offset position and set the internal pointer to the next byte. + * + * If the position is invalid the method will return false. + * + * If the $position parameter is set to null the value of $this->offset will be used. + * + * @param int $length + * @param int|null $position + * @return string|false + */ + public function readBytes($length, $position = null) + { + $length = (int) $length; + if ($position !== null) { + // check if needed bytes are available in the current buffer + if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) { + $this->reset($position, $length); + $offset = $this->offset; + } else { + $offset = $position - $this->position; + } + } else { + $offset = $this->offset; + } + + if ( + ($offset + $length) > $this->bufferLength + && ((!$this->increaseLength($length)) || ($offset + $length) > $this->bufferLength) + ) { + return false; + } + + $bytes = \substr($this->buffer, $offset, $length); + $this->offset = $offset + $length; + + return $bytes; + } + + /** + * Read a line from the current position. + * + * @param int $length + * @return string|bool + */ + public function readLine($length = 1024) + { + if ($this->ensureContent() === false) { + return false; + } + + $line = ''; + while ($this->ensureContent()) { + $char = $this->readByte(); + + if ($char === "\n") { + break; + } + + if ($char === "\r") { + if ($this->getByte() === "\n") { + $this->addOffset(1); + } + break; + } + + $line .= $char; + + if (\strlen($line) >= $length) { + break; + } + } + + return $line; + } + + /** + * Set the offset position in the current buffer. + * + * @param int $offset + */ + public function setOffset($offset) + { + if ($offset > $this->bufferLength || $offset < 0) { + throw new \OutOfRangeException( + \sprintf('Offset (%s) out of range (length: %s)', $offset, $this->bufferLength) + ); + } + + $this->offset = (int) $offset; + } + + /** + * Returns the current offset in the current buffer. + * + * @return int + */ + public function getOffset() + { + return $this->offset; + } + + /** + * Add an offset to the current offset. + * + * @param int $offset + */ + public function addOffset($offset) + { + $this->setOffset($this->offset + $offset); + } + + /** + * Make sure that there is at least one character beyond the current offset in the buffer. + * + * @return bool + */ + public function ensureContent() + { + while ($this->offset >= $this->bufferLength) { + if (!$this->increaseLength()) { + return false; + } + } + return true; + } + + /** + * Returns the stream. + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * Gets the total available length. + * + * @return int + */ + public function getTotalLength() + { + if ($this->totalLength === null) { + $stat = \fstat($this->stream); + $this->totalLength = $stat['size']; + } + + return $this->totalLength; + } + + /** + * Resets the buffer to a position and re-read the buffer with the given length. + * + * If the $pos parameter is negative the start buffer position will be the $pos'th position from + * the end of the file. + * + * If the $pos parameter is negative and the absolute value is bigger then the totalLength of + * the file $pos will set to zero. + * + * @param int|null $pos Start position of the new buffer + * @param int $length Length of the new buffer. Mustn't be negative + */ + public function reset($pos = 0, $length = 200) + { + if ($pos === null) { + $pos = $this->position + $this->offset; + } elseif ($pos < 0) { + $pos = \max(0, $this->getTotalLength() + $pos); + } + + \fseek($this->stream, $pos); + + $this->position = $pos; + $this->buffer = $length > 0 ? \fread($this->stream, $length) : ''; + $this->bufferLength = \strlen($this->buffer); + $this->offset = 0; + + // If a stream wrapper is in use it is possible that + // length values > 8096 will be ignored, so use the + // increaseLength()-method to correct that behavior + if ($this->bufferLength < $length && $this->increaseLength($length - $this->bufferLength)) { + // increaseLength parameter is $minLength, so cut to have only the required bytes in the buffer + $this->buffer = \substr($this->buffer, 0, $length); + $this->bufferLength = \strlen($this->buffer); + } + } + + /** + * Ensures bytes in the buffer with a specific length and location in the file. + * + * @param int $pos + * @param int $length + * @see reset() + */ + public function ensure($pos, $length) + { + if ( + $pos >= $this->position + && $pos < ($this->position + $this->bufferLength) + && ($this->position + $this->bufferLength) >= ($pos + $length) + ) { + $this->offset = $pos - $this->position; + } else { + $this->reset($pos, $length); + } + } + + /** + * Forcefully read more data into the buffer. + * + * @param int $minLength + * @return bool Returns false if the stream reaches the end + */ + public function increaseLength($minLength = 100) + { + $length = \max($minLength, 100); + + if (\feof($this->stream) || $this->getTotalLength() === $this->position + $this->bufferLength) { + return false; + } + + $newLength = $this->bufferLength + $length; + do { + $this->buffer .= \fread($this->stream, $newLength - $this->bufferLength); + $this->bufferLength = \strlen($this->buffer); + } while (($this->bufferLength !== $newLength) && !\feof($this->stream)); + + return true; + } +} diff --git a/Fpdi/PdfParser/Tokenizer.php b/Fpdi/PdfParser/Tokenizer.php new file mode 100644 index 0000000..36eb062 --- /dev/null +++ b/Fpdi/PdfParser/Tokenizer.php @@ -0,0 +1,154 @@ +streamReader = $streamReader; + } + + /** + * Get the stream reader instance. + * + * @return StreamReader + */ + public function getStreamReader() + { + return $this->streamReader; + } + + /** + * Clear the token stack. + */ + public function clearStack() + { + $this->stack = []; + } + + /** + * Push a token onto the stack. + * + * @param string $token + */ + public function pushStack($token) + { + $this->stack[] = $token; + } + + /** + * Get next token. + * + * @return bool|string + */ + public function getNextToken() + { + $token = \array_pop($this->stack); + if ($token !== null) { + return $token; + } + + if (($byte = $this->streamReader->readByte()) === false) { + return false; + } + + if (\in_array($byte, ["\x20", "\x0A", "\x0D", "\x0C", "\x09", "\x00"], true)) { + if ($this->leapWhiteSpaces() === false) { + return false; + } + $byte = $this->streamReader->readByte(); + } + + switch ($byte) { + case '/': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '<': + case '>': + return $byte; + case '%': + $this->streamReader->readLine(); + return $this->getNextToken(); + } + + /* This way is faster than checking single bytes. + */ + $bufferOffset = $this->streamReader->getOffset(); + do { + $lastBuffer = $this->streamReader->getBuffer(false); + $pos = \strcspn( + $lastBuffer, + "\x00\x09\x0A\x0C\x0D\x20()<>[]{}/%", + $bufferOffset + ); + } while ( + // Break the loop if a delimiter or white space char is matched + // in the current buffer or increase the buffers length + $lastBuffer !== false && + ( + $bufferOffset + $pos === \strlen($lastBuffer) && + $this->streamReader->increaseLength() + ) + ); + + $result = \substr($lastBuffer, $bufferOffset - 1, $pos + 1); + $this->streamReader->setOffset($bufferOffset + $pos); + + return $result; + } + + /** + * Leap white spaces. + * + * @return boolean + */ + public function leapWhiteSpaces() + { + do { + if (!$this->streamReader->ensureContent()) { + return false; + } + + $buffer = $this->streamReader->getBuffer(false); + $matches = \strspn($buffer, "\x20\x0A\x0C\x0D\x09\x00", $this->streamReader->getOffset()); + if ($matches > 0) { + $this->streamReader->addOffset($matches); + } + } while ($this->streamReader->getOffset() >= $this->streamReader->getBufferLength()); + + return true; + } +} diff --git a/Fpdi/PdfParser/Type/PdfArray.php b/Fpdi/PdfParser/Type/PdfArray.php new file mode 100644 index 0000000..e1ab08a --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfArray.php @@ -0,0 +1,85 @@ +getNextToken()) !== ']') { + if ($token === false || ($value = $parser->readValue($token)) === false) { + return false; + } + + $result[] = $value; + } + + $v = new self(); + $v->value = $result; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param PdfType[] $values + * @return self + */ + public static function create(array $values = []) + { + $v = new self(); + $v->value = $values; + + return $v; + } + + /** + * Ensures that the passed array is a PdfArray instance with a (optional) specific size. + * + * @param mixed $array + * @param null|int $size + * @return self + * @throws PdfTypeException + */ + public static function ensure($array, $size = null) + { + $result = PdfType::ensureType(self::class, $array, 'Array value expected.'); + + if ($size !== null && \count($array->value) !== $size) { + throw new PdfTypeException( + \sprintf('Array with %s entries expected.', $size), + PdfTypeException::INVALID_DATA_SIZE + ); + } + + return $result; + } +} diff --git a/Fpdi/PdfParser/Type/PdfBoolean.php b/Fpdi/PdfParser/Type/PdfBoolean.php new file mode 100644 index 0000000..68502d7 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfBoolean.php @@ -0,0 +1,42 @@ +value = (bool) $value; + return $v; + } + + /** + * Ensures that the passed value is a PdfBoolean instance. + * + * @param mixed $value + * @return self + * @throws PdfTypeException + */ + public static function ensure($value) + { + return PdfType::ensureType(self::class, $value, 'Boolean value expected.'); + } +} diff --git a/Fpdi/PdfParser/Type/PdfDictionary.php b/Fpdi/PdfParser/Type/PdfDictionary.php new file mode 100644 index 0000000..1b5d164 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfDictionary.php @@ -0,0 +1,134 @@ +getNextToken(); + if ($token === '>' && $streamReader->getByte() === '>') { + $streamReader->addOffset(1); + break; + } + + $key = $parser->readValue($token); + if ($key === false) { + return false; + } + + // ensure the first value to be a Name object + if (!($key instanceof PdfName)) { + $lastToken = null; + // ignore all other entries and search for the closing brackets + while (($token = $tokenizer->getNextToken()) !== '>' && $token !== false && $lastToken !== '>') { + $lastToken = $token; + } + + if ($token === false) { + return false; + } + + break; + } + + + $value = $parser->readValue(); + if ($value === false) { + return false; + } + + if ($value instanceof PdfNull) { + continue; + } + + // catch missing value + if ($value instanceof PdfToken && $value->value === '>' && $streamReader->getByte() === '>') { + $streamReader->addOffset(1); + break; + } + + $entries[$key->value] = $value; + } + + $v = new self(); + $v->value = $entries; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param PdfType[] $entries The keys are the name entries of the dictionary. + * @return self + */ + public static function create(array $entries = []) + { + $v = new self(); + $v->value = $entries; + + return $v; + } + + /** + * Get a value by its key from a dictionary or a default value. + * + * @param mixed $dictionary + * @param string $key + * @param PdfType|null $default + * @return PdfNull|PdfType + * @throws PdfTypeException + */ + public static function get($dictionary, $key, PdfType $default = null) + { + $dictionary = self::ensure($dictionary); + + if (isset($dictionary->value[$key])) { + return $dictionary->value[$key]; + } + + return $default === null + ? new PdfNull() + : $default; + } + + /** + * Ensures that the passed value is a PdfDictionary instance. + * + * @param mixed $dictionary + * @return self + * @throws PdfTypeException + */ + public static function ensure($dictionary) + { + return PdfType::ensureType(self::class, $dictionary, 'Dictionary value expected.'); + } +} diff --git a/Fpdi/PdfParser/Type/PdfHexString.php b/Fpdi/PdfParser/Type/PdfHexString.php new file mode 100644 index 0000000..99fdeb0 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfHexString.php @@ -0,0 +1,77 @@ +getOffset(); + + while (true) { + $buffer = $streamReader->getBuffer(false); + $pos = \strpos($buffer, '>', $bufferOffset); + if ($pos === false) { + if (!$streamReader->increaseLength()) { + return false; + } + continue; + } + + break; + } + + $result = \substr($buffer, $bufferOffset, $pos - $bufferOffset); + $streamReader->setOffset($pos + 1); + + $v = new self(); + $v->value = $result; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param string $string The hex encoded string. + * @return self + */ + public static function create($string) + { + $v = new self(); + $v->value = $string; + + return $v; + } + + /** + * Ensures that the passed value is a PdfHexString instance. + * + * @param mixed $hexString + * @return self + * @throws PdfTypeException + */ + public static function ensure($hexString) + { + return PdfType::ensureType(self::class, $hexString, 'Hex string value expected.'); + } +} diff --git a/Fpdi/PdfParser/Type/PdfIndirectObject.php b/Fpdi/PdfParser/Type/PdfIndirectObject.php new file mode 100644 index 0000000..87b2ea0 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfIndirectObject.php @@ -0,0 +1,103 @@ +readValue(); + if ($value === false) { + return false; + } + + $nextToken = $tokenizer->getNextToken(); + if ($nextToken === 'stream') { + $value = PdfStream::parse($value, $reader, $parser); + } elseif ($nextToken !== false) { + $tokenizer->pushStack($nextToken); + } + + $v = new self(); + $v->objectNumber = (int) $objectNumberToken; + $v->generationNumber = (int) $objectGenerationNumberToken; + $v->value = $value; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param int $objectNumber + * @param int $generationNumber + * @param PdfType $value + * @return self + */ + public static function create($objectNumber, $generationNumber, PdfType $value) + { + $v = new self(); + $v->objectNumber = (int) $objectNumber; + $v->generationNumber = (int) $generationNumber; + $v->value = $value; + + return $v; + } + + /** + * Ensures that the passed value is a PdfIndirectObject instance. + * + * @param mixed $indirectObject + * @return self + * @throws PdfTypeException + */ + public static function ensure($indirectObject) + { + return PdfType::ensureType(self::class, $indirectObject, 'Indirect object expected.'); + } + + /** + * The object number. + * + * @var int + */ + public $objectNumber; + + /** + * The generation number. + * + * @var int + */ + public $generationNumber; +} diff --git a/Fpdi/PdfParser/Type/PdfIndirectObjectReference.php b/Fpdi/PdfParser/Type/PdfIndirectObjectReference.php new file mode 100644 index 0000000..a3e24ab --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfIndirectObjectReference.php @@ -0,0 +1,52 @@ +value = (int) $objectNumber; + $v->generationNumber = (int) $generationNumber; + + return $v; + } + + /** + * Ensures that the passed value is a PdfIndirectObject instance. + * + * @param mixed $value + * @return self + * @throws PdfTypeException + */ + public static function ensure($value) + { + return PdfType::ensureType(self::class, $value, 'Indirect reference value expected.'); + } + + /** + * The generation number. + * + * @var int + */ + public $generationNumber; +} diff --git a/Fpdi/PdfParser/Type/PdfName.php b/Fpdi/PdfParser/Type/PdfName.php new file mode 100644 index 0000000..a1ba066 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfName.php @@ -0,0 +1,82 @@ +getByte(), "\x00\x09\x0A\x0C\x0D\x20()<>[]{}/%") === 0) { + $v->value = (string) $tokenizer->getNextToken(); + return $v; + } + + $v->value = ''; + return $v; + } + + /** + * Unescapes a name string. + * + * @param string $value + * @return string + */ + public static function unescape($value) + { + if (strpos($value, '#') === false) { + return $value; + } + + return preg_replace_callback('/#([a-fA-F\d]{2})/', function ($matches) { + return chr(hexdec($matches[1])); + }, $value); + } + + /** + * Helper method to create an instance. + * + * @param string $string + * @return self + */ + public static function create($string) + { + $v = new self(); + $v->value = $string; + + return $v; + } + + /** + * Ensures that the passed value is a PdfName instance. + * + * @param mixed $name + * @return self + * @throws PdfTypeException + */ + public static function ensure($name) + { + return PdfType::ensureType(self::class, $name, 'Name value expected.'); + } +} diff --git a/Fpdi/PdfParser/Type/PdfNull.php b/Fpdi/PdfParser/Type/PdfNull.php new file mode 100644 index 0000000..ebcc234 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfNull.php @@ -0,0 +1,19 @@ +value = $value + 0; + + return $v; + } + + /** + * Ensures that the passed value is a PdfNumeric instance. + * + * @param mixed $value + * @return self + * @throws PdfTypeException + */ + public static function ensure($value) + { + return PdfType::ensureType(self::class, $value, 'Numeric value expected.'); + } +} diff --git a/Fpdi/PdfParser/Type/PdfStream.php b/Fpdi/PdfParser/Type/PdfStream.php new file mode 100644 index 0000000..700e50a --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfStream.php @@ -0,0 +1,326 @@ +value = $dictionary; + $v->reader = $reader; + $v->parser = $parser; + + $offset = $reader->getOffset(); + + // Find the first "newline" + while (($firstByte = $reader->getByte($offset)) !== false) { + if ($firstByte !== "\n" && $firstByte !== "\r") { + $offset++; + } else { + break; + } + } + + if ($firstByte === false) { + throw new PdfTypeException( + 'Unable to parse stream data. No newline after the stream keyword found.', + PdfTypeException::NO_NEWLINE_AFTER_STREAM_KEYWORD + ); + } + + $sndByte = $reader->getByte($offset + 1); + if ($firstByte === "\n" || $firstByte === "\r") { + $offset++; + } + + if ($sndByte === "\n" && $firstByte !== "\n") { + $offset++; + } + + $reader->setOffset($offset); + // let's only save the byte-offset and read the stream only when needed + $v->stream = $reader->getPosition() + $reader->getOffset(); + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param PdfDictionary $dictionary + * @param string $stream + * @return self + */ + public static function create(PdfDictionary $dictionary, $stream) + { + $v = new self(); + $v->value = $dictionary; + $v->stream = (string) $stream; + + return $v; + } + + /** + * Ensures that the passed value is a PdfStream instance. + * + * @param mixed $stream + * @return self + * @throws PdfTypeException + */ + public static function ensure($stream) + { + return PdfType::ensureType(self::class, $stream, 'Stream value expected.'); + } + + /** + * The stream or its byte-offset position. + * + * @var int|string + */ + protected $stream; + + /** + * The stream reader instance. + * + * @var StreamReader|null + */ + protected $reader; + + /** + * The PDF parser instance. + * + * @var PdfParser + */ + protected $parser; + + /** + * Get the stream data. + * + * @param bool $cache Whether cache the stream data or not. + * @return bool|string + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getStream($cache = false) + { + if (\is_int($this->stream)) { + $length = PdfDictionary::get($this->value, 'Length'); + if ($this->parser !== null) { + $length = PdfType::resolve($length, $this->parser); + } + + if (!($length instanceof PdfNumeric) || $length->value === 0) { + $this->reader->reset($this->stream, 100000); + $buffer = $this->extractStream(); + } else { + $this->reader->reset($this->stream, $length->value); + $buffer = $this->reader->getBuffer(false); + if ($this->parser !== null) { + $this->reader->reset($this->stream + strlen($buffer)); + $this->parser->getTokenizer()->clearStack(); + $token = $this->parser->readValue(); + if ($token === false || !($token instanceof PdfToken) || $token->value !== 'endstream') { + $this->reader->reset($this->stream, 100000); + $buffer = $this->extractStream(); + $this->reader->reset($this->stream + strlen($buffer)); + } + } + } + + if ($cache === false) { + return $buffer; + } + + $this->stream = $buffer; + $this->reader = null; + } + + return $this->stream; + } + + /** + * Extract the stream "manually". + * + * @return string + * @throws PdfTypeException + */ + protected function extractStream() + { + while (true) { + $buffer = $this->reader->getBuffer(false); + $length = \strpos($buffer, 'endstream'); + if ($length === false) { + if (!$this->reader->increaseLength(100000)) { + throw new PdfTypeException('Cannot extract stream.'); + } + continue; + } + break; + } + + $buffer = \substr($buffer, 0, $length); + $lastByte = \substr($buffer, -1); + + /* Check for EOL marker = + * CARRIAGE RETURN (\r) and a LINE FEED (\n) or just a LINE FEED (\n}, + * and not by a CARRIAGE RETURN (\r) alone + */ + if ($lastByte === "\n") { + $buffer = \substr($buffer, 0, -1); + + $lastByte = \substr($buffer, -1); + if ($lastByte === "\r") { + $buffer = \substr($buffer, 0, -1); + } + } + + // There are streams in the wild, which have only white signs in them but need to be parsed manually due + // to a problem encountered before (e.g. Length === 0). We should set them to empty streams to avoid problems + // in further processing (e.g. applying of filters). + if (trim($buffer) === '') { + $buffer = ''; + } + + return $buffer; + } + + /** + * Get the unfiltered stream data. + * + * @return string + * @throws FilterException + * @throws PdfParserException + */ + public function getUnfilteredStream() + { + $stream = $this->getStream(); + $filters = PdfDictionary::get($this->value, 'Filter'); + if ($filters instanceof PdfNull) { + return $stream; + } + + if ($filters instanceof PdfArray) { + $filters = $filters->value; + } else { + $filters = [$filters]; + } + + $decodeParams = PdfDictionary::get($this->value, 'DecodeParms'); + if ($decodeParams instanceof PdfArray) { + $decodeParams = $decodeParams->value; + } else { + $decodeParams = [$decodeParams]; + } + + foreach ($filters as $key => $filter) { + if (!($filter instanceof PdfName)) { + continue; + } + + $decodeParam = null; + if (isset($decodeParams[$key])) { + $decodeParam = ($decodeParams[$key] instanceof PdfDictionary ? $decodeParams[$key] : null); + } + + switch ($filter->value) { + case 'FlateDecode': + case 'Fl': + case 'LZWDecode': + case 'LZW': + if (\strpos($filter->value, 'LZW') === 0) { + $filterObject = new Lzw(); + } else { + $filterObject = new Flate(); + } + + $stream = $filterObject->decode($stream); + + if ($decodeParam instanceof PdfDictionary) { + $predictor = PdfDictionary::get($decodeParam, 'Predictor', PdfNumeric::create(1)); + if ($predictor->value !== 1) { + if (!\class_exists(Predictor::class)) { + throw new PdfParserException( + 'This PDF document makes use of features which are only implemented in the ' . + 'commercial "FPDI PDF-Parser" add-on (see https://www.setasign.com/fpdi-pdf-' . + 'parser).', + PdfParserException::IMPLEMENTED_IN_FPDI_PDF_PARSER + ); + } + + $colors = PdfDictionary::get($decodeParam, 'Colors', PdfNumeric::create(1)); + $bitsPerComponent = PdfDictionary::get( + $decodeParam, + 'BitsPerComponent', + PdfNumeric::create(8) + ); + + $columns = PdfDictionary::get($decodeParam, 'Columns', PdfNumeric::create(1)); + + $filterObject = new Predictor( + $predictor->value, + $colors->value, + $bitsPerComponent->value, + $columns->value + ); + + $stream = $filterObject->decode($stream); + } + } + + break; + case 'ASCII85Decode': + case 'A85': + $filterObject = new Ascii85(); + $stream = $filterObject->decode($stream); + break; + + case 'ASCIIHexDecode': + case 'AHx': + $filterObject = new AsciiHex(); + $stream = $filterObject->decode($stream); + break; + + default: + throw new FilterException( + \sprintf('Unsupported filter "%s".', $filter->value), + FilterException::UNSUPPORTED_FILTER + ); + } + } + + return $stream; + } +} diff --git a/Fpdi/PdfParser/Type/PdfString.php b/Fpdi/PdfParser/Type/PdfString.php new file mode 100644 index 0000000..45da6d8 --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfString.php @@ -0,0 +1,172 @@ +getOffset(); + $openBrackets = 1; + do { + $buffer = $streamReader->getBuffer(false); + for ($length = \strlen($buffer); $openBrackets !== 0 && $pos < $length; $pos++) { + switch ($buffer[$pos]) { + case '(': + $openBrackets++; + break; + case ')': + $openBrackets--; + break; + case '\\': + $pos++; + } + } + } while ($openBrackets !== 0 && $streamReader->increaseLength()); + + $result = \substr($buffer, $startPos, $openBrackets + $pos - $startPos - 1); + $streamReader->setOffset($pos); + + $v = new self(); + $v->value = $result; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param string $value The string needs to be escaped accordingly. + * @return self + */ + public static function create($value) + { + $v = new self(); + $v->value = $value; + + return $v; + } + + /** + * Ensures that the passed value is a PdfString instance. + * + * @param mixed $string + * @return self + * @throws PdfTypeException + */ + public static function ensure($string) + { + return PdfType::ensureType(self::class, $string, 'String value expected.'); + } + + /** + * Unescapes escaped sequences in a PDF string according to the PDF specification. + * + * @param string $s + * @return string + */ + public static function unescape($s) + { + $out = ''; + /** @noinspection ForeachInvariantsInspection */ + for ($count = 0, $n = \strlen($s); $count < $n; $count++) { + if ($s[$count] !== '\\') { + $out .= $s[$count]; + } else { + // A backslash at the end of the string - ignore it + if ($count === ($n - 1)) { + break; + } + + switch ($s[++$count]) { + case ')': + case '(': + case '\\': + $out .= $s[$count]; + break; + + case 'f': + $out .= "\x0C"; + break; + + case 'b': + $out .= "\x08"; + break; + + case 't': + $out .= "\x09"; + break; + + case 'r': + $out .= "\x0D"; + break; + + case 'n': + $out .= "\x0A"; + break; + + case "\r": + if ($count !== $n - 1 && $s[$count + 1] === "\n") { + $count++; + } + break; + + case "\n": + break; + + default: + $actualChar = \ord($s[$count]); + // ascii 48 = number 0 + // ascii 57 = number 9 + if ($actualChar >= 48 && $actualChar <= 57) { + $oct = '' . $s[$count]; + + /** @noinspection NotOptimalIfConditionsInspection */ + if ( + $count + 1 < $n + && \ord($s[$count + 1]) >= 48 + && \ord($s[$count + 1]) <= 57 + ) { + $count++; + $oct .= $s[$count]; + + /** @noinspection NotOptimalIfConditionsInspection */ + if ( + $count + 1 < $n + && \ord($s[$count + 1]) >= 48 + && \ord($s[$count + 1]) <= 57 + ) { + $oct .= $s[++$count]; + } + } + + $out .= \chr(\octdec($oct)); + } else { + // If the character is not one of those defined, the backslash is ignored + $out .= $s[$count]; + } + } + } + } + return $out; + } +} diff --git a/Fpdi/PdfParser/Type/PdfToken.php b/Fpdi/PdfParser/Type/PdfToken.php new file mode 100644 index 0000000..4caa91a --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfToken.php @@ -0,0 +1,43 @@ +value = $token; + + return $v; + } + + /** + * Ensures that the passed value is a PdfToken instance. + * + * @param mixed $token + * @return self + * @throws PdfTypeException + */ + public static function ensure($token) + { + return PdfType::ensureType(self::class, $token, 'Token value expected.'); + } +} diff --git a/Fpdi/PdfParser/Type/PdfType.php b/Fpdi/PdfParser/Type/PdfType.php new file mode 100644 index 0000000..336e1bb --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfType.php @@ -0,0 +1,78 @@ +value, $parser, $stopAtIndirectObject); + } + + if ($value instanceof PdfIndirectObjectReference) { + return self::resolve($parser->getIndirectObject($value->value), $parser, $stopAtIndirectObject); + } + + return $value; + } + + /** + * Ensure that a value is an instance of a specific PDF type. + * + * @param string $type + * @param PdfType $value + * @param string $errorMessage + * @return mixed + * @throws PdfTypeException + */ + protected static function ensureType($type, $value, $errorMessage) + { + if (!($value instanceof $type)) { + throw new PdfTypeException( + $errorMessage, + PdfTypeException::INVALID_DATA_TYPE + ); + } + + return $value; + } + + /** + * The value of the PDF type. + * + * @var mixed + */ + public $value; +} diff --git a/Fpdi/PdfParser/Type/PdfTypeException.php b/Fpdi/PdfParser/Type/PdfTypeException.php new file mode 100644 index 0000000..b4c718b --- /dev/null +++ b/Fpdi/PdfParser/Type/PdfTypeException.php @@ -0,0 +1,24 @@ +value; + $ax = PdfNumeric::ensure(PdfType::resolve($array[0], $parser))->value; + $ay = PdfNumeric::ensure(PdfType::resolve($array[1], $parser))->value; + $bx = PdfNumeric::ensure(PdfType::resolve($array[2], $parser))->value; + $by = PdfNumeric::ensure(PdfType::resolve($array[3], $parser))->value; + + return new self($ax, $ay, $bx, $by); + } + + /** + * Rectangle constructor. + * + * @param float|int $ax + * @param float|int $ay + * @param float|int $bx + * @param float|int $by + */ + public function __construct($ax, $ay, $bx, $by) + { + $this->llx = \min($ax, $bx); + $this->lly = \min($ay, $by); + $this->urx = \max($ax, $bx); + $this->ury = \max($ay, $by); + } + + /** + * Get the width of the rectangle. + * + * @return float|int + */ + public function getWidth() + { + return $this->urx - $this->llx; + } + + /** + * Get the height of the rectangle. + * + * @return float|int + */ + public function getHeight() + { + return $this->ury - $this->lly; + } + + /** + * Get the lower left abscissa. + * + * @return float|int + */ + public function getLlx() + { + return $this->llx; + } + + /** + * Get the lower left ordinate. + * + * @return float|int + */ + public function getLly() + { + return $this->lly; + } + + /** + * Get the upper right abscissa. + * + * @return float|int + */ + public function getUrx() + { + return $this->urx; + } + + /** + * Get the upper right ordinate. + * + * @return float|int + */ + public function getUry() + { + return $this->ury; + } + + /** + * Get the rectangle as an array. + * + * @return array + */ + public function toArray() + { + return [ + $this->llx, + $this->lly, + $this->urx, + $this->ury + ]; + } + + /** + * Get the rectangle as a PdfArray. + * + * @return PdfArray + */ + public function toPdfArray() + { + $array = new PdfArray(); + $array->value[] = PdfNumeric::create($this->llx); + $array->value[] = PdfNumeric::create($this->lly); + $array->value[] = PdfNumeric::create($this->urx); + $array->value[] = PdfNumeric::create($this->ury); + + return $array; + } +} diff --git a/Fpdi/PdfReader/Page.php b/Fpdi/PdfReader/Page.php new file mode 100644 index 0000000..d45980d --- /dev/null +++ b/Fpdi/PdfReader/Page.php @@ -0,0 +1,271 @@ +pageObject = $page; + $this->parser = $parser; + } + + /** + * Get the indirect object of this page. + * + * @return PdfIndirectObject + */ + public function getPageObject() + { + return $this->pageObject; + } + + /** + * Get the dictionary of this page. + * + * @return PdfDictionary + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getPageDictionary() + { + if (null === $this->pageDictionary) { + $this->pageDictionary = PdfDictionary::ensure(PdfType::resolve($this->getPageObject(), $this->parser)); + } + + return $this->pageDictionary; + } + + /** + * Get a page attribute. + * + * @param string $name + * @param bool $inherited + * @return PdfType|null + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getAttribute($name, $inherited = true) + { + $dict = $this->getPageDictionary(); + + if (isset($dict->value[$name])) { + return $dict->value[$name]; + } + + $inheritedKeys = ['Resources', 'MediaBox', 'CropBox', 'Rotate']; + if ($inherited && \in_array($name, $inheritedKeys, true)) { + if ($this->inheritedAttributes === null) { + $this->inheritedAttributes = []; + $inheritedKeys = \array_filter($inheritedKeys, function ($key) use ($dict) { + return !isset($dict->value[$key]); + }); + + if (\count($inheritedKeys) > 0) { + $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser); + while ($parentDict instanceof PdfDictionary) { + foreach ($inheritedKeys as $index => $key) { + if (isset($parentDict->value[$key])) { + $this->inheritedAttributes[$key] = $parentDict->value[$key]; + unset($inheritedKeys[$index]); + } + } + + /** @noinspection NotOptimalIfConditionsInspection */ + if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) { + $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser); + } else { + break; + } + } + } + } + + if (isset($this->inheritedAttributes[$name])) { + return $this->inheritedAttributes[$name]; + } + } + + return null; + } + + /** + * Get the rotation value. + * + * @return int + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getRotation() + { + $rotation = $this->getAttribute('Rotate'); + if (null === $rotation) { + return 0; + } + + $rotation = PdfNumeric::ensure(PdfType::resolve($rotation, $this->parser))->value % 360; + + if ($rotation < 0) { + $rotation += 360; + } + + return $rotation; + } + + /** + * Get a boundary of this page. + * + * @param string $box + * @param bool $fallback + * @return bool|Rectangle + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + * @see PageBoundaries + */ + public function getBoundary($box = PageBoundaries::CROP_BOX, $fallback = true) + { + $value = $this->getAttribute($box); + + if ($value !== null) { + return Rectangle::byPdfArray($value, $this->parser); + } + + if ($fallback === false) { + return false; + } + + switch ($box) { + case PageBoundaries::BLEED_BOX: + case PageBoundaries::TRIM_BOX: + case PageBoundaries::ART_BOX: + return $this->getBoundary(PageBoundaries::CROP_BOX, true); + case PageBoundaries::CROP_BOX: + return $this->getBoundary(PageBoundaries::MEDIA_BOX, true); + } + + return false; + } + + /** + * Get the width and height of this page. + * + * @param string $box + * @param bool $fallback + * @return array|bool + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getWidthAndHeight($box = PageBoundaries::CROP_BOX, $fallback = true) + { + $boundary = $this->getBoundary($box, $fallback); + if ($boundary === false) { + return false; + } + + $rotation = $this->getRotation(); + $interchange = ($rotation / 90) % 2; + + return [ + $interchange ? $boundary->getHeight() : $boundary->getWidth(), + $interchange ? $boundary->getWidth() : $boundary->getHeight() + ]; + } + + /** + * Get the raw content stream. + * + * @return string + * @throws PdfReaderException + * @throws PdfTypeException + * @throws FilterException + * @throws PdfParserException + */ + public function getContentStream() + { + $dict = $this->getPageDictionary(); + $contents = PdfType::resolve(PdfDictionary::get($dict, 'Contents'), $this->parser); + if ($contents instanceof PdfNull) { + return ''; + } + + if ($contents instanceof PdfArray) { + $result = []; + foreach ($contents->value as $content) { + $content = PdfType::resolve($content, $this->parser); + if (!($content instanceof PdfStream)) { + continue; + } + $result[] = $content->getUnfilteredStream(); + } + + return \implode("\n", $result); + } + + if ($contents instanceof PdfStream) { + return $contents->getUnfilteredStream(); + } + + throw new PdfReaderException( + 'Array or stream expected.', + PdfReaderException::UNEXPECTED_DATA_TYPE + ); + } +} diff --git a/Fpdi/PdfReader/PageBoundaries.php b/Fpdi/PdfReader/PageBoundaries.php new file mode 100644 index 0000000..a6290c5 --- /dev/null +++ b/Fpdi/PdfReader/PageBoundaries.php @@ -0,0 +1,94 @@ +parser = $parser; + } + + /** + * PdfReader destructor. + */ + public function __destruct() + { + if ($this->parser !== null) { + $this->parser->cleanUp(); + } + } + + /** + * Get the pdf parser instance. + * + * @return PdfParser + */ + public function getParser() + { + return $this->parser; + } + + /** + * Get the PDF version. + * + * @return string + * @throws PdfParserException + */ + public function getPdfVersion() + { + return \implode('.', $this->parser->getPdfVersion()); + } + + /** + * Get the page count. + * + * @return int + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getPageCount() + { + if ($this->pageCount === null) { + $catalog = $this->parser->getCatalog(); + + $pages = PdfType::resolve(PdfDictionary::get($catalog, 'Pages'), $this->parser); + $count = PdfType::resolve(PdfDictionary::get($pages, 'Count'), $this->parser); + + $this->pageCount = PdfNumeric::ensure($count)->value; + } + + return $this->pageCount; + } + + /** + * Get a page instance. + * + * @param int $pageNumber + * @return Page + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + * @throws \InvalidArgumentException + */ + public function getPage($pageNumber) + { + if (!\is_numeric($pageNumber)) { + throw new \InvalidArgumentException( + 'Page number needs to be a number.' + ); + } + + if ($pageNumber < 1 || $pageNumber > $this->getPageCount()) { + throw new \InvalidArgumentException( + \sprintf( + 'Page number "%s" out of available page range (1 - %s)', + $pageNumber, + $this->getPageCount() + ) + ); + } + + $this->readPages(); + + $page = $this->pages[$pageNumber - 1]; + + if ($page instanceof PdfIndirectObjectReference) { + $readPages = function ($kids) use (&$readPages) { + $kids = PdfArray::ensure($kids); + + /** @noinspection LoopWhichDoesNotLoopInspection */ + foreach ($kids->value as $reference) { + $reference = PdfIndirectObjectReference::ensure($reference); + $object = $this->parser->getIndirectObject($reference->value); + $type = PdfDictionary::get($object->value, 'Type'); + + if ($type->value === 'Pages') { + return $readPages(PdfDictionary::get($object->value, 'Kids')); + } + + return $object; + } + + throw new PdfReaderException( + 'Kids array cannot be empty.', + PdfReaderException::KIDS_EMPTY + ); + }; + + $page = $this->parser->getIndirectObject($page->value); + $dict = PdfType::resolve($page, $this->parser); + $type = PdfDictionary::get($dict, 'Type'); + + if ($type->value === 'Pages') { + $kids = PdfType::resolve(PdfDictionary::get($dict, 'Kids'), $this->parser); + try { + $page = $this->pages[$pageNumber - 1] = $readPages($kids); + } catch (PdfReaderException $e) { + if ($e->getCode() !== PdfReaderException::KIDS_EMPTY) { + throw $e; + } + + // let's reset the pages array and read all page objects + $this->pages = []; + $this->readPages(true); + // @phpstan-ignore-next-line + $page = $this->pages[$pageNumber - 1]; + } + } else { + $this->pages[$pageNumber - 1] = $page; + } + } + + return new Page($page, $this->parser); + } + + /** + * Walk the page tree and resolve all indirect objects of all pages. + * + * @param bool $readAll + * @throws CrossReferenceException + * @throws PdfParserException + * @throws PdfTypeException + */ + protected function readPages($readAll = false) + { + if (\count($this->pages) > 0) { + return; + } + + $readPages = function ($kids, $count) use (&$readPages, $readAll) { + $kids = PdfArray::ensure($kids); + $isLeaf = ($count->value === \count($kids->value)); + + foreach ($kids->value as $reference) { + $reference = PdfIndirectObjectReference::ensure($reference); + + if (!$readAll && $isLeaf) { + $this->pages[] = $reference; + continue; + } + + $object = $this->parser->getIndirectObject($reference->value); + $type = PdfDictionary::get($object->value, 'Type'); + + if ($type->value === 'Pages') { + $readPages(PdfDictionary::get($object->value, 'Kids'), PdfDictionary::get($object->value, 'Count')); + } else { + $this->pages[] = $object; + } + } + }; + + $catalog = $this->parser->getCatalog(); + $pages = PdfType::resolve(PdfDictionary::get($catalog, 'Pages'), $this->parser); + $count = PdfType::resolve(PdfDictionary::get($pages, 'Count'), $this->parser); + $kids = PdfType::resolve(PdfDictionary::get($pages, 'Kids'), $this->parser); + $readPages($kids, $count); + } +} diff --git a/Fpdi/PdfReader/PdfReaderException.php b/Fpdi/PdfReader/PdfReaderException.php new file mode 100644 index 0000000..30d9b2b --- /dev/null +++ b/Fpdi/PdfReader/PdfReaderException.php @@ -0,0 +1,34 @@ +array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/courierb.php b/Fpdi/font/courierb.php new file mode 100644 index 0000000..97ecd70 --- /dev/null +++ b/Fpdi/font/courierb.php @@ -0,0 +1,10 @@ +array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/courierbi.php b/Fpdi/font/courierbi.php new file mode 100644 index 0000000..c4bfff8 --- /dev/null +++ b/Fpdi/font/courierbi.php @@ -0,0 +1,10 @@ +array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/courieri.php b/Fpdi/font/courieri.php new file mode 100644 index 0000000..015a15a --- /dev/null +++ b/Fpdi/font/courieri.php @@ -0,0 +1,10 @@ +array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/helvetica.php b/Fpdi/font/helvetica.php new file mode 100644 index 0000000..927759b --- /dev/null +++ b/Fpdi/font/helvetica.php @@ -0,0 +1,21 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>278,'"'=>355,'#'=>556,'$'=>556,'%'=>889,'&'=>667,'\''=>191,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>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'=>556,'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'=>667,'Z'=>611,'['=>278,'\\'=>278,']'=>278,'^'=>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,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>222,chr(131)=>556, + chr(132)=>333,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>222,chr(146)=>222,chr(147)=>333,chr(148)=>333,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>500,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>260,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>556,chr(182)=>537,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>500,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>556,chr(241)=>556, + chr(242)=>556,chr(243)=>556,chr(244)=>556,chr(245)=>556,chr(246)=>556,chr(247)=>584,chr(248)=>611,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/helveticab.php b/Fpdi/font/helveticab.php new file mode 100644 index 0000000..bcd7367 --- /dev/null +++ b/Fpdi/font/helveticab.php @@ -0,0 +1,21 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>333,'"'=>474,'#'=>556,'$'=>556,'%'=>889,'&'=>722,'\''=>238,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>333,';'=>333,'<'=>584,'='=>584,'>'=>584,'?'=>611,'@'=>975,'A'=>722, + 'B'=>722,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>556,'K'=>722,'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'=>667,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>584,'_'=>556,'`'=>333,'a'=>556,'b'=>611,'c'=>556,'d'=>611,'e'=>556,'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'=>500,'{'=>389,'|'=>280,'}'=>389,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>278,chr(131)=>556, + chr(132)=>500,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>278,chr(146)=>278,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>556,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>280,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>611,chr(182)=>556,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>556,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>611,chr(241)=>611, + chr(242)=>611,chr(243)=>611,chr(244)=>611,chr(245)=>611,chr(246)=>611,chr(247)=>584,chr(248)=>611,chr(249)=>611,chr(250)=>611,chr(251)=>611,chr(252)=>611,chr(253)=>556,chr(254)=>611,chr(255)=>556); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/helveticabi.php b/Fpdi/font/helveticabi.php new file mode 100644 index 0000000..0243cde --- /dev/null +++ b/Fpdi/font/helveticabi.php @@ -0,0 +1,21 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>333,'"'=>474,'#'=>556,'$'=>556,'%'=>889,'&'=>722,'\''=>238,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>333,';'=>333,'<'=>584,'='=>584,'>'=>584,'?'=>611,'@'=>975,'A'=>722, + 'B'=>722,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>556,'K'=>722,'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'=>667,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>584,'_'=>556,'`'=>333,'a'=>556,'b'=>611,'c'=>556,'d'=>611,'e'=>556,'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'=>500,'{'=>389,'|'=>280,'}'=>389,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>278,chr(131)=>556, + chr(132)=>500,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>278,chr(146)=>278,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>556,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>280,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>611,chr(182)=>556,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>556,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>611,chr(241)=>611, + chr(242)=>611,chr(243)=>611,chr(244)=>611,chr(245)=>611,chr(246)=>611,chr(247)=>584,chr(248)=>611,chr(249)=>611,chr(250)=>611,chr(251)=>611,chr(252)=>611,chr(253)=>556,chr(254)=>611,chr(255)=>556); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/helveticai.php b/Fpdi/font/helveticai.php new file mode 100644 index 0000000..06ec735 --- /dev/null +++ b/Fpdi/font/helveticai.php @@ -0,0 +1,21 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>278,'"'=>355,'#'=>556,'$'=>556,'%'=>889,'&'=>667,'\''=>191,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>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'=>556,'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'=>667,'Z'=>611,'['=>278,'\\'=>278,']'=>278,'^'=>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,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>222,chr(131)=>556, + chr(132)=>333,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>222,chr(146)=>222,chr(147)=>333,chr(148)=>333,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>500,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>260,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>556,chr(182)=>537,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>500,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>556,chr(241)=>556, + chr(242)=>556,chr(243)=>556,chr(244)=>556,chr(245)=>556,chr(246)=>556,chr(247)=>584,chr(248)=>611,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/symbol.php b/Fpdi/font/symbol.php new file mode 100644 index 0000000..f8f0c33 --- /dev/null +++ b/Fpdi/font/symbol.php @@ -0,0 +1,20 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>713,'#'=>500,'$'=>549,'%'=>833,'&'=>778,'\''=>439,'('=>333,')'=>333,'*'=>500,'+'=>549, + ','=>250,'-'=>549,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>278,';'=>278,'<'=>549,'='=>549,'>'=>549,'?'=>444,'@'=>549,'A'=>722, + 'B'=>667,'C'=>722,'D'=>612,'E'=>611,'F'=>763,'G'=>603,'H'=>722,'I'=>333,'J'=>631,'K'=>722,'L'=>686,'M'=>889,'N'=>722,'O'=>722,'P'=>768,'Q'=>741,'R'=>556,'S'=>592,'T'=>611,'U'=>690,'V'=>439,'W'=>768, + 'X'=>645,'Y'=>795,'Z'=>611,'['=>333,'\\'=>863,']'=>333,'^'=>658,'_'=>500,'`'=>500,'a'=>631,'b'=>549,'c'=>549,'d'=>494,'e'=>439,'f'=>521,'g'=>411,'h'=>603,'i'=>329,'j'=>603,'k'=>549,'l'=>549,'m'=>576, + 'n'=>521,'o'=>549,'p'=>549,'q'=>521,'r'=>549,'s'=>603,'t'=>439,'u'=>576,'v'=>713,'w'=>686,'x'=>493,'y'=>686,'z'=>494,'{'=>480,'|'=>200,'}'=>480,'~'=>549,chr(127)=>0,chr(128)=>0,chr(129)=>0,chr(130)=>0,chr(131)=>0, + chr(132)=>0,chr(133)=>0,chr(134)=>0,chr(135)=>0,chr(136)=>0,chr(137)=>0,chr(138)=>0,chr(139)=>0,chr(140)=>0,chr(141)=>0,chr(142)=>0,chr(143)=>0,chr(144)=>0,chr(145)=>0,chr(146)=>0,chr(147)=>0,chr(148)=>0,chr(149)=>0,chr(150)=>0,chr(151)=>0,chr(152)=>0,chr(153)=>0, + chr(154)=>0,chr(155)=>0,chr(156)=>0,chr(157)=>0,chr(158)=>0,chr(159)=>0,chr(160)=>750,chr(161)=>620,chr(162)=>247,chr(163)=>549,chr(164)=>167,chr(165)=>713,chr(166)=>500,chr(167)=>753,chr(168)=>753,chr(169)=>753,chr(170)=>753,chr(171)=>1042,chr(172)=>987,chr(173)=>603,chr(174)=>987,chr(175)=>603, + chr(176)=>400,chr(177)=>549,chr(178)=>411,chr(179)=>549,chr(180)=>549,chr(181)=>713,chr(182)=>494,chr(183)=>460,chr(184)=>549,chr(185)=>549,chr(186)=>549,chr(187)=>549,chr(188)=>1000,chr(189)=>603,chr(190)=>1000,chr(191)=>658,chr(192)=>823,chr(193)=>686,chr(194)=>795,chr(195)=>987,chr(196)=>768,chr(197)=>768, + chr(198)=>823,chr(199)=>768,chr(200)=>768,chr(201)=>713,chr(202)=>713,chr(203)=>713,chr(204)=>713,chr(205)=>713,chr(206)=>713,chr(207)=>713,chr(208)=>768,chr(209)=>713,chr(210)=>790,chr(211)=>790,chr(212)=>890,chr(213)=>823,chr(214)=>549,chr(215)=>250,chr(216)=>713,chr(217)=>603,chr(218)=>603,chr(219)=>1042, + chr(220)=>987,chr(221)=>603,chr(222)=>987,chr(223)=>603,chr(224)=>494,chr(225)=>329,chr(226)=>790,chr(227)=>790,chr(228)=>786,chr(229)=>713,chr(230)=>384,chr(231)=>384,chr(232)=>384,chr(233)=>384,chr(234)=>384,chr(235)=>384,chr(236)=>494,chr(237)=>494,chr(238)=>494,chr(239)=>494,chr(240)=>0,chr(241)=>329, + chr(242)=>274,chr(243)=>686,chr(244)=>686,chr(245)=>686,chr(246)=>384,chr(247)=>384,chr(248)=>384,chr(249)=>384,chr(250)=>384,chr(251)=>384,chr(252)=>494,chr(253)=>494,chr(254)=>494,chr(255)=>0); +$uv = array(32=>160,33=>33,34=>8704,35=>35,36=>8707,37=>array(37,2),39=>8715,40=>array(40,2),42=>8727,43=>array(43,2),45=>8722,46=>array(46,18),64=>8773,65=>array(913,2),67=>935,68=>array(916,2),70=>934,71=>915,72=>919,73=>921,74=>977,75=>array(922,4),79=>array(927,2),81=>920,82=>929,83=>array(931,3),86=>962,87=>937,88=>926,89=>936,90=>918,91=>91,92=>8756,93=>93,94=>8869,95=>95,96=>63717,97=>array(945,2),99=>967,100=>array(948,2),102=>966,103=>947,104=>951,105=>953,106=>981,107=>array(954,4),111=>array(959,2),113=>952,114=>961,115=>array(963,3),118=>982,119=>969,120=>958,121=>968,122=>950,123=>array(123,3),126=>8764,160=>8364,161=>978,162=>8242,163=>8804,164=>8725,165=>8734,166=>402,167=>9827,168=>9830,169=>9829,170=>9824,171=>8596,172=>array(8592,4),176=>array(176,2),178=>8243,179=>8805,180=>215,181=>8733,182=>8706,183=>8226,184=>247,185=>array(8800,2),187=>8776,188=>8230,189=>array(63718,2),191=>8629,192=>8501,193=>8465,194=>8476,195=>8472,196=>8855,197=>8853,198=>8709,199=>array(8745,2),201=>8835,202=>8839,203=>8836,204=>8834,205=>8838,206=>array(8712,2),208=>8736,209=>8711,210=>63194,211=>63193,212=>63195,213=>8719,214=>8730,215=>8901,216=>172,217=>array(8743,2),219=>8660,220=>array(8656,4),224=>9674,225=>9001,226=>array(63720,3),229=>8721,230=>array(63723,10),241=>9002,242=>8747,243=>8992,244=>63733,245=>8993,246=>array(63734,9)); +?> diff --git a/Fpdi/font/times.php b/Fpdi/font/times.php new file mode 100644 index 0000000..81f2a8b --- /dev/null +++ b/Fpdi/font/times.php @@ -0,0 +1,21 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>408,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>180,'('=>333,')'=>333,'*'=>500,'+'=>564, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>278,';'=>278,'<'=>564,'='=>564,'>'=>564,'?'=>444,'@'=>921,'A'=>722, + 'B'=>667,'C'=>667,'D'=>722,'E'=>611,'F'=>556,'G'=>722,'H'=>722,'I'=>333,'J'=>389,'K'=>722,'L'=>611,'M'=>889,'N'=>722,'O'=>722,'P'=>556,'Q'=>722,'R'=>667,'S'=>556,'T'=>611,'U'=>722,'V'=>722,'W'=>944, + 'X'=>722,'Y'=>722,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>469,'_'=>500,'`'=>333,'a'=>444,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>333,'g'=>500,'h'=>500,'i'=>278,'j'=>278,'k'=>500,'l'=>278,'m'=>778, + 'n'=>500,'o'=>500,'p'=>500,'q'=>500,'r'=>333,'s'=>389,'t'=>278,'u'=>500,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>444,'{'=>480,'|'=>200,'}'=>480,'~'=>541,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>444,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>889,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>444,chr(148)=>444,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>980, + chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>444,chr(159)=>722,chr(160)=>250,chr(161)=>333,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>200,chr(167)=>500,chr(168)=>333,chr(169)=>760,chr(170)=>276,chr(171)=>500,chr(172)=>564,chr(173)=>333,chr(174)=>760,chr(175)=>333, + chr(176)=>400,chr(177)=>564,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>500,chr(182)=>453,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>310,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>444,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>889,chr(199)=>667,chr(200)=>611,chr(201)=>611,chr(202)=>611,chr(203)=>611,chr(204)=>333,chr(205)=>333,chr(206)=>333,chr(207)=>333,chr(208)=>722,chr(209)=>722,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>564,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>722,chr(222)=>556,chr(223)=>500,chr(224)=>444,chr(225)=>444,chr(226)=>444,chr(227)=>444,chr(228)=>444,chr(229)=>444,chr(230)=>667,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>500, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>564,chr(248)=>500,chr(249)=>500,chr(250)=>500,chr(251)=>500,chr(252)=>500,chr(253)=>500,chr(254)=>500,chr(255)=>500); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/timesb.php b/Fpdi/font/timesb.php new file mode 100644 index 0000000..7db704f --- /dev/null +++ b/Fpdi/font/timesb.php @@ -0,0 +1,21 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>555,'#'=>500,'$'=>500,'%'=>1000,'&'=>833,'\''=>278,'('=>333,')'=>333,'*'=>500,'+'=>570, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>570,'='=>570,'>'=>570,'?'=>500,'@'=>930,'A'=>722, + 'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>778,'I'=>389,'J'=>500,'K'=>778,'L'=>667,'M'=>944,'N'=>722,'O'=>778,'P'=>611,'Q'=>778,'R'=>722,'S'=>556,'T'=>667,'U'=>722,'V'=>722,'W'=>1000, + 'X'=>722,'Y'=>722,'Z'=>667,'['=>333,'\\'=>278,']'=>333,'^'=>581,'_'=>500,'`'=>333,'a'=>500,'b'=>556,'c'=>444,'d'=>556,'e'=>444,'f'=>333,'g'=>500,'h'=>556,'i'=>278,'j'=>333,'k'=>556,'l'=>278,'m'=>833, + 'n'=>556,'o'=>500,'p'=>556,'q'=>556,'r'=>444,'s'=>389,'t'=>333,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>444,'{'=>394,'|'=>220,'}'=>394,'~'=>520,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>500,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>667,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>444,chr(159)=>722,chr(160)=>250,chr(161)=>333,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>220,chr(167)=>500,chr(168)=>333,chr(169)=>747,chr(170)=>300,chr(171)=>500,chr(172)=>570,chr(173)=>333,chr(174)=>747,chr(175)=>333, + chr(176)=>400,chr(177)=>570,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>556,chr(182)=>540,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>330,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>389,chr(205)=>389,chr(206)=>389,chr(207)=>389,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>570,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>722,chr(222)=>611,chr(223)=>556,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>722,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>556, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>570,chr(248)=>500,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/timesbi.php b/Fpdi/font/timesbi.php new file mode 100644 index 0000000..089f21a --- /dev/null +++ b/Fpdi/font/timesbi.php @@ -0,0 +1,21 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>389,'"'=>555,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>278,'('=>333,')'=>333,'*'=>500,'+'=>570, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>570,'='=>570,'>'=>570,'?'=>500,'@'=>832,'A'=>667, + 'B'=>667,'C'=>667,'D'=>722,'E'=>667,'F'=>667,'G'=>722,'H'=>778,'I'=>389,'J'=>500,'K'=>667,'L'=>611,'M'=>889,'N'=>722,'O'=>722,'P'=>611,'Q'=>722,'R'=>667,'S'=>556,'T'=>611,'U'=>722,'V'=>667,'W'=>889, + 'X'=>667,'Y'=>611,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>570,'_'=>500,'`'=>333,'a'=>500,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>333,'g'=>500,'h'=>556,'i'=>278,'j'=>278,'k'=>500,'l'=>278,'m'=>778, + 'n'=>556,'o'=>500,'p'=>500,'q'=>500,'r'=>389,'s'=>389,'t'=>278,'u'=>556,'v'=>444,'w'=>667,'x'=>500,'y'=>444,'z'=>389,'{'=>348,'|'=>220,'}'=>348,'~'=>570,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>500,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>944,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>389,chr(159)=>611,chr(160)=>250,chr(161)=>389,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>220,chr(167)=>500,chr(168)=>333,chr(169)=>747,chr(170)=>266,chr(171)=>500,chr(172)=>606,chr(173)=>333,chr(174)=>747,chr(175)=>333, + chr(176)=>400,chr(177)=>570,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>576,chr(182)=>500,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>300,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667, + chr(198)=>944,chr(199)=>667,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>389,chr(205)=>389,chr(206)=>389,chr(207)=>389,chr(208)=>722,chr(209)=>722,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>570,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>611,chr(222)=>611,chr(223)=>500,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>722,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>556, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>570,chr(248)=>500,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>444,chr(254)=>500,chr(255)=>444); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/timesi.php b/Fpdi/font/timesi.php new file mode 100644 index 0000000..f958b5b --- /dev/null +++ b/Fpdi/font/timesi.php @@ -0,0 +1,21 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>420,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>214,'('=>333,')'=>333,'*'=>500,'+'=>675, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>675,'='=>675,'>'=>675,'?'=>500,'@'=>920,'A'=>611, + 'B'=>611,'C'=>667,'D'=>722,'E'=>611,'F'=>611,'G'=>722,'H'=>722,'I'=>333,'J'=>444,'K'=>667,'L'=>556,'M'=>833,'N'=>667,'O'=>722,'P'=>611,'Q'=>722,'R'=>611,'S'=>500,'T'=>556,'U'=>722,'V'=>611,'W'=>833, + 'X'=>611,'Y'=>556,'Z'=>556,'['=>389,'\\'=>278,']'=>389,'^'=>422,'_'=>500,'`'=>333,'a'=>500,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>278,'g'=>500,'h'=>500,'i'=>278,'j'=>278,'k'=>444,'l'=>278,'m'=>722, + 'n'=>500,'o'=>500,'p'=>500,'q'=>500,'r'=>389,'s'=>389,'t'=>278,'u'=>500,'v'=>444,'w'=>667,'x'=>444,'y'=>444,'z'=>389,'{'=>400,'|'=>275,'}'=>400,'~'=>541,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>556,chr(133)=>889,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>500,chr(139)=>333,chr(140)=>944,chr(141)=>350,chr(142)=>556,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>556,chr(148)=>556,chr(149)=>350,chr(150)=>500,chr(151)=>889,chr(152)=>333,chr(153)=>980, + chr(154)=>389,chr(155)=>333,chr(156)=>667,chr(157)=>350,chr(158)=>389,chr(159)=>556,chr(160)=>250,chr(161)=>389,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>275,chr(167)=>500,chr(168)=>333,chr(169)=>760,chr(170)=>276,chr(171)=>500,chr(172)=>675,chr(173)=>333,chr(174)=>760,chr(175)=>333, + chr(176)=>400,chr(177)=>675,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>500,chr(182)=>523,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>310,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>611,chr(193)=>611,chr(194)=>611,chr(195)=>611,chr(196)=>611,chr(197)=>611, + chr(198)=>889,chr(199)=>667,chr(200)=>611,chr(201)=>611,chr(202)=>611,chr(203)=>611,chr(204)=>333,chr(205)=>333,chr(206)=>333,chr(207)=>333,chr(208)=>722,chr(209)=>667,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>675,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>556,chr(222)=>611,chr(223)=>500,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>667,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>500, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>675,chr(248)=>500,chr(249)=>500,chr(250)=>500,chr(251)=>500,chr(252)=>500,chr(253)=>444,chr(254)=>500,chr(255)=>444); +$enc = 'cp1252'; +$uv = array(0=>array(0,128),128=>8364,130=>8218,131=>402,132=>8222,133=>8230,134=>array(8224,2),136=>710,137=>8240,138=>352,139=>8249,140=>338,142=>381,145=>array(8216,2),147=>array(8220,2),149=>8226,150=>array(8211,2),152=>732,153=>8482,154=>353,155=>8250,156=>339,158=>382,159=>376,160=>array(160,96)); +?> diff --git a/Fpdi/font/zapfdingbats.php b/Fpdi/font/zapfdingbats.php new file mode 100644 index 0000000..7c2cb5e --- /dev/null +++ b/Fpdi/font/zapfdingbats.php @@ -0,0 +1,20 @@ +0,chr(1)=>0,chr(2)=>0,chr(3)=>0,chr(4)=>0,chr(5)=>0,chr(6)=>0,chr(7)=>0,chr(8)=>0,chr(9)=>0,chr(10)=>0,chr(11)=>0,chr(12)=>0,chr(13)=>0,chr(14)=>0,chr(15)=>0,chr(16)=>0,chr(17)=>0,chr(18)=>0,chr(19)=>0,chr(20)=>0,chr(21)=>0, + chr(22)=>0,chr(23)=>0,chr(24)=>0,chr(25)=>0,chr(26)=>0,chr(27)=>0,chr(28)=>0,chr(29)=>0,chr(30)=>0,chr(31)=>0,' '=>278,'!'=>974,'"'=>961,'#'=>974,'$'=>980,'%'=>719,'&'=>789,'\''=>790,'('=>791,')'=>690,'*'=>960,'+'=>939, + ','=>549,'-'=>855,'.'=>911,'/'=>933,'0'=>911,'1'=>945,'2'=>974,'3'=>755,'4'=>846,'5'=>762,'6'=>761,'7'=>571,'8'=>677,'9'=>763,':'=>760,';'=>759,'<'=>754,'='=>494,'>'=>552,'?'=>537,'@'=>577,'A'=>692, + 'B'=>786,'C'=>788,'D'=>788,'E'=>790,'F'=>793,'G'=>794,'H'=>816,'I'=>823,'J'=>789,'K'=>841,'L'=>823,'M'=>833,'N'=>816,'O'=>831,'P'=>923,'Q'=>744,'R'=>723,'S'=>749,'T'=>790,'U'=>792,'V'=>695,'W'=>776, + 'X'=>768,'Y'=>792,'Z'=>759,'['=>707,'\\'=>708,']'=>682,'^'=>701,'_'=>826,'`'=>815,'a'=>789,'b'=>789,'c'=>707,'d'=>687,'e'=>696,'f'=>689,'g'=>786,'h'=>787,'i'=>713,'j'=>791,'k'=>785,'l'=>791,'m'=>873, + 'n'=>761,'o'=>762,'p'=>762,'q'=>759,'r'=>759,'s'=>892,'t'=>892,'u'=>788,'v'=>784,'w'=>438,'x'=>138,'y'=>277,'z'=>415,'{'=>392,'|'=>392,'}'=>668,'~'=>668,chr(127)=>0,chr(128)=>390,chr(129)=>390,chr(130)=>317,chr(131)=>317, + chr(132)=>276,chr(133)=>276,chr(134)=>509,chr(135)=>509,chr(136)=>410,chr(137)=>410,chr(138)=>234,chr(139)=>234,chr(140)=>334,chr(141)=>334,chr(142)=>0,chr(143)=>0,chr(144)=>0,chr(145)=>0,chr(146)=>0,chr(147)=>0,chr(148)=>0,chr(149)=>0,chr(150)=>0,chr(151)=>0,chr(152)=>0,chr(153)=>0, + chr(154)=>0,chr(155)=>0,chr(156)=>0,chr(157)=>0,chr(158)=>0,chr(159)=>0,chr(160)=>0,chr(161)=>732,chr(162)=>544,chr(163)=>544,chr(164)=>910,chr(165)=>667,chr(166)=>760,chr(167)=>760,chr(168)=>776,chr(169)=>595,chr(170)=>694,chr(171)=>626,chr(172)=>788,chr(173)=>788,chr(174)=>788,chr(175)=>788, + chr(176)=>788,chr(177)=>788,chr(178)=>788,chr(179)=>788,chr(180)=>788,chr(181)=>788,chr(182)=>788,chr(183)=>788,chr(184)=>788,chr(185)=>788,chr(186)=>788,chr(187)=>788,chr(188)=>788,chr(189)=>788,chr(190)=>788,chr(191)=>788,chr(192)=>788,chr(193)=>788,chr(194)=>788,chr(195)=>788,chr(196)=>788,chr(197)=>788, + chr(198)=>788,chr(199)=>788,chr(200)=>788,chr(201)=>788,chr(202)=>788,chr(203)=>788,chr(204)=>788,chr(205)=>788,chr(206)=>788,chr(207)=>788,chr(208)=>788,chr(209)=>788,chr(210)=>788,chr(211)=>788,chr(212)=>894,chr(213)=>838,chr(214)=>1016,chr(215)=>458,chr(216)=>748,chr(217)=>924,chr(218)=>748,chr(219)=>918, + chr(220)=>927,chr(221)=>928,chr(222)=>928,chr(223)=>834,chr(224)=>873,chr(225)=>828,chr(226)=>924,chr(227)=>924,chr(228)=>917,chr(229)=>930,chr(230)=>931,chr(231)=>463,chr(232)=>883,chr(233)=>836,chr(234)=>836,chr(235)=>867,chr(236)=>867,chr(237)=>696,chr(238)=>696,chr(239)=>874,chr(240)=>0,chr(241)=>874, + chr(242)=>760,chr(243)=>946,chr(244)=>771,chr(245)=>865,chr(246)=>771,chr(247)=>888,chr(248)=>967,chr(249)=>888,chr(250)=>831,chr(251)=>873,chr(252)=>927,chr(253)=>970,chr(254)=>918,chr(255)=>0); +$uv = array(32=>32,33=>array(9985,4),37=>9742,38=>array(9990,4),42=>9755,43=>9758,44=>array(9996,28),72=>9733,73=>array(10025,35),108=>9679,109=>10061,110=>9632,111=>array(10063,4),115=>9650,116=>9660,117=>9670,118=>10070,119=>9687,120=>array(10072,7),128=>array(10088,14),161=>array(10081,7),168=>9827,169=>9830,170=>9829,171=>9824,172=>array(9312,10),182=>array(10102,31),213=>8594,214=>array(8596,2),216=>array(10136,24),241=>array(10161,14)); +?> diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a7faf0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 wander + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d877cc7 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# FPDI +PDF文件生成,FPDF,FPDI,中文支持,旋转文字方向,水印,模版生成,图片合并等 +---- + +建议在 PHP7.1 上运行以获取最佳性能; + +功能描述 +---- + +* PDF文件生成,FPDF,FPDI,中文支持,旋转文字方向,水印,模版生成,图片合并等 +* 参考_test目录下例子 + + +开源协议 +---- + +* fpdi 基于`MIT`协议发布,任何人可以用在任何地方,不受约束 +* fpdi 部分代码来自互联网,若有异议,可以联系作者(13834563@qq.com)进行删除 + diff --git a/_test/111.jpeg b/_test/111.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..167b5236289ce8a43a0174de7856f3e5e0fe8347 GIT binary patch literal 108883 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 +$pdf1->AddPage(); +$pdf1->SetFont('GB', '', 10); +$txt = <<Write(10, iconv("UTF-8", "gbk", $txt)); +$pdf1->Output('F', 'test.pdf'); diff --git a/_test/test.pdf b/_test/test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ab788292e2879baff22cc3f691e065069207b098 GIT binary patch literal 7137 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 BQ*8hM literal 0 HcmV?d00001 diff --git a/_test/test.php b/_test/test.php new file mode 100644 index 0000000..a81b219 --- /dev/null +++ b/_test/test.php @@ -0,0 +1,41 @@ +AddGBFont(); +$file = __DIR__ . '/test.pdf'; +//获取页数 +$pageCount = $pdf->setSourceFile($file); +//遍历所有页面 +for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) { + //导入页面 + $templateId = $pdf->importPage($pageNo); + //获取导入页面的大小 + $size = $pdf->getTemplateSize($templateId); + //创建页面(横向或纵向取决于导入的页面大小) + if ($size['width'] > $size['height']) { + $pdf->AddPage('L', array($size['width'], $size['height'])); + } else { + $pdf->AddPage('P', array($size['width'], $size['height'])); + } + if ($pageNo == $pageCount) { + $imgFile = __DIR__ . '/111.jpeg'; + $pdf->Image($imgFile, 120, 60, 50, 50, 'jpeg'); + } + $pdf->SetFont('GB', '', 10); + if ($pageNo == 1) { + $pdf->Text(22, 26, iconv("UTF-8", "gbk", '张三')); + $pdf->Text(55, 26, iconv("UTF-8", "gbk", '330108192238333333')); + } + $pdf->SetTextColor(211, 211, 211); + $txt = iconv("UTF-8", "gbk", '张三 330192238333333 张三 330192238333333 张三 330192238333333 张三 330192238333333 张三 330192238333333 张三 330192238333333'); + for ($i=2; $i<6; $i++) { + $pdf->RotatedText(15, $i*50, $txt, 30); + } + //使用导入的页面 + $pdf->useTemplate($templateId); +} +$pdf->Output('F', '2.pdf'); +die(); diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..61230ae --- /dev/null +++ b/autoload.php @@ -0,0 +1,20 @@ +=7.1", + "ext-zlib": "*" + }, + "autoload": { + "psr-4": { + "Fpdi\\": "Fpdi" + } + } +} diff --git a/include.php b/include.php new file mode 100644 index 0000000..cdd23be --- /dev/null +++ b/include.php @@ -0,0 +1,14 @@ +