Passed
Push — main ( fca3a6...3a25b2 )
by Stefan
02:26
created

XPDF::resetCols()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 13
rs 9.9
c 0
b 0
f 0
1
<?php
2
namespace SKien\XFPDF;
3
4
use OPlathey\FPDF\FPDF;
5
6
/**
7
 * Extension of FPDF-Class to generate tables/datagrid with: <ul>
8
 * <li> Page Header/Footer including Logo </li>
9
 * <li> Colheaders </li>
10
 * <li> Totals, Subtotals, Pagetotals and Carry over </li>
11
 * <li> Use defined fonts, colors and stripped datarows </li>
12
 * <li> Specify print format with JSOn template file </li></ul>
13
 * 
14
 * #### History
15
 * - *2020-04-21*   Integrated extension to set bookmarks from O.Plathey
16
 * - *2020-04-21*   Added new functions to set internal links within the datagrid
17
 * - *2020-11-18*   Moved XPDFFont into a separate file to correspond to PSR-0 / PSR-4 (one file per class) 
18
 * - *2020-11-18*   Set namespace to fit PSR-4 recommendations for autoloading.
19
 * - *2020-11-18*   Added missing PHP 7.4 type hints / docBlock changes 
20
 * - *2020-11-21*   Support of image columns 
21
 * - *2020-11-23*   Added separate font for subject in page header (2'nd line) 
22
 * - *2020-11-23*   customizeable Height of the header logo  
23
 *
24
 * @package SKien/XFPDF
25
 * @since 1.1.0
26
 * @version 1.1.0
27
 * @author Stefanius <[email protected]>
28
 * @copyright MIT License - see the LICENSE file for details
29
 */
