@things-factory/integration-headless 7.0.39 → 7.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist-server/engine/connector/headless-connector.d.ts +1 -0
  2. package/dist-server/engine/connector/headless-connector.js +3 -0
  3. package/dist-server/engine/connector/headless-connector.js.map +1 -1
  4. package/dist-server/engine/task/headless-pdf-capture-board.d.ts +1 -0
  5. package/dist-server/engine/task/headless-pdf-capture-board.js +311 -0
  6. package/dist-server/engine/task/headless-pdf-capture-board.js.map +1 -0
  7. package/dist-server/engine/task/headless-pdf-capture.js +78 -19
  8. package/dist-server/engine/task/headless-pdf-capture.js.map +1 -1
  9. package/dist-server/engine/task/headless-pdf-open.js +246 -18
  10. package/dist-server/engine/task/headless-pdf-open.js.map +1 -1
  11. package/dist-server/engine/task/headless-pdf-save.js +43 -110
  12. package/dist-server/engine/task/headless-pdf-save.js.map +1 -1
  13. package/dist-server/engine/task/index.d.ts +1 -0
  14. package/dist-server/engine/task/index.js +1 -0
  15. package/dist-server/engine/task/index.js.map +1 -1
  16. package/dist-server/tsconfig.tsbuildinfo +1 -1
  17. package/helps/integration/connector/headless-connector.ja.md +71 -0
  18. package/helps/integration/connector/headless-connector.ko.md +72 -0
  19. package/helps/integration/connector/headless-connector.md +71 -0
  20. package/helps/integration/connector/headless-connector.ms.md +73 -0
  21. package/helps/integration/connector/headless-connector.zh.md +71 -0
  22. package/helps/integration/task/headless-pdf-capture-board.ja.md +51 -0
  23. package/helps/integration/task/headless-pdf-capture-board.ko.md +51 -0
  24. package/helps/integration/task/headless-pdf-capture-board.md +51 -0
  25. package/helps/integration/task/headless-pdf-capture-board.ms.md +53 -0
  26. package/helps/integration/task/headless-pdf-capture-board.zh.md +51 -0
  27. package/helps/integration/task/headless-pdf-capture.ja.md +45 -0
  28. package/helps/integration/task/headless-pdf-capture.ko.md +45 -0
  29. package/helps/integration/task/headless-pdf-capture.md +45 -0
  30. package/helps/integration/task/headless-pdf-capture.ms.md +47 -0
  31. package/helps/integration/task/headless-pdf-capture.zh.md +45 -0
  32. package/helps/integration/task/headless-pdf-open.ja.md +55 -0
  33. package/helps/integration/task/headless-pdf-open.ko.md +55 -0
  34. package/helps/integration/task/headless-pdf-open.md +55 -0
  35. package/helps/integration/task/headless-pdf-open.ms.md +57 -0
  36. package/helps/integration/task/headless-pdf-open.zh.md +55 -0
  37. package/helps/integration/task/headless-pdf-save.ja.md +23 -0
  38. package/helps/integration/task/headless-pdf-save.ko.md +25 -0
  39. package/helps/integration/task/headless-pdf-save.md +23 -0
  40. package/helps/integration/task/headless-pdf-save.ms.md +23 -0
  41. package/helps/integration/task/headless-pdf-save.zh.md +23 -0
  42. package/package.json +5 -3
  43. package/server/engine/connector/headless-connector.ts +4 -0
  44. package/server/engine/task/headless-pdf-capture-board.ts +363 -0
  45. package/server/engine/task/headless-pdf-capture.ts +85 -20
  46. package/server/engine/task/headless-pdf-open.ts +277 -18
  47. package/server/engine/task/headless-pdf-save.ts +53 -124
  48. package/server/engine/task/index.ts +1 -0
  49. package/translations/en.json +1 -0
  50. package/translations/ko.json +1 -0
