Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
//============================================================+
3
// File name   : tcpdf_static.php
4
// Version     : 1.1.4
5
// Begin       : 2002-08-03
6
// Last Update : 2023-09-06
7
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8
// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9
// -------------------------------------------------------------------
10
// Copyright (C) 2002-2023 Nicola Asuni - Tecnick.com LTD
11
//
12
// This file is part of TCPDF software library.
13
//
14
// TCPDF is free software: you can redistribute it and/or modify it
15
// under the terms of the GNU Lesser General Public License as
16
// published by the Free Software Foundation, either version 3 of the
17
// License, or (at your option) any later version.
18
//
19
// TCPDF is distributed in the hope that it will be useful, but
20
// WITHOUT ANY WARRANTY; without even the implied warranty of
21
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
// See the GNU Lesser General Public License for more details.
23
//
24
// You should have received a copy of the License
25
// along with TCPDF. If not, see
26
// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27
//
28
// See LICENSE.TXT file for more information.
29
// -------------------------------------------------------------------
30
//
31
// Description :
32
//   Static methods used by the TCPDF class.
33
//
34
//============================================================+
35
 
36
/**
37
 * @file
38
 * This is a PHP class that contains static methods for the TCPDF class.<br>
39
 * @package com.tecnick.tcpdf
40
 * @author Nicola Asuni
41
 * @version 1.1.2
42
 */
43
 
44
/**
45
 * @class TCPDF_STATIC
46
 * Static methods used by the TCPDF class.
47
 * @package com.tecnick.tcpdf
48
 * @brief PHP class for generating PDF documents without requiring external extensions.
49
 * @version 1.1.1
50
 * @author Nicola Asuni - info@tecnick.com
51
 */
52
class TCPDF_STATIC {
53
 
54
	/**
55
	 * Current TCPDF version.
56
	 * @private static
57
	 */
58
	private static $tcpdf_version = '6.6.5';
59
 
60
	/**
61
	 * String alias for total number of pages.
62
	 * @public static
63
	 */
64
	public static $alias_tot_pages = '{:ptp:}';
65
 
66
	/**
67
	 * String alias for page number.
68
	 * @public static
69
	 */
70
	public static $alias_num_page = '{:pnp:}';
71
 
72
	/**
73
	 * String alias for total number of pages in a single group.
74
	 * @public static
75
	 */
76
	public static $alias_group_tot_pages = '{:ptg:}';
77
 
78
	/**
79
	 * String alias for group page number.
80
	 * @public static
81
	 */
82
	public static $alias_group_num_page = '{:png:}';
83
 
84
	/**
85
	 * String alias for right shift compensation used to correctly align page numbers on the right.
86
	 * @public static
87
	 */
88
	public static $alias_right_shift = '{rsc:';
89
 
90
	/**
91
	 * Encryption padding string.
92
	 * @public static
93
	 */
94
	public static $enc_padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
95
 
96
	/**
97
	 * ByteRange placemark used during digital signature process.
98
	 * @since 4.6.028 (2009-08-25)
99
	 * @public static
100
	 */
101
	public static $byterange_string = '/ByteRange[0 ********** ********** **********]';
102
 
103
	/**
104
	 * Array page boxes names
105
	 * @public static
106
	 */
107
	public static $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
108
 
109
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
110
 
111
	/**
112
	 * Return the current TCPDF version.
113
	 * @return string TCPDF version string
114
	 * @since 5.9.012 (2010-11-10)
115
	 * @public static
116
	 */
117
	public static function getTCPDFVersion() {
118
		return self::$tcpdf_version;
119
	}
120
 
121
	/**
122
	 * Return the current TCPDF producer.
123
	 * @return string TCPDF producer string
124
	 * @since 6.0.000 (2013-03-16)
125
	 * @public static
126
	 */
127
	public static function getTCPDFProducer() {
128
		return "\x54\x43\x50\x44\x46\x20\x28\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
129
	}
130
 
131
	/**
132
	 * Check if the URL exist.
133
	 * @param string $url URL to check.
134
	 * @return boolean true if the URl exist, false otherwise.
135
	 * @since 5.9.204 (2013-01-28)
136
	 * @public static
137
	 */
138
	public static function isValidURL($url) {
139
		$headers = @get_headers($url);
140
		if ($headers === false) {
141
			return false;
142
		}
143
    	return (strpos($headers[0], '200') !== false);
144
	}
145
 
146
	/**
147
	 * Removes SHY characters from text.
148
	 * Unicode Data:<ul>
149
	 * <li>Name : SOFT HYPHEN, commonly abbreviated as SHY</li>
150
	 * <li>HTML Entity (decimal): "&amp;#173;"</li>
151
	 * <li>HTML Entity (hex): "&amp;#xad;"</li>
152
	 * <li>HTML Entity (named): "&amp;shy;"</li>
153
	 * <li>How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173]</li>
154
	 * <li>UTF-8 (hex): 0xC2 0xAD (c2ad)</li>
155
	 * <li>UTF-8 character: chr(194).chr(173)</li>
156
	 * </ul>
157
	 * @param string $txt input string
158
	 * @param boolean $unicode True if we are in unicode mode, false otherwise.
159
	 * @return string without SHY characters.
160
	 * @since (4.5.019) 2009-02-28
161
	 * @public static
162
	 */
163
	public static function removeSHY($txt='', $unicode=true) {
164
		$txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
165
		if (!$unicode) {
166
			$txt = preg_replace('/([\\xad]{1})/', '', $txt);
167
		}
168
		return $txt;
169
	}
170
 
171
 
172
	/**
173
	 * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages)
174
	 * @param string|array|int $brd Indicates if borders must be drawn around the cell block. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
175
	 * @param string $position multicell position: 'start', 'middle', 'end'
176
	 * @param boolean $opencell True when the cell is left open at the page bottom, false otherwise.
177
	 * @return array border mode array
178
	 * @since 4.4.002 (2008-12-09)
179
	 * @public static
180
	 */
181
	public static function getBorderMode($brd, $position='start', $opencell=true) {
182
		if ((!$opencell) OR empty($brd)) {
183
			return $brd;
184
		}
185
		if ($brd == 1) {
186
			$brd = 'LTRB';
187
		}
188
		if (is_string($brd)) {
189
			// convert string to array
190
			$slen = strlen($brd);
191
			$newbrd = array();
192
			for ($i = 0; $i < $slen; ++$i) {
193
				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
194
			}
195
			$brd = $newbrd;
196
		}
197
		foreach ($brd as $border => $style) {
198
			switch ($position) {
199
				case 'start': {
200
					if (strpos($border, 'B') !== false) {
201
						// remove bottom line
202
						$newkey = str_replace('B', '', $border);
203
						if (strlen($newkey) > 0) {
204
							$brd[$newkey] = $style;
205
						}
206
						unset($brd[$border]);
207
					}
208
					break;
209
				}
210
				case 'middle': {
211
					if (strpos($border, 'B') !== false) {
212
						// remove bottom line
213
						$newkey = str_replace('B', '', $border);
214
						if (strlen($newkey) > 0) {
215
							$brd[$newkey] = $style;
216
						}
217
						unset($brd[$border]);
218
						$border = $newkey;
219
					}
220
					if (strpos($border, 'T') !== false) {
221
						// remove bottom line
222
						$newkey = str_replace('T', '', $border);
223
						if (strlen($newkey) > 0) {
224
							$brd[$newkey] = $style;
225
						}
226
						unset($brd[$border]);
227
					}
228
					break;
229
				}
230
				case 'end': {
231
					if (strpos($border, 'T') !== false) {
232
						// remove bottom line
233
						$newkey = str_replace('T', '', $border);
234
						if (strlen($newkey) > 0) {
235
							$brd[$newkey] = $style;
236
						}
237
						unset($brd[$border]);
238
					}
239
					break;
240
				}
241
			}
242
		}
243
		return $brd;
244
	}
245
 
246
	/**
247
	 * Determine whether a string is empty.
248
	 * @param string $str string to be checked
249
	 * @return bool true if string is empty
250
	 * @since 4.5.044 (2009-04-16)
251
	 * @public static
252
	 */
253
	public static function empty_string($str) {
254
		return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
255
	}
256
 
257
	/**
258
	 * Returns a temporary filename for caching object on filesystem.
259
	 * @param string $type Type of file (name of the subdir on the tcpdf cache folder).
260
	 * @param string $file_id TCPDF file_id.
261
	 * @return string filename.
262
	 * @since 4.5.000 (2008-12-31)
263
	 * @public static
264
	 */
265
	public static function getObjFilename($type='tmp', $file_id='') {
266
		return tempnam(K_PATH_CACHE, '__tcpdf_'.$file_id.'_'.$type.'_'.md5(TCPDF_STATIC::getRandomSeed()).'_');
267
	}
268
 
269
	/**
270
	 * Add "\" before "\", "(" and ")"
271
	 * @param string $s string to escape.
272
	 * @return string escaped string.
273
	 * @public static
274
	 */
275
	public static function _escape($s) {
276
		// the chr(13) substitution fixes the Bugs item #1421290.
277
		return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
278
	}
279
 
280
	/**
281
	* Escape some special characters (&lt; &gt; &amp;) for XML output.
282
	* @param string $str Input string to convert.
283
	* @return string converted string
284
	* @since 5.9.121 (2011-09-28)
285
	 * @public static
286
	 */
287
	public static function _escapeXML($str) {
288
		$replaceTable = array("\0" => '', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;');
289
		$str = strtr($str === null ? '' : $str, $replaceTable);
290
		return $str;
291
	}
292
 
293
	/**
294
	 * Creates a copy of a class object
295
	 * @param object $object class object to be cloned
296
	 * @return object cloned object
297
	 * @since 4.5.029 (2009-03-19)
298
	 * @public static
299
	 */
300
	public static function objclone($object) {
301
		if (($object instanceof Imagick) AND (version_compare(phpversion('imagick'), '3.0.1') !== 1)) {
302
			// on the versions after 3.0.1 the clone() method was deprecated in favour of clone keyword
303
			return @$object->clone();
304
		}
305
		return @clone($object);
306
	}
307
 
308
	/**
309
	 * Output input data and compress it if possible.
310
	 * @param string $data Data to output.
311
	 * @param int $length Data length in bytes.
312
	 * @since 5.9.086
313
	 * @public static
314
	 */
315
	public static function sendOutputData($data, $length) {
316
		if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) OR empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
317
			// the content length may vary if the server is using compression
318
			header('Content-Length: '.$length);
319
		}
320
		echo $data;
321
	}
322
 
323
	/**
324
	 * Replace page number aliases with number.
325
	 * @param string $page Page content.
326
	 * @param array $replace Array of replacements (array keys are replacement strings, values are alias arrays).
327
	 * @param int $diff If passed, this will be set to the total char number difference between alias and replacements.
328
	 * @return array replaced page content and updated $diff parameter as array.
329
	 * @public static
330
	 */
331
	public static function replacePageNumAliases($page, $replace, $diff=0) {
332
		foreach ($replace as $rep) {
333
			foreach ($rep[3] as $a) {
334
				if (strpos($page, $a) !== false) {
335
					$page = str_replace($a, $rep[0], $page);
336
					$diff += ($rep[2] - $rep[1]);
337
				}
338
			}
339
		}
340
		return array($page, $diff);
341
	}
342
 
343
	/**
344
	 * Returns timestamp in seconds from formatted date-time.
345
	 * @param string $date Formatted date-time.
346
	 * @return int seconds.
347
	 * @since 5.9.152 (2012-03-23)
348
	 * @public static
349
	 */
350
	public static function getTimestamp($date) {
351
		if (($date[0] == 'D') AND ($date[1] == ':')) {
352
			// remove date prefix if present
353
			$date = substr($date, 2);
354
		}
355
		return strtotime($date);
356
	}
357
 
358
	/**
359
	 * Returns a formatted date-time.
360
	 * @param int $time Time in seconds.
361
	 * @return string escaped date string.
362
	 * @since 5.9.152 (2012-03-23)
363
	 * @public static
364
	 */
365
	public static function getFormattedDate($time) {
366
		return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0).'\'';
367
	}
368
 
369
	/**
370
	 * Returns a string containing random data to be used as a seed for encryption methods.
371
	 * @param string $seed starting seed value
372
	 * @return string containing random data
373
	 * @author Nicola Asuni
374
	 * @since 5.9.006 (2010-10-19)
375
	 * @public static
376
	 */
377
	public static function getRandomSeed($seed='') {
378
		$rnd = uniqid(rand().microtime(true), true);
379
		if (function_exists('posix_getpid')) {
380
			$rnd .= posix_getpid();
381
		}
382
		if (function_exists('openssl_random_pseudo_bytes') AND (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
383
			// this is not used on windows systems because it is very slow for a know bug
384
			$rnd .= openssl_random_pseudo_bytes(512);
385
		} else {
386
			for ($i = 0; $i < 23; ++$i) {
387
				$rnd .= uniqid('', true);
388
			}
389
		}
390
		return $rnd.$seed.__FILE__.serialize($_SERVER).microtime(true);
391
	}
392
 
393
	/**
394
	 * Encrypts a string using MD5 and returns it's value as a binary string.
395
	 * @param string $str input string
396
	 * @return string MD5 encrypted binary string
397
	 * @since 2.0.000 (2008-01-02)
398
	 * @public static
399
	 */
400
	public static function _md5_16($str) {
401
		return pack('H*', md5($str));
402
	}
403
 
404
	/**
405
	 * Returns the input text encrypted using AES algorithm and the specified key.
406
	 * This method requires openssl or mcrypt. Text is padded to 16bytes blocks
407
	 * @param string $key encryption key
408
	 * @param string $text input text to be encrypted
409
	 * @return string encrypted text
410
	 * @author Nicola Asuni
411
	 * @since 5.0.005 (2010-05-11)
412
	 * @public static
413
	 */
414
	public static function _AES($key, $text) {
415
		// padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
416
		$padding = 16 - (strlen($text) % 16);
417
		$text .= str_repeat(chr($padding), $padding);
418
		if (extension_loaded('openssl')) {
419
			$algo = 'aes-256-cbc';
420
			if (strlen($key) == 16) {
421
				$algo = 'aes-128-cbc';
422
			}
423
			$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($algo));
424
			$text = openssl_encrypt($text, $algo, $key, OPENSSL_RAW_DATA, $iv);
425
			return $iv.substr($text, 0, -16);
426
		}
427
		$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
428
		$text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
429
		$text = $iv.$text;
430
		return $text;
431
	}
432
 
433
	/**
434
	 * Returns the input text encrypted using AES algorithm and the specified key.
435
	 * This method requires openssl or mcrypt. Text is not padded
436
	 * @param string $key encryption key
437
	 * @param string $text input text to be encrypted
438
	 * @return string encrypted text
439
	 * @author Nicola Asuni
440
	 * @since TODO
441
	 * @public static
442
	 */
443
	public static function _AESnopad($key, $text) {
444
		if (extension_loaded('openssl')) {
445
			$algo = 'aes-256-cbc';
446
			if (strlen($key) == 16) {
447
				$algo = 'aes-128-cbc';
448
			}
449
			$iv = str_repeat("\x00", openssl_cipher_iv_length($algo));
450
			$text = openssl_encrypt($text, $algo, $key, OPENSSL_RAW_DATA, $iv);
451
			return substr($text, 0, -16);
452
		}
453
		$iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
454
		$text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
455
		return $text;
456
	}
457
 
458
	/**
459
	 * Returns the input text encrypted using RC4 algorithm and the specified key.
460
	 * RC4 is the standard encryption algorithm used in PDF format
461
	 * @param string $key Encryption key.
462
	 * @param string $text Input text to be encrypted.
463
	 * @param string $last_enc_key Reference to last RC4 key encrypted.
464
	 * @param string $last_enc_key_c Reference to last RC4 computed key.
465
	 * @return string encrypted text
466
	 * @since 2.0.000 (2008-01-02)
467
	 * @author Klemen Vodopivec, Nicola Asuni
468
	 * @public static
469
	 */
