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 . .
|
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 {Logger} from "./wwwroot/core/logging/logger.js";
|
||||||
import {launch} from "./bot.js";
|
import {launch} from "./bot.js";
|
||||||
|
|
||||||
await launch();
|
//await launch();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
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",
|
"author": "Naaturel",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"canvas": "^3.2.0",
|
||||||
"discord.js": "^14.22.1",
|
"discord.js": "^14.22.1",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.21.2",
|
"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 templatePath = "./wwwroot/assets/name-card-template.png";
|
||||||
const avatarPath = "./tests/assets/avatar-test.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 sharp from "sharp";
|
||||||
import {Logger} from "../logging/logger.js";
|
import {Logger} from "../logging/logger.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { createCanvas, registerFont } from 'canvas';
|
||||||
|
|
||||||
export class NameCardCreator {
|
export class NameCardCreator {
|
||||||
|
|
||||||
constructor(templatePath) {
|
constructor(templatePath) {
|
||||||
this.templatePath = templatePath;
|
this.templatePath = templatePath;
|
||||||
this.fontPath = "./wwwroot/assets/fonts/Fredoka/Fredoka-VariableFont_wdth,wght.ttf";
|
this.template = this.loadTemplate();
|
||||||
this.fontData = this.loadFontData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,6 +19,12 @@ export class NameCardCreator {
|
|||||||
return sharp(this.templatePath);
|
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
|
* Combines a template image with a user avatar and saves it
|
||||||
* @param avatarPath {string}
|
* @param avatarPath {string}
|
||||||
@@ -27,23 +34,22 @@ export class NameCardCreator {
|
|||||||
async getWelcomeCard(avatarPath, name) {
|
async getWelcomeCard(avatarPath, name) {
|
||||||
try{
|
try{
|
||||||
|
|
||||||
const template = this.loadTemplate();
|
const avatarSize = 550;
|
||||||
|
|
||||||
const avatarSize = 450;
|
|
||||||
const avatar = await this.handleAvatar(avatarPath, avatarSize);
|
const avatar = await this.handleAvatar(avatarPath, avatarSize);
|
||||||
const avatarPos = { x : 1500-(avatarSize/2), y : 100}
|
|
||||||
|
|
||||||
const messageWidth = 1500;
|
const messageWidth = 2000;
|
||||||
const messageHeight = 200;
|
const messageHeight = 700;
|
||||||
const messageBuffer = await this.getMessageBuffer(`Hello ${name}!`, messageWidth, messageHeight);
|
const messageBuffer = await this.getMessageBuffer(name, messageWidth, messageHeight);
|
||||||
const messagePos = { x : 1500-(messageWidth/2), y : avatarPos.y + 550}
|
|
||||||
|
|
||||||
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([
|
.composite([
|
||||||
{ input: avatar, top: avatarPos.y, left: avatarPos.x },
|
{ input: avatar, top: avatarPos.y, left: avatarPos.x },
|
||||||
{ input: messageBuffer, top: messagePos.y, left: messagePos.x }
|
{ input: messageBuffer, top: messagePos.y, left: messagePos.x }
|
||||||
])
|
])
|
||||||
.toFile("namecard.png")
|
.toFile("namecard-.png")
|
||||||
|
|
||||||
console.log("✅ Welcome card created: welcome-card.png");
|
console.log("✅ Welcome card created: welcome-card.png");
|
||||||
return result;
|
return result;
|
||||||
@@ -108,9 +114,16 @@ export class NameCardCreator {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @param outputWidth
|
||||||
|
* @param outputHeight
|
||||||
|
* @returns {Promise<void | Buffer<ArrayBufferLike>>}
|
||||||
|
*/
|
||||||
async getMessageBuffer(message, outputWidth, outputHeight){
|
async getMessageBuffer(message, outputWidth, outputHeight){
|
||||||
const messageSvg = this.getMessageSvg(message, outputWidth, outputHeight);
|
return this.getMessageCanvas(message, outputWidth, outputHeight).toBuffer();
|
||||||
return Buffer.from(messageSvg, "utf-8");
|
//return Buffer.from(messageSvg, "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,7 +133,7 @@ export class NameCardCreator {
|
|||||||
* @param outputHeight
|
* @param outputHeight
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getMessageSvg(message, outputWidth, outputHeight) {
|
/*getMessageSvg(message, outputWidth, outputHeight) {
|
||||||
const safeMessage =
|
const safeMessage =
|
||||||
message
|
message
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
@@ -128,27 +141,52 @@ export class NameCardCreator {
|
|||||||
.replace(/>/g, ">");
|
.replace(/>/g, ">");
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<svg width="${outputWidth}" height="${outputHeight}" xmlns="http://www.w3.org/2000/svg">
|
<svg width="${outputWidth}" height="${outputHeight}" xmlns="http://www.w3.org/2000/svg">
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Fredoka';
|
font-family: 'Fredoka';
|
||||||
src: url(data:font/truetype;charset=utf-8;base64,${this.fontData}) format('truetype');
|
src: url("data:font/ttf;base64,${this.fontData}") format('truetype');
|
||||||
}
|
}
|
||||||
.title {
|
text {
|
||||||
font-family: 'Fredoka', sans-serif;
|
font-family: 'Fredoka', sans-serif;
|
||||||
fill: #ede6e6;
|
fill: #ede6e6;
|
||||||
font-size: ${outputHeight/2}px;
|
font-size: 95px;
|
||||||
font-weight: bold;
|
dominant-baseline: middle;
|
||||||
dominant-baseline: middle;
|
text-anchor: middle;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<text x="50%" y="50%" dy="0">Welcome ${safeMessage}, to the</text>
|
||||||
<text x="50%" y="50%" text-anchor="middle" class="title">${safeMessage}</text>
|
<text x="50%" y="50%" dy="1.3em">Spicy Jail ~</text>
|
||||||
</svg>
|
</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(){
|
/*loadFontData(){
|
||||||
return fs.readFileSync(this.fontPath).toString("base64");
|
const fullPath = path.resolve(this.fontPath);
|
||||||
}
|
return fs.readFileSync(fullPath)
|
||||||
|
.toString("base64")
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user