@@ -0,0 +1,23 @@
1
+ ### 无头PDF保存任务 (Headless PDF Save Task)
2
+
3
+ **无头PDF保存任务**是将先前生成的PDF文档保存为文件的任务。此任务将PDF文档保存为附件,并返回保存文件的元数据。
4
+
5
+ #### 主要功能
6
+
7
+ - **保存PDF文档**: 保存由`headless-pdf-open`、`headless-pdf-capture`和`headless-pdf-capture-board`任务生成的PDF文档。文档将以指定的文件名保存,并返回文件的路径和元数据。
8
+ - **添加最后一页**: 如果在`headless-pdf-open`任务中指定了最后一页,则在保存之前将最后一页添加到PDF文档中。
9
+ - **保存为附件**: 将生成的PDF文档保存为附件,并将文件元数据记录到数据库中。保存的文件将作为`Attachment`记录进行管理。
10
+
11
+ #### 返回数据
12
+
13
+ - **id**: 保存的附件的唯一ID。
14
+ - **name**: 附件的名称。
15
+ - **path**: 附件的路径。
16
+ - **mimetype**: 文件的MIME类型(例如:`application/pdf`)。
17
+ - **refBy**: 引用信息(此任务中为`pdf-published`)。
18
+ - **fullpath**: 文件的完整路径。
19
+
20
+ #### 使用注意事项
21
+
22
+ - **必须先执行`headless-pdf-open`和`headless-pdf-capture`任务**: `headless-pdf-save`任务必须在执行了`headless-pdf-open`和必要的`headless-pdf-capture`任务后运行,否则会因为没有PDF文档可保存而导致错误。
23
+ - **文件名设置**: PDF文档的文件名默认设置为`document-<时间戳>.pdf`,如有需要,可以在`headless-pdf-open`中设置自定义文件名。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-headless",
3
- "version": "7.0.39",
3
+ "version": "7.0.41",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -22,10 +22,12 @@
22
22
  "clean": "npm run clean:server"
23
23
  },
24
24
  "dependencies": {
25
+ "@things-factory/attachment-base": "^7.0.41",
26
+ "@things-factory/board-service": "^7.0.41",
25
27
  "@things-factory/integration-base": "^7.0.38",
26
- "@things-factory/pdf": "^7.0.37",
28
+ "@things-factory/shell": "^7.0.33",
27
29
  "ejs": "^3.1.10",
28
30
  "pdf-lib": "^1.17.1"
29
31
  },
30
- "gitHead": "97f7588d477aa1d6083b51113d7666b42f530989"
32
+ "gitHead": "9d9bd55f0ab565cf41cdc33fdaf416f7e5c26f5c"
31
33
  }
@@ -59,6 +59,10 @@ export class HeadlessConnector implements Connector {
59
59
  get description() {
60
60
  return 'Headless Pool Connector'
61
61
  }
62
+
63
+ get help() {
64
+ return 'integration/connector/headless-connector'
65
+ }
62
66
  }
63
67
 
64
68
  ConnectionManager.registerConnector('headless-connector', new HeadlessConnector())
