@things-factory/integration-headless 7.0.38 → 7.0.39

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-headless",
3
- "version": "7.0.38",
3
+ "version": "7.0.39",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -27,5 +27,5 @@
27
27
  "ejs": "^3.1.10",
28
28
  "pdf-lib": "^1.17.1"
29
29
  },
30
- "gitHead": "735942469a12f77ed857ad617b97148d178d162d"
30
+ "gitHead": "97f7588d477aa1d6083b51113d7666b42f530989"
31
31
  }
@@ -1,5 +1,4 @@
1
- import * as ejs from 'ejs'
2
- import { PDFDocument, rgb, degrees } from 'pdf-lib'
1
+ import { PDFDocument } from 'pdf-lib'
3
2
 
4
3
  import { TaskRegistry } from '@things-factory/integration-base'
5
4
  import { ConnectionManager } from '@things-factory/integration-base'
@@ -11,28 +10,22 @@ async function HeadlessPDFCapture(step, context) {
11
10
  format,
12
11
  width,
13
12
  height,
14
- margin,
13
+ marginTop,
14
+ marginBottom,
15
+ marginLeft,
16
+ marginRight,
15
17
  scale,
16
18
  printBackground,
17
19
  landscape,
18
- displayHeaderFooter,
19
- headerTemplate,
20
- footerTemplate,
21
20
  preferCSSPageSize
22
21
  } = stepOptions || {}
23
- var { domain, data, __headless_pdf } = context
22
+ var { domain, __headless_pdf } = context
24
23
 
25
24
  if (!__headless_pdf) {
26
- throw new Error('it requires headless-pdf-open to be performed first')
25
+ throw new Error('It requires headless-pdf-open to be performed first')
27
26
  }
28
27
 
29
- var {
30
- pdf,
31
- header: headerTemplateFromContext,
32
- footer: footerTemplateFromContext,
33
- watermark,
34
- pageCount
35
- } = __headless_pdf
28
+ var { pdf, pageCount } = __headless_pdf
36
29
 
37
30
  var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
38
31
  let browser
@@ -55,13 +48,16 @@ async function HeadlessPDFCapture(step, context) {
55
48
  format,
56
49
  width,
57
50
  height,
58
- margin,
51
+ margin: {
52
+ top: marginTop,
53
+ right: marginRight,
54
+ bottom: marginBottom,
55
+ left: marginLeft
56
+ },
59
57
  scale,
60
58
  printBackground,
61
59
  landscape,
62
- displayHeaderFooter,
63
- headerTemplate: displayHeaderFooter ? headerTemplate : undefined,
64
- footerTemplate: displayHeaderFooter ? footerTemplate : undefined,
60
+ displayHeaderFooter: false, // 첫 번째 패스에서는 헤더/푸터를 렌더링하지 않음
65
61
  preferCSSPageSize
66
62
  }
67
63
 
@@ -72,51 +68,7 @@ async function HeadlessPDFCapture(step, context) {
72
68
  const pdfDoc = await PDFDocument.load(pageBuffer)
73
69
  const pages = await pdf.copyPages(pdfDoc, pdfDoc.getPageIndices())
74
70
 
75
- pages.forEach((newPage, index) => {
76
- const currentPageNumber = pageCount + index + 1
77
-
78
- // 헤더와 푸터 렌더링 (페이지 번호 및 총 페이지 수 포함)
79
- if (displayHeaderFooter && (headerTemplateFromContext || footerTemplateFromContext)) {
80
- const renderedHeader = headerTemplateFromContext
81
- ? ejs.render(headerTemplateFromContext, {
82
- ...data,
83
- pageNumber: currentPageNumber,
84
- totalPages: pageCount + pages.length
85
- })
86
- : ''
87
- const renderedFooter = footerTemplateFromContext
88
- ? ejs.render(footerTemplateFromContext, {
89
- ...data,
90
- pageNumber: currentPageNumber,
91
- totalPages: pageCount + pages.length
92
- })
93
- : ''
94
-
95
- // Header/Footer 그리기
96
- if (renderedHeader) {
97
- newPage.drawText(renderedHeader, { x: 50, y: newPage.getHeight() - 30, size: 12, color: rgb(0, 0, 0) })
98
- }
99
- if (renderedFooter) {
100
- newPage.drawText(renderedFooter, { x: 50, y: 30, size: 12, color: rgb(0, 0, 0) })
101
- }
102
- }
103
-
104
- // 워터마크 추가
105
- if (watermark) {
106
- const fontSize = 50
107
- const pageWidth = newPage.getWidth()
108
- const pageHeight = newPage.getHeight()
109
-
110
- newPage.drawText(watermark, {
111
- x: pageWidth / 2 - (fontSize * watermark.length) / 4,
112
- y: pageHeight / 2 - fontSize / 2,
113
- size: fontSize,
114
- color: rgb(0.75, 0.75, 0.75),
115
- rotate: degrees(-45),
116
- opacity: 0.5
117
- })
118
- }
119
-
71
+ pages.forEach(newPage => {
120
72
  pdf.addPage(newPage)
121
73
  })
122
74
 
@@ -150,6 +102,7 @@ HeadlessPDFCapture.parameterSpec = [
150
102
  label: 'page-format',
151
103
  property: {
152
104
  options: [
105
+ { display: '', value: '' },
153
106
  { display: 'A4', value: 'A4' },
154
107
  { display: 'A3', value: 'A3' },
155
108
  { display: 'Letter', value: 'Letter' },
@@ -173,21 +126,37 @@ HeadlessPDFCapture.parameterSpec = [
173
126
  description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
174
127
  },
175
128
  {
176
- type: 'json',
177
- name: 'margin',
178
- label: 'page-margins',
179
- fields: [
180
- { type: 'string', name: 'top', label: 'Top Margin', description: 'Top margin (e.g., "1in")' },
181
- { type: 'string', name: 'right', label: 'Right Margin', description: 'Right margin (e.g., "1in")' },
182
- { type: 'string', name: 'bottom', label: 'Bottom Margin', description: 'Bottom margin (e.g., "1in")' },
183
- { type: 'string', name: 'left', label: 'Left Margin', description: 'Left margin (e.g., "1in")' }
184
- ],
185
- description: 'Set the margins for the page'
129
+ type: 'string',
130
+ name: 'marginTop',
131
+ label: 'page-margin-top',
132
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
133
+ description: 'Set the top margin for the page'
134
+ },
135
+ {
136
+ type: 'string',
137
+ name: 'marginBottom',
138
+ label: 'page-margin-bottom',
139
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
140
+ description: 'Set the bottom margin for the page'
141
+ },
142
+ {
143
+ type: 'string',
144
+ name: 'marginLeft',
145
+ label: 'page-margin-left',
146
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
147
+ description: 'Set the left margin for the page'
148
+ },
149
+ {
150
+ type: 'string',
151
+ name: 'marginRight',
152
+ label: 'page-margin-right',
153
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
154
+ description: 'Set the right margin for the page'
186
155
  },
187
156
  {
188
157
  type: 'number',
189
158
  name: 'scale',
190
- label: 'scale',
159
+ label: 'page-scale',
191
160
  defaultValue: 1,
192
161
  description: 'Set the scale of the page content (default is 1)'
193
162
  },
@@ -205,13 +174,6 @@ HeadlessPDFCapture.parameterSpec = [
205
174
  defaultValue: false,
206
175
  description: 'Print the PDF in landscape orientation'
207
176
  },
208
- {
209
- type: 'boolean',
210
- name: 'displayHeaderFooter',
211
- label: 'display-header/footer',
212
- defaultValue: false,
213
- description: 'Display header and footer in the PDF'
214
- },
215
177
  {
216
178
  type: 'boolean',
217
179
  name: 'preferCSSPageSize',
@@ -76,24 +76,26 @@ HeadlessPDFOpen.parameterSpec = [
76
76
  label: 'pdf-last-page'
77
77
  },
78
78
  {
79
- type: 'textarea',
79
+ type: 'string',
80
80
  name: 'header',
81
- label: 'header' // header template
81
+ label: 'header',
82
+ placeholder: 'Page <%= pageNumber %> of <%= totalPages %>'
82
83
  },
83
84
  {
84
- type: 'textarea',
85
+ type: 'string',
85
86
  name: 'footer',
86
- label: 'footer' // footer template
87
+ label: 'footer',
88
+ placeholder: 'Page <%= pageNumber %> of <%= totalPages %>'
87
89
  },
88
90
  {
89
- type: 'textarea',
91
+ type: 'string',
90
92
  name: 'watermark',
91
- label: 'watermark' // watermark
93
+ label: 'watermark'
92
94
  },
93
95
  {
94
96
  type: 'string',
95
97
  name: 'fileName',
96
- label: 'filename' // 파일 이름 지정
98
+ label: 'filename'
97
99
  }
98
100
  ]
99
101
  HeadlessPDFOpen.help = 'integration/task/headless-pdf-open'
@@ -1,9 +1,71 @@
1
1
  import * as fs from 'fs'
2
2
  import * as path from 'path'
3
+ import * as ejs from 'ejs'
3
4
  import { PDFDocument, rgb, degrees } from 'pdf-lib'
4
5
 
5
6
  import { TaskRegistry } from '@things-factory/integration-base'
6
7
 
8
+ async function HeadlessPDFRenderHeadersAndFooters(pdf, headerTemplate, footerTemplate, watermark, totalPages) {
9
+ const pages = pdf.getPages()
10
+
11
+ pages.forEach((page, index) => {
12
+ const pageNumber = index + 1
13
+ const pageWidth = page.getWidth()
14
+
15
+ // 헤더와 푸터 렌더링 (페이지 번호 및 총 페이지 수 포함)
16
+ if (headerTemplate || footerTemplate) {
17
+ const renderedHeader = headerTemplate
18
+ ? ejs.render(headerTemplate, {
19
+ pageNumber,
20
+ totalPages
21
+ })
22
+ : ''
23
+ const renderedFooter = footerTemplate
24
+ ? ejs.render(footerTemplate, {
25
+ pageNumber,
26
+ totalPages
27
+ })
28
+ : ''
29
+
30
+ const fontSize = 12 // 폰트 크기 설정
31
+ const textWidthHeader = fontSize * renderedHeader.length * 0.5
32
+ const textWidthFooter = fontSize * renderedFooter.length * 0.5
33
+
34
+ // Header/Footer 그리기 (가운데 정렬)
35
+ if (renderedHeader) {
36
+ page.drawText(renderedHeader, {
37
+ x: (pageWidth - textWidthHeader) / 2,
38
+ y: page.getHeight() - 30,
39
+ size: fontSize,
40
+ color: rgb(0, 0, 0)
41
+ })
42
+ }
43
+ if (renderedFooter) {
44
+ page.drawText(renderedFooter, {
45
+ x: (pageWidth - textWidthFooter) / 2,
46
+ y: 30,
47
+ size: fontSize,
48
+ color: rgb(0, 0, 0)
49
+ })
50
+ }
51
+ }
52
+
53
+ // 워터마크 추가
54
+ if (watermark) {
55
+ const fontSize = 50
56
+
57
+ page.drawText(watermark, {
58
+ x: pageWidth / 2 - (fontSize * watermark.length) / 4,
59
+ y: page.getHeight() / 2 - fontSize / 2,
60
+ size: fontSize,
61
+ color: rgb(0.75, 0.75, 0.75),
62
+ rotate: degrees(-45),
63
+ opacity: 0.5
64
+ })
65
+ }
66
+ })
67
+ }
68
+
7
69
  async function HeadlessPDFSave(step, context) {
8
70
  var { params: stepOptions } = step
9
71
  var { outputPath } = stepOptions || {}
@@ -11,16 +73,16 @@ async function HeadlessPDFSave(step, context) {
11
73
 
12
74
  var { pdf, coverPage, lastPage, watermark, fileName } = __headless_pdf
13
75
 
14
- // 파일 이름이 설정되지 않았을 경우 기본 파일 이름 생성
76
+ const totalPages = pdf.getPages().length
77
+ await HeadlessPDFRenderHeadersAndFooters(pdf, __headless_pdf.header, __headless_pdf.footer, watermark, totalPages)
78
+
15
79
  const finalFileName = fileName || `document-${Date.now()}.pdf`
16
- const savePath = path.join(outputPath || '/path/to/default/storage', finalFileName)
80
+ const savePath = path.join(outputPath || './attachments', finalFileName)
17
81
 
18
- // PDF 문서에 coverPage 및 lastPage 추가
19
82
  if (coverPage) {
20
83
  const coverPageDoc = await PDFDocument.load(coverPage)
21
84
  const [cover] = await pdf.copyPages(coverPageDoc, [0])
22
85
 
23
- // 커버 페이지에 워터마크 추가
24
86
  if (watermark) {
25
87
  const fontSize = 50
26
88
  const pageWidth = cover.getWidth()
@@ -43,7 +105,6 @@ async function HeadlessPDFSave(step, context) {
43
105
  const lastPageDoc = await PDFDocument.load(lastPage)
44
106
  const [last] = await pdf.copyPages(lastPageDoc, [0])
45
107
 
46
- // 마지막 페이지에 워터마크 추가
47
108
  if (watermark) {
48
109
  const fontSize = 50
49
110
  const pageWidth = last.getWidth()
@@ -62,13 +123,12 @@ async function HeadlessPDFSave(step, context) {
62
123
  pdf.addPage(last)
63
124
  }
64
125
 
65
- // PDF 파일로 저장
66
126
  const pdfBytes = await pdf.save()
67
127
  fs.writeFileSync(savePath, pdfBytes)
68
128
 
69
129
  return {
70
130
  data: {
71
- link: savePath // 저장된 PDF 파일의 경로를 반환
131
+ link: savePath
72
132
  }
73
133
  }
74
134
  }
@@ -77,7 +137,7 @@ HeadlessPDFSave.parameterSpec = [
77
137
  {
78
138
  type: 'string',
79
139
  name: 'outputPath',
80
- label: 'output-path' // PDF를 저장할 경로
140
+ label: 'output-path'
81
141
  }
82
142
  ]
83
143
 
@@ -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
  }