Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
23.46% covered (danger)
23.46%
38 / 162
18.05% covered (danger)
18.05%
24 / 133
CRAP
0.00% covered (danger)
0.00%
0 / 1
ComputedStyle
23.46% covered (danger)
23.46%
38 / 162
18.05% covered (danger)
18.05%
24 / 133
12374.22
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 all
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCustomProperty
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getUnknown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBackgroundColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundImage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundRepeat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundPosition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundAttachment
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundOrigin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBackgroundClip
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOpacity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFontFamily
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFontSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFontStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFontWeight
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getLineHeight
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 getTextAlign
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextAlignLast
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTextDecorationLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextDecorationStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextDecorationColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextDecorationThickness
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextTransform
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextIndent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextJustify
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLetterSpacing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWordSpacing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWhiteSpace
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWordBreak
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOverflowWrap
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVerticalAlign
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDirection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUnicodeBidi
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTabSize
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 getQuotes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplay
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPosition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTop
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBottom
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLeft
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getZIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getWidth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMinWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMinHeight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMaxWidth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMaxHeight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMarginTop
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMarginRight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMarginBottom
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMarginLeft
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPaddingTop
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPaddingRight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPaddingBottom
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPaddingLeft
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderTopWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderRightWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderBottomWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderLeftWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderTopStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderRightStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderBottomStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderLeftStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderTopColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderRightColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderBottomColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderLeftColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderTopLeftRadius
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderTopRightRadius
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderBottomLeftRadius
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderBottomRightRadius
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBoxSizing
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBoxShadow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOverflow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOverflowX
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOverflowY
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVisibility
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOutlineWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOutlineStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOutlineColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOutlineOffset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFloat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getClear
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAspectRatio
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFlexDirection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFlexWrap
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getJustifyContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAlignItems
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAlignContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAlignSelf
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFlexGrow
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFlexShrink
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFlexBasis
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOrder
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getRowGap
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumnGap
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBorderCollapse
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCaptionSide
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListStyleType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getListStylePosition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListStyleImage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPage
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getBreakBefore
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBreakAfter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBreakInside
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBoxDecorationBreak
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOrphans
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getWidows
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getColumnCount
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 getColumnWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumnRuleWidth
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumnRuleStyle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumnRuleColor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumnSpan
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumnFill
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTextShadow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getClipPath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFilter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTransform
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 expectKeyword
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 expectColor
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 expectColorOrKeyword
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 expectLength
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 expectLengthOrKeyword
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 expectLengthOrPercentage
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 expectLengthPercentageOrKeyword
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 expectNumber
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Css\Cascade;
6
7use Phpdftk\Css\Value\Color;
8use Phpdftk\Css\Value\CssFunction;
9use Phpdftk\Css\Value\Integer;
10use Phpdftk\Css\Value\Keyword;
11use Phpdftk\Css\Value\Length;
12use Phpdftk\Css\Value\Number;
13use Phpdftk\Css\Value\Percentage;
14use Phpdftk\Css\Value\Url;
15use Phpdftk\Css\Value\Value;
16use Phpdftk\Css\Value\ValueList;
17
18/**
19 * Typed accessor surface over a per-element {@see CascadedValues} bag
20 * per `docs/plans/contracts.md` Â§1D.5. Each getter narrows the
21 * underlying `Value` to the type(s) the property's grammar permits;
22 * properties not explicitly set fall back to the registry's initial
23 * value (via `CascadedValues::get()`).
24 *
25 * The class is intentionally a thin wrapper â€” it adds no caching or
26 * computation of its own. The cascade resolves percentages, inherits
27 * values, and produces the initial-value fallbacks; ComputedStyle
28 * just exposes them with PHP-static-analysable signatures so
29 * downstream consumers (the box generator, painter, SVG renderer)
30 * don't have to type-check every `?Value` themselves.
31 *
32 * **Type narrowing fallback.** When a property's parsed value doesn't
33 * match its declared type (an invalid `font-weight: foo` slipping
34 * through), the getter returns the property's initial value instead
35 * of throwing â€” keeping the accessor surface infallible at the cost
36 * of swallowing parser errors that should have been caught earlier.
37 */
38final readonly class ComputedStyle
39{
40    public function __construct(private CascadedValues $values) {}
41
42    // ========================================================================
43    // Generic access (escape hatches per contracts.md)
44    // ========================================================================
45
46    public function get(string $property): ?Value
47    {
48        return $this->values->get($property);
49    }
50
51    public function has(string $property): bool
52    {
53        return $this->values->has($property);
54    }
55
56    /** @return array<string, Value> */
57    public function all(): array
58    {
59        return $this->values->all();
60    }
61
62    public function getCustomProperty(string $name): ?Value
63    {
64        $name = str_starts_with($name, '--') ? $name : '--' . $name;
65        return $this->values->get($name);
66    }
67
68    public function getUnknown(string $name): ?Value
69    {
70        return $this->values->get($name);
71    }
72
73    // ========================================================================
74    // Color & background
75    // ========================================================================
76
77    public function getColor(): Color
78    {
79        return $this->expectColor('color', new Color(0.0, 0.0, 0.0, 1.0));
80    }
81
82    public function getBackgroundColor(): Color|Keyword
83    {
84        return $this->expectColorOrKeyword('background-color', 'transparent');
85    }
86
87    public function getBackgroundImage(): Value
88    {
89        return $this->values->get('background-image') ?? new Keyword('none');
90    }
91
92    public function getBackgroundRepeat(): Keyword
93    {
94        return $this->expectKeyword('background-repeat', 'repeat');
95    }
96
97    public function getBackgroundPosition(): Value
98    {
99        return $this->values->get('background-position') ?? new Keyword('0% 0%');
100    }
101
102    public function getBackgroundSize(): Value
103    {
104        return $this->values->get('background-size') ?? new Keyword('auto');
105    }
106
107    public function getBackgroundAttachment(): Keyword
108    {
109        return $this->expectKeyword('background-attachment', 'scroll');
110    }
111
112    public function getBackgroundOrigin(): Keyword
113    {
114        return $this->expectKeyword('background-origin', 'padding-box');
115    }
116
117    public function getBackgroundClip(): Keyword
118    {
119        return $this->expectKeyword('background-clip', 'border-box');
120    }
121
122    public function getOpacity(): Number
123    {
124        return $this->expectNumber('opacity', 1.0);
125    }
126
127    // ========================================================================
128    // Font & text
129    // ========================================================================
130
131    public function getFontFamily(): Value
132    {
133        return $this->values->get('font-family') ?? new Keyword('serif');
134    }
135
136    public function getFontSize(): Length
137    {
138        return $this->expectLength('font-size', 16.0);
139    }
140
141    public function getFontStyle(): Keyword
142    {
143        return $this->expectKeyword('font-style', 'normal');
144    }
145
146    public function getFontWeight(): Integer|Keyword
147    {
148        $v = $this->values->get('font-weight');
149        return $v instanceof Integer || $v instanceof Keyword ? $v : new Keyword('normal');
150    }
151
152    public function getLineHeight(): Length|Number|Keyword
153    {
154        $v = $this->values->get('line-height');
155        return $v instanceof Length || $v instanceof Number || $v instanceof Keyword
156            ? $v
157            : new Keyword('normal');
158    }
159
160    public function getTextAlign(): Keyword
161    {
162        return $this->expectKeyword('text-align', 'start');
163    }
164
165    public function getTextAlignLast(): Keyword
166    {
167        return $this->expectKeyword('text-align-last', 'auto');
168    }
169
170    public function getTextDecorationLine(): Keyword
171    {
172        return $this->expectKeyword('text-decoration-line', 'none');
173    }
174
175    public function getTextDecorationStyle(): Keyword
176    {
177        return $this->expectKeyword('text-decoration-style', 'solid');
178    }
179
180    public function getTextDecorationColor(): Color
181    {
182        return $this->expectColor('text-decoration-color', new Color(0.0, 0.0, 0.0, 1.0));
183    }
184
185    public function getTextDecorationThickness(): Length|Keyword
186    {
187        return $this->expectLengthOrKeyword('text-decoration-thickness', 'auto');
188    }
189
190    public function getTextTransform(): Keyword
191    {
192        return $this->expectKeyword('text-transform', 'none');
193    }
194
195    public function getTextIndent(): Length
196    {
197        return $this->expectLength('text-indent', 0.0);
198    }
199
200    public function getTextJustify(): Keyword
201    {
202        return $this->expectKeyword('text-justify', 'auto');
203    }
204
205    public function getLetterSpacing(): Length|Keyword
206    {
207        return $this->expectLengthOrKeyword('letter-spacing', 'normal');
208    }
209
210    public function getWordSpacing(): Length|Keyword
211    {
212        return $this->expectLengthOrKeyword('word-spacing', 'normal');
213    }
214
215    public function getWhiteSpace(): Keyword
216    {
217        return $this->expectKeyword('white-space', 'normal');
218    }
219
220    public function getWordBreak(): Keyword
221    {
222        return $this->expectKeyword('word-break', 'normal');
223    }
224
225    public function getOverflowWrap(): Keyword
226    {
227        return $this->expectKeyword('overflow-wrap', 'normal');
228    }
229
230    public function getVerticalAlign(): Length|Keyword
231    {
232        return $this->expectLengthOrKeyword('vertical-align', 'baseline');
233    }
234
235    public function getDirection(): Keyword
236    {
237        return $this->expectKeyword('direction', 'ltr');
238    }
239
240    public function getUnicodeBidi(): Keyword
241    {
242        return $this->expectKeyword('unicode-bidi', 'normal');
243    }
244
245    public function getTabSize(): Length|Integer
246    {
247        $v = $this->values->get('tab-size');
248        return $v instanceof Length || $v instanceof Integer ? $v : new Integer(8);
249    }
250
251    public function getQuotes(): Value
252    {
253        return $this->values->get('quotes') ?? new Keyword('auto');
254    }
255
256    // ========================================================================
257    // Box model
258    // ========================================================================
259
260    public function getDisplay(): Keyword
261    {
262        return $this->expectKeyword('display', 'inline');
263    }
264
265    public function getPosition(): Keyword
266    {
267        return $this->expectKeyword('position', 'static');
268    }
269
270    public function getTop(): Length|Percentage|Keyword
271    {
272        return $this->expectLengthPercentageOrKeyword('top', 'auto');
273    }
274
275    public function getRight(): Length|Percentage|Keyword
276    {
277        return $this->expectLengthPercentageOrKeyword('right', 'auto');
278    }
279
280    public function getBottom(): Length|Percentage|Keyword
281    {
282        return $this->expectLengthPercentageOrKeyword('bottom', 'auto');
283    }
284
285    public function getLeft(): Length|Percentage|Keyword
286    {
287        return $this->expectLengthPercentageOrKeyword('left', 'auto');
288    }
289
290    public function getZIndex(): Integer|Keyword
291    {
292        $v = $this->values->get('z-index');
293        return $v instanceof Integer || $v instanceof Keyword ? $v : new Keyword('auto');
294    }
295
296    public function getWidth(): Length|Percentage|Keyword
297    {
298        return $this->expectLengthPercentageOrKeyword('width', 'auto');
299    }
300
301    public function getHeight(): Length|Percentage|Keyword
302    {
303        return $this->expectLengthPercentageOrKeyword('height', 'auto');
304    }
305
306    public function getMinWidth(): Length|Percentage|Keyword
307    {
308        return $this->expectLengthPercentageOrKeyword('min-width', 'auto');
309    }
310
311    public function getMinHeight(): Length|Percentage|Keyword
312    {
313        return $this->expectLengthPercentageOrKeyword('min-height', 'auto');
314    }
315
316    public function getMaxWidth(): Length|Percentage|Keyword
317    {
318        return $this->expectLengthPercentageOrKeyword('max-width', 'none');
319    }
320
321    public function getMaxHeight(): Length|Percentage|Keyword
322    {
323        return $this->expectLengthPercentageOrKeyword('max-height', 'none');
324    }
325
326    public function getMarginTop(): Length|Percentage|Keyword
327    {
328        return $this->expectLengthPercentageOrKeyword('margin-top', 'auto');
329    }
330
331    public function getMarginRight(): Length|Percentage|Keyword
332    {
333        return $this->expectLengthPercentageOrKeyword('margin-right', 'auto');
334    }
335
336    public function getMarginBottom(): Length|Percentage|Keyword
337    {
338        return $this->expectLengthPercentageOrKeyword('margin-bottom', 'auto');
339    }
340
341    public function getMarginLeft(): Length|Percentage|Keyword
342    {
343        return $this->expectLengthPercentageOrKeyword('margin-left', 'auto');
344    }
345
346    public function getPaddingTop(): Length|Percentage
347    {
348        return $this->expectLengthOrPercentage('padding-top', 0.0);
349    }
350
351    public function getPaddingRight(): Length|Percentage
352    {
353        return $this->expectLengthOrPercentage('padding-right', 0.0);
354    }
355
356    public function getPaddingBottom(): Length|Percentage
357    {
358        return $this->expectLengthOrPercentage('padding-bottom', 0.0);
359    }
360
361    public function getPaddingLeft(): Length|Percentage
362    {
363        return $this->expectLengthOrPercentage('padding-left', 0.0);
364    }
365
366    public function getBorderTopWidth(): Length
367    {
368        return $this->expectLength('border-top-width', 0.0);
369    }
370
371    public function getBorderRightWidth(): Length
372    {
373        return $this->expectLength('border-right-width', 0.0);
374    }
375
376    public function getBorderBottomWidth(): Length
377    {
378        return $this->expectLength('border-bottom-width', 0.0);
379    }
380
381    public function getBorderLeftWidth(): Length
382    {
383        return $this->expectLength('border-left-width', 0.0);
384    }
385
386    public function getBorderTopStyle(): Keyword
387    {
388        return $this->expectKeyword('border-top-style', 'none');
389    }
390
391    public function getBorderRightStyle(): Keyword
392    {
393        return $this->expectKeyword('border-right-style', 'none');
394    }
395
396    public function getBorderBottomStyle(): Keyword
397    {
398        return $this->expectKeyword('border-bottom-style', 'none');
399    }
400
401    public function getBorderLeftStyle(): Keyword
402    {
403        return $this->expectKeyword('border-left-style', 'none');
404    }
405
406    public function getBorderTopColor(): Color
407    {
408        return $this->expectColor('border-top-color', new Color(0.0, 0.0, 0.0, 1.0));
409    }
410
411    public function getBorderRightColor(): Color
412    {
413        return $this->expectColor('border-right-color', new Color(0.0, 0.0, 0.0, 1.0));
414    }
415
416    public function getBorderBottomColor(): Color
417    {
418        return $this->expectColor('border-bottom-color', new Color(0.0, 0.0, 0.0, 1.0));
419    }
420
421    public function getBorderLeftColor(): Color
422    {
423        return $this->expectColor('border-left-color', new Color(0.0, 0.0, 0.0, 1.0));
424    }
425
426    public function getBorderTopLeftRadius(): Value
427    {
428        return $this->values->get('border-top-left-radius') ?? new Length(0.0, \Phpdftk\Css\Value\LengthUnit::Px);
429    }
430
431    public function getBorderTopRightRadius(): Value
432    {
433        return $this->values->get('border-top-right-radius') ?? new Length(0.0, \Phpdftk\Css\Value\LengthUnit::Px);
434    }
435
436    public function getBorderBottomLeftRadius(): Value
437    {
438        return $this->values->get('border-bottom-left-radius') ?? new Length(0.0, \Phpdftk\Css\Value\LengthUnit::Px);
439    }
440
441    public function getBorderBottomRightRadius(): Value
442    {
443        return $this->values->get('border-bottom-right-radius') ?? new Length(0.0, \Phpdftk\Css\Value\LengthUnit::Px);
444    }
445
446    public function getBoxSizing(): Keyword
447    {
448        return $this->expectKeyword('box-sizing', 'content-box');
449    }
450
451    public function getBoxShadow(): Value
452    {
453        return $this->values->get('box-shadow') ?? new Keyword('none');
454    }
455
456    public function getOverflow(): Keyword
457    {
458        return $this->expectKeyword('overflow', 'visible');
459    }
460
461    public function getOverflowX(): Keyword
462    {
463        return $this->expectKeyword('overflow-x', 'visible');
464    }
465
466    public function getOverflowY(): Keyword
467    {
468        return $this->expectKeyword('overflow-y', 'visible');
469    }
470
471    public function getVisibility(): Keyword
472    {
473        return $this->expectKeyword('visibility', 'visible');
474    }
475
476    public function getOutlineWidth(): Length
477    {
478        return $this->expectLength('outline-width', 0.0);
479    }
480
481    public function getOutlineStyle(): Keyword
482    {
483        return $this->expectKeyword('outline-style', 'none');
484    }
485
486    public function getOutlineColor(): Color
487    {
488        return $this->expectColor('outline-color', new Color(0.0, 0.0, 0.0, 1.0));
489    }
490
491    public function getOutlineOffset(): Length
492    {
493        return $this->expectLength('outline-offset', 0.0);
494    }
495
496    public function getFloat(): Keyword
497    {
498        return $this->expectKeyword('float', 'none');
499    }
500
501    public function getClear(): Keyword
502    {
503        return $this->expectKeyword('clear', 'none');
504    }
505
506    public function getAspectRatio(): Value
507    {
508        return $this->values->get('aspect-ratio') ?? new Keyword('auto');
509    }
510
511    // ========================================================================
512    // Flex
513    // ========================================================================
514
515    public function getFlexDirection(): Keyword
516    {
517        return $this->expectKeyword('flex-direction', 'row');
518    }
519
520    public function getFlexWrap(): Keyword
521    {
522        return $this->expectKeyword('flex-wrap', 'nowrap');
523    }
524
525    public function getJustifyContent(): Keyword
526    {
527        return $this->expectKeyword('justify-content', 'flex-start');
528    }
529
530    public function getAlignItems(): Keyword
531    {
532        return $this->expectKeyword('align-items', 'stretch');
533    }
534
535    public function getAlignContent(): Keyword
536    {
537        return $this->expectKeyword('align-content', 'stretch');
538    }
539
540    public function getAlignSelf(): Keyword
541    {
542        return $this->expectKeyword('align-self', 'auto');
543    }
544
545    public function getFlexGrow(): Number
546    {
547        return $this->expectNumber('flex-grow', 0.0);
548    }
549
550    public function getFlexShrink(): Number
551    {
552        return $this->expectNumber('flex-shrink', 1.0);
553    }
554
555    public function getFlexBasis(): Length|Percentage|Keyword
556    {
557        return $this->expectLengthPercentageOrKeyword('flex-basis', 'auto');
558    }
559
560    public function getOrder(): Integer
561    {
562        $v = $this->values->get('order');
563        return $v instanceof Integer ? $v : new Integer(0);
564    }
565
566    public function getRowGap(): Length|Keyword
567    {
568        return $this->expectLengthOrKeyword('row-gap', 'normal');
569    }
570
571    public function getColumnGap(): Length|Keyword
572    {
573        return $this->expectLengthOrKeyword('column-gap', 'normal');
574    }
575
576    // ========================================================================
577    // Tables
578    // ========================================================================
579
580    public function getBorderCollapse(): Keyword
581    {
582        return $this->expectKeyword('border-collapse', 'separate');
583    }
584
585    public function getCaptionSide(): Keyword
586    {
587        return $this->expectKeyword('caption-side', 'top');
588    }
589
590    // ========================================================================
591    // Lists
592    // ========================================================================
593
594    public function getListStyleType(): Keyword|CssFunction
595    {
596        $v = $this->values->get('list-style-type');
597        return $v instanceof Keyword || $v instanceof CssFunction
598            ? $v
599            : new Keyword('disc');
600    }
601
602    public function getListStylePosition(): Keyword
603    {
604        return $this->expectKeyword('list-style-position', 'outside');
605    }
606
607    public function getListStyleImage(): Value
608    {
609        return $this->values->get('list-style-image') ?? new Keyword('none');
610    }
611
612    // ========================================================================
613    // Paged media
614    // ========================================================================
615
616    public function getPage(): Keyword|CssFunction
617    {
618        $v = $this->values->get('page');
619        return $v instanceof Keyword || $v instanceof CssFunction
620            ? $v
621            : new Keyword('auto');
622    }
623
624    public function getBreakBefore(): Keyword
625    {
626        return $this->expectKeyword('break-before', 'auto');
627    }
628
629    public function getBreakAfter(): Keyword
630    {
631        return $this->expectKeyword('break-after', 'auto');
632    }
633
634    public function getBreakInside(): Keyword
635    {
636        return $this->expectKeyword('break-inside', 'auto');
637    }
638
639    public function getBoxDecorationBreak(): Keyword
640    {
641        return $this->expectKeyword('box-decoration-break', 'slice');
642    }
643
644    public function getOrphans(): Integer
645    {
646        $v = $this->values->get('orphans');
647        return $v instanceof Integer ? $v : new Integer(2);
648    }
649
650    public function getWidows(): Integer
651    {
652        $v = $this->values->get('widows');
653        return $v instanceof Integer ? $v : new Integer(2);
654    }
655
656    // ========================================================================
657    // Multi-column
658    // ========================================================================
659
660    public function getColumnCount(): Integer|Keyword
661    {
662        $v = $this->values->get('column-count');
663        return $v instanceof Integer || $v instanceof Keyword ? $v : new Keyword('auto');
664    }
665
666    public function getColumnWidth(): Length|Keyword
667    {
668        return $this->expectLengthOrKeyword('column-width', 'auto');
669    }
670
671    public function getColumnRuleWidth(): Length
672    {
673        return $this->expectLength('column-rule-width', 0.0);
674    }
675
676    public function getColumnRuleStyle(): Keyword
677    {
678        return $this->expectKeyword('column-rule-style', 'none');
679    }
680
681    public function getColumnRuleColor(): Color
682    {
683        return $this->expectColor('column-rule-color', new Color(0.0, 0.0, 0.0, 1.0));
684    }
685
686    public function getColumnSpan(): Keyword
687    {
688        return $this->expectKeyword('column-span', 'none');
689    }
690
691    public function getColumnFill(): Keyword
692    {
693        return $this->expectKeyword('column-fill', 'balance');
694    }
695
696    // ========================================================================
697    // Effects
698    // ========================================================================
699
700    public function getTextShadow(): Value
701    {
702        return $this->values->get('text-shadow') ?? new Keyword('none');
703    }
704
705    public function getClipPath(): Value
706    {
707        return $this->values->get('clip-path') ?? new Keyword('none');
708    }
709
710    public function getFilter(): Value
711    {
712        return $this->values->get('filter') ?? new Keyword('none');
713    }
714
715    public function getTransform(): Value
716    {
717        return $this->values->get('transform') ?? new Keyword('none');
718    }
719
720    // ========================================================================
721    // Type-narrowing helpers
722    // ========================================================================
723
724    private function expectKeyword(string $prop, string $fallback): Keyword
725    {
726        $v = $this->values->get($prop);
727        return $v instanceof Keyword ? $v : new Keyword($fallback);
728    }
729
730    private function expectColor(string $prop, Color $fallback): Color
731    {
732        $v = $this->values->get($prop);
733        return $v instanceof Color ? $v : $fallback;
734    }
735
736    private function expectColorOrKeyword(string $prop, string $keywordFallback): Color|Keyword
737    {
738        $v = $this->values->get($prop);
739        return $v instanceof Color || $v instanceof Keyword ? $v : new Keyword($keywordFallback);
740    }
741
742    private function expectLength(string $prop, float $fallbackPx): Length
743    {
744        $v = $this->values->get($prop);
745        return $v instanceof Length ? $v : new Length($fallbackPx, \Phpdftk\Css\Value\LengthUnit::Px);
746    }
747
748    private function expectLengthOrKeyword(string $prop, string $keywordFallback): Length|Keyword
749    {
750        $v = $this->values->get($prop);
751        return $v instanceof Length || $v instanceof Keyword ? $v : new Keyword($keywordFallback);
752    }
753
754    private function expectLengthOrPercentage(string $prop, float $fallbackPx): Length|Percentage
755    {
756        $v = $this->values->get($prop);
757        return $v instanceof Length || $v instanceof Percentage
758            ? $v
759            : new Length($fallbackPx, \Phpdftk\Css\Value\LengthUnit::Px);
760    }
761
762    private function expectLengthPercentageOrKeyword(string $prop, string $keywordFallback): Length|Percentage|Keyword
763    {
764        $v = $this->values->get($prop);
765        return $v instanceof Length || $v instanceof Percentage || $v instanceof Keyword
766            ? $v
767            : new Keyword($keywordFallback);
768    }
769
770    private function expectNumber(string $prop, float $fallback): Number
771    {
772        $v = $this->values->get($prop);
773        return $v instanceof Number ? $v : new Number($fallback);
774    }
775}