@@ -0,0 +1,363 @@
1
+ import * as ejs from 'ejs'
2
+ import { TaskRegistry } from '@things-factory/integration-base'
3
+ import { ConnectionManager } from '@things-factory/integration-base'
4
+ import { access } from '@things-factory/utils'
5
+ import { BoardFunc } from '@things-factory/board-service'
6
+ import { PDFDocument } from 'pdf-lib'
7
+
8
+ const PAGE_FORMATS = {
9
+ A4: { width: 595.28, height: 841.89 },
10
+ A3: { width: 841.89, height: 1190.55 },
11
+ Letter: { width: 612, height: 792 },
12
+ Legal: { width: 612, height: 1008 }
13
+ }
14
+
15
+ async function HeadlessPDFCaptureBoard(step, context) {
16
+ var { connection: connectionName, params: stepOptions } = step
17
+ var {
18
+ accessor,
19
+ board,
20
+ draft,
21
+ format = 'A4', // Set default value to A4
22
+ width,
23
+ height,
24
+ marginTop = 0,
25
+ marginBottom = 0,
26
+ marginLeft = 0,
27
+ marginRight = 0,
28
+ scale,
29
+ printBackground,
30
+ landscape,
31
+ preferCSSPageSize
32
+ } = stepOptions || {}
33
+ var { domain, data, user, logger, __headless_pdf } = context
34
+
35
+ if (!__headless_pdf) {
36
+ throw new Error('It requires headless-pdf-open to be performed first')
37
+ }
38
+
39
+ if (!board || !board.id) {
40
+ throw new Error('The board property must be set')
41
+ }
42
+
43
+ var { pdfDoc, header, footer, watermark } = __headless_pdf
44
+
45
+ var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
46
+ let browser
47
+
48
+ try {
49
+ browser = await headlessPool.acquire()
50
+ const page = await browser.newPage()
51
+
52
+ const boardInput = access(accessor, data)
53
+
54
+ var { model, base } = await BoardFunc.headlessModel({ domain, id: board.id, model }, draft)
55
+ const [fontsToUse, fontStyles] = await BoardFunc.fonts(domain)
56
+
57
+ model.fonts = fontsToUse
58
+ model.fontStyles = fontStyles
59
+
60
+ var { width: boardWidthPx, height: boardHeightPx } = model
61
+
62
+ if (preferCSSPageSize) {
63
+ width = undefined
64
+ height = undefined
65
+ } else if (PAGE_FORMATS[format]) {
66
+ const pageDimensions = PAGE_FORMATS[format]
67
+ width = pageDimensions.width
68
+ height = pageDimensions.height
69
+ }
70
+
71
+ if (landscape) {
72
+ ;[width, height] = [height, width]
73
+ }
74
+
75
+ const contentWidth = width - marginLeft - marginRight
76
+ const contentHeight = height - marginTop - marginBottom
77
+
78
+ const boardWidthPt = (boardWidthPx * 72) / 96
79
+ const boardHeightPt = (boardHeightPx * 72) / 96
80
+
81
+ const widthRatio = contentWidth / boardWidthPt
82
+ const heightRatio = contentHeight / boardHeightPt
83
+ const ratio = Math.min(widthRatio, heightRatio)
84
+
85
+ const scaledBoardWidthPt = boardWidthPt * ratio
86
+ const scaledBoardHeightPt = boardHeightPt * ratio
87
+
88
+ const offsetX = (contentWidth - scaledBoardWidthPt) / 2 + marginLeft
89
+ const offsetY = (contentHeight - scaledBoardHeightPt) / 2 + marginBottom
90
+
91
+ await page.setViewport({ width: Math.round(boardWidthPx * ratio), height: Math.round(boardHeightPx * ratio) })
92
+ await page.setRequestInterception(true)
93
+ await page.setDefaultTimeout(10000)
94
+
95
+ page.on('console', async msg => {
96
+ console.log(`[browser ${msg.type()}] ${msg.text()}`)
97
+ })
98
+
99
+ page.on('requestfailed', request => {
100
+ console.log('Request failed:', request.url())
101
+ })
102
+
103
+ const protocol = 'http'
104
+ const host = 'localhost'
105
+ const port = process.env.PORT ? `:${process.env.PORT}` : ''
106
+ const path = '/internal-board-service-view'
107
+ const url = `${protocol}://${host}${port}${path}`
108
+
109
+ const token = await user?.sign()
110
+
111
+ page.on('request', request => {
112
+ if (request.url() === url) {
113
+ request.continue({
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ 'x-things-factory-domain': domain?.subdomain,
118
+ Authorization: 'Bearer ' + token
119
+ },
120
+ postData: JSON.stringify({
121
+ model,
122
+ base
123
+ })
124
+ })
125
+ } else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
126
+ request.continue({
127
+ headers: {
128
+ ...request.headers(),
129
+ 'x-things-factory-domain': domain?.subdomain,
130
+ Authorization: 'Bearer ' + token
131
+ }
132
+ })
133
+ } else {
134
+ request.continue()
135
+ }
136
+ })
137
+
138
+ await page.goto(url)
139
+
140
+ await page.evaluate(data => {
141
+ //@ts-ignore
142
+ s.data = data
143
+ return new Promise(resolve => {
144
+ requestAnimationFrame(() => resolve(0))
145
+ })
146
+ }, boardInput)
147
+
148
+ const renderTemplateSafely = (template, data) => {
149
+ try {
150
+ return ejs.render(template, data)
151
+ } catch (error) {
152
+ logger.warn(`Template rendering error: ${error.message}`)
153
+ return template
154
+ }
155
+ }
156
+
157
+ header = renderTemplateSafely(header, __headless_pdf)
158
+ footer = renderTemplateSafely(footer, __headless_pdf)
159
+
160
+ // Apply header, footer, and watermark using Puppeteer
161
+ // Apply header, footer, and watermark using Puppeteer
162
+ if (header || footer || watermark) {
163
+ await page.evaluate(
164
+ ({ header, footer, watermark, isLandscape }) => {
165
+ const setPositioning = (element, position) => {
166
+ element.style.position = 'fixed'
167
+ element.style.left = '0'
168
+ element.style.width = '100%'
169
+ element.style.textAlign = 'center'
170
+ element.style.fontSize = '12px'
171
+ element.style.margin = '0'
172
+ element.style.padding = '0'
173
+ if (position === 'header') {
174
+ element.style.top = '0'
175
+ } else if (position === 'footer') {
176
+ element.style.bottom = '0'
177
+ }
178
+ }
179
+
180
+ if (header) {
181
+ const headerElement = document.createElement('div')
182
+ headerElement.innerHTML = header
183
+ setPositioning(headerElement, 'header')
184
+ document.body.appendChild(headerElement)
185
+ }
186
+
187
+ if (footer) {
188
+ const footerElement = document.createElement('div')
189
+ footerElement.innerHTML = footer
190
+ setPositioning(footerElement, 'footer')
191
+ document.body.appendChild(footerElement)
192
+ }
193
+
194
+ if (watermark) {
195
+ const watermarkElement = document.createElement('div')
196
+ watermarkElement.innerHTML = watermark
197
+ watermarkElement.style.position = 'fixed'
198
+ watermarkElement.style.top = isLandscape ? '40%' : '50%'
199
+ watermarkElement.style.left = '50%'
200
+ watermarkElement.style.transform = 'translate(-50%, -50%) rotate(-45deg)'
201
+ watermarkElement.style.opacity = '0.2'
202
+ watermarkElement.style.fontSize = '50px'
203
+ watermarkElement.style.color = 'red'
204
+ watermarkElement.style.pointerEvents = 'none'
205
+ watermarkElement.style.zIndex = '9999'
206
+ document.body.appendChild(watermarkElement)
207
+ }
208
+ },
209
+ { header, footer, watermark, isLandscape: landscape }
210
+ )
211
+ }
212
+
213
+ const pageOptions = {
214
+ format,
215
+ width: `${scaledBoardWidthPt}px`,
216
+ height: `${scaledBoardHeightPt}px`,
217
+ margin: {
218
+ top: `${offsetY}px`,
219
+ right: `${offsetX}px`,
220
+ bottom: `${offsetY}px`,
221
+ left: `${offsetX}px`
222
+ },
223
+ scale,
224
+ printBackground,
225
+ landscape,
226
+ displayHeaderFooter: false, // Handled via Puppeteer directly
227
+ preferCSSPageSize
228
+ }
229
+
230
+ const contentBuffer = await page.pdf(pageOptions)
231
+ const contentPdfDoc = await PDFDocument.load(contentBuffer)
232
+ const copiedPages = await pdfDoc.copyPages(contentPdfDoc, contentPdfDoc.getPageIndices())
233
+
234
+ copiedPages.forEach(page => pdfDoc.addPage(page))
235
+
236
+ __headless_pdf.pageCount += copiedPages.length
237
+
238
+ await page.close()
239
+ headlessPool.release(browser)
240
+
241
+ return {
242
+ data: __headless_pdf
243
+ }
244
+ } catch (error) {
245
+ if (browser) {
246
+ await browser.close()
247
+ }
248
+
249
+ throw error
250
+ }
251
+ }
252
+
253
+ HeadlessPDFCaptureBoard.parameterSpec = [
254
+ {
255
+ type: 'scenario-step-input',
256
+ name: 'accessor',
257
+ label: 'accessor'
258
+ },
259
+ {
260
+ type: 'resource-object',
261
+ name: 'board',
262
+ label: 'board',
263
+ property: {
264
+ queryName: 'boards'
265
+ }
266
+ },
267
+ {
268
+ type: 'boolean',
269
+ name: 'draft',
270
+ label: 'board-draft',
271
+ defaultValue: false,
272
+ description: 'Set whether to get the current working version or the last released version'
273
+ },
274
+ {
275
+ type: 'select',
276
+ name: 'format',
277
+ label: 'page-format',
278
+ property: {
279
+ options: [
280
+ { display: '', value: '' },
281
+ { display: 'A4', value: 'A4' },
282
+ { display: 'A3', value: 'A3' },
283
+ { display: 'Letter', value: 'Letter' },
284
+ { display: 'Legal', value: 'Legal' }
285
+ ]
286
+ },
287
+ description: 'Select the paper format for the PDF'
288
+ },
289
+ {
290
+ type: 'string',
291
+ name: 'width',
292
+ label: 'page-width',
293
+ placeholder: '(e.g., "8.5in", "21cm", "600px")',
294
+ description: 'Specify the width of the page (e.g., "8.5in", "21cm", "600px")'
295
+ },
296
+ {
297
+ type: 'string',
298
+ name: 'height',
299
+ label: 'page-height',
300
+ placeholder: '(e.g., "11in", "29.7cm", "800px")',
301
+ description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
302
+ },
303
+ {
304
+ type: 'string',
305
+ name: 'marginTop',
306
+ label: 'page-margin-top',
307
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
308
+ description: 'Set the top margin for the page'
309
+ },
310
+ {
311
+ type: 'string',
312
+ name: 'marginBottom',
313
+ label: 'page-margin-bottom',
314
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
315
+ description: 'Set the bottom margin for the page'
316
+ },
317
+ {
318
+ type: 'string',
319
+ name: 'marginLeft',
320
+ label: 'page-margin-left',
321
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
322
+ description: 'Set the left margin for the page'
323
+ },
324
+ {
325
+ type: 'string',
326
+ name: 'marginRight',
327
+ label: 'page-margin-right',
328
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
329
+ description: 'Set the right margin for the page'
330
+ },
331
+ {
332
+ type: 'number',
333
+ name: 'scale',
334
+ label: 'page-scale',
335
+ defaultValue: 1,
336
+ description: 'Set the scale of the page content (default is 1)'
337
+ },
338
+ {
339
+ type: 'boolean',
340
+ name: 'printBackground',
341
+ label: 'print-background',
342
+ defaultValue: true,
343
+ description: 'Include background graphics when printing the page'
344
+ },
345
+ {
346
+ type: 'boolean',
347
+ name: 'landscape',
348
+ label: 'landscape',
349
+ defaultValue: false,
350
+ description: 'Print the PDF in landscape orientation'
351
+ },
352
+ {
353
+ type: 'boolean',
354
+ name: 'preferCSSPageSize',
355
+ label: 'prefer-css-page-size',
356
+ defaultValue: false,
357
+ description: 'Whether to prefer the CSS-defined page size over the given width and height'
358
+ }
359
+ ]
360
+
361
+ HeadlessPDFCaptureBoard.help = 'integration/task/headless-pdf-capture-board'
362
+
363
+ TaskRegistry.registerTaskHandler('headless-pdf-capture-board', HeadlessPDFCaptureBoard)
@@ -1,11 +1,13 @@
1
- import { PDFDocument } from 'pdf-lib'
2
-
1
+ import * as ejs from 'ejs'
3
2
  import { TaskRegistry } from '@things-factory/integration-base'
