Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
Box
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
2 / 2
2
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addChild
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\HtmlToPdf\Box;
6
7use Phpdftk\Css\Cascade\CascadedValues;
8use Phpdftk\Html\Dom\Element;
9use Phpdftk\HtmlToPdf\Layout\BoxGeometry;
10use Phpdftk\HtmlToPdf\Layout\LineBox;
11use Phpdftk\HtmlToPdf\Layout\MultiColumnLayout;
12
13/**
14 * Base of the box tree produced by {@see BoxGenerator}.
15 *
16 * Each non-anonymous box carries its originating DOM element + the
17 * cascade's resolved property bag for that element. Anonymous boxes
18 * (wrappers synthesised by CSS Display 3 box-generation rules) have a
19 * null `$element` but inherit a style derived from their parent.
20 *
21 * `$children` is in document / logical order; layout (Phase 1F) walks the
22 * tree to compute positions and dimensions without re-ordering.
23 *
24 * Phase 1E.1 ships the structural box tree only — every box's geometry
25 * (`x`, `y`, `width`, `height`) is layout's responsibility, not box
26 * generation's. Keep that boundary sharp; this class doesn't track
27 * positions or sizes.
28 */
29abstract class Box
30{
31    /** @var list<Box> */
32    public array $children = [];
33
34    /** Resolved geometry — populated by layout, blank until {@see \Phpdftk\HtmlToPdf\Layout\BlockLayout} runs. */
35    public BoxGeometry $geometry;
36
37    /**
38     * Line boxes produced by {@see \Phpdftk\HtmlToPdf\Layout\InlineLayout}
39     * when this box's children form an inline formatting context. Empty
40     * for block-context parents and for boxes without inline content.
41     *
42     * @var list<LineBox>
43     */
44    public array $lineBoxes = [];
45
46    /**
47     * Set by {@see \Phpdftk\HtmlToPdf\Layout\BlockLayout} when this box's
48     * cascade declares a multi-column container (CSS Multi-column 1).
49     * Null on every other box. The painter reads this to stroke
50     * `column-rule` between adjacent columns.
51     */
52    public ?MultiColumnLayout $multiColumn = null;
53
54    /**
55     * Set by {@see \Phpdftk\HtmlToPdf\Layout\BlockLayout} on every box
56     * that falls entirely after a `line-clamp` container's clamp point.
57     * CSS Overflow 4 §6 discards that content: the boxes still exist in
58     * the tree (their geometry is what located the clamp point in the
59     * first place) but nothing about them is painted — not their
60     * background, not their borders, not their out-of-flow descendants.
61     */
62    public bool $hiddenByLineClamp = false;
63
64    /**
65     * True when this box is an out-of-flow (abs-pos / fixed) box whose
66     * computed `display` was inline-level BEFORE the CSS Display §2.7
67     * blockification that rewrites out-of-flow displays to `block` (set by
68     * {@see \Phpdftk\HtmlToPdf\Box\BoxGenerator}). It preserves the
69     * discriminator the cascade otherwise loses, so the static-position
70     * recovery in {@see \Phpdftk\HtmlToPdf\Layout\BlockLayout} can apply the
71     * CSS 2.1 §10.6.4 inline-continuation static position (end/top of the
72     * previous sibling's last line box) ONLY to boxes that would have laid
73     * out inline in normal flow. A box whose static display was block-level
74     * (this flag false) uses the ordinary block-flow static position
75     * (container inline-start / next block position) instead.
76     */
77    public bool $wasInlineLevel = false;
78
79    /**
80     * The HTML "ordinal value" this box's `::marker` counts with, when the
81     * box is a `display: list-item` one. Null on every other box.
82     *
83     * Assigned by {@see BoxGenerator} during the generation walk rather
84     * than derived from the DOM on demand, because the answer depends on
85     * facts only box generation knows: HTML's "list owner" is the nearest
86     * ancestor `ol` / `ul` / `menu` THAT GENERATES BOXES, and an item that
87     * generates no boxes consumes no ordinal. A DOM-only walk can see
88     * neither, and cannot see that a `display: list-item` `<span>` is an
89     * item at all.
90     *
91     * Both marker paths read it, which is what keeps an `inside` marker
92     * (materialised as inline content by BoxGenerator) and an `outside`
93     * one (painted beside the box by
94     * {@see \Phpdftk\HtmlToPdf\Painter\Painter}) numbering the same list
95     * identically.
96     */
97    public ?int $listItemOrdinal = null;
98
99    /**
100     * True when this box must not draw a `::marker`, even though its
101     * `display` is `list-item`.
102     *
103     * HTML §15.3.9 says a `<fieldset>` "is expected to not generate a
104     * `::marker` pseudo-element" — its contents render through an
105     * anonymous content box that owns no marker position. The suppression
106     * is unconditional, so it cannot be spelled as a `list-style-type:
107     * none` UA rule an author could override with `list-style-type:
108     * decimal`.
109     *
110     * The counter still advances: `display: list-item` implies
111     * `counter-increment: list-item 1` whether or not anything is drawn,
112     * so a marker-less fieldset still consumes an ordinal from the list
113     * around it.
114     */
115    public bool $suppressesListMarker = false;
116
117    /**
118     * Set by {@see \Phpdftk\HtmlToPdf\Layout\BlockLayout} on an
119     * out-of-flow box that CSS Anchor Positioning 1 §10
120     * (`position-visibility`) makes *strongly hidden*. A strongly hidden
121     * box is skipped by the painter along with its whole subtree —
122     * including descendants that set `visibility: visible` and
123     * out-of-flow descendants that escape it — which is what separates
124     * it from ordinary `visibility: hidden`.
125     */
126    public bool $hiddenByPositionVisibility = false;
127
128    /**
129     * `'before'` / `'after'` when this box was generated for a
130     * pseudo-element of {@see $element}, null when it is the element's
131     * own box. A pseudo-element shares its originating element's
132     * `Element` instance, so this is the only thing that tells the two
133     * apart — which CSS Anchor Positioning 1 §3.2 needs, because a
134     * pseudo-element's IMPLICIT anchor is its originating element.
135     */
136    public ?string $pseudoElement = null;
137
138    /**
139     * True when this box is an `<iframe>` / `<frame>` whose children are
140     * the box tree of the document it EMBEDS (HTML §7.3, a child
141     * navigable), rather than fallback content.
142     *
143     * The distinction matters because a frame is a replaced element, and
144     * layout otherwise skips a replaced box's children on the (correct,
145     * for `<img>` / `<video>` / `<object>`) grounds that they are fallback
146     * content the resource replaced. A frame is the one replaced element
147     * whose "resource" IS a box tree, so its children are exactly what
148     * has to be laid out and painted.
149     */
150    public bool $hostsChildNavigable = false;
151
152    public function __construct(
153        public readonly ?Element $element,
154        public readonly CascadedValues $style,
155    ) {
156        $this->geometry = new BoxGeometry();
157    }
158
159    public function addChild(self $child): void
160    {
161        $this->children[] = $child;
162    }
163}