Skip to content

Instantly share code, notes, and snippets.

@tehbeard
Last active July 21, 2024 21:50
Show Gist options
  • Save tehbeard/a54a2c77b4b8bf1af26920ce7a7f347b to your computer and use it in GitHub Desktop.
Save tehbeard/a54a2c77b4b8bf1af26920ce7a7f347b to your computer and use it in GitHub Desktop.
<?php
/**
* Copyright (c) 2024 tehbeard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class TypstService
{
public function __construct(private string $binPath = __DIR__ . "/../bin/typst", private string $tmpFilePath = "")
{
}
public function getBinaryPath(): string
{
return $this->binPath;
}
public function setBinaryPath(string $binPath): self
{
$this->binPath = $binPath;
return $this;
}
private ?string $windowsTmpFile = null;
private function resolveOutputFile(?string $output): string
{
if (PHP_OS_FAMILY === 'Windows' && is_null($output)) {
$tFile = tempnam($this->tmpFilePath, "typst");
if ($tFile === false) {
throw new \RuntimeException("Unable to allocate temporary file");
}
$this->windowsTmpFile = $tFile;
return $this->windowsTmpFile;
} elseif (is_null($output)) {
return "/dev/stdout";
} else {
return $output;
}
}
private ?string $lastError = null;
public function getLastError(): ?string
{
return $this->lastError;
}
/**
* @param resource|null $inputResource
*/
public function compile(
?string $inputFile = null,
?string $inputString = null,
$inputResource = null,
?string $outputFile = null,
?array $variables = null,
?string $cwd = null,
): string|false {
if (
sizeof(array_filter([$inputFile, $inputString, $inputResource], fn($n) => !is_null($n))) != 1
) {
throw new \RuntimeException("One of input file, string or resource fields MUST be supplied.");
}
if (!is_null($inputResource) && !is_resource($inputResource)) {
throw new \RuntimeException("inputResource is not a resource!");
}
$cmd = [ $this->getBinaryPath(), 'compile', '-f', 'pdf'];
if (is_array($variables)) {
foreach ($variables as $key => $val) {
if (!is_scalar($val)) {
throw new \RuntimeException("Invalid value for " . $key . ", expected scalar value (did you forgot to json_encode a value?)");
}
$cmd[] = sprintf("--input %s=%s", escapeshellarg((string)$key), escapeshellarg((string)$val));
}
}
$cmd[] = escapeshellarg(is_null($inputFile) ? "-" : $inputFile);
$cmd[] = escapeshellarg($this->resolveOutputFile($outputFile));
$descriptorspec = [
is_null($inputResource) ? ["pipe", "r"] : $inputResource, // stdin
["pipe", "w"], // stdout
["pipe", "w"] // stderr
];
$ph = proc_open(
join(" ", $cmd),
$descriptorspec,
$pipes,
$cwd
);
if (!is_resource($ph)) {
throw new \RuntimeException("Could not open typst process.");
}
// If input is the
if (is_null($inputFile) && is_null($inputResource) && !is_null($inputString)) {
fwrite($pipes[0], $inputString);
}
fclose($pipes[0]);
$pdfData = "";
if (is_null($outputFile)) {
$pdfData = stream_get_contents($pipes[1]);
}
$errorData = stream_get_contents($pipes[2]);
$code = proc_close($ph);
if ($code !== 0) {
$this->lastError = $errorData ? $errorData : "";
return false;
} else {
if (!is_null($this->windowsTmpFile)) {
$pdfData = file_get_contents($this->windowsTmpFile);
unlink($this->windowsTmpFile);
$this->windowsTmpFile = null;
}
return $pdfData;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment