ship the side art as a cached stylesheet so cached loads paint in one frame
Deploy / deploy (push) Successful in 3s
Deploy / deploy (push) Successful in 3s
This commit is contained in:
+58
-38
@@ -1,9 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* ANSI art (a .ans file you can `cat` in a terminal) to HTML for the side
|
||||
* panels. Understands foreground colors: truecolor (38;2;r;g;b), 256-color
|
||||
* (38;5;n), the 16 basic ones, and resets; everything else is ignored.
|
||||
* $mirror flips it left-right, swapping /\ () <> [] {} so shapes still read.
|
||||
* ANSI art (a .ans file you can `cat` in a terminal) for the side panels.
|
||||
* Understands foreground colors: truecolor (38;2;r;g;b), 256-color (38;5;n),
|
||||
* the 16 basic ones, and resets; everything else is ignored. The right panel
|
||||
* is the left mirrored, swapping /\ () <> [] {} so shapes still read.
|
||||
*
|
||||
* The art ships as a stylesheet (sideart.php), not HTML: one full-size text
|
||||
* layer per color, each the `content` of a ::before on an empty <b>, stacked
|
||||
* by style.css. It's render-blocking and cached, so the first paint already
|
||||
* has it; inline, it made the page too big for one network round trip and
|
||||
* Firefox painted the page half-arrived (a dark empty flash) on cached loads.
|
||||
*/
|
||||
|
||||
function ansi_color(array $p, ?string $cur): ?string
|
||||
@@ -38,35 +44,55 @@ function ansi_color(array $p, ?string $cur): ?string
|
||||
return $cur;
|
||||
}
|
||||
|
||||
/** ansi_html(), cached on disk until the .ans file changes (parsing costs ~30 ms a side). */
|
||||
function ansi_html_cached(string $file, bool $mirror = false): string
|
||||
/**
|
||||
* The stylesheet for both panels, cached on disk until the .ans file (or this
|
||||
* file) changes. Returns [path to the css, layers on the left, on the right].
|
||||
*/
|
||||
function ansi_css_cached(string $file): array
|
||||
{
|
||||
$dir = (defined('DATA_DIR') ? DATA_DIR : sys_get_temp_dir()) . '/art-cache';
|
||||
$base = $dir . '/' . md5($file . '|' . (int) $mirror);
|
||||
$key = $base . '-' . filemtime($file) . '-layers.html'; // bump the suffix when the markup changes
|
||||
if (is_file($key)) {
|
||||
return (string) file_get_contents($key);
|
||||
$base = $dir . '/' . md5($file);
|
||||
$key = $base . '-' . filemtime($file) . '-' . filemtime(__FILE__);
|
||||
if (!is_file("$key.css") || !is_file("$key.json")) {
|
||||
@mkdir($dir, 0755, true);
|
||||
array_map('unlink', glob($base . '-*') ?: []); // every deploy bumps the mtimes
|
||||
$css = '';
|
||||
$counts = [];
|
||||
foreach (['left' => false, 'right' => true] as $side => $mirror) {
|
||||
$layers = ansi_layers($file, $mirror);
|
||||
$counts[$side] = count($layers);
|
||||
foreach ($layers as $i => [$color, $text]) {
|
||||
$css .= ".side-$side > b:nth-child(" . ($i + 1) . ')::before{content:' . ansi_css_string($text)
|
||||
. ($color !== '' ? ";color:#$color" : '') . "}\n";
|
||||
}
|
||||
}
|
||||
foreach (["$key.css" => $css, "$key.json" => json_encode($counts)] as $path => $data) {
|
||||
$tmp = $path . '.' . getmypid();
|
||||
if (@file_put_contents($tmp, $data) !== false) {
|
||||
rename($tmp, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
$html = ansi_html($file, $mirror);
|
||||
@mkdir($dir, 0755, true);
|
||||
array_map('unlink', glob($base . '-*.html') ?: []); // every deploy bumps the mtime
|
||||
$tmp = $key . '.' . getmypid();
|
||||
if (@file_put_contents($tmp, $html) !== false) {
|
||||
rename($tmp, $key);
|
||||
}
|
||||
return $html;
|
||||
return ["$key.css", ...array_values((array) json_decode((string) @file_get_contents("$key.json"), true))];
|
||||
}
|
||||
|
||||
function ansi_html(string $file, bool $mirror = false): string
|
||||
/** A CSS string literal; newlines become \A (the space after it ends the escape). */
|
||||
function ansi_css_string(string $text): string
|
||||
{
|
||||
return '"' . str_replace(['\\', '"', "\n"], ['\\\\', '\\"', '\\A '], $text) . '"';
|
||||
}
|
||||
|
||||
/** The art as [[hex color or '', text], ...]: one full-size layer per color. */
|
||||
function ansi_layers(string $file, bool $mirror = false): array
|
||||
{
|
||||
$raw = (string) file_get_contents($file);
|
||||
if (!preg_match('/[\x80-\xff]/', $raw)) {
|
||||
$html = ansi_html_bytes($raw, $mirror);
|
||||
if ($html !== null) {
|
||||
return $html;
|
||||
$layers = ansi_layers_bytes($raw, $mirror);
|
||||
if ($layers !== null) {
|
||||
return $layers;
|
||||
}
|
||||
}
|
||||
return ansi_html_cells($raw, $mirror);
|
||||
return ansi_layers_cells($raw, $mirror);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +138,7 @@ function ansi_parse_bytes(string $raw): ?array
|
||||
* 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
|
||||
function ansi_layers_bytes(string $raw, bool $mirror): ?array
|
||||
{
|
||||
static $from = null, $to = null; // byte 0 -> 0xff, anything else -> 0
|
||||
$from ??= implode(array_map('chr', range(0, 255)));
|
||||
@@ -139,21 +165,19 @@ function ansi_html_bytes(string $raw, bool $mirror): ?string
|
||||
$blank = implode("\n", array_fill(0, count($chars), str_repeat(' ', $width)));
|
||||
$n = strlen($all);
|
||||
|
||||
$html = '';
|
||||
$layers = [];
|
||||
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
|
||||
if (trim($layer) !== '') { // skip colors that only touch blanks
|
||||
$layers[] = [(string) $color, $layer];
|
||||
}
|
||||
$style = $color !== '' ? " style=\"color:#$color\"" : '';
|
||||
$html .= "<span$style>" . htmlspecialchars($layer) . '</span>';
|
||||
}
|
||||
return $html;
|
||||
return $layers;
|
||||
}
|
||||
|
||||
/** General path: a [char, color] cell at a time, so multibyte characters work. */
|
||||
function ansi_html_cells(string $raw, bool $mirror): string
|
||||
function ansi_layers_cells(string $raw, bool $mirror): array
|
||||
{
|
||||
static $flip = ['/' => '\\', '\\' => '/', '(' => ')', ')' => '(', '<' => '>', '>' => '<',
|
||||
'[' => ']', ']' => '[', '{' => '}', '}' => '{'];
|
||||
@@ -183,9 +207,6 @@ function ansi_html_cells(string $raw, bool $mirror): string
|
||||
}
|
||||
unset($cells);
|
||||
|
||||
// One full-size text layer per color, holding only that color's
|
||||
// characters; CSS stacks them. A few dozen elements instead of a span per
|
||||
// color run (thousands), which is what made the art slow to lay out.
|
||||
$colors = [];
|
||||
foreach ($lines as $cells) {
|
||||
foreach ($cells as [$ch, $color]) {
|
||||
@@ -194,7 +215,7 @@ function ansi_html_cells(string $raw, bool $mirror): string
|
||||
}
|
||||
}
|
||||
}
|
||||
$html = '';
|
||||
$layers = [];
|
||||
foreach (array_keys($colors) as $color) {
|
||||
$rows = [];
|
||||
foreach ($lines as $cells) {
|
||||
@@ -204,8 +225,7 @@ function ansi_html_cells(string $raw, bool $mirror): string
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
$style = $color !== '' ? " style=\"color:#$color\"" : '';
|
||||
$html .= "<span$style>" . htmlspecialchars(implode("\n", $rows)) . '</span>';
|
||||
$layers[] = [(string) $color, implode("\n", $rows)];
|
||||
}
|
||||
return $html;
|
||||
return $layers;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user