-
Notifications
You must be signed in to change notification settings - Fork 1
/
metaImage.js
142 lines (118 loc) · 4.42 KB
/
metaImage.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { createCanvas, registerFont, loadImage } from "canvas";
import Image from "@11ty/eleventy-img";
// register fonts
registerFont("./config/ogImage/fonts/NotoSans-Regular.ttf", { family: "Noto" });
registerFont("./config/ogImage/fonts/NotoSans-Italic.ttf", { family: "Noto", style: "italic" });
// wrap text in a canvas
// adapted from https://urre.me/writings/dynamic-open-graph-images/
async function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
let words = text.split(" ");
let line = "";
for (let n = 0; n < words.length; n++) {
let testLine = line + words[n] + " ";
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
drawTextBG(ctx, line, ctx.font, ctx.textAlign, x, y);
// context.fillText(line, x, y)
line = words[n] + " ";
y += lineHeight;
} else {
line = testLine;
}
}
drawTextBG(ctx, line, ctx.font, ctx.textAlign, x, y);
//context.fillText(line, x, y)
}
// draw background behind text on canvas
// adapted from https://stackoverflow.com/a/18901408
async function drawTextBG(ctx, txt, font, align, x, y) {
// save current state as we make a lot of changes
ctx.save();
// set font
ctx.font = font;
// draw text from top - makes life easier at the moment
ctx.textBaseline = "top";
// color for background
ctx.fillStyle = "#cad3f5";
// get text metrics
let metrics = ctx.measureText(txt);
// draw background rect assuming height of font, based text alignment
if (ctx.textAlign === "start") {
ctx.fillRect(x - 8, y - metrics.emHeightDescent, metrics.width + 4, metrics.emHeightDescent + 4);
} else if (ctx.textAlign === "end") {
ctx.fillRect(x - metrics.width - 8, y - metrics.emHeightDescent, metrics.width + 16, metrics.emHeightDescent + 4);
}
// text color
ctx.fillStyle = "#1e2030";
ctx.textAlign = align;
// draw text on top
ctx.fillText(txt, x, y - metrics.emHeightDescent);
// restore original state
ctx.restore();
}
/**
* Generate an HTML meta image using canvas.
* The image will be saved in `./dist/assets/img/meta`
* @param string titleText The main text to print on the image
* @returns Path to the saved image
*/
async function generateMetaImage(titleText) {
const width = 1200;
const height = 630;
let titleFontSize = 72;
let titleLineHeight = titleFontSize * 1.375;
let subText = this.ctx.meta.domain;
// create the canvas
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
// create rectangle with background
ctx.fillStyle = "#171717";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// add the image
const bgImage = await loadImage("./config/ogImage/images/gradient2.png");
ctx.drawImage(bgImage, 0, 0);
// add subText text
ctx.fillStyle = "#fafafa";
ctx.font = "italic 48px 'Noto'";
ctx.textAlign = "end";
// ctx.fillText(imageFooterText, canvas.width - 48, canvas.height - 50);
drawTextBG(ctx, subText, ctx.font, ctx.textAlign, canvas.width - 48, canvas.height - 50);
// add image title text
ctx.font = `normal ${titleFontSize}px "Noto"`;
ctx.textAlign = "start";
ctx.fillStyle = "#fafafa";
wrapText(ctx, titleText, 64, 144, canvas.width - 96, titleLineHeight);
//drawTextBG(ctx, imageTitle, ctx.font, 48, 110);
// create the buffer
let buffer = canvas.toBuffer("image/png");
// use 11ty/eleventy-img to save it
let imgMetadata = await Image(buffer, {
widths: [width],
formats: ["png"],
outputDir: "./dist/assets/img/meta",
urlPath: "/assets/img/meta",
sharpPngOptions: {
quality: 95
},
filenameFormat: (id, src, width, format) => {
let name = "";
let url = `${this.ctx.page.url}`; // the url to the current page
if (url === "/") {
name = "index"; // for / root
} else if (url.startsWith("/") && !url.endsWith("/")) {
name = url.replace(".", "_").substring(1); // for /404.html and similar pages
} else {
if (url.startsWith("/posts/") && url.match(/\//g).length > 3) name = "post_"; // prefix /posts/xxxx/title/ pages
name += url
.substring(1, this.ctx.page.url.length - 1)
.split("/")
.at(-1); // for /path/to/page/ pages
}
return `${name}-${width}w.${format}`;
}
});
let url = imgMetadata.png[imgMetadata.png.length - 1].url;
return this.ctx.env === "production" ? `${this.ctx.meta.base_url}${url}` : url;
}
export default generateMetaImage;