Get the basic flow working
- ID
071da8b- date
2025-01-25 19:20:27+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
8294391- message
Get the basic flow working- changed files
2 files, 138 additions, 4 deletions
Changed files
index.html (3148 → 4900)
diff --git a/index.html b/index.html
index 8683a30..81dab6e 100644
--- a/index.html
+++ b/index.html
@@ -71,7 +71,7 @@
// Get the epub file from the file input.
const file = uploadForm.querySelector('input[type="file"]').files[0];
- console.debug(`Got file ${file}`);
+ console.debug(`Got file ${file.name}`);
// Find the `content
JSZip.loadAsync(file)
@@ -87,10 +87,53 @@
// Read the `content.opf` file, and get the key information
// about this fic.
- const contentOpfContent = await zip.file(contentOpfPath).async('string');
- const ficInfo = getFicInfo(contentOpfContent);
+ const contentOpf = await zip.file(contentOpfPath).async('string');
+ const ficInfo = getFicInfo(contentOpf);
console.debug(`Got fic info: ${JSON.stringify(ficInfo)}`);
+ // Create a cover image for this book which shows the title
+ // and author on a solid colour background.
+ //
+ // Then add this cover image to the zip file.
+ const canvas = createCoverImage(ficInfo);
+
+ const blob = await new Promise(
+ resolve => canvas.toBlob(resolve, 'image/png')
+ );
+
+ zip.file('media/cover.png', blob, { base64: true });
+
+ // Now modify the `content.opf` file to insert this image
+ // as the cover, and replace the file in the zip.
+ //
+ // This sort of text-based replacement is utterly cheating,
+ // but it also works.
+ const updatedContentOpf =
+ contentOpf
+ .replace(
+ "</manifest>",
+ '<item id="cover-image" properties="cover-image" href="media/cover.png" media-type="image/png"/></manifest>'
+ )
+ .replace(
+ "</metadata>",
+ '<meta name="cover" content="cover-image"/></metadata>'
+ );
+
+ zip.remove(contentOpfPath);
+ zip.file(contentOpfPath, updatedContentOpf);
+
+ // Create a download link for this zip file, and make
+ // the generated cover the link.
+ const zipContent = await zip.generateAsync({ type: 'blob' });
+
+ const downloadLink = document.createElement('a');
+ downloadLink.href = URL.createObjectURL(zipContent);
+ downloadLink.download = file.name;
+ downloadLink.appendChild(canvas);
+
+ document.querySelector('main').appendChild(downloadLink);
+
+
reportSuccess(`
<span style="color: ${chooseColour(ficInfo.fandom)}">
Found fic info: ${JSON.stringify(ficInfo)}`)
static/app.js (4489 → 6831)
diff --git a/static/app.js b/static/app.js
index b180dac..aa82ae6 100644
--- a/static/app.js
+++ b/static/app.js
@@ -168,6 +168,97 @@ function numToHex(n) {
+/**
+ * Create a PNG cover image for a book.
+ *
+ * Returns a <canvas> element for this cover.
+ */
+function createCoverImage(ficInfo) {
+ // I want something with a rough 2:3 ratio that's big enough
+ // for the text to look sharp, but we don't need anything huge.
+ const width = 600;
+ const height = 900;
+
+ const canvas = document.createElement("canvas");
+ canvas.setAttribute("width", 600);
+ canvas.setAttribute("height", 900);
+
+ const ctx = canvas.getContext("2d");
+ ctx.fillStyle = chooseColour(ficInfo.fandom);
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // Set text style. We know white will look okay because we
+ // chose a dark background.
+ ctx.fillStyle = 'white';
+ ctx.textAlign = 'center';
+
+ // Add the author name.
+ ctx.font = '60px Georgia';
+ ctx.fillText(ficInfo.author, width / 2, height * 0.85);
+
+ // Add the title.
+ //
+ // This may be split across multiple lines if it's long; we only
+ // show part of the title if it's long to keep the text readable.
+ //
+ // If there are too many lines, we truncate it and add ellipsis
+ // to indicate it's been truncated.
+ ctx.font = '80px Georgia';
+
+ var lines = getLines(ctx, [ficInfo.title, ficInfo.author, ficInfo.fandom].join(" "), width * 0.85);
+
+ if (lines.length > 6) {
+ lines = lines.slice(0, 5);
+ lines[4] += '…'
+ }
+
+ const lineHeight = 95;
+ const titleMidpoint = height * 0.425 - (lineHeight / 2 * (lines.length - 1));
+
+ for (i = 0; i < lines.length; i++) {
+ ctx.fillText(
+ lines[i],
+ width / 2,
+ titleMidpoint + i * lineHeight
+ );
+ }
+
+ return canvas;
+}
+
+/**
+ * Split a piece of text into lines to fit into a <canvas> without
+ * wrapping.
+ *
+ * This function comes from Stack Overflow user crazy2be:
+ * https://stackoverflow.com/a/16599668/1558022
+ */
+function getLines(ctx, text, maxWidth) {
+ var words = text.split(" ");
+ var lines = [];
+ var currentLine = words[0];
+
+ for (var i = 1; i < words.length; i++) {
+ var word = words[i];
+ var width = ctx.measureText(currentLine + " " + word).width;
+ if (width < maxWidth) {
+ currentLine += " " + word;
+ } else {
+ lines.push(currentLine);
+ currentLine = word;
+ }
+ }
+ lines.push(currentLine);
+ return lines;
+}
+
+
+
if (typeof module !== 'undefined') {
- module.exports = { chooseColour, findContentOpfPath, getFicInfo };
+ module.exports = {
+ chooseColour,
+ createCoverImage,
+ findContentOpfPath,
+ getFicInfo,
+ };
}