470
	public static function _RC4($key, $text, &$last_enc_key, &$last_enc_key_c) {
471
		if (function_exists('mcrypt_encrypt') AND ($out = @mcrypt_encrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
472
			// try to use mcrypt function if exist
473
			return $out;
474
		}
475
		if ($last_enc_key != $key) {
476
			$k = str_repeat($key, (int) ((256 / strlen($key)) + 1));
477
			$rc4 = range(0, 255);
478
			$j = 0;
479
			for ($i = 0; $i < 256; ++$i) {
480
				$t = $rc4[$i];
481
				$j = ($j + $t + ord($k[$i])) % 256;
482
				$rc4[$i] = $rc4[$j];
483
				$rc4[$j] = $t;
484
			}
485
			$last_enc_key = $key;
486
			$last_enc_key_c = $rc4;
487
		} else {
488
			$rc4 = $last_enc_key_c;
489
		}
490
		$len = strlen($text);
491
		$a = 0;
492
		$b = 0;
493
		$out = '';
494
		for ($i = 0; $i < $len; ++$i) {
495
			$a = ($a + 1) % 256;
496
			$t = $rc4[$a];
497
			$b = ($b + $t) % 256;
498
			$rc4[$a] = $rc4[$b];
499
			$rc4[$b] = $t;
500
			$k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
501
			$out .= chr(ord($text[$i]) ^ $k);
502
		}
503
		return $out;
504
	}
505
 
506
	/**
507
	 * Return the permission code used on encryption (P value).
508
	 * @param array $permissions the set of permissions (specify the ones you want to block).
509
	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
510
	 * @since 5.0.005 (2010-05-12)
511
	 * @author Nicola Asuni
512
	 * @public static
513
	 */
514
	public static function getUserPermissionCode($permissions, $mode=0) {
515
		$options = array(
516
			'owner' => 2, // bit 2 -- inverted logic: cleared by default
517
			'print' => 4, // bit 3
518
			'modify' => 8, // bit 4
519
			'copy' => 16, // bit 5
520
			'annot-forms' => 32, // bit 6
521
			'fill-forms' => 256, // bit 9
522
			'extract' => 512, // bit 10
523
			'assemble' => 1024,// bit 11
524
			'print-high' => 2048 // bit 12
525
			);
526
		$protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
527
		foreach ($permissions as $permission) {
528
			if (isset($options[$permission])) {
529
				if (($mode > 0) OR ($options[$permission] <= 32)) {
530
					// set only valid permissions
531
					if ($options[$permission] == 2) {
532
						// the logic for bit 2 is inverted (cleared by default)
533
						$protection += $options[$permission];
534
					} else {
535
						$protection -= $options[$permission];
536
					}
537
				}
538
			}
539
		}
540
		return $protection;
541
	}
542
 
543
	/**
544
	 * Convert hexadecimal string to string
545
	 * @param string $bs byte-string to convert
546
	 * @return string
547
	 * @since 5.0.005 (2010-05-12)
548
	 * @author Nicola Asuni
549
	 * @public static
550
	 */
551
	public static function convertHexStringToString($bs) {
552
		$string = ''; // string to be returned
553
		$bslength = strlen($bs);
554
		if (($bslength % 2) != 0) {
555
			// padding
556
			$bs .= '0';
557
			++$bslength;
558
		}
559
		for ($i = 0; $i < $bslength; $i += 2) {
560
			$string .= chr(hexdec($bs[$i].$bs[($i + 1)]));
561
		}
562
		return $string;
563
	}
564
 
565
	/**
566
	 * Convert string to hexadecimal string (byte string)
567
	 * @param string $s string to convert
568
	 * @return string byte string
569
	 * @since 5.0.010 (2010-05-17)
570
	 * @author Nicola Asuni
571
	 * @public static
572
	 */
573
	public static function convertStringToHexString($s) {
574
		$bs = '';
575
		$chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
576
		foreach ($chars as $c) {
577
			$bs .= sprintf('%02s', dechex(ord($c)));
578
		}
579
		return $bs;
580
	}
581
 
582
	/**
583
	 * Convert encryption P value to a string of bytes, low-order byte first.
584
	 * @param string $protection 32bit encryption permission value (P value)
585
	 * @return string
586
	 * @since 5.0.005 (2010-05-12)
587
	 * @author Nicola Asuni
588
	 * @public static
589
	 */
590
	public static function getEncPermissionsString($protection) {
591
		$binprot = sprintf('%032b', $protection);
592
		$str = chr(bindec(substr($binprot, 24, 8)));
593
		$str .= chr(bindec(substr($binprot, 16, 8)));
594
		$str .= chr(bindec(substr($binprot, 8, 8)));
595
		$str .= chr(bindec(substr($binprot, 0, 8)));
596
		return $str;
597
	}
598
 
599
	/**
600
	 * Encode a name object.
601
	 * @param string $name Name object to encode.
602
	 * @return string Encoded name object.
603
	 * @author Nicola Asuni
604
	 * @since 5.9.097 (2011-06-23)
605
	 * @public static
606
	 */
607
	public static function encodeNameObject($name) {
608
		$escname = '';
609
		$length = strlen($name);
610
		for ($i = 0; $i < $length; ++$i) {
611
			$chr = $name[$i];
612
			if (preg_match('/[0-9a-zA-Z#_=-]/', $chr) == 1) {
613
				$escname .= $chr;
614
			} else {
615
				$escname .= sprintf('#%02X', ord($chr));
616
			}
617
		}
618
		return $escname;
619
	}
620
 
621
	/**
622
	 * Convert JavaScript form fields properties array to Annotation Properties array.
623
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
624
	 * @param array $spot_colors Reference to spot colors array.
625
	 * @param boolean $rtl True if in Right-To-Left text direction mode, false otherwise.
626
	 * @return array of annotation properties
627
	 * @author Nicola Asuni
628
	 * @since 4.8.000 (2009-09-06)
629
	 * @public static
630
	 */
631
	public static function getAnnotOptFromJSProp($prop, &$spot_colors, $rtl=false) {
632
		if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
633
			// the annotation options are already defined
634
			return $prop['aopt'];
635
		}
636
		$opt = array(); // value to be returned
637
		// alignment: Controls how the text is laid out within the text field.
638
		if (isset($prop['alignment'])) {
639
			switch ($prop['alignment']) {
640
				case 'left': {
641
					$opt['q'] = 0;
642
					break;
643
				}
644
				case 'center': {
645
					$opt['q'] = 1;
646
					break;
647
				}
648
				case 'right': {
649
					$opt['q'] = 2;
650
					break;
651
				}
652
				default: {
653
					$opt['q'] = ($rtl)?2:0;
654
					break;
655
				}
656
			}
657
		}
658
		// lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
659
		if (isset($prop['lineWidth'])) {
660
			$linewidth = intval($prop['lineWidth']);
661
		} else {
662
			$linewidth = 1;
663
		}
664
		// borderStyle: The border style for a field.
665
		if (isset($prop['borderStyle'])) {
666
			switch ($prop['borderStyle']) {
667
				case 'border.d':
668
				case 'dashed': {
669
					$opt['border'] = array(0, 0, $linewidth, array(3, 2));
670
					$opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
671
					break;
672
				}
673
				case 'border.b':
674
				case 'beveled': {
675
					$opt['border'] = array(0, 0, $linewidth);
676
					$opt['bs'] = array('w'=>$linewidth, 's'=>'B');
677
					break;
678
				}
679
				case 'border.i':
680
				case 'inset': {
681
					$opt['border'] = array(0, 0, $linewidth);
682
					$opt['bs'] = array('w'=>$linewidth, 's'=>'I');
683
					break;
684
				}
685
				case 'border.u':
686
				case 'underline': {
687
					$opt['border'] = array(0, 0, $linewidth);
688
					$opt['bs'] = array('w'=>$linewidth, 's'=>'U');
689
					break;
690
				}
691
				case 'border.s':
692
				case 'solid': {
693
					$opt['border'] = array(0, 0, $linewidth);
694
					$opt['bs'] = array('w'=>$linewidth, 's'=>'S');
695
					break;
696
				}
697
				default: {
698
					break;
699
				}
700
			}
701
		}
702
		if (isset($prop['border']) AND is_array($prop['border'])) {
703
			$opt['border'] = $prop['border'];
704
		}
705
		if (!isset($opt['mk'])) {
706
			$opt['mk'] = array();
707
		}
708
		if (!isset($opt['mk']['if'])) {
709
			$opt['mk']['if'] = array();
710
		}
711
		$opt['mk']['if']['a'] = array(0.5, 0.5);
712
		// buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
713
		if (isset($prop['buttonAlignX'])) {
714
			$opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
715
		}
716
		// buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
717
		if (isset($prop['buttonAlignY'])) {
718
			$opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
719
		}
720
		// buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
721
		if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
722
			$opt['mk']['if']['fb'] = true;
723
		}
724
		// buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
725
		if (isset($prop['buttonScaleHow'])) {
726
			switch ($prop['buttonScaleHow']) {
727
				case 'scaleHow.proportional': {
728
					$opt['mk']['if']['s'] = 'P';
729
					break;
730
				}
731
				case 'scaleHow.anamorphic': {
732
					$opt['mk']['if']['s'] = 'A';
733
					break;
734
				}
735
			}
736
		}
737
		// buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
738
		if (isset($prop['buttonScaleWhen'])) {
739
			switch ($prop['buttonScaleWhen']) {
740
				case 'scaleWhen.always': {
741
					$opt['mk']['if']['sw'] = 'A';
742
					break;
743
				}
744
				case 'scaleWhen.never': {
745
					$opt['mk']['if']['sw'] = 'N';
746
					break;
747
				}
748
				case 'scaleWhen.tooBig': {
749
					$opt['mk']['if']['sw'] = 'B';
750
					break;
751
				}
752
				case 'scaleWhen.tooSmall': {
753
					$opt['mk']['if']['sw'] = 'S';
754
					break;
755
				}
756
			}
757
		}
758
		// buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
759
		if (isset($prop['buttonPosition'])) {
760
			switch ($prop['buttonPosition']) {
761
				case 0:
762
				case 'position.textOnly': {
763
					$opt['mk']['tp'] = 0;
764
					break;
765
				}
766
				case 1:
767
				case 'position.iconOnly': {
768
					$opt['mk']['tp'] = 1;
769
					break;
770
				}
771
				case 2:
772
				case 'position.iconTextV': {
773
					$opt['mk']['tp'] = 2;
774
					break;
775
				}
776
				case 3:
777
				case 'position.textIconV': {
778
					$opt['mk']['tp'] = 3;
779
					break;
780
				}
781
				case 4:
782
				case 'position.iconTextH': {
783
					$opt['mk']['tp'] = 4;
784
					break;
785
				}
786
				case 5:
787
				case 'position.textIconH': {
788
					$opt['mk']['tp'] = 5;
789
					break;
790
				}
791
				case 6:
792
				case 'position.overlay': {
793
					$opt['mk']['tp'] = 6;
794
					break;
795
				}
796
			}
797
		}
798
		// fillColor: Specifies the background color for a field.
799
		if (isset($prop['fillColor'])) {
800
			if (is_array($prop['fillColor'])) {
801
				$opt['mk']['bg'] = $prop['fillColor'];
802
			} else {
803
				$opt['mk']['bg'] = TCPDF_COLORS::convertHTMLColorToDec($prop['fillColor'], $spot_colors);
804
			}
805
		}
806
		// strokeColor: Specifies the stroke color for a field that is used to stroke the rectangle of the field with a line as large as the line width.
807
		if (isset($prop['strokeColor'])) {
808
			if (is_array($prop['strokeColor'])) {
809
				$opt['mk']['bc'] = $prop['strokeColor'];
810
			} else {
811
				$opt['mk']['bc'] = TCPDF_COLORS::convertHTMLColorToDec($prop['strokeColor'], $spot_colors);
812
			}
813
		}
814
		// rotation: The rotation of a widget in counterclockwise increments.
815
		if (isset($prop['rotation'])) {
816
			$opt['mk']['r'] = $prop['rotation'];
817
		}
818
		// charLimit: Limits the number of characters that a user can type into a text field.
819
		if (isset($prop['charLimit'])) {
820
			$opt['maxlen'] = intval($prop['charLimit']);
821
		}
822
		$ff = 0;
823
		// readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
824
		if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
825
			$ff += 1 << 0;
826
		}
827
		// required: Specifies whether a field requires a value.
828
		if (isset($prop['required']) AND ($prop['required'] == 'true')) {
829
			$ff += 1 << 1;
830
		}
831
		// multiline: Controls how text is wrapped within the field.
832
		if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
833
			$ff += 1 << 12;
834
		}
835
		// password: Specifies whether the field should display asterisks when data is entered in the field.
836
		if (isset($prop['password']) AND ($prop['password'] == 'true')) {
837
			$ff += 1 << 13;
838
		}
839
		// NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
840
		if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
841
			$ff += 1 << 14;
842
		}
843
		// Radio: If set, the field is a set of radio buttons.
844
		if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
845
			$ff += 1 << 15;
846
		}
847
		// Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
848
		if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
849
			$ff += 1 << 16;
850
		}
851
		// Combo: If set, the field is a combo box; if clear, the field is a list box.
852
		if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
853
			$ff += 1 << 17;
854
		}
855
		// editable: Controls whether a combo box is editable.
856
		if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
857
			$ff += 1 << 18;
858
		}
859
		// Sort: If set, the field's option items shall be sorted alphabetically.
860
		if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
861
			$ff += 1 << 19;
862
		}
863
		// fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
864
		if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
865
			$ff += 1 << 20;
866
		}
867
		// multipleSelection: If true, indicates that a list box allows a multiple selection of items.
868
		if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
869
			$ff += 1 << 21;
870
		}
871
		// doNotSpellCheck: If true, spell checking is not performed on this editable text field.
872
		if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
873
			$ff += 1 << 22;
874
		}
875
		// doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
876
		if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
877
			$ff += 1 << 23;
878
		}
879
		// comb: If set to true, the field background is drawn as series of boxes (one for each character in the value of the field) and each character of the content is drawn within those boxes. The number of boxes drawn is determined from the charLimit property. It applies only to text fields. The setter will also raise if any of the following field properties are also set multiline, password, and fileSelect. A side-effect of setting this property is that the doNotScroll property is also set.
880
		if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
881
			$ff += 1 << 24;
882
		}
883
		// radiosInUnison: If false, even if a group of radio buttons have the same name and export value, they behave in a mutually exclusive fashion, like HTML radio buttons.
884
		if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
885
			$ff += 1 << 25;
886
		}
887
		// richText: If true, the field allows rich text formatting.
888
		if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
889
			$ff += 1 << 25;
890
		}
891
		// commitOnSelChange: Controls whether a field value is committed after a selection change.
892
		if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
893
			$ff += 1 << 26;
894
		}
895
		$opt['ff'] = $ff;
896
		// defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
897
		if (isset($prop['defaultValue'])) {
898
			$opt['dv'] = $prop['defaultValue'];
899
		}
900
		$f = 4; // default value for annotation flags
901
		// readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
902
		if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
903
			$f += 1 << 6;
904
		}
905
		// display: Controls whether the field is hidden or visible on screen and in print.
906
		if (isset($prop['display'])) {
907
			if ($prop['display'] == 'display.visible') {
908
				//
909
			} elseif ($prop['display'] == 'display.hidden') {
910
				$f += 1 << 1;
911
			} elseif ($prop['display'] == 'display.noPrint') {
912
				$f -= 1 << 2;
913
			} elseif ($prop['display'] == 'display.noView') {
914
				$f += 1 << 5;
915
			}
916
		}
917
		$opt['f'] = $f;
918
		// currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
919
		if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
920
			$opt['i'] = $prop['currentValueIndices'];
921
		}
922
		// value: The value of the field data that the user has entered.
923
		if (isset($prop['value'])) {
924
			if (is_array($prop['value'])) {
925
				$opt['opt'] = array();
926
				foreach ($prop['value'] AS $key => $optval) {
927
					// exportValues: An array of strings representing the export values for the field.
928
					if (isset($prop['exportValues'][$key])) {
929
						$opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
930
					} else {
931
						$opt['opt'][$key] = $prop['value'][$key];
932
					}
933
				}
934
			} else {
935
				$opt['v'] = $prop['value'];
936
			}
937
		}
938
		// richValue: This property specifies the text contents and formatting of a rich text field.
939
		if (isset($prop['richValue'])) {
940
			$opt['rv'] = $prop['richValue'];
941
		}
942
		// submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
943
		if (isset($prop['submitName'])) {
944
			$opt['tm'] = $prop['submitName'];
945
		}
946
		// name: Fully qualified field name.
947
		if (isset($prop['name'])) {
948
			$opt['t'] = $prop['name'];
949
		}
950
		// userName: The user name (short description string) of the field.
951
		if (isset($prop['userName'])) {
952
			$opt['tu'] = $prop['userName'];
953
		}
954
		// highlight: Defines how a button reacts when a user clicks it.