30
class XPDF extends FPDF
31
{
32
    /** predifined Col-ID for automated row number */
33
    const COL_ROW_NR = 1000;
34
    
35
    /** Bottom margin for trigger of the auto pagebreak */
36
    const BOTTOM_MARGIN = 12;
37
    
38
    /** totals info                         */
39
    const FLAG_TOTALS = 0x0007;
40
    /** calc total for column               */ 
41
    const FLAG_TOTALS_CALC = 0x0001;
42
    /** print text in totals row            */
43
    const FLAG_TOTALS_TEXT = 0x0002;
44
    /** leave empty in totals row           */
45
    const FLAG_TOTALS_EMPTY = 0x0004;
46
    /** create internal link                */
47
    const FLAG_INT_LINK = 0x0008;
48
    /** special format for the cell         */
49
    const FLAG_FORMAT = 0x00F0;
50
    /** format cell as currency with symbol */
51
    const FLAG_CUR_SYMBOL = 0x0010;
52
    /** format cell as currency without symbol  */
53
    const FLAG_CUR_PLAIN = 0x0020;
54
    /** format cell as date/time            */
55
    const FLAG_DATE = 0x0030;
56
    /** format cell as date/time            */
57
    const FLAG_TIME = 0x0040;
58
    /** format cell as date/time            */
59
    const FLAG_DATE_TIME = 0x0050;
60
    /** format cell as number               */
61
    const FLAG_NUMBER = 0x0060;
62
    /** cell containes image                */
63
    const FLAG_IMAGE = 0x0100;
64
    /** suppress zero values                */
65
    const FLAG_NO_ZERO = 0x0200;
66
    
67
    
68
    /** crate totals row on the end of report  */
69
    const TOTALS = 0x01;
70
    /** create totals row on each pagebreak    */
71
    const PAGE_TOTALS = 0x02;
72
    /** create carry over on the beginning of each new page    */
73
    const CARRY_OVER = 0x04;
74
    /** create     */
75
    const SUB_TOTALS = 0x08;
76
    
77
    /** @var string pageheader  */
78
    protected string $strPageTitle;
79
    /** @var string logo    */
80
    protected string $strLogo;
81
    /** @var float height of the loge in user units    */
82
    protected float $fltLogoHeight;
83
    /** @var string subject in page header  */
84
    protected string $strPageSubject;
85
    /** @var string pagefooter  */
86
    protected string $strPageFooter;
87
    /** @var XPDFFont font to use in header of the document */
88
    protected XPDFFont $fontHeader;
89
    /** @var XPDFFont font to use for subject in the header of the document */
90
    protected XPDFFont $fontSubject;
91
    /** @var XPDFFont font to use in footer of the document */
92
    protected XPDFFont $fontFooter;
93
    /** @var XPDFFont font to use in col headers of the grid    */
94
    protected XPDFFont $fontColHeader;
95
    /** @var XPDFFont font to use in sub headers of the grid    */
96
    protected XPDFFont $fontSubHeader;
97
    /** @var XPDFFont font to use in rows of a grid */
98
    protected XPDFFont $fontRows;
99
    /** @var string textcolor to use in header of the document  */
100
    protected string $strHeaderTextColor = '#000000';
101
    /** @var string textcolor to use in footer of the document  */
102
    protected string $strFooterTextColor = '#000000';
103
    /** @var string textcolor to use in colheader of the document   */
104
    protected string $strColHeaderTextColor = '#00007F';
105
    /** @var string textcolor to use in subheader of the document   */
106
    protected string $strSubHeaderTextColor = '#000000';
107
    /** @var string textcolor to use in rows of the document    */
108
    protected string $strRowTextColor = '#000000';
109
    /** @var string textcolor to use for internal links */
110
    protected string $strLinkTextColor = '#0000FF';
111
    /** @var string drawcolor to use in header of the document  */
112
    protected string $strHeaderDrawColor = '#404040';
113
    /** @var string drawcolor to use in footer of the document  */
114
    protected string $strFooterDrawColor = '#404040';
115
    /** @var string fillcolor to use in colheader of the document   */
116
    protected string $strColHeaderFillColor = '#D7D7D7';
117
    /** @var string fillcolor to use in subheader of the document   */
118
    protected string $strSubHeaderFillColor = '#A7A7A7';
119
    /** @var bool   strip datarows for better contrast   */
120
    protected bool $bStripped = true;
121
    /** @var string drawcolor to use in rows of the document (striped)  */
122
    protected string $strRowDrawColor = '#404040';
123
    /** @var string fillcolor to use in rows of the document (striped)  */
124
    protected string $strRowFillColor = '#E0EBFF';
125
    /** @var bool   currently inside of of grid      */
126
    protected bool $bInGrid = false;
127
    /** @var int|string border   */
128
    protected $border = 1;
129
    /** @var int        index of last col    */
130
    protected int $iMaxCol = -1;
131
    /** @var int        index of last title col (in case of colspans in header < $iMaxCol        */
132
    protected int $iMaxColHeader = -1;
133
    /** @var array      titles for the table header      */
134
    protected array $aColHeader = Array();
135
    /** @var array      width of each col in percent         */
136
    protected array $aColWidth = Array();
137
    /** @var array      align of each datacol (header always center)         */
138
    protected array $aColAlign = Array();
139
    /** @var array      flags for each datacol       */
140
    protected array $aColFlags = Array();
141
    /** @var array      fieldname or number of each datacol      */
142
    protected array $aColField = Array();
143
    /** @var array      colspan of the headercols        */
144
    protected array $aColSpan  = Array();
145
    /** @var array      info for image cols      */
146
    protected array $aImgInfo  = Array();
147
    /** @var bool       enable automatic calculation of totals       */
148
    protected bool $bCalcTotals = false;
149
    /** @var string     text for totals      */
150
    protected string $strTotals = 'Total:';
151
    /** @var bool       print subtotals on each pagebreak        */
152
    protected bool $bPageTotals = false;
153
    /** @var string     text for subtotals       */
154
    protected string $strPageTotals = '';
155
    /** @var bool       print carry over on top of each new page         */
156
    protected bool $bCarryOver = false;
157
    /** @var string     text for carry over      */
158
    protected string $strCarryOver = 'Carry over:';
159
    /** @var string     text for subtotals       */
160
    protected string $strSubTotals = 'Subtotal:';
161
    /** @var int        index of last totals col         */
162
    protected int $iMaxColTotals = -1;
163
    /** @var array      calculated totals        */
164
    protected array $aTotals = Array();
165
    /** @var array      calculated subtotals         */
166
    protected array $aSubTotals = Array();
167
    /** @var array      colspan of the totals        */
168
    protected array $aTotalsColSpan = Array();
169
    /** @var int        current rownumber    */
170
    protected int $iRow;
171
    /** @var float      lineheight in mm     */
172
    protected float $fltLineHeight = 8.0;
173
    /** @var string      */
174
    protected string $strCharset = 'UTF-8';
175
    /** @var string      */
176
    protected string $strLocale = '';
177
    /** @var string     format for Date cell     */
178
    protected string $strFormatD = '%Y-%d-%m';
179
    /** @var string     format for Time cell     */
180
    protected string $strFormatT = '%H:%M';
181
    /** @var string     format for Datetime cell     */
182
    protected string $strFormatDT = '%Y-%d-%m %H:%M';
183
    /** @var int        decimals for number format       */
184
    protected int $iNumberDecimals = 2;
185
    /** @var string     prefix for number format */
186
    protected string $strNumberPrefix = '';
187
    /** @var string     suffix for number format */
188
    protected string $strNumberSuffix = ''; 
189
    /** @var bool       setlocale() not called or returned false!   */
190
    protected bool $bInvalidLocale = true;
191
    
192
    /**
193
     * class constructor.
194
     * allows to set up the page size, the orientation and the unit of measure used in all methods (except for font sizes).
195
     * @param string $orientation
196
     * @param string $unit
197
     * @param string|array $size
198
     * @see FPDF::__construct()
199
     */
200
    public function __construct(string $orientation = 'P', string $unit = 'mm', $size = 'A4') 
201
    {
202
        parent::__construct($orientation, $unit, $size);
203
        
204
        $this->setDisplayMode('fullpage', 'single');
205
        $this->setAutoPageBreak(true, self::BOTTOM_MARGIN);
206
        $this->aliasNbPages('{NP}');
207
        $this->setLocale("en_US.utf8, en_US");
208
        
209
        
210
        $this->strPageTitle = '';
211
        $this->strPageFooter = "{PN}/{NP}\t{D} {T}";
212
        $this->strLogo = '';
213
        $this->fltLogoHeight = 8.0;
214
        
215
        $this->iRow = 0;
216
        
217
        $this->fontHeader = new XPDFFont('Arial', 'B', 12);
218
        $this->fontSubject = new XPDFFont('Arial', 'I', 8);
219
        $this->fontFooter = new XPDFFont('Arial', 'I', 8);
220
        $this->fontColHeader = new XPDFFont('Arial', 'B', 10);
221
        $this->fontSubHeader = new XPDFFont('Arial', 'B', 10);
222
        $this->fontRows = new XPDFFont('Arial', '', 10);
223
    }
224
225
    /**
226
     * Set information for document to create.
227
     * @param string $strTitle
228
     * @param string $strSubject
229
     * @param string $strAuthor
230
     * @param string $strKeywords
231
     * @param bool $isUTF8  Indicates if the strings encoded in ISO-8859-1 (false) or UTF-8 (true). (Default: false)
232
     */
233
    public function setInfo(string $strTitle, string $strSubject, string $strAuthor, string $strKeywords = '', bool $isUTF8 = false) : void 
234
    {
235
        $this->setTitle($strTitle, $isUTF8);
236
        $this->setSubject($strSubject, $isUTF8);
237
        $this->setAuthor($strAuthor, $isUTF8);
238
        $this->setKeywords($strKeywords, $isUTF8);
239
        $this->setCreator('FPDF - Dokument Generator');
240
    }
241
    
242
    /**
243
     * Set charset. 
244
     * for europe/germany should be set to 'windows-1252//TRANSLIT' to support 
245
     * proper Euro-sign and umlauts. <br/> 
246
     * <br/>
247
     * Can be controled through JSON-Format in InitGrid()
248
     * @param string $strCharset
249
     * @see XPDF::initGrid()
250
     * @link http://www.php.net/manual/en/function.iconv.php
251
     */
252
    public function setCharset(string $strCharset) : void 
253
    {
254
        $this->strCharset = $strCharset;
255
    }
256
    
257
    /**
258
     * Set locale for formating.
259
     * If $strLocale is an comma separated list each value is tried to be 
260
     * set as new locale until success. <br/>
261
     * Example: <i>"de_DE.utf8, de_DE, de, DE"</i><br/>  
262
     * <br/>
263
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
264
     * @param string $strLocale
265
     * @see XPDF::initGrid()
266
     * @link http://www.php.net/manual/en/function.setlocale.php
267
     */
268
    public function setLocale(string $strLocale) : void 
269
    {
270
        if ($this->strLocale != $strLocale) {
271
            $this->strLocale = $strLocale;
272
            
273
            // if locale contains multiple coma separated values, just explode and trim...
274
            $locale = $this->strLocale;
275
            if (strpos($this->strLocale, ',')) {
276
                $locale = array_map('trim', explode(',', $this->strLocale));
277
            }
278
            $this->bInvalidLocale = false;
279
            if (setlocale(LC_ALL, $locale) === false) {
280
                trigger_error('setlocale("' . $this->strLocale . '") failed!', E_USER_NOTICE);
281
                // TODO: obsolete?
282
                $this->bInvalidLocale = true;
283
            }
284
        }
285
    }
286
    
287
    /**
288
     * Set the pageheader of the document.
289
     * The title is printed in the left of the pageheader using the font set with XPDF:SetHeaderFont() <br/>
290
     * Optional the subject and/or the logo can be set.
291
     * Subject and logo can also be set using XPDF:SetSubject() and XPDF:SetLogo() <br/>
292
     * The page header is separated from the report by a double line.
293
     * @param string $strTitle         Title of the Report
294
     * @param string $strHeaderSubject Subject of the Report
295
     * @param string $strLogo          Logo to print.
296
     * @see XPDF:SetHeaderFont()
297
     * @see XPDF:SetSubject()
298
     * @see XPDF:SetLogo()  
299
     */
300
    public function setPageHeader(string $strTitle, string $strHeaderSubject = '', string $strLogo = '') : void
301
    {
302
        $this->strPageTitle = $strTitle;
303
        if (strlen($strLogo) > 0) {
304
            $this->strLogo = $strLogo;
305
        }
306
        $this->strPageSubject = $strHeaderSubject;
307
    }
308
    
309
    /**
310
     * Set the subject in the pageheader of the document.
311
     * The subject is printed in the left of the pageheader in the 2'nd line using the font set 
312
     * with XPDF:SetSubjectFont() <br/>
313
     * @param string $strPageSubject
314
     * @see XPDF:SetSubjectFont()
315
     */
316
    public function setPageSubject(string $strPageSubject) : void
317
    {
318
        $this->strPageSubject = $strPageSubject;
319
    }
320
    
321
    /**
322
     * Set logo printed in the document header.
323
     * The logo is printed right-aligned in the header, and by default,  the logo will be 
324
     * scaled to a height of 8mm. Another height can be set with XPDF::setLogoHeight(). <br/>
325
     * For convinience, the loge can be set directly within XPDF::setPageHeader().
326
     * @param string $strLogo  image file to print.
327
     * @see XPDF::setLogoHeight()
328
     * @see XPDF::setPageHeader()
329
     */
330
    public function setLogo(string $strLogo) : void
331
    {
332
        $this->strLogo = $strLogo;
333
    }
334
    
335
    /**
336
     * Set height of the logo in the document header.
337
     * @param float $fltLogoHeight height of the logo image
338
     */
339
    public function setLogoHeight(float $fltLogoHeight) : void
340
    {
341
        $this->fltLogoHeight = $fltLogoHeight;
342
    }
343
    
344
    /**
345
     * Set the pagefooter of the document.
346
     * @param string $strFooter     The footer can consist of up to three sections delimitet by TAB <b>('\t')</b><br/>
347
     *      possible placeholders are <ul>
348
     *      <li> '{D}'  -> current date (DD.MM.YYYY) </li>
349
     *      <li> '{T}'  -> current time (HH:ii) </li>
350
     *      <li> '{PN}' -> pagenumber </li>
351
     *      <li> '{NP}' -> total number of pages </li></ul>
352
     * default footer is: <b>'Page {PN}/{NP}\t{D}  {T}' </b>
353
     */
354
    public function setPageFooter($strFooter) : void
355
    {
356
        $this->strPageFooter = $strFooter;
357
    }
358
    
359
    /**
360
     * Initialisation of grid. <ul>
361
     * <li> fonts </li>
362
     * <li> colors </li>
363
     * <li> totals/subtotals/carry over text </li>
364
     * <li> charset </li>
365
     * <li> formating </li></ul>
366
     * See xfpdf-sample.json for more information about this file.
367
     * @param string $strFilename
368
     */
369
    public function initGrid(string $strFilename) : void
370
    {
371
        if (file_exists($strFilename)) {
372
            $strJSON = file_get_contents($strFilename);
373
            $jsonData = json_decode($strJSON);
374
            if ($jsonData) {
375
                $this->fontHeader = $this->propertyFont($jsonData, 'fontHeader', $this->fontHeader);
376
                $this->fontSubject = $this->propertyFont($jsonData, 'fontSubject', $this->fontSubject);
377
                $this->fontFooter = $this->propertyFont($jsonData, 'fontFooter', $this->fontFooter);
378
                $this->fontColHeader = $this->propertyFont($jsonData, 'fontColHeader', $this->fontColHeader);
379
                $this->fontSubHeader = $this->propertyFont($jsonData, 'fontSubHeader', $this->fontSubHeader);
380
                $this->fontRows = $this->propertyFont($jsonData, 'fontRows', $this->fontRows);
381
                
382
                $this->fltLineHeight = $this->property($jsonData, 'dblLineHeight', $this->fltLineHeight);
383
                $this->fltLineHeight = $this->property($jsonData, 'fltLineHeight', $this->fltLineHeight);
384
                
385
                $this->strHeaderTextColor = $this->property($jsonData, 'strHeaderTextColor', $this->strHeaderTextColor);
386
                $this->strHeaderDrawColor = $this->property($jsonData, 'strHeaderDrawColor', $this->strHeaderDrawColor);
387
                
388
                $this->strFooterTextColor = $this->property($jsonData, 'strFooterTextColor', $this->strFooterTextColor);
389
                $this->strFooterDrawColor = $this->property($jsonData, 'strFooterDrawColor', $this->strFooterDrawColor);
390
                
391
                $this->strColHeaderTextColor = $this->property($jsonData, 'strColHeaderTextColor', $this->strColHeaderTextColor);
392
                $this->strColHeaderFillColor = $this->property($jsonData, 'strColHeaderFillColor', $this->strColHeaderFillColor);
393
                $this->strSubHeaderTextColor = $this->property($jsonData, 'strSubHeaderTextColor', $this->strSubHeaderTextColor);
394
                $this->strSubHeaderFillColor = $this->property($jsonData, 'strSubHeaderFillColor', $this->strSubHeaderFillColor);
395
                $this->strRowTextColor = $this->property($jsonData, 'strRowTextColor', $this->strRowTextColor);
396
                $this->strRowDrawColor = $this->property($jsonData, 'strRowDrawColor', $this->strRowDrawColor);
397
                $this->strRowFillColor = $this->property($jsonData, 'strRowFillColor', $this->strRowFillColor);
398
                $this->strLinkTextColor = $this->property($jsonData, 'strLinkTextColor', $this->strLinkTextColor);
399
                
400
                $this->bStripped = $this->property($jsonData, 'bStripped', $this->bStripped);
401
                $this->border = $this->property($jsonData, 'border', $this->border);
402
                
403
                $this->bCalcTotals = $this->property($jsonData, 'bCalcTotals', $this->bCalcTotals);
404
                $this->bPageTotals = $this->property($jsonData, 'bPageTotals', $this->bPageTotals);
405
                $this->bCarryOver = $this->property($jsonData, 'bCarryOver', $this->bCarryOver);
406
                
407
                $this->strTotals = $this->property($jsonData, 'strTotals', $this->strTotals);
408
                $this->strPageTotals = $this->property($jsonData, 'strPageTotals', $this->strPageTotals);
409
                $this->strCarryOver = $this->property($jsonData, 'strCarryOver', $this->strCarryOver);
410
                
411
                $this->strCharset = $this->property($jsonData, 'strCharset', $this->strCharset);
412
                $this->setLocale($this->property($jsonData, 'strLocale', $this->strLocale));
413
                $this->strFormatD = $this->property($jsonData, 'strFormatD', $this->strFormatD);
414
                $this->strFormatT = $this->property($jsonData, 'strFormatT', $this->strFormatT);
415
                $this->strFormatDT = $this->property($jsonData, 'strFormatDT', $this->strFormatDT);
416
            } else {
417
                trigger_error('unable to decode contents of JSON file [' . $strFilename . ']', E_USER_NOTICE);
418
            }
419
        }
420
    }
421
    
422
    /**
423
     * Set the Date Format to use.
424
     * Format string corresponds to strftime() function. <br/>
425
     * <br/>
426
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
427
     * @param string $strFormatD
428
     * @see XPDF::initGrid()
429
     * @link http://www.php.net/manual/en/function.strftime.php
430
     */
431
    public function setDateFormat(string $strFormatD) : void 
432
    {
433
        $this->strFormatD = $strFormatD;
434
    }
435
    
436
    /**
437
     * Set the Time Format to use.
438
     * Format string corresponds to strftime() function. <br/>
439
     * <br/>
440
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
441
     * @param string $strFormatT
442
     * @see XPDF::initGrid()
443
     * @link http://www.php.net/manual/en/function.strftime.php
444
     */
445
    public function setTimeFormat(string $strFormatT) : void
446
    {
447
        $this->strFormatT = $strFormatT;
448
    }
449
    
450
    /**
451
     * Set the DateTime Format to use.
452
     * Format string corresponds to strftime() function. <br/>
453
     * <br/>
454
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
455
     * @param string $strFormatDT
456
     * @see XPDF::initGrid()
457
     * @link http://www.php.net/manual/en/function.strftime.php
458
     */
459
    public function setDateTimeFormat(string $strFormatDT) : void
460
    {
461
        $this->strFormatDT = $strFormatDT;
462
    }
463
    
464
    /**
465
     * Set format for numbers.
466
     * Decimal point and thousands separator from locale settings is used if available. <br/>
467
     * <br/>
468
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
469
     * @param int $iDecimals
470
     * @param string $strPrefix
471
     * @param string $strSuffix
472
     * @see XPDF::initGrid()
473
     */
474
    public function setNumberFormat(int $iDecimals, string $strPrefix = '', string $strSuffix = '') : void
475
    {
476
        $this->iNumberDecimals = $iDecimals;
477
        $this->strNumberPrefix = $strPrefix;
478
        $this->strNumberSuffix = $strSuffix;
479
    }
480
481
    /**
482
     * Set font for page header.
483
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
484
     * @see XPDF::initGrid()
485
     * @param string $strFontname
486
     * @param string $strStyle
487
     * @param int $iSize
488
     */
489
    public function setHeaderFont(string $strFontname, string $strStyle, int $iSize) : void 
490
    {
491
        $this->fontHeader = new XPDFFont($strFontname, $strStyle, $iSize);
492
    }
493
    
494
    /**
495
     * Set font for subject in the page header.
496
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
497
     * @see XPDF::initGrid()
498
     * @param string $strFontname
499
     * @param string $strStyle
500
     * @param int $iSize
501
     */
502
    public function setSubjectFont(string $strFontname, string $strStyle, int $iSize) : void
503
    {
504
        $this->fontSubject = new XPDFFont($strFontname, $strStyle, $iSize);
505
    }
506
    
507
    /**
508
     * Set font for page footer.
509
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
510
     * @see XPDF::initGrid()
511
     * @param string $strFontname
512
     * @param string $strStyle
513
     * @param int $iSize
514
     */
515
    public function setFooterFont(string $strFontname, string $strStyle, int $iSize) : void
516
    {
517
        $this->fontFooter = new XPDFFont($strFontname, $strStyle, $iSize);
518
    }
519
    
520
    /**
521
     * Set font for col headers.
522
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
523
     * @see XPDF::initGrid()
524
     * @param string $strFontname
525
     * @param string $strStyle
526
     * @param int $iSize
527
     */
528
    public function setColHeaderFont(string $strFontname, string $strStyle, int $iSize) : void
529
    {
530
        $this->fontColHeader = new XPDFFont($strFontname, $strStyle, $iSize);
531
    }
532
533
    /**
534
     * Set font for sub headers.
535
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
536
     * @see XPDF::initGrid()
537
     * @param string $strFontname
538
     * @param string $strStyle
539
     * @param int $iSize
540
     */
541
    public function setSubHeaderFont(string $strFontname, string $strStyle, int $iSize) : void
542
    {
543
        $this->fontSubHeader = new XPDFFont($strFontname, $strStyle, $iSize);
544
    }
545
    
546
    /**
547
     * Set font for data rows.
548
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
549
     * @see XPDF::initGrid()
550
     * @param string $strFontname
551
     * @param string $strStyle
552
     * @param int $iSize
553
     */
554
    public function setRowFont(string $strFontname, string $strStyle, int $iSize) : void
555
    {
556
        $this->fontRows = new XPDFFont($strFontname, $strStyle, $iSize);
557
        $this->selectFont($this->fontRows);
558
    }
559
560
    /**
561
     * Set lineheight.
562
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
563
     * @see XPDF::initGrid()
564
     * @param float $fltLineHeight  lineheight in mm
565
     */
566
    public function setLineHeight(float $fltLineHeight) : void 
567
    {
568
        $this->fltLineHeight = $fltLineHeight;
569
    }
570
    
571
    /**
572
     * Set colors for text and drawing in the pageheader.
573
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
574
     * @see XPDF::initGrid()
575
     * @param string $strTextColor
576
     * @param string $strDrawColor
577
     */
578
    public function setHeaderColors(string $strTextColor, string $strDrawColor) : void
579
    {
580
        $this->strHeaderTextColor = $strTextColor;
581
        $this->strHeaderDrawColor = $strDrawColor;
582
    }
583
584
    /**
585
     * Set colors for text and drawing in the colheader.
586
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
587
     * @see XPDF::initGrid()
588
     * @param string $strTextColor
589
     * @param string $strFillColor
590
     */
591
    public function setColHeaderColors(string $strTextColor, string $strFillColor) : void
592
    {
593
        $this->strColHeaderTextColor = $strTextColor;
594
        $this->strColHeaderFillColor = $strFillColor;
595
    }
596
597
    /**
598
     * Set colors for text and drawing in the grid.
599
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
600
     * @see XPDF::initGrid()
601
     * @param string $strTextColor
602
     * @param string $strDrawColor
603
     * @param string $strFillColor
604
     * @param bool $bStripped
605
     */
606
    public function setRowColors(string $strTextColor, string $strDrawColor, string $strFillColor, bool $bStripped = true) : void 
607
    {
608
        $this->strRowTextColor = $strTextColor;
609
        $this->strRowDrawColor = $strDrawColor;
610
        $this->strRowFillColor = $strFillColor;
611
        $this->bStripped = $bStripped;
612
    }
613
    
614
    /**
615
     * Set colors for text and drawing in the pagefooter.
616
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
617
     * @see XPDF::initGrid()
618
     * @param string $strTextColor
619
     * @param string $strDrawColor
620
     */
621
    public function setFooterColors(string $strTextColor, string $strDrawColor) : void 
622
    {
623
        $this->strFooterTextColor = $strTextColor;
624
        $this->strFooterDrawColor = $strDrawColor;
625
    }
626
627
    /**
628
     * Enables automatic calclation of totals.
629
     * <ul>
630
     * <li> totals over all at end of grid  (self::TOTALS) </li>
631
     * <li> subtotals at end of each page (self::PAGE_TOTALS) </li>
632
     * <li> carry over at beginning of new page (self::CARRY_OVER) </li></ul>
633
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
634
     * @see XPDF::initGrid()
635
     * @param int $iTotals  combination of 
636
     */
637
    public function enableTotals(int $iTotals = self::TOTALS) : void
638
    {
639
        $this->bCalcTotals = ($iTotals & self::TOTALS) != 0;
640
        $this->bPageTotals = ($iTotals & self::PAGE_TOTALS) != 0;
641
        $this->bCarryOver = ($iTotals & self::CARRY_OVER) != 0;
642
        if ($this->bPageTotals) {
643
            // we must increase the bottom margin to trigger the pagebreak
644
            $this->setAutoPageBreak(true, self::BOTTOM_MARGIN + $this->fltLineHeight);
645
        }
646
    }
647
    
648
    /**
649
     * Set text for totals, subtotals and carry over row.
650
     * Following placeholders will be replaced: <ul>
651
     * <li>  {PN} -> current page  </li>
652
     * <li>  {PN-1} -> previous page </li></ul>
653
     * <b>!! Can be controled/overwritten through JSON-Format in InitGrid() !!</b>
654
     * @see XPDF::initGrid()
655
     * @param string $strTotals
656
     * @param string $strPageTotals
657
     * @param string $strCarryOver
658
     */
659
    public function setTotalsText(string $strTotals, string $strPageTotals = '', string $strCarryOver = '') : void
660
    {
661
        $this->strTotals = $strTotals;
662
        if (strlen($strPageTotals) > 0) {
663
            $this->strPageTotals = $strPageTotals;
664
        }
665
        if (strlen($strCarryOver) > 0) {
666
            $this->strCarryOver = $strCarryOver;
667
        }
668
    }
669
    
670
    /**
671
     * reset coldefinitions
672
     */
673
    public function resetCols() : void
674
    {
675
        $this->iMaxCol = -1;
676
        $this->aColWidth = array();
677
        $this->aColAlign = array();
678
        $this->aColFlags = array();
679
        $this->aColField = array();
680
        $this->iMaxColHeader = -1;
681
        $this->aColHeader = array();
682
        $this->aColSpan = array();
683
        $this->aTotalsColSpan = array();
684
        $this->aSubTotals = array();
685
        $this->aTotals = array();
686
    }
687
    
688
    /**
689
     * Add Column to the Grid.
690
     * String is directly mapped to field in datarow, number is requested through Col() method
691
     * @param string $strColHeader title text in the header, if equal -1, colspan of previous col ist increased
692
     * @param float $fltWidth      width in mm (if -1, col is enhanced so table uses on full page width)
693
     * @param string $strAlign     alignment of datacol 'L', 'C' or 'R' - headerer cells allways centered
694
     * @param string $strField     data-field or Column ID.
695
     * @param int $wFlags          Flags to define behaviour for column
696
     * @return int                 Index of the inserted col
697
     */
698
    public function addCol(string $strColHeader, float $fltWidth, string $strAlign, string $strField, int $wFlags = 0) : int
699
    {
700
        $this->iMaxCol++;
701
        $this->aColWidth[$this->iMaxCol] = $fltWidth;
702
        $this->aColAlign[$this->iMaxCol] = $strAlign;
703
        $this->aColFlags[$this->iMaxCol] = $wFlags;
704
        $this->aColField[$this->iMaxCol] = $strField;
705
        if ($strColHeader != -1) {
706
            $this->iMaxColHeader++;
707
            $this->aColHeader[$this->iMaxColHeader] = $strColHeader;
708
            $this->aColSpan[$this->iMaxColHeader] = 1;
709
        } else {
710
            $this->aColSpan[$this->iMaxColHeader]++;
711
        }
712
        if ($this->iMaxCol == 0 || ($wFlags & self::FLAG_TOTALS) != 0) {
713
            $this->iMaxColTotals++;
714
            $this->aTotals[$this->iMaxCol] = 0.0;
715
            $this->aSubTotals[$this->iMaxCol] = 0.0;
716
            $this->aTotalsColSpan[$this->iMaxColTotals] = 1;
717
        } else {
718
            $this->aTotalsColSpan[$this->iMaxColTotals]++;
719
        }
720
        return $this->iMaxCol;
721
    }
722
    
723
    /**
724
     * Set infos for image col.
725
     * Set the margin from the top left corner of the cell and the height/width of the image. <ul>
726
     * <li> If no height and width specified, the image is printed in its origin size </li>
727
     * <li> If only one of both is specified, the image is scaled and the aspect ratio is retained </li></ul>
728
     * @param int $iCol        index of the col (usually the return value of the AddCol() Method)
729
     * @param float $fltTop    top margin from row in user units
730
     * @param float $fltLeft   left margin from cell in user units
731
     * @param float $fltHeight height of the image in user units
732
     * @param float $fltWidth  width of the image in user units
733
     */
734
    public function setColImageInfo(int $iCol, float $fltTop, float $fltLeft, float $fltHeight = -1, float $fltWidth = -1) : void
735
    {
736
        if ($this->aColFlags[$iCol] & self::FLAG_IMAGE == 0) {
737
            trigger_error('Col #' . $iCol . ' is not defined as image col!', E_USER_WARNING);
738
        }
739
        $this->aImgInfo[$iCol] = array(
740
            'fltTop' => $fltTop,
741
            'fltLeft' => $fltLeft,
742
            'fltWidth' => $fltHeight,
743
            'fltHeight' => $fltWidth);
744
    }
745
    
746
    /**
747
     * Have to be called once before datarows be added to the document.
748
     */
749
    public function prepare() : void
750
    {
751
        $this->calcColWidth();
752
        $this->bInGrid = true;
753
        $this->addPage();
754
        
755
        $this->selectDrawColor($this->strRowDrawColor);
756
        $this->selectFillColor($this->strRowFillColor);
757
        $this->selectTextColor($this->strRowTextColor);
758
        $this->selectFont($this->fontRows);
759
        $this->setLineWidth(0.2);
760
    }
761
    
762
    /**
763
     * Build row.
764
     * If fieldname specified in AddCol(), directly the value from the associative array is inserted
765
     * (in case of DATE-Field value is formated d.m.Y)
766
     * all other columns are requested through GetCol() - method
767
     * @param array $row    current row as associative array (may comes from DB query)
768
     */
769
    public function row(array $row) : void
770
    {
771
        $a = $this->saveSettings();
772
        $this->iRow++;
773
        if (($strPreRow = $this->preRow($row)) != '') {
774
            $this->subHeader($strPreRow);
775
        }
776
        if (!$this->isRowVisible($row)) {
777
            return;
778
        }
779
        $this->rowInner($row);
780
        $this->postRow($row);
781
        $this->restoreSettings($a);
782
    }
783
    
784
    /**
785
     * Mark the end of the grid.
786
     * If totals enabled, total row will be printed. <br/>
787
     */
788
    public function endGrid() : void
789
    {
790
        $this->totalsRow(self::TOTALS);
791
        // Internal flag is needed to suppress any printing of colheaders or subtotals after end of grid!
792
        $this->bInGrid = false;
793
    }
794
    
795
    /**
796
     * Starts group for new subtotals.
797
     * Reset calculated subtotals and print subheader if strHeader is set
798
     * @param string $strTotals
799
     * @param string $strHeader
800
     */
801
    public function startGroup(string $strTotals, ?string $strHeader = null) : void
802
    {
803
        $this->strSubTotals = $strTotals;
804
        $iCount = count($this->aSubTotals);
805
        for ($i = 0; $i < $iCount; $i++) {
806
            $this->aSubTotals[$i] = 0.0;
807
        }
808
        if ($strHeader) {
809
            $this->subHeader($strHeader);
810
        }
811
    }
812
    
813
    /**
814
     * End group and print subtotals row.
815
     */
816
    public function endGroup() : void
817
    {
818
        $this->totalsRow(self::SUB_TOTALS);
819
        $this->strSubTotals = '';
820
    }
821
    
822
    /**
823
     * Selects given font.
824
     * @param XPDFFont $font
825
     */
826
    public function selectFont(?XPDFFont $font) : void
827
    {
828
        if ($font !== null) {
829
            $this->setFont($font->strFontname, $font->strStyle, $font->iSize);
830
        }
831
    }
832
    
833
    /**
834
     * Set color for text.
835
     * @param string $strColor color to select in HTML-Format #RRGGBB
836
     */
837
    public function selectTextColor(string $strColor) : void
838
    {
839
        $r = 0; $g = 0; $b = 0;
840
        $this->getRGB($strColor, $r, $g, $b);
841
        $this->setTextColor($r, $g, $b);
842
    }
843
    
844
    /**
845
     * Set color for drawing.
846
     * @param string $strColor color to select in HTML-Format #RRGGBB
847
     */
848
    public function selectDrawColor(string $strColor) : void
849
    {
850
        $r = 0; $g = 0; $b = 0;
851
        $this->getRGB($strColor, $r, $g, $b);
852
        $this->setDrawColor($r, $g, $b);
853
    }
854
    
855
    /**
856
     * Set fillcolor.
857
     * @param string $strColor color to select in HTML-Format #RRGGBB
858
     */
859
    public function selectFillColor(string $strColor) : void
860
    {
861
        $r = 0; $g = 0; $b = 0;
862
        $this->getRGB($strColor, $r, $g, $b);
863
        $this->setFillColor($r, $g, $b);
864
    }
865
    
866
    /**
867
     * Get the height of the current font in user units.
868
     * @return float
869
     */
870
    public function getTextHeight() : float
871
    {
872
        // TODO: ??? 
873
        return 1.0;
874
    }
875
    
876
    /**
877
     * Last step: create the document.
878
     * If nor filename is given, the title set with SetInfo() or SetTitle()
879
     * method is used (or 'XFPDF.pdf' if no title set so far).
880
     * If the filename not ending with .pdf (case insensitive), the extension ist appended.
881
     * @param string $strFilename  Filename
882
     */
883
    public function createPDF(string $strFilename = '') : void
884
    {
885
        if (empty($strFilename)) {
886
            $strFilename = isset($this->metadata['Title']) ? $this->metadata['Title'] : 'XFPDF.pdf';
887
        }
888
        if (strtolower(substr($strFilename, -4)) !== '.pdf') {
889
            $strFilename .= '.pdf';
890
        }
891
        $this->output($strFilename, 'I');
892
    }
893
    
894
    /**
895
     * Print pageheader / logo / colheaders.
896
     * {@inheritDoc}
897
     * @see \OPlathey\FPDF\FPDF::header()
898
     */
899
    public function header() : void
900
    {
901
        if (!empty($this->strPageTitle)) {
902
            $fltLogoHeight = 0.0;
903
            if (!empty($this->strLogo)) {
904
                list($iWidth, $iHeight) = getimagesize($this->strLogo);
905
                if ($iWidth > 0 && $iHeight > 0) {
906
                    // scale image to desired high
907
                    $iWidth *= $this->fltLogoHeight / $iHeight;
908
                    $x = $this->w - $this->rMargin - $iWidth - 1;
909
                    $y = $this->tMargin + 0.5;
910
                    $this->Image($this->strLogo, $x, $y, $iWidth);
911
                    $fltLogoHeight = $this->fltLogoHeight;
912
                }
913
            }
914
                
915
            $this->selectDrawColor($this->strHeaderDrawColor);
916
            $this->selectFont($this->fontHeader);
917
            $this->selectTextColor($this->strHeaderTextColor);
918
            $this->setLineWidth(0.2);
919
            $strPageTitle = $this->replacePlaceholder($this->strPageTitle);
920
            $strPageSubject = $this->replacePlaceholder($this->strPageSubject);
921
            $this->cell(0, $this->FontSize, $strPageTitle, 0, 0, 'L');
922
            $this->ln();
923
            if (strlen($strPageSubject) > 0) {
924
                $this->selectFont($this->fontSubject);
925
                $this->cell(0, $this->FontSize, $strPageSubject, 0, 0, 'L');
926
                $this->ln();
927
            }
928
            
929
            $y = $this->getY();
930
            if (($fltLogoHeight + $this->tMargin) > $y) {
931
                $y = $fltLogoHeight + $this->tMargin;
932
                $this->setY($y);
933
            }
934
            $y += 2.0;
935
            $this->line($this->lMargin, $y, $this->w - $this->rMargin, $y);
936
            $y += 0.5;
937
            $this->line($this->lMargin, $y, $this->w - $this->rMargin, $y);
938
            $this->ln(6);
939
        }
940
        if ($this->iMaxCol > 0 && $this->bInGrid) {
941
            $this->colHeader();
942
            if ($this->bCarryOver && $this->page > 1) {
943
                $this->totalsRow(self::CARRY_OVER);
944
            }
945
        }
946
    }
947
    
948
    /**
949
     * Print pagefooter.
950
     * {@inheritDoc}
951
     * @see \OPlathey\FPDF\FPDF::footer()
952
     */
953
    public function footer() : void 
954
    {
955
        if ($this->bPageTotals) {
956
            $this->totalsRow(self::PAGE_TOTALS);
957
        }
958
        if (!empty($this->strPageFooter)) {
959
            $this->selectDrawColor($this->strFooterDrawColor);
960
            $this->selectFont($this->fontFooter);
961
            $this->selectTextColor($this->strFooterTextColor);
962
            $this->setLineWidth(0.2);
963
    
964
            // Position 2mm from the bottom border
965
            $this->setY(-self::BOTTOM_MARGIN + 2);
966
            $this->line($this->lMargin, $this->GetY() - 0.5, $this->w - $this->rMargin, $this->getY() - 0.5);
967
            $iWidth = $this->w - $this->rMargin - $this->lMargin;
968
            $aCell = explode("\t", $this->strPageFooter, 3);
969
            $aAlign = array('', '', '');
970
            switch (count($aCell)) {
971
                case 1:
972
                    $aAlign = array('C', '', '');
973
                    break;
974
                case 2:
975
                    $aAlign = array('L', 'R', '');
976
                    break;
977
                case 3:
978
                    $aAlign = array('L', 'C', 'R');
979
                    break;
980
            }
981
            $i = 0;
982
            foreach ($aCell as $strCell) {
983
                $strCell = $this->replacePlaceholder($strCell);
984
                $this->cell($iWidth / count($aCell), 7, $strCell, 'T', 0, $aAlign[$i++]);
985
            }
986
        }
987
    }
988
989
    /**
990
     * Create headercols for grid.
991
     */
992
    protected function colHeader() : void
993
    {
994
        $this->selectFillColor($this->strColHeaderFillColor);
995
        $this->selectTextColor($this->strColHeaderTextColor);
996
        $this->setLineWidth(0.2);
997
        $this->selectFont($this->fontColHeader);
998
        
999
        $iCol = 0;
1000
        for ($i = 0; $i <= $this->iMaxColHeader; $i++) {
1001
            $iWidth = 0;
1002
            if ($this->aColSpan[$i] > 1) {
1003
                $j = 0;
1004
                while ($j < $this->aColSpan[$i]) {
1005
                    $iWidth += $this->aColWidth[$iCol++];
1006
                    $j++;
1007
                }
1008
            } else {
1009
                $iWidth = $this->aColWidth[$iCol++];
1010
            }
1011
            
1012
            $strHeader = $this->aColHeader[$i];
1013
            $this->cell($iWidth, $this->fltLineHeight, $strHeader, 1, 0, 'C', true);
1014
        }
1015
        $this->ln();
1016
    }
1017
    
1018
    /**
1019
     * Insert Subheader into the grid. 
1020
     * @param string $strText
1021
     */
1022
    protected function subHeader(string $strText) : void 
1023
    {
1024
        $a = $this->saveSettings();
1025
        
1026
        $this->selectFillColor($this->strSubHeaderFillColor);
1027
        $this->selectTextColor($this->strSubHeaderTextColor);
1028
        $this->selectFont($this->fontSubHeader);
1029
        
1030
        // increase pagebreak trigger to ensure not only subheader fits on current page
1031
        $iBottomMargin = $this->bMargin;
1032
        $this->setAutoPageBreak(true, $iBottomMargin + $this->fltLineHeight);
1033
        $iWidth = $this->w - $this->lMargin - $this->rMargin;
1034
        $this->cell($iWidth, $this->fltLineHeight, $strText, $this->border, 0, 'L', true);
1035
        $this->ln();
1036
        $this->restoreSettings($a);
1037
        // reset pagebreak trigger
1038
        $this->setAutoPageBreak(true, $iBottomMargin);
1039
    }
1040
    
1041
    /**
1042
     * Calculates width for dynamic col.
1043
     * Dynamic col is specified with a width of -1. <br/>
1044
     * <b>Only one col with width of -1 is allowed!</b> <br/>
1045
     * If no dyn. Col is specified, last col is assumed as dynamic. <br/><br/> 
1046
     * Sum of all other cols is subtracted from page width. <br/>
1047
     */
1048
    protected function calcColWidth() : void
1049
    {
1050
        $iGridWidth = $this->w - $this->lMargin - $this->rMargin;
1051
        $iCol = -1;
1052
        for ($i = 0; $i <= $this->iMaxCol; $i++) {
1053
            if ($this->aColWidth[$i] < 0) {
1054
                if ($iCol >= 0) {
1055
                    trigger_error('Only one dynamic col is allowed!', E_USER_WARNING);
1056
                }
1057
                $iCol = $i;
1058
            }
1059
        }
1060
        if ($iCol < 0) {
1061
            $iCol = $this->iMaxCol;
1062
        }
1063
        $iWidth = 0;
1064
        for ($i = 0; $i <= $this->iMaxCol; $i++) {
1065
            if ($i != $iCol) {
1066
                $iWidth += $this->aColWidth[$i];
1067
            }
1068
        }
1069
        $this->aColWidth[$iCol] = $iGridWidth - $iWidth;
1070
    }
1071
    
1072
    /**
1073
     * Inner 'pure' function to build the row. 
1074
     * @param array $row
1075
     */
1076
    protected function rowInner(array $row) : void
1077
    { 
1078
        $this->bInGrid = true;
1079
        for ($i = 0; $i <= $this->iMaxCol; $i++) {
1080
            $strCell = '';
1081
            $field = $this->aColField[$i];
1082
            $wFlags = $this->aColFlags[$i];
1083
            $bFill = $this->bStripped && (($this->iRow % 2) == 0);
1084
            
1085
            // calc totals
1086
            if (isset($row[$field])) {
1087
                $this->calcTotals($i, $row[$field]);
1088
            }
1089
        
1090
            // save for restore, if changed for current col
1091
            $a = $this->saveSettings();
1092
        
1093
            if (is_numeric($field)) {
1094
                $strCell = $this->col($field, $row, $bFill);
1095
            } else {
1096
                // directly get value from row data
1097
                if (!isset($row[$field])) {
1098
                    $strCell = '';
1099
                } else {
1100
                    $strCell = $row[$field];
1101
                }
1102
                $strCell = $this->formatValue($strCell, $wFlags);
1103
            }
1104
                    
1105
            if ($this->isImageCol($i)) {
1106
                $this->drawImageCol($i, $strCell, $bFill);
1107
            } else {
1108
                $link = $this->getColLink($i, $row);
1109
                $this->cell($this->aColWidth[$i], $this->fltLineHeight, $this->convText($strCell), $this->border, 0, $this->aColAlign[$i], $bFill, $link);
1110
            }
1111
                
1112
            $this->restoreSettings($a);
1113
        }
1114
        $this->ln();
1115
    }
1116
    
1117
    /**
1118
     * Print totals/subtotals row.
1119
     * @param int $iTotals
1120
     */
1121
    protected function totalsRow(int $iTotals) : void
1122
    {
1123
        if ($this->bInGrid && $this->bCalcTotals) {
1124
            $a = $this->saveSettings();
1125
            $this->setTotalsRowFormat($iTotals);
1126
            $aTotals = $this->getTotalsRowValues($iTotals);
1127
            $strText = $this->getTotalsRowText($iTotals);
1128
            $iCol = 0;
1129
            for ($iTotalsCol = 0; $iTotalsCol <= $this->iMaxColTotals; $iTotalsCol++) {
1130
                $strCol = '';
1131
                $strAlign = 'C';
1132
                
1133
                if ($this->isTotalsTextCol($iCol)) {
1134
                    $strCol = $this->convText($strText);
1135
                    $strAlign = 'L';
1136
                } elseif ($this->isTotalsCalcCol($iCol)) {
1137
                    $strCol = $this->formatValue($aTotals[$iCol], $this->aColFlags[$iCol]);
1138
                    $strAlign = 'R';
1139
                }
1140
                $iWidth = $this->calcTotalsColWidth($iTotalsCol, $iCol);
1141
                $this->cell($iWidth, $this->fltLineHeight, $this->convText($strCol), 1, 0, $strAlign, true);
1142
                $iCol += $this->aTotalsColSpan[$iTotalsCol];
1143
            }
1144
            $this->ln();
1145
            $this->restoreSettings($a);
1146
        }
1147
    }
1148
1149
    /**
1150
     * Set the format for the requested totals row.
1151
     * - totals and pagetotals use colors and font from ColHeader <br/>
1152
     * - all other types uses format from subheader <br/>
1153
     * @param int $iTotals
1154
     */
1155
    protected function setTotalsRowFormat(int $iTotals) : void
1156
    {
1157
        if ($iTotals == self::TOTALS || $iTotals == self::PAGE_TOTALS) {
1158
            $this->selectFillColor($this->strColHeaderFillColor);
1159
            $this->selectTextColor($this->strColHeaderTextColor);
1160
            $this->setLineWidth(0.2);
1161
            $this->selectFont($this->fontColHeader);
1162
        } else {
1163
            $this->selectFillColor($this->strSubHeaderFillColor);
1164
            $this->selectTextColor($this->strSubHeaderTextColor);
1165
            $this->setLineWidth(0.2);
1166
            $this->selectFont($this->fontSubHeader);
1167
        }
1168
    }
1169
    
1170
    /**
1171
     * Get the tect for requested totals row.
1172
     * Get the text dependent on the type of the totals row and replaace
1173
     * all supported placeholders. 
1174
     * @param int $iTotals
1175
     * @return string
1176
     */
1177
    protected function getTotalsRowText(int $iTotals) : string
1178
    {
1179
        $strText = '';
1180
        switch ($iTotals) {
1181
            case self::TOTALS:
1182
                $strText = $this->strTotals;
1183
                break;
1184
            case self::PAGE_TOTALS:
1185
                $strText = $this->strPageTotals;
1186
                break;
1187
            case self::CARRY_OVER:
1188
                $strText = $this->strCarryOver;
1189
                break;
1190
            case self::SUB_TOTALS:
1191
                $strText = $this->strSubTotals;
1192
                break;
1193
            default:
1194
                break;
1195
        }
1196
        // replace supported placeholders
1197
        $strText = str_replace('{PN}', strval($this->page), $strText);
1198
        $strText = str_replace('{PN-1}', strval($this->page - 1), $strText);
1199
        return $strText;         
1200
    }
1201
    
1202
    /**
1203
     * Get the calculated values for the requested totals row.
1204
     * @param int $iTotals
1205
     * @return array
1206
     */
1207
    protected function getTotalsRowValues(int $iTotals) : array
1208
    {
1209
        if ($iTotals == self::SUB_TOTALS) {
1210
            return $this->aSubTotals;
1211
        } else {
1212
            return $this->aTotals;
1213
        }
1214
    }
1215
1216
    /**
1217
     * Check, if requested col is set for the output of totals text.
1218
     * @param int $iCol
1219
     * @return bool
1220
     */
1221
    protected function isTotalsTextCol(int $iCol) : bool
1222
    {
1223
        return ($this->aColFlags[$iCol] & self::FLAG_TOTALS_TEXT) != 0;
1224
    }
1225
1226
    /**
1227
     * Check, if requested col is defined for totals calculation.
1228
     * @param int $iCol
1229
     * @return bool
1230
     */
1231
    protected function isTotalsCalcCol(int $iCol) : bool
1232
    {
1233
        return ($this->aColFlags[$iCol] & self::FLAG_TOTALS_CALC) != 0;
1234
    }
1235
    
1236
    /**
1237
     * Calculates the width of the requested totals col.
1238
     * 
1239
     * @param int $iTotalsCol
1240
     * @param int $iCol
1241
     * @return float
1242
     */
1243
    protected function calcTotalsColWidth(int $iTotalsCol, int $iCol) : float
1244
    {
1245
        $fltWidth = 0;
1246
        if ($this->aTotalsColSpan[$iTotalsCol] > 1) {
1247
            $j = 0;
1248
            while ($j < $this->aTotalsColSpan[$iTotalsCol]) {
1249
                $fltWidth += $this->aColWidth[$iCol++];
1250
                $j++;
1251
            }
1252
        } else {
1253
            $fltWidth = $this->aColWidth[$iCol++];
1254
        }
1255
        return $fltWidth;
1256
    }
1257
1258
    /**
1259
     * Calculate totals for given col.
1260
     * @param int $iCol
1261
     * @param mixed $value
1262
     */
1263
    protected function calcTotals(int $iCol, $value) : void
1264
    {
1265
        // calc totals if enabled
1266
        if ($this->bCalcTotals && $this->isTotalsCalcCol($iCol)) {
1267
            if (is_numeric($value)) {
1268
                $this->aTotals[$iCol] += $value;
1269
                $this->aSubTotals[$iCol] += $value;
1270
            }
1271
        }
1272
    }
1273
    /**
1274
     * Check, if requested col is iamge col.
1275
     * @param int $iCol
1276
     * @return bool
1277
     */
1278
    protected function isImageCol(int $iCol) : bool
1279
    {
1280
        return ($this->aColFlags[$iCol] & self::FLAG_IMAGE) != 0;
1281
    }
1282
    
1283
    /**
1284
     * Draw the image for a image col.
1285
     * @param int $iCol
1286
     * @param string $strImage
1287
     */
1288
    protected function drawImageCol(int $iCol, string $strImage, bool $bFill) : void
1289
    {
1290
        $fltTop = $this->getY();
1291
        $fltLeft = $this->getX();
1292
        $fltHeight = 0;
1293
        $fltWidth = 0;
1294
        if (isset($this->aImgInfo[$iCol])) {
1295
            $fltTop += $this->aImgInfo[$iCol]['fltTop'];
1296
            $fltLeft += $this->aImgInfo[$iCol]['fltLeft'];
1297
            if ($this->aImgInfo[$iCol]['fltHeight'] > 0) {
1298
                $fltHeight = $this->aImgInfo[$iCol]['fltHeight'];
1299
            }
1300
            if ($this->aImgInfo[$iCol]['fltWidth'] > 0) {
1301
                $fltWidth = $this->aImgInfo[$iCol]['fltWidth'];
1302
            }
1303
        }
1304
        $this->cell($this->aColWidth[$iCol], $this->fltLineHeight, '', $this->border, 0, 'C', $bFill);
1305
        $this->image($strImage, $fltLeft, $fltTop, $fltWidth, $fltHeight);
1306
    }
1307
    
1308
    /**
1309
     * Hook to hide a row dependend on row data.
1310
     * Can be overloaded in subclass to hide a row.
1311
     * @param array $row
1312
     * @return bool    function must return false, if row should not be printed
1313
     */
1314
    protected function isRowVisible(/** @scrutinizer ignore-unused */ array $row) : bool
1315
    {
1316
        return true;
1317
    }
1318
    
1319
    /**
1320
     * Get link information for requested col.
1321
     * Set text color and underline style if col contains link
1322
     * @param int $iCol
1323
     * @param array $row
1324
     * @return string|int
1325
     */
1326
    protected function getColLink(int $iCol, array $row)
1327
    {
1328
        $strLink = '';
1329
        if (($this->aColFlags[$iCol] & self::FLAG_INT_LINK) != 0) {
1330
            $strLink = $this->internalLink($iCol, $row);
1331
            $this->setFont('', 'U');
1332
            $this->selectTextColor($this->strLinkTextColor);
1333
        }
1334
        return $strLink;
1335
    }
1336
    
1337
    /**
1338
     * Called before next row is printed.
1339
     * This function can be overloaded in derived class to <ul>
1340
     * <li> change row data or add values </li>
1341
     * <li> specify text for subtitle before row is printed </li></ul>
1342
     * The $row parameter is defined as reference so $row may be changed within function in derived class. <br/>
1343
     * If the method returns a non-empty string, a subtitle containing the text is printed before the row
1344
     * @param array $row
1345
     * @return string
1346
     */
1347
    protected function preRow(/** @scrutinizer ignore-unused */ array &$row) : string
1348
    {
1349
        return '';
1350
    }
1351
    
1352
    /**
1353
     * Called after the output of a row.
1354
     * To be overloaded in derived class
1355
     * @param array $row
1356
     */
1357
    protected function postRow(/** @scrutinizer ignore-unused */ array $row) : void
1358
    {
1359
    }
1360
    
1361
    /**
1362
     * Get content of a col for current row - to overide in derived class.
1363
     * @param int $iCol     requested colnumber defined in AddCol()
1364
     * @param array $row    current record from DB
1365
     * @param bool          $bFill  
1366
     * @return string
1367
     */
1368
    protected function col(int $iCol, array $row, /** @scrutinizer ignore-unused */ bool &$bFill) : string 
1369
    {
1370
        $strCol = '';
1371
        switch ($iCol) {
1372
            case self::COL_ROW_NR:
1373
                $strCol = $this->iRow;
1374
                break;
1375
            default:
1376
                break;
1377
        }
1378
        return $strCol;
1379
    }
1380
    
1381
    /**
1382
     * @param int $iCol
1383
     * @param array $row
1384
     * @return int
1385
     */
1386
    protected function internalLink(/** @scrutinizer ignore-unused */ int $iCol, /** @scrutinizer ignore-unused */ array $row) : int 
1387
    {
1388
        return $this->addLink();
1389
    }
1390
    
1391
    /**
1392
     * Divides color in HTML notation into red, green and blue component
1393
     * @param string $strColor color in HTML notation #RRGGBB
1394
     * @param int $r    red component of color
1395
     * @param int $g    green component of color
1396
     * @param int $b    blue component of color
1397
     */
1398
    protected function getRGB(string $strColor, int &$r, int &$g, int &$b) : void
1399
    {
1400
        if ($strColor[0] == '#') {
1401
            if (strlen($strColor) == 7) {
1402
                $r = intval(substr($strColor, 1, 2), 16);
1403
                $g = intval(substr($strColor, 3, 2), 16);
1404
                $b = intval(substr($strColor, 5, 2), 16);
1405
            } elseif (strlen($strColor) == 4) {
1406
                $r = intval(substr($strColor, 1, 1), 16);
1407
                $g = intval(substr($strColor, 2, 1), 16);
1408
                $b = intval(substr($strColor, 3, 1), 16);
1409
                $r = $r + (16 * $r);
1410
                $g = $g + (16 * $g);
1411
                $b = $b + (16 * $b);
1412
            }
1413
        }
1414
    }
1415
    
1416
    /**
1417
     * Save some setting to restore after operations changing settings.
1418
     * @return array
1419
     */
1420
    protected function saveSettings() : array
1421
    {
1422
        $a = array();
1423
        
1424
        $a['family'] = $this->FontFamily;
1425
        $a['style']  = $this->FontStyle;
1426
        $a['size']   = $this->FontSizePt;
1427
        $a['ul']     = $this->underline;
1428
        $a['lw']     = $this->LineWidth;
1429
        $a['dc']     = $this->DrawColor;
1430
        $a['fc']     = $this->FillColor;
1431
        $a['tc']     = $this->TextColor;
1432
        $a['cf']     = $this->ColorFlag;
1433
        
1434
        return $a;
1435
    }
1436
1437
    /**
1438
     * Restore settings.
1439
     * Restore only values differing   
1440
     * @param array $a
1441
     */
1442
    protected function restoreSettings(array $a) : void
1443
    {
1444
        // Restore line width
1445
        if ($this->LineWidth != $a['lw']) {
1446
            $this->LineWidth = $a['lw'];
1447
            $this->out(sprintf('%.2F w', $a['lw'] * $this->k));
1448
        }
1449
        // Restore font
1450
        if (($a['family'] != $this->FontFamily) ||
1451
             $a['style'] != $this->FontStyle ||
1452
             $a['size'] != $this->FontSizePt) {
1453
            $this->setFont($a['family'], $a['style'], $a['size']);
1454
        }
1455
        $this->underline = $a['ul'];
1456
        
1457
        // Restore colors
1458
        if ($this->DrawColor != $a['dc']) {
1459
            $this->DrawColor = $a['dc'];
1460
            $this->out($a['dc']);
1461
        }
1462
        if ($this->FillColor != $a['fc']) {
1463
            $this->FillColor = $a['fc'];
1464
            $this->out($a['fc']);
1465
        }
1466
        $this->TextColor = $a['tc'];
1467
        $this->ColorFlag = $a['cf'];
1468
    }
1469
    
1470
    /**
1471
     * Checks if object contains property with given name.
1472
     * If object doesn't have requested property, default value will be returned
1473
     * @param \stdClass $obj    from JSON-Data
1474
     * @param string $strName
1475
     * @param mixed $default
1476
     * @return mixed
1477
     */
1478
    protected function property(\stdClass $obj, string $strName, $default = '')
1479
    {
1480
        $value = $default; 
1481
        if (property_exists($obj, $strName)) {
1482
            $value = $obj->$strName;
1483
        } 
1484
        return $value;
1485
    }
1486
1487
    /**
1488
     * Checks if object contains font property with given name.
1489
     * If object doesn't have requested property, default font will be returned
1490
     * @param \stdClass $obj
1491
     * @param string $strName
1492
     * @param XPDFFont $fontDefault
1493
     * @return XPDFFont
1494
     */
1495
    protected function propertyFont(\stdClass $obj, string $strName, XPDFFont $fontDefault) : XPDFFont
1496
    {
1497
        $font = $fontDefault;
1498
        if (property_exists($obj, $strName)) {
1499
            $oFont = $obj->$strName;
1500
            $font = new XPDFFont($oFont->name, $oFont->style, $oFont->size);
1501
        }
1502
        return $font;
1503
    }
1504
    
1505
    /**
1506
     * @param string $strText
1507
     * @return string
1508
     */
1509
    protected function convText(string $strText) : string
1510
    {
1511
        // $strCharset = mb_detect_encoding($strText);
1512
        if ($this->strCharset != 'UTF-8') {
1513
            $strText = iconv('UTF-8', $this->strCharset, $strText);
1514
        }
1515
        return html_entity_decode($strText, ENT_QUOTES, 'UTF-8');
1516
    }
1517
1518
    /**
1519
     * Formatting of the cell data.
1520
     * @param mixed $value
1521
     * @param int $iFormat
1522
     * @return string
1523
     */
1524
    protected function formatValue($value, int $iFormat) : string
1525
    {
1526
        $strValue = strval($value);
1527
        if (($iFormat & self::FLAG_NO_ZERO) && floatval($value) != 0.0) {
1528
            // suppress zero values...
1529
            $strValue = '';
1530
        } else {
1531
            switch ($iFormat & self::FLAG_FORMAT) {
1532
                case self::FLAG_CUR_SYMBOL:
1533
                    $strValue = $this->formatCurrency(floatval($value), true);
1534
                    break;
1535
                case self::FLAG_CUR_PLAIN:
1536
                    $strValue = $this->formatCurrency(floatval($value), false);
1537
                    break;
1538
                case self::FLAG_DATE:
1539
                    $strValue = $this->formatDate($value);
1540
                    break;
1541
                case self::FLAG_TIME:
1542
                    $strValue = $this->formatTime($value);
1543
                    break;
1544
                case self::FLAG_DATE_TIME: 
1545
                    $strValue = $this->formatDateTime($value);
1546
                    break;
1547
                case self::FLAG_NUMBER: 
1548
                    $strValue = $this->formatNumber(floatval($value));
1549
                    break;
1550
            }
1551
        }
1552
        return $strValue;
1553
    }
1554
        
1555
    /**
1556
     * Formats value as number according to locale settings on system.
1557
     * @param float $fltValue
1558
     * @param int $iDecimals
1559
     * @param string $strPrefix
1560
     * @param string $strSuffix
1561
     * @return string
1562
     */
1563
    protected function formatNumber(float $fltValue, ?int $iDecimals = null, ?string $strPrefix = null, ?string $strSuffix = null) : string
1564
    {
1565
        if (!$this->bInvalidLocale) {
1566
            $li = localeconv();
1567
        } else {
1568
            $li = array('decimal_point' => '.', 'thousands_sep' => ',');
1569
        }
1570
        $iDecimals ??= $this->iNumberDecimals;
1571
        $strPrefix ??= $this->strNumberPrefix;
1572
        $strSuffix ??= $this->strNumberSuffix;
1573
        $strValue = number_format($fltValue, $iDecimals, $li['decimal_point'], $li['thousands_sep']);
1574
        if (strlen($strPrefix) > 0) {
1575
            $strValue .= $strPrefix . $strValue;  
1576
        } 
1577
        if (strlen($strSuffix) > 0) {
1578
            $strValue = $strValue . $strSuffix;  
1579
        } 
1580
        return $strValue;
1581
    }
1582
1583
    /**
1584
     * Formats value as localized currency.
1585
     * money_format($format, $number) has been DEPRECATED as of PHP 7.4.0.
1586
     * @param float $fltValue
1587
     * @param bool $bSymbol
1588
     * @return string
1589
     */
1590
    protected function formatCurrency(float $fltValue, bool $bSymbol = true) : string
1591
    {
1592
        if (!$this->bInvalidLocale) {
1593
            $li = localeconv();
1594
        } else {
1595
            $li = array('mon_decimal_point' => '.', 'mon_thousands_sep' => ',');
1596
            $bSymbol = false;
1597
        }
1598
        $strValue = number_format($fltValue, 2, $li['mon_decimal_point'], $li['mon_thousands_sep']);
1599
        if ($bSymbol) {
1600
            $bPrecedes = ($fltValue >= 0 ? $li['p_cs_precedes'] : $li['n_cs_precedes']);
1601
            $bSpace = ($fltValue >= 0 ? $li['p_sep_by_space'] : $li['n_sep_by_space']);
1602
            $strSep = $bSpace ? ' ' : '';
1603
            if ($bPrecedes) {
1604
                $strValue = $li['currency_symbol'] . $strSep . $strValue;
1605
            } else {
1606
                $strValue = $strValue . $strSep . $li['currency_symbol'];
1607
            }
1608
        }
1609
        return $strValue;
1610
    }
1611
    
1612
    /**
1613
     * Format date.
1614
     * uses strftime()
1615
     * @param mixed $Date       DateTime-Object, unix timestamp or valid Date string
1616
     * @return string
1617
     */
1618
    protected function formatDate($Date) : string
1619
    {
1620
        $strDate = '';
1621
        if (is_object($Date) && get_class($Date) == 'DateTime') {
1622
            // DateTime-object
1623
            $strDate = strftime($this->strFormatD, $Date->getTimestamp());
1624
        } else if (is_numeric($Date)) {
1625
            $strDate = strftime($this->strFormatD, $Date);
1626
        } else {
1627
            $unixtime = strtotime($Date);
1628
            $strDate = ($unixtime === false) ? 'invalid' : strftime($this->strFormatD, $unixtime);
1629
        }
1630
        return $strDate;
1631
    }
1632
    
1633
    /**
1634
     * Format time.
1635
     * uses strftime()
1636
     * @param mixed $Time       DateTime-Object, unix timestamp or valid Time string
1637
     * @return string
1638
     */
1639
    protected function formatTime($Time) : string
1640
    {
1641
        $strTime = '';
1642
        if (is_object($Time) && get_class($Time) == 'DateTime') {
1643
            // DateTime-object
1644
            $strTime = strftime($this->strFormatT, $Time->getTimestamp());
1645
        } else if (is_numeric($Time)) {
1646
            $strTime = strftime($this->strFormatT, $Time);
1647
        } else {
1648
            $strTime = strftime($this->strFormatT, strtotime($Time));
1649
        }
1650
        return $strTime;
1651
    }
1652
    
1653
    /**
1654
     * Format date time.
1655
     * uses strftime()
1656
     * @param mixed $DT     DateTime-Object, unix timestamp or valid DateTime string
1657
     * @return string
1658
     */
1659
    protected function formatDateTime($DT) : string
1660
    {
1661
        $strDT = '';
1662
        if (is_object($DT) && get_class($DT) == 'DateTime') {
1663
            // DateTime-object
1664
            $strDT = strftime($this->strFormatDT, $DT->getTimestamp());
1665
        } else if (is_numeric($DT)) {
1666
            $strDT = strftime($this->strFormatDT, $DT);
1667
        } else {
1668
            $strDT = strftime($this->strFormatDT, strtotime($DT));
1669
        }
1670
        return $strDT;
1671
    }
1672
    
1673
    /**
1674
     * Replace the placeholders that can be used in header and footer. 
1675
     * @param string $strText
1676
     * @return string
1677
     */
1678
    protected function replacePlaceholder(string $strText) : string
1679
    {
1680
        $strText = str_replace("{D}", strftime('%x'), $strText);
1681
        $strText = str_replace("{T}", strftime('%X'), $strText);
1682
        $strText = str_replace("{PN}", strval($this->pageNo()), $strText);
1683
        
1684
        return $strText;
1685
    }
1686
}
1687