Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
CounterStyleDefinition
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
3 / 3
9
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
 effectiveRange
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 usesNegativeSign
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\HtmlToPdf\Layout;
6
7/**
8 * One CSS Counter Styles 3 counter style — the resolved form of an
9 * `@counter-style` rule (§4) or of a predefined style (§6, §7).
10 *
11 * `system: extends …` is resolved away before a definition is built: the
12 * extending style copies every descriptor it did not itself specify, so by
13 * the time the formatter sees a definition it is always one of the five
14 * concrete systems.
15 */
16final class CounterStyleDefinition
17{
18    public const string CYCLIC = 'cyclic';
19    public const string NUMERIC = 'numeric';
20    public const string ALPHABETIC = 'alphabetic';
21    public const string SYMBOLIC = 'symbolic';
22    public const string ADDITIVE = 'additive';
23    public const string FIXED = 'fixed';
24
25    /**
26     * Not a CSS `system` value — the internal marker for the East Asian
27     * styles of CSS Counter Styles 3 §7.1 (Chinese / Japanese / Korean,
28     * informal and formal).
29     *
30     * The spec presents those styles as `additive`, but a greedy additive
31     * pass cannot produce the Chinese zero filler: 101 must be 一百零一,
32     * and additive gives 一百一. They are generated place-by-place instead.
33     */
34    public const string CJK_IDEOGRAPHIC = 'cjk-ideographic';
35
36    /** Never drop a `1` before a place multiplier — 10 is 壹拾. */
37    public const string ELIDE_NONE = 'none';
38    /** Always drop it — 10 is 十, 100 is 百, 1000 is 千. */
39    public const string ELIDE_ALL = 'all';
40    /** Drop it only in a leading tens place — 10 is 十 but 100 is 一百. */
41    public const string ELIDE_LEADING_TENS = 'leading-tens';
42
43    /**
44     * @param list<string>                $symbols         `symbols` descriptor, in order.
45     * @param list<array{0: int, 1: string}> $additiveSymbols `additive-symbols`, weight-descending.
46     */
47    public function __construct(
48        public readonly string $system,
49        public readonly array $symbols = [],
50        public readonly array $additiveSymbols = [],
51        public readonly string $negativePrefix = '-',
52        public readonly string $negativeSuffix = '',
53        public readonly string $prefix = '',
54        /**
55         * CSS Counter Styles 3 §4.7 — the initial `suffix` is a full stop
56         * followed by a space, which is why every list marker in the CSS 2.1
57         * reference files reads `1. ` and not `1.`.
58         */
59        public readonly string $suffix = '. ',
60        /** Null means `range: auto`, which each system resolves for itself. */
61        public readonly ?int $rangeMin = null,
62        public readonly ?int $rangeMax = null,
63        public readonly int $padLength = 0,
64        public readonly string $padSymbol = '',
65        public readonly ?string $fallback = 'decimal',
66        /** `system: fixed <first>` — the counter value the first symbol represents. */
67        public readonly int $fixedFirst = 1,
68        /**
69         * {@see CJK_IDEOGRAPHIC} only — the tens / hundreds / thousands place
70         * markers (十 百 千, or their formal 拾 佰 仟 variants).
71         *
72         * @var list<string>
73         */
74        public readonly array $cjkMultipliers = [],
75        /** {@see CJK_IDEOGRAPHIC} only — one of the `ELIDE_*` constants. */
76        public readonly string $cjkElideOne = self::ELIDE_NONE,
77        /**
78         * {@see CJK_IDEOGRAPHIC} only — insert the zero digit where an
79         * interior place was skipped. True for Chinese, false for Japanese
80         * and Korean: 1005 is 一千零五 but 千五.
81         */
82        public readonly bool $cjkZeroFiller = false,
83    ) {}
84
85    /**
86     * CSS Counter Styles 3 §4.4 — the `range: auto` resolution. Each system
87     * knows which counter values it can represent at all; outside that the
88     * style must defer to its `fallback`.
89     *
90     * @return array{0: int, 1: int}
91     */
92    public function effectiveRange(): array
93    {
94        if ($this->rangeMin !== null || $this->rangeMax !== null) {
95            return [$this->rangeMin ?? PHP_INT_MIN, $this->rangeMax ?? PHP_INT_MAX];
96        }
97        return match ($this->system) {
98            self::ALPHABETIC, self::SYMBOLIC => [1, PHP_INT_MAX],
99            self::ADDITIVE => [0, PHP_INT_MAX],
100            default => [PHP_INT_MIN, PHP_INT_MAX],
101        };
102    }
103
104    /**
105     * Does this system spell negative values with the `negative` descriptor?
106     *
107     * Cyclic and fixed index their symbol list by the counter value itself,
108     * so a negative value is a position in the cycle rather than a signed
109     * magnitude; every other system represents magnitudes and needs the sign.
110     */
111    public function usesNegativeSign(): bool
112    {
113        return $this->system !== self::CYCLIC && $this->system !== self::FIXED;
114    }
115}