|
| 1 | +<script setup lang="ts"> |
| 2 | +import { Base64 } from 'js-base64'; |
| 3 | +import createQPDFModule from 'qpdf-wasm-esm-embedded'; |
| 4 | +import { useDownloadFileFromBase64Refs } from '@/composable/downloadBase64'; |
| 5 | +
|
| 6 | +const status = ref<'idle' | 'done' | 'error' | 'processing'>('idle'); |
| 7 | +const file = ref<File | null>(null); |
| 8 | +
|
| 9 | +const restrictAccessibility = useStorage('pdf-encrypt:accessibility', false); |
| 10 | +const restrictAnnotate = useStorage('pdf-encrypt:annotate', false); |
| 11 | +const restrictAssemble = useStorage('pdf-encrypt:assemble', false); |
| 12 | +const restrictExtract = useStorage('pdf-encrypt:extract', false); |
| 13 | +const restrictForm = useStorage('pdf-encrypt:form', false); |
| 14 | +const restrictModifyOther = useStorage('pdf-encrypt:modoth', false); |
| 15 | +const clearTextMetadata = useStorage('pdf-encrypt:clearmeta', false); |
| 16 | +const restrictModify = useStorage('pdf-encrypt:mod', 'all'); |
| 17 | +const restrictPrint = useStorage('pdf-encrypt:print', 'full'); |
| 18 | +const userPassword = ref(''); |
| 19 | +const ownerPassword = ref(''); |
| 20 | +
|
| 21 | +const base64OutputPDF = ref(''); |
| 22 | +const logs = ref<string[]>([]); |
| 23 | +const fileName = ref(''); |
| 24 | +const fileExtension = ref('pdf'); |
| 25 | +const { download } = useDownloadFileFromBase64Refs( |
| 26 | + { |
| 27 | + source: base64OutputPDF, |
| 28 | + filename: fileName, |
| 29 | + extension: fileExtension, |
| 30 | + }); |
| 31 | +
|
| 32 | +function onFileUploaded(uploadedFile: File) { |
| 33 | + file.value = uploadedFile; |
| 34 | + fileName.value = `encrypted_${file.value.name}`; |
| 35 | +} |
| 36 | +
|
| 37 | +async function onProcessClicked() { |
| 38 | + if (!file.value) { |
| 39 | + return; |
| 40 | + } |
| 41 | + const fileBuffer = await file.value.arrayBuffer(); |
| 42 | +
|
| 43 | + status.value = 'processing'; |
| 44 | + try { |
| 45 | + const options = [ |
| 46 | + '--verbose', |
| 47 | + '--encrypt', |
| 48 | + ]; |
| 49 | + options.push(`${userPassword.value}`); |
| 50 | + options.push(`${ownerPassword.value}`); |
| 51 | + options.push('128'); |
| 52 | + options.push('--use-aes=y'); |
| 53 | + options.push(`--accessibility=${(restrictAccessibility.value ? 'n' : 'y')}`); |
| 54 | + options.push(`--annotate=${(restrictAnnotate.value ? 'n' : 'y')}`); |
| 55 | + options.push(`--assemble=${(restrictAssemble.value ? 'n' : 'y')}`); |
| 56 | + options.push(`--extract=${(restrictExtract.value ? 'n' : 'y')}`); |
| 57 | + options.push(`--form=${(restrictForm.value ? 'n' : 'y')}`); |
| 58 | + options.push(`--modify-other=${(restrictModifyOther.value ? 'n' : 'y')}`); |
| 59 | + options.push(`--modify=${(restrictModify.value)}`); |
| 60 | + options.push(`--print=${(restrictPrint.value)}`); |
| 61 | + if (clearTextMetadata.value) { |
| 62 | + options.push('--cleartext-metadata'); |
| 63 | + } |
| 64 | + options.push('--'); |
| 65 | + options.push('in.pdf'); |
| 66 | + options.push('out.pdf'); |
| 67 | + const outPdfBuffer = await callMainWithInOutPdf(fileBuffer, |
| 68 | + options, 0); |
| 69 | + base64OutputPDF.value = `data:application/pdf;base64,${Base64.fromUint8Array(outPdfBuffer)}`; |
| 70 | + status.value = 'done'; |
| 71 | +
|
| 72 | + download(); |
| 73 | + } |
| 74 | + catch (e) { |
| 75 | + status.value = 'error'; |
| 76 | + } |
| 77 | +} |
| 78 | +
|
| 79 | +async function callMainWithInOutPdf(data: ArrayBuffer, args: string[], expected_exitcode: number) { |
| 80 | + logs.value = []; |
| 81 | + const mod = await createQPDFModule({ |
| 82 | + print(text: string) { |
| 83 | + logs.value.push(text); |
| 84 | + }, |
| 85 | + printErr(text: string) { |
| 86 | + logs.value.push(text); |
| 87 | + }, |
| 88 | + }); |
| 89 | + mod.FS.writeFile('in.pdf', new Uint8Array(data)); |
| 90 | + const ret = mod.callMain(args); |
| 91 | + if (expected_exitcode !== ret) { |
| 92 | + throw new Error('Process run failed'); |
| 93 | + } |
| 94 | + return mod.FS.readFile('out.pdf'); |
| 95 | +} |
| 96 | +
|
| 97 | +const printRestrictionOptions = [{ value: 'none', label: 'Disallow printing' }, |
| 98 | + { value: 'low', label: 'Allow only low-resolution printing' }, |
| 99 | + { value: 'full', label: 'Allow full printing' }, |
| 100 | +]; |
| 101 | +const modificationRestrictionOptions = [ |
| 102 | + { value: 'none', label: 'Allow no modifications' }, |
| 103 | + { value: 'assembly', label: 'Allow document assembly only' }, |
| 104 | + { value: 'form', label: 'Allow document assembly only + filling in form fields and signing' }, |
| 105 | + { value: 'annotate', label: 'Allow document assembly only + filling in form fields and signing + commenting and modifying forms' }, |
| 106 | + { value: 'all', label: 'Allow full document modification' }, |
| 107 | +]; |
| 108 | +</script> |
| 109 | + |
| 110 | +<template> |
| 111 | + <div> |
| 112 | + <div style="flex: 0 0 100%"> |
| 113 | + <div mx-auto max-w-600px> |
| 114 | + <c-file-upload |
| 115 | + title="Drag and drop a PDF file here, or click to select a file" |
| 116 | + accept=".pdf" |
| 117 | + @file-upload="onFileUploaded" |
| 118 | + /> |
| 119 | + <div mt-2 text-center> |
| 120 | + <strong>Output file:</strong> {{ fileName }} |
| 121 | + </div> |
| 122 | + </div> |
| 123 | + </div> |
| 124 | + |
| 125 | + <c-card title="Permissions" mb-3 mt-3> |
| 126 | + <n-space> |
| 127 | + <n-checkbox v-model:checked="restrictAccessibility"> |
| 128 | + Restrict accessibility (usually ignored) |
| 129 | + </n-checkbox> |
| 130 | + <n-checkbox v-model:checked="restrictAnnotate"> |
| 131 | + Restrict commenting/filling form fields |
| 132 | + </n-checkbox> |
| 133 | + <n-checkbox v-model:checked="restrictAssemble"> |
| 134 | + Restrict document assembly |
| 135 | + </n-checkbox> |
| 136 | + <n-checkbox v-model:checked="restrictExtract"> |
| 137 | + Restrict text/graphic extraction |
| 138 | + </n-checkbox> |
| 139 | + <n-checkbox v-model:checked="restrictForm"> |
| 140 | + Restrict filling form fields |
| 141 | + </n-checkbox> |
| 142 | + <n-checkbox v-model:checked="restrictModifyOther"> |
| 143 | + Restrict other modifications |
| 144 | + </n-checkbox> |
| 145 | + <n-checkbox v-model:checked="clearTextMetadata"> |
| 146 | + Prevent encryption of metadata |
| 147 | + </n-checkbox> |
| 148 | + </n-space> |
| 149 | + <c-select |
| 150 | + v-model:value="restrictModify" |
| 151 | + :options="modificationRestrictionOptions" |
| 152 | + label="Control modify access by level" |
| 153 | + mt-3 |
| 154 | + /> |
| 155 | + <c-select |
| 156 | + v-model:value="restrictPrint" |
| 157 | + :options="printRestrictionOptions" |
| 158 | + label="Control printing access" |
| 159 | + mt-3 |
| 160 | + /> |
| 161 | + </c-card> |
| 162 | + <n-form-item |
| 163 | + label="Owner password:" |
| 164 | + label-placement="left" |
| 165 | + mb-1 |
| 166 | + > |
| 167 | + <n-input |
| 168 | + :value="ownerPassword" |
| 169 | + type="password" |
| 170 | + placeholder="Owner password (optional)" |
| 171 | + /> |
| 172 | + </n-form-item> |
| 173 | + |
| 174 | + <n-form-item |
| 175 | + label="User password:" |
| 176 | + label-placement="left" |
| 177 | + mb-1 |
| 178 | + > |
| 179 | + <n-input |
| 180 | + :value="userPassword" |
| 181 | + type="password" |
| 182 | + placeholder="User password (optional)" |
| 183 | + /> |
| 184 | + </n-form-item> |
| 185 | + |
| 186 | + <div mt-3 flex justify-center> |
| 187 | + <c-button :disabled="!file" @click="onProcessClicked()"> |
| 188 | + Encrypt PDF |
| 189 | + </c-button> |
| 190 | + </div> |
| 191 | + |
| 192 | + <n-divider /> |
| 193 | + |
| 194 | + <div mt-3 flex justify-center> |
| 195 | + <c-alert v-if="status === 'error'" type="error"> |
| 196 | + An error occured processing {{ fileName }} |
| 197 | + </c-alert> |
| 198 | + <n-spin |
| 199 | + v-if="status === 'processing'" |
| 200 | + size="small" |
| 201 | + /> |
| 202 | + </div> |
| 203 | + |
| 204 | + <c-card title="Logs"> |
| 205 | + <pre>{{ logs.join('\n') }}</pre> |
| 206 | + </c-card> |
| 207 | + </div> |
| 208 | +</template> |
0 commit comments