1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
// 23.03.23 |
5
|
|
|
namespace AlecRabbit\Spinner\Core\Color\A; |
6
|
|
|
|
7
|
|
|
use AlecRabbit\Spinner\Contract\IAnsiStyleConverter; |
8
|
|
|
use AlecRabbit\Spinner\Contract\StyleMode; |
9
|
|
|
use AlecRabbit\Spinner\Core\Color\Ansi8Color; |
10
|
|
|
use AlecRabbit\Spinner\Exception\InvalidArgumentException; |
11
|
|
|
use AlecRabbit\Spinner\Exception\LogicException; |
12
|
|
|
use AlecRabbit\Spinner\Helper\Asserter; |
13
|
|
|
use AlecRabbit\Spinner\Mixin\AnsiColorTableTrait; |
14
|
|
|
|
15
|
|
|
abstract class AAnsiStyleConverter implements IAnsiStyleConverter |
16
|
|
|
{ |
17
|
|
|
use AnsiColorTableTrait; |
18
|
|
|
|
19
|
|
|
public function __construct( |
20
|
|
|
protected StyleMode $styleMode, |
21
|
|
|
) { |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** @inheritdoc */ |
25
|
|
|
public function ansiCode(int|string $color, StyleMode $styleMode): string |
26
|
|
|
{ |
27
|
|
|
$styleMode = $styleMode->lowest($this->styleMode); |
28
|
|
|
|
29
|
|
|
$this->assertColor($color, $styleMode); |
30
|
|
|
|
31
|
|
|
$color24 = (string)$color; |
32
|
|
|
|
33
|
|
|
return match ($styleMode) { |
34
|
|
|
StyleMode::ANSI4 => $this->convert4($color, $styleMode), |
35
|
|
|
StyleMode::ANSI8 => $this->convert8($color, $styleMode), |
36
|
|
|
StyleMode::ANSI24 => $this->convert24($color24, $styleMode), |
37
|
|
|
default => throw new LogicException( |
38
|
|
|
sprintf( |
39
|
|
|
'%s::%s: Unable to convert "%s" to ansi code.', |
40
|
|
|
StyleMode::class, |
41
|
|
|
$styleMode->name, |
|
|
|
|
42
|
|
|
$color |
43
|
|
|
) |
44
|
|
|
), |
45
|
|
|
}; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @throws InvalidArgumentException |
50
|
|
|
*/ |
51
|
|
|
protected function assertColor(int|string $color, StyleMode $styleMode): void |
52
|
|
|
{ |
53
|
|
|
match (true) { |
54
|
|
|
is_int($color) => Asserter::assertIntColor($color, $styleMode), |
|
|
|
|
55
|
|
|
is_string($color) => Asserter::assertHexStringColor($color), |
|
|
|
|
56
|
|
|
}; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @throws InvalidArgumentException |
61
|
|
|
*/ |
62
|
|
|
protected function convert4(int|string $color, StyleMode $styleMode): string |
63
|
|
|
{ |
64
|
|
|
if (is_int($color)) { |
65
|
|
|
return (string)$color; |
66
|
|
|
} |
67
|
|
|
return $this->convertFromHexToAnsiColorCode($color, $styleMode); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @throws InvalidArgumentException |
72
|
|
|
*/ |
73
|
|
|
protected function convertFromHexToAnsiColorCode(string $hexColor, StyleMode $styleMode): string |
74
|
|
|
{ |
75
|
|
|
$hexColor = str_replace('#', '', $hexColor); |
76
|
|
|
|
77
|
|
|
if (3 === strlen($hexColor)) { |
78
|
|
|
$hexColor = $hexColor[0] . $hexColor[0] . $hexColor[1] . $hexColor[1] . $hexColor[2] . $hexColor[2]; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
if (6 !== strlen($hexColor)) { |
82
|
|
|
throw new InvalidArgumentException(sprintf('Invalid "#%s" color.', $hexColor)); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
$color = hexdec($hexColor); |
86
|
|
|
|
87
|
|
|
$r = ($color >> 16) & 255; |
88
|
|
|
$g = ($color >> 8) & 255; |
89
|
|
|
$b = $color & 255; |
90
|
|
|
|
91
|
|
|
return match ($styleMode) { |
92
|
|
|
StyleMode::ANSI4 => (string)$this->convertFromRGB($r, $g, $b, $styleMode), |
93
|
|
|
StyleMode::ANSI8 => '8;5;' . ((string)$this->convertFromRGB($r, $g, $b, $styleMode)), |
94
|
|
|
StyleMode::ANSI24 => sprintf('8;2;%d;%d;%d', $r, $g, $b), |
95
|
|
|
StyleMode::NONE => throw new InvalidArgumentException( |
96
|
|
|
sprintf( |
97
|
|
|
'Hex color cannot be converted to %s.', |
98
|
|
|
$styleMode->name |
|
|
|
|
99
|
|
|
) |
100
|
|
|
), |
101
|
|
|
}; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @throws InvalidArgumentException |
106
|
|
|
*/ |
107
|
|
|
protected function convertFromRGB(int $r, int $g, int $b, StyleMode $styleMode): int |
108
|
|
|
{ |
109
|
|
|
return match ($styleMode) { |
110
|
|
|
StyleMode::ANSI4 => $this->degradeHexColorToAnsi4($r, $g, $b), |
111
|
|
|
StyleMode::ANSI8 => $this->degradeHexColorToAnsi8($r, $g, $b), |
112
|
|
|
default => throw new InvalidArgumentException( |
113
|
|
|
sprintf( |
114
|
|
|
'RGB cannot be converted to %s.', |
115
|
|
|
$styleMode->name |
|
|
|
|
116
|
|
|
) |
117
|
|
|
) |
118
|
|
|
}; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
protected function degradeHexColorToAnsi4(int $r, int $g, int $b): int |
122
|
|
|
{ |
123
|
|
|
/** @psalm-suppress TypeDoesNotContainType */ |
124
|
|
|
if (0 === round($this->getSaturation($r, $g, $b) / 50)) { // 0 === round(... - it is a hack |
125
|
|
|
return 0; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return (int)((round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255)); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
protected function getSaturation(int $r, int $g, int $b): int |
132
|
|
|
{ |
133
|
|
|
$rf = $r / 255; |
134
|
|
|
$gf = $g / 255; |
135
|
|
|
$bf = $b / 255; |
136
|
|
|
$v = max($rf, $gf, $bf); |
137
|
|
|
|
138
|
|
|
if (0 === $diff = $v - min($rf, $gf, $bf)) { |
139
|
|
|
return 0; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
return (int)($diff * 100 / $v); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license). |
147
|
|
|
*/ |
148
|
|
|
protected function degradeHexColorToAnsi8(int $r, int $g, int $b): int |
149
|
|
|
{ |
150
|
|
|
if ($r === $g && $g === $b) { |
151
|
|
|
if ($r < 8) { |
152
|
|
|
return 16; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
if ($r > 248) { |
156
|
|
|
return 231; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return (int)round(($r - 8) / 247 * 24) + 232; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return 16 + |
163
|
|
|
(36 * (int)round($r / 255 * 5)) + |
164
|
|
|
(6 * (int)round($g / 255 * 5)) + |
165
|
|
|
(int)round($b / 255 * 5); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @throws InvalidArgumentException |
170
|
|
|
*/ |
171
|
|
|
protected function convert8(int|string $color, StyleMode $styleMode): string |
172
|
|
|
{ |
173
|
|
|
if (is_int($color)) { |
174
|
|
|
return '8;5;' . $color; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$index = Ansi8Color::getIndex($color); |
178
|
|
|
|
179
|
|
|
if ($index) { |
|
|
|
|
180
|
|
|
return '8;5;' . $index; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
return $this->convertFromHexToAnsiColorCode($color, $styleMode); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @throws InvalidArgumentException |
188
|
|
|
*/ |
189
|
|
|
protected function convert24(string $color, StyleMode $styleMode): string |
190
|
|
|
{ |
191
|
|
|
return $this->convertFromHexToAnsiColorCode($color, $styleMode); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
public function isDisabled(): bool |
195
|
|
|
{ |
196
|
|
|
return !$this->styleMode->isStylingEnabled(); |
197
|
|
|
} |
198
|
|
|
} |