1
|
|
|
<?php |
2
|
|
|
namespace OPlathey\FPDF; |
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
/******************************************************************************* |
7
|
|
|
* FPDF * |
8
|
|
|
* * |
9
|
|
|
* Version: 1.82 * |
10
|
|
|
* Date: 2019-12-07 * |
11
|
|
|
* Author: Olivier PLATHEY * |
12
|
|
|
* http://www.fpdf.org/en/doc/index.php |
13
|
|
|
*******************************************************************************/ |
14
|
|
|
|
15
|
|
|
define('FPDF_VERSION', '1.82'); |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Modified version of O.Platheys FPDF.php |
19
|
|
|
* |
20
|
|
|
* Based on version 1.82 of FPDF.php, extended by |
21
|
|
|
* - namespace to include through autoloader |
22
|
|
|
* - PHP 7.4 typehints |
23
|
|
|
* - phpDoc comments |
24
|
|
|
* |
25
|
|
|
* @link http://www.fpdf.org/en/doc/index.php |
26
|
|
|
* @package OPlathey/FPDF |
27
|
|
|
* @author O.Plathey |
28
|
|
|
* @copyright MIT License - see the LICENSE file for details |
29
|
|
|
*/ |
30
|
|
|
class FPDF |
31
|
|
|
{ |
32
|
|
|
/** @var int current page number */ |
33
|
|
|
protected int $page = 0; |
34
|
|
|
/** @var int current object number */ |
35
|
|
|
protected int $n = 2; |
36
|
|
|
/** @var array array of object offsets */ |
37
|
|
|
protected array $offsets; |
38
|
|
|
/** @var string buffer holding in-memory PDF */ |
39
|
|
|
protected string $buffer = ''; |
40
|
|
|
/** @var array array containing pages */ |
41
|
|
|
protected array $pages = array(); |
42
|
|
|
/** @var int current document state */ |
43
|
|
|
protected int $state = 0; |
44
|
|
|
/** @var bool compression flag */ |
45
|
|
|
protected bool $compress; |
46
|
|
|
/** @var float scale factor (number of points in user unit) */ |
47
|
|
|
protected float $k; |
48
|
|
|
/** @var string default orientation */ |
49
|
|
|
protected string $DefOrientation; |
50
|
|
|
/** @var string current orientation */ |
51
|
|
|
protected string $CurOrientation; |
52
|
|
|
/** @var array default page size */ |
53
|
|
|
protected array $DefPageSize; |
54
|
|
|
/** @var array current page size */ |
55
|
|
|
protected array $CurPageSize; |
56
|
|
|
/** @var int current page rotation */ |
57
|
|
|
protected int $CurRotation; |
58
|
|
|
/** @var array page-related data */ |
59
|
|
|
protected array $PageInfo = array(); |
60
|
|
|
/** @var float width of current page in points */ |
61
|
|
|
protected float $wPt; |
62
|
|
|
/** @var float height of current page in points */ |
63
|
|
|
protected float $hPt; |
64
|
|
|
/** @var float width of current page in user unit */ |
65
|
|
|
protected float $w; |
66
|
|
|
/** @var float height of current page in user unit */ |
67
|
|
|
protected float $h; |
68
|
|
|
/** @var float left margin */ |
69
|
|
|
protected float $lMargin; |
70
|
|
|
/** @var float top margin */ |
71
|
|
|
protected float $tMargin; |
72
|
|
|
/** @var float right margin */ |
73
|
|
|
protected float $rMargin; |
74
|
|
|
/** @var float page break margin */ |
75
|
|
|
protected float $bMargin; |
76
|
|
|
/** @var float cell margin */ |
77
|
|
|
protected float $cMargin; |
78
|
|
|
/** @var float current X-position in user unit */ |
79
|
|
|
protected float $x; |
80
|
|
|
/** @var float current Y-position in user unit */ |
81
|
|
|
protected float $y; |
82
|
|
|
/** @var float height of last printed cell */ |
83
|
|
|
protected float $lasth = 0.0; |
84
|
|
|
/** @var float line width in user unit */ |
85
|
|
|
protected float $LineWidth; |
86
|
|
|
/** @var string path containing fonts */ |
87
|
|
|
protected string $fontpath; |
88
|
|
|
/** @var array array of used fonts */ |
89
|
|
|
protected array $fonts = array(); |
90
|
|
|
/** @var array array of font files */ |
91
|
|
|
protected array $FontFiles = array(); |
92
|
|
|
/** @var array array of encodings */ |
93
|
|
|
protected array $encodings = array(); |
94
|
|
|
/** @var array array of ToUnicode CMaps */ |
95
|
|
|
protected array $cmaps = array(); |
96
|
|
|
/** @var string current font family */ |
97
|
|
|
protected string $FontFamily = ''; |
98
|
|
|
/** @var string current font style */ |
99
|
|
|
protected string $FontStyle = ''; |
100
|
|
|
/** @var bool underlining flag */ |
101
|
|
|
protected bool $underline = false; |
102
|
|
|
/** @var array current font info */ |
103
|
|
|
protected array $CurrentFont; |
104
|
|
|
/** @var float current font size in points */ |
105
|
|
|
protected float $FontSizePt = 12.0; |
106
|
|
|
/** @var float current font size in user unit */ |
107
|
|
|
protected float $FontSize; |
108
|
|
|
/** @var string commands for drawing color */ |
109
|
|
|
protected string $DrawColor = '0 G'; |
110
|
|
|
/** @var string commands for filling color */ |
111
|
|
|
protected string $FillColor = '0 g'; |
112
|
|
|
/** @var string commands for text color */ |
113
|
|
|
protected string $TextColor = '0 g'; |
114
|
|
|
/** @var bool indicates whether fill and text colors are different */ |
115
|
|
|
protected bool $ColorFlag = false; |
116
|
|
|
/** @var bool indicates whether alpha channel is used */ |
117
|
|
|
protected bool $WithAlpha = false; |
118
|
|
|
/** @var float word spacing */ |
119
|
|
|
protected float $ws = 0.0; |
120
|
|
|
/** @var array array of used images */ |
121
|
|
|
protected array $images = array(); |
122
|
|
|
/** @var array array of links in pages */ |
123
|
|
|
protected array $PageLinks; |
124
|
|
|
/** @var array array of internal links */ |
125
|
|
|
protected array $links = array(); |
126
|
|
|
/** @var bool automatic page breaking */ |
127
|
|
|
protected bool $AutoPageBreak; |
128
|
|
|
/** @var float threshold used to trigger page breaks */ |
129
|
|
|
protected float $PageBreakTrigger; |
130
|
|
|
/** @var bool flag set when processing header */ |
131
|
|
|
protected bool $InHeader = false; |
132
|
|
|
/** @var bool flag set when processing footer */ |
133
|
|
|
protected bool $InFooter = false; |
134
|
|
|
/** @var string alias for total number of pages */ |
135
|
|
|
protected string $AliasNbPages; |
136
|
|
|
/** @var string|float zoom display mode */ |
137
|
|
|
protected $ZoomMode; |
138
|
|
|
/** @var string layout display mode */ |
139
|
|
|
protected string $LayoutMode; |
140
|
|
|
/** @var array document properties */ |
141
|
|
|
protected array $metadata; |
142
|
|
|
/** @var string PDF version number */ |
143
|
|
|
protected string $PDFVersion; |
144
|
|
|
/** @var array */ |
145
|
|
|
protected array $outlines = array(); |
146
|
|
|
/** @var int */ |
147
|
|
|
protected int $outlineRoot; |
148
|
|
|
/** @var string */ |
149
|
|
|
protected string $charset = 'UTF-8'; |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* This is the class constructor. |
153
|
|
|
* It allows to set up the page size, the orientation and the unit of measure used |
154
|
|
|
* in all methods (except for font sizes). |
155
|
|
|
* @param string $orientation Default page orientation. <br/> |
156
|
|
|
* Possible values are (case insensitive): <ul> |
157
|
|
|
* <li> 'P' or 'Portrait' </li> |
158
|
|
|
* <li> 'L' or 'Landscape' </li></ul> |
159
|
|
|
* Default value is 'P'. <br/> |
160
|
|
|
* @param string $unit User unit. <br/> |
161
|
|
|
* Possible values are: <ul> |
162
|
|
|
* <li> 'pt': point, </li> |
163
|
|
|
* <li> 'mm': millimeter, </li> |
164
|
|
|
* <li> 'cm': centimeter, </li> |
165
|
|
|
* <li> 'in': inch </li></ul> |
166
|
|
|
* A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). <br/> |
167
|
|
|
* This is a very common unit in typography; font sizes are expressed in that unit. <br/> |
168
|
|
|
* Default value is 'mm'. <br/> |
169
|
|
|
* @param string|array $size The size used for pages. <br/> |
170
|
|
|
* It can be either one of the following values (case insensitive): <ul> |
171
|
|
|
* <li> 'A3' </li> |
172
|
|
|
* <li> 'A4' </li> |
173
|
|
|
* <li> 'A5' </li> |
174
|
|
|
* <li> 'Letter' </li> |
175
|
|
|
* <li> 'Legal' </li></ul> |
176
|
|
|
* or an array containing the width and the height (expressed in the unit given by unit). <br/> |
177
|
|
|
* Default value is 'A4'. |
178
|
|
|
*/ |
179
|
|
|
public function __construct(string $orientation = 'P', string $unit = 'mm', $size = 'A4') |
180
|
|
|
{ |
181
|
|
|
// Some checks |
182
|
|
|
$this->doChecks(); |
183
|
|
|
$this->getFontPath(); |
184
|
|
|
|
185
|
|
|
// Scale factor |
186
|
|
|
switch ($unit) { |
187
|
|
|
case 'pt': |
188
|
|
|
$this->k = 1; |
189
|
|
|
break; |
190
|
|
|
case 'mm': |
191
|
|
|
$this->k = 72 / 25.4; |
192
|
|
|
break; |
193
|
|
|
case 'cm': |
194
|
|
|
$this->k = 72 / 2.54; |
195
|
|
|
break; |
196
|
|
|
case 'in': |
197
|
|
|
$this->k = 72; |
198
|
|
|
break; |
199
|
|
|
default: |
200
|
|
|
$this->error('Incorrect unit: ' . $unit); |
201
|
|
|
break; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
// Page sizes |
205
|
|
|
$size = $this->getPageSize($size); |
206
|
|
|
$this->DefPageSize = $size; |
207
|
|
|
$this->CurPageSize = $size; |
208
|
|
|
|
209
|
|
|
// Page orientation |
210
|
|
|
$orientation = strtolower($orientation); |
211
|
|
|
if ($orientation == 'p' || $orientation == 'portrait') { |
212
|
|
|
$this->DefOrientation = 'P'; |
213
|
|
|
$this->w = $size[0]; |
214
|
|
|
$this->h = $size[1]; |
215
|
|
|
} elseif ($orientation == 'l' || $orientation == 'landscape') { |
216
|
|
|
$this->DefOrientation = 'L'; |
217
|
|
|
$this->w = $size[1]; |
218
|
|
|
$this->h = $size[0]; |
219
|
|
|
} else { |
220
|
|
|
$this->error('Incorrect orientation: ' . $orientation); |
221
|
|
|
} |
222
|
|
|
$this->CurOrientation = $this->DefOrientation; |
223
|
|
|
$this->wPt = $this->w * $this->k; |
224
|
|
|
$this->hPt = $this->h * $this->k; |
225
|
|
|
|
226
|
|
|
// set some default values |
227
|
|
|
// - no page rotation |
228
|
|
|
// - page margins 1cm |
229
|
|
|
// - interior cell margin 1mm |
230
|
|
|
// - line width 0.2mm |
231
|
|
|
// - automatic page break |
232
|
|
|
// - default display mode |
233
|
|
|
// - enable compression |
234
|
|
|
// - PDF version 1.3 |
235
|
|
|
$this->CurRotation = 0; |
236
|
|
|
|
237
|
|
|
$margin = 28.35 / $this->k; |
238
|
|
|
$this->setMargins($margin, $margin); |
239
|
|
|
$this->cMargin = $margin / 10; |
240
|
|
|
$this->LineWidth = 0.567 / $this->k; |
241
|
|
|
$this->setAutoPageBreak(true, 2 * $margin); |
242
|
|
|
$this->setDisplayMode('default'); |
243
|
|
|
$this->setCompression(true); |
244
|
|
|
$this->PDFVersion = '1.3'; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Defines the left, top and right margins. |
249
|
|
|
* By default, they equal 1 cm. Call this method to change them. |
250
|
|
|
* @param float $left Left margin. |
251
|
|
|
* @param float $top Top margin. |
252
|
|
|
* @param float $right Right margin. Default value is the left one. |
253
|
|
|
*/ |
254
|
|
|
public function setMargins(float $left, float $top, ?float $right = null) : void |
255
|
|
|
{ |
256
|
|
|
// Set left, top and right margins |
257
|
|
|
$this->lMargin = $left; |
258
|
|
|
$this->tMargin = $top; |
259
|
|
|
if ($right === null) { |
260
|
|
|
$right = $left; |
261
|
|
|
} |
262
|
|
|
$this->rMargin = $right; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Defines the left margin. |
267
|
|
|
* The method can be called before creating the first page. |
268
|
|
|
* If the current X-position gets out of page, it is brought back to the margin. |
269
|
|
|
* @param float $margin Left margin. |
270
|
|
|
*/ |
271
|
|
|
public function setLeftMargin(float $margin) : void |
272
|
|
|
{ |
273
|
|
|
// Set left margin |
274
|
|
|
$this->lMargin = $margin; |
275
|
|
|
if ($this->page > 0 && $this->x < $margin) { |
276
|
|
|
$this->x = $margin; |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Defines the top margin. |
282
|
|
|
* The method can be called before creating the first page. |
283
|
|
|
* @param float $margin |
284
|
|
|
*/ |
285
|
|
|
public function setTopMargin(float $margin) : void |
286
|
|
|
{ |
287
|
|
|
// Set top margin |
288
|
|
|
$this->tMargin = $margin; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Defines the right margin. |
293
|
|
|
* The method can be called before creating the first page. |
294
|
|
|
* @param float $margin |
295
|
|
|
*/ |
296
|
|
|
public function setRightMargin(float $margin) : void |
297
|
|
|
{ |
298
|
|
|
// Set right margin |
299
|
|
|
$this->rMargin = $margin; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Enables or disables the automatic page breaking mode. |
304
|
|
|
* When enabling, the second parameter is the distance from the bottom of the page |
305
|
|
|
* that defines the triggering limit. |
306
|
|
|
* By default, the mode is on and the margin is 2 cm. |
307
|
|
|
* @param bool $auto indicating if mode should be on or off. |
308
|
|
|
* @param float $margin Distance from the bottom of the page. |
309
|
|
|
*/ |
310
|
|
|
public function setAutoPageBreak(bool $auto, float $margin = 0) : void |
311
|
|
|
{ |
312
|
|
|
// Set auto page break mode and triggering margin |
313
|
|
|
$this->AutoPageBreak = $auto; |
314
|
|
|
$this->bMargin = $margin; |
315
|
|
|
$this->PageBreakTrigger = $this->h - $margin; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Defines the way the document is to be displayed by the viewer. |
320
|
|
|
* The zoom level can be set: <br/> |
321
|
|
|
* pages can be displayed <ul> |
322
|
|
|
* <li> entirely on screen </li> |
323
|
|
|
* <li> occupy the full width of the window </li> |
324
|
|
|
* <li> use real size </li> |
325
|
|
|
* <li> be scaled by a specific zooming factor </li> |
326
|
|
|
* <li> or use viewer default (configured in the Preferences menu of Adobe Reader). </li></ul> |
327
|
|
|
* The page layout can be specified too: <ul> |
328
|
|
|
* <li> single at once </li> |
329
|
|
|
* <li> continuous display </li> |
330
|
|
|
* <li> two columns </li> |
331
|
|
|
* <li> or viewer default. </li></ul> |
332
|
|
|
* <b>Note: this settings are ignored when displaying the PDF inside of a browser!</b> |
333
|
|
|
* @param string|float $zoom The zoom to use. <br/> |
334
|
|
|
* It can be one of the following string values: <ul> |
335
|
|
|
* <li> 'fullpage': displays the entire page on screen </li> |
336
|
|
|
* <li> 'fullwidth': uses maximum width of window </li> |
337
|
|
|
* <li> 'real': uses real size (equivalent to 100% zoom) </li> |
338
|
|
|
* <li> 'default': uses viewer default mode </li> |
339
|
|
|
* <li> or a number indicating the zooming factor to use. </li></ul> |
340
|
|
|
* @param string $layout The page layout. Possible values are: <ul> |
341
|
|
|
* <li> 'single': displays one page at once </li> |
342
|
|
|
* <li> 'continuous': displays pages continuously </li> |
343
|
|
|
* <li> 'two': displays two pages on two columns </li> |
344
|
|
|
* <li> 'defaul't: uses viewer default mode </li></ul> |
345
|
|
|
* Default value is default. |
346
|
|
|
*/ |
347
|
|
|
public function setDisplayMode($zoom, string $layout = 'default') : void |
348
|
|
|
{ |
349
|
|
|
// Set display mode in viewer |
350
|
|
|
if ($zoom == 'fullpage' || $zoom == 'fullwidth' || $zoom == 'real' || $zoom == 'default' || !is_string($zoom)) { |
351
|
|
|
$this->ZoomMode = $zoom; |
352
|
|
|
} else { |
353
|
|
|
$this->error('Incorrect zoom display mode: ' . $zoom); |
354
|
|
|
} |
355
|
|
|
if ($layout == 'single' || $layout == 'continuous' || $layout == 'two' || $layout == 'default') { |
356
|
|
|
$this->LayoutMode = $layout; |
357
|
|
|
} else { |
358
|
|
|
$this->error('Incorrect layout display mode: ' . $layout); |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Activates or deactivates page compression. |
364
|
|
|
* When activated, the internal representation of each page is compressed, which leads to |
365
|
|
|
* a compression ratio of about 2 for the resulting document. |
366
|
|
|
* Compression is on by default. <br/> |
367
|
|
|
* <br/> |
368
|
|
|
* <b>Note: the Zlib extension is required for this feature. If not present, compression will be turned off.</b> |
369
|
|
|
* @param bool $compress |
370
|
|
|
*/ |
371
|
|
|
public function setCompression(bool $compress) : void |
372
|
|
|
{ |
373
|
|
|
// Set page compression |
374
|
|
|
if (function_exists('gzcompress')) { |
375
|
|
|
$this->compress = $compress; |
376
|
|
|
} else { |
377
|
|
|
$this->compress = false; |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Defines the title of the document. |
383
|
|
|
* @param string $title The title. |
384
|
|
|
* @param bool $isUTF8 Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: true. |
385
|
|
|
*/ |
386
|
|
|
public function setTitle(string $title, bool $isUTF8 = true) : void |
387
|
|
|
{ |
388
|
|
|
// Title of document |
389
|
|
|
$this->metadata['Title'] = $isUTF8 ? $title : utf8_encode($title); |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Defines the author of the document. |
394
|
|
|
* @param string $author |
395
|
|
|
* @param bool $isUTF8 Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: true. |
396
|
|
|
*/ |
397
|
|
|
public function setAuthor(string $author, bool $isUTF8 = true) : void |
398
|
|
|
{ |
399
|
|
|
// Author of document |
400
|
|
|
$this->metadata['Author'] = $isUTF8 ? $author : utf8_encode($author); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Defines the subject of the document. |
405
|
|
|
* @param string $subject |
406
|
|
|
* @param bool $isUTF8 Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: true. |
407
|
|
|
*/ |
408
|
|
|
public function setSubject(string $subject, bool $isUTF8 = true) : void |
409
|
|
|
{ |
410
|
|
|
// Subject of document |
411
|
|
|
$this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode($subject); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. |
416
|
|
|
* @param string $keywords |
417
|
|
|
* @param bool $isUTF8 Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: true. |
418
|
|
|
*/ |
419
|
|
|
public function setKeywords(string $keywords, bool $isUTF8 = true) : void |
420
|
|
|
{ |
421
|
|
|
// Keywords of document |
422
|
|
|
$this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode($keywords); |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Defines the creator of the document. This is typically the name of the application that generates the PDF. |
427
|
|
|
* @param string $creator |
428
|
|
|
* @param bool $isUTF8 Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: true. |
429
|
|
|
*/ |
430
|
|
|
public function setCreator(string $creator, bool $isUTF8 = true) : void |
431
|
|
|
{ |
432
|
|
|
// Creator of document |
433
|
|
|
$this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode($creator); |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Defines an alias for the total number of pages. It will be substituted as the document is closed. |
438
|
|
|
* @param string $alias The alias. Default value: {nb}. |
439
|
|
|
*/ |
440
|
|
|
public function aliasNbPages(string $alias = '{nb}') : void |
441
|
|
|
{ |
442
|
|
|
// Define an alias for total number of pages |
443
|
|
|
$this->AliasNbPages = $alias; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* This method is automatically called in case of a fatal error. |
448
|
|
|
* It simply throws an exception with the provided message. |
449
|
|
|
* An inherited class may override it to customize the error handling but |
450
|
|
|
* the method should never return, otherwise the resulting document would probably be invalid. |
451
|
|
|
* @param string $msg The error message. |
452
|
|
|
* @throws \Exception |
453
|
|
|
*/ |
454
|
|
|
public function error(string $msg) : void |
455
|
|
|
{ |
456
|
|
|
// Fatal error |
457
|
|
|
throw new \Exception('FPDF error: ' . $msg); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Terminates the PDF document. |
462
|
|
|
* It is not necessary to call this method explicitly because Output() does it |
463
|
|
|
* automatically. If the document contains no page, AddPage() is called to prevent |
464
|
|
|
* from getting an invalid document. |
465
|
|
|
*/ |
466
|
|
|
public function close() : void |
467
|
|
|
{ |
468
|
|
|
// Terminate document |
469
|
|
|
if ($this->state == 3) { |
470
|
|
|
return; |
471
|
|
|
} |
472
|
|
|
if ($this->page == 0) { |
473
|
|
|
$this->addPage(); |
474
|
|
|
} |
475
|
|
|
// Page footer |
476
|
|
|
$this->InFooter = true; |
477
|
|
|
$this->footer(); |
478
|
|
|
$this->InFooter = false; |
479
|
|
|
// Close page |
480
|
|
|
$this->endPage(); |
481
|
|
|
// Close document |
482
|
|
|
$this->endDoc(); |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* Adds a new page to the document. |
487
|
|
|
* If a page is already present, the Footer() method is called first to output the |
488
|
|
|
* footer. Then the page is added, the current position set to the top-left corner |
489
|
|
|
* according to the left and top margins, and Header() is called to display the header. |
490
|
|
|
* The font which was set before calling is automatically restored. There is no need |
491
|
|
|
* to call SetFont() again if you want to continue with the same font. The same is |
492
|
|
|
* true for colors and line width. |
493
|
|
|
* The origin of the Y-position system is at the top-left corner and increasing |
494
|
|
|
* Y-positions go downwards. |
495
|
|
|
* @param string $orientation Default page orientation. <br/> |
496
|
|
|
* Possible values are (case insensitive): <ul> |
497
|
|
|
* <li> 'P' or 'Portrait' </li> |
498
|
|
|
* <li> 'L' or 'Landscape' </li></ul> |
499
|
|
|
* Default value is 'P'. <br/> |
500
|
|
|
* @param string|array $size The size used for pages. <br/> |
501
|
|
|
* It can be either one of the following values (case insensitive): <ul> |
502
|
|
|
* <li> 'A3' </li> |
503
|
|
|
* <li> 'A4' </li> |
504
|
|
|
* <li> 'A5' </li> |
505
|
|
|
* <li> 'Letter' </li> |
506
|
|
|
* <li> 'Legal' </li></ul> |
507
|
|
|
* or an array containing the width and the height (expressed in the unit given by unit). <br/> |
508
|
|
|
* Default value is 'A4'. <br/> |
509
|
|
|
* @param int $rotation Angle by which to rotate the page. <br/> |
510
|
|
|
* It must be a multiple of 90; positive values mean clockwise rotation. </br> |
511
|
|
|
* The default value is 0. |
512
|
|
|
*/ |
513
|
|
|
public function addPage(string $orientation = '', $size = '', int $rotation = 0) : void |
514
|
|
|
{ |
515
|
|
|
// Start a new page |
516
|
|
|
if ($this->state == 3) { |
517
|
|
|
$this->error('The document is closed'); |
518
|
|
|
} |
519
|
|
|
$family = $this->FontFamily; |
520
|
|
|
$style = $this->FontStyle . ($this->underline ? 'U' : ''); |
521
|
|
|
$fontsize = $this->FontSizePt; |
522
|
|
|
$lw = $this->LineWidth; |
523
|
|
|
$dc = $this->DrawColor; |
524
|
|
|
$fc = $this->FillColor; |
525
|
|
|
$tc = $this->TextColor; |
526
|
|
|
$cf = $this->ColorFlag; |
527
|
|
|
if ($this->page > 0) { |
528
|
|
|
// Page footer |
529
|
|
|
$this->InFooter = true; |
530
|
|
|
$this->footer(); |
531
|
|
|
$this->InFooter = false; |
532
|
|
|
// Close page |
533
|
|
|
$this->endPage(); |
534
|
|
|
} |
535
|
|
|
// Start new page |
536
|
|
|
$this->beginPage($orientation, $size, $rotation); |
537
|
|
|
// Set line cap style to square |
538
|
|
|
$this->out('2 J'); |
539
|
|
|
// Set line width |
540
|
|
|
$this->LineWidth = $lw; |
541
|
|
|
$this->out(sprintf('%.2F w', $lw * $this->k)); |
542
|
|
|
// Set font |
543
|
|
|
if ($family) { |
544
|
|
|
$this->setFont($family, $style, $fontsize); |
545
|
|
|
} |
546
|
|
|
// Set colors |
547
|
|
|
$this->DrawColor = $dc; |
548
|
|
|
if ($dc != '0 G') { |
549
|
|
|
$this->out($dc); |
550
|
|
|
} |
551
|
|
|
$this->FillColor = $fc; |
552
|
|
|
if ($fc != '0 g') { |
553
|
|
|
$this->out($fc); |
554
|
|
|
} |
555
|
|
|
$this->TextColor = $tc; |
556
|
|
|
$this->ColorFlag = $cf; |
557
|
|
|
// Page header |
558
|
|
|
$this->InHeader = true; |
559
|
|
|
$this->header(); |
560
|
|
|
$this->InHeader = false; |
561
|
|
|
// Restore line width |
562
|
|
|
if ($this->LineWidth != $lw) { |
563
|
|
|
$this->LineWidth = $lw; |
564
|
|
|
$this->out(sprintf('%.2F w', $lw * $this->k)); |
565
|
|
|
} |
566
|
|
|
// Restore font |
567
|
|
|
if ($family) { |
568
|
|
|
$this->setFont($family, $style, $fontsize); |
569
|
|
|
} |
570
|
|
|
// Restore colors |
571
|
|
|
if ($this->DrawColor != $dc) { |
572
|
|
|
$this->DrawColor = $dc; |
573
|
|
|
$this->out($dc); |
574
|
|
|
} |
575
|
|
|
if ($this->FillColor != $fc) { |
576
|
|
|
$this->FillColor = $fc; |
577
|
|
|
$this->out($fc); |
578
|
|
|
} |
579
|
|
|
$this->TextColor = $tc; |
580
|
|
|
$this->ColorFlag = $cf; |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* This method is used to render the page header. |
585
|
|
|
* It is automatically called by AddPage() and should not be called directly by the |
586
|
|
|
* application. The implementation in FPDF is empty, so you have to subclass it and |
587
|
|
|
* override the method if you want a specific processing. |
588
|
|
|
*/ |
589
|
|
|
public function header() : void |
590
|
|
|
{ |
591
|
|
|
// To be implemented in your own inherited class |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* This method is used to render the page footer. |
596
|
|
|
* It is automatically called by AddPage() and Close() and should not be called |
597
|
|
|
* directly by the application. The implementation in FPDF is empty, so you have to |
598
|
|
|
* subclass it and override the method if you want a specific processing. |
599
|
|
|
*/ |
600
|
|
|
public function footer() : void |
601
|
|
|
{ |
602
|
|
|
// To be implemented in your own inherited class |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Returns the current page number. |
607
|
|
|
* @return int |
608
|
|
|
*/ |
609
|
|
|
public function pageNo() : int |
610
|
|
|
{ |
611
|
|
|
// Get current page number |
612
|
|
|
return $this->page; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Defines the color used for all drawing operations (lines, rectangles and cell borders). |
617
|
|
|
* It can be expressed in RGB components or gray scale. The method can be called before |
618
|
|
|
* the first page is created and the value is retained from page to page. |
619
|
|
|
* @param int $r If g and b are given, red component; if not, indicates the gray level. Value between 0 and 255. |
620
|
|
|
* @param int $g Green component (between 0 and 255). |
621
|
|
|
* @param int $b Blue component (between 0 and 255). |
622
|
|
|
*/ |
623
|
|
|
public function setDrawColor(int $r, ?int $g = null, ?int $b = null) : void |
624
|
|
|
{ |
625
|
|
|
// Set color for all stroking operations |
626
|
|
|
if (($r === 0 && $g === 0 && $b === 0) || $g === null) { |
627
|
|
|
$this->DrawColor = sprintf('%.3F G', $r / 255); |
628
|
|
|
} else { |
629
|
|
|
$this->DrawColor = sprintf('%.3F %.3F %.3F RG', $r / 255, $g / 255, $b / 255); |
630
|
|
|
} |
631
|
|
|
if ($this->page > 0) { |
632
|
|
|
$this->out($this->DrawColor); |
633
|
|
|
} |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
/** |
637
|
|
|
* Defines the color used for all filling operations (filled rectangles and cell backgrounds). |
638
|
|
|
* It can be expressed in RGB components or gray scale. The method can be called before the |
639
|
|
|
* first page is created and the value is retained from page to page. |
640
|
|
|
* @param int $r If g and b are given, red component; if not, indicates the gray level. Value between 0 and 255. |
641
|
|
|
* @param int $g Green component (between 0 and 255). |
642
|
|
|
* @param int $b Blue component (between 0 and 255). |
643
|
|
|
*/ |
644
|
|
|
public function setFillColor(int $r, ?int $g = null, ?int $b = null) : void |
645
|
|
|
{ |
646
|
|
|
// Set color for all filling operations |
647
|
|
|
if (($r === 0 && $g === 0 && $b === 0) || $g === null) { |
648
|
|
|
$this->FillColor = sprintf('%.3F g', $r / 255); |
649
|
|
|
} else { |
650
|
|
|
$this->FillColor = sprintf('%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255); |
651
|
|
|
} |
652
|
|
|
$this->ColorFlag = ($this->FillColor != $this->TextColor); |
653
|
|
|
if ($this->page > 0) { |
654
|
|
|
$this->out($this->FillColor); |
655
|
|
|
} |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
/** |
659
|
|
|
* Defines the color used for text. |
660
|
|
|
* It can be expressed in RGB components or gray scale. The method can be called before the |
661
|
|
|
* first page is created and the value is retained from page to page. |
662
|
|
|
* @param int $r If g and b are given, red component; if not, indicates the gray level. Value between 0 and 255. |
663
|
|
|
* @param int $g Green component (between 0 and 255). |
664
|
|
|
* @param int $b Blue component (between 0 and 255). |
665
|
|
|
*/ |
666
|
|
|
public function setTextColor(int $r, ?int $g = null, ?int $b = null) : void |
667
|
|
|
{ |
668
|
|
|
// Set color for text |
669
|
|
|
if (($r === 0 && $g === 0 && $b === 0) || $g === null) { |
670
|
|
|
$this->TextColor = sprintf('%.3F g', $r / 255); |
671
|
|
|
} else { |
672
|
|
|
$this->TextColor = sprintf('%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255); |
673
|
|
|
} |
674
|
|
|
$this->ColorFlag = ($this->FillColor != $this->TextColor); |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
/** |
678
|
|
|
* Returns the length of a string in user unit for current Font. |
679
|
|
|
* A font must be selected. |
680
|
|
|
* @param string $s The string whose length is to be computed. |
681
|
|
|
* @return float |
682
|
|
|
*/ |
683
|
|
|
public function getStringWidth(string $s) : float |
684
|
|
|
{ |
685
|
|
|
// Get width of a string in the current font |
686
|
|
|
$s = $this->conv((string) $s); |
687
|
|
|
$cw = &$this->CurrentFont['cw']; |
688
|
|
|
$w = 0; |
689
|
|
|
$l = strlen($s); |
690
|
|
|
for ($i = 0; $i < $l; $i++) { |
691
|
|
|
$w += $cw[$s[$i]]; |
692
|
|
|
} |
693
|
|
|
return $w * $this->FontSize / 1000; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Defines the line width. |
698
|
|
|
* By default, the value equals 0.2 mm. The method can be called before the first |
699
|
|
|
* page is created and the value is retained from page to page. |
700
|
|
|
* @param float $width |
701
|
|
|
*/ |
702
|
|
|
public function setLineWidth(float $width) : void |
703
|
|
|
{ |
704
|
|
|
// Set line width |
705
|
|
|
$this->LineWidth = $width; |
706
|
|
|
if ($this->page > 0) { |
707
|
|
|
$this->out(sprintf('%.2F w', $width * $this->k)); |
708
|
|
|
} |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
/** |
712
|
|
|
* Draws a line between two points. |
713
|
|
|
* The X/Y-positions refer to the top left corner of the page. |
714
|
|
|
* Set margins are NOT taken into account. |
715
|
|
|
* @param float $x1 X-position upper left corner |
716
|
|
|
* @param float $y1 Y-position upper left corner |
717
|
|
|
* @param float $x2 X-position lower right corner |
718
|
|
|
* @param float $y2 Y-position lower right corner |
719
|
|
|
*/ |
720
|
|
|
public function line(float $x1, float $y1, float $x2, float $y2) : void |
721
|
|
|
{ |
722
|
|
|
// Draw a line |
723
|
|
|
$x1 *= $this->k; |
724
|
|
|
$x2 *= $this->k; |
725
|
|
|
$y1 = ($this->h - $y1) * $this->k; |
726
|
|
|
$y2 = ($this->h - $y2) * $this->k; |
727
|
|
|
$this->out(sprintf('%.2F %.2F m %.2F %.2F l S', $x1, $y1, $x2, $y2)); |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Outputs a rectangle. |
732
|
|
|
* It can be drawn (border only), filled (with no border) or both. |
733
|
|
|
* The X/Y-position refer to the top left corner of the page. |
734
|
|
|
* Set margins are NOT taken into account. |
735
|
|
|
* @param float $x X-position upper left corner |
736
|
|
|
* @param float $y Y-position upper left corner |
737
|
|
|
* @param float $w Width |
738
|
|
|
* @param float $h Height |
739
|
|
|
* @param string $style Style of rendering. <br/> |
740
|
|
|
* Possible values are: <ul> |
741
|
|
|
* <li> 'D' or empty string: draw the shape. This is the default value. </li> |
742
|
|
|
* <li> 'F': fill. </li> |
743
|
|
|
* <li> 'DF' or 'FD': draw the shape and fill. </li></ul> |
744
|
|
|
*/ |
745
|
|
|
public function rect(float $x, float $y, float $w, float $h, string $style = '') : void |
746
|
|
|
{ |
747
|
|
|
// Draw a rectangle |
748
|
|
|
if ($style == 'F') { |
749
|
|
|
$op = 'f'; |
750
|
|
|
} elseif ($style == 'FD' || $style == 'DF') { |
751
|
|
|
$op = 'B'; |
752
|
|
|
} else { |
753
|
|
|
$op = 'S'; |
754
|
|
|
} |
755
|
|
|
$x *= $this->k; |
756
|
|
|
$w *= $this->k; |
757
|
|
|
$y = ($this->h - $y) * $this->k; |
758
|
|
|
$h *= -$this->k; |
759
|
|
|
$this->out(sprintf('%.2F %.2F %.2F %.2F re %s', $x, $y, $w, $h, $op)); |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
/** |
763
|
|
|
* Imports a TrueType, OpenType or Type1 font and makes it available. |
764
|
|
|
* It is necessary to generate a font definition file first with the MakeFont utility. |
765
|
|
|
* The definition file (and the font file itself when embedding) must be present in |
766
|
|
|
* the font directory. If it is not found, the error "Could not include font definition file" |
767
|
|
|
* is raised. |
768
|
|
|
* @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. |
769
|
|
|
* @param string $style Font style. <br/> |
770
|
|
|
* Possible values are (case insensitive): <ul> |
771
|
|
|
* <li> empty string: regular </li> |
772
|
|
|
* <li> 'B': bold </li> |
773
|
|
|
* <li> 'I': italic </li> |
774
|
|
|
* <li> 'BI' or 'IB': bold italic </li></ul> |
775
|
|
|
* The default value is regular. <br/> |
776
|
|
|
* @param string $file The font definition file. <br/> |
777
|
|
|
* By default, the name is built from the family and style, in lower case with no space. |
778
|
|
|
*/ |
779
|
|
|
public function addFont(string $family, string $style = '', string $file = '') : void |
780
|
|
|
{ |
781
|
|
|
// Add a TrueType, OpenType or Type1 font |
782
|
|
|
$family = strtolower($family); |
783
|
|
|
if ($file == '') { |
784
|
|
|
$file = str_replace(' ', '', $family) . strtolower($style) . '.php'; |
785
|
|
|
} |
786
|
|
|
$style = strtoupper($style); |
787
|
|
|
if ($style == 'IB') { |
788
|
|
|
$style = 'BI'; |
789
|
|
|
} |
790
|
|
|
$fontkey = $family . $style; |
791
|
|
|
if (isset($this->fonts[$fontkey])) { |
792
|
|
|
return; |
793
|
|
|
} |
794
|
|
|
$info = $this->loadFont($file); |
795
|
|
|
$info['i'] = count($this->fonts) + 1; |
796
|
|
|
if (!empty($info['file'])) { |
797
|
|
|
// Embedded font |
798
|
|
|
if ($info['type'] == 'TrueType') { |
799
|
|
|
$this->FontFiles[$info['file']] = array('length1'=>$info['originalsize']); |
800
|
|
|
} else { |
801
|
|
|
$this->FontFiles[$info['file']] = array('length1'=>$info['size1'], 'length2'=>$info['size2']); |
802
|
|
|
} |
803
|
|
|
} |
804
|
|
|
$this->fonts[$fontkey] = $info; |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
/** |
808
|
|
|
* Sets the font used to print character strings. |
809
|
|
|
* It is mandatory to call this method at least once before printing text or the |
810
|
|
|
* resulting document would not be valid. |
811
|
|
|
* The font can be either a standard one or a font added via the AddFont() method. |
812
|
|
|
* Standard fonts use the Windows encoding cp1252 (Western Europe). |
813
|
|
|
* The method can be called before the first page is created and the font is kept from page to page. |
814
|
|
|
* If you just wish to change the current font size, it is simpler to call SetFontSize().<br/> |
815
|
|
|
* |
816
|
|
|
* <b>Note:</b><br/> |
817
|
|
|
* the font definition files must be accessible. |
818
|
|
|
* They are searched successively in: <ul> |
819
|
|
|
* <li> The directory defined by the FPDF_FONTPATH constant (if this constant is defined) </li> |
820
|
|
|
* <li> The 'font' directory located in the same directory as fpdf.php (if it exists) </li> |
821
|
|
|
* <li> The directories accessible through include() </li></ul> |
822
|
|
|
* @param string $family Family font. <br/> |
823
|
|
|
* It can be either a name defined by AddFont() or one of the standard families (case insensitive): <ul> |
824
|
|
|
* <li> 'Courier' (fixed-width) </li> |
825
|
|
|
* <li> 'Helvetica' or 'Arial' (synonymous; sans serif) </li> |
826
|
|
|
* <li> 'Times' (serif) </li> |
827
|
|
|
* <li> 'Symbol' (symbolic) </li> |
828
|
|
|
* <li> 'ZapfDingbats' (symbolic)</li></ul> |
829
|
|
|
* It is also possible to pass an empty string. In that case, the current family is kept.<br/> |
830
|
|
|
* @param string $style Font style. <br> |
831
|
|
|
* ossible values are (case insensitive): <ul> |
832
|
|
|
* <li> empty string: regular </li> |
833
|
|
|
* <li> 'B': bold </li> |
834
|
|
|
* <li> 'I': italic </li> |
835
|
|
|
* <li> 'U': underline </li> |
836
|
|
|
* <li> or any combination. </li></ul> |
837
|
|
|
* The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats.<br/> |
838
|
|
|
* @param float $size Font size in points. <br/> |
839
|
|
|
* The default value is the current size. <br/> |
840
|
|
|
* If no size has been specified since the beginning of the document, the value taken is 12. |
841
|
|
|
*/ |
842
|
|
|
public function setFont(string $family, string $style = '', float $size = 0) : void |
843
|
|
|
{ |
844
|
|
|
// Select a font; size given in points |
845
|
|
|
if ($family == '') { |
846
|
|
|
$family = $this->FontFamily; |
847
|
|
|
} else { |
848
|
|
|
$family = strtolower($family); |
849
|
|
|
} |
850
|
|
|
$style = strtoupper($style); |
851
|
|
|
if (strpos($style, 'U') !== false) { |
852
|
|
|
$this->underline = true; |
853
|
|
|
$style = str_replace('U', '', $style); |
854
|
|
|
} else { |
855
|
|
|
$this->underline = false; |
856
|
|
|
} |
857
|
|
|
if ($style == 'IB') { |
858
|
|
|
$style = 'BI'; |
859
|
|
|
} |
860
|
|
|
if ($size == 0) { |
861
|
|
|
$size = $this->FontSizePt; |
862
|
|
|
} |
863
|
|
|
// Test if font is already selected |
864
|
|
|
if ($this->FontFamily == $family && $this->FontStyle == $style && $this->FontSizePt == $size) { |
865
|
|
|
return; |
866
|
|
|
} |
867
|
|
|
// Test if font is already loaded |
868
|
|
|
$fontkey = $family . $style; |
869
|
|
|
if (!isset($this->fonts[$fontkey])) { |
870
|
|
|
// Test if one of the core fonts |
871
|
|
|
if ($this->isCoreFont($family)) { |
872
|
|
|
if ($family == 'symbol' || $family == 'zapfdingbats') { |
873
|
|
|
$style = ''; |
874
|
|
|
} |
875
|
|
|
$fontkey = $family . $style; |
876
|
|
|
if (!isset($this->fonts[$fontkey])) { |
877
|
|
|
$this->addFont($family, $style); |
878
|
|
|
} |
879
|
|
|
} else { |
880
|
|
|
$this->error('Undefined font: ' . $family . ' ' . $style); |
881
|
|
|
} |
882
|
|
|
} |
883
|
|
|
// Select it |
884
|
|
|
$this->FontFamily = $family; |
885
|
|
|
$this->FontStyle = $style; |
886
|
|
|
$this->FontSizePt = $size; |
887
|
|
|
$this->FontSize = $size / $this->k; |
888
|
|
|
$this->CurrentFont = &$this->fonts[$fontkey]; |
889
|
|
|
if ($this->page > 0) { |
890
|
|
|
$this->out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); |
891
|
|
|
} |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
/** |
895
|
|
|
* Defines the size of the current font. |
896
|
|
|
* @param float $size The size (in points). |
897
|
|
|
*/ |
898
|
|
|
public function setFontSize(float $size) : void |
899
|
|
|
{ |
900
|
|
|
// Set font size in points |
901
|
|
|
if ($this->FontSizePt == $size) { |
902
|
|
|
return; |
903
|
|
|
} |
904
|
|
|
$this->FontSizePt = $size; |
905
|
|
|
$this->FontSize = $size / $this->k; |
906
|
|
|
if ($this->page > 0) { |
907
|
|
|
$this->out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); |
908
|
|
|
} |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* Check for core font |
913
|
|
|
* @param string $strFamily |
914
|
|
|
* @return bool |
915
|
|
|
*/ |
916
|
|
|
protected function isCoreFont(string &$strFamily) : bool |
917
|
|
|
{ |
918
|
|
|
$strFamily = strtolower($strFamily); |
919
|
|
|
if ($strFamily == 'arial') { |
920
|
|
|
$strFamily = 'helvetica'; |
921
|
|
|
} |
922
|
|
|
// Core fonts |
923
|
|
|
$aCoreFonts = array('courier', 'helvetica', 'times', 'symbol', 'zapfdingbats'); |
924
|
|
|
return in_array($strFamily, $aCoreFonts); |
925
|
|
|
} |
926
|
|
|
|
927
|
|
|
/** |
928
|
|
|
* Creates a new internal link and returns its identifier. |
929
|
|
|
* An internal link is a clickable area which directs to another place within the document. |
930
|
|
|
* The identifier can then be passed to Cell(), Write(), Image() or Link(). |
931
|
|
|
* The destination is defined with SetLink(). |
932
|
|
|
* @return int |
933
|
|
|
*/ |
934
|
|
|
public function addLink() : int |
935
|
|
|
{ |
936
|
|
|
// Create a new internal link |
937
|
|
|
$n = count($this->links) + 1; |
938
|
|
|
$this->links[$n] = array(0, 0); |
939
|
|
|
return $n; |
940
|
|
|
} |
941
|
|
|
|
942
|
|
|
/** |
943
|
|
|
* Defines the page and position a link points to. |
944
|
|
|
* @param int $link The link identifier created by AddLink(). |
945
|
|
|
* @param float $y Y-position of target position; -1 indicates the current position. The default value is 0 (top of page). |
946
|
|
|
* @param int $page Number of target page; -1 indicates the current page. This is the default value. |
947
|
|
|
*/ |
948
|
|
|
public function setLink(int $link, float $y = 0, int $page = -1) : void |
949
|
|
|
{ |
950
|
|
|
// Set destination of internal link |
951
|
|
|
if ($y == -1) { |
952
|
|
|
$y = $this->y; |
953
|
|
|
} |
954
|
|
|
if ($page == -1) { |
955
|
|
|
$page = $this->page; |
956
|
|
|
} |
957
|
|
|
$this->links[$link] = array($page, $y); |
958
|
|
|
} |
959
|
|
|
|
960
|
|
|
/** |
961
|
|
|
* Puts a link on a rectangular area of the page. |
962
|
|
|
* Text or image links are generally put via Cell(), Write() or Image(), but this |
963
|
|
|
* method can be useful for instance to define a clickable area inside an image. |
964
|
|
|
* Target can be an external URL or an internal link ID created and specified by AddLink()/SetLink() |
965
|
|
|
* @param float $x X-position |
966
|
|
|
* @param float $y Y-position |
967
|
|
|
* @param float $w Width |
968
|
|
|
* @param float $h Height |
969
|
|
|
* @param string|int $link URL or link-ID |
970
|
|
|
*/ |
971
|
|
|
public function link(float $x, float $y, float $w, float $h, $link) : void |
972
|
|
|
{ |
973
|
|
|
// Put a link on the page |
974
|
|
|
$this->PageLinks[$this->page][] = array($x * $this->k, $this->hPt - $y * $this->k, $w * $this->k, $h * $this->k, $link); |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
/** |
978
|
|
|
* Prints a character string. |
979
|
|
|
* The origin is on the left of the first character, on the baseline. |
980
|
|
|
* This method allows to place a string precisely on the page, but it is usually |
981
|
|
|
* easier to use Cell(), MultiCell() or Write() which are the standard methods |
982
|
|
|
* to print text. |
983
|
|
|
* @param float $x X-position |
984
|
|
|
* @param float $y Y-position |
985
|
|
|
* @param string $txt String to print. |
986
|
|
|
*/ |
987
|
|
|
public function text(float $x, float $y, string $txt) : void |
988
|
|
|
{ |
989
|
|
|
// Output a string |
990
|
|
|
if (!isset($this->CurrentFont)) { |
991
|
|
|
$this->error('No font has been set'); |
992
|
|
|
} |
993
|
|
|
$txt = $this->conv($txt); |
994
|
|
|
$s = sprintf('BT %.2F %.2F Td (%s) Tj ET', $x * $this->k, ($this->h - $y) * $this->k, $this->escape($txt)); |
995
|
|
|
if ($this->underline && $txt != '') { |
996
|
|
|
$s .= ' ' . $this->doUnderline($x, $y, $txt); |
997
|
|
|
} |
998
|
|
|
if ($this->ColorFlag) { |
999
|
|
|
$s = 'q ' . $this->TextColor . ' ' . $s . ' Q'; |
1000
|
|
|
} |
1001
|
|
|
$this->out($s); |
1002
|
|
|
} |
1003
|
|
|
|
1004
|
|
|
/** |
1005
|
|
|
* Whenever a page break condition is met, the method is called, and the break is |
1006
|
|
|
* issued or not depending on the returned value. |
1007
|
|
|
* The default implementation returns a value according to the mode selected by |
1008
|
|
|
* SetAutoPageBreak(). |
1009
|
|
|
* This method is called automatically and should not be called directly by the application.<br/> |
1010
|
|
|
* <br/> |
1011
|
|
|
* For usage in derived classes see example at http://www.fpdf.org/en/doc/acceptpagebreak.htm. |
1012
|
|
|
* @link http://www.fpdf.org/en/doc/acceptpagebreak.htm |
1013
|
|
|
* @return bool |
1014
|
|
|
*/ |
1015
|
|
|
public function acceptPageBreak() : bool |
1016
|
|
|
{ |
1017
|
|
|
// Accept automatic page break or not |
1018
|
|
|
return $this->AutoPageBreak; |
1019
|
|
|
} |
1020
|
|
|
|
1021
|
|
|
/** |
1022
|
|
|
* Prints a cell (rectangular area) with optional borders, background color and character string. |
1023
|
|
|
* The upper-left corner of the cell corresponds to the current position. The text can be |
1024
|
|
|
* aligned or centered. After the call, the current position moves to the right or to the next line. |
1025
|
|
|
* It is possible to put a link on the text. |
1026
|
|
|
* If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done |
1027
|
|
|
* before outputting. |
1028
|
|
|
* @param float $w Cell width. If 0, the cell extends up to the right margin. |
1029
|
|
|
* @param float $h Cell height. Default value: 0. |
1030
|
|
|
* @param string $txt String to print. Default value: empty string. |
1031
|
|
|
* @param int|string $border Indicates if borders must be drawn around the cell. <br/> |
1032
|
|
|
* The value can be either a number: <ul> |
1033
|
|
|
* <li>0: no border </li> |
1034
|
|
|
* <li>1: frame </li></ul> |
1035
|
|
|
* or a string containing some or all of the following characters (in any order): <ul> |
1036
|
|
|
* <li> 'L': left </li> |
1037
|
|
|
* <li> 'T': top </li> |
1038
|
|
|
* <li> 'R': right </li> |
1039
|
|
|
* <li> 'B': bottom </li></ul> |
1040
|
|
|
* Default value: 0. <br/> |
1041
|
|
|
* @param float $ln Indicates where the current position should go after the call. <br/> |
1042
|
|
|
* Possible values are: <ul> |
1043
|
|
|
* <li> 0: to the right </li> |
1044
|
|
|
* <li> 1: to the beginning of the next line </li> |
1045
|
|
|
* <li> 2: below </li></ul> |
1046
|
|
|
* Putting 1 is equivalent to putting 0 and calling Ln() just after. <br/> |
1047
|
|
|
* Default value: 0. <br/> |
1048
|
|
|
* @param string $align Allows to center or align the text. <br/> |
1049
|
|
|
* Possible values are: <ul> |
1050
|
|
|
* <li> 'L' or empty string: left align (default value) </li> |
1051
|
|
|
* <li> 'C': center </li> |
1052
|
|
|
* <li> 'R': right align </li></ul> |
1053
|
|
|
* @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). <br/> |
1054
|
|
|
* If set to true, current FillColor is used for the background. <br/> |
1055
|
|
|
* Default value: false. <br/> |
1056
|
|
|
* @param string|int $link URL or identifier for internal link created by AddLink(). |
1057
|
|
|
*/ |
1058
|
|
|
public function cell(float $w, float $h = 0, string $txt = '', $border = 0, float $ln = 0, $align = '', $fill = false, $link = '') : void |
1059
|
|
|
{ |
1060
|
|
|
$txt = $this->conv($txt); |
1061
|
|
|
$this->internalCell($w, $h, $txt, $border, $ln, $align, $fill, $link); |
1062
|
|
|
} |
1063
|
|
|
|
1064
|
|
|
|
1065
|
|
|
/** |
1066
|
|
|
* The internal function is necessaray for calls from multiCell() and write() where the |
1067
|
|
|
* text is already converted from UTF-8 for some width calculations. Multiple conversion |
1068
|
|
|
* leads to undetermined characters. |
1069
|
|
|
* @see FPDF::cell() |
1070
|
|
|
* @param float $w |
1071
|
|
|
* @param float $h |
1072
|
|
|
* @param string $txt |
1073
|
|
|
* @param int|string $border |
1074
|
|
|
* @param float $ln |
1075
|
|
|
* @param string $align |
1076
|
|
|
* @param boolean $fill |
1077
|
|
|
* @param string|int $link |
1078
|
|
|
*/ |
1079
|
|
|
private function internalCell(float $w, float $h = 0, string $txt = '', $border = 0, float $ln = 0, $align = '', $fill = false, $link = '') : void |
1080
|
|
|
{ |
1081
|
|
|
// Output a cell |
1082
|
|
|
$k = $this->k; |
1083
|
|
|
if ($this->y + $h > $this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->acceptPageBreak()) { |
1084
|
|
|
// Automatic page break |
1085
|
|
|
$x = $this->x; |
1086
|
|
|
$ws = $this->ws; |
1087
|
|
|
if ($ws > 0) { |
1088
|
|
|
$this->ws = 0; |
1089
|
|
|
$this->out('0 Tw'); |
1090
|
|
|
} |
1091
|
|
|
$this->addPage($this->CurOrientation, $this->CurPageSize, $this->CurRotation); |
1092
|
|
|
$this->x = $x; |
1093
|
|
|
if ($ws > 0) { |
1094
|
|
|
$this->ws = $ws; |
1095
|
|
|
$this->out(sprintf('%.3F Tw', $ws * $k)); |
1096
|
|
|
} |
1097
|
|
|
} |
1098
|
|
|
if ($w == 0) { |
1099
|
|
|
$w = $this->w - $this->rMargin - $this->x; |
1100
|
|
|
} |
1101
|
|
|
$s = ''; |
1102
|
|
|
if ($fill || $border == 1) { |
1103
|
|
|
if ($fill) { |
1104
|
|
|
$op = ($border == 1) ? 'B' : 'f'; |
1105
|
|
|
} else { |
1106
|
|
|
$op = 'S'; |
1107
|
|
|
} |
1108
|
|
|
$s = sprintf('%.2F %.2F %.2F %.2F re %s ', $this->x * $k, ($this->h - $this->y) * $k, $w * $k, -$h * $k, $op); |
1109
|
|
|
} |
1110
|
|
|
if (is_string($border)) { |
1111
|
|
|
$x = $this->x; |
1112
|
|
|
$y = $this->y; |
1113
|
|
|
if (strpos($border, 'L') !== false) { |
1114
|
|
|
$s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x * $k, ($this->h - $y) * $k, $x * $k, ($this->h - ($y + $h)) * $k); |
1115
|
|
|
} |
1116
|
|
|
if (strpos($border, 'T') !== false) { |
1117
|
|
|
$s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x * $k, ($this->h - $y) * $k, ($x + $w) * $k, ($this->h - $y) * $k); |
1118
|
|
|
} |
1119
|
|
|
if (strpos($border, 'R') !== false) { |
1120
|
|
|
$s .= sprintf('%.2F %.2F m %.2F %.2F l S ', ($x + $w) * $k, ($this->h - $y) * $k, ($x + $w) * $k, ($this->h - ($y + $h)) * $k); |
1121
|
|
|
} |
1122
|
|
|
if (strpos($border, 'B') !== false) { |
1123
|
|
|
$s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x * $k, ($this->h - ($y + $h)) * $k, ($x + $w) * $k, ($this->h - ($y + $h)) * $k); |
1124
|
|
|
} |
1125
|
|
|
} |
1126
|
|
|
if ($txt !== '') { |
1127
|
|
|
if (!isset($this->CurrentFont)) { |
1128
|
|
|
$this->error('No font has been set'); |
1129
|
|
|
} |
1130
|
|
|
if ($align == 'R') { |
1131
|
|
|
$dx = $w - $this->cMargin - $this->getStringWidth($txt); |
1132
|
|
|
} elseif ($align == 'C') { |
1133
|
|
|
$dx = ($w - $this->getStringWidth($txt)) / 2; |
1134
|
|
|
} else { |
1135
|
|
|
$dx = $this->cMargin; |
1136
|
|
|
} |
1137
|
|
|
if ($this->ColorFlag) { |
1138
|
|
|
$s .= 'q ' . $this->TextColor . ' '; |
1139
|
|
|
} |
1140
|
|
|
$s .= sprintf('BT %.2F %.2F Td (%s) Tj ET', ($this->x + $dx) * $k, ($this->h - ($this->y + 0.5 * $h + 0.3 * $this->FontSize)) * $k, $this->escape($txt)); |
1141
|
|
|
if ($this->underline) { |
1142
|
|
|
$s .= ' ' . $this->doUnderline($this->x + $dx, $this->y + 0.5 * $h + 0.3 * $this->FontSize, $txt); |
1143
|
|
|
} |
1144
|
|
|
if ($this->ColorFlag) { |
1145
|
|
|
$s .= ' Q'; |
1146
|
|
|
} |
1147
|
|
|
if ($link) { |
1148
|
|
|
$this->link($this->x + $dx, $this->y + 0.5 * $h - 0.5 * $this->FontSize, $this->getStringWidth($txt), $this->FontSize, $link); |
1149
|
|
|
} |
1150
|
|
|
} |
1151
|
|
|
if ($s) { |
1152
|
|
|
$this->out($s); |
1153
|
|
|
} |
1154
|
|
|
$this->lasth = $h; |
1155
|
|
|
if ($ln > 0) { |
1156
|
|
|
// Go to next line |
1157
|
|
|
$this->y += $h; |
1158
|
|
|
if ($ln == 1) { |
1159
|
|
|
$this->x = $this->lMargin; |
1160
|
|
|
} |
1161
|
|
|
} else { |
1162
|
|
|
$this->x += $w; |
1163
|
|
|
} |
1164
|
|
|
} |
1165
|
|
|
|
1166
|
|
|
/** |
1167
|
|
|
* This method allows printing text with line breaks. |
1168
|
|
|
* They can be automatic (as soon as the text reaches the right border of the cell) or |
1169
|
|
|
* explicit (via the \n character). |
1170
|
|
|
* As many cells as necessary are output, one below the other. |
1171
|
|
|
* Text can be aligned, centered or justified. The cell block can be framed and the background painted. |
1172
|
|
|
* @param float $w Cell width. If 0, the cell extends up to the right margin. |
1173
|
|
|
* @param float $h Cell height. Default value: 0. |
1174
|
|
|
* @param string $txt String to print. Default value: empty string. |
1175
|
|
|
* @param int|string $border Indicates if borders must be drawn around the cell. <br/> |
1176
|
|
|
* The value can be either a number: <ul> |
1177
|
|
|
* <li>0: no border </li> |
1178
|
|
|
* <li>1: frame </li></ul> |
1179
|
|
|
* or a string containing some or all of the following characters (in any order): <ul> |
1180
|
|
|
* <li> 'L': left </li> |
1181
|
|
|
* <li> 'T': top </li> |
1182
|
|
|
* <li> 'R': right </li> |
1183
|
|
|
* <li> 'B': bottom </li></ul> |
1184
|
|
|
* Default value: 0. <br/> |
1185
|
|
|
* @param string $align Allows to center or align the text. <br/> |
1186
|
|
|
* Possible values are: <ul> |
1187
|
|
|
* <li> 'L' or empty string: left align (default value) </li> |
1188
|
|
|
* <li> 'C': center </li> |
1189
|
|
|
* <li> 'R': right align </li></ul> |
1190
|
|
|
* @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). <br/> |
1191
|
|
|
* If set to true, current FillColor is used for the background. <br/> |
1192
|
|
|
* Default value: false. |
1193
|
|
|
*/ |
1194
|
|
|
public function multiCell(float $w, float $h, string $txt, $border = 0, string $align = 'J', bool $fill = false) : void |
1195
|
|
|
{ |
1196
|
|
|
// Output text with automatic or explicit line breaks |
1197
|
|
|
if (!isset($this->CurrentFont)) { |
1198
|
|
|
$this->error('No font has been set'); |
1199
|
|
|
} |
1200
|
|
|
$cw = &$this->CurrentFont['cw']; |
1201
|
|
|
if ($w == 0) { |
1202
|
|
|
$w = $this->w - $this->rMargin - $this->x; |
1203
|
|
|
} |
1204
|
|
|
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize; |
1205
|
|
|
$s = $this->conv(str_replace("\r", '', $txt)); |
1206
|
|
|
$nb = strlen($s); |
1207
|
|
|
if ($nb > 0 && $s[$nb - 1] == "\n") { |
1208
|
|
|
$nb--; |
1209
|
|
|
} |
1210
|
|
|
$b = 0; |
1211
|
|
|
$b2 = ''; |
1212
|
|
|
if ($border) { |
1213
|
|
|
if ($border == 1) { |
1214
|
|
|
$border = 'LTRB'; |
1215
|
|
|
$b = 'LRT'; |
1216
|
|
|
$b2 = 'LR'; |
1217
|
|
|
} else { |
1218
|
|
|
$b2 = ''; |
1219
|
|
|
if (strpos($border, 'L') !== false) { |
1220
|
|
|
$b2 .= 'L'; |
1221
|
|
|
} |
1222
|
|
|
if (strpos($border, 'R') !== false) { |
1223
|
|
|
$b2 .= 'R'; |
1224
|
|
|
} |
1225
|
|
|
$b = (strpos($border, 'T') !== false) ? $b2 . 'T' : $b2; |
1226
|
|
|
} |
1227
|
|
|
} |
1228
|
|
|
$sep = -1; |
1229
|
|
|
$i = 0; |
1230
|
|
|
$j = 0; |
1231
|
|
|
$l = 0; |
1232
|
|
|
$ns = 0; |
1233
|
|
|
$nl = 1; |
1234
|
|
|
$ls = 0; |
1235
|
|
|
while ($i < $nb) { |
1236
|
|
|
// Get next character |
1237
|
|
|
$c = $s[$i]; |
1238
|
|
|
if ($c == "\n") { |
1239
|
|
|
// Explicit line break |
1240
|
|
|
if ($this->ws > 0) { |
1241
|
|
|
// reset wordspacing |
1242
|
|
|
$this->ws = 0; |
1243
|
|
|
$this->out('0 Tw'); |
1244
|
|
|
} |
1245
|
|
|
$strLine = substr($s, $j, $i - $j); |
1246
|
|
|
$this->internalCell($w, $h, $strLine, $b, 2, $align, $fill); |
1247
|
|
|
$i++; |
1248
|
|
|
$sep = -1; |
1249
|
|
|
$j = $i; |
1250
|
|
|
$l = 0; |
1251
|
|
|
$ns = 0; |
1252
|
|
|
$nl++; |
1253
|
|
|
if ($border && $nl == 2) { |
1254
|
|
|
$b = $b2; |
1255
|
|
|
} |
1256
|
|
|
continue; |
1257
|
|
|
} |
1258
|
|
|
if ($c == ' ') { |
1259
|
|
|
$sep = $i; // last space |
1260
|
|
|
$ls = $l; // current line width until last space |
1261
|
|
|
$ns++; // number of spaces |
1262
|
|
|
} |
1263
|
|
|
$l += $cw[$c]; |
1264
|
|
|
if ($l > $wmax) { |
1265
|
|
|
// Automatic line break |
1266
|
|
|
if ($sep == -1) { |
1267
|
|
|
// no space in this line... hard break |
1268
|
|
|
if ($i == $j) { |
1269
|
|
|
$i++; |
1270
|
|
|
} |
1271
|
|
|
if ($this->ws > 0) { |
1272
|
|
|
// reset wordspacing |
1273
|
|
|
$this->ws = 0; |
1274
|
|
|
$this->out('0 Tw'); |
1275
|
|
|
} |
1276
|
|
|
$strLine = substr($s, $j, $i - $j); |
1277
|
|
|
$this->internalCell($w, $h, $strLine, $b, 2, $align, $fill); |
1278
|
|
|
} else { |
1279
|
|
|
if ($align == 'J') { |
1280
|
|
|
// justified print - calc wordspacing |
1281
|
|
|
$this->ws = ($ns > 1) ? ($wmax - $ls) / 1000 * $this->FontSize / ($ns - 1) : 0; |
1282
|
|
|
$this->out(sprintf('%.3F Tw', $this->ws * $this->k)); |
1283
|
|
|
} |
1284
|
|
|
$strLine = substr($s, $j, $sep - $j); |
1285
|
|
|
$this->internalCell($w, $h, $strLine, $b, 2, $align, $fill); |
1286
|
|
|
$i = $sep + 1; |
1287
|
|
|
} |
1288
|
|
|
$sep = -1; |
1289
|
|
|
$j = $i; |
1290
|
|
|
$l = 0; |
1291
|
|
|
$ns = 0; |
1292
|
|
|
$nl++; |
1293
|
|
|
if ($border && $nl == 2) { |
1294
|
|
|
$b = $b2; |
1295
|
|
|
} |
1296
|
|
|
} else { |
1297
|
|
|
$i++; |
1298
|
|
|
} |
1299
|
|
|
} |
1300
|
|
|
// Last chunk |
1301
|
|
|
if ($this->ws > 0) { |
1302
|
|
|
$this->ws = 0; |
1303
|
|
|
$this->out('0 Tw'); |
1304
|
|
|
} |
1305
|
|
|
if ($border && strpos($border, 'B') !== false) { |
1306
|
|
|
$b .= 'B'; |
1307
|
|
|
} |
1308
|
|
|
$this->cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill); |
1309
|
|
|
$this->x = $this->lMargin; |
1310
|
|
|
} |
1311
|
|
|
|
1312
|
|
|
/** |
1313
|
|
|
* This method prints text from the current position. |
1314
|
|
|
* When the right margin is reached (or the \n character is met) a line break occurs |
1315
|
|
|
* and text continues from the left margin. Upon method exit, the current position |
1316
|
|
|
* is left just at the end of the text. |
1317
|
|
|
* It is possible to put a link on the text. |
1318
|
|
|
* @param float $h Line height. |
1319
|
|
|
* @param string $txt String to print. |
1320
|
|
|
* @param string|int $link URL or identifier for internal link created by AddLink(). |
1321
|
|
|
*/ |
1322
|
|
|
public function write(float $h, string $txt, $link = '') : void |
1323
|
|
|
{ |
1324
|
|
|
// Output text in flowing mode |
1325
|
|
|
if (!isset($this->CurrentFont)) { |
1326
|
|
|
$this->error('No font has been set'); |
1327
|
|
|
} |
1328
|
|
|
$cw = &$this->CurrentFont['cw']; |
1329
|
|
|
$w = $this->w - $this->rMargin - $this->x; |
1330
|
|
|
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize; |
1331
|
|
|
$s = $this->conv(str_replace("\r", '', $txt)); |
1332
|
|
|
$nb = strlen($s); |
1333
|
|
|
$sep = -1; |
1334
|
|
|
$i = 0; |
1335
|
|
|
$j = 0; |
1336
|
|
|
$l = 0; |
1337
|
|
|
$nl = 1; |
1338
|
|
|
while ($i < $nb) { |
1339
|
|
|
// Get next character |
1340
|
|
|
$c = $s[$i]; |
1341
|
|
|
if ($c == "\n") { |
1342
|
|
|
// Explicit line break |
1343
|
|
|
$this->internalCell($w, $h, substr($s, $j, $i - $j), 0, 2, '', false, $link); |
1344
|
|
|
$i++; |
1345
|
|
|
$sep = -1; |
1346
|
|
|
$j = $i; |
1347
|
|
|
$l = 0; |
1348
|
|
|
if ($nl == 1) { |
1349
|
|
|
$this->x = $this->lMargin; |
1350
|
|
|
$w = $this->w - $this->rMargin - $this->x; |
1351
|
|
|
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize; |
1352
|
|
|
} |
1353
|
|
|
$nl++; |
1354
|
|
|
continue; |
1355
|
|
|
} |
1356
|
|
|
if ($c == ' ') { |
1357
|
|
|
$sep = $i; |
1358
|
|
|
} |
1359
|
|
|
$l += $cw[$c]; |
1360
|
|
|
if ($l > $wmax) { |
1361
|
|
|
// Automatic line break |
1362
|
|
|
if ($sep == -1) { |
1363
|
|
|
if ($this->x > $this->lMargin) { |
1364
|
|
|
// Move to next line |
1365
|
|
|
$this->x = $this->lMargin; |
1366
|
|
|
$this->y += $h; |
1367
|
|
|
$w = $this->w - $this->rMargin - $this->x; |
1368
|
|
|
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize; |
1369
|
|
|
$i++; |
1370
|
|
|
$nl++; |
1371
|
|
|
continue; |
1372
|
|
|
} |
1373
|
|
|
if ($i == $j) { |
1374
|
|
|
$i++; |
1375
|
|
|
} |
1376
|
|
|
$this->internalCell($w, $h, substr($s, $j, $i - $j), 0, 2, '', false, $link); |
1377
|
|
|
} else { |
1378
|
|
|
$this->internalCell($w, $h, substr($s, $j, $sep - $j), 0, 2, '', false, $link); |
1379
|
|
|
$i = $sep + 1; |
1380
|
|
|
} |
1381
|
|
|
$sep = -1; |
1382
|
|
|
$j = $i; |
1383
|
|
|
$l = 0; |
1384
|
|
|
if ($nl == 1) { |
1385
|
|
|
$this->x = $this->lMargin; |
1386
|
|
|
$w = $this->w - $this->rMargin - $this->x; |
1387
|
|
|
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize; |
1388
|
|
|
} |
1389
|
|
|
$nl++; |
1390
|
|
|
} else { |
1391
|
|
|
$i++; |
1392
|
|
|
} |
1393
|
|
|
} |
1394
|
|
|
// Last chunk |
1395
|
|
|
if ($i != $j) { |
1396
|
|
|
$this->internalCell($l / 1000 * $this->FontSize, $h, substr($s, $j), 0, 0, '', false, $link); |
1397
|
|
|
} |
1398
|
|
|
} |
1399
|
|
|
|
1400
|
|
|
/** |
1401
|
|
|
* Performs a line break. |
1402
|
|
|
* The current X-position goes back to the left margin and the Y-position increases by |
1403
|
|
|
* the amount passed in parameter. |
1404
|
|
|
* @param float $h The height of the break. <br/> |
1405
|
|
|
* By default, the value equals the height of the last printed cell. |
1406
|
|
|
*/ |
1407
|
|
|
public function ln(float $h = null) : void |
1408
|
|
|
{ |
1409
|
|
|
// Line feed; default value is the last cell height |
1410
|
|
|
$this->x = $this->lMargin; |
1411
|
|
|
if ($h === null) { |
1412
|
|
|
$this->y += $this->lasth; |
1413
|
|
|
} else { |
1414
|
|
|
$this->y += $h; |
1415
|
|
|
} |
1416
|
|
|
} |
1417
|
|
|
|
1418
|
|
|
/** |
1419
|
|
|
* Puts an image. |
1420
|
|
|
* The size it will take on the page can be specified in different ways: <ul> |
1421
|
|
|
* <li> explicit width and height (expressed in user unit or dpi) </li> |
1422
|
|
|
* <li> one explicit dimension, the other being calculated automatically in order to keep the original proportions </li> |
1423
|
|
|
* <li> no explicit dimension, in which case the image is put at 96 dpi </li></ul> |
1424
|
|
|
* Supported formats are JPEG, PNG and GIF. <b>The GD extension is required for GIF.</b><br/> |
1425
|
|
|
* For JPEGs, all flavors are allowed: <ul> |
1426
|
|
|
* <li> gray scales </li> |
1427
|
|
|
* <li> true colors (24 bits) </li> |
1428
|
|
|
* <li> CMYK (32 bits) </li></ul> |
1429
|
|
|
* For PNGs, are allowed: <ul> |
1430
|
|
|
* <li> gray scales on at most 8 bits (256 levels) </li> |
1431
|
|
|
* <li> indexed colors </li> |
1432
|
|
|
* <li> true colors (24 bits) </li></ul> |
1433
|
|
|
* For GIFs: in case of an animated GIF, only the first frame is displayed. <br/><br/> |
1434
|
|
|
* Transparency is supported. <br/><br/> |
1435
|
|
|
* The format can be specified explicitly or inferred from the file extension. <br/><br/> |
1436
|
|
|
* It is possible to put a link on the image. <br/><br/> |
1437
|
|
|
* <b>Remark:</b> if an image is used several times, only one copy is embedded in the file. |
1438
|
|
|
* @param string $file Path or URL of the image. |
1439
|
|
|
* @param float $x X-position of the upper-left corner. <br/> <br/> |
1440
|
|
|
* If not specified or equal to null, the current X-position is used. <br/> |
1441
|
|
|
* @param float $y Y-position of the upper-left corner. <br/> |
1442
|
|
|
* If not specified or equal to null, the current Y-position is used; <br/> |
1443
|
|
|
* moreover, a page break is triggered first if necessary (in case automatic page breaking is enabled) and, <br/> |
1444
|
|
|
* after the call, the current Y-position is moved to the bottom of the image. <br/> |
1445
|
|
|
* @param float $w Width of the image in the page. <br/> |
1446
|
|
|
* There are three cases: <ul> |
1447
|
|
|
* <li> If the value is positive, it represents the width in user unit </li> |
1448
|
|
|
* <li> If the value is negative, the absolute value represents the horizontal resolution in dpi </li> |
1449
|
|
|
* <li> If the value is not specified or equal to zero, it is automatically calculated </li><ul> |
1450
|
|
|
* @param float $h Height of the image in the page. <br/> |
1451
|
|
|
* There are three cases: <ul> |
1452
|
|
|
* <li> If the value is positive, it represents the height in user unit </li> |
1453
|
|
|
* <li> If the value is negative, the absolute value represents the vertical resolution in dpi </li> |
1454
|
|
|
* <li> If the value is not specified or equal to zero, it is automatically calculated </li><ul> |
1455
|
|
|
* @param string $type Image format. <br/> |
1456
|
|
|
* Possible values are (case insensitive): <ul> |
1457
|
|
|
* <li> JPG </li> |
1458
|
|
|
* <li> JPEG </li> |
1459
|
|
|
* <li> PNG </li> |
1460
|
|
|
* <li> GIF </li></ul> |
1461
|
|
|
* If not specified, the type is inferred from the file extension. <br/> |
1462
|
|
|
* @param string|int $link URL or identifier for internal link created by AddLink(). |
1463
|
|
|
*/ |
1464
|
|
|
public function image(string $file, ?float $x = null, ?float $y = null, float $w = 0, float $h = 0, string $type = '', $link = '') : void |
1465
|
|
|
{ |
1466
|
|
|
// Put an image on the page |
1467
|
|
|
if ($file == '') { |
1468
|
|
|
$this->error('Image file name is empty'); |
1469
|
|
|
return; |
1470
|
|
|
} |
1471
|
|
|
if (!isset($this->images[$file])) { |
1472
|
|
|
// First use of this image, get info |
1473
|
|
|
if ($type == '') { |
1474
|
|
|
$pos = strrpos($file, '.'); |
1475
|
|
|
if (!$pos) { |
1476
|
|
|
$this->error('Image file has no extension and no type was specified: ' . $file); |
1477
|
|
|
return; |
1478
|
|
|
} |
1479
|
|
|
$type = substr($file, $pos + 1); |
1480
|
|
|
} |
1481
|
|
|
$type = strtolower($type); |
1482
|
|
|
if ($type == 'jpeg') { |
1483
|
|
|
$type = 'jpg'; |
1484
|
|
|
} |
1485
|
|
|
$mtd = 'parse' . ucfirst($type); |
1486
|
|
|
if (!method_exists($this, $mtd)) { |
1487
|
|
|
$this->error('Unsupported image type: ' . $type); |
1488
|
|
|
} |
1489
|
|
|
$info = $this->$mtd($file); |
1490
|
|
|
$info['i'] = count($this->images) + 1; |
1491
|
|
|
$this->images[$file] = $info; |
1492
|
|
|
} else { |
1493
|
|
|
$info = $this->images[$file]; |
1494
|
|
|
} |
1495
|
|
|
|
1496
|
|
|
// Automatic width and height calculation if needed |
1497
|
|
|
if ($w == 0 && $h == 0) { |
1498
|
|
|
// Put image at 96 dpi |
1499
|
|
|
$w = -96; |
1500
|
|
|
$h = -96; |
1501
|
|
|
} |
1502
|
|
|
if ($w < 0) { |
1503
|
|
|
$w = -$info['w'] * 72 / $w / $this->k; |
1504
|
|
|
} |
1505
|
|
|
if ($h < 0) { |
1506
|
|
|
$h = -$info['h'] * 72 / $h / $this->k; |
1507
|
|
|
} |
1508
|
|
|
if ($w == 0) { |
1509
|
|
|
$w = $h * $info['w'] / $info['h']; |
1510
|
|
|
} |
1511
|
|
|
if ($h == 0) { |
1512
|
|
|
$h = $w * $info['h'] / $info['w']; |
1513
|
|
|
} |
1514
|
|
|
|
1515
|
|
|
// Flowing mode |
1516
|
|
|
if ($y === null) { |
1517
|
|
|
if ($this->y + $h > $this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->acceptPageBreak()) { |
1518
|
|
|
// Automatic page break |
1519
|
|
|
$x2 = $this->x; |
1520
|
|
|
$this->addPage($this->CurOrientation, $this->CurPageSize, $this->CurRotation); |
1521
|
|
|
$this->x = $x2; |
1522
|
|
|
} |
1523
|
|
|
$y = $this->y; |
1524
|
|
|
$this->y += $h; |
1525
|
|
|
} |
1526
|
|
|
|
1527
|
|
|
if ($x === null) { |
1528
|
|
|
$x = $this->x; |
1529
|
|
|
} |
1530
|
|
|
$this->out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q', $w * $this->k, $h * $this->k, $x * $this->k, ($this->h - ($y + $h)) * $this->k, $info['i'])); |
1531
|
|
|
if ($link) { |
1532
|
|
|
$this->link($x, $y, $w, $h, $link); |
1533
|
|
|
} |
1534
|
|
|
} |
1535
|
|
|
|
1536
|
|
|
/** |
1537
|
|
|
* Set bookmark at current position. |
1538
|
|
|
* <b>from FPDF.org extension to create Bookmarks</b> |
1539
|
|
|
* @param string $txt |
1540
|
|
|
* @param bool $isUTF8 |
1541
|
|
|
* @param int $level |
1542
|
|
|
* @param int $y |
1543
|
|
|
*/ |
1544
|
|
|
public function bookmark(string $txt, bool $isUTF8 = true, int $level = 0, int $y = 0) : void |
1545
|
|
|
{ |
1546
|
|
|
if (!$isUTF8) { |
1547
|
|
|
$txt = utf8_encode($txt); |
1548
|
|
|
} |
1549
|
|
|
if ($y == -1) { |
1550
|
|
|
$y = $this->getY(); |
1551
|
|
|
} |
1552
|
|
|
$this->outlines[] = array('t' => $txt, 'l' => $level, 'y' => ($this->h - $y) * $this->k, 'p' => $this->pageNo()); |
1553
|
|
|
} |
1554
|
|
|
|
1555
|
|
|
/** |
1556
|
|
|
* Get current page width. |
1557
|
|
|
* @return float |
1558
|
|
|
*/ |
1559
|
|
|
public function getPageWidth() : float |
1560
|
|
|
{ |
1561
|
|
|
// Get current page width |
1562
|
|
|
return $this->w; |
1563
|
|
|
} |
1564
|
|
|
|
1565
|
|
|
/** |
1566
|
|
|
* Get current page height. |
1567
|
|
|
* @return float |
1568
|
|
|
*/ |
1569
|
|
|
public function getPageHeight() : float |
1570
|
|
|
{ |
1571
|
|
|
// Get current page height |
1572
|
|
|
return $this->h; |
1573
|
|
|
} |
1574
|
|
|
|
1575
|
|
|
/** |
1576
|
|
|
* Get the trigger for autopagebreak |
1577
|
|
|
* @return float |
1578
|
|
|
*/ |
1579
|
|
|
public function getPageBreakTrigger() : float |
1580
|
|
|
{ |
1581
|
|
|
// Get current page height |
1582
|
|
|
return $this->PageBreakTrigger; |
1583
|
|
|
} |
1584
|
|
|
|
1585
|
|
|
/** |
1586
|
|
|
* Get current x position. |
1587
|
|
|
* @return float |
1588
|
|
|
*/ |
1589
|
|
|
public function getX() : float |
1590
|
|
|
{ |
1591
|
|
|
// GetX position |
1592
|
|
|
return $this->x; |
1593
|
|
|
} |
1594
|
|
|
|
1595
|
|
|
/** |
1596
|
|
|
* Set new X position. |
1597
|
|
|
* If the passed value is negative, it is relative to the right of the page. |
1598
|
|
|
* @param float $x |
1599
|
|
|
*/ |
1600
|
|
|
public function setX(float $x) : void |
1601
|
|
|
{ |
1602
|
|
|
// Set x position |
1603
|
|
|
if ($x >= 0) { |
1604
|
|
|
$this->x = $x; |
1605
|
|
|
} else { |
1606
|
|
|
$this->x = $this->w + $x; |
1607
|
|
|
} |
1608
|
|
|
} |
1609
|
|
|
|
1610
|
|
|
/** |
1611
|
|
|
* Get current Y position. |
1612
|
|
|
* @return float |
1613
|
|
|
*/ |
1614
|
|
|
public function getY() : float |
1615
|
|
|
{ |
1616
|
|
|
// Get y position |
1617
|
|
|
return $this->y; |
1618
|
|
|
} |
1619
|
|
|
|
1620
|
|
|
/** |
1621
|
|
|
* Set new Y position and optionally moves the current X-position back to the left margin. |
1622
|
|
|
* If the passed value is negative, it is relative to the bottom of the page. |
1623
|
|
|
* @param float $y |
1624
|
|
|
* @param bool $resetX |
1625
|
|
|
*/ |
1626
|
|
|
public function setY(float $y, bool $resetX = true) : void |
1627
|
|
|
{ |
1628
|
|
|
// Set y position and optionally reset x |
1629
|
|
|
if ($y >= 0) { |
1630
|
|
|
$this->y = $y; |
1631
|
|
|
} else { |
1632
|
|
|
$this->y = $this->h + $y; |
1633
|
|
|
} |
1634
|
|
|
if ($resetX) { |
1635
|
|
|
$this->x = $this->lMargin; |
1636
|
|
|
} |
1637
|
|
|
} |
1638
|
|
|
|
1639
|
|
|
/** |
1640
|
|
|
* Set new X and Y position. |
1641
|
|
|
* If the passed values are negative, they are relative respectively to the right and bottom of the page. |
1642
|
|
|
* @param float $x |
1643
|
|
|
* @param float $y |
1644
|
|
|
*/ |
1645
|
|
|
public function setXY(float $x, float $y) : void |
1646
|
|
|
{ |
1647
|
|
|
// Set x and y positions |
1648
|
|
|
$this->setX($x); |
1649
|
|
|
$this->setY($y, false); |
1650
|
|
|
} |
1651
|
|
|
|
1652
|
|
|
/** |
1653
|
|
|
* Send the document to a given destination: browser, file or string. |
1654
|
|
|
* In the case of a browser, the PDF viewer may be used or a download may be forced. |
1655
|
|
|
* The method first calls Close() if necessary to terminate the document. |
1656
|
|
|
* @param string $dest Destination where to send the document. <br/> |
1657
|
|
|
* It can be one of the following: <ul> |
1658
|
|
|
* <li> 'I': send the file inline to the browser. The PDF viewer is used if available. </li> |
1659
|
|
|
* <li> 'D': send to the browser and force a file download with the name given by name. </li> |
1660
|
|
|
* <li> 'F': save to a local file with the name given by name (may include a path). </li> |
1661
|
|
|
* <li> 'S': return the document as a string. </li></ul> |
1662
|
|
|
* The default value is I. <br/> |
1663
|
|
|
* @param string $name The name of the file. It is ignored in case of destination 'S'. <br/> |
1664
|
|
|
* The default value is doc.pdf. <br/> |
1665
|
|
|
* @param bool $isUTF8 Indicates if name is encoded in ISO-8859-1 (false) or UTF-8 (true). <br/> |
1666
|
|
|
* Only used for destinations I and D. <br/> |
1667
|
|
|
* The default value is true. <br/> |
1668
|
|
|
* @return string |
1669
|
|
|
*/ |
1670
|
|
|
public function output(string $dest = '', string $name = '', bool $isUTF8 = true) : string |
1671
|
|
|
{ |
1672
|
|
|
// Output PDF to some destination |
1673
|
|
|
$this->close(); |
1674
|
|
|
if (strlen($name) == 1 && strlen($dest) != 1) { |
1675
|
|
|
// Fix parameter order |
1676
|
|
|
$tmp = $dest; |
1677
|
|
|
$dest = $name; |
1678
|
|
|
$name = $tmp; |
1679
|
|
|
} |
1680
|
|
|
if ($dest == '') { |
1681
|
|
|
$dest = 'I'; |
1682
|
|
|
} |
1683
|
|
|
if ($name == '') { |
1684
|
|
|
$name = 'doc.pdf'; |
1685
|
|
|
} |
1686
|
|
|
switch (strtoupper($dest)) { |
1687
|
|
|
case 'I': |
1688
|
|
|
// Send to standard output |
1689
|
|
|
$this->checkOutput(); |
1690
|
|
|
if (PHP_SAPI != 'cli') { |
1691
|
|
|
// We send to a browser |
1692
|
|
|
header('Content-Type: application/pdf; charset=UTF-8'); |
1693
|
|
|
header('Content-Disposition: inline; ' . $this->httpEncode('filename', $name, $isUTF8)); |
1694
|
|
|
header('Cache-Control: private, max-age=0, must-revalidate'); |
1695
|
|
|
header('Pragma: public'); |
1696
|
|
|
} |
1697
|
|
|
echo $this->buffer; |
1698
|
|
|
break; |
1699
|
|
|
case 'D': |
1700
|
|
|
// Download file |
1701
|
|
|
$this->checkOutput(); |
1702
|
|
|
header('Content-Type: application/x-download'); |
1703
|
|
|
header('Content-Disposition: attachment; ' . $this->httpEncode('filename', $name, $isUTF8)); |
1704
|
|
|
header('Cache-Control: private, max-age=0, must-revalidate'); |
1705
|
|
|
header('Pragma: public'); |
1706
|
|
|
echo $this->buffer; |
1707
|
|
|
break; |
1708
|
|
|
case 'F': |
1709
|
|
|
// Save to local file |
1710
|
|
|
if (!file_put_contents($name, $this->buffer)) { |
1711
|
|
|
$this->error('Unable to create output file: ' . $name); |
1712
|
|
|
} |
1713
|
|
|
break; |
1714
|
|
|
case 'S': |
1715
|
|
|
// Return as a string |
1716
|
|
|
return $this->buffer; |
1717
|
|
|
default: |
1718
|
|
|
$this->error('Incorrect output destination: ' . $dest); |
1719
|
|
|
} |
1720
|
|
|
return ''; |
1721
|
|
|
} |
1722
|
|
|
|
1723
|
|
|
/** |
1724
|
|
|
* Some internal checks before starting. |
1725
|
|
|
*/ |
1726
|
|
|
protected function doChecks() : void |
1727
|
|
|
{ |
1728
|
|
|
// Check mbstring overloading |
1729
|
|
|
if ((ini_get('mbstring.func_overload') & 2) !== 0) { |
1730
|
|
|
$this->error('mbstring overloading must be disabled'); |
1731
|
|
|
} |
1732
|
|
|
} |
1733
|
|
|
|
1734
|
|
|
/** |
1735
|
|
|
* get the path for the font definitions |
1736
|
|
|
*/ |
1737
|
|
|
protected function getFontPath() : void |
1738
|
|
|
{ |
1739
|
|
|
// Font path |
1740
|
|
|
$this->fontpath = ''; |
1741
|
|
|
if (defined('FPDF_FONTPATH')) { |
1742
|
|
|
$this->fontpath = FPDF_FONTPATH; |
1743
|
|
|
if (substr($this->fontpath, -1) != '/' && substr($this->fontpath, -1) != '\\') { |
1744
|
|
|
$this->fontpath .= '/'; |
1745
|
|
|
} |
1746
|
|
|
} elseif (is_dir(dirname(__FILE__) . '/font')) { |
1747
|
|
|
$this->fontpath = dirname(__FILE__) . '/font/'; |
1748
|
|
|
} |
1749
|
|
|
} |
1750
|
|
|
|
1751
|
|
|
/** |
1752
|
|
|
* Some internal checks before output. |
1753
|
|
|
*/ |
1754
|
|
|
protected function checkOutput() : void |
1755
|
|
|
{ |
1756
|
|
|
if (PHP_SAPI != 'cli') { |
1757
|
|
|
$file = ''; |
1758
|
|
|
$line = 0; |
1759
|
|
|
if (headers_sent($file, $line)) { |
1760
|
|
|
$this->error("Some data has already been output, can't send PDF file (output started at $file:$line)"); |
1761
|
|
|
} |
1762
|
|
|
} |
1763
|
|
|
if (ob_get_length() !== false) { |
1764
|
|
|
// The output buffer is not empty |
1765
|
|
|
if (preg_match('/^(\xEF\xBB\xBF)?\s*$/', ob_get_contents())) { |
1766
|
|
|
// It contains only a UTF-8 BOM and/or whitespace, let's clean it |
1767
|
|
|
ob_clean(); |
1768
|
|
|
} else { |
1769
|
|
|
$this->error("Some data has already been output, can't send PDF file"); |
1770
|
|
|
} |
1771
|
|
|
} |
1772
|
|
|
} |
1773
|
|
|
|
1774
|
|
|
/** |
1775
|
|
|
* Get dimensions of selected pagesize. |
1776
|
|
|
* @param string|array $size |
1777
|
|
|
* @return array |
1778
|
|
|
*/ |
1779
|
|
|
protected function getPageSize($size) : array |
1780
|
|
|
{ |
1781
|
|
|
if (is_string($size)) { |
1782
|
|
|
$aStdPageSizes = array( |
1783
|
|
|
'a3'=>array(841.89, 1190.55), |
1784
|
|
|
'a4'=>array(595.28, 841.89), |
1785
|
|
|
'a5'=>array(420.94, 595.28), |
1786
|
|
|
'letter'=>array(612, 792), |
1787
|
|
|
'legal'=>array(612, 1008) |
1788
|
|
|
); |
1789
|
|
|
|
1790
|
|
|
$size = strtolower($size); |
1791
|
|
|
if (!isset($aStdPageSizes[$size])) { |
1792
|
|
|
$this->error('Unknown page size: ' . $size); |
1793
|
|
|
$a = $aStdPageSizes['a4']; |
1794
|
|
|
} else { |
1795
|
|
|
$a = $aStdPageSizes[$size]; |
1796
|
|
|
} |
1797
|
|
|
return array($a[0] / $this->k, $a[1] / $this->k); |
1798
|
|
|
} else { |
1799
|
|
|
if ($size[0] > $size[1]) { |
1800
|
|
|
return array($size[1], $size[0]); |
1801
|
|
|
} else { |
1802
|
|
|
return $size; |
1803
|
|
|
} |
1804
|
|
|
} |
1805
|
|
|
} |
1806
|
|
|
|
1807
|
|
|
/** |
1808
|
|
|
* Start new page. |
1809
|
|
|
* @param string $orientation |
1810
|
|
|
* @param string|array $size |
1811
|
|
|
* @param int $rotation |
1812
|
|
|
*/ |
1813
|
|
|
protected function beginPage(string $orientation, $size, int $rotation) : void |
1814
|
|
|
{ |
1815
|
|
|
$this->page++; |
1816
|
|
|
$this->pages[$this->page] = ''; |
1817
|
|
|
$this->state = 2; |
1818
|
|
|
$this->x = $this->lMargin; |
1819
|
|
|
$this->y = $this->tMargin; |
1820
|
|
|
$this->FontFamily = ''; |
1821
|
|
|
// Check page size and orientation |
1822
|
|
|
if ($orientation == '') { |
1823
|
|
|
$orientation = $this->DefOrientation; |
1824
|
|
|
} else { |
1825
|
|
|
$orientation = strtoupper($orientation[0]); |
1826
|
|
|
} |
1827
|
|
|
if ($size == '') { |
1828
|
|
|
$size = $this->DefPageSize; |
1829
|
|
|
} else { |
1830
|
|
|
$size = $this->getPageSize($size); |
1831
|
|
|
} |
1832
|
|
|
if ($orientation != $this->CurOrientation || $size[0] != $this->CurPageSize[0] || $size[1] != $this->CurPageSize[1]) { |
1833
|
|
|
// New size or orientation |
1834
|
|
|
if ($orientation == 'P') { |
1835
|
|
|
$this->w = $size[0]; |
1836
|
|
|
$this->h = $size[1]; |
1837
|
|
|
} else { |
1838
|
|
|
$this->w = $size[1]; |
1839
|
|
|
$this->h = $size[0]; |
1840
|
|
|
} |
1841
|
|
|
$this->wPt = $this->w * $this->k; |
1842
|
|
|
$this->hPt = $this->h * $this->k; |
1843
|
|
|
$this->PageBreakTrigger = $this->h - $this->bMargin; |
1844
|
|
|
$this->CurOrientation = $orientation; |
1845
|
|
|
$this->CurPageSize = $size; |
1846
|
|
|
} |
1847
|
|
|
if ($orientation != $this->DefOrientation || $size[0] != $this->DefPageSize[0] || $size[1] != $this->DefPageSize[1]) { |
1848
|
|
|
$this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); |
1849
|
|
|
} |
1850
|
|
|
if ($rotation != 0) { |
1851
|
|
|
if ($rotation % 90 != 0) { |
1852
|
|
|
$this->error('Incorrect rotation value: ' . $rotation); |
1853
|
|
|
} |
1854
|
|
|
$this->CurRotation = $rotation; |
1855
|
|
|
$this->PageInfo[$this->page]['rotation'] = $rotation; |
1856
|
|
|
} |
1857
|
|
|
} |
1858
|
|
|
|
1859
|
|
|
/** |
1860
|
|
|
* End of current page. |
1861
|
|
|
*/ |
1862
|
|
|
protected function endPage() : void |
1863
|
|
|
{ |
1864
|
|
|
$this->state = 1; |
1865
|
|
|
} |
1866
|
|
|
|
1867
|
|
|
/** |
1868
|
|
|
* Load a font definition file from the font directory. |
1869
|
|
|
* @param string $font |
1870
|
|
|
* @return array |
1871
|
|
|
*/ |
1872
|
|
|
protected function loadFont(string $font) : array |
1873
|
|
|
{ |
1874
|
|
|
// Load a font definition file from the font directory |
1875
|
|
|
if (strpos($font, '/') !== false || strpos($font, "\\") !== false) { |
1876
|
|
|
$this->error('Incorrect font definition file name: ' . $font); |
1877
|
|
|
return []; |
1878
|
|
|
} |
1879
|
|
|
// following vars must be initialized in the font definition file beeing included |
1880
|
|
|
$name = null; |
1881
|
|
|
$enc = null; |
1882
|
|
|
$subsetted = null; |
1883
|
|
|
include($this->fontpath . $font); |
1884
|
|
|
|
1885
|
|
|
// phpstan can't see the code dynamicly included before so assuming $name, $enc, $subsetted always set to null! |
1886
|
|
|
if (!isset($name)) { /* @phpstan-ignore-line */ |
1887
|
|
|
$this->error('Could not include font definition file'); |
1888
|
|
|
} |
1889
|
|
|
if (isset($enc)) { /* @phpstan-ignore-line */ |
1890
|
|
|
$enc = strtolower($enc); |
1891
|
|
|
} |
1892
|
|
|
if (!isset($subsetted)) { /* @phpstan-ignore-line */ |
1893
|
|
|
$subsetted = false; |
1894
|
|
|
} |
1895
|
|
|
return get_defined_vars(); |
1896
|
|
|
} |
1897
|
|
|
|
1898
|
|
|
/** |
1899
|
|
|
* Check if string only contains ascii chars (0...127). |
1900
|
|
|
* @param string $s |
1901
|
|
|
* @return bool |
1902
|
|
|
*/ |
1903
|
|
|
protected function isAscii(string $s) : bool |
1904
|
|
|
{ |
1905
|
|
|
// Test if string is ASCII |
1906
|
|
|
$nb = strlen($s); |
1907
|
|
|
for ($i = 0; $i < $nb; $i++) { |
1908
|
|
|
if (ord($s[$i]) > 127) { |
1909
|
|
|
return false; |
1910
|
|
|
} |
1911
|
|
|
} |
1912
|
|
|
return true; |
1913
|
|
|
} |
1914
|
|
|
|
1915
|
|
|
/** |
1916
|
|
|
* @param string $param |
1917
|
|
|
* @param string $value |
1918
|
|
|
* @param bool $isUTF8 |
1919
|
|
|
* @return string |
1920
|
|
|
*/ |
1921
|
|
|
protected function httpEncode(string $param, string $value, bool $isUTF8) : string |
1922
|
|
|
{ |
1923
|
|
|
// Encode HTTP header field parameter |
1924
|
|
|
if ($this->isAscii($value)) { |
1925
|
|
|
return $param . '="' . $value . '"'; |
1926
|
|
|
} |
1927
|
|
|
if (!$isUTF8) { |
1928
|
|
|
$value = utf8_encode($value); |
1929
|
|
|
} |
1930
|
|
|
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false) { |
1931
|
|
|
return $param . '="' . rawurlencode($value) . '"'; |
1932
|
|
|
} else { |
1933
|
|
|
return $param . "*=UTF-8''" . rawurlencode($value); |
1934
|
|
|
} |
1935
|
|
|
} |
1936
|
|
|
|
1937
|
|
|
/** |
1938
|
|
|
* Convert UTF8 to UTF16. |
1939
|
|
|
* @param string $s |
1940
|
|
|
* @return string |
1941
|
|
|
*/ |
1942
|
|
|
protected function convUTF8toUTF16(string $s) : string |
1943
|
|
|
{ |
1944
|
|
|
// Convert UTF-8 to UTF-16BE with BOM |
1945
|
|
|
$res = "\xFE\xFF"; |
1946
|
|
|
$nb = strlen($s); |
1947
|
|
|
$i = 0; |
1948
|
|
|
while ($i < $nb) { |
1949
|
|
|
$c1 = ord($s[$i++]); |
1950
|
|
|
if ($c1 >= 224) { |
1951
|
|
|
// 3-byte character |
1952
|
|
|
$c2 = ord($s[$i++]); |
1953
|
|
|
$c3 = ord($s[$i++]); |
1954
|
|
|
$res .= chr((($c1 & 0x0F) << 4) + (($c2 & 0x3C) >> 2)); |
1955
|
|
|
$res .= chr((($c2 & 0x03) << 6) + ($c3 & 0x3F)); |
1956
|
|
|
} elseif ($c1 >= 192) { |
1957
|
|
|
// 2-byte character |
1958
|
|
|
$c2 = ord($s[$i++]); |
1959
|
|
|
$res .= chr(($c1 & 0x1C) >> 2); |
1960
|
|
|
$res .= chr((($c1 & 0x03) << 6) + ($c2 & 0x3F)); |
1961
|
|
|
} else { |
1962
|
|
|
// Single-byte character |
1963
|
|
|
$res .= "\0" . chr($c1); |
1964
|
|
|
} |
1965
|
|
|
} |
1966
|
|
|
return $res; |
1967
|
|
|
} |
1968
|
|
|
|
1969
|
|
|
/** |
1970
|
|
|
* Escape special characters. |
1971
|
|
|
* @param string $s |
1972
|
|
|
* @return string |
1973
|
|
|
*/ |
1974
|
|
|
protected function escape(string $s) : string |
1975
|
|
|
{ |
1976
|
|
|
// Escape special characters |
1977
|
|
|
if (strpos($s, '(') !== false || |
1978
|
|
|
strpos($s, ')') !== false || |
1979
|
|
|
strpos($s, '\\') !== false || |
1980
|
|
|
strpos($s, "\r") !== false) { |
1981
|
|
|
$s = str_replace(array('\\', '(', ')', "\r"), array('\\\\', '\\(', '\\)', '\\r'), $s); |
1982
|
|
|
} |
1983
|
|
|
return $s; |
1984
|
|
|
} |
1985
|
|
|
|
1986
|
|
|
/** |
1987
|
|
|
* @param string $s |
1988
|
|
|
* @return string |
1989
|
|
|
*/ |
1990
|
|
|
protected function conv(string $s) : string |
1991
|
|
|
{ |
1992
|
|
|
if ($this->charset != 'ISO-8859-15') { |
1993
|
|
|
$s = iconv($this->charset, 'ISO-8859-15//IGNORE', $s); |
1994
|
|
|
} |
1995
|
|
|
$s= str_replace('{eur}', chr(128) . ' ', $s); |
1996
|
|
|
return $s; |
1997
|
|
|
} |
1998
|
|
|
|
1999
|
|
|
|
2000
|
|
|
/** |
2001
|
|
|
* Format a text string. |
2002
|
|
|
* @param string $s |
2003
|
|
|
* @return string |
2004
|
|
|
*/ |
2005
|
|
|
protected function textString(string $s) : string |
2006
|
|
|
{ |
2007
|
|
|
// Format a text string |
2008
|
|
|
if (!$this->isAscii($s)) { |
2009
|
|
|
$s = $this->convUTF8toUTF16($s); |
2010
|
|
|
} |
2011
|
|
|
return '(' . $this->escape($s) . ')'; |
2012
|
|
|
} |
2013
|
|
|
|
2014
|
|
|
/** |
2015
|
|
|
* Underline text with 'simple' line. |
2016
|
|
|
* @param float $x |
2017
|
|
|
* @param float $y |
2018
|
|
|
* @param string $txt |
2019
|
|
|
* @return string |
2020
|
|
|
*/ |
2021
|
|
|
protected function doUnderline(float $x, float $y, string $txt) : string |
2022
|
|
|
{ |
2023
|
|
|
// Underline text |
2024
|
|
|
$up = $this->CurrentFont['up']; |
2025
|
|
|
$ut = $this->CurrentFont['ut']; |
2026
|
|
|
$w = $this->getStringWidth($txt) + $this->ws * substr_count($txt, ' '); |
2027
|
|
|
return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ($this->h - ($y - $up / 1000 * $this->FontSize)) * $this->k, $w * $this->k, -$ut / 1000 * $this->FontSizePt); |
2028
|
|
|
} |
2029
|
|
|
|
2030
|
|
|
/** |
2031
|
|
|
* Extract info from a JPEG file. |
2032
|
|
|
* @param string $file |
2033
|
|
|
* @return array |
2034
|
|
|
*/ |
2035
|
|
|
protected function parseJpg(string $file) : array |
2036
|
|
|
{ |
2037
|
|
|
// Extract info from a JPEG file |
2038
|
|
|
$a = getimagesize($file); |
2039
|
|
|
if (!$a) { |
|
|
|
|
2040
|
|
|
$this->error('Missing or incorrect image file: ' . $file); |
2041
|
|
|
return []; |
2042
|
|
|
} |
2043
|
|
|
if ($a[2] != 2) { |
2044
|
|
|
$this->error('Not a JPEG file: ' . $file); |
2045
|
|
|
return []; |
2046
|
|
|
} |
2047
|
|
|
if (!isset($a['channels']) || $a['channels'] == 3) { |
2048
|
|
|
$colspace = 'DeviceRGB'; |
2049
|
|
|
} elseif ($a['channels'] == 4) { |
2050
|
|
|
$colspace = 'DeviceCMYK'; |
2051
|
|
|
} else { |
2052
|
|
|
$colspace = 'DeviceGray'; |
2053
|
|
|
} |
2054
|
|
|
$bpc = isset($a['bits']) ? $a['bits'] : 8; |
2055
|
|
|
$data = file_get_contents($file); |
2056
|
|
|
return array('w' => $a[0], 'h' => $a[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data); |
2057
|
|
|
} |
2058
|
|
|
|
2059
|
|
|
/** |
2060
|
|
|
* Extract info from a PNG file. |
2061
|
|
|
* @param string $file |
2062
|
|
|
* @return array |
2063
|
|
|
*/ |
2064
|
|
|
protected function parsePng(string $file) : array |
2065
|
|
|
{ |
2066
|
|
|
// Extract info from a PNG file |
2067
|
|
|
$f = fopen($file, 'rb'); |
2068
|
|
|
if ($f === false) { |
2069
|
|
|
$this->error('Can\'t open image file: ' . $file); |
2070
|
|
|
return []; |
2071
|
|
|
} |
2072
|
|
|
$info = $this->parsePngStream($f, $file); |
2073
|
|
|
fclose($f); |
2074
|
|
|
return $info; |
2075
|
|
|
} |
2076
|
|
|
|
2077
|
|
|
/** |
2078
|
|
|
* Extract info from a PNG stream |
2079
|
|
|
* @param resource $f |
2080
|
|
|
* @param string $file |
2081
|
|
|
* @return array |
2082
|
|
|
*/ |
2083
|
|
|
protected function parsePngStream($f, string $file) : array |
2084
|
|
|
{ |
2085
|
|
|
// Check signature |
2086
|
|
|
if ($this->readStream($f, 8) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { |
2087
|
|
|
$this->error('Not a PNG file: ' . $file); |
2088
|
|
|
return []; |
2089
|
|
|
} |
2090
|
|
|
|
2091
|
|
|
// Read header chunk |
2092
|
|
|
$this->readStream($f, 4); |
2093
|
|
|
if ($this->readStream($f, 4) != 'IHDR') { |
2094
|
|
|
$this->error('Incorrect PNG file: ' . $file); |
2095
|
|
|
return []; |
2096
|
|
|
} |
2097
|
|
|
$w = $this->readInt($f); |
2098
|
|
|
$h = $this->readInt($f); |
2099
|
|
|
$bpc = ord($this->readStream($f, 1)); |
2100
|
|
|
if ($bpc > 8) { |
2101
|
|
|
$this->error('16-bit depth not supported: ' . $file); |
2102
|
|
|
return []; |
2103
|
|
|
} |
2104
|
|
|
$ct = ord($this->readStream($f, 1)); |
2105
|
|
|
$colspace = ''; |
2106
|
|
|
if ($ct == 0 || $ct == 4) { |
2107
|
|
|
$colspace = 'DeviceGray'; |
2108
|
|
|
} elseif ($ct == 2 || $ct == 6) { |
2109
|
|
|
$colspace = 'DeviceRGB'; |
2110
|
|
|
} elseif ($ct == 3) { |
2111
|
|
|
$colspace = 'Indexed'; |
2112
|
|
|
} else { |
2113
|
|
|
$this->error('Unknown color type: ' . $file); |
2114
|
|
|
return []; |
2115
|
|
|
} |
2116
|
|
|
if (ord($this->readStream($f, 1)) != 0) { |
2117
|
|
|
$this->error('Unknown compression method: ' . $file); |
2118
|
|
|
return []; |
2119
|
|
|
} |
2120
|
|
|
if (ord($this->readStream($f, 1)) != 0) { |
2121
|
|
|
$this->error('Unknown filter method: ' . $file); |
2122
|
|
|
return []; |
2123
|
|
|
} |
2124
|
|
|
if (ord($this->readStream($f, 1)) != 0) { |
2125
|
|
|
$this->error('Interlacing not supported: ' . $file); |
2126
|
|
|
return []; |
2127
|
|
|
} |
2128
|
|
|
$this->readStream($f, 4); |
2129
|
|
|
$dp = '/Predictor 15 /Colors ' . ($colspace == 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; |
2130
|
|
|
|
2131
|
|
|
// Scan chunks looking for palette, transparency and image data |
2132
|
|
|
$pal = ''; |
2133
|
|
|
$trns = ''; |
2134
|
|
|
$data = ''; |
2135
|
|
|
do { |
2136
|
|
|
$n = $this->readInt($f); |
2137
|
|
|
$type = $this->readStream($f, 4); |
2138
|
|
|
if ($type == 'PLTE') { |
2139
|
|
|
// Read palette |
2140
|
|
|
$pal = $this->readStream($f, $n); |
2141
|
|
|
$this->readStream($f, 4); |
2142
|
|
|
} elseif ($type == 'tRNS') { |
2143
|
|
|
// Read transparency info |
2144
|
|
|
$t = $this->readStream($f, $n); |
2145
|
|
|
if ($ct == 0) { |
2146
|
|
|
$trns = array(ord(substr($t, 1, 1))); |
2147
|
|
|
} elseif ($ct == 2) { |
2148
|
|
|
$trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1))); |
2149
|
|
|
} else { |
2150
|
|
|
$pos = strpos($t, chr(0)); |
2151
|
|
|
if ($pos !== false) { |
2152
|
|
|
$trns = array($pos); |
2153
|
|
|
} |
2154
|
|
|
} |
2155
|
|
|
$this->readStream($f, 4); |
2156
|
|
|
} elseif ($type == 'IDAT') { |
2157
|
|
|
// Read image data block |
2158
|
|
|
$data .= $this->readStream($f, $n); |
2159
|
|
|
$this->readStream($f, 4); |
2160
|
|
|
} elseif ($type == 'IEND') { |
2161
|
|
|
break; |
2162
|
|
|
} else { |
2163
|
|
|
$this->readStream($f, $n + 4); |
2164
|
|
|
} |
2165
|
|
|
} while ($n); |
2166
|
|
|
|
2167
|
|
|
if ($colspace == 'Indexed' && empty($pal)) { |
2168
|
|
|
$this->error('Missing palette in ' . $file); |
2169
|
|
|
return []; |
2170
|
|
|
} |
2171
|
|
|
$info = array( |
2172
|
|
|
'w' => $w, |
2173
|
|
|
'h' => $h, |
2174
|
|
|
'cs' => $colspace, |
2175
|
|
|
'bpc' => $bpc, |
2176
|
|
|
'f' => 'FlateDecode', |
2177
|
|
|
'dp' => $dp, |
2178
|
|
|
'pal' => $pal, |
2179
|
|
|
'trns' => $trns |
2180
|
|
|
); |
2181
|
|
|
|
2182
|
|
|
if ($ct >= 4) { |
2183
|
|
|
// Extract alpha channel |
2184
|
|
|
if (!function_exists('gzuncompress')) { |
2185
|
|
|
$this->error('Zlib not available, can\'t handle alpha channel: ' . $file); |
2186
|
|
|
return []; |
2187
|
|
|
} |
2188
|
|
|
$data = gzuncompress($data); |
2189
|
|
|
$color = ''; |
2190
|
|
|
$alpha = ''; |
2191
|
|
|
if ($ct == 4) { |
2192
|
|
|
// Gray image |
2193
|
|
|
$len = 2 * $w; |
2194
|
|
|
for ($i = 0; $i < $h; $i++) { |
2195
|
|
|
$pos = (1 + $len) * $i; |
2196
|
|
|
$color .= $data[$pos]; |
2197
|
|
|
$alpha .= $data[$pos]; |
2198
|
|
|
$line = substr($data, $pos + 1, $len); |
2199
|
|
|
$color .= preg_replace('/(.)./s', '$1', $line); |
2200
|
|
|
$alpha .= preg_replace('/.(.)/s', '$1', $line); |
2201
|
|
|
} |
2202
|
|
|
} else { |
2203
|
|
|
// RGB image |
2204
|
|
|
$len = 4 * $w; |
2205
|
|
|
for ($i = 0; $i < $h; $i++) { |
2206
|
|
|
$pos = (1 + $len) * $i; |
2207
|
|
|
$color .= $data[$pos]; |
2208
|
|
|
$alpha .= $data[$pos]; |
2209
|
|
|
$line = substr($data, $pos + 1, $len); |
2210
|
|
|
$color .= preg_replace('/(.{3})./s', '$1', $line); |
2211
|
|
|
$alpha .= preg_replace('/.{3}(.)/s', '$1', $line); |
2212
|
|
|
} |
2213
|
|
|
} |
2214
|
|
|
unset($data); |
2215
|
|
|
$data = gzcompress($color); |
2216
|
|
|
$info['smask'] = gzcompress($alpha); |
2217
|
|
|
$this->WithAlpha = true; |
2218
|
|
|
if ($this->PDFVersion < '1.4') { |
2219
|
|
|
$this->PDFVersion = '1.4'; |
2220
|
|
|
} |
2221
|
|
|
} |
2222
|
|
|
$info['data'] = $data; |
2223
|
|
|
return $info; |
2224
|
|
|
} |
2225
|
|
|
|
2226
|
|
|
/** |
2227
|
|
|
* Read n bytes from stream. |
2228
|
|
|
* @param resource $f |
2229
|
|
|
* @param int $n |
2230
|
|
|
* @return string |
2231
|
|
|
*/ |
2232
|
|
|
protected function readStream($f, int $n) : string |
2233
|
|
|
{ |
2234
|
|
|
// Read n bytes from stream |
2235
|
|
|
$res = ''; |
2236
|
|
|
while ($n > 0 && !feof($f)) { |
2237
|
|
|
$s = fread($f, $n); |
2238
|
|
|
if ($s === false) { |
2239
|
|
|
$this->error('Error while reading stream'); |
2240
|
|
|
return ''; |
2241
|
|
|
} |
2242
|
|
|
$n -= strlen($s); |
2243
|
|
|
$res .= $s; |
2244
|
|
|
} |
2245
|
|
|
if ($n > 0) { |
2246
|
|
|
$this->error('Unexpected end of stream'); |
2247
|
|
|
} |
2248
|
|
|
return $res; |
2249
|
|
|
} |
2250
|
|
|
|
2251
|
|
|
/** |
2252
|
|
|
* Read a 4-byte integer from stream. |
2253
|
|
|
* @param resource $f |
2254
|
|
|
* @return int |
2255
|
|
|
*/ |
2256
|
|
|
protected function readInt($f) : int |
2257
|
|
|
{ |
2258
|
|
|
// Read a 4-byte integer from stream |
2259
|
|
|
$a = unpack('Ni', $this->readStream($f, 4)); |
2260
|
|
|
return $a['i']; |
2261
|
|
|
} |
2262
|
|
|
|
2263
|
|
|
/** |
2264
|
|
|
* Extract info from a GIF file. |
2265
|
|
|
* 1. GIF is converted to PNG using GD extension since PDF don't support |
2266
|
|
|
* GIF image format. <br/> |
2267
|
|
|
* 2. The converted image is written to memory stream <br/> |
2268
|
|
|
* 3. The PNG-memory stream is parsed using internal function for PNG |
2269
|
|
|
* @param string $file |
2270
|
|
|
* @return array |
2271
|
|
|
*/ |
2272
|
|
|
protected function parseGif(string $file) : array |
2273
|
|
|
{ |
2274
|
|
|
// Extract info from a GIF file (via PNG conversion) |
2275
|
|
|
if (!function_exists('imagepng')) { |
2276
|
|
|
$this->error('GD extension is required for GIF support'); |
2277
|
|
|
return []; |
2278
|
|
|
} |
2279
|
|
|
if (!function_exists('imagecreatefromgif')) { |
2280
|
|
|
$this->error('GD has no GIF read support'); |
2281
|
|
|
return []; |
2282
|
|
|
} |
2283
|
|
|
$im = imagecreatefromgif($file); |
2284
|
|
|
if ($im === false) { |
2285
|
|
|
$this->error('Missing or incorrect image file: ' . $file); |
2286
|
|
|
return []; |
2287
|
|
|
} |
2288
|
|
|
imageinterlace($im, 0); |
2289
|
|
|
ob_start(); |
2290
|
|
|
imagepng($im); |
2291
|
|
|
$data = ob_get_clean(); |
2292
|
|
|
imagedestroy($im); |
2293
|
|
|
$f = fopen('php://temp', 'rb+'); |
2294
|
|
|
if ($f === false) { |
2295
|
|
|
$this->error('Unable to create memory stream'); |
2296
|
|
|
return []; |
2297
|
|
|
} |
2298
|
|
|
fwrite($f, $data); |
2299
|
|
|
rewind($f); |
2300
|
|
|
$info = $this->parsePngStream($f, $file); |
2301
|
|
|
fclose($f); |
2302
|
|
|
return $info; |
2303
|
|
|
} |
2304
|
|
|
|
2305
|
|
|
/** |
2306
|
|
|
* Add a line to the document. |
2307
|
|
|
* @param string $s |
2308
|
|
|
*/ |
2309
|
|
|
protected function out(string $s) : void |
2310
|
|
|
{ |
2311
|
|
|
// Add a line to the document |
2312
|
|
|
switch ($this->state) { |
2313
|
|
|
case 0: // no page added so far |
2314
|
|
|
$this->error('No page has been added yet'); |
2315
|
|
|
break; |
2316
|
|
|
case 1: // all output in the endDoc() method goes direct to the buffer |
2317
|
|
|
$this->put($s); |
2318
|
|
|
break; |
2319
|
|
|
case 2: // page output is stored in internal array |
2320
|
|
|
$this->pages[$this->page] .= $s . "\n"; |
2321
|
|
|
break; |
2322
|
|
|
case 3: // document is closed so far |
2323
|
|
|
$this->error('The document is closed'); |
2324
|
|
|
break; |
2325
|
|
|
} |
2326
|
|
|
} |
2327
|
|
|
|
2328
|
|
|
/** |
2329
|
|
|
* Add a command to the document. |
2330
|
|
|
* @param string $s |
2331
|
|
|
*/ |
2332
|
|
|
protected function put(string $s) : void |
2333
|
|
|
{ |
2334
|
|
|
$this->buffer .= $s . "\n"; |
2335
|
|
|
} |
2336
|
|
|
|
2337
|
|
|
/** |
2338
|
|
|
* Get current length of the output buffer. |
2339
|
|
|
* @return int |
2340
|
|
|
*/ |
2341
|
|
|
protected function getOffset() : int |
2342
|
|
|
{ |
2343
|
|
|
return strlen($this->buffer); |
2344
|
|
|
} |
2345
|
|
|
|
2346
|
|
|
/** |
2347
|
|
|
* Begin a new object. |
2348
|
|
|
* @param int $n |
2349
|
|
|
*/ |
2350
|
|
|
protected function newObject(?int $n = null) : void |
2351
|
|
|
{ |
2352
|
|
|
// Begin a new object |
2353
|
|
|
if ($n === null) { |
2354
|
|
|
$n = ++$this->n; |
2355
|
|
|
} |
2356
|
|
|
$this->offsets[$n] = $this->getOffset(); |
2357
|
|
|
$this->put($n . ' 0 obj'); |
2358
|
|
|
} |
2359
|
|
|
|
2360
|
|
|
/** |
2361
|
|
|
* Add data from stream to the document. |
2362
|
|
|
* @param string $data |
2363
|
|
|
*/ |
2364
|
|
|
protected function putStream(string $data) : void |
2365
|
|
|
{ |
2366
|
|
|
$this->put('stream'); |
2367
|
|
|
$this->put($data); |
2368
|
|
|
$this->put('endstream'); |
2369
|
|
|
} |
2370
|
|
|
|
2371
|
|
|
/** |
2372
|
|
|
* Add Stream-object to the document. |
2373
|
|
|
* @param string $data |
2374
|
|
|
*/ |
2375
|
|
|
protected function putStreamObject(string $data) : void |
2376
|
|
|
{ |
2377
|
|
|
$entries = ''; |
2378
|
|
|
if ($this->compress) { |
2379
|
|
|
$entries = '/Filter /FlateDecode '; |
2380
|
|
|
$data = gzcompress($data); |
2381
|
|
|
} |
2382
|
|
|
$entries .= '/Length ' . strlen($data); |
2383
|
|
|
$this->newObject(); |
2384
|
|
|
$this->put('<<' . $entries . '>>'); |
2385
|
|
|
$this->putStream($data); |
2386
|
|
|
$this->put('endobj'); |
2387
|
|
|
} |
2388
|
|
|
|
2389
|
|
|
/** |
2390
|
|
|
* Add all pages to the document. |
2391
|
|
|
*/ |
2392
|
|
|
protected function putPages() : void |
2393
|
|
|
{ |
2394
|
|
|
$nb = $this->page; |
2395
|
|
|
for ($n = 1; $n <= $nb; $n++) { |
2396
|
|
|
$this->PageInfo[$n]['n'] = $this->n + 1 + 2 * ($n - 1); |
2397
|
|
|
} |
2398
|
|
|
for ($n = 1; $n <= $nb; $n++) { |
2399
|
|
|
$this->putPage($n); |
2400
|
|
|
} |
2401
|
|
|
// Pages root |
2402
|
|
|
$this->newObject(1); |
2403
|
|
|
$this->put('<</Type /Pages'); |
2404
|
|
|
$kids = '/Kids ['; |
2405
|
|
|
for ($n = 1; $n <= $nb; $n++) { |
2406
|
|
|
$kids .= $this->PageInfo[$n]['n'] . ' 0 R '; |
2407
|
|
|
} |
2408
|
|
|
$this->put($kids . ']'); |
2409
|
|
|
$this->put('/Count ' . $nb); |
2410
|
|
|
if ($this->DefOrientation == 'P') { |
2411
|
|
|
$w = $this->DefPageSize[0]; |
2412
|
|
|
$h = $this->DefPageSize[1]; |
2413
|
|
|
} else { |
2414
|
|
|
$w = $this->DefPageSize[1]; |
2415
|
|
|
$h = $this->DefPageSize[0]; |
2416
|
|
|
} |
2417
|
|
|
$this->put(sprintf('/MediaBox [0 0 %.2F %.2F]', $w * $this->k, $h * $this->k)); |
2418
|
|
|
$this->put('>>'); |
2419
|
|
|
$this->put('endobj'); |
2420
|
|
|
} |
2421
|
|
|
|
2422
|
|
|
/** |
2423
|
|
|
* Add Pageinfos to the document. |
2424
|
|
|
* @param int $n |
2425
|
|
|
*/ |
2426
|
|
|
protected function putPage(int $n) : void |
2427
|
|
|
{ |
2428
|
|
|
$this->newObject(); |
2429
|
|
|
$this->put('<</Type /Page'); |
2430
|
|
|
$this->put('/Parent 1 0 R'); |
2431
|
|
|
if (isset($this->PageInfo[$n]['size'])) { |
2432
|
|
|
$this->put(sprintf('/MediaBox [0 0 %.2F %.2F]', $this->PageInfo[$n]['size'][0], $this->PageInfo[$n]['size'][1])); |
2433
|
|
|
} |
2434
|
|
|
if (isset($this->PageInfo[$n]['rotation'])) { |
2435
|
|
|
$this->put('/Rotate ' . $this->PageInfo[$n]['rotation']); |
2436
|
|
|
} |
2437
|
|
|
$this->put('/Resources 2 0 R'); |
2438
|
|
|
if (isset($this->PageLinks[$n])) { |
2439
|
|
|
// Links |
2440
|
|
|
$annots = '/Annots ['; |
2441
|
|
|
foreach ($this->PageLinks[$n] as $pl) { |
2442
|
|
|
$rect = sprintf('%.2F %.2F %.2F %.2F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3]); |
2443
|
|
|
$annots .= '<</Type /Annot /Subtype /Link /Rect [' . $rect . '] /Border [0 0 0] '; |
2444
|
|
|
if (is_string($pl[4])) { |
2445
|
|
|
$annots .= '/A <</S /URI /URI ' . $this->textString($pl[4]) . '>>>>'; |
2446
|
|
|
} else { |
2447
|
|
|
$l = $this->links[$pl[4]]; |
2448
|
|
|
if (isset($this->PageInfo[$l[0]]['size'])) { |
2449
|
|
|
$h = $this->PageInfo[$l[0]]['size'][1]; |
2450
|
|
|
} else { |
2451
|
|
|
$h = ($this->DefOrientation == 'P') ? $this->DefPageSize[1] * $this->k : $this->DefPageSize[0] * $this->k; |
2452
|
|
|
} |
2453
|
|
|
$annots .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>', $this->PageInfo[$l[0]]['n'], $h - $l[1] * $this->k); |
2454
|
|
|
} |
2455
|
|
|
} |
2456
|
|
|
$this->put($annots . ']'); |
2457
|
|
|
} |
2458
|
|
|
if ($this->WithAlpha) { |
2459
|
|
|
$this->put('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>'); |
2460
|
|
|
} |
2461
|
|
|
$this->put('/Contents ' . ($this->n + 1) . ' 0 R>>'); |
2462
|
|
|
$this->put('endobj'); |
2463
|
|
|
// Page content |
2464
|
|
|
if (!empty($this->AliasNbPages)) { |
2465
|
|
|
$this->pages[$n] = str_replace($this->AliasNbPages, strval($this->page), $this->pages[$n]); |
2466
|
|
|
} |
2467
|
|
|
$this->putStreamObject($this->pages[$n]); |
2468
|
|
|
} |
2469
|
|
|
|
2470
|
|
|
/** |
2471
|
|
|
* Add fonts to the document. |
2472
|
|
|
*/ |
2473
|
|
|
protected function putFonts() : void |
2474
|
|
|
{ |
2475
|
|
|
foreach ($this->FontFiles as $file => $info) { |
2476
|
|
|
// Font file embedding |
2477
|
|
|
$this->newObject(); |
2478
|
|
|
$this->FontFiles[$file]['n'] = $this->n; |
2479
|
|
|
$font = file_get_contents($this->fontpath . $file, true); |
2480
|
|
|
if (!$font) { |
2481
|
|
|
$this->error('Font file not found: ' . $file); |
2482
|
|
|
} |
2483
|
|
|
$compressed = (substr($file, -2) == '.z'); |
2484
|
|
|
if (!$compressed && isset($info['length2'])) { |
2485
|
|
|
$font = substr($font, 6, $info['length1']) . substr($font, 6 + $info['length1'] + 6, $info['length2']); |
2486
|
|
|
} |
2487
|
|
|
$this->put('<</Length ' . strlen($font)); |
2488
|
|
|
if ($compressed) { |
2489
|
|
|
$this->put('/Filter /FlateDecode'); |
2490
|
|
|
} |
2491
|
|
|
$this->put('/Length1 ' . $info['length1']); |
2492
|
|
|
if (isset($info['length2'])) { |
2493
|
|
|
$this->put('/Length2 ' . $info['length2'] . ' /Length3 0'); |
2494
|
|
|
} |
2495
|
|
|
$this->put('>>'); |
2496
|
|
|
$this->putStream($font); |
2497
|
|
|
$this->put('endobj'); |
2498
|
|
|
} |
2499
|
|
|
foreach ($this->fonts as $k => $font) { |
2500
|
|
|
// Encoding |
2501
|
|
|
if (isset($font['diff'])) { |
2502
|
|
|
if (!isset($this->encodings[$font['enc']])) { |
2503
|
|
|
$this->newObject(); |
2504
|
|
|
$this->put('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' . $font['diff'] . ']>>'); |
2505
|
|
|
$this->put('endobj'); |
2506
|
|
|
$this->encodings[$font['enc']] = $this->n; |
2507
|
|
|
} |
2508
|
|
|
} |
2509
|
|
|
// ToUnicode CMap |
2510
|
|
|
$cmapkey = ''; |
2511
|
|
|
if (isset($font['uv'])) { |
2512
|
|
|
if (isset($font['enc'])) { |
2513
|
|
|
$cmapkey = $font['enc']; |
2514
|
|
|
} else { |
2515
|
|
|
$cmapkey = $font['name']; |
2516
|
|
|
} |
2517
|
|
|
if (!isset($this->cmaps[$cmapkey])) { |
2518
|
|
|
$cmap = $this->toUnicodeCMap($font['uv']); |
2519
|
|
|
$this->putStreamObject($cmap); |
2520
|
|
|
$this->cmaps[$cmapkey] = $this->n; |
2521
|
|
|
} |
2522
|
|
|
} |
2523
|
|
|
// Font object |
2524
|
|
|
$this->fonts[$k]['n'] = $this->n + 1; |
2525
|
|
|
$type = $font['type']; |
2526
|
|
|
$name = $font['name']; |
2527
|
|
|
if ($font['subsetted']) { |
2528
|
|
|
$name = 'AAAAAA+' . $name; |
2529
|
|
|
} |
2530
|
|
|
if ($type == 'Core') { |
2531
|
|
|
// Core font |
2532
|
|
|
$this->newObject(); |
2533
|
|
|
$this->put('<</Type /Font'); |
2534
|
|
|
$this->put('/BaseFont /' . $name); |
2535
|
|
|
$this->put('/Subtype /Type1'); |
2536
|
|
|
if ($name != 'Symbol' && $name != 'ZapfDingbats') { |
2537
|
|
|
$this->put('/Encoding /WinAnsiEncoding'); |
2538
|
|
|
} |
2539
|
|
|
if (isset($font['uv'])) { |
2540
|
|
|
$this->put('/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R'); |
2541
|
|
|
} |
2542
|
|
|
$this->put('>>'); |
2543
|
|
|
$this->put('endobj'); |
2544
|
|
|
} elseif ($type == 'Type1' || $type == 'TrueType') { |
2545
|
|
|
// Additional Type1 or TrueType/OpenType font |
2546
|
|
|
$this->newObject(); |
2547
|
|
|
$this->put('<</Type /Font'); |
2548
|
|
|
$this->put('/BaseFont /' . $name); |
2549
|
|
|
$this->put('/Subtype /' . $type); |
2550
|
|
|
$this->put('/FirstChar 32 /LastChar 255'); |
2551
|
|
|
$this->put('/Widths ' . ($this->n + 1) . ' 0 R'); |
2552
|
|
|
$this->put('/FontDescriptor ' . ($this->n + 2) . ' 0 R'); |
2553
|
|
|
if (isset($font['diff'])) { |
2554
|
|
|
$this->put('/Encoding ' . $this->encodings[$font['enc']] . ' 0 R'); |
2555
|
|
|
} else { |
2556
|
|
|
$this->put('/Encoding /WinAnsiEncoding'); |
2557
|
|
|
} |
2558
|
|
|
if (isset($font['uv'])) { |
2559
|
|
|
$this->put('/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R'); |
2560
|
|
|
} |
2561
|
|
|
$this->put('>>'); |
2562
|
|
|
$this->put('endobj'); |
2563
|
|
|
// Widths |
2564
|
|
|
$this->newObject(); |
2565
|
|
|
$cw = &$font['cw']; |
2566
|
|
|
$s = '['; |
2567
|
|
|
for ($i = 32; $i <= 255; $i++) { |
2568
|
|
|
$s .= $cw[chr($i)] . ' '; |
2569
|
|
|
} |
2570
|
|
|
$this->put($s . ']'); |
2571
|
|
|
$this->put('endobj'); |
2572
|
|
|
// Descriptor |
2573
|
|
|
$this->newObject(); |
2574
|
|
|
$s = '<</Type /FontDescriptor /FontName /' . $name; |
2575
|
|
|
foreach ($font['desc'] as $k2 => $v) { |
2576
|
|
|
$s .= ' /' . $k2 . ' ' . $v; |
2577
|
|
|
} |
2578
|
|
|
if (!empty($font['file'])) { |
2579
|
|
|
$s .= ' /FontFile' . ($type == 'Type1' ? '' : '2') . ' ' . $this->FontFiles[$font['file']]['n'] . ' 0 R'; |
2580
|
|
|
} |
2581
|
|
|
$this->put($s . '>>'); |
2582
|
|
|
$this->put('endobj'); |
2583
|
|
|
} else { |
2584
|
|
|
// Allow for additional types |
2585
|
|
|
$mtd = '_put' . strtolower($type); |
2586
|
|
|
if (!method_exists($this, $mtd)) { |
2587
|
|
|
$this->error('Unsupported font type: ' . $type); |
2588
|
|
|
} |
2589
|
|
|
$this->$mtd($font); |
2590
|
|
|
} |
2591
|
|
|
} |
2592
|
|
|
} |
2593
|
|
|
|
2594
|
|
|
/** |
2595
|
|
|
* @param array $uv |
2596
|
|
|
* @return string |
2597
|
|
|
*/ |
2598
|
|
|
protected function toUnicodeCMap(array $uv) : string |
2599
|
|
|
{ |
2600
|
|
|
$ranges = ''; |
2601
|
|
|
$nbr = 0; |
2602
|
|
|
$chars = ''; |
2603
|
|
|
$nbc = 0; |
2604
|
|
|
foreach ($uv as $c => $v) { |
2605
|
|
|
if (is_array($v)) { |
2606
|
|
|
$ranges .= sprintf("<%02X> <%02X> <%04X>\n", $c, $c + $v[1] - 1, $v[0]); |
2607
|
|
|
$nbr++; |
2608
|
|
|
} else { |
2609
|
|
|
$chars .= sprintf("<%02X> <%04X>\n", $c, $v); |
2610
|
|
|
$nbc++; |
2611
|
|
|
} |
2612
|
|
|
} |
2613
|
|
|
$s = "/CIDInit /ProcSet findresource begin\n"; |
2614
|
|
|
$s .= "12 dict begin\n"; |
2615
|
|
|
$s .= "begincmap\n"; |
2616
|
|
|
$s .= "/CIDSystemInfo\n"; |
2617
|
|
|
$s .= "<</Registry (Adobe)\n"; |
2618
|
|
|
$s .= "/Ordering (UCS)\n"; |
2619
|
|
|
$s .= "/Supplement 0\n"; |
2620
|
|
|
$s .= ">> def\n"; |
2621
|
|
|
$s .= "/CMapName /Adobe-Identity-UCS def\n"; |
2622
|
|
|
$s .= "/CMapType 2 def\n"; |
2623
|
|
|
$s .= "1 begincodespacerange\n"; |
2624
|
|
|
$s .= "<00> <FF>\n"; |
2625
|
|
|
$s .= "endcodespacerange\n"; |
2626
|
|
|
if ($nbr > 0) { |
2627
|
|
|
$s .= "$nbr beginbfrange\n"; |
2628
|
|
|
$s .= $ranges; |
2629
|
|
|
$s .= "endbfrange\n"; |
2630
|
|
|
} |
2631
|
|
|
if ($nbc > 0) { |
2632
|
|
|
$s .= "$nbc beginbfchar\n"; |
2633
|
|
|
$s .= $chars; |
2634
|
|
|
$s .= "endbfchar\n"; |
2635
|
|
|
} |
2636
|
|
|
$s .= "endcmap\n"; |
2637
|
|
|
$s .= "CMapName currentdict /CMap defineresource pop\n"; |
2638
|
|
|
$s .= "end\n"; |
2639
|
|
|
$s .= "end"; |
2640
|
|
|
return $s; |
2641
|
|
|
} |
2642
|
|
|
|
2643
|
|
|
/** |
2644
|
|
|
* Add all containing images to the document. |
2645
|
|
|
*/ |
2646
|
|
|
protected function putImages() : void |
2647
|
|
|
{ |
2648
|
|
|
foreach (array_keys($this->images) as $file) { |
2649
|
|
|
$this->putImage($this->images[$file]); |
2650
|
|
|
unset($this->images[$file]['data']); |
2651
|
|
|
unset($this->images[$file]['smask']); |
2652
|
|
|
} |
2653
|
|
|
} |
2654
|
|
|
|
2655
|
|
|
/** |
2656
|
|
|
* Add image to the document. |
2657
|
|
|
* @param array $info |
2658
|
|
|
*/ |
2659
|
|
|
protected function putImage(array &$info) : void |
2660
|
|
|
{ |
2661
|
|
|
$this->newObject(); |
2662
|
|
|
$info['n'] = $this->n; |
2663
|
|
|
$this->put('<</Type /XObject'); |
2664
|
|
|
$this->put('/Subtype /Image'); |
2665
|
|
|
$this->put('/Width ' . $info['w']); |
2666
|
|
|
$this->put('/Height ' . $info['h']); |
2667
|
|
|
if ($info['cs'] == 'Indexed') { |
2668
|
|
|
$this->put('/ColorSpace [/Indexed /DeviceRGB ' . (strlen($info['pal']) / 3 - 1) . ' ' . ($this->n + 1) . ' 0 R]'); |
2669
|
|
|
} else { |
2670
|
|
|
$this->put('/ColorSpace /' . $info['cs']); |
2671
|
|
|
if ($info['cs'] == 'DeviceCMYK') { |
2672
|
|
|
$this->put('/Decode [1 0 1 0 1 0 1 0]'); |
2673
|
|
|
} |
2674
|
|
|
} |
2675
|
|
|
$this->put('/BitsPerComponent ' . $info['bpc']); |
2676
|
|
|
if (isset($info['f'])) { |
2677
|
|
|
$this->put('/Filter /' . $info['f']); |
2678
|
|
|
} |
2679
|
|
|
if (isset($info['dp'])) { |
2680
|
|
|
$this->put('/DecodeParms <<' . $info['dp'] . '>>'); |
2681
|
|
|
} |
2682
|
|
|
if (isset($info['trns']) && is_array($info['trns'])) { |
2683
|
|
|
$trns = ''; |
2684
|
|
|
$cnt = count($info['trns']); |
2685
|
|
|
for ($i = 0; $i < $cnt; $i++) { |
2686
|
|
|
$trns .= $info['trns'][$i] . ' ' . $info['trns'][$i] . ' '; |
2687
|
|
|
} |
2688
|
|
|
$this->put('/Mask [' . $trns . ']'); |
2689
|
|
|
} |
2690
|
|
|
if (isset($info['smask'])) { |
2691
|
|
|
$this->put('/SMask ' . ($this->n + 1) . ' 0 R'); |
2692
|
|
|
} |
2693
|
|
|
$this->put('/Length ' . strlen($info['data']) . '>>'); |
2694
|
|
|
$this->putStream($info['data']); |
2695
|
|
|
$this->put('endobj'); |
2696
|
|
|
// Soft mask |
2697
|
|
|
if (isset($info['smask'])) { |
2698
|
|
|
$dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' . $info['w']; |
2699
|
|
|
$smask = array( |
2700
|
|
|
'w'=>$info['w'], |
2701
|
|
|
'h'=>$info['h'], |
2702
|
|
|
'cs'=>'DeviceGray', |
2703
|
|
|
'bpc'=>8, |
2704
|
|
|
'f'=>$info['f'], |
2705
|
|
|
'dp'=>$dp, |
2706
|
|
|
'data'=>$info['smask'] |
2707
|
|
|
); |
2708
|
|
|
$this->putImage($smask); |
2709
|
|
|
} |
2710
|
|
|
// Palette |
2711
|
|
|
if ($info['cs'] == 'Indexed') { |
2712
|
|
|
$this->putStreamObject($info['pal']); |
2713
|
|
|
} |
2714
|
|
|
} |
2715
|
|
|
|
2716
|
|
|
/** |
2717
|
|
|
* |
2718
|
|
|
*/ |
2719
|
|
|
protected function putXObjectDict() : void |
2720
|
|
|
{ |
2721
|
|
|
$this->put('/XObject <<'); |
2722
|
|
|
foreach ($this->images as $image) { |
2723
|
|
|
$this->put('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); |
2724
|
|
|
} |
2725
|
|
|
$this->put('>>'); |
2726
|
|
|
} |
2727
|
|
|
|
2728
|
|
|
/** |
2729
|
|
|
* Insert the fonts dictionary. |
2730
|
|
|
*/ |
2731
|
|
|
protected function putFontsDict() : void |
2732
|
|
|
{ |
2733
|
|
|
$this->put('/Font <<'); |
2734
|
|
|
foreach ($this->fonts as $font) { |
2735
|
|
|
$this->put('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); |
2736
|
|
|
} |
2737
|
|
|
$this->put('>>'); |
2738
|
|
|
} |
2739
|
|
|
|
2740
|
|
|
/** |
2741
|
|
|
* Insert the resource dictionary. |
2742
|
|
|
*/ |
2743
|
|
|
protected function putResourceDict() : void |
2744
|
|
|
{ |
2745
|
|
|
$this->newObject(2); |
2746
|
|
|
$this->put('<<'); |
2747
|
|
|
$this->put('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); |
2748
|
|
|
$this->putFontsDict(); |
2749
|
|
|
$this->putXObjectDict(); |
2750
|
|
|
$this->put('>>'); |
2751
|
|
|
$this->put('endobj'); |
2752
|
|
|
} |
2753
|
|
|
|
2754
|
|
|
/** |
2755
|
|
|
* Insert all resources. |
2756
|
|
|
* - fonts |
2757
|
|
|
* - images |
2758
|
|
|
* - resource dictonary |
2759
|
|
|
*/ |
2760
|
|
|
protected function putResources() : void |
2761
|
|
|
{ |
2762
|
|
|
$this->putFonts(); |
2763
|
|
|
$this->putImages(); |
2764
|
|
|
} |
2765
|
|
|
|
2766
|
|
|
/** |
2767
|
|
|
* |
2768
|
|
|
*/ |
2769
|
|
|
protected function putBookmarks() : void |
2770
|
|
|
{ |
2771
|
|
|
$nb = count($this->outlines); |
2772
|
|
|
if ($nb == 0) { |
2773
|
|
|
return; |
2774
|
|
|
} |
2775
|
|
|
$lru = array(); |
2776
|
|
|
$level = 0; |
2777
|
|
|
foreach ($this->outlines as $i => $o) { |
2778
|
|
|
if ($o['l'] > 0) { |
2779
|
|
|
$parent = $lru[$o['l'] - 1]; |
2780
|
|
|
// Set parent and last pointers |
2781
|
|
|
$this->outlines[$i]['parent'] = $parent; |
2782
|
|
|
$this->outlines[$parent]['last'] = $i; |
2783
|
|
|
if ($o['l'] > $level) { |
2784
|
|
|
// Level increasing: set first pointer |
2785
|
|
|
$this->outlines[$parent]['first'] = $i; |
2786
|
|
|
} |
2787
|
|
|
} else { |
2788
|
|
|
$this->outlines[$i]['parent'] = $nb; |
2789
|
|
|
} |
2790
|
|
|
if ($o['l'] <= $level && $i > 0) { |
2791
|
|
|
// Set prev and next pointers |
2792
|
|
|
$prev = $lru[$o['l']]; |
2793
|
|
|
$this->outlines[$prev]['next'] = $i; |
2794
|
|
|
$this->outlines[$i]['prev'] = $prev; |
2795
|
|
|
} |
2796
|
|
|
$lru[$o['l']] = $i; |
2797
|
|
|
$level = $o['l']; |
2798
|
|
|
} |
2799
|
|
|
// Outline items |
2800
|
|
|
$n = $this->n + 1; |
2801
|
|
|
foreach ($this->outlines as $i => $o) { |
2802
|
|
|
$this->newObject(); |
2803
|
|
|
$this->put('<</Title ' . $this->textString($o['t'])); |
2804
|
|
|
$this->put('/Parent ' . ($n + $o['parent']) . ' 0 R'); |
2805
|
|
|
if (isset($o['prev'])) { |
2806
|
|
|
$this->put('/Prev ' . ($n + $o['prev']) . ' 0 R'); |
2807
|
|
|
} |
2808
|
|
|
if (isset($o['next'])) { |
2809
|
|
|
$this->put('/Next ' . ($n + $o['next']) . ' 0 R'); |
2810
|
|
|
} |
2811
|
|
|
if (isset($o['first'])) { |
2812
|
|
|
$this->put('/First ' . ($n + $o['first']) . ' 0 R'); |
2813
|
|
|
} |
2814
|
|
|
if (isset($o['last'])) { |
2815
|
|
|
$this->put('/Last ' . ($n + $o['last']) . ' 0 R'); |
2816
|
|
|
} |
2817
|
|
|
$this->put(sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]', $this->PageInfo[$o['p']]['n'], $o['y'])); |
2818
|
|
|
$this->put('/Count 0>>'); |
2819
|
|
|
$this->put('endobj'); |
2820
|
|
|
} |
2821
|
|
|
// Outline root |
2822
|
|
|
$this->newObject(); |
2823
|
|
|
$this->outlineRoot = $this->n; |
2824
|
|
|
$this->put('<</Type /Outlines /First ' . $n . ' 0 R'); |
2825
|
|
|
$this->put('/Last ' . ($n + $lru[0]) . ' 0 R>>'); |
2826
|
|
|
$this->put('endobj'); |
2827
|
|
|
} |
2828
|
|
|
|
2829
|
|
|
/** |
2830
|
|
|
* create the file info. |
2831
|
|
|
*/ |
2832
|
|
|
protected function putInfo() : void |
2833
|
|
|
{ |
2834
|
|
|
$this->newObject(); |
2835
|
|
|
$this->put('<<'); |
2836
|
|
|
$this->metadata['Producer'] = 'FPDF ' . FPDF_VERSION; |
2837
|
|
|
$this->metadata['CreationDate'] = 'D:' . @date('YmdHis'); |
2838
|
|
|
foreach ($this->metadata as $key => $value) { |
2839
|
|
|
$this->put('/' . $key . ' ' . $this->textString($value)); |
2840
|
|
|
} |
2841
|
|
|
$this->put('>>'); |
2842
|
|
|
$this->put('endobj'); |
2843
|
|
|
} |
2844
|
|
|
|
2845
|
|
|
/** |
2846
|
|
|
* Create catalog. |
2847
|
|
|
* - zoom mode |
2848
|
|
|
* - page layout |
2849
|
|
|
* - outlines |
2850
|
|
|
*/ |
2851
|
|
|
protected function putCatalog() : void |
2852
|
|
|
{ |
2853
|
|
|
$this->newObject(); |
2854
|
|
|
$this->put('<<'); |
2855
|
|
|
|
2856
|
|
|
$n = $this->PageInfo[1]['n']; |
2857
|
|
|
$this->put('/Type /Catalog'); |
2858
|
|
|
$this->put('/Pages 1 0 R'); |
2859
|
|
|
if ($this->ZoomMode == 'fullpage') { |
2860
|
|
|
$this->put('/OpenAction [' . $n . ' 0 R /Fit]'); |
2861
|
|
|
} elseif ($this->ZoomMode == 'fullwidth') { |
2862
|
|
|
$this->put('/OpenAction [' . $n . ' 0 R /FitH null]'); |
2863
|
|
|
} elseif ($this->ZoomMode == 'real') { |
2864
|
|
|
$this->put('/OpenAction [' . $n . ' 0 R /XYZ null null 1]'); |
2865
|
|
|
} elseif (!is_string($this->ZoomMode)) { |
2866
|
|
|
$this->put('/OpenAction [' . $n . ' 0 R /XYZ null null ' . sprintf('%.2F', $this->ZoomMode / 100) . ']'); |
2867
|
|
|
} |
2868
|
|
|
if ($this->LayoutMode == 'single') { |
2869
|
|
|
$this->put('/PageLayout /SinglePage'); |
2870
|
|
|
} elseif ($this->LayoutMode == 'continuous') { |
2871
|
|
|
$this->put('/PageLayout /OneColumn'); |
2872
|
|
|
} elseif ($this->LayoutMode == 'two') { |
2873
|
|
|
$this->put('/PageLayout /TwoColumnLeft'); |
2874
|
|
|
} |
2875
|
|
|
if (count($this->outlines) > 0) { |
2876
|
|
|
$this->put('/Outlines ' . $this->outlineRoot . ' 0 R'); |
2877
|
|
|
$this->put('/PageMode /UseOutlines'); |
2878
|
|
|
} |
2879
|
|
|
|
2880
|
|
|
$this->put('>>'); |
2881
|
|
|
$this->put('endobj'); |
2882
|
|
|
} |
2883
|
|
|
|
2884
|
|
|
/** |
2885
|
|
|
* Create Header. |
2886
|
|
|
* Header only contains the PDF Version |
2887
|
|
|
*/ |
2888
|
|
|
protected function putHeader() : void |
2889
|
|
|
{ |
2890
|
|
|
$this->put('%PDF-' . $this->PDFVersion); |
2891
|
|
|
} |
2892
|
|
|
|
2893
|
|
|
/** |
2894
|
|
|
* Create Trailer. |
2895
|
|
|
* |
2896
|
|
|
*/ |
2897
|
|
|
protected function putTrailer() : void |
2898
|
|
|
{ |
2899
|
|
|
$this->put('trailer'); |
2900
|
|
|
$this->put('<<'); |
2901
|
|
|
$this->put('/Size ' . ($this->n + 1)); |
2902
|
|
|
$this->put('/Root ' . $this->n . ' 0 R'); |
2903
|
|
|
$this->put('/Info ' . ($this->n - 1) . ' 0 R'); |
2904
|
|
|
$this->put('>>'); |
2905
|
|
|
} |
2906
|
|
|
|
2907
|
|
|
/** |
2908
|
|
|
* End of document. |
2909
|
|
|
* Generate the whole document into internal buffer: <ul> |
2910
|
|
|
* <li> header (PDF Version)</li> |
2911
|
|
|
* <li> pages </li> |
2912
|
|
|
* <li> ressources (fonts, images) </li> |
2913
|
|
|
* <li> ressources dictionary </li> |
2914
|
|
|
* <li> bookmarks </li> |
2915
|
|
|
* <li> fileinfo </li> |
2916
|
|
|
* <li> zoommode, layout </li> |
2917
|
|
|
* <li> page - ref's </li> |
2918
|
|
|
* <li> trailer </li></ul> |
2919
|
|
|
*/ |
2920
|
|
|
protected function endDoc() : void |
2921
|
|
|
{ |
2922
|
|
|
$this->putHeader(); |
2923
|
|
|
$this->putPages(); |
2924
|
|
|
$this->putResources(); |
2925
|
|
|
$this->putResourceDict(); |
2926
|
|
|
$this->putBookmarks(); |
2927
|
|
|
|
2928
|
|
|
// Info |
2929
|
|
|
$this->putInfo(); |
2930
|
|
|
|
2931
|
|
|
// Catalog (zoommode, page layout) |
2932
|
|
|
$this->putCatalog(); |
2933
|
|
|
|
2934
|
|
|
// Cross-ref |
2935
|
|
|
$offset = $this->getOffset(); |
2936
|
|
|
$this->put('xref'); |
2937
|
|
|
$this->put('0 ' . ($this->n + 1)); |
2938
|
|
|
$this->put('0000000000 65535 f '); |
2939
|
|
|
for ($i = 1; $i <= $this->n; $i++) { |
2940
|
|
|
$this->put(sprintf('%010d 00000 n ', $this->offsets[$i])); |
2941
|
|
|
} |
2942
|
|
|
// Trailer |
2943
|
|
|
$this->putTrailer(); |
2944
|
|
|
|
2945
|
|
|
// EOF |
2946
|
|
|
$this->put('startxref'); |
2947
|
|
|
$this->put(strval($offset)); |
2948
|
|
|
$this->put('%%EOF'); |
2949
|
|
|
$this->state = 3; |
2950
|
|
|
} |
2951
|
|
|
} |
2952
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.