955
		if (isset($prop['highlight'])) {
956
			switch ($prop['highlight']) {
957
				case 'none':
958
				case 'highlight.n': {
959
					$opt['h'] = 'N';
960
					break;
961
				}
962
				case 'invert':
963
				case 'highlight.i': {
964
					$opt['h'] = 'i';
965
					break;
966
				}
967
				case 'push':
968
				case 'highlight.p': {
969
					$opt['h'] = 'P';
970
					break;
971
				}
972
				case 'outline':
973
				case 'highlight.o': {
974
					$opt['h'] = 'O';
975
					break;
976
				}
977
			}
978
		}
979
		// Unsupported options:
980
		// - calcOrderIndex: Changes the calculation order of fields in the document.
981
		// - delay: Delays the redrawing of a field's appearance.
982
		// - defaultStyle: This property defines the default style attributes for the form field.
983
		// - style: Allows the user to set the glyph style of a check box or radio button.
984
		// - textColor, textFont, textSize
985
		return $opt;
986
	}
987
 
988
	/**
989
	 * Format the page numbers.
990
	 * This method can be overridden for custom formats.
991
	 * @param int $num page number
992
	 * @return string
993
	 * @since 4.2.005 (2008-11-06)
994
	 * @public static
995
	 */
996
	public static function formatPageNumber($num) {
997
		return number_format((float)$num, 0, '', '.');
998
	}
999
 
1000
	/**
1001
	 * Format the page numbers on the Table Of Content.
1002
	 * This method can be overridden for custom formats.
1003
	 * @param int $num page number
1004
	 * @return string
1005
	 * @since 4.5.001 (2009-01-04)
1006
	 * @see addTOC(), addHTMLTOC()
1007
	 * @public static
1008
	 */
1009
	public static function formatTOCPageNumber($num) {
1010
		return number_format((float)$num, 0, '', '.');
1011
	}
1012
 
1013
	/**
1014
	 * Extracts the CSS properties from a CSS string.
1015
	 * @param string $cssdata string containing CSS definitions.
1016
	 * @return array An array where the keys are the CSS selectors and the values are the CSS properties.
1017
	 * @author Nicola Asuni
1018
	 * @since 5.1.000 (2010-05-25)
1019
	 * @public static
1020
	 */
1021
	public static function extractCSSproperties($cssdata) {
1022
		if (empty($cssdata)) {
1023
			return array();
1024
		}
1025
		// remove comments
1026
		$cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
1027
		// remove newlines and multiple spaces
1028
		$cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
1029
		// remove some spaces
1030
		$cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
1031
		// remove empty blocks
1032
		$cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
1033
		// replace media type parenthesis
1034
		$cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1§', $cssdata);
1035
		$cssdata = preg_replace('/\}\}/si', '}§', $cssdata);
1036
		// trim string
1037
		$cssdata = trim($cssdata);
1038
		// find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
1039
		$cssblocks = array();
1040
		$matches = array();
1041
		if (preg_match_all('/@media[\s]+([^\§]*)§([^§]*)§/i', $cssdata, $matches) > 0) {
1042
			foreach ($matches[1] as $key => $type) {
1043
				$cssblocks[$type] = $matches[2][$key];
1044
			}
1045
			// remove media blocks
1046
			$cssdata = preg_replace('/@media[\s]+([^\§]*)§([^§]*)§/i', '', $cssdata);
1047
		}
1048
		// keep 'all' and 'print' media, other media types are discarded
1049
		if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
1050
			$cssdata .= $cssblocks['all'];
1051
		}
1052
		if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
1053
			$cssdata .= $cssblocks['print'];
1054
		}
1055
		// reset css blocks array
1056
		$cssblocks = array();
1057
		$matches = array();
1058
		// explode css data string into array
1059
		if (substr($cssdata, -1) == '}') {
1060
			// remove last parethesis
1061
			$cssdata = substr($cssdata, 0, -1);
1062
		}
1063
		$matches = explode('}', $cssdata);
1064
		foreach ($matches as $key => $block) {
1065
			// index 0 contains the CSS selector, index 1 contains CSS properties
1066
			$cssblocks[$key] = explode('{', $block);
1067
			if (!isset($cssblocks[$key][1])) {
1068
				// remove empty definitions
1069
				unset($cssblocks[$key]);
1070
			}
1071
		}
1072
		// split groups of selectors (comma-separated list of selectors)
1073
		foreach ($cssblocks as $key => $block) {
1074
			if (strpos($block[0], ',') > 0) {
1075
				$selectors = explode(',', $block[0]);
1076
				foreach ($selectors as $sel) {
1077
					$cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
1078
				}
1079
				unset($cssblocks[$key]);
1080
			}
1081
		}
1082
		// covert array to selector => properties
1083
		$cssdata = array();
1084
		foreach ($cssblocks as $block) {
1085
			$selector = $block[0];
1086
			// calculate selector's specificity
1087
			$matches = array();
1088
			$a = 0; // the declaration is not from is a 'style' attribute
1089
			$b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
1090
			$c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
1091
			$c += intval(preg_match_all('/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', $selector, $matches)); // number of pseudo-classes
1092
			$d = intval(preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9]+/', ' '.$selector, $matches)); // number of element names
1093
			$d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
1094
			$specificity = $a.$b.$c.$d;
1095
			// add specificity to the beginning of the selector
1096
			$cssdata[$specificity.' '.$selector] = $block[1];
1097
		}
1098
		// sort selectors alphabetically to account for specificity
1099
		ksort($cssdata, SORT_STRING);
1100
		// return array
1101
		return $cssdata;
1102
	}
1103
 
1104
	/**
1105
	 * Cleanup HTML code (requires HTML Tidy library).
1106
	 * @param string $html htmlcode to fix
1107
	 * @param string $default_css CSS commands to add
1108
	 * @param array|null $tagvs parameters for setHtmlVSpace method
1109
	 * @param array|null $tidy_options options for tidy_parse_string function
1110
	 * @param array $tagvspaces Array of vertical spaces for tags.
1111
	 * @return string XHTML code cleaned up
1112
	 * @author Nicola Asuni
1113
	 * @since 5.9.017 (2010-11-16)
1114
	 * @see setHtmlVSpace()
1115
	 * @public static
1116
	 */
1117
	public static function fixHTMLCode($html, $default_css, $tagvs, $tidy_options, &$tagvspaces) {
1118
		// configure parameters for HTML Tidy
1119
		if (TCPDF_STATIC::empty_string($tidy_options)) {
1120
			$tidy_options = array (
1121
				'clean' => 1,
1122
				'drop-empty-paras' => 0,
1123
				'drop-proprietary-attributes' => 1,
1124
				'fix-backslash' => 1,
1125
				'hide-comments' => 1,
1126
				'join-styles' => 1,
1127
				'lower-literals' => 1,
1128
				'merge-divs' => 1,
1129
				'merge-spans' => 1,
1130
				'output-xhtml' => 1,
1131
				'word-2000' => 1,
1132
				'wrap' => 0,
1133
				'output-bom' => 0,
1134
				//'char-encoding' => 'utf8',
1135
				//'input-encoding' => 'utf8',
1136
				//'output-encoding' => 'utf8'
1137
			);
1138
		}
1139
		// clean up the HTML code
1140
		$tidy = tidy_parse_string($html, $tidy_options);
1141
		// fix the HTML
1142
		$tidy->cleanRepair();
1143
		// get the CSS part
1144
		$tidy_head = tidy_get_head($tidy);
1145
		$css = $tidy_head->value;
1146
		$css = preg_replace('/<style([^>]+)>/ims', '<style>', $css);
1147
		$css = preg_replace('/<\/style>(.*)<style>/ims', "\n", $css);
1148
		$css = str_replace('/*<![CDATA[*/', '', $css);
1149
		$css = str_replace('/*]]>*/', '', $css);
1150
		preg_match('/<style>(.*)<\/style>/ims', $css, $matches);
1151
		if (isset($matches[1])) {
1152
			$css = strtolower($matches[1]);
1153
		} else {
1154
			$css = '';
1155
		}
1156
		// include default css
1157
		$css = '<style>'.$default_css.$css.'</style>';
1158
		// get the body part
1159
		$tidy_body = tidy_get_body($tidy);
1160
		$html = $tidy_body->value;
1161
		// fix some self-closing tags
1162
		$html = str_replace('<br>', '<br />', $html);
1163
		// remove some empty tag blocks
1164
		$html = preg_replace('/<div([^\>]*)><\/div>/', '', $html);
1165
		$html = preg_replace('/<p([^\>]*)><\/p>/', '', $html);
1166
		if (!TCPDF_STATIC::empty_string($tagvs)) {
1167
			// set vertical space for some XHTML tags
1168
			$tagvspaces = $tagvs;
1169
		}
1170
		// return the cleaned XHTML code + CSS
1171
		return $css.$html;
1172
	}
1173
 
1174
	/**
1175
	 * Returns true if the CSS selector is valid for the selected HTML tag
1176
	 * @param array $dom array of HTML tags and properties
1177
	 * @param int $key key of the current HTML tag
1178
	 * @param string $selector CSS selector string
1179
	 * @return true if the selector is valid, false otherwise
1180
	 * @since 5.1.000 (2010-05-25)
1181
	 * @public static
1182
	 */
1183
	public static function isValidCSSSelectorForTag($dom, $key, $selector) {
1184
		$valid = false; // value to be returned
1185
		$tag = $dom[$key]['value'];
1186
		$class = array();
1187
		if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
1188
			$class = explode(' ', strtolower($dom[$key]['attribute']['class']));
1189
		}
1190
		$id = '';
1191
		if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
1192
			$id = strtolower($dom[$key]['attribute']['id']);
1193
		}
1194
		$selector = preg_replace('/([\>\+\~\s]{1})([\.]{1})([^\>\+\~\s]*)/si', '\\1*.\\3', $selector);
1195
		$matches = array();
1196
		if (preg_match_all('/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
1197
			$parentop = array_pop($matches[1]);
1198
			$operator = $parentop[0];
1199
			$offset = $parentop[1];
1200
			$lasttag = array_pop($matches[2]);
1201
			$lasttag = strtolower(trim($lasttag[0]));
1202
			if (($lasttag == '*') OR ($lasttag == $tag)) {
1203
				// the last element on selector is our tag or 'any tag'
1204
				$attrib = array_pop($matches[3]);
1205
				$attrib = strtolower(trim($attrib[0]));
1206
				if (!empty($attrib)) {
1207
					// check if matches class, id, attribute, pseudo-class or pseudo-element
1208
					switch ($attrib[0]) {
1209
						case '.': { // class
1210
							if (in_array(substr($attrib, 1), $class)) {
1211
								$valid = true;
1212
							}
1213
							break;
1214
						}
1215
						case '#': { // ID
1216
							if (substr($attrib, 1) == $id) {
1217
								$valid = true;
1218
							}
1219
							break;
1220
						}
1221
						case '[': { // attribute
1222
							$attrmatch = array();
1223
							if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
1224
								$att = strtolower($attrmatch[1]);
1225
								$val = $attrmatch[3];
1226
								if (isset($dom[$key]['attribute'][$att])) {
1227
									switch ($attrmatch[2]) {
1228
										case '=': {
1229
											if ($dom[$key]['attribute'][$att] == $val) {
1230
												$valid = true;
1231
											}
1232
											break;
1233
										}
1234
										case '~=': {
1235
											if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
1236
												$valid = true;
1237
											}
1238
											break;
1239
										}
1240
										case '^=': {
1241
											if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
1242
												$valid = true;
1243
											}
1244
											break;
1245
										}
1246
										case '$=': {
1247
											if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
1248
												$valid = true;
1249
											}
1250
											break;
1251
										}
1252
										case '*=': {
1253
											if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
1254
												$valid = true;
1255
											}
1256
											break;
1257
										}
1258
										case '|=': {
1259
											if ($dom[$key]['attribute'][$att] == $val) {
1260
												$valid = true;
1261
											} elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
1262
												$valid = true;
1263
											}
1264
											break;
1265
										}
1266
										default: {
1267
											$valid = true;
1268
										}
1269
									}
1270
								}
1271
							}
1272
							break;
1273
						}
1274
						case ':': { // pseudo-class or pseudo-element
1275
							if ($attrib[1] == ':') { // pseudo-element
1276
								// pseudo-elements are not supported!
1277
								// (::first-line, ::first-letter, ::before, ::after)
1278
							} else { // pseudo-class
1279
								// pseudo-classes are not supported!
1280
								// (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked)
1281
							}
1282
							break;
1283
						}
1284
					} // end of switch
1285
				} else {
1286
					$valid = true;
1287
				}
1288
				if ($valid AND ($offset > 0)) {
1289
					$valid = false;
1290
					// check remaining selector part
1291
					$selector = substr($selector, 0, $offset);
1292
					switch ($operator) {
1293
						case ' ': { // descendant of an element
1294
							while ($dom[$key]['parent'] > 0) {
1295
								if (self::isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
1296
									$valid = true;
1297
									break;
1298
								} else {
1299
									$key = $dom[$key]['parent'];
1300
								}
1301
							}
1302
							break;
1303
						}
1304
						case '>': { // child of an element
1305
							$valid = self::isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
1306
							break;
1307
						}
1308
						case '+': { // immediately preceded by an element
1309
							for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
1310
								if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
1311
									$valid = self::isValidCSSSelectorForTag($dom, $i, $selector);
1312
									break;
1313
								}
1314
							}
1315
							break;
1316
						}
1317
						case '~': { // preceded by an element
1318
							for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
1319
								if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
1320
									if (self::isValidCSSSelectorForTag($dom, $i, $selector)) {
1321
										break;
1322
									}
1323
								}
1324
							}
1325
							break;
1326
						}
1327
					}
1328
				}
1329
			}
1330
		}
1331
		return $valid;
1332
	}
1333
 
1334
	/**
1335
	 * Returns the styles array that apply for the selected HTML tag.
1336
	 * @param array $dom array of HTML tags and properties
1337
	 * @param int $key key of the current HTML tag
1338
	 * @param array $css array of CSS properties
1339
	 * @return array containing CSS properties
1340
	 * @since 5.1.000 (2010-05-25)
1341
	 * @public static
1342
	 */
1343
	public static function getCSSdataArray($dom, $key, $css) {
1344
		$cssarray = array(); // style to be returned
1345
		// get parent CSS selectors
1346
		$selectors = array();
1347
		if (isset($dom[($dom[$key]['parent'])]['csssel'])) {
1348
			$selectors = $dom[($dom[$key]['parent'])]['csssel'];
1349
		}
1350
		// get all styles that apply
1351
		foreach($css as $selector => $style) {
1352
			$pos = strpos($selector, ' ');
1353
			// get specificity
1354
			$specificity = substr($selector, 0, $pos);
1355
			// remove specificity
1356
			$selector = substr($selector, $pos);
1357
			// check if this selector apply to current tag
1358
			if (self::isValidCSSSelectorForTag($dom, $key, $selector)) {
1359
				if (!in_array($selector, $selectors)) {
1360
					// add style if not already added on parent selector
1361
					$cssarray[] = array('k' => $selector, 's' => $specificity, 'c' => $style);
1362
					$selectors[] = $selector;
1363
				}
1364
			}
1365
		}
1366
		if (isset($dom[$key]['attribute']['style'])) {
1367
			// attach inline style (latest properties have high priority)
1368
			$cssarray[] = array('k' => '', 's' => '1000', 'c' => $dom[$key]['attribute']['style']);
1369
		}
1370
		// order the css array to account for specificity
1371
		$cssordered = array();
1372
		foreach ($cssarray as $key => $val) {
1373
			$skey = sprintf('%04d', $key);
1374
			$cssordered[$val['s'].'_'.$skey] = $val;
1375
		}
1376
		// sort selectors alphabetically to account for specificity
1377
		ksort($cssordered, SORT_STRING);
1378
		return array($selectors, $cssordered);
1379
	}
1380
 
1381
	/**
1382
	 * Compact CSS data array into single string.
1383
	 * @param array $css array of CSS properties
1384
	 * @return string containing merged CSS properties
1385
	 * @since 5.9.070 (2011-04-19)
1386
	 * @public static
1387
	 */
