Skip to content

Commit 6bb07bf

Browse files
committed
feat(ghouldscript): add isRequirePassword
1 parent 159bb2c commit 6bb07bf

File tree

9 files changed

+204
-72
lines changed

9 files changed

+204
-72
lines changed

packages/ghoulscript/README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ const input = document.querySelector('input[type="file"]')
1818

1919
input.addEventListener('change', async () => {
2020
if (input.files) {
21-
const file = input.files[0]
22-
const buffer = await file.arrayBuffer()
23-
const output = await optimizePDF(new Uint8Array(buffer))
21+
const file = input.files[0]
22+
const output = await optimizePDF(file)
23+
const outputURL = URL.createObjectURL(new Blob([output], { type: 'application/pdf' }))
2424

25-
window.open(URL.createObjectURL(new Blob([output], { type: 'application/pdf' })), '_blank')
25+
window.open(outputURL, '_blank')
2626
}
2727
})
2828
```
@@ -60,7 +60,7 @@ await fs.writeFile(resolve(__dirname, './sample.compressed.pdf'), output)
6060
|---------------------------|:---------:|:--------:|------------------------------------------------------------------------------------|
6161
| `password` | `String` | - | Document protection password |
6262
| `pdfSettings` | `String` | `screen` | Preset setting, valid value is `screen`, `ebook`, `printer`, `prepress`, `default` |
63-
| `fastWebView` | `Boolean` | `true` | Enable Linearization |
63+
| `fastWebView` | `Boolean` | `true` | Enable Fast Web View (Linearization) |
6464
| `compatibilityLevel` | `String` | `1.4` | Compability version |
6565
| `colorConversionStrategy` | `String` | `RGB` | Color conversion strategy, valid value is `RGB`, `CMYK` |
6666
| `noTransparency` | `Boolean` | `true` | Remove transparency |
@@ -183,6 +183,20 @@ console.log(info)
183183
*/
184184
```
185185

186+
## isRequirePassword (file: Buffer)
187+
188+
Check document is require password or not to open.
189+
190+
```ts
191+
import { isRequirePassword } from '@privyid/ghoulscript'
192+
193+
const bufferA = await fs.readFile(resolve(__dirname, './sample.pdf'))
194+
const bufferB = await fs.readFile(resolve(__dirname, './sample.protected.pdf'))
195+
196+
console.log(await isRequirePassword(bufferA)) // false
197+
console.log(await isRequirePassword(bufferB)) // true
198+
```
199+
186200
## License
187201

188202
[AGPL-3.0](./LICENSE)

