1<?php
2
3namespace Mpdf\Writer;
4
5use Mpdf\Strict;
6use Mpdf\Mpdf;
7use Mpdf\Form;
8
9final class PageWriter
10{
11
12	use Strict;
13
14	/**
15	 * @var \Mpdf\Mpdf
16	 */
17	private $mpdf;
18
19	/**
20	 * @var \Mpdf\Form
21	 */
22	private $form;
23
24	/**
25	 * @var \Mpdf\Writer\BaseWriter
26	 */
27	private $writer;
28
29	/**
30	 * @var \Mpdf\Writer\MetadataWriter
31	 */
32	private $metadataWriter;
33
34	public function __construct(Mpdf $mpdf, Form $form, BaseWriter $writer, MetadataWriter $metadataWriter)
35	{
36		$this->mpdf = $mpdf;
37		$this->form = $form;
38		$this->writer = $writer;
39		$this->metadataWriter = $metadataWriter;
40	}
41
42	public function writePages() // _putpages
43	{
44		$nb = $this->mpdf->page;
45		$filter = $this->mpdf->compress ? '/Filter /FlateDecode ' : '';
46
47		if ($this->mpdf->DefOrientation === 'P') {
48			$defwPt = $this->mpdf->fwPt;
49			$defhPt = $this->mpdf->fhPt;
50		} else {
51			$defwPt = $this->mpdf->fhPt;
52			$defhPt = $this->mpdf->fwPt;
53		}
54
55		$annotid = (3 + 2 * $nb);
56
57		// Active Forms
58		$totaladdnum = 0;
59		for ($n = 1; $n <= $nb; $n++) {
60			if (isset($this->mpdf->PageLinks[$n])) {
61				$totaladdnum += count($this->mpdf->PageLinks[$n]);
62			}
63
64			/* -- ANNOTATIONS -- */
65			if (isset($this->mpdf->PageAnnots[$n])) {
66				foreach ($this->mpdf->PageAnnots[$n] as $k => $pl) {
67					if (!empty($pl['opt']['popup']) || !empty($pl['opt']['file'])) {
68						$totaladdnum += 2;
69					} else {
70						$totaladdnum++;
71					}
72				}
73			}
74			/* -- END ANNOTATIONS -- */
75
76			/* -- FORMS -- */
77			if (count($this->form->forms) > 0) {
78				$this->form->countPageForms($n, $totaladdnum);
79			}
80			/* -- END FORMS -- */
81		}
82
83		/* -- FORMS -- */
84		// Make a note in the radio button group of the obj_id it will have
85		$ctr = 0;
86		if (count($this->form->form_radio_groups)) {
87			foreach ($this->form->form_radio_groups as $name => $frg) {
88				$this->form->form_radio_groups[$name]['obj_id'] = $annotid + $totaladdnum + $ctr;
89				$ctr++;
90			}
91		}
92		/* -- END FORMS -- */
93
94		// Select unused fonts (usually default font)
95		$unused = [];
96		foreach ($this->mpdf->fonts as $fk => $font) {
97			if (isset($font['type']) && $font['type'] === 'TTF' && !$font['used']) {
98				$unused[] = $fk;
99			}
100		}
101
102		for ($n = 1; $n <= $nb; $n++) {
103
104			$thispage = $this->mpdf->pages[$n];
105
106			if (isset($this->mpdf->OrientationChanges[$n])) {
107				$hPt = $this->mpdf->pageDim[$n]['w'] * Mpdf::SCALE;
108				$wPt = $this->mpdf->pageDim[$n]['h'] * Mpdf::SCALE;
109				$owidthPt_LR = $this->mpdf->pageDim[$n]['outer_width_TB'] * Mpdf::SCALE;
110				$owidthPt_TB = $this->mpdf->pageDim[$n]['outer_width_LR'] * Mpdf::SCALE;
111			} else {
112				$wPt = $this->mpdf->pageDim[$n]['w'] * Mpdf::SCALE;
113				$hPt = $this->mpdf->pageDim[$n]['h'] * Mpdf::SCALE;
114				$owidthPt_LR = $this->mpdf->pageDim[$n]['outer_width_LR'] * Mpdf::SCALE;
115				$owidthPt_TB = $this->mpdf->pageDim[$n]['outer_width_TB'] * Mpdf::SCALE;
116			}
117
118			// Remove references to unused fonts (usually default font)
119			foreach ($unused as $fk) {
120				if ($this->mpdf->fonts[$fk]['sip'] || $this->mpdf->fonts[$fk]['smp']) {
121					foreach ($this->mpdf->fonts[$fk]['subsetfontids'] as $k => $fid) {
122						$thispage = preg_replace('/\s\/F' . $fid . ' \d[\d.]* Tf\s/is', ' ', $thispage);
123					}
124				} else {
125					$thispage = preg_replace('/\s\/F' . $this->mpdf->fonts[$fk]['i'] . ' \d[\d.]* Tf\s/is', ' ', $thispage);
126				}
127			}
128
129			// Clean up repeated /GS1 gs statements
130			// For some reason using + for repetition instead of {2,20} crashes PHP Script Interpreter ???
131			$thispage = preg_replace('/(\/GS1 gs\n){2,20}/', "/GS1 gs\n", $thispage);
132
133			$thispage = preg_replace('/(\s*___BACKGROUND___PATTERNS' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage);
134			$thispage = preg_replace('/(\s*___HEADER___MARKER' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage);
135			$thispage = preg_replace('/(\s*___PAGE___START' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage);
136			$thispage = preg_replace('/(\s*___TABLE___BACKGROUNDS' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage);
137
138			// mPDF 5.7.3 TRANSFORMS
139			while (preg_match('/(\% BTR(.*?)\% ETR)/is', $thispage, $m)) {
140				$thispage = preg_replace('/(\% BTR.*?\% ETR)/is', '', $thispage, 1) . "\n" . $m[2];
141			}
142
143			// Page
144			$this->writer->object();
145			$this->writer->write('<</Type /Page');
146			$this->writer->write('/Parent 1 0 R');
147
148			if (isset($this->mpdf->OrientationChanges[$n])) {
149
150				$this->writer->write(sprintf('/MediaBox [0 0 %.3F %.3F]', $hPt, $wPt));
151
152				// If BleedBox is defined, it must be larger than the TrimBox, but smaller than the MediaBox
153				$bleedMargin = $this->mpdf->pageDim[$n]['bleedMargin'] * Mpdf::SCALE;
154
155				if ($bleedMargin && ($owidthPt_TB || $owidthPt_LR)) {
156					$x0 = $owidthPt_TB - $bleedMargin;
157					$y0 = $owidthPt_LR - $bleedMargin;
158					$x1 = $hPt - $owidthPt_TB + $bleedMargin;
159					$y1 = $wPt - $owidthPt_LR + $bleedMargin;
160					$this->writer->write(sprintf('/BleedBox [%.3F %.3F %.3F %.3F]', $x0, $y0, $x1, $y1));
161				}
162
163				$this->writer->write(sprintf('/TrimBox [%.3F %.3F %.3F %.3F]', $owidthPt_TB, $owidthPt_LR, $hPt - $owidthPt_TB, $wPt - $owidthPt_LR));
164
165				if ($this->mpdf->displayDefaultOrientation) {
166					if ($this->mpdf->DefOrientation === 'P') {
167						$this->writer->write('/Rotate 270');
168					} else {
169						$this->writer->write('/Rotate 90');
170					}
171				}
172
173			} else { // elseif($wPt != $defwPt || $hPt != $defhPt) {
174
175				$this->writer->write(sprintf('/MediaBox [0 0 %.3F %.3F]', $wPt, $hPt));
176				$bleedMargin = $this->mpdf->pageDim[$n]['bleedMargin'] * Mpdf::SCALE;
177
178				if ($bleedMargin && ($owidthPt_TB || $owidthPt_LR)) {
179					$x0 = $owidthPt_LR - $bleedMargin;
180					$y0 = $owidthPt_TB - $bleedMargin;
181					$x1 = $wPt - $owidthPt_LR + $bleedMargin;
182					$y1 = $hPt - $owidthPt_TB + $bleedMargin;
183					$this->writer->write(sprintf('/BleedBox [%.3F %.3F %.3F %.3F]', $x0, $y0, $x1, $y1));
184				}
185
186				$this->writer->write(sprintf('/TrimBox [%.3F %.3F %.3F %.3F]', $owidthPt_LR, $owidthPt_TB, $wPt - $owidthPt_LR, $hPt - $owidthPt_TB));
187			}
188			$this->writer->write('/Resources 2 0 R');
189
190			// Important to keep in RGB colorSpace when using transparency
191			if (!$this->mpdf->PDFA && !$this->mpdf->PDFX) {
192				if ($this->mpdf->restrictColorSpace === 3) {
193					$this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceCMYK >> ');
194				} elseif ($this->mpdf->restrictColorSpace === 1) {
195					$this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceGray >> ');
196				} else {
197					$this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceRGB >> ');
198				}
199			}
200
201			$annotsnum = 0;
202			$embeddedfiles = []; // mPDF 5.7.2 /EmbeddedFiles
203
204			if (isset($this->mpdf->PageLinks[$n])) {
205				$annotsnum += count($this->mpdf->PageLinks[$n]);
206			}
207
208			if (isset($this->mpdf->PageAnnots[$n])) {
209				foreach ($this->mpdf->PageAnnots[$n] as $k => $pl) {
210					if (!empty($pl['opt']['file'])) {
211						$embeddedfiles[$annotsnum + 1] = true;
212					} // mPDF 5.7.2 /EmbeddedFiles
213					if (!empty($pl['opt']['popup']) || !empty($pl['opt']['file'])) {
214						$annotsnum += 2;
215					} else {
216						$annotsnum++;
217					}
218					$this->mpdf->PageAnnots[$n][$k]['pageobj'] = $this->mpdf->n;
219				}
220			}
221
222			// Active Forms
223			$formsnum = 0;
224			if (count($this->form->forms) > 0) {
225				foreach ($this->form->forms as $val) {
226					if ($val['page'] == $n) {
227						$formsnum++;
228					}
229				}
230			}
231
232			if ($annotsnum || $formsnum) {
233
234				$s = '/Annots [ ';
235
236				for ($i = 0; $i < $annotsnum; $i++) {
237					if (!isset($embeddedfiles[$i])) {
238						$s .= ($annotid + $i) . ' 0 R ';
239					} // mPDF 5.7.2 /EmbeddedFiles
240				}
241
242				$annotid += $annotsnum;
243
244				/* -- FORMS -- */
245				if (count($this->form->forms) > 0) {
246					$this->form->addFormIds($n, $s, $annotid);
247				}
248				/* -- END FORMS -- */
249
250				$s .= '] ';
251				$this->writer->write($s);
252			}
253
254			$this->writer->write('/Contents ' . ($this->mpdf->n + 1) . ' 0 R>>');
255			$this->writer->write('endobj');
256
257			// Page content
258			$this->writer->object();
259			$p = $this->mpdf->compress ? gzcompress($thispage) : $thispage;
260			$this->writer->write('<<' . $filter . '/Length ' . strlen($p) . '>>');
261			$this->writer->stream($p);
262			$this->writer->write('endobj');
263		}
264
265		$this->metadataWriter->writeAnnotations(); // mPDF 5.7.2
266
267		// Pages root
268		$this->mpdf->offsets[1] = strlen($this->mpdf->buffer);
269		$this->writer->write('1 0 obj');
270		$this->writer->write('<</Type /Pages');
271
272		$kids = '/Kids [';
273
274		for ($i = 0; $i < $nb; $i++) {
275			$kids .= (3 + 2 * $i) . ' 0 R ';
276		}
277
278		$this->writer->write($kids . ']');
279		$this->writer->write('/Count ' . $nb);
280		$this->writer->write(sprintf('/MediaBox [0 0 %.3F %.3F]', $defwPt, $defhPt));
281		$this->writer->write('>>');
282		$this->writer->write('endobj');
283	}
284
285}
286