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