Connexion
FR
apps/caramail-chat / src / Chat / Styled.php · commit 6eebcdb
<?phpnamespace App\Chat;/** * The coded text of the 2002 applet (net.spray.chat.a.g / a.b): a line is segments joined by "\r", each one * "FF" (font) + "CC" (colour) + text. Fonts 00..03 = 10, 12, 14, 16 pt, 04..07 the same in bold; a negative font * ("-1") = its absolute value, without smileys (the nicknames). Colour = an index of PALETTE; "11" is the rainbow * and font "03" the wave: both are spread one segment per character when the line arrives (spread()). */final class Styled{    /** L[] of the applet: the text colours (00..10), then the hover, selection and tooltip colours. */    public const PALETTE = [        '#000000', '#0000ff', '#ff0000', '#ff00c8', '#ff9d00', '#646464', '#1a1a8c', '#7d0000', '#147300', '#6e265f',        '#9b9b14', '#eeeeff', '#000080', '#00c0ff', '#ffc0c0', '#ffffc0', '#ff6060', '#74c900', '#a4a4a4', '#ffdd80',    ];    /** The colours a member can choose for his sentences (index => colour). 11 = rainbow. */    public const CHOICES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];    public const RAINBOW = 11;    public const WAVE_FONT = 3;    public const SIZES = [10, 12, 14, 16];    private const WAVE = ['00', '01', '02', '01'];    private const RAINBOW_CYCLE = ['03', '09', '07', '06', '01', '08', '10', '04', '02'];    /** "FFCC" + text: what a sentence is sent as (70 / 72 frames). */    public static function encode(string $text, int $color = 0, int $font = 1, bool $bold = false): string    {        $font = max(0, min(3, $font)) + ($bold ? 4 : 0);        return sprintf('%02d%02d', $font, max(0, min(11, $color))) . self::strip($text);    }    /** A typed text never carries the separators of the format (tab, line ends). */    public static function strip(string $text): string    {        return str_replace(["\t", "\r", "\n"], ' ', $text);    }    /**     * The segments of a coded line.     *     * @return list<array{font: int, size: int, bold: bool, color: int, text: string, smileys: bool}>     */    public static function segments(string $coded, bool $boldLine = false): array    {        $out = [];        foreach (explode("\r", $coded) as $part) {            if (!preg_match('/^(-\d|\d\d)(\d\d)/', $part)) {                if ($part !== '') {                    $out[] = self::segment(1, 0, $part, true, $boldLine);                }                continue;            }            $fontCode = (int)substr($part, 0, 2);            $color = (int)substr($part, 2, 2);            $out[] = self::segment($fontCode, $color, (string)substr($part, 4), $fontCode >= 0, $boldLine);        }        return $out;    }    /** @return array{font: int, size: int, bold: bool, color: int, text: string, smileys: bool} */    private static function segment(int $fontCode, int $color, string $text, bool $smileys, bool $boldLine): array    {        $font = abs($fontCode) % 8;        return [            'font' => $font % 4,            'size' => self::SIZES[$font % 4],            'bold' => $font >= 4 || $boldLine,            'color' => ($color >= 0 && $color <= 10) ? $color : 0,            'text' => $text,            'smileys' => $smileys,        ];    }    /**     * The wave (font 03) and the rainbow (colour 11): the text after the first four characters becomes one segment     * per character (a smiley whole), fonts / colours cycling from a random start, a space getting "00" (as the applet did once, when a     * line arrived). Any other line is given back as it is.     */    public static function spread(string $coded, ?\Closure $random = null): string    {        if (strlen($coded) < 4) {            return $coded;        }        $font = substr($coded, 0, 2);        $color = substr($coded, 2, 2);        $wave = $font === '03';        $rainbow = $color === '11';        if (!$wave && !$rainbow) {            return $coded;        }        $random ??= static fn (int $n): int => random_int(0, $n - 1);        $f = $random(4);        $c = $random(9);        $nonSpace = 0;        $parts = [];        // a smiley stays whole (":-)", "#13", "@05"): one unit of the cycle, else its characters would be split into        // segments of their own and it would show as text        $units = [];        foreach (Smileys::parse((string)substr($coded, 4)) as $piece) {            if (is_array($piece)) {                $units[] = $piece['source'];            } else {                array_push($units, ...mb_str_split($piece));            }        }        foreach ($units as $char) {            if ($char === ' ') {                $parts[] = ($wave ? '00' : $font) . ($rainbow ? '00' : $color) . ' ';                continue;            }            $parts[] = ($wave ? self::WAVE[($f + intdiv($nonSpace, 2)) % 4] : $font) . ($rainbow ? self::RAINBOW_CYCLE[($c + $nonSpace) % 9] : $color) . $char;            $nonSpace++;        }        return implode("\r", $parts);    }    /** The plain text of a coded line (search, tests, logs). */    public static function plain(string $coded): string    {        return implode('', array_map(static fn (array $s) => $s['text'], self::segments($coded)));    }}