TableBox   F
last analyzed

Complexity

Total Complexity 219

Size/Duplication

Total Lines 1382
Duplicated Lines 0 %

Importance

Changes 41
Bugs 2 Features 0
Metric Value
wmc 219
eloc 697
c 41
b 2
f 0
dl 0
loc 1382
rs 1.903

39 Methods

Rating   Name   Duplication   Size   Complexity  
A appendTableWrapperBox() 0 2 1
A createRowGroupBox() 0 15 1
A appendBlockBox() 0 2 1
A getColumns() 0 14 5
A appendTableRowGroupBox() 0 28 3
A appendTableRowBox() 0 13 1
A getCells() 0 12 4
A getRows() 0 12 4
A appendInlineBox() 0 2 1
A appendInlineBlockBox() 0 2 1
A getAutoColumnsWidth() 0 8 2
B removeEmptyRows() 0 15 7
B addToOthers() 0 50 11
A getAutoColumnsMinWidth() 0 8 2
A getCurrentOthersWidth() 0 11 3
A getRowInnerWidth() 0 8 2
D shrinkToFit() 0 109 20
B measureWidth() 0 42 8
C spanRows() 0 58 14
C addToPreferredOthers() 0 63 11
A setColumnWidth() 0 7 1
A finish() 0 23 4
B expandPercentsToMin() 0 42 6
A willFit() 0 7 1
A minContentGuess() 0 10 3
B setUpWidths() 0 40 11
A maxContentGuess() 0 12 3
A getTotalPercentage() 0 8 2
A getTotalPercentageWidth() 0 8 2
A setRowsWidth() 0 25 5
A minContentPercentageGuess() 0 7 1
A minContentSpecifiedGuess() 0 18 4
B setUpSizingTypes() 0 34 11
A getAutoColumnsMaxWidth() 0 8 2
A saveState() 0 8 2
B tryPreferred() 0 57 11
A getMinWidth() 0 11 2
C applyPercentage() 0 55 13
F measureHeight() 0 134 33

How to fix   Complexity   

Complex Class

Complex classes like TableBox often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TableBox, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * TableBox class.
6
 *
7
 * @package   YetiForcePDF\Layout
8
 *
9
 * @copyright YetiForce Sp. z o.o
10
 * @license   MIT
11
 * @author    Rafal Pospiech <[email protected]>
12
 */
13
14
namespace YetiForcePDF\Layout;
15
16
use YetiForcePDF\Html\Element;
17
use YetiForcePDF\Math;
18
use YetiForcePDF\Style\Style;
19
20
/**
21
 * Class TableBox.
22
 */
