Completed
Branch dependabot/composer/tijsverkoy... (491ea6)
by
unknown
32:00 queued 25:42
created
vendor/phenx/php-svg-lib/src/Svg/Tag/StyleTag.php 1 patch
Indentation   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -12,16 +12,16 @@
 block discarded – undo
12 12
 
13 13
 class StyleTag extends AbstractTag
14 14
 {
15
-    protected $text = "";
15
+	protected $text = "";
16 16
 
17
-    public function end()
18
-    {
19
-        $parser = new CSS\Parser($this->text);
20
-        $this->document->appendStyleSheet($parser->parse());
21
-    }
17
+	public function end()
18
+	{
19
+		$parser = new CSS\Parser($this->text);
20
+		$this->document->appendStyleSheet($parser->parse());
21
+	}
22 22
 
23
-    public function appendText($text)
24
-    {
25
-        $this->text .= $text;
26
-    }
23
+	public function appendText($text)
24
+	{
25
+		$this->text .= $text;
26
+	}
27 27
 } 
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Tag/Group.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -12,22 +12,22 @@
 block discarded – undo
12 12
 
13 13
 class Group extends AbstractTag
14 14
 {
15
-    protected function before($attributes)
16
-    {
17
-        $surface = $this->document->getSurface();
15
+	protected function before($attributes)
16
+	{
17
+		$surface = $this->document->getSurface();
18 18
 
19
-        $surface->save();
19
+		$surface->save();
20 20
 
21
-        $style = $this->makeStyle($attributes);
21
+		$style = $this->makeStyle($attributes);
22 22
 
23
-        $this->setStyle($style);
24
-        $surface->setStyle($style);
23
+		$this->setStyle($style);
24
+		$surface->setStyle($style);
25 25
 
26
-        $this->applyTransform($attributes);
27
-    }
26
+		$this->applyTransform($attributes);
27
+	}
28 28
 
29
-    protected function after()
30
-    {
31
-        $this->document->getSurface()->restore();
32
-    }
29
+	protected function after()
30
+	{
31
+		$this->document->getSurface()->restore();
32
+	}
33 33
 } 
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Tag/Stop.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -10,8 +10,8 @@
 block discarded – undo
10 10
 
11 11
 class Stop extends AbstractTag
12 12
 {
13
-    public function start($attributes)
14
-    {
13
+	public function start($attributes)
14
+	{
15 15
 
16
-    }
16
+	}
17 17
 } 
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Tag/LinearGradient.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -14,70 +14,70 @@
 block discarded – undo
14 14
 
15 15
 class LinearGradient extends AbstractTag
16 16
 {
17
-    protected $x1;
18
-    protected $y1;
19
-    protected $x2;
20
-    protected $y2;
17
+	protected $x1;
18
+	protected $y1;
19
+	protected $x2;
20
+	protected $y2;
21 21
 
22
-    /** @var Gradient\Stop[] */
23
-    protected $stops = array();
22
+	/** @var Gradient\Stop[] */
23
+	protected $stops = array();
24 24
 
25
-    public function start($attributes)
26
-    {
27
-        parent::start($attributes);
25
+	public function start($attributes)
26
+	{
27
+		parent::start($attributes);
28 28
 
29
-        if (isset($attributes['x1'])) {
30
-            $this->x1 = $attributes['x1'];
31
-        }
32
-        if (isset($attributes['y1'])) {
33
-            $this->y1 = $attributes['y1'];
34
-        }
35
-        if (isset($attributes['x2'])) {
36
-            $this->x2 = $attributes['x2'];
37
-        }
38
-        if (isset($attributes['y2'])) {
39
-            $this->y2 = $attributes['y2'];
40
-        }
41
-    }
29
+		if (isset($attributes['x1'])) {
30
+			$this->x1 = $attributes['x1'];
31
+		}
32
+		if (isset($attributes['y1'])) {
33
+			$this->y1 = $attributes['y1'];
34
+		}
35
+		if (isset($attributes['x2'])) {
36
+			$this->x2 = $attributes['x2'];
37
+		}
38
+		if (isset($attributes['y2'])) {
39
+			$this->y2 = $attributes['y2'];
40
+		}
41
+	}
42 42
 
43
-    public function getStops() {
44
-        if (empty($this->stops)) {
45
-            foreach ($this->children as $_child) {
46
-                if ($_child->tagName != "stop") {
47
-                    continue;
48
-                }
43
+	public function getStops() {
44
+		if (empty($this->stops)) {
45
+			foreach ($this->children as $_child) {
46
+				if ($_child->tagName != "stop") {
47
+					continue;
48
+				}
49 49
 
50
-                $_stop = new Gradient\Stop();
51
-                $_attributes = $_child->attributes;
50
+				$_stop = new Gradient\Stop();
51
+				$_attributes = $_child->attributes;
52 52
 
53
-                // Style
54
-                if (isset($_attributes["style"])) {
55
-                    $_style = Style::parseCssStyle($_attributes["style"]);
53
+				// Style
54
+				if (isset($_attributes["style"])) {
55
+					$_style = Style::parseCssStyle($_attributes["style"]);
56 56
 
57
-                    if (isset($_style["stop-color"])) {
58
-                        $_stop->color = Style::parseColor($_style["stop-color"]);
59
-                    }
57
+					if (isset($_style["stop-color"])) {
58
+						$_stop->color = Style::parseColor($_style["stop-color"]);
59
+					}
60 60
 
61
-                    if (isset($_style["stop-opacity"])) {
62
-                        $_stop->opacity = max(0, min(1.0, $_style["stop-opacity"]));
63
-                    }
64
-                }
61
+					if (isset($_style["stop-opacity"])) {
62
+						$_stop->opacity = max(0, min(1.0, $_style["stop-opacity"]));
63
+					}
64
+				}
65 65
 
66
-                // Attributes
67
-                if (isset($_attributes["offset"])) {
68
-                    $_stop->offset = $_attributes["offset"];
69
-                }
70
-                if (isset($_attributes["stop-color"])) {
71
-                    $_stop->color = Style::parseColor($_attributes["stop-color"]);
72
-                }
73
-                if (isset($_attributes["stop-opacity"])) {
74
-                    $_stop->opacity = max(0, min(1.0, $_attributes["stop-opacity"]));
75
-                }
66
+				// Attributes
67
+				if (isset($_attributes["offset"])) {
68
+					$_stop->offset = $_attributes["offset"];
69
+				}
70
+				if (isset($_attributes["stop-color"])) {
71
+					$_stop->color = Style::parseColor($_attributes["stop-color"]);
72
+				}
73
+				if (isset($_attributes["stop-opacity"])) {
74
+					$_stop->opacity = max(0, min(1.0, $_attributes["stop-opacity"]));
75
+				}
76 76
 
77
-                $this->stops[] = $_stop;
78
-            }
79
-        }
77
+				$this->stops[] = $_stop;
78
+			}
79
+		}
80 80
 
81
-        return $this->stops;
82
-    }
81
+		return $this->stops;
82
+	}
83 83
 } 
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Tag/RadialGradient.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -10,8 +10,8 @@
 block discarded – undo
10 10
 
11 11
 class RadialGradient extends AbstractTag
12 12
 {
13
-    public function start($attributes)
14
-    {
13
+	public function start($attributes)
14
+	{
15 15
 
16
-    }
16
+	}
17 17
 } 
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Tag/ClipPath.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -12,22 +12,22 @@
 block discarded – undo
12 12
 
13 13
 class ClipPath extends AbstractTag
14 14
 {
15
-    protected function before($attributes)
16
-    {
17
-        $surface = $this->document->getSurface();
15
+	protected function before($attributes)
16
+	{
17
+		$surface = $this->document->getSurface();
18 18
 
19
-        $surface->save();
19
+		$surface->save();
20 20
 
21
-        $style = $this->makeStyle($attributes);
21
+		$style = $this->makeStyle($attributes);
22 22
 
23
-        $this->setStyle($style);
24
-        $surface->setStyle($style);
23
+		$this->setStyle($style);
24
+		$surface->setStyle($style);
25 25
 
26
-        $this->applyTransform($attributes);
27
-    }
26
+		$this->applyTransform($attributes);
27
+	}
28 28
 
29
-    protected function after()
30
-    {
31
-        $this->document->getSurface()->restore();
32
-    }
29
+	protected function after()
30
+	{
31
+		$this->document->getSurface()->restore();
32
+	}
33 33
 } 
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Tag/Shape.php 3 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -33,7 +33,7 @@
 block discarded – undo
33 33
         if ($this->hasShape) {
34 34
             $style = $surface->getStyle();
35 35
 
36
-            $fill   = $style->fill   && is_array($style->fill);
36
+            $fill   = $style->fill && is_array($style->fill);
37 37
             $stroke = $style->stroke && is_array($style->stroke);
38 38
 
39 39
             if ($fill) {
Please login to merge, or discard this patch.
Indentation   +33 added lines, -33 removed lines patch added patch discarded remove patch
@@ -12,34 +12,34 @@  discard block
 block discarded – undo
12 12
 
13 13
 class Shape extends AbstractTag
14 14
 {
15
-    protected function before($attributes)
16
-    {
17
-        $surface = $this->document->getSurface();
15
+	protected function before($attributes)
16
+	{
17
+		$surface = $this->document->getSurface();
18 18
 
19
-        $surface->save();
19
+		$surface->save();
20 20
 
21
-        $style = $this->makeStyle($attributes);
21
+		$style = $this->makeStyle($attributes);
22 22
 
23
-        $this->setStyle($style);
24
-        $surface->setStyle($style);
23
+		$this->setStyle($style);
24
+		$surface->setStyle($style);
25 25
 
26
-        $this->applyTransform($attributes);
27
-    }
26
+		$this->applyTransform($attributes);
27
+	}
28 28
 
29
-    protected function after()
30
-    {
31
-        $surface = $this->document->getSurface();
29
+	protected function after()
30
+	{
31
+		$surface = $this->document->getSurface();
32 32
 
33
-        if ($this->hasShape) {
34
-            $style = $surface->getStyle();
33
+		if ($this->hasShape) {
34
+			$style = $surface->getStyle();
35 35
 
36
-            $fill   = $style->fill   && is_array($style->fill);
37
-            $stroke = $style->stroke && is_array($style->stroke);
36
+			$fill   = $style->fill   && is_array($style->fill);
37
+			$stroke = $style->stroke && is_array($style->stroke);
38 38
 
39
-            if ($fill) {
40
-                if ($stroke) {
41
-                    $surface->fillStroke(false);
42
-                } else {
39
+			if ($fill) {
40
+				if ($stroke) {
41
+					$surface->fillStroke(false);
42
+				} else {
43 43
 //                    if (is_string($style->fill)) {
44 44
 //                        /** @var LinearGradient|RadialGradient $gradient */
45 45
 //                        $gradient = $this->getDocument()->getDef($style->fill);
@@ -47,17 +47,17 @@  discard block
 block discarded – undo
47 47
 //                        var_dump($gradient->getStops());
48 48
 //                    }
49 49
 
50
-                    $surface->fill();
51
-                }
52
-            }
53
-            elseif ($stroke) {
54
-                $surface->stroke(false);
55
-            }
56
-            else {
57
-                $surface->endPath();
58
-            }
59
-        }
60
-
61
-        $surface->restore();
62
-    }
50
+					$surface->fill();
51
+				}
52
+			}
53
+			elseif ($stroke) {
54
+				$surface->stroke(false);
55
+			}
56
+			else {
57
+				$surface->endPath();
58
+			}
59
+		}
60
+
61
+		$surface->restore();
62
+	}
63 63
 } 
64 64
\ No newline at end of file
Please login to merge, or discard this patch.
Braces   +2 added lines, -4 removed lines patch added patch discarded remove patch
@@ -49,11 +49,9 @@
 block discarded – undo
49 49
 
50 50
                     $surface->fill();
51 51
                 }
52
-            }
53
-            elseif ($stroke) {
52
+            } elseif ($stroke) {
54 53
                 $surface->stroke(false);
55
-            }
56
-            else {
54
+            } else {
57 55
                 $surface->endPath();
58 56
             }
59 57
         }
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php 3 patches
Braces   +6 added lines, -2 removed lines patch added patch discarded remove patch
@@ -4641,8 +4641,12 @@
 block discarded – undo
4641 4641
             die("Unable to stream pdf: headers already sent");
4642 4642
         }
4643 4643
 
4644
-        if (!isset($options["compress"])) $options["compress"] = true;
4645
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4644
+        if (!isset($options["compress"])) {
4645
+        	$options["compress"] = true;
4646
+        }
4647
+        if (!isset($options["Attachment"])) {
4648
+        	$options["Attachment"] = true;
4649
+        }
4646 4650
 
4647 4651
         $debug = !$options['compress'];
4648 4652
         $tmp = ltrim($this->output($debug));
Please login to merge, or discard this patch.
Indentation   +6362 added lines, -6362 removed lines patch added patch discarded remove patch
@@ -20,1276 +20,1276 @@  discard block
 block discarded – undo
20 20
 
21 21
 class CPdf
22 22
 {
23
-    const PDF_VERSION = '1.7';
24
-
25
-    const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
26
-    const ACROFORM_SIG_APPENDONLY =       0x0002;
27
-
28
-    const ACROFORM_FIELD_BUTTON =   'Btn';
29
-    const ACROFORM_FIELD_TEXT =     'Tx';
30
-    const ACROFORM_FIELD_CHOICE =   'Ch';
31
-    const ACROFORM_FIELD_SIG =      'Sig';
32
-
33
-    const ACROFORM_FIELD_READONLY =               0x0001;
34
-    const ACROFORM_FIELD_REQUIRED =               0x0002;
35
-
36
-    const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
37
-    const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
38
-    const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
39
-
40
-    const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
41
-    const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
42
-    const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
43
-    const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
44
-
45
-    const XOBJECT_SUBTYPE_FORM = 'Form';
46
-
47
-    /**
48
-     * @var integer The current number of pdf objects in the document
49
-     */
50
-    public $numObj = 0;
51
-
52
-    /**
53
-     * @var array This array contains all of the pdf objects, ready for final assembly
54
-     */
55
-    public $objects = [];
56
-
57
-    /**
58
-     * @var integer The objectId (number within the objects array) of the document catalog
59
-     */
60
-    public $catalogId;
61
-
62
-    /**
63
-     * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
64
-     */
65
-    protected $indirectReferenceId = 0;
66
-
67
-    /**
68
-     * @var integer The objectId (number within the objects array)
69
-     */
70
-    protected $embeddedFilesId = 0;
71
-
72
-    /**
73
-     * AcroForm objectId
74
-     *
75
-     * @var integer
76
-     */
77
-    public $acroFormId;
78
-
79
-    /**
80
-     * @var int
81
-     */
82
-    public $signatureMaxLen = 5000;
83
-
84
-    /**
85
-     * @var array Array carrying information about the fonts that the system currently knows about
86
-     * Used to ensure that a font is not loaded twice, among other things
87
-     */
88
-    public $fonts = [];
89
-
90
-    /**
91
-     * @var string The default font metrics file to use if no other font has been loaded.
92
-     * The path to the directory containing the font metrics should be included
93
-     */
94
-    public $defaultFont = './fonts/Helvetica.afm';
95
-
96
-    /**
97
-     * @string A record of the current font
98
-     */
99
-    public $currentFont = '';
100
-
101
-    /**
102
-     * @var string The current base font
103
-     */
104
-    public $currentBaseFont = '';
105
-
106
-    /**
107
-     * @var integer The number of the current font within the font array
108
-     */
109
-    public $currentFontNum = 0;
110
-
111
-    /**
112
-     * @var integer
113
-     */
114
-    public $currentNode;
115
-
116
-    /**
117
-     * @var integer Object number of the current page
118
-     */
119
-    public $currentPage;
120
-
121
-    /**
122
-     * @var integer Object number of the currently active contents block
123
-     */
124
-    public $currentContents;
125
-
126
-    /**
127
-     * @var integer Number of fonts within the system
128
-     */
129
-    public $numFonts = 0;
130
-
131
-    /**
132
-     * @var integer Number of graphic state resources used
133
-     */
134
-    private $numStates = 0;
135
-
136
-    /**
137
-     * @var array Number of graphic state resources used
138
-     */
139
-    private $gstates = [];
140
-
141
-    /**
142
-     * @var array Current color for fill operations, defaults to inactive value,
143
-     * all three components should be between 0 and 1 inclusive when active
144
-     */
145
-    public $currentColor = null;
146
-
147
-    /**
148
-     * @var array Current color for stroke operations (lines etc.)
149
-     */
150
-    public $currentStrokeColor = null;
151
-
152
-    /**
153
-     * @var string Fill rule (nonzero or evenodd)
154
-     */
155
-    public $fillRule = "nonzero";
156
-
157
-    /**
158
-     * @var string Current style that lines are drawn in
159
-     */
160
-    public $currentLineStyle = '';
161
-
162
-    /**
163
-     * @var array Current line transparency (partial graphics state)
164
-     */
165
-    public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
166
-
167
-    /**
168
-     * array Current fill transparency (partial graphics state)
169
-     */
170
-    public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
171
-
172
-    /**
173
-     * @var array An array which is used to save the state of the document, mainly the colors and styles
174
-     * it is used to temporarily change to another state, then change back to what it was before
175
-     */
176
-    public $stateStack = [];
177
-
178
-    /**
179
-     * @var integer Number of elements within the state stack
180
-     */
181
-    public $nStateStack = 0;
182
-
183
-    /**
184
-     * @var integer Number of page objects within the document
185
-     */
186
-    public $numPages = 0;
187
-
188
-    /**
189
-     * @var array Object Id storage stack
190
-     */
191
-    public $stack = [];
192
-
193
-    /**
194
-     * @var integer Number of elements within the object Id storage stack
195
-     */
196
-    public $nStack = 0;
197
-
198
-    /**
199
-     * an array which contains information about the objects which are not firmly attached to pages
200
-     * these have been added with the addObject function
201
-     */
202
-    public $looseObjects = [];
203
-
204
-    /**
205
-     * array contains information about how the loose objects are to be added to the document
206
-     */
207
-    public $addLooseObjects = [];
208
-
209
-    /**
210
-     * @var integer The objectId of the information object for the document
211
-     * this contains authorship, title etc.
212
-     */
213
-    public $infoObject = 0;
214
-
215
-    /**
216
-     * @var integer Number of images being tracked within the document
217
-     */
218
-    public $numImages = 0;
219
-
220
-    /**
221
-     * @var array An array containing options about the document
222
-     * it defaults to turning on the compression of the objects
223
-     */
224
-    public $options = ['compression' => true];
225
-
226
-    /**
227
-     * @var integer The objectId of the first page of the document
228
-     */
229
-    public $firstPageId;
230
-
231
-    /**
232
-     * @var integer The object Id of the procset object
233
-     */
234
-    public $procsetObjectId;
235
-
236
-    /**
237
-     * @var array Store the information about the relationship between font families
238
-     * this used so that the code knows which font is the bold version of another font, etc.
239
-     * the value of this array is initialised in the constructor function.
240
-     */
241
-    public $fontFamilies = [];
242
-
243
-    /**
244
-     * @var string Folder for php serialized formats of font metrics files.
245
-     * If empty string, use same folder as original metrics files.
246
-     * This can be passed in from class creator.
247
-     * If this folder does not exist or is not writable, Cpdf will be **much** slower.
248
-     * Because of potential trouble with php safe mode, folder cannot be created at runtime.
249
-     */
250
-    public $fontcache = '';
251
-
252
-    /**
253
-     * @var integer The version of the font metrics cache file.
254
-     * This value must be manually incremented whenever the internal font data structure is modified.
255
-     */
256
-    public $fontcacheVersion = 6;
257
-
258
-    /**
259
-     * @var string Temporary folder.
260
-     * If empty string, will attempt system tmp folder.
261
-     * This can be passed in from class creator.
262
-     */
263
-    public $tmp = '';
264
-
265
-    /**
266
-     * @var string Track if the current font is bolded or italicised
267
-     */
268
-    public $currentTextState = '';
269
-
270
-    /**
271
-     * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
272
-     */
273
-    public $messages = '';
274
-
275
-    /**
276
-     * @var string The encryption array for the document encryption is stored here
277
-     */
278
-    public $arc4 = '';
279
-
280
-    /**
281
-     * @var integer The object Id of the encryption information
282
-     */
283
-    public $arc4_objnum = 0;
284
-
285
-    /**
286
-     * @var string The file identifier, used to uniquely identify a pdf document
287
-     */
288
-    public $fileIdentifier = '';
289
-
290
-    /**
291
-     * @var boolean A flag to say if a document is to be encrypted or not
292
-     */
293
-    public $encrypted = false;
294
-
295
-    /**
296
-     * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
297
-     */
298
-    public $encryptionKey = '';
299
-
300
-    /**
301
-     * @var array Array which forms a stack to keep track of nested callback functions
302
-     */
303
-    public $callback = [];
304
-
305
-    /**
306
-     * @var integer The number of callback functions in the callback array
307
-     */
308
-    public $nCallback = 0;
309
-
310
-    /**
311
-     * @var array Store label->id pairs for named destinations, these will be used to replace internal links
312
-     * done this way so that destinations can be defined after the location that links to them
313
-     */
314
-    public $destinations = [];
315
-
316
-    /**
317
-     * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
318
-     * publiciables within the class, so that the user can rollback at will (from each 'start' command)
319
-     * note that this includes the objects array, so these can be large.
320
-     */
321
-    public $checkpoint = '';
322
-
323
-    /**
324
-     * @var array Table of Image origin filenames and image labels which were already added with o_image().
325
-     * Allows to merge identical images
326
-     */
327
-    public $imagelist = [];
328
-
329
-    /**
330
-     * @var array Table of already added alpha and plain image files for transparent PNG images.
331
-     */
332
-    protected $imageAlphaList = [];
333
-
334
-    /**
335
-     * @var array List of temporary image files to be deleted after processing.
336
-     */
337
-    protected $imageCache = [];
338
-
339
-    /**
340
-     * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
341
-     */
342
-    public $isUnicode = false;
343
-
344
-    /**
345
-     * @var string the JavaScript code of the document
346
-     */
347
-    public $javascript = '';
348
-
349
-    /**
350
-     * @var boolean whether the compression is possible
351
-     */
352
-    protected $compressionReady = false;
353
-
354
-    /**
355
-     * @var array Current page size
356
-     */
357
-    protected $currentPageSize = ["width" => 0, "height" => 0];
358
-
359
-    /**
360
-     * @var array All the chars that will be required in the font subsets
361
-     */
362
-    protected $stringSubsets = [];
363
-
364
-    /**
365
-     * @var string The target internal encoding
366
-     */
367
-    protected static $targetEncoding = 'Windows-1252';
368
-
369
-    /**
370
-     * @var array
371
-     */
372
-    protected $byteRange = array();
373
-
374
-    /**
375
-     * @var array The list of the core fonts
376
-     */
377
-    protected static $coreFonts = [
378
-        'courier',
379
-        'courier-bold',
380
-        'courier-oblique',
381
-        'courier-boldoblique',
382
-        'helvetica',
383
-        'helvetica-bold',
384
-        'helvetica-oblique',
385
-        'helvetica-boldoblique',
386
-        'times-roman',
387
-        'times-bold',
388
-        'times-italic',
389
-        'times-bolditalic',
390
-        'symbol',
391
-        'zapfdingbats'
392
-    ];
393
-
394
-    /**
395
-     * Class constructor
396
-     * This will start a new document
397
-     *
398
-     * @param array   $pageSize  Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
399
-     * @param boolean $isUnicode Whether text will be treated as Unicode or not.
400
-     * @param string  $fontcache The font cache folder
401
-     * @param string  $tmp       The temporary folder
402
-     */
403
-    function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
404
-    {
405
-        $this->isUnicode = $isUnicode;
406
-        $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
407
-        $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
408
-        $this->newDocument($pageSize);
409
-
410
-        $this->compressionReady = function_exists('gzcompress');
411
-
412
-        if (in_array('Windows-1252', mb_list_encodings())) {
413
-            self::$targetEncoding = 'Windows-1252';
414
-        }
415
-
416
-        // also initialize the font families that are known about already
417
-        $this->setFontFamily('init');
418
-    }
419
-
420
-    public function __destruct()
421
-    {
422
-        foreach ($this->imageCache as $file) {
423
-            if (file_exists($file)) {
424
-                unlink($file);
425
-            }
426
-        }
427
-    }
428
-
429
-    /**
430
-     * Document object methods (internal use only)
431
-     *
432
-     * There is about one object method for each type of object in the pdf document
433
-     * Each function has the same call list ($id,$action,$options).
434
-     * $id = the object ID of the object, or what it is to be if it is being created
435
-     * $action = a string specifying the action to be performed, though ALL must support:
436
-     *           'new' - create the object with the id $id
437
-     *           'out' - produce the output for the pdf object
438
-     * $options = optional, a string or array containing the various parameters for the object
439
-     *
440
-     * These, in conjunction with the output function are the ONLY way for output to be produced
441
-     * within the pdf 'file'.
442
-     */
443
-
444
-    /**
445
-     * Destination object, used to specify the location for the user to jump to, presently on opening
446
-     *
447
-     * @param $id
448
-     * @param $action
449
-     * @param string $options
450
-     * @return string|null
451
-     */
452
-    protected function o_destination($id, $action, $options = '')
453
-    {
454
-        switch ($action) {
455
-            case 'new':
456
-                $this->objects[$id] = ['t' => 'destination', 'info' => []];
457
-                $tmp = '';
458
-                switch ($options['type']) {
459
-                    case 'XYZ':
460
-                    /** @noinspection PhpMissingBreakStatementInspection */
461
-                    case 'FitR':
462
-                        $tmp = ' ' . $options['p3'] . $tmp;
463
-                    case 'FitH':
464
-                    case 'FitV':
465
-                    case 'FitBH':
466
-                    /** @noinspection PhpMissingBreakStatementInspection */
467
-                    case 'FitBV':
468
-                        $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
469
-                    case 'Fit':
470
-                    case 'FitB':
471
-                        $tmp = $options['type'] . $tmp;
472
-                        $this->objects[$id]['info']['string'] = $tmp;
473
-                        $this->objects[$id]['info']['page'] = $options['page'];
474
-                }
475
-                break;
476
-
477
-            case 'out':
478
-                $o = &$this->objects[$id];
479
-
480
-                $tmp = $o['info'];
481
-                $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
482
-
483
-                return $res;
484
-        }
485
-
486
-        return null;
487
-    }
488
-
489
-    /**
490
-     * set the viewer preferences
491
-     *
492
-     * @param $id
493
-     * @param $action
494
-     * @param string|array $options
495
-     * @return string|null
496
-     */
497
-    protected function o_viewerPreferences($id, $action, $options = '')
498
-    {
499
-        switch ($action) {
500
-            case 'new':
501
-                $this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
502
-                break;
503
-
504
-            case 'add':
505
-                $o = &$this->objects[$id];
506
-
507
-                foreach ($options as $k => $v) {
508
-                    switch ($k) {
509
-                        // Boolean keys
510
-                        case 'HideToolbar':
511
-                        case 'HideMenubar':
512
-                        case 'HideWindowUI':
513
-                        case 'FitWindow':
514
-                        case 'CenterWindow':
515
-                        case 'DisplayDocTitle':
516
-                        case 'PickTrayByPDFSize':
517
-                            $o['info'][$k] = (bool)$v;
518
-                            break;
519
-
520
-                        // Integer keys
521
-                        case 'NumCopies':
522
-                            $o['info'][$k] = (int)$v;
523
-                            break;
524
-
525
-                        // Name keys
526
-                        case 'ViewArea':
527
-                        case 'ViewClip':
528
-                        case 'PrintClip':
529
-                        case 'PrintArea':
530
-                            $o['info'][$k] = (string)$v;
531
-                            break;
532
-
533
-                        // Named with limited valid values
534
-                        case 'NonFullScreenPageMode':
535
-                            if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
536
-                                break;
537
-                            }
538
-                            $o['info'][$k] = $v;
539
-                            break;
540
-
541
-                        case 'Direction':
542
-                            if (!in_array($v, ['L2R', 'R2L'])) {
543
-                                break;
544
-                            }
545
-                            $o['info'][$k] = $v;
546
-                            break;
547
-
548
-                        case 'PrintScaling':
549
-                            if (!in_array($v, ['None', 'AppDefault'])) {
550
-                                break;
551
-                            }
552
-                            $o['info'][$k] = $v;
553
-                            break;
554
-
555
-                        case 'Duplex':
556
-                            if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
557
-                                break;
558
-                            }
559
-                            $o['info'][$k] = $v;
560
-                            break;
561
-
562
-                        // Integer array
563
-                        case 'PrintPageRange':
564
-                            // Cast to integer array
565
-                            foreach ($v as $vK => $vV) {
566
-                                $v[$vK] = (int)$vV;
567
-                            }
568
-                            $o['info'][$k] = array_values($v);
569
-                            break;
570
-                    }
571
-                }
572
-                break;
573
-
574
-            case 'out':
575
-                $o = &$this->objects[$id];
576
-                $res = "\n$id 0 obj\n<< ";
577
-
578
-                foreach ($o['info'] as $k => $v) {
579
-                    if (is_string($v)) {
580
-                        $v = '/' . $v;
581
-                    } elseif (is_int($v)) {
582
-                        $v = (string) $v;
583
-                    } elseif (is_bool($v)) {
584
-                        $v = ($v ? 'true' : 'false');
585
-                    } elseif (is_array($v)) {
586
-                        $v = '[' . implode(' ', $v) . ']';
587
-                    }
588
-                    $res .= "\n/$k $v";
589
-                }
590
-                $res .= "\n>>\nendobj";
591
-
592
-                return $res;
593
-        }
594
-
595
-        return null;
596
-    }
597
-
598
-    /**
599
-     * define the document catalog, the overall controller for the document
600
-     *
601
-     * @param $id
602
-     * @param $action
603
-     * @param string|array $options
604
-     * @return string|null
605
-     */
606
-    protected function o_catalog($id, $action, $options = '')
607
-    {
608
-        if ($action !== 'new') {
609
-            $o = &$this->objects[$id];
610
-        }
611
-
612
-        switch ($action) {
613
-            case 'new':
614
-                $this->objects[$id] = ['t' => 'catalog', 'info' => []];
615
-                $this->catalogId = $id;
616
-                break;
617
-
618
-            case 'acroform':
619
-            case 'outlines':
620
-            case 'pages':
621
-            case 'openHere':
622
-            case 'names':
623
-                $o['info'][$action] = $options;
624
-                break;
625
-
626
-            case 'viewerPreferences':
627
-                if (!isset($o['info']['viewerPreferences'])) {
628
-                    $this->numObj++;
629
-                    $this->o_viewerPreferences($this->numObj, 'new');
630
-                    $o['info']['viewerPreferences'] = $this->numObj;
631
-                }
632
-
633
-                $vp = $o['info']['viewerPreferences'];
634
-                $this->o_viewerPreferences($vp, 'add', $options);
635
-
636
-                break;
637
-
638
-            case 'out':
639
-                $res = "\n$id 0 obj\n<< /Type /Catalog";
640
-
641
-                foreach ($o['info'] as $k => $v) {
642
-                    switch ($k) {
643
-                        case 'outlines':
644
-                            $res .= "\n/Outlines $v 0 R";
645
-                            break;
646
-
647
-                        case 'pages':
648
-                            $res .= "\n/Pages $v 0 R";
649
-                            break;
650
-
651
-                        case 'viewerPreferences':
652
-                            $res .= "\n/ViewerPreferences $v 0 R";
653
-                            break;
654
-
655
-                        case 'openHere':
656
-                            $res .= "\n/OpenAction $v 0 R";
657
-                            break;
658
-
659
-                        case 'names':
660
-                            $res .= "\n/Names $v 0 R";
661
-                            break;
662
-
663
-                        case 'acroform':
664
-                            $res .= "\n/AcroForm $v 0 R";
665
-                            break;
666
-                    }
667
-                }
668
-
669
-                $res .= " >>\nendobj";
670
-
671
-                return $res;
672
-        }
673
-
674
-        return null;
675
-    }
676
-
677
-    /**
678
-     * object which is a parent to the pages in the document
679
-     *
680
-     * @param $id
681
-     * @param $action
682
-     * @param string $options
683
-     * @return string|null
684
-     */
685
-    protected function o_pages($id, $action, $options = '')
686
-    {
687
-        if ($action !== 'new') {
688
-            $o = &$this->objects[$id];
689
-        }
690
-
691
-        switch ($action) {
692
-            case 'new':
693
-                $this->objects[$id] = ['t' => 'pages', 'info' => []];
694
-                $this->o_catalog($this->catalogId, 'pages', $id);
695
-                break;
696
-
697
-            case 'page':
698
-                if (!is_array($options)) {
699
-                    // then it will just be the id of the new page
700
-                    $o['info']['pages'][] = $options;
701
-                } else {
702
-                    // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
703
-                    // and pos is either 'before' or 'after', saying where this page will fit.
704
-                    if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
705
-                        $i = array_search($options['rid'], $o['info']['pages']);
706
-                        if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
707
-
708
-                            // then there is a match
709
-                            // make a space
710
-                            switch ($options['pos']) {
711
-                                case 'before':
712
-                                    $k = $i;
713
-                                    break;
714
-
715
-                                case 'after':
716
-                                    $k = $i + 1;
717
-                                    break;
718
-
719
-                                default:
720
-                                    $k = -1;
721
-                                    break;
722
-                            }
723
-
724
-                            if ($k >= 0) {
725
-                                for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
726
-                                    $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
727
-                                }
728
-
729
-                                $o['info']['pages'][$k] = $options['id'];
730
-                            }
731
-                        }
732
-                    }
733
-                }
734
-                break;
735
-
736
-            case 'procset':
737
-                $o['info']['procset'] = $options;
738
-                break;
739
-
740
-            case 'mediaBox':
741
-                $o['info']['mediaBox'] = $options;
742
-                // which should be an array of 4 numbers
743
-                $this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
744
-                break;
745
-
746
-            case 'font':
747
-                $o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
748
-                break;
749
-
750
-            case 'extGState':
751
-                $o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
752
-                break;
753
-
754
-            case 'xObject':
755
-                $o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
756
-                break;
757
-
758
-            case 'out':
759
-                if (count($o['info']['pages'])) {
760
-                    $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
761
-                    foreach ($o['info']['pages'] as $v) {
762
-                        $res .= "$v 0 R\n";
763
-                    }
764
-
765
-                    $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
766
-
767
-                    if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
768
-                        isset($o['info']['procset']) ||
769
-                        (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
770
-                    ) {
771
-                        $res .= "\n/Resources <<";
772
-
773
-                        if (isset($o['info']['procset'])) {
774
-                            $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
775
-                        }
776
-
777
-                        if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
778
-                            $res .= "\n/Font << ";
779
-                            foreach ($o['info']['fonts'] as $finfo) {
780
-                                $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
781
-                            }
782
-                            $res .= "\n>>";
783
-                        }
784
-
785
-                        if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
786
-                            $res .= "\n/XObject << ";
787
-                            foreach ($o['info']['xObjects'] as $finfo) {
788
-                                $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
789
-                            }
790
-                            $res .= "\n>>";
791
-                        }
792
-
793
-                        if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
794
-                            $res .= "\n/ExtGState << ";
795
-                            foreach ($o['info']['extGStates'] as $gstate) {
796
-                                $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
797
-                            }
798
-                            $res .= "\n>>";
799
-                        }
800
-
801
-                        $res .= "\n>>";
802
-                        if (isset($o['info']['mediaBox'])) {
803
-                            $tmp = $o['info']['mediaBox'];
804
-                            $res .= "\n/MediaBox [" . sprintf(
805
-                                    '%.3F %.3F %.3F %.3F',
806
-                                    $tmp[0],
807
-                                    $tmp[1],
808
-                                    $tmp[2],
809
-                                    $tmp[3]
810
-                                ) . ']';
811
-                        }
812
-                    }
813
-
814
-                    $res .= "\n >>\nendobj";
815
-                } else {
816
-                    $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
817
-                }
818
-
819
-                return $res;
820
-        }
821
-
822
-        return null;
823
-    }
824
-
825
-    /**
826
-     * define the outlines in the doc, empty for now
827
-     *
828
-     * @param $id
829
-     * @param $action
830
-     * @param string $options
831
-     * @return string|null
832
-     */
833
-    protected function o_outlines($id, $action, $options = '')
834
-    {
835
-        if ($action !== 'new') {
836
-            $o = &$this->objects[$id];
837
-        }
838
-
839
-        switch ($action) {
840
-            case 'new':
841
-                $this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
842
-                $this->o_catalog($this->catalogId, 'outlines', $id);
843
-                break;
844
-
845
-            case 'outline':
846
-                $o['info']['outlines'][] = $options;
847
-                break;
848
-
849
-            case 'out':
850
-                if (count($o['info']['outlines'])) {
851
-                    $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
852
-                    foreach ($o['info']['outlines'] as $v) {
853
-                        $res .= "$v 0 R ";
854
-                    }
855
-
856
-                    $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
857
-                } else {
858
-                    $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
859
-                }
860
-
861
-                return $res;
862
-        }
863
-
864
-        return null;
865
-    }
866
-
867
-    /**
868
-     * an object to hold the font description
869
-     *
870
-     * @param $id
871
-     * @param $action
872
-     * @param string|array $options
873
-     * @return string|null
874
-     * @throws FontNotFoundException
875
-     */
876
-    protected function o_font($id, $action, $options = '')
877
-    {
878
-        if ($action !== 'new') {
879
-            $o = &$this->objects[$id];
880
-        }
881
-
882
-        switch ($action) {
883
-            case 'new':
884
-                $this->objects[$id] = [
885
-                    't'    => 'font',
886
-                    'info' => [
887
-                        'name'         => $options['name'],
888
-                        'fontFileName' => $options['fontFileName'],
889
-                        'SubType'      => 'Type1',
890
-                        'isSubsetting'   => $options['isSubsetting']
891
-                    ]
892
-                ];
893
-                $fontNum = $this->numFonts;
894
-                $this->objects[$id]['info']['fontNum'] = $fontNum;
895
-
896
-                // deal with the encoding and the differences
897
-                if (isset($options['differences'])) {
898
-                    // then we'll need an encoding dictionary
899
-                    $this->numObj++;
900
-                    $this->o_fontEncoding($this->numObj, 'new', $options);
901
-                    $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
902
-                } else {
903
-                    if (isset($options['encoding'])) {
904
-                        // we can specify encoding here
905
-                        switch ($options['encoding']) {
906
-                            case 'WinAnsiEncoding':
907
-                            case 'MacRomanEncoding':
908
-                            case 'MacExpertEncoding':
909
-                                $this->objects[$id]['info']['encoding'] = $options['encoding'];
910
-                                break;
911
-
912
-                            case 'none':
913
-                                break;
914
-
915
-                            default:
916
-                                $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
917
-                                break;
918
-                        }
919
-                    } else {
920
-                        $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
921
-                    }
922
-                }
923
-
924
-                if ($this->fonts[$options['fontFileName']]['isUnicode']) {
925
-                    // For Unicode fonts, we need to incorporate font data into
926
-                    // sub-sections that are linked from the primary font section.
927
-                    // Look at o_fontGIDtoCID and o_fontDescendentCID functions
928
-                    // for more information.
929
-                    //
930
-                    // All of this code is adapted from the excellent changes made to
931
-                    // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
932
-
933
-                    $toUnicodeId = ++$this->numObj;
934
-                    $this->o_toUnicode($toUnicodeId, 'new');
935
-                    $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
936
-
937
-                    $cidFontId = ++$this->numObj;
938
-                    $this->o_fontDescendentCID($cidFontId, 'new', $options);
939
-                    $this->objects[$id]['info']['cidFont'] = $cidFontId;
940
-                }
941
-
942
-                // also tell the pages node about the new font
943
-                $this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
944
-                break;
945
-
946
-            case 'add':
947
-                $font_options = $this->processFont($id, $o['info']);
948
-
949
-                if ($font_options !== false) {
950
-                    foreach ($font_options as $k => $v) {
951
-                        switch ($k) {
952
-                            case 'BaseFont':
953
-                                $o['info']['name'] = $v;
954
-                                break;
955
-                            case 'FirstChar':
956
-                            case 'LastChar':
957
-                            case 'Widths':
958
-                            case 'FontDescriptor':
959
-                            case 'SubType':
960
-                                $this->addMessage('o_font ' . $k . " : " . $v);
961
-                                $o['info'][$k] = $v;
962
-                                break;
963
-                        }
964
-                    }
965
-
966
-                    // pass values down to descendent font
967
-                    if (isset($o['info']['cidFont'])) {
968
-                        $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
969
-                    }
970
-                }
971
-                break;
972
-
973
-            case 'out':
974
-                if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
975
-                    // For Unicode fonts, we need to incorporate font data into
976
-                    // sub-sections that are linked from the primary font section.
977
-                    // Look at o_fontGIDtoCID and o_fontDescendentCID functions
978
-                    // for more information.
979
-                    //
980
-                    // All of this code is adapted from the excellent changes made to
981
-                    // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
982
-
983
-                    $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
984
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
985
-
986
-                    // The horizontal identity mapping for 2-byte CIDs; may be used
987
-                    // with CIDFonts using any Registry, Ordering, and Supplement values.
988
-                    $res .= "/Encoding /Identity-H\n";
989
-                    $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
990
-                    $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
991
-                    $res .= ">>\n";
992
-                    $res .= "endobj";
993
-                } else {
994
-                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
995
-                    $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
996
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
997
-
998
-                    if (isset($o['info']['encodingDictionary'])) {
999
-                        // then place a reference to the dictionary
1000
-                        $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
1001
-                    } else {
1002
-                        if (isset($o['info']['encoding'])) {
1003
-                            // use the specified encoding
1004
-                            $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1005
-                        }
1006
-                    }
1007
-
1008
-                    if (isset($o['info']['FirstChar'])) {
1009
-                        $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1010
-                    }
1011
-
1012
-                    if (isset($o['info']['LastChar'])) {
1013
-                        $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1014
-                    }
1015
-
1016
-                    if (isset($o['info']['Widths'])) {
1017
-                        $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1018
-                    }
1019
-
1020
-                    if (isset($o['info']['FontDescriptor'])) {
1021
-                        $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1022
-                    }
1023
-
1024
-                    $res .= ">>\n";
1025
-                    $res .= "endobj";
1026
-                }
1027
-
1028
-                return $res;
1029
-        }
1030
-
1031
-        return null;
1032
-    }
1033
-
1034
-    protected function getFontSubsettingTag(array $font): string
1035
-    {
1036
-        // convert font num to hexavigesimal numeral system letters A - Z only
1037
-        $base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
1038
-        for ($i = 0; $i < strlen($base_26); $i++) {
1039
-            $char = $base_26[$i];
1040
-            if ($char <= "9") {
1041
-                $base_26[$i] = chr(65 + intval($char));
1042
-            } else {
1043
-                $base_26[$i] = chr(ord($char) + 10);
1044
-            }
1045
-        }
1046
-
1047
-        return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1048
-    }
1049
-
1050
-    /**
1051
-     * @param int $fontObjId
1052
-     * @param array $object_info
1053
-     * @return array|false
1054
-     * @throws FontNotFoundException
1055
-     */
1056
-    private function processFont(int $fontObjId, array $object_info)
1057
-    {
1058
-        $fontFileName = $object_info['fontFileName'];
1059
-        if (!isset($this->fonts[$fontFileName])) {
1060
-            return false;
1061
-        }
1062
-
1063
-        $font = &$this->fonts[$fontFileName];
1064
-
1065
-        $fileSuffix = $font['fileSuffix'];
1066
-        $fileSuffixLower = strtolower($font['fileSuffix']);
1067
-        $fbfile = "$fontFileName.$fileSuffix";
1068
-        $isTtfFont = $fileSuffixLower === 'ttf';
1069
-        $isPfbFont = $fileSuffixLower === 'pfb';
1070
-
1071
-        $this->addMessage('selectFont: checking for - ' . $fbfile);
1072
-
1073
-        if (!$fileSuffix) {
1074
-            $this->addMessage(
1075
-                'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1076
-            );
1077
-
1078
-            return false;
1079
-        } else {
1080
-            $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
1081
-            //        $fontObj = $this->numObj;
1082
-            $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
1083
-
1084
-            // find the array of font widths, and put that into an object.
1085
-            $firstChar = -1;
1086
-            $lastChar = 0;
1087
-            $widths = [];
1088
-            $cid_widths = [];
1089
-
1090
-            foreach ($font['C'] as $num => $d) {
1091
-                if (intval($num) > 0 || $num == '0') {
1092
-                    if (!$font['isUnicode']) {
1093
-                        // With Unicode, widths array isn't used
1094
-                        if ($lastChar > 0 && $num > $lastChar + 1) {
1095
-                            for ($i = $lastChar + 1; $i < $num; $i++) {
1096
-                                $widths[] = 0;
1097
-                            }
1098
-                        }
1099
-                    }
1100
-
1101
-                    $widths[] = $d;
1102
-
1103
-                    if ($font['isUnicode']) {
1104
-                        $cid_widths[$num] = $d;
1105
-                    }
1106
-
1107
-                    if ($firstChar == -1) {
1108
-                        $firstChar = $num;
1109
-                    }
1110
-
1111
-                    $lastChar = $num;
1112
-                }
1113
-            }
1114
-
1115
-            // also need to adjust the widths for the differences array
1116
-            if (isset($object['differences'])) {
1117
-                foreach ($object['differences'] as $charNum => $charName) {
1118
-                    if ($charNum > $lastChar) {
1119
-                        if (!$object['isUnicode']) {
1120
-                            // With Unicode, widths array isn't used
1121
-                            for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1122
-                                $widths[] = 0;
1123
-                            }
1124
-                        }
1125
-
1126
-                        $lastChar = $charNum;
1127
-                    }
1128
-
1129
-                    if (isset($font['C'][$charName])) {
1130
-                        $widths[$charNum - $firstChar] = $font['C'][$charName];
1131
-                        if ($font['isUnicode']) {
1132
-                            $cid_widths[$charName] = $font['C'][$charName];
1133
-                        }
1134
-                    }
1135
-                }
1136
-            }
1137
-
1138
-            if ($font['isUnicode']) {
1139
-                $font['CIDWidths'] = $cid_widths;
1140
-            }
1141
-
1142
-            $this->addMessage('selectFont: FirstChar = ' . $firstChar);
1143
-            $this->addMessage('selectFont: LastChar = ' . $lastChar);
1144
-
1145
-            $widthid = -1;
1146
-
1147
-            if (!$font['isUnicode']) {
1148
-                // With Unicode, widths array isn't used
1149
-
1150
-                $this->numObj++;
1151
-                $this->o_contents($this->numObj, 'new', 'raw');
1152
-                $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1153
-                $widthid = $this->numObj;
1154
-            }
1155
-
1156
-            $missing_width = 500;
1157
-            $stemV = 70;
1158
-
1159
-            if (isset($font['MissingWidth'])) {
1160
-                $missing_width = $font['MissingWidth'];
1161
-            }
1162
-            if (isset($font['StdVW'])) {
1163
-                $stemV = $font['StdVW'];
1164
-            } else {
1165
-                if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
1166
-                    $stemV = 120;
1167
-                }
1168
-            }
1169
-
1170
-            // load the pfb file, and put that into an object too.
1171
-            // note that pdf supports only binary format type 1 font files, though there is a
1172
-            // simple utility to convert them from pfa to pfb.
1173
-            $data = file_get_contents($fbfile);
1174
-
1175
-            // create the font descriptor
1176
-            $this->numObj++;
1177
-            $fontDescriptorId = $this->numObj;
1178
-
1179
-            $this->numObj++;
1180
-            $pfbid = $this->numObj;
1181
-
1182
-            // determine flags (more than a little flakey, hopefully will not matter much)
1183
-            $flags = 0;
1184
-
1185
-            if ($font['ItalicAngle'] != 0) {
1186
-                $flags += pow(2, 6);
1187
-            }
1188
-
1189
-            if ($font['IsFixedPitch'] === 'true') {
1190
-                $flags += 1;
1191
-            }
1192
-
1193
-            $flags += pow(2, 5); // assume non-sybolic
1194
-            $list = [
1195
-                'Ascent'       => 'Ascender',
1196
-                'CapHeight'    => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
1197
-                'MissingWidth' => 'MissingWidth',
1198
-                'Descent'      => 'Descender',
1199
-                'FontBBox'     => 'FontBBox',
1200
-                'ItalicAngle'  => 'ItalicAngle'
1201
-            ];
1202
-            $fdopt = [
1203
-                'Flags'    => $flags,
1204
-                'FontName' => $adobeFontName,
1205
-                'StemV'    => $stemV
1206
-            ];
1207
-
1208
-            foreach ($list as $k => $v) {
1209
-                if (isset($font[$v])) {
1210
-                    $fdopt[$k] = $font[$v];
1211
-                }
1212
-            }
1213
-
1214
-            if ($isPfbFont) {
1215
-                $fdopt['FontFile'] = $pfbid;
1216
-            } elseif ($isTtfFont) {
1217
-                $fdopt['FontFile2'] = $pfbid;
1218
-            }
1219
-
1220
-            $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
1221
-
1222
-            // embed the font program
1223
-            $this->o_contents($this->numObj, 'new');
1224
-            $this->objects[$pfbid]['c'] .= $data;
1225
-
1226
-            // determine the cruicial lengths within this file
1227
-            if ($isPfbFont) {
1228
-                $l1 = strpos($data, 'eexec') + 6;
1229
-                $l2 = strpos($data, '00000000') - $l1;
1230
-                $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
1231
-                $this->o_contents(
1232
-                    $this->numObj,
1233
-                    'add',
1234
-                    ['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
1235
-                );
1236
-            } elseif ($isTtfFont) {
1237
-                $l1 = mb_strlen($data, '8bit');
1238
-                $this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
1239
-            }
1240
-
1241
-            // tell the font object about all this new stuff
1242
-            $options = [
1243
-                'BaseFont'       => $adobeFontName,
1244
-                'MissingWidth'   => $missing_width,
1245
-                'Widths'         => $widthid,
1246
-                'FirstChar'      => $firstChar,
1247
-                'LastChar'       => $lastChar,
1248
-                'FontDescriptor' => $fontDescriptorId
1249
-            ];
1250
-
1251
-            if ($isTtfFont) {
1252
-                $options['SubType'] = 'TrueType';
1253
-            }
1254
-
1255
-            $this->addMessage("adding extra info to font.($fontObjId)");
1256
-
1257
-            foreach ($options as $fk => $fv) {
1258
-                $this->addMessage("$fk : $fv");
1259
-            }
1260
-        }
1261
-
1262
-        return $options;
1263
-    }
1264
-
1265
-    /**
1266
-     * A toUnicode section, needed for unicode fonts
1267
-     *
1268
-     * @param $id
1269
-     * @param $action
1270
-     * @return null|string
1271
-     */
1272
-    protected function o_toUnicode($id, $action)
1273
-    {
1274
-        switch ($action) {
1275
-            case 'new':
1276
-                $this->objects[$id] = [
1277
-                    't'    => 'toUnicode'
1278
-                ];
1279
-                break;
1280
-            case 'add':
1281
-                break;
1282
-            case 'out':
1283
-                $ordering = 'UCS';
1284
-                $registry = 'Adobe';
1285
-
1286
-                if ($this->encrypted) {
1287
-                    $this->encryptInit($id);
1288
-                    $ordering = $this->ARC4($ordering);
1289
-                    $registry = $this->filterText($this->ARC4($registry), false, false);
1290
-                }
1291
-
1292
-                $stream = <<<EOT
23
+	const PDF_VERSION = '1.7';
24
+
25
+	const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
26
+	const ACROFORM_SIG_APPENDONLY =       0x0002;
27
+
28
+	const ACROFORM_FIELD_BUTTON =   'Btn';
29
+	const ACROFORM_FIELD_TEXT =     'Tx';
30
+	const ACROFORM_FIELD_CHOICE =   'Ch';
31
+	const ACROFORM_FIELD_SIG =      'Sig';
32
+
33
+	const ACROFORM_FIELD_READONLY =               0x0001;
34
+	const ACROFORM_FIELD_REQUIRED =               0x0002;
35
+
36
+	const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
37
+	const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
38
+	const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
39
+
40
+	const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
41
+	const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
42
+	const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
43
+	const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
44
+
45
+	const XOBJECT_SUBTYPE_FORM = 'Form';
46
+
47
+	/**
48
+	 * @var integer The current number of pdf objects in the document
49
+	 */
50
+	public $numObj = 0;
51
+
52
+	/**
53
+	 * @var array This array contains all of the pdf objects, ready for final assembly
54
+	 */
55
+	public $objects = [];
56
+
57
+	/**
58
+	 * @var integer The objectId (number within the objects array) of the document catalog
59
+	 */
60
+	public $catalogId;
61
+
62
+	/**
63
+	 * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
64
+	 */
65
+	protected $indirectReferenceId = 0;
66
+
67
+	/**
68
+	 * @var integer The objectId (number within the objects array)
69
+	 */
70
+	protected $embeddedFilesId = 0;
71
+
72
+	/**
73
+	 * AcroForm objectId
74
+	 *
75
+	 * @var integer
76
+	 */
77
+	public $acroFormId;
78
+
79
+	/**
80
+	 * @var int
81
+	 */
82
+	public $signatureMaxLen = 5000;
83
+
84
+	/**
85
+	 * @var array Array carrying information about the fonts that the system currently knows about
86
+	 * Used to ensure that a font is not loaded twice, among other things
87
+	 */
88
+	public $fonts = [];
89
+
90
+	/**
91
+	 * @var string The default font metrics file to use if no other font has been loaded.
92
+	 * The path to the directory containing the font metrics should be included
93
+	 */
94
+	public $defaultFont = './fonts/Helvetica.afm';
95
+
96
+	/**
97
+	 * @string A record of the current font
98
+	 */
99
+	public $currentFont = '';
100
+
101
+	/**
102
+	 * @var string The current base font
103
+	 */
104
+	public $currentBaseFont = '';
105
+
106
+	/**
107
+	 * @var integer The number of the current font within the font array
108
+	 */
109
+	public $currentFontNum = 0;
110
+
111
+	/**
112
+	 * @var integer
113
+	 */
114
+	public $currentNode;
115
+
116
+	/**
117
+	 * @var integer Object number of the current page
118
+	 */
119
+	public $currentPage;
120
+
121
+	/**
122
+	 * @var integer Object number of the currently active contents block
123
+	 */
124
+	public $currentContents;
125
+
126
+	/**
127
+	 * @var integer Number of fonts within the system
128
+	 */
129
+	public $numFonts = 0;
130
+
131
+	/**
132
+	 * @var integer Number of graphic state resources used
133
+	 */
134
+	private $numStates = 0;
135
+
136
+	/**
137
+	 * @var array Number of graphic state resources used
138
+	 */
139
+	private $gstates = [];
140
+
141
+	/**
142
+	 * @var array Current color for fill operations, defaults to inactive value,
143
+	 * all three components should be between 0 and 1 inclusive when active
144
+	 */
145
+	public $currentColor = null;
146
+
147
+	/**
148
+	 * @var array Current color for stroke operations (lines etc.)
149
+	 */
150
+	public $currentStrokeColor = null;
151
+
152
+	/**
153
+	 * @var string Fill rule (nonzero or evenodd)
154
+	 */
155
+	public $fillRule = "nonzero";
156
+
157
+	/**
158
+	 * @var string Current style that lines are drawn in
159
+	 */
160
+	public $currentLineStyle = '';
161
+
162
+	/**
163
+	 * @var array Current line transparency (partial graphics state)
164
+	 */
165
+	public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
166
+
167
+	/**
168
+	 * array Current fill transparency (partial graphics state)
169
+	 */
170
+	public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
171
+
172
+	/**
173
+	 * @var array An array which is used to save the state of the document, mainly the colors and styles
174
+	 * it is used to temporarily change to another state, then change back to what it was before
175
+	 */
176
+	public $stateStack = [];
177
+
178
+	/**
179
+	 * @var integer Number of elements within the state stack
180
+	 */
181
+	public $nStateStack = 0;
182
+
183
+	/**
184
+	 * @var integer Number of page objects within the document
185
+	 */
186
+	public $numPages = 0;
187
+
188
+	/**
189
+	 * @var array Object Id storage stack
190
+	 */
191
+	public $stack = [];
192
+
193
+	/**
194
+	 * @var integer Number of elements within the object Id storage stack
195
+	 */
196
+	public $nStack = 0;
197
+
198
+	/**
199
+	 * an array which contains information about the objects which are not firmly attached to pages
200
+	 * these have been added with the addObject function
201
+	 */
202
+	public $looseObjects = [];
203
+
204
+	/**
205
+	 * array contains information about how the loose objects are to be added to the document
206
+	 */
207
+	public $addLooseObjects = [];
208
+
209
+	/**
210
+	 * @var integer The objectId of the information object for the document
211
+	 * this contains authorship, title etc.
212
+	 */
213
+	public $infoObject = 0;
214
+
215
+	/**
216
+	 * @var integer Number of images being tracked within the document
217
+	 */
218
+	public $numImages = 0;
219
+
220
+	/**
221
+	 * @var array An array containing options about the document
222
+	 * it defaults to turning on the compression of the objects
223
+	 */
224
+	public $options = ['compression' => true];
225
+
226
+	/**
227
+	 * @var integer The objectId of the first page of the document
228
+	 */
229
+	public $firstPageId;
230
+
231
+	/**
232
+	 * @var integer The object Id of the procset object
233
+	 */
234
+	public $procsetObjectId;
235
+
236
+	/**
237
+	 * @var array Store the information about the relationship between font families
238
+	 * this used so that the code knows which font is the bold version of another font, etc.
239
+	 * the value of this array is initialised in the constructor function.
240
+	 */
241
+	public $fontFamilies = [];
242
+
243
+	/**
244
+	 * @var string Folder for php serialized formats of font metrics files.
245
+	 * If empty string, use same folder as original metrics files.
246
+	 * This can be passed in from class creator.
247
+	 * If this folder does not exist or is not writable, Cpdf will be **much** slower.
248
+	 * Because of potential trouble with php safe mode, folder cannot be created at runtime.
249
+	 */
250
+	public $fontcache = '';
251
+
252
+	/**
253
+	 * @var integer The version of the font metrics cache file.
254
+	 * This value must be manually incremented whenever the internal font data structure is modified.
255
+	 */
256
+	public $fontcacheVersion = 6;
257
+
258
+	/**
259
+	 * @var string Temporary folder.
260
+	 * If empty string, will attempt system tmp folder.
261
+	 * This can be passed in from class creator.
262
+	 */
263
+	public $tmp = '';
264
+
265
+	/**
266
+	 * @var string Track if the current font is bolded or italicised
267
+	 */
268
+	public $currentTextState = '';
269
+
270
+	/**
271
+	 * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
272
+	 */
273
+	public $messages = '';
274
+
275
+	/**
276
+	 * @var string The encryption array for the document encryption is stored here
277
+	 */
278
+	public $arc4 = '';
279
+
280
+	/**
281
+	 * @var integer The object Id of the encryption information
282
+	 */
283
+	public $arc4_objnum = 0;
284
+
285
+	/**
286
+	 * @var string The file identifier, used to uniquely identify a pdf document
287
+	 */
288
+	public $fileIdentifier = '';
289
+
290
+	/**
291
+	 * @var boolean A flag to say if a document is to be encrypted or not
292
+	 */
293
+	public $encrypted = false;
294
+
295
+	/**
296
+	 * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
297
+	 */
298
+	public $encryptionKey = '';
299
+
300
+	/**
301
+	 * @var array Array which forms a stack to keep track of nested callback functions
302
+	 */
303
+	public $callback = [];
304
+
305
+	/**
306
+	 * @var integer The number of callback functions in the callback array
307
+	 */
308
+	public $nCallback = 0;
309
+
310
+	/**
311
+	 * @var array Store label->id pairs for named destinations, these will be used to replace internal links
312
+	 * done this way so that destinations can be defined after the location that links to them
313
+	 */
314
+	public $destinations = [];
315
+
316
+	/**
317
+	 * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
318
+	 * publiciables within the class, so that the user can rollback at will (from each 'start' command)
319
+	 * note that this includes the objects array, so these can be large.
320
+	 */
321
+	public $checkpoint = '';
322
+
323
+	/**
324
+	 * @var array Table of Image origin filenames and image labels which were already added with o_image().
325
+	 * Allows to merge identical images
326
+	 */
327
+	public $imagelist = [];
328
+
329
+	/**
330
+	 * @var array Table of already added alpha and plain image files for transparent PNG images.
331
+	 */
332
+	protected $imageAlphaList = [];
333
+
334
+	/**
335
+	 * @var array List of temporary image files to be deleted after processing.
336
+	 */
337
+	protected $imageCache = [];
338
+
339
+	/**
340
+	 * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
341
+	 */
342
+	public $isUnicode = false;
343
+
344
+	/**
345
+	 * @var string the JavaScript code of the document
346
+	 */
347
+	public $javascript = '';
348
+
349
+	/**
350
+	 * @var boolean whether the compression is possible
351
+	 */
352
+	protected $compressionReady = false;
353
+
354
+	/**
355
+	 * @var array Current page size
356
+	 */
357
+	protected $currentPageSize = ["width" => 0, "height" => 0];
358
+
359
+	/**
360
+	 * @var array All the chars that will be required in the font subsets
361
+	 */
362
+	protected $stringSubsets = [];
363
+
364
+	/**
365
+	 * @var string The target internal encoding
366
+	 */
367
+	protected static $targetEncoding = 'Windows-1252';
368
+
369
+	/**
370
+	 * @var array
371
+	 */
372
+	protected $byteRange = array();
373
+
374
+	/**
375
+	 * @var array The list of the core fonts
376
+	 */
377
+	protected static $coreFonts = [
378
+		'courier',
379
+		'courier-bold',
380
+		'courier-oblique',
381
+		'courier-boldoblique',
382
+		'helvetica',
383
+		'helvetica-bold',
384
+		'helvetica-oblique',
385
+		'helvetica-boldoblique',
386
+		'times-roman',
387
+		'times-bold',
388
+		'times-italic',
389
+		'times-bolditalic',
390
+		'symbol',
391
+		'zapfdingbats'
392
+	];
393
+
394
+	/**
395
+	 * Class constructor
396
+	 * This will start a new document
397
+	 *
398
+	 * @param array   $pageSize  Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
399
+	 * @param boolean $isUnicode Whether text will be treated as Unicode or not.
400
+	 * @param string  $fontcache The font cache folder
401
+	 * @param string  $tmp       The temporary folder
402
+	 */
403
+	function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
404
+	{
405
+		$this->isUnicode = $isUnicode;
406
+		$this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
407
+		$this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
408
+		$this->newDocument($pageSize);
409
+
410
+		$this->compressionReady = function_exists('gzcompress');
411
+
412
+		if (in_array('Windows-1252', mb_list_encodings())) {
413
+			self::$targetEncoding = 'Windows-1252';
414
+		}
415
+
416
+		// also initialize the font families that are known about already
417
+		$this->setFontFamily('init');
418
+	}
419
+
420
+	public function __destruct()
421
+	{
422
+		foreach ($this->imageCache as $file) {
423
+			if (file_exists($file)) {
424
+				unlink($file);
425
+			}
426
+		}
427
+	}
428
+
429
+	/**
430
+	 * Document object methods (internal use only)
431
+	 *
432
+	 * There is about one object method for each type of object in the pdf document
433
+	 * Each function has the same call list ($id,$action,$options).
434
+	 * $id = the object ID of the object, or what it is to be if it is being created
435
+	 * $action = a string specifying the action to be performed, though ALL must support:
436
+	 *           'new' - create the object with the id $id
437
+	 *           'out' - produce the output for the pdf object
438
+	 * $options = optional, a string or array containing the various parameters for the object
439
+	 *
440
+	 * These, in conjunction with the output function are the ONLY way for output to be produced
441
+	 * within the pdf 'file'.
442
+	 */
443
+
444
+	/**
445
+	 * Destination object, used to specify the location for the user to jump to, presently on opening
446
+	 *
447
+	 * @param $id
448
+	 * @param $action
449
+	 * @param string $options
450
+	 * @return string|null
451
+	 */
452
+	protected function o_destination($id, $action, $options = '')
453
+	{
454
+		switch ($action) {
455
+			case 'new':
456
+				$this->objects[$id] = ['t' => 'destination', 'info' => []];
457
+				$tmp = '';
458
+				switch ($options['type']) {
459
+					case 'XYZ':
460
+					/** @noinspection PhpMissingBreakStatementInspection */
461
+					case 'FitR':
462
+						$tmp = ' ' . $options['p3'] . $tmp;
463
+					case 'FitH':
464
+					case 'FitV':
465
+					case 'FitBH':
466
+					/** @noinspection PhpMissingBreakStatementInspection */
467
+					case 'FitBV':
468
+						$tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
469
+					case 'Fit':
470
+					case 'FitB':
471
+						$tmp = $options['type'] . $tmp;
472
+						$this->objects[$id]['info']['string'] = $tmp;
473
+						$this->objects[$id]['info']['page'] = $options['page'];
474
+				}
475
+				break;
476
+
477
+			case 'out':
478
+				$o = &$this->objects[$id];
479
+
480
+				$tmp = $o['info'];
481
+				$res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
482
+
483
+				return $res;
484
+		}
485
+
486
+		return null;
487
+	}
488
+
489
+	/**
490
+	 * set the viewer preferences
491
+	 *
492
+	 * @param $id
493
+	 * @param $action
494
+	 * @param string|array $options
495
+	 * @return string|null
496
+	 */
497
+	protected function o_viewerPreferences($id, $action, $options = '')
498
+	{
499
+		switch ($action) {
500
+			case 'new':
501
+				$this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
502
+				break;
503
+
504
+			case 'add':
505
+				$o = &$this->objects[$id];
506
+
507
+				foreach ($options as $k => $v) {
508
+					switch ($k) {
509
+						// Boolean keys
510
+						case 'HideToolbar':
511
+						case 'HideMenubar':
512
+						case 'HideWindowUI':
513
+						case 'FitWindow':
514
+						case 'CenterWindow':
515
+						case 'DisplayDocTitle':
516
+						case 'PickTrayByPDFSize':
517
+							$o['info'][$k] = (bool)$v;
518
+							break;
519
+
520
+						// Integer keys
521
+						case 'NumCopies':
522
+							$o['info'][$k] = (int)$v;
523
+							break;
524
+
525
+						// Name keys
526
+						case 'ViewArea':
527
+						case 'ViewClip':
528
+						case 'PrintClip':
529
+						case 'PrintArea':
530
+							$o['info'][$k] = (string)$v;
531
+							break;
532
+
533
+						// Named with limited valid values
534
+						case 'NonFullScreenPageMode':
535
+							if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
536
+								break;
537
+							}
538
+							$o['info'][$k] = $v;
539
+							break;
540
+
541
+						case 'Direction':
542
+							if (!in_array($v, ['L2R', 'R2L'])) {
543
+								break;
544
+							}
545
+							$o['info'][$k] = $v;
546
+							break;
547
+
548
+						case 'PrintScaling':
549
+							if (!in_array($v, ['None', 'AppDefault'])) {
550
+								break;
551
+							}
552
+							$o['info'][$k] = $v;
553
+							break;
554
+
555
+						case 'Duplex':
556
+							if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
557
+								break;
558
+							}
559
+							$o['info'][$k] = $v;
560
+							break;
561
+
562
+						// Integer array
563
+						case 'PrintPageRange':
564
+							// Cast to integer array
565
+							foreach ($v as $vK => $vV) {
566
+								$v[$vK] = (int)$vV;
567
+							}
568
+							$o['info'][$k] = array_values($v);
569
+							break;
570
+					}
571
+				}
572
+				break;
573
+
574
+			case 'out':
575
+				$o = &$this->objects[$id];
576
+				$res = "\n$id 0 obj\n<< ";
577
+
578
+				foreach ($o['info'] as $k => $v) {
579
+					if (is_string($v)) {
580
+						$v = '/' . $v;
581
+					} elseif (is_int($v)) {
582
+						$v = (string) $v;
583
+					} elseif (is_bool($v)) {
584
+						$v = ($v ? 'true' : 'false');
585
+					} elseif (is_array($v)) {
586
+						$v = '[' . implode(' ', $v) . ']';
587
+					}
588
+					$res .= "\n/$k $v";
589
+				}
590
+				$res .= "\n>>\nendobj";
591
+
592
+				return $res;
593
+		}
594
+
595
+		return null;
596
+	}
597
+
598
+	/**
599
+	 * define the document catalog, the overall controller for the document
600
+	 *
601
+	 * @param $id
602
+	 * @param $action
603
+	 * @param string|array $options
604
+	 * @return string|null
605
+	 */
606
+	protected function o_catalog($id, $action, $options = '')
607
+	{
608
+		if ($action !== 'new') {
609
+			$o = &$this->objects[$id];
610
+		}
611
+
612
+		switch ($action) {
613
+			case 'new':
614
+				$this->objects[$id] = ['t' => 'catalog', 'info' => []];
615
+				$this->catalogId = $id;
616
+				break;
617
+
618
+			case 'acroform':
619
+			case 'outlines':
620
+			case 'pages':
621
+			case 'openHere':
622
+			case 'names':
623
+				$o['info'][$action] = $options;
624
+				break;
625
+
626
+			case 'viewerPreferences':
627
+				if (!isset($o['info']['viewerPreferences'])) {
628
+					$this->numObj++;
629
+					$this->o_viewerPreferences($this->numObj, 'new');
630
+					$o['info']['viewerPreferences'] = $this->numObj;
631
+				}
632
+
633
+				$vp = $o['info']['viewerPreferences'];
634
+				$this->o_viewerPreferences($vp, 'add', $options);
635
+
636
+				break;
637
+
638
+			case 'out':
639
+				$res = "\n$id 0 obj\n<< /Type /Catalog";
640
+
641
+				foreach ($o['info'] as $k => $v) {
642
+					switch ($k) {
643
+						case 'outlines':
644
+							$res .= "\n/Outlines $v 0 R";
645
+							break;
646
+
647
+						case 'pages':
648
+							$res .= "\n/Pages $v 0 R";
649
+							break;
650
+
651
+						case 'viewerPreferences':
652
+							$res .= "\n/ViewerPreferences $v 0 R";
653
+							break;
654
+
655
+						case 'openHere':
656
+							$res .= "\n/OpenAction $v 0 R";
657
+							break;
658
+
659
+						case 'names':
660
+							$res .= "\n/Names $v 0 R";
661
+							break;
662
+
663
+						case 'acroform':
664
+							$res .= "\n/AcroForm $v 0 R";
665
+							break;
666
+					}
667
+				}
668
+
669
+				$res .= " >>\nendobj";
670
+
671
+				return $res;
672
+		}
673
+
674
+		return null;
675
+	}
676
+
677
+	/**
678
+	 * object which is a parent to the pages in the document
679
+	 *
680
+	 * @param $id
681
+	 * @param $action
682
+	 * @param string $options
683
+	 * @return string|null
684
+	 */
685
+	protected function o_pages($id, $action, $options = '')
686
+	{
687
+		if ($action !== 'new') {
688
+			$o = &$this->objects[$id];
689
+		}
690
+
691
+		switch ($action) {
692
+			case 'new':
693
+				$this->objects[$id] = ['t' => 'pages', 'info' => []];
694
+				$this->o_catalog($this->catalogId, 'pages', $id);
695
+				break;
696
+
697
+			case 'page':
698
+				if (!is_array($options)) {
699
+					// then it will just be the id of the new page
700
+					$o['info']['pages'][] = $options;
701
+				} else {
702
+					// then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
703
+					// and pos is either 'before' or 'after', saying where this page will fit.
704
+					if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
705
+						$i = array_search($options['rid'], $o['info']['pages']);
706
+						if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
707
+
708
+							// then there is a match
709
+							// make a space
710
+							switch ($options['pos']) {
711
+								case 'before':
712
+									$k = $i;
713
+									break;
714
+
715
+								case 'after':
716
+									$k = $i + 1;
717
+									break;
718
+
719
+								default:
720
+									$k = -1;
721
+									break;
722
+							}
723
+
724
+							if ($k >= 0) {
725
+								for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
726
+									$o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
727
+								}
728
+
729
+								$o['info']['pages'][$k] = $options['id'];
730
+							}
731
+						}
732
+					}
733
+				}
734
+				break;
735
+
736
+			case 'procset':
737
+				$o['info']['procset'] = $options;
738
+				break;
739
+
740
+			case 'mediaBox':
741
+				$o['info']['mediaBox'] = $options;
742
+				// which should be an array of 4 numbers
743
+				$this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
744
+				break;
745
+
746
+			case 'font':
747
+				$o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
748
+				break;
749
+
750
+			case 'extGState':
751
+				$o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
752
+				break;
753
+
754
+			case 'xObject':
755
+				$o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
756
+				break;
757
+
758
+			case 'out':
759
+				if (count($o['info']['pages'])) {
760
+					$res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
761
+					foreach ($o['info']['pages'] as $v) {
762
+						$res .= "$v 0 R\n";
763
+					}
764
+
765
+					$res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
766
+
767
+					if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
768
+						isset($o['info']['procset']) ||
769
+						(isset($o['info']['extGStates']) && count($o['info']['extGStates']))
770
+					) {
771
+						$res .= "\n/Resources <<";
772
+
773
+						if (isset($o['info']['procset'])) {
774
+							$res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
775
+						}
776
+
777
+						if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
778
+							$res .= "\n/Font << ";
779
+							foreach ($o['info']['fonts'] as $finfo) {
780
+								$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
781
+							}
782
+							$res .= "\n>>";
783
+						}
784
+
785
+						if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
786
+							$res .= "\n/XObject << ";
787
+							foreach ($o['info']['xObjects'] as $finfo) {
788
+								$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
789
+							}
790
+							$res .= "\n>>";
791
+						}
792
+
793
+						if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
794
+							$res .= "\n/ExtGState << ";
795
+							foreach ($o['info']['extGStates'] as $gstate) {
796
+								$res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
797
+							}
798
+							$res .= "\n>>";
799
+						}
800
+
801
+						$res .= "\n>>";
802
+						if (isset($o['info']['mediaBox'])) {
803
+							$tmp = $o['info']['mediaBox'];
804
+							$res .= "\n/MediaBox [" . sprintf(
805
+									'%.3F %.3F %.3F %.3F',
806
+									$tmp[0],
807
+									$tmp[1],
808
+									$tmp[2],
809
+									$tmp[3]
810
+								) . ']';
811
+						}
812
+					}
813
+
814
+					$res .= "\n >>\nendobj";
815
+				} else {
816
+					$res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
817
+				}
818
+
819
+				return $res;
820
+		}
821
+
822
+		return null;
823
+	}
824
+
825
+	/**
826
+	 * define the outlines in the doc, empty for now
827
+	 *
828
+	 * @param $id
829
+	 * @param $action
830
+	 * @param string $options
831
+	 * @return string|null
832
+	 */
833
+	protected function o_outlines($id, $action, $options = '')
834
+	{
835
+		if ($action !== 'new') {
836
+			$o = &$this->objects[$id];
837
+		}
838
+
839
+		switch ($action) {
840
+			case 'new':
841
+				$this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
842
+				$this->o_catalog($this->catalogId, 'outlines', $id);
843
+				break;
844
+
845
+			case 'outline':
846
+				$o['info']['outlines'][] = $options;
847
+				break;
848
+
849
+			case 'out':
850
+				if (count($o['info']['outlines'])) {
851
+					$res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
852
+					foreach ($o['info']['outlines'] as $v) {
853
+						$res .= "$v 0 R ";
854
+					}
855
+
856
+					$res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
857
+				} else {
858
+					$res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
859
+				}
860
+
861
+				return $res;
862
+		}
863
+
864
+		return null;
865
+	}
866
+
867
+	/**
868
+	 * an object to hold the font description
869
+	 *
870
+	 * @param $id
871
+	 * @param $action
872
+	 * @param string|array $options
873
+	 * @return string|null
874
+	 * @throws FontNotFoundException
875
+	 */
876
+	protected function o_font($id, $action, $options = '')
877
+	{
878
+		if ($action !== 'new') {
879
+			$o = &$this->objects[$id];
880
+		}
881
+
882
+		switch ($action) {
883
+			case 'new':
884
+				$this->objects[$id] = [
885
+					't'    => 'font',
886
+					'info' => [
887
+						'name'         => $options['name'],
888
+						'fontFileName' => $options['fontFileName'],
889
+						'SubType'      => 'Type1',
890
+						'isSubsetting'   => $options['isSubsetting']
891
+					]
892
+				];
893
+				$fontNum = $this->numFonts;
894
+				$this->objects[$id]['info']['fontNum'] = $fontNum;
895
+
896
+				// deal with the encoding and the differences
897
+				if (isset($options['differences'])) {
898
+					// then we'll need an encoding dictionary
899
+					$this->numObj++;
900
+					$this->o_fontEncoding($this->numObj, 'new', $options);
901
+					$this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
902
+				} else {
903
+					if (isset($options['encoding'])) {
904
+						// we can specify encoding here
905
+						switch ($options['encoding']) {
906
+							case 'WinAnsiEncoding':
907
+							case 'MacRomanEncoding':
908
+							case 'MacExpertEncoding':
909
+								$this->objects[$id]['info']['encoding'] = $options['encoding'];
910
+								break;
911
+
912
+							case 'none':
913
+								break;
914
+
915
+							default:
916
+								$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
917
+								break;
918
+						}
919
+					} else {
920
+						$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
921
+					}
922
+				}
923
+
924
+				if ($this->fonts[$options['fontFileName']]['isUnicode']) {
925
+					// For Unicode fonts, we need to incorporate font data into
926
+					// sub-sections that are linked from the primary font section.
927
+					// Look at o_fontGIDtoCID and o_fontDescendentCID functions
928
+					// for more information.
929
+					//
930
+					// All of this code is adapted from the excellent changes made to
931
+					// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
932
+
933
+					$toUnicodeId = ++$this->numObj;
934
+					$this->o_toUnicode($toUnicodeId, 'new');
935
+					$this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
936
+
937
+					$cidFontId = ++$this->numObj;
938
+					$this->o_fontDescendentCID($cidFontId, 'new', $options);
939
+					$this->objects[$id]['info']['cidFont'] = $cidFontId;
940
+				}
941
+
942
+				// also tell the pages node about the new font
943
+				$this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
944
+				break;
945
+
946
+			case 'add':
947
+				$font_options = $this->processFont($id, $o['info']);
948
+
949
+				if ($font_options !== false) {
950
+					foreach ($font_options as $k => $v) {
951
+						switch ($k) {
952
+							case 'BaseFont':
953
+								$o['info']['name'] = $v;
954
+								break;
955
+							case 'FirstChar':
956
+							case 'LastChar':
957
+							case 'Widths':
958
+							case 'FontDescriptor':
959
+							case 'SubType':
960
+								$this->addMessage('o_font ' . $k . " : " . $v);
961
+								$o['info'][$k] = $v;
962
+								break;
963
+						}
964
+					}
965
+
966
+					// pass values down to descendent font
967
+					if (isset($o['info']['cidFont'])) {
968
+						$this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
969
+					}
970
+				}
971
+				break;
972
+
973
+			case 'out':
974
+				if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
975
+					// For Unicode fonts, we need to incorporate font data into
976
+					// sub-sections that are linked from the primary font section.
977
+					// Look at o_fontGIDtoCID and o_fontDescendentCID functions
978
+					// for more information.
979
+					//
980
+					// All of this code is adapted from the excellent changes made to
981
+					// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
982
+
983
+					$res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
984
+					$res .= "/BaseFont /" . $o['info']['name'] . "\n";
985
+
986
+					// The horizontal identity mapping for 2-byte CIDs; may be used
987
+					// with CIDFonts using any Registry, Ordering, and Supplement values.
988
+					$res .= "/Encoding /Identity-H\n";
989
+					$res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
990
+					$res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
991
+					$res .= ">>\n";
992
+					$res .= "endobj";
993
+				} else {
994
+					$res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
995
+					$res .= "/Name /F" . $o['info']['fontNum'] . "\n";
996
+					$res .= "/BaseFont /" . $o['info']['name'] . "\n";
997
+
998
+					if (isset($o['info']['encodingDictionary'])) {
999
+						// then place a reference to the dictionary
1000
+						$res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
1001
+					} else {
1002
+						if (isset($o['info']['encoding'])) {
1003
+							// use the specified encoding
1004
+							$res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1005
+						}
1006
+					}
1007
+
1008
+					if (isset($o['info']['FirstChar'])) {
1009
+						$res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1010
+					}
1011
+
1012
+					if (isset($o['info']['LastChar'])) {
1013
+						$res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1014
+					}
1015
+
1016
+					if (isset($o['info']['Widths'])) {
1017
+						$res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1018
+					}
1019
+
1020
+					if (isset($o['info']['FontDescriptor'])) {
1021
+						$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1022
+					}
1023
+
1024
+					$res .= ">>\n";
1025
+					$res .= "endobj";
1026
+				}
1027
+
1028
+				return $res;
1029
+		}
1030
+
1031
+		return null;
1032
+	}
1033
+
1034
+	protected function getFontSubsettingTag(array $font): string
1035
+	{
1036
+		// convert font num to hexavigesimal numeral system letters A - Z only
1037
+		$base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
1038
+		for ($i = 0; $i < strlen($base_26); $i++) {
1039
+			$char = $base_26[$i];
1040
+			if ($char <= "9") {
1041
+				$base_26[$i] = chr(65 + intval($char));
1042
+			} else {
1043
+				$base_26[$i] = chr(ord($char) + 10);
1044
+			}
1045
+		}
1046
+
1047
+		return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1048
+	}
1049
+
1050
+	/**
1051
+	 * @param int $fontObjId
1052
+	 * @param array $object_info
1053
+	 * @return array|false
1054
+	 * @throws FontNotFoundException
1055
+	 */
1056
+	private function processFont(int $fontObjId, array $object_info)
1057
+	{
1058
+		$fontFileName = $object_info['fontFileName'];
1059
+		if (!isset($this->fonts[$fontFileName])) {
1060
+			return false;
1061
+		}
1062
+
1063
+		$font = &$this->fonts[$fontFileName];
1064
+
1065
+		$fileSuffix = $font['fileSuffix'];
1066
+		$fileSuffixLower = strtolower($font['fileSuffix']);
1067
+		$fbfile = "$fontFileName.$fileSuffix";
1068
+		$isTtfFont = $fileSuffixLower === 'ttf';
1069
+		$isPfbFont = $fileSuffixLower === 'pfb';
1070
+
1071
+		$this->addMessage('selectFont: checking for - ' . $fbfile);
1072
+
1073
+		if (!$fileSuffix) {
1074
+			$this->addMessage(
1075
+				'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1076
+			);
1077
+
1078
+			return false;
1079
+		} else {
1080
+			$adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
1081
+			//        $fontObj = $this->numObj;
1082
+			$this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
1083
+
1084
+			// find the array of font widths, and put that into an object.
1085
+			$firstChar = -1;
1086
+			$lastChar = 0;
1087
+			$widths = [];
1088
+			$cid_widths = [];
1089
+
1090
+			foreach ($font['C'] as $num => $d) {
1091
+				if (intval($num) > 0 || $num == '0') {
1092
+					if (!$font['isUnicode']) {
1093
+						// With Unicode, widths array isn't used
1094
+						if ($lastChar > 0 && $num > $lastChar + 1) {
1095
+							for ($i = $lastChar + 1; $i < $num; $i++) {
1096
+								$widths[] = 0;
1097
+							}
1098
+						}
1099
+					}
1100
+
1101
+					$widths[] = $d;
1102
+
1103
+					if ($font['isUnicode']) {
1104
+						$cid_widths[$num] = $d;
1105
+					}
1106
+
1107
+					if ($firstChar == -1) {
1108
+						$firstChar = $num;
1109
+					}
1110
+
1111
+					$lastChar = $num;
1112
+				}
1113
+			}
1114
+
1115
+			// also need to adjust the widths for the differences array
1116
+			if (isset($object['differences'])) {
1117
+				foreach ($object['differences'] as $charNum => $charName) {
1118
+					if ($charNum > $lastChar) {
1119
+						if (!$object['isUnicode']) {
1120
+							// With Unicode, widths array isn't used
1121
+							for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1122
+								$widths[] = 0;
1123
+							}
1124
+						}
1125
+
1126
+						$lastChar = $charNum;
1127
+					}
1128
+
1129
+					if (isset($font['C'][$charName])) {
1130
+						$widths[$charNum - $firstChar] = $font['C'][$charName];
1131
+						if ($font['isUnicode']) {
1132
+							$cid_widths[$charName] = $font['C'][$charName];
1133
+						}
1134
+					}
1135
+				}
1136
+			}
1137
+
1138
+			if ($font['isUnicode']) {
1139
+				$font['CIDWidths'] = $cid_widths;
1140
+			}
1141
+
1142
+			$this->addMessage('selectFont: FirstChar = ' . $firstChar);
1143
+			$this->addMessage('selectFont: LastChar = ' . $lastChar);
1144
+
1145
+			$widthid = -1;
1146
+
1147
+			if (!$font['isUnicode']) {
1148
+				// With Unicode, widths array isn't used
1149
+
1150
+				$this->numObj++;
1151
+				$this->o_contents($this->numObj, 'new', 'raw');
1152
+				$this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1153
+				$widthid = $this->numObj;
1154
+			}
1155
+
1156
+			$missing_width = 500;
1157
+			$stemV = 70;
1158
+
1159
+			if (isset($font['MissingWidth'])) {
1160
+				$missing_width = $font['MissingWidth'];
1161
+			}
1162
+			if (isset($font['StdVW'])) {
1163
+				$stemV = $font['StdVW'];
1164
+			} else {
1165
+				if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
1166
+					$stemV = 120;
1167
+				}
1168
+			}
1169
+
1170
+			// load the pfb file, and put that into an object too.
1171
+			// note that pdf supports only binary format type 1 font files, though there is a
1172
+			// simple utility to convert them from pfa to pfb.
1173
+			$data = file_get_contents($fbfile);
1174
+
1175
+			// create the font descriptor
1176
+			$this->numObj++;
1177
+			$fontDescriptorId = $this->numObj;
1178
+
1179
+			$this->numObj++;
1180
+			$pfbid = $this->numObj;
1181
+
1182
+			// determine flags (more than a little flakey, hopefully will not matter much)
1183
+			$flags = 0;
1184
+
1185
+			if ($font['ItalicAngle'] != 0) {
1186
+				$flags += pow(2, 6);
1187
+			}
1188
+
1189
+			if ($font['IsFixedPitch'] === 'true') {
1190
+				$flags += 1;
1191
+			}
1192
+
1193
+			$flags += pow(2, 5); // assume non-sybolic
1194
+			$list = [
1195
+				'Ascent'       => 'Ascender',
1196
+				'CapHeight'    => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
1197
+				'MissingWidth' => 'MissingWidth',
1198
+				'Descent'      => 'Descender',
1199
+				'FontBBox'     => 'FontBBox',
1200
+				'ItalicAngle'  => 'ItalicAngle'
1201
+			];
1202
+			$fdopt = [
1203
+				'Flags'    => $flags,
1204
+				'FontName' => $adobeFontName,
1205
+				'StemV'    => $stemV
1206
+			];
1207
+
1208
+			foreach ($list as $k => $v) {
1209
+				if (isset($font[$v])) {
1210
+					$fdopt[$k] = $font[$v];
1211
+				}
1212
+			}
1213
+
1214
+			if ($isPfbFont) {
1215
+				$fdopt['FontFile'] = $pfbid;
1216
+			} elseif ($isTtfFont) {
1217
+				$fdopt['FontFile2'] = $pfbid;
1218
+			}
1219
+
1220
+			$this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
1221
+
1222
+			// embed the font program
1223
+			$this->o_contents($this->numObj, 'new');
1224
+			$this->objects[$pfbid]['c'] .= $data;
1225
+
1226
+			// determine the cruicial lengths within this file
1227
+			if ($isPfbFont) {
1228
+				$l1 = strpos($data, 'eexec') + 6;
1229
+				$l2 = strpos($data, '00000000') - $l1;
1230
+				$l3 = mb_strlen($data, '8bit') - $l2 - $l1;
1231
+				$this->o_contents(
1232
+					$this->numObj,
1233
+					'add',
1234
+					['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
1235
+				);
1236
+			} elseif ($isTtfFont) {
1237
+				$l1 = mb_strlen($data, '8bit');
1238
+				$this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
1239
+			}
1240
+
1241
+			// tell the font object about all this new stuff
1242
+			$options = [
1243
+				'BaseFont'       => $adobeFontName,
1244
+				'MissingWidth'   => $missing_width,
1245
+				'Widths'         => $widthid,
1246
+				'FirstChar'      => $firstChar,
1247
+				'LastChar'       => $lastChar,
1248
+				'FontDescriptor' => $fontDescriptorId
1249
+			];
1250
+
1251
+			if ($isTtfFont) {
1252
+				$options['SubType'] = 'TrueType';
1253
+			}
1254
+
1255
+			$this->addMessage("adding extra info to font.($fontObjId)");
1256
+
1257
+			foreach ($options as $fk => $fv) {
1258
+				$this->addMessage("$fk : $fv");
1259
+			}
1260
+		}
1261
+
1262
+		return $options;
1263
+	}
1264
+
1265
+	/**
1266
+	 * A toUnicode section, needed for unicode fonts
1267
+	 *
1268
+	 * @param $id
1269
+	 * @param $action
1270
+	 * @return null|string
1271
+	 */
1272
+	protected function o_toUnicode($id, $action)
1273
+	{
1274
+		switch ($action) {
1275
+			case 'new':
1276
+				$this->objects[$id] = [
1277
+					't'    => 'toUnicode'
1278
+				];
1279
+				break;
1280
+			case 'add':
1281
+				break;
1282
+			case 'out':
1283
+				$ordering = 'UCS';
1284
+				$registry = 'Adobe';
1285
+
1286
+				if ($this->encrypted) {
1287
+					$this->encryptInit($id);
1288
+					$ordering = $this->ARC4($ordering);
1289
+					$registry = $this->filterText($this->ARC4($registry), false, false);
1290
+				}
1291
+
1292
+				$stream = <<<EOT
1293 1293
 /CIDInit /ProcSet findresource begin
1294 1294
 12 dict begin
1295 1295
 begincmap
@@ -1312,5108 +1312,5108 @@  discard block
 block discarded – undo
1312 1312
 end
1313 1313
 EOT;
1314 1314
 
1315
-                $res = "\n$id 0 obj\n";
1316
-                $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1317
-                $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1318
-
1319
-                return $res;
1320
-        }
1321
-
1322
-        return null;
1323
-    }
1324
-
1325
-    /**
1326
-     * a font descriptor, needed for including additional fonts
1327
-     *
1328
-     * @param $id
1329
-     * @param $action
1330
-     * @param string $options
1331
-     * @return null|string
1332
-     */
1333
-    protected function o_fontDescriptor($id, $action, $options = '')
1334
-    {
1335
-        if ($action !== 'new') {
1336
-            $o = &$this->objects[$id];
1337
-        }
1338
-
1339
-        switch ($action) {
1340
-            case 'new':
1341
-                $this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
1342
-                break;
1343
-
1344
-            case 'out':
1345
-                $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1346
-                foreach ($o['info'] as $label => $value) {
1347
-                    switch ($label) {
1348
-                        case 'Ascent':
1349
-                        case 'CapHeight':
1350
-                        case 'Descent':
1351
-                        case 'Flags':
1352
-                        case 'ItalicAngle':
1353
-                        case 'StemV':
1354
-                        case 'AvgWidth':
1355
-                        case 'Leading':
1356
-                        case 'MaxWidth':
1357
-                        case 'MissingWidth':
1358
-                        case 'StemH':
1359
-                        case 'XHeight':
1360
-                        case 'CharSet':
1361
-                            if (mb_strlen($value, '8bit')) {
1362
-                                $res .= "/$label $value\n";
1363
-                            }
1364
-
1365
-                            break;
1366
-                        case 'FontFile':
1367
-                        case 'FontFile2':
1368
-                        case 'FontFile3':
1369
-                            $res .= "/$label $value 0 R\n";
1370
-                            break;
1371
-
1372
-                        case 'FontBBox':
1373
-                            $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1374
-                            break;
1375
-
1376
-                        case 'FontName':
1377
-                            $res .= "/$label /$value\n";
1378
-                            break;
1379
-                    }
1380
-                }
1381
-
1382
-                $res .= ">>\nendobj";
1383
-
1384
-                return $res;
1385
-        }
1386
-
1387
-        return null;
1388
-    }
1389
-
1390
-    /**
1391
-     * the font encoding
1392
-     *
1393
-     * @param $id
1394
-     * @param $action
1395
-     * @param string $options
1396
-     * @return null|string
1397
-     */
1398
-    protected function o_fontEncoding($id, $action, $options = '')
1399
-    {
1400
-        if ($action !== 'new') {
1401
-            $o = &$this->objects[$id];
1402
-        }
1403
-
1404
-        switch ($action) {
1405
-            case 'new':
1406
-                // the options array should contain 'differences' and maybe 'encoding'
1407
-                $this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
1408
-                break;
1409
-
1410
-            case 'out':
1411
-                $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1412
-                if (!isset($o['info']['encoding'])) {
1413
-                    $o['info']['encoding'] = 'WinAnsiEncoding';
1414
-                }
1415
-
1416
-                if ($o['info']['encoding'] !== 'none') {
1417
-                    $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1418
-                }
1419
-
1420
-                $res .= "/Differences \n[";
1421
-
1422
-                $onum = -100;
1423
-
1424
-                foreach ($o['info']['differences'] as $num => $label) {
1425
-                    if ($num != $onum + 1) {
1426
-                        // we cannot make use of consecutive numbering
1427
-                        $res .= "\n$num /$label";
1428
-                    } else {
1429
-                        $res .= " /$label";
1430
-                    }
1431
-
1432
-                    $onum = $num;
1433
-                }
1434
-
1435
-                $res .= "\n]\n>>\nendobj";
1436
-
1437
-                return $res;
1438
-        }
1439
-
1440
-        return null;
1441
-    }
1442
-
1443
-    /**
1444
-     * a descendent cid font, needed for unicode fonts
1445
-     *
1446
-     * @param $id
1447
-     * @param $action
1448
-     * @param string|array $options
1449
-     * @return null|string
1450
-     */
1451
-    protected function o_fontDescendentCID($id, $action, $options = '')
1452
-    {
1453
-        if ($action !== 'new') {
1454
-            $o = &$this->objects[$id];
1455
-        }
1456
-
1457
-        switch ($action) {
1458
-            case 'new':
1459
-                $this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
1460
-
1461
-                // we need a CID system info section
1462
-                $cidSystemInfoId = ++$this->numObj;
1463
-                $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1464
-                $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1465
-
1466
-                // and a CID to GID map
1467
-                $cidToGidMapId = ++$this->numObj;
1468
-                $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1469
-                $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1470
-                break;
1471
-
1472
-            case 'add':
1473
-                foreach ($options as $k => $v) {
1474
-                    switch ($k) {
1475
-                        case 'BaseFont':
1476
-                            $o['info']['name'] = $v;
1477
-                            break;
1478
-
1479
-                        case 'FirstChar':
1480
-                        case 'LastChar':
1481
-                        case 'MissingWidth':
1482
-                        case 'FontDescriptor':
1483
-                        case 'SubType':
1484
-                            $this->addMessage("o_fontDescendentCID $k : $v");
1485
-                            $o['info'][$k] = $v;
1486
-                            break;
1487
-                    }
1488
-                }
1489
-
1490
-                // pass values down to cid to gid map
1491
-                $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1492
-                break;
1493
-
1494
-            case 'out':
1495
-                $res = "\n$id 0 obj\n";
1496
-                $res .= "<</Type /Font\n";
1497
-                $res .= "/Subtype /CIDFontType2\n";
1498
-                $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1499
-                $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1500
-                //      if (isset($o['info']['FirstChar'])) {
1501
-                //        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1502
-                //      }
1503
-
1504
-                //      if (isset($o['info']['LastChar'])) {
1505
-                //        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1506
-                //      }
1507
-                if (isset($o['info']['FontDescriptor'])) {
1508
-                    $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1509
-                }
1510
-
1511
-                if (isset($o['info']['MissingWidth'])) {
1512
-                    $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1513
-                }
1514
-
1515
-                if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1516
-                    $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1517
-                    $w = '';
1518
-                    foreach ($cid_widths as $cid => $width) {
1519
-                        $w .= "$cid [$width] ";
1520
-                    }
1521
-                    $res .= "/W [$w]\n";
1522
-                }
1523
-
1524
-                $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1525
-                $res .= ">>\n";
1526
-                $res .= "endobj";
1527
-
1528
-                return $res;
1529
-        }
1530
-
1531
-        return null;
1532
-    }
1533
-
1534
-    /**
1535
-     * CID system info section, needed for unicode fonts
1536
-     *
1537
-     * @param $id
1538
-     * @param $action
1539
-     * @return null|string
1540
-     */
1541
-    protected function o_cidSystemInfo($id, $action)
1542
-    {
1543
-        switch ($action) {
1544
-            case 'new':
1545
-                $this->objects[$id] = [
1546
-                    't' => 'cidSystemInfo'
1547
-                ];
1548
-                break;
1549
-            case 'add':
1550
-                break;
1551
-            case 'out':
1552
-                $ordering = 'UCS';
1553
-                $registry = 'Adobe';
1554
-
1555
-                if ($this->encrypted) {
1556
-                    $this->encryptInit($id);
1557
-                    $ordering = $this->ARC4($ordering);
1558
-                    $registry = $this->ARC4($registry);
1559
-                }
1560
-
1561
-
1562
-                $res = "\n$id 0 obj\n";
1563
-
1564
-                $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1565
-                $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1566
-                $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1567
-                $res .= ">>";
1568
-
1569
-                $res .= "\nendobj";
1570
-
1571
-                return $res;
1572
-        }
1573
-
1574
-        return null;
1575
-    }
1576
-
1577
-    /**
1578
-     * a font glyph to character map, needed for unicode fonts
1579
-     *
1580
-     * @param $id
1581
-     * @param $action
1582
-     * @param string $options
1583
-     * @return null|string
1584
-     */
1585
-    protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1586
-    {
1587
-        if ($action !== 'new') {
1588
-            $o = &$this->objects[$id];
1589
-        }
1590
-
1591
-        switch ($action) {
1592
-            case 'new':
1593
-                $this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
1594
-                break;
1595
-
1596
-            case 'out':
1597
-                $res = "\n$id 0 obj\n";
1598
-                $fontFileName = $o['info']['fontFileName'];
1599
-                $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1600
-
1601
-                $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1602
-                    $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1603
-
1604
-                if (!$compressed && isset($o['raw'])) {
1605
-                    $res .= $tmp;
1606
-                } else {
1607
-                    $res .= "<<";
1608
-
1609
-                    if (!$compressed && $this->compressionReady && $this->options['compression']) {
1610
-                        // then implement ZLIB based compression on this content stream
1611
-                        $compressed = true;
1612
-                        $tmp = gzcompress($tmp, 6);
1613
-                    }
1614
-                    if ($compressed) {
1615
-                        $res .= "\n/Filter /FlateDecode";
1616
-                    }
1617
-
1618
-                    if ($this->encrypted) {
1619
-                        $this->encryptInit($id);
1620
-                        $tmp = $this->ARC4($tmp);
1621
-                    }
1622
-
1623
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1624
-                }
1625
-
1626
-                $res .= "\nendobj";
1627
-
1628
-                return $res;
1629
-        }
1630
-
1631
-        return null;
1632
-    }
1633
-
1634
-    /**
1635
-     * the document procset, solves some problems with printing to old PS printers
1636
-     *
1637
-     * @param $id
1638
-     * @param $action
1639
-     * @param string $options
1640
-     * @return null|string
1641
-     */
1642
-    protected function o_procset($id, $action, $options = '')
1643
-    {
1644
-        if ($action !== 'new') {
1645
-            $o = &$this->objects[$id];
1646
-        }
1647
-
1648
-        switch ($action) {
1649
-            case 'new':
1650
-                $this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
1651
-                $this->o_pages($this->currentNode, 'procset', $id);
1652
-                $this->procsetObjectId = $id;
1653
-                break;
1654
-
1655
-            case 'add':
1656
-                // this is to add new items to the procset list, despite the fact that this is considered
1657
-                // obsolete, the items are required for printing to some postscript printers
1658
-                switch ($options) {
1659
-                    case 'ImageB':
1660
-                    case 'ImageC':
1661
-                    case 'ImageI':
1662
-                        $o['info'][$options] = 1;
1663
-                        break;
1664
-                }
1665
-                break;
1666
-
1667
-            case 'out':
1668
-                $res = "\n$id 0 obj\n[";
1669
-                foreach ($o['info'] as $label => $val) {
1670
-                    $res .= "/$label ";
1671
-                }
1672
-                $res .= "]\nendobj";
1673
-
1674
-                return $res;
1675
-        }
1676
-
1677
-        return null;
1678
-    }
1679
-
1680
-    /**
1681
-     * define the document information
1682
-     *
1683
-     * @param $id
1684
-     * @param $action
1685
-     * @param string $options
1686
-     * @return null|string
1687
-     */
1688
-    protected function o_info($id, $action, $options = '')
1689
-    {
1690
-        switch ($action) {
1691
-            case 'new':
1692
-                $this->infoObject = $id;
1693
-                $date = 'D:' . @date('Ymd');
1694
-                $this->objects[$id] = [
1695
-                    't'    => 'info',
1696
-                    'info' => [
1697
-                        'Producer'      => 'CPDF (dompdf)',
1698
-                        'CreationDate' => $date
1699
-                    ]
1700
-                ];
1701
-                break;
1702
-            case 'Title':
1703
-            case 'Author':
1704
-            case 'Subject':
1705
-            case 'Keywords':
1706
-            case 'Creator':
1707
-            case 'Producer':
1708
-            case 'CreationDate':
1709
-            case 'ModDate':
1710
-            case 'Trapped':
1711
-                $this->objects[$id]['info'][$action] = $options;
1712
-                break;
1713
-
1714
-            case 'out':
1715
-                $encrypted = $this->encrypted;
1716
-                if ($encrypted) {
1717
-                    $this->encryptInit($id);
1718
-                }
1719
-
1720
-                $res = "\n$id 0 obj\n<<\n";
1721
-                $o = &$this->objects[$id];
1722
-                foreach ($o['info'] as $k => $v) {
1723
-                    $res .= "/$k (";
1724
-
1725
-                    // dates must be outputted as-is, without Unicode transformations
1726
-                    if ($k !== 'CreationDate' && $k !== 'ModDate') {
1727
-                        $v = $this->utf8toUtf16BE($v);
1728
-                    }
1729
-
1730
-                    if ($encrypted) {
1731
-                        $v = $this->ARC4($v);
1732
-                    }
1733
-
1734
-                    $res .= $this->filterText($v, false, false);
1735
-                    $res .= ")\n";
1736
-                }
1737
-
1738
-                $res .= ">>\nendobj";
1739
-
1740
-                return $res;
1741
-        }
1742
-
1743
-        return null;
1744
-    }
1745
-
1746
-    /**
1747
-     * an action object, used to link to URLS initially
1748
-     *
1749
-     * @param $id
1750
-     * @param $action
1751
-     * @param string $options
1752
-     * @return null|string
1753
-     */
1754
-    protected function o_action($id, $action, $options = '')
1755
-    {
1756
-        if ($action !== 'new') {
1757
-            $o = &$this->objects[$id];
1758
-        }
1759
-
1760
-        switch ($action) {
1761
-            case 'new':
1762
-                if (is_array($options)) {
1763
-                    $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
1764
-                } else {
1765
-                    // then assume a URI action
1766
-                    $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
1767
-                }
1768
-                break;
1769
-
1770
-            case 'out':
1771
-                if ($this->encrypted) {
1772
-                    $this->encryptInit($id);
1773
-                }
1774
-
1775
-                $res = "\n$id 0 obj\n<< /Type /Action";
1776
-                switch ($o['type']) {
1777
-                    case 'ilink':
1778
-                        if (!isset($this->destinations[(string)$o['info']['label']])) {
1779
-                            break;
1780
-                        }
1781
-
1782
-                        // there will be an 'label' setting, this is the name of the destination
1783
-                        $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1784
-                        break;
1785
-
1786
-                    case 'URI':
1787
-                        $res .= "\n/S /URI\n/URI (";
1788
-                        if ($this->encrypted) {
1789
-                            $res .= $this->filterText($this->ARC4($o['info']), false, false);
1790
-                        } else {
1791
-                            $res .= $this->filterText($o['info'], false, false);
1792
-                        }
1793
-
1794
-                        $res .= ")";
1795
-                        break;
1796
-                }
1797
-
1798
-                $res .= "\n>>\nendobj";
1799
-
1800
-                return $res;
1801
-        }
1802
-
1803
-        return null;
1804
-    }
1805
-
1806
-    /**
1807
-     * an annotation object, this will add an annotation to the current page.
1808
-     * initially will support just link annotations
1809
-     *
1810
-     * @param $id
1811
-     * @param $action
1812
-     * @param string $options
1813
-     * @return null|string
1814
-     */
1815
-    protected function o_annotation($id, $action, $options = '')
1816
-    {
1817
-        if ($action !== 'new') {
1818
-            $o = &$this->objects[$id];
1819
-        }
1820
-
1821
-        switch ($action) {
1822
-            case 'new':
1823
-                // add the annotation to the current page
1824
-                $pageId = $this->currentPage;
1825
-                $this->o_page($pageId, 'annot', $id);
1826
-
1827
-                // and add the action object which is going to be required
1828
-                switch ($options['type']) {
1829
-                    case 'link':
1830
-                        $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1831
-                        $this->numObj++;
1832
-                        $this->o_action($this->numObj, 'new', $options['url']);
1833
-                        $this->objects[$id]['info']['actionId'] = $this->numObj;
1834
-                        break;
1835
-
1836
-                    case 'ilink':
1837
-                        // this is to a named internal link
1838
-                        $label = $options['label'];
1839
-                        $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1840
-                        $this->numObj++;
1841
-                        $this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
1842
-                        $this->objects[$id]['info']['actionId'] = $this->numObj;
1843
-                        break;
1844
-                }
1845
-                break;
1846
-
1847
-            case 'out':
1848
-                $res = "\n$id 0 obj\n<< /Type /Annot";
1849
-                switch ($o['info']['type']) {
1850
-                    case 'link':
1851
-                    case 'ilink':
1852
-                        $res .= "\n/Subtype /Link";
1853
-                        break;
1854
-                }
1855
-                $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1856
-                $res .= "\n/Border [0 0 0]";
1857
-                $res .= "\n/H /I";
1858
-                $res .= "\n/Rect [ ";
1859
-
1860
-                foreach ($o['info']['rect'] as $v) {
1861
-                    $res .= sprintf("%.4F ", $v);
1862
-                }
1863
-
1864
-                $res .= "]";
1865
-                $res .= "\n>>\nendobj";
1866
-
1867
-                return $res;
1868
-        }
1869
-
1870
-        return null;
1871
-    }
1872
-
1873
-    /**
1874
-     * a page object, it also creates a contents object to hold its contents
1875
-     *
1876
-     * @param $id
1877
-     * @param $action
1878
-     * @param string $options
1879
-     * @return null|string
1880
-     */
1881
-    protected function o_page($id, $action, $options = '')
1882
-    {
1883
-        if ($action !== 'new') {
1884
-            $o = &$this->objects[$id];
1885
-        }
1886
-
1887
-        switch ($action) {
1888
-            case 'new':
1889
-                $this->numPages++;
1890
-                $this->objects[$id] = [
1891
-                    't'    => 'page',
1892
-                    'info' => [
1893
-                        'parent'  => $this->currentNode,
1894
-                        'pageNum' => $this->numPages,
1895
-                        'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1896
-                    ]
1897
-                ];
1898
-
1899
-                if (is_array($options)) {
1900
-                    // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1901
-                    $options['id'] = $id;
1902
-                    $this->o_pages($this->currentNode, 'page', $options);
1903
-                } else {
1904
-                    $this->o_pages($this->currentNode, 'page', $id);
1905
-                }
1906
-
1907
-                $this->currentPage = $id;
1908
-                //make a contents object to go with this page
1909
-                $this->numObj++;
1910
-                $this->o_contents($this->numObj, 'new', $id);
1911
-                $this->currentContents = $this->numObj;
1912
-                $this->objects[$id]['info']['contents'] = [];
1913
-                $this->objects[$id]['info']['contents'][] = $this->numObj;
1914
-
1915
-                $match = ($this->numPages % 2 ? 'odd' : 'even');
1916
-                foreach ($this->addLooseObjects as $oId => $target) {
1917
-                    if ($target === 'all' || $match === $target) {
1918
-                        $this->objects[$id]['info']['contents'][] = $oId;
1919
-                    }
1920
-                }
1921
-                break;
1922
-
1923
-            case 'content':
1924
-                $o['info']['contents'][] = $options;
1925
-                break;
1926
-
1927
-            case 'annot':
1928
-                // add an annotation to this page
1929
-                if (!isset($o['info']['annot'])) {
1930
-                    $o['info']['annot'] = [];
1931
-                }
1932
-
1933
-                // $options should contain the id of the annotation dictionary
1934
-                $o['info']['annot'][] = $options;
1935
-                break;
1936
-
1937
-            case 'out':
1938
-                $res = "\n$id 0 obj\n<< /Type /Page";
1939
-                if (isset($o['info']['mediaBox'])) {
1940
-                    $tmp = $o['info']['mediaBox'];
1941
-                    $res .= "\n/MediaBox [" . sprintf(
1942
-                            '%.3F %.3F %.3F %.3F',
1943
-                            $tmp[0],
1944
-                            $tmp[1],
1945
-                            $tmp[2],
1946
-                            $tmp[3]
1947
-                        ) . ']';
1948
-                }
1949
-                $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1950
-
1951
-                if (isset($o['info']['annot'])) {
1952
-                    $res .= "\n/Annots [";
1953
-                    foreach ($o['info']['annot'] as $aId) {
1954
-                        $res .= " $aId 0 R";
1955
-                    }
1956
-                    $res .= " ]";
1957
-                }
1958
-
1959
-                $count = count($o['info']['contents']);
1960
-                if ($count == 1) {
1961
-                    $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1962
-                } else {
1963
-                    if ($count > 1) {
1964
-                        $res .= "\n/Contents [\n";
1965
-
1966
-                        // reverse the page contents so added objects are below normal content
1967
-                        //foreach (array_reverse($o['info']['contents']) as $cId) {
1968
-                        // Back to normal now that I've got transparency working --Benj
1969
-                        foreach ($o['info']['contents'] as $cId) {
1970
-                            $res .= "$cId 0 R\n";
1971
-                        }
1972
-                        $res .= "]";
1973
-                    }
1974
-                }
1975
-
1976
-                $res .= "\n>>\nendobj";
1977
-
1978
-                return $res;
1979
-        }
1980
-
1981
-        return null;
1982
-    }
1983
-
1984
-    /**
1985
-     * the contents objects hold all of the content which appears on pages
1986
-     *
1987
-     * @param $id
1988
-     * @param $action
1989
-     * @param string|array $options
1990
-     * @return null|string
1991
-     */
1992
-    protected function o_contents($id, $action, $options = '')
1993
-    {
1994
-        if ($action !== 'new') {
1995
-            $o = &$this->objects[$id];
1996
-        }
1997
-
1998
-        switch ($action) {
1999
-            case 'new':
2000
-                $this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
2001
-                if (mb_strlen($options, '8bit') && intval($options)) {
2002
-                    // then this contents is the primary for a page
2003
-                    $this->objects[$id]['onPage'] = $options;
2004
-                } else {
2005
-                    if ($options === 'raw') {
2006
-                        // then this page contains some other type of system object
2007
-                        $this->objects[$id]['raw'] = 1;
2008
-                    }
2009
-                }
2010
-                break;
2011
-
2012
-            case 'add':
2013
-                // add more options to the declaration
2014
-                foreach ($options as $k => $v) {
2015
-                    $o['info'][$k] = $v;
2016
-                }
2017
-
2018
-            case 'out':
2019
-                $tmp = $o['c'];
2020
-                $res = "\n$id 0 obj\n";
2021
-
2022
-                if (isset($this->objects[$id]['raw'])) {
2023
-                    $res .= $tmp;
2024
-                } else {
2025
-                    $res .= "<<";
2026
-                    if ($this->compressionReady && $this->options['compression']) {
2027
-                        // then implement ZLIB based compression on this content stream
2028
-                        $res .= " /Filter /FlateDecode";
2029
-                        $tmp = gzcompress($tmp, 6);
2030
-                    }
2031
-
2032
-                    if ($this->encrypted) {
2033
-                        $this->encryptInit($id);
2034
-                        $tmp = $this->ARC4($tmp);
2035
-                    }
2036
-
2037
-                    foreach ($o['info'] as $k => $v) {
2038
-                        $res .= "\n/$k $v";
2039
-                    }
2040
-
2041
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2042
-                }
2043
-
2044
-                $res .= "\nendobj";
2045
-
2046
-                return $res;
2047
-        }
2048
-
2049
-        return null;
2050
-    }
2051
-
2052
-    /**
2053
-     * @param $id
2054
-     * @param $action
2055
-     * @return string|null
2056
-     */
2057
-    protected function o_embedjs($id, $action)
2058
-    {
2059
-        switch ($action) {
2060
-            case 'new':
2061
-                $this->objects[$id] = [
2062
-                    't'    => 'embedjs',
2063
-                    'info' => [
2064
-                        'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2065
-                    ]
2066
-                ];
2067
-                break;
2068
-
2069
-            case 'out':
2070
-                $o = &$this->objects[$id];
2071
-                $res = "\n$id 0 obj\n<< ";
2072
-                foreach ($o['info'] as $k => $v) {
2073
-                    $res .= "\n/$k $v";
2074
-                }
2075
-                $res .= "\n>>\nendobj";
2076
-
2077
-                return $res;
2078
-        }
2079
-
2080
-        return null;
2081
-    }
2082
-
2083
-    /**
2084
-     * @param $id
2085
-     * @param $action
2086
-     * @param string $code
2087
-     * @return null|string
2088
-     */
2089
-    protected function o_javascript($id, $action, $code = '')
2090
-    {
2091
-        switch ($action) {
2092
-            case 'new':
2093
-                $this->objects[$id] = [
2094
-                    't'    => 'javascript',
2095
-                    'info' => [
2096
-                        'S'  => '/JavaScript',
2097
-                        'JS' => '(' . $this->filterText($code, true, false) . ')',
2098
-                    ]
2099
-                ];
2100
-                break;
2101
-
2102
-            case 'out':
2103
-                $o = &$this->objects[$id];
2104
-                $res = "\n$id 0 obj\n<< ";
2105
-
2106
-                foreach ($o['info'] as $k => $v) {
2107
-                    $res .= "\n/$k $v";
2108
-                }
2109
-                $res .= "\n>>\nendobj";
2110
-
2111
-                return $res;
2112
-        }
2113
-
2114
-        return null;
2115
-    }
2116
-
2117
-    /**
2118
-     * an image object, will be an XObject in the document, includes description and data
2119
-     *
2120
-     * @param $id
2121
-     * @param $action
2122
-     * @param string $options
2123
-     * @return null|string
2124
-     */
2125
-    protected function o_image($id, $action, $options = '')
2126
-    {
2127
-        switch ($action) {
2128
-            case 'new':
2129
-                // make the new object
2130
-                $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2131
-
2132
-                $info =& $this->objects[$id]['info'];
2133
-
2134
-                $info['Type'] = '/XObject';
2135
-                $info['Subtype'] = '/Image';
2136
-                $info['Width'] = $options['iw'];
2137
-                $info['Height'] = $options['ih'];
2138
-
2139
-                if (isset($options['masked']) && $options['masked']) {
2140
-                    $info['SMask'] = ($this->numObj - 1) . ' 0 R';
2141
-                }
2142
-
2143
-                if (!isset($options['type']) || $options['type'] === 'jpg') {
2144
-                    if (!isset($options['channels'])) {
2145
-                        $options['channels'] = 3;
2146
-                    }
2147
-
2148
-                    switch ($options['channels']) {
2149
-                        case 1:
2150
-                            $info['ColorSpace'] = '/DeviceGray';
2151
-                            break;
2152
-                        case 4:
2153
-                            $info['ColorSpace'] = '/DeviceCMYK';
2154
-                            break;
2155
-                        default:
2156
-                            $info['ColorSpace'] = '/DeviceRGB';
2157
-                            break;
2158
-                    }
2159
-
2160
-                    if ($info['ColorSpace'] === '/DeviceCMYK') {
2161
-                        $info['Decode'] = '[1 0 1 0 1 0 1 0]';
2162
-                    }
2163
-
2164
-                    $info['Filter'] = '/DCTDecode';
2165
-                    $info['BitsPerComponent'] = 8;
2166
-                } else {
2167
-                    if ($options['type'] === 'png') {
2168
-                        $info['Filter'] = '/FlateDecode';
2169
-                        $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2170
-
2171
-                        if ($options['isMask']) {
2172
-                            $info['ColorSpace'] = '/DeviceGray';
2173
-                        } else {
2174
-                            if (mb_strlen($options['pdata'], '8bit')) {
2175
-                                $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2176
-                                $this->numObj++;
2177
-                                $this->o_contents($this->numObj, 'new');
2178
-                                $this->objects[$this->numObj]['c'] = $options['pdata'];
2179
-                                $tmp .= $this->numObj . ' 0 R';
2180
-                                $tmp .= ' ]';
2181
-                                $info['ColorSpace'] = $tmp;
2182
-
2183
-                                if (isset($options['transparency'])) {
2184
-                                    $transparency = $options['transparency'];
2185
-                                    switch ($transparency['type']) {
2186
-                                        case 'indexed':
2187
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2188
-                                            $info['Mask'] = $tmp;
2189
-                                            break;
2190
-
2191
-                                        case 'color-key':
2192
-                                            $tmp = ' [ ' .
2193
-                                                $transparency['r'] . ' ' . $transparency['r'] .
2194
-                                                $transparency['g'] . ' ' . $transparency['g'] .
2195
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2196
-                                                ' ] ';
2197
-                                            $info['Mask'] = $tmp;
2198
-                                            break;
2199
-                                    }
2200
-                                }
2201
-                            } else {
2202
-                                if (isset($options['transparency'])) {
2203
-                                    $transparency = $options['transparency'];
2204
-
2205
-                                    switch ($transparency['type']) {
2206
-                                        case 'indexed':
2207
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2208
-                                            $info['Mask'] = $tmp;
2209
-                                            break;
2210
-
2211
-                                        case 'color-key':
2212
-                                            $tmp = ' [ ' .
2213
-                                                $transparency['r'] . ' ' . $transparency['r'] . ' ' .
2214
-                                                $transparency['g'] . ' ' . $transparency['g'] . ' ' .
2215
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2216
-                                                ' ] ';
2217
-                                            $info['Mask'] = $tmp;
2218
-                                            break;
2219
-                                    }
2220
-                                }
2221
-                                $info['ColorSpace'] = '/' . $options['color'];
2222
-                            }
2223
-                        }
2224
-
2225
-                        $info['BitsPerComponent'] = $options['bitsPerComponent'];
2226
-                    }
2227
-                }
2228
-
2229
-                // assign it a place in the named resource dictionary as an external object, according to
2230
-                // the label passed in with it.
2231
-                $this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
2232
-
2233
-                // also make sure that we have the right procset object for it.
2234
-                $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
2235
-                break;
2236
-
2237
-            case 'out':
2238
-                $o = &$this->objects[$id];
2239
-                $tmp = &$o['data'];
2240
-                $res = "\n$id 0 obj\n<<";
2241
-
2242
-                foreach ($o['info'] as $k => $v) {
2243
-                    $res .= "\n/$k $v";
2244
-                }
2245
-
2246
-                if ($this->encrypted) {
2247
-                    $this->encryptInit($id);
2248
-                    $tmp = $this->ARC4($tmp);
2249
-                }
2250
-
2251
-                $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2252
-
2253
-                return $res;
2254
-        }
2255
-
2256
-        return null;
2257
-    }
2258
-
2259
-    /**
2260
-     * graphics state object
2261
-     *
2262
-     * @param $id
2263
-     * @param $action
2264
-     * @param string $options
2265
-     * @return null|string
2266
-     */
2267
-    protected function o_extGState($id, $action, $options = "")
2268
-    {
2269
-        static $valid_params = [
2270
-            "LW",
2271
-            "LC",
2272
-            "LC",
2273
-            "LJ",
2274
-            "ML",
2275
-            "D",
2276
-            "RI",
2277
-            "OP",
2278
-            "op",
2279
-            "OPM",
2280
-            "Font",
2281
-            "BG",
2282
-            "BG2",
2283
-            "UCR",
2284
-            "TR",
2285
-            "TR2",
2286
-            "HT",
2287
-            "FL",
2288
-            "SM",
2289
-            "SA",
2290
-            "BM",
2291
-            "SMask",
2292
-            "CA",
2293
-            "ca",
2294
-            "AIS",
2295
-            "TK"
2296
-        ];
2297
-
2298
-        switch ($action) {
2299
-            case "new":
2300
-                $this->objects[$id] = ['t' => 'extGState', 'info' => $options];
2301
-
2302
-                // Tell the pages about the new resource
2303
-                $this->numStates++;
2304
-                $this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
2305
-                break;
2306
-
2307
-            case "out":
2308
-                $o = &$this->objects[$id];
2309
-                $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2310
-
2311
-                foreach ($o["info"] as $k => $v) {
2312
-                    if (!in_array($k, $valid_params)) {
2313
-                        continue;
2314
-                    }
2315
-                    $res .= "/$k $v\n";
2316
-                }
2317
-
2318
-                $res .= ">>\nendobj";
2319
-
2320
-                return $res;
2321
-        }
2322
-
2323
-        return null;
2324
-    }
2325
-
2326
-    /**
2327
-     * @param integer $id
2328
-     * @param string $action
2329
-     * @param mixed $options
2330
-     * @return string
2331
-     */
2332
-    protected function o_xobject($id, $action, $options = '')
2333
-    {
2334
-        switch ($action) {
2335
-            case 'new':
2336
-                $this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
2337
-                break;
2338
-
2339
-            case 'procset':
2340
-                $this->objects[$id]['procset'] = $options;
2341
-                break;
2342
-
2343
-            case 'font':
2344
-                $this->objects[$id]['fonts'][$options['fontNum']] = [
2345
-                  'objNum' => $options['objNum'],
2346
-                  'fontNum' => $options['fontNum']
2347
-                ];
2348
-                break;
2349
-
2350
-            case 'xObject':
2351
-                $this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
2352
-                break;
2353
-
2354
-            case 'out':
2355
-                $o = &$this->objects[$id];
2356
-                $res = "\n$id 0 obj\n<< /Type /XObject\n";
2357
-
2358
-                foreach ($o["info"] as $k => $v) {
2359
-                    switch ($k) {
2360
-                        case 'Subtype':
2361
-                            $res .= "/Subtype /$v\n";
2362
-                            break;
2363
-                        case 'bbox':
2364
-                            $res .= "/BBox [";
2365
-                            foreach ($v as $value) {
2366
-                                $res .= sprintf("%.4F ", $value);
2367
-                            }
2368
-                            $res .= "]\n";
2369
-                            break;
2370
-                        default:
2371
-                            $res .= "/$k $v\n";
2372
-                            break;
2373
-                    }
2374
-                }
2375
-                $res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
2376
-
2377
-                $res .= "/Resources <<";
2378
-                if (isset($o['procset'])) {
2379
-                    $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2380
-                } else {
2381
-                    $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2382
-                }
2383
-                if (isset($o['fonts']) && count($o['fonts'])) {
2384
-                    $res .= "\n/Font << ";
2385
-                    foreach ($o['fonts'] as $finfo) {
2386
-                        $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2387
-                    }
2388
-                    $res .= "\n>>";
2389
-                }
2390
-                if (isset($o['xObjects']) && count($o['xObjects'])) {
2391
-                    $res .= "\n/XObject << ";
2392
-                    foreach ($o['xObjects'] as $finfo) {
2393
-                        $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2394
-                    }
2395
-                    $res .= "\n>>";
2396
-                }
2397
-                $res .= "\n>>\n";
2398
-
2399
-                $tmp = $o["c"];
2400
-                if ($this->compressionReady && $this->options['compression']) {
2401
-                    // then implement ZLIB based compression on this content stream
2402
-                    $res .= " /Filter /FlateDecode\n";
2403
-                    $tmp = gzcompress($tmp, 6);
2404
-                }
2405
-
2406
-                if ($this->encrypted) {
2407
-                    $this->encryptInit($id);
2408
-                    $tmp = $this->ARC4($tmp);
2409
-                }
2410
-
2411
-                $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2412
-                $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2413
-
2414
-                return $res;
2415
-        }
2416
-
2417
-        return null;
2418
-    }
2419
-
2420
-    /**
2421
-     * @param $id
2422
-     * @param $action
2423
-     * @param string $options
2424
-     * @return null|string
2425
-     */
2426
-    protected function o_acroform($id, $action, $options = '')
2427
-    {
2428
-        switch ($action) {
2429
-            case "new":
2430
-                $this->o_catalog($this->catalogId, 'acroform', $id);
2431
-                $this->objects[$id] = array('t' => 'acroform', 'info' => $options);
2432
-                break;
2433
-
2434
-            case 'addfield':
2435
-                $this->objects[$id]['info']['Fields'][] = $options;
2436
-                break;
2437
-
2438
-            case 'font':
2439
-                $this->objects[$id]['fonts'][$options['fontNum']] = [
2440
-                  'objNum' => $options['objNum'],
2441
-                  'fontNum' => $options['fontNum']
2442
-                ];
2443
-                break;
2444
-
2445
-            case "out":
2446
-                $o = &$this->objects[$id];
2447
-                $res = "\n$id 0 obj\n<<";
2448
-
2449
-                foreach ($o["info"] as $k => $v) {
2450
-                    switch ($k) {
2451
-                        case 'Fields':
2452
-                            $res .= " /Fields [";
2453
-                            foreach ($v as $i) {
2454
-                                $res .= "$i 0 R ";
2455
-                            }
2456
-                            $res .= "]\n";
2457
-                            break;
2458
-                        default:
2459
-                            $res .= "/$k $v\n";
2460
-                    }
2461
-                }
2462
-
2463
-                $res .= "/DR <<\n";
2464
-                if (isset($o['fonts']) && count($o['fonts'])) {
2465
-                    $res .= "/Font << \n";
2466
-                    foreach ($o['fonts'] as $finfo) {
2467
-                        $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2468
-                    }
2469
-                    $res .= ">>\n";
2470
-                }
2471
-                $res .= ">>\n";
2472
-
2473
-                $res .= ">>\nendobj";
2474
-
2475
-                return $res;
2476
-        }
2477
-
2478
-        return null;
2479
-    }
2480
-
2481
-    /**
2482
-     * @param $id
2483
-     * @param $action
2484
-     * @param mixed $options
2485
-     * @return null|string
2486
-     */
2487
-    protected function o_field($id, $action, $options = '')
2488
-    {
2489
-        switch ($action) {
2490
-            case "new":
2491
-                $this->o_page($options['pageid'], 'annot', $id);
2492
-                $this->o_acroform($this->acroFormId, 'addfield', $id);
2493
-                $this->objects[$id] = ['t' => 'field', 'info' => $options];
2494
-                break;
2495
-
2496
-            case 'set':
2497
-                $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2498
-                break;
2499
-
2500
-            case "out":
2501
-                $o = &$this->objects[$id];
2502
-                $res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
2503
-
2504
-                $encrypted = $this->encrypted;
2505
-                if ($encrypted) {
2506
-                    $this->encryptInit($id);
2507
-                }
2508
-
2509
-                foreach ($o["info"] as $k => $v) {
2510
-                    switch ($k) {
2511
-                        case 'pageid':
2512
-                            $res .= "/P $v 0 R\n";
2513
-                            break;
2514
-                        case 'value':
2515
-                            if ($encrypted) {
2516
-                                $v = $this->filterText($this->ARC4($v), false, false);
2517
-                            }
2518
-                            $res .= "/V ($v)\n";
2519
-                            break;
2520
-                        case 'refvalue':
2521
-                            $res .= "/V $v 0 R\n";
2522
-                            break;
2523
-                        case 'da':
2524
-                            if ($encrypted) {
2525
-                                $v = $this->filterText($this->ARC4($v), false, false);
2526
-                            }
2527
-                            $res .= "/DA ($v)\n";
2528
-                            break;
2529
-                        case 'options':
2530
-                            $res .= "/Opt [\n";
2531
-                            foreach ($v as $opt) {
2532
-                                if ($encrypted) {
2533
-                                    $opt = $this->filterText($this->ARC4($opt), false, false);
2534
-                                }
2535
-                                $res .= "($opt)\n";
2536
-                            }
2537
-                            $res .= "]\n";
2538
-                            break;
2539
-                        case 'rect':
2540
-                            $res .= "/Rect [";
2541
-                            foreach ($v as $value) {
2542
-                                $res .= sprintf("%.4F ", $value);
2543
-                            }
2544
-                            $res .= "]\n";
2545
-                            break;
2546
-                        case 'appearance':
2547
-                            $res .= "/AP << ";
2548
-                            foreach ($v as $a => $ref) {
2549
-                                $res .= "/$a $ref 0 R ";
2550
-                            }
2551
-                            $res .= ">>\n";
2552
-                            break;
2553
-                        case 'T':
2554
-                            if ($encrypted) {
2555
-                                $v = $this->filterText($this->ARC4($v), false, false);
2556
-                            }
2557
-                            $res .= "/T ($v)\n";
2558
-                            break;
2559
-                        default:
2560
-                            $res .= "/$k $v\n";
2561
-                    }
2562
-
2563
-                }
2564
-
2565
-                $res .= ">>\nendobj";
2566
-
2567
-                return $res;
2568
-        }
2569
-
2570
-        return null;
2571
-    }
2572
-
2573
-    /**
2574
-     *
2575
-     * @param $id
2576
-     * @param $action
2577
-     * @param string $options
2578
-     * @return null|string
2579
-     */
2580
-    protected function o_sig($id, $action, $options = '')
2581
-    {
2582
-        $sign_maxlen = $this->signatureMaxLen;
2583
-
2584
-        switch ($action) {
2585
-            case "new":
2586
-                $this->objects[$id] = array('t' => 'sig', 'info' => $options);
2587
-                $this->byteRange[$id] = ['t' => 'sig'];
2588
-                break;
2589
-
2590
-            case 'byterange':
2591
-                $o = &$this->objects[$id];
2592
-                $content =& $options['content'];
2593
-                $content_len = strlen($content);
2594
-                $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2595
-                $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2596
-                $rangeStartPos = $pos + $len + 1 + 10; // before '<'
2597
-                $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2598
-
2599
-                $fuid = uniqid();
2600
-                $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2601
-                $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2602
-
2603
-                if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2604
-                    throw new \Exception("Unable to write temporary file for signing.");
2605
-                }
2606
-                if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
2607
-                    FILE_APPEND) === false) {
2608
-                    throw new \Exception("Unable to write temporary file for signing.");
2609
-                }
2610
-
2611
-                if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
2612
-                    $o['info']['SignCert'],
2613
-                    array($o['info']['PrivKey'], $o['info']['Password']),
2614
-                    array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
2615
-                    throw new \Exception("Failed to prepare signature.");
2616
-                }
2617
-
2618
-                $signature = file_get_contents($tmpOutput);
2619
-
2620
-                unlink($tmpInput);
2621
-                unlink($tmpOutput);
2622
-
2623
-                $sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
2624
-                list($head, $signature) = explode("\n\n", $sign);
2625
-
2626
-                $signature = base64_decode(trim($signature));
2627
-
2628
-                $signature = current(unpack('H*', $signature));
2629
-                $signature = str_pad($signature, $sign_maxlen, '0');
2630
-                $siglen = strlen($signature);
2631
-                if (strlen($signature) > $sign_maxlen) {
2632
-                    throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
2633
-                }
2634
-
2635
-                $content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
2636
-                break;
2637
-
2638
-            case "out":
2639
-                $res = "\n$id 0 obj\n<<\n";
2640
-
2641
-                $encrypted = $this->encrypted;
2642
-                if ($encrypted) {
2643
-                    $this->encryptInit($id);
2644
-                }
2645
-
2646
-                $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2647
-                $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2648
-                $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2649
-                $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2650
-
2651
-                $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2652
-                if ($encrypted) {
2653
-                    $date = $this->ARC4($date);
2654
-                }
2655
-
2656
-                $res .= "/M ($date)\n";
2657
-                $res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
2658
-
2659
-                $o = &$this->objects[$id];
2660
-                foreach ($o['info'] as $k => $v) {
2661
-                    switch ($k) {
2662
-                        case 'Name':
2663
-                        case 'Location':
2664
-                        case 'Reason':
2665
-                        case 'ContactInfo':
2666
-                            if ($v !== null && $v !== '') {
2667
-                                $res .= "/$k (" .
2668
-                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2669
-                            }
2670
-                            break;
2671
-                    }
2672
-                }
2673
-                $res .= ">>\nendobj";
2674
-
2675
-                return $res;
2676
-        }
2677
-
2678
-        return null;
2679
-    }
2680
-
2681
-    /**
2682
-     * encryption object.
2683
-     *
2684
-     * @param $id
2685
-     * @param $action
2686
-     * @param string $options
2687
-     * @return string|null
2688
-     */
2689
-    protected function o_encryption($id, $action, $options = '')
2690
-    {
2691
-        switch ($action) {
2692
-            case 'new':
2693
-                // make the new object
2694
-                $this->objects[$id] = ['t' => 'encryption', 'info' => $options];
2695
-                $this->arc4_objnum = $id;
2696
-                break;
2697
-
2698
-            case 'keys':
2699
-                // figure out the additional parameters required
2700
-                $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2701
-                    . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2702
-                    . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2703
-                    . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2704
-
2705
-                $info = $this->objects[$id]['info'];
2706
-
2707
-                $len = mb_strlen($info['owner'], '8bit');
2708
-
2709
-                if ($len > 32) {
2710
-                    $owner = substr($info['owner'], 0, 32);
2711
-                } else {
2712
-                    if ($len < 32) {
2713
-                        $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2714
-                    } else {
2715
-                        $owner = $info['owner'];
2716
-                    }
2717
-                }
2718
-
2719
-                $len = mb_strlen($info['user'], '8bit');
2720
-                if ($len > 32) {
2721
-                    $user = substr($info['user'], 0, 32);
2722
-                } else {
2723
-                    if ($len < 32) {
2724
-                        $user = $info['user'] . substr($pad, 0, 32 - $len);
2725
-                    } else {
2726
-                        $user = $info['user'];
2727
-                    }
2728
-                }
2729
-
2730
-                $tmp = $this->md5_16($owner);
2731
-                $okey = substr($tmp, 0, 5);
2732
-                $this->ARC4_init($okey);
2733
-                $ovalue = $this->ARC4($user);
2734
-                $this->objects[$id]['info']['O'] = $ovalue;
2735
-
2736
-                // now make the u value, phew.
2737
-                $tmp = $this->md5_16(
2738
-                    $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2739
-                );
2740
-
2741
-                $ukey = substr($tmp, 0, 5);
2742
-                $this->ARC4_init($ukey);
2743
-                $this->encryptionKey = $ukey;
2744
-                $this->encrypted = true;
2745
-                $uvalue = $this->ARC4($pad);
2746
-                $this->objects[$id]['info']['U'] = $uvalue;
2747
-                // initialize the arc4 array
2748
-                break;
2749
-
2750
-            case 'out':
2751
-                $o = &$this->objects[$id];
2752
-
2753
-                $res = "\n$id 0 obj\n<<";
2754
-                $res .= "\n/Filter /Standard";
2755
-                $res .= "\n/V 1";
2756
-                $res .= "\n/R 2";
2757
-                $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2758
-                $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2759
-                // and the p-value needs to be converted to account for the twos-complement approach
2760
-                $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2761
-                $res .= "\n/P " . ($o['info']['p']);
2762
-                $res .= "\n>>\nendobj";
2763
-
2764
-                return $res;
2765
-        }
2766
-
2767
-        return null;
2768
-    }
2769
-
2770
-    protected function o_indirect_references($id, $action, $options = null)
2771
-    {
2772
-        switch ($action) {
2773
-            case 'new':
2774
-            case 'add':
2775
-                if ($id === 0) {
2776
-                    $id = ++$this->numObj;
2777
-                    $this->o_catalog($this->catalogId, 'names', $id);
2778
-                    $this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
2779
-                    $this->indirectReferenceId = $id;
2780
-                } else {
2781
-                    $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2782
-                }
2783
-                break;
2784
-            case 'out':
2785
-                $res = "\n$id 0 obj << ";
2786
-
2787
-                foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2788
-                    $res .= "/$referenceObjName $referenceObjId 0 R ";
2789
-                }
2790
-
2791
-                $res .= ">> endobj";
2792
-                return $res;
2793
-        }
2794
-
2795
-        return null;
2796
-    }
2797
-
2798
-    protected function o_names($id, $action, $options = null)
2799
-    {
2800
-        switch ($action) {
2801
-            case 'new':
2802
-            case 'add':
2803
-                if ($id === 0) {
2804
-                    $id = ++$this->numObj;
2805
-                    $this->objects[$id] = ['t' => 'names', 'info' => [$options]];
2806
-                    $this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
2807
-                    $this->embeddedFilesId = $id;
2808
-                } else {
2809
-                    $this->objects[$id]['info'][] = $options;
2810
-                }
2811
-                break;
2812
-            case 'out':
2813
-                $info = &$this->objects[$id]['info'];
2814
-                $res = '';
2815
-                if (count($info) > 0) {
2816
-                    $res = "\n$id 0 obj << /Names [ ";
2817
-
2818
-                    if ($this->encrypted) {
2819
-                        $this->encryptInit($id);
2820
-                    }
2821
-
2822
-                    foreach ($info as $entry) {
2823
-                        if ($this->encrypted) {
2824
-                            $filename = $this->ARC4($entry['filename']);
2825
-                        } else {
2826
-                            $filename = $entry['filename'];
2827
-                        }
2828
-
2829
-                        $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2830
-                    }
2831
-
2832
-                    $res .= "] >> endobj";
2833
-                }
2834
-                return $res;
2835
-        }
2836
-
2837
-        return null;
2838
-    }
2839
-
2840
-    protected function o_embedded_file_dictionary($id, $action, $options = null)
2841
-    {
2842
-        switch ($action) {
2843
-            case 'new':
2844
-                $embeddedFileId = ++$this->numObj;
2845
-                $options['embedded_reference'] = $embeddedFileId;
2846
-                $this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
2847
-                $this->o_embedded_file($embeddedFileId, 'new', $options);
2848
-                $options['dict_reference'] = $id;
2849
-                $this->o_names($this->embeddedFilesId, 'add', $options);
2850
-                break;
2851
-            case 'out':
2852
-                $info = &$this->objects[$id]['info'];
2853
-                $filename = $this->utf8toUtf16BE($info['filename']);
2854
-                $description = $this->utf8toUtf16BE($info['description']);
2855
-
2856
-                if ($this->encrypted) {
2857
-                    $this->encryptInit($id);
2858
-                    $filename = $this->ARC4($filename);
2859
-                    $description = $this->ARC4($description);
2860
-                }
2861
-
2862
-                $filename = $this->filterText($filename, false, false);
2863
-                $description = $this->filterText($description, false, false);
2864
-
2865
-                $res = "\n$id 0 obj <</Type /Filespec /EF";
2866
-                $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2867
-                $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2868
-                $res .= " >> endobj";
2869
-                return $res;
2870
-        }
2871
-
2872
-        return null;
2873
-    }
2874
-
2875
-    protected function o_embedded_file($id, $action, $options = null): ?string
2876
-    {
2877
-        switch ($action) {
2878
-            case 'new':
2879
-                $this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
2880
-                break;
2881
-            case 'out':
2882
-                $info = &$this->objects[$id]['info'];
2883
-
2884
-                if ($this->compressionReady) {
2885
-                    $filepath = $info['filepath'];
2886
-                    $checksum = md5_file($filepath);
2887
-                    $f = fopen($filepath, "rb");
2888
-
2889
-                    $file_content_compressed = '';
2890
-                    $deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
2891
-                    while (($block = fread($f, 8192))) {
2892
-                        $file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
2893
-                    }
2894
-                    $file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
2895
-                    $file_size_uncompressed = ftell($f);
2896
-                    fclose($f);
2897
-                } else {
2898
-                    $file_content = file_get_contents($info['filepath']);
2899
-                    $file_size_uncompressed = mb_strlen($file_content, '8bit');
2900
-                    $checksum = md5($file_content);
2901
-                }
2902
-
2903
-                if ($this->encrypted) {
2904
-                    $this->encryptInit($id);
2905
-                    $checksum = $this->ARC4($checksum);
2906
-                    $file_content_compressed = $this->ARC4($file_content_compressed);
2907
-                }
2908
-                $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2909
-
2910
-                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2911
-                    " /Type/EmbeddedFile /Filter/FlateDecode" .
2912
-                    " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2913
-
2914
-                return $res;
2915
-        }
2916
-
2917
-        return null;
2918
-    }
2919
-
2920
-    /**
2921
-     * ARC4 functions
2922
-     * A series of function to implement ARC4 encoding in PHP
2923
-     */
2924
-
2925
-    /**
2926
-     * calculate the 16 byte version of the 128 bit md5 digest of the string
2927
-     *
2928
-     * @param $string
2929
-     * @return string
2930
-     */
2931
-    function md5_16($string)
2932
-    {
2933
-        $tmp = md5($string);
2934
-        $out = '';
2935
-        for ($i = 0; $i <= 30; $i = $i + 2) {
2936
-            $out .= chr(hexdec(substr($tmp, $i, 2)));
2937
-        }
2938
-
2939
-        return $out;
2940
-    }
2941
-
2942
-    /**
2943
-     * initialize the encryption for processing a particular object
2944
-     *
2945
-     * @param $id
2946
-     */
2947
-    function encryptInit($id)
2948
-    {
2949
-        $tmp = $this->encryptionKey;
2950
-        $hex = dechex($id);
2951
-        if (mb_strlen($hex, '8bit') < 6) {
2952
-            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2953
-        }
2954
-        $tmp .= chr(hexdec(substr($hex, 4, 2)))
2955
-            . chr(hexdec(substr($hex, 2, 2)))
2956
-            . chr(hexdec(substr($hex, 0, 2)))
2957
-            . chr(0)
2958
-            . chr(0)
2959
-        ;
2960
-        $key = $this->md5_16($tmp);
2961
-        $this->ARC4_init(substr($key, 0, 10));
2962
-    }
2963
-
2964
-    /**
2965
-     * initialize the ARC4 encryption
2966
-     *
2967
-     * @param string $key
2968
-     */
2969
-    function ARC4_init($key = '')
2970
-    {
2971
-        $this->arc4 = '';
2972
-
2973
-        // setup the control array
2974
-        if (mb_strlen($key, '8bit') == 0) {
2975
-            return;
2976
-        }
2977
-
2978
-        $k = '';
2979
-        while (mb_strlen($k, '8bit') < 256) {
2980
-            $k .= $key;
2981
-        }
2982
-
2983
-        $k = substr($k, 0, 256);
2984
-        for ($i = 0; $i < 256; $i++) {
2985
-            $this->arc4 .= chr($i);
2986
-        }
2987
-
2988
-        $j = 0;
2989
-
2990
-        for ($i = 0; $i < 256; $i++) {
2991
-            $t = $this->arc4[$i];
2992
-            $j = ($j + ord($t) + ord($k[$i])) % 256;
2993
-            $this->arc4[$i] = $this->arc4[$j];
2994
-            $this->arc4[$j] = $t;
2995
-        }
2996
-    }
2997
-
2998
-    /**
2999
-     * ARC4 encrypt a text string
3000
-     *
3001
-     * @param $text
3002
-     * @return string
3003
-     */
3004
-    function ARC4($text)
3005
-    {
3006
-        $len = mb_strlen($text, '8bit');
3007
-        $a = 0;
3008
-        $b = 0;
3009
-        $c = $this->arc4;
3010
-        $out = '';
3011
-        for ($i = 0; $i < $len; $i++) {
3012
-            $a = ($a + 1) % 256;
3013
-            $t = $c[$a];
3014
-            $b = ($b + ord($t)) % 256;
3015
-            $c[$a] = $c[$b];
3016
-            $c[$b] = $t;
3017
-            $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
3018
-            $out .= chr(ord($text[$i]) ^ $k);
3019
-        }
3020
-
3021
-        return $out;
3022
-    }
3023
-
3024
-    /**
3025
-     * functions which can be called to adjust or add to the document
3026
-     */
3027
-
3028
-    /**
3029
-     * add a link in the document to an external URL
3030
-     *
3031
-     * @param $url
3032
-     * @param $x0
3033
-     * @param $y0
3034
-     * @param $x1
3035
-     * @param $y1
3036
-     */
3037
-    function addLink($url, $x0, $y0, $x1, $y1)
3038
-    {
3039
-        $this->numObj++;
3040
-        $info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
3041
-        $this->o_annotation($this->numObj, 'new', $info);
3042
-    }
3043
-
3044
-    /**
3045
-     * add a link in the document to an internal destination (ie. within the document)
3046
-     *
3047
-     * @param $label
3048
-     * @param $x0
3049
-     * @param $y0
3050
-     * @param $x1
3051
-     * @param $y1
3052
-     */
3053
-    function addInternalLink($label, $x0, $y0, $x1, $y1)
3054
-    {
3055
-        $this->numObj++;
3056
-        $info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
3057
-        $this->o_annotation($this->numObj, 'new', $info);
3058
-    }
3059
-
3060
-    /**
3061
-     * set the encryption of the document
3062
-     * can be used to turn it on and/or set the passwords which it will have.
3063
-     * also the functions that the user will have are set here, such as print, modify, add
3064
-     *
3065
-     * @param string $userPass
3066
-     * @param string $ownerPass
3067
-     * @param array $pc
3068
-     */
3069
-    function setEncryption($userPass = '', $ownerPass = '', $pc = [])
3070
-    {
3071
-        $p = bindec("11000000");
3072
-
3073
-        $options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
3074
-
3075
-        foreach ($pc as $k => $v) {
3076
-            if ($v && isset($options[$k])) {
3077
-                $p += $options[$k];
3078
-            } else {
3079
-                if (isset($options[$v])) {
3080
-                    $p += $options[$v];
3081
-                }
3082
-            }
3083
-        }
3084
-
3085
-        // implement encryption on the document
3086
-        if ($this->arc4_objnum == 0) {
3087
-            // then the block does not exist already, add it.
3088
-            $this->numObj++;
3089
-            if (mb_strlen($ownerPass) == 0) {
3090
-                $ownerPass = $userPass;
3091
-            }
3092
-
3093
-            $this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
3094
-        }
3095
-    }
3096
-
3097
-    /**
3098
-     * should be used for internal checks, not implemented as yet
3099
-     */
3100
-    function checkAllHere()
3101
-    {
3102
-    }
3103
-
3104
-    /**
3105
-     * return the pdf stream as a string returned from the function
3106
-     *
3107
-     * @param bool $debug
3108
-     * @return string
3109
-     */
3110
-    function output($debug = false)
3111
-    {
3112
-        if ($debug) {
3113
-            // turn compression off
3114
-            $this->options['compression'] = false;
3115
-        }
3116
-
3117
-        if ($this->javascript) {
3118
-            $this->numObj++;
3119
-
3120
-            $js_id = $this->numObj;
3121
-            $this->o_embedjs($js_id, 'new');
3122
-            $this->o_javascript(++$this->numObj, 'new', $this->javascript);
3123
-
3124
-            $id = $this->catalogId;
3125
-
3126
-            $this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
3127
-        }
3128
-
3129
-        if ($this->fileIdentifier === '') {
3130
-            $tmp = implode('', $this->objects[$this->infoObject]['info']);
3131
-            $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3132
-        }
3133
-
3134
-        if ($this->arc4_objnum) {
3135
-            $this->o_encryption($this->arc4_objnum, 'keys');
3136
-            $this->ARC4_init($this->encryptionKey);
3137
-        }
3138
-
3139
-        $this->checkAllHere();
3140
-
3141
-        $xref = [];
3142
-        $content = '%PDF-' . self::PDF_VERSION;
3143
-        $pos = mb_strlen($content, '8bit');
3144
-
3145
-        // pre-process o_font objects before output of all objects
3146
-        foreach ($this->objects as $k => $v) {
3147
-            if ($v['t'] === 'font') {
3148
-                $this->o_font($k, 'add');
3149
-            }
3150
-        }
3151
-
3152
-        foreach ($this->objects as $k => $v) {
3153
-            $tmp = 'o_' . $v['t'];
3154
-            $cont = $this->$tmp($k, 'out');
3155
-            $content .= $cont;
3156
-            $xref[] = $pos + 1; //+1 to account for \n at the start of each object
3157
-            $pos += mb_strlen($cont, '8bit');
3158
-        }
3159
-
3160
-        $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3161
-
3162
-        foreach ($xref as $p) {
3163
-            $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3164
-        }
3165
-
3166
-        $content .= "trailer\n<<\n" .
3167
-            '/Size ' . (count($xref) + 1) . "\n" .
3168
-            '/Root 1 0 R' . "\n" .
3169
-            '/Info ' . $this->infoObject . " 0 R\n"
3170
-        ;
3171
-
3172
-        // if encryption has been applied to this document then add the marker for this dictionary
3173
-        if ($this->arc4_objnum > 0) {
3174
-            $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3175
-        }
3176
-
3177
-        $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3178
-
3179
-        // account for \n added at start of xref table
3180
-        $pos++;
3181
-
3182
-        $content .= ">>\nstartxref\n$pos\n%%EOF\n";
3183
-
3184
-        if (count($this->byteRange) > 0) {
3185
-            foreach ($this->byteRange as $k => $v) {
3186
-                $tmp = 'o_' . $v['t'];
3187
-                $this->$tmp($k, 'byterange', ['content' => &$content]);
3188
-            }
3189
-        }
3190
-
3191
-        return $content;
3192
-    }
3193
-
3194
-    /**
3195
-     * initialize a new document
3196
-     * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
3197
-     * this function is called automatically by the constructor function
3198
-     *
3199
-     * @param array $pageSize
3200
-     */
3201
-    private function newDocument($pageSize = [0, 0, 612, 792])
3202
-    {
3203
-        $this->numObj = 0;
3204
-        $this->objects = [];
3205
-
3206
-        $this->numObj++;
3207
-        $this->o_catalog($this->numObj, 'new');
3208
-
3209
-        $this->numObj++;
3210
-        $this->o_outlines($this->numObj, 'new');
3211
-
3212
-        $this->numObj++;
3213
-        $this->o_pages($this->numObj, 'new');
3214
-
3215
-        $this->o_pages($this->numObj, 'mediaBox', $pageSize);
3216
-        $this->currentNode = 3;
3217
-
3218
-        $this->numObj++;
3219
-        $this->o_procset($this->numObj, 'new');
3220
-
3221
-        $this->numObj++;
3222
-        $this->o_info($this->numObj, 'new');
3223
-
3224
-        $this->numObj++;
3225
-        $this->o_page($this->numObj, 'new');
3226
-
3227
-        // need to store the first page id as there is no way to get it to the user during
3228
-        // startup
3229
-        $this->firstPageId = $this->currentContents;
3230
-    }
3231
-
3232
-    /**
3233
-     * open the font file and return a php structure containing it.
3234
-     * first check if this one has been done before and saved in a form more suited to php
3235
-     * note that if a php serialized version does not exist it will try and make one, but will
3236
-     * require write access to the directory to do it... it is MUCH faster to have these serialized
3237
-     * files.
3238
-     *
3239
-     * @param $font
3240
-     */
3241
-    private function openFont($font)
3242
-    {
3243
-        // assume that $font contains the path and file but not the extension
3244
-        $name = basename($font);
3245
-        $dir = dirname($font);
3246
-
3247
-        $fontcache = $this->fontcache;
3248
-        if ($fontcache == '') {
3249
-            $fontcache = $dir;
3250
-        }
3251
-
3252
-        //$name       filename without folder and extension of font metrics
3253
-        //$dir        folder of font metrics
3254
-        //$fontcache  folder of runtime created php serialized version of font metrics.
3255
-        //            If this is not given, the same folder as the font metrics will be used.
3256
-        //            Storing and reusing serialized versions improves speed much
3257
-
3258
-        $this->addMessage("openFont: $font - $name");
3259
-
3260
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3261
-            $metrics_name = "$name.afm";
3262
-        } else {
3263
-            $metrics_name = "$name.ufm";
3264
-        }
3265
-
3266
-        $cache_name = "$metrics_name.json";
3267
-        $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3268
-
3269
-        if (file_exists($fontcache . '/' . $cache_name)) {
3270
-            $this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3271
-            $cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3272
-            if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3273
-                $this->addMessage('openFont: font cache is out of date, regenerating');
3274
-            } else {
3275
-                $this->fonts[$font] = $cached_font_info;
3276
-            }
3277
-        }
3278
-
3279
-        if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3280
-            // then rebuild the php_<font>.afm file from the <font>.afm file
3281
-            $this->addMessage("openFont: build php file from $dir/$metrics_name");
3282
-            $data = [];
3283
-
3284
-            // 20 => 'space'
3285
-            $data['codeToName'] = [];
3286
-
3287
-            // Since we're not going to enable Unicode for the core fonts we need to use a font-based
3288
-            // setting for Unicode support rather than a global setting.
3289
-            $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
3290
-
3291
-            $cidtogid = '';
3292
-            if ($data['isUnicode']) {
3293
-                $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3294
-            }
3295
-
3296
-            $file = file("$dir/$metrics_name");
3297
-
3298
-            foreach ($file as $rowA) {
3299
-                $row = trim($rowA);
3300
-                $pos = strpos($row, ' ');
3301
-
3302
-                if ($pos) {
3303
-                    // then there must be some keyword
3304
-                    $key = substr($row, 0, $pos);
3305
-                    switch ($key) {
3306
-                        case 'FontName':
3307
-                        case 'FullName':
3308
-                        case 'FamilyName':
3309
-                        case 'PostScriptName':
3310
-                        case 'Weight':
3311
-                        case 'ItalicAngle':
3312
-                        case 'IsFixedPitch':
3313
-                        case 'CharacterSet':
3314
-                        case 'UnderlinePosition':
3315
-                        case 'UnderlineThickness':
3316
-                        case 'Version':
3317
-                        case 'EncodingScheme':
3318
-                        case 'CapHeight':
3319
-                        case 'XHeight':
3320
-                        case 'Ascender':
3321
-                        case 'Descender':
3322
-                        case 'StdHW':
3323
-                        case 'StdVW':
3324
-                        case 'StartCharMetrics':
3325
-                        case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font.  Otherwise it's too big.
3326
-                            $data[$key] = trim(substr($row, $pos));
3327
-                            break;
3328
-
3329
-                        case 'FontBBox':
3330
-                            $data[$key] = explode(' ', trim(substr($row, $pos)));
3331
-                            break;
3332
-
3333
-                        //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
3334
-                        case 'C': // Found in AFM files
3335
-                            $bits = explode(';', trim($row));
3336
-                            $dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
3337
-
3338
-                            foreach ($bits as $bit) {
3339
-                                $bits2 = explode(' ', trim($bit));
3340
-                                if (mb_strlen($bits2[0], '8bit') == 0) {
3341
-                                    continue;
3342
-                                }
3343
-
3344
-                                if (count($bits2) > 2) {
3345
-                                    $dtmp[$bits2[0]] = [];
3346
-                                    for ($i = 1; $i < count($bits2); $i++) {
3347
-                                        $dtmp[$bits2[0]][] = $bits2[$i];
3348
-                                    }
3349
-                                } else {
3350
-                                    if (count($bits2) == 2) {
3351
-                                        $dtmp[$bits2[0]] = $bits2[1];
3352
-                                    }
3353
-                                }
3354
-                            }
3355
-
3356
-                            $c = (int)$dtmp['C'];
3357
-                            $n = $dtmp['N'];
3358
-                            $width = floatval($dtmp['WX']);
3359
-
3360
-                            if ($c >= 0) {
3361
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3362
-                                    $data['codeToName'][$c] = $n;
3363
-                                }
3364
-                                $data['C'][$c] = $width;
3365
-                            } elseif (isset($n)) {
3366
-                                $data['C'][$n] = $width;
3367
-                            }
3368
-
3369
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3370
-                                $data['MissingWidth'] = $width;
3371
-                            }
3372
-
3373
-                            break;
3374
-
3375
-                        // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3376
-                        case 'U': // Found in UFM files
3377
-                            if (!$data['isUnicode']) {
3378
-                                break;
3379
-                            }
3380
-
3381
-                            $bits = explode(';', trim($row));
3382
-                            $dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
3383
-
3384
-                            foreach ($bits as $bit) {
3385
-                                $bits2 = explode(' ', trim($bit));
3386
-                                if (mb_strlen($bits2[0], '8bit') === 0) {
3387
-                                    continue;
3388
-                                }
3389
-
3390
-                                if (count($bits2) > 2) {
3391
-                                    $dtmp[$bits2[0]] = [];
3392
-                                    for ($i = 1; $i < count($bits2); $i++) {
3393
-                                        $dtmp[$bits2[0]][] = $bits2[$i];
3394
-                                    }
3395
-                                } else {
3396
-                                    if (count($bits2) == 2) {
3397
-                                        $dtmp[$bits2[0]] = $bits2[1];
3398
-                                    }
3399
-                                }
3400
-                            }
3401
-
3402
-                            $c = (int)$dtmp['U'];
3403
-                            $n = $dtmp['N'];
3404
-                            $glyph = $dtmp['G'];
3405
-                            $width = floatval($dtmp['WX']);
3406
-
3407
-                            if ($c >= 0) {
3408
-                                // Set values in CID to GID map
3409
-                                if ($c >= 0 && $c < 0xFFFF && $glyph) {
3410
-                                    $cidtogid[$c * 2] = chr($glyph >> 8);
3411
-                                    $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3412
-                                }
3413
-
3414
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3415
-                                    $data['codeToName'][$c] = $n;
3416
-                                }
3417
-                                $data['C'][$c] = $width;
3418
-                            } elseif (isset($n)) {
3419
-                                $data['C'][$n] = $width;
3420
-                            }
3421
-
3422
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3423
-                                $data['MissingWidth'] = $width;
3424
-                            }
3425
-
3426
-                            break;
3427
-
3428
-                        case 'KPX':
3429
-                            break; // don't include them as they are not used yet
3430
-                            //KPX Adieresis yacute -40
3431
-                            /*$bits = explode(' ', trim($row));
1315
+				$res = "\n$id 0 obj\n";
1316
+				$res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1317
+				$res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1318
+
1319
+				return $res;
1320
+		}
1321
+
1322
+		return null;
1323
+	}
1324
+
1325
+	/**
1326
+	 * a font descriptor, needed for including additional fonts
1327
+	 *
1328
+	 * @param $id
1329
+	 * @param $action
1330
+	 * @param string $options
1331
+	 * @return null|string
1332
+	 */
1333
+	protected function o_fontDescriptor($id, $action, $options = '')
1334
+	{
1335
+		if ($action !== 'new') {
1336
+			$o = &$this->objects[$id];
1337
+		}
1338
+
1339
+		switch ($action) {
1340
+			case 'new':
1341
+				$this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
1342
+				break;
1343
+
1344
+			case 'out':
1345
+				$res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1346
+				foreach ($o['info'] as $label => $value) {
1347
+					switch ($label) {
1348
+						case 'Ascent':
1349
+						case 'CapHeight':
1350
+						case 'Descent':
1351
+						case 'Flags':
1352
+						case 'ItalicAngle':
1353
+						case 'StemV':
1354
+						case 'AvgWidth':
1355
+						case 'Leading':
1356
+						case 'MaxWidth':
1357
+						case 'MissingWidth':
1358
+						case 'StemH':
1359
+						case 'XHeight':
1360
+						case 'CharSet':
1361
+							if (mb_strlen($value, '8bit')) {
1362
+								$res .= "/$label $value\n";
1363
+							}
1364
+
1365
+							break;
1366
+						case 'FontFile':
1367
+						case 'FontFile2':
1368
+						case 'FontFile3':
1369
+							$res .= "/$label $value 0 R\n";
1370
+							break;
1371
+
1372
+						case 'FontBBox':
1373
+							$res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1374
+							break;
1375
+
1376
+						case 'FontName':
1377
+							$res .= "/$label /$value\n";
1378
+							break;
1379
+					}
1380
+				}
1381
+
1382
+				$res .= ">>\nendobj";
1383
+
1384
+				return $res;
1385
+		}
1386
+
1387
+		return null;
1388
+	}
1389
+
1390
+	/**
1391
+	 * the font encoding
1392
+	 *
1393
+	 * @param $id
1394
+	 * @param $action
1395
+	 * @param string $options
1396
+	 * @return null|string
1397
+	 */
1398
+	protected function o_fontEncoding($id, $action, $options = '')
1399
+	{
1400
+		if ($action !== 'new') {
1401
+			$o = &$this->objects[$id];
1402
+		}
1403
+
1404
+		switch ($action) {
1405
+			case 'new':
1406
+				// the options array should contain 'differences' and maybe 'encoding'
1407
+				$this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
1408
+				break;
1409
+
1410
+			case 'out':
1411
+				$res = "\n$id 0 obj\n<< /Type /Encoding\n";
1412
+				if (!isset($o['info']['encoding'])) {
1413
+					$o['info']['encoding'] = 'WinAnsiEncoding';
1414
+				}
1415
+
1416
+				if ($o['info']['encoding'] !== 'none') {
1417
+					$res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1418
+				}
1419
+
1420
+				$res .= "/Differences \n[";
1421
+
1422
+				$onum = -100;
1423
+
1424
+				foreach ($o['info']['differences'] as $num => $label) {
1425
+					if ($num != $onum + 1) {
1426
+						// we cannot make use of consecutive numbering
1427
+						$res .= "\n$num /$label";
1428
+					} else {
1429
+						$res .= " /$label";
1430
+					}
1431
+
1432
+					$onum = $num;
1433
+				}
1434
+
1435
+				$res .= "\n]\n>>\nendobj";
1436
+
1437
+				return $res;
1438
+		}
1439
+
1440
+		return null;
1441
+	}
1442
+
1443
+	/**
1444
+	 * a descendent cid font, needed for unicode fonts
1445
+	 *
1446
+	 * @param $id
1447
+	 * @param $action
1448
+	 * @param string|array $options
1449
+	 * @return null|string
1450
+	 */
1451
+	protected function o_fontDescendentCID($id, $action, $options = '')
1452
+	{
1453
+		if ($action !== 'new') {
1454
+			$o = &$this->objects[$id];
1455
+		}
1456
+
1457
+		switch ($action) {
1458
+			case 'new':
1459
+				$this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
1460
+
1461
+				// we need a CID system info section
1462
+				$cidSystemInfoId = ++$this->numObj;
1463
+				$this->o_cidSystemInfo($cidSystemInfoId, 'new');
1464
+				$this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1465
+
1466
+				// and a CID to GID map
1467
+				$cidToGidMapId = ++$this->numObj;
1468
+				$this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1469
+				$this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1470
+				break;
1471
+
1472
+			case 'add':
1473
+				foreach ($options as $k => $v) {
1474
+					switch ($k) {
1475
+						case 'BaseFont':
1476
+							$o['info']['name'] = $v;
1477
+							break;
1478
+
1479
+						case 'FirstChar':
1480
+						case 'LastChar':
1481
+						case 'MissingWidth':
1482
+						case 'FontDescriptor':
1483
+						case 'SubType':
1484
+							$this->addMessage("o_fontDescendentCID $k : $v");
1485
+							$o['info'][$k] = $v;
1486
+							break;
1487
+					}
1488
+				}
1489
+
1490
+				// pass values down to cid to gid map
1491
+				$this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1492
+				break;
1493
+
1494
+			case 'out':
1495
+				$res = "\n$id 0 obj\n";
1496
+				$res .= "<</Type /Font\n";
1497
+				$res .= "/Subtype /CIDFontType2\n";
1498
+				$res .= "/BaseFont /" . $o['info']['name'] . "\n";
1499
+				$res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1500
+				//      if (isset($o['info']['FirstChar'])) {
1501
+				//        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1502
+				//      }
1503
+
1504
+				//      if (isset($o['info']['LastChar'])) {
1505
+				//        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1506
+				//      }
1507
+				if (isset($o['info']['FontDescriptor'])) {
1508
+					$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1509
+				}
1510
+
1511
+				if (isset($o['info']['MissingWidth'])) {
1512
+					$res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1513
+				}
1514
+
1515
+				if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1516
+					$cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1517
+					$w = '';
1518
+					foreach ($cid_widths as $cid => $width) {
1519
+						$w .= "$cid [$width] ";
1520
+					}
1521
+					$res .= "/W [$w]\n";
1522
+				}
1523
+
1524
+				$res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1525
+				$res .= ">>\n";
1526
+				$res .= "endobj";
1527
+
1528
+				return $res;
1529
+		}
1530
+
1531
+		return null;
1532
+	}
1533
+
1534
+	/**
1535
+	 * CID system info section, needed for unicode fonts
1536
+	 *
1537
+	 * @param $id
1538
+	 * @param $action
1539
+	 * @return null|string
1540
+	 */
1541
+	protected function o_cidSystemInfo($id, $action)
1542
+	{
1543
+		switch ($action) {
1544
+			case 'new':
1545
+				$this->objects[$id] = [
1546
+					't' => 'cidSystemInfo'
1547
+				];
1548
+				break;
1549
+			case 'add':
1550
+				break;
1551
+			case 'out':
1552
+				$ordering = 'UCS';
1553
+				$registry = 'Adobe';
1554
+
1555
+				if ($this->encrypted) {
1556
+					$this->encryptInit($id);
1557
+					$ordering = $this->ARC4($ordering);
1558
+					$registry = $this->ARC4($registry);
1559
+				}
1560
+
1561
+
1562
+				$res = "\n$id 0 obj\n";
1563
+
1564
+				$res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1565
+				$res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1566
+				$res .= "/Supplement 0\n"; // The supplement number of the character collection.
1567
+				$res .= ">>";
1568
+
1569
+				$res .= "\nendobj";
1570
+
1571
+				return $res;
1572
+		}
1573
+
1574
+		return null;
1575
+	}
1576
+
1577
+	/**
1578
+	 * a font glyph to character map, needed for unicode fonts
1579
+	 *
1580
+	 * @param $id
1581
+	 * @param $action
1582
+	 * @param string $options
1583
+	 * @return null|string
1584
+	 */
1585
+	protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1586
+	{
1587
+		if ($action !== 'new') {
1588
+			$o = &$this->objects[$id];
1589
+		}
1590
+
1591
+		switch ($action) {
1592
+			case 'new':
1593
+				$this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
1594
+				break;
1595
+
1596
+			case 'out':
1597
+				$res = "\n$id 0 obj\n";
1598
+				$fontFileName = $o['info']['fontFileName'];
1599
+				$tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1600
+
1601
+				$compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1602
+					$this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1603
+
1604
+				if (!$compressed && isset($o['raw'])) {
1605
+					$res .= $tmp;
1606
+				} else {
1607
+					$res .= "<<";
1608
+
1609
+					if (!$compressed && $this->compressionReady && $this->options['compression']) {
1610
+						// then implement ZLIB based compression on this content stream
1611
+						$compressed = true;
1612
+						$tmp = gzcompress($tmp, 6);
1613
+					}
1614
+					if ($compressed) {
1615
+						$res .= "\n/Filter /FlateDecode";
1616
+					}
1617
+
1618
+					if ($this->encrypted) {
1619
+						$this->encryptInit($id);
1620
+						$tmp = $this->ARC4($tmp);
1621
+					}
1622
+
1623
+					$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1624
+				}
1625
+
1626
+				$res .= "\nendobj";
1627
+
1628
+				return $res;
1629
+		}
1630
+
1631
+		return null;
1632
+	}
1633
+
1634
+	/**
1635
+	 * the document procset, solves some problems with printing to old PS printers
1636
+	 *
1637
+	 * @param $id
1638
+	 * @param $action
1639
+	 * @param string $options
1640
+	 * @return null|string
1641
+	 */
1642
+	protected function o_procset($id, $action, $options = '')
1643
+	{
1644
+		if ($action !== 'new') {
1645
+			$o = &$this->objects[$id];
1646
+		}
1647
+
1648
+		switch ($action) {
1649
+			case 'new':
1650
+				$this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
1651
+				$this->o_pages($this->currentNode, 'procset', $id);
1652
+				$this->procsetObjectId = $id;
1653
+				break;
1654
+
1655
+			case 'add':
1656
+				// this is to add new items to the procset list, despite the fact that this is considered
1657
+				// obsolete, the items are required for printing to some postscript printers
1658
+				switch ($options) {
1659
+					case 'ImageB':
1660
+					case 'ImageC':
1661
+					case 'ImageI':
1662
+						$o['info'][$options] = 1;
1663
+						break;
1664
+				}
1665
+				break;
1666
+
1667
+			case 'out':
1668
+				$res = "\n$id 0 obj\n[";
1669
+				foreach ($o['info'] as $label => $val) {
1670
+					$res .= "/$label ";
1671
+				}
1672
+				$res .= "]\nendobj";
1673
+
1674
+				return $res;
1675
+		}
1676
+
1677
+		return null;
1678
+	}
1679
+
1680
+	/**
1681
+	 * define the document information
1682
+	 *
1683
+	 * @param $id
1684
+	 * @param $action
1685
+	 * @param string $options
1686
+	 * @return null|string
1687
+	 */
1688
+	protected function o_info($id, $action, $options = '')
1689
+	{
1690
+		switch ($action) {
1691
+			case 'new':
1692
+				$this->infoObject = $id;
1693
+				$date = 'D:' . @date('Ymd');
1694
+				$this->objects[$id] = [
1695
+					't'    => 'info',
1696
+					'info' => [
1697
+						'Producer'      => 'CPDF (dompdf)',
1698
+						'CreationDate' => $date
1699
+					]
1700
+				];
1701
+				break;
1702
+			case 'Title':
1703
+			case 'Author':
1704
+			case 'Subject':
1705
+			case 'Keywords':
1706
+			case 'Creator':
1707
+			case 'Producer':
1708
+			case 'CreationDate':
1709
+			case 'ModDate':
1710
+			case 'Trapped':
1711
+				$this->objects[$id]['info'][$action] = $options;
1712
+				break;
1713
+
1714
+			case 'out':
1715
+				$encrypted = $this->encrypted;
1716
+				if ($encrypted) {
1717
+					$this->encryptInit($id);
1718
+				}
1719
+
1720
+				$res = "\n$id 0 obj\n<<\n";
1721
+				$o = &$this->objects[$id];
1722
+				foreach ($o['info'] as $k => $v) {
1723
+					$res .= "/$k (";
1724
+
1725
+					// dates must be outputted as-is, without Unicode transformations
1726
+					if ($k !== 'CreationDate' && $k !== 'ModDate') {
1727
+						$v = $this->utf8toUtf16BE($v);
1728
+					}
1729
+
1730
+					if ($encrypted) {
1731
+						$v = $this->ARC4($v);
1732
+					}
1733
+
1734
+					$res .= $this->filterText($v, false, false);
1735
+					$res .= ")\n";
1736
+				}
1737
+
1738
+				$res .= ">>\nendobj";
1739
+
1740
+				return $res;
1741
+		}
1742
+
1743
+		return null;
1744
+	}
1745
+
1746
+	/**
1747
+	 * an action object, used to link to URLS initially
1748
+	 *
1749
+	 * @param $id
1750
+	 * @param $action
1751
+	 * @param string $options
1752
+	 * @return null|string
1753
+	 */
1754
+	protected function o_action($id, $action, $options = '')
1755
+	{
1756
+		if ($action !== 'new') {
1757
+			$o = &$this->objects[$id];
1758
+		}
1759
+
1760
+		switch ($action) {
1761
+			case 'new':
1762
+				if (is_array($options)) {
1763
+					$this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
1764
+				} else {
1765
+					// then assume a URI action
1766
+					$this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
1767
+				}
1768
+				break;
1769
+
1770
+			case 'out':
1771
+				if ($this->encrypted) {
1772
+					$this->encryptInit($id);
1773
+				}
1774
+
1775
+				$res = "\n$id 0 obj\n<< /Type /Action";
1776
+				switch ($o['type']) {
1777
+					case 'ilink':
1778
+						if (!isset($this->destinations[(string)$o['info']['label']])) {
1779
+							break;
1780
+						}
1781
+
1782
+						// there will be an 'label' setting, this is the name of the destination
1783
+						$res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1784
+						break;
1785
+
1786
+					case 'URI':
1787
+						$res .= "\n/S /URI\n/URI (";
1788
+						if ($this->encrypted) {
1789
+							$res .= $this->filterText($this->ARC4($o['info']), false, false);
1790
+						} else {
1791
+							$res .= $this->filterText($o['info'], false, false);
1792
+						}
1793
+
1794
+						$res .= ")";
1795
+						break;
1796
+				}
1797
+
1798
+				$res .= "\n>>\nendobj";
1799
+
1800
+				return $res;
1801
+		}
1802
+
1803
+		return null;
1804
+	}
1805
+
1806
+	/**
1807
+	 * an annotation object, this will add an annotation to the current page.
1808
+	 * initially will support just link annotations
1809
+	 *
1810
+	 * @param $id
1811
+	 * @param $action
1812
+	 * @param string $options
1813
+	 * @return null|string
1814
+	 */
1815
+	protected function o_annotation($id, $action, $options = '')
1816
+	{
1817
+		if ($action !== 'new') {
1818
+			$o = &$this->objects[$id];
1819
+		}
1820
+
1821
+		switch ($action) {
1822
+			case 'new':
1823
+				// add the annotation to the current page
1824
+				$pageId = $this->currentPage;
1825
+				$this->o_page($pageId, 'annot', $id);
1826
+
1827
+				// and add the action object which is going to be required
1828
+				switch ($options['type']) {
1829
+					case 'link':
1830
+						$this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1831
+						$this->numObj++;
1832
+						$this->o_action($this->numObj, 'new', $options['url']);
1833
+						$this->objects[$id]['info']['actionId'] = $this->numObj;
1834
+						break;
1835
+
1836
+					case 'ilink':
1837
+						// this is to a named internal link
1838
+						$label = $options['label'];
1839
+						$this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1840
+						$this->numObj++;
1841
+						$this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
1842
+						$this->objects[$id]['info']['actionId'] = $this->numObj;
1843
+						break;
1844
+				}
1845
+				break;
1846
+
1847
+			case 'out':
1848
+				$res = "\n$id 0 obj\n<< /Type /Annot";
1849
+				switch ($o['info']['type']) {
1850
+					case 'link':
1851
+					case 'ilink':
1852
+						$res .= "\n/Subtype /Link";
1853
+						break;
1854
+				}
1855
+				$res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1856
+				$res .= "\n/Border [0 0 0]";
1857
+				$res .= "\n/H /I";
1858
+				$res .= "\n/Rect [ ";
1859
+
1860
+				foreach ($o['info']['rect'] as $v) {
1861
+					$res .= sprintf("%.4F ", $v);
1862
+				}
1863
+
1864
+				$res .= "]";
1865
+				$res .= "\n>>\nendobj";
1866
+
1867
+				return $res;
1868
+		}
1869
+
1870
+		return null;
1871
+	}
1872
+
1873
+	/**
1874
+	 * a page object, it also creates a contents object to hold its contents
1875
+	 *
1876
+	 * @param $id
1877
+	 * @param $action
1878
+	 * @param string $options
1879
+	 * @return null|string
1880
+	 */
1881
+	protected function o_page($id, $action, $options = '')
1882
+	{
1883
+		if ($action !== 'new') {
1884
+			$o = &$this->objects[$id];
1885
+		}
1886
+
1887
+		switch ($action) {
1888
+			case 'new':
1889
+				$this->numPages++;
1890
+				$this->objects[$id] = [
1891
+					't'    => 'page',
1892
+					'info' => [
1893
+						'parent'  => $this->currentNode,
1894
+						'pageNum' => $this->numPages,
1895
+						'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1896
+					]
1897
+				];
1898
+
1899
+				if (is_array($options)) {
1900
+					// then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1901
+					$options['id'] = $id;
1902
+					$this->o_pages($this->currentNode, 'page', $options);
1903
+				} else {
1904
+					$this->o_pages($this->currentNode, 'page', $id);
1905
+				}
1906
+
1907
+				$this->currentPage = $id;
1908
+				//make a contents object to go with this page
1909
+				$this->numObj++;
1910
+				$this->o_contents($this->numObj, 'new', $id);
1911
+				$this->currentContents = $this->numObj;
1912
+				$this->objects[$id]['info']['contents'] = [];
1913
+				$this->objects[$id]['info']['contents'][] = $this->numObj;
1914
+
1915
+				$match = ($this->numPages % 2 ? 'odd' : 'even');
1916
+				foreach ($this->addLooseObjects as $oId => $target) {
1917
+					if ($target === 'all' || $match === $target) {
1918
+						$this->objects[$id]['info']['contents'][] = $oId;
1919
+					}
1920
+				}
1921
+				break;
1922
+
1923
+			case 'content':
1924
+				$o['info']['contents'][] = $options;
1925
+				break;
1926
+
1927
+			case 'annot':
1928
+				// add an annotation to this page
1929
+				if (!isset($o['info']['annot'])) {
1930
+					$o['info']['annot'] = [];
1931
+				}
1932
+
1933
+				// $options should contain the id of the annotation dictionary
1934
+				$o['info']['annot'][] = $options;
1935
+				break;
1936
+
1937
+			case 'out':
1938
+				$res = "\n$id 0 obj\n<< /Type /Page";
1939
+				if (isset($o['info']['mediaBox'])) {
1940
+					$tmp = $o['info']['mediaBox'];
1941
+					$res .= "\n/MediaBox [" . sprintf(
1942
+							'%.3F %.3F %.3F %.3F',
1943
+							$tmp[0],
1944
+							$tmp[1],
1945
+							$tmp[2],
1946
+							$tmp[3]
1947
+						) . ']';
1948
+				}
1949
+				$res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1950
+
1951
+				if (isset($o['info']['annot'])) {
1952
+					$res .= "\n/Annots [";
1953
+					foreach ($o['info']['annot'] as $aId) {
1954
+						$res .= " $aId 0 R";
1955
+					}
1956
+					$res .= " ]";
1957
+				}
1958
+
1959
+				$count = count($o['info']['contents']);
1960
+				if ($count == 1) {
1961
+					$res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1962
+				} else {
1963
+					if ($count > 1) {
1964
+						$res .= "\n/Contents [\n";
1965
+
1966
+						// reverse the page contents so added objects are below normal content
1967
+						//foreach (array_reverse($o['info']['contents']) as $cId) {
1968
+						// Back to normal now that I've got transparency working --Benj
1969
+						foreach ($o['info']['contents'] as $cId) {
1970
+							$res .= "$cId 0 R\n";
1971
+						}
1972
+						$res .= "]";
1973
+					}
1974
+				}
1975
+
1976
+				$res .= "\n>>\nendobj";
1977
+
1978
+				return $res;
1979
+		}
1980
+
1981
+		return null;
1982
+	}
1983
+
1984
+	/**
1985
+	 * the contents objects hold all of the content which appears on pages
1986
+	 *
1987
+	 * @param $id
1988
+	 * @param $action
1989
+	 * @param string|array $options
1990
+	 * @return null|string
1991
+	 */
1992
+	protected function o_contents($id, $action, $options = '')
1993
+	{
1994
+		if ($action !== 'new') {
1995
+			$o = &$this->objects[$id];
1996
+		}
1997
+
1998
+		switch ($action) {
1999
+			case 'new':
2000
+				$this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
2001
+				if (mb_strlen($options, '8bit') && intval($options)) {
2002
+					// then this contents is the primary for a page
2003
+					$this->objects[$id]['onPage'] = $options;
2004
+				} else {
2005
+					if ($options === 'raw') {
2006
+						// then this page contains some other type of system object
2007
+						$this->objects[$id]['raw'] = 1;
2008
+					}
2009
+				}
2010
+				break;
2011
+
2012
+			case 'add':
2013
+				// add more options to the declaration
2014
+				foreach ($options as $k => $v) {
2015
+					$o['info'][$k] = $v;
2016
+				}
2017
+
2018
+			case 'out':
2019
+				$tmp = $o['c'];
2020
+				$res = "\n$id 0 obj\n";
2021
+
2022
+				if (isset($this->objects[$id]['raw'])) {
2023
+					$res .= $tmp;
2024
+				} else {
2025
+					$res .= "<<";
2026
+					if ($this->compressionReady && $this->options['compression']) {
2027
+						// then implement ZLIB based compression on this content stream
2028
+						$res .= " /Filter /FlateDecode";
2029
+						$tmp = gzcompress($tmp, 6);
2030
+					}
2031
+
2032
+					if ($this->encrypted) {
2033
+						$this->encryptInit($id);
2034
+						$tmp = $this->ARC4($tmp);
2035
+					}
2036
+
2037
+					foreach ($o['info'] as $k => $v) {
2038
+						$res .= "\n/$k $v";
2039
+					}
2040
+
2041
+					$res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2042
+				}
2043
+
2044
+				$res .= "\nendobj";
2045
+
2046
+				return $res;
2047
+		}
2048
+
2049
+		return null;
2050
+	}
2051
+
2052
+	/**
2053
+	 * @param $id
2054
+	 * @param $action
2055
+	 * @return string|null
2056
+	 */
2057
+	protected function o_embedjs($id, $action)
2058
+	{
2059
+		switch ($action) {
2060
+			case 'new':
2061
+				$this->objects[$id] = [
2062
+					't'    => 'embedjs',
2063
+					'info' => [
2064
+						'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2065
+					]
2066
+				];
2067
+				break;
2068
+
2069
+			case 'out':
2070
+				$o = &$this->objects[$id];
2071
+				$res = "\n$id 0 obj\n<< ";
2072
+				foreach ($o['info'] as $k => $v) {
2073
+					$res .= "\n/$k $v";
2074
+				}
2075
+				$res .= "\n>>\nendobj";
2076
+
2077
+				return $res;
2078
+		}
2079
+
2080
+		return null;
2081
+	}
2082
+
2083
+	/**
2084
+	 * @param $id
2085
+	 * @param $action
2086
+	 * @param string $code
2087
+	 * @return null|string
2088
+	 */
2089
+	protected function o_javascript($id, $action, $code = '')
2090
+	{
2091
+		switch ($action) {
2092
+			case 'new':
2093
+				$this->objects[$id] = [
2094
+					't'    => 'javascript',
2095
+					'info' => [
2096
+						'S'  => '/JavaScript',
2097
+						'JS' => '(' . $this->filterText($code, true, false) . ')',
2098
+					]
2099
+				];
2100
+				break;
2101
+
2102
+			case 'out':
2103
+				$o = &$this->objects[$id];
2104
+				$res = "\n$id 0 obj\n<< ";
2105
+
2106
+				foreach ($o['info'] as $k => $v) {
2107
+					$res .= "\n/$k $v";
2108
+				}
2109
+				$res .= "\n>>\nendobj";
2110
+
2111
+				return $res;
2112
+		}
2113
+
2114
+		return null;
2115
+	}
2116
+
2117
+	/**
2118
+	 * an image object, will be an XObject in the document, includes description and data
2119
+	 *
2120
+	 * @param $id
2121
+	 * @param $action
2122
+	 * @param string $options
2123
+	 * @return null|string
2124
+	 */
2125
+	protected function o_image($id, $action, $options = '')
2126
+	{
2127
+		switch ($action) {
2128
+			case 'new':
2129
+				// make the new object
2130
+				$this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2131
+
2132
+				$info =& $this->objects[$id]['info'];
2133
+
2134
+				$info['Type'] = '/XObject';
2135
+				$info['Subtype'] = '/Image';
2136
+				$info['Width'] = $options['iw'];
2137
+				$info['Height'] = $options['ih'];
2138
+
2139
+				if (isset($options['masked']) && $options['masked']) {
2140
+					$info['SMask'] = ($this->numObj - 1) . ' 0 R';
2141
+				}
2142
+
2143
+				if (!isset($options['type']) || $options['type'] === 'jpg') {
2144
+					if (!isset($options['channels'])) {
2145
+						$options['channels'] = 3;
2146
+					}
2147
+
2148
+					switch ($options['channels']) {
2149
+						case 1:
2150
+							$info['ColorSpace'] = '/DeviceGray';
2151
+							break;
2152
+						case 4:
2153
+							$info['ColorSpace'] = '/DeviceCMYK';
2154
+							break;
2155
+						default:
2156
+							$info['ColorSpace'] = '/DeviceRGB';
2157
+							break;
2158
+					}
2159
+
2160
+					if ($info['ColorSpace'] === '/DeviceCMYK') {
2161
+						$info['Decode'] = '[1 0 1 0 1 0 1 0]';
2162
+					}
2163
+
2164
+					$info['Filter'] = '/DCTDecode';
2165
+					$info['BitsPerComponent'] = 8;
2166
+				} else {
2167
+					if ($options['type'] === 'png') {
2168
+						$info['Filter'] = '/FlateDecode';
2169
+						$info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2170
+
2171
+						if ($options['isMask']) {
2172
+							$info['ColorSpace'] = '/DeviceGray';
2173
+						} else {
2174
+							if (mb_strlen($options['pdata'], '8bit')) {
2175
+								$tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2176
+								$this->numObj++;
2177
+								$this->o_contents($this->numObj, 'new');
2178
+								$this->objects[$this->numObj]['c'] = $options['pdata'];
2179
+								$tmp .= $this->numObj . ' 0 R';
2180
+								$tmp .= ' ]';
2181
+								$info['ColorSpace'] = $tmp;
2182
+
2183
+								if (isset($options['transparency'])) {
2184
+									$transparency = $options['transparency'];
2185
+									switch ($transparency['type']) {
2186
+										case 'indexed':
2187
+											$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2188
+											$info['Mask'] = $tmp;
2189
+											break;
2190
+
2191
+										case 'color-key':
2192
+											$tmp = ' [ ' .
2193
+												$transparency['r'] . ' ' . $transparency['r'] .
2194
+												$transparency['g'] . ' ' . $transparency['g'] .
2195
+												$transparency['b'] . ' ' . $transparency['b'] .
2196
+												' ] ';
2197
+											$info['Mask'] = $tmp;
2198
+											break;
2199
+									}
2200
+								}
2201
+							} else {
2202
+								if (isset($options['transparency'])) {
2203
+									$transparency = $options['transparency'];
2204
+
2205
+									switch ($transparency['type']) {
2206
+										case 'indexed':
2207
+											$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2208
+											$info['Mask'] = $tmp;
2209
+											break;
2210
+
2211
+										case 'color-key':
2212
+											$tmp = ' [ ' .
2213
+												$transparency['r'] . ' ' . $transparency['r'] . ' ' .
2214
+												$transparency['g'] . ' ' . $transparency['g'] . ' ' .
2215
+												$transparency['b'] . ' ' . $transparency['b'] .
2216
+												' ] ';
2217
+											$info['Mask'] = $tmp;
2218
+											break;
2219
+									}
2220
+								}
2221
+								$info['ColorSpace'] = '/' . $options['color'];
2222
+							}
2223
+						}
2224
+
2225
+						$info['BitsPerComponent'] = $options['bitsPerComponent'];
2226
+					}
2227
+				}
2228
+
2229
+				// assign it a place in the named resource dictionary as an external object, according to
2230
+				// the label passed in with it.
2231
+				$this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
2232
+
2233
+				// also make sure that we have the right procset object for it.
2234
+				$this->o_procset($this->procsetObjectId, 'add', 'ImageC');
2235
+				break;
2236
+
2237
+			case 'out':
2238
+				$o = &$this->objects[$id];
2239
+				$tmp = &$o['data'];
2240
+				$res = "\n$id 0 obj\n<<";
2241
+
2242
+				foreach ($o['info'] as $k => $v) {
2243
+					$res .= "\n/$k $v";
2244
+				}
2245
+
2246
+				if ($this->encrypted) {
2247
+					$this->encryptInit($id);
2248
+					$tmp = $this->ARC4($tmp);
2249
+				}
2250
+
2251
+				$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2252
+
2253
+				return $res;
2254
+		}
2255
+
2256
+		return null;
2257
+	}
2258
+
2259
+	/**
2260
+	 * graphics state object
2261
+	 *
2262
+	 * @param $id
2263
+	 * @param $action
2264
+	 * @param string $options
2265
+	 * @return null|string
2266
+	 */
2267
+	protected function o_extGState($id, $action, $options = "")
2268
+	{
2269
+		static $valid_params = [
2270
+			"LW",
2271
+			"LC",
2272
+			"LC",
2273
+			"LJ",
2274
+			"ML",
2275
+			"D",
2276
+			"RI",
2277
+			"OP",
2278
+			"op",
2279
+			"OPM",
2280
+			"Font",
2281
+			"BG",
2282
+			"BG2",
2283
+			"UCR",
2284
+			"TR",
2285
+			"TR2",
2286
+			"HT",
2287
+			"FL",
2288
+			"SM",
2289
+			"SA",
2290
+			"BM",
2291
+			"SMask",
2292
+			"CA",
2293
+			"ca",
2294
+			"AIS",
2295
+			"TK"
2296
+		];
2297
+
2298
+		switch ($action) {
2299
+			case "new":
2300
+				$this->objects[$id] = ['t' => 'extGState', 'info' => $options];
2301
+
2302
+				// Tell the pages about the new resource
2303
+				$this->numStates++;
2304
+				$this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
2305
+				break;
2306
+
2307
+			case "out":
2308
+				$o = &$this->objects[$id];
2309
+				$res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2310
+
2311
+				foreach ($o["info"] as $k => $v) {
2312
+					if (!in_array($k, $valid_params)) {
2313
+						continue;
2314
+					}
2315
+					$res .= "/$k $v\n";
2316
+				}
2317
+
2318
+				$res .= ">>\nendobj";
2319
+
2320
+				return $res;
2321
+		}
2322
+
2323
+		return null;
2324
+	}
2325
+
2326
+	/**
2327
+	 * @param integer $id
2328
+	 * @param string $action
2329
+	 * @param mixed $options
2330
+	 * @return string
2331
+	 */
2332
+	protected function o_xobject($id, $action, $options = '')
2333
+	{
2334
+		switch ($action) {
2335
+			case 'new':
2336
+				$this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
2337
+				break;
2338
+
2339
+			case 'procset':
2340
+				$this->objects[$id]['procset'] = $options;
2341
+				break;
2342
+
2343
+			case 'font':
2344
+				$this->objects[$id]['fonts'][$options['fontNum']] = [
2345
+				  'objNum' => $options['objNum'],
2346
+				  'fontNum' => $options['fontNum']
2347
+				];
2348
+				break;
2349
+
2350
+			case 'xObject':
2351
+				$this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
2352
+				break;
2353
+
2354
+			case 'out':
2355
+				$o = &$this->objects[$id];
2356
+				$res = "\n$id 0 obj\n<< /Type /XObject\n";
2357
+
2358
+				foreach ($o["info"] as $k => $v) {
2359
+					switch ($k) {
2360
+						case 'Subtype':
2361
+							$res .= "/Subtype /$v\n";
2362
+							break;
2363
+						case 'bbox':
2364
+							$res .= "/BBox [";
2365
+							foreach ($v as $value) {
2366
+								$res .= sprintf("%.4F ", $value);
2367
+							}
2368
+							$res .= "]\n";
2369
+							break;
2370
+						default:
2371
+							$res .= "/$k $v\n";
2372
+							break;
2373
+					}
2374
+				}
2375
+				$res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
2376
+
2377
+				$res .= "/Resources <<";
2378
+				if (isset($o['procset'])) {
2379
+					$res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2380
+				} else {
2381
+					$res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2382
+				}
2383
+				if (isset($o['fonts']) && count($o['fonts'])) {
2384
+					$res .= "\n/Font << ";
2385
+					foreach ($o['fonts'] as $finfo) {
2386
+						$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2387
+					}
2388
+					$res .= "\n>>";
2389
+				}
2390
+				if (isset($o['xObjects']) && count($o['xObjects'])) {
2391
+					$res .= "\n/XObject << ";
2392
+					foreach ($o['xObjects'] as $finfo) {
2393
+						$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2394
+					}
2395
+					$res .= "\n>>";
2396
+				}
2397
+				$res .= "\n>>\n";
2398
+
2399
+				$tmp = $o["c"];
2400
+				if ($this->compressionReady && $this->options['compression']) {
2401
+					// then implement ZLIB based compression on this content stream
2402
+					$res .= " /Filter /FlateDecode\n";
2403
+					$tmp = gzcompress($tmp, 6);
2404
+				}
2405
+
2406
+				if ($this->encrypted) {
2407
+					$this->encryptInit($id);
2408
+					$tmp = $this->ARC4($tmp);
2409
+				}
2410
+
2411
+				$res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2412
+				$res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2413
+
2414
+				return $res;
2415
+		}
2416
+
2417
+		return null;
2418
+	}
2419
+
2420
+	/**
2421
+	 * @param $id
2422
+	 * @param $action
2423
+	 * @param string $options
2424
+	 * @return null|string
2425
+	 */
2426
+	protected function o_acroform($id, $action, $options = '')
2427
+	{
2428
+		switch ($action) {
2429
+			case "new":
2430
+				$this->o_catalog($this->catalogId, 'acroform', $id);
2431
+				$this->objects[$id] = array('t' => 'acroform', 'info' => $options);
2432
+				break;
2433
+
2434
+			case 'addfield':
2435
+				$this->objects[$id]['info']['Fields'][] = $options;
2436
+				break;
2437
+
2438
+			case 'font':
2439
+				$this->objects[$id]['fonts'][$options['fontNum']] = [
2440
+				  'objNum' => $options['objNum'],
2441
+				  'fontNum' => $options['fontNum']
2442
+				];
2443
+				break;
2444
+
2445
+			case "out":
2446
+				$o = &$this->objects[$id];
2447
+				$res = "\n$id 0 obj\n<<";
2448
+
2449
+				foreach ($o["info"] as $k => $v) {
2450
+					switch ($k) {
2451
+						case 'Fields':
2452
+							$res .= " /Fields [";
2453
+							foreach ($v as $i) {
2454
+								$res .= "$i 0 R ";
2455
+							}
2456
+							$res .= "]\n";
2457
+							break;
2458
+						default:
2459
+							$res .= "/$k $v\n";
2460
+					}
2461
+				}
2462
+
2463
+				$res .= "/DR <<\n";
2464
+				if (isset($o['fonts']) && count($o['fonts'])) {
2465
+					$res .= "/Font << \n";
2466
+					foreach ($o['fonts'] as $finfo) {
2467
+						$res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2468
+					}
2469
+					$res .= ">>\n";
2470
+				}
2471
+				$res .= ">>\n";
2472
+
2473
+				$res .= ">>\nendobj";
2474
+
2475
+				return $res;
2476
+		}
2477
+
2478
+		return null;
2479
+	}
2480
+
2481
+	/**
2482
+	 * @param $id
2483
+	 * @param $action
2484
+	 * @param mixed $options
2485
+	 * @return null|string
2486
+	 */
2487
+	protected function o_field($id, $action, $options = '')
2488
+	{
2489
+		switch ($action) {
2490
+			case "new":
2491
+				$this->o_page($options['pageid'], 'annot', $id);
2492
+				$this->o_acroform($this->acroFormId, 'addfield', $id);
2493
+				$this->objects[$id] = ['t' => 'field', 'info' => $options];
2494
+				break;
2495
+
2496
+			case 'set':
2497
+				$this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2498
+				break;
2499
+
2500
+			case "out":
2501
+				$o = &$this->objects[$id];
2502
+				$res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
2503
+
2504
+				$encrypted = $this->encrypted;
2505
+				if ($encrypted) {
2506
+					$this->encryptInit($id);
2507
+				}
2508
+
2509
+				foreach ($o["info"] as $k => $v) {
2510
+					switch ($k) {
2511
+						case 'pageid':
2512
+							$res .= "/P $v 0 R\n";
2513
+							break;
2514
+						case 'value':
2515
+							if ($encrypted) {
2516
+								$v = $this->filterText($this->ARC4($v), false, false);
2517
+							}
2518
+							$res .= "/V ($v)\n";
2519
+							break;
2520
+						case 'refvalue':
2521
+							$res .= "/V $v 0 R\n";
2522
+							break;
2523
+						case 'da':
2524
+							if ($encrypted) {
2525
+								$v = $this->filterText($this->ARC4($v), false, false);
2526
+							}
2527
+							$res .= "/DA ($v)\n";
2528
+							break;
2529
+						case 'options':
2530
+							$res .= "/Opt [\n";
2531
+							foreach ($v as $opt) {
2532
+								if ($encrypted) {
2533
+									$opt = $this->filterText($this->ARC4($opt), false, false);
2534
+								}
2535
+								$res .= "($opt)\n";
2536
+							}
2537
+							$res .= "]\n";
2538
+							break;
2539
+						case 'rect':
2540
+							$res .= "/Rect [";
2541
+							foreach ($v as $value) {
2542
+								$res .= sprintf("%.4F ", $value);
2543
+							}
2544
+							$res .= "]\n";
2545
+							break;
2546
+						case 'appearance':
2547
+							$res .= "/AP << ";
2548
+							foreach ($v as $a => $ref) {
2549
+								$res .= "/$a $ref 0 R ";
2550
+							}
2551
+							$res .= ">>\n";
2552
+							break;
2553
+						case 'T':
2554
+							if ($encrypted) {
2555
+								$v = $this->filterText($this->ARC4($v), false, false);
2556
+							}
2557
+							$res .= "/T ($v)\n";
2558
+							break;
2559
+						default:
2560
+							$res .= "/$k $v\n";
2561
+					}
2562
+
2563
+				}
2564
+
2565
+				$res .= ">>\nendobj";
2566
+
2567
+				return $res;
2568
+		}
2569
+
2570
+		return null;
2571
+	}
2572
+
2573
+	/**
2574
+	 *
2575
+	 * @param $id
2576
+	 * @param $action
2577
+	 * @param string $options
2578
+	 * @return null|string
2579
+	 */
2580
+	protected function o_sig($id, $action, $options = '')
2581
+	{
2582
+		$sign_maxlen = $this->signatureMaxLen;
2583
+
2584
+		switch ($action) {
2585
+			case "new":
2586
+				$this->objects[$id] = array('t' => 'sig', 'info' => $options);
2587
+				$this->byteRange[$id] = ['t' => 'sig'];
2588
+				break;
2589
+
2590
+			case 'byterange':
2591
+				$o = &$this->objects[$id];
2592
+				$content =& $options['content'];
2593
+				$content_len = strlen($content);
2594
+				$pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2595
+				$len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2596
+				$rangeStartPos = $pos + $len + 1 + 10; // before '<'
2597
+				$content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2598
+
2599
+				$fuid = uniqid();
2600
+				$tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2601
+				$tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2602
+
2603
+				if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2604
+					throw new \Exception("Unable to write temporary file for signing.");
2605
+				}
2606
+				if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
2607
+					FILE_APPEND) === false) {
2608
+					throw new \Exception("Unable to write temporary file for signing.");
2609
+				}
2610
+
2611
+				if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
2612
+					$o['info']['SignCert'],
2613
+					array($o['info']['PrivKey'], $o['info']['Password']),
2614
+					array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
2615
+					throw new \Exception("Failed to prepare signature.");
2616
+				}
2617
+
2618
+				$signature = file_get_contents($tmpOutput);
2619
+
2620
+				unlink($tmpInput);
2621
+				unlink($tmpOutput);
2622
+
2623
+				$sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
2624
+				list($head, $signature) = explode("\n\n", $sign);
2625
+
2626
+				$signature = base64_decode(trim($signature));
2627
+
2628
+				$signature = current(unpack('H*', $signature));
2629
+				$signature = str_pad($signature, $sign_maxlen, '0');
2630
+				$siglen = strlen($signature);
2631
+				if (strlen($signature) > $sign_maxlen) {
2632
+					throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
2633
+				}
2634
+
2635
+				$content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
2636
+				break;
2637
+
2638
+			case "out":
2639
+				$res = "\n$id 0 obj\n<<\n";
2640
+
2641
+				$encrypted = $this->encrypted;
2642
+				if ($encrypted) {
2643
+					$this->encryptInit($id);
2644
+				}
2645
+
2646
+				$res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2647
+				$res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2648
+				$res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2649
+				$res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2650
+
2651
+				$date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2652
+				if ($encrypted) {
2653
+					$date = $this->ARC4($date);
2654
+				}
2655
+
2656
+				$res .= "/M ($date)\n";
2657
+				$res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
2658
+
2659
+				$o = &$this->objects[$id];
2660
+				foreach ($o['info'] as $k => $v) {
2661
+					switch ($k) {
2662
+						case 'Name':
2663
+						case 'Location':
2664
+						case 'Reason':
2665
+						case 'ContactInfo':
2666
+							if ($v !== null && $v !== '') {
2667
+								$res .= "/$k (" .
2668
+								  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2669
+							}
2670
+							break;
2671
+					}
2672
+				}
2673
+				$res .= ">>\nendobj";
2674
+
2675
+				return $res;
2676
+		}
2677
+
2678
+		return null;
2679
+	}
2680
+
2681
+	/**
2682
+	 * encryption object.
2683
+	 *
2684
+	 * @param $id
2685
+	 * @param $action
2686
+	 * @param string $options
2687
+	 * @return string|null
2688
+	 */
2689
+	protected function o_encryption($id, $action, $options = '')
2690
+	{
2691
+		switch ($action) {
2692
+			case 'new':
2693
+				// make the new object
2694
+				$this->objects[$id] = ['t' => 'encryption', 'info' => $options];
2695
+				$this->arc4_objnum = $id;
2696
+				break;
2697
+
2698
+			case 'keys':
2699
+				// figure out the additional parameters required
2700
+				$pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2701
+					. chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2702
+					. chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2703
+					. chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2704
+
2705
+				$info = $this->objects[$id]['info'];
2706
+
2707
+				$len = mb_strlen($info['owner'], '8bit');
2708
+
2709
+				if ($len > 32) {
2710
+					$owner = substr($info['owner'], 0, 32);
2711
+				} else {
2712
+					if ($len < 32) {
2713
+						$owner = $info['owner'] . substr($pad, 0, 32 - $len);
2714
+					} else {
2715
+						$owner = $info['owner'];
2716
+					}
2717
+				}
2718
+
2719
+				$len = mb_strlen($info['user'], '8bit');
2720
+				if ($len > 32) {
2721
+					$user = substr($info['user'], 0, 32);
2722
+				} else {
2723
+					if ($len < 32) {
2724
+						$user = $info['user'] . substr($pad, 0, 32 - $len);
2725
+					} else {
2726
+						$user = $info['user'];
2727
+					}
2728
+				}
2729
+
2730
+				$tmp = $this->md5_16($owner);
2731
+				$okey = substr($tmp, 0, 5);
2732
+				$this->ARC4_init($okey);
2733
+				$ovalue = $this->ARC4($user);
2734
+				$this->objects[$id]['info']['O'] = $ovalue;
2735
+
2736
+				// now make the u value, phew.
2737
+				$tmp = $this->md5_16(
2738
+					$user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2739
+				);
2740
+
2741
+				$ukey = substr($tmp, 0, 5);
2742
+				$this->ARC4_init($ukey);
2743
+				$this->encryptionKey = $ukey;
2744
+				$this->encrypted = true;
2745
+				$uvalue = $this->ARC4($pad);
2746
+				$this->objects[$id]['info']['U'] = $uvalue;
2747
+				// initialize the arc4 array
2748
+				break;
2749
+
2750
+			case 'out':
2751
+				$o = &$this->objects[$id];
2752
+
2753
+				$res = "\n$id 0 obj\n<<";
2754
+				$res .= "\n/Filter /Standard";
2755
+				$res .= "\n/V 1";
2756
+				$res .= "\n/R 2";
2757
+				$res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2758
+				$res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2759
+				// and the p-value needs to be converted to account for the twos-complement approach
2760
+				$o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2761
+				$res .= "\n/P " . ($o['info']['p']);
2762
+				$res .= "\n>>\nendobj";
2763
+
2764
+				return $res;
2765
+		}
2766
+
2767
+		return null;
2768
+	}
2769
+
2770
+	protected function o_indirect_references($id, $action, $options = null)
2771
+	{
2772
+		switch ($action) {
2773
+			case 'new':
2774
+			case 'add':
2775
+				if ($id === 0) {
2776
+					$id = ++$this->numObj;
2777
+					$this->o_catalog($this->catalogId, 'names', $id);
2778
+					$this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
2779
+					$this->indirectReferenceId = $id;
2780
+				} else {
2781
+					$this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2782
+				}
2783
+				break;
2784
+			case 'out':
2785
+				$res = "\n$id 0 obj << ";
2786
+
2787
+				foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2788
+					$res .= "/$referenceObjName $referenceObjId 0 R ";
2789
+				}
2790
+
2791
+				$res .= ">> endobj";
2792
+				return $res;
2793
+		}
2794
+
2795
+		return null;
2796
+	}
2797
+
2798
+	protected function o_names($id, $action, $options = null)
2799
+	{
2800
+		switch ($action) {
2801
+			case 'new':
2802
+			case 'add':
2803
+				if ($id === 0) {
2804
+					$id = ++$this->numObj;
2805
+					$this->objects[$id] = ['t' => 'names', 'info' => [$options]];
2806
+					$this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
2807
+					$this->embeddedFilesId = $id;
2808
+				} else {
2809
+					$this->objects[$id]['info'][] = $options;
2810
+				}
2811
+				break;
2812
+			case 'out':
2813
+				$info = &$this->objects[$id]['info'];
2814
+				$res = '';
2815
+				if (count($info) > 0) {
2816
+					$res = "\n$id 0 obj << /Names [ ";
2817
+
2818
+					if ($this->encrypted) {
2819
+						$this->encryptInit($id);
2820
+					}
2821
+
2822
+					foreach ($info as $entry) {
2823
+						if ($this->encrypted) {
2824
+							$filename = $this->ARC4($entry['filename']);
2825
+						} else {
2826
+							$filename = $entry['filename'];
2827
+						}
2828
+
2829
+						$res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2830
+					}
2831
+
2832
+					$res .= "] >> endobj";
2833
+				}
2834
+				return $res;
2835
+		}
2836
+
2837
+		return null;
2838
+	}
2839
+
2840
+	protected function o_embedded_file_dictionary($id, $action, $options = null)
2841
+	{
2842
+		switch ($action) {
2843
+			case 'new':
2844
+				$embeddedFileId = ++$this->numObj;
2845
+				$options['embedded_reference'] = $embeddedFileId;
2846
+				$this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
2847
+				$this->o_embedded_file($embeddedFileId, 'new', $options);
2848
+				$options['dict_reference'] = $id;
2849
+				$this->o_names($this->embeddedFilesId, 'add', $options);
2850
+				break;
2851
+			case 'out':
2852
+				$info = &$this->objects[$id]['info'];
2853
+				$filename = $this->utf8toUtf16BE($info['filename']);
2854
+				$description = $this->utf8toUtf16BE($info['description']);
2855
+
2856
+				if ($this->encrypted) {
2857
+					$this->encryptInit($id);
2858
+					$filename = $this->ARC4($filename);
2859
+					$description = $this->ARC4($description);
2860
+				}
2861
+
2862
+				$filename = $this->filterText($filename, false, false);
2863
+				$description = $this->filterText($description, false, false);
2864
+
2865
+				$res = "\n$id 0 obj <</Type /Filespec /EF";
2866
+				$res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2867
+				$res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2868
+				$res .= " >> endobj";
2869
+				return $res;
2870
+		}
2871
+
2872
+		return null;
2873
+	}
2874
+
2875
+	protected function o_embedded_file($id, $action, $options = null): ?string
2876
+	{
2877
+		switch ($action) {
2878
+			case 'new':
2879
+				$this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
2880
+				break;
2881
+			case 'out':
2882
+				$info = &$this->objects[$id]['info'];
2883
+
2884
+				if ($this->compressionReady) {
2885
+					$filepath = $info['filepath'];
2886
+					$checksum = md5_file($filepath);
2887
+					$f = fopen($filepath, "rb");
2888
+
2889
+					$file_content_compressed = '';
2890
+					$deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
2891
+					while (($block = fread($f, 8192))) {
2892
+						$file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
2893
+					}
2894
+					$file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
2895
+					$file_size_uncompressed = ftell($f);
2896
+					fclose($f);
2897
+				} else {
2898
+					$file_content = file_get_contents($info['filepath']);
2899
+					$file_size_uncompressed = mb_strlen($file_content, '8bit');
2900
+					$checksum = md5($file_content);
2901
+				}
2902
+
2903
+				if ($this->encrypted) {
2904
+					$this->encryptInit($id);
2905
+					$checksum = $this->ARC4($checksum);
2906
+					$file_content_compressed = $this->ARC4($file_content_compressed);
2907
+				}
2908
+				$file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2909
+
2910
+				$res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2911
+					" /Type/EmbeddedFile /Filter/FlateDecode" .
2912
+					" /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2913
+
2914
+				return $res;
2915
+		}
2916
+
2917
+		return null;
2918
+	}
2919
+
2920
+	/**
2921
+	 * ARC4 functions
2922
+	 * A series of function to implement ARC4 encoding in PHP
2923
+	 */
2924
+
2925
+	/**
2926
+	 * calculate the 16 byte version of the 128 bit md5 digest of the string
2927
+	 *
2928
+	 * @param $string
2929
+	 * @return string
2930
+	 */
2931
+	function md5_16($string)
2932
+	{
2933
+		$tmp = md5($string);
2934
+		$out = '';
2935
+		for ($i = 0; $i <= 30; $i = $i + 2) {
2936
+			$out .= chr(hexdec(substr($tmp, $i, 2)));
2937
+		}
2938
+
2939
+		return $out;
2940
+	}
2941
+
2942
+	/**
2943
+	 * initialize the encryption for processing a particular object
2944
+	 *
2945
+	 * @param $id
2946
+	 */
2947
+	function encryptInit($id)
2948
+	{
2949
+		$tmp = $this->encryptionKey;
2950
+		$hex = dechex($id);
2951
+		if (mb_strlen($hex, '8bit') < 6) {
2952
+			$hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2953
+		}
2954
+		$tmp .= chr(hexdec(substr($hex, 4, 2)))
2955
+			. chr(hexdec(substr($hex, 2, 2)))
2956
+			. chr(hexdec(substr($hex, 0, 2)))
2957
+			. chr(0)
2958
+			. chr(0)
2959
+		;
2960
+		$key = $this->md5_16($tmp);
2961
+		$this->ARC4_init(substr($key, 0, 10));
2962
+	}
2963
+
2964
+	/**
2965
+	 * initialize the ARC4 encryption
2966
+	 *
2967
+	 * @param string $key
2968
+	 */
2969
+	function ARC4_init($key = '')
2970
+	{
2971
+		$this->arc4 = '';
2972
+
2973
+		// setup the control array
2974
+		if (mb_strlen($key, '8bit') == 0) {
2975
+			return;
2976
+		}
2977
+
2978
+		$k = '';
2979
+		while (mb_strlen($k, '8bit') < 256) {
2980
+			$k .= $key;
2981
+		}
2982
+
2983
+		$k = substr($k, 0, 256);
2984
+		for ($i = 0; $i < 256; $i++) {
2985
+			$this->arc4 .= chr($i);
2986
+		}
2987
+
2988
+		$j = 0;
2989
+
2990
+		for ($i = 0; $i < 256; $i++) {
2991
+			$t = $this->arc4[$i];
2992
+			$j = ($j + ord($t) + ord($k[$i])) % 256;
2993
+			$this->arc4[$i] = $this->arc4[$j];
2994
+			$this->arc4[$j] = $t;
2995
+		}
2996
+	}
2997
+
2998
+	/**
2999
+	 * ARC4 encrypt a text string
3000
+	 *
3001
+	 * @param $text
3002
+	 * @return string
3003
+	 */
3004
+	function ARC4($text)
3005
+	{
3006
+		$len = mb_strlen($text, '8bit');
3007
+		$a = 0;
3008
+		$b = 0;
3009
+		$c = $this->arc4;
3010
+		$out = '';
3011
+		for ($i = 0; $i < $len; $i++) {
3012
+			$a = ($a + 1) % 256;
3013
+			$t = $c[$a];
3014
+			$b = ($b + ord($t)) % 256;
3015
+			$c[$a] = $c[$b];
3016
+			$c[$b] = $t;
3017
+			$k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
3018
+			$out .= chr(ord($text[$i]) ^ $k);
3019
+		}
3020
+
3021
+		return $out;
3022
+	}
3023
+
3024
+	/**
3025
+	 * functions which can be called to adjust or add to the document
3026
+	 */
3027
+
3028
+	/**
3029
+	 * add a link in the document to an external URL
3030
+	 *
3031
+	 * @param $url
3032
+	 * @param $x0
3033
+	 * @param $y0
3034
+	 * @param $x1
3035
+	 * @param $y1
3036
+	 */
3037
+	function addLink($url, $x0, $y0, $x1, $y1)
3038
+	{
3039
+		$this->numObj++;
3040
+		$info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
3041
+		$this->o_annotation($this->numObj, 'new', $info);
3042
+	}
3043
+
3044
+	/**
3045
+	 * add a link in the document to an internal destination (ie. within the document)
3046
+	 *
3047
+	 * @param $label
3048
+	 * @param $x0
3049
+	 * @param $y0
3050
+	 * @param $x1
3051
+	 * @param $y1
3052
+	 */
3053
+	function addInternalLink($label, $x0, $y0, $x1, $y1)
3054
+	{
3055
+		$this->numObj++;
3056
+		$info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
3057
+		$this->o_annotation($this->numObj, 'new', $info);
3058
+	}
3059
+
3060
+	/**
3061
+	 * set the encryption of the document
3062
+	 * can be used to turn it on and/or set the passwords which it will have.
3063
+	 * also the functions that the user will have are set here, such as print, modify, add
3064
+	 *
3065
+	 * @param string $userPass
3066
+	 * @param string $ownerPass
3067
+	 * @param array $pc
3068
+	 */
3069
+	function setEncryption($userPass = '', $ownerPass = '', $pc = [])
3070
+	{
3071
+		$p = bindec("11000000");
3072
+
3073
+		$options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
3074
+
3075
+		foreach ($pc as $k => $v) {
3076
+			if ($v && isset($options[$k])) {
3077
+				$p += $options[$k];
3078
+			} else {
3079
+				if (isset($options[$v])) {
3080
+					$p += $options[$v];
3081
+				}
3082
+			}
3083
+		}
3084
+
3085
+		// implement encryption on the document
3086
+		if ($this->arc4_objnum == 0) {
3087
+			// then the block does not exist already, add it.
3088
+			$this->numObj++;
3089
+			if (mb_strlen($ownerPass) == 0) {
3090
+				$ownerPass = $userPass;
3091
+			}
3092
+
3093
+			$this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
3094
+		}
3095
+	}
3096
+
3097
+	/**
3098
+	 * should be used for internal checks, not implemented as yet
3099
+	 */
3100
+	function checkAllHere()
3101
+	{
3102
+	}
3103
+
3104
+	/**
3105
+	 * return the pdf stream as a string returned from the function
3106
+	 *
3107
+	 * @param bool $debug
3108
+	 * @return string
3109
+	 */
3110
+	function output($debug = false)
3111
+	{
3112
+		if ($debug) {
3113
+			// turn compression off
3114
+			$this->options['compression'] = false;
3115
+		}
3116
+
3117
+		if ($this->javascript) {
3118
+			$this->numObj++;
3119
+
3120
+			$js_id = $this->numObj;
3121
+			$this->o_embedjs($js_id, 'new');
3122
+			$this->o_javascript(++$this->numObj, 'new', $this->javascript);
3123
+
3124
+			$id = $this->catalogId;
3125
+
3126
+			$this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
3127
+		}
3128
+
3129
+		if ($this->fileIdentifier === '') {
3130
+			$tmp = implode('', $this->objects[$this->infoObject]['info']);
3131
+			$this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3132
+		}
3133
+
3134
+		if ($this->arc4_objnum) {
3135
+			$this->o_encryption($this->arc4_objnum, 'keys');
3136
+			$this->ARC4_init($this->encryptionKey);
3137
+		}
3138
+
3139
+		$this->checkAllHere();
3140
+
3141
+		$xref = [];
3142
+		$content = '%PDF-' . self::PDF_VERSION;
3143
+		$pos = mb_strlen($content, '8bit');
3144
+
3145
+		// pre-process o_font objects before output of all objects
3146
+		foreach ($this->objects as $k => $v) {
3147
+			if ($v['t'] === 'font') {
3148
+				$this->o_font($k, 'add');
3149
+			}
3150
+		}
3151
+
3152
+		foreach ($this->objects as $k => $v) {
3153
+			$tmp = 'o_' . $v['t'];
3154
+			$cont = $this->$tmp($k, 'out');
3155
+			$content .= $cont;
3156
+			$xref[] = $pos + 1; //+1 to account for \n at the start of each object
3157
+			$pos += mb_strlen($cont, '8bit');
3158
+		}
3159
+
3160
+		$content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3161
+
3162
+		foreach ($xref as $p) {
3163
+			$content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3164
+		}
3165
+
3166
+		$content .= "trailer\n<<\n" .
3167
+			'/Size ' . (count($xref) + 1) . "\n" .
3168
+			'/Root 1 0 R' . "\n" .
3169
+			'/Info ' . $this->infoObject . " 0 R\n"
3170
+		;
3171
+
3172
+		// if encryption has been applied to this document then add the marker for this dictionary
3173
+		if ($this->arc4_objnum > 0) {
3174
+			$content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3175
+		}
3176
+
3177
+		$content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3178
+
3179
+		// account for \n added at start of xref table
3180
+		$pos++;
3181
+
3182
+		$content .= ">>\nstartxref\n$pos\n%%EOF\n";
3183
+
3184
+		if (count($this->byteRange) > 0) {
3185
+			foreach ($this->byteRange as $k => $v) {
3186
+				$tmp = 'o_' . $v['t'];
3187
+				$this->$tmp($k, 'byterange', ['content' => &$content]);
3188
+			}
3189
+		}
3190
+
3191
+		return $content;
3192
+	}
3193
+
3194
+	/**
3195
+	 * initialize a new document
3196
+	 * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
3197
+	 * this function is called automatically by the constructor function
3198
+	 *
3199
+	 * @param array $pageSize
3200
+	 */
3201
+	private function newDocument($pageSize = [0, 0, 612, 792])
3202
+	{
3203
+		$this->numObj = 0;
3204
+		$this->objects = [];
3205
+
3206
+		$this->numObj++;
3207
+		$this->o_catalog($this->numObj, 'new');
3208
+
3209
+		$this->numObj++;
3210
+		$this->o_outlines($this->numObj, 'new');
3211
+
3212
+		$this->numObj++;
3213
+		$this->o_pages($this->numObj, 'new');
3214
+
3215
+		$this->o_pages($this->numObj, 'mediaBox', $pageSize);
3216
+		$this->currentNode = 3;
3217
+
3218
+		$this->numObj++;
3219
+		$this->o_procset($this->numObj, 'new');
3220
+
3221
+		$this->numObj++;
3222
+		$this->o_info($this->numObj, 'new');
3223
+
3224
+		$this->numObj++;
3225
+		$this->o_page($this->numObj, 'new');
3226
+
3227
+		// need to store the first page id as there is no way to get it to the user during
3228
+		// startup
3229
+		$this->firstPageId = $this->currentContents;
3230
+	}
3231
+
3232
+	/**
3233
+	 * open the font file and return a php structure containing it.
3234
+	 * first check if this one has been done before and saved in a form more suited to php
3235
+	 * note that if a php serialized version does not exist it will try and make one, but will
3236
+	 * require write access to the directory to do it... it is MUCH faster to have these serialized
3237
+	 * files.
3238
+	 *
3239
+	 * @param $font
3240
+	 */
3241
+	private function openFont($font)
3242
+	{
3243
+		// assume that $font contains the path and file but not the extension
3244
+		$name = basename($font);
3245
+		$dir = dirname($font);
3246
+
3247
+		$fontcache = $this->fontcache;
3248
+		if ($fontcache == '') {
3249
+			$fontcache = $dir;
3250
+		}
3251
+
3252
+		//$name       filename without folder and extension of font metrics
3253
+		//$dir        folder of font metrics
3254
+		//$fontcache  folder of runtime created php serialized version of font metrics.
3255
+		//            If this is not given, the same folder as the font metrics will be used.
3256
+		//            Storing and reusing serialized versions improves speed much
3257
+
3258
+		$this->addMessage("openFont: $font - $name");
3259
+
3260
+		if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3261
+			$metrics_name = "$name.afm";
3262
+		} else {
3263
+			$metrics_name = "$name.ufm";
3264
+		}
3265
+
3266
+		$cache_name = "$metrics_name.json";
3267
+		$this->addMessage("metrics: $metrics_name, cache: $cache_name");
3268
+
3269
+		if (file_exists($fontcache . '/' . $cache_name)) {
3270
+			$this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3271
+			$cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3272
+			if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3273
+				$this->addMessage('openFont: font cache is out of date, regenerating');
3274
+			} else {
3275
+				$this->fonts[$font] = $cached_font_info;
3276
+			}
3277
+		}
3278
+
3279
+		if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3280
+			// then rebuild the php_<font>.afm file from the <font>.afm file
3281
+			$this->addMessage("openFont: build php file from $dir/$metrics_name");
3282
+			$data = [];
3283
+
3284
+			// 20 => 'space'
3285
+			$data['codeToName'] = [];
3286
+
3287
+			// Since we're not going to enable Unicode for the core fonts we need to use a font-based
3288
+			// setting for Unicode support rather than a global setting.
3289
+			$data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
3290
+
3291
+			$cidtogid = '';
3292
+			if ($data['isUnicode']) {
3293
+				$cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3294
+			}
3295
+
3296
+			$file = file("$dir/$metrics_name");
3297
+
3298
+			foreach ($file as $rowA) {
3299
+				$row = trim($rowA);
3300
+				$pos = strpos($row, ' ');
3301
+
3302
+				if ($pos) {
3303
+					// then there must be some keyword
3304
+					$key = substr($row, 0, $pos);
3305
+					switch ($key) {
3306
+						case 'FontName':
3307
+						case 'FullName':
3308
+						case 'FamilyName':
3309
+						case 'PostScriptName':
3310
+						case 'Weight':
3311
+						case 'ItalicAngle':
3312
+						case 'IsFixedPitch':
3313
+						case 'CharacterSet':
3314
+						case 'UnderlinePosition':
3315
+						case 'UnderlineThickness':
3316
+						case 'Version':
3317
+						case 'EncodingScheme':
3318
+						case 'CapHeight':
3319
+						case 'XHeight':
3320
+						case 'Ascender':
3321
+						case 'Descender':
3322
+						case 'StdHW':
3323
+						case 'StdVW':
3324
+						case 'StartCharMetrics':
3325
+						case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font.  Otherwise it's too big.
3326
+							$data[$key] = trim(substr($row, $pos));
3327
+							break;
3328
+
3329
+						case 'FontBBox':
3330
+							$data[$key] = explode(' ', trim(substr($row, $pos)));
3331
+							break;
3332
+
3333
+						//C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
3334
+						case 'C': // Found in AFM files
3335
+							$bits = explode(';', trim($row));
3336
+							$dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
3337
+
3338
+							foreach ($bits as $bit) {
3339
+								$bits2 = explode(' ', trim($bit));
3340
+								if (mb_strlen($bits2[0], '8bit') == 0) {
3341
+									continue;
3342
+								}
3343
+
3344
+								if (count($bits2) > 2) {
3345
+									$dtmp[$bits2[0]] = [];
3346
+									for ($i = 1; $i < count($bits2); $i++) {
3347
+										$dtmp[$bits2[0]][] = $bits2[$i];
3348
+									}
3349
+								} else {
3350
+									if (count($bits2) == 2) {
3351
+										$dtmp[$bits2[0]] = $bits2[1];
3352
+									}
3353
+								}
3354
+							}
3355
+
3356
+							$c = (int)$dtmp['C'];
3357
+							$n = $dtmp['N'];
3358
+							$width = floatval($dtmp['WX']);
3359
+
3360
+							if ($c >= 0) {
3361
+								if (!ctype_xdigit($n) || $c != hexdec($n)) {
3362
+									$data['codeToName'][$c] = $n;
3363
+								}
3364
+								$data['C'][$c] = $width;
3365
+							} elseif (isset($n)) {
3366
+								$data['C'][$n] = $width;
3367
+							}
3368
+
3369
+							if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3370
+								$data['MissingWidth'] = $width;
3371
+							}
3372
+
3373
+							break;
3374
+
3375
+						// U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3376
+						case 'U': // Found in UFM files
3377
+							if (!$data['isUnicode']) {
3378
+								break;
3379
+							}
3380
+
3381
+							$bits = explode(';', trim($row));
3382
+							$dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
3383
+
3384
+							foreach ($bits as $bit) {
3385
+								$bits2 = explode(' ', trim($bit));
3386
+								if (mb_strlen($bits2[0], '8bit') === 0) {
3387
+									continue;
3388
+								}
3389
+
3390
+								if (count($bits2) > 2) {
3391
+									$dtmp[$bits2[0]] = [];
3392
+									for ($i = 1; $i < count($bits2); $i++) {
3393
+										$dtmp[$bits2[0]][] = $bits2[$i];
3394
+									}
3395
+								} else {
3396
+									if (count($bits2) == 2) {
3397
+										$dtmp[$bits2[0]] = $bits2[1];
3398
+									}
3399
+								}
3400
+							}
3401
+
3402
+							$c = (int)$dtmp['U'];
3403
+							$n = $dtmp['N'];
3404
+							$glyph = $dtmp['G'];
3405
+							$width = floatval($dtmp['WX']);
3406
+
3407
+							if ($c >= 0) {
3408
+								// Set values in CID to GID map
3409
+								if ($c >= 0 && $c < 0xFFFF && $glyph) {
3410
+									$cidtogid[$c * 2] = chr($glyph >> 8);
3411
+									$cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3412
+								}
3413
+
3414
+								if (!ctype_xdigit($n) || $c != hexdec($n)) {
3415
+									$data['codeToName'][$c] = $n;
3416
+								}
3417
+								$data['C'][$c] = $width;
3418
+							} elseif (isset($n)) {
3419
+								$data['C'][$n] = $width;
3420
+							}
3421
+
3422
+							if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3423
+								$data['MissingWidth'] = $width;
3424
+							}
3425
+
3426
+							break;
3427
+
3428
+						case 'KPX':
3429
+							break; // don't include them as they are not used yet
3430
+							//KPX Adieresis yacute -40
3431
+							/*$bits = explode(' ', trim($row));
3432 3432
                             $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
3433 3433
                             break;*/
3434
-                    }
3435
-                }
3436
-            }
3437
-
3438
-            if ($this->compressionReady && $this->options['compression']) {
3439
-                // then implement ZLIB based compression on CIDtoGID string
3440
-                $data['CIDtoGID_Compressed'] = true;
3441
-                $cidtogid = gzcompress($cidtogid, 6);
3442
-            }
3443
-            $data['CIDtoGID'] = base64_encode($cidtogid);
3444
-            $data['_version_'] = $this->fontcacheVersion;
3445
-            $this->fonts[$font] = $data;
3446
-
3447
-            //Because of potential trouble with php safe mode, expect that the folder already exists.
3448
-            //If not existing, this will hit performance because of missing cached results.
3449
-            if (is_dir($fontcache) && is_writable($fontcache)) {
3450
-                file_put_contents("$fontcache/$cache_name", json_encode($data, JSON_PRETTY_PRINT));
3451
-            }
3452
-            $data = null;
3453
-        }
3454
-
3455
-        if (!isset($this->fonts[$font])) {
3456
-            $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3457
-        }
3458
-    }
3459
-
3460
-    /**
3461
-     * if the font is not loaded then load it and make the required object
3462
-     * else just make it the current font
3463
-     * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
3464
-     * note that encoding='none' will need to be used for symbolic fonts
3465
-     * and 'differences' => an array of mappings between numbers 0->255 and character names.
3466
-     *
3467
-     * @param string $fontName
3468
-     * @param string $encoding
3469
-     * @param bool $set
3470
-     * @param bool $isSubsetting
3471
-     * @return int
3472
-     * @throws FontNotFoundException
3473
-     */
3474
-    function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
3475
-    {
3476
-        if ($fontName === null || $fontName === '') {
3477
-            return $this->currentFontNum;
3478
-        }
3479
-
3480
-        $ext = substr($fontName, -4);
3481
-        if ($ext === '.afm' || $ext === '.ufm') {
3482
-            $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3483
-        }
3484
-
3485
-        if (!isset($this->fonts[$fontName])) {
3486
-            $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3487
-
3488
-            // load the file
3489
-            $this->openFont($fontName);
3490
-
3491
-            if (isset($this->fonts[$fontName])) {
3492
-                $this->numObj++;
3493
-                $this->numFonts++;
3494
-
3495
-                $font = &$this->fonts[$fontName];
3496
-
3497
-                $name = basename($fontName);
3498
-                $options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
3499
-
3500
-                if (is_array($encoding)) {
3501
-                    // then encoding and differences might be set
3502
-                    if (isset($encoding['encoding'])) {
3503
-                        $options['encoding'] = $encoding['encoding'];
3504
-                    }
3505
-
3506
-                    if (isset($encoding['differences'])) {
3507
-                        $options['differences'] = $encoding['differences'];
3508
-                    }
3509
-                } else {
3510
-                    if (mb_strlen($encoding, '8bit')) {
3511
-                        // then perhaps only the encoding has been set
3512
-                        $options['encoding'] = $encoding;
3513
-                    }
3514
-                }
3515
-
3516
-                $this->o_font($this->numObj, 'new', $options);
3517
-
3518
-                if (file_exists("$fontName.ttf")) {
3519
-                    $fileSuffix = 'ttf';
3520
-                } elseif (file_exists("$fontName.TTF")) {
3521
-                    $fileSuffix = 'TTF';
3522
-                } elseif (file_exists("$fontName.pfb")) {
3523
-                    $fileSuffix = 'pfb';
3524
-                } elseif (file_exists("$fontName.PFB")) {
3525
-                    $fileSuffix = 'PFB';
3526
-                } else {
3527
-                    $fileSuffix = '';
3528
-                }
3529
-
3530
-                $font['fileSuffix'] = $fileSuffix;
3531
-
3532
-                $font['fontNum'] = $this->numFonts;
3533
-                $font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
3534
-
3535
-                // also set the differences here, note that this means that these will take effect only the
3536
-                //first time that a font is selected, else they are ignored
3537
-                if (isset($options['differences'])) {
3538
-                    $font['differences'] = $options['differences'];
3539
-                }
3540
-            }
3541
-        }
3542
-
3543
-        if ($set && isset($this->fonts[$fontName])) {
3544
-            // so if for some reason the font was not set in the last one then it will not be selected
3545
-            $this->currentBaseFont = $fontName;
3546
-
3547
-            // the next lines mean that if a new font is selected, then the current text state will be
3548
-            // applied to it as well.
3549
-            $this->currentFont = $this->currentBaseFont;
3550
-            $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3551
-        }
3552
-
3553
-        return $this->currentFontNum;
3554
-    }
3555
-
3556
-    /**
3557
-     * sets up the current font, based on the font families, and the current text state
3558
-     * note that this system is quite flexible, a bold-italic font can be completely different to a
3559
-     * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3560
-     * This function is to be called whenever the currentTextState is changed, it will update
3561
-     * the currentFont setting to whatever the appropriate family one is.
3562
-     * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3563
-     * This function will change the currentFont to whatever it should be, but will not change the
3564
-     * currentBaseFont.
3565
-     */
3566
-    private function setCurrentFont()
3567
-    {
3568
-        //   if (strlen($this->currentBaseFont) == 0){
3569
-        //     // then assume an initial font
3570
-        //     $this->selectFont($this->defaultFont);
3571
-        //   }
3572
-        //   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3573
-        //   if (strlen($this->currentTextState)
3574
-        //     && isset($this->fontFamilies[$cf])
3575
-        //       && isset($this->fontFamilies[$cf][$this->currentTextState])){
3576
-        //     // then we are in some state or another
3577
-        //     // and this font has a family, and the current setting exists within it
3578
-        //     // select the font, then return it
3579
-        //     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3580
-        //     $this->selectFont($nf,'',0);
3581
-        //     $this->currentFont = $nf;
3582
-        //     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3583
-        //   } else {
3584
-        //     // the this font must not have the right family member for the current state
3585
-        //     // simply assume the base font
3586
-        $this->currentFont = $this->currentBaseFont;
3587
-        $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3588
-        //  }
3589
-    }
3590
-
3591
-    /**
3592
-     * function for the user to find out what the ID is of the first page that was created during
3593
-     * startup - useful if they wish to add something to it later.
3594
-     *
3595
-     * @return int
3596
-     */
3597
-    function getFirstPageId()
3598
-    {
3599
-        return $this->firstPageId;
3600
-    }
3601
-
3602
-    /**
3603
-     * add content to the currently active object
3604
-     *
3605
-     * @param $content
3606
-     */
3607
-    private function addContent($content)
3608
-    {
3609
-        $this->objects[$this->currentContents]['c'] .= $content;
3610
-    }
3611
-
3612
-    /**
3613
-     * sets the color for fill operations
3614
-     *
3615
-     * @param array $color
3616
-     * @param bool  $force
3617
-     */
3618
-    function setColor($color, $force = false)
3619
-    {
3620
-        $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3621
-
3622
-        if (!$force && $this->currentColor == $new_color) {
3623
-            return;
3624
-        }
3625
-
3626
-        if (isset($new_color[3])) {
3627
-            $this->currentColor = $new_color;
3628
-            $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3629
-        } else {
3630
-            if (isset($new_color[2])) {
3631
-                $this->currentColor = $new_color;
3632
-                $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3633
-            }
3634
-        }
3635
-    }
3636
-
3637
-    /**
3638
-     * sets the color for fill operations
3639
-     *
3640
-     * @param string $fillRule
3641
-     */
3642
-    function setFillRule($fillRule)
3643
-    {
3644
-        if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3645
-            return;
3646
-        }
3647
-
3648
-        $this->fillRule = $fillRule;
3649
-    }
3650
-
3651
-    /**
3652
-     * sets the color for stroke operations
3653
-     *
3654
-     * @param array $color
3655
-     * @param bool  $force
3656
-     */
3657
-    function setStrokeColor($color, $force = false)
3658
-    {
3659
-        $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3660
-
3661
-        if (!$force && $this->currentStrokeColor == $new_color) {
3662
-            return;
3663
-        }
3664
-
3665
-        if (isset($new_color[3])) {
3666
-            $this->currentStrokeColor = $new_color;
3667
-            $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3668
-        } else {
3669
-            if (isset($new_color[2])) {
3670
-                $this->currentStrokeColor = $new_color;
3671
-                $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3672
-            }
3673
-        }
3674
-    }
3675
-
3676
-    /**
3677
-     * Set the graphics state for compositions
3678
-     *
3679
-     * @param $parameters
3680
-     */
3681
-    function setGraphicsState($parameters)
3682
-    {
3683
-        // Create a new graphics state object if necessary
3684
-        if (($gstate = array_search($parameters, $this->gstates)) === false) {
3685
-            $this->numObj++;
3686
-            $this->o_extGState($this->numObj, 'new', $parameters);
3687
-            $gstate = $this->numStates;
3688
-            $this->gstates[$gstate] = $parameters;
3689
-        }
3690
-        $this->addContent("\n/GS$gstate gs");
3691
-    }
3692
-
3693
-    /**
3694
-     * Set current blend mode & opacity for lines.
3695
-     *
3696
-     * Valid blend modes are:
3697
-     *
3698
-     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3699
-     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3700
-     * Exclusion
3701
-     *
3702
-     * @param string $mode    the blend mode to use
3703
-     * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3704
-     */
3705
-    function setLineTransparency($mode, $opacity)
3706
-    {
3707
-        static $blend_modes = [
3708
-            "Normal",
3709
-            "Multiply",
3710
-            "Screen",
3711
-            "Overlay",
3712
-            "Darken",
3713
-            "Lighten",
3714
-            "ColorDogde",
3715
-            "ColorBurn",
3716
-            "HardLight",
3717
-            "SoftLight",
3718
-            "Difference",
3719
-            "Exclusion"
3720
-        ];
3721
-
3722
-        if (!in_array($mode, $blend_modes)) {
3723
-            $mode = "Normal";
3724
-        }
3725
-
3726
-        if (is_null($this->currentLineTransparency)) {
3727
-            $this->currentLineTransparency = [];
3728
-        }
3729
-
3730
-        if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
3731
-            $this->currentLineTransparency['mode'] : '') &&
3732
-            $opacity === (key_exists('opacity', $this->currentLineTransparency) ?
3733
-            $this->currentLineTransparency["opacity"] : '')) {
3734
-            return;
3735
-        }
3736
-
3737
-        $this->currentLineTransparency["mode"] = $mode;
3738
-        $this->currentLineTransparency["opacity"] = $opacity;
3739
-
3740
-        $options = [
3741
-            "BM" => "/$mode",
3742
-            "CA" => (float)$opacity
3743
-        ];
3744
-
3745
-        $this->setGraphicsState($options);
3746
-    }
3747
-
3748
-    /**
3749
-     * Set current blend mode & opacity for filled objects.
3750
-     *
3751
-     * Valid blend modes are:
3752
-     *
3753
-     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3754
-     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3755
-     * Exclusion
3756
-     *
3757
-     * @param string $mode    the blend mode to use
3758
-     * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3759
-     */
3760
-    function setFillTransparency($mode, $opacity)
3761
-    {
3762
-        static $blend_modes = [
3763
-            "Normal",
3764
-            "Multiply",
3765
-            "Screen",
3766
-            "Overlay",
3767
-            "Darken",
3768
-            "Lighten",
3769
-            "ColorDogde",
3770
-            "ColorBurn",
3771
-            "HardLight",
3772
-            "SoftLight",
3773
-            "Difference",
3774
-            "Exclusion"
3775
-        ];
3776
-
3777
-        if (!in_array($mode, $blend_modes)) {
3778
-            $mode = "Normal";
3779
-        }
3780
-
3781
-        if (is_null($this->currentFillTransparency)) {
3782
-            $this->currentFillTransparency = [];
3783
-        }
3784
-
3785
-        if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
3786
-            $this->currentFillTransparency['mode'] : '') &&
3787
-            $opacity === (key_exists('opacity', $this->currentFillTransparency) ?
3788
-            $this->currentFillTransparency["opacity"] : '')) {
3789
-            return;
3790
-        }
3791
-
3792
-        $this->currentFillTransparency["mode"] = $mode;
3793
-        $this->currentFillTransparency["opacity"] = $opacity;
3794
-
3795
-        $options = [
3796
-            "BM" => "/$mode",
3797
-            "ca" => (float)$opacity,
3798
-        ];
3799
-
3800
-        $this->setGraphicsState($options);
3801
-    }
3802
-
3803
-    /**
3804
-     * draw a line from one set of coordinates to another
3805
-     *
3806
-     * @param float $x1
3807
-     * @param float $y1
3808
-     * @param float $x2
3809
-     * @param float $y2
3810
-     * @param bool  $stroke
3811
-     */
3812
-    function line($x1, $y1, $x2, $y2, $stroke = true)
3813
-    {
3814
-        $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3815
-
3816
-        if ($stroke) {
3817
-            $this->addContent(' S');
3818
-        }
3819
-    }
3820
-
3821
-    /**
3822
-     * draw a bezier curve based on 4 control points
3823
-     *
3824
-     * @param float $x0
3825
-     * @param float $y0
3826
-     * @param float $x1
3827
-     * @param float $y1
3828
-     * @param float $x2
3829
-     * @param float $y2
3830
-     * @param float $x3
3831
-     * @param float $y3
3832
-     */
3833
-    function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3834
-    {
3835
-        // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3836
-        // as the control points for the curve.
3837
-        $this->addContent(
3838
-            sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3839
-        );
3840
-    }
3841
-
3842
-    /**
3843
-     * draw a part of an ellipse
3844
-     *
3845
-     * @param float $x0
3846
-     * @param float $y0
3847
-     * @param float $astart
3848
-     * @param float $afinish
3849
-     * @param float $r1
3850
-     * @param float $r2
3851
-     * @param float $angle
3852
-     * @param int $nSeg
3853
-     */
3854
-    function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3855
-    {
3856
-        $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3857
-    }
3858
-
3859
-    /**
3860
-     * draw a filled ellipse
3861
-     *
3862
-     * @param float $x0
3863
-     * @param float $y0
3864
-     * @param float $r1
3865
-     * @param float $r2
3866
-     * @param float $angle
3867
-     * @param int $nSeg
3868
-     * @param float $astart
3869
-     * @param float $afinish
3870
-     */
3871
-    function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3872
-    {
3873
-        $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3874
-    }
3875
-
3876
-    /**
3877
-     * @param float $x
3878
-     * @param float $y
3879
-     */
3880
-    function lineTo($x, $y)
3881
-    {
3882
-        $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3883
-    }
3884
-
3885
-    /**
3886
-     * @param float $x
3887
-     * @param float $y
3888
-     */
3889
-    function moveTo($x, $y)
3890
-    {
3891
-        $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3892
-    }
3893
-
3894
-    /**
3895
-     * draw a bezier curve based on 4 control points
3896
-     *
3897
-     * @param float $x1
3898
-     * @param float $y1
3899
-     * @param float $x2
3900
-     * @param float $y2
3901
-     * @param float $x3
3902
-     * @param float $y3
3903
-     */
3904
-    function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3905
-    {
3906
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3907
-    }
3908
-
3909
-    /**
3910
-     * draw a bezier curve based on 4 control points
3911
-     *
3912
-     * @param float $cpx
3913
-     * @param float $cpy
3914
-     * @param float $x
3915
-     * @param float $y
3916
-     */
3917
-    function quadTo($cpx, $cpy, $x, $y)
3918
-    {
3919
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3920
-    }
3921
-
3922
-    function closePath()
3923
-    {
3924
-        $this->addContent(' h');
3925
-    }
3926
-
3927
-    function endPath()
3928
-    {
3929
-        $this->addContent(' n');
3930
-    }
3931
-
3932
-    /**
3933
-     * draw an ellipse
3934
-     * note that the part and filled ellipse are just special cases of this function
3935
-     *
3936
-     * draws an ellipse in the current line style
3937
-     * centered at $x0,$y0, radii $r1,$r2
3938
-     * if $r2 is not set, then a circle is drawn
3939
-     * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3940
-     * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3941
-     * pretty crappy shape at 2, as we are approximating with bezier curves.
3942
-     *
3943
-     * @param float $x0
3944
-     * @param float $y0
3945
-     * @param float $r1
3946
-     * @param float $r2
3947
-     * @param float $angle
3948
-     * @param int   $nSeg
3949
-     * @param float $astart
3950
-     * @param float $afinish
3951
-     * @param bool  $close
3952
-     * @param bool  $fill
3953
-     * @param bool  $stroke
3954
-     * @param bool  $incomplete
3955
-     */
3956
-    function ellipse(
3957
-        $x0,
3958
-        $y0,
3959
-        $r1,
3960
-        $r2 = 0,
3961
-        $angle = 0,
3962
-        $nSeg = 8,
3963
-        $astart = 0,
3964
-        $afinish = 360,
3965
-        $close = true,
3966
-        $fill = false,
3967
-        $stroke = true,
3968
-        $incomplete = false
3969
-    ) {
3970
-        if ($r1 == 0) {
3971
-            return;
3972
-        }
3973
-
3974
-        if ($r2 == 0) {
3975
-            $r2 = $r1;
3976
-        }
3977
-
3978
-        if ($nSeg < 2) {
3979
-            $nSeg = 2;
3980
-        }
3981
-
3982
-        $astart = deg2rad((float)$astart);
3983
-        $afinish = deg2rad((float)$afinish);
3984
-        $totalAngle = $afinish - $astart;
3985
-
3986
-        $dt = $totalAngle / $nSeg;
3987
-        $dtm = $dt / 3;
3988
-
3989
-        if ($angle != 0) {
3990
-            $a = -1 * deg2rad((float)$angle);
3991
-
3992
-            $this->addContent(
3993
-                sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
3994
-            );
3995
-
3996
-            $x0 = 0;
3997
-            $y0 = 0;
3998
-        }
3999
-
4000
-        $t1 = $astart;
4001
-        $a0 = $x0 + $r1 * cos($t1);
4002
-        $b0 = $y0 + $r2 * sin($t1);
4003
-        $c0 = -$r1 * sin($t1);
4004
-        $d0 = $r2 * cos($t1);
4005
-
4006
-        if (!$incomplete) {
4007
-            $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4008
-        }
4009
-
4010
-        for ($i = 1; $i <= $nSeg; $i++) {
4011
-            // draw this bit of the total curve
4012
-            $t1 = $i * $dt + $astart;
4013
-            $a1 = $x0 + $r1 * cos($t1);
4014
-            $b1 = $y0 + $r2 * sin($t1);
4015
-            $c1 = -$r1 * sin($t1);
4016
-            $d1 = $r2 * cos($t1);
4017
-
4018
-            $this->addContent(
4019
-                sprintf(
4020
-                    "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
4021
-                    ($a0 + $c0 * $dtm),
4022
-                    ($b0 + $d0 * $dtm),
4023
-                    ($a1 - $c1 * $dtm),
4024
-                    ($b1 - $d1 * $dtm),
4025
-                    $a1,
4026
-                    $b1
4027
-                )
4028
-            );
4029
-
4030
-            $a0 = $a1;
4031
-            $b0 = $b1;
4032
-            $c0 = $c1;
4033
-            $d0 = $d1;
4034
-        }
4035
-
4036
-        if (!$incomplete) {
4037
-            if ($fill) {
4038
-                $this->addContent(' f');
4039
-            }
4040
-
4041
-            if ($stroke) {
4042
-                if ($close) {
4043
-                    $this->addContent(' s'); // small 's' signifies closing the path as well
4044
-                } else {
4045
-                    $this->addContent(' S');
4046
-                }
4047
-            }
4048
-        }
4049
-
4050
-        if ($angle != 0) {
4051
-            $this->addContent(' Q');
4052
-        }
4053
-    }
4054
-
4055
-    /**
4056
-     * this sets the line drawing style.
4057
-     * width, is the thickness of the line in user units
4058
-     * cap is the type of cap to put on the line, values can be 'butt','round','square'
4059
-     *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
4060
-     *    end of the line.
4061
-     * join can be 'miter', 'round', 'bevel'
4062
-     * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
4063
-     *   on and off dashes.
4064
-     *   (2) represents 2 on, 2 off, 2 on , 2 off ...
4065
-     *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4066
-     * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4067
-     *
4068
-     * @param float  $width
4069
-     * @param string $cap
4070
-     * @param string $join
4071
-     * @param array  $dash
4072
-     * @param int    $phase
4073
-     */
4074
-    function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4075
-    {
4076
-        // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
4077
-        $string = '';
4078
-
4079
-        if ($width > 0) {
4080
-            $string .= "$width w";
4081
-        }
4082
-
4083
-        $ca = ['butt' => 0, 'round' => 1, 'square' => 2];
4084
-
4085
-        if (isset($ca[$cap])) {
4086
-            $string .= " $ca[$cap] J";
4087
-        }
4088
-
4089
-        $ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
4090
-
4091
-        if (isset($ja[$join])) {
4092
-            $string .= " $ja[$join] j";
4093
-        }
4094
-
4095
-        if (is_array($dash)) {
4096
-            $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4097
-        }
4098
-
4099
-        $this->currentLineStyle = $string;
4100
-        $this->addContent("\n$string");
4101
-    }
4102
-
4103
-    /**
4104
-     * draw a polygon, the syntax for this is similar to the GD polygon command
4105
-     *
4106
-     * @param float[] $p
4107
-     * @param bool    $fill
4108
-     */
4109
-    public function polygon(array $p, bool $fill = false): void
4110
-    {
4111
-        $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4112
-
4113
-        $n = count($p);
4114
-        for ($i = 2; $i < $n; $i = $i + 2) {
4115
-            $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4116
-        }
4117
-
4118
-        if ($fill) {
4119
-            $this->addContent(' f');
4120
-        } else {
4121
-            $this->addContent(' S');
4122
-        }
4123
-    }
4124
-
4125
-    /**
4126
-     * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4127
-     * the coordinates of the upper-right corner
4128
-     *
4129
-     * @param float $x1
4130
-     * @param float $y1
4131
-     * @param float $width
4132
-     * @param float $height
4133
-     */
4134
-    function filledRectangle($x1, $y1, $width, $height)
4135
-    {
4136
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
4137
-    }
4138
-
4139
-    /**
4140
-     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4141
-     * the coordinates of the upper-right corner
4142
-     *
4143
-     * @param float $x1
4144
-     * @param float $y1
4145
-     * @param float $width
4146
-     * @param float $height
4147
-     */
4148
-    function rectangle($x1, $y1, $width, $height)
4149
-    {
4150
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
4151
-    }
4152
-
4153
-    /**
4154
-     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4155
-     * the coordinates of the upper-right corner
4156
-     *
4157
-     * @param float $x1
4158
-     * @param float $y1
4159
-     * @param float $width
4160
-     * @param float $height
4161
-     */
4162
-    function rect($x1, $y1, $width, $height)
4163
-    {
4164
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
4165
-    }
4166
-
4167
-    function stroke(bool $close = false)
4168
-    {
4169
-        $this->addContent("\n" . ($close ? "s" : "S"));
4170
-    }
4171
-
4172
-    function fill()
4173
-    {
4174
-        $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4175
-    }
4176
-
4177
-    function fillStroke(bool $close = false)
4178
-    {
4179
-        $this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
4180
-    }
4181
-
4182
-    /**
4183
-     * @param string $subtype
4184
-     * @param integer $x
4185
-     * @param integer $y
4186
-     * @param integer $w
4187
-     * @param integer $h
4188
-     * @return int
4189
-     */
4190
-    function addXObject($subtype, $x, $y, $w, $h)
4191
-    {
4192
-        $id = ++$this->numObj;
4193
-        $this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
4194
-        return $id;
4195
-    }
4196
-
4197
-    /**
4198
-     * @param integer $numXObject
4199
-     * @param string $type
4200
-     * @param array $options
4201
-     */
4202
-    function setXObjectResource($numXObject, $type, $options)
4203
-    {
4204
-        if (in_array($type, ['procset', 'font', 'xObject'])) {
4205
-            $this->o_xobject($numXObject, $type, $options);
4206
-        }
4207
-    }
4208
-
4209
-    /**
4210
-     * add signature
4211
-     *
4212
-     * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
4213
-     *
4214
-     * $signatureId = $cpdf->addSignature([
4215
-     *   'signcert' => file_get_contents('dompdf.crt'),
4216
-     *   'privkey' => file_get_contents('dompdf.key'),
4217
-     *   'password' => 'password',
4218
-     *   'name' => 'DomPDF DEMO',
4219
-     *   'location' => 'Home',
4220
-     *   'reason' => 'First Form',
4221
-     *   'contactinfo' => 'info'
4222
-     * ]);
4223
-     * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
4224
-     *
4225
-     * @param string $signcert
4226
-     * @param string $privkey
4227
-     * @param string $password
4228
-     * @param string|null $name
4229
-     * @param string|null $location
4230
-     * @param string|null $reason
4231
-     * @param string|null $contactinfo
4232
-     * @return int
4233
-     */
4234
-    function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
4235
-        $sigId = ++$this->numObj;
4236
-        $this->o_sig($sigId, 'new', [
4237
-          'SignCert' => $signcert,
4238
-          'PrivKey' => $privkey,
4239
-          'Password' => $password,
4240
-          'Name' => $name,
4241
-          'Location' => $location,
4242
-          'Reason' => $reason,
4243
-          'ContactInfo' => $contactinfo
4244
-        ]);
4245
-
4246
-        return $sigId;
4247
-    }
4248
-
4249
-    /**
4250
-     * add field to form
4251
-     *
4252
-     * @param string $type ACROFORM_FIELD_*
4253
-     * @param string $name
4254
-     * @param $x0
4255
-     * @param $y0
4256
-     * @param $x1
4257
-     * @param $y1
4258
-     * @param integer $ff Field Flag ACROFORM_FIELD_*_*
4259
-     * @param float $size
4260
-     * @param array $color
4261
-     * @return int
4262
-     */
4263
-    public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4264
-    {
4265
-        if (!$this->numFonts) {
4266
-            $this->selectFont($this->defaultFont);
4267
-        }
4268
-
4269
-        $color = implode(' ', $color) . ' rg';
4270
-
4271
-        $currentFontNum = $this->currentFontNum;
4272
-        $font = array_filter(
4273
-            $this->objects[$this->currentNode]['info']['fonts'],
4274
-            function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4275
-        );
4276
-
4277
-        $this->o_acroform($this->acroFormId, 'font',
4278
-          ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4279
-
4280
-        $fieldId = ++$this->numObj;
4281
-        $this->o_field($fieldId, 'new', [
4282
-          'rect' => [$x0, $y0, $x1, $y1],
4283
-          'F' => 4,
4284
-          'FT' => "/$type",
4285
-          'T' => $name,
4286
-          'Ff' => $ff,
4287
-          'pageid' => $this->currentPage,
4288
-          'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4289
-        ]);
4290
-
4291
-        return $fieldId;
4292
-    }
4293
-
4294
-    /**
4295
-     * set Field value
4296
-     *
4297
-     * @param integer $numFieldObj
4298
-     * @param string $value
4299
-     */
4300
-    public function setFormFieldValue($numFieldObj, $value)
4301
-    {
4302
-        $this->o_field($numFieldObj, 'set', ['value' => $value]);
4303
-    }
4304
-
4305
-    /**
4306
-     * set Field value (reference)
4307
-     *
4308
-     * @param integer $numFieldObj
4309
-     * @param integer $numObj Object number
4310
-     */
4311
-    public function setFormFieldRefValue($numFieldObj, $numObj)
4312
-    {
4313
-        $this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
4314
-    }
4315
-
4316
-    /**
4317
-     * set Field Appearanc (reference)
4318
-     *
4319
-     * @param integer $numFieldObj
4320
-     * @param integer $normalNumObj
4321
-     * @param integer|null $rolloverNumObj
4322
-     * @param integer|null $downNumObj
4323
-     */
4324
-    public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
4325
-    {
4326
-        $appearance['N'] = $normalNumObj;
4327
-
4328
-        if ($rolloverNumObj !== null) {
4329
-            $appearance['R'] = $rolloverNumObj;
4330
-        }
4331
-
4332
-        if ($downNumObj !== null) {
4333
-            $appearance['D'] = $downNumObj;
4334
-        }
4335
-
4336
-        $this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
4337
-    }
4338
-
4339
-    /**
4340
-     * set Choice Field option values
4341
-     *
4342
-     * @param integer $numFieldObj
4343
-     * @param array $value
4344
-     */
4345
-    public function setFormFieldOpt($numFieldObj, $value)
4346
-    {
4347
-        $this->o_field($numFieldObj, 'set', ['options' => $value]);
4348
-    }
4349
-
4350
-    /**
4351
-     * add form to document
4352
-     *
4353
-     * @param integer $sigFlags
4354
-     * @param boolean $needAppearances
4355
-     */
4356
-    public function addForm($sigFlags = 0, $needAppearances = false)
4357
-    {
4358
-        $this->acroFormId = ++$this->numObj;
4359
-        $this->o_acroform($this->acroFormId, 'new', [
4360
-          'NeedAppearances' => $needAppearances ? 'true' : 'false',
4361
-          'SigFlags' => $sigFlags
4362
-        ]);
4363
-    }
4364
-
4365
-    /**
4366
-     * save the current graphic state
4367
-     */
4368
-    function save()
4369
-    {
4370
-        // we must reset the color cache or it will keep bad colors after clipping
4371
-        $this->currentColor = null;
4372
-        $this->currentStrokeColor = null;
4373
-        $this->addContent("\nq");
4374
-    }
4375
-
4376
-    /**
4377
-     * restore the last graphic state
4378
-     */
4379
-    function restore()
4380
-    {
4381
-        // we must reset the color cache or it will keep bad colors after clipping
4382
-        $this->currentColor = null;
4383
-        $this->currentStrokeColor = null;
4384
-        $this->addContent("\nQ");
4385
-    }
4386
-
4387
-    /**
4388
-     * draw a clipping rectangle, all the elements added after this will be clipped
4389
-     *
4390
-     * @param float $x1
4391
-     * @param float $y1
4392
-     * @param float $width
4393
-     * @param float $height
4394
-     */
4395
-    function clippingRectangle($x1, $y1, $width, $height)
4396
-    {
4397
-        $this->save();
4398
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
4399
-    }
4400
-
4401
-    /**
4402
-     * draw a clipping rounded rectangle, all the elements added after this will be clipped
4403
-     *
4404
-     * @param float $x1
4405
-     * @param float $y1
4406
-     * @param float $w
4407
-     * @param float $h
4408
-     * @param float $rTL
4409
-     * @param float $rTR
4410
-     * @param float $rBR
4411
-     * @param float $rBL
4412
-     */
4413
-    function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4414
-    {
4415
-        $this->save();
4416
-
4417
-        // start: top edge, left end
4418
-        $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
4419
-
4420
-        // line: bottom edge, left end
4421
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
4422
-
4423
-        // curve: bottom-left corner
4424
-        $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
4425
-
4426
-        // line: right edge, bottom end
4427
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
4428
-
4429
-        // curve: bottom-right corner
4430
-        $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
4431
-
4432
-        // line: right edge, top end
4433
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
4434
-
4435
-        // curve: bottom-right corner
4436
-        $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
4437
-
4438
-        // line: bottom edge, right end
4439
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
4440
-
4441
-        // curve: top-right corner
4442
-        $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
4443
-
4444
-        // line: top edge, left end
4445
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
4446
-
4447
-        // Close & clip
4448
-        $this->addContent(" W n");
4449
-    }
4450
-
4451
-    /**
4452
-     * draw a clipping polygon, the syntax for this is similar to the GD polygon command
4453
-     *
4454
-     * @param float[] $p
4455
-     */
4456
-    public function clippingPolygon(array $p): void
4457
-    {
4458
-        $this->save();
4459
-
4460
-        $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4461
-
4462
-        $n = count($p);
4463
-        for ($i = 2; $i < $n; $i = $i + 2) {
4464
-            $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4465
-        }
4466
-
4467
-        $this->addContent("W n");
4468
-    }
4469
-
4470
-    /**
4471
-     * ends the last clipping shape
4472
-     */
4473
-    function clippingEnd()
4474
-    {
4475
-        $this->restore();
4476
-    }
4477
-
4478
-    /**
4479
-     * scale
4480
-     *
4481
-     * @param float $s_x scaling factor for width as percent
4482
-     * @param float $s_y scaling factor for height as percent
4483
-     * @param float $x   Origin abscissa
4484
-     * @param float $y   Origin ordinate
4485
-     */
4486
-    function scale($s_x, $s_y, $x, $y)
4487
-    {
4488
-        $y = $this->currentPageSize["height"] - $y;
4489
-
4490
-        $tm = [
4491
-            $s_x,
4492
-            0,
4493
-            0,
4494
-            $s_y,
4495
-            $x * (1 - $s_x),
4496
-            $y * (1 - $s_y)
4497
-        ];
4498
-
4499
-        $this->transform($tm);
4500
-    }
4501
-
4502
-    /**
4503
-     * translate
4504
-     *
4505
-     * @param float $t_x movement to the right
4506
-     * @param float $t_y movement to the bottom
4507
-     */
4508
-    function translate($t_x, $t_y)
4509
-    {
4510
-        $tm = [
4511
-            1,
4512
-            0,
4513
-            0,
4514
-            1,
4515
-            $t_x,
4516
-            -$t_y
4517
-        ];
4518
-
4519
-        $this->transform($tm);
4520
-    }
4521
-
4522
-    /**
4523
-     * rotate
4524
-     *
4525
-     * @param float $angle angle in degrees for counter-clockwise rotation
4526
-     * @param float $x     Origin abscissa
4527
-     * @param float $y     Origin ordinate
4528
-     */
4529
-    function rotate($angle, $x, $y)
4530
-    {
4531
-        $y = $this->currentPageSize["height"] - $y;
4532
-
4533
-        $a = deg2rad($angle);
4534
-        $cos_a = cos($a);
4535
-        $sin_a = sin($a);
4536
-
4537
-        $tm = [
4538
-            $cos_a,
4539
-            -$sin_a,
4540
-            $sin_a,
4541
-            $cos_a,
4542
-            $x - $sin_a * $y - $cos_a * $x,
4543
-            $y - $cos_a * $y + $sin_a * $x,
4544
-        ];
4545
-
4546
-        $this->transform($tm);
4547
-    }
4548
-
4549
-    /**
4550
-     * skew
4551
-     *
4552
-     * @param float $angle_x
4553
-     * @param float $angle_y
4554
-     * @param float $x Origin abscissa
4555
-     * @param float $y Origin ordinate
4556
-     */
4557
-    function skew($angle_x, $angle_y, $x, $y)
4558
-    {
4559
-        $y = $this->currentPageSize["height"] - $y;
4560
-
4561
-        $tan_x = tan(deg2rad($angle_x));
4562
-        $tan_y = tan(deg2rad($angle_y));
4563
-
4564
-        $tm = [
4565
-            1,
4566
-            -$tan_y,
4567
-            -$tan_x,
4568
-            1,
4569
-            $tan_x * $y,
4570
-            $tan_y * $x,
4571
-        ];
4572
-
4573
-        $this->transform($tm);
4574
-    }
4575
-
4576
-    /**
4577
-     * apply graphic transformations
4578
-     *
4579
-     * @param array $tm transformation matrix
4580
-     */
4581
-    function transform($tm)
4582
-    {
4583
-        $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
4584
-    }
4585
-
4586
-    /**
4587
-     * add a new page to the document
4588
-     * this also makes the new page the current active object
4589
-     *
4590
-     * @param int $insert
4591
-     * @param int $id
4592
-     * @param string $pos
4593
-     * @return int
4594
-     */
4595
-    function newPage($insert = 0, $id = 0, $pos = 'after')
4596
-    {
4597
-        // if there is a state saved, then go up the stack closing them
4598
-        // then on the new page, re-open them with the right setings
4599
-
4600
-        if ($this->nStateStack) {
4601
-            for ($i = $this->nStateStack; $i >= 1; $i--) {
4602
-                $this->restoreState($i);
4603
-            }
4604
-        }
4605
-
4606
-        $this->numObj++;
4607
-
4608
-        if ($insert) {
4609
-            // the id from the ezPdf class is the id of the contents of the page, not the page object itself
4610
-            // query that object to find the parent
4611
-            $rid = $this->objects[$id]['onPage'];
4612
-            $opt = ['rid' => $rid, 'pos' => $pos];
4613
-            $this->o_page($this->numObj, 'new', $opt);
4614
-        } else {
4615
-            $this->o_page($this->numObj, 'new');
4616
-        }
4617
-
4618
-        // if there is a stack saved, then put that onto the page
4619
-        if ($this->nStateStack) {
4620
-            for ($i = 1; $i <= $this->nStateStack; $i++) {
4621
-                $this->saveState($i);
4622
-            }
4623
-        }
4624
-
4625
-        // and if there has been a stroke or fill color set, then transfer them
4626
-        if (isset($this->currentColor)) {
4627
-            $this->setColor($this->currentColor, true);
4628
-        }
4629
-
4630
-        if (isset($this->currentStrokeColor)) {
4631
-            $this->setStrokeColor($this->currentStrokeColor, true);
4632
-        }
4633
-
4634
-        // if there is a line style set, then put this in too
4635
-        if (mb_strlen($this->currentLineStyle, '8bit')) {
4636
-            $this->addContent("\n$this->currentLineStyle");
4637
-        }
4638
-
4639
-        // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
4640
-        return $this->currentContents;
4641
-    }
4642
-
4643
-    /**
4644
-     * Streams the PDF to the client.
4645
-     *
4646
-     * @param string $filename The filename to present to the client.
4647
-     * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
4648
-     */
4649
-    function stream($filename = "document.pdf", $options = [])
4650
-    {
4651
-        if (headers_sent()) {
4652
-            die("Unable to stream pdf: headers already sent");
4653
-        }
4654
-
4655
-        if (!isset($options["compress"])) $options["compress"] = true;
4656
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4657
-
4658
-        $debug = !$options['compress'];
4659
-        $tmp = ltrim($this->output($debug));
4660
-
4661
-        header("Cache-Control: private");
4662
-        header("Content-Type: application/pdf");
4663
-        header("Content-Length: " . mb_strlen($tmp, "8bit"));
4664
-
4665
-        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4666
-        $attachment = $options["Attachment"] ? "attachment" : "inline";
4667
-
4668
-        $encoding = mb_detect_encoding($filename);
4669
-        $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
4670
-        $fallbackfilename = str_replace("\"", "", $fallbackfilename);
4671
-        $encodedfilename = rawurlencode($filename);
4672
-
4673
-        $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
4674
-        if ($fallbackfilename !== $filename) {
4675
-            $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
4676
-        }
4677
-        header($contentDisposition);
4678
-
4679
-        echo $tmp;
4680
-        flush();
4681
-    }
4682
-
4683
-    /**
4684
-     * return the height in units of the current font in the given size
4685
-     *
4686
-     * @param float $size
4687
-     *
4688
-     * @return float
4689
-     */
4690
-    public function getFontHeight(float $size): float
4691
-    {
4692
-        if (!$this->numFonts) {
4693
-            $this->selectFont($this->defaultFont);
4694
-        }
4695
-
4696
-        $font = $this->fonts[$this->currentFont];
4697
-
4698
-        // for the current font, and the given size, what is the height of the font in user units
4699
-        if (isset($font['Ascender']) && isset($font['Descender'])) {
4700
-            $h = $font['Ascender'] - $font['Descender'];
4701
-        } else {
4702
-            $h = $font['FontBBox'][3] - $font['FontBBox'][1];
4703
-        }
4704
-
4705
-        // have to adjust by a font offset for Windows fonts.  unfortunately it looks like
4706
-        // the bounding box calculations are wrong and I don't know why.
4707
-        if (isset($font['FontHeightOffset'])) {
4708
-            // For CourierNew from Windows this needs to be -646 to match the
4709
-            // Adobe native Courier font.
4710
-            //
4711
-            // For FreeMono from GNU this needs to be -337 to match the
4712
-            // Courier font.
4713
-            //
4714
-            // Both have been added manually to the .afm and .ufm files.
4715
-            $h += (int)$font['FontHeightOffset'];
4716
-        }
4717
-
4718
-        return $size * $h / 1000;
4719
-    }
4720
-
4721
-    /**
4722
-     * @param float $size
4723
-     *
4724
-     * @return float
4725
-     */
4726
-    public function getFontXHeight(float $size): float
4727
-    {
4728
-        if (!$this->numFonts) {
4729
-            $this->selectFont($this->defaultFont);
4730
-        }
4731
-
4732
-        $font = $this->fonts[$this->currentFont];
4733
-
4734
-        // for the current font, and the given size, what is the height of the font in user units
4735
-        if (isset($font['XHeight'])) {
4736
-            $xh = $font['Ascender'] - $font['Descender'];
4737
-        } else {
4738
-            $xh = $this->getFontHeight($size) / 2;
4739
-        }
4740
-
4741
-        return $size * $xh / 1000;
4742
-    }
4743
-
4744
-    /**
4745
-     * return the font descender, this will normally return a negative number
4746
-     * if you add this number to the baseline, you get the level of the bottom of the font
4747
-     * it is in the pdf user units
4748
-     *
4749
-     * @param float $size
4750
-     *
4751
-     * @return float
4752
-     */
4753
-    public function getFontDescender(float $size): float
4754
-    {
4755
-        // note that this will most likely return a negative value
4756
-        if (!$this->numFonts) {
4757
-            $this->selectFont($this->defaultFont);
4758
-        }
4759
-
4760
-        //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
4761
-        $h = $this->fonts[$this->currentFont]['Descender'];
4762
-
4763
-        return $size * $h / 1000;
4764
-    }
4765
-
4766
-    /**
4767
-     * filter the text, this is applied to all text just before being inserted into the pdf document
4768
-     * it escapes the various things that need to be escaped, and so on
4769
-     *
4770
-     * @param $text
4771
-     * @param bool $bom
4772
-     * @param bool $convert_encoding
4773
-     * @return string
4774
-     */
4775
-    function filterText($text, $bom = true, $convert_encoding = true)
4776
-    {
4777
-        if (!$this->numFonts) {
4778
-            $this->selectFont($this->defaultFont);
4779
-        }
4780
-
4781
-        if ($convert_encoding) {
4782
-            $cf = $this->currentFont;
4783
-            if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4784
-                $text = $this->utf8toUtf16BE($text, $bom);
4785
-            } else {
4786
-                //$text = html_entity_decode($text, ENT_QUOTES);
4787
-                $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4788
-            }
4789
-        } elseif ($bom) {
4790
-            $text = $this->utf8toUtf16BE($text, $bom);
4791
-        }
4792
-
4793
-        // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4794
-        return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
4795
-    }
4796
-
4797
-    /**
4798
-     * return array containing codepoints (UTF-8 character values) for the
4799
-     * string passed in.
4800
-     *
4801
-     * based on the excellent TCPDF code by Nicola Asuni and the
4802
-     * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4803
-     *
4804
-     * @param string $text UTF-8 string to process
4805
-     * @return array UTF-8 codepoints array for the string
4806
-     */
4807
-    function utf8toCodePointsArray(&$text)
4808
-    {
4809
-        $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4810
-        $unicode = []; // array containing unicode values
4811
-        $bytes = []; // array containing single character byte sequences
4812
-        $numbytes = 1; // number of octets needed to represent the UTF-8 character
4813
-
4814
-        for ($i = 0; $i < $length; $i++) {
4815
-            $c = ord($text[$i]); // get one string character at time
4816
-            if (count($bytes) === 0) { // get starting octect
4817
-                if ($c <= 0x7F) {
4818
-                    $unicode[] = $c; // use the character "as is" because is ASCII
4819
-                    $numbytes = 1;
4820
-                } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4821
-                    $bytes[] = ($c - 0xC0) << 0x06;
4822
-                    $numbytes = 2;
4823
-                } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4824
-                    $bytes[] = ($c - 0xE0) << 0x0C;
4825
-                    $numbytes = 3;
4826
-                } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4827
-                    $bytes[] = ($c - 0xF0) << 0x12;
4828
-                    $numbytes = 4;
4829
-                } else {
4830
-                    // use replacement character for other invalid sequences
4831
-                    $unicode[] = 0xFFFD;
4832
-                    $bytes = [];
4833
-                    $numbytes = 1;
4834
-                }
4835
-            } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4836
-                $bytes[] = $c - 0x80;
4837
-                if (count($bytes) === $numbytes) {
4838
-                    // compose UTF-8 bytes to a single unicode value
4839
-                    $c = $bytes[0];
4840
-                    for ($j = 1; $j < $numbytes; $j++) {
4841
-                        $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4842
-                    }
4843
-                    if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
4844
-                        // The definition of UTF-8 prohibits encoding character numbers between
4845
-                        // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4846
-                        // encoding form (as surrogate pairs) and do not directly represent
4847
-                        // characters.
4848
-                        $unicode[] = 0xFFFD; // use replacement character
4849
-                    } else {
4850
-                        $unicode[] = $c; // add char to array
4851
-                    }
4852
-                    // reset data for next char
4853
-                    $bytes = [];
4854
-                    $numbytes = 1;
4855
-                }
4856
-            } else {
4857
-                // use replacement character for other invalid sequences
4858
-                $unicode[] = 0xFFFD;
4859
-                $bytes = [];
4860
-                $numbytes = 1;
4861
-            }
4862
-        }
4863
-
4864
-        return $unicode;
4865
-    }
4866
-
4867
-    /**
4868
-     * convert UTF-8 to UTF-16 with an additional byte order marker
4869
-     * at the front if required.
4870
-     *
4871
-     * based on the excellent TCPDF code by Nicola Asuni and the
4872
-     * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4873
-     *
4874
-     * @param string  $text UTF-8 string to process
4875
-     * @param boolean $bom  whether to add the byte order marker
4876
-     * @return string UTF-16 result string
4877
-     */
4878
-    function utf8toUtf16BE(&$text, $bom = true)
4879
-    {
4880
-        $out = $bom ? "\xFE\xFF" : '';
4881
-
4882
-        $unicode = $this->utf8toCodePointsArray($text);
4883
-        foreach ($unicode as $c) {
4884
-            if ($c === 0xFFFD) {
4885
-                $out .= "\xFF\xFD"; // replacement character
4886
-            } elseif ($c < 0x10000) {
4887
-                $out .= chr($c >> 0x08) . chr($c & 0xFF);
4888
-            } else {
4889
-                $c -= 0x10000;
4890
-                $w1 = 0xD800 | ($c >> 0x10);
4891
-                $w2 = 0xDC00 | ($c & 0x3FF);
4892
-                $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4893
-            }
4894
-        }
4895
-
4896
-        return $out;
4897
-    }
4898
-
4899
-    /**
4900
-     * given a start position and information about how text is to be laid out, calculate where
4901
-     * on the page the text will end
4902
-     *
4903
-     * @param $x
4904
-     * @param $y
4905
-     * @param $angle
4906
-     * @param $size
4907
-     * @param $wa
4908
-     * @param $text
4909
-     * @return array
4910
-     */
4911
-    private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4912
-    {
4913
-        // given this information return an array containing x and y for the end position as elements 0 and 1
4914
-        $w = $this->getTextWidth($size, $text);
4915
-
4916
-        // need to adjust for the number of spaces in this text
4917
-        $words = explode(' ', $text);
4918
-        $nspaces = count($words) - 1;
4919
-        $w += $wa * $nspaces;
4920
-        $a = deg2rad((float)$angle);
4921
-
4922
-        return [cos($a) * $w + $x, -sin($a) * $w + $y];
4923
-    }
4924
-
4925
-    /**
4926
-     * Callback method used by smallCaps
4927
-     *
4928
-     * @param array $matches
4929
-     *
4930
-     * @return string
4931
-     */
4932
-    function toUpper($matches)
4933
-    {
4934
-        return mb_strtoupper($matches[0]);
4935
-    }
4936
-
4937
-    function concatMatches($matches)
4938
-    {
4939
-        $str = "";
4940
-        foreach ($matches as $match) {
4941
-            $str .= $match[0];
4942
-        }
4943
-
4944
-        return $str;
4945
-    }
4946
-
4947
-    /**
4948
-     * register text for font subsetting
4949
-     *
4950
-     * @param string $font
4951
-     * @param string $text
4952
-     */
4953
-    function registerText($font, $text)
4954
-    {
4955
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4956
-            return;
4957
-        }
4958
-
4959
-        if (!isset($this->stringSubsets[$font])) {
4960
-            $base_subset = "\u{fffd}\u{fffe}\u{ffff}";
4961
-            $this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
4962
-        }
4963
-
4964
-        $this->stringSubsets[$font] = array_unique(
4965
-            array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4966
-        );
4967
-    }
4968
-
4969
-    /**
4970
-     * add text to the document, at a specified location, size and angle on the page
4971
-     *
4972
-     * @param float  $x
4973
-     * @param float  $y
4974
-     * @param float  $size
4975
-     * @param string $text
4976
-     * @param float  $angle
4977
-     * @param float  $wordSpaceAdjust
4978
-     * @param float  $charSpaceAdjust
4979
-     * @param bool   $smallCaps
4980
-     */
4981
-    function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4982
-    {
4983
-        if (!$this->numFonts) {
4984
-            $this->selectFont($this->defaultFont);
4985
-        }
4986
-
4987
-        $text = str_replace(["\r", "\n"], "", $text);
4988
-
4989
-        // if ($smallCaps) {
4990
-        //     preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4991
-        //     $lower = $this->concatMatches($matches);
4992
-        //     d($lower);
4993
-
4994
-        //     preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4995
-        //     $other = $this->concatMatches($matches);
4996
-        //     d($other);
4997
-
4998
-        //     $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4999
-        // }
5000
-
5001
-        // if there are any open callbacks, then they should be called, to show the start of the line
5002
-        if ($this->nCallback > 0) {
5003
-            for ($i = $this->nCallback; $i > 0; $i--) {
5004
-                // call each function
5005
-                $info = [
5006
-                    'x'         => $x,
5007
-                    'y'         => $y,
5008
-                    'angle'     => $angle,
5009
-                    'status'    => 'sol',
5010
-                    'p'         => $this->callback[$i]['p'],
5011
-                    'nCallback' => $this->callback[$i]['nCallback'],
5012
-                    'height'    => $this->callback[$i]['height'],
5013
-                    'descender' => $this->callback[$i]['descender']
5014
-                ];
5015
-
5016
-                $func = $this->callback[$i]['f'];
5017
-                $this->$func($info);
5018
-            }
5019
-        }
5020
-
5021
-        if ($angle == 0) {
5022
-            $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5023
-        } else {
5024
-            $a = deg2rad((float)$angle);
5025
-            $this->addContent(
5026
-                sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5027
-            );
5028
-        }
5029
-
5030
-        if ($wordSpaceAdjust != 0) {
5031
-            $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
5032
-        }
5033
-
5034
-        if ($charSpaceAdjust != 0) {
5035
-            $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
5036
-        }
5037
-
5038
-        $len = mb_strlen($text);
5039
-        $start = 0;
5040
-
5041
-        if ($start < $len) {
5042
-            $part = $text; // OAR - Don't need this anymore, given that $start always equals zero.  substr($text, $start);
5043
-            $place_text = $this->filterText($part, false);
5044
-            // modify unicode text so that extra word spacing is manually implemented (bug #)
5045
-            if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5046
-                $space_scale = 1000 / $size;
5047
-                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5048
-            }
5049
-            $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5050
-            $this->addContent(" [($place_text)] TJ");
5051
-        }
5052
-
5053
-        if ($wordSpaceAdjust != 0) {
5054
-            $this->addContent(sprintf(" %.3F Tw", 0));
5055
-        }
5056
-
5057
-        if ($charSpaceAdjust != 0) {
5058
-            $this->addContent(sprintf(" %.3F Tc", 0));
5059
-        }
5060
-
5061
-        $this->addContent(' ET');
5062
-
5063
-        // if there are any open callbacks, then they should be called, to show the end of the line
5064
-        if ($this->nCallback > 0) {
5065
-            for ($i = $this->nCallback; $i > 0; $i--) {
5066
-                // call each function
5067
-                $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
5068
-                $info = [
5069
-                    'x'         => $tmp[0],
5070
-                    'y'         => $tmp[1],
5071
-                    'angle'     => $angle,
5072
-                    'status'    => 'eol',
5073
-                    'p'         => $this->callback[$i]['p'],
5074
-                    'nCallback' => $this->callback[$i]['nCallback'],
5075
-                    'height'    => $this->callback[$i]['height'],
5076
-                    'descender' => $this->callback[$i]['descender']
5077
-                ];
5078
-                $func = $this->callback[$i]['f'];
5079
-                $this->$func($info);
5080
-            }
5081
-        }
5082
-
5083
-        if ($this->fonts[$this->currentFont]['isSubsetting']) {
5084
-            $this->registerText($this->currentFont, $text);
5085
-        }
5086
-    }
5087
-
5088
-    /**
5089
-     * calculate how wide a given text string will be on a page, at a given size.
5090
-     * this can be called externally, but is also used by the other class functions
5091
-     *
5092
-     * @param float  $size
5093
-     * @param string $text
5094
-     * @param float  $wordSpacing
5095
-     * @param float  $charSpacing
5096
-     *
5097
-     * @return float
5098
-     */
5099
-    public function getTextWidth(float $size, string $text, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
5100
-    {
5101
-        static $ord_cache = [];
5102
-
5103
-        // this function should not change any of the settings, though it will need to
5104
-        // track any directives which change during calculation, so copy them at the start
5105
-        // and put them back at the end.
5106
-        $store_currentTextState = $this->currentTextState;
5107
-
5108
-        if (!$this->numFonts) {
5109
-            $this->selectFont($this->defaultFont);
5110
-        }
5111
-
5112
-        $text = str_replace(["\r", "\n"], "", $text);
5113
-
5114
-        // hmm, this is where it all starts to get tricky - use the font information to
5115
-        // calculate the width of each character, add them up and convert to user units
5116
-        $w = 0;
5117
-        $cf = $this->currentFont;
5118
-        $current_font = $this->fonts[$cf];
5119
-        $space_scale = 1000 / ($size > 0 ? $size : 1);
5120
-
5121
-        if ($current_font['isUnicode']) {
5122
-            // for Unicode, use the code points array to calculate width rather
5123
-            // than just the string itself
5124
-            $unicode = $this->utf8toCodePointsArray($text);
5125
-
5126
-            foreach ($unicode as $char) {
5127
-                // check if we have to replace character
5128
-                if (isset($current_font['differences'][$char])) {
5129
-                    $char = $current_font['differences'][$char];
5130
-                }
5131
-
5132
-                if (isset($current_font['C'][$char])) {
5133
-                    $char_width = $current_font['C'][$char];
5134
-
5135
-                    // add the character width
5136
-                    $w += $char_width;
5137
-
5138
-                    // add additional padding for space
5139
-                    if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5140
-                        $w += $wordSpacing * $space_scale;
5141
-                    }
5142
-                }
5143
-            }
5144
-
5145
-            // add additional char spacing
5146
-            if ($charSpacing != 0) {
5147
-                $w += $charSpacing * $space_scale * count($unicode);
5148
-            }
5149
-
5150
-        } else {
5151
-            // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
5152
-            if ($this->isUnicode) {
5153
-                $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
5154
-            }
5155
-
5156
-            $len = mb_strlen($text, 'Windows-1252');
5157
-
5158
-            for ($i = 0; $i < $len; $i++) {
5159
-                $c = $text[$i];
5160
-                $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
5161
-
5162
-                // check if we have to replace character
5163
-                if (isset($current_font['differences'][$char])) {
5164
-                    $char = $current_font['differences'][$char];
5165
-                }
5166
-
5167
-                if (isset($current_font['C'][$char])) {
5168
-                    $char_width = $current_font['C'][$char];
5169
-
5170
-                    // add the character width
5171
-                    $w += $char_width;
5172
-
5173
-                    // add additional padding for space
5174
-                    if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5175
-                        $w += $wordSpacing * $space_scale;
5176
-                    }
5177
-                }
5178
-            }
5179
-
5180
-            // add additional char spacing
5181
-            if ($charSpacing != 0) {
5182
-                $w += $charSpacing * $space_scale * $len;
5183
-            }
5184
-        }
5185
-
5186
-        $this->currentTextState = $store_currentTextState;
5187
-        $this->setCurrentFont();
5188
-
5189
-        return $w * $size / 1000;
5190
-    }
5191
-
5192
-    /**
5193
-     * this will be called at a new page to return the state to what it was on the
5194
-     * end of the previous page, before the stack was closed down
5195
-     * This is to get around not being able to have open 'q' across pages
5196
-     *
5197
-     * @param int $pageEnd
5198
-     */
5199
-    function saveState($pageEnd = 0)
5200
-    {
5201
-        if ($pageEnd) {
5202
-            // this will be called at a new page to return the state to what it was on the
5203
-            // end of the previous page, before the stack was closed down
5204
-            // This is to get around not being able to have open 'q' across pages
5205
-            $opt = $this->stateStack[$pageEnd];
5206
-            // ok to use this as stack starts numbering at 1
5207
-            $this->setColor($opt['col'], true);
5208
-            $this->setStrokeColor($opt['str'], true);
5209
-            $this->addContent("\n" . $opt['lin']);
5210
-            //    $this->currentLineStyle = $opt['lin'];
5211
-        } else {
5212
-            $this->nStateStack++;
5213
-            $this->stateStack[$this->nStateStack] = [
5214
-                'col' => $this->currentColor,
5215
-                'str' => $this->currentStrokeColor,
5216
-                'lin' => $this->currentLineStyle
5217
-            ];
5218
-        }
5219
-
5220
-        $this->save();
5221
-    }
5222
-
5223
-    /**
5224
-     * restore a previously saved state
5225
-     *
5226
-     * @param int $pageEnd
5227
-     */
5228
-    function restoreState($pageEnd = 0)
5229
-    {
5230
-        if (!$pageEnd) {
5231
-            $n = $this->nStateStack;
5232
-            $this->currentColor = $this->stateStack[$n]['col'];
5233
-            $this->currentStrokeColor = $this->stateStack[$n]['str'];
5234
-            $this->addContent("\n" . $this->stateStack[$n]['lin']);
5235
-            $this->currentLineStyle = $this->stateStack[$n]['lin'];
5236
-            $this->stateStack[$n] = null;
5237
-            unset($this->stateStack[$n]);
5238
-            $this->nStateStack--;
5239
-        }
5240
-
5241
-        $this->restore();
5242
-    }
5243
-
5244
-    /**
5245
-     * make a loose object, the output will go into this object, until it is closed, then will revert to
5246
-     * the current one.
5247
-     * this object will not appear until it is included within a page.
5248
-     * the function will return the object number
5249
-     *
5250
-     * @return int
5251
-     */
5252
-    function openObject()
5253
-    {
5254
-        $this->nStack++;
5255
-        $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5256
-        // add a new object of the content type, to hold the data flow
5257
-        $this->numObj++;
5258
-        $this->o_contents($this->numObj, 'new');
5259
-        $this->currentContents = $this->numObj;
5260
-        $this->looseObjects[$this->numObj] = 1;
5261
-
5262
-        return $this->numObj;
5263
-    }
5264
-
5265
-    /**
5266
-     * open an existing object for editing
5267
-     *
5268
-     * @param $id
5269
-     */
5270
-    function reopenObject($id)
5271
-    {
5272
-        $this->nStack++;
5273
-        $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5274
-        $this->currentContents = $id;
5275
-
5276
-        // also if this object is the primary contents for a page, then set the current page to its parent
5277
-        if (isset($this->objects[$id]['onPage'])) {
5278
-            $this->currentPage = $this->objects[$id]['onPage'];
5279
-        }
5280
-    }
5281
-
5282
-    /**
5283
-     * close an object
5284
-     */
5285
-    function closeObject()
5286
-    {
5287
-        // close the object, as long as there was one open in the first place, which will be indicated by
5288
-        // an objectId on the stack.
5289
-        if ($this->nStack > 0) {
5290
-            $this->currentContents = $this->stack[$this->nStack]['c'];
5291
-            $this->currentPage = $this->stack[$this->nStack]['p'];
5292
-            $this->nStack--;
5293
-            // easier to probably not worry about removing the old entries, they will be overwritten
5294
-            // if there are new ones.
5295
-        }
5296
-    }
5297
-
5298
-    /**
5299
-     * stop an object from appearing on pages from this point on
5300
-     *
5301
-     * @param $id
5302
-     */
5303
-    function stopObject($id)
5304
-    {
5305
-        // if an object has been appearing on pages up to now, then stop it, this page will
5306
-        // be the last one that could contain it.
5307
-        if (isset($this->addLooseObjects[$id])) {
5308
-            $this->addLooseObjects[$id] = '';
5309
-        }
5310
-    }
5311
-
5312
-    /**
5313
-     * after an object has been created, it wil only show if it has been added, using this function.
5314
-     *
5315
-     * @param $id
5316
-     * @param string $options
5317
-     */
5318
-    function addObject($id, $options = 'add')
5319
-    {
5320
-        // add the specified object to the page
5321
-        if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
5322
-            // then it is a valid object, and it is not being added to itself
5323
-            switch ($options) {
5324
-                case 'all':
5325
-                    // then this object is to be added to this page (done in the next block) and
5326
-                    // all future new pages.
5327
-                    $this->addLooseObjects[$id] = 'all';
5328
-
5329
-                case 'add':
5330
-                    if (isset($this->objects[$this->currentContents]['onPage'])) {
5331
-                        // then the destination contents is the primary for the page
5332
-                        // (though this object is actually added to that page)
5333
-                        $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
5334
-                    }
5335
-                    break;
5336
-
5337
-                case 'even':
5338
-                    $this->addLooseObjects[$id] = 'even';
5339
-                    $pageObjectId = $this->objects[$this->currentContents]['onPage'];
5340
-                    if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
5341
-                        $this->addObject($id);
5342
-                        // hacky huh :)
5343
-                    }
5344
-                    break;
5345
-
5346
-                case 'odd':
5347
-                    $this->addLooseObjects[$id] = 'odd';
5348
-                    $pageObjectId = $this->objects[$this->currentContents]['onPage'];
5349
-                    if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
5350
-                        $this->addObject($id);
5351
-                        // hacky huh :)
5352
-                    }
5353
-                    break;
5354
-
5355
-                case 'next':
5356
-                    $this->addLooseObjects[$id] = 'all';
5357
-                    break;
5358
-
5359
-                case 'nexteven':
5360
-                    $this->addLooseObjects[$id] = 'even';
5361
-                    break;
5362
-
5363
-                case 'nextodd':
5364
-                    $this->addLooseObjects[$id] = 'odd';
5365
-                    break;
5366
-            }
5367
-        }
5368
-    }
5369
-
5370
-    /**
5371
-     * return a storable representation of a specific object
5372
-     *
5373
-     * @param $id
5374
-     * @return string|null
5375
-     */
5376
-    function serializeObject($id)
5377
-    {
5378
-        if (array_key_exists($id, $this->objects)) {
5379
-            return serialize($this->objects[$id]);
5380
-        }
5381
-
5382
-        return null;
5383
-    }
5384
-
5385
-    /**
5386
-     * restore an object from its stored representation. Returns its new object id.
5387
-     *
5388
-     * @param $obj
5389
-     * @return int
5390
-     */
5391
-    function restoreSerializedObject($obj)
5392
-    {
5393
-        $obj_id = $this->openObject();
5394
-        $this->objects[$obj_id] = unserialize($obj);
5395
-        $this->closeObject();
5396
-
5397
-        return $obj_id;
5398
-    }
5399
-
5400
-    /**
5401
-     * Embeds a file inside the PDF
5402
-     *
5403
-     * @param string $filepath path to the file to store inside the PDF
5404
-     * @param string $embeddedFilename the filename displayed in the list of embedded files
5405
-     * @param string $description a description in the list of embedded files
5406
-     */
5407
-    public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
5408
-    {
5409
-        $this->numObj++;
5410
-        $this->o_embedded_file_dictionary(
5411
-            $this->numObj,
5412
-            'new',
5413
-            [
5414
-                'filepath' => $filepath,
5415
-                'filename' => $embeddedFilename,
5416
-                'description' => $description
5417
-            ]
5418
-        );
5419
-    }
5420
-
5421
-    /**
5422
-     * Add content to the documents info object
5423
-     *
5424
-     * @param string|array $label
5425
-     * @param string       $value
5426
-     */
5427
-    public function addInfo($label, string $value = ""): void
5428
-    {
5429
-        // this will only work if the label is one of the valid ones.
5430
-        // modify this so that arrays can be passed as well.
5431
-        // if $label is an array then assume that it is key => value pairs
5432
-        // else assume that they are both scalar, anything else will probably error
5433
-        if (is_array($label)) {
5434
-            foreach ($label as $l => $v) {
5435
-                $this->o_info($this->infoObject, $l, (string) $v);
5436
-            }
5437
-        } else {
5438
-            $this->o_info($this->infoObject, $label, $value);
5439
-        }
5440
-    }
5441
-
5442
-    /**
5443
-     * set the viewer preferences of the document, it is up to the browser to obey these.
5444
-     *
5445
-     * @param $label
5446
-     * @param int $value
5447
-     */
5448
-    function setPreferences($label, $value = 0)
5449
-    {
5450
-        // this will only work if the label is one of the valid ones.
5451
-        if (is_array($label)) {
5452
-            foreach ($label as $l => $v) {
5453
-                $this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
5454
-            }
5455
-        } else {
5456
-            $this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
5457
-        }
5458
-    }
5459
-
5460
-    /**
5461
-     * extract an integer from a position in a byte stream
5462
-     *
5463
-     * @param $data
5464
-     * @param $pos
5465
-     * @param $num
5466
-     * @return int
5467
-     */
5468
-    private function getBytes(&$data, $pos, $num)
5469
-    {
5470
-        // return the integer represented by $num bytes from $pos within $data
5471
-        $ret = 0;
5472
-        for ($i = 0; $i < $num; $i++) {
5473
-            $ret *= 256;
5474
-            $ret += ord($data[$pos + $i]);
5475
-        }
5476
-
5477
-        return $ret;
5478
-    }
5479
-
5480
-    /**
5481
-     * Check if image already added to pdf image directory.
5482
-     * If yes, need not to create again (pass empty data)
5483
-     *
5484
-     * @param string $imgname
5485
-     * @return bool
5486
-     */
5487
-    function image_iscached($imgname)
5488
-    {
5489
-        return isset($this->imagelist[$imgname]);
5490
-    }
5491
-
5492
-    /**
5493
-     * add a PNG image into the document, from a GD object
5494
-     * this should work with remote files
5495
-     *
5496
-     * @param \GdImage|resource $img A GD resource
5497
-     * @param string $file The PNG file
5498
-     * @param float $x X position
5499
-     * @param float $y Y position
5500
-     * @param float $w Width
5501
-     * @param float $h Height
5502
-     * @param bool $is_mask true if the image is a mask
5503
-     * @param bool $mask true if the image is masked
5504
-     * @throws Exception
5505
-     */
5506
-    function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5507
-    {
5508
-        if (!function_exists("imagepng")) {
5509
-            throw new \Exception("The PHP GD extension is required, but is not installed.");
5510
-        }
5511
-
5512
-        //if already cached, need not to read again
5513
-        if (isset($this->imagelist[$file])) {
5514
-            $data = null;
5515
-        } else {
5516
-            // Example for transparency handling on new image. Retain for current image
5517
-            // $tIndex = imagecolortransparent($img);
5518
-            // if ($tIndex > 0) {
5519
-            //   $tColor    = imagecolorsforindex($img, $tIndex);
5520
-            //   $new_tIndex    = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
5521
-            //   imagefill($new_img, 0, 0, $new_tIndex);
5522
-            //   imagecolortransparent($new_img, $new_tIndex);
5523
-            // }
5524
-            // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
5525
-            //imagealphablending($img, true);
5526
-
5527
-            //default, but explicitely set to ensure pdf compatibility
5528
-            imagesavealpha($img, false/*!$is_mask && !$mask*/);
5529
-
5530
-            $error = 0;
5531
-            //DEBUG_IMG_TEMP
5532
-            //debugpng
5533
-            if (defined("DEBUGPNG") && DEBUGPNG) {
5534
-                print '[addImagePng ' . $file . ']';
5535
-            }
5536
-
5537
-            ob_start();
5538
-            @imagepng($img);
5539
-            $data = ob_get_clean();
5540
-
5541
-            if ($data == '') {
5542
-                $error = 1;
5543
-                $errormsg = 'trouble writing file from GD';
5544
-                //DEBUG_IMG_TEMP
5545
-                //debugpng
5546
-                if (defined("DEBUGPNG") && DEBUGPNG) {
5547
-                    print 'trouble writing file from GD';
5548
-                }
5549
-            }
5550
-
5551
-            if ($error) {
5552
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5553
-
5554
-                return;
5555
-            }
5556
-        }  //End isset($this->imagelist[$file]) (png Duplicate removal)
5557
-
5558
-        $this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
5559
-    }
5560
-
5561
-    /**
5562
-     * @param $file
5563
-     * @param $x
5564
-     * @param $y
5565
-     * @param $w
5566
-     * @param $h
5567
-     * @param $byte
5568
-     */
5569
-    protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
5570
-    {
5571
-        // generate images
5572
-        $img = @imagecreatefrompng($file);
5573
-
5574
-        if ($img === false) {
5575
-            return;
5576
-        }
5577
-
5578
-        // FIXME The pixel transformation doesn't work well with 8bit PNGs
5579
-        $eight_bit = ($byte & 4) !== 4;
5580
-
5581
-        $wpx = imagesx($img);
5582
-        $hpx = imagesy($img);
5583
-
5584
-        imagesavealpha($img, false);
5585
-
5586
-        // create temp alpha file
5587
-        $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
5588
-        @unlink($tempfile_alpha);
5589
-        $tempfile_alpha = "$tempfile_alpha.png";
5590
-
5591
-        // create temp plain file
5592
-        $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
5593
-        @unlink($tempfile_plain);
5594
-        $tempfile_plain = "$tempfile_plain.png";
5595
-
5596
-        $imgalpha = imagecreate($wpx, $hpx);
5597
-        imagesavealpha($imgalpha, false);
5598
-
5599
-        // generate gray scale palette (0 -> 255)
5600
-        for ($c = 0; $c < 256; ++$c) {
5601
-            imagecolorallocate($imgalpha, $c, $c, $c);
5602
-        }
5603
-
5604
-        // Use PECL gmagick + Graphics Magic to process transparent PNG images
5605
-        if (extension_loaded("gmagick")) {
5606
-            $gmagick = new \Gmagick($file);
5607
-            $gmagick->setimageformat('png');
5608
-
5609
-            // Get opacity channel (negative of alpha channel)
5610
-            $alpha_channel_neg = clone $gmagick;
5611
-            $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
5612
-
5613
-            // Negate opacity channel
5614
-            $alpha_channel = new \Gmagick();
5615
-            $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
5616
-            $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
5617
-            $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
5618
-            $alpha_channel->writeimage($tempfile_alpha);
5619
-
5620
-            // Cast to 8bit+palette
5621
-            $imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5622
-            imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5623
-            imagedestroy($imgalpha_);
5624
-            imagepng($imgalpha, $tempfile_alpha);
5625
-
5626
-            // Make opaque image
5627
-            $color_channels = new \Gmagick();
5628
-            $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
5629
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
5630
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
5631
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
5632
-            $color_channels->writeimage($tempfile_plain);
5633
-
5634
-            $imgplain = @imagecreatefrompng($tempfile_plain);
5635
-        }
5636
-        // Use PECL imagick + ImageMagic to process transparent PNG images
5637
-        elseif (extension_loaded("imagick")) {
5638
-            // Native cloning was added to pecl-imagick in svn commit 263814
5639
-            // the first version containing it was 3.0.1RC1
5640
-            static $imagickClonable = null;
5641
-            if ($imagickClonable === null) {
5642
-                $imagickClonable = true;
5643
-                if (defined('Imagick::IMAGICK_EXTVER')) {
5644
-                    $imagickVersion = \Imagick::IMAGICK_EXTVER;
5645
-                } else {
5646
-                    $imagickVersion = '0';
5647
-                }
5648
-                if (version_compare($imagickVersion, '0.0.1', '>=')) {
5649
-                    $imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
5650
-                }
5651
-            }
5652
-
5653
-            $imagick = new \Imagick($file);
5654
-            $imagick->setFormat('png');
5655
-
5656
-            // Get opacity channel (negative of alpha channel)
5657
-            if ($imagick->getImageAlphaChannel()) {
5658
-                $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
5659
-                $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
5660
-                // Since ImageMagick7 negate invert transparency as default
5661
-                if (\Imagick::getVersion()['versionNumber'] < 1800) {
5662
-                    $alpha_channel->negateImage(true);
5663
-                }
5664
-                $alpha_channel->writeImage($tempfile_alpha);
5665
-
5666
-                // Cast to 8bit+palette
5667
-                $imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5668
-                imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5669
-                imagedestroy($imgalpha_);
5670
-                imagepng($imgalpha, $tempfile_alpha);
5671
-            } else {
5672
-                $tempfile_alpha = null;
5673
-            }
5674
-
5675
-            // Make opaque image
5676
-            $color_channels = new \Imagick();
5677
-            $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
5678
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
5679
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
5680
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
5681
-            $color_channels->writeImage($tempfile_plain);
5682
-
5683
-            $imgplain = @imagecreatefrompng($tempfile_plain);
5684
-        } else {
5685
-            // allocated colors cache
5686
-            $allocated_colors = [];
5687
-
5688
-            // extract alpha channel
5689
-            for ($xpx = 0; $xpx < $wpx; ++$xpx) {
5690
-                for ($ypx = 0; $ypx < $hpx; ++$ypx) {
5691
-                    $color = imagecolorat($img, $xpx, $ypx);
5692
-                    $col = imagecolorsforindex($img, $color);
5693
-                    $alpha = $col['alpha'];
5694
-
5695
-                    if ($eight_bit) {
5696
-                        // with gamma correction
5697
-                        $gammacorr = 2.2;
5698
-                        $pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
5699
-                    } else {
5700
-                        // without gamma correction
5701
-                        $pixel = (127 - $alpha) * 2;
5702
-
5703
-                        $key = $col['red'] . $col['green'] . $col['blue'];
5704
-
5705
-                        if (!isset($allocated_colors[$key])) {
5706
-                            $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5707
-                            $allocated_colors[$key] = $pixel_img;
5708
-                        } else {
5709
-                            $pixel_img = $allocated_colors[$key];
5710
-                        }
5711
-
5712
-                        imagesetpixel($img, $xpx, $ypx, $pixel_img);
5713
-                    }
5714
-
5715
-                    imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
5716
-                }
5717
-            }
5718
-
5719
-            // extract image without alpha channel
5720
-            $imgplain = imagecreatetruecolor($wpx, $hpx);
5721
-            imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
5722
-            imagedestroy($img);
5723
-
5724
-            imagepng($imgalpha, $tempfile_alpha);
5725
-            imagepng($imgplain, $tempfile_plain);
5726
-        }
5727
-
5728
-        $this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
5729
-
5730
-        // embed mask image
5731
-        if ($tempfile_alpha) {
5732
-            $this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
5733
-            imagedestroy($imgalpha);
5734
-            $this->imageCache[] = $tempfile_alpha;
5735
-        }
5736
-
5737
-        // embed image, masked with previously embedded mask
5738
-        $this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
5739
-        imagedestroy($imgplain);
5740
-        $this->imageCache[] = $tempfile_plain;
5741
-    }
5742
-
5743
-    /**
5744
-     * add a PNG image into the document, from a file
5745
-     * this should work with remote files
5746
-     *
5747
-     * @param $file
5748
-     * @param $x
5749
-     * @param $y
5750
-     * @param int $w
5751
-     * @param int $h
5752
-     * @throws Exception
5753
-     */
5754
-    function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5755
-    {
5756
-        if (!function_exists("imagecreatefrompng")) {
5757
-            throw new \Exception("The PHP GD extension is required, but is not installed.");
5758
-        }
5759
-
5760
-        if (isset($this->imageAlphaList[$file])) {
5761
-            [$alphaFile, $plainFile] = $this->imageAlphaList[$file];
5762
-
5763
-            if ($alphaFile) {
5764
-                $img = null;
5765
-                $this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
5766
-            }
5767
-
5768
-            $img = null;
5769
-            $this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
5770
-            return;
5771
-        }
5772
-
5773
-        //if already cached, need not to read again
5774
-        if (isset($this->imagelist[$file])) {
5775
-            $img = null;
5776
-        } else {
5777
-            $info = file_get_contents($file, false, null, 24, 5);
5778
-            $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
5779
-            $bit_depth = $meta["bitDepth"];
5780
-            $color_type = $meta["colorType"];
5781
-
5782
-            // http://www.w3.org/TR/PNG/#11IHDR
5783
-            // 3 => indexed
5784
-            // 4 => greyscale with alpha
5785
-            // 6 => fullcolor with alpha
5786
-            $is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
5787
-
5788
-            if ($is_alpha) { // exclude grayscale alpha
5789
-                $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
5790
-                return;
5791
-            }
5792
-
5793
-            //png files typically contain an alpha channel.
5794
-            //pdf file format or class.pdf does not support alpha blending.
5795
-            //on alpha blended images, more transparent areas have a color near black.
5796
-            //This appears in the result on not storing the alpha channel.
5797
-            //Correct would be the box background image or its parent when transparent.
5798
-            //But this would make the image dependent on the background.
5799
-            //Therefore create an image with white background and copy in
5800
-            //A more natural background than black is white.
5801
-            //Therefore create an empty image with white background and merge the
5802
-            //image in with alpha blending.
5803
-            $imgtmp = @imagecreatefrompng($file);
5804
-            if (!$imgtmp) {
5805
-                return;
5806
-            }
5807
-            $sx = imagesx($imgtmp);
5808
-            $sy = imagesy($imgtmp);
5809
-            $img = imagecreatetruecolor($sx, $sy);
5810
-            imagealphablending($img, true);
5811
-
5812
-            // @todo is it still needed ??
5813
-            $ti = imagecolortransparent($imgtmp);
5814
-            if ($ti >= 0) {
5815
-                $tc = imagecolorsforindex($imgtmp, $ti);
5816
-                $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5817
-                imagefill($img, 0, 0, $ti);
5818
-                imagecolortransparent($img, $ti);
5819
-            } else {
5820
-                imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5821
-            }
5822
-
5823
-            imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5824
-            imagedestroy($imgtmp);
5825
-        }
5826
-        $this->addImagePng($img, $file, $x, $y, $w, $h);
5827
-
5828
-        if ($img) {
5829
-            imagedestroy($img);
5830
-        }
5831
-    }
5832
-
5833
-    /**
5834
-     * add a PNG image into the document, from a memory buffer of the file
5835
-     *
5836
-     * @param $data
5837
-     * @param $file
5838
-     * @param $x
5839
-     * @param $y
5840
-     * @param float $w
5841
-     * @param float $h
5842
-     * @param bool $is_mask
5843
-     * @param null $mask
5844
-     */
5845
-    function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5846
-    {
5847
-        if (isset($this->imagelist[$file])) {
5848
-            $data = null;
5849
-            $info['width'] = $this->imagelist[$file]['w'];
5850
-            $info['height'] = $this->imagelist[$file]['h'];
5851
-            $label = $this->imagelist[$file]['label'];
5852
-        } else {
5853
-            if ($data == null) {
5854
-                $this->addMessage('addPngFromBuf error - data not present!');
5855
-
5856
-                return;
5857
-            }
5858
-
5859
-            $error = 0;
5860
-
5861
-            if (!$error) {
5862
-                $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5863
-
5864
-                if (mb_substr($data, 0, 8, '8bit') != $header) {
5865
-                    $error = 1;
5866
-
5867
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
5868
-                        print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5869
-                    }
5870
-
5871
-                    $errormsg = 'this file does not have a valid header';
5872
-                }
5873
-            }
5874
-
5875
-            if (!$error) {
5876
-                // set pointer
5877
-                $p = 8;
5878
-                $len = mb_strlen($data, '8bit');
5879
-
5880
-                // cycle through the file, identifying chunks
5881
-                $haveHeader = 0;
5882
-                $info = [];
5883
-                $idata = '';
5884
-                $pdata = '';
5885
-
5886
-                while ($p < $len) {
5887
-                    $chunkLen = $this->getBytes($data, $p, 4);
5888
-                    $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5889
-
5890
-                    switch ($chunkType) {
5891
-                        case 'IHDR':
5892
-                            // this is where all the file information comes from
5893
-                            $info['width'] = $this->getBytes($data, $p + 8, 4);
5894
-                            $info['height'] = $this->getBytes($data, $p + 12, 4);
5895
-                            $info['bitDepth'] = ord($data[$p + 16]);
5896
-                            $info['colorType'] = ord($data[$p + 17]);
5897
-                            $info['compressionMethod'] = ord($data[$p + 18]);
5898
-                            $info['filterMethod'] = ord($data[$p + 19]);
5899
-                            $info['interlaceMethod'] = ord($data[$p + 20]);
5900
-
5901
-                            //print_r($info);
5902
-                            $haveHeader = 1;
5903
-                            if ($info['compressionMethod'] != 0) {
5904
-                                $error = 1;
5905
-
5906
-                                //debugpng
5907
-                                if (defined("DEBUGPNG") && DEBUGPNG) {
5908
-                                    print '[addPngFromFile unsupported compression method ' . $file . ']';
5909
-                                }
5910
-
5911
-                                $errormsg = 'unsupported compression method';
5912
-                            }
5913
-
5914
-                            if ($info['filterMethod'] != 0) {
5915
-                                $error = 1;
5916
-
5917
-                                //debugpng
5918
-                                if (defined("DEBUGPNG") && DEBUGPNG) {
5919
-                                    print '[addPngFromFile unsupported filter method ' . $file . ']';
5920
-                                }
5921
-
5922
-                                $errormsg = 'unsupported filter method';
5923
-                            }
5924
-                            break;
5925
-
5926
-                        case 'PLTE':
5927
-                            $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5928
-                            break;
5929
-
5930
-                        case 'IDAT':
5931
-                            $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5932
-                            break;
5933
-
5934
-                        case 'tRNS':
5935
-                            //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5936
-                            //print "tRNS found, color type = ".$info['colorType']."\n";
5937
-                            $transparency = [];
5938
-
5939
-                            switch ($info['colorType']) {
5940
-                                // indexed color, rbg
5941
-                                case 3:
5942
-                                    /* corresponding to entries in the plte chunk
3434
+					}
3435
+				}
3436
+			}
3437
+
3438
+			if ($this->compressionReady && $this->options['compression']) {
3439
+				// then implement ZLIB based compression on CIDtoGID string
3440
+				$data['CIDtoGID_Compressed'] = true;
3441
+				$cidtogid = gzcompress($cidtogid, 6);
3442
+			}
3443
+			$data['CIDtoGID'] = base64_encode($cidtogid);
3444
+			$data['_version_'] = $this->fontcacheVersion;
3445
+			$this->fonts[$font] = $data;
3446
+
3447
+			//Because of potential trouble with php safe mode, expect that the folder already exists.
3448
+			//If not existing, this will hit performance because of missing cached results.
3449
+			if (is_dir($fontcache) && is_writable($fontcache)) {
3450
+				file_put_contents("$fontcache/$cache_name", json_encode($data, JSON_PRETTY_PRINT));
3451
+			}
3452
+			$data = null;
3453
+		}
3454
+
3455
+		if (!isset($this->fonts[$font])) {
3456
+			$this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3457
+		}
3458
+	}
3459
+
3460
+	/**
3461
+	 * if the font is not loaded then load it and make the required object
3462
+	 * else just make it the current font
3463
+	 * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
3464
+	 * note that encoding='none' will need to be used for symbolic fonts
3465
+	 * and 'differences' => an array of mappings between numbers 0->255 and character names.
3466
+	 *
3467
+	 * @param string $fontName
3468
+	 * @param string $encoding
3469
+	 * @param bool $set
3470
+	 * @param bool $isSubsetting
3471
+	 * @return int
3472
+	 * @throws FontNotFoundException
3473
+	 */
3474
+	function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
3475
+	{
3476
+		if ($fontName === null || $fontName === '') {
3477
+			return $this->currentFontNum;
3478
+		}
3479
+
3480
+		$ext = substr($fontName, -4);
3481
+		if ($ext === '.afm' || $ext === '.ufm') {
3482
+			$fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3483
+		}
3484
+
3485
+		if (!isset($this->fonts[$fontName])) {
3486
+			$this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3487
+
3488
+			// load the file
3489
+			$this->openFont($fontName);
3490
+
3491
+			if (isset($this->fonts[$fontName])) {
3492
+				$this->numObj++;
3493
+				$this->numFonts++;
3494
+
3495
+				$font = &$this->fonts[$fontName];
3496
+
3497
+				$name = basename($fontName);
3498
+				$options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
3499
+
3500
+				if (is_array($encoding)) {
3501
+					// then encoding and differences might be set
3502
+					if (isset($encoding['encoding'])) {
3503
+						$options['encoding'] = $encoding['encoding'];
3504
+					}
3505
+
3506
+					if (isset($encoding['differences'])) {
3507
+						$options['differences'] = $encoding['differences'];
3508
+					}
3509
+				} else {
3510
+					if (mb_strlen($encoding, '8bit')) {
3511
+						// then perhaps only the encoding has been set
3512
+						$options['encoding'] = $encoding;
3513
+					}
3514
+				}
3515
+
3516
+				$this->o_font($this->numObj, 'new', $options);
3517
+
3518
+				if (file_exists("$fontName.ttf")) {
3519
+					$fileSuffix = 'ttf';
3520
+				} elseif (file_exists("$fontName.TTF")) {
3521
+					$fileSuffix = 'TTF';
3522
+				} elseif (file_exists("$fontName.pfb")) {
3523
+					$fileSuffix = 'pfb';
3524
+				} elseif (file_exists("$fontName.PFB")) {
3525
+					$fileSuffix = 'PFB';
3526
+				} else {
3527
+					$fileSuffix = '';
3528
+				}
3529
+
3530
+				$font['fileSuffix'] = $fileSuffix;
3531
+
3532
+				$font['fontNum'] = $this->numFonts;
3533
+				$font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
3534
+
3535
+				// also set the differences here, note that this means that these will take effect only the
3536
+				//first time that a font is selected, else they are ignored
3537
+				if (isset($options['differences'])) {
3538
+					$font['differences'] = $options['differences'];
3539
+				}
3540
+			}
3541
+		}
3542
+
3543
+		if ($set && isset($this->fonts[$fontName])) {
3544
+			// so if for some reason the font was not set in the last one then it will not be selected
3545
+			$this->currentBaseFont = $fontName;
3546
+
3547
+			// the next lines mean that if a new font is selected, then the current text state will be
3548
+			// applied to it as well.
3549
+			$this->currentFont = $this->currentBaseFont;
3550
+			$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3551
+		}
3552
+
3553
+		return $this->currentFontNum;
3554
+	}
3555
+
3556
+	/**
3557
+	 * sets up the current font, based on the font families, and the current text state
3558
+	 * note that this system is quite flexible, a bold-italic font can be completely different to a
3559
+	 * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3560
+	 * This function is to be called whenever the currentTextState is changed, it will update
3561
+	 * the currentFont setting to whatever the appropriate family one is.
3562
+	 * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3563
+	 * This function will change the currentFont to whatever it should be, but will not change the
3564
+	 * currentBaseFont.
3565
+	 */
3566
+	private function setCurrentFont()
3567
+	{
3568
+		//   if (strlen($this->currentBaseFont) == 0){
3569
+		//     // then assume an initial font
3570
+		//     $this->selectFont($this->defaultFont);
3571
+		//   }
3572
+		//   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3573
+		//   if (strlen($this->currentTextState)
3574
+		//     && isset($this->fontFamilies[$cf])
3575
+		//       && isset($this->fontFamilies[$cf][$this->currentTextState])){
3576
+		//     // then we are in some state or another
3577
+		//     // and this font has a family, and the current setting exists within it
3578
+		//     // select the font, then return it
3579
+		//     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3580
+		//     $this->selectFont($nf,'',0);
3581
+		//     $this->currentFont = $nf;
3582
+		//     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3583
+		//   } else {
3584
+		//     // the this font must not have the right family member for the current state
3585
+		//     // simply assume the base font
3586
+		$this->currentFont = $this->currentBaseFont;
3587
+		$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3588
+		//  }
3589
+	}
3590
+
3591
+	/**
3592
+	 * function for the user to find out what the ID is of the first page that was created during
3593
+	 * startup - useful if they wish to add something to it later.
3594
+	 *
3595
+	 * @return int
3596
+	 */
3597
+	function getFirstPageId()
3598
+	{
3599
+		return $this->firstPageId;
3600
+	}
3601
+
3602
+	/**
3603
+	 * add content to the currently active object
3604
+	 *
3605
+	 * @param $content
3606
+	 */
3607
+	private function addContent($content)
3608
+	{
3609
+		$this->objects[$this->currentContents]['c'] .= $content;
3610
+	}
3611
+
3612
+	/**
3613
+	 * sets the color for fill operations
3614
+	 *
3615
+	 * @param array $color
3616
+	 * @param bool  $force
3617
+	 */
3618
+	function setColor($color, $force = false)
3619
+	{
3620
+		$new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3621
+
3622
+		if (!$force && $this->currentColor == $new_color) {
3623
+			return;
3624
+		}
3625
+
3626
+		if (isset($new_color[3])) {
3627
+			$this->currentColor = $new_color;
3628
+			$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3629
+		} else {
3630
+			if (isset($new_color[2])) {
3631
+				$this->currentColor = $new_color;
3632
+				$this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3633
+			}
3634
+		}
3635
+	}
3636
+
3637
+	/**
3638
+	 * sets the color for fill operations
3639
+	 *
3640
+	 * @param string $fillRule
3641
+	 */
3642
+	function setFillRule($fillRule)
3643
+	{
3644
+		if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3645
+			return;
3646
+		}
3647
+
3648
+		$this->fillRule = $fillRule;
3649
+	}
3650
+
3651
+	/**
3652
+	 * sets the color for stroke operations
3653
+	 *
3654
+	 * @param array $color
3655
+	 * @param bool  $force
3656
+	 */
3657
+	function setStrokeColor($color, $force = false)
3658
+	{
3659
+		$new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3660
+
3661
+		if (!$force && $this->currentStrokeColor == $new_color) {
3662
+			return;
3663
+		}
3664
+
3665
+		if (isset($new_color[3])) {
3666
+			$this->currentStrokeColor = $new_color;
3667
+			$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3668
+		} else {
3669
+			if (isset($new_color[2])) {
3670
+				$this->currentStrokeColor = $new_color;
3671
+				$this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3672
+			}
3673
+		}
3674
+	}
3675
+
3676
+	/**
3677
+	 * Set the graphics state for compositions
3678
+	 *
3679
+	 * @param $parameters
3680
+	 */
3681
+	function setGraphicsState($parameters)
3682
+	{
3683
+		// Create a new graphics state object if necessary
3684
+		if (($gstate = array_search($parameters, $this->gstates)) === false) {
3685
+			$this->numObj++;
3686
+			$this->o_extGState($this->numObj, 'new', $parameters);
3687
+			$gstate = $this->numStates;
3688
+			$this->gstates[$gstate] = $parameters;
3689
+		}
3690
+		$this->addContent("\n/GS$gstate gs");
3691
+	}
3692
+
3693
+	/**
3694
+	 * Set current blend mode & opacity for lines.
3695
+	 *
3696
+	 * Valid blend modes are:
3697
+	 *
3698
+	 * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3699
+	 * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3700
+	 * Exclusion
3701
+	 *
3702
+	 * @param string $mode    the blend mode to use
3703
+	 * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3704
+	 */
3705
+	function setLineTransparency($mode, $opacity)
3706
+	{
3707
+		static $blend_modes = [
3708
+			"Normal",
3709
+			"Multiply",
3710
+			"Screen",
3711
+			"Overlay",
3712
+			"Darken",
3713
+			"Lighten",
3714
+			"ColorDogde",
3715
+			"ColorBurn",
3716
+			"HardLight",
3717
+			"SoftLight",
3718
+			"Difference",
3719
+			"Exclusion"
3720
+		];
3721
+
3722
+		if (!in_array($mode, $blend_modes)) {
3723
+			$mode = "Normal";
3724
+		}
3725
+
3726
+		if (is_null($this->currentLineTransparency)) {
3727
+			$this->currentLineTransparency = [];
3728
+		}
3729
+
3730
+		if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
3731
+			$this->currentLineTransparency['mode'] : '') &&
3732
+			$opacity === (key_exists('opacity', $this->currentLineTransparency) ?
3733
+			$this->currentLineTransparency["opacity"] : '')) {
3734
+			return;
3735
+		}
3736
+
3737
+		$this->currentLineTransparency["mode"] = $mode;
3738
+		$this->currentLineTransparency["opacity"] = $opacity;
3739
+
3740
+		$options = [
3741
+			"BM" => "/$mode",
3742
+			"CA" => (float)$opacity
3743
+		];
3744
+
3745
+		$this->setGraphicsState($options);
3746
+	}
3747
+
3748
+	/**
3749
+	 * Set current blend mode & opacity for filled objects.
3750
+	 *
3751
+	 * Valid blend modes are:
3752
+	 *
3753
+	 * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3754
+	 * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3755
+	 * Exclusion
3756
+	 *
3757
+	 * @param string $mode    the blend mode to use
3758
+	 * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3759
+	 */
3760
+	function setFillTransparency($mode, $opacity)
3761
+	{
3762
+		static $blend_modes = [
3763
+			"Normal",
3764
+			"Multiply",
3765
+			"Screen",
3766
+			"Overlay",
3767
+			"Darken",
3768
+			"Lighten",
3769
+			"ColorDogde",
3770
+			"ColorBurn",
3771
+			"HardLight",
3772
+			"SoftLight",
3773
+			"Difference",
3774
+			"Exclusion"
3775
+		];
3776
+
3777
+		if (!in_array($mode, $blend_modes)) {
3778
+			$mode = "Normal";
3779
+		}
3780
+
3781
+		if (is_null($this->currentFillTransparency)) {
3782
+			$this->currentFillTransparency = [];
3783
+		}
3784
+
3785
+		if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
3786
+			$this->currentFillTransparency['mode'] : '') &&
3787
+			$opacity === (key_exists('opacity', $this->currentFillTransparency) ?
3788
+			$this->currentFillTransparency["opacity"] : '')) {
3789
+			return;
3790
+		}
3791
+
3792
+		$this->currentFillTransparency["mode"] = $mode;
3793
+		$this->currentFillTransparency["opacity"] = $opacity;
3794
+
3795
+		$options = [
3796
+			"BM" => "/$mode",
3797
+			"ca" => (float)$opacity,
3798
+		];
3799
+
3800
+		$this->setGraphicsState($options);
3801
+	}
3802
+
3803
+	/**
3804
+	 * draw a line from one set of coordinates to another
3805
+	 *
3806
+	 * @param float $x1
3807
+	 * @param float $y1
3808
+	 * @param float $x2
3809
+	 * @param float $y2
3810
+	 * @param bool  $stroke
3811
+	 */
3812
+	function line($x1, $y1, $x2, $y2, $stroke = true)
3813
+	{
3814
+		$this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3815
+
3816
+		if ($stroke) {
3817
+			$this->addContent(' S');
3818
+		}
3819
+	}
3820
+
3821
+	/**
3822
+	 * draw a bezier curve based on 4 control points
3823
+	 *
3824
+	 * @param float $x0
3825
+	 * @param float $y0
3826
+	 * @param float $x1
3827
+	 * @param float $y1
3828
+	 * @param float $x2
3829
+	 * @param float $y2
3830
+	 * @param float $x3
3831
+	 * @param float $y3
3832
+	 */
3833
+	function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3834
+	{
3835
+		// in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3836
+		// as the control points for the curve.
3837
+		$this->addContent(
3838
+			sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3839
+		);
3840
+	}
3841
+
3842
+	/**
3843
+	 * draw a part of an ellipse
3844
+	 *
3845
+	 * @param float $x0
3846
+	 * @param float $y0
3847
+	 * @param float $astart
3848
+	 * @param float $afinish
3849
+	 * @param float $r1
3850
+	 * @param float $r2
3851
+	 * @param float $angle
3852
+	 * @param int $nSeg
3853
+	 */
3854
+	function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3855
+	{
3856
+		$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3857
+	}
3858
+
3859
+	/**
3860
+	 * draw a filled ellipse
3861
+	 *
3862
+	 * @param float $x0
3863
+	 * @param float $y0
3864
+	 * @param float $r1
3865
+	 * @param float $r2
3866
+	 * @param float $angle
3867
+	 * @param int $nSeg
3868
+	 * @param float $astart
3869
+	 * @param float $afinish
3870
+	 */
3871
+	function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3872
+	{
3873
+		$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3874
+	}
3875
+
3876
+	/**
3877
+	 * @param float $x
3878
+	 * @param float $y
3879
+	 */
3880
+	function lineTo($x, $y)
3881
+	{
3882
+		$this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3883
+	}
3884
+
3885
+	/**
3886
+	 * @param float $x
3887
+	 * @param float $y
3888
+	 */
3889
+	function moveTo($x, $y)
3890
+	{
3891
+		$this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3892
+	}
3893
+
3894
+	/**
3895
+	 * draw a bezier curve based on 4 control points
3896
+	 *
3897
+	 * @param float $x1
3898
+	 * @param float $y1
3899
+	 * @param float $x2
3900
+	 * @param float $y2
3901
+	 * @param float $x3
3902
+	 * @param float $y3
3903
+	 */
3904
+	function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3905
+	{
3906
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3907
+	}
3908
+
3909
+	/**
3910
+	 * draw a bezier curve based on 4 control points
3911
+	 *
3912
+	 * @param float $cpx
3913
+	 * @param float $cpy
3914
+	 * @param float $x
3915
+	 * @param float $y
3916
+	 */
3917
+	function quadTo($cpx, $cpy, $x, $y)
3918
+	{
3919
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3920
+	}
3921
+
3922
+	function closePath()
3923
+	{
3924
+		$this->addContent(' h');
3925
+	}
3926
+
3927
+	function endPath()
3928
+	{
3929
+		$this->addContent(' n');
3930
+	}
3931
+
3932
+	/**
3933
+	 * draw an ellipse
3934
+	 * note that the part and filled ellipse are just special cases of this function
3935
+	 *
3936
+	 * draws an ellipse in the current line style
3937
+	 * centered at $x0,$y0, radii $r1,$r2
3938
+	 * if $r2 is not set, then a circle is drawn
3939
+	 * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3940
+	 * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3941
+	 * pretty crappy shape at 2, as we are approximating with bezier curves.
3942
+	 *
3943
+	 * @param float $x0
3944
+	 * @param float $y0
3945
+	 * @param float $r1
3946
+	 * @param float $r2
3947
+	 * @param float $angle
3948
+	 * @param int   $nSeg
3949
+	 * @param float $astart
3950
+	 * @param float $afinish
3951
+	 * @param bool  $close
3952
+	 * @param bool  $fill
3953
+	 * @param bool  $stroke
3954
+	 * @param bool  $incomplete
3955
+	 */
3956
+	function ellipse(
3957
+		$x0,
3958
+		$y0,
3959
+		$r1,
3960
+		$r2 = 0,
3961
+		$angle = 0,
3962
+		$nSeg = 8,
3963
+		$astart = 0,
3964
+		$afinish = 360,
3965
+		$close = true,
3966
+		$fill = false,
3967
+		$stroke = true,
3968
+		$incomplete = false
3969
+	) {
3970
+		if ($r1 == 0) {
3971
+			return;
3972
+		}
3973
+
3974
+		if ($r2 == 0) {
3975
+			$r2 = $r1;
3976
+		}
3977
+
3978
+		if ($nSeg < 2) {
3979
+			$nSeg = 2;
3980
+		}
3981
+
3982
+		$astart = deg2rad((float)$astart);
3983
+		$afinish = deg2rad((float)$afinish);
3984
+		$totalAngle = $afinish - $astart;
3985
+
3986
+		$dt = $totalAngle / $nSeg;
3987
+		$dtm = $dt / 3;
3988
+
3989
+		if ($angle != 0) {
3990
+			$a = -1 * deg2rad((float)$angle);
3991
+
3992
+			$this->addContent(
3993
+				sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
3994
+			);
3995
+
3996
+			$x0 = 0;
3997
+			$y0 = 0;
3998
+		}
3999
+
4000
+		$t1 = $astart;
4001
+		$a0 = $x0 + $r1 * cos($t1);
4002
+		$b0 = $y0 + $r2 * sin($t1);
4003
+		$c0 = -$r1 * sin($t1);
4004
+		$d0 = $r2 * cos($t1);
4005
+
4006
+		if (!$incomplete) {
4007
+			$this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4008
+		}
4009
+
4010
+		for ($i = 1; $i <= $nSeg; $i++) {
4011
+			// draw this bit of the total curve
4012
+			$t1 = $i * $dt + $astart;
4013
+			$a1 = $x0 + $r1 * cos($t1);
4014
+			$b1 = $y0 + $r2 * sin($t1);
4015
+			$c1 = -$r1 * sin($t1);
4016
+			$d1 = $r2 * cos($t1);
4017
+
4018
+			$this->addContent(
4019
+				sprintf(
4020
+					"\n%.3F %.3F %.3F %.3F %.3F %.3F c",
4021
+					($a0 + $c0 * $dtm),
4022
+					($b0 + $d0 * $dtm),
4023
+					($a1 - $c1 * $dtm),
4024
+					($b1 - $d1 * $dtm),
4025
+					$a1,
4026
+					$b1
4027
+				)
4028
+			);
4029
+
4030
+			$a0 = $a1;
4031
+			$b0 = $b1;
4032
+			$c0 = $c1;
4033
+			$d0 = $d1;
4034
+		}
4035
+
4036
+		if (!$incomplete) {
4037
+			if ($fill) {
4038
+				$this->addContent(' f');
4039
+			}
4040
+
4041
+			if ($stroke) {
4042
+				if ($close) {
4043
+					$this->addContent(' s'); // small 's' signifies closing the path as well
4044
+				} else {
4045
+					$this->addContent(' S');
4046
+				}
4047
+			}
4048
+		}
4049
+
4050
+		if ($angle != 0) {
4051
+			$this->addContent(' Q');
4052
+		}
4053
+	}
4054
+
4055
+	/**
4056
+	 * this sets the line drawing style.
4057
+	 * width, is the thickness of the line in user units
4058
+	 * cap is the type of cap to put on the line, values can be 'butt','round','square'
4059
+	 *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
4060
+	 *    end of the line.
4061
+	 * join can be 'miter', 'round', 'bevel'
4062
+	 * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
4063
+	 *   on and off dashes.
4064
+	 *   (2) represents 2 on, 2 off, 2 on , 2 off ...
4065
+	 *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4066
+	 * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4067
+	 *
4068
+	 * @param float  $width
4069
+	 * @param string $cap
4070
+	 * @param string $join
4071
+	 * @param array  $dash
4072
+	 * @param int    $phase
4073
+	 */
4074
+	function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4075
+	{
4076
+		// this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
4077
+		$string = '';
4078
+
4079
+		if ($width > 0) {
4080
+			$string .= "$width w";
4081
+		}
4082
+
4083
+		$ca = ['butt' => 0, 'round' => 1, 'square' => 2];
4084
+
4085
+		if (isset($ca[$cap])) {
4086
+			$string .= " $ca[$cap] J";
4087
+		}
4088
+
4089
+		$ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
4090
+
4091
+		if (isset($ja[$join])) {
4092
+			$string .= " $ja[$join] j";
4093
+		}
4094
+
4095
+		if (is_array($dash)) {
4096
+			$string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4097
+		}
4098
+
4099
+		$this->currentLineStyle = $string;
4100
+		$this->addContent("\n$string");
4101
+	}
4102
+
4103
+	/**
4104
+	 * draw a polygon, the syntax for this is similar to the GD polygon command
4105
+	 *
4106
+	 * @param float[] $p
4107
+	 * @param bool    $fill
4108
+	 */
4109
+	public function polygon(array $p, bool $fill = false): void
4110
+	{
4111
+		$this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4112
+
4113
+		$n = count($p);
4114
+		for ($i = 2; $i < $n; $i = $i + 2) {
4115
+			$this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4116
+		}
4117
+
4118
+		if ($fill) {
4119
+			$this->addContent(' f');
4120
+		} else {
4121
+			$this->addContent(' S');
4122
+		}
4123
+	}
4124
+
4125
+	/**
4126
+	 * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4127
+	 * the coordinates of the upper-right corner
4128
+	 *
4129
+	 * @param float $x1
4130
+	 * @param float $y1
4131
+	 * @param float $width
4132
+	 * @param float $height
4133
+	 */
4134
+	function filledRectangle($x1, $y1, $width, $height)
4135
+	{
4136
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
4137
+	}
4138
+
4139
+	/**
4140
+	 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4141
+	 * the coordinates of the upper-right corner
4142
+	 *
4143
+	 * @param float $x1
4144
+	 * @param float $y1
4145
+	 * @param float $width
4146
+	 * @param float $height
4147
+	 */
4148
+	function rectangle($x1, $y1, $width, $height)
4149
+	{
4150
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
4151
+	}
4152
+
4153
+	/**
4154
+	 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4155
+	 * the coordinates of the upper-right corner
4156
+	 *
4157
+	 * @param float $x1
4158
+	 * @param float $y1
4159
+	 * @param float $width
4160
+	 * @param float $height
4161
+	 */
4162
+	function rect($x1, $y1, $width, $height)
4163
+	{
4164
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
4165
+	}
4166
+
4167
+	function stroke(bool $close = false)
4168
+	{
4169
+		$this->addContent("\n" . ($close ? "s" : "S"));
4170
+	}
4171
+
4172
+	function fill()
4173
+	{
4174
+		$this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4175
+	}
4176
+
4177
+	function fillStroke(bool $close = false)
4178
+	{
4179
+		$this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
4180
+	}
4181
+
4182
+	/**
4183
+	 * @param string $subtype
4184
+	 * @param integer $x
4185
+	 * @param integer $y
4186
+	 * @param integer $w
4187
+	 * @param integer $h
4188
+	 * @return int
4189
+	 */
4190
+	function addXObject($subtype, $x, $y, $w, $h)
4191
+	{
4192
+		$id = ++$this->numObj;
4193
+		$this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
4194
+		return $id;
4195
+	}
4196
+
4197
+	/**
4198
+	 * @param integer $numXObject
4199
+	 * @param string $type
4200
+	 * @param array $options
4201
+	 */
4202
+	function setXObjectResource($numXObject, $type, $options)
4203
+	{
4204
+		if (in_array($type, ['procset', 'font', 'xObject'])) {
4205
+			$this->o_xobject($numXObject, $type, $options);
4206
+		}
4207
+	}
4208
+
4209
+	/**
4210
+	 * add signature
4211
+	 *
4212
+	 * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
4213
+	 *
4214
+	 * $signatureId = $cpdf->addSignature([
4215
+	 *   'signcert' => file_get_contents('dompdf.crt'),
4216
+	 *   'privkey' => file_get_contents('dompdf.key'),
4217
+	 *   'password' => 'password',
4218
+	 *   'name' => 'DomPDF DEMO',
4219
+	 *   'location' => 'Home',
4220
+	 *   'reason' => 'First Form',
4221
+	 *   'contactinfo' => 'info'
4222
+	 * ]);
4223
+	 * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
4224
+	 *
4225
+	 * @param string $signcert
4226
+	 * @param string $privkey
4227
+	 * @param string $password
4228
+	 * @param string|null $name
4229
+	 * @param string|null $location
4230
+	 * @param string|null $reason
4231
+	 * @param string|null $contactinfo
4232
+	 * @return int
4233
+	 */
4234
+	function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
4235
+		$sigId = ++$this->numObj;
4236
+		$this->o_sig($sigId, 'new', [
4237
+		  'SignCert' => $signcert,
4238
+		  'PrivKey' => $privkey,
4239
+		  'Password' => $password,
4240
+		  'Name' => $name,
4241
+		  'Location' => $location,
4242
+		  'Reason' => $reason,
4243
+		  'ContactInfo' => $contactinfo
4244
+		]);
4245
+
4246
+		return $sigId;
4247
+	}
4248
+
4249
+	/**
4250
+	 * add field to form
4251
+	 *
4252
+	 * @param string $type ACROFORM_FIELD_*
4253
+	 * @param string $name
4254
+	 * @param $x0
4255
+	 * @param $y0
4256
+	 * @param $x1
4257
+	 * @param $y1
4258
+	 * @param integer $ff Field Flag ACROFORM_FIELD_*_*
4259
+	 * @param float $size
4260
+	 * @param array $color
4261
+	 * @return int
4262
+	 */
4263
+	public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4264
+	{
4265
+		if (!$this->numFonts) {
4266
+			$this->selectFont($this->defaultFont);
4267
+		}
4268
+
4269
+		$color = implode(' ', $color) . ' rg';
4270
+
4271
+		$currentFontNum = $this->currentFontNum;
4272
+		$font = array_filter(
4273
+			$this->objects[$this->currentNode]['info']['fonts'],
4274
+			function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4275
+		);
4276
+
4277
+		$this->o_acroform($this->acroFormId, 'font',
4278
+		  ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4279
+
4280
+		$fieldId = ++$this->numObj;
4281
+		$this->o_field($fieldId, 'new', [
4282
+		  'rect' => [$x0, $y0, $x1, $y1],
4283
+		  'F' => 4,
4284
+		  'FT' => "/$type",
4285
+		  'T' => $name,
4286
+		  'Ff' => $ff,
4287
+		  'pageid' => $this->currentPage,
4288
+		  'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4289
+		]);
4290
+
4291
+		return $fieldId;
4292
+	}
4293
+
4294
+	/**
4295
+	 * set Field value
4296
+	 *
4297
+	 * @param integer $numFieldObj
4298
+	 * @param string $value
4299
+	 */
4300
+	public function setFormFieldValue($numFieldObj, $value)
4301
+	{
4302
+		$this->o_field($numFieldObj, 'set', ['value' => $value]);
4303
+	}
4304
+
4305
+	/**
4306
+	 * set Field value (reference)
4307
+	 *
4308
+	 * @param integer $numFieldObj
4309
+	 * @param integer $numObj Object number
4310
+	 */
4311
+	public function setFormFieldRefValue($numFieldObj, $numObj)
4312
+	{
4313
+		$this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
4314
+	}
4315
+
4316
+	/**
4317
+	 * set Field Appearanc (reference)
4318
+	 *
4319
+	 * @param integer $numFieldObj
4320
+	 * @param integer $normalNumObj
4321
+	 * @param integer|null $rolloverNumObj
4322
+	 * @param integer|null $downNumObj
4323
+	 */
4324
+	public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
4325
+	{
4326
+		$appearance['N'] = $normalNumObj;
4327
+
4328
+		if ($rolloverNumObj !== null) {
4329
+			$appearance['R'] = $rolloverNumObj;
4330
+		}
4331
+
4332
+		if ($downNumObj !== null) {
4333
+			$appearance['D'] = $downNumObj;
4334
+		}
4335
+
4336
+		$this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
4337
+	}
4338
+
4339
+	/**
4340
+	 * set Choice Field option values
4341
+	 *
4342
+	 * @param integer $numFieldObj
4343
+	 * @param array $value
4344
+	 */
4345
+	public function setFormFieldOpt($numFieldObj, $value)
4346
+	{
4347
+		$this->o_field($numFieldObj, 'set', ['options' => $value]);
4348
+	}
4349
+
4350
+	/**
4351
+	 * add form to document
4352
+	 *
4353
+	 * @param integer $sigFlags
4354
+	 * @param boolean $needAppearances
4355
+	 */
4356
+	public function addForm($sigFlags = 0, $needAppearances = false)
4357
+	{
4358
+		$this->acroFormId = ++$this->numObj;
4359
+		$this->o_acroform($this->acroFormId, 'new', [
4360
+		  'NeedAppearances' => $needAppearances ? 'true' : 'false',
4361
+		  'SigFlags' => $sigFlags
4362
+		]);
4363
+	}
4364
+
4365
+	/**
4366
+	 * save the current graphic state
4367
+	 */
4368
+	function save()
4369
+	{
4370
+		// we must reset the color cache or it will keep bad colors after clipping
4371
+		$this->currentColor = null;
4372
+		$this->currentStrokeColor = null;
4373
+		$this->addContent("\nq");
4374
+	}
4375
+
4376
+	/**
4377
+	 * restore the last graphic state
4378
+	 */
4379
+	function restore()
4380
+	{
4381
+		// we must reset the color cache or it will keep bad colors after clipping
4382
+		$this->currentColor = null;
4383
+		$this->currentStrokeColor = null;
4384
+		$this->addContent("\nQ");
4385
+	}
4386
+
4387
+	/**
4388
+	 * draw a clipping rectangle, all the elements added after this will be clipped
4389
+	 *
4390
+	 * @param float $x1
4391
+	 * @param float $y1
4392
+	 * @param float $width
4393
+	 * @param float $height
4394
+	 */
4395
+	function clippingRectangle($x1, $y1, $width, $height)
4396
+	{
4397
+		$this->save();
4398
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
4399
+	}
4400
+
4401
+	/**
4402
+	 * draw a clipping rounded rectangle, all the elements added after this will be clipped
4403
+	 *
4404
+	 * @param float $x1
4405
+	 * @param float $y1
4406
+	 * @param float $w
4407
+	 * @param float $h
4408
+	 * @param float $rTL
4409
+	 * @param float $rTR
4410
+	 * @param float $rBR
4411
+	 * @param float $rBL
4412
+	 */
4413
+	function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4414
+	{
4415
+		$this->save();
4416
+
4417
+		// start: top edge, left end
4418
+		$this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
4419
+
4420
+		// line: bottom edge, left end
4421
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
4422
+
4423
+		// curve: bottom-left corner
4424
+		$this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
4425
+
4426
+		// line: right edge, bottom end
4427
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
4428
+
4429
+		// curve: bottom-right corner
4430
+		$this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
4431
+
4432
+		// line: right edge, top end
4433
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
4434
+
4435
+		// curve: bottom-right corner
4436
+		$this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
4437
+
4438
+		// line: bottom edge, right end
4439
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
4440
+
4441
+		// curve: top-right corner
4442
+		$this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
4443
+
4444
+		// line: top edge, left end
4445
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
4446
+
4447
+		// Close & clip
4448
+		$this->addContent(" W n");
4449
+	}
4450
+
4451
+	/**
4452
+	 * draw a clipping polygon, the syntax for this is similar to the GD polygon command
4453
+	 *
4454
+	 * @param float[] $p
4455
+	 */
4456
+	public function clippingPolygon(array $p): void
4457
+	{
4458
+		$this->save();
4459
+
4460
+		$this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4461
+
4462
+		$n = count($p);
4463
+		for ($i = 2; $i < $n; $i = $i + 2) {
4464
+			$this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4465
+		}
4466
+
4467
+		$this->addContent("W n");
4468
+	}
4469
+
4470
+	/**
4471
+	 * ends the last clipping shape
4472
+	 */
4473
+	function clippingEnd()
4474
+	{
4475
+		$this->restore();
4476
+	}
4477
+
4478
+	/**
4479
+	 * scale
4480
+	 *
4481
+	 * @param float $s_x scaling factor for width as percent
4482
+	 * @param float $s_y scaling factor for height as percent
4483
+	 * @param float $x   Origin abscissa
4484
+	 * @param float $y   Origin ordinate
4485
+	 */
4486
+	function scale($s_x, $s_y, $x, $y)
4487
+	{
4488
+		$y = $this->currentPageSize["height"] - $y;
4489
+
4490
+		$tm = [
4491
+			$s_x,
4492
+			0,
4493
+			0,
4494
+			$s_y,
4495
+			$x * (1 - $s_x),
4496
+			$y * (1 - $s_y)
4497
+		];
4498
+
4499
+		$this->transform($tm);
4500
+	}
4501
+
4502
+	/**
4503
+	 * translate
4504
+	 *
4505
+	 * @param float $t_x movement to the right
4506
+	 * @param float $t_y movement to the bottom
4507
+	 */
4508
+	function translate($t_x, $t_y)
4509
+	{
4510
+		$tm = [
4511
+			1,
4512
+			0,
4513
+			0,
4514
+			1,
4515
+			$t_x,
4516
+			-$t_y
4517
+		];
4518
+
4519
+		$this->transform($tm);
4520
+	}
4521
+
4522
+	/**
4523
+	 * rotate
4524
+	 *
4525
+	 * @param float $angle angle in degrees for counter-clockwise rotation
4526
+	 * @param float $x     Origin abscissa
4527
+	 * @param float $y     Origin ordinate
4528
+	 */
4529
+	function rotate($angle, $x, $y)
4530
+	{
4531
+		$y = $this->currentPageSize["height"] - $y;
4532
+
4533
+		$a = deg2rad($angle);
4534
+		$cos_a = cos($a);
4535
+		$sin_a = sin($a);
4536
+
4537
+		$tm = [
4538
+			$cos_a,
4539
+			-$sin_a,
4540
+			$sin_a,
4541
+			$cos_a,
4542
+			$x - $sin_a * $y - $cos_a * $x,
4543
+			$y - $cos_a * $y + $sin_a * $x,
4544
+		];
4545
+
4546
+		$this->transform($tm);
4547
+	}
4548
+
4549
+	/**
4550
+	 * skew
4551
+	 *
4552
+	 * @param float $angle_x
4553
+	 * @param float $angle_y
4554
+	 * @param float $x Origin abscissa
4555
+	 * @param float $y Origin ordinate
4556
+	 */
4557
+	function skew($angle_x, $angle_y, $x, $y)
4558
+	{
4559
+		$y = $this->currentPageSize["height"] - $y;
4560
+
4561
+		$tan_x = tan(deg2rad($angle_x));
4562
+		$tan_y = tan(deg2rad($angle_y));
4563
+
4564
+		$tm = [
4565
+			1,
4566
+			-$tan_y,
4567
+			-$tan_x,
4568
+			1,
4569
+			$tan_x * $y,
4570
+			$tan_y * $x,
4571
+		];
4572
+
4573
+		$this->transform($tm);
4574
+	}
4575
+
4576
+	/**
4577
+	 * apply graphic transformations
4578
+	 *
4579
+	 * @param array $tm transformation matrix
4580
+	 */
4581
+	function transform($tm)
4582
+	{
4583
+		$this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
4584
+	}
4585
+
4586
+	/**
4587
+	 * add a new page to the document
4588
+	 * this also makes the new page the current active object
4589
+	 *
4590
+	 * @param int $insert
4591
+	 * @param int $id
4592
+	 * @param string $pos
4593
+	 * @return int
4594
+	 */
4595
+	function newPage($insert = 0, $id = 0, $pos = 'after')
4596
+	{
4597
+		// if there is a state saved, then go up the stack closing them
4598
+		// then on the new page, re-open them with the right setings
4599
+
4600
+		if ($this->nStateStack) {
4601
+			for ($i = $this->nStateStack; $i >= 1; $i--) {
4602
+				$this->restoreState($i);
4603
+			}
4604
+		}
4605
+
4606
+		$this->numObj++;
4607
+
4608
+		if ($insert) {
4609
+			// the id from the ezPdf class is the id of the contents of the page, not the page object itself
4610
+			// query that object to find the parent
4611
+			$rid = $this->objects[$id]['onPage'];
4612
+			$opt = ['rid' => $rid, 'pos' => $pos];
4613
+			$this->o_page($this->numObj, 'new', $opt);
4614
+		} else {
4615
+			$this->o_page($this->numObj, 'new');
4616
+		}
4617
+
4618
+		// if there is a stack saved, then put that onto the page
4619
+		if ($this->nStateStack) {
4620
+			for ($i = 1; $i <= $this->nStateStack; $i++) {
4621
+				$this->saveState($i);
4622
+			}
4623
+		}
4624
+
4625
+		// and if there has been a stroke or fill color set, then transfer them
4626
+		if (isset($this->currentColor)) {
4627
+			$this->setColor($this->currentColor, true);
4628
+		}
4629
+
4630
+		if (isset($this->currentStrokeColor)) {
4631
+			$this->setStrokeColor($this->currentStrokeColor, true);
4632
+		}
4633
+
4634
+		// if there is a line style set, then put this in too
4635
+		if (mb_strlen($this->currentLineStyle, '8bit')) {
4636
+			$this->addContent("\n$this->currentLineStyle");
4637
+		}
4638
+
4639
+		// the call to the o_page object set currentContents to the present page, so this can be returned as the page id
4640
+		return $this->currentContents;
4641
+	}
4642
+
4643
+	/**
4644
+	 * Streams the PDF to the client.
4645
+	 *
4646
+	 * @param string $filename The filename to present to the client.
4647
+	 * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
4648
+	 */
4649
+	function stream($filename = "document.pdf", $options = [])
4650
+	{
4651
+		if (headers_sent()) {
4652
+			die("Unable to stream pdf: headers already sent");
4653
+		}
4654
+
4655
+		if (!isset($options["compress"])) $options["compress"] = true;
4656
+		if (!isset($options["Attachment"])) $options["Attachment"] = true;
4657
+
4658
+		$debug = !$options['compress'];
4659
+		$tmp = ltrim($this->output($debug));
4660
+
4661
+		header("Cache-Control: private");
4662
+		header("Content-Type: application/pdf");
4663
+		header("Content-Length: " . mb_strlen($tmp, "8bit"));
4664
+
4665
+		$filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4666
+		$attachment = $options["Attachment"] ? "attachment" : "inline";
4667
+
4668
+		$encoding = mb_detect_encoding($filename);
4669
+		$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
4670
+		$fallbackfilename = str_replace("\"", "", $fallbackfilename);
4671
+		$encodedfilename = rawurlencode($filename);
4672
+
4673
+		$contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
4674
+		if ($fallbackfilename !== $filename) {
4675
+			$contentDisposition .= "; filename*=UTF-8''$encodedfilename";
4676
+		}
4677
+		header($contentDisposition);
4678
+
4679
+		echo $tmp;
4680
+		flush();
4681
+	}
4682
+
4683
+	/**
4684
+	 * return the height in units of the current font in the given size
4685
+	 *
4686
+	 * @param float $size
4687
+	 *
4688
+	 * @return float
4689
+	 */
4690
+	public function getFontHeight(float $size): float
4691
+	{
4692
+		if (!$this->numFonts) {
4693
+			$this->selectFont($this->defaultFont);
4694
+		}
4695
+
4696
+		$font = $this->fonts[$this->currentFont];
4697
+
4698
+		// for the current font, and the given size, what is the height of the font in user units
4699
+		if (isset($font['Ascender']) && isset($font['Descender'])) {
4700
+			$h = $font['Ascender'] - $font['Descender'];
4701
+		} else {
4702
+			$h = $font['FontBBox'][3] - $font['FontBBox'][1];
4703
+		}
4704
+
4705
+		// have to adjust by a font offset for Windows fonts.  unfortunately it looks like
4706
+		// the bounding box calculations are wrong and I don't know why.
4707
+		if (isset($font['FontHeightOffset'])) {
4708
+			// For CourierNew from Windows this needs to be -646 to match the
4709
+			// Adobe native Courier font.
4710
+			//
4711
+			// For FreeMono from GNU this needs to be -337 to match the
4712
+			// Courier font.
4713
+			//
4714
+			// Both have been added manually to the .afm and .ufm files.
4715
+			$h += (int)$font['FontHeightOffset'];
4716
+		}
4717
+
4718
+		return $size * $h / 1000;
4719
+	}
4720
+
4721
+	/**
4722
+	 * @param float $size
4723
+	 *
4724
+	 * @return float
4725
+	 */
4726
+	public function getFontXHeight(float $size): float
4727
+	{
4728
+		if (!$this->numFonts) {
4729
+			$this->selectFont($this->defaultFont);
4730
+		}
4731
+
4732
+		$font = $this->fonts[$this->currentFont];
4733
+
4734
+		// for the current font, and the given size, what is the height of the font in user units
4735
+		if (isset($font['XHeight'])) {
4736
+			$xh = $font['Ascender'] - $font['Descender'];
4737
+		} else {
4738
+			$xh = $this->getFontHeight($size) / 2;
4739
+		}
4740
+
4741
+		return $size * $xh / 1000;
4742
+	}
4743
+
4744
+	/**
4745
+	 * return the font descender, this will normally return a negative number
4746
+	 * if you add this number to the baseline, you get the level of the bottom of the font
4747
+	 * it is in the pdf user units
4748
+	 *
4749
+	 * @param float $size
4750
+	 *
4751
+	 * @return float
4752
+	 */
4753
+	public function getFontDescender(float $size): float
4754
+	{
4755
+		// note that this will most likely return a negative value
4756
+		if (!$this->numFonts) {
4757
+			$this->selectFont($this->defaultFont);
4758
+		}
4759
+
4760
+		//$h = $this->fonts[$this->currentFont]['FontBBox'][1];
4761
+		$h = $this->fonts[$this->currentFont]['Descender'];
4762
+
4763
+		return $size * $h / 1000;
4764
+	}
4765
+
4766
+	/**
4767
+	 * filter the text, this is applied to all text just before being inserted into the pdf document
4768
+	 * it escapes the various things that need to be escaped, and so on
4769
+	 *
4770
+	 * @param $text
4771
+	 * @param bool $bom
4772
+	 * @param bool $convert_encoding
4773
+	 * @return string
4774
+	 */
4775
+	function filterText($text, $bom = true, $convert_encoding = true)
4776
+	{
4777
+		if (!$this->numFonts) {
4778
+			$this->selectFont($this->defaultFont);
4779
+		}
4780
+
4781
+		if ($convert_encoding) {
4782
+			$cf = $this->currentFont;
4783
+			if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4784
+				$text = $this->utf8toUtf16BE($text, $bom);
4785
+			} else {
4786
+				//$text = html_entity_decode($text, ENT_QUOTES);
4787
+				$text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4788
+			}
4789
+		} elseif ($bom) {
4790
+			$text = $this->utf8toUtf16BE($text, $bom);
4791
+		}
4792
+
4793
+		// the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4794
+		return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
4795
+	}
4796
+
4797
+	/**
4798
+	 * return array containing codepoints (UTF-8 character values) for the
4799
+	 * string passed in.
4800
+	 *
4801
+	 * based on the excellent TCPDF code by Nicola Asuni and the
4802
+	 * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4803
+	 *
4804
+	 * @param string $text UTF-8 string to process
4805
+	 * @return array UTF-8 codepoints array for the string
4806
+	 */
4807
+	function utf8toCodePointsArray(&$text)
4808
+	{
4809
+		$length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4810
+		$unicode = []; // array containing unicode values
4811
+		$bytes = []; // array containing single character byte sequences
4812
+		$numbytes = 1; // number of octets needed to represent the UTF-8 character
4813
+
4814
+		for ($i = 0; $i < $length; $i++) {
4815
+			$c = ord($text[$i]); // get one string character at time
4816
+			if (count($bytes) === 0) { // get starting octect
4817
+				if ($c <= 0x7F) {
4818
+					$unicode[] = $c; // use the character "as is" because is ASCII
4819
+					$numbytes = 1;
4820
+				} elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4821
+					$bytes[] = ($c - 0xC0) << 0x06;
4822
+					$numbytes = 2;
4823
+				} elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4824
+					$bytes[] = ($c - 0xE0) << 0x0C;
4825
+					$numbytes = 3;
4826
+				} elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4827
+					$bytes[] = ($c - 0xF0) << 0x12;
4828
+					$numbytes = 4;
4829
+				} else {
4830
+					// use replacement character for other invalid sequences
4831
+					$unicode[] = 0xFFFD;
4832
+					$bytes = [];
4833
+					$numbytes = 1;
4834
+				}
4835
+			} elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4836
+				$bytes[] = $c - 0x80;
4837
+				if (count($bytes) === $numbytes) {
4838
+					// compose UTF-8 bytes to a single unicode value
4839
+					$c = $bytes[0];
4840
+					for ($j = 1; $j < $numbytes; $j++) {
4841
+						$c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4842
+					}
4843
+					if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
4844
+						// The definition of UTF-8 prohibits encoding character numbers between
4845
+						// U+D800 and U+DFFF, which are reserved for use with the UTF-16
4846
+						// encoding form (as surrogate pairs) and do not directly represent
4847
+						// characters.
4848
+						$unicode[] = 0xFFFD; // use replacement character
4849
+					} else {
4850
+						$unicode[] = $c; // add char to array
4851
+					}
4852
+					// reset data for next char
4853
+					$bytes = [];
4854
+					$numbytes = 1;
4855
+				}
4856
+			} else {
4857
+				// use replacement character for other invalid sequences
4858
+				$unicode[] = 0xFFFD;
4859
+				$bytes = [];
4860
+				$numbytes = 1;
4861
+			}
4862
+		}
4863
+
4864
+		return $unicode;
4865
+	}
4866
+
4867
+	/**
4868
+	 * convert UTF-8 to UTF-16 with an additional byte order marker
4869
+	 * at the front if required.
4870
+	 *
4871
+	 * based on the excellent TCPDF code by Nicola Asuni and the
4872
+	 * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4873
+	 *
4874
+	 * @param string  $text UTF-8 string to process
4875
+	 * @param boolean $bom  whether to add the byte order marker
4876
+	 * @return string UTF-16 result string
4877
+	 */
4878
+	function utf8toUtf16BE(&$text, $bom = true)
4879
+	{
4880
+		$out = $bom ? "\xFE\xFF" : '';
4881
+
4882
+		$unicode = $this->utf8toCodePointsArray($text);
4883
+		foreach ($unicode as $c) {
4884
+			if ($c === 0xFFFD) {
4885
+				$out .= "\xFF\xFD"; // replacement character
4886
+			} elseif ($c < 0x10000) {
4887
+				$out .= chr($c >> 0x08) . chr($c & 0xFF);
4888
+			} else {
4889
+				$c -= 0x10000;
4890
+				$w1 = 0xD800 | ($c >> 0x10);
4891
+				$w2 = 0xDC00 | ($c & 0x3FF);
4892
+				$out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4893
+			}
4894
+		}
4895
+
4896
+		return $out;
4897
+	}
4898
+
4899
+	/**
4900
+	 * given a start position and information about how text is to be laid out, calculate where
4901
+	 * on the page the text will end
4902
+	 *
4903
+	 * @param $x
4904
+	 * @param $y
4905
+	 * @param $angle
4906
+	 * @param $size
4907
+	 * @param $wa
4908
+	 * @param $text
4909
+	 * @return array
4910
+	 */
4911
+	private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4912
+	{
4913
+		// given this information return an array containing x and y for the end position as elements 0 and 1
4914
+		$w = $this->getTextWidth($size, $text);
4915
+
4916
+		// need to adjust for the number of spaces in this text
4917
+		$words = explode(' ', $text);
4918
+		$nspaces = count($words) - 1;
4919
+		$w += $wa * $nspaces;
4920
+		$a = deg2rad((float)$angle);
4921
+
4922
+		return [cos($a) * $w + $x, -sin($a) * $w + $y];
4923
+	}
4924
+
4925
+	/**
4926
+	 * Callback method used by smallCaps
4927
+	 *
4928
+	 * @param array $matches
4929
+	 *
4930
+	 * @return string
4931
+	 */
4932
+	function toUpper($matches)
4933
+	{
4934
+		return mb_strtoupper($matches[0]);
4935
+	}
4936
+
4937
+	function concatMatches($matches)
4938
+	{
4939
+		$str = "";
4940
+		foreach ($matches as $match) {
4941
+			$str .= $match[0];
4942
+		}
4943
+
4944
+		return $str;
4945
+	}
4946
+
4947
+	/**
4948
+	 * register text for font subsetting
4949
+	 *
4950
+	 * @param string $font
4951
+	 * @param string $text
4952
+	 */
4953
+	function registerText($font, $text)
4954
+	{
4955
+		if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4956
+			return;
4957
+		}
4958
+
4959
+		if (!isset($this->stringSubsets[$font])) {
4960
+			$base_subset = "\u{fffd}\u{fffe}\u{ffff}";
4961
+			$this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
4962
+		}
4963
+
4964
+		$this->stringSubsets[$font] = array_unique(
4965
+			array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4966
+		);
4967
+	}
4968
+
4969
+	/**
4970
+	 * add text to the document, at a specified location, size and angle on the page
4971
+	 *
4972
+	 * @param float  $x
4973
+	 * @param float  $y
4974
+	 * @param float  $size
4975
+	 * @param string $text
4976
+	 * @param float  $angle
4977
+	 * @param float  $wordSpaceAdjust
4978
+	 * @param float  $charSpaceAdjust
4979
+	 * @param bool   $smallCaps
4980
+	 */
4981
+	function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4982
+	{
4983
+		if (!$this->numFonts) {
4984
+			$this->selectFont($this->defaultFont);
4985
+		}
4986
+
4987
+		$text = str_replace(["\r", "\n"], "", $text);
4988
+
4989
+		// if ($smallCaps) {
4990
+		//     preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4991
+		//     $lower = $this->concatMatches($matches);
4992
+		//     d($lower);
4993
+
4994
+		//     preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4995
+		//     $other = $this->concatMatches($matches);
4996
+		//     d($other);
4997
+
4998
+		//     $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4999
+		// }
5000
+
5001
+		// if there are any open callbacks, then they should be called, to show the start of the line
5002
+		if ($this->nCallback > 0) {
5003
+			for ($i = $this->nCallback; $i > 0; $i--) {
5004
+				// call each function
5005
+				$info = [
5006
+					'x'         => $x,
5007
+					'y'         => $y,
5008
+					'angle'     => $angle,
5009
+					'status'    => 'sol',
5010
+					'p'         => $this->callback[$i]['p'],
5011
+					'nCallback' => $this->callback[$i]['nCallback'],
5012
+					'height'    => $this->callback[$i]['height'],
5013
+					'descender' => $this->callback[$i]['descender']
5014
+				];
5015
+
5016
+				$func = $this->callback[$i]['f'];
5017
+				$this->$func($info);
5018
+			}
5019
+		}
5020
+
5021
+		if ($angle == 0) {
5022
+			$this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5023
+		} else {
5024
+			$a = deg2rad((float)$angle);
5025
+			$this->addContent(
5026
+				sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5027
+			);
5028
+		}
5029
+
5030
+		if ($wordSpaceAdjust != 0) {
5031
+			$this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
5032
+		}
5033
+
5034
+		if ($charSpaceAdjust != 0) {
5035
+			$this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
5036
+		}
5037
+
5038
+		$len = mb_strlen($text);
5039
+		$start = 0;
5040
+
5041
+		if ($start < $len) {
5042
+			$part = $text; // OAR - Don't need this anymore, given that $start always equals zero.  substr($text, $start);
5043
+			$place_text = $this->filterText($part, false);
5044
+			// modify unicode text so that extra word spacing is manually implemented (bug #)
5045
+			if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5046
+				$space_scale = 1000 / $size;
5047
+				$place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5048
+			}
5049
+			$this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5050
+			$this->addContent(" [($place_text)] TJ");
5051
+		}
5052
+
5053
+		if ($wordSpaceAdjust != 0) {
5054
+			$this->addContent(sprintf(" %.3F Tw", 0));
5055
+		}
5056
+
5057
+		if ($charSpaceAdjust != 0) {
5058
+			$this->addContent(sprintf(" %.3F Tc", 0));
5059
+		}
5060
+
5061
+		$this->addContent(' ET');
5062
+
5063
+		// if there are any open callbacks, then they should be called, to show the end of the line
5064
+		if ($this->nCallback > 0) {
5065
+			for ($i = $this->nCallback; $i > 0; $i--) {
5066
+				// call each function
5067
+				$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
5068
+				$info = [
5069
+					'x'         => $tmp[0],
5070
+					'y'         => $tmp[1],
5071
+					'angle'     => $angle,
5072
+					'status'    => 'eol',
5073
+					'p'         => $this->callback[$i]['p'],
5074
+					'nCallback' => $this->callback[$i]['nCallback'],
5075
+					'height'    => $this->callback[$i]['height'],
5076
+					'descender' => $this->callback[$i]['descender']
5077
+				];
5078
+				$func = $this->callback[$i]['f'];
5079
+				$this->$func($info);
5080
+			}
5081
+		}
5082
+
5083
+		if ($this->fonts[$this->currentFont]['isSubsetting']) {
5084
+			$this->registerText($this->currentFont, $text);
5085
+		}
5086
+	}
5087
+
5088
+	/**
5089
+	 * calculate how wide a given text string will be on a page, at a given size.
5090
+	 * this can be called externally, but is also used by the other class functions
5091
+	 *
5092
+	 * @param float  $size
5093
+	 * @param string $text
5094
+	 * @param float  $wordSpacing
5095
+	 * @param float  $charSpacing
5096
+	 *
5097
+	 * @return float
5098
+	 */
5099
+	public function getTextWidth(float $size, string $text, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
5100
+	{
5101
+		static $ord_cache = [];
5102
+
5103
+		// this function should not change any of the settings, though it will need to
5104
+		// track any directives which change during calculation, so copy them at the start
5105
+		// and put them back at the end.
5106
+		$store_currentTextState = $this->currentTextState;
5107
+
5108
+		if (!$this->numFonts) {
5109
+			$this->selectFont($this->defaultFont);
5110
+		}
5111
+
5112
+		$text = str_replace(["\r", "\n"], "", $text);
5113
+
5114
+		// hmm, this is where it all starts to get tricky - use the font information to
5115
+		// calculate the width of each character, add them up and convert to user units
5116
+		$w = 0;
5117
+		$cf = $this->currentFont;
5118
+		$current_font = $this->fonts[$cf];
5119
+		$space_scale = 1000 / ($size > 0 ? $size : 1);
5120
+
5121
+		if ($current_font['isUnicode']) {
5122
+			// for Unicode, use the code points array to calculate width rather
5123
+			// than just the string itself
5124
+			$unicode = $this->utf8toCodePointsArray($text);
5125
+
5126
+			foreach ($unicode as $char) {
5127
+				// check if we have to replace character
5128
+				if (isset($current_font['differences'][$char])) {
5129
+					$char = $current_font['differences'][$char];
5130
+				}
5131
+
5132
+				if (isset($current_font['C'][$char])) {
5133
+					$char_width = $current_font['C'][$char];
5134
+
5135
+					// add the character width
5136
+					$w += $char_width;
5137
+
5138
+					// add additional padding for space
5139
+					if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5140
+						$w += $wordSpacing * $space_scale;
5141
+					}
5142
+				}
5143
+			}
5144
+
5145
+			// add additional char spacing
5146
+			if ($charSpacing != 0) {
5147
+				$w += $charSpacing * $space_scale * count($unicode);
5148
+			}
5149
+
5150
+		} else {
5151
+			// If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
5152
+			if ($this->isUnicode) {
5153
+				$text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
5154
+			}
5155
+
5156
+			$len = mb_strlen($text, 'Windows-1252');
5157
+
5158
+			for ($i = 0; $i < $len; $i++) {
5159
+				$c = $text[$i];
5160
+				$char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
5161
+
5162
+				// check if we have to replace character
5163
+				if (isset($current_font['differences'][$char])) {
5164
+					$char = $current_font['differences'][$char];
5165
+				}
5166
+
5167
+				if (isset($current_font['C'][$char])) {
5168
+					$char_width = $current_font['C'][$char];
5169
+
5170
+					// add the character width
5171
+					$w += $char_width;
5172
+
5173
+					// add additional padding for space
5174
+					if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5175
+						$w += $wordSpacing * $space_scale;
5176
+					}
5177
+				}
5178
+			}
5179
+
5180
+			// add additional char spacing
5181
+			if ($charSpacing != 0) {
5182
+				$w += $charSpacing * $space_scale * $len;
5183
+			}
5184
+		}
5185
+
5186
+		$this->currentTextState = $store_currentTextState;
5187
+		$this->setCurrentFont();
5188
+
5189
+		return $w * $size / 1000;
5190
+	}
5191
+
5192
+	/**
5193
+	 * this will be called at a new page to return the state to what it was on the
5194
+	 * end of the previous page, before the stack was closed down
5195
+	 * This is to get around not being able to have open 'q' across pages
5196
+	 *
5197
+	 * @param int $pageEnd
5198
+	 */
5199
+	function saveState($pageEnd = 0)
5200
+	{
5201
+		if ($pageEnd) {
5202
+			// this will be called at a new page to return the state to what it was on the
5203
+			// end of the previous page, before the stack was closed down
5204
+			// This is to get around not being able to have open 'q' across pages
5205
+			$opt = $this->stateStack[$pageEnd];
5206
+			// ok to use this as stack starts numbering at 1
5207
+			$this->setColor($opt['col'], true);
5208
+			$this->setStrokeColor($opt['str'], true);
5209
+			$this->addContent("\n" . $opt['lin']);
5210
+			//    $this->currentLineStyle = $opt['lin'];
5211
+		} else {
5212
+			$this->nStateStack++;
5213
+			$this->stateStack[$this->nStateStack] = [
5214
+				'col' => $this->currentColor,
5215
+				'str' => $this->currentStrokeColor,
5216
+				'lin' => $this->currentLineStyle
5217
+			];
5218
+		}
5219
+
5220
+		$this->save();
5221
+	}
5222
+
5223
+	/**
5224
+	 * restore a previously saved state
5225
+	 *
5226
+	 * @param int $pageEnd
5227
+	 */
5228
+	function restoreState($pageEnd = 0)
5229
+	{
5230
+		if (!$pageEnd) {
5231
+			$n = $this->nStateStack;
5232
+			$this->currentColor = $this->stateStack[$n]['col'];
5233
+			$this->currentStrokeColor = $this->stateStack[$n]['str'];
5234
+			$this->addContent("\n" . $this->stateStack[$n]['lin']);
5235
+			$this->currentLineStyle = $this->stateStack[$n]['lin'];
5236
+			$this->stateStack[$n] = null;
5237
+			unset($this->stateStack[$n]);
5238
+			$this->nStateStack--;
5239
+		}
5240
+
5241
+		$this->restore();
5242
+	}
5243
+
5244
+	/**
5245
+	 * make a loose object, the output will go into this object, until it is closed, then will revert to
5246
+	 * the current one.
5247
+	 * this object will not appear until it is included within a page.
5248
+	 * the function will return the object number
5249
+	 *
5250
+	 * @return int
5251
+	 */
5252
+	function openObject()
5253
+	{
5254
+		$this->nStack++;
5255
+		$this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5256
+		// add a new object of the content type, to hold the data flow
5257
+		$this->numObj++;
5258
+		$this->o_contents($this->numObj, 'new');
5259
+		$this->currentContents = $this->numObj;
5260
+		$this->looseObjects[$this->numObj] = 1;
5261
+
5262
+		return $this->numObj;
5263
+	}
5264
+
5265
+	/**
5266
+	 * open an existing object for editing
5267
+	 *
5268
+	 * @param $id
5269
+	 */
5270
+	function reopenObject($id)
5271
+	{
5272
+		$this->nStack++;
5273
+		$this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5274
+		$this->currentContents = $id;
5275
+
5276
+		// also if this object is the primary contents for a page, then set the current page to its parent
5277
+		if (isset($this->objects[$id]['onPage'])) {
5278
+			$this->currentPage = $this->objects[$id]['onPage'];
5279
+		}
5280
+	}
5281
+
5282
+	/**
5283
+	 * close an object
5284
+	 */
5285
+	function closeObject()
5286
+	{
5287
+		// close the object, as long as there was one open in the first place, which will be indicated by
5288
+		// an objectId on the stack.
5289
+		if ($this->nStack > 0) {
5290
+			$this->currentContents = $this->stack[$this->nStack]['c'];
5291
+			$this->currentPage = $this->stack[$this->nStack]['p'];
5292
+			$this->nStack--;
5293
+			// easier to probably not worry about removing the old entries, they will be overwritten
5294
+			// if there are new ones.
5295
+		}
5296
+	}
5297
+
5298
+	/**
5299
+	 * stop an object from appearing on pages from this point on
5300
+	 *
5301
+	 * @param $id
5302
+	 */
5303
+	function stopObject($id)
5304
+	{
5305
+		// if an object has been appearing on pages up to now, then stop it, this page will
5306
+		// be the last one that could contain it.
5307
+		if (isset($this->addLooseObjects[$id])) {
5308
+			$this->addLooseObjects[$id] = '';
5309
+		}
5310
+	}
5311
+
5312
+	/**
5313
+	 * after an object has been created, it wil only show if it has been added, using this function.
5314
+	 *
5315
+	 * @param $id
5316
+	 * @param string $options
5317
+	 */
5318
+	function addObject($id, $options = 'add')
5319
+	{
5320
+		// add the specified object to the page
5321
+		if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
5322
+			// then it is a valid object, and it is not being added to itself
5323
+			switch ($options) {
5324
+				case 'all':
5325
+					// then this object is to be added to this page (done in the next block) and
5326
+					// all future new pages.
5327
+					$this->addLooseObjects[$id] = 'all';
5328
+
5329
+				case 'add':
5330
+					if (isset($this->objects[$this->currentContents]['onPage'])) {
5331
+						// then the destination contents is the primary for the page
5332
+						// (though this object is actually added to that page)
5333
+						$this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
5334
+					}
5335
+					break;
5336
+
5337
+				case 'even':
5338
+					$this->addLooseObjects[$id] = 'even';
5339
+					$pageObjectId = $this->objects[$this->currentContents]['onPage'];
5340
+					if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
5341
+						$this->addObject($id);
5342
+						// hacky huh :)
5343
+					}
5344
+					break;
5345
+
5346
+				case 'odd':
5347
+					$this->addLooseObjects[$id] = 'odd';
5348
+					$pageObjectId = $this->objects[$this->currentContents]['onPage'];
5349
+					if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
5350
+						$this->addObject($id);
5351
+						// hacky huh :)
5352
+					}
5353
+					break;
5354
+
5355
+				case 'next':
5356
+					$this->addLooseObjects[$id] = 'all';
5357
+					break;
5358
+
5359
+				case 'nexteven':
5360
+					$this->addLooseObjects[$id] = 'even';
5361
+					break;
5362
+
5363
+				case 'nextodd':
5364
+					$this->addLooseObjects[$id] = 'odd';
5365
+					break;
5366
+			}
5367
+		}
5368
+	}
5369
+
5370
+	/**
5371
+	 * return a storable representation of a specific object
5372
+	 *
5373
+	 * @param $id
5374
+	 * @return string|null
5375
+	 */
5376
+	function serializeObject($id)
5377
+	{
5378
+		if (array_key_exists($id, $this->objects)) {
5379
+			return serialize($this->objects[$id]);
5380
+		}
5381
+
5382
+		return null;
5383
+	}
5384
+
5385
+	/**
5386
+	 * restore an object from its stored representation. Returns its new object id.
5387
+	 *
5388
+	 * @param $obj
5389
+	 * @return int
5390
+	 */
5391
+	function restoreSerializedObject($obj)
5392
+	{
5393
+		$obj_id = $this->openObject();
5394
+		$this->objects[$obj_id] = unserialize($obj);
5395
+		$this->closeObject();
5396
+
5397
+		return $obj_id;
5398
+	}
5399
+
5400
+	/**
5401
+	 * Embeds a file inside the PDF
5402
+	 *
5403
+	 * @param string $filepath path to the file to store inside the PDF
5404
+	 * @param string $embeddedFilename the filename displayed in the list of embedded files
5405
+	 * @param string $description a description in the list of embedded files
5406
+	 */
5407
+	public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
5408
+	{
5409
+		$this->numObj++;
5410
+		$this->o_embedded_file_dictionary(
5411
+			$this->numObj,
5412
+			'new',
5413
+			[
5414
+				'filepath' => $filepath,
5415
+				'filename' => $embeddedFilename,
5416
+				'description' => $description
5417
+			]
5418
+		);
5419
+	}
5420
+
5421
+	/**
5422
+	 * Add content to the documents info object
5423
+	 *
5424
+	 * @param string|array $label
5425
+	 * @param string       $value
5426
+	 */
5427
+	public function addInfo($label, string $value = ""): void
5428
+	{
5429
+		// this will only work if the label is one of the valid ones.
5430
+		// modify this so that arrays can be passed as well.
5431
+		// if $label is an array then assume that it is key => value pairs
5432
+		// else assume that they are both scalar, anything else will probably error
5433
+		if (is_array($label)) {
5434
+			foreach ($label as $l => $v) {
5435
+				$this->o_info($this->infoObject, $l, (string) $v);
5436
+			}
5437
+		} else {
5438
+			$this->o_info($this->infoObject, $label, $value);
5439
+		}
5440
+	}
5441
+
5442
+	/**
5443
+	 * set the viewer preferences of the document, it is up to the browser to obey these.
5444
+	 *
5445
+	 * @param $label
5446
+	 * @param int $value
5447
+	 */
5448
+	function setPreferences($label, $value = 0)
5449
+	{
5450
+		// this will only work if the label is one of the valid ones.
5451
+		if (is_array($label)) {
5452
+			foreach ($label as $l => $v) {
5453
+				$this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
5454
+			}
5455
+		} else {
5456
+			$this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
5457
+		}
5458
+	}
5459
+
5460
+	/**
5461
+	 * extract an integer from a position in a byte stream
5462
+	 *
5463
+	 * @param $data
5464
+	 * @param $pos
5465
+	 * @param $num
5466
+	 * @return int
5467
+	 */
5468
+	private function getBytes(&$data, $pos, $num)
5469
+	{
5470
+		// return the integer represented by $num bytes from $pos within $data
5471
+		$ret = 0;
5472
+		for ($i = 0; $i < $num; $i++) {
5473
+			$ret *= 256;
5474
+			$ret += ord($data[$pos + $i]);
5475
+		}
5476
+
5477
+		return $ret;
5478
+	}
5479
+
5480
+	/**
5481
+	 * Check if image already added to pdf image directory.
5482
+	 * If yes, need not to create again (pass empty data)
5483
+	 *
5484
+	 * @param string $imgname
5485
+	 * @return bool
5486
+	 */
5487
+	function image_iscached($imgname)
5488
+	{
5489
+		return isset($this->imagelist[$imgname]);
5490
+	}
5491
+
5492
+	/**
5493
+	 * add a PNG image into the document, from a GD object
5494
+	 * this should work with remote files
5495
+	 *
5496
+	 * @param \GdImage|resource $img A GD resource
5497
+	 * @param string $file The PNG file
5498
+	 * @param float $x X position
5499
+	 * @param float $y Y position
5500
+	 * @param float $w Width
5501
+	 * @param float $h Height
5502
+	 * @param bool $is_mask true if the image is a mask
5503
+	 * @param bool $mask true if the image is masked
5504
+	 * @throws Exception
5505
+	 */
5506
+	function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5507
+	{
5508
+		if (!function_exists("imagepng")) {
5509
+			throw new \Exception("The PHP GD extension is required, but is not installed.");
5510
+		}
5511
+
5512
+		//if already cached, need not to read again
5513
+		if (isset($this->imagelist[$file])) {
5514
+			$data = null;
5515
+		} else {
5516
+			// Example for transparency handling on new image. Retain for current image
5517
+			// $tIndex = imagecolortransparent($img);
5518
+			// if ($tIndex > 0) {
5519
+			//   $tColor    = imagecolorsforindex($img, $tIndex);
5520
+			//   $new_tIndex    = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
5521
+			//   imagefill($new_img, 0, 0, $new_tIndex);
5522
+			//   imagecolortransparent($new_img, $new_tIndex);
5523
+			// }
5524
+			// blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
5525
+			//imagealphablending($img, true);
5526
+
5527
+			//default, but explicitely set to ensure pdf compatibility
5528
+			imagesavealpha($img, false/*!$is_mask && !$mask*/);
5529
+
5530
+			$error = 0;
5531
+			//DEBUG_IMG_TEMP
5532
+			//debugpng
5533
+			if (defined("DEBUGPNG") && DEBUGPNG) {
5534
+				print '[addImagePng ' . $file . ']';
5535
+			}
5536
+
5537
+			ob_start();
5538
+			@imagepng($img);
5539
+			$data = ob_get_clean();
5540
+
5541
+			if ($data == '') {
5542
+				$error = 1;
5543
+				$errormsg = 'trouble writing file from GD';
5544
+				//DEBUG_IMG_TEMP
5545
+				//debugpng
5546
+				if (defined("DEBUGPNG") && DEBUGPNG) {
5547
+					print 'trouble writing file from GD';
5548
+				}
5549
+			}
5550
+
5551
+			if ($error) {
5552
+				$this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5553
+
5554
+				return;
5555
+			}
5556
+		}  //End isset($this->imagelist[$file]) (png Duplicate removal)
5557
+
5558
+		$this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
5559
+	}
5560
+
5561
+	/**
5562
+	 * @param $file
5563
+	 * @param $x
5564
+	 * @param $y
5565
+	 * @param $w
5566
+	 * @param $h
5567
+	 * @param $byte
5568
+	 */
5569
+	protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
5570
+	{
5571
+		// generate images
5572
+		$img = @imagecreatefrompng($file);
5573
+
5574
+		if ($img === false) {
5575
+			return;
5576
+		}
5577
+
5578
+		// FIXME The pixel transformation doesn't work well with 8bit PNGs
5579
+		$eight_bit = ($byte & 4) !== 4;
5580
+
5581
+		$wpx = imagesx($img);
5582
+		$hpx = imagesy($img);
5583
+
5584
+		imagesavealpha($img, false);
5585
+
5586
+		// create temp alpha file
5587
+		$tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
5588
+		@unlink($tempfile_alpha);
5589
+		$tempfile_alpha = "$tempfile_alpha.png";
5590
+
5591
+		// create temp plain file
5592
+		$tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
5593
+		@unlink($tempfile_plain);
5594
+		$tempfile_plain = "$tempfile_plain.png";
5595
+
5596
+		$imgalpha = imagecreate($wpx, $hpx);
5597
+		imagesavealpha($imgalpha, false);
5598
+
5599
+		// generate gray scale palette (0 -> 255)
5600
+		for ($c = 0; $c < 256; ++$c) {
5601
+			imagecolorallocate($imgalpha, $c, $c, $c);
5602
+		}
5603
+
5604
+		// Use PECL gmagick + Graphics Magic to process transparent PNG images
5605
+		if (extension_loaded("gmagick")) {
5606
+			$gmagick = new \Gmagick($file);
5607
+			$gmagick->setimageformat('png');
5608
+
5609
+			// Get opacity channel (negative of alpha channel)
5610
+			$alpha_channel_neg = clone $gmagick;
5611
+			$alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
5612
+
5613
+			// Negate opacity channel
5614
+			$alpha_channel = new \Gmagick();
5615
+			$alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
5616
+			$alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
5617
+			$alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
5618
+			$alpha_channel->writeimage($tempfile_alpha);
5619
+
5620
+			// Cast to 8bit+palette
5621
+			$imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5622
+			imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5623
+			imagedestroy($imgalpha_);
5624
+			imagepng($imgalpha, $tempfile_alpha);
5625
+
5626
+			// Make opaque image
5627
+			$color_channels = new \Gmagick();
5628
+			$color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
5629
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
5630
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
5631
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
5632
+			$color_channels->writeimage($tempfile_plain);
5633
+
5634
+			$imgplain = @imagecreatefrompng($tempfile_plain);
5635
+		}
5636
+		// Use PECL imagick + ImageMagic to process transparent PNG images
5637
+		elseif (extension_loaded("imagick")) {
5638
+			// Native cloning was added to pecl-imagick in svn commit 263814
5639
+			// the first version containing it was 3.0.1RC1
5640
+			static $imagickClonable = null;
5641
+			if ($imagickClonable === null) {
5642
+				$imagickClonable = true;
5643
+				if (defined('Imagick::IMAGICK_EXTVER')) {
5644
+					$imagickVersion = \Imagick::IMAGICK_EXTVER;
5645
+				} else {
5646
+					$imagickVersion = '0';
5647
+				}
5648
+				if (version_compare($imagickVersion, '0.0.1', '>=')) {
5649
+					$imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
5650
+				}
5651
+			}
5652
+
5653
+			$imagick = new \Imagick($file);
5654
+			$imagick->setFormat('png');
5655
+
5656
+			// Get opacity channel (negative of alpha channel)
5657
+			if ($imagick->getImageAlphaChannel()) {
5658
+				$alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
5659
+				$alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
5660
+				// Since ImageMagick7 negate invert transparency as default
5661
+				if (\Imagick::getVersion()['versionNumber'] < 1800) {
5662
+					$alpha_channel->negateImage(true);
5663
+				}
5664
+				$alpha_channel->writeImage($tempfile_alpha);
5665
+
5666
+				// Cast to 8bit+palette
5667
+				$imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5668
+				imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5669
+				imagedestroy($imgalpha_);
5670
+				imagepng($imgalpha, $tempfile_alpha);
5671
+			} else {
5672
+				$tempfile_alpha = null;
5673
+			}
5674
+
5675
+			// Make opaque image
5676
+			$color_channels = new \Imagick();
5677
+			$color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
5678
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
5679
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
5680
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
5681
+			$color_channels->writeImage($tempfile_plain);
5682
+
5683
+			$imgplain = @imagecreatefrompng($tempfile_plain);
5684
+		} else {
5685
+			// allocated colors cache
5686
+			$allocated_colors = [];
5687
+
5688
+			// extract alpha channel
5689
+			for ($xpx = 0; $xpx < $wpx; ++$xpx) {
5690
+				for ($ypx = 0; $ypx < $hpx; ++$ypx) {
5691
+					$color = imagecolorat($img, $xpx, $ypx);
5692
+					$col = imagecolorsforindex($img, $color);
5693
+					$alpha = $col['alpha'];
5694
+
5695
+					if ($eight_bit) {
5696
+						// with gamma correction
5697
+						$gammacorr = 2.2;
5698
+						$pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
5699
+					} else {
5700
+						// without gamma correction
5701
+						$pixel = (127 - $alpha) * 2;
5702
+
5703
+						$key = $col['red'] . $col['green'] . $col['blue'];
5704
+
5705
+						if (!isset($allocated_colors[$key])) {
5706
+							$pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5707
+							$allocated_colors[$key] = $pixel_img;
5708
+						} else {
5709
+							$pixel_img = $allocated_colors[$key];
5710
+						}
5711
+
5712
+						imagesetpixel($img, $xpx, $ypx, $pixel_img);
5713
+					}
5714
+
5715
+					imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
5716
+				}
5717
+			}
5718
+
5719
+			// extract image without alpha channel
5720
+			$imgplain = imagecreatetruecolor($wpx, $hpx);
5721
+			imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
5722
+			imagedestroy($img);
5723
+
5724
+			imagepng($imgalpha, $tempfile_alpha);
5725
+			imagepng($imgplain, $tempfile_plain);
5726
+		}
5727
+
5728
+		$this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
5729
+
5730
+		// embed mask image
5731
+		if ($tempfile_alpha) {
5732
+			$this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
5733
+			imagedestroy($imgalpha);
5734
+			$this->imageCache[] = $tempfile_alpha;
5735
+		}
5736
+
5737
+		// embed image, masked with previously embedded mask
5738
+		$this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
5739
+		imagedestroy($imgplain);
5740
+		$this->imageCache[] = $tempfile_plain;
5741
+	}
5742
+
5743
+	/**
5744
+	 * add a PNG image into the document, from a file
5745
+	 * this should work with remote files
5746
+	 *
5747
+	 * @param $file
5748
+	 * @param $x
5749
+	 * @param $y
5750
+	 * @param int $w
5751
+	 * @param int $h
5752
+	 * @throws Exception
5753
+	 */
5754
+	function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5755
+	{
5756
+		if (!function_exists("imagecreatefrompng")) {
5757
+			throw new \Exception("The PHP GD extension is required, but is not installed.");
5758
+		}
5759
+
5760
+		if (isset($this->imageAlphaList[$file])) {
5761
+			[$alphaFile, $plainFile] = $this->imageAlphaList[$file];
5762
+
5763
+			if ($alphaFile) {
5764
+				$img = null;
5765
+				$this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
5766
+			}
5767
+
5768
+			$img = null;
5769
+			$this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
5770
+			return;
5771
+		}
5772
+
5773
+		//if already cached, need not to read again
5774
+		if (isset($this->imagelist[$file])) {
5775
+			$img = null;
5776
+		} else {
5777
+			$info = file_get_contents($file, false, null, 24, 5);
5778
+			$meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
5779
+			$bit_depth = $meta["bitDepth"];
5780
+			$color_type = $meta["colorType"];
5781
+
5782
+			// http://www.w3.org/TR/PNG/#11IHDR
5783
+			// 3 => indexed
5784
+			// 4 => greyscale with alpha
5785
+			// 6 => fullcolor with alpha
5786
+			$is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
5787
+
5788
+			if ($is_alpha) { // exclude grayscale alpha
5789
+				$this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
5790
+				return;
5791
+			}
5792
+
5793
+			//png files typically contain an alpha channel.
5794
+			//pdf file format or class.pdf does not support alpha blending.
5795
+			//on alpha blended images, more transparent areas have a color near black.
5796
+			//This appears in the result on not storing the alpha channel.
5797
+			//Correct would be the box background image or its parent when transparent.
5798
+			//But this would make the image dependent on the background.
5799
+			//Therefore create an image with white background and copy in
5800
+			//A more natural background than black is white.
5801
+			//Therefore create an empty image with white background and merge the
5802
+			//image in with alpha blending.
5803
+			$imgtmp = @imagecreatefrompng($file);
5804
+			if (!$imgtmp) {
5805
+				return;
5806
+			}
5807
+			$sx = imagesx($imgtmp);
5808
+			$sy = imagesy($imgtmp);
5809
+			$img = imagecreatetruecolor($sx, $sy);
5810
+			imagealphablending($img, true);
5811
+
5812
+			// @todo is it still needed ??
5813
+			$ti = imagecolortransparent($imgtmp);
5814
+			if ($ti >= 0) {
5815
+				$tc = imagecolorsforindex($imgtmp, $ti);
5816
+				$ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5817
+				imagefill($img, 0, 0, $ti);
5818
+				imagecolortransparent($img, $ti);
5819
+			} else {
5820
+				imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5821
+			}
5822
+
5823
+			imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5824
+			imagedestroy($imgtmp);
5825
+		}
5826
+		$this->addImagePng($img, $file, $x, $y, $w, $h);
5827
+
5828
+		if ($img) {
5829
+			imagedestroy($img);
5830
+		}
5831
+	}
5832
+
5833
+	/**
5834
+	 * add a PNG image into the document, from a memory buffer of the file
5835
+	 *
5836
+	 * @param $data
5837
+	 * @param $file
5838
+	 * @param $x
5839
+	 * @param $y
5840
+	 * @param float $w
5841
+	 * @param float $h
5842
+	 * @param bool $is_mask
5843
+	 * @param null $mask
5844
+	 */
5845
+	function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5846
+	{
5847
+		if (isset($this->imagelist[$file])) {
5848
+			$data = null;
5849
+			$info['width'] = $this->imagelist[$file]['w'];
5850
+			$info['height'] = $this->imagelist[$file]['h'];
5851
+			$label = $this->imagelist[$file]['label'];
5852
+		} else {
5853
+			if ($data == null) {
5854
+				$this->addMessage('addPngFromBuf error - data not present!');
5855
+
5856
+				return;
5857
+			}
5858
+
5859
+			$error = 0;
5860
+
5861
+			if (!$error) {
5862
+				$header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5863
+
5864
+				if (mb_substr($data, 0, 8, '8bit') != $header) {
5865
+					$error = 1;
5866
+
5867
+					if (defined("DEBUGPNG") && DEBUGPNG) {
5868
+						print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5869
+					}
5870
+
5871
+					$errormsg = 'this file does not have a valid header';
5872
+				}
5873
+			}
5874
+
5875
+			if (!$error) {
5876
+				// set pointer
5877
+				$p = 8;
5878
+				$len = mb_strlen($data, '8bit');
5879
+
5880
+				// cycle through the file, identifying chunks
5881
+				$haveHeader = 0;
5882
+				$info = [];
5883
+				$idata = '';
5884
+				$pdata = '';
5885
+
5886
+				while ($p < $len) {
5887
+					$chunkLen = $this->getBytes($data, $p, 4);
5888
+					$chunkType = mb_substr($data, $p + 4, 4, '8bit');
5889
+
5890
+					switch ($chunkType) {
5891
+						case 'IHDR':
5892
+							// this is where all the file information comes from
5893
+							$info['width'] = $this->getBytes($data, $p + 8, 4);
5894
+							$info['height'] = $this->getBytes($data, $p + 12, 4);
5895
+							$info['bitDepth'] = ord($data[$p + 16]);
5896
+							$info['colorType'] = ord($data[$p + 17]);
5897
+							$info['compressionMethod'] = ord($data[$p + 18]);
5898
+							$info['filterMethod'] = ord($data[$p + 19]);
5899
+							$info['interlaceMethod'] = ord($data[$p + 20]);
5900
+
5901
+							//print_r($info);
5902
+							$haveHeader = 1;
5903
+							if ($info['compressionMethod'] != 0) {
5904
+								$error = 1;
5905
+
5906
+								//debugpng
5907
+								if (defined("DEBUGPNG") && DEBUGPNG) {
5908
+									print '[addPngFromFile unsupported compression method ' . $file . ']';
5909
+								}
5910
+
5911
+								$errormsg = 'unsupported compression method';
5912
+							}
5913
+
5914
+							if ($info['filterMethod'] != 0) {
5915
+								$error = 1;
5916
+
5917
+								//debugpng
5918
+								if (defined("DEBUGPNG") && DEBUGPNG) {
5919
+									print '[addPngFromFile unsupported filter method ' . $file . ']';
5920
+								}
5921
+
5922
+								$errormsg = 'unsupported filter method';
5923
+							}
5924
+							break;
5925
+
5926
+						case 'PLTE':
5927
+							$pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5928
+							break;
5929
+
5930
+						case 'IDAT':
5931
+							$idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5932
+							break;
5933
+
5934
+						case 'tRNS':
5935
+							//this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5936
+							//print "tRNS found, color type = ".$info['colorType']."\n";
5937
+							$transparency = [];
5938
+
5939
+							switch ($info['colorType']) {
5940
+								// indexed color, rbg
5941
+								case 3:
5942
+									/* corresponding to entries in the plte chunk
5943 5943
                                      Alpha for palette index 0: 1 byte
5944 5944
                                      Alpha for palette index 1: 1 byte
5945 5945
                                      ...etc...
5946 5946
                                     */
5947
-                                    // there will be one entry for each palette entry. up until the last non-opaque entry.
5948
-                                    // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5949
-                                    $transparency['type'] = 'indexed';
5950
-                                    $trans = 0;
5951
-
5952
-                                    for ($i = $chunkLen; $i >= 0; $i--) {
5953
-                                        if (ord($data[$p + 8 + $i]) == 0) {
5954
-                                            $trans = $i;
5955
-                                        }
5956
-                                    }
5957
-
5958
-                                    $transparency['data'] = $trans;
5959
-                                    break;
5960
-
5961
-                                // grayscale
5962
-                                case 0:
5963
-                                    /* corresponding to entries in the plte chunk
5947
+									// there will be one entry for each palette entry. up until the last non-opaque entry.
5948
+									// set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5949
+									$transparency['type'] = 'indexed';
5950
+									$trans = 0;
5951
+
5952
+									for ($i = $chunkLen; $i >= 0; $i--) {
5953
+										if (ord($data[$p + 8 + $i]) == 0) {
5954
+											$trans = $i;
5955
+										}
5956
+									}
5957
+
5958
+									$transparency['data'] = $trans;
5959
+									break;
5960
+
5961
+								// grayscale
5962
+								case 0:
5963
+									/* corresponding to entries in the plte chunk
5964 5964
                                      Gray: 2 bytes, range 0 .. (2^bitdepth)-1
5965 5965
                                     */
5966
-                                    //            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5967
-                                    $transparency['type'] = 'indexed';
5968
-                                    $transparency['data'] = ord($data[$p + 8 + 1]);
5969
-                                    break;
5970
-
5971
-                                // truecolor
5972
-                                case 2:
5973
-                                    /* corresponding to entries in the plte chunk
5966
+									//            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5967
+									$transparency['type'] = 'indexed';
5968
+									$transparency['data'] = ord($data[$p + 8 + 1]);
5969
+									break;
5970
+
5971
+								// truecolor
5972
+								case 2:
5973
+									/* corresponding to entries in the plte chunk
5974 5974
                                      Red: 2 bytes, range 0 .. (2^bitdepth)-1
5975 5975
                                      Green: 2 bytes, range 0 .. (2^bitdepth)-1
5976 5976
                                      Blue: 2 bytes, range 0 .. (2^bitdepth)-1
5977 5977
                                     */
5978
-                                    $transparency['r'] = $this->getBytes($data, $p + 8, 2);
5979
-                                    // r from truecolor
5980
-                                    $transparency['g'] = $this->getBytes($data, $p + 10, 2);
5981
-                                    // g from truecolor
5982
-                                    $transparency['b'] = $this->getBytes($data, $p + 12, 2);
5983
-                                    // b from truecolor
5984
-
5985
-                                    $transparency['type'] = 'color-key';
5986
-                                    break;
5987
-
5988
-                                //unsupported transparency type
5989
-                                default:
5990
-                                    if (defined("DEBUGPNG") && DEBUGPNG) {
5991
-                                        print '[addPngFromFile unsupported transparency type ' . $file . ']';
5992
-                                    }
5993
-                                    break;
5994
-                            }
5995
-
5996
-                            // KS End new code
5997
-                            break;
5998
-
5999
-                        default:
6000
-                            break;
6001
-                    }
6002
-
6003
-                    $p += $chunkLen + 12;
6004
-                }
6005
-
6006
-                if (!$haveHeader) {
6007
-                    $error = 1;
6008
-
6009
-                    //debugpng
6010
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
6011
-                        print '[addPngFromFile information header is missing ' . $file . ']';
6012
-                    }
6013
-
6014
-                    $errormsg = 'information header is missing';
6015
-                }
6016
-
6017
-                if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
6018
-                    $error = 1;
6019
-
6020
-                    //debugpng
6021
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
6022
-                        print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6023
-                    }
6024
-
6025
-                    $errormsg = 'There appears to be no support for interlaced images in pdf.';
6026
-                }
6027
-            }
6028
-
6029
-            if (!$error && $info['bitDepth'] > 8) {
6030
-                $error = 1;
6031
-
6032
-                //debugpng
6033
-                if (defined("DEBUGPNG") && DEBUGPNG) {
6034
-                    print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6035
-                }
6036
-
6037
-                $errormsg = 'only bit depth of 8 or less is supported';
6038
-            }
6039
-
6040
-            if (!$error) {
6041
-                switch ($info['colorType']) {
6042
-                    case 3:
6043
-                        $color = 'DeviceRGB';
6044
-                        $ncolor = 1;
6045
-                        break;
6046
-
6047
-                    case 2:
6048
-                        $color = 'DeviceRGB';
6049
-                        $ncolor = 3;
6050
-                        break;
6051
-
6052
-                    case 0:
6053
-                        $color = 'DeviceGray';
6054
-                        $ncolor = 1;
6055
-                        break;
6056
-
6057
-                    default:
6058
-                        $error = 1;
6059
-
6060
-                        //debugpng
6061
-                        if (defined("DEBUGPNG") && DEBUGPNG) {
6062
-                            print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6063
-                        }
6064
-
6065
-                        $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
6066
-                }
6067
-            }
6068
-
6069
-            if ($error) {
6070
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6071
-
6072
-                return;
6073
-            }
6074
-
6075
-            //print_r($info);
6076
-            // so this image is ok... add it in.
6077
-            $this->numImages++;
6078
-            $im = $this->numImages;
6079
-            $label = "I$im";
6080
-            $this->numObj++;
6081
-
6082
-            //  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
6083
-            $options = [
6084
-                'label'            => $label,
6085
-                'data'             => $idata,
6086
-                'bitsPerComponent' => $info['bitDepth'],
6087
-                'pdata'            => $pdata,
6088
-                'iw'               => $info['width'],
6089
-                'ih'               => $info['height'],
6090
-                'type'             => 'png',
6091
-                'color'            => $color,
6092
-                'ncolor'           => $ncolor,
6093
-                'masked'           => $mask,
6094
-                'isMask'           => $is_mask
6095
-            ];
6096
-
6097
-            if (isset($transparency)) {
6098
-                $options['transparency'] = $transparency;
6099
-            }
6100
-
6101
-            $this->o_image($this->numObj, 'new', $options);
6102
-            $this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
6103
-        }
6104
-
6105
-        if ($is_mask) {
6106
-            return;
6107
-        }
6108
-
6109
-        if ($w <= 0 && $h <= 0) {
6110
-            $w = $info['width'];
6111
-            $h = $info['height'];
6112
-        }
6113
-
6114
-        if ($w <= 0) {
6115
-            $w = $h / $info['height'] * $info['width'];
6116
-        }
6117
-
6118
-        if ($h <= 0) {
6119
-            $h = $w * $info['height'] / $info['width'];
6120
-        }
6121
-
6122
-        $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
6123
-    }
6124
-
6125
-    /**
6126
-     * add a JPEG image into the document, from a file
6127
-     *
6128
-     * @param $img
6129
-     * @param $x
6130
-     * @param $y
6131
-     * @param int $w
6132
-     * @param int $h
6133
-     */
6134
-    function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
6135
-    {
6136
-        // attempt to add a jpeg image straight from a file, using no GD commands
6137
-        // note that this function is unable to operate on a remote file.
6138
-
6139
-        if (!file_exists($img)) {
6140
-            return;
6141
-        }
6142
-
6143
-        if ($this->image_iscached($img)) {
6144
-            $data = null;
6145
-            $imageWidth = $this->imagelist[$img]['w'];
6146
-            $imageHeight = $this->imagelist[$img]['h'];
6147
-            $channels = $this->imagelist[$img]['c'];
6148
-        } else {
6149
-            $tmp = getimagesize($img);
6150
-            $imageWidth = $tmp[0];
6151
-            $imageHeight = $tmp[1];
6152
-
6153
-            if (isset($tmp['channels'])) {
6154
-                $channels = $tmp['channels'];
6155
-            } else {
6156
-                $channels = 3;
6157
-            }
6158
-
6159
-            $data = file_get_contents($img);
6160
-        }
6161
-
6162
-        if ($w <= 0 && $h <= 0) {
6163
-            $w = $imageWidth;
6164
-        }
6165
-
6166
-        if ($w == 0) {
6167
-            $w = $h / $imageHeight * $imageWidth;
6168
-        }
6169
-
6170
-        if ($h == 0) {
6171
-            $h = $w * $imageHeight / $imageWidth;
6172
-        }
6173
-
6174
-        $this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
6175
-    }
6176
-
6177
-    /**
6178
-     * common code used by the two JPEG adding functions
6179
-     * @param $data
6180
-     * @param $imgname
6181
-     * @param $imageWidth
6182
-     * @param $imageHeight
6183
-     * @param $x
6184
-     * @param $y
6185
-     * @param int $w
6186
-     * @param int $h
6187
-     * @param int $channels
6188
-     */
6189
-    private function addJpegImage_common(
6190
-        &$data,
6191
-        $imgname,
6192
-        $imageWidth,
6193
-        $imageHeight,
6194
-        $x,
6195
-        $y,
6196
-        $w = 0,
6197
-        $h = 0,
6198
-        $channels = 3
6199
-    ) {
6200
-        if ($this->image_iscached($imgname)) {
6201
-            $label = $this->imagelist[$imgname]['label'];
6202
-            //debugpng
6203
-            //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
6204
-
6205
-        } else {
6206
-            if ($data == null) {
6207
-                $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6208
-
6209
-                return;
6210
-            }
6211
-
6212
-            // note that this function is not to be called externally
6213
-            // it is just the common code between the GD and the file options
6214
-            $this->numImages++;
6215
-            $im = $this->numImages;
6216
-            $label = "I$im";
6217
-            $this->numObj++;
6218
-
6219
-            $this->o_image(
6220
-                $this->numObj,
6221
-                'new',
6222
-                [
6223
-                    'label'    => $label,
6224
-                    'data'     => &$data,
6225
-                    'iw'       => $imageWidth,
6226
-                    'ih'       => $imageHeight,
6227
-                    'channels' => $channels
6228
-                ]
6229
-            );
6230
-
6231
-            $this->imagelist[$imgname] = [
6232
-                'label' => $label,
6233
-                'w'     => $imageWidth,
6234
-                'h'     => $imageHeight,
6235
-                'c'     => $channels
6236
-            ];
6237
-        }
6238
-
6239
-        $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
6240
-    }
6241
-
6242
-    /**
6243
-     * specify where the document should open when it first starts
6244
-     *
6245
-     * @param $style
6246
-     * @param int $a
6247
-     * @param int $b
6248
-     * @param int $c
6249
-     */
6250
-    function openHere($style, $a = 0, $b = 0, $c = 0)
6251
-    {
6252
-        // this function will open the document at a specified page, in a specified style
6253
-        // the values for style, and the required parameters are:
6254
-        // 'XYZ'  left, top, zoom
6255
-        // 'Fit'
6256
-        // 'FitH' top
6257
-        // 'FitV' left
6258
-        // 'FitR' left,bottom,right
6259
-        // 'FitB'
6260
-        // 'FitBH' top
6261
-        // 'FitBV' left
6262
-        $this->numObj++;
6263
-        $this->o_destination(
6264
-            $this->numObj,
6265
-            'new',
6266
-            ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6267
-        );
6268
-        $id = $this->catalogId;
6269
-        $this->o_catalog($id, 'openHere', $this->numObj);
6270
-    }
6271
-
6272
-    /**
6273
-     * Add JavaScript code to the PDF document
6274
-     *
6275
-     * @param string $code
6276
-     */
6277
-    function addJavascript($code)
6278
-    {
6279
-        $this->javascript .= $code;
6280
-    }
6281
-
6282
-    /**
6283
-     * create a labelled destination within the document
6284
-     *
6285
-     * @param $label
6286
-     * @param $style
6287
-     * @param int $a
6288
-     * @param int $b
6289
-     * @param int $c
6290
-     */
6291
-    function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
6292
-    {
6293
-        // associates the given label with the destination, it is done this way so that a destination can be specified after
6294
-        // it has been linked to
6295
-        // styles are the same as the 'openHere' function
6296
-        $this->numObj++;
6297
-        $this->o_destination(
6298
-            $this->numObj,
6299
-            'new',
6300
-            ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6301
-        );
6302
-        $id = $this->numObj;
6303
-
6304
-        // store the label->idf relationship, note that this means that labels can be used only once
6305
-        $this->destinations["$label"] = $id;
6306
-    }
6307
-
6308
-    /**
6309
-     * define font families, this is used to initialize the font families for the default fonts
6310
-     * and for the user to add new ones for their fonts. The default bahavious can be overridden should
6311
-     * that be desired.
6312
-     *
6313
-     * @param $family
6314
-     * @param string $options
6315
-     */
6316
-    function setFontFamily($family, $options = '')
6317
-    {
6318
-        if (!is_array($options)) {
6319
-            if ($family === 'init') {
6320
-                // set the known family groups
6321
-                // these font families will be used to enable bold and italic markers to be included
6322
-                // within text streams. html forms will be used... <b></b> <i></i>
6323
-                $this->fontFamilies['Helvetica.afm'] =
6324
-                    [
6325
-                        'b'  => 'Helvetica-Bold.afm',
6326
-                        'i'  => 'Helvetica-Oblique.afm',
6327
-                        'bi' => 'Helvetica-BoldOblique.afm',
6328
-                        'ib' => 'Helvetica-BoldOblique.afm'
6329
-                    ];
6330
-
6331
-                $this->fontFamilies['Courier.afm'] =
6332
-                    [
6333
-                        'b'  => 'Courier-Bold.afm',
6334
-                        'i'  => 'Courier-Oblique.afm',
6335
-                        'bi' => 'Courier-BoldOblique.afm',
6336
-                        'ib' => 'Courier-BoldOblique.afm'
6337
-                    ];
6338
-
6339
-                $this->fontFamilies['Times-Roman.afm'] =
6340
-                    [
6341
-                        'b'  => 'Times-Bold.afm',
6342
-                        'i'  => 'Times-Italic.afm',
6343
-                        'bi' => 'Times-BoldItalic.afm',
6344
-                        'ib' => 'Times-BoldItalic.afm'
6345
-                    ];
6346
-            }
6347
-        } else {
6348
-
6349
-            // the user is trying to set a font family
6350
-            // note that this can also be used to set the base ones to something else
6351
-            if (mb_strlen($family)) {
6352
-                $this->fontFamilies[$family] = $options;
6353
-            }
6354
-        }
6355
-    }
6356
-
6357
-    /**
6358
-     * used to add messages for use in debugging
6359
-     *
6360
-     * @param $message
6361
-     */
6362
-    function addMessage($message)
6363
-    {
6364
-        $this->messages .= $message . "\n";
6365
-    }
6366
-
6367
-    /**
6368
-     * a few functions which should allow the document to be treated transactionally.
6369
-     *
6370
-     * @param $action
6371
-     */
6372
-    function transaction($action)
6373
-    {
6374
-        switch ($action) {
6375
-            case 'start':
6376
-                // store all the data away into the checkpoint variable
6377
-                $data = get_object_vars($this);
6378
-                $this->checkpoint = $data;
6379
-                unset($data);
6380
-                break;
6381
-
6382
-            case 'commit':
6383
-                if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
6384
-                    $tmp = $this->checkpoint['checkpoint'];
6385
-                    $this->checkpoint = $tmp;
6386
-                    unset($tmp);
6387
-                } else {
6388
-                    $this->checkpoint = '';
6389
-                }
6390
-                break;
6391
-
6392
-            case 'rewind':
6393
-                // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
6394
-                if (is_array($this->checkpoint)) {
6395
-                    // can only abort if were inside a checkpoint
6396
-                    $tmp = $this->checkpoint;
6397
-
6398
-                    foreach ($tmp as $k => $v) {
6399
-                        if ($k !== 'checkpoint') {
6400
-                            $this->$k = $v;
6401
-                        }
6402
-                    }
6403
-                    unset($tmp);
6404
-                }
6405
-                break;
6406
-
6407
-            case 'abort':
6408
-                if (is_array($this->checkpoint)) {
6409
-                    // can only abort if were inside a checkpoint
6410
-                    $tmp = $this->checkpoint;
6411
-                    foreach ($tmp as $k => $v) {
6412
-                        $this->$k = $v;
6413
-                    }
6414
-                    unset($tmp);
6415
-                }
6416
-                break;
6417
-        }
6418
-    }
5978
+									$transparency['r'] = $this->getBytes($data, $p + 8, 2);
5979
+									// r from truecolor
5980
+									$transparency['g'] = $this->getBytes($data, $p + 10, 2);
5981
+									// g from truecolor
5982
+									$transparency['b'] = $this->getBytes($data, $p + 12, 2);
5983
+									// b from truecolor
5984
+
5985
+									$transparency['type'] = 'color-key';
5986
+									break;
5987
+
5988
+								//unsupported transparency type
5989
+								default:
5990
+									if (defined("DEBUGPNG") && DEBUGPNG) {
5991
+										print '[addPngFromFile unsupported transparency type ' . $file . ']';
5992
+									}
5993
+									break;
5994
+							}
5995
+
5996
+							// KS End new code
5997
+							break;
5998
+
5999
+						default:
6000
+							break;
6001
+					}
6002
+
6003
+					$p += $chunkLen + 12;
6004
+				}
6005
+
6006
+				if (!$haveHeader) {
6007
+					$error = 1;
6008
+
6009
+					//debugpng
6010
+					if (defined("DEBUGPNG") && DEBUGPNG) {
6011
+						print '[addPngFromFile information header is missing ' . $file . ']';
6012
+					}
6013
+
6014
+					$errormsg = 'information header is missing';
6015
+				}
6016
+
6017
+				if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
6018
+					$error = 1;
6019
+
6020
+					//debugpng
6021
+					if (defined("DEBUGPNG") && DEBUGPNG) {
6022
+						print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6023
+					}
6024
+
6025
+					$errormsg = 'There appears to be no support for interlaced images in pdf.';
6026
+				}
6027
+			}
6028
+
6029
+			if (!$error && $info['bitDepth'] > 8) {
6030
+				$error = 1;
6031
+
6032
+				//debugpng
6033
+				if (defined("DEBUGPNG") && DEBUGPNG) {
6034
+					print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6035
+				}
6036
+
6037
+				$errormsg = 'only bit depth of 8 or less is supported';
6038
+			}
6039
+
6040
+			if (!$error) {
6041
+				switch ($info['colorType']) {
6042
+					case 3:
6043
+						$color = 'DeviceRGB';
6044
+						$ncolor = 1;
6045
+						break;
6046
+
6047
+					case 2:
6048
+						$color = 'DeviceRGB';
6049
+						$ncolor = 3;
6050
+						break;
6051
+
6052
+					case 0:
6053
+						$color = 'DeviceGray';
6054
+						$ncolor = 1;
6055
+						break;
6056
+
6057
+					default:
6058
+						$error = 1;
6059
+
6060
+						//debugpng
6061
+						if (defined("DEBUGPNG") && DEBUGPNG) {
6062
+							print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6063
+						}
6064
+
6065
+						$errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
6066
+				}
6067
+			}
6068
+
6069
+			if ($error) {
6070
+				$this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6071
+
6072
+				return;
6073
+			}
6074
+
6075
+			//print_r($info);
6076
+			// so this image is ok... add it in.
6077
+			$this->numImages++;
6078
+			$im = $this->numImages;
6079
+			$label = "I$im";
6080
+			$this->numObj++;
6081
+
6082
+			//  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
6083
+			$options = [
6084
+				'label'            => $label,
6085
+				'data'             => $idata,
6086
+				'bitsPerComponent' => $info['bitDepth'],
6087
+				'pdata'            => $pdata,
6088
+				'iw'               => $info['width'],
6089
+				'ih'               => $info['height'],
6090
+				'type'             => 'png',
6091
+				'color'            => $color,
6092
+				'ncolor'           => $ncolor,
6093
+				'masked'           => $mask,
6094
+				'isMask'           => $is_mask
6095
+			];
6096
+
6097
+			if (isset($transparency)) {
6098
+				$options['transparency'] = $transparency;
6099
+			}
6100
+
6101
+			$this->o_image($this->numObj, 'new', $options);
6102
+			$this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
6103
+		}
6104
+
6105
+		if ($is_mask) {
6106
+			return;
6107
+		}
6108
+
6109
+		if ($w <= 0 && $h <= 0) {
6110
+			$w = $info['width'];
6111
+			$h = $info['height'];
6112
+		}
6113
+
6114
+		if ($w <= 0) {
6115
+			$w = $h / $info['height'] * $info['width'];
6116
+		}
6117
+
6118
+		if ($h <= 0) {
6119
+			$h = $w * $info['height'] / $info['width'];
6120
+		}
6121
+
6122
+		$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
6123
+	}
6124
+
6125
+	/**
6126
+	 * add a JPEG image into the document, from a file
6127
+	 *
6128
+	 * @param $img
6129
+	 * @param $x
6130
+	 * @param $y
6131
+	 * @param int $w
6132
+	 * @param int $h
6133
+	 */
6134
+	function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
6135
+	{
6136
+		// attempt to add a jpeg image straight from a file, using no GD commands
6137
+		// note that this function is unable to operate on a remote file.
6138
+
6139
+		if (!file_exists($img)) {
6140
+			return;
6141
+		}
6142
+
6143
+		if ($this->image_iscached($img)) {
6144
+			$data = null;
6145
+			$imageWidth = $this->imagelist[$img]['w'];
6146
+			$imageHeight = $this->imagelist[$img]['h'];
6147
+			$channels = $this->imagelist[$img]['c'];
6148
+		} else {
6149
+			$tmp = getimagesize($img);
6150
+			$imageWidth = $tmp[0];
6151
+			$imageHeight = $tmp[1];
6152
+
6153
+			if (isset($tmp['channels'])) {
6154
+				$channels = $tmp['channels'];
6155
+			} else {
6156
+				$channels = 3;
6157
+			}
6158
+
6159
+			$data = file_get_contents($img);
6160
+		}
6161
+
6162
+		if ($w <= 0 && $h <= 0) {
6163
+			$w = $imageWidth;
6164
+		}
6165
+
6166
+		if ($w == 0) {
6167
+			$w = $h / $imageHeight * $imageWidth;
6168
+		}
6169
+
6170
+		if ($h == 0) {
6171
+			$h = $w * $imageHeight / $imageWidth;
6172
+		}
6173
+
6174
+		$this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
6175
+	}
6176
+
6177
+	/**
6178
+	 * common code used by the two JPEG adding functions
6179
+	 * @param $data
6180
+	 * @param $imgname
6181
+	 * @param $imageWidth
6182
+	 * @param $imageHeight
6183
+	 * @param $x
6184
+	 * @param $y
6185
+	 * @param int $w
6186
+	 * @param int $h
6187
+	 * @param int $channels
6188
+	 */
6189
+	private function addJpegImage_common(
6190
+		&$data,
6191
+		$imgname,
6192
+		$imageWidth,
6193
+		$imageHeight,
6194
+		$x,
6195
+		$y,
6196
+		$w = 0,
6197
+		$h = 0,
6198
+		$channels = 3
6199
+	) {
6200
+		if ($this->image_iscached($imgname)) {
6201
+			$label = $this->imagelist[$imgname]['label'];
6202
+			//debugpng
6203
+			//if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
6204
+
6205
+		} else {
6206
+			if ($data == null) {
6207
+				$this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6208
+
6209
+				return;
6210
+			}
6211
+
6212
+			// note that this function is not to be called externally
6213
+			// it is just the common code between the GD and the file options
6214
+			$this->numImages++;
6215
+			$im = $this->numImages;
6216
+			$label = "I$im";
6217
+			$this->numObj++;
6218
+
6219
+			$this->o_image(
6220
+				$this->numObj,
6221
+				'new',
6222
+				[
6223
+					'label'    => $label,
6224
+					'data'     => &$data,
6225
+					'iw'       => $imageWidth,
6226
+					'ih'       => $imageHeight,
6227
+					'channels' => $channels
6228
+				]
6229
+			);
6230
+
6231
+			$this->imagelist[$imgname] = [
6232
+				'label' => $label,
6233
+				'w'     => $imageWidth,
6234
+				'h'     => $imageHeight,
6235
+				'c'     => $channels
6236
+			];
6237
+		}
6238
+
6239
+		$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
6240
+	}
6241
+
6242
+	/**
6243
+	 * specify where the document should open when it first starts
6244
+	 *
6245
+	 * @param $style
6246
+	 * @param int $a
6247
+	 * @param int $b
6248
+	 * @param int $c
6249
+	 */
6250
+	function openHere($style, $a = 0, $b = 0, $c = 0)
6251
+	{
6252
+		// this function will open the document at a specified page, in a specified style
6253
+		// the values for style, and the required parameters are:
6254
+		// 'XYZ'  left, top, zoom
6255
+		// 'Fit'
6256
+		// 'FitH' top
6257
+		// 'FitV' left
6258
+		// 'FitR' left,bottom,right
6259
+		// 'FitB'
6260
+		// 'FitBH' top
6261
+		// 'FitBV' left
6262
+		$this->numObj++;
6263
+		$this->o_destination(
6264
+			$this->numObj,
6265
+			'new',
6266
+			['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6267
+		);
6268
+		$id = $this->catalogId;
6269
+		$this->o_catalog($id, 'openHere', $this->numObj);
6270
+	}
6271
+
6272
+	/**
6273
+	 * Add JavaScript code to the PDF document
6274
+	 *
6275
+	 * @param string $code
6276
+	 */
6277
+	function addJavascript($code)
6278
+	{
6279
+		$this->javascript .= $code;
6280
+	}
6281
+
6282
+	/**
6283
+	 * create a labelled destination within the document
6284
+	 *
6285
+	 * @param $label
6286
+	 * @param $style
6287
+	 * @param int $a
6288
+	 * @param int $b
6289
+	 * @param int $c
6290
+	 */
6291
+	function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
6292
+	{
6293
+		// associates the given label with the destination, it is done this way so that a destination can be specified after
6294
+		// it has been linked to
6295
+		// styles are the same as the 'openHere' function
6296
+		$this->numObj++;
6297
+		$this->o_destination(
6298
+			$this->numObj,
6299
+			'new',
6300
+			['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6301
+		);
6302
+		$id = $this->numObj;
6303
+
6304
+		// store the label->idf relationship, note that this means that labels can be used only once
6305
+		$this->destinations["$label"] = $id;
6306
+	}
6307
+
6308
+	/**
6309
+	 * define font families, this is used to initialize the font families for the default fonts
6310
+	 * and for the user to add new ones for their fonts. The default bahavious can be overridden should
6311
+	 * that be desired.
6312
+	 *
6313
+	 * @param $family
6314
+	 * @param string $options
6315
+	 */
6316
+	function setFontFamily($family, $options = '')
6317
+	{
6318
+		if (!is_array($options)) {
6319
+			if ($family === 'init') {
6320
+				// set the known family groups
6321
+				// these font families will be used to enable bold and italic markers to be included
6322
+				// within text streams. html forms will be used... <b></b> <i></i>
6323
+				$this->fontFamilies['Helvetica.afm'] =
6324
+					[
6325
+						'b'  => 'Helvetica-Bold.afm',
6326
+						'i'  => 'Helvetica-Oblique.afm',
6327
+						'bi' => 'Helvetica-BoldOblique.afm',
6328
+						'ib' => 'Helvetica-BoldOblique.afm'
6329
+					];
6330
+
6331
+				$this->fontFamilies['Courier.afm'] =
6332
+					[
6333
+						'b'  => 'Courier-Bold.afm',
6334
+						'i'  => 'Courier-Oblique.afm',
6335
+						'bi' => 'Courier-BoldOblique.afm',
6336
+						'ib' => 'Courier-BoldOblique.afm'
6337
+					];
6338
+
6339
+				$this->fontFamilies['Times-Roman.afm'] =
6340
+					[
6341
+						'b'  => 'Times-Bold.afm',
6342
+						'i'  => 'Times-Italic.afm',
6343
+						'bi' => 'Times-BoldItalic.afm',
6344
+						'ib' => 'Times-BoldItalic.afm'
6345
+					];
6346
+			}
6347
+		} else {
6348
+
6349
+			// the user is trying to set a font family
6350
+			// note that this can also be used to set the base ones to something else
6351
+			if (mb_strlen($family)) {
6352
+				$this->fontFamilies[$family] = $options;
6353
+			}
6354
+		}
6355
+	}
6356
+
6357
+	/**
6358
+	 * used to add messages for use in debugging
6359
+	 *
6360
+	 * @param $message
6361
+	 */
6362
+	function addMessage($message)
6363
+	{
6364
+		$this->messages .= $message . "\n";
6365
+	}
6366
+
6367
+	/**
6368
+	 * a few functions which should allow the document to be treated transactionally.
6369
+	 *
6370
+	 * @param $action
6371
+	 */
6372
+	function transaction($action)
6373
+	{
6374
+		switch ($action) {
6375
+			case 'start':
6376
+				// store all the data away into the checkpoint variable
6377
+				$data = get_object_vars($this);
6378
+				$this->checkpoint = $data;
6379
+				unset($data);
6380
+				break;
6381
+
6382
+			case 'commit':
6383
+				if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
6384
+					$tmp = $this->checkpoint['checkpoint'];
6385
+					$this->checkpoint = $tmp;
6386
+					unset($tmp);
6387
+				} else {
6388
+					$this->checkpoint = '';
6389
+				}
6390
+				break;
6391
+
6392
+			case 'rewind':
6393
+				// do not destroy the current checkpoint, but move us back to the state then, so that we can try again
6394
+				if (is_array($this->checkpoint)) {
6395
+					// can only abort if were inside a checkpoint
6396
+					$tmp = $this->checkpoint;
6397
+
6398
+					foreach ($tmp as $k => $v) {
6399
+						if ($k !== 'checkpoint') {
6400
+							$this->$k = $v;
6401
+						}
6402
+					}
6403
+					unset($tmp);
6404
+				}
6405
+				break;
6406
+
6407
+			case 'abort':
6408
+				if (is_array($this->checkpoint)) {
6409
+					// can only abort if were inside a checkpoint
6410
+					$tmp = $this->checkpoint;
6411
+					foreach ($tmp as $k => $v) {
6412
+						$this->$k = $v;
6413
+					}
6414
+					unset($tmp);
6415
+				}
6416
+				break;
6417
+		}
6418
+	}
6419 6419
 }
Please login to merge, or discard this patch.
Spacing   +231 added lines, -231 removed lines patch added patch discarded remove patch
@@ -23,24 +23,24 @@  discard block
 block discarded – undo
23 23
     const PDF_VERSION = '1.7';
24 24
 
25 25
     const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
26
-    const ACROFORM_SIG_APPENDONLY =       0x0002;
26
+    const ACROFORM_SIG_APPENDONLY = 0x0002;
27 27
 
28
-    const ACROFORM_FIELD_BUTTON =   'Btn';
29
-    const ACROFORM_FIELD_TEXT =     'Tx';
30
-    const ACROFORM_FIELD_CHOICE =   'Ch';
31
-    const ACROFORM_FIELD_SIG =      'Sig';
28
+    const ACROFORM_FIELD_BUTTON = 'Btn';
29
+    const ACROFORM_FIELD_TEXT = 'Tx';
30
+    const ACROFORM_FIELD_CHOICE = 'Ch';
31
+    const ACROFORM_FIELD_SIG = 'Sig';
32 32
 
33
-    const ACROFORM_FIELD_READONLY =               0x0001;
34
-    const ACROFORM_FIELD_REQUIRED =               0x0002;
33
+    const ACROFORM_FIELD_READONLY = 0x0001;
34
+    const ACROFORM_FIELD_REQUIRED = 0x0002;
35 35
 
36
-    const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
37
-    const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
38
-    const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
36
+    const ACROFORM_FIELD_TEXT_MULTILINE = 0x1000;
37
+    const ACROFORM_FIELD_TEXT_PASSWORD = 0x2000;
38
+    const ACROFORM_FIELD_TEXT_RICHTEXT = 0x10000;
39 39
 
40
-    const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
41
-    const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
42
-    const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
43
-    const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
40
+    const ACROFORM_FIELD_CHOICE_COMBO = 0x20000;
41
+    const ACROFORM_FIELD_CHOICE_EDIT = 0x40000;
42
+    const ACROFORM_FIELD_CHOICE_SORT = 0x80000;
43
+    const ACROFORM_FIELD_CHOICE_MULTISELECT = 0x200000;
44 44
 
45 45
     const XOBJECT_SUBTYPE_FORM = 'Form';
46 46
 
@@ -459,16 +459,16 @@  discard block
 block discarded – undo
459 459
                     case 'XYZ':
460 460
                     /** @noinspection PhpMissingBreakStatementInspection */
461 461
                     case 'FitR':
462
-                        $tmp = ' ' . $options['p3'] . $tmp;
462
+                        $tmp = ' '.$options['p3'].$tmp;
463 463
                     case 'FitH':
464 464
                     case 'FitV':
465 465
                     case 'FitBH':
466 466
                     /** @noinspection PhpMissingBreakStatementInspection */
467 467
                     case 'FitBV':
468
-                        $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
468
+                        $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
469 469
                     case 'Fit':
470 470
                     case 'FitB':
471
-                        $tmp = $options['type'] . $tmp;
471
+                        $tmp = $options['type'].$tmp;
472 472
                         $this->objects[$id]['info']['string'] = $tmp;
473 473
                         $this->objects[$id]['info']['page'] = $options['page'];
474 474
                 }
@@ -478,7 +478,7 @@  discard block
 block discarded – undo
478 478
                 $o = &$this->objects[$id];
479 479
 
480 480
                 $tmp = $o['info'];
481
-                $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
481
+                $res = "\n$id 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
482 482
 
483 483
                 return $res;
484 484
         }
@@ -514,12 +514,12 @@  discard block
 block discarded – undo
514 514
                         case 'CenterWindow':
515 515
                         case 'DisplayDocTitle':
516 516
                         case 'PickTrayByPDFSize':
517
-                            $o['info'][$k] = (bool)$v;
517
+                            $o['info'][$k] = (bool) $v;
518 518
                             break;
519 519
 
520 520
                         // Integer keys
521 521
                         case 'NumCopies':
522
-                            $o['info'][$k] = (int)$v;
522
+                            $o['info'][$k] = (int) $v;
523 523
                             break;
524 524
 
525 525
                         // Name keys
@@ -527,33 +527,33 @@  discard block
 block discarded – undo
527 527
                         case 'ViewClip':
528 528
                         case 'PrintClip':
529 529
                         case 'PrintArea':
530
-                            $o['info'][$k] = (string)$v;
530
+                            $o['info'][$k] = (string) $v;
531 531
                             break;
532 532
 
533 533
                         // Named with limited valid values
534 534
                         case 'NonFullScreenPageMode':
535
-                            if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
535
+                            if ( ! in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
536 536
                                 break;
537 537
                             }
538 538
                             $o['info'][$k] = $v;
539 539
                             break;
540 540
 
541 541
                         case 'Direction':
542
-                            if (!in_array($v, ['L2R', 'R2L'])) {
542
+                            if ( ! in_array($v, ['L2R', 'R2L'])) {
543 543
                                 break;
544 544
                             }
545 545
                             $o['info'][$k] = $v;
546 546
                             break;
547 547
 
548 548
                         case 'PrintScaling':
549
-                            if (!in_array($v, ['None', 'AppDefault'])) {
549
+                            if ( ! in_array($v, ['None', 'AppDefault'])) {
550 550
                                 break;
551 551
                             }
552 552
                             $o['info'][$k] = $v;
553 553
                             break;
554 554
 
555 555
                         case 'Duplex':
556
-                            if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
556
+                            if ( ! in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
557 557
                                 break;
558 558
                             }
559 559
                             $o['info'][$k] = $v;
@@ -563,7 +563,7 @@  discard block
 block discarded – undo
563 563
                         case 'PrintPageRange':
564 564
                             // Cast to integer array
565 565
                             foreach ($v as $vK => $vV) {
566
-                                $v[$vK] = (int)$vV;
566
+                                $v[$vK] = (int) $vV;
567 567
                             }
568 568
                             $o['info'][$k] = array_values($v);
569 569
                             break;
@@ -577,13 +577,13 @@  discard block
 block discarded – undo
577 577
 
578 578
                 foreach ($o['info'] as $k => $v) {
579 579
                     if (is_string($v)) {
580
-                        $v = '/' . $v;
580
+                        $v = '/'.$v;
581 581
                     } elseif (is_int($v)) {
582 582
                         $v = (string) $v;
583 583
                     } elseif (is_bool($v)) {
584 584
                         $v = ($v ? 'true' : 'false');
585 585
                     } elseif (is_array($v)) {
586
-                        $v = '[' . implode(' ', $v) . ']';
586
+                        $v = '['.implode(' ', $v).']';
587 587
                     }
588 588
                     $res .= "\n/$k $v";
589 589
                 }
@@ -624,7 +624,7 @@  discard block
 block discarded – undo
624 624
                 break;
625 625
 
626 626
             case 'viewerPreferences':
627
-                if (!isset($o['info']['viewerPreferences'])) {
627
+                if ( ! isset($o['info']['viewerPreferences'])) {
628 628
                     $this->numObj++;
629 629
                     $this->o_viewerPreferences($this->numObj, 'new');
630 630
                     $o['info']['viewerPreferences'] = $this->numObj;
@@ -695,7 +695,7 @@  discard block
 block discarded – undo
695 695
                 break;
696 696
 
697 697
             case 'page':
698
-                if (!is_array($options)) {
698
+                if ( ! is_array($options)) {
699 699
                     // then it will just be the id of the new page
700 700
                     $o['info']['pages'][] = $options;
701 701
                 } else {
@@ -762,7 +762,7 @@  discard block
 block discarded – undo
762 762
                         $res .= "$v 0 R\n";
763 763
                     }
764 764
 
765
-                    $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
765
+                    $res .= "]\n/Count ".count($this->objects[$id]['info']['pages']);
766 766
 
767 767
                     if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
768 768
                         isset($o['info']['procset']) ||
@@ -771,13 +771,13 @@  discard block
 block discarded – undo
771 771
                         $res .= "\n/Resources <<";
772 772
 
773 773
                         if (isset($o['info']['procset'])) {
774
-                            $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
774
+                            $res .= "\n/ProcSet ".$o['info']['procset']." 0 R";
775 775
                         }
776 776
 
777 777
                         if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
778 778
                             $res .= "\n/Font << ";
779 779
                             foreach ($o['info']['fonts'] as $finfo) {
780
-                                $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
780
+                                $res .= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
781 781
                             }
782 782
                             $res .= "\n>>";
783 783
                         }
@@ -785,7 +785,7 @@  discard block
 block discarded – undo
785 785
                         if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
786 786
                             $res .= "\n/XObject << ";
787 787
                             foreach ($o['info']['xObjects'] as $finfo) {
788
-                                $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
788
+                                $res .= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
789 789
                             }
790 790
                             $res .= "\n>>";
791 791
                         }
@@ -793,7 +793,7 @@  discard block
 block discarded – undo
793 793
                         if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
794 794
                             $res .= "\n/ExtGState << ";
795 795
                             foreach ($o['info']['extGStates'] as $gstate) {
796
-                                $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
796
+                                $res .= "\n/GS".$gstate['stateNum']." ".$gstate['objNum']." 0 R";
797 797
                             }
798 798
                             $res .= "\n>>";
799 799
                         }
@@ -801,13 +801,13 @@  discard block
 block discarded – undo
801 801
                         $res .= "\n>>";
802 802
                         if (isset($o['info']['mediaBox'])) {
803 803
                             $tmp = $o['info']['mediaBox'];
804
-                            $res .= "\n/MediaBox [" . sprintf(
804
+                            $res .= "\n/MediaBox [".sprintf(
805 805
                                     '%.3F %.3F %.3F %.3F',
806 806
                                     $tmp[0],
807 807
                                     $tmp[1],
808 808
                                     $tmp[2],
809 809
                                     $tmp[3]
810
-                                ) . ']';
810
+                                ).']';
811 811
                         }
812 812
                     }
813 813
 
@@ -853,7 +853,7 @@  discard block
 block discarded – undo
853 853
                         $res .= "$v 0 R ";
854 854
                     }
855 855
 
856
-                    $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
856
+                    $res .= "] /Count ".count($o['info']['outlines'])." >>\nendobj";
857 857
                 } else {
858 858
                     $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
859 859
                 }
@@ -957,7 +957,7 @@  discard block
 block discarded – undo
957 957
                             case 'Widths':
958 958
                             case 'FontDescriptor':
959 959
                             case 'SubType':
960
-                                $this->addMessage('o_font ' . $k . " : " . $v);
960
+                                $this->addMessage('o_font '.$k." : ".$v);
961 961
                                 $o['info'][$k] = $v;
962 962
                                 break;
963 963
                         }
@@ -981,44 +981,44 @@  discard block
 block discarded – undo
981 981
                     // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
982 982
 
983 983
                     $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
984
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
984
+                    $res .= "/BaseFont /".$o['info']['name']."\n";
985 985
 
986 986
                     // The horizontal identity mapping for 2-byte CIDs; may be used
987 987
                     // with CIDFonts using any Registry, Ordering, and Supplement values.
988 988
                     $res .= "/Encoding /Identity-H\n";
989
-                    $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
990
-                    $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
989
+                    $res .= "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n";
990
+                    $res .= "/ToUnicode ".$o['info']['toUnicode']." 0 R\n";
991 991
                     $res .= ">>\n";
992 992
                     $res .= "endobj";
993 993
                 } else {
994
-                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
995
-                    $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
996
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
994
+                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
995
+                    $res .= "/Name /F".$o['info']['fontNum']."\n";
996
+                    $res .= "/BaseFont /".$o['info']['name']."\n";
997 997
 
998 998
                     if (isset($o['info']['encodingDictionary'])) {
999 999
                         // then place a reference to the dictionary
1000
-                        $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
1000
+                        $res .= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
1001 1001
                     } else {
1002 1002
                         if (isset($o['info']['encoding'])) {
1003 1003
                             // use the specified encoding
1004
-                            $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1004
+                            $res .= "/Encoding /".$o['info']['encoding']."\n";
1005 1005
                         }
1006 1006
                     }
1007 1007
 
1008 1008
                     if (isset($o['info']['FirstChar'])) {
1009
-                        $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1009
+                        $res .= "/FirstChar ".$o['info']['FirstChar']."\n";
1010 1010
                     }
1011 1011
 
1012 1012
                     if (isset($o['info']['LastChar'])) {
1013
-                        $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1013
+                        $res .= "/LastChar ".$o['info']['LastChar']."\n";
1014 1014
                     }
1015 1015
 
1016 1016
                     if (isset($o['info']['Widths'])) {
1017
-                        $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1017
+                        $res .= "/Widths ".$o['info']['Widths']." 0 R\n";
1018 1018
                     }
1019 1019
 
1020 1020
                     if (isset($o['info']['FontDescriptor'])) {
1021
-                        $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1021
+                        $res .= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1022 1022
                     }
1023 1023
 
1024 1024
                     $res .= ">>\n";
@@ -1044,7 +1044,7 @@  discard block
 block discarded – undo
1044 1044
             }
1045 1045
         }
1046 1046
 
1047
-        return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1047
+        return 'SUB'.str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1048 1048
     }
1049 1049
 
1050 1050
     /**
@@ -1056,7 +1056,7 @@  discard block
 block discarded – undo
1056 1056
     private function processFont(int $fontObjId, array $object_info)
1057 1057
     {
1058 1058
         $fontFileName = $object_info['fontFileName'];
1059
-        if (!isset($this->fonts[$fontFileName])) {
1059
+        if ( ! isset($this->fonts[$fontFileName])) {
1060 1060
             return false;
1061 1061
         }
1062 1062
 
@@ -1068,9 +1068,9 @@  discard block
 block discarded – undo
1068 1068
         $isTtfFont = $fileSuffixLower === 'ttf';
1069 1069
         $isPfbFont = $fileSuffixLower === 'pfb';
1070 1070
 
1071
-        $this->addMessage('selectFont: checking for - ' . $fbfile);
1071
+        $this->addMessage('selectFont: checking for - '.$fbfile);
1072 1072
 
1073
-        if (!$fileSuffix) {
1073
+        if ( ! $fileSuffix) {
1074 1074
             $this->addMessage(
1075 1075
                 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1076 1076
             );
@@ -1089,7 +1089,7 @@  discard block
 block discarded – undo
1089 1089
 
1090 1090
             foreach ($font['C'] as $num => $d) {
1091 1091
                 if (intval($num) > 0 || $num == '0') {
1092
-                    if (!$font['isUnicode']) {
1092
+                    if ( ! $font['isUnicode']) {
1093 1093
                         // With Unicode, widths array isn't used
1094 1094
                         if ($lastChar > 0 && $num > $lastChar + 1) {
1095 1095
                             for ($i = $lastChar + 1; $i < $num; $i++) {
@@ -1116,7 +1116,7 @@  discard block
 block discarded – undo
1116 1116
             if (isset($object['differences'])) {
1117 1117
                 foreach ($object['differences'] as $charNum => $charName) {
1118 1118
                     if ($charNum > $lastChar) {
1119
-                        if (!$object['isUnicode']) {
1119
+                        if ( ! $object['isUnicode']) {
1120 1120
                             // With Unicode, widths array isn't used
1121 1121
                             for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1122 1122
                                 $widths[] = 0;
@@ -1139,17 +1139,17 @@  discard block
 block discarded – undo
1139 1139
                 $font['CIDWidths'] = $cid_widths;
1140 1140
             }
1141 1141
 
1142
-            $this->addMessage('selectFont: FirstChar = ' . $firstChar);
1143
-            $this->addMessage('selectFont: LastChar = ' . $lastChar);
1142
+            $this->addMessage('selectFont: FirstChar = '.$firstChar);
1143
+            $this->addMessage('selectFont: LastChar = '.$lastChar);
1144 1144
 
1145 1145
             $widthid = -1;
1146 1146
 
1147
-            if (!$font['isUnicode']) {
1147
+            if ( ! $font['isUnicode']) {
1148 1148
                 // With Unicode, widths array isn't used
1149 1149
 
1150 1150
                 $this->numObj++;
1151 1151
                 $this->o_contents($this->numObj, 'new', 'raw');
1152
-                $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1152
+                $this->objects[$this->numObj]['c'] .= '['.implode(' ', $widths).']';
1153 1153
                 $widthid = $this->numObj;
1154 1154
             }
1155 1155
 
@@ -1313,8 +1313,8 @@  discard block
 block discarded – undo
1313 1313
 EOT;
1314 1314
 
1315 1315
                 $res = "\n$id 0 obj\n";
1316
-                $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1317
-                $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1316
+                $res .= "<</Length ".mb_strlen($stream, '8bit')." >>\n";
1317
+                $res .= "stream\n".$stream."\nendstream"."\nendobj";
1318 1318
 
1319 1319
                 return $res;
1320 1320
         }
@@ -1409,12 +1409,12 @@  discard block
 block discarded – undo
1409 1409
 
1410 1410
             case 'out':
1411 1411
                 $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1412
-                if (!isset($o['info']['encoding'])) {
1412
+                if ( ! isset($o['info']['encoding'])) {
1413 1413
                     $o['info']['encoding'] = 'WinAnsiEncoding';
1414 1414
                 }
1415 1415
 
1416 1416
                 if ($o['info']['encoding'] !== 'none') {
1417
-                    $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1417
+                    $res .= "/BaseEncoding /".$o['info']['encoding']."\n";
1418 1418
                 }
1419 1419
 
1420 1420
                 $res .= "/Differences \n[";
@@ -1495,8 +1495,8 @@  discard block
 block discarded – undo
1495 1495
                 $res = "\n$id 0 obj\n";
1496 1496
                 $res .= "<</Type /Font\n";
1497 1497
                 $res .= "/Subtype /CIDFontType2\n";
1498
-                $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1499
-                $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1498
+                $res .= "/BaseFont /".$o['info']['name']."\n";
1499
+                $res .= "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n";
1500 1500
                 //      if (isset($o['info']['FirstChar'])) {
1501 1501
                 //        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1502 1502
                 //      }
@@ -1505,11 +1505,11 @@  discard block
 block discarded – undo
1505 1505
                 //        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1506 1506
                 //      }
1507 1507
                 if (isset($o['info']['FontDescriptor'])) {
1508
-                    $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1508
+                    $res .= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1509 1509
                 }
1510 1510
 
1511 1511
                 if (isset($o['info']['MissingWidth'])) {
1512
-                    $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1512
+                    $res .= "/DW ".$o['info']['MissingWidth']."\n";
1513 1513
                 }
1514 1514
 
1515 1515
                 if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
@@ -1521,7 +1521,7 @@  discard block
 block discarded – undo
1521 1521
                     $res .= "/W [$w]\n";
1522 1522
                 }
1523 1523
 
1524
-                $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1524
+                $res .= "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n";
1525 1525
                 $res .= ">>\n";
1526 1526
                 $res .= "endobj";
1527 1527
 
@@ -1561,8 +1561,8 @@  discard block
 block discarded – undo
1561 1561
 
1562 1562
                 $res = "\n$id 0 obj\n";
1563 1563
 
1564
-                $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1565
-                $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1564
+                $res .= '<</Registry ('.$registry.")\n"; // A string identifying an issuer of character collections
1565
+                $res .= '/Ordering ('.$ordering.")\n"; // A string that uniquely names a character collection issued by a specific registry
1566 1566
                 $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1567 1567
                 $res .= ">>";
1568 1568
 
@@ -1601,12 +1601,12 @@  discard block
 block discarded – undo
1601 1601
                 $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1602 1602
                     $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1603 1603
 
1604
-                if (!$compressed && isset($o['raw'])) {
1604
+                if ( ! $compressed && isset($o['raw'])) {
1605 1605
                     $res .= $tmp;
1606 1606
                 } else {
1607 1607
                     $res .= "<<";
1608 1608
 
1609
-                    if (!$compressed && $this->compressionReady && $this->options['compression']) {
1609
+                    if ( ! $compressed && $this->compressionReady && $this->options['compression']) {
1610 1610
                         // then implement ZLIB based compression on this content stream
1611 1611
                         $compressed = true;
1612 1612
                         $tmp = gzcompress($tmp, 6);
@@ -1620,7 +1620,7 @@  discard block
 block discarded – undo
1620 1620
                         $tmp = $this->ARC4($tmp);
1621 1621
                     }
1622 1622
 
1623
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1623
+                    $res .= "\n/Length ".mb_strlen($tmp, '8bit').">>\nstream\n$tmp\nendstream";
1624 1624
                 }
1625 1625
 
1626 1626
                 $res .= "\nendobj";
@@ -1690,7 +1690,7 @@  discard block
 block discarded – undo
1690 1690
         switch ($action) {
1691 1691
             case 'new':
1692 1692
                 $this->infoObject = $id;
1693
-                $date = 'D:' . @date('Ymd');
1693
+                $date = 'D:'.@date('Ymd');
1694 1694
                 $this->objects[$id] = [
1695 1695
                     't'    => 'info',
1696 1696
                     'info' => [
@@ -1775,12 +1775,12 @@  discard block
 block discarded – undo
1775 1775
                 $res = "\n$id 0 obj\n<< /Type /Action";
1776 1776
                 switch ($o['type']) {
1777 1777
                     case 'ilink':
1778
-                        if (!isset($this->destinations[(string)$o['info']['label']])) {
1778
+                        if ( ! isset($this->destinations[(string) $o['info']['label']])) {
1779 1779
                             break;
1780 1780
                         }
1781 1781
 
1782 1782
                         // there will be an 'label' setting, this is the name of the destination
1783
-                        $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1783
+                        $res .= "\n/S /GoTo\n/D ".$this->destinations[(string) $o['info']['label']]." 0 R";
1784 1784
                         break;
1785 1785
 
1786 1786
                     case 'URI':
@@ -1852,7 +1852,7 @@  discard block
 block discarded – undo
1852 1852
                         $res .= "\n/Subtype /Link";
1853 1853
                         break;
1854 1854
                 }
1855
-                $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1855
+                $res .= "\n/A ".$o['info']['actionId']." 0 R";
1856 1856
                 $res .= "\n/Border [0 0 0]";
1857 1857
                 $res .= "\n/H /I";
1858 1858
                 $res .= "\n/Rect [ ";
@@ -1926,7 +1926,7 @@  discard block
 block discarded – undo
1926 1926
 
1927 1927
             case 'annot':
1928 1928
                 // add an annotation to this page
1929
-                if (!isset($o['info']['annot'])) {
1929
+                if ( ! isset($o['info']['annot'])) {
1930 1930
                     $o['info']['annot'] = [];
1931 1931
                 }
1932 1932
 
@@ -1938,15 +1938,15 @@  discard block
 block discarded – undo
1938 1938
                 $res = "\n$id 0 obj\n<< /Type /Page";
1939 1939
                 if (isset($o['info']['mediaBox'])) {
1940 1940
                     $tmp = $o['info']['mediaBox'];
1941
-                    $res .= "\n/MediaBox [" . sprintf(
1941
+                    $res .= "\n/MediaBox [".sprintf(
1942 1942
                             '%.3F %.3F %.3F %.3F',
1943 1943
                             $tmp[0],
1944 1944
                             $tmp[1],
1945 1945
                             $tmp[2],
1946 1946
                             $tmp[3]
1947
-                        ) . ']';
1947
+                        ).']';
1948 1948
                 }
1949
-                $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1949
+                $res .= "\n/Parent ".$o['info']['parent']." 0 R";
1950 1950
 
1951 1951
                 if (isset($o['info']['annot'])) {
1952 1952
                     $res .= "\n/Annots [";
@@ -1958,7 +1958,7 @@  discard block
 block discarded – undo
1958 1958
 
1959 1959
                 $count = count($o['info']['contents']);
1960 1960
                 if ($count == 1) {
1961
-                    $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1961
+                    $res .= "\n/Contents ".$o['info']['contents'][0]." 0 R";
1962 1962
                 } else {
1963 1963
                     if ($count > 1) {
1964 1964
                         $res .= "\n/Contents [\n";
@@ -2038,7 +2038,7 @@  discard block
 block discarded – undo
2038 2038
                         $res .= "\n/$k $v";
2039 2039
                     }
2040 2040
 
2041
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2041
+                    $res .= "\n/Length ".mb_strlen($tmp, '8bit')." >>\nstream\n$tmp\nendstream";
2042 2042
                 }
2043 2043
 
2044 2044
                 $res .= "\nendobj";
@@ -2061,7 +2061,7 @@  discard block
 block discarded – undo
2061 2061
                 $this->objects[$id] = [
2062 2062
                     't'    => 'embedjs',
2063 2063
                     'info' => [
2064
-                        'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2064
+                        'Names' => '[(EmbeddedJS) '.($id + 1).' 0 R]'
2065 2065
                     ]
2066 2066
                 ];
2067 2067
                 break;
@@ -2094,7 +2094,7 @@  discard block
 block discarded – undo
2094 2094
                     't'    => 'javascript',
2095 2095
                     'info' => [
2096 2096
                         'S'  => '/JavaScript',
2097
-                        'JS' => '(' . $this->filterText($code, true, false) . ')',
2097
+                        'JS' => '('.$this->filterText($code, true, false).')',
2098 2098
                     ]
2099 2099
                 ];
2100 2100
                 break;
@@ -2129,7 +2129,7 @@  discard block
 block discarded – undo
2129 2129
                 // make the new object
2130 2130
                 $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2131 2131
 
2132
-                $info =& $this->objects[$id]['info'];
2132
+                $info = & $this->objects[$id]['info'];
2133 2133
 
2134 2134
                 $info['Type'] = '/XObject';
2135 2135
                 $info['Subtype'] = '/Image';
@@ -2137,11 +2137,11 @@  discard block
 block discarded – undo
2137 2137
                 $info['Height'] = $options['ih'];
2138 2138
 
2139 2139
                 if (isset($options['masked']) && $options['masked']) {
2140
-                    $info['SMask'] = ($this->numObj - 1) . ' 0 R';
2140
+                    $info['SMask'] = ($this->numObj - 1).' 0 R';
2141 2141
                 }
2142 2142
 
2143
-                if (!isset($options['type']) || $options['type'] === 'jpg') {
2144
-                    if (!isset($options['channels'])) {
2143
+                if ( ! isset($options['type']) || $options['type'] === 'jpg') {
2144
+                    if ( ! isset($options['channels'])) {
2145 2145
                         $options['channels'] = 3;
2146 2146
                     }
2147 2147
 
@@ -2166,17 +2166,17 @@  discard block
 block discarded – undo
2166 2166
                 } else {
2167 2167
                     if ($options['type'] === 'png') {
2168 2168
                         $info['Filter'] = '/FlateDecode';
2169
-                        $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2169
+                        $info['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
2170 2170
 
2171 2171
                         if ($options['isMask']) {
2172 2172
                             $info['ColorSpace'] = '/DeviceGray';
2173 2173
                         } else {
2174 2174
                             if (mb_strlen($options['pdata'], '8bit')) {
2175
-                                $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2175
+                                $tmp = ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata'], '8bit') / 3 - 1).' ';
2176 2176
                                 $this->numObj++;
2177 2177
                                 $this->o_contents($this->numObj, 'new');
2178 2178
                                 $this->objects[$this->numObj]['c'] = $options['pdata'];
2179
-                                $tmp .= $this->numObj . ' 0 R';
2179
+                                $tmp .= $this->numObj.' 0 R';
2180 2180
                                 $tmp .= ' ]';
2181 2181
                                 $info['ColorSpace'] = $tmp;
2182 2182
 
@@ -2184,15 +2184,15 @@  discard block
 block discarded – undo
2184 2184
                                     $transparency = $options['transparency'];
2185 2185
                                     switch ($transparency['type']) {
2186 2186
                                         case 'indexed':
2187
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2187
+                                            $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] ';
2188 2188
                                             $info['Mask'] = $tmp;
2189 2189
                                             break;
2190 2190
 
2191 2191
                                         case 'color-key':
2192
-                                            $tmp = ' [ ' .
2193
-                                                $transparency['r'] . ' ' . $transparency['r'] .
2194
-                                                $transparency['g'] . ' ' . $transparency['g'] .
2195
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2192
+                                            $tmp = ' [ '.
2193
+                                                $transparency['r'].' '.$transparency['r'].
2194
+                                                $transparency['g'].' '.$transparency['g'].
2195
+                                                $transparency['b'].' '.$transparency['b'].
2196 2196
                                                 ' ] ';
2197 2197
                                             $info['Mask'] = $tmp;
2198 2198
                                             break;
@@ -2204,21 +2204,21 @@  discard block
 block discarded – undo
2204 2204
 
2205 2205
                                     switch ($transparency['type']) {
2206 2206
                                         case 'indexed':
2207
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2207
+                                            $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] ';
2208 2208
                                             $info['Mask'] = $tmp;
2209 2209
                                             break;
2210 2210
 
2211 2211
                                         case 'color-key':
2212
-                                            $tmp = ' [ ' .
2213
-                                                $transparency['r'] . ' ' . $transparency['r'] . ' ' .
2214
-                                                $transparency['g'] . ' ' . $transparency['g'] . ' ' .
2215
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2212
+                                            $tmp = ' [ '.
2213
+                                                $transparency['r'].' '.$transparency['r'].' '.
2214
+                                                $transparency['g'].' '.$transparency['g'].' '.
2215
+                                                $transparency['b'].' '.$transparency['b'].
2216 2216
                                                 ' ] ';
2217 2217
                                             $info['Mask'] = $tmp;
2218 2218
                                             break;
2219 2219
                                     }
2220 2220
                                 }
2221
-                                $info['ColorSpace'] = '/' . $options['color'];
2221
+                                $info['ColorSpace'] = '/'.$options['color'];
2222 2222
                             }
2223 2223
                         }
2224 2224
 
@@ -2248,7 +2248,7 @@  discard block
 block discarded – undo
2248 2248
                     $tmp = $this->ARC4($tmp);
2249 2249
                 }
2250 2250
 
2251
-                $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2251
+                $res .= "\n/Length ".mb_strlen($tmp, '8bit').">>\nstream\n$tmp\nendstream\nendobj";
2252 2252
 
2253 2253
                 return $res;
2254 2254
         }
@@ -2309,7 +2309,7 @@  discard block
 block discarded – undo
2309 2309
                 $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2310 2310
 
2311 2311
                 foreach ($o["info"] as $k => $v) {
2312
-                    if (!in_array($k, $valid_params)) {
2312
+                    if ( ! in_array($k, $valid_params)) {
2313 2313
                         continue;
2314 2314
                     }
2315 2315
                     $res .= "/$k $v\n";
@@ -2376,21 +2376,21 @@  discard block
 block discarded – undo
2376 2376
 
2377 2377
                 $res .= "/Resources <<";
2378 2378
                 if (isset($o['procset'])) {
2379
-                    $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2379
+                    $res .= "\n/ProcSet ".$o['procset']." 0 R";
2380 2380
                 } else {
2381 2381
                     $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2382 2382
                 }
2383 2383
                 if (isset($o['fonts']) && count($o['fonts'])) {
2384 2384
                     $res .= "\n/Font << ";
2385 2385
                     foreach ($o['fonts'] as $finfo) {
2386
-                        $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2386
+                        $res .= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
2387 2387
                     }
2388 2388
                     $res .= "\n>>";
2389 2389
                 }
2390 2390
                 if (isset($o['xObjects']) && count($o['xObjects'])) {
2391 2391
                     $res .= "\n/XObject << ";
2392 2392
                     foreach ($o['xObjects'] as $finfo) {
2393
-                        $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2393
+                        $res .= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
2394 2394
                     }
2395 2395
                     $res .= "\n>>";
2396 2396
                 }
@@ -2408,8 +2408,8 @@  discard block
 block discarded – undo
2408 2408
                     $tmp = $this->ARC4($tmp);
2409 2409
                 }
2410 2410
 
2411
-                $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2412
-                $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2411
+                $res .= "/Length ".mb_strlen($tmp, '8bit')." >>\n";
2412
+                $res .= "stream\n".$tmp."\nendstream"."\nendobj";
2413 2413
 
2414 2414
                 return $res;
2415 2415
         }
@@ -2464,7 +2464,7 @@  discard block
 block discarded – undo
2464 2464
                 if (isset($o['fonts']) && count($o['fonts'])) {
2465 2465
                     $res .= "/Font << \n";
2466 2466
                     foreach ($o['fonts'] as $finfo) {
2467
-                        $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2467
+                        $res .= "/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R\n";
2468 2468
                     }
2469 2469
                     $res .= ">>\n";
2470 2470
                 }
@@ -2589,7 +2589,7 @@  discard block
 block discarded – undo
2589 2589
 
2590 2590
             case 'byterange':
2591 2591
                 $o = &$this->objects[$id];
2592
-                $content =& $options['content'];
2592
+                $content = & $options['content'];
2593 2593
                 $content_len = strlen($content);
2594 2594
                 $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2595 2595
                 $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
@@ -2597,8 +2597,8 @@  discard block
 block discarded – undo
2597 2597
                 $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2598 2598
 
2599 2599
                 $fuid = uniqid();
2600
-                $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2601
-                $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2600
+                $tmpInput = $this->tmp."/pkcs7.tmp.".$fuid.'.in';
2601
+                $tmpOutput = $this->tmp."/pkcs7.tmp.".$fuid.'.out';
2602 2602
 
2603 2603
                 if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2604 2604
                     throw new \Exception("Unable to write temporary file for signing.");
@@ -2643,12 +2643,12 @@  discard block
 block discarded – undo
2643 2643
                     $this->encryptInit($id);
2644 2644
                 }
2645 2645
 
2646
-                $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2647
-                $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2646
+                $res .= "/ByteRange ".sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2647
+                $res .= "/Contents <".str_pad('', $sign_maxlen, '0').">\n";
2648 2648
                 $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2649 2649
                 $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2650 2650
 
2651
-                $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2651
+                $date = "D:".substr_replace(date('YmdHisO'), '\'', -2, 0).'\'';
2652 2652
                 if ($encrypted) {
2653 2653
                     $date = $this->ARC4($date);
2654 2654
                 }
@@ -2664,8 +2664,8 @@  discard block
 block discarded – undo
2664 2664
                         case 'Reason':
2665 2665
                         case 'ContactInfo':
2666 2666
                             if ($v !== null && $v !== '') {
2667
-                                $res .= "/$k (" .
2668
-                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2667
+                                $res .= "/$k (".
2668
+                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v).") \n";
2669 2669
                             }
2670 2670
                             break;
2671 2671
                     }
@@ -2697,10 +2697,10 @@  discard block
 block discarded – undo
2697 2697
 
2698 2698
             case 'keys':
2699 2699
                 // figure out the additional parameters required
2700
-                $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2701
-                    . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2702
-                    . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2703
-                    . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2700
+                $pad = chr(0x28).chr(0xBF).chr(0x4E).chr(0x5E).chr(0x4E).chr(0x75).chr(0x8A).chr(0x41)
2701
+                    . chr(0x64).chr(0x00).chr(0x4E).chr(0x56).chr(0xFF).chr(0xFA).chr(0x01).chr(0x08)
2702
+                    . chr(0x2E).chr(0x2E).chr(0x00).chr(0xB6).chr(0xD0).chr(0x68).chr(0x3E).chr(0x80)
2703
+                    . chr(0x2F).chr(0x0C).chr(0xA9).chr(0xFE).chr(0x64).chr(0x53).chr(0x69).chr(0x7A);
2704 2704
 
2705 2705
                 $info = $this->objects[$id]['info'];
2706 2706
 
@@ -2710,7 +2710,7 @@  discard block
 block discarded – undo
2710 2710
                     $owner = substr($info['owner'], 0, 32);
2711 2711
                 } else {
2712 2712
                     if ($len < 32) {
2713
-                        $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2713
+                        $owner = $info['owner'].substr($pad, 0, 32 - $len);
2714 2714
                     } else {
2715 2715
                         $owner = $info['owner'];
2716 2716
                     }
@@ -2721,7 +2721,7 @@  discard block
 block discarded – undo
2721 2721
                     $user = substr($info['user'], 0, 32);
2722 2722
                 } else {
2723 2723
                     if ($len < 32) {
2724
-                        $user = $info['user'] . substr($pad, 0, 32 - $len);
2724
+                        $user = $info['user'].substr($pad, 0, 32 - $len);
2725 2725
                     } else {
2726 2726
                         $user = $info['user'];
2727 2727
                     }
@@ -2735,7 +2735,7 @@  discard block
 block discarded – undo
2735 2735
 
2736 2736
                 // now make the u value, phew.
2737 2737
                 $tmp = $this->md5_16(
2738
-                    $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2738
+                    $user.$ovalue.chr($info['p']).chr(255).chr(255).chr(255).hex2bin($this->fileIdentifier)
2739 2739
                 );
2740 2740
 
2741 2741
                 $ukey = substr($tmp, 0, 5);
@@ -2754,11 +2754,11 @@  discard block
 block discarded – undo
2754 2754
                 $res .= "\n/Filter /Standard";
2755 2755
                 $res .= "\n/V 1";
2756 2756
                 $res .= "\n/R 2";
2757
-                $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2758
-                $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2757
+                $res .= "\n/O (".$this->filterText($o['info']['O'], false, false).')';
2758
+                $res .= "\n/U (".$this->filterText($o['info']['U'], false, false).')';
2759 2759
                 // and the p-value needs to be converted to account for the twos-complement approach
2760 2760
                 $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2761
-                $res .= "\n/P " . ($o['info']['p']);
2761
+                $res .= "\n/P ".($o['info']['p']);
2762 2762
                 $res .= "\n>>\nendobj";
2763 2763
 
2764 2764
                 return $res;
@@ -2826,7 +2826,7 @@  discard block
 block discarded – undo
2826 2826
                             $filename = $entry['filename'];
2827 2827
                         }
2828 2828
 
2829
-                        $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2829
+                        $res .= "($filename) ".$entry['dict_reference']." 0 R ";
2830 2830
                     }
2831 2831
 
2832 2832
                     $res .= "] >> endobj";
@@ -2863,7 +2863,7 @@  discard block
 block discarded – undo
2863 2863
                 $description = $this->filterText($description, false, false);
2864 2864
 
2865 2865
                 $res = "\n$id 0 obj <</Type /Filespec /EF";
2866
-                $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2866
+                $res .= " <</F ".$info['embedded_reference']." 0 R >>";
2867 2867
                 $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2868 2868
                 $res .= " >> endobj";
2869 2869
                 return $res;
@@ -2907,8 +2907,8 @@  discard block
 block discarded – undo
2907 2907
                 }
2908 2908
                 $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2909 2909
 
2910
-                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2911
-                    " /Type/EmbeddedFile /Filter/FlateDecode" .
2910
+                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>".
2911
+                    " /Type/EmbeddedFile /Filter/FlateDecode".
2912 2912
                     " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2913 2913
 
2914 2914
                 return $res;
@@ -2949,7 +2949,7 @@  discard block
 block discarded – undo
2949 2949
         $tmp = $this->encryptionKey;
2950 2950
         $hex = dechex($id);
2951 2951
         if (mb_strlen($hex, '8bit') < 6) {
2952
-            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2952
+            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')).$hex;
2953 2953
         }
2954 2954
         $tmp .= chr(hexdec(substr($hex, 4, 2)))
2955 2955
             . chr(hexdec(substr($hex, 2, 2)))
@@ -3128,7 +3128,7 @@  discard block
 block discarded – undo
3128 3128
 
3129 3129
         if ($this->fileIdentifier === '') {
3130 3130
             $tmp = implode('', $this->objects[$this->infoObject]['info']);
3131
-            $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3131
+            $this->fileIdentifier = md5('DOMPDF'.__FILE__.$tmp.microtime().mt_rand());
3132 3132
         }
3133 3133
 
3134 3134
         if ($this->arc4_objnum) {
@@ -3139,7 +3139,7 @@  discard block
 block discarded – undo
3139 3139
         $this->checkAllHere();
3140 3140
 
3141 3141
         $xref = [];
3142
-        $content = '%PDF-' . self::PDF_VERSION;
3142
+        $content = '%PDF-'.self::PDF_VERSION;
3143 3143
         $pos = mb_strlen($content, '8bit');
3144 3144
 
3145 3145
         // pre-process o_font objects before output of all objects
@@ -3150,31 +3150,31 @@  discard block
 block discarded – undo
3150 3150
         }
3151 3151
 
3152 3152
         foreach ($this->objects as $k => $v) {
3153
-            $tmp = 'o_' . $v['t'];
3153
+            $tmp = 'o_'.$v['t'];
3154 3154
             $cont = $this->$tmp($k, 'out');
3155 3155
             $content .= $cont;
3156 3156
             $xref[] = $pos + 1; //+1 to account for \n at the start of each object
3157 3157
             $pos += mb_strlen($cont, '8bit');
3158 3158
         }
3159 3159
 
3160
-        $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3160
+        $content .= "\nxref\n0 ".(count($xref) + 1)."\n0000000000 65535 f \n";
3161 3161
 
3162 3162
         foreach ($xref as $p) {
3163
-            $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3163
+            $content .= str_pad($p, 10, "0", STR_PAD_LEFT)." 00000 n \n";
3164 3164
         }
3165 3165
 
3166
-        $content .= "trailer\n<<\n" .
3167
-            '/Size ' . (count($xref) + 1) . "\n" .
3168
-            '/Root 1 0 R' . "\n" .
3169
-            '/Info ' . $this->infoObject . " 0 R\n"
3166
+        $content .= "trailer\n<<\n".
3167
+            '/Size '.(count($xref) + 1)."\n".
3168
+            '/Root 1 0 R'."\n".
3169
+            '/Info '.$this->infoObject." 0 R\n"
3170 3170
         ;
3171 3171
 
3172 3172
         // if encryption has been applied to this document then add the marker for this dictionary
3173 3173
         if ($this->arc4_objnum > 0) {
3174
-            $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3174
+            $content .= '/Encrypt '.$this->arc4_objnum." 0 R\n";
3175 3175
         }
3176 3176
 
3177
-        $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3177
+        $content .= '/ID[<'.$this->fileIdentifier.'><'.$this->fileIdentifier.">]\n";
3178 3178
 
3179 3179
         // account for \n added at start of xref table
3180 3180
         $pos++;
@@ -3183,7 +3183,7 @@  discard block
 block discarded – undo
3183 3183
 
3184 3184
         if (count($this->byteRange) > 0) {
3185 3185
             foreach ($this->byteRange as $k => $v) {
3186
-                $tmp = 'o_' . $v['t'];
3186
+                $tmp = 'o_'.$v['t'];
3187 3187
                 $this->$tmp($k, 'byterange', ['content' => &$content]);
3188 3188
             }
3189 3189
         }
@@ -3257,7 +3257,7 @@  discard block
 block discarded – undo
3257 3257
 
3258 3258
         $this->addMessage("openFont: $font - $name");
3259 3259
 
3260
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3260
+        if ( ! $this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3261 3261
             $metrics_name = "$name.afm";
3262 3262
         } else {
3263 3263
             $metrics_name = "$name.ufm";
@@ -3266,17 +3266,17 @@  discard block
 block discarded – undo
3266 3266
         $cache_name = "$metrics_name.json";
3267 3267
         $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3268 3268
 
3269
-        if (file_exists($fontcache . '/' . $cache_name)) {
3269
+        if (file_exists($fontcache.'/'.$cache_name)) {
3270 3270
             $this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3271
-            $cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3272
-            if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3271
+            $cached_font_info = json_decode(file_get_contents($fontcache.'/'.$cache_name), true);
3272
+            if ( ! isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3273 3273
                 $this->addMessage('openFont: font cache is out of date, regenerating');
3274 3274
             } else {
3275 3275
                 $this->fonts[$font] = $cached_font_info;
3276 3276
             }
3277 3277
         }
3278 3278
 
3279
-        if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3279
+        if ( ! isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3280 3280
             // then rebuild the php_<font>.afm file from the <font>.afm file
3281 3281
             $this->addMessage("openFont: build php file from $dir/$metrics_name");
3282 3282
             $data = [];
@@ -3353,12 +3353,12 @@  discard block
 block discarded – undo
3353 3353
                                 }
3354 3354
                             }
3355 3355
 
3356
-                            $c = (int)$dtmp['C'];
3356
+                            $c = (int) $dtmp['C'];
3357 3357
                             $n = $dtmp['N'];
3358 3358
                             $width = floatval($dtmp['WX']);
3359 3359
 
3360 3360
                             if ($c >= 0) {
3361
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3361
+                                if ( ! ctype_xdigit($n) || $c != hexdec($n)) {
3362 3362
                                     $data['codeToName'][$c] = $n;
3363 3363
                                 }
3364 3364
                                 $data['C'][$c] = $width;
@@ -3366,7 +3366,7 @@  discard block
 block discarded – undo
3366 3366
                                 $data['C'][$n] = $width;
3367 3367
                             }
3368 3368
 
3369
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3369
+                            if ( ! isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3370 3370
                                 $data['MissingWidth'] = $width;
3371 3371
                             }
3372 3372
 
@@ -3374,7 +3374,7 @@  discard block
 block discarded – undo
3374 3374
 
3375 3375
                         // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3376 3376
                         case 'U': // Found in UFM files
3377
-                            if (!$data['isUnicode']) {
3377
+                            if ( ! $data['isUnicode']) {
3378 3378
                                 break;
3379 3379
                             }
3380 3380
 
@@ -3399,7 +3399,7 @@  discard block
 block discarded – undo
3399 3399
                                 }
3400 3400
                             }
3401 3401
 
3402
-                            $c = (int)$dtmp['U'];
3402
+                            $c = (int) $dtmp['U'];
3403 3403
                             $n = $dtmp['N'];
3404 3404
                             $glyph = $dtmp['G'];
3405 3405
                             $width = floatval($dtmp['WX']);
@@ -3411,7 +3411,7 @@  discard block
 block discarded – undo
3411 3411
                                     $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3412 3412
                                 }
3413 3413
 
3414
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3414
+                                if ( ! ctype_xdigit($n) || $c != hexdec($n)) {
3415 3415
                                     $data['codeToName'][$c] = $n;
3416 3416
                                 }
3417 3417
                                 $data['C'][$c] = $width;
@@ -3419,7 +3419,7 @@  discard block
 block discarded – undo
3419 3419
                                 $data['C'][$n] = $width;
3420 3420
                             }
3421 3421
 
3422
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3422
+                            if ( ! isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3423 3423
                                 $data['MissingWidth'] = $width;
3424 3424
                             }
3425 3425
 
@@ -3452,7 +3452,7 @@  discard block
 block discarded – undo
3452 3452
             $data = null;
3453 3453
         }
3454 3454
 
3455
-        if (!isset($this->fonts[$font])) {
3455
+        if ( ! isset($this->fonts[$font])) {
3456 3456
             $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3457 3457
         }
3458 3458
     }
@@ -3482,7 +3482,7 @@  discard block
 block discarded – undo
3482 3482
             $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3483 3483
         }
3484 3484
 
3485
-        if (!isset($this->fonts[$fontName])) {
3485
+        if ( ! isset($this->fonts[$fontName])) {
3486 3486
             $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3487 3487
 
3488 3488
             // load the file
@@ -3619,7 +3619,7 @@  discard block
 block discarded – undo
3619 3619
     {
3620 3620
         $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3621 3621
 
3622
-        if (!$force && $this->currentColor == $new_color) {
3622
+        if ( ! $force && $this->currentColor == $new_color) {
3623 3623
             return;
3624 3624
         }
3625 3625
 
@@ -3641,7 +3641,7 @@  discard block
 block discarded – undo
3641 3641
      */
3642 3642
     function setFillRule($fillRule)
3643 3643
     {
3644
-        if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3644
+        if ( ! in_array($fillRule, ["nonzero", "evenodd"])) {
3645 3645
             return;
3646 3646
         }
3647 3647
 
@@ -3658,7 +3658,7 @@  discard block
 block discarded – undo
3658 3658
     {
3659 3659
         $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3660 3660
 
3661
-        if (!$force && $this->currentStrokeColor == $new_color) {
3661
+        if ( ! $force && $this->currentStrokeColor == $new_color) {
3662 3662
             return;
3663 3663
         }
3664 3664
 
@@ -3719,7 +3719,7 @@  discard block
 block discarded – undo
3719 3719
             "Exclusion"
3720 3720
         ];
3721 3721
 
3722
-        if (!in_array($mode, $blend_modes)) {
3722
+        if ( ! in_array($mode, $blend_modes)) {
3723 3723
             $mode = "Normal";
3724 3724
         }
3725 3725
 
@@ -3739,7 +3739,7 @@  discard block
 block discarded – undo
3739 3739
 
3740 3740
         $options = [
3741 3741
             "BM" => "/$mode",
3742
-            "CA" => (float)$opacity
3742
+            "CA" => (float) $opacity
3743 3743
         ];
3744 3744
 
3745 3745
         $this->setGraphicsState($options);
@@ -3774,7 +3774,7 @@  discard block
 block discarded – undo
3774 3774
             "Exclusion"
3775 3775
         ];
3776 3776
 
3777
-        if (!in_array($mode, $blend_modes)) {
3777
+        if ( ! in_array($mode, $blend_modes)) {
3778 3778
             $mode = "Normal";
3779 3779
         }
3780 3780
 
@@ -3794,7 +3794,7 @@  discard block
 block discarded – undo
3794 3794
 
3795 3795
         $options = [
3796 3796
             "BM" => "/$mode",
3797
-            "ca" => (float)$opacity,
3797
+            "ca" => (float) $opacity,
3798 3798
         ];
3799 3799
 
3800 3800
         $this->setGraphicsState($options);
@@ -3979,15 +3979,15 @@  discard block
 block discarded – undo
3979 3979
             $nSeg = 2;
3980 3980
         }
3981 3981
 
3982
-        $astart = deg2rad((float)$astart);
3983
-        $afinish = deg2rad((float)$afinish);
3982
+        $astart = deg2rad((float) $astart);
3983
+        $afinish = deg2rad((float) $afinish);
3984 3984
         $totalAngle = $afinish - $astart;
3985 3985
 
3986 3986
         $dt = $totalAngle / $nSeg;
3987 3987
         $dtm = $dt / 3;
3988 3988
 
3989 3989
         if ($angle != 0) {
3990
-            $a = -1 * deg2rad((float)$angle);
3990
+            $a = -1 * deg2rad((float) $angle);
3991 3991
 
3992 3992
             $this->addContent(
3993 3993
                 sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
@@ -4003,7 +4003,7 @@  discard block
 block discarded – undo
4003 4003
         $c0 = -$r1 * sin($t1);
4004 4004
         $d0 = $r2 * cos($t1);
4005 4005
 
4006
-        if (!$incomplete) {
4006
+        if ( ! $incomplete) {
4007 4007
             $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4008 4008
         }
4009 4009
 
@@ -4033,7 +4033,7 @@  discard block
 block discarded – undo
4033 4033
             $d0 = $d1;
4034 4034
         }
4035 4035
 
4036
-        if (!$incomplete) {
4036
+        if ( ! $incomplete) {
4037 4037
             if ($fill) {
4038 4038
                 $this->addContent(' f');
4039 4039
             }
@@ -4093,7 +4093,7 @@  discard block
 block discarded – undo
4093 4093
         }
4094 4094
 
4095 4095
         if (is_array($dash)) {
4096
-            $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4096
+            $string .= ' [ '.implode(' ', $dash)." ] $phase d";
4097 4097
         }
4098 4098
 
4099 4099
         $this->currentLineStyle = $string;
@@ -4166,17 +4166,17 @@  discard block
 block discarded – undo
4166 4166
 
4167 4167
     function stroke(bool $close = false)
4168 4168
     {
4169
-        $this->addContent("\n" . ($close ? "s" : "S"));
4169
+        $this->addContent("\n".($close ? "s" : "S"));
4170 4170
     }
4171 4171
 
4172 4172
     function fill()
4173 4173
     {
4174
-        $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4174
+        $this->addContent("\nf".($this->fillRule === "evenodd" ? "*" : ""));
4175 4175
     }
4176 4176
 
4177 4177
     function fillStroke(bool $close = false)
4178 4178
     {
4179
-        $this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
4179
+        $this->addContent("\n".($close ? "b" : "B").($this->fillRule === "evenodd" ? "*" : ""));
4180 4180
     }
4181 4181
 
4182 4182
     /**
@@ -4262,16 +4262,16 @@  discard block
 block discarded – undo
4262 4262
      */
4263 4263
     public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4264 4264
     {
4265
-        if (!$this->numFonts) {
4265
+        if ( ! $this->numFonts) {
4266 4266
             $this->selectFont($this->defaultFont);
4267 4267
         }
4268 4268
 
4269
-        $color = implode(' ', $color) . ' rg';
4269
+        $color = implode(' ', $color).' rg';
4270 4270
 
4271 4271
         $currentFontNum = $this->currentFontNum;
4272 4272
         $font = array_filter(
4273 4273
             $this->objects[$this->currentNode]['info']['fonts'],
4274
-            function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4274
+            function($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4275 4275
         );
4276 4276
 
4277 4277
         $this->o_acroform($this->acroFormId, 'font',
@@ -4285,7 +4285,7 @@  discard block
 block discarded – undo
4285 4285
           'T' => $name,
4286 4286
           'Ff' => $ff,
4287 4287
           'pageid' => $this->currentPage,
4288
-          'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4288
+          'da' => "$color /F$this->currentFontNum ".sprintf('%.1F Tf ', $size)
4289 4289
         ]);
4290 4290
 
4291 4291
         return $fieldId;
@@ -4652,17 +4652,17 @@  discard block
 block discarded – undo
4652 4652
             die("Unable to stream pdf: headers already sent");
4653 4653
         }
4654 4654
 
4655
-        if (!isset($options["compress"])) $options["compress"] = true;
4656
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4655
+        if ( ! isset($options["compress"])) $options["compress"] = true;
4656
+        if ( ! isset($options["Attachment"])) $options["Attachment"] = true;
4657 4657
 
4658
-        $debug = !$options['compress'];
4658
+        $debug = ! $options['compress'];
4659 4659
         $tmp = ltrim($this->output($debug));
4660 4660
 
4661 4661
         header("Cache-Control: private");
4662 4662
         header("Content-Type: application/pdf");
4663
-        header("Content-Length: " . mb_strlen($tmp, "8bit"));
4663
+        header("Content-Length: ".mb_strlen($tmp, "8bit"));
4664 4664
 
4665
-        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4665
+        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")).".pdf";
4666 4666
         $attachment = $options["Attachment"] ? "attachment" : "inline";
4667 4667
 
4668 4668
         $encoding = mb_detect_encoding($filename);
@@ -4689,7 +4689,7 @@  discard block
 block discarded – undo
4689 4689
      */
4690 4690
     public function getFontHeight(float $size): float
4691 4691
     {
4692
-        if (!$this->numFonts) {
4692
+        if ( ! $this->numFonts) {
4693 4693
             $this->selectFont($this->defaultFont);
4694 4694
         }
4695 4695
 
@@ -4712,7 +4712,7 @@  discard block
 block discarded – undo
4712 4712
             // Courier font.
4713 4713
             //
4714 4714
             // Both have been added manually to the .afm and .ufm files.
4715
-            $h += (int)$font['FontHeightOffset'];
4715
+            $h += (int) $font['FontHeightOffset'];
4716 4716
         }
4717 4717
 
4718 4718
         return $size * $h / 1000;
@@ -4725,7 +4725,7 @@  discard block
 block discarded – undo
4725 4725
      */
4726 4726
     public function getFontXHeight(float $size): float
4727 4727
     {
4728
-        if (!$this->numFonts) {
4728
+        if ( ! $this->numFonts) {
4729 4729
             $this->selectFont($this->defaultFont);
4730 4730
         }
4731 4731
 
@@ -4753,7 +4753,7 @@  discard block
 block discarded – undo
4753 4753
     public function getFontDescender(float $size): float
4754 4754
     {
4755 4755
         // note that this will most likely return a negative value
4756
-        if (!$this->numFonts) {
4756
+        if ( ! $this->numFonts) {
4757 4757
             $this->selectFont($this->defaultFont);
4758 4758
         }
4759 4759
 
@@ -4774,7 +4774,7 @@  discard block
 block discarded – undo
4774 4774
      */
4775 4775
     function filterText($text, $bom = true, $convert_encoding = true)
4776 4776
     {
4777
-        if (!$this->numFonts) {
4777
+        if ( ! $this->numFonts) {
4778 4778
             $this->selectFont($this->defaultFont);
4779 4779
         }
4780 4780
 
@@ -4884,12 +4884,12 @@  discard block
 block discarded – undo
4884 4884
             if ($c === 0xFFFD) {
4885 4885
                 $out .= "\xFF\xFD"; // replacement character
4886 4886
             } elseif ($c < 0x10000) {
4887
-                $out .= chr($c >> 0x08) . chr($c & 0xFF);
4887
+                $out .= chr($c >> 0x08).chr($c & 0xFF);
4888 4888
             } else {
4889 4889
                 $c -= 0x10000;
4890 4890
                 $w1 = 0xD800 | ($c >> 0x10);
4891 4891
                 $w2 = 0xDC00 | ($c & 0x3FF);
4892
-                $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4892
+                $out .= chr($w1 >> 0x08).chr($w1 & 0xFF).chr($w2 >> 0x08).chr($w2 & 0xFF);
4893 4893
             }
4894 4894
         }
4895 4895
 
@@ -4917,7 +4917,7 @@  discard block
 block discarded – undo
4917 4917
         $words = explode(' ', $text);
4918 4918
         $nspaces = count($words) - 1;
4919 4919
         $w += $wa * $nspaces;
4920
-        $a = deg2rad((float)$angle);
4920
+        $a = deg2rad((float) $angle);
4921 4921
 
4922 4922
         return [cos($a) * $w + $x, -sin($a) * $w + $y];
4923 4923
     }
@@ -4952,11 +4952,11 @@  discard block
 block discarded – undo
4952 4952
      */
4953 4953
     function registerText($font, $text)
4954 4954
     {
4955
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4955
+        if ( ! $this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4956 4956
             return;
4957 4957
         }
4958 4958
 
4959
-        if (!isset($this->stringSubsets[$font])) {
4959
+        if ( ! isset($this->stringSubsets[$font])) {
4960 4960
             $base_subset = "\u{fffd}\u{fffe}\u{ffff}";
4961 4961
             $this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
4962 4962
         }
@@ -4980,7 +4980,7 @@  discard block
 block discarded – undo
4980 4980
      */
4981 4981
     function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4982 4982
     {
4983
-        if (!$this->numFonts) {
4983
+        if ( ! $this->numFonts) {
4984 4984
             $this->selectFont($this->defaultFont);
4985 4985
         }
4986 4986
 
@@ -5021,7 +5021,7 @@  discard block
 block discarded – undo
5021 5021
         if ($angle == 0) {
5022 5022
             $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5023 5023
         } else {
5024
-            $a = deg2rad((float)$angle);
5024
+            $a = deg2rad((float) $angle);
5025 5025
             $this->addContent(
5026 5026
                 sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5027 5027
             );
@@ -5044,9 +5044,9 @@  discard block
 block discarded – undo
5044 5044
             // modify unicode text so that extra word spacing is manually implemented (bug #)
5045 5045
             if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5046 5046
                 $space_scale = 1000 / $size;
5047
-                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5047
+                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20".(-round($space_scale * $wordSpaceAdjust))."\x00\x20(", $place_text);
5048 5048
             }
5049
-            $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5049
+            $this->addContent(" /F$this->currentFontNum ".sprintf('%.1F Tf ', $size));
5050 5050
             $this->addContent(" [($place_text)] TJ");
5051 5051
         }
5052 5052
 
@@ -5105,7 +5105,7 @@  discard block
 block discarded – undo
5105 5105
         // and put them back at the end.
5106 5106
         $store_currentTextState = $this->currentTextState;
5107 5107
 
5108
-        if (!$this->numFonts) {
5108
+        if ( ! $this->numFonts) {
5109 5109
             $this->selectFont($this->defaultFont);
5110 5110
         }
5111 5111
 
@@ -5206,7 +5206,7 @@  discard block
 block discarded – undo
5206 5206
             // ok to use this as stack starts numbering at 1
5207 5207
             $this->setColor($opt['col'], true);
5208 5208
             $this->setStrokeColor($opt['str'], true);
5209
-            $this->addContent("\n" . $opt['lin']);
5209
+            $this->addContent("\n".$opt['lin']);
5210 5210
             //    $this->currentLineStyle = $opt['lin'];
5211 5211
         } else {
5212 5212
             $this->nStateStack++;
@@ -5227,11 +5227,11 @@  discard block
 block discarded – undo
5227 5227
      */
5228 5228
     function restoreState($pageEnd = 0)
5229 5229
     {
5230
-        if (!$pageEnd) {
5230
+        if ( ! $pageEnd) {
5231 5231
             $n = $this->nStateStack;
5232 5232
             $this->currentColor = $this->stateStack[$n]['col'];
5233 5233
             $this->currentStrokeColor = $this->stateStack[$n]['str'];
5234
-            $this->addContent("\n" . $this->stateStack[$n]['lin']);
5234
+            $this->addContent("\n".$this->stateStack[$n]['lin']);
5235 5235
             $this->currentLineStyle = $this->stateStack[$n]['lin'];
5236 5236
             $this->stateStack[$n] = null;
5237 5237
             unset($this->stateStack[$n]);
@@ -5505,7 +5505,7 @@  discard block
 block discarded – undo
5505 5505
      */
5506 5506
     function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5507 5507
     {
5508
-        if (!function_exists("imagepng")) {
5508
+        if ( ! function_exists("imagepng")) {
5509 5509
             throw new \Exception("The PHP GD extension is required, but is not installed.");
5510 5510
         }
5511 5511
 
@@ -5531,7 +5531,7 @@  discard block
 block discarded – undo
5531 5531
             //DEBUG_IMG_TEMP
5532 5532
             //debugpng
5533 5533
             if (defined("DEBUGPNG") && DEBUGPNG) {
5534
-                print '[addImagePng ' . $file . ']';
5534
+                print '[addImagePng '.$file.']';
5535 5535
             }
5536 5536
 
5537 5537
             ob_start();
@@ -5549,7 +5549,7 @@  discard block
 block discarded – undo
5549 5549
             }
5550 5550
 
5551 5551
             if ($error) {
5552
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5552
+                $this->addMessage('PNG error - ('.$file.') '.$errormsg);
5553 5553
 
5554 5554
                 return;
5555 5555
             }
@@ -5700,9 +5700,9 @@  discard block
 block discarded – undo
5700 5700
                         // without gamma correction
5701 5701
                         $pixel = (127 - $alpha) * 2;
5702 5702
 
5703
-                        $key = $col['red'] . $col['green'] . $col['blue'];
5703
+                        $key = $col['red'].$col['green'].$col['blue'];
5704 5704
 
5705
-                        if (!isset($allocated_colors[$key])) {
5705
+                        if ( ! isset($allocated_colors[$key])) {
5706 5706
                             $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5707 5707
                             $allocated_colors[$key] = $pixel_img;
5708 5708
                         } else {
@@ -5753,7 +5753,7 @@  discard block
 block discarded – undo
5753 5753
      */
5754 5754
     function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5755 5755
     {
5756
-        if (!function_exists("imagecreatefrompng")) {
5756
+        if ( ! function_exists("imagecreatefrompng")) {
5757 5757
             throw new \Exception("The PHP GD extension is required, but is not installed.");
5758 5758
         }
5759 5759
 
@@ -5801,7 +5801,7 @@  discard block
 block discarded – undo
5801 5801
             //Therefore create an empty image with white background and merge the
5802 5802
             //image in with alpha blending.
5803 5803
             $imgtmp = @imagecreatefrompng($file);
5804
-            if (!$imgtmp) {
5804
+            if ( ! $imgtmp) {
5805 5805
                 return;
5806 5806
             }
5807 5807
             $sx = imagesx($imgtmp);
@@ -5858,21 +5858,21 @@  discard block
 block discarded – undo
5858 5858
 
5859 5859
             $error = 0;
5860 5860
 
5861
-            if (!$error) {
5862
-                $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5861
+            if ( ! $error) {
5862
+                $header = chr(137).chr(80).chr(78).chr(71).chr(13).chr(10).chr(26).chr(10);
5863 5863
 
5864 5864
                 if (mb_substr($data, 0, 8, '8bit') != $header) {
5865 5865
                     $error = 1;
5866 5866
 
5867 5867
                     if (defined("DEBUGPNG") && DEBUGPNG) {
5868
-                        print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5868
+                        print '[addPngFromFile this file does not have a valid header '.$file.']';
5869 5869
                     }
5870 5870
 
5871 5871
                     $errormsg = 'this file does not have a valid header';
5872 5872
                 }
5873 5873
             }
5874 5874
 
5875
-            if (!$error) {
5875
+            if ( ! $error) {
5876 5876
                 // set pointer
5877 5877
                 $p = 8;
5878 5878
                 $len = mb_strlen($data, '8bit');
@@ -5905,7 +5905,7 @@  discard block
 block discarded – undo
5905 5905
 
5906 5906
                                 //debugpng
5907 5907
                                 if (defined("DEBUGPNG") && DEBUGPNG) {
5908
-                                    print '[addPngFromFile unsupported compression method ' . $file . ']';
5908
+                                    print '[addPngFromFile unsupported compression method '.$file.']';
5909 5909
                                 }
5910 5910
 
5911 5911
                                 $errormsg = 'unsupported compression method';
@@ -5916,7 +5916,7 @@  discard block
 block discarded – undo
5916 5916
 
5917 5917
                                 //debugpng
5918 5918
                                 if (defined("DEBUGPNG") && DEBUGPNG) {
5919
-                                    print '[addPngFromFile unsupported filter method ' . $file . ']';
5919
+                                    print '[addPngFromFile unsupported filter method '.$file.']';
5920 5920
                                 }
5921 5921
 
5922 5922
                                 $errormsg = 'unsupported filter method';
@@ -5988,7 +5988,7 @@  discard block
 block discarded – undo
5988 5988
                                 //unsupported transparency type
5989 5989
                                 default:
5990 5990
                                     if (defined("DEBUGPNG") && DEBUGPNG) {
5991
-                                        print '[addPngFromFile unsupported transparency type ' . $file . ']';
5991
+                                        print '[addPngFromFile unsupported transparency type '.$file.']';
5992 5992
                                     }
5993 5993
                                     break;
5994 5994
                             }
@@ -6003,12 +6003,12 @@  discard block
 block discarded – undo
6003 6003
                     $p += $chunkLen + 12;
6004 6004
                 }
6005 6005
 
6006
-                if (!$haveHeader) {
6006
+                if ( ! $haveHeader) {
6007 6007
                     $error = 1;
6008 6008
 
6009 6009
                     //debugpng
6010 6010
                     if (defined("DEBUGPNG") && DEBUGPNG) {
6011
-                        print '[addPngFromFile information header is missing ' . $file . ']';
6011
+                        print '[addPngFromFile information header is missing '.$file.']';
6012 6012
                     }
6013 6013
 
6014 6014
                     $errormsg = 'information header is missing';
@@ -6019,25 +6019,25 @@  discard block
 block discarded – undo
6019 6019
 
6020 6020
                     //debugpng
6021 6021
                     if (defined("DEBUGPNG") && DEBUGPNG) {
6022
-                        print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6022
+                        print '[addPngFromFile no support for interlaced images in pdf '.$file.']';
6023 6023
                     }
6024 6024
 
6025 6025
                     $errormsg = 'There appears to be no support for interlaced images in pdf.';
6026 6026
                 }
6027 6027
             }
6028 6028
 
6029
-            if (!$error && $info['bitDepth'] > 8) {
6029
+            if ( ! $error && $info['bitDepth'] > 8) {
6030 6030
                 $error = 1;
6031 6031
 
6032 6032
                 //debugpng
6033 6033
                 if (defined("DEBUGPNG") && DEBUGPNG) {
6034
-                    print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6034
+                    print '[addPngFromFile bit depth of 8 or less is supported '.$file.']';
6035 6035
                 }
6036 6036
 
6037 6037
                 $errormsg = 'only bit depth of 8 or less is supported';
6038 6038
             }
6039 6039
 
6040
-            if (!$error) {
6040
+            if ( ! $error) {
6041 6041
                 switch ($info['colorType']) {
6042 6042
                     case 3:
6043 6043
                         $color = 'DeviceRGB';
@@ -6059,7 +6059,7 @@  discard block
 block discarded – undo
6059 6059
 
6060 6060
                         //debugpng
6061 6061
                         if (defined("DEBUGPNG") && DEBUGPNG) {
6062
-                            print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6062
+                            print '[addPngFromFile alpha channel not supported: '.$info['colorType'].' '.$file.']';
6063 6063
                         }
6064 6064
 
6065 6065
                         $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
@@ -6067,7 +6067,7 @@  discard block
 block discarded – undo
6067 6067
             }
6068 6068
 
6069 6069
             if ($error) {
6070
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6070
+                $this->addMessage('PNG error - ('.$file.') '.$errormsg);
6071 6071
 
6072 6072
                 return;
6073 6073
             }
@@ -6136,7 +6136,7 @@  discard block
 block discarded – undo
6136 6136
         // attempt to add a jpeg image straight from a file, using no GD commands
6137 6137
         // note that this function is unable to operate on a remote file.
6138 6138
 
6139
-        if (!file_exists($img)) {
6139
+        if ( ! file_exists($img)) {
6140 6140
             return;
6141 6141
         }
6142 6142
 
@@ -6204,7 +6204,7 @@  discard block
 block discarded – undo
6204 6204
 
6205 6205
         } else {
6206 6206
             if ($data == null) {
6207
-                $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6207
+                $this->addMessage('addJpegImage_common error - ('.$imgname.') data not present!');
6208 6208
 
6209 6209
                 return;
6210 6210
             }
@@ -6315,7 +6315,7 @@  discard block
 block discarded – undo
6315 6315
      */
6316 6316
     function setFontFamily($family, $options = '')
6317 6317
     {
6318
-        if (!is_array($options)) {
6318
+        if ( ! is_array($options)) {
6319 6319
             if ($family === 'init') {
6320 6320
                 // set the known family groups
6321 6321
                 // these font families will be used to enable bold and italic markers to be included
@@ -6361,7 +6361,7 @@  discard block
 block discarded – undo
6361 6361
      */
6362 6362
     function addMessage($message)
6363 6363
     {
6364
-        $this->messages .= $message . "\n";
6364
+        $this->messages .= $message."\n";
6365 6365
     }
6366 6366
 
6367 6367
     /**
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Gradient/Stop.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -10,7 +10,7 @@
 block discarded – undo
10 10
 
11 11
 class Stop
12 12
 {
13
-    public $offset;
14
-    public $color;
15
-    public $opacity = 1.0;
13
+	public $offset;
14
+	public $color;
15
+	public $opacity = 1.0;
16 16
 }
Please login to merge, or discard this patch.