art.php: bitwise byte-string fast path and memoized color codes (105ms -> 21ms uncached)
Deploy / deploy (push) Successful in 37s
Deploy / deploy (push) Successful in 37s
This commit is contained in:
+79
-1
@@ -58,11 +58,89 @@ function ansi_html_cached(string $file, bool $mirror = false): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ansi_html(string $file, bool $mirror = false): string
|
function ansi_html(string $file, bool $mirror = false): string
|
||||||
|
{
|
||||||
|
$raw = (string) file_get_contents($file);
|
||||||
|
if (!preg_match('/[\x80-\xff]/', $raw)) {
|
||||||
|
$html = ansi_html_bytes($raw, $mirror);
|
||||||
|
if ($html !== null) {
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ansi_html_cells($raw, $mirror);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast path for plain-ASCII art (one byte per cell): each line becomes a
|
||||||
|
* string of characters plus a parallel string of color indices, and every
|
||||||
|
* color layer is cut out of the whole art with bitwise string ops, which run
|
||||||
|
* in C. Returns null if there are too many colors to index in a byte.
|
||||||
|
*/
|
||||||
|
function ansi_html_bytes(string $raw, bool $mirror): ?string
|
||||||
|
{
|
||||||
|
static $from = null, $to = null; // byte 0 -> 0xff, anything else -> 0
|
||||||
|
$from ??= implode(array_map('chr', range(0, 255)));
|
||||||
|
$to ??= "\xff" . str_repeat("\0", 255);
|
||||||
|
|
||||||
|
static $memo = [];
|
||||||
|
$index = ['' => 0]; // color hex => byte (0 = uncolored)
|
||||||
|
$chars = [];
|
||||||
|
$masks = [];
|
||||||
|
foreach (explode("\n", rtrim($raw, "\n")) as $line) {
|
||||||
|
$c = '';
|
||||||
|
$m = '';
|
||||||
|
$cur = null;
|
||||||
|
foreach (preg_split('/(\e\[[0-9;]*m)/', $line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $part) {
|
||||||
|
if ($part[0] === "\e") {
|
||||||
|
// a handful of distinct codes repeated thousands of times: decode each once
|
||||||
|
$cur = $memo[$part . '|' . $cur] ??= ansi_color(explode(';', substr($part, 2, -1)), $cur);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$k = $index[$cur ?? ''] ??= count($index);
|
||||||
|
$c .= $part;
|
||||||
|
$m .= str_repeat(chr($k), strlen($part));
|
||||||
|
}
|
||||||
|
$chars[] = $c;
|
||||||
|
$masks[] = $m;
|
||||||
|
}
|
||||||
|
if (count($index) > 250) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$width = max(array_map('strlen', $chars) ?: [0]);
|
||||||
|
foreach ($chars as $i => $c) {
|
||||||
|
$chars[$i] = str_pad($c, $width);
|
||||||
|
$masks[$i] = str_pad($masks[$i], $width, "\0");
|
||||||
|
if ($mirror) {
|
||||||
|
$chars[$i] = strtr(strrev($chars[$i]), '/\\()<>[]{}', '\\/)(><][}{');
|
||||||
|
$masks[$i] = strrev($masks[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the whole art as one string; 0xfe in the mask marks the line breaks
|
||||||
|
$all = implode("\n", $chars);
|
||||||
|
$mask = implode("\xfe", $masks);
|
||||||
|
$blank = implode("\n", array_fill(0, count($chars), str_repeat(' ', $width)));
|
||||||
|
$n = strlen($all);
|
||||||
|
|
||||||
|
$html = '';
|
||||||
|
foreach ($index as $color => $k) {
|
||||||
|
$sel = strtr($mask ^ str_repeat(chr($k), $n), $from, $to); // 0xff where this color, else 0
|
||||||
|
$layer = ($all & $sel) | ($blank & ~$sel);
|
||||||
|
if (trim($layer) === '') {
|
||||||
|
continue; // only blanks in this color
|
||||||
|
}
|
||||||
|
$style = $color !== '' ? " style=\"color:#$color\"" : '';
|
||||||
|
$html .= "<span$style>" . htmlspecialchars($layer) . '</span>';
|
||||||
|
}
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** General path: a [char, color] cell at a time, so multibyte characters work. */
|
||||||
|
function ansi_html_cells(string $raw, bool $mirror): string
|
||||||
{
|
{
|
||||||
static $flip = ['/' => '\\', '\\' => '/', '(' => ')', ')' => '(', '<' => '>', '>' => '<',
|
static $flip = ['/' => '\\', '\\' => '/', '(' => ')', ')' => '(', '<' => '>', '>' => '<',
|
||||||
'[' => ']', ']' => '[', '{' => '}', '}' => '{'];
|
'[' => ']', ']' => '[', '{' => '}', '}' => '{'];
|
||||||
$lines = [];
|
$lines = [];
|
||||||
foreach (explode("\n", rtrim((string) file_get_contents($file), "\n")) as $line) {
|
foreach (explode("\n", rtrim($raw, "\n")) as $line) {
|
||||||
// split into [char, color] cells
|
// split into [char, color] cells
|
||||||
$cells = [];
|
$cells = [];
|
||||||
$cur = null;
|
$cur = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user