1388
	public static function getTagStyleFromCSSarray($css) {
1389
		$tagstyle = ''; // value to be returned
1390
		foreach ($css as $style) {
1391
			// split single css commands
1392
			$csscmds = explode(';', $style['c']);
1393
			foreach ($csscmds as $cmd) {
1394
				if (!empty($cmd)) {
1395
					$pos = strpos($cmd, ':');
1396
					if ($pos !== false) {
1397
						$cmd = substr($cmd, 0, ($pos + 1));
1398
						if (strpos($tagstyle, $cmd) !== false) {
1399
							// remove duplicate commands (last commands have high priority)
1400
							$tagstyle = preg_replace('/'.$cmd.'[^;]+/i', '', $tagstyle);
1401
						}
1402
					}
1403
				}
1404
			}
1405
			$tagstyle .= ';'.$style['c'];
1406
		}
1407
		// remove multiple semicolons
1408
		$tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
1409
		return $tagstyle;
1410
	}
1411
 
1412
	/**
1413
	 * Returns the Roman representation of an integer number
1414
	 * @param int $number number to convert
1415
	 * @return string roman representation of the specified number
1416
	 * @since 4.4.004 (2008-12-10)
1417
	 * @public static
1418
	 */
1419
	public static function intToRoman($number) {
1420
		$roman = '';
1421
		if ($number >= 4000) {
1422
			// do not represent numbers above 4000 in Roman numerals
1423
			return strval($number);
1424
		}
1425
		while ($number >= 1000) {
1426
			$roman .= 'M';
1427
			$number -= 1000;
1428
		}
1429
		while ($number >= 900) {
1430
			$roman .= 'CM';
1431
			$number -= 900;
1432
		}
1433
		while ($number >= 500) {
1434
			$roman .= 'D';
1435
			$number -= 500;
1436
		}
1437
		while ($number >= 400) {
1438
			$roman .= 'CD';
1439
			$number -= 400;
1440
		}
1441
		while ($number >= 100) {
1442
			$roman .= 'C';
1443
			$number -= 100;
1444
		}
1445
		while ($number >= 90) {
1446
			$roman .= 'XC';
1447
			$number -= 90;
1448
		}
1449
		while ($number >= 50) {
1450
			$roman .= 'L';
1451
			$number -= 50;
1452
		}
1453
		while ($number >= 40) {
1454
			$roman .= 'XL';
1455
			$number -= 40;
1456
		}
1457
		while ($number >= 10) {
1458
			$roman .= 'X';
1459
			$number -= 10;
1460
		}
1461
		while ($number >= 9) {
1462
			$roman .= 'IX';
1463
			$number -= 9;
1464
		}
1465
		while ($number >= 5) {
1466
			$roman .= 'V';
1467
			$number -= 5;
1468
		}
1469
		while ($number >= 4) {
1470
			$roman .= 'IV';
1471
			$number -= 4;
1472
		}
1473
		while ($number >= 1) {
1474
			$roman .= 'I';
1475
			--$number;
1476
		}
1477
		return $roman;
1478
	}
1479
 
1480
	/**
1481
	 * Find position of last occurrence of a substring in a string
1482
	 * @param string $haystack The string to search in.
1483
	 * @param string $needle substring to search.
1484
	 * @param int $offset May be specified to begin searching an arbitrary number of characters into the string.
1485
	 * @return int|false Returns the position where the needle exists. Returns FALSE if the needle was not found.
1486
	 * @since 4.8.038 (2010-03-13)
1487
	 * @public static
1488
	 */
1489
	public static function revstrpos($haystack, $needle, $offset = 0) {
1490
		$length = strlen($haystack);
1491
		$offset = ($offset > 0)?($length - $offset):abs($offset);
1492
		$pos = strpos(strrev($haystack), strrev($needle), $offset);
1493
		return ($pos === false)?false:($length - $pos - strlen($needle));
1494
	}
1495
 
1496
	/**
1497
	 * Returns an array of hyphenation patterns.
1498
	 * @param string $file TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
1499
	 * @return array of hyphenation patterns
1500
	 * @author Nicola Asuni
1501
	 * @since 4.9.012 (2010-04-12)
1502
	 * @public static
1503
	 */
1504
	public static function getHyphenPatternsFromTEX($file) {
1505
		// TEX patterns are available at:
1506
		// http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
1507
		$data = file_get_contents($file);
1508
		$patterns = array();
1509
		// remove comments
1510
		$data = preg_replace('/\%[^\n]*/', '', $data);
1511
		// extract the patterns part
1512
		preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
1513
		$data = trim(substr($matches[0], 10, -1));
1514
		// extract each pattern
1515
		$patterns_array = preg_split('/[\s]+/', $data);
1516
		// create new language array of patterns
1517
		$patterns = array();
1518
		foreach($patterns_array as $val) {
1519
			if (!TCPDF_STATIC::empty_string($val)) {
1520
				$val = trim($val);
1521
				$val = str_replace('\'', '\\\'', $val);
1522
				$key = preg_replace('/[0-9]+/', '', $val);
1523
				$patterns[$key] = $val;
1524
			}
1525
		}
1526
		return $patterns;
1527
	}
1528
 
1529
	/**
1530
	 * Get the Path-Painting Operators.
1531
	 * @param string $style Style of rendering. Possible values are:
1532
	 * <ul>
1533
	 *   <li>S or D: Stroke the path.</li>
1534
	 *   <li>s or d: Close and stroke the path.</li>
1535
	 *   <li>f or F: Fill the path, using the nonzero winding number rule to determine the region to fill.</li>
1536
	 *   <li>f* or F*: Fill the path, using the even-odd rule to determine the region to fill.</li>
1537
	 *   <li>B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
1538
	 *   <li>B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill.</li>
1539
	 *   <li>b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
1540
	 *   <li>b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill.</li>
1541
	 *   <li>CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path.</li>
1542
	 *   <li>CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path</li>
1543
	 *   <li>n: End the path object without filling or stroking it.</li>
1544
	 * </ul>
1545
	 * @param string $default default style
1546
	 * @return string
1547
	 * @author Nicola Asuni
1548
	 * @since 5.0.000 (2010-04-30)
1549
	 * @public static
1550
	 */
1551
	public static function getPathPaintOperator($style, $default='S') {
1552
		$op = '';
1553
		switch($style) {
1554
			case 'S':
1555
			case 'D': {
1556
				$op = 'S';
1557
				break;
1558
			}
1559
			case 's':
1560
			case 'd': {
1561
				$op = 's';
1562
				break;
1563
			}
1564
			case 'f':
1565
			case 'F': {
1566
				$op = 'f';
1567
				break;
1568
			}
1569
			case 'f*':
1570
			case 'F*': {
1571
				$op = 'f*';
1572
				break;
1573
			}
1574
			case 'B':
1575
			case 'FD':
1576
			case 'DF': {
1577
				$op = 'B';
1578
				break;
1579
			}
1580
			case 'B*':
1581
			case 'F*D':
1582
			case 'DF*': {
1583
				$op = 'B*';
1584
				break;
1585
			}
1586
			case 'b':
1587
			case 'fd':
1588
			case 'df': {
1589
				$op = 'b';
1590
				break;
1591
			}
1592
			case 'b*':
1593
			case 'f*d':
1594
			case 'df*': {
1595
				$op = 'b*';
1596
				break;
1597
			}
1598
			case 'CNZ': {
1599
				$op = 'W n';
1600
				break;
1601
			}
1602
			case 'CEO': {
1603
				$op = 'W* n';
1604
				break;
1605
			}
1606
			case 'n': {
1607
				$op = 'n';
1608
				break;
1609
			}
1610
			default: {
1611
				if (!empty($default)) {
1612
					$op = self::getPathPaintOperator($default, '');
1613
				} else {
1614
					$op = '';
1615
				}
1616
			}
1617
		}
1618
		return $op;
1619
	}
1620
 
1621
	/**
1622
	 * Get the product of two SVG tranformation matrices
1623
	 * @param array $ta first SVG tranformation matrix
1624
	 * @param array $tb second SVG tranformation matrix
1625
	 * @return array transformation array
1626
	 * @author Nicola Asuni
1627
	 * @since 5.0.000 (2010-05-02)
1628
	 * @public static
1629
	 */
1630
	public static function getTransformationMatrixProduct($ta, $tb) {
1631
		$tm = array();
1632
		$tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
1633
		$tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
1634
		$tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
1635
		$tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
1636
		$tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
1637
		$tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
1638
		return $tm;
1639
	}
1640
 
1641
	/**
1642
	 * Get the tranformation matrix from SVG transform attribute
1643
	 * @param string $attribute transformation
1644
	 * @return array of transformations
1645
	 * @author Nicola Asuni
1646
	 * @since 5.0.000 (2010-05-02)
1647
	 * @public static
1648
	 */
1649
	public static function getSVGTransformMatrix($attribute) {
1650
		// identity matrix
1651
		$tm = array(1, 0, 0, 1, 0, 0);
1652
		$transform = array();
1653
		if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
1654
			foreach ($transform as $key => $data) {
1655
				if (!empty($data[2])) {
1656
					$a = 1;
1657
					$b = 0;
1658
					$c = 0;
1659
					$d = 1;
1660
					$e = 0;
1661
					$f = 0;
1662
					$regs = array();
1663
					switch ($data[1]) {
1664
						case 'matrix': {
1665
							if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
1666
								$a = $regs[1];
1667
								$b = $regs[2];
1668
								$c = $regs[3];
1669
								$d = $regs[4];
1670
								$e = $regs[5];
1671
								$f = $regs[6];
1672
							}
1673
							break;
1674
						}
1675
						case 'translate': {
1676
							if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
1677
								$e = $regs[1];
1678
								$f = $regs[2];
1679
							} elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
1680
								$e = $regs[1];
1681
							}
1682
							break;
1683
						}
1684
						case 'scale': {
1685
							if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
1686
								$a = $regs[1];
1687
								$d = $regs[2];
1688
							} elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
1689
								$a = $regs[1];
1690
								$d = $a;
1691
							}
1692
							break;
1693
						}
1694
						case 'rotate': {
1695
							if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
1696
								$ang = deg2rad($regs[1]);
1697
								$x = $regs[2];
1698
								$y = $regs[3];
1699
								$a = cos($ang);
1700
								$b = sin($ang);
1701
								$c = -$b;
1702
								$d = $a;
1703
								$e = ($x * (1 - $a)) - ($y * $c);
1704
								$f = ($y * (1 - $d)) - ($x * $b);
1705
							} elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
1706
								$ang = deg2rad($regs[1]);
1707
								$a = cos($ang);
1708
								$b = sin($ang);
1709
								$c = -$b;
1710
								$d = $a;
1711
								$e = 0;
1712
								$f = 0;
1713
							}
1714
							break;
1715
						}
1716
						case 'skewX': {
1717
							if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
1718
								$c = tan(deg2rad($regs[1]));
1719
							}
1720
							break;
1721
						}
1722
						case 'skewY': {
1723
							if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
1724
								$b = tan(deg2rad($regs[1]));
1725
							}
1726
							break;
1727
						}
1728
					}
1729
					$tm = self::getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
1730
				}
1731
			}
1732
		}
1733
		return $tm;
1734
	}
1735
 
1736
	/**
1737
	 * Returns the angle in radiants between two vectors
1738
	 * @param int $x1 X coordinate of first vector point
1739
	 * @param int $y1 Y coordinate of first vector point
1740
	 * @param int $x2 X coordinate of second vector point
1741
	 * @param int $y2 Y coordinate of second vector point
1742
	 * @author Nicola Asuni
1743
	 * @since 5.0.000 (2010-05-04)
1744
	 * @public static
1745
	 */
1746
	public static function getVectorsAngle($x1, $y1, $x2, $y2) {
1747
		$dprod = ($x1 * $x2) + ($y1 * $y2);
1748
		$dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
1749
		$dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
1750
		$angle = acos($dprod / ($dist1 * $dist2));
1751
		if (is_nan($angle)) {
1752
			$angle = M_PI;
1753
		}
1754
		if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
1755
			$angle *= -1;
1756
		}
1757
		return $angle;
1758
	}
1759
 
1760
	/**
1761
	 * Split string by a regular expression.
1762
	 * This is a wrapper for the preg_split function to avoid the bug: https://bugs.php.net/bug.php?id=45850
1763
	 * @param string $pattern The regular expression pattern to search for without the modifiers, as a string.
1764
	 * @param string $modifiers The modifiers part of the pattern,
1765
	 * @param string $subject The input string.
1766
	 * @param int $limit If specified, then only substrings up to limit are returned with the rest of the string being placed in the last substring. A limit of -1, 0 or NULL means "no limit" and, as is standard across PHP, you can use NULL to skip to the flags parameter.
1767
	 * @param int $flags The flags as specified on the preg_split PHP function.
1768
	 * @return array Returns an array containing substrings of subject split along boundaries matched by pattern.modifier
1769
	 * @author Nicola Asuni
1770
	 * @since 6.0.023
1771
	 * @public static
1772
	 */
1773
	public static function pregSplit($pattern, $modifiers, $subject, $limit=NULL, $flags=NULL) {
1774
		// PHP 8.1 deprecates nulls for $limit and $flags
1775
		$limit = $limit === null ? -1 : $limit;
1776
		$flags = $flags === null ? 0 : $flags;
1777
		// the bug only happens on PHP 5.2 when using the u modifier
1778
		if ((strpos($modifiers, 'u') === FALSE) OR (count(preg_split('//u', "\n\t", -1, PREG_SPLIT_NO_EMPTY)) == 2)) {
1779
			$ret = preg_split($pattern.$modifiers, $subject, $limit, $flags);
1780
			if ($ret === false) {
1781
				return array();
1782
			}
1783
			return is_array($ret) ? $ret : array();
1784
		}
1785
		// preg_split is bugged - try alternative solution
1786
		$ret = array();
1787
		while (($nl = strpos($subject, "\n")) !== FALSE) {
1788
			$ret = array_merge($ret, preg_split($pattern.$modifiers, substr($subject, 0, $nl), $limit, $flags));
1789
			$ret[] = "\n";
1790
			$subject = substr($subject, ($nl + 1));
1791
		}
1792
		if (strlen($subject) > 0) {
1793
			$ret = array_merge($ret, preg_split($pattern.$modifiers, $subject, $limit, $flags));
1794
		}
1795
		return $ret;
1796
	}
1797
 
1798
	/**
1799
	 * Wrapper to use fopen only with local files
1800
	 * @param string $filename Name of the file to open
1801
	 * @param string $mode
1802
	 * @return resource|false Returns a file pointer resource on success, or FALSE on error.
1803
	 * @public static
1804
	 */
1805
	public static function fopenLocal($filename, $mode) {
1806
		if (strpos($filename, '://') === false) {
1807
			$filename = 'file://'.$filename;
1808
		} elseif (stream_is_local($filename) !== true) {
1809
			return false;
1810
		}
1811
		return fopen($filename, $mode);
1812
	}
1813
 
1814
	/**
1815
	 * Check if the URL exist.
1816
	 * @param string $url URL to check.
1817
	 * @return bool Returns TRUE if the URL exists; FALSE otherwise.
1818
	 * @public static
1819
	 * @since 6.2.25
1820
	 */
1821
	public static function url_exists($url) {
1822
		$crs = curl_init();
1823
		// encode query params in URL to get right response form the server
1824
		$url = self::encodeUrlQuery($url);
1825
		curl_setopt($crs, CURLOPT_URL, $url);
1826
		curl_setopt($crs, CURLOPT_NOBODY, true);
1827
		curl_setopt($crs, CURLOPT_FAILONERROR, true);
1828
		if ((ini_get('open_basedir') == '') && (!ini_get('safe_mode'))) {
1829
			curl_setopt($crs, CURLOPT_FOLLOWLOCATION, true);
1830
		}
1831
		curl_setopt($crs, CURLOPT_CONNECTTIMEOUT, 5);
1832
		curl_setopt($crs, CURLOPT_TIMEOUT, 30);
1833
		curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false);
1834
		curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false);
1835
		curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file');
1836
		curl_setopt($crs, CURLOPT_MAXREDIRS, 5);
1837
		if (defined('CURLOPT_PROTOCOLS')) {
1838
		    curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP |  CURLPROTO_FTP | CURLPROTO_FTPS);
1839
		}
1840
		curl_exec($crs);
1841
		$code = curl_getinfo($crs, CURLINFO_HTTP_CODE);
1842
		curl_close($crs);
1843
		return ($code == 200);
1844
	}
1845
 
1846
	/**
1847
	 * Encode query params in URL
1848
	 *
1849
	 * @param string $url
1850
	 * @return string
1851
	 * @since 6.3.3 (2019-11-01)
1852
	 * @public static
1853
	 */
