PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.23
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.23
1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 All 169 releases
woocommerce-pos / vendor_prefixed / dompdf / dompdf / src / FrameReflower / Text.php

Text.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.23, at vendor_prefixed/dompdf/dompdf/src/FrameReflower/Text.php

497 lines 19.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @package dompdf
5 * @link https://github.com/dompdf/dompdf
6 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 */
8 namespace WCPOS\Vendor\Dompdf\FrameReflower;
9
10 use WCPOS\Vendor\Dompdf\Exception;
11 use WCPOS\Vendor\Dompdf\FontMetrics;
12 use WCPOS\Vendor\Dompdf\FrameDecorator\Block as BlockFrameDecorator;
13 use WCPOS\Vendor\Dompdf\FrameDecorator\Inline as InlineFrameDecorator;
14 use WCPOS\Vendor\Dompdf\FrameDecorator\Text as TextFrameDecorator;
15 use WCPOS\Vendor\Dompdf\Helpers;
16 /**
17 * Reflows text frames.
18 *
19 * @package dompdf
20 */
21 class Text extends AbstractFrameReflower
22 {
23 /**
24 * PHP string representation of HTML entity <shy>
25 */
26 const SOFT_HYPHEN = "­";
27 /**
28 * The regex splits on everything that's a separator (^\S double negative),
29 * excluding the following non-breaking space characters:
30 * * nbsp (\xA0)
31 * * narrow nbsp (\x{202F})
32 * * figure space (\x{2007})
33 */
34 public static $_whitespace_pattern = '/([^\\S\\xA0\\x{202F}\\x{2007}]+)/u';
35 /**
36 * The regex splits on everything that's a separator (^\S double negative)
37 * plus dashes, excluding the following non-breaking space characters:
38 * * nbsp (\xA0)
39 * * narrow nbsp (\x{202F})
40 * * figure space (\x{2007})
41 */
42 public static $_wordbreak_pattern = '/([^\\S\\xA0\\x{202F}\\x{2007}\\n]+|\\R|\\-+|\\xAD+)/u';
43 /**
44 * Frame for this reflower
45 *
46 * @var TextFrameDecorator
47 */
48 protected $_frame;
49 /**
50 * @var FontMetrics
51 */
52 private $fontMetrics;
53 /**
54 * @param TextFrameDecorator $frame
55 * @param FontMetrics $fontMetrics
56 */
57 public function __construct(TextFrameDecorator $frame, FontMetrics $fontMetrics)
58 {
59 parent::__construct($frame);
60 $this->setFontMetrics($fontMetrics);
61 }
62 /**
63 * Apply text transform and white-space collapse according to style.
64 *
65 * * http://www.w3.org/TR/CSS21/text.html#propdef-text-transform
66 * * http://www.w3.org/TR/CSS21/text.html#propdef-white-space
67 *
68 * @param string $text
69 * @return string
70 */
71 protected function pre_process_text(string $text) : string
72 {
73 $style = $this->_frame->get_style();
74 // Handle text transform
75 switch ($style->text_transform) {
76 case "capitalize":
77 $text = Helpers::mb_ucwords($text);
78 break;
79 case "uppercase":
80 $text = \mb_convert_case($text, \MB_CASE_UPPER, "UTF-8");
81 break;
82 case "lowercase":
83 $text = \mb_convert_case($text, \MB_CASE_LOWER, "UTF-8");
84 break;
85 default:
86 break;
87 }
88 // Handle white-space collapse
89 switch ($style->white_space) {
90 default:
91 case "normal":
92 case "nowrap":
93 $text = \preg_replace(self::$_whitespace_pattern, " ", $text) ?? "";
94 break;
95 case "pre-line":
96 // Collapse white space except for line breaks
97 $text = \preg_replace('/([^\\S\\xA0\\x{202F}\\x{2007}\\n]+)/u', " ", $text) ?? "";
98 break;
99 case "pre":
100 case "pre-wrap":
101 break;
102 }
103 return $text;
104 }
105 /**
106 * @param string $text
107 * @param BlockFrameDecorator $block
108 * @param bool $nowrap
109 *
110 * @return int|false
111 */
112 protected function line_break(string $text, BlockFrameDecorator $block, bool $nowrap = \false)
113 {
114 $fontMetrics = $this->getFontMetrics();
115 $frame = $this->_frame;
116 $style = $frame->get_style();
117 $font = $style->font_family;
118 $size = $style->font_size;
119 $word_spacing = $style->word_spacing;
120 $letter_spacing = $style->letter_spacing;
121 // Determine the available width
122 $current_line = $block->get_current_line_box();
123 $line_width = $frame->get_containing_block("w");
124 $current_line_width = $current_line->left + $current_line->w + $current_line->right;
125 $available_width = $line_width - $current_line_width;
126 // Determine the frame width including margin, padding & border
127 $visible_text = \preg_replace('/\\xAD/u', "", $text);
128 $text_width = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
129 $mbp_width = (float) $style->length_in_pt([$style->margin_left, $style->border_left_width, $style->padding_left, $style->padding_right, $style->border_right_width, $style->margin_right], $line_width);
130 $frame_width = $text_width + $mbp_width;
131 if (Helpers::lengthLessOrEqual($frame_width, $available_width)) {
132 return \false;
133 }
134 $force_first = $current_line->left == 0 && $current_line->right == 0 && $current_line->is_empty();
135 if ($nowrap) {
136 return $force_first ? \false : 0;
137 }
138 // Split the text into words
139 $words = \preg_split(self::$_wordbreak_pattern, $text, -1, \PREG_SPLIT_DELIM_CAPTURE);
140 $wc = \count($words);
141 // Determine the split point
142 $width = 0.0;
143 $str = "";
144 $space_width = $fontMetrics->getTextWidth(" ", $font, $size, $word_spacing, $letter_spacing);
145 $shy_width = $fontMetrics->getTextWidth(self::SOFT_HYPHEN, $font, $size);
146 // @todo support <wbr>
147 for ($i = 0; $i < $wc; $i += 2) {
148 // Allow trailing white space to overflow. White space is always
149 // collapsed to the standard space character currently, so only
150 // handle that for now
151 $sep = $words[$i + 1] ?? "";
152 $word = $sep === " " ? $words[$i] : $words[$i] . $sep;
153 $word_width = $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
154 $used_width = $width + $word_width + $mbp_width;
155 if ($used_width > 0 && Helpers::lengthGreater($used_width, $available_width)) {
156 // If the previous split happened by soft hyphen, we have to
157 // append its width again because the last hyphen of a line
158 // won't be removed
159 if (isset($words[$i - 1]) && self::SOFT_HYPHEN === $words[$i - 1]) {
160 $width += $shy_width;
161 }
162 break;
163 }
164 // If the word is splitted by soft hyphen, but no line break is needed
165 // we have to reduce the width. But the str is not modified, otherwise
166 // the wrong offset is calculated at the end of this method.
167 if ($sep === self::SOFT_HYPHEN) {
168 $width += $word_width - $shy_width;
169 $str .= $word;
170 } elseif ($sep === " ") {
171 $width += $word_width + $space_width;
172 $str .= $word . $sep;
173 } else {
174 $width += $word_width;
175 $str .= $word;
176 }
177 }
178 // The first word has overflowed. Force it onto the line, or as many
179 // characters as fit if breaking words is allowed
180 if ($force_first && $width === 0.0) {
181 if ($sep === " ") {
182 $word .= $sep;
183 }
184 // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
185 $wrap = $style->overflow_wrap;
186 $break_word = $wrap === "anywhere" || $wrap === "break-word";
187 if ($break_word) {
188 $s = "";
189 $len = \mb_strlen($word, "UTF-8");
190 for ($j = 0; $j < $len; $j++) {
191 $c = \mb_substr($word, $j, 1, "UTF-8");
192 $w = $fontMetrics->getTextWidth($s . $c, $font, $size, $word_spacing, $letter_spacing);
193 if (Helpers::lengthGreater($w, $available_width)) {
194 break;
195 }
196 $s .= $c;
197 }
198 // Always force the first character onto the line
199 $str = $j === 0 ? $s . $c : $s;
200 } else {
201 $str = $word;
202 }
203 }
204 $offset = \mb_strlen($str, "UTF-8");
205 return $offset;
206 }
207 /**
208 * @param string $text
209 * @return int|false
210 */
211 protected function newline_break(string $text)
212 {
213 if (($i = \mb_strpos($text, "\n", 0, "UTF-8")) === \false) {
214 return \false;
215 }
216 return $i + 1;
217 }
218 /**
219 * @param BlockFrameDecorator $block
220 * @return bool|null Whether to add a new line at the end. `null` if reflow
221 * should be stopped.
222 */
223 protected function layout_line(BlockFrameDecorator $block) : ?bool
224 {
225 $frame = $this->_frame;
226 $style = $frame->get_style();
227 $current_line = $block->get_current_line_box();
228 $text = $frame->get_text();
229 // Trim leading white space if this is the first text on the line
230 if ($current_line->is_empty() && !$frame->is_pre()) {
231 $text = \ltrim($text, " ");
232 }
233 if ($text === "") {
234 $frame->set_text("");
235 $style->set_used("width", 0.0);
236 return \false;
237 }
238 // Determine the next line break
239 // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
240 $white_space = $style->white_space;
241 $nowrap = $white_space === "nowrap" || $white_space === "pre";
242 switch ($white_space) {
243 default:
244 case "normal":
245 case "nowrap":
246 $split = $this->line_break($text, $block, $nowrap);
247 $add_line = \false;
248 break;
249 case "pre":
250 case "pre-line":
251 case "pre-wrap":
252 $hard_split = $this->newline_break($text);
253 $first_line = $hard_split !== \false ? \mb_substr($text, 0, $hard_split, "UTF-8") : $text;
254 $soft_split = $this->line_break($first_line, $block, $nowrap);
255 $split = $soft_split !== \false ? $soft_split : $hard_split;
256 $add_line = $hard_split !== \false;
257 break;
258 }
259 if ($split === 0) {
260 // Make sure to move text when floating frames leave no space to
261 // place anything onto the line
262 // TODO: Would probably be better to move just below the current
263 // floating frame instead of trying to place text in line-height
264 // increments
265 if ($current_line->h === 0.0) {
266 // Line height might be 0
267 $h = \max($frame->get_margin_height(), 1.0);
268 $block->maximize_line_height($h, $frame);
269 }
270 // Break line and repeat layout
271 $block->add_line();
272 // Find the appropriate inline ancestor to split
273 $child = $frame;
274 $p = $child->get_parent();
275 while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) {
276 $child = $p;
277 $p = $p->get_parent();
278 }
279 if ($p instanceof InlineFrameDecorator) {
280 // Split parent and stop current reflow. Reflow continues
281 // via child-reflow loop of split parent
282 $p->split($child);
283 return null;
284 }
285 return $this->layout_line($block);
286 }
287 // Final split point is determined
288 if ($split !== \false && $split < \mb_strlen($text, "UTF-8")) {
289 // Split the line
290 $frame->set_text($text);
291 $frame->split_text($split, \true);
292 $add_line = \true;
293 // Remove inner soft hyphens
294 $t = $frame->get_text();
295 $shyPosition = \mb_strpos($t, self::SOFT_HYPHEN, 0, "UTF-8");
296 if (\false !== $shyPosition && $shyPosition < \mb_strlen($t, "UTF-8") - 1) {
297 $t = \str_replace(self::SOFT_HYPHEN, "", \mb_substr($t, 0, -1, "UTF-8")) . \mb_substr($t, -1, null, "UTF-8");
298 $frame->set_text($t);
299 }
300 } else {
301 // No split required
302 // Remove soft hyphens
303 $text = \str_replace(self::SOFT_HYPHEN, "", $text);
304 $frame->set_text($text);
305 }
306 // Set our new width
307 $frame->recalculate_width();
308 return $add_line;
309 }
310 /**
311 * @param BlockFrameDecorator|null $block
312 * @throws Exception
313 */
314 function reflow(?BlockFrameDecorator $block = null)
315 {
316 $frame = $this->_frame;
317 $page = $frame->get_root();
318 $page->check_forced_page_break($frame);
319 if ($page->is_full()) {
320 return;
321 }
322 $style = $frame->get_style();
323 // Handle text transform and white space
324 $frame->set_text($this->pre_process_text($frame->get_text()));
325 // map text to fonts based on supported Unicode range
326 $frame->apply_font_mapping();
327 $text = $frame->get_text();
328 // Determine the text height
329 $size = $style->font_size;
330 $font = $style->font_family;
331 $font_height = $this->getFontMetrics()->getFontHeight($font, $size);
332 $style->set_used("height", $font_height);
333 if ($block === null) {
334 return;
335 }
336 $add_line = $this->layout_line($block);
337 if ($add_line === null) {
338 return;
339 }
340 $frame->position();
341 // Skip wrapped white space between block-level elements in case white
342 // space is collapsed
343 $text = $frame->get_text();
344 if ($text === "" && $frame->get_margin_width() === 0.0) {
345 return;
346 }
347 $line = $block->add_frame_to_line($frame);
348 $trimmed = \trim($text);
349 // Split the text into words (used to determine spacing between
350 // words on justified lines)
351 if ($trimmed !== "") {
352 $words = \preg_split(self::$_whitespace_pattern, $trimmed);
353 $line->wc += \count($words);
354 }
355 if ($add_line) {
356 $block->add_line();
357 }
358 }
359 /**
360 * Trim trailing white space from the frame text.
361 */
362 public function trim_trailing_ws() : void
363 {
364 $this->_frame->trim_trailing_ws();
365 }
366 public function reset() : void
367 {
368 parent::reset();
369 }
370 //........................................................................
371 public function get_min_max_width() : array
372 {
373 $fontMetrics = $this->getFontMetrics();
374 $frame = $this->_frame;
375 $style = $frame->get_style();
376 // Handle text transform and white space
377 $frame->set_text($this->pre_process_text($frame->get_text()));
378 // map text to fonts based on supported Unicode range
379 $frame->apply_font_mapping();
380 $text = $frame->get_text();
381 $font = $style->font_family;
382 $size = $style->font_size;
383 $word_spacing = $style->word_spacing;
384 $letter_spacing = $style->letter_spacing;
385 if (!$frame->is_pre()) {
386 // Determine whether the frame is at the start of its parent block.
387 // Trim leading white space in that case
388 $child = $frame;
389 $p = $frame->get_parent();
390 while (!$p->is_block() && !$child->get_prev_sibling()) {
391 $child = $p;
392 $p = $p->get_parent();
393 }
394 if (!$child->get_prev_sibling()) {
395 $text = \ltrim($text, " ");
396 }
397 // Determine whether the frame is at the end of its parent block.
398 // Trim trailing white space in that case
399 $child = $frame;
400 $p = $frame->get_parent();
401 while (!$p->is_block() && !$child->get_next_sibling()) {
402 $child = $p;
403 $p = $p->get_parent();
404 }
405 if (!$child->get_next_sibling()) {
406 $text = \rtrim($text, " ");
407 }
408 }
409 // Strip soft hyphens for max-line-width calculations
410 $visible_text = \preg_replace('/\\xAD/u', "", $text);
411 // Determine minimum text width
412 switch ($style->white_space) {
413 default:
414 case "normal":
415 case "pre-line":
416 case "pre-wrap":
417 // The min width is the longest word or, if breaking words is
418 // allowed with the `anywhere` keyword, the widest character.
419 // For performance reasons, we only check the first character in
420 // the latter case.
421 // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
422 if ($style->overflow_wrap === "anywhere") {
423 $char = \mb_substr($visible_text, 0, 1, "UTF-8");
424 $min = $fontMetrics->getTextWidth($char, $font, $size, $word_spacing, $letter_spacing);
425 } else {
426 // Find the longest word
427 $words = \preg_split(self::$_wordbreak_pattern, $text, -1, \PREG_SPLIT_DELIM_CAPTURE);
428 $lengths = \array_map(function ($chunk) use($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
429 // Allow trailing white space to overflow. As in actual
430 // layout above, only handle a single space for now
431 $sep = $chunk[1] ?? "";
432 $word = $sep === " " ? $chunk[0] : $chunk[0] . $sep;
433 return $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
434 }, \array_chunk($words, 2));
435 $min = \max($lengths);
436 }
437 break;
438 case "pre":
439 // Find the longest line
440 $lines = \array_flip(\preg_split("/\\R/u", $visible_text));
441 \array_walk($lines, function (&$chunked_text_width, $chunked_text) use($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
442 $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
443 });
444 \arsort($lines);
445 $min = \reset($lines);
446 break;
447 case "nowrap":
448 $min = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
449 break;
450 }
451 // Determine maximum text width
452 switch ($style->white_space) {
453 default:
454 case "normal":
455 $max = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
456 break;
457 case "pre-line":
458 case "pre-wrap":
459 // Find the longest line
460 $lines = \array_flip(\preg_split("/\\R/u", $visible_text));
461 \array_walk($lines, function (&$chunked_text_width, $chunked_text) use($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
462 $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
463 });
464 \arsort($lines);
465 $max = \reset($lines);
466 break;
467 case "pre":
468 case "nowrap":
469 $max = $min;
470 break;
471 }
472 // Account for margins, borders, and padding
473 $dims = [$style->padding_left, $style->padding_right, $style->border_left_width, $style->border_right_width, $style->margin_left, $style->margin_right];
474 // The containing block is not defined yet, treat percentages as 0
475 $delta = (float) $style->length_in_pt($dims, 0);
476 $min += $delta;
477 $max += $delta;
478 return [$min, $max, "min" => $min, "max" => $max];
479 }
480 /**
481 * @param FontMetrics $fontMetrics
482 * @return $this
483 */
484 public function setFontMetrics(FontMetrics $fontMetrics)
485 {
486 $this->fontMetrics = $fontMetrics;
487 return $this;
488 }
489 /**
490 * @return FontMetrics
491 */
492 public function getFontMetrics()
493 {
494 return $this->fontMetrics;
495 }
496 }
497