Name card almost done

This commit is contained in:
2025-11-11 00:00:01 +01:00
parent 57f8515491
commit d5071dbf69
2 changed files with 46 additions and 38 deletions

View File

@@ -29,13 +29,19 @@ export class NameCardCreator {
const template = this.loadTemplate();
const avatar = await this.handleAvatar(avatarPath);
const messageBuffer = await this.getMessageBuffer(`Hello ${name}!`);
const avatarSize = 450;
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 result = await template
.composite([
{ input: avatar, top: 215, left: 275 },
{ input: messageBuffer, top: 400, left: 1300 }
{ input: avatar, top: avatarPos.y, left: avatarPos.x },
{ input: messageBuffer, top: messagePos.y, left: messagePos.x }
])
.toFile("namecard.png")
@@ -50,69 +56,71 @@ export class NameCardCreator {
/**
*
* @param avatarPath
* @param outputSize
* @returns {Promise<Buffer<ArrayBufferLike>>}
*/
async handleAvatar(avatarPath) {
const avatarSize = 670;
async handleAvatar(avatarPath, outputSize) {
const avatarSize = outputSize;
const borderSize = 8;
const radius = avatarSize / 2;
const totalSize = avatarSize + borderSize * 2;
const avatarRadius = avatarSize / 2;
const maskBuffer = Buffer.from(`
<svg width="${avatarSize}" height="${avatarSize}" xmlns="http://www.w3.org/2000/svg">
<circle cx="${avatarRadius}" cy="${avatarRadius}" r="${avatarRadius}" fill="white"/>
</svg>
`);
const avatarBuffer = await sharp(avatarPath)
.resize(avatarSize, avatarSize, { fit: "cover" })
.png()
.toBuffer();
const maskSvg = Buffer.from(`
<svg width="${avatarSize}" height="${avatarSize}" xmlns="http://www.w3.org/2000/svg">
<circle cx="${radius}" cy="${radius}" r="${radius}" fill="white"/>
</svg>
`);
const roundedAvatar = await sharp(avatarBuffer)
.composite([{ input: maskSvg, blend: "dest-in" }])
const roundedAvatarBuffer = await sharp(avatarBuffer)
.composite([{ input: maskBuffer, blend: "dest-in" }])
.png()
.toBuffer();
const roundedBorder = Buffer.from(`
<svg width="${totalSize}" height="${totalSize}" xmlns="http://www.w3.org/2000/svg">
<circle
cx="${totalSize / 2}"
cy="${totalSize / 2}"
r="${radius + borderSize / 2}"
stroke="#ffffff"
stroke-width="${borderSize}"
fill="none"
/>
</svg>
`)
const backgroundBuffer = Buffer.from(`
<svg width="${totalSize}" height="${totalSize}" xmlns="http://www.w3.org/2000/svg">
<circle
cx="${totalSize / 2}"
cy="${totalSize / 2}"
r="${avatarRadius}"
stroke="#ffffff"
stroke-width="${borderSize}"
fill="white"/>
</svg>
`);
return await sharp({
create: {
width: totalSize,
height: totalSize,
channels: 4,
background: "#0000"
}
}).composite([
{ input: roundedAvatar, top: 0, left: 0 },
{ input: roundedBorder, top: borderSize, left: borderSize }
])
background: "#0000",
}})
.composite([
{ input: backgroundBuffer, top: 0, left: 0 },
{ input: roundedAvatarBuffer, top: borderSize, left: borderSize }
])
.png()
.toBuffer();
}
async getMessageBuffer(message){
const messageSvg = this.getMessageSvg(message);
async getMessageBuffer(message, outputWidth, outputHeight){
const messageSvg = this.getMessageSvg(message, outputWidth, outputHeight);
return Buffer.from(messageSvg, "utf-8");
}
/**
*
* @param message {string}
* @param outputWidth
* @param outputHeight
* @returns {string}
*/
getMessageSvg(message) {
getMessageSvg(message, outputWidth, outputHeight) {
const safeMessage =
message
.replace(/&/g, "&amp;")
@@ -120,7 +128,7 @@ export class NameCardCreator {
.replace(/>/g, "&gt;");
return `
<svg width="1500" height="200" xmlns="http://www.w3.org/2000/svg">
<svg width="${outputWidth}" height="${outputHeight}" xmlns="http://www.w3.org/2000/svg">
<style>
@font-face {
font-family: 'Fredoka';
@@ -129,7 +137,7 @@ export class NameCardCreator {
.title {
font-family: 'Fredoka', sans-serif;
fill: #ede6e6;
font-size: 80px;
font-size: ${outputHeight/2}px;
font-weight: bold;
dominant-baseline: middle;
}