UNPKG

@justherza/canvafy

Version:

Make configurable canvas easily with Canvafy

259 lines (226 loc) 9.24 kB
"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'); } };