Passed
Push — master ( 200d88...9b81d7 )
by Alec
02:43 queued 15s
created

AAnsiStyleConverter::convert4()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 6
rs 10
c 1
b 0
f 0
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,
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on AlecRabbit\Spinner\Contract\StyleMode.
Loading history...
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),
0 ignored issues
show
Bug introduced by
Are you sure the usage of AlecRabbit\Spinner\Helpe...lor($color, $styleMode) targeting AlecRabbit\Spinner\Helpe...erter::assertIntColor() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
It seems like $color can also be of type string; however, parameter $color of AlecRabbit\Spinner\Helpe...erter::assertIntColor() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

54
            is_int($color) => Asserter::assertIntColor(/** @scrutinizer ignore-type */ $color, $styleMode),
Loading history...
55
            is_string($color) => Asserter::assertHexStringColor($color),
0 ignored issues
show
Bug introduced by
Are you sure the usage of AlecRabbit\Spinner\Helpe...tHexStringColor($color) targeting AlecRabbit\Spinner\Helpe...:assertHexStringColor() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on AlecRabbit\Spinner\Contract\StyleMode.
Loading history...
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
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on AlecRabbit\Spinner\Contract\StyleMode.
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $index of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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
}