4
3
  import { ConnectionManager } from '@things-factory/integration-base'
4
+ import { access } from '@things-factory/utils'
5
+ import { PDFDocument } from 'pdf-lib'
5
6
 
6
7
  async function HeadlessPDFCapture(step, context) {
7
8
  var { connection: connectionName, params: stepOptions } = step
8
9
  var {
10
+ accessor,
9
11
  htmlContent,
10
12
  format,
11
13
  width,
@@ -19,26 +21,90 @@ async function HeadlessPDFCapture(step, context) {
19
21
  landscape,
20
22
  preferCSSPageSize
21
23
  } = stepOptions || {}
22
- var { domain, __headless_pdf } = context
24
+ var { domain, data, logger, __headless_pdf } = context
23
25
 
24
26
  if (!__headless_pdf) {
25
27
  throw new Error('It requires headless-pdf-open to be performed first')
26
28
  }
27
29
 
28
- var { pdf, pageCount } = __headless_pdf
30
+ var { pdfDoc, header, footer, watermark } = __headless_pdf
29
31
 
30
32
  var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
31
33
  let browser
32
34
 
33
35
  try {
34
- // Puppeteer를 사용하여 브라우저와 페이지 인스턴스를 생성
35
36
  browser = await headlessPool.acquire()
36
37
  const page = await browser.newPage()
37
38
 
38
- // 설정된 HTML 콘텐츠를 페이지에 설정
39
- await page.setContent(htmlContent)
39
+ const templateInput = access(accessor, data)
40
+
41
+ const renderTemplateSafely = (template, data) => {
42
+ try {
43
+ return ejs.render(template, data)
44
+ } catch (error) {
45
+ logger.warn(`Template rendering error: ${error.message}`)
46
+ return template
47
+ }
48
+ }
49
+
50
+ const renderedHtmlContent = renderTemplateSafely(htmlContent, templateInput)
51
+ await page.setContent(renderedHtmlContent)
52
+
53
+ header = renderTemplateSafely(header, __headless_pdf)
54
+ footer = renderTemplateSafely(footer, __headless_pdf)
55
+
56
+ // Apply header, footer, and watermark using Puppeteer
57
+ if (header || footer || watermark) {
58
+ await page.evaluate(
59
+ ({ header, footer, watermark, isLandscape }) => {
60
+ const setPositioning = (element, position) => {
61
+ element.style.position = 'fixed'
62
+ element.style.left = '0'
63
+ element.style.width = '100%'
64
+ element.style.textAlign = 'center'
65
+ element.style.fontSize = '12px'
66
+ element.style.margin = '0'
67
+ element.style.padding = '0'
68
+ if (position === 'header') {
69
+ element.style.top = '0'
70
+ } else if (position === 'footer') {
71
+ element.style.bottom = '0'
72
+ }
73
+ }
74
+
75
+ if (header) {
76
+ const headerElement = document.createElement('div')
77
+ headerElement.innerHTML = header
78
+ setPositioning(headerElement, 'header')
79
+ document.body.appendChild(headerElement)
80
+ }
81
+
82
+ if (footer) {
83
+ const footerElement = document.createElement('div')
84
+ footerElement.innerHTML = footer
85
+ setPositioning(footerElement, 'footer')
86
+ document.body.appendChild(footerElement)
87
+ }
88
+
89
+ if (watermark) {
90
+ const watermarkElement = document.createElement('div')
91
+ watermarkElement.innerHTML = watermark
92
+ watermarkElement.style.position = 'fixed'
93
+ watermarkElement.style.top = isLandscape ? '40%' : '50%'
94
+ watermarkElement.style.left = '50%'
95
+ watermarkElement.style.transform = 'translate(-50%, -50%) rotate(-45deg)'
96
+ watermarkElement.style.opacity = '0.2'
97
+ watermarkElement.style.fontSize = '50px'
98
+ watermarkElement.style.color = 'red'
99
+ watermarkElement.style.pointerEvents = 'none'
100
+ watermarkElement.style.zIndex = '9999'
101
+ document.body.appendChild(watermarkElement)
102
+ }
103
+ },
104
+ { header, footer, watermark, isLandscape: landscape }
105
+ )
106
+ }
40
107
 
41
- // PDF 페이지 생성 옵션 설정
42
108
  if (preferCSSPageSize) {
43
109
  width = undefined
44
110
  height = undefined
@@ -57,23 +123,17 @@ async function HeadlessPDFCapture(step, context) {
57
123
  scale,
58
124
  printBackground,
59
125
  landscape,
60
- displayHeaderFooter: false, // 번째 패스에서는 헤더/푸터를 렌더링하지 않음
126
+ displayHeaderFooter: false, // Handled via Puppeteer directly
61
127
  preferCSSPageSize
62
128
  }
63
129
 
64
- // 페이지를 PDF로 변환
65
- const pageBuffer = await page.pdf(pageOptions)
66
-
67
- // PDF-lib을 사용하여 PDF를 로드하고 추가 조작
68
- const pdfDoc = await PDFDocument.load(pageBuffer)
69
- const pages = await pdf.copyPages(pdfDoc, pdfDoc.getPageIndices())
130
+ const contentBuffer = await page.pdf(pageOptions)
131
+ const contentPdfDoc = await PDFDocument.load(contentBuffer)
132
+ const copiedPages = await pdfDoc.copyPages(contentPdfDoc, contentPdfDoc.getPageIndices())
70
133
 
71
- pages.forEach(newPage => {
72
- pdf.addPage(newPage)
73
- })
134
+ copiedPages.forEach(page => pdfDoc.addPage(page))
74
135
 
75
- // 페이지 카운트 업데이트
76
- __headless_pdf.pageCount += pages.length
136
+ __headless_pdf.pageCount += copiedPages.length
77
137
 
78
138
  await page.close()
79
139
  headlessPool.release(browser)
@@ -91,6 +151,11 @@ async function HeadlessPDFCapture(step, context) {
91
151
  }
92
152
 
93
153
  HeadlessPDFCapture.parameterSpec = [
154
+ {
155
+ type: 'scenario-step-input',
156
+ name: 'accessor',
157
+ label: 'accessor'
158
+ },
94
159
  {
95
160
  type: 'textarea',
96
161
  name: 'htmlContent',