PluginProbe
MBE eShip / trunk
MBE eShip vtrunk
2.8.1 trunk 1.0.0 1.1.0 1.1.3 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.0 2.1.1 2.1.2 2.2.1 All 37 releases
mail-boxes-etc / lib / dompdf / src / Adapter / GD.php

GD.php in MBE eShip trunk, at lib/dompdf/src/Adapter/GD.php

930 lines 24.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package dompdf
4 * @link https://github.com/dompdf/dompdf
5 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
6 */
7 namespace Dompdf\Adapter;
8
9 use Dompdf\Canvas;
10 use Dompdf\Dompdf;
11 use Dompdf\Helpers;
12 use Dompdf\Image\Cache;
13
14 /**
15 * Image rendering interface
16 *
17 * Renders to an image format supported by GD (jpeg, gif, png, xpm).
18 * Not super-useful day-to-day but handy nonetheless
19 *
20 * @package dompdf
21 */
22 class GD implements Canvas
23 {
24 /**
25 * @var Dompdf
26 */
27 protected $_dompdf;
28
29 /**
30 * Resource handle for the image
31 *
32 * @var \GdImage|resource
33 */
34 protected $_img;
35
36 /**
37 * Resource handle for the image
38 *
39 * @var \GdImage[]|resource[]
40 */
41 protected $_imgs;
42
43 /**
44 * Apparent canvas width in pixels
45 *
46 * @var int
47 */
48 protected $_width;
49
50 /**
51 * Apparent canvas height in pixels
52 *
53 * @var int
54 */
55 protected $_height;
56
57 /**
58 * Actual image width in pixels
59 *
60 * @var int
61 */
62 protected $_actual_width;
63
64 /**
65 * Actual image height in pixels
66 *
67 * @var int
68 */
69 protected $_actual_height;
70
71 /**
72 * Current page number
73 *
74 * @var int
75 */
76 protected $_page_number;
77
78 /**
79 * Total number of pages
80 *
81 * @var int
82 */
83 protected $_page_count;
84
85 /**
86 * Image antialias factor
87 *
88 * @var float
89 */
90 protected $_aa_factor;
91
92 /**
93 * Allocated colors
94 *
95 * @var array
96 */
97 protected $_colors;
98
99 /**
100 * Background color
101 *
102 * @var int
103 */
104 protected $_bg_color;
105
106 /**
107 * Background color array
108 *
109 * @var int
110 */
111 protected $_bg_color_array;
112
113 /**
114 * Actual DPI
115 *
116 * @var int
117 */
118 protected $dpi;
119
120 /**
121 * Amount to scale font sizes
122 *
123 * Font sizes are 72 DPI, GD internally uses 96. Scale them proportionally.
124 * 72 / 96 = 0.75.
125 *
126 * @var float
127 */
128 const FONT_SCALE = 0.75;
129
130 /**
131 * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link CPDF::$PAPER_SIZES}) or
132 * an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`).
133 * @param string $orientation The paper orientation, either `portrait` or `landscape`.
134 * @param Dompdf $dompdf The Dompdf instance.
135 * @param float $aa_factor Anti-aliasing factor, 1 for no AA
136 * @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1
137 */
138 public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null, $aa_factor = 1.0, $bg_color = [1, 1, 1, 0])
139 {
140 if (is_array($paper)) {
141 $size = array_map("floatval", $paper);
142 } else {
143 $paper = strtolower($paper);
144 $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
145 }
146
147 if (strtolower($orientation) === "landscape") {
148 [$size[2], $size[3]] = [$size[3], $size[2]];
149 }
150
151 if ($dompdf === null) {
152 $this->_dompdf = new Dompdf();
153 } else {
154 $this->_dompdf = $dompdf;
155 }
156
157 $this->dpi = $this->get_dompdf()->getOptions()->getDpi();
158
159 if ($aa_factor < 1) {
160 $aa_factor = 1;
161 }
162
163 $this->_aa_factor = $aa_factor;
164
165 $size[2] *= $aa_factor;
166 $size[3] *= $aa_factor;
167
168 $this->_width = $size[2] - $size[0];
169 $this->_height = $size[3] - $size[1];
170
171 $this->_actual_width = $this->_upscale($this->_width);
172 $this->_actual_height = $this->_upscale($this->_height);
173
174 $this->_page_number = $this->_page_count = 0;
175
176 if (is_null($bg_color) || !is_array($bg_color)) {
177 // Pure white bg
178 $bg_color = [1, 1, 1, 0];
179 }
180
181 $this->_bg_color_array = $bg_color;
182
183 $this->new_page();
184 }
185
186 public function get_dompdf()
187 {
188 return $this->_dompdf;
189 }
190
191 /**
192 * Return the GD image resource
193 *
194 * @return \GdImage|resource
195 */
196 public function get_image()
197 {
198 return $this->_img;
199 }
200
201 /**
202 * Return the image's width in pixels
203 *
204 * @return int
205 */
206 public function get_width()
207 {
208 return round($this->_width / $this->_aa_factor);
209 }
210
211 /**
212 * Return the image's height in pixels
213 *
214 * @return int
215 */
216 public function get_height()
217 {
218 return round($this->_height / $this->_aa_factor);
219 }
220
221 public function get_page_number()
222 {
223 return $this->_page_number;
224 }
225
226 public function get_page_count()
227 {
228 return $this->_page_count;
229 }
230
231 /**
232 * Sets the current page number
233 *
234 * @param int $num
235 */
236 public function set_page_number($num)
237 {
238 $this->_page_number = $num;
239 }
240
241 public function set_page_count($count)
242 {
243 $this->_page_count = $count;
244 }
245
246 public function set_opacity(float $opacity, string $mode = "Normal"): void
247 {
248 // FIXME
249 }
250
251 /**
252 * Allocate a new color. Allocate with GD as needed and store
253 * previously allocated colors in $this->_colors.
254 *
255 * @param array $color The new current color
256 * @return int The allocated color
257 */
258 protected function _allocate_color($color)
259 {
260 $a = isset($color["alpha"]) ? $color["alpha"] : 1;
261
262 if (isset($color["c"])) {
263 $color = Helpers::cmyk_to_rgb($color);
264 }
265
266 list($r, $g, $b) = $color;
267
268 $r = round($r * 255);
269 $g = round($g * 255);
270 $b = round($b * 255);
271 $a = round(127 - ($a * 127));
272
273 // Clip values
274 $r = $r > 255 ? 255 : $r;
275 $g = $g > 255 ? 255 : $g;
276 $b = $b > 255 ? 255 : $b;
277 $a = $a > 127 ? 127 : $a;
278
279 $r = $r < 0 ? 0 : $r;
280 $g = $g < 0 ? 0 : $g;
281 $b = $b < 0 ? 0 : $b;
282 $a = $a < 0 ? 0 : $a;
283
284 $key = sprintf("#%02X%02X%02X%02X", $r, $g, $b, $a);
285
286 if (isset($this->_colors[$key])) {
287 return $this->_colors[$key];
288 }
289
290 if ($a != 0) {
291 $this->_colors[$key] = imagecolorallocatealpha($this->get_image(), $r, $g, $b, $a);
292 } else {
293 $this->_colors[$key] = imagecolorallocate($this->get_image(), $r, $g, $b);
294 }
295
296 return $this->_colors[$key];
297 }
298
299 /**
300 * Scales value up to the current canvas DPI from 72 DPI
301 *
302 * @param float $length
303 * @return int
304 */
305 protected function _upscale($length)
306 {
307 return round(($length * $this->dpi) / 72 * $this->_aa_factor);
308 }
309
310 /**
311 * Scales value down from the current canvas DPI to 72 DPI
312 *
313 * @param float $length
314 * @return float
315 */
316 protected function _downscale($length)
317 {
318 return round(($length / $this->dpi * 72) / $this->_aa_factor);
319 }
320
321 protected function convertStyle(array $style, int $color, int $width): array
322 {
323 $gdStyle = [];
324
325 if (count($style) === 1) {
326 $style[] = $style[0];
327 }
328
329 foreach ($style as $index => $s) {
330 $d = $this->_upscale($s);
331
332 for ($i = 0; $i < $d; $i++) {
333 for ($j = 0; $j < $width; $j++) {
334 $gdStyle[] = $index % 2 === 0
335 ? $color
336 : IMG_COLOR_TRANSPARENT;
337 }
338 }
339 }
340
341 return $gdStyle;
342 }
343
344 public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
345 {
346 // Account for the fact that round and square caps are expected to
347 // extend outwards
348 if ($cap === "round" || $cap === "square") {
349 // Shift line by half width
350 $w = $width / 2;
351 $a = $x2 - $x1;
352 $b = $y2 - $y1;
353 $c = sqrt($a ** 2 + $b ** 2);
354 $dx = $a * $w / $c;
355 $dy = $b * $w / $c;
356
357 $x1 -= $dx;
358 $x2 -= $dx;
359 $y1 -= $dy;
360 $y2 -= $dy;
361
362 // Adapt dash pattern
363 if (is_array($style)) {
364 foreach ($style as $index => &$s) {
365 $s = $index % 2 === 0 ? $s + $width : $s - $width;
366 }
367 }
368 }
369
370 // Scale by the AA factor and DPI
371 $x1 = $this->_upscale($x1);
372 $y1 = $this->_upscale($y1);
373 $x2 = $this->_upscale($x2);
374 $y2 = $this->_upscale($y2);
375 $width = $this->_upscale($width);
376
377 $c = $this->_allocate_color($color);
378
379 // Convert the style array if required
380 if (is_array($style) && count($style) > 0) {
381 $gd_style = $this->convertStyle($style, $c, $width);
382
383 if (!empty($gd_style)) {
384 imagesetstyle($this->get_image(), $gd_style);
385 $c = IMG_COLOR_STYLED;
386 }
387 }
388
389 imagesetthickness($this->get_image(), $width);
390
391 imageline($this->get_image(), $x1, $y1, $x2, $y2, $c);
392 }
393
394 public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
395 {
396 // Account for the fact that round and square caps are expected to
397 // extend outwards
398 if ($cap === "round" || $cap === "square") {
399 // Adapt dash pattern
400 if (is_array($style)) {
401 foreach ($style as $index => &$s) {
402 $s = $index % 2 === 0 ? $s + $width : $s - $width;
403 }
404 }
405 }
406
407 // Scale by the AA factor and DPI
408 $x = $this->_upscale($x);
409 $y = $this->_upscale($y);
410 $w = $this->_upscale($r1 * 2);
411 $h = $this->_upscale($r2 * 2);
412 $width = $this->_upscale($width);
413
414 // Adapt angles as imagearc counts clockwise
415 $start = 360 - $aend;
416 $end = 360 - $astart;
417
418 $c = $this->_allocate_color($color);
419
420 // Convert the style array if required
421 if (is_array($style) && count($style) > 0) {
422 $gd_style = $this->convertStyle($style, $c, $width);
423
424 if (!empty($gd_style)) {
425 imagesetstyle($this->get_image(), $gd_style);
426 $c = IMG_COLOR_STYLED;
427 }
428 }
429
430 imagesetthickness($this->get_image(), $width);
431
432 imagearc($this->get_image(), $x, $y, $w, $h, $start, $end, $c);
433 }
434
435 public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
436 {
437 // Account for the fact that round and square caps are expected to
438 // extend outwards
439 if ($cap === "round" || $cap === "square") {
440 // Adapt dash pattern
441 if (is_array($style)) {
442 foreach ($style as $index => &$s) {
443 $s = $index % 2 === 0 ? $s + $width : $s - $width;
444 }
445 }
446 }
447
448 // Scale by the AA factor and DPI
449 $x1 = $this->_upscale($x1);
450 $y1 = $this->_upscale($y1);
451 $w = $this->_upscale($w);
452 $h = $this->_upscale($h);
453 $width = $this->_upscale($width);
454
455 $c = $this->_allocate_color($color);
456
457 // Convert the style array if required
458 if (is_array($style) && count($style) > 0) {
459 $gd_style = $this->convertStyle($style, $c, $width);
460
461 if (!empty($gd_style)) {
462 imagesetstyle($this->get_image(), $gd_style);
463 $c = IMG_COLOR_STYLED;
464 }
465 }
466
467 imagesetthickness($this->get_image(), $width);
468
469 if ($c === IMG_COLOR_STYLED) {
470 imagepolygon($this->get_image(), [
471 $x1, $y1,
472 $x1 + $w, $y1,
473 $x1 + $w, $y1 + $h,
474 $x1, $y1 + $h
475 ], $c);
476 } else {
477 imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
478 }
479 }
480
481 public function filled_rectangle($x1, $y1, $w, $h, $color)
482 {
483 // Scale by the AA factor and DPI
484 $x1 = $this->_upscale($x1);
485 $y1 = $this->_upscale($y1);
486 $w = $this->_upscale($w);
487 $h = $this->_upscale($h);
488
489 $c = $this->_allocate_color($color);
490
491 imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
492 }
493
494 public function clipping_rectangle($x1, $y1, $w, $h)
495 {
496 // @todo
497 }
498
499 public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
500 {
501 // @todo
502 }
503
504 public function clipping_polygon(array $points): void
505 {
506 // @todo
507 }
508
509 public function clipping_end()
510 {
511 // @todo
512 }
513
514 public function save()
515 {
516 $this->get_dompdf()->getOptions()->setDpi(72);
517 }
518
519 public function restore()
520 {
521 $this->get_dompdf()->getOptions()->setDpi($this->dpi);
522 }
523
524 public function rotate($angle, $x, $y)
525 {
526 // @todo
527 }
528
529 public function skew($angle_x, $angle_y, $x, $y)
530 {
531 // @todo
532 }
533
534 public function scale($s_x, $s_y, $x, $y)
535 {
536 // @todo
537 }
538
539 public function translate($t_x, $t_y)
540 {
541 // @todo
542 }
543
544 public function transform($a, $b, $c, $d, $e, $f)
545 {
546 // @todo
547 }
548
549 public function polygon($points, $color, $width = null, $style = [], $fill = false)
550 {
551 // Scale each point by the AA factor and DPI
552 foreach (array_keys($points) as $i) {
553 $points[$i] = $this->_upscale($points[$i]);
554 }
555
556 $width = isset($width) ? $this->_upscale($width) : null;
557
558 $c = $this->_allocate_color($color);
559
560 // Convert the style array if required
561 if (is_array($style) && count($style) > 0 && isset($width) && !$fill) {
562 $gd_style = $this->convertStyle($style, $c, $width);
563
564 if (!empty($gd_style)) {
565 imagesetstyle($this->get_image(), $gd_style);
566 $c = IMG_COLOR_STYLED;
567 }
568 }
569
570 imagesetthickness($this->get_image(), isset($width) ? $width : 0);
571
572 if ($fill) {
573 imagefilledpolygon($this->get_image(), $points, $c);
574 } else {
575 imagepolygon($this->get_image(), $points, $c);
576 }
577 }
578
579 public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
580 {
581 // Scale by the AA factor and DPI
582 $x = $this->_upscale($x);
583 $y = $this->_upscale($y);
584 $d = $this->_upscale(2 * $r);
585 $width = isset($width) ? $this->_upscale($width) : null;
586
587 $c = $this->_allocate_color($color);
588
589 // Convert the style array if required
590 if (is_array($style) && count($style) > 0 && isset($width) && !$fill) {
591 $gd_style = $this->convertStyle($style, $c, $width);
592
593 if (!empty($gd_style)) {
594 imagesetstyle($this->get_image(), $gd_style);
595 $c = IMG_COLOR_STYLED;
596 }
597 }
598
599 imagesetthickness($this->get_image(), isset($width) ? $width : 0);
600
601 if ($fill) {
602 imagefilledellipse($this->get_image(), $x, $y, $d, $d, $c);
603 } else {
604 imageellipse($this->get_image(), $x, $y, $d, $d, $c);
605 }
606 }
607
608 /**
609 * @throws \Exception
610 */
611 public function image($img, $x, $y, $w, $h, $resolution = "normal")
612 {
613 $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext());
614
615 if (!$img_type) {
616 return;
617 }
618
619 $func_name = "imagecreatefrom$img_type";
620 if (!function_exists($func_name)) {
621 if (!method_exists(Helpers::class, $func_name)) {
622 throw new \Exception("Function $func_name() not found. Cannot convert $img_type image: $img. Please install the image PHP extension.");
623 }
624 $func_name = [Helpers::class, $func_name];
625 }
626 $src = @call_user_func($func_name, $img);
627
628 if (!$src) {
629 return; // Probably should add to $_dompdf_errors or whatever here
630 }
631
632 // Scale by the AA factor and DPI
633 $x = $this->_upscale($x);
634 $y = $this->_upscale($y);
635
636 $w = $this->_upscale($w);
637 $h = $this->_upscale($h);
638
639 $img_w = imagesx($src);
640 $img_h = imagesy($src);
641
642 imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h);
643 }
644
645 public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0)
646 {
647 // Scale by the AA factor and DPI
648 $x = $this->_upscale($x);
649 $y = $this->_upscale($y);
650 $size = $this->_upscale($size) * self::FONT_SCALE;
651
652 $h = round($this->get_font_height_actual($font, $size));
653 $c = $this->_allocate_color($color);
654
655 // imagettftext() converts numeric entities to their respective
656 // character. Preserve any originally double encoded entities to be
657 // represented as is.
658 // eg: &amp;#160; will render &#160; rather than its character.
659 $text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&#38;\1', $text);
660
661 $text = mb_encode_numericentity($text, [0x0080, 0xff, 0, 0xff], 'UTF-8');
662
663 $font = $this->get_ttf_file($font);
664
665 // FIXME: word spacing
666 imagettftext($this->get_image(), $size, $angle, $x, $y + $h, $c, $font, $text);
667 }
668
669 public function javascript($code)
670 {
671 // Not implemented
672 }
673
674 public function add_named_dest($anchorname)
675 {
676 // Not implemented
677 }
678
679 public function add_link($url, $x, $y, $width, $height)
680 {
681 // Not implemented
682 }
683
684 public function add_info(string $label, string $value): void
685 {
686 // N/A
687 }
688
689 public function set_default_view($view, $options = [])
690 {
691 // N/A
692 }
693
694 public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
695 {
696 $font = $this->get_ttf_file($font);
697 $size = $this->_upscale($size) * self::FONT_SCALE;
698
699 // imagettfbbox() converts numeric entities to their respective
700 // character. Preserve any originally double encoded entities to be
701 // represented as is.
702 // eg: &amp;#160; will render &#160; rather than its character.
703 $text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&#38;\1', $text);
704
705 $text = mb_encode_numericentity($text, [0x0080, 0xffff, 0, 0xffff], 'UTF-8');
706
707 // FIXME: word spacing
708 list($x1, , $x2) = imagettfbbox($size, 0, $font, $text);
709
710 // Add additional 1pt to prevent text overflow issues
711 return $this->_downscale($x2 - $x1) + 1;
712 }
713
714 /**
715 * @param string|null $font
716 * @return string
717 */
718 public function get_ttf_file($font)
719 {
720 if ($font === null) {
721 $font = "";
722 }
723
724 if ( stripos($font, ".ttf") === false ) {
725 $font .= ".ttf";
726 }
727
728 if (!file_exists($font)) {
729 $font_metrics = $this->_dompdf->getFontMetrics();
730 $font = $font_metrics->getFont($this->_dompdf->getOptions()->getDefaultFont()) . ".ttf";
731 if (!file_exists($font)) {
732 if (strpos($font, "mono")) {
733 $font = $font_metrics->getFont("DejaVu Mono") . ".ttf";
734 } elseif (strpos($font, "sans") !== false) {
735 $font = $font_metrics->getFont("DejaVu Sans") . ".ttf";
736 } elseif (strpos($font, "serif")) {
737 $font = $font_metrics->getFont("DejaVu Serif") . ".ttf";
738 } else {
739 $font = $font_metrics->getFont("DejaVu Sans") . ".ttf";
740 }
741 }
742 }
743
744 return $font;
745 }
746
747 public function get_font_height($font, $size)
748 {
749 $size = $this->_upscale($size) * self::FONT_SCALE;
750
751 $height = $this->get_font_height_actual($font, $size);
752
753 return $this->_downscale($height);
754 }
755
756 /**
757 * @param string $font
758 * @param float $size
759 *
760 * @return float
761 */
762 protected function get_font_height_actual($font, $size)
763 {
764 $font = $this->get_ttf_file($font);
765 $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
766
767 // FIXME: word spacing
768 list(, $y2, , , , $y1) = imagettfbbox($size, 0, $font, "MXjpqytfhl"); // Test string with ascenders, descenders and caps
769 return ($y2 - $y1) * $ratio;
770 }
771
772 public function get_font_baseline($font, $size)
773 {
774 $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
775 return $this->get_font_height($font, $size) / $ratio;
776 }
777
778 public function new_page()
779 {
780 $this->_page_number++;
781 $this->_page_count++;
782
783 $this->_img = imagecreatetruecolor($this->_actual_width, $this->_actual_height);
784
785 $this->_bg_color = $this->_allocate_color($this->_bg_color_array);
786 imagealphablending($this->_img, true);
787 imagesavealpha($this->_img, true);
788 imagefill($this->_img, 0, 0, $this->_bg_color);
789
790 $this->_imgs[] = $this->_img;
791 }
792
793 public function open_object()
794 {
795 // N/A
796 }
797
798 public function close_object()
799 {
800 // N/A
801 }
802
803 public function add_object()
804 {
805 // N/A
806 }
807
808 public function page_script($callback): void
809 {
810 // N/A
811 }
812
813 public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
814 {
815 // N/A
816 }
817
818 public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
819 {
820 // N/A
821 }
822
823 /**
824 * Streams the image to the client.
825 *
826 * @param string $filename The filename to present to the client.
827 * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
828 * 'page' => Number of the page to output (defaults to the first); 'Attachment': 1 or 0 (default 1).
829 */
830 public function stream($filename, $options = [])
831 {
832 if (headers_sent()) {
833 die("Unable to stream image: headers already sent");
834 }
835
836 if (!isset($options["type"])) $options["type"] = "png";
837 if (!isset($options["Attachment"])) $options["Attachment"] = true;
838 $type = strtolower($options["type"]);
839
840 switch ($type) {
841 case "jpg":
842 case "jpeg":
843 $contentType = "image/jpeg";
844 $extension = ".jpg";
845 break;
846 case "png":
847 default:
848 $contentType = "image/png";
849 $extension = ".png";
850 break;
851 }
852
853 header("Cache-Control: private");
854 header("Content-Type: $contentType");
855
856 $filename = str_replace(["\n", "'"], "", basename($filename, ".$type")) . $extension;
857 $attachment = $options["Attachment"] ? "attachment" : "inline";
858 header(Helpers::buildContentDispositionHeader($attachment, $filename));
859
860 $this->_output($options);
861 flush();
862 }
863
864 /**
865 * Returns the image as a string.
866 *
867 * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
868 * 'page' => Number of the page to output (defaults to the first).
869 * @return string
870 */
871 public function output($options = [])
872 {
873 ob_start();
874
875 $this->_output($options);
876
877 return ob_get_clean();
878 }
879
880 /**
881 * Outputs the image stream directly.
882 *
883 * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
884 * 'page' => Number of the page to output (defaults to the first).
885 */
886 protected function _output($options = [])
887 {
888 if (!isset($options["type"])) $options["type"] = "png";
889 if (!isset($options["page"])) $options["page"] = 1;
890 $type = strtolower($options["type"]);
891
892 if (isset($this->_imgs[$options["page"] - 1])) {
893 $img = $this->_imgs[$options["page"] - 1];
894 } else {
895 $img = $this->_imgs[0];
896 }
897
898 // Perform any antialiasing
899 if ($this->_aa_factor != 1) {
900 $dst_w = round($this->_actual_width / $this->_aa_factor);
901 $dst_h = round($this->_actual_height / $this->_aa_factor);
902 $dst = imagecreatetruecolor($dst_w, $dst_h);
903 imagecopyresampled($dst, $img, 0, 0, 0, 0,
904 $dst_w, $dst_h,
905 $this->_actual_width, $this->_actual_height);
906 } else {
907 $dst = $img;
908 }
909
910 switch ($type) {
911 case "jpg":
912 case "jpeg":
913 if (!isset($options["quality"])) {
914 $options["quality"] = 75;
915 }
916
917 imagejpeg($dst, null, $options["quality"]);
918 break;
919 case "png":
920 default:
921 imagepng($dst);
922 break;
923 }
924
925 if ($this->_aa_factor != 1) {
926 imagedestroy($dst);
927 }
928 }
929 }
930