Passed
Push — developer ( d0391c...1a872b )
by Mariusz
04:53
created

Style::getMaxLineHeight()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 1 Features 0
Metric Value
eloc 14
c 10
b 1
f 0
dl 0
loc 22
rs 8.4444
cc 8
nc 17
nop 0
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Style class.
6
 *
7
 * @package   YetiForcePDF\Style
8
 *
9
 * @copyright YetiForce Sp. z o.o
10
 * @license   MIT
11
 * @author    Rafal Pospiech <[email protected]>
12
 */
13
14
namespace YetiForcePDF\Style;
15
16
use Sabberworm\CSS\Parser as CSSParser;
17
use YetiForcePDF\Html\Element;
18
use YetiForcePDF\Layout\Box;
19
use YetiForcePDF\Layout\InlineBox;
20
use YetiForcePDF\Layout\TableBox;
21
use YetiForcePDF\Layout\TableCellBox;
22
use YetiForcePDF\Math;
23
use YetiForcePDF\Objects\Font;
24
use YetiForcePDF\Objects\GraphicState;
25
use YetiForcePDF\Objects\ImageStream;
26
27
/**
28
 * Class Style.
29
 */
30
class Style extends \YetiForcePDF\Base
31
{
32
	/**
33
	 * @var \YetiForcePDF\Document
34
	 */
35
	protected $document;
36
	/**
37
	 * CSS text to parse.
38
	 *
39
	 * @var string|null
40
	 */
41
	protected $content;
42
	/**
43
	 * @var Element
44
	 */
45
	protected $element;
46
	/**
47
	 * @var Font
48
	 */
49
	protected $font;
50
	/**
51
	 * @var GraphicState
52
	 */
53
	protected $graphicState;
54
	/**
55
	 * @var Box
56
	 */
57
	protected $box;
58
	/**
59
	 * @var bool
60
	 */
61
	protected $parsed = false;
62
	/**
63
	 * Element name.
64
	 *
65
	 * @var string
66
	 */
67
	protected $elementName = '';
68
	/**
69
	 * @var ImageStream
70
	 */
71
	protected $backgroundImage;
72
	/**
73
	 * Max line height cache.
74
	 *
75
	 * @var string
76
	 */
77
	protected $maxLineHeight;
78
79
	/**
80
	 * Css properties that are inherited by default.
81
	 *
82
	 * @var array
83
	 */
84
	protected $inherited = [
85
		'azimuth',
86
		'background-image-resolution',
87
		'border-collapse',
88
		'border-spacing',
89
		'caption-side',
90
		'color',
91
		'cursor',
92
		'direction',
93
		'elevation',
94
		'empty-cells',
95
		'font-family',
96
		'font-size',
97
		'font-style',
98
		'font-variant',
99
		'font-weight',
100
		'image-resolution',
101
		'letter-spacing',
102
		'line-height',
103
		'list-style-image',
104
		'list-style-position',
105
		'list-style-type',
106
		'list-style',
107
		'orphans',
108
		'opacity',
109
		'page-break-inside',
110
		'pitch-range',
111
		'pitch',
112
		'quotes',
113
		'richness',
114
		'speak-header',
115
		'speak-numeral',
116
		'speak-punctuation',
117
		'speak',
118
		'speech-rate',
119
		'stress',
120
		'text-align',
121
		'text-indent',
122
		'text-transform',
123
		'visibility',
124
		'voice-family',
125
		'volume',
126
		'white-space',
127
		'word-wrap',
128
		'widows',
129
		'word-spacing',
130
	];
131
	/**
132
	 * Rules that are mandatory with default values.
133
	 *
134
	 * @var array
135
	 */
136
	protected $mandatoryRules = [
137
		'font-family' => '',
138
		'font-size' => '12px',
139
		'font-weight' => 'normal',
140
		'font-style' => 'normal',
141
		'margin-left' => '0',
142
		'margin-top' => '0',
143
		'margin-right' => '0',
144
		'margin-bottom' => '0',
145
		'padding-left' => '0',
146
		'padding-top' => '0',
147
		'padding-right' => '0',
148
		'padding-bottom' => '0',
149
		'border-left-width' => '0',
150
		'border-top-width' => '0',
151
		'border-right-width' => '0',
152
		'border-bottom-width' => '0',
153
		'border-left-color' => '#000000',
154
		'border-top-color' => '#000000',
155
		'border-right-color' => '#000000',
156
		'border-bottom-color' => '#000000',
157
		'border-left-style' => 'none',
158
		'border-top-style' => 'none',
159
		'border-right-style' => 'none',
160
		'border-bottom-style' => 'none',
161
		'border-collapse' => 'separate',
162
		'border-spacing' => '2px',
163
		'box-sizing' => 'border-box',
164
		'display' => 'inline',
165
		'width' => 'auto',
166
		'height' => 'auto',
167
		'overflow' => 'visible',
168
		'vertical-align' => 'baseline',
169
		'line-height' => '1.4em',
170
		'background-color' => 'transparent',
171
		'color' => '#000000',
172
		'word-wrap' => 'normal',
173
		'max-width' => 'none',
174
		'min-width' => '0',
175
		'white-space' => 'normal',
176
		'opacity' => '1',
177
	];
178
179
	/**
180
	 * Original css rules.
181
	 *
182
	 * @var array
183
	 */
184
	protected $originalRules = [];
185
	/**
186
	 * Css rules (computed).
187
	 *
188
	 * @var array
189
	 */
190
	protected $rules = [];
191
192
	/**
193
	 * Initial rules because rules may change during the rearrangement.
194
	 *
195
	 * @var array
196
	 */
197
	protected $initialRules = [];
198
199
	/**
200
	 * Default styles for certain elements.
201
	 *
202
	 * @var array
203
	 */
204
	protected $elementDefaults = [
205
		'a:link' => [
206
			'color' => 'blue',
207
			'text-decoration' => 'underline',
208
			'cursor' => 'auto',
209
		],
210
		'a:visited' => [
211
			'color' => 'blue',
212
			'text-decoration' => 'underline',
213
			'cursor' => 'auto',
214
		],
215
		'a:link:active' => [
216
			'color' => 'blue',
217
		],
218
		'a:visited:active' => [
219
			'color' => 'blue',
220
		],
221
		'address' => [
222
			'display' => 'block',
223
			'font-style' => 'italic',
224
		],
225
		'area' => [
226
			'display' => 'none',
227
		],
228
		'article' => [
229
			'display' => 'block',
230
		],
231
		'aside' => [
232
			'display' => 'block',
233
		],
234
		'b' => [
235
			'font-weight' => 'bold',
236
		],
237
		'bdo' => [
238
			'unicode-bidi' => 'bidi-override',
239
		],
240
		'blockquote' => [
241
			'display' => 'block',
242
			'margin-top' => '1em',
243
			'margin-bottom' => '1em',
244
			'margin-left' => '40px',
245
			'margin-right' => '40px',
246
		],
247
		'body' => [
248
			'display' => 'block',
249
			'margin' => '8px',
250
		],
251
		'body:focus' => [
252
			'outline' => 'none',
253
		],
254
		'br' => [
255
			'display' => 'block',
256
		],
257
		'button' => [
258
			'display' => 'inline-block',
259
			'padding' => '10px',
260
		],
261
		'caption' => [
262
			'display' => 'table-caption',
263
			'text-align' => 'center',
264
		],
265
		'cite' => [
266
			'font-style' => 'italic',
267
		],
268
		'code' => [
269
			'font-family' => 'monospace',
270
		],
271
		'col' => [
272
			'display' => 'table-column',
273
		],
274
		'colgroup' => [
275
			'display:table-column-group',
276
		],
277
		'datalist' => [
278
			'display' => 'none',
279
		],
280
		'dd' => [
281
			'display' => 'block',
282
			'margin-left' => '40px',
283
		],
284
		'del' => [
285
			'text-decoration' => 'line-through',
286
		],
287
		'details' => [
288
			'display' => 'block',
289
		],
290
		'dfn' => [
291
			'font-style' => 'italic',
292
		],
293
		'div' => [
294
			'display' => 'block',
295
		],
296
		'dl' => [
297
			'display' => 'block',
298
			'margin-top' => '1em',
299
			'margin-bottom' => '1em',
300
			'margin-left' => '0',
301
			'margin-right' => '0',
302
		],
303
		'dt' => [
304
			'display' => 'block',
305
		],
306
		'em' => [
307
			'font-style' => 'italic',
308
		],
309
		'embed:focus' => [
310
			'outline' => 'none',
311
		],
312
		'fieldset' => [
313
			'display' => 'block',
314
			'margin-left' => '2px',
315
			'margin-right' => '2px',
316
			'padding-top' => '0.35em',
317
			'padding-bottom' => '0.625em',
318
			'padding-left' => '0.75em',
319
			'padding-right' => '0.75em',
320
		],
321
		'figcaption' => [
322
			'display' => 'block',
323
		],
324
		'figure' => [
325
			'display' => 'block',
326
			'margin-top' => '1em',
327
			'margin-bottom' => '1em',
328
			'margin-left' => '40px',
329
			'margin-right' => '40px',
330
		],
331
		'footer' => [
332
			'display' => 'block',
333
		],
334
		'form' => [
335
			'display' => 'block',
336
			'margin-top' => '0em',
337
		],
338
		'h1' => [
339
			'display' => 'block',
340
			'font-size' => '2em',
341
			'margin-top' => '0.67em',
342
			'margin-bottom' => '0.67em',
343
			'margin-left' => '0',
344
			'margin-right' => '0',
345
			'font-weight' => 'bold',
346
		],
347
		'h2' => [
348
			'display' => 'block',
349
			'font-size' => '1.5em',
350
			'margin-top' => '0.83em',
351
			'margin-bottom' => '0.83em',
352
			'margin-left' => '0',
353
			'margin-right' => '0',
354
			'font-weight' => 'bold',
355
		],
356
		'h3' => [
357
			'display' => 'block',
358
			'font-size' => '1.4em',
359
			'margin-top' => '1em',
360
			'margin-bottom' => '1em',
361
			'margin-left' => '0',
362
			'margin-right' => '0',
363
			'font-weight' => 'bold',
364
		],
365
		'h4' => [
366
			'display' => 'block',
367
			'font-size' => '1.3em',
368
			'margin-top' => '1.33em',
369
			'margin-bottom' => '1.33em',
370
			'margin-left' => '0',
371
			'margin-right' => '0',
372
			'font-weight' => 'bold',
373
		],
374
		'h5' => [
375
			'display' => 'block',
376
			'font-size' => '1.2em',
377
			'margin-top' => '1.67em',
378
			'margin-bottom' => '1.67em',
379
			'margin-left' => '0',
380
			'margin-right' => '0',
381
			'font-weight' => 'bold',
382
		],
383
		'h6' => [
384
			'display' => 'block',
385
			'font-size' => '1em',
386
			'margin-top' => '2.33em',
387
			'margin-bottom' => '2.33em',
388
			'margin-left' => '0',
389
			'margin-right' => '0',
390
			'font-weight' => 'bold',
391
		],
392
		'head' => [
393
			'display' => 'none',
394
		],
395
		'header' => [
396
			'display' => 'block',
397
		],
398
		'hr' => [
399
			'display' => 'block',
400
			'margin-top' => '0.5em',
401
			'margin-bottom' => '0.5em',
402
			'margin-left' => '0px',
403
			'margin-right' => '0px',
404
			'border-style' => 'solid',
405
			'border-color' => 'lightgray',
406
			'border-top-width' => '1px',
407
		],
408
		'html' => [
409
			'display' => 'block',
410
		],
411
		'html:focus' => [
412
			'outline' => 'none',
413
		],
414
		'i' => [
415
			'font-style' => 'italic',
416
		],
417
		'iframe:focus' => [
418
			'outline' => 'none',
419
		],
420
		'iframe[seamless]' => [
421
			'display' => 'block',
422
		],
423
		'img' => [
424
			'display' => 'inline-block',
425
		],
426
		'ins' => [
427
			'text-decoration' => 'underline',
428
		],
429
		'kbd' => [
430
			'font-family' => 'monospace',
431
		],
432
		'label' => [
433
			'cursor' => 'default',
434
		],
435
		'legend' => [
436
			'display' => 'block',
437
			'padding-left' => '2px',
438
			'padding-right' => '2px',
439
			'border' => 'none',
440
		],
441
		'li' => [
442
			'display' => 'list-item',
443
		],
444
		'link' => [
445
			'display' => 'none',
446
		],
447
		'map' => [
448
			'display' => 'inline',
449
		],
450
		'mark' => [
451
			'background-color' => 'yellow',
452
			'color' => 'black',
453
		],
454
		'menu' => [
455
			'display' => 'block',
456
			'list-style-type' => 'disc',
457
			'margin-top' => '1em',
458
			'margin-bottom' => '1em',
459
			'margin-left' => '0',
460
			'margin-right' => '0',
461
			'padding-left' => '40px',
462
		],
463
		'nav' => [
464
			'display' => 'block',
465
		],
466
		'object:focus' => [
467
			'outline' => 'none',
468
		],
469
		'ol' => [
470
			'display' => 'block',
471
			'list-style-type' => 'decimal',
472
			'margin-top' => '1em',
473
			'margin-bottom' => '1em',
474
			'margin-left' => '0',
475
			'margin-right' => '0',
476
			'padding-left' => '40px',
477
		],
478
		'output' => [
479
			'display' => 'inline',
480
		],
481
		'p' => [
482
			'display' => 'block',
483
			'margin-top' => '1em',
484
			'margin-bottom' => '1em',
485
			'margin-left' => '0',
486
			'margin-right' => '0',
487
		],
488
		'param' => [
489
			'display' => 'none',
490
		],
491
		'pre' => [
492
			'display' => 'block',
493
			'font-family' => 'monospace',
494
			'white-space' => 'pre',
495
			'margin' => '1em',
496
		],
497
		'q' => [
498
			'display' => 'inline',
499
		],
500
		'q::before' => [
501
			'content' => 'open-quote',
502
		],
503
		'q::after' => [
504
			'content' => 'close-quote',
505
		],
506
		'rt' => [
507
			'line-height' => 'normal',
508
		],
509
		's' => [
510
			'text-decoration' => 'line-through',
511
		],
512
		'samp' => [
513
			'font-family' => 'monospace',
514
		],
515
		'script' => [
516
			'display' => 'none',
517
		],
518
		'section' => [
519
			'display' => 'block',
520
		],
521
		'small' => [
522
			'font-size' => 'smaller',
523
		],
524
		'strike' => [
525
			'text-decoration' => 'line-through',
526
		],
527
		'strong' => [
528
			'font-weight' => 'bold',
529
		],
530
		'style' => [
531
			'display' => 'none',
532
		],
533
		'sub' => [
534
			'vertical-align' => 'sub',
535
			'font-size' => 'smaller',
536
		],
537
		'summary' => [
538
			'display' => 'block',
539
		],
540
		'sup' => [
541
			'vertical-align' => 'super',
542
			'font-size' => 'smaller',
543
		],
544
		'table' => [
545
			'display' => 'table',
546
			'border-collapse' => 'separate',
547
			'border-spacing' => '2px',
548
			'border-color' => 'gray',
549
		],
550
		'tbody' => [
551
			'display' => 'table-row-group',
552
			'vertical-align' => 'middle',
553
			'border-color' => 'inherit',
554
		],
555
		'td' => [
556
			'display' => 'table-cell',
557
			'vertical-align' => 'inherit',
558
			'padding' => '1px',
559
		],
560
		'tfoot' => [
561
			'display' => 'table-footer-group',
562
			'vertical-align' => 'middle',
563
			'border-color' => 'inherit',
564
		],
565
		'th' => [
566
			'display' => 'table-cell',
567
			'vertical-align' => 'inherit',
568
			'font-weight' => 'bold',
569
			'text-align' => 'center',
570
			'padding' => '1px',
571
			'background-color' => '#ddd',
572
			'line-height' => '1.6em',
573
		],
574
		'thead' => [
575
			'display' => 'table-header-group',
576
			'vertical-align' => 'middle',
577
			'border-color' => 'inherit',
578
		],
579
		'title' => [
580
			'display' => 'none',
581
		],
582
		'tr' => [
583
			'display' => 'table-row',
584
			'vertical-align' => 'inherit',
585
			'border-color' => 'inherit',
586
		],
587
		'u' => [
588
			'text-decoration' => 'underline',
589
		],
590
		'ul' => [
591
			'display' => 'block',
592
			'list-style-type' => 'disc',
593
			'margin-top' => '1em',
594
			'margin-bottom' => '1em',
595
			'margin-left' => '0',
596
			'margin-right' => '0',
597
			'padding-left' => '40px',
598
		],
599
		'var' => [
600
			'font-style' => 'italic',
601
		],
602
	];
603
604
	/**
605
	 * Initialisation.
606
	 *
607
	 * @return \YetiForcePDF\Style\Style
608
	 */
609
	public function init(): self
610
	{
611
		parent::init();
612
		$this->parse();
613
		return $this;
614
	}
615
616
	/**
617
	 * Set box for this element (element is always inside box).
618
	 *
619
	 * @param \YetiForcePDF\Layout\Box $box
620
	 *
621
	 * @return $this
622
	 */
623
	public function setBox($box)
624
	{
625
		$this->box = $box;
626
627
		return $this;
628
	}
629
630
	/**
631
	 * Get box.
632
	 *
633
	 * @return \YetiForcePDF\Layout\Box
634
	 */
635
	public function getBox()
636
	{
637
		return $this->box;
638
	}
639
640
	/**
641
	 * Set element.
642
	 *
643
	 * @param \YetiForcePDF\Html\Element $element
644
	 *
645
	 * @return \YetiForcePDF\Style\Style
646
	 */
647
	public function setElement(Element $element): self
648
	{
649
		$this->element = $element;
650
		$this->setElementName($element->getDOMElement()->nodeName);
651
652
		return $this;
653
	}
654
655
	/**
656
	 * Get element.
657
	 *
658
	 * @return \YetiForcePDF\Html\Element
659
	 */
660
	public function getElement()
661
	{
662
		return $this->element;
663
	}
664
665
	/**
666
	 * Set element name.
667
	 *
668
	 * @param string $elementName
669
	 *
670
	 * @return $this
671
	 */
672
	public function setElementName(string $elementName)
673
	{
674
		$this->elementName = strtolower($elementName);
675
676
		return $this;
677
	}
678
679
	/**
680
	 * Get element name.
681
	 *
682
	 * @return string
683
	 */
684
	public function getElementName()
685
	{
686
		return $this->elementName;
687
	}
688
689
	/**
690
	 * Set content.
691
	 *
692
	 * @param string|null $content
693
	 *
694
	 * @return $this
695
	 */
696
	public function setContent(string $content = null)
697
	{
698
		if ($content) {
699
			$content = preg_replace('/data:image\/([a-z]+);/', 'data_image/$1_', $content);
700
		}
701
		$this->content = $content;
702
		return $this;
703
	}
704
705
	/**
706
	 * Set margins.
707
	 *
708
	 * @param string $top
709
	 * @param string $right
710
	 * @param string $bottom
711
	 * @param string $left
712
	 *
713
	 * @return $this
714
	 */
715
	public function setMargins(string $top = null, string $right = null, string $bottom = null, string $left = null)
716
	{
717
		if (null !== $top) {
718
			$this->rules['margin-top'] = $top;
719
		}
720
		if (null !== $right) {
721
			$this->rules['margin-right'] = $right;
722
		}
723
		if (null !== $bottom) {
724
			$this->rules['margin-bottom'] = $bottom;
725
		}
726
		if (null !== $left) {
727
			$this->rules['margin-left'] = $left;
728
		}
729
730
		return $this;
731
	}
732
733
	/**
734
	 * Get parent style.
735
	 *
736
	 * @return Style|null
737
	 */
738
	public function getParent()
739
	{
740
		if ($this->box) {
741
			if ($parentBox = $this->box->getParent()) {
742
				return $parentBox->getStyle();
743
			}
744
		}
745
	}
746
747
	/**
748
	 * Get children styles.
749
	 *
750
	 * @param array $rules - filter styles with specified rules
751
	 *
752
	 * @return \YetiForcePDF\Style\Style[]
753
	 */
754
	public function getChildren(array $rules = [])
0 ignored issues
show
Unused Code introduced by
The parameter $rules is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

754
	public function getChildren(/** @scrutinizer ignore-unused */ array $rules = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
755
	{
756
		$childrenStyles = [];
757
		foreach ($this->box->getChildren() as $childBox) {
758
			$childrenStyles[] = $childBox->getStyle();
759
		}
760
761
		return $childrenStyles;
762
	}
763
764
	/**
765
	 * Do we have children?
766
	 *
767
	 * @return bool
768
	 */
769
	public function hasChildren()
770
	{
771
		return $this->box->hasChildren();
772
	}
773
774
	/**
775
	 * Get previous element style.
776
	 *
777
	 * @return \YetiForcePDF\Style\Style
778
	 */
779
	public function getPrevious()
780
	{
781
		if ($previous = $this->box->getPrevious()) {
782
			return $previous->getStyle();
783
		}
784
	}
785
786
	/**
787
	 * Get next element style.
788
	 *
789
	 * @return \YetiForcePDF\Style\Style
790
	 */
791
	public function getNext()
792
	{
793
		if ($next = $this->box->getNext()) {
794
			return $next->getStyle();
795
		}
796
	}
797
798
	/**
799
	 * Get rules (or concrete rule if specified).
800
	 *
801
	 * @param string|null $ruleName
802
	 *
803
	 * @return array|string
804
	 */
805
	public function getRules(string $ruleName = null)
806
	{
807
		if ($ruleName) {
808
			if (isset($this->rules[$ruleName])) {
809
				if (\is_array($this->rules[$ruleName])) {
810
					return $this->rules[$ruleName];
811
				}
812
				return (string) $this->rules[$ruleName];
813
			}
814
815
			return '';
816
		}
817
818
		return $this->rules;
819
	}
820
821
	/**
822
	 * Get original rules (or concrete rule if specified).
823
	 *
824
	 * @param string|null $ruleName
825
	 *
826
	 * @return array|mixed
827
	 */
828
	public function getOriginalRules(string $ruleName = null)
829
	{
830
		if ($ruleName) {
831
			if (isset($this->originalRules[$ruleName])) {
832
				return $this->originalRules[$ruleName];
833
			}
834
835
			return '';
836
		}
837
838
		return $this->originalRules;
839
	}
840
841
	/**
842
	 * Set rule.
843
	 *
844
	 * @param string $ruleName
845
	 * @param string $ruleValue
846
	 *
847
	 * @return $this
848
	 */
849
	public function setRule(string $ruleName, $ruleValue)
850
	{
851
		$this->rules[$ruleName] = $ruleValue;
852
853
		return $this;
854
	}
855
856
	/**
857
	 * Set rules.
858
	 *
859
	 * @param array $rules
860
	 *
861
	 * @return $this
862
	 */
863
	public function setRules(array $rules)
864
	{
865
		$this->rules = array_merge($this->rules, $rules);
866
867
		return $this;
868
	}
869
870
	/**
871
	 * Get rules that are inherited from parent.
872
	 *
873
	 * @return array
874
	 */
875
	public function getInheritedRules(): array
876
	{
877
		$inheritedRules = [];
878
		foreach ($this->rules as $ruleName => $ruleValue) {
879
			if (\in_array($ruleName, $this->inherited)) {
880
				$inheritedRules[$ruleName] = $this->originalRules[$ruleName];
881
			}
882
		}
883
884
		return $inheritedRules;
885
	}
886
887
	/**
888
	 * Get background image stream.
889
	 *
890
	 * @return ImageStream
891
	 */
892
	public function getBackgroundImageStream()
893
	{
894
		return $this->backgroundImage;
895
	}
896
897
	/**
898
	 * Get horizontal borders width.
899
	 *
900
	 * @return string
901
	 */
902
	public function getHorizontalBordersWidth()
903
	{
904
		return Math::add((string) $this->rules['border-left-width'], (string) $this->rules['border-right-width']);
905
	}
906
907
	/**
908
	 * Get vertical borders width.
909
	 *
910
	 * @return string
911
	 */
912
	public function getVerticalBordersWidth()
913
	{
914
		return Math::add((string) $this->rules['border-top-width'], (string) $this->rules['border-bottom-width']);
915
	}
916
917
	/**
918
	 * Get horizontal paddings width.
919
	 *
920
	 * @return string
921
	 */
922
	public function getHorizontalPaddingsWidth()
923
	{
924
		return Math::add((string) $this->rules['padding-left'], (string) $this->rules['padding-right']);
925
	}
926
927
	/**
928
	 * Get vertical paddings width.
929
	 *
930
	 * @return string
931
	 */
932
	public function getVerticalPaddingsWidth()
933
	{
934
		return Math::add((string) $this->rules['padding-top'], (string) $this->rules['padding-bottom']);
935
	}
936
937
	/**
938
	 * Get horizontal margins width.
939
	 *
940
	 * @return string
941
	 */
942
	public function getHorizontalMarginsWidth()
943
	{
944
		return Math::add((string) $this->rules['margin-left'], (string) $this->rules['margin-right']);
945
	}
946
947
	/**
948
	 * Get vertical paddings width.
949
	 *
950
	 * @return string
951
	 */
952
	public function getVerticalMarginsWidth()
953
	{
954
		return Math::add((string) $this->rules['margin-top'], (string) $this->rules['margin-bottom']);
955
	}
956
957
	/**
958
	 * Get full left space.
959
	 *
960
	 * @return string
961
	 */
962
	public function getFullLeftSpace()
963
	{
964
		return Math::add((string) $this->rules['margin-left'], Math::add((string) $this->rules['padding-left'], (string) $this->rules['border-left-width']));
965
	}
966
967
	/**
968
	 * Get full right space.
969
	 *
970
	 * @return string
971
	 */
972
	public function getFullRightSpace()
973
	{
974
		return Math::add(Math::add((string) $this->rules['margin-right'], (string) $this->rules['padding-right']), (string) $this->rules['border-right-width']);
975
	}
976
977
	/**
978
	 * Get offset top -  get top border width and top padding.
979
	 *
980
	 * @param bool $withBorders
981
	 *
982
	 * @return string
983
	 */
984
	public function getOffsetTop()
985
	{
986
		return Math::add((string) $this->rules['border-top-width'], (string) $this->rules['padding-top']);
987
	}
988
989
	/**
990
	 * Get offset left - get left border width and left padding.
991
	 *
992
	 * @param bool $withBorders
993
	 *
994
	 * @return string
995
	 */
996
	public function getOffsetLeft()
997
	{
998
		return Math::add((string) $this->rules['border-left-width'], (string) $this->rules['padding-left']);
999
	}
1000
1001
	/**
1002
	 * Get current style font.
1003
	 *
1004
	 * @return Font
1005
	 */
1006
	public function getFont(): Font
1007
	{
1008
		return $this->font;
1009
	}
1010
1011
	/**
1012
	 * Get graphic state.
1013
	 *
1014
	 * @return GraphicState
1015
	 */
1016
	public function getGraphicState(): GraphicState
1017
	{
1018
		return $this->graphicState;
1019
	}
1020
1021
	/**
1022
	 * Convert units from unit to pdf document units.
1023
	 *
1024
	 * @param string $unit
1025
	 * @param float  $size
1026
	 * @param mixed  $isFont
1027
	 *
1028
	 * @return NumericValue
1029
	 */
1030
	public function convertUnits(string $unit, string $size, $isFont = false)
0 ignored issues
show
Unused Code introduced by
The parameter $isFont is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1030
	public function convertUnits(string $unit, string $size, /** @scrutinizer ignore-unused */ $isFont = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1031
	{
1032
		if ('' === $size) {
1033
			$size = '0';
1034
		}
1035
		switch ($unit) {
1036
			case 'px':
1037
			case 'pt':
1038
				return $size;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $size returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1039
			case 'mm':
1040
				return Math::div($size, Math::div('72', '25.4'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return YetiForcePDF\Math...ath::div('72', '25.4')) returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1041
			case 'cm':
1042
				return Math::div($size, Math::div('72', '2.54'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return YetiForcePDF\Math...ath::div('72', '2.54')) returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1043
			case 'in':
1044
				return Math::div($size, '72');
0 ignored issues
show
Bug Best Practice introduced by
The expression return YetiForcePDF\Math::div($size, '72') returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1045
			case '%':
1046
				return $size . '%'; // percent values are calculated later
0 ignored issues
show
Bug Best Practice introduced by
The expression return $size . '%' returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1047
			case 'em':
1048
			default:
1049
				if ($parent = $this->getParent()) {
1050
					$font = $parent->getFont()->getClosestWithUnit('px');
1051
					return Math::mul($font->getSize()->getConverted(), $size);
0 ignored issues
show
Bug Best Practice introduced by
The expression return YetiForcePDF\Math...>getConverted(), $size) returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1052
				}
1053
				return Math::mul($this->getFont()->getSize()->getConverted(), $size);
0 ignored issues
show
Bug Best Practice introduced by
The expression return YetiForcePDF\Math...>getConverted(), $size) returns the type string which is incompatible with the documented return type YetiForcePDF\Style\NumericValue.
Loading history...
1054
		}
1055
	}
1056
1057
	/**
1058
	 * Is this box have a borders?
1059
	 *
1060
	 * @return bool
1061
	 */
1062
	public function haveSpacing()
1063
	{
1064
		$spacing = Math::max($this->getHorizontalBordersWidth(), $this->getHorizontalPaddingsWidth());
1065
1066
		return Math::comp($spacing, '0') > 0;
1067
	}
1068
1069
	/**
1070
	 * Get line height.
1071
	 *
1072
	 * @return string
1073
	 */
1074
	public function getLineHeight()
1075
	{
1076
		$box = $this->getBox();
1077
		if (!$box->isRenderable() && !$this->haveSpacing()) {
1078
			return '0';
1079
		}
1080
		if ($box instanceof InlineBox) {
1081
			return Math::add((string) $this->rules['line-height'], $this->getVerticalBordersWidth());
1082
		}
1083
1084
		return Math::add((string) $this->rules['line-height'], Math::add($this->getVerticalPaddingsWidth(), $this->getVerticalBordersWidth()));
1085
	}
1086
1087
	/**
1088
	 * Get line height.
1089
	 *
1090
	 * @return string
1091
	 */
1092
	public function getMaxLineHeight()
1093
	{
1094
		if ($this->maxLineHeight) {
1095
			return $this->maxLineHeight;
1096
		}
1097
		$box = $this->getBox();
1098
		$lineHeight = $this->getRules('line-height');
1099
		if (false !== strpos($lineHeight, '%')) {
0 ignored issues
show
Bug introduced by
It seems like $lineHeight can also be of type array; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1099
		if (false !== strpos(/** @scrutinizer ignore-type */ $lineHeight, '%')) {
Loading history...
1100
			// TODO: Add percentage support
1101
			$lineHeight = '0.00';
1102
		}
1103
		if ('inline' !== $this->getRules('display') && '0.00' !== $this->getVerticalPaddingsWidth() && '0.00' !== $this->getVerticalBordersWidth()) {
1104
			$lineHeight = Math::add($lineHeight, $this->getVerticalPaddingsWidth(), $this->getVerticalBordersWidth());
0 ignored issues
show
Bug introduced by
It seems like $lineHeight can also be of type array; however, parameter $numbers of YetiForcePDF\Math::add() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1104
			$lineHeight = Math::add(/** @scrutinizer ignore-type */ $lineHeight, $this->getVerticalPaddingsWidth(), $this->getVerticalBordersWidth());
Loading history...
1105
		}
1106
		foreach ($box->getChildren() as $child) {
1107
			$maxLineHeight = $child->getStyle()->getMaxLineHeight();
1108
			$lineHeight = Math::max($lineHeight, $maxLineHeight, $child->getDimensions()->getHeight());
0 ignored issues
show
Bug introduced by
It seems like $child->getDimensions()->getHeight() can also be of type null; however, parameter $numbers of YetiForcePDF\Math::max() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1108
			$lineHeight = Math::max($lineHeight, $maxLineHeight, /** @scrutinizer ignore-type */ $child->getDimensions()->getHeight());
Loading history...
Bug introduced by
It seems like $lineHeight can also be of type array; however, parameter $numbers of YetiForcePDF\Math::max() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1108
			$lineHeight = Math::max(/** @scrutinizer ignore-type */ $lineHeight, $maxLineHeight, $child->getDimensions()->getHeight());
Loading history...
1109
		}
1110
		if (!$box instanceof LineBox) {
0 ignored issues
show
Bug introduced by
The type YetiForcePDF\Style\LineBox was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1111
			$this->maxLineHeight = $lineHeight;
0 ignored issues
show
Documentation Bug introduced by
It seems like $lineHeight can also be of type array. However, the property $maxLineHeight is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1112
		}
1113
		return $lineHeight;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $lineHeight also could return the type array which is incompatible with the documented return type string.
Loading history...
1114
	}
1115
1116
	/**
1117
	 * Get mandatory rules - with default for all elements.
1118
	 *
1119
	 * @param string $elementName
1120
	 *
1121
	 * @return array
1122
	 */
1123
	public function getMandatoryRules()
1124
	{
1125
		return $this->mandatoryRules;
1126
	}
1127
1128
	/**
1129
	 * Get default element rules.
1130
	 *
1131
	 * @return array
1132
	 */
1133
	public function getDefaultRules()
1134
	{
1135
		$rules = [];
1136
		if (!empty($this->elementDefaults[$this->elementName])) {
1137
			foreach ($this->elementDefaults[$this->elementName] as $ruleName => $ruleValue) {
1138
				$rules[$ruleName] = $ruleValue;
1139
			}
1140
		}
1141
1142
		return $rules;
1143
	}
1144
1145
	/**
1146
	 * Parse inline style without inheritance and normalizer.
1147
	 *
1148
	 * @return $this
1149
	 */
1150
	public function parseInline()
1151
	{
1152
		$parsed = array_merge($this->getMandatoryRules(), $this->getDefaultRules());
1153
		if ($this->content) {
1154
			$rules = explode(';', $this->content);
1155
		} else {
1156
			$rules = [];
1157
		}
1158
		$rulesParsed = [];
1159
		foreach ($rules as $rule) {
1160
			$rule = trim($rule);
1161
			if ('' !== $rule && false !== strpos($rule, ':')) {
1162
				$ruleExploded = explode(':', $rule);
1163
				$ruleName = trim($ruleExploded[0]);
1164
				$ruleValue = trim($ruleExploded[1]);
1165
				$rulesParsed[$ruleName] = $ruleValue;
1166
			}
1167
		}
1168
		$rulesParsed = array_merge($parsed, $rulesParsed);
1169
		if ($this->getElement()) {
1170
			if ($this->getElement()->getDOMElement() instanceof \DOMText) {
0 ignored issues
show
introduced by
$this->getElement()->getDOMElement() is never a sub-type of DOMText.
Loading history...
1171
				$rulesParsed['display'] = 'inline';
1172
			}
1173
		}
1174
		$this->rules = $rulesParsed;
1175
1176
		return $this;
1177
	}
1178
1179
	/**
1180
	 * First of all parse font for convertUnits method.
1181
	 *
1182
	 * @param array $rulesParsed
1183
	 *
1184
	 * @return $this
1185
	 */
1186
	protected function parseFont(array $rulesParsed)
1187
	{
1188
		$finalRules = [];
1189
		foreach ($rulesParsed as $ruleName => $ruleValue) {
1190
			if ('font' === substr($ruleName, 0, 4)) {
1191
				$normalizerName = \YetiForcePDF\Style\Normalizer\Normalizer::getNormalizerClassName($ruleName);
1192
				$normalizer = (new $normalizerName())
1193
					->setDocument($this->document)
1194
					->setStyle($this)
1195
					->init();
1196
				foreach ($normalizer->normalize($ruleValue) as $name => $value) {
1197
					$finalRules[$name] = $value;
1198
				}
1199
			}
1200
		}
1201
		if (isset($finalRules['font-family'], $finalRules['font-weight'], $finalRules['font-style'], $finalRules['font-size'])) {
1202
			$this->font = (new \YetiForcePDF\Objects\Font())
1203
				->setDocument($this->document)
1204
				->setFamily($finalRules['font-family'])
1205
				->setWeight($finalRules['font-weight'])
1206
				->setStyle($finalRules['font-style'])
1207
				->init();
1208
			if ($parent = $this->getParent()) {
1209
				$this->font->setParent($parent->getFont());
1210
			}
1211
			// size must be defined after initialisation because we could get cloned font that already exists
1212
			$this->font->setSize($finalRules['font-size'], $rulesParsed['font-size']);
0 ignored issues
show
Unused Code introduced by
The call to YetiForcePDF\Objects\Font::setSize() has too many arguments starting with $rulesParsed['font-size']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1212
			$this->font->/** @scrutinizer ignore-call */ 
1213
                setSize($finalRules['font-size'], $rulesParsed['font-size']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1213
		}
1214
		return array_merge($rulesParsed, $finalRules);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_merge($rulesParsed, $finalRules) returns the type array which is incompatible with the documented return type YetiForcePDF\Style\Style.
Loading history...
1215
	}
1216
1217
	/**
1218
	 * Parse and load image file.
1219
	 *
1220
	 * @param array $rulesParsed
1221
	 *
1222
	 * @return array
1223
	 */
1224
	protected function parseImage(array $rulesParsed)
1225
	{
1226
		if ($element = $this->getElement()) {
1227
			if (($domElement = $element->getDOMElement()) && isset($domElement->tagName)) {
1228
				if ('img' === $domElement->tagName && $domElement->getAttribute('src')) {
1229
					$rulesParsed['background-image'] = 'url(' . $domElement->getAttribute('src') . ');';
1230
				}
1231
			}
1232
		}
1233
		if (!isset($rulesParsed['background-image'])) {
1234
			return $rulesParsed;
1235
		}
1236
		$src = $rulesParsed['background-image'];
1237
		if ('url' !== substr($src, 0, 3)) {
1238
			return $rulesParsed;
1239
		}
1240
		$src = trim(substr($src, 3), ';)(\'\"');
1241
		$this->backgroundImage = (new ImageStream())
1242
			->setDocument($this->document)
1243
			->init();
1244
		$this->backgroundImage->loadImage($src);
1245
		$imageName = $this->backgroundImage->getImageName();
1246
		$this->document->getCurrentPage()->addResource('XObject', $imageName, $this->backgroundImage);
1247
		return $rulesParsed;
1248
	}
1249
1250
	/**
1251
	 * Parse graphic states.
1252
	 *
1253
	 * @param array $rulesParsed
1254
	 *
1255
	 * @return array
1256
	 */
1257
	protected function parseGraphicState(array $rulesParsed)
1258
	{
1259
		$ruleName = 'opacity';
1260
		if (!isset($rulesParsed[$ruleName])) {
1261
			return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type YetiForcePDF\Style\Style which is incompatible with the documented return type array.
Loading history...
1262
		}
1263
		$ruleValue = $rulesParsed['opacity'];
1264
		$normalizerName = \YetiForcePDF\Style\Normalizer\Normalizer::getNormalizerClassName($ruleName);
1265
		$normalizer = (new $normalizerName())
1266
			->setDocument($this->document)
1267
			->setStyle($this)
1268
			->init();
1269
		foreach ($normalizer->normalize($ruleValue, $ruleName) as $name => $value) {
1270
			$rulesParsed[$name] = $value;
1271
		}
1272
		$this->graphicState = (new GraphicState())
1273
			->setDocument($this->document)
1274
			->init();
1275
		$this->graphicState->addValue('ca', $rulesParsed[$ruleName]);
1276
		$this->graphicState->addValue('CA', $rulesParsed[$ruleName]);
1277
1278
		return $rulesParsed;
1279
	}
1280
1281
	/**
1282
	 * Apply text style - default style for text nodes.
1283
	 *
1284
	 * @param array  $rulesParsed
1285
	 * @param &array $inherited
1286
	 *
1287
	 * @return array
1288
	 */
1289
	public function applyTextStyle($rulesParsed)
1290
	{
1291
		if ($this->getElement()->getDOMElement() instanceof \DOMText) {
0 ignored issues
show
introduced by
$this->getElement()->getDOMElement() is never a sub-type of DOMText.
Loading history...
1292
			$rulesParsed['display'] = 'inline';
1293
			$rulesParsed['line-height'] = '1';
1294
			// if this is text node it's mean that it was wrapped by anonymous inline element
1295
			// so wee need to copy vertical align property (because it is not inherited by default)
1296
			if ($this->getParent()) {
1297
				$rulesParsed['vertical-align'] = $this->getParent()->getRules('vertical-align');
1298
			}
1299
		}
1300
1301
		return $rulesParsed;
1302
	}
1303
1304
	/**
1305
	 * Apply border spacing for table cell elements.
1306
	 *
1307
	 * @param array $rulesParsed
1308
	 *
1309
	 * @return array
1310
	 */
1311
	public function applyBorderSpacing($rulesParsed)
1312
	{
1313
		if ($this->getElement()) {
1314
			if ($this->box instanceof TableCellBox) {
1315
				$tableWrapperStyle = $this->getParent();
1316
				if ('collapse' !== $tableWrapperStyle->getRules('border-collapse')) {
1317
					$padding = $tableWrapperStyle->getRules('border-spacing');
1318
					$tableWrapperStyle->setRule('padding-top', $padding);
0 ignored issues
show
Bug introduced by
It seems like $padding can also be of type array; however, parameter $ruleValue of YetiForcePDF\Style\Style::setRule() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1318
					$tableWrapperStyle->setRule('padding-top', /** @scrutinizer ignore-type */ $padding);
Loading history...
1319
					$tableWrapperStyle->setRule('padding-right', $padding);
1320
					$tableWrapperStyle->setRule('padding-bottom', $padding);
1321
					$tableWrapperStyle->setRule('padding-left', $padding);
1322
				}
1323
			}
1324
		}
1325
1326
		return $rulesParsed;
1327
	}
1328
1329
	/**
1330
	 * Get parent original value - traverse tree to first occurence.
1331
	 *
1332
	 * @param string $ruleName
1333
	 *
1334
	 * @return string|null
1335
	 */
1336
	public function getParentOriginalValue(string $ruleName)
1337
	{
1338
		if ($parent = $this->getParent()) {
1339
			$parentValue = $parent->getOriginalRules($ruleName);
1340
			if (null !== $parentValue) {
1341
				return $parentValue;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $parentValue also could return the type array which is incompatible with the documented return type null|string.
Loading history...
1342
			}
1343
1344
			return $parent->getParentOriginalValue($ruleName);
1345
		}
1346
	}
1347
1348
	/**
1349
	 * Import class rules if exists.
1350
	 *
1351
	 * @param array $parsed rules
1352
	 */
1353
	protected function importClassRules()
1354
	{
1355
		$element = $this->getElement();
1356
		if ($element && !empty($element->getClassNames())) {
1357
			return $this->document->getCssSelectorRules($element->getClassNames());
0 ignored issues
show
Bug introduced by
$element->getClassNames() of type string[] is incompatible with the type string expected by parameter $selector of YetiForcePDF\Document::getCssSelectorRules(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1357
			return $this->document->getCssSelectorRules(/** @scrutinizer ignore-type */ $element->getClassNames());
Loading history...
1358
		}
1359
		return [];
1360
	}
1361
1362
	/**
1363
	 * Inherite rule.
1364
	 *
1365
	 * @param string $ruleName
1366
	 *
1367
	 * @return string $ruleValue
1368
	 */
1369
	protected function inheriteRule(string $ruleName)
1370
	{
1371
		if (isset($this->originalRules[$ruleName]) && 'inherit' !== $this->originalRules[$ruleName]) {
1372
			return $this->originalRules[$ruleName];
1373
		}
1374
		$parent = $this->getParent();
1375
		if ($parent) {
1376
			return $parent->inheriteRule($ruleName);
1377
		}
1378
		return $this->mandatoryRules[$ruleName];
1379
	}
1380
1381
	/**
1382
	 * Inherite rules.
1383
	 *
1384
	 * @param array $rules
1385
	 *
1386
	 * @return array
1387
	 */
1388
	protected function inherite(array $rules)
1389
	{
1390
		foreach ($rules as $ruleName => $ruleValue) {
1391
			if ('inherit' === $ruleValue) {
1392
				$rules[$ruleName] = $this->inheriteRule($ruleName);
1393
			}
1394
		}
1395
		return $rules;
1396
	}
1397
1398
	/**
1399
	 * Parse css style.
1400
	 *
1401
	 * @return $this
1402
	 */
1403
	protected function parse()
1404
	{
1405
		if ($this->parsed) {
1406
			return $this;
1407
		}
1408
		$this->mandatoryRules['font-family'] = Font::$defaultFontFamily;
1409
		$mandatory = [];
1410
		foreach ($this->getMandatoryRules() as $mandatoryName => $mandatoryValue) {
1411
			$mandatory[$mandatoryName] = $mandatoryValue;
1412
		}
1413
		$default = $this->getDefaultRules();
1414
		if ($this->document->inDebugMode() && $this->getBox() instanceof \YetiForcePDF\Layout\LineBox) {
1415
			$this->content = 'border:1px solid red;';
1416
		}
1417
		$inherited = [];
1418
		if ($parent = $this->getParent()) {
1419
			$inherited = $parent->getInheritedRules();
1420
		}
1421
		$class = $this->importClassRules();
1422
		if ($this->content) {
1423
			$inlineTemp = explode(';', $this->content);
1424
		} else {
1425
			$inlineTemp = [];
1426
		}
1427
		$inline = [];
1428
		foreach ($inlineTemp as $rule) {
1429
			$rule = trim($rule);
1430
			if ('' !== $rule && false !== strpos($rule, ':')) {
1431
				$ruleExploded = explode(':', $rule);
1432
				$inline[trim($ruleExploded[0])] = trim($ruleExploded[1]);
1433
			}
1434
		}
1435
		// inherited below are inherited that doesn't have default values
1436
		$rulesParsed = array_merge($mandatory, $inherited, $default, $class, $inline);
1437
1438
		// inherited below are those which have value = 'inherit' so they must inherit from parent
1439
		$rulesParsed = $this->inherite($rulesParsed);
1440
		$this->originalRules = $rulesParsed;
1441
		foreach ($rulesParsed as $ruleName => $ruleValue) {
1442
			if (strpos($ruleValue, 'data_image') > 0) {
1443
				$ruleValue = preg_replace('/data_image\/([a-z]+)_/', 'data:image/$1;', $ruleValue);
0 ignored issues
show
Unused Code introduced by
The assignment to $ruleValue is dead and can be removed.
Loading history...
1444
			}
1445
		}
1446
		$rulesParsed = $this->parseFont($rulesParsed);
1447
1448
		if ($this->getElement()) {
1449
			$rulesParsed = $this->applyTextStyle($rulesParsed);
0 ignored issues
show
Bug introduced by
$rulesParsed of type YetiForcePDF\Style\Style is incompatible with the type array expected by parameter $rulesParsed of YetiForcePDF\Style\Style::applyTextStyle(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1449
			$rulesParsed = $this->applyTextStyle(/** @scrutinizer ignore-type */ $rulesParsed);
Loading history...
1450
			$rulesParsed = $this->applyBorderSpacing($rulesParsed);
1451
		}
1452
		$rulesParsed = $this->parseImage($rulesParsed);
0 ignored issues
show
Bug introduced by
It seems like $rulesParsed can also be of type YetiForcePDF\Style\Style; however, parameter $rulesParsed of YetiForcePDF\Style\Style::parseImage() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1452
		$rulesParsed = $this->parseImage(/** @scrutinizer ignore-type */ $rulesParsed);
Loading history...
1453
		$rulesParsed = $this->parseGraphicState($rulesParsed);
1454
		$finalRules = [];
1455
		foreach ($rulesParsed as $ruleName => $ruleValue) {
1456
			if ('font' !== substr($ruleName, 0, 4)) {
1457
				$normalizerName = \YetiForcePDF\Style\Normalizer\Normalizer::getNormalizerClassName($ruleName);
1458
				$normalizer = (new $normalizerName())
1459
					->setDocument($this->document)
1460
					->setStyle($this)
1461
					->init();
1462
				foreach ($normalizer->normalize($ruleValue, $ruleName) as $name => $value) {
1463
					$finalRules[$name] = $value;
1464
				}
1465
			} else {
1466
				$finalRules[$ruleName] = $ruleValue;
1467
			}
1468
		}
1469
		if ('inline' === $finalRules['display']) {
1470
			$finalRules['margin-top'] = '0';
1471
			$finalRules['margin-bottom'] = '0';
1472
		} elseif (\in_array($finalRules['display'], [
1473
			'table-cell', 'table-row', 'table-row-group', 'table-column',
1474
			'table-column-group', 'table-header-group', 'table-footer-group',
1475
		])) {
1476
			$finalRules['margin-top'] = '0';
1477
			$finalRules['margin-bottom'] = '0';
1478
			$finalRules['margin-left'] = '0';
1479
			$finalRules['margin-right'] = '0';
1480
		}
1481
		$this->rules = $finalRules;
1482
		$this->setInitialRules($finalRules);
1483
		$this->parsed = true;
1484
		unset($finalRules, $rules, $rulesParsed, $default, $inherited, $class, $inline, $inlineTemp);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rules does not exist. Did you maybe mean $rule?
Loading history...
1485
		return $this;
1486
	}
1487
1488
	/**
1489
	 * Fix tables.
1490
	 *
1491
	 * @return $this
1492
	 */
1493
	public function fixTables()
1494
	{
1495
		$box = $this->getBox();
1496
		if ($box->wasCut()) {
1497
			return $this;
1498
		}
1499
		$boxStyle = $box->getStyle();
1500
		$boxes = [$box];
1501
		if (!$box instanceof TableBox) {
1502
			$boxes = $box->getBoxesByType('TableBox');
1503
		}
1504
		foreach ($boxes as $box) {
1505
			// max cell borders widths top,right,bottom,left
1506
			$cellBorders = ['0', '0', '0', '0'];
1507
			$rowGroups = $box->getChildren(true, true);
1508
			$rowGroupsCount = \count($rowGroups);
0 ignored issues
show
Unused Code introduced by
The assignment to $rowGroupsCount is dead and can be removed.
Loading history...
1509
			foreach ($rowGroups as $rowGroupIndex => $rowGroup) {
1510
				$rows = $rowGroup->getChildren(true, true);
1511
				$rowsCount = \count($rows);
1512
				foreach ($rows as $rowIndex => $row) {
1513
					$columns = $row->getChildren(true, true);
1514
					$columnsCount = \count($columns);
1515
					foreach ($columns as $columnIndex => $column) {
1516
						$rowStyle = $column->getParent()->getStyle();
1517
						$columnStyle = $column->getStyle();
1518
						if ($columnIndex + 1 < $columnsCount) {
1519
							$columnStyle->setRule('padding-right', '0');
1520
						}
1521
						$columnStyle->setRule('padding-bottom', '0');
1522
						$cellBorders = [
1523
							Math::max($cellBorders[0], $rowStyle->getRules('border-top-width')),
1524
							Math::max($cellBorders[1], $rowStyle->getRules('border-right-width')),
1525
							Math::max($cellBorders[2], $rowStyle->getRules('border-bottom-width')),
1526
							Math::max($cellBorders[3], $rowStyle->getRules('border-left-width')),
1527
						];
1528
						if ('collapse' === $columnStyle->getRules('border-collapse')) {
1529
							$cellStyle = $column->getFirstChild()->getStyle();
1530
							$cellBorders = [
1531
								Math::max($cellBorders[0], $cellStyle->getRules('border-top-width')),
1532
								Math::max($cellBorders[1], $cellStyle->getRules('border-right-width')),
1533
								Math::max($cellBorders[2], $cellStyle->getRules('border-bottom-width')),
1534
								Math::max($cellBorders[3], $cellStyle->getRules('border-left-width')),
1535
							];
1536
							if ($rowIndex + $column->getRowSpan() < $rowsCount) {
1537
								$cellStyle->setRule('border-bottom-width', '0');
1538
							} elseif (0 === Math::comp($cellStyle->getRules('border-bottom-width'), '0')) {
1539
								$cellStyle->setRule('border-bottom-width', $this->getInitialRules('border-bottom-width'));
1540
							}
1541
							if ($columnIndex + $column->getColSpan() < $columnsCount) {
1542
								$cellStyle->setRule('border-right-width', '0');
1543
							}
1544
						}
1545
						// move specified css width to proper elements
1546
						$cellStyle = $column->getFirstChild()->getStyle();
1547
						if ('auto' !== $cellStyle->getRules('width')) {
1548
							$columnStyle->setRule('width', $cellStyle->getRules('width'));
1549
							$cellStyle->setRule('width', 'auto');
1550
						}
1551
					}
1552
					if ('collapse' === $boxStyle->getRules('border-collapse')) {
1553
						$tableWrapperStyle = $box->getParent()->getStyle();
1554
						if (Math::comp($cellBorders[0], $tableWrapperStyle->getRules('border-top-width')) >= 0) {
1555
							$tableWrapperStyle->setRule('border-top-width', '0');
1556
						}
1557
						if (Math::comp($cellBorders[1], $tableWrapperStyle->getRules('border-right-width')) >= 0) {
1558
							$tableWrapperStyle->setRule('border-right-width', '0');
1559
						}
1560
						if (Math::comp($cellBorders[2], $tableWrapperStyle->getRules('border-bottom-width')) >= 0) {
1561
							$tableWrapperStyle->setRule('border-bottom-width', '0');
1562
						}
1563
						if (Math::comp($cellBorders[3], $tableWrapperStyle->getRules('border-left-width')) >= 0) {
1564
							$tableWrapperStyle->setRule('border-left-width', '0');
1565
						}
1566
					}
1567
				}
1568
			}
1569
			foreach ($box->getChildren() as $rowGroupIndex => $rowGroup) {
1570
				$rows = $rowGroup->getChildren();
1571
				if ('collapse' === $boxStyle->getRules('border-collapse')) {
1572
					$rowsCount = \count($rows) - 1;
1573
					foreach ($rows as $rowIndex => $row) {
1574
						if ($rowIndex < $rowsCount) {
1575
							$row->getStyle()->setRule('border-bottom-width', '0');
1576
						}
1577
					}
1578
				} else {
1579
					foreach ($rows as $row) {
1580
						$rowStyle = $row->getStyle();
1581
						$rowStyle->setRule('border-top-width', '0');
1582
						$rowStyle->setRule('border-right-width', '0');
1583
						$rowStyle->setRule('border-bottom-width', '0');
1584
						$rowStyle->setRule('border-left-width', '0');
1585
						if ('transparent' !== $rowStyle->getRules('background-color')) {
1586
							foreach ($row->getChildren() as $column) {
1587
								$cell = $column->getFirstChild();
1588
								$cell->getStyle()->setRule('background-color', $rowStyle->getRules('background-color'));
1589
							}
1590
							$rowStyle->setRule('background-color', 'transparent');
1591
						}
1592
					}
1593
				}
1594
			}
1595
		}
1596
		unset($rowGroup, $boxes, $rows, $columns);
1597
		return $this;
1598
	}
1599
1600
	/**
1601
	 * Fix dom tree - after dom tree is parsed we must clean up or add some rules.
1602
	 *
1603
	 * @return $this
1604
	 */
1605
	public function fixDomTree()
1606
	{
1607
		if ($this->getBox()->wasCut()) {
1608
			return $this;
1609
		}
1610
		foreach ($this->box->getChildren() as $childBox) {
1611
			$childBox->getStyle()->fixTables();
1612
			$childBox->getStyle()->fixDomTree();
1613
		}
1614
1615
		return $this;
1616
	}
1617
1618
	/**
1619
	 * Clear style for first inline element (in line).
1620
	 *
1621
	 * @return $this
1622
	 */
1623
	public function clearFirstInline()
1624
	{
1625
		$box = $this->getBox();
1626
		$dimensions = $box->getDimensions();
1627
		if ($dimensions->getWidth()) {
1628
			$dimensions->setWidth(Math::sub($dimensions->getWidth(), $this->getFullRightSpace()));
1629
		}
1630
		$this->setRule('margin-right', '0');
1631
		$this->setRule('padding-right', '0');
1632
		$this->setRule('border-right-width', '0');
1633
		if ('inline' === $this->rules['display']) {
1634
			$this->setRule('margin-top', '0');
1635
			$this->setRule('margin-bottom', '0');
1636
		}
1637
1638
		return $this;
1639
	}
1640
1641
	/**
1642
	 * Clear style for last inline element (in line).
1643
	 *
1644
	 * @return $this
1645
	 */
1646
	public function clearLastInline()
1647
	{
1648
		$box = $this->getBox();
1649
		$leftSpace = $this->getFullLeftSpace();
1650
		$dimensions = $box->getDimensions();
1651
		if ($dimensions->getWidth()) {
1652
			$dimensions->setWidth(Math::sub($dimensions->getWidth(), $leftSpace));
1653
		}
1654
		$offset = $box->getOffset();
1655
		if ($offset->getLeft()) {
1656
			$offset->setLeft(Math::sub($offset->getLeft(), $leftSpace));
1657
		}
1658
		$coordinates = $box->getCoordinates();
1659
		if ($coordinates->getX()) {
1660
			$coordinates->setX(Math::sub($coordinates->getX(), $leftSpace));
1661
		}
1662
		$this->setRule('margin-left', '0');
1663
		$this->setRule('padding-left', '0');
1664
		$this->setRule('border-left-width', '0');
1665
		if ('inline' === $this->rules['display']) {
1666
			$this->setRule('margin-top', '0');
1667
			$this->setRule('margin-bottom', '0');
1668
		}
1669
1670
		return $this;
1671
	}
1672
1673
	/**
1674
	 * Clear style for middle inline element (in line).
1675
	 *
1676
	 * @return $this
1677
	 */
1678
	public function clearMiddleInline()
1679
	{
1680
		$box = $this->getBox();
1681
		$leftSpace = $this->getFullLeftSpace();
1682
		$dimensions = $box->getDimensions();
1683
		if ($dimensions->getWidth()) {
1684
			$sub = Math::add($this->getHorizontalMarginsWidth(), $this->getHorizontalBordersWidth());
1685
			$dimensions->setWidth(Math::sub($dimensions->getWidth(), $sub));
1686
		}
1687
		$offset = $box->getOffset();
1688
		if ($offset->getLeft()) {
1689
			$offset->setLeft(Math::sub($offset->getLeft(), $leftSpace));
1690
		}
1691
		$coordinates = $box->getCoordinates();
1692
		if ($coordinates->getX()) {
1693
			$coordinates->setX(Math::sub($coordinates->getX(), $leftSpace));
1694
		}
1695
		$this->setRule('margin-left', '0');
1696
		$this->setRule('margin-right', '0');
1697
		$this->setRule('padding-left', '0');
1698
		$this->setRule('padding-right', '0');
1699
		$this->setRule('border-right-width', '0');
1700
		$this->setRule('border-left-width', '0');
1701
		if ('inline' === $this->rules['display']) {
1702
			$this->setRule('margin-top', '0');
1703
			$this->setRule('margin-bottom', '0');
1704
		}
1705
1706
		return $this;
1707
	}
1708
1709
	/**
1710
	 * Parse css rules.
1711
	 *
1712
	 * @param string $cssString
1713
	 *
1714
	 * @return $this
1715
	 */
1716
	public function parseCss(string $cssString): self
1717
	{
1718
		$css = (new CSSParser($cssString))->parse();
1719
		foreach ($css->getAllRuleSets() as $ruleSet) {
1720
			$selector = implode(' ', $ruleSet->getSelector());
0 ignored issues
show
Bug introduced by
The method getSelector() does not exist on Sabberworm\CSS\RuleSet\RuleSet. It seems like you code against a sub-type of Sabberworm\CSS\RuleSet\RuleSet such as Sabberworm\CSS\RuleSet\DeclarationBlock. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1720
			$selector = implode(' ', $ruleSet->/** @scrutinizer ignore-call */ getSelector());
Loading history...
1721
			$selectorRules = (array) $ruleSet->getRules();
1722
			$rules = [];
1723
			foreach ($selectorRules as $rule) {
1724
				$rules[$rule->getRule()] = trim((string) $rule->getValue(), '" \'');
1725
			}
1726
			$this->document->addCssSelectorRules($selector, $rules);
1727
		}
1728
1729
		return $this;
1730
	}
1731
1732
	/**
1733
	 * Get transformations.
1734
	 *
1735
	 * @param string $x
1736
	 * @param string $y
1737
	 *
1738
	 * @return string
1739
	 */
1740
	public function getTransformations(string $x, string $y)
1741
	{
1742
		return "1 0 0 1 {$x} {$y} cm";
1743
	}
1744
1745
	/**
1746
	 * Clone.
1747
	 *
1748
	 * @param Box $box
1749
	 *
1750
	 * @return Style
1751
	 */
1752
	public function clone(Box $box)
1753
	{
1754
		$newStyle = clone $this;
1755
		$newStyle->setBox($box);
1756
1757
		return $newStyle;
1758
	}
1759
1760
	public function __clone()
1761
	{
1762
		$this->font = clone $this->font;
1763
		if ($this->element) {
1764
			$this->element = clone $this->element;
1765
		}
1766
	}
1767
1768
	/**
1769
	 * Get initial rules because rules may change during the rearrangement.
1770
	 *
1771
	 * @param string $ruleName
1772
	 *
1773
	 * @return array
1774
	 */
1775
	public function getInitialRules(string $ruleName = '')
1776
	{
1777
		if ($ruleName && isset($this->initialRules[$ruleName])) {
1778
			return $this->initialRules[$ruleName];
1779
		}
1780
		if ($ruleName) {
1781
			return null;
1782
		}
1783
		return $this->initialRules;
1784
	}
1785
1786
	/**
1787
	 * Set initial rules because rules may change during the rearrangement.
1788
	 *
1789
	 * @param array $initialRules
1790
	 *
1791
	 * @return self
1792
	 */
1793
	public function setInitialRules(array $initialRules)
1794
	{
1795
		$this->initialRules = $initialRules;
1796
1797
		return $this;
1798
	}
1799
}
1800