1<?php 2 3namespace Mpdf\Writer; 4 5use Mpdf\Strict; 6use Mpdf\Mpdf; 7 8final class BackgroundWriter 9{ 10 11 use Strict; 12 13 /** 14 * @var \Mpdf\Mpdf 15 */ 16 private $mpdf; 17 18 /** 19 * @var \Mpdf\Writer\BaseWriter 20 */ 21 private $writer; 22 23 public function __construct(Mpdf $mpdf, BaseWriter $writer) 24 { 25 $this->mpdf = $mpdf; 26 $this->writer = $writer; 27 } 28 29 public function writePatterns() // _putpatterns 30 { 31 $patternCount = count($this->mpdf->patterns); 32 33 for ($i = 1; $i <= $patternCount; $i++) { 34 35 $x = $this->mpdf->patterns[$i]['x']; 36 $y = $this->mpdf->patterns[$i]['y']; 37 $w = $this->mpdf->patterns[$i]['w']; 38 $h = $this->mpdf->patterns[$i]['h']; 39 $pgh = $this->mpdf->patterns[$i]['pgh']; 40 $orig_w = $this->mpdf->patterns[$i]['orig_w']; 41 $orig_h = $this->mpdf->patterns[$i]['orig_h']; 42 $image_id = $this->mpdf->patterns[$i]['image_id']; 43 $itype = $this->mpdf->patterns[$i]['itype']; 44 45 if (isset($this->mpdf->patterns[$i]['bpa'])) { 46 $bpa = $this->mpdf->patterns[$i]['bpa']; 47 } else { 48 $bpa = []; // background positioning area 49 } 50 51 if ($this->mpdf->patterns[$i]['x_repeat']) { 52 $x_repeat = true; 53 } else { 54 $x_repeat = false; 55 } 56 57 if ($this->mpdf->patterns[$i]['y_repeat']) { 58 $y_repeat = true; 59 } else { 60 $y_repeat = false; 61 } 62 63 $x_pos = $this->mpdf->patterns[$i]['x_pos']; 64 65 if (false !== strpos($x_pos, '%')) { 66 $x_pos = (float) $x_pos; 67 $x_pos /= 100; 68 69 if (isset($bpa['w']) && $bpa['w']) { 70 $x_pos = ($bpa['w'] * $x_pos) - ($orig_w / Mpdf::SCALE * $x_pos); 71 } else { 72 $x_pos = ($w * $x_pos) - ($orig_w / Mpdf::SCALE * $x_pos); 73 } 74 } 75 76 $y_pos = $this->mpdf->patterns[$i]['y_pos']; 77 78 if (false !== strpos($y_pos, '%')) { 79 $y_pos = (float) $y_pos; 80 $y_pos /= 100; 81 82 if (isset($bpa['h']) && $bpa['h']) { 83 $y_pos = ($bpa['h'] * $y_pos) - ($orig_h / Mpdf::SCALE * $y_pos); 84 } else { 85 $y_pos = ($h * $y_pos) - ($orig_h / Mpdf::SCALE * $y_pos); 86 } 87 } 88 89 if (isset($bpa['x']) && $bpa['x']) { 90 $adj_x = ($x_pos + $bpa['x']) * Mpdf::SCALE; 91 } else { 92 $adj_x = ($x_pos + $x) * Mpdf::SCALE; 93 } 94 95 if (isset($bpa['y']) && $bpa['y']) { 96 $adj_y = (($pgh - $y_pos - $bpa['y']) * Mpdf::SCALE) - $orig_h; 97 } else { 98 $adj_y = (($pgh - $y_pos - $y) * Mpdf::SCALE) - $orig_h; 99 } 100 101 $img_obj = false; 102 103 if ($itype === 'svg' || $itype === 'wmf') { 104 foreach ($this->mpdf->formobjects as $fo) { 105 if ($fo['i'] == $image_id) { 106 $img_obj = $fo['n']; 107 $fo_w = $fo['w']; 108 $fo_h = -$fo['h']; 109 $wmf_x = $fo['x']; 110 $wmf_y = $fo['y']; 111 break; 112 } 113 } 114 } else { 115 foreach ($this->mpdf->images as $img) { 116 if ($img['i'] == $image_id) { 117 $img_obj = $img['n']; 118 break; 119 } 120 } 121 } 122 123 if (!$img_obj) { 124 throw new \Mpdf\MpdfException('Problem: Image object not found for background pattern ' . $img['i']); 125 } 126 127 $this->writer->object(); 128 $this->writer->write('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); 129 130 if ($itype === 'svg' || $itype === 'wmf') { 131 $this->writer->write('/XObject <</FO' . $image_id . ' ' . $img_obj . ' 0 R >>'); 132 133 // ******* ADD ANY ExtGStates, Shading AND Fonts needed for the FormObject 134 // Set in classes/svg array['fo'] = true 135 // Required that _putshaders comes before _putpatterns in _putresources 136 // This adds any resources associated with any FormObject to every Formobject - overkill but works! 137 if (count($this->mpdf->extgstates)) { 138 $this->writer->write('/ExtGState <<'); 139 foreach ($this->mpdf->extgstates as $k => $extgstate) { 140 if (isset($extgstate['fo']) && $extgstate['fo']) { 141 if (isset($extgstate['trans'])) { 142 $this->writer->write('/' . $extgstate['trans'] . ' ' . $extgstate['n'] . ' 0 R'); 143 } else { 144 $this->writer->write('/GS' . $k . ' ' . $extgstate['n'] . ' 0 R'); 145 } 146 } 147 } 148 $this->writer->write('>>'); 149 } 150 151 /* -- BACKGROUNDS -- */ 152 if (isset($this->mpdf->gradients) && ( count($this->mpdf->gradients) > 0)) { 153 $this->writer->write('/Shading <<'); 154 foreach ($this->mpdf->gradients as $id => $grad) { 155 if (isset($grad['fo']) && $grad['fo']) { 156 $this->writer->write('/Sh' . $id . ' ' . $grad['id'] . ' 0 R'); 157 } 158 } 159 $this->writer->write('>>'); 160 } 161 162 /* -- END BACKGROUNDS -- */ 163 $this->writer->write('/Font <<'); 164 165 foreach ($this->mpdf->fonts as $font) { 166 if (!$font['used'] && $font['type'] === 'TTF') { 167 continue; 168 } 169 if (isset($font['fo']) && $font['fo']) { 170 if ($font['type'] === 'TTF' && ($font['sip'] || $font['smp'])) { 171 foreach ($font['n'] as $k => $fid) { 172 $this->writer->write('/F' . $font['subsetfontids'][$k] . ' ' . $font['n'][$k] . ' 0 R'); 173 } 174 } else { 175 $this->writer->write('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); 176 } 177 } 178 } 179 $this->writer->write('>>'); 180 } else { 181 $this->writer->write('/XObject <</I' . $image_id . ' ' . $img_obj . ' 0 R >>'); 182 } 183 184 $this->writer->write('>>'); 185 $this->writer->write('endobj'); 186 187 $this->writer->object(); 188 $this->mpdf->patterns[$i]['n'] = $this->mpdf->n; 189 $this->writer->write('<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 2'); 190 $this->writer->write('/Resources ' . ($this->mpdf->n - 1) . ' 0 R'); 191 192 $this->writer->write(sprintf('/BBox [0 0 %.3F %.3F]', $orig_w, $orig_h)); 193 194 if ($x_repeat) { 195 $this->writer->write(sprintf('/XStep %.3F', $orig_w)); 196 } else { 197 $this->writer->write(sprintf('/XStep %d', 99999)); 198 } 199 200 if ($y_repeat) { 201 $this->writer->write(sprintf('/YStep %.3F', $orig_h)); 202 } else { 203 $this->writer->write(sprintf('/YStep %d', 99999)); 204 } 205 206 if ($itype === 'svg' || $itype === 'wmf') { 207 $this->writer->write(sprintf('/Matrix [1 0 0 -1 %.3F %.3F]', $adj_x, $adj_y + $orig_h)); 208 $s = sprintf('q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $orig_w / $fo_w, -$orig_h / $fo_h, -($orig_w / $fo_w) * $wmf_x, ($orig_w / $fo_w) * $wmf_y, $image_id); 209 } else { 210 $this->writer->write(sprintf('/Matrix [1 0 0 1 %.3F %.3F]', $adj_x, $adj_y)); 211 $s = sprintf('q %.3F 0 0 %.3F 0 0 cm /I%d Do Q', $orig_w, $orig_h, $image_id); 212 } 213 214 if ($this->mpdf->compress) { 215 $this->writer->write('/Filter /FlateDecode'); 216 $s = gzcompress($s); 217 } 218 $this->writer->write('/Length ' . strlen($s) . '>>'); 219 $this->writer->stream($s); 220 $this->writer->write('endobj'); 221 } 222 } 223 224 public function writeShaders() // _putshaders 225 { 226 $maxid = count($this->mpdf->gradients); // index for transparency gradients 227 228 foreach ($this->mpdf->gradients as $id => $grad) { 229 230 if (empty($grad['is_mask']) && ($grad['type'] == 2 || $grad['type'] == 3)) { 231 232 $this->writer->object(); 233 $this->writer->write('<<'); 234 $this->writer->write('/FunctionType 3'); 235 $this->writer->write('/Domain [0 1]'); 236 237 $fn = []; 238 $bd = []; 239 $en = []; 240 241 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { 242 $fn[] = ($this->mpdf->n + 1 + $i) . ' 0 R'; 243 $en[] = '0 1'; 244 if ($i > 0) { 245 $bd[] = sprintf('%.3F', $grad['stops'][$i]['offset']); 246 } 247 } 248 249 $this->writer->write('/Functions [' . implode(' ', $fn) . ']'); 250 $this->writer->write('/Bounds [' . implode(' ', $bd) . ']'); 251 $this->writer->write('/Encode [' . implode(' ', $en) . ']'); 252 $this->writer->write('>>'); 253 $this->writer->write('endobj'); 254 255 $f1 = $this->mpdf->n; 256 257 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { 258 $this->writer->object(); 259 $this->writer->write('<<'); 260 $this->writer->write('/FunctionType 2'); 261 $this->writer->write('/Domain [0 1]'); 262 $this->writer->write('/C0 [' . $grad['stops'][$i]['col'] . ']'); 263 $this->writer->write('/C1 [' . $grad['stops'][$i + 1]['col'] . ']'); 264 $this->writer->write('/N 1'); 265 $this->writer->write('>>'); 266 $this->writer->write('endobj'); 267 } 268 } 269 270 if ($grad['type'] == 2 || $grad['type'] == 3) { 271 272 if (isset($grad['trans']) && $grad['trans']) { 273 274 $this->writer->object(); 275 $this->writer->write('<<'); 276 $this->writer->write('/FunctionType 3'); 277 $this->writer->write('/Domain [0 1]'); 278 279 $fn = []; 280 $bd = []; 281 $en = []; 282 283 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { 284 $fn[] = ($this->mpdf->n + 1 + $i) . ' 0 R'; 285 $en[] = '0 1'; 286 if ($i > 0) { 287 $bd[] = sprintf('%.3F', $grad['stops'][$i]['offset']); 288 } 289 } 290 291 $this->writer->write('/Functions [' . implode(' ', $fn) . ']'); 292 $this->writer->write('/Bounds [' . implode(' ', $bd) . ']'); 293 $this->writer->write('/Encode [' . implode(' ', $en) . ']'); 294 $this->writer->write('>>'); 295 $this->writer->write('endobj'); 296 297 $f2 = $this->mpdf->n; 298 299 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { 300 $this->writer->object(); 301 $this->writer->write('<<'); 302 $this->writer->write('/FunctionType 2'); 303 $this->writer->write('/Domain [0 1]'); 304 $this->writer->write(sprintf('/C0 [%.3F]', $grad['stops'][$i]['opacity'])); 305 $this->writer->write(sprintf('/C1 [%.3F]', $grad['stops'][$i + 1]['opacity'])); 306 $this->writer->write('/N 1'); 307 $this->writer->write('>>'); 308 $this->writer->write('endobj'); 309 } 310 } 311 } 312 313 if (empty($grad['is_mask'])) { 314 315 $this->writer->object(); 316 $this->writer->write('<<'); 317 $this->writer->write('/ShadingType ' . $grad['type']); 318 319 if (isset($grad['colorspace'])) { 320 $this->writer->write('/ColorSpace /Device' . $grad['colorspace']); // Can use CMYK if all C0 and C1 above have 4 values 321 } else { 322 $this->writer->write('/ColorSpace /DeviceRGB'); 323 } 324 325 if ($grad['type'] == 2) { 326 $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3])); 327 $this->writer->write('/Function ' . $f1 . ' 0 R'); 328 $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); 329 $this->writer->write('>>'); 330 } elseif ($grad['type'] == 3) { 331 // x0, y0, r0, x1, y1, r1 332 // at this this time radius of inner circle is 0 333 $ir = 0; 334 if (isset($grad['coords'][5]) && $grad['coords'][5]) { 335 $ir = $grad['coords'][5]; 336 } 337 $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $ir, $grad['coords'][2], $grad['coords'][3], $grad['coords'][4])); 338 $this->writer->write('/Function ' . $f1 . ' 0 R'); 339 $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); 340 $this->writer->write('>>'); 341 } elseif ($grad['type'] == 6) { 342 $this->writer->write('/BitsPerCoordinate 16'); 343 $this->writer->write('/BitsPerComponent 8'); 344 if ($grad['colorspace'] === 'CMYK') { 345 $this->writer->write('/Decode[0 1 0 1 0 1 0 1 0 1 0 1]'); 346 } elseif ($grad['colorspace'] === 'Gray') { 347 $this->writer->write('/Decode[0 1 0 1 0 1]'); 348 } else { 349 $this->writer->write('/Decode[0 1 0 1 0 1 0 1 0 1]'); 350 } 351 $this->writer->write('/BitsPerFlag 8'); 352 $this->writer->write('/Length ' . strlen($grad['stream'])); 353 $this->writer->write('>>'); 354 $this->writer->stream($grad['stream']); 355 } 356 357 $this->writer->write('endobj'); 358 } 359 360 $this->mpdf->gradients[$id]['id'] = $this->mpdf->n; 361 362 // set pattern object 363 $this->writer->object(); 364 $out = '<< /Type /Pattern /PatternType 2'; 365 $out .= ' /Shading ' . $this->mpdf->gradients[$id]['id'] . ' 0 R'; 366 $out .= ' >>'; 367 $out .= "\n" . 'endobj'; 368 $this->writer->write($out); 369 370 371 $this->mpdf->gradients[$id]['pattern'] = $this->mpdf->n; 372 373 if (isset($grad['trans']) && $grad['trans']) { 374 375 // luminosity pattern 376 $transid = $id + $maxid; 377 378 $this->writer->object(); 379 $this->writer->write('<<'); 380 $this->writer->write('/ShadingType ' . $grad['type']); 381 $this->writer->write('/ColorSpace /DeviceGray'); 382 383 if ($grad['type'] == 2) { 384 $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3])); 385 $this->writer->write('/Function ' . $f2 . ' 0 R'); 386 $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); 387 $this->writer->write('>>'); 388 } elseif ($grad['type'] == 3) { 389 // x0, y0, r0, x1, y1, r1 390 // at this this time radius of inner circle is 0 391 $ir = 0; 392 if (isset($grad['coords'][5]) && $grad['coords'][5]) { 393 $ir = $grad['coords'][5]; 394 } 395 $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $ir, $grad['coords'][2], $grad['coords'][3], $grad['coords'][4])); 396 $this->writer->write('/Function ' . $f2 . ' 0 R'); 397 $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); 398 $this->writer->write('>>'); 399 } elseif ($grad['type'] == 6) { 400 $this->writer->write('/BitsPerCoordinate 16'); 401 $this->writer->write('/BitsPerComponent 8'); 402 $this->writer->write('/Decode[0 1 0 1 0 1]'); 403 $this->writer->write('/BitsPerFlag 8'); 404 $this->writer->write('/Length ' . strlen($grad['stream_trans'])); 405 $this->writer->write('>>'); 406 $this->writer->stream($grad['stream_trans']); 407 } 408 $this->writer->write('endobj'); 409 410 $this->mpdf->gradients[$transid]['id'] = $this->mpdf->n; 411 412 $this->writer->object(); 413 $this->writer->write('<< /Type /Pattern /PatternType 2'); 414 $this->writer->write('/Shading ' . $this->mpdf->gradients[$transid]['id'] . ' 0 R'); 415 $this->writer->write('>>'); 416 $this->writer->write('endobj'); 417 418 $this->mpdf->gradients[$transid]['pattern'] = $this->mpdf->n; 419 $this->writer->object(); 420 421 // Need to extend size of viewing box in case of transformations 422 $str = 'q /a0 gs /Pattern cs /p' . $transid . ' scn -' . ($this->mpdf->wPt / 2) . ' -' . ($this->mpdf->hPt / 2) . ' ' . (2 * $this->mpdf->wPt) . ' ' . (2 * $this->mpdf->hPt) . ' re f Q'; 423 $filter = ($this->mpdf->compress) ? '/Filter /FlateDecode ' : ''; 424 $p = ($this->mpdf->compress) ? gzcompress($str) : $str; 425 426 $this->writer->write('<< /Type /XObject /Subtype /Form /FormType 1 ' . $filter); 427 $this->writer->write('/Length ' . strlen($p)); 428 $this->writer->write('/BBox [-' . ($this->mpdf->wPt / 2) . ' -' . ($this->mpdf->hPt / 2) . ' ' . (2 * $this->mpdf->wPt) . ' ' . (2 * $this->mpdf->hPt) . ']'); 429 $this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceGray >>'); 430 $this->writer->write('/Resources <<'); 431 $this->writer->write('/ExtGState << /a0 << /ca 1 /CA 1 >> >>'); 432 $this->writer->write('/Pattern << /p' . $transid . ' ' . $this->mpdf->gradients[$transid]['pattern'] . ' 0 R >>'); 433 $this->writer->write('>>'); 434 $this->writer->write('>>'); 435 $this->writer->stream($p); 436 $this->writer->write('endobj'); 437 $this->writer->object(); 438 $this->writer->write('<< /Type /Mask /S /Luminosity /G ' . ($this->mpdf->n - 1) . ' 0 R >>' . "\n" . 'endobj'); 439 $this->writer->object(); 440 $this->writer->write('<< /Type /ExtGState /SMask ' . ($this->mpdf->n - 1) . ' 0 R /AIS false >>' . "\n" . 'endobj'); 441 442 if (isset($grad['fo']) && $grad['fo']) { 443 $this->mpdf->extgstates[] = ['n' => $this->mpdf->n, 'trans' => 'TGS' . $id, 'fo' => true]; 444 } else { 445 $this->mpdf->extgstates[] = ['n' => $this->mpdf->n, 'trans' => 'TGS' . $id]; 446 } 447 } 448 } 449 } 450 451} 452