Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.52% covered (warning)
65.52%
19 / 29
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Rasteriser
65.52% covered (warning)
65.52%
19 / 29
20.00% covered (danger)
20.00%
1 / 5
10.62
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rasterise
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
4.02
 dpi
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 ghostscriptBinary
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isAvailable
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\WptHarness;
6
7/**
8 * Renders a PDF to a PNG file for downstream perceptual diffing.
9 *
10 * v1 implementation: shells out to Ghostscript with `-sDEVICE=png16m`
11 * at the configured DPI (default 96 — the WPT reference). One PNG
12 * per page; this method returns the path for `$pageIndex`.
13 *
14 * v2 implementation (Phase 4C): swap to `phpdftk/raster` so the
15 * harness has zero external dependencies. Until then the gs path
16 * is the canonical implementation.
17 */
18final class Rasteriser
19{
20    public function __construct(
21        private readonly int $dpi = 96,
22        private readonly string $ghostscriptBinary = 'gs',
23    ) {}
24
25    /**
26     * Rasterise `$pdfPath` to a temp PNG. Returns the path. Caller
27     * is responsible for unlinking the result.
28     *
29     * `$pageIndex` is 0-based; Ghostscript's `-dFirstPage` /
30     * `-dLastPage` are 1-based so we add 1 internally.
31     */
32    public function rasterise(string $pdfPath, int $pageIndex = 0): string
33    {
34        if (!is_file($pdfPath)) {
35            throw new \RuntimeException("PDF not found: $pdfPath");
36        }
37        $outPath = tempnam(sys_get_temp_dir(), 'wpt_render_') . '.png';
38        $page = $pageIndex + 1;
39        $cmd = sprintf(
40            '%s -dNOPAUSE -dBATCH -dQUIET -sDEVICE=png16m '
41                . '-r%d -dFirstPage=%d -dLastPage=%d '
42                . '-sOutputFile=%s %s 2>&1',
43            escapeshellcmd($this->ghostscriptBinary),
44            $this->dpi,
45            $page,
46            $page,
47            escapeshellarg($outPath),
48            escapeshellarg($pdfPath),
49        );
50        exec($cmd, $output, $status);
51        if ($status !== 0 || !is_file($outPath)) {
52            $err = implode("\n", $output);
53            throw new \RuntimeException("Ghostscript rasterise failed (exit $status): $err");
54        }
55        return $outPath;
56    }
57
58    public function dpi(): int
59    {
60        return $this->dpi;
61    }
62
63    public function ghostscriptBinary(): string
64    {
65        return $this->ghostscriptBinary;
66    }
67
68    /**
69     * Probe whether the configured Ghostscript binary is callable.
70     * Used by the harness CLI to fail fast with a useful message
71     * when the substrate isn't installed.
72     */
73    public function isAvailable(): bool
74    {
75        $cmd = sprintf(
76            '%s --version 2>/dev/null',
77            escapeshellcmd($this->ghostscriptBinary),
78        );
79        exec($cmd, $_, $status);
80        return $status === 0;
81    }
82}