MDL-49515 libraries: Update FPDI to 1.5.4
[moodle.git] / mod / assign / feedback / editpdf / fpdi / fpdi_pdf_parser.php
CommitLineData
5c386472
DW
1<?php\r
2//\r
858d457d 3// FPDI - Version 1.5.4\r
5c386472 4//\r
858d457d 5// Copyright 2004-2015 Setasign - Jan Slabon\r
5c386472
DW
6//\r
7// Licensed under the Apache License, Version 2.0 (the "License");\r
8// you may not use this file except in compliance with the License.\r
9// You may obtain a copy of the License at\r
10//\r
11// http://www.apache.org/licenses/LICENSE-2.0\r
12//\r
13// Unless required by applicable law or agreed to in writing, software\r
14// distributed under the License is distributed on an "AS IS" BASIS,\r
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16// See the License for the specific language governing permissions and\r
17// limitations under the License.\r
18//\r
19\r
858d457d
AG
20if (!class_exists('pdf_parser')) {\r
21 require_once('pdf_parser.php');\r
22}\r
5c386472 23\r
858d457d
AG
24/**\r
25 * Class fpdi_pdf_parser\r
26 */\r
27class fpdi_pdf_parser extends pdf_parser\r
28{\r
5c386472
DW
29 /**\r
30 * Pages\r
858d457d
AG
31 *\r
32 * Index begins at 0\r
5c386472
DW
33 *\r
34 * @var array\r
35 */\r
858d457d 36 protected $_pages;\r
5c386472
DW
37 \r
38 /**\r
39 * Page count\r
858d457d 40 *\r
5c386472
DW
41 * @var integer\r
42 */\r
858d457d 43 protected $_pageCount;\r
5c386472
DW
44 \r
45 /**\r
858d457d
AG
46 * Current page number\r
47 *\r
5c386472
DW
48 * @var integer\r
49 */\r
858d457d 50 public $pageNo;\r
5c386472
DW
51 \r
52 /**\r
858d457d
AG
53 * PDF version of imported document\r
54 *\r
5c386472
DW
55 * @var string\r
56 */\r
858d457d 57 public $_pdfVersion;\r
5c386472
DW
58 \r
59 /**\r
60 * Available BoxTypes\r
61 *\r
62 * @var array\r
63 */\r
858d457d 64 public $availableBoxes = array('/MediaBox', '/CropBox', '/BleedBox', '/TrimBox', '/ArtBox');\r
5c386472
DW
65 \r
66 /**\r
858d457d 67 * The constructor.\r
5c386472 68 *\r
858d457d 69 * @param string $filename The source filename\r
5c386472 70 */\r
858d457d
AG
71 public function __construct($filename)\r
72 {\r
73 parent::__construct($filename);\r
5c386472
DW
74\r
75 // resolve Pages-Dictonary\r
858d457d 76 $pages = $this->resolveObject($this->_root[1][1]['/Pages']);\r
5c386472
DW
77\r
78 // Read pages\r
858d457d 79 $this->_readPages($pages, $this->_pages);\r
5c386472
DW
80 \r
81 // count pages;\r
858d457d 82 $this->_pageCount = count($this->_pages);\r
5c386472
DW
83 }\r
84 \r
85 /**\r
858d457d 86 * Get page count from source file.\r
5c386472
DW
87 *\r
88 * @return int\r
89 */\r
858d457d
AG
90 public function getPageCount()\r
91 {\r
92 return $this->_pageCount;\r
5c386472
DW
93 }\r
94\r
5c386472 95 /**\r
858d457d 96 * Set the page number.\r
5c386472 97 *\r
858d457d
AG
98 * @param int $pageNo Page number to use\r
99 * @throws InvalidArgumentException\r
5c386472 100 */\r
858d457d
AG
101 public function setPageNo($pageNo)\r
102 {\r
103 $pageNo = ((int) $pageNo) - 1;\r
5c386472 104\r
858d457d
AG
105 if ($pageNo < 0 || $pageNo >= $this->getPageCount()) {\r
106 throw new InvalidArgumentException('Invalid page number!');\r
5c386472
DW
107 }\r
108\r
858d457d 109 $this->pageNo = $pageNo;\r
5c386472
DW
110 }\r
111 \r
112 /**\r
113 * Get page-resources from current page\r
114 *\r
858d457d 115 * @return array|boolean\r
5c386472 116 */\r
858d457d
AG
117 public function getPageResources()\r
118 {\r
119 return $this->_getPageResources($this->_pages[$this->pageNo]);\r
5c386472
DW
120 }\r
121 \r
122 /**\r
858d457d 123 * Get page-resources from a /Page dictionary.\r
5c386472
DW
124 *\r
125 * @param array $obj Array of pdf-data\r
858d457d 126 * @return array|boolean\r
5c386472 127 */\r
858d457d
AG
128 protected function _getPageResources($obj)\r
129 {\r
130 $obj = $this->resolveObject($obj);\r
5c386472
DW
131\r
132 // If the current object has a resources\r
133 // dictionary associated with it, we use\r
134 // it. Otherwise, we move back to its\r
135 // parent object.\r
858d457d
AG
136 if (isset($obj[1][1]['/Resources'])) {\r
137 $res = $this->resolveObject($obj[1][1]['/Resources']);\r
138 if ($res[0] == pdf_parser::TYPE_OBJECT)\r
5c386472
DW
139 return $res[1];\r
140 return $res;\r
5c386472 141 }\r
5c386472 142\r
858d457d
AG
143 if (!isset($obj[1][1]['/Parent'])) {\r
144 return false;\r
145 }\r
146\r
147 $res = $this->_getPageResources($obj[1][1]['/Parent']);\r
148 if ($res[0] == pdf_parser::TYPE_OBJECT)\r
149 return $res[1];\r
150 return $res;\r
151 }\r
5c386472
DW
152\r
153 /**\r
858d457d 154 * Get content of current page.\r
5c386472 155 *\r
858d457d 156 * If /Contents is an array, the streams are concatenated\r
5c386472
DW
157 *\r
158 * @return string\r
159 */\r
858d457d
AG
160 public function getContent()\r
161 {\r
5c386472
DW
162 $buffer = '';\r
163 \r
858d457d
AG
164 if (isset($this->_pages[$this->pageNo][1][1]['/Contents'])) {\r
165 $contents = $this->_getPageContent($this->_pages[$this->pageNo][1][1]['/Contents']);\r
166 foreach ($contents AS $tmpContent) {\r
167 $buffer .= $this->_unFilterStream($tmpContent) . ' ';\r
5c386472
DW
168 }\r
169 }\r
170 \r
171 return $buffer;\r
172 }\r
858d457d 173\r
5c386472 174 /**\r
858d457d 175 * Resolve all content objects.\r
5c386472 176 *\r
858d457d 177 * @param array $contentRef\r
5c386472
DW
178 * @return array\r
179 */\r
858d457d
AG
180 protected function _getPageContent($contentRef)\r
181 {\r
5c386472
DW
182 $contents = array();\r
183 \r
858d457d
AG
184 if ($contentRef[0] == pdf_parser::TYPE_OBJREF) {\r
185 $content = $this->resolveObject($contentRef);\r
186 if ($content[1][0] == pdf_parser::TYPE_ARRAY) {\r
5c386472
DW
187 $contents = $this->_getPageContent($content[1]);\r
188 } else {\r
189 $contents[] = $content;\r
190 }\r
858d457d
AG
191 } else if ($contentRef[0] == pdf_parser::TYPE_ARRAY) {\r
192 foreach ($contentRef[1] AS $tmp_content_ref) {\r
193 $contents = array_merge($contents, $this->_getPageContent($tmp_content_ref));\r
5c386472
DW
194 }\r
195 }\r
196\r
197 return $contents;\r
198 }\r
199\r
5c386472 200 /**\r
858d457d 201 * Get a boundary box from a page\r
5c386472 202 *\r
858d457d 203 * Array format is same as used by FPDF_TPL.\r
5c386472 204 *\r
858d457d
AG
205 * @param array $page a /Page dictionary\r
206 * @param string $boxIndex Type of box {see {@link $availableBoxes})\r
5c386472 207 * @param float Scale factor from user space units to points\r
858d457d
AG
208 *\r
209 * @return array|boolean\r
5c386472 210 */\r
858d457d
AG
211 protected function _getPageBox($page, $boxIndex, $k)\r
212 {\r
213 $page = $this->resolveObject($page);\r
5c386472 214 $box = null;\r
858d457d
AG
215 if (isset($page[1][1][$boxIndex])) {\r
216 $box = $page[1][1][$boxIndex];\r
217 }\r
5c386472 218 \r
858d457d
AG
219 if (!is_null($box) && $box[0] == pdf_parser::TYPE_OBJREF) {\r
220 $tmp_box = $this->resolveObject($box);\r
5c386472
DW
221 $box = $tmp_box[1];\r
222 }\r
223 \r
858d457d
AG
224 if (!is_null($box) && $box[0] == pdf_parser::TYPE_ARRAY) {\r
225 $b = $box[1];\r
226 return array(\r
227 'x' => $b[0][1] / $k,\r
228 'y' => $b[1][1] / $k,\r
229 'w' => abs($b[0][1] - $b[2][1]) / $k,\r
230 'h' => abs($b[1][1] - $b[3][1]) / $k,\r
231 'llx' => min($b[0][1], $b[2][1]) / $k,\r
232 'lly' => min($b[1][1], $b[3][1]) / $k,\r
233 'urx' => max($b[0][1], $b[2][1]) / $k,\r
234 'ury' => max($b[1][1], $b[3][1]) / $k,\r
235 );\r
236 } else if (!isset($page[1][1]['/Parent'])) {\r
5c386472
DW
237 return false;\r
238 } else {\r
858d457d 239 return $this->_getPageBox($this->resolveObject($page[1][1]['/Parent']), $boxIndex, $k);\r
5c386472
DW
240 }\r
241 }\r
242\r
243 /**\r
858d457d 244 * Get all page boundary boxes by page number\r
5c386472 245 * \r
858d457d
AG
246 * @param int $pageNo The page number\r
247 * @param float $k Scale factor from user space units to points\r
5c386472 248 * @return array\r
858d457d 249 * @throws InvalidArgumentException\r
5c386472 250 */\r
858d457d
AG
251 public function getPageBoxes($pageNo, $k)\r
252 {\r
253 if (!isset($this->_pages[$pageNo - 1])) {\r
254 throw new InvalidArgumentException('Page ' . $pageNo . ' does not exists.');\r
255 }\r
256\r
257 return $this->_getPageBoxes($this->_pages[$pageNo - 1], $k);\r
5c386472
DW
258 }\r
259 \r
260 /**\r
858d457d 261 * Get all boxes from /Page dictionary\r
5c386472 262 *\r
858d457d
AG
263 * @param array $page A /Page dictionary\r
264 * @param float $k Scale factor from user space units to points\r
5c386472
DW
265 * @return array\r
266 */\r
858d457d
AG
267 protected function _getPageBoxes($page, $k)\r
268 {\r
5c386472
DW
269 $boxes = array();\r
270\r
271 foreach($this->availableBoxes AS $box) {\r
858d457d 272 if ($_box = $this->_getPageBox($page, $box, $k)) {\r
5c386472
DW
273 $boxes[$box] = $_box;\r
274 }\r
275 }\r
276\r
277 return $boxes;\r
278 }\r
279\r
280 /**\r
858d457d 281 * Get the page rotation by page number\r
5c386472 282 *\r
858d457d
AG
283 * @param integer $pageNo\r
284 * @throws InvalidArgumentException\r
5c386472
DW
285 * @return array\r
286 */\r
858d457d
AG
287 public function getPageRotation($pageNo)\r
288 {\r
289 if (!isset($this->_pages[$pageNo - 1])) {\r
290 throw new InvalidArgumentException('Page ' . $pageNo . ' does not exists.');\r
291 }\r
292\r
293 return $this->_getPageRotation($this->_pages[$pageNo - 1]);\r
5c386472 294 }\r
858d457d
AG
295\r
296 /**\r
297 * Get the rotation value of a page\r
298 *\r
299 * @param array $obj A /Page dictionary\r
300 * @return array|bool\r
301 */\r
302 protected function _getPageRotation($obj)\r
303 {\r
304 $obj = $this->resolveObject($obj);\r
305 if (isset($obj[1][1]['/Rotate'])) {\r
306 $res = $this->resolveObject($obj[1][1]['/Rotate']);\r
307 if ($res[0] == pdf_parser::TYPE_OBJECT)\r
5c386472
DW
308 return $res[1];\r
309 return $res;\r
5c386472 310 }\r
858d457d
AG
311\r
312 if (!isset($obj[1][1]['/Parent'])) {\r
313 return false;\r
314 }\r
315\r
316 $res = $this->_getPageRotation($obj[1][1]['/Parent']);\r
317 if ($res[0] == pdf_parser::TYPE_OBJECT)\r
318 return $res[1];\r
319\r
320 return $res;\r
5c386472 321 }\r
858d457d 322\r
5c386472 323 /**\r
858d457d 324 * Read all pages\r
5c386472 325 *\r
858d457d
AG
326 * @param array $pages /Pages dictionary\r
327 * @param array $result The result array\r
328 * @throws Exception\r
5c386472 329 */\r
858d457d
AG
330 protected function _readPages(&$pages, &$result)\r
331 {\r
5c386472 332 // Get the kids dictionary\r
858d457d
AG
333 $_kids = $this->resolveObject($pages[1][1]['/Kids']);\r
334\r
335 if (!is_array($_kids)) {\r
336 throw new Exception('Cannot find /Kids in current /Page-Dictionary');\r
5c386472 337 }\r
858d457d
AG
338\r
339 if ($_kids[0] === self::TYPE_OBJECT) {\r
340 $_kids = $_kids[1];\r
341 }\r
342\r
343 $kids = $_kids[1];\r
344\r
5c386472 345 foreach ($kids as $v) {\r
858d457d 346 $pg = $this->resolveObject($v);\r
5c386472
DW
347 if ($pg[1][1]['/Type'][1] === '/Pages') {\r
348 // If one of the kids is an embedded\r
349 // /Pages array, resolve it as well.\r
858d457d 350 $this->_readPages($pg, $result);\r
5c386472
DW
351 } else {\r
352 $result[] = $pg;\r
353 }\r
354 }\r
355 }\r
858d457d 356}