23
class TableBox extends BlockBox
24
{
25
	/**
26
	 * @var array minimal widths
27
	 */
28
	protected $minWidths = [];
29
	/**
30
	 * @var array preferred widths
31
	 */
32
	protected $preferredWidths = [];
33
	/**
34
	 * @var array maximal widths
35
	 */
36
	protected $contentWidths = [];
37
	/**
38
	 * @var string total min width
39
	 */
40
	protected $minWidth = '0';
41
	/**
42
	 * @var string total preferred width
43
	 */
44
	protected $preferredWidth = '0';
45
	/**
46
	 * @var string total max width
47
	 */
48
	protected $contentWidth = '0';
49
	/**
50
	 * @var array percentages for each percentage column
51
	 */
52
	protected $percentages = [];
53
	/**
54
	 * @var string cell spacing total width
55
	 */
56
	protected $cellSpacingWidth = '0';
57
	/**
58
	 * @var string total border width
59
	 */
60
	protected $borderWidth = '0';
61
	/**
62
	 * @var array percentage columns
63
	 */
64
	protected $percentColumns = [];
65
	/**
66
	 * @var array pixel columns
67
	 */
68
	protected $pixelColumns = [];
69
	/**
70
	 * @var array auto width columns
71
	 */
72
	protected $autoColumns = [];
73
	/**
74
	 * @var array saving state
75
	 */
76
	protected $beforeWidths = [];
77
	/**
78
	 * @var array rows
79
	 */
80
	protected $rows = [];
81
	/**
82
	 * @var TableRowGroupBox|null
83
	 */
84
	protected $anonymousRowGroup;
85
	/**
86
	 * Parent width cache.
87
	 *
88
	 * @var string
89
	 */
90
	protected $parentWidth = '0';
91
92
	/**
93
	 * We shouldn't append block box here.
94
	 *
95
	 * @param mixed $childDomElement
96
	 * @param mixed $element
97
	 * @param mixed $style
98
	 * @param mixed $parentBlock
99
	 */
100
	public function appendBlockBox($childDomElement, $element, $style, $parentBlock)
101
	{
102
	}
103
104
	/**
105
	 * We shouldn't append table wrapper here.
106
	 *
107
	 * @param mixed $childDomElement
108
	 * @param mixed $element
109
	 * @param mixed $style
110
	 * @param mixed $parentBlock
111
	 */
112
	public function appendTableWrapperBox($childDomElement, $element, $style, $parentBlock)
113
	{
114
	}
115
116
	/**
117
	 * We shouldn't append inline block box here.
118
	 *
119
	 * @param mixed $childDomElement
120
	 * @param mixed $element
121
	 * @param mixed $style
122
	 * @param mixed $parentBlock
123
	 */
124
	public function appendInlineBlockBox($childDomElement, $element, $style, $parentBlock)
125
	{
126
	}
127
128
	/**
129
	 * We shouldn't append inline box here.
130
	 *
131
	 * @param mixed $childDomElement
132
	 * @param mixed $element
133
	 * @param mixed $style
134
	 * @param mixed $parentBlock
135
	 */
136
	public function appendInlineBox($childDomElement, $element, $style, $parentBlock)
137
	{
138
	}
139
140
	/**
141
	 * Create row group inside table
142
	 * return TableRowGroupBox.
143
	 */
144
	public function createRowGroupBox()
145
	{
146
		$style = (new \YetiForcePDF\Style\Style())
147
			->setDocument($this->document)
148
			->setContent('')
149
			->parseInline();
150
		$box = (new TableRowGroupBox())
151
			->setDocument($this->document)
152
			->setParent($this)
153
			->setStyle($style)
154
			->init();
155
		$this->appendChild($box);
156
		$box->getStyle()->init();
157
158
		return $box;
159
	}
160
161
	/**
162
	 * Append table row group box element.
163
	 *
164
	 * @param \DOMNode                      $childDomElement
165
	 * @param Element                       $element
166
	 * @param Style                         $style
167
	 * @param \YetiForcePDF\Layout\BlockBox $parentBlock
168
	 * @param string                        $display
169
	 *
170
	 * @return $this
171
	 */
172
	public function appendTableRowGroupBox($childDomElement, $element, $style, $parentBlock, string $display)
0 ignored issues
show
Unused Code introduced by
The parameter $parentBlock 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

172
	public function appendTableRowGroupBox($childDomElement, $element, $style, /** @scrutinizer ignore-unused */ $parentBlock, string $display)

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...
Unused Code introduced by
The parameter $style 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

172
	public function appendTableRowGroupBox($childDomElement, $element, /** @scrutinizer ignore-unused */ $style, $parentBlock, string $display)

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...
Unused Code introduced by
The parameter $childDomElement 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

172
	public function appendTableRowGroupBox(/** @scrutinizer ignore-unused */ $childDomElement, $element, $style, $parentBlock, string $display)

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...
173
	{
174
		$cleanStyle = (new \YetiForcePDF\Style\Style())
175
			->setDocument($this->document)
176
			->setContent('')
177
			->parseInline();
178
		$rowGroupClass = 'YetiForcePDF\\Layout\\TableRowGroupBox';
179
		switch ($display) {
180
			case 'table-header-group':
181
				$rowGroupClass = 'YetiForcePDF\\Layout\\TableHeaderGroupBox';
182
183
				break;
184
			case 'table-footer-group':
185
				$rowGroupClass = 'YetiForcePDF\\Layout\\TableFooterGroupBox';
186
187
				break;
188
		}
189
		$box = (new $rowGroupClass())
190
			->setDocument($this->document)
191
			->setParent($this)
192
			->setElement($element)
193
			->setStyle($cleanStyle)
194
			->init();
195
		$this->appendChild($box);
196
		$box->getStyle()->init()->setRule('display', 'block');
197
		$box->buildTree($box);
198
199
		return $box;
200
	}
201
202
	/**
203
	 * Append table row group box element.
204
	 *
205
	 * @param \DOMNode                      $childDomElement
206
	 * @param Element                       $element
207
	 * @param Style                         $style
208
	 * @param \YetiForcePDF\Layout\BlockBox $parentBlock
209
	 *
210
	 * @return $this
211
	 */
212
	public function appendTableRowBox($childDomElement, $element, $style, $parentBlock)
0 ignored issues
show
Unused Code introduced by
The parameter $parentBlock 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

212
	public function appendTableRowBox($childDomElement, $element, $style, /** @scrutinizer ignore-unused */ $parentBlock)

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...
Unused Code introduced by
The parameter $childDomElement 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

212
	public function appendTableRowBox(/** @scrutinizer ignore-unused */ $childDomElement, $element, $style, $parentBlock)

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...
213
	{
214
		$box = (new TableRowBox())
215
			->setDocument($this->document)
216
			->setParent($this)
217
			->setElement($element)
218
			->setStyle($style)
219
			->init();
220
		$this->appendChild($box);
221
		$box->getStyle()->init()->setRule('display', 'block');
222
		$box->buildTree($box);
223
224
		return $box;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $box returns the type YetiForcePDF\Layout\TableRowBox which is incompatible with the documented return type YetiForcePDF\Layout\TableBox.
Loading history...
225
	}
226
227
	/**
228
	 * Get all rows from all row groups.
229
	 *
230
	 * @return array of LineBoxes and TableRowBoxes
231
	 */
232
	public function getRows()
233
	{
234
		$rows = [];
235
		foreach ($this->getChildren() as $rowGroup) {
236
			if ($rowGroup instanceof TableRowGroupBox) {
237
				foreach ($rowGroup->getChildren() as $row) {
238
					$rows[] = $row;
239
				}
240
			}
241
		}
242
243
		return $rows;
244
	}
245
246
	/**
247
	 * Get columns - get table cells segregated by columns.
248
	 *
249
	 * @return array
250
	 */
251
	public function getColumns()
252
	{
253
		$columns = [];
254
		foreach ($this->getChildren() as $rowGroup) {
255
			foreach ($rowGroup->getChildren() as $row) {
256
				foreach ($row->getChildren() as $columnIndex => $column) {
257
					if ($column instanceof TableColumnBox) {
258
						$columns[$columnIndex][] = $column;
259
					}
260
				}
261
			}
262
		}
263
264
		return $columns;
265
	}
266
267
	/**
268
	 * Get cells.
269
	 *
270
	 * @return array
271
	 */
272
	public function getCells()
273
	{
274
		$cells = [];
275
		foreach ($this->getChildren() as $rowGroup) {
276
			foreach ($rowGroup->getChildren() as $row) {
277
				foreach ($row->getChildren() as $column) {
278
					$cells[] = $column->getFirstChild();
279
				}
280
			}
281
		}
282
283
		return $cells;
284
	}
285
286
	/**
287
	 * Get minimal and maximal column widths.
288
	 *
289
	 * @param string $availableSpace
290
	 *
291
	 * @return array
292
	 */
293
	public function setUpWidths(string $availableSpace)
0 ignored issues
show
Unused Code introduced by
The parameter $availableSpace 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

293
	public function setUpWidths(/** @scrutinizer ignore-unused */ string $availableSpace)

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...
294
	{
295
		foreach ($this->getChildren() as $rowGroup) {
296
			foreach ($rowGroup->getChildren() as $row) {
297
				if ($columns = $row->getChildren()) {
298
					foreach ($columns as $columnIndex => $column) {
299
						$cell = $column->getFirstChild();
300
						$cellStyle = $cell->getStyle();
301
						$columnInnerWidth = $cell->getDimensions()->getMaxWidth();
302
						$styleWidth = $column->getStyle()->getRules('width');
303
						$this->contentWidths[$columnIndex] = Math::max($this->contentWidths[$columnIndex] ?? '0', $columnInnerWidth);
304
						$minColumnWidth = $cell->getDimensions()->getMinWidth();
305
						if ($column->getColSpan() > 1) {
0 ignored issues
show
Bug introduced by
The method getColSpan() does not exist on YetiForcePDF\Layout\Box. It seems like you code against a sub-type of YetiForcePDF\Layout\Box such as YetiForcePDF\Layout\TableColumnBox. ( Ignorable by Annotation )

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

305
						if ($column->/** @scrutinizer ignore-call */ getColSpan() > 1) {
Loading history...
306
							$minColumnWidth = Math::div($minColumnWidth, (string) $column->getColSpan());
307
						}
308
						$this->minWidths[$columnIndex] = Math::max($this->minWidths[$columnIndex] ?? '0', $minColumnWidth);
309
						if ('auto' !== $styleWidth && false === strpos($styleWidth, '%')) {
0 ignored issues
show
Bug introduced by
It seems like $styleWidth 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

309
						if ('auto' !== $styleWidth && false === strpos(/** @scrutinizer ignore-type */ $styleWidth, '%')) {
Loading history...
310
							if ($column->getColSpan() > 1) {
311
								$styleWidth = Math::div($styleWidth, (string) $column->getColSpan());
0 ignored issues
show
Bug introduced by
It seems like $styleWidth can also be of type array; however, parameter $numbers of YetiForcePDF\Math::div() 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

311
								$styleWidth = Math::div(/** @scrutinizer ignore-type */ $styleWidth, (string) $column->getColSpan());
Loading history...
312
							}
313
							$preferred = Math::max($styleWidth, $minColumnWidth);
0 ignored issues
show
Bug introduced by
It seems like $styleWidth 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

313
							$preferred = Math::max(/** @scrutinizer ignore-type */ $styleWidth, $minColumnWidth);
Loading history...
314
							$this->minWidths[$columnIndex] = $preferred;
315
						} elseif (strpos($styleWidth, '%') > 0) {
316
							$preferred = Math::max($this->preferredWidths[$columnIndex] ?? '0', $columnInnerWidth);
317
							$this->percentages[$columnIndex] = Math::max($this->percentages[$columnIndex] ?? '0', trim($styleWidth, '%'));
0 ignored issues
show
Bug introduced by
It seems like $styleWidth can also be of type array; however, parameter $string of trim() 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

317
							$this->percentages[$columnIndex] = Math::max($this->percentages[$columnIndex] ?? '0', trim(/** @scrutinizer ignore-type */ $styleWidth, '%'));
Loading history...
318
						} else {
319
							$preferred = Math::max($this->preferredWidths[$columnIndex] ?? '0', $columnInnerWidth);
320
						}
321
						$this->preferredWidths[$columnIndex] = $preferred;
322
					}
323
					$this->borderWidth = Math::add($this->borderWidth, $cellStyle->getHorizontalBordersWidth());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cellStyle does not seem to be defined for all execution paths leading up to this point.
Loading history...
324
					$this->minWidth = Math::add($this->minWidth, $this->minWidths[$columnIndex]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $columnIndex does not seem to be defined for all execution paths leading up to this point.
Loading history...
325
					$this->contentWidth = Math::add($this->contentWidth, $this->contentWidths[$columnIndex]);
326
					$this->preferredWidth = Math::add($this->preferredWidth, $this->preferredWidths[$columnIndex]);
327
				}
328
			}
329
		}
330
		if ('collapse' !== $this->getParent()->getStyle()->getRules('border-collapse')) {
331
			$spacing = $this->getStyle()->getRules('border-spacing');
332
			$this->cellSpacingWidth = Math::mul((string) (\count($columns) + 1), $spacing);
0 ignored issues
show
Bug introduced by
It seems like $spacing can also be of type array; however, parameter $numbers of YetiForcePDF\Math::mul() 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

332
			$this->cellSpacingWidth = Math::mul((string) (\count($columns) + 1), /** @scrutinizer ignore-type */ $spacing);
Loading history...
Comprehensibility Best Practice introduced by
The variable $columns does not seem to be defined for all execution paths leading up to this point.
Loading history...
333
		}
334
	}
335
336
	/**
337
	 * Set up sizing types for columns.
338
	 *
339
	 * @return $this
340
	 */
341
	protected function setUpSizingTypes()
342
	{
343
		$columnSizingTypes = [];
344
		// rowGroup -> row -> columns
345
		$columns = $this->getFirstChild()->getFirstChild()->getChildren();
346
		foreach ($columns as $columnIndex => $column) {
347
			$columnStyleWidth = $column->getStyle()->getRules('width');
348
			if (strpos($columnStyleWidth, '%') > 0) {
0 ignored issues
show
Bug introduced by
It seems like $columnStyleWidth 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

348
			if (strpos(/** @scrutinizer ignore-type */ $columnStyleWidth, '%') > 0) {
Loading history...
349
				$columnSizingTypes[$columnIndex] = 'percent';
350
			} elseif ('auto' !== $columnStyleWidth) {
351
				$columnSizingTypes[$columnIndex] = 'pixel';
352
			} else {
353
				$columnSizingTypes[$columnIndex] = 'auto';
354
			}
355
		}
356
		$this->percentColumns = [];
357
		$this->pixelColumns = [];
358
		$this->autoColumns = [];
359
		foreach ($this->getChildren() as $rowGroup) {
360
			foreach ($rowGroup->getChildren() as $row) {
361
				foreach ($row->getChildren() as $columnIndex => $column) {
362
					if (isset($columnSizingTypes[$columnIndex]) && 'percent' === $columnSizingTypes[$columnIndex]) {
363
						$this->percentColumns[$columnIndex][] = $column;
364
					} elseif (isset($columnSizingTypes[$columnIndex]) && 'pixel' === $columnSizingTypes[$columnIndex]) {
365
						$this->pixelColumns[$columnIndex][] = $column;
366
					} else {
367
						$this->autoColumns[$columnIndex][] = $column;
368
					}
369
				}
370
			}
371
		}
372
		unset($columnSizingTypes);
373
374
		return $this;
375
	}
376
377
	/**
378
	 * Set rows width.
379
	 *
380
	 * @param string $width
381
	 *
382
	 * @return $this
383
	 */
384
	protected function setRowsWidth()
385
	{
386
		$width = '0';
387
		if (empty($this->rows)) {
388
			return $this;
389
		}
390
		foreach ($this->rows[0]->getChildren() as $column) {
391
			$width = Math::add($width, $column->getDimensions()->getWidth());
392
		}
393
		foreach ($this->rows as $row) {
394
			$rowStyle = $row->getStyle();
395
			$rowWidth = $width;
396
			if ('separate' === $this->getStyle()->getRules('border-collapse')) {
397
				$rowSpacing = Math::add($rowStyle->getHorizontalPaddingsWidth(), $rowStyle->getHorizontalBordersWidth());
398
				$rowWidth = Math::add($rowWidth, $rowSpacing);
399
			}
400
			$row->getDimensions()->setWidth($rowWidth);
401
		}
402
		$row->getParent()->getDimensions()->setWidth($rowWidth);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $row seems to be defined by a foreach iteration on line 393. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $rowWidth seems to be defined by a foreach iteration on line 393. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
403
		$style = $this->getStyle();
404
		$spacing = Math::add($style->getHorizontalPaddingsWidth(), $style->getHorizontalBordersWidth());
405
		$rowWidth = Math::add($rowWidth, $spacing);
406
		$this->getDimensions()->setWidth($rowWidth);
407
408
		return $this;
409
	}
410
411
	/**
412
	 * Minimal content guess.
413
	 *
414
	 * @param array $rows
415
	 *
416
	 * @return $this
417
	 */
418
	protected function minContentGuess(array $rows)
419
	{
420
		foreach ($rows as $row) {
421
			foreach ($row->getChildren() as $columnIndex => $column) {
422
				$this->setColumnWidth($column, $this->minWidths[$columnIndex]);
423
			}
424
		}
425
		$this->setRowsWidth();
426
427
		return $this;
428
	}
429
430
	/**
431
	 * Add to preferred others (add left space to preferred width of auto/pixel columns).
432
	 *
433
	 * @param string $leftSpace
434
	 *
435
	 * @return string
436
	 */
437
	protected function addToPreferredOthers(string $leftSpace)
438
	{
439
		$autoNeededTotal = '0';
440
		$pixelNeededTotal = '0';
441
		$autoNeeded = [];
442
		$pixelNeeded = [];
443
		foreach ($this->autoColumns as $columnIndex => $columns) {
444
			$colDmns = $columns[0]->getDimensions();
445
			$colWidth = $colDmns->getInnerWidth();
446
			if (Math::comp($this->preferredWidths[$columnIndex], $colWidth) > 0) {
447
				$autoNeeded[$columnIndex] = Math::sub($this->preferredWidths[$columnIndex], $colWidth);
448
				$autoNeededTotal = Math::add($autoNeededTotal, $autoNeeded[$columnIndex]);
449
			}
450
		}
451
		foreach ($this->pixelColumns as $columnIndex => $columns) {
452
			$colDmns = $columns[0]->getDimensions();
453
			$colWidth = $colDmns->getInnerWidth();
454
			if (Math::comp($this->preferredWidths[$columnIndex], $colWidth) > 0) {
455
				$pixelNeeded[$columnIndex] = Math::sub($this->preferredWidths[$columnIndex], $colWidth);
456
				$pixelNeededTotal = Math::add($pixelNeededTotal, $pixelNeeded[$columnIndex]);
457
			}
458
		}
459
		// ok, we know where we need to add extra space
460
		$totalNeeded = Math::add($autoNeededTotal, $pixelNeededTotal);
461
		$totalToAdd = Math::min($leftSpace, $totalNeeded);
462
		// we know how much we can distribute
463
		Math::setAccurate(true);
464
		$autoTotalRatio = Math::div($autoNeededTotal, $totalNeeded);
465
		$addToAutoTotal = Math::mul($autoTotalRatio, $totalToAdd);
466
		Math::setAccurate(false);
467
		$addToPixelTotal = Math::sub($totalToAdd, $addToAutoTotal);
468
		// we know how much space we can add to each column type (auto and pixel)
469
		// now we must distribute this space according to concrete column needs
470
		foreach ($this->autoColumns as $columnIndex => $columns) {
471
			if (isset($autoNeeded[$columnIndex])) {
472
				Math::setAccurate(true);
473
				$neededRatio = Math::div($autoNeeded[$columnIndex], $autoNeededTotal);
474
				$add = Math::mul($neededRatio, $addToAutoTotal);
475
				Math::setAccurate(false);
476
				$columnWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
477
				foreach ($columns as $column) {
478
					$colDmns = $column->getDimensions();
479
					$colDmns->setWidth($columnWidth);
480
					$column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
481
				}
482
			}
483
		}
484
		foreach ($this->pixelColumns as $columnIndex => $columns) {
485
			if (isset($pixelNeeded[$columnIndex])) {
486
				Math::setAccurate(true);
487
				$neededRatio = Math::div($pixelNeeded[$columnIndex], $pixelNeededTotal);
488
				$add = Math::mul($neededRatio, $addToPixelTotal);
489
				Math::setAccurate(false);
490
				$columnWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
491
				foreach ($columns as $column) {
492
					$colDmns = $column->getDimensions();
493
					$colDmns->setWidth($columnWidth);
494
					$column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
495
				}
496
			}
497
		}
498
499
		return Math::sub($leftSpace, $totalToAdd);
500
	}
501
502
	/**
503
	 * Get current others width (auto, pixel columns).
504
	 *
505
	 * @return string
506
	 */
507
	protected function getCurrentOthersWidth()
508
	{
509
		$currentOthersWidth = '0';
510
		foreach ($this->autoColumns as $columns) {
511
			$currentOthersWidth = Math::add($currentOthersWidth, $columns[0]->getDimensions()->getInnerWidth());
512
		}
513
		foreach ($this->pixelColumns as $columns) {
514
			$currentOthersWidth = Math::add($currentOthersWidth, $columns[0]->getDimensions()->getInnerWidth());
515
		}
516
517
		return $currentOthersWidth;
518
	}
519
520
	/**
521
	 * Get total percentage.
522
	 *
523
	 * @return string
524
	 */
525
	protected function getTotalPercentage()
526
	{
527
		$totalPercentageSpecified = '0';
528
		foreach ($this->percentColumns as $columnIndex => $columns) {
529
			$totalPercentageSpecified = Math::add($totalPercentageSpecified, $this->percentages[$columnIndex]);
530
		}
531
532
		return $totalPercentageSpecified;
533
	}
534
535
	/**
536
	 * Get total percentages width.
537
	 *
538
	 * @return string
539
	 */
540
	protected function getTotalPercentageWidth()
541
	{
542
		$totalPercentageColumnsWidth = '0';
543
		foreach ($this->percentColumns as $columns) {
544
			$totalPercentageColumnsWidth = Math::add($totalPercentageColumnsWidth, $columns[0]->getDimensions()->getInnerWidth());
545
		}
546
547
		return $totalPercentageColumnsWidth;
548
	}
549
550
	/**
551
	 * Expand percents to min width.
552
	 *
553
	 * @param string $availableSpace
554
	 *
555
	 * @return $this
556
	 */
557
	protected function expandPercentsToMin(string $availableSpace)
558
	{
559
		$totalPercentageSpecified = $this->getTotalPercentage();
560
		$maxPercentRatio = '0';
561
		$maxPercentRatioIndex = 0;
562
		$ratioPercent = '0';
563
		foreach ($this->percentages as $columnIndex => $percent) {
564
			Math::setAccurate(true);
565
			$ratio = Math::div($this->minWidths[$columnIndex], $percent);
566
			Math::setAccurate(false);
567
			if (Math::comp($ratio, $maxPercentRatio) > 0) {
568
				$maxPercentRatio = $ratio;
569
				$maxPercentRatioIndex = $columnIndex;
570
				$ratioPercent = $percent;
571
			}
572
		}
573
		$minWidth = $this->minWidths[$maxPercentRatioIndex];
574
		// lowerPercent = minWidth
575
		$onePercent = Math::div($minWidth, $ratioPercent);
576
		// we have one percent width, we must apply this to all percentages and other columns
577
		$currentPercentsWidth = '0';
578
		foreach ($this->percentColumns as $columnIndex => $columns) {
579
			$columnWidth = Math::mul($this->percentages[$columnIndex], $onePercent);
580
			foreach ($columns as $column) {
581
				$columnStyle = $column->getStyle();
582
				$column->getDimensions()->setWidth(Math::add($columnWidth, $columnStyle->getHorizontalPaddingsWidth()));
583
				$column->getFirstChild()->getDimensions()->setWidth($columnWidth);
584
			}
585
			$this->minWidths[$columnIndex] = $columnWidth;
586
			$currentPercentsWidth = Math::add($currentPercentsWidth, $column->getDimensions()->getInnerWidth());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.
Loading history...
587
		}
588
		// percentage columns are satisfied, other columns must fulfill percentages
589
		$otherPercent = Math::sub('100', $totalPercentageSpecified);
590
		$othersShouldHaveWidth = Math::mul($otherPercent, $onePercent);
591
		if (Math::comp(Math::add($othersShouldHaveWidth, $currentPercentsWidth), $availableSpace) > 0) {
592
			$othersShouldHaveWidth = Math::sub($availableSpace, $currentPercentsWidth);
593
		}
594
		$currentOthersWidth = $this->getCurrentOthersWidth();
595
		$leftSpace = Math::sub($othersShouldHaveWidth, $currentOthersWidth);
596
		$this->addToOthers($leftSpace);
597
598
		return $this;
599
	}
600
601
	/**
602
	 * Apply percentage dimensions.
603
	 *
604
	 * @param string $availableSpace
605
	 *
606
	 * @return $this
607
	 */
608
	protected function applyPercentage(string $availableSpace)
609
	{
610
		$currentRowsWidth = '0';
611
		if ('auto' === $this->getParent()->getStyle()->getRules('width')) {
612
			foreach ($this->getRows()[0]->getChildren() as $columnIndex => $column) {
613
				$currentRowsWidth = Math::add($currentRowsWidth, $column->getDimensions()->getInnerWidth());
614
			}
615
		} else {
616
			$currentRowsWidth = $this->getParent()->getDimensions()->getInnerWidth();
617
			if ('separate' === $this->getStyle()->getRules('border-collapse')) {
618
				$rowStyle = $this->getRows()[0]->getStyle();
619
				$spacing = Math::add($rowStyle->getHorizontalPaddingsWidth(), $rowStyle->getHorizontalBordersWidth());
620
				$currentRowsWidth = Math::sub($currentRowsWidth, $spacing);
621
			}
622
		}
623
		$mustExpand = false;
624
		foreach ($this->percentColumns as $columnIndex => $columns) {
625
			$columnWidth = Math::percent($this->percentages[$columnIndex], $currentRowsWidth);
626
			if (Math::comp($this->minWidths[$columnIndex], $columnWidth) > 0) {
627
				// we need to expand proportionally
628
				$mustExpand = true;
629
630
				break;
631
			}
632
		}
633
		if ($mustExpand) {
634
			$this->expandPercentsToMin($availableSpace);
635
		} else {
636
			// everything is ok we can resize percentages
637
			$percentsWidth = '0';
638
			foreach ($this->percentColumns as $columnIndex => $columns) {
639
				$columnWidth = Math::percent($this->percentages[$columnIndex], $currentRowsWidth);
640
				$percentsWidth = Math::add($percentsWidth, $columnWidth);
641
				$padding = $columns[0]->getStyle()->getHorizontalPaddingsWidth();
642
				$columnWidth = Math::sub($columnWidth, $padding);
643
				foreach ($columns as $column) {
644
					$this->setColumnWidth($column, $columnWidth);
645
				}
646
			}
647
			$totalPercentage = $this->getTotalPercentage();
648
			if (0 !== Math::comp($totalPercentage, '100') && 0 === Math::comp($this->getCurrentOthersWidth(), '0')) {
0 ignored issues
show
introduced by
The condition 0 === YetiForcePDF\Math:...rentOthersWidth(), '0') is always false.
Loading history...
649
				// we have some space available
650
				$leftSpace = Math::sub($availableSpace, $percentsWidth);
651
				$add = Math::div($leftSpace, (string) \count($this->percentColumns));
652
				foreach ($this->percentColumns as $columnIndex => $columns) {
653
					foreach ($columns as $column) {
654
						$columnWidth = Math::add($column->getDimensions()->getWidth(), $add);
655
						$column->getDimensions()->setWidth($columnWidth);
656
						$column->getFirstChild()->getDimensions()->setWidth($column->getDimensions()->getInnerWidth());
657
					}
658
				}
659
			}
660
		}
661
662
		return $this;
663
	}
664
665
	/**
666
	 * Save current columns width state.
667
	 *
668
	 * @param array $rows
669
	 *
670
	 * @return $this
671
	 */
672
	protected function saveState(array $rows)
673
	{
674
		$this->beforeWidths = [];
675
		foreach ($rows[0]->getChildren() as $columnIndex => $column) {
676
			$this->beforeWidths[$columnIndex] = $column->getDimensions()->getWidth();
677
		}
678
679
		return $this;
680
	}
681
682
	/**
683
	 * Minimal content percentage guess.
684
	 *
685
	 * @param array  $rows
686
	 * @param string $availableSpace
687
	 *
688
	 * @return $this
689
	 */
690
	protected function minContentPercentageGuess(array $rows, string $availableSpace)
691
	{
692
		$this->saveState($rows);
693
		$this->applyPercentage($availableSpace);
694
		$this->setRowsWidth();
695
696
		return $this;
697
	}
698
699
	/**
700
	 * Minimal content specified guess.
701
	 *
702
	 * @param array  $rows
703
	 * @param string $availableSpace
704
	 *
705
	 * @return $this
706
	 */
707
	protected function minContentSpecifiedGuess(array $rows, string $availableSpace)
708
	{
709
		$this->saveState($rows);
710
		$leftWidth = '0';
711
		foreach ($this->pixelColumns as $columnIndex => $columns) {
712
			foreach ($columns as $column) {
713
				$this->setColumnWidth($column, $this->preferredWidths[$columnIndex]);
714
			}
715
			$leftWidth = Math::add($leftWidth, $column->getDimensions()->getWidth());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.
Loading history...
716
		}
717
		foreach ($this->autoColumns as $columnIndex => $columns) {
718
			$leftWidth = Math::add($leftWidth, $columns[0]->getDimensions()->getWidth());
719
		}
720
721
		$this->applyPercentage($availableSpace);
722
		$this->setRowsWidth();
723
724
		return $this;
725
	}
726
727
	/**
728
	 * Set column width.
729
	 *
730
	 * @param $column
731
	 * @param string $width
732
	 */
733
	protected function setColumnWidth($column, string $width)
734
	{
735
		$columnStyle = $column->getStyle();
736
		$cell = $column->getFirstChild();
737
		$width = Math::add($width, $columnStyle->getHorizontalPaddingsWidth());
738
		$column->getDimensions()->setWidth($width);
739
		$cell->getDimensions()->setWidth($column->getDimensions()->getInnerWidth());
740
	}
741
742
	/**
743
	 * Maximal content guess.
744
	 *
745
	 * @param array  $rows
746
	 * @param string $availableSpace
747
	 *
748
	 * @return $this
749
	 */
750
	protected function maxContentGuess(array $rows, string $availableSpace)
751
	{
752
		$this->saveState($rows);
753
		foreach ($this->autoColumns as $columnIndex => $columns) {
754
			foreach ($columns as $column) {
755
				$this->setColumnWidth($column, $this->contentWidths[$columnIndex]);
756
			}
757
		}
758
		$this->applyPercentage($availableSpace);
759
		$this->setRowsWidth();
760
761
		return $this;
762
	}
763
764
	/**
765
	 * Span rows.
766
	 *
767
	 * @return $this
768
	 */
769
	public function spanRows()
770
	{
771
		$toRemove = [];
772
		foreach ($this->getChildren() as $rowGroup) {
773
			foreach ($rowGroup->getChildren() as $rowIndex => $row) {
774
				foreach ($row->getChildren() as $columnIndex => $column) {
775
					if ($column->getRowSpan() > 1) {
0 ignored issues
show
Bug introduced by
The method getRowSpan() does not exist on YetiForcePDF\Layout\Box. It seems like you code against a sub-type of YetiForcePDF\Layout\Box such as YetiForcePDF\Layout\TableRowBox or YetiForcePDF\Layout\TableColumnBox. ( Ignorable by Annotation )

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

775
					if ($column->/** @scrutinizer ignore-call */ getRowSpan() > 1) {
Loading history...
776
						$rowSpans = $column->getRowSpan();
777
						$spanHeight = '0';
778
						for ($i = 1; $i < $rowSpans; ++$i) {
779
							$spanColumn = $row->getParent()->getChildren()[$rowIndex + $i]->getChildren()[$columnIndex];
780
							$spanHeight = Math::add($spanHeight, $spanColumn->getDimensions()->getHeight());
0 ignored issues
show
Bug introduced by
It seems like $spanColumn->getDimensions()->getHeight() can also be of type null; 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

780
							$spanHeight = Math::add($spanHeight, /** @scrutinizer ignore-type */ $spanColumn->getDimensions()->getHeight());
Loading history...
781
							$toRemove[] = $spanColumn;
782
						}
783
						$colDmns = $column->getDimensions();
784
						$colDmns->setHeight(Math::add($colDmns->getHeight(), $spanHeight));
785
						$cell = $column->getFirstChild();
786
						$colInnerHeight = $colDmns->getInnerHeight();
787
						$cell->getDimensions()->setHeight($colInnerHeight);
788
						$cellHeight = '0';
789
						foreach ($cell->getChildren() as $cellChild) {
790
							$cellHeight = Math::add($cellHeight, $cellChild->getDimensions()->getOuterHeight());
791
						}
792
						$columnStyle = $column->getStyle();
793
						$cellStyle = $column->getFirstChild()->getStyle();
794
						if ('collapse' === $columnStyle->getRules('border-collapse') && $rowIndex + $i === \count($this->getChildren())) {
795
							// TODO: store original border widths inside cell
796
							$cellStyle->setRule('border-bottom-width', $cellStyle->getRules('border-top-width'));
0 ignored issues
show
Bug introduced by
It seems like $cellStyle->getRules('border-top-width') 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

796
							$cellStyle->setRule('border-bottom-width', /** @scrutinizer ignore-type */ $cellStyle->getRules('border-top-width'));
Loading history...
797
						}
798
						$toDisposition = Math::sub($colInnerHeight, $cellHeight);
799
						switch ($cellStyle->getRules('vertical-align')) {
800
							case 'baseline':
801
							case 'middle':
802
								$padding = Math::div($toDisposition, '2');
803
								$cellStyle->setRule('padding-top', $padding);
804
								$cellStyle->setRule('padding-bottom', $padding);
805
								break;
806
							case 'top':
807
								$cellStyle->setRule('padding-bottom', $toDisposition);
808
								break;
809
							case 'bottom':
810
								$cellStyle->setRule('padding-top', $toDisposition);
811
								break;
812
						}
813
						$cell->measureWidth();
0 ignored issues
show
Bug introduced by
The method measureWidth() does not exist on YetiForcePDF\Layout\Box. It seems like you code against a sub-type of said class. However, the method does not exist in YetiForcePDF\Layout\ElementBox. Are you sure you never get one of those? ( Ignorable by Annotation )

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

813
						$cell->/** @scrutinizer ignore-call */ 
814
             measureWidth();
Loading history...
814
						$cell->measureHeight();
0 ignored issues
show
Bug introduced by
The method measureHeight() does not exist on YetiForcePDF\Layout\Box. It seems like you code against a sub-type of said class. However, the method does not exist in YetiForcePDF\Layout\ElementBox. Are you sure you never get one of those? ( Ignorable by Annotation )

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

814
						$cell->/** @scrutinizer ignore-call */ 
815
             measureHeight();
Loading history...
815
						$cell->measureOffset();
0 ignored issues
show
Bug introduced by
The method measureOffset() does not exist on YetiForcePDF\Layout\Box. It seems like you code against a sub-type of said class. However, the method does not exist in YetiForcePDF\Layout\ElementBox. Are you sure you never get one of those? ( Ignorable by Annotation )

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

815
						$cell->/** @scrutinizer ignore-call */ 
816
             measureOffset();
Loading history...
816
						$cell->alignText();
817
						$cell->measurePosition();
0 ignored issues
show
Bug introduced by
The method measurePosition() does not exist on YetiForcePDF\Layout\Box. It seems like you code against a sub-type of said class. However, the method does not exist in YetiForcePDF\Layout\ElementBox. Are you sure you never get one of those? ( Ignorable by Annotation )

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

817
						$cell->/** @scrutinizer ignore-call */ 
818
             measurePosition();
Loading history...
818
					}
819
				}
820
			}
821
		}
822
		foreach ($toRemove as $remove) {
823
			$remove->setDisplayable(false)->setRenderable(false)->setForMeasurement(false);
824
		}
825
826
		return $this;
827
	}
828
829
	/**
830
	 * Finish table width calculations.
831
	 *
832
	 * @return $this
833
	 */
834
	protected function finish()
835
	{
836
		foreach ($this->rows as $row) {
837
			$row->spanColumns();
838
		}
839
		$style = $this->getStyle();
840
		$width = $this->rows[0]->getDimensions()->getWidth();
841
		$width = Math::add($width, $style->getHorizontalPaddingsWidth(), $style->getHorizontalBordersWidth());
842
		$this->getDimensions()->setWidth($width);
843
		$parent = $this->getParent();
844
		$parentStyle = $parent->getStyle();
845
		if ('auto' === $parentStyle->getRules('width')) {
846
			$parentSpacing = Math::add($parentStyle->getHorizontalBordersWidth(), $parentStyle->getHorizontalPaddingsWidth());
847
			$width = Math::add($width, $parentSpacing);
848
			$parent->getDimensions()->setWidth($width);
849
		} else {
850
			$parent->applyStyleWidth();
851
		}
852
		foreach ($this->getCells() as $cell) {
853
			$cell->measureWidth();
854
		}
855
856
		return $this;
857
	}
858
859
	/**
860
	 * Check whenever table fill fit to available space.
861
	 *
862
	 * @param string $availableSpace
863
	 *
864
	 * @return bool
865
	 */
866
	protected function willFit(string $availableSpace)
867
	{
868
		$row = $this->rows[0];
869
		$width = $row->getDimensions()->getWidth();
870
		$width = Math::add($width, $this->getStyle()->getHorizontalBordersWidth(), $this->getStyle()->getHorizontalPaddingsWidth());
871
872
		return Math::comp($availableSpace, $width) >= 0;
873
	}
874
875
	/**
876
	 * Get row inner width.
877
	 *
878
	 * @return string
879
	 */
880
	protected function getRowInnerWidth()
881
	{
882
		$width = '0';
883
		foreach ($this->rows[0]->getChildren() as $column) {
884
			$width = Math::add($width, $column->getDimensions()->getWidth());
885
		}
886
887
		return $width;
888
	}
889
890
	/**
891
	 * Get auto columns max width.
892
	 *
893
	 * @return string
894
	 */
895
	protected function getAutoColumnsMaxWidth()
896
	{
897
		$autoColumnsMaxWidth = '0';
898
		foreach ($this->autoColumns as $columnIndex => $columns) {
899
			$autoColumnsMaxWidth = Math::add($autoColumnsMaxWidth, $this->contentWidths[$columnIndex]);
900
		}
901
902
		return $autoColumnsMaxWidth;
903
	}
904
905
	/**
906
	 * Get auto columns min width.
907
	 *
908
	 * @return string
909
	 */
910
	protected function getAutoColumnsMinWidth()
911
	{
912
		$autoColumnsMinWidth = '0';
913
		foreach ($this->autoColumns as $columnIndex => $columns) {
914
			$autoColumnsMinWidth = Math::add($autoColumnsMinWidth, $this->minWidths[$columnIndex]);
915
		}
916
917
		return $autoColumnsMinWidth;
918
	}
919
920
	/**
921
	 * Get auto columns width.
922
	 *
923
	 * @return string
924
	 */
925
	protected function getAutoColumnsWidth()
926
	{
927
		$autoColumnsWidth = '0';
928
		foreach ($this->autoColumns as $columns) {
929
			$autoColumnsWidth = Math::add($autoColumnsWidth, $columns[0]->getDimensions()->getInnerWidth());
930
		}
931
932
		return $autoColumnsWidth;
933
	}
934
935
	/**
936
	 * Shrink to fit.
937
	 *
938
	 * @param string $availableSpace
939
	 * @param int    $step
940
	 *
941
	 * @return TableBox
942
	 */
943
	protected function shrinkToFit(string $availableSpace, int $step)
944
	{
945
		$parentStyle = $this->getParent()->getStyle();
946
		$parentSpacing = Math::add($parentStyle->getHorizontalBordersWidth(), $parentStyle->getHorizontalPaddingsWidth());
947
		$availableSpace = Math::sub($availableSpace, $this->cellSpacingWidth, $parentSpacing);
948
		$currentWidth = Math::sub($this->getRowInnerWidth(), $this->cellSpacingWidth);
949
		$toRemoveTotal = Math::sub($currentWidth, $availableSpace);
950
		$totalPercentages = '0';
951
		foreach ($this->percentages as $percentage) {
952
			$totalPercentages = Math::add($totalPercentages, $percentage);
953
		}
954
		$percentagesFullWidth = Math::percent($totalPercentages, $availableSpace);
955
		$eachPercentagesWidth = [];
956
		foreach ($this->percentages as $columnIndex => $percent) {
957
			$eachPercentagesWidth[$columnIndex] = Math::percent($percent, $availableSpace);
958
		}
959
		$nonPercentageSpace = Math::sub($availableSpace, $percentagesFullWidth);
960
		$autoColumnsMinWidth = $this->getAutoColumnsMinWidth();
961
		$autoColumnsMaxWidth = $this->getAutoColumnsMaxWidth();
962
		$totalPixelWidth = '0';
963
		foreach ($this->pixelColumns as $columnIndex => $columns) {
964
			$totalPixelWidth = Math::add($totalPixelWidth, $this->preferredWidths[$columnIndex]);
965
		}
966
		switch ($step) {
967
			case 0:
968
				// minimal stays minimal - decreasing percents
969
				$rowWidth = '0';
970
				foreach ($this->percentColumns as $columnIndex => $columns) {
971
					$totalPercent = Math::div($this->percentages[$columnIndex], $totalPercentages);
972
					$toRemove = Math::percent($totalPercent, $toRemoveTotal);
973
					foreach ($columns as $column) {
974
						$cDimensions = $column->getDimensions();
975
						$cDimensions->setWidth(Math::sub($cDimensions->getWidth(), $toRemove));
976
						$column->getFirstChild()->getDimensions()->setWidth($cDimensions->getInnerWidth());
977
					}
978
					$rowWidth = Math::add($rowWidth, $cDimensions->getWidth());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cDimensions does not seem to be defined for all execution paths leading up to this point.
Loading history...
979
				}
980
				$this->setRowsWidth();
981
982
				break;
983
			case 1:
984
				// minimal stays minimal, decreasing pixels
985
				$toPixelDisposition = Math::sub($nonPercentageSpace, $autoColumnsMinWidth);
986
				foreach ($this->pixelColumns as $columnIndex => $columns) {
987
					Math::setAccurate(true);
988
					$ratio = Math::div($this->preferredWidths[$columnIndex], $totalPixelWidth);
989
					$columnWidth = Math::mul($toPixelDisposition, $ratio);
990
					Math::setAccurate(false);
991
					foreach ($columns as $column) {
992
						$columnDimensions = $column->getDimensions();
993
						$columnDimensions->setWidth(Math::add($columnWidth, $column->getStyle()->getHorizontalPaddingsWidth()));
994
						$column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
995
					}
996
				}
997
				foreach ($this->percentColumns as $columnIndex => $columns) {
998
					foreach ($columns as $column) {
999
						$columnDimensions = $column->getDimensions();
1000
						$columnDimensions->setWidth(Math::add($eachPercentagesWidth[$columnIndex], $column->getStyle()->getHorizontalPaddingsWidth()));
1001
						$column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
1002
					}
1003
				}
1004
				$this->setRowsWidth();
1005
1006
				break;
1007
			case 2:
1008
				// minimal stays minimal, pixels stays untouched, auto columns decreasing
1009
				$toAutoDisposition = Math::sub($nonPercentageSpace, $totalPixelWidth);
1010
				$nonMinWidthColumns = [];
1011
				foreach ($this->autoColumns as $columnIndex => $columns) {
1012
					Math::setAccurate(true);
1013
					$ratio = Math::div($this->contentWidths[$columnIndex], $autoColumnsMaxWidth);
1014
					$columnWidth = Math::mul($toAutoDisposition, $ratio);
1015
					Math::setAccurate(false);
1016
					if (Math::comp($this->minWidths[$columnIndex], $columnWidth) > 0) {
1017
						$toAutoDisposition = Math::sub($toAutoDisposition, Math::sub($this->minWidths[$columnIndex], $columnWidth));
1018
						$columnWidth = $this->minWidths[$columnIndex];
1019
						foreach ($columns as $column) {
1020
							$columnDimensions = $column->getDimensions();
1021
							$columnDimensions->setWidth(Math::add($columnWidth, $column->getStyle()->getHorizontalPaddingsWidth()));
1022
							$column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
1023
						}
1024
					} else {
1025
						$nonMinWidthColumns[$columnIndex] = $columns;
1026
					}
1027
				}
1028
				foreach ($nonMinWidthColumns as $columnIndex => $columns) {
1029
					Math::setAccurate(true);
1030
					$ratio = Math::div($this->contentWidths[$columnIndex], $autoColumnsMaxWidth);
1031
					$columnWidth = Math::mul($toAutoDisposition, $ratio);
1032
					Math::setAccurate(false);
1033
					foreach ($columns as $column) {
1034
						$columnDimensions = $column->getDimensions();
1035
						$columnDimensions->setWidth(Math::add($columnWidth, $column->getStyle()->getHorizontalPaddingsWidth()));
1036
						$column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
1037
					}
1038
				}
1039
				foreach ($this->percentColumns as $columnIndex => $columns) {
1040
					foreach ($columns as $column) {
1041
						$columnDimensions = $column->getDimensions();
1042
						$columnDimensions->setWidth(Math::add($eachPercentagesWidth[$columnIndex], $column->getStyle()->getHorizontalPaddingsWidth()));
1043
						$column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
1044
					}
1045
				}
1046
				$this->setRowsWidth();
1047
1048
				break;
1049
		}
1050
1051
		return $this->finish();
1052
	}
1053
1054
	/**
1055
	 * Add to others (left space to auto/pixel columns).
1056
	 *
1057
	 * @param string $leftSpace
1058
	 * @param bool   $withPreferred
1059
	 *
1060
	 * @return $this
1061
	 */
1062
	protected function addToOthers(string $leftSpace, bool $withPreferred = false)
1063
	{
1064
		// first of all try to redistribute space to columns that need it most (width is under preferred)
1065
		// left space is the space that we can add to other column types that needs extra space to preferred width
1066
		if ($withPreferred) {
1067
			$leftSpace = $this->addToPreferredOthers($leftSpace);
1068
		}
1069
1070
		// ok, we've redistribute space to columns that needs it but if there is space left we must redistribute it
1071
		// to fulfill percentages
1072
		if (0 === Math::comp($leftSpace, '0')) {
0 ignored issues
show
introduced by
The condition 0 === YetiForcePDF\Math::comp($leftSpace, '0') is always false.
Loading history...
1073
			return $this;
1074
		}
1075
		// first redistribute it to auto columns because they are most flexible ones
1076
		if (!empty($this->autoColumns)) {
1077
			$autoColumnsMaxWidth = $this->getAutoColumnsMaxWidth();
1078
			foreach ($this->autoColumns as $columnIndex => $columns) {
1079
				Math::setAccurate(true);
1080
				$ratio = Math::div($this->contentWidths[$columnIndex], $autoColumnsMaxWidth);
1081
				$add = Math::mul($leftSpace, $ratio);
1082
				Math::setAccurate(false);
1083
				$colWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
1084
				foreach ($columns as $column) {
1085
					$colDmns = $column->getDimensions();
1086
					$colDmns->setWidth($colWidth);
1087
					$column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
1088
				}
1089
				if (!$withPreferred) {
1090
					// if not to preferred it means that we adding to min widths
1091
					$this->minWidths[$columnIndex] = $colWidth;
1092
				}
1093
			}
1094
		} elseif ($count = \count($this->pixelColumns)) {
1095
			// next redistribute left space to pixel columns if there where no auto columns
1096
			$add = Math::div($leftSpace, (string) $count);
1097
			foreach ($this->pixelColumns as $columnIndex => $columns) {
1098
				$colWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
1099
				foreach ($columns as $column) {
1100
					$colDmns = $column->getDimensions();
1101
					$colDmns->setWidth($colWidth);
1102
					$column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
1103
				}
1104
				if (!$withPreferred) {
1105
					// if not to preferred it means that we adding to min widths
1106
					$this->minWidths[$columnIndex] = $colWidth;
1107
				}
1108
			}
1109
		}
1110
1111
		return $this;
1112
	}
1113
1114
	/**
1115
	 * Try preferred width.
1116
	 *
1117
	 * @param string $leftSpace
1118
	 * @param bool   $outerWidthSet
1119
	 *
1120
	 * @return $this|TableBox
1121
	 */
1122
	protected function tryPreferred(string $leftSpace, bool $outerWidthSet)
1123
	{
1124
		// left space is 100% width that we can use
1125
		$totalPercentages = '0';
1126
		$totalPercentagesWidth = '0';
1127
		foreach ($this->percentages as $columnIndex => $percentage) {
1128
			$totalPercentages = Math::add($totalPercentages, $percentage);
1129
			$colWidth = $this->rows[0]->getChildren()[$columnIndex]->getDimensions()->getInnerWidth();
1130
			$totalPercentagesWidth = Math::add($totalPercentagesWidth, $colWidth);
1131
		}
1132
		$forPercentages = Math::percent($totalPercentages, $leftSpace);
1133
		$neededTotal = '0';
1134
		$needed = [];
1135
		foreach ($this->percentColumns as $columnIndex => $columns) {
1136
			$colDmns = $columns[0]->getDimensions();
1137
			$colWidth = $colDmns->getInnerWidth();
1138
			if (Math::comp($colWidth, $this->contentWidths[$columnIndex]) < 0) {
1139
				$needed[$columnIndex] = Math::sub($this->contentWidths[$columnIndex], $colWidth);
1140
				$neededTotal = Math::add($neededTotal, $needed[$columnIndex]);
1141
			}
1142
		}
1143
		if (0 === Math::comp($neededTotal, '0') && !$outerWidthSet) {
0 ignored issues
show
introduced by
The condition 0 === YetiForcePDF\Math::comp($neededTotal, '0') is always false.
Loading history...
1144
			return $this->setRowsWidth();
1145
		}
1146
		$currentPercentsWidth = '0';
1147
		$addToPercents = Math::min($neededTotal, $forPercentages);
1148
		foreach ($this->percentColumns as $columnIndex => $columns) {
1149
			if (Math::comp($addToPercents, $neededTotal) < 0) {
1150
				Math::setAccurate(true);
1151
				$ratio = Math::div($this->percentages[$columnIndex], $totalPercentages);
1152
				$add = Math::mul($ratio, $addToPercents);
1153
				Math::setAccurate(false);
1154
			} else {
1155
				if (isset($needed[$columnIndex])) {
1156
					$add = $needed[$columnIndex];
1157
				} else {
1158
					$add = '0';
1159
				}
1160
			}
1161
			foreach ($columns as $column) {
1162
				$colDmns = $column->getDimensions();
1163
				$colDmns->setWidth(Math::add($colDmns->getWidth(), $add));
1164
				$column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
1165
			}
1166
			$currentPercentsWidth = Math::add($currentPercentsWidth, $colDmns->getInnerWidth());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $colDmns does not seem to be defined for all execution paths leading up to this point.
Loading history...
1167
		}
1168
		// we've added space to percentage columns, now we must calculate how much space we need to add (to have 100%)
1169
		$leftSpace2 = Math::sub($leftSpace, $addToPercents);
1170
		if (0 === Math::comp($leftSpace2, '0')) {
0 ignored issues
show
introduced by
The condition 0 === YetiForcePDF\Math::comp($leftSpace2, '0') is always false.
Loading history...
1171
			return $this->setRowsWidth();
1172
		}
1173
		// left space MUST be redistributed to fulfill new percentages
1174
		$this->addToOthers($leftSpace2, true);
1175
		// percent columns were redistributed in the first step so we don't need to do anything
1176
		$this->setRowsWidth();
1177
1178
		return $this;
1179
	}
1180
1181
	/**
1182
	 * Get table min width.
1183
	 *
1184
	 * @return string
1185
	 */
1186
	public function getMinWidth()
1187
	{
1188
		foreach ($this->getCells() as $cell) {
1189
			$cell->measureWidth();
1190
		}
1191
		$this->rows = $this->getRows();
1192
		$this->setUpSizingTypes();
1193
		$this->setUpWidths('0');
1194
		$this->minContentGuess($this->rows)->finish();
1195
1196
		return $this->getDimensions()->getWidth();
1197
	}
1198
1199
	/**
1200
	 * {@inheritdoc}
1201
	 */
1202
	public function measureWidth(bool $afterPageDividing = false)
1203
	{
1204
		if ($this->parentWidth === $this->getParent()->getParent()->getDimensions()->getWidth()) {
1205
			return $this;
1206
		}
1207
		$this->parentWidth = $this->getParent()->getParent()->getDimensions()->getWidth();
1208
		foreach ($this->getCells() as $cell) {
1209
			$cell->measureWidth($afterPageDividing);
1210
		}
1211
		$step = 0;
1212
		$this->setUpSizingTypes();
1213
		$availableSpace = $this->getParent()->getDimensions()->computeAvailableSpace();
1214
		$outerWidthSet = false;
1215
		if ('auto' !== $this->getParent()->getStyle()->getRules('width')) {
1216
			$this->getParent()->applyStyleWidth();
1217
			$availableSpace = Math::min($availableSpace, $this->getParent()->getDimensions()->getInnerWidth());
1218
			$outerWidthSet = true;
1219
		}
1220
		$rows = $this->rows = $this->getRows();
1221
		$this->setUpWidths($availableSpace);
1222
		$this->minContentGuess($rows);
1223
		$this->minContentPercentageGuess($rows, $availableSpace);
1224
		if (!$this->willFit($availableSpace)) {
1225
			return $this->shrinkToFit($availableSpace, $step);
1226
		}
1227
		$step = 1;
1228
		$this->minContentSpecifiedGuess($rows, $availableSpace);
1229
		if (!$this->willFit($availableSpace)) {
1230
			return $this->shrinkToFit($availableSpace, $step);
1231
		}
1232
		$step = 2;
1233
		$this->maxContentGuess($rows, $availableSpace);
1234
		if (!$this->willFit($availableSpace)) {
1235
			return $this->shrinkToFit($availableSpace, $step);
1236
		}
1237
		$currentWidth = $this->getDimensions()->getWidth();
1238
		$leftSpace = Math::sub($availableSpace, $currentWidth);
1239
		if (Math::comp($leftSpace, '0') > 0) {
1240
			$this->tryPreferred($leftSpace, $outerWidthSet);
1241
		}
1242
1243
		return $this->finish();
1244
	}
1245
1246
	/**
1247
	 * {@inheritdoc}
1248
	 */
1249
	public function measureHeight(bool $afterPageDividing = false)
1250
	{
1251
		if ($this->wasCut()) {
1252
			return $this;
1253
		}
1254
		foreach ($this->getCells() as $cell) {
1255
			$cell->measureHeight($afterPageDividing);
1256
		}
1257
		$style = $this->getStyle();
1258
		$maxRowHeights = [];
1259
		foreach ($this->getChildren() as $rowGroupIndex => $rowGroup) {
1260
			$rows = $rowGroup->getChildren();
1261
			$spannedRowsCount = []; // spannedRowsCount is array of number of row spans for each row
1262
			foreach ($rows as $rowIndex => $row) {
1263
				foreach ($row->getChildren() as $column) {
1264
					$spannedRowsCount[$rowIndex] = max($spannedRowsCount[$rowIndex] ?? '0', $column->getRowSpan());
1265
				}
1266
			}
1267
			$rowsCount = []; // rowsCount is array with number of rows they must share for each row
1268
			foreach ($spannedRowsCount as $currentRowSpanIndex => $currentRowsCount) {
1269
				for ($i = 0; $i < $currentRowsCount; ++$i) {
1270
					$rowsCount[$currentRowSpanIndex + $i] = max($rowsCount[$currentRowSpanIndex + $i] ?? '0', $currentRowsCount);
1271
				}
1272
			}
1273
			// get maximal height of each row
1274
			foreach ($rows as $rowIndex => $row) {
1275
				foreach ($row->getChildren() as $column) {
1276
					$cell = $column->getFirstChild();
1277
					if (!isset($maxRowHeights[$rowGroupIndex][$rowIndex])) {
1278
						$maxRowHeights[$rowGroupIndex][$rowIndex] = '0';
1279
					}
1280
					$columnStyle = $column->getStyle();
1281
					$columnVerticalSize = Math::add($columnStyle->getVerticalMarginsWidth(), $columnStyle->getVerticalPaddingsWidth(), $columnStyle->getVerticalBordersWidth());
1282
					$columnHeight = Math::add($cell->getDimensions()->getOuterHeight(), $columnVerticalSize);
1283
					// for now ignore height of column that have span greater than 1
1284
					if (1 === $column->getRowSpan()) {
1285
						$maxRowHeights[$rowGroupIndex][$rowIndex] = Math::max($maxRowHeights[$rowGroupIndex][$rowIndex], $columnHeight);
1286
					}
1287
				}
1288
			}
1289
			// column that is spanned with more than 1 row must have height that is equal to all spanned rows height
1290
			foreach ($rows as $rowIndex => $row) {
1291
				$currentRowMax = $maxRowHeights[$rowGroupIndex][$rowIndex] ?? '0';
1292
				foreach ($row->getChildren() as $column) {
1293
					$rowSpan = $column->getRowSpan();
1294
					if ($rowSpan > 1) {
1295
						$spannedRowsHeight = '0';
1296
						// get sum of spanned row height starting from current row
1297
						for ($i = 0; $i < $rowSpan; ++$i) {
1298
							if (isset($maxRowHeights[$rowGroupIndex][$rowIndex + $i])) {
1299
								$spannedRowsHeight = Math::add($spannedRowsHeight, $maxRowHeights[$rowGroupIndex][$rowIndex + $i]);
1300
							}
1301
						}
1302
						$fromOtherRows = Math::div($spannedRowsHeight, (string) $rowSpan);
1303
						$fromColumnHeight = Math::div($column->getDimensions()->getOuterHeight(), (string) $rowSpan);
1304
						$currentRowMax = Math::max($currentRowMax, $fromOtherRows, $fromColumnHeight);
1305
						// if column that have rowSpan >1 is higher than sum of all other spanned rows max height expand others
1306
						if (Math::comp($fromColumnHeight, $fromOtherRows) > 0) {
1307
							for ($i = 0; $i < $rowSpan; ++$i) {
1308
								$maxRowHeights[$rowGroupIndex][$rowIndex + $i] = $currentRowMax;
1309
							}
1310
						}
1311
					}
1312
				}
1313
				$maxRowHeights[$rowGroupIndex][$rowIndex] = $currentRowMax;
1314
			}
1315
		}
1316
		$tableHeight = '0';
1317
		$rowGroups = $this->getChildren();
1318
		foreach ($rowGroups as $rowGroupIndex => $rowGroup) {
1319
			$rowGroupHeight = '0';
1320
			$rows = $rowGroup->getChildren();
1321
			foreach ($rows as $rowIndex => $row) {
1322
				$rowStyle = $row->getStyle();
1323
				$row->getDimensions()->setHeight(Math::add($maxRowHeights[$rowGroupIndex][$rowIndex], $rowStyle->getVerticalBordersWidth(), $rowStyle->getVerticalPaddingsWidth()));
1324
				$rowGroupHeight = Math::add($rowGroupHeight, $row->getDimensions()->getHeight());
0 ignored issues
show
Bug introduced by
It seems like $row->getDimensions()->getHeight() can also be of type null; 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

1324
				$rowGroupHeight = Math::add($rowGroupHeight, /** @scrutinizer ignore-type */ $row->getDimensions()->getHeight());
Loading history...
1325
				foreach ($row->getChildren() as $column) {
1326
					if ($column->getRowSpan() > 1 && $afterPageDividing) {
1327
						continue;
1328
					}
1329
					$column->getDimensions()->setHeight($row->getDimensions()->getInnerHeight());
1330
					$cell = $column->getFirstChild();
1331
					$cellStyle = $cell->getStyle();
1332
					if ('auto' !== $cellStyle->getRules('height')) {
1333
						$cellStyle->getRules()['height']->convert($cellStyle);
1334
						$height = $cellStyle->getRules('height');
1335
					} else {
1336
						$height = $column->getDimensions()->getInnerHeight();
1337
						$height = Math::div($height, (string) $column->getRowSpan());
1338
					}
1339
					$cellChildrenHeight = '0';
1340
					foreach ($cell->getChildren() as $cellChild) {
1341
						$cellChildrenHeight = Math::add($cellChildrenHeight, $cellChild->getDimensions()->getOuterHeight());
1342
					}
1343
					$cellVerticalSize = Math::add($cellStyle->getVerticalBordersWidth(), $cellStyle->getVerticalPaddingsWidth());
1344
					$cellChildrenHeight = Math::add($cellChildrenHeight, $cellVerticalSize);
1345
					$cellChildrenHeight = Math::div($cellChildrenHeight, (string) $column->getRowSpan());
1346
					// add vertical padding if needed
1347
					if (Math::comp($height, $cellChildrenHeight) > 0) {
0 ignored issues
show
Bug introduced by
It seems like $height can also be of type array; however, parameter $left of YetiForcePDF\Math::comp() 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

1347
					if (Math::comp(/** @scrutinizer ignore-type */ $height, $cellChildrenHeight) > 0) {
Loading history...
1348
						$freeSpace = Math::sub($height, $cellChildrenHeight);
0 ignored issues
show
Bug introduced by
It seems like $height can also be of type array; however, parameter $numbers of YetiForcePDF\Math::sub() 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

1348
						$freeSpace = Math::sub(/** @scrutinizer ignore-type */ $height, $cellChildrenHeight);
Loading history...
1349
						$cellStyle = $cell->getStyle();
1350
						switch ($cellStyle->getRules('vertical-align')) {
1351
							case 'top':
1352
								$freeSpace = Math::add($freeSpace, $cellStyle->getRules('padding-bottom'));
0 ignored issues
show
Bug introduced by
It seems like $cellStyle->getRules('padding-bottom') 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

1352
								$freeSpace = Math::add($freeSpace, /** @scrutinizer ignore-type */ $cellStyle->getRules('padding-bottom'));
Loading history...
1353
								$cellStyle->setRule('padding-bottom', $freeSpace);
1354
								break;
1355
							case 'bottom':
1356
								$freeSpace = Math::add($freeSpace, $cellStyle->getRules('padding-top'));
1357
								$cellStyle->setRule('padding-top', $freeSpace);
1358
								break;
1359
							case 'baseline':
1360
							case 'middle':
1361
							default:
1362
								$disposition = Math::div($freeSpace, '2');
1363
								$paddingTop = Math::add($cellStyle->getRules('padding-top'), $disposition);
1364
								$paddingBottom = Math::add($cellStyle->getRules('padding-bottom'), $disposition);
1365
								$cellStyle->setRule('padding-top', $paddingTop);
1366
								$cellStyle->setRule('padding-bottom', $paddingBottom);
1367
								break;
1368
						}
1369
					}
1370
					$height = Math::max($height, $cellChildrenHeight);
0 ignored issues
show
Bug introduced by
It seems like $height 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

1370
					$height = Math::max(/** @scrutinizer ignore-type */ $height, $cellChildrenHeight);
Loading history...
1371
					$cell->getDimensions()->setHeight($height);
1372
				}
1373
			}
1374
			if (isset($row) && 'separate' === $row->getStyle()->getRules('border-collapse')) {
1375
				$rowGroupHeight = Math::add($rowGroupHeight, $row->getStyle()->getRules('border-spacing'));
1376
			}
1377
			$rowGroup->getDimensions()->setHeight($rowGroupHeight);
1378
			$tableHeight = Math::add($tableHeight, $rowGroupHeight);
1379
		}
1380
		$this->getDimensions()->setHeight(Math::add($tableHeight, $style->getVerticalBordersWidth(), $style->getVerticalPaddingsWidth()));
1381
1382
		return $this;
1383
	}
1384
1385
	/**
1386
	 * Remove empty rows.
1387
	 *
1388
	 * @return $this
1389
	 */
1390
	public function removeEmptyRows()
1391
	{
1392
		foreach ($this->getChildren() as $rowGroup) {
1393
			if (!$rowGroup->containContent() || !$rowGroup->hasChildren()) {
1394
				$this->removeChild($rowGroup);
1395
			} else {
1396
				foreach ($rowGroup->getChildren() as $row) {
1397
					if (!$row->containContent() || !$row->hasChildren()) {
1398
						$rowGroup->removeChild($row);
1399
					}
1400
				}
1401
			}
1402
		}
1403
1404
		return $this;
1405
	}
1406
}
1407