├── .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 | [](https://github.com/fruitcake/laravel-weasyprint/actions/workflows/run-tests.yml)
3 | [](http://choosealicense.com/licenses/mit/)
4 | [](https://packagist.org/packages/fruitcake/laravel-weasyprint)
5 | [](https://packagist.org/packages/fruitcake/laravel-weasyprint)
6 | [](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?bJ0c?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?|?y??+
221 | =?X???_,?,S-?,Y)YXm?????Ěk]c}džj?c?Φ?浭?-?v??};?]???N????"?&?1=?x????tv(??}???????\'{\'??I?ߝY?Σ?
222 |
223 | ??-r?q?r?.d.?_xp??Uە?Z???M?v?m???=????+K?G?ǔ????
224 | ^???W?W????b?j?>:>?>?>?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 |
--------------------------------------------------------------------------------