1<?php
2
3namespace Mpdf\Writer;
4
5use Mpdf\Strict;
6use Mpdf\Mpdf;
7use Mpdf\Pdf\Protection;
8
9final class BaseWriter
10{
11
12	use Strict;
13
14	/**
15	 * @var \Mpdf\Mpdf
16	 */
17	private $mpdf;
18
19	/**
20	 * @var \Mpdf\Pdf\Protection
21	 */
22	private $protection;
23
24	public function __construct(Mpdf $mpdf, Protection $protection)
25	{
26		$this->mpdf = $mpdf;
27		$this->protection = $protection;
28	}
29
30	public function write($s, $ln = true)
31	{
32		if ($this->mpdf->state === 2) {
33			$this->endPage($s, $ln);
34		} else {
35			$this->mpdf->buffer .= $s . ($ln ? "\n" : '');
36		}
37	}
38
39	public function string($s)
40	{
41		if ($this->mpdf->encrypted) {
42			$s = $this->protection->rc4($this->protection->objectKey($this->mpdf->currentObjectNumber), $s);
43		}
44
45		return '(' . $this->escape($s) . ')';
46	}
47
48	public function object($obj_id = false, $onlynewobj = false)
49	{
50		if (!$obj_id) {
51			$obj_id = ++$this->mpdf->n;
52		}
53
54		// Begin a new object
55		if (!$onlynewobj) {
56			$this->mpdf->offsets[$obj_id] = strlen($this->mpdf->buffer);
57			$this->write($obj_id . ' 0 obj');
58			$this->mpdf->currentObjectNumber = $obj_id; // for later use with encryption
59		}
60	}
61
62	public function stream($s)
63	{
64		if ($this->mpdf->encrypted) {
65			$s = $this->protection->rc4($this->protection->objectKey($this->mpdf->currentObjectNumber), $s);
66		}
67
68		$this->write('stream');
69		$this->write($s);
70		$this->write('endstream');
71	}
72
73	public function utf16BigEndianTextString($s) // _UTF16BEtextstring
74	{
75		$s = $this->utf8ToUtf16BigEndian($s, true);
76		if ($this->mpdf->encrypted) {
77			$s = $this->protection->rc4($this->protection->objectKey($this->mpdf->currentObjectNumber), $s);
78		}
79
80		return '(' . $this->escape($s) . ')';
81	}
82
83	// Converts UTF-8 strings to UTF16-BE.
84	public function utf8ToUtf16BigEndian($str, $setbom = true) // UTF8ToUTF16BE
85	{
86		if ($this->mpdf->checkSIP && preg_match("/([\x{20000}-\x{2FFFF}])/u", $str)) {
87			if (!in_array($this->mpdf->currentfontfamily, ['gb', 'big5', 'sjis', 'uhc', 'gbB', 'big5B', 'sjisB', 'uhcB', 'gbI', 'big5I', 'sjisI', 'uhcI',
88				'gbBI', 'big5BI', 'sjisBI', 'uhcBI'])) {
89				$str = preg_replace("/[\x{20000}-\x{2FFFF}]/u", chr(0), $str);
90			}
91		}
92		if ($this->mpdf->checkSMP && preg_match("/([\x{10000}-\x{1FFFF}])/u", $str)) {
93			$str = preg_replace("/[\x{10000}-\x{1FFFF}]/u", chr(0), $str);
94		}
95
96		$outstr = ''; // string to be returned
97		if ($setbom) {
98			$outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
99		}
100
101		$outstr .= mb_convert_encoding($str, 'UTF-16BE', 'UTF-8');
102
103		return $outstr;
104	}
105
106	public function escape($s) // _escape
107	{
108		return strtr($s, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
109	}
110
111	public function escapeSlashes($s) // _escapeName
112	{
113		return strtr($s, ['/' => '#2F']);
114	}
115
116	/**
117	 * Un-escapes a PDF string
118	 *
119	 * @param string $s
120	 * @return string
121	 */
122	public function unescape($s)
123	{
124		$out = '';
125		for ($count = 0, $n = strlen($s); $count < $n; $count++) {
126			if ($count === $n - 1 || $s[$count] !== '\\') {
127				$out .= $s[$count];
128			} else {
129				switch ($s[++$count]) {
130					case ')':
131					case '(':
132					case '\\':
133						$out .= $s[$count];
134						break;
135					case 'f':
136						$out .= chr(0x0C);
137						break;
138					case 'b':
139						$out .= chr(0x08);
140						break;
141					case 't':
142						$out .= chr(0x09);
143						break;
144					case 'r':
145						$out .= chr(0x0D);
146						break;
147					case 'n':
148						$out .= chr(0x0A);
149						break;
150					case "\r":
151						if ($count !== $n - 1 && $s[$count + 1] === "\n") {
152							$count++;
153						}
154						break;
155					case "\n":
156						break;
157					default:
158						// Octal-Values
159						$ord = ord($s[$count]);
160						if ($ord >= ord('0') && $ord <= ord('9')) {
161							$oct = ''. $s[$count];
162							$ord = ord($s[$count + 1]);
163							if ($ord >= ord('0') && $ord <= ord('9')) {
164								$oct .= $s[++$count];
165								$ord = ord($s[$count + 1]);
166								if ($ord >= ord('0') && $ord <= ord('9')) {
167									$oct .= $s[++$count];
168								}
169							}
170							$out .= chr(octdec($oct));
171						} else {
172							$out .= $s[$count];
173						}
174				}
175			}
176		}
177
178		return $out;
179	}
180
181	private function endPage($s, $ln)
182	{
183		if ($this->mpdf->bufferoutput) {
184
185			$this->mpdf->headerbuffer.= $s . "\n";
186
187		} elseif ($this->mpdf->ColActive && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) {
188
189			// Captures everything in buffer for columns; Almost everything is sent from fn. Cell() except:
190			// Images sent from Image() or
191			// later sent as write($textto) in printbuffer
192			// Line()
193
194			if (preg_match('/q \d+\.\d\d+ 0 0 (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ cm \/(I|FO)\d+ Do Q/', $s, $m)) { // Image data
195
196				$h = ($m[1] / Mpdf::SCALE);
197				// Update/overwrite the lowest bottom of printing y value for a column
198				$this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'] = $this->mpdf->y + $h;
199
200			} elseif ($this->mpdf->tableLevel > 0 && preg_match('/\d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ ([\-]{0,1}\d+\.\d\d+) re/', $s, $m)) { // Rect in table
201
202				$h = ($m[1] / Mpdf::SCALE);
203				// Update/overwrite the lowest bottom of printing y value for a column
204				$this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'] = max($this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'], $this->mpdf->y + $h);
205
206			} elseif (isset($this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'])) {
207
208				$h = $this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'] - $this->mpdf->y;
209
210			} else {
211
212				$h = 0;
213
214			}
215
216			if ($h < 0) {
217				$h = -$h;
218			}
219
220			$this->mpdf->columnbuffer[] = [
221				's' => $s, // Text string to output
222				'col' => $this->mpdf->CurrCol, // Column when printed
223				'x' => $this->mpdf->x, // x when printed
224				'y' => $this->mpdf->y, // this->y when printed (after column break)
225				'h' => $h        // actual y at bottom when printed = y+h
226			];
227
228		} elseif ($this->mpdf->table_rotate && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) {
229
230			// Captures eveything in buffer for rotated tables;
231			$this->mpdf->tablebuffer .= $s . "\n";
232
233		} elseif ($this->mpdf->kwt && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) {
234
235			// Captures eveything in buffer for keep-with-table (h1-6);
236			$this->mpdf->kwt_buffer[] = [
237				's' => $s, // Text string to output
238				'x' => $this->mpdf->x, // x when printed
239				'y' => $this->mpdf->y, // y when printed
240			];
241
242		} elseif ($this->mpdf->keep_block_together && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) {
243			// do nothing
244		} else {
245			$this->mpdf->pages[$this->mpdf->page] .= $s . ($ln ? "\n" : '');
246		}
247	}
248
249}
250