|
| 1 | +import { createCanvas, GlobalFonts, loadImage } from '@napi-rs/canvas'; |
| 2 | + |
| 3 | +// TODO: maybe rewrite in rust? |
| 4 | + |
| 5 | +/** @type {import("@vercel/node").VercelApiHandler} */ |
| 6 | +export default async function handler(request, response) { |
| 7 | + if (!request.url) return response.status(400); |
| 8 | + |
| 9 | + const url = new URL(request.url, `http://${request.headers.host}`); |
| 10 | + const { searchParams } = url; |
| 11 | + const hasContent = searchParams.has('content'); |
| 12 | + const content = hasContent ? searchParams.get('content') : ''; |
| 13 | + |
| 14 | + GlobalFonts.registerFromPath('./assets/NotoColorEmoji-Regular.ttf', 'NotoColorEmoji'); |
| 15 | + GlobalFonts.registerFromPath('./assets/PTSerif-Italic.ttf', 'pt'); |
| 16 | + |
| 17 | + const fontSize = 72; |
| 18 | + const lineHeight = fontSize + 6; |
| 19 | + |
| 20 | + const imageSize = 800; |
| 21 | + const maxWidth = 540; |
| 22 | + |
| 23 | + const words = content.split(' '); |
| 24 | + |
| 25 | + // create canvas same width and height as image |
| 26 | + const canvas = createCanvas(imageSize, imageSize); |
| 27 | + const ctx = canvas.getContext('2d'); |
| 28 | + |
| 29 | + const tile = await loadImage('./assets/tile.jpg'); |
| 30 | + ctx.drawImage(tile, 0, 0, imageSize, imageSize); |
| 31 | + |
| 32 | + ctx.font = `400 ${fontSize}px pt, NotoColorEmoji`; |
| 33 | + ctx.textAlign = 'center'; |
| 34 | + ctx.fillStyle = '#0f1b65'; |
| 35 | + |
| 36 | + // check line amount |
| 37 | + let line = ''; |
| 38 | + /** @type {string[]} */ |
| 39 | + const lines = []; |
| 40 | + |
| 41 | + for (const word of words) { |
| 42 | + const testLine = `${line} ${word}`; |
| 43 | + const testWidth = ctx.measureText(testLine).width; |
| 44 | + |
| 45 | + if (testWidth > maxWidth) { |
| 46 | + // go to next line |
| 47 | + lines.push(line.trim()); |
| 48 | + line = word; |
| 49 | + continue; |
| 50 | + } |
| 51 | + |
| 52 | + line = testLine; |
| 53 | + } |
| 54 | + |
| 55 | + lines.push(line); |
| 56 | + |
| 57 | + // fill tile with text |
| 58 | + let startX = 0; |
| 59 | + let startY = imageSize / 2 - ((lines.length - 1) * lineHeight) / 2 + 24; |
| 60 | + |
| 61 | + for (const line of lines) { |
| 62 | + startX = imageSize / 2; |
| 63 | + ctx.fillText(line, startX, startY); |
| 64 | + startY += lineHeight; |
| 65 | + } |
| 66 | + |
| 67 | + response.status(200).setHeader('Content-Type', 'image/png').send(canvas.toBuffer('image/png')); |
| 68 | +} |
0 commit comments