quote-image-gen / server.js
eienmojiki's picture
Update server.js
84e1e28 verified
const express = require("express");
const cors = require("cors");
const { createCanvas, registerFont } = require("canvas");
const path = require("path");
const fs = require("fs");
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static("public"));
// Create fonts directory if it doesn't exist
const fontsDir = path.join(__dirname, "fonts");
if (!fs.existsSync(fontsDir)) {
fs.mkdirSync(fontsDir);
}
// Store available fonts and their variations
const availableFonts = new Map();
function initializeFonts() {
if (!fs.existsSync(fontsDir)) {
console.log(
"Created fonts directory. Please add font files (.ttf or .otf) to the fonts folder."
);
return;
}
const fontFiles = fs
.readdirSync(fontsDir)
.filter(
(file) =>
file.toLowerCase().endsWith(".ttf") ||
file.toLowerCase().endsWith(".otf")
);
fontFiles.forEach((file) => {
const fontPath = path.join(fontsDir, file);
const fontName = file.replace(/\.(ttf|otf)$/i, "");
// Parse font variations (Regular, Bold, Italic, etc.)
let weight = "normal";
let style = "normal";
const lowerFontName = fontName.toLowerCase();
if (lowerFontName.includes("bold")) weight = "bold";
if (lowerFontName.includes("light")) weight = "light";
if (lowerFontName.includes("medium")) weight = "medium";
if (lowerFontName.includes("italic")) style = "italic";
// Register font with canvas
registerFont(fontPath, {
family: fontName.split("-")[0], // Get base font name
weight,
style,
});
// Store font info
const familyName = fontName.split("-")[0];
if (!availableFonts.has(familyName)) {
availableFonts.set(familyName, []);
}
availableFonts.get(familyName).push({
fullName: fontName,
weight,
style,
});
});
console.log("Available font families:", Array.from(availableFonts.keys()));
}
// Initialize fonts
initializeFonts();
// Store requests history
let requestsHistory = [];
function generateQuoteImage(ctx, canvas, data) {
const {
text,
author,
bgColor,
barColor,
textColor,
authorColor,
quoteFontFamily,
quoteFontWeight,
quoteFontStyle,
authorFontFamily,
authorFontWeight,
authorFontStyle,
barWidth = 4,
} = data;
// Constants for layout
const margin = 80;
const quoteMarkSize = 120;
const padding = 30;
const lineHeight = 50;
// Clear canvas
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw bars
ctx.fillStyle = barColor;
ctx.fillRect(margin, margin, barWidth, canvas.height - margin * 2);
ctx.fillRect(
canvas.width - margin - barWidth,
margin,
barWidth,
canvas.height - margin * 2
);
// Set up quote font
ctx.fillStyle = barColor;
const quoteMarkFont = [];
if (quoteFontStyle === "italic") quoteMarkFont.push("italic");
quoteMarkFont.push(quoteFontWeight);
quoteMarkFont.push(`${quoteMarkSize}px`);
quoteMarkFont.push(`"${quoteFontFamily}"`);
ctx.font = quoteMarkFont.join(" ");
ctx.textBaseline = "top";
// Calculate quote mark metrics
const quoteMarkWidth = ctx.measureText('"').width;
const textStartX = margin + barWidth + padding + quoteMarkWidth + padding;
const textEndX = canvas.width - margin - barWidth - padding * 2;
const maxWidth = textEndX - textStartX;
// Set up quote text font
ctx.fillStyle = textColor;
const quoteFont = [];
if (quoteFontStyle === "italic") quoteFont.push("italic");
quoteFont.push(quoteFontWeight);
quoteFont.push("36px");
quoteFont.push(`"${quoteFontFamily}"`);
ctx.font = quoteFont.join(" ");
// Word wrap text
const words = text.split(" ");
const lines = [];
let currentLine = "";
for (let word of words) {
const testLine = currentLine + (currentLine ? " " : "") + word;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth) {
if (currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = word;
}
} else {
currentLine = testLine;
}
}
if (currentLine) {
lines.push(currentLine);
}
// Calculate total height of text block
const totalTextHeight = lines.length * lineHeight;
const authorHeight = 60; // Space reserved for author
const availableHeight = canvas.height - margin * 2;
// Calculate starting Y position to center text block
let startY =
margin + (availableHeight - (totalTextHeight + authorHeight)) / 2;
// Draw quote mark at the same vertical position as first line
ctx.fillStyle = barColor;
ctx.font = quoteMarkFont.join(" ");
ctx.fillText('"', margin + barWidth + padding, startY);
// Draw quote lines
ctx.fillStyle = textColor;
ctx.font = quoteFont.join(" ");
lines.forEach((line, index) => {
ctx.fillText(line.trim(), textStartX, startY + index * lineHeight);
});
// Draw author below the quote
ctx.fillStyle = authorColor;
const authorFont = [];
if (authorFontStyle === "italic") authorFont.push("italic");
authorFont.push(authorFontWeight);
authorFont.push("28px");
authorFont.push(`"${authorFontFamily}"`);
ctx.font = authorFont.join(" ");
// Ensure author doesn't overflow
let authorText = `- ${author}`;
let authorMetrics = ctx.measureText(authorText);
if (authorMetrics.width > maxWidth) {
const ellipsis = "...";
while (authorMetrics.width > maxWidth && author.length > 0) {
author = author.slice(0, -1);
authorText = `- ${author}${ellipsis}`;
authorMetrics = ctx.measureText(authorText);
}
}
// Position author text below quote with spacing
const authorY = startY + totalTextHeight + 40;
ctx.fillText(authorText, textStartX, authorY);
}
// API Endpoints
app.get("/api/fonts", (req, res) => {
const fontDetails = Array.from(availableFonts.entries()).map(
([family, variations]) => ({
family,
variations: variations.map((v) => ({
weight: v.weight,
style: v.style,
fullName: v.fullName,
})),
})
);
res.json(fontDetails);
});
app.post("/api/generate-quote", (req, res) => {
try {
const data = req.body;
// Validate fonts exist
if (!availableFonts.has(data.quoteFontFamily)) {
throw new Error("Selected quote font is not available");
}
if (!availableFonts.has(data.authorFontFamily)) {
throw new Error("Selected author font is not available");
}
// Store request
requestsHistory.unshift({
timestamp: new Date(),
request: data,
});
// Keep only last 10 requests
requestsHistory = requestsHistory.slice(0, 10);
// Create canvas
const canvas = createCanvas(1200, 675); // 16:9 ratio
const ctx = canvas.getContext("2d");
// Generate quote image
generateQuoteImage(ctx, canvas, data);
// Send response
res.json({
success: true,
imageUrl: canvas.toDataURL(),
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error("Error generating quote:", error);
res.status(500).json({
success: false,
error: error.message,
});
}
});
app.get("/api/requests-history", (req, res) => {
res.json(requestsHistory);
});
// Start server
const PORT = process.env.PORT || 7860;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`View the application at http://localhost:${PORT}`);
});