diff --git a/package.json b/package.json index 03411e81..2dc6835c 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@types/jszip": "^3.4.0", "@types/less": "^3.0.8", "@types/opentype.js": "^1.3.9", + "@types/pdfkit": "^0.17.6", "electron": "^40.6.0", "electron-builder": "^26.8.1", "puppeteer": "^24.36.0", @@ -54,7 +55,6 @@ "vite-tsconfig-paths": "^6.0.5" }, "dependencies": { - "7z-wasm": "^1.2.0", "@bjorn3/browser_wasi_shim": "^0.4.2", "@bokuweb/zstd-wasm": "^0.0.27", "@ffmpeg/core": "^0.12.10", @@ -73,6 +73,7 @@ "@types/pako": "^2.0.4", "@types/papaparse": "^5.5.2", "@types/three": "^0.182.0", + "7z-wasm": "^1.2.0", "bson": "^7.2.0", "celaria-formats": "^1.0.2", "chess.js": "^1.4.0", @@ -90,6 +91,7 @@ "pako": "^2.1.0", "papaparse": "^5.5.3", "pdf-parse": "^2.4.5", + "pdfkit": "^0.18.0", "pdftoimg-js": "^0.2.5", "pe-library": "^2.0.1", "sass": "^1.98.0", diff --git a/src/handlers/index.ts b/src/handlers/index.ts index 0c6fae4b..908d38d9 100644 --- a/src/handlers/index.ts +++ b/src/handlers/index.ts @@ -71,6 +71,7 @@ import piskelHandler from "./piskel.ts"; import xcursorHandler from "./xcursor.ts"; import shToElfHandler from "./shToElf.ts"; import cssHandler from "./css.ts"; +import textToPdfHandler from "./text-to-pdf.ts"; import TypstHandler from "./typst.ts"; const handlers: FormatHandler[] = []; @@ -148,6 +149,7 @@ try { handlers.push(new fenToJsonHandler()) } catch (_) { }; try { handlers.push(new piskelHandler()) } catch (_) { }; try { handlers.push(new xcursorHandler()) } catch (_) { }; try { handlers.push(new shToElfHandler()) } catch (_) { }; +try { handlers.push(new textToPdfHandler()) } catch (_) { }; try { handlers.push(new cssHandler()) } catch (_) { }; try { handlers.push(new TypstHandler()) } catch (_) { }; diff --git a/src/handlers/text-to-pdf.ts b/src/handlers/text-to-pdf.ts new file mode 100644 index 00000000..eeebfa69 --- /dev/null +++ b/src/handlers/text-to-pdf.ts @@ -0,0 +1,76 @@ +import { type FileData, type FileFormat, type FormatHandler } from "../FormatHandler.ts"; +import CommonFormats from "../CommonFormats.ts"; +import PDFDocument from "pdfkit/js/pdfkit.standalone"; + +class textToPdfHandler implements FormatHandler { + public name = "text-to-pdf"; + public supportedFormats?: FileFormat[] = [ + CommonFormats.TEXT.builder("text").allowFrom(true).allowTo(false), + CommonFormats.PDF.builder("pdf").allowFrom(false).allowTo(true).markLossless(), + ]; + public ready = false; + + async init() { + this.ready = true; + } + + async doConvert( + inputFiles: FileData[], + inputFormat: FileFormat, + outputFormat: FileFormat, + ): Promise { + const outputFiles: FileData[] = []; + + for (const file of inputFiles) { + const rawText = new TextDecoder().decode(file.bytes); + const hasLetters = /\p{L}/u.test(rawText); + const hasEmojis = /\p{Emoji}/u.test(rawText); + if (hasEmojis && !hasLetters) { + throw `Input file "${file.name}" does not contain any letters, only emojis.`; + } + let text = rawText; + if (hasEmojis) { + text = rawText.replace(/\p{Emoji}/gu, " "); // Remove emojis + } + + const doc = new PDFDocument({ + size: "A4", + margins: { top: 72, bottom: 72, left: 72, right: 72 }, + }); + + const pdfBytes = await new Promise((resolve, reject) => { + const chunks: Uint8Array[] = []; + + doc.on("data", (chunk: Uint8Array) => chunks.push(chunk)); + doc.on("end", async () => { + try { + const buffer = await new Blob(chunks as BlobPart[], { type: "application/pdf" }).arrayBuffer(); + resolve(new Uint8Array(buffer)); + } catch (error) { + reject(error); + } + }); + + doc.on("error", reject); + + doc.font("Courier").fontSize(11).fillColor("#000000"); + doc.text(text.replace(/\r\n|\r/g, '\n'), { + width: 595.28 - 72 - 72, + align: "left", + }); + + doc.end(); + }); + + const basename = file.name.split(".").slice(0, -1).join(".") || "document"; + outputFiles.push({ + name: `${basename}.${outputFormat.extension}`, + bytes: pdfBytes, + }); + } + + return outputFiles; + } +} + +export default textToPdfHandler; diff --git a/tsconfig.json b/tsconfig.json index 8e40c70e..c17b7df2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,9 @@ "DOM.Iterable" ], "types": [ - "vite/client" + "vite/client", + "node", + "@types/pdfkit" ], "skipLibCheck": true, /* Bundler mode */