import sharp from "sharp"; import {Logger} from "../logging/logger.js"; import fs from "fs"; export class NameCardCreator { constructor(templatePath) { this.templatePath = templatePath; this.fontPath = "./wwwroot/assets/fonts/Fredoka/Fredoka-VariableFont_wdth,wght.ttf"; this.fontData = this.loadFontData(); } /** * Loads the template file into a sharp instance * @returns {sharp.Sharp} sharp image object */ loadTemplate() { return sharp(this.templatePath); } /** * Combines a template image with a user avatar and saves it * @param avatarPath {string} * @param name {string} * @returns {Promise} resulting image buffer */ async getWelcomeCard(avatarPath, name) { try{ const template = this.loadTemplate(); const avatarSize = 450; const avatar = await this.handleAvatar(avatarPath, avatarSize); const avatarPos = { x : 1500-(avatarSize/2), y : 100} const messageWidth = 1500; const messageHeight = 200; const messageBuffer = await this.getMessageBuffer(`Hello ${name}!`, messageWidth, messageHeight); const messagePos = { x : 1500-(messageWidth/2), y : avatarPos.y + 550} const result = await template .composite([ { input: avatar, top: avatarPos.y, left: avatarPos.x }, { input: messageBuffer, top: messagePos.y, left: messagePos.x } ]) .toFile("namecard.png") console.log("✅ Welcome card created: welcome-card.png"); return result; } catch(err) { console.log(err); await Logger.error("Unable to create name card", err); } } /** * * @param avatarPath * @param outputSize * @returns {Promise>} */ async handleAvatar(avatarPath, outputSize) { const avatarSize = outputSize; const borderSize = 8; const totalSize = avatarSize + borderSize * 2; const avatarRadius = avatarSize / 2; const maskBuffer = Buffer.from(` `); const avatarBuffer = await sharp(avatarPath) .resize(avatarSize, avatarSize, { fit: "cover" }) .png() .toBuffer(); const roundedAvatarBuffer = await sharp(avatarBuffer) .composite([{ input: maskBuffer, blend: "dest-in" }]) .png() .toBuffer(); const backgroundBuffer = Buffer.from(` `); return await sharp({ create: { width: totalSize, height: totalSize, channels: 4, background: "#0000", }}) .composite([ { input: backgroundBuffer, top: 0, left: 0 }, { input: roundedAvatarBuffer, top: borderSize, left: borderSize } ]) .png() .toBuffer(); } async getMessageBuffer(message, outputWidth, outputHeight){ const messageSvg = this.getMessageSvg(message, outputWidth, outputHeight); return Buffer.from(messageSvg, "utf-8"); } /** * * @param message {string} * @param outputWidth * @param outputHeight * @returns {string} */ getMessageSvg(message, outputWidth, outputHeight) { const safeMessage = message .replace(/&/g, "&") .replace(//g, ">"); return ` ${safeMessage} `; } loadFontData(){ return fs.readFileSync(this.fontPath).toString("base64"); } }