InlineBox::createText()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 19
rs 9.7666
c 0
b 0
f 0
cc 4
nc 4
nop 2
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * InlineBox 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 InlineBox.
22
 */
23
class InlineBox extends ElementBox implements BoxInterface, BuildTreeInterface, AppendChildInterface
24
{
25
	/**
26
	 * @var \YetiForcePDF\Layout\TextBox
27
	 */
28
	protected $previousTextBox;
29
	/**
30
	 * Parent width cache.
31
	 *
32
	 * @var string
33
	 */
34
	protected $parentWidth = '0';
35
	/**
36
	 * Parent height cache.
37
	 *
38
	 * @var string
39
	 */
40
	protected $parentHeight = '0';
41
42
	/**
43
	 * Go up to Line box and clone and wrap element.
44
	 *
45
	 * @param Box $box
46
	 *
47
	 * @return Box
48
	 */
49
	public function cloneParent(Box $box)
50
	{
51
		if ($parent = $this->getParent()) {
52
			$clone = clone $this;
53
			$clone->getStyle()->setBox($clone);
54
			$clone->getDimensions()->setBox($clone);
55
			$clone->getOffset()->setBox($clone);
56
			$clone->getElement()->setBox($clone);
57
			$clone->getCoordinates()->setBox($clone);
58
			$clone->appendChild($box);
59
			if (!$parent instanceof LineBox) {
60
				$parent->cloneParent($clone);
0 ignored issues
show
Bug introduced by
The method cloneParent() does not exist on YetiForcePDF\Layout\Box. Did you maybe mean clone()? ( Ignorable by Annotation )

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

60
				$parent->/** @scrutinizer ignore-call */ 
61
             cloneParent($clone);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
61
			} else {
62
				$parent->appendChild($clone);
63
			}
64
		}
65
		return $box;
66
	}
67
68
	/**
69
	 * {@inheritdoc}
70
	 */
71
	public function appendBlockBox($childDomElement, $element, $style, $parentBlock)
72
	{
73
		$box = (new BlockBox())
74
			->setDocument($this->document)
75
			->setParent($this)
76
			->setElement($element)
77
			->setStyle($style)
78
			->init();
79
		// if we add this child to parent box we loose parent inline styles if nested
80
		// so we need to wrap this box later and split lines at block element
81
		if (isset($this->getChildren()[0])) {
82
			$this->cloneParent($box);
83
		} else {
84
			$this->appendChild($box);
85
		}
86
		$box->getStyle()->init();
87
		$box->buildTree($box);
88
		return $box;
89
	}
90
91
	/**
92
	 * {@inheritdoc}
93
	 */
94
	public function appendTableWrapperBox($childDomElement, $element, $style, $parentBlock)
95
	{
96
		$box = (new TableWrapperBox())
97
			->setDocument($this->document)
98
			->setParent($this)
99
			->setElement($element)
100
			->setStyle($style)
101
			->init();
102
		// if we add this child to parent box we loose parent inline styles if nested
103
		// so we need to wrap this box later and split lines at block element
104
		if (isset($this->getChildren()[0])) {
105
			$this->cloneParent($box);
106
		} else {
107
			$this->appendChild($box);
108
		}
109
		$box->getStyle()->init();
110
		// we don't want to build tree from here - we will build it from TableBox
111
		return $box;
112
	}
113
114
	/**
115
	 * {@inheritdoc}
116
	 */
117
	public function appendInlineBlockBox($childDomElement, $element, $style, $parentBlock)
118
	{
119
		$box = (new InlineBlockBox())
120
			->setDocument($this->document)
121
			->setParent($this)
122
			->setElement($element)
123
			->setStyle($style)
124
			->init();
125
		// if we add this child to parent box we loose parent inline styles if nested
126
		// so we need to wrap this box later and split lines at block element
127
		if (isset($this->getChildren()[0])) {
128
			$this->cloneParent($box);
129
		} else {
130
			$this->appendChild($box);
131
		}
132
		$box->getStyle()->init();
133
		$box->buildTree($box);
134
		return $box;
135
	}
136
137
	/**
138
	 * {@inheritdoc}
139
	 */
140
	public function appendInlineBox($childDomElement, $element, $style, $parentBlock)
141
	{
142
		$box = (new self())
143
			->setDocument($this->document)
144
			->setParent($this)
145
			->setElement($element)
146
			->setStyle($style)
147
			->init();
148
		if (isset($this->getChildren()[0])) {
149
			$this->cloneParent($box);
150
		} else {
151
			$this->appendChild($box);
152
		}
153
		$box->getStyle()->init();
154
		$box->buildTree($parentBlock);
155
		return $box;
156
	}
157
158
	/**
159
	 * Create text.
160
	 *
161
	 * @param $content
162
	 * @param bool $sameId
163
	 *
164
	 * @return $this
165
	 */
166
	public function createText($content, bool $sameId = false)
167
	{
168
		if ($sameId && $this->previousTextBox) {
169
			$box = $this->previousTextBox->clone();
170
		} else {
171
			$box = (new TextBox())
172
				->setDocument($this->document)
173
				->setParent($this)
174
				->init();
175
		}
176
		$box->setText($content);
177
		$this->previousTextBox = $box;
178
		if (isset($this->getChildren()[0])) {
179
			$this->previousTextBox = $this->cloneParent($box);
180
		} else {
181
			$this->appendChild($box);
182
			$this->previousTextBox = $box;
183
		}
184
		return $box;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $box returns the type YetiForcePDF\Layout\TextBox which is incompatible with the documented return type YetiForcePDF\Layout\InlineBox.
Loading history...
185
	}
186
187
	/**
188
	 * Get previous sibling inline-level element text.
189
	 *
190
	 * @return string|null
191
	 */
192
	protected function getPreviousText()
193
	{
194
		$closest = $this->getClosestLineBox()->getLastChild();
195
		$previousTop = $closest->getPrevious();
196
		if ($previousTop && $textBox = $previousTop->getFirstTextBox()) {
197
			return $textBox->getText();
198
		}
199
	}
200
201
	/**
202
	 * Add text.
203
	 *
204
	 * @param \DOMNode                           $childDomElement
205
	 * @param Element                            $element
206
	 * @param Style                              $style
207
	 * @param \YetiForcePDF\Layout\BlockBox|null $parentBlock
208
	 *
209
	 * @return $this
210
	 */
211
	public function appendText($childDomElement, $element = null, $style = null, $parentBlock = null)
0 ignored issues
show
Unused Code introduced by
The parameter $element 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

211
	public function appendText($childDomElement, /** @scrutinizer ignore-unused */ $element = null, $style = null, $parentBlock = null)

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

211
	public function appendText($childDomElement, $element = null, /** @scrutinizer ignore-unused */ $style = null, $parentBlock = null)

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 $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

211
	public function appendText($childDomElement, $element = null, $style = null, /** @scrutinizer ignore-unused */ $parentBlock = null)

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...
212
	{
213
		$text = $childDomElement->textContent;
214
		$whiteSpace = $this->getStyle()->getRules('white-space');
215
		switch ($whiteSpace) {
216
			case 'normal':
217
			case 'nowrap':
218
				$text = preg_replace('/([\t ]+)?\r([\t ]+)?/u', "\r", $text);
219
				$text = preg_replace('/\r+/u', ' ', $text);
220
				$text = preg_replace('/\t+/u', ' ', $text);
221
				$text = preg_replace('/ +/u', ' ', $text);
222
				break;
223
		}
224
		if ('' !== $text) {
225
			if ('normal' === $whiteSpace) {
226
				$words = preg_split('/ /u', $text, 0);
227
				$count = \count($words);
228
				if ($count) {
229
					foreach ($words as $index => $word) {
230
						if ('' !== $word) {
231
							$this->createText($word);
232
							$parent = $this->getParent();
233
							$anonymous = ($parent instanceof self && $parent->isAnonymous()) || $parent instanceof LineBox;
234
							if ($index + 1 !== $count || $anonymous) {
235
								$this->createText(' ', true);
236
							}
237
						} else {
238
							$this->createText(' ', true);
239
						}
240
					}
241
				} else {
242
					$this->createText(' ', true);
243
				}
244
			} elseif ('nowrap' === $whiteSpace) {
245
				$this->createText($text, true);
246
			}
247
		}
248
		return $this;
249
	}
250
251
	/**
252
	 * Measure width.
253
	 *
254
	 * @param bool $afterPageDividing
255
	 *
256
	 * @return $this
257
	 */
258
	public function measureWidth(bool $afterPageDividing = false)
259
	{
260
		$style = $this->getStyle();
261
		if ($this->parentWidth === $this->getParent()->getDimensions()->getWidth() && null !== $this->getDimensions()->getWidth()) {
262
			if (!$this->isForMeasurement()) {
263
				$this->getDimensions()->setWidth(Math::add($style->getHorizontalBordersWidth(), $style->getHorizontalPaddingsWidth()));
264
			}
265
			return $this;
266
		}
267
		$this->parentWidth = $this->getParent()->getDimensions()->getWidth();
268
		$width = '0';
269
		if ($this->isForMeasurement()) {
270
			foreach ($this->getChildren() as $child) {
271
				$child->measureWidth($afterPageDividing);
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

271
				$child->/** @scrutinizer ignore-call */ 
272
            measureWidth($afterPageDividing);
Loading history...
272
				$width = Math::add($width, $child->getDimensions()->getOuterWidth());
273
			}
274
		}
275
		$width = Math::add($width, $style->getHorizontalBordersWidth(), $style->getHorizontalPaddingsWidth());
276
		$this->getDimensions()->setWidth($width);
277
		$this->applyStyleWidth();
278
		return $this;
279
	}
280
281
	/**
282
	 * Measure height.
283
	 *
284
	 * @param bool $afterPageDividing
285
	 *
286
	 * @return $this
287
	 */
288
	public function measureHeight(bool $afterPageDividing = false)
289
	{
290
		foreach ($this->getChildren() as $child) {
291
			$child->measureHeight($afterPageDividing);
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

291
			$child->/** @scrutinizer ignore-call */ 
292
           measureHeight($afterPageDividing);
Loading history...
292
		}
293
		$this->getDimensions()->setHeight(Math::add($this->getStyle()->getLineHeight(), $this->getStyle()->getVerticalPaddingsWidth()));
294
		$this->applyStyleHeight();
295
		return $this;
296
	}
297
298
	/**
299
	 * Position.
300
	 *
301
	 * @param bool $afterPageDividing
302
	 *
303
	 * @return $this
304
	 */
305
	public function measureOffset(bool $afterPageDividing = false)
306
	{
307
		$rules = $this->getStyle()->getRules();
308
		$parent = $this->getParent();
309
		$top = $parent->getStyle()->getOffsetTop();
310
		$lineHeight = $this->getClosestLineBox()->getDimensions()->getHeight();
311
		if ('bottom' === $rules['vertical-align']) {
312
			$top = Math::sub($lineHeight, $this->getStyle()->getFont()->getTextHeight());
0 ignored issues
show
Bug introduced by
It seems like $lineHeight can also be of type null; 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

312
			$top = Math::sub(/** @scrutinizer ignore-type */ $lineHeight, $this->getStyle()->getFont()->getTextHeight());
Loading history...
313
		} elseif ('top' === $rules['vertical-align']) {
314
			$top = $this->getStyle()->getFont()->getDescender();
315
		} elseif ('middle' === $rules['vertical-align'] || 'baseline' === $rules['vertical-align']) {
316
			$height = $this->getStyle()->getFont()->getTextHeight();
317
			$top = Math::sub(Math::div($lineHeight, '2'), Math::div($height, '2'));
0 ignored issues
show
Bug introduced by
It seems like $lineHeight can also be of type null; 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

317
			$top = Math::sub(Math::div(/** @scrutinizer ignore-type */ $lineHeight, '2'), Math::div($height, '2'));
Loading history...
318
		}
319
		// margin top inside inline and inline block doesn't affect relative to line top position
320
		// it only affects line margins
321
		$left = (string) $rules['margin-left'];
322
		if ($previous = $this->getPrevious()) {
323
			$left = Math::add($left, $previous->getOffset()->getLeft(), $previous->getDimensions()->getWidth(), $previous->getStyle()->getRules('margin-right'));
0 ignored issues
show
Bug introduced by
It seems like $previous->getStyle()->getRules('margin-right') 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

323
			$left = Math::add($left, $previous->getOffset()->getLeft(), $previous->getDimensions()->getWidth(), /** @scrutinizer ignore-type */ $previous->getStyle()->getRules('margin-right'));
Loading history...
324
		} else {
325
			$left = Math::add($left, $parent->getStyle()->getOffsetLeft());
326
		}
327
		$this->getOffset()->setLeft($left);
328
		$this->getOffset()->setTop($top);
329
		foreach ($this->getChildren() as $child) {
330
			$child->measureOffset($afterPageDividing);
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

330
			$child->/** @scrutinizer ignore-call */ 
331
           measureOffset($afterPageDividing);
Loading history...
331
		}
332
		return $this;
333
	}
334
335
	/**
336
	 * Position.
337
	 *
338
	 * @param bool $afterPageDividing
339
	 *
340
	 * @return $this
341
	 */
342
	public function measurePosition(bool $afterPageDividing = false)
343
	{
344
		$parent = $this->getParent();
345
		$this->getCoordinates()->setX(Math::add($parent->getCoordinates()->getX(), $this->getOffset()->getLeft()));
346
		$parent = $this->getClosestLineBox();
347
		$this->getCoordinates()->setY(Math::add($parent->getCoordinates()->getY(), $this->getOffset()->getTop()));
348
		foreach ($this->getChildren() as $child) {
349
			$child->measurePosition($afterPageDividing);
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

349
			$child->/** @scrutinizer ignore-call */ 
350
           measurePosition($afterPageDividing);
Loading history...
350
		}
351
		return $this;
352
	}
353
354
	public function __clone()
355
	{
356
		$this->element = clone $this->element;
357
		$this->element->setBox($this);
358
		$this->style = clone $this->style;
359
		$this->style->setBox($this);
360
		$this->offset = clone $this->offset;
361
		$this->offset->setBox($this);
362
		$this->dimensions = clone $this->dimensions;
363
		$this->dimensions->setBox($this);
0 ignored issues
show
Bug introduced by
The method setBox() does not exist on YetiForcePDF\Layout\Box. ( Ignorable by Annotation )

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

363
		$this->dimensions->/** @scrutinizer ignore-call */ 
364
                     setBox($this);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
364
		$this->coordinates = clone $this->coordinates;
365
		$this->coordinates->setBox($this);
366
		$this->children = [];
367
	}
368
369
	public function addBackgroundColorInstructions(array $element, $pdfX, $pdfY, $width, $height)
370
	{
371
		if ('none' === $this->getStyle()->getRules('display')) {
372
			return $element;
373
		}
374
		$rules = $this->style->getRules();
375
		$graphicState = $this->style->getGraphicState();
376
		$graphicStateStr = '/' . $graphicState->getNumber() . ' gs';
377
		if ('transparent' !== $rules['background-color']) {
378
			$bgColor = [
379
				'q',
380
				$graphicStateStr,
381
				"1 0 0 1 $pdfX $pdfY cm",
382
				"{$rules['background-color'][0]} {$rules['background-color'][1]} {$rules['background-color'][2]} rg",
383
				"0 0 $width $height re",
384
				'f',
385
				'Q',
386
			];
387
			$element = array_merge($element, $bgColor);
388
		}
389
		return $element;
390
	}
391
392
	/**
393
	 * Get element PDF instructions to use in content stream.
394
	 *
395
	 * @return string
396
	 */
397
	public function getInstructions(): string
398
	{
399
		$coordinates = $this->getCoordinates();
400
		$pdfX = $coordinates->getPdfX();
401
		$pdfY = $coordinates->getPdfY();
402
		$dimensions = $this->getDimensions();
403
		$width = $dimensions->getWidth();
404
		$height = $dimensions->getHeight();
405
		$element = [];
406
		$element = $this->addBackgroundColorInstructions($element, $pdfX, $pdfY, $width, $height);
407
		$element = $this->addBorderInstructions($element, $pdfX, $pdfY, $width, $height);
0 ignored issues
show
Bug introduced by
It seems like $height can also be of type null; however, parameter $height of YetiForcePDF\Layout\Box::addBorderInstructions() 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

407
		$element = $this->addBorderInstructions($element, $pdfX, $pdfY, $width, /** @scrutinizer ignore-type */ $height);
Loading history...
408
		return implode("\n", $element);
409
	}
410
}
411