Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/node
- //
- // generate_img.js version 0.0.99.20211018 (2021-10-18) written by cleemy desu wayo
- //
- // this is a personal work and is a PDS (Public Domain Software)
- //
- // --------
- // requirements: Node.js v10 or later, node-canvas
- // (and maybe this code works under only Linux)
- // --------
- //
- // see https://twitter.com/cleemy4545/status/1450188684045787142 and the thread
- //
- // old version: https://pastebin.com/T5VxqDGX
- //
- 'use strict';
- const fs = require('fs');
- const canvas = require('canvas');
- if ((process.argv[2] !== '--stdin') && (! fs.existsSync(process.argv[2]))) {
- console.log('please specify a valid file name (or specify "--stdin")');
- process.exit(1);
- }
- let dirImg = './';
- let dirTextimg = './';
- let isDirTextimgOverridden = false;
- let mainCanvas = null;
- let mainCanvasContext = null;
- let pos = new Map([['x', 0], ['y', 0]]);
- let margin = new Map([['x', 0], ['y', 0]]);
- let step = new Map([['x', 1], ['y', 0]]);
- let textSize = 16;
- let textColor = [];
- let textFont = 'unifont';
- let textStep = 1;
- //
- // pasteImg(params): draw new image
- //
- const pasteImg = function(params) {
- let imgPiece = new canvas.Image;
- imgPiece.src = params.get('src');
- mainCanvasContext.drawImage(imgPiece, params.get('x'), params.get('y'));
- return [imgPiece.width, imgPiece.height];
- }
- //
- // parseArgs(sourceStr): parse sourceStr and return an Array
- //
- // '(100,100,20,#ffffff)' --> return ['100', '100', '20', '#ffffff']
- // '(100)' --> return ['100']
- // ' (100) ' --> return ['100']
- // '( 100 , 100 )' --> return ['100', '100']
- // '( 100 , 10 0 )' --> return ['100', '10 0']
- // '(,,20)' --> return ['', '', '20']
- // '(20,,)' --> return ['20', '', '']
- // '(,,,)' --> return ['', '', '', '']
- // '( , , , )' --> return ['', '', '', '']
- // '()' --> return ['']
- // '( )' --> return ['']
- // '' --> return []
- // '(())' --> return ['()']
- // '(100,100' (invalid string) --> return []
- // [100, 100] (not string) --> return []
- //
- const parseArgs = function(sourceStr) {
- if (Object.prototype.toString.call(sourceStr) !== '[object String]') {
- return [];
- }
- const args_str = sourceStr.trim().replace(/^\((.*)\)$/, '$1'); // ' (100,100) ' --> '100,100'
- if (args_str === sourceStr) {
- return [];
- }
- return args_str.split(',').map(s => s.trim());
- }
- //
- // properFilePath(filePath, baseFilePath): return a proper file path
- //
- // this ignores base_file_path_src if file_path starts with '/', 'file:///',
- // 'https://', 'ftp://' or some such
- //
- const properFilePath = function(filePath, baseFilePathSrc = '') {
- // has a control code?
- if (`${filePath}${baseFilePathSrc}`.match(/[\x00-\x1f\x7f-\x9f]/)) { return '' }
- let resultStr = '';
- if (filePath.startsWith('/') || filePath.startsWith('file:///')) {
- resultStr = filePath.replace(/^(file:)?\/+/, '/');
- } else if (filePath.match(/^[a-zA-Z]+:\/\//)) {
- resultStr = filePath.replace(/^([a-zA-Z]+):\/\/+/, '$1://');
- } else {
- let baseFilePath = baseFilePathSrc;
- if (baseFilePath === null || baseFilePath === undefined || baseFilePath === '') {
- baseFilePath = '.';
- } else if (baseFilePath.startsWith('/') || baseFilePath.startsWith('file:///')) {
- baseFilePath = baseFilePath.replace(/^(file:)?\/+/, '/');
- } else if (baseFilePath.match(/^[a-zA-Z]+:\/\//)) {
- baseFilePath = baseFilePath.replace(/^([a-zA-Z]+):\/\/+/, '$1://');
- } else {
- baseFilePath = './' + baseFilePath.replace(/^(\.\/+)+/, '');
- }
- resultStr = baseFilePath.replace(/\/+$/, '') + '/' + filePath.replace(/^(\.\/+)+/, '');
- }
- const filePrefixAllowlist = ['./', '/'];
- if (!filePrefixAllowlist.some(str => resultStr.startsWith(str))) {
- resultStr = '';
- }
- return resultStr;
- }
- let lines = [];
- if (process.argv[2] === '--stdin') {
- lines = fs.readFileSync('/dev/stdin', 'utf8').toString().split('\n');
- } else {
- lines = fs.readFileSync(process.argv[2], 'utf8').toString().split('\n');
- }
- lines.forEach(line => {
- let m = null;
- if (m = line.match(/^dir *: *([-_a-zA-Z0-9./%:]+) *$/)) {
- dirImg = properFilePath(m[1]);
- if (!isDirTextimgOverridden) { dirTextimg = dirImg }
- } else if (m = line.match(/^dir *\( *textimg *\) *: *([-_a-zA-Z0-9./%:]+) *$/)) {
- dirTextimg = properFilePath(m[1]);
- isDirTextimgOverridden = true;
- } else if (m = line.match(/^bg *: *([-_a-zA-Z0-9./%:]+) *$/)) {
- const bgSrc = properFilePath(m[1], dirImg);
- if (! fs.existsSync(bgSrc)) { return false }
- let imgPiece = new canvas.Image;
- imgPiece.src = bgSrc;
- mainCanvas = canvas.createCanvas(imgPiece.width, imgPiece.height);
- mainCanvasContext = mainCanvas.getContext('2d');
- mainCanvasContext.drawImage(imgPiece, 0, 0);
- } else if ((m = line.match(/^paste *(\(.*?\))? *: *([-_a-zA-Z0-9./%:]+) *$/)) && mainCanvas) {
- const filePath = properFilePath(m[2], dirImg);
- if (! fs.existsSync(filePath)) { return false }
- let pasteParams = new Map([['src', filePath ],
- ['x', pos.get('x')],
- ['y', pos.get('y')]]);
- const pasteArgs = parseArgs(m[1]);
- const argsLength = pasteArgs.length;
- if (argsLength >= 1) {
- if (pasteArgs[0].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
- pasteParams.set('x', pos.get('x') + parseFloat(pasteArgs[0]));
- }
- if ((argsLength >= 2) && pasteArgs[1].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
- pasteParams.set('y', pos.get('y') + parseFloat(pasteArgs[1]));
- }
- }
- let newImgPieceSize = { x: 0, y: 0 };
- [newImgPieceSize['x'], newImgPieceSize['y']] = pasteImg(pasteParams);
- ['x', 'y'].forEach(axis => {
- pos.set(axis, pos.get(axis) + newImgPieceSize[axis] * step.get(axis) + margin.get(axis));
- });
- } else if ((m = line.match(/^textimg *(\(.*?\))? *: *(.+)$/)) && mainCanvas) {
- let xPosBuff = pos.get('x'); // "textimg:" does not change pos
- let yPosBuff = pos.get('y'); // "textimg:" does not change pos
- const textimgArgs = parseArgs(m[1]);
- const argsLength = textimgArgs.length;
- if (argsLength >= 1) {
- if (textimgArgs[0].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
- xPosBuff += parseFloat(textimgArgs[0]);
- }
- if ((argsLength >= 2) && textimgArgs[1].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
- yPosBuff += parseFloat(textimgArgs[1]);
- }
- }
- Array.from(m[2]).forEach(c => {
- let filePath = properFilePath('textimg_' + c.codePointAt(0).toString(16) + '.png', dirTextimg);
- if (! fs.existsSync(filePath)) { return false }
- let pasteParams = new Map([['src', filePath],
- ['x', xPosBuff],
- ['y', yPosBuff]]);
- let newImgPieceSize = { x: 0, y: 0 };
- [newImgPieceSize['x'], newImgPieceSize['y']] = pasteImg(pasteParams);
- xPosBuff = xPosBuff + newImgPieceSize['x']; // "textimg:" does not change pos
- });
- } else if ((m = line.match(/^text *(\(.*?\))? *: *(.+)$/)) && mainCanvas) {
- let textParams = new Map([['body', m[2] ],
- ['x', pos.get('x') ],
- ['y', pos.get('y') ],
- ['size', parseFloat(textSize)],
- ['color', textColor ]]);
- const textArgs = parseArgs(m[1]);
- const argsLength = textArgs.length;
- if (argsLength >= 1) {
- if (textArgs[0].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
- textParams.set('x', pos.get('x') + parseFloat(textArgs[0]));
- }
- if ((argsLength >= 2) && textArgs[1].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
- textParams.set('y', pos.get('y') + parseFloat(textArgs[1]));
- }
- if ((argsLength >= 3) && textArgs[2].match(/^[0-9]+(\.[0-9]+)?$/)) {
- textParams.set('size', parseFloat(textArgs[2]));
- }
- if ((argsLength >= 4) && textArgs[3].startsWith('#')) {
- const matched_rgb = textArgs[3].match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/)
- if (matched_rgb !== null) {
- const matched_rgb_array = matched_rgb.slice(1, 4).map(s => parseInt(s, 16));
- textParams.set('color', matched_rgb_array);
- }
- }
- }
- mainCanvasContext.font = textParams.get('size').toString() + 'px ' + textFont;
- mainCanvasContext.fillStyle = 'rgb(' + textParams.get('color').join(',') + ')';
- mainCanvasContext.fillText(textParams.get('body'),
- textParams.get('x'),
- textParams.get('y') + textParams.get('size'));
- pos.set('y', pos.get('y') + parseFloat(textSize) * parseFloat(textStep) + margin.get('y'));
- } else if (m = line.match(/^text_([a-z]+) *: *(.+?) *$/)) {
- if (m[1] === 'font') {
- textFont = m[2];
- } else if (m[1] === 'size') {
- textSize = parseFloat(m[2]);
- } else if (m[1] === 'color') {
- let regex;
- let radix;
- if (m[2].startsWith('#')) {
- regex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}) *$/;
- radix = 16;
- } else {
- regex = /^([0-9]+) *, *([0-9]+) *, *([0-9]+) *$/;
- radix = 10;
- }
- const matched_rgb = m[2].match(regex);
- if (matched_rgb !== null) {
- textColor = matched_rgb.slice(1, 4).map(s => parseInt(s, radix));
- }
- } else if (m[1] === 'step') {
- textStep = parseFloat(m[2]);
- }
- } else if (m = line.match(/^(blank|margin|pos|step) *(\(.*?\))? *: *([-0-9.]+?) *$/)) {
- const args = parseArgs(m[2]);
- if ((args.length !== 0) && (['x', 'y', ''].indexOf(args[0]) < 0)) {
- return false;
- }
- let axes = ['x', 'y'];
- if (['x', 'y'].indexOf(args[0]) >= 0) {
- axes = [args[0]];
- }
- axes.forEach(axis => {
- switch (m[1]) {
- case 'blank' : pos.set (axis, pos.get(axis) + parseFloat(m[3])); break;
- case 'margin' : margin.set (axis, parseFloat(m[3])); break;
- case 'pos' : pos.set (axis, parseFloat(m[3])); break;
- case 'step' : step.set (axis, parseFloat(m[3])); break;
- }
- });
- }
- });
- if (mainCanvas === null) {
- console.log('error: no background image');
- process.exit(2);
- }
- mainCanvas.createPNGStream().pipe(fs.createWriteStream('generated.png'));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement