Name card done
This commit is contained in:
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.idea
|
||||
node_modules
|
||||
@@ -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
2
app.js
@@ -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());
|
||||
|
||||
BIN
namecard.png
BIN
namecard.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
90
wwwroot/core/welcome/nameCardCreator-2.js
Normal file
90
wwwroot/core/welcome/nameCardCreator-2.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, "&")
|
||||
@@ -128,27 +141,52 @@ export class NameCardCreator {
|
||||
.replace(/>/g, ">");
|
||||
|
||||
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")
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user