Last active
December 21, 2023 23:27
-
-
Save sinnbeck/f13b74b4e70254488d977814c0f8527b to your computer and use it in GitHub Desktop.
Browsershot for chrome-php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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