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 aac726b9..00000000 Binary files a/docs/static/og-image.png and /dev/null differ diff --git a/docs/utilities/opengraph-images/index.js b/docs/utilities/opengraph-images/index.js new file mode 100644 index 00000000..408bb389 --- /dev/null +++ b/docs/utilities/opengraph-images/index.js @@ -0,0 +1,140 @@ +const fs = require('fs') +const path = require('path') +const { createCanvas, registerFont, loadImage } = require('canvas') + +const multilineText = function ( + ctx, + text, + x, + y, + maxWidth, + lineHeight, + start = 'top', +) { + const words = text.split(' ') + + let numberOfLines = 1 + + if (start === 'bottom') { + let currentLine = '' + + for (let index = 0; index < words.length; index += 1) { + const temporaryLine = `${currentLine + words[index]} ` + const temporaryWidth = ctx.measureText(temporaryLine).width + + if (temporaryWidth > 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 00000000..419c5c83 Binary files /dev/null and b/docs/utilities/opengraph-images/logo.png differ