1
+ <!DOCTYPE html>
2
+ < html lang ="zh-CN ">
3
+
4
+ < head >
5
+ < meta charset ="UTF-8 ">
6
+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
7
+ < title > PDF 水印添加工具</ title >
8
+ < link href ="
https://cdn.jsdelivr.net/npm/[email protected] /dist/css/bootstrap.min.css "
rel ="
stylesheet "
9
+ integrity ="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7 " crossorigin ="anonymous ">
10
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js "> </ script >
11
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js "> </ script >
12
+ < style >
13
+ body {
14
+ font-family : Arial, sans-serif;
15
+ line-height : 1.6 ;
16
+ margin : 0 ;
17
+ padding : 20px ;
18
+ background-color : # f5f5f5 ;
19
+ color : # 333 ;
20
+ }
21
+
22
+ label {
23
+ display : block;
24
+ font-weight : bold;
25
+ margin-bottom : 5px ;
26
+ }
27
+
28
+ input [type = "text" ],
29
+ input [type = "file" ] {
30
+ width : 100% ;
31
+ padding : 10px ;
32
+ border : 1px solid # ddd ;
33
+ border-radius : 4px ;
34
+ box-sizing : border-box;
35
+ }
36
+
37
+ input [type = "color" ] {
38
+ height : 40px ;
39
+ width : 100% ;
40
+ }
41
+
42
+ input [type = "range" ] {
43
+ width : 100% ;
44
+ }
45
+
46
+ .btn {
47
+ background-color : # 3498db ;
48
+ color : white;
49
+ border : none;
50
+ padding : 10px 20px ;
51
+ cursor : pointer;
52
+ font-size : 16px ;
53
+ border-radius : 4px ;
54
+ display : block;
55
+ margin : 20px auto;
56
+ transition : background-color 0.3s ;
57
+ }
58
+
59
+ .btn : hover {
60
+ background-color : # 2980b9 ;
61
+ }
62
+
63
+ .btn : disabled {
64
+ background-color : # bdc3c7 ;
65
+ cursor : not-allowed;
66
+ }
67
+
68
+ .preview {
69
+ margin-top : 30px ;
70
+ border : 1px solid # ddd ;
71
+ border-radius : 4px ;
72
+ padding : 10px ;
73
+ background-color : # f9f9f9 ;
74
+ }
75
+
76
+ .preview iframe {
77
+ width : 100% ;
78
+ height : 400px ;
79
+ border : none;
80
+ }
81
+
82
+ .value-display {
83
+ display : inline-block;
84
+ min-width : 30px ;
85
+ text-align : right;
86
+ }
87
+ </ style >
88
+ </ head >
89
+
90
+ < body >
91
+ < div class ="container-sm mx-auto p-4 bg-white rounded shadow-sm " style ="max-width: 800px; ">
92
+ < h1 class ="text-center mb-4 " style ="color: #2c3e50 "> PDF 水印添加工具</ h1 >
93
+
94
+ < div class ="m-4 ">
95
+ < label for ="pdfFile "> 上传PDF文件:</ label >
96
+ < input type ="file " id ="pdfFile " accept ="application/pdf ">
97
+ </ div >
98
+
99
+
100
+ < div class ="d-flex gap-4 mb-4 ">
101
+ < div class ="flex-grow-1 ">
102
+ < label for ="watermarkText "> 水印文本:</ label >
103
+ < input type ="text " id ="watermarkText " value ="CONFIDENTIAL " placeholder ="输入水印文本 ">
104
+ </ div >
105
+ < div class ="flex-grow-1 ">
106
+ < label for ="watermarkColor "> 水印颜色:</ label >
107
+ < input type ="color " id ="watermarkColor " value ="#FF0000 ">
108
+ </ div >
109
+ </ div >
110
+
111
+ < div class ="d-flex gap-4 mb-4 ">
112
+ < div class ="flex-grow-1 ">
113
+ < label for ="opacity "> 不透明度: < span id ="opacityValue " class ="value-display "> 0.6</ span > </ label >
114
+ < input type ="range " id ="opacity " min ="0.1 " max ="1 " step ="0.1 " value ="0.6 ">
115
+ </ div >
116
+ < div class ="flex-grow-1 ">
117
+ < label for ="fontSize "> 字体大小: < span id ="fontSizeValue " class ="value-display "> 60</ span > px</ label >
118
+ < input type ="range " id ="fontSize " min ="20 " max ="100 " step ="1 " value ="60 ">
119
+ </ div >
120
+ < div class ="flex-grow-1 ">
121
+ < label for ="rotation "> 旋转角度: < span id ="rotationValue " class ="value-display "> 45</ span > °</ label >
122
+ < input type ="range " id ="rotation " min ="0 " max ="360 " step ="5 " value ="45 ">
123
+ </ div >
124
+ </ div >
125
+
126
+ < button id ="addWatermarkBtn " class ="btn " disabled > 添加水印</ button >
127
+
128
+ < div id ="progressContainer " class ="w-100 rounded-1 my-2 " style ="display: none; ">
129
+ < div class ="progress " style ="height: 20px; ">
130
+ < div id ="progressBar " class ="progress-bar "> </ div >
131
+ </ div >
132
+ < p id ="progressText " style ="text-align: center; "> 处理中: 0%</ p >
133
+ </ div >
134
+
135
+ < div id ="previewContainer " class ="preview " style ="display: none; ">
136
+ < h2 > PDF 预览:</ h2 >
137
+ < iframe id ="pdfPreview " title ="PDF Preview "> </ iframe >
138
+ < p class ="fs-6 mt-1 " style ="color: #7f8c8d; "> 注意: 预览显示的是原始PDF。添加水印后,将自动下载处理后的文件。</ p >
139
+ </ div >
140
+
141
+ < button id ="downloadBtn " class ="btn " style ="display: none; "> 下载 PDF</ button >
142
+ < div class ="mt-4 p-4 rounded-1 fs-6 " style ="background-color: #f8f9fa; ">
143
+ < h3 class ="mt-0 "> 使用说明:</ h3 >
144
+ < ol class ="ps-xl-0 ">
145
+ < li > 上传PDF文件</ li >
146
+ < li > 设置水印文本、颜色、不透明度、大小和角度</ li >
147
+ < li > 点击"添加水印"按钮</ li >
148
+ < li > 处理完成后,点击"下载 PDF"按钮下载带水印的文件</ li >
149
+ </ ol >
150
+ </ div >
151
+ </ div >
152
+
153
+ < script type ="module ">
154
+ // 获取DOM元素
155
+ const pdfFileInput = document . getElementById ( 'pdfFile' ) ;
156
+ const watermarkTextInput = document . getElementById ( 'watermarkText' ) ;
157
+ const watermarkColorInput = document . getElementById ( 'watermarkColor' ) ;
158
+ const opacityInput = document . getElementById ( 'opacity' ) ;
159
+ const fontSizeInput = document . getElementById ( 'fontSize' ) ;
160
+ const rotationInput = document . getElementById ( 'rotation' ) ;
161
+ const addWatermarkBtn = document . getElementById ( 'addWatermarkBtn' ) ;
162
+ const previewContainer = document . getElementById ( 'previewContainer' ) ;
163
+ const pdfPreview = document . getElementById ( 'pdfPreview' ) ;
164
+ const progressContainer = document . getElementById ( 'progressContainer' ) ;
165
+ const progressBar = document . getElementById ( 'progressBar' ) ;
166
+ const progressText = document . getElementById ( 'progressText' ) ;
167
+ const downloadBtn = document . getElementById ( 'downloadBtn' )
168
+
169
+ // 显示当前值
170
+ const opacityValue = document . getElementById ( 'opacityValue' ) ;
171
+ const fontSizeValue = document . getElementById ( 'fontSizeValue' ) ;
172
+ const rotationValue = document . getElementById ( 'rotationValue' ) ;
173
+
174
+ // 更新显示值
175
+ opacityInput . addEventListener ( 'input' , ( ) => {
176
+ opacityValue . textContent = opacityInput . value ;
177
+ } ) ;
178
+
179
+ fontSizeInput . addEventListener ( 'input' , ( ) => {
180
+ fontSizeValue . textContent = fontSizeInput . value ;
181
+ } ) ;
182
+
183
+ rotationInput . addEventListener ( 'input' , ( ) => {
184
+ rotationValue . textContent = rotationInput . value ;
185
+ } ) ;
186
+
187
+ // 文件选择处理
188
+ pdfFileInput . addEventListener ( 'change' , ( event ) => {
189
+ const file = event . target . files [ 0 ] ;
190
+ if ( file && file . type === 'application/pdf' ) {
191
+ // 启用添加水印按钮
192
+ addWatermarkBtn . disabled = false ;
193
+
194
+ // 创建预览
195
+ const fileURL = URL . createObjectURL ( file ) ;
196
+ pdfPreview . src = fileURL ;
197
+ previewContainer . style . display = 'block' ;
198
+ return ;
199
+ }
200
+
201
+ if ( file && file . type !== 'application/pdf' ) {
202
+ alert ( '请选择有效的PDF文件' ) ;
203
+ pdfFileInput . value = '' ;
204
+ addWatermarkBtn . disabled = true ;
205
+ previewContainer . style . display = 'none' ;
206
+ }
207
+ } ) ;
208
+
209
+ // 将十六进制颜色转换为RGB
210
+ function hexToRgb ( hex ) {
211
+ const r = parseInt ( hex . slice ( 1 , 3 ) , 16 ) / 255 ;
212
+ const g = parseInt ( hex . slice ( 3 , 5 ) , 16 ) / 255 ;
213
+ const b = parseInt ( hex . slice ( 5 , 7 ) , 16 ) / 255 ;
214
+ return { r, g, b } ;
215
+ }
216
+
217
+ // 添加水印处理
218
+ addWatermarkBtn . addEventListener ( 'click' , async ( ) => {
219
+ const file = pdfFileInput . files [ 0 ] ;
220
+ if ( ! file ) {
221
+ alert ( '请先选择PDF文件' ) ;
222
+ return ;
223
+ }
224
+
225
+ const watermarkText = watermarkTextInput . value || 'CONFIDENTIAL' ;
226
+ const watermarkColor = watermarkColorInput . value ;
227
+ const opacity = parseFloat ( opacityInput . value ) ;
228
+ const fontSize = parseInt ( fontSizeInput . value ) ;
229
+ const rotation = parseInt ( rotationInput . value ) ;
230
+
231
+ // 显示进度条
232
+ progressContainer . style . display = 'block' ;
233
+ progressBar . style . width = '0%' ;
234
+ progressText . textContent = '处理中: 0%' ;
235
+ addWatermarkBtn . disabled = true ;
236
+
237
+ try {
238
+ // 加载PDF文档
239
+ const arrayBuffer = await file . arrayBuffer ( ) ;
240
+ const pdfDoc = await PDFLib . PDFDocument . load ( arrayBuffer ) ;
241
+
242
+ // 获取所有页面
243
+ const pages = pdfDoc . getPages ( ) ;
244
+ const { r, g, b } = hexToRgb ( watermarkColor ) ;
245
+ const font = await pdfDoc . embedFont ( PDFLib . StandardFonts . HelveticaBold ) ;
246
+
247
+ // 处理每个页面
248
+ for ( let i = 0 ; i < pages . length ; i ++ ) {
249
+ const page = pages [ i ] ;
250
+ const { width, height } = page . getSize ( ) ;
251
+
252
+ // 计算水印位置
253
+ const textWidth = font . widthOfTextAtSize ( watermarkText , fontSize ) ;
254
+ const textHeight = font . heightAtSize ( fontSize ) ;
255
+
256
+ // 绘制水印
257
+ page . drawText ( watermarkText , {
258
+ x : width / 2 - textWidth / 2 ,
259
+ y : height / 2 - textHeight / 2 ,
260
+ size : fontSize ,
261
+ font : font ,
262
+ color : PDFLib . rgb ( r , g , b ) ,
263
+ opacity : opacity ,
264
+ rotate : PDFLib . degrees ( rotation ) ,
265
+ } ) ;
266
+
267
+ // 更新进度
268
+ const progress = Math . round ( ( ( i + 1 ) / pages . length ) * 100 ) ;
269
+ progressBar . style . width = `${ progress } %` ;
270
+ progressText . textContent = `处理中: ${ progress } %` ;
271
+
272
+ // 允许UI更新
273
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
274
+ }
275
+
276
+ // 保存带水印的PDF
277
+ const watermarkedPdfBytes = await pdfDoc . save ( ) ;
278
+
279
+ // 创建Blob并下载
280
+ const blob = new Blob ( [ watermarkedPdfBytes ] , { type : 'application/pdf' } ) ;
281
+
282
+ // 预览水印文件
283
+ const fileURL = URL . createObjectURL ( blob ) ;
284
+ pdfPreview . src = fileURL ;
285
+ previewContainer . style . display = 'block' ;
286
+
287
+ // 下载水印文件
288
+ downloadBtn . style . display = 'block' ;
289
+ downloadBtn . addEventListener ( 'click' , ( ) => {
290
+ saveAs ( blob , `watermarked_${ file . name } ` ) ;
291
+ } ) ;
292
+ } catch ( error ) {
293
+ console . error ( 'PDF处理错误:' , error ) ;
294
+ alert ( '添加水印时出错: ' + error . message ) ;
295
+ } finally {
296
+ progressContainer . style . display = 'none' ;
297
+ addWatermarkBtn . disabled = false ;
298
+ }
299
+ } ) ;
300
+ </ script >
301
+ </ body >
302
+
303
+ </ html >
0 commit comments