Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:

strategy:
matrix:
php: [8.4, 8.3, 8.2, 8.1]
laravel: [12.*, 11.*, 10.*, 9.*]
php: [8.4, 8.3, 8.2]
laravel: [12.*, 11.*]
dependency-version: [prefer-stable]
exclude:
- laravel: 11.*
Expand All @@ -34,7 +34,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.13'
cache: 'pip' # caching pip dependencies

- name: Install Dependencies
Expand Down
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
}
],
"require": {
"php": "^8.1",
"illuminate/support": "^9|^10|^11|^12",
"illuminate/filesystem": "^9|^10|^11|^12",
"php": "^8.2",
"illuminate/support": "^11|^12",
"illuminate/filesystem": "^11|^12",
"pontedilana/php-weasyprint": "^2.1"
},
"require-dev": {
"orchestra/testbench": "^7|^8|^9|^10",
"squizlabs/php_codesniffer": "^3.5"
"squizlabs/php_codesniffer": "^3.5",
"spatie/laravel-pdf": "^2.1"
},
"autoload": {
"psr-4": {
Expand Down
133 changes: 133 additions & 0 deletions src/Support/WeasyPrintDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

namespace Fruitcake\WeasyPrint\Support;

use Pontedilana\PhpWeasyPrint\Pdf;
use Spatie\LaravelPdf\Drivers\PdfDriver;
use Spatie\LaravelPdf\PdfOptions;

class WeasyPrintDriver implements PdfDriver
{
public function __construct(protected readonly Pdf $weasy)
{

}

public function generatePdf(
string $html,
?string $headerHtml,
?string $footerHtml,
PdfOptions $options,
): string {
$html = $this->mergeHeaderFooter($html, $headerHtml, $footerHtml);

return $this->weasy->getOutputFromHtml($html, $this->prepareOptions($options));
}

public function savePdf(
string $html,
?string $headerHtml,
?string $footerHtml,
PdfOptions $options,
string $path,
): void {
$html = $this->mergeHeaderFooter($html, $headerHtml, $footerHtml);

$this->weasy->generateFromHtml($html, $path, $this->prepareOptions($options), true);
}

/**
* Based on the laravel-pdf DompdfDriver, but the footer block is BEFORE the rest of the body,
* so it renders the running element correctly.
*
* @param string $html
* @param string|null $headerHtml
* @param string|null $footerHtml
* @return string
*
* @see \Spatie\LaravelPdf\Drivers\DomPdfDriver::mergeHeaderFooter
*/
protected function mergeHeaderFooter(string $html, ?string $headerHtml, ?string $footerHtml): string
{
if (! $headerHtml && ! $footerHtml) {
return $html;
}

$headerBlock = $headerHtml
? '<div class="pdf-header">'.$headerHtml.'</div>'
: '';

$footerBlock = $footerHtml
? '<div class="pdf-footer">'.$footerHtml.'</div>'
: '';

if (preg_match('/<body([^>]*)>/i', $html, $matches)) {
return preg_replace(
'/<body([^>]*)>/i',
'<body$1>'.$headerBlock.$footerBlock,
$html,
);
}

return $headerBlock.$footerBlock.$html;
}

/**
* Based on DomPdfDriver::injectMarginCss but also adds the header/footer as running elements.
*
*
* @see \Spatie\LaravelPdf\Drivers\DomPdfDriver::injectMarginCss
*/
protected function prepareOptions(PdfOptions $options): array
{
$marginCss = '';
if ($options->margins) {
$unit = $options->margins['unit'] ?? 'mm';
$top = $options->margins['top'] . $unit;
$right = $options->margins['right'] . $unit;
$bottom = $options->margins['bottom'] . $unit;
$left = $options->margins['left'] . $unit;

$marginCss = "margin: {$top} {$right} {$bottom} {$left};";
}

$sizeCss = '';
if ($options->paperSize) {
$unit = $options->paperSize['unit'] ?? 'mm';
$sizeWidth = $options->paperSize['width'].$unit;
$sizeHeight = $options->paperSize['height'].$unit;
$sizeCss = "size: {$sizeWidth} {$sizeHeight};";
} elseif ($options->format || $options->orientation) {
$format = strtolower($options->format);
$orientation = strtolower($options->orientation);
$sizeCss = "size: {$format} {$orientation};";
}

$stylesheet = <<<CSS
@page {
{$sizeCss}
{$marginCss}

@top-left {
content: element(pdfHeader);
}
@bottom-left {
content: element(pdfFooter);
}
}
.pdf-header {
position: running(pdfHeader);
}
.pdf-footer {
position: running(pdfFooter);
}

.pageNumber::before { content: counter(page); }
.totalPages::before { content: counter(pages); }
CSS;

return [
'stylesheet' => $stylesheet,
];
}
}
3 changes: 3 additions & 0 deletions src/WeasyPrintProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Fruitcake\WeasyPrint;

use Fruitcake\WeasyPrint\Support\WeasyPrintDriver;
use Illuminate\Support\ServiceProvider;
use Pontedilana\PhpWeasyPrint\Pdf;

Expand Down Expand Up @@ -52,6 +53,8 @@ public function register()
return $wrapper;
});
$this->app->alias('weasyprint.pdf.wrapper', WeasyPrintWrapper::class);

