From 93a5f8325405b419325e312b4705fcae7928c21a Mon Sep 17 00:00:00 2001 From: Hans Pagel Date: Mon, 8 Feb 2021 16:09:49 +0100 Subject: [PATCH] docs: refactor the open graph image generation --- docs/gridsome.server.js | 99 +--------------- docs/static/og-image.png | Bin 16427 -> 0 bytes docs/utilities/opengraph-images/index.js | 140 +++++++++++++++++++++++ docs/utilities/opengraph-images/logo.png | Bin 0 -> 1801 bytes 4 files changed, 144 insertions(+), 95 deletions(-) delete mode 100644 docs/static/og-image.png create mode 100644 docs/utilities/opengraph-images/index.js create mode 100644 docs/utilities/opengraph-images/logo.png diff --git a/docs/gridsome.server.js b/docs/gridsome.server.js index 976c8bad..4ff7a6f8 100644 --- a/docs/gridsome.server.js +++ b/docs/gridsome.server.js @@ -1,44 +1,8 @@ -const fs = require('fs') -const { createCanvas, registerFont } = require('canvas') const path = require('path') const globby = require('globby') +const { createDefaultOpenGraphImage, createSpecificOpenGraphImage } = require('./utilities/opengraph-images') -registerFont('fonts/Inter-Regular.otf', { family: 'InterRegular' }) -registerFont('fonts/Inter-Medium.otf', { family: 'InterMedium' }) - -const wrapText = function (context, text, x, y, maxWidth, lineHeight) { - const words = text.split(' ') - let line = '' - - for (let n = 0; n < words.length; n += 1) { - const testLine = `${line + words[n]} ` - const metrics = context.measureText(testLine) - const testWidth = metrics.width - if (testWidth > maxWidth && n > 0) { - context.fillText(line, x, y) - line = `${words[n]} ` - y += lineHeight - } else { - line = testLine - } - } - context.fillText(line, x, y) -} - -const calculateReadingTime = function (text) { - const wordsPerMinute = 200 - const textLength = text.split(' ').length - - if (textLength > 0) { - const value = Math.ceil(textLength / wordsPerMinute) - - if (value === 1) { - return `${value} minute` - } - - return `${value} minutes` - } -} +createDefaultOpenGraphImage('The headless editor framework for web artisans.', 'static/images/og-image.png') module.exports = function (api) { @@ -111,64 +75,9 @@ module.exports = function (api) { }) }) - // Generate OpenGraph images for all pages api.onCreateNode(options => { - if (process.env.NODE_ENV !== 'production') { - return + if (/* process.env.NODE_ENV === 'production' && */options.internal.typeName === 'DocPage') { + createSpecificOpenGraphImage(options.title, options.content, `static/images${options.path}og-image.png`) } - - if (options.internal.typeName !== 'DocPage') { - return - } - - const imagePath = `static/images${options.path}` - const imageFile = `static/images${options.path}og-image.png` - - // console.log(`Found Post “${options.title}” in ${options.internal.origin} …`) - - const width = 1200 - const height = 630 - const border = 40 - const canvas = createCanvas(width, height) - const context = canvas.getContext('2d') - - // background - context.fillStyle = '#000000' - context.fillRect(0, 0, width, height) - - // project - const project = 'tiptap documentation' - context.textBaseline = 'top' - context.fillStyle = '#666666' - context.font = '32pt InterRegular' - context.fillText(project, border, border) - - // title - const { title } = options - const lineHeight = 96 - context.textBaseline = 'top' - context.fillStyle = '#ffffff' - context.font = '58pt InterMedium' - wrapText(context, title, border, border + 60 + border, width - border - border, lineHeight) - - // reading time - const readingTime = calculateReadingTime(options.content) - context.textBaseline = 'bottom' - context.fillStyle = '#666666' - context.font = '32pt InterRegular' - context.fillText(readingTime, border, height - border) - - // store - const buffer = canvas.toBuffer('image/png') - - fs.mkdir(imagePath, { recursive: true }, error => { - if (error) { - throw error - } - - fs.writeFileSync(imageFile, buffer) - - // console.log(`OpenGraph image generated (${imageFile}).`) - }) }) } diff --git a/docs/static/og-image.png b/docs/static/og-image.png deleted file mode 100644 index aac726b96f79141543b932ea8825c00d9f8bf4c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16427 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 yKW+IN40iGG_fxgM!QeL-?0Ulgv&^8HxA( maxWidth && index > 0) { + numberOfLines += 1 + currentLine = `${words[index]} ` + } else { + currentLine = temporaryLine + } + } + + const textHeight = numberOfLines * lineHeight + y = ctx.canvas.height - y - textHeight + } + + let currentLine = '' + + for (let index = 0; index < words.length; index += 1) { + const testLine = `${currentLine + words[index]} ` + const metrics = ctx.measureText(testLine) + const testWidth = metrics.width + + if (testWidth > maxWidth && index > 0) { + ctx.fillText(currentLine, x, y) + currentLine = `${words[index]} ` + y += lineHeight + } else { + currentLine = testLine + } + } + + ctx.fillText(currentLine, x, y) +} + +const calculateReadingTime = function (text) { + const wordsPerMinute = 200 + const textLength = text.split(' ').length + + if (textLength > 0) { + const value = Math.ceil(textLength / wordsPerMinute) + + if (value === 1) { + return `${value} minute` + } + + return `${value} minutes` + } +} + +const width = 1200 +const height = 720 +const border = 50 + +registerFont('fonts/Inter-Regular.otf', { family: 'InterRegular' }) +registerFont('fonts/Inter-Bold.otf', { family: 'InterBold' }) + +function writeImageFile(canvas, output) { + const buffer = canvas.toBuffer('image/png') + const directory = output.substring(0, output.lastIndexOf('/')) + + fs.mkdir(directory, { recursive: true }, error => { + if (error) { + throw error + } + + fs.writeFileSync(output, buffer) + }) +} + +module.exports = { + async createDefaultOpenGraphImage(text, output) { + // canvas + const canvas = createCanvas(width, height) + const ctx = canvas.getContext('2d') + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, width, height) + + // logo + const image = await loadImage(path.resolve(__dirname, 'logo.png')) + ctx.drawImage(image, border, border) + + // title + const lineHeight = 110 + ctx.textBaseline = 'top' + ctx.fillStyle = '#000000' + ctx.font = '70pt InterBold' + multilineText(ctx, text, border, border, width - 7 * border, lineHeight, 'bottom') + + writeImageFile(canvas, output) + }, + async createSpecificOpenGraphImage(text, content = '', output) { + const readingTime = calculateReadingTime(content) + + // canvas + const canvas = createCanvas(width, height) + const ctx = canvas.getContext('2d') + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, width, height) + + // logo + const image = await loadImage(path.resolve(__dirname, 'logo.png')) + ctx.drawImage(image, border, border) + + // title + const lineHeight = 90 + ctx.textBaseline = 'top' + ctx.fillStyle = '#000000' + ctx.font = '60pt InterBold' + multilineText(ctx, text, border, border + 80, width - 5 * border, lineHeight, 'bottom') + + // reading time + ctx.textBaseline = 'bottom' + ctx.fillStyle = '#666666' + ctx.font = '32pt InterRegular' + ctx.fillText(readingTime, border, height - border) + + writeImageFile(canvas, output) + }, +} diff --git a/docs/utilities/opengraph-images/logo.png b/docs/utilities/opengraph-images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..419c5c8368d6c66a23b630741a46ad41ed124e16 GIT binary patch literal 1801 zcmV+k2ln`hP)oHoF3Fz7J0n99j z86XQI`$S>B2`ywE})Z-nc(Y`HtxzJ=Y2_jDx19|28uJwrBn^>08%> z;}0-H^A@jXVS0TDX7bccECK&CI~C^SDy4858VnLUH*XH!44ow#OlMIZ5{Yp*CbQ$1 z>e`wWrad6)vcWj;06F3TVw+gxxymsw1k!ui1mXiHW$m@XYkee|DbVizWUkq!8GU9~ zCjAoHBc@Eb3Sk7&*40H7MLBIyG^ zZ;?KO?xg=>90Xp+o`s+z4*wxNE41s|)UKra-!SIf8}sHqo-(Pp!0`-_e@eMQdngat z+qW2=wu6J90sIkZIYqaAEud!;+Q0fWKqTf(o2O|E)zRPz;Yq6;d57etK?i{_)LC-8 z)G>8k*ND@l%4?fn5^3qvRS|YO8E=v^XCZLb+x%dcdyq9VXxWJkxxeC^H~`89q!50nA!jl@Du8&z7M@m zUGIA4&yjwBWbi`|8lM~bhSne(%%9Jg-zH}l3WPW3Ds!`GmLantM|ylu>JJE$BfYyf z4_v2qqP}z8aQ*BM7U{%S90y-oq-*MIsht`zJuL^@*cD|{De2FWmNu-F*7D(%1yNFEZyQd-(E2Q|;iS>{i z0<;Mjdddp;UH3z^m#^Gwgpo+gBC<@MZbL*E9~@KLNe@N6`obrkkXBcl8~F8>p1cC# zFDYC)^2tCRI^W@Ypj`LRnKATjz|;eTWbMdTg^c_1-JzSVF?C%8@>6|X=8QnxH+U`` zd1XYtro&S2BJ^$iW<_R)&$gzu z5YJ`9eYzg)CH+!55NLKS&<(1CqJL}?iyEGkjPW?^n@ZO)D4;t|OuM9XQ@WsflWDVQ zkw+o^w@3=~uPZ%h$z)n19vT=K(npq4TfO=sh5&RBZSJ1ky^tY1HGgyD^G)L}a!;O> zNppobbBj8hKzlNDtEH7`k1p1=6({|8zZf23Od}qkBO0?TU~8(qr;Wau_~j|v4CC(7gnAxXrAf1 zMgS=2qGv|N7tNbtyoGQG1^&@XW)FncAes4lf&D>d)o-^O!;DGHB0U4c94UNYieWr- zb@NIL6XrqY6mQZnlcDPXDWLtzbsv0J?5~Gpn90y*qcArju-mY*H-kbb^xx28m_a!~ rdT5XcBlW?epf~#H@0