Skip to content

Commit

Permalink
feat: add og link previews
Browse files Browse the repository at this point in the history
  • Loading branch information
agustinustheo committed Mar 11, 2024
1 parent 2743a60 commit 7ca36dc
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@udecode/plate": "^14.2.0",
"@udecode/plate-common": "^7.0.2",
"axios": "^0.26.1",
"canvas": "^2.11.2",
"compression-webpack-plugin": "10.0.0",
"date-fns": "^2.28.0",
"detect-browser": "^5.3.0",
Expand Down
111 changes: 111 additions & 0 deletions pages/api/opengraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { NextApiRequest, NextApiResponse } from 'next';

import { createCanvas, CanvasRenderingContext2D } from 'canvas';
import sharp from 'sharp';

function wrapText(
context: CanvasRenderingContext2D,
text: string,
maxWidth: number,
) {
const words = text.split(' ');
const lines: string[] = [];
let currentLine: string = words[0];

for (let i = 1; i < words.length; i++) {
const word = words[i];
const width = context.measureText(currentLine + ' ' + word).width;
if (width < maxWidth) {
currentLine += ' ' + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine); // Push the last line
return lines;
}

function cutAndAddEllipsis(text: string) {
const maxWidth = 125;
if (text.length <= maxWidth) {
return text;
}

return text.substring(0, maxWidth) + '...';
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
// If there are missing parameters, return an error response
if (req.query.text == null || req.query.text == undefined) {
return res.status(400).json({
status: 'error',
message: `Missing required query parameters: 'text'`,
});
}

// Ensure text is a string
const requestText = req.query.text;
if (typeof requestText !== 'string') {
return res.status(400).json({
status: 'error',
message: `Invalid type for 'text'. Expected a string.`,
});
}

try {
// Cut the text if needed and add an ellipsis
const text = cutAndAddEllipsis(requestText);
const fontSize = 48;

// Load the original image to get its dimensions
const originalImage = await sharp(
'https://raw.githubusercontent.com/agustinustheo/sharp-canvas-ts/main/template.jpg',
).metadata();

// Create a canvas that matches the original image's dimensions
const canvas = createCanvas(originalImage.width!, originalImage.height!);
const context = canvas.getContext('2d');

// Optional: Register a custom font
context.font = `${fontSize}px sans-serif`;
context.fillStyle = 'black'; // Text color
context.textAlign = 'left';
context.textBaseline = 'top';

const maxWidth = originalImage.width! * 0.8; // Max width for the text
const lineHeight = fontSize * 1.2; // Line height
const lines = wrapText(context, text, maxWidth);
const textHeight = lines.length * lineHeight; // Calculate the total height of the text block

// Calculate the starting Y position to center the text block
const startY = (originalImage.height! - textHeight) / 2;

// Draw each line of text
lines.forEach((line, i) => {
const x = (originalImage.width! - context.measureText(line).width) / 2; // Center each line
const y = startY + i * lineHeight;
context.fillText(line, x, y);
});

// Convert the canvas to a Buffer
const textBuffer = canvas.toBuffer();

// Overlay the text image on the original image
const imageBuffer = await sharp(
'https://raw.githubusercontent.com/agustinustheo/sharp-canvas-ts/main/template.jpg',
)
.composite([{ input: textBuffer, blend: 'over' }])
.png()
.toBuffer();

res.setHeader('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
console.error('Error:', error);
res.status(500).json({ status: 'error', message: 'Internal server error' });
}
}
74 changes: 74 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3605,6 +3605,21 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"

"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
dependencies:
detect-libc "^2.0.0"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.7"
nopt "^5.0.0"
npmlog "^5.0.1"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.11"

"@material-ui/core@^4.12.3":
version "4.12.3"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.3.tgz#80d665caf0f1f034e52355c5450c0e38b099d3ca"
Expand Down Expand Up @@ -8378,6 +8393,15 @@ canvas-renderer@~2.1.1:
resolved "https://registry.yarnpkg.com/canvas-renderer/-/canvas-renderer-2.1.1.tgz#d91fe9511ab48056ff9fa8a04514bede76535f55"
integrity sha512-/V0XetN7s1Mk3NO7x2wxPZYv0pLMQtGAhecuOuKR88beiYCUle1AbCcFZNLu+4NVzi9RVHS0rXtIgzPEaKidLw==

canvas@^2.11.2:
version "2.11.2"
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860"
integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.0"
nan "^2.17.0"
simple-get "^3.0.3"

capability@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/capability/-/capability-0.2.5.tgz#51ad87353f1936ffd77f2f21c74633a4dea88801"
Expand Down Expand Up @@ -9467,6 +9491,13 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"

decompress-response@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies:
mimic-response "^2.0.0"

decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
Expand Down Expand Up @@ -14565,6 +14596,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==

mimic-response@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==

mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
Expand Down Expand Up @@ -14648,6 +14684,11 @@ minipass@^3.0.0, minipass@^3.1.1:
dependencies:
yallist "^4.0.0"

minipass@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==

minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
Expand Down Expand Up @@ -14775,6 +14816,11 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==

nan@^2.17.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0"
integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==

nano-css@^5.3.1:
version "5.3.5"
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.5.tgz#3075ea29ffdeb0c7cb6d25edb21d8f7fa8e8fe8e"
Expand Down Expand Up @@ -15065,6 +15111,13 @@ nodemon@^2.0.15:
undefsafe "^2.0.5"
update-notifier "^5.1.0"

nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
dependencies:
abbrev "1"

nopt@~1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
Expand Down Expand Up @@ -17780,6 +17833,15 @@ simple-concat@^1.0.0:
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==

simple-get@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
dependencies:
decompress-response "^4.2.0"
once "^1.3.1"
simple-concat "^1.0.0"

simple-get@^4.0.0, simple-get@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
Expand Down Expand Up @@ -18571,6 +18633,18 @@ tar@^6.0.2:
mkdirp "^1.0.3"
yallist "^4.0.0"

tar@^6.1.11:
version "6.2.0"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73"
integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^5.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"

telejson@^5.3.2, telejson@^5.3.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-5.3.3.tgz#fa8ca84543e336576d8734123876a9f02bf41d2e"
Expand Down

0 comments on commit 7ca36dc

Please sign in to comment.