@justherza/canvafy
Version:
Make configurable canvas easily with Canvafy
259 lines (226 loc) • 9.24 kB
JavaScript
"use strict";
const { createCanvas, loadImage, registerFont } = require('canvas');
module.exports = class Top {
constructor(options) {
this.font = { name: options?.font?.name ?? "Poppins", path: options?.font?.path };
this.usersData = options?.usersData || [{ top: 1, avatar: "https://i.pinimg.com/736x/c6/a8/5f/c6a85f7dbcbf367d5dc1baa2aaa19a73.jpg", tag: "Beş#0005", score: 5 },{ top: 2, avatar: "https://i.pinimg.com/736x/c6/a8/5f/c6a85f7dbcbf367d5dc1baa2aaa19a73.jpg", tag: "Beş#0005", score: 5 },{ top: 3, avatar: "https://i.pinimg.com/736x/c6/a8/5f/c6a85f7dbcbf367d5dc1baa2aaa19a73.jpg", tag: "Beş#0005", score: 5 }];
this.background = {
type: "none",
background: "none"
};
this.abbreviateNumber = false;
this.opacity = 0;
this.scoreMessage = "";
this.colors = options?.colors || { box: '#212121', username: '#ffffff', score: '#ffffff', firstRank: '#f7c716', secondRank: '#9e9e9e', thirdRank: '#94610f' };
this.emojiCache = new Map();
}
setUsersData(usersData) {
if(usersData.length > 10){
throw new Error("setUsersData values cannot be greater than 10.");
}
this.usersData = usersData;
return this;
}
setScoreMessage(message) {
this.scoreMessage = message;
return this;
}
setColors(colors) {
this.colors = colors;
return this;
}
setabbreviateNumber(bool){
if(typeof bool !== "boolean") {
throw new Error("You must give a abbreviate number true or false argument.");
}
this.abbreviateNumber = bool;
return this;
}
setOpacity(opacity = 0) {
if (opacity) {
if (opacity >= 0 && opacity <= 1) {
this.opacity = opacity;
return this;
} else {
throw new Error("The value of the opacity of setOpacity method must be between 0 and 1 (0 and 1 included).");
}
}
}
setBackground(type, value) {
if (type === 'color') {
if (value) {
if (/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/.test(value)) {
this.background.type = "color";
this.background.background = value;
return this;
} else {
throw new Error("Invalid color for the second argument in setForeground method. You must give a hexadecimal color.");
}
} else {
throw new Error("You must give a hexadecimal color as a second argument of setBackground method.");
}
} else if (type === 'image') {
if (value) {
this.background.type = "image";
this.background.background = value;
return this;
} else {
throw new Error("You must give a background URL as a second argument.");
}
} else {
throw new Error("The first argument of setBackground must be 'color' or 'image'.");
}
}
getEmojiCodePoint(emoji) {
return Array.from(emoji).map(char =>
char.codePointAt(0).toString(16)
).join('-');
}
async loadEmojiImage(emojiChar) {
if (this.emojiCache.has(emojiChar)) {
return this.emojiCache.get(emojiChar);
}
try {
const codePoint = this.getEmojiCodePoint(emojiChar);
const emojiUrl = `https://twemoji.maxcdn.com/v/latest/72x72/${codePoint}.png`;
const emojiImage = await loadImage(emojiUrl);
this.emojiCache.set(emojiChar, emojiImage);
return emojiImage;
} catch (error) {
return null;
}
}
async drawTextWithEmoji(ctx, text, x, y, fontSize, color, font, maxWidth, align = 'left') {
const emojiRegex = /(\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\p{Extended_Pictographic})/gu;
if (!emojiRegex.test(text)) {
ctx.fillStyle = color;
ctx.font = font;
ctx.textAlign = align;
ctx.fillText(text, x, y, maxWidth);
return;
}
const parts = text.split(emojiRegex).filter(part => part !== '');
let currentX = x;
ctx.fillStyle = color;
ctx.font = font;
for (const part of parts) {
if (emojiRegex.test(part)) {
const emojiImage = await this.loadEmojiImage(part);
if (emojiImage) {
ctx.drawImage(emojiImage, currentX, y - fontSize * 0.8, fontSize, fontSize);
currentX += fontSize;
} else {
ctx.fillText(part, currentX, y);
currentX += ctx.measureText(part).width;
}
} else if (part.trim()) {
ctx.fillText(part, currentX, y);
currentX += ctx.measureText(part).width;
}
}
}
async build() {
if (this.font.path) {
registerFont(this.font.path, { family: this.font.name });
}
const fillRoundRect=(ctx,x,y,w,h,r,f,s)=>{
if(typeof r==="number")r={tl:r,tr:r,br:r,bl:r};else {
var defaultRadius={tl:0,tr:0,br:0,bl:0};
for(var side in defaultRadius){r[side]=r[side]||defaultRadius[side]}};
ctx.beginPath();
ctx.moveTo(x + r.tl, y);
ctx.lineTo(x + w - r.tr, y);
ctx.quadraticCurveTo(x+w, y, x + w, y + r.tr);
ctx.lineTo(x + w, y + h - r.br);
ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h);
ctx.lineTo(x + r.bl, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl);
ctx.lineTo(x, y + r.tl);
ctx.quadraticCurveTo(x, y, x + r.tl, y);
ctx.closePath();
if(f)ctx.fill();
if(s)ctx.stroke();}
const abbreviateNumber = (value) => {
var newValue = value;
if (value >= 1000) {
var suffixes = ["", "K", "M", "B","T"];
var suffixNum = Math.floor( (""+value).length/3 );
var shortValue = '';
for (var precision = 2; precision >= 1; precision--) {
shortValue = parseFloat( (suffixNum != 0 ? (value / Math.pow(1000,suffixNum) ) : value).toPrecision(precision));
var dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g,'');
if (dotLessShortValue.length <= 2) { break; }
}
if (shortValue % 1 != 0) shortValue = shortValue.toFixed(1);
newValue = shortValue+suffixes[suffixNum];
}
return newValue;
}
let yuksek = this.usersData.length * 74.5;
const canvas = createCanvas(680, yuksek);
const ctx = canvas.getContext('2d');
ctx.globalAlpha = 1;
if (this.background.type === "color") {
ctx.beginPath();
ctx.fillStyle = this.background.background;
ctx.fillRect(0, 0, canvas.width, canvas.height)
} else if (this.background.type === "image") {
try {
ctx.drawImage(await loadImage(this.background.background), 0, 0, canvas.width, canvas.height);
} catch {
throw new Error("The image given in the second parameter of the setBackground method is not valid or you are not connected to the internet.");
}
}
if(this.usersData) {
var Box_Y = 0, Avatar_Y = 0, Tag_Y = 45, XP_Y = 45, Level_Y = 30, Rank_Y = 45;
for(var i=0; i < this.usersData.length; i++) {
ctx.save();
ctx.fillStyle = this.colors.box;
ctx.globalAlpha = this.opacity;
fillRoundRect(ctx, 0, Box_Y, canvas.width, 70, 15, true, false);
ctx.globalAlpha = 1;
const avatar = await loadImage(this.usersData[i].avatar);
ctx.clip();
ctx.drawImage(avatar, 0, Avatar_Y, 70, 70);
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 6;
ctx.shadowColor = "#0a0a0a";
await this.drawTextWithEmoji(ctx, this.usersData[i].tag, 80, Tag_Y, 25, this.colors.username, `bold 25px ${this.font.name}`, 260, 'left');
ctx.fillStyle = this.colors.score;
ctx.font = `bold 20px ${this.font.name}`;
ctx.textAlign = 'right';
ctx.fillText(`${this.scoreMessage} ${this.abbreviateNumber == true ? `${abbreviateNumber(this.usersData[i].score)}`:`${this.usersData[i].score}`}`, 560, XP_Y, 200);
if(this.usersData[i].top === 1) {
ctx.fillStyle = this.colors.firstRank;
}
else if(this.usersData[i].top === 2) {
ctx.fillStyle = this.colors.secondRank;
}
else if(this.usersData[i].top === 3) {
ctx.fillStyle = this.colors.thirdRank;
}
ctx.font = `bold 30px ${this.font.name}`;
ctx.textAlign = 'right';
ctx.fillText("#" + this.usersData[i].top, 660, Rank_Y, 75);
Box_Y = Box_Y + 75;
Avatar_Y = Avatar_Y + 75;
Tag_Y = Tag_Y + 75;
XP_Y = XP_Y + 75;
Level_Y = Level_Y + 75;
Rank_Y = Rank_Y + 75;
ctx.restore();
}
} else {
ctx.font = `bold 40px ${this.font.name}`;
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 6;
ctx.shadowColor = "#0a0a0a"
ctx.fillText('Not found!', 340, 370, 500);
}
return canvas.toBuffer('image/png');
}
};