1854
	public static function encodeUrlQuery($url) {
1855
		$urlData = parse_url($url);
1856
		if (isset($urlData['query']) && $urlData['query']) {
1857
			$urlQueryData = array();
1858
			parse_str(urldecode($urlData['query']), $urlQueryData);
1859
			$port = isset($urlData['port']) ? ':'.$urlData['port'] : '';
1860
			$updatedUrl = $urlData['scheme'].'://'.$urlData['host'].$port.$urlData['path'].'?'.http_build_query($urlQueryData);
1861
		} else {
1862
			$updatedUrl = $url;
1863
		}
1864
		return $updatedUrl;
1865
	}
1866
 
1867
	/**
1868
	 * Wrapper for file_exists.
1869
	 * Checks whether a file or directory exists.
1870
	 * Only allows some protocols and local files.
1871
	 * @param string $filename Path to the file or directory.
1872
	 * @return bool Returns TRUE if the file or directory specified by filename exists; FALSE otherwise.
1873
	 * @public static
1874
	 */
1875
	public static function file_exists($filename) {
1876
		if (preg_match('|^https?://|', $filename) == 1) {
1877
			return self::url_exists($filename);
1878
		}
1879
		if (strpos($filename, '://')) {
1880
			return false; // only support http and https wrappers for security reasons
1881
		}
1882
		return @file_exists($filename);
1883
	}
1884
 
1885
	/**
1886
	 * Reads entire file into a string.
1887
	 * The file can be also an URL.
1888
	 * @param string $file Name of the file or URL to read.
1889
	 * @return string|false The function returns the read data or FALSE on failure.
1890
	 * @author Nicola Asuni
1891
	 * @since 6.0.025
1892
	 * @public static
1893
	 */
1894
	public static function fileGetContents($file) {
1895
		$alt = array($file);
1896
		//
1897
		if ((strlen($file) > 1)
1898
		    && ($file[0] === '/')
1899
		    && ($file[1] !== '/')
1900
		    && !empty($_SERVER['DOCUMENT_ROOT'])
1901
		    && ($_SERVER['DOCUMENT_ROOT'] !== '/')
1902
		) {
1903
		    $findroot = strpos($file, $_SERVER['DOCUMENT_ROOT']);
1904
		    if (($findroot === false) || ($findroot > 1)) {
1905
			$alt[] = htmlspecialchars_decode(urldecode($_SERVER['DOCUMENT_ROOT'].$file));
1906
		    }
1907
		}
1908
		//
1909
		$protocol = 'http';
1910
		if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off')) {
1911
		    $protocol .= 's';
1912
		}
1913
		//
1914
		$url = $file;
1915
		if (preg_match('%^//%', $url) && !empty($_SERVER['HTTP_HOST'])) {
1916
			$url = $protocol.':'.str_replace(' ', '%20', $url);
1917
		}
1918
		$url = htmlspecialchars_decode($url);
1919
		$alt[] = $url;
1920
		//
1921
		if (preg_match('%^(https?)://%', $url)
1922
		    && empty($_SERVER['HTTP_HOST'])
1923
		    && empty($_SERVER['DOCUMENT_ROOT'])
1924
		) {
1925
			$urldata = parse_url($url);
1926
			if (empty($urldata['query'])) {
1927
				$host = $protocol.'://'.$_SERVER['HTTP_HOST'];
1928
				if (strpos($url, $host) === 0) {
1929
				    // convert URL to full server path
1930
				    $tmp = str_replace($host, $_SERVER['DOCUMENT_ROOT'], $url);
1931
				    $alt[] = htmlspecialchars_decode(urldecode($tmp));
1932
				}
1933
			}
1934
		}
1935
		//
1936
		if (isset($_SERVER['SCRIPT_URI'])
1937
		    && !preg_match('%^(https?|ftp)://%', $file)
1938
		    && !preg_match('%^//%', $file)
1939
		) {
1940
		    $urldata = @parse_url($_SERVER['SCRIPT_URI']);
1941
		    $alt[] = $urldata['scheme'].'://'.$urldata['host'].(($file[0] == '/') ? '' : '/').$file;
1942
		}
1943
		//
1944
		$alt = array_unique($alt);
1945
		foreach ($alt as $path) {
1946
			if (!self::file_exists($path)) {
1947
				continue;
1948
			}
1949
			$ret = @file_get_contents($path);
1950
			if ( $ret != false ) {
1951
			    return $ret;
1952
			}
1953
			// try to use CURL for URLs
1954
			if (!ini_get('allow_url_fopen')
1955
				&& function_exists('curl_init')
1956
				&& preg_match('%^(https?|ftp)://%', $path)
1957
			) {
1958
				// try to get remote file data using cURL
1959
				$crs = curl_init();
1960
				curl_setopt($crs, CURLOPT_URL, $path);
1961
				curl_setopt($crs, CURLOPT_BINARYTRANSFER, true);
1962
				curl_setopt($crs, CURLOPT_FAILONERROR, true);
1963
				curl_setopt($crs, CURLOPT_RETURNTRANSFER, true);
1964
				if ((ini_get('open_basedir') == '') && (!ini_get('safe_mode'))) {
1965
				    curl_setopt($crs, CURLOPT_FOLLOWLOCATION, true);
1966
				}
1967
				curl_setopt($crs, CURLOPT_CONNECTTIMEOUT, 5);
1968
				curl_setopt($crs, CURLOPT_TIMEOUT, 30);
1969
				curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false);
1970
				curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false);
1971
				curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file');
1972
				curl_setopt($crs, CURLOPT_MAXREDIRS, 5);
1973
				if (defined('CURLOPT_PROTOCOLS')) {
1974
				    curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP |  CURLPROTO_FTP | CURLPROTO_FTPS);
1975
				}
1976
				$ret = curl_exec($crs);
1977
				curl_close($crs);
1978
				if ($ret !== false) {
1979
					return $ret;
1980
				}
1981
			}
1982
		}
1983
		return false;
1984
	}
1985
 
1986
	/**
1987
	 * Get ULONG from string (Big Endian 32-bit unsigned integer).
1988
	 * @param string $str string from where to extract value
1989
	 * @param int $offset point from where to read the data
1990
	 * @return int 32 bit value
1991
	 * @author Nicola Asuni
1992
	 * @since 5.2.000 (2010-06-02)
1993
	 * @public static
1994
	 */
1995
	public static function _getULONG($str, $offset) {
1996
		$v = unpack('Ni', substr($str, $offset, 4));
1997
		return $v['i'];
1998
	}
1999
 
2000
	/**
2001
	 * Get USHORT from string (Big Endian 16-bit unsigned integer).
2002
	 * @param string $str string from where to extract value
2003
	 * @param int $offset point from where to read the data
2004
	 * @return int 16 bit value
2005
	 * @author Nicola Asuni
2006
	 * @since 5.2.000 (2010-06-02)
2007
	 * @public static
2008
	 */
2009
	public static function _getUSHORT($str, $offset) {
2010
		$v = unpack('ni', substr($str, $offset, 2));
2011
		return $v['i'];
2012
	}
2013
 
2014
	/**
2015
	 * Get SHORT from string (Big Endian 16-bit signed integer).
2016
	 * @param string $str String from where to extract value.
2017
	 * @param int $offset Point from where to read the data.
2018
	 * @return int 16 bit value
2019
	 * @author Nicola Asuni
2020
	 * @since 5.2.000 (2010-06-02)
2021
	 * @public static
2022
	 */
2023
	public static function _getSHORT($str, $offset) {
2024
		$v = unpack('si', substr($str, $offset, 2));
2025
		return $v['i'];
2026
	}
2027
 
2028
	/**
2029
	 * Get FWORD from string (Big Endian 16-bit signed integer).
2030
	 * @param string $str String from where to extract value.
2031
	 * @param int $offset Point from where to read the data.
2032
	 * @return int 16 bit value
2033
	 * @author Nicola Asuni
2034
	 * @since 5.9.123 (2011-09-30)
2035
	 * @public static
2036
	 */
2037
	public static function _getFWORD($str, $offset) {
2038
		$v = self::_getUSHORT($str, $offset);
2039
		if ($v > 0x7fff) {
2040
			$v -= 0x10000;
2041
		}
2042
		return $v;
2043
	}
2044
 
2045
	/**
2046
	 * Get UFWORD from string (Big Endian 16-bit unsigned integer).
2047
	 * @param string $str string from where to extract value
2048
	 * @param int $offset point from where to read the data
2049
	 * @return int 16 bit value
2050
	 * @author Nicola Asuni
2051
	 * @since 5.9.123 (2011-09-30)
2052
	 * @public static
2053
	 */
2054
	public static function _getUFWORD($str, $offset) {
2055
		$v = self::_getUSHORT($str, $offset);
2056
		return $v;
2057
	}
2058
 
2059
	/**
2060
	 * Get FIXED from string (32-bit signed fixed-point number (16.16).
2061
	 * @param string $str string from where to extract value
2062
	 * @param int $offset point from where to read the data
2063
	 * @return int 16 bit value
2064
	 * @author Nicola Asuni
2065
	 * @since 5.9.123 (2011-09-30)
2066
	 * @public static
2067
	 */
2068
	public static function _getFIXED($str, $offset) {
2069
		// mantissa
2070
		$m = self::_getFWORD($str, $offset);
2071
		// fraction
2072
		$f = self::_getUSHORT($str, ($offset + 2));
2073
		$v = floatval(''.$m.'.'.$f.'');
2074
		return $v;
2075
	}
2076
 
2077
	/**
2078
	 * Get BYTE from string (8-bit unsigned integer).
2079
	 * @param string $str String from where to extract value.
2080
	 * @param int $offset Point from where to read the data.
2081
	 * @return int 8 bit value
2082
	 * @author Nicola Asuni
2083
	 * @since 5.2.000 (2010-06-02)
2084
	 * @public static
2085
	 */
2086
	public static function _getBYTE($str, $offset) {
2087
		$v = unpack('Ci', substr($str, $offset, 1));
2088
		return $v['i'];
2089
	}
2090
	/**
2091
	 * Binary-safe and URL-safe file read.
2092
	 * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached.
2093
	 * @param resource $handle
2094
	 * @param int $length
2095
	 * @return string|false Returns the read string or FALSE in case of error.
2096
	 * @author Nicola Asuni
2097
	 * @since 4.5.027 (2009-03-16)
2098
	 * @public static
2099
	 */
2100
	public static function rfread($handle, $length) {
2101
		$data = fread($handle, $length);
2102
		if ($data === false) {
2103
			return false;
2104
		}
2105
		$rest = ($length - strlen($data));
2106
		if (($rest > 0) && !feof($handle)) {
2107
			$data .= self::rfread($handle, $rest);
2108
		}
2109
		return $data;
2110
	}
2111
 
2112
	/**
2113
	 * Read a 4-byte (32 bit) integer from file.
2114
	 * @param resource $f file resource.
2115
	 * @return int 4-byte integer
2116
	 * @public static
2117
	 */
2118
	public static function _freadint($f) {
2119
		$a = unpack('Ni', fread($f, 4));
2120
		return $a['i'];
2121
	}
2122
 
2123
	/**
2124
	 * Array of page formats
2125
	 * measures are calculated in this way: (inches * 72) or (millimeters * 72 / 25.4)
2126
	 * @public static
2127
	 *
2128
     * @var array<string,float[]>
2129
	 */
