From b307eda20e92f3fe37453794ae3349c6afb3e8af Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 26 Dec 2022 20:56:44 -0700 Subject: [PATCH] refactor: Convert to PNG without using files --- src/card.php | 86 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/src/card.php b/src/card.php index 08025205..98a41842 100644 --- a/src/card.php +++ b/src/card.php @@ -500,50 +500,84 @@ function convertSvgToPng(string $svg): string $svg = preg_replace("/(animation: currstreak[^;'\"]+)/m", "font-size: 28px;", $svg); $svg = preg_replace("/(\X*?)<\/a>/m", '\1', $svg); - // save svg to random file - $filename = uniqid(); - file_put_contents("$filename.svg", $svg); + // escape svg for shell + $svg = escapeshellarg($svg); + + // `--pipe`: read input from pipe (stdin) + // `--export-filename -`: write output to stdout + // `-w 495 -h 195`: set width and height of the output image + // `--export-type png`: set the output format to PNG + $cmd = "echo {$svg} | inkscape --pipe --export-filename - -w 495 -h 195 --export-type png"; // convert svg to png - $out = shell_exec("inkscape -w 495 -h 195 {$filename}.svg -o {$filename}.png"); // skipcq: PHP-A1009 - if ($out !== null) { - throw new Exception("Error converting SVG to PNG: $out"); + $png = shell_exec($cmd); // skipcq: PHP-A1009 + + // check if the conversion was successful + if (empty($png)) { + // `2>&1`: redirect stderr to stdout + $error = shell_exec("$cmd 2>&1"); // skipcq: PHP-A1009 + throw new Exception("Failed to convert SVG to PNG: {$error}", 500); } - // read png data and delete temporary files - $png = file_get_contents("{$filename}.png"); - unlink("{$filename}.svg"); - unlink("{$filename}.png"); + // return the generated png return $png; } /** - * Set headers and echo response based on type + * Return headers and response based on type * * @param string|array $output The stats (array) or error message (string) to display + * + * @return array The Content-Type header and the response body, and status code in case of an error */ -function renderOutput(string|array $output, int $responseCode = 200): void +function generateOutput(string|array $output): array { $requestedType = $_REQUEST["type"] ?? "svg"; - http_response_code($responseCode); // output JSON data if ($requestedType === "json") { - // set content type to JSON - header("Content-Type: application/json"); // generate array from output $data = gettype($output) === "string" ? ["error" => $output] : $output; - // output as JSON - echo json_encode($data); + return [ + "contentType" => "application/json", + "body" => json_encode($data), + ]; } - // output SVG or PNG card - else { - // set content type to SVG or PNG - header("Content-Type: image/" . ($requestedType === "png" ? "png" : "svg+xml")); - // render SVG card - $svg = gettype($output) === "string" ? generateErrorCard($output) : generateCard($output); - // output PNG if PNG is requested, otherwise output SVG - echo $requestedType === "png" ? convertSvgToPng($svg) : $svg; + // Generate SVG card + $svg = gettype($output) === "string" ? generateErrorCard($output) : generateCard($output); + // output PNG card + if ($requestedType === "png") { + try { + $png = convertSvgToPng($svg); + return [ + "contentType" => "image/png", + "body" => $png, + ]; + } catch (Exception $e) { + return [ + "contentType" => "image/svg+xml", + "status" => 500, + "body" => generateErrorCard($e->getMessage()), + ]; + } } - exit(); + // output SVG card + return [ + "contentType" => "image/svg+xml", + "body" => $svg, + ]; +} + +/** + * Set headers and output response + * + * @param string|array $output The Content-Type header and the response body + * @param int $responseCode The HTTP response code to send + */ +function renderOutput(string|array $output, int $responseCode = 200): void +{ + $response = generateOutput($output); + http_response_code($response["status"] ?? $responseCode); + header("Content-Type: {$response["contentType"]}"); + exit($response["body"]); }