1*dc4d9dc6SAnna Dabrowska<?php 2*dc4d9dc6SAnna Dabrowska/** 3*dc4d9dc6SAnna Dabrowska * This file is part of FPDI 4*dc4d9dc6SAnna Dabrowska * 5*dc4d9dc6SAnna Dabrowska * @package setasign\Fpdi 6*dc4d9dc6SAnna Dabrowska * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com) 7*dc4d9dc6SAnna Dabrowska * @license http://opensource.org/licenses/mit-license The MIT License 8*dc4d9dc6SAnna Dabrowska */ 9*dc4d9dc6SAnna Dabrowska 10*dc4d9dc6SAnna Dabrowskanamespace setasign\Fpdi\PdfReader; 11*dc4d9dc6SAnna Dabrowska 12*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Filter\FilterException; 13*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\PdfParser; 14*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\PdfParserException; 15*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfArray; 16*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfDictionary; 17*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfIndirectObject; 18*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfNull; 19*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfNumeric; 20*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfStream; 21*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfType; 22*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfTypeException; 23*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfReader\DataStructure\Rectangle; 24*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException; 25*dc4d9dc6SAnna Dabrowska 26*dc4d9dc6SAnna Dabrowska/** 27*dc4d9dc6SAnna Dabrowska * Class representing a page of a PDF document 28*dc4d9dc6SAnna Dabrowska * 29*dc4d9dc6SAnna Dabrowska * @package setasign\Fpdi\PdfReader 30*dc4d9dc6SAnna Dabrowska */ 31*dc4d9dc6SAnna Dabrowskaclass Page 32*dc4d9dc6SAnna Dabrowska{ 33*dc4d9dc6SAnna Dabrowska /** 34*dc4d9dc6SAnna Dabrowska * @var PdfIndirectObject 35*dc4d9dc6SAnna Dabrowska */ 36*dc4d9dc6SAnna Dabrowska protected $pageObject; 37*dc4d9dc6SAnna Dabrowska 38*dc4d9dc6SAnna Dabrowska /** 39*dc4d9dc6SAnna Dabrowska * @var PdfDictionary 40*dc4d9dc6SAnna Dabrowska */ 41*dc4d9dc6SAnna Dabrowska protected $pageDictionary; 42*dc4d9dc6SAnna Dabrowska 43*dc4d9dc6SAnna Dabrowska /** 44*dc4d9dc6SAnna Dabrowska * @var PdfParser 45*dc4d9dc6SAnna Dabrowska */ 46*dc4d9dc6SAnna Dabrowska protected $parser; 47*dc4d9dc6SAnna Dabrowska 48*dc4d9dc6SAnna Dabrowska /** 49*dc4d9dc6SAnna Dabrowska * Inherited attributes 50*dc4d9dc6SAnna Dabrowska * 51*dc4d9dc6SAnna Dabrowska * @var null|array 52*dc4d9dc6SAnna Dabrowska */ 53*dc4d9dc6SAnna Dabrowska protected $inheritedAttributes; 54*dc4d9dc6SAnna Dabrowska 55*dc4d9dc6SAnna Dabrowska /** 56*dc4d9dc6SAnna Dabrowska * Page constructor. 57*dc4d9dc6SAnna Dabrowska * 58*dc4d9dc6SAnna Dabrowska * @param PdfIndirectObject $page 59*dc4d9dc6SAnna Dabrowska * @param PdfParser $parser 60*dc4d9dc6SAnna Dabrowska */ 61*dc4d9dc6SAnna Dabrowska public function __construct(PdfIndirectObject $page, PdfParser $parser) 62*dc4d9dc6SAnna Dabrowska { 63*dc4d9dc6SAnna Dabrowska $this->pageObject = $page; 64*dc4d9dc6SAnna Dabrowska $this->parser = $parser; 65*dc4d9dc6SAnna Dabrowska } 66*dc4d9dc6SAnna Dabrowska 67*dc4d9dc6SAnna Dabrowska /** 68*dc4d9dc6SAnna Dabrowska * Get the indirect object of this page. 69*dc4d9dc6SAnna Dabrowska * 70*dc4d9dc6SAnna Dabrowska * @return PdfIndirectObject 71*dc4d9dc6SAnna Dabrowska */ 72*dc4d9dc6SAnna Dabrowska public function getPageObject() 73*dc4d9dc6SAnna Dabrowska { 74*dc4d9dc6SAnna Dabrowska return $this->pageObject; 75*dc4d9dc6SAnna Dabrowska } 76*dc4d9dc6SAnna Dabrowska 77*dc4d9dc6SAnna Dabrowska /** 78*dc4d9dc6SAnna Dabrowska * Get the dictionary of this page. 79*dc4d9dc6SAnna Dabrowska * 80*dc4d9dc6SAnna Dabrowska * @return PdfDictionary 81*dc4d9dc6SAnna Dabrowska * @throws PdfParserException 82*dc4d9dc6SAnna Dabrowska * @throws PdfTypeException 83*dc4d9dc6SAnna Dabrowska * @throws CrossReferenceException 84*dc4d9dc6SAnna Dabrowska */ 85*dc4d9dc6SAnna Dabrowska public function getPageDictionary() 86*dc4d9dc6SAnna Dabrowska { 87*dc4d9dc6SAnna Dabrowska if (null === $this->pageDictionary) { 88*dc4d9dc6SAnna Dabrowska $this->pageDictionary = PdfDictionary::ensure(PdfType::resolve($this->getPageObject(), $this->parser)); 89*dc4d9dc6SAnna Dabrowska } 90*dc4d9dc6SAnna Dabrowska 91*dc4d9dc6SAnna Dabrowska return $this->pageDictionary; 92*dc4d9dc6SAnna Dabrowska } 93*dc4d9dc6SAnna Dabrowska 94*dc4d9dc6SAnna Dabrowska /** 95*dc4d9dc6SAnna Dabrowska * Get a page attribute. 96*dc4d9dc6SAnna Dabrowska * 97*dc4d9dc6SAnna Dabrowska * @param string $name 98*dc4d9dc6SAnna Dabrowska * @param bool $inherited 99*dc4d9dc6SAnna Dabrowska * @return PdfType|null 100*dc4d9dc6SAnna Dabrowska * @throws PdfParserException 101*dc4d9dc6SAnna Dabrowska * @throws PdfTypeException 102*dc4d9dc6SAnna Dabrowska * @throws CrossReferenceException 103*dc4d9dc6SAnna Dabrowska */ 104*dc4d9dc6SAnna Dabrowska public function getAttribute($name, $inherited = true) 105*dc4d9dc6SAnna Dabrowska { 106*dc4d9dc6SAnna Dabrowska $dict = $this->getPageDictionary(); 107*dc4d9dc6SAnna Dabrowska 108*dc4d9dc6SAnna Dabrowska if (isset($dict->value[$name])) { 109*dc4d9dc6SAnna Dabrowska return $dict->value[$name]; 110*dc4d9dc6SAnna Dabrowska } 111*dc4d9dc6SAnna Dabrowska 112*dc4d9dc6SAnna Dabrowska $inheritedKeys = ['Resources', 'MediaBox', 'CropBox', 'Rotate']; 113*dc4d9dc6SAnna Dabrowska if ($inherited && \in_array($name, $inheritedKeys, true)) { 114*dc4d9dc6SAnna Dabrowska if ($this->inheritedAttributes === null) { 115*dc4d9dc6SAnna Dabrowska $this->inheritedAttributes = []; 116*dc4d9dc6SAnna Dabrowska $inheritedKeys = \array_filter($inheritedKeys, function ($key) use ($dict) { 117*dc4d9dc6SAnna Dabrowska return !isset($dict->value[$key]); 118*dc4d9dc6SAnna Dabrowska }); 119*dc4d9dc6SAnna Dabrowska 120*dc4d9dc6SAnna Dabrowska if (\count($inheritedKeys) > 0) { 121*dc4d9dc6SAnna Dabrowska $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser); 122*dc4d9dc6SAnna Dabrowska while ($parentDict instanceof PdfDictionary) { 123*dc4d9dc6SAnna Dabrowska foreach ($inheritedKeys as $index => $key) { 124*dc4d9dc6SAnna Dabrowska if (isset($parentDict->value[$key])) { 125*dc4d9dc6SAnna Dabrowska $this->inheritedAttributes[$key] = $parentDict->value[$key]; 126*dc4d9dc6SAnna Dabrowska unset($inheritedKeys[$index]); 127*dc4d9dc6SAnna Dabrowska } 128*dc4d9dc6SAnna Dabrowska } 129*dc4d9dc6SAnna Dabrowska 130*dc4d9dc6SAnna Dabrowska /** @noinspection NotOptimalIfConditionsInspection */ 131*dc4d9dc6SAnna Dabrowska if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) { 132*dc4d9dc6SAnna Dabrowska $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser); 133*dc4d9dc6SAnna Dabrowska } else { 134*dc4d9dc6SAnna Dabrowska break; 135*dc4d9dc6SAnna Dabrowska } 136*dc4d9dc6SAnna Dabrowska } 137*dc4d9dc6SAnna Dabrowska } 138*dc4d9dc6SAnna Dabrowska } 139*dc4d9dc6SAnna Dabrowska 140*dc4d9dc6SAnna Dabrowska if (isset($this->inheritedAttributes[$name])) { 141*dc4d9dc6SAnna Dabrowska return $this->inheritedAttributes[$name]; 142*dc4d9dc6SAnna Dabrowska } 143*dc4d9dc6SAnna Dabrowska } 144*dc4d9dc6SAnna Dabrowska 145*dc4d9dc6SAnna Dabrowska return null; 146*dc4d9dc6SAnna Dabrowska } 147*dc4d9dc6SAnna Dabrowska 148*dc4d9dc6SAnna Dabrowska /** 149*dc4d9dc6SAnna Dabrowska * Get the rotation value. 150*dc4d9dc6SAnna Dabrowska * 151*dc4d9dc6SAnna Dabrowska * @return int 152*dc4d9dc6SAnna Dabrowska * @throws PdfParserException 153*dc4d9dc6SAnna Dabrowska * @throws PdfTypeException 154*dc4d9dc6SAnna Dabrowska * @throws CrossReferenceException 155*dc4d9dc6SAnna Dabrowska */ 156*dc4d9dc6SAnna Dabrowska public function getRotation() 157*dc4d9dc6SAnna Dabrowska { 158*dc4d9dc6SAnna Dabrowska $rotation = $this->getAttribute('Rotate'); 159*dc4d9dc6SAnna Dabrowska if (null === $rotation) { 160*dc4d9dc6SAnna Dabrowska return 0; 161*dc4d9dc6SAnna Dabrowska } 162*dc4d9dc6SAnna Dabrowska 163*dc4d9dc6SAnna Dabrowska $rotation = PdfNumeric::ensure(PdfType::resolve($rotation, $this->parser))->value % 360; 164*dc4d9dc6SAnna Dabrowska 165*dc4d9dc6SAnna Dabrowska if ($rotation < 0) { 166*dc4d9dc6SAnna Dabrowska $rotation += 360; 167*dc4d9dc6SAnna Dabrowska } 168*dc4d9dc6SAnna Dabrowska 169*dc4d9dc6SAnna Dabrowska return $rotation; 170*dc4d9dc6SAnna Dabrowska } 171*dc4d9dc6SAnna Dabrowska 172*dc4d9dc6SAnna Dabrowska /** 173*dc4d9dc6SAnna Dabrowska * Get a boundary of this page. 174*dc4d9dc6SAnna Dabrowska * 175*dc4d9dc6SAnna Dabrowska * @param string $box 176*dc4d9dc6SAnna Dabrowska * @param bool $fallback 177*dc4d9dc6SAnna Dabrowska * @return bool|Rectangle 178*dc4d9dc6SAnna Dabrowska * @throws PdfParserException 179*dc4d9dc6SAnna Dabrowska * @throws PdfTypeException 180*dc4d9dc6SAnna Dabrowska * @throws CrossReferenceException 181*dc4d9dc6SAnna Dabrowska * @see PageBoundaries 182*dc4d9dc6SAnna Dabrowska */ 183*dc4d9dc6SAnna Dabrowska public function getBoundary($box = PageBoundaries::CROP_BOX, $fallback = true) 184*dc4d9dc6SAnna Dabrowska { 185*dc4d9dc6SAnna Dabrowska $value = $this->getAttribute($box); 186*dc4d9dc6SAnna Dabrowska 187*dc4d9dc6SAnna Dabrowska if ($value !== null) { 188*dc4d9dc6SAnna Dabrowska return Rectangle::byPdfArray($value, $this->parser); 189*dc4d9dc6SAnna Dabrowska } 190*dc4d9dc6SAnna Dabrowska 191*dc4d9dc6SAnna Dabrowska if ($fallback === false) { 192*dc4d9dc6SAnna Dabrowska return false; 193*dc4d9dc6SAnna Dabrowska } 194*dc4d9dc6SAnna Dabrowska 195*dc4d9dc6SAnna Dabrowska switch ($box) { 196*dc4d9dc6SAnna Dabrowska case PageBoundaries::BLEED_BOX: 197*dc4d9dc6SAnna Dabrowska case PageBoundaries::TRIM_BOX: 198*dc4d9dc6SAnna Dabrowska case PageBoundaries::ART_BOX: 199*dc4d9dc6SAnna Dabrowska return $this->getBoundary(PageBoundaries::CROP_BOX, true); 200*dc4d9dc6SAnna Dabrowska case PageBoundaries::CROP_BOX: 201*dc4d9dc6SAnna Dabrowska return $this->getBoundary(PageBoundaries::MEDIA_BOX, true); 202*dc4d9dc6SAnna Dabrowska } 203*dc4d9dc6SAnna Dabrowska 204*dc4d9dc6SAnna Dabrowska return false; 205*dc4d9dc6SAnna Dabrowska } 206*dc4d9dc6SAnna Dabrowska 207*dc4d9dc6SAnna Dabrowska /** 208*dc4d9dc6SAnna Dabrowska * Get the width and height of this page. 209*dc4d9dc6SAnna Dabrowska * 210*dc4d9dc6SAnna Dabrowska * @param string $box 211*dc4d9dc6SAnna Dabrowska * @param bool $fallback 212*dc4d9dc6SAnna Dabrowska * @return array|bool 213*dc4d9dc6SAnna Dabrowska * @throws PdfParserException 214*dc4d9dc6SAnna Dabrowska * @throws PdfTypeException 215*dc4d9dc6SAnna Dabrowska * @throws CrossReferenceException 216*dc4d9dc6SAnna Dabrowska */ 217*dc4d9dc6SAnna Dabrowska public function getWidthAndHeight($box = PageBoundaries::CROP_BOX, $fallback = true) 218*dc4d9dc6SAnna Dabrowska { 219*dc4d9dc6SAnna Dabrowska $boundary = $this->getBoundary($box, $fallback); 220*dc4d9dc6SAnna Dabrowska if ($boundary === false) { 221*dc4d9dc6SAnna Dabrowska return false; 222*dc4d9dc6SAnna Dabrowska } 223*dc4d9dc6SAnna Dabrowska 224*dc4d9dc6SAnna Dabrowska $rotation = $this->getRotation(); 225*dc4d9dc6SAnna Dabrowska $interchange = ($rotation / 90) % 2; 226*dc4d9dc6SAnna Dabrowska 227*dc4d9dc6SAnna Dabrowska return [ 228*dc4d9dc6SAnna Dabrowska $interchange ? $boundary->getHeight() : $boundary->getWidth(), 229*dc4d9dc6SAnna Dabrowska $interchange ? $boundary->getWidth() : $boundary->getHeight() 230*dc4d9dc6SAnna Dabrowska ]; 231*dc4d9dc6SAnna Dabrowska } 232*dc4d9dc6SAnna Dabrowska 233*dc4d9dc6SAnna Dabrowska /** 234*dc4d9dc6SAnna Dabrowska * Get the raw content stream. 235*dc4d9dc6SAnna Dabrowska * 236*dc4d9dc6SAnna Dabrowska * @return string 237*dc4d9dc6SAnna Dabrowska * @throws PdfReaderException 238*dc4d9dc6SAnna Dabrowska * @throws PdfTypeException 239*dc4d9dc6SAnna Dabrowska * @throws FilterException 240*dc4d9dc6SAnna Dabrowska * @throws PdfParserException 241*dc4d9dc6SAnna Dabrowska */ 242*dc4d9dc6SAnna Dabrowska public function getContentStream() 243*dc4d9dc6SAnna Dabrowska { 244*dc4d9dc6SAnna Dabrowska $dict = $this->getPageDictionary(); 245*dc4d9dc6SAnna Dabrowska $contents = PdfType::resolve(PdfDictionary::get($dict, 'Contents'), $this->parser); 246*dc4d9dc6SAnna Dabrowska if ($contents instanceof PdfNull) { 247*dc4d9dc6SAnna Dabrowska return ''; 248*dc4d9dc6SAnna Dabrowska } 249*dc4d9dc6SAnna Dabrowska 250*dc4d9dc6SAnna Dabrowska if ($contents instanceof PdfArray) { 251*dc4d9dc6SAnna Dabrowska $result = []; 252*dc4d9dc6SAnna Dabrowska foreach ($contents->value as $content) { 253*dc4d9dc6SAnna Dabrowska $content = PdfType::resolve($content, $this->parser); 254*dc4d9dc6SAnna Dabrowska if (!($content instanceof PdfStream)) { 255*dc4d9dc6SAnna Dabrowska continue; 256*dc4d9dc6SAnna Dabrowska } 257*dc4d9dc6SAnna Dabrowska $result[] = $content->getUnfilteredStream(); 258*dc4d9dc6SAnna Dabrowska } 259*dc4d9dc6SAnna Dabrowska 260*dc4d9dc6SAnna Dabrowska return \implode("\n", $result); 261*dc4d9dc6SAnna Dabrowska } 262*dc4d9dc6SAnna Dabrowska 263*dc4d9dc6SAnna Dabrowska if ($contents instanceof PdfStream) { 264*dc4d9dc6SAnna Dabrowska return $contents->getUnfilteredStream(); 265*dc4d9dc6SAnna Dabrowska } 266*dc4d9dc6SAnna Dabrowska 267*dc4d9dc6SAnna Dabrowska throw new PdfReaderException( 268*dc4d9dc6SAnna Dabrowska 'Array or stream expected.', 269*dc4d9dc6SAnna Dabrowska PdfReaderException::UNEXPECTED_DATA_TYPE 270*dc4d9dc6SAnna Dabrowska ); 271*dc4d9dc6SAnna Dabrowska } 272*dc4d9dc6SAnna Dabrowska} 273