2130
	public static $page_formats = array(
2131
		// ISO 216 A Series + 2 SIS 014711 extensions
2132
		'A0'                     => array( 2383.937,  3370.394), // = (  841 x 1189 ) mm  = ( 33.11 x 46.81 ) in
2133
		'A1'                     => array( 1683.780,  2383.937), // = (  594 x 841  ) mm  = ( 23.39 x 33.11 ) in
2134
		'A2'                     => array( 1190.551,  1683.780), // = (  420 x 594  ) mm  = ( 16.54 x 23.39 ) in
2135
		'A3'                     => array(  841.890,  1190.551), // = (  297 x 420  ) mm  = ( 11.69 x 16.54 ) in
2136
		'A4'                     => array(  595.276,   841.890), // = (  210 x 297  ) mm  = (  8.27 x 11.69 ) in
2137
		'A5'                     => array(  419.528,   595.276), // = (  148 x 210  ) mm  = (  5.83 x 8.27  ) in
2138
		'A6'                     => array(  297.638,   419.528), // = (  105 x 148  ) mm  = (  4.13 x 5.83  ) in
2139
		'A7'                     => array(  209.764,   297.638), // = (   74 x 105  ) mm  = (  2.91 x 4.13  ) in
2140
		'A8'                     => array(  147.402,   209.764), // = (   52 x 74   ) mm  = (  2.05 x 2.91  ) in
2141
		'A9'                     => array(  104.882,   147.402), // = (   37 x 52   ) mm  = (  1.46 x 2.05  ) in
2142
		'A10'                    => array(   73.701,   104.882), // = (   26 x 37   ) mm  = (  1.02 x 1.46  ) in
2143
		'A11'                    => array(   51.024,    73.701), // = (   18 x 26   ) mm  = (  0.71 x 1.02  ) in
2144
		'A12'                    => array(   36.850,    51.024), // = (   13 x 18   ) mm  = (  0.51 x 0.71  ) in
2145
		// ISO 216 B Series + 2 SIS 014711 extensions
2146
		'B0'                     => array( 2834.646,  4008.189), // = ( 1000 x 1414 ) mm  = ( 39.37 x 55.67 ) in
2147
		'B1'                     => array( 2004.094,  2834.646), // = (  707 x 1000 ) mm  = ( 27.83 x 39.37 ) in
2148
		'B2'                     => array( 1417.323,  2004.094), // = (  500 x 707  ) mm  = ( 19.69 x 27.83 ) in
2149
		'B3'                     => array( 1000.630,  1417.323), // = (  353 x 500  ) mm  = ( 13.90 x 19.69 ) in
2150
		'B4'                     => array(  708.661,  1000.630), // = (  250 x 353  ) mm  = (  9.84 x 13.90 ) in
2151
		'B5'                     => array(  498.898,   708.661), // = (  176 x 250  ) mm  = (  6.93 x 9.84  ) in
2152
		'B6'                     => array(  354.331,   498.898), // = (  125 x 176  ) mm  = (  4.92 x 6.93  ) in
2153
		'B7'                     => array(  249.449,   354.331), // = (   88 x 125  ) mm  = (  3.46 x 4.92  ) in
2154
		'B8'                     => array(  175.748,   249.449), // = (   62 x 88   ) mm  = (  2.44 x 3.46  ) in
2155
		'B9'                     => array(  124.724,   175.748), // = (   44 x 62   ) mm  = (  1.73 x 2.44  ) in
2156
		'B10'                    => array(   87.874,   124.724), // = (   31 x 44   ) mm  = (  1.22 x 1.73  ) in
2157
		'B11'                    => array(   62.362,    87.874), // = (   22 x 31   ) mm  = (  0.87 x 1.22  ) in
2158
		'B12'                    => array(   42.520,    62.362), // = (   15 x 22   ) mm  = (  0.59 x 0.87  ) in
2159
		// ISO 216 C Series + 2 SIS 014711 extensions + 5 EXTENSION
2160
		'C0'                     => array( 2599.370,  3676.535), // = (  917 x 1297 ) mm  = ( 36.10 x 51.06 ) in
2161
		'C1'                     => array( 1836.850,  2599.370), // = (  648 x 917  ) mm  = ( 25.51 x 36.10 ) in
2162
		'C2'                     => array( 1298.268,  1836.850), // = (  458 x 648  ) mm  = ( 18.03 x 25.51 ) in
2163
		'C3'                     => array(  918.425,  1298.268), // = (  324 x 458  ) mm  = ( 12.76 x 18.03 ) in
2164
		'C4'                     => array(  649.134,   918.425), // = (  229 x 324  ) mm  = (  9.02 x 12.76 ) in
2165
		'C5'                     => array(  459.213,   649.134), // = (  162 x 229  ) mm  = (  6.38 x 9.02  ) in
2166
		'C6'                     => array(  323.150,   459.213), // = (  114 x 162  ) mm  = (  4.49 x 6.38  ) in
2167
		'C7'                     => array(  229.606,   323.150), // = (   81 x 114  ) mm  = (  3.19 x 4.49  ) in
2168
		'C8'                     => array(  161.575,   229.606), // = (   57 x 81   ) mm  = (  2.24 x 3.19  ) in
2169
		'C9'                     => array(  113.386,   161.575), // = (   40 x 57   ) mm  = (  1.57 x 2.24  ) in
2170
		'C10'                    => array(   79.370,   113.386), // = (   28 x 40   ) mm  = (  1.10 x 1.57  ) in
2171
		'C11'                    => array(   56.693,    79.370), // = (   20 x 28   ) mm  = (  0.79 x 1.10  ) in
2172
		'C12'                    => array(   39.685,    56.693), // = (   14 x 20   ) mm  = (  0.55 x 0.79  ) in
2173
		'C76'                    => array(  229.606,   459.213), // = (   81 x 162  ) mm  = (  3.19 x 6.38  ) in
2174
		'DL'                     => array(  311.811,   623.622), // = (  110 x 220  ) mm  = (  4.33 x 8.66  ) in
2175
		'DLE'                    => array(  323.150,   637.795), // = (  114 x 225  ) mm  = (  4.49 x 8.86  ) in
2176
		'DLX'                    => array(  340.158,   666.142), // = (  120 x 235  ) mm  = (  4.72 x 9.25  ) in
2177
		'DLP'                    => array(  280.630,   595.276), // = (   99 x 210  ) mm  = (  3.90 x 8.27  ) in (1/3 A4)
2178
		// SIS 014711 E Series
2179
		'E0'                     => array( 2491.654,  3517.795), // = (  879 x 1241 ) mm  = ( 34.61 x 48.86 ) in
2180
		'E1'                     => array( 1757.480,  2491.654), // = (  620 x 879  ) mm  = ( 24.41 x 34.61 ) in
2181
		'E2'                     => array( 1247.244,  1757.480), // = (  440 x 620  ) mm  = ( 17.32 x 24.41 ) in
2182
		'E3'                     => array(  878.740,  1247.244), // = (  310 x 440  ) mm  = ( 12.20 x 17.32 ) in
2183
		'E4'                     => array(  623.622,   878.740), // = (  220 x 310  ) mm  = (  8.66 x 12.20 ) in
2184
		'E5'                     => array(  439.370,   623.622), // = (  155 x 220  ) mm  = (  6.10 x 8.66  ) in
2185
		'E6'                     => array(  311.811,   439.370), // = (  110 x 155  ) mm  = (  4.33 x 6.10  ) in
2186
		'E7'                     => array(  221.102,   311.811), // = (   78 x 110  ) mm  = (  3.07 x 4.33  ) in
2187
		'E8'                     => array(  155.906,   221.102), // = (   55 x 78   ) mm  = (  2.17 x 3.07  ) in
2188
		'E9'                     => array(  110.551,   155.906), // = (   39 x 55   ) mm  = (  1.54 x 2.17  ) in
2189
		'E10'                    => array(   76.535,   110.551), // = (   27 x 39   ) mm  = (  1.06 x 1.54  ) in
2190
		'E11'                    => array(   53.858,    76.535), // = (   19 x 27   ) mm  = (  0.75 x 1.06  ) in
2191
		'E12'                    => array(   36.850,    53.858), // = (   13 x 19   ) mm  = (  0.51 x 0.75  ) in
2192
		// SIS 014711 G Series
2193
		'G0'                     => array( 2715.591,  3838.110), // = (  958 x 1354 ) mm  = ( 37.72 x 53.31 ) in
2194
		'G1'                     => array( 1919.055,  2715.591), // = (  677 x 958  ) mm  = ( 26.65 x 37.72 ) in
2195
		'G2'                     => array( 1357.795,  1919.055), // = (  479 x 677  ) mm  = ( 18.86 x 26.65 ) in
2196
		'G3'                     => array(  958.110,  1357.795), // = (  338 x 479  ) mm  = ( 13.31 x 18.86 ) in
2197
		'G4'                     => array(  677.480,   958.110), // = (  239 x 338  ) mm  = (  9.41 x 13.31 ) in
2198
		'G5'                     => array(  479.055,   677.480), // = (  169 x 239  ) mm  = (  6.65 x 9.41  ) in
2199
		'G6'                     => array(  337.323,   479.055), // = (  119 x 169  ) mm  = (  4.69 x 6.65  ) in
2200
		'G7'                     => array(  238.110,   337.323), // = (   84 x 119  ) mm  = (  3.31 x 4.69  ) in
2201
		'G8'                     => array(  167.244,   238.110), // = (   59 x 84   ) mm  = (  2.32 x 3.31  ) in
2202
		'G9'                     => array(  119.055,   167.244), // = (   42 x 59   ) mm  = (  1.65 x 2.32  ) in
2203
		'G10'                    => array(   82.205,   119.055), // = (   29 x 42   ) mm  = (  1.14 x 1.65  ) in
2204
		'G11'                    => array(   59.528,    82.205), // = (   21 x 29   ) mm  = (  0.83 x 1.14  ) in
2205
		'G12'                    => array(   39.685,    59.528), // = (   14 x 21   ) mm  = (  0.55 x 0.83  ) in
2206
		// ISO Press
2207
		'RA0'                    => array( 2437.795,  3458.268), // = (  860 x 1220 ) mm  = ( 33.86 x 48.03 ) in
2208
		'RA1'                    => array( 1729.134,  2437.795), // = (  610 x 860  ) mm  = ( 24.02 x 33.86 ) in
2209
		'RA2'                    => array( 1218.898,  1729.134), // = (  430 x 610  ) mm  = ( 16.93 x 24.02 ) in
2210
		'RA3'                    => array(  864.567,  1218.898), // = (  305 x 430  ) mm  = ( 12.01 x 16.93 ) in
2211
		'RA4'                    => array(  609.449,   864.567), // = (  215 x 305  ) mm  = (  8.46 x 12.01 ) in
2212
		'SRA0'                   => array( 2551.181,  3628.346), // = (  900 x 1280 ) mm  = ( 35.43 x 50.39 ) in
2213
		'SRA1'                   => array( 1814.173,  2551.181), // = (  640 x 900  ) mm  = ( 25.20 x 35.43 ) in
2214
		'SRA2'                   => array( 1275.591,  1814.173), // = (  450 x 640  ) mm  = ( 17.72 x 25.20 ) in
2215
		'SRA3'                   => array(  907.087,  1275.591), // = (  320 x 450  ) mm  = ( 12.60 x 17.72 ) in
2216
		'SRA4'                   => array(  637.795,   907.087), // = (  225 x 320  ) mm  = (  8.86 x 12.60 ) in
2217
		// German DIN 476
2218
		'4A0'                    => array( 4767.874,  6740.787), // = ( 1682 x 2378 ) mm  = ( 66.22 x 93.62 ) in
2219
		'2A0'                    => array( 3370.394,  4767.874), // = ( 1189 x 1682 ) mm  = ( 46.81 x 66.22 ) in
2220
		// Variations on the ISO Standard
2221
		'A2_EXTRA'               => array( 1261.417,  1754.646), // = (  445 x 619  ) mm  = ( 17.52 x 24.37 ) in
2222
		'A3+'                    => array(  932.598,  1369.134), // = (  329 x 483  ) mm  = ( 12.95 x 19.02 ) in
2223
		'A3_EXTRA'               => array(  912.756,  1261.417), // = (  322 x 445  ) mm  = ( 12.68 x 17.52 ) in
2224
		'A3_SUPER'               => array(  864.567,  1440.000), // = (  305 x 508  ) mm  = ( 12.01 x 20.00 ) in
2225
		'SUPER_A3'               => array(  864.567,  1380.472), // = (  305 x 487  ) mm  = ( 12.01 x 19.17 ) in
2226
		'A4_EXTRA'               => array(  666.142,   912.756), // = (  235 x 322  ) mm  = (  9.25 x 12.68 ) in
2227
		'A4_SUPER'               => array(  649.134,   912.756), // = (  229 x 322  ) mm  = (  9.02 x 12.68 ) in
2228
		'SUPER_A4'               => array(  643.465,  1009.134), // = (  227 x 356  ) mm  = (  8.94 x 14.02 ) in
2229
		'A4_LONG'                => array(  595.276,   986.457), // = (  210 x 348  ) mm  = (  8.27 x 13.70 ) in
2230
		'F4'                     => array(  595.276,   935.433), // = (  210 x 330  ) mm  = (  8.27 x 12.99 ) in
2231
		'SO_B5_EXTRA'            => array(  572.598,   782.362), // = (  202 x 276  ) mm  = (  7.95 x 10.87 ) in
2232
		'A5_EXTRA'               => array(  490.394,   666.142), // = (  173 x 235  ) mm  = (  6.81 x 9.25  ) in
2233
		// ANSI Series
2234
		'ANSI_E'                 => array( 2448.000,  3168.000), // = (  864 x 1118 ) mm  = ( 34.00 x 44.00 ) in
2235
		'ANSI_D'                 => array( 1584.000,  2448.000), // = (  559 x 864  ) mm  = ( 22.00 x 34.00 ) in
2236
		'ANSI_C'                 => array( 1224.000,  1584.000), // = (  432 x 559  ) mm  = ( 17.00 x 22.00 ) in
2237
		'ANSI_B'                 => array(  792.000,  1224.000), // = (  279 x 432  ) mm  = ( 11.00 x 17.00 ) in
2238
		'ANSI_A'                 => array(  612.000,   792.000), // = (  216 x 279  ) mm  = (  8.50 x 11.00 ) in
2239
		// Traditional 'Loose' North American Paper Sizes
2240
		'USLEDGER'               => array( 1224.000,   792.000), // = (  432 x 279  ) mm  = ( 17.00 x 11.00 ) in
2241
		'LEDGER'                 => array( 1224.000,   792.000), // = (  432 x 279  ) mm  = ( 17.00 x 11.00 ) in
2242
		'ORGANIZERK'             => array(  792.000,  1224.000), // = (  279 x 432  ) mm  = ( 11.00 x 17.00 ) in
2243
		'BIBLE'                  => array(  792.000,  1224.000), // = (  279 x 432  ) mm  = ( 11.00 x 17.00 ) in
2244
		'USTABLOID'              => array(  792.000,  1224.000), // = (  279 x 432  ) mm  = ( 11.00 x 17.00 ) in
2245
		'TABLOID'                => array(  792.000,  1224.000), // = (  279 x 432  ) mm  = ( 11.00 x 17.00 ) in
2246
		'ORGANIZERM'             => array(  612.000,   792.000), // = (  216 x 279  ) mm  = (  8.50 x 11.00 ) in
2247
		'USLETTER'               => array(  612.000,   792.000), // = (  216 x 279  ) mm  = (  8.50 x 11.00 ) in
2248
		'LETTER'                 => array(  612.000,   792.000), // = (  216 x 279  ) mm  = (  8.50 x 11.00 ) in
2249
		'USLEGAL'                => array(  612.000,  1008.000), // = (  216 x 356  ) mm  = (  8.50 x 14.00 ) in
2250
		'LEGAL'                  => array(  612.000,  1008.000), // = (  216 x 356  ) mm  = (  8.50 x 14.00 ) in
2251
		'GOVERNMENTLETTER'       => array(  576.000,   756.000), // = (  203 x 267  ) mm  = (  8.00 x 10.50 ) in
2252
		'GLETTER'                => array(  576.000,   756.000), // = (  203 x 267  ) mm  = (  8.00 x 10.50 ) in
2253
		'JUNIORLEGAL'            => array(  576.000,   360.000), // = (  203 x 127  ) mm  = (  8.00 x 5.00  ) in
2254
		'JLEGAL'                 => array(  576.000,   360.000), // = (  203 x 127  ) mm  = (  8.00 x 5.00  ) in
2255
		// Other North American Paper Sizes
2256
		'QUADDEMY'               => array( 2520.000,  3240.000), // = (  889 x 1143 ) mm  = ( 35.00 x 45.00 ) in
2257
		'SUPER_B'                => array(  936.000,  1368.000), // = (  330 x 483  ) mm  = ( 13.00 x 19.00 ) in
2258
		'QUARTO'                 => array(  648.000,   792.000), // = (  229 x 279  ) mm  = (  9.00 x 11.00 ) in
2259
		'GOVERNMENTLEGAL'        => array(  612.000,   936.000), // = (  216 x 330  ) mm  = (  8.50 x 13.00 ) in
2260
		'FOLIO'                  => array(  612.000,   936.000), // = (  216 x 330  ) mm  = (  8.50 x 13.00 ) in
2261
		'MONARCH'                => array(  522.000,   756.000), // = (  184 x 267  ) mm  = (  7.25 x 10.50 ) in
2262
		'EXECUTIVE'              => array(  522.000,   756.000), // = (  184 x 267  ) mm  = (  7.25 x 10.50 ) in
2263
		'ORGANIZERL'             => array(  396.000,   612.000), // = (  140 x 216  ) mm  = (  5.50 x 8.50  ) in
2264
		'STATEMENT'              => array(  396.000,   612.000), // = (  140 x 216  ) mm  = (  5.50 x 8.50  ) in
2265
		'MEMO'                   => array(  396.000,   612.000), // = (  140 x 216  ) mm  = (  5.50 x 8.50  ) in
2266
		'FOOLSCAP'               => array(  595.440,   936.000), // = (  210 x 330  ) mm  = (  8.27 x 13.00 ) in
2267
		'COMPACT'                => array(  306.000,   486.000), // = (  108 x 171  ) mm  = (  4.25 x 6.75  ) in
2268
		'ORGANIZERJ'             => array(  198.000,   360.000), // = (   70 x 127  ) mm  = (  2.75 x 5.00  ) in
2269
		// Canadian standard CAN 2-9.60M
2270
		'P1'                     => array( 1587.402,  2437.795), // = (  560 x 860  ) mm  = ( 22.05 x 33.86 ) in
2271
		'P2'                     => array( 1218.898,  1587.402), // = (  430 x 560  ) mm  = ( 16.93 x 22.05 ) in
2272
		'P3'                     => array(  793.701,  1218.898), // = (  280 x 430  ) mm  = ( 11.02 x 16.93 ) in
2273
		'P4'                     => array(  609.449,   793.701), // = (  215 x 280  ) mm  = (  8.46 x 11.02 ) in
2274
		'P5'                     => array(  396.850,   609.449), // = (  140 x 215  ) mm  = (  5.51 x 8.46  ) in
2275
		'P6'                     => array(  303.307,   396.850), // = (  107 x 140  ) mm  = (  4.21 x 5.51  ) in
2276
		// North American Architectural Sizes
2277
		'ARCH_E'                 => array( 2592.000,  3456.000), // = (  914 x 1219 ) mm  = ( 36.00 x 48.00 ) in
2278
		'ARCH_E1'                => array( 2160.000,  3024.000), // = (  762 x 1067 ) mm  = ( 30.00 x 42.00 ) in
2279
		'ARCH_D'                 => array( 1728.000,  2592.000), // = (  610 x 914  ) mm  = ( 24.00 x 36.00 ) in
2280
		'BROADSHEET'             => array( 1296.000,  1728.000), // = (  457 x 610  ) mm  = ( 18.00 x 24.00 ) in
2281
		'ARCH_C'                 => array( 1296.000,  1728.000), // = (  457 x 610  ) mm  = ( 18.00 x 24.00 ) in
2282
		'ARCH_B'                 => array(  864.000,  1296.000), // = (  305 x 457  ) mm  = ( 12.00 x 18.00 ) in
2283
		'ARCH_A'                 => array(  648.000,   864.000), // = (  229 x 305  ) mm  = (  9.00 x 12.00 ) in
2284
		// -- North American Envelope Sizes
2285
		// - Announcement Envelopes
2286
		'ANNENV_A2'              => array(  314.640,   414.000), // = (  111 x 146  ) mm  = (  4.37 x 5.75  ) in
2287
		'ANNENV_A6'              => array(  342.000,   468.000), // = (  121 x 165  ) mm  = (  4.75 x 6.50  ) in
2288
		'ANNENV_A7'              => array(  378.000,   522.000), // = (  133 x 184  ) mm  = (  5.25 x 7.25  ) in
2289
		'ANNENV_A8'              => array(  396.000,   584.640), // = (  140 x 206  ) mm  = (  5.50 x 8.12  ) in
2290
		'ANNENV_A10'             => array(  450.000,   692.640), // = (  159 x 244  ) mm  = (  6.25 x 9.62  ) in
2291
		'ANNENV_SLIM'            => array(  278.640,   638.640), // = (   98 x 225  ) mm  = (  3.87 x 8.87  ) in
2292
		// - Commercial Envelopes
2293
		'COMMENV_N6_1/4'         => array(  252.000,   432.000), // = (   89 x 152  ) mm  = (  3.50 x 6.00  ) in
2294
		'COMMENV_N6_3/4'         => array(  260.640,   468.000), // = (   92 x 165  ) mm  = (  3.62 x 6.50  ) in
2295
		'COMMENV_N8'             => array(  278.640,   540.000), // = (   98 x 191  ) mm  = (  3.87 x 7.50  ) in
2296
		'COMMENV_N9'             => array(  278.640,   638.640), // = (   98 x 225  ) mm  = (  3.87 x 8.87  ) in
2297
		'COMMENV_N10'            => array(  296.640,   684.000), // = (  105 x 241  ) mm  = (  4.12 x 9.50  ) in
2298
		'COMMENV_N11'            => array(  324.000,   746.640), // = (  114 x 263  ) mm  = (  4.50 x 10.37 ) in
2299
		'COMMENV_N12'            => array(  342.000,   792.000), // = (  121 x 279  ) mm  = (  4.75 x 11.00 ) in
2300
		'COMMENV_N14'            => array(  360.000,   828.000), // = (  127 x 292  ) mm  = (  5.00 x 11.50 ) in
2301
		// - Catalogue Envelopes
2302
		'CATENV_N1'              => array(  432.000,   648.000), // = (  152 x 229  ) mm  = (  6.00 x 9.00  ) in
2303
		'CATENV_N1_3/4'          => array(  468.000,   684.000), // = (  165 x 241  ) mm  = (  6.50 x 9.50  ) in
2304
		'CATENV_N2'              => array(  468.000,   720.000), // = (  165 x 254  ) mm  = (  6.50 x 10.00 ) in
2305
		'CATENV_N3'              => array(  504.000,   720.000), // = (  178 x 254  ) mm  = (  7.00 x 10.00 ) in
2306
		'CATENV_N6'              => array(  540.000,   756.000), // = (  191 x 267  ) mm  = (  7.50 x 10.50 ) in
2307
		'CATENV_N7'              => array(  576.000,   792.000), // = (  203 x 279  ) mm  = (  8.00 x 11.00 ) in
2308
		'CATENV_N8'              => array(  594.000,   810.000), // = (  210 x 286  ) mm  = (  8.25 x 11.25 ) in
2309
		'CATENV_N9_1/2'          => array(  612.000,   756.000), // = (  216 x 267  ) mm  = (  8.50 x 10.50 ) in
2310
		'CATENV_N9_3/4'          => array(  630.000,   810.000), // = (  222 x 286  ) mm  = (  8.75 x 11.25 ) in
2311
		'CATENV_N10_1/2'         => array(  648.000,   864.000), // = (  229 x 305  ) mm  = (  9.00 x 12.00 ) in
2312
		'CATENV_N12_1/2'         => array(  684.000,   900.000), // = (  241 x 318  ) mm  = (  9.50 x 12.50 ) in
2313
		'CATENV_N13_1/2'         => array(  720.000,   936.000), // = (  254 x 330  ) mm  = ( 10.00 x 13.00 ) in
2314
		'CATENV_N14_1/4'         => array(  810.000,   882.000), // = (  286 x 311  ) mm  = ( 11.25 x 12.25 ) in
2315
		'CATENV_N14_1/2'         => array(  828.000,  1044.000), // = (  292 x 368  ) mm  = ( 11.50 x 14.50 ) in
2316
		// Japanese (JIS P 0138-61) Standard B-Series
2317
		'JIS_B0'                 => array( 2919.685,  4127.244), // = ( 1030 x 1456 ) mm  = ( 40.55 x 57.32 ) in
2318
		'JIS_B1'                 => array( 2063.622,  2919.685), // = (  728 x 1030 ) mm  = ( 28.66 x 40.55 ) in
2319
		'JIS_B2'                 => array( 1459.843,  2063.622), // = (  515 x 728  ) mm  = ( 20.28 x 28.66 ) in
2320
		'JIS_B3'                 => array( 1031.811,  1459.843), // = (  364 x 515  ) mm  = ( 14.33 x 20.28 ) in
2321
		'JIS_B4'                 => array(  728.504,  1031.811), // = (  257 x 364  ) mm  = ( 10.12 x 14.33 ) in
2322
		'JIS_B5'                 => array(  515.906,   728.504), // = (  182 x 257  ) mm  = (  7.17 x 10.12 ) in
2323
		'JIS_B6'                 => array(  362.835,   515.906), // = (  128 x 182  ) mm  = (  5.04 x 7.17  ) in
2324
		'JIS_B7'                 => array(  257.953,   362.835), // = (   91 x 128  ) mm  = (  3.58 x 5.04  ) in
2325
		'JIS_B8'                 => array(  181.417,   257.953), // = (   64 x 91   ) mm  = (  2.52 x 3.58  ) in
2326
		'JIS_B9'                 => array(  127.559,   181.417), // = (   45 x 64   ) mm  = (  1.77 x 2.52  ) in
2327
		'JIS_B10'                => array(   90.709,   127.559), // = (   32 x 45   ) mm  = (  1.26 x 1.77  ) in
2328
		'JIS_B11'                => array(   62.362,    90.709), // = (   22 x 32   ) mm  = (  0.87 x 1.26  ) in
2329
		'JIS_B12'                => array(   45.354,    62.362), // = (   16 x 22   ) mm  = (  0.63 x 0.87  ) in
2330
		// PA Series
2331
		'PA0'                    => array( 2381.102,  3174.803), // = (  840 x 1120 ) mm  = ( 33.07 x 44.09 ) in
2332
		'PA1'                    => array( 1587.402,  2381.102), // = (  560 x 840  ) mm  = ( 22.05 x 33.07 ) in
2333
		'PA2'                    => array( 1190.551,  1587.402), // = (  420 x 560  ) mm  = ( 16.54 x 22.05 ) in
2334
		'PA3'                    => array(  793.701,  1190.551), // = (  280 x 420  ) mm  = ( 11.02 x 16.54 ) in
2335
		'PA4'                    => array(  595.276,   793.701), // = (  210 x 280  ) mm  = (  8.27 x 11.02 ) in
2336
		'PA5'                    => array(  396.850,   595.276), // = (  140 x 210  ) mm  = (  5.51 x 8.27  ) in
2337
		'PA6'                    => array(  297.638,   396.850), // = (  105 x 140  ) mm  = (  4.13 x 5.51  ) in
2338
		'PA7'                    => array(  198.425,   297.638), // = (   70 x 105  ) mm  = (  2.76 x 4.13  ) in
2339
		'PA8'                    => array(  147.402,   198.425), // = (   52 x 70   ) mm  = (  2.05 x 2.76  ) in
2340
		'PA9'                    => array(   99.213,   147.402), // = (   35 x 52   ) mm  = (  1.38 x 2.05  ) in
2341
		'PA10'                   => array(   73.701,    99.213), // = (   26 x 35   ) mm  = (  1.02 x 1.38  ) in
2342
		// Standard Photographic Print Sizes
2343
		'PASSPORT_PHOTO'         => array(   99.213,   127.559), // = (   35 x 45   ) mm  = (  1.38 x 1.77  ) in
2344
		'E'                      => array(  233.858,   340.157), // = (   82 x 120  ) mm  = (  3.25 x 4.72  ) in
2345
		'L'                      => array(  252.283,   360.000), // = (   89 x 127  ) mm  = (  3.50 x 5.00  ) in
2346
		'3R'                     => array(  252.283,   360.000), // = (   89 x 127  ) mm  = (  3.50 x 5.00  ) in
2347
		'KG'                     => array(  289.134,   430.866), // = (  102 x 152  ) mm  = (  4.02 x 5.98  ) in
2348
		'4R'                     => array(  289.134,   430.866), // = (  102 x 152  ) mm  = (  4.02 x 5.98  ) in
2349
		'4D'                     => array(  340.157,   430.866), // = (  120 x 152  ) mm  = (  4.72 x 5.98  ) in
2350
		'2L'                     => array(  360.000,   504.567), // = (  127 x 178  ) mm  = (  5.00 x 7.01  ) in
2351
		'5R'                     => array(  360.000,   504.567), // = (  127 x 178  ) mm  = (  5.00 x 7.01  ) in
2352
		'8P'                     => array(  430.866,   575.433), // = (  152 x 203  ) mm  = (  5.98 x 7.99  ) in
2353
		'6R'                     => array(  430.866,   575.433), // = (  152 x 203  ) mm  = (  5.98 x 7.99  ) in
2354
		'6P'                     => array(  575.433,   720.000), // = (  203 x 254  ) mm  = (  7.99 x 10.00 ) in
2355
		'8R'                     => array(  575.433,   720.000), // = (  203 x 254  ) mm  = (  7.99 x 10.00 ) in
2356
		'6PW'                    => array(  575.433,   864.567), // = (  203 x 305  ) mm  = (  7.99 x 12.01 ) in
2357
		'S8R'                    => array(  575.433,   864.567), // = (  203 x 305  ) mm  = (  7.99 x 12.01 ) in
2358
		'4P'                     => array(  720.000,   864.567), // = (  254 x 305  ) mm  = ( 10.00 x 12.01 ) in
2359
		'10R'                    => array(  720.000,   864.567), // = (  254 x 305  ) mm  = ( 10.00 x 12.01 ) in
2360
		'4PW'                    => array(  720.000,  1080.000), // = (  254 x 381  ) mm  = ( 10.00 x 15.00 ) in
2361
		'S10R'                   => array(  720.000,  1080.000), // = (  254 x 381  ) mm  = ( 10.00 x 15.00 ) in
2362
		'11R'                    => array(  790.866,  1009.134), // = (  279 x 356  ) mm  = ( 10.98 x 14.02 ) in
2363
		'S11R'                   => array(  790.866,  1224.567), // = (  279 x 432  ) mm  = ( 10.98 x 17.01 ) in
2364
		'12R'                    => array(  864.567,  1080.000), // = (  305 x 381  ) mm  = ( 12.01 x 15.00 ) in
2365
		'S12R'                   => array(  864.567,  1292.598), // = (  305 x 456  ) mm  = ( 12.01 x 17.95 ) in
2366
		// Common Newspaper Sizes
2367
		'NEWSPAPER_BROADSHEET'   => array( 2125.984,  1700.787), // = (  750 x 600  ) mm  = ( 29.53 x 23.62 ) in
2368
		'NEWSPAPER_BERLINER'     => array( 1332.283,   892.913), // = (  470 x 315  ) mm  = ( 18.50 x 12.40 ) in
2369
		'NEWSPAPER_TABLOID'      => array( 1218.898,   793.701), // = (  430 x 280  ) mm  = ( 16.93 x 11.02 ) in
2370
		'NEWSPAPER_COMPACT'      => array( 1218.898,   793.701), // = (  430 x 280  ) mm  = ( 16.93 x 11.02 ) in
2371
		// Business Cards
2372
		'CREDIT_CARD'            => array(  153.014,   242.646), // = (   54 x 86   ) mm  = (  2.13 x 3.37  ) in
2373
		'BUSINESS_CARD'          => array(  153.014,   242.646), // = (   54 x 86   ) mm  = (  2.13 x 3.37  ) in
2374
		'BUSINESS_CARD_ISO7810'  => array(  153.014,   242.646), // = (   54 x 86   ) mm  = (  2.13 x 3.37  ) in
2375
		'BUSINESS_CARD_ISO216'   => array(  147.402,   209.764), // = (   52 x 74   ) mm  = (  2.05 x 2.91  ) in
2376
		'BUSINESS_CARD_IT'       => array(  155.906,   240.945), // = (   55 x 85   ) mm  = (  2.17 x 3.35  ) in
2377
		'BUSINESS_CARD_UK'       => array(  155.906,   240.945), // = (   55 x 85   ) mm  = (  2.17 x 3.35  ) in
2378
		'BUSINESS_CARD_FR'       => array(  155.906,   240.945), // = (   55 x 85   ) mm  = (  2.17 x 3.35  ) in
2379
		'BUSINESS_CARD_DE'       => array(  155.906,   240.945), // = (   55 x 85   ) mm  = (  2.17 x 3.35  ) in
2380
		'BUSINESS_CARD_ES'       => array(  155.906,   240.945), // = (   55 x 85   ) mm  = (  2.17 x 3.35  ) in
2381
		'BUSINESS_CARD_CA'       => array(  144.567,   252.283), // = (   51 x 89   ) mm  = (  2.01 x 3.50  ) in
2382
		'BUSINESS_CARD_US'       => array(  144.567,   252.283), // = (   51 x 89   ) mm  = (  2.01 x 3.50  ) in
2383
		'BUSINESS_CARD_JP'       => array(  155.906,   257.953), // = (   55 x 91   ) mm  = (  2.17 x 3.58  ) in
2384
		'BUSINESS_CARD_HK'       => array(  153.071,   255.118), // = (   54 x 90   ) mm  = (  2.13 x 3.54  ) in
2385
		'BUSINESS_CARD_AU'       => array(  155.906,   255.118), // = (   55 x 90   ) mm  = (  2.17 x 3.54  ) in
2386
		'BUSINESS_CARD_DK'       => array(  155.906,   255.118), // = (   55 x 90   ) mm  = (  2.17 x 3.54  ) in
2387
		'BUSINESS_CARD_SE'       => array(  155.906,   255.118), // = (   55 x 90   ) mm  = (  2.17 x 3.54  ) in
2388
		'BUSINESS_CARD_RU'       => array(  141.732,   255.118), // = (   50 x 90   ) mm  = (  1.97 x 3.54  ) in
2389
		'BUSINESS_CARD_CZ'       => array(  141.732,   255.118), // = (   50 x 90   ) mm  = (  1.97 x 3.54  ) in
2390
		'BUSINESS_CARD_FI'       => array(  141.732,   255.118), // = (   50 x 90   ) mm  = (  1.97 x 3.54  ) in
2391
		'BUSINESS_CARD_HU'       => array(  141.732,   255.118), // = (   50 x 90   ) mm  = (  1.97 x 3.54  ) in
2392
		'BUSINESS_CARD_IL'       => array(  141.732,   255.118), // = (   50 x 90   ) mm  = (  1.97 x 3.54  ) in
2393
		// Billboards
2394
		'4SHEET'                 => array( 2880.000,  4320.000), // = ( 1016 x 1524 ) mm  = ( 40.00 x 60.00 ) in
2395
		'6SHEET'                 => array( 3401.575,  5102.362), // = ( 1200 x 1800 ) mm  = ( 47.24 x 70.87 ) in
2396
		'12SHEET'                => array( 8640.000,  4320.000), // = ( 3048 x 1524 ) mm  = (120.00 x 60.00 ) in
2397
		'16SHEET'                => array( 5760.000,  8640.000), // = ( 2032 x 3048 ) mm  = ( 80.00 x 120.00) in
2398
		'32SHEET'                => array(11520.000,  8640.000), // = ( 4064 x 3048 ) mm  = (160.00 x 120.00) in
2399
		'48SHEET'                => array(17280.000,  8640.000), // = ( 6096 x 3048 ) mm  = (240.00 x 120.00) in
2400
		'64SHEET'                => array(23040.000,  8640.000), // = ( 8128 x 3048 ) mm  = (320.00 x 120.00) in
2401
		'96SHEET'                => array(34560.000,  8640.000), // = (12192 x 3048 ) mm  = (480.00 x 120.00) in
2402
		// -- Old European Sizes
2403
		// - Old Imperial English Sizes
2404
		'EN_EMPEROR'             => array( 3456.000,  5184.000), // = ( 1219 x 1829 ) mm  = ( 48.00 x 72.00 ) in
2405
		'EN_ANTIQUARIAN'         => array( 2232.000,  3816.000), // = (  787 x 1346 ) mm  = ( 31.00 x 53.00 ) in
2406
		'EN_GRAND_EAGLE'         => array( 2070.000,  3024.000), // = (  730 x 1067 ) mm  = ( 28.75 x 42.00 ) in
2407
		'EN_DOUBLE_ELEPHANT'     => array( 1926.000,  2880.000), // = (  679 x 1016 ) mm  = ( 26.75 x 40.00 ) in
2408
		'EN_ATLAS'               => array( 1872.000,  2448.000), // = (  660 x 864  ) mm  = ( 26.00 x 34.00 ) in
2409
		'EN_COLOMBIER'           => array( 1692.000,  2484.000), // = (  597 x 876  ) mm  = ( 23.50 x 34.50 ) in
2410
		'EN_ELEPHANT'            => array( 1656.000,  2016.000), // = (  584 x 711  ) mm  = ( 23.00 x 28.00 ) in
2411
		'EN_DOUBLE_DEMY'         => array( 1620.000,  2556.000), // = (  572 x 902  ) mm  = ( 22.50 x 35.50 ) in
2412
		'EN_IMPERIAL'            => array( 1584.000,  2160.000), // = (  559 x 762  ) mm  = ( 22.00 x 30.00 ) in
2413
		'EN_PRINCESS'            => array( 1548.000,  2016.000), // = (  546 x 711  ) mm  = ( 21.50 x 28.00 ) in
2414
		'EN_CARTRIDGE'           => array( 1512.000,  1872.000), // = (  533 x 660  ) mm  = ( 21.00 x 26.00 ) in
2415
		'EN_DOUBLE_LARGE_POST'   => array( 1512.000,  2376.000), // = (  533 x 838  ) mm  = ( 21.00 x 33.00 ) in
2416
		'EN_ROYAL'               => array( 1440.000,  1800.000), // = (  508 x 635  ) mm  = ( 20.00 x 25.00 ) in
2417
		'EN_SHEET'               => array( 1404.000,  1692.000), // = (  495 x 597  ) mm  = ( 19.50 x 23.50 ) in
2418
		'EN_HALF_POST'           => array( 1404.000,  1692.000), // = (  495 x 597  ) mm  = ( 19.50 x 23.50 ) in
2419
		'EN_SUPER_ROYAL'         => array( 1368.000,  1944.000), // = (  483 x 686  ) mm  = ( 19.00 x 27.00 ) in
2420
		'EN_DOUBLE_POST'         => array( 1368.000,  2196.000), // = (  483 x 775  ) mm  = ( 19.00 x 30.50 ) in
2421
		'EN_MEDIUM'              => array( 1260.000,  1656.000), // = (  445 x 584  ) mm  = ( 17.50 x 23.00 ) in
2422
		'EN_DEMY'                => array( 1260.000,  1620.000), // = (  445 x 572  ) mm  = ( 17.50 x 22.50 ) in
2423
		'EN_LARGE_POST'          => array( 1188.000,  1512.000), // = (  419 x 533  ) mm  = ( 16.50 x 21.00 ) in
2424
		'EN_COPY_DRAUGHT'        => array( 1152.000,  1440.000), // = (  406 x 508  ) mm  = ( 16.00 x 20.00 ) in
2425
		'EN_POST'                => array( 1116.000,  1386.000), // = (  394 x 489  ) mm  = ( 15.50 x 19.25 ) in
2426
		'EN_CROWN'               => array( 1080.000,  1440.000), // = (  381 x 508  ) mm  = ( 15.00 x 20.00 ) in
2427
		'EN_PINCHED_POST'        => array( 1062.000,  1332.000), // = (  375 x 470  ) mm  = ( 14.75 x 18.50 ) in
2428
		'EN_BRIEF'               => array(  972.000,  1152.000), // = (  343 x 406  ) mm  = ( 13.50 x 16.00 ) in
2429
		'EN_FOOLSCAP'            => array(  972.000,  1224.000), // = (  343 x 432  ) mm  = ( 13.50 x 17.00 ) in
2430
		'EN_SMALL_FOOLSCAP'      => array(  954.000,  1188.000), // = (  337 x 419  ) mm  = ( 13.25 x 16.50 ) in
2431
		'EN_POTT'                => array(  900.000,  1080.000), // = (  318 x 381  ) mm  = ( 12.50 x 15.00 ) in
2432
		// - Old Imperial Belgian Sizes
2433
		'BE_GRAND_AIGLE'         => array( 1984.252,  2948.031), // = (  700 x 1040 ) mm  = ( 27.56 x 40.94 ) in
2434
		'BE_COLOMBIER'           => array( 1757.480,  2409.449), // = (  620 x 850  ) mm  = ( 24.41 x 33.46 ) in
2435
		'BE_DOUBLE_CARRE'        => array( 1757.480,  2607.874), // = (  620 x 920  ) mm  = ( 24.41 x 36.22 ) in
2436
		'BE_ELEPHANT'            => array( 1746.142,  2182.677), // = (  616 x 770  ) mm  = ( 24.25 x 30.31 ) in
2437
		'BE_PETIT_AIGLE'         => array( 1700.787,  2381.102), // = (  600 x 840  ) mm  = ( 23.62 x 33.07 ) in
2438
		'BE_GRAND_JESUS'         => array( 1559.055,  2069.291), // = (  550 x 730  ) mm  = ( 21.65 x 28.74 ) in
2439
		'BE_JESUS'               => array( 1530.709,  2069.291), // = (  540 x 730  ) mm  = ( 21.26 x 28.74 ) in
2440
		'BE_RAISIN'              => array( 1417.323,  1842.520), // = (  500 x 650  ) mm  = ( 19.69 x 25.59 ) in
2441
		'BE_GRAND_MEDIAN'        => array( 1303.937,  1714.961), // = (  460 x 605  ) mm  = ( 18.11 x 23.82 ) in
2442
		'BE_DOUBLE_POSTE'        => array( 1233.071,  1601.575), // = (  435 x 565  ) mm  = ( 17.13 x 22.24 ) in
2443
		'BE_COQUILLE'            => array( 1218.898,  1587.402), // = (  430 x 560  ) mm  = ( 16.93 x 22.05 ) in
2444
		'BE_PETIT_MEDIAN'        => array( 1176.378,  1502.362), // = (  415 x 530  ) mm  = ( 16.34 x 20.87 ) in
2445
		'BE_RUCHE'               => array( 1020.472,  1303.937), // = (  360 x 460  ) mm  = ( 14.17 x 18.11 ) in
2446
		'BE_PROPATRIA'           => array(  977.953,  1218.898), // = (  345 x 430  ) mm  = ( 13.58 x 16.93 ) in
2447
		'BE_LYS'                 => array(  898.583,  1125.354), // = (  317 x 397  ) mm  = ( 12.48 x 15.63 ) in
2448
		'BE_POT'                 => array(  870.236,  1088.504), // = (  307 x 384  ) mm  = ( 12.09 x 15.12 ) in
2449
		'BE_ROSETTE'             => array(  765.354,   983.622), // = (  270 x 347  ) mm  = ( 10.63 x 13.66 ) in
2450
		// - Old Imperial French Sizes
2451
		'FR_UNIVERS'             => array( 2834.646,  3685.039), // = ( 1000 x 1300 ) mm  = ( 39.37 x 51.18 ) in
2452
		'FR_DOUBLE_COLOMBIER'    => array( 2551.181,  3571.654), // = (  900 x 1260 ) mm  = ( 35.43 x 49.61 ) in
2453
		'FR_GRANDE_MONDE'        => array( 2551.181,  3571.654), // = (  900 x 1260 ) mm  = ( 35.43 x 49.61 ) in
2454
		'FR_DOUBLE_SOLEIL'       => array( 2267.717,  3401.575), // = (  800 x 1200 ) mm  = ( 31.50 x 47.24 ) in
2455
		'FR_DOUBLE_JESUS'        => array( 2154.331,  3174.803), // = (  760 x 1120 ) mm  = ( 29.92 x 44.09 ) in
2456
		'FR_GRAND_AIGLE'         => array( 2125.984,  3004.724), // = (  750 x 1060 ) mm  = ( 29.53 x 41.73 ) in
2457
		'FR_PETIT_AIGLE'         => array( 1984.252,  2664.567), // = (  700 x 940  ) mm  = ( 27.56 x 37.01 ) in
2458
		'FR_DOUBLE_RAISIN'       => array( 1842.520,  2834.646), // = (  650 x 1000 ) mm  = ( 25.59 x 39.37 ) in
2459
		'FR_JOURNAL'             => array( 1842.520,  2664.567), // = (  650 x 940  ) mm  = ( 25.59 x 37.01 ) in
2460
		'FR_COLOMBIER_AFFICHE'   => array( 1785.827,  2551.181), // = (  630 x 900  ) mm  = ( 24.80 x 35.43 ) in
2461
		'FR_DOUBLE_CAVALIER'     => array( 1757.480,  2607.874), // = (  620 x 920  ) mm  = ( 24.41 x 36.22 ) in
2462
		'FR_CLOCHE'              => array( 1700.787,  2267.717), // = (  600 x 800  ) mm  = ( 23.62 x 31.50 ) in
2463
		'FR_SOLEIL'              => array( 1700.787,  2267.717), // = (  600 x 800  ) mm  = ( 23.62 x 31.50 ) in
2464
		'FR_DOUBLE_CARRE'        => array( 1587.402,  2551.181), // = (  560 x 900  ) mm  = ( 22.05 x 35.43 ) in
2465
		'FR_DOUBLE_COQUILLE'     => array( 1587.402,  2494.488), // = (  560 x 880  ) mm  = ( 22.05 x 34.65 ) in
2466
		'FR_JESUS'               => array( 1587.402,  2154.331), // = (  560 x 760  ) mm  = ( 22.05 x 29.92 ) in
2467
		'FR_RAISIN'              => array( 1417.323,  1842.520), // = (  500 x 650  ) mm  = ( 19.69 x 25.59 ) in
2468
		'FR_CAVALIER'            => array( 1303.937,  1757.480), // = (  460 x 620  ) mm  = ( 18.11 x 24.41 ) in
2469
		'FR_DOUBLE_COURONNE'     => array( 1303.937,  2040.945), // = (  460 x 720  ) mm  = ( 18.11 x 28.35 ) in
2470
		'FR_CARRE'               => array( 1275.591,  1587.402), // = (  450 x 560  ) mm  = ( 17.72 x 22.05 ) in
2471
		'FR_COQUILLE'            => array( 1247.244,  1587.402), // = (  440 x 560  ) mm  = ( 17.32 x 22.05 ) in
2472
		'FR_DOUBLE_TELLIERE'     => array( 1247.244,  1927.559), // = (  440 x 680  ) mm  = ( 17.32 x 26.77 ) in
2473
		'FR_DOUBLE_CLOCHE'       => array( 1133.858,  1700.787), // = (  400 x 600  ) mm  = ( 15.75 x 23.62 ) in
2474
		'FR_DOUBLE_POT'          => array( 1133.858,  1757.480), // = (  400 x 620  ) mm  = ( 15.75 x 24.41 ) in
2475
		'FR_ECU'                 => array( 1133.858,  1474.016), // = (  400 x 520  ) mm  = ( 15.75 x 20.47 ) in
2476
		'FR_COURONNE'            => array( 1020.472,  1303.937), // = (  360 x 460  ) mm  = ( 14.17 x 18.11 ) in
2477
		'FR_TELLIERE'            => array(  963.780,  1247.244), // = (  340 x 440  ) mm  = ( 13.39 x 17.32 ) in
2478
		'FR_POT'                 => array(  878.740,  1133.858), // = (  310 x 400  ) mm  = ( 12.20 x 15.75 ) in
2479
	);