$this->app->bind('laravel-pdf.driver.weasyprint', WeasyPrintDriver::class);
}

public function boot()
Expand Down
142 changes: 142 additions & 0 deletions tests/LaravelPdfDriverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

namespace Fruitcake\WeasyPrint\Tests;

use Fruitcake\WeasyPrint\Support\WeasyPrintDriver;
use Spatie\LaravelPdf\Drivers\PdfDriver;
use Spatie\LaravelPdf\Facades\Pdf;
use Spatie\LaravelPdf\PdfOptions;

class LaravelPdfDriverTest extends TestCase
{
protected function getPackageProviders($app)
{
return array_merge(parent::getPackageProviders($app), [
\Spatie\LaravelPdf\PdfServiceProvider::class,
]);
}

public function testDriverIsRegistered(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$this->assertInstanceOf(WeasyPrintDriver::class, $driver);
$this->assertInstanceOf(PdfDriver::class, $driver);
}

public function testGeneratePdfFromHtml(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$options = new PdfOptions();
$content = $driver->generatePdf('<h1>Test</h1>', null, null, $options);

$this->assertNotEmpty($content);
$this->assertStringStartsWith('%PDF', $content);
}

public function testGeneratePdfWithFormatAndOrientation(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$options = new PdfOptions();
$options->format = 'a4';
$options->orientation = 'landscape';

$content = $driver->generatePdf('<h1>Test</h1>', null, null, $options);

$this->assertNotEmpty($content);
$this->assertStringStartsWith('%PDF', $content);
}

public function testGeneratePdfWithMargins(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$options = new PdfOptions();
$options->margins = [
'top' => 10,
'right' => 10,
'bottom' => 10,
'left' => 10,
'unit' => 'mm',
];

$content = $driver->generatePdf('<h1>Test</h1>', null, null, $options);

$this->assertNotEmpty($content);
$this->assertStringStartsWith('%PDF', $content);
}

public function testGeneratePdfWithCustomPaperSize(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$options = new PdfOptions();
$options->paperSize = [
'width' => 200,
'height' => 300,
'unit' => 'mm',
];

$content = $driver->generatePdf('<h1>Test</h1>', null, null, $options);

$this->assertNotEmpty($content);
$this->assertStringStartsWith('%PDF', $content);
}

public function testGeneratePdfWithHeaderAndFooter(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$options = new PdfOptions();
$content = $driver->generatePdf(
'<html><body><p>Body</p></body></html>',
'<span>Header</span>',
'<span>Footer</span>',
$options,
);

$this->assertNotEmpty($content);
$this->assertStringStartsWith('%PDF', $content);
}

public function testSavePdf(): void
{
$driver = $this->app->make('laravel-pdf.driver.weasyprint');

$options = new PdfOptions();
$path = tempnam(sys_get_temp_dir(), 'pdf_') . '.pdf';

try {
$driver->savePdf('<h1>Test</h1>', null, null, $options, $path);

$this->assertFileExists($path);
$this->assertStringStartsWith('%PDF', file_get_contents($path));
} finally {
@unlink($path);
}
}

public function testViaSpatieDriverMethod(): void
{
$builder = Pdf::driver('weasyprint')->html('<h1>Test</h1>');

$content = $builder->base64();

$this->assertNotEmpty($content);
$decoded = base64_decode($content);
$this->assertStringStartsWith('%PDF', $decoded);
}

public function testViaSpatieWithView(): void
{
$builder = Pdf::driver('weasyprint')->view('test');

$content = $builder->base64();

$this->assertNotEmpty($content);
$decoded = base64_decode($content);
$this->assertStringStartsWith('%PDF', $decoded);
}
}