1
|
|
|
<?php |
2
|
|
|
namespace Dompdf; |
3
|
|
|
|
4
|
|
|
class Helpers |
5
|
|
|
{ |
6
|
|
|
/** |
7
|
|
|
* print_r wrapper for html/cli output |
8
|
|
|
* |
9
|
|
|
* Wraps print_r() output in < pre > tags if the current sapi is not 'cli'. |
10
|
|
|
* Returns the output string instead of displaying it if $return is true. |
11
|
|
|
* |
12
|
|
|
* @param mixed $mixed variable or expression to display |
13
|
|
|
* @param bool $return |
14
|
|
|
* |
15
|
|
|
* @return string|null |
16
|
|
|
*/ |
17
|
|
|
public static function pre_r($mixed, $return = false) |
18
|
|
|
{ |
19
|
|
|
if ($return) { |
20
|
|
|
return "<pre>" . print_r($mixed, true) . "</pre>"; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
if (php_sapi_name() !== "cli") { |
24
|
|
|
echo "<pre>"; |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
print_r($mixed); |
28
|
|
|
|
29
|
|
|
if (php_sapi_name() !== "cli") { |
30
|
|
|
echo "</pre>"; |
31
|
|
|
} else { |
32
|
|
|
echo "\n"; |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
flush(); |
36
|
|
|
|
37
|
|
|
return null; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* builds a full url given a protocol, hostname, base path and url |
42
|
|
|
* |
43
|
|
|
* @param string $protocol |
44
|
|
|
* @param string $host |
45
|
|
|
* @param string $base_path |
46
|
|
|
* @param string $url |
47
|
|
|
* @return string |
48
|
|
|
* |
49
|
|
|
* Initially the trailing slash of $base_path was optional, and conditionally appended. |
50
|
|
|
* However on dynamically created sites, where the page is given as url parameter, |
51
|
|
|
* the base path might not end with an url. |
52
|
|
|
* Therefore do not append a slash, and **require** the $base_url to ending in a slash |
53
|
|
|
* when needed. |
54
|
|
|
* Vice versa, on using the local file system path of a file, make sure that the slash |
55
|
|
|
* is appended (o.k. also for Windows) |
56
|
|
|
*/ |
57
|
|
|
public static function build_url($protocol, $host, $base_path, $url) |
58
|
|
|
{ |
59
|
|
|
$protocol = mb_strtolower($protocol); |
60
|
|
|
if (strlen($url) == 0) { |
61
|
|
|
//return $protocol . $host . rtrim($base_path, "/\\") . "/"; |
|
|
|
|
62
|
|
|
return $protocol . $host . $base_path; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
// Is the url already fully qualified, a Data URI, or a reference to a named anchor? |
66
|
|
|
if (mb_strpos($url, "://") !== false || mb_substr($url, 0, 1) === "#" || mb_strpos($url, "data:") === 0 || mb_strpos($url, "mailto:") === 0) { |
67
|
|
|
return $url; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$ret = $protocol; |
71
|
|
|
|
72
|
|
|
if (!in_array(mb_strtolower($protocol), array("http://", "https://", "ftp://", "ftps://"))) { |
73
|
|
|
//On Windows local file, an abs path can begin also with a '\' or a drive letter and colon |
74
|
|
|
//drive: followed by a relative path would be a drive specific default folder. |
75
|
|
|
//not known in php app code, treat as abs path |
76
|
|
|
//($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/')) |
|
|
|
|
77
|
|
|
if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || (mb_strlen($url) > 1 && $url[0] !== '\\' && $url[1] !== ':'))) { |
78
|
|
|
// For rel path and local acess we ignore the host, and run the path through realpath() |
79
|
|
|
$ret .= realpath($base_path) . '/'; |
80
|
|
|
} |
81
|
|
|
$ret .= $url; |
82
|
|
|
$ret = preg_replace('/\?(.*)$/', "", $ret); |
83
|
|
|
return $ret; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
// Protocol relative urls (e.g. "//example.org/style.css") |
87
|
|
|
if (strpos($url, '//') === 0) { |
88
|
|
|
$ret .= substr($url, 2); |
89
|
|
|
//remote urls with backslash in html/css are not really correct, but lets be genereous |
90
|
|
|
} elseif ($url[0] === '/' || $url[0] === '\\') { |
91
|
|
|
// Absolute path |
92
|
|
|
$ret .= $host . $url; |
93
|
|
|
} else { |
94
|
|
|
// Relative path |
95
|
|
|
//$base_path = $base_path !== "" ? rtrim($base_path, "/\\") . "/" : ""; |
|
|
|
|
96
|
|
|
$ret .= $host . $base_path . $url; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $ret; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Builds a HTTP Content-Disposition header string using `$dispositionType` |
104
|
|
|
* and `$filename`. |
105
|
|
|
* |
106
|
|
|
* If the filename contains any characters not in the ISO-8859-1 character |
107
|
|
|
* set, a fallback filename will be included for clients not supporting the |
108
|
|
|
* `filename*` parameter. |
109
|
|
|
* |
110
|
|
|
* @param string $dispositionType |
111
|
|
|
* @param string $filename |
112
|
|
|
* @return string |
113
|
|
|
*/ |
114
|
|
|
public static function buildContentDispositionHeader($dispositionType, $filename) |
115
|
|
|
{ |
116
|
|
|
$encoding = mb_detect_encoding($filename); |
117
|
|
|
$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding); |
118
|
|
|
$fallbackfilename = str_replace("\"", "", $fallbackfilename); |
119
|
|
|
$encodedfilename = rawurlencode($filename); |
120
|
|
|
|
121
|
|
|
$contentDisposition = "Content-Disposition: $dispositionType; filename=\"$fallbackfilename\""; |
122
|
|
|
if ($fallbackfilename !== $filename) { |
123
|
|
|
$contentDisposition .= "; filename*=UTF-8''$encodedfilename"; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
return $contentDisposition; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Converts decimal numbers to roman numerals |
131
|
|
|
* |
132
|
|
|
* @param int $num |
133
|
|
|
* |
134
|
|
|
* @throws Exception |
135
|
|
|
* @return string |
136
|
|
|
*/ |
137
|
|
|
public static function dec2roman($num) |
138
|
|
|
{ |
139
|
|
|
|
140
|
|
|
static $ones = array("", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"); |
141
|
|
|
static $tens = array("", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"); |
142
|
|
|
static $hund = array("", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"); |
143
|
|
|
static $thou = array("", "m", "mm", "mmm"); |
144
|
|
|
|
145
|
|
|
if (!is_numeric($num)) { |
146
|
|
|
throw new Exception("dec2roman() requires a numeric argument."); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
if ($num > 4000 || $num < 0) { |
150
|
|
|
return "(out of range)"; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
$num = strrev((string)$num); |
154
|
|
|
|
155
|
|
|
$ret = ""; |
156
|
|
|
switch (mb_strlen($num)) { |
157
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
158
|
|
|
case 4: |
|
|
|
|
159
|
|
|
$ret .= $thou[$num[3]]; |
160
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
161
|
|
|
case 3: |
|
|
|
|
162
|
|
|
$ret .= $hund[$num[2]]; |
163
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
164
|
|
|
case 2: |
|
|
|
|
165
|
|
|
$ret .= $tens[$num[1]]; |
166
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
167
|
|
|
case 1: |
|
|
|
|
168
|
|
|
$ret .= $ones[$num[0]]; |
169
|
|
|
default: |
170
|
|
|
break; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return $ret; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Determines whether $value is a percentage or not |
178
|
|
|
* |
179
|
|
|
* @param float $value |
180
|
|
|
* |
181
|
|
|
* @return bool |
182
|
|
|
*/ |
183
|
|
|
public static function is_percent($value) |
184
|
|
|
{ |
185
|
|
|
return false !== mb_strpos($value, "%"); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Parses a data URI scheme |
190
|
|
|
* http://en.wikipedia.org/wiki/Data_URI_scheme |
191
|
|
|
* |
192
|
|
|
* @param string $data_uri The data URI to parse |
193
|
|
|
* |
194
|
|
|
* @return array|bool The result with charset, mime type and decoded data |
195
|
|
|
*/ |
196
|
|
|
public static function parse_data_uri($data_uri) |
197
|
|
|
{ |
198
|
|
|
if (!preg_match('/^data:(?P<mime>[a-z0-9\/+-.]+)(;charset=(?P<charset>[a-z0-9-])+)?(?P<base64>;base64)?\,(?P<data>.*)?/is', $data_uri, $match)) { |
199
|
|
|
return false; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
$match['data'] = rawurldecode($match['data']); |
203
|
|
|
$result = array( |
204
|
|
|
'charset' => $match['charset'] ? $match['charset'] : 'US-ASCII', |
205
|
|
|
'mime' => $match['mime'] ? $match['mime'] : 'text/plain', |
206
|
|
|
'data' => $match['base64'] ? base64_decode($match['data']) : $match['data'], |
207
|
|
|
); |
208
|
|
|
|
209
|
|
|
return $result; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Encodes a Uniform Resource Identifier (URI) by replacing non-alphanumeric |
214
|
|
|
* characters with a percent (%) sign followed by two hex digits, excepting |
215
|
|
|
* characters in the URI reserved character set. |
216
|
|
|
* |
217
|
|
|
* Assumes that the URI is a complete URI, so does not encode reserved |
218
|
|
|
* characters that have special meaning in the URI. |
219
|
|
|
* |
220
|
|
|
* Simulates the encodeURI function available in JavaScript |
221
|
|
|
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI |
222
|
|
|
* |
223
|
|
|
* Source: http://stackoverflow.com/q/4929584/264628 |
224
|
|
|
* |
225
|
|
|
* @param string $uri The URI to encode |
226
|
|
|
* @return string The original URL with special characters encoded |
227
|
|
|
*/ |
228
|
|
|
public static function encodeURI($uri) { |
229
|
|
|
$unescaped = array( |
230
|
|
|
'%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~', |
231
|
|
|
'%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')' |
232
|
|
|
); |
233
|
|
|
$reserved = array( |
234
|
|
|
'%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':', |
235
|
|
|
'%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$' |
236
|
|
|
); |
237
|
|
|
$score = array( |
238
|
|
|
'%23'=>'#' |
239
|
|
|
); |
240
|
|
|
return strtr(rawurlencode(rawurldecode($uri)), array_merge($reserved,$unescaped,$score)); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Decoder for RLE8 compression in windows bitmaps |
245
|
|
|
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp |
246
|
|
|
* |
247
|
|
|
* @param string $str Data to decode |
248
|
|
|
* @param integer $width Image width |
249
|
|
|
* |
250
|
|
|
* @return string |
251
|
|
|
*/ |
252
|
|
|
public static function rle8_decode($str, $width) |
253
|
|
|
{ |
254
|
|
|
$lineWidth = $width + (3 - ($width - 1) % 4); |
255
|
|
|
$out = ''; |
256
|
|
|
$cnt = strlen($str); |
257
|
|
|
|
258
|
|
|
for ($i = 0; $i < $cnt; $i++) { |
259
|
|
|
$o = ord($str[$i]); |
260
|
|
|
switch ($o) { |
261
|
|
|
case 0: # ESCAPE |
262
|
|
|
$i++; |
263
|
|
|
switch (ord($str[$i])) { |
264
|
|
View Code Duplication |
case 0: # NEW LINE |
|
|
|
|
265
|
|
|
$padCnt = $lineWidth - strlen($out) % $lineWidth; |
266
|
|
|
if ($padCnt < $lineWidth) { |
267
|
|
|
$out .= str_repeat(chr(0), $padCnt); # pad line |
268
|
|
|
} |
269
|
|
|
break; |
270
|
|
View Code Duplication |
case 1: # END OF FILE |
|
|
|
|
271
|
|
|
$padCnt = $lineWidth - strlen($out) % $lineWidth; |
272
|
|
|
if ($padCnt < $lineWidth) { |
273
|
|
|
$out .= str_repeat(chr(0), $padCnt); # pad line |
274
|
|
|
} |
275
|
|
|
break 3; |
276
|
|
|
case 2: # DELTA |
277
|
|
|
$i += 2; |
278
|
|
|
break; |
279
|
|
|
default: # ABSOLUTE MODE |
280
|
|
|
$num = ord($str[$i]); |
281
|
|
|
for ($j = 0; $j < $num; $j++) { |
282
|
|
|
$out .= $str[++$i]; |
283
|
|
|
} |
284
|
|
|
if ($num % 2) { |
285
|
|
|
$i++; |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
break; |
289
|
|
|
default: |
290
|
|
|
$out .= str_repeat($str[++$i], $o); |
291
|
|
|
} |
292
|
|
|
} |
293
|
|
|
return $out; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Decoder for RLE4 compression in windows bitmaps |
298
|
|
|
* see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp |
299
|
|
|
* |
300
|
|
|
* @param string $str Data to decode |
301
|
|
|
* @param integer $width Image width |
302
|
|
|
* |
303
|
|
|
* @return string |
304
|
|
|
*/ |
305
|
|
|
public static function rle4_decode($str, $width) |
306
|
|
|
{ |
307
|
|
|
$w = floor($width / 2) + ($width % 2); |
308
|
|
|
$lineWidth = $w + (3 - (($width - 1) / 2) % 4); |
309
|
|
|
$pixels = array(); |
310
|
|
|
$cnt = strlen($str); |
311
|
|
|
$c = 0; |
312
|
|
|
|
313
|
|
|
for ($i = 0; $i < $cnt; $i++) { |
314
|
|
|
$o = ord($str[$i]); |
315
|
|
|
switch ($o) { |
316
|
|
|
case 0: # ESCAPE |
317
|
|
|
$i++; |
318
|
|
|
switch (ord($str[$i])) { |
319
|
|
|
case 0: # NEW LINE |
320
|
|
|
while (count($pixels) % $lineWidth != 0) { |
321
|
|
|
$pixels[] = 0; |
322
|
|
|
} |
323
|
|
|
break; |
324
|
|
|
case 1: # END OF FILE |
325
|
|
|
while (count($pixels) % $lineWidth != 0) { |
326
|
|
|
$pixels[] = 0; |
327
|
|
|
} |
328
|
|
|
break 3; |
329
|
|
|
case 2: # DELTA |
330
|
|
|
$i += 2; |
331
|
|
|
break; |
332
|
|
|
default: # ABSOLUTE MODE |
333
|
|
|
$num = ord($str[$i]); |
334
|
|
|
for ($j = 0; $j < $num; $j++) { |
335
|
|
|
if ($j % 2 == 0) { |
336
|
|
|
$c = ord($str[++$i]); |
337
|
|
|
$pixels[] = ($c & 240) >> 4; |
338
|
|
|
} else { |
339
|
|
|
$pixels[] = $c & 15; |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
if ($num % 2 == 0) { |
344
|
|
|
$i++; |
345
|
|
|
} |
346
|
|
|
} |
347
|
|
|
break; |
348
|
|
|
default: |
349
|
|
|
$c = ord($str[++$i]); |
350
|
|
|
for ($j = 0; $j < $o; $j++) { |
351
|
|
|
$pixels[] = ($j % 2 == 0 ? ($c & 240) >> 4 : $c & 15); |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
$out = ''; |
357
|
|
|
if (count($pixels) % 2) { |
358
|
|
|
$pixels[] = 0; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
$cnt = count($pixels) / 2; |
362
|
|
|
|
363
|
|
|
for ($i = 0; $i < $cnt; $i++) { |
364
|
|
|
$out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]); |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
return $out; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* parse a full url or pathname and return an array(protocol, host, path, |
372
|
|
|
* file + query + fragment) |
373
|
|
|
* |
374
|
|
|
* @param string $url |
375
|
|
|
* @return array |
376
|
|
|
*/ |
377
|
|
|
public static function explode_url($url) |
|
|
|
|
378
|
|
|
{ |
379
|
|
|
$protocol = ""; |
|
|
|
|
380
|
|
|
$host = ""; |
381
|
|
|
$path = ""; |
382
|
|
|
$file = ""; |
383
|
|
|
|
384
|
|
|
$arr = parse_url($url); |
385
|
|
|
if ( isset($arr["scheme"]) ) { |
386
|
|
|
$arr["scheme"] = mb_strtolower($arr["scheme"]); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
// Exclude windows drive letters... |
390
|
|
|
if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && strlen($arr["scheme"]) > 1) { |
391
|
|
|
$protocol = $arr["scheme"] . "://"; |
392
|
|
|
|
393
|
|
|
if (isset($arr["user"])) { |
394
|
|
|
$host .= $arr["user"]; |
395
|
|
|
|
396
|
|
|
if (isset($arr["pass"])) { |
397
|
|
|
$host .= ":" . $arr["pass"]; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
$host .= "@"; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
if (isset($arr["host"])) { |
404
|
|
|
$host .= $arr["host"]; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
if (isset($arr["port"])) { |
408
|
|
|
$host .= ":" . $arr["port"]; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
if (isset($arr["path"]) && $arr["path"] !== "") { |
412
|
|
|
// Do we have a trailing slash? |
413
|
|
|
if ($arr["path"][mb_strlen($arr["path"]) - 1] === "/") { |
414
|
|
|
$path = $arr["path"]; |
415
|
|
|
$file = ""; |
416
|
|
|
} else { |
417
|
|
|
$path = rtrim(dirname($arr["path"]), '/\\') . "/"; |
418
|
|
|
$file = basename($arr["path"]); |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
if (isset($arr["query"])) { |
423
|
|
|
$file .= "?" . $arr["query"]; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
if (isset($arr["fragment"])) { |
427
|
|
|
$file .= "#" . $arr["fragment"]; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
} else { |
431
|
|
|
|
432
|
|
|
$i = mb_stripos($url, "file://"); |
433
|
|
|
if ($i !== false) { |
434
|
|
|
$url = mb_substr($url, $i + 7); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
$protocol = ""; // "file://"; ? why doesn't this work... It's because of |
438
|
|
|
// network filenames like //COMPU/SHARENAME |
439
|
|
|
|
440
|
|
|
$host = ""; // localhost, really |
441
|
|
|
$file = basename($url); |
442
|
|
|
|
443
|
|
|
$path = dirname($url); |
444
|
|
|
|
445
|
|
|
// Check that the path exists |
446
|
|
|
if ($path !== false) { |
447
|
|
|
$path .= '/'; |
448
|
|
|
|
449
|
|
|
} else { |
450
|
|
|
// generate a url to access the file if no real path found. |
451
|
|
|
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://'; |
452
|
|
|
|
453
|
|
|
$host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : php_uname("n"); |
454
|
|
|
|
455
|
|
|
if (substr($arr["path"], 0, 1) === '/') { |
456
|
|
|
$path = dirname($arr["path"]); |
457
|
|
|
} else { |
458
|
|
|
$path = '/' . rtrim(dirname($_SERVER["SCRIPT_NAME"]), '/') . '/' . $arr["path"]; |
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
$ret = array($protocol, $host, $path, $file, |
464
|
|
|
"protocol" => $protocol, |
465
|
|
|
"host" => $host, |
466
|
|
|
"path" => $path, |
467
|
|
|
"file" => $file); |
468
|
|
|
return $ret; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Print debug messages |
473
|
|
|
* |
474
|
|
|
* @param string $type The type of debug messages to print |
475
|
|
|
* @param string $msg The message to show |
476
|
|
|
*/ |
477
|
|
|
public static function dompdf_debug($type, $msg) |
478
|
|
|
{ |
479
|
|
|
global $_DOMPDF_DEBUG_TYPES, $_dompdf_show_warnings, $_dompdf_debug; |
|
|
|
|
480
|
|
|
if (isset($_DOMPDF_DEBUG_TYPES[$type]) && ($_dompdf_show_warnings || $_dompdf_debug)) { |
481
|
|
|
$arr = debug_backtrace(); |
482
|
|
|
|
483
|
|
|
echo basename($arr[0]["file"]) . " (" . $arr[0]["line"] . "): " . $arr[1]["function"] . ": "; |
484
|
|
|
Helpers::pre_r($msg); |
485
|
|
|
} |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
/** |
489
|
|
|
* Stores warnings in an array for display later |
490
|
|
|
* This function allows warnings generated by the DomDocument parser |
491
|
|
|
* and CSS loader ({@link Stylesheet}) to be captured and displayed |
492
|
|
|
* later. Without this function, errors are displayed immediately and |
493
|
|
|
* PDF streaming is impossible. |
494
|
|
|
* @see http://www.php.net/manual/en/function.set-error_handler.php |
495
|
|
|
* |
496
|
|
|
* @param int $errno |
497
|
|
|
* @param string $errstr |
498
|
|
|
* @param string $errfile |
499
|
|
|
* @param string $errline |
500
|
|
|
* |
501
|
|
|
* @throws Exception |
502
|
|
|
*/ |
503
|
|
|
public static function record_warnings($errno, $errstr, $errfile, $errline) |
|
|
|
|
504
|
|
|
{ |
505
|
|
|
// Not a warning or notice |
506
|
|
|
if (!($errno & (E_WARNING | E_NOTICE | E_USER_NOTICE | E_USER_WARNING))) { |
507
|
|
|
throw new Exception($errstr . " $errno"); |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
global $_dompdf_warnings; |
|
|
|
|
511
|
|
|
global $_dompdf_show_warnings; |
|
|
|
|
512
|
|
|
|
513
|
|
|
if ($_dompdf_show_warnings) { |
514
|
|
|
echo $errstr . "\n"; |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
$_dompdf_warnings[] = $errstr; |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* @param $c |
522
|
|
|
* @return bool|string |
523
|
|
|
*/ |
524
|
|
|
public static function unichr($c) |
525
|
|
|
{ |
526
|
|
|
if ($c <= 0x7F) { |
527
|
|
|
return chr($c); |
528
|
|
|
} else if ($c <= 0x7FF) { |
529
|
|
|
return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F); |
530
|
|
|
} else if ($c <= 0xFFFF) { |
531
|
|
|
return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) |
532
|
|
|
. chr(0x80 | $c & 0x3F); |
533
|
|
|
} else if ($c <= 0x10FFFF) { |
534
|
|
|
return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) |
535
|
|
|
. chr(0x80 | $c >> 6 & 0x3F) |
536
|
|
|
. chr(0x80 | $c & 0x3F); |
537
|
|
|
} |
538
|
|
|
return false; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
/** |
542
|
|
|
* Converts a CMYK color to RGB |
543
|
|
|
* |
544
|
|
|
* @param float|float[] $c |
545
|
|
|
* @param float $m |
546
|
|
|
* @param float $y |
547
|
|
|
* @param float $k |
548
|
|
|
* |
549
|
|
|
* @return float[] |
550
|
|
|
*/ |
551
|
|
|
public static function cmyk_to_rgb($c, $m = null, $y = null, $k = null) |
552
|
|
|
{ |
553
|
|
|
if (is_array($c)) { |
554
|
|
|
list($c, $m, $y, $k) = $c; |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
$c *= 255; |
558
|
|
|
$m *= 255; |
559
|
|
|
$y *= 255; |
560
|
|
|
$k *= 255; |
561
|
|
|
|
562
|
|
|
$r = (1 - round(2.55 * ($c + $k))); |
563
|
|
|
$g = (1 - round(2.55 * ($m + $k))); |
564
|
|
|
$b = (1 - round(2.55 * ($y + $k))); |
565
|
|
|
|
566
|
|
|
if ($r < 0) { |
567
|
|
|
$r = 0; |
568
|
|
|
} |
569
|
|
|
if ($g < 0) { |
570
|
|
|
$g = 0; |
571
|
|
|
} |
572
|
|
|
if ($b < 0) { |
573
|
|
|
$b = 0; |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
return array( |
577
|
|
|
$r, $g, $b, |
578
|
|
|
"r" => $r, "g" => $g, "b" => $b |
579
|
|
|
); |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
/** |
583
|
|
|
* getimagesize doesn't give a good size for 32bit BMP image v5 |
584
|
|
|
* |
585
|
|
|
* @param string $filename |
586
|
|
|
* @return array The same format as getimagesize($filename) |
587
|
|
|
*/ |
588
|
|
|
public static function dompdf_getimagesize($filename, $context = null) |
589
|
|
|
{ |
590
|
|
|
static $cache = array(); |
591
|
|
|
|
592
|
|
|
if (isset($cache[$filename])) { |
593
|
|
|
return $cache[$filename]; |
594
|
|
|
} |
595
|
|
|
|
596
|
|
|
list($width, $height, $type) = getimagesize($filename); |
597
|
|
|
|
598
|
|
|
// Custom types |
599
|
|
|
$types = array( |
600
|
|
|
IMAGETYPE_JPEG => "jpeg", |
601
|
|
|
IMAGETYPE_GIF => "gif", |
602
|
|
|
IMAGETYPE_BMP => "bmp", |
603
|
|
|
IMAGETYPE_PNG => "png", |
604
|
|
|
); |
605
|
|
|
|
606
|
|
|
$type = isset($types[$type]) ? $types[$type] : null; |
607
|
|
|
|
608
|
|
|
if ($width == null || $height == null) { |
609
|
|
|
list($data, $headers) = Helpers::getFileContent($filename, $context); |
|
|
|
|
610
|
|
|
|
611
|
|
|
if (substr($data, 0, 2) === "BM") { |
612
|
|
|
$meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data); |
613
|
|
|
$width = (int)$meta['width']; |
614
|
|
|
$height = (int)$meta['height']; |
615
|
|
|
$type = "bmp"; |
616
|
|
|
} |
617
|
|
|
else { |
618
|
|
|
if (strpos($data, "<svg") !== false) { |
619
|
|
|
$doc = new \Svg\Document(); |
620
|
|
|
$doc->loadFile($filename); |
621
|
|
|
|
622
|
|
|
list($width, $height) = $doc->getDimensions(); |
623
|
|
|
$type = "svg"; |
624
|
|
|
} |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
return $cache[$filename] = array($width, $height, $type); |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
/** |
633
|
|
|
* Credit goes to mgutt |
634
|
|
|
* http://www.programmierer-forum.de/function-imagecreatefrombmp-welche-variante-laeuft-t143137.htm |
635
|
|
|
* Modified by Fabien Menager to support RGB555 BMP format |
636
|
|
|
*/ |
637
|
|
|
public static function imagecreatefrombmp($filename, $context = null) |
|
|
|
|
638
|
|
|
{ |
639
|
|
|
if (!function_exists("imagecreatetruecolor")) { |
640
|
|
|
trigger_error("The PHP GD extension is required, but is not installed.", E_ERROR); |
641
|
|
|
return false; |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
// version 1.00 |
645
|
|
|
if (!($fh = fopen($filename, 'rb'))) { |
646
|
|
|
trigger_error('imagecreatefrombmp: Can not open ' . $filename, E_USER_WARNING); |
647
|
|
|
return false; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
$bytes_read = 0; |
651
|
|
|
|
652
|
|
|
// read file header |
653
|
|
|
$meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14)); |
654
|
|
|
|
655
|
|
|
// check for bitmap |
656
|
|
View Code Duplication |
if ($meta['type'] != 19778) { |
|
|
|
|
657
|
|
|
trigger_error('imagecreatefrombmp: ' . $filename . ' is not a bitmap!', E_USER_WARNING); |
658
|
|
|
return false; |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
// read image header |
662
|
|
|
$meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40)); |
663
|
|
|
$bytes_read += 40; |
664
|
|
|
|
665
|
|
|
// read additional bitfield header |
666
|
|
|
if ($meta['compression'] == 3) { |
667
|
|
|
$meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12)); |
668
|
|
|
$bytes_read += 12; |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
// set bytes and padding |
672
|
|
|
$meta['bytes'] = $meta['bits'] / 8; |
673
|
|
|
$meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4))); |
674
|
|
|
if ($meta['decal'] == 4) { |
675
|
|
|
$meta['decal'] = 0; |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
// obtain imagesize |
679
|
|
|
if ($meta['imagesize'] < 1) { |
680
|
|
|
$meta['imagesize'] = $meta['filesize'] - $meta['offset']; |
681
|
|
|
// in rare cases filesize is equal to offset so we need to read physical size |
682
|
|
|
if ($meta['imagesize'] < 1) { |
683
|
|
|
$meta['imagesize'] = @filesize($filename) - $meta['offset']; |
684
|
|
View Code Duplication |
if ($meta['imagesize'] < 1) { |
|
|
|
|
685
|
|
|
trigger_error('imagecreatefrombmp: Can not obtain filesize of ' . $filename . '!', E_USER_WARNING); |
686
|
|
|
return false; |
687
|
|
|
} |
688
|
|
|
} |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
// calculate colors |
692
|
|
|
$meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors']; |
693
|
|
|
|
694
|
|
|
// read color palette |
695
|
|
|
$palette = array(); |
696
|
|
|
if ($meta['bits'] < 16) { |
697
|
|
|
$palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4)); |
698
|
|
|
// in rare cases the color value is signed |
699
|
|
|
if ($palette[1] < 0) { |
700
|
|
|
foreach ($palette as $i => $color) { |
701
|
|
|
$palette[$i] = $color + 16777216; |
702
|
|
|
} |
703
|
|
|
} |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
// ignore extra bitmap headers |
707
|
|
|
if ($meta['headersize'] > $bytes_read) { |
708
|
|
|
fread($fh, $meta['headersize'] - $bytes_read); |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
// create gd image |
712
|
|
|
$im = imagecreatetruecolor($meta['width'], $meta['height']); |
713
|
|
|
$data = fread($fh, $meta['imagesize']); |
714
|
|
|
|
715
|
|
|
// uncompress data |
716
|
|
|
switch ($meta['compression']) { |
717
|
|
|
case 1: |
718
|
|
|
$data = Helpers::rle8_decode($data, $meta['width']); |
719
|
|
|
break; |
720
|
|
|
case 2: |
721
|
|
|
$data = Helpers::rle4_decode($data, $meta['width']); |
722
|
|
|
break; |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
$p = 0; |
726
|
|
|
$vide = chr(0); |
727
|
|
|
$y = $meta['height'] - 1; |
728
|
|
|
$error = 'imagecreatefrombmp: ' . $filename . ' has not enough data!'; |
729
|
|
|
|
730
|
|
|
// loop through the image data beginning with the lower left corner |
731
|
|
|
while ($y >= 0) { |
732
|
|
|
$x = 0; |
733
|
|
|
while ($x < $meta['width']) { |
734
|
|
|
switch ($meta['bits']) { |
735
|
|
|
case 32: |
736
|
|
|
case 24: |
737
|
|
View Code Duplication |
if (!($part = substr($data, $p, 3 /*$meta['bytes']*/))) { |
|
|
|
|
738
|
|
|
trigger_error($error, E_USER_WARNING); |
739
|
|
|
return $im; |
740
|
|
|
} |
741
|
|
|
$color = unpack('V', $part . $vide); |
742
|
|
|
break; |
743
|
|
|
case 16: |
744
|
|
View Code Duplication |
if (!($part = substr($data, $p, 2 /*$meta['bytes']*/))) { |
|
|
|
|
745
|
|
|
trigger_error($error, E_USER_WARNING); |
746
|
|
|
return $im; |
747
|
|
|
} |
748
|
|
|
$color = unpack('v', $part); |
749
|
|
|
|
750
|
|
|
if (empty($meta['rMask']) || $meta['rMask'] != 0xf800) { |
751
|
|
|
$color[1] = (($color[1] & 0x7c00) >> 7) * 65536 + (($color[1] & 0x03e0) >> 2) * 256 + (($color[1] & 0x001f) << 3); // 555 |
752
|
|
|
} else { |
753
|
|
|
$color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); // 565 |
754
|
|
|
} |
755
|
|
|
break; |
756
|
|
|
case 8: |
757
|
|
|
$color = unpack('n', $vide . substr($data, $p, 1)); |
758
|
|
|
$color[1] = $palette[$color[1] + 1]; |
759
|
|
|
break; |
760
|
|
|
case 4: |
761
|
|
|
$color = unpack('n', $vide . substr($data, floor($p), 1)); |
762
|
|
|
$color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F; |
763
|
|
|
$color[1] = $palette[$color[1] + 1]; |
764
|
|
|
break; |
765
|
|
|
case 1: |
766
|
|
|
$color = unpack('n', $vide . substr($data, floor($p), 1)); |
767
|
|
|
switch (($p * 8) % 8) { |
768
|
|
|
case 0: |
769
|
|
|
$color[1] = $color[1] >> 7; |
770
|
|
|
break; |
771
|
|
|
case 1: |
772
|
|
|
$color[1] = ($color[1] & 0x40) >> 6; |
773
|
|
|
break; |
774
|
|
|
case 2: |
775
|
|
|
$color[1] = ($color[1] & 0x20) >> 5; |
776
|
|
|
break; |
777
|
|
|
case 3: |
778
|
|
|
$color[1] = ($color[1] & 0x10) >> 4; |
779
|
|
|
break; |
780
|
|
|
case 4: |
781
|
|
|
$color[1] = ($color[1] & 0x8) >> 3; |
782
|
|
|
break; |
783
|
|
|
case 5: |
784
|
|
|
$color[1] = ($color[1] & 0x4) >> 2; |
785
|
|
|
break; |
786
|
|
|
case 6: |
787
|
|
|
$color[1] = ($color[1] & 0x2) >> 1; |
788
|
|
|
break; |
789
|
|
|
case 7: |
790
|
|
|
$color[1] = ($color[1] & 0x1); |
791
|
|
|
break; |
792
|
|
|
} |
793
|
|
|
$color[1] = $palette[$color[1] + 1]; |
794
|
|
|
break; |
795
|
|
|
default: |
796
|
|
|
trigger_error('imagecreatefrombmp: ' . $filename . ' has ' . $meta['bits'] . ' bits and this is not supported!', E_USER_WARNING); |
797
|
|
|
return false; |
798
|
|
|
} |
799
|
|
|
imagesetpixel($im, $x, $y, $color[1]); |
800
|
|
|
$x++; |
801
|
|
|
$p += $meta['bytes']; |
802
|
|
|
} |
803
|
|
|
$y--; |
804
|
|
|
$p += $meta['decal']; |
805
|
|
|
} |
806
|
|
|
fclose($fh); |
807
|
|
|
return $im; |
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
/** |
811
|
|
|
* Gets the content of the file at the specified path using one of |
812
|
|
|
* the following methods, in preferential order: |
813
|
|
|
* - file_get_contents: if allow_url_fopen is true or the file is local |
814
|
|
|
* - curl: if allow_url_fopen is false and curl is available |
815
|
|
|
* |
816
|
|
|
* @param string $uri |
817
|
|
|
* @param resource $context (ignored if curl is used) |
818
|
|
|
* @param int $offset |
819
|
|
|
* @param int $maxlen (ignored if curl is used) |
820
|
|
|
* @return bool|array |
821
|
|
|
*/ |
822
|
|
|
public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null) |
823
|
|
|
{ |
824
|
|
|
$result = false; |
825
|
|
|
$headers = null; |
826
|
|
|
list($proto, $host, $path, $file) = Helpers::explode_url($uri); |
|
|
|
|
827
|
|
|
$is_local_path = ($proto == "" || $proto === "file://"); |
828
|
|
|
|
829
|
|
|
set_error_handler(array("\\Dompdf\\Helpers", "record_warnings")); |
830
|
|
|
|
831
|
|
|
if ($is_local_path || ini_get("allow_url_fopen")) { |
832
|
|
|
if ($is_local_path === false) { |
833
|
|
|
$uri = Helpers::encodeURI($uri); |
834
|
|
|
} |
835
|
|
|
if (isset($maxlen)) { |
836
|
|
|
$result = file_get_contents($uri, null, $context, $offset, $maxlen); |
837
|
|
|
} else { |
838
|
|
|
$result = file_get_contents($uri, null, $context, $offset); |
839
|
|
|
} |
840
|
|
|
if (isset($http_response_header)) { |
841
|
|
|
$headers = $http_response_header; |
842
|
|
|
} |
843
|
|
|
|
844
|
|
|
} elseif (function_exists("curl_exec")) { |
845
|
|
|
$curl = curl_init($uri); |
846
|
|
|
|
847
|
|
|
//TODO: use $context to define additional curl options |
848
|
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, 10); |
849
|
|
|
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); |
850
|
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
851
|
|
|
curl_setopt($curl, CURLOPT_HEADER, true); |
852
|
|
|
if ($offset > 0) { |
853
|
|
|
curl_setopt($curl, CURLOPT_RESUME_FROM, $offset); |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
$data = curl_exec($curl); |
857
|
|
|
$raw_headers = substr($data, 0, curl_getinfo($curl, CURLINFO_HEADER_SIZE)); |
858
|
|
|
$headers = preg_split("/[\n\r]+/", trim($raw_headers)); |
859
|
|
|
$result = substr($data, curl_getinfo($curl, CURLINFO_HEADER_SIZE)); |
860
|
|
|
curl_close($curl); |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
restore_error_handler(); |
864
|
|
|
|
865
|
|
|
return array($result, $headers); |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
public static function mb_ucwords($str) { |
869
|
|
|
$max_len = mb_strlen($str); |
870
|
|
|
if ($max_len === 1) { |
871
|
|
|
return mb_strtoupper($str); |
872
|
|
|
} |
873
|
|
|
|
874
|
|
|
$str = mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1); |
875
|
|
|
|
876
|
|
|
foreach (array(' ', '.', ',', '!', '?', '-', '+') as $s) { |
877
|
|
|
$pos = 0; |
878
|
|
|
while (($pos = mb_strpos($str, $s, $pos)) !== false) { |
879
|
|
|
$pos++; |
880
|
|
|
// Nothing to do if the separator is the last char of the string |
881
|
|
|
if ($pos !== false && $pos < $max_len) { |
882
|
|
|
// If the char we want to upper is the last char there is nothing to append behind |
883
|
|
|
if ($pos + 1 < $max_len) { |
884
|
|
|
$str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)) . mb_substr($str, $pos + 1); |
885
|
|
|
} else { |
886
|
|
|
$str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)); |
887
|
|
|
} |
888
|
|
|
} |
889
|
|
|
} |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
return $str; |
893
|
|
|
} |
894
|
|
|
} |
895
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.