setPageSize('A4')->setOrientation('portrait');
* $client->convertUrlToFile('https://example.com', 'example.pdf');
*/
namespace Pdfly;
class Error extends \Exception
{
}
class HtmlToPdfClient
{
private array $fields = [];
private int $timeout = 120;
public function __construct(private string $apiKey, private string $endpoint = 'https://pdfly.host/v1/convert')
{
}
// ---- Conversions ------------------------------------------------------
/** @return string PDF bytes */
public function convertUrl(string $url): string
{
return $this->request(['url' => $url]);
}
public function convertUrlToFile(string $url, string $filePath): void
{
$this->save($this->convertUrl($url), $filePath);
}
/** @return string PDF bytes */
public function convertString(string $html): string
{
return $this->request(['text' => $html]);
}
public function convertStringToFile(string $html, string $filePath): void
{
$this->save($this->convertString($html), $filePath);
}
/** Converts a local .html file (asset paths inside it must be absolute URLs). */
public function convertFile(string $htmlFilePath): string
{
$html = @file_get_contents($htmlFilePath);
if ($html === false) {
throw new Error("Cannot read $htmlFilePath", 0);
}
return $this->convertString($html);
}
public function convertFileToFile(string $htmlFilePath, string $filePath): void
{
$this->save($this->convertFile($htmlFilePath), $filePath);
}
/** Sends the PDF straight to the browser. */
public function convertUrlToBrowser(string $url, string $filename = 'document.pdf', bool $download = false): void
{
$this->output($this->convertUrl($url), $filename, $download);
}
public function convertStringToBrowser(string $html, string $filename = 'document.pdf', bool $download = false): void
{
$this->output($this->convertString($html), $filename, $download);
}
// ---- Options (all return $this, so they can be chained) ---------------
/** A0–A6, Letter, Legal, Tabloid, Ledger. Default A4. */
public function setPageSize(string $size): static { return $this->set('page_size', $size); }
/** e.g. "210mm", "8.5in" */
public function setPageWidth(string $width): static { return $this->set('page_width', $width); }
public function setPageHeight(string $height): static { return $this->set('page_height', $height); }
public function setPageDimensions(string $width, string $height): static { return $this->setPageWidth($width)->setPageHeight($height); }
/** "portrait" or "landscape" */
public function setOrientation(string $orientation): static { return $this->set('orientation', $orientation); }
/** e.g. "10mm", "0.5in". Default 0.4in. */
public function setMarginTop(string $margin): static { return $this->set('margin_top', $margin); }
public function setMarginRight(string $margin): static { return $this->set('margin_right', $margin); }
public function setMarginBottom(string $margin): static { return $this->set('margin_bottom', $margin); }
public function setMarginLeft(string $margin): static { return $this->set('margin_left', $margin); }
public function setPageMargins(string $top, string $right, string $bottom, string $left): static
{
return $this->setMarginTop($top)->setMarginRight($right)->setMarginBottom($bottom)->setMarginLeft($left);
}
public function setNoMargins(bool $value = true): static { return $this->set('no_margins', $value); }
/** HTML for every page header/footer. Use and . */
public function setHeaderHtml(string $html): static { return $this->set('header_html', $html); }
public function setFooterHtml(string $html): static { return $this->set('footer_html', $html); }
public function setNoBackground(bool $value = true): static { return $this->set('no_background', $value); }
/** Use the page's @media print CSS instead of screen CSS. */
public function setUsePrintMedia(bool $value = true): static { return $this->set('use_print_media', $value); }
/** Wait this many milliseconds after load before printing (max 30000). */
public function setJavascriptDelay(int $ms): static { return $this->set('javascript_delay', $ms); }
/** Wait until this CSS selector exists on the page before printing. */
public function setWaitForElement(string $selector): static { return $this->set('wait_for_element', $selector); }
/** Percent, 10–200. Default 100. */
public function setScaleFactor(int $percent): static { return $this->set('scale_factor', $percent); }
/** e.g. "1-3,5" */
public function setPrintPageRange(string $range): static { return $this->set('print_page_range', $range); }
/** Put the whole page on one tall PDF page. */
public function setSinglePage(bool $value = true): static { return $this->set('single_page', $value); }
/** Let CSS @page { size: ... } decide the page size. */
public function setCssPageSize(bool $value = true): static { return $this->set('css_page_size', $value); }
public function setTimeout(int $seconds): static { $this->timeout = $seconds; return $this; }
/** Pass any API parameter directly. */
public function set(string $name, string|int|bool|null $value): static
{
if ($value === null) {
unset($this->fields[$name]);
} else {
$this->fields[$name] = is_bool($value) ? ($value ? 'true' : 'false') : (string) $value;
}
return $this;
}
// ---- Internals --------------------------------------------------------
private function request(array $source): string
{
$ch = curl_init($this->endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($source + $this->fields),
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->apiKey],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => $this->timeout,
]);
$body = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false) {
throw new Error('pdfly: ' . curl_error($ch), 0);
}
if ($status !== 200) {
$json = json_decode($body, true);
throw new Error('pdfly: ' . ($json['error']['message'] ?? "HTTP $status"), $status);
}
return $body;
}
private function save(string $pdf, string $filePath): void
{
if (@file_put_contents($filePath, $pdf) === false) {
throw new Error("Cannot write $filePath", 0);
}
}
private function output(string $pdf, string $filename, bool $download): void
{
header('Content-Type: application/pdf');
header('Content-Length: ' . strlen($pdf));
header('Content-Disposition: ' . ($download ? 'attachment' : 'inline') . '; filename="' . addcslashes($filename, '"\\') . '"');
echo $pdf;
}
}