Advertisement
Guest User

generate_img.js 0.0.99.20211018 (2021-10-18) by cleemy desu wayo

a guest
Oct 18th, 2021
78
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/node
  2. //
  3. // generate_img.js version 0.0.99.20211018 (2021-10-18) written by cleemy desu wayo
  4. //
  5. // this is a personal work and is a PDS (Public Domain Software)
  6. //
  7. // --------
  8. // requirements: Node.js v10 or later, node-canvas
  9. // (and maybe this code works under only Linux)
  10. // --------
  11. //
  12. // see https://twitter.com/cleemy4545/status/1450188684045787142 and the thread
  13. //
  14. // old version: https://pastebin.com/T5VxqDGX
  15. //
  16.  
  17. 'use strict';
  18. const fs     = require('fs');
  19. const canvas = require('canvas');
  20.  
  21. if ((process.argv[2] !== '--stdin') && (! fs.existsSync(process.argv[2]))) {
  22.   console.log('please specify a valid file name (or specify "--stdin")');
  23.   process.exit(1);
  24. }
  25.  
  26. let dirImg                 = './';
  27. let dirTextimg             = './';
  28. let isDirTextimgOverridden = false;
  29. let mainCanvas        = null;
  30. let mainCanvasContext = null;
  31. let pos               = new Map([['x', 0], ['y', 0]]);
  32. let margin            = new Map([['x', 0], ['y', 0]]);
  33. let step              = new Map([['x', 1], ['y', 0]]);
  34. let textSize          = 16;
  35. let textColor         = [];
  36. let textFont          = 'unifont';
  37. let textStep          = 1;
  38.  
  39. //
  40. // pasteImg(params): draw new image
  41. //
  42. const pasteImg = function(params) {
  43.   let imgPiece = new canvas.Image;
  44.   imgPiece.src = params.get('src');
  45.  
  46.   mainCanvasContext.drawImage(imgPiece, params.get('x'), params.get('y'));
  47.  
  48.   return [imgPiece.width, imgPiece.height];
  49. }
  50.  
  51. //
  52. // parseArgs(sourceStr): parse sourceStr and return an Array
  53. //
  54. // '(100,100,20,#ffffff)'      --> return ['100', '100', '20', '#ffffff']
  55. // '(100)'                     --> return ['100']
  56. // '   (100)   '               --> return ['100']
  57. // '(   100  ,   100   )'      --> return ['100', '100']
  58. // '(   100  ,   10 0  )'      --> return ['100', '10 0']
  59. // '(,,20)'                    --> return ['', '', '20']
  60. // '(20,,)'                    --> return ['20', '', '']
  61. // '(,,,)'                     --> return ['', '', '', '']
  62. // '( , , , )'                 --> return ['', '', '', '']
  63. // '()'                        --> return ['']
  64. // '( )'                       --> return ['']
  65. // ''                          --> return []
  66. // '(())'                      --> return ['()']
  67. // '(100,100' (invalid string) --> return []
  68. // [100, 100] (not string)     --> return []
  69. //
  70. const parseArgs = function(sourceStr) {
  71.   if (Object.prototype.toString.call(sourceStr) !== '[object String]') {
  72.     return [];
  73.   }
  74.  
  75.   const args_str = sourceStr.trim().replace(/^\((.*)\)$/, '$1');  // ' (100,100) ' --> '100,100'
  76.   if (args_str === sourceStr) {
  77.     return [];
  78.   }
  79.  
  80.   return args_str.split(',').map(s => s.trim());
  81. }
  82.  
  83. //
  84. // properFilePath(filePath, baseFilePath): return a proper file path
  85. //
  86. // this ignores base_file_path_src if file_path starts with '/', 'file:///',
  87. // 'https://', 'ftp://' or some such
  88. //
  89. const properFilePath = function(filePath, baseFilePathSrc = '') {
  90.  
  91.   // has a control code?
  92.   if (`${filePath}${baseFilePathSrc}`.match(/[\x00-\x1f\x7f-\x9f]/)) { return '' }
  93.  
  94.   let resultStr = '';
  95.  
  96.   if (filePath.startsWith('/') || filePath.startsWith('file:///')) {
  97.     resultStr = filePath.replace(/^(file:)?\/+/, '/');
  98.   } else if (filePath.match(/^[a-zA-Z]+:\/\//)) {
  99.     resultStr = filePath.replace(/^([a-zA-Z]+):\/\/+/, '$1://');
  100.   } else {
  101.  
  102.     let baseFilePath = baseFilePathSrc;
  103.  
  104.     if (baseFilePath === null || baseFilePath === undefined || baseFilePath === '') {
  105.       baseFilePath = '.';
  106.     } else if (baseFilePath.startsWith('/') || baseFilePath.startsWith('file:///')) {
  107.       baseFilePath = baseFilePath.replace(/^(file:)?\/+/, '/');
  108.     } else if (baseFilePath.match(/^[a-zA-Z]+:\/\//)) {
  109.       baseFilePath = baseFilePath.replace(/^([a-zA-Z]+):\/\/+/, '$1://');
  110.     } else {
  111.       baseFilePath = './' + baseFilePath.replace(/^(\.\/+)+/, '');
  112.     }
  113.  
  114.     resultStr = baseFilePath.replace(/\/+$/, '') + '/' + filePath.replace(/^(\.\/+)+/, '');
  115.   }
  116.  
  117.   const filePrefixAllowlist = ['./', '/'];
  118.   if (!filePrefixAllowlist.some(str => resultStr.startsWith(str))) {
  119.     resultStr = '';
  120.   }
  121.  
  122.   return resultStr;
  123. }
  124.  
  125. let lines = [];
  126. if (process.argv[2] === '--stdin') {
  127.   lines = fs.readFileSync('/dev/stdin', 'utf8').toString().split('\n');
  128. } else {
  129.   lines = fs.readFileSync(process.argv[2], 'utf8').toString().split('\n');
  130. }
  131.  
  132. lines.forEach(line => {
  133.   let m = null;
  134.  
  135.   if (m = line.match(/^dir *: *([-_a-zA-Z0-9./%:]+) *$/)) {
  136.     dirImg = properFilePath(m[1]);
  137.     if (!isDirTextimgOverridden) { dirTextimg = dirImg }
  138.  
  139.   } else if (m = line.match(/^dir *\( *textimg *\) *: *([-_a-zA-Z0-9./%:]+) *$/)) {
  140.     dirTextimg = properFilePath(m[1]);
  141.     isDirTextimgOverridden = true;
  142.  
  143.   } else if (m = line.match(/^bg *: *([-_a-zA-Z0-9./%:]+) *$/)) {
  144.     const bgSrc = properFilePath(m[1], dirImg);
  145.     if (! fs.existsSync(bgSrc)) { return false }
  146.  
  147.     let imgPiece = new canvas.Image;
  148.     imgPiece.src = bgSrc;
  149.     mainCanvas = canvas.createCanvas(imgPiece.width, imgPiece.height);
  150.     mainCanvasContext = mainCanvas.getContext('2d');
  151.     mainCanvasContext.drawImage(imgPiece, 0, 0);
  152.  
  153.   } else if ((m = line.match(/^paste *(\(.*?\))? *: *([-_a-zA-Z0-9./%:]+) *$/)) && mainCanvas) {
  154.  
  155.     const filePath = properFilePath(m[2], dirImg);
  156.     if (! fs.existsSync(filePath)) { return false }
  157.  
  158.     let pasteParams = new Map([['src', filePath    ],
  159.                                ['x',   pos.get('x')],
  160.                                ['y',   pos.get('y')]]);
  161.  
  162.     const pasteArgs  = parseArgs(m[1]);
  163.     const argsLength = pasteArgs.length;
  164.     if (argsLength >= 1) {
  165.       if (pasteArgs[0].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
  166.         pasteParams.set('x', pos.get('x') + parseFloat(pasteArgs[0]));
  167.       }
  168.       if ((argsLength >= 2) && pasteArgs[1].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
  169.         pasteParams.set('y', pos.get('y') + parseFloat(pasteArgs[1]));
  170.       }
  171.     }
  172.  
  173.     let newImgPieceSize = { x: 0, y: 0 };
  174.     [newImgPieceSize['x'], newImgPieceSize['y']] = pasteImg(pasteParams);
  175.  
  176.     ['x', 'y'].forEach(axis => {
  177.       pos.set(axis, pos.get(axis) + newImgPieceSize[axis] * step.get(axis) + margin.get(axis));
  178.     });
  179.  
  180.   } else if ((m = line.match(/^textimg *(\(.*?\))? *: *(.+)$/)) && mainCanvas) {
  181.  
  182.     let xPosBuff = pos.get('x');      // "textimg:" does not change pos
  183.     let yPosBuff = pos.get('y');      // "textimg:" does not change pos
  184.  
  185.     const textimgArgs = parseArgs(m[1]);
  186.     const argsLength = textimgArgs.length;
  187.     if (argsLength >= 1) {
  188.       if (textimgArgs[0].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
  189.         xPosBuff += parseFloat(textimgArgs[0]);
  190.       }
  191.       if ((argsLength >= 2) && textimgArgs[1].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
  192.         yPosBuff += parseFloat(textimgArgs[1]);
  193.       }
  194.     }
  195.  
  196.     Array.from(m[2]).forEach(c => {
  197.  
  198.       let filePath = properFilePath('textimg_' + c.codePointAt(0).toString(16) + '.png', dirTextimg);
  199.       if (! fs.existsSync(filePath)) { return false }
  200.  
  201.       let pasteParams = new Map([['src', filePath],
  202.                                  ['x',   xPosBuff],
  203.                                  ['y',   yPosBuff]]);
  204.  
  205.       let newImgPieceSize = { x: 0, y: 0 };
  206.       [newImgPieceSize['x'], newImgPieceSize['y']] = pasteImg(pasteParams);
  207.  
  208.       xPosBuff = xPosBuff + newImgPieceSize['x'];      // "textimg:" does not change pos
  209.     });
  210.  
  211.   } else if ((m = line.match(/^text *(\(.*?\))? *: *(.+)$/)) && mainCanvas) {
  212.     let textParams = new Map([['body',  m[2]                ],
  213.                               ['x',     pos.get('x')        ],
  214.                               ['y',     pos.get('y')        ],
  215.                               ['size',  parseFloat(textSize)],
  216.                               ['color', textColor           ]]);
  217.  
  218.     const textArgs   = parseArgs(m[1]);
  219.     const argsLength = textArgs.length;
  220.     if (argsLength >= 1) {
  221.       if (textArgs[0].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
  222.         textParams.set('x', pos.get('x') + parseFloat(textArgs[0]));
  223.       }
  224.       if ((argsLength >= 2) && textArgs[1].match(/^-?[0-9]+(\.[0-9]+)?$/)) {
  225.         textParams.set('y', pos.get('y') + parseFloat(textArgs[1]));
  226.       }
  227.       if ((argsLength >= 3) && textArgs[2].match(/^[0-9]+(\.[0-9]+)?$/)) {
  228.         textParams.set('size', parseFloat(textArgs[2]));
  229.       }
  230.       if ((argsLength >= 4) && textArgs[3].startsWith('#')) {
  231.         const matched_rgb = textArgs[3].match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/)
  232.         if (matched_rgb !== null) {
  233.           const matched_rgb_array = matched_rgb.slice(1, 4).map(s => parseInt(s, 16));
  234.           textParams.set('color', matched_rgb_array);
  235.         }
  236.       }
  237.     }
  238.  
  239.     mainCanvasContext.font = textParams.get('size').toString() + 'px ' + textFont;
  240.     mainCanvasContext.fillStyle = 'rgb(' + textParams.get('color').join(',') + ')';
  241.     mainCanvasContext.fillText(textParams.get('body'),
  242.                                textParams.get('x'),
  243.                                textParams.get('y') + textParams.get('size'));
  244.     pos.set('y', pos.get('y') + parseFloat(textSize) * parseFloat(textStep) + margin.get('y'));
  245.  
  246.   } else if (m = line.match(/^text_([a-z]+) *: *(.+?) *$/)) {
  247.     if (m[1] === 'font') {
  248.       textFont = m[2];
  249.     } else if (m[1] === 'size') {
  250.       textSize = parseFloat(m[2]);
  251.     } else if (m[1] === 'color') {
  252.       let regex;
  253.       let radix;
  254.       if (m[2].startsWith('#')) {
  255.         regex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}) *$/;
  256.         radix = 16;
  257.       } else {
  258.         regex = /^([0-9]+) *, *([0-9]+) *, *([0-9]+) *$/;
  259.         radix = 10;
  260.       }
  261.       const matched_rgb = m[2].match(regex);
  262.       if (matched_rgb !== null) {
  263.         textColor = matched_rgb.slice(1, 4).map(s => parseInt(s, radix));
  264.       }
  265.     } else if (m[1] === 'step') {
  266.       textStep = parseFloat(m[2]);
  267.     }
  268.  
  269.   } else if (m = line.match(/^(blank|margin|pos|step) *(\(.*?\))? *: *([-0-9.]+?) *$/)) {
  270.     const args = parseArgs(m[2]);
  271.     if ((args.length !== 0) && (['x', 'y', ''].indexOf(args[0]) < 0)) {
  272.       return false;
  273.     }
  274.  
  275.     let axes = ['x', 'y'];
  276.     if (['x', 'y'].indexOf(args[0]) >= 0) {
  277.       axes = [args[0]];
  278.     }
  279.  
  280.     axes.forEach(axis => {
  281.       switch (m[1]) {
  282.         case 'blank'  : pos.set    (axis, pos.get(axis) + parseFloat(m[3])); break;
  283.         case 'margin' : margin.set (axis, parseFloat(m[3])); break;
  284.         case 'pos'    : pos.set    (axis, parseFloat(m[3])); break;
  285.         case 'step'   : step.set   (axis, parseFloat(m[3])); break;
  286.       }
  287.     });
  288.   }
  289. });
  290.  
  291. if (mainCanvas === null) {
  292.   console.log('error: no background image');
  293.   process.exit(2);
  294. }
  295.  
  296. mainCanvas.createPNGStream().pipe(fs.createWriteStream('generated.png'));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement