Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
InlineFragment
100.00% covered (success)
100.00%
22 / 22
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
 withBlockOffset
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\HtmlToPdf\Layout;
6
7use Phpdftk\Css\Value\Color;
8use Phpdftk\HtmlToPdf\Box\AtomicInlineBox;
9use Phpdftk\Text\ShapedRun;
10
11/**
12 * One positioned shaped-text fragment inside a {@see LineBox}. `x` is the
13 * fragment's left edge relative to the line box's left edge; `width` is
14 * the run's total advance.
15 *
16 * Multiple fragments per line allow a single line to mix runs from
17 * different inline parents (e.g. `<p>hello <em>world</em></p>` produces a
18 * "hello " fragment and a "world" fragment on the same line).
19 */
20final readonly class InlineFragment
21{
22    public function __construct(
23        public float $x,
24        public float $width,
25        public ShapedRun $shapedRun,
26        /**
27         * Additional Y offset applied to the fragment's baseline relative
28         * to the line's main baseline. Used for `vertical-align: sub` /
29         * `super` — negative values lift the fragment (higher on the page),
30         * positive values drop it. Layout-space Y, top-down.
31         */
32        public float $baselineShift = 0.0,
33        /**
34         * When non-null, the fragment originated inside an `<a href>`
35         * subtree; the painter emits a `/Link` annotation covering the
36         * fragment's rect, targeting this URI.
37         */
38        public ?string $href = null,
39        /**
40         * `true` when the fragment's cascaded `font-weight` is bold-ish
41         * (≥ 600). The painter renders these in PDF text mode 2 (fill +
42         * stroke) as a fake-bold fallback when a real bold font isn't
43         * available.
44         */
45        public bool $isBold = false,
46        /**
47         * `true` when the fragment's cascaded `font-style` is `italic` or
48         * `oblique`. The painter applies a skew transform in `Tm` as a
49         * fake-italic fallback for the same reason.
50         */
51        public bool $isItalic = false,
52        /**
53         * Text-decoration lines effective for this fragment (per CSS Text
54         * Decoration 4 §2 the property applies to "all in-flow boxes" but
55         * propagates from the inline element where it was set). Values
56         * are CSS keywords from the `text-decoration-line` vocabulary:
57         * `'underline'` / `'overline'` / `'line-through'`.
58         *
59         * @var list<string>
60         */
61        public array $decorationLines = [],
62        /**
63         * Per-fragment fill color. When non-null the painter sets the text
64         * fill colour to this before emitting the fragment, overriding the
65         * line's block-level default — needed for inline elements like
66         * `<a>` whose UA `color` differs from the surrounding paragraph.
67         */
68        public ?Color $textColor = null,
69        /**
70         * Per-fragment inline background, propagated downward from an
71         * `InlineBox` whose cascade sets `background-color`. When non-null
72         * the painter fills a rect under the fragment in this colour before
73         * the text emits, matching browser inline-background rendering of
74         * elements like `<mark>`.
75         */
76        public ?Color $backgroundColor = null,
77        /**
78         * Companion `<a title>` text. Lands on the link annotation's
79         * `/Contents` field — PDF viewers show this as a tooltip on hover.
80         */
81        public ?string $linkTitle = null,
82        /**
83         * Per-fragment text-decoration colour (CSS Text Decoration 4 §3).
84         * When non-null the painter uses this when stroking the fragment's
85         * underline / overline / line-through, overriding the block-level
86         * default — so an inline element like
87         * `<u style="text-decoration-color: red">` paints a red underline
88         * even when the surrounding paragraph carries the cascaded default.
89         */
90        public ?Color $decorationColor = null,
91        /**
92         * `true` when the fragment is a pure whitespace token (spaces,
93         * tabs, newlines). Used by `applyTextAlign` to honour
94         * CSS Text 3 §5.5 — trailing whitespace at the end of a line
95         * "hangs" and is excluded from the alignment slack
96         * calculation — and by future justify-distribution work to
97         * skip the trailing-ws gap.
98         */
99        public bool $isWhitespace = false,
100        /**
101         * The fragment's own used `line-height` in layout units (CSS Inline
102         * 3 §3), resolved against the inline box it came from — NOT the
103         * block's. Drives the half-leading extent math in `lineMetrics`:
104         * leading = lineHeight − (ascent + descent), split half above / half
105         * below the fragment's content box. The sentinel `-1.0` means "unset"
106         * — the line sizer then falls back to the fragment's font ascent +
107         * descent (no leading), preserving pre-half-leading behaviour for
108         * synthetic fragments (ellipsis, trims) that don't carry a resolved
109         * value. `-1.0` rather than `0.0` so an explicit `line-height: 0` is
110         * honoured as zero leading, not mistaken for unset.
111         */
112        public float $lineHeight = -1.0,
113        /**
114         * The line-relative `vertical-align` keyword for this fragment's
115         * originating inline box (CSS2 §10.8.1) — one of `top`, `bottom`,
116         * `middle`, `text-top`, `text-bottom`. The default `baseline` (also
117         * covers `sub`/`super`/`<length>`, which are folded into
118         * `baselineShift` during the tree walk since they compose through
119         * nesting). The keyword forms do NOT compose and are resolved against
120         * the line box / strut in `finalizeLine`, which overrides
121         * `baselineShift` for these fragments.
122         */
123        public string $verticalAlign = 'baseline',
124        /**
125         * Vertical writing-mode transpose (CSS Writing Modes 4 §7.1): the
126         * fragment's offset DOWN the column (physical +Y = inline axis)
127         * from the line box's `y`. `applyVerticalLineShift` moves each
128         * fragment's original inline advance (its horizontal `x`) here and
129         * repurposes `x` as the column's block-axis (physical-X) position,
130         * so multiple fragments in one source line stack down a single
131         * column. The painter adds it to the column top. Zero (the default)
132         * for horizontal-tb, so every non-vertical path is byte-identical.
133         */
134        public float $blockOffset = 0.0,
135        /**
136         * When non-null, this fragment stands in for an inline atomic /
137         * replaced box (`<img>`, inline-block, inline SVG). Its glyph run is
138         * empty (advance-only), so line-box sizing derives the fragment's
139         * ascent/descent from this box's committed margin-box height rather
140         * than the surrounding font metrics (CSS 2.1 §10.8 — an inline
141         * replaced/inline-block box contributes its margin-box height to the
142         * line, with the box's baseline at its bottom margin edge). The atomic
143         * box's own `geometry->y` is re-committed against the finalized line
144         * baseline in {@see InlineLayout::commitAtomicFragmentY}. Text
145         * fragments keep `null`, so their sizing path is unchanged.
146         */
147        public ?AtomicInlineBox $atomicBox = null,
148        /**
149         * How far this fragment's background extends beyond the line box,
150         * above and below.
151         *
152         * CSS 2.1 §10.6.1 — an inline box's vertical padding and border do
153         * NOT change the line box height, but they ARE painted, bleeding over
154         * the adjacent lines. Layout ignores these entirely; only the painter
155         * reads them.
156         */
157        public float $bgExtendAbove = 0.0,
158        public float $bgExtendBelow = 0.0,
159    ) {}
160
161    /**
162     * Clone with a different {@see $blockOffset}, preserving every other
163     * field. Used when a vertical writing-mode box shrink-wraps its inline
164     * size and its transposed runs have to be re-aligned against the size
165     * the box actually ended up with (CSS Writing Modes 4 §7.1).
166     */
167    public function withBlockOffset(float $blockOffset): self
168    {
169        return new self(
170            $this->x,
171            $this->width,
172            $this->shapedRun,
173            $this->baselineShift,
174            $this->href,
175            $this->isBold,
176            $this->isItalic,
177            $this->decorationLines,
178            $this->textColor,
179            $this->backgroundColor,
180            $this->linkTitle,
181            $this->decorationColor,
182            $this->isWhitespace,
183            $this->lineHeight,
184            $this->verticalAlign,
185            $blockOffset,
186            $this->atomicBox,
187            $this->bgExtendAbove,
188            $this->bgExtendBelow,
189        );
190    }
191}