2480
 
2481
 
2482
	/**
2483
	 * Get page dimensions from format name.
2484
	 * @param mixed $format The format name @see self::$page_format<ul>
2485
	 * @return array containing page width and height in points
2486
	 * @since 5.0.010 (2010-05-17)
2487
	 * @public static
2488
	 */
2489
	public static function getPageSizeFromFormat($format) {
2490
		if (isset(self::$page_formats[$format])) {
2491
			return self::$page_formats[$format];
2492
		}
2493
		return self::$page_formats['A4'];
2494
	}
2495
 
2496
	/**
2497
	 * Set page boundaries.
2498
	 * @param int $page page number
2499
	 * @param string $type valid values are: <ul><li>'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;</li><li>'CropBox' : the visible region of default user space;</li><li>'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;</li><li>'TrimBox' : the intended dimensions of the finished page after trimming;</li><li>'ArtBox' : the page's meaningful content (including potential white space).</li></ul>
2500
	 * @param float $llx lower-left x coordinate in user units.
2501
	 * @param float $lly lower-left y coordinate in user units.
2502
	 * @param float $urx upper-right x coordinate in user units.
2503
	 * @param float $ury upper-right y coordinate in user units.
2504
	 * @param boolean $points If true uses user units as unit of measure, otherwise uses PDF points.
2505
	 * @param float $k Scale factor (number of points in user unit).
2506
	 * @param array $pagedim Array of page dimensions.
2507
	 * @return array pagedim array of page dimensions.
2508
	 * @since 5.0.010 (2010-05-17)
2509
	 * @public static
2510
	 */
