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