├── .editorconfig ├── LICENSE ├── composer.json ├── config ├── .gitkeep └── weasyprint.php ├── readme.md ├── requirements.txt └── src ├── Facades └── WeasyPrint.php ├── IlluminateWeasyPrintPdf.php ├── WeasyPrintProvider.php ├── WeasyPrintWrapper.php └── WeasyPrintWrapperFaker.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2023 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fruitcake/laravel-weasyprint", 3 | "description": "WeasyPrint for Laravel", 4 | "keywords": ["laravel", "weasyprint", "pdf"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Fruitcake", 9 | "homepage": "https://fruitcake.nl" 10 | }, 11 | { 12 | "name": "Barry vd. Heuvel", 13 | "email": "barry@fruitcake.nl" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.1", 18 | "illuminate/support": "^9|^10|^11|^12", 19 | "illuminate/filesystem": "^9|^10|^11|^12", 20 | "pontedilana/php-weasyprint": "^1.5|^2" 21 | }, 22 | "require-dev": { 23 | "orchestra/testbench": "^7|^8|^9|^10", 24 | "squizlabs/php_codesniffer": "^3.5" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Fruitcake\\WeasyPrint\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Fruitcake\\WeasyPrint\\Tests\\": "tests/" 34 | } 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "0.1-dev" 39 | }, 40 | "laravel": { 41 | "providers": [ 42 | "Fruitcake\\WeasyPrint\\WeasyPrintProvider" 43 | ], 44 | "aliases": { 45 | "WeasyPrint": "Fruitcake\\WeasyPrint\\\\Facades\\WeasyPrint" 46 | } 47 | } 48 | }, 49 | "scripts": { 50 | "actions": "composer test && composer check-style", 51 | "test": "phpunit", 52 | "check-style": "phpcs -p --standard=PSR12 --exclude=Generic.Files.LineLength --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests", 53 | "fix-style": "phpcbf -p --standard=PSR12 --exclude=Generic.Files.LineLength --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests" 54 | }, 55 | "minimum-stability": "dev", 56 | "prefer-stable": true 57 | } 58 | -------------------------------------------------------------------------------- /config/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruitcake/laravel-weasyprint/eed48f92452d64414e047ea7389678b0b12959f9/config/.gitkeep -------------------------------------------------------------------------------- /config/weasyprint.php: -------------------------------------------------------------------------------- 1 | [ 33 | 'binary' => env('WEASYPRINT_BINARY', '/usr/local/bin/weasyprint'), 34 | 'timeout' => 10, // Default timeout is 10 seconds 35 | 'options' => [ 36 | 'encoding' => null, 37 | 'stylesheet' => [], //An optional list of user stylesheets. The list can include are CSS objects, filenames, URLs, or file-like objects.s 38 | 'media-type' => null, //Media type to use for @media. 39 | 'base-url' => null, 40 | 'attachment' => [], //A list of additional file attachments for the generated PDF document 41 | 'presentational-hints' => null, 42 | 'pdf-identifier' => null, // A bytestring used as PDF file identifier. 43 | 'pdf-variant' => null, // A PDF variant name. 44 | 'pdf-version' => null, // A PDF version number. 45 | 'pdf-forms' => null, // (bool) Whether PDF forms have to be included. 46 | 'custom-metadata' => null, 47 | 'uncompressed-pdf' => null, //Whether PDF content should be compressed. 48 | 'full-fonts' => null, 49 | 'hinting' => null, 50 | 'dpi' => null, 51 | 'jpeg-quality' => null, 52 | 'optimize-images' => null, 53 | 'cache-folder' => null, 54 | 'timeout' => null, 55 | // Deprecated 56 | 'format' => null, 57 | 'resolution' => null, 58 | 'optimize-size' => null, 59 | ], 60 | 'env' => [], 61 | ], 62 | ]; 63 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## WeasyPrint PDF Wrapper for Laravel 2 | [![Tests](https://github.com/fruitcake/laravel-weasyprint/actions/workflows/run-tests.yml/badge.svg)](https://github.com/fruitcake/laravel-weasyprint/actions/workflows/run-tests.yml) 3 | [![Packagist License](https://img.shields.io/badge/Licence-MIT-blue)](http://choosealicense.com/licenses/mit/) 4 | [![Latest Stable Version](https://img.shields.io/packagist/v/fruitcake/laravel-weasyprint?label=Stable)](https://packagist.org/packages/fruitcake/laravel-weasyprint) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/fruitcake/laravel-weasyprint?label=Downloads)](https://packagist.org/packages/fruitcake/laravel-weasyprint) 6 | [![Fruitcake](https://img.shields.io/badge/Powered%20By-Fruitcake-b2bc35.svg)](https://fruitcake.nl/) 7 | 8 | This package is a ServiceProvider for WeasyPrint: [https://github.com/pontedilana/php-weasyprint](https://github.com/pontedilana/php-weasyprint). 9 | 10 | This package is based heavily on https://github.com/barryvdh/laravel-snappy but uses WeasyPrint instead of WKHTMLTOPDF 11 | 12 | ### WeasyPrint Installation 13 | 14 | Follow the setup here: https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation 15 | 16 | ### Testing the WeasyPrint installation 17 | 18 | After installing, you should be able to run WeasyPrint from the command line / shell. 19 | 20 | ```shell 21 | weasyprint https://laravel.com/docs laravel-docs.pdf 22 | ``` 23 | 24 | ### Package Installation 25 | 26 | Require this package in your composer.json and update composer. 27 | 28 | ```bash 29 | composer require fruitcake/laravel-weasyprint 30 | ``` 31 | 32 | ### Configuration 33 | 34 | You can publish the config file: 35 | 36 | ```bash 37 | php artisan vendor:publish --provider="Fruitcake\WeasyPrint\WeasyPrintProvider" 38 | ``` 39 | 40 | ### Usage 41 | 42 | You can create a new WeasyPrint instance and load an HTML string, file or view name. You can save it to a file, or inline (show in browser) or download. 43 | 44 | Using the App container: 45 | 46 | ```php 47 | Bill

You owe me money, dude.

'; 60 | $weasyPrint->generateFromHtml($html, '/tmp/bill-123.pdf'); 61 | $weasyPrint->generate('https://laravel.com/docs/10.x', '/tmp/laravel-docs.pdf'); 62 | 63 | //Or output: 64 | return response( 65 | $weasyPrint->getOutputFromHtml($html), 66 | 200, 67 | array( 68 | 'Content-Type' => 'application/pdf', 69 | 'Content-Disposition' => 'attachment; filename="file.pdf"' 70 | ) 71 | ); 72 | } 73 | } 74 | 75 | ``` 76 | 77 | Or use the Facade to access easy helper methods. 78 | 79 | Inline a PDF: 80 | 81 | ```php 82 | $pdf = \WeasyPrint::loadHTML('

Test

'); 83 | return $pdf->inline(); 84 | ``` 85 | 86 | Or download: 87 | 88 | ```php 89 | $pdf = \WeasyPrint::loadView('pdf.invoice', $data); 90 | return $pdf->download('invoice.pdf'); 91 | ``` 92 | 93 | You can chain the methods: 94 | 95 | ```php 96 | return \WeasyPrint::loadFile('https://laravel.com/docs')->inline('laravel.pdf'); 97 | ``` 98 | 99 | You can change the orientation and paper size 100 | 101 | ```php 102 | \WeasyPrint::loadHTML($html)->setPaper('a4')->setOrientation('landscape')->setOption('margin-bottom', 0)->save('myfile.pdf') 103 | ``` 104 | 105 | If you need the output as a string, you can get the rendered PDF with the output() function, so you can save/output it yourself. 106 | 107 | See the [php-weasyprint](https://github.com/pontedilana/php-weasyprint) for more information/settings. 108 | 109 | ### Testing - PDF fake 110 | 111 | As an alternative to mocking, you may use the `WeasyPrint` facade's `fake` method. When using fakes, assertions are made after the code under test is executed: 112 | 113 | ```php 114 | = 64 2 | -------------------------------------------------------------------------------- /src/Facades/WeasyPrint.php: -------------------------------------------------------------------------------- 1 | fs = $fs; 25 | } 26 | 27 | /** 28 | * Wrapper for the "file_get_contents" function 29 | * 30 | * @param string $filename 31 | * 32 | * @return string 33 | */ 34 | protected function getFileContents($filename): string 35 | { 36 | return $this->fs->get($filename); 37 | } 38 | 39 | /** 40 | * Wrapper for the "file_exists" function 41 | * 42 | * @param string $filename 43 | * 44 | * @return boolean 45 | */ 46 | protected function fileExists($filename): bool 47 | { 48 | return $this->fs->exists($filename); 49 | } 50 | 51 | /** 52 | * Wrapper for the "is_file" method 53 | * 54 | * @param string $filename 55 | * 56 | * @return boolean 57 | */ 58 | protected function isFile($filename): bool 59 | { 60 | return strlen($filename) <= PHP_MAXPATHLEN && $this->fs->isFile($filename); 61 | } 62 | 63 | /** 64 | * Wrapper for the "filesize" function 65 | * 66 | * @param string $filename 67 | * 68 | * @return integer or FALSE on failure 69 | */ 70 | protected function filesize($filename): int 71 | { 72 | return $this->fs->size($filename); 73 | } 74 | 75 | /** 76 | * Wrapper for the "unlink" function 77 | * 78 | * @param string $filename 79 | * 80 | * @return boolean 81 | */ 82 | protected function unlink($filename): bool 83 | { 84 | return $this->fs->delete($filename); 85 | } 86 | 87 | /** 88 | * Wrapper for the "is_dir" function 89 | * 90 | * @param string $filename 91 | * 92 | * @return boolean 93 | */ 94 | protected function isDir($filename): bool 95 | { 96 | return $this->fs->isDirectory($filename); 97 | } 98 | 99 | /** 100 | * Wrapper for the mkdir function 101 | * 102 | * @param string $pathname 103 | * 104 | * @return boolean 105 | */ 106 | protected function mkdir($pathname): bool 107 | { 108 | return $this->fs->makeDirectory($pathname, 0777, true, true); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/WeasyPrintProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom($configPath, 'weasyprint'); 26 | } 27 | 28 | public function boot() 29 | { 30 | $configPath = __DIR__ . '/../config/weasyprint.php'; 31 | $this->publishes([$configPath => config_path('weasyprint.php')], 'config'); 32 | 33 | $this->app->bind('weasyprint.pdf', function ($app) { 34 | $binary = $app['config']->get('weasyprint.pdf.binary', '/usr/local/bin/weasyprint'); 35 | $options = $app['config']->get('weasyprint.pdf.options', array()); 36 | $env = $app['config']->get('weasyprint.pdf.env', array()); 37 | $timeout = $app['config']->get('weasyprint.pdf.timeout', false); 38 | 39 | $weasy = new IlluminateWeasyPrintPdf($app['files'], $binary, $options, $env); 40 | if ($timeout && is_int($timeout)) { 41 | $weasy->setTimeout($timeout); 42 | } 43 | 44 | return $weasy; 45 | }); 46 | $this->app->alias('weasyprint.pdf', Pdf::class); 47 | 48 | $this->app->bind('weasyprint.pdf.wrapper', function ($app) { 49 | return new WeasyPrintWrapper($app['weasyprint.pdf']); 50 | }); 51 | $this->app->alias('weasyprint.pdf.wrapper', WeasyPrintWrapper::class); 52 | } 53 | 54 | /** 55 | * Get the services provided by the provider. 56 | * 57 | * @return array 58 | */ 59 | public function provides() 60 | { 61 | return array('weasyprint.pdf', 'weasyprint.pdf.wrapper', WeasyPrintWrapper::class, Pdf::class); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/WeasyPrintWrapper.php: -------------------------------------------------------------------------------- 1 | weasy = $weasy; 45 | } 46 | 47 | /** 48 | * Get the WeasyPrint instance. 49 | * 50 | * @return Pdf 51 | */ 52 | public function weasy() 53 | { 54 | return $this->weasy; 55 | } 56 | 57 | /** 58 | * Set temporary folder 59 | * 60 | * @param string $path 61 | */ 62 | public function setTemporaryFolder($path) 63 | { 64 | $this->weasy->setTemporaryFolder($path); 65 | return $this; 66 | } 67 | 68 | /** 69 | * Set the paper size (default A4) 70 | * 71 | * @param string $paper 72 | * @param string $orientation 73 | * @return $this 74 | */ 75 | public function setPaper($paper, $orientation = null) 76 | { 77 | $this->weasy->setOption('page-size', $paper); 78 | if ($orientation) { 79 | $this->weasy->setOption('orientation', $orientation); 80 | } 81 | return $this; 82 | } 83 | 84 | /** 85 | * Set the orientation (default portrait) 86 | * 87 | * @param string $orientation 88 | * @return $this 89 | */ 90 | public function setOrientation($orientation) 91 | { 92 | $this->weasy->setOption('orientation', $orientation); 93 | return $this; 94 | } 95 | 96 | /** 97 | * Show or hide warnings 98 | * 99 | * @param bool $warnings 100 | * @return $this 101 | * @deprecated 102 | */ 103 | public function setWarnings($warnings) 104 | { 105 | //Doesn't do anything 106 | return $this; 107 | } 108 | 109 | /** 110 | * @param string $name 111 | * @param mixed $value 112 | * @return $this 113 | */ 114 | public function setOption($name, $value) 115 | { 116 | if ($value instanceof Renderable) { 117 | $value = $value->render(); 118 | } 119 | $this->weasy->setOption($name, $value); 120 | return $this; 121 | } 122 | 123 | /** 124 | * @param array $options 125 | * @return $this 126 | */ 127 | public function setOptions($options) 128 | { 129 | $this->weasy->setOptions($options); 130 | return $this; 131 | } 132 | 133 | /** 134 | * Load a HTML string 135 | * 136 | * @param Array|string|Renderable $html 137 | * @return $this 138 | */ 139 | public function loadHTML($html) 140 | { 141 | if ($html instanceof Renderable) { 142 | $html = $html->render(); 143 | } 144 | $this->html = $html; 145 | $this->file = null; 146 | return $this; 147 | } 148 | 149 | /** 150 | * Load a HTML file 151 | * 152 | * @param string $file 153 | * @return $this 154 | */ 155 | public function loadFile($file) 156 | { 157 | $this->html = null; 158 | $this->file = $file; 159 | return $this; 160 | } 161 | 162 | /** 163 | * Load a View and convert to HTML 164 | * 165 | * @param string $view 166 | * @param array $data 167 | * @param array $mergeData 168 | * @return $this 169 | */ 170 | public function loadView($view, $data = array(), $mergeData = array()) 171 | { 172 | $view = View::make($view, $data, $mergeData); 173 | 174 | return $this->loadHTML($view); 175 | } 176 | 177 | /** 178 | * Output the PDF as a string. 179 | * 180 | * @return string The rendered PDF as string 181 | * @throws \InvalidArgumentException 182 | */ 183 | public function output() 184 | { 185 | if ($this->html) { 186 | return $this->weasy->getOutputFromHtml($this->html, $this->options); 187 | } 188 | 189 | if ($this->file) { 190 | return $this->weasy->getOutput($this->file, $this->options); 191 | } 192 | 193 | throw new \InvalidArgumentException('PDF Generator requires a html or file in order to produce output.'); 194 | } 195 | 196 | /** 197 | * Save the PDF to a file 198 | * 199 | * @param $filename 200 | * @return $this 201 | */ 202 | public function save($filename, $overwrite = false) 203 | { 204 | 205 | if ($this->html) { 206 | $this->weasy->generateFromHtml($this->html, $filename, $this->options, $overwrite); 207 | } elseif ($this->file) { 208 | $this->weasy->generate($this->file, $filename, $this->options, $overwrite); 209 | } 210 | 211 | return $this; 212 | } 213 | 214 | /** 215 | * Make the PDF downloadable by the user 216 | * 217 | * @param string $filename 218 | * @return \Illuminate\Http\Response 219 | */ 220 | public function download($filename = 'document.pdf') 221 | { 222 | return new Response($this->output(), 200, array( 223 | 'Content-Type' => 'application/pdf', 224 | 'Content-Disposition' => 'attachment; filename="' . $filename . '"' 225 | )); 226 | } 227 | 228 | /** 229 | * Return a response with the PDF to show in the browser 230 | * 231 | * @param string $filename 232 | * @return \Illuminate\Http\Response 233 | */ 234 | public function inline($filename = 'document.pdf') 235 | { 236 | return new Response($this->output(), 200, array( 237 | 'Content-Type' => 'application/pdf', 238 | 'Content-Disposition' => 'inline; filename="' . $filename . '"', 239 | )); 240 | } 241 | 242 | /** 243 | * Return a response with the PDF to show in the browser 244 | * 245 | * @param string $filename 246 | * @return \Symfony\Component\HttpFoundation\StreamedResponse 247 | * @deprecated use inline() instead 248 | */ 249 | public function stream($filename = 'document.pdf') 250 | { 251 | return new StreamedResponse(function () { 252 | echo $this->output(); 253 | }, 200, array( 254 | 'Content-Type' => 'application/pdf', 255 | 'Content-Disposition' => 'inline; filename="' . $filename . '"', 256 | )); 257 | } 258 | 259 | /** 260 | * Call WeasyPrint instance. 261 | * 262 | * Also shortcut's 263 | * ->html => loadHtml 264 | * ->view => loadView 265 | * ->file => loadFile 266 | * 267 | * @param string $name 268 | * @param array $arguments 269 | * @return mixed 270 | */ 271 | public function __call($name, array $arguments) 272 | { 273 | $method = 'load' . ucfirst($name); 274 | if (method_exists($this, $method)) { 275 | return call_user_func_array(array($this, $method), $arguments); 276 | } 277 | 278 | return call_user_func_array(array($this->weasy, $name), $arguments); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/WeasyPrintWrapperFaker.php: -------------------------------------------------------------------------------- 1 | view = ViewFacade::make($view, $data, $mergeData); 25 | return parent::loadView($view, $data, $mergeData); 26 | } 27 | 28 | 29 | /** 30 | * Ensure that the response has a view as its original content. 31 | * 32 | * @return $this 33 | */ 34 | protected function ensureResponseHasView() 35 | { 36 | if (! isset($this->view) || ! $this->view instanceof View) { 37 | return PHPUnit::fail('The response is not a view.'); 38 | } 39 | 40 | return $this; 41 | } 42 | 43 | public function assertViewIs($value) 44 | { 45 | PHPUnit::assertEquals($value, $this->view->getName()); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Assert that the response view has a given piece of bound data. 52 | * 53 | * @param string|array $key 54 | * @param mixed $value 55 | * @return $this 56 | */ 57 | public function assertViewHas($key, $value = null) 58 | { 59 | if (is_array($key)) { 60 | return $this->assertViewHasAll($key); 61 | } 62 | 63 | $this->ensureResponseHasView(); 64 | 65 | if (is_null($value)) { 66 | PHPUnit::assertArrayHasKey($key, $this->view->getData()); 67 | } elseif ($value instanceof \Closure) { 68 | PHPUnit::assertTrue($value($this->view->$key)); 69 | } else { 70 | PHPUnit::assertEquals($value, $this->view->$key); 71 | } 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Assert that the response view has a given list of bound data. 78 | * 79 | * @param array $bindings 80 | * @return $this 81 | */ 82 | public function assertViewHasAll(array $bindings) 83 | { 84 | foreach ($bindings as $key => $value) { 85 | if (is_int($key)) { 86 | $this->assertViewHas($value); 87 | } else { 88 | $this->assertViewHas($key, $value); 89 | } 90 | } 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Assert that the response view is missing a piece of bound data. 97 | * 98 | * @param string $key 99 | * @return $this 100 | */ 101 | public function assertViewMissing($key) 102 | { 103 | $this->ensureResponseHasView(); 104 | 105 | PHPUnit::assertArrayNotHasKey($key, $this->view->getData()); 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Assert that the given string is contained within the response. 112 | * 113 | * @param string $value 114 | * @return $this 115 | */ 116 | public function assertSee($value) 117 | { 118 | PHPUnit::assertStringContainsString($value, $this->html); 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Assert that the given string is contained within the response text. 125 | * 126 | * @param string $value 127 | * @return $this 128 | */ 129 | public function assertSeeText($value) 130 | { 131 | PHPUnit::assertStringContainsString($value, strip_tags($this->html)); 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Assert that the given string is not contained within the response. 138 | * 139 | * @param string $value 140 | * @return $this 141 | */ 142 | public function assertDontSee($value) 143 | { 144 | PHPUnit::assertStringNotContainsString($value, $this->html); 145 | 146 | return $this; 147 | } 148 | 149 | /** 150 | * Assert that the given string is not contained within the response text. 151 | * 152 | * @param string $value 153 | * @return $this 154 | */ 155 | public function assertDontSeeText($value) 156 | { 157 | PHPUnit::assertStringNotContainsString($value, strip_tags($this->html)); 158 | 159 | return $this; 160 | } 161 | 162 | /** 163 | * Assert that the given string is equal to the saved filename. 164 | * 165 | * @param string $value 166 | * @return $this 167 | */ 168 | public function assertFileNameIs($value) 169 | { 170 | PHPUnit::assertEquals($value, $this->filename); 171 | 172 | return $this; 173 | } 174 | 175 | public function output() 176 | { 177 | return '%PDF-1.3 178 | %????????? 179 | 4 0 obj 180 | << /Length 5 0 R /Filter /FlateDecode >> 181 | stream 182 | x+T(T0BSKS=# 183 | 184 | C= 185 | K??T?p?<}?bC??bC0,N??5?34???05j1?7)N? 186 | z/? 187 | endstream 188 | endobj 189 | 5 0 obj 190 | 73 191 | endobj 192 | 2 0 obj 193 | << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 595.28 841.89] 194 | >> 195 | endobj 196 | 6 0 obj 197 | << /ProcSet [ /PDF ] /ColorSpace << /Cs1 7 0 R >> >> 198 | endobj 199 | 8 0 obj 200 | << /Length 9 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> 201 | stream 202 | x??wTS??Ͻ7??" %?z ?;HQ?I?P??&vDF)VdT?G?"cE 203 | ??b? ?P??QDE?݌k ?5?ޚ??Y?????g?}׺P???tX?4?X???\???X??ffG?D???=???HƳ??.?d??,?P&s???"7C$ 204 | E?6<~&??S??2????)2?12? ??"?įl???+?ɘ?&?Y??4???Pޚ%ᣌ?\?%?g?|e?TI???(????L0?_??&?l?2E???9?r??9h?x?g??Ib?טi???f??S?b1+??M?xL??? 205 | ?0??o?E%Ym?h?????Y??h????~S?=?z?U?&?ϞA??Y?l?/??$Z????U?m@??O? ??ޜ??l^??? 206 | \' 207 | ???ls?k.+?7???oʿ?9?????V;???#I3eE妧?KD?? 208 | ??d?????9i???,?????UQ? ??h??? 214 | p1?v?jpԁz?N?6p\W? 215 | 216 | ?G@ 217 | ???ٰG???Dx????J?>???,?_@?FDB?X$!k?"??E????H?q???a???Y??bVa?bJ0՘c?VL?6f3????bձ?X\'??&?x?*???s?b|!??`[????a?;???p~?\2n5??׌???? 218 | ߏƿ\'? Zk?!? $l$???4Q??Ot"?y?\b)???A?I&N?I?$R$)???TIj"]&=&?!??:dGrY@^O?$? _%??P?n?X????ZO?D}J}/G?3???ɭ???k??{%O?חw?_.?\'_!J????Q?@?S???V?F??=?IE???b?b?b?b??5?Q%?????O?@??%?!BӥyҸ?M?:?e?0G7??ӓ????? e%e[?(????R?0`?3R????????4?????6?i^??)??*n*|?"?f????LUo?՝?m?O?0j&jaj?j??.??ϧ?w?ϝ_4????갺?z??j???=???U?4?5?n?ɚ??4ǴhZ 219 | ?Z?Z?^0????Tf%??9?????-?>?ݫ=?c??Xg?N??]?.[7A?\?SwBOK/X/_?Q?>Q?????G?[??? ?`?A???????a?a??c#????*?Z?;?8c?q??>?[&???I?I??MS???T`?ϴ? 220 | k?h&4?5?Ǣ??YY?F֠9?:>?>?>?v??}/?a??v?????????O8? ? 225 | ?FV> 226 | 2 u?????/?_$\?B?Cv?< 5 227 | ]?s.,4?&?y?Ux~xw-bEDCĻH????G??KwF?G?E?GME{E?EK?X,Y??F?Z? ?=$vr????K???? 228 | ??.3\????r???Ϯ?_?Yq*??©?L??_?w?ד??????+??]?e???????D??]?cI?II?OA??u?_?䩔???)3?ѩ?i?????B%a??+]3=\'?/?4?0C??i??U?@ёL(sYf????L?H?$?%?Y 229 | ?j??gGe??Q?????n?????~5f5wug?v????5?k??֮\۹Nw]??????m mH???Fˍen???Q?Q??`h????B?BQ?-?[l?ll??f??jۗ"^??b???O%ܒ??Y}W???????????w?w????X?bY^?Ю?]?????W?Va[q`i?d??2???J?jGէ??????{?????׿?m???>???Pk?Am?a?????꺿g_D?H??G?G??u?;??7?7?6?Ʊ?q?o???C{??P3???8!9????? 230 | ???ҝ????ˁ??^?r?۽??U??g?9];}?}????????_?~i??m??p???㭎?}??]?/???}?????.?{?^?=?}????^??z8?h?c??\' 231 | O*????????f?????`ϳ?g???C/????O?ϩ?+F?F?G?Gό???z????ˌ??ㅿ)????ѫ?~w??gb???k???Jި?9???m?d???wi獵?ޫ???????c?Ǒ??O?O????w| ??x&mf?????? 232 | endstream 233 | endobj 234 | 9 0 obj 235 | 2612 236 | endobj 237 | 7 0 obj 238 | [ /ICCBased 8 0 R ] 239 | endobj 240 | 3 0 obj 241 | << /Type /Pages /MediaBox [0 0 595.28 841.89] /Count 1 /Kids [ 2 0 R ] >> 242 | endobj 243 | 10 0 obj 244 | << /Type /Catalog /Pages 3 0 R >> 245 | endobj 246 | 11 0 obj 247 | (Leeg) 248 | endobj 249 | 12 0 obj 250 | (Mac OS X 10.13.3 Quartz PDFContext) 251 | endobj 252 | 13 0 obj 253 | (Pages) 254 | endobj 255 | 14 0 obj 256 | (D:20180330091154Z00\'00\') 257 | endobj 258 | 1 0 obj 259 | << /Title 11 0 R /Producer 12 0 R /Creator 13 0 R /CreationDate 14 0 R /ModDate 260 | 14 0 R >> 261 | endobj 262 | xref 263 | 0 15 264 | 0000000000 65535 f 265 | 0000003414 00000 n 266 | 0000000187 00000 n 267 | 0000003133 00000 n 268 | 0000000022 00000 n 269 | 0000000169 00000 n 270 | 0000000297 00000 n 271 | 0000003098 00000 n 272 | 0000000365 00000 n 273 | 0000003078 00000 n 274 | 0000003222 00000 n 275 | 0000003272 00000 n 276 | 0000003295 00000 n 277 | 0000003348 00000 n 278 | 0000003372 00000 n 279 | trailer 280 | << /Size 15 /Root 10 0 R /Info 1 0 R /ID [ 281 | ] >> 282 | startxref 283 | 3519 284 | %%EOF'; 285 | } 286 | 287 | /** 288 | * Save the PDF to a file 289 | * 290 | * @param $filename 291 | * @return $this 292 | */ 293 | public function save($filename, $overwrite = false) 294 | { 295 | $this->filename = $filename; 296 | return $this; 297 | } 298 | } 299 | --------------------------------------------------------------------------------