2511
	public static function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points, $k, $pagedim=array()) {
2512
		if (!isset($pagedim[$page])) {
2513
			// initialize array
2514
			$pagedim[$page] = array();
2515
		}
2516
		if (!in_array($type, self::$pageboxes)) {
2517
			return;
2518
		}
2519
		if ($points) {
2520
			$k = 1;
2521
		}
2522
		$pagedim[$page][$type]['llx'] = ($llx * $k);
2523
		$pagedim[$page][$type]['lly'] = ($lly * $k);
2524
		$pagedim[$page][$type]['urx'] = ($urx * $k);
2525
		$pagedim[$page][$type]['ury'] = ($ury * $k);
2526
		return $pagedim;
2527
	}
2528
 
2529
	/**
2530
	 * Swap X and Y coordinates of page boxes (change page boxes orientation).
2531
	 * @param int $page page number
2532
	 * @param array $pagedim Array of page dimensions.
2533
	 * @return array pagedim array of page dimensions.
2534
	 * @since 5.0.010 (2010-05-17)
2535
	 * @public static
2536
	 */
2537
	public static function swapPageBoxCoordinates($page, $pagedim) {
2538
		foreach (self::$pageboxes as $type) {
2539
			// swap X and Y coordinates
2540
			if (isset($pagedim[$page][$type])) {
2541
				$tmp = $pagedim[$page][$type]['llx'];
2542
				$pagedim[$page][$type]['llx'] = $pagedim[$page][$type]['lly'];
2543
				$pagedim[$page][$type]['lly'] = $tmp;
2544
				$tmp = $pagedim[$page][$type]['urx'];
2545
				$pagedim[$page][$type]['urx'] = $pagedim[$page][$type]['ury'];
2546
				$pagedim[$page][$type]['ury'] = $tmp;
2547
			}
2548
		}
2549
		return $pagedim;
2550
	}
2551
 
2552
	/**
2553
	 * Get the canonical page layout mode.
2554
	 * @param string $layout The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2555
	 * @return string Canonical page layout name.
2556
	 * @public static
2557
	 */
2558
	public static function getPageLayoutMode($layout='SinglePage') {
2559
		switch ($layout) {
2560
			case 'default':
2561
			case 'single':
2562
			case 'SinglePage': {
2563
				$layout_mode = 'SinglePage';
2564
				break;
2565
			}
2566
			case 'continuous':
2567
			case 'OneColumn': {
2568
				$layout_mode = 'OneColumn';
2569
				break;
2570
			}
2571
			case 'two':
2572
			case 'TwoColumnLeft': {
2573
				$layout_mode = 'TwoColumnLeft';
2574
				break;
2575
			}
2576
			case 'TwoColumnRight': {
2577
				$layout_mode = 'TwoColumnRight';
2578
				break;
2579
			}
2580
			case 'TwoPageLeft': {
2581
				$layout_mode = 'TwoPageLeft';
2582
				break;
2583
			}
2584
			case 'TwoPageRight': {
2585
				$layout_mode = 'TwoPageRight';
2586
				break;
2587
			}
2588
			default: {
2589
				$layout_mode = 'SinglePage';
2590
			}
2591
		}
2592
		return $layout_mode;
2593
	}
2594
 
2595
	/**
2596
	 * Get the canonical page layout mode.
2597
	 * @param string $mode A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2598
	 * @return string Canonical page mode name.
2599
	 * @public static
2600
	 */
2601
	public static function getPageMode($mode='UseNone') {
2602
		switch ($mode) {
2603
			case 'UseNone': {
2604
				$page_mode = 'UseNone';
2605
				break;
2606
			}
2607
			case 'UseOutlines': {
2608
				$page_mode = 'UseOutlines';
2609
				break;
2610
			}
2611
			case 'UseThumbs': {
2612
				$page_mode = 'UseThumbs';
2613
				break;
2614
			}
2615
			case 'FullScreen': {
2616
				$page_mode = 'FullScreen';
2617
				break;
2618
			}
2619
			case 'UseOC': {
2620
				$page_mode = 'UseOC';
2621
				break;
2622
			}
2623
			case '': {
2624
				$page_mode = 'UseAttachments';
2625
				break;
2626
			}
2627
			default: {
2628
				$page_mode = 'UseNone';
2629
			}
2630
		}
2631
		return $page_mode;
2632
	}
2633
 
2634
 
2635
} // END OF TCPDF_STATIC CLASS
2636
 
2637
//============================================================+
2638
// END OF FILE
2639
//============================================================+