PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.22
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.22
1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 All 168 releases
woocommerce-pos / vendor_prefixed / dompdf / dompdf / src / Renderer / AbstractRenderer.php

AbstractRenderer.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.22, at vendor_prefixed/dompdf/dompdf/src/Renderer/AbstractRenderer.php

1,139 lines 44.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @package dompdf
5 * @link https://github.com/dompdf/dompdf
6 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 */
8 namespace WCPOS\Vendor\Dompdf\Renderer;
9
10 use DOMElement;
11 use WCPOS\Vendor\Dompdf\Adapter\CPDF;
12 use WCPOS\Vendor\Dompdf\Css\Color;
13 use WCPOS\Vendor\Dompdf\Css\Style;
14 use WCPOS\Vendor\Dompdf\Dompdf;
15 use WCPOS\Vendor\Dompdf\Frame;
16 use WCPOS\Vendor\Dompdf\Helpers;
17 use WCPOS\Vendor\Dompdf\Image\Cache;
18 /**
19 * Base renderer class
20 *
21 * @package dompdf
22 */
23 abstract class AbstractRenderer
24 {
25 /**
26 * Rendering backend
27 *
28 * @var \Dompdf\Canvas
29 */
30 protected $_canvas;
31 /**
32 * Current dompdf instance
33 *
34 * @var Dompdf
35 */
36 protected $_dompdf;
37 /**
38 * Class constructor
39 *
40 * @param Dompdf $dompdf The current dompdf instance
41 */
42 function __construct(Dompdf $dompdf)
43 {
44 $this->_dompdf = $dompdf;
45 $this->_canvas = $dompdf->getCanvas();
46 }
47 /**
48 * Render a frame.
49 *
50 * Specialized in child classes
51 *
52 * @param Frame $frame The frame to render
53 */
54 abstract function render(Frame $frame);
55 /**
56 * @param Frame $frame
57 * @param float[] $border_box
58 */
59 protected function _render_background(Frame $frame, array $border_box) : void
60 {
61 $style = $frame->get_style();
62 $color = $style->background_color;
63 $image = $style->background_image;
64 [$x, $y, $w, $h] = $border_box;
65 if ($color === "transparent" && $image === "none") {
66 return;
67 }
68 if ($style->has_border_radius()) {
69 [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box);
70 $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
71 }
72 if ($color !== "transparent") {
73 $this->_canvas->filled_rectangle($x, $y, $w, $h, $color);
74 }
75 if ($image !== "none") {
76 $this->_background_image($image, $x, $y, $w, $h, $style);
77 }
78 if ($style->has_border_radius()) {
79 $this->_canvas->clipping_end();
80 }
81 }
82 /**
83 * @param Frame $frame
84 * @param float[] $border_box
85 * @param string $corner_style
86 */
87 protected function _render_border(Frame $frame, array $border_box, string $corner_style = "bevel") : void
88 {
89 $style = $frame->get_style();
90 $bp = $style->get_border_properties();
91 [$x, $y, $w, $h] = $border_box;
92 [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box);
93 // Short-cut: If all the borders are "solid" with the same color and
94 // style, and no radius, we'd better draw a rectangle
95 if ($bp["top"]["style"] === "solid" && $bp["top"] === $bp["right"] && $bp["right"] === $bp["bottom"] && $bp["bottom"] === $bp["left"] && !$style->has_border_radius()) {
96 $props = $bp["top"];
97 if ($props["color"] === "transparent" || $props["width"] <= 0) {
98 return;
99 }
100 $width = (float) $style->length_in_pt($props["width"]);
101 $this->_canvas->rectangle($x + $width / 2, $y + $width / 2, $w - $width, $h - $width, $props["color"], $width);
102 return;
103 }
104 // Do it the long way
105 $widths = [(float) $style->length_in_pt($bp["top"]["width"]), (float) $style->length_in_pt($bp["right"]["width"]), (float) $style->length_in_pt($bp["bottom"]["width"]), (float) $style->length_in_pt($bp["left"]["width"])];
106 foreach ($bp as $side => $props) {
107 if ($props["style"] === "none" || $props["style"] === "hidden" || $props["color"] === "transparent" || $props["width"] <= 0) {
108 continue;
109 }
110 [$x, $y, $w, $h] = $border_box;
111 $method = "_border_" . $props["style"];
112 switch ($side) {
113 case "top":
114 $length = $w;
115 $r1 = $tl;
116 $r2 = $tr;
117 break;
118 case "bottom":
119 $length = $w;
120 $y += $h;
121 $r1 = $bl;
122 $r2 = $br;
123 break;
124 case "left":
125 $length = $h;
126 $r1 = $tl;
127 $r2 = $bl;
128 break;
129 case "right":
130 $length = $h;
131 $x += $w;
132 $r1 = $tr;
133 $r2 = $br;
134 break;
135 default:
136 break;
137 }
138 // draw rounded corners
139 $this->{$method}($x, $y, $length, $props["color"], $widths, $side, $corner_style, $r1, $r2);
140 }
141 }
142 /**
143 * @param Frame $frame
144 * @param float[] $border_box
145 * @param string $corner_style
146 */
147 protected function _render_outline(Frame $frame, array $border_box, string $corner_style = "bevel") : void
148 {
149 $style = $frame->get_style();
150 $width = $style->outline_width;
151 $outline_style = $style->outline_style;
152 $color = $style->outline_color;
153 if ($outline_style === "none" || $color === "transparent" || $width <= 0) {
154 return;
155 }
156 $offset = $style->outline_offset;
157 [$x, $y, $w, $h] = $border_box;
158 $d = $width + $offset;
159 $outline_box = [$x - $d, $y - $d, $w + $d * 2, $h + $d * 2];
160 [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $outline_box);
161 $x -= $offset;
162 $y -= $offset;
163 $w += $offset * 2;
164 $h += $offset * 2;
165 // For a simple outline, we can draw a rectangle
166 if ($outline_style === "solid" && !$style->has_border_radius()) {
167 $x -= $width / 2;
168 $y -= $width / 2;
169 $w += $width;
170 $h += $width;
171 $this->_canvas->rectangle($x, $y, $w, $h, $color, $width);
172 return;
173 }
174 $x -= $width;
175 $y -= $width;
176 $w += $width * 2;
177 $h += $width * 2;
178 $method = "_border_" . $outline_style;
179 $widths = \array_fill(0, 4, $width);
180 $sides = ["top", "right", "left", "bottom"];
181 foreach ($sides as $side) {
182 switch ($side) {
183 case "top":
184 $length = $w;
185 $side_x = $x;
186 $side_y = $y;
187 $r1 = $tl;
188 $r2 = $tr;
189 break;
190 case "bottom":
191 $length = $w;
192 $side_x = $x;
193 $side_y = $y + $h;
194 $r1 = $bl;
195 $r2 = $br;
196 break;
197 case "left":
198 $length = $h;
199 $side_x = $x;
200 $side_y = $y;
201 $r1 = $tl;
202 $r2 = $bl;
203 break;
204 case "right":
205 $length = $h;
206 $side_x = $x + $w;
207 $side_y = $y;
208 $r1 = $tr;
209 $r2 = $br;
210 break;
211 default:
212 break;
213 }
214 $this->{$method}($side_x, $side_y, $length, $color, $widths, $side, $corner_style, $r1, $r2);
215 }
216 }
217 /**
218 * Render a background image over a rectangular area
219 *
220 * @param string $url The background image to load
221 * @param float $x The left edge of the rectangular area
222 * @param float $y The top edge of the rectangular area
223 * @param float $width The width of the rectangular area
224 * @param float $height The height of the rectangular area
225 * @param Style $style The associated Style object
226 *
227 * @throws \Exception
228 */
229 protected function _background_image(string $url, float $x, float $y, float $width, float $height, Style $style) : void
230 {
231 if (!\function_exists("imagecreatetruecolor")) {
232 throw new \Exception("The PHP GD extension is required, but is not installed.");
233 }
234 $sheet = $style->get_stylesheet();
235 // Skip degenerate cases
236 if ($width == 0 || $height == 0) {
237 return;
238 }
239 $box_width = $width;
240 $box_height = $height;
241 //debugpng
242 if ($this->_dompdf->getOptions()->getDebugPng()) {
243 print '[_background_image ' . $url . ']';
244 }
245 list($img, $type, ) = Cache::resolve_url($url, $sheet->get_protocol(), $sheet->get_host(), $sheet->get_base_path(), $this->_dompdf->getOptions());
246 // Bail if the image is no good
247 if (Cache::is_broken($img)) {
248 return;
249 }
250 //Try to optimize away reading and composing of same background multiple times
251 //Postponing read with imagecreatefrom ...()
252 //final composition parameters and name not known yet
253 //Therefore read dimension directly from file, instead of creating gd object first.
254 //$img_w = imagesx($src); $img_h = imagesy($src);
255 list($img_w, $img_h) = Helpers::dompdf_getimagesize($img, $this->_dompdf->getHttpContext());
256 if ($img_w == 0 || $img_h == 0) {
257 return;
258 }
259 // save for later check if file needs to be resized.
260 $org_img_w = $img_w;
261 $org_img_h = $img_h;
262 $repeat = $style->background_repeat;
263 $dpi = $this->_dompdf->getOptions()->getDpi();
264 //Increase background resolution and dependent box size according to image resolution to be placed in
265 //Then image can be copied in without resize
266 $bg_width = \round((float) ($width * $dpi) / 72);
267 $bg_height = \round((float) ($height * $dpi) / 72);
268 list($img_w, $img_h) = $this->_resize_background_image($img_w, $img_h, $bg_width, $bg_height, $style->background_size, $dpi);
269 //Need %bg_x, $bg_y as background pos, where img starts, converted to pixel
270 list($bg_x, $bg_y) = $style->background_position;
271 if (Helpers::is_percent($bg_x)) {
272 // The point $bg_x % from the left edge of the image is placed
273 // $bg_x % from the left edge of the background rectangle
274 $p = (float) $bg_x / 100.0;
275 $x1 = $p * $img_w;
276 $x2 = $p * $bg_width;
277 $bg_x = $x2 - $x1;
278 } else {
279 $bg_x = (float) ($style->length_in_pt($bg_x) * $dpi) / 72;
280 }
281 $bg_x = \round($bg_x + (float) $style->length_in_pt($style->border_left_width) * $dpi / 72);
282 if (Helpers::is_percent($bg_y)) {
283 // The point $bg_y % from the left edge of the image is placed
284 // $bg_y % from the left edge of the background rectangle
285 $p = (float) $bg_y / 100.0;
286 $y1 = $p * $img_h;
287 $y2 = $p * $bg_height;
288 $bg_y = $y2 - $y1;
289 } else {
290 $bg_y = (float) ($style->length_in_pt($bg_y) * $dpi) / 72;
291 }
292 $bg_y = \round($bg_y + (float) $style->length_in_pt($style->border_top_width) * $dpi / 72);
293 //clip background to the image area on partial repeat. Nothing to do if img off area
294 //On repeat, normalize start position to the tile at immediate left/top or 0/0 of area
295 //On no repeat with positive offset: move size/start to have offset==0
296 //Handle x/y Dimensions separately
297 if ($repeat !== "repeat" && $repeat !== "repeat-x") {
298 //No repeat x
299 if ($bg_x < 0) {
300 $bg_width = $img_w + $bg_x;
301 } else {
302 $x += $bg_x * 72 / $dpi;
303 $bg_width = $bg_width - $bg_x;
304 if ($bg_width > $img_w) {
305 $bg_width = $img_w;
306 }
307 $bg_x = 0;
308 }
309 if ($bg_width <= 0) {
310 return;
311 }
312 $width = (float) ($bg_width * 72) / $dpi;
313 } else {
314 //repeat x
315 if ($bg_x < 0) {
316 $bg_x = -(-$bg_x % $img_w);
317 } else {
318 $bg_x = $bg_x % $img_w;
319 if ($bg_x > 0) {
320 $bg_x -= $img_w;
321 }
322 }
323 }
324 if ($repeat !== "repeat" && $repeat !== "repeat-y") {
325 //no repeat y
326 if ($bg_y < 0) {
327 $bg_height = $img_h + $bg_y;
328 } else {
329 $y += $bg_y * 72 / $dpi;
330 $bg_height = $bg_height - $bg_y;
331 if ($bg_height > $img_h) {
332 $bg_height = $img_h;
333 }
334 $bg_y = 0;
335 }
336 if ($bg_height <= 0) {
337 return;
338 }
339 $height = (float) ($bg_height * 72) / $dpi;
340 } else {
341 //repeat y
342 if ($bg_y < 0) {
343 $bg_y = -(-$bg_y % $img_h);
344 } else {
345 $bg_y = $bg_y % $img_h;
346 if ($bg_y > 0) {
347 $bg_y -= $img_h;
348 }
349 }
350 }
351 //Optimization, if repeat has no effect
352 if ($repeat === "repeat" && $bg_y <= 0 && $img_h + $bg_y >= $bg_height) {
353 $repeat = "repeat-x";
354 }
355 if ($repeat === "repeat" && $bg_x <= 0 && $img_w + $bg_x >= $bg_width) {
356 $repeat = "repeat-y";
357 }
358 if ($repeat === "repeat-x" && $bg_x <= 0 && $img_w + $bg_x >= $bg_width || $repeat === "repeat-y" && $bg_y <= 0 && $img_h + $bg_y >= $bg_height) {
359 $repeat = "no-repeat";
360 }
361 // Avoid rendering identical background-image variants multiple times
362 // This is not dependent of background color of box! .'_'.(is_array($bg_color) ? $bg_color["hex"] : $bg_color)
363 // Note: Here, bg_* are the start values, not end values after going through the tile loops!
364 $key = \implode("_", [$bg_width, $bg_height, $img_w, $img_h, $bg_x, $bg_y, $repeat]);
365 // FIXME: This will fail when a file with that exact name exists in the
366 // same directory, included in the document as regular image
367 $cpdfKey = $img . "_" . $key;
368 $tmpFile = Cache::getTempImage($img, $key);
369 $cached = $this->_canvas instanceof CPDF && $this->_canvas->get_cpdf()->image_iscached($cpdfKey) || $tmpFile !== null && \file_exists($tmpFile);
370 if (!$cached) {
371 // determmine if image is too big to be rendered
372 $maxImageBytes = $this->_dompdf->getOptions()->getImageByteSizeLimit();
373 $bgBytes = $bg_width * $bg_height * 4 * 4;
374 // 4 channels with 4 bytes per channel (e.g. truecolor with alpha)
375 if ($maxImageBytes > 0 && ($bgBytes === null || $bgBytes > $maxImageBytes)) {
376 Helpers::record_warnings(\E_USER_WARNING, "Background image dimensions are too large to be rendered (calculated size: " . \round($bgBytes / 1048576, 2) . " MB).", __FILE__, __LINE__);
377 return;
378 }
379 // img: image url string
380 // img_w, img_h: original image size in px
381 // width, height: box size in pt
382 // bg_width, bg_height: box size in px
383 // x, y: left/top edge of box on page in pt
384 // start_x, start_y: placement of image relative to pattern
385 // $repeat: repeat mode
386 // $bg: GD object of result image
387 // $src: GD object of original image
388 // Create a new image to fit over the background rectangle
389 $bg = \imagecreatetruecolor($bg_width, $bg_height);
390 $cpdfFromGd = \true;
391 $func_name = "imagecreatefrom{$type}";
392 if (\method_exists(Helpers::class, $func_name)) {
393 $func_name = [Helpers::class, $func_name];
394 } elseif (!\function_exists($func_name)) {
395 if (isset($bg) && \PHP_MAJOR_VERSION < 8) {
396 \imagedestroy($bg);
397 }
398 return;
399 }
400 switch (\strtolower($type)) {
401 /** @noinspection PhpMissingBreakStatementInspection */
402 case "png":
403 $cpdfFromGd = \false;
404 \imagesavealpha($bg, \true);
405 \imagealphablending($bg, \false);
406 /** @noinspection PhpMissingBreakStatementInspection */
407 case "jpeg":
408 /** @noinspection PhpMissingBreakStatementInspection */
409 case "webp":
410 /** @noinspection PhpMissingBreakStatementInspection */
411 case "gif":
412 case "bmp":
413 $src = @\call_user_func($func_name, $img);
414 break;
415 default:
416 return;
417 }
418 if ($src == null) {
419 if (isset($bg) && \PHP_MAJOR_VERSION < 8) {
420 \imagedestroy($bg);
421 }
422 return;
423 }
424 if ($img_w != $org_img_w || $img_h != $org_img_h) {
425 $imgBytes = $img_w * $img_h * 4 * 4;
426 // 4 channels with 4 bytes per channel (e.g. truecolor with alpha)
427 if ($maxImageBytes > 0 && ($imgBytes === null || $imgBytes > $maxImageBytes)) {
428 Helpers::record_warnings(\E_USER_WARNING, "Background image is too large to be rendered (calculated size: " . \round($imgBytes / 1048576, 2) . " MB).", __FILE__, __LINE__);
429 if (\PHP_MAJOR_VERSION < 8) {
430 \imagedestroy($src);
431 }
432 $src = null;
433 } else {
434 $newSrc = \imagecreatetruecolor($img_w, $img_h);
435 \imagealphablending($newSrc, \false);
436 \imagesavealpha($newSrc, \true);
437 \imagecopyresampled($newSrc, $src, 0, 0, 0, 0, $img_w, $img_h, \imagesx($src), \imagesy($src));
438 if (\PHP_MAJOR_VERSION < 8) {
439 \imagedestroy($src);
440 }
441 $src = $newSrc;
442 }
443 }
444 if ($src == null) {
445 if (isset($bg) && \PHP_MAJOR_VERSION < 8) {
446 \imagedestroy($bg);
447 }
448 return;
449 }
450 //Background color if box is not relevant here
451 //Non transparent image: box clipped to real size. Background non relevant.
452 //Transparent image: The image controls the transparency and lets shine through whatever background.
453 //However on transparent image preset the composed image with the transparency color,
454 //to keep the transparency when copying over the non transparent parts of the tiles.
455 $ti = \imagecolortransparent($src);
456 $palletsize = \imagecolorstotal($src);
457 if ($ti >= 0 && $ti < $palletsize) {
458 $tc = \imagecolorsforindex($src, $ti);
459 $ti = \imagecolorallocate($bg, $tc['red'], $tc['green'], $tc['blue']);
460 \imagefill($bg, 0, 0, $ti);
461 \imagecolortransparent($bg, $ti);
462 }
463 //This has only an effect for the non repeatable dimension.
464 //compute start of src and dest coordinates of the single copy
465 if ($bg_x < 0) {
466 $dst_x = 0;
467 $src_x = -$bg_x;
468 } else {
469 $src_x = 0;
470 $dst_x = $bg_x;
471 }
472 if ($bg_y < 0) {
473 $dst_y = 0;
474 $src_y = -$bg_y;
475 } else {
476 $src_y = 0;
477 $dst_y = $bg_y;
478 }
479 //For historical reasons exchange meanings of variables:
480 //start_* will be the start values, while bg_* will be the temporary start values in the loops
481 $start_x = $bg_x;
482 $start_y = $bg_y;
483 // Copy regions from the source image to the background
484 if ($repeat === "no-repeat") {
485 // Simply place the image on the background
486 \imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $img_h);
487 } elseif ($repeat === "repeat-x") {
488 for ($bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w) {
489 if ($bg_x < 0) {
490 $dst_x = 0;
491 $src_x = -$bg_x;
492 $w = $img_w + $bg_x;
493 } else {
494 $dst_x = $bg_x;
495 $src_x = 0;
496 $w = $img_w;
497 }
498 \imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $img_h);
499 }
500 } elseif ($repeat === "repeat-y") {
501 for ($bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h) {
502 if ($bg_y < 0) {
503 $dst_y = 0;
504 $src_y = -$bg_y;
505 $h = $img_h + $bg_y;
506 } else {
507 $dst_y = $bg_y;
508 $src_y = 0;
509 $h = $img_h;
510 }
511 \imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $h);
512 }
513 } elseif ($repeat === "repeat") {
514 for ($bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h) {
515 for ($bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w) {
516 if ($bg_x < 0) {
517 $dst_x = 0;
518 $src_x = -$bg_x;
519 $w = $img_w + $bg_x;
520 } else {
521 $dst_x = $bg_x;
522 $src_x = 0;
523 $w = $img_w;
524 }
525 if ($bg_y < 0) {
526 $dst_y = 0;
527 $src_y = -$bg_y;
528 $h = $img_h + $bg_y;
529 } else {
530 $dst_y = $bg_y;
531 $src_y = 0;
532 $h = $img_h;
533 }
534 \imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $h);
535 }
536 }
537 } else {
538 print 'Unknown repeat!';
539 }
540 if (\PHP_MAJOR_VERSION < 8) {
541 \imagedestroy($src);
542 }
543 if ($cpdfFromGd && $this->_canvas instanceof CPDF) {
544 // Skip writing temp file as the GD object is added directly
545 } else {
546 $tmpDir = $this->_dompdf->getOptions()->getTempDir();
547 $tmpName = @\tempnam($tmpDir, "bg_dompdf_img_");
548 @\unlink($tmpName);
549 $tmpFile = "{$tmpName}.png";
550 \imagepng($bg, $tmpFile);
551 if (\PHP_MAJOR_VERSION < 8) {
552 \imagedestroy($bg);
553 }
554 Cache::addTempImage($img, $tmpFile, $key);
555 }
556 } else {
557 $bg = null;
558 $cpdfFromGd = $tmpFile === null;
559 }
560 if ($this->_dompdf->getOptions()->getDebugPng()) {
561 print '[_background_image ' . $tmpFile . ']';
562 }
563 $this->_canvas->clipping_rectangle($x, $y, $box_width, $box_height);
564 // When using cpdf and optimization to direct png creation from gd object is available,
565 // don't create temp file, but place gd object directly into the pdf
566 if ($cpdfFromGd && $this->_canvas instanceof CPDF) {
567 // Note: CPDF_Adapter image converts y position
568 $this->_canvas->get_cpdf()->addImagePng($bg, $cpdfKey, $x, $this->_canvas->get_height() - $y - $height, $width, $height);
569 if (isset($bg) && \PHP_MAJOR_VERSION < 8) {
570 \imagedestroy($bg);
571 }
572 } else {
573 $this->_canvas->image($tmpFile, $x, $y, $width, $height);
574 }
575 $this->_canvas->clipping_end();
576 }
577 /**
578 * @param float $img_width
579 * @param float $img_height
580 * @param float $container_width
581 * @param float $container_height
582 * @param array|string $bg_resize
583 * @param int $dpi
584 *
585 * @return float[]
586 */
587 protected function _resize_background_image(float $img_width, float $img_height, float $container_width, float $container_height, $bg_resize, int $dpi) : array
588 {
589 // We got two some specific numbers and/or auto definitions
590 if (\is_array($bg_resize)) {
591 $is_auto_width = $bg_resize[0] === 'auto';
592 if ($is_auto_width) {
593 $new_img_width = $img_width;
594 } else {
595 $new_img_width = $bg_resize[0];
596 if (Helpers::is_percent($new_img_width)) {
597 $new_img_width = \round($container_width / 100 * (float) $new_img_width);
598 } else {
599 $new_img_width = \round($new_img_width * $dpi / 72);
600 }
601 }
602 $is_auto_height = $bg_resize[1] === 'auto';
603 if ($is_auto_height) {
604 $new_img_height = $img_height;
605 } else {
606 $new_img_height = $bg_resize[1];
607 if (Helpers::is_percent($new_img_height)) {
608 $new_img_height = \round($container_height / 100 * (float) $new_img_height);
609 } else {
610 $new_img_height = \round($new_img_height * $dpi / 72);
611 }
612 }
613 // if one of both was set to auto the other one needs to scale proportionally
614 if ($is_auto_width !== $is_auto_height) {
615 if ($is_auto_height) {
616 $new_img_height = \round($new_img_width * ($img_height / $img_width));
617 } else {
618 $new_img_width = \round($new_img_height * ($img_width / $img_height));
619 }
620 }
621 } else {
622 $container_ratio = $container_height / $container_width;
623 if ($bg_resize === 'cover' || $bg_resize === 'contain') {
624 $img_ratio = $img_height / $img_width;
625 if ($bg_resize === 'cover' && $container_ratio > $img_ratio || $bg_resize === 'contain' && $container_ratio < $img_ratio) {
626 $new_img_height = $container_height;
627 $new_img_width = \round($container_height / $img_ratio);
628 } else {
629 $new_img_width = $container_width;
630 $new_img_height = \round($container_width * $img_ratio);
631 }
632 } else {
633 $new_img_width = $img_width;
634 $new_img_height = $img_height;
635 }
636 }
637 return [$new_img_width, $new_img_height];
638 }
639 // Border rendering functions
640 /**
641 * @param float $x
642 * @param float $y
643 * @param float $length
644 * @param array $color
645 * @param float[] $widths
646 * @param string $side
647 * @param string $corner_style
648 * @param float $r1
649 * @param float $r2
650 */
651 protected function _border_dotted($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
652 {
653 $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dotted", $r1, $r2);
654 }
655 /**
656 * @param float $x
657 * @param float $y
658 * @param float $length
659 * @param array $color
660 * @param float[] $widths
661 * @param string $side
662 * @param string $corner_style
663 * @param float $r1
664 * @param float $r2
665 */
666 protected function _border_dashed($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
667 {
668 $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dashed", $r1, $r2);
669 }
670 /**
671 * @param float $x
672 * @param float $y
673 * @param float $length
674 * @param array $color
675 * @param float[] $widths
676 * @param string $side
677 * @param string $corner_style
678 * @param float $r1
679 * @param float $r2
680 */
681 protected function _border_solid($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
682 {
683 $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "solid", $r1, $r2);
684 }
685 /**
686 * @param string $side
687 * @param float $ratio
688 * @param float $top
689 * @param float $right
690 * @param float $bottom
691 * @param float $left
692 * @param float $x
693 * @param float $y
694 * @param float $length
695 * @param float $r1
696 * @param float $r2
697 */
698 protected function _apply_ratio($side, $ratio, $top, $right, $bottom, $left, &$x, &$y, &$length, &$r1, &$r2)
699 {
700 switch ($side) {
701 case "top":
702 $r1 -= $left * $ratio;
703 $r2 -= $right * $ratio;
704 $x += $left * $ratio;
705 $y += $top * $ratio;
706 $length -= $left * $ratio + $right * $ratio;
707 break;
708 case "bottom":
709 $r1 -= $right * $ratio;
710 $r2 -= $left * $ratio;
711 $x += $left * $ratio;
712 $y -= $bottom * $ratio;
713 $length -= $left * $ratio + $right * $ratio;
714 break;
715 case "left":
716 $r1 -= $top * $ratio;
717 $r2 -= $bottom * $ratio;
718 $x += $left * $ratio;
719 $y += $top * $ratio;
720 $length -= $top * $ratio + $bottom * $ratio;
721 break;
722 case "right":
723 $r1 -= $bottom * $ratio;
724 $r2 -= $top * $ratio;
725 $x -= $right * $ratio;
726 $y += $top * $ratio;
727 $length -= $top * $ratio + $bottom * $ratio;
728 break;
729 default:
730 return;
731 }
732 }
733 /**
734 * @param float $x
735 * @param float $y
736 * @param float $length
737 * @param array $color
738 * @param float[] $widths
739 * @param string $side
740 * @param string $corner_style
741 * @param float $r1
742 * @param float $r2
743 */
744 protected function _border_double($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
745 {
746 list($top, $right, $bottom, $left) = $widths;
747 $third_widths = [$top / 3, $right / 3, $bottom / 3, $left / 3];
748 // draw the outer border
749 $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2);
750 $this->_apply_ratio($side, 2 / 3, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
751 $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2);
752 }
753 /**
754 * @param float $x
755 * @param float $y
756 * @param float $length
757 * @param array $color
758 * @param float[] $widths
759 * @param string $side
760 * @param string $corner_style
761 * @param float $r1
762 * @param float $r2
763 */
764 protected function _border_groove($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
765 {
766 list($top, $right, $bottom, $left) = $widths;
767 $half_widths = [$top / 2, $right / 2, $bottom / 2, $left / 2];
768 $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
769 $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
770 $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
771 }
772 /**
773 * @param float $x
774 * @param float $y
775 * @param float $length
776 * @param array $color
777 * @param float[] $widths
778 * @param string $side
779 * @param string $corner_style
780 * @param float $r1
781 * @param float $r2
782 */
783 protected function _border_ridge($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
784 {
785 list($top, $right, $bottom, $left) = $widths;
786 $half_widths = [$top / 2, $right / 2, $bottom / 2, $left / 2];
787 $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
788 $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
789 $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
790 }
791 /**
792 * @param $c
793 * @return mixed
794 */
795 protected function _tint($c)
796 {
797 if (!\is_numeric($c)) {
798 return $c;
799 }
800 return \min(1, $c + 0.16);
801 }
802 /**
803 * @param $c
804 * @return mixed
805 */
806 protected function _shade($c)
807 {
808 if (!\is_numeric($c)) {
809 return $c;
810 }
811 return \max(0, $c - 0.33);
812 }
813 /**
814 * @param float $x
815 * @param float $y
816 * @param float $length
817 * @param array $color
818 * @param float[] $widths
819 * @param string $side
820 * @param string $corner_style
821 * @param float $r1
822 * @param float $r2
823 */
824 protected function _border_inset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
825 {
826 switch ($side) {
827 case "top":
828 case "left":
829 $shade = \array_map([$this, "_shade"], $color);
830 $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2);
831 break;
832 case "bottom":
833 case "right":
834 $tint = \array_map([$this, "_tint"], $color);
835 $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2);
836 break;
837 default:
838 return;
839 }
840 }
841 /**
842 * @param float $x
843 * @param float $y
844 * @param float $length
845 * @param array $color
846 * @param float[] $widths
847 * @param string $side
848 * @param string $corner_style
849 * @param float $r1
850 * @param float $r2
851 */
852 protected function _border_outset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
853 {
854 switch ($side) {
855 case "top":
856 case "left":
857 $tint = \array_map([$this, "_tint"], $color);
858 $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2);
859 break;
860 case "bottom":
861 case "right":
862 $shade = \array_map([$this, "_shade"], $color);
863 $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2);
864 break;
865 default:
866 return;
867 }
868 }
869 /**
870 * Get the dash pattern and cap style for the given border style, width, and
871 * line length.
872 *
873 * The base pattern is adjusted so that it fits the given line length
874 * symmetrically.
875 *
876 * @param string $style
877 * @param float $width
878 * @param float $length
879 *
880 * @return array
881 */
882 protected function dashPattern(string $style, float $width, float $length) : array
883 {
884 if ($style === "dashed") {
885 $w = 3 * $width;
886 if ($length < $w) {
887 $s = $w;
888 } else {
889 // Scale dashes and gaps
890 $r = \round($length / $w);
891 $r = $r % 2 === 0 ? $r + 1 : $r;
892 $s = $length / $r;
893 }
894 return [[$s], "butt"];
895 }
896 if ($style === "dotted") {
897 // Draw circles along the line
898 // Round caps extend outwards by half line width, so a zero dash
899 // width results in a circle
900 $gap = $width <= 1 ? 2 : 1;
901 $w = ($gap + 1) * $width;
902 if ($length < $w) {
903 $s = $w;
904 } else {
905 // Only scale gaps
906 $l = $length - $width;
907 $r = \max(\round($l / $w), 1);
908 $s = $l / $r;
909 }
910 return [[0, $s], "round"];
911 }
912 return [[], "butt"];
913 }
914 /**
915 * Draws a solid, dotted, or dashed line, observing the border radius
916 *
917 * @param float $x
918 * @param float $y
919 * @param float $length
920 * @param array $color
921 * @param float[] $widths
922 * @param string $side
923 * @param string $corner_style
924 * @param string $pattern_name
925 * @param float $r1
926 * @param float $r2
927 */
928 protected function _border_line($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $pattern_name = "none", $r1 = 0, $r2 = 0)
929 {
930 /** used by $$side */
931 [$top, $right, $bottom, $left] = $widths;
932 $width = ${$side};
933 // No need to clip corners if border radius is large enough
934 $cornerClip = $corner_style === "bevel" && ($r1 < $width || $r2 < $width);
935 $lineLength = $length - $r1 - $r2;
936 [$pattern, $cap] = $this->dashPattern($pattern_name, $width, $lineLength);
937 // Determine arc border radius for corner arcs
938 $halfWidth = $width / 2;
939 $ar1 = \max($r1 - $halfWidth, 0);
940 $ar2 = \max($r2 - $halfWidth, 0);
941 // Small angle adjustments to prevent the background from shining through
942 $adj1 = $ar1 / 80;
943 $adj2 = $ar2 / 80;
944 // Adjust line width and corner angles to account for the fact that
945 // round caps extend outwards. The line is actually only shifted below,
946 // not shortened, as otherwise the end dash (circle) will vanish
947 // occasionally
948 $dl = $cap === "round" ? $halfWidth : 0;
949 if ($cap === "round" && $ar1 > 0) {
950 $adj1 -= \rad2deg(\asin($halfWidth / $ar1));
951 }
952 if ($cap === "round" && $ar2 > 0) {
953 $adj2 -= \rad2deg(\asin($halfWidth / $ar2));
954 }
955 switch ($side) {
956 case "top":
957 if ($cornerClip) {
958 $points = [
959 $x,
960 $y,
961 $x,
962 $y - 1,
963 // Extend outwards to avoid gaps
964 $x + $length,
965 $y - 1,
966 // Extend outwards to avoid gaps
967 $x + $length,
968 $y,
969 $x + $length - \max($right, $r2),
970 $y + \max($width, $r2),
971 $x + \max($left, $r1),
972 $y + \max($width, $r1),
973 ];
974 $this->_canvas->clipping_polygon($points);
975 }
976 $y += $halfWidth;
977 if ($ar1 > 0 && $adj1 > -22.5) {
978 $this->_canvas->arc($x + $r1, $y + $ar1, $ar1, $ar1, 90 - $adj1, 135 + $adj1, $color, $width, $pattern, $cap);
979 }
980 if ($lineLength > 0) {
981 $this->_canvas->line($x + $dl + $r1, $y, $x + $dl + $length - $r2, $y, $color, $width, $pattern, $cap);
982 }
983 if ($ar2 > 0 && $adj2 > -22.5) {
984 $this->_canvas->arc($x + $length - $r2, $y + $ar2, $ar2, $ar2, 45 - $adj2, 90 + $adj2, $color, $width, $pattern, $cap);
985 }
986 break;
987 case "bottom":
988 if ($cornerClip) {
989 $points = [
990 $x,
991 $y,
992 $x,
993 $y + 1,
994 // Extend outwards to avoid gaps
995 $x + $length,
996 $y + 1,
997 // Extend outwards to avoid gaps
998 $x + $length,
999 $y,
1000 $x + $length - \max($right, $r2),
1001 $y - \max($width, $r2),
1002 $x + \max($left, $r1),
1003 $y - \max($width, $r1),
1004 ];
1005 $this->_canvas->clipping_polygon($points);
1006 }
1007 $y -= $halfWidth;
1008 if ($ar1 > 0 && $adj1 > -22.5) {
1009 $this->_canvas->arc($x + $r1, $y - $ar1, $ar1, $ar1, 225 - $adj1, 270 + $adj1, $color, $width, $pattern, $cap);
1010 }
1011 if ($lineLength > 0) {
1012 $this->_canvas->line($x + $dl + $r1, $y, $x + $dl + $length - $r2, $y, $color, $width, $pattern, $cap);
1013 }
1014 if ($ar2 > 0 && $adj2 > -22.5) {
1015 $this->_canvas->arc($x + $length - $r2, $y - $ar2, $ar2, $ar2, 270 - $adj2, 315 + $adj2, $color, $width, $pattern, $cap);
1016 }
1017 break;
1018 case "left":
1019 if ($cornerClip) {
1020 $points = [
1021 $x,
1022 $y,
1023 $x - 1,
1024 $y,
1025 // Extend outwards to avoid gaps
1026 $x - 1,
1027 $y + $length,
1028 // Extend outwards to avoid gaps
1029 $x,
1030 $y + $length,
1031 $x + \max($width, $r2),
1032 $y + $length - \max($bottom, $r2),
1033 $x + \max($width, $r1),
1034 $y + \max($top, $r1),
1035 ];
1036 $this->_canvas->clipping_polygon($points);
1037 }
1038 $x += $halfWidth;
1039 if ($ar1 > 0 && $adj1 > -22.5) {
1040 $this->_canvas->arc($x + $ar1, $y + $r1, $ar1, $ar1, 135 - $adj1, 180 + $adj1, $color, $width, $pattern, $cap);
1041 }
1042 if ($lineLength > 0) {
1043 $this->_canvas->line($x, $y + $dl + $r1, $x, $y + $dl + $length - $r2, $color, $width, $pattern, $cap);
1044 }
1045 if ($ar2 > 0 && $adj2 > -22.5) {
1046 $this->_canvas->arc($x + $ar2, $y + $length - $r2, $ar2, $ar2, 180 - $adj2, 225 + $adj2, $color, $width, $pattern, $cap);
1047 }
1048 break;
1049 case "right":
1050 if ($cornerClip) {
1051 $points = [
1052 $x,
1053 $y,
1054 $x + 1,
1055 $y,
1056 // Extend outwards to avoid gaps
1057 $x + 1,
1058 $y + $length,
1059 // Extend outwards to avoid gaps
1060 $x,
1061 $y + $length,
1062 $x - \max($width, $r2),
1063 $y + $length - \max($bottom, $r2),
1064 $x - \max($width, $r1),
1065 $y + \max($top, $r1),
1066 ];
1067 $this->_canvas->clipping_polygon($points);
1068 }
1069 $x -= $halfWidth;
1070 if ($ar1 > 0 && $adj1 > -22.5) {
1071 $this->_canvas->arc($x - $ar1, $y + $r1, $ar1, $ar1, 0 - $adj1, 45 + $adj1, $color, $width, $pattern, $cap);
1072 }
1073 if ($lineLength > 0) {
1074 $this->_canvas->line($x, $y + $dl + $r1, $x, $y + $dl + $length - $r2, $color, $width, $pattern, $cap);
1075 }
1076 if ($ar2 > 0 && $adj2 > -22.5) {
1077 $this->_canvas->arc($x - $ar2, $y + $length - $r2, $ar2, $ar2, 315 - $adj2, 360 + $adj2, $color, $width, $pattern, $cap);
1078 }
1079 break;
1080 }
1081 if ($cornerClip) {
1082 $this->_canvas->clipping_end();
1083 }
1084 }
1085 /**
1086 * @param float $opacity
1087 */
1088 protected function _set_opacity(float $opacity) : void
1089 {
1090 if ($opacity >= 0.0 && $opacity <= 1.0) {
1091 $this->_canvas->set_opacity($opacity);
1092 }
1093 }
1094 /**
1095 * Add a named destination if the element has an ID or is an anchor element
1096 * with `name` attribute.
1097 *
1098 * @param DOMElement $node
1099 */
1100 protected function addNamedDest(DOMElement $node) : void
1101 {
1102 $id = $node->getAttribute("id");
1103 if ($id !== "") {
1104 $this->_canvas->add_named_dest($id);
1105 }
1106 if ($node->nodeName === "a") {
1107 $name = $node->getAttribute("name");
1108 if ($name !== "") {
1109 $this->_canvas->add_named_dest($name);
1110 }
1111 }
1112 }
1113 /**
1114 * Add a hyperlink if the element is an anchor element with `href`
1115 * attribute.
1116 *
1117 * @param DOMElement $node
1118 * @param float[] $borderBox
1119 */
1120 protected function addHyperlink(DOMElement $node, array $borderBox) : void
1121 {
1122 if ($node->nodeName === "a" && ($href = $node->getAttribute("href")) !== "") {
1123 [$x, $y, $w, $h] = $borderBox;
1124 $dompdf = $this->_dompdf;
1125 $href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href, $dompdf->getOptions()->getChroot()) ?? $href;
1126 $this->_canvas->add_link($href, $x, $y, $w, $h);
1127 }
1128 }
1129 /**
1130 * @param float[] $box
1131 * @param array|string $color
1132 * @param array $style
1133 */
1134 protected function debugLayout(array $box, $color = "red", array $style = []) : void
1135 {
1136 $this->_canvas->rectangle($box[0], $box[1], $box[2], $box[3], Color::parse($color), 0.1, $style);
1137 }
1138 }
1139