These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @package dompdf |
||
4 | * @link http://dompdf.github.com/ |
||
5 | * @author Benj Carson <[email protected]> |
||
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License |
||
7 | */ |
||
8 | namespace Dompdf\FrameReflower; |
||
9 | |||
10 | use Dompdf\Adapter\CPDF; |
||
11 | use Dompdf\Css\Style; |
||
12 | use Dompdf\Dompdf; |
||
13 | use Dompdf\Helpers; |
||
14 | use Dompdf\Frame; |
||
15 | use Dompdf\FrameDecorator\Block; |
||
16 | use Dompdf\Frame\Factory; |
||
17 | |||
18 | /** |
||
19 | * Base reflower class |
||
20 | * |
||
21 | * Reflower objects are responsible for determining the width and height of |
||
22 | * individual frames. They also create line and page breaks as necessary. |
||
23 | * |
||
24 | * @package dompdf |
||
25 | */ |
||
26 | abstract class AbstractFrameReflower |
||
27 | { |
||
28 | |||
29 | /** |
||
30 | * Frame for this reflower |
||
31 | * |
||
32 | * @var Frame |
||
33 | */ |
||
34 | protected $_frame; |
||
35 | |||
36 | /** |
||
37 | * Cached min/max size |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | protected $_min_max_cache; |
||
42 | |||
43 | /** |
||
44 | * AbstractFrameReflower constructor. |
||
45 | * @param Frame $frame |
||
46 | */ |
||
47 | function __construct(Frame $frame) |
||
48 | { |
||
49 | $this->_frame = $frame; |
||
50 | $this->_min_max_cache = null; |
||
51 | } |
||
52 | |||
53 | function dispose() |
||
54 | { |
||
55 | } |
||
56 | |||
57 | /** |
||
58 | * @return Dompdf |
||
59 | */ |
||
60 | function get_dompdf() |
||
61 | { |
||
62 | return $this->_frame->get_dompdf(); |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Collapse frames margins |
||
67 | * http://www.w3.org/TR/CSS2/box.html#collapsing-margins |
||
68 | */ |
||
69 | protected function _collapse_margins() |
||
70 | { |
||
71 | $frame = $this->_frame; |
||
72 | $cb = $frame->get_containing_block(); |
||
73 | $style = $frame->get_style(); |
||
74 | |||
75 | // Margins of float/absolutely positioned/inline-block elements do not collapse. |
||
76 | if (!$frame->is_in_flow() || $frame->is_inline_block()) { |
||
77 | return; |
||
78 | } |
||
79 | |||
80 | $t = $style->length_in_pt($style->margin_top, $cb["h"]); |
||
81 | $b = $style->length_in_pt($style->margin_bottom, $cb["h"]); |
||
82 | |||
83 | // Handle 'auto' values |
||
84 | if ($t === "auto") { |
||
85 | $style->margin_top = "0pt"; |
||
86 | $t = 0; |
||
87 | } |
||
88 | |||
89 | if ($b === "auto") { |
||
90 | $style->margin_bottom = "0pt"; |
||
91 | $b = 0; |
||
92 | } |
||
93 | |||
94 | // Collapse vertical margins: |
||
95 | $n = $frame->get_next_sibling(); |
||
96 | if ( $n && !$n->is_block() & !$n->is_table() ) { |
||
97 | while ($n = $n->get_next_sibling()) { |
||
98 | if ($n->is_block() || $n->is_table()) { |
||
99 | break; |
||
100 | } |
||
101 | |||
102 | if (!$n->get_first_child()) { |
||
103 | $n = null; |
||
104 | break; |
||
105 | } |
||
106 | } |
||
107 | } |
||
108 | |||
109 | if ($n) { |
||
110 | $n_style = $n->get_style(); |
||
111 | $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["h"]); |
||
112 | |||
113 | $b = $this->_get_collapsed_margin_length($b, $n_t); |
||
114 | $style->margin_bottom = $b . "pt"; |
||
115 | $n_style->margin_top = "0pt"; |
||
116 | } |
||
117 | |||
118 | // Collapse our first child's margin, if there is no border or padding |
||
119 | View Code Duplication | if ($style->border_top_width == 0 && $style->length_in_pt($style->padding_top) == 0) { |
|
0 ignored issues
–
show
|
|||
120 | $f = $this->_frame->get_first_child(); |
||
121 | if ( $f && !$f->is_block() && !$f->is_table() ) { |
||
122 | while ( $f = $f->get_next_sibling() ) { |
||
123 | if ( $f->is_block() || $f->is_table() ) { |
||
124 | break; |
||
125 | } |
||
126 | |||
127 | if ( !$f->get_first_child() ) { |
||
128 | $f = null; |
||
129 | break; |
||
130 | } |
||
131 | } |
||
132 | } |
||
133 | |||
134 | // Margin are collapsed only between block-level boxes |
||
135 | if ($f) { |
||
136 | $f_style = $f->get_style(); |
||
137 | $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["h"]); |
||
138 | |||
139 | $t = $this->_get_collapsed_margin_length($t, $f_t); |
||
140 | $style->margin_top = $t."pt"; |
||
141 | $f_style->margin_top = "0pt"; |
||
142 | } |
||
143 | } |
||
144 | |||
145 | // Collapse our last child's margin, if there is no border or padding |
||
146 | View Code Duplication | if ($style->border_bottom_width == 0 && $style->length_in_pt($style->padding_bottom) == 0) { |
|
0 ignored issues
–
show
The property
border_bottom_width does not exist on object<Dompdf\Css\Style> . Since you implemented __get , maybe consider adding a @property annotation.
Since your code implements the magic getter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
If the property has read access only, you can use the @property-read annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
147 | $l = $this->_frame->get_last_child(); |
||
148 | if ( $l && !$l->is_block() && !$l->is_table() ) { |
||
149 | while ( $l = $l->get_prev_sibling() ) { |
||
150 | if ( $l->is_block() || $l->is_table() ) { |
||
151 | break; |
||
152 | } |
||
153 | |||
154 | if ( !$l->get_last_child() ) { |
||
155 | $l = null; |
||
156 | break; |
||
157 | } |
||
158 | } |
||
159 | } |
||
160 | |||
161 | // Margin are collapsed only between block-level boxes |
||
162 | if ($l) { |
||
163 | $l_style = $l->get_style(); |
||
164 | $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["h"]); |
||
165 | |||
166 | $b = $this->_get_collapsed_margin_length($b, $l_b); |
||
167 | $style->margin_bottom = $b."pt"; |
||
168 | $l_style->margin_bottom = "0pt"; |
||
169 | } |
||
170 | } |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Get the combined (collapsed) length of two adjoining margins. |
||
175 | * |
||
176 | * See http://www.w3.org/TR/CSS2/box.html#collapsing-margins. |
||
177 | * |
||
178 | * @param number $length1 |
||
179 | * @param number $length2 |
||
180 | * @return number |
||
181 | */ |
||
182 | private function _get_collapsed_margin_length($length1, $length2) |
||
183 | { |
||
184 | if ($length1 < 0 && $length2 < 0) { |
||
185 | return min($length1, $length2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0 |
||
186 | } |
||
187 | |||
188 | if ($length1 < 0 || $length2 < 0) { |
||
189 | return $length1 + $length2; // x + y = x - abs(y), if y < 0 |
||
190 | } |
||
191 | |||
192 | return max($length1, $length2); |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * @param Block|null $block |
||
197 | * @return mixed |
||
198 | */ |
||
199 | abstract function reflow(Block $block = null); |
||
200 | |||
201 | /** |
||
202 | * Required for table layout: Returns an array(0 => min, 1 => max, "min" |
||
203 | * => min, "max" => max) of the minimum and maximum widths of this frame. |
||
204 | * This provides a basic implementation. Child classes should override |
||
205 | * this if necessary. |
||
206 | * |
||
207 | * @return array|null |
||
208 | */ |
||
209 | function get_min_max_width() |
||
210 | { |
||
211 | if (!is_null($this->_min_max_cache)) { |
||
212 | return $this->_min_max_cache; |
||
213 | } |
||
214 | |||
215 | $style = $this->_frame->get_style(); |
||
216 | |||
217 | // Account for margins & padding |
||
218 | $dims = array($style->padding_left, |
||
219 | $style->padding_right, |
||
220 | $style->border_left_width, |
||
221 | $style->border_right_width, |
||
222 | $style->margin_left, |
||
223 | $style->margin_right); |
||
224 | |||
225 | $cb_w = $this->_frame->get_containing_block("w"); |
||
226 | $delta = (float)$style->length_in_pt($dims, $cb_w); |
||
227 | |||
228 | // Handle degenerate case |
||
229 | if (!$this->_frame->get_first_child()) { |
||
230 | return $this->_min_max_cache = array( |
||
231 | $delta, $delta, |
||
232 | "min" => $delta, |
||
233 | "max" => $delta, |
||
234 | ); |
||
235 | } |
||
236 | |||
237 | $low = array(); |
||
238 | $high = array(); |
||
239 | |||
240 | for ($iter = $this->_frame->get_children()->getIterator(); $iter->valid(); $iter->next()) { |
||
241 | $inline_min = 0; |
||
242 | $inline_max = 0; |
||
243 | |||
244 | // Add all adjacent inline widths together to calculate max width |
||
245 | while ($iter->valid() && in_array($iter->current()->get_style()->display, Style::$INLINE_TYPES)) { |
||
246 | $child = $iter->current(); |
||
247 | |||
248 | $minmax = $child->get_min_max_width(); |
||
249 | |||
250 | if (in_array($iter->current()->get_style()->white_space, array("pre", "nowrap"))) { |
||
251 | $inline_min += $minmax["min"]; |
||
252 | } else { |
||
253 | $low[] = $minmax["min"]; |
||
254 | } |
||
255 | |||
256 | $inline_max += $minmax["max"]; |
||
257 | $iter->next(); |
||
258 | } |
||
259 | |||
260 | if ($inline_max > 0) { |
||
261 | $high[] = $inline_max; |
||
262 | } |
||
263 | if ($inline_min > 0) { |
||
264 | $low[] = $inline_min; |
||
265 | } |
||
266 | |||
267 | if ($iter->valid()) { |
||
268 | list($low[], $high[]) = $iter->current()->get_min_max_width(); |
||
269 | continue; |
||
270 | } |
||
271 | } |
||
272 | $min = count($low) ? max($low) : 0; |
||
273 | $max = count($high) ? max($high) : 0; |
||
274 | |||
275 | // Use specified width if it is greater than the minimum defined by the |
||
276 | // content. If the width is a percentage ignore it for now. |
||
277 | $width = $style->width; |
||
278 | if ($width !== "auto" && !Helpers::is_percent($width)) { |
||
279 | $width = (float)$style->length_in_pt($width, $cb_w); |
||
280 | if ($min < $width) { |
||
281 | $min = $width; |
||
282 | } |
||
283 | if ($max < $width) { |
||
284 | $max = $width; |
||
285 | } |
||
286 | } |
||
287 | |||
288 | $min += $delta; |
||
289 | $max += $delta; |
||
290 | return $this->_min_max_cache = array($min, $max, "min" => $min, "max" => $max); |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * Parses a CSS string containing quotes and escaped hex characters |
||
295 | * |
||
296 | * @param $string string The CSS string to parse |
||
297 | * @param $single_trim |
||
298 | * @return string |
||
299 | */ |
||
300 | protected function _parse_string($string, $single_trim = false) |
||
301 | { |
||
302 | if ($single_trim) { |
||
303 | $string = preg_replace('/^[\"\']/', "", $string); |
||
304 | $string = preg_replace('/[\"\']$/', "", $string); |
||
305 | } else { |
||
306 | $string = trim($string, "'\""); |
||
307 | } |
||
308 | |||
309 | $string = str_replace(array("\\\n", '\\"', "\\'"), |
||
310 | array("", '"', "'"), $string); |
||
311 | |||
312 | // Convert escaped hex characters into ascii characters (e.g. \A => newline) |
||
313 | $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/", |
||
314 | function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); }, |
||
315 | $string); |
||
316 | return $string; |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Parses a CSS "quotes" property |
||
321 | * |
||
322 | * @return array|null An array of pairs of quotes |
||
323 | */ |
||
324 | protected function _parse_quotes() |
||
325 | { |
||
326 | // Matches quote types |
||
327 | $re = '/(\'[^\']*\')|(\"[^\"]*\")/'; |
||
328 | |||
329 | $quotes = $this->_frame->get_style()->quotes; |
||
330 | |||
331 | // split on spaces, except within quotes |
||
332 | if (!preg_match_all($re, "$quotes", $matches, PREG_SET_ORDER)) { |
||
333 | return null; |
||
334 | } |
||
335 | |||
336 | $quotes_array = array(); |
||
337 | foreach ($matches as $_quote) { |
||
338 | $quotes_array[] = $this->_parse_string($_quote[0], true); |
||
339 | } |
||
340 | |||
341 | if (empty($quotes_array)) { |
||
342 | $quotes_array = array('"', '"'); |
||
343 | } |
||
344 | |||
345 | return array_chunk($quotes_array, 2); |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Parses the CSS "content" property |
||
350 | * |
||
351 | * @return string|null The resulting string |
||
352 | */ |
||
353 | protected function _parse_content() |
||
354 | { |
||
355 | // Matches generated content |
||
356 | $re = "/\n" . |
||
357 | "\s(counters?\\([^)]*\\))|\n" . |
||
358 | "\A(counters?\\([^)]*\\))|\n" . |
||
359 | "\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\3|\n" . |
||
360 | "\A([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\5|\n" . |
||
361 | "\s([^\s\"']+)|\n" . |
||
362 | "\A([^\s\"']+)\n" . |
||
363 | "/xi"; |
||
364 | |||
365 | $content = $this->_frame->get_style()->content; |
||
366 | |||
367 | $quotes = $this->_parse_quotes(); |
||
368 | |||
369 | // split on spaces, except within quotes |
||
370 | if (!preg_match_all($re, $content, $matches, PREG_SET_ORDER)) { |
||
371 | return null; |
||
372 | } |
||
373 | |||
374 | $text = ""; |
||
375 | |||
376 | foreach ($matches as $match) { |
||
377 | View Code Duplication | if (isset($match[2]) && $match[2] !== "") { |
|
378 | $match[1] = $match[2]; |
||
379 | } |
||
380 | |||
381 | View Code Duplication | if (isset($match[6]) && $match[6] !== "") { |
|
382 | $match[4] = $match[6]; |
||
383 | } |
||
384 | |||
385 | View Code Duplication | if (isset($match[8]) && $match[8] !== "") { |
|
386 | $match[7] = $match[8]; |
||
387 | } |
||
388 | |||
389 | if (isset($match[1]) && $match[1] !== "") { |
||
390 | // counters?(...) |
||
391 | $match[1] = mb_strtolower(trim($match[1])); |
||
392 | |||
393 | // Handle counter() references: |
||
394 | // http://www.w3.org/TR/CSS21/generate.html#content |
||
395 | |||
396 | $i = mb_strpos($match[1], ")"); |
||
397 | if ($i === false) { |
||
398 | continue; |
||
399 | } |
||
400 | |||
401 | preg_match('/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]*)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i', $match[1], $args); |
||
402 | $counter_id = $args[3]; |
||
403 | if (strtolower($args[1]) == 'counter') { |
||
404 | // counter(name [,style]) |
||
405 | View Code Duplication | if (isset($args[5])) { |
|
406 | $type = trim($args[5]); |
||
407 | } else { |
||
408 | $type = null; |
||
409 | } |
||
410 | $p = $this->_frame->lookup_counter_frame($counter_id); |
||
411 | |||
412 | $text .= $p->counter_value($counter_id, $type); |
||
413 | |||
414 | } else if (strtolower($args[1]) == 'counters') { |
||
415 | // counters(name, string [,style]) |
||
416 | if (isset($args[5])) { |
||
417 | $string = $this->_parse_string($args[5]); |
||
418 | } else { |
||
419 | $string = ""; |
||
420 | } |
||
421 | |||
422 | View Code Duplication | if (isset($args[7])) { |
|
423 | $type = trim($args[7]); |
||
424 | } else { |
||
425 | $type = null; |
||
426 | } |
||
427 | |||
428 | $p = $this->_frame->lookup_counter_frame($counter_id); |
||
429 | $tmp = array(); |
||
430 | while ($p) { |
||
431 | // We only want to use the counter values when they actually increment the counter |
||
432 | if (array_key_exists($counter_id, $p->_counters)) { |
||
433 | array_unshift($tmp, $p->counter_value($counter_id, $type)); |
||
434 | } |
||
435 | $p = $p->lookup_counter_frame($counter_id); |
||
436 | } |
||
437 | $text .= implode($string, $tmp); |
||
438 | } else { |
||
439 | // countertops? |
||
440 | continue; |
||
441 | } |
||
442 | |||
443 | } else if (isset($match[4]) && $match[4] !== "") { |
||
444 | // String match |
||
445 | $text .= $this->_parse_string($match[4]); |
||
446 | } else if (isset($match[7]) && $match[7] !== "") { |
||
447 | // Directive match |
||
448 | |||
449 | if ($match[7] === "open-quote") { |
||
450 | // FIXME: do something here |
||
451 | $text .= $quotes[0][0]; |
||
452 | } else if ($match[7] === "close-quote") { |
||
453 | // FIXME: do something else here |
||
454 | $text .= $quotes[0][1]; |
||
455 | } else if ($match[7] === "no-open-quote") { |
||
456 | // FIXME: |
||
457 | } else if ($match[7] === "no-close-quote") { |
||
458 | // FIXME: |
||
459 | } else if (mb_strpos($match[7], "attr(") === 0) { |
||
460 | $i = mb_strpos($match[7], ")"); |
||
461 | if ($i === false) { |
||
462 | continue; |
||
463 | } |
||
464 | |||
465 | $attr = mb_substr($match[7], 5, $i - 5); |
||
466 | if ($attr == "") { |
||
467 | continue; |
||
468 | } |
||
469 | |||
470 | $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr); |
||
471 | } else { |
||
472 | continue; |
||
473 | } |
||
474 | } |
||
475 | } |
||
476 | |||
477 | return $text; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * Sets the generated content of a generated frame |
||
482 | */ |
||
483 | protected function _set_content() |
||
484 | { |
||
485 | $frame = $this->_frame; |
||
486 | $style = $frame->get_style(); |
||
487 | |||
488 | // if the element was pushed to a new page use the saved counter value, otherwise use the CSS reset value |
||
489 | if ($style->counter_reset && ($reset = $style->counter_reset) !== "none") { |
||
490 | $vars = preg_split('/\s+/', trim($reset), 2); |
||
491 | $frame->reset_counter($vars[0], (isset($frame->_counters['__' . $vars[0]]) ? $frame->_counters['__' . $vars[0]] : (isset($vars[1]) ? $vars[1] : 0))); |
||
492 | } |
||
493 | |||
494 | if ($style->counter_increment && ($increment = $style->counter_increment) !== "none") { |
||
495 | $frame->increment_counters($increment); |
||
496 | } |
||
497 | |||
498 | if ($style->content && $frame->get_node()->nodeName === "dompdf_generated") { |
||
499 | $content = $this->_parse_content(); |
||
500 | // add generated content to the font subset |
||
501 | // FIXME: This is currently too late because the font subset has already been generated. |
||
502 | // See notes in issue #750. |
||
503 | if ($frame->get_dompdf()->getOptions()->getIsFontSubsettingEnabled() && $frame->get_dompdf()->get_canvas() instanceof CPDF) { |
||
504 | $frame->get_dompdf()->get_canvas()->register_string_subset($style->font_family, $content); |
||
505 | } |
||
506 | |||
507 | $node = $frame->get_node()->ownerDocument->createTextNode($content); |
||
508 | |||
509 | $new_style = $style->get_stylesheet()->create_style(); |
||
510 | $new_style->inherit($style); |
||
511 | |||
512 | $new_frame = new Frame($node); |
||
513 | $new_frame->set_style($new_style); |
||
514 | |||
515 | Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root()); |
||
516 | $frame->append_child($new_frame); |
||
517 | } |
||
518 | } |
||
519 | |||
520 | /** |
||
521 | * Determine current frame width based on contents |
||
522 | * |
||
523 | * @return float |
||
524 | */ |
||
525 | public function calculate_auto_width() |
||
526 | { |
||
527 | return $this->_frame->get_margin_width(); |
||
528 | } |
||
529 | } |
||
530 |
Since your code implements the magic getter
_get
, this function will be called for any read access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.If the property has read access only, you can use the @property-read annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.