Passed
Push — main ( b08f68...83c25f )
by Stefan
02:16
created

XPDF::isTotalsTextCol()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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