Page.php 7.4 KB

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