Skip to content

Instantly share code, notes, and snippets.

@sinnbeck
Last active December 21, 2023 23:27
Show Gist options
  • Save sinnbeck/f13b74b4e70254488d977814c0f8527b to your computer and use it in GitHub Desktop.
Save sinnbeck/f13b74b4e70254488d977814c0f8527b to your computer and use it in GitHub Desktop.
Browsershot for chrome-php
<?php
declare(strict_types=1);
namespace App\Services;
use Exception;
use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Page;
use HeadlessChromium\PageUtils\PagePdf;
use HeadlessChromium\PageUtils\PageScreenshot;
use Spatie\Browsershot\Exceptions\CouldNotTakeBrowsershot;
class Browsershot
{
private BrowserFactory $browser;
private array $browserOptions = [];
private array $options = [
'marginTop' => 0,
'marginRight' => 0,
'marginBottom' => 0,
'marginLeft' => 0,
];
private string $html = '';
private string $url;
private string $eventName = Page::LOAD;
const FORMATS = [
'letter' => [
8.5,
11,
],
'legal' => [
8.5,
14,
],
'tabloid' => [
11,
17,
],
'ledger' => [
17,
11,
],
'a0' => [
33.1,
46.8,
],
'a1' => [
23.4,
33.1,
],
'a2' => [
16.54,
23.4,
],
'a3' => [
11.7,
16.54,
],
'a4' => [
8.27,
11.7,
],
'a5' => [
5.83,
8.27,
],
'a6' => [
4.13,
5.83,
],
];
const UNIT_TO_PIXELS = [
'px' => 1,
'in' => 96,
'cm' => 37.8,
'mm' => 3.78,
];
public function __construct(string $url = '', bool $deviceEmulate = false)
{
$this->browser = (new BrowserFactory());
$this->url = $url;
if (!$deviceEmulate) {
$this->windowSize(800, 600);
}
}
public function __call($method, $parameters)
{
return $this;
}
public static function url(string $url): static
{
return (new static())->setUrl($url);
}
public function setUrl(string $url): self
{
$this->html = '';
$this->url = $url;
$this->hideBrowserHeaderAndFooter();
return $this;
}
public static function html(string $html): static
{
return (new static())->setHtml($html);
}
public function setHtml(string $html): self
{
$this->html = $html;
$this->url = '';
$this->hideBrowserHeaderAndFooter();
return $this;
}
public function noSandbox()
{
$this->browserOptions['noSandbox'] = true;
return $this;
}
public function newHeadless()
{
$this->browserOptions['headless'] = false;
$this->browserOptions['customFlags'] = [
'--headless=new',
];
}
public function windowSize(int $width, int $height): self
{
$this->browserOptions['windowS0.03ize'] = [
$width,
$height,
];
return $this;
}
public function format(string $format): self
{
$format = strtolower($format);
if (!array_key_exists($format, self::FORMATS)) {
throw new Exception('Invalid format ' . $format);
}
$this->options['paperWidth'] = self::FORMATS[$format][0];
$this->options['paperHeight'] = self::FORMATS[$format][1];
return $this;
}
public function paperSize(float $width, float $height, string $unit = 'mm'): self
{
$this->options['paperWidth'] = $this->convertUnitToInches($width, $unit);
$this->options['paperHeight'] = $this->convertUnitToInches($height, $unit);
return $this;
}
public function ignoreHttpsErrors(): self
{
$this->browserOptions['ignoreCertificateErrors'] = true;
return $this;
}
public function showBackground(): self
{
$this->options['printBackground'] = true;
return $this;
}
public function waitUntilNetworkIdle(): self
{
$this->eventName = Page::NETWORK_IDLE;
return $this;
}
public function scale(float $scale): self
{
$this->options['scale'] = $scale;
return $this;
}
public function showBrowserHeaderAndFooter(): self
{
$this->options['displayHeaderFooter'] = true;
return $this;
}
public function hideBrowserHeaderAndFooter()
{
$this->options['displayHeaderFooter'] = false;
return $this;
}
public function hideHeader()
{
return $this->options['headerTemplate'] = '<p></p>';
}
public function hideFooter()
{
return $this->options['footerTemplate'] = '<p></p>';
}
public function headerHtml($html): self
{
$this->options['headerTemplate'] = $html;
return $this;
}
public function footerHtml($html): self
{
$this->options['footerTemplate'] = $html;
return $this;
}
public function pages(string $ranges): self
{
$this->options['pageRanges'] = $ranges;
return $this;
}
public function margins(float $top, float $right, float $bottom, float $left, string $unit = 'mm'): self
{
$this->options['marginTop'] = $this->convertUnitToInches($top, $unit);
$this->options['marginRight'] = $this->convertUnitToInches($right, $unit);
$this->options['marginBottom'] = $this->convertUnitToInches($bottom, $unit);
$this->options['marginLeft'] = $this->convertUnitToInches($left, $unit);
return $this;
}
public function save(string $targetPath): void
{
$extension = strtolower(pathinfo($targetPath, PATHINFO_EXTENSION));
if ($extension === '') {
throw CouldNotTakeBrowsershot::outputFileDidNotHaveAnExtension($targetPath);
}
if ($extension === 'pdf') {
$this->savePdf($targetPath);
return;
}
$this->saveScreenshot($targetPath);
}
public function base64Screenshot(): string
{
return $this->makeScreenshot()->getBase64();
}
public function saveScreenshot(string $path): void
{
$pdf = $this->makePdf();
$pdf->saveToFile($path);
}
public function screenshot()
{
return base64_decode($this->base64Screenshot());
}
public function makeScreenshot(): PageScreenshot
{
return $this->makePage()->screenshot();
}
public function base64Pdf(): string
{
return $this->makePdf()->getBase64();
}
public function savePdf(string $path): void
{
$pdf = $this->makePdf();
$pdf->saveToFile($path);
}
public function pdf()
{
return base64_decode($this->base64Pdf());
}
private function makePdf(): PagePdf
{
$page = $this->makePage();
return $page
->pdf($this->options);
}
private function makePage(): Page
{
$page = $this->browser->createBrowser($this->browserOptions)
->createPage();
if ($this->html !== '') {
$page->setHtml($this->html, 3000, $this->eventName);
} else if ($this->url !== '') {
$page->navigate($this->url)->waitForNavigation();
}
return $page;
}
private function convertUnitToInches(float $value, string $unit): float
{
return $this->convertUnitToInchesPuppeteer($value . $unit);
}
private function convertUnitToInchesPuppeteer($parameter = null, $lengthUnit = 'in')
{
if ($parameter === null) {
return null;
}
if (is_numeric($parameter)) {
// Treat numbers as pixel values to be aligned with phantom's paperSize.
$pixels = $parameter;
} else if (is_string($parameter)) {
$text = $parameter;
$unit = strtolower(substr($text, -2));
if (array_key_exists($unit, self::UNIT_TO_PIXELS)) {
$valueText = substr($text, 0, -2);
} else {
// In case of an unknown unit, try to parse the whole parameter as the number of pixels.
// This is consistent with phantom's paperSize behavior.
$unit = 'px';
$valueText = $text;
}
$value = (float)$valueText;
if (!is_nan($value)) {
$pixels = $value * self::UNIT_TO_PIXELS[$unit];
} else {
throw new Exception('Failed to parse parameter value: ' . $text);
}
} else {
throw new Exception('page.pdf() Cannot handle parameter type: ' . gettype($parameter));
}
return $pixels / self::UNIT_TO_PIXELS[$lengthUnit];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment