@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.
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.40",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -22,10 +22,11 @@
22
22
  "clean": "npm run clean:server"
23
23
  },
24
24
  "dependencies": {
25
+ "@things-factory/board-service": "^7.0.40",
25
26
  "@things-factory/integration-base": "^7.0.38",
26
27
  "@things-factory/pdf": "^7.0.37",
27
28
  "ejs": "^3.1.10",
28
29
  "pdf-lib": "^1.17.1"
29
30
  },
30
- "gitHead": "735942469a12f77ed857ad617b97148d178d162d"
31
+ "gitHead": "59ccd4873d87a1b38df8387809a68c958076d264"
31
32
  }
@@ -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: '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,51 +1,110 @@
1
1
  import * as ejs from 'ejs'
2
- import { PDFDocument, rgb, degrees } from 'pdf-lib'
3
-
4
2
  import { TaskRegistry } from '@things-factory/integration-base'
5
3
  import { ConnectionManager } from '@things-factory/integration-base'
4
+ import { access } from '@things-factory/utils'
5
+ import { PDFDocument } from 'pdf-lib'
6
6
 
7
7
  async function HeadlessPDFCapture(step, context) {
8
8
  var { connection: connectionName, params: stepOptions } = step
9
9
  var {
10
+ accessor,
10
11
  htmlContent,
11
12
  format,
12
13
  width,
13
14
  height,
14
- margin,
15
+ marginTop,
16
+ marginBottom,
17
+ marginLeft,
18
+ marginRight,
15
19
  scale,
16
20
  printBackground,
17
21
  landscape,
18
- displayHeaderFooter,
19
- headerTemplate,
20
- footerTemplate,
21
22
  preferCSSPageSize
22
23
  } = stepOptions || {}
23
- var { domain, data, __headless_pdf } = context
24
+ var { domain, data, logger, __headless_pdf } = context
24
25
 
25
26
  if (!__headless_pdf) {
26
- throw new Error('it requires headless-pdf-open to be performed first')
27
+ throw new Error('It requires headless-pdf-open to be performed first')
27
28
  }
28
29
 
29
- var {
30
- pdf,
31
- header: headerTemplateFromContext,
32
- footer: footerTemplateFromContext,
33
- watermark,
34
- pageCount
35
- } = __headless_pdf
30
+ var { pdfDoc, header, footer, watermark } = __headless_pdf
36
31
 
37
32
  var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
38
33
  let browser
39
34
 
40
35
  try {
41
- // Puppeteer를 사용하여 브라우저와 페이지 인스턴스를 생성
42
36
  browser = await headlessPool.acquire()
43
37
  const page = await browser.newPage()
44
38
 
45
- // 설정된 HTML 콘텐츠를 페이지에 설정
46
- 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
+ }
47
107
 
48
- // PDF 페이지 생성 옵션 설정
49
108
  if (preferCSSPageSize) {
50
109
  width = undefined
51
110
  height = undefined
@@ -55,73 +114,26 @@ async function HeadlessPDFCapture(step, context) {
55
114
  format,
56
115
  width,
57
116
  height,
58
- margin,
117
+ margin: {
118
+ top: marginTop,
119
+ right: marginRight,
120
+ bottom: marginBottom,
121
+ left: marginLeft
122
+ },
59
123
  scale,
60
124
  printBackground,
61
125
  landscape,
62
- displayHeaderFooter,
63
- headerTemplate: displayHeaderFooter ? headerTemplate : undefined,
64
- footerTemplate: displayHeaderFooter ? footerTemplate : undefined,
126
+ displayHeaderFooter: false, // Handled via Puppeteer directly
65
127
  preferCSSPageSize
66
128
  }
67
129
 
68
- // 페이지를 PDF로 변환
69
- const pageBuffer = await page.pdf(pageOptions)
70
-
71
- // PDF-lib을 사용하여 PDF를 로드하고 추가 조작
72
- const pdfDoc = await PDFDocument.load(pageBuffer)
73
- const pages = await pdf.copyPages(pdfDoc, pdfDoc.getPageIndices())
74
-
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
- }
130
+ const contentBuffer = await page.pdf(pageOptions)
131
+ const contentPdfDoc = await PDFDocument.load(contentBuffer)
132
+ const copiedPages = await pdfDoc.copyPages(contentPdfDoc, contentPdfDoc.getPageIndices())
119
133
 
120
- pdf.addPage(newPage)
121
- })
134
+ copiedPages.forEach(page => pdfDoc.addPage(page))
122
135
 
123
- // 페이지 카운트 업데이트
124
- __headless_pdf.pageCount += pages.length
136
+ __headless_pdf.pageCount += copiedPages.length
125
137
 
126
138
  await page.close()
127
139
  headlessPool.release(browser)
@@ -139,6 +151,11 @@ async function HeadlessPDFCapture(step, context) {
139
151
  }
140
152
 
141
153
  HeadlessPDFCapture.parameterSpec = [
154
+ {
155
+ type: 'scenario-step-input',
156
+ name: 'accessor',
157
+ label: 'accessor'
158
+ },
142
159
  {
143
160
  type: 'textarea',
144
161
  name: 'htmlContent',
@@ -150,6 +167,7 @@ HeadlessPDFCapture.parameterSpec = [
150
167
  label: 'page-format',
151
168
  property: {
152
169
  options: [
170
+ { display: '', value: '' },
153
171
  { display: 'A4', value: 'A4' },
154
172
  { display: 'A3', value: 'A3' },
155
173
  { display: 'Letter', value: 'Letter' },
@@ -173,21 +191,37 @@ HeadlessPDFCapture.parameterSpec = [
173
191
  description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
174
192
  },
175
193
  {
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'
194
+ type: 'string',
195
+ name: 'marginTop',
196
+ label: 'page-margin-top',
197
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
198
+ description: 'Set the top margin for the page'
199
+ },
200
+ {
201
+ type: 'string',
202
+ name: 'marginBottom',
203
+ label: 'page-margin-bottom',
204
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
205
+ description: 'Set the bottom margin for the page'
206
+ },
207
+ {
208
+ type: 'string',
209
+ name: 'marginLeft',
210
+ label: 'page-margin-left',
211
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
212
+ description: 'Set the left margin for the page'
213
+ },
214
+ {
215
+ type: 'string',
216
+ name: 'marginRight',
217
+ label: 'page-margin-right',
218
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
219
+ description: 'Set the right margin for the page'
186
220
  },
187
221
  {
188
222
  type: 'number',
189
223
  name: 'scale',
190
- label: 'scale',
224
+ label: 'page-scale',
191
225
  defaultValue: 1,
192
226
  description: 'Set the scale of the page content (default is 1)'
193
227
  },
@@ -205,13 +239,6 @@ HeadlessPDFCapture.parameterSpec = [
205
239
  defaultValue: false,
206
240
  description: 'Print the PDF in landscape orientation'
207
241
  },
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
242
  {
216
243
  type: 'boolean',
217
244
  name: 'preferCSSPageSize',