packages/ghoulscript/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@privyid/ghoulscript",
33
"packageManager": "yarn@4.2.2",
4-
"version": "0.1.0-alpha.1",
4+
"version": "0.1.0-alpha.2",
55
"type": "module",
66
"main": "./dist/index.cjs",
77
"module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
1919
"scripts": {
2020
"prepare": "yarn build",
2121
"build": "unbuild",
22-
"test": "jiti ./tests/sample.ts"
22+
"test": "node --test"
2323
},
2424
"devDependencies": {
2525
"jiti": "1.21.0",

packages/ghoulscript/src/core.ts

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface CompressOptions {
1919
*/
2020
pdfSettings: 'screen' | 'ebook' | 'printer' | 'prepress' | 'default',
2121
/**
22-
* Enable Linearization
22+
* Enable Fast Web View (Linearization)
2323
* @default true
2424
*/
2525
fastWebView: boolean,
@@ -63,8 +63,17 @@ export interface CompressOptions {
6363
pageList?: PageList,
6464
}
6565

66-
async function createPDF (inputs: ArrayBufferView[], options: Partial<CompressOptions> = {}): Promise<Uint8Array> {
67-
const gs = await useGS()
66+
type InputFile = ArrayBufferView | Blob
67+
68+
async function readFile (input: InputFile): Promise<ArrayBufferView> {
69+
if (input instanceof globalThis.Blob)
70+
return new Uint8Array(await input.arrayBuffer())
71+
72+
return input
73+
}
74+
75+
async function createPDF (inputs: InputFile[], options: Partial<CompressOptions> = {}): Promise<Uint8Array> {
76+
const gs = await useGS({ print () {}, printErr () {} })
6877
const opts = defu<CompressOptions, [CompressOptions]>(options, {
6978
pdfSettings : 'screen',
7079
compatibilityLevel : '1.4',
@@ -129,7 +138,7 @@ async function createPDF (inputs: ArrayBufferView[], options: Partial<CompressOp
129138
for (const [i, input] of inputs.entries()) {
130139
const inputFilename = `./input-${i}`
131140

132-
gs.FS.writeFile(inputFilename, input)
141+
gs.FS.writeFile(inputFilename, await readFile(input))
133142
args.push(inputFilename)
134143
}
135144

@@ -144,7 +153,7 @@ async function createPDF (inputs: ArrayBufferView[], options: Partial<CompressOp
144153
* @param option
145154
* @returns
146155
*/
147-
export async function optimizePDF (input: ArrayBufferView, option: Partial<CompressOptions> = {}) {
156+
export async function optimizePDF (input: InputFile, option: Partial<CompressOptions> = {}): Promise<Uint8Array> {
148157
return await createPDF([input], option)
149158
}
150159

@@ -154,7 +163,7 @@ export async function optimizePDF (input: ArrayBufferView, option: Partial<Compr
154163
* @param option
155164
* @returns
156165
*/
157-
export async function combinePDF (inputs: ArrayBufferView[], option: Partial<CompressOptions> = {}) {
166+
export async function combinePDF (inputs: InputFile[], option: Partial<CompressOptions> = {}): Promise<Uint8Array> {
158167
return await createPDF(inputs, option)
159168
}
160169

@@ -165,7 +174,7 @@ export async function combinePDF (inputs: ArrayBufferView[], option: Partial<Com
165174
* @param option
166175
* @returns
167176
*/
168-
export async function splitPdf (input: ArrayBufferView, pageLists: PageList[], option: Partial<CompressOptions> = {}) {
177+
export async function splitPdf (input: InputFile, pageLists: PageList[], option: Partial<CompressOptions> = {}): Promise<Uint8Array[]> {
169178
return await Promise.all(
170179
pageLists.map(async (pageList) => {
171180
return await createPDF([input], defu({ pageList }, option))
@@ -180,7 +189,7 @@ export async function splitPdf (input: ArrayBufferView, pageLists: PageList[], o
180189
* @param ownerPassword
181190
* @returns
182191
*/
183-
export async function addPassword (input: ArrayBufferView, userPassword: string, ownerPassword: string = userPassword) {
192+
export async function addPassword (input: InputFile, userPassword: string, ownerPassword: string = userPassword): Promise<Uint8Array> {
184193
return await createPDF([input], {
185194
ownerPassword,
186195
userPassword,
@@ -193,7 +202,7 @@ export async function addPassword (input: ArrayBufferView, userPassword: string,
193202
* @param password
194203
* @returns
195204
*/
196-
export async function removePassword (input: ArrayBufferView, password: string) {
205+
export async function removePassword (input: InputFile, password: string): Promise<Uint8Array> {
197206
return await createPDF([input], { keepPassword: false, password: password })
198207
}
199208

@@ -227,8 +236,8 @@ export interface RenderOptions {
227236
* @param options
228237
* @returns
229238
*/
230-
export async function renderPageAsImage (input: ArrayBufferView, pageNumber: number = 1, options: Partial<RenderOptions> = {}) {
231-
const gs = await useGS()
239+
export async function renderPageAsImage (input: InputFile, pageNumber: number = 1, options: Partial<RenderOptions> = {}): Promise<Uint8Array> {
240+
const gs = await useGS({ print () {}, printErr () {} })
232241
const opts = defu<RenderOptions, [RenderOptions]>(options, {
233242
format : 'jpg',
234243
graphicsAlphaBits: 4,
@@ -251,7 +260,7 @@ export async function renderPageAsImage (input: ArrayBufferView, pageNumber: num
251260
'./input',
252261
]
253262

254-
gs.FS.writeFile('./input', input)
263+
gs.FS.writeFile('./input', await readFile(input))
255264

256265
await gs.callMain(args)
257266

@@ -273,7 +282,7 @@ export interface Info {
273282
* @param options
274283
* @returns
275284
*/
276-
export async function getInfo (input: ArrayBufferView, options: Pick<CompressOptions, 'password'> = {}): Promise<Info> {
285+
export async function getInfo (input: InputFile, options: Pick<CompressOptions, 'password'> = {}): Promise<Info> {
277286
const info: Info = {
278287
numPages: 0,
279288
pages : [],
@@ -313,9 +322,42 @@ export async function getInfo (input: ArrayBufferView, options: Pick<CompressOpt
313322
if (options.password)
314323
args.splice(-1, 0, `-sPDFPassword=${options.password}`)
315324

316-
gs.FS.writeFile('./input', input)
325+
gs.FS.writeFile('./input', await readFile(input))
317326

318327
await gs.callMain(args)
319328

320329
return info
321330
}
331+
332+
/**
333+
* Check document is encrypted using password or not
334+
* @param input
335+
* @returns - true if required
336+
*/
337+
export async function isRequirePassword (input: InputFile): Promise<boolean> {
338+
let result = false
339+
340+
const gs = await useGS({
341+
print () {},
342+
printErr (str) {
343+
if (str.match('This file requires a password for access'))
344+
result = true
345+
},
346+
})
347+
348+
const args = [
349+
'-dQUIET',
350+
'-dNOPAUSE',
351+
'-dNODISPLAY',
352+
'-dBATCH',
353+
'-dSAFER',
354+
'-dPDFINFO',
355+
'./input',
356+
]
357+
358+
gs.FS.writeFile('./input', await readFile(input))
359+
360+
await gs.callMain(args)
361+
362+
return result
363+
}

packages/ghoulscript/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,7 @@ export const renderPageAsImage: typeof core.renderPageAsImage = async (...args:
5151
export const getInfo: typeof core.getInfo = async (...args: CommandArgs<'getInfo'>): CommandResult<'getInfo'> => {
5252
return await call('getInfo', args)
5353
}
54+
55+
export const isRequirePassword: typeof core.isRequirePassword = async (...args: CommandArgs<'isRequirePassword'>): CommandResult<'isRequirePassword'> => {
56+
return await call('isRequirePassword', args)
57+
}

packages/ghoulscript/src/rpc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ let worker: Worker
2424

2525
export async function useWorkerRPC () {
2626
if (!worker)
27-
worker = new Worker(new URL('rpc.worker.mjs', import.meta.url))
27+
worker = new Worker(new URL('rpc.worker.mjs', import.meta.url), { type: 'module' })
2828

2929
return worker
3030
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { fileURLToPath } from 'node:url'
2+
import { dirname, resolve } from 'node:path'
3+
import {
4+
describe,
5+
it,
6+
} from 'node:test'
7+
import fs from 'node:fs/promises'
8+
import assert from 'node:assert'
9+
import {
10+
optimizePDF,
11+
combinePDF,
12+
splitPdf,
13+
addPassword,
14+
removePassword,
15+
isRequirePassword,
16+
getInfo,
17+
} from '../dist/index.mjs'
18+
19+
const _filename = fileURLToPath(import.meta.url)
20+
const _dirname = dirname(_filename)
21+
22+
describe('optimizePDF', () => {
23+
it('should able to optimize size of PDF file', async () => {
24+
const input = await fs.readFile(resolve(_dirname, './sample.pdf'))
25+
const output = await optimizePDF(input)
26+
27+
assert.ok(output.byteLength < input.byteLength)
28+
})
29+
})
30+
31+
describe('combinePDF', () => {
32+
it('should able to combine 2 PDFs into 1', async () => {
33+
const input = await fs.readFile(resolve(_dirname, './sample.pdf'))
34+
const input2 = await fs.readFile(resolve(_dirname, './sample-2.pdf'))
35+
const output = await combinePDF([input, input2])
36+
37+
const info1 = await getInfo(input)
38+
const info2 = await getInfo(input2)
39+
const info3 = await getInfo(output)
40+
41+
assert.equal(info1.numPages + info2.numPages, info3.numPages)
42+
})
43+
})
44+
45+
describe('splitPdf', () => {
46+
it('should able to split single PDF into multiple file', async () => {
47+
const input = await fs.readFile(resolve(_dirname, './sample.pdf'))
48+
const outputs = await splitPdf(input, ['1-3', '4-10'])
49+
50+
const info1 = await getInfo(outputs[0])
51+
const info2 = await getInfo(outputs[1])
52+
53+
assert.equal(info1.numPages, 3)
54+
assert.equal(info2.numPages, 7)
55+
})
56+
})
57+
58+
describe('addPassword', () => {
59+
it('should able to add password to PDf file', async () => {
60+
const input = await fs.readFile(resolve(_dirname, './sample.pdf'))
61+
const output = await addPassword(input, '******')
62+
63+
const a = await isRequirePassword(input)
64+
const b = await isRequirePassword(output)
65+
66+
assert.equal(a, false)
67+
assert.equal(b, true)
68+
})
69+
})
70+
71+
describe('removePassword', () => {
72+
it('should able to remove password existing PDF', async () => {
73+
const input = await fs.readFile(resolve(_dirname, './sample.protected.pdf'))
74+
const output = await removePassword(input, '123456')
75+
76+
const a = await isRequirePassword(input)
77+
const b = await isRequirePassword(output)
78+
79+
assert.equal(a, true)
80+
assert.equal(b, false)
81+
})
82+
})
83+
84+
describe('isRequirePassword', () => {
85+
it('should return false if file can be open without password', async () => {
86+
const input = await fs.readFile(resolve(_dirname, './sample.pdf'))
87+
const result = await isRequirePassword(input)
88+
89+
assert.equal(result, false)
90+
})
91+
92+
it('should return true if file require password to open', async () => {
93+
const input = await fs.readFile(resolve(_dirname, './sample.protected.pdf'))
94+
const result = await isRequirePassword(input)
95+
96+
assert.equal(result, true)
97+
})
98+
})
858 KB
Binary file not shown.

packages/ghoulscript/tests/sample.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)