@things-factory/integration-headless 7.0.38 → 7.0.40

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.
@@ -2,39 +2,208 @@ import * as ejs from 'ejs'
2
2
  import { PDFDocument } from 'pdf-lib'
3
3
  import { TaskRegistry } from '@things-factory/integration-base'
4
4
  import { ConnectionManager } from '@things-factory/integration-base'
5
+ import { access } from '@things-factory/utils'
5
6
 
6
7
  async function HeadlessPDFOpen(step, context) {
7
8
  var { connection: connectionName, params: stepOptions } = step
8
9
  var {
10
+ accessor,
9
11
  coverPage,
10
12
  lastPage,
11
13
  header,
12
14
  footer,
13
15
  watermark,
14
- fileName // 추가된 파일 이름
16
+ fileName,
17
+ format,
18
+ width,
19
+ height,
20
+ marginTop,
21
+ marginBottom,
22
+ marginLeft,
23
+ marginRight,
24
+ scale,
25
+ printBackground,
26
+ landscape,
27
+ preferCSSPageSize
15
28
  } = stepOptions || {}
16
-
17
- const pdf = await PDFDocument.create()
29
+ const { data, logger } = context
18
30
 
19
31
  const headlessPool = ConnectionManager.getConnectionInstanceByName(context.domain, connectionName)
20
32
  let browser
21
33
 
34
+ // Create a new PDF document using pdf-lib
35
+ const pdfDoc = await PDFDocument.create()
36
+
22
37
  try {
23
38
  browser = await headlessPool.acquire()
24
39
  const page = await browser.newPage()
25
40
 
26
- // Cover Page를 PDF로 변환
41
+ const templateInput = access(accessor, data)
42
+
43
+ const renderTemplateSafely = (template, data) => {
44
+ try {
45
+ return ejs.render(template, data)
46
+ } catch (error) {
47
+ logger.warn(`Template rendering error: ${error.message}`)
48
+ return template
49
+ }
50
+ }
51
+
52
+ // Convert Cover Page to PDF and add it to the document (if provided)
27
53
  if (coverPage) {
28
- await page.setContent(coverPage)
29
- const coverPageBuffer = await page.pdf()
30
- coverPage = coverPageBuffer
54
+ const renderedCoverPage = renderTemplateSafely(coverPage, templateInput)
55
+
56
+ await page.setContent(renderedCoverPage)
57
+
58
+ // Apply header, footer, and watermark using Puppeteer
59
+ // Apply header, footer, and watermark using Puppeteer
60
+ if (header || footer || watermark) {
61
+ await page.evaluate(
62
+ ({ header, footer, watermark, isLandscape }) => {
63
+ const setPositioning = (element, position) => {
64
+ element.style.position = 'fixed'
65
+ element.style.left = '0'
66
+ element.style.width = '100%'
67
+ element.style.textAlign = 'center'
68
+ element.style.fontSize = '12px'
69
+ if (position === 'header') {
70
+ element.style.top = '0'
71
+ } else if (position === 'footer') {
72
+ element.style.bottom = '0'
73
+ }
74
+ }
75
+
76
+ // if (header) {
77
+ // const headerElement = document.createElement('div')
78
+ // headerElement.innerHTML = header
79
+ // setPositioning(headerElement, 'header')
80
+ // document.body.appendChild(headerElement)
81
+ // }
82
+
83
+ // if (footer) {
84
+ // const footerElement = document.createElement('div')
85
+ // footerElement.innerHTML = footer
86
+ // setPositioning(footerElement, 'footer')
87
+ // document.body.appendChild(footerElement)
88
+ // }
89
+
90
+ if (watermark) {
91
+ const watermarkElement = document.createElement('div')
92
+ watermarkElement.innerHTML = watermark
93
+ watermarkElement.style.position = 'fixed'
94
+ watermarkElement.style.top = isLandscape ? '40%' : '50%'
95
+ watermarkElement.style.left = '50%'
96
+ watermarkElement.style.transform = 'translate(-50%, -50%) rotate(-45deg)'
97
+ watermarkElement.style.opacity = '0.2'
98
+ watermarkElement.style.fontSize = '50px'
99
+ watermarkElement.style.color = 'red'
100
+ watermarkElement.style.pointerEvents = 'none'
101
+ watermarkElement.style.zIndex = '9999'
102
+ document.body.appendChild(watermarkElement)
103
+ }
104
+ },
105
+ { header, footer, watermark, isLandscape: landscape }
106
+ )
107
+ }
108
+
109
+ const pageOptions = {
110
+ format,
111
+ width,
112
+ height,
113
+ margin: {
114
+ top: marginTop,
115
+ right: marginRight,
116
+ bottom: marginBottom,
117
+ left: marginLeft
118
+ },
119
+ scale,
120
+ printBackground,
121
+ landscape,
122
+ displayHeaderFooter: false, // Handled via Puppeteer directly
123
+ preferCSSPageSize
124
+ }
125
+
126
+ const coverPageBuffer = await page.pdf(pageOptions)
127
+ const coverPageDoc = await PDFDocument.load(coverPageBuffer)
128
+ const coverPages = await pdfDoc.copyPages(coverPageDoc, coverPageDoc.getPageIndices())
129
+ coverPages.forEach(page => pdfDoc.addPage(page))
31
130
  }
32
131
 
33
- // Last Page를 PDF로 변환
132
+ var lastPageBuffer
133
+
134
+ // Process Last Page (if provided) but add it later in the save task
34
135
  if (lastPage) {
35
- await page.setContent(lastPage)
36
- const lastPageBuffer = await page.pdf()
37
- lastPage = lastPageBuffer
136
+ const renderedLastPage = renderTemplateSafely(lastPage, templateInput)
137
+ await page.setContent(renderedLastPage)
138
+
139
+ // Apply header, footer, and watermark using Puppeteer
140
+ if (header || footer || watermark) {
141
+ await page.evaluate(
142
+ ({ header, footer, watermark, isLandscape }) => {
143
+ const setPositioning = (element, position) => {
144
+ element.style.position = 'fixed'
145
+ element.style.left = '0'
146
+ element.style.width = '100%'
147
+ element.style.textAlign = 'center'
148
+ element.style.fontSize = '12px'
149
+ if (position === 'header') {
150
+ element.style.top = '0'
151
+ } else if (position === 'footer') {
152
+ element.style.bottom = '0'
153
+ }
154
+ }
155
+
156
+ // if (header) {
157
+ // const headerElement = document.createElement('div')
158
+ // headerElement.innerHTML = header
159
+ // setPositioning(headerElement, 'header')
160
+ // document.body.appendChild(headerElement)
161
+ // }
162
+
163
+ // if (footer) {
164
+ // const footerElement = document.createElement('div')
165
+ // footerElement.innerHTML = footer
166
+ // setPositioning(footerElement, 'footer')
167
+ // document.body.appendChild(footerElement)
168
+ // }
169
+
170
+ if (watermark) {
171
+ const watermarkElement = document.createElement('div')
172
+ watermarkElement.innerHTML = watermark
173
+ watermarkElement.style.position = 'fixed'
174
+ watermarkElement.style.top = isLandscape ? '40%' : '50%'
175
+ watermarkElement.style.left = '50%'
176
+ watermarkElement.style.transform = 'translate(-50%, -50%) rotate(-45deg)'
177
+ watermarkElement.style.opacity = '0.2'
178
+ watermarkElement.style.fontSize = '50px'
179
+ watermarkElement.style.color = 'red'
180
+ watermarkElement.style.pointerEvents = 'none'
181
+ watermarkElement.style.zIndex = '9999'
182
+ document.body.appendChild(watermarkElement)
183
+ }
184
+ },
185
+ { header, footer, watermark, isLandscape: landscape }
186
+ )
187
+ }
188
+
189
+ const pageOptions = {
190
+ format,
191
+ width,
192
+ height,
193
+ margin: {
194
+ top: marginTop,
195
+ right: marginRight,
196
+ bottom: marginBottom,
197
+ left: marginLeft
198
+ },
199
+ scale,
200
+ printBackground,
201
+ landscape,
202
+ displayHeaderFooter: false, // Handled via Puppeteer directly
203
+ preferCSSPageSize
204
+ }
205
+
206
+ lastPageBuffer = await page.pdf(pageOptions)
38
207
  }
39
208
 
40
209
  await page.close()
@@ -46,25 +215,29 @@ async function HeadlessPDFOpen(step, context) {
46
215
  throw error
47
216
  }
48
217
 
49
- const data = {
50
- pdf,
51
- coverPage,
52
- lastPage,
218
+ const pdfInfo = {
219
+ pdfDoc,
220
+ lastPageBuffer,
53
221
  header,
54
222
  footer,
55
223
  watermark,
56
224
  fileName,
57
- pageCount: 0
225
+ pageCount: pdfDoc.getPageCount()
58
226
  }
59
227
 
60
- context.__headless_pdf = data
228
+ context.__headless_pdf = pdfInfo
61
229
 
62
230
  return {
63
- data
231
+ data: pdfInfo
64
232
  }
65
233
  }
66
234
 
67
235
  HeadlessPDFOpen.parameterSpec = [
236
+ {
237
+ type: 'scenario-step-input',
238
+ name: 'accessor',
239
+ label: 'accessor'
240
+ },
68
241
  {
69
242
  type: 'textarea',
70
243
  name: 'coverPage',
@@ -76,26 +249,114 @@ HeadlessPDFOpen.parameterSpec = [
76
249
  label: 'pdf-last-page'
77
250
  },
78
251
  {
79
- type: 'textarea',
252
+ type: 'string',
80
253
  name: 'header',
81
- label: 'header' // header template
254
+ label: 'header',
255
+ placeholder: 'Page <%= pageNumber %> of <%= totalPages %>'
82
256
  },
83
257
  {
84
- type: 'textarea',
258
+ type: 'string',
85
259
  name: 'footer',
86
- label: 'footer' // footer template
260
+ label: 'footer',
261
+ placeholder: 'Page <%= pageNumber %> of <%= totalPages %>'
87
262
  },
88
263
  {
89
- type: 'textarea',
264
+ type: 'string',
90
265
  name: 'watermark',
91
- label: 'watermark' // watermark
266
+ label: 'watermark'
92
267
  },
93
268
  {
94
269
  type: 'string',
95
270
  name: 'fileName',
96
- label: 'filename' // 파일 이름 지정
271
+ label: 'filename'
272
+ },
273
+ {
274
+ type: 'select',
275
+ name: 'format',
276
+ label: 'page-format',
277
+ property: {
278
+ options: [
279
+ { display: '', value: '' },
280
+ { display: 'A4', value: 'A4' },
281
+ { display: 'A3', value: 'A3' },
282
+ { display: 'Letter', value: 'Letter' },
283
+ { display: 'Legal', value: 'Legal' }
284
+ ]
285
+ },
286
+ description: 'Select the paper format for the PDF'
287
+ },
288
+ {
289
+ type: 'string',
290
+ name: 'width',
291
+ label: 'page-width',
292
+ placeholder: '(e.g., "8.5in", "21cm", "600px")',
293
+ description: 'Specify the width of the page (e.g., "8.5in", "21cm", "600px")'
294
+ },
295
+ {
296
+ type: 'string',
297
+ name: 'height',
298
+ label: 'page-height',
299
+ placeholder: '(e.g., "11in", "29.7cm", "800px")',
300
+ description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
301
+ },
302
+ {
303
+ type: 'string',
304
+ name: 'marginTop',
305
+ label: 'page-margin-top',
306
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
307
+ description: 'Set the top margin for the page'
308
+ },
309
+ {
310
+ type: 'string',
311
+ name: 'marginBottom',
312
+ label: 'page-margin-bottom',
313
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
314
+ description: 'Set the bottom margin for the page'
315
+ },
316
+ {
317
+ type: 'string',
318
+ name: 'marginLeft',
319
+ label: 'page-margin-left',
320
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
321
+ description: 'Set the left margin for the page'
322
+ },
323
+ {
324
+ type: 'string',
325
+ name: 'marginRight',
326
+ label: 'page-margin-right',
327
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
328
+ description: 'Set the right margin for the page'
329
+ },
330
+ {
331
+ type: 'number',
332
+ name: 'scale',
333
+ label: 'page-scale',
334
+ defaultValue: 1,
335
+ description: 'Set the scale of the page content (default is 1)'
336
+ },
337
+ {
338
+ type: 'boolean',
339
+ name: 'printBackground',
340
+ label: 'print-background',
341
+ defaultValue: true,
342
+ description: 'Include background graphics when printing the page'
343
+ },
344
+ {
345
+ type: 'boolean',
346
+ name: 'landscape',
347
+ label: 'landscape',
348
+ defaultValue: false,
349
+ description: 'Print the PDF in landscape orientation'
350
+ },
351
+ {
352
+ type: 'boolean',
353
+ name: 'preferCSSPageSize',
354
+ label: 'prefer-css-page-size',
355
+ defaultValue: false,
356
+ description: 'Whether to prefer the CSS-defined page size over the given width and height'
97
357
  }
98
358
  ]
359
+
99
360
  HeadlessPDFOpen.help = 'integration/task/headless-pdf-open'
100
361
 
101
362
  TaskRegistry.registerTaskHandler('headless-pdf-open', HeadlessPDFOpen)
@@ -1,74 +1,39 @@
1
1
  import * as fs from 'fs'
2
2
  import * as path from 'path'
3
- import { PDFDocument, rgb, degrees } from 'pdf-lib'
4
-
5
3
  import { TaskRegistry } from '@things-factory/integration-base'
4
+ import { PDFDocument } from 'pdf-lib'
6
5
 
7
6
  async function HeadlessPDFSave(step, context) {
8
7
  var { params: stepOptions } = step
9
8
  var { outputPath } = stepOptions || {}
10
- var { domain, data, __headless_pdf } = context
11
-
12
- var { pdf, coverPage, lastPage, watermark, fileName } = __headless_pdf
13
-
14
- // 파일 이름이 설정되지 않았을 경우 기본 파일 이름 생성
15
- const finalFileName = fileName || `document-${Date.now()}.pdf`
16
- const savePath = path.join(outputPath || '/path/to/default/storage', finalFileName)
17
-
18
- // PDF 문서에 coverPage 및 lastPage 추가
19
- if (coverPage) {
20
- const coverPageDoc = await PDFDocument.load(coverPage)
21
- const [cover] = await pdf.copyPages(coverPageDoc, [0])
22
-
23
- // 커버 페이지에 워터마크 추가
24
- if (watermark) {
25
- const fontSize = 50
26
- const pageWidth = cover.getWidth()
27
- const pageHeight = cover.getHeight()
9
+ var { __headless_pdf } = context
28
10
 
29
- cover.drawText(watermark, {
30
- x: pageWidth / 2 - (fontSize * watermark.length) / 4,
31
- y: pageHeight / 2 - fontSize / 2,
32
- size: fontSize,
33
- color: rgb(0.75, 0.75, 0.75),
34
- rotate: degrees(-45),
35
- opacity: 0.5
36
- })
37
- }
38
-
39
- pdf.insertPage(0, cover)
11
+ if (!__headless_pdf || !__headless_pdf.pdfDoc) {
12
+ throw new Error(
13
+ 'No PDF document found. Ensure that headless-pdf-open and headless-pdf-capture tasks are executed before saving.'
14
+ )
40
15
  }
41
16
 
42
- if (lastPage) {
43
- const lastPageDoc = await PDFDocument.load(lastPage)
44
- const [last] = await pdf.copyPages(lastPageDoc, [0])
17
+ const pdfDoc = __headless_pdf.pdfDoc
45
18
 
46
- // 마지막 페이지에 워터마크 추가
47
- if (watermark) {
48
- const fontSize = 50
49
- const pageWidth = last.getWidth()
50
- const pageHeight = last.getHeight()
19
+ // Add last page if it exists
20
+ if (__headless_pdf.lastPageBuffer) {
21
+ const lastPageDoc = await PDFDocument.load(__headless_pdf.lastPageBuffer)
22
+ const copiedLastPages = await pdfDoc.copyPages(lastPageDoc, lastPageDoc.getPageIndices())
23
+ copiedLastPages.forEach(page => pdfDoc.addPage(page))
24
+ }
51
25
 
52
- last.drawText(watermark, {
53
- x: pageWidth / 2 - (fontSize * watermark.length) / 4,
54
- y: pageHeight / 2 - fontSize / 2,
55
- size: fontSize,
56
- color: rgb(0.75, 0.75, 0.75),
57
- rotate: degrees(-45),
58
- opacity: 0.5
59
- })
60
- }
26
+ const pdfBytes = await pdfDoc.save()
61
27
 
62
- pdf.addPage(last)
63
- }
28
+ const finalFileName = __headless_pdf.fileName || `document-${Date.now()}.pdf`
29
+ const savePath = path.join(outputPath || './attachments', finalFileName)
64
30
 
65
- // PDF 파일로 저장
66
- const pdfBytes = await pdf.save()
31
+ // Save the final PDF to the specified path
67
32
  fs.writeFileSync(savePath, pdfBytes)
68
33
 
69
34
  return {
70
35
  data: {
71
- link: savePath // 저장된 PDF 파일의 경로를 반환
36
+ link: savePath
72
37
  }
73
38
  }
74
39
  }
@@ -77,7 +42,8 @@ HeadlessPDFSave.parameterSpec = [
77
42
  {
78
43
  type: 'string',
79
44
  name: 'outputPath',
80
- label: 'output-path' // PDF를 저장할 경로
45
+ label: 'output-path',
46
+ description: 'The directory path where the PDF should be saved. Defaults to "./attachments".'
81
47
  }
82
48
  ]
83
49
 
@@ -1,3 +1,4 @@
1
1
  import './headless-pdf-capture'
2
+ import './headless-pdf-capture-board'
2
3
  import './headless-pdf-open'
3
4
  import './headless-pdf-save'
@@ -8,7 +8,10 @@
8
8
  "label.output-path": "output path",
9
9
  "label.page-format": "page format",
10
10
  "label.page-height": "page height",
11
- "label.page-margins": "page margins",
11
+ "label.page-margin-top": "page margin top",
12
+ "label.page-margin-bottom": "page margin bottom",
13
+ "label.page-margin-left": "page margin left",
14
+ "label.page-margin-right": "page margins right",
12
15
  "label.page-width": "page width",
13
16
  "label.pdf-cover-page": "cover page template",
14
17
  "label.pdf-last-page": "last page template",
@@ -16,6 +19,6 @@
16
19
  "label.pool-size-min": "minimum pool size",
17
20
  "label.prefer-css-page-size": "prefer css page size",
18
21
  "label.print-background": "print background",
19
- "label.scale": "scale",
22
+ "label.page-scale": "scale",
20
23
  "label.watermark": "watermark"
21
24
  }
@@ -8,7 +8,10 @@
8
8
  "label.output-path": "出力パス",
9
9
  "label.page-format": "ページ形式",
10
10
  "label.page-height": "ページ高さ",
11
- "label.page-margins": "ページ余白",
11
+ "label.page-margin-top": "ページ上余白",
12
+ "label.page-margin-bottom": "ページ下余白",
13
+ "label.page-margin-left": "ページ左余白",
14
+ "label.page-margin-right": "ページ右余白",
12
15
  "label.page-width": "ページ幅",
13
16
  "label.pdf-cover-page": "表紙テンプレート",
14
17
  "label.pdf-last-page": "最終ページテンプレート",
@@ -16,6 +19,6 @@
16
19
  "label.pool-size-min": "最小プールサイズ",
17
20
  "label.prefer-css-page-size": "cssページサイズを優先",
18
21
  "label.print-background": "背景を印刷",
19
- "label.scale": "スケール",
22
+ "label.page-scale": "スケール",
20
23
  "label.watermark": "透かし"
21
24
  }
@@ -8,7 +8,10 @@
8
8
  "label.output-path": "출력 패스",
9
9
  "label.page-format": "페이지 형식",
10
10
  "label.page-height": "페이지 높이",
11
- "label.page-margins": "페이지 여백",
11
+ "label.page-margin-top": "페이지 여백 (위쪽)",
12
+ "label.page-margin-bottom": "페이지 여백 (아래쪽)",
13
+ "label.page-margin-left": "페이지 여백 (왼쪽)",
14
+ "label.page-margin-right": "페이지 여백 (오른쪽)",
12
15
  "label.page-width": "페이지 폭",
13
16
  "label.pdf-cover-page": "표지 템플릿",
14
17
  "label.pdf-last-page": "뒷표지 템플릿",
@@ -16,6 +19,6 @@
16
19
  "label.pool-size-min": "최대 풀(Pool) 사이즈",
17
20
  "label.prefer-css-page-size": "선호 CSS 페이지 크기",
18
21
  "label.print-background": "배경 포함여부",
19
- "label.scale": "페이지 비율",
22
+ "label.page-scale": "페이지 비율",
20
23
  "label.watermark": "워터마크"
21
24
  }
@@ -8,7 +8,10 @@
8
8
  "label.output-path": "laluan output",
9
9
  "label.page-format": "format halaman",
10
10
  "label.page-height": "tinggi halaman",
11
- "label.page-margins": "margin halaman",
11
+ "label.page-margin-top": "margin atas halaman",
12
+ "label.page-margin-bottom": "margin bawah halaman",
13
+ "label.page-margin-left": "margin kiri halaman",
14
+ "label.page-margin-right": "margin kanan halaman",
12
15
  "label.page-width": "lebar halaman",
13
16
  "label.pdf-cover-page": "templat muka surat depan",
14
17
  "label.pdf-last-page": "templat muka surat terakhir",
@@ -16,6 +19,6 @@
16
19
  "label.pool-size-min": "saiz pool minimum",
17
20
  "label.prefer-css-page-size": "utamakan saiz halaman css",
18
21
  "label.print-background": "cetak latar belakang",
19
- "label.scale": "skala",
22
+ "label.page-scale": "skala",
20
23
  "label.watermark": "tanda air"
21
24
  }
@@ -8,7 +8,10 @@
8
8
  "label.output-path": "输出路径",
9
9
  "label.page-format": "页面格式",
10
10
  "label.page-height": "页面高度",
11
- "label.page-margins": "页面边距",
11
+ "label.page-margin-top": "页面上边距",
12
+ "label.page-margin-bottom": "页面下边距",
13
+ "label.page-margin-left": "页面左边距",
14
+ "label.page-margin-right": "页面右边距",
12
15
  "label.page-width": "页面宽度",
13
16
  "label.pdf-cover-page": "封面模板",
14
17
  "label.pdf-last-page": "尾页模板",
@@ -16,6 +19,6 @@
16
19
  "label.pool-size-min": "最小池大小",
17
20
  "label.prefer-css-page-size": "偏好 css 页面大小",
18
21
  "label.print-background": "打印背景",
19
- "label.scale": "缩放",
22
+ "label.page-scale": "缩放",
20
23
  "label.watermark": "水印"
21
24
  }