1
This commit is contained in:
commit
efd543fe59
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/.git
|
||||
/.idea
|
||||
/vendor
|
||||
/Cache
|
||||
/nbproject
|
||||
/composer.lock
|
||||
.DS_Store
|
||||
cookie
|
381
Fpdi/CFpdf.php
Normal file
381
Fpdi/CFpdf.php
Normal file
@ -0,0 +1,381 @@
|
||||
<?php
|
||||
|
||||
namespace Fpdi;
|
||||
|
||||
/**
|
||||
* 中文支持及文字图片旋转支持
|
||||
*/
|
||||
class CFpdf extends \Fpdi\Fpdf
|
||||
{
|
||||
protected $angle = 0; //旋转角度
|
||||
|
||||
protected $Big5_widths = array(
|
||||
' ' => 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('<</Type /Font');
|
||||
$this->_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('<</Type /Font');
|
||||
$this->_out('/Subtype /CIDFontType0');
|
||||
$this->_out('/BaseFont /' . $font['name']);
|
||||
$this->_out('/CIDSystemInfo <</Registry ' . $this->_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('<</Type /FontDescriptor');
|
||||
$this->_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);
|
||||
}
|
||||
}
|
1795
Fpdi/Fpdf.php
Normal file
1795
Fpdi/Fpdf.php
Normal file
File diff suppressed because it is too large
Load Diff
21
Fpdi/FpdfTpl.php
Normal file
21
Fpdi/FpdfTpl.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi;
|
||||
|
||||
/**
|
||||
* Class FpdfTpl
|
||||
*
|
||||
* This class adds a templating feature to FPDF.
|
||||
*/
|
||||
class FpdfTpl extends \Fpdi\CFpdf
|
||||
{
|
||||
use FpdfTplTrait;
|
||||
}
|
470
Fpdi/FpdfTplTrait.php
Normal file
470
Fpdi/FpdfTplTrait.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi;
|
||||
|
||||
/**
|
||||
* Trait FpdfTplTrait
|
||||
*
|
||||
* This class adds a templating feature to tFPDF.
|
||||
*/
|
||||
trait FpdfTplTrait
|
||||
{
|
||||
/**
|
||||
* Data of all created templates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $templates = [];
|
||||
|
||||
/**
|
||||
* The template id for the currently created template.
|
||||
*
|
||||
* @var null|int
|
||||
*/
|
||||
protected $currentTemplateId;
|
||||
|
||||
/**
|
||||
* A counter for template ids.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $templateId = 0;
|
||||
|
||||
/**
|
||||
* Set the page format of the current page.
|
||||
*
|
||||
* @param array $size An array with two values defining the size.
|
||||
* @param string $orientation "L" for landscape, "P" for portrait.
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function setPageFormat($size, $orientation)
|
||||
{
|
||||
if ($this->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('<</Type /XObject /Subtype /Form /FormType 1');
|
||||
$this->_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 <</Type/Group/S/Transparency>>');
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
153
Fpdi/Fpdi.php
Normal file
153
Fpdi/Fpdi.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObject;
|
||||
use Fpdi\PdfParser\Type\PdfNull;
|
||||
|
||||
/**
|
||||
* Class Fpdi
|
||||
*
|
||||
* This class let you import pages of existing PDF documents into a reusable structure for FPDF.
|
||||
*/
|
||||
class Fpdi extends FpdfTpl
|
||||
{
|
||||
use FpdiTrait;
|
||||
|
||||
/**
|
||||
* FPDI version
|
||||
*
|
||||
* @string
|
||||
*/
|
||||
const VERSION = '2.3.6';
|
||||
|
||||
protected function _enddoc()
|
||||
{
|
||||
parent::_enddoc();
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
}
|
18
Fpdi/FpdiException.php
Normal file
18
Fpdi/FpdiException.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi;
|
||||
|
||||
/**
|
||||
* Base exception class for the FPDI package.
|
||||
*/
|
||||
class FpdiException extends \Exception
|
||||
{
|
||||
}
|
559
Fpdi/FpdiTrait.php
Normal file
559
Fpdi/FpdiTrait.php
Normal file
@ -0,0 +1,559 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\Filter\FilterException;
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
use Fpdi\PdfParser\Type\PdfArray;
|
||||
use Fpdi\PdfParser\Type\PdfBoolean;
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
use Fpdi\PdfParser\Type\PdfHexString;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObject;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObjectReference;
|
||||
use Fpdi\PdfParser\Type\PdfName;
|
||||
use Fpdi\PdfParser\Type\PdfNull;
|
||||
use Fpdi\PdfParser\Type\PdfNumeric;
|
||||
use Fpdi\PdfParser\Type\PdfStream;
|
||||
use Fpdi\PdfParser\Type\PdfString;
|
||||
use Fpdi\PdfParser\Type\PdfToken;
|
||||
use Fpdi\PdfParser\Type\PdfType;
|
||||
use Fpdi\PdfParser\Type\PdfTypeException;
|
||||
use Fpdi\PdfReader\PageBoundaries;
|
||||
use Fpdi\PdfReader\PdfReader;
|
||||
use Fpdi\PdfReader\PdfReaderException;
|
||||
use /* This namespace/class is used by the commercial FPDI PDF-Parser add-on. */
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
/** @noinspection PhpUndefinedNamespaceInspection */
|
||||
FpdiPdfParser\PdfParser\PdfParser as FpdiPdfParser;
|
||||
|
||||
/**
|
||||
* The FpdiTrait
|
||||
*
|
||||
* This trait offers the core functionalities of FPDI. By passing them to a trait we can reuse it with e.g. TCPDF in a
|
||||
* very easy way.
|
||||
*/
|
||||
trait FpdiTrait
|
||||
{
|
||||
/**
|
||||
* The pdf reader instances.
|
||||
*
|
||||
* @var PdfReader[]
|
||||
*/
|
||||
protected $readers = [];
|
||||
|
||||
/**
|
||||
* Instances created internally.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $createdReaders = [];
|
||||
|
||||
/**
|
||||
* The current reader id.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $currentReaderId;
|
||||
|
||||
/**
|
||||
* Data of all imported pages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $importedPages = [];
|
||||
|
||||
/**
|
||||
* A map from object numbers of imported objects to new assigned object numbers by FPDF.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objectMap = [];
|
||||
|
||||
/**
|
||||
* An array with information about objects, which needs to be copied to the resulting document.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objectsToCopy = [];
|
||||
|
||||
/**
|
||||
* Release resources and file handles.
|
||||
*
|
||||
* This method is called internally when the document is created successfully. By default it only cleans up
|
||||
* stream reader instances which were created internally.
|
||||
*
|
||||
* @param bool $allReaders
|
||||
*/
|
||||
public function cleanUp($allReaders = false)
|
||||
{
|
||||
$readers = $allReaders ? array_keys($this->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');
|
||||
}
|
||||
}
|
||||
}
|
95
Fpdi/PdfParser/CrossReference/AbstractReader.php
Normal file
95
Fpdi/PdfParser/CrossReference/AbstractReader.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\CrossReference;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
use Fpdi\PdfParser\Type\PdfToken;
|
||||
use Fpdi\PdfParser\Type\PdfTypeException;
|
||||
|
||||
/**
|
||||
* Abstract class for cross-reference reader classes.
|
||||
*/
|
||||
abstract class AbstractReader
|
||||
{
|
||||
/**
|
||||
* @var PdfParser
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* @var PdfDictionary
|
||||
*/
|
||||
protected $trailer;
|
||||
|
||||
/**
|
||||
* AbstractReader constructor.
|
||||
*
|
||||
* @param PdfParser $parser
|
||||
* @throws CrossReferenceException
|
||||
* @throws PdfTypeException
|
||||
*/
|
||||
public function __construct(PdfParser $parser)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
326
Fpdi/PdfParser/CrossReference/CrossReference.php
Normal file
326
Fpdi/PdfParser/CrossReference/CrossReference.php
Normal file
@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\CrossReference;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObject;
|
||||
use Fpdi\PdfParser\Type\PdfNumeric;
|
||||
use Fpdi\PdfParser\Type\PdfStream;
|
||||
use Fpdi\PdfParser\Type\PdfToken;
|
||||
use Fpdi\PdfParser\Type\PdfTypeException;
|
||||
|
||||
/**
|
||||
* Class CrossReference
|
||||
*
|
||||
* This class processes the standard cross reference of a PDF document.
|
||||
*/
|
||||
class CrossReference
|
||||
{
|
||||
/**
|
||||
* The byte length in which the "startxref" keyword should be searched.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $trailerSearchLength = 5500;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $fileHeaderOffset = 0;
|
||||
|
||||
/**
|
||||
* @var PdfParser
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* @var ReaderInterface[]
|
||||
*/
|
||||
protected $readers = [];
|
||||
|
||||
/**
|
||||
* CrossReference constructor.
|
||||
*
|
||||
* @param PdfParser $parser
|
||||
* @throws CrossReferenceException
|
||||
* @throws PdfTypeException
|
||||
*/
|
||||
public function __construct(PdfParser $parser, $fileHeaderOffset = 0)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
79
Fpdi/PdfParser/CrossReference/CrossReferenceException.php
Normal file
79
Fpdi/PdfParser/CrossReference/CrossReferenceException.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\CrossReference;
|
||||
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
|
||||
/**
|
||||
* Exception used by the CrossReference and Reader classes.
|
||||
*/
|
||||
class CrossReferenceException extends PdfParserException
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const INVALID_DATA = 0x0101;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const XREF_MISSING = 0x0102;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const ENTRIES_TOO_LARGE = 0x0103;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const ENTRIES_TOO_SHORT = 0x0104;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const NO_ENTRIES = 0x0105;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const NO_TRAILER_FOUND = 0x0106;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const NO_STARTXREF_FOUND = 0x0107;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const NO_XREF_FOUND = 0x0108;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const UNEXPECTED_END = 0x0109;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const OBJECT_NOT_FOUND = 0x010A;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const COMPRESSED_XREF = 0x010B;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const ENCRYPTED = 0x010C;
|
||||
}
|
199
Fpdi/PdfParser/CrossReference/FixedReader.php
Normal file
199
Fpdi/PdfParser/CrossReference/FixedReader.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\CrossReference;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
|
||||
/**
|
||||
* Class FixedReader
|
||||
*
|
||||
* This reader allows a very less overhead parsing of single entries of the cross-reference, because the main entries
|
||||
* are only read when needed and not in a single run.
|
||||
*/
|
||||
class FixedReader extends AbstractReader implements ReaderInterface
|
||||
{
|
||||
/**
|
||||
* @var StreamReader
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
/**
|
||||
* Data of subsections.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $subSections;
|
||||
|
||||
/**
|
||||
* FixedReader constructor.
|
||||
*
|
||||
* @param PdfParser $parser
|
||||
* @throws CrossReferenceException
|
||||
*/
|
||||
public function __construct(PdfParser $parser)
|
||||
{
|
||||
$this->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:
|
||||
*
|
||||
* <code>
|
||||
* xref
|
||||
* 1 7
|
||||
* 0000000000 65535 f
|
||||
* 0000000009 00000 n
|
||||
* 0000412075 00000 n
|
||||
* 0000412172 00000 n
|
||||
* 0000412359 00000 n
|
||||
* 0000412417 00000 n
|
||||
* 0000412468 00000 n
|
||||
* </code>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
167
Fpdi/PdfParser/CrossReference/LineReader.php
Normal file
167
Fpdi/PdfParser/CrossReference/LineReader.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\CrossReference;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
|
||||
/**
|
||||
* Class LineReader
|
||||
*
|
||||
* This reader class read all cross-reference entries in a single run.
|
||||
* It supports reading cross-references with e.g. invalid data (e.g. entries with a length < or > 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;
|
||||
}
|
||||
}
|
34
Fpdi/PdfParser/CrossReference/ReaderInterface.php
Normal file
34
Fpdi/PdfParser/CrossReference/ReaderInterface.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\CrossReference;
|
||||
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
|
||||
/**
|
||||
* ReaderInterface for cross-reference readers.
|
||||
*/
|
||||
interface ReaderInterface
|
||||
{
|
||||
/**
|
||||
* Get an offset by an object number.
|
||||
*
|
||||
* @param int $objectNumber
|
||||
* @return int|bool False if the offset was not found.
|
||||
*/
|
||||
public function getOffsetFor($objectNumber);
|
||||
|
||||
/**
|
||||
* Get the trailer related to this cross reference.
|
||||
*
|
||||
* @return PdfDictionary
|
||||
*/
|
||||
public function getTrailer();
|
||||
}
|
102
Fpdi/PdfParser/Filter/Ascii85.php
Normal file
102
Fpdi/PdfParser/Filter/Ascii85.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Class for handling ASCII base-85 encoded data
|
||||
*/
|
||||
class Ascii85 implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Decode ASCII85 encoded string.
|
||||
*
|
||||
* @param string $data The input string
|
||||
* @return string
|
||||
* @throws Ascii85Exception
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$out = '';
|
||||
$state = 0;
|
||||
$chn = null;
|
||||
|
||||
$data = \preg_replace('/\s/', '', $data);
|
||||
|
||||
$l = \strlen($data);
|
||||
|
||||
/** @noinspection ForeachInvariantsInspection */
|
||||
for ($k = 0; $k < $l; ++$k) {
|
||||
$ch = \ord($data[$k]) & 0xff;
|
||||
|
||||
//Start <~
|
||||
if ($k === 0 && $ch === 60 && isset($data[$k + 1]) && (\ord($data[$k + 1]) & 0xFF) === 126) {
|
||||
$k++;
|
||||
continue;
|
||||
}
|
||||
//End ~>
|
||||
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;
|
||||
}
|
||||
}
|
27
Fpdi/PdfParser/Filter/Ascii85Exception.php
Normal file
27
Fpdi/PdfParser/Filter/Ascii85Exception.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Exception for Ascii85 filter class
|
||||
*/
|
||||
class Ascii85Exception extends FilterException
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const ILLEGAL_CHAR_FOUND = 0x0301;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const ILLEGAL_LENGTH = 0x0302;
|
||||
}
|
47
Fpdi/PdfParser/Filter/AsciiHex.php
Normal file
47
Fpdi/PdfParser/Filter/AsciiHex.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Class for handling ASCII hexadecimal encoded data
|
||||
*/
|
||||
class AsciiHex implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Converts an ASCII hexadecimal encoded string into its binary representation.
|
||||
*
|
||||
* @param string $data The input string
|
||||
* @return string
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$data = \preg_replace('/[^0-9A-Fa-f]/', '', \rtrim($data, '>'));
|
||||
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 ? '' : '>');
|
||||
}
|
||||
}
|
23
Fpdi/PdfParser/Filter/FilterException.php
Normal file
23
Fpdi/PdfParser/Filter/FilterException.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
|
||||
/**
|
||||
* Exception for filters
|
||||
*/
|
||||
class FilterException extends PdfParserException
|
||||
{
|
||||
const UNSUPPORTED_FILTER = 0x0201;
|
||||
|
||||
const NOT_IMPLEMENTED = 0x0202;
|
||||
}
|
25
Fpdi/PdfParser/Filter/FilterInterface.php
Normal file
25
Fpdi/PdfParser/Filter/FilterInterface.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Interface for filters
|
||||
*/
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* Decode a string.
|
||||
*
|
||||
* @param string $data The input string
|
||||
* @return string
|
||||
*/
|
||||
public function decode($data);
|
||||
}
|
86
Fpdi/PdfParser/Filter/Flate.php
Normal file
86
Fpdi/PdfParser/Filter/Flate.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Class for handling zlib/deflate encoded data
|
||||
*/
|
||||
class Flate implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Checks whether the zlib extension is loaded.
|
||||
*
|
||||
* Used for testing purpose.
|
||||
*
|
||||
* @return boolean
|
||||
* @internal
|
||||
*/
|
||||
protected function extensionLoaded()
|
||||
{
|
||||
return \extension_loaded('zlib');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a flate compressed string.
|
||||
*
|
||||
* @param string|false $data The input string
|
||||
* @return string
|
||||
* @throws FlateException
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
if ($this->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;
|
||||
}
|
||||
}
|
27
Fpdi/PdfParser/Filter/FlateException.php
Normal file
27
Fpdi/PdfParser/Filter/FlateException.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Exception for flate filter class
|
||||
*/
|
||||
class FlateException extends FilterException
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const NO_ZLIB = 0x0401;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const DECOMPRESS_ERROR = 0x0402;
|
||||
}
|
187
Fpdi/PdfParser/Filter/Lzw.php
Normal file
187
Fpdi/PdfParser/Filter/Lzw.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Class for handling LZW encoded data
|
||||
*/
|
||||
class Lzw implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sTable = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $dataLength = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $tIdx;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $bitsToGet = 9;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $bytePointer;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $nextData = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $nextBits = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $andTable = [511, 1023, 2047, 4095];
|
||||
|
||||
/**
|
||||
* Method to decode LZW compressed data.
|
||||
*
|
||||
* @param string $data The compressed data
|
||||
* @return string The uncompressed data
|
||||
* @throws LzwException
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
if ($data[0] === "\x00" && $data[1] === "\x01") {
|
||||
throw new LzwException(
|
||||
'LZW flavour not supported.',
|
||||
LzwException::LZW_FLAVOUR_NOT_SUPPORTED
|
||||
);
|
||||
}
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
22
Fpdi/PdfParser/Filter/LzwException.php
Normal file
22
Fpdi/PdfParser/Filter/LzwException.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Filter;
|
||||
|
||||
/**
|
||||
* Exception for LZW filter class
|
||||
*/
|
||||
class LzwException extends FilterException
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const LZW_FLAVOUR_NOT_SUPPORTED = 0x0501;
|
||||
}
|
381
Fpdi/PdfParser/PdfParser.php
Normal file
381
Fpdi/PdfParser/PdfParser.php
Normal file
@ -0,0 +1,381 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReference;
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\Type\PdfArray;
|
||||
use Fpdi\PdfParser\Type\PdfBoolean;
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
use Fpdi\PdfParser\Type\PdfHexString;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObject;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObjectReference;
|
||||
use Fpdi\PdfParser\Type\PdfName;
|
||||
use Fpdi\PdfParser\Type\PdfNull;
|
||||
use Fpdi\PdfParser\Type\PdfNumeric;
|
||||
use Fpdi\PdfParser\Type\PdfStream;
|
||||
use Fpdi\PdfParser\Type\PdfString;
|
||||
use Fpdi\PdfParser\Type\PdfToken;
|
||||
use Fpdi\PdfParser\Type\PdfType;
|
||||
|
||||
/**
|
||||
* A PDF parser class
|
||||
*/
|
||||
class PdfParser
|
||||
{
|
||||
/**
|
||||
* @var StreamReader
|
||||
*/
|
||||
protected $streamReader;
|
||||
|
||||
/**
|
||||
* @var Tokenizer
|
||||
*/
|
||||
protected $tokenizer;
|
||||
|
||||
/**
|
||||
* The file header.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fileHeader;
|
||||
|
||||
/**
|
||||
* The offset to the file header.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $fileHeaderOffset;
|
||||
|
||||
/**
|
||||
* @var CrossReference|null
|
||||
*/
|
||||
protected $xref;
|
||||
|
||||
/**
|
||||
* All read objects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objects = [];
|
||||
|
||||
/**
|
||||
* PdfParser constructor.
|
||||
*
|
||||
* @param StreamReader $streamReader
|
||||
*/
|
||||
public function __construct(StreamReader $streamReader)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
49
Fpdi/PdfParser/PdfParserException.php
Normal file
49
Fpdi/PdfParser/PdfParserException.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser;
|
||||
|
||||
use Fpdi\FpdiException;
|
||||
|
||||
/**
|
||||
* Exception for the pdf parser class
|
||||
*/
|
||||
class PdfParserException extends FpdiException
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const NOT_IMPLEMENTED = 0x0001;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const IMPLEMENTED_IN_FPDI_PDF_PARSER = 0x0002;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const INVALID_DATA_TYPE = 0x0003;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const FILE_HEADER_NOT_FOUND = 0x0004;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const PDF_VERSION_NOT_FOUND = 0x0005;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const INVALID_DATA_SIZE = 0x0006;
|
||||
}
|
471
Fpdi/PdfParser/StreamReader.php
Normal file
471
Fpdi/PdfParser/StreamReader.php
Normal file
@ -0,0 +1,471 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser;
|
||||
|
||||
/**
|
||||
* A stream reader class
|
||||
*/
|
||||
class StreamReader
|
||||
{
|
||||
/**
|
||||
* Creates a stream reader instance by a string value.
|
||||
*
|
||||
* @param string $content
|
||||
* @param int $maxMemory
|
||||
* @return StreamReader
|
||||
*/
|
||||
public static function createByString($content, $maxMemory = 2097152)
|
||||
{
|
||||
$h = \fopen('php://temp/maxmemory:' . ((int) $maxMemory), 'r+b');
|
||||
\fwrite($h, $content);
|
||||
\rewind($h);
|
||||
|
||||
return new self($h, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stream reader instance by a filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return StreamReader
|
||||
*/
|
||||
public static function createByFile($filename)
|
||||
{
|
||||
$h = \fopen($filename, 'rb');
|
||||
return new self($h, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines whether the stream should be closed when the stream reader instance is deconstructed or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $closeStream;
|
||||
|
||||
/**
|
||||
* The stream resource.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $stream;
|
||||
|
||||
/**
|
||||
* The byte-offset position in the stream.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $position;
|
||||
|
||||
/**
|
||||
* The byte-offset position in the buffer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $offset;
|
||||
|
||||
/**
|
||||
* The buffer length.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $bufferLength;
|
||||
|
||||
/**
|
||||
* The total length of the stream.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $totalLength;
|
||||
|
||||
/**
|
||||
* The buffer.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $buffer;
|
||||
|
||||
/**
|
||||
* StreamReader constructor.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param bool $closeStream Defines whether to close the stream resource if the instance is destructed or not.
|
||||
*/
|
||||
public function __construct($stream, $closeStream = false)
|
||||
{
|
||||
if (!\is_resource($stream)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'No stream given.'
|
||||
);
|
||||
}
|
||||
|
||||
$metaData = \stream_get_meta_data($stream);
|
||||
if (!$metaData['seekable']) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Given stream is not seekable!'
|
||||
);
|
||||
}
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
154
Fpdi/PdfParser/Tokenizer.php
Normal file
154
Fpdi/PdfParser/Tokenizer.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser;
|
||||
|
||||
/**
|
||||
* A tokenizer class.
|
||||
*/
|
||||
class Tokenizer
|
||||
{
|
||||
/**
|
||||
* @var StreamReader
|
||||
*/
|
||||
protected $streamReader;
|
||||
|
||||
/**
|
||||
* A token stack.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $stack = [];
|
||||
|
||||
/**
|
||||
* Tokenizer constructor.
|
||||
*
|
||||
* @param StreamReader $streamReader
|
||||
*/
|
||||
public function __construct(StreamReader $streamReader)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
85
Fpdi/PdfParser/Type/PdfArray.php
Normal file
85
Fpdi/PdfParser/Type/PdfArray.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\Tokenizer;
|
||||
|
||||
/**
|
||||
* Class representing a PDF array object
|
||||
*
|
||||
* @property array $value The value of the PDF type.
|
||||
*/
|
||||
class PdfArray extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses an array of the passed tokenizer and parser.
|
||||
*
|
||||
* @param Tokenizer $tokenizer
|
||||
* @param PdfParser $parser
|
||||
* @return bool|self
|
||||
* @throws PdfTypeException
|
||||
*/
|
||||
public static function parse(Tokenizer $tokenizer, PdfParser $parser)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Recurse into this function until we reach the end of the array.
|
||||
while (($token = $tokenizer->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;
|
||||
}
|
||||
}
|
42
Fpdi/PdfParser/Type/PdfBoolean.php
Normal file
42
Fpdi/PdfParser/Type/PdfBoolean.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
/**
|
||||
* Class representing a boolean PDF object
|
||||
*/
|
||||
class PdfBoolean extends PdfType
|
||||
{
|
||||
/**
|
||||
* Helper method to create an instance.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return self
|
||||
*/
|
||||
public static function create($value)
|
||||
{
|
||||
$v = new self();
|
||||
$v->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.');
|
||||
}
|
||||
}
|
134
Fpdi/PdfParser/Type/PdfDictionary.php
Normal file
134
Fpdi/PdfParser/Type/PdfDictionary.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
use Fpdi\PdfParser\Tokenizer;
|
||||
|
||||
/**
|
||||
* Class representing a PDF dictionary object
|
||||
*/
|
||||
class PdfDictionary extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses a dictionary of the passed tokenizer, stream-reader and parser.
|
||||
*
|
||||
* @param Tokenizer $tokenizer
|
||||
* @param StreamReader $streamReader
|
||||
* @param PdfParser $parser
|
||||
* @return bool|self
|
||||
* @throws PdfTypeException
|
||||
*/
|
||||
public static function parse(Tokenizer $tokenizer, StreamReader $streamReader, PdfParser $parser)
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
while (true) {
|
||||
$token = $tokenizer->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.');
|
||||
}
|
||||
}
|
77
Fpdi/PdfParser/Type/PdfHexString.php
Normal file
77
Fpdi/PdfParser/Type/PdfHexString.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
|
||||
/**
|
||||
* Class representing a hexadecimal encoded PDF string object
|
||||
*/
|
||||
class PdfHexString extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses a hexadecimal string object from the stream reader.
|
||||
*
|
||||
* @param StreamReader $streamReader
|
||||
* @return bool|self
|
||||
*/
|
||||
public static function parse(StreamReader $streamReader)
|
||||
{
|
||||
$bufferOffset = $streamReader->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.');
|
||||
}
|
||||
}
|
103
Fpdi/PdfParser/Type/PdfIndirectObject.php
Normal file
103
Fpdi/PdfParser/Type/PdfIndirectObject.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
use Fpdi\PdfParser\Tokenizer;
|
||||
|
||||
/**
|
||||
* Class representing an indirect object
|
||||
*/
|
||||
class PdfIndirectObject extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses an indirect object from a tokenizer, parser and stream-reader.
|
||||
*
|
||||
* @param int $objectNumberToken
|
||||
* @param int $objectGenerationNumberToken
|
||||
* @param PdfParser $parser
|
||||
* @param Tokenizer $tokenizer
|
||||
* @param StreamReader $reader
|
||||
* @return bool|self
|
||||
* @throws PdfTypeException
|
||||
*/
|
||||
public static function parse(
|
||||
$objectNumberToken,
|
||||
$objectGenerationNumberToken,
|
||||
PdfParser $parser,
|
||||
Tokenizer $tokenizer,
|
||||
StreamReader $reader
|
||||
) {
|
||||
$value = $parser->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;
|
||||
}
|
52
Fpdi/PdfParser/Type/PdfIndirectObjectReference.php
Normal file
52
Fpdi/PdfParser/Type/PdfIndirectObjectReference.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
/**
|
||||
* Class representing an indirect object reference
|
||||
*/
|
||||
class PdfIndirectObjectReference extends PdfType
|
||||
{
|
||||
/**
|
||||
* Helper method to create an instance.
|
||||
*
|
||||
* @param int $objectNumber
|
||||
* @param int $generationNumber
|
||||
* @return self
|
||||
*/
|
||||
public static function create($objectNumber, $generationNumber)
|
||||
{
|
||||
$v = new self();
|
||||
$v->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;
|
||||
}
|
82
Fpdi/PdfParser/Type/PdfName.php
Normal file
82
Fpdi/PdfParser/Type/PdfName.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
use Fpdi\PdfParser\Tokenizer;
|
||||
|
||||
/**
|
||||
* Class representing a PDF name object
|
||||
*/
|
||||
class PdfName extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses a name object from the passed tokenizer and stream-reader.
|
||||
*
|
||||
* @param Tokenizer $tokenizer
|
||||
* @param StreamReader $streamReader
|
||||
* @return self
|
||||
*/
|
||||
public static function parse(Tokenizer $tokenizer, StreamReader $streamReader)
|
||||
{
|
||||
$v = new self();
|
||||
if (\strspn($streamReader->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.');
|
||||
}
|
||||
}
|
19
Fpdi/PdfParser/Type/PdfNull.php
Normal file
19
Fpdi/PdfParser/Type/PdfNull.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
/**
|
||||
* Class representing a PDF null object
|
||||
*/
|
||||
class PdfNull extends PdfType
|
||||
{
|
||||
// empty body
|
||||
}
|
43
Fpdi/PdfParser/Type/PdfNumeric.php
Normal file
43
Fpdi/PdfParser/Type/PdfNumeric.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
/**
|
||||
* Class representing a numeric PDF object
|
||||
*/
|
||||
class PdfNumeric extends PdfType
|
||||
{
|
||||
/**
|
||||
* Helper method to create an instance.
|
||||
*
|
||||
* @param int|float $value
|
||||
* @return PdfNumeric
|
||||
*/
|
||||
public static function create($value)
|
||||
{
|
||||
$v = new self();
|
||||
$v->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.');
|
||||
}
|
||||
}
|
326
Fpdi/PdfParser/Type/PdfStream.php
Normal file
326
Fpdi/PdfParser/Type/PdfStream.php
Normal file
@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\Filter\Ascii85;
|
||||
use Fpdi\PdfParser\Filter\AsciiHex;
|
||||
use Fpdi\PdfParser\Filter\FilterException;
|
||||
use Fpdi\PdfParser\Filter\Flate;
|
||||
use Fpdi\PdfParser\Filter\Lzw;
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
use FpdiPdfParser\PdfParser\Filter\Predictor;
|
||||
|
||||
/**
|
||||
* Class representing a PDF stream object
|
||||
*/
|
||||
class PdfStream extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses a stream from a stream reader.
|
||||
*
|
||||
* @param PdfDictionary $dictionary
|
||||
* @param StreamReader $reader
|
||||
* @param PdfParser $parser Optional to keep backwards compatibility
|
||||
* @return self
|
||||
* @throws PdfTypeException
|
||||
*/
|
||||
public static function parse(PdfDictionary $dictionary, StreamReader $reader, PdfParser $parser = null)
|
||||
{
|
||||
$v = new self();
|
||||
$v->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;
|
||||
}
|
||||
}
|
172
Fpdi/PdfParser/Type/PdfString.php
Normal file
172
Fpdi/PdfParser/Type/PdfString.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\StreamReader;
|
||||
|
||||
/**
|
||||
* Class representing a PDF string object
|
||||
*/
|
||||
class PdfString extends PdfType
|
||||
{
|
||||
/**
|
||||
* Parses a string object from the stream reader.
|
||||
*
|
||||
* @param StreamReader $streamReader
|
||||
* @return self
|
||||
*/
|
||||
public static function parse(StreamReader $streamReader)
|
||||
{
|
||||
$pos = $startPos = $streamReader->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;
|
||||
}
|
||||
}
|
43
Fpdi/PdfParser/Type/PdfToken.php
Normal file
43
Fpdi/PdfParser/Type/PdfToken.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
/**
|
||||
* Class representing PDF token object
|
||||
*/
|
||||
class PdfToken extends PdfType
|
||||
{
|
||||
/**
|
||||
* Helper method to create an instance.
|
||||
*
|
||||
* @param string $token
|
||||
* @return self
|
||||
*/
|
||||
public static function create($token)
|
||||
{
|
||||
$v = new self();
|
||||
$v->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.');
|
||||
}
|
||||
}
|
78
Fpdi/PdfParser/Type/PdfType.php
Normal file
78
Fpdi/PdfParser/Type/PdfType.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
|
||||
/**
|
||||
* A class defining a PDF data type
|
||||
*/
|
||||
class PdfType
|
||||
{
|
||||
/**
|
||||
* Resolves a PdfType value to its value.
|
||||
*
|
||||
* This method is used to evaluate indirect and direct object references until a final value is reached.
|
||||
*
|
||||
* @param PdfType $value
|
||||
* @param PdfParser $parser
|
||||
* @param bool $stopAtIndirectObject
|
||||
* @return PdfType
|
||||
* @throws CrossReferenceException
|
||||
* @throws PdfParserException
|
||||
*/
|
||||
public static function resolve(PdfType $value, PdfParser $parser, $stopAtIndirectObject = false)
|
||||
{
|
||||
if ($value instanceof PdfIndirectObject) {
|
||||
if ($stopAtIndirectObject === true) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return self::resolve($value->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;
|
||||
}
|
24
Fpdi/PdfParser/Type/PdfTypeException.php
Normal file
24
Fpdi/PdfParser/Type/PdfTypeException.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfParser\Type;
|
||||
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
|
||||
/**
|
||||
* Exception class for pdf type classes
|
||||
*/
|
||||
class PdfTypeException extends PdfParserException
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const NO_NEWLINE_AFTER_STREAM_KEYWORD = 0x0601;
|
||||
}
|
173
Fpdi/PdfReader/DataStructure/Rectangle.php
Normal file
173
Fpdi/PdfReader/DataStructure/Rectangle.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfReader\DataStructure;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
use Fpdi\PdfParser\Type\PdfArray;
|
||||
use Fpdi\PdfParser\Type\PdfNumeric;
|
||||
use Fpdi\PdfParser\Type\PdfType;
|
||||
use Fpdi\PdfParser\Type\PdfTypeException;
|
||||
|
||||
/**
|
||||
* Class representing a rectangle
|
||||
*/
|
||||
class Rectangle
|
||||
{
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
protected $llx;
|
||||
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
protected $lly;
|
||||
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
protected $urx;
|
||||
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
protected $ury;
|
||||
|
||||
/**
|
||||
* Create a rectangle instance by a PdfArray.
|
||||
*
|
||||
* @param PdfArray|mixed $array
|
||||
* @param PdfParser $parser
|
||||
* @return Rectangle
|
||||
* @throws PdfTypeException
|
||||
* @throws CrossReferenceException
|
||||
* @throws PdfParserException
|
||||
*/
|
||||
public static function byPdfArray($array, PdfParser $parser)
|
||||
{
|
||||
$array = PdfArray::ensure(PdfType::resolve($array, $parser), 4)->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;
|
||||
}
|
||||
}
|
271
Fpdi/PdfReader/Page.php
Normal file
271
Fpdi/PdfReader/Page.php
Normal file
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfReader;
|
||||
|
||||
use Fpdi\PdfParser\Filter\FilterException;
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
use Fpdi\PdfParser\Type\PdfArray;
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObject;
|
||||
use Fpdi\PdfParser\Type\PdfNull;
|
||||
use Fpdi\PdfParser\Type\PdfNumeric;
|
||||
use Fpdi\PdfParser\Type\PdfStream;
|
||||
use Fpdi\PdfParser\Type\PdfType;
|
||||
use Fpdi\PdfParser\Type\PdfTypeException;
|
||||
use Fpdi\PdfReader\DataStructure\Rectangle;
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
|
||||
/**
|
||||
* Class representing a page of a PDF document
|
||||
*/
|
||||
class Page
|
||||
{
|
||||
/**
|
||||
* @var PdfIndirectObject
|
||||
*/
|
||||
protected $pageObject;
|
||||
|
||||
/**
|
||||
* @var PdfDictionary
|
||||
*/
|
||||
protected $pageDictionary;
|
||||
|
||||
/**
|
||||
* @var PdfParser
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* Inherited attributes
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
protected $inheritedAttributes;
|
||||
|
||||
/**
|
||||
* Page constructor.
|
||||
*
|
||||
* @param PdfIndirectObject $page
|
||||
* @param PdfParser $parser
|
||||
*/
|
||||
public function __construct(PdfIndirectObject $page, PdfParser $parser)
|
||||
{
|
||||
$this->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
|
||||
);
|
||||
}
|
||||
}
|
94
Fpdi/PdfReader/PageBoundaries.php
Normal file
94
Fpdi/PdfReader/PageBoundaries.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfReader;
|
||||
|
||||
/**
|
||||
* An abstract class for page boundary constants and some helper methods
|
||||
*/
|
||||
abstract class PageBoundaries
|
||||
{
|
||||
/**
|
||||
* MediaBox
|
||||
*
|
||||
* The media box defines the boundaries of the physical medium on which the page is to be printed.
|
||||
*
|
||||
* @see PDF 32000-1:2008 - 14.11.2 Page Boundaries
|
||||
* @var string
|
||||
*/
|
||||
const MEDIA_BOX = 'MediaBox';
|
||||
|
||||
/**
|
||||
* CropBox
|
||||
*
|
||||
* The crop box defines the region to which the contents of the page shall be clipped (cropped) when displayed or
|
||||
* printed.
|
||||
*
|
||||
* @see PDF 32000-1:2008 - 14.11.2 Page Boundaries
|
||||
* @var string
|
||||
*/
|
||||
const CROP_BOX = 'CropBox';
|
||||
|
||||
/**
|
||||
* BleedBox
|
||||
*
|
||||
* The bleed box defines the region to which the contents of the page shall be clipped when output in a
|
||||
* production environment.
|
||||
*
|
||||
* @see PDF 32000-1:2008 - 14.11.2 Page Boundaries
|
||||
* @var string
|
||||
*/
|
||||
const BLEED_BOX = 'BleedBox';
|
||||
|
||||
/**
|
||||
* TrimBox
|
||||
*
|
||||
* The trim box defines the intended dimensions of the finished page after trimming.
|
||||
*
|
||||
* @see PDF 32000-1:2008 - 14.11.2 Page Boundaries
|
||||
* @var string
|
||||
*/
|
||||
const TRIM_BOX = 'TrimBox';
|
||||
|
||||
/**
|
||||
* ArtBox
|
||||
*
|
||||
* The art box defines the extent of the page’s meaningful content (including potential white space) as intended
|
||||
* by the page’s creator.
|
||||
*
|
||||
* @see PDF 32000-1:2008 - 14.11.2 Page Boundaries
|
||||
* @var string
|
||||
*/
|
||||
const ART_BOX = 'ArtBox';
|
||||
|
||||
/**
|
||||
* All page boundaries
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $all = array(
|
||||
self::MEDIA_BOX,
|
||||
self::CROP_BOX,
|
||||
self::BLEED_BOX,
|
||||
self::TRIM_BOX,
|
||||
self::ART_BOX
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks if a name is a valid page boundary name.
|
||||
*
|
||||
* @param string $name The boundary name
|
||||
* @return boolean A boolean value whether the name is valid or not.
|
||||
*/
|
||||
public static function isValidName($name)
|
||||
{
|
||||
return \in_array($name, self::$all, true);
|
||||
}
|
||||
}
|
234
Fpdi/PdfReader/PdfReader.php
Normal file
234
Fpdi/PdfReader/PdfReader.php
Normal file
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfReader;
|
||||
|
||||
use Fpdi\PdfParser\CrossReference\CrossReferenceException;
|
||||
use Fpdi\PdfParser\PdfParser;
|
||||
use Fpdi\PdfParser\PdfParserException;
|
||||
use Fpdi\PdfParser\Type\PdfArray;
|
||||
use Fpdi\PdfParser\Type\PdfDictionary;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObject;
|
||||
use Fpdi\PdfParser\Type\PdfIndirectObjectReference;
|
||||
use Fpdi\PdfParser\Type\PdfNumeric;
|
||||
use Fpdi\PdfParser\Type\PdfType;
|
||||
use Fpdi\PdfParser\Type\PdfTypeException;
|
||||
|
||||
/**
|
||||
* A PDF reader class
|
||||
*/
|
||||
class PdfReader
|
||||
{
|
||||
/**
|
||||
* @var PdfParser
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $pageCount;
|
||||
|
||||
/**
|
||||
* Indirect objects of resolved pages.
|
||||
*
|
||||
* @var PdfIndirectObjectReference[]|PdfIndirectObject[]
|
||||
*/
|
||||
protected $pages = [];
|
||||
|
||||
/**
|
||||
* PdfReader constructor.
|
||||
*
|
||||
* @param PdfParser $parser
|
||||
*/
|
||||
public function __construct(PdfParser $parser)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
34
Fpdi/PdfReader/PdfReaderException.php
Normal file
34
Fpdi/PdfReader/PdfReaderException.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
namespace Fpdi\PdfReader;
|
||||
|
||||
use Fpdi\FpdiException;
|
||||
|
||||
/**
|
||||
* Exception for the pdf reader class
|
||||
*/
|
||||
class PdfReaderException extends FpdiException
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const KIDS_EMPTY = 0x0101;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const UNEXPECTED_DATA_TYPE = 0x0102;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const MISSING_DATA = 0x0103;
|
||||
}
|
10
Fpdi/font/courier.php
Normal file
10
Fpdi/font/courier.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Courier';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
for($i=0;$i<=255;$i++)
|
||||
$cw[chr($i)] = 600;
|
||||
$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));
|
||||
?>
|
10
Fpdi/font/courierb.php
Normal file
10
Fpdi/font/courierb.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Courier-Bold';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
for($i=0;$i<=255;$i++)
|
||||
$cw[chr($i)] = 600;
|
||||
$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));
|
||||
?>
|
10
Fpdi/font/courierbi.php
Normal file
10
Fpdi/font/courierbi.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Courier-BoldOblique';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
for($i=0;$i<=255;$i++)
|
||||
$cw[chr($i)] = 600;
|
||||
$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));
|
||||
?>
|
10
Fpdi/font/courieri.php
Normal file
10
Fpdi/font/courieri.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Courier-Oblique';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
for($i=0;$i<=255;$i++)
|
||||
$cw[chr($i)] = 600;
|
||||
$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));
|
||||
?>
|
21
Fpdi/font/helvetica.php
Normal file
21
Fpdi/font/helvetica.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Helvetica';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/helveticab.php
Normal file
21
Fpdi/font/helveticab.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Helvetica-Bold';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/helveticabi.php
Normal file
21
Fpdi/font/helveticabi.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Helvetica-BoldOblique';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/helveticai.php
Normal file
21
Fpdi/font/helveticai.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Helvetica-Oblique';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
20
Fpdi/font/symbol.php
Normal file
20
Fpdi/font/symbol.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Symbol';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/times.php
Normal file
21
Fpdi/font/times.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Times-Roman';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/timesb.php
Normal file
21
Fpdi/font/timesb.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Times-Bold';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/timesbi.php
Normal file
21
Fpdi/font/timesbi.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Times-BoldItalic';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
Fpdi/font/timesi.php
Normal file
21
Fpdi/font/timesi.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'Times-Italic';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
20
Fpdi/font/zapfdingbats.php
Normal file
20
Fpdi/font/zapfdingbats.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
$type = 'Core';
|
||||
$name = 'ZapfDingbats';
|
||||
$up = -100;
|
||||
$ut = 50;
|
||||
$cw = array(
|
||||
chr(0)=>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));
|
||||
?>
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -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.
|
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# FPDI
|
||||
PDF文件生成,FPDF,FPDI,中文支持,旋转文字方向,水印,模版生成,图片合并等
|
||||
----
|
||||
|
||||
建议在 PHP7.1 上运行以获取最佳性能;
|
||||
|
||||
功能描述
|
||||
----
|
||||
|
||||
* PDF文件生成,FPDF,FPDI,中文支持,旋转文字方向,水印,模版生成,图片合并等
|
||||
* 参考_test目录下例子
|
||||
|
||||
|
||||
开源协议
|
||||
----
|
||||
|
||||
* fpdi 基于`MIT`协议发布,任何人可以用在任何地方,不受约束
|
||||
* fpdi 部分代码来自互联网,若有异议,可以联系作者(13834563@qq.com)进行删除
|
||||
|
BIN
_test/111.jpeg
Normal file
BIN
_test/111.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
28
_test/create.php
Normal file
28
_test/create.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
// 1. 手动加载入口文件
|
||||
include __DIR__ ."/../include.php";
|
||||
// initiate FPDI
|
||||
$pdf1 = new \Fpdi\Fpdi();
|
||||
$pdf1->AddGBFont();
|
||||
$pdf1->AddPage();
|
||||
$pdf1->SetFont('GB', '', 10);
|
||||
$txt = <<<EOF
|
||||
甲方:杭州百度网计算机技术有限公司
|
||||
乙方: 身份证号:
|
||||
欢迎使用 FPDI 1.5.4!
|
||||
据报道,目前,天文学家最新发明一种方法“透视”早期宇宙的迷雾,这样便于探测到宇宙早期恒星和星系释放的光线。
|
||||
观察这些宇宙初期恒星诞生是科学家长期以来的一个目标,因为这将有助于解释宇宙是如何从大爆炸后的虚无境地演变成 138 亿年后现今观察到的复杂宇宙,现在这将是詹姆斯·韦伯太空望远镜和平方公里阵列射电望远镜(SKA)的主要勘测任务之一。
|
||||
但是詹姆斯·韦伯太空望远镜观测的是红外波长范围,而新一代陆基 SKA 望远镜(预计 2024 年前后完工,真正投入使用将在 2030 年),将通过射电电波研究早期宇宙。
|
||||
对于当前正在使用的射电望远镜而言,其技术挑战在于通过厚密氢云探测到恒星信号,氢云能更好地吸收光线,从而阻挡人们的观测视野。射电信号失真也会成为干扰因素,因此,探测宇宙初期恒星是现代射电宇宙学面临的重大挑战之一。
|
||||
例如:天文学家试图探测比银河系信号微弱 10 万倍的系外信号,目前,英国剑桥大学研究人员最新开发一种数学方法,可使他们“透视”原始星云和其他宇宙噪声信号。因此,这将使他们避免由射电望远镜引起信号失真的不利影响。
|
||||
该观点是宇宙氢分析射电实验(REACH)的一部分,这将允许天文学家通过与氢云的相互作用来观察宇宙初期的恒星,就像我们通过观察雾中阴影来推断景观一样。希望它能提高射电望远镜观测宇宙演变关键时期的质量和可靠性,预计宇宙氢分析射电实验的第一次观测将于今年晚些时候进行。
|
||||
该研究报告负责人、剑桥大学卡文迪什实验室埃洛伊·德莱拉·阿西多(Eloy de Lera Acedo)博士说:“在宇宙第一批恒星形成的时候,宇宙基本上空荡荡的,主要由氢和氦构成,在引力作用下,这些元素最终聚集在一起,形成了适合核聚变的条件,这将促进第一批恒星的诞生,但是它们被所谓的中性氢云包围,中性氢云能较好地吸收光线,所以人们很难直接探测或者观察氢云后方的光线。”
|
||||
2018 年,另一支研究小组发表研究结果,暗示可能探测到宇宙最早的光,但当时他们无法重复该实验结果,从而让他们相信最初的研究结果可能源自望远镜的干扰。阿西多博士说:“最初的研究结果需要新的物理学理论进行解释,因为氢气的温度应该比我们理解的宇宙温度阈值低很多,或者无法解释的背景辐射温度升高,可能是众所周知的宇宙微波背景辐射所致,如果我们能确认之前实验中发现的光信号来自于宇宙第一批恒星,那么这项研究的意义将非常巨大。”
|
||||
为了研究宇宙发展的这段时期,通常被称为“宇宙黎明”,天文学家使用了 21 厘米长信号线,这是早期宇宙氢原子电磁辐射信号,他们寻找一种射电信号,能测量对比氢辐射和氢雾背景辐射之间的差异。
|
||||
该方法是由阿西多博士和同事使用贝叶斯统计法来探测望远镜干扰和宇宙噪音信号,这样信号就被分离开来,要做到这一点,需要不同领域的最新技术进行验证。
|
||||
据悉,目前位于南非卡鲁射电保护区的平方公里阵列射电望远镜项目已完成,该地区因具有优越的天空射电观测条件而被选中,这里远离人为制造的射频干扰,例如:电视和调频无线电信号等。
|
||||
基于对宇宙微波背景辐射(CMB)的研究分析,人们已较深入地理解大爆炸和宇宙初期状况,但是宇宙第一束光线的形成时间仍是宇宙探索史上一个未揭晓的谜团。目前这项最新研究发表在近期出版的《自然天文学杂志》上。
|
||||
EOF;
|
||||
$pdf1->Write(10, iconv("UTF-8", "gbk", $txt));
|
||||
$pdf1->Output('F', 'test.pdf');
|
BIN
_test/test.pdf
Normal file
BIN
_test/test.pdf
Normal file
Binary file not shown.
41
_test/test.php
Normal file
41
_test/test.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
// 1. 手动加载入口文件
|
||||
include __DIR__ ."/../include.php";
|
||||
|
||||
$pdf = new \Fpdi\Fpdi();
|
||||
$pdf->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();
|
20
autoload.php
Normal file
20
autoload.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of FPDI
|
||||
*
|
||||
* @package Fpdi
|
||||
* @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
|
||||
* @license http://opensource.org/licenses/mit-license The MIT License
|
||||
*/
|
||||
|
||||
spl_autoload_register(static function ($class) {
|
||||
if (strpos($class, 'Fpdi\\') === 0) {
|
||||
$filename = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 14)) . '.php';
|
||||
$fullpath = __DIR__ . DIRECTORY_SEPARATOR . $filename;
|
||||
if (is_file($fullpath)) {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once $fullpath;
|
||||
}
|
||||
}
|
||||
});
|
27
composer.json
Normal file
27
composer.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "library",
|
||||
"name": "wander/fpdi",
|
||||
"homepage": "https://gitee.com/wanders/fpdi.git",
|
||||
"description": "fpdf fpdi--PHP版",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "wander",
|
||||
"email": "13834563@qq.com"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"fpdf",
|
||||
"fpdi",
|
||||
"pdf"
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"ext-zlib": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fpdi\\": "Fpdi"
|
||||
}
|
||||
}
|
||||
}
|
14
include.php
Normal file
14
include.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
spl_autoload_register(function ($classname) {
|
||||
$pathname = __DIR__ . DIRECTORY_SEPARATOR;
|
||||
$filename = str_replace('\\', DIRECTORY_SEPARATOR, $classname) . '.php';
|
||||
if (file_exists($pathname . $filename)) {
|
||||
$prefix = 'Fpdi';
|
||||
if (stripos($classname, $prefix) === 0) {
|
||||
include $pathname . $filename;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user