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