Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
CascadedValues
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
10 / 10
15
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
 markDeclared
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wasDeclared
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDeclarations
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 has
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 get
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 all
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 customProperties
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 normalise
100.00% covered (success)
100.00%
1 / 1
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\Value;
8
9/**
10 * Per-element bag of resolved property → value pairs after the cascade
11 * has been applied. Phase 1D.3 ships this as a simple map; once the full
12 * `ComputedStyle` accessor surface lands (per `contracts.md`), this becomes
13 * the underlying storage backing those getters.
14 *
15 * `get($name)` falls back to the registry's initial value when the property
16 * was never set, so callers can treat the map as if every registered
17 * property is present.
18 */
19final class CascadedValues
20{
21    /** @var array<string, Value> */
22    private array $values = [];
23
24    /** @var array<string, Value> */
25    private array $customProperties = [];
26
27    /**
28     * Properties that a DECLARATION won the cascade for on this element,
29     * as opposed to arriving from inheritance or the registry's initial
30     * value.
31     *
32     * `has()` cannot answer that question for an INHERITED property —
33     * `color`, `white-space`, `border-spacing` and friends are present in
34     * every element's map no matter what the author wrote — and that is
35     * exactly what a presentational-hint mapping needs to know, since a
36     * hint must lose to any author declaration.
37     *
38     * @var array<string, true>
39     */
40    private array $declared = [];
41
42    public function __construct(private readonly PropertyRegistry $registry) {}
43
44    /** Mark `$name` as supplied by a declaration (see {@see wasDeclared()}). */
45    public function markDeclared(string $name): void
46    {
47        $this->declared[$this->normalise($name)] = true;
48    }
49
50    /**
51     * Did a declaration supply this property's value on this element?
52     * False for a value that came from inheritance or the initial value.
53     */
54    public function wasDeclared(string $name): bool
55    {
56        return isset($this->declared[$this->normalise($name)]);
57    }
58
59    /**
60     * Did ANY declaration supply a value on this element?
61     *
62     * False for a bag built purely from inheritance + initial values.
63     * Pseudo-element box generation uses this to tell "an author styled
64     * `::first-letter`" from "nobody did, and this bag is just the host's
65     * inherited text styles" — a distinction `has()` cannot make, since
66     * every registered property answers `has()` once inheritance has run.
67     */
68    public function hasDeclarations(): bool
69    {
70        return $this->declared !== [];
71    }
72
73    public function set(string $name, Value $value): void
74    {
75        $key = $this->normalise($name);
76        if (str_starts_with($key, '--')) {
77            $this->customProperties[$key] = $value;
78            return;
79        }
80        $this->values[$key] = $value;
81    }
82
83    public function has(string $name): bool
84    {
85        $key = $this->normalise($name);
86        if (str_starts_with($key, '--')) {
87            return isset($this->customProperties[$key]);
88        }
89        return isset($this->values[$key]);
90    }
91
92    public function get(string $name): ?Value
93    {
94        $key = $this->normalise($name);
95        if (str_starts_with($key, '--')) {
96            return $this->customProperties[$key] ?? null;
97        }
98        if (isset($this->values[$key])) {
99            return $this->values[$key];
100        }
101        return $this->registry->get($key)?->initial;
102    }
103
104    /** @return array<string, Value> standard properties only */
105    public function all(): array
106    {
107        return $this->values;
108    }
109
110    /** @return array<string, Value> declared custom properties only */
111    public function customProperties(): array
112    {
113        return $this->customProperties;
114    }
115
116    /**
117     * Custom-property names are case-sensitive per the spec; standard
118     * properties are lower-cased.
119     */
120    private function normalise(string $name): string
121    {
122        return str_starts_with($name, '--') ? $name : strtolower($name);
123    }
124}