diff --git a/web/src/cover-generator.ts b/web/src/cover-generator.ts new file mode 100644 index 0000000..4955680 --- /dev/null +++ b/web/src/cover-generator.ts @@ -0,0 +1,82 @@ +import { showToast } from "./toast"; + +function getToken(): string { + return localStorage.getItem("token") || ""; +} + +export async function generateCoverBlob( + fileUrl: string, + formatGroup: string, +): Promise { + try { + const View = (await import("foliate-js/view.js")).default; + const view = new View(); + + const resp = await fetch(fileUrl, { + headers: { Authorization: `Bearer ${getToken()}` }, + }); + if (!resp.ok) { + showToast("Failed to fetch book file for cover generation", "error"); + return null; + } + + const blob = await resp.blob(); + const file = new File([blob], "book", { type: blob.type }); + + const pdfOptions = + formatGroup === "fixed_layout" + ? { + pdf: { + cMapUrl: "/static/vendor/pdfjs/cmaps/", + standardFontDataUrl: "/static/vendor/pdfjs/standard_fonts/", + }, + } + : {}; + + await view.open(file, pdfOptions); + + if (!view.book?.sections?.length) { + showToast("Could not read book sections", "error"); + return null; + } + + if (formatGroup === "fixed_layout") { + const canvas = document.createElement("canvas"); + await view.renderer?.renderPage(view.book.sections[0], canvas); + return new Promise((resolve) => { + canvas.toBlob( + (b) => resolve(b), + "image/jpeg", + 0.85, + ); + }); + } + + const coverHref = view.book.cover; + if (coverHref) { + const coverBlob = await coverHref.blob(); + if (coverBlob.type.startsWith("image/")) { + return coverBlob; + } + return new Promise((resolve) => { + const img = new Image(); + img.onload = () => { + const c = document.createElement("canvas"); + c.width = img.naturalWidth; + c.height = img.naturalHeight; + c.getContext("2d")?.drawImage(img, 0, 0); + c.toBlob((b) => resolve(b), "image/jpeg", 0.85); + }; + img.onerror = () => resolve(null); + img.src = URL.createObjectURL(coverBlob); + }); + } + + showToast("No cover found in book file", "error"); + return null; + } catch (e) { + console.error("Cover generation failed:", e); + showToast("Cover generation failed", "error"); + return null; + } +}