Name card done

This commit is contained in:
2025-11-11 12:25:39 +01:00
parent d5071dbf69
commit 0f1fff0609
8 changed files with 174 additions and 38 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
.idea
node_modules

View File

@@ -8,4 +8,9 @@ RUN npm install
COPY . .
CMD ["node", "app.js"]
RUN apt-get update && apt-get install -y fontconfig
RUN mkdir -p /usr/local/share/fonts/truetype/fredoka
COPY ./wwwroot/assets/fonts/Fredoka/fredoka.ttf /usr/local/share/fonts/truetype/fredoka/fredoka.ttf
RUN fc-cache -fv
CMD ["npm", "run", "name-card-test"]

2
app.js
View File

@@ -3,7 +3,7 @@ import express from "express";
import {Logger} from "./wwwroot/core/logging/logger.js";
import {launch} from "./bot.js";
await launch();
//await launch();
const app = express();
app.use(express.json());

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -21,6 +21,7 @@
"author": "Naaturel",
"license": "MIT",
"dependencies": {
"canvas": "^3.2.0",
"discord.js": "^14.22.1",
"dotenv": "^16.0.3",
"express": "^4.21.2",

View File

@@ -1,4 +1,4 @@
import {NameCardCreator} from "../wwwroot/core/welcome/nameCardCreator.js";
import {NameCardCreator} from "../wwwroot/core/welcome/nameCardCreator-2.js";
const templatePath = "./wwwroot/assets/name-card-template.png";
const avatarPath = "./tests/assets/avatar-test.png";

View File

@@ -0,0 +1,90 @@
import fs from "fs";
import path from "path";
import { createCanvas, loadImage, registerFont } from "canvas";
export class NameCardCreator {
constructor(templatePath) {
this.templatePath = templatePath;
this.loadFont("./wwwroot/assets/fonts/Fredoka/static/Fredoka-Bold.ttf")
}
loadFont(fontPath){
const fullPath = path.resolve(fontPath);
if (!fs.existsSync(fullPath)) {
throw new Error(`Font file not found at ${fullPath}`);
}
registerFont(fullPath, { family: "Fredoka Bold" });
}
/**
* Crée la carte de bienvenue entièrement via Canvas
*/
async getWelcomeCard(avatarPath, name) {
try {
const canvasWidth = 3000;
const canvasHeight = 1000;
const canvas = createCanvas(canvasWidth, canvasHeight);
const ctx = canvas.getContext("2d");
const template = await loadImage(this.templatePath);
ctx.drawImage(template, 0, 0, canvasWidth, canvasHeight);
const avatarSize = 675;
const avatar = await loadImage(avatarPath);
const avatarX = 225;
const avatarY = (canvasHeight - avatarSize) / 2;
ctx.save();
ctx.beginPath();
ctx.arc(
avatarX + avatarSize / 2,
avatarY + avatarSize / 2,
avatarSize / 2,
0,
Math.PI * 2,
true
);
ctx.closePath();
ctx.clip();
ctx.drawImage(avatar, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
ctx.lineWidth = 8;
ctx.strokeStyle = "#ffffff";
ctx.beginPath();
ctx.arc(
avatarX + avatarSize / 2,
avatarY + avatarSize / 2,
avatarSize / 2,
0,
Math.PI * 2,
true
);
ctx.stroke();
const messageX = avatarX + 1525;
const messageY = (canvasHeight / 2)+75;
ctx.fillStyle = "#ede6e6";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = '115px "Fredoka Bold"';
const lines = [`Welcome ${name}, to the`, "Spicy Jail ~"];
const lineHeight = 130;
const startY = messageY - ((lines.length - 1) / 2) * lineHeight;
lines.forEach((line, i) => {
ctx.fillText(line, messageX, startY + i * lineHeight);
});
const buffer = canvas.toBuffer("image/png");
fs.writeFileSync("./tests/result/namecard.png", buffer);
console.log("✅ Name card created: namecard.png");
return buffer;
} catch (err) {
console.error("Error creating name card:", err);
throw err;
}
}
}

View File

@@ -1,13 +1,14 @@
import sharp from "sharp";
import {Logger} from "../logging/logger.js";
import fs from "fs";
import path from "path";
import { createCanvas, registerFont } from 'canvas';
export class NameCardCreator {
constructor(templatePath) {
this.templatePath = templatePath;
this.fontPath = "./wwwroot/assets/fonts/Fredoka/Fredoka-VariableFont_wdth,wght.ttf";
this.fontData = this.loadFontData();
this.template = this.loadTemplate();
}
/**
@@ -18,6 +19,12 @@ export class NameCardCreator {
return sharp(this.templatePath);
}
loadFont(fontPath) {
const fullPath = path.resolve(fontPath);
console.debug(fullPath);
registerFont(fullPath, { family: 'Fredoka' });
}
/**
* Combines a template image with a user avatar and saves it
* @param avatarPath {string}
@@ -27,23 +34,22 @@ export class NameCardCreator {
async getWelcomeCard(avatarPath, name) {
try{
const template = this.loadTemplate();
const avatarSize = 450;
const avatarSize = 550;
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 messageWidth = 2000;
const messageHeight = 700;
const messageBuffer = await this.getMessageBuffer(name, messageWidth, messageHeight);
const result = await template
const avatarPos = { x : 150, y : (1000-avatarSize+16)/2}
const messagePos = { x : avatarPos.x + 700, y : (1000-messageHeight)/2}
const result = await this.template
.composite([
{ input: avatar, top: avatarPos.y, left: avatarPos.x },
{ input: messageBuffer, top: messagePos.y, left: messagePos.x }
])
.toFile("namecard.png")
.toFile("namecard-.png")
console.log("✅ Welcome card created: welcome-card.png");
return result;
@@ -108,9 +114,16 @@ export class NameCardCreator {
.toBuffer();
}
/**
*
* @param message
* @param outputWidth
* @param outputHeight
* @returns {Promise<void | Buffer<ArrayBufferLike>>}
*/
async getMessageBuffer(message, outputWidth, outputHeight){
const messageSvg = this.getMessageSvg(message, outputWidth, outputHeight);
return Buffer.from(messageSvg, "utf-8");
return this.getMessageCanvas(message, outputWidth, outputHeight).toBuffer();
//return Buffer.from(messageSvg, "utf-8");
}
/**
@@ -120,7 +133,7 @@ export class NameCardCreator {
* @param outputHeight
* @returns {string}
*/
getMessageSvg(message, outputWidth, outputHeight) {
/*getMessageSvg(message, outputWidth, outputHeight) {
const safeMessage =
message
.replace(/&/g, "&amp;")
@@ -128,27 +141,52 @@ export class NameCardCreator {
.replace(/>/g, "&gt;");
return `
<svg width="${outputWidth}" height="${outputHeight}" xmlns="http://www.w3.org/2000/svg">
<style>
@font-face {
font-family: 'Fredoka';
src: url(data:font/truetype;charset=utf-8;base64,${this.fontData}) format('truetype');
}
.title {
font-family: 'Fredoka', sans-serif;
fill: #ede6e6;
font-size: ${outputHeight/2}px;
font-weight: bold;
dominant-baseline: middle;
}
</style>
<text x="50%" y="50%" text-anchor="middle" class="title">${safeMessage}</text>
</svg>
<svg width="${outputWidth}" height="${outputHeight}" xmlns="http://www.w3.org/2000/svg">
<style>
@font-face {
font-family: 'Fredoka';
src: url("data:font/ttf;base64,${this.fontData}") format('truetype');
}
text {
font-family: 'Fredoka', sans-serif;
fill: #ede6e6;
font-size: 95px;
dominant-baseline: middle;
text-anchor: middle;
}
</style>
<text x="50%" y="50%" dy="0">Welcome ${safeMessage}, to the</text>
<text x="50%" y="50%" dy="1.3em">Spicy Jail ~</text>
</svg>
`;
}*/
/**
*
* @returns {Canvas}
*/
getMessageCanvas(name, outputWidth, outputHeight){
const canvas = createCanvas(outputWidth, outputHeight);
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#0000';
ctx.fillRect(0, 0, outputWidth, outputHeight);
ctx.fillStyle = '#ede6e6';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '110px "Fredoka"';
const message = `Welcome ${name}, to the Spicy Jail ~`;
ctx.fillText(message, outputWidth / 2, outputHeight / 2);
return canvas;
}
loadFontData(){
return fs.readFileSync(this.fontPath).toString("base64");
}
/*loadFontData(){
const fullPath = path.resolve(this.fontPath);
return fs.readFileSync(fullPath)
.toString("base64")
}*/
}