封装一个wkhtmltopdf的扩展类
# 封装一个wkhtmltopdf的通用类
# wkhtmltopdf基本使用
wkhtmltopdf --margin-bottom 10 --footer-right [page]/[topage] --footer-line --footer-spacing 3 C:/Users/admin/AppData/Local/Temp/wkh4125.html E:/pdf/test.pdf
1
# 通用类
<?php
/**
* Created by PhpStorm
* @Author: wangwin
* @Date: 2024/7/24
* @Time: 17:52
* @Version: 1.0
*/
require __DIR__.'/../vendor/autoload.php'; // 引入Composer的自动加载文件
class PDFGenerator4
{
protected $binary;
protected $options = [];
protected $defaultOptions = [
'page-size' => 'A4',
'orientation' => 'Portrait',
'encoding' => 'UTF-8',
'margin-top' => 10,
'margin-right' => 10,
'margin-bottom' => 10,
'margin-left' => 10,
];
protected $tempFilePath;
public function __construct($binary = '/usr/local/bin/wkhtmltopdf')
{
$this->binary = $binary;
$this->options = $this->defaultOptions;
}
public function setOption($key, $value)
{
$this->options[$key] = $value;
return $this;
}
public function setOptions(array $options)
{
$this->options = array_merge($this->options, $options);
return $this;
}
protected function buildCommand($input, $output)
{
$command = escapeshellcmd($this->binary);
foreach ( $this->options as $key => $value ) {
if (is_bool($value)) {
$command .= $value ? " --$key" : "";
} else {
$command .= " --$key ".escapeshellarg($value);
}
}
$command .= " ".escapeshellarg($input)." ".escapeshellarg($output);
return $command;
}
public function generateFromHtml($html)
{
$tempDir = sys_get_temp_dir();
$this->tempFilePath = tempnam($tempDir, 'wkhtmlto').'.pdf';
$tempHtmlFile = tempnam($tempDir, 'html_').'.html';
file_put_contents($tempHtmlFile, $html);
$command = $this->buildCommand($tempHtmlFile, $this->tempFilePath);
exec($command, $output, $returnVar);
unlink($tempHtmlFile); // 删除临时 HTML 文件
if ($returnVar !== 0) {
if (file_exists($this->tempFilePath)) {
unlink($this->tempFilePath);
}
throw new Exception("Could not create PDF: ".implode("\n", $output));
}
return $this;
}
public function generateFromUrl($url)
{
$tempDir = sys_get_temp_dir();
$this->tempFilePath = tempnam($tempDir, 'wkhtmlto').'.pdf';
$command = $this->buildCommand($url, $this->tempFilePath);
exec($command, $output, $returnVar);
if ($returnVar !== 0) {
if (file_exists($this->tempFilePath)) {
unlink($this->tempFilePath);
}
throw new Exception("Could not create PDF: ".implode("\n", $output));
}
return $this;
}
public function download()
{
if ($this->tempFilePath === null || ! file_exists($this->tempFilePath)) {
throw new Exception("No PDF generated to download.");
}
$pdfData = file_get_contents($this->tempFilePath);
unlink($this->tempFilePath); // 删除临时 PDF 文件
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="document.pdf"');
header('Content-Length: '.strlen($pdfData));
echo $pdfData;
}
public function save($path)
{
if ($this->tempFilePath === null || ! file_exists($this->tempFilePath)) {
throw new Exception("No PDF generated to save.");
}
if (! file_put_contents($path, file_get_contents($this->tempFilePath))) {
throw new Exception("Could not save PDF to the specified path.");
}
unlink($this->tempFilePath); // 删除临时 PDF 文件
}
// public function renderTemplate($template, $data = [])
// {
// $html = file_get_contents($template);
//
// foreach ( $data as $key => $value ) {
// $html = str_replace("{{$key}}", $value, $html);
// }
//
// return $html;
// }
public function renderTemplate($templatePath, array $data = [])
{
if (! file_exists($templatePath)) {
throw new Exception("Template file does not exist.");
}
$template = file_get_contents($templatePath);
$output = $this->parseTemplate($template, $data);
return $output;
}
protected function parseTemplate($template, array $data)
{
// Basic variable replacement
$template = preg_replace_callback('/{{\s*(\w+)\s*}}/', function ($matches) use ($data) {
return $data[$matches[1]] ?? '';
}, $template);
// Conditional statements
$template = preg_replace_callback('/@if\s*\((.*?)\)\s*(.*?)@endif/s', function ($matches) use ($data) {
$condition = $this->evaluateCondition($matches[1], $data);
return $condition ? $matches[2] : '';
}, $template);
// Looping statements
$template = preg_replace_callback('/@foreach\s*\(\$([\w]+)\s+as\s+(\w+)\)\s*(.*?)@endforeach/s', function ($matches) use ($data) {
$arrayKey = $matches[1];
$itemKey = $matches[2];
$loopContent = $matches[3];
$array = $data[$arrayKey] ?? [];
$output = '';
foreach ( $array as $item ) {
$loopContentWithItem = str_replace("{{$itemKey}}", $item, $loopContent);
$output .= $loopContentWithItem;
}
return $output;
}, $template);
// PHP function calls
$template = preg_replace_callback('/{{\s*([\w]+)\s*\((.*?)\)\s*}}/', function ($matches) use ($data) {
$function = $matches[1];
$args = array_map('trim', explode(',', $matches[2]));
return $this->callPhpFunction($function, $args);
}, $template);
return $template;
}
protected function evaluateCondition($condition, array $data)
{
// Simple condition evaluation
$condition = trim($condition);
if (isset($data[$condition])) {
return (bool)$data[$condition];
}
// Evaluate numeric and boolean conditions
if (preg_match('/^\d+$/', $condition)) {
return (bool)(int)$condition;
}
if (in_array(strtolower($condition), ['true', 'false'], true)) {
return strtolower($condition) === 'true';
}
return false;
}
protected function callPhpFunction($name, array $args)
{
if (function_exists($name)) {
return call_user_func_array($name, $args);
}
return '';
}
}
$pdfGenerator = new PDFGenerator4('wkhtmltopdf');
$pdfGenerator->setOption('page-size', 'Letter')
->setOption('orientation', 'Landscape');
// Template file path
$templatePath = './template.html';
// Template data
$data = [
'title' => 'My PDF Title',
'content' => 'This is the content of the PDF.',
'items' => ['Item 1', 'Item 2', 'Item 3'],
'showContent' => true,
];
// Render template
$html = $pdfGenerator->renderTemplate($templatePath, $data);
// 生成 PDF
// $html = '<html><body><h1>Hello, world1111111111111111!</h1></body></html>';
$pdfGenerator->generateFromHtml($html);
// 下载 PDF
$pdfGenerator->download();
// 或者保存 PDF
// $pdfGenerator->save(__DIR__.'/output/document22.pdf');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# 通用类常见选项参数及其意义
页面大小:
'page-size'
:设置生成的 PDF 页面尺寸。例如:'A4'
:标准的A4纸大小。'Letter'
:美国信纸大小。'Legal'
:法律文书大小。'A3'
:比A4更大的纸张。
页面方向:
'orientation'
:设置页面的方向,可以是:'Portrait'
:纵向。'Landscape'
:横向。
编码:
'encoding'
:指定生成 PDF 时使用的字符编码。例如'UTF-8'
。
页边距:
'margin-top'
:页面内容到页面顶部的距离,单位为毫米。'margin-right'
:页面内容到页面右侧的距离,单位为毫米。'margin-bottom'
:页面内容到页面底部的距离,单位为毫米。'margin-left'
:页面内容到页面左侧的距离,单位为毫米。
打印背景:
'print-media-type'
:是否使用打印媒体类型的样式(CSS)。通常为布尔值:true
:使用打印媒体类型样式。false
:不使用打印媒体类型样式。
页面标题:
'title'
:设置生成的 PDF 的页面标题。
页眉和页脚:
'header-html'
:指定包含页眉 HTML 内容的文件路径。'footer-html'
:指定包含页脚 HTML 内容的文件路径。'header-spacing'
:页眉与页面内容之间的距离,单位为毫米。'footer-spacing'
:页脚与页面内容之间的距离,单位为毫米。
缩放:
'zoom'
:页面的缩放比例。例如:1.0
:原始大小。1.5
:放大1.5倍。0.5
:缩小为原来的一半。
自定义水印:
'watermark'
:设置水印内容。这通常是一个字符串,会在所有页面上显示。
分页:
'page-width'
:设置页面的宽度,单位为像素。'page-height'
:设置页面的高度,单位为像素。'disable-smart-shrinking'
:是否禁用智能缩小,布尔值:true
:禁用智能缩小。false
:启用智能缩小。
图片质量:
'image-dpi'
:设置图像的DPI(每英寸的点数),影响图片质量。'lowquality'
:是否生成低质量的PDF,布尔值:true
:生成低质量的PDF。false
:生成高质量的PDF。
延迟:
'no-stop-slow-scripts'
:不停止运行慢脚本,布尔值:true
:继续运行慢脚本。false
:停止慢脚本。
'javascript-delay'
:页面加载后延迟的时间,单位为毫秒。
其他选项:
'disable-javascript'
:是否禁用 JavaScript,布尔值:true
:禁用 JavaScript。false
:启用 JavaScript。
'no-background'
:是否不打印背景,布尔值:true
:不打印背景。false
:打印背景。
// 实例化 PDFGenerator
$pdfGenerator = new PDFGenerator3('wkhtmltopdf');
// 设置选项
$pdfGenerator->setOption('page-size', 'A4') // 设置页面尺寸为A4
->setOption('orientation', 'Portrait') // 设置页面方向为纵向
->setOption('margin-top', 20) // 设置上边距为20毫米
->setOption('margin-right', 20) // 设置右边距为20毫米
->setOption('margin-bottom', 20) // 设置下边距为20毫米
->setOption('margin-left', 20) // 设置左边距为20毫米
->setOption('print-media-type', true) // 使用打印媒体类型的样式
->setOption('header-html', '/path/to/header.html') // 指定页眉HTML文件
->setOption('footer-html', '/path/to/footer.html') // 指定页脚HTML文件
->setOption('zoom', 1.2); // 设置页面缩放比例为1.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 注意事项
- 布尔值选项可以使用
true
或false
。 - 数值选项(如边距、缩放等)可以使用整数或浮点数,单位通常为毫米或像素,具体取决于选项。
- 选项的具体值和支持情况可能会随
wkhtmltopdf
的版本有所变化,建议参考 wkhtmltopdf 的官方文档 (opens new window) 以获取最新的选项及其说明。
# laravel中封装snany的一个类
<?php
/**
* Created by PhpStorm
* @Author: wangwin
* @Date: 2024/7/19
* @Time: 17:29
* @Version: 1.0
*/
namespace App\Extend\Services;
use Barryvdh\Snappy\Facades\SnappyPdf;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Exception;
use Illuminate\Http\Response;
class PdfDownloadService
{
protected ?string $headerView;
protected ?string $footerView;
/**
* PdfDownloadService constructor.
*
* @param string|null $headerView
* @param string|null $footerView
*/
public function __construct(?string $headerView = 'pdf.header', ?string $footerView = 'pdf.footer')
{
$this->headerView = $headerView;
$this->footerView = $footerView;
}
/**
* 设置页眉视图
*
* @param string $view
*/
public function setHeaderView(string $view)
{
$this->headerView = $view;
}
/**
* 设置页脚视图
*
* @param string $view
*/
public function setFooterView(string $view)
{
$this->footerView = $view;
}
/**
* 生成 PDF 文件
*
* @param string $viewName 视图名称
* @param array $data 视图数据
* @param array $options PDF 选项
* @param string|null $prefix 文件前缀
* @param bool $download 是否下载
* @param bool $save 是否保存
* @param string $savePath 保存路径
* @return \Illuminate\Http\Response|string
*/
public function generatePdf(string $viewName, array $data, array $options = [], string $prefix = null, bool $download = true, bool $save = false, string $savePath = '')
{
try {
// 渲染页眉和页脚
$headerHtml = View::make($this->headerView)->render();
$footerHtml = View::make($this->footerView)->render();
// 合并 PDF 选项
$pdfOptions = array_merge([
'header-html' => $headerHtml,
'header-line' => true,
'header-spacing' => 10,
'footer-html' => $footerHtml,
'footer-center' => '第[page]页,共[topage]页',
'footer-spacing' => 6,
'footer-font-size' => 8,
], $options);
// 创建 PDF 实例
$pdf = SnappyPdf::loadView($viewName, $data);
$pdf->setOptions($pdfOptions);
// 生成唯一文件名
$filename = $this->generateFilename($prefix).'.pdf';
$filePath = rtrim($savePath, '/').'/'.$filename;
// 根据需求保存、下载或流式传输 PDF
if ($save) {
$pdf->save($filePath);
return $filePath; // 返回文件路径
}
$filename=rawurlencode($filename);
if ($download) {
return $pdf->download($filename);
}
return $pdf->stream(rawurlencode($filename));
} catch (Exception $e) {
// 处理异常,返回错误信息
return 'Error generating PDF: '.$e->getMessage();
}
}
/**
* 用于调试页面
* @param string $viewName
* @param array $data
* @return \Illuminate\Container\Container|mixed|object
*/
public function view(string $viewName, array $data): mixed
{
return view($viewName, $data);
}
/**
* 生成自定义文件名
*
* @param string|null $prefix 文件前缀
* @return string
*/
protected function generateFilename(?string $prefix = null): string
{
// 使用 UUID 生成唯一文件名
// $uuid = Str::uuid()->toString();
$uuid = date('Y_m_d_H_i_s');
return ($prefix ? $prefix.'_' : '').$uuid;
}
/**
* 直接操作 SnappyPdf 实例
*
* @return SnappyPdf
*/
public function getPdfInstance(): SnappyPdf
{
return SnappyPdf::getFacadeRoot();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
在线编辑 (opens new window)
上次更新: 2025/02/25, 18:30:54