@things-factory/integration-headless 7.0.37

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 (45) hide show
  1. package/dist-server/engine/connector/headless-connector.d.ts +13 -0
  2. package/dist-server/engine/connector/headless-connector.js +51 -0
  3. package/dist-server/engine/connector/headless-connector.js.map +1 -0
  4. package/dist-server/engine/connector/headless-pool.d.ts +3 -0
  5. package/dist-server/engine/connector/headless-pool.js +63 -0
  6. package/dist-server/engine/connector/headless-pool.js.map +1 -0
  7. package/dist-server/engine/connector/index.d.ts +1 -0
  8. package/dist-server/engine/connector/index.js +4 -0
  9. package/dist-server/engine/connector/index.js.map +1 -0
  10. package/dist-server/engine/index.d.ts +2 -0
  11. package/dist-server/engine/index.js +5 -0
  12. package/dist-server/engine/index.js.map +1 -0
  13. package/dist-server/engine/task/headless-pdf-capture.d.ts +1 -0
  14. package/dist-server/engine/task/headless-pdf-capture.js +176 -0
  15. package/dist-server/engine/task/headless-pdf-capture.js.map +1 -0
  16. package/dist-server/engine/task/headless-pdf-open.d.ts +1 -0
  17. package/dist-server/engine/task/headless-pdf-open.js +61 -0
  18. package/dist-server/engine/task/headless-pdf-open.js.map +1 -0
  19. package/dist-server/engine/task/headless-pdf-save.d.ts +1 -0
  20. package/dist-server/engine/task/headless-pdf-save.js +72 -0
  21. package/dist-server/engine/task/headless-pdf-save.js.map +1 -0
  22. package/dist-server/engine/task/index.d.ts +3 -0
  23. package/dist-server/engine/task/index.js +6 -0
  24. package/dist-server/engine/task/index.js.map +1 -0
  25. package/dist-server/index.d.ts +1 -0
  26. package/dist-server/index.js +4 -0
  27. package/dist-server/index.js.map +1 -0
  28. package/dist-server/tsconfig.tsbuildinfo +1 -0
  29. package/package.json +31 -0
  30. package/server/engine/connector/headless-connector.ts +64 -0
  31. package/server/engine/connector/headless-pool.ts +69 -0
  32. package/server/engine/connector/index.ts +1 -0
  33. package/server/engine/index.ts +2 -0
  34. package/server/engine/task/headless-pdf-capture.ts +222 -0
  35. package/server/engine/task/headless-pdf-open.ts +75 -0
  36. package/server/engine/task/headless-pdf-save.ts +84 -0
  37. package/server/engine/task/index.ts +3 -0
  38. package/server/index.ts +1 -0
  39. package/server/tsconfig.json +10 -0
  40. package/things-factory.config.js +1 -0
  41. package/translations/en.json +19 -0
  42. package/translations/ja.json +19 -0
  43. package/translations/ko.json +19 -0
  44. package/translations/ms.json +19 -0
  45. package/translations/zh.json +19 -0
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@things-factory/integration-headless",
3
+ "version": "7.0.37",
4
+ "main": "dist-server/index.js",
5
+ "things-factory": true,
6
+ "author": "heartyoh <heartyoh@hatiolab.com>",
7
+ "description": "Module that provide headless connection on integration engine",
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "@things-factory:registry": "https://registry.npmjs.org"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/hatiolab/things-factory.git",
16
+ "directory": "packages/integration-headless"
17
+ },
18
+ "scripts": {
19
+ "build": "npm run build:server",
20
+ "build:server": "npx tsc --p ./server/tsconfig.json",
21
+ "clean:server": "npx rimraf dist-server",
22
+ "clean": "npm run clean:server"
23
+ },
24
+ "dependencies": {
25
+ "@things-factory/integration-base": "^7.0.33",
26
+ "@things-factory/pdf": "^7.0.37",
27
+ "ejs": "^3.1.10",
28
+ "pdf-lib": "^1.17.1"
29
+ },
30
+ "gitHead": "28b852bbf7bd1160522195f9db1775db56f18b30"
31
+ }
@@ -0,0 +1,64 @@
1
+ import { ConnectionManager, Connector } from '@things-factory/integration-base'
2
+ import { createHeadlessPool, destroyHeadlessPool } from './headless-pool'
3
+
4
+ export class HeadlessConnector implements Connector {
5
+ async ready(connectionConfigs) {
6
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
7
+
8
+ ConnectionManager.logger.info('headless-connector connections are ready')
9
+ }
10
+
11
+ async connect(connection) {
12
+ var {
13
+ endpoint: uri = '1',
14
+ params: { min = 2, max = 10 }
15
+ } = connection
16
+
17
+ const headlessPool = createHeadlessPool({
18
+ min,
19
+ max,
20
+ testOnBorrow: true,
21
+ acquireTimeoutMillis: 15000
22
+ })
23
+
24
+ ConnectionManager.addConnectionInstance(connection, headlessPool)
25
+
26
+ ConnectionManager.logger.info(
27
+ `headless-connector connection(${connection.name}:${connection.endpoint}) is connected`
28
+ )
29
+ }
30
+
31
+ async disconnect(connection) {
32
+ const headlessPool = ConnectionManager.getConnectionInstance(connection)
33
+ destroyHeadlessPool(headlessPool)
34
+
35
+ ConnectionManager.removeConnectionInstance(connection)
36
+
37
+ ConnectionManager.logger.info(`headless-connector connection(${connection.name}) is disconnected`)
38
+ }
39
+
40
+ get parameterSpec() {
41
+ return [
42
+ {
43
+ type: 'number',
44
+ name: 'min',
45
+ label: 'pool-size-min'
46
+ },
47
+ {
48
+ type: 'number',
49
+ name: 'max',
50
+ label: 'pool-size-max'
51
+ }
52
+ ]
53
+ }
54
+
55
+ get taskPrefixes() {
56
+ return ['headless']
57
+ }
58
+
59
+ get description() {
60
+ return 'Headless Pool Connector'
61
+ }
62
+ }
63
+
64
+ ConnectionManager.registerConnector('headless-connector', new HeadlessConnector())
@@ -0,0 +1,69 @@
1
+ import * as genericPool from 'generic-pool'
2
+
3
+ import { config, logger } from '@things-factory/env'
4
+
5
+ try {
6
+ var puppeteer = require('puppeteer')
7
+ } catch (err) {
8
+ logger.error(err)
9
+ }
10
+
11
+ export function createHeadlessPool(options: genericPool.Options) {
12
+ return genericPool.createPool(
13
+ {
14
+ create() {
15
+ console.log('headless-pool-integration about to create')
16
+ return initializeChromium()
17
+ },
18
+ validate(browser) {
19
+ return Promise.race([
20
+ new Promise(res => setTimeout(() => res(false), 1500)),
21
+ browser
22
+ //@ts-ignore
23
+ .version()
24
+ .then(_ => true)
25
+ .catch(_ => false)
26
+ ])
27
+ },
28
+ destroy(browser) {
29
+ //@ts-ignore
30
+ return browser.close()
31
+ }
32
+ } as genericPool.Factory<any>,
33
+ options
34
+ )
35
+ }
36
+
37
+ export async function destroyHeadlessPool(headlessPool: genericPool.Pool<any>) {
38
+ if (headlessPool) {
39
+ console.log('headless-pool-integration about to destroy')
40
+
41
+ await headlessPool.drain()
42
+ await headlessPool.clear()
43
+ }
44
+ }
45
+
46
+ const CHROMIUM_PATH = config.get('CHROMIUM_PATH')
47
+
48
+ async function initializeChromium() {
49
+ try {
50
+ if (!puppeteer) {
51
+ return
52
+ }
53
+
54
+ var launchSetting = {
55
+ args: ['--hide-scrollbars', '--mute-audio', '--no-sandbox', '--use-gl=egl'],
56
+ headless: 'shell'
57
+ }
58
+
59
+ if (CHROMIUM_PATH) {
60
+ launchSetting['executablePath'] = CHROMIUM_PATH
61
+ }
62
+
63
+ const browser = await puppeteer.launch(launchSetting)
64
+
65
+ return browser
66
+ } catch (err) {
67
+ logger.error(err)
68
+ }
69
+ }
@@ -0,0 +1 @@
1
+ import './headless-connector'
@@ -0,0 +1,2 @@
1
+ import './connector'
2
+ import './task'
@@ -0,0 +1,222 @@
1
+ import * as ejs from 'ejs'
2
+ import { PDFDocument, rgb, degrees } from 'pdf-lib'
3
+
4
+ import { TaskRegistry } from '@things-factory/integration-base'
5
+ import { ConnectionManager } from '@things-factory/integration-base'
6
+
7
+ async function HeadlessPDFCapture(step, context) {
8
+ var { connection: connectionName, params: stepOptions } = step
9
+ var {
10
+ htmlContent,
11
+ format,
12
+ width,
13
+ height,
14
+ margin,
15
+ scale,
16
+ printBackground,
17
+ landscape,
18
+ displayHeaderFooter,
19
+ headerTemplate,
20
+ footerTemplate,
21
+ preferCSSPageSize
22
+ } = stepOptions || {}
23
+ var { domain, data, __headless_pdf } = context
24
+
25
+ if (!__headless_pdf) {
26
+ throw new Error('it requires headless-pdf-open to be performed first')
27
+ }
28
+
29
+ var {
30
+ pdf,
31
+ header: headerTemplateFromContext,
32
+ footer: footerTemplateFromContext,
33
+ watermark,
34
+ pageCount
35
+ } = __headless_pdf
36
+
37
+ var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
38
+ let browser
39
+
40
+ try {
41
+ // Puppeteer를 사용하여 브라우저와 페이지 인스턴스를 생성
42
+ browser = await headlessPool.acquire()
43
+ const page = await browser.newPage()
44
+
45
+ // 설정된 HTML 콘텐츠를 페이지에 설정
46
+ await page.setContent(htmlContent)
47
+
48
+ // PDF 페이지 생성 옵션 설정
49
+ if (preferCSSPageSize) {
50
+ width = undefined
51
+ height = undefined
52
+ }
53
+
54
+ const pageOptions = {
55
+ format,
56
+ width,
57
+ height,
58
+ margin,
59
+ scale,
60
+ printBackground,
61
+ landscape,
62
+ displayHeaderFooter,
63
+ headerTemplate: displayHeaderFooter ? headerTemplate : undefined,
64
+ footerTemplate: displayHeaderFooter ? footerTemplate : undefined,
65
+ preferCSSPageSize
66
+ }
67
+
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
+ }
119
+
120
+ pdf.addPage(newPage)
121
+ })
122
+
123
+ // 페이지 카운트 업데이트
124
+ __headless_pdf.pageCount += pages.length
125
+
126
+ await page.close()
127
+ headlessPool.release(browser)
128
+
129
+ return {
130
+ data: __headless_pdf
131
+ }
132
+ } catch (error) {
133
+ if (browser) {
134
+ await browser.close()
135
+ }
136
+
137
+ throw error
138
+ }
139
+ }
140
+
141
+ HeadlessPDFCapture.parameterSpec = [
142
+ {
143
+ type: 'string',
144
+ name: 'htmlContent',
145
+ label: 'html-content'
146
+ },
147
+ {
148
+ type: 'select',
149
+ name: 'format',
150
+ label: 'page-format',
151
+ options: [
152
+ { label: 'A4', value: 'A4' },
153
+ { label: 'A3', value: 'A3' },
154
+ { label: 'Letter', value: 'Letter' },
155
+ { label: 'Legal', value: 'Legal' }
156
+ ],
157
+ description: 'Select the paper format for the PDF'
158
+ },
159
+ {
160
+ type: 'string',
161
+ name: 'width',
162
+ label: 'page-width',
163
+ description: 'Specify the width of the page (e.g., "8.5in", "21cm", "600px")'
164
+ },
165
+ {
166
+ type: 'string',
167
+ name: 'height',
168
+ label: 'page-height',
169
+ description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
170
+ },
171
+ {
172
+ type: 'json',
173
+ name: 'margin',
174
+ label: 'page-margins',
175
+ fields: [
176
+ { type: 'string', name: 'top', label: 'Top Margin', description: 'Top margin (e.g., "1in")' },
177
+ { type: 'string', name: 'right', label: 'Right Margin', description: 'Right margin (e.g., "1in")' },
178
+ { type: 'string', name: 'bottom', label: 'Bottom Margin', description: 'Bottom margin (e.g., "1in")' },
179
+ { type: 'string', name: 'left', label: 'Left Margin', description: 'Left margin (e.g., "1in")' }
180
+ ],
181
+ description: 'Set the margins for the page'
182
+ },
183
+ {
184
+ type: 'number',
185
+ name: 'scale',
186
+ label: 'scale',
187
+ defaultValue: 1,
188
+ description: 'Set the scale of the page content (default is 1)'
189
+ },
190
+ {
191
+ type: 'boolean',
192
+ name: 'printBackground',
193
+ label: 'print-background',
194
+ defaultValue: true,
195
+ description: 'Include background graphics when printing the page'
196
+ },
197
+ {
198
+ type: 'boolean',
199
+ name: 'landscape',
200
+ label: 'landscape',
201
+ defaultValue: false,
202
+ description: 'Print the PDF in landscape orientation'
203
+ },
204
+ {
205
+ type: 'boolean',
206
+ name: 'displayHeaderFooter',
207
+ label: 'display-header/footer',
208
+ defaultValue: false,
209
+ description: 'Display header and footer in the PDF'
210
+ },
211
+ {
212
+ type: 'boolean',
213
+ name: 'preferCSSPageSize',
214
+ label: 'prefer-css-page-size',
215
+ defaultValue: false,
216
+ description: 'Whether to prefer the CSS-defined page size over the given width and height'
217
+ }
218
+ ]
219
+
220
+ HeadlessPDFCapture.help = 'integration/task/headless-pdf-capture'
221
+
222
+ TaskRegistry.registerTaskHandler('headless-pdf-capture', HeadlessPDFCapture)
@@ -0,0 +1,75 @@
1
+ import * as ejs from 'ejs'
2
+ import { PDFDocument } from 'pdf-lib'
3
+
4
+ import { TaskRegistry } from '@things-factory/integration-base'
5
+ import { ConnectionManager } from '@things-factory/integration-base'
6
+
7
+ async function HeadlessPDFOpen(step, context) {
8
+ var { connection: connectionName, params: stepOptions } = step
9
+ var {
10
+ coverPage,
11
+ lastPage,
12
+ header,
13
+ footer,
14
+ watermark,
15
+ fileName // 추가된 파일 이름
16
+ } = stepOptions || {}
17
+
18
+ // var { domain, data, variables } = context
19
+ // var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
20
+
21
+ const pdf = await PDFDocument.create()
22
+
23
+ const data = {
24
+ pdf,
25
+ coverPage,
26
+ lastPage,
27
+ header,
28
+ footer,
29
+ watermark,
30
+ fileName, // 파일 이름을 context.data에 저장
31
+ pageCount: 0 // 페이지 카운트를 초기화하여 context에 저장
32
+ }
33
+
34
+ context.__headless_pdf = data
35
+
36
+ return {
37
+ data
38
+ }
39
+ }
40
+
41
+ HeadlessPDFOpen.parameterSpec = [
42
+ {
43
+ type: 'string',
44
+ name: 'coverPage',
45
+ label: 'pdf-cover-page'
46
+ },
47
+ {
48
+ type: 'string',
49
+ name: 'lastPage',
50
+ label: 'pdf-last-page'
51
+ },
52
+ {
53
+ type: 'string',
54
+ name: 'header',
55
+ label: 'header' // header template
56
+ },
57
+ {
58
+ type: 'string',
59
+ name: 'footer',
60
+ label: 'footer' // footer template
61
+ },
62
+ {
63
+ type: 'string',
64
+ name: 'watermark',
65
+ label: 'watermark' // watermark
66
+ },
67
+ {
68
+ type: 'string',
69
+ name: 'fileName',
70
+ label: 'filename' // 파일 이름 지정
71
+ }
72
+ ]
73
+ HeadlessPDFOpen.help = 'integration/task/headless-open'
74
+
75
+ TaskRegistry.registerTaskHandler('headless-open', HeadlessPDFOpen)
@@ -0,0 +1,84 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { PDFDocument, rgb, degrees } from 'pdf-lib'
4
+
5
+ import { TaskRegistry } from '@things-factory/integration-base'
6
+
7
+ async function HeadlessPDFSave(step, context) {
8
+ var { params: stepOptions } = step
9
+ var { outputPath } = stepOptions || {}
10
+ var { pdf, coverPage, lastPage, header, footer, watermark } = context.data
11
+
12
+ // 파일 이름이 설정되지 않았을 경우 기본 파일 이름 생성
13
+ const finalFileName = context.data.fileName || `document-${Date.now()}.pdf`
14
+ const savePath = path.join(outputPath || '/path/to/default/storage', finalFileName)
15
+
16
+ // PDF 문서에 coverPage 및 lastPage 추가
17
+ if (coverPage) {
18
+ const coverPageDoc = await PDFDocument.load(coverPage)
19
+ const [cover] = await pdf.copyPages(coverPageDoc, [0])
20
+
21
+ // 커버 페이지에 워터마크 추가
22
+ if (watermark) {
23
+ const fontSize = 50
24
+ const pageWidth = cover.getWidth()
25
+ const pageHeight = cover.getHeight()
26
+
27
+ cover.drawText(watermark, {
28
+ x: pageWidth / 2 - (fontSize * watermark.length) / 4,
29
+ y: pageHeight / 2 - fontSize / 2,
30
+ size: fontSize,
31
+ color: rgb(0.75, 0.75, 0.75),
32
+ rotate: degrees(-45),
33
+ opacity: 0.5
34
+ })
35
+ }
36
+
37
+ pdf.insertPage(0, cover)
38
+ }
39
+
40
+ if (lastPage) {
41
+ const lastPageDoc = await PDFDocument.load(lastPage)
42
+ const [last] = await pdf.copyPages(lastPageDoc, [0])
43
+
44
+ // 마지막 페이지에 워터마크 추가
45
+ if (watermark) {
46
+ const fontSize = 50
47
+ const pageWidth = last.getWidth()
48
+ const pageHeight = last.getHeight()
49
+
50
+ last.drawText(watermark, {
51
+ x: pageWidth / 2 - (fontSize * watermark.length) / 4,
52
+ y: pageHeight / 2 - fontSize / 2,
53
+ size: fontSize,
54
+ color: rgb(0.75, 0.75, 0.75),
55
+ rotate: degrees(-45),
56
+ opacity: 0.5
57
+ })
58
+ }
59
+
60
+ pdf.addPage(last)
61
+ }
62
+
63
+ // PDF 파일로 저장
64
+ const pdfBytes = await pdf.save()
65
+ fs.writeFileSync(savePath, pdfBytes)
66
+
67
+ return {
68
+ data: {
69
+ link: savePath // 저장된 PDF 파일의 경로를 반환
70
+ }
71
+ }
72
+ }
73
+
74
+ HeadlessPDFSave.parameterSpec = [
75
+ {
76
+ type: 'string',
77
+ name: 'outputPath',
78
+ label: 'output-path' // PDF를 저장할 경로
79
+ }
80
+ ]
81
+
82
+ HeadlessPDFSave.help = 'integration/task/headless-pdf-save'
83
+
84
+ TaskRegistry.registerTaskHandler('headless-pdf-save', HeadlessPDFSave)
@@ -0,0 +1,3 @@
1
+ import './headless-pdf-capture'
2
+ import './headless-pdf-open'
3
+ import './headless-pdf-save'
@@ -0,0 +1 @@
1
+ import './engine'
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "strict": false,
5
+ "module": "commonjs",
6
+ "outDir": "../dist-server",
7
+ "baseUrl": "./"
8
+ },
9
+ "include": ["./**/*"]
10
+ }
@@ -0,0 +1 @@
1
+ export default {}
@@ -0,0 +1,19 @@
1
+ {
2
+ "label.display-header/footer": "display header/footer",
3
+ "label.filename": "filename",
4
+ "label.footer": "footer template",
5
+ "label.header": "header template",
6
+ "label.html-content": "html content",
7
+ "label.landscape": "landscape",
8
+ "label.output-path": "output path",
9
+ "label.page-format": "page format",
10
+ "label.page-height": "page height",
11
+ "label.page-margins": "page margins",
12
+ "label.page-width": "page width",
13
+ "label.pdf-cover-page": "cover page template",
14
+ "label.pdf-last-page": "last page template",
15
+ "label.prefer-css-page-size": "prefer css page size",
16
+ "label.print-background": "print background",
17
+ "label.scale": "scale",
18
+ "label.watermark": "watermark"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "label.display-header/footer": "ヘッダー/フッターを表示",
3
+ "label.filename": "ファイル名",
4
+ "label.footer": "フッターテンプレート",
5
+ "label.header": "ヘッダーテンプレート",
6
+ "label.html-content": "html コンテンツ",
7
+ "label.landscape": "横向き",
8
+ "label.output-path": "出力パス",
9
+ "label.page-format": "ページ形式",
10
+ "label.page-height": "ページ高さ",
11
+ "label.page-margins": "ページ余白",
12
+ "label.page-width": "ページ幅",
13
+ "label.pdf-cover-page": "表紙テンプレート",
14
+ "label.pdf-last-page": "最終ページテンプレート",
15
+ "label.prefer-css-page-size": "cssページサイズを優先",
16
+ "label.print-background": "背景を印刷",
17
+ "label.scale": "スケール",
18
+ "label.watermark": "透かし"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "label.display-header/footer": "머릿글/바닥글 표시여부",
3
+ "label.filename": "파일이름",
4
+ "label.footer": "바닥글 템플릿",
5
+ "label.header": "머릿글 템플릿",
6
+ "label.html-content": "본문 템플릿",
7
+ "label.landscape": "가로 여부",
8
+ "label.output-path": "출력 패스",
9
+ "label.page-format": "페이지 형식",
10
+ "label.page-height": "페이지 높이",
11
+ "label.page-margins": "페이지 여백",
12
+ "label.page-width": "페이지 폭",
13
+ "label.pdf-cover-page": "표지 템플릿",
14
+ "label.pdf-last-page": "뒷표지 템플릿",
15
+ "label.prefer-css-page-size": "선호 CSS 페이지 크기",
16
+ "label.print-background": "배경 포함여부",
17
+ "label.scale": "페이지 비율",
18
+ "label.watermark": "워터마크"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "label.display-header/footer": "papar pengepala/pengaki",
3
+ "label.filename": "nama fail",
4
+ "label.footer": "templat pengaki",
5
+ "label.header": "templat pengepala",
6
+ "label.html-content": "kandungan html",
7
+ "label.landscape": "melintang",
8
+ "label.output-path": "laluan output",
9
+ "label.page-format": "format halaman",
10
+ "label.page-height": "tinggi halaman",
11
+ "label.page-margins": "margin halaman",
12
+ "label.page-width": "lebar halaman",
13
+ "label.pdf-cover-page": "templat muka surat depan",
14
+ "label.pdf-last-page": "templat muka surat terakhir",
15
+ "label.prefer-css-page-size": "utamakan saiz halaman css",
16
+ "label.print-background": "cetak latar belakang",
17
+ "label.scale": "skala",
18
+ "label.watermark": "tanda air"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "label.display-header/footer": "显示页眉/页脚",
3
+ "label.filename": "文件名",
4
+ "label.footer": "页脚模板",
5
+ "label.header": "页眉模板",
6
+ "label.html-content": "html 内容",
7
+ "label.landscape": "横向",
8
+ "label.output-path": "输出路径",
9
+ "label.page-format": "页面格式",
10
+ "label.page-height": "页面高度",
11
+ "label.page-margins": "页面边距",
12
+ "label.page-width": "页面宽度",
13
+ "label.pdf-cover-page": "封面模板",
14
+ "label.pdf-last-page": "尾页模板",
15
+ "label.prefer-css-page-size": "偏好 css 页面大小",
16
+ "label.print-background": "打印背景",
17
+ "label.scale": "缩放",
18
+ "label.watermark": "水印"
19
+ }