Skip to content

Instantly share code, notes, and snippets.

@marshmallowrobot
Created January 16, 2023 18:44
Show Gist options
  • Save marshmallowrobot/06108e75fec53408188f759beb7ed6ce to your computer and use it in GitHub Desktop.
Save marshmallowrobot/06108e75fec53408188f759beb7ed6ce to your computer and use it in GitHub Desktop.
Programmatically generate an image with custom text using a headless browser to screenshot an HTML page
const { join } = require('node:path');
const { writeFileSync } = require('node:fs');
const puppeteer = require('puppeteer');
const imageHtmlHead = () => {
return `
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro&family=PT+Sans&display=swap" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Source+Serif+Pro:wght@400;700&family=PT+Sans:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-family: 'PT Sans';
font-size: 12px;
color: white;
}
body {
min-height: 576px;
min-width: 1024px;
}
.container {
background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/A_laptop_and_a_notebook_on_the_wooden_table_%28Pixabay%29.jpg/1024px-A_laptop_and_a_notebook_on_the_wooden_table_%28Pixabay%29.jpg');
background-repeat: no-repeat;
display: flex;
flex-direction: row;
/* maintain 16:9 aspect ratio */
height: 576px;
width: 1024px;
}
.color-field {
border-bottom: 576px solid rgba(0, 103, 78, 0.75);
border-right: 180px solid transparent;
position: relative;
width: 512px;
z-index: 0;
}
.title {
font-family: 'Source Serif Pro', serif;
font-size: 5rem;
margin: 26rem 0 0 2rem;
position: absolute;
z-index: 1;
}
.subtitle {
font-family: 'PT Sans', sans-serif;
font-size: 2.2rem;
position: absolute;
margin: 32rem 0 0 2rem;
z-index: 1;
}
</style>
</head>
`;
}
const imageHtmlBody = (titleText, subtitleText) => {
return `
<body>
<div class="container">
<div class="color-field"></div>
<span class="title">${titleText}</span>
<span class="subtitle">${subtitleText}</span>
</div>
</body>
`;
}
const imageMarkup = (titleText, subtitleText) => {
return `
<!DOCTYPE html>
<html>
${imageHtmlHead()}
${imageHtmlBody(titleText, subtitleText)}
</html>
`;
}
/**
* Creates a new image with the given text values.
* Writes the image to the given path.
* Returns the file name of the newly generated image.
*
* NOTE: The given text becomes the generated file name.
* Dont use crazy characters, try not to duplicate.
* **/
module.exports = generateImage = async (baseWritePath, titleText, subtitleText) => {
const imageName = `${titleText}-${subtitleText}.png`;
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
await page.setContent(imageMarkup(titleText, subtitleText));
const content = await page.$('body');
const imageBuffer = await content.screenshot();
writeFileSync(join(baseWritePath, imageName), imageBuffer);
return imageName;
}
catch (error) {
console.error(error);
return '';
}
finally {
await browser.close();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment