diff --git a/client-tauri/index.html b/client-tauri/index.html index 2d851d5b9..92c0a3c96 100644 --- a/client-tauri/index.html +++ b/client-tauri/index.html @@ -5,8 +5,6 @@ Tauri + React + TS - - diff --git a/client-tauri/package.json b/client-tauri/package.json index 869ab4021..eb95ccaa3 100644 --- a/client-tauri/package.json +++ b/client-tauri/package.json @@ -25,6 +25,7 @@ "react-icons": "^4.11.0", "react-router-bootstrap": "^0.26.2", "react-router-dom": "^6.18.0", + "vite-plugin-node-polyfills": "^0.21.0", "vite-plugin-top-level-await": "^1.3.1" }, "devDependencies": { diff --git a/client-tauri/src/App.tsx b/client-tauri/src/App.tsx index cbab0b925..28d489cd9 100644 --- a/client-tauri/src/App.tsx +++ b/client-tauri/src/App.tsx @@ -21,7 +21,7 @@ import resourcesToBackend from "i18next-resources-to-backend"; i18next.use(LanguageDetector).use(initReactI18next).use(resourcesToBackend((language: string, namespace: string) => import(`../../shared-operations/public/locales/${namespace}/${language}.json`))) .init({ - debug: true, + debug: false, ns: ["common"], // Preload this namespace, no need to add the others, they will load once their module is loaded defaultNS: "common", fallbackLng: "en", diff --git a/client-tauri/src/pages/Dynamic.tsx b/client-tauri/src/pages/Dynamic.tsx index 2ca5d19e2..81abcdd66 100644 --- a/client-tauri/src/pages/Dynamic.tsx +++ b/client-tauri/src/pages/Dynamic.tsx @@ -38,24 +38,15 @@ function Dynamic() { }); } - function formDataToObject(formData: FormData): Record { - const result: Record = {}; - - formData.forEach((value, key) => { - result[key] = value.toString(); - }); - - return result; - } - async function handleSubmit(e: BaseSyntheticEvent) { + console.clear(); if(!activeOperator.current) { throw new Error("Please select an Operator in the Dropdown"); } const formData = new FormData(e.target); - - const action: Action = {type: activeOperator.current.constructor.name, values: formDataToObject(formData)}; + const values = Object.fromEntries(formData.entries()); + let action: Action = {type: activeOperator.current.type, values: values}; // Validate PDF File @@ -68,7 +59,6 @@ function Dynamic() { for (let i = 0; i < files.length; i++) { const file = filesArray[i]; if(file) { - console.log(new Uint8Array(await file.arrayBuffer())); inputs.push(new PdfFile( file.name.replace(/\.[^/.]+$/, ""), // Strip Extension new Uint8Array(await file.arrayBuffer()), @@ -80,26 +70,25 @@ function Dynamic() { } } - const pdfValidationResults = await JoiPDFFileSchema.validate(inputs); - if(pdfValidationResults.error) { - console.log({error: "PDF validation failed", details: pdfValidationResults.error.message}); - } - const pdfFiles: PdfFile[] = pdfValidationResults.value; + const validationResults = activeOperator.current.schema.validate({input: inputs, values: action.values}); - // Validate Action Values - const actionValidationResults = activeOperator.current.schema.validate({input: pdfFiles, values: action.values}); - - if(actionValidationResults.error) { - console.log({error: "Value validation failed", details: actionValidationResults.error.message}); - return; + if(validationResults.error) { + console.log({error: "Validation failed", details: validationResults.error.message}); + } + else { + action.values = validationResults.value.values; + const operation = new activeOperator.current(action); + operation.run(validationResults.value.input, (progress) => {}).then(async pdfFiles => { + console.log("Done"); + console.log(pdfFiles); + + for await (const pdfFile of (pdfFiles as PdfFile[])) { + var blob = new Blob([await pdfFile.uint8Array], {type: "application/pdf"}); + var objectUrl = URL.createObjectURL(blob); + window.open(objectUrl); + } + }); } - - action.values = pdfValidationResults.value.values; - const operation = new activeOperator.current(action); - - operation.run(pdfValidationResults.value, (progress) => {}).then(pdfFiles => { - console.log("Done"); - }); }; function capitalizeFirstLetter(string: String) { diff --git a/client-tauri/vite.config.ts b/client-tauri/vite.config.ts index c37ca4b1f..78dfb6118 100644 --- a/client-tauri/vite.config.ts +++ b/client-tauri/vite.config.ts @@ -1,14 +1,27 @@ import { defineConfig } from "vite"; +import { nodePolyfills } from 'vite-plugin-node-polyfills' import react from "@vitejs/plugin-react"; import topLevelAwait from "vite-plugin-top-level-await"; -import dynamicImport from 'vite-plugin-dynamic-import' -import compileTime from "vite-plugin-compile-time" -import { fileURLToPath, URL } from 'node:url' +import dynamicImport from 'vite-plugin-dynamic-import'; +import compileTime from "vite-plugin-compile-time"; +import { fileURLToPath, URL } from 'node:url'; // https://vitejs.dev/config/ export default defineConfig(async () => ({ plugins: [ + // Thanks: https://stackoverflow.com/questions/74417822/how-can-i-use-buffer-process-in-vite-app + nodePolyfills({ + include: [], + + globals: { + Buffer: true, // can also be 'build', 'dev', or false + global: false, + process: true, + }, + // Whether to polyfill `node:` protocol imports. + protocolImports: false, + }), react(), topLevelAwait({ // The export name of top-level await promise for each chunk module diff --git a/package-lock.json b/package-lock.json index 11699920d..2b3094c37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "react-icons": "^4.11.0", "react-router-bootstrap": "^0.26.2", "react-router-dom": "^6.18.0", + "vite-plugin-node-polyfills": "^0.21.0", "vite-plugin-top-level-await": "^1.3.1" }, "devDependencies": { @@ -75,6 +76,21 @@ } } }, + "client-tauri/node_modules/vite-plugin-node-polyfills": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.21.0.tgz", + "integrity": "sha512-Sk4DiKnmxN8E0vhgEhzLudfJQfaT8k4/gJ25xvUPG54KjLJ6HAmDKbr4rzDD/QWEY+Lwg80KE85fGYBQihEPQA==", + "dependencies": { + "@rollup/plugin-inject": "^5.0.5", + "node-stdlib-browser": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/davidmyersdev" + }, + "peerDependencies": { + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -1157,6 +1173,57 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz", + "integrity": "sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg==", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.1.3.tgz", + "integrity": "sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -5617,6 +5684,14 @@ "node": ">= 6" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "engines": { + "node": ">=10.18" + } + }, "node_modules/i18next": { "version": "23.7.16", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.16.tgz", @@ -8317,6 +8392,24 @@ "node": ">=8" } }, + "node_modules/sonic-forest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sonic-forest/-/sonic-forest-1.0.3.tgz", + "integrity": "sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ==", + "dependencies": { + "tree-dump": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8573,6 +8666,17 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -8672,6 +8776,21 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "optional": true }, + "node_modules/tree-dump": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.1.tgz", + "integrity": "sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -10525,12 +10644,55 @@ "license": "ISC", "dependencies": { "@stirling-tools/joi": "github:Stirling-Tools/joi", + "buffer": "^6.0.3", "i18next-resources-to-backend": "^1.2.0", "image-js": "^0.35.5", + "memfs": "^4.9.2", "next-i18next": "^15.1.1", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.0.269" } + }, + "shared-operations/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "shared-operations/node_modules/memfs": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.9.2.tgz", + "integrity": "sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.1.2", + "sonic-forest": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } } } } diff --git a/shared-operations/package.json b/shared-operations/package.json index c568d1343..7dce373a6 100644 --- a/shared-operations/package.json +++ b/shared-operations/package.json @@ -10,8 +10,10 @@ "license": "ISC", "dependencies": { "@stirling-tools/joi": "github:Stirling-Tools/joi", + "buffer": "^6.0.3", "i18next-resources-to-backend": "^1.2.0", "image-js": "^0.35.5", + "memfs": "^4.9.2", "next-i18next": "^15.1.1", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.0.269" diff --git a/shared-operations/public/wasm/pdfcpu/pdfcpu.wasm b/shared-operations/public/wasm/pdfcpu/pdfcpu.wasm index fc7855d3f..b860b423f 100644 Binary files a/shared-operations/public/wasm/pdfcpu/pdfcpu.wasm and b/shared-operations/public/wasm/pdfcpu/pdfcpu.wasm differ diff --git a/shared-operations/src/functions/impose.ts b/shared-operations/src/functions/impose.ts index 8b715dafb..0b3f7a885 100644 --- a/shared-operations/src/functions/impose.ts +++ b/shared-operations/src/functions/impose.ts @@ -87,12 +87,10 @@ export class Impose extends Operator { [ "pdfcpu.wasm", "nup", - "-c", - "disable", - "f:" + this.actionValues.format, - "/output.pdf", - String(this.actionValues.nup), - "input.pdf", + "formsize:" + this.actionValues.format, // configuration string + "/output.pdf", // outFile + String(this.actionValues.nup), // nupvalue + "/input.pdf" // infile ], await input.uint8Array ); diff --git a/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js b/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js index 104eb58d5..da9bf4478 100644 --- a/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js +++ b/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js @@ -1,89 +1,32 @@ -// imports browserfs via index.html script-tag -import wasmUrl from '../../../public/wasm/pdfcpu/pdfcpu.wasm?url' +import "./wasm_exec_memfs.js"; -let wasmLocation = "/wasm/pdfcpu/"; - -let fs; -let Buffer; - -// TODO: This can later be defered to load asynchronously -configureFs(); -loadWasm(); - -function configureFs() { - BrowserFS.configure( - { - fs: "InMemory", - }, - function (e) { - if (e) { - // An error happened! - throw e; - } - fs = BrowserFS.BFSRequire("fs"); - Buffer = BrowserFS.BFSRequire("buffer").Buffer; - - window.fs = fs; - window.Buffer = Buffer; - } - ); -} - -function loadWasm() { - import("./wasm_exec.js"); -} - -const runWasm = async (param) => { - if (window.cachedWasmResponse === undefined) { - const response = await fetch(wasmUrl); - const buffer = await response.arrayBuffer(); - window.cachedWasmResponse = buffer; - window.go = new Go(); - } - const { instance } = await WebAssembly.instantiate( - window.cachedWasmResponse, - window.go.importObject - ); - window.go.argv = param; - await window.go.run(instance); - return window.go.exitCode; -}; - -async function loadFileAsync(data) { - console.log(`Writing file to MemoryFS`); - await fs.writeFile(`/input.pdf`, data); - console.log(`Write done. Validating...`); - let exitcode = await runWasm([ - "pdfcpu.wasm", - "validate", - "-c", - "disable", - `/input.pdf`, - ]); - - console.log("Exit Code: " + exitcode); - if (exitcode !== 0) - throw new Error("There was an error validating your PDFs"); - - console.log(`File is Valid`); -} +import wasmUrl from '../../../public/wasm/pdfcpu/pdfcpu.wasm?url'; export async function oneToOne(wasmArray, snapshot) { - await loadFileAsync(Buffer.from(snapshot)); - - console.log("Nuping File"); - let exitcode = await runWasm(wasmArray); - - if (exitcode !== 0) { - console.error("There was an error nuping your PDFs"); - return; + if (!WebAssembly.instantiateStreaming) { // polyfill + WebAssembly.instantiateStreaming = async (resp, importObject) => { + const source = await (await resp).arrayBuffer(); + return await WebAssembly.instantiate(source, importObject); + }; } - await fs.unlink("input.pdf"); - const contents = fs.readFileSync("output.pdf"); - fs.unlink("output.pdf"); - console.log("Your File ist Ready!"); - return new Uint8Array(contents); + const go = new Go(); + go.argv = wasmArray; + + const webAssemblyInstantiatedSource = await WebAssembly.instantiateStreaming(fetch(wasmUrl), go.importObject); + let inst = webAssemblyInstantiatedSource.instance + + await globalThis.fs.promises.writeFile("/input.pdf", Buffer.from(snapshot)); + + await go.run(inst); + inst = await WebAssembly.instantiate(webAssemblyInstantiatedSource.module, go.importObject); // reset instance + + globalThis.fs.promises.unlink("/input.pdf"); + const result = await globalThis.fs.promises.readFile("/output.pdf"); + + globalThis.fs.promises.unlink("/output.pdf"); + + return result; } export async function manyToOne() { diff --git a/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server.js b/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server.js index e37414612..1ae8e4d9a 100644 --- a/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server.js +++ b/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server.js @@ -1,112 +1,32 @@ -import { WasmFs } from '@wasmer/wasmfs'; +import "./wasm_exec_memfs.js"; +import fs from "node:fs"; + import path from "path"; import { fileURLToPath } from 'url'; -let nodeWasmLocation = "./dist/public/wasm/pdfcpu/"; // TODO: Replace with __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); -let fs; -const wasmfs = new WasmFs(); - -// TODO: This can later be defered to load asynchronously -(async () => { - configureFs(); - await loadWasm(); -})(); - -function configureFs() { - // Can't use BrowserFS: https://github.com/jvilk/BrowserFS/issues/271 - fs = wasmfs.fs; - global.fs = fs; - - console.log("InMemoryFs configured"); -} - -async function loadWasm() { - // global.crypto = (await import("crypto")).webcrypto; // wasm dependecy - await import("./wasm_exec.js"); -} - -const runWasm = async (param) => { - if (global.cachedWasmResponse === undefined) { - const buffer = (await import("fs")).readFileSync(nodeWasmLocation + "/pdfcpu.wasm"); - global.cachedWasmResponse = buffer; - global.go = new Go(); - } - const { instance } = await WebAssembly.instantiate( - global.cachedWasmResponse, - global.go.importObject - ); - global.go.argv = param; - await global.go.run(instance); - return global.go.exitCode; -}; - -async function loadFileAsync(data) { - console.log(`Writing file to Disk`); - if(fs === undefined) { - throw new Error("FS hasn't loaded, this should never happen.") - } - fs.writeFileSync(`input.pdf`, data); - console.log(`Write done. Validating...`); - let exitcode = await runWasm([ - "pdfcpu.wasm", - "validate", - "-c", - "disable", - `input.pdf`, - ]); - if (exitcode !== 0) - throw new Error("There was an error validating your PDFs"); - - // // Get logs of command - // wasmfs.getStdOut().then(response => { - // console.log(response); - // }); - - console.log(`File is Valid`); -} +const nodeWasmLocation = path.join(__dirname, "../../../public/wasm/pdfcpu/", "pdfcpu.wasm"); export async function oneToOne(wasmArray, snapshot) { - await loadFileAsync(Buffer.from(snapshot)); + const go = new Go(); + go.argv = wasmArray; - console.log("Nuping File"); + const wasmFile = fs.readFileSync(nodeWasmLocation); + const webAssemblyInstantiatedSource = await WebAssembly.instantiate(wasmFile, go.importObject); - let exitcode = await runWasm(wasmArray); - if (exitcode !== 0) { - console.error("There was an error nuping your PDFs"); - return; - } - console.log("Nuping Done"); + await globalThis.fs.promises.writeFile("/input.pdf", Buffer.from(snapshot)); + + await go.run(webAssemblyInstantiatedSource.instance); + + globalThis.fs.promises.unlink("/input.pdf"); + + const pdfcpu_result = await globalThis.fs.promises.readFile("/output.pdf"); + + globalThis.fs.promises.unlink("/output.pdf"); - /* TODO: - * Make this more elegant, this waits for the write to finish. - * Maybe replace wasmfs with https://github.com/streamich/memfs - */ - await checkExistsWithTimeout("/output.pdf", 1000); - console.log("Write started..."); - let fileSize; - while (true) { - fileSize = fs.statSync("/output.pdf").size; - await new Promise((resolve, reject) => { - setTimeout(() => { - resolve(); - }, 50); - }); - if(fileSize > 0 && fileSize == fs.statSync("/output.pdf").size) // Wait for file Size not changing anymore. - break; - } - - console.log("Could be done?"); - - fs.unlinkSync("input.pdf"); - - const data = fs.readFileSync("/output.pdf"); - if(data.length == 0) { - throw Error("File Size 0 that should not happen. The write probably didn't finish in time."); - } - fs.unlinkSync("output.pdf"); - console.log("Your File ist Ready!"); - return new Uint8Array(data); + return new Uint8Array(pdfcpu_result); } export async function manyToOne() { @@ -119,30 +39,4 @@ export async function oneToMany() { export async function manyToMany() { //TODO: Do this if necessary for some pdfcpu operations -} - -// THX: https://stackoverflow.com/questions/26165725/nodejs-check-file-exists-if-not-wait-till-it-exist -function checkExistsWithTimeout(filePath, timeout) { - return new Promise(function (resolve, reject) { - - var timer = setTimeout(function () { - watcher.close(); - reject(new Error('File did not exists and was not created during the timeout.')); - }, timeout); - - fs.access(filePath, fs.constants.R_OK, function (err) { - if (!err) { - clearTimeout(timer); - watcher.close(); - resolve(); - } - }); - - var dir = path.dirname(filePath); - var watcher = fs.watch(dir, function (eventType, filename) { - clearTimeout(timer); - watcher.close(); - resolve(); - }); - }); } \ No newline at end of file diff --git a/shared-operations/src/wasm/pdfcpu/wasm_exec.js b/shared-operations/src/wasm/pdfcpu/wasm_exec.js index 92a7bdaf3..15ee8320f 100644 --- a/shared-operations/src/wasm/pdfcpu/wasm_exec.js +++ b/shared-operations/src/wasm/pdfcpu/wasm_exec.js @@ -1,92 +1,27 @@ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. + +"use strict"; + (() => { - // Map multiple JavaScript environments to a single common API, - // preferring web standards over Node.js API. - // - // Environments considered: - // - Browsers - // - Node.js - // - Electron - // - Parcel - // - Webpack - - console.log("pdfcpu wasm_exec imported") - if (typeof global !== "undefined") { - // global already exists - } else if (typeof window !== "undefined") { - window.global = window; - } else if (typeof self !== "undefined") { - self.global = self; - } else { - throw new Error("cannot export Go (neither global, window nor self is defined)"); - } - - let logFS = false - var handler = { - get: function (target, property) { - if (property in target && target[property] instanceof Function) { - return function () { - if (logFS) { - console.log(property, 'called', arguments); - } - // 将callback替换 - if (arguments[arguments.length - 1] instanceof Function) { - var origCB = arguments[arguments.length - 1]; - var newCB = function () { - if (logFS) { - console.log('callback for', property, 'get called with args:', arguments); - } - return Reflect.apply(origCB, arguments.callee, arguments); - } - arguments[arguments.length - 1] = newCB; - } - return Reflect.apply(target[property], target, arguments); - } - } else { - return target[property] - } - } - } - - if (!global.require && typeof require !== "undefined") { - global.require = require; - } - - - if (!global.fs && global.require) { - - //const fs = require("fs"); - if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { - global.fs = fs; - } - - } - const enosys = () => { - const err = new Error("not implemented"); + const err = new Error("enosys not implemented"); + console.error(err.stack); err.code = "ENOSYS"; return err; }; - if (!global.fs) { + if (!globalThis.fs) { let outputBuf = ""; - global.fs = { - constants: { - O_WRONLY: -1, - O_RDWR: -1, - O_CREAT: -1, - O_TRUNC: -1, - O_APPEND: -1, - O_EXCL: -1 - }, // unused + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); + console.log(outputBuf.substring(0, nl)); + outputBuf = outputBuf.substring(nl + 1); } return buf.length; }, @@ -98,296 +33,71 @@ const n = this.writeSync(fd, buf); callback(null, n); }, - chmod(path, mode, callback) { - callback(enosys()); - }, - chown(path, uid, gid, callback) { - callback(enosys()); - }, - close(fd, callback) { - callback(enosys()); - }, - fchmod(fd, mode, callback) { - callback(enosys()); - }, - fchown(fd, uid, gid, callback) { - callback(enosys()); - }, - fstat(fd, callback) { - callback(enosys()); - }, - fsync(fd, callback) { - callback(null); - }, - ftruncate(fd, length, callback) { - callback(enosys()); - }, - lchown(path, uid, gid, callback) { - callback(enosys()); - }, - link(path, link, callback) { - callback(enosys()); - }, - lstat(path, callback) { - callback(enosys()); - }, - mkdir(path, perm, callback) { - callback(enosys()); - }, - open(path, flags, mode, callback) { - callback(enosys()); - }, - read(fd, buffer, offset, length, position, callback) { - callback(enosys()); - }, - readdir(path, callback) { - callback(enosys()); - }, - readlink(path, callback) { - callback(enosys()); - }, - rename(from, to, callback) { - callback(enosys()); - }, - rmdir(path, callback) { - callback(enosys()); - }, - stat(path, callback) { - callback(enosys()); - }, - symlink(path, link, callback) { - callback(enosys()); - }, - truncate(path, length, callback) { - callback(enosys()); - }, - unlink(path, callback) { - callback(enosys()); - }, - utimes(path, atime, mtime, callback) { - callback(enosys()); - }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) {callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, }; } - if (!global.process) { - global.process = { - getuid() { - return -1; - }, - getgid() { - return -1; - }, - geteuid() { - return -1; - }, - getegid() { - return -1; - }, - getgroups() { - throw enosys(); - }, + if (!globalThis.process) { + globalThis.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, pid: -1, ppid: -1, - umask() { - throw enosys(); - }, - cwd() { - throw enosys(); - }, - chdir() { - throw enosys(); - }, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, } } - // if (!global.crypto && global.require) { - // const nodeCrypto = require("crypto"); - // global.crypto = { - // getRandomValues(b) { - // nodeCrypto.randomFillSync(b); - // }, - // }; - // } - if (!global.crypto) { - throw new Error("global.crypto is not available, polyfill required (getRandomValues only)"); - } - if (!global.crypto.getRandomValues) { - throw new Error("global.crypto.getRandomValues is not available, polyfill required (getRandomValues only)"); + if (!globalThis.crypto) { + throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); } - if (!global.performance) { - global.performance = { - now() { - const [sec, nsec] = process.hrtime(); - return sec * 1000 + nsec / 1000000; - }, - }; + if (!globalThis.performance) { + throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); } - if (!global.TextEncoder && global.require) { - global.TextEncoder = require("util").TextEncoder; - } - if (!global.TextEncoder) { - throw new Error("global.TextEncoder is not available, polyfill required"); + if (!globalThis.TextEncoder) { + throw new Error("globalThis.TextEncoder is not available, polyfill required"); } - if (!global.TextDecoder && global.require) { - global.TextDecoder = require("util").TextDecoder; + if (!globalThis.TextDecoder) { + throw new Error("globalThis.TextDecoder is not available, polyfill required"); } - if (!global.TextDecoder) { - throw new Error("global.TextDecoder is not available, polyfill required"); - } - - - const isNodeJS = global.process && global.process.title === "node"; - - if (!isNodeJS) { - // console.log("ini browser fs") - // var myfs = global.BrowserFS.BFSRequire('fs'); - // global.Buffer = global.BrowserFS.BFSRequire('buffer').Buffer; - // global.fs = myfs; - - global.fs.constants = { - O_RDONLY: 0, - O_WRONLY: 1, - O_RDWR: 2, - O_CREAT: 64, - O_CREATE: 64, - O_EXCL: 128, - O_NOCTTY: 256, - O_TRUNC: 512, - O_APPEND: 1024, - O_DIRECTORY: 65536, - O_NOATIME: 262144, - O_NOFOLLOW: 131072, - O_SYNC: 1052672, - O_DIRECT: 16384, - O_NONBLOCK: 2048, - }; - - let outputBuf = ""; - - global.fs.writeSyncOriginal = global.fs.writeSync - global.fs.writeSync = function (fd, buf) { - if (fd === 1 || fd === 2) { - outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); - } - return buf.length; - } else { - return global.fs.writeSyncOriginal(...arguments); - } - }; - - global.fs.writeOriginal = global.fs.write - global.fs.write = function (fd, buf, offset, length, position, callback) { - // (corresponding to STDOUT/STDERR) - if (fd === 1 || fd === 2) { - if (offset !== 0 || length !== buf.length || position !== null) { - throw new Error("not implemented"); - } - const n = this.writeSync(fd, buf); - callback(null, n, buf); - } else { - // buf: read buf first - arguments[1] = global.Buffer.from(arguments[1]); - return global.fs.writeOriginal(...arguments); - } - }; - - - - global.fs.openOriginal = global.fs.open - global.fs.open = function (path, flags, mode, callback) { - var myflags = 'r'; - var O = global.fs.constants; - - // Convert numeric flags to string flags - // FIXME: maybe wrong... - console.log("open dir?", path, 'flag', flags, myflags) - if (flags & O.O_WRONLY) { // 'w' - myflags = 'w'; - if (flags & O.O_EXCL) { - myflags = 'wx'; - } - } else if (flags & O.O_RDWR) { // 'r+' or 'w+' - if (flags & O.O_CREAT && flags & O.O_TRUNC) { // w+ - if (flags & O.O_EXCL) { - myflags = 'wx+'; - } else { - myflags = 'w+'; - } - } else { // r+ - myflags = 'r+'; - } - } else if (flags & O.O_APPEND) { // 'a' - console.log("append error") - throw new Error("Not implmented"); - } else { - // 打开文件 - myflags = 'r+'; - console.log("open dir?", path, 'flag', flags, myflags) - } - - - return global.fs.openOriginal(path, myflags, mode, callback); - }; - - global.fs.fstatOriginal = global.fs.fstat; - global.fs.fstat = function (fd, callback) { - return global.fs.fstatOriginal(fd, function () { - var retStat = arguments[1]; - delete retStat['fileData']; - retStat.atimeMs = retStat.atime.getTime(); - retStat.mtimeMs = retStat.mtime.getTime(); - retStat.ctimeMs = retStat.ctime.getTime(); - retStat.birthtimeMs = retStat.birthtime.getTime(); - return callback(arguments[0], retStat); - - }); - }; - - - - global.fs.closeOriginal = global.fs.close; - global.fs.close = function (fd, callback) { - return global.fs.closeOriginal(fd, function () { - if (typeof arguments[0] === 'undefined') arguments[0] = null; - return callback(...arguments); - }); - } - - // global.fs.renameOriginal = global.fs.rename - // global.fs.rename = function (from, to, callback) { - // console.log("rename a0", arguments[0]) - // global.fs.renameOriginal(from, to); - // callback(arguments[0]) - // } - - // global.fs.renameSyncOriginal = global.fs.renameSync - // global.fs.renameSync = function(fd, options) { - // console.log("Sync") - // } - - - global.fs = new Proxy(global.fs, handler); - } - - // End of polyfills for common API. const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); - global.Go = class { + globalThis.Go = class { constructor() { this.argv = ["js"]; this.env = {}; this.exit = (code) => { - this.exitCode = code; if (code !== 0) { console.warn("exit code:", code); } @@ -404,6 +114,10 @@ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); } + const setInt32 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + } + const getInt64 = (addr) => { const low = this.mem.getUint32(addr + 0, true); const high = this.mem.getInt32(addr + 4, true); @@ -497,7 +211,10 @@ const timeOrigin = Date.now() - performance.now(); this.importObject = { - go: { + _gotest: { + add: (a, b) => a + b, + }, + gojs: { // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). @@ -537,8 +254,8 @@ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); }, - // func walltime1() (sec int64, nsec int32) - "runtime.walltime1": (sp) => { + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { sp >>>= 0; const msec = (new Date).getTime(); setInt64(sp + 8, msec / 1000); @@ -560,7 +277,7 @@ this._resume(); } }, - getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + getInt64(sp + 8), )); this.mem.setInt32(sp + 16, id, true); }, @@ -642,6 +359,7 @@ storeValue(sp + 56, result); this.mem.setUint8(sp + 64, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 56, err); this.mem.setUint8(sp + 64, 0); } @@ -658,6 +376,7 @@ storeValue(sp + 40, result); this.mem.setUint8(sp + 48, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, err); this.mem.setUint8(sp + 48, 0); } @@ -674,6 +393,7 @@ storeValue(sp + 40, result); this.mem.setUint8(sp + 48, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, err); this.mem.setUint8(sp + 48, 0); } @@ -755,7 +475,7 @@ null, true, false, - global, + globalThis, this, ]; this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id @@ -764,10 +484,10 @@ [null, 2], [true, 3], [false, 4], - [global, 5], + [globalThis, 5], [this, 6], ]); - this._idPool = []; // unused ids that have been garbage collected + this._idPool = []; // unused ids that have been garbage collected this.exited = false; // whether the Go program has exited // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. @@ -805,6 +525,13 @@ offset += 8; }); + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + this._inst.exports.run(argc, argv); if (this.exited) { this._resolveExitPromise(); @@ -814,7 +541,7 @@ _resume() { if (this.exited) { - throw new Error("Go program has already exited"); + console.warn("Go program has already exited"); } this._inst.exports.resume(); if (this.exited) { @@ -825,51 +552,11 @@ _makeFuncWrapper(id) { const go = this; return function () { - const event = { - id: id, - this: this, - args: arguments - }; + const event = { id: id, this: this, args: arguments }; go._pendingEvent = event; go._resume(); return event.result; }; } } - - if ( - typeof module !== "undefined" && - global.require && - global.require.main === module && - global.process && - global.process.versions && - !global.process.versions.electron - ) { - if (process.argv.length < 3) { - console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); - process.exit(1); - } - - const go = new Go(); - go.argv = process.argv.slice(2); - go.env = Object.assign({ - TMPDIR: require("os").tmpdir() - }, process.env); - go.exit = process.exit; - WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { - process.on("exit", (code) => { // Node.js exits if no event handler is pending - if (code === 0 && !go.exited) { - // deadlock, make Go print error and stack traces - go._pendingEvent = { - id: 0 - }; - go._resume(); - } - }); - return go.run(result.instance); - }).catch((err) => { - console.error(err); - process.exit(1); - }); - } -})(); \ No newline at end of file +})(); diff --git a/shared-operations/src/wasm/pdfcpu/wasm_exec_memfs.js b/shared-operations/src/wasm/pdfcpu/wasm_exec_memfs.js new file mode 100644 index 000000000..cee161194 --- /dev/null +++ b/shared-operations/src/wasm/pdfcpu/wasm_exec_memfs.js @@ -0,0 +1,37 @@ +import memfs from 'memfs'; + +globalThis.fs = memfs.fs; + +import "./wasm_exec.js"; + +const encoder = new TextEncoder("utf-8"); +const decoder = new TextDecoder("utf-8"); +let outputBuf = ""; + +globalThis.fs.writeSyncOriginal = globalThis.fs.writeSync; +globalThis.fs.writeSync = function(fd, buf) { + if (fd === 1 || fd === 2) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + } else { + return globalThis.fs.writeSyncOriginal(...arguments); + } +}; + +globalThis.fs.writeOriginal = globalThis.fs.write; +globalThis.fs.write = function(fd, buf, offset, length, position, callback) { + if (fd === 1 || fd === 2) { + if (offset !== 0 || length !== buf.length || position !== null) { + throw new Error("fs func not implemented"); + } + const n = this.writeSync(fd, buf); + callback(null, n, buf); + } else { + return globalThis.fs.writeOriginal(...arguments); + } +}; \ No newline at end of file