From d32f77aa872b5a98ad03a346fdfca5c74f1e40ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Ronvel?= Date: Fri, 28 May 2021 11:11:30 +0200 Subject: [PATCH] Add a browser build (#176) --- CHANGELOG | 6 + Makefile | 13 + browser/termkit.js | 61660 +++++++++++++++++++++++++++++++++++++++ browser/termkit.min.js | 1 + lib/browser.js | 33 + lib/image.js | 7 +- package.json | 6 +- 7 files changed, 61720 insertions(+), 6 deletions(-) create mode 100644 browser/termkit.js create mode 100644 browser/termkit.min.js create mode 100644 lib/browser.js diff --git a/CHANGELOG b/CHANGELOG index f04a9fca..5bec8ccf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ +v2.1.4 +------ + +Add a browser build (#176) + + v2.1.3 ------ diff --git a/Makefile b/Makefile index ce4040e0..16440e12 100644 --- a/Makefile +++ b/Makefile @@ -28,17 +28,30 @@ publish: log/npm-publish.log log/github-push.log # Clean temporary things, or things that can be automatically regenerated clean: clean-all +# browserify +browser: browser/termkit.js browser/termkit.min.js + # Variables MOCHA=./node_modules/mocha/bin/mocha JSHINT=./node_modules/jshint/bin/jshint --verbose +BROWSERIFY=browserify +UGLIFY=uglifyjs # Files rules +# Build the browser lib +browser/termkit.js: lib/*.js lib/*/*.js + ${BROWSERIFY} lib/browser.js -s TerminalKit -i get-pixels -i child_pty -o browser/termkit.js + +# Build the browser minified lib +browser/termkit.min.js: browser/termkit.js + ${UGLIFY} browser/termkit.js -o browser/termkit.min.js -m + # JsHint STDOUT test log/jshint.log: log/npm-dev-install.log lib/*.js lib/colorScheme/*.json lib/termconfig/*.js ${JSHINT} lib/*.js lib/colorScheme/*.json lib/termconfig/*.js | tee log/jshint.log ; exit $${PIPESTATUS[0]} diff --git a/browser/termkit.js b/browser/termkit.js new file mode 100644 index 00000000..43d687d7 --- /dev/null +++ b/browser/termkit.js @@ -0,0 +1,61660 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.TerminalKit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i termkit.chroma( color.code ) ) ; + + register = 16 ; + + for ( z = 0 ; z >= -2 ; z -- ) { + if ( z > 0 ) { + saturationMark = '!'.repeat( z ) ; + } + else if ( z < 0 ) { + saturationMark = '~'.repeat( -z ) ; + } + else { + saturationMark = '' ; + } + + for ( j = 2 ; j >= -3 ; j -- ) { + + if ( j > 0 ) { + lightnessMark = '+'.repeat( j ) ; + } + else if ( j < 0 ) { + lightnessMark = '-'.repeat( -j ) ; + } + else { + lightnessMark = '' ; + } + + suffix = saturationMark + lightnessMark ; + + for ( i = 0 ; i < 12 ; i ++ ) { + chromaColor = this.clStep( baseChromaColors[ i ] , z , j ) ; + this.addColor( register , chromaColor , this.adaptivePaletteDef[ i ].names , '@' , suffix ) ; + register ++ ; + } + } + + } +} ; + + + +Palette.prototype.generateExtra = function() { + if ( this.system ) { return ; } + + var i , register ; + + register = 232 ; + + for ( i = 0 ; i < 13 && i < this.extraPaletteDef.length ; i ++ ) { + this.addColor( register , termkit.chroma( this.extraPaletteDef[ i ].code ) , this.extraPaletteDef[ i ].names , '*' ) ; + register ++ ; + } +} ; + + + +const grayscaleNames = [ + [ 'black' ] , + [ 'darkest-gray' ] , + [ 'darker-gray' ] , + [ 'dark-gray' ] , + [ 'dark-medium-gray' ] , + [ 'medium-gray' , 'gray' ] , + [ 'light-medium-gray' ] , + [ 'light-gray' ] , + [ 'lighter-gray' ] , + [ 'lightest-gray' ] , + [ 'white' ] +] ; + +Palette.prototype.generateGrayscale = function() { + if ( this.system ) { return ; } + + var i , register , chromaColor ; + + register = 245 ; + + for ( i = 0 ; i <= 10 ; i ++ ) { + chromaColor = termkit.chroma( 0 , 0 , 10 * i , 'hcl' ) ; + this.addColor( register , chromaColor , grayscaleNames[ i ] , '@' ) ; + register ++ ; + } +} ; + + + +Palette.prototype.getRgb = function( register ) { + var chromaColor = this.chromaColors[ register ] ; + if ( ! chromaColor ) { return null ; } + var [ r , g , b ] = chromaColor.rgb() ; + return { r , g , b } ; +} ; + + + +Palette.prototype.addColor = function( register , chromaColor , names , prefix = '' , suffix = '' ) { + var targetRegister , + [ r , g , b ] = chromaColor.rgb() ; + + this.chromaColors[ register ] = chromaColor ; + + if ( this.term.support.trueColor ) { + this.escape[ register ] = this.term.str.colorRgb( r , g , b ) ; + this.bgEscape[ register ] = this.term.str.bgColorRgb( r , g , b ) ; + } + else if ( this.term.support['256colors'] ) { + targetRegister = this.term.registerForRgb( + { r , g , b } , + r === g && g === b ? 232 : 0 , // minRegister is the start of the grayscale if r=g=b + 255 + ) ; + + this.escape[ register ] = this.term.str.color256( targetRegister ) ; + this.bgEscape[ register ] = this.term.str.bgColor256( targetRegister ) ; + } + else { + targetRegister = this.term.registerForRgb( { r , g , b } , 0 , 15 ) ; + this.escape[ register ] = this.term.str.color256( targetRegister ) ; + this.bgEscape[ register ] = this.term.str.bgColor256( targetRegister ) ; + } + + names.forEach( name => { + var strippedName = prefix + name.replace( /-/g , '' ) + suffix ; + name = prefix + name + suffix ; + this.colorIndex[ name ] = register ; + + if ( strippedName !== name ) { + this.colorIndex[ strippedName ] = register ; + } + } ) ; +} ; + + + +const FIX_STEP = 1.1 ; + +Palette.prototype.clStep = function( chromaColor , cAdjust , lAdjust , fixRgb = true ) { + var c , l , rgb , avg , sortedChannels , preserveLOverC ; + + if ( ! cAdjust && ! lAdjust ) { return chromaColor ; } + + c = chromaColor.get( 'hcl.c' ) ; + l = chromaColor.get( 'hcl.l' ) ; + + /* + c += c * cAdjust / 3 ; + l += l * lAdjust / 4 ; + //*/ + + c *= ( cAdjust > 0 ? 1.6 : 1.7 ) ** cAdjust ; + l *= ( lAdjust > 0 ? 1.2 : 1.35 ) ** lAdjust ; + + chromaColor = chromaColor.set( 'hcl.c' , c ).set( 'hcl.l' , l ) ; + + if ( ! fixRgb || ! chromaColor.clipped ) { return chromaColor ; } + + // RGB is clipped and should be fixed. + // The most critical part is when the hue get changed, since it's arguably the most important information. + // Lightness is somewhat important too, but less than hue and a bit more than the Chroma. + // Chroma will be preserved if the adjustement is greater on it than on lightness. + + //preserveLOverC = Math.abs( lAdjust ) >= Math.abs( cAdjust ) ; + preserveLOverC = Math.abs( lAdjust ) >= cAdjust ; + + for ( ;; ) { + // chromaColor.clipped is not reliable since integer rounding counts as clipping... + rgb = chromaColor._rgb._unclipped ; + rgb.length = 3 ; + + if ( rgb.every( channel => channel > -5 && channel < 260 ) ) { return chromaColor ; } + + sortedChannels = [ ... rgb ].sort() ; + + //console.log( "Clipped!" , rgb , chromaColor.rgb() ) ; + + if ( sortedChannels[ 2 ] >= 256 ) { + // Clipping will affect hue! + avg = ( sortedChannels[ 0 ] + sortedChannels[ 1 ] + sortedChannels[ 2 ] ) / 3 ; + + if ( preserveLOverC ) { + // Desaturate a bit and retry + c = chromaColor.get( 'hcl.c' ) ; + c /= FIX_STEP ; + chromaColor = chromaColor.set( 'hcl.c' , c ) ; + } + else { + // Darken a bit and retry + l = chromaColor.get( 'hcl.l' ) ; + l /= FIX_STEP ; + chromaColor = chromaColor.set( 'hcl.l' , l ) ; + } + + // It was too bright anyway, let it be clipped + if ( avg > 255 ) { return chromaColor ; } + } + else if ( sortedChannels[ 1 ] < 0 ) { + // Clipping will affect hue! + avg = ( sortedChannels[ 0 ] + sortedChannels[ 1 ] + sortedChannels[ 2 ] ) / 3 ; + + if ( preserveLOverC ) { + // Desaturate a bit and retry + c = chromaColor.get( 'hcl.c' ) ; + c /= FIX_STEP ; + chromaColor = chromaColor.set( 'hcl.c' , c ) ; + } + else { + // Lighten a bit and retry + l = chromaColor.get( 'hcl.l' ) ; + l *= FIX_STEP ; + chromaColor = chromaColor.set( 'hcl.l' , l ) ; + } + + // It was too dark anyway, let it be clipped + if ( avg < 0 ) { return chromaColor ; } + } + else { + // This clipping (lowest channel below 0) will not affect hue, only lightness, let it be clipped + return chromaColor ; + } + } +} ; + + +},{"./termkit.js":50}],2:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const termkit = require( './termkit.js' ) ; + + + +/* + Rect: rectangular region, clipping, iterators for blitters, etc... + + new Rect( xmin , ymin , xmax , ymax ) + new Rect( object ) having properties: xmin , ymin , xmax , ymax + new Rect( Terminal ) + new Rect( ScreenBuffer ) +*/ +function Rect( xmin , ymin , xmax , ymax ) { + var src = xmin ; + + this.xmin = 0 ; + this.xmax = 0 ; + this.ymin = 0 ; + this.ymax = 0 ; + this.width = 0 ; + this.height = 0 ; + this.isNull = true ; + + if ( src && ( typeof src === 'object' || typeof src === 'function' ) ) { + if ( src instanceof termkit.Terminal ) { + this.set( { + xmin: 1 , + ymin: 1 , + xmax: src.width , + ymax: src.height + } ) ; + } + else if ( src instanceof termkit.ScreenBuffer ) { + this.set( { + xmin: 0 , + ymin: 0 , + xmax: src.width - 1 , + ymax: src.height - 1 + } ) ; + } + else if ( src instanceof termkit.TextBuffer ) { + this.set( { + xmin: 0 , + ymin: 0 , + xmax: src.width - 1 , + ymax: src.height - 1 + } ) ; + } + else if ( src instanceof Rect ) { + this.set( src ) ; + } + else if ( src.xmin !== undefined || src.ymin !== undefined || src.xmax !== undefined || src.ymax !== undefined ) { + this.set( { + xmin: src.xmin !== undefined ? src.xmin : 0 , + ymin: src.ymin !== undefined ? src.ymin : 0 , + xmax: src.xmax !== undefined ? src.xmax : 1 , + ymax: src.ymax !== undefined ? src.ymax : 1 + } ) ; + } + else if ( src.x !== undefined || src.y !== undefined || src.width !== undefined || src.height !== undefined ) { + this.set( { + xmin: src.x !== undefined ? src.x : 0 , + ymin: src.y !== undefined ? src.y : 0 , + xmax: src.width !== undefined ? src.x + src.width - 1 : 1 , + ymax: src.height !== undefined ? src.y + src.height - 1 : 1 + } ) ; + } + } + else { + this.set( { + xmin: xmin !== undefined ? xmin : 0 , + ymin: ymin !== undefined ? ymin : 0 , + xmax: xmax !== undefined ? xmax : 1 , + ymax: ymax !== undefined ? ymax : 1 + } ) ; + } +} + +module.exports = Rect ; + + + +// Backward compatibility +Rect.create = ( ... args ) => new Rect( ... args ) ; + + + +Rect.prototype.set = function( data ) { + if ( data.xmin !== undefined ) { this.xmin = Math.floor( data.xmin ) ; } + if ( data.xmax !== undefined ) { this.xmax = Math.floor( data.xmax ) ; } + if ( data.ymin !== undefined ) { this.ymin = Math.floor( data.ymin ) ; } + if ( data.ymax !== undefined ) { this.ymax = Math.floor( data.ymax ) ; } + + this.width = this.xmax - this.xmin + 1 ; + this.height = this.ymax - this.ymin + 1 ; + this.isNull = this.xmin > this.xmax || this.ymin > this.ymax ; +} ; + + + +// Set the reset size, keeping *min and adjusting *max +Rect.prototype.setSize = function( data ) { + if ( data.width !== undefined ) { + this.width = Math.floor( data.width ) ; + this.xmax = this.xmin + this.width - 1 ; + } + + if ( data.height !== undefined ) { + this.height = Math.floor( data.height ) ; + this.ymax = this.ymin + this.height - 1 ; + } + + this.isNull = this.xmin > this.xmax || this.ymin > this.ymax ; +} ; + + + +Rect.prototype.isInside = function( x , y ) { + return x >= this.xmin && x <= this.xmax && y >= this.ymin && y <= this.ymax ; +} ; + + + +// Clip the src according to the dst, offset* are offsets of the srcRect relative to the dst coordinate system +Rect.prototype.clip = function( dstRect , offsetX , offsetY , dstClipping ) { + var srcRect = this ; + + offsetX = offsetX || 0 ; + offsetY = offsetY || 0 ; + + srcRect.set( { + xmin: Math.max( srcRect.xmin , dstRect.xmin - offsetX ) , + ymin: Math.max( srcRect.ymin , dstRect.ymin - offsetY ) , + xmax: Math.min( srcRect.xmax , dstRect.xmax - offsetX ) , + ymax: Math.min( srcRect.ymax , dstRect.ymax - offsetY ) + } ) ; + + if ( dstClipping ) { + dstRect.set( { + xmin: Math.max( dstRect.xmin , srcRect.xmin + offsetX ) , + ymin: Math.max( dstRect.ymin , srcRect.ymin + offsetY ) , + xmax: Math.min( dstRect.xmax , srcRect.xmax + offsetX ) , + ymax: Math.min( dstRect.ymax , srcRect.ymax + offsetY ) + } ) ; + } + + return this ; +} ; + + + +// Merge with another Rect, enlarge the current Rect so that it includes the Rect argument +Rect.prototype.merge = function( rect ) { + this.set( { + xmin: Math.min( this.xmin , rect.xmin ) , + ymin: Math.min( this.ymin , rect.ymin ) , + xmax: Math.max( this.xmax , rect.xmax ) , + ymax: Math.max( this.ymax , rect.ymax ) + } ) ; + + return this ; +} ; + + + +/* + Given a srcRect, a dstRect, offsetX and offsetY, return an array of up to 4 objects consisting of the same properties + found in entry, wrapping the src into the dst, i.e. the src is always fully visible in the dst, it is just as if + the dst where circular + + Mandatory params: + * dstRect + * srcRect + * offsetX + * offsetY + Optionnal params: + * wrapOnly: 'x' , 'y' (only wrap along that axis) +*/ +Rect.wrappingRect = function( p ) { + var regions = [] , nw , ne , sw , se ; + + + // Originate, North-West region + nw = { + srcRect: new Rect( p.srcRect ) , + dstRect: new Rect( p.dstRect ) , + offsetX: p.offsetX , + offsetY: p.offsetY + } ; + + // Modulate offsets so they are in-range + if ( p.wrapOnly !== 'y' ) { + nw.offsetX = nw.offsetX % p.dstRect.width ; + if ( nw.offsetX < 0 ) { nw.offsetX += p.dstRect.width ; } + } + + if ( p.wrapOnly !== 'x' ) { + nw.offsetY = nw.offsetY % p.dstRect.height ; + if ( nw.offsetY < 0 ) { nw.offsetY += p.dstRect.height ; } + } + + // Mutual clipping + nw.srcRect.clip( nw.dstRect , nw.offsetX , nw.offsetY , true ) ; + if ( ! nw.srcRect.isNull ) { regions.push( nw ) ; } + + // Wrap-x North-Est region + if ( nw.srcRect.width < p.srcRect.width && p.wrapOnly !== 'y' ) { + ne = { + srcRect: new Rect( p.srcRect ) , + dstRect: new Rect( p.dstRect ) , + offsetX: nw.offsetX - p.dstRect.width , + offsetY: nw.offsetY + } ; + + // Mutual clipping + ne.srcRect.clip( ne.dstRect , ne.offsetX , ne.offsetY , true ) ; + if ( ! ne.srcRect.isNull ) { regions.push( ne ) ; } + } + + + // Wrap-y South-West region + if ( nw.srcRect.height < p.srcRect.height && p.wrapOnly !== 'x' ) { + sw = { + srcRect: new Rect( p.srcRect ) , + dstRect: new Rect( p.dstRect ) , + offsetX: nw.offsetX , + offsetY: nw.offsetY - p.dstRect.height + } ; + + // Mutual clipping + sw.srcRect.clip( sw.dstRect , sw.offsetX , sw.offsetY , true ) ; + if ( ! sw.srcRect.isNull ) { regions.push( sw ) ; } + } + + + // Wrap-x + wrap-y South-Est region, do it only if it has wrapped already + if ( ne && sw ) { + se = { + srcRect: new Rect( p.srcRect ) , + dstRect: new Rect( p.dstRect ) , + offsetX: nw.offsetX - p.dstRect.width , + offsetY: nw.offsetY - p.dstRect.height + } ; + + // Mutual clipping + se.srcRect.clip( se.dstRect , se.offsetX , se.offsetY , true ) ; + if ( ! se.srcRect.isNull ) { regions.push( se ) ; } + } + + return regions ; +} ; + + + +/* + This iterator generate synchronous line or cell for dst & src Rect. + It is totally buffer agnostic. + Buffer specificities should be added in p.context by the callee. + + Iterator. + Mandatory params: + * dstRect: Rect describing the dst geometry + * srcRect: Rect describing the src geometry + * type: 'line' or 'cell' + Optionnal params: + * context: an object that will be transmitted as is to the iterator + * dstClipRect: a clipping Rect for the dst + * srcClipRect: a clipping Rect for the src + * offsetX: the X-offset of the origin of the srcRect relative to the dst coordinate system + * offsetY: the Y-offset of the origin of the srcRect relative to the dst coordinate system + * multiply: the byte size of a cell by which all offset should be multiplied +*/ +Rect.regionIterator = function( p , iterator ) { + var i , j , srcX , srcY , dstX , dstY , srcStart , dstStart , isFullWidth ; + + if ( ! p.multiply ) { p.multiply = 1 ; } + if ( ! p.offsetX ) { p.offsetX = 0 ; } + if ( ! p.offsetY ) { p.offsetY = 0 ; } + + if ( p.dstClipRect ) { p.dstClipRect.clip( p.dstRect ) ; } + else { p.dstClipRect = new Rect( p.dstRect ) ; } + + if ( p.srcClipRect ) { p.srcClipRect.clip( p.srcRect ) ; } + else { p.srcClipRect = new Rect( p.srcRect ) ; } + + // Mutual clipping + p.srcClipRect.clip( p.dstClipRect , p.offsetX , p.offsetY , true ) ; + + // If out of bounds, or if everything is clipped away, return now + if ( p.dstRect.isNull || p.srcClipRect.isNull || p.dstClipRect.isNull ) { return ; } + + switch ( p.type ) { + case 'line' : + for ( j = 0 ; j < p.srcClipRect.height ; j ++ ) { + srcY = p.srcClipRect.ymin + j ; + dstY = p.dstClipRect.ymin + j ; + + iterator( { + context: p.context , + srcXmin: p.srcClipRect.xmin , + srcXmax: p.srcClipRect.xmax , + srcY: srcY , + srcStart: ( srcY * p.srcRect.width + p.srcClipRect.xmin ) * p.multiply , + srcEnd: ( srcY * p.srcRect.width + p.srcClipRect.xmax + 1 ) * p.multiply , + dstXmin: p.dstClipRect.xmin , + dstXmax: p.dstClipRect.xmax , + dstY: dstY , + dstStart: ( dstY * p.dstRect.width + p.dstClipRect.xmin ) * p.multiply , + dstEnd: ( dstY * p.dstRect.width + p.dstClipRect.xmax + 1 ) * p.multiply + //, lastLine: j === p.srcClipRect.height - 1 + } ) ; + } + break ; + + case 'reversedLine' : + // Same than 'line' but start from the last line. + // Useful for copying overlapping region of the same buffer, when the src rect has a lower Y-offset than the dst rect. + for ( j = p.srcClipRect.height - 1 ; j >= 0 ; j -- ) { + srcY = p.srcClipRect.ymin + j ; + dstY = p.dstClipRect.ymin + j ; + + iterator( { + context: p.context , + srcXmin: p.srcClipRect.xmin , + srcXmax: p.srcClipRect.xmax , + srcY: srcY , + srcStart: ( srcY * p.srcRect.width + p.srcClipRect.xmin ) * p.multiply , + srcEnd: ( srcY * p.srcRect.width + p.srcClipRect.xmax + 1 ) * p.multiply , + dstXmin: p.dstClipRect.xmin , + dstXmax: p.dstClipRect.xmax , + dstY: dstY , + dstStart: ( dstY * p.dstRect.width + p.dstClipRect.xmin ) * p.multiply , + dstEnd: ( dstY * p.dstRect.width + p.dstClipRect.xmax + 1 ) * p.multiply + } ) ; + } + break ; + + case 'cell' : + for ( j = 0 ; j < p.srcClipRect.height ; j ++ ) { + for ( i = 0 ; i < p.srcClipRect.width ; i ++ ) { + srcX = p.srcClipRect.xmin + i ; + srcY = p.srcClipRect.ymin + j ; + + dstX = p.dstClipRect.xmin + i ; + dstY = p.dstClipRect.ymin + j ; + + srcStart = ( srcY * p.srcRect.width + srcX ) * p.multiply ; + dstStart = ( dstY * p.dstRect.width + dstX ) * p.multiply ; + + isFullWidth = iterator( { + context: p.context , + srcX: srcX , + srcY: srcY , + srcStart: srcStart , + srcEnd: srcStart + p.multiply , + dstX: dstX , + dstY: dstY , + dstStart: dstStart , + dstEnd: dstStart + p.multiply , + startOfBlitLine: ! i , + endOfBlitLine: i === p.srcClipRect.width - 1 + } ) ; + + if ( isFullWidth ) { i ++ ; } + } + } + break ; + } +} ; + + + +/* + This is the tile-variant of the regionIterator. + + Iterator. + Mandatory params: + * dstRect + * srcRect + * type: 'line' or 'cell' + Optionnal params: + * context: an object that will be transmitted as is to the iterator + * dstClipRect + * srcClipRect + * offsetX + * offsetY + * multiply +*/ +Rect.tileIterator = function( p , iterator ) { + var srcI , srcJ , srcX , srcY , dstI , dstJ , dstX , dstY , streak , srcStart , dstStart ; + + if ( ! p.multiply ) { p.multiply = 1 ; } + if ( ! p.offsetX ) { p.offsetX = 0 ; } + if ( ! p.offsetY ) { p.offsetY = 0 ; } + + if ( p.dstClipRect ) { p.dstClipRect.clip( p.dstRect ) ; } + else { p.dstClipRect = new Rect( p.dstRect ) ; } + + if ( p.srcClipRect ) { p.srcClipRect.clip( p.srcRect ) ; } + else { p.srcClipRect = new Rect( p.srcRect ) ; } + + + switch ( p.type ) { + case 'cell' : + for ( dstJ = 0 ; dstJ < p.dstClipRect.height ; dstJ ++ ) { + srcJ = ( dstJ - p.offsetY ) % p.srcClipRect.height ; + if ( srcJ < 0 ) { srcJ += p.srcClipRect.height ; } + + for ( dstI = 0 ; dstI < p.dstClipRect.width ; dstI ++ ) { + srcI = ( dstI - p.offsetX ) % p.srcClipRect.width ; + if ( srcI < 0 ) { srcI += p.srcClipRect.width ; } + + srcX = p.srcClipRect.xmin + srcI ; + srcY = p.srcClipRect.ymin + srcJ ; + + dstX = p.dstClipRect.xmin + dstI ; + dstY = p.dstClipRect.ymin + dstJ ; + + srcStart = ( srcY * p.srcRect.width + srcX ) * p.multiply ; + dstStart = ( dstY * p.dstRect.width + dstX ) * p.multiply ; + + iterator( { + context: p.context , + srcX: srcX , + srcY: srcY , + srcStart: srcStart , + srcEnd: srcStart + p.multiply , + dstX: dstX , + dstY: dstY , + dstStart: dstStart , + dstEnd: dstStart + p.multiply + } ) ; + } + } + break ; + + case 'line' : + for ( dstJ = 0 ; dstJ < p.dstClipRect.height ; dstJ ++ ) { + srcJ = ( dstJ - p.offsetY ) % p.srcClipRect.height ; + if ( srcJ < 0 ) { srcJ += p.srcClipRect.height ; } + + dstI = 0 ; + while ( dstI < p.dstClipRect.width ) { + srcI = ( dstI - p.offsetX ) % p.srcClipRect.width ; + if ( srcI < 0 ) { srcI += p.srcClipRect.width ; } + + streak = Math.min( p.srcClipRect.width - srcI , p.dstClipRect.width - dstI ) ; + + srcX = p.srcClipRect.xmin + srcI ; + srcY = p.srcClipRect.ymin + srcJ ; + + dstX = p.dstClipRect.xmin + dstI ; + dstY = p.dstClipRect.ymin + dstJ ; + + srcStart = ( srcY * p.srcRect.width + srcX ) * p.multiply ; + dstStart = ( dstY * p.dstRect.width + dstX ) * p.multiply ; + + iterator( { + context: p.context , + srcXmin: srcX , + srcXmax: srcX + streak - 1 , + srcY: srcY , + srcStart: srcStart , + srcEnd: srcStart + streak * p.multiply , + dstXmin: dstX , + dstXmax: dstX + streak - 1 , + dstY: dstY , + dstStart: dstStart , + dstEnd: dstStart + streak * p.multiply + } ) ; + + dstI += streak ; + } + } + break ; + } +} ; + + + +/* + This is the wrap-variant of the regionIterator. + + Iterator. + Mandatory params: + * dstRect + * srcRect + * type: 'line' or 'cell' + Optionnal params: + * context: an object that will be transmitted as is to the iterator + * dstClipRect + * srcClipRect + * offsetX + * offsetY + * multiply + * wrapOnly: 'x' , 'y' (only wrap along that axis) +*/ +Rect.wrapIterator = function( p , iterator ) { + var i , regions ; + + regions = Rect.wrappingRect( { + dstRect: p.dstClipRect , + srcRect: p.srcClipRect , + offsetX: p.offsetX , + offsetY: p.offsetY , + wrapOnly: p.wrap + } ) ; + + for ( i = 0 ; i < regions.length ; i ++ ) { + p.dstClipRect = regions[ i ].dstRect ; + p.srcClipRect = regions[ i ].srcRect ; + p.offsetX = regions[ i ].offsetX ; + p.offsetY = regions[ i ].offsetY ; + + Rect.regionIterator( p , iterator ) ; + } +} ; + + +},{"./termkit.js":50}],3:[function(require,module,exports){ +(function (Buffer){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const misc = require( './misc.js' ) ; + +const fs = require( 'fs' ) ; +const string = require( 'string-kit' ) ; +const NextGenEvents = require( 'nextgen-events' ) ; + + + +/* + options: + * width: buffer width (default to dst.width) + * height: buffer height (default to dst.height) + * dst: writting destination + * inline: for terminal dst only, draw inline instead of at some position (do not moveTo) + * x: default position in the dst + * y: default position in the dst + * wrap: default wrapping behavior of .put() + * noFill: do not call .fill() with default values at ScreenBuffer creation + * blending: false/null or true or object (blending options): default blending params (can be overriden by .draw()) + * palette: Palette instance +*/ +function ScreenBuffer( options = {} ) { + this.dst = options.dst ; // a terminal or another screenBuffer + this.inline = !! options.inline ; // it's a terminal and we want to draw inline to it (no moveTo) + this.width = Math.floor( options.width ) || ( options.dst ? options.dst.width : 1 ) ; + this.height = Math.floor( options.height ) || ( options.dst ? options.dst.height : 1 ) ; + this.x = options.x !== undefined ? options.x : ( options.dst && options.dst instanceof termkit.Terminal ? 1 : 0 ) ; // eslint-disable-line + this.y = options.y !== undefined ? options.y : ( options.dst && options.dst instanceof termkit.Terminal ? 1 : 0 ) ; // eslint-disable-line + this.cx = 0 ; + this.cy = 0 ; + this.ch = false ; // cursor hidden + this.lastCh = null ; // cursor hidden on last terminal draw, avoid unecessary escape sequence output + this.lastBuffer = null ; + this.lastBufferUpToDate = false ; + this.blending = options.blending || false ; + this.wrap = !! options.wrap ; + this.buffer = Buffer.allocUnsafe( this.width * this.height * this.ITEM_SIZE ) ; + + this.palette = options.palette || ( this.dst && this.dst.palette ) ; + + if ( ! options.noFill ) { this.fill() ; } +} + +module.exports = ScreenBuffer ; + +ScreenBuffer.prototype = Object.create( NextGenEvents.prototype ) ; +ScreenBuffer.prototype.constructor = ScreenBuffer ; +ScreenBuffer.prototype.bitsPerColor = 8 ; + +// Backward compatibility +ScreenBuffer.create = ( ... args ) => new ScreenBuffer( ... args ) ; + + + +const termkit = require( './termkit.js' ) ; +const Rect = termkit.Rect ; + + + +/* + options: + * attr: attributes passed to .put() + * transparencyChar: a char that is transparent + * transparencyType: bit flags for the transparency char +*/ +ScreenBuffer.createFromString = function( options , data ) { + var x , y , length , attr , attrTrans , width , height , lineWidth , screenBuffer ; + + // Manage options + if ( ! options ) { options = {} ; } + + if ( typeof data !== 'string' ) { + if ( ! data.toString ) { throw new Error( '[terminal] ScreenBuffer.createFromDataString(): argument #1 should be a string or provide a .toString() method.' ) ; } + data = data.toString() ; + } + + // Transform the data into an array of lines + data = termkit.stripControlChars( data , true ).split( '\n' ) ; + + // Compute the buffer size + width = 0 ; + height = data.length ; + + attr = options.attr !== undefined ? options.attr : ScreenBuffer.prototype.DEFAULT_ATTR ; + if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = ScreenBuffer.object2attr( attr ) ; } + + attrTrans = attr ; + + if ( options.transparencyChar ) { + if ( ! options.transparencyType ) { attrTrans |= TRANSPARENCY ; } + else { attrTrans |= options.transparencyType & TRANSPARENCY ; } + } + + // Compute the width of the screenBuffer + for ( y = 0 ; y < data.length ; y ++ ) { + lineWidth = string.unicode.width( data[ y ] ) ; + if ( lineWidth > width ) { width = lineWidth ; } + } + + // Create the buffer with the right width & height + screenBuffer = new ScreenBuffer( { width: width , height: height } ) ; + + // Fill the buffer with data + for ( y = 0 ; y < data.length ; y ++ ) { + if ( ! options.transparencyChar ) { + screenBuffer.put( { x: 0 , y: y , attr: attr } , data[ y ] ) ; + } + else { + length = data[ y ].length ; + + for ( x = 0 ; x < length ; x ++ ) { + if ( data[ y ][ x ] === options.transparencyChar ) { + screenBuffer.put( { x: x , y: y , attr: attrTrans } , data[ y ][ x ] ) ; + } + else { + screenBuffer.put( { x: x , y: y , attr: attr } , data[ y ][ x ] ) ; + } + } + } + } + + return screenBuffer ; +} ; + + + +// Backward compatibility +ScreenBuffer.createFromChars = ScreenBuffer.createFromString ; + + + +// Shared +ScreenBuffer.prototype.setClearAttr = function( attr ) { + this.CLEAR_ATTR = this.object2attr( attr ) ; + this.CLEAR_BUFFER = Buffer.allocUnsafe( this.ITEM_SIZE ) ; + + if ( Buffer.isBuffer( this.CLEAR_ATTR ) ) { + // ScreenBufferHD + this.CLEAR_ATTR.copy( this.CLEAR_BUFFER ) ; + } + else { // if ( this.ATTR_SIZE === 4 ) { + this.CLEAR_BUFFER.writeInt32BE( this.CLEAR_ATTR , 0 ) ; + } + + this.CLEAR_BUFFER.write( ' \x00\x00\x00' , this.ATTR_SIZE ) ; // space +} ; + + + +/* + options: + attr: optional, the attribute to fill (default to DEFAULT_ATTR) + char: optional, the buffer will be filled with that char (default to space) + region: optional, a Rect compliant object defining the region to fill, instead a filling the whole ScreenBuffer + start: optional (internal), start offset + end: optional (internal), end offset + clearBuffer: optional (internal), a Buffer to use to clear (instead of char+attr) + buffer: optional (internal), used when we want to clear a Buffer instance, not a ScreenBuffer instance +*/ +// Shared +ScreenBuffer.prototype.fill = function( options ) { + var i , attr , char , start , end , region , + srcRect , toRect , + clearBuffer = this.CLEAR_BUFFER , + buffer = this.buffer ; + + if ( options && typeof options === 'object' ) { + if ( options.char || options.attr ) { + clearBuffer = Buffer.allocUnsafe( this.ITEM_SIZE ) ; + + // Write the attributes + attr = options.attr !== undefined ? options.attr : this.DEFAULT_ATTR ; + if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = this.object2attr( attr ) ; } + + this.writeAttr( clearBuffer , attr , 0 ) ; + + // Write the character + char = options.char && typeof options.char === 'string' ? options.char : ' ' ; + //char = punycode.ucs2.encode( [ punycode.ucs2.decode( termkit.stripControlChars( char ) )[ 0 ] ] ) ; + char = string.unicode.firstChar( termkit.stripControlChars( char ) ) ; + + //clearBuffer.write( char , this.ATTR_SIZE , this.CHAR_SIZE ) ; + this.writeChar( clearBuffer , char , 0 ) ; + } + else if ( options.clearBuffer ) { + clearBuffer = options.clearBuffer ; + } + + // This option is used when we want to clear a Buffer instance, not a ScreenBuffer instance + if ( options.buffer ) { buffer = options.buffer ; } + + start = options.start ? Math.floor( options.start / this.ITEM_SIZE ) : 0 ; + end = options.end ? Math.floor( options.end / this.ITEM_SIZE ) : buffer.length / this.ITEM_SIZE ; + region = options.region ? options.region : null ; + } + else { + start = 0 ; + end = buffer.length / this.ITEM_SIZE ; + } + + if ( region ) { + srcRect = new Rect( 0 , 0 , 0 , 0 ) ; + + toRect = new Rect( region ) ; + toRect.clip( new Rect( this ) ) ; + if ( toRect.isNull ) { return ; } + + // We use the blitter to fill the region + Rect.tileIterator( { + type: 'line' , + context: { srcBuffer: clearBuffer , dstBuffer: this.buffer } , + srcRect: srcRect , + dstRect: new Rect( this ) , + dstClipRect: toRect , + multiply: this.ITEM_SIZE + } , this.blitterLineIterator.bind( this ) ) ; + } + else { + for ( i = start ; i < end ; i ++ ) { + clearBuffer.copy( buffer , i * this.ITEM_SIZE ) ; + } + } +} ; + + + +// Clear the buffer: fill it with blank +// Shared +ScreenBuffer.prototype.clear = ScreenBuffer.prototype.fill ; + + + +ScreenBuffer.prototype.preserveMarkupFormat = misc.preserveMarkupFormat ; +ScreenBuffer.prototype.markupOptions = misc.markupOptions ; + + + +/* + put( options , str ) + put( options , format , [arg1] , [arg2] , ... ) + + options: + * x: bypass this.cx + * y: bypass this.cy + * markup: boolean or 'ansi' or 'legacyAnsi', true if the text contains markup that should be interpreted, + 'ansi' if it contains ansi code, 'legacyAnsi' is bold is bright fg and blink is bright bg + * attr: standard attributes + * resumeAttr: attr to resume to + * wrap: text wrapping, when the cursor move beyond the last column, it is moved to the begining of the next line + * newLine: if true, then \r and \n produce new lines, false by default: .put() does not manage lines + * direction: 'right' (default), 'left', 'up', 'down' or 'none'/null (do not move after puting a char) + * dx: x increment after each character (default: 1) + * dy: y increment after each character (default: 0) +*/ +// Shared +ScreenBuffer.prototype.put = function( options , str , ... args ) { + var parser , legacyColor , startX , startY , x , y , dx , dy , baseAttr , attr , attrObject , wrap ; + + // Manage options + if ( ! options ) { options = {} ; } + + wrap = options.wrap !== undefined ? options.wrap : this.wrap ; + + startX = x = Math.floor( options.x !== undefined ? options.x : this.cx ) ; + startY = y = Math.floor( options.y !== undefined ? options.y : this.cy ) ; + + + // Process directions/increments + dx = 1 ; + dy = 0 ; + + switch ( options.direction ) { + //case 'right' : // not needed, use the default dx & dy + case 'left' : + dx = -1 ; + break ; + case 'up' : + dx = 0 ; + dy = -1 ; + break ; + case 'down' : + dx = 0 ; + dy = 1 ; + break ; + case null : + case 'none' : + dx = 0 ; + dy = 0 ; + break ; + } + + if ( typeof options.dx === 'number' ) { dx = options.dx ; } + if ( typeof options.dy === 'number' ) { dy = options.dy ; } + + + // Process attributes + attr = options.attr !== undefined ? options.attr : this.DEFAULT_ATTR ; + if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = this.object2attr( attr ) ; } + baseAttr = attr ; + + // It's already in the correct format + if ( options.resumeAttr !== undefined ) { attr = options.resumeAttr ; } + + + // Process the input string + if ( typeof str !== 'string' ) { + if ( str.toString ) { str = str.toString() ; } + else { return ; } + } + + if ( args.length ) { + str = options.markup === true ? this.preserveMarkupFormat( str , ... args ) : string.format( str , ... args ) ; + } + + legacyColor = false ; + parser = null ; + + switch ( options.markup ) { + case 'ansi' : parser = termkit.parseAnsi ; break ; + case 'legacyAnsi' : parser = termkit.parseAnsi ; legacyColor = true ; break ; + case true : parser = termkit.parseMarkup ; break ; + } + + // The processing of raw chunk of text + var processRaw = part => { + //part = termkit.stripControlChars( part ) ; + + var isFullWidth , + offset , char , charCode , + characters = string.unicode.toArray( part ) , + i , iMax = characters.length ; + + for ( i = 0 ; i < iMax ; i ++ ) { + offset = ( y * this.width + x ) * this.ITEM_SIZE ; + char = characters[ i ] ; + charCode = char.charCodeAt( 0 ) ; + + if ( charCode < 0x20 || charCode === 0x7f ) { + if ( options.newLine && ( charCode === 0x0a || charCode === 0x0d ) ) { + if ( dx ) { + x = startX ; + y ++ ; + } + else { + y = startY ; + x ++ ; + } + + continue ; + } + else { + char = ' ' ; // Space + charCode = 0x20 ; + } + } + + isFullWidth = string.unicode.isFullWidth( char ) ; + + if ( isFullWidth ) { + if ( x + isFullWidth * dx >= 0 && x + isFullWidth * ( dx || 1 ) < this.width && y >= 0 && y < this.height ) { + // This is a full-width char! Needs extra care! + + if ( dx < 0 ) { offset -= this.ITEM_SIZE ; } + + // Check if we are writing on a fullwidth char + if ( this.hasTrailingFullWidth( this.buffer , offset ) && x ) { this.removeFullWidth( this.buffer , offset - this.ITEM_SIZE ) ; } + + // Write the attributes + this.writeAttr( this.buffer , attr , offset , this.LEADING_FULLWIDTH ) ; + + // Write the character + this.writeChar( this.buffer , char , offset ) ; + + offset += this.ITEM_SIZE ; + + // Check if we are writing on a fullwidth char + if ( this.hasLeadingFullWidth( this.buffer , offset ) && x < this.width - 1 ) { this.removeFullWidth( this.buffer , offset + this.ITEM_SIZE ) ; } + + // Write the attributes + this.writeAttr( this.buffer , attr , offset , this.TRAILING_FULLWIDTH ) ; + + // Write a blank character + this.writeChar( this.buffer , ' ' , offset ) ; + } + } + else if ( x >= 0 && x < this.width && y >= 0 && y < this.height ) { + // Check if we are writing on a fullwidth char + if ( this.hasLeadingFullWidth( this.buffer , offset ) && x < this.width - 1 ) { this.removeFullWidth( this.buffer , offset + this.ITEM_SIZE ) ; } + else if ( this.hasTrailingFullWidth( this.buffer , offset ) && x ) { this.removeFullWidth( this.buffer , offset - this.ITEM_SIZE ) ; } + + // Write the attributes + this.writeAttr( this.buffer , attr , offset ) ; + + // Write the character + this.writeChar( this.buffer , char , offset ) ; + } + + x += dx * ( 1 + isFullWidth ) ; + y += dy ; + + if ( wrap ) { + if ( x < 0 ) { + x = this.width - 1 ; + y -- ; + } + else if ( x >= this.width ) { + x = 0 ; + y ++ ; + } + } + } + } ; + + + if ( ! options.markup ) { + processRaw( str ) ; + } + else { + attrObject = this.attr2object( attr ) ; + parser( str , this.markupOptions ).forEach( part => { + if ( typeof part === 'string' ) { + processRaw( part ) ; + } + else { + if ( part.markup.reset ) { + attr = part.markup.special ? this.DEFAULT_ATTR : baseAttr ; + attrObject = this.attr2object( attr ) ; + } + else { + Object.assign( attrObject , part.markup ) ; + + // Remove incompatible flags + if ( attrObject.defaultColor && attrObject.color ) { delete attrObject.defaultColor ; } + if ( attrObject.bgDefaultColor && attrObject.bgColor ) { delete attrObject.bgDefaultColor ; } + + attr = this.object2attr( attrObject , undefined , legacyColor ) ; + } + + if ( part.markup.raw ) { + processRaw( part.markup.raw ) ; + } + } + } ) ; + } + + this.cx = x ; + this.cy = y ; + + return attr ; +} ; + + + +/* + options: + * x: bypass this.cx + * y: bypass this.cy +*/ +// Shared +ScreenBuffer.prototype.get = function( options ) { + var x , y , offset ; + + // Manage options + if ( ! options ) { options = {} ; } + + x = options.x !== undefined ? options.x : this.cx ; + y = options.y !== undefined ? options.y : this.cy ; + + if ( typeof x !== 'number' || x < 0 || x >= this.width ) { return null ; } + x = Math.floor( x ) ; + + if ( typeof y !== 'number' || y < 0 || y >= this.height ) { return null ; } + y = Math.floor( y ) ; + + offset = ( y * this.width + x ) * this.ITEM_SIZE ; + + return { + attr: this.attr2object( this.readAttr( this.buffer , offset ) ) , + char: this.readChar( this.buffer , offset ) + } ; +} ; + + + +// Resize a screenBuffer, using a Rect +// Shared +ScreenBuffer.prototype.resize = function( fromRect ) { + // Do not reference directly the userland variable, clone it + fromRect = new Rect( fromRect ) ; + + var offsetX = -fromRect.xmin , + offsetY = -fromRect.ymin ; + + // Create the toRect region + var toRect = new Rect( { + xmin: 0 , + ymin: 0 , + xmax: fromRect.width - 1 , + ymax: fromRect.height - 1 + } ) ; + + fromRect.clip( new Rect( this ) ) ; + + if ( toRect.isNull ) { return false ; } + + // Generate a new buffer + var resizedBuffer = Buffer.allocUnsafe( toRect.width * toRect.height * this.ITEM_SIZE ) ; + this.fill( { buffer: resizedBuffer } ) ; + + // We use the blitter to reconstruct the buffer geometry + Rect.regionIterator( { + type: 'line' , + context: { srcBuffer: this.buffer , dstBuffer: resizedBuffer } , + dstRect: toRect , + dstClipRect: new Rect( toRect ) , + srcRect: new Rect( this ) , + srcClipRect: fromRect , + offsetX: offsetX , + offsetY: offsetY , + multiply: this.ITEM_SIZE + } , this.blitterLineIterator.bind( this ) ) ; + + // Now, we have to replace the old buffer with the new, and set the width & height + this.width = toRect.width ; + this.height = toRect.height ; + this.buffer = resizedBuffer ; + + // Disable the lastBuffer, so `draw( { delta: true } )` will not be bugged + this.lastBuffer = null ; + + // This exists to improve compatibilities with the Terminal object + this.emit( 'resize' , this.width , this.height ) ; + + return true ; +} ; + + + +// Shared +ScreenBuffer.prototype.draw = function( options ) { + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + // Transmitted options (do not edit the user provided options, clone them) + var tr = { + dst: options.dst || this.dst , + inline: options.inline !== undefined ? !! options.inline : this.inline , + offsetX: options.x !== undefined ? Math.floor( options.x ) : Math.floor( this.x ) , + offsetY: options.y !== undefined ? Math.floor( options.y ) : Math.floor( this.y ) , + dstClipRect: options.dstClipRect ? new Rect( options.dstClipRect ) : undefined , + srcClipRect: options.srcClipRect ? new Rect( options.srcClipRect ) : undefined , + delta: options.delta , + blending: options.blending !== undefined ? options.blending : this.blending , + wrap: options.wrap , + tile: options.tile + } ; + + if ( tr.dst instanceof ScreenBuffer ) { + return this.blitter( tr ) ; + } + else if ( tr.dst instanceof termkit.Terminal ) { + return this.terminalBlitter( tr ) ; + } +} ; + + + +// Shared +ScreenBuffer.prototype.moveTo = function( x , y ) { + this.cx = Math.max( 0 , Math.min( x , this.width - 1 ) ) ; + this.cy = Math.max( 0 , Math.min( y , this.height - 1 ) ) ; +} ; + + + +// Shared +ScreenBuffer.prototype.drawCursor = function( options ) { + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + var dst = options.dst || this.dst ; + + if ( dst instanceof ScreenBuffer ) { + if ( this.ch ) { + dst.ch = true ; + } + else { + dst.ch = false ; + dst.moveTo( this.cx + this.x , this.cy + this.y ) ; + } + } + else if ( dst instanceof termkit.Terminal ) { + if ( this.ch ) { + if ( this.ch !== this.lastCh ) { dst.hideCursor() ; } + } + else { + if ( this.ch !== this.lastCh ) { dst.hideCursor( false ) ; } + dst.moveTo( + Math.max( 1 , Math.min( this.cx + this.x , dst.width ) ) , + Math.max( 1 , Math.min( this.cy + this.y , dst.height ) ) + ) ; + } + + this.lastCh = this.ch ; + } +} ; + + + +// Shared +ScreenBuffer.prototype.blitter = function( p ) { + var tr , iterator , iteratorCallback ; + + // Default options & iterator + tr = { + type: 'line' , + context: { srcBuffer: this.buffer , dstBuffer: p.dst.buffer , blending: p.blending } , + dstRect: new Rect( p.dst ) , + srcRect: new Rect( this ) , + dstClipRect: p.dstClipRect || new Rect( p.dst ) , + srcClipRect: p.srcClipRect || new Rect( this ) , + offsetX: p.offsetX , + offsetY: p.offsetY , + wrap: p.wrap , + tile: p.tile , + multiply: this.ITEM_SIZE + } ; + + iterator = 'regionIterator' ; + iteratorCallback = this.blitterLineIterator.bind( this ) ; + + + // If blending is on, switch to the cell iterator + if ( p.blending ) { + tr.type = 'cell' ; + iteratorCallback = this.blitterCellBlendingIterator.bind( this ) ; + } + + if ( p.wrap ) { iterator = 'wrapIterator' ; } + else if ( p.tile ) { iterator = 'tileIterator' ; } + else { iterator = 'regionIterator' ; } + + Rect[ iterator ]( tr , iteratorCallback ) ; +} ; + + + +// /!\ WARNING: We have to check full-width char in the previous dst x, and strip full-width at the end of the src +// Shared +ScreenBuffer.prototype.blitterLineIterator = function( p ) { + if ( p.dstStart >= this.ITEM_SIZE ) { + // Remove overlapping dst fullwidth at the begining + this.removeLeadingFullWidth( p.context.dstBuffer , p.dstStart - this.ITEM_SIZE ) ; + } + + p.context.srcBuffer.copy( p.context.dstBuffer , p.dstStart , p.srcStart , p.srcEnd ) ; + this.removeLeadingFullWidth( p.context.dstBuffer , p.dstEnd - this.ITEM_SIZE ) ; + + if ( p.dstEnd < p.context.dstBuffer.length ) { + // Remove overlapping dst fullwidth at the end + this.removeTrailingFullWidth( p.context.dstBuffer , p.dstEnd ) ; + } +} ; + + + +ScreenBuffer.prototype.blitterCellBlendingIterator = function( p ) { + var attr = this.readAttr( p.context.srcBuffer , p.srcStart ) , + blending = attr & TRANSPARENCY ; + + if ( blending === TRANSPARENCY ) { + // Fully transparent, do nothing + return ; + } + + // First, manage fullwidth chars + if ( p.startOfBlitLine && p.dstStart >= this.ITEM_SIZE ) { + // Remove overlapping dst fullwidth at the begining + this.removeLeadingFullWidth( p.context.dstBuffer , p.dstStart - this.ITEM_SIZE ) ; + } + + if ( p.endOfBlitLine && p.dstEnd < p.context.dstBuffer.length ) { + // Remove overlapping dst fullwidth at the end + this.removeTrailingFullWidth( p.context.dstBuffer , p.dstEnd ) ; + } + + if ( blending === NONE && ! ( p.endOfBlitLine || ! ( attr & LEADING_FULLWIDTH ) ) ) { + // Fully opaque, copy it + p.context.srcBuffer.copy( p.context.dstBuffer , p.dstStart , p.srcStart , p.srcEnd ) ; + return ; + } + + + // Blending part... + + if ( ! ( blending & FG_TRANSPARENCY ) ) { + // Copy source foreground color + p.context.srcBuffer.copy( + p.context.dstBuffer , + p.dstStart + 3 , + p.srcStart + 3 , + p.srcStart + 4 + ) ; + } + + if ( ! ( blending & BG_TRANSPARENCY ) ) { + // Copy source background color + p.context.srcBuffer.copy( + p.context.dstBuffer , + p.dstStart + 2 , + p.srcStart + 2 , + p.srcStart + 3 + ) ; + } + + if ( ! ( blending & STYLE_TRANSPARENCY ) ) { + // Copy source style + p.context.srcBuffer.copy( + p.context.dstBuffer , + p.dstStart + 1 , + p.srcStart + 1 , + p.srcStart + 2 + ) ; + } + + if ( ! ( blending & CHAR_TRANSPARENCY ) ) { + if ( p.endOfBlitLine && ( attr & LEADING_FULLWIDTH ) ) { + // Leading fullwidth at the end of the blit line, output a space instead + this.writeChar( p.context.dstBuffer , ' ' , p.dstStart ) ; + } + else { + // Copy source character + p.context.srcBuffer.copy( + p.context.dstBuffer , + p.dstStart + this.ATTR_SIZE , + p.srcStart + this.ATTR_SIZE , + p.srcEnd + ) ; + } + } +} ; + + + +// Shared +ScreenBuffer.prototype.terminalBlitter = function( p ) { + var tr , iterator , iteratorCallback , context ; + + context = { + srcBuffer: this.buffer , + blending: p.blending , + term: p.dst , + inline: p.inline , + deltaEscapeSequence: p.dst.support.deltaEscapeSequence , + rawTerm: p.dst.raw , + lastAttr: null , + sequence: '' , + cells: 0 , + moves: 0 , + attrs: 0 , + writes: 0 + } ; + + // Default options & iterator + tr = { + type: 'line' , + context: context , + dstRect: new Rect( p.dst ) , + srcRect: new Rect( this ) , + dstClipRect: p.dstClipRect , + srcClipRect: p.srcClipRect , + offsetX: p.offsetX , + offsetY: p.offsetY , + multiply: this.ITEM_SIZE + } ; + + // If in inline mode, the height is virtually infinity + if ( p.inline ) { tr.dstRect.setSize( { height: Infinity } ) ; } + + if ( p.delta && ! p.inline ) { + if ( ! this.lastBuffer || this.lastBuffer.length !== this.buffer.length ) { + this.lastBuffer = Buffer.from( this.buffer ) ; + iteratorCallback = this.terminalBlitterLineIterator.bind( this ) ; + } + else if ( this.lastBufferUpToDate ) { + context.srcLastBuffer = this.lastBuffer ; + + iteratorCallback = this.terminalBlitterCellIterator.bind( this ) ; + tr.type = 'cell' ; + } + else { + this.buffer.copy( this.lastBuffer ) ; + iteratorCallback = this.terminalBlitterLineIterator.bind( this ) ; + } + + this.lastBufferUpToDate = true ; + } + else { + this.lastBufferUpToDate = false ; + iteratorCallback = this.terminalBlitterLineIterator.bind( this ) ; + } + + + if ( p.wrap ) { iterator = 'wrapIterator' ; } + else if ( p.tile ) { iterator = 'tileIterator' ; } + else { iterator = 'regionIterator' ; } + + Rect[ iterator ]( tr , iteratorCallback ) ; + + // Write remaining sequence + if ( context.sequence.length ) { context.rawTerm( context.sequence ) ; context.writes ++ ; } + + // Copy buffer to lastBuffer + // Already done by terminalBlitterCellIterator() + // if ( p.delta ) { this.buffer.copy( this.lastBuffer ) ; } + + // Return some stats back to the caller + return { + cells: context.cells , + moves: context.moves , + attrs: context.attrs , + writes: context.writes + } ; +} ; + + + +ScreenBuffer.prototype.terminalBlitterLineIterator = function( p ) { + var offset , attr ; + + if ( ! p.context.inline ) { + p.context.sequence += p.context.term.optimized.moveTo( p.dstXmin , p.dstY ) ; + p.context.moves ++ ; + } + + for ( offset = p.srcStart ; offset < p.srcEnd ; offset += this.ITEM_SIZE ) { + attr = this.readAttr( p.context.srcBuffer , offset ) ; + + if ( ( attr & TRANSPARENCY ) === TRANSPARENCY ) { + // Fully transparent, do nothing except moving one char right + p.context.sequence += p.context.term.optimized.right ; + continue ; + } + else if ( attr & TRAILING_FULLWIDTH ) { + // Trailing fullwidth cell, the previous char already shifted the cursor to the right + continue ; + } + + if ( ( attr & ATTR_MASK ) !== ( p.context.lastAttr & ATTR_MASK ) ) { + p.context.sequence += p.context.lastAttr === null || ! p.context.deltaEscapeSequence ? + this.generateEscapeSequence( p.context.term , attr ) : + this.generateDeltaEscapeSequence( p.context.term , attr , p.context.lastAttr ) ; + p.context.lastAttr = attr ; + } + + p.context.sequence += this.readChar( p.context.srcBuffer , offset ) ; + p.context.cells ++ ; + } + + if ( p.context.inline ) { //&& ! p.lastLine ) { + // When we are at the bottom of the screen, the terminal may create a new line + // using the current attr for background color, just like .eraseLineAfter() would do... + // So we have to reset *BEFORE* the new line. + p.context.sequence += p.context.term.optimized.styleReset + '\n' ; + p.context.attrs ++ ; + p.context.lastAttr = null ; + } + + // Output buffering saves a good amount of CPU usage both for the node's processus and the terminal processus + if ( p.context.sequence.length > OUTPUT_THRESHOLD ) { + p.context.rawTerm( p.context.sequence ) ; + p.context.sequence = '' ; + p.context.writes ++ ; + } +} ; + + + +ScreenBuffer.prototype.terminalBlitterCellIterator = function( p ) { + var attr = this.readAttr( p.context.srcBuffer , p.srcStart ) ; + + // If last buffer's cell === current buffer's cell, no need to refresh... skip that now + if ( p.context.srcLastBuffer ) { + if ( + attr === this.readAttr( p.context.srcLastBuffer , p.srcStart ) && + this.readChar( p.context.srcBuffer , p.srcStart ) === this.readChar( p.context.srcLastBuffer , p.srcStart ) ) { + return ; + } + + p.context.srcBuffer.copy( p.context.srcLastBuffer , p.srcStart , p.srcStart , p.srcEnd ) ; + } + + if ( ( attr & TRANSPARENCY ) === TRANSPARENCY || ( attr & TRAILING_FULLWIDTH ) ) { + // Fully transparent or trailing fullwidth, do nothing + // Check that after eventually updating lastBuffer + return ; + } + + p.context.cells ++ ; + + if ( p.dstX !== p.context.cx || p.dstY !== p.context.cy ) { + p.context.sequence += p.context.term.optimized.moveTo( p.dstX , p.dstY ) ; + p.context.moves ++ ; + } + + if ( ( attr & ATTR_MASK ) !== ( p.context.lastAttr & ATTR_MASK ) ) { + p.context.sequence += p.context.lastAttr === null || ! p.context.deltaEscapeSequence ? + this.generateEscapeSequence( p.context.term , attr ) : + this.generateDeltaEscapeSequence( p.context.term , attr , p.context.lastAttr ) ; + p.context.lastAttr = attr ; + p.context.attrs ++ ; + } + + p.context.sequence += this.readChar( p.context.srcBuffer , p.srcStart ) ; + + // Output buffering saves a good amount of CPU usage both for the node's processus and the terminal processus + if ( p.context.sequence.length > OUTPUT_THRESHOLD ) { + p.context.rawTerm( p.context.sequence ) ; + p.context.sequence = '' ; + p.context.writes ++ ; + } + + // Next expected cursor position + p.context.cy = p.dstY ; + + if ( attr & LEADING_FULLWIDTH ) { + p.context.cx = p.dstX + 2 ; + return true ; // i.e.: tell the master iterator that this is a full-width char + } + + p.context.cx = p.dstX + 1 ; +} ; + + + +ScreenBuffer.fromNdarrayImage = function( pixels , options ) { + var term = options.terminal || termkit.terminal ; + + var x , xMax = pixels.shape[ 0 ] , + y , yMax = Math.ceil( pixels.shape[ 1 ] / 2 ) , + hasAlpha = pixels.shape[ 2 ] === 4 , + maxRegister = term.support['256colors'] ? 255 : 15 , + fgColor , bgColor , cache = {} ; + + var image = new ScreenBuffer( { + width: xMax , height: yMax , blending: true , noFill: true + } ) ; + + for ( x = 0 ; x < xMax ; x ++ ) { + for ( y = 0 ; y < yMax ; y ++ ) { + fgColor = term.registerForRgbCache( + cache , + pixels.get( x , y * 2 , 0 ) , + pixels.get( x , y * 2 , 1 ) , + pixels.get( x , y * 2 , 2 ) , + 0 , maxRegister , 1 + ) ; + + if ( y * 2 + 1 < pixels.shape[ 1 ] ) { + bgColor = term.registerForRgbCache( + cache , + pixels.get( x , y * 2 + 1 , 0 ) , + pixels.get( x , y * 2 + 1 , 1 ) , + pixels.get( x , y * 2 + 1 , 2 ) , + 0 , maxRegister , 1 + ) ; + + image.put( + { + x: x , + y: y , + attr: { + color: fgColor , + fgTransparency: hasAlpha && pixels.get( x , y * 2 , 3 ) < 127 , + bgColor: bgColor , + bgTransparency: hasAlpha && pixels.get( x , y * 2 + 1 , 3 ) < 127 + } + } , + '▀' + ) ; + } + else { + image.put( + { + x: x , + y: y , + attr: { + color: fgColor , + fgTransparency: hasAlpha && pixels.get( x , y * 2 , 3 ) < 127 , + bgTransparency: true + } + } , + '▀' + ) ; + } + } + } + + return image ; +} ; + + + +ScreenBuffer.loadImage = termkit.image.load.bind( ScreenBuffer , ScreenBuffer.fromNdarrayImage ) ; + + + +// Shared +ScreenBuffer.prototype.dumpChars = function() { + var y , x , offset , str = '' ; + + for ( y = 0 ; y < this.height ; y ++ ) { + for ( x = 0 ; x < this.width ; x ++ ) { + offset = ( y * this.width + x ) * this.ITEM_SIZE ; + str += this.readChar( this.buffer , offset ) ; + } + + str += '\n' ; + } + + return str ; +} ; + + + +ScreenBuffer.prototype.dump = function() { + var y , x , offset , str = '' , char ; + + for ( y = 0 ; y < this.height ; y ++ ) { + for ( x = 0 ; x < this.width ; x ++ ) { + offset = ( y * this.width + x ) * this.ITEM_SIZE ; + + char = this.readChar( this.buffer , offset ) ; + str += char + ( string.unicode.isFullWidth( char ) ? ' ' : ' ' ) ; + + str += string.format( '%x%x%x%x ' , + this.buffer.readUInt8( offset ) , + this.buffer.readUInt8( offset + 1 ) , + this.buffer.readUInt8( offset + 2 ) , + this.buffer.readUInt8( offset + 3 ) + ) ; + } + + str += '\n' ; + } + + return str ; +} ; + + + +ScreenBuffer.prototype.readAttr = function( buffer , at ) { + return buffer.readInt32BE( at ) ; +} ; + + + +ScreenBuffer.prototype.writeAttr = function( buffer , attr , at , fullWidth = 0 ) { + return buffer.writeInt32BE( attr | fullWidth , at ) ; +} ; + + + +ScreenBuffer.prototype.hasLeadingFullWidth = function( buffer , at ) { + return !! ( buffer.readInt32BE( at ) & LEADING_FULLWIDTH ) ; +} ; + + + +ScreenBuffer.prototype.hasTrailingFullWidth = function( buffer , at ) { + return !! ( buffer.readInt32BE( at ) & TRAILING_FULLWIDTH ) ; +} ; + + + +ScreenBuffer.prototype.removeLeadingFullWidth = function( buffer , at ) { + var attr = buffer.readInt32BE( at ) ; + if ( ! ( attr & LEADING_FULLWIDTH ) ) { return ; } + attr ^= LEADING_FULLWIDTH ; + buffer.writeInt32BE( attr , at ) ; + buffer.write( ' ' , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBuffer.prototype.removeTrailingFullWidth = function( buffer , at ) { + var attr = buffer.readInt32BE( at ) ; + if ( ! ( attr & TRAILING_FULLWIDTH ) ) { return ; } + attr ^= TRAILING_FULLWIDTH ; + buffer.writeInt32BE( attr , at ) ; + buffer.write( ' ' , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBuffer.prototype.removeFullWidth = function( buffer , at ) { + var attr = buffer.readInt32BE( at ) ; + if ( ! ( attr & FULLWIDTH ) ) { return ; } + attr = attr & REMOVE_FULLWIDTH_FLAG ; + buffer.writeInt32BE( attr , at ) ; + buffer.write( ' ' , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBuffer.prototype.readChar = function( buffer , at ) { + var bytes ; + + at += this.ATTR_SIZE ; + + if ( buffer[ at ] < 0x80 ) { bytes = 1 ; } + else if ( buffer[ at ] < 0xc0 ) { return '\x00' ; } // We are in a middle of an unicode multibyte sequence... something was wrong... + else if ( buffer[ at ] < 0xe0 ) { bytes = 2 ; } + else if ( buffer[ at ] < 0xf0 ) { bytes = 3 ; } + else if ( buffer[ at ] < 0xf8 ) { bytes = 4 ; } + else if ( buffer[ at ] < 0xfc ) { bytes = 5 ; } + else { bytes = 6 ; } + + if ( bytes > this.CHAR_SIZE ) { return '\x00' ; } + + return buffer.toString( 'utf8' , at , at + bytes ) ; +} ; + + + +ScreenBuffer.prototype.writeChar = function( buffer , char , at ) { + return buffer.write( char , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBuffer.prototype.generateEscapeSequence = function( term , attr ) { + var palette = this.palette || term.palette ; + + var esc = term.optimized.styleReset + + ( attr & FG_DEFAULT_COLOR ? term.optimized.defaultColor : palette.escape[ attr & 255 ] ) + + ( attr & BG_DEFAULT_COLOR ? term.optimized.bgDefaultColor : palette.bgEscape[ ( attr >>> 8 ) & 255 ] ) ; + + // Style part + if ( attr & BOLD ) { esc += term.optimized.bold ; } + if ( attr & DIM ) { esc += term.optimized.dim ; } + if ( attr & ITALIC ) { esc += term.optimized.italic ; } + if ( attr & UNDERLINE ) { esc += term.optimized.underline ; } + if ( attr & BLINK ) { esc += term.optimized.blink ; } + if ( attr & INVERSE ) { esc += term.optimized.inverse ; } + if ( attr & HIDDEN ) { esc += term.optimized.hidden ; } + if ( attr & STRIKE ) { esc += term.optimized.strike ; } + + return esc ; +} ; + + + +// Generate only the delta between the last and new attributes, may speed up things for the terminal process +// as well as consume less bandwidth, at the cost of small CPU increase in the application process +ScreenBuffer.prototype.generateDeltaEscapeSequence = function( term , attr , lastAttr ) { + var palette = this.palette || term.palette ; + + var esc = '' , + color = attr & 255 , + lastColor = lastAttr & 255 , + bgColor = ( attr >>> 8 ) & 255 , + lastBgColor = ( lastAttr >>> 8 ) & 255 ; + + if ( attr & FG_DEFAULT_COLOR ) { + if ( ! ( lastAttr & FG_DEFAULT_COLOR ) ) { esc += term.optimized.defaultColor ; } + } + else if ( color !== lastColor || ( lastAttr & FG_DEFAULT_COLOR ) ) { + esc += palette.escape[ color ] ; + } + + if ( attr & BG_DEFAULT_COLOR ) { + if ( ! ( lastAttr & BG_DEFAULT_COLOR ) ) { esc += term.optimized.bgDefaultColor ; } + } + else if ( bgColor !== lastBgColor || ( lastAttr & BG_DEFAULT_COLOR ) ) { + esc += palette.bgEscape[ bgColor ] ; + } + + if ( ( attr & STYLE_MASK ) !== ( lastAttr & STYLE_MASK ) ) { + // Bold and dim style are particular: all terminal has noBold = noDim + + if ( ( attr & BOLD_DIM ) !== ( lastAttr & BOLD_DIM ) ) { + if ( ( ( lastAttr & BOLD ) && ! ( attr & BOLD ) ) || + ( ( lastAttr & DIM ) && ! ( attr & DIM ) ) ) { + esc += term.optimized.noBold ; + if ( attr & BOLD ) { esc += term.optimized.bold ; } + if ( attr & DIM ) { esc += term.optimized.dim ; } + } + else { + if ( ( attr & BOLD ) && ! ( lastAttr & BOLD ) ) { esc += term.optimized.bold ; } + if ( ( attr & DIM ) && ! ( lastAttr & DIM ) ) { esc += term.optimized.dim ; } + } + } + + if ( ( attr & ITALIC ) !== ( lastAttr & ITALIC ) ) { + esc += attr & ITALIC ? term.optimized.italic : term.optimized.noItalic ; + } + + if ( ( attr & UNDERLINE ) !== ( lastAttr & UNDERLINE ) ) { + esc += attr & UNDERLINE ? term.optimized.underline : term.optimized.noUnderline ; + } + + if ( ( attr & BLINK ) !== ( lastAttr & BLINK ) ) { + esc += attr & BLINK ? term.optimized.blink : term.optimized.noBlink ; + } + + if ( ( attr & INVERSE ) !== ( lastAttr & INVERSE ) ) { + esc += attr & INVERSE ? term.optimized.inverse : term.optimized.noInverse ; + } + + if ( ( attr & HIDDEN ) !== ( lastAttr & HIDDEN ) ) { + esc += attr & HIDDEN ? term.optimized.hidden : term.optimized.noHidden ; + } + + if ( ( attr & STRIKE ) !== ( lastAttr & STRIKE ) ) { + esc += attr & STRIKE ? term.optimized.strike : term.optimized.noStrike ; + } + } + + return esc ; +} ; + + + +/* + * lineOffset: how many lines should we scroll: + >0: scroll down + <0: scroll up + * terminalScrolling: if true, the underlying terminal is scrolled, the lastBuffer is scrolled too, + but the screenbuffer is not drawn +*/ +// Shared +ScreenBuffer.prototype.vScroll = function( lineOffset , attr , ymin , ymax , terminalScrolling ) { + if ( ! lineOffset ) { return ; } + + // Arguments management + // Backward compatibility, when terminalScrolling was the 2nd argument + if ( typeof attr === 'boolean' ) { + terminalScrolling = attr ; + attr = ymin = ymax = undefined ; + } + + if ( attr === undefined || attr === null ) { + attr = this.DEFAULT_ATTR ; + } + else if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { + attr = this.object2attr( attr ) ; + } + + if ( ymin === undefined || ymin === null ) { + ymin = 0 ; + ymax = this.height - 1 ; + } + else { + if ( ymin < 0 ) { ymin = 0 ; } + if ( ymax > this.height - 1 ) { ymax = this.height - 1 ; } + } + + var scrollOffset = lineOffset * this.width * this.ITEM_SIZE , + startOffset = ymin * this.width * this.ITEM_SIZE , + endOffset = ( ymax + 1 ) * this.width * this.ITEM_SIZE ; + + if ( scrollOffset > 0 ) { + //this.buffer.copy( this.buffer , scrollOffset , 0 , this.buffer.length - scrollOffset ) ; + //this.fill( { end: scrollOffset } ) ; + this.buffer.copy( this.buffer , startOffset + scrollOffset , startOffset , endOffset - scrollOffset ) ; + this.fill( { start: startOffset , end: startOffset + scrollOffset } ) ; + } + else { + //this.buffer.copy( this.buffer , 0 , -scrollOffset , this.buffer.length ) ; + //this.fill( { start: this.buffer.length + scrollOffset } ) ; + this.buffer.copy( this.buffer , startOffset , startOffset - scrollOffset , endOffset ) ; + this.fill( { start: endOffset + scrollOffset , end: endOffset } ) ; + } + + if ( terminalScrolling && this.dst instanceof termkit.Terminal ) { + // Scroll lastBuffer accordingly + if ( this.lastBufferUpToDate && this.lastBuffer ) { + if ( scrollOffset > 0 ) { + //this.lastBuffer.copy( this.lastBuffer , scrollOffset , 0 , this.lastBuffer.length - scrollOffset ) ; + //this.fill( { end: scrollOffset , buffer: this.lastBuffer } ) ; + this.lastBuffer.copy( this.lastBuffer , startOffset + scrollOffset , startOffset , endOffset - scrollOffset ) ; + this.fill( { buffer: this.lastBuffer , start: startOffset , end: startOffset + scrollOffset } ) ; + } + else { + //this.lastBuffer.copy( this.lastBuffer , 0 , -scrollOffset , this.lastBuffer.length ) ; + //this.fill( { start: this.buffer.length + scrollOffset , buffer: this.lastBuffer } ) ; + this.lastBuffer.copy( this.lastBuffer , startOffset , startOffset - scrollOffset , endOffset ) ; + this.fill( { buffer: this.lastBuffer , start: endOffset + scrollOffset , end: endOffset } ) ; + } + } + + //this.dst.scrollingRegion( this.y , this.y + this.height - 1 ) ; + this.dst.scrollingRegion( this.y + ymin , this.y + ymax ) ; + + if ( lineOffset > 0 ) { this.dst.scrollDown( lineOffset ) ; } + else { this.dst.scrollUp( -lineOffset ) ; } + + this.dst.resetScrollingRegion() ; + } +} ; + + + +ScreenBuffer.prototype.copyRegion = function( from , to , isMove , attr ) { + var bufferRect = new Rect( this ) , + fromRect = new Rect( from ) , + toRect = new Rect( to ) ; + + toRect.setSize( fromRect ) ; // Ensure toRect has the size of fromRect + fromRect.clip( bufferRect ) ; + toRect.clip( bufferRect ) ; + + if ( fromRect.isNull ) { return ; } + + if ( ! toRect.isNull ) { + // We use the blitter to copy the region + Rect.regionIterator( { + type: toRect.ymin - fromRect.ymin > 0 ? 'reversedLine' : 'line' , + context: { srcBuffer: this.buffer , dstBuffer: this.buffer } , + srcRect: new Rect( this ) , + srcClipRect: fromRect , + dstRect: new Rect( this ) , + dstClipRect: toRect , + offsetX: toRect.xmin - fromRect.xmin , + offsetY: toRect.ymin - fromRect.ymin , + multiply: this.ITEM_SIZE + } , this.blitterLineIterator.bind( this ) ) ; + } + + if ( isMove ) { + throw new Error( "Move is not coded ATM" ) ; + } +} ; + + + + + +/* + Methods that are both static and instance member. + It must be possible to call them without any instance AND invoke instance specific method. +*/ + + + +ScreenBuffer.attr2object = function( attr ) { + var object = {} ; + + // Default color + if ( attr & FG_DEFAULT_COLOR ) { object.color = 0 ; object.defaultColor = true ; } + else { object.color = attr & 255 ; } + + if ( attr & BG_DEFAULT_COLOR ) { object.bgColor = 0 ; object.bgDefaultColor = true ; } + else { object.bgColor = ( attr >>> 8 ) & 255 ; } + + // Style part + if ( attr & BOLD ) { object.bold = true ; } + if ( attr & DIM ) { object.dim = true ; } + if ( attr & ITALIC ) { object.italic = true ; } + if ( attr & UNDERLINE ) { object.underline = true ; } + if ( attr & BLINK ) { object.blink = true ; } + if ( attr & INVERSE ) { object.inverse = true ; } + if ( attr & HIDDEN ) { object.hidden = true ; } + if ( attr & STRIKE ) { object.strike = true ; } + + // Blending part + if ( attr & FG_TRANSPARENCY ) { object.fgTransparency = true ; } + if ( attr & BG_TRANSPARENCY ) { object.bgTransparency = true ; } + if ( attr & STYLE_TRANSPARENCY ) { object.styleTransparency = true ; } + if ( attr & CHAR_TRANSPARENCY ) { object.charTransparency = true ; } + if ( ( attr & TRANSPARENCY ) === TRANSPARENCY ) { object.transparency = true ; } + + return object ; +} ; + +ScreenBuffer.prototype.attr2object = ScreenBuffer.attr2object ; + + + +ScreenBuffer.object2attr = function( object , colorNameToIndex , legacyColor = false ) { + var attr = 0 , brightFg = false , brightBg = false ; + + if ( ! object || typeof object !== 'object' ) { object = {} ; } + colorNameToIndex = colorNameToIndex || termkit.colorNameToIndex ; + + // Style part + if ( object.bold ) { + if ( legacyColor ) { brightFg = true ; } + else { attr |= BOLD ; } + } + if ( object.dim ) { attr |= DIM ; } + if ( object.italic ) { attr |= ITALIC ; } + if ( object.underline ) { attr |= UNDERLINE ; } + if ( object.blink ) { + if ( legacyColor ) { brightBg = true ; } + else { attr |= BLINK ; } + } + if ( object.inverse ) { attr |= INVERSE ; } + if ( object.hidden ) { attr |= HIDDEN ; } + if ( object.strike ) { attr |= STRIKE ; } + + // Color part + if ( typeof object.color === 'string' ) { + if ( object.color === 'default' ) { object.color = 0 ; object.defaultColor = true ; } + else { object.color = colorNameToIndex( object.color ) ; } + } + + if ( typeof object.color !== 'number' || object.color < 0 || object.color > 255 ) { + object.color = 0 ; + object.defaultColor = true ; + } + else { + object.color = Math.floor( object.color ) ; + if ( legacyColor && object.color <= 15 ) { + if ( brightFg && object.color <= 7 ) { object.color += 8 ; } + else if ( ! brightFg && object.color >= 8 ) { object.color -= 8 ; } + } + } + + attr += object.color ; + + // Background color part + if ( typeof object.bgColor === 'string' ) { + if ( object.bgColor === 'default' ) { object.bgColor = 0 ; object.bgDefaultColor = true ; } + else { object.bgColor = colorNameToIndex( object.bgColor ) ; } + } + + if ( typeof object.bgColor !== 'number' || object.bgColor < 0 || object.bgColor > 255 ) { + object.bgColor = 0 ; + object.bgDefaultColor = true ; + } + else { + object.bgColor = Math.floor( object.bgColor ) ; + if ( legacyColor && object.bgColor <= 15 ) { + if ( brightBg && object.bgColor <= 7 ) { object.bgColor += 8 ; } + else if ( ! brightBg && object.bgColor >= 8 ) { object.bgColor -= 8 ; } + } + } + + attr += object.bgColor << 8 ; + + // Default color + if ( object.defaultColor ) { attr |= FG_DEFAULT_COLOR ; } + if ( object.bgDefaultColor ) { attr |= BG_DEFAULT_COLOR ; } + + // Blending part + if ( object.transparency ) { attr |= TRANSPARENCY ; } + if ( object.fgTransparency ) { attr |= FG_TRANSPARENCY ; } + if ( object.bgTransparency ) { attr |= BG_TRANSPARENCY ; } + if ( object.styleTransparency ) { attr |= STYLE_TRANSPARENCY ; } + if ( object.charTransparency ) { attr |= CHAR_TRANSPARENCY ; } + + return attr ; +} ; + +ScreenBuffer.prototype.object2attr = function( object , colorNameToIndex = this.palette && this.palette.colorNameToIndex , legacyColor = false ) { + return ScreenBuffer.object2attr( object , colorNameToIndex , legacyColor ) ; +} ; + + + +// Add some attributes from an object to an existing attr integer +ScreenBuffer.attrAndObject = function( attr , object , colorNameToIndex ) { + if ( ! object || typeof object !== 'object' ) { return attr ; } + colorNameToIndex = colorNameToIndex || termkit.colorNameToIndex ; + + // Color part + if ( object.defaultColor || object.color === 'default' ) { + attr -= attr & 255 ; + attr |= FG_DEFAULT_COLOR ; + } + else if ( typeof object.color === 'string' ) { + attr = attr - ( attr & 255 ) + colorNameToIndex( object.color ) ; + if ( attr & FG_DEFAULT_COLOR ) { attr ^= FG_DEFAULT_COLOR ; } + } + else if ( typeof object.color === 'number' && object.color >= 0 && object.color <= 255 ) { + attr = attr - ( attr & 255 ) + object.color ; + if ( attr & FG_DEFAULT_COLOR ) { attr ^= FG_DEFAULT_COLOR ; } + } + + // Background color part + if ( object.bgDefaultColor || object.bgColor === 'default' ) { + attr -= ( ( ( attr >>> 8 ) & 255 ) << 8 ) ; + attr |= BG_DEFAULT_COLOR ; + } + else if ( typeof object.bgColor === 'string' ) { + attr = attr - ( ( ( attr >>> 8 ) & 255 ) << 8 ) + ( colorNameToIndex( object.bgColor ) << 8 ) ; + if ( attr & BG_DEFAULT_COLOR ) { attr ^= BG_DEFAULT_COLOR ; } + } + else if ( typeof object.bgColor === 'number' && object.bgColor >= 0 && object.bgColor <= 255 ) { + attr = attr - ( ( ( attr >>> 8 ) & 255 ) << 8 ) + ( object.bgColor << 8 ) ; + if ( attr & BG_DEFAULT_COLOR ) { attr ^= BG_DEFAULT_COLOR ; } + } + + // Style part + if ( object.bold === true ) { attr |= BOLD ; } + else if ( object.bold === false ) { attr &= ~ BOLD ; } + + if ( object.dim === true ) { attr |= DIM ; } + else if ( object.dim === false ) { attr &= ~ DIM ; } + + if ( object.italic === true ) { attr |= ITALIC ; } + else if ( object.italic === false ) { attr &= ~ ITALIC ; } + + if ( object.underline === true ) { attr |= UNDERLINE ; } + else if ( object.underline === false ) { attr &= ~ UNDERLINE ; } + + if ( object.blink === true ) { attr |= BLINK ; } + else if ( object.blink === false ) { attr &= ~ BLINK ; } + + if ( object.inverse === true ) { attr |= INVERSE ; } + else if ( object.inverse === false ) { attr &= ~ INVERSE ; } + + if ( object.hidden === true ) { attr |= HIDDEN ; } + else if ( object.hidden === false ) { attr &= ~ HIDDEN ; } + + if ( object.strike === true ) { attr |= STRIKE ; } + else if ( object.strike === false ) { attr &= ~ STRIKE ; } + + // Blending part + if ( object.transparency === true ) { attr |= TRANSPARENCY ; } + else if ( object.transparency === false ) { attr &= ~ TRANSPARENCY ; } + + if ( object.fgTransparency === true ) { attr |= FG_TRANSPARENCY ; } + else if ( object.fgTransparency === false ) { attr &= ~ FG_TRANSPARENCY ; } + + if ( object.bgTransparency === true ) { attr |= BG_TRANSPARENCY ; } + else if ( object.bgTransparency === false ) { attr &= ~ BG_TRANSPARENCY ; } + + if ( object.styleTransparency === true ) { attr |= STYLE_TRANSPARENCY ; } + else if ( object.styleTransparency === false ) { attr &= ~ STYLE_TRANSPARENCY ; } + + if ( object.charTransparency === true ) { attr |= CHAR_TRANSPARENCY ; } + else if ( object.charTransparency === false ) { attr &= ~ CHAR_TRANSPARENCY ; } + + return attr ; +} ; + +ScreenBuffer.prototype.attrAndObject = function( attr , object ) { + return ScreenBuffer.attrAndObject( attr , object , this.palette && this.palette.colorNameToIndex ) ; +} ; + +// Add the selection flag, i.e. the INVERSE flag +ScreenBuffer.attrSelect = ScreenBuffer.prototype.attrSelect = attr => attr | INVERSE ; +ScreenBuffer.attrUnselect = ScreenBuffer.prototype.attrUnselect = attr => attr & ~ INVERSE ; + + + + + +/* Constants */ + + + +// General purpose flags +const NONE = 0 ; // Nothing + +// Style mask and flags +const STYLE_MASK = 255 << 16 ; + +const BOLD = 1 << 16 ; +const DIM = 2 << 16 ; +const ITALIC = 4 << 16 ; +const UNDERLINE = 8 << 16 ; +const BLINK = 16 << 16 ; +const INVERSE = 32 << 16 ; +const HIDDEN = 64 << 16 ; +const STRIKE = 128 << 16 ; + +const BOLD_DIM = BOLD | DIM ; + +// Blending flags, mask and misc flags +const FG_TRANSPARENCY = 1 << 24 ; +const BG_TRANSPARENCY = 2 << 24 ; +const STYLE_TRANSPARENCY = 4 << 24 ; +const CHAR_TRANSPARENCY = 8 << 24 ; +const TRANSPARENCY = FG_TRANSPARENCY | BG_TRANSPARENCY | STYLE_TRANSPARENCY | CHAR_TRANSPARENCY ; + +// Special color: default terminal color +const FG_DEFAULT_COLOR = 16 << 24 ; +const BG_DEFAULT_COLOR = 32 << 24 ; + +// Attribute mask: anything except fullwidth flags +const ATTR_MASK = 255 + ( 255 << 8 ) + ( 255 << 16 ) + TRANSPARENCY + FG_DEFAULT_COLOR + BG_DEFAULT_COLOR ; + +// E.g.: if it needs redraw +// Was never implemented, could be replaced by a full-transparency check +//const VOID = 32 << 24 ; + +const LEADING_FULLWIDTH = 64 << 24 ; +const TRAILING_FULLWIDTH = 128 << 24 ; +const FULLWIDTH = LEADING_FULLWIDTH | TRAILING_FULLWIDTH ; +const REMOVE_FULLWIDTH_FLAG = ~ FULLWIDTH ; + +// Unused bits: none + + + +// Tuning +const OUTPUT_THRESHOLD = 10000 ; // minimum amount of data to retain before sending them to the terminal + +// DEPRECATED version 1 of ScreenBuffer file format +const HEADER_SIZE = 40 ; // Header consists of 40 bytes + + + +// Data structure +ScreenBuffer.prototype.ATTR_SIZE = 4 ; // do not edit, everything use Buffer.writeInt32BE() +ScreenBuffer.prototype.CHAR_SIZE = 4 ; +ScreenBuffer.prototype.ITEM_SIZE = ScreenBuffer.prototype.ATTR_SIZE + ScreenBuffer.prototype.CHAR_SIZE ; + +ScreenBuffer.DEFAULT_ATTR = // <- used by TextBuffer +ScreenBuffer.prototype.DEFAULT_ATTR = ScreenBuffer.object2attr( { defaultColor: true , bgDefaultColor: true } ) ; + +ScreenBuffer.prototype.CLEAR_ATTR = ScreenBuffer.object2attr( { defaultColor: true , bgDefaultColor: true , transparency: true } ) ; +ScreenBuffer.prototype.CLEAR_BUFFER = Buffer.allocUnsafe( ScreenBuffer.prototype.ITEM_SIZE ) ; +ScreenBuffer.prototype.CLEAR_BUFFER.writeInt32BE( ScreenBuffer.prototype.CLEAR_ATTR , 0 ) ; +ScreenBuffer.prototype.CLEAR_BUFFER.write( ' \x00\x00\x00' , ScreenBuffer.prototype.ATTR_SIZE ) ; // space + +ScreenBuffer.prototype.LEADING_FULLWIDTH = LEADING_FULLWIDTH ; +ScreenBuffer.prototype.TRAILING_FULLWIDTH = TRAILING_FULLWIDTH ; + + + +// Loader/Saver, mostly obsolete + +ScreenBuffer.loadSyncV1 = function( filepath ) { + var content , width , height , size , screenBuffer ; + + // Let it crash if nothing found + content = fs.readFileSync( filepath ) ; + + // No header found? + if ( content.length < HEADER_SIZE ) { throw new Error( 'No header found: this is not a ScreenBuffer file' ) ; } + + // See if we have got a 'SB' at the begining of the file + if ( content[ 0 ] !== 83 || content[ 1 ] !== 66 ) { throw new Error( 'Magic number mismatch: this is not a ScreenBuffer file' ) ; } + + // Get the geometry + width = content.readUInt16BE( 4 ) ; + height = content.readUInt16BE( 6 ) ; + + // Guess the file size + size = HEADER_SIZE + width * height * ScreenBuffer.prototype.ITEM_SIZE ; + + // Bad size? + if ( content.length !== size ) { throw new Error( 'Bad file size: this is not a ScreenBuffer file' ) ; } + + // So the file exists, create a canvas based upon it + screenBuffer = new ScreenBuffer( { + width: width , + height: height + } ) ; + + content.copy( screenBuffer.buffer , 0 , HEADER_SIZE ) ; + + return screenBuffer ; +} ; + + + +ScreenBuffer.prototype.saveSyncV1 = function( filepath ) { + var content ; + + content = Buffer.allocUnsafe( HEADER_SIZE + this.buffer.length ) ; + + // Clear the header area + content.fill( 0 , 0 , HEADER_SIZE ) ; + + // Write the 'SB' magic number + content[ 0 ] = 83 ; + content[ 1 ] = 66 ; + + // Set the geometry + content.writeUInt16BE( this.width , 4 ) ; + content.writeUInt16BE( this.height , 6 ) ; + + this.buffer.copy( content , HEADER_SIZE ) ; + + // Let it crash if something bad happens + fs.writeFileSync( filepath , content ) ; +} ; + + + +ScreenBuffer.loadSyncV2 = function( filepath ) { + var i , content , header , screenBuffer ; + + // Let it crash if nothing found + content = fs.readFileSync( filepath ) ; + + // See if we have got a 'SB' at the begining of the file + if ( content.length < 3 || content.toString( 'ascii' , 0 , 3 ) !== 'SB\n' ) { + throw new Error( 'Magic number mismatch: this is not a ScreenBuffer file' ) ; + } + + // search for the second \n + for ( i = 3 ; i < content.length ; i ++ ) { + if ( content[ i ] === 0x0a ) { break ; } + } + + if ( i === content.length ) { + throw new Error( 'No header found: this is not a ScreenBuffer file' ) ; + } + + // Try to parse a JSON header + try { + header = JSON.parse( content.toString( 'utf8' , 3 , i ) ) ; + } + catch( error ) { + throw new Error( 'No correct one-lined JSON header found: this is not a ScreenBuffer file' ) ; + } + + // Mandatory header field + if ( header.version === undefined || header.width === undefined || header.height === undefined ) { + throw new Error( 'Missing mandatory header data, this is a corrupted or obsolete ScreenBuffer file' ) ; + } + + // Check bitsPerColor + if ( header.bitsPerColor && header.bitsPerColor !== ScreenBuffer.prototype.bitsPerColor ) { + throw new Error( 'Bad Bits Per Color: ' + header.bitsPerColor + ' (should be ' + ScreenBuffer.prototype.bitsPerColor + ')' ) ; + } + + // Bad size? + if ( content.length !== i + 1 + header.width * header.height * ScreenBuffer.prototype.ITEM_SIZE ) { + throw new Error( 'Bad file size: this is a corrupted ScreenBuffer file' ) ; + } + + // So the file exists, create a canvas based upon it + screenBuffer = new ScreenBuffer( { + width: header.width , + height: header.height + } ) ; + + content.copy( screenBuffer.buffer , 0 , i + 1 ) ; + + return screenBuffer ; +} ; + + + +// This new format use JSON header for a maximal flexibility rather than a fixed binary header. +// The header start with a magic number SB\n then a compact single-line JSON that end with an \n. +// So the data part start after the second \n, providing a variable header size. +// This will allow adding meta data without actually changing the file format. +ScreenBuffer.prototype.saveSyncV2 = function( filepath ) { + var content , header ; + + header = { + version: 2 , + width: this.width , + height: this.height , + bpp: this.bpp + } ; + + header = 'SB\n' + JSON.stringify( header ) + '\n' ; + + content = Buffer.allocUnsafe( header.length + this.buffer.length ) ; + content.write( header ) ; + + this.buffer.copy( content , header.length ) ; + + // Let it crash if something bad happens + fs.writeFileSync( filepath , content ) ; +} ; + + + +ScreenBuffer.loadSync = ScreenBuffer.loadSyncV2 ; +ScreenBuffer.prototype.saveSync = ScreenBuffer.prototype.saveSyncV2 ; + + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./misc.js":42,"./termkit.js":50,"buffer":147,"fs":136,"nextgen-events":72,"string-kit":123}],4:[function(require,module,exports){ +(function (Buffer){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const ScreenBuffer = require( './ScreenBuffer.js' ) ; + +const fs = require( 'fs' ) ; +const string = require( 'string-kit' ) ; + + + +/* + options: + * width: buffer width (default to dst.width) + * height: buffer height (default to dst.height) + * dst: writting destination + * inline: for terminal dst only, draw inline instead of at some position (do not moveTo) + * x: default position in the dst + * y: default position in the dst + * wrap: default wrapping behavior of .put() + * noFill: do not call .fill() with default values at ScreenBuffer creation + * blending: false/null or true or object (blending options): default blending params (can be overriden by .draw()) +*/ +function ScreenBufferHD( options = {} ) { + ScreenBuffer.call( this , options ) ; +} + +module.exports = ScreenBufferHD ; + + + +const termkit = require( './termkit.js' ) ; + + + +ScreenBufferHD.prototype = Object.create( ScreenBuffer.prototype ) ; +ScreenBufferHD.prototype.constructor = ScreenBufferHD ; +ScreenBufferHD.prototype.bitsPerColor = 24 ; + +// Backward compatibility +ScreenBufferHD.create = ( ... args ) => new ScreenBufferHD( ... args ) ; + + + +/* + options: + * attr: attributes passed to .put() + * transparencyChar: a char that is transparent + * transparencyType: bit flags for the transparency char +*/ +ScreenBufferHD.createFromString = function( options , data ) { + var x , y , length , attr , attrTrans , width , height , lineWidth , screenBuffer ; + + // Manage options + if ( ! options ) { options = {} ; } + + if ( typeof data !== 'string' ) { + if ( ! data.toString ) { throw new Error( '[terminal] ScreenBufferHD.createFromDataString(): argument #1 should be a string or provide a .toString() method.' ) ; } + data = data.toString() ; + } + + // Transform the data into an array of lines + data = termkit.stripControlChars( data , true ).split( '\n' ) ; + + // Compute the buffer size + width = 0 ; + height = data.length ; + + attr = options.attr !== undefined ? options.attr : ScreenBufferHD.prototype.DEFAULT_ATTR ; + if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = ScreenBufferHD.object2attr( attr ) ; } + + attrTrans = attr ; + + if ( options.transparencyChar ) { + if ( ! options.transparencyType ) { attrTrans |= ScreenBufferHD.prototype.TRANSPARENCY ; } + else { attrTrans |= options.transparencyType & ScreenBufferHD.prototype.TRANSPARENCY ; } + } + + // Compute the width of the screenBuffer + for ( y = 0 ; y < data.length ; y ++ ) { + lineWidth = string.unicode.width( data[ y ] ) ; + if ( lineWidth > width ) { width = lineWidth ; } + } + + // Create the buffer with the right width & height + screenBuffer = new ScreenBufferHD( { width: width , height: height } ) ; + + // Fill the buffer with data + for ( y = 0 ; y < data.length ; y ++ ) { + if ( ! options.transparencyChar ) { + screenBuffer.put( { x: 0 , y: y , attr: attr } , data[ y ] ) ; + } + else { + length = data[ y ].length ; + + for ( x = 0 ; x < length ; x ++ ) { + if ( data[ y ][ x ] === options.transparencyChar ) { + screenBuffer.put( { x: x , y: y , attr: attrTrans } , data[ y ][ x ] ) ; + } + else { + screenBuffer.put( { x: x , y: y , attr: attr } , data[ y ][ x ] ) ; + } + } + } + } + + return screenBuffer ; +} ; + + + +// Backward compatibility +ScreenBufferHD.createFromChars = ScreenBufferHD.createFromString ; + + + +var colorScheme = require( './colorScheme/gnome.json' ).map( o => ( { color: { r: o.r , g: o.g , b: o.b } } ) ) ; +var bgColorScheme = colorScheme.map( o => ( { bgColor: { r: o.r , g: o.g , b: o.b } } ) ) ; + +ScreenBufferHD.prototype.markupToAttrObject = { + normal: { + '-': { dim: true } , + '+': { bold: true } , + '_': { underline: true } , + '/': { italic: true } , + '!': { inverse: true } , + + 'k': colorScheme[ 0 ] , + 'r': colorScheme[ 1 ] , + 'g': colorScheme[ 2 ] , + 'y': colorScheme[ 3 ] , + 'b': colorScheme[ 4 ] , + 'm': colorScheme[ 5 ] , + 'c': colorScheme[ 6 ] , + 'w': colorScheme[ 7 ] , + 'K': colorScheme[ 8 ] , + 'R': colorScheme[ 9 ] , + 'G': colorScheme[ 10 ] , + 'Y': colorScheme[ 11 ] , + 'B': colorScheme[ 12 ] , + 'M': colorScheme[ 13 ] , + 'C': colorScheme[ 14 ] , + 'W': colorScheme[ 15 ] + } , + background: { + 'k': bgColorScheme[ 0 ] , + 'r': bgColorScheme[ 1 ] , + 'g': bgColorScheme[ 2 ] , + 'y': bgColorScheme[ 3 ] , + 'b': bgColorScheme[ 4 ] , + 'm': bgColorScheme[ 5 ] , + 'c': bgColorScheme[ 6 ] , + 'w': bgColorScheme[ 7 ] , + 'K': bgColorScheme[ 8 ] , + 'R': bgColorScheme[ 9 ] , + 'G': bgColorScheme[ 10 ] , + 'Y': bgColorScheme[ 11 ] , + 'B': bgColorScheme[ 12 ] , + 'M': bgColorScheme[ 13 ] , + 'C': bgColorScheme[ 14 ] , + 'W': bgColorScheme[ 15 ] + } +} ; + + + +ScreenBufferHD.prototype.blitterCellBlendingIterator = function( p ) { + var attr = this.readAttr( p.context.srcBuffer , p.srcStart ) ; + + var blendFn = ScreenBufferHD.blendFn.normal ; + var opacity = 1 ; + var blendSrcFgWithDstBg = false ; + + if ( typeof p.context.blending === 'object' ) { + if ( p.context.blending.fn ) { blendFn = p.context.blending.fn ; } + if ( p.context.blending.opacity !== undefined ) { opacity = p.context.blending.opacity ; } + if ( p.context.blending.blendSrcFgWithDstBg ) { blendSrcFgWithDstBg = true ; } + } + + if ( + ( attr[ BPOS_MISC ] & STYLE_TRANSPARENCY ) && + ( attr[ BPOS_MISC ] & CHAR_TRANSPARENCY ) && + ( ! opacity || ( attr[ BPOS_A ] === 0 && attr[ BPOS_BG_A ] === 0 ) ) + ) { + // Fully transparent, do nothing + return ; + } + + // First, manage fullwidth chars + if ( p.startOfBlitLine && p.dstStart >= this.ITEM_SIZE ) { + // Remove overlapping dst fullwidth at the begining + this.removeLeadingFullWidth( p.context.dstBuffer , p.dstStart - this.ITEM_SIZE ) ; + } + + if ( p.endOfBlitLine && p.dstEnd < p.context.dstBuffer.length ) { + // Remove overlapping dst fullwidth at the end + this.removeTrailingFullWidth( p.context.dstBuffer , p.dstEnd ) ; + } + + if ( + ! ( attr[ BPOS_MISC ] & STYLE_TRANSPARENCY ) && + ! ( attr[ BPOS_MISC ] & CHAR_TRANSPARENCY ) && + attr[ BPOS_A ] === 255 && attr[ BPOS_BG_A ] === 255 && opacity === 1 && + blendFn === ScreenBufferHD.blendFn.normal && + ! ( p.endOfBlitLine || ! ( attr[ BPOS_MISC ] & LEADING_FULLWIDTH ) ) + ) { + // Fully opaque, copy it + p.context.srcBuffer.copy( p.context.dstBuffer , p.dstStart , p.srcStart , p.srcEnd ) ; + return ; + } + + // Blending part... + + var alpha ; // Normalized alpha + + if ( attr[ BPOS_A ] ) { + alpha = opacity * attr[ BPOS_A ] / 255 ; + + if ( blendSrcFgWithDstBg ) { + p.context.dstBuffer[ p.dstStart + BPOS_R ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_R ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_R ] , + alpha , + blendFn + ) ; + p.context.dstBuffer[ p.dstStart + BPOS_G ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_G ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_G ] , + alpha , + blendFn + ) ; + p.context.dstBuffer[ p.dstStart + BPOS_B ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_B ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_B ] , + alpha , + blendFn + ) ; + // Blending alpha is special + p.context.dstBuffer[ p.dstStart + BPOS_A ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_A ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_A ] , + opacity , + ScreenBufferHD.blendFn.screen + ) ; + } + else { + p.context.dstBuffer[ p.dstStart + BPOS_R ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_R ] , + p.context.dstBuffer[ p.dstStart + BPOS_R ] , + alpha , + blendFn + ) ; + p.context.dstBuffer[ p.dstStart + BPOS_G ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_G ] , + p.context.dstBuffer[ p.dstStart + BPOS_G ] , + alpha , + blendFn + ) ; + p.context.dstBuffer[ p.dstStart + BPOS_B ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_B ] , + p.context.dstBuffer[ p.dstStart + BPOS_B ] , + alpha , + blendFn + ) ; + // Blending alpha is special + p.context.dstBuffer[ p.dstStart + BPOS_A ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_A ] , + p.context.dstBuffer[ p.dstStart + BPOS_A ] , + opacity , + ScreenBufferHD.blendFn.screen + ) ; + } + } + + if ( attr[ BPOS_BG_A ] ) { + alpha = opacity * attr[ BPOS_BG_A ] / 255 ; + + p.context.dstBuffer[ p.dstStart + BPOS_BG_R ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_BG_R ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_R ] , + alpha , + blendFn + ) ; + p.context.dstBuffer[ p.dstStart + BPOS_BG_G ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_BG_G ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_G ] , + alpha , + blendFn + ) ; + p.context.dstBuffer[ p.dstStart + BPOS_BG_B ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_BG_B ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_B ] , + alpha , + blendFn + ) ; + // Blending alpha is special + p.context.dstBuffer[ p.dstStart + BPOS_BG_A ] = alphaBlend( + p.context.srcBuffer[ p.srcStart + BPOS_BG_A ] , + p.context.dstBuffer[ p.dstStart + BPOS_BG_A ] , + opacity , + ScreenBufferHD.blendFn.screen + ) ; + } + + if ( ! ( attr[ BPOS_MISC ] & STYLE_TRANSPARENCY ) ) { + p.context.dstBuffer[ p.dstStart + BPOS_STYLE ] = + p.context.srcBuffer[ p.srcStart + BPOS_STYLE ] ; + } + + if ( ! ( attr[ BPOS_MISC ] & CHAR_TRANSPARENCY ) ) { + if ( p.endOfBlitLine && ( attr[ BPOS_MISC ] & LEADING_FULLWIDTH ) ) { + // Leading fullwidth at the end of the blit line, output a space instead + this.writeChar( p.context.dstBuffer , ' ' , p.dstStart ) ; + } + else { + // Copy source character + p.context.srcBuffer.copy( + p.context.dstBuffer , + p.dstStart + this.ATTR_SIZE , + p.srcStart + this.ATTR_SIZE , + p.srcEnd + ) ; + } + } +} ; + + + +function alphaBlend( src , dst , alpha , fn ) { + return Math.round( fn( src , dst ) * alpha + dst * ( 1 - alpha ) ) ; +} + + + +// https://en.wikipedia.org/wiki/Blend_modes +ScreenBufferHD.blendFn = { + normal: src => src , + multiply: ( src , dst ) => 255 * ( ( src / 255 ) * ( dst / 255 ) ) , + screen: ( src , dst ) => 255 * ( 1 - ( 1 - src / 255 ) * ( 1 - dst / 255 ) ) , + overlay: ( src , dst ) => dst <= 127 ? + 255 * ( 2 * ( src / 255 ) * ( dst / 255 ) ) : + 255 * ( 1 - 2 * ( 1 - src / 255 ) * ( 1 - dst / 255 ) ) , + hardLight: ( src , dst ) => src <= 127 ? + 255 * ( 2 * ( src / 255 ) * ( dst / 255 ) ) : + 255 * ( 1 - 2 * ( 1 - src / 255 ) * ( 1 - dst / 255 ) ) , + softLight: ( src , dst ) => { + src /= 255 ; + dst /= 255 ; + return 255 * ( ( 1 - 2 * src ) * dst * dst + 2 * src * dst ) ; + } +} ; + + + +function attrEquals( attr1 , attr2 ) { + if ( attr1.readUInt32BE( BPOS_FG ) !== attr2.readUInt32BE( BPOS_FG ) ) { return false ; } + if ( attr1.readUInt32BE( BPOS_BG ) !== attr2.readUInt32BE( BPOS_BG ) ) { return false ; } + if ( attr1[ BPOS_STYLE ] !== attr2[ BPOS_STYLE ] ) { return false ; } + if ( ( attr1[ BPOS_MISC ] & MISC_ATTR_MASK ) !== ( attr2[ BPOS_MISC ] & MISC_ATTR_MASK ) ) { return false ; } + + return true ; +} + + + +ScreenBufferHD.prototype.terminalBlitterLineIterator = function( p ) { + var offset , attr ; + + if ( ! p.context.inline ) { + p.context.sequence += p.context.term.optimized.moveTo( p.dstXmin , p.dstY ) ; + p.context.moves ++ ; + } + + for ( offset = p.srcStart ; offset < p.srcEnd ; offset += this.ITEM_SIZE ) { + attr = this.readAttr( p.context.srcBuffer , offset ) ; + + if ( attr[ BPOS_MISC ] & TRAILING_FULLWIDTH ) { + // Trailing fullwidth cell, the previous char already shifted the cursor to the right + continue ; + } + + if ( ! p.context.lastAttr || ! attrEquals( attr , p.context.lastAttr ) ) { + p.context.sequence += ! p.context.lastAttr || ! p.context.deltaEscapeSequence ? + this.generateEscapeSequence( p.context.term , attr ) : + this.generateDeltaEscapeSequence( p.context.term , attr , p.context.lastAttr ) ; + p.context.lastAttr = attr ; + p.context.attrs ++ ; + } + + p.context.sequence += this.readChar( p.context.srcBuffer , offset ) ; + p.context.cells ++ ; + } + + if ( p.context.inline ) { + // When we are at the bottom of the screen, the terminal may create a new line + // using the current attr for background color, just like .eraseLineAfter() would do... + // So we have to reset *BEFORE* the new line. + p.context.sequence += p.context.term.optimized.styleReset + '\n' ; + p.context.attrs ++ ; + p.context.lastAttr = null ; + } + + // Output buffering saves a good amount of CPU usage both for the node's processus and the terminal processus + if ( p.context.sequence.length > OUTPUT_THRESHOLD ) { + p.context.rawTerm( p.context.sequence ) ; + p.context.sequence = '' ; + p.context.writes ++ ; + } +} ; + + + +ScreenBufferHD.prototype.terminalBlitterCellIterator = function( p ) { + //var attr = p.context.srcBuffer.readUInt32BE( p.srcStart ) ; + var attr = this.readAttr( p.context.srcBuffer , p.srcStart ) ; + + // If last buffer's cell === current buffer's cell, no need to refresh... skip that now + if ( p.context.srcLastBuffer ) { + if ( + attrEquals( attr , this.readAttr( p.context.srcLastBuffer , p.srcStart ) ) && + this.readChar( p.context.srcBuffer , p.srcStart ) === this.readChar( p.context.srcLastBuffer , p.srcStart ) ) { + return ; + } + + p.context.srcBuffer.copy( p.context.srcLastBuffer , p.srcStart , p.srcStart , p.srcEnd ) ; + } + + if ( attr[ BPOS_MISC ] & TRAILING_FULLWIDTH ) { + // Trailing fullwidth, do nothing + // Check that after eventually updating lastBuffer + return ; + } + + p.context.cells ++ ; + + if ( p.dstX !== p.context.cx || p.dstY !== p.context.cy ) { + p.context.sequence += p.context.term.optimized.moveTo( p.dstX , p.dstY ) ; + p.context.moves ++ ; + } + + if ( ! p.context.lastAttr || ! attrEquals( attr , p.context.lastAttr ) ) { + p.context.sequence += ! p.context.lastAttr || ! p.context.deltaEscapeSequence ? + this.generateEscapeSequence( p.context.term , attr ) : + this.generateDeltaEscapeSequence( p.context.term , attr , p.context.lastAttr ) ; + p.context.lastAttr = attr ; + p.context.attrs ++ ; + } + + p.context.sequence += this.readChar( p.context.srcBuffer , p.srcStart ) ; + + // Output buffering saves a good amount of CPU usage both for the node's processus and the terminal processus + if ( p.context.sequence.length > OUTPUT_THRESHOLD ) { + p.context.rawTerm( p.context.sequence ) ; + p.context.sequence = '' ; + p.context.writes ++ ; + } + + // Next expected cursor position + p.context.cy = p.dstY ; + + if ( attr & LEADING_FULLWIDTH ) { + p.context.cx = p.dstX + 2 ; + return true ; // i.e.: tell the master iterator that this is a full-width char + } + + p.context.cx = p.dstX + 1 ; +} ; + + + +ScreenBufferHD.fromNdarrayImage = function( pixels /*, options */ ) { + var x , xMax = pixels.shape[ 0 ] , + y , yMax = Math.ceil( pixels.shape[ 1 ] / 2 ) , + hasAlpha = pixels.shape[ 2 ] === 4 ; + + var image = new ScreenBufferHD( { + width: xMax , height: yMax , blending: true , noFill: true + } ) ; + + for ( x = 0 ; x < xMax ; x ++ ) { + for ( y = 0 ; y < yMax ; y ++ ) { + if ( y * 2 + 1 < pixels.shape[ 1 ] ) { + image.put( + { + x: x , + y: y , + attr: { + color: { + r: pixels.get( x , y * 2 , 0 ) , + g: pixels.get( x , y * 2 , 1 ) , + b: pixels.get( x , y * 2 , 2 ) , + a: hasAlpha ? pixels.get( x , y * 2 , 3 ) : 255 + } , + bgColor: { + r: pixels.get( x , y * 2 + 1 , 0 ) , + g: pixels.get( x , y * 2 + 1 , 1 ) , + b: pixels.get( x , y * 2 + 1 , 2 ) , + a: hasAlpha ? pixels.get( x , y * 2 + 1 , 3 ) : 255 + } + } + } , + '▀' + ) ; + } + else { + image.put( + { + x: x , + y: y , + attr: { + color: { + r: pixels.get( x , y * 2 , 0 ) , + g: pixels.get( x , y * 2 , 1 ) , + b: pixels.get( x , y * 2 , 2 ) , + a: hasAlpha ? pixels.get( x , y * 2 , 3 ) : 255 + } , + bgColor: { + r: 0 , + g: 0 , + b: 0 , + a: 0 + } + } + } , + '▀' + ) ; + } + } + } + + return image ; +} ; + + + +ScreenBufferHD.loadImage = termkit.image.load.bind( ScreenBufferHD , ScreenBufferHD.fromNdarrayImage ) ; + + + +ScreenBufferHD.prototype.dump = function() { + var y , x , offset , str = '' , char ; + + for ( y = 0 ; y < this.height ; y ++ ) { + for ( x = 0 ; x < this.width ; x ++ ) { + offset = ( y * this.width + x ) * this.ITEM_SIZE ; + + char = this.readChar( this.buffer , offset ) ; + str += char + ( string.unicode.isFullWidth( char ) ? ' ' : ' ' ) ; + + str += string.format( '%x%x%x%x %x%x%x%x %x%x ' , + this.buffer.readUInt8( offset ) , + this.buffer.readUInt8( offset + 1 ) , + this.buffer.readUInt8( offset + 2 ) , + this.buffer.readUInt8( offset + 3 ) , + this.buffer.readUInt8( offset + 4 ) , + this.buffer.readUInt8( offset + 5 ) , + this.buffer.readUInt8( offset + 6 ) , + this.buffer.readUInt8( offset + 7 ) , + this.buffer.readUInt8( offset + 8 ) , + this.buffer.readUInt8( offset + 9 ) + ) ; + } + + str += '\n' ; + } + + return str ; +} ; + + + +ScreenBufferHD.prototype.readAttr = function( buffer , at ) { + return buffer.slice( at , at + this.ATTR_SIZE ) ; +} ; + + + +ScreenBufferHD.prototype.writeAttr = function( buffer , attr , at , fullWidth = 0 ) { + if ( ! fullWidth ) { return attr.copy( buffer , at ) ; } + attr.copy( buffer , at ) ; + return buffer.writeUInt8( attr[ BPOS_MISC ] | fullWidth , at + BPOS_MISC ) ; +} ; + + + +ScreenBufferHD.prototype.hasLeadingFullWidth = function( buffer , at ) { + return !! ( buffer.readUInt8( at + BPOS_MISC ) & LEADING_FULLWIDTH ) ; +} ; + + + +ScreenBufferHD.prototype.hasTrailingFullWidth = function( buffer , at ) { + return !! ( buffer.readUInt8( at + BPOS_MISC ) & TRAILING_FULLWIDTH ) ; +} ; + + + +ScreenBufferHD.prototype.removeLeadingFullWidth = function( buffer , at ) { + var attr = buffer.readUInt8( at + BPOS_MISC ) ; + if ( ! ( attr & LEADING_FULLWIDTH ) ) { return ; } + attr ^= LEADING_FULLWIDTH ; + buffer.writeUInt8( attr , at + BPOS_MISC ) ; + buffer.write( ' ' , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBufferHD.prototype.removeTrailingFullWidth = function( buffer , at ) { + var attr = buffer.readUInt8( at + BPOS_MISC ) ; + if ( ! ( attr & TRAILING_FULLWIDTH ) ) { return ; } + attr ^= TRAILING_FULLWIDTH ; + buffer.writeUInt8( attr , at + BPOS_MISC ) ; + buffer.write( ' ' , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBufferHD.prototype.removeFullWidth = function( buffer , at ) { + var attr = buffer.readUInt8( at + BPOS_MISC ) ; + if ( ! ( attr & FULLWIDTH ) ) { return ; } + attr = attr & REMOVE_FULLWIDTH_FLAG ; + buffer.writeUInt8( attr , at + BPOS_MISC ) ; + buffer.write( ' ' , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBufferHD.prototype.attrLeadingFullWidth = function( attr ) { + attr.writeUInt8( attr[ BPOS_MISC ] | LEADING_FULLWIDTH , BPOS_MISC ) ; + return attr ; +} ; + + + +ScreenBufferHD.prototype.attrTrailingFullWidth = function( attr ) { + attr.writeUInt8( attr[ BPOS_MISC ] | TRAILING_FULLWIDTH , BPOS_MISC ) ; + return attr ; +} ; + + + +ScreenBufferHD.prototype.readChar = function( buffer , at ) { + var bytes ; + + at += this.ATTR_SIZE ; + + if ( buffer[ at ] < 0x80 ) { bytes = 1 ; } + else if ( buffer[ at ] < 0xc0 ) { return '\x00' ; } // We are in a middle of an unicode multibyte sequence... something was wrong... + else if ( buffer[ at ] < 0xe0 ) { bytes = 2 ; } + else if ( buffer[ at ] < 0xf0 ) { bytes = 3 ; } + else if ( buffer[ at ] < 0xf8 ) { bytes = 4 ; } + else if ( buffer[ at ] < 0xfc ) { bytes = 5 ; } + else { bytes = 6 ; } + + if ( bytes > this.CHAR_SIZE ) { return '\x00' ; } + + return buffer.toString( 'utf8' , at , at + bytes ) ; +} ; + + + +ScreenBufferHD.prototype.writeChar = function( buffer , char , at ) { + return buffer.write( char , at + this.ATTR_SIZE , this.CHAR_SIZE ) ; +} ; + + + +ScreenBufferHD.prototype.generateEscapeSequence = function( term , attr ) { + var esc = term.optimized.styleReset + + term.optimized.color24bits( attr[ BPOS_R ] , attr[ BPOS_G ] , attr[ BPOS_B ] ) + + term.optimized.bgColor24bits( attr[ BPOS_BG_R ] , attr[ BPOS_BG_G ] , attr[ BPOS_BG_B ] ) ; + + var style = attr[ BPOS_STYLE ] ; + + // Style part + if ( style & BOLD ) { esc += term.optimized.bold ; } + if ( style & DIM ) { esc += term.optimized.dim ; } + if ( style & ITALIC ) { esc += term.optimized.italic ; } + if ( style & UNDERLINE ) { esc += term.optimized.underline ; } + if ( style & BLINK ) { esc += term.optimized.blink ; } + if ( style & INVERSE ) { esc += term.optimized.inverse ; } + if ( style & HIDDEN ) { esc += term.optimized.hidden ; } + if ( style & STRIKE ) { esc += term.optimized.strike ; } + + return esc ; +} ; + + + +// Generate only the delta between the last and new attributes, may speed up things for the terminal process +// as well as consume less bandwidth, at the cost of small CPU increase in the application process +ScreenBufferHD.prototype.generateDeltaEscapeSequence = function( term , attr , lastAttr ) { + var esc = '' ; + + // Color + if ( + attr[ BPOS_R ] !== lastAttr[ BPOS_R ] || + attr[ BPOS_G ] !== lastAttr[ BPOS_G ] || + attr[ BPOS_B ] !== lastAttr[ BPOS_B ] + ) { + esc += term.optimized.color24bits( attr[ BPOS_R ] , attr[ BPOS_G ] , attr[ BPOS_B ] ) ; + } + + // Bg color + if ( + attr[ BPOS_BG_R ] !== lastAttr[ BPOS_BG_R ] || + attr[ BPOS_BG_G ] !== lastAttr[ BPOS_BG_G ] || + attr[ BPOS_BG_B ] !== lastAttr[ BPOS_BG_B ] + ) { + esc += term.optimized.bgColor24bits( attr[ BPOS_BG_R ] , attr[ BPOS_BG_G ] , attr[ BPOS_BG_B ] ) ; + } + + + var style = attr[ BPOS_STYLE ] ; + var lastStyle = lastAttr[ BPOS_STYLE ] ; + + if ( style !== lastStyle ) { + // Bold and dim style are particular: all terminal has noBold = noDim + + if ( ( style & BOLD_DIM ) !== ( lastStyle & BOLD_DIM ) ) { + if ( ( ( lastStyle & BOLD ) && ! ( style & BOLD ) ) || + ( ( lastStyle & DIM ) && ! ( style & DIM ) ) ) { + esc += term.optimized.noBold ; + if ( style & BOLD ) { esc += term.optimized.bold ; } + if ( style & DIM ) { esc += term.optimized.dim ; } + } + else { + if ( ( style & BOLD ) && ! ( lastStyle & BOLD ) ) { esc += term.optimized.bold ; } + if ( ( style & DIM ) && ! ( lastStyle & DIM ) ) { esc += term.optimized.dim ; } + } + } + + if ( ( style & ITALIC ) !== ( lastStyle & ITALIC ) ) { + esc += style & ITALIC ? term.optimized.italic : term.optimized.noItalic ; + } + + if ( ( style & UNDERLINE ) !== ( lastStyle & UNDERLINE ) ) { + esc += style & UNDERLINE ? term.optimized.underline : term.optimized.noUnderline ; + } + + if ( ( style & BLINK ) !== ( lastStyle & BLINK ) ) { + esc += style & BLINK ? term.optimized.blink : term.optimized.noBlink ; + } + + if ( ( style & INVERSE ) !== ( lastStyle & INVERSE ) ) { + esc += style & INVERSE ? term.optimized.inverse : term.optimized.noInverse ; + } + + if ( ( style & HIDDEN ) !== ( lastStyle & HIDDEN ) ) { + esc += style & HIDDEN ? term.optimized.hidden : term.optimized.noHidden ; + } + + if ( ( style & STRIKE ) !== ( lastStyle & STRIKE ) ) { + esc += style & STRIKE ? term.optimized.strike : term.optimized.noStrike ; + } + } + + return esc ; +} ; + + + + + +/* + Methods that are both static and instance member. + It must be possible to call them without any instance AND invoke instance specific method. +*/ + + + +ScreenBufferHD.attr2object = function( attr ) { + var object = { color: {} , bgColor: {} } ; + + // Color part + object.color.r = attr[ BPOS_R ] ; + object.color.g = attr[ BPOS_G ] ; + object.color.b = attr[ BPOS_B ] ; + object.color.a = attr[ BPOS_A ] ; + + // Background color part + object.bgColor.r = attr[ BPOS_BG_R ] ; + object.bgColor.g = attr[ BPOS_BG_G ] ; + object.bgColor.b = attr[ BPOS_BG_B ] ; + object.bgColor.a = attr[ BPOS_BG_A ] ; + + // Style part + object.bold = !! ( attr[ BPOS_STYLE ] & BOLD ) ; + object.dim = !! ( attr[ BPOS_STYLE ] & DIM ) ; + object.italic = !! ( attr[ BPOS_STYLE ] & ITALIC ) ; + object.underline = !! ( attr[ BPOS_STYLE ] & UNDERLINE ) ; + object.blink = !! ( attr[ BPOS_STYLE ] & BLINK ) ; + object.inverse = !! ( attr[ BPOS_STYLE ] & INVERSE ) ; + object.hidden = !! ( attr[ BPOS_STYLE ] & HIDDEN ) ; + object.strike = !! ( attr[ BPOS_STYLE ] & STRIKE ) ; + + // Misc part + object.styleTransparency = !! ( attr[ BPOS_MISC ] & STYLE_TRANSPARENCY ) ; + object.charTransparency = !! ( attr[ BPOS_MISC ] & CHAR_TRANSPARENCY ) ; + + return object ; +} ; + +ScreenBufferHD.prototype.attr2object = ScreenBufferHD.attr2object ; + + + +ScreenBufferHD.object2attr = function( object ) { + var attr = Buffer.allocUnsafe( ScreenBufferHD.prototype.ATTR_SIZE ) ; + + if ( ! object || typeof object !== 'object' ) { object = {} ; } + + // Misc and color part + attr[ BPOS_MISC ] = 0 ; + + if ( object.color && typeof object.color === 'object' ) { + // Color part + attr[ BPOS_R ] = + object.color.r || 0 ; + attr[ BPOS_G ] = + object.color.g || 0 ; + attr[ BPOS_B ] = + object.color.b || 0 ; + attr[ BPOS_A ] = object.color.a !== undefined ? + object.color.a || 0 : 255 ; + } + else { + attr[ BPOS_R ] = 0 ; + attr[ BPOS_G ] = 0 ; + attr[ BPOS_B ] = 0 ; + attr[ BPOS_A ] = 255 ; + } + + if ( object.bgColor && typeof object.bgColor === 'object' ) { + // Background color part + attr[ BPOS_BG_R ] = + object.bgColor.r || 0 ; + attr[ BPOS_BG_G ] = + object.bgColor.g || 0 ; + attr[ BPOS_BG_B ] = + object.bgColor.b || 0 ; + attr[ BPOS_BG_A ] = object.bgColor.a !== undefined ? + object.bgColor.a || 0 : 255 ; + } + else { + attr[ BPOS_BG_R ] = 0 ; + attr[ BPOS_BG_G ] = 0 ; + attr[ BPOS_BG_B ] = 0 ; + attr[ BPOS_BG_A ] = 255 ; + } + + if ( object.styleTransparency ) { attr[ BPOS_MISC ] |= STYLE_TRANSPARENCY ; } + if ( object.charTransparency ) { attr[ BPOS_MISC ] |= CHAR_TRANSPARENCY ; } + + // Style part + attr[ BPOS_STYLE ] = 0 ; + + if ( object.bold ) { attr[ BPOS_STYLE ] |= BOLD ; } + if ( object.dim ) { attr[ BPOS_STYLE ] |= DIM ; } + if ( object.italic ) { attr[ BPOS_STYLE ] |= ITALIC ; } + if ( object.underline ) { attr[ BPOS_STYLE ] |= UNDERLINE ; } + if ( object.blink ) { attr[ BPOS_STYLE ] |= BLINK ; } + if ( object.inverse ) { attr[ BPOS_STYLE ] |= INVERSE ; } + if ( object.hidden ) { attr[ BPOS_STYLE ] |= HIDDEN ; } + if ( object.strike ) { attr[ BPOS_STYLE ] |= STRIKE ; } + + return attr ; +} ; + +ScreenBufferHD.prototype.object2attr = ScreenBufferHD.object2attr ; + + + +ScreenBufferHD.attrAndObject = function( attr , object ) { + if ( ! object || typeof object !== 'object' ) { return attr ; } + + // Misc and color part + + if ( object.color && typeof object.color === 'object' ) { + if ( object.color.r !== undefined ) { attr[ BPOS_R ] = + object.color.r || 0 ; } + if ( object.color.g !== undefined ) { attr[ BPOS_G ] = + object.color.g || 0 ; } + if ( object.color.b !== undefined ) { attr[ BPOS_B ] = + object.color.b || 0 ; } + if ( object.color.a !== undefined ) { attr[ BPOS_A ] = + object.color.a || 0 ; } + } + + if ( object.bgColor && typeof object.bgColor === 'object' ) { + if ( object.bgColor.r !== undefined ) { attr[ BPOS_BG_R ] = + object.bgColor.r || 0 ; } + if ( object.bgColor.g !== undefined ) { attr[ BPOS_BG_G ] = + object.bgColor.g || 0 ; } + if ( object.bgColor.b !== undefined ) { attr[ BPOS_BG_B ] = + object.bgColor.b || 0 ; } + if ( object.bgColor.a !== undefined ) { attr[ BPOS_BG_A ] = + object.bgColor.a || 0 ; } + } + + if ( object.styleTransparency === true ) { attr[ BPOS_MISC ] |= STYLE_TRANSPARENCY ; } + else if ( object.styleTransparency === false ) { attr[ BPOS_MISC ] &= ~ STYLE_TRANSPARENCY ; } + + if ( object.charTransparency === true ) { attr[ BPOS_MISC ] |= CHAR_TRANSPARENCY ; } + else if ( object.charTransparency === false ) { attr[ BPOS_MISC ] &= ~ CHAR_TRANSPARENCY ; } + + // Style part + if ( object.bold === true ) { attr[ BPOS_STYLE ] |= BOLD ; } + else if ( object.bold === false ) { attr[ BPOS_STYLE ] &= ~ BOLD ; } + + if ( object.dim === true ) { attr[ BPOS_STYLE ] |= DIM ; } + else if ( object.dim === false ) { attr[ BPOS_STYLE ] &= ~ DIM ; } + + if ( object.italic === true ) { attr[ BPOS_STYLE ] |= ITALIC ; } + else if ( object.italic === false ) { attr[ BPOS_STYLE ] &= ~ ITALIC ; } + + if ( object.underline === true ) { attr[ BPOS_STYLE ] |= UNDERLINE ; } + else if ( object.underline === false ) { attr[ BPOS_STYLE ] &= ~ UNDERLINE ; } + + if ( object.blink === true ) { attr[ BPOS_STYLE ] |= BLINK ; } + else if ( object.blink === false ) { attr[ BPOS_STYLE ] &= ~ BLINK ; } + + if ( object.inverse === true ) { attr[ BPOS_STYLE ] |= INVERSE ; } + else if ( object.inverse === false ) { attr[ BPOS_STYLE ] &= ~ INVERSE ; } + + if ( object.hidden === true ) { attr[ BPOS_STYLE ] |= HIDDEN ; } + else if ( object.hidden === false ) { attr[ BPOS_STYLE ] &= ~ HIDDEN ; } + + if ( object.strike === true ) { attr[ BPOS_STYLE ] |= STRIKE ; } + else if ( object.strike === false ) { attr[ BPOS_STYLE ] &= ~ STRIKE ; } + + return attr ; +} ; + +ScreenBufferHD.prototype.attrAndObject = ScreenBufferHD.attrAndObject ; + +// Add the selection flag, i.e. the INVERSE flag +ScreenBufferHD.attrSelect = ScreenBufferHD.prototype.attrSelect = attr => { attr[ BPOS_STYLE ] |= INVERSE ; return attr ; } ; +ScreenBufferHD.attrUnselect = ScreenBufferHD.prototype.attrUnselect = attr => { attr[ BPOS_STYLE ] &= ~ INVERSE ; return attr ; } ; + + + + + +/* Constants */ + + + +// General purpose flags +const NONE = 0 ; // Nothing + +// Attr byte positions +const BPOS_R = 0 ; +const BPOS_G = 1 ; +const BPOS_B = 2 ; +const BPOS_A = 3 ; +const BPOS_BG_R = 4 ; +const BPOS_BG_G = 5 ; +const BPOS_BG_B = 6 ; +const BPOS_BG_A = 7 ; +const BPOS_STYLE = 8 ; +const BPOS_MISC = 9 ; + +const BPOS_FG = 0 ; +const BPOS_BG = 4 ; + + +// Style flags +const BOLD = 1 ; +const DIM = 2 ; +const ITALIC = 4 ; +const UNDERLINE = 8 ; +const BLINK = 16 ; +const INVERSE = 32 ; +const HIDDEN = 64 ; +const STRIKE = 128 ; + +const BOLD_DIM = BOLD | DIM ; + +// Misc flags +const STYLE_TRANSPARENCY = 4 ; +const CHAR_TRANSPARENCY = 8 ; + +// Special color: default terminal color +//const FG_DEFAULT_COLOR = 16 ; +//const BG_DEFAULT_COLOR = 32 ; + +const MISC_ATTR_MASK = STYLE_TRANSPARENCY | CHAR_TRANSPARENCY ; // | FG_DEFAULT_COLOR | BG_DEFAULT_COLOR ; + +// E.g.: if it needs redraw +// Was never implemented, could be replaced by a full-transparency check +//const VOID = 32 ; + +const LEADING_FULLWIDTH = 64 ; +const TRAILING_FULLWIDTH = 128 ; +const FULLWIDTH = LEADING_FULLWIDTH | TRAILING_FULLWIDTH ; +const REMOVE_FULLWIDTH_FLAG = 255 ^ FULLWIDTH ; + +// Unused bits: 1, 2, 16, 32 + + + +// Tuning +const OUTPUT_THRESHOLD = 10000 ; // minimum amount of data to retain before sending them to the terminal + + + +/* + Cell structure: + - 4 bytes: fg rgba + - 4 bytes: bg rgba + - 1 byte: style + - 1 byte: misc/blending flags +*/ + +// Data structure +ScreenBufferHD.prototype.ATTR_SIZE = 10 ; +ScreenBufferHD.prototype.CHAR_SIZE = 4 ; +ScreenBufferHD.prototype.ITEM_SIZE = ScreenBufferHD.prototype.ATTR_SIZE + ScreenBufferHD.prototype.CHAR_SIZE ; + +ScreenBufferHD.DEFAULT_ATTR = // <- used by TextBuffer +ScreenBufferHD.prototype.DEFAULT_ATTR = ScreenBufferHD.object2attr( { + color: { + r: 255 , g: 255 , b: 255 , a: 255 + } , + bgColor: { + r: 0 , g: 0 , b: 0 , a: 255 + } +} ) ; + +ScreenBufferHD.prototype.CLEAR_ATTR = ScreenBufferHD.object2attr( { + color: { + r: 255 , g: 255 , b: 255 , a: 0 + } , + bgColor: { + r: 0 , g: 0 , b: 0 , a: 0 + } , + charTransparency: true , + styleTransparency: true +} ) ; + +ScreenBufferHD.prototype.CLEAR_BUFFER = Buffer.allocUnsafe( ScreenBufferHD.prototype.ITEM_SIZE ) ; +ScreenBufferHD.prototype.CLEAR_ATTR.copy( ScreenBufferHD.prototype.CLEAR_BUFFER ) ; +ScreenBufferHD.prototype.CLEAR_BUFFER.write( ' \x00\x00\x00' , ScreenBufferHD.prototype.ATTR_SIZE ) ; // space + +ScreenBufferHD.prototype.LEADING_FULLWIDTH = LEADING_FULLWIDTH ; +ScreenBufferHD.prototype.TRAILING_FULLWIDTH = TRAILING_FULLWIDTH ; + + + +// Loader/Saver, mostly obsolete + +ScreenBufferHD.loadSyncV2 = function( filepath ) { + var i , content , header , screenBuffer ; + + // Let it crash if nothing found + content = fs.readFileSync( filepath ) ; + + // See if we have got a 'SB' at the begining of the file + if ( content.length < 3 || content.toString( 'ascii' , 0 , 3 ) !== 'SB\n' ) { + throw new Error( 'Magic number mismatch: this is not a ScreenBufferHD file' ) ; + } + + // search for the second \n + for ( i = 3 ; i < content.length ; i ++ ) { + if ( content[ i ] === 0x0a ) { break ; } + } + + if ( i === content.length ) { + throw new Error( 'No header found: this is not a ScreenBufferHD file' ) ; + } + + // Try to parse a JSON header + try { + header = JSON.parse( content.toString( 'utf8' , 3 , i ) ) ; + } + catch( error ) { + throw new Error( 'No correct one-lined JSON header found: this is not a ScreenBufferHD file' ) ; + } + + // Mandatory header field + if ( header.version === undefined || header.width === undefined || header.height === undefined ) { + throw new Error( 'Missing mandatory header data, this is a corrupted or obsolete ScreenBufferHD file' ) ; + } + + // Check bitsPerColor + if ( header.bitsPerColor && header.bitsPerColor !== ScreenBufferHD.prototype.bitsPerColor ) { + throw new Error( 'Bad Bits Per Color: ' + header.bitsPerColor + ' (should be ' + ScreenBufferHD.prototype.bitsPerColor + ')' ) ; + } + + // Bad size? + if ( content.length !== i + 1 + header.width * header.height * ScreenBufferHD.prototype.ITEM_SIZE ) { + throw new Error( 'Bad file size: this is a corrupted ScreenBufferHD file' ) ; + } + + // So the file exists, create a canvas based upon it + screenBuffer = new ScreenBufferHD( { + width: header.width , + height: header.height + } ) ; + + content.copy( screenBuffer.buffer , 0 , i + 1 ) ; + + return screenBuffer ; +} ; + + + +// This new format use JSON header for a maximal flexibility rather than a fixed binary header. +// The header start with a magic number SB\n then a compact single-line JSON that end with an \n. +// So the data part start after the second \n, providing a variable header size. +// This will allow adding meta data without actually changing the file format. +ScreenBufferHD.prototype.saveSyncV2 = function( filepath ) { + var content , header ; + + header = { + version: 2 , + width: this.width , + height: this.height + } ; + + header = 'SB\n' + JSON.stringify( header ) + '\n' ; + + content = Buffer.allocUnsafe( header.length + this.buffer.length ) ; + content.write( header ) ; + + this.buffer.copy( content , header.length ) ; + + // Let it crash if something bad happens + fs.writeFileSync( filepath , content ) ; +} ; + + + +ScreenBufferHD.loadSync = ScreenBufferHD.loadSyncV2 ; +ScreenBufferHD.prototype.saveSync = ScreenBufferHD.prototype.saveSyncV2 ; + + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./ScreenBuffer.js":3,"./colorScheme/gnome.json":11,"./termkit.js":50,"buffer":147,"fs":136,"string-kit":123}],5:[function(require,module,exports){ +(function (process,Buffer){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const tree = require( 'tree-kit' ) ; +const string = require( 'string-kit' ) ; +const NextGenEvents = require( 'nextgen-events' ) ; +const Promise = require( 'seventh' ) ; + +const termkit = require( './termkit.js' ) ; +//function noop() {} + + + +/* + Since there is a lot of hack with the Terminal instance creation, we can't use the 'new' operator at all... +*/ +function Terminal( ... args ) { return Terminal.create( ... args ) ; } + +Terminal.prototype = Object.create( NextGenEvents.prototype ) ; +Terminal.prototype.constructor = Terminal ; +module.exports = Terminal ; + + + +Terminal.create = function( createOptions ) { + // Default options... + if ( ! createOptions || typeof createOptions !== 'object' ) { createOptions = {} ; } + if ( ! createOptions.stdin ) { createOptions.stdin = process.stdin ; } + if ( ! createOptions.stdout ) { createOptions.stdout = process.stdout ; } + if ( ! createOptions.stderr ) { createOptions.stderr = process.stderr ; } + if ( typeof createOptions.generic !== 'string' ) { createOptions.generic = 'xterm' ; } + + var k ; + + var termconfig ; + var chainable = Object.create( notChainable ) ; + var options = { + on: '' , off: '' , params: 0 , out: createOptions.stdout + } ; + + var term = applyEscape.bind( undefined , options ) ; + + // Yay, this is a nasty hack... + Object.setPrototypeOf( term , chainable ) ; + term.apply = Function.prototype.apply ; + term.call = Function.prototype.call ; + + // Fix the root + options.root = term ; + term.root = term ; + + term.options = options ; + term.stdin = createOptions.stdin ; + term.stdout = createOptions.stdout ; + term.stderr = createOptions.stderr ; + term.generic = createOptions.generic ; + term.appId = createOptions.appId ; + term.appName = createOptions.appName ; + term.isTTY = createOptions.isTTY === undefined ? true : !! createOptions.isTTY ; + term.isSSH = !! createOptions.isSSH ; + term.pid = createOptions.pid ; + term.grabbing = false ; + term.mouseGrabbing = false ; + term.focusGrabbing = false ; + term.timeout = term.isSSH ? 500 : 200 ; // The value is a bit high, to prevent lag due to huge terminal load + term.shutdown = false ; + term.raw = term.stdout.write.bind( term.stdout ) ; // Used by ScreenBuffer, for optimization + + term.onStdin = onStdin.bind( term ) ; // bindings... + term.prependStdinChunk = null ; + + term.lock = {} ; + + term.wrapOptions = { + x: 1 , + width: null , + continue: false , + offset: 0 + } ; + + // Screen size + term.width = undefined ; + term.height = undefined ; + onResize.call( term ) ; + + // Resizing event, by order of preference + if ( createOptions.preferProcessSigwinch ) { process.on( 'SIGWINCH' , onResize.bind( term ) ) ; } + else if ( term.stdout.isTTY ) { term.stdout.on( 'resize' , onResize.bind( term ) ) ; } + else if ( createOptions.processSigwinch ) { process.on( 'SIGWINCH' , onResize.bind( term ) ) ; } + + // States + term.state = { + fullscreen: false , + button: { + left: null , + middle: null , + right: null , + other: null + } + } ; + + if ( term.appId ) { + // We have got the real terminal app + try { + term.termconfigFile = term.appId + '.js' ; + termconfig = require( './termconfig/' + term.termconfigFile ) ; + } + catch ( error ) {} // Do nothing, let the next if block handle the case + } + + if ( ! termconfig ) { + // The real terminal app is not known, or we fail to load it... + // Fallback to the terminal generic (most of time, got from the $TERM env variable). + try { + // If a .generic.js file exists, this is a widely used terminal generic, 'xterm' for example. + // We should use this generic files because despite advertising them as 'xterm', + // most terminal sucks at being truly 'xterm' compatible (only 33% to 50% of xterm capabilities + // are supported, even gnome-terminal and Konsole are bad). + // So we will try to maintain a fail-safe xterm generic config. + term.termconfigFile = term.generic + '.generic.js' ; + termconfig = require( './termconfig/' + term.termconfigFile ) ; + } + catch ( error ) { + try { + // No generic config exists, try a specific config + term.termconfigFile = term.generic + '.js' ; + termconfig = require( './termconfig/' + term.termconfigFile ) ; + } + catch ( error_ ) { + // Nothing found, fallback to the most common terminal generic + term.termconfigFile = 'xterm.generic.js' ; + termconfig = require( './termconfig/' + term.termconfigFile ) ; + } + } + } + + //console.log( term.termconfigFile ) ; + + // if needed, this should be replaced by some tput commands? + + term.esc = tree.extend( { deep: true } , {} , termconfig.esc ) ; + term.support = tree.extend( { deep: true } , {} , termconfig.support ) ; + + tree.extend( + null , // Do not use deep:true here + term.esc , + pseudoEsc , + { + // Aliases + gray: term.esc.brightBlack , + grey: term.esc.brightBlack , + bgGray: term.esc.bgBrightBlack , + bgGrey: term.esc.bgBrightBlack + } + ) ; + + term.handler = tree.extend( null , {} , termconfig.handler ) ; + term.keymap = tree.extend( { deep: true } , {} , termconfig.keymap ) ; + term.colorRegister = tree.extend( { deep: true } , [] , defaultColorRegister , termconfig.colorRegister ) ; + + term.escHandler = { root: term } ; + term.escOffHandler = { root: term } ; + + // reverse keymap + term.rKeymap = [] ; + term.rKeymapMaxSize = -1 ; + term.rKeymapStarter = [] ; + term.rKeymapStarterMaxSize = -1 ; + + Object.keys( term.keymap ).forEach( ( key ) => { + + var i , j , keymapObject , code , codeList = term.keymap[ key ] ; + + if ( ! Array.isArray( codeList ) ) { codeList = [ codeList ] ; term.keymap[ key ] = codeList ; } + + for ( j = 0 ; j < codeList.length ; j ++ ) { + code = codeList[ j ] ; + + if ( typeof code === 'object' ) { + keymapObject = code ; + keymapObject.name = key ; + code = keymapObject.code ; + } + else { + keymapObject = { + code: code , + name: key , + matches: [ key ] + } ; + + term.keymap[ key ][ j ] = { code: code } ; + } + + // keymap handler + if ( keymapObject.handler && typeof keymapObject.handler !== 'function' ) { + term.keymap[ key ][ j ].handler = term.handler[ keymapObject.handler ] ; + } + + if ( code ) { + if ( code.length > term.rKeymapMaxSize ) { + for ( i = term.rKeymapMaxSize + 1 ; i <= code.length ; i ++ ) { term.rKeymap[ i ] = {} ; } + term.rKeymapMaxSize = code.length ; + } + + if ( term.rKeymap[ code.length ][ code ] ) { + term.rKeymap[ code.length ][ code ].matches.push( key ) ; + } + else { + term.rKeymap[ code.length ][ code ] = keymapObject ; + term.rKeymap[ code.length ][ code ].matches = [ key ] ; + } + } + else { + if ( ! keymapObject.starter || ! keymapObject.ender || ! keymapObject.handler ) { continue ; } + + if ( keymapObject.starter.length > term.rKeymapStarterMaxSize ) { + for ( i = term.rKeymapStarterMaxSize + 1 ; i <= keymapObject.starter.length ; i ++ ) { term.rKeymapStarter[ i ] = {} ; } + term.rKeymapStarterMaxSize = keymapObject.starter.length ; + } + + if ( term.rKeymapStarter[ keymapObject.starter.length ][ keymapObject.starter ] ) { + term.rKeymapStarter[ keymapObject.starter.length ][ keymapObject.starter ].push( key ) ; + } + else { + term.rKeymapStarter[ keymapObject.starter.length ][ keymapObject.starter ] = [ keymapObject ] ; + } + } + } + } ) ; + + + // Create methods for the 'chainable' prototype + + Object.keys( term.esc ).forEach( ( key ) => { + + if ( ! term.esc[ key ] || typeof term.esc[ key ] !== 'object' ) { + console.error( "Bad escape sequence entry '" + key + "' using termconfig: '" + term.termconfigFile + "'." ) ; + return ; + } + + // build-time resolution + if ( typeof term.esc[ key ].on === 'function' ) { term.esc[ key ].on = term.esc[ key ].on.call( term ) ; } + if ( typeof term.esc[ key ].off === 'function' ) { term.esc[ key ].off = term.esc[ key ].off.call( term ) ; } + + // dynamic handler + if ( term.esc[ key ].handler ) { + if ( typeof term.esc[ key ].handler === 'function' ) { term.escHandler[ key ] = term.esc[ key ].handler.bind( term ) ; } + else { term.escHandler[ key ] = term.handler[ term.esc[ key ].handler ] ; } + } + + // dynamic off handler + if ( term.esc[ key ].offHandler ) { + if ( typeof term.esc[ key ].offHandler === 'function' ) { term.escOffHandler[ key ] = term.esc[ key ].offHandler.bind( term ) ; } + else { term.escOffHandler[ key ] = term.handler[ term.esc[ key ].offHandler ] ; } + } + + Object.defineProperty( chainable , key , { + configurable: true , + get: function() { + var fn , chainOptions ; + + chainOptions = Object.assign( {} , this.options ) ; + + chainOptions.on += this.root.esc[ key ].on || '' ; + chainOptions.off = ( this.root.esc[ key ].off || '' ) + chainOptions.off ; + chainOptions.params += string.format.count( this.root.esc[ key ].on ) ; + + if ( ! chainOptions.onHasFormatting && + ( chainOptions.params || + ( typeof this.root.esc[ key ].on === 'string' && + string.format.hasFormatting( this.root.esc[ key ].on ) ) ) ) { + chainOptions.onHasFormatting = true ; + } + + if ( ! chainOptions.offHasFormatting && + ( typeof this.root.esc[ key ].off === 'string' && + string.format.hasFormatting( this.root.esc[ key ].off ) ) ) { + chainOptions.offHasFormatting = true ; + } + + if ( this.root.esc[ key ].err ) { chainOptions.err = true ; chainOptions.out = this.root.stderr ; } + if ( this.root.esc[ key ].str ) { chainOptions.str = true ; } + if ( this.root.esc[ key ].bind ) { chainOptions.bind = true ; } + if ( this.root.esc[ key ].forceStyleOnReset ) { chainOptions.forceStyleOnReset = true ; } + if ( this.root.esc[ key ].noFormat ) { chainOptions.noFormat = true ; } + if ( this.root.esc[ key ].markupOnly ) { chainOptions.markupOnly = true ; } + if ( this.root.esc[ key ].wrap ) { chainOptions.wrap = true ; } + + fn = applyEscape.bind( undefined , chainOptions ) ; + + // Yay, this is a nasty hack... + Object.setPrototypeOf( fn , chainable ) ; + fn.apply = Function.prototype.apply ; + fn.root = this.root || this ; + fn.options = chainOptions ; + + // Replace the getter by the newly created function, to speed up further call + Object.defineProperty( this , key , { value: fn , configurable: true } ) ; + + //console.log( 'Create function:' , key ) ; + + return fn ; + } + } ) ; + } ) ; + + // Format and markup config + term.resetString = '' ; + term.setResetString = function( str ) { + term.resetString = string.markupMethod.call( term.formatConfig.rawMarkupConfig , str ) ; + } ; + + var resetFn = ( extra ) => term.str.styleReset() + term.resetString + extra ; + + term.formatConfig = { + fn: {} , + endingMarkupReset: true , + markupReset: resetFn.bind( undefined , '' ) , + //markupReset: term.str.styleReset() , + shiftMarkup: { + '#': 'background' + } , + markup: { + ":": resetFn.bind( undefined , '' ) , + " ": resetFn.bind( undefined , ' ' ) , + //":": term.str.styleReset() , + //" ": term.str.styleReset() + ' ' , + + "-": term.str.dim() , + "+": term.str.bold() , + "_": term.str.underline() , + "/": term.str.italic() , + "!": term.str.inverse() , + + "b": term.str.blue() , + "B": term.str.brightBlue() , + "c": term.str.cyan() , + "C": term.str.brightCyan() , + "g": term.str.green() , + "G": term.str.brightGreen() , + "k": term.str.black() , + "K": term.str.brightBlack() , + "m": term.str.magenta() , + "M": term.str.brightMagenta() , + "r": term.str.red() , + "R": term.str.brightRed() , + "w": term.str.white() , + "W": term.str.brightWhite() , + "y": term.str.yellow() , + "Y": term.str.brightYellow() + } , + shiftedMarkup: { + background: { + ":": resetFn.bind( undefined , '' ) , + " ": resetFn.bind( undefined , ' ' ) , + //":": term.str.styleReset() , + //" ": term.str.styleReset() + ' ' , + + "b": term.str.bgBlue() , + "B": term.str.bgBrightBlue() , + "c": term.str.bgCyan() , + "C": term.str.bgBrightCyan() , + "g": term.str.bgGreen() , + "G": term.str.bgBrightGreen() , + "k": term.str.bgBlack() , + "K": term.str.bgBrightBlack() , + "m": term.str.bgMagenta() , + "M": term.str.bgBrightMagenta() , + "r": term.str.bgRed() , + "R": term.str.bgBrightRed() , + "w": term.str.bgWhite() , + "W": term.str.bgBrightWhite() , + "y": term.str.bgYellow() , + "Y": term.str.bgBrightYellow() + } + } + } ; + + term.formatConfig.rawMarkupConfig = Object.create( term.formatConfig ) ; + term.formatConfig.rawMarkupConfig.startingMarkupReset = false ; + term.formatConfig.rawMarkupConfig.endingMarkupReset = false ; + + for ( k in term.escHandler ) { term.formatConfig.fn[ k ] = term.escHandler[ k ] ; } + for ( k in term.escOffHandler ) { term.formatConfig.fn[ k + '_off' ] = term.escOffHandler[ k ] ; } + + term.format = string.createFormatter( term.formatConfig ) ; + term.markup = string.createMarkup( term.formatConfig ) ; + term.options = options ; + + // Should come after any escape sequence definitions + createOptimized( term ) ; + + // Register various exit handler + // Fix the issue #3, turn grabInput off on exit + // Disable input grabbing at exit. + // Note: the terminal can still send some garbage if it was about to do it when exit kickin. + + process.on( 'exit' , () => { + //console.log( '>>> exit' ) ; + // Cleanup was done from elsewhere, probably by .asyncCleanup() + if ( term.shutdown ) { return ; } + term.shutdown = true ; + term.styleReset() ; + term.grabInput( false ) ; + } ) ; + + // Promise.asyncExit() produce this: + process.on( 'asyncExit' , ( code , timeout , done ) => { + //console.log( '>>> asyncExit' ) ; + term.asyncCleanup().then( done ) ; + } ) ; + + // The event loop is empty, we have more time to clean up things: + // We keep the process running for a little bit of time, to prevent the terminal from displaying garbage. + process.once( 'beforeExit' , () => { + //console.log( '>>> beforeExit' ) ; + term.asyncCleanup() ; + } ) ; + + // Should be done at the end, once everything is working: it needs a configured terminal to generate escape sequences + term.palette = new termkit.Palette( { system: true , term: term } ) ; + + return term ; +} ; + + + + + +/* Optimized */ + + + +function createOptimized( term ) { + // This is a subset of the terminal capability, mainly used to speed up ScreenBuffer and ScreenBufferHD + var i ; + + term.optimized = {} ; + + // reset + term.optimized.styleReset = term.str.styleReset() ; + + // Styles + term.optimized.bold = term.str.bold() ; + term.optimized.dim = term.str.dim() ; + term.optimized.italic = term.str.italic() ; + term.optimized.underline = term.str.underline() ; + term.optimized.blink = term.str.blink() ; + term.optimized.inverse = term.str.inverse() ; + term.optimized.hidden = term.str.hidden() ; + term.optimized.strike = term.str.strike() ; + + term.optimized.noBold = term.str.bold( false ) ; + term.optimized.noDim = term.str.dim( false ) ; + term.optimized.noItalic = term.str.italic( false ) ; + term.optimized.noUnderline = term.str.underline( false ) ; + term.optimized.noBlink = term.str.blink( false ) ; + term.optimized.noInverse = term.str.inverse( false ) ; + term.optimized.noHidden = term.str.hidden( false ) ; + term.optimized.noStrike = term.str.strike( false ) ; + + // Colors + term.optimized.color256 = [] ; + term.optimized.bgColor256 = [] ; + + for ( i = 0 ; i <= 255 ; i ++ ) { + term.optimized.color256[ i ] = term.str.color256( i ) ; + term.optimized.bgColor256[ i ] = term.str.bgColor256( i ) ; + } + + term.optimized.defaultColor = term.str.defaultColor() ; + term.optimized.bgDefaultColor = term.str.bgDefaultColor() ; + + // Move To + term.optimized.moveTo = term.esc.moveTo.optimized || term.str.moveTo ; + + // Move 1 char to the right + term.optimized.right = term.str.right( 1 ) ; + + // 24 bits colors + term.optimized.color24bits = term.esc.color24bits.optimized || term.str.color24bits ; + term.optimized.bgColor24bits = term.esc.bgColor24bits.optimized || term.str.bgColor24bits ; +} + + + + + +/* Apply */ + + + +// CAUTION: 'options' MUST NOT BE OVERWRITTEN! +// It is binded at the function creation and contains function specificities! +function applyEscape( options , ... args ) { + var fn , newOptions , wrapOptions ; + + // Cause trouble because the shutting down process itself needs to send escape sequences asynchronously + //if ( options.root.shutdown && ! options.str ) { return options.root ; } + + if ( options.bounded ) { args = options.bounded.concat( args ) ; } + + //console.error( args ) ; + if ( options.bind ) { + newOptions = Object.assign( {} , options , { bind: false , bounded: args } ) ; + fn = applyEscape.bind( this , newOptions ) ; + + // Still a nasty hack... + Object.setPrototypeOf( fn , Object.getPrototypeOf( options.root ) ) ; + fn.apply = Function.prototype.apply ; + fn.root = options.root ; + fn.options = newOptions ; + + return fn ; + } + + var onFormat = [ options.on ] , output , on , off ; + var action = args[ options.params ] ; + + // If not enough arguments, return right now + // Well... what about term.up(), term.previousLine(), and so on? + //if ( arguments.length < 1 + options.params && ( action === null || action === false ) ) { return options.root ; } + + if ( options.params ) { + onFormat = onFormat.concat( args.slice( 0 , options.params ) ) ; + } + + //console.log( '\n>>> Action:' , action , '<<<\n' ) ; + //console.log( 'Attributes:' , attributes ) ; + if ( action === undefined || action === true ) { + on = options.onHasFormatting ? options.root.format( ... onFormat ) : options.on ; + if ( options.str ) { return on ; } + options.out.write( on ) ; + return options.root ; + } + + if ( action === null || action === false ) { + off = options.offHasFormatting ? options.root.format( options.off ) : options.off ; + if ( options.str ) { return off ; } + options.out.write( off ) ; + return options.root ; + } + + if ( typeof action !== 'string' ) { + if ( typeof action.toString === 'function' ) { action = action.toString() ; } + else { action = '' ; } + } + + // So we have got a string + + on = options.onHasFormatting ? options.root.format( ... onFormat ) : options.on ; + + if ( options.markupOnly ) { + action = options.root.markup( ... args.slice( options.params ) ) ; + } + else if ( ! options.noFormat ) { + action = options.root.format( ... args.slice( options.params ) ) ; + } + + if ( options.wrap ) { + if ( options.root.wrapOptions.x && options.root.wrapOptions.x > 1 ) { + wrapOptions = { + width: options.root.wrapOptions.width || options.root.width - options.root.wrapOptions.x + 1 , + glue: '\n' + options.root.str.column( options.root.wrapOptions.x ) , + offset: options.root.wrapOptions.offset , + updateOffset: true , + skipFn: termkit.escapeSequenceSkipFn + } ; + + action = string.wordwrap( action , wrapOptions ) ; + + if ( ! options.root.wrapOptions.continue ) { + action = options.root.str.column( options.root.wrapOptions.x ) + action ; + } + + options.root.wrapOptions.continue = true ; + options.root.wrapOptions.offset = wrapOptions.offset ; + } + else { + wrapOptions = { + width: options.root.wrapOptions.width || options.root.width , + glue: '\n' , + offset: options.root.wrapOptions.offset , + updateOffset: true , + skipFn: termkit.escapeSequenceSkipFn + } ; + + action = string.wordwrap( action , wrapOptions ) ; + options.root.wrapOptions.continue = true ; + options.root.wrapOptions.offset = wrapOptions.offset ; + } + } + else { + // All non-wrapped string display reset the offset + options.root.wrapOptions.continue = false ; + options.root.wrapOptions.offset = 0 ; + } + + off = options.offHasFormatting ? options.root.format( options.off ) : options.off ; + + if ( options.forceStyleOnReset ) { + action = action.replace( new RegExp( string.escape.regExp( options.root.optimized.styleReset ) , 'g' ) , options.root.optimized.styleReset + on ) ; + } + + if ( options.root.resetString ) { + output = options.root.resetString + on + action + off + options.root.resetString ; + } + else { + output = on + action + off ; + } + + // tmp hack? + if ( options.crlf ) { output = output.replace( /\n/g , '\r\n' ) ; } + + if ( options.str ) { return output ; } + + options.out.write( output ) ; + + return options.root ; +} + + + + + +/* Pseudo esc */ + + + +var pseudoEsc = { + // It just set error:true so it will write to STDERR instead of STDOUT + error: { err: true } , + + // It just set str:true so it will not write anything, but return the value in a string + str: { str: true } , + + // It just set attr:true so it will not write anything, but return an attribute object + attr: { attr: true } , + + // It just set bind to an empty array so it will not do anything except returning a wrapper + bindArgs: { bind: true } , + + // It just set forceStyleOnReset:true so it will find style reset and recall the full chain + forceStyleOnReset: { forceStyleOnReset: true } , + + // It just set noFormat:true so it will not call string.format() on user input, + // only useful for ScreenBuffer, so blit-like redraw() can perform slightly faster + noFormat: { noFormat: true } , + + // It just set markupOnly:true so it will not use format string but allow caret ^ markup + markupOnly: { markupOnly: true } , + + // It just set wrap:true so it will wrap words on different lines + wrap: { wrap: true } , + + move: { + on: '%[move:%a%a]F' , + handler: function move( x , y ) { + + var sequence = '' ; + + if ( x ) { + if ( x > 0 ) { sequence += this.root.format( this.root.esc.right.on , x ) ; } + else { sequence += this.root.format( this.root.esc.left.on , -x ) ; } + } + + if ( y ) { + if ( y > 0 ) { sequence += this.root.format( this.root.esc.down.on , y ) ; } + else { sequence += this.root.format( this.root.esc.up.on , -y ) ; } + } + + return sequence ; + } + } , + + color: { + on: '%[color:%a]F' , + off: function() { return this.root.esc.defaultColor.on ; } , + handler: function color( c ) { + if ( typeof c === 'string' ) { c = termkit.colorNameToIndex( c ) ; } + if ( typeof c !== 'number' ) { return '' ; } + + c = Math.floor( c ) ; + + if ( c < 0 || c > 15 ) { return '' ; } + + if ( c <= 7 ) { return this.root.format( this.root.esc.darkColor.on , c ) ; } + return this.root.format( this.root.esc.brightColor.on , c - 8 ) ; + } + } , + + bgColor: { + on: '%[bgColor:%a]F' , + off: function() { return this.root.esc.bgDefaultColor.on ; } , + handler: function bgColor( c ) { + if ( typeof c === 'string' ) { c = termkit.colorNameToIndex( c ) ; } + if ( typeof c !== 'number' ) { return '' ; } + + c = Math.floor( c ) ; + + if ( c < 0 || c > 15 ) { return '' ; } + + if ( c <= 7 ) { return this.root.format( this.root.esc.bgDarkColor.on , c ) ; } + return this.root.format( this.root.esc.bgBrightColor.on , c - 8 ) ; + } + } , + + colorRgb: { + on: '%[colorRgb:%a%a%a]F' , + off: function() { return this.root.esc.defaultColor.on ; } , + handler: colorRgbHandler + + } , + + bgColorRgb: { + on: '%[bgColorRgb:%a%a%a]F' , + off: function() { return this.root.esc.bgDefaultColor.on ; } , + handler: bgColorRgbHandler + } , + + colorRgbHex: { + on: '%[colorRgbHex:%a]F' , + off: function() { return this.root.esc.defaultColor.on ; } , + handler: colorRgbHandler + + } , + + bgColorRgbHex: { + on: '%[bgColorRgbHex:%a]F' , + off: function() { return this.root.esc.bgDefaultColor.on ; } , + handler: bgColorRgbHandler + } , + + colorGrayscale: { + on: '%[colorGrayscale:%a]F' , + off: function() { return this.root.esc.defaultColor.on ; } , + handler: function colorGrayscale( g ) { + var c ; + + if ( typeof g !== 'number' ) { return '' ; } + if ( g < 0 || g > 255 ) { return '' ; } + + if ( ! this.root.esc.color24bits.na && ! this.root.esc.color24bits.fb ) { + // The terminal supports 24bits! Yeah! + return this.root.format( this.root.esc.color24bits.on , g , g , g ) ; + } + + if ( ! this.root.esc.color256.na && ! this.root.esc.color256.fb ) { + // The terminal supports 256 colors + + // Convert to 0..25 range + g = Math.round( g * 25 / 255 ) ; + + if ( g < 0 || g > 25 ) { return '' ; } + + if ( g === 0 ) { c = 16 ; } + else if ( g === 25 ) { c = 231 ; } + else { c = g + 231 ; } + + return this.root.format( this.root.esc.color256.on , c ) ; + } + + // The terminal does not support 256 colors, fallback + c = this.root.registerForRgb( g , g , g , 0 , 15 ) ; + return this.root.format( this.root.esc.color.on , c ) ; + } + } , + + bgColorGrayscale: { + on: '%[bgColorGrayscale:%a]F' , + off: function() { return this.root.esc.bgDefaultColor.on ; } , + handler: function bgColorGrayscale( g ) { + var c ; + + if ( typeof g !== 'number' ) { return '' ; } + if ( g < 0 || g > 255 ) { return '' ; } + + if ( ! this.root.esc.bgColor24bits.na && ! this.root.esc.bgColor24bits.fb ) { + // The terminal supports 24bits! Yeah! + return this.root.format( this.root.esc.bgColor24bits.on , g , g , g ) ; + } + + if ( ! this.root.esc.bgColor256.na && ! this.root.esc.bgColor256.fb ) { + // Convert to 0..25 range + g = Math.round( g * 25 / 255 ) ; + + if ( g < 0 || g > 25 ) { return '' ; } + + if ( g === 0 ) { c = 16 ; } + else if ( g === 25 ) { c = 231 ; } + else { c = g + 231 ; } + + return this.root.format( this.root.esc.bgColor256.on , c ) ; + } + + // The terminal does not support 256 colors, fallback + c = this.root.registerForRgb( g , g , g , 0 , 15 ) ; + return this.root.format( this.root.esc.bgColor.on , c ) ; + } + } +} ; + + + + + +/* Internal/private functions */ + + + +function colorRgbHandler( r , g , b ) { + var c , rgb ; + + if ( typeof r === 'string' ) { + rgb = termkit.hexToRgba( r ) ; + r = rgb.r ; g = rgb.g ; b = rgb.b ; + } + + if ( + typeof r !== 'number' || isNaN( r ) || + typeof g !== 'number' || isNaN( g ) || + typeof b !== 'number' || isNaN( b ) || + r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 + ) { + return '' ; + } + + if ( ! this.root.esc.color24bits.na && ! this.root.esc.color24bits.fb ) { + // The terminal supports 24bits! Yeah! + return this.root.format( this.root.esc.color24bits.on , r , g , b ) ; + } + + if ( ! this.root.esc.color256.na && ! this.root.esc.color256.fb ) { + // The terminal supports 256 colors + + // Convert to 0..5 range + r = Math.round( r * 5 / 255 ) ; + g = Math.round( g * 5 / 255 ) ; + b = Math.round( b * 5 / 255 ) ; + + c = 16 + r * 36 + g * 6 + b ; + + // min:16 max:231 + //if ( c < 16 || c > 231 ) { return '' ; } + + return this.root.format( this.root.esc.color256.on , c ) ; + } + + // The terminal does not support 256 colors, fallback + c = this.root.registerForRgb( r , g , b , 0 , 15 ) ; + return this.root.format( this.root.esc.color.on , c ) ; +} + + + +function bgColorRgbHandler( r , g , b ) { + var c , rgb ; + + if ( typeof r === 'string' ) { + rgb = termkit.hexToRgba( r ) ; + r = rgb.r ; g = rgb.g ; b = rgb.b ; + } + + if ( + typeof r !== 'number' || isNaN( r ) || + typeof g !== 'number' || isNaN( g ) || + typeof b !== 'number' || isNaN( b ) || + r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 + ) { + return '' ; + } + + if ( ! this.root.esc.bgColor24bits.na && ! this.root.esc.bgColor24bits.fb ) { + // The terminal supports 24bits! Yeah! + return this.root.format( this.root.esc.bgColor24bits.on , r , g , b ) ; + } + + if ( ! this.root.esc.bgColor256.na && ! this.root.esc.bgColor256.fb ) { + // The terminal supports 256 colors + + // Convert to 0..5 range + r = Math.round( r * 5 / 255 ) ; + g = Math.round( g * 5 / 255 ) ; + b = Math.round( b * 5 / 255 ) ; + + c = 16 + r * 36 + g * 6 + b ; + + // min:16 max:231 + //if ( c < 16 || c > 231 ) { return '' ; } + + return this.root.format( this.root.esc.bgColor256.on , c ) ; + } + + // The terminal does not support 256 colors, fallback + c = this.root.registerForRgb( r , g , b , 0 , 15 ) ; + return this.root.format( this.root.esc.bgColor.on , c ) ; +} + + + +// Called by either SIGWINCH signal or stdout's 'resize' event. +// It is not meant to be used by end-user. +function onResize() { + if ( this.stdout.columns && this.stdout.rows ) { + this.width = this.stdout.columns ; + this.height = this.stdout.rows ; + } + + this.emit( 'resize' , this.width , this.height ) ; +} + + + + + +/* Advanced methods */ + + + +// Complexes functions that cannot be chained. +// It is the ancestors of the terminal object, so it should inherit from async.EventEmitter. +var notChainable = Object.create( Terminal.prototype ) ; + + + +// Complexes high-level features have their own file +notChainable.yesOrNo = require( './yesOrNo.js' ) ; +notChainable.inputField = require( './inputField.js' ) ; +notChainable.fileInput = require( './fileInput.js' ) ; +notChainable.singleRowMenu = notChainable.singleLineMenu = require( './singleLineMenu.js' ) ; +notChainable.singleColumnMenu = require( './singleColumnMenu.js' ) ; +notChainable.gridMenu = require( './gridMenu.js' ) ; +notChainable.progressBar = require( './progressBar.js' ) ; +notChainable.bar = require( './bar.js' ) ; +notChainable.slowTyping = require( './slowTyping.js' ) ; + + + +notChainable.createDocument = function( options ) { + if ( ! options || typeof options !== 'object' ) { options = {} ; } + options.outputDst = this ; + options.eventSource = this ; + return new termkit.Document( options ) ; +} ; + + + +notChainable.createInlineElement = function( Type , options ) { + return termkit.Element.createInline( this , Type , options ) ; +} ; + + + +notChainable.table = function( table , options = {} ) { + return termkit.Element.createInline( this , termkit.TextTable , + Object.assign( {} , options , { + cellContents: table , + fit: options.fit !== undefined ? !! options.fit : true + } ) + ) ; +} ; + + + +notChainable.spinner = function( options = {} ) { + if ( typeof options === 'string' ) { options = { animation: options } ; } + return termkit.Element.createInline( this , termkit.AnimatedText , options ) ; +} ; + + + +/* + .wrapColumn() + .wrapColumn( width ) + .wrapColumn( x , width ) + .wrapColumn( namedParameters ) + + width: the width for word-wrapping, null for terminal width + x: x position (for column wrapping) + continue: true if we are continuing + offset: offset of the first line (default: 0) +*/ +notChainable.wrapColumn = function( ... args ) { + this.wrapOptions.continue = false ; + this.wrapOptions.offset = 0 ; + + if ( ! args.length ) { return ; } + + if ( args[ 0 ] && typeof args[ 0 ] === 'object' ) { + Object.assign( this.wrapOptions , args[ 0 ] ) ; + return this.wrap ; + } + + if ( args.length === 1 ) { + this.wrapOptions.x = 1 ; + this.wrapOptions.width = args[ 0 ] ; + return this.wrap ; + } + + this.wrapOptions.x = args[ 0 ] ; + this.wrapOptions.width = args[ 1 ] ; + return this.wrap ; +} ; + + + +// Fail-safe alternate screen buffer +notChainable.fullscreen = function( options ) { + if ( options === false ) { + if ( ! this.state.fullscreen ) { return this ; } + + // Disable fullscreen mode + this.state.fullscreen = false ; + this.moveTo( 1 , this.height , '\n' ) ; + this.alternateScreenBuffer( false ) ; + return this ; + } + + if ( ! options ) { options = {} ; } + + this.state.fullscreen = true ; + if ( ! options.noAlternate ) { this.alternateScreenBuffer( true ) ; } + this.clear() ; +} ; + + + + + +/* Input management */ + + + +function onStdin( chunk ) { + var i , j , buffer , startBuffer , char , codepoint , + keymapCode , keymapStartCode , keymap , keymapList , + regexp , matches , bytes , found , handlerResult , + accumulate = false , + index = 0 , length = chunk.length ; + + if ( this.shutdown ) { return ; } + + if ( this.prependStdinChunk ) { + chunk = Buffer.concat( [ this.prependStdinChunk , chunk ] ) ; + } + + while ( index < length ) { + found = false ; + bytes = 1 ; + + if ( chunk[ index ] <= 0x1f || chunk[ index ] === 0x7f ) { + // Those are ASCII control character and DEL key + + for ( i = Math.min( length , Math.max( this.rKeymapMaxSize , this.rKeymapStarterMaxSize ) ) ; i > 0 ; i -- ) { + buffer = chunk.slice( index ) ; + keymapCode = buffer.toString() ; + startBuffer = chunk.slice( index , index + i ) ; + keymapStartCode = startBuffer.toString() ; + + + if ( this.rKeymap[ i ] && this.rKeymap[ i ][ keymapStartCode ] ) { + // First test fixed sequences + + keymap = this.rKeymap[ i ][ keymapStartCode ] ; + found = true ; + + if ( keymap.handler ) { + handlerResult = keymap.handler.call( this , keymap.name , chunk.slice( index + i ) ) ; + bytes = i + handlerResult.eaten ; + + if ( ! handlerResult.disable ) { + this.emit( keymap.event , handlerResult.name , handlerResult.data ) ; + } + } + else if ( keymap.event ) { + bytes = i ; + this.emit( keymap.event , keymap.name , keymap.data , { code: startBuffer } ) ; + } + else { + bytes = i ; + this.emit( 'key' , keymap.name , keymap.matches , { isCharacter: false , code: startBuffer } ) ; + } + + break ; + } + else if ( this.rKeymapStarter[ i ] && this.rKeymapStarter[ i ][ keymapStartCode ] ) { + // Then test pattern sequences + + keymapList = this.rKeymapStarter[ i ][ keymapStartCode ] ; + + //console.log( 'for i:' , keymapList ) ; + + for ( j = 0 ; j < keymapList.length ; j ++ ) { + keymap = keymapList[ j ] ; + + if ( keymap.altEnder ) { + regexp = '^' + + string.escape.regExp( keymap.starter ) + + '([ -~]*?)' + // [ -~] match only all ASCII non-control character + '(' + string.escape.regExp( keymap.ender ) + '|' + string.escape.regExp( keymap.altEnder ) + ')' ; + } + else { + regexp = '^' + + string.escape.regExp( keymap.starter ) + + '([ -~]*?)' + // [ -~] match only all ASCII non-control character + string.escape.regExp( keymap.ender ) ; + } + + matches = keymapCode.match( new RegExp( regexp ) , 'g' ) ; + + //console.log( 'for j:' , keymap , regexp , matches ) ; + + if ( matches ) { + found = true ; + + handlerResult = keymap.handler.call( this , keymap.name , matches[ 1 ] ) ; + bytes = matches[ 0 ].length ; + this.emit( keymap.event , handlerResult.name , handlerResult.data ) ; + + break ; + } + else if ( keymap.accumulate ) { + found = true ; + accumulate = true ; + break ; + } + } + + if ( found ) { break ; } + } + } + + // Nothing was found, so to not emit trash, we just abort the current buffer processing + if ( ! found ) { this.emit( 'unknown' , chunk ) ; break ; } + } + else if ( chunk[ index ] >= 0x80 ) { + // Unicode bytes per char guessing + if ( chunk[ index ] < 0xc0 ) { continue ; } // We are in a middle of an unicode multibyte sequence... Something fails somewhere, we will just continue for now... + else if ( chunk[ index ] < 0xe0 ) { bytes = 2 ; } + else if ( chunk[ index ] < 0xf0 ) { bytes = 3 ; } + else if ( chunk[ index ] < 0xf8 ) { bytes = 4 ; } + else if ( chunk[ index ] < 0xfc ) { bytes = 5 ; } + else { bytes = 6 ; } + + buffer = chunk.slice( index , index + bytes ) ; + char = buffer.toString( 'utf8' ) ; + + //if ( bytes > 2 ) { codepoint = punycode.ucs2.decode( char )[ 0 ] ; } + if ( bytes > 2 ) { codepoint = string.unicode.firstCodePoint( char ) ; } + else { codepoint = char.charCodeAt( 0 ) ; } + + this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: codepoint , code: buffer } ) ; + } + else { + // Standard ASCII + char = String.fromCharCode( chunk[ index ] ) ; + this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: chunk[ index ] , code: chunk[ index ] } ) ; + } + + index += bytes ; + } + + if ( accumulate ) { + this.prependStdinChunk = chunk ; + } + else { + this.prependStdinChunk = null ; + } +} + + + +/* + * options `false` or `Object` where: + * mouse `string` one of: + * 'button' + * 'drag' + * 'motion' + * focus `boolean` + * safe `boolean` (optional), when set and when *options* is set to `false`, it turns *.grabInput()* + it returns a promise that resolve when input grabbing is safely turned off, avoiding extra junks to + be echoed when the terminal left the raw mode. It is mostly useful after grabbing mouse motion. +*/ +notChainable.grabInput = function( options , safe ) { + // RESET + this.mouseButton( false ) ; + this.mouseDrag( false ) ; + this.mouseMotion( false ) ; + //this.mouseSGR( false ) ; + this.focusEvent( false ) ; + this.stdin.removeListener( 'data' , this.onStdin ) ; + + // Start disabling everything + this.grabbing = false ; + this.mouseGrabbing = false ; + this.focusGrabbing = false ; + + // Disable grabInput mode + var disable = () => { + // Very important: removing all listeners don't switch back to pause mode. + // This is some nasty Node.js quirks (the documentation pleads for backward compatibility). + this.stdin.pause() ; + + try { + this.stdin.setRawMode( false ) ; + } + catch ( error ) { + // That's not critical in any way and thus can be ignored: we are probably reading from a non-TTY + } + } ; + + if ( options === false ) { + if ( safe ) { + return Promise.resolveSafeTimeout( this.timeout / 2 ).then( disable ) ; + } + + disable() ; + return Promise.resolved ; + } + + // Should not be moved before, because shutdown typically needs .grabInput( false ) + if ( this.shutdown ) { return Promise.resolved ; } + + this.grabbing = true ; + + if ( ! options ) { options = {} ; } + + // SET + try { + this.stdin.setRawMode( true ) ; + } + catch ( error ) { + // Same here, that's not critical in any way and thus can be ignored: we are probably reading from a non-TTY + } + + this.stdin.on( 'data' , this.onStdin ) ; + + // Very important: after the first this.stdin.pause(), listening for data seems to not switch back to flowing mode. + // Again, a nasty Node.js quirk. + this.stdin.resume() ; + + if ( options.mouse ) { + this.mouseGrabbing = true ; + + switch ( options.mouse ) { + case 'button' : this.mouseButton.mouseSGR() ; break ; + case 'drag' : this.mouseDrag.mouseSGR() ; break ; + case 'motion' : this.mouseMotion.mouseSGR() ; break ; + } + } + + if ( options.focus ) { + this.focusEvent() ; + this.focusGrabbing = true ; + } + + return Promise.resolved ; +} ; + + + +// Like process.exit(), but perform cleanup of the terminal first. +// It is asynchronous, so it should be followed by a 'return' if needed. +// A better way to handle that is to use Promise.asyncExit(), that is detected by the Terminal instance. +notChainable.processExit = function( code ) { + this( '\n' ) ; + this.asyncCleanup().then( () => process.exit( code ) ) ; +} ; + + + +notChainable.asyncCleanup = async function() { + if ( this.shutdown ) { return ; } + this.shutdown = true ; + + this.styleReset() ; + + var wasGrabbing = this.grabbing ; + + await this.waitStreamDone( this.stdout ) ; + + if ( ! this.isTTY || ! wasGrabbing ) { return ; } + + await Promise.resolveSafeTimeout( this.timeout / 4 ) ; + return this.grabInput( false , true ) ; +} ; + + + +notChainable.waitStreamDone = function( stream ) { + if ( ! stream._writableState.needDrain ) { return Promise.resolved ; } + return Promise.onceEvent( stream , 'drain' ) ; +} ; + + + +notChainable.object2attr = function( object ) { + var attr = this.esc.styleReset.on ; + + if ( ! object || typeof object !== 'object' ) { object = {} ; } + + // Color part + if ( typeof object.color === 'string' ) { object.color = termkit.colorNameToIndex( object.color ) ; } + if ( typeof object.color !== 'number' || object.color < 0 || object.color > 255 ) { object.color = 7 ; } + else { object.color = Math.floor( object.color ) ; } + + attr += this.str.color( object.color ) ; + + // Background color part + if ( typeof object.bgColor === 'string' ) { object.bgColor = termkit.colorNameToIndex( object.bgColor ) ; } + if ( typeof object.bgColor !== 'number' || object.bgColor < 0 || object.bgColor > 255 ) { object.bgColor = 0 ; } + else { object.bgColor = Math.floor( object.bgColor ) ; } + + attr += this.str.bgColor( object.bgColor ) ; + + // Style part + if ( object.bold ) { attr += this.esc.bold.on ; } + if ( object.dim ) { attr += this.esc.dim.on ; } + if ( object.italic ) { attr += this.esc.italic.on ; } + if ( object.underline ) { attr += this.esc.underline.on ; } + if ( object.blink ) { attr += this.esc.blink.on ; } + if ( object.inverse ) { attr += this.esc.inverse.on ; } + if ( object.hidden ) { attr += this.esc.hidden.on ; } + if ( object.strike ) { attr += this.esc.strike.on ; } + + return attr ; +} ; + + + +// Erase a whole rectangular area +// .eraseArea( x , y , [width] , [height] ) +notChainable.eraseArea = function( xMin , yMin , width = 1 , height = 1 ) { + xMin = Math.min( xMin , this.width ) ; + yMin = Math.min( yMin , this.height ) ; + + var y , + xMax = Math.min( xMin + width , this.width + 1 ) , + yMax = Math.min( yMin + height , this.height + 1 ) , + str = ' '.repeat( xMax - xMin ) ; + + for ( y = yMin ; y < yMax ; y ++ ) { + this.moveTo( xMin , y , str ) ; + } +} ; + + + +// A facility for those who don't want to deal with requestCursorLocation() and events... +notChainable.getCursorLocation = function( callback ) { + var wasGrabbing = this.grabbing , alreadyCleanedUp = false , error ; + + if ( this.shutdown ) { return Promise.resolved ; } + + // First, check capabilities: + if ( this.esc.requestCursorLocation.na ) { + error = new Error( 'Terminal is not capable' ) ; + if ( callback ) { + callback( error ) ; + return Promise.resolved ; + } + + return Promise.reject( error ) ; + } + + var promise = new Promise() ; + + // Now .getCursorLocation() cannot run in concurrency anymore + if ( this.lock.getCursorLocation ) { + this.once( 'unlock_getCursorLocation' , () => { + this.getCursorLocation().then( + data => { + if ( callback ) { callback( undefined , data.x , data.y ) ; } + else { promise.resolve( data ) ; } + } , + error_ => { + if ( callback ) { callback( error_ ) ; } + else { promise.reject( error_ ) ; } + } + ) ; + } ) ; + + return promise ; + } + + this.lock.getCursorLocation = true ; + + var cleanup = ( error_ , x , y ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + this.removeListener( 'terminal' , onTerminal ) ; + if ( ! wasGrabbing ) { this.grabInput( false ) ; } + + if ( error_ ) { + if ( this.shutdown ) { error_.code = 'shutdown' ; } + + if ( callback ) { callback( error_ ) ; } + else { promise.reject( error_ ) ; } + return ; + } + + if ( callback ) { callback( undefined , x , y ) ; } + else { promise.resolve( { x , y } ) ; } + } ; + + var onTerminal = ( name , data ) => { + if ( name !== 'CURSOR_LOCATION' ) { return ; } + this.lock.getCursorLocation = false ; + this.emit( 'unlock_getCursorLocation' ) ; + cleanup( undefined , data.x , data.y ) ; + } ; + + if ( ! wasGrabbing ) { this.grabInput() ; } + + this.on( 'terminal' , onTerminal ) ; + this.requestCursorLocation() ; + + Promise.resolveSafeTimeout( this.timeout ).then( () => { + if ( alreadyCleanedUp ) { return ; } + var error_ = new Error( '.getCursorLocation() timed out' ) ; + error_.code = 'timeout' ; + cleanup( error_ ) ; + } ) ; + + return promise ; +} ; + + + +// Get the RGB value for a color register +notChainable.getColor = function( register , callback ) { + var wasGrabbing = this.grabbing , alreadyCleanedUp = false , error ; + + if ( this.shutdown ) { return Promise.resolved ; } + + // First, check capabilities: + if ( this.esc.requestColor.na ) { + error = new Error( 'Terminal is not capable' ) ; + + if ( callback ) { + callback( error ) ; + return Promise.resolved ; + } + + return Promise.reject( error ) ; + } + + var promise = new Promise() ; + + var cleanup = ( error_ , data ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + this.removeListener( 'terminal' , onTerminal ) ; + if ( ! wasGrabbing ) { this.grabInput( false ) ; } + + if ( error_ ) { + if ( this.shutdown ) { error_.code = 'shutdown' ; } + + if ( callback ) { callback( error_ ) ; } + else { promise.reject( error_ ) ; } + return ; + } + + if ( callback ) { callback( undefined , data ) ; } + else { promise.resolve( data ) ; } + } ; + + var onTerminal = ( name , data ) => { + + if ( name !== 'COLOR_REGISTER' ) { return ; } + + // We have got a color definition, but this is not for our register, so this is not our response + if ( data.register !== register ) { return ; } + + // This is a good opportunity to update the color register + if ( register < 16 ) { this.colorRegister[ register ] = { r: data.r , g: data.g , b: data.b } ; } + + // Everything is fine... + cleanup( undefined , data ) ; + } ; + + if ( ! wasGrabbing ) { this.grabInput() ; } + + this.requestColor( register ) ; + this.on( 'terminal' , onTerminal ) ; + + Promise.resolveSafeTimeout( this.timeout ).then( () => { + if ( alreadyCleanedUp ) { return ; } + var error_ = new Error( '.getColor() timed out' ) ; + error_.code = 'timeout' ; + cleanup( error_ ) ; + } ) ; + + return promise ; +} ; + + + +// Get the current 16 colors palette of the terminal, if possible +notChainable.getPalette = function( callback ) { + var defaultPalette , + wasGrabbing = this.grabbing ; + + if ( this.shutdown ) { return Promise.resolved ; } + + if ( ! wasGrabbing ) { this.grabInput() ; } + + // First, check capabilities, if not capable, return the default palette + if ( this.esc.requestColor.na ) { + defaultPalette = this.colorRegister.slice( 0 , 16 ) ; + + if ( callback ) { + callback( undefined , defaultPalette ) ; + return Promise.resolved ; + } + + return Promise.resolve( defaultPalette ) ; + } + + return Promise.concurrent( + 4 , + [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 ] , + register => this.getColor( register ) + ) + .then( + palette => { + if ( ! wasGrabbing ) { this.grabInput( false ) ; } + if ( callback ) { callback( undefined , palette ) ; } + else { return palette ; } + } , + error => { + if ( ! wasGrabbing ) { this.grabInput( false ) ; } + + if ( callback ) { callback( error ) ; } + else { throw error ; } // re-throw if not using callback + } + ) ; +} ; + + + +// Set the color for a register +notChainable.setColor = function( register , r , g , b , names ) { + if ( r && typeof r === 'object' ) { + b = r.b ; + g = r.g ; + r = r.r ; + names = g ; + } + + // Allow modification of register > 15 ? It looks like terminals don't allow it... (at least not gnome) + if ( typeof register !== 'number' || register < 0 || register > 15 ) { throw new Error( 'Bad register value' ) ; } + + if ( ! Array.isArray( names ) ) { names = [] ; } + + if ( + typeof r !== 'number' || r < 0 || r > 255 || + typeof g !== 'number' || g < 0 || g > 255 || + typeof b !== 'number' || b < 0 || b > 255 + ) { + throw new Error( 'Bad RGB value' ) ; + } + + // Issue an error, or not? + if ( this.setColorLL.na ) { return ; } + + // This is a good opportunity to update the color register + this.colorRegister[ register ] = { + r: r , g: g , b: b , names: names + } ; + + // Call the Low Level set color + this.setColorLL( register , r , g , b ) ; +} ; + + + +// Set the current 16 colors palette of the terminal, if possible +notChainable.setPalette = function( palette ) { + var i ; + + if ( typeof palette === 'string' ) { + try { + palette = require( './colorScheme/' + palette + '.json' ) ; + } + catch( error ) { + throw new Error( '[terminal] .setPalette(): color scheme not found: ' + palette ) ; + } + } + + if ( ! Array.isArray( palette ) ) { throw new Error( '[terminal] .setPalette(): argument #0 should be an Array of RGB Object or a built-in color scheme' ) ; } + + // Issue an error, or not? + if ( this.setColorLL.na ) { return ; } + + for ( i = 0 ; i <= 15 ; i ++ ) { + if ( ! palette[ i ] || typeof palette[ i ] !== 'object' ) { continue ; } + this.setColor( i , palette[ i ] ) ; + } +} ; + + + +notChainable.getClipboard = function( source = 'c' ) { + var wasGrabbing = this.grabbing , alreadyCleanedUp = false , extClipboard ; + + if ( this.shutdown ) { return Promise.resolved ; } + + // First, check capabilities: + if ( this.esc.requestClipboard.na ) { + extClipboard = require( './extClipboard.js' ) ; + return extClipboard.getClipboard( source ).catch( () => '' ) ; + } + + var promise = new Promise() ; + + var cleanup = ( error_ , data ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + this.removeListener( 'terminal' , onTerminal ) ; + if ( ! wasGrabbing ) { this.grabInput( false ) ; } + + if ( error_ ) { + if ( this.shutdown ) { error_.code = 'shutdown' ; } + promise.reject( error_ ) ; + return ; + } + + promise.resolve( data ) ; + } ; + + var onTerminal = ( name , data ) => { + //console.log( "EVENT: " , name , data ) ; + if ( name !== 'CLIPBOARD' ) { return ; } + + // We have got some content, but this is not for our source, so this is not our response + //if ( data.source !== source ) { return ; } + + // Everything is fine... + cleanup( undefined , data.content ) ; + } ; + + if ( ! wasGrabbing ) { this.grabInput() ; } + + this.requestClipboard( source[ 0 ] ) ; + this.on( 'terminal' , onTerminal ) ; + + Promise.resolveSafeTimeout( this.timeout ).then( () => { + if ( alreadyCleanedUp ) { return ; } + var error_ = new Error( '.getClipboard() timed out' ) ; + error_.code = 'timeout' ; + cleanup( error_ ) ; + } ) ; + + return promise ; +} ; + + + +// Set the color for a register +notChainable.setClipboard = async function( str , source = 'c' ) { + var extClipboard ; + + if ( this.esc.setClipboardLL.na ) { + extClipboard = require( './extClipboard.js' ) ; + return extClipboard.setClipboard( str , source ).catch( () => undefined ) ; + } + + var base64 = Buffer.from( str ).toString( 'base64' ) ; + + //console.log( "base64:" , base64 , "retro:" , Buffer.from( base64 , 'base64' ).toString() ) ; + + // Call the Low Level set clipboard + this.setClipboardLL( source[ 0 ] , base64 ) ; + return Promise.resolved ; +} ; + + + +// A facility for those who don't want to deal with requestCursorLocation() and events... +notChainable.getTerminfo = function( key ) { + var wasGrabbing = this.grabbing , alreadyCleanedUp = false ; + + if ( this.shutdown ) { return Promise.resolved ; } + + // First, check capabilities: + if ( this.esc.xtgettcapLL.na ) { + return Promise.reject( new Error( 'Terminal is not capable' ) ) ; + } + + var promise = new Promise() ; + + // Avoid concurrency + if ( this.lock.getTerminfo ) { + this.once( 'unlock_getTerminfo' , () => this.getTerminfo( key ).propagate( promise ) ) ; + return promise ; + } + + this.lock.getTerminfo = true ; + + var cleanup = ( error , data ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + this.removeListener( 'terminal' , onTerminal ) ; + if ( ! wasGrabbing ) { this.grabInput( false ) ; } + + if ( error ) { + if ( this.shutdown ) { error.code = 'shutdown' ; } + promise.reject( error ) ; + return ; + } + + if ( ! data.valid || data.key !== key ) { + promise.resolve() ; + return ; + } + + promise.resolve( data.value ) ; + } ; + + var onTerminal = ( name , data ) => { + if ( name !== 'TERMINFO' ) { return ; } + this.lock.getTerminfo = false ; + this.emit( 'unlock_getTerminfo' ) ; + cleanup( undefined , data ) ; + } ; + + if ( ! wasGrabbing ) { this.grabInput() ; } + + this.on( 'terminal' , onTerminal ) ; + //this.xtgettcapLL( keys.map( key => Buffer.from( key ).toString( 'hex' ) ).join( ';' ) ) ; + this.xtgettcapLL( Buffer.from( key ).toString( 'hex' ) ) ; + + Promise.resolveSafeTimeout( this.timeout ).then( () => { + if ( alreadyCleanedUp ) { return ; } + var error = new Error( '.getTerminfo() timed out' ) ; + error.code = 'timeout' ; + cleanup( error ) ; + } ) ; + + return promise ; +} ; + + + + + +/* Utilities */ + + + +// Default colors, used for guessing +var defaultColorRegister = require( './colorScheme/default.json' ) ; + +( function buildDefaultColorRegister() { + var register , offset , factor , l ; + + for ( register = 16 ; register < 232 ; register ++ ) { + // RGB 6x6x6 + offset = register - 16 ; + factor = 255 / 5 ; + defaultColorRegister[ register ] = { + r: Math.round( ( Math.floor( offset / 36 ) % 6 ) * factor ) , + g: Math.round( ( Math.floor( offset / 6 ) % 6 ) * factor ) , + b: Math.round( ( offset % 6 ) * factor ) , + names: [] + } ; + } + + for ( register = 232 ; register <= 255 ; register ++ ) { + // Grayscale 0..23 + offset = register - 231 ; // not 232, because the first of them is not a #000000 black + factor = 255 / 25 ; // not 23, because the last is not a #ffffff white + l = Math.round( offset * factor ) ; + defaultColorRegister[ register ] = { + r: l , g: l , b: l , names: [] + } ; + } +} )() ; + + + +// If register hasn't changed, this is used to get the RGB value for them +notChainable.rgbForRegister = function( register ) { + if ( register < 0 || register > 255 ) { throw new Error( 'Bad register value' ) ; } + + // Simply clone it + return { + r: this.colorRegister[ register ].r , + g: this.colorRegister[ register ].g , + b: this.colorRegister[ register ].b + } ; +} ; + + + +// If register hasn't changed, this is used to get it for an RGB +// .registerForRgb( r , g , b , [minRegister] , [maxRegister] ) +// .registerForRgb( rgbObject , [minRegister] , [maxRegister] ) + +// Lab HCL cylinder coordinate distance +notChainable.registerForRgb = function( r , g , b , minRegister , maxRegister ) { + // Manage function arguments + if ( r && typeof r === 'object' ) { + // Manage the .registerForRgb( rgbObject , [minRegister] , [maxRegister] ) variante + maxRegister = b ; + minRegister = g ; + b = r.b ; + g = r.g ; + r = r.r ; + } + + if ( + typeof r !== 'number' || r < 0 || r > 255 || + typeof g !== 'number' || g < 0 || g > 255 || + typeof b !== 'number' || b < 0 || b > 255 + ) { + throw new Error( 'Bad RGB value' ) ; + } + + if ( typeof maxRegister !== 'number' || maxRegister < 0 || maxRegister > 255 ) { maxRegister = 15 ; } + if ( typeof minRegister !== 'number' || minRegister < 0 || minRegister > 255 ) { minRegister = 0 ; } + + if ( minRegister > maxRegister ) { + var tmp ; + tmp = maxRegister ; + maxRegister = minRegister ; + minRegister = tmp ; + } + + return this._registerForRgb( r , g , b , minRegister , maxRegister ) ; +} ; + + + +notChainable._registerForRgb = function( r , g , b , minRegister , maxRegister ) { + // Search for the best match + var register , delta , + minDelta = Infinity , + rgb = [ r , g , b ] ; + + for ( register = minRegister ; register <= maxRegister ; register ++ ) { + delta = termkit.chroma.distance( rgb , this.colorRegister[ register ] , 'hcl' ) ; + if ( delta < minDelta ) { + minDelta = delta ; + minRegister = register ; + } + } + + return minRegister ; +} ; + + + +notChainable.colorNameForRgb = function( r , g , b ) { + return termkit.indexToColorName( this.registerForRgb( r , g , b , 0 , 15 ) ) ; +} ; + + + +notChainable.colorNameForHex = function( hex ) { + var rgba = termkit.hexToRgba( hex ) ; + return this.colorNameForRgb( rgba.r , rgba.g , rgba.b ) ; +} ; + + + +notChainable.registerForRgbCache = function( cache , r , g , b , minRegister , maxRegister ) { + var key = r + '-' + g + '-' + b ; + if ( cache[ key ] ) { return cache[ key ] ; } + return ( cache[ key ] = this._registerForRgb( r , g , b , minRegister , maxRegister ) ) ; +} ; + + + + + +/* ScreenBuffer compatible methods */ + + + +// Cursor is always drawn so there is nothing to do here +notChainable.drawCursor = function() {} ; + +// /!\ Missing: markup, attr return +notChainable.put = function( options , str , ... args ) { + var i , x , y , dx , dy , attr , wrap , characters , len , moveToNeeded , inline ; + + // Manage options + if ( ! options ) { options = {} ; } + + wrap = options.wrap === undefined ? true : options.wrap ; + + x = options.x || 0 ; + y = options.y || 0 ; + + if ( typeof x !== 'number' || x < 1 ) { x = 1 ; } + else if ( x > this.width ) { x = this.width ; } + else { x = Math.floor( x ) ; } + + if ( typeof y !== 'number' || y < 1 ) { y = 1 ; } + else if ( y > this.height ) { y = this.height ; } + else { y = Math.floor( y ) ; } + + + // Process directions/increments + dx = 1 ; + dy = 0 ; + + switch ( options.direction ) { + //case 'right' : // not needed, use the default dx & dy + case 'left' : + dx = -1 ; + break ; + case 'up' : + dx = 0 ; + dy = -1 ; + break ; + case 'down' : + dx = 0 ; + dy = 1 ; + break ; + case null : + case 'none' : + dx = 0 ; + dy = 0 ; + break ; + } + + if ( typeof options.dx === 'number' ) { dx = options.dx ; } + if ( typeof options.dy === 'number' ) { dy = options.dy ; } + + inline = ( dx === 1 && dy === 0 ) ; + + + // Process attributes + attr = options.attr || this.esc.styleReset.on ; + + if ( attr && typeof attr === 'object' ) { attr = this.object2attr( attr ) ; } + if ( typeof attr !== 'string' ) { attr = this.esc.styleReset.on ; } + + + // Process the input string + if ( typeof str !== 'string' ) { + if ( str.toString ) { str = str.toString() ; } + else { return ; } + } + + if ( args.length ) { str = string.format( str , ... args ) ; } + str = termkit.stripControlChars( str ) ; + + //characters = punycode.ucs2.decode( str ) ; + characters = string.unicode.toArray( str ) ; + len = characters.length ; + + moveToNeeded = true ; + this.stdout.write( attr ) ; + + for ( i = 0 ; i < len ; i ++ ) { + if ( moveToNeeded ) { this.moveTo( x , y ) ; } + this( characters[ i ] ) ; + + x += dx ; + y += dy ; + + moveToNeeded = ! inline ; + + if ( x < 0 ) { + if ( ! wrap ) { break ; } + x = this.width - 1 ; + y -- ; + moveToNeeded = true ; + } + else if ( x >= this.width ) { + if ( ! wrap ) { break ; } + x = 0 ; + y ++ ; + moveToNeeded = true ; + } + + if ( y < 0 ) { break ; } + else if ( y >= this.height ) { break ; } + } +} ; + + + +notChainable.drawNdarrayImage = function( pixels /* , options */ ) { + var x , xMax = Math.min( pixels.shape[ 0 ] , this.width ) , + y , yMax = Math.ceil( pixels.shape[ 1 ] / 2 ) , + hasAlpha = pixels.shape[ 2 ] === 4 , + maxRegister = this.support['256colors'] ? 255 : 15 , + fgColor , bgColor , fgAlpha , bgAlpha , cache = {} ; + + + for ( y = 0 ; y < yMax ; y ++ ) { + for ( x = 0 ; x < xMax ; x ++ ) { + if ( this.support.trueColor ) { + fgAlpha = hasAlpha ? pixels.get( x , y * 2 , 3 ) / 255 : 1 ; + + if ( y * 2 + 1 < pixels.shape[ 1 ] ) { + bgAlpha = hasAlpha ? pixels.get( x , y * 2 + 1 , 3 ) / 255 : 1 ; + + this.noFormat( + this.optimized.color24bits( + Math.round( fgAlpha * pixels.get( x , y * 2 , 0 ) ) , + Math.round( fgAlpha * pixels.get( x , y * 2 , 1 ) ) , + Math.round( fgAlpha * pixels.get( x , y * 2 , 2 ) ) + ) + + this.optimized.bgColor24bits( + Math.round( bgAlpha * pixels.get( x , y * 2 + 1 , 0 ) ) , + Math.round( bgAlpha * pixels.get( x , y * 2 + 1 , 1 ) ) , + Math.round( bgAlpha * pixels.get( x , y * 2 + 1 , 2 ) ) + ) + + '▀' + ) ; + } + else { + this.noFormat( + this.optimized.color24bits( + Math.round( fgAlpha * pixels.get( x , y * 2 , 0 ) ) , + Math.round( fgAlpha * pixels.get( x , y * 2 , 1 ) ) , + Math.round( fgAlpha * pixels.get( x , y * 2 , 2 ) ) + ) + + this.optimized.bgColor24bits( 0 , 0 , 0 ) + + '▀' + ) ; + } + } + else { + fgColor = hasAlpha && pixels.get( x , y * 2 , 3 ) < 127 ? + 0 : + this.registerForRgbCache( + cache , + pixels.get( x , y * 2 , 0 ) , + pixels.get( x , y * 2 , 1 ) , + pixels.get( x , y * 2 , 2 ) , + 0 , maxRegister + ) ; + + if ( y * 2 + 1 < pixels.shape[ 1 ] ) { + bgColor = hasAlpha && pixels.get( x , y * 2 + 1 , 3 ) < 127 ? + 0 : + this.registerForRgbCache( + cache , + pixels.get( x , y * 2 + 1 , 0 ) , + pixels.get( x , y * 2 + 1 , 1 ) , + pixels.get( x , y * 2 + 1 , 2 ) , + 0 , maxRegister + ) ; + + this.noFormat( this.optimized.color256[ fgColor ] + this.optimized.bgColor256[ bgColor ] + '▀' ) ; + } + else { + this.noFormat( this.optimized.color256[ fgColor ] + this.optimized.bgColor256[ 0 ] + '▀' ) ; + } + } + } + + this.styleReset()( '\n' ) ; + } +} ; + + + +notChainable.drawImage = function( filepath , options , callback ) { + return termkit.image.load.call( this , notChainable.drawNdarrayImage.bind( this ) , filepath , options , callback ) ; +} ; + + +}).call(this)}).call(this,require('_process'),require("buffer").Buffer) +},{"./bar.js":8,"./colorScheme/default.json":10,"./extClipboard.js":37,"./fileInput.js":38,"./gridMenu.js":39,"./inputField.js":41,"./progressBar.js":44,"./singleColumnMenu.js":45,"./singleLineMenu.js":46,"./slowTyping.js":47,"./termkit.js":50,"./yesOrNo.js":57,"_process":179,"buffer":147,"nextgen-events":72,"seventh":108,"string-kit":123,"tree-kit":134}],6:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const fs = require( 'fs' ) ; +const string = require( 'string-kit' ) ; + + + +// A buffer suitable for text editor + + + +function TextBuffer( options = {} ) { + this.ScreenBuffer = options.ScreenBuffer || ( options.dst && options.dst.constructor ) || termkit.ScreenBuffer ; + + // a screenBuffer + this.dst = options.dst ; + this.palette = options.palette || ( this.dst && this.dst.palette ) ; + + // virtually infinity by default + this.width = options.width || Infinity ; // not used except by the blitter + this.height = options.height || Infinity ; // not used except by the blitter + this.dstClipRect = options.dstClipRect ? new termkit.Rect( options.dstClipRect ) : null ; + + this.x = options.x || 0 ; + this.y = options.y || 0 ; + + this.firstLineRightShift = options.firstLineRightShift || 0 ; + + this.cx = 0 ; + this.cy = 0 ; + this.ch = false ; // cursor hidden + + this.voidTextBuffer = null ; // Another TextBuffer used as fallback for empty cells, usefull for placeholder/hint/etc + + this.defaultAttr = this.ScreenBuffer.prototype.DEFAULT_ATTR ; + this.voidAttr = this.ScreenBuffer.prototype.DEFAULT_ATTR ; + this.preserveMarkupFormat = this.ScreenBuffer.prototype.preserveMarkupFormat ; + this.markupToAttrObject = this.ScreenBuffer.prototype.markupToAttrObject ; + + this.hidden = false ; + + this.tabWidth = options.tabWidth || 4 ; + this.forceInBound = !! options.forceInBound ; + + // If set to a number, force line-splitting when exceeding that width + this.lineWrapWidth = options.lineWrapWidth || null ; + + // If true, force word-aware line-splitting + this.wordWrap = !! options.wordWrap ; + + // DEPRECATED but kept for backward compatibility. + if ( options.wordWrapWidth ) { + this.lineWrapWidth = options.wordWrapWidth ; + this.wordWrap = true ; + } + + this.selectionRegion = null ; + + this.buffer = [ [] ] ; + + this.stateMachine = options.stateMachine || null ; + + if ( options.hidden ) { this.setHidden( options.hidden ) ; } +} + +module.exports = TextBuffer ; + + + +// Backward compatibility +TextBuffer.create = ( ... args ) => new TextBuffer( ... args ) ; + + + +function Cell( char = ' ' , attr = null , misc = null ) { + this.char = char || ' ' ; + this.filler = char === null ; + this.attr = attr ; + this.misc = misc ; +} + +TextBuffer.Cell = Cell ; + + + +const termkit = require( './termkit.js' ) ; + + + +TextBuffer.prototype.getText = function() { + return this.buffer.map( line => string.unicode.fromCells( line ) ).join( '' ) ; +} ; + + + +// TODOC +// Get the text, but separate before the cursor and after the cursor +TextBuffer.prototype.getCursorSplittedText = function() { + var y , line , before = '' , after = '' ; + + for ( y = 0 ; y < this.buffer.length ; y ++ ) { + line = this.buffer[ y ] ; + if ( y < this.cy ) { + before += string.unicode.fromCells( line ) ; + } + else if ( y > this.cy ) { + after += string.unicode.fromCells( line ) ; + } + else { + before += string.unicode.fromCells( line.slice( 0 , this.cx ) ) ; + after += string.unicode.fromCells( line.slice( this.cx ) ) ; + } + } + + return [ before , after ] ; +} ; + + + +// .setText( text , [[hasMarkup] , baseAttr ] ) +TextBuffer.prototype.setText = function( text , hasMarkup , baseAttr ) { + // Argument management + if ( typeof hasMarkup !== 'boolean' && typeof hasMarkup !== 'string' ) { + baseAttr = hasMarkup ; + hasMarkup = false ; + } + + var legacyColor = false , parser = null ; + + switch ( hasMarkup ) { + case 'ansi' : parser = termkit.parseAnsi ; break ; + case 'legacyAnsi' : parser = termkit.parseAnsi ; legacyColor = true ; break ; + case true : parser = termkit.parseMarkup ; break ; + } + + if ( baseAttr === undefined ) { baseAttr = this.defaultAttr ; } + if ( typeof baseAttr === 'object' ) { baseAttr = this.object2attr( baseAttr ) ; } + + // It must be reset now, because word-wrapping will be faster (always splice at the end of the array) + this.buffer.length = 0 ; + + text.split( /(?<=\n)/g ).forEach( line => { + var index = this.buffer.length ; + this.buffer[ index ] = this.lineToCells( line , parser , baseAttr , 0 , legacyColor ) ; + + // /!\ Warning /!\ string.unicode.toCells() strips '\n', so we need to restore it at the end of the line + if ( line[ line.length - 1 ] === '\n' ) { + this.buffer[ index ].push( new Cell( '\n' , baseAttr ) ) ; + } + + // word-wrap the current line, which is always the last line of the array (=faster) + if ( this.lineWrapWidth ) { this.wrapLine( index ) ; } + } ) ; +} ; + + + +// Internal, transform a line of text, with or without markup to cells... +TextBuffer.prototype.lineToCells = function( line , parser , baseAttr , offset = 0 , legacyColor = false ) { + var attr = baseAttr , + attrObject , cells ; + + if ( ! parser ) { + return string.unicode.toCells( Cell , line , this.tabWidth , offset , attr ) ; + } + + // Reset attr at each end of line + attr = baseAttr ; + attrObject = this.ScreenBuffer.attr2object( attr ) ; + cells = [] ; + + parser( line , termkit.markupOptions ).forEach( part => { + if ( typeof part === 'string' ) { + cells.push( ... string.unicode.toCells( Cell , part , this.tabWidth , offset + cells.length , attr ) ) ; + return ; + } + + if ( part.markup.reset ) { + attr = part.markup.special ? this.ScreenBuffer.DEFAULT_ATTR : baseAttr ; + attrObject = this.ScreenBuffer.attr2object( attr ) ; + } + else { + Object.assign( attrObject , part.markup ) ; + + // Remove incompatible flags + if ( attrObject.defaultColor && attrObject.color !== undefined ) { delete attrObject.defaultColor ; } + if ( attrObject.bgDefaultColor && attrObject.bgColor !== undefined ) { delete attrObject.bgDefaultColor ; } + + attr = this.object2attr( attrObject , undefined , legacyColor ) ; + } + + if ( part.markup.raw ) { + cells.push( ... string.unicode.toCells( Cell , part.markup.raw , this.tabWidth , offset + cells.length , attr ) ) ; + } + } ) ; + + return cells ; +} ; + + + +TextBuffer.prototype.setHidden = function( value ) { + this.hidden = + typeof value === 'string' && value.length ? value[ 0 ] : + value ? termkit.spChars.password : + false ; +} ; + +TextBuffer.prototype.getHidden = function() { return this.hidden ; } ; + + + +TextBuffer.prototype.setVoidTextBuffer = function( textBuffer = null ) { + this.voidTextBuffer = textBuffer ; +} ; + +TextBuffer.prototype.getVoidTextBuffer = function() { return this.voidTextBuffer ; } ; + + + +TextBuffer.prototype.getContentSize = function() { + return { + width: Math.max( 1 , ... this.buffer.map( line => line.length ) ) , + height: this.buffer.length + } ; +} ; + + + +// Cursor offset in the text-content (excluding fillers) +TextBuffer.prototype.getCursorOffset = function() { + var x , y , line , offset = 0 ; + + for ( y = 0 ; y < this.cy ; y ++ ) { + line = this.buffer[ y ] ; + if ( ! line ) { continue ; } + for ( x = 0 ; x < line.length ; x ++ ) { + if ( ! line[ x ].filler ) { offset ++ ; } + } + } + + line = this.buffer[ this.cy ] ; + if ( line ) { + for ( x = 0 ; x < this.cx && x < line.length ; x ++ ) { + if ( ! line[ x ].filler ) { offset ++ ; } + } + } + + return offset ; +} ; + + + +// Set the cursor position (cx,cy) depending on the offset in the text-content (excludind fillers) +TextBuffer.prototype.setCursorOffset = function( offset ) { + var line ; + + //console.error( "Entering" , offset ) ; + this.cy = this.cx = 0 ; + + if ( offset <= 0 ) { return ; } + + while ( this.cy < this.buffer.length ) { + this.cx = 0 ; + line = this.buffer[ this.cy ] ; + //console.error( " iter cy" , offset , this.cy , this.cx , "---" , line.length ) ; + if ( ! line ) { continue ; } + + while ( this.cx < line.length ) { + //console.error( " iter cx" , offset , this.cy , this.cx ) ; + if ( line[ this.cx ].filler ) { + this.cx ++ ; + } + else { + offset -- ; + this.cx ++ ; + if ( offset <= 0 ) { + if ( this.cx === line.length && line[ line.length - 1 ].char === '\n' ) { + //console.error( " Exit with \\n" ) ; + this.cx = 0 ; + this.cy ++ ; + } + //console.error( "Exit" , this.cy , this.cx ) ; + return ; + } + } + } + + this.cy ++ ; + } + + //console.error( "End of input" , offset , this.cy , this.cx ) ; +} ; + + + +// Recompute tabs +TextBuffer.prototype.reTabLine = function( startAt = 0 ) { + var length , cell , index , fillSize , input , output , + linePosition = startAt ; + + if ( this.buffer[ this.cy ] === undefined ) { this.buffer[ this.cy ] = [] ; } + + input = this.buffer[ this.cy ] ; + output = input.slice( 0 , startAt ) ; + length = input.length ; + + for ( index = startAt ; index < length ; index ++ ) { + cell = input[ index ] ; + + if ( cell.char === '\t' ) { + fillSize = this.tabWidth - ( linePosition % this.tabWidth ) - 1 ; + output.push( cell ) ; + linePosition += 1 + fillSize ; + + while ( fillSize -- ) { + // /!\ First or second? + //output.push( new Cell( null ) ) ; + output.push( new Cell( null , cell.attr , cell.misc ) ) ; + } + + // Skip input filler + while ( index + 1 < length && input[ index + 1 ].filler ) { index ++ ; } + } + else { + output.push( cell ) ; + linePosition ++ ; + } + } + + this.buffer[ this.cy ] = output ; +} ; + + + +// Forbidden split for word-wrap, only if there is only one space before +const FORBIDDEN_SPLIT = new Set( [ + // French typo double graph punctuation, + '!' , '?' , ':' , ';' , '«' , '»' , + // Other common punctuation that are often misused, should not be splitted anyway + ',' , '.' , '…' +] ) ; + + + +// Wrap/word-wrap the current line, stop on the next explicit '\n' or at the end of the buffer. +// Return the next line to scan. +// /!\ Should probably .reTabLine() +TextBuffer.prototype.wrapLine = function( startY = this.cy , width = this.lineWrapWidth , wordWrap = this.wordWrap ) { + var x , y , rightShift , endY , line , lineWidth , previousLine , lastChar , found , cursorInlineOffset , + checkCursor = this.cy === startY ; + + if ( startY >= this.buffer.length ) { return startY ; } + + // First check early exit conditions + line = this.buffer[ startY ] ; + previousLine = this.buffer[ startY - 1 ] ; + rightShift = startY ? 0 : this.firstLineRightShift ; + lineWidth = width - rightShift ; + //console.error( "startY:" , startY ) ; + if ( ! width || ( + line.length && line.length <= lineWidth && line[ line.length - 1 ].char === '\n' + && ( ! previousLine || ! previousLine.length || previousLine[ previousLine.length - 1 ].char === '\n' ) + ) ) { + //console.error( "exit" , previousLine); + // There is nothing to do: we only have one line and it is not even longer than the lineWidth + return startY + 1 ; + } + + // Avoid creating arrays if early exit triggers + var unifiedLine = [] , replacementLines = [] ; + + // First, search BACKWARD for the previous \n or start of buffer, to adjust startY value + for ( y = startY - 1 ; y >= 0 ; y -- ) { + line = this.buffer[ y ] ; + + if ( line.length && line[ line.length - 1 ].char === '\n' ) { + startY = y + 1 ; + break ; + } + else if ( ! y ) { + startY = 0 ; + break ; + } + } + //console.error( "startY aft:" , startY ) ; + + // Then, search for the next \n and concat everything in a single line + for ( y = startY ; y < this.buffer.length ; y ++ ) { + //console.error( " iter" , y , this.buffer.length) ; + line = this.buffer[ y ] ; + unifiedLine.push( ... line ) ; + + if ( line.length && line[ line.length - 1 ].char === '\n' ) { + //console.error( "has \\n" ) ; + // If we found the next \n, we don't go any further, but we still increment y because of endY + y ++ ; + break ; + } + } + + // Save the last line index + endY = y ; + rightShift = startY ? 0 : this.firstLineRightShift ; + //console.error( "endY:" , endY ) ; + + if ( checkCursor ) { + // Compute the cursor "inline" position + cursorInlineOffset = 0 ; + for ( y = startY ; y < this.cy ; y ++ ) { + // +1 because the cursor is allowed to be ahead by one cell + cursorInlineOffset += this.buffer[ y ].length ; + } + cursorInlineOffset += this.cx ; + } + + while ( unifiedLine.length ) { + lineWidth = width - rightShift ; + rightShift = 0 ; // Next time rightShift will be 0 + + if ( unifiedLine.length <= lineWidth ) { + // No more than the allowed lineWidth: add it and finish + replacementLines.push( unifiedLine ) ; + + // If the length is EXACTLY the line-width and it's the last lines, create a new empty line + if ( unifiedLine.length === lineWidth ) { + replacementLines.push( [] ) ; + } + break ; + } + + if ( ! wordWrap ) { + replacementLines.push( unifiedLine.splice( 0 , lineWidth ) ) ; + continue ; + } + + found = false ; + x = lineWidth ; + + if ( unifiedLine[ x ].char === ' ' ) { + // Search forward for the first non-space + while ( x < unifiedLine.length && unifiedLine[ x ].char === ' ' ) { x ++ ; } + + if ( x >= unifiedLine.length ) { + // No non-space found: feed every remaining cells + replacementLines.push( unifiedLine ) ; + break ; + } + + if ( x === lineWidth + 1 && FORBIDDEN_SPLIT.has( unifiedLine[ x ].char ) && unifiedLine[ lineWidth - 1 ].char !== ' ' ) { + // Dang! We can't split here! We will search backward starting from lineWidth - 1 + x = lineWidth - 1 ; + } + else { + // Else, cut at that non-space + found = true ; + } + } + + if ( ! found ) { + // Search backward for the first space + lastChar = null ; + + while ( x >= 0 && ( unifiedLine[ x ].char !== ' ' || ( FORBIDDEN_SPLIT.has( lastChar ) && x > 0 && unifiedLine[ x - 1 ].char !== ' ' ) ) ) { + lastChar = unifiedLine[ x ].char ; + x -- ; + } + + if ( x < 0 ) { x = lineWidth ; } // No space found, cut at the lineWidth + else { x ++ ; } // Cut just after the space + } + + replacementLines.push( unifiedLine.splice( 0 , x ) ) ; + } + + this.buffer.splice( startY , endY - startY , ... replacementLines ) ; + + + // New endY to be returned, and used for cursor computing + endY = startY + replacementLines.length ; + + if ( checkCursor ) { + //console.error( "cursorInlineOffset:" , cursorInlineOffset , "endY:" , endY ) ; + for ( y = startY ; ; y ++ ) { + //console.error( " iter" , y , "-- cursorInlineOffset:" , cursorInlineOffset , "this.buffer[ y ].length:" , this.buffer[ y ] && this.buffer[ y ].length ) ; + if ( y >= endY ) { + //console.error( " exit #1" ) ; + if ( y > 0 ) { y -- ; } + this.cy = y ; + this.cx = this.buffer[ y ] ? this.buffer[ y ].length : 0 ; + break ; + } + + if ( ! this.buffer[ y ] ) { + //console.error( " exit #2" ) ; + this.cy = y ; + this.cx = 0 ; + break ; + } + + if ( cursorInlineOffset < this.buffer[ y ].length ) { + //console.error( " exit #3" ) ; + this.cy = y ; + this.cx = cursorInlineOffset ; + break ; + } + + cursorInlineOffset -= this.buffer[ y ].length ; + } + + //* + // If we are after a true line breaker, go to the next line + if ( this.cx && this.buffer[ this.cy ] && this.buffer[ this.cy ][ this.cx - 1 ] && this.buffer[ this.cy ][ this.cx - 1 ].char === '\n' ) { + this.cy ++ ; + this.cx = 0 ; + if ( ! this.buffer[ this.cy ] ) { this.buffer[ this.cy ] = [] ; } + } + //*/ + } + + return endY ; +} ; + + + +TextBuffer.prototype.wrapAllLines = function( width = this.lineWrapWidth , wordWrap = this.wordWrap ) { + var y = 0 ; + + while ( y < this.buffer.length ) { + y = this.wrapLine( y , width , wordWrap ) ; + } +} ; + + + +// Probably DEPRECATED +TextBuffer.prototype.wordWrapLine = function( startY = this.cy , width = this.lineWrapWidth ) { + return this.wrapLine( startY , width , true ) ; +} ; + + + +// Probably DEPRECATED +TextBuffer.prototype.wordWrapAllLines = function( width = this.lineWrapWidth ) { + var y = 0 ; + + while ( y < this.buffer.length ) { + y = this.wrapLine( y , width , true ) ; + } +} ; + + + +TextBuffer.prototype.setDefaultAttr = function( attr ) { + if ( attr && typeof attr === 'object' ) { attr = this.object2attr( attr ) ; } + else if ( typeof attr !== 'number' ) { return ; } + + this.defaultAttr = attr ; +} ; + + + +TextBuffer.prototype.setEmptyCellAttr = // DEPRECATED +TextBuffer.prototype.setVoidAttr = function( attr ) { + if ( attr === null ) { this.voidAttr = null ; } // null: don't draw + else if ( attr && typeof attr === 'object' ) { attr = this.object2attr( attr ) ; } + else if ( typeof attr !== 'number' ) { return ; } + + this.voidAttr = attr ; +} ; + + + +TextBuffer.prototype.setAttrAt = function( attr , x , y ) { + if ( attr && typeof attr === 'object' ) { attr = this.object2attr( attr ) ; } + else if ( typeof attr !== 'number' ) { return ; } + + this.setAttrCodeAt( attr , x , y ) ; +} ; + + + +// Faster than setAttrAt(), do no check attr, assume an attr code (number) +TextBuffer.prototype.setAttrCodeAt = function( attr , x , y ) { + if ( ! this.buffer[ y ] ) { this.buffer[ y ] = [] ; } + + if ( ! this.buffer[ y ][ x ] ) { this.buffer[ y ][ x ] = new Cell( ' ' , attr ) ; } + else { this.buffer[ y ][ x ].attr = attr ; } +} ; + + + +const WHOLE_BUFFER_REGION = { + xmin: 0 , xmax: Infinity , ymin: 0 , ymax: Infinity +} ; + +// Set a whole region +TextBuffer.prototype.setAttrRegion = function( attr , region ) { + if ( attr && typeof attr === 'object' ) { attr = this.object2attr( attr ) ; } + else if ( typeof attr !== 'number' ) { return ; } + + this.setAttrCodeRegion( attr , region ) ; +} ; + + + +// Faster than setAttrRegion(), do no check attr, assume an attr code (number) +TextBuffer.prototype.setAttrCodeRegion = function( attr , region = WHOLE_BUFFER_REGION ) { + var x , y , xmin , xmax , ymax ; + + ymax = Math.min( region.ymax , this.buffer.length - 1 ) ; + + for ( y = region.ymin ; y <= ymax ; y ++ ) { + if ( ! this.buffer[ y ] ) { this.buffer[ y ] = [] ; } + + xmin = y === region.ymin ? region.xmin : 0 ; + xmax = y === region.ymax ? Math.min( region.xmax , this.buffer[ y ].length - 1 ) : this.buffer[ y ].length - 1 ; + + for ( x = xmin ; x <= xmax ; x ++ ) { + this.buffer[ y ][ x ].attr = attr ; + } + } +} ; + + + +TextBuffer.prototype.setSelectionRegion = function( region ) { + if ( this.selectionRegion ) { + // Start by unhilighting existing selection + this.hilightSelection( false ) ; + } + else { + this.selectionRegion = {} ; + } + + if ( region.xmin !== undefined && region.ymin !== undefined ) { + this.selectionRegion.xmin = region.xmin ; + this.selectionRegion.ymin = region.ymin ; + } + + if ( region.xmax !== undefined && region.ymax !== undefined ) { + this.selectionRegion.xmax = region.xmax ; + this.selectionRegion.ymax = region.ymax ; + } + + this.hilightSelection() ; +} ; + + + +TextBuffer.prototype.resetSelectionRegion = function() { + if ( ! this.selectionRegion ) { return ; } + + // Start by unhilighting existing selection + this.hilightSelection( false ) ; + this.selectionRegion = null ; +} ; + + + +// Internal +TextBuffer.prototype.hilightSelection = function( turnOn = true ) { + var x , y , xmin , xmax , ymax , + region = this.selectionRegion ; + + if ( ! region || region.xmin === undefined || region.ymin === undefined || region.xmax === undefined || region.ymax === undefined ) { + return ; + } + + ymax = Math.min( region.ymax , this.buffer.length - 1 ) ; + + for ( y = region.ymin ; y <= ymax ; y ++ ) { + if ( ! this.buffer[ y ] ) { this.buffer[ y ] = [] ; } + + xmin = y === region.ymin ? region.xmin : 0 ; + xmax = y === region.ymax ? Math.min( region.xmax , this.buffer[ y ].length - 1 ) : this.buffer[ y ].length - 1 ; + + for ( x = xmin ; x <= xmax ; x ++ ) { + this.buffer[ y ][ x ].attr = turnOn ? + this.ScreenBuffer.attrSelect( this.buffer[ y ][ x ].attr ) : + this.ScreenBuffer.attrUnselect( this.buffer[ y ][ x ].attr ) ; + } + } +} ; + + + +TextBuffer.prototype.getSelectionText = function() { + var x , y , xmin , xmax , ymax , cell , + str = '' , + region = this.selectionRegion ; + + if ( ! region || region.xmin === undefined || region.ymin === undefined || region.xmax === undefined || region.ymax === undefined ) { + return str ; + } + + ymax = Math.min( region.ymax , this.buffer.length - 1 ) ; + + for ( y = region.ymin ; y <= ymax ; y ++ ) { + if ( ! this.buffer[ y ] ) { this.buffer[ y ] = [] ; } + + xmin = y === region.ymin ? region.xmin : 0 ; + xmax = y === region.ymax ? Math.min( region.xmax , this.buffer[ y ].length - 1 ) : this.buffer[ y ].length - 1 ; + + for ( x = xmin ; x <= xmax ; x ++ ) { + cell = this.buffer[ y ][ x ] ; + str += cell.filler ? '' : cell.char ; + } + } + + return str ; +} ; + + + +// Misc data are lazily created +TextBuffer.prototype.getMisc = function() { + if ( ! this.buffer[ this.cy ] || ! this.buffer[ this.cy ][ this.cx ] ) { return ; } + if ( ! this.buffer[ this.cy ][ this.cx ].misc ) { this.buffer[ this.cy ][ this.cx ].misc = {} ; } + return this.buffer[ this.cy ][ this.cx ].misc ; +} ; + + + +TextBuffer.prototype.getMiscAt = function( x , y ) { + if ( ! this.buffer[ y ] || ! this.buffer[ y ][ x ] ) { return ; } + if ( ! this.buffer[ y ][ x ].misc ) { this.buffer[ y ][ x ].misc = {} ; } + return this.buffer[ y ][ x ].misc ; +} ; + + + +TextBuffer.prototype.iterate = function( options , callback ) { + var x , y , yMax , offset = 0 , length ; + + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + else if ( ! options || typeof options !== 'object' ) { options = {} ; } + + if ( ! this.buffer.length ) { return ; } + + for ( y = 0 , yMax = this.buffer.length ; y < yMax ; y ++ ) { + if ( this.buffer[ y ] ) { + length = this.buffer[ y ].length ; + + for ( x = 0 ; x < length ; x ++ ) { + if ( this.buffer[ y ][ x ].filler ) { continue ; } + + callback( { + offset: offset , + x: x , + y: y , + text: this.buffer[ y ][ x ].char , + attr: this.buffer[ y ][ x ].attr , + misc: this.buffer[ y ][ x ].misc + } ) ; + + offset ++ ; + } + } + } + + // Call the callback one last time at the end of the buffer, with an empty string. + // Useful for 'Ne' (Neon) state machine. + if ( options.finalCall ) { + callback( { + offset: offset + 1 , + x: null , + y: y , + text: '' , + attr: null , + misc: null + } ) ; + } +} ; + + + +TextBuffer.prototype.moveTo = function( x , y ) { + this.cx = x >= 0 ? x : 0 ; + this.cy = y >= 0 ? y : 0 ; +} ; + + + +TextBuffer.prototype.move = function( x , y ) { this.moveTo( this.cx + x , this.cy + y ) ; } ; +TextBuffer.prototype.moveToColumn = function( x ) { this.moveTo( x , this.cy ) ; } ; +TextBuffer.prototype.moveToLine = TextBuffer.prototype.moveToRow = function( y ) { this.moveTo( this.cx , y ) ; } ; + + + +TextBuffer.prototype.moveUp = function() { + this.cy = this.cy > 0 ? this.cy - 1 : 0 ; + if ( this.forceInBound ) { this.moveInBound( true ) ; } +} ; + + + +TextBuffer.prototype.moveDown = function() { + this.cy ++ ; + if ( this.forceInBound ) { this.moveInBound( true ) ; } +} ; + + + +TextBuffer.prototype.moveLeft = function() { + this.cx = this.cx > 0 ? this.cx - 1 : 0 ; + if ( this.forceInBound ) { this.moveInBound( true ) ; } +} ; + + + +TextBuffer.prototype.moveRight = function() { + this.cx ++ ; + if ( this.forceInBound ) { this.moveInBound( true ) ; } +} ; + + + +TextBuffer.prototype.moveForward = function( testFn , justSkipFiller ) { + var oldCx = this.cx , + currentLine = this.buffer[ this.cy ] ; + + //if ( justSkipFiller && ( ! currentLine || ! currentLine[ this.cx ] || ! currentLine[ this.cx ].filler || currentLine[ this.cx ].char !== '\n' ) ) { return ; } + if ( justSkipFiller && ( ! currentLine || ! currentLine[ this.cx ] || ! currentLine[ this.cx ].filler ) ) { return ; } + + for ( ;; ) { + if ( ! currentLine || this.cx + 1 > currentLine.length || ( this.cx < currentLine.length && currentLine[ this.cx ].char === '\n' ) ) { + if ( this.cy + 1 < this.buffer.length || ! this.forceInBound ) { + this.cy ++ ; + this.cx = 0 ; + } + else { + this.cx = oldCx ; + } + + break ; + } + + this.cx ++ ; + + if ( ! currentLine[ this.cx ] || ( ! currentLine[ this.cx ].filler && ( ! testFn || testFn( currentLine[ this.cx ].char ) ) ) ) { break ; } + } + + if ( this.forceInBound ) { this.moveInBound() ; } +} ; + + + +TextBuffer.prototype.moveBackward = function( testFn , justSkipFiller ) { + var lineLength , + currentLine = this.buffer[ this.cy ] ; + + //if ( justSkipFiller && ( ! currentLine || ! currentLine[ this.cx ] || ! currentLine[ this.cx ].filler || currentLine[ this.cx ].char !== '\n' ) ) { return ; } + if ( justSkipFiller && ( ! currentLine || ! currentLine[ this.cx ] || ! currentLine[ this.cx ].filler ) ) { return ; } + + for ( ;; ) { + lineLength = currentLine ? currentLine.length : 0 ; + + if ( this.cx > lineLength ) { this.cx = lineLength ; } + else { this.cx -- ; } + + if ( this.cx < 0 ) { + this.cy -- ; + + if ( this.cy < 0 ) { this.cy = 0 ; this.cx = 0 ; break ; } + + this.moveToEndOfLine() ; + break ; + } + + if ( + ! currentLine || ! currentLine[ this.cx ] + || ( + ( ! currentLine[ this.cx ].filler || currentLine[ this.cx ].char !== '\n' ) + && ( ! testFn || testFn( currentLine[ this.cx ].char ) ) + ) + ) { + break ; + } + } + + if ( this.forceInBound ) { this.moveInBound() ; } +} ; + + + +// Rough word boundary test +const WORD_BOUNDARY = new Set( [ ' ' , '\t' , '.' , ',' , ';' , ':' , '!' , '?' , '/' , '\\' , '(' , ')' , '[' , ']' , '{' , '}' , '<' , '>' , '=' , "'" , '"' ] ) ; + +TextBuffer.prototype.wordBoundary_ = function( method , checkInitial ) { + var initialChar , nonBoundarySeen = false ; + + if ( checkInitial && this.buffer[ this.cy ] && this.buffer[ this.cy ][ this.cx ] ) { + initialChar = this.buffer[ this.cy ][ this.cx ].char ; + if ( ! WORD_BOUNDARY.has( initialChar ) ) { nonBoundarySeen = true ; } + } + + this[ method ]( char => { + if ( WORD_BOUNDARY.has( char ) ) { + if ( nonBoundarySeen ) { return true ; } + return false ; + } + + nonBoundarySeen = true ; + return false ; + + } ) ; +} ; + + + +TextBuffer.prototype.moveToEndOfWord = function() { + return this.wordBoundary_( 'moveForward' , true ) ; +} ; + + + +TextBuffer.prototype.moveToStartOfWord = function() { + var char , oldCx = this.cx , oldCy = this.cy ; + this.wordBoundary_( 'moveBackward' ) ; + + if ( this.cx < oldCx && this.cy === oldCy && this.buffer[ this.cy ] && this.buffer[ this.cy ][ this.cx ] ) { + char = this.buffer[ this.cy ][ this.cx ].char ; + if ( WORD_BOUNDARY.has( char ) ) { this.moveForward() ; } + } + else if ( this.cy < oldCy && oldCx !== 0 && this.buffer[ oldCy ] && this.buffer[ oldCy ][ 0 ] ) { + char = this.buffer[ oldCy ][ 0 ].char ; + if ( ! WORD_BOUNDARY.has( char ) ) { + this.cx = 0 ; + this.cy = oldCy ; + } + } +} ; + + + +TextBuffer.prototype.moveToStartOfLine = function() { this.cx = 0 ; } ; + + + +TextBuffer.prototype.moveToEndOfLine = function() { + var currentLine = this.buffer[ this.cy ] ; + + if ( ! currentLine ) { + this.cx = 0 ; + } + else if ( currentLine.length && currentLine[ currentLine.length - 1 ].char === '\n' ) { + this.cx = currentLine.length - 1 ; + } + else { + this.cx = currentLine.length ; + } +} ; + + + +// Move to the start of the buffer: 0,0 +TextBuffer.prototype.moveToStartOfBuffer = function() { this.cx = this.cy = 0 ; } ; + + + +// Move to the end of the buffer: end of line of the last line +TextBuffer.prototype.moveToEndOfBuffer = function() { + this.cy = this.buffer.length ? this.buffer.length - 1 : 0 ; + this.moveToEndOfLine() ; +} ; + + + +TextBuffer.prototype.moveInBound = function( ignoreCx ) { + var currentLine = this.buffer[ this.cy ] ; + + if ( this.cy > this.buffer.length ) { this.cy = this.buffer.length ; } + + if ( ignoreCx ) { return ; } + + if ( ! currentLine ) { + this.cx = 0 ; + } + else if ( currentLine.length && currentLine[ currentLine.length - 1 ].char === '\n' ) { + if ( this.cx > currentLine.length - 1 ) { this.cx = currentLine.length - 1 ; } + } + else if ( this.cx > currentLine.length ) { + this.cx = currentLine.length ; + } +} ; + + + +// .insert( text , [[hasMarkup] , attr ] ) +TextBuffer.prototype.insert = function( text , hasMarkup , attr ) { + var lines , index , length ; + + if ( ! text ) { return ; } + + if ( typeof hasMarkup !== 'boolean' && typeof hasMarkup !== 'string' ) { + attr = hasMarkup ; + hasMarkup = false ; + } + + var legacyColor = false , parser = null ; + + switch ( hasMarkup ) { + case 'ansi' : parser = termkit.parseAnsi ; break ; + case 'legacyAnsi' : parser = termkit.parseAnsi ; legacyColor = true ; break ; + case true : parser = termkit.parseMarkup ; break ; + } + + lines = text.split( '\n' ) ; + length = lines.length ; + + if ( attr && typeof attr === 'object' ) { attr = this.object2attr( attr ) ; } + else if ( typeof attr !== 'number' ) { attr = this.defaultAttr ; } + + if ( this.forceInBound ) { this.moveInBound() ; } + + this.inlineInsert( lines[ 0 ] , parser , attr ) ; + + for ( index = 1 ; index < length ; index ++ ) { + this.newLine( true ) ; + this.inlineInsert( lines[ index ] , parser , attr ) ; + } +} ; + + + +TextBuffer.prototype.prepend = function( text , hasMarkup , attr ) { + this.moveToStartOfBuffer() ; + this.insert( text , hasMarkup , attr ) ; +} ; + + + +TextBuffer.prototype.append = function( text , hasMarkup , attr ) { + this.moveToEndOfBuffer() ; + this.insert( text , hasMarkup , attr ) ; +} ; + + + +// Internal API: +// Insert inline chars (no control chars) +TextBuffer.prototype.inlineInsert = function( text , parser , attr , legacyColor = false ) { + var currentLine , currentLineLength , hasNL , nlCell , tabIndex , fillSize , cells ; + + this.moveForward( undefined , true ) ; // just skip filler char + + // Should come after moving forward (rely on this.cx) + //cells = string.unicode.toCells( Cell , text , this.tabWidth , this.cx , attr ) ; + cells = this.lineToCells( text , parser , attr , this.cx , legacyColor ) ; + + // Is this a new line? + if ( this.cy >= this.buffer.length ) { + // Create all missing lines, if any + while ( this.buffer.length < this.cy ) { + this.buffer.push( [ new Cell( '\n' , this.defaultAttr ) ] ) ; + } + + // Add a '\n' to the last line, if it is missing + if ( + this.cy && ( + ! this.buffer[ this.cy - 1 ].length || + this.buffer[ this.cy - 1 ][ this.buffer[ this.cy - 1 ].length - 1 ].char !== '\n' + ) + ) { + this.buffer[ this.cy - 1 ].push( new Cell( '\n' , this.defaultAttr ) ) ; + } + + this.buffer[ this.cy ] = [] ; + } + + currentLine = this.buffer[ this.cy ] ; + currentLineLength = currentLine.length ; + hasNL = currentLineLength && currentLine[ currentLineLength - 1 ].char === '\n' ; + + // Apply + if ( this.cx === currentLineLength ) { + if ( hasNL ) { + currentLine.splice( currentLineLength - 1 , 0 , new Cell( ' ' , this.defaultAttr ) , ... cells ) ; + } + else { + currentLine.push( ... cells ) ; + } + } + else if ( this.cx < currentLineLength ) { + currentLine.splice( this.cx , 0 , ... cells ) ; + } + // this.cx > currentLineLength + else if ( hasNL ) { + fillSize = this.cx - currentLineLength + 1 ; + nlCell = currentLine.pop() ; + while ( fillSize -- ) { currentLine.push( new Cell( ' ' , this.defaultAttr ) ) ; } + currentLine.push( ... cells , nlCell ) ; + } + else { + fillSize = this.cx - currentLineLength ; + while ( fillSize -- ) { currentLine.push( new Cell( ' ' , this.defaultAttr ) ) ; } + currentLine.push( ... cells ) ; + } + + // Patch tab if needed + tabIndex = this.indexOfCharInLine( currentLine , '\t' , this.cx ) ; + this.cx += cells.length ; + + // (AFTER cx++) word-wrap the current line, which is always the last line of the array (=faster) + if ( this.lineWrapWidth ) { this.wrapLine() ; } + + if ( tabIndex !== -1 ) { this.reTabLine( tabIndex ) ; } +} ; + + + +// Internal utility function +TextBuffer.prototype.indexOfCharInLine = function( line , char , index = 0 ) { + var iMax = line.length ; + + for ( ; index < iMax ; index ++ ) { + if ( line[ index ].char === char ) { return index ; } + } + + // Like .indexOf() does... + return -1 ; +} ; + + + +// /!\ Bug with tabs and count > 1 !!! /!\ + +// Delete chars +TextBuffer.prototype.delete = function( count ) { + var currentLine , inlineCount ; + + if ( count === undefined ) { count = 1 ; } + + if ( this.forceInBound ) { this.moveInBound() ; } + + if ( this.buffer[ this.cy ] && this.buffer[ this.cy ][ this.cx ] && this.buffer[ this.cy ][ this.cx ].filler ) { + this.moveBackward( undefined , true ) ; // just skip filler char + count -- ; + } + + + while ( count > 0 ) { + currentLine = this.buffer[ this.cy ] ; + + // If we are already at the end of the buffer... + if ( this.cy >= this.buffer.length || + ( this.cy === this.buffer.length - 1 && this.cx >= currentLine.length ) ) { + return ; + } + + if ( currentLine ) { + // If the cursor is too far away, move it at the end of the line + if ( this.cx > currentLine.length ) { this.cx = currentLine.length ; } + + if ( currentLine[ this.cx ] && currentLine[ this.cx ].char !== '\n' ) { + // Compute inline delete + //inlineCount = Math.min( count , currentLine.length - this.cx ) ; + inlineCount = this.countInlineForward( count ) ; + + // Apply inline delete + if ( inlineCount > 0 ) { + currentLine.splice( this.cx , inlineCount ) ; + } + + count -= inlineCount ; + } + } + + if ( count > 0 ) { + if ( this.joinLine( true ) ) { count -- ; } + } + } + + // word-wrap the current line, which is always the last line of the array (=faster) + if ( this.lineWrapWidth ) { this.wrapLine() ; } + + // Patch tab if needed + //tabIndex = currentLine.indexOf( '\t' , this.cx ) ; + //if ( tabIndex !== -1 ) { this.reTabLine( tabIndex ) ; } + this.reTabLine() ; // Do it every time, before finding a better way to do it +} ; + + + +// /!\ Bug with tabs and count > 1 !!! /!\ + +// Delete backward chars +TextBuffer.prototype.backDelete = function( count ) { + var currentLine , inlineCount , tabIndex ; + + if ( count === undefined ) { count = 1 ; } + + if ( this.forceInBound ) { this.moveInBound() ; } + + if ( this.buffer[ this.cy ] && this.cx && this.buffer[ this.cy ][ this.cx - 1 ] && this.buffer[ this.cy ][ this.cx - 1 ].filler ) { + this.moveBackward( undefined , true ) ; // just skip filler char + //count -- ; // do not downcount: the cursor is always on a \x00 before deleting a \t + } + + + while ( count > 0 ) { + currentLine = this.buffer[ this.cy ] ; + + // If we are already at the begining of the buffer... + if ( this.cy === 0 && this.cx === 0 ) { return ; } + + if ( currentLine ) { + // If the cursor is to far away, move it at the end of the line, it will cost one 'count' + if ( this.cx > currentLine.length ) { + if ( currentLine.length && currentLine[ currentLine.length - 1 ].char === '\n' ) { this.cx = currentLine.length - 1 ; } + else { this.cx = currentLine.length ; } + + count -- ; + } + else if ( this.cx && this.cx === currentLine.length && currentLine[ currentLine.length - 1 ].char === '\n' ) { + this.cx = currentLine.length - 1 ; + } + + // Compute inline delete + inlineCount = this.countInlineBackward( count ) ; + + // Apply inline delete + if ( inlineCount > 0 ) { + currentLine.splice( this.cx - inlineCount , inlineCount ) ; + this.cx -= inlineCount ; + } + + count -= inlineCount ; + } + + if ( count > 0 ) { + this.cy -- ; + this.cx = currentLine ? currentLine.length : 0 ; + if ( this.joinLine( true ) ) { count -- ; } + } + } + + // word-wrap the current line, which is always the last line of the array (=faster) + if ( this.lineWrapWidth ) { this.wrapLine() ; } + + // Patch tab if needed + //tabIndex = currentLine.indexOf( '\t' , this.cx ) ; + //if ( tabIndex !== -1 ) { this.reTabLine( tabIndex ) ; } + this.reTabLine( tabIndex ) ; // Do it every time, before finding a better way to do it +} ; + + + +// Fix a backward counter, get an additional count for each null char encountered +TextBuffer.prototype.countInlineBackward = function( count ) { + var c , x ; + + for ( x = this.cx - 1 , c = 0 ; x >= 0 && c < count ; x -- , c ++ ) { + if ( this.buffer[ this.cy ][ x ] && this.buffer[ this.cy ][ x ].filler ) { count ++ ; } + } + + return c ; +} ; + + + +// Fix a forward counter, get an additional count for each null char encountered +TextBuffer.prototype.countInlineForward = function( count ) { + var c , x , xMax = this.buffer[ this.cy ].length ; + + for ( x = this.cx , c = 0 ; x < xMax && c < count ; x ++ , c ++ ) { + if ( this.buffer[ this.cy ][ x + 1 ] && this.buffer[ this.cy ][ x + 1 ].filler ) { count ++ ; } + } + + return c ; +} ; + + + +TextBuffer.prototype.newLine = function( internalCall ) { + var currentLine , currentLineLength , nextLine = [] , tabIndex ; + + if ( ! internalCall && this.forceInBound ) { this.moveInBound() ; } + + if ( this.buffer[ this.cy ] === undefined ) { this.buffer[ this.cy ] = [] ; } + + currentLine = this.buffer[ this.cy ] ; + currentLineLength = currentLine.length ; + + // Apply + if ( this.cx < currentLineLength ) { + nextLine = currentLine.slice( this.cx ) ; + currentLine.length = this.cx ; + } + + currentLine.push( new Cell( '\n' , this.defaultAttr ) ) ; + + this.buffer.splice( this.cy + 1 , 0 , nextLine ) ; + + this.cx = 0 ; + this.cy ++ ; + + // Patch tab if needed + if ( ! internalCall ) { + // word-wrap the current line, which is always the last line of the array (=faster) + if ( this.lineWrapWidth ) { this.wrapLine() ; } + + tabIndex = this.indexOfCharInLine( currentLine , '\t' , this.cx ) ; + if ( tabIndex !== -1 ) { this.reTabLine( tabIndex ) ; } + } +} ; + + + +TextBuffer.prototype.joinLine = function( internalCall ) { + var tabIndex , currentLine , + hasDeleted = false ; + + if ( ! internalCall && this.forceInBound ) { this.moveInBound() ; } + + if ( this.buffer[ this.cy ] === undefined ) { this.buffer[ this.cy ] = [] ; } + if ( this.buffer[ this.cy + 1 ] === undefined ) { this.buffer[ this.cy + 1 ] = [] ; } + + currentLine = this.buffer[ this.cy ] ; + + if ( currentLine.length && currentLine[ currentLine.length - 1 ].char === '\n' ) { + // Remove the last '\n' if any + currentLine.length -- ; + hasDeleted = true ; + } + + this.cx = currentLine.length ; + + currentLine.splice( currentLine.length , 0 , ... this.buffer[ this.cy + 1 ] ) ; + + this.buffer.splice( this.cy + 1 , 1 ) ; + + // Patch tab if needed + if ( ! internalCall ) { + // word-wrap the current line, which is always the last line of the array (=faster) + if ( this.lineWrapWidth ) { this.wrapLine() ; } + + tabIndex = this.indexOfCharInLine( currentLine , '\t' , this.cx ) ; + if ( tabIndex !== -1 ) { this.reTabLine( tabIndex ) ; } + } + + return hasDeleted ; +} ; + + + +/* + A TextBuffer can only draw to a ScreenBuffer. + To display it, you need to: + - draw the TextBuffer to a ScreenBuffer + - then draw that ScreenBuffer to the terminal +*/ +TextBuffer.prototype.draw = function( options = {} ) { + // Transmitted options (do not edit the user provided options, clone them) + var tr = { + dst: options.dst || this.dst , + offsetX: options.x !== undefined ? Math.floor( options.x ) : Math.floor( this.x ) , + offsetY: options.y !== undefined ? Math.floor( options.y ) : Math.floor( this.y ) , + dstClipRect: options.dstClipRect ? new termkit.Rect( options.dstClipRect ) : this.dstClipRect , + srcClipRect: options.srcClipRect ? new termkit.Rect( options.srcClipRect ) : undefined , + blending: options.blending , + wrap: options.wrap , + tile: options.tile + } ; + + if ( tr.dst instanceof this.ScreenBuffer ) { + this.blitter( tr ) ; + + if ( options.cursor ) { + tr.dst.cx = this.cx + tr.offsetX ; + tr.dst.cy = this.cy + tr.offsetY ; + } + } +} ; + + + +TextBuffer.prototype.drawCursor = function( options ) { + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + var cx , + dst = options.dst || this.dst ; + + if ( dst instanceof this.ScreenBuffer ) { + cx = this.cy ? this.cx : this.cx + this.firstLineRightShift ; + + if ( ! this.ch && ( this.dstClipRect || new termkit.Rect( this.dst ) ).isInside( cx + this.x , this.cy + this.y ) ) { + dst.cx = cx + this.x ; + dst.cy = this.cy + this.y ; + dst.ch = false ; + } + else { + dst.ch = true ; + } + } +} ; + + + +TextBuffer.prototype.blitter = function( p ) { + var tr , srcRect , srcClipRect , srcAltBuffer , iterator , iteratorCallback ; + + srcRect = new termkit.Rect( this ) ; + + if ( this.voidTextBuffer ) { + srcAltBuffer = this.voidTextBuffer.buffer ; + srcRect.merge( new termkit.Rect( this.voidTextBuffer ) ) ; + } + + srcClipRect = p.srcClipRect || new termkit.Rect( srcRect ) ; + + // Default options & iterator + tr = { + type: 'line' , + context: { + srcFirstLineRightShift: this.firstLineRightShift , + srcBuffer: this.buffer , + srcAltBuffer , + dstBuffer: p.dst.buffer , + forceChar: this.hidden , + voidAttr: this.voidAttr , + writeAttr: + this.ScreenBuffer === termkit.ScreenBuffer ? + ( dst , attr , offset ) => { dst.writeInt32BE( attr , offset ) ; } : + ( dst , attr , offset ) => { attr.copy( dst , offset ) ; } + } , + dstRect: new termkit.Rect( p.dst ) , + srcRect , + dstClipRect: p.dstClipRect || new termkit.Rect( p.dst ) , + srcClipRect , + offsetX: p.offsetX , + offsetY: p.offsetY , + wrap: p.wrap , + tile: p.tile , + multiply: this.ScreenBuffer.prototype.ITEM_SIZE + } ; + + iteratorCallback = this.blitterLineIterator.bind( this ) ; + + if ( p.wrap ) { iterator = 'wrapIterator' ; } + else if ( p.tile ) { iterator = 'tileIterator' ; } + else { iterator = 'regionIterator' ; } + + termkit.Rect[ iterator ]( tr , iteratorCallback ) ; +} ; + + + +TextBuffer.prototype.blitterLineIterator = function( p ) { + //console.error( "blitter line" , p.srcY ) ; + var srcRShift , srcX , srcXmax , srcExistingXmax , dstOffset , cells , cell , attr , charCode ; + + //if ( ! global.deb ) { global.deb = [] ; } + //global.deb.push( p ) ; + + srcRShift = p.srcY ? 0 : p.context.srcFirstLineRightShift ; + srcX = p.srcXmin - srcRShift ; + srcXmax = p.srcXmax - srcRShift ; + dstOffset = p.dstStart ; + + cells = p.context.srcBuffer[ p.srcY ] ; + + if ( cells ) { + //console.error( " C1" ) ; + srcExistingXmax = srcXmax ; + + if ( srcExistingXmax >= cells.length ) { srcExistingXmax = cells.length - 1 ; } + + // Write existing cells + for ( ; srcX <= srcExistingXmax ; srcX ++ , dstOffset += this.ScreenBuffer.prototype.ITEM_SIZE ) { + if ( srcX < 0 ) { continue ; } // right-shifted + cell = cells[ srcX ] ; + + // Write the attributes + p.context.writeAttr( p.context.dstBuffer , cell.attr , dstOffset ) ; + + if ( p.context.forceChar ) { + // Write the forced character (i.e. hidden) + p.context.dstBuffer.write( p.context.forceChar , dstOffset + this.ScreenBuffer.prototype.ATTR_SIZE , this.ScreenBuffer.prototype.CHAR_SIZE ) ; + } + else if ( ( charCode = cell.char.charCodeAt( 0 ) ) < 0x20 || charCode === 0x7f ) { + // Replace the control char by a white space + p.context.dstBuffer.write( ' ' , dstOffset + this.ScreenBuffer.prototype.ATTR_SIZE , this.ScreenBuffer.prototype.CHAR_SIZE ) ; + } + else { + // Write the character + p.context.dstBuffer.write( cell.char , dstOffset + this.ScreenBuffer.prototype.ATTR_SIZE , this.ScreenBuffer.prototype.CHAR_SIZE ) ; + } + } + } + + + if ( p.context.srcAltBuffer ) { + cells = p.context.srcAltBuffer[ p.srcY ] ; + + if ( cells ) { + //console.error( " C2" , srcX ) ; + srcExistingXmax = srcXmax ; + + if ( srcExistingXmax >= cells.length ) { srcExistingXmax = cells.length - 1 ; } + + // Write existing cells + for ( ; srcX <= srcExistingXmax ; srcX ++ , dstOffset += this.ScreenBuffer.prototype.ITEM_SIZE ) { + if ( srcX < 0 ) { continue ; } // right-shifted + cell = cells[ srcX ] ; + + // Write the attributes + p.context.writeAttr( p.context.dstBuffer , cell.attr , dstOffset ) ; + + if ( ( charCode = cell.char.charCodeAt( 0 ) ) < 0x20 || charCode === 0x7f ) { + // Replace the control char by a white space + p.context.dstBuffer.write( ' ' , dstOffset + this.ScreenBuffer.prototype.ATTR_SIZE , this.ScreenBuffer.prototype.CHAR_SIZE ) ; + } + else { + // Write the character + p.context.dstBuffer.write( cell.char , dstOffset + this.ScreenBuffer.prototype.ATTR_SIZE , this.ScreenBuffer.prototype.CHAR_SIZE ) ; + } + } + } + } + + + // Write blank + // Temp? + attr = p.context.voidAttr ; + if ( attr !== null ) { + for ( ; srcX <= srcXmax ; srcX ++ , dstOffset += this.ScreenBuffer.prototype.ITEM_SIZE ) { + // Write the attributes + p.context.writeAttr( p.context.dstBuffer , attr , dstOffset ) ; + + // Write the character + p.context.dstBuffer.write( ' ' , dstOffset + this.ScreenBuffer.prototype.ATTR_SIZE , this.ScreenBuffer.prototype.CHAR_SIZE ) ; + } + } +} ; + + + +// Naive loading +TextBuffer.prototype.load = function( path , callback ) { + this.buffer[ 0 ] = [] ; + this.buffer.length = 1 ; + + // Naive file loading, optimization are for later + fs.readFile( path , ( error , data ) => { + if ( error ) { callback( error ) ; return ; } + this.setText( data.toString() ) ; + callback() ; + } ) ; +} ; + + + +// Naive saving +TextBuffer.prototype.save = function( path , callback ) { + // Naive file saving, optimization are for later + fs.writeFile( path , this.getText() , ( error ) => { + if ( error ) { callback( error ) ; return ; } + callback() ; + } ) ; +} ; + + + +TextBuffer.prototype.object2attr = function( attrObject , colorNameToIndex = this.palette?.colorNameToIndex , legacyColor = false ) { + return this.ScreenBuffer.object2attr( attrObject , colorNameToIndex , legacyColor ) ; +} ; + + + + + +/* API for the text-machine module */ + + + +TextBuffer.prototype.runStateMachine = function() { + if ( ! this.stateMachine ) { return ; } + + this.stateMachine.reset() ; + + this.iterate( { finalCall: true } , data => { + data.textBuffer = this ; + this.stateMachine.pushEvent( data.text , data ) ; + } ) ; +} ; + + + +const TextMachineApi = {} ; +TextBuffer.TextMachineApi = TextMachineApi ; + + + +TextMachineApi.style = ( context , style ) => { + if ( context.x === null ) { return ; } // This is a newline or end of buffer character, there is no style to apply here + if ( ! style.code ) { style.code = context.textBuffer.ScreenBuffer.object2attr( style ) ; } // cache it now + + context.textBuffer.setAttrCodeAt( style.code , context.x , context.y ) ; +} ; + + + +TextMachineApi.startingStyle = ( context , style ) => { + if ( ! context.startingContext || context.startingContext.x === null ) { return ; } + if ( ! style.code ) { style.code = context.textBuffer.ScreenBuffer.object2attr( style ) ; } // cache it now + + context.textBuffer.setAttrCodeAt( style.code , context.startingContext.x , context.startingContext.y ) ; +} ; + +TextMachineApi.openingStyle = TextMachineApi.startingStyle ; + + + +TextMachineApi.blockStyle = function( context , style ) { + if ( context.x === null || ! context.startingContext || context.startingContext.x === null ) { return ; } + if ( ! style.code ) { style.code = context.textBuffer.ScreenBuffer.object2attr( style ) ; } // cache it now + + context.textBuffer.setAttrCodeRegion( style.code , { + xmin: context.startingContext.x , + xmax: context.x , + ymin: context.startingContext.y , + ymax: context.y + } ) ; +} ; + + + +TextMachineApi.hint = function( context , hints ) { + var misc ; + + if ( hints[ context.buffer ] ) { + misc = context.textBuffer.getMiscAt( context.x , context.y ) ; + if ( misc ) { misc.hint = hints[ context.buffer ] ; } + } +} ; + + +},{"./termkit.js":50,"fs":136,"string-kit":123}],7:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +/* + Ideally, this should be done using a graph algorithm, but we will just brute-force it for instance... +*/ + +module.exports = function autoComplete( array , startString , returnAlternatives , prefix , postfix ) { + var i , j , exitLoop , candidate = [] , completed = startString , hasCompleted = false ; + + if ( ! prefix ) { prefix = '' ; } + if ( ! postfix ) { postfix = '' ; } + + for ( i = 0 ; i < array.length ; i ++ ) { + if ( array[ i ].slice( 0 , startString.length ) === startString ) { candidate.push( array[ i ] ) ; } + } + + if ( ! candidate.length ) { return prefix + completed + postfix ; } + + if ( candidate.length === 1 ) { return prefix + candidate[ 0 ] + postfix ; } + + + // Multiple candidate, complete only the part they have in common + + j = startString.length ; + + exitLoop = false ; + + for ( j = startString.length ; j < candidate[ 0 ].length ; j ++ ) { + for ( i = 1 ; i < candidate.length ; i ++ ) { + if ( candidate[ i ][ j ] !== candidate[ 0 ][ j ] ) { exitLoop = true ; break ; } + } + + if ( exitLoop ) { break ; } + + completed += candidate[ 0 ][ j ] ; + hasCompleted = true ; + } + + if ( returnAlternatives && ! hasCompleted ) { + candidate.prefix = prefix ; + candidate.postfix = postfix ; + return candidate ; + } + + return prefix + completed + postfix ; +} ; + + +},{}],8:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const spChars = require( './spChars.js' ) ; + + + +/* + bar( value , options ) + * value `number` the value to display as bar + * options `object` of options, where: + * innerSize `number` the inner width in characters (default: 10) + * barStyle `function` the style of the bar, default to term.blue + * str `boolean` (default: false) if true it outputs nothing, instead it returns a string +*/ +module.exports = function( value , options ) { + var str = '' , barString = '' ; + + options = options || {} ; + + if ( isNaN( value ) || value < 0 ) { value = 0 ; } + else if ( value > 1 ) { value = 1 ; } + + var innerSize = options.innerSize || 10 ; + var fullBlocks = Math.floor( value * innerSize ) ; + var partialBlock = Math.round( ( value * innerSize - fullBlocks ) * 8 ) ; + var barStyle = options.barStyle || this.blue ; + + barString += '█'.repeat( fullBlocks ) ; + + if ( fullBlocks < innerSize ) { + barString += spChars.enlargingBlock[ partialBlock ] ; + barString += ' '.repeat( innerSize - fullBlocks - 1 ) ; + } + + if ( options.str ) { + str += this.str.inverse( '▉' ) ; + str += barStyle.str( barString ) ; + str += this.str( '▏' ) ; + return str ; + } + + this.inverse( '▉' ) ; + barStyle( barString ) ; + this( '▏' ) ; + + return this ; +} ; + + +},{"./spChars.js":48}],9:[function(require,module,exports){ +(function (global){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +module.exports = require( './termkit-no-lazy-require.js' ) ; +global.IS_BROWSER = true ; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./termkit-no-lazy-require.js":49}],10:[function(require,module,exports){ +module.exports=[ + { "r": 0, "g": 0, "b": 0, "names": [ "black" ] } , + { "r": 180, "g": 0, "b": 0, "names": [ "red" ] } , + { "r": 0, "g": 180, "b": 0, "names": [ "green" ] } , + { "r": 180, "g": 180, "b": 0, "names": [ "yellow" ] } , + { "r": 0, "g": 0, "b": 180, "names": [ "blue" ] } , + { "r": 180, "g": 0, "b": 180, "names": [ "magenta" ] } , + { "r": 0, "g": 180, "b": 180, "names": [ "cyan" ] } , + { "r": 220, "g": 220, "b": 220, "names": [ "white" ] } , + { "r": 55, "g": 55, "b": 55, "names": [ "brightBlack" , "gray" , "grey" ] } , + { "r": 250, "g": 0, "b": 0, "names": [ "brightRed" ] } , + { "r": 0, "g": 250, "b": 0, "names": [ "brightGreen" ] } , + { "r": 250, "g": 250, "b": 0, "names": [ "brightYellow" ] } , + { "r": 0, "g": 0, "b": 250, "names": [ "brightBlue" ] } , + { "r": 250, "g": 0, "b": 250, "names": [ "brightMagenta" ] } , + { "r": 0, "g": 250, "b": 250, "names": [ "brightCyan" ] } , + { "r": 250, "g": 250, "b": 250, "names": [ "brightWhite" ] } +] +},{}],11:[function(require,module,exports){ +module.exports=[ + { "r": 0, "g": 0, "b": 0, "names": [ "black" ] } , + { "r": 204, "g": 0, "b": 0, "names": [ "red" ] } , + { "r": 78, "g": 154, "b": 6, "names": [ "green" ] } , + { "r": 196, "g": 160, "b": 0, "names": [ "yellow" ] } , + { "r": 52, "g": 101, "b": 164, "names": [ "blue" ] } , + { "r": 117, "g": 80, "b": 123, "names": [ "magenta" ] } , + { "r": 6, "g": 152, "b": 154, "names": [ "cyan" ] } , + { "r": 211, "g": 215, "b": 207, "names": [ "white" ] } , + { "r": 85, "g": 87, "b": 83, "names": [ "brightBlack" , "gray" , "grey" ] } , + { "r": 239, "g": 41, "b": 41, "names": [ "brightRed" ] } , + { "r": 138, "g": 226, "b": 52, "names": [ "brightGreen" ] } , + { "r": 252, "g": 233, "b": 79, "names": [ "brightYellow" ] } , + { "r": 114, "g": 159, "b": 207, "names": [ "brightBlue" ] } , + { "r": 173, "g": 127, "b": 168, "names": [ "brightMagenta" ] } , + { "r": 52, "g": 226, "b": 226, "names": [ "brightCyan" ] } , + { "r": 238, "g": 238, "b": 236, "names": [ "brightWhite" ] } +] +},{}],12:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Promise = require( 'seventh' ) ; +const exec = require( 'child_process' ).exec ; +const path = require( 'path' ) ; +const os = require( 'os' ) ; + +const termkit = require( './termkit.js' ) ; + + + +// Try to guess the terminal without any async system call, using TERM and COLORTERM. +// Argument 'unpipe' is used when we will get a TTY even if we haven't one ATM. +exports.guessTerminal = function( unpipe ) { + var envVar , version ; + + var isSSH = !! process.env.SSH_CONNECTION ; + var isTTY = !! process.stdout.isTTY ; + + if ( ! isTTY && ! unpipe ) { + return { + isTTY: isTTY , + isSSH: isSSH , + appId: 'none' , + safe: true , + generic: 'none' + } ; + } + + var platform = os.platform() ; + var t256color = ( process.env.TERM && process.env.TERM.match( /256/ ) ) || + ( process.env.COLORTERM && process.env.COLORTERM.match( /256/ ) ) ; + var tTrueColor = process.env.COLORTERM && process.env.COLORTERM.match( /^(truecolor|24bits?)$/ ) ; + + var appId = + process.env.COLORTERM && ! tTrueColor ? process.env.COLORTERM : + process.env.TERM_PROGRAM ? process.env.TERM_PROGRAM : + process.env.TERM ; + + if ( platform === 'darwin' ) { appId = path.parse( appId ).name ; } + + // safe is true if we are sure about our guess + var safe = + appId !== process.env.TERM + || ( process.env.TERM && process.env.TERM !== 'xterm' && process.env.TERM !== 'xterm-256color' ) ; + + var generic = appId ; + + switch ( appId ) { + case 'xterm' : + case 'xterm-256color' : + if ( safe ) { break ; } + + if ( tTrueColor ) { + appId = generic = 'xterm-truecolor' ; + } + + // Many terminal advertise them as xterm, we will try to guess some of them here, + // using environment variable + if ( process.env.VTE_VERSION ) { + version = parseInt( process.env.VTE_VERSION , 10 ) ; + + if ( version >= 3803 ) { + appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ; + safe = true ; + break ; + } + } + + // BTW OSX terminals advertise them as xterm, while having their own key mapping... + if ( platform === 'darwin' ) { + appId = 'osx-256color' ; + break ; + } + + for ( envVar in process.env ) { + if ( envVar.match( /KONSOLE/ ) ) { + appId = t256color || tTrueColor ? 'konsole-256color' : 'konsole' ; + safe = true ; + break ; + } + } + + break ; + + case 'linux' : + case 'aterm' : + case 'kuake' : + case 'tilda' : + case 'terminology' : + case 'wterm' : + case 'mrxvt' : + break ; + + case 'gnome' : + case 'gnome-256color' : + case 'gnome-terminal' : + case 'gnome-terminal-256color' : + case 'terminator' : // it uses gnome terminal lib + case 'guake' : // same here + appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ; + break ; + case 'konsole' : + appId = t256color || tTrueColor ? 'konsole-256color' : 'konsole' ; + break ; + case 'rxvt' : + case 'rxvt-xpm' : + case 'rxvt-unicode-256color' : + case 'urxvt256c' : + case 'urxvt256c-ml' : + case 'rxvt-unicode' : + case 'urxvt' : + case 'urxvt-ml' : + if ( process.env.TERM === 'rxvt' ) { appId = 'rxvt-256color' ; } + else { appId = t256color || tTrueColor ? 'rxvt-256color' : 'rxvt' ; } + break ; + case 'xfce' : + case 'xfce-terminal' : + case 'xfce4-terminal' : + appId = 'xfce' ; + break ; + case 'eterm' : + case 'Eterm' : + appId = t256color || tTrueColor ? 'eterm-256color' : 'eterm' ; + break ; + case 'atomic-terminal' : + appId = 'atomic-terminal' ; + break ; + case 'xterm-kitty' : + case 'kitty' : + appId = 'kitty' ; + break ; + + // OSX Terminals + + case 'iTerm' : + case 'iterm' : + case 'iTerm2' : + case 'iterm2' : + case 'Terminal' : + case 'terminal' : + case 'Apple_Terminal' : + appId = 'osx-256color' ; + break ; + + default : + if ( ! appId ) { generic = 'unknown' ; } + else { generic = appId = generic.toLowerCase() ; } + break ; + } + + return { + isTTY , isSSH , appId , safe , generic: safe ? appId : generic + } ; +} ; + + + +function getParentProcess( pid ) { + var parentPid , appName ; + + return new Promise( ( resolve , reject ) => { + exec( 'ps -h -o ppid -p ' + pid , ( error , stdout ) => { + if ( error ) { reject( error ) ; return ; } + + parentPid = parseInt( stdout.match( /[0-9]+/gm )[ 0 ] , 10 ) ; + //console.error( "--- Parent PID: " , parentPid , stdout.match( /[0-9]+/gm ) ) ; + if( ! parentPid ) { reject( new Error( "Couldn't get parent PID" ) ) ; return ; } + + exec( 'ps -h -o comm -p ' + parentPid , ( error_ , stdout_ ) => { + if ( error_ ) { reject( error_ ) ; return ; } + + appName = stdout_.trim() ; + //console.error( "+++ appName: " , appName ) ; + resolve( { pid: parentPid , appName } ) ; + } ) ; + } ) ; + } ) ; +} + + + +// Work localy, do not work over SSH +exports.getParentTerminalInfo = async function( callback ) { + var loopAgain , error , appName , appNames = [] , appId , pid = process.pid ; + + if ( process.env.SSH_CONNECTION ) { + error = new Error( 'SSH connection detected, .getParentTerminalInfo() is useless in this context.' ) ; + if ( callback ) { callback( error ) ; return ; } + throw error ; + } + + var platform = os.platform() ; + var t256color = ( process.env.TERM && process.env.TERM.match( /256/ ) ) || + ( process.env.COLORTERM && process.env.COLORTERM.match( /256/ ) ) ; + var tTrueColor = process.env.COLORTERM && process.env.COLORTERM.match( /^(truecolor|24bits?)$/ ) ; + + try { + loopAgain = true ; + + while ( loopAgain ) { + ( { appName , pid } = await getParentProcess( pid ) ) ; + + //console.log( 'found:' , appName , pid ) ; + + if ( platform === 'darwin' ) { appName = path.parse( appName ).name ; } + appNames.push( appName ) ; + + // Do NOT skip the first, there are case where the terminal may run directly node.js without any shell in between + //if ( ++ loop <= 1 ) { asyncCallback( undefined , true ) ; return ; } + + loopAgain = false ; + + switch ( appName ) { + case 'linux' : + case 'xterm' : + case 'konsole' : + case 'gnome-terminal' : + case 'aterm' : + case 'guake' : + case 'kuake' : + case 'tilda' : + case 'terminology' : + case 'wterm' : + case 'mrxvt' : + appId = t256color || tTrueColor ? appName + '-256color' : appName ; + break ; + case 'atomic-terminal' : + appId = appName ; + break ; + case 'login' : + appName = 'linux' ; + appId = appName ; + break ; + // Use terminator as gnome-terminal, since it uses the gnome-terminal renderer + case 'terminator' : + appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ; + break ; + // Use rxvt as xterm-256color + case 'rxvt' : + case 'urxvt256c' : + case 'urxvt256c-ml' : + appId = 'rxvt-256color' ; + break ; + // Use rxvt as xterm + case 'urxvt' : + case 'urxvt-ml' : + appId = 'rxvt' ; + break ; + // xfce4-terminal + case 'xfce4-terminal' : + appId = 'xfce' ; + break ; + case 'gnome-terminal-' : + appName = 'gnome-terminal' ; + appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ; + break ; + case 'Eterm' : + case 'eterm' : + appName = 'Eterm' ; + appId = t256color || tTrueColor ? 'eterm-256color' : 'eterm' ; + break ; + case 'kitty' : + appName = appId = 'kitty' ; + break ; + + // OSX Terminals + + case 'iTerm' : + case 'iTerm2' : + case 'Terminal' : + appId = 'osx-256color' ; + break ; + + default : + if ( appName.match( /gnome-terminal/ ) ) { + appName = 'gnome-terminal' ; + appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ; + break ; + } + + if ( ! pid || pid === 1 ) { + throw new Error( 'Terminal not found, app names: ' + appNames.join( ', ' ) ) ; + } + + loopAgain = true ; + } + } + } + catch ( error_ ) { + if ( callback ) { callback( error_ ) ; return ; } + throw error_ ; + } + + var result = { + appId: appId , + appName: appName , + pid: pid , + safe: true + } ; + + if ( callback ) { callback( undefined , result ) ; return ; } + + return result ; +} ; + + + +// Work locally, do not work over SSH +exports.getDetectedTerminal = async function( callback ) { + var terminal , info , + guessed = termkit.guessTerminal() ; + + if ( guessed.safe || guessed.isSSH ) { + // If we have a good guess, use it now + terminal = termkit.createTerminal( { + stdin: process.stdin , + stdout: process.stdout , + stderr: process.stderr , + generic: ( process.env.TERM && process.env.TERM.toLowerCase() ) || 'unknown' , + appId: guessed.safe ? guessed.appId : undefined , + // appName: guessed.safe ? guessed.appName : undefined , + isTTY: guessed.isTTY , + isSSH: guessed.isSSH , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + + if ( callback ) { callback( undefined , terminal ) ; } + return terminal ; + } + + try { + info = await termkit.getParentTerminalInfo() ; + + terminal = termkit.createTerminal( { + stdin: process.stdin , + stdout: process.stdout , + stderr: process.stderr , + generic: ( process.env.TERM && process.env.TERM.toLowerCase() ) || 'unknown' , + appId: info.appId , + appName: info.appName , + isTTY: guessed.isTTY , + isSSH: guessed.isSSH , + pid: info.pid , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + } + catch ( error ) { + // Do not issue error + terminal = termkit.createTerminal( { + stdin: process.stdin , + stdout: process.stdout , + stderr: process.stderr , + generic: ( process.env.TERM && process.env.TERM.toLowerCase() ) || 'unknown' , + appId: guessed.safe ? guessed.appId : undefined , + // appName: guessed.safe ? guessed.appName : undefined , + isTTY: guessed.isTTY , + isSSH: guessed.isSSH , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + } + + if ( callback ) { callback( undefined , terminal ) ; } + return terminal ; +} ; + + +}).call(this)}).call(this,require('_process')) +},{"./termkit.js":50,"_process":179,"child_process":136,"os":166,"path":178,"seventh":108}],13:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Text = require( './Text.js' ) ; +const spChars = require( '../spChars.js' ) ; + + + +function AnimatedText( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + options.attr = options.attr || {} ; + + if ( Array.isArray( options.animation ) ) { + this.animation = options.animation ; + } + else if ( typeof options.animation === 'string' ) { + this.animation = spChars.animation[ options.animation ] || spChars.animation.lineSpinner ; + if ( options.contentHasMarkup !== false ) { options.contentHasMarkup = true ; } + } + else { + this.animation = spChars.animation.lineSpinner ; + } + + this.animation = this.animation.map( e => Array.isArray( e ) ? e : [ e ] ) ; + + this.isAnimated = false ; + this.frameDuration = options.frameDuration || 150 ; + this.animationSpeed = options.animationSpeed || 1 ; + this.frame = options.frame || 0 ; + this.autoUpdateTimer = null ; + + this.autoUpdate = this.autoUpdate.bind( this ) ; + + options.content = this.animation[ this.frame ] ; + + Text.call( this , options ) ; + + if ( this.elementType === 'AnimatedText' && ! options.noDraw ) { + this.draw() ; + this.animate() ; + } +} + +module.exports = AnimatedText ; + +AnimatedText.prototype = Object.create( Text.prototype ) ; +AnimatedText.prototype.constructor = AnimatedText ; +AnimatedText.prototype.elementType = 'AnimatedText' ; + +AnimatedText.prototype.inlineCursorRestoreAfterDraw = true ; + + + +AnimatedText.prototype.animate = function( animationSpeed = 1 ) { + this.isAnimated = !! animationSpeed ; + this.animationSpeed = + animationSpeed || 0 ; + + if ( ! this.isAnimated ) { + if ( this.autoUpdateTimer ) { clearTimeout( this.autoUpdateTimer ) ; } + this.autoUpdateTimer = null ; + return ; + } + + if ( ! this.autoUpdateTimer ) { + this.autoUpdateTimer = setTimeout( () => this.autoUpdate() , this.frameDuration / this.animationSpeed ) ; + } +} ; + + + +AnimatedText.prototype.autoUpdate = function() { + this.frame = ( this.frame + 1 ) % this.animation.length ; + this.content = this.animation[ this.frame ] ; + this.draw() ; + this.autoUpdateTimer = setTimeout( () => this.autoUpdate() , this.frameDuration / this.animationSpeed ) ; +} ; + + +},{"../spChars.js":48,"./Text.js":32}],14:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const builtinBarChars = require( '../spChars.js' ).bar ; + + + +function Bar( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + this.minValue = + options.minValue || 0 ; + this.maxValue = options.maxValue !== undefined ? + options.maxValue || 0 : 1 ; + this.value = + options.value || 0 ; + + if ( this.value < this.minValue ) { this.value = this.minValue ; } + else if ( this.value > this.maxValue ) { this.value = this.maxValue ; } + + this.borderAttr = options.borderAttr || { bold: true } ; + this.bodyAttr = options.bodyAttr || { color: 'blue' } ; + this.barChars = builtinBarChars.classic ; + + if ( typeof options.barChars === 'object' ) { + this.barChars = options.barChars ; + } + else if ( typeof options.barChars === 'string' && builtinBarChars[ options.barChars ] ) { + this.barChars = builtinBarChars[ options.barChars ] ; + } + + this.overTextFullAttr = options.overTextFullAttr || { bgColor: 'blue' } ; + this.overTextEmptyAttr = options.overTextEmptyAttr || { bgColor: 'default' } ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Bar' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Bar ; + +Bar.prototype = Object.create( Element.prototype ) ; +Bar.prototype.constructor = Bar ; +Bar.prototype.elementType = 'Bar' ; + + + +Bar.prototype.preDrawSelf = function() { + var index , x , fullCells , emptyCells , partialCellRate , + fullContent , fullContentWidth = 0 , emptyContent , emptyContentWidth = 0 , + noPartialCell = false , + partialCell = null , + innerSize = this.outputWidth - 2 , + rate = ( this.value - this.minValue ) / ( this.maxValue - this.minValue ) ; + + if ( ! rate || rate < 0 ) { rate = 0 ; } + else if ( rate > 1 ) { rate = 1 ; } + + fullCells = Math.floor( rate * innerSize ) ; + partialCellRate = rate * innerSize - fullCells ; + + if ( this.content ) { + if ( partialCellRate < 0.5 ) { + fullContent = Element.truncateContent( this.content , fullCells , this.contentHasMarkup ) ; + fullContentWidth = Element.getLastTruncateWidth() ; + if ( fullContentWidth < this.contentWidth ) { noPartialCell = true ; } + } + else { + fullContent = Element.truncateContent( this.content , fullCells + 1 , this.contentHasMarkup ) ; + fullContentWidth = Element.getLastTruncateWidth() ; + + if ( fullContentWidth < this.contentWidth || fullContentWidth === fullCells + 1 ) { + noPartialCell = true ; + fullCells ++ ; + } + } + } + + + if ( ! noPartialCell && fullCells < innerSize ) { + if ( this.barChars.body.length <= 2 ) { + // There is no chars for partial cells + if ( partialCellRate >= 0.5 ) { fullCells ++ ; } + } + else if ( this.barChars.body.length === 3 ) { + partialCell = this.barChars.body[ 1 ] ; + } + else if ( this.barChars.body.length === 4 ) { + partialCell = this.barChars.body[ partialCellRate < 0.5 ? 1 : 2 ] ; + } + else { + index = Math.floor( 1.5 + partialCellRate * ( this.barChars.body.length - 3 ) ) ; + partialCell = this.barChars.body[ index ] ; + } + } + + emptyCells = innerSize - fullCells - ( partialCell ? 1 : 0 ) ; + + if ( this.content && fullContentWidth < this.contentWidth ) { + emptyContent = Element.truncateContent( this.content.slice( fullContent.length ) , emptyCells , this.contentHasMarkup ) ; + emptyContentWidth = Element.getLastTruncateWidth() ; + } + + + // Rendering... + + x = this.outputX ; + this.outputDst.put( { + x: x ++ , y: this.outputY , attr: this.borderAttr , markup: true + } , this.barChars.border[ 0 ] ) ; + + if ( fullContentWidth ) { + this.outputDst.put( { + x: x , y: this.outputY , attr: this.overTextFullAttr , markup: true + } , fullContent ) ; + x += fullContentWidth ; + } + + if ( fullCells - fullContentWidth > 0 ) { + this.outputDst.put( { + x: x , y: this.outputY , attr: this.bodyAttr , markup: true + } , this.barChars.body[ 0 ].repeat( fullCells - fullContentWidth ) ) ; + x += fullCells - fullContentWidth ; + } + + if ( partialCell ) { + this.outputDst.put( { + x: x ++ , y: this.outputY , attr: this.bodyAttr , markup: true + } , partialCell ) ; + } + + if ( emptyContentWidth ) { + this.outputDst.put( { + x: x , y: this.outputY , attr: this.overTextEmptyAttr , markup: true + } , emptyContent ) ; + x += emptyContentWidth ; + } + + if ( emptyCells - emptyContentWidth > 0 ) { + this.outputDst.put( { + x: x , y: this.outputY , attr: this.bodyAttr , markup: true + } , this.barChars.body[ this.barChars.body.length - 1 ].repeat( emptyCells - emptyContentWidth ) ) ; + x += emptyCells - emptyContentWidth ; + } + + this.outputDst.put( { + x: x , y: this.outputY , attr: this.borderAttr , markup: true + } , this.barChars.border[ 1 ] ) ; +} ; + + + +Bar.prototype.getValue = function() { return this.value ; } ; + +Bar.prototype.setValue = function( value , internalAndNoDraw ) { + this.value = + value || 0 ; + + if ( this.value < this.minValue ) { this.value = this.minValue ; } + else if ( this.value > this.maxValue ) { this.value = this.maxValue ; } + + if ( ! internalAndNoDraw ) { this.draw() ; } +} ; + + +},{"../spChars.js":48,"./Element.js":23}],15:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +/* Base class for menus (ColumnMenu, RowMenu, etc) */ + + + +const tree = require( 'tree-kit' ) ; + +const Element = require( './Element.js' ) ; +const Button = require( './Button.js' ) ; + + + +function BaseMenu( options = {} ) { + Element.call( this , options ) ; + + this.backgroundAttr = options.backgroundAttr || { bgColor: 'white' , color: 'black' } ; + this.contentEllipsis = options.contentEllipsis || '…' ; + this.previousPageContent = options.previousPageContent || '«' ; + this.previousPageContentHasMarkup = !! options.previousPageContentHasMarkup ; + this.nextPageContent = options.nextPageContent || '»' ; + this.nextPageContentHasMarkup = !! options.nextPageContentHasMarkup ; + this.itemsDef = options.items || [] ; + this.previousPageDef = options.previousPage ; + this.nextPageDef = options.nextPage ; + this.masterDef = options.master ; + this.separatorDef = options.separator ; + this.buttons = [] ; + this.focusChild = null ; + + // Pagination + this.page = 0 ; + this.maxPage = 0 ; + + // Submenu + this.hasSubmenu = !! options.submenu ; + this.isSubmenu = !! options.isSubmenu ; + this.submenu = null ; // A child (column) menu + this.submenuParentButton = null ; // The button that opened the submenu + this.submenuOptions = null ; + + if ( this.hasSubmenu ) { + // Use tree-kit because 'options' comes from an Object.create() and has almost no owned properties + this.submenuOptions = tree.extend( null , {} , options , { + // Things to clear or to force + internal: true , + parent: null , + items: null + //x: undefined , outputX: undefined , + //y: undefined , outputY: undefined , + //width: undefined , outputWidth: undefined , + //height: undefined , outputHeight: undefined , + //submenu: false + } ) ; + + if ( options.submenu && typeof options.submenu === 'object' ) { + Object.assign( this.submenuOptions , options.submenu ) ; + } + } + + this.onButtonSubmit = this.onButtonSubmit.bind( this ) ; + this.onButtonToggle = this.onButtonToggle.bind( this ) ; + this.onButtonFocus = this.onButtonFocus.bind( this ) ; + this.onButtonBlinked = this.onButtonBlinked.bind( this ) ; + this.onSubmenuSubmit = this.onSubmenuSubmit.bind( this ) ; + this.onKey = this.onKey.bind( this ) ; + this.onWheel = this.onWheel.bind( this ) ; + this.onFocus = this.onFocus.bind( this ) ; + + // Global default attributes + this.buttonBlurAttr = options.buttonBlurAttr || this.defaultOptions.buttonBlurAttr || { bgColor: 'black' , color: 'white' , bold: true } ; + this.buttonEvenBlurAttr = options.buttonEvenBlurAttr || null ; + this.buttonFocusAttr = options.buttonFocusAttr || this.defaultOptions.buttonFocusAttr || { bgColor: 'white' , color: 'black' , bold: true } ; + this.buttonDisabledAttr = options.buttonDisabledAttr || this.defaultOptions.buttonDisabledAttr || { bgColor: 'black' , color: 'brightBlack' , bold: true } ; + this.buttonSubmittedAttr = options.buttonSubmittedAttr || this.defaultOptions.buttonSubmittedAttr || { bgColor: 'brightBlack' , color: 'brightWhite' , bold: true } ; + this.turnedOnBlurAttr = options.turnedOnBlurAttr || this.defaultOptions.turnedOnBlurAttr || { bgColor: 'cyan' } ; + this.turnedOnFocusAttr = options.turnedOnFocusAttr || this.defaultOptions.turnedOnFocusAttr || { bgColor: 'brightCyan' , bold: true } ; + this.turnedOffBlurAttr = options.turnedOffBlurAttr || this.defaultOptions.turnedOffBlurAttr || { bgColor: 'gray' , dim: true } ; + this.turnedOffFocusAttr = options.turnedOffFocusAttr || this.defaultOptions.turnedOffFocusAttr || { bgColor: 'white' , color: 'black' , bold: true } ; + + // Padding + this.blurLeftPadding = options.blurLeftPadding || options.leftPadding || '' ; + this.blurRightPadding = options.blurRightPadding || options.rightPadding || '' ; + this.focusLeftPadding = options.focusLeftPadding || options.leftPadding || '' ; + this.focusRightPadding = options.focusRightPadding || options.rightPadding || '' ; + this.disabledLeftPadding = options.disabledLeftPadding || options.leftPadding || '' ; + this.disabledRightPadding = options.disabledRightPadding || options.rightPadding || '' ; + this.submittedLeftPadding = options.submittedLeftPadding || options.leftPadding || '' ; + this.submittedRightPadding = options.submittedRightPadding || options.rightPadding || '' ; + this.turnedOnFocusLeftPadding = options.turnedOnFocusLeftPadding || options.turnedOnLeftPadding || options.leftPadding || '' ; + this.turnedOnFocusRightPadding = options.turnedOnFocusRightPadding || options.turnedOnRightPadding || options.rightPadding || '' ; + this.turnedOffFocusLeftPadding = options.turnedOffFocusLeftPadding || options.turnedOffLeftPadding || options.leftPadding || '' ; + this.turnedOffFocusRightPadding = options.turnedOffFocusRightPadding || options.turnedOffRightPadding || options.rightPadding || '' ; + this.turnedOnBlurLeftPadding = options.turnedOnBlurLeftPadding || options.turnedOnLeftPadding || options.leftPadding || '' ; + this.turnedOnBlurRightPadding = options.turnedOnBlurRightPadding || options.turnedOnRightPadding || options.rightPadding || '' ; + this.turnedOffBlurLeftPadding = options.turnedOffBlurLeftPadding || options.turnedOffLeftPadding || options.leftPadding || '' ; + this.turnedOffBlurRightPadding = options.turnedOffBlurRightPadding || options.turnedOffRightPadding || options.rightPadding || '' ; + this.paddingHasMarkup = !! options.paddingHasMarkup ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + if ( options.buttonKeyBindings ) { this.buttonKeyBindings = options.buttonKeyBindings ; } + if ( options.buttonActionKeyBindings ) { this.buttonActionKeyBindings = options.buttonActionKeyBindings ; } + if ( options.toggleButtonKeyBindings ) { this.toggleButtonKeyBindings = options.toggleButtonKeyBindings ; } + if ( options.toggleButtonActionKeyBindings ) { this.toggleButtonActionKeyBindings = options.toggleButtonActionKeyBindings ; } + + this.on( 'key' , this.onKey ) ; + this.on( 'wheel' , this.onWheel ) ; + this.on( 'focus' , this.onFocus ) ; +} + +module.exports = BaseMenu ; + +BaseMenu.prototype = Object.create( Element.prototype ) ; +BaseMenu.prototype.constructor = BaseMenu ; +BaseMenu.prototype.elementType = 'BaseMenu' ; + +BaseMenu.prototype.needInput = true ; + + + +BaseMenu.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + if ( this.submenu ) { this.submenu.destroy( true ) ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'wheel' , this.onWheel ) ; + this.off( 'focus' , this.onFocus ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +BaseMenu.prototype.previousPage = function( focusType ) { + var focusAware ; + + if ( this.maxPage && this.page > 0 ) { + this.page -- ; + this.initPage() ; + this.focusChild = this.children[ this.children.length - 2 ] ; + focusAware = this.document.giveFocusTo_( this.focusChild , focusType ) ; + if ( ! focusAware ) { this.document.focusPrevious() ; } + this.updateDraw() ; + } +} ; + + + +BaseMenu.prototype.nextPage = function( focusType ) { + var focusAware ; + + if ( this.maxPage && this.page < this.maxPage ) { + this.page ++ ; + this.initPage() ; + this.focusChild = this.children[ 1 ] ; + focusAware = this.document.giveFocusTo_( this.focusChild , focusType ) ; + if ( ! focusAware ) { this.document.focusNext() ; } + this.updateDraw() ; + } +} ; + + + +BaseMenu.prototype.toPage = function( page , focusType ) { + var focusAware ; + + if ( this.maxPage && page !== this.page ) { + this.page = page ; + this.initPage() ; + this.focusChild = this.children[ 1 ] ; + focusAware = this.document.giveFocusTo_( this.focusChild , focusType ) ; + if ( ! focusAware ) { this.document.focusNext() ; } + this.updateDraw() ; + } +} ; + + + +BaseMenu.prototype.focusValue = function( itemValue , focusType , forceInitPage = false ) { + var focusAware , itemDef , item ; + + itemDef = this.itemsDef.find( it => ! it.disabled && it.value === itemValue ) ; + if ( ! itemDef ) { return ; } + + if ( this.page !== itemDef.page || forceInitPage ) { + this.page = itemDef.page ; + this.initPage() ; + } + + item = this.buttons.find( it => it.def === itemDef ) ; + if ( ! item ) { return ; } // Not possible, but well... + + this.focusChild = item ; + + focusAware = this.document.giveFocusTo_( this.focusChild , focusType ) ; + if ( ! focusAware ) { this.document.focusNext() ; } + + this.draw() ; +} ; + + + +BaseMenu.prototype.setItem = function( itemValue , itemOptions ) { + var itemDef , focusValue ; + + itemDef = this.itemsDef.find( it => it.value === itemValue ) ; + if ( ! itemDef ) { return false ; } + + Object.assign( itemDef , itemOptions ) ; + + focusValue = this.focusChild && this.focusChild.value ; + + this.initChildren( true ) ; + + if ( focusValue !== undefined ) { + // Note: .focusValue() call .draw() behind the scene, and last argument force a .initPage() call + this.focusValue( focusValue , 'refocus' , true ) ; + } + else { + this.initPage() ; + this.draw() ; + } + + return true ; +} ; + + + +BaseMenu.prototype.onKey = function( key , trash , data ) { + switch( this.keyBindings[ key ] ) { + case 'previous' : + this.focusChild = this.focusPreviousChild( ! this.maxPage ) ; + if ( this.focusChild === this.children[ 0 ] && this.maxPage && this.page > 0 ) { + this.previousPage( 'backCycle' ) ; + } + break ; + case 'next' : + this.focusChild = this.focusNextChild( ! this.maxPage ) ; + if ( this.focusChild === this.children[ this.children.length - 1 ] && this.maxPage && this.page < this.maxPage ) { + this.nextPage( 'cycle' ) ; + } + break ; + case 'previousPage' : + if ( this.maxPage && this.page > 0 ) { + this.previousPage( 'backCycle' ) ; + } + break ; + case 'nextPage' : + if ( this.maxPage && this.page < this.maxPage ) { + this.nextPage( 'cycle' ) ; + } + break ; + case 'firstPage' : + if ( this.maxPage && this.page !== 0 ) { + this.toPage( 0 , 'backCycle' ) ; + } + break ; + case 'lastPage' : + if ( this.maxPage && this.page !== this.maxPage ) { + this.toPage( this.maxPage , 'cycle' ) ; + } + break ; + case 'parentMenu' : + if ( this.isSubmenu ) { + // Back up the parent, because current instance can be destroyed by parent.closeSubmenu() + let parent = this.parent ; + if ( this.parent.submenuOptions.hideParent ) { this.parent.closeSubmenu() ; } + parent.document.giveFocusTo( parent ) ; + } + break ; + case 'submenu' : + if ( this.hasSubmenu && this.focusChild?.def?.items ) { + this.openSubmenu( this.focusChild.value , this.focusChild ) ; + if ( this.submenu ) { this.document.giveFocusTo( this.submenu ) ; } + } + break ; + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +BaseMenu.prototype.onWheel = function( data ) { + if ( data.yDirection < 0 ) { this.previousPage( 'backCycle' ) ; } + else if ( data.yDirection > 0 ) { this.nextPage( 'cycle' ) ; } +} ; + + + +BaseMenu.prototype.onFocus = function( focus , type ) { + if ( type === 'cycle' || type === 'backCycle' ) { return ; } + //if ( type === 'backCycle' ) { return ; } + + if ( focus ) { + // Defer to the next tick to avoid recursive events producing wrong listener order + process.nextTick( () => { + if ( this.focusChild && ! this.focusChild.destroyed ) { this.document.giveFocusTo( this.focusChild , 'delegate' ) ; } + else { this.focusChild = this.focusNextChild() ; } + } ) ; + } +} ; + + + +BaseMenu.prototype.onButtonSubmit = function( buttonValue , action , button ) { + switch ( button.internalRole ) { + case 'previousPage' : + this.previousPage() ; + break ; + case 'nextPage' : + this.nextPage() ; + break ; + default : + if ( this.hasSubmenu && button.def.items ) { + if ( this.submenuOptions.openOn === 'parentSubmit' ) { + this.openSubmenu( button.value , button ) ; + } + + if ( this.submenu ) { + this.document.giveFocusTo( this.submenu ) ; + } + } + else { + this.emit( 'submit' , buttonValue , action , this ) ; + } + } +} ; + + + +BaseMenu.prototype.onButtonBlinked = function( buttonValue , action , button ) { + switch ( button.internalRole ) { + case 'previousPage' : + case 'nextPage' : + break ; + default : + if ( this.hasSubmenu && button.def.items ) { + if ( this.submenuOptions.openOn === 'parentBlinked' ) { + this.openSubmenu( button.value , button ) ; + } + } + else { + this.emit( 'blinked' , buttonValue , action , this ) ; + } + } +} ; + + + +BaseMenu.prototype.onButtonFocus = function( focus , type , button ) { + switch ( button.internalRole ) { + case 'previousPage' : + case 'nextPage' : + break ; + default : + if ( focus && this.hasSubmenu && button.def.items && this.submenuOptions.openOn === 'parentFocus' ) { + this.openSubmenu( button.value , button ) ; + } + + this.emit( 'itemFocus' , button.value , focus , button ) ; + } +} ; + + + +BaseMenu.prototype.onSubmenuSubmit = function( buttonValue , action , button ) { + button.once( 'blinked' , ( buttonValue_ , reserved , button_ ) => { + if ( this.submenuOptions.closeOn === 'childSubmit' ) { + this.closeSubmenu() ; + this.document.giveFocusTo( this.submenuParentButton || this ) ; + } + this.emit( 'blinked' , buttonValue_ , reserved , this ) ; + } ) ; + + this.emit( 'submit' , buttonValue , action , this ) ; +} ; + + + +// Userland: .submenu( itemValue ) +// Internal: .submenu( itemValue , button ) +BaseMenu.prototype.openSubmenu = function( itemValue , button = null ) { + var x , y , width , height , + itemDef = button ? this.itemsDef.find( it => it === button.def ) : + this.itemsDef.find( it => it.value === itemValue ) ; + + if ( ! itemDef || ! itemDef.items || ! itemDef.items.length ) { return ; } + + if ( this.submenu ) { + if ( this.submenu.def === itemDef ) { return ; } + this.closeSubmenu() ; + } + + this.submenuParentButton = button ; + + switch ( this.submenuOptions.disposition ) { + case 'overwrite' : + x = this.outputX ; + y = this.outputY ; + //width = this.outputWidth ; + width = this.submenuOptions.width ; + //height = this.outputHeight ; + height = this.submenuOptions.height ; + break ; + case 'right' : + default : + x = this.outputX + this.outputWidth ; + y = this.outputY ; + width = this.submenuOptions.width || this.outputWidth ; + break ; + } + + if ( this.submenuOptions.hideParent ) { + this.children.forEach( e => e.hidden = true ) ; + } + + this.submenu = new this.constructor( Object.assign( {} , this.submenuOptions , { + internal: true , + parent: this , + isSubmenu: true , + def: itemDef , + outputX: x , + outputY: y , + outputWidth: width , + outputHeight: height , + items: itemDef.items , + noDraw: true + } ) ) ; + + this.redraw() ; + + if ( this.submenuOptions.focusOnOpen ) { + this.document.giveFocusTo( this.submenu ) ; + } + + this.submenu.on( 'submit' , this.onSubmenuSubmit ) ; + + // Re-emit itemFocus from child + this.submenu.on( 'itemFocus' , ( ... args ) => this.emit( 'itemFocus' , ... args ) ) ; +} ; + + + +BaseMenu.prototype.closeSubmenu = function() { + if ( ! this.submenu ) { return false ; } + if ( this.submenuOptions.hideParent ) { this.children.forEach( e => e.hidden = false ) ; } + this.submenu.destroy() ; + this.submenu = null ; + return true ; +} ; + + + +// Should be redefined in the derivative class +BaseMenu.prototype.defaultOptions = {} ; +BaseMenu.prototype.keyBindings = {} ; +BaseMenu.prototype.buttonKeyBindings = {} ; +BaseMenu.prototype.buttonActionKeyBindings = {} ; +BaseMenu.prototype.toggleButtonKeyBindings = {} ; +BaseMenu.prototype.toggleButtonActionKeyBindings = {} ; +BaseMenu.prototype.initPage = function() {} ; +BaseMenu.prototype.onButtonToggle = function() {} ; +BaseMenu.prototype.childUseParentKeyValue = false ; + + +}).call(this)}).call(this,require('_process')) +},{"./Button.js":16,"./Element.js":23,"_process":179,"tree-kit":134}],16:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Text = require( './Text.js' ) ; + + + +function Button( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( ! Array.isArray( options.content ) ) { options.content = [ options.content || '' ] ; } + + this.blurContent = + options.blurContent ? ( Array.isArray( options.blurContent ) ? options.blurContent : [ options.blurContent ] ) : + options.content ; + + this.focusContent = + options.focusContent ? ( Array.isArray( options.focusContent ) ? options.focusContent : [ options.focusContent ] ) : + options.content ; + + this.disabledContent = + options.disabledContent ? ( Array.isArray( options.disabledContent ) ? options.disabledContent : [ options.disabledContent ] ) : + options.content ; + + this.submittedContent = + options.submittedContent ? ( Array.isArray( options.submittedContent ) ? options.submittedContent : [ options.submittedContent ] ) : + options.content ; + + this.turnedOnBlurContent = + options.turnedOnBlurContent ? ( Array.isArray( options.turnedOnBlurContent ) ? options.turnedOnBlurContent : [ options.turnedOnBlurContent ] ) : + options.turnedOnContent ? ( Array.isArray( options.turnedOnContent ) ? options.turnedOnContent : [ options.turnedOnContent ] ) : + options.content ; + + this.turnedOffBlurContent = + options.turnedOffBlurContent ? ( Array.isArray( options.turnedOffBlurContent ) ? options.turnedOffBlurContent : [ options.turnedOffBlurContent ] ) : + options.turnedOffContent ? ( Array.isArray( options.turnedOffContent ) ? options.turnedOffContent : [ options.turnedOffContent ] ) : + options.content ; + + this.turnedOnFocusContent = + options.turnedOnFocusContent ? ( Array.isArray( options.turnedOnFocusContent ) ? options.turnedOnFocusContent : [ options.turnedOnFocusContent ] ) : + options.turnedOnContent ? ( Array.isArray( options.turnedOnContent ) ? options.turnedOnContent : [ options.turnedOnContent ] ) : + options.content ; + + this.turnedOffFocusContent = + options.turnedOffFocusContent ? ( Array.isArray( options.turnedOffFocusContent ) ? options.turnedOffFocusContent : [ options.turnedOffFocusContent ] ) : + options.turnedOffContent ? ( Array.isArray( options.turnedOffContent ) ? options.turnedOffContent : [ options.turnedOffContent ] ) : + options.content ; + + this.blurLeftPadding = options.blurLeftPadding || options.leftPadding || '' ; + this.blurRightPadding = options.blurRightPadding || options.rightPadding || '' ; + this.focusLeftPadding = options.focusLeftPadding || options.leftPadding || '' ; + this.focusRightPadding = options.focusRightPadding || options.rightPadding || '' ; + this.disabledLeftPadding = options.disabledLeftPadding || options.leftPadding || '' ; + this.disabledRightPadding = options.disabledRightPadding || options.rightPadding || '' ; + this.submittedLeftPadding = options.submittedLeftPadding || options.leftPadding || '' ; + this.submittedRightPadding = options.submittedRightPadding || options.rightPadding || '' ; + + // Used by ToggleButton, it's easier to move the functionality here to compute size at only one place + this.turnedOnBlurLeftPadding = options.turnedOnBlurLeftPadding || options.turnedOnLeftPadding || options.leftPadding || '' ; + this.turnedOnBlurRightPadding = options.turnedOnBlurRightPadding || options.turnedOnRightPadding || options.rightPadding || '' ; + this.turnedOffBlurLeftPadding = options.turnedOffBlurLeftPadding || options.turnedOffLeftPadding || options.leftPadding || '' ; + this.turnedOffBlurRightPadding = options.turnedOffBlurRightPadding || options.turnedOffRightPadding || options.rightPadding || '' ; + this.turnedOnFocusLeftPadding = options.turnedOnFocusLeftPadding || options.turnedOnLeftPadding || options.leftPadding || '' ; + this.turnedOnFocusRightPadding = options.turnedOnFocusRightPadding || options.turnedOnRightPadding || options.rightPadding || '' ; + this.turnedOffFocusLeftPadding = options.turnedOffFocusLeftPadding || options.turnedOffLeftPadding || options.leftPadding || '' ; + this.turnedOffFocusRightPadding = options.turnedOffFocusRightPadding || options.turnedOffRightPadding || options.rightPadding || '' ; + + this.paddingHasMarkup = !! options.paddingHasMarkup ; + + // Used by menus, to assign nextPage/previousPage action + this.internalRole = options.internalRole || null ; + + // Force delete on width/height, should be computed from content + delete options.width ; + delete options.height ; + + Text.call( this , options ) ; + + if ( this.setContent === Button.prototype.setContent ) { + this.setContent( options.content || '' , options.contentHasMarkup , true , true ) ; + } + + this.blurAttr = options.blurAttr || { bgColor: 'brightBlack' } ; + this.focusAttr = options.focusAttr || { bgColor: 'blue' } ; + this.disabledAttr = options.disabledAttr || { bgColor: 'black' , color: 'brightBlack' } ; + this.submittedAttr = options.submittedAttr || { bgColor: 'brightBlue' } ; + this.turnedOnBlurAttr = options.turnedOnBlurAttr || { bgColor: 'cyan' } ; + this.turnedOnFocusAttr = options.turnedOnFocusAttr || { bgColor: 'brightCyan' , bold: true } ; + this.turnedOffBlurAttr = options.turnedOffBlurAttr || { bgColor: 'gray' , dim: true } ; + this.turnedOffFocusAttr = options.turnedOffFocusAttr || { bgColor: 'white' , color: 'black' , bold: true } ; + + this.disabled = !! options.disabled ; + this.submitted = !! options.submitted ; + this.submitOnce = !! options.submitOnce ; + + this.attr = null ; + this.leftPadding = null ; + this.rightPadding = null ; + this.updateStatus() ; + + this.onKey = this.onKey.bind( this ) ; + this.onShortcut = this.onShortcut.bind( this ) ; + this.onFocus = this.onFocus.bind( this ) ; + this.onClick = this.onClick.bind( this ) ; + this.onRightClick = this.onRightClick.bind( this ) ; + this.onMiddleClick = this.onMiddleClick.bind( this ) ; + this.onHover = this.onHover.bind( this ) ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + if ( options.actionKeyBindings ) { this.actionKeyBindings = options.actionKeyBindings ; } + + this.on( 'key' , this.onKey ) ; + this.on( 'shortcut' , this.onShortcut ) ; + this.on( 'focus' , this.onFocus ) ; + this.on( 'click' , this.onClick ) ; + this.on( 'rightClick' , this.onRightClick ) ; + this.on( 'middleClick' , this.onMiddleClick ) ; + this.on( 'hover' , this.onHover ) ; + + if ( this.elementType === 'Button' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Button ; + +Button.prototype = Object.create( Text.prototype ) ; +Button.prototype.constructor = Button ; +Button.prototype.elementType = 'Button' ; + +Button.prototype.needInput = true ; + + + +Button.prototype.keyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + ALT_ENTER: 'submit' +} ; + + + +Button.prototype.actionKeyBindings = {} ; + + + +Button.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'shortcut' , this.onShortcut ) ; + this.off( 'focus' , this.onFocus ) ; + this.off( 'click' , this.onClick ) ; + this.off( 'hover' , this.onHover ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +Button.prototype.setContent = function( content , hasMarkup , dontDraw = false , dontResize = false ) { + Element.prototype.setContent.call( this , content , hasMarkup , true , true ) ; + + this.blurContent = this.focusContent = + this.disabledContent = this.submittedContent = + this.turnedOnBlurContent = this.turnedOffBlurContent = + this.turnedOnFocusContent = this.turnedOffFocusContent = + this.content ; + + if ( ! dontResize && this.resizeOnContent ) { this.resizeOnContent() ; } + if ( ! dontDraw ) { this.redraw() ; } +} ; + + + +Button.prototype.computeRequiredWidth = function() { + return ( + Math.max( + Element.computeContentWidth( this.blurLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.focusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.disabledLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.submittedLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurLeftPadding , this.paddingHasMarkup ) + ) + Math.max( + Element.computeContentWidth( this.blurRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.focusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.disabledRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.submittedRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurRightPadding , this.paddingHasMarkup ) + ) + Math.max( + Element.computeContentWidth( this.blurContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.focusContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.disabledContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.submittedContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurContent , this.contentHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurContent , this.contentHasMarkup ) + ) || 1 + ) ; +} ; + + + +Button.prototype.drawSelfCursor = function() { + // Move the cursor back to the first cell + this.outputDst.moveTo( this.outputX , this.outputY ) ; + this.outputDst.drawCursor() ; +} ; + + + +// Blink effect, when the button is submitted +Button.prototype.blink = function( special = null , animationCountdown = 4 ) { + if ( animationCountdown ) { + if ( animationCountdown % 2 ) { this.attr = this.focusAttr ; } + else { this.attr = this.blurAttr ; } + + this.draw() ; + setTimeout( () => this.blink( special , animationCountdown - 1 ) , 80 ) ; + } + else { + this.updateStatus() ; + this.draw() ; + this.emit( 'blinked' , this.value , special , this ) ; + } +} ; + + + +Button.prototype.onFocus = function( focus , type ) { + this.hasFocus = focus ; + this.updateStatus() ; + this.draw() ; +} ; + + + +Button.prototype.updateStatus = function() { + if ( this.disabled ) { + this.attr = this.disabledAttr ; + this.content = this.disabledContent ; + this.leftPadding = this.disabledLeftPadding ; + this.rightPadding = this.disabledRightPadding ; + } + else if ( this.submitted ) { + this.attr = this.submittedAttr ; + this.content = this.submittedContent ; + this.leftPadding = this.submittedLeftPadding ; + this.rightPadding = this.submittedRightPadding ; + } + else if ( this.hasFocus ) { + this.attr = this.focusAttr ; + this.content = this.focusContent ; + this.leftPadding = this.focusLeftPadding ; + this.rightPadding = this.focusRightPadding ; + } + else { + this.attr = this.blurAttr ; + this.content = this.blurContent ; + this.leftPadding = this.blurLeftPadding ; + this.rightPadding = this.blurRightPadding ; + } +} ; + + + +Button.prototype.submit = function( special ) { + if ( this.submitOnce ) { this.submitted = true ; } + this.emit( 'submit' , this.value , special , this ) ; + + // Blink call .updateStatus() + this.blink( special ) ; +} ; + + + +Button.prototype.unsubmit = function() { + this.submitted = false ; + this.updateStatus() ; +} ; + + + +Button.prototype.onKey = function( key , altKeys , data ) { + switch( this.keyBindings[ key ] ) { + case 'submit' : + if ( this.disabled || this.submitted ) { break ; } + this.submit( this.actionKeyBindings[ key ] ) ; + break ; + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +Button.prototype.onHover = function( data ) { + if ( this.disabled || this.submitted ) { return ; } + this.document.giveFocusTo( this , 'hover' ) ; +} ; + + + +Button.prototype.onClick = function( data ) { + if ( this.disabled || this.submitted ) { return ; } + this.document.giveFocusTo( this , 'select' ) ; + this.submit( this.actionKeyBindings.click ) ; +} ; + + + +Button.prototype.onRightClick = function( data ) { + if ( this.disabled || this.submitted ) { return ; } + this.document.giveFocusTo( this , 'select' ) ; + this.submit( this.actionKeyBindings.rightClick ) ; +} ; + + + +Button.prototype.onMiddleClick = function( data ) { + if ( this.disabled || this.submitted ) { return ; } + this.document.giveFocusTo( this , 'select' ) ; + this.submit( this.actionKeyBindings.middleClick ) ; +} ; + + + +Button.prototype.onShortcut = function() { + if ( this.disabled || this.submitted ) { return ; } + this.document.giveFocusTo( this , 'select' ) ; + this.submit() ; +} ; + + +},{"./Element.js":23,"./Text.js":32}],17:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const BaseMenu = require( './BaseMenu.js' ) ; +const Button = require( './Button.js' ) ; +const ToggleButton = require( './ToggleButton.js' ) ; + + + +// Inherit from BaseMenu for common methods + +function ColumnMenu( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + this.onParentResize = this.onParentResize.bind( this ) ; + + // Overwritten by Element() when .autoWidth is set + if ( ! options.outputWidth && ! options.width ) { + if ( options.parent ) { + options.outputWidth = Math.min( options.parent.inputWidth , options.parent.outputWidth ) ; + } + else if ( options.inlineTerm ) { + options.outputWidth = options.inlineTerm.width ; + } + } + + this.buttonsMaxWidth = 0 ; + this.buttonPaddingWidth = 0 ; + this.buttonSymbolWidth = 0 ; + + this.pageHeight = 0 ; // current page height, computed + this.pageItemsDef = null ; + //this.masterItem = options.masterItem || null ; + + if ( ! options.multiLineItems ) { + options.height = options.items && options.items.length ; + } + + BaseMenu.call( this , options ) ; + + this.maxHeight = + this.autoHeight && this.outputDst ? Math.round( this.outputDst.height * this.autoHeight ) : + options.maxHeight ? options.maxHeight : + options.pageMaxHeight ? options.pageMaxHeight : // for backward compatibility + this.strictInline ? this.inlineTerm.height : + Infinity ; + + this.on( 'parentResize' , this.onParentResize ) ; + + this.multiLineItems = !! options.multiLineItems ; + + this.initChildren() ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'ColumnMenu' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = ColumnMenu ; + +ColumnMenu.prototype = Object.create( BaseMenu.prototype ) ; +ColumnMenu.prototype.constructor = ColumnMenu ; +ColumnMenu.prototype.elementType = 'ColumnMenu' ; + + + +ColumnMenu.prototype.inlineNewLine = true ; +ColumnMenu.prototype.ButtonClass = Button ; + + + +ColumnMenu.prototype.defaultOptions = { + buttonBlurAttr: { bgColor: 'black' , color: 'white' , bold: true } , + buttonEvenBlurAttr: null , + buttonFocusAttr: { bgColor: 'white' , color: 'black' , bold: true } , + buttonDisabledAttr: { bgColor: 'black' , color: 'gray' , bold: true } , + buttonSubmittedAttr: { bgColor: 'gray' , color: 'brightWhite' , bold: true } , + turnedOnBlurAttr: { bgColor: 'cyan' } , + turnedOnFocusAttr: { bgColor: 'brightCyan' , bold: true } , + turnedOffBlurAttr: { bgColor: 'gray' , dim: true } , + turnedOffFocusAttr: { bgColor: 'white' , color: 'black' , bold: true } +} ; + + + +ColumnMenu.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'focus' , this.onFocus ) ; + this.off( 'parentResize' , this.onParentResize ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +ColumnMenu.prototype.keyBindings = { + UP: 'previous' , + DOWN: 'next' , + PAGE_UP: 'previousPage' , + PAGE_DOWN: 'nextPage' , + HOME: 'firstPage' , + END: 'lastPage' , + // ENTER: 'submit' , + // KP_ENTER: 'submit' , + ALT_ENTER: 'submit' , + ESCAPE: 'parentMenu' , + LEFT: 'parentMenu' , + RIGHT: 'submenu' +} ; + +ColumnMenu.prototype.buttonKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' +} ; + +ColumnMenu.prototype.toggleButtonKeyBindings = { + ENTER: 'toggle' , + KP_ENTER: 'toggle' +} ; + + + +// Pre-compute page and eventually create Buttons automatically +ColumnMenu.prototype.initChildren = function( noInitPage = false ) { + // Do not exit now: maybe there are masterDef and separatorDef (SelectList*) + //if ( ! this.itemsDef.length ) { return ; } + + // Reset things, because .initChildren() can be called multiple times on 'parentResize' events + this.pageItemsDef = [] ; + + this.buttonPaddingWidth = + Math.max( + Element.computeContentWidth( this.blurLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.focusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.disabledLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.submittedLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurLeftPadding , this.paddingHasMarkup ) + ) + Math.max( + Element.computeContentWidth( this.blurRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.focusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.disabledRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.submittedRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurRightPadding , this.paddingHasMarkup ) + ) ; + + if ( this.buttonPaddingWidth > this.outputWidth ) { + // The padding itself is bigger than the width... so what should we do? + return ; + } + + var ellipsisWidth = Element.computeContentWidth( this.contentEllipsis , false ) ; + + + this.previousPageDef = Object.assign( { content: '▲' , internalRole: 'previousPage' } , this.previousPageDef ) ; + this.previousPageDef.contentHasMarkup = this.previousPageDef.contentHasMarkup || this.previousPageDef.markup ; + this.previousPageDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.previousPageDef.content , this.previousPageDef.contentHasMarkup ) ; + this.previousPageDef.buttonContent = this.previousPageDef.content ; + + this.nextPageDef = Object.assign( { content: '▼' , internalRole: 'nextPage' } , this.nextPageDef ) ; + this.nextPageDef.contentHasMarkup = this.nextPageDef.contentHasMarkup || this.nextPageDef.markup ; + this.nextPageDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.nextPageDef.content , this.nextPageDef.contentHasMarkup ) ; + this.nextPageDef.buttonContent = this.nextPageDef.content ; + + if ( this.masterDef ) { + this.masterDef = Object.assign( { content: 'column-menu' , internalRole: 'master' } , this.masterDef ) ; + this.masterDef.contentHasMarkup = this.masterDef.contentHasMarkup || this.masterDef.markup ; + + this.masterDef.buttonContent = this.masterDef.content ; + + if ( this.masterDef.symbol ) { + this.buttonSymbolWidth = 1 + Element.computeContentWidth( this.masterDef.symbol ) ; + this.masterDef.buttonContent += ' ' + this.masterDef.symbol ; + } + + this.masterDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.masterDef.buttonContent , this.masterDef.contentHasMarkup ) ; + } + + this.buttonsMaxWidth = Math.max( this.buttonsMaxWidth , this.previousPageDef.width , this.nextPageDef.width , this.masterDef ? this.masterDef.width : 0 ) ; + + + var page = 0 , pageHeight = 0 ; + + this.itemsDef.forEach( ( def , index ) => { + def.contentHasMarkup = def.contentHasMarkup || def.markup ; + def.buttonContent = def.content ; + def.buttonBlurContent = def.blurContent ; + def.buttonFocusContent = def.focusContent ; + def.buttonDisabledContent = def.disabledContent ; + def.buttonSubmittedContent = def.submittedContent ; + def.buttonTurnedOnBlurContent = def.turnedOnBlurContent ; + def.buttonTurnedOffBlurContent = def.turnedOffBlurContent ; + def.buttonTurnedOnFocusContent = def.turnedOnFocusContent ; + def.buttonTurnedOffFocusContent = def.turnedOffFocusContent ; + + // Computing all button content variant like suggested by issue #144 is really complicated, + // All variant should come out with the same width and height. + + var contentWidth = Element.computeContentWidth( def.content , def.contentHasMarkup ) , + buttonHeight = 1 , + isLastItem = index === this.itemsDef.length - 1 , + // height without previous/next buttons: + pageMaxInnerHeight = Math.max( 1 , ! isLastItem ? this.maxHeight - 1 : this.maxHeight ) , + overflow = this.buttonPaddingWidth + contentWidth - this.outputWidth ; + + if ( overflow > 0 ) { + if ( this.multiLineItems ) { + def.buttonContent = Element.wordWrapContent( def.content , this.outputWidth - this.buttonPaddingWidth , def.contentHasMarkup ) ; + contentWidth = this.outputWidth - this.buttonPaddingWidth ; + buttonHeight = def.buttonContent.length ; + } + else { + def.buttonContent = Element.truncateContent( def.content , contentWidth - overflow - ellipsisWidth , def.contentHasMarkup ) + this.contentEllipsis ; + contentWidth = Element.computeContentWidth( def.buttonContent , def.contentHasMarkup ) ; + } + } + + if ( index && pageHeight + buttonHeight > pageMaxInnerHeight ) { + // ^--- check if there is content on this page, which is always true except for the very first item of page 0 + page ++ ; + pageHeight = 1 ; + + // Still too big? + if ( pageHeight + buttonHeight > pageMaxInnerHeight && buttonHeight > 1 ) { + buttonHeight = pageMaxInnerHeight - pageHeight ; + def.buttonContent.length = buttonHeight ; + def.buttonContent[ buttonHeight - 1 ] = + Element.truncateContent( + def.buttonContent[ buttonHeight - 1 ].trimRight() , + contentWidth - ellipsisWidth , + def.contentHasMarkup + ) + + this.contentEllipsis ; + } + } + + pageHeight += buttonHeight ; + + def.width = this.buttonPaddingWidth + contentWidth ; + def.page = page ; + + if ( def.width + this.buttonSymbolWidth > this.buttonsMaxWidth ) { + this.buttonsMaxWidth = def.width + this.buttonSymbolWidth ; + } + + if ( ! this.pageItemsDef[ page ] ) { this.pageItemsDef[ page ] = [] ; } + this.pageItemsDef[ page ].push( def ) ; + } ) ; + + this.maxPage = page ; + + if ( this.separatorDef ) { + this.separatorDef = Object.assign( { content: '-' , disabled: true , internalRole: 'separator' } , this.separatorDef ) ; + this.separatorDef.width = Element.computeContentWidth( this.separatorDef.content , this.separatorDef.contentHasMarkup ) ; + + if ( this.separatorDef.contentRepeat && this.separatorDef.width < this.buttonsMaxWidth - this.buttonPaddingWidth ) { + this.separatorDef.content = this.separatorDef.content.repeat( Math.floor( ( this.buttonsMaxWidth - this.buttonPaddingWidth ) / this.separatorDef.width ) ) ; + this.separatorDef.width = Element.computeContentWidth( this.separatorDef.content , this.separatorDef.contentHasMarkup ) ; + } + + this.separatorDef.width += this.buttonPaddingWidth ; + this.separatorDef.buttonContent = this.separatorDef.content ; + } + + if ( this.masterDef && this.masterDef.width < this.buttonsMaxWidth ) { + this.masterDef.buttonContent = this.masterDef.content + ' ' + ' '.repeat( this.buttonsMaxWidth - this.masterDef.width ) + this.masterDef.symbol ; + this.masterDef.width = this.buttonsMaxWidth ; + } + + + // Force at least an empty page + if ( ! this.pageItemsDef.length ) { this.pageItemsDef.push( [] ) ; } + + this.pageItemsDef.forEach( ( pageDef , index ) => { + if ( index ) { pageDef.unshift( this.previousPageDef ) ; } + if ( index < this.pageItemsDef.length - 1 ) { pageDef.push( this.nextPageDef ) ; } + if ( this.separatorDef ) { pageDef.unshift( this.separatorDef ) ; } + if ( this.masterDef ) { pageDef.unshift( this.masterDef ) ; } + } ) ; + + // /!\ Adjust the output width? /!\ + if ( this.outputWidth > this.buttonsMaxWidth ) { + this.outputWidth = this.buttonsMaxWidth ; + } + + // Only initPage if we are not a superclass of the object + if ( this.elementType === 'ColumnMenu' && ! noInitPage ) { this.initPage() ; } +} ; + + + +ColumnMenu.prototype.initPage = function( page = this.page ) { + var buttonOffsetX = 0 , buttonOffsetY = 0 ; + + if ( ! this.pageItemsDef[ page ] ) { return ; } + + this.buttons.forEach( button => button.destroy( false , true ) ) ; + this.buttons.length = 0 ; + + this.pageItemsDef[ page ].forEach( ( def , index ) => { + var ButtonConstructor , isToggle , key , value , blurAttr ; + + if ( ! Array.isArray( def.buttonContent ) ) { + def.buttonContent = [ def.buttonContent + ' '.repeat( this.buttonsMaxWidth - def.width ) ] ; + } + + ButtonConstructor = def.internalRole ? Button : this.ButtonClass ; + isToggle = ButtonConstructor === ToggleButton || ButtonConstructor.prototype instanceof ToggleButton ; + + key = def.key ; // For ToggleButton + value = this.childUseParentKeyValue && key && this.value && typeof this.value === 'object' ? this.value[ key ] : def.value ; + + if ( index % 2 ) { + // Odd + blurAttr = def.blurAttr || this.buttonBlurAttr ; + } + else { + // Even + blurAttr = def.evenBlurAttr || def.blurAttr || this.buttonEvenBlurAttr || this.buttonBlurAttr ; + } + + this.buttons[ index ] = new ButtonConstructor( { + internal: true , + parent: this , + childId: index , + internalRole: def.internalRole , + contentHasMarkup: def.contentHasMarkup , + content: def.buttonContent , + blurContent: def.buttonBlurContent , + focusContent: def.buttonFocusContent , + disabledContent: def.buttonDisabledContent , + submittedContent: def.buttonSubmittedContent , + turnedOnBlurContent: def.buttonTurnedOnBlurContent , + turnedOffBlurContent: def.buttonTurnedOffBlurContent , + turnedOnFocusContent: def.buttonTurnedOnFocusContent , + turnedOffFocusContent: def.buttonTurnedOffFocusContent , + disabled: def.disabled , + def , + key , + value , + outputX: this.outputX + buttonOffsetX , + outputY: this.outputY + buttonOffsetY , + + blurAttr , + focusAttr: def.focusAttr || this.buttonFocusAttr , + disabledAttr: def.disabledAttr || this.buttonDisabledAttr , + submittedAttr: def.submittedAttr || this.buttonSubmittedAttr , + turnedOnFocusAttr: def.turnedOnFocusAttr || this.turnedOnFocusAttr , + turnedOffFocusAttr: def.turnedOffFocusAttr || this.turnedOffFocusAttr , + turnedOnBlurAttr: def.turnedOnBlurAttr || this.turnedOnBlurAttr , + turnedOffBlurAttr: def.turnedOffBlurAttr || this.turnedOffBlurAttr , + + blurLeftPadding: this.blurLeftPadding , + blurRightPadding: this.blurRightPadding , + focusLeftPadding: this.focusLeftPadding , + focusRightPadding: this.focusRightPadding , + disabledLeftPadding: this.disabledLeftPadding , + disabledRightPadding: this.disabledRightPadding , + submittedLeftPadding: this.submittedLeftPadding , + submittedRightPadding: this.submittedRightPadding , + + turnedOnFocusLeftPadding: this.turnedOnFocusLeftPadding , + turnedOnFocusRightPadding: this.turnedOnFocusRightPadding , + turnedOffFocusLeftPadding: this.turnedOffFocusLeftPadding , + turnedOffFocusRightPadding: this.turnedOffFocusRightPadding , + turnedOnBlurLeftPadding: this.turnedOnBlurLeftPadding , + turnedOnBlurRightPadding: this.turnedOnBlurRightPadding , + turnedOffBlurLeftPadding: this.turnedOffBlurLeftPadding , + turnedOffBlurRightPadding: this.turnedOffBlurRightPadding , + + paddingHasMarkup: this.paddingHasMarkup , + + keyBindings: isToggle ? this.toggleButtonKeyBindings : this.buttonKeyBindings , + actionKeyBindings: isToggle ? this.toggleButtonActionKeyBindings : this.buttonActionKeyBindings , + shortcuts: def.shortcuts , + + noDraw: true + } ) ; + + this.buttons[ index ].on( 'submit' , this.onButtonSubmit ) ; + this.buttons[ index ].on( 'blinked' , this.onButtonBlinked ) ; + this.buttons[ index ].on( 'focus' , this.onButtonFocus ) ; + + if ( isToggle ) { + this.buttons[ index ].on( 'toggle' , this.onButtonToggle ) ; + } + + buttonOffsetY += this.buttons[ index ].outputHeight ; + } ) ; + + // Set outputHeight to the correct value + if ( buttonOffsetY < this.outputHeight ) { this.needRedraw = true ; } + this.pageHeight = this.outputHeight = buttonOffsetY ; +} ; + + + +ColumnMenu.prototype.onParentResize = function() { + if ( ! this.autoWidth && ! this.autoHeight ) { return ; } + + if ( this.autoWidth ) { + this.outputWidth = Math.round( this.outputDst.width * this.autoWidth ) ; + } + + if ( this.autoHeight ) { + this.maxHeight = Math.round( this.outputDst.height * this.autoHeight ) ; + } + + this.initChildren( true ) ; + this.page = 0 ; + this.initPage() ; + this.draw() ; +} ; + + +},{"./BaseMenu.js":15,"./Button.js":16,"./Element.js":23,"./ToggleButton.js":35}],18:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const ColumnMenu = require( './ColumnMenu.js' ) ; +const ToggleButton = require( './ToggleButton.js' ) ; + + + +// Inherit from ColumnMenu for common methods + +function ColumnMenuMulti( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + // ColumnMenuMulti value is always a set of values + if ( options.value && typeof options.value === 'object' ) { + this.setValue( options.value , true ) ; + } + else { + this.value = {} ; + } + + ColumnMenu.call( this , options ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'ColumnMenuMulti' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = ColumnMenuMulti ; + +ColumnMenuMulti.prototype = Object.create( ColumnMenu.prototype ) ; +ColumnMenuMulti.prototype.constructor = ColumnMenuMulti ; +ColumnMenuMulti.prototype.elementType = 'ColumnMenuMulti' ; + + + +ColumnMenuMulti.prototype.ButtonClass = ToggleButton ; +ColumnMenuMulti.prototype.childUseParentKeyValue = true ; + + + +ColumnMenuMulti.prototype.initChildren = function( noInitPage = false ) { + ColumnMenu.prototype.initChildren.call( this ) ; + + // Only initPage if we are not a superclass of the object + if ( this.elementType === 'ColumnMenuMulti' && ! noInitPage ) { this.initPage() ; } +} ; + + + +// Set all existing children ToggleButton to the correct value +ColumnMenuMulti.prototype.setValue = function( value , noDraw ) { + if ( ! value || typeof value !== 'object' ) { return ; } + + if ( Array.isArray( value ) || value instanceof Set ) { + this.value = {} ; + for ( let key of value ) { + if ( key && typeof key === 'string' ) { + this.value[ key ] = true ; + } + } + } + else { + this.value = value ; + } + + // Can be called during init, before .initPage() is called + if ( ! this.buttons ) { return ; } + + this.buttons.forEach( button => { + if ( button.internalRole || ! button.key || ! ( button instanceof ToggleButton ) ) { return ; } + button.setValue( this.value[ button.key ] ) ; + } ) ; + + if ( ! noDraw ) { this.draw() ; } +} ; + + + +ColumnMenuMulti.prototype.onKey = function( key , altKeys , data ) { + switch( this.keyBindings[ key ] ) { + case 'previous' : + this.focusChild = this.focusPreviousChild( ! this.maxPage ) ; + if ( this.focusChild === this.children[ 0 ] && this.maxPage && this.page > 0 ) { + this.previousPage( 'backCycle' ) ; + } + break ; + case 'next' : + this.focusChild = this.focusNextChild( ! this.maxPage ) ; + if ( this.focusChild === this.children[ this.children.length - 1 ] && this.maxPage && this.page < this.maxPage ) { + this.nextPage( 'cycle' ) ; + } + break ; + case 'previousPage' : + if ( this.maxPage && this.page > 0 ) { + this.previousPage( 'backCycle' ) ; + } + break ; + case 'nextPage' : + if ( this.maxPage && this.page < this.maxPage ) { + this.nextPage( 'cycle' ) ; + } + break ; + case 'firstPage' : + if ( this.maxPage && this.page !== 0 ) { + this.toPage( 0 , 'backCycle' ) ; + } + break ; + case 'lastPage' : + if ( this.maxPage && this.page !== this.maxPage ) { + this.toPage( this.maxPage , 'cycle' ) ; + } + break ; + case 'submit' : + this.emit( 'submit' , this.value , undefined , this ) ; + break ; + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +ColumnMenuMulti.prototype.onButtonSubmit = function( buttonValue , action , button ) { + switch ( button.internalRole ) { + case 'previousPage' : + this.previousPage() ; + break ; + case 'nextPage' : + this.nextPage() ; + break ; + default : + this.emit( 'submit' , this.value , action , this ) ; + } +} ; + + + +ColumnMenuMulti.prototype.onButtonToggle = function( buttonValue , action , button ) { + if ( ! button.key ) { return ; } + + if ( buttonValue ) { this.value[ button.key ] = true ; } + else { delete this.value[ button.key ] ; } + + this.emit( 'itemToggle' , button.key , buttonValue , button ) ; +} ; + + +},{"./ColumnMenu.js":17,"./ToggleButton.js":35}],19:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +// Container: an enclosed surface (ScreenBuffer), with a viewport to allow scrolling. + + + +const Element = require( './Element.js' ) ; +const Slider = require( './Slider.js' ) ; +const ScreenBuffer = require( '../ScreenBuffer.js' ) ; + + + +function Container( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + this.onKey = this.onKey.bind( this ) ; + this.onClick = this.onClick.bind( this ) ; + this.onDrag = this.onDrag.bind( this ) ; + this.onWheel = this.onWheel.bind( this ) ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + + //console.error( 'this.document:' , !! this.document , !! ( this.document && this.document.palette ) , !! options.palette ) ; + this.palette = options.palette || ( this.document && this.document.palette ) ; + this.object2attr = object => ScreenBuffer.object2attr( object , this.palette && this.palette.colorNameToIndex ) ; + + this.scrollable = !! options.scrollable ; + this.hasVScrollBar = this.scrollable && !! options.vScrollBar ; + this.hasHScrollBar = this.scrollable && !! options.hScrollBar ; + this.scrollX = options.scrollX || 0 ; + this.scrollY = options.scrollY || 0 ; + this.vScrollBarSlider = null ; + this.hScrollBarSlider = null ; + + this.movable = !! options.movable ; + + this.viewportX = this.outputX + this.containerBorderSize ; + this.viewportY = this.outputY + this.containerBorderSize ; + this.viewportWidth = this.outputWidth - this.containerBorderSize * 2 ; + this.viewportHeight = this.outputHeight - this.containerBorderSize * 2 ; + + this.inputX = options.inputX || this.viewportX + this.scrollX ; + this.inputY = options.inputY || this.viewportY + this.scrollY ; + this.inputWidth = options.inputWidth || this.viewportWidth ; + this.inputHeight = options.inputHeight || this.viewportHeight ; + + this.inputDst = new ScreenBuffer( { + dst: this.outputDst , + x: this.inputX , + y: this.inputY , + width: this.inputWidth , + height: this.inputHeight , + palette: this.palette + } ) ; + + this.deltaDraw = false ; // Useful for Document, not so useful for other containers + this.backgroundAttr = options.backgroundAttr || { bgColor: 'default' } ; + + this.on( 'key' , this.onKey ) ; + this.on( 'click' , this.onClick ) ; + this.on( 'drag' , this.onDrag ) ; + this.on( 'wheel' , this.onWheel ) ; + + this.initChildren() ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Container' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Container ; + +Container.prototype = Object.create( Element.prototype ) ; +Container.prototype.constructor = Container ; +Container.prototype.elementType = 'Container' ; + +Container.prototype.isContainer = true ; +Container.prototype.containerBorderSize = 0 ; + + + +Container.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'click' , this.onClick ) ; + this.off( 'drag' , this.onDrag ) ; + this.off( 'wheel' , this.onWheel ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +Container.prototype.keyBindings = { + UP: 'tinyScrollUp' , + DOWN: 'tinyScrollDown' , + PAGE_UP: 'scrollUp' , + PAGE_DOWN: 'scrollDown' , + ' ': 'scrollDown' , + HOME: 'scrollTop' , + END: 'scrollBottom' , + LEFT: 'scrollLeft' , + RIGHT: 'scrollRight' +} ; + + + +Container.prototype.initChildren = function() { + if ( this.hasVScrollBar ) { + this.vScrollBarSlider = new Slider( { + internal: true , + parent: this , + x: this.viewportWidth - 1 , + y: 0 , + height: this.viewportHeight , + isVertical: true , + valueToRate: scrollY => -scrollY / Math.max( 1 , this.inputHeight - this.viewportHeight ) , + rateToValue: rate => -rate * Math.max( 1 , this.inputHeight - this.viewportHeight ) , + noDraw: true + } ) ; + + this.vScrollBarSlider.on( 'slideStep' , d => this.scroll( 0 , -d * Math.ceil( this.viewportHeight / 2 ) ) ) ; + this.vScrollBarSlider.on( 'slide' , value => this.scrollTo( null , value ) ) ; + } + + if ( this.hasHScrollBar ) { + this.hScrollBarSlider = new Slider( { + internal: true , + parent: this , + x: 0 , + y: this.inputWidth - this.containerBorderSize * 2 - 1 , + width: this.viewportWidth - this.hasVScrollBar , + valueToRate: scrollY => -scrollY / Math.max( 1 , this.inputWidth - this.viewportWidth ) , + rateToValue: rate => -rate * Math.max( 1 , this.inputWidth - this.viewportWidth ) , + noDraw: true + } ) ; + + this.hScrollBarSlider.on( 'slideStep' , d => this.scroll( -d * Math.ceil( this.viewportWidth / 2 ) , 0 ) ) ; + this.hScrollBarSlider.on( 'slide' , value => this.scrollTo( value , null ) ) ; + } +} ; + + + +// Accept ScreenBuffer#resize() argument: x, y, width, height. +// Should it support output* and input* args? +Container.prototype.resizeViewport = function( to ) { + this.viewportWidth = to.width ; + this.viewportHeight = to.height ; +} ; + + + +Container.prototype.resizeInput = function( to ) { + if ( ! to.x ) { to.x = 0 ; } + if ( ! to.y ) { to.y = 0 ; } + + this.inputDst.resize( to ) ; + + this.inputWidth = this.inputDst.width ; + this.inputHeight = this.inputDst.height ; + + this.children.forEach( child => child.emit( 'parentResize' , to ) ) ; +} ; + + + +Container.prototype.resize = function( to ) { + if ( ! to.x ) { to.x = 0 ; } + if ( ! to.y ) { to.y = 0 ; } + + this.inputDst.resize( to ) ; + + this.viewportWidth = this.inputWidth = this.inputDst.width ; + this.viewportHeight = this.inputHeight = this.inputDst.height ; + + this.children.forEach( child => child.emit( 'parentResize' , to ) ) ; +} ; + + + +Container.prototype.move = function( dx , dy , noDraw = false ) { + return this.moveTo( this.outputX + dx , this.outputY + dy , noDraw ) ; +} ; + + + +Container.prototype.moveTo = function( x , y , noDraw = false ) { + this.outputX = x ; + this.outputY = y ; + this.viewportX = this.outputX + this.containerBorderSize ; + this.viewportY = this.outputY + this.containerBorderSize ; + this.inputDst.x = this.inputX = this.viewportX + this.scrollX ; + this.inputDst.y = this.inputY = this.viewportY + this.scrollY ; + + if ( ! noDraw ) { this.redraw() ; } +} ; + + + +Container.prototype.scroll = function( dx , dy , dontDraw = false ) { + return this.scrollTo( dx ? this.scrollX + dx : null , dy ? this.scrollY + dy : null , dontDraw ) ; +} ; + + + +Container.prototype.scrollToTop = function( dontDraw = false ) { + return this.scrollTo( null , 0 , dontDraw ) ; +} ; + + + +Container.prototype.scrollToBottom = function( dontDraw = false ) { + // Ignore extra scrolling here + return this.scrollTo( null , this.viewportHeight - this.inputHeight , dontDraw ) ; +} ; + + + +Container.prototype.scrollTo = function( x , y , noDraw = false ) { + if ( ! this.scrollable ) { return ; } + + if ( x !== undefined && x !== null ) { + // Got a +1 after content size because of the word-wrap thing and eventual invisible \n + this.scrollX = Math.min( 0 , Math.max( Math.round( x ) , + this.viewportWidth - this.inputWidth + 1 + ) ) ; + + this.inputDst.x = this.inputX = this.viewportX + this.scrollX ; + //console.error( "New x-scrolling:" , x , this.scrollX , this.viewportX , this.inputX ) ; + } + + if ( y !== undefined && y !== null ) { + this.scrollY = Math.min( 0 , Math.max( Math.round( y ) , + this.viewportHeight - this.inputHeight + ) ) ; + + this.inputDst.y = this.inputY = this.viewportY + this.scrollY ; + //console.error( "New y-scrolling:" , y , this.scrollY , this.viewportY , this.inputY ) ; + } + + if ( this.vScrollBarSlider ) { + this.vScrollBarSlider.setValue( this.scrollY , true ) ; + this.vScrollBarSlider.setSizeAndPosition( { y: -this.scrollY } ) ; + } + + if ( this.hScrollBarSlider ) { + this.hScrollBarSlider.setValue( this.scrollX , true ) ; + } + + if ( ! noDraw ) { this.draw() ; } +} ; + + + +Container.prototype.preDrawSelf = function() { + this.inputDst.fill( { char: ' ' , attr: this.backgroundAttr } ) ; +} ; + + + +Container.prototype.postDrawSelf = function() { + this.inputDst.draw( { + dst: this.outputDst , + delta: this.deltaDraw , // Draw only diff or not? + inline: this.strictInline , + x: this.inputX , + y: this.inputY , + dstClipRect: { + x: this.viewportX , + y: this.viewportY , + width: this.viewportWidth , + height: this.viewportHeight + } + } ) ; +} ; + + + +Container.prototype.drawSelfCursor = function( elementTargeted ) { + if ( elementTargeted ) { this.restoreCursor() ; } + else { this.inputDst.drawCursor() ; } +} ; + + + +Container.prototype.onKey = function( key , trash , data ) { + switch( this.keyBindings[ key ] ) { + case 'tinyScrollUp' : + this.scroll( 0 , Math.ceil( this.viewportHeight / 5 ) ) ; + break ; + + case 'tinyScrollDown' : + this.scroll( 0 , -Math.ceil( this.viewportHeight / 5 ) ) ; + break ; + + case 'scrollUp' : + this.scroll( 0 , Math.ceil( this.viewportHeight / 2 ) ) ; + break ; + + case 'scrollDown' : + this.scroll( 0 , -Math.ceil( this.viewportHeight / 2 ) ) ; + break ; + + case 'scrollLeft' : + this.scroll( Math.ceil( this.viewportWidth / 2 ) , 0 ) ; + break ; + + case 'scrollRight' : + this.scroll( -Math.ceil( this.viewportWidth / 2 ) , 0 ) ; + break ; + + case 'scrollTop' : + this.scrollToTop() ; + break ; + + case 'scrollBottom' : + this.scrollToBottom() ; + break ; + + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +Container.prototype.onClick = function( data ) { + // It is susceptible to click event only when it is scrollable + if ( this.scrollable && ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } +} ; + + + +Container.prototype.onWheel = function( data ) { + // It's a "tiny" scroll + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + + if ( this.scrollable ) { + this.scroll( 0 , -data.yDirection * Math.ceil( this.viewportHeight / 5 ) ) ; + } +} ; + + + +Container.prototype.onDrag = function( data ) { + if ( ! this.movable || ( ! data.dx && ! data.dy ) ) { return ; } + this.move( data.dx , data.dy ) ; +} ; + + +},{"../ScreenBuffer.js":3,"./Element.js":23,"./Slider.js":31}],20:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Container = require( './Container.js' ) ; + +const Promise = require( 'seventh' ) ; + + + +function Document( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( ! options.inlineTerm ) { + options.outputX = 1 ; + options.outputY = 1 ; + options.outputWidth = options.outputDst.width ; + options.outputHeight = options.outputDst.height ; + } + + // Bypass the Element rule for strictInline, this mode should only be used for inline static Element + this.strictInlineSupport = !! options.strictInline ; + this.noInput = !! options.noInput ; + + Container.call( this , options ) ; + + // A document does not have parent + this.parent = null ; + + // The document of a document is itself + this.document = this ; + + // Being the top-level element before the Terminal object, this must use delta-drawing (except for strictInline mode) + this.deltaDraw = ! this.strictInline ; + + this.id = '_document' + '_' + ( nextId ++ ) ; + this.eventSource = options.eventSource ; + this.focusElement = null ; + this.hoverElement = null ; + this.clickOutCandidates = new Set() ; + + this.motionData = { + motion: false , + xFrom: null , + yFrom: null , + x: null , + y: null , + dx: null , + dy: null + //element: null , + //localDx: null , + //localDy: null + } ; + + this.draggingData = { + dragging: false , + xFrom: null , + yFrom: null , + x: null , + y: null , + dx: null , + dy: null , + element: null , + localDx: null , + localDy: null + } ; + + this.elements = {} ; + this.onEventSourceKey = this.onEventSourceKey.bind( this ) ; + this.onEventSourceMouse = this.onEventSourceMouse.bind( this ) ; + this.onEventSourceResize = this.onEventSourceResize.bind( this ) ; + + if ( ! this.strictInline && ! this.noInput ) { + // Do not change turn on/change input grabbing mode in strictInline mode + this.eventSource.grabInput( { mouse: 'motion' } ) ; + //this.eventSource.grabInput( { mouse: 'button' } ) ; + } + + this.elementByShortcut = {} ; + + //* + this.setClipboard = Promise.debounceUpdate( async ( str , source ) => { + if ( ! this.outputDst.setClipboard ) { return ; } + await this.outputDst.setClipboard( str , source ) ; + + // Avoid running too much xclip shell command + await Promise.resolveTimeout( 500 ) ; + } ) ; + + this.getClipboard = Promise.debounceDelay( 500 , async ( source ) => { + if ( ! this.outputDst.getClipboard ) { return '' ; } + return this.outputDst.getClipboard( source ) ; + } ) ; + //*/ + + this.eventSource.on( 'key' , this.onEventSourceKey ) ; + this.eventSource.on( 'mouse' , this.onEventSourceMouse ) ; + this.eventSource.on( 'resize' , this.onEventSourceResize ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Document' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Document ; + +//Document.prototype = Object.create( Element.prototype ) ; +Document.prototype = Object.create( Container.prototype ) ; +Document.prototype.constructor = Document ; +Document.prototype.elementType = 'Document' ; + + + +Document.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.eventSource.off( 'key' , this.onEventSourceKey ) ; + this.eventSource.off( 'mouse' , this.onEventSourceMouse ) ; + this.eventSource.off( 'resize' , this.onEventSourceResize ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; + + this.eventSource = null ; + this.setClipboard = null ; + this.getClipboard = null ; +} ; + + + +Document.prototype.keyBindings = Object.assign( {} , Container.prototype.keyBindings , { + TAB: 'focusNext' , + SHIFT_TAB: 'focusPrevious' +} ) ; + + + +// Next element ID +var nextId = 0 ; + +Document.prototype.assignId = function( element , id ) { + if ( ! id || typeof id !== 'string' || id[ 0 ] === '_' || this.elements[ id ] ) { + id = '_' + element.elementType + '_' + ( nextId ++ ) ; + } + + element.id = id ; + this.elements[ id ] = element ; +} ; + + + +Document.prototype.unassignId = function( element , id ) { + element.id = null ; + delete this.elements[ id ] ; +} ; + + + +Document.prototype.giveFocusTo = function( element , type ) { + if ( ! ( element instanceof Element ) ) { throw new TypeError( '' + element + ' is not an instance of Element.' ) ; } + if ( ! type ) { type = 'direct' ; } + if ( this.isAncestorOf( element ) ) { return this.giveFocusTo_( element , type ) ; } +} ; + + + +Document.prototype.giveFocusTo_ = function( element , type ) { + var ancestor , focusAware ; + + if ( this.focusElement !== element ) { + if ( this.focusElement ) { this.focusElement.emit( 'focus' , false , type , this.focusElement ) ; } + this.focusElement = element ; + this.focusElement.emit( 'focus' , true , type , this.focusElement ) ; + } + + // Return false if the focus was given to an element that does not care about focus and key event + focusAware = ! this.focusElement.disabled && ( this.focusElement.listenerCount( 'focus' ) || this.focusElement.listenerCount( 'key' ) ) ; + + if ( focusAware ) { + ancestor = this.focusElement ; + + while ( ancestor ) { + if ( ancestor.listenerCount( 'clickOut' ) ) { this.clickOutCandidates.add( ancestor ) ; } + ancestor = ancestor.parent ; + } + } + + return focusAware ; +} ; + + + +Document.prototype.focusNext = function() { + var index , startingElement , currentElement , focusAware ; + + if ( ! this.focusElement || ! this.isAncestorOf( this.focusElement ) ) { currentElement = this ; } + else { currentElement = this.focusElement ; } + + if ( currentElement === this && ! this.children.length ) { return ; } + + startingElement = currentElement ; + + for ( ;; ) { + if ( currentElement.children.length && ! currentElement.noChildFocus ) { + // Give focus to the first child of the element + currentElement = currentElement.children[ 0 ] ; + if ( ! currentElement.hidden ) { focusAware = this.giveFocusTo_( currentElement , 'cycle' ) ; } + } + else if ( currentElement.parent ) { + for ( ;; ) { + index = currentElement.parent.children.indexOf( currentElement ) ; + + if ( index + 1 < currentElement.parent.children.length ) { + // Give focus to the next sibling + currentElement = currentElement.parent.children[ index + 1 ] ; + if ( ! currentElement.hidden ) { + focusAware = this.giveFocusTo_( currentElement , 'cycle' ) ; + break ; + } + } + else if ( currentElement.parent.parent ) { + currentElement = currentElement.parent ; + } + else { + // We are at the top-level, just below the document, so cycle again at the first-top-level child + + // This check fixes infinite loop + if ( startingElement === currentElement.parent ) { return ; } + + currentElement = currentElement.parent.children[ 0 ] ; + if ( ! currentElement.hidden ) { + focusAware = this.giveFocusTo_( currentElement , 'cycle' ) ; + break ; + } + } + } + } + else { + // Nothing to do: no children, no parent, nothing... + return ; + } + + // Exit if the focus was given to a focus-aware element or if we have done a full loop already + //console.error( 'end of loop: ' , focusAware , startingElement.content , currentElement.content ) ; + if ( startingElement === currentElement || ( ! currentElement.hidden && focusAware ) ) { break ; } + } +} ; + + + +Document.prototype.focusPrevious = function() { + var index , startingElement , currentElement , focusAware ; + + if ( ! this.focusElement || ! this.isAncestorOf( this.focusElement ) ) { currentElement = this ; } + else { currentElement = this.focusElement ; } + + startingElement = currentElement ; + + for ( ;; ) { + if ( currentElement.parent ) { + index = currentElement.parent.children.indexOf( currentElement ) ; + + if ( index - 1 >= 0 ) { + // Give focus to the last child of the last child of the ... of the previous sibling + currentElement = currentElement.parent.children[ index - 1 ] ; + + while ( currentElement.children.length && ! currentElement.noChildFocus ) { + currentElement = currentElement.children[ currentElement.children.length - 1 ] ; + } + + if ( ! currentElement.hidden ) { focusAware = this.giveFocusTo_( currentElement , 'backCycle' ) ; } + } + else if ( currentElement.parent.parent ) { + currentElement = currentElement.parent ; + if ( ! currentElement.hidden ) { focusAware = this.giveFocusTo_( currentElement , 'backCycle' ) ; } + } + else { + // We are at the top-level, just below the document, so cycle again to the last child of the last child + // of the ... of the last-top-level child + + // This check fixes infinite loop + if ( startingElement === currentElement.parent ) { return ; } + + currentElement = currentElement.parent.children[ currentElement.parent.children.length - 1 ] ; + + while ( currentElement.children.length && ! currentElement.noChildFocus ) { + currentElement = currentElement.children[ currentElement.children.length - 1 ] ; + } + + if ( ! currentElement.hidden ) { focusAware = this.giveFocusTo_( currentElement , 'backCycle' ) ; } + } + } + else if ( currentElement.children.length ) { + // Give focus to the last child of the element + currentElement = currentElement.children[ currentElement.children.length - 1 ] ; + + while ( currentElement.children.length && ! currentElement.noChildFocus ) { + currentElement = currentElement.children[ currentElement.children.length - 1 ] ; + } + + if ( ! currentElement.hidden ) { focusAware = this.giveFocusTo_( currentElement , 'backCycle' ) ; } + } + else { + // Nothing to do: no children, no parent, nothing... + return ; + } + + // Exit if the focus was given to a focus-aware element or if we have done a full loop already + //console.error( 'end of loop: ' , focusAware , startingElement.content , currentElement.content ) ; + if ( startingElement === currentElement || ( ! currentElement.hidden && focusAware ) ) { break ; } + } +} ; + + + +Document.prototype.onEventSourceKey = function( key , altKeys , data ) { + if ( this.focusElement ) { + this.bubblingEvent( this.focusElement , key , altKeys , data ) ; + } + else { + this.defaultKeyHandling( key , altKeys , data ) ; + } +} ; + + + +Document.prototype.bubblingEvent = function( element , key , altKeys , data ) { + if ( element !== this ) { + element.emit( 'key' , key , altKeys , data , ( interruption , event ) => { + // Interruption means: the child consume the event, it does not want bubbling + if ( ! interruption ) { + if ( element.parent ) { this.bubblingEvent( element.parent , key , altKeys , data ) ; } + else { this.defaultKeyHandling( key , altKeys , data ) ; } + } + } ) ; + } + else { + this.defaultKeyHandling( key , altKeys , data ) ; + } +} ; + + + +Document.prototype.defaultKeyHandling = function( key , altKeys , data ) { + switch ( this.keyBindings[ key ] ) { + case 'focusNext' : + this.focusNext() ; + break ; + case 'focusPrevious' : + this.focusPrevious() ; + break ; + default : + if ( this.elementByShortcut[ key ] && this.elementByShortcut[ key ].document === this ) { + this.elementByShortcut[ key ].emit( 'shortcut' , key , altKeys , data ) ; + } + else { + this.emit( 'key' , key , altKeys , data ) ; + } + break ; + } +} ; + + + +Document.prototype.createShortcuts = function( element , ... keys ) { + if ( element.document !== this ) { return ; } + keys.forEach( key => this.elementByShortcut[ key ] = element ) ; +} ; + + + +Document.prototype.removeElementShortcuts = function( element ) { + for ( let key in this.elementByShortcut ) { + if ( this.elementByShortcut[ key ] === element ) { this.elementByShortcut[ key ] = null ; } + } +} ; + + + +Document.prototype.onEventSourceMouse = function( name , data ) { + var matches ; + + switch ( name ) { + case 'MOUSE_LEFT_BUTTON_PRESSED' : + this.mouseClick( data ) ; + break ; + + case 'MOUSE_MOTION' : + this.mouseMotion( data ) ; + break ; + + case 'MOUSE_DRAG' : + this.mouseDrag( data ) ; + break ; + + case 'MOUSE_RIGHT_BUTTON_PRESSED' : + this.mouseClick( data , 'rightClick' ) ; + break ; + + case 'MOUSE_MIDDLE_BUTTON_PRESSED' : + this.mouseClick( data , 'middleClick' ) ; + break ; + + case 'MOUSE_WHEEL_UP' : + data.yDirection = -1 ; + this.mouseWheel( data ) ; + break ; + + case 'MOUSE_WHEEL_DOWN' : + data.yDirection = 1 ; + this.mouseWheel( data ) ; + break ; + + // We only catch left mouse dragging ATM + case 'MOUSE_LEFT_BUTTON_RELEASED' : + if ( this.draggingData.dragging ) { this.mouseDragEnd( data ) ; } + break ; + } +} ; + + + +/* + /!\ Not sure if it's the correct way to do that /!\ + E.g: Does an element that listen to 'hover' intercept 'click'? + It is already proven to be bad for the mouse wheel, for ColumnMenu, it would prevent the menu to scroll on mouse wheel + because the buttons (children) catch the event without doing anything at all with it. + + Mouse event must have event bubbling too. +*/ +const COMMON_MOUSE_AWARE_FILTER = element => + element.listenerCount( 'click' ) || element.listenerCount( 'clickOut' ) || + element.listenerCount( 'rightClick' ) || element.listenerCount( 'middleClick' ) || //element.listenerCount( 'wheel' ) || + element.listenerCount( 'dragStart' ) || element.listenerCount( 'drag' ) || element.listenerCount( 'dragEnd' ) || + element.listenerCount( 'hover' ) || element.listenerCount( 'leave' ) || element.listenerCount( 'enter' ) ; + + + +// 'clickType' can be 'click' (normal left click), 'rightClick' or 'middleClick' +Document.prototype.mouseClick = function( data , clickType = 'click' ) { + var matches = this.childrenAt( data.x - this.outputX , data.y - this.outputY , COMMON_MOUSE_AWARE_FILTER ) ; + //console.error( "\n\n\n\n" , matches ) ; + + if ( ! matches.length ) { + if ( this.clickOutCandidates.size ) { + for ( let candidate of this.clickOutCandidates ) { + // Check that the candidate is still attached + if ( candidate.document === this ) { + candidate.emit( 'clickOut' ) ; + } + } + this.clickOutCandidates.clear() ; + } + + return ; + } + + if ( this.clickOutCandidates.size ) { + for ( let candidate of this.clickOutCandidates ) { + // Check that the candidate is still attached and is not on the click's tree branch + if ( candidate.document === this && candidate !== matches[ 0 ].element && ! candidate.isAncestorOf( matches[ 0 ].element ) ) { + candidate.emit( 'clickOut' ) ; + } + } + + this.clickOutCandidates.clear() ; + } + + matches[ 0 ].element.emit( clickType , { x: matches[ 0 ].x , y: matches[ 0 ].y } , matches[ 0 ].element ) ; +} ; + + + +Document.prototype.mouseMotionStart = function( data ) { + var matches ; + + this.motionData.motion = true ; + this.motionData.xFrom = data.xFrom ; + this.motionData.yFrom = data.yFrom ; + this.motionData.x = data.xFrom ; // We use xFrom/yFrom, .mouseMotion() will update it using x/y, setting dx/dy to the delta + this.motionData.y = data.yFrom ; + + //this.motionData.element = matches[ 0 ].element ; + //this.motionData.localDx = matches[ 0 ].x - data.xFrom ; + //this.motionData.localDy = matches[ 0 ].y - data.yFrom ; + //matches[ 0 ].element.emit( 'motionStart' , { x: matches[ 0 ].x , y: matches[ 0 ].y } , matches[ 0 ].element ) ; +} ; + + + +Document.prototype.mouseMotionEnd = function() { + this.motionData.motion = false ; +} ; + + + +// Also called from within .mouseDrag() +Document.prototype.mouseMotion = function( data , exclude = null ) { + var starting = false ; + + if ( ! this.motionData.motion ) { + starting = true ; + this.mouseMotionStart( data ) ; + } + + this.motionData.dx = data.x - this.motionData.x ; + this.motionData.dy = data.y - this.motionData.y ; + this.motionData.x = data.x ; + this.motionData.y = data.y ; + + // Newest Gnome-Terminal send motion event even when no progress have been made, this check avoid useless computing. + if ( ! starting && ! this.motionData.dx && ! this.motionData.dy ) { return ; } + + var matches = this.childrenAt( data.x - this.outputX , data.y - this.outputY , COMMON_MOUSE_AWARE_FILTER ) ; + //console.error( "\n\n\n\n" , matches ) ; + + if ( ! matches.length ) { + if ( this.hoverElement ) { + this.hoverElement.emit( 'leave' ) ; + this.hoverElement = null ; + } + + return ; + } + + if ( matches[ 0 ] !== exclude ) { + matches[ 0 ].element.emit( 'hover' , { x: matches[ 0 ].x , y: matches[ 0 ].y } , matches[ 0 ].element ) ; + } + + matches.forEach( match => { + if ( match.element.listenerCount( 'clickOut' ) ) { + this.clickOutCandidates.add( match.element ) ; + } + } ) ; + + if ( matches[ 0 ].element !== this.hoverElement ) { + if ( this.hoverElement ) { this.hoverElement.emit( 'leave' ) ; } + + this.hoverElement = matches[ 0 ].element ; + this.hoverElement.emit( 'enter' ) ; + } +} ; + + + +Document.prototype.mouseDragStart = function( data ) { + var matches ; + + this.draggingData.dragging = true ; + this.draggingData.xFrom = data.xFrom ; + this.draggingData.yFrom = data.yFrom ; + this.draggingData.x = data.xFrom ; // We use xFrom/yFrom, .mouseDrag() will update it using x/y, setting dx/dy to the delta + this.draggingData.y = data.yFrom ; + + matches = this.childrenAt( data.xFrom - this.outputX , data.yFrom - this.outputY , COMMON_MOUSE_AWARE_FILTER ) ; + + if ( ! matches.length ) { + if ( this.hoverElement ) { + this.hoverElement.emit( 'leave' ) ; + this.hoverElement = null ; + } + + return ; + } + + this.draggingData.element = matches[ 0 ].element ; + this.draggingData.localDx = matches[ 0 ].x - data.xFrom ; + this.draggingData.localDy = matches[ 0 ].y - data.yFrom ; + + matches[ 0 ].element.emit( 'dragStart' , { x: matches[ 0 ].x , y: matches[ 0 ].y } , matches[ 0 ].element ) ; +} ; + + + +Document.prototype.mouseDragEnd = function( data ) { + if ( this.draggingData.element ) { + this.draggingData.element.emit( + 'dragEnd' , + { + xFrom: this.draggingData.xFrom + this.draggingData.localDx , + yFrom: this.draggingData.yFrom + this.draggingData.localDy , + x: this.draggingData.x + this.draggingData.localDx , + y: this.draggingData.y + this.draggingData.localDy + } , + this.draggingData.element + ) ; + } + + this.draggingData.dragging = false ; +} ; + + + +Document.prototype.mouseDrag = function( data ) { + var starting = false ; + + if ( ! this.draggingData.dragging ) { + starting = true ; + this.mouseDragStart( data ) ; + } + + //console.error( "mouseDrag" , data ) ; + + this.draggingData.dx = data.x - this.draggingData.x ; + this.draggingData.dy = data.y - this.draggingData.y ; + this.draggingData.x = data.x ; + this.draggingData.y = data.y ; + + // Newest Gnome-Terminal send drag event even when no progress have been made, this check avoid useless computing. + if ( ! starting && ! this.draggingData.dx && ! this.draggingData.dy ) { return ; } + + // To send a 'drag' event, the origin of the drag should be on the same element + if ( this.draggingData.element ) { + this.draggingData.element.emit( + 'drag' , + { + xFrom: this.draggingData.xFrom + this.draggingData.localDx , + yFrom: this.draggingData.yFrom + this.draggingData.localDy , + x: data.x + this.draggingData.localDx , + y: data.y + this.draggingData.localDy , + dx: this.draggingData.dx , + dy: this.draggingData.dy + } , + this.draggingData.element + ) ; + } + + // Call .mouseMotion() but exclude the current dragged element + this.mouseMotion( data , this.draggingData.element ) ; +} ; + + + +Document.prototype.mouseWheel = function( data ) { + //var matches = this.childrenAt( data.x - this.outputX , data.y - this.outputY , COMMON_MOUSE_AWARE_FILTER ) ; + var matches = this.childrenAt( data.x - this.outputX , data.y - this.outputY , element => element.listenerCount( 'wheel' ) ) ; + if ( ! matches.length ) { return ; } + matches[ 0 ].element.emit( 'wheel' , { x: matches[ 0 ].x , y: matches[ 0 ].y , yDirection: data.yDirection } , matches[ 0 ].element ) ; +} ; + + + +Document.prototype.onEventSourceResize = function( width , height ) { + // Do not resize when on inline mode + if ( this.inlineTerm ) { return ; } + //console.error( "Document#onEventSourceResize() " , width , height ) ; + + // Always resize inputDst/viewport to match outputDst (Terminal) + this.resize( { + x: 0 , + y: 0 , + width: width , + height: height + } ) ; + + this.outputWidth = width ; + this.outputHeight = height ; + + //this.inputDst.clear() ; + //this.postDrawSelf() ; + + this.draw() ; +} ; + + +},{"./Container.js":19,"./Element.js":23,"seventh":108}],21:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +//const Element = require( './Element.js' ) ; +const RowMenu = require( './RowMenu.js' ) ; +const ColumnMenu = require( './ColumnMenu.js' ) ; + + + +function DropDownMenu( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + var i , iMax ; + + RowMenu.call( this , options ) ; + + this.initPage() ; + + this.columnMenu = null ; + this.columnButtonFocusAttr = options.buttonFocusAttr || { bgColor: 'blue' , color: 'white' , bold: true } ; + this.columnButtonBlurAttr = options.buttonBlurAttr || { bgColor: 'brightBlack' , color: 'white' , bold: true } ; + this.clearColumnMenuOnSubmit = !! options.clearColumnMenuOnSubmit ; + + this.onClickOut = this.onClickOut.bind( this ) ; + this.onButtonFocus = this.onButtonFocus.bind( this ) ; + this.onButtonSubmit = this.onButtonSubmit.bind( this ) ; + this.onColumnMenuSubmit = this.onColumnMenuSubmit.bind( this ) ; + //this.onColumnMenuFocus = this.onColumnMenuFocus.bind( this ) ; + + this.on( 'clickOut' , this.onClickOut ) ; + + for ( i = 0 , iMax = this.buttons.length ; i < iMax ; i ++ ) { + this.buttons[ i ].on( 'focus' , this.onButtonFocus ) ; + } + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'DropDownMenu' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = DropDownMenu ; + +DropDownMenu.prototype = Object.create( RowMenu.prototype ) ; +DropDownMenu.prototype.constructor = DropDownMenu ; +DropDownMenu.prototype.elementType = 'DropDownMenu' ; + + + +DropDownMenu.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'clickOut' , this.onClickOut ) ; + + RowMenu.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +DropDownMenu.prototype.keyBindings = { + LEFT: 'previous' , + RIGHT: 'next' , + ESCAPE: 'clearColumnMenu' , + UP: 'clearColumnMenu' , + DOWN: 'dropDown' , + ENTER: 'submit' , + KP_ENTER: 'submit' , + ALT_ENTER: 'submit' +} ; + + + +DropDownMenu.prototype.dropDown = function( index , x , y , submittedButtonValue , submittedButtonAction ) { + var itemsDef = this.itemsDef[ index ].items ; + + //console.error( "Submit!" , button.childId ) ; + + if ( this.columnMenu ) { + // Already dropped down? Nothing to do! + if ( this.columnMenu.index === index ) { return ; } + this.clearColumnMenu() ; + } + + // No submenu, leave now... + if ( ! itemsDef || ! itemsDef.length ) { + if ( submittedButtonValue && this.itemsDef[ index ].topSubmit ) { + // Top-button without submenu that have a 'topSubmit' flag on submits themselves + this.emit( 'submit' , submittedButtonValue , submittedButtonAction , this ) ; + } + + return ; + } + + // Make the ColumnMenu a child of the button, so focus cycle will work as expected + this.columnMenu = new ColumnMenu( { + internal: true , + parent: this.children[ index ] , + x: x , + y: y , + width: this.outputWidth - x , + leftPadding: ' ' , + rightPadding: ' ' , + items: itemsDef , + buttonFocusAttr: this.columnButtonFocusAttr , + buttonBlurAttr: this.columnButtonBlurAttr + } ) ; + + this.columnMenu.on( 'submit' , this.onColumnMenuSubmit ) ; + //this.columnMenu.on( 'focus' , this.onColumnMenuFocus ) ; + + // unused ATM + //this.columnMenu.menuIndex = index ; + + //this.document.giveFocusTo( this.columnMenu , 'delegate' ) ; +} ; + + + +DropDownMenu.prototype.clearColumnMenu = function() { + if ( ! this.columnMenu ) { return false ; } + this.columnMenu.destroy() ; + this.columnMenu = null ; + return true ; +} ; + + + +DropDownMenu.prototype.setDropDownItem = function( topItemValue , dropDownItemValue , itemOptions ) { + var topItem = this.itemsDef.find( e => e.value === topItemValue ) ; + if ( ! topItem ) { return false ; } + var dropDownItem = topItem.items && topItem.items.find( e => e.value === dropDownItemValue ) ; + if ( ! dropDownItem ) { return false ; } + this.clearColumnMenu() ; + Object.assign( dropDownItem , itemOptions ) ; + return true ; +} ; + + + +DropDownMenu.prototype.onClickOut = function( buttonValue , data , button ) { + this.clearColumnMenu() ; +} ; + + + +/* Does not work: focus is on column-menu children, we should have a way to make focus event bubbling up +DropDownMenu.prototype.onColumnMenuFocus = function( focus , type ) { + if ( ! focus && this.columnMenu ) { + this.clearColumnMenu() ; + } +} ; +*/ + + + +DropDownMenu.prototype.onButtonSubmit = function( buttonValue , action , button ) { + this.dropDown( button.childId , button.outputX , button.outputY + 1 , buttonValue , action ) ; +} ; + + + +DropDownMenu.prototype.onButtonFocus = function( focus , type , button ) { + if ( focus ) { this.dropDown( button.childId , button.outputX , button.outputY + 1 ) ; } +} ; + + + +DropDownMenu.prototype.onColumnMenuSubmit = function( buttonValue , action , button ) { + button.once( 'blinked' , ( buttonValue_ , reserved , button_ ) => { + if ( this.clearColumnMenuOnSubmit ) { this.clearColumnMenu() ; } + this.emit( 'blinked' , buttonValue_ , reserved , this ) ; + } ) ; + + this.emit( 'submit' , buttonValue , action , this ) ; +} ; + + + +DropDownMenu.prototype.onKey = function( key , trash , data ) { + switch( this.keyBindings[ key ] ) { + case 'previous' : + this.focusChild = this.focusPreviousChild() ; + //this.clearColumnMenu() ; + break ; + case 'next' : + this.focusChild = this.focusNextChild() ; + //this.clearColumnMenu() ; + break ; + case 'dropDown' : + if ( this.columnMenu ) { this.columnMenu.focusNextChild() ; } + //this.focusChild = this.focusNextChild() ; + //this.clearColumnMenu() ; + break ; + case 'clearColumnMenu' : + // Bubble up only if something was cleared + return this.clearColumnMenu() ; + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + +},{"./ColumnMenu.js":17,"./RowMenu.js":28}],22:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const TextBox = require( './TextBox.js' ) ; +const string = require( 'string-kit' ) ; + + + +function EditableTextBox( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( options.value ) { options.content = options.value ; } + + TextBox.call( this , options ) ; + + this.onKey = this.onKey.bind( this ) ; + this.onFocus = this.onFocus.bind( this ) ; + //this.onClick = this.onClick.bind( this ) ; + this.onMiddleClick = this.onMiddleClick.bind( this ) ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + + // Editable textbox get extraScrolling by default + this.extraScrolling = options.extraScrolling !== undefined ? !! options.extraScrolling : true ; + + this.updateStatus() ; + + this.on( 'key' , this.onKey ) ; + this.on( 'focus' , this.onFocus ) ; + //this.on( 'click' , this.onClick ) ; + this.on( 'middleClick' , this.onMiddleClick ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'EditableTextBox' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = EditableTextBox ; + +EditableTextBox.prototype = Object.create( TextBox.prototype ) ; +EditableTextBox.prototype.constructor = EditableTextBox ; +EditableTextBox.prototype.elementType = 'EditableTextBox' ; + +EditableTextBox.prototype.needInput = true ; + + + +EditableTextBox.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'focus' , this.onFocus ) ; + //this.off( 'click' , this.onClick ) ; + this.off( 'middleClick' , this.onMiddleClick ) ; + + TextBox.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +EditableTextBox.prototype.keyBindings = { + ENTER: 'newLine' , + KP_ENTER: 'newLine' , + BACKSPACE: 'backDelete' , + DELETE: 'delete' , + LEFT: 'backward' , + RIGHT: 'forward' , + CTRL_LEFT: 'startOfWord' , + CTRL_RIGHT: 'endOfWord' , + UP: 'up' , + DOWN: 'down' , + HOME: 'startOfLine' , + END: 'endOfLine' , + TAB: 'tab' , + PAGE_UP: 'scrollUp' , + PAGE_DOWN: 'scrollDown' , + CTRL_O: 'copyClipboard' , + CTRL_P: 'pasteClipboard' +} ; + + + +EditableTextBox.prototype.drawSelfCursor = function() { + this.textBuffer.drawCursor() ; +} ; + + + +EditableTextBox.prototype.getValue = TextBox.prototype.getContent ; + + + +EditableTextBox.prototype.setValue = function( value , dontDraw ) { + return TextBox.prototype.setContent.call( value , false , dontDraw ) ; +} ; + + + +EditableTextBox.prototype.onKey = function( key , trash , data ) { + var dy ; + + if ( data && data.isCharacter ) { + this.textBuffer.insert( key , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + } + else { + // Here we have a special key + + switch( this.keyBindings[ key ] ) { + case 'newLine' : + this.textBuffer.newLine() ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + break ; + + case 'backDelete' : + this.textBuffer.backDelete() ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + break ; + + case 'delete' : + this.textBuffer.delete() ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + break ; + + case 'backward' : + this.textBuffer.moveBackward() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'forward' : + this.textBuffer.moveForward() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'startOfWord' : + this.textBuffer.moveToStartOfWord() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'endOfWord' : + this.textBuffer.moveToEndOfWord() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'startOfLine' : + this.textBuffer.moveToColumn( 0 ) ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'endOfLine' : + this.textBuffer.moveToEndOfLine() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'down' : + this.textBuffer.moveDown() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'up' : + this.textBuffer.moveUp() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'left' : + this.textBuffer.moveLeft() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'right' : + this.textBuffer.moveRight() ; + this.autoScrollAndDrawCursor() ; + break ; + + case 'tab' : + this.textBuffer.insert( '\t' , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + break ; + + case 'scrollUp' : + dy = Math.ceil( this.outputHeight / 2 ) ; + this.textBuffer.move( 0 , -dy ) ; + this.scroll( 0 , dy ) ; + break ; + + case 'scrollDown' : + dy = -Math.ceil( this.outputHeight / 2 ) ; + this.textBuffer.move( 0 , -dy ) ; + this.scroll( 0 , dy ) ; + break ; + + case 'pasteClipboard' : + if ( this.document ) { + this.document.getClipboard().then( str => { + if ( str ) { + this.textBuffer.insert( str , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + } + } ) + .catch( () => undefined ) ; + } + break ; + + case 'copyClipboard' : + if ( this.document ) { + this.document.setClipboard( this.textBuffer.getSelectionText() ).catch( () => undefined ) ; + } + break ; + + default : + return ; // Bubble up + } + } + + return true ; // Do not bubble up +} ; + + + +EditableTextBox.prototype.onFocus = function( focus , type ) { + this.hasFocus = focus ; + this.updateStatus() ; + this.draw() ; +} ; + + + +EditableTextBox.prototype.onClick = function( data ) { + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + else { + this.textBuffer.moveTo( data.x - this.scrollX , data.y - this.scrollY ) ; + this.drawCursor() ; + } +} ; + + + +EditableTextBox.prototype.onMiddleClick = function( data ) { + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + + // Do not moveTo, it's quite boring + //this.textBuffer.moveTo( data.x , data.y ) ; + + if ( this.document ) { + this.document.getClipboard( 'primary' ).then( str => { + if ( str ) { + this.textBuffer.insert( str , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + this.autoScrollAndDraw() ; + } + //else { this.drawCursor() ; } + } ) + .catch( () => undefined ) ; + } + //else { this.drawCursor() ; } +} ; + + + +// There isn't much to do ATM +EditableTextBox.prototype.updateStatus = function() {} ; + + +},{"./TextBox.js":33,"string-kit":123}],23:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const misc = require( '../misc.js' ) ; +const string = require( 'string-kit' ) ; +const NextGenEvents = require( 'nextgen-events' ) ; + + + +function Element( options = {} ) { + this.setInterruptible( true ) ; + + this.parent = options.parent && options.parent.elementType ? options.parent : null ; + this.document = null ; + this.destroyed = false ; + this.inlineTerm = options.inlineTerm || null ; // inline mode, with this terminal as output + this.strictInline = !! ( + this.inlineTerm && this.strictInlineSupport + && ( options.strictInline || options.strictInline === undefined ) + ) ; + this.restoreCursorAfterDraw = !! ( this.inlineTerm && this.inlineCursorRestoreAfterDraw && ! this.strictInline ) ; + + this.outputDst = options.outputDst || ( options.parent && options.parent.inputDst ) , + this.inputDst = null ; + this.label = options.label || '' ; + this.key = options.key || null ; + + if ( this.value === undefined ) { + // Because it can be set already by the derivative class before calling Element (preprocessing of userland values) + this.value = options.value === undefined ? null : options.value ; + } + + this.childId = options.childId === undefined ? null : options.childId ; // An ID given to this element by its parent, often the index in its children array + this.def = options.def || null ; // internal usage, store the original def object that created the item, if any... + this.hidden = !! options.hidden ; // hidden: not visible and no interaction possible with this element, it also affects children + this.disabled = !! options.disabled ; // disabled: mostly for user-input, the element is often grayed and unselectable, effect depends on the element's type + + this.content = '' ; + this.contentHasMarkup = false ; + this.contentWidth = 0 ; + + if ( this.setContent === Element.prototype.setContent ) { + this.setContent( options.content || '' , options.contentHasMarkup , true , true ) ; + } + + this.meta = options.meta ; // associate data to the element for userland business logic + + this.autoWidth = + options.autoWidth || 0 ; + this.autoHeight = + options.autoHeight || 0 ; + this.outputX = options.outputX || options.x || 0 ; + this.outputY = options.outputY || options.y || 0 ; + this.savedZIndex = this.zIndex = options.zIndex || options.z || 0 ; + this.interceptTempZIndex = !! options.interceptTempZIndex ; // intercept child .topZ()/.bottomZ()/.restoreZ() + this.outputWidth = + this.autoWidth && this.outputDst ? Math.round( this.outputDst.width * this.autoWidth ) : + options.outputWidth ? options.outputWidth : + options.width ? options.width : + this.strictInline ? this.inlineTerm.width : + 1 ; + this.outputHeight = + this.autoHeight && this.outputDst ? Math.round( this.outputDst.height * this.autoHeight ) : + options.outputHeight ? options.outputHeight : + options.height ? options.height : + this.strictInline ? this.inlineTerm.height : + 1 ; + + // Used by .updateDraw() + this.needRedraw = false ; + + this.savedCursorX = 0 ; + this.savedCursorY = 0 ; + + this.hasFocus = false ; + this.children = [] ; + this.zChildren = [] ; // like children, but ordered by zIndex + //this.onKey = this.onKey.bind( this ) , writable: true } , + + // Children needs an inputDst, by default, everything is the same as for output (except for Container) + this.inputDst = this.outputDst ; + this.inputX = this.outputX ; + this.inputY = this.outputY ; + this.inputWidth = this.outputWidth ; + this.inputHeight = this.outputHeight ; + + if ( this.parent ) { this.parent.attach( this , options.id ) ; } + + if ( options.shortcuts && this.document ) { + if ( Array.isArray( options.shortcuts ) ) { this.document.createShortcuts( this , ... options.shortcuts ) ; } + else { this.document.createShortcuts( this , options.shortcuts ) ; } + } +} + +module.exports = Element ; + +Element.prototype = Object.create( NextGenEvents.prototype ) ; +Element.prototype.constructor = Element ; +Element.prototype.elementType = 'Element' ; + + + +// Destroy the element and all its children, detaching them and removing listeners +Element.prototype.destroy = function( isSubDestroy = false , noDraw = false ) { + if ( this.destroyed ) { return ; } + + var i , iMax , document = this.document ; + + // Destroy children first + for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) { + this.children[ i ].destroy( true ) ; + } + + this.children.length = 0 ; + this.zChildren.length = 0 ; + this.document.removeElementShortcuts( this ) ; + + if ( ! isSubDestroy ) { + this.detach( noDraw ) ; + if ( this.inlineTerm && document !== this ) { document.destroy() ; } + } + else { + delete this.document.elements[ this.id ] ; + this.id = null ; + this.parent = null ; + this.document = null ; + } + + this.destroyed = true ; +} ; + + + +Element.prototype.show = function( noDraw = false ) { + if ( ! this.hidden ) { return this ; } + this.hidden = false ; + if ( ! noDraw ) { this.redraw() ; } + return this ; +} ; + + + +Element.prototype.hide = function( noDraw = false ) { + if ( this.hidden ) { return this ; } + this.hidden = true ; + + if ( ! noDraw ) { + // .redraw() with the 'force' option on, because .redraw() does nothing if the element is hidden, but here we want to clear it from its parent + this.redraw( true ) ; + } + + return this ; +} ; + + + +// Clear the Element, destroy all children +Element.prototype.clear = function() { + var i , iMax ; + + // Destroy children first + for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) { + this.children[ i ].destroy( true ) ; + } + + this.children.length = 0 ; + this.zChildren.length = 0 ; + this.draw() ; +} ; + + + +Element.prototype.attach = function( child , id ) { + // Insert it if it is not already a child + if ( this.children.indexOf( child ) === -1 ) { + child.parent = this ; + this.children.push( child ) ; + this.zInsert( child ) ; + //this.zSort() ; + + //this.document.assignId( this , options.id ) ; + + // Re-assign the child's outputDst to this inputDst + child.outputDst = this.inputDst ; + if ( ! child.inputDst ) { child.inputDst = child.outputDst ; } + + if ( this.document !== child.document ) { + child.recursiveFixAttachment( this.document , id ) ; + } + } + + // /!\ Draw? /!\ + + return this ; +} ; + + + +Element.prototype.attachTo = function( parent , id ) { + if ( parent.elementType ) { parent.attach( this , id ) ; } + return this ; +} ; + + + +Element.prototype.recursiveFixAttachment = function( document , id = this.id ) { + var i , iMax ; + + // Can be null when in inline mode, or when detaching + if ( document ) { document.assignId( this , id ) ; } + else if ( this.document ) { this.document.unassignId( this , this.id ) ; } // force actual id here + else { this.id = null ; } + + this.document = document || null ; + + if ( this.parent ) { + // Re-assign the outputDst to the parent's inputDst + this.outputDst = this.parent.inputDst ; + if ( ! this.inputDst ) { this.inputDst = this.outputDst ; } + } + + for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) { + //console.error( ">>>" , i , iMax ) ; + this.children[ i ].recursiveFixAttachment( document ) ; + } +} ; + + + + +Element.prototype.detach = function( noDraw = false ) { + var index , parent = this.parent ; + + // Already detached + if ( ! parent ) { return ; } + + index = parent.children.indexOf( this ) ; + if ( index >= 0 ) { parent.children.splice( index , 1 ) ; } + + index = parent.zChildren.indexOf( this ) ; + if ( index >= 0 ) { parent.zChildren.splice( index , 1 ) ; } + + delete this.document.elements[ this.id ] ; + this.parent = null ; + this.recursiveFixAttachment( null ) ; + + // Redraw + if ( ! noDraw ) { + // /!\ Draw parent should work, but not always /!\ + //parent.draw() ; + parent.document.draw() ; + } + + return this ; +} ; + + + +// Sort zChildren, only necessary when a child zIndex changed +Element.prototype.zSort = function() { + this.zChildren.sort( ( a , b ) => a.zIndex - b.zIndex ) ; +} ; + + + +// Insert a child into the zChildren array, shift all greater zIndex to the left +// Used this instead of .push() and .zSort() +Element.prototype.zInsert = function( child ) { + var current , + i = this.zChildren.length ; + + while ( i -- ) { + current = this.zChildren[ i ] ; + if ( child.zIndex >= current.zIndex ) { + this.zChildren[ i + 1 ] = child ; + return ; + } + + this.zChildren[ i + 1 ] = current ; + } + + this.zChildren[ 0 ] = child ; +} ; + + + +// Change zIndex and call parent.zSort() immediately +Element.prototype.updateZ = Element.prototype.updateZIndex = function( z ) { + this.savedZIndex = this.zIndex = z ; + this.parent.zSort() ; +} ; + + + +// Change zIndex to make it on top of all siblings +Element.prototype.topZ = function() { + if ( this.parent.interceptTempZIndex ) { return this.parent.topZ() ; } + + if ( ! this.parent.zChildren.length ) { return ; } + this.zIndex = this.parent.zChildren[ this.parent.zChildren.length - 1 ].zIndex + 1 ; + this.parent.zSort() ; +} ; + + + +// Change zIndex to make it on bottom of all siblings +Element.prototype.bottomZ = function() { + if ( this.parent.interceptTempZIndex ) { return this.parent.bottomZ() ; } + + if ( ! this.parent.zChildren.length ) { return ; } + this.zIndex = this.parent.zChildren[ 0 ].zIndex - 1 ; + this.parent.zSort() ; +} ; + + + +Element.prototype.restoreZ = function() { + if ( this.parent.interceptTempZIndex ) { return this.parent.restoreZ() ; } + + this.zIndex = this.savedZIndex ; + this.parent.zSort() ; +} ; + + + +Element.computeContentWidth = ( content , hasMarkup ) => { + if ( Array.isArray( content ) ) { + return ( + hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ? Math.max( ... content.map( line => misc.ansiWidth( line ) ) ) : + hasMarkup ? Math.max( ... content.map( line => misc.markupWidth( line ) ) ) : + Math.max( ... content.map( line => string.unicode.width( line ) ) ) + ) ; + } + + return ( + hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ? misc.ansiWidth( content ) : + hasMarkup ? misc.markupWidth( content ) : + string.unicode.width( content ) + ) ; +} ; + +var lastTruncateWidth = 0 ; +Element.getLastTruncateWidth = () => lastTruncateWidth ; + +Element.truncateContent = ( content , maxWidth , hasMarkup ) => { + var str ; + + if ( hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ) { + str = misc.truncateAnsiString( content , maxWidth ) ; + lastTruncateWidth = misc.getLastTruncateWidth() ; + } + else if ( hasMarkup ) { + str = misc.truncateMarkupString( content , maxWidth ) ; + lastTruncateWidth = misc.getLastTruncateWidth() ; + } + else { + str = string.unicode.truncateWidth( content , maxWidth ) ; + lastTruncateWidth = string.unicode.getLastTruncateWidth() ; + } + + return str ; +} ; + +Element.wordwrapContent = // <-- DEPRECATED +Element.wordWrapContent = ( content , width , hasMarkup ) => + hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ? misc.wordWrapAnsi( content , width ) : + hasMarkup ? misc.wordWrapMarkup( content , width ) : + string.wordwrap( content , { width , fill: true , noJoin: true } ) ; + + + +Element.prototype.setContent = function( content , hasMarkup , dontDraw = false , dontResize = false ) { + if ( this.forceContentArray && ! Array.isArray( content ) ) { content = [ content || '' ] ; } + + this.content = content ; + this.contentHasMarkup = hasMarkup ; + this.contentWidth = Element.computeContentWidth( content , this.contentHasMarkup ) ; + + if ( ! dontResize && this.resizeOnContent ) { this.resizeOnContent() ; } + if ( ! dontDraw ) { this.redraw() ; } +} ; + + + +Element.prototype.isAncestorOf = function( element ) { + var currentElement = element ; + + for ( ;; ) { + if ( currentElement === this ) { + // Self found: ancestor match! + return true ; + } + else if ( ! currentElement.parent ) { + // The element is either detached or attached to another parent element + return false ; + } + else if ( currentElement.parent.children.indexOf( currentElement ) === -1 ) { + // Detached but still retain a ref to its parent. + // It's probably a bug, so we will remove that link now. + currentElement.parent = null ; + return false ; + } + + currentElement = currentElement.parent ; + } +} ; + + + +Element.prototype.getParentContainer = function() { + var currentElement = this ; + + for ( ;; ) { + if ( ! currentElement.parent ) { return null ; } + if ( currentElement.parent.isContainer ) { return currentElement.parent ; } + + currentElement = currentElement.parent ; + } +} ; + + + +// Internal: get the index of the direct child that have the focus or have a descendant having the focus +Element.prototype.getFocusBranchIndex = function() { + var index , currentElement ; + + if ( ! this.document.focusElement ) { return null ; } + + currentElement = this.document.focusElement ; + + for ( ;; ) { + if ( currentElement === this ) { + // Self found: ancestor match! + return null ; + } + else if ( ! currentElement.parent ) { + // The element is either detached or attached to another parent element + return null ; + } + + if ( currentElement.parent === this ) { + index = currentElement.parent.children.indexOf( currentElement ) ; + + if ( index === -1 ) { + // Detached but still retain a ref to its parent. + // It's probably a bug, so we will remove that link now. + currentElement.parent = null ; + return null ; + } + + return index ; + } + + currentElement = currentElement.parent ; + } +} ; + + + +Element.prototype.focusNextChild = function( loop = true ) { + var index , startingIndex , focusAware ; + + if ( ! this.children.length || ! this.document ) { return null ; } + + //if ( ! this.document.focusElement || ( index = this.children.indexOf( this.document.focusElement ) ) === -1 ) + if ( ! this.document.focusElement || ( index = this.getFocusBranchIndex() ) === null ) { + index = this.children.length - 1 ; + } + + startingIndex = index ; + + for ( ;; ) { + index ++ ; + if ( index >= this.children.length ) { + if ( loop ) { index = 0 ; } + else { index = this.children.length - 1 ; break ; } + } + + focusAware = this.document.giveFocusTo_( this.children[ index ] , 'cycle' ) ; + + // Exit if the focus was given to a focus-aware element or if we have done a full loop already + if ( focusAware || startingIndex === index ) { break ; } + } + + return this.children[ index ] ; +} ; + + + +Element.prototype.focusPreviousChild = function( loop = true ) { + var index , startingIndex , focusAware ; + + if ( ! this.children.length || ! this.document ) { return null ; } + + //if ( ! this.document.focusElement || ( index = this.children.indexOf( this.document.focusElement ) ) === -1 ) + if ( ! this.document.focusElement || ( index = this.getFocusBranchIndex() ) === null ) { + index = 0 ; + } + + startingIndex = index ; + + for ( ;; ) { + index -- ; + if ( index < 0 ) { + if ( loop ) { index = this.children.length - 1 ; } + else { index = 0 ; break ; } + } + + focusAware = this.document.giveFocusTo_( this.children[ index ] , 'backCycle' ) ; + + // Exit if the focus was given to a focus-aware element or if we have done a full loop already + if ( focusAware || startingIndex === index ) { break ; } + } + + return this.children[ index ] ; +} ; + + + +// Get all child element matching a x,y coordinate relative to the current element +Element.prototype.childrenAt = function( x , y , filter = null , matches = [] ) { + var i , current ; + + // Search children, order by descending zIndex, because we want the top element first + i = this.zChildren.length ; + while ( i -- ) { + current = this.zChildren[ i ] ; + + // Filter out hidden element now + if ( current.hidden ) { continue ; } + + if ( + x >= current.outputX && x <= current.outputX + current.outputWidth - 1 && + y >= current.outputY && y <= current.outputY + current.outputHeight - 1 + ) { + // Bounding box match! + + // Check and add children of children first (depth-first) + if ( current.isContainer ) { + current.childrenAt( x - current.inputX , y - current.inputY , filter , matches ) ; + } + else { + current.childrenAt( x , y , filter , matches ) ; + } + + if ( ! filter || filter( current ) ) { + matches.push( { element: current , x: x - current.outputX , y: y - current.outputY } ) ; + } + } + else if ( ! current.isContainer ) { + // If it is not a container, give a chance to its children to get selected + current.childrenAt( x , y , filter , matches ) ; + } + } + + return matches ; +} ; + + + +Element.prototype.saveCursor = function() { + if ( this.inputDst ) { + this.savedCursorX = this.inputDst.cx ; + this.savedCursorY = this.inputDst.cy ; + } + else if ( this.outputDst ) { + this.savedCursorX = this.outputDst.cx ; + this.savedCursorY = this.outputDst.cy ; + } + + return this ; +} ; + + + +Element.prototype.restoreCursor = function() { + if ( this.inputDst ) { + this.inputDst.cx = this.savedCursorX ; + this.inputDst.cy = this.savedCursorY ; + this.inputDst.drawCursor() ; + } + else if ( this.outputDst ) { + this.outputDst.cx = this.savedCursorX ; + this.outputDst.cy = this.savedCursorY ; + this.outputDst.drawCursor() ; + } + + return this ; +} ; + + + +Element.prototype.draw = function( isInitialInlineDraw = false ) { + if ( ! this.document || this.hidden ) { return this ; } + + if ( ! isInitialInlineDraw ) { + if ( this.restoreCursorAfterDraw ) { this.inlineTerm.saveCursor() ; } + else if ( ! this.strictInline ) { this.saveCursor() ; } + } + + this.descendantDraw() ; + this.ascendantDraw() ; + + if ( ! isInitialInlineDraw ) { + if ( this.restoreCursorAfterDraw ) { this.inlineTerm.restoreCursor() ; } + else if ( ! this.strictInline ) { this.drawCursor() ; } + } + + return this ; +} ; + + + +// .draw() is used when drawing the current Element is enough: the Element has not moved, and has not been resized. +// If it has, then it is necessary to draw the closest ancestor which is a container. +// /!\ THIS METHOD IS WRONG: it should draw the parent container, but don't redraw any children of its children Container +// /!\ Maybe rename this .outerDraw() or .parentDraw() +// Option 'force' redraw even if the element is hidden, in fact it is used by the .hide() method to effectively hide the element on the parent container. +Element.prototype.redraw = function( force = false ) { + if ( ! this.document || ( this.hidden && ! force ) ) { return this ; } + + var container = this.getParentContainer() ; + + //console.error( "parentContainer:" , container ) ; + if ( ! container ) { this.draw() ; } + else { container.draw() ; } + + return this ; +} ; + + + +// Hard to find a good name, .draw() or .redraw() depending on what have been updated +Element.prototype.updateDraw = function() { + if ( this.needRedraw ) { this.redraw() ; } + else { this.draw() ; } + this.needRedraw = false ; +} ; + + + +// Draw all the children +Element.prototype.descendantDraw = function( isSubcall ) { + var i , iMax ; + + if ( this.hidden ) { return this ; } + + if ( this.preDrawSelf ) { + //console.error( 'preDrawSelf: ' , this.elementType , this.id ) ; + this.preDrawSelf( ! isSubcall ) ; + } + + // Draw children, order by ascending zIndex + for ( i = 0 , iMax = this.zChildren.length ; i < iMax ; i ++ ) { + this.zChildren[ i ].descendantDraw( true ) ; + } + + if ( isSubcall && this.postDrawSelf ) { + //console.error( 'postDrawSelf: ' , this.elementType , this.id ) ; + this.postDrawSelf( ! isSubcall ) ; + } + + return this ; +} ; + + + +// Post-draw from the current element through all the ancestor chain +Element.prototype.ascendantDraw = function() { + //console.error( '\nascendantDraw: ' , this.elementType , this.id ) ; + var currentElement ; + + if ( this.postDrawSelf && ! this.hidden ) { + //console.error( 'postDrawSelf: ' , this.elementType , this.id ) ; + this.postDrawSelf( true ) ; + } + + currentElement = this ; + + while ( currentElement.parent && currentElement.outputDst !== currentElement.document.outputDst ) { + currentElement = currentElement.parent ; + + if ( currentElement.outputDst !== currentElement.inputDst && currentElement.postDrawSelf && ! currentElement.hidden ) { + //console.error( 'postDrawSelf: ' , currentElement.elementType , currentElement.id ) ; + currentElement.postDrawSelf( false ) ; + } + } + + return this ; +} ; + + + +// Draw cursor from the current element through all the ancestor chain +Element.prototype.drawCursor = function() { + var currentElement ; + + if ( this.drawSelfCursor && ! this.hidden ) { + this.drawSelfCursor( true ) ; + } + + currentElement = this ; + + while ( currentElement.outputDst !== currentElement.document.outputDst && currentElement.parent ) { + currentElement = currentElement.parent ; + + if ( currentElement.drawSelfCursor && ! currentElement.hidden ) { + currentElement.drawSelfCursor( false ) ; + } + } + + return this ; +} ; + + + +// For inline widget, having eventually a document just for him, that fit its own size +Element.createInline = async function( term , Type , options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + options.inlineTerm = term ; + //options.outputDst = term ; + //options.eventSource = term ; + + var cursorPosition , + position = { + x: options.outputX || options.x , + y: options.outputY || options.y + } ; + + // Don't use 'delete', because options = Object.create( options ) -- doesn't work with inheritance + options.x = options.y = options.outputX = options.outputY = 0 ; + + var element = new Type( options ) ; + + if ( position.x === undefined || position.y === undefined ) { + if ( element.strictInline ) { + // We do not want any asyncness for pure inline elements, and since we draw in inline mode, we don't care about it... + // ... BUT we need a position anyway for the clipping purpose! It can't be 0 since we draw on the terminal and top-left is (1,1). + position.x = position.y = 1 ; + } + else { + cursorPosition = await term.getCursorLocation() ; + + if ( position.x === undefined ) { + position.x = cursorPosition.x ; + + if ( cursorPosition.x > 1 && element.inlineNewLine ) { + position.x = 1 ; + if ( position.y === undefined ) { position.y = cursorPosition.y + 1 ; } + } + } + + if ( position.y === undefined ) { position.y = cursorPosition.y ; } + } + } + + if ( ! element.strictInline ) { + let scrollY = position.y + element.outputHeight - term.height ; + //console.error( "INLINE -- element.outputWidth" , element.outputWidth ) ; + //console.error( "INLINE -- element.outputHeight" , element.outputHeight ) ; + //console.error( "INLINE -- element.outputY" , element.outputY ) ; + //console.error( "INLINE -- scrollY" , scrollY ) ; + + if ( scrollY > 0 ) { + term.scrollUp( scrollY ) ; + term.up( scrollY ) ; // move the cursor up, so save/restore cursor could work + position.y -= scrollY ; + } + } + + var documentOptions = { + internal: true , + inlineTerm: term , + strictInline: element.strictInline , + noInput: element.strictInline || ! element.needInput , + outputX: position.x , + outputY: position.y , + outputWidth: element.outputWidth , + outputHeight: element.outputHeight , + outputDst: term , + eventSource: term , + noDraw: true + } ; + + var document = new Document( documentOptions ) ; + + document.attach( element ) ; + + // Should probably resize the container + element.on( 'resize' , () => { throw new Error( 'not coded!' ) ; } ) ; + + element.draw( true ) ; + term.styleReset() ; + + if ( element.staticInline ) { element.destroy( undefined , true ) ; } + + return element ; +} ; + + + +// Should be redefined +Element.prototype.isContainer = false ; // boolean, true if it's a container, having a different inputDst and outputDst and local coords +Element.prototype.forceContentArray = false ; // boolean, true if content should be an array of string instead of a string +Element.prototype.noChildFocus = false ; // boolean, true if the focus should not be transmitted to children of this Element +Element.prototype.computeBoundingBoxes = null ; // function, bounding boxes for elements that can be drawn +Element.prototype.resizeOnContent = null ; // function, if set, resize on content update, called by .setContent() +Element.prototype.preDrawSelf = null ; // function, things to draw for the element before drawing its children +Element.prototype.postDrawSelf = null ; // function, things to draw for the element after drawing its children +Element.prototype.drawSelfCursor = null ; // function, draw the element cursor +Element.prototype.getValue = () => null ; // function, get the value of the element if any... +Element.prototype.setValue = () => undefined ; // function, set the value of the element if any... +Element.prototype.strictInlineSupport = false ; // no support for strictInline mode by default +Element.prototype.staticInline = false ; // boolean, true if the inline version is static and could be destroyed immediately after been drawn +Element.prototype.inlineCursorRestoreAfterDraw = false ; // when set, save/restore cursor in inline mode (forced when strictInline is true) +Element.prototype.needInput = false ; // no need for input by default (used to configure inline mode) + +const Document = require( './Document.js' ) ; + + +},{"../misc.js":42,"./Document.js":20,"nextgen-events":72,"string-kit":123}],24:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const LabeledInput = require( './LabeledInput.js' ) ; +const Button = require( './Button.js' ) ; + + + +function Form( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( ! options.outputWidth && ! options.width ) { options.outputWidth = 78 ; } + + Element.call( this , options ) ; + + this.submitValue = null ; + + this.inputsDef = options.inputs || [] ; + this.labeledInputs = [] ; + this.buttonsDef = options.buttons || [] ; + this.buttons = [] ; + this.focusChild = null ; + this.onButtonSubmit = this.onButtonSubmit.bind( this ) ; + this.onKey = this.onKey.bind( this ) ; + this.onFocus = this.onFocus.bind( this ) ; + + // Global default attributes + this.textAttr = options.textAttr || null ; + this.voidAttr = options.voidAttr || options.emptyAttr || null ; + this.labelFocusAttr = options.labelFocusAttr || null ; + this.labelBlurAttr = options.labelBlurAttr || null ; + this.buttonFocusAttr = options.buttonFocusAttr || null ; + this.buttonBlurAttr = options.buttonBlurAttr || null ; + this.turnedOnBlurAttr = options.turnedOnBlurAttr || null ; + this.turnedOnFocusAttr = options.turnedOnFocusAttr || null ; + this.turnedOffBlurAttr = options.turnedOffBlurAttr || null ; + this.turnedOffFocusAttr = options.turnedOffFocusAttr || null ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + if ( options.textInputKeyBindings ) { this.textInputKeyBindings = options.textInputKeyBindings ; } + + this.initChildren() ; + + this.on( 'key' , this.onKey ) ; + this.on( 'focus' , this.onFocus ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Form' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Form ; + +Form.prototype = Object.create( Element.prototype ) ; +Form.prototype.constructor = Form ; +Form.prototype.elementType = 'Form' ; + +Form.prototype.needInput = true ; + + + +Form.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'focus' , this.onFocus ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +Form.prototype.keyBindings = { + LEFT: 'previous' , + RIGHT: 'next' , + UP: 'previous' , + DOWN: 'next' , + ENTER: 'next' , + KP_ENTER: 'next' , + ALT_ENTER: 'next' +} ; + + + +Form.prototype.textInputKeyBindings = {} ; +Form.prototype.selectInputKeyBindings = {} ; +Form.prototype.selectMultiInputKeyBindings = {} ; + + + +// Create LabeledInput and Button automatically +Form.prototype.initChildren = function() { + var labelMaxWidth = 0 , + offsetX = 0 , offsetY = 0 , + buttonsTextWidth = 0 , buttonSpacing = 0 ; + + this.inputsDef.forEach( def => { + def.labelWidth = Element.computeContentWidth( def.label , def.labelHasMarkup ) ; + if ( def.labelWidth > labelMaxWidth ) { labelMaxWidth = def.labelWidth ; } + } ) ; + + this.inputsDef.forEach( ( def , index ) => { + var height = 1 , + label = def.label + ' '.repeat( labelMaxWidth - def.labelWidth ) ; + + switch ( def.type ) { + case 'select' : + //def.type = 'select' ; + //if ( def.height ) { height = 1 ; } + + this.labeledInputs[ index ] = new LabeledInput( { + internal: true , + parent: this , + type: def.type , + key: def.key , + label: label , + content: def.content , + value: def.value , + items: def.items , + outputX: this.outputX , + outputY: this.outputY + offsetY , + outputWidth: def.outputWidth || def.width || this.outputWidth , + outputHeight: height , + labelFocusAttr: def.labelFocusAttr || this.labelFocusAttr , + labelBlurAttr: def.labelBlurAttr || this.labelBlurAttr , + buttonBlurAttr: def.buttonBlurAttr || this.buttonBlurAttr , + buttonFocusAttr: def.buttonFocusAttr || this.buttonFocusAttr , + buttonDisabledAttr: def.buttonDisabledAttr || this.buttonDisabledAttr , + buttonSubmittedAttr: def.buttonSubmittedAttr || this.buttonSubmittedAttr , + keyBindings: this.selectInputKeyBindings , + noDraw: true + } ) ; + + break ; + + case 'select-multi' : + case 'selectMulti' : + //def.type = 'select' ; + //if ( def.height ) { height = 1 ; } + + this.labeledInputs[ index ] = new LabeledInput( { + internal: true , + parent: this , + type: def.type , + key: def.key , + label: label , + content: def.content , + value: def.value , + items: def.items , + outputX: this.outputX , + outputY: this.outputY + offsetY , + outputWidth: def.outputWidth || def.width || this.outputWidth , + outputHeight: height , + labelFocusAttr: def.labelFocusAttr || this.labelFocusAttr , + labelBlurAttr: def.labelBlurAttr || this.labelBlurAttr , + buttonBlurAttr: def.buttonBlurAttr || this.buttonBlurAttr , + buttonFocusAttr: def.buttonFocusAttr || this.buttonFocusAttr , + buttonDisabledAttr: def.buttonDisabledAttr || this.buttonDisabledAttr , + buttonSubmittedAttr: def.buttonSubmittedAttr || this.buttonSubmittedAttr , + turnedOnBlurAttr: def.turnedOnBlurAttr || this.turnedOnBlurAttr , + turnedOnFocusAttr: def.turnedOnFocusAttr || this.turnedOnFocusAttr , + turnedOffBlurAttr: def.turnedOffBlurAttr || this.turnedOffBlurAttr , + turnedOffFocusAttr: def.turnedOffFocusAttr || this.turnedOffFocusAttr , + keyBindings: this.selectInputKeyBindings , + noDraw: true + } ) ; + + break ; + + case 'text' : + default : + def.type = 'text' ; + if ( def.height ) { height = def.height ; } + + this.labeledInputs[ index ] = new LabeledInput( { + internal: true , + parent: this , + type: def.type , + key: def.key , + label: label , + content: def.content , + outputX: this.outputX , + outputY: this.outputY + offsetY , + outputWidth: def.outputWidth || def.width || this.outputWidth , + outputHeight: height , + lineWrap: !! def.lineWrap , + wordWrap: !! def.wordWrap , + scrollable: !! def.scrollable , + vScrollBar: !! def.vScrollBar , + hScrollBar: !! def.hScrollBar , + hiddenContent: def.hiddenContent , + labelFocusAttr: def.labelFocusAttr || this.labelFocusAttr , + labelBlurAttr: def.labelBlurAttr || this.labelBlurAttr , + textAttr: def.textAttr || this.textAttr , + voidAttr: def.voidAttr || def.emptyAttr || this.voidAttr , + keyBindings: this.textInputKeyBindings , + allowNewLine: height > 1 , + noDraw: true + } ) ; + + break ; + } + + offsetY += height ; + } ) ; + + + // Submit Button part + if ( ! this.buttonsDef.length ) { + this.buttonsDef.push( { + content: 'Submit' , + value: 'submit' + } ) ; + } + + this.buttonsDef.forEach( def => { + def.contentWidth = Element.computeContentWidth( def.content , def.contentHasMarkup ) ; + buttonsTextWidth += def.contentWidth ; + } ) ; + + buttonSpacing = Math.floor( ( this.outputWidth - buttonsTextWidth ) / ( this.buttonsDef.length + 1 ) ) ; + + offsetX = buttonSpacing ; + offsetY ++ ; + + this.buttonsDef.forEach( ( def , index ) => { + this.buttons[ index ] = new Button( { + internal: true , + parent: this , + content: def.content , + value: def.value , + outputX: this.outputX + offsetX , + outputY: this.outputY + offsetY , + focusAttr: def.focusAttr || this.buttonFocusAttr , + blurAttr: def.blurAttr || this.buttonBlurAttr , + noDraw: true + } ) ; + + this.buttons[ index ].on( 'submit' , this.onButtonSubmit ) ; + + offsetX += def.contentWidth + buttonSpacing ; + } ) ; +} ; + + + +Form.prototype.getValue = function() { + var fields = {} ; + + this.labeledInputs.forEach( labeledInput => { + fields[ labeledInput.key ] = labeledInput.getValue() ; + } ) ; + + return { submit: this.submitValue , fields } ; +} ; + + + +Form.prototype.onKey = function( key , altKeys , data ) { + switch( this.keyBindings[ key ] ) { + case 'previous' : + this.focusChild = this.focusPreviousChild() ; + break ; + case 'next' : + this.focusChild = this.focusNextChild() ; + break ; + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +Form.prototype.onFocus = function( focus , type ) { + if ( type === 'cycle' || type === 'backCycle' ) { return ; } + + if ( focus ) { + // Defer to the next tick to avoid recursive events producing wrong listener order + process.nextTick( () => { + if ( this.focusChild ) { this.document.giveFocusTo( this.focusChild , 'delegate' ) ; } + else { this.focusChild = this.focusNextChild() ; } + } ) ; + } +} ; + + + +Form.prototype.onButtonSubmit = function( buttonValue , action ) { + this.submitValue = buttonValue ; + this.emit( 'submit' , this.getValue() , action , this ) ; +} ; + + +}).call(this)}).call(this,require('_process')) +},{"./Button.js":16,"./Element.js":23,"./LabeledInput.js":26,"_process":179}],25:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Promise = require( 'seventh' ) ; +const TextBox = require( './TextBox.js' ) ; +const EditableTextBox = require( './EditableTextBox.js' ) ; +const RowMenu = require( './RowMenu.js' ) ; +const string = require( 'string-kit' ) ; +const computeAutoCompleteArray = require( '../autoComplete.js' ) ; + + + +/* + This is the Document-model version of .inputField(). + Like an EditableTextBox, with a one-line hard-line-wrap TextBuffer, outputHeight start at 1 but can grow + as more input is entered by the user, can auto-complete with or without menu, have history, and so on... +*/ + +/* + Check-list of things that .inputField() has and InlineInput still don't: + * Inline mode, capable of adding a new line at the end of the screen when it is needed + * editing actions: deleteAllBefore, deleteAllAfter, deletePreviousWord, deleteNextWord + * meta key for more keyboard commands, e.g.: maybe something like CTRL_K --> META_ + * allow placeholder to be used as default (submitting without actually entering anything) when appropriate + * disable echoing (no output and no cursor movements) + * setting the cursor "offset" position beforehand + * min/max length + * Maybe (very low priority): support for the .inputField()'s token feature (tokenHook, tokenResetHook, tokenRegExp). +*/ + +function InlineInput( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( options.value ) { options.content = options.value ; } + + // It is always 1 at the begining + options.outputHeight = 1 ; + + // No scrolling + options.scrollable = options.hasVScrollBar = options.hasHScrollBar = options.extraScrolling = false ; + options.scrollX = options.scrollY = 0 ; + + // It always have line-wrapping on + options.lineWrap = true ; + + this.onAutoCompleteMenuSubmit = this.onAutoCompleteMenuSubmit.bind( this ) ; + this.onAutoCompleteMenuCancel = this.onAutoCompleteMenuCancel.bind( this ) ; + + this.promptTextBox = null ; + + if ( options.prompt ) { + this.promptTextBox = new TextBox( Object.assign( + { + textAttr: options.textAttr + } , + options.prompt , + { + internal: true , + //parent: this , + outputX: options.outputX || options.x , + outputY: options.outputY || options.y , + outputWidth: options.outputWidth || options.width , + outputHeight: options.outputHeight || options.height , + lineWrap: options.lineWrap , + wordWrap: options.wordWrap || options.wordwrap + } + ) ) ; + + // Drop void cells + this.promptTextBox.textBuffer.setVoidAttr( null ) ; + + let size = this.promptTextBox.getContentSize() ; + this.promptTextBox.setSizeAndPosition( size ) ; + + if ( size.height > 1 ) { + options.outputY = ( options.outputY || options.y ) + size.height - 1 ; + options.firstLineRightShift = this.promptTextBox.textBuffer.buffer[ this.promptTextBox.textBuffer.buffer.length - 1 ].length ; + } + else { + options.firstLineRightShift = size.width ; + } + } + + EditableTextBox.call( this , options ) ; + + + this.history = options.history ; + this.contentArray = options.history ? [ ... options.history , this.content ] : [ this.content ] ; + this.contentIndex = this.contentArray.length - 1 ; + + this.disabled = !! options.disabled ; + this.submitted = !! options.submitted ; + this.cancelable = !! options.cancelable ; + this.canceled = !! options.canceled ; + + this.autoComplete = options.autoComplete ; + this.useAutoCompleteHint = !! ( this.autoComplete && ( options.useAutoCompleteHint || options.autoCompleteHint ) ) ; + this.useAutoCompleteMenu = !! ( this.autoComplete && ( options.useAutoCompleteMenu || options.autoCompleteMenu ) ) ; + this.autoCompleteMenu = null ; + this.autoCompleteLeftPart = null ; + this.autoCompleteRightPart = null ; + + this.menuOptions = Object.assign( {} , this.defaultMenuOptions , options.menu ) ; + + this.placeholder = options.placeholder ; + this.placeholderHasMarkup = options.placeholderHasMarkup ; + + if ( this.placeholder ) { + this.setAltContent( this.placeholder , this.placeholderHasMarkup ) ; + } + + if ( this.promptTextBox ) { + this.attach( this.promptTextBox ) ; + } + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'InlineInput' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = InlineInput ; + +InlineInput.prototype = Object.create( EditableTextBox.prototype ) ; +InlineInput.prototype.constructor = InlineInput ; +InlineInput.prototype.elementType = 'InlineInput' ; + +// Has a fallback textBuffer for hint/placeholder +InlineInput.prototype.useAltTextBuffer = true ; + + + +InlineInput.prototype.defaultMenuOptions = { + buttonBlurAttr: { bgColor: 'default' , color: 'default' } , + buttonFocusAttr: { bgColor: 'green' , color: 'blue' , dim: true } , + buttonDisabledAttr: { bgColor: 'white' , color: 'brightBlack' } , + buttonSubmittedAttr: { bgColor: 'brightWhite' , color: 'brightBlack' } , + buttonSeparatorAttr: { bgColor: 'default' } , + backgroundAttr: { bgColor: 'default' } , + //leftPadding: ' ' , rightPadding: ' ' , + justify: true , + keyBindings: Object.assign( {} , RowMenu.prototype.keyBindings , { + TAB: 'next' , + SHIFT_TAB: 'previous' + } ) +} ; + + + +InlineInput.prototype.keyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + ESCAPE: 'cancel' , + TAB: 'autoComplete' , + CTRL_R: 'historyAutoComplete' , + UP: 'historyPrevious' , + DOWN: 'historyNext' , + BACKSPACE: 'backDelete' , + DELETE: 'delete' , + LEFT: 'backward' , + RIGHT: 'forward' , + CTRL_LEFT: 'startOfWord' , + CTRL_RIGHT: 'endOfWord' , + HOME: 'startOfLine' , + END: 'endOfLine' , + CTRL_O: 'copyClipboard' , + CTRL_P: 'pasteClipboard' +} ; + + + +InlineInput.prototype.preDrawSelf = function() { + /* + if ( this.promptTextBuffer ) { + // It's best to force the dst now, because it avoids to set textBuffer.dst everytime it changes, + // and it could be changed by userland (so hard to keep it in sync without setters/getters) + this.promptTextBuffer.draw( { dst: this.outputDst } ) ; + } + //*/ + + EditableTextBox.prototype.preDrawSelf.call( this ) ; +} ; + + + +InlineInput.prototype.autoResizeAndDraw = function( onlyDrawCursor = false ) { + var height = Math.max( this.textBuffer.buffer.length , ( this.altTextBuffer && this.altTextBuffer.buffer.length ) || 0 ) ; + + if ( height > this.outputHeight ) { + this.setSizeAndPosition( { outputHeight: height } ) ; + } + + if ( ! onlyDrawCursor ) { + this.draw() ; + } + else { + this.drawCursor() ; + } +} ; + + + +InlineInput.prototype.autoResizeAndDrawCursor = function() { + return this.autoResizeAndDraw( true ) ; +} ; + + + +InlineInput.prototype.runAutoCompleteHint = async function( autoComplete ) { + //console.error( "bob, please") + var autoCompleted ; + + var [ leftPart , rightPart ] = this.textBuffer.getCursorSplittedText() ; + + if ( rightPart ) { + this.altTextBuffer.setText( '' ) ; + } + else { + if ( Array.isArray( autoComplete ) ) { + autoCompleted = computeAutoCompleteArray( autoComplete , leftPart , false ) ; + } + else if ( typeof autoComplete === 'function' ) { + autoCompleted = await autoComplete( leftPart , false ) ; + } + else { + return ; + } + + if ( Array.isArray( autoCompleted ) ) { + if ( ! autoCompleted.length ) { return ; } + autoCompleted = autoCompleted[ 0 ] ; + } + + if ( autoCompleted === leftPart ) { + this.altTextBuffer.setText( '' ) ; + } + else { + this.altTextBuffer.setText( autoCompleted ) ; + //this.altTextBuffer.runStateMachine() ; + } + } + + this.autoResizeAndDraw() ; +} ; + + + +InlineInput.prototype.runAutoComplete = async function( autoComplete ) { + var autoCompleted ; + + [ this.autoCompleteLeftPart , this.autoCompleteRightPart ] = this.textBuffer.getCursorSplittedText() ; + + if ( Array.isArray( autoComplete ) ) { + autoCompleted = computeAutoCompleteArray( autoComplete , this.autoCompleteLeftPart , this.useAutoCompleteMenu ) ; + } + else if ( typeof autoComplete === 'function' ) { + autoCompleted = await autoComplete( this.autoCompleteLeftPart , this.useAutoCompleteMenu ) ; + } + else { + return ; + } + + if ( Array.isArray( autoCompleted ) ) { + if ( ! autoCompleted.length ) { return ; } + + if ( this.useAutoCompleteMenu ) { + this.runAutoCompleteMenu( autoCompleted ) ; + return ; + } + + autoCompleted = autoCompleted[ 0 ] ; + } + + this.runAutoCompleted( autoCompleted ) ; +} ; + + + +InlineInput.prototype.runAutoCompleted = async function( autoCompleted ) { + this.textBuffer.setText( autoCompleted + this.autoCompleteRightPart ) ; + this.textBuffer.setCursorOffset( autoCompleted.length ) ; + this.textBuffer.runStateMachine() ; + this.autoResizeAndDraw() ; +} ; + + + +InlineInput.prototype.runAutoCompleteMenu = async function( items ) { + // No items, leave now... + if ( ! items || ! items.length ) { return ; } + + if ( this.autoCompleteMenu ) { + // Should never happen, but just in case... + this.autoCompleteMenu.destroy() ; + } + + // Make the ColumnMenu a child of the button, so focus cycle will work as expected + this.autoCompleteMenu = new RowMenu( Object.assign( {} , this.menuOptions , { + internal: true , + parent: this , + x: this.outputX , + y: this.outputY + this.outputHeight , + outputWidth: this.outputWidth , + items: items.map( item => ( { value: item , content: item } ) ) + } ) ) ; + + this.document.giveFocusTo( this.autoCompleteMenu ) ; + + this.autoCompleteMenu.once( 'submit' , this.onAutoCompleteMenuSubmit ) ; + this.autoCompleteMenu.once( 'cancel' , this.onAutoCompleteMenuCancel ) ; +} ; + + + +InlineInput.prototype.onAutoCompleteMenuSubmit = function( selectedText ) { + this.autoCompleteMenu.destroy() ; + this.autoCompleteMenu = null ; + this.document.giveFocusTo( this ) ; + this.runAutoCompleted( selectedText ) ; +} ; + + + +InlineInput.prototype.onAutoCompleteMenuCancel = function() { + this.autoCompleteMenu.destroy() ; + this.autoCompleteMenu = null ; + this.document.giveFocusTo( this ) ; +} ; + + + +InlineInput.prototype.onKey = function( key , trash , data ) { + if ( this.autoCompleteMenu ) { + // If the autoCompleteMenu is on, force a cancel + this.autoCompleteMenu.emit( 'cancel' ) ; + } + + if ( data && data.isCharacter ) { + if ( this.placeholder ) { + // Remove the placeholder on the first input + this.placeholder = null ; + this.setAltContent( '' , false , true ) ; + } + + this.textBuffer.insert( key , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + + if ( this.useAutoCompleteHint ) { this.runAutoCompleteHint( this.autoComplete ) ; } + else { this.autoResizeAndDraw() ; } + } + else { + // Here we have a special key + + switch( this.keyBindings[ key ] ) { + case 'submit' : + if ( this.disabled || this.submitted || this.canceled ) { break ; } + //this.submitted = true ; + this.emit( 'submit' , this.getValue() , undefined , this ) ; + break ; + + case 'cancel' : + if ( ! this.cancelable || this.disabled || this.canceled ) { break ; } + //this.canceled = true ; + this.emit( 'cancel' , this ) ; + break ; + + case 'autoComplete' : + if ( ! this.autoComplete ) { break ; } + this.runAutoComplete( this.autoComplete ) ; + break ; + + case 'historyAutoComplete' : + if ( ! this.autoComplete ) { break ; } + this.runAutoComplete( this.history ) ; + break ; + + case 'historyPrevious' : + if ( this.contentIndex <= 0 ) { break ; } + this.contentArray[ this.contentIndex ] = this.getContent() ; + this.contentIndex -- ; + this.setContent( this.contentArray[ this.contentIndex ] ) ; + this.textBuffer.runStateMachine() ; + this.autoResizeAndDraw() ; + break ; + + case 'historyNext' : + if ( this.contentIndex >= this.contentArray.length - 1 ) { break ; } + this.contentArray[ this.contentIndex ] = this.getContent() ; + this.contentIndex ++ ; + this.setContent( this.contentArray[ this.contentIndex ] ) ; + this.textBuffer.runStateMachine() ; + this.autoResizeAndDraw() ; + break ; + + case 'backDelete' : + this.textBuffer.backDelete() ; + this.textBuffer.runStateMachine() ; + if ( this.useAutoCompleteHint ) { this.runAutoCompleteHint( this.autoComplete ) ; } + else { this.autoResizeAndDraw() ; } + break ; + + case 'delete' : + this.textBuffer.delete() ; + this.textBuffer.runStateMachine() ; + if ( this.useAutoCompleteHint ) { this.runAutoCompleteHint( this.autoComplete ) ; } + else { this.autoResizeAndDraw() ; } + break ; + + case 'backward' : + this.textBuffer.moveBackward() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'forward' : + this.textBuffer.moveForward() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'startOfWord' : + this.textBuffer.moveToStartOfWord() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'endOfWord' : + this.textBuffer.moveToEndOfWord() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'startOfLine' : + this.textBuffer.moveToColumn( 0 ) ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'endOfLine' : + this.textBuffer.moveToEndOfLine() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'left' : + this.textBuffer.moveLeft() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'right' : + this.textBuffer.moveRight() ; + this.autoResizeAndDrawCursor() ; + break ; + + case 'pasteClipboard' : + if ( this.document ) { + this.document.getClipboard().then( str => { + if ( str ) { + this.textBuffer.insert( str , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + if ( this.useAutoCompleteHint ) { this.runAutoCompleteHint( this.autoComplete ) ; } + else { this.autoResizeAndDraw() ; } + } + } ) + .catch( () => undefined ) ; + } + break ; + + case 'copyClipboard' : + if ( this.document ) { + this.document.setClipboard( this.textBuffer.getSelectionText() ).catch( () => undefined ) ; + } + break ; + + default : + return ; // Bubble up + } + } + + return true ; // Do not bubble up +} ; + + + +/* +InlineInput.prototype.onFocus = function( focus , type ) { + this.hasFocus = focus ; + this.updateStatus() ; + this.draw() ; +} ; +*/ + + +/* +InlineInput.prototype.onClick = function( data ) { + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + else { + this.textBuffer.moveTo( data.x - this.scrollX , data.y - this.scrollY ) ; + this.drawCursor() ; + } +} ; +*/ + +/* +InlineInput.prototype.onMiddleClick = function( data ) { + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + + // Do not moveTo, it's quite boring + //this.textBuffer.moveTo( data.x , data.y ) ; + + if ( this.document ) { + this.document.getClipboard( 'primary' ).then( str => { + if ( str ) { + this.textBuffer.insert( str , this.textAttr ) ; + this.textBuffer.runStateMachine() ; + this.autoResizeAndDraw() ; + } + //else { this.drawCursor() ; } + } ) + .catch( () => undefined ) ; + } + //else { this.drawCursor() ; } +} ; +*/ + + +// There isn't much to do ATM +//InlineInput.prototype.updateStatus = function() {} ; + + +},{"../autoComplete.js":7,"./EditableTextBox.js":22,"./RowMenu.js":28,"./TextBox.js":33,"seventh":108,"string-kit":123}],26:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Text = require( './Text.js' ) ; +const EditableTextBox = require( './EditableTextBox.js' ) ; +const SelectList = require( './SelectList.js' ) ; +const SelectListMulti = require( './SelectListMulti.js' ) ; + +const string = require( 'string-kit' ) ; +//const autoComplete = require( './autoComplete.js' ) ; + + +// Labeled: american english, Labelled british english +// (to me, 'labelled' seems more natural, but there are 10 times more results on Google for 'labeled', so I will go for it) +function LabeledInput( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + // For text-input only + this.hiddenContent = options.hiddenContent ; + this.hasInputFocus = false ; + + // For SelectList, this apply temp zIndex manipulation for the children to this element + this.interceptTempZIndex = true ; + + this.labelFocusAttr = options.labelFocusAttr || { bold: true } ; + this.labelBlurAttr = options.labelBlurAttr || { dim: true } ; + + this.buttonBlurAttr = options.buttonBlurAttr || { bgColor: 'cyan' , color: 'white' , bold: true } ; + this.buttonFocusAttr = options.buttonFocusAttr || { bgColor: 'brightCyan' , color: 'black' , bold: true } ; + this.buttonDisabledAttr = options.buttonDisabledAttr || { bgColor: 'cyan' , color: 'gray' , bold: true } ; + this.buttonSubmittedAttr = options.buttonSubmittedAttr || { bgColor: 'brightCyan' , color: 'brightWhite' , bold: true } ; + this.turnedOnBlurAttr = options.turnedOnBlurAttr || { bgColor: 'cyan' } ; + this.turnedOnFocusAttr = options.turnedOnFocusAttr || { bgColor: 'brightCyan' , bold: true } ; + this.turnedOffBlurAttr = options.turnedOffBlurAttr || { bgColor: 'gray' , dim: true } ; + this.turnedOffFocusAttr = options.turnedOffFocusAttr || { bgColor: 'white' , color: 'black' , bold: true } ; + + // TextBufffer needs computed attr, not object one + this.textAttr = options.textAttr || { bgColor: 'blue' } ; + this.voidAttr = options.voidAttr || options.emptyAttr || { bgColor: 'blue' } ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + + if ( this.label ) { + this.labelText = new Text( { + internal: true , + parent: this , + content: this.label , + x: this.outputX , + y: this.outputY , + height: 1 , + attr: this.labelBlurAttr , + leftPadding: this.labelBlurLeftPadding , + rightPadding: this.labelBlurRightPadding , + noDraw: true + } ) ; + } + + this.inputType = options.type || 'text' ; + + this.onKey = this.onKey.bind( this ) ; + this.onFocus = this.onFocus.bind( this ) ; + this.onClick = this.onClick.bind( this ) ; + this.onInputSubmit = this.onInputSubmit.bind( this ) ; + + this.initInput( options ) ; + this.updateStatus() ; + + this.on( 'key' , this.onKey ) ; + this.on( 'focus' , this.onFocus ) ; + this.on( 'click' , this.onClick ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'LabeledInput' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = LabeledInput ; + +LabeledInput.prototype = Object.create( Element.prototype ) ; +LabeledInput.prototype.constructor = LabeledInput ; +LabeledInput.prototype.elementType = 'LabeledInput' ; + +LabeledInput.prototype.needInput = true ; +LabeledInput.prototype.noChildFocus = true ; +LabeledInput.prototype.propagateZ = true ; + + + +LabeledInput.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'focus' , this.onFocus ) ; + this.off( 'click' , this.onClick ) ; + if ( this.input ) { this.off( 'submit' , this.onInputSubmit ) ; } + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +LabeledInput.prototype.keyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + ALT_ENTER: 'submit' + //ESCAPE: 'cancel' , +} ; + + + +LabeledInput.prototype.editableTextBoxKeyBindings = { + BACKSPACE: 'backDelete' , + DELETE: 'delete' , + LEFT: 'backward' , + RIGHT: 'forward' , + CTRL_LEFT: 'startOfWord' , + CTRL_RIGHT: 'endOfWord' , + HOME: 'startOfLine' , + END: 'endOfLine' , + CTRL_O: 'copyClipboard' , + CTRL_P: 'pasteClipboard' +} ; + + + +LabeledInput.prototype.multiLineEditableTextBoxKeyBindings = Object.assign( {} , LabeledInput.prototype.editableTextBoxKeyBindings , { + ENTER: 'newLine' , + KP_ENTER: 'newLine' , + UP: 'up' , + DOWN: 'down' , + PAGE_UP: 'scrollUp' , + PAGE_DOWN: 'scrollDown' , + CTRL_O: 'copyClipboard' , + CTRL_P: 'pasteClipboard' +} ) ; + + + +LabeledInput.prototype.selectListKeyBindings = { + UP: 'previous' , + DOWN: 'next' , + ENTER: 'submit' , + KP_ENTER: 'submit' +} ; + + + +LabeledInput.prototype.selectListMultiKeyBindings = { + UP: 'previous' , + DOWN: 'next' , + ENTER: 'submit' , + KP_ENTER: 'submit' +} ; + + + +LabeledInput.prototype.initInput = function( options ) { + switch ( this.inputType ) { + case 'text' : + this.initTextInput( options ) ; + break ; + case 'select' : + this.initSelectInput( options ) ; + break ; + case 'selectMulti' : + this.initSelectMultiInput( options ) ; + break ; + default : + throw new Error( 'Unknown input type: ' + this.inputType ) ; + } + + // Allow label highlight + this.input.on( 'focus' , this.onChildFocus.bind( this ) ) ; +} ; + + + +LabeledInput.prototype.initTextInput = function( options ) { + if ( options.inputKeyBindings ) { this.inputKeyBindings = options.inputKeyBindings ; } + else if ( options.allowNewLine ) { this.inputKeyBindings = this.multiLineEditableTextBoxKeyBindings ; } + else { this.inputKeyBindings = this.editableTextBoxKeyBindings ; } + + this.input = new EditableTextBox( { + internal: true , + parent: this , + content: options.content , + value: options.value , + x: this.outputX + ( this.labelText ? this.labelText.outputWidth : 0 ) , + y: this.outputY , + width: this.outputWidth - ( this.labelText ? this.labelText.outputWidth : 0 ) , + height: this.outputHeight , + lineWrap: !! options.lineWrap , + wordWrap: !! options.wordWrap , + scrollable: !! options.scrollable , + vScrollBar: !! options.vScrollBar , + hScrollBar: !! options.hScrollBar , + hiddenContent: this.hiddenContent , + textAttr: this.textAttr , + voidAttr: this.voidAttr , + keyBindings: this.inputKeyBindings , + noDraw: true + } ) ; +} ; + + + +LabeledInput.prototype.initSelectInput = function( options ) { + if ( options.inputKeyBindings ) { this.inputKeyBindings = options.inputKeyBindings ; } + else { this.inputKeyBindings = this.selectListKeyBindings ; } + + this.input = new SelectList( { + internal: true , + parent: this , + content: options.content , + value: options.value , + x: this.outputX + ( this.labelText ? this.labelText.outputWidth : 0 ) , + y: this.outputY , + width: this.outputWidth - ( this.labelText ? this.labelText.outputWidth : 0 ) , + items: options.items , + buttonBlurAttr: this.buttonBlurAttr , + buttonFocusAttr: this.buttonFocusAttr , + buttonDisabledAttr: this.buttonDisabledAttr , + buttonSubmittedAttr: this.buttonSubmittedAttr , + keyBindings: this.inputKeyBindings , + noDraw: true + } ) ; + + this.input.on( 'submit' , this.onInputSubmit ) ; +} ; + + + +LabeledInput.prototype.initSelectMultiInput = function( options ) { + if ( options.inputKeyBindings ) { this.inputKeyBindings = options.inputKeyBindings ; } + else { this.inputKeyBindings = this.selectListMultiKeyBindings ; } + + this.input = new SelectListMulti( { + internal: true , + parent: this , + content: options.content , + value: options.value , + x: this.outputX + ( this.labelText ? this.labelText.outputWidth : 0 ) , + y: this.outputY , + width: this.outputWidth - ( this.labelText ? this.labelText.outputWidth : 0 ) , + items: options.items , + buttonBlurAttr: this.buttonBlurAttr , + buttonFocusAttr: this.buttonFocusAttr , + buttonDisabledAttr: this.buttonDisabledAttr , + buttonSubmittedAttr: this.buttonSubmittedAttr , + turnedOnBlurAttr: this.turnedOnBlurAttr , + turnedOnFocusAttr: this.turnedOnFocusAttr , + turnedOffBlurAttr: this.turnedOffBlurAttr , + turnedOffFocusAttr: this.turnedOffFocusAttr , + keyBindings: this.inputKeyBindings , + noDraw: true + } ) ; + + this.input.on( 'submit' , this.onInputSubmit ) ; +} ; + + + +LabeledInput.prototype.updateStatus = function() { + /* + if ( this.disabled ) { + this.labelText.attr = this.labelDisabledAttr ; + this.labelText.leftPadding = this.labelDisabledLeftPadding ; + this.labelText.rightPadding = this.labelDisabledRightPadding ; + } + else if ( this.submitted ) { + this.labelText.attr = this.labelSubmittedAttr ; + this.labelText.leftPadding = this.labelSubmittedLeftPadding ; + this.labelText.rightPadding = this.labelSubmittedRightPadding ; + } + else */ + if ( this.hasFocus || this.hasInputFocus ) { + if ( this.labelText ) { + this.labelText.attr = this.labelFocusAttr ; + this.labelText.leftPadding = this.labelFocusLeftPadding ; + this.labelText.rightPadding = this.labelFocusRightPadding ; + } + } + else if ( this.labelText ) { + this.labelText.attr = this.labelBlurAttr ; + this.labelText.leftPadding = this.labelBlurLeftPadding ; + this.labelText.rightPadding = this.labelBlurRightPadding ; + } +} ; + + + +// Directly linked to the EditableTextBox +LabeledInput.prototype.getValue = function() { return this.input.getValue() ; } ; +LabeledInput.prototype.setValue = function( value , dontDraw ) { return this.input.setValue( value , dontDraw ) ; } ; +LabeledInput.prototype.getContent = function() { return this.input.getContent() ; } ; +LabeledInput.prototype.setContent = function( content , hasMarkup , dontDraw ) { return this.input.setContent( content , hasMarkup , dontDraw ) ; } ; + + + +LabeledInput.prototype.drawSelfCursor = function() { + if ( this.input.drawSelfCursor ) { this.input.drawSelfCursor() ; } +} ; + + + +LabeledInput.prototype.onKey = function( key , altKeys , data ) { + // Give full priority to the child input + if ( this.input.emit( 'key' , key , altKeys , data ).interrupt ) { return true ; } + + switch( this.keyBindings[ key ] ) { + case 'submit' : + this.emit( 'submit' , this.getValue() , undefined , this ) ; + break ; + + default : + return ; + } + + return true ; // Do not bubble up +} ; + + + +LabeledInput.prototype.onInputSubmit = function( data ) { + this.emit( 'submit' , this.getValue() , undefined , this ) ; +} ; + + + +LabeledInput.prototype.onFocus = function( focus , type ) { + this.hasFocus = focus ; + + if ( type === 'delegate' ) { return ; } + + if ( focus && type !== 'backCycle' && this.input ) { + // Defer to the next tick to avoid recursive events producing wrong listener order + process.nextTick( () => { + this.document.giveFocusTo( this.input , 'delegate' ) ; + } ) ; + } + else { + // This is done by .onChildFocus() if there is an attached input + this.updateStatus() ; + //this.draw() ; + if ( this.labelText ) { this.labelText.draw() ; } + } +} ; + + + +LabeledInput.prototype.onChildFocus = function( focus , type ) { + this.hasInputFocus = focus ; + this.updateStatus() ; + if ( this.labelText ) { this.labelText.draw() ; } +} ; + + + +LabeledInput.prototype.onClick = function( data ) { + this.document.giveFocusTo( this , 'select' ) ; +} ; + + +}).call(this)}).call(this,require('_process')) +},{"./EditableTextBox.js":22,"./Element.js":23,"./SelectList.js":29,"./SelectListMulti.js":30,"./Text.js":32,"_process":179,"string-kit":123}],27:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Container = require( './Container.js' ) ; +const boxesChars = require( '../spChars.js' ).box ; + + + +function Layout( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + this.onParentResize = this.onParentResize.bind( this ) ; + + this.layoutDef = options.layout ; + this.computed = {} ; + this.boxesContainer = {} ; + this.boxChars = boxesChars.light ; + + if ( options.boxChars ) { + if ( typeof options.boxChars === 'object' ) { + this.boxChars = options.boxChars ; + } + else if ( typeof options.boxChars === 'string' && boxesChars[ options.boxChars ] ) { + this.boxChars = boxesChars[ options.boxChars ] ; + } + } + + this.on( 'parentResize' , this.onParentResize ) ; + + this.computeBoundingBoxes() ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Layout' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Layout ; + +Layout.prototype = Object.create( Element.prototype ) ; +Layout.prototype.constructor = Layout ; +Layout.prototype.elementType = 'Layout' ; + + + +Layout.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'parentResize' , this.onParentResize ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +Layout.prototype.computeBoundingBoxes = function() { + var computed = this.computed = {} ; + + var layoutDef = this.layoutDef ; + + var parent = { + width_: this.outputDst.width , + height_: this.outputDst.height , + dx_: this.outputDst.width - 1 , + dy_: this.outputDst.height - 1 , + xmin_: 0 , + ymin_: 0 + } ; + + var inProgress = { + offsetX: ( this.layoutDef.x ) || 0 , + offsetY: ( this.layoutDef.y ) || 0 , + remainingDx: parent.dx_ , + remainingDy: parent.dy_ + } ; + + this.computeBoundingBoxes_( layoutDef , computed , parent , inProgress ) ; +} ; + + + +Layout.prototype.computeBoundingBoxes_ = function( layoutDef , computed , parent , inProgress ) { + var i , nextInProgress , hasChild = false ; + + //console.error( "\n\nlayoutDef #" + layoutDef.id + ':\n' , computed ) ; + + this.computeDxDy( layoutDef , computed , parent , inProgress ) ; + + //console.error( "\n\nlayoutDef #" + layoutDef.id + ':\n' , computed ) ; + + computed.xmin_ = parent.xmin_ + inProgress.offsetX ; + computed.xmax_ = computed.xmin_ + computed.dx_ ; + computed.ymin_ = parent.ymin_ + inProgress.offsetY ; + computed.ymax_ = computed.ymin_ + computed.dy_ ; + + //console.error( "\n\nlayoutDef #" + layoutDef.id + ':\n' , computed ) ; + + // Check if it goes out of its parent + if ( computed.xmax_ > parent.xmax_ ) { + computed.xmax_ = parent.xmax_ ; + computed.dx_ = computed.xmax_ - computed.xmin_ ; + } + + if ( computed.ymax_ > parent.ymax_ ) { + computed.ymax_ = parent.ymax_ ; + computed.dy_ = computed.ymax_ - computed.ymin_ ; + } + + // Width and height are not used internally, but provided for userland + computed.width_ = computed.dx_ + 1 ; + computed.height_ = computed.dy_ + 1 ; + + computed.columns = [] ; + computed.rows = [] ; + + //console.error( "\n\nlayoutDef #" + layoutDef.id + ':\n' , computed ) ; + + nextInProgress = { + offsetX: 0 , + offsetY: 0 , + remainingDx: computed.dx_ , + remainingDy: computed.dy_ , + autoDxCount: 0 , + autoDyCount: 0 + } ; + + if ( layoutDef.columns && layoutDef.columns.length ) { + // First pass + for ( i = 0 ; i < layoutDef.columns.length ; i ++ ) { + computed.columns[ i ] = {} ; + this.computeDxDy( layoutDef.columns[ i ] , computed.columns[ i ] , computed , nextInProgress , true ) ; + + if ( computed.columns[ i ].dx_ !== undefined ) { nextInProgress.remainingDx -= computed.columns[ i ].dx_ ; } + else { nextInProgress.autoDxCount ++ ; } + } + + for ( i = 0 ; i < layoutDef.columns.length ; i ++ ) { + this.computeBoundingBoxes_( layoutDef.columns[ i ] , computed.columns[ i ] , computed , nextInProgress ) ; + nextInProgress.offsetX = computed.columns[ i ].xmax_ - computed.xmin_ ; + } + + hasChild = true ; + } + else if ( layoutDef.rows && layoutDef.rows.length ) { + // First pass + for ( i = 0 ; i < layoutDef.rows.length ; i ++ ) { + computed.rows[ i ] = {} ; + this.computeDxDy( layoutDef.rows[ i ] , computed.rows[ i ] , computed , nextInProgress , true ) ; + + if ( computed.rows[ i ].dy_ !== undefined ) { nextInProgress.remainingDy -= computed.rows[ i ].dy_ ; } + else { nextInProgress.autoDyCount ++ ; } + } + + for ( i = 0 ; i < layoutDef.rows.length ; i ++ ) { + this.computeBoundingBoxes_( layoutDef.rows[ i ] , computed.rows[ i ] , computed , nextInProgress ) ; + nextInProgress.offsetY = computed.rows[ i ].ymax_ - computed.ymin_ ; + } + + hasChild = true ; + } + + computed.width_ = computed.dx_ + 1 ; + computed.height_ = computed.dy_ + 1 ; + + this.round( computed ) ; + //console.error( "\n\nfinal #" + layoutDef.id + ':\n' , computed ) ; + + // Container surfaces are only created for "leaf" boxes, i.e. boxes that don't have child + if ( ! hasChild ) { + if ( this.boxesContainer[ layoutDef.id ] ) { + if ( this.boxesContainer[ layoutDef.id ].width !== computed.width - 2 || this.boxesContainer[ layoutDef.id ].height !== computed.height - 2 ) { + this.boxesContainer[ layoutDef.id ].resize( { + x: 0 , + y: 0 , + width: computed.width - 2 , + height: computed.height - 2 + } ) ; + } + + this.boxesContainer[ layoutDef.id ].moveTo( computed.xmin + 1 , computed.ymin + 1 , true ) ; + } + else { + var container = new Container( { + internal: true , + id: layoutDef.id , + parent: this , + outputDst: this.outputDst , + outputX: computed.xmin + 1 , + outputY: computed.ymin + 1 , + outputWidth: computed.width - 2 , + outputHeight: computed.height - 2 + } ) ; + + layoutDef.id = container.id ; + this.boxesContainer[ layoutDef.id ] = container ; + } + } +} ; + + + +Layout.prototype.computeDxDy = function( layoutDef , computed , parent , inProgress , firstPass ) { + //console.error( ">>>>>>>>>> #" + layoutDef.id + ' firstPass: ' , !! firstPass ) ; + + // Dx + if ( firstPass || computed.dx_ === undefined ) { + if ( layoutDef.width !== undefined ) { + computed.dx_ = Math.max( 0 , Math.min( parent.dx_ , layoutDef.width - 1 ) ) ; + } + else if ( layoutDef.widthPercent !== undefined ) { + computed.dx_ = Math.max( 0 , Math.min( parent.dx_ , parent.dx_ * layoutDef.widthPercent / 100 ) ) ; + } + else if ( ! firstPass ) { + //console.error( ">>>>>>>>>> #" + layoutDef.id + ' remaining dx: ' , inProgress.remainingDx , '/' , inProgress.autoDxCount , ' --- ' , inProgress ) ; + computed.dx_ = Math.max( 0 , inProgress.remainingDx / ( inProgress.autoDxCount || 1 ) ) ; + //console.error( ">>>>>>>>>> #" + layoutDef.id + ' computed dx: ' , computed.dx_ ) ; + } + } + + // Dy + if ( firstPass || computed.dy_ === undefined ) { + if ( layoutDef.height !== undefined ) { + computed.dy_ = Math.max( 0 , Math.min( parent.dy_ , layoutDef.height - 1 ) ) ; + } + else if ( layoutDef.heightPercent !== undefined ) { + computed.dy_ = Math.max( 0 , Math.min( parent.dy_ , parent.dy_ * layoutDef.heightPercent / 100 ) ) ; + } + else if ( ! firstPass ) { + computed.dy_ = Math.max( 0 , inProgress.remainingDy / ( inProgress.autoDyCount || 1 ) ) ; + } + } +} ; + + + +Layout.prototype.round = function( computed ) { + computed.xmin = Math.round( computed.xmin_ ) ; + computed.xmax = Math.round( computed.xmax_ ) ; + computed.ymin = Math.round( computed.ymin_ ) ; + computed.ymax = Math.round( computed.ymax_ ) ; + + computed.dx = computed.xmax - computed.xmin ; + computed.dy = computed.ymax - computed.ymin ; + computed.width = computed.dx + 1 ; + computed.height = computed.dy + 1 ; +} ; + + + +Layout.prototype.preDrawSelf = function() { + var y , tees = {} ; + + //this.computeBoundingBoxes() ; + + // Draw the top border + this.outputDst.put( + { x: this.computed.xmin , y: this.computed.ymin } , + this.boxChars.topLeft + this.boxChars.horizontal.repeat( this.computed.dx - 1 ) + this.boxChars.topRight + ) ; + + // Draw the bottom border + this.outputDst.put( + { x: this.computed.xmin , y: this.computed.ymax } , + this.boxChars.bottomLeft + this.boxChars.horizontal.repeat( this.computed.dx - 1 ) + this.boxChars.bottomRight + ) ; + + // Draw the left and right border + for ( y = this.computed.ymin + 1 ; y < this.computed.ymax ; y ++ ) { + this.outputDst.put( { x: this.computed.xmin , y: y } , this.boxChars.vertical ) ; + this.outputDst.put( { x: this.computed.xmax , y: y } , this.boxChars.vertical ) ; + } + + this.drawRecursive( this.computed , tees ) ; +} ; + + + +Layout.prototype.drawRecursive = function( computed , tees ) { + var i ; + + if ( computed.columns.length ) { + for ( i = 0 ; i < computed.columns.length ; i ++ ) { + this.drawColumn( computed.columns[ i ] , tees , i === computed.columns.length - 1 ) ; + } + } + else if ( computed.rows.length ) { + for ( i = 0 ; i < computed.rows.length ; i ++ ) { + this.drawRow( computed.rows[ i ] , tees , i === computed.rows.length - 1 ) ; + } + } +} ; + + + +Layout.prototype.drawColumn = function( computed , tees , last ) { + var y ; + + if ( ! last ) { + // Draw Tee-junction + this.drawTee( computed.xmax , computed.ymin , 'top' , tees ) ; + this.drawTee( computed.xmax , computed.ymax , 'bottom' , tees ) ; + + // Draw the right border + for ( y = computed.ymin + 1 ; y < computed.ymax ; y ++ ) { + this.outputDst.put( { x: computed.xmax , y: y } , this.boxChars.vertical ) ; + } + } + + this.drawRecursive( computed , tees ) ; +} ; + + + +Layout.prototype.drawTee = function( x , y , type , tees ) { + var key = x + ':' + y ; + + if ( ! tees[ key ] ) { + this.outputDst.put( { x: x , y: y } , this.boxChars[ type + 'Tee' ] ) ; + tees[ key ] = type ; + } + else if ( tees[ key ] !== type ) { + this.outputDst.put( { x: x , y: y } , this.boxChars.cross ) ; + } +} ; + + + +Layout.prototype.drawRow = function( computed , tees , last ) { + if ( ! last ) { + // Draw Tee-junction + this.drawTee( computed.xmin , computed.ymax , 'left' , tees ) ; + this.drawTee( computed.xmax , computed.ymax , 'right' , tees ) ; + + // Draw the bottom border + this.outputDst.put( { x: computed.xmin + 1 , y: computed.ymax } , this.boxChars.horizontal.repeat( computed.dx - 1 ) ) ; + } + + this.drawRecursive( computed , tees ) ; +} ; + + + +Layout.prototype.onParentResize = function() { + this.computeBoundingBoxes() ; +} ; + + +},{"../spChars.js":48,"./Container.js":19,"./Element.js":23}],28:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const BaseMenu = require( './BaseMenu.js' ) ; +const Button = require( './Button.js' ) ; +const ToggleButton = require( './ToggleButton.js' ) ; + +const misc = require( '../misc.js' ) ; +const string = require( 'string-kit' ) ; + + + +// Inherit from BaseMenu for common methods + +function RowMenu( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( ! options.outputWidth && ! options.width ) { + options.outputWidth = Math.min( options.parent.inputWidth , options.parent.outputWidth ) ; + } + + this.buttonPaddingWidth = 0 ; + this.buttonSymbolWidth = 0 ; + this.pageItemsDef = [] ; + + BaseMenu.call( this , options ) ; + + this.justify = !! options.justify ; + + this.separator = options.separator || options.buttonSeparator || ' ' ; + this.separatorHasMarkup = !! ( options.separatorHasMarkup || options.buttonSeparatorHasMarkup ) ; + this.separatorAttr = Object.assign( {} , this.backgroundAttr , options.separatorAttr || options.buttonSeparatorAttr ) ; + this.separatorWidth = 0 ; // Computed later + + this.initChildren() ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'RowMenu' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = RowMenu ; + +RowMenu.prototype = Object.create( BaseMenu.prototype ) ; +RowMenu.prototype.constructor = RowMenu ; +RowMenu.prototype.elementType = 'RowMenu' ; + + + +RowMenu.prototype.inlineNewLine = true ; +RowMenu.prototype.ButtonClass = Button ; + + + +RowMenu.prototype.defaultOptions = { + buttonBlurAttr: { bgColor: 'white' , color: 'black' } , + buttonFocusAttr: { bgColor: 'green' , color: 'blue' , dim: true } , + buttonDisabledAttr: { bgColor: 'white' , color: 'brightBlack' } , + buttonSubmittedAttr: { bgColor: 'brightWhite' , color: 'brightBlack' } +} ; + + + +RowMenu.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'focus' , this.onFocus ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +RowMenu.prototype.keyBindings = { + LEFT: 'previous' , + RIGHT: 'next' , + PAGE_UP: 'previousPage' , + PAGE_DOWN: 'nextPage' , + HOME: 'firstPage' , + END: 'lastPage' , + //ENTER: 'submit' , + //KP_ENTER: 'submit' , + ALT_ENTER: 'submit' +} ; + + + +RowMenu.prototype.buttonKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' +} ; + + + +RowMenu.prototype.toggleButtonKeyBindings = { + ENTER: 'toggle' , + KP_ENTER: 'toggle' +} ; + + + +// Pre-compute page and eventually create Buttons automatically +RowMenu.prototype.initChildren = function( noInitPage = false ) { + if ( ! this.itemsDef.length ) { return ; } + + this.buttonPaddingWidth = + Math.max( + Element.computeContentWidth( this.blurLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.focusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.disabledLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.submittedLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurLeftPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurLeftPadding , this.paddingHasMarkup ) + ) + Math.max( + Element.computeContentWidth( this.blurRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.focusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.disabledRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.submittedRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnFocusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffFocusRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOnBlurRightPadding , this.paddingHasMarkup ) , + Element.computeContentWidth( this.turnedOffBlurRightPadding , this.paddingHasMarkup ) + ) ; + + if ( this.buttonPaddingWidth > this.outputWidth ) { + // The padding itself is bigger than the width... so what should we do? + return ; + } + + var ellipsisWidth = Element.computeContentWidth( this.contentEllipsis , false ) ; + this.separatorWidth = Element.computeContentWidth( this.separator , this.separatorHasMarkup ) ; + + + this.previousPageDef = Object.assign( { content: '◀' , internalRole: 'previousPage' } , this.previousPageDef ) ; + this.previousPageDef.contentHasMarkup = this.previousPageDef.contentHasMarkup || this.previousPageDef.markup ; + this.previousPageDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.previousPageDef.content , this.previousPageDef.contentHasMarkup ) ; + this.previousPageDef.buttonContent = this.previousPageDef.content ; + + this.nextPageDef = Object.assign( { content: '▶' , internalRole: 'nextPage' } , this.nextPageDef ) ; + this.nextPageDef.contentHasMarkup = this.nextPageDef.contentHasMarkup || this.nextPageDef.markup ; + this.nextPageDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.nextPageDef.content , this.nextPageDef.contentHasMarkup ) ; + this.nextPageDef.buttonContent = this.nextPageDef.content ; + + + var page = 0 , pageWidth = 0 , pageItemCount = 0 ; + + this.itemsDef.forEach( ( def , index ) => { + def.buttonContent = def.content ; + def.contentHasMarkup = def.contentHasMarkup || def.markup ; + + var contentWidth = Element.computeContentWidth( def.content , def.contentHasMarkup ) , + isLastItem = index === this.itemsDef.length - 1 ; + + def.width = contentWidth + this.buttonPaddingWidth + this.buttonSymbolWidth ; + + var overflow = pageWidth + def.width + + ( pageItemCount ? this.separatorWidth : 0 ) + + ( isLastItem ? 0 : this.nextPageDef.width + this.separatorWidth ) + - this.outputWidth ; + + //console.error( "overflow",overflow,pageWidth,def.width,isLastItem,this.nextPageDef.width,this.separatorWidth,this.outputWidth); + if ( overflow > 0 ) { + if ( pageItemCount ) { + page ++ ; + pageItemCount = 0 ; + pageWidth = this.previousPageDef.width + this.separatorWidth ; + + overflow = pageWidth + def.width + + ( isLastItem ? 0 : this.nextPageDef.width + this.separatorWidth ) + - this.outputWidth ; + } + + if ( overflow > 0 ) { + def.buttonContent = Element.truncateContent( def.content , contentWidth - overflow - ellipsisWidth , def.contentHasMarkup ) + this.contentEllipsis ; + contentWidth = Element.computeContentWidth( def.buttonContent , def.contentHasMarkup ) ; + } + } + + def.page = page ; + pageWidth += def.width + ( pageItemCount ? this.separatorWidth : 0 ) ; + pageItemCount ++ ; + + if ( ! this.pageItemsDef[ page ] ) { this.pageItemsDef[ page ] = [] ; } + this.pageItemsDef[ page ].push( def ) ; + } ) ; + + this.maxPage = page ; + + // Force at least an empty page + if ( ! this.pageItemsDef.length ) { this.pageItemsDef.push( [] ) ; } + + this.pageItemsDef.forEach( ( pageDef , index ) => { + if ( index ) { pageDef.unshift( this.previousPageDef ) ; } + if ( index < this.pageItemsDef.length - 1 ) { pageDef.push( this.nextPageDef ) ; } + pageDef.buttonsWidth = pageDef.reduce( ( acc , def ) => acc + def.width , 0 ) ; + pageDef.buttonsAndSeparatorsWidth = pageDef.buttonsWidth + ( pageDef.length - 1 ) * this.separatorWidth ; + pageDef.justifyWidth = Math.max( 0 , + this.justify ? ( this.outputWidth - pageDef.buttonsAndSeparatorsWidth ) / ( pageDef.length - 1 ) + : 0 + ) ; + //console.error( '\n>>> ' , pageDef.buttonsWidth,pageDef.buttonsAndSeparatorsWidth,pageDef.justifyWidth) ; + } ) ; + + // Only initPage if we are not a superclass of the object + if ( this.elementType === 'RowMenu' && ! noInitPage ) { this.initPage() ; } +} ; + + + +RowMenu.prototype.initPage = function( page = this.page ) { + var pageDef = this.pageItemsDef[ page ] , + justifyWidthError = 0 , + buttonOffsetX = 0 , + buttonOffsetY = 0 ; + + if ( ! pageDef ) { return ; } + + this.buttons.forEach( button => button.destroy( false , true ) ) ; + this.buttons.length = 0 ; + + //console.error( "pageDef.justifyWidth" , pageDef.justifyWidth ) ; + + pageDef.forEach( ( def , index ) => { + var ButtonConstructor , isToggle , key , value , blurAttr ; + + ButtonConstructor = def.internalRole ? Button : this.ButtonClass ; + isToggle = ButtonConstructor === ToggleButton || ButtonConstructor.prototype instanceof ToggleButton ; + + key = def.key ; // For ToggleButton + value = this.childUseParentKeyValue && key && this.value && typeof this.value === 'object' ? this.value[ key ] : def.value ; + + if ( index % 2 ) { + // Odd + blurAttr = def.blurAttr || this.buttonBlurAttr ; + } + else { + // Even + blurAttr = def.evenBlurAttr || def.blurAttr || this.buttonEvenBlurAttr || this.buttonBlurAttr ; + } + + this.buttons[ index ] = new ButtonConstructor( { + internal: true , + parent: this , + childId: index , + internalRole: def.internalRole , + content: def.buttonContent , + contentHasMarkup: def.contentHasMarkup , + disabled: def.disabled , + def , + key , + value , + outputX: this.outputX + buttonOffsetX , + outputY: this.outputY + buttonOffsetY , + + blurAttr , + focusAttr: def.focusAttr || this.buttonFocusAttr , + disabledAttr: def.disabledAttr || this.buttonDisabledAttr , + submittedAttr: def.submittedAttr || this.buttonSubmittedAttr , + turnedOnFocusAttr: def.turnedOnFocusAttr || this.turnedOnFocusAttr , + turnedOffFocusAttr: def.turnedOffFocusAttr || this.turnedOffFocusAttr , + turnedOnBlurAttr: def.turnedOnBlurAttr || this.turnedOnBlurAttr , + turnedOffBlurAttr: def.turnedOffBlurAttr || this.turnedOffBlurAttr , + + blurLeftPadding: this.blurLeftPadding , + blurRightPadding: this.blurRightPadding , + focusLeftPadding: this.focusLeftPadding , + focusRightPadding: this.focusRightPadding , + disabledLeftPadding: this.disabledLeftPadding , + disabledRightPadding: this.disabledRightPadding , + submittedLeftPadding: this.submittedLeftPadding , + submittedRightPadding: this.submittedRightPadding , + + turnedOnFocusLeftPadding: this.turnedOnFocusLeftPadding , + turnedOnFocusRightPadding: this.turnedOnFocusRightPadding , + turnedOffFocusLeftPadding: this.turnedOffFocusLeftPadding , + turnedOffFocusRightPadding: this.turnedOffFocusRightPadding , + turnedOnBlurLeftPadding: this.turnedOnBlurLeftPadding , + turnedOnBlurRightPadding: this.turnedOnBlurRightPadding , + turnedOffBlurLeftPadding: this.turnedOffBlurLeftPadding , + turnedOffBlurRightPadding: this.turnedOffBlurRightPadding , + + paddingHasMarkup: this.paddingHasMarkup , + + keyBindings: isToggle ? this.toggleButtonKeyBindings : this.buttonKeyBindings , + actionKeyBindings: isToggle ? this.toggleButtonActionKeyBindings : this.buttonActionKeyBindings , + shortcuts: def.shortcuts , + + noDraw: true + } ) ; + + this.buttons[ index ].on( 'submit' , this.onButtonSubmit ) ; + this.buttons[ index ].on( 'focus' , this.onButtonFocus ) ; + this.buttons[ index ].on( 'blinked' , this.onButtonBlinked ) ; + + if ( isToggle ) { + this.buttons[ index ].on( 'toggle' , this.onButtonToggle ) ; + } + + var justifyWidthFloat = pageDef.justifyWidth + justifyWidthError ; + var justifyWidth = Math.round( justifyWidthFloat ) ; + justifyWidthError = justifyWidthFloat - justifyWidth ; + + buttonOffsetX += this.buttons[ index ].outputWidth + this.separatorWidth + justifyWidth ; + } ) ; + + // Set outputWidth to the correct value + //if ( buttonOffsetX < this.outputWidth ) { this.needRedraw = true ; } + //this.pageWidth = buttonOffsetX ; + //this.outputWidth = buttonOffsetY ; +} ; + + + +RowMenu.prototype.preDrawSelf = function() { + //console.error( string.format( "Call preDrawSelf(), page %i" , this.page )); + this.outputDst.put( { x: this.outputX , y: this.outputY , attr: this.backgroundAttr } , ' '.repeat( this.outputWidth ) ) ; + + if ( this.separator ) { + let index , button , nextButton ; + for ( index = 0 ; index < this.buttons.length - 1 ; index ++ ) { + button = this.buttons[ index ] ; + nextButton = this.buttons[ index + 1 ] ; + this.outputDst.put( { + x: Math.round( button.outputX + button.outputWidth + nextButton.outputX ) / 2 , + y: this.outputY , + attr: this.separatorAttr + } , + this.separator + ) ; + //console.error( string.format( "Add one at %i" , button.outputX + button.outputWidth + Math.round( this.pageItemsDef[ this.page ].justifyWidth / 2 ))); + } + //console.error( string.format( "%Y" , this.buttons[this.buttons.length-1].content )); + } +} ; + + +},{"../misc.js":42,"./BaseMenu.js":15,"./Button.js":16,"./Element.js":23,"./ToggleButton.js":35,"string-kit":123}],29:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const BaseMenu = require( './BaseMenu.js' ) ; +const ColumnMenu = require( './ColumnMenu.js' ) ; +const Button = require( './Button.js' ) ; + + + +// Inherit from ColumnMenu for common methods + +function SelectList( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( ! options.master || typeof options.master !== 'object' ) { + options.master = Object.assign( {} , this.defaultOptions.master ) ; + } + else { + options.master = Object.assign( {} , this.defaultOptions.master , options.master ) ; + } + + if ( options.content ) { + options.master.content = options.content ; + } + + if ( ! options.separator || typeof options.separator !== 'object' ) { + options.separator = Object.assign( {} , this.defaultOptions.separator ) ; + } + else { + options.separator = Object.assign( {} , this.defaultOptions.separator , options.separator ) ; + } + + ColumnMenu.call( this , options ) ; + + this.showMenu = false ; + this.zIndexRef = this.zIndex ; // Back-up for zIndex + + if ( options.value !== undefined && this.setValue( options.value , true ) ) { + // .initPage() is called by .setValue() only when a correct value was found + this.toggle( this.showMenu , options.noDraw ) ; + } + else { + this.initPage() ; + this.toggle( this.showMenu , options.noDraw ) ; + } + + + this.onClickOut = this.onClickOut.bind( this ) ; + + this.on( 'clickOut' , this.onClickOut ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'SelectList' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = SelectList ; + +SelectList.prototype = Object.create( ColumnMenu.prototype ) ; +SelectList.prototype.constructor = SelectList ; +SelectList.prototype.elementType = 'SelectList' ; + + + +SelectList.prototype.defaultOptions = { + buttonBlurAttr: { bgColor: 'gray' , color: 'white' , bold: true } , + buttonFocusAttr: { bgColor: 'white' , color: 'black' , bold: true } , + buttonDisabledAttr: { + bgColor: 'gray' , color: 'white' , bold: true , dim: true + } , + buttonSubmittedAttr: { bgColor: 'gray' , color: 'brightWhite' , bold: true } , + turnedOnBlurAttr: { bgColor: 'cyan' } , + turnedOnFocusAttr: { bgColor: 'brightCyan' , bold: true } , + turnedOffBlurAttr: { bgColor: 'gray' , dim: true } , + turnedOffFocusAttr: { bgColor: 'white' , color: 'black' , bold: true } , + + master: { + content: 'select-list' , + symbol: '▼' , + internalRole: 'toggle' + } , + separator: { + content: '-' , + contentRepeat: true , + internalRole: 'separator' + } +} ; + + + +SelectList.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'clickOut' , this.onClickOut ) ; + + ColumnMenu.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +SelectList.prototype.toggle = function( showMenu = null , noDraw = false ) { + var i , iMax ; + + if ( showMenu === null ) { this.showMenu = ! this.showMenu ; } + else { this.showMenu = !! showMenu ; } + + for ( i = 1 , iMax = this.buttons.length ; i < iMax ; i ++ ) { + this.buttons[ i ].hidden = ! this.showMenu ; + } + + // Adjust outputHeight, to avoid the list to be clickable when reduced + this.outputHeight = this.showMenu ? this.pageHeight : 1 ; + + if ( this.showMenu ) { this.topZ() ; } + else { this.restoreZ() ; } + + // Return now if noDraw is set, bypassing both drawing and focus + if ( noDraw ) { return ; } + + if ( showMenu ) { + for ( i = 1 , iMax = this.buttons.length ; i < iMax ; i ++ ) { + if ( this.buttons[ i ].value === this.value ) { + this.document.giveFocusTo( this.buttons[ i ] ) ; + break ; + } + } + } + else { + this.document.giveFocusTo( this.buttons[ 0 ] ) ; + } + + this.redraw() ; +} ; + + + +// selectOnly: don't draw, don't toggle +SelectList.prototype.select = function( button , selectOnly ) { + // /!\ Warning! button can be a button (called from .onButtonSubmit()) or a buttonDef (called from .setValue()) /!\ + // This is nasty and should be fixed! + // Ideally, a button should retain a 'def' pointer + var width , + extraWidth = 1 + this.buttonSymbolWidth , + buttonContent = button.content ; + + if ( Array.isArray( buttonContent ) ) { buttonContent = buttonContent[ 0 ] || '' ; } + + width = Element.computeContentWidth( buttonContent , button.contentHasMarkup ) + this.buttonPaddingWidth + this.buttonSymbolWidth ; + + if ( width > this.buttonsMaxWidth ) { + // This is the normal case here... + this.masterDef.buttonContent = + Element.truncateContent( buttonContent , this.buttonsMaxWidth - this.buttonSymbolWidth , button.contentHasMarkup ) + + ' ' + this.masterDef.symbol ; + } + else if ( width < this.buttonsMaxWidth ) { + this.masterDef.buttonContent = buttonContent + ' '.repeat( this.buttonsMaxWidth - width ) + ' ' + this.masterDef.symbol ; + } + else { + this.masterDef.buttonContent = buttonContent + ' ' + this.masterDef.symbol ; + } + + this.masterDef.contentHasMarkup = button.contentHasMarkup ; + this.masterDef.width = this.buttonsMaxWidth ; + this.value = this.masterDef.value = button.value ; + + // Only when it's a buttonDef ATM: + if ( button.page !== undefined ) { this.page = button.page ; } + + this.initPage() ; + + if ( selectOnly ) { return ; } + + this.toggle( false ) ; // , noDraw ) ; +} ; + + + +SelectList.prototype.onButtonSubmit = function( buttonValue , action , button ) { + switch ( button.internalRole ) { + case 'previousPage' : + this.previousPage() ; + break ; + case 'nextPage' : + this.nextPage() ; + break ; + case 'toggle' : + this.toggle() ; + break ; + default : + this.select( button ) ; + this.emit( 'submit' , buttonValue , action , this ) ; + } +} ; + + + +// .setValue() will also select the first matching item +SelectList.prototype.setValue = function( value , noDraw ) { + var buttonDef = this.itemsDef.find( b => b.value === value ) ; + if ( ! buttonDef ) { return false ; } + this.select( buttonDef , noDraw ) ; + return true ; +} ; + + + +SelectList.prototype.onClickOut = function() { this.toggle( false ) ; } ; +SelectList.prototype.getValue = function() { return this.value ; } ; + + +},{"./BaseMenu.js":15,"./Button.js":16,"./ColumnMenu.js":17,"./Element.js":23}],30:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const BaseMenu = require( './BaseMenu.js' ) ; +const ColumnMenuMulti = require( './ColumnMenuMulti.js' ) ; +const Button = require( './Button.js' ) ; + + + +// Inherit from ColumnMenuMulti for common methods + +function SelectListMulti( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( ! options.master || typeof options.master !== 'object' ) { + options.master = Object.assign( {} , this.defaultOptions.master ) ; + } + else { + options.master = Object.assign( {} , this.defaultOptions.master , options.master ) ; + } + + if ( options.content ) { + options.master.content = options.content ; + } + + if ( ! options.separator || typeof options.separator !== 'object' ) { + options.separator = Object.assign( {} , this.defaultOptions.separator ) ; + } + else { + options.separator = Object.assign( {} , this.defaultOptions.separator , options.separator ) ; + } + + ColumnMenuMulti.call( this , options ) ; + + this.showMenu = false ; + this.zIndexRef = this.zIndex ; // Back-up for zIndex + + this.initPage() ; + this.toggle( this.showMenu , options.noDraw ) ; + + this.onClickOut = this.onClickOut.bind( this ) ; + + this.on( 'clickOut' , this.onClickOut ) ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'SelectListMulti' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = SelectListMulti ; + +SelectListMulti.prototype = Object.create( ColumnMenuMulti.prototype ) ; +SelectListMulti.prototype.constructor = SelectListMulti ; +SelectListMulti.prototype.elementType = 'SelectListMulti' ; + + + +SelectListMulti.prototype.defaultOptions = { + buttonBlurAttr: { bgColor: 'gray' , color: 'white' , bold: true } , + buttonFocusAttr: { bgColor: 'white' , color: 'black' , bold: true } , + buttonDisabledAttr: { + bgColor: 'gray' , color: 'white' , bold: true , dim: true + } , + buttonSubmittedAttr: { bgColor: 'gray' , color: 'brightWhite' , bold: true } , + turnedOnBlurAttr: { bgColor: 'cyan' } , + turnedOnFocusAttr: { bgColor: 'brightCyan' , bold: true } , + turnedOffBlurAttr: { bgColor: 'gray' , dim: true } , + turnedOffFocusAttr: { bgColor: 'white' , color: 'black' , bold: true } , + + master: { + content: 'select-list-multi' , + symbol: '▼' , + internalRole: 'toggle' + } , + separator: { + content: '-' , + contentRepeat: true , + internalRole: 'separator' + } +} ; + + + +SelectListMulti.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'clickOut' , this.onClickOut ) ; + + ColumnMenuMulti.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +SelectListMulti.prototype.toggle = function( showMenu = null , noDraw = false ) { + var i , iMax ; + + if ( showMenu === null ) { this.showMenu = ! this.showMenu ; } + else { this.showMenu = !! showMenu ; } + + for ( i = 1 , iMax = this.buttons.length ; i < iMax ; i ++ ) { + this.buttons[ i ].hidden = ! this.showMenu ; + } + + // Adjust outputHeight, to avoid the list to be clickable when reduced + this.outputHeight = this.showMenu ? this.pageHeight : 1 ; + + if ( this.showMenu ) { this.topZ() ; } + else { this.restoreZ() ; } + + // Return now if noDraw is set, bypassing both drawing and focus + if ( noDraw ) { return ; } + + this.document.giveFocusTo( this.buttons[ 0 ] ) ; + + this.redraw() ; +} ; + + + +SelectListMulti.prototype.onButtonSubmit = function( buttonValue , action , button ) { + switch ( button.internalRole ) { + case 'previousPage' : + this.previousPage() ; + break ; + case 'nextPage' : + this.nextPage() ; + break ; + case 'toggle' : + this.toggle() ; + + // Submit when reducing + if ( ! this.showMenu ) { + this.emit( 'submit' , this.value , action , this ) ; + } + break ; + default : + this.emit( 'submit' , this.value , action , this ) ; + } +} ; + + + +SelectListMulti.prototype.onClickOut = function() { + if ( this.showMenu ) { + this.toggle( false ) ; + // We also submit when the menu is closed on click out + this.emit( 'submit' , this.value , undefined , this ) ; + } +} ; + +SelectListMulti.prototype.getValue = function() { return this.value ; } ; + + +},{"./BaseMenu.js":15,"./Button.js":16,"./ColumnMenuMulti.js":18,"./Element.js":23}],31:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Button = require( './Button.js' ) ; + +// Default transfer function +const IDENTITY = v => v ; + + + +function Slider( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + this.onClick = this.onClick.bind( this ) ; + this.onDrag = this.onDrag.bind( this ) ; + this.onWheel = this.onWheel.bind( this ) ; + this.onButtonSubmit = this.onButtonSubmit.bind( this ) ; + + this.isVertical = !! options.isVertical ; + this.slideRate = 0 ; + this.handleOffset = 0 ; + + this.rateToValue = typeof options.rateToValue === 'function' ? options.rateToValue : IDENTITY ; + this.valueToRate = typeof options.valueToRate === 'function' ? options.valueToRate : IDENTITY ; + + this.buttonBlurAttr = options.buttonBlurAttr || { bgColor: 'black' , color: 'white' , bold: true } ; + this.buttonFocusAttr = options.buttonFocusAttr || { bgColor: 'white' , color: 'black' , bold: true } ; + this.buttonSubmittedAttr = options.buttonSubmittedAttr || { bgColor: 'gray' , color: 'brightWhite' , bold: true } ; + + this.backwardSymbol = options.backwardSymbol || ( this.isVertical ? '▲' : '◀' ) ; + this.forwardSymbol = options.forwardSymbol || ( this.isVertical ? '▼' : '▶' ) ; + + this.handleAttr = options.handleAttr || { bgColor: 'brightWhite' , color: 'black' } ; + this.handleSymbol = options.handleSymbol || '◆' ; + + this.barAttr = options.barAttr || { bgColor: 'gray' , color: 'brightWhite' } ; + this.barSymbol = options.barSymbol || ' ' ; + + this.backwardButton = this.forwardButton = null ; + + this.on( 'click' , this.onClick ) ; + this.on( 'drag' , this.onDrag ) ; + this.on( 'wheel' , this.onWheel ) ; + + this.initChildren() ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Slider' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Slider ; + +Slider.prototype = Object.create( Element.prototype ) ; +Slider.prototype.constructor = Slider ; +Slider.prototype.elementType = 'Slider' ; + +Slider.prototype.needInput = true ; + + + +// Unused ATM: no onKey registered +Slider.prototype.keyBindings = { + UP: 'backward' , + DOWN: 'forward' , + LEFT: 'backward' , + RIGHT: 'forward' , + PAGE_UP: 'backward' , + PAGE_DOWN: 'forward' , + ' ': 'forward' , + HOME: 'start' , + END: 'end' +} ; + + + +Slider.prototype.buttonKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' +} ; + + + +Slider.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'click' , this.onClick ) ; + this.off( 'drag' , this.onDrag ) ; + this.off( 'wheel' , this.onWheel ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +// Create Buttons automatically +Slider.prototype.initChildren = function() { + this.backwardButton = new Button( { + internal: true , + parent: this , + internalRole: 'backward' , + content: this.backwardSymbol , + outputX: this.outputX , + outputY: this.outputY , + blurAttr: this.buttonBlurAttr , + focusAttr: this.buttonFocusAttr , + //disabledAttr: this.buttonDisabledAttr , + submittedAttr: this.buttonSubmittedAttr , + keyBindings: this.buttonKeyBindings , + //shortcuts: def.shortcuts , + noDraw: true + } ) ; + + this.backwardButton.on( 'submit' , this.onButtonSubmit ) ; + + this.forwardButton = new Button( { + internal: true , + parent: this , + internalRole: 'forward' , + content: this.forwardSymbol , + outputX: this.isVertical ? this.outputX : this.outputX + this.outputWidth - 1 , + outputY: this.isVertical ? this.outputY + this.outputHeight - 1 : this.outputY , + blurAttr: this.buttonBlurAttr , + focusAttr: this.buttonFocusAttr , + //disabledAttr: this.buttonDisabledAttr , + submittedAttr: this.buttonSubmittedAttr , + keyBindings: this.buttonKeyBindings , + //shortcuts: def.shortcuts , + noDraw: true + } ) ; + + this.forwardButton.on( 'submit' , this.onButtonSubmit ) ; + + this.computeHandleOffset() ; +} ; + + + +Slider.prototype.setSizeAndPosition = function( options ) { + this.outputX = + options.outputX !== undefined ? options.outputX : + options.x !== undefined ? options.x : + this.outputX || 0 ; + this.outputY = + options.outputY !== undefined ? options.outputY : + options.y !== undefined ? options.y : + this.outputY || 0 ; + this.outputWidth = + options.outputWidth !== undefined ? options.outputWidth : + options.width !== undefined ? options.width : + this.outputWidth || 1 ; + this.outputHeight = + options.outputHeight !== undefined ? options.outputHeight : + options.height !== undefined ? options.height : + this.outputHeight || 1 ; + + this.backwardButton.outputX = this.outputX ; + this.backwardButton.outputY = this.outputY ; + this.forwardButton.outputX = this.isVertical ? this.outputX : this.outputX + this.outputWidth - 1 ; + this.forwardButton.outputY = this.isVertical ? this.outputY + this.outputHeight - 1 : this.outputY ; +} ; + + + +Slider.prototype.preDrawSelf = function() { + return this.isVertical ? this.preDrawSelfVertical() : this.preDrawSelfHorizontal() ; +} ; + + + +Slider.prototype.preDrawSelfVertical = function() { + var offset = 0 , + y = this.outputY + 1 , + yMax = this.outputY + this.outputHeight - 2 ; + + for ( ; y <= yMax ; y ++ , offset ++ ) { + if ( offset === this.handleOffset ) { + this.outputDst.put( { x: this.outputX , y , attr: this.handleAttr } , this.handleSymbol ) ; + } + else { + this.outputDst.put( { x: this.outputX , y , attr: this.barAttr } , this.barSymbol ) ; + } + } +} ; + + + +Slider.prototype.preDrawSelfHorizontal = function() { + var offset = 0 , + x = this.outputX + 1 , + xMax = this.outputX + this.outputWidth - 2 ; + + for ( ; x <= xMax ; x ++ , offset ++ ) { + if ( offset === this.handleOffset ) { + this.outputDst.put( { x , y: this.outputY , attr: this.handleAttr } , this.handleSymbol ) ; + } + else { + this.outputDst.put( { x , y: this.outputY , attr: this.barAttr } , this.barSymbol ) ; + } + } +} ; + + + +// No need to draw the cursor, however, when drawing, it is necessary to move it (not draw) at the handle position, +// in case no other widget would draw the cursor, it avoid the cursor to be hanging at the bottom and one cell off +// to the right of the slider, which is pretty annoying... +Slider.prototype.postDrawSelf = function() { + if ( this.isVertical ) { + this.outputDst.moveTo( this.outputX , this.outputY + this.handleOffset + 1 ) ; + } + else { + this.outputDst.moveTo( this.outputX + this.handleOffset + 1 , this.outputY ) ; + } +} ; + +/* +Slider.prototype.drawSelfCursor = function() { + // Move the cursor back to the handle + this.outputDst.moveTo( this.outputX , this.outputY + this.handleOffset + 1 ) ; + this.outputDst.drawCursor() ; +} ; +*/ + + + +// Compute the handle y position from the slideRate value +Slider.prototype.computeHandleOffset = function() { + var delta = ( this.isVertical ? this.outputHeight : this.outputWidth ) - 3 ; // minus the two buttons + this.handleOffset = Math.round( delta * this.slideRate ) ; +} ; + + + +// Set the handle position and compute the slideRate +Slider.prototype.setHandleOffset = function( offset , internalAndNoDraw = false ) { + var delta = ( this.isVertical ? this.outputHeight : this.outputWidth ) - 3 ; // minus the two buttons + + this.handleOffset = Math.max( 0 , Math.min( delta , Math.round( offset || 0 ) ) ) ; + this.slideRate = Math.max( 0 , Math.min( 1 , this.handleOffset / delta || 0 ) ) ; + + if ( ! internalAndNoDraw ) { + this.emit( 'slide' , this.getValue() ) ; + this.draw() ; + } +} ; + + + +Slider.prototype.setSlideRate = function( rate , internalAndNoDraw = false ) { + this.slideRate = Math.max( 0 , Math.min( 1 , rate || 0 ) ) ; + this.computeHandleOffset() ; + + if ( ! internalAndNoDraw ) { + this.emit( 'slide' , this.getValue() ) ; + this.draw() ; + } +} ; + + + +Slider.prototype.getHandleOffset = function() { return this.handleOffset ; } ; +Slider.prototype.getSlideRate = function() { return this.slideRate ; } ; + + + +Slider.prototype.onButtonSubmit = function( buttonValue , action , button ) { + switch ( button.internalRole ) { + case 'backward' : + this.emit( 'slideStep' , -1 ) ; + break ; + case 'forward' : + this.emit( 'slideStep' , 1 ) ; + break ; + } +} ; + + + +Slider.prototype.getValue = function() { + return this.rateToValue( this.slideRate ) ; +} ; + + + +Slider.prototype.setValue = function( value , internalAndNoDraw ) { + return this.setSlideRate( this.valueToRate( value ) , internalAndNoDraw ) ; +} ; + + + +Slider.prototype.onClick = function( data ) { + if ( ! this.hasFocus ) { this.document.giveFocusTo( this , 'select' ) ; } + this.setHandleOffset( ( this.isVertical ? data.y : data.x ) - 1 ) ; +} ; + + + +Slider.prototype.onDrag = function( data ) { + this.setHandleOffset( ( this.isVertical ? data.y : data.x ) - 1 ) ; +} ; + + + +Slider.prototype.onWheel = function( data ) { + this.emit( 'slideStep' , data.yDirection ) ; +} ; + + +},{"./Button.js":16,"./Element.js":23}],32:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; + + + +function Text( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + this.attr = options.attr || { bgColor: 'brightBlack' } ; + + this.leftPadding = options.leftPadding || '' ; + this.rightPadding = options.rightPadding || '' ; + + if ( ! Array.isArray( options.content ) ) { options.content = [ options.content || '' ] ; } + + // Usually done by the Element's constructor, but it's required now + this.content = options.content ; + this.contentHasMarkup = options.contentHasMarkup ; + + // For width and height, we centralize here works for sub-class having animations + if ( ! options.width ) { + options.width = this.computeRequiredWidth() ; + } + + options.height = this.computeRequiredHeight() ; + + Element.call( this , options ) ; + + if ( this.elementType === 'Text' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Text ; + +Text.prototype = Object.create( Element.prototype ) ; +Text.prototype.constructor = Text ; +Text.prototype.elementType = 'Text' ; + +Text.prototype.forceContentArray = true ; + + + +Text.prototype.computeRequiredWidth = function() { + return ( + Element.computeContentWidth( this.leftPadding , this.paddingHasMarkup ) + + Element.computeContentWidth( this.rightPadding , this.paddingHasMarkup ) + + ( this.animation ? + Math.max( ... this.animation.map( e => Element.computeContentWidth( e , this.contentHasMarkup ) ) ) : + Element.computeContentWidth( this.content , this.contentHasMarkup ) || 1 + ) + ) ; +} ; + + + +Text.prototype.computeRequiredHeight = function() { + return ( + this.animation ? + Math.max( ... this.animation.map( e => e.length ) ) : + this.content.length + ) ; +} ; + + + +Text.prototype.resizeOnContent = function() { + this.width = this.computeRequiredWidth( this.content , this.contentHasMarkup ) ; + this.height = this.computeRequiredHeight( this.content , this.contentHasMarkup ) ; +} ; + + + +Text.prototype.postDrawSelf = function() { + if ( ! this.outputDst ) { return this ; } + + var gap , resumeAttr , lineNumber , contentLine ; + + for ( lineNumber = 0 ; lineNumber < this.outputHeight ; lineNumber ++ ) { + contentLine = this.content[ lineNumber ] || '' ; + + // Prepare the cursor position, since we will not "put at" + // We use cx/cy directly because we can violate bounds limit, and let .put() clip things away + this.outputDst.cx = this.outputX ; + this.outputDst.cy = this.outputY + lineNumber ; + //this.outputDst.moveTo( this.outputX , this.outputY + lineNumber ) ; + + // Write the left padding + if ( this.leftPadding ) { + this.outputDst.put( { attr: this.attr , markup: this.paddingHasMarkup } , this.leftPadding ) ; + } + + // Write the content + resumeAttr = this.outputDst.put( { attr: this.attr , resumeAttr: resumeAttr , markup: this.contentHasMarkup } , contentLine ) ; + + // Write the right padding + if ( this.rightPadding ) { + this.outputDst.put( { attr: this.attr , markup: this.paddingHasMarkup } , this.rightPadding ) ; + } + + // NOTE: this.outputDst.cx is not incremented when it is already at the last column, + // so the "gap" formula only works if lesser than width - 1 + if ( this.outputDst.cx < this.outputDst.width - 1 ) { + gap = this.outputX + this.outputWidth - this.outputDst.cx ; + + if ( gap > 0 ) { + this.outputDst.put( { attr: this.attr } , ' '.repeat( gap ) ) ; + } + } + } +} ; + + +},{"./Element.js":23}],33:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Slider = require( './Slider.js' ) ; + +const ScreenBuffer = require( '../ScreenBuffer.js' ) ; +const TextBuffer = require( '../TextBuffer.js' ) ; +const Rect = require( '../Rect.js' ) ; + +const string = require( 'string-kit' ) ; + + + +function TextBox( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + this.onKey = this.onKey.bind( this ) ; + this.onClick = this.onClick.bind( this ) ; + this.onDrag = this.onDrag.bind( this ) ; + this.onWheel = this.onWheel.bind( this ) ; + this.onParentResize = this.onParentResize.bind( this ) ; + + if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; } + + this.textAttr = options.textAttr || options.attr || { bgColor: 'default' } ; + this.altTextAttr = options.altTextAttr || Object.assign( {} , this.textAttr , { color: 'gray' , italic: true } ) ; + this.voidAttr = options.voidAttr || options.emptyAttr || options.attr || { bgColor: 'default' } ; + + this.scrollable = !! options.scrollable ; + this.hasVScrollBar = this.scrollable && !! options.vScrollBar ; + this.hasHScrollBar = this.scrollable && !! options.hScrollBar ; + this.scrollX = options.scrollX || 0 ; + this.scrollY = options.scrollY || 0 ; + + // false: scroll down to the bottom of the content, both content bottom and textBox bottom on the same cell + // true: scroll down until the bottom of the content reaches the top of the textBox + this.extraScrolling = !! options.extraScrolling ; + + // Right shift of the first-line, may be useful for prompt, or continuing another box in the flow + this.firstLineRightShift = options.firstLineRightShift || 0 ; + + this.wordWrap = !! ( options.wordWrap || options.wordwrap ) ; + this.lineWrap = !! ( options.lineWrap || this.wordWrap ) ; + + this.hiddenContent = options.hiddenContent ; + + this.stateMachine = options.stateMachine ; + + this.textAreaWidth = this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth ; + this.textAreaHeight = this.hasHScrollBar ? this.outputHeight - 1 : this.outputHeight ; + + this.textBuffer = null ; + this.altTextBuffer = null ; + this.vScrollBarSlider = null ; + this.hScrollBarSlider = null ; + + this.on( 'key' , this.onKey ) ; + this.on( 'click' , this.onClick ) ; + this.on( 'drag' , this.onDrag ) ; + this.on( 'wheel' , this.onWheel ) ; + this.on( 'parentResize' , this.onParentResize ) ; + + this.initChildren() ; + + if ( this.setContent === TextBox.prototype.setContent ) { + this.setContent( options.content , options.contentHasMarkup , true ) ; + } + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'TextBox' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = TextBox ; + +TextBox.prototype = Object.create( Element.prototype ) ; +TextBox.prototype.constructor = TextBox ; +TextBox.prototype.elementType = 'TextBox' ; + + + +// Support for strictInline mode +TextBox.prototype.strictInlineSupport = true ; + + + +TextBox.prototype.destroy = function( isSubDestroy , noDraw = false ) { + if ( this.destroyed ) { return ; } + + this.off( 'key' , this.onKey ) ; + this.off( 'click' , this.onClick ) ; + this.off( 'drag' , this.onDrag ) ; + this.off( 'wheel' , this.onWheel ) ; + this.off( 'parentResize' , this.onParentResize ) ; + + Element.prototype.destroy.call( this , isSubDestroy , noDraw ) ; +} ; + + + +TextBox.prototype.keyBindings = { + UP: 'tinyScrollUp' , + DOWN: 'tinyScrollDown' , + PAGE_UP: 'scrollUp' , + PAGE_DOWN: 'scrollDown' , + ' ': 'scrollDown' , + HOME: 'scrollTop' , + END: 'scrollBottom' , + LEFT: 'scrollLeft' , + RIGHT: 'scrollRight' , + CTRL_O: 'copyClipboard' +} ; + + + +TextBox.prototype.initChildren = function() { + this.textBuffer = new TextBuffer( { + dst: this.outputDst , + //palette: this.document.palette , + x: this.outputX , + y: this.outputY , + //width: this.textAreaWidth , + //height: this.textAreaHeight + firstLineRightShift: this.firstLineRightShift , + lineWrapWidth: this.lineWrap ? this.textAreaWidth : null , + wordWrap: this.wordWrap , + dstClipRect: { + x: this.outputX , + y: this.outputY , + width: this.textAreaWidth , + height: this.textAreaHeight + } , + hidden: this.hiddenContent , + forceInBound: true , + stateMachine: this.stateMachine + } ) ; + + this.setAttr( undefined , undefined , true ) ; + + + if ( this.useAltTextBuffer ) { + this.altTextBuffer = new TextBuffer( { + firstLineRightShift: this.firstLineRightShift , + lineWrapWidth: this.lineWrap ? this.textAreaWidth : null , + wordWrap: this.wordWrap , + dstClipRect: { + x: this.outputX , + y: this.outputY , + width: this.textAreaWidth , + height: this.textAreaHeight + } + //, stateMachine: this.stateMachine + } ) ; + + this.setAltAttr() ; + this.textBuffer.setVoidTextBuffer( this.altTextBuffer ) ; + } + + + if ( this.hasVScrollBar ) { + this.vScrollBarSlider = new Slider( { + internal: true , + parent: this , + x: this.outputX + this.outputWidth - 1 , + y: this.outputY , + height: this.outputHeight , + isVertical: true , + valueToRate: scrollY => -scrollY / Math.max( 1 , this.textBuffer.buffer.length - this.textAreaHeight ) , + rateToValue: rate => -rate * Math.max( 1 , this.textBuffer.buffer.length - this.textAreaHeight ) , + noDraw: true + } ) ; + + this.vScrollBarSlider.on( 'slideStep' , d => this.scroll( 0 , -d * Math.ceil( this.textAreaHeight / 2 ) ) ) ; + this.vScrollBarSlider.on( 'slide' , value => this.scrollTo( null , value ) ) ; + } + + if ( this.hasHScrollBar ) { + this.hScrollBarSlider = new Slider( { + internal: true , + parent: this , + x: this.outputX , + y: this.outputY + this.outputHeight - 1 , + width: this.outputWidth - this.hasVScrollBar , + valueToRate: scrollX => { + var lineWidth = this.textBuffer.getContentSize().width ; + return -scrollX / Math.max( 1 , lineWidth - this.textAreaWidth ) ; + } , + rateToValue: rate => { + var lineWidth = this.textBuffer.getContentSize().width ; + return -rate * Math.max( 1 , lineWidth - this.textAreaWidth ) ; + } , + noDraw: true + } ) ; + + this.hScrollBarSlider.on( 'slideStep' , d => this.scroll( -d * Math.ceil( this.textAreaWidth / 2 ) , 0 ) ) ; + this.hScrollBarSlider.on( 'slide' , value => this.scrollTo( value , null ) ) ; + } +} ; + + + +TextBox.prototype.setSizeAndPosition = function( options ) { + this.outputX = + options.outputX !== undefined ? options.outputX : + options.x !== undefined ? options.x : + this.outputX || 0 ; + this.outputY = + options.outputY !== undefined ? options.outputY : + options.y !== undefined ? options.y : + this.outputY || 0 ; + this.outputWidth = + options.outputWidth !== undefined ? options.outputWidth : + options.width !== undefined ? options.width : + this.outputWidth || 1 ; + this.outputHeight = + options.outputHeight !== undefined ? options.outputHeight : + options.height !== undefined ? options.height : + this.outputHeight || 1 ; + + this.textAreaWidth = this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth ; + this.textAreaHeight = this.hasHScrollBar ? this.outputHeight - 1 : this.outputHeight ; + + this.textBuffer.lineWrapWidth = this.lineWrap ? this.textAreaWidth : null ; + if ( this.altTextBuffer ) { this.altTextBuffer.lineWrapWidth = this.lineWrap ? this.textAreaWidth : null ; } + + this.textBuffer.x = this.outputX ; + this.textBuffer.y = this.outputY ; + + this.textBuffer.dstClipRect = new Rect( { + x: this.outputX , + y: this.outputY , + width: this.textAreaWidth , + height: this.textAreaHeight + } ) ; + + // Update word-wrap + if ( this.lineWrap ) { + this.textBuffer.wrapAllLines() ; + if ( this.altTextBuffer ) { this.altTextBuffer.wrapAllLines() ; } + } + + if ( this.vScrollBarSlider ) { + this.vScrollBarSlider.setSizeAndPosition( { + outputX: this.outputX + this.outputWidth - 1 , + outputY: this.outputY , + outputHeight: this.outputHeight + } ) ; + } + + if ( this.hScrollBarSlider ) { + this.hScrollBarSlider.setSizeAndPosition( { + outputX: this.outputX , + outputY: this.outputY + this.outputHeight - 1 , + outputWidth: this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth + } ) ; + } +} ; + + + +TextBox.prototype.preDrawSelf = function() { + // It's best to force the dst now, because it avoids to set textBuffer.dst everytime it changes, + // and it could be changed by userland (so hard to keep it in sync without setters/getters) + this.textBuffer.draw( { dst: this.outputDst } ) ; +} ; + + + +TextBox.prototype.scroll = function( dx , dy , dontDraw = false ) { + return this.scrollTo( dx ? this.scrollX + dx : null , dy ? this.scrollY + dy : null , dontDraw ) ; +} ; + + + +TextBox.prototype.scrollToTop = function( dontDraw = false ) { + return this.scrollTo( null , 0 , dontDraw ) ; +} ; + + + +TextBox.prototype.scrollToBottom = function( dontDraw = false ) { + // Ignore extra scrolling here + return this.scrollTo( null , this.textAreaHeight - this.textBuffer.buffer.length , dontDraw ) ; +} ; + + + +TextBox.prototype.scrollTo = function( x , y , noDraw = false ) { + if ( ! this.scrollable ) { return ; } + + if ( x !== undefined && x !== null ) { + // Got a +1 after content size because of the word-wrap thing and eventual invisible \n + this.scrollX = Math.min( 0 , Math.max( Math.round( x ) , + ( this.extraScrolling ? 1 : this.textAreaWidth ) - this.textBuffer.getContentSize().width + 1 + ) ) ; + + this.textBuffer.x = this.outputX + this.scrollX ; + } + + if ( y !== undefined && y !== null ) { + this.scrollY = Math.min( 0 , Math.max( Math.round( y ) , + ( this.extraScrolling ? 1 : this.textAreaHeight ) - this.textBuffer.buffer.length + ) ) ; + + this.textBuffer.y = this.outputY + this.scrollY ; + } + + if ( this.vScrollBarSlider ) { + this.vScrollBarSlider.setValue( this.scrollY , true ) ; + } + + if ( this.hScrollBarSlider ) { + this.hScrollBarSlider.setValue( this.scrollX , true ) ; + } + + if ( ! noDraw ) { this.draw() ; } +} ; + + + +TextBox.prototype.autoScrollAndDraw = function( onlyDrawCursor = false ) { + var x , y ; + + // We use cx-1 because at least we want to see the char just before the cursor (backspace, etc...) + // But do nothing if there is no scrolling yet (do not set x to 0 if it's unnecessary) + if ( this.textBuffer.cx - 1 < -this.scrollX && this.scrollX !== 0 ) { + x = -Math.max( 0 , this.textBuffer.cx - 1 ) ; + } + else if ( this.textBuffer.cx > this.textAreaWidth - this.scrollX - 1 ) { + x = this.textAreaWidth - 1 - this.textBuffer.cx ; + } + + if ( this.textBuffer.cy < -this.scrollY ) { + y = -this.textBuffer.cy ; + } + else if ( this.textBuffer.cy > this.textAreaHeight - this.scrollY - 1 ) { + y = this.textAreaHeight - 1 - this.textBuffer.cy ; + } + + if ( x !== undefined || y !== undefined ) { + // .scrollTo() call .draw(), so no need to do that here... + this.scrollTo( x , y ) ; + } + else if ( ! onlyDrawCursor ) { + this.draw() ; + } + else { + this.drawCursor() ; + } +} ; + + + +TextBox.prototype.autoScrollAndDrawCursor = function() { return this.autoScrollAndDraw( true ) ; } ; + + + +TextBox.prototype.setAttr = function( textAttr = this.textAttr , voidAttr = this.voidAttr , dontDraw = false , dontSetContent = false ) { + this.textAttr = textAttr ; + this.voidAttr = voidAttr ; + this.textBuffer.setDefaultAttr( this.textAttr ) ; + this.textBuffer.setVoidAttr( this.voidAttr ) ; + + if ( ! dontSetContent ) { this.setContent( this.content , this.contentHasMarkup , dontDraw ) ; } +} ; + + + +TextBox.prototype.setAltAttr = function( altTextAttr = this.altTextAttr ) { + this.altTextAttr = altTextAttr ; + this.altTextBuffer.setDefaultAttr( this.altTextAttr ) ; + this.altTextBuffer.setVoidAttr( this.voidAttr ) ; +} ; + + + +TextBox.prototype.getContentSize = function() { return this.textBuffer.getContentSize() ; } ; +TextBox.prototype.getContent = function() { return this.textBuffer.getText() ; } ; + + + +TextBox.prototype.setContent = function( content , hasMarkup , dontDraw ) { + var contentSize ; + + if ( typeof content !== 'string' ) { + if ( content === null || content === undefined ) { content = '' ; } + else { content = '' + content ; } + } + + this.content = content ; + this.contentHasMarkup = hasMarkup ; + + this.textBuffer.setText( this.content , this.contentHasMarkup , this.textAttr ) ; + + if ( this.stateMachine ) { + this.textBuffer.runStateMachine() ; + } + + // Move the cursor at the end of the input + this.textBuffer.moveToEndOfBuffer() ; + + if ( ! dontDraw ) { + this.drawCursor() ; + this.redraw() ; + } +} ; + + + +// Get content for alternate textBuffer +TextBox.prototype.getAltContent = function() { + if ( ! this.altTextBuffer ) { return null ; } + return this.altTextBuffer.getText() ; +} ; + + + +// Set content for alternate textBuffer +TextBox.prototype.setAltContent = function( content , hasMarkup , dontDraw ) { + if ( ! this.altTextBuffer ) { return ; } + + var contentSize ; + + if ( typeof content !== 'string' ) { + if ( content === null || content === undefined ) { content = '' ; } + else { content = '' + content ; } + } + + //this.content = content ; + //this.contentHasMarkup = hasMarkup ; + + this.altTextBuffer.setText( content , hasMarkup , this.altTextAttr ) ; + + //if ( this.stateMachine ) { this.altTextBuffer.runStateMachine() ; } + + if ( ! dontDraw ) { + this.drawCursor() ; + this.redraw() ; + } +} ; + + + +TextBox.prototype.prependContent = function( content , dontDraw ) { return this.addContent( content , 'prepend' , dontDraw ) ; } ; +TextBox.prototype.appendContent = function( content , dontDraw ) { return this.addContent( content , 'append' , dontDraw ) ; } ; +TextBox.prototype.appendLog = function( content , dontDraw ) { return this.addContent( content , 'appendLog' , dontDraw ) ; } ; + + + +TextBox.prototype.addContent = function( content , mode , dontDraw ) { + var contentSize , scroll = false ; + + if ( typeof content !== 'string' ) { + if ( content === null || content === undefined ) { content = '' ; } + else { content = '' + content ; } + } + + switch ( mode ) { + case 'prepend' : + this.content = content + this.content ; + this.textBuffer.prepend( content , this.contentHasMarkup , this.textAttr ) ; + break ; + case 'appendLog' : + scroll = this.textBuffer.buffer.length <= this.textAreaHeight || this.scrollY <= this.textAreaHeight - this.textBuffer.buffer.length ; + content = '\n' + content ; + this.content += content ; + this.textBuffer.append( content , this.contentHasMarkup , this.textAttr ) ; + break ; + case 'append' : + default : + this.content += content ; + this.textBuffer.append( content , this.contentHasMarkup , this.textAttr ) ; + break ; + } + + if ( this.stateMachine ) { + this.textBuffer.runStateMachine() ; + } + + // Move the cursor at the end of the input + this.textBuffer.moveToEndOfBuffer() ; + + if ( scroll ) { + this.scrollToBottom( dontDraw ) ; + } + else if ( ! dontDraw ) { + // Set it again to the scrollY value: it forces re-computing of the slider rate depending on new content + if ( this.vScrollBarSlider ) { + this.vScrollBarSlider.setValue( this.scrollY , true ) ; + } + + this.drawCursor() ; + this.draw() ; + //this.redraw() ; + } +} ; + + + +TextBox.prototype.onKey = function( key , trash , data ) { + switch( this.keyBindings[ key ] ) { + case 'tinyScrollUp' : + this.scroll( 0 , Math.ceil( this.textAreaHeight / 5 ) ) ; + break ; + + case 'tinyScrollDown' : + this.scroll( 0 , -Math.ceil( this.textAreaHeight / 5 ) ) ; + break ; + + case 'scrollUp' : + this.scroll( 0 , Math.ceil( this.textAreaHeight / 2 ) ) ; + break ; + + case 'scrollDown' : + this.scroll( 0 , -Math.ceil( this.textAreaHeight / 2 ) ) ; + break ; + + case 'scrollLeft' : + this.scroll( Math.ceil( this.textAreaWidth / 2 ) , 0 ) ; + break ; + + case 'scrollRight' : + this.scroll( -Math.ceil( this.textAreaWidth / 2 ) , 0 ) ; + break ; + + case 'scrollTop' : + this.scrollToTop() ; + break ; + + case 'scrollBottom' : + this.scrollToBottom() ; + break ; + + case 'copyClipboard' : + if ( this.document ) { + this.document.setClipboard( this.textBuffer.getSelectionText() ).catch( () => undefined ) ; + } + break ; + + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +TextBox.prototype.onClick = function( data ) { + // It is susceptible to click event only when it is scrollable + if ( this.scrollable && ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } +} ; + + + +TextBox.prototype.onWheel = function( data ) { + // It's a "tiny" scroll + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + + if ( this.scrollable ) { + this.scroll( 0 , -data.yDirection * Math.ceil( this.textAreaHeight / 5 ) ) ; + } +} ; + + + +TextBox.prototype.onDrag = function( data ) { + var xmin , ymin , xmax , ymax ; + + if ( ! this.hasFocus ) { + this.document.giveFocusTo( this , 'select' ) ; + } + + if ( data.yFrom < data.y || ( data.yFrom === data.y && data.xFrom <= data.x ) ) { + ymin = data.yFrom ; + ymax = data.y ; + xmin = data.xFrom ; + xmax = data.x ; + } + else { + ymin = data.y ; + ymax = data.yFrom ; + xmin = data.x ; + xmax = data.xFrom ; + } + + this.textBuffer.setSelectionRegion( { + xmin: xmin - this.scrollX , + xmax: xmax - this.scrollX , + ymin: ymin - this.scrollY , + ymax: ymax - this.scrollY + } ) ; + + if ( this.document ) { + this.document.setClipboard( this.textBuffer.getSelectionText() , 'primary' ).catch( () => undefined ) ; + } + + this.draw() ; +} ; + + + +TextBox.prototype.onParentResize = function() { + if ( ! this.autoWidth && ! this.autoHeight ) { return ; } + + var options = {} ; + + if ( this.autoWidth ) { + options.outputWidth = Math.round( this.outputDst.width * this.autoWidth ) ; + } + + if ( this.autoHeight ) { + options.outputHeight = Math.round( this.outputDst.height * this.autoHeight ) ; + } + + this.setSizeAndPosition( options ) ; + this.draw() ; +} ; + + +},{"../Rect.js":2,"../ScreenBuffer.js":3,"../TextBuffer.js":6,"./Element.js":23,"./Slider.js":31,"string-kit":123}],34:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const TextBox = require( './TextBox.js' ) ; +const boxesChars = require( '../spChars.js' ).box ; + + + +function TextTable( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + Element.call( this , options ) ; + + this.cellContents = options.cellContents ; // Should be an array of array of text + this.contentHasMarkup = options.contentHasMarkup ; + + this.textBoxes = null ; // Same format: array of array of textBoxes + this.rowCount = 0 ; + this.columnCount = 0 ; + + this.rowHeights = [] ; + this.columnWidths = [] ; + + this.textAttr = options.textAttr || { bgColor: 'default' } ; + this.voidAttr = options.voidAttr || options.emptyAttr || null ; + + this.firstRowTextAttr = options.firstRowTextAttr || null ; + this.firstRowVoidAttr = options.firstRowVoidAttr || null ; + this.evenRowTextAttr = options.evenRowTextAttr || null ; + this.evenRowVoidAttr = options.evenRowVoidAttr || null ; + + this.firstColumnTextAttr = options.firstColumnTextAttr || null ; + this.firstColumnVoidAttr = options.firstColumnVoidAttr || null ; + this.evenColumnTextAttr = options.evenColumnTextAttr || null ; + this.evenColumnVoidAttr = options.evenColumnVoidAttr || null ; + + this.firstCellTextAttr = options.firstCellTextAttr || null ; + this.firstCellVoidAttr = options.firstCellVoidAttr || null ; + + // When rowNumber AND columnNumber are both even + this.evenCellTextAttr = options.evenCellTextAttr || null ; + this.evenCellVoidAttr = options.evenCellVoidAttr || null ; + + // Checker-like: when rowNumber + columnNumber is even + this.checkerEvenCellTextAttr = options.checkerEvenCellTextAttr || null ; + this.checkerEvenCellVoidAttr = options.checkerEvenCellVoidAttr || null ; + + /* + // Select attr + // /!\ NOT IMPLEMENTED YET /!\ + // Would allow one to navigate the table (it could be useful for making editable cells) + this.selectedTextAttr = options.selectedTextAttr || null ; + this.selectedVoidAttr = options.selectedVoidAttr || null ; + this.selectable = options.selectable || null ; // Can be 'row', 'column' or 'cell' + this.selectedX = this.selectedY = 0 ; + */ + + this.expandToWidth = options.expandToWidth !== undefined ? !! options.expandToWidth : !! options.fit ; + this.shrinkToWidth = options.shrinkToWidth !== undefined ? !! options.shrinkToWidth : !! options.fit ; + this.expandToHeight = + options.expandToHeight !== undefined ? !! options.expandToHeight : + ! options.height ? false : + !! options.fit ; + this.shrinkToHeight = + options.shrinkToHeight !== undefined ? !! options.shrinkToHeight : + ! options.height ? false : + !! options.fit ; + this.wordWrap = options.wordWrap !== undefined || options.wordwrap !== undefined ? + !! ( options.wordWrap || options.wordwrap ) : !! options.fit ; + this.lineWrap = this.wordWrap || ( options.lineWrap !== undefined ? !! options.lineWrap : !! options.fit ) ; + + this.hasBorder = options.hasBorder !== undefined ? !! options.hasBorder : true ; // Default to true + this.borderAttr = options.borderAttr || this.textAttr ; + this.borderChars = boxesChars.light ; + + if ( typeof options.borderChars === 'object' ) { + this.borderChars = boxesChars.__fix__( options.borderChars ) ; + } + else if ( typeof options.borderChars === 'string' && boxesChars[ options.borderChars ] ) { + this.borderChars = boxesChars[ options.borderChars ] ; + } + + if ( options.textBoxKeyBindings ) { this.textBoxKeyBindings = options.textBoxKeyBindings ; } + + this.initChildren() ; + this.computeCells() ; + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'TextTable' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = TextTable ; + +TextTable.prototype = Object.create( Element.prototype ) ; +TextTable.prototype.constructor = TextTable ; +TextTable.prototype.elementType = 'TextTable' ; + + + +// Support for strictInline mode +TextTable.prototype.strictInlineSupport = true ; +TextTable.prototype.staticInline = true ; + + + +TextTable.prototype.textBoxKeyBindings = TextBox.prototype.keyBindings ; + + + +TextTable.prototype.setCellContent = function( x , y , content , dontDraw = false , dontUpdateLayout = false ) { + var textBox = this.textBoxes[ y ] && this.textBoxes[ y ][ x ] ; + + // We don't add cell, it should already exists + if ( ! textBox ) { return ; } + + // Save cell content + this.cellContents[ y ][ x ] = content ; + textBox.setContent( content , this.contentHasMarkup , true ) ; + + if ( ! dontUpdateLayout ) { + this.computeCells() ; + if ( ! dontDraw ) { this.draw() ; } + } + else { + if ( ! dontDraw ) { textBox.draw() ; } + } +} ; + + + +TextTable.prototype.setCellAttr = function( x , y , textAttr , voidAttr , dontDraw = false ) { + var textBox = this.textBoxes[ y ] && this.textBoxes[ y ][ x ] ; + if ( ! textBox ) { return ; } + + if ( voidAttr === undefined ) { voidAttr = textAttr ; } + + textBox.setAttr( textAttr , voidAttr , dontDraw ) ; +} ; + + + +TextTable.prototype.resetCellAttr = function( x , y , dontDraw = false ) { + var textBox = this.textBoxes[ y ] && this.textBoxes[ y ][ x ] ; + if ( ! textBox ) { return ; } + + var textAttr = this.getTextAttrForCell( x , y ) , + voidAttr = this.getVoidAttrForCell( x , y , textAttr ) ; + + textBox.setAttr( textAttr , voidAttr , dontDraw ) ; +} ; + + + +TextTable.prototype.setRowAttr = function( y , textAttr , voidAttr , dontDraw = false ) { + for ( let x = 0 ; x < this.columnCount ; x ++ ) { this.setCellAttr( x , y , textAttr , voidAttr , true ) ; } + if ( ! dontDraw ) { this.draw() ; } +} ; + + + +TextTable.prototype.resetRowAttr = function( y , dontDraw = false ) { + for ( let x = 0 ; x < this.columnCount ; x ++ ) { this.resetCellAttr( x , y , true ) ; } + if ( ! dontDraw ) { this.draw() ; } +} ; + + + +TextTable.prototype.setColumnAttr = function( x , textAttr , voidAttr , dontDraw = false ) { + for ( let y = 0 ; y < this.rowCount ; y ++ ) { this.setCellAttr( x , y , textAttr , voidAttr , true ) ; } + if ( ! dontDraw ) { this.draw() ; } +} ; + + + +TextTable.prototype.resetColumnAttr = function( x , dontDraw = false ) { + for ( let y = 0 ; y < this.rowCount ; y ++ ) { this.resetCellAttr( x , y , true ) ; } + if ( ! dontDraw ) { this.draw() ; } +} ; + + + +TextTable.prototype.setTableAttr = function( textAttr , voidAttr , dontDraw = false ) { + for ( let y = 0 ; y < this.rowCount ; y ++ ) { + for ( let x = 0 ; x < this.columnCount ; x ++ ) { this.setCellAttr( x , y , textAttr , voidAttr , true ) ; } + } + + if ( ! dontDraw ) { this.draw() ; } +} ; + + + +TextTable.prototype.resetTableAttr = function( dontDraw = false ) { + for ( let y = 0 ; y < this.rowCount ; y ++ ) { + for ( let x = 0 ; x < this.columnCount ; x ++ ) { this.resetCellAttr( x , y , true ) ; } + } + + if ( ! dontDraw ) { this.draw() ; } +} ; + + + +TextTable.prototype.getTextAttrForCell = function( x , y ) { + return this.firstCellTextAttr && ! x && ! y ? this.firstCellTextAttr : + this.firstRowTextAttr && ! y ? this.firstRowTextAttr : + this.firstColumnTextAttr && ! x ? this.firstColumnTextAttr : + this.evenCellTextAttr && ! ( x % 2 ) && ! ( y % 2 ) ? this.evenCellTextAttr : + this.checkerEvenCellTextAttr && ! ( ( x + y ) % 2 ) ? this.checkerEvenCellTextAttr : + this.evenRowTextAttr && ! ( y % 2 ) ? this.evenRowTextAttr : + this.evenColumnTextAttr && ! ( y % 2 ) ? this.evenColumnTextAttr : + this.textAttr ; +} ; + + + +TextTable.prototype.getVoidAttrForCell = function( x , y , textAttr ) { + return this.firstCellVoidAttr && ! x && ! y ? this.firstCellVoidAttr : + this.firstRowVoidAttr && ! y ? this.firstRowVoidAttr : + this.firstColumnVoidAttr && ! x ? this.firstColumnVoidAttr : + this.evenCellVoidAttr && ! ( x % 2 ) && ! ( y % 2 ) ? this.evenCellVoidAttr : + this.checkerEvenCellVoidAttr && ! ( ( x + y ) % 2 ) ? this.checkerEvenCellVoidAttr : + this.evenRowVoidAttr && ! ( y % 2 ) ? this.evenRowVoidAttr : + this.evenColumnVoidAttr && ! ( y % 2 ) ? this.evenColumnVoidAttr : + this.voidAttr || textAttr ; +} ; + + + +TextTable.prototype.initChildren = function() { + var row , cellContent , textAttr , voidAttr ; + + this.rowCount = this.cellContents.length ; + this.columnCount = 0 ; + this.textBoxes = [] ; + + var x = 0 , y = 0 ; + + for ( row of this.cellContents ) { + this.textBoxes[ y ] = [] ; + x = 0 ; + + for ( cellContent of row ) { + if ( x >= this.columnCount ) { this.columnCount = x + 1 ; } + + textAttr = this.getTextAttrForCell( x , y ) ; + voidAttr = this.getVoidAttrForCell( x , y , textAttr ) ; + + this.textBoxes[ y ][ x ] = new TextBox( { + internal: true , + parent: this , + content: cellContent , + contentHasMarkup: this.contentHasMarkup , + //value: cellContent , + x: this.outputX , + y: this.outputY , + width: this.outputWidth , + height: this.outputHeight , + lineWrap: this.lineWrap , + wordWrap: this.wordWrap , + //scrollable: !! this.scrollable , + //vScrollBar: !! this.vScrollBar , + //hScrollBar: !! this.hScrollBar , + //hiddenContent: this.hiddenContent , + textAttr , + voidAttr , + keyBindings: this.textBoxKeyBindings , + noDraw: true + } ) ; + + x ++ ; + } + + y ++ ; + } +} ; + + + +TextTable.prototype.computeCells = function() { + var shrinked = this.computeColumnWidths() ; + + if ( shrinked ) { + this.textBoxesWordWrap() ; + //this.computeColumnWidths() ; + } + + this.computeRowHeights() ; + this.textBoxesSizeAndPosition() ; +} ; + + + +TextTable.prototype.computeColumnWidths = function() { + var x , y , textBox , max , width ; + + this.contentWidth = + this.hasBorder ; // +true = 1 + + for ( x = 0 ; x < this.columnCount ; x ++ ) { + max = 0 ; + for ( y = 0 ; y < this.rowCount ; y ++ ) { + textBox = this.textBoxes[ y ][ x ] ; + if ( ! textBox ) { continue ; } + width = textBox.getContentSize().width || 1 ; + if ( width > max ) { max = width ; } + } + this.columnWidths[ x ] = max ; + this.contentWidth += max + this.hasBorder ; // +true = 1 + } + + if ( this.expandToWidth && this.contentWidth < this.outputWidth ) { + this.expand( this.contentWidth , this.outputWidth , this.columnWidths ) ; + } + else if ( this.shrinkToWidth && this.contentWidth > this.outputWidth ) { + this.shrink( this.contentWidth , this.outputWidth , this.columnWidths ) ; + return true ; + } + + return false ; +} ; + + + +TextTable.prototype.computeRowHeights = function() { + var x , y , textBox , max , height ; + + this.contentHeight = + this.hasBorder ; // +true = 1 + + for ( y = 0 ; y < this.rowCount ; y ++ ) { + max = 0 ; + for ( x = 0 ; x < this.columnCount ; x ++ ) { + textBox = this.textBoxes[ y ][ x ] ; + if ( ! textBox ) { continue ; } + height = textBox.getContentSize().height || 1 ; + if ( height > max ) { max = height ; } + } + this.rowHeights[ y ] = max ; + this.contentHeight += max + this.hasBorder ; // +true = 1 + } + + if ( this.expandToHeight && this.contentHeight < this.outputHeight ) { + this.expand( this.contentHeight , this.outputHeight , this.rowHeights ) ; + } + else if ( this.shrinkToHeight && this.contentHeight > this.outputHeight ) { + this.shrink( this.contentHeight , this.outputHeight , this.rowHeights ) ; + return true ; + } +} ; + + + +// Expand an array of size, using proportional expansion +TextTable.prototype.expand = function( contentSize , outputSize , sizeArray ) { + var x , + floatSize = 0 , + remainder = 0 , + count = sizeArray.length , + noBorderWantedSize = outputSize - ( this.hasBorder ? count + 1 : 0 ) ; + + if ( noBorderWantedSize <= 0 ) { return ; } + + var noBorderSize = contentSize - ( this.hasBorder ? count + 1 : 0 ) , + rate = noBorderWantedSize / noBorderSize ; + + // Adjust from left to right + for ( x = 0 ; x < count ; x ++ ) { + floatSize = sizeArray[ x ] * rate + remainder ; + sizeArray[ x ] = Math.max( 1 , Math.round( floatSize ) ) ; + remainder = floatSize - sizeArray[ x ] ; + } +} ; + + + +// Shrink an array of size, larger are shrinked first +TextTable.prototype.shrink = function( contentSize , outputSize , sizeArray ) { + var x , max , + secondMax = 0 , + maxIndexes = [] , + count = sizeArray.length , + floatColumnDelta , columnDelta , partialColumn , + delta = contentSize - outputSize ; + + //console.log( contentSize , outputSize , delta ) ; + + while ( delta > 0 ) { + max = 0 ; + secondMax = 0 ; + maxIndexes.length = 0 ; + + for ( x = 0 ; x < count ; x ++ ) { + if ( sizeArray[ x ] > max ) { + secondMax = max ; + max = sizeArray[ x ] ; + maxIndexes.length = 0 ; + maxIndexes.push( x ) ; + } + else if ( sizeArray[ x ] === max ) { + maxIndexes.push( x ) ; + } + else if ( sizeArray[ x ] > secondMax ) { + secondMax = sizeArray[ x ] ; + } + } + + // We can't shrink anymore + // /!\ we should probably test that before entering the loop! + if ( ! max ) { return ; } + + floatColumnDelta = Math.min( max - secondMax , delta / maxIndexes.length ) ; + columnDelta = Math.floor( floatColumnDelta ) ; + + if ( columnDelta >= 0 ) { + for ( let index of maxIndexes ) { + sizeArray[ index ] -= columnDelta ; + delta -= columnDelta ; + } + } + + if ( columnDelta !== floatColumnDelta ) { + partialColumn = delta % maxIndexes.length ; + for ( let i = 0 ; i < maxIndexes.length && i < partialColumn ; i ++ ) { + sizeArray[ maxIndexes[ i ] ] -- ; + } + delta -= partialColumn ; + } + } +} ; + + + +TextTable.prototype.textBoxesWordWrap = function() { + var x , y , textBox ; + + for ( y = 0 ; y < this.rowCount ; y ++ ) { + for ( x = 0 ; x < this.columnCount ; x ++ ) { + textBox = this.textBoxes[ y ][ x ] ; + + if ( textBox ) { + textBox.setSizeAndPosition( { + outputX: this.outputX , + outputY: this.outputY , + outputWidth: this.columnWidths[ x ] , + outputHeight: this.outputHeight + } ) ; + } + } + } +} ; + + + +TextTable.prototype.textBoxesSizeAndPosition = function() { + var x , y , outputX , outputY , textBox ; + + outputY = this.outputY + this.hasBorder ; + + for ( y = 0 ; y < this.rowCount ; y ++ ) { + outputX = this.outputX + this.hasBorder ; + + for ( x = 0 ; x < this.columnCount ; x ++ ) { + textBox = this.textBoxes[ y ][ x ] ; + + if ( textBox ) { + textBox.setSizeAndPosition( { + outputX , + outputY , + outputWidth: this.columnWidths[ x ] , + outputHeight: this.rowHeights[ y ] + } ) ; + } + + outputX += this.columnWidths[ x ] + this.hasBorder ; + } + + outputY += this.rowHeights[ y ] + this.hasBorder ; + } +} ; + + + +TextTable.prototype.preDrawSelf = function() { + // This only draw the frame/border + if ( ! this.hasBorder ) { return ; } + + var i , j , x , y ; + + //console.log( this.columnWidths , this.rowHeights , this.columnCount , this.rowCount ) ; + + y = this.outputY ; + + for ( j = 0 ; j < this.rowHeights.length ; j ++ ) { + x = this.outputX ; + + for ( i = 0 ; i < this.columnWidths.length ; i ++ ) { + // For each cell... + + // ... draw the top-left corner + this.outputDst.put( { x , y , attr: this.borderAttr } , + j ? + ( i ? this.borderChars.cross : this.borderChars.leftTee ) : + ( i ? this.borderChars.topTee : this.borderChars.topLeft ) + ) ; + + // ... draw the left border + this.outputDst.put( { + x , y: y + 1 , direction: 'down' , attr: this.borderAttr + } , this.borderChars.vertical.repeat( this.rowHeights[ j ] ) ) ; + x ++ ; + + // ... draw the top border + this.outputDst.put( { x , y , attr: this.borderAttr } , this.borderChars.horizontal.repeat( this.columnWidths[ i ] ) ) ; + x += this.columnWidths[ i ] ; + } + + // Draw the top-right corner only for the last cell + this.outputDst.put( { x , y , attr: this.borderAttr } , j ? this.borderChars.rightTee : this.borderChars.topRight ) ; + + // Draw the right border only for the last cell + this.outputDst.put( { + x , y: y + 1 , direction: 'down' , attr: this.borderAttr + } , this.borderChars.vertical.repeat( this.rowHeights[ j ] ) ) ; + y += this.rowHeights[ j ] + 1 ; + } + + + // Only for the last row, we have to draw the bottom border and corners + x = this.outputX ; + + for ( i = 0 ; i < this.columnWidths.length ; i ++ ) { + // For each bottom cells... + + // ... draw the bottom-left corner + this.outputDst.put( { x , y , attr: this.borderAttr } , i ? this.borderChars.bottomTee : this.borderChars.bottomLeft ) ; + x ++ ; + + // ... draw the bottom border + this.outputDst.put( { x , y , attr: this.borderAttr } , this.borderChars.horizontal.repeat( this.columnWidths[ i ] ) ) ; + x += this.columnWidths[ i ] ; + } + + // Draw the bottom-right corner only for the last cell of the last row + this.outputDst.put( { x , y , attr: this.borderAttr } , this.borderChars.bottomRight ) ; +} ; + + +},{"../spChars.js":48,"./Element.js":23,"./TextBox.js":33}],35:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Text = require( './Text.js' ) ; +const Button = require( './Button.js' ) ; + + + +function ToggleButton( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + // Almost everything is constructed by the normal 'submit-button' + Button.call( this , options ) ; + + this.value = !! options.value ; + this.key = options.key || null ; + + if ( this.elementType === 'ToggleButton' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = ToggleButton ; + +ToggleButton.prototype = Object.create( Button.prototype ) ; +ToggleButton.prototype.constructor = ToggleButton ; +ToggleButton.prototype.elementType = 'ToggleButton' ; + + + +ToggleButton.prototype.keyBindings = { + ENTER: 'toggle' , + KP_ENTER: 'toggle' , + ALT_ENTER: 'submit' +} ; + + + +// No blink effect +ToggleButton.prototype.blink = function() {} ; + + + +ToggleButton.prototype.setValue = function( value , noDraw ) { + value = !! value ; + if ( this.value === value ) { return ; } + this.value = value ; + this.updateStatus() ; + this.emit( 'toggle' , this.value , undefined , this ) ; + this.emit( this.value ? 'turnOn' : 'turnOff' , this.value , undefined , this ) ; + if ( ! noDraw ) { this.draw() ; } +} ; + + + +ToggleButton.prototype.toggle = function( noDraw ) { + this.setValue( ! this.value , noDraw ) ; +} ; + + + +ToggleButton.prototype.updateStatus = function() { + if ( this.disabled ) { + this.attr = this.disabledAttr ; + this.leftPadding = this.disabledLeftPadding ; + this.rightPadding = this.disabledRightPadding ; + } + else if ( this.hasFocus ) { + if ( this.value ) { + this.attr = this.turnedOnFocusAttr ; + this.leftPadding = this.turnedOnFocusLeftPadding ; + this.rightPadding = this.turnedOnFocusRightPadding ; + } + else { + this.attr = this.turnedOffFocusAttr ; + this.leftPadding = this.turnedOffFocusLeftPadding ; + this.rightPadding = this.turnedOffFocusRightPadding ; + } + } + else if ( this.value ) { + this.attr = this.turnedOnBlurAttr ; + this.leftPadding = this.turnedOnBlurLeftPadding ; + this.rightPadding = this.turnedOnBlurRightPadding ; + } + else { + this.attr = this.turnedOffBlurAttr ; + this.leftPadding = this.turnedOffBlurLeftPadding ; + this.rightPadding = this.turnedOffBlurRightPadding ; + } +} ; + + + +ToggleButton.prototype.onKey = function( key , altKeys , data ) { + switch( this.keyBindings[ key ] ) { + case 'toggle' : + if ( this.disabled ) { break ; } + this.toggle() ; + break ; + case 'submit' : + if ( this.disabled ) { break ; } + this.emit( 'submit' , this.key ? { [ this.key ]: this.value } : this.value , this.actionKeyBindings[ key ] , this ) ; + break ; + default : + return ; // Bubble up + } + + return true ; // Do not bubble up +} ; + + + +ToggleButton.prototype.onHover = function( data ) { + if ( this.disabled ) { return ; } + this.document.giveFocusTo( this , 'hover' ) ; +} ; + + + +ToggleButton.prototype.onClick = function( data ) { + if ( this.disabled ) { return ; } + this.document.giveFocusTo( this , 'select' ) ; + this.toggle() ; +} ; + + + +ToggleButton.prototype.onShortcut = function() { + if ( this.disabled ) { return ; } + this.document.giveFocusTo( this , 'select' ) ; + this.toggle() ; +} ; + + +},{"./Button.js":16,"./Element.js":23,"./Text.js":32}],36:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Element = require( './Element.js' ) ; +const Container = require( './Container.js' ) ; +const boxesChars = require( '../spChars.js' ).box ; + + + +function Window( options ) { + // Clone options if necessary + options = ! options ? {} : options.internal ? options : Object.create( options ) ; + options.internal = true ; + + if ( options.movable === undefined ) { options.movable = true ; } + if ( options.title ) { options.content = options.title ; } + if ( options.titleHasMarkup ) { options.contentHasMarkup = options.titleHasMarkup ; } + + Container.call( this , options ) ; + + this.boxChars = boxesChars.double ; + + if ( options.boxChars ) { + if ( typeof options.boxChars === 'object' ) { + this.boxChars = options.boxChars ; + } + else if ( typeof options.boxChars === 'string' && boxesChars[ options.boxChars ] ) { + this.boxChars = boxesChars[ options.boxChars ] ; + } + } + + // Only draw if we are not a superclass of the object + if ( this.elementType === 'Window' && ! options.noDraw ) { this.draw() ; } +} + +module.exports = Window ; + +Window.prototype = Object.create( Container.prototype ) ; +Window.prototype.constructor = Window ; +Window.prototype.elementType = 'Window' ; + +Window.prototype.containerBorderSize = 1 ; + + + +Window.prototype.preDrawSelf = function() { + var y , title , titleWidth , + titleMaxWidth = this.outputWidth - 8 ; + + // Draw the top border + if ( this.content && titleMaxWidth >= 1 ) { + title = Element.truncateContent( this.content , titleMaxWidth , this.contentHasMarkup ) ; + titleWidth = Element.getLastTruncateWidth() ; + + this.outputDst.put( + { x: this.outputX , y: this.outputY , markup: this.contentHasMarkup } , + this.boxChars.topLeft + this.boxChars.horizontal + + '[' + title + ']' + + this.boxChars.horizontal.repeat( this.outputWidth - 5 - titleWidth ) + + this.boxChars.topRight + ) ; + } + else { + this.outputDst.put( + { x: this.outputX , y: this.outputY } , + this.boxChars.topLeft + this.boxChars.horizontal.repeat( this.outputWidth - 2 ) + this.boxChars.topRight + ) ; + } + + // Draw the bottom border + this.outputDst.put( + { x: this.outputX , y: this.outputY + this.outputHeight - 1 } , + this.boxChars.bottomLeft + this.boxChars.horizontal.repeat( this.outputWidth - 2 ) + this.boxChars.bottomRight + ) ; + + // Draw the left and right border + for ( y = this.outputY + 1 ; y < this.outputY + this.outputHeight - 1 ; y ++ ) { + this.outputDst.put( { x: this.outputX , y: y } , this.boxChars.vertical ) ; + this.outputDst.put( { x: this.outputX + this.outputWidth - 1 , y: y } , this.boxChars.vertical ) ; + } + + Container.prototype.preDrawSelf.call( this ) ; +} ; + + + +// Allow only the top-border/title-bar to be draggable? +//Window.prototype.onDrag = function( data ) { Container.prototype.onDrag.call( this , data ) ; } ; + + +},{"../spChars.js":48,"./Container.js":19,"./Element.js":23}],37:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Promise = require( 'seventh' ) ; +const string = require( 'string-kit' ) ; + +require( './patches.js' ) ; +const execAsync = require( 'child_process' ).execAsync ; +const execFileAsync = require( 'child_process' ).execFileAsync ; +const spawn = require( 'child_process' ).spawn ; + + + +const XCLIP_SELECTION_ARG = { + c: 'clipboard' , + p: 'primary' , + s: 'secondary' +} ; + + + +if ( process.platform === 'linux' ) { + exports.getClipboard = async ( source ) => { + var arg = XCLIP_SELECTION_ARG[ source[ 0 ] ] || 'clipboard' ; + return await execFileAsync( 'xclip' , [ '-o' , '-selection' , arg ] ) ; + } ; + + exports.setClipboard = async ( str , source ) => { + var promise = new Promise() ; + var arg = XCLIP_SELECTION_ARG[ source[ 0 ] ] || 'clipboard' ; + var xclip = spawn( 'xclip' , [ '-i' , '-selection' , arg ] ) ; + + xclip.on( 'error' , error => { + //console.error( 'xclip error:' , error ) ; + promise.reject( error ) ; + } ) ; + + xclip.on( 'exit' , code => { + //console.log( 'xclip exited with code:' , code ) ; + if ( code !== 0 ) { promise.reject( code ) ; } + else { promise.resolve() ; } + } ) ; + + // Send the string to push to the clipboard + xclip.stdin.end( str ) ; + + return promise ; + } ; +} +else { + exports.getClipboard = () => Promise.reject( new Error( 'No clipboard manipulation program found' ) ) ; + exports.setClipboard = () => Promise.reject( new Error( 'No clipboard manipulation program found' ) ) ; +} + + +}).call(this)}).call(this,require('_process')) +},{"./patches.js":43,"_process":179,"child_process":136,"seventh":108,"string-kit":123}],38:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +var Promise = require( 'seventh' ) ; +var autoComplete = require( './autoComplete.js' ) ; +var fs = require( 'fs' ) ; +var path = require( 'path' ) ; + + + +/* + /!\ Document that!!! /!\ +*/ +module.exports = function fileInput( options , callback ) { + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + var baseDir ; + + var promise = new Promise() ; + + if ( options.baseDir ) { + baseDir = path.resolve( options.baseDir ) ; + + if ( ! path.isAbsolute( baseDir ) ) { + fs.realpath( options.baseDir , ( error , resolvedPath ) => { + if ( error ) { + if ( callback ) { callback( error ) ; } + else { promise.reject( error ) ; } + return ; + } + + options.baseDir = resolvedPath ; + + this.fileInput( options ).then( + input => { + if ( callback ) { callback( input ) ; } + else { promise.resolve( input ) ; } + } , + error_ => { + if ( callback ) { callback( error_ ) ; } + else { promise.reject( error_ ) ; } + } + ) ; + } ) ; + + return promise ; + } + } + else { + baseDir = process.cwd() ; + } + + if ( baseDir[ baseDir.length - 1 ] !== '/' ) { baseDir += '/' ; } + + var autoCompleter = async function autoCompleter( inputString ) { + var inputDir , inputFile , currentDir , files , completion ; + + if ( inputString[ inputString.length - 1 ] === '/' ) { + inputDir = inputString ; + inputFile = '' ; + } + else { + inputDir = path.dirname( inputString ) ; + inputDir = inputDir === '.' ? '' : inputDir + '/' ; + inputFile = path.basename( inputString ) ; + } + + + // If the input start with a '/', then forget about the baseDir + if ( path.isAbsolute( inputString ) ) { currentDir = inputDir ; } + else { currentDir = baseDir + inputDir ; } + + + //console.error( "### '" + inputDir +"' '"+ inputFile +"' '"+ currentDir + "'" ) ; + try { + files = await readdir( currentDir ) ; + } + catch ( error ) { + return inputString ; + } + + if ( ! Array.isArray( files ) || ! files.length ) { return inputString ; } + + completion = autoComplete( files , inputFile , true ) ; + + // force inputField() to prefix that *AFTER* singleLineMenu() + if ( Array.isArray( completion ) ) { completion.prefix = inputDir ; } + else { completion = path.normalize( inputDir + completion ) ; } + + return completion ; + } ; + + // Transmit options to inputField() + options = Object.assign( {} , options , { autoComplete: autoCompleter , autoCompleteMenu: true , minLength: 1 } ) ; + + this.inputField( options ).promise.then( + input => { + if ( ! input && typeof input !== 'string' ) { + input = undefined ; + } + else { + input = path.resolve( path.isAbsolute( input ) ? input : baseDir + input ) ; + } + + if ( callback ) { callback( undefined , input ) ; } + else { promise.resolve( input ) ; } + } , + error => { + if ( callback ) { callback( error ) ; } + else { promise.reject( error ) ; } + } + ) ; + + return promise ; +} ; + + + +// Like fs.readdir(), but performs fs.stat() for each file in order to add a '/' to directories +function readdir( dir ) { + var promise = new Promise() ; + + if ( dir[ dir.length - 1 ] !== '/' ) { dir += '/' ; } + + fs.readdir( dir , ( error , files ) => { + + if ( error ) { promise.reject( error ) ; return ; } + + Promise.map( files , file => { + return new Promise( ( resolve , reject ) => { + fs.lstat( dir + file , ( error_ , stats ) => { + if ( error_ ) { reject( error_ ) ; return ; } + if ( stats.isDirectory() ) { file += '/' ; } + resolve( file ) ; + } ) ; + } ) ; + } ) + .toPromise( promise ) ; + } ) ; + + return promise ; +} + + +}).call(this)}).call(this,require('_process')) +},{"./autoComplete.js":7,"_process":179,"fs":136,"path":178,"seventh":108}],39:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +var Promise = require( 'seventh' ) ; + + + +var defaultKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + //LEFT: 'previous' , + //RIGHT: 'next' , + //UP: 'previousRow' , + //DOWN: 'nextRow' , + UP: 'previous' , + DOWN: 'next' , + LEFT: 'previousColumn' , + RIGHT: 'nextColumn' , + TAB: 'cycleNext' , + SHIFT_TAB: 'cyclePrevious' , + HOME: 'first' , + END: 'last' +} ; + + + +/* + gridMenu( menuItems , [options] , callback ) + * menuItems `array` of menu item text + * options `object` of options, where: + * y `number` the line where the menu will be displayed, default to the next line + * x `number` the column where the menu will be displayed (default: 1) + * width `number` the maximum width of the grid menu (default: terminal's width) + * style `function` the style of unselected items, default to `term` + * selectedStyle `function` the style of the selected item, default to `term.inverse` + * leftPadding `string` the text to put before a menu item, default to ' ' + * selectedLeftPadding `string` the text to put before a selected menu item, default to ' ' + * rightPadding `string` the text to put after a menu item, default to ' ' + * selectedRightPadding `string` the text to put after a selected menu item, default to ' ' + * itemMaxWidth `number` the max width for an item, default to the 1/3 of the terminal width + or of the specified width option + * keyBindings `Object` overide default key bindings + * exitOnUnexpectedKey `boolean` if an unexpected key is pressed, it exits, calling the callback with undefined values + * callback( error , response ), where: + * error + * response `Object` where: + * selectedIndex `number` the user-selected menu item index + * selectedText `string` the user-selected menu item text + * x `number` the x coordinate of the selected menu item (the first character) + * y `number` the y coordinate of the selected menu item + * unexpectedKey `string` when 'exitOnUnexpectedKey' option is set, this contains the key that produced the exit +*/ +module.exports = function gridMenu( menuItems_ , options , callback ) { + if ( arguments.length < 1 ) { throw new Error( '[terminal] gridMenu() needs at least an array of menuItems argument' ) ; } + + if ( ! Array.isArray( menuItems_ ) || ! menuItems_.length ) { throw new TypeError( '[terminal] gridMenu(): argument #0 should be a non-empty array' ) ; } + + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + else if ( ! options || typeof options !== 'object' ) { options = {} ; } + + if ( ! options.style ) { options.style = this ; } + if ( ! options.selectedStyle ) { options.selectedStyle = this.inverse ; } + + if ( options.leftPadding === undefined ) { options.leftPadding = ' ' ; } + if ( options.selectedLeftPadding === undefined ) { options.selectedLeftPadding = ' ' ; } + if ( options.rightPadding === undefined ) { options.rightPadding = ' ' ; } + if ( options.selectedRightPadding === undefined ) { options.selectedRightPadding = ' ' ; } + + if ( ! options.x ) { options.x = 1 ; } + + if ( ! options.y ) { this( '\n' ) ; } + else { this.moveTo( options.x , options.y ) ; } + + if ( ! options.width ) { options.width = this.width - options.x + 1 ; } + + // itemMaxWidth default to 1/3 of the terminal width + if ( ! options.itemMaxWidth ) { options.itemMaxWidth = Math.floor( ( options.width - 1 ) / 3 ) ; } + + var keyBindings = options.keyBindings || defaultKeyBindings ; + + if ( ! this.grabbing ) { this.grabInput() ; } + + + var start = {} , selectedIndex = 0 , finished = false , alreadyCleanedUp = false , + itemInnerWidth = 0 , itemOuterWidth = 0 , + menuItems , columns , rows , padLength ; + + padLength = Math.max( options.leftPadding.length , options.selectedLeftPadding.length ) + + Math.max( options.rightPadding.length , options.selectedRightPadding.length ) ; + + menuItems_ = menuItems_.map( element => { + if ( typeof element !== 'string' ) { element = '' + element ; } + itemInnerWidth = Math.max( itemInnerWidth , element.length ) ; + return element ; + } ) ; + + itemInnerWidth = Math.min( itemInnerWidth , options.itemMaxWidth - padLength ) ; + itemOuterWidth = itemInnerWidth + padLength ; + columns = Math.floor( options.width / itemOuterWidth ) ; + rows = Math.ceil( menuItems_.length / columns ) ; + + menuItems = menuItems_.map( ( element , index ) => ( { + // row first + //offsetX: ( index % columns ) * itemOuterWidth , + //offsetY: Math.floor( index / columns ) , + + // column first + offsetY: index % rows , + offsetX: options.x - 1 + Math.floor( index / rows ) * itemOuterWidth , + + index: index , + text: element , + displayText: element.length > itemInnerWidth ? + element.slice( 0 , itemInnerWidth - 1 ) + '…' : + element + ' '.repeat( itemInnerWidth - element.length ) + } ) ) ; + + + //console.log( menuItems ) ; process.exit() ; + + var cleanup = ( error , data ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + finished = true ; + this.removeListener( 'key' , onKey ) ; + this.removeListener( 'mouse' , onMouse ) ; + this.moveTo( 1 , start.y + rows ) ; + + if ( error ) { + if ( callback ) { callback( error ) ; } + else { controller.promise.reject( error ) ; } + return ; + } + + var value = data !== undefined ? data : { + selectedIndex: selectedIndex , + selectedText: menuItems[ selectedIndex ].text , + x: 1 + menuItems[ selectedIndex ].offsetX , + y: start.y + menuItems[ selectedIndex ].offsetY + } ; + + if ( callback ) { callback( undefined , value ) ; } + else { controller.promise.resolve( value ) ; } + } ; + + // Compute the coordinate of the end of a string, given a start coordinate + var redraw = () => { + for ( var i = 0 ; i < menuItems.length ; i ++ ) { redrawItem( i ) ; } + redrawCursor() ; + } ; + + var redrawItem = ( index ) => { + var item = menuItems[ index ] ; + + this.moveTo( 1 + item.offsetX , start.y + item.offsetY ) ; + + if ( index === selectedIndex ) { + options.selectedStyle.noFormat( options.selectedLeftPadding ) ; + options.selectedStyle.noFormat( item.displayText ) ; + options.selectedStyle.noFormat( options.selectedRightPadding ) ; + } + else { + options.style.noFormat( options.leftPadding ) ; + options.style.noFormat( item.displayText ) ; + options.style.noFormat( options.rightPadding ) ; + } + } ; + + var redrawCursor = () => { + this.moveTo( 1 + menuItems[ selectedIndex ].offsetX , start.y + menuItems[ selectedIndex ].offsetY ) ; + } ; + + + var onKey = ( key , trash , data ) => { + if ( finished ) { return ; } + + var oldSelectedIndex = selectedIndex ; + + switch( keyBindings[ key ] ) { + case 'submit' : + cleanup() ; + break ; + + case 'previous' : + if ( selectedIndex > 0 ) { + selectedIndex -- ; + redrawItem( selectedIndex ) ; + redrawItem( selectedIndex + 1 ) ; + redrawCursor() ; + //redraw() ; + } + break ; + + case 'next' : + if ( selectedIndex < menuItems.length - 1 ) { + selectedIndex ++ ; + redrawItem( selectedIndex - 1 ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + /* + case 'previousRow' : + if ( selectedIndex >= columns ) + { + selectedIndex -= columns ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + + case 'nextRow' : + if ( selectedIndex < menuItems.length - columns ) + { + selectedIndex += columns ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + */ + case 'previousColumn' : + if ( selectedIndex >= rows ) { + selectedIndex -= rows ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + + case 'nextColumn' : + if ( selectedIndex < menuItems.length - rows ) { + selectedIndex += rows ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + + case 'cyclePrevious' : + selectedIndex -- ; + + if ( selectedIndex < 0 ) { selectedIndex = menuItems.length - 1 ; } + + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + break ; + + case 'cycleNext' : + selectedIndex ++ ; + + if ( selectedIndex >= menuItems.length ) { selectedIndex = 0 ; } + + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + break ; + + case 'first' : + if ( selectedIndex !== 0 ) { + selectedIndex = 0 ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + + case 'last' : + if ( selectedIndex !== menuItems.length - 1 ) { + selectedIndex = menuItems.length - 1 ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + } + break ; + + default : + if ( options.exitOnUnexpectedKey ) { + cleanup( undefined , { unexpectedKey: key , unexpectedKeyData: data } ) ; + } + break ; + } + } ; + + + var onMouse = ( name , data ) => { + + if ( finished ) { return ; } + + // If out of bounds, exit now! + if ( data.y < start.y || data.y >= start.y + rows ) { return ; } + + var i , inBounds = false , + oldSelectedIndex = selectedIndex ; + + for ( i = 0 ; i < menuItems.length ; i ++ ) { + if ( + data.y === start.y + menuItems[ i ].offsetY && + data.x >= 1 + menuItems[ i ].offsetX && + data.x < 1 + menuItems[ i ].offsetX + itemOuterWidth + ) { + inBounds = true ; + + if ( selectedIndex !== i ) { + selectedIndex = i ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + } + + break ; + } + } + + if ( inBounds && name === 'MOUSE_LEFT_BUTTON_PRESSED' ) { + cleanup() ; + } + } ; + + + this.getCursorLocation( ( error , x , y ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // So we just move to the last line and create a new line. + //cleanup( error ) ; return ; + this.row.eraseLineAfter( this.height )( '\n' ) ; + x = 1 ; + y = this.height ; + } + + start.x = x ; + start.y = y ; + + var extra = start.y + rows - this.height ; + + if ( extra > 0 ) { + // create extra lines + this( '\n'.repeat( extra ) ) ; + start.y -= extra ; + } + + redraw() ; + + this.on( 'key' , onKey ) ; + if ( this.mouseGrabbing ) { this.on( 'mouse' , onMouse ) ; } + } ) ; + + // For compatibility + var controller = {} ; + + controller.promise = new Promise() ; + + return controller ; +} ; + + +},{"seventh":108}],40:[function(require,module,exports){ +(function (global){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const ndarray = require( 'ndarray' ) ; +const Promise = require( 'seventh' ) ; + + + +const image = {} ; +module.exports = image ; + + + +var getPixels ; +if ( global.IS_BROWSER ) { getPixels = require( '@cronvel/get-pixels' ) ; } + + + +image.load = function load( fn , filepath , options , callback ) { + if ( ! getPixels ) { + // It loads some rarely useful files, do it only at last minute + try { + // Try the original get-pixels first, if available + getPixels = require( 'get-pixels' ) ; + } + catch ( error ) { + getPixels = require( '@cronvel/get-pixels' ) ; + } + } + + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + else if ( ! options || typeof options !== 'object' ) { options = {} ; } + + var promise = new Promise() ; + + getPixels( filepath , ( error_ , pixels ) => { + + if ( error_ ) { + var error = new Error( 'Bad image URL: ' + error_ ) ; + error.code = error_.code ; + error.parent = error_ ; + + if ( callback ) { callback( error ) ; } + else { promise.reject( error ) ; } + + return ; + } + + if ( pixels.shape.length === 4 ) { + // Probably a GIF or a format having animation, + // we just extract the first image: a 3D array + pixels = pixels.pick( 0 , null , null , null ) ; + } + + if ( options.shrink ) { pixels = image.shrinkNdarrayImage( pixels , options.shrink ) ; } + + var result = fn( pixels , options ) ; + + if ( callback ) { callback( undefined , result ) ; } + else { promise.resolve( result ) ; } + } ) ; + + return promise ; +} ; + + + +// Naive interpolation +image.shrinkNdarrayImage = function shrinkNdarrayImage( pixels , options ) { + var rate = Math.min( options.width / pixels.shape[ 0 ] , options.height / pixels.shape[ 1 ] ) ; + if ( rate >= 1 ) { return pixels ; } + + var dstWidth = Math.ceil( pixels.shape[ 0 ] * rate ) ; + var dstHeight = Math.ceil( pixels.shape[ 1 ] * rate ) ; + + var xDst , yDst , xSrc , xSrcMin , xSrcMax , ySrc , ySrcMin , ySrcMax , + r , g , b , a , count , + hasAlpha = pixels.shape[ 2 ] === 4 ; + + var shrinkedPixels = ndarray( + new Uint8Array( dstWidth * dstHeight * pixels.shape[ 2 ] ) , + [ dstWidth , dstHeight , pixels.shape[ 2 ] ] + ) ; + + for ( xDst = 0 ; xDst < dstWidth ; xDst ++ ) { + for ( yDst = 0 ; yDst < dstHeight ; yDst ++ ) { + xSrcMin = Math.ceil( xDst / rate ) ; + xSrcMax = Math.min( Math.ceil( ( xDst + 1 ) / rate - 1 ) , pixels.shape[ 0 ] - 1 ) ; + ySrcMin = Math.ceil( yDst / rate ) ; + ySrcMax = Math.min( Math.ceil( ( yDst + 1 ) / rate - 1 ) , pixels.shape[ 1 ] - 1 ) ; + count = r = g = b = a = 0 ; + + for ( xSrc = xSrcMin ; xSrc <= xSrcMax ; xSrc ++ ) { + for ( ySrc = ySrcMin ; ySrc <= ySrcMax ; ySrc ++ ) { + r += pixels.get( xSrc , ySrc , 0 ) ; + g += pixels.get( xSrc , ySrc , 1 ) ; + b += pixels.get( xSrc , ySrc , 2 ) ; + if ( hasAlpha ) { a += pixels.get( xSrc , ySrc , 3 ) ; } + count ++ ; + } + } + + shrinkedPixels.set( xDst , yDst , 0 , Math.round( r / count ) ) ; + shrinkedPixels.set( xDst , yDst , 1 , Math.round( g / count ) ) ; + shrinkedPixels.set( xDst , yDst , 2 , Math.round( b / count ) ) ; + if ( hasAlpha ) { shrinkedPixels.set( xDst , yDst , 3 , Math.round( a / count ) ) ; } + } + } + + return shrinkedPixels ; +} ; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"@cronvel/get-pixels":58,"get-pixels":136,"ndarray":71,"seventh":108}],41:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const NextGenEvents = require( 'nextgen-events' ) ; +const Promise = require( 'seventh' ) ; +const string = require( 'string-kit' ) ; +const autoComplete = require( './autoComplete.js' ) ; + + + +const defaultKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + ESCAPE: 'cancel' , + BACKSPACE: 'backDelete' , + DELETE: 'delete' , + LEFT: 'backward' , + RIGHT: 'forward' , + UP: 'historyPrevious' , + DOWN: 'historyNext' , + HOME: 'startOfInput' , + END: 'endOfInput' , + TAB: 'autoComplete' , + CTRL_R: 'autoCompleteUsingHistory' , + CTRL_LEFT: 'previousWord' , + CTRL_RIGHT: 'nextWord' , + ALT_D: 'deleteNextWord' , + CTRL_W: 'deletePreviousWord' , + CTRL_U: 'deleteAllBefore' , + CTRL_K: 'deleteAllAfter' +} ; + + + +const defaultTokenRegExp = /\S+/g ; + + + +/* + inputField( [options] , callback ) + * options `Object` where: + * y `number` the line where the input field will start (default to the current cursor location) + * x `number` the column where the input field will start (default to the current cursor location, + or 1 if the *y* option is defined) + * echo `boolean` if true (the default), input are displayed on the terminal + * echoChar `string` or `true` if set, all characters are replaced by this one (useful for password fields), + if true, it is replaced by a dot: • + * default `string` default input/placeholder + * cursorPosition `integer` (default: -1, end of input) set the cursor position/offset in the input, + negative value starts from the end + * cancelable `boolean` if true (default: false), it is cancelable by user using the cancel key (default: ESC), + thus will return undefined + * style `Function` style used, default to the terminal instance (no style) + * hintStyle `Function` style used for hint (auto-completion preview), default to `terminal.brightBlack` (gray) + * maxLength `number` maximum length of the input + * minLength `number` minimum length of the input + * history `Array` (optional) an history array, so UP and DOWN keys move up and down in the history + * autoComplete `Array` or `Function( inputString , [callback] )` (optional) an array of possible completion, + so the TAB key will auto-complete the input field. If it is a function, it should accept an input `string` + and return the completed `string` (if no completion can be done, it should return the input string, + if multiple candidate are possible, it should return an array of string), if **the function accepts 2 arguments** + (checked using *function*.length), then **the auto-completer will be asynchronous**! + If it does not accept a callback but returns a *thenable* (Promise-like), it will be asynchronous too. + /!\ Also, if autoCompleteMenu is set and the array contains a special property 'prefix', it will be prepended + after autoCompleteMenu()! + * autoCompleteMenu `boolean` or `Object` of options, used in conjunction with the 'autoComplete' options, if *truthy* + any auto-complete attempt having many completion candidates will display a menu to let the user choose between each + possibilities. If an object is given, it should contain options for the [.singleLineMenu()](#ref.singleLineMenu) + that is used for the completion (notice: some options are overwritten: 'y' and 'exitOnUnexpectedKey') + * autoCompleteHint `boolean` if true (default: false) use the hintStyle to write the auto-completion preview + at the right of the input + * keyBindings `Object` overide default key bindings + * tokenHook `Function( token , isEndOfInput , previousTokens , term , config )` this is a hook called for each + token of the input, where: + * token `String` is the current token + * isEndOfInput `boolean` true if this is the **last token and if it ends the input string** + (e.g. it is possible for the last token to be followed by some blank char, in that case *isEndOfInput* + would be false) + * previousTokens `Array` of `String` is a array containing all tokens before the current one + * term is a Terminal instance + * config `Object` is an object containing dynamic settings that can be altered by the hook, where: + * style `Function` style in use (see the *style* option) + * hintStyle `Function` style in use for hint (see the *hintStyle* option) + * tokenRegExp `RegExp` the regexp in use for tokenization (see the *tokenRegExp* option) + * autoComplete `Array` or `Function( inputString , [callback] )` (see the *autoComplete* option) + * autoCompleteMenu `boolean` or `Object` (see the *autoCompleteMenu* option) + * autoCompleteHint `boolean` enable/disable the auto-completion preview (see the *autoCompleteHint* option) + The config settings are always reset on new input, on new tokenization pass. + The hook can return a *style* (`Function`, like the *style* option) that will be used to print that token. + Used together, this can achieve syntax hilighting, as well as dynamic behavior suitable for a shell. + Finally, it can return a string, styled or not, that will be displayed in place of that token, + useful if the token should have multiple styles (but that string **MUST** contains the same number of + printable character, or it would lead hazardous behavior). + * tokenResetHook `Function( term , config )` this is a hook called before the first token + * tokenRegExp `RegExp` this is the regex used to tokenize the input, by default a token is space-delimited, + so "one two three" would be tokenized as [ "one" , "two" , "three" ]. + * callback( error , input ) + * error `mixed` truthy if an underlying error occurs + * input `string` the user input +*/ +module.exports = function inputField( options , callback ) { + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + else if ( ! options || typeof options !== 'object' ) { options = {} ; } + + if ( options.echo === undefined ) { options.echo = true ; } + + if ( typeof options.maxLength !== 'number' ) { options.maxLength = Infinity ; } + if ( typeof options.minLength !== 'number' ) { options.minLength = 0 ; } + + if ( options.echoChar && typeof options.echoChar !== 'string' ) { options.echoChar = '•' ; } + + if ( options.autoCompleteMenu ) { + if ( typeof options.autoCompleteMenu !== 'object' ) { options.autoCompleteMenu = {} ; } + options.autoCompleteMenu.exitOnUnexpectedKey = true ; + delete options.autoCompleteMenu.y ; + } + + var keyBindings = options.keyBindings || defaultKeyBindings ; + + if ( options.tokenRegExp && ( ! ( options.tokenRegExp instanceof RegExp ) || ! options.tokenRegExp.flags.includes( 'g' ) ) ) { + throw new Error( ".inputField(): if set, the 'tokenRegExp' option should be a RegExp with the 'g' flag" ) ; + } + + if ( ! this.grabbing ) { this.grabInput() ; } + + + + var controller , finished = false , paused = false , alreadyCleanedUp = false , + offset = options.cursorPosition !== undefined ? options.cursorPosition : -1 , + echo = !! options.echo , + start = {} , end = {} , cursor = {} , endHint = {} , + inputs = [] , inputIndex , + alwaysRedraw = options.tokenHook || options.autoCompleteHint , + hint = [] , meta = false ; + + var dynamic = { + style: options.style || this , + hintStyle: options.hintStyle || this.brightBlack , + tokenRegExp: options.tokenRegExp || defaultTokenRegExp , + autoComplete: options.autoComplete , + autoCompleteMenu: options.autoCompleteMenu , + autoCompleteHint: !! options.autoCompleteHint + } ; + + + + // Now inputs is an array of input, input being an array of char (thanks to JS using UCS-2 instead of UTF-8) + + if ( Array.isArray( options.history ) ) { + inputs = options.history.map( str => string.unicode.toArray( str ).slice( 0 , options.maxLength ) ) ; + } + + + if ( options.default && typeof options.default === 'string' ) { + inputs.push( string.unicode.toArray( options.default ).slice( 0 , options.maxLength ) ) ; + } + else { + inputs.push( [] ) ; + } + + + + var init = () => { + inputIndex = inputs.length - 1 ; + offset = boundOffset( offset ) ; + + if ( options.y !== undefined ) { + options.x = options.x || 1 ; + this.moveTo.eraseLineAfter( options.x , options.y ) ; + finishInit( options.x , options.y ) ; + } + else { + // Get the cursor location before getting started + this.getCursorLocation( ( error , x , y ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // So we just move to the last line, create a new line, and add a little prompt (it would be misleading otherwise). + //cleanup( error ) ; return ; + this.row.eraseLineAfter( this.height )( '\n> ' ) ; + x = 3 ; + y = this.height ; + } + + finishInit( x , y ) ; + } ) ; + } + } ; + + + + var finishInit = ( x , y ) => { + start.x = end.x = cursor.x = x ; + start.y = end.y = cursor.y = y ; + + if ( inputs[ inputIndex ].length ) { + // There is already something (placeholder, ...), so redraw now! + computeAllCoordinate() ; + redraw() ; + } + + this.on( 'key' , onKey ) ; + //controller.ready = true ; + controller.emit( 'ready' ) ; + } ; + + + + var cleanup = ( error , input ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + finished = true ; + this.removeListener( 'key' , onKey ) ; + + if ( error === 'abort' ) { return ; } + + this.styleReset() ; + + if ( error ) { + if ( callback ) { callback( error ) ; } + else { controller.promise.reject( error ) ; } + return ; + } + + var value ; + + if ( typeof input === 'string' ) { value = input ; } + else if ( input ) { value = input.join( '' ) ; } + + if ( callback ) { callback( undefined , value ) ; } + else { controller.promise.resolve( value ) ; } + } ; + + + + // Compute the coordinate of the cursor and end of a string, given a start coordinate + var computeAllCoordinate = () => { + var scroll , + inputWidth = string.unicode.arrayWidth( inputs[ inputIndex ] ) , + hintWidth = string.unicode.arrayWidth( hint ) ; + + end = offsetCoordinate( inputWidth ) ; + endHint = offsetCoordinate( inputWidth + hintWidth ) ; + + if ( endHint.y > this.height ) { + // We have gone out of the screen, scroll! + scroll = endHint.y - this.height ; + + dynamic.style.noFormat( '\n'.repeat( scroll ) ) ; + + start.y -= scroll ; + end.y -= scroll ; + endHint.y -= scroll ; + } + + cursorCoordinate() ; + } ; + + + + // Cursor coordinate from the current offset + var cursorCoordinate = () => { + cursor = offsetCoordinate( string.unicode.arrayWidth( inputs[ inputIndex ] , offset ) ) ; + } ; + + + + // Compute the coordinate of an offset, given a start coordinate + var offsetCoordinate = offset_ => { + return { + x: 1 + ( start.x + offset_ - 1 ) % this.width , + y: start.y + Math.floor( ( start.x + offset_ - 1 ) / this.width ) + } ; + } ; + + + + var boundOffset = offset_ => { + if ( typeof offset_ !== 'number' || isNaN( offset_ ) ) { return inputs[ inputIndex ].length ; } + + if ( offset_ < 0 ) { offset_ = inputs[ inputIndex ].length + 1 + offset_ ; } + + if ( offset_ < 0 ) { offset_ = 0 ; } + else if ( offset_ >= inputs[ inputIndex ].length ) { offset_ = inputs[ inputIndex ].length ; } + + return offset_ ; + } ; + + + + // Compute the coordinate of the end of a string, given a start coordinate + var redraw = ( extraLines , forceClear ) => { + var i , hintCleared ; + + extraLines = extraLines || 0 ; + + if ( ! dynamic.autoCompleteHint && forceClear ) { + // Used by history, when autoCompleteHint is off, the current line is not erased + this.moveTo( end.x , end.y ) ; + dynamic.style.noFormat.eraseLineAfter( '' ) ; + } + + this.moveTo( start.x , start.y ) ; + + if ( options.tokenHook ) { writeTokens( inputs[ inputIndex ].join( '' ) ) ; } + else if ( options.echoChar ) { dynamic.style.noFormat( options.echoChar.repeat( inputs[ inputIndex ].length ) ) ; } + else { dynamic.style.noFormat( inputs[ inputIndex ].join( '' ) ) ; } + + hintCleared = clearHint() ; + + if ( extraLines > 0 ) { + // If the previous input was using more lines, erase them now + for ( i = 1 ; i <= extraLines ; i ++ ) { + this.moveTo( 1 , end.y + i ) ; + dynamic.style.noFormat.eraseLineAfter( '' ) ; + } + } + + if ( ! hintCleared && ( cursor.y < end.y || end.x === this.width ) ) { + this.moveTo( end.x , end.y ) ; + dynamic.style.noFormat.eraseLineAfter( '' ) ; + } + + this.moveTo( cursor.x , cursor.y ) ; + } ; + + + + // Not used internally for instance, only for controller.redrawCursor() + var redrawCursor = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , redrawCursor ) ; return ; } + this.moveTo( cursor.x , cursor.y ) ; + } ; + + + + var pause = () => { + if ( paused ) { return ; } + paused = true ; + + // Don't redraw now if not ready, it will be drawn once ready (avoid double-draw) + //if ( controller.hasState( 'ready' ) ) { redraw() ; } + } ; + + + + var resume = () => { + if ( ! paused ) { return ; } + paused = false ; + + // Don't redraw now if not ready, it will be drawn once ready (avoid double-draw) + if ( controller.hasState( 'ready' ) ) { redraw() ; } + } ; + + + + var clearHint = () => { + // First, check if there are some hints to be cleared + if ( ! dynamic.autoCompleteHint ) { return false ; } + + var y = end.y ; + + this.moveTo( end.x , end.y ) ; + dynamic.style.noFormat.eraseLineAfter( '' ) ; + + // If the previous input was using more lines, erase them now + while ( y < endHint.y ) { + y ++ ; + this.moveTo( 1 , y ) ; + dynamic.style.noFormat.eraseLineAfter( '' ) ; + } + + this.moveTo( cursor.x , cursor.y ) ; + + return true ; + } ; + + + + // Compute the coordinate of the end of a string, given a start coordinate + var autoCompleteMenu = ( menu ) => { + paused = true ; + + this.singleLineMenu( menu , dynamic.autoCompleteMenu , ( error , response ) => { + // Unpause unconditionnally + paused = false ; + if ( error ) { return ; } + + if ( response.selectedText ) { + // Prepend something before the text + if ( menu.prefix ) { response.selectedText = menu.prefix + response.selectedText ; } + + // Append something after the text + if ( menu.postfix ) { response.selectedText += menu.postfix ; } + + response.selectedText = string.unicode.toArray( response.selectedText ).slice( 0 , options.maxLength ) ; + + inputs[ inputIndex ] = response.selectedText.concat( + inputs[ inputIndex ].slice( offset , options.maxLength + offset - response.selectedText.length ) + ) ; + + offset = response.selectedText.length ; + } + + if ( echo ) { + // Erase the menu + this.column.eraseLineAfter( 1 ) ; + + // If the input field was ending on the last line, we need to move it one line up + if ( end.y >= this.height && start.y > 1 ) { start.y -- ; } + + computeAllCoordinate() ; + redraw() ; + this.moveTo( cursor.x , cursor.y ) ; + } + + if ( response.unexpectedKey && response.unexpectedKey !== 'TAB' ) { + // Forward the key to the event handler + onKey( response.unexpectedKey , undefined , response.unexpectedKeyData ) ; + } + } ) + .on( 'highlight' , eventData => controller.emit( 'highlight' , eventData ) ) ; + } ; + + + + var writeTokens = ( text ) => { + var match , lastIndex , lastEndIndex = 0 , tokens = [] , tokenStyle , isEndOfInput ; + + // Reset dynamic stuffs + dynamic.style = options.style || this ; + dynamic.hintStyle = options.hintStyle || this.brightBlack ; + dynamic.tokenRegExp = options.tokenRegExp || defaultTokenRegExp ; + dynamic.autoComplete = options.autoComplete ; + dynamic.autoCompleteMenu = options.autoCompleteMenu ; + dynamic.autoCompleteHint = !! options.autoCompleteHint ; + + dynamic.tokenRegExp.lastIndex = 0 ; + + if ( options.tokenResetHook ) { options.tokenResetHook( this , dynamic ) ; } + + while ( ( match = dynamic.tokenRegExp.exec( text ) ) !== null ) { + // Back-up that now, since it can be modified by the hook + lastIndex = dynamic.tokenRegExp.lastIndex ; + + if ( match.index > lastEndIndex ) { dynamic.style.noFormat( text.slice( lastEndIndex , match.index ) ) ; } + + isEndOfInput = match.index + match[ 0 ].length === text.length ; + + tokenStyle = options.tokenHook( match[ 0 ] , isEndOfInput , tokens , this , dynamic ) ; + + if ( typeof tokenStyle === 'function' ) { tokenStyle.noFormat( match[ 0 ] ) ; } + else if ( typeof tokenStyle === 'string' ) { this.noFormat( tokenStyle ) ; } + else { dynamic.style.noFormat( match[ 0 ] ) ; } + + tokens.push( match[ 0 ] ) ; + + lastEndIndex = match.index + match[ 0 ].length ; + + // Restore it, if it was modified + dynamic.tokenRegExp.lastIndex = lastIndex ; + } + + if ( lastEndIndex < text.length ) { dynamic.style.noFormat( text.slice( lastEndIndex ) ) ; } + } ; + + + + var autoCompleteHint = () => { + // The cursor should be at the end ATM + if ( ! dynamic.autoComplete || ! dynamic.autoCompleteHint || offset < inputs[ inputIndex ].length ) { + return ; + } + + var autoCompleted , inputText = inputs[ inputIndex ].join( '' ) ; + + var finishCompletion = () => { + if ( Array.isArray( autoCompleted ) ) { return ; } + + hint = string.unicode.toArray( autoCompleted.slice( inputText.length ) ) + .slice( 0 , options.maxLength - inputs[ inputIndex ].length ) ; + + computeAllCoordinate() ; + this.moveTo( end.x , end.y ) ; // computeAllCoordinate() can add some newline + dynamic.hintStyle.noFormat( hint.join( '' ) ) ; + this.moveTo( cursor.x , cursor.y ) ; + } ; + + if ( Array.isArray( dynamic.autoComplete ) ) { + autoCompleted = autoComplete( dynamic.autoComplete , inputText , dynamic.autoCompleteMenu ) ; + } + else if ( typeof dynamic.autoComplete === 'function' ) { + if ( dynamic.autoComplete.length === 2 ) { + dynamic.autoComplete( inputText , ( error , autoCompleted_ ) => { + if ( error ) { cleanup( error ) ; return ; } + + autoCompleted = autoCompleted_ ; + finishCompletion() ; + } ) ; + return ; + } + + autoCompleted = dynamic.autoComplete( inputText ) ; + + if ( Promise.isThenable( autoCompleted ) ) { + autoCompleted.then( + autoCompleted_ => { + autoCompleted = autoCompleted_ ; + finishCompletion() ; + } , + error => { cleanup( error ) ; } + ) ; + return ; + } + } + + finishCompletion() ; + } ; + + + + // The main method: the key event handler + var onKey = ( key , trash , data ) => { + + if ( finished || paused ) { return ; } + + var leftPart , autoCompleteUsed , autoCompleted , extraLines , charToDelete , cutOffset , altKey , + lastOffset = offset ; + + // if previous keystroke triggered the 'meta' keybinding, prepend ALT_ to this key + if ( meta ) { + meta = false ; + altKey = 'ALT_' + key.toUpperCase() ; + + if ( data ) { data.isCharacter = false ; } + if ( keyBindings[ altKey ] ) { key = altKey ; } + } + + if ( data && data.isCharacter ) { + // if data.isCharacter, this is a regular UTF-8 character, not a special key + + if ( inputs[ inputIndex ].length >= options.maxLength ) { return ; } + + // Insert version + //inputs[ inputIndex ] = inputs[ inputIndex ].slice( 0 , offset ) + key + inputs[ inputIndex ].slice( offset ) ; + inputs[ inputIndex ].splice( offset , 0 , key ) ; + offset ++ ; + + if ( echo ) { + if ( offset === inputs[ inputIndex ].length && ! alwaysRedraw ) { + dynamic.style.noFormat( options.echoChar || key ) ; + // Now it's done by computeAllCoordinate() + //if ( cursor.x >= this.width ) { dynamic.style.noFormat( '\n' ) ; } + computeAllCoordinate() ; + } + else { + // redraw() is mandatory in insert mode + computeAllCoordinate() ; + redraw() ; + if ( dynamic.autoCompleteHint ) { autoCompleteHint() ; } + } + } + } + else { + // Here we have a special key + + switch( keyBindings[ key ] ) { + case 'submit' : + if ( inputs[ inputIndex ].length < options.minLength ) { break ; } + clearHint() ; + cleanup( undefined , inputs[ inputIndex ] ) ; + break ; + + case 'cancel' : + if ( options.cancelable ) { cleanup() ; } + break ; + + case 'meta' : + meta = true ; + break ; + + case 'backDelete' : + if ( inputs[ inputIndex ].length && offset > 0 ) { + charToDelete = inputs[ inputIndex ][ offset - 1 ] ; + inputs[ inputIndex ].splice( offset - 1 , 1 ) ; + offset -- ; + + if ( echo ) { + // The cursor position check should happen BEFORE we modify it with computeAllCoordinate() + if ( cursor.y < end.y || cursor.x === 1 || alwaysRedraw ) { + computeAllCoordinate() ; + // Every time something is deleted, we need a redraw with the forceClear option on + redraw( undefined , true ) ; + if ( dynamic.autoCompleteHint ) { autoCompleteHint() ; } + } + else { + computeAllCoordinate() ; + + // .backDelete() does not work with full-width, Terminal Kit should stop using it until a reliable escape sequence combo is found + //this.backDelete() ; + if ( string.unicode.isFullWidth( charToDelete ) ) { + this.left( 2 ) ; + this.delete( 2 ) ; + } + else { + this.left( 1 ) ; + this.delete( 1 ) ; + } + } + } + } + break ; + + case 'delete' : + if ( inputs[ inputIndex ].length && offset < inputs[ inputIndex ].length ) { + charToDelete = inputs[ inputIndex ][ offset ] ; + inputs[ inputIndex ].splice( offset , 1 ) ; + + if ( echo ) { + // The cursor position check should happen BEFORE we modify it with computeAllCoordinate() + if ( cursor.y < end.y || alwaysRedraw ) { + computeAllCoordinate() ; + // Every time something is deleted, we need a redraw with the forceClear option on + redraw( undefined , true ) ; + if ( dynamic.autoCompleteHint ) { autoCompleteHint() ; } + } + else { + computeAllCoordinate() ; + this.delete( string.unicode.isFullWidth( charToDelete ) ? 2 : 1 ) ; + } + } + } + break ; + + case 'deleteAllBefore' : + if ( inputs[ inputIndex ].length && offset > 0 ) { + //inputs[ inputIndex ] = inputs[ inputIndex ].slice( 0 , offset - 1 ) + inputs[ inputIndex ].slice( offset ) ; + inputs[ inputIndex ].splice( 0 , offset ) ; + offset = 0 ; + + if ( echo ) { + computeAllCoordinate() ; + // Need forceClear + redraw( undefined , true ) ; + } + } + break ; + + case 'deleteAllAfter' : + if ( inputs[ inputIndex ].length && offset < inputs[ inputIndex ].length ) { + inputs[ inputIndex ].splice( offset , inputs[ inputIndex ].length - offset ) ; + + if ( echo ) { + computeAllCoordinate() ; + // Need forceClear + redraw( undefined , true ) ; + if ( dynamic.autoCompleteHint ) { autoCompleteHint() ; } + } + } + break ; + + case 'backward' : + if ( inputs[ inputIndex ].length && offset > 0 ) { + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + clearHint() ; + } + + offset -- ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + } + break ; + + case 'forward' : + if ( inputs[ inputIndex ].length && offset < inputs[ inputIndex ].length ) { + offset ++ ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + autoCompleteHint() ; + } + } + + break ; + + case 'deletePreviousWord' : + if ( inputs[ inputIndex ].length && offset > 0 ) { + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + clearHint() ; + } + + cutOffset = offset -- ; + + while ( offset > 0 && inputs[ inputIndex ][ offset ] === ' ' ) { offset -- ; } + while ( offset > 0 && inputs[ inputIndex ][ offset - 1 ] !== ' ' ) { offset -- ; } + + inputs[ inputIndex ].splice( offset , cutOffset - offset ) ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + redraw( undefined , true ) ; + } + } + break ; + + case 'deleteNextWord' : + if ( inputs[ inputIndex ].length && offset < inputs[ inputIndex ].length ) { + cutOffset = offset ; + + while ( offset < inputs[ inputIndex ].length && inputs[ inputIndex ][ offset ] === ' ' ) { offset ++ ; } + while ( offset < inputs[ inputIndex ].length && inputs[ inputIndex ][ offset ] !== ' ' ) { offset ++ ; } + while ( offset < inputs[ inputIndex ].length && inputs[ inputIndex ][ offset ] === ' ' ) { offset ++ ; } + + inputs[ inputIndex ].splice( cutOffset , offset - cutOffset ) ; + offset = Math.min( inputs[ inputIndex ].length , cutOffset ) ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + redraw( undefined , true ) ; + } + + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + autoCompleteHint() ; + } + } + break ; + + case 'previousWord' : + if ( inputs[ inputIndex ].length && offset > 0 ) { + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + clearHint() ; + } + + offset -- ; + + while ( offset > 0 && inputs[ inputIndex ][ offset ] === ' ' ) { offset -- ; } + while ( offset > 0 && inputs[ inputIndex ][ offset - 1 ] !== ' ' ) { offset -- ; } + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + } + break ; + + case 'nextWord' : + if ( inputs[ inputIndex ].length && offset < inputs[ inputIndex ].length ) { + while ( offset < inputs[ inputIndex ].length && inputs[ inputIndex ][ offset ] === ' ' ) { offset ++ ; } + while ( offset < inputs[ inputIndex ].length && inputs[ inputIndex ][ offset ] !== ' ' ) { offset ++ ; } + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + autoCompleteHint() ; + } + } + + break ; + + case 'startOfInput' : + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + clearHint() ; + } + + offset = 0 ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + break ; + + case 'endOfInput' : + offset = inputs[ inputIndex ].length ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + + if ( dynamic.autoCompleteHint && lastOffset !== inputs[ inputIndex ].length ) { + autoCompleteHint() ; + } + + break ; + + case 'historyNext' : + if ( inputIndex < inputs.length - 1 ) { + inputIndex ++ ; + offset = inputs[ inputIndex ].length ; + + if ( echo ) { + extraLines = end.y - start.y ; + computeAllCoordinate() ; + extraLines -= end.y - start.y ; + redraw( extraLines , true ) ; + this.moveTo( cursor.x , cursor.y ) ; + } + + // Not sure if this is desirable + //if ( dynamic.autoCompleteHint ) { autoCompleteHint() ; } + } + break ; + + case 'historyPrevious' : + if ( inputIndex > 0 ) { + inputIndex -- ; + offset = inputs[ inputIndex ].length ; + + if ( echo ) { + extraLines = end.y - start.y ; + computeAllCoordinate() ; + extraLines -= end.y - start.y ; + redraw( extraLines , true ) ; + this.moveTo( cursor.x , cursor.y ) ; + } + + // Not sure if this is desirable + //if ( dynamic.autoCompleteHint ) { autoCompleteHint() ; } + } + break ; + + case 'autoCompleteUsingHistory' : + case 'autoComplete' : + autoCompleteUsed = keyBindings[ key ] === 'autoCompleteUsingHistory' ? options.history : dynamic.autoComplete ; + + if ( ! autoCompleteUsed ) { break ; } + + leftPart = inputs[ inputIndex ].slice( 0 , offset ) ; + + var finishCompletion = () => { + if ( Array.isArray( autoCompleted ) ) { + if ( dynamic.autoCompleteMenu ) { autoCompleteMenu( autoCompleted ) ; } + return ; + } + + leftPart = string.unicode.toArray( autoCompleted ).slice( 0 , options.maxLength ) ; + + inputs[ inputIndex ] = leftPart.concat( + inputs[ inputIndex ].slice( offset , options.maxLength + offset - leftPart.length ) + ) ; + + offset = leftPart.length ; + + if ( echo ) { + computeAllCoordinate() ; + redraw() ; + } + } ; + + if ( Array.isArray( autoCompleteUsed ) ) { + autoCompleted = autoComplete( autoCompleteUsed , leftPart.join( '' ) , dynamic.autoCompleteMenu ) ; + } + else if ( typeof autoCompleteUsed === 'function' ) { + if ( autoCompleteUsed.length === 2 ) { + autoCompleteUsed( leftPart.join( '' ) , ( error , autoCompleted_ ) => { + if ( error ) { cleanup( error ) ; return ; } + + autoCompleted = autoCompleted_ ; + finishCompletion() ; + } ) ; + return ; + } + + autoCompleted = autoCompleteUsed( leftPart.join( '' ) ) ; + + if ( Promise.isThenable( autoCompleted ) ) { + autoCompleted.then( + autoCompleted_ => { + autoCompleted = autoCompleted_ ; + finishCompletion() ; + } , + error => { cleanup( error ) ; } + ) ; + return ; + } + } + + finishCompletion() ; + + break ; + } + } + } ; + + + // Return a controller for the input field + + controller = Object.create( NextGenEvents.prototype ) ; + + controller.defineStates( 'ready' ) ; + + // /!\ .ready is deprecated, it is now a getter to .hasState('ready') + Object.defineProperty( controller , 'ready' , { + get: function() { return this.hasState( 'ready' ) ; } + } ) ; + + // Tmp, for compatibility + controller.widgetType = 'inputField' ; + + // Stop everything and do not even call the callback + controller.abort = () => { + if ( finished ) { return ; } + cleanup( 'abort' ) ; + } ; + + // Stop and call the completion callback with the current input + controller.stop = () => { + if ( finished ) { return ; } + cleanup( undefined , inputs[ inputIndex ] ) ; + } ; + + // Pause and resume: the input field will not respond to event when paused + controller.pause = pause ; + controller.resume = resume ; + controller.focus = ( value ) => { + if ( value ) { resume() ; } + else { pause() ; } + } ; + + // Get the current input + controller.getInput = () => inputs[ inputIndex ].join( '' ) ; + + controller.value = controller.getInput ; + + // Get the current position + controller.getPosition = () => ( { x: start.x , y: start.y } ) ; + + // Hide the input field + controller.hide = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.hide ) ; return ; } + + var i , j ; + + for ( i = start.x , j = start.y ; j <= end.y ; i = 1 , j ++ ) { + this.moveTo.eraseLineAfter( i , j ) ; + } + + echo = false ; + } ; + + // Show the input field + controller.show = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.show ) ; return ; } + echo = true ; + redraw() ; + } ; + + // Redraw the input field + controller.redraw = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.redraw ) ; return ; } + redraw( undefined , true ) ; + } ; + + // Redraw the cursor + controller.redrawCursor = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.redrawCursor ) ; return ; } + redrawCursor() ; + } ; + + controller.getCursorPosition = () => offset ; + + controller.setCursorPosition = newOffset => { + newOffset = boundOffset( newOffset ) ; + + if ( newOffset !== offset ) { + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + clearHint() ; + } + + offset = newOffset ; + + if ( echo ) { + computeAllCoordinate() ; + this.moveTo( cursor.x , cursor.y ) ; + } + + if ( dynamic.autoCompleteHint && offset === inputs[ inputIndex ].length ) { + autoCompleteHint() ; + } + } + } ; + + // Rebase the input field where the cursor is + controller.rebase = ( x , y ) => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.rebase ) ; return ; } + + var rebaseTo = ( x_ , y_ ) => { + + start.x = x_ ; + start.y = y_ ; + + if ( options.echo ) { + echo = true ; + computeAllCoordinate() ; + redraw() ; + } + + controller.emit( 'rebased' ) ; + } ; + + if ( x !== undefined && y !== undefined ) { + rebaseTo( x , y ) ; + return ; + } + + // First, disable echoing: getCursorLocation is async! + echo = false ; + + this.getCursorLocation( ( error , x_ , y_ ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // Here we just ignore the rebase. + //cleanup( error ) ; + return ; + } + + rebaseTo( x_ , y_ ) ; + } ) ; + } ; + + controller.promise = new Promise() ; + + + // Init the input field + init() ; + + return controller ; +} ; + + +},{"./autoComplete.js":7,"nextgen-events":72,"seventh":108,"string-kit":123}],42:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const string = require( 'string-kit' ) ; + + + +const misc = {} ; +module.exports = misc ; + + + +const colorNameToIndexDict = { + // ANSI + black: 0 , + red: 1 , + green: 2 , + yellow: 3 , + blue: 4 , + magenta: 5 , + violet: 5 , + cyan: 6 , + white: 7 , + grey: 8 , + gray: 8 , + brightblack: 8 , + brightred: 9 , + brightgreen: 10 , + brightyellow: 11 , + brightblue: 12 , + brightmagenta: 13 , + brightviolet: 13 , + brightcyan: 14 , + brightwhite: 15 +} ; + + + +// Color name to index +misc.colorNameToIndex = color => colorNameToIndexDict[ color.toLowerCase() ] ; + + + +const indexToColorNameArray = [ + "black" , "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , + "gray" , "brightRed" , "brightGreen" , "brightYellow" , "brightBlue" , "brightMagenta" , "brightCyan" , "brightWhite" +] ; + + + +// Color name to index +misc.indexToColorName = index => indexToColorNameArray[ index ] ; + + + +misc.hexToRgba = hex => { + // Strip the # if necessary + if ( hex[ 0 ] === '#' ) { hex = hex.slice( 1 ) ; } + + if ( hex.length === 3 ) { + hex = hex[ 0 ] + hex[ 0 ] + hex[ 1 ] + hex[ 1 ] + hex[ 2 ] + hex[ 2 ] ; + } + + return { + r: parseInt( hex.slice( 0 , 2 ) , 16 ) , + g: parseInt( hex.slice( 2 , 4 ) , 16 ) , + b: parseInt( hex.slice( 4 , 6 ) , 16 ) , + a: hex.length > 6 ? parseInt( hex.slice( 6 , 8 ) , 16 ) : 255 + } ; +} ; + + + +// DEPRECATED function names +misc.color2index = misc.colorNameToIndex ; +misc.index2color = misc.indexToColorName ; +misc.hexToColor = misc.hexToRgba ; + + + +// Strip all control chars, if newline is true, only newline control chars are preserved +misc.stripControlChars = ( str , newline ) => { + if ( newline ) { return str.replace( /[\x00-\x09\x0b-\x1f\x7f]/g , '' ) ; } + return str.replace( /[\x00-\x1f\x7f]/g , '' ) ; +} ; + + + +// From https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings +const escapeSequenceRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g ; +const escapeSequenceParserRegex = /([\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><])|([^\u001b\u009b]+)/g ; + + + +misc.stripEscapeSequences = str => str.replace( escapeSequenceRegex , '' ) ; + + + +// Return the real width of the string (i.e. as displayed in the terminal) +misc.ansiWidth = +misc.stringWidth = str => { + var matches , width = 0 ; + + // Reset + escapeSequenceParserRegex.lastIndex = 0 ; + + while ( ( matches = escapeSequenceParserRegex.exec( str ) ) ) { + if ( matches[ 2 ] ) { + width += string.unicode.width( matches[ 2 ] ) ; + } + } + + return width ; +} ; + + + +// Userland may use this, it is more efficient than .truncateString() + .stringWidth(), +// and BTW even more than testing .stringWidth() then .truncateString() + .stringWidth() +var lastTruncateWidth = 0 ; +misc.getLastTruncateWidth = () => lastTruncateWidth ; + + + +// Truncate a string to a given real width +misc.truncateAnsiString = +misc.truncateString = ( str , maxWidth ) => { + var matches , width = 0 ; + + lastTruncateWidth = 0 ; + + // Reset + escapeSequenceParserRegex.lastIndex = 0 ; + + while ( ( matches = escapeSequenceParserRegex.exec( str ) ) ) { + if ( matches[ 2 ] ) { + width += string.unicode.width( matches[ 2 ] ) ; + + if ( width >= maxWidth ) { + if ( width === maxWidth ) { + return str.slice( 0 , matches.index + matches[ 2 ].length ) ; + } + + return str.slice( 0 , matches.index ) + string.unicode.truncateWidth( matches[ 2 ] , maxWidth - lastTruncateWidth ) ; + } + + lastTruncateWidth = width ; + } + } + + return str ; +} ; + + + +// width of a string with a markup, without control chars +misc.markupWidth = str => { + return string.unicode.width( str.replace( /\^\[[^\]]*]|\^(.)/g , ( match , second ) => { + if ( second === ' ' || second === '^' ) { + return second ; + } + + return '' ; + } ) ) ; +} ; + + + +// Truncate a string to a given real width, the string may contains markup, but no control chars +misc.truncateMarkupString = ( str , maxWidth ) => { + var index = 0 , charWidth , + strArray = string.unicode.toArray( str ) ; + + lastTruncateWidth = 0 ; + + while ( index < strArray.length ) { + if ( strArray[ index ] === '^' ) { + index ++ ; + + if ( strArray[ index ] === '[' ) { + while ( index < strArray.length && strArray[ index ] !== ']' ) { index ++ ; } + index ++ ; + continue ; + } + + if ( strArray[ index ] !== ' ' && strArray[ index ] !== '^' ) { + index ++ ; + continue ; + } + } + + charWidth = string.unicode.isFullWidth( strArray[ index ] ) ? 2 : 1 ; + + if ( lastTruncateWidth + charWidth > maxWidth ) { + strArray.length = index ; + return strArray.join( '' ) ; + } + + lastTruncateWidth += charWidth ; + index ++ ; + } + + return str ; +} ; + + + +// Function used for sequenceSkip option of string-kit's .wordwrap() +// TODO: many issues remaining +misc.escapeSequenceSkipFn = ( strArray , index ) => { + //console.error( '>>> Entering' ) ; + var code ; + + if ( strArray[ index ] !== '\x1b' ) { return index ; } + index ++ ; + if ( strArray[ index ] !== '[' ) { return index ; } + index ++ ; + + for ( ; index < strArray.length ; index ++ ) { + code = strArray[ index ].charCodeAt( 0 ) ; + //console.error( 'code:' , strArray[ index ] , code.toString( 16 ) ) ; + + if ( ( code >= 0x41 && code <= 0x5a ) || ( code >= 0x61 && code <= 0x7a ) ) { + //console.error( "<<< break!" ) ; + index ++ ; + break ; + } + } + + return index ; +} ; + + + +misc.wordWrapAnsi = ( str , width ) => string.wordwrap( str , { + width: width , + noJoin: true , + fill: true , + regroupFn: strArray => { + var sequence = '' , + csi = false , + newStrArray = [] ; + + strArray.forEach( char => { + var charCode ; + + if ( csi ) { + sequence += char ; + charCode = char.charCodeAt( 0 ) ; + + if ( ( charCode >= 0x41 && charCode <= 0x5a ) || ( charCode >= 0x61 && charCode <= 0x7a ) ) { + newStrArray.push( sequence ) ; + sequence = '' ; + csi = false ; + } + } + else if ( sequence ) { + sequence += char ; + + if ( char === '[' ) { + csi = true ; + } + else { + newStrArray.push( sequence ) ; + sequence = '' ; + } + } + else if ( char === '\x1b' ) { + sequence = char ; + } + else { + newStrArray.push( char ) ; + } + } ) ; + + return newStrArray ; + } , + charWidthFn: char => { + if ( char[ 0 ] === '\x1b' ) { return 0 ; } + return string.unicode.charWidth( char ) ; + } +} ) ; + + + +misc.wordwrapMarkup = // <-- DEPRECATED +misc.wordWrapMarkup = ( str , width ) => string.wordwrap( str , { + width: width , + noJoin: true , + fill: true , + regroupFn: strArray => { + var markup = '' , + complexMarkup = false , + newStrArray = [] ; + + strArray.forEach( char => { + if ( complexMarkup ) { + markup += char ; + + if ( char === ']' ) { + newStrArray.push( markup ) ; + markup = '' ; + complexMarkup = false ; + } + } + else if ( markup ) { + markup += char ; + + if ( char === '[' ) { + complexMarkup = true ; + } + else { + newStrArray.push( markup ) ; + markup = '' ; + } + } + else if ( char === '^' ) { + markup = char ; + } + else { + newStrArray.push( char ) ; + } + } ) ; + + return newStrArray ; + } , + charWidthFn: char => { + if ( char[ 0 ] === '^' && char[ 1 ] ) { + if ( char[ 1 ] === '^' || char[ 1 ] === ' ' ) { return 1 ; } + return 0 ; + } + + return string.unicode.charWidth( char ) ; + } +} ) ; + + + +misc.preserveMarkupFormat = string.createFormatter( { + argumentSanitizer: str => str.replace( /[\x00-\x1f\x7f^]/g , char => char === '^' ? '^^' : '' ) , + noMarkup: true +} ) ; + + + +misc.markupOptions = { + complexMarkupAliases: { + c: 'color' , + fg: 'color' , + bg: 'bgColor' + } , + shiftMarkup: { + '#': 'background' + } , + markup: { + ':': { reset: true } , + ' ': { reset: true , raw: ' ' } , + ';': { reset: true , special: true } , // "Special reset" can reset forced attr (Document-model) + + '-': { dim: true } , + '+': { bold: true } , + '_': { underline: true } , + '/': { italic: true } , + '!': { inverse: true } , + + 'k': { color: 0 } , + 'r': { color: 1 } , + 'g': { color: 2 } , + 'y': { color: 3 } , + 'b': { color: 4 } , + 'm': { color: 5 } , + 'c': { color: 6 } , + 'w': { color: 7 } , + 'K': { color: 8 } , + 'R': { color: 9 } , + 'G': { color: 10 } , + 'Y': { color: 11 } , + 'B': { color: 12 } , + 'M': { color: 13 } , + 'C': { color: 14 } , + 'W': { color: 15 } + } , + shiftedMarkup: { + background: { + ':': { reset: true , defaultColor: true , bgDefaultColor: true } , + ' ': { + reset: true , defaultColor: true , bgDefaultColor: true , raw: ' ' + } , + ';': { + reset: true , special: true , defaultColor: true , bgDefaultColor: true + } , + + 'k': { bgColor: 0 } , + 'r': { bgColor: 1 } , + 'g': { bgColor: 2 } , + 'y': { bgColor: 3 } , + 'b': { bgColor: 4 } , + 'm': { bgColor: 5 } , + 'c': { bgColor: 6 } , + 'w': { bgColor: 7 } , + 'K': { bgColor: 8 } , + 'R': { bgColor: 9 } , + 'G': { bgColor: 10 } , + 'Y': { bgColor: 11 } , + 'B': { bgColor: 12 } , + 'M': { bgColor: 13 } , + 'C': { bgColor: 14 } , + 'W': { bgColor: 15 } + } + } +} ; + + + +// /!\ Should be moved to string-kit once finished /!\ +const parseMarkupRegexp = /\^\[([^\]]*)]|\^(.)|([^^]+)/g ; + +misc.parseMarkup = ( str , options ) => { + var complex , markup , raw , match , + base = options.markup , + output = [] ; + + parseMarkupRegexp.lastIndex = 0 ; + + while ( ( match = parseMarkupRegexp.exec( str ) ) ) { + [ , complex , markup , raw ] = match ; + + if ( complex ) { + var custom = {} ; + complex.split( ',' ).forEach( part => { + var [ k , v ] = part.split( ':' ) ; + if ( options.complexMarkupAliases[ k ] ) { k = options.complexMarkupAliases[ k ] ; } + custom[ k ] = v || true ; + } ) ; + + output.push( { markup: custom } ) ; + } + else if ( raw ) { output.push( raw ) ; } + else if ( markup === '^' ) { output.push( '^' ) ; } + else if ( options.shiftMarkup[ markup ] ) { base = options.shiftedMarkup[ options.shiftMarkup[ markup ] ] ; continue ; } + else if ( base[ markup ] ) { output.push( { markup: base[ markup ] } ) ; } + + base = options.markup ; + } + + return output ; +} ; + + + +const ANSI_CODES = { + '0': { reset: true } , + + '1': { bold: true } , + '2': { dim: true } , + '22': { bold: false , dim: false } , + '3': { italic: true } , + '23': { italic: false } , + '4': { underline: true } , + '24': { underline: false } , + '5': { blink: true } , + '25': { blink: false } , + '7': { inverse: true } , + '27': { inverse: false } , + '8': { hidden: true } , + '28': { hidden: false } , + '9': { strike: true } , + '29': { strike: false } , + + '30': { color: 0 } , + '31': { color: 1 } , + '32': { color: 2 } , + '33': { color: 3 } , + '34': { color: 4 } , + '35': { color: 5 } , + '36': { color: 6 } , + '37': { color: 7 } , + '39:': { defaultColor: true } , + + '90': { color: 8 } , + '91': { color: 9 } , + '92': { color: 10 } , + '93': { color: 11 } , + '94': { color: 12 } , + '95': { color: 13 } , + '96': { color: 14 } , + '97': { color: 15 } , + + '40': { bgColor: 0 } , + '41': { bgColor: 1 } , + '42': { bgColor: 2 } , + '43': { bgColor: 3 } , + '44': { bgColor: 4 } , + '45': { bgColor: 5 } , + '46': { bgColor: 6 } , + '47': { bgColor: 7 } , + '49:': { bgDefaultColor: true } , + + '100': { bgColor: 8 } , + '101': { bgColor: 9 } , + '102': { bgColor: 10 } , + '103': { bgColor: 11 } , + '104': { bgColor: 12 } , + '105': { bgColor: 13 } , + '106': { bgColor: 14 } , + '107': { bgColor: 15 } +} ; + + + +// /!\ Should be moved to string-kit once finished /!\ +const parseAnsiRegexp = /\x1b\[([0-9;]+)m|(.[^\x1b]*)/g ; + +misc.parseAnsi = str => { + var match , ansiCodes , raw , output = [] ; + + parseAnsiRegexp.lastIndex = 0 ; + + while ( ( match = parseAnsiRegexp.exec( str ) ) ) { + [ , ansiCodes , raw ] = match ; + + if ( raw ) { output.push( raw ) ; } + else { + ansiCodes.split( ';' ).forEach( ansiCode => { + if ( ANSI_CODES[ ansiCode ] ) { output.push( { markup: ANSI_CODES[ ansiCode ] } ) ; } + } ) ; + } + } + + return output ; +} ; + + +},{"string-kit":123}],43:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const Promise = require( 'seventh' ) ; + +// Patch the child process module to support asyncness +Promise.promisifyNodeApi( require( 'child_process' ) ) ; + + +},{"child_process":136,"seventh":108}],44:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +//var string = require( 'string-kit' ) ; + + + +/* + progressBar( options ) + * options `object` of options, all of them are OPTIONAL, where: + * width: `number` the total width of the progress bar, default to the max available width + * percent: `boolean` if true, it shows the progress in percent alongside with the progress bar + * eta: `boolean` if true, it shows the Estimated Time of Arrival alongside with the progress bar + * items `number` the number of items, turns the 'item mode' on + * title `string` the title of the current progress bar, turns the 'title mode' on + * barStyle `function` the style of the progress bar items, default to `term.cyan` + * barBracketStyle `function` the style of the progress bar bracket character, default to options.barStyle if given + or `term.blue` + * percentStyle `function` the style of percent value string, default to `term.yellow` + * etaStyle `function` the style of the ETA display, default to `term.bold` + * itemStyle `function` the style of the item display, default to `term.dim` + * titleStyle `function` the style of the title display, default to `term.bold` + * itemSize `number` the size of the item status, default to 33% of width + * titleSize `number` the size of the title, default to 33% of width or title.length depending on context + * barChar `string` the char used for the bar, default to '=' + * barHeadChar `string` the char used for the bar, default to '>' + * maxRefreshTime `number` the maximum time between two refresh in ms, default to 500ms + * minRefreshTime `number` the minimum time between two refresh in ms, default to 100ms + * inline `boolean` (default: false) if true it is not locked in place, i.e. it redraws itself on the current line + * syncMode `boolean` true if it should work in sync mode + * y `integer` if set, display the progressBar on that line y-coord + * x `integer` if set and the 'y' option is set, display the progressBar starting on that x-coord +*/ +module.exports = function progressBar_( options ) { + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + var controller = {} , progress , ready = false , pause = false , + maxItems , itemsDone = 0 , itemsStarted = [] , itemFiller , + title , titleFiller , + width , y , startX , endX , oldWidth , + wheel , wheelCounter = 0 , itemRollCounter = 0 , + updateCount = 0 , progressUpdateCount = 0 , + lastUpdateTime , lastRedrawTime , + startingTime , redrawTimer , + etaStartingTime , lastEta , etaFiller ; + + etaStartingTime = startingTime = ( new Date() ).getTime() ; + + wheel = [ '|' , '/' , '-' , '\\' ] ; + + options.syncMode = !! options.syncMode ; + + width = options.width || this.width - 1 ; + + if ( ! options.barBracketStyle ) { + if ( options.barStyle ) { options.barBracketStyle = options.barStyle ; } + else { options.barBracketStyle = this.blue ; } + } + + if ( ! options.barStyle ) { options.barStyle = this.cyan ; } + if ( ! options.percentStyle ) { options.percentStyle = this.yellow ; } + if ( ! options.etaStyle ) { options.etaStyle = this.bold ; } + if ( ! options.itemStyle ) { options.itemStyle = this.dim ; } + if ( ! options.titleStyle ) { options.titleStyle = this.bold ; } + + if ( ! options.barChar ) { options.barChar = '=' ; } + else { options.barChar = options.barChar[ 0 ] ; } + + if ( ! options.barHeadChar ) { options.barHeadChar = '>' ; } + else { options.barHeadChar = options.barHeadChar[ 0 ] ; } + + if ( typeof options.maxRefreshTime !== 'number' ) { options.maxRefreshTime = 500 ; } + if ( typeof options.minRefreshTime !== 'number' ) { options.minRefreshTime = 100 ; } + + if ( typeof options.items === 'number' ) { maxItems = options.items ; } + if ( maxItems && typeof options.itemSize !== 'number' ) { options.itemSize = Math.round( width / 3 ) ; } + + itemFiller = ' '.repeat( options.itemSize ) ; + + + if ( options.title && typeof options.title === 'string' ) { + title = options.title ; + + if ( typeof options.titleSize !== 'number' ) { + options.titleSize = Math.round( Math.min( options.title.length + 1 , width / 3 ) ) ; + } + } + + titleFiller = ' '.repeat( options.titleSize ) ; + + + etaFiller = ' ' ; // 11 chars + + // This is a naive ETA for instance... + var etaString = updated => { + var eta = '' , elapsedTime , elapsedEtaTime , remainingTime , + averageUpdateDelay , averageUpdateProgress , lastUpdateElapsedTime , fakeProgress ; + + if ( progress >= 1 ) { + eta = ' done' ; + } + else if ( progress > 0 ) { + elapsedTime = ( new Date() ).getTime() - startingTime ; + elapsedEtaTime = ( new Date() ).getTime() - etaStartingTime ; + + if ( ! updated && progressUpdateCount > 1 ) { + lastUpdateElapsedTime = ( new Date() ).getTime() - lastUpdateTime ; + averageUpdateDelay = elapsedEtaTime / progressUpdateCount ; + averageUpdateProgress = progress / progressUpdateCount ; + + //console.log( '\n' , elapsedEtaTime , lastUpdateElapsedTime , averageUpdateDelay , averageUpdateProgress , '\n' ) ; + + // Do not update ETA if it's not an update, except if update time is too long + if ( lastUpdateElapsedTime < averageUpdateDelay ) { + fakeProgress = progress + averageUpdateProgress * lastUpdateElapsedTime / averageUpdateDelay ; + } + else { + fakeProgress = progress + averageUpdateProgress ; + } + + if ( fakeProgress > 0.99 ) { fakeProgress = 0.99 ; } + } + else { + fakeProgress = progress ; + } + + remainingTime = elapsedEtaTime * ( ( 1 - fakeProgress ) / fakeProgress ) / 1000 ; + + eta = ' in ' ; + + if ( remainingTime < 10 ) { eta += Math.round( remainingTime * 10 ) / 10 + 's' ; } + else if ( remainingTime < 120 ) { eta += Math.round( remainingTime ) + 's' ; } + else if ( remainingTime < 7200 ) { eta += Math.round( remainingTime / 60 ) + 'min' ; } + else if ( remainingTime < 172800 ) { eta += Math.round( remainingTime / 3600 ) + 'hours' ; } + else if ( remainingTime < 31536000 ) { eta += Math.round( remainingTime / 86400 ) + 'days' ; } + else { eta = 'few years' ; } + } + else { + etaStartingTime = ( new Date() ).getTime() ; + } + + eta = ( eta + etaFiller ).slice( 0 , etaFiller.length ) ; + lastEta = eta ; + + return eta ; + } ; + + + + var redraw = updated => { + var time , itemIndex , itemName = itemFiller , titleName = titleFiller , + innerBarSize , progressSize , voidSize , + progressBar = '' , voidBar = '' , percent = '' , eta = '' ; + + if ( ! ready || pause ) { return ; } + + time = ( new Date() ).getTime() ; + + // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit) + if ( ( ! progress || progress < 1 ) && lastRedrawTime && time < lastRedrawTime + options.minRefreshTime ) { + if ( ! options.syncMode ) { + if ( redrawTimer ) { clearTimeout( redrawTimer ) ; } + redrawTimer = setTimeout( redraw.bind( this , updated ) , lastRedrawTime + options.minRefreshTime - time ) ; + } + return ; + } + + + this.saveCursor() ; + + // If 'y' is null, we are in the blind mode, we haven't get the cursor location + if ( y === null ) { this.column( startX ) ; } + else { this.moveTo( startX , y ) ; } + + //this.noFormat( Math.floor( progress * 100 ) + '%' ) ; + + innerBarSize = width - 2 ; + + if ( options.percent ) { + innerBarSize -= 4 ; + percent = ( ' ' + Math.round( ( progress || 0 ) * 100 ) + '%' ).slice( -4 ) ; + } + + if ( options.eta ) { + eta = etaString( updated ) ; + innerBarSize -= eta.length ; + } + + innerBarSize -= options.itemSize || 0 ; + if ( maxItems ) { + if ( ! itemsStarted.length ) { + itemName = '' ; + } + else if ( itemsStarted.length === 1 ) { + itemName = ' ' + itemsStarted[ 0 ] ; + } + else { + itemIndex = ( itemRollCounter ++ ) % itemsStarted.length ; + itemName = ' [' + ( itemIndex + 1 ) + '/' + itemsStarted.length + '] ' + itemsStarted[ itemIndex ] ; + } + + if ( itemName.length > itemFiller.length ) { itemName = itemName.slice( 0 , itemFiller.length - 1 ) + '…' ; } + else if ( itemName.length < itemFiller.length ) { itemName = ( itemName + itemFiller ).slice( 0 , itemFiller.length ) ; } + } + + innerBarSize -= options.titleSize || 0 ; + if ( title ) { + titleName = title ; + + if ( titleName.length >= titleFiller.length ) { titleName = titleName.slice( 0 , titleFiller.length - 2 ) + '… ' ; } + else { titleName = ( titleName + titleFiller ).slice( 0 , titleFiller.length ) ; } + } + + progressSize = progress === undefined ? 1 : Math.round( innerBarSize * Math.max( Math.min( progress , 1 ) , 0 ) ) ; + voidSize = innerBarSize - progressSize ; + + /* + console.log( "Size:" , width , + voidSize , innerBarSize , progressSize , eta.length , title.length , itemName.length , + voidSize + progressSize + eta.length + title.length + itemName.length + ) ; + //*/ + + if ( progressSize ) { + if ( progress === undefined ) { + progressBar = wheel[ ++ wheelCounter % wheel.length ] ; + } + else { + progressBar += options.barChar.repeat( progressSize - 1 ) ; + progressBar += options.barHeadChar ; + } + } + + voidBar += ' '.repeat( voidSize ) ; + + options.titleStyle( titleName ) ; + + if ( percent ) { options.percentStyle( percent ) ; } + + if ( progress === undefined ) { this( ' ' ) ; } + else { options.barBracketStyle( '[' ) ; } + + options.barStyle( progressBar ) ; + this( voidBar ) ; + + if ( progress === undefined ) { this( ' ' ) ; /*this( '+' ) ;*/ } + else { options.barBracketStyle( ']' ) ; } + + options.etaStyle( eta ) ; + //this( '*' ) ; + options.itemStyle( itemName ) ; + //this( '&' ) ; + + this.restoreCursor() ; + + if ( ! options.syncMode ) { + if ( redrawTimer ) { clearTimeout( redrawTimer ) ; } + if ( ! progress || progress < 1 ) { redrawTimer = setTimeout( redraw , options.maxRefreshTime ) ; } + } + + lastRedrawTime = time ; + } ; + + + if ( options.syncMode || options.inline || options.y ) { + oldWidth = width ; + + if ( options.y ) { + startX = + options.x || 1 ; + y = + options.y || 1 ; + } + else { + startX = 1 ; + y = null ; + } + + endX = Math.min( startX + width , this.width ) ; + width = endX - startX ; + + if ( width !== oldWidth ) { + // Should resize all part here + if ( options.titleSize ) { options.titleSize = Math.floor( options.titleSize * width / oldWidth ) ; } + if ( options.itemSize ) { options.itemSize = Math.floor( options.itemSize * width / oldWidth ) ; } + } + + ready = true ; + redraw() ; + } + else { + // Get the cursor location before getting started + this.getCursorLocation( ( error , x_ , y_ ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // So we just move to the last line and create a new line. + //cleanup( error ) ; return ; + this.row.eraseLineAfter( this.height )( '\n' ) ; + x_ = 1 ; + y_ = this.height ; + } + + var oldWidth_ = width ; + + startX = x_ ; + endX = Math.min( x_ + width , this.width ) ; + y = y_ ; + width = endX - startX ; + + if ( width !== oldWidth_ ) { + // Should resize all part here + if ( options.titleSize ) { options.titleSize = Math.floor( options.titleSize * width / oldWidth_ ) ; } + if ( options.itemSize ) { options.itemSize = Math.floor( options.itemSize * width / oldWidth_ ) ; } + } + + ready = true ; + redraw() ; + } ) ; + } + + controller.startItem = name => { + itemsStarted.push( name ) ; + + // No need to redraw NOW if there are other items running. + // Let the timer do the job. + if ( itemsStarted.length === 1 ) { + // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit) + if ( progress >= 1 ) { redraw() ; return ; } + + if ( options.syncMode ) { + redraw() ; + } + else { + // Using a setTimeout with a 0ms time and redrawTimer clearing has a nice effect: + // if multiple synchronous update are performed, redraw will be called once + if ( redrawTimer ) { clearTimeout( redrawTimer ) ; } + redrawTimer = setTimeout( redraw , 0 ) ; + } + } + } ; + + controller.itemDone = name => { + var index ; + + itemsDone ++ ; + + if ( maxItems ) { progress = itemsDone / maxItems ; } + else { progress = undefined ; } + + lastUpdateTime = ( new Date() ).getTime() ; + updateCount ++ ; + progressUpdateCount ++ ; + + index = itemsStarted.indexOf( name ) ; + if ( index >= 0 ) { itemsStarted.splice( index , 1 ) ; } + + // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit) + if ( progress >= 1 ) { redraw( true ) ; return ; } + + if ( options.syncMode ) { + redraw() ; + } + else { + // Using a setTimeout with a 0ms time and redrawTimer clearing has a nice effect: + // if multiple synchronous update are performed, redraw will be called once + if ( redrawTimer ) { clearTimeout( redrawTimer ) ; } + redrawTimer = setTimeout( redraw.bind( this , true ) , 0 ) ; + } + } ; + + controller.update = toUpdate => { + if ( ! toUpdate ) { toUpdate = {} ; } + else if ( typeof toUpdate === 'number' ) { toUpdate = { progress: toUpdate } ; } + + if ( 'progress' in toUpdate ) { + if ( typeof toUpdate.progress !== 'number' ) { + progress = undefined ; + } + else { + // Not sure if it is a good thing to let the user set progress to a value that is lesser than the current one + progress = toUpdate.progress ; + + if ( progress > 1 ) { progress = 1 ; } + else if ( progress < 0 ) { progress = 0 ; } + + if ( progress > 0 ) { progressUpdateCount ++ ; } + + lastUpdateTime = ( new Date() ).getTime() ; + updateCount ++ ; + } + } + + if ( typeof toUpdate.items === 'number' ) { + maxItems = toUpdate.items ; + if ( maxItems ) { progress = itemsDone / maxItems ; } + + if ( typeof options.itemSize !== 'number' ) { + options.itemSize = Math.round( width / 3 ) ; + itemFiller = ' '.repeat( options.itemSize ) ; + } + } + + if ( typeof toUpdate.title === 'string' ) { + title = toUpdate.title ; + + if ( typeof options.titleSize !== 'number' ) { + options.titleSize = Math.round( width / 3 ) ; + titleFiller = ' '.repeat( options.titleSize ) ; + } + } + + // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit) + if ( progress >= 1 ) { redraw( true ) ; return ; } + + if ( options.syncMode ) { + redraw() ; + } + else { + // Using a setTimeout with a 0ms time and redrawTimer clearing has a nice effect: + // if multiple synchronous update are performed, redraw will be called once + if ( redrawTimer ) { clearTimeout( redrawTimer ) ; } + redrawTimer = setTimeout( redraw.bind( this , true ) , 0 ) ; + } + } ; + + controller.pause = controller.stop = () => { + pause = true ; + } ; + + controller.resume = () => { + if ( pause ) { + pause = false ; + redraw() ; + } + } ; + + controller.reset = () => { + etaStartingTime = startingTime = ( new Date() ).getTime() ; + itemsDone = 0 ; + progress = undefined ; + itemsStarted.length = 0 ; + wheelCounter = itemRollCounter = updateCount = progressUpdateCount = 0 ; + redraw() ; + } ; + + return controller ; +} ; + + +},{}],45:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const termkit = require( './termkit' ) ; +const stringWidth = termkit.stringWidth ; +const string = require( 'string-kit' ) ; +const NextGenEvents = require( 'nextgen-events' ) ; +const Promise = require( 'seventh' ) ; + + + +const defaultKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + UP: 'previous' , + DOWN: 'next' , + TAB: 'cycleNext' , + SHIFT_TAB: 'cyclePrevious' , + HOME: 'first' , + END: 'last' , + BACKSPACE: 'cancel' , + DELETE: 'cancel' , + ESCAPE: 'escape' +} ; + + + +/* + singleColumnMenu( menuItems , [options] , callback ) + * menuItems `array` of menu item text + * options `object` of options, where: + * y `number` the line where the menu will be displayed, default to the next line + * style `function` the style of unselected items, default to `term` + * selectedStyle `function` the style of the selected item, default to `term.inverse` + * submittedStyle `function` the style of the submitted item, default to `term.bgGray.bold` + * disabledStyle `function` the style of unselected items **when the menu is paused/disabled**, + default to `term.dim` + * disabledSelectedStyle `function` the style of the selected item **when the menu is paused/disabled**, + default to `term.bgGray.dim` + * disabledSubmittedStyle `function` the style of the submitted item **when the menu is paused/disabled**, + default to `term.bgGray` + * leftPadding `string` the text to put before a menu item, default to ' ' + * selectedLeftPadding `string` the text to put before a selected menu item, default to ' ' + * submittedLeftPadding `string` the text to put before a submitted menu item, default to ' ' + * extraLines `number` ensure that many lines after the bottom of the menu + * oneLineItem `boolean` if true (default: false), big items do not span multiple lines, instead they are truncated + and ended with an ellipsis char + * itemMaxWidth `number` the max width for an item, default to the terminal width + * continueOnSubmit `boolean` if true, the submit action does not end the menu, the callback argument is ignored. + The 'submit' event should be listened instead. + * selectedIndex `number` selected index at initialization (default: 0) + * unsubmittableIndexes `Array` of `boolean` indexes that are not submittable (default: []) + * submitted `boolean` if true, selected index is already submitted at initialization (default: false) + * paused `boolean` (default: false) true if the menu start in paused/disabled mode + * scrollRegionBottom `number` if set, it indicates the bottom line of the current scroll region + * keyBindings `Object` overide default key bindings + * cancelable `boolean` if ESCAPE is pressed, it exits, calling the callback with undefined values + * exitOnUnexpectedKey `boolean` if an unexpected key is pressed, it exits, calling the callback with undefined values + * callback( error , response ), where: + * error + * response `Object` where: + * selectedIndex `number` the user-selected menu item index + * selectedText `string` the user-selected menu item text + * x `number` the x coordinate of the selected menu item (the first character) + * y `number` the y coordinate of the selected menu item + * unexpectedKey `string` when 'exitOnUnexpectedKey' option is set, this contains the key that produced the exit + * canceled `bool` when 'cancelable' option is set, this is set to true +*/ +module.exports = function singleColumnMenu( menuItemsArg , options , callback ) { + if ( arguments.length < 1 ) { throw new Error( '[terminal] singleColumnMenu() needs at least an array of menuItems' ) ; } + if ( ! Array.isArray( menuItemsArg ) || ! menuItemsArg.length ) { throw new TypeError( '[terminal] singleColumnMenu(): argument #0 should be a non-empty array' ) ; } + + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + else if ( ! options || typeof options !== 'object' ) { options = {} ; } + + if ( ! options.style ) { options.style = this ; } + if ( ! options.selectedStyle ) { options.selectedStyle = this.inverse ; } + if ( ! options.submittedStyle ) { options.submittedStyle = this.bgGray.bold ; } + if ( ! options.disabledStyle ) { options.disabledStyle = this.dim ; } + if ( ! options.disabledSelectedStyle ) { options.disabledSelectedStyle = this.bgGray.dim ; } + if ( ! options.disabledSubmittedStyle ) { options.disabledSubmittedStyle = this.bgGray ; } + + if ( options.leftPadding === undefined ) { options.leftPadding = ' ' ; } + if ( options.selectedLeftPadding === undefined ) { options.selectedLeftPadding = options.leftPadding ; } + if ( options.submittedLeftPadding === undefined ) { options.submittedLeftPadding = options.leftPadding ; } + + if ( typeof options.extraLines !== 'number' || options.extraLines < 0 ) { options.extraLines = 1 ; } + + if ( ! options.itemMaxWidth ) { options.itemMaxWidth = this.width - 1 ; } + + if ( ! options.unsubmittableIndexes ) { options.unsubmittableIndexes = [] ; } + + var selectedIndex = options.selectedIndex || 0 ; + var submittedIndex = options.submitted ? options.selectedIndex : null ; + var paused = !! options.paused ; + + var keyBindings = options.keyBindings || defaultKeyBindings ; + + if ( ! this.grabbing ) { this.grabInput() ; } + + + var start = {} , end = {} , textWidth , outerWidth , paddingLength , + menuItems , offsetY = 0 , lineCount = 0 , scrollLines = 0 , + controller , finished = false , alreadyCleanedUp = false ; + + + + // Functions... + + + + var init = () => { + computeItems( menuItemsArg ) ; + + if ( options.y !== undefined ) { + this.moveTo( 1 , options.y ) ; + finishInit( 1 , options.y ) ; + } + else { + this( '\n' ) ; + this.getCursorLocation( ( error , x , y ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // So we just move to the last line and create a new line. + //cleanup( error ) ; return ; + this.row.eraseLineAfter( this.height )( '\n' ) ; + x = 1 ; + y = this.height ; + } + + finishInit( x , y ) ; + } ) ; + } + } ; + + + + var computeItems = ( menuItems_ ) => { + textWidth = 0 ; + + paddingLength = Math.max( stringWidth( options.leftPadding ) , stringWidth( options.selectedLeftPadding ) ) ; + + menuItems_ = menuItems_.map( element => { + if ( typeof element !== 'string' ) { element = '' + element ; } + textWidth = Math.max( textWidth , stringWidth( element ) ) ; + return element ; + } ) ; + + if ( ! options.oneLineItem && textWidth > options.itemMaxWidth - paddingLength ) { + outerWidth = Math.min( textWidth + paddingLength , this.width ) ; + + menuItems = menuItems_.map( ( element , index ) => { + + var item , lines , + lineLength = options.itemMaxWidth - paddingLength ; + + lines = string.wordwrap( element , { + width: lineLength , + noJoin: true , + fill: true , + skipFn: termkit.escapeSequenceSkipFn + } ) ; + + item = { + offsetY: offsetY , + index: index , + text: element , + displayText: lines + } ; + + offsetY += lines.length ; + + return item ; + } ) ; + + lineCount = offsetY ; + } + else { + textWidth = Math.min( textWidth , options.itemMaxWidth - paddingLength ) ; + outerWidth = Math.min( textWidth + paddingLength , this.width ) ; + + menuItems = menuItems_.map( ( element , index ) => { + var elementWidth = stringWidth( element ) ; + + return { + offsetY: index , + index: index , + text: element , + displayText: [ elementWidth > textWidth ? + element.slice( 0 , textWidth - 1 ) + '…' : + element + ' '.repeat( textWidth - elementWidth ) ] + } ; + } ) ; + + lineCount = menuItems.length ; + } + } ; + + + + var finishInit = ( x , y ) => { + // It is possible for userland to end the menu immediately + if ( finished ) { return ; } + + prepareArea( x , y ) ; + redraw() ; + + this.on( 'key' , onKey ) ; + if ( this.mouseGrabbing ) { this.on( 'mouse' , onMouse ) ; } + + controller.emit( 'ready' ) ; + emitHighlight() ; + } ; + + + + var emitHighlight = () => { + var item = menuItems[ selectedIndex ] ; + + controller.emit( 'highlight' , { + highlightedIndex: item.index , + highlightedText: item.text , + submitted: submittedIndex !== null , + x: 1 , + y: start.y + item.offsetY + } ) ; + + } ; + + + + var prepareArea = ( x , y ) => { + start.x = x ; + start.y = y ; + + end.x = 1 ; + end.y = y + lineCount ; + + scrollLines = start.y + lineCount - ( options.scrollRegionBottom || this.height ) - 1 + options.extraLines ; + + if ( scrollLines > 0 ) { + // create extra lines + this( '\n'.repeat( scrollLines ) ) ; + start.y -= scrollLines ; + end.y -= scrollLines ; + } + } ; + + + + var cleanup = ( error , data , eraseMenu ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + finished = true ; + this.removeListener( 'key' , onKey ) ; + this.removeListener( 'mouse' , onMouse ) ; + + if ( error === 'abort' ) { return ; } + + if ( controller.hasState( 'ready' ) ) { + if ( eraseMenu ) { erase() ; } + else { this.moveTo( 1 , end.y ) ; } + } + + if ( error ) { + if ( callback ) { callback( error ) ; } + else { controller.promise.reject( error ) ; } + return ; + } + + var value = data !== undefined ? data : { + selectedIndex: selectedIndex , + selectedText: menuItems[ selectedIndex ].text , + submitted: submittedIndex !== null , + x: 1 , + y: start.y + menuItems[ selectedIndex ].offsetY + } ; + + if ( callback ) { callback( undefined , value ) ; } + else { controller.promise.resolve( value ) ; } + } ; + + + + var erase = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , erase ) ; return ; } + + var i , j ; + + for ( i = start.x , j = start.y ; j <= end.y ; i = 1 , j ++ ) { + this.moveTo.eraseLineAfter( i , j ) ; + } + + this.moveTo( 1 , start.y ) ; + } ; + + + + // Compute the coordinate of the end of a string, given a start coordinate + var redraw = () => { + for ( var i = 0 ; i < menuItems.length ; i ++ ) { redrawItem( i ) ; } + redrawCursor() ; + } ; + + + + var redrawItem = ( index ) => { + + // Called by finishInit before emitting 'ready' + //if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , redrawItem.bind( undefined , index ) ) ; return ; } + + var item = menuItems[ index ] ; + + item.displayText.forEach( ( text , line ) => { + + this.moveTo( 1 , start.y + item.offsetY + line ) ; + + if ( paused || options.unsubmittableIndexes[ index ] ) { + if ( index === submittedIndex ) { + if ( line ) { options.disabledSubmittedStyle.forceStyleOnReset.noFormat( options.leftPadding ) ; } + else { options.disabledSubmittedStyle.forceStyleOnReset.noFormat( options.submittedLeftPadding ) ; } + + options.disabledSubmittedStyle.forceStyleOnReset.noFormat( text ) ; + } + else if ( index === selectedIndex ) { + if ( line ) { options.disabledSelectedStyle.forceStyleOnReset.noFormat( options.leftPadding ) ; } + else { options.disabledSelectedStyle.forceStyleOnReset.noFormat( options.selectedLeftPadding ) ; } + + options.disabledSelectedStyle.forceStyleOnReset.noFormat( text ) ; + } + else { + options.disabledStyle.forceStyleOnReset.noFormat( options.leftPadding ) ; + options.disabledStyle.forceStyleOnReset.noFormat( text ) ; + } + } + else if ( index === submittedIndex ) { + if ( line ) { options.submittedStyle.forceStyleOnReset.noFormat( options.leftPadding ) ; } + else { options.submittedStyle.forceStyleOnReset.noFormat( options.submittedLeftPadding ) ; } + + options.submittedStyle.forceStyleOnReset.noFormat( text ) ; + } + else if ( index === selectedIndex ) { + if ( line ) { options.selectedStyle.forceStyleOnReset.noFormat( options.leftPadding ) ; } + else { options.selectedStyle.forceStyleOnReset.noFormat( options.selectedLeftPadding ) ; } + + options.selectedStyle.forceStyleOnReset.noFormat( text ) ; + } + else { + options.style.forceStyleOnReset.noFormat( options.leftPadding ) ; + options.style.forceStyleOnReset.noFormat( text ) ; + } + } ) ; + } ; + + + + var redrawCursor = () => { + // Called by finishInit before emitting 'ready' + //if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , redrawCursor ) ; return ; } + this.moveTo( 1 , start.y + menuItems[ selectedIndex ].offsetY ) ; + } ; + + + + var select = ( index ) => { + var oldSelectedIndex = selectedIndex ; + + if ( selectedIndex !== index && index >= 0 && index < menuItems.length ) { + selectedIndex = index ; + + // Don't redraw now if not ready, it will be drawn once ready (avoid double-draw) + if ( controller.hasState( 'ready' ) ) { + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + emitHighlight() ; + } + } + } ; + + + + var submit = () => { + if ( submittedIndex !== null || options.unsubmittableIndexes[ selectedIndex ] ) { return ; } + submittedIndex = selectedIndex ; + + // Don't redraw now if not ready, it will be drawn once ready (avoid double-draw) + if ( controller.hasState( 'ready' ) ) { + redrawItem( submittedIndex ) ; + redrawCursor() ; + } + + controller.emit( 'submit' , { + selectedIndex: submittedIndex , + selectedText: menuItems[ submittedIndex ].text , + submitted: true , + x: 1 , + y: start.y + menuItems[ submittedIndex ].offsetY + } ) ; + + if ( ! options.continueOnSubmit ) { cleanup() ; } + } ; + + + + var cancel = () => { + var oldSelectedIndex = submittedIndex ; + + if ( submittedIndex === null ) { return ; } + submittedIndex = null ; + redrawItem( oldSelectedIndex ) ; + redrawCursor() ; + controller.emit( 'cancel' ) ; + } ; + + + + var pause = () => { + if ( paused ) { return ; } + paused = true ; + + // Don't redraw now if not ready, it will be drawn once ready (avoid double-draw) + if ( controller.hasState( 'ready' ) ) { redraw() ; } + } ; + + + + var resume = () => { + if ( ! paused ) { return ; } + paused = false ; + + // Don't redraw now if not ready, it will be drawn once ready (avoid double-draw) + if ( controller.hasState( 'ready' ) ) { redraw() ; } + } ; + + + + var onKey = ( key , trash , data ) => { + + if ( finished || paused ) { return ; } + + var oldSelectedIndex = selectedIndex ; + + switch ( keyBindings[ key ] ) { + case 'submit' : + submit() ; + break ; + + case 'previous' : + if ( submittedIndex !== null ) { return ; } + if ( selectedIndex > 0 ) { + selectedIndex -- ; + redrawItem( selectedIndex ) ; + redrawItem( selectedIndex + 1 ) ; + redrawCursor() ; + //redraw() ; + emitHighlight() ; + } + break ; + + case 'next' : + if ( submittedIndex !== null ) { return ; } + if ( selectedIndex < menuItems.length - 1 ) { + selectedIndex ++ ; + redrawItem( selectedIndex - 1 ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + emitHighlight() ; + } + break ; + + case 'cyclePrevious' : + if ( submittedIndex !== null ) { return ; } + selectedIndex -- ; + + if ( selectedIndex < 0 ) { selectedIndex = menuItems.length - 1 ; } + + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + emitHighlight() ; + break ; + + case 'cycleNext' : + if ( submittedIndex !== null ) { return ; } + selectedIndex ++ ; + + if ( selectedIndex >= menuItems.length ) { selectedIndex = 0 ; } + + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + emitHighlight() ; + break ; + + case 'first' : + if ( submittedIndex !== null ) { return ; } + if ( selectedIndex !== 0 ) { + selectedIndex = 0 ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + emitHighlight() ; + } + break ; + + case 'last' : + if ( submittedIndex !== null ) { return ; } + if ( selectedIndex !== menuItems.length - 1 ) { + selectedIndex = menuItems.length - 1 ; + redrawItem( oldSelectedIndex ) ; + redrawItem( selectedIndex ) ; + redrawCursor() ; + //redraw() ; + emitHighlight() ; + } + break ; + + case 'cancel' : + cancel() ; + break ; + + case 'escape' : + if ( options.cancelable ) { + cleanup( undefined , { canceled: true } ) ; + } + if ( options.exitOnUnexpectedKey ) { + cleanup( undefined , { unexpectedKey: key , unexpectedKeyData: data } ) ; + } + break ; + + default : + if ( options.exitOnUnexpectedKey ) { + cleanup( undefined , { unexpectedKey: key , unexpectedKeyData: data } ) ; + } + break ; + } + } ; + + + + var onMouse = ( name , data ) => { + + if ( finished || paused || submittedIndex !== null ) { return ; } + + // If out of bounds, exit now! + if ( data.y < start.y || data.y >= end.y ) { return ; } + + var i , yMin , yMax , + inBounds = false ; + + for ( i = 0 ; i < menuItems.length ; i ++ ) { + yMin = start.y + menuItems[ i ].offsetY ; + yMax = start.y + menuItems[ i ].offsetY + menuItems[ i ].displayText.length - 1 ; + + if ( data.y >= yMin && data.y <= yMax && data.x < 1 + outerWidth ) { + inBounds = true ; + select( i ) ; + break ; + } + } + + if ( inBounds && name === 'MOUSE_LEFT_BUTTON_PRESSED' ) { + submit() ; + } + } ; + + + + // Return a controller for the menu + + controller = Object.create( NextGenEvents.prototype ) ; + + controller.defineStates( 'ready' ) ; + + // Stop everything and do not even call the callback + controller.abort = () => { + if ( finished ) { return ; } + cleanup( 'abort' ) ; + } ; + + // Stop and call the completion callback with no item + controller.stop = ( eraseMenu ) => { + if ( finished ) { return ; } + cleanup( undefined , undefined , eraseMenu ) ; + } ; + + controller.select = select ; + controller.submit = submit ; + controller.cancel = cancel ; + controller.erase = erase ; + + // Pause and resume: the menu will not respond to event when paused + controller.pause = pause ; + controller.resume = resume ; + controller.focus = ( value ) => { + if ( value ) { resume() ; } + else { pause() ; } + } ; + + // Get the current state + controller.getState = () => ( { + selectedIndex: selectedIndex , + selectedText: menuItems[ selectedIndex ].text , + submitted: submittedIndex !== null , + start: start , + end: end , + x: 1 , + y: start.y + menuItems[ selectedIndex ].offsetY + //scrollLines: scrollLines + } ) ; + + // Get the current position + controller.getPosition = () => ( { x: start.x , y: start.y } ) ; + + // Hide the menu + controller.hide = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.hide ) ; return ; } + erase() ; + } ; + + // Show the menu + controller.show = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.show ) ; return ; } + redraw() ; + } ; + + // Redraw the menu + controller.redraw = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.redraw ) ; return ; } + redraw() ; + } ; + + // Redraw the cursor + controller.redrawCursor = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.redrawCursor ) ; return ; } + redrawCursor() ; + } ; + + // Rebase the menu where the cursor is + controller.rebase = () => { + if ( ! controller.hasState( 'ready' ) ) { controller.once( 'ready' , controller.rebase ) ; return ; } + + // First, disable the menu: getCursorLocation is async! + var wasPaused = paused ; + paused = true ; + + this.getCursorLocation( ( error , x , y ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // Here we just ignore the rebase. + //cleanup( error ) ; + return ; + } + + paused = wasPaused ; + prepareArea( x , y ) ; + redraw() ; + controller.emit( 'rebased' ) ; + } ) ; + } ; + + controller.promise = new Promise() ; + + // Init the menu + init() ; + + return controller ; +} ; + + +},{"./termkit":50,"nextgen-events":72,"seventh":108,"string-kit":123}],46:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const termkit = require( './termkit' ) ; +const stringWidth = termkit.stringWidth ; +const NextGenEvents = require( 'nextgen-events' ) ; +const Promise = require( 'seventh' ) ; + + + +const defaultKeyBindings = { + ENTER: 'submit' , + KP_ENTER: 'submit' , + LEFT: 'previous' , + RIGHT: 'next' , + UP: 'previousPage' , + DOWN: 'nextPage' , + TAB: 'cycleNext' , + SHIFT_TAB: 'cyclePrevious' , + HOME: 'first' , + END: 'last' , + ESCAPE: 'escape' +} ; + + + +/* + singleLineMenu( menuItems , [options] , callback ) + * menuItems `array` of menu item text + * options `object` of options, where: + * y `number` the line where the menu will be displayed, default to the next line + * separator `string` (default: ' ') the string separating each menu item + * nextPageHint `string` (default: ' » ') string indicator for a next page + * previousPageHint `string` (default: ' « ') string indicator for a previous page + * style `function` the style of unselected items, default to `term` + * selectedStyle `function` the style of the selected item, default to `term.dim.blue.bgGreen` + * selectedIndex `number` selected index at initialization (default: 0) + * align `string` one of 'left' (default), 'right' or 'center', align the menu accordingly + * fillIn `boolean` if true (default: false), the menu will fill in the whole line with white chars + * keyBindings `Object` overide default key bindings + * cancelable `boolean` if ESCAPE is pressed, it exits, calling the callback with undefined values + * exitOnUnexpectedKey `boolean` if an unexpected key is pressed, it exits, calling the callback with undefined values + * callback( error , response ), where: + * error + * response `Object` where: + * selectedIndex `number` the user-selected menu item index + * selectedText `string` the user-selected menu item text + * x `number` the x coordinate of the selected menu item (the first character) + * y `number` the y coordinate of the selected menu item (same coordinate for all items since it's a single line menu) + * unexpectedKey `string` when 'exitOnUnexpectedKey' option is set, this contains the key that produced the exit + * canceled `bool` when 'cancelable' option is set, this is set to true +*/ +module.exports = function singleLineMenu( menuItems_ , options , callback ) { + if ( arguments.length < 1 ) { throw new Error( '[terminal] singleLineMenu() needs at least an array of menuItems' ) ; } + if ( ! Array.isArray( menuItems_ ) || ! menuItems_.length ) { throw new TypeError( '[terminal] singleLineMenu(): argument #0 should be a non-empty array' ) ; } + + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + else if ( ! options || typeof options !== 'object' ) { options = {} ; } + + if ( options.separator === undefined ) { options.separator = ' ' ; } + if ( options.nextPageHint === undefined ) { options.nextPageHint = ' » ' ; } + if ( options.previousPageHint === undefined ) { options.previousPageHint = ' « ' ; } + if ( ! options.style ) { options.style = this ; } + if ( ! options.selectedStyle ) { options.selectedStyle = this.dim.blue.bgGreen ; } + + if ( ! options.y ) { this( '\n' ) ; } + else { this.moveTo( 1 , options.y ) ; } + + var keyBindings = options.keyBindings || defaultKeyBindings ; + + if ( ! this.grabbing ) { this.grabInput() ; } + + var menuItems = menuItems_.map( e => typeof e === 'string' ? e : '' + e ) ; + + var selectedIndexInPage = options.selectedIndex = options.selectedIndex || 0 ; + var start = {} , selectedPage = 0 , finished = false , menuPages = [] , alreadyCleanedUp = false ; + + // Width + var nextPageHintWidth = stringWidth( options.nextPageHint ) , + previousPageHintWidth = stringWidth( options.previousPageHint ) , + separatorWidth = stringWidth( options.separator ) ; + + var computePages = () => { + var i , itemWidth , displayText , p = 0 , endX = 1 , nextEndX , firstItem = true , + lastItem , lineWidth , offset , + xMax = this.width - nextPageHintWidth ; + + menuPages = [ [] ] ; + + for ( i = 0 ; i < menuItems.length ; i ++ ) { + if ( p >= menuPages.length ) { menuPages.push( [] ) ; } + + itemWidth = stringWidth( menuItems[ i ] ) ; + nextEndX = endX + itemWidth + separatorWidth ; + + if ( nextEndX > xMax ) { + if ( firstItem ) { + itemWidth = xMax - endX ; + displayText = termkit.truncateString( menuItems[ i ] , itemWidth - 1 ) + '…' ; + + if ( i === options.selectedIndex ) { + selectedPage = p ; + selectedIndexInPage = menuPages[ p ].length ; + } + + menuPages[ p ].push( { + index: i , + text: menuItems[ i ] , + displayText: displayText , + displayTextWidth: itemWidth , + x: endX + } ) ; + } + else { + i -- ; + } + + p ++ ; + endX = 1 + previousPageHintWidth ; + firstItem = true ; + + continue ; + } + + if ( i === options.selectedIndex ) { + selectedPage = p ; + selectedIndexInPage = menuPages[ p ].length ; + } + + menuPages[ p ].push( { + index: i , + text: menuItems[ i ] , + displayText: menuItems[ i ] , + displayTextWidth: itemWidth , + x: endX + } ) ; + + endX = nextEndX ; + firstItem = false ; + } + + for ( p = 0 ; p < menuPages.length ; p ++ ) { + lastItem = menuPages[ p ][ menuPages[ p ].length - 1 ] ; + lineWidth = lastItem.x + lastItem.displayTextWidth - 1 ; + if ( p < menuPages.length - 1 ) { lineWidth += nextPageHintWidth ; } + + menuPages[ p ].x = 1 ; + + if ( lineWidth < this.width ) { + if ( options.align === 'right' ) { offset = this.width - lineWidth ; } + else if ( options.align === 'center' ) { offset = Math.floor( ( this.width - lineWidth ) / 2 ) ; } + else { offset = 0 ; } + + menuPages[ p ].x += offset ; + + if ( offset ) { + menuPages[ p ].forEach( item => item.x += offset ) ; + } + } + } + } ; + + var cleanup = ( error , data ) => { + if ( alreadyCleanedUp ) { return ; } + alreadyCleanedUp = true ; + + finished = true ; + this.removeListener( 'key' , onKey ) ; + this.removeListener( 'mouse' , onMouse ) ; + + if ( error ) { + if ( callback ) { callback( error ) ; } + else { controller.promise.reject( error ) ; } + return ; + } + + var page = menuPages[ selectedPage ] ; + + var value = data !== undefined ? data : { + selectedIndex: page[ selectedIndexInPage ].index , + selectedText: page[ selectedIndexInPage ].text , + x: page[ selectedIndexInPage ].x , + y: start.y + } ; + + if ( callback ) { callback( undefined , value ) ; } + else { controller.promise.resolve( value ) ; } + } ; + + // Compute the coordinate of the end of a string, given a start coordinate + var redraw = () => { + var i , cursorX , + page = menuPages[ selectedPage ] , + endX = page.x ; + + this.moveTo.eraseLineAfter( 1 , start.y ) ; + + if ( options.fillIn && endX > 1 ) { options.style.noFormat( ' '.repeat( endX - 1 ) ) ; } + else { this.column( endX ) ; } + + if ( selectedPage ) { + options.style.forceStyleOnReset.noFormat( options.previousPageHint ) ; + endX += previousPageHintWidth ; + } + + for ( i = 0 ; i < page.length ; i ++ ) { + if ( i ) { + options.style.forceStyleOnReset.noFormat( options.separator ) ; + endX += separatorWidth ; + } + + if ( i === selectedIndexInPage ) { + options.selectedStyle.forceStyleOnReset.noFormat( page[ i ].displayText ) ; + cursorX = endX ; + } + else { + options.style.forceStyleOnReset.noFormat( page[ i ].displayText ) ; + } + + endX += page[ i ].displayTextWidth ; + } + + if ( selectedPage < menuPages.length - 1 ) { + options.style.forceStyleOnReset.noFormat( options.nextPageHint ) ; + endX += nextPageHintWidth ; + } + + if ( options.fillIn && endX < this.width ) { options.style.noFormat( ' '.repeat( this.width - endX ) ) ; } + + this.column( cursorX ) ; + } ; + + var emitHighlight = () => { + var item = menuPages[ selectedPage ][ selectedIndexInPage ] ; + + controller.emit( 'highlight' , { + highlightedIndex: item.index , + highlightedText: item.text , + x: item.x , + y: start.y + } ) ; + } ; + + + var onKey = ( key , trash , data ) => { + if ( finished ) { return ; } + + var changed = false , + page = menuPages[ selectedPage ] ; + + switch( keyBindings[ key ] ) { + case 'submit' : + cleanup() ; + break ; + + case 'previous' : + if ( selectedIndexInPage > 0 ) { + selectedIndexInPage -- ; + changed = true ; + } + else if ( selectedPage > 0 ) { + selectedPage -- ; + selectedIndexInPage = menuPages[ selectedPage ].length - 1 ; + changed = true ; + } + break ; + + case 'next' : + if ( selectedIndexInPage < page.length - 1 ) { + selectedIndexInPage ++ ; + changed = true ; + } + else if ( selectedPage < menuPages.length - 1 ) { + selectedPage ++ ; + selectedIndexInPage = 0 ; + changed = true ; + } + break ; + + case 'cycleNext' : + if ( selectedPage === menuPages.length - 1 && selectedIndexInPage === page.length - 1 ) { + selectedPage = 0 ; + selectedIndexInPage = 0 ; + changed = true ; + } + else if ( selectedIndexInPage < page.length - 1 ) { + selectedIndexInPage ++ ; + changed = true ; + } + else if ( selectedPage < menuPages.length - 1 ) { + selectedPage ++ ; + selectedIndexInPage = 0 ; + changed = true ; + } + break ; + + case 'cyclePrevious' : + if ( selectedPage === 0 && selectedIndexInPage === 0 ) { + selectedPage = menuPages.length - 1 ; + selectedIndexInPage = menuPages[ selectedPage ].length - 1 ; + changed = true ; + } + else if ( selectedIndexInPage > 0 ) { + selectedIndexInPage -- ; + changed = true ; + } + else if ( selectedPage > 0 ) { + selectedPage -- ; + selectedIndexInPage = menuPages[ selectedPage ].length - 1 ; + changed = true ; + } + break ; + + case 'first' : + if ( selectedPage !== 0 || selectedIndexInPage !== 0 ) { + selectedPage = 0 ; + selectedIndexInPage = 0 ; + changed = true ; + } + break ; + + case 'last' : + if ( selectedPage !== menuPages.length - 1 || selectedIndexInPage !== menuPages[ selectedPage ].length - 1 ) { + selectedPage = menuPages.length - 1 ; + selectedIndexInPage = menuPages[ selectedPage ].length - 1 ; + changed = true ; + } + break ; + + case 'previousPage' : + if ( selectedPage > 0 ) { + selectedPage -- ; + selectedIndexInPage = 0 ; + changed = true ; + } + break ; + + case 'nextPage' : + if ( selectedPage < menuPages.length - 1 ) { + selectedPage ++ ; + selectedIndexInPage = 0 ; + changed = true ; + } + break ; + + case 'escape' : + if ( options.cancelable ) { + cleanup( undefined , { canceled: true } ) ; + } + if ( options.exitOnUnexpectedKey ) { + cleanup( undefined , { unexpectedKey: key , unexpectedKeyData: data } ) ; + } + break ; + + default : + if ( options.exitOnUnexpectedKey ) { + cleanup( undefined , { unexpectedKey: key , unexpectedKeyData: data } ) ; + } + break ; + + } + + if ( changed ) { + redraw() ; + emitHighlight() ; + } + } ; + + + var onMouse = ( name , data ) => { + if ( finished ) { return ; } + + // If out of bounds, exit now! + if ( data.y !== start.y ) { return ; } + + var i , item , nextButtonX , + inBounds = false , + page = menuPages[ selectedPage ] ; + + // First check previous/next page button click + if ( name === 'MOUSE_LEFT_BUTTON_PRESSED' ) { + if ( selectedPage > 0 && data.x >= 1 && data.x < 1 + previousPageHintWidth ) { + selectedPage -- ; + selectedIndexInPage = 0 ; + redraw() ; + emitHighlight() ; + return ; + } + + nextButtonX = page[ page.length - 1 ].x + page[ page.length - 1 ].displayTextWidth ; + + if ( selectedPage < menuPages.length - 1 && data.x >= nextButtonX && data.x < nextButtonX + nextPageHintWidth ) { + selectedPage ++ ; + selectedIndexInPage = 0 ; + redraw() ; + emitHighlight() ; + return ; + } + } + + for ( i = 0 ; i < page.length ; i ++ ) { + item = page[ i ] ; + + if ( data.x >= item.x && data.x < item.x + item.displayTextWidth ) { + inBounds = true ; + + if ( selectedIndexInPage !== i ) { + selectedIndexInPage = i ; + redraw() ; + emitHighlight() ; + } + + break ; + } + } + + if ( inBounds && name === 'MOUSE_LEFT_BUTTON_PRESSED' ) { + cleanup() ; + } + } ; + + var controller = Object.create( NextGenEvents.prototype ) ; + + controller.promise = new Promise() ; + + this.getCursorLocation( ( error , x , y ) => { + if ( error ) { + // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior. + // So we just move to the last line and create a new line. + //cleanup( error ) ; return ; + this.row.eraseLineAfter( this.height )( '\n' ) ; + x = 1 ; + y = this.height ; + } + + start.x = x ; + start.y = y ; + computePages() ; + redraw() ; + + // Emit the first auto-selected item + emitHighlight() ; + + this.on( 'key' , onKey ) ; + if ( this.mouseGrabbing ) { this.on( 'mouse' , onMouse ) ; } + } ) ; + + return controller ; +} ; + + +},{"./termkit":50,"nextgen-events":72,"seventh":108}],47:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +var Promise = require( 'seventh' ) ; + + + +/* + fakeTyping( str , [options] , callback ) + * str + * options + * style + * delay + * flashStyle + * flashDelay + * callback +*/ +module.exports = function slowTyping( str , options , callback ) { + if ( typeof str !== 'string' ) { throw new TypeError( '[terminal] slowTyping(): argument #0 should be a string' ) ; } + if ( typeof options === 'function' ) { callback = options ; options = {} ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + if ( ! options.style ) { options.style = this.green ; } + if ( ! options.delay ) { options.delay = 150 ; } + if ( ! options.flashStyle ) { options.flashStyle = this.bold.brightGreen ; } + if ( ! options.flashDelay ) { options.flashDelay = 100 ; } + + var index , unflashTimer , promise = new Promise() ; + + var printChar = () => { + if ( unflashTimer ) { + clearTimeout( unflashTimer ) ; + unflashTimer = null ; + unflash() ; + } + + if ( index === undefined ) { + index = 0 ; + } + else if ( index >= str.length ) { + if ( callback ) { callback() ; } + else { promise.resolve() ; } + return ; + } + else { + if ( options.flashStyle && str[ index ].match( /\S/ ) ) { + options.flashStyle( str[ index ] ) ; + unflashTimer = setTimeout( unflash , options.flashDelay ) ; + } + else { + options.style( str[ index ] ) ; + } + + index ++ ; + } + + setTimeout( printChar , ( 0.2 + Math.random() * 1.8 ) * options.delay ) ; + } ; + + var unflash = () => { + this.left( 1 ) ; + options.style( str[ index - 1 ] ) ; + unflashTimer = null ; + } ; + + printChar() ; + + return promise ; +} ; + + +},{"seventh":108}],48:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +// Characters that are hard to type +// Comments explain how to type it on a linux platform, using a fr layout keyboard + +const BIT_DOTS = "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿".split( '' ) ; +const GROWING_BLOCK = [ ' ' , '▁' , '▂' , '▃' , '▄' , '▅' , '▆' , '▇' , '█' ] ; +const ENLARGING_BLOCK = [ ' ' , '▏' , '▎' , '▍' , '▌' , '▋' , '▊' , '▉' , '█' ] ; + + + +module.exports = { + password: '●' , // Currently: the same as blackCircle + + forwardSingleQuote: '´' , // Altgr + , + overscore: '¯' , // Altgr + Shift + $ + multiply: '×' , // Altgr + Shift + ; + divide: '÷' , // Altgr + Shift + : + + // Arrows + up: '↑' , // Altgr + Shift + u + down: '↓' , // Altgr + u + left: '←' , // Altgr + y + right: '→' , // Altgr + i + leftAndRight: '↔' , + upAndDown: '↕' , + upLeft: '↖' , + upRight: '↗' , + downRight: '↘' , + downLeft: '↙' , + upLeftAndDownRight: '⤡' , + upRightAndDownLeft: '⤢' , + + // Those names are most common in the UTF-8 parlance + northWest: '↖' , + northEast: '↗' , + southEast: '↘' , + southWest: '↙' , + northWestAndSouthEast: '⤡' , + northEastAndSouthWest: '⤢' , + + fullBlock: '█' , + upperHalfBlock: '▀' , + lowerHalfBlock: '▄' , + + // Array of 0-8 growing/enlarging blocks + growingBlock: GROWING_BLOCK , + enlargingBlock: ENLARGING_BLOCK , + + bitDots: BIT_DOTS , + + // When editing this, update spChars.md doc + bar: { + classic: { + border: [ '[' , ']' ] , + body: [ '=' , ' ' ] + } , + classicWithArrow: { + border: [ '[' , ']' ] , + body: [ '=' , '>' , ' ' ] + } , + classicWithHalf: { + border: [ '[' , ']' ] , + body: [ '=' , ' ' , '-' , '=' , ' ' ] + } , + solid: { + border: [ '^!▉' , '▏' ] , + body: [ '█' , ... ENLARGING_BLOCK , ' ' ] + } + } , + + // When editing this, update spChars.md doc + box: { + __fix__: object => ( { + vertical: object.vertical || ' ' , + horizontal: object.horizontal || ' ' , + topLeft: object.topLeft || ' ' , + topRight: object.topRight || ' ' , + bottomLeft: object.bottomLeft || ' ' , + bottomRight: object.bottomRight || ' ' , + topTee: object.topTee || ' ' , + bottomTee: object.bottomTee || ' ' , + leftTee: object.leftTee || ' ' , + rightTee: object.rightTee || ' ' , + cross: object.cross || ' ' + } ) , + plain: { + vertical: '█' , + horizontal: '█' , + topLeft: '█' , + topRight: '█' , + bottomLeft: '█' , + bottomRight: '█' , + topTee: '█' , + bottomTee: '█' , + leftTee: '█' , + rightTee: '█' , + cross: '█' + } , + empty: { + vertical: ' ' , + horizontal: ' ' , + topLeft: ' ' , + topRight: ' ' , + bottomLeft: ' ' , + bottomRight: ' ' , + topTee: ' ' , + bottomTee: ' ' , + leftTee: ' ' , + rightTee: ' ' , + cross: ' ' + } , + ascii: { + vertical: '|' , + horizontal: '-' , + topLeft: '|' , + topRight: '|' , + bottomLeft: '|' , + bottomRight: '|' , + topTee: '-' , + bottomTee: '-' , + leftTee: '|' , + rightTee: '|' , + cross: '+' + } , + light: { + vertical: '│' , + horizontal: '─' , + topLeft: '┌' , + topRight: '┐' , + bottomLeft: '└' , + bottomRight: '┘' , + topTee: '┬' , + bottomTee: '┴' , + leftTee: '├' , + rightTee: '┤' , + cross: '┼' + } , + lightRounded: { + vertical: '│' , + horizontal: '─' , + topLeft: '╭' , + topRight: '╮' , + bottomLeft: '╰' , + bottomRight: '╯' , + topTee: '┬' , + bottomTee: '┴' , + leftTee: '├' , + rightTee: '┤' , + cross: '┼' + } , + heavy: { + vertical: '┃' , + horizontal: '━' , + topLeft: '┏' , + topRight: '┓' , + bottomLeft: '┗' , + bottomRight: '┛' , + topTee: '┳' , + bottomTee: '┻' , + leftTee: '┣' , + rightTee: '┫' , + cross: '╋' + } , + double: { + vertical: '║' , + horizontal: '═' , + topLeft: '╔' , + topRight: '╗' , + bottomLeft: '╚' , + bottomRight: '╝' , + topTee: '╦' , + bottomTee: '╩' , + leftTee: '╠' , + rightTee: '╣' , + cross: '╬' + } , + dotted: { + vertical: '┊' , + horizontal: '┄' , + topLeft: '┌' , + topRight: '┐' , + bottomLeft: '└' , + bottomRight: '┘' , + topTee: '┬' , + bottomTee: '┴' , + leftTee: '├' , + rightTee: '┤' , + cross: '┼' + } + } , + + // When editing this, update spChars.md doc + animation: { + asciiSpinner: [ '│' , '/' , '-' , '\\' ] , + lineSpinner: [ '│' , '/' , '─' , '\\' ] , + dotSpinner: [ + BIT_DOTS[ 7 ] , + BIT_DOTS[ 19 ] , + BIT_DOTS[ 49 ] , + BIT_DOTS[ 112 ] , + BIT_DOTS[ 224 ] , + BIT_DOTS[ 200 ] , + BIT_DOTS[ 140 ] , + BIT_DOTS[ 14 ] + ] , + bitDots: BIT_DOTS , + impulse: [ + "∙∙∙" , + "●∙∙" , + "∙●∙" , + "∙∙●" , + "∙●∙" , + "●∙∙" , + "∙∙∙" , + "∙∙∙" + ] , + unboxing: [ ' ' , '▁' , '▂' , '▃' , '▄' , '▅' , '▆' , '▇' , '█' , '▉' , '▊' , '▋' , '▌' , '▍' , '▎' , '▏' ] , + 'unboxing-color': [ + '^r^#^b ' , '^r^#^b▁' , '^r^#^b▂' , '^r^#^b▃' , '^r^#^b▄' , '^r^#^b▅' , '^r^#^b▆' , '^r^#^b▇' , '^r^#^m█' , '^r^#^m▉' , '^r^#^m▊' , '^r^#^m▋' , '^r^#^m▌' , '^r^#^m▍' , '^r^#^m▎' , '^r^#^m▏' , + '^m^#^y█' , '^m^#^y▇' , '^m^#^y▆' , '^m^#^y▅' , '^m^#^y▄' , '^m^#^y▃' , '^m^#^y▂' , '^m^#^y▁' , '^b^#^y ' , '^b^#^y▏' , '^b^#^y▎' , '^b^#^y▍' , '^b^#^y▌' , '^b^#^y▋' , '^b^#^y▊' , '^b^#^y▉' + ] + } , + + blackSquare: '■' , + whiteSquare: '□' , + blackCircle: '●' , + whiteCircle: '○' , + blackUpTriangle: '▲' , + whiteUpTriangle: '△' , + blackDownTriangle: '▼' , + whiteDownTriangle: '▽' , + blackLeftTriangle: '◀' , + whiteLeftTriangle: '◁' , + blackRightTriangle: '▶' , + whiteRightTriangle: '▷' , + blackDiamond: '◆' , + whiteDiamond: '◇' , + blackStar: '★' , + whiteStar: '☆' , + spadeSuit: '♠' , + heartSuit: '♥' , + diamondSuit: '♦' , + clubSuit: '♣' , + + // Powerline specific characters (https://powerline.readthedocs.io) + // It is displayed only with the appropriate font + powerline: { + branch: '' , + line: '' , + readOnly: '' , + rightTriangleSeparator: '' , + rightArrowSeparator: '' , + leftTriangleSeparator: '' , + leftArrowSeparator: '' + } +} ; + + +},{}],49:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const termkit = {} ; +module.exports = termkit ; + +const lazy = require( 'lazyness' )( require ) ; + + + +// Global config +termkit.globalConfig = {} ; + + + +termkit.tty = require( './tty.js' ) ; + +// For some reason, starting from node v4, once process.stdin getter is triggered, the 'tty' command would not work properly. +// This 'hack' cache the result of the command 'tty' if we are in the linux console, so 'gpm' can work. +if ( process.env.TERM === 'linux' ) { termkit.tty.getPath() ; } + + + +// Core submodules +Object.assign( termkit , require( './misc.js' ) ) ; +Object.assign( termkit , require( './detectTerminal.js' ) ) ; + +termkit.Terminal = require( './Terminal.js' ) ; +termkit.createTerminal = termkit.Terminal.create ; + +// Windows patches +if ( process.platform === 'win32' ) { require( './windows.js' )( termkit ) ; } + + + +// Lazy submodules +termkit.image = require( './image.js' ) ; +termkit.Palette = require( './Palette.js' ) ; +termkit.Rect = require( './Rect.js' ) ; +termkit.ScreenBuffer = require( './ScreenBuffer.js' ) ; +termkit.ScreenBufferHD = require( './ScreenBufferHD.js' ) ; +termkit.TextBuffer = require( './TextBuffer.js' ) ; +termkit.Vte = require( './vte/Vte.js' ) ; +termkit.autoComplete = require( './autoComplete.js' ) ; +termkit.spChars = require( './spChars.js' ) ; + +// Document model +termkit.Element = require( './document/Element.js' ) ; +termkit.Document = require( './document/Document.js' ) ; +termkit.Container = require( './document/Container.js' ) ; +termkit.Text = require( './document/Text.js' ) ; +termkit.AnimatedText = require( './document/AnimatedText.js' ) ; +termkit.Button = require( './document/Button.js' ) ; +termkit.ToggleButton = require( './document/ToggleButton.js' ) ; +termkit.TextBox = require( './document/TextBox.js' ) ; +termkit.EditableTextBox = require( './document/EditableTextBox.js' ) ; +termkit.Slider = require( './document/Slider.js' ) ; +termkit.Bar = require( './document/Bar.js' ) ; +termkit.LabeledInput = require( './document/LabeledInput.js' ) ; +termkit.InlineInput = require( './document/InlineInput.js' ) ; +termkit.Form = require( './document/Form.js' ) ; +termkit.RowMenu = require( './document/RowMenu.js' ) ; +termkit.ColumnMenu = require( './document/ColumnMenu.js' ) ; +termkit.ColumnMenuMulti = require( './document/ColumnMenuMulti.js' ) ; +termkit.SelectList = require( './document/SelectList.js' ) ; +termkit.SelectListMulti = require( './document/SelectListMulti.js' ) ; +termkit.DropDownMenu = require( './document/DropDownMenu.js' ) ; +termkit.TextTable = require( './document/TextTable.js' ) ; +termkit.Layout = require( './document/Layout.js' ) ; +termkit.Window = require( './document/Window.js' ) ; + +// External modules +termkit.chroma = require( 'chroma-js' ) ; + + + +lazy.properties( termkit , { + terminal: () => { + var guessed = termkit.guessTerminal() ; + return termkit.createTerminal( { + stdin: process.stdin , + stdout: process.stdout , + stderr: process.stderr , + generic: guessed.generic || 'unknown' , + appId: guessed.safe ? guessed.appId : undefined , + // appName: guessed.safe ? guessed.appName : undefined , + isTTY: guessed.isTTY , + isSSH: guessed.isSSH , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + } , + realTerminal: () => { + var guessed = termkit.guessTerminal( true ) ; + var input = termkit.tty.getInput() ; + var output = termkit.tty.getOutput() ; + + return termkit.createTerminal( { + stdin: input , + stdout: output , + stderr: process.stderr , + generic: guessed.generic || 'unknown' , + appId: guessed.safe ? guessed.appId : undefined , + // appName: guessed.safe ? guessed.appName : undefined , + isTTY: true , + isSSH: guessed.isSSH , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + } +} , true ) ; + + +}).call(this)}).call(this,require('_process')) +},{"./Palette.js":1,"./Rect.js":2,"./ScreenBuffer.js":3,"./ScreenBufferHD.js":4,"./Terminal.js":5,"./TextBuffer.js":6,"./autoComplete.js":7,"./detectTerminal.js":12,"./document/AnimatedText.js":13,"./document/Bar.js":14,"./document/Button.js":16,"./document/ColumnMenu.js":17,"./document/ColumnMenuMulti.js":18,"./document/Container.js":19,"./document/Document.js":20,"./document/DropDownMenu.js":21,"./document/EditableTextBox.js":22,"./document/Element.js":23,"./document/Form.js":24,"./document/InlineInput.js":25,"./document/LabeledInput.js":26,"./document/Layout.js":27,"./document/RowMenu.js":28,"./document/SelectList.js":29,"./document/SelectListMulti.js":30,"./document/Slider.js":31,"./document/Text.js":32,"./document/TextBox.js":33,"./document/TextTable.js":34,"./document/ToggleButton.js":35,"./document/Window.js":36,"./image.js":40,"./misc.js":42,"./spChars.js":48,"./tty.js":51,"./vte/Vte.js":53,"./windows.js":56,"_process":179,"chroma-js":59,"lazyness":68}],50:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const termkit = {} ; +module.exports = termkit ; + +const lazy = require( 'lazyness' )( require ) ; + + + +// Global config +termkit.globalConfig = {} ; + + + +lazy.requireProperty( termkit , 'tty' , './tty.js' ) ; + +// For some reason, starting from node v4, once process.stdin getter is triggered, the 'tty' command would not work properly. +// This 'hack' cache the result of the command 'tty' if we are in the linux console, so 'gpm' can work. +if ( process.env.TERM === 'linux' ) { termkit.tty.getPath() ; } + + + +// Core submodules +Object.assign( termkit , require( './misc.js' ) ) ; +Object.assign( termkit , require( './detectTerminal.js' ) ) ; + +termkit.Terminal = require( './Terminal.js' ) ; +termkit.createTerminal = termkit.Terminal.create ; + +// Windows patches +if ( process.platform === 'win32' ) { require( './windows.js' )( termkit ) ; } + + + +// Lazy submodules +lazy.requireProperties( termkit , { + image: './image.js' , + Palette: './Palette.js' , + Rect: './Rect.js' , + ScreenBuffer: './ScreenBuffer.js' , + ScreenBufferHD: './ScreenBufferHD.js' , + TextBuffer: './TextBuffer.js' , + Vte: './vte/Vte.js' , + autoComplete: './autoComplete.js' , + spChars: './spChars.js' , + + // Document model + Element: './document/Element.js' , + Document: './document/Document.js' , + Container: './document/Container.js' , + Text: './document/Text.js' , + AnimatedText: './document/AnimatedText.js' , + Button: './document/Button.js' , + ToggleButton: './document/ToggleButton.js' , + TextBox: './document/TextBox.js' , + EditableTextBox: './document/EditableTextBox.js' , + Slider: './document/Slider.js' , + Bar: './document/Bar.js' , + LabeledInput: './document/LabeledInput.js' , + InlineInput: './document/InlineInput.js' , + Form: './document/Form.js' , + RowMenu: './document/RowMenu.js' , + ColumnMenu: './document/ColumnMenu.js' , + ColumnMenuMulti: './document/ColumnMenuMulti.js' , + SelectList: './document/SelectList.js' , + SelectListMulti: './document/SelectListMulti.js' , + DropDownMenu: './document/DropDownMenu.js' , + TextTable: './document/TextTable.js' , + Layout: './document/Layout.js' , + Window: './document/Window.js' , + + // External modules + chroma: 'chroma-js' +} ) ; + + + +lazy.properties( termkit , { + terminal: () => { + var guessed = termkit.guessTerminal() ; + return termkit.createTerminal( { + stdin: process.stdin , + stdout: process.stdout , + stderr: process.stderr , + generic: guessed.generic || 'unknown' , + appId: guessed.safe ? guessed.appId : undefined , + // appName: guessed.safe ? guessed.appName : undefined , + isTTY: guessed.isTTY , + isSSH: guessed.isSSH , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + } , + realTerminal: () => { + var guessed = termkit.guessTerminal( true ) ; + var input = termkit.tty.getInput() ; + var output = termkit.tty.getOutput() ; + + return termkit.createTerminal( { + stdin: input , + stdout: output , + stderr: process.stderr , + generic: guessed.generic || 'unknown' , + appId: guessed.safe ? guessed.appId : undefined , + // appName: guessed.safe ? guessed.appName : undefined , + isTTY: true , + isSSH: guessed.isSSH , + processSigwinch: true , + preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch + } ) ; + } +} , true ) ; + + +}).call(this)}).call(this,require('_process')) +},{"./Terminal.js":5,"./detectTerminal.js":12,"./misc.js":42,"./windows.js":56,"_process":179,"lazyness":68}],51:[function(require,module,exports){ +(function (process){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +//var exec = require( 'child_process' ).exec ; +//var spawn = require( 'child_process' ).spawn ; +var execSync = require( 'child_process' ).execSync ; +var fs = require( 'fs' ) ; +var tty = require( 'tty' ) ; + + + + +var tty_ = {} ; +module.exports = tty_ ; + + + +var cachedGetPath ; + +/* + getPath( [stdin] ) + * stdin: a stream that is the current STDIN of the terminal + + Returns an object, where: + * ttyPath: the path of the tty + * ttyIndex: the index number of the tty, only if it is a /dev/tty*, /dev/pts/* return null +*/ +tty_.getPath = function getPath( stdin ) { + var cacheIt , result , ttyPath , ttyIndex , matches ; + + // Manage arguments + if ( ! stdin ) { + // getPath() does not work as soon as process.stdin getter is triggered (since node v4) + // So 0 should be used instead of process.stdin + stdin = 0 ; + //stdin = process.stdin ; + } + + if ( stdin === 0 || stdin === process.stdin ) { + if ( cachedGetPath ) { return cachedGetPath ; } + cacheIt = true ; + } + + + try { + // if no stdio are passed, the command will report 'not a TTY' + ttyPath = execSync( 'tty' , { stdio: [ stdin , null , null ] } ).toString() ; + } + catch ( error ) { + ttyPath = error.stdout.toString() ; + } + + ttyPath = ttyPath.trim() ; + + //console.log( 'TTY path:' , ttyPath ) ; + + matches = ttyPath.match( /\/dev\/tty([0-9]*)/ ) ; + + ttyIndex = matches ? matches[ 1 ] || null : null ; + + result = { + path: ttyPath , + index: ttyIndex + } ; + + if ( cacheIt ) { cachedGetPath = result ; } + + return result ; +} ; + + + +/* + getInput() + + Open a TTY input file descriptor and transform it into a regular node.js TTY input stream. + It returns the TTY input `Stream` use instead of process.stdin + + This code was borrowed from the 'ttys' module by Nathan Rajlich. +*/ +tty_.getInput = function getInput() { + var inputFd , input ; + + inputFd = fs.openSync( '/dev/tty' , 'r' ) ; + if ( ! tty.isatty( inputFd ) ) { throw new Error( 'Input file descriptor is not a TTY.' ) ; } + input = new tty.ReadStream( inputFd ) ; + input._type = 'tty' ; + + return input ; +} ; + + + +/* + getOutput() + + Open a TTY output file descriptor and transform it into a regular node.js TTY output stream. + It returns the TTY output `Stream` use instead of process.stdin + + This code was borrowed from the 'ttys' module by Nathan Rajlich. +*/ +tty_.getOutput = function getOutput() { + var outputFd , output ; + + outputFd = fs.openSync( '/dev/tty' , 'w' ) ; + if ( ! tty.isatty( outputFd ) ) { throw new Error( 'Output file descriptor is not a TTY.' ) ; } + output = new tty.WriteStream( outputFd ) ; + output._type = 'tty' ; + + // Hack to have the stdout stream not keep the event loop alive. + // See: https://github.com/joyent/node/issues/1726 + // XXX: remove/fix this once src/node.js does something different as well. + // @cronvel: that doesn't work much either... + if ( output._handle && output._handle.unref ) { + output._handle.unref() ; + } + + // Update the "columns" and "rows" properties on the stdout stream + // whenever the console window gets resized. + if ( output._refreshSize ) { + process.on( 'SIGWINCH' , () => { + output._refreshSize() ; + } ) ; + } + + return output ; +} ; + + + +}).call(this)}).call(this,require('_process')) +},{"_process":179,"child_process":136,"fs":136,"tty":198}],52:[function(require,module,exports){ +(function (Buffer){(function (){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const fromOutputSequence = require( './fromOutputSequence.js' ) ; +const string = require( 'string-kit' ) ; +const NextGenEvents = require( 'nextgen-events' ) ; +const Promise = require( 'seventh' ) ; + + + +function SequencesReader( options = {} ) { +} + +module.exports = SequencesReader ; + +SequencesReader.prototype = Object.create( NextGenEvents.prototype ) ; +SequencesReader.prototype.constructor = SequencesReader ; + + + +async function readStream( stream , size = 1 ) { + var data ; + + //stream.on( 'readable' , () => console.error( 'yay readable' ) ) ; + data = stream.read( size ) ; + //console.error( 'read stream:' , data ) ; + + while ( data === null ) { + await Promise.onceEventOrError( stream , 'readable' , [ 'close' , 'end' ] ) ; + data = stream.read( size ) ; + //console.error( 'read stream (loop):' , data ) ; + } + + return data ; +} + + + +function charCodeIsAlpha( charCode ) { + return ( charCode >= 0x41 && charCode <= 0x5a ) || ( charCode >= 0x61 && charCode <= 0x7a ) ; +} + +function toCharCodeStr( charCode ) { + var charCodeStr = charCode.toString( 16 ) ; + charCodeStr = charCodeStr.length > 1 ? '\\x' + charCodeStr : '\\x0' + charCodeStr ; + return charCodeStr ; +} + +function toCharOrCharCodeStr( charCode ) { + if ( charCode <= 0x1f || charCode === 0x7f ) { return toCharCodeStr( charCode ) ; } + return String.fromCharCode( charCode ) ; +} + +function defaultArgs( args , defaultArgs_ ) { + if ( ! defaultArgs_ || ! defaultArgs_.length ) { return args ; } + + args = Array.from( args ) ; + + defaultArgs_.forEach( ( e , index ) => { + if ( e !== undefined && args[ index ] === undefined ) { + args[ index ] = e ; + } + } ) ; + + return args ; +} + +// ESC sequence type that eat 3 bytes +const ESC_3_BYTES = new Set( + // Set character set + '(' , ')' , '*' , '+' , '-' , '.' , '/' , + // Misc + ' ' , '#' , '%' +) ; + +// E.g.: ESC [ ? ... letter +const CSI_TYPE_EXTRA_CHAR_BEFORE = new Set( + '?' , '>' , '<' +) ; + +// E.g.: ESC [ ... space letter +const CSI_TYPE_EXTRA_CHAR_AFTER = new Set( + ' ' , '$' , '#' , '"' , "'" , '*' +) ; + +// E.g.: ESC [ ... ; ? \x07 +const OSC_TYPE_EXTRA_PARAM_AFTER = new Set( + '?' +) ; + + + +SequencesReader.prototype.streamToEvent = async function( stream ) { + var charCode , charCodeStr , char , bytes , codepoint , + type , args , argIndex , emitParsedSequence , event , subTree , subSubTree , basePtr , + charBuffer = Buffer.alloc( 6 ) ; + + for ( ;; ) { + charCode = ( await readStream( stream ) )[ 0 ] ; + //console.error( 'got charCode:' , charCode.toString(16) ) ; + + if ( charCode <= 0x1f || charCode === 0x7f ) { + + charCodeStr = toCharCodeStr( charCode ) ; + //console.error( 'control:' , charCodeStr ) ; + + if ( charCode === 0x1b ) { // Escape, probably an escape sequence! + charCode = ( await readStream( stream ) )[ 0 ] ; + + if ( charCode === 0x5b ) { // [ =CSI + args = '' ; + + while ( ! charCodeIsAlpha( charCode = ( await readStream( stream ) )[ 0 ] ) ) { + args += String.fromCharCode( charCode ) ; + } + + type = String.fromCharCode( charCode ) ; + args = args ? args.split( ';' ) : [] ; + emitParsedSequence = true ; + + if ( args.length && CSI_TYPE_EXTRA_CHAR_BEFORE.has( args[ 0 ][ 0 ] ) ) { + type = args[ 0 ][ 0 ] + type ; + args[ 0 ] = args[ 0 ].slice( 1 ) ; + } + + if ( args.length && CSI_TYPE_EXTRA_CHAR_AFTER.has( args[ args.length - 1 ][ args[ args.length - 1 ] - 1 ] ) ) { + type = args[ args.length - 1 ][ args[ args.length - 1 ] - 1 ] + type ; + args[ args.length - 1 ] = args[ args.length - 1 ].slice( 0 , -1 ) ; + } + + subTree = fromOutputSequence.CSI[ type ] ; + console.error( ">>>>>>>>>> CSI parsing:" , type , args ) ; + + if ( subTree ) { + if ( subTree.subTree && args.length ) { + basePtr = subTree ; + + for ( argIndex = 0 ; argIndex < args.length ; argIndex ++ ) { + subSubTree = basePtr.subTree[ args[ argIndex ] ] ; + + if ( subSubTree ) { + if ( subSubTree.subTree ) { + basePtr = subSubTree ; + continue ; + } + + event = subSubTree.event || basePtr.event || subTree.event ; + + if ( event !== 'none' ) { + this.emit( + event , + subSubTree.subType || basePtr.subType || subTree.subType , + subSubTree.arg !== undefined ? subSubTree.arg : ( basePtr.arg !== undefined ? basePtr.arg : subTree.arg ) , /* eslint-disable-line no-nested-ternary */ + subSubTree.extraArgs || basePtr.extraArgs || subTree.extraArgs || defaultArgs( args.slice( argIndex + 1 ) , subSubTree.defaultExtraArgs ) + ) ; + } + + emitParsedSequence = false ; + + if ( subSubTree.continue ) { + basePtr = subTree ; + continue ; + } + } + else { + emitParsedSequence = true ; + } + break ; + } + } + else if ( subTree.event ) { + if ( subTree.event !== 'none' ) { + this.emit( + subTree.event , + subTree.subType , + subTree.arg , + subTree.extraArgs || defaultArgs( args , subTree.defaultExtraArgs ) + ) ; + } + + emitParsedSequence = false ; + } + } + + if ( emitParsedSequence ) { + this.emit( 'CSI' , type , args ) ; + } + + continue ; + } + else if ( charCode === 0x5d ) { // ] =OSC + args = '' ; + + for ( ;; ) { + charCode = ( await readStream( stream ) )[ 0 ] ; + + if ( charCode === 0x07 ) { break ; } // This is the OSC terminator, leave + + if ( charCode === 0x1b ) { + charCode = ( await readStream( stream ) )[ 0 ] ; + if ( charCode === 0x5c ) { break ; } // ESC \ =string terminator, so we leave + // Ok, that was not the string terminator, so add the ESC key to the arg, but it is a bit strange to do that... + args += String.fromCharCode( 0x1b ) ; + } + + args += String.fromCharCode( charCode ) ; + } + + args = args ? args.split( ';' ) : [] ; + type = args.shift() ; + + if ( OSC_TYPE_EXTRA_PARAM_AFTER.has( args[ args.length - 1 ] ) ) { + type += args.pop() ; + } + + emitParsedSequence = true ; + + subTree = fromOutputSequence.OSC[ type ] ; + + if ( subTree ) { + if ( subTree.subTree ) { + for ( argIndex = 0 ; argIndex < args.length ; argIndex ++ ) { + subSubTree = subTree.subTree[ args[ argIndex ] ] ; + if ( subSubTree ) { + event = subSubTree.event || subTree.event ; + + if ( event !== 'none' ) { + this.emit( event , subSubTree.subType || subTree.subType , args.slice( argIndex + 1 ) ) ; + } + + emitParsedSequence = false ; + } + else { + emitParsedSequence = true ; + } + break ; + } + } + else if ( subTree.event ) { + if ( subTree.event !== 'none' ) { + this.emit( subTree.event , subTree.subType , args ) ; + } + + emitParsedSequence = false ; + } + } + + if ( emitParsedSequence ) { + this.emit( 'OSC' , type , args ) ; + } + + continue ; + } + else { + // Single/simple escape + type = toCharOrCharCodeStr( charCode ) ; + args = null ; + + if ( ESC_3_BYTES.has( type ) ) { + // This is a 3 bytes ESC + args = [ toCharOrCharCodeStr( ( await readStream( stream ) )[ 0 ] ) ] ; + } + + emitParsedSequence = true ; + subTree = fromOutputSequence.ESC[ type ] ; + + if ( subTree ) { + if ( subTree.event !== 'none' ) { + this.emit( subTree.event , subTree.subType , subTree.arg , args || subTree.extraArgs ) ; + } + + emitParsedSequence = false ; + } + + if ( emitParsedSequence ) { + this.emit( 'ESC' , type , args ) ; + } + + continue ; + } + } + else if ( fromOutputSequence.control[ charCodeStr ] ) { + subTree = fromOutputSequence.control[ charCodeStr ] ; + + if ( subTree.event !== 'none' ) { + this.emit( subTree.event , subTree.subType , subTree.arg , subTree.extraArgs ) ; + } + + continue ; + } + else { + this.emit( 'control' , charCodeStr ) ; + continue ; + } + } + + if ( charCode >= 0x80 ) { + // Unicode bytes per char guessing + if ( charCode < 0xc0 ) { continue ; } // We are in a middle of an unicode multibyte sequence... Something fails somewhere, we will just continue for now... + else if ( charCode < 0xe0 ) { bytes = 2 ; } + else if ( charCode < 0xf0 ) { bytes = 3 ; } + else if ( charCode < 0xf8 ) { bytes = 4 ; } + else if ( charCode < 0xfc ) { bytes = 5 ; } + else { bytes = 6 ; } + + charBuffer[ 0 ] = charCode ; + charBuffer[ 1 ] = charBuffer[ 2 ] = charBuffer[ 3 ] = charBuffer[ 4 ] = charBuffer[ 5 ] = 0 ; + ( await readStream( stream , bytes - 1 ) ).copy( charBuffer , 1 ) ; + + char = charBuffer.toString( 'utf8' ) ; + codepoint = string.unicode.firstCodePoint( char ) ; + + //this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: codepoint , code: charBuffer } ) ; + this.emit( 'char' , char , codepoint ) ; + } + else { + // Standard ASCII + char = String.fromCharCode( charCode ) ; + //this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: charCode , code: charCode } ) ; + this.emit( 'char' , char , charCode ) ; + } + } +} ; + + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./fromOutputSequence.js":54,"buffer":147,"nextgen-events":72,"seventh":108,"string-kit":123}],53:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +const termkit = require( '../termkit.js' ) ; +const ScreenBuffer = require( '../ScreenBuffer.js' ) ; +const Rect = require( '../Rect.js' ) ; +const string = require( 'string-kit' ) ; +const toInputSequence = require( './toInputSequence.js' ) ; +const SequencesReader = require( './SequencesReader.js' ) ; + +const NextGenEvents = require( 'nextgen-events' ) ; +const Promise = require( 'seventh' ) ; + +const spawn = require( 'child_process' ).spawn ; + +const logRed = ( ... args ) => console.error( '\x1b[31m' , ... args , '\x1b[m' ) ; +const log = ( ... args ) => console.error( ... args ) ; + + + +/* + options: + * width: buffer width (default to dst.width) + * height: buffer height (default to dst.height) + * dst: writting destination + * palette: Palette instance + * eventInput: optional, an event emitter used as input source, can be a Terminal instance + * + any ScreenBuffer options + + Properties: + * childStdin: the stdin stream for the child, that will be used for its keyboard/mouse event + * childStdout: the stdout stream for the child, that will be displayed on the terminal +*/ +function Vte( options = {} ) { + this.width = Math.floor( options.width ) || ( options.dst ? options.dst.width : 80 ) ; + this.height = Math.floor( options.height ) || ( options.dst ? options.dst.height : 25 ) ; + this.palette = options.palette || ( this.dst && this.dst.palette ) ; + + this.screenBuffer = new ScreenBuffer( Object.assign( {} , options , this , { wrap: true } ) ) ; + this.screenBuffer.setClearAttr( { defaultColor: true , bgDefaultColor: true } ) ; + + // To avoid mistake, cx/cy are starting at 0, like the internal screenBuffer does + this.cx = 0 ; + this.cy = 0 ; + this.savedCx = 0 ; + this.savedCy = 0 ; + + this.attr = 0 ; + this.resetAttr() ; + + this.scrollingRegion = null ; + this.tabWidth = 8 ; + this.mouseEvent = null ; + this.focusEvent = false ; + this.mouseIsDragging = false ; + + this.eventInput = options.eventInput ; + this.childSequencesReader = new SequencesReader() ; + this.childProcess = null ; + + this.onEventInputKey = this.onEventInputKey.bind( this ) ; + this.onEventInputMouse = this.onEventInputMouse.bind( this ) ; + this.onEventInputTerminal = this.onEventInputTerminal.bind( this ) ; + + this.onChildOutputReset = this.onChildOutputReset.bind( this ) ; + this.onChildOutputChar = this.onChildOutputChar.bind( this ) ; + this.onChildOutputCursor = this.onChildOutputCursor.bind( this ) ; + this.onChildOutputEdit = this.onChildOutputEdit.bind( this ) ; + this.onChildOutputAttr = this.onChildOutputAttr.bind( this ) ; + this.onChildOutputPalette = this.onChildOutputPalette.bind( this ) ; + this.onChildOutputCursorAttr = this.onChildOutputCursorAttr.bind( this ) ; + this.onChildOutputBell = this.onChildOutputBell.bind( this ) ; + this.onChildOutputDevice = this.onChildOutputDevice.bind( this ) ; + this.onChildOutputSystem = this.onChildOutputSystem.bind( this ) ; + + this.onChildOutputControl = this.onChildOutputControl.bind( this ) ; + this.onChildOutputESC = this.onChildOutputESC.bind( this ) ; + this.onChildOutputCSI = this.onChildOutputCSI.bind( this ) ; + this.onChildOutputOSC = this.onChildOutputOSC.bind( this ) ; + + // The real draw is sync, so there is no need for Promise.debounceUpdate(), Promise.debounce() avoid an extra draw with no delta + //this.drawDebounce = Promise.debounceUpdate( this.drawDelay.bind( this ) ) ; + this.drawDebounce = Promise.debounce( this.drawDelay.bind( this ) ) ; +} + +module.exports = Vte ; + +Vte.prototype = Object.create( NextGenEvents.prototype ) ; +Vte.prototype.constructor = Vte ; + + + +// Run a child process +Vte.prototype.run = function( command , args ) { + var childPty , child , + promise = new Promise() ; + + if ( this.childProcess ) { return ; } + + this.start() ; + + try { + // If child_pty is present, then use it instead of spawn + // So programs launched would think they truly run inside a TTY + childPty = require( 'child_pty' ) ; + child = childPty.spawn( command , args , { + columns: this.width , + rows: this.height + //stdio: [ 'pty' , 'pty' , 'pty' ] + } ) ; + } + catch ( error ) { + logRed( "'child_pty' optional dependency not found, using regular child_process.spawn()" ) ; + child = spawn( command , args ) ; + } + + this.childProcess = child ; + + //this.on( 'input' , data => { log( 'stdin:' , data ) ; child.stdin.write( data ) ; } ) ; + this.on( 'input' , data => child.stdin.write( data ) ) ; + this.childSequencesReader.streamToEvent( child.stdout ) ; + + //child.stdout.on( 'data' , this.onChildOutput ) ; + //child.stderr.on( 'data' , this.onChildOutput ) ; + + // Tmp, to close the app during alpha phase + child.on( 'close' , code => { + this.childProcess = null ; + promise.resolve() ; + } ) ; + + return promise ; +} ; + + + +Vte.prototype.start = function() { + if ( this.eventInput ) { + this.eventInput.on( 'key' , this.onEventInputKey ) ; + this.eventInput.on( 'mouse' , this.onEventInputMouse ) ; + this.eventInput.on( 'terminal' , this.onEventInputTerminal ) ; + } + + this.childSequencesReader.on( 'reset' , this.onChildOutputReset ) ; + this.childSequencesReader.on( 'char' , this.onChildOutputChar ) ; + this.childSequencesReader.on( 'cursor' , this.onChildOutputCursor ) ; + this.childSequencesReader.on( 'edit' , this.onChildOutputEdit ) ; + this.childSequencesReader.on( 'attr' , this.onChildOutputAttr ) ; + this.childSequencesReader.on( 'palette' , this.onChildOutputPalette ) ; + this.childSequencesReader.on( 'cursorAttr' , this.onChildOutputCursorAttr ) ; + this.childSequencesReader.on( 'bell' , this.onChildOutputBell ) ; + this.childSequencesReader.on( 'device' , this.onChildOutputDevice ) ; + this.childSequencesReader.on( 'system' , this.onChildOutputSystem ) ; + + this.childSequencesReader.on( 'control' , this.onChildOutputControl ) ; + this.childSequencesReader.on( 'ESC' , this.onChildOutputESC ) ; + this.childSequencesReader.on( 'CSI' , this.onChildOutputCSI ) ; + this.childSequencesReader.on( 'OSC' , this.onChildOutputOSC ) ; +} ; + + + +Vte.prototype.draw = function() { + var stats = this.screenBuffer.draw( { delta: true } ) ; + this.screenBuffer.drawCursor() ; + log( 'draw stats:' , stats ) ; +} ; + + + +// Full redraw, no delta +Vte.prototype.redraw = function() { + var stats = this.screenBuffer.draw( { delta: false } ) ; + this.screenBuffer.drawCursor() ; + log( 'redraw stats:' , stats ) ; +} ; + + + +Vte.prototype.drawDelay = async function() { + //await Promise.resolveTimeout( 10 ) ; + await Promise.resolveNextTick() ; + this.draw() ; +} ; + + + +Vte.prototype.putChar = function( char ) { + var charCode = char.charCodeAt( 0 ) ; log( 'putChar:' , charCode <= 0x1f || charCode === 0x7f ? '(ctrl)' : char , charCode >= 0x10 ? '\\x' + charCode.toString( 16 ) : '\\x0' + charCode.toString( 16 ) , 'at:' , this.cx , this.cy ) ; + this.screenBuffer.put( { x: this.cx , y: this.cy , attr: this.attr } , char ) ; + this.cx ++ ; + + if ( this.cx >= this.width ) { + this.newLine() ; + } + else { + this.drawDebounce() ; + } +} ; + + + +// internal = don't adjust +Vte.prototype.moveCursorTo = function( x , y , internal = false ) { + if ( internal ) { + if ( x !== undefined ) { this.cx = x ; } + if ( y !== undefined ) { this.cy = y ; } + } + else { + if ( x !== undefined ) { this.cx = x - 1 ; } + if ( y !== undefined ) { this.cy = y - 1 ; } + } + + if ( this.cx < 0 ) { this.cx = 0 ; } + else if ( this.cx >= this.width - 1 ) { this.cx = this.width - 1 ; } + + if ( this.cy < 0 ) { this.cy = 0 ; } + else if ( this.cy >= this.height - 1 ) { this.cy = this.height - 1 ; } + + this.screenBuffer.cx = this.cx ; + this.screenBuffer.cy = this.cy ; + this.screenBuffer.drawCursor() ; +} ; + + + +// Relative move +Vte.prototype.moveCursor = function( x , y ) { + this.moveCursorTo( this.cx + x , this.cy + y , true ) ; +} ; + + + +// Next horizontal tab +Vte.prototype.nextTab = function() { + this.moveCursorTo( Math.ceil( ( this.cx + 1 ) / this.tabWidth ) * this.tabWidth , undefined , true ) ; +} ; + + + +Vte.prototype.vScroll = function( lineOffset , noDraw ) { + var ymin = 0 , + ymax = this.height - 1 ; + + if ( this.scrollingRegion && this.cy >= this.scrollingRegion.ymin && this.cy <= this.scrollingRegion.ymax ) { + ( { ymin , ymax } = this.scrollingRegion ) ; + } + + log( '################### vScroll:' , lineOffset , ymin , ymax ) ; + this.screenBuffer.vScroll( lineOffset , this.attr , ymin , ymax , true ) ; + + if ( ! noDraw ) { this.drawDebounce() ; } +} ; + + + +Vte.prototype.lineFeed = function( carriageReturn , noDraw ) { + var ymin = 0 , + ymax = this.height - 1 ; + + if ( this.scrollingRegion && this.cy >= this.scrollingRegion.ymin && this.cy <= this.scrollingRegion.ymax ) { + ( { ymin , ymax } = this.scrollingRegion ) ; + } + + this.screenBuffer.cy = ++ this.cy ; + + if ( carriageReturn ) { + this.screenBuffer.cx = this.cx = 0 ; + } + + if ( this.cy > ymax ) { + // Scroll up! + // This will immediately scroll the underlying terminal using the scrolling region capability + this.screenBuffer.cy = this.cy = ymax ; + this.vScroll( -1 , noDraw ) ; + if ( ! noDraw ) { this.screenBuffer.drawCursor() ; } + } + else if ( ! noDraw ) { + this.drawDebounce() ; + } +} ; + +Vte.prototype.newLine = function( noDraw ) { return this.lineFeed( true , noDraw ) ; } ; + + + +Vte.prototype.reverseLineFeed = function( carriageReturn , noDraw ) { + var ymin = 0 , + ymax = this.height - 1 ; + + if ( this.scrollingRegion && this.cy >= this.scrollingRegion.ymin && this.cy <= this.scrollingRegion.ymax ) { + ( { ymin , ymax } = this.scrollingRegion ) ; + } + + this.screenBuffer.cy = -- this.cy ; + + if ( carriageReturn ) { + this.screenBuffer.cx = this.cx = 0 ; + } + + if ( this.cy < ymin ) { + // Scroll down! + // This will immediately scroll the underlying terminal using the scrolling region capability + this.screenBuffer.cy = this.cy = ymin ; + this.vScroll( 1 , noDraw ) ; + if ( ! noDraw ) { this.screenBuffer.drawCursor() ; } + } + else if ( ! noDraw ) { + this.drawDebounce() ; + } +} ; + + + +Vte.prototype.eraseLine = function( mode , noDraw ) { + if ( mode === 'after' ) { + this.screenBuffer.fill( { + region: { + xmin: this.cx , xmax: this.width - 1 , ymin: this.cy , ymax: this.cy + } , + attr: this.attr + } ) ; + } + else if ( mode === 'before' ) { + this.screenBuffer.fill( { + region: { + xmin: 0 , xmax: this.cx , ymin: this.cy , ymax: this.cy + } , + attr: this.attr + } ) ; + } + else { + this.screenBuffer.fill( { + region: { + xmin: 0 , xmax: this.width - 1 , ymin: this.cy , ymax: this.cy + } , + attr: this.attr + } ) ; + } + + if ( ! noDraw ) { this.drawDebounce() ; } +} ; + + + +Vte.prototype.eraseDisplay = function( mode , noDraw ) { + if ( mode === 'after' ) { + // First, erase the current line from the cursor + this.screenBuffer.fill( { + region: { + xmin: this.cx , xmax: this.width - 1 , ymin: this.cy , ymax: this.cy + } , + attr: this.attr + } ) ; + // Then, erase all lines below the current line + this.screenBuffer.fill( { + region: { + xmin: 0 , xmax: this.width - 1 , ymin: this.cy + 1 , ymax: this.height - 1 + } , + attr: this.attr + } ) ; + } + else if ( mode === 'before' ) { + // First, erase all lines above the current line + this.screenBuffer.fill( { + region: { + xmin: 0 , xmax: this.width - 1 , ymin: 0 , ymax: this.cy - 1 + } , + attr: this.attr + } ) ; + // Then, erase the current line up to the cursor + this.screenBuffer.fill( { + region: { + xmin: 0 , xmax: this.cx , ymin: this.cy , ymax: this.cy + } , + attr: this.attr + } ) ; + } + else { + this.screenBuffer.fill( { attr: this.attr } ) ; + } + + if ( ! noDraw ) { this.drawDebounce() ; } +} ; + + + +Vte.prototype.backDelete = function( count = 1 ) { + if ( count > this.cx ) { count = this.cx ; } + if ( count <= 0 ) { return ; } + + // Shift the end of the line + this.screenBuffer.copyRegion( { + xmin: this.cx , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + } , { x: this.cx - count , y: this.cy } ) ; + this.cx -= count ; + + // Fill the end of the line with blank + this.screenBuffer.fill( { region: { + xmin: this.width - count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + } , + attr: this.attr } , ' ' ) ; + + this.screenBuffer.cx = this.cx ; + + this.drawDebounce() ; +} ; + + + +Vte.prototype.delete = function( count = 1 ) { + if ( count > this.width - this.cx ) { count = this.width - this.cx ; } + if ( count <= 0 ) { return ; } + + // Shift the end of the line + if ( this.cx + count < this.width ) { + this.screenBuffer.copyRegion( { + xmin: this.cx + count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + } , { x: this.cx , y: this.cy } ) ; + log( "delete:" , count , "copy region:" , { + xmin: this.cx + count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + } , { x: this.cx , y: this.cy } ) ; + } + + // Fill the end of the line with blank + this.screenBuffer.fill( { region: { + xmin: this.width - count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + } , + attr: this.attr } , ' ' ) ; + log( "delete:" , count , "fill region:" , { + xmin: this.width - count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + } ) ; + + this.drawDebounce() ; +} ; + + + +Vte.prototype.erase = function( count = 1 ) { + if ( count > this.width - this.cx ) { count = this.width - this.cx ; } + if ( count <= 0 ) { return ; } + + // Fill with blank + this.screenBuffer.fill( { region: { + xmin: this.cx , ymin: this.cy , xmax: this.cx + count - 1 , ymax: this.cy + } , + attr: this.attr } , ' ' ) ; + log( "erase:" , count , "fill region:" , { + xmin: this.cx , ymin: this.cy , xmax: this.cx + count - 1 , ymax: this.cy + } ) ; + + this.drawDebounce() ; +} ; + + + +Vte.prototype.deleteLine = function( count = 1 ) { + if ( count > this.height - this.cy ) { count = this.height - this.cy ; } + if ( count <= 0 ) { return ; } + + // Shift from the end of the screen + if ( this.cy + count < this.height ) { + this.screenBuffer.copyRegion( { + xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1 + } , { x: 0 , y: this.cy } ) ; + log( "deleteLine:" , count , "copy region:" , { + xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1 + } , { x: 0 , y: this.cy } ) ; + } + + // Fill the end of the screen with blank + this.screenBuffer.fill( { region: { + xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1 + } , + attr: this.attr } , ' ' ) ; + log( "deleteLine:" , count , "fill region:" , { + xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1 + } ) ; + + // This move x to the left + this.cx = this.screenBuffer.cx = 0 ; + + this.drawDebounce() ; +} ; + + + +Vte.prototype.insertLine = function( count = 1 ) { + if ( count > this.height - this.cy ) { count = this.height - this.cy ; } + if ( count <= 0 ) { return ; } + + // Shift to the end of the screen + if ( this.cy + count < this.height ) { + //this.screenBuffer.copyRegion( { xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1 } , { x: 0 , y: this.cy } ) ; + this.screenBuffer.copyRegion( { + xmin: 0 , ymin: this.cy , xmax: this.width - 1 , ymax: this.height - count + } , { x: 0 , y: this.cy + count } ) ; + log( "insertLine:" , count , "copy region:" , { + xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1 + } , { x: 0 , y: this.cy } ) ; + } + + // Fill the inserted lines with blank + //this.screenBuffer.fill( { region: { xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1 } , attr: this.attr } , ' ' ) ; + this.screenBuffer.fill( { region: { + xmin: 0 , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + count + } , + attr: this.attr } , ' ' ) ; + log( "insertLine:" , count , "fill region:" , { + xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1 + } ) ; + + // This move x to the left + this.cx = this.screenBuffer.cx = 0 ; + + this.drawDebounce() ; +} ; + + + +Vte.prototype.resetAttr = function() { this.attr = this.screenBuffer.DEFAULT_ATTR ; } ; +Vte.prototype.setAttr = function( attrObject ) { this.attr = this.screenBuffer.object2attr( attrObject ) ; } ; +Vte.prototype.addAttr = function( attrObject ) { this.attr = this.screenBuffer.attrAndObject( this.attr , attrObject ) ; } ; + + + +Vte.prototype.setVScrollingRegion = function( ymin = null , ymax = null , internal = false ) { + log( "########################### setVScrollingRegion:" , ymin , ymax , internal ) ; + if ( ymin === null || ymax === null ) { + this.scrollingRegion = null ; + } + else if ( internal ) { + this.scrollingRegion = new Rect( 0 , Math.max( 0 , ymin ) , this.width - 1 , Math.min( this.height - 1 , ymax ) ) ; + } + else { + this.scrollingRegion = new Rect( 0 , Math.max( 0 , ymin - 1 ) , this.width - 1 , Math.min( this.height - 1 , ymax - 1 ) ) ; + } + log( "########################### setVScrollingRegion region:" , this.scrollingRegion ) ; +} ; + + + +// Emit cursor location escape sequence +Vte.prototype.emitCursorLocation = function( decVariant ) { + //this.emit( 'input' , '\x1b[' + ( decVariant ? '?' : '' ) + this.cy + ';' + this.cx + 'R' ) ; + this.emit( 'input' , string.format( toInputSequence.reports[ decVariant ? 'cursorLocationDecVariant' : 'cursorLocation' ] , this.cx , this.cy ) ) ; +} ; + + + +// Emit the screen size +Vte.prototype.emitScreenSize = function( decVariant ) { + this.emit( 'input' , string.format( toInputSequence.reports.screenSize , this.width , this.height ) ) ; +} ; + + + +// Emit the focus +Vte.prototype.emitFocus = function( isIn ) { + this.emit( 'input' , toInputSequence.reports[ isIn ? 'focusIn' : 'focusOut' ] ) ; +} ; + + + +// Emit the focus +Vte.prototype.emitRegisterColor = function( register ) { + logRed( "emitRegisterColor" , register ) ; + var rgb = this.screenBuffer.palette.getRgb( register ) ; + logRed( "emitRegisterColor >>> " , rgb ) ; + if ( ! rgb ) { return ; } + this.emit( 'input' , string.format( toInputSequence.reports.registerColor , register , rgb.r , rgb.g , rgb.b ) ) ; +} ; + + + +// Emit mouse event escape sequence using the SGR protocol +Vte.prototype.emitMouseSGR = function( type , data ) { + var code = 0 , released = false ; + + if ( data.shift ) { code |= 4 ; } + if ( data.alt ) { code |= 8 ; } + if ( data.ctrl ) { code |= 16 ; } + + switch ( type ) { + case 'MOUSE_LEFT_BUTTON_PRESSED' : + break ; + case 'MOUSE_MIDDLE_BUTTON_PRESSED' : + code |= 1 ; + break ; + case 'MOUSE_RIGHT_BUTTON_PRESSED' : + code |= 2 ; + break ; + case 'MOUSE_OTHER_BUTTON_PRESSED' : + code |= 3 ; + break ; + case 'MOUSE_LEFT_BUTTON_RELEASED' : + released = true ; + break ; + case 'MOUSE_MIDDLE_BUTTON_RELEASED' : + code |= 1 ; + released = true ; + break ; + case 'MOUSE_RIGHT_BUTTON_RELEASED' : + code |= 2 ; + released = true ; + break ; + case 'MOUSE_OTHER_BUTTON_RELEASED' : + code |= 3 ; + released = true ; + break ; + case 'MOUSE_WHEEL_UP' : + code |= 64 ; + break ; + case 'MOUSE_WHEEL_DOWN' : + code |= 65 ; + break ; + case 'MOUSE_MOTION' : + code |= 32 ; + break ; + } + + this.emit( 'input' , '\x1b[<' + code + ';' + data.x + ';' + data.y + ( released ? 'm' : 'M' ) ) ; +} ; + + + +// Event handlers + + + +Vte.prototype.onEventInputKey = function( key , altKeys , data ) { + log( 'onEventInputKey:' , key ) ; + if ( data.isCharacter ) { + this.emit( 'input' , key ) ; + } + else if ( toInputSequence.specialKeys[ key ] ) { + this.emit( 'input' , toInputSequence.specialKeys[ key ] ) ; + } +} ; + + + +Vte.prototype.onEventInputMouse = function( type , data ) { + if ( ! this.mouseEvent ) { return ; } + //log( 'onEventInputMouse:' , type , data ) ; + + // /!\ Not sure if it's the most reliable way to do that + if ( this.eventInput === this.screenBuffer.dst ) { + // We MUST add an offset to the coordinate + data.x -= this.screenBuffer.x - 1 ; + data.y -= this.screenBuffer.y - 1 ; + } + + switch ( type ) { + case 'MOUSE_LEFT_BUTTON_PRESSED' : + case 'MOUSE_MIDDLE_BUTTON_PRESSED' : + case 'MOUSE_RIGHT_BUTTON_PRESSED' : + case 'MOUSE_OTHER_BUTTON_PRESSED' : + this.mouseIsDragging = true ; + this.emitMouseSGR( type , data ) ; + break ; + case 'MOUSE_LEFT_BUTTON_RELEASED' : + case 'MOUSE_MIDDLE_BUTTON_RELEASED' : + case 'MOUSE_RIGHT_BUTTON_RELEASED' : + case 'MOUSE_OTHER_BUTTON_RELEASED' : + this.mouseIsDragging = false ; + this.emitMouseSGR( type , data ) ; + break ; + case 'MOUSE_WHEEL_UP' : + case 'MOUSE_WHEEL_DOWN' : + this.emitMouseSGR( type , data ) ; + break ; + case 'MOUSE_MOTION' : + if ( this.mouseEvent === 'motion' || ( this.mouseEvent === 'drag' && this.mouseIsDragging ) ) { + this.emitMouseSGR( type , data ) ; + } + break ; + } +} ; + + + +Vte.prototype.onEventInputTerminal = function( type , data ) { + switch ( type ) { + case 'FOCUS_IN' : + if ( this.focusEvent ) { this.emitFocus( true ) ; } + break ; + case 'FOCUS_OUT' : + if ( this.focusEvent ) { this.emitFocus( false ) ; } + break ; + } +} ; + + + +Vte.prototype.onChildOutputReset = function() { + logRed( 'full reset' ) ; +} ; + + + +Vte.prototype.onChildOutputChar = function( char , charCode ) { + //log( '>>> put char charCode:' , charCode ) ; + //log( 'put char:' , charCode <= 0x1f || charCode === 0x7f ? '(ctrl)' : char , charCode >= 0x10 ? '\\x' + charCode.toString( 16 ) : '\\x0' + charCode.toString( 16 ) ) ; + this.putChar( char ) ; +} ; + + + +Vte.prototype.onChildOutputCursor = function( subType , arg , extraArgs ) { + log( 'cursor:' , subType , arg , extraArgs ) ; + + var arg1 = extraArgs && extraArgs[ 0 ] ? + extraArgs[ 0 ] : undefined ; + var arg2 = extraArgs && extraArgs[ 1 ] ? + extraArgs[ 1 ] : undefined ; + + switch ( subType ) { + //case 'newLine' : return this.newLine() ; + case 'lineFeed' : + return this.lineFeed() ; + case 'carriageReturn' : + return this.moveCursorTo( 0 , undefined , true ) ; + case 'tab' : + return this.nextTab() ; + case 'move' : + // unused + this.moveCursor( arg1 , arg2 ) ; + break ; + case 'up' : + this.moveCursor( 0 , -arg1 ) ; + break ; + case 'down' : + this.moveCursor( 0 , arg1 ) ; + break ; + case 'right' : + this.moveCursor( arg1 , 0 ) ; + break ; + case 'left' : + this.moveCursor( -arg1 , 0 ) ; + break ; + case 'moveToYX' : + // Swap the args + this.moveCursorTo( arg2 , arg1 ) ; + break ; + case 'column' : + this.moveCursorTo( arg1 ) ; + break ; + case 'row' : + this.moveCursorTo( undefined , arg1 ) ; + break ; + case 'previousLine' : + this.moveCursor( -this.cx , -arg1 ) ; + break ; + case 'nextLine' : + this.moveCursor( -this.cx , arg1 ) ; + break ; + case 'save' : + this.savedCx = this.cx ; + this.savedCy = this.cy ; + break ; + case 'restore' : + this.moveCursorTo( this.savedCx , this.savedCy , true ) ; + break ; + default : + logRed( 'Unknown/unsupported cursor action' , subType , arg , extraArgs ) ; + } +} ; + + + +Vte.prototype.onChildOutputEdit = function( subType , arg , extraArgs ) { + var arg1 = extraArgs && extraArgs[ 0 ] ? + extraArgs[ 0 ] : undefined ; + var arg2 = extraArgs && extraArgs[ 1 ] ? + extraArgs[ 1 ] : undefined ; + + switch ( subType ) { + case 'backDelete' : + log( 'backDelete' , arg1 ) ; + return this.backDelete( arg1 ) ; + case 'delete' : + log( 'delete' , arg1 ) ; + return this.delete( arg1 ) ; + case 'erase' : + log( 'erase' , arg ) ; + this.erase( arg ) ; + break ; + case 'deleteLine' : + log( 'deleteLine' , arg1 ) ; + this.deleteLine( arg1 ) ; + break ; + case 'insertLine' : + log( 'insertLine' , arg1 ) ; + this.insertLine( arg1 ) ; + break ; + case 'eraseLine' : + log( 'eraseLine' , arg ) ; + this.eraseLine( arg ) ; + break ; + case 'eraseDisplay' : + log( 'eraseDisplay' , arg ) ; + this.eraseDisplay( arg ) ; + break ; + case 'reverseLineFeed' : + log( 'reverseLineFeed' ) ; + this.reverseLineFeed( arg ) ; + break ; + case 'vScrollingRegion' : + log( 'vScrollingRegion' , arg1 , arg2 ) ; + this.setVScrollingRegion( arg1 , arg2 ) ; + //this.setVScrollingRegion( arg1 , arg2 , true ) ; + break ; + case 'vScrollUp' : + log( 'vScrollUp' , arg1 ) ; + this.vScroll( -arg1 ) ; + break ; + case 'vScrollDown' : + log( 'vScrollDown' , arg1 ) ; + this.vScroll( arg1 ) ; + break ; + default : + logRed( 'Unknown/unsupported edit action' , subType , arg , extraArgs ) ; + } +} ; + + + +Vte.prototype.onChildOutputAttr = function( subType , arg , extraArgs ) { + switch ( subType ) { + case 'reset' : + log( 'ATTR reset' ) ; + this.resetAttr() ; + break ; + case 'bold' : + log( 'ATTR bold:' , arg ) ; + this.addAttr( { bold: arg } ) ; + break ; + case 'dim' : + log( 'ATTR dim:' , arg ) ; + this.addAttr( { dim: arg } ) ; + break ; + case 'italic' : + log( 'ATTR italic:' , arg ) ; + this.addAttr( { italic: arg } ) ; + break ; + case 'underline' : + log( 'ATTR underline:' , arg ) ; + this.addAttr( { underline: arg } ) ; + break ; + case 'blink' : + log( 'ATTR blink:' , arg ) ; + this.addAttr( { blink: arg } ) ; + break ; + case 'inverse' : + log( 'ATTR inverse:' , arg ) ; + this.addAttr( { inverse: arg } ) ; + break ; + case 'hidden' : + log( 'ATTR hidden:' , arg ) ; + this.addAttr( { hidden: arg } ) ; + break ; + case 'strike' : + log( 'ATTR strike:' , arg ) ; + this.addAttr( { strike: arg } ) ; + break ; + case 'noDimNoBold' : + log( 'ATTR noDimNoBold' ) ; + this.addAttr( { bold: false , dim: false } ) ; + break ; + case 'color' : + log( 'ATTR color:' , arg ) ; + this.addAttr( { color: arg } ) ; + break ; + case 'color256' : + log( 'ATTR color256:' , extraArgs ) ; + this.addAttr( { color: + extraArgs[ 0 ] } ) ; + break ; + case 'colorRgb' : + log( 'ATTR colorRgb:' , extraArgs , 'not supported ATM' ) ; + break ; + case 'bgColor' : + log( 'ATTR bgColor:' , arg ) ; + this.addAttr( { bgColor: arg } ) ; + break ; + case 'bgColor256' : + log( 'ATTR bgColor256:' , extraArgs ) ; + this.addAttr( { bgColor: + extraArgs[ 0 ] } ) ; + break ; + case 'bgColorRgb' : + log( 'ATTR bgColorRgb:' , extraArgs , 'not supported ATM' ) ; + break ; + default : + logRed( 'Unknown/unsupported ATTR' , subType , arg , extraArgs ) ; + } +} ; + + + +Vte.prototype.onChildOutputPalette = function( subType , extraArgs ) { + logRed( 'Palette command:' , subType , extraArgs ) ; + + var arg1 = extraArgs && extraArgs[ 0 ] ? + extraArgs[ 0 ] : undefined ; + + switch ( subType ) { + case 'getColor' : + if ( ! isNaN( arg1 ) ) { this.emitRegisterColor( arg1 ) ; } + break ; + } +} ; + + + +Vte.prototype.onChildOutputCursorAttr = function( subType , args ) { + logRed( 'Cursor ATTR command:' , subType , args ) ; +} ; + + + +Vte.prototype.onChildOutputBell = function() { + logRed( 'bell' ) ; +} ; + + + +Vte.prototype.onChildOutputDevice = function( subType , arg , extraArgs ) { + logRed( 'Device command:' , subType , arg , extraArgs ) ; + switch ( subType ) { + case 'mouseButton' : + this.mouseEvent = arg ? 'button' : null ; + break ; + case 'mouseDrag' : + this.mouseEvent = arg ? 'drag' : null ; + break ; + case 'mouseMotion' : + this.mouseEvent = arg ? 'motion' : null ; + break ; + case 'focusEvent' : + this.focusEvent = !! arg ; + break ; + case 'cursorLocation' : + // Arg is true for DEC mode (add a '?' to the sequence) + this.emitCursorLocation( arg ) ; + break ; + case 'screenSize' : + this.emitScreenSize( arg ) ; + break ; + default : + logRed( 'Unknown/unsupported device command' , subType , arg , extraArgs ) ; + } +} ; + + + +Vte.prototype.onChildOutputSystem = function( subType , args ) { + logRed( 'System command:' , subType , args ) ; +} ; + + + +// Triggered when unknown/unsupported sequences are produced + +Vte.prototype.onChildOutputControl = function( charCodeStr ) { + logRed( 'control' , charCodeStr ) ; +} ; + + + +Vte.prototype.onChildOutputESC = function( type , args ) { + logRed( 'ESC -- type:' , type , args ) ; +} ; + + + +Vte.prototype.onChildOutputCSI = function( type , args ) { + logRed( 'CSI -- type:' , type , ', args:' , args ) ; +} ; + + + +Vte.prototype.onChildOutputOSC = function( type , args ) { + logRed( 'OSC -- type:' , type , ', args:' , args ) ; +} ; + + +},{"../Rect.js":2,"../ScreenBuffer.js":3,"../termkit.js":50,"./SequencesReader.js":52,"./toInputSequence.js":55,"child_process":136,"child_pty":136,"nextgen-events":72,"seventh":108,"string-kit":123}],54:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +// Control char (not escape sequence) +exports.control = { + '\\x07': { event: 'bell' } , + + // Looks like it just moves to the left + //'\\x08': { event: 'edit' , subType: 'backDelete' , extraArgs: [ 1 ] } , + '\\x08': { event: 'cursor' , subType: 'left' , extraArgs: [ 1 ] } , + + '\\x09': { event: 'cursor' , subType: 'tab' } , + '\\x0a': { event: 'cursor' , subType: 'lineFeed' } , + '\\x0d': { event: 'cursor' , subType: 'carriageReturn' } , + + // Looks like it does nothing at all + //'\\x7f': { event: 'edit' , subType: 'delete' , extraArgs: [ 1 ] } , + '\\x7f': { event: 'none' } +} ; + + + +// ESC (simple ESC + char sequence) +exports.ESC = { + '7': { event: 'cursor' , subType: 'save' } , + '8': { event: 'cursor' , subType: 'restore' } , + 'c': { event: 'reset' } , + 'M': { event: 'edit' , subType: 'reverseLineFeed' } +} ; + + + +// CSI tree (Control Sequence Introducer) +exports.CSI = { + A: { event: 'cursor' , subType: 'up' , defaultExtraArgs: [ 1 ] } , + B: { event: 'cursor' , subType: 'down' , defaultExtraArgs: [ 1 ] } , + C: { event: 'cursor' , subType: 'right' , defaultExtraArgs: [ 1 ] } , + D: { event: 'cursor' , subType: 'left' , defaultExtraArgs: [ 1 ] } , + E: { event: 'cursor' , subType: 'nextLine' , defaultExtraArgs: [ 1 ] } , + F: { event: 'cursor' , subType: 'previousLine' , defaultExtraArgs: [ 1 ] } , + G: { event: 'cursor' , subType: 'column' } , + H: { event: 'cursor' , subType: 'moveToYX' , defaultExtraArgs: [ 1 , 1 ] } , + + J: { + event: 'edit' , + subType: 'eraseDisplay' , + arg: 'after' , + subTree: { + '0': { arg: 'after' } , + '1': { arg: 'before' } , + '2': { arg: 'display' } + } + } , + + K: { + event: 'edit' , + subType: 'eraseLine' , + arg: 'after' , + subTree: { + '0': { arg: 'after' } , + '1': { arg: 'before' } , + '2': { arg: 'line' } + } + } , + + L: { event: 'edit' , subType: 'insertLine' , defaultExtraArgs: [ 1 ] } , + M: { event: 'edit' , subType: 'deleteLine' , defaultExtraArgs: [ 1 ] } , + + P: { event: 'edit' , subType: 'delete' , defaultExtraArgs: [ 1 ] } , + S: { event: 'edit' , subType: 'vScrollUp' , defaultExtraArgs: [ 1 ] } , + T: { event: 'edit' , subType: 'vScrollDown' , defaultExtraArgs: [ 1 ] } , + X: { event: 'edit' , subType: 'erase' , defaultExtraArgs: [ 1 ] } , + + d: { event: 'cursor' , subType: 'row' } , + + '?h': { + event: 'device' , + arg: true , + subTree: { + '1000': { subType: 'mouseButton' , continue: true } , + '1002': { subType: 'mouseDrag' , continue: true } , + '1003': { subType: 'mouseMotion' , continue: true } , + '1004': { subType: 'focusEvent' , continue: true } , + '1006': { event: 'none' , continue: true } // we only support SGR anyway + } + } , + '?l': { + // This is the "turn off" counter-part of '?h' type, the subTree is copied from '?h' after the current assignment + event: 'device' , + arg: false , + subTree: null + } , + + n: { + // Device status report + event: 'device' , + subTree: { + '6': { subType: 'cursorLocation' } + } + } , + '?n': { + // Device status report (again) + event: 'device' , + subTree: { + '6': { subType: 'cursorLocation' , arg: true } + } + } , + + m: { + // Known as SGR (Select Graphic Rendition) + event: 'attr' , + subType: 'reset' , // if empty, it is usually a reset + subTree: { + '0': { subType: 'reset' , continue: true } , + + '1': { subType: 'bold' , arg: true , continue: true } , + '2': { subType: 'dim' , arg: true , continue: true } , + '3': { subType: 'italic' , arg: true , continue: true } , + '4': { subType: 'underline' , arg: true , continue: true } , + '5': { subType: 'blink' , arg: true , continue: true } , + '7': { subType: 'inverse' , arg: true , continue: true } , + '8': { subType: 'hidden' , arg: true , continue: true } , + '9': { subType: 'strike' , arg: true , continue: true } , + + '21': { subType: 'bold' , arg: false , continue: true } , + '22': { subType: 'noDimNoBold' , continue: true } , + '23': { subType: 'italic' , arg: false , continue: true } , + '24': { subType: 'underline' , arg: false , continue: true } , + '25': { subType: 'blink' , arg: false , continue: true } , + '27': { subType: 'inverse' , arg: false , continue: true } , + '28': { subType: 'hidden' , arg: false , continue: true } , + '29': { subType: 'strike' , arg: false , continue: true } , + + '30': { subType: 'color' , arg: 'black' , continue: true } , + '31': { subType: 'color' , arg: 'red' , continue: true } , + '32': { subType: 'color' , arg: 'green' , continue: true } , + '33': { subType: 'color' , arg: 'yellow' , continue: true } , + '34': { subType: 'color' , arg: 'blue' , continue: true } , + '35': { subType: 'color' , arg: 'magenta' , continue: true } , + '36': { subType: 'color' , arg: 'cyan' , continue: true } , + '37': { subType: 'color' , arg: 'white' , continue: true } , + '38': { + subTree: { + '2': { subType: 'colorRgb' } , + '5': { subType: 'color256' } + } + } , + '39': { subType: 'color' , arg: 'default' , continue: true } , + + '40': { subType: 'bgColor' , arg: 'black' , continue: true } , + '41': { subType: 'bgColor' , arg: 'red' , continue: true } , + '42': { subType: 'bgColor' , arg: 'green' , continue: true } , + '43': { subType: 'bgColor' , arg: 'yellow' , continue: true } , + '44': { subType: 'bgColor' , arg: 'blue' , continue: true } , + '45': { subType: 'bgColor' , arg: 'magenta' , continue: true } , + '46': { subType: 'bgColor' , arg: 'cyan' , continue: true } , + '47': { subType: 'bgColor' , arg: 'white' , continue: true } , + '48': { + subTree: { + '2': { subType: 'bgColorRgb' } , + '5': { subType: 'bgColor256' } + } + } , + '49': { subType: 'bgColor' , arg: 'default' , continue: true } , + + '90': { subType: 'color' , arg: 'gray' , continue: true } , + '91': { subType: 'color' , arg: 'brightRed' , continue: true } , + '92': { subType: 'color' , arg: 'brightGreen' , continue: true } , + '93': { subType: 'color' , arg: 'brightYellow' , continue: true } , + '94': { subType: 'color' , arg: 'brightBlue' , continue: true } , + '95': { subType: 'color' , arg: 'brightMagenta' , continue: true } , + '96': { subType: 'color' , arg: 'brightCyan' , continue: true } , + '97': { subType: 'color' , arg: 'brightWhite' , continue: true } , + + '100': { subType: 'bgColor' , arg: 'gray' , continue: true } , + '101': { subType: 'bgColor' , arg: 'brightRed' , continue: true } , + '102': { subType: 'bgColor' , arg: 'brightGreen' , continue: true } , + '103': { subType: 'bgColor' , arg: 'brightYellow' , continue: true } , + '104': { subType: 'bgColor' , arg: 'brightBlue' , continue: true } , + '105': { subType: 'bgColor' , arg: 'brightMagenta' , continue: true } , + '106': { subType: 'bgColor' , arg: 'brightCyan' , continue: true } , + '107': { subType: 'bgColor' , arg: 'brightWhite' , continue: true } + } + } , + + r: { event: 'edit' , subType: 'vScrollingRegion' } , + t: { + event: 'device' , + subTree: { + '18': { subType: 'screenSize' } + } + } +} ; + +exports.CSI['?l'].subTree = exports.CSI['?h'].subTree ; + + + +// OSC tree (OS Command) +exports.OSC = { + '0': { event: 'system' , subType: 'setWindowTitle' } , + '1': { event: 'system' , subType: 'setIconName' } , + '2': { event: 'system' , subType: 'setWindowTitle' } , + '4': { event: 'palette' , subType: 'setColor' } , + '4?': { event: 'palette' , subType: 'getColor' } , + '7': { event: 'system' , subType: 'setCwd' } , + '9': { event: 'system' , subType: 'notify' } , // iTerm2 growl notification, only a body argument + '10': { event: 'palette' , subType: 'setDefaultColor' } , + '11': { event: 'palette' , subType: 'setDefaultBgColor' } , + '12': { event: 'cursorAttr' , subType: 'setColor' } , + '17': { event: 'palette' , subType: 'setHighlightBgColor' } , + '50': { event: 'cursorAttr' , subType: 'setShape' } , + '104': { event: 'palette' , subType: 'resetColor' } , + '110': { event: 'palette' , subType: 'resetDefaultColor' } , + '111': { event: 'palette' , subType: 'resetDefaultBgColor' } , + '112': { event: 'cursorAttr' , subType: 'resetColor' } , + '117': { event: 'palette' , subType: 'resetHighlightBgColor' } , + '777': { // rxvt/urxvt module, only support notifications + event: 'system' , + subTree: { + 'notify': { subType: 'notify' } // notify with a title and body arguments + } + } +} ; + + +},{}],55:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +exports.specialKeys = { + /* + From the Xterm config (lib/termconfig/xterm.js). + When multiple keys exist, the chosen one is (by order of preference): + - ultimately the sequence that avoid overlapping + - the more consistent sequence + - the actual Gnome-terminal sequence + - the actual Xterm sequence + */ + + ESCAPE: '\x1b' , + TAB: '\x09' , + ENTER: '\x0d' , + + SHIFT_TAB: '\x1b[Z' , + ALT_TAB: '\x1b\x09' , // Also CTRL_ALT_I, most of time it is grabbed by the window manager before reaching the terminal + ALT_ENTER: '\x1b\x0d' , + + UP: '\x1bOA' , + DOWN: '\x1bOB' , + RIGHT: '\x1bOC' , + LEFT: '\x1bOD' , + + SHIFT_UP: '\x1b[1;2A' , + SHIFT_DOWN: '\x1b[1;2B' , + SHIFT_RIGHT: '\x1b[1;2C' , + SHIFT_LEFT: '\x1b[1;2D' , + ALT_UP: '\x1b[1;3A' , + ALT_DOWN: '\x1b[1;3B' , + ALT_RIGHT: '\x1b[1;3C' , + ALT_LEFT: '\x1b[1;3D' , + CTRL_UP: '\x1b[1;5A' , + CTRL_DOWN: '\x1b[1;5B' , + CTRL_RIGHT: '\x1b[1;5C' , + CTRL_LEFT: '\x1b[1;5D' , + + BACKSPACE: '\x7f' , + INSERT: '\x1b[2~' , + DELETE: '\x1b[3~' , + HOME: '\x1b[1~' , + END: '\x1b[4~' , + PAGE_UP: '\x1b[5~' , + PAGE_DOWN: '\x1b[6~' , + + CTRL_BACKSPACE: '\x08' , + CTRL_INSERT: '\x1b[2;5~' , + CTRL_DELETE: '\x1b[3;5~' , + CTRL_HOME: '\x1b[1;5~' , + CTRL_END: '\x1b[4;5~' , + CTRL_PAGE_UP: '\x1b[5;5~' , + CTRL_PAGE_DOWN: '\x1b[6;5~' , + + SHIFT_INSERT: '\x1b[2;2~' , + SHIFT_DELETE: '\x1b[3;2~' , + SHIFT_HOME: '\x1b[1;2~' , + SHIFT_END: '\x1b[4;2~' , + SHIFT_PAGE_UP: '\x1b[5;2~' , + SHIFT_PAGE_DOWN: '\x1b[6;2~' , + + ALT_BACKSPACE: '\x1b\x7f' , + ALT_INSERT: '\x1b[2;3~' , + ALT_DELETE: '\x1b[3;3~' , + ALT_HOME: '\x1b[1;3~' , + ALT_END: '\x1b[4;3~' , + ALT_PAGE_UP: '\x1b[5;3~' , + ALT_PAGE_DOWN: '\x1b[6;3~' , + + // Application Keypad + /* + KP_NUMLOCK: [] , // '\x1bOP' , + KP_DIVIDE: '\x1bOo' , + KP_MULTIPLY: '\x1bOj' , + KP_MINUS: '\x1bOm' , + KP_0: [] , // '\x1b[2~' , + KP_1: [] , // '\x1bOF' , + KP_2: [] , // '\x1b[B' , + KP_3: [] , // '\x1b[6~' , + KP_4: [] , // '\x1b[D' , + KP_5: [ '\x1bOE' , '\x1b[E' ] , + KP_6: [] , // '\x1b[C' , + KP_7: [] , // '\x1bOH' , + KP_8: [] , // '\x1b[A' , + KP_9: [] , // '\x1b[5~' , + KP_PLUS: '\x1bOk' , + KP_DELETE: [] , // '\x1b[3~' , + KP_ENTER: '\x1bOM' , + */ + + F1: '\x1bOP' , + F2: '\x1bOQ' , + F3: '\x1bOR' , + F4: '\x1bOS' , + F5: '\x1b[15~' , + F6: '\x1b[17~' , + F7: '\x1b[18~' , + F8: '\x1b[19~' , + F9: '\x1b[20~' , + F10: '\x1b[21~' , + F11: '\x1b[23~' , + F12: '\x1b[24~' , + + SHIFT_F1: '\x1bO1;2P' , + SHIFT_F2: '\x1bO1;2Q' , + SHIFT_F3: '\x1bO1;2R' , + SHIFT_F4: '\x1bO1;2S' , + SHIFT_F5: '\x1b[15;2~' , + SHIFT_F6: '\x1b[17;2~' , + SHIFT_F7: '\x1b[18;2~' , + SHIFT_F8: '\x1b[19;2~' , + SHIFT_F9: '\x1b[20;2~' , + SHIFT_F10: '\x1b[21;2~' , + SHIFT_F11: '\x1b[23;2~' , + SHIFT_F12: '\x1b[24;2~' , + + CTRL_F1: '\x1bO1;5P' , + CTRL_F2: '\x1bO1;5Q' , + CTRL_F3: '\x1bO1;5R' , // '\x1b[1;5R' is also used for cursor location response... :/ + CTRL_F4: '\x1bO1;5S' , + CTRL_F5: '\x1b[15;5~' , + CTRL_F6: '\x1b[17;5~' , + CTRL_F7: '\x1b[18;5~' , + CTRL_F8: '\x1b[19;5~' , + CTRL_F9: '\x1b[20;5~' , + CTRL_F10: '\x1b[21;5~' , + CTRL_F11: '\x1b[23;5~' , + CTRL_F12: '\x1b[24;5~' , + + CTRL_SHIFT_F1: '\x1bO1;6P' , + CTRL_SHIFT_F2: '\x1bO1;6Q' , + CTRL_SHIFT_F3: '\x1bO1;6R' , + CTRL_SHIFT_F4: '\x1bO1;6S' , + CTRL_SHIFT_F5: '\x1b[15;6~' , + CTRL_SHIFT_F6: '\x1b[17;6~' , + CTRL_SHIFT_F7: '\x1b[18;6~' , + CTRL_SHIFT_F8: '\x1b[19;6~' , + CTRL_SHIFT_F9: '\x1b[20;6~' , + CTRL_SHIFT_F10: '\x1b[21;6~' , + CTRL_SHIFT_F11: '\x1b[23;6~' , + CTRL_SHIFT_F12: '\x1b[24;6~' , + + NUL: '\x00' , + + //CTRL_SPACE: '\x00' , // also NUL + ALT_SPACE: '\x1b ' , + CTRL_ALT_SPACE: '\x1b\x00' +} ; + + + +// Complete with Modifier + [A-Z] +for ( let i = 1 ; i <= 26 ; i ++ ) { + exports.specialKeys[ 'CTRL_' + String.fromCharCode( 64 + i ) ] = String.fromCharCode( i ) ; + exports.specialKeys[ 'ALT_' + String.fromCharCode( 64 + i ) ] = '\x1b' + String.fromCharCode( 96 + i ) ; + exports.specialKeys[ 'CTRL_ALT_' + String.fromCharCode( 64 + i ) ] = '\x1b' + String.fromCharCode( i ) ; + exports.specialKeys[ 'ALT_SHIFT_' + String.fromCharCode( 64 + i ) ] = '\x1b' + String.fromCharCode( 64 + i ) ; +} + + + +exports.reports = { + cursorLocation: '\x1b[%+1u;%-1uR' , + cursorLocationDecVariant: '\x1b[?%+1u;%-1uR' , + screenSize: '\x1b[8;%+1u;%-1ut' , + focusIn: '\x1b[I' , + focusOut: '\x1b[O' , + registerColor: '\x1b]4;%u;rgb:%x/%x/%x\x07' // register,r,g,b +} ; + + +},{}],56:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +/* + Windows compatibility. +*/ + +module.exports = function( termkit ) { + termkit.globalConfig.preferProcessSigwinch = true ; +} ; + + + +},{}],57:[function(require,module,exports){ +/* + Terminal Kit + + Copyright (c) 2009 - 2021 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +var Promise = require( 'seventh' ) ; + + + +/* + yesOrNo( [yes] , [no] , callback ) + * options `Object` + * yes `string` or `Array` contains a key code or an array of key code that will trigger the yes + * no `string` or `Array` contains a key code or an array of key code that will trigger the no + * echoYes `string` if defined this will be what will be outputed in case of yes + * echoNo `string` if defined this will be what will be outputed in case of no + * callback( error , result ) + * result: true for 'yes' or false for 'no' +*/ +module.exports = function yesOrNo( options , callback ) { + if ( typeof options === 'function' ) { callback = options ; options = undefined ; } + + if ( ! options || typeof options !== 'object' ) { + options = { + yes: [ 'y' , 'Y' ] , + no: [ 'n' , 'N' ] , + echoYes: 'yes' , + echoNo: 'no' + } ; + } + + if ( typeof options.yes === 'string' ) { options.yes = [ options.yes ] ; } + if ( ! Array.isArray( options.yes ) ) { options.yes = [ 'y' , 'Y' ] ; } + + if ( typeof options.no === 'string' ) { options.no = [ options.no ] ; } + if ( ! Array.isArray( options.no ) ) { options.no = [ 'n' , 'N' ] ; } + + if ( ! this.grabbing ) { this.grabInput() ; } + + var onKey = key => { + if ( options.yes.indexOf( key ) !== -1 ) { + if ( options.echoYes ) { this( options.echoYes ) ; } + this.removeListener( 'key' , onKey ) ; + + if ( callback ) { callback( undefined , true ) ; } + else { controller.promise.resolve( true ) ; } + } + else if ( options.no.indexOf( key ) !== -1 ) { + if ( options.echoNo ) { this( options.echoNo ) ; } + this.removeListener( 'key' , onKey ) ; + + if ( callback ) { callback( undefined , false ) ; } + else { controller.promise.resolve( false ) ; } + } + } ; + + this.on( 'key' , onKey ) ; + + var controller = {} ; //Object.create( NextGenEvents.prototype ) ; + + // Stop everything and do not even call the callback + controller.abort = () => { + this.removeListener( 'key' , onKey ) ; + } ; + + controller.promise = new Promise() ; + + return controller ; +} ; + + +},{"seventh":108}],58:[function(require,module,exports){ +(function (Buffer){(function (){ +'use strict' + +var ndarray = require('ndarray') +var PNG = require('pngjs').PNG +var jpeg = require('jpeg-js') +var pack = require('ndarray-pack') +var GifReader = require('omggif').GifReader +var Bitmap = require('node-bitmap') +var fs = require('fs') +var extname = require('path').extname + +function handlePNG(data, cb) { + var png = new PNG(); + png.parse(data, function(err, img_data) { + if(err) { + cb(err) + return + } + cb(null, ndarray(new Uint8Array(img_data.data), + [img_data.width|0, img_data.height|0, 4], + [4, 4*img_data.width|0, 1], + 0)) + }) +} + +function handleJPEG(data, cb) { + var jpegData + try { + jpegData = jpeg.decode(data) + } + catch(e) { + cb(e) + return + } + if(!jpegData) { + cb(new Error("Error decoding jpeg")) + return + } + var nshape = [ jpegData.height, jpegData.width, 4 ] + var result = ndarray(jpegData.data, nshape) + cb(null, result.transpose(1,0)) +} + +function handleGIF(data, cb) { + var reader, nshape, ndata, result + try { + reader = new GifReader(data) + } catch(err) { + cb(err) + return + } + if(reader.numFrames() > 0) { + nshape = [reader.numFrames(), reader.height, reader.width, 4] + ndata = new Uint8Array(nshape[0] * nshape[1] * nshape[2] * nshape[3]) + result = ndarray(ndata, nshape) + try { + for(var i=0; i max ? max : x; + }; + + var clip_rgb = function (rgb) { + rgb._clipped = false; + rgb._unclipped = rgb.slice(0); + for (var i=0; i<=3; i++) { + if (i < 3) { + if (rgb[i] < 0 || rgb[i] > 255) { rgb._clipped = true; } + rgb[i] = limit(rgb[i], 0, 255); + } else if (i === 3) { + rgb[i] = limit(rgb[i], 0, 1); + } + } + return rgb; + }; + + // ported from jQuery's $.type + var classToType = {}; + for (var i = 0, list = ['Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Undefined', 'Null']; i < list.length; i += 1) { + var name = list[i]; + + classToType[("[object " + name + "]")] = name.toLowerCase(); + } + var type = function(obj) { + return classToType[Object.prototype.toString.call(obj)] || "object"; + }; + + var unpack = function (args, keyOrder) { + if ( keyOrder === void 0 ) keyOrder=null; + + // if called with more than 3 arguments, we return the arguments + if (args.length >= 3) { return Array.prototype.slice.call(args); } + // with less than 3 args we check if first arg is object + // and use the keyOrder string to extract and sort properties + if (type(args[0]) == 'object' && keyOrder) { + return keyOrder.split('') + .filter(function (k) { return args[0][k] !== undefined; }) + .map(function (k) { return args[0][k]; }); + } + // otherwise we just return the first argument + // (which we suppose is an array of args) + return args[0]; + }; + + var last = function (args) { + if (args.length < 2) { return null; } + var l = args.length-1; + if (type(args[l]) == 'string') { return args[l].toLowerCase(); } + return null; + }; + + var PI = Math.PI; + + var utils = { + clip_rgb: clip_rgb, + limit: limit, + type: type, + unpack: unpack, + last: last, + PI: PI, + TWOPI: PI*2, + PITHIRD: PI/3, + DEG2RAD: PI / 180, + RAD2DEG: 180 / PI + }; + + var input = { + format: {}, + autodetect: [] + }; + + var last$1 = utils.last; + var clip_rgb$1 = utils.clip_rgb; + var type$1 = utils.type; + + + var Color = function Color() { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var me = this; + if (type$1(args[0]) === 'object' && + args[0].constructor && + args[0].constructor === this.constructor) { + // the argument is already a Color instance + return args[0]; + } + + // last argument could be the mode + var mode = last$1(args); + var autodetect = false; + + if (!mode) { + autodetect = true; + if (!input.sorted) { + input.autodetect = input.autodetect.sort(function (a,b) { return b.p - a.p; }); + input.sorted = true; + } + // auto-detect format + for (var i = 0, list = input.autodetect; i < list.length; i += 1) { + var chk = list[i]; + + mode = chk.test.apply(chk, args); + if (mode) { break; } + } + } + + if (input.format[mode]) { + var rgb = input.format[mode].apply(null, autodetect ? args : args.slice(0,-1)); + me._rgb = clip_rgb$1(rgb); + } else { + throw new Error('unknown format: '+args); + } + + // add alpha channel + if (me._rgb.length === 3) { me._rgb.push(1); } + }; + + Color.prototype.toString = function toString () { + if (type$1(this.hex) == 'function') { return this.hex(); } + return ("[" + (this._rgb.join(',')) + "]"); + }; + + var Color_1 = Color; + + var chroma = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( chroma.Color, [ null ].concat( args) )); + }; + + chroma.Color = Color_1; + chroma.version = '2.1.2'; + + var chroma_1 = chroma; + + var unpack$1 = utils.unpack; + var max = Math.max; + + var rgb2cmyk = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$1(args, 'rgb'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + r = r / 255; + g = g / 255; + b = b / 255; + var k = 1 - max(r,max(g,b)); + var f = k < 1 ? 1 / (1-k) : 0; + var c = (1-r-k) * f; + var m = (1-g-k) * f; + var y = (1-b-k) * f; + return [c,m,y,k]; + }; + + var rgb2cmyk_1 = rgb2cmyk; + + var unpack$2 = utils.unpack; + + var cmyk2rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$2(args, 'cmyk'); + var c = args[0]; + var m = args[1]; + var y = args[2]; + var k = args[3]; + var alpha = args.length > 4 ? args[4] : 1; + if (k === 1) { return [0,0,0,alpha]; } + return [ + c >= 1 ? 0 : 255 * (1-c) * (1-k), // r + m >= 1 ? 0 : 255 * (1-m) * (1-k), // g + y >= 1 ? 0 : 255 * (1-y) * (1-k), // b + alpha + ]; + }; + + var cmyk2rgb_1 = cmyk2rgb; + + var unpack$3 = utils.unpack; + var type$2 = utils.type; + + + + Color_1.prototype.cmyk = function() { + return rgb2cmyk_1(this._rgb); + }; + + chroma_1.cmyk = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['cmyk']) )); + }; + + input.format.cmyk = cmyk2rgb_1; + + input.autodetect.push({ + p: 2, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$3(args, 'cmyk'); + if (type$2(args) === 'array' && args.length === 4) { + return 'cmyk'; + } + } + }); + + var unpack$4 = utils.unpack; + var last$2 = utils.last; + var rnd = function (a) { return Math.round(a*100)/100; }; + + /* + * supported arguments: + * - hsl2css(h,s,l) + * - hsl2css(h,s,l,a) + * - hsl2css([h,s,l], mode) + * - hsl2css([h,s,l,a], mode) + * - hsl2css({h,s,l,a}, mode) + */ + var hsl2css = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var hsla = unpack$4(args, 'hsla'); + var mode = last$2(args) || 'lsa'; + hsla[0] = rnd(hsla[0] || 0); + hsla[1] = rnd(hsla[1]*100) + '%'; + hsla[2] = rnd(hsla[2]*100) + '%'; + if (mode === 'hsla' || (hsla.length > 3 && hsla[3]<1)) { + hsla[3] = hsla.length > 3 ? hsla[3] : 1; + mode = 'hsla'; + } else { + hsla.length = 3; + } + return (mode + "(" + (hsla.join(',')) + ")"); + }; + + var hsl2css_1 = hsl2css; + + var unpack$5 = utils.unpack; + + /* + * supported arguments: + * - rgb2hsl(r,g,b) + * - rgb2hsl(r,g,b,a) + * - rgb2hsl([r,g,b]) + * - rgb2hsl([r,g,b,a]) + * - rgb2hsl({r,g,b,a}) + */ + var rgb2hsl = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$5(args, 'rgba'); + var r = args[0]; + var g = args[1]; + var b = args[2]; + + r /= 255; + g /= 255; + b /= 255; + + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + + var l = (max + min) / 2; + var s, h; + + if (max === min){ + s = 0; + h = Number.NaN; + } else { + s = l < 0.5 ? (max - min) / (max + min) : (max - min) / (2 - max - min); + } + + if (r == max) { h = (g - b) / (max - min); } + else if (g == max) { h = 2 + (b - r) / (max - min); } + else if (b == max) { h = 4 + (r - g) / (max - min); } + + h *= 60; + if (h < 0) { h += 360; } + if (args.length>3 && args[3]!==undefined) { return [h,s,l,args[3]]; } + return [h,s,l]; + }; + + var rgb2hsl_1 = rgb2hsl; + + var unpack$6 = utils.unpack; + var last$3 = utils.last; + + + var round = Math.round; + + /* + * supported arguments: + * - rgb2css(r,g,b) + * - rgb2css(r,g,b,a) + * - rgb2css([r,g,b], mode) + * - rgb2css([r,g,b,a], mode) + * - rgb2css({r,g,b,a}, mode) + */ + var rgb2css = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var rgba = unpack$6(args, 'rgba'); + var mode = last$3(args) || 'rgb'; + if (mode.substr(0,3) == 'hsl') { + return hsl2css_1(rgb2hsl_1(rgba), mode); + } + rgba[0] = round(rgba[0]); + rgba[1] = round(rgba[1]); + rgba[2] = round(rgba[2]); + if (mode === 'rgba' || (rgba.length > 3 && rgba[3]<1)) { + rgba[3] = rgba.length > 3 ? rgba[3] : 1; + mode = 'rgba'; + } + return (mode + "(" + (rgba.slice(0,mode==='rgb'?3:4).join(',')) + ")"); + }; + + var rgb2css_1 = rgb2css; + + var unpack$7 = utils.unpack; + var round$1 = Math.round; + + var hsl2rgb = function () { + var assign; + + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + args = unpack$7(args, 'hsl'); + var h = args[0]; + var s = args[1]; + var l = args[2]; + var r,g,b; + if (s === 0) { + r = g = b = l*255; + } else { + var t3 = [0,0,0]; + var c = [0,0,0]; + var t2 = l < 0.5 ? l * (1+s) : l+s-l*s; + var t1 = 2 * l - t2; + var h_ = h / 360; + t3[0] = h_ + 1/3; + t3[1] = h_; + t3[2] = h_ - 1/3; + for (var i=0; i<3; i++) { + if (t3[i] < 0) { t3[i] += 1; } + if (t3[i] > 1) { t3[i] -= 1; } + if (6 * t3[i] < 1) + { c[i] = t1 + (t2 - t1) * 6 * t3[i]; } + else if (2 * t3[i] < 1) + { c[i] = t2; } + else if (3 * t3[i] < 2) + { c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6; } + else + { c[i] = t1; } + } + (assign = [round$1(c[0]*255),round$1(c[1]*255),round$1(c[2]*255)], r = assign[0], g = assign[1], b = assign[2]); + } + if (args.length > 3) { + // keep alpha channel + return [r,g,b,args[3]]; + } + return [r,g,b,1]; + }; + + var hsl2rgb_1 = hsl2rgb; + + var RE_RGB = /^rgb\(\s*(-?\d+),\s*(-?\d+)\s*,\s*(-?\d+)\s*\)$/; + var RE_RGBA = /^rgba\(\s*(-?\d+),\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*([01]|[01]?\.\d+)\)$/; + var RE_RGB_PCT = /^rgb\(\s*(-?\d+(?:\.\d+)?)%,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*\)$/; + var RE_RGBA_PCT = /^rgba\(\s*(-?\d+(?:\.\d+)?)%,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)$/; + var RE_HSL = /^hsl\(\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*\)$/; + var RE_HSLA = /^hsla\(\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)$/; + + var round$2 = Math.round; + + var css2rgb = function (css) { + css = css.toLowerCase().trim(); + var m; + + if (input.format.named) { + try { + return input.format.named(css); + } catch (e) { + // eslint-disable-next-line + } + } + + // rgb(250,20,0) + if ((m = css.match(RE_RGB))) { + var rgb = m.slice(1,4); + for (var i=0; i<3; i++) { + rgb[i] = +rgb[i]; + } + rgb[3] = 1; // default alpha + return rgb; + } + + // rgba(250,20,0,0.4) + if ((m = css.match(RE_RGBA))) { + var rgb$1 = m.slice(1,5); + for (var i$1=0; i$1<4; i$1++) { + rgb$1[i$1] = +rgb$1[i$1]; + } + return rgb$1; + } + + // rgb(100%,0%,0%) + if ((m = css.match(RE_RGB_PCT))) { + var rgb$2 = m.slice(1,4); + for (var i$2=0; i$2<3; i$2++) { + rgb$2[i$2] = round$2(rgb$2[i$2] * 2.55); + } + rgb$2[3] = 1; // default alpha + return rgb$2; + } + + // rgba(100%,0%,0%,0.4) + if ((m = css.match(RE_RGBA_PCT))) { + var rgb$3 = m.slice(1,5); + for (var i$3=0; i$3<3; i$3++) { + rgb$3[i$3] = round$2(rgb$3[i$3] * 2.55); + } + rgb$3[3] = +rgb$3[3]; + return rgb$3; + } + + // hsl(0,100%,50%) + if ((m = css.match(RE_HSL))) { + var hsl = m.slice(1,4); + hsl[1] *= 0.01; + hsl[2] *= 0.01; + var rgb$4 = hsl2rgb_1(hsl); + rgb$4[3] = 1; + return rgb$4; + } + + // hsla(0,100%,50%,0.5) + if ((m = css.match(RE_HSLA))) { + var hsl$1 = m.slice(1,4); + hsl$1[1] *= 0.01; + hsl$1[2] *= 0.01; + var rgb$5 = hsl2rgb_1(hsl$1); + rgb$5[3] = +m[4]; // default alpha = 1 + return rgb$5; + } + }; + + css2rgb.test = function (s) { + return RE_RGB.test(s) || + RE_RGBA.test(s) || + RE_RGB_PCT.test(s) || + RE_RGBA_PCT.test(s) || + RE_HSL.test(s) || + RE_HSLA.test(s); + }; + + var css2rgb_1 = css2rgb; + + var type$3 = utils.type; + + + + + Color_1.prototype.css = function(mode) { + return rgb2css_1(this._rgb, mode); + }; + + chroma_1.css = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['css']) )); + }; + + input.format.css = css2rgb_1; + + input.autodetect.push({ + p: 5, + test: function (h) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + + if (!rest.length && type$3(h) === 'string' && css2rgb_1.test(h)) { + return 'css'; + } + } + }); + + var unpack$8 = utils.unpack; + + input.format.gl = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var rgb = unpack$8(args, 'rgba'); + rgb[0] *= 255; + rgb[1] *= 255; + rgb[2] *= 255; + return rgb; + }; + + chroma_1.gl = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['gl']) )); + }; + + Color_1.prototype.gl = function() { + var rgb = this._rgb; + return [rgb[0]/255, rgb[1]/255, rgb[2]/255, rgb[3]]; + }; + + var unpack$9 = utils.unpack; + + var rgb2hcg = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$9(args, 'rgb'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var c = delta * 100 / 255; + var _g = min / (255 - delta) * 100; + var h; + if (delta === 0) { + h = Number.NaN; + } else { + if (r === max) { h = (g - b) / delta; } + if (g === max) { h = 2+(b - r) / delta; } + if (b === max) { h = 4+(r - g) / delta; } + h *= 60; + if (h < 0) { h += 360; } + } + return [h, c, _g]; + }; + + var rgb2hcg_1 = rgb2hcg; + + var unpack$a = utils.unpack; + var floor = Math.floor; + + /* + * this is basically just HSV with some minor tweaks + * + * hue.. [0..360] + * chroma .. [0..1] + * grayness .. [0..1] + */ + + var hcg2rgb = function () { + var assign, assign$1, assign$2, assign$3, assign$4, assign$5; + + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + args = unpack$a(args, 'hcg'); + var h = args[0]; + var c = args[1]; + var _g = args[2]; + var r,g,b; + _g = _g * 255; + var _c = c * 255; + if (c === 0) { + r = g = b = _g; + } else { + if (h === 360) { h = 0; } + if (h > 360) { h -= 360; } + if (h < 0) { h += 360; } + h /= 60; + var i = floor(h); + var f = h - i; + var p = _g * (1 - c); + var q = p + _c * (1 - f); + var t = p + _c * f; + var v = p + _c; + switch (i) { + case 0: (assign = [v, t, p], r = assign[0], g = assign[1], b = assign[2]); break + case 1: (assign$1 = [q, v, p], r = assign$1[0], g = assign$1[1], b = assign$1[2]); break + case 2: (assign$2 = [p, v, t], r = assign$2[0], g = assign$2[1], b = assign$2[2]); break + case 3: (assign$3 = [p, q, v], r = assign$3[0], g = assign$3[1], b = assign$3[2]); break + case 4: (assign$4 = [t, p, v], r = assign$4[0], g = assign$4[1], b = assign$4[2]); break + case 5: (assign$5 = [v, p, q], r = assign$5[0], g = assign$5[1], b = assign$5[2]); break + } + } + return [r, g, b, args.length > 3 ? args[3] : 1]; + }; + + var hcg2rgb_1 = hcg2rgb; + + var unpack$b = utils.unpack; + var type$4 = utils.type; + + + + + + + Color_1.prototype.hcg = function() { + return rgb2hcg_1(this._rgb); + }; + + chroma_1.hcg = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['hcg']) )); + }; + + input.format.hcg = hcg2rgb_1; + + input.autodetect.push({ + p: 1, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$b(args, 'hcg'); + if (type$4(args) === 'array' && args.length === 3) { + return 'hcg'; + } + } + }); + + var unpack$c = utils.unpack; + var last$4 = utils.last; + var round$3 = Math.round; + + var rgb2hex = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$c(args, 'rgba'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + var a = ref[3]; + var mode = last$4(args) || 'auto'; + if (a === undefined) { a = 1; } + if (mode === 'auto') { + mode = a < 1 ? 'rgba' : 'rgb'; + } + r = round$3(r); + g = round$3(g); + b = round$3(b); + var u = r << 16 | g << 8 | b; + var str = "000000" + u.toString(16); //#.toUpperCase(); + str = str.substr(str.length - 6); + var hxa = '0' + round$3(a * 255).toString(16); + hxa = hxa.substr(hxa.length - 2); + switch (mode.toLowerCase()) { + case 'rgba': return ("#" + str + hxa); + case 'argb': return ("#" + hxa + str); + default: return ("#" + str); + } + }; + + var rgb2hex_1 = rgb2hex; + + var RE_HEX = /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; + var RE_HEXA = /^#?([A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/; + + var hex2rgb = function (hex) { + if (hex.match(RE_HEX)) { + // remove optional leading # + if (hex.length === 4 || hex.length === 7) { + hex = hex.substr(1); + } + // expand short-notation to full six-digit + if (hex.length === 3) { + hex = hex.split(''); + hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; + } + var u = parseInt(hex, 16); + var r = u >> 16; + var g = u >> 8 & 0xFF; + var b = u & 0xFF; + return [r,g,b,1]; + } + + // match rgba hex format, eg #FF000077 + if (hex.match(RE_HEXA)) { + if (hex.length === 5 || hex.length === 9) { + // remove optional leading # + hex = hex.substr(1); + } + // expand short-notation to full eight-digit + if (hex.length === 4) { + hex = hex.split(''); + hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]+hex[3]+hex[3]; + } + var u$1 = parseInt(hex, 16); + var r$1 = u$1 >> 24 & 0xFF; + var g$1 = u$1 >> 16 & 0xFF; + var b$1 = u$1 >> 8 & 0xFF; + var a = Math.round((u$1 & 0xFF) / 0xFF * 100) / 100; + return [r$1,g$1,b$1,a]; + } + + // we used to check for css colors here + // if _input.css? and rgb = _input.css hex + // return rgb + + throw new Error(("unknown hex color: " + hex)); + }; + + var hex2rgb_1 = hex2rgb; + + var type$5 = utils.type; + + + + + Color_1.prototype.hex = function(mode) { + return rgb2hex_1(this._rgb, mode); + }; + + chroma_1.hex = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['hex']) )); + }; + + input.format.hex = hex2rgb_1; + input.autodetect.push({ + p: 4, + test: function (h) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + + if (!rest.length && type$5(h) === 'string' && [3,4,5,6,7,8,9].indexOf(h.length) >= 0) { + return 'hex'; + } + } + }); + + var unpack$d = utils.unpack; + var TWOPI = utils.TWOPI; + var min = Math.min; + var sqrt = Math.sqrt; + var acos = Math.acos; + + var rgb2hsi = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + /* + borrowed from here: + http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/rgb2hsi.cpp + */ + var ref = unpack$d(args, 'rgb'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + r /= 255; + g /= 255; + b /= 255; + var h; + var min_ = min(r,g,b); + var i = (r+g+b) / 3; + var s = i > 0 ? 1 - min_/i : 0; + if (s === 0) { + h = NaN; + } else { + h = ((r-g)+(r-b)) / 2; + h /= sqrt((r-g)*(r-g) + (r-b)*(g-b)); + h = acos(h); + if (b > g) { + h = TWOPI - h; + } + h /= TWOPI; + } + return [h*360,s,i]; + }; + + var rgb2hsi_1 = rgb2hsi; + + var unpack$e = utils.unpack; + var limit$1 = utils.limit; + var TWOPI$1 = utils.TWOPI; + var PITHIRD = utils.PITHIRD; + var cos = Math.cos; + + /* + * hue [0..360] + * saturation [0..1] + * intensity [0..1] + */ + var hsi2rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + /* + borrowed from here: + http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/hsi2rgb.cpp + */ + args = unpack$e(args, 'hsi'); + var h = args[0]; + var s = args[1]; + var i = args[2]; + var r,g,b; + + if (isNaN(h)) { h = 0; } + if (isNaN(s)) { s = 0; } + // normalize hue + if (h > 360) { h -= 360; } + if (h < 0) { h += 360; } + h /= 360; + if (h < 1/3) { + b = (1-s)/3; + r = (1+s*cos(TWOPI$1*h)/cos(PITHIRD-TWOPI$1*h))/3; + g = 1 - (b+r); + } else if (h < 2/3) { + h -= 1/3; + r = (1-s)/3; + g = (1+s*cos(TWOPI$1*h)/cos(PITHIRD-TWOPI$1*h))/3; + b = 1 - (r+g); + } else { + h -= 2/3; + g = (1-s)/3; + b = (1+s*cos(TWOPI$1*h)/cos(PITHIRD-TWOPI$1*h))/3; + r = 1 - (g+b); + } + r = limit$1(i*r*3); + g = limit$1(i*g*3); + b = limit$1(i*b*3); + return [r*255, g*255, b*255, args.length > 3 ? args[3] : 1]; + }; + + var hsi2rgb_1 = hsi2rgb; + + var unpack$f = utils.unpack; + var type$6 = utils.type; + + + + + + + Color_1.prototype.hsi = function() { + return rgb2hsi_1(this._rgb); + }; + + chroma_1.hsi = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['hsi']) )); + }; + + input.format.hsi = hsi2rgb_1; + + input.autodetect.push({ + p: 2, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$f(args, 'hsi'); + if (type$6(args) === 'array' && args.length === 3) { + return 'hsi'; + } + } + }); + + var unpack$g = utils.unpack; + var type$7 = utils.type; + + + + + + + Color_1.prototype.hsl = function() { + return rgb2hsl_1(this._rgb); + }; + + chroma_1.hsl = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['hsl']) )); + }; + + input.format.hsl = hsl2rgb_1; + + input.autodetect.push({ + p: 2, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$g(args, 'hsl'); + if (type$7(args) === 'array' && args.length === 3) { + return 'hsl'; + } + } + }); + + var unpack$h = utils.unpack; + var min$1 = Math.min; + var max$1 = Math.max; + + /* + * supported arguments: + * - rgb2hsv(r,g,b) + * - rgb2hsv([r,g,b]) + * - rgb2hsv({r,g,b}) + */ + var rgb2hsl$1 = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$h(args, 'rgb'); + var r = args[0]; + var g = args[1]; + var b = args[2]; + var min_ = min$1(r, g, b); + var max_ = max$1(r, g, b); + var delta = max_ - min_; + var h,s,v; + v = max_ / 255.0; + if (max_ === 0) { + h = Number.NaN; + s = 0; + } else { + s = delta / max_; + if (r === max_) { h = (g - b) / delta; } + if (g === max_) { h = 2+(b - r) / delta; } + if (b === max_) { h = 4+(r - g) / delta; } + h *= 60; + if (h < 0) { h += 360; } + } + return [h, s, v] + }; + + var rgb2hsv = rgb2hsl$1; + + var unpack$i = utils.unpack; + var floor$1 = Math.floor; + + var hsv2rgb = function () { + var assign, assign$1, assign$2, assign$3, assign$4, assign$5; + + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + args = unpack$i(args, 'hsv'); + var h = args[0]; + var s = args[1]; + var v = args[2]; + var r,g,b; + v *= 255; + if (s === 0) { + r = g = b = v; + } else { + if (h === 360) { h = 0; } + if (h > 360) { h -= 360; } + if (h < 0) { h += 360; } + h /= 60; + + var i = floor$1(h); + var f = h - i; + var p = v * (1 - s); + var q = v * (1 - s * f); + var t = v * (1 - s * (1 - f)); + + switch (i) { + case 0: (assign = [v, t, p], r = assign[0], g = assign[1], b = assign[2]); break + case 1: (assign$1 = [q, v, p], r = assign$1[0], g = assign$1[1], b = assign$1[2]); break + case 2: (assign$2 = [p, v, t], r = assign$2[0], g = assign$2[1], b = assign$2[2]); break + case 3: (assign$3 = [p, q, v], r = assign$3[0], g = assign$3[1], b = assign$3[2]); break + case 4: (assign$4 = [t, p, v], r = assign$4[0], g = assign$4[1], b = assign$4[2]); break + case 5: (assign$5 = [v, p, q], r = assign$5[0], g = assign$5[1], b = assign$5[2]); break + } + } + return [r,g,b,args.length > 3?args[3]:1]; + }; + + var hsv2rgb_1 = hsv2rgb; + + var unpack$j = utils.unpack; + var type$8 = utils.type; + + + + + + + Color_1.prototype.hsv = function() { + return rgb2hsv(this._rgb); + }; + + chroma_1.hsv = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['hsv']) )); + }; + + input.format.hsv = hsv2rgb_1; + + input.autodetect.push({ + p: 2, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$j(args, 'hsv'); + if (type$8(args) === 'array' && args.length === 3) { + return 'hsv'; + } + } + }); + + var labConstants = { + // Corresponds roughly to RGB brighter/darker + Kn: 18, + + // D65 standard referent + Xn: 0.950470, + Yn: 1, + Zn: 1.088830, + + t0: 0.137931034, // 4 / 29 + t1: 0.206896552, // 6 / 29 + t2: 0.12841855, // 3 * t1 * t1 + t3: 0.008856452, // t1 * t1 * t1 + }; + + var unpack$k = utils.unpack; + var pow = Math.pow; + + var rgb2lab = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$k(args, 'rgb'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + var ref$1 = rgb2xyz(r,g,b); + var x = ref$1[0]; + var y = ref$1[1]; + var z = ref$1[2]; + var l = 116 * y - 16; + return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z)]; + }; + + var rgb_xyz = function (r) { + if ((r /= 255) <= 0.04045) { return r / 12.92; } + return pow((r + 0.055) / 1.055, 2.4); + }; + + var xyz_lab = function (t) { + if (t > labConstants.t3) { return pow(t, 1 / 3); } + return t / labConstants.t2 + labConstants.t0; + }; + + var rgb2xyz = function (r,g,b) { + r = rgb_xyz(r); + g = rgb_xyz(g); + b = rgb_xyz(b); + var x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / labConstants.Xn); + var y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / labConstants.Yn); + var z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / labConstants.Zn); + return [x,y,z]; + }; + + var rgb2lab_1 = rgb2lab; + + var unpack$l = utils.unpack; + var pow$1 = Math.pow; + + /* + * L* [0..100] + * a [-100..100] + * b [-100..100] + */ + var lab2rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$l(args, 'lab'); + var l = args[0]; + var a = args[1]; + var b = args[2]; + var x,y,z, r,g,b_; + + y = (l + 16) / 116; + x = isNaN(a) ? y : y + a / 500; + z = isNaN(b) ? y : y - b / 200; + + y = labConstants.Yn * lab_xyz(y); + x = labConstants.Xn * lab_xyz(x); + z = labConstants.Zn * lab_xyz(z); + + r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB + g = xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z); + b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z); + + return [r,g,b_,args.length > 3 ? args[3] : 1]; + }; + + var xyz_rgb = function (r) { + return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * pow$1(r, 1 / 2.4) - 0.055) + }; + + var lab_xyz = function (t) { + return t > labConstants.t1 ? t * t * t : labConstants.t2 * (t - labConstants.t0) + }; + + var lab2rgb_1 = lab2rgb; + + var unpack$m = utils.unpack; + var type$9 = utils.type; + + + + + + + Color_1.prototype.lab = function() { + return rgb2lab_1(this._rgb); + }; + + chroma_1.lab = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['lab']) )); + }; + + input.format.lab = lab2rgb_1; + + input.autodetect.push({ + p: 2, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$m(args, 'lab'); + if (type$9(args) === 'array' && args.length === 3) { + return 'lab'; + } + } + }); + + var unpack$n = utils.unpack; + var RAD2DEG = utils.RAD2DEG; + var sqrt$1 = Math.sqrt; + var atan2 = Math.atan2; + var round$4 = Math.round; + + var lab2lch = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$n(args, 'lab'); + var l = ref[0]; + var a = ref[1]; + var b = ref[2]; + var c = sqrt$1(a * a + b * b); + var h = (atan2(b, a) * RAD2DEG + 360) % 360; + if (round$4(c*10000) === 0) { h = Number.NaN; } + return [l, c, h]; + }; + + var lab2lch_1 = lab2lch; + + var unpack$o = utils.unpack; + + + + var rgb2lch = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$o(args, 'rgb'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + var ref$1 = rgb2lab_1(r,g,b); + var l = ref$1[0]; + var a = ref$1[1]; + var b_ = ref$1[2]; + return lab2lch_1(l,a,b_); + }; + + var rgb2lch_1 = rgb2lch; + + var unpack$p = utils.unpack; + var DEG2RAD = utils.DEG2RAD; + var sin = Math.sin; + var cos$1 = Math.cos; + + var lch2lab = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + /* + Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel. + These formulas were invented by David Dalrymple to obtain maximum contrast without going + out of gamut if the parameters are in the range 0-1. + + A saturation multiplier was added by Gregor Aisch + */ + var ref = unpack$p(args, 'lch'); + var l = ref[0]; + var c = ref[1]; + var h = ref[2]; + if (isNaN(h)) { h = 0; } + h = h * DEG2RAD; + return [l, cos$1(h) * c, sin(h) * c] + }; + + var lch2lab_1 = lch2lab; + + var unpack$q = utils.unpack; + + + + var lch2rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$q(args, 'lch'); + var l = args[0]; + var c = args[1]; + var h = args[2]; + var ref = lch2lab_1 (l,c,h); + var L = ref[0]; + var a = ref[1]; + var b_ = ref[2]; + var ref$1 = lab2rgb_1 (L,a,b_); + var r = ref$1[0]; + var g = ref$1[1]; + var b = ref$1[2]; + return [r, g, b, args.length > 3 ? args[3] : 1]; + }; + + var lch2rgb_1 = lch2rgb; + + var unpack$r = utils.unpack; + + + var hcl2rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var hcl = unpack$r(args, 'hcl').reverse(); + return lch2rgb_1.apply(void 0, hcl); + }; + + var hcl2rgb_1 = hcl2rgb; + + var unpack$s = utils.unpack; + var type$a = utils.type; + + + + + + + Color_1.prototype.lch = function() { return rgb2lch_1(this._rgb); }; + Color_1.prototype.hcl = function() { return rgb2lch_1(this._rgb).reverse(); }; + + chroma_1.lch = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['lch']) )); + }; + chroma_1.hcl = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['hcl']) )); + }; + + input.format.lch = lch2rgb_1; + input.format.hcl = hcl2rgb_1; + + ['lch','hcl'].forEach(function (m) { return input.autodetect.push({ + p: 2, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$s(args, m); + if (type$a(args) === 'array' && args.length === 3) { + return m; + } + } + }); }); + + /** + X11 color names + + http://www.w3.org/TR/css3-color/#svg-color + */ + + var w3cx11 = { + aliceblue: '#f0f8ff', + antiquewhite: '#faebd7', + aqua: '#00ffff', + aquamarine: '#7fffd4', + azure: '#f0ffff', + beige: '#f5f5dc', + bisque: '#ffe4c4', + black: '#000000', + blanchedalmond: '#ffebcd', + blue: '#0000ff', + blueviolet: '#8a2be2', + brown: '#a52a2a', + burlywood: '#deb887', + cadetblue: '#5f9ea0', + chartreuse: '#7fff00', + chocolate: '#d2691e', + coral: '#ff7f50', + cornflower: '#6495ed', + cornflowerblue: '#6495ed', + cornsilk: '#fff8dc', + crimson: '#dc143c', + cyan: '#00ffff', + darkblue: '#00008b', + darkcyan: '#008b8b', + darkgoldenrod: '#b8860b', + darkgray: '#a9a9a9', + darkgreen: '#006400', + darkgrey: '#a9a9a9', + darkkhaki: '#bdb76b', + darkmagenta: '#8b008b', + darkolivegreen: '#556b2f', + darkorange: '#ff8c00', + darkorchid: '#9932cc', + darkred: '#8b0000', + darksalmon: '#e9967a', + darkseagreen: '#8fbc8f', + darkslateblue: '#483d8b', + darkslategray: '#2f4f4f', + darkslategrey: '#2f4f4f', + darkturquoise: '#00ced1', + darkviolet: '#9400d3', + deeppink: '#ff1493', + deepskyblue: '#00bfff', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1e90ff', + firebrick: '#b22222', + floralwhite: '#fffaf0', + forestgreen: '#228b22', + fuchsia: '#ff00ff', + gainsboro: '#dcdcdc', + ghostwhite: '#f8f8ff', + gold: '#ffd700', + goldenrod: '#daa520', + gray: '#808080', + green: '#008000', + greenyellow: '#adff2f', + grey: '#808080', + honeydew: '#f0fff0', + hotpink: '#ff69b4', + indianred: '#cd5c5c', + indigo: '#4b0082', + ivory: '#fffff0', + khaki: '#f0e68c', + laserlemon: '#ffff54', + lavender: '#e6e6fa', + lavenderblush: '#fff0f5', + lawngreen: '#7cfc00', + lemonchiffon: '#fffacd', + lightblue: '#add8e6', + lightcoral: '#f08080', + lightcyan: '#e0ffff', + lightgoldenrod: '#fafad2', + lightgoldenrodyellow: '#fafad2', + lightgray: '#d3d3d3', + lightgreen: '#90ee90', + lightgrey: '#d3d3d3', + lightpink: '#ffb6c1', + lightsalmon: '#ffa07a', + lightseagreen: '#20b2aa', + lightskyblue: '#87cefa', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#b0c4de', + lightyellow: '#ffffe0', + lime: '#00ff00', + limegreen: '#32cd32', + linen: '#faf0e6', + magenta: '#ff00ff', + maroon: '#800000', + maroon2: '#7f0000', + maroon3: '#b03060', + mediumaquamarine: '#66cdaa', + mediumblue: '#0000cd', + mediumorchid: '#ba55d3', + mediumpurple: '#9370db', + mediumseagreen: '#3cb371', + mediumslateblue: '#7b68ee', + mediumspringgreen: '#00fa9a', + mediumturquoise: '#48d1cc', + mediumvioletred: '#c71585', + midnightblue: '#191970', + mintcream: '#f5fffa', + mistyrose: '#ffe4e1', + moccasin: '#ffe4b5', + navajowhite: '#ffdead', + navy: '#000080', + oldlace: '#fdf5e6', + olive: '#808000', + olivedrab: '#6b8e23', + orange: '#ffa500', + orangered: '#ff4500', + orchid: '#da70d6', + palegoldenrod: '#eee8aa', + palegreen: '#98fb98', + paleturquoise: '#afeeee', + palevioletred: '#db7093', + papayawhip: '#ffefd5', + peachpuff: '#ffdab9', + peru: '#cd853f', + pink: '#ffc0cb', + plum: '#dda0dd', + powderblue: '#b0e0e6', + purple: '#800080', + purple2: '#7f007f', + purple3: '#a020f0', + rebeccapurple: '#663399', + red: '#ff0000', + rosybrown: '#bc8f8f', + royalblue: '#4169e1', + saddlebrown: '#8b4513', + salmon: '#fa8072', + sandybrown: '#f4a460', + seagreen: '#2e8b57', + seashell: '#fff5ee', + sienna: '#a0522d', + silver: '#c0c0c0', + skyblue: '#87ceeb', + slateblue: '#6a5acd', + slategray: '#708090', + slategrey: '#708090', + snow: '#fffafa', + springgreen: '#00ff7f', + steelblue: '#4682b4', + tan: '#d2b48c', + teal: '#008080', + thistle: '#d8bfd8', + tomato: '#ff6347', + turquoise: '#40e0d0', + violet: '#ee82ee', + wheat: '#f5deb3', + white: '#ffffff', + whitesmoke: '#f5f5f5', + yellow: '#ffff00', + yellowgreen: '#9acd32' + }; + + var w3cx11_1 = w3cx11; + + var type$b = utils.type; + + + + + + Color_1.prototype.name = function() { + var hex = rgb2hex_1(this._rgb, 'rgb'); + for (var i = 0, list = Object.keys(w3cx11_1); i < list.length; i += 1) { + var n = list[i]; + + if (w3cx11_1[n] === hex) { return n.toLowerCase(); } + } + return hex; + }; + + input.format.named = function (name) { + name = name.toLowerCase(); + if (w3cx11_1[name]) { return hex2rgb_1(w3cx11_1[name]); } + throw new Error('unknown color name: '+name); + }; + + input.autodetect.push({ + p: 5, + test: function (h) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + + if (!rest.length && type$b(h) === 'string' && w3cx11_1[h.toLowerCase()]) { + return 'named'; + } + } + }); + + var unpack$t = utils.unpack; + + var rgb2num = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var ref = unpack$t(args, 'rgb'); + var r = ref[0]; + var g = ref[1]; + var b = ref[2]; + return (r << 16) + (g << 8) + b; + }; + + var rgb2num_1 = rgb2num; + + var type$c = utils.type; + + var num2rgb = function (num) { + if (type$c(num) == "number" && num >= 0 && num <= 0xFFFFFF) { + var r = num >> 16; + var g = (num >> 8) & 0xFF; + var b = num & 0xFF; + return [r,g,b,1]; + } + throw new Error("unknown num color: "+num); + }; + + var num2rgb_1 = num2rgb; + + var type$d = utils.type; + + + + Color_1.prototype.num = function() { + return rgb2num_1(this._rgb); + }; + + chroma_1.num = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['num']) )); + }; + + input.format.num = num2rgb_1; + + input.autodetect.push({ + p: 5, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + if (args.length === 1 && type$d(args[0]) === 'number' && args[0] >= 0 && args[0] <= 0xFFFFFF) { + return 'num'; + } + } + }); + + var unpack$u = utils.unpack; + var type$e = utils.type; + var round$5 = Math.round; + + Color_1.prototype.rgb = function(rnd) { + if ( rnd === void 0 ) rnd=true; + + if (rnd === false) { return this._rgb.slice(0,3); } + return this._rgb.slice(0,3).map(round$5); + }; + + Color_1.prototype.rgba = function(rnd) { + if ( rnd === void 0 ) rnd=true; + + return this._rgb.slice(0,4).map(function (v,i) { + return i<3 ? (rnd === false ? v : round$5(v)) : v; + }); + }; + + chroma_1.rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['rgb']) )); + }; + + input.format.rgb = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var rgba = unpack$u(args, 'rgba'); + if (rgba[3] === undefined) { rgba[3] = 1; } + return rgba; + }; + + input.autodetect.push({ + p: 3, + test: function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + args = unpack$u(args, 'rgba'); + if (type$e(args) === 'array' && (args.length === 3 || + args.length === 4 && type$e(args[3]) == 'number' && args[3] >= 0 && args[3] <= 1)) { + return 'rgb'; + } + } + }); + + /* + * Based on implementation by Neil Bartlett + * https://github.com/neilbartlett/color-temperature + */ + + var log = Math.log; + + var temperature2rgb = function (kelvin) { + var temp = kelvin / 100; + var r,g,b; + if (temp < 66) { + r = 255; + g = -155.25485562709179 - 0.44596950469579133 * (g = temp-2) + 104.49216199393888 * log(g); + b = temp < 20 ? 0 : -254.76935184120902 + 0.8274096064007395 * (b = temp-10) + 115.67994401066147 * log(b); + } else { + r = 351.97690566805693 + 0.114206453784165 * (r = temp-55) - 40.25366309332127 * log(r); + g = 325.4494125711974 + 0.07943456536662342 * (g = temp-50) - 28.0852963507957 * log(g); + b = 255; + } + return [r,g,b,1]; + }; + + var temperature2rgb_1 = temperature2rgb; + + /* + * Based on implementation by Neil Bartlett + * https://github.com/neilbartlett/color-temperature + **/ + + + var unpack$v = utils.unpack; + var round$6 = Math.round; + + var rgb2temperature = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var rgb = unpack$v(args, 'rgb'); + var r = rgb[0], b = rgb[2]; + var minTemp = 1000; + var maxTemp = 40000; + var eps = 0.4; + var temp; + while (maxTemp - minTemp > eps) { + temp = (maxTemp + minTemp) * 0.5; + var rgb$1 = temperature2rgb_1(temp); + if ((rgb$1[2] / rgb$1[0]) >= (b / r)) { + maxTemp = temp; + } else { + minTemp = temp; + } + } + return round$6(temp); + }; + + var rgb2temperature_1 = rgb2temperature; + + Color_1.prototype.temp = + Color_1.prototype.kelvin = + Color_1.prototype.temperature = function() { + return rgb2temperature_1(this._rgb); + }; + + chroma_1.temp = + chroma_1.kelvin = + chroma_1.temperature = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return new (Function.prototype.bind.apply( Color_1, [ null ].concat( args, ['temp']) )); + }; + + input.format.temp = + input.format.kelvin = + input.format.temperature = temperature2rgb_1; + + var type$f = utils.type; + + Color_1.prototype.alpha = function(a, mutate) { + if ( mutate === void 0 ) mutate=false; + + if (a !== undefined && type$f(a) === 'number') { + if (mutate) { + this._rgb[3] = a; + return this; + } + return new Color_1([this._rgb[0], this._rgb[1], this._rgb[2], a], 'rgb'); + } + return this._rgb[3]; + }; + + Color_1.prototype.clipped = function() { + return this._rgb._clipped || false; + }; + + Color_1.prototype.darken = function(amount) { + if ( amount === void 0 ) amount=1; + + var me = this; + var lab = me.lab(); + lab[0] -= labConstants.Kn * amount; + return new Color_1(lab, 'lab').alpha(me.alpha(), true); + }; + + Color_1.prototype.brighten = function(amount) { + if ( amount === void 0 ) amount=1; + + return this.darken(-amount); + }; + + Color_1.prototype.darker = Color_1.prototype.darken; + Color_1.prototype.brighter = Color_1.prototype.brighten; + + Color_1.prototype.get = function(mc) { + var ref = mc.split('.'); + var mode = ref[0]; + var channel = ref[1]; + var src = this[mode](); + if (channel) { + var i = mode.indexOf(channel); + if (i > -1) { return src[i]; } + throw new Error(("unknown channel " + channel + " in mode " + mode)); + } else { + return src; + } + }; + + var type$g = utils.type; + var pow$2 = Math.pow; + + var EPS = 1e-7; + var MAX_ITER = 20; + + Color_1.prototype.luminance = function(lum) { + if (lum !== undefined && type$g(lum) === 'number') { + if (lum === 0) { + // return pure black + return new Color_1([0,0,0,this._rgb[3]], 'rgb'); + } + if (lum === 1) { + // return pure white + return new Color_1([255,255,255,this._rgb[3]], 'rgb'); + } + // compute new color using... + var cur_lum = this.luminance(); + var mode = 'rgb'; + var max_iter = MAX_ITER; + + var test = function (low, high) { + var mid = low.interpolate(high, 0.5, mode); + var lm = mid.luminance(); + if (Math.abs(lum - lm) < EPS || !max_iter--) { + // close enough + return mid; + } + return lm > lum ? test(low, mid) : test(mid, high); + }; + + var rgb = (cur_lum > lum ? test(new Color_1([0,0,0]), this) : test(this, new Color_1([255,255,255]))).rgb(); + return new Color_1(rgb.concat( [this._rgb[3]])); + } + return rgb2luminance.apply(void 0, (this._rgb).slice(0,3)); + }; + + + var rgb2luminance = function (r,g,b) { + // relative luminance + // see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + r = luminance_x(r); + g = luminance_x(g); + b = luminance_x(b); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; + }; + + var luminance_x = function (x) { + x /= 255; + return x <= 0.03928 ? x/12.92 : pow$2((x+0.055)/1.055, 2.4); + }; + + var interpolator = {}; + + var type$h = utils.type; + + + var mix = function (col1, col2, f) { + if ( f === void 0 ) f=0.5; + var rest = [], len = arguments.length - 3; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 3 ]; + + var mode = rest[0] || 'lrgb'; + if (!interpolator[mode] && !rest.length) { + // fall back to the first supported mode + mode = Object.keys(interpolator)[0]; + } + if (!interpolator[mode]) { + throw new Error(("interpolation mode " + mode + " is not defined")); + } + if (type$h(col1) !== 'object') { col1 = new Color_1(col1); } + if (type$h(col2) !== 'object') { col2 = new Color_1(col2); } + return interpolator[mode](col1, col2, f) + .alpha(col1.alpha() + f * (col2.alpha() - col1.alpha())); + }; + + Color_1.prototype.mix = + Color_1.prototype.interpolate = function(col2, f) { + if ( f === void 0 ) f=0.5; + var rest = [], len = arguments.length - 2; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 2 ]; + + return mix.apply(void 0, [ this, col2, f ].concat( rest )); + }; + + Color_1.prototype.premultiply = function(mutate) { + if ( mutate === void 0 ) mutate=false; + + var rgb = this._rgb; + var a = rgb[3]; + if (mutate) { + this._rgb = [rgb[0]*a, rgb[1]*a, rgb[2]*a, a]; + return this; + } else { + return new Color_1([rgb[0]*a, rgb[1]*a, rgb[2]*a, a], 'rgb'); + } + }; + + Color_1.prototype.saturate = function(amount) { + if ( amount === void 0 ) amount=1; + + var me = this; + var lch = me.lch(); + lch[1] += labConstants.Kn * amount; + if (lch[1] < 0) { lch[1] = 0; } + return new Color_1(lch, 'lch').alpha(me.alpha(), true); + }; + + Color_1.prototype.desaturate = function(amount) { + if ( amount === void 0 ) amount=1; + + return this.saturate(-amount); + }; + + var type$i = utils.type; + + Color_1.prototype.set = function(mc, value, mutate) { + if ( mutate === void 0 ) mutate=false; + + var ref = mc.split('.'); + var mode = ref[0]; + var channel = ref[1]; + var src = this[mode](); + if (channel) { + var i = mode.indexOf(channel); + if (i > -1) { + if (type$i(value) == 'string') { + switch(value.charAt(0)) { + case '+': src[i] += +value; break; + case '-': src[i] += +value; break; + case '*': src[i] *= +(value.substr(1)); break; + case '/': src[i] /= +(value.substr(1)); break; + default: src[i] = +value; + } + } else if (type$i(value) === 'number') { + src[i] = value; + } else { + throw new Error("unsupported value for Color.set"); + } + var out = new Color_1(src, mode); + if (mutate) { + this._rgb = out._rgb; + return this; + } + return out; + } + throw new Error(("unknown channel " + channel + " in mode " + mode)); + } else { + return src; + } + }; + + var rgb$1 = function (col1, col2, f) { + var xyz0 = col1._rgb; + var xyz1 = col2._rgb; + return new Color_1( + xyz0[0] + f * (xyz1[0]-xyz0[0]), + xyz0[1] + f * (xyz1[1]-xyz0[1]), + xyz0[2] + f * (xyz1[2]-xyz0[2]), + 'rgb' + ) + }; + + // register interpolator + interpolator.rgb = rgb$1; + + var sqrt$2 = Math.sqrt; + var pow$3 = Math.pow; + + var lrgb = function (col1, col2, f) { + var ref = col1._rgb; + var x1 = ref[0]; + var y1 = ref[1]; + var z1 = ref[2]; + var ref$1 = col2._rgb; + var x2 = ref$1[0]; + var y2 = ref$1[1]; + var z2 = ref$1[2]; + return new Color_1( + sqrt$2(pow$3(x1,2) * (1-f) + pow$3(x2,2) * f), + sqrt$2(pow$3(y1,2) * (1-f) + pow$3(y2,2) * f), + sqrt$2(pow$3(z1,2) * (1-f) + pow$3(z2,2) * f), + 'rgb' + ) + }; + + // register interpolator + interpolator.lrgb = lrgb; + + var lab$1 = function (col1, col2, f) { + var xyz0 = col1.lab(); + var xyz1 = col2.lab(); + return new Color_1( + xyz0[0] + f * (xyz1[0]-xyz0[0]), + xyz0[1] + f * (xyz1[1]-xyz0[1]), + xyz0[2] + f * (xyz1[2]-xyz0[2]), + 'lab' + ) + }; + + // register interpolator + interpolator.lab = lab$1; + + var _hsx = function (col1, col2, f, m) { + var assign, assign$1; + + var xyz0, xyz1; + if (m === 'hsl') { + xyz0 = col1.hsl(); + xyz1 = col2.hsl(); + } else if (m === 'hsv') { + xyz0 = col1.hsv(); + xyz1 = col2.hsv(); + } else if (m === 'hcg') { + xyz0 = col1.hcg(); + xyz1 = col2.hcg(); + } else if (m === 'hsi') { + xyz0 = col1.hsi(); + xyz1 = col2.hsi(); + } else if (m === 'lch' || m === 'hcl') { + m = 'hcl'; + xyz0 = col1.hcl(); + xyz1 = col2.hcl(); + } + + var hue0, hue1, sat0, sat1, lbv0, lbv1; + if (m.substr(0, 1) === 'h') { + (assign = xyz0, hue0 = assign[0], sat0 = assign[1], lbv0 = assign[2]); + (assign$1 = xyz1, hue1 = assign$1[0], sat1 = assign$1[1], lbv1 = assign$1[2]); + } + + var sat, hue, lbv, dh; + + if (!isNaN(hue0) && !isNaN(hue1)) { + // both colors have hue + if (hue1 > hue0 && hue1 - hue0 > 180) { + dh = hue1-(hue0+360); + } else if (hue1 < hue0 && hue0 - hue1 > 180) { + dh = hue1+360-hue0; + } else{ + dh = hue1 - hue0; + } + hue = hue0 + f * dh; + } else if (!isNaN(hue0)) { + hue = hue0; + if ((lbv1 == 1 || lbv1 == 0) && m != 'hsv') { sat = sat0; } + } else if (!isNaN(hue1)) { + hue = hue1; + if ((lbv0 == 1 || lbv0 == 0) && m != 'hsv') { sat = sat1; } + } else { + hue = Number.NaN; + } + + if (sat === undefined) { sat = sat0 + f * (sat1 - sat0); } + lbv = lbv0 + f * (lbv1-lbv0); + return new Color_1([hue, sat, lbv], m); + }; + + var lch$1 = function (col1, col2, f) { + return _hsx(col1, col2, f, 'lch'); + }; + + // register interpolator + interpolator.lch = lch$1; + interpolator.hcl = lch$1; + + var num$1 = function (col1, col2, f) { + var c1 = col1.num(); + var c2 = col2.num(); + return new Color_1(c1 + f * (c2-c1), 'num') + }; + + // register interpolator + interpolator.num = num$1; + + var hcg$1 = function (col1, col2, f) { + return _hsx(col1, col2, f, 'hcg'); + }; + + // register interpolator + interpolator.hcg = hcg$1; + + var hsi$1 = function (col1, col2, f) { + return _hsx(col1, col2, f, 'hsi'); + }; + + // register interpolator + interpolator.hsi = hsi$1; + + var hsl$1 = function (col1, col2, f) { + return _hsx(col1, col2, f, 'hsl'); + }; + + // register interpolator + interpolator.hsl = hsl$1; + + var hsv$1 = function (col1, col2, f) { + return _hsx(col1, col2, f, 'hsv'); + }; + + // register interpolator + interpolator.hsv = hsv$1; + + var clip_rgb$2 = utils.clip_rgb; + var pow$4 = Math.pow; + var sqrt$3 = Math.sqrt; + var PI$1 = Math.PI; + var cos$2 = Math.cos; + var sin$1 = Math.sin; + var atan2$1 = Math.atan2; + + var average = function (colors, mode, weights) { + if ( mode === void 0 ) mode='lrgb'; + if ( weights === void 0 ) weights=null; + + var l = colors.length; + if (!weights) { weights = Array.from(new Array(l)).map(function () { return 1; }); } + // normalize weights + var k = l / weights.reduce(function(a, b) { return a + b; }); + weights.forEach(function (w,i) { weights[i] *= k; }); + // convert colors to Color objects + colors = colors.map(function (c) { return new Color_1(c); }); + if (mode === 'lrgb') { + return _average_lrgb(colors, weights) + } + var first = colors.shift(); + var xyz = first.get(mode); + var cnt = []; + var dx = 0; + var dy = 0; + // initial color + for (var i=0; i= 360) { A$1 -= 360; } + xyz[i$1] = A$1; + } else { + xyz[i$1] = xyz[i$1]/cnt[i$1]; + } + } + alpha /= l; + return (new Color_1(xyz, mode)).alpha(alpha > 0.99999 ? 1 : alpha, true); + }; + + + var _average_lrgb = function (colors, weights) { + var l = colors.length; + var xyz = [0,0,0,0]; + for (var i=0; i < colors.length; i++) { + var col = colors[i]; + var f = weights[i] / l; + var rgb = col._rgb; + xyz[0] += pow$4(rgb[0],2) * f; + xyz[1] += pow$4(rgb[1],2) * f; + xyz[2] += pow$4(rgb[2],2) * f; + xyz[3] += rgb[3] * f; + } + xyz[0] = sqrt$3(xyz[0]); + xyz[1] = sqrt$3(xyz[1]); + xyz[2] = sqrt$3(xyz[2]); + if (xyz[3] > 0.9999999) { xyz[3] = 1; } + return new Color_1(clip_rgb$2(xyz)); + }; + + // minimal multi-purpose interface + + // @requires utils color analyze + + + var type$j = utils.type; + + var pow$5 = Math.pow; + + var scale = function(colors) { + + // constructor + var _mode = 'rgb'; + var _nacol = chroma_1('#ccc'); + var _spread = 0; + // const _fixed = false; + var _domain = [0, 1]; + var _pos = []; + var _padding = [0,0]; + var _classes = false; + var _colors = []; + var _out = false; + var _min = 0; + var _max = 1; + var _correctLightness = false; + var _colorCache = {}; + var _useCache = true; + var _gamma = 1; + + // private methods + + var setColors = function(colors) { + colors = colors || ['#fff', '#000']; + if (colors && type$j(colors) === 'string' && chroma_1.brewer && + chroma_1.brewer[colors.toLowerCase()]) { + colors = chroma_1.brewer[colors.toLowerCase()]; + } + if (type$j(colors) === 'array') { + // handle single color + if (colors.length === 1) { + colors = [colors[0], colors[0]]; + } + // make a copy of the colors + colors = colors.slice(0); + // convert to chroma classes + for (var c=0; c= _classes[i]) { + i++; + } + return i-1; + } + return 0; + }; + + var tMapLightness = function (t) { return t; }; + var tMapDomain = function (t) { return t; }; + + // const classifyValue = function(value) { + // let val = value; + // if (_classes.length > 2) { + // const n = _classes.length-1; + // const i = getClass(value); + // const minc = _classes[0] + ((_classes[1]-_classes[0]) * (0 + (_spread * 0.5))); // center of 1st class + // const maxc = _classes[n-1] + ((_classes[n]-_classes[n-1]) * (1 - (_spread * 0.5))); // center of last class + // val = _min + ((((_classes[i] + ((_classes[i+1] - _classes[i]) * 0.5)) - minc) / (maxc-minc)) * (_max - _min)); + // } + // return val; + // }; + + var getColor = function(val, bypassMap) { + var col, t; + if (bypassMap == null) { bypassMap = false; } + if (isNaN(val) || (val === null)) { return _nacol; } + if (!bypassMap) { + if (_classes && (_classes.length > 2)) { + // find the class + var c = getClass(val); + t = c / (_classes.length-2); + } else if (_max !== _min) { + // just interpolate between min/max + t = (val - _min) / (_max - _min); + } else { + t = 1; + } + } else { + t = val; + } + + // domain map + t = tMapDomain(t); + + if (!bypassMap) { + t = tMapLightness(t); // lightness correction + } + + if (_gamma !== 1) { t = pow$5(t, _gamma); } + + t = _padding[0] + (t * (1 - _padding[0] - _padding[1])); + + t = Math.min(1, Math.max(0, t)); + + var k = Math.floor(t * 10000); + + if (_useCache && _colorCache[k]) { + col = _colorCache[k]; + } else { + if (type$j(_colors) === 'array') { + //for i in [0.._pos.length-1] + for (var i=0; i<_pos.length; i++) { + var p = _pos[i]; + if (t <= p) { + col = _colors[i]; + break; + } + if ((t >= p) && (i === (_pos.length-1))) { + col = _colors[i]; + break; + } + if (t > p && t < _pos[i+1]) { + t = (t-p)/(_pos[i+1]-p); + col = chroma_1.interpolate(_colors[i], _colors[i+1], t, _mode); + break; + } + } + } else if (type$j(_colors) === 'function') { + col = _colors(t); + } + if (_useCache) { _colorCache[k] = col; } + } + return col; + }; + + var resetCache = function () { return _colorCache = {}; }; + + setColors(colors); + + // public interface + + var f = function(v) { + var c = chroma_1(getColor(v)); + if (_out && c[_out]) { return c[_out](); } else { return c; } + }; + + f.classes = function(classes) { + if (classes != null) { + if (type$j(classes) === 'array') { + _classes = classes; + _domain = [classes[0], classes[classes.length-1]]; + } else { + var d = chroma_1.analyze(_domain); + if (classes === 0) { + _classes = [d.min, d.max]; + } else { + _classes = chroma_1.limits(d, 'e', classes); + } + } + return f; + } + return _classes; + }; + + + f.domain = function(domain) { + if (!arguments.length) { + return _domain; + } + _min = domain[0]; + _max = domain[domain.length-1]; + _pos = []; + var k = _colors.length; + if ((domain.length === k) && (_min !== _max)) { + // update positions + for (var i = 0, list = Array.from(domain); i < list.length; i += 1) { + var d = list[i]; + + _pos.push((d-_min) / (_max-_min)); + } + } else { + for (var c=0; c 2) { + // set domain map + var tOut = domain.map(function (d,i) { return i/(domain.length-1); }); + var tBreaks = domain.map(function (d) { return (d - _min) / (_max - _min); }); + if (!tBreaks.every(function (val, i) { return tOut[i] === val; })) { + tMapDomain = function (t) { + if (t <= 0 || t >= 1) { return t; } + var i = 0; + while (t >= tBreaks[i+1]) { i++; } + var f = (t - tBreaks[i]) / (tBreaks[i+1] - tBreaks[i]); + var out = tOut[i] + f * (tOut[i+1] - tOut[i]); + return out; + }; + } + + } + } + _domain = [_min, _max]; + return f; + }; + + f.mode = function(_m) { + if (!arguments.length) { + return _mode; + } + _mode = _m; + resetCache(); + return f; + }; + + f.range = function(colors, _pos) { + setColors(colors, _pos); + return f; + }; + + f.out = function(_o) { + _out = _o; + return f; + }; + + f.spread = function(val) { + if (!arguments.length) { + return _spread; + } + _spread = val; + return f; + }; + + f.correctLightness = function(v) { + if (v == null) { v = true; } + _correctLightness = v; + resetCache(); + if (_correctLightness) { + tMapLightness = function(t) { + var L0 = getColor(0, true).lab()[0]; + var L1 = getColor(1, true).lab()[0]; + var pol = L0 > L1; + var L_actual = getColor(t, true).lab()[0]; + var L_ideal = L0 + ((L1 - L0) * t); + var L_diff = L_actual - L_ideal; + var t0 = 0; + var t1 = 1; + var max_iter = 20; + while ((Math.abs(L_diff) > 1e-2) && (max_iter-- > 0)) { + (function() { + if (pol) { L_diff *= -1; } + if (L_diff < 0) { + t0 = t; + t += (t1 - t) * 0.5; + } else { + t1 = t; + t += (t0 - t) * 0.5; + } + L_actual = getColor(t, true).lab()[0]; + return L_diff = L_actual - L_ideal; + })(); + } + return t; + }; + } else { + tMapLightness = function (t) { return t; }; + } + return f; + }; + + f.padding = function(p) { + if (p != null) { + if (type$j(p) === 'number') { + p = [p,p]; + } + _padding = p; + return f; + } else { + return _padding; + } + }; + + f.colors = function(numColors, out) { + // If no arguments are given, return the original colors that were provided + if (arguments.length < 2) { out = 'hex'; } + var result = []; + + if (arguments.length === 0) { + result = _colors.slice(0); + + } else if (numColors === 1) { + result = [f(0.5)]; + + } else if (numColors > 1) { + var dm = _domain[0]; + var dd = _domain[1] - dm; + result = __range__(0, numColors, false).map(function (i) { return f( dm + ((i/(numColors-1)) * dd) ); }); + + } else { // returns all colors based on the defined classes + colors = []; + var samples = []; + if (_classes && (_classes.length > 2)) { + for (var i = 1, end = _classes.length, asc = 1 <= end; asc ? i < end : i > end; asc ? i++ : i--) { + samples.push((_classes[i-1]+_classes[i])*0.5); + } + } else { + samples = _domain; + } + result = samples.map(function (v) { return f(v); }); + } + + if (chroma_1[out]) { + result = result.map(function (c) { return c[out](); }); + } + return result; + }; + + f.cache = function(c) { + if (c != null) { + _useCache = c; + return f; + } else { + return _useCache; + } + }; + + f.gamma = function(g) { + if (g != null) { + _gamma = g; + return f; + } else { + return _gamma; + } + }; + + f.nodata = function(d) { + if (d != null) { + _nacol = chroma_1(d); + return f; + } else { + return _nacol; + } + }; + + return f; + }; + + function __range__(left, right, inclusive) { + var range = []; + var ascending = left < right; + var end = !inclusive ? right : ascending ? right + 1 : right - 1; + for (var i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i); + } + return range; + } + + // + // interpolates between a set of colors uzing a bezier spline + // + + // @requires utils lab + + + + + var bezier = function(colors) { + var assign, assign$1, assign$2; + + var I, lab0, lab1, lab2; + colors = colors.map(function (c) { return new Color_1(c); }); + if (colors.length === 2) { + // linear interpolation + (assign = colors.map(function (c) { return c.lab(); }), lab0 = assign[0], lab1 = assign[1]); + I = function(t) { + var lab = ([0, 1, 2].map(function (i) { return lab0[i] + (t * (lab1[i] - lab0[i])); })); + return new Color_1(lab, 'lab'); + }; + } else if (colors.length === 3) { + // quadratic bezier interpolation + (assign$1 = colors.map(function (c) { return c.lab(); }), lab0 = assign$1[0], lab1 = assign$1[1], lab2 = assign$1[2]); + I = function(t) { + var lab = ([0, 1, 2].map(function (i) { return ((1-t)*(1-t) * lab0[i]) + (2 * (1-t) * t * lab1[i]) + (t * t * lab2[i]); })); + return new Color_1(lab, 'lab'); + }; + } else if (colors.length === 4) { + // cubic bezier interpolation + var lab3; + (assign$2 = colors.map(function (c) { return c.lab(); }), lab0 = assign$2[0], lab1 = assign$2[1], lab2 = assign$2[2], lab3 = assign$2[3]); + I = function(t) { + var lab = ([0, 1, 2].map(function (i) { return ((1-t)*(1-t)*(1-t) * lab0[i]) + (3 * (1-t) * (1-t) * t * lab1[i]) + (3 * (1-t) * t * t * lab2[i]) + (t*t*t * lab3[i]); })); + return new Color_1(lab, 'lab'); + }; + } else if (colors.length === 5) { + var I0 = bezier(colors.slice(0, 3)); + var I1 = bezier(colors.slice(2, 5)); + I = function(t) { + if (t < 0.5) { + return I0(t*2); + } else { + return I1((t-0.5)*2); + } + }; + } + return I; + }; + + var bezier_1 = function (colors) { + var f = bezier(colors); + f.scale = function () { return scale(f); }; + return f; + }; + + /* + * interpolates between a set of colors uzing a bezier spline + * blend mode formulas taken from http://www.venture-ware.com/kevin/coding/lets-learn-math-photoshop-blend-modes/ + */ + + + + + var blend = function (bottom, top, mode) { + if (!blend[mode]) { + throw new Error('unknown blend mode ' + mode); + } + return blend[mode](bottom, top); + }; + + var blend_f = function (f) { return function (bottom,top) { + var c0 = chroma_1(top).rgb(); + var c1 = chroma_1(bottom).rgb(); + return chroma_1.rgb(f(c0, c1)); + }; }; + + var each = function (f) { return function (c0, c1) { + var out = []; + out[0] = f(c0[0], c1[0]); + out[1] = f(c0[1], c1[1]); + out[2] = f(c0[2], c1[2]); + return out; + }; }; + + var normal = function (a) { return a; }; + var multiply = function (a,b) { return a * b / 255; }; + var darken$1 = function (a,b) { return a > b ? b : a; }; + var lighten = function (a,b) { return a > b ? a : b; }; + var screen = function (a,b) { return 255 * (1 - (1-a/255) * (1-b/255)); }; + var overlay = function (a,b) { return b < 128 ? 2 * a * b / 255 : 255 * (1 - 2 * (1 - a / 255 ) * ( 1 - b / 255 )); }; + var burn = function (a,b) { return 255 * (1 - (1 - b / 255) / (a/255)); }; + var dodge = function (a,b) { + if (a === 255) { return 255; } + a = 255 * (b / 255) / (1 - a / 255); + return a > 255 ? 255 : a + }; + + // # add = (a,b) -> + // # if (a + b > 255) then 255 else a + b + + blend.normal = blend_f(each(normal)); + blend.multiply = blend_f(each(multiply)); + blend.screen = blend_f(each(screen)); + blend.overlay = blend_f(each(overlay)); + blend.darken = blend_f(each(darken$1)); + blend.lighten = blend_f(each(lighten)); + blend.dodge = blend_f(each(dodge)); + blend.burn = blend_f(each(burn)); + // blend.add = blend_f(each(add)); + + var blend_1 = blend; + + // cubehelix interpolation + // based on D.A. Green "A colour scheme for the display of astronomical intensity images" + // http://astron-soc.in/bulletin/11June/289392011.pdf + + var type$k = utils.type; + var clip_rgb$3 = utils.clip_rgb; + var TWOPI$2 = utils.TWOPI; + var pow$6 = Math.pow; + var sin$2 = Math.sin; + var cos$3 = Math.cos; + + + var cubehelix = function(start, rotations, hue, gamma, lightness) { + if ( start === void 0 ) start=300; + if ( rotations === void 0 ) rotations=-1.5; + if ( hue === void 0 ) hue=1; + if ( gamma === void 0 ) gamma=1; + if ( lightness === void 0 ) lightness=[0,1]; + + var dh = 0, dl; + if (type$k(lightness) === 'array') { + dl = lightness[1] - lightness[0]; + } else { + dl = 0; + lightness = [lightness, lightness]; + } + + var f = function(fract) { + var a = TWOPI$2 * (((start+120)/360) + (rotations * fract)); + var l = pow$6(lightness[0] + (dl * fract), gamma); + var h = dh !== 0 ? hue[0] + (fract * dh) : hue; + var amp = (h * l * (1-l)) / 2; + var cos_a = cos$3(a); + var sin_a = sin$2(a); + var r = l + (amp * ((-0.14861 * cos_a) + (1.78277* sin_a))); + var g = l + (amp * ((-0.29227 * cos_a) - (0.90649* sin_a))); + var b = l + (amp * (+1.97294 * cos_a)); + return chroma_1(clip_rgb$3([r*255,g*255,b*255,1])); + }; + + f.start = function(s) { + if ((s == null)) { return start; } + start = s; + return f; + }; + + f.rotations = function(r) { + if ((r == null)) { return rotations; } + rotations = r; + return f; + }; + + f.gamma = function(g) { + if ((g == null)) { return gamma; } + gamma = g; + return f; + }; + + f.hue = function(h) { + if ((h == null)) { return hue; } + hue = h; + if (type$k(hue) === 'array') { + dh = hue[1] - hue[0]; + if (dh === 0) { hue = hue[1]; } + } else { + dh = 0; + } + return f; + }; + + f.lightness = function(h) { + if ((h == null)) { return lightness; } + if (type$k(h) === 'array') { + lightness = h; + dl = h[1] - h[0]; + } else { + lightness = [h,h]; + dl = 0; + } + return f; + }; + + f.scale = function () { return chroma_1.scale(f); }; + + f.hue(hue); + + return f; + }; + + var digits = '0123456789abcdef'; + + var floor$2 = Math.floor; + var random = Math.random; + + var random_1 = function () { + var code = '#'; + for (var i=0; i<6; i++) { + code += digits.charAt(floor$2(random() * 16)); + } + return new Color_1(code, 'hex'); + }; + + var log$1 = Math.log; + var pow$7 = Math.pow; + var floor$3 = Math.floor; + var abs = Math.abs; + + + var analyze = function (data, key) { + if ( key === void 0 ) key=null; + + var r = { + min: Number.MAX_VALUE, + max: Number.MAX_VALUE*-1, + sum: 0, + values: [], + count: 0 + }; + if (type(data) === 'object') { + data = Object.values(data); + } + data.forEach(function (val) { + if (key && type(val) === 'object') { val = val[key]; } + if (val !== undefined && val !== null && !isNaN(val)) { + r.values.push(val); + r.sum += val; + if (val < r.min) { r.min = val; } + if (val > r.max) { r.max = val; } + r.count += 1; + } + }); + + r.domain = [r.min, r.max]; + + r.limits = function (mode, num) { return limits(r, mode, num); }; + + return r; + }; + + + var limits = function (data, mode, num) { + if ( mode === void 0 ) mode='equal'; + if ( num === void 0 ) num=7; + + if (type(data) == 'array') { + data = analyze(data); + } + var min = data.min; + var max = data.max; + var values = data.values.sort(function (a,b) { return a-b; }); + + if (num === 1) { return [min,max]; } + + var limits = []; + + if (mode.substr(0,1) === 'c') { // continuous + limits.push(min); + limits.push(max); + } + + if (mode.substr(0,1) === 'e') { // equal interval + limits.push(min); + for (var i=1; i 0'); + } + var min_log = Math.LOG10E * log$1(min); + var max_log = Math.LOG10E * log$1(max); + limits.push(min); + for (var i$1=1; i$1 pb + var pr = p - pb; + limits.push((values[pb]*(1-pr)) + (values[pb+1]*pr)); + } + } + limits.push(max); + + } + + else if (mode.substr(0,1) === 'k') { // k-means clustering + /* + implementation based on + http://code.google.com/p/figue/source/browse/trunk/figue.js#336 + simplified for 1-d input values + */ + var cluster; + var n = values.length; + var assignments = new Array(n); + var clusterSizes = new Array(num); + var repeat = true; + var nb_iters = 0; + var centroids = null; + + // get seed values + centroids = []; + centroids.push(min); + for (var i$3=1; i$3 200) { + repeat = false; + } + } + + // finished k-means clustering + // the next part is borrowed from gabrielflor.it + var kClusters = {}; + for (var j$5=0; j$5 l2 ? (l1 + 0.05) / (l2 + 0.05) : (l2 + 0.05) / (l1 + 0.05); + }; + + var sqrt$4 = Math.sqrt; + var atan2$2 = Math.atan2; + var abs$1 = Math.abs; + var cos$4 = Math.cos; + var PI$2 = Math.PI; + + var deltaE = function(a, b, L, C) { + if ( L === void 0 ) L=1; + if ( C === void 0 ) C=1; + + // Delta E (CMC) + // see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html + a = new Color_1(a); + b = new Color_1(b); + var ref = Array.from(a.lab()); + var L1 = ref[0]; + var a1 = ref[1]; + var b1 = ref[2]; + var ref$1 = Array.from(b.lab()); + var L2 = ref$1[0]; + var a2 = ref$1[1]; + var b2 = ref$1[2]; + var c1 = sqrt$4((a1 * a1) + (b1 * b1)); + var c2 = sqrt$4((a2 * a2) + (b2 * b2)); + var sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + (0.01765 * L1)); + var sc = ((0.0638 * c1) / (1.0 + (0.0131 * c1))) + 0.638; + var h1 = c1 < 0.000001 ? 0.0 : (atan2$2(b1, a1) * 180.0) / PI$2; + while (h1 < 0) { h1 += 360; } + while (h1 >= 360) { h1 -= 360; } + var t = (h1 >= 164.0) && (h1 <= 345.0) ? (0.56 + abs$1(0.2 * cos$4((PI$2 * (h1 + 168.0)) / 180.0))) : (0.36 + abs$1(0.4 * cos$4((PI$2 * (h1 + 35.0)) / 180.0))); + var c4 = c1 * c1 * c1 * c1; + var f = sqrt$4(c4 / (c4 + 1900.0)); + var sh = sc * (((f * t) + 1.0) - f); + var delL = L1 - L2; + var delC = c1 - c2; + var delA = a1 - a2; + var delB = b1 - b2; + var dH2 = ((delA * delA) + (delB * delB)) - (delC * delC); + var v1 = delL / (L * sl); + var v2 = delC / (C * sc); + var v3 = sh; + return sqrt$4((v1 * v1) + (v2 * v2) + (dH2 / (v3 * v3))); + }; + + // simple Euclidean distance + var distance = function(a, b, mode) { + if ( mode === void 0 ) mode='lab'; + + // Delta E (CIE 1976) + // see http://www.brucelindbloom.com/index.html?Equations.html + a = new Color_1(a); + b = new Color_1(b); + var l1 = a.get(mode); + var l2 = b.get(mode); + var sum_sq = 0; + for (var i in l1) { + var d = (l1[i] || 0) - (l2[i] || 0); + sum_sq += d*d; + } + return Math.sqrt(sum_sq); + }; + + var valid = function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + try { + new (Function.prototype.bind.apply( Color_1, [ null ].concat( args) )); + return true; + } catch (e) { + return false; + } + }; + + // some pre-defined color scales: + + + + + var scales = { + cool: function cool() { return scale([chroma_1.hsl(180,1,.9), chroma_1.hsl(250,.7,.4)]) }, + hot: function hot() { return scale(['#000','#f00','#ff0','#fff'], [0,.25,.75,1]).mode('rgb') } + }; + + /** + ColorBrewer colors for chroma.js + + Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The + Pennsylvania State University. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for the + specific language governing permissions and limitations under the License. + */ + + var colorbrewer = { + // sequential + OrRd: ['#fff7ec', '#fee8c8', '#fdd49e', '#fdbb84', '#fc8d59', '#ef6548', '#d7301f', '#b30000', '#7f0000'], + PuBu: ['#fff7fb', '#ece7f2', '#d0d1e6', '#a6bddb', '#74a9cf', '#3690c0', '#0570b0', '#045a8d', '#023858'], + BuPu: ['#f7fcfd', '#e0ecf4', '#bfd3e6', '#9ebcda', '#8c96c6', '#8c6bb1', '#88419d', '#810f7c', '#4d004b'], + Oranges: ['#fff5eb', '#fee6ce', '#fdd0a2', '#fdae6b', '#fd8d3c', '#f16913', '#d94801', '#a63603', '#7f2704'], + BuGn: ['#f7fcfd', '#e5f5f9', '#ccece6', '#99d8c9', '#66c2a4', '#41ae76', '#238b45', '#006d2c', '#00441b'], + YlOrBr: ['#ffffe5', '#fff7bc', '#fee391', '#fec44f', '#fe9929', '#ec7014', '#cc4c02', '#993404', '#662506'], + YlGn: ['#ffffe5', '#f7fcb9', '#d9f0a3', '#addd8e', '#78c679', '#41ab5d', '#238443', '#006837', '#004529'], + Reds: ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#a50f15', '#67000d'], + RdPu: ['#fff7f3', '#fde0dd', '#fcc5c0', '#fa9fb5', '#f768a1', '#dd3497', '#ae017e', '#7a0177', '#49006a'], + Greens: ['#f7fcf5', '#e5f5e0', '#c7e9c0', '#a1d99b', '#74c476', '#41ab5d', '#238b45', '#006d2c', '#00441b'], + YlGnBu: ['#ffffd9', '#edf8b1', '#c7e9b4', '#7fcdbb', '#41b6c4', '#1d91c0', '#225ea8', '#253494', '#081d58'], + Purples: ['#fcfbfd', '#efedf5', '#dadaeb', '#bcbddc', '#9e9ac8', '#807dba', '#6a51a3', '#54278f', '#3f007d'], + GnBu: ['#f7fcf0', '#e0f3db', '#ccebc5', '#a8ddb5', '#7bccc4', '#4eb3d3', '#2b8cbe', '#0868ac', '#084081'], + Greys: ['#ffffff', '#f0f0f0', '#d9d9d9', '#bdbdbd', '#969696', '#737373', '#525252', '#252525', '#000000'], + YlOrRd: ['#ffffcc', '#ffeda0', '#fed976', '#feb24c', '#fd8d3c', '#fc4e2a', '#e31a1c', '#bd0026', '#800026'], + PuRd: ['#f7f4f9', '#e7e1ef', '#d4b9da', '#c994c7', '#df65b0', '#e7298a', '#ce1256', '#980043', '#67001f'], + Blues: ['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#08519c', '#08306b'], + PuBuGn: ['#fff7fb', '#ece2f0', '#d0d1e6', '#a6bddb', '#67a9cf', '#3690c0', '#02818a', '#016c59', '#014636'], + Viridis: ['#440154', '#482777', '#3f4a8a', '#31678e', '#26838f', '#1f9d8a', '#6cce5a', '#b6de2b', '#fee825'], + + // diverging + + Spectral: ['#9e0142', '#d53e4f', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#e6f598', '#abdda4', '#66c2a5', '#3288bd', '#5e4fa2'], + RdYlGn: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850', '#006837'], + RdBu: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#f7f7f7', '#d1e5f0', '#92c5de', '#4393c3', '#2166ac', '#053061'], + PiYG: ['#8e0152', '#c51b7d', '#de77ae', '#f1b6da', '#fde0ef', '#f7f7f7', '#e6f5d0', '#b8e186', '#7fbc41', '#4d9221', '#276419'], + PRGn: ['#40004b', '#762a83', '#9970ab', '#c2a5cf', '#e7d4e8', '#f7f7f7', '#d9f0d3', '#a6dba0', '#5aae61', '#1b7837', '#00441b'], + RdYlBu: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee090', '#ffffbf', '#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'], + BrBG: ['#543005', '#8c510a', '#bf812d', '#dfc27d', '#f6e8c3', '#f5f5f5', '#c7eae5', '#80cdc1', '#35978f', '#01665e', '#003c30'], + RdGy: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#ffffff', '#e0e0e0', '#bababa', '#878787', '#4d4d4d', '#1a1a1a'], + PuOr: ['#7f3b08', '#b35806', '#e08214', '#fdb863', '#fee0b6', '#f7f7f7', '#d8daeb', '#b2abd2', '#8073ac', '#542788', '#2d004b'], + + // qualitative + + Set2: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494', '#b3b3b3'], + Accent: ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f', '#bf5b17', '#666666'], + Set1: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#f781bf', '#999999'], + Set3: ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f'], + Dark2: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666'], + Paired: ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00', '#cab2d6', '#6a3d9a', '#ffff99', '#b15928'], + Pastel2: ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9', '#fff2ae', '#f1e2cc', '#cccccc'], + Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2'], + }; + + // add lowercase aliases for case-insensitive matches + for (var i$1 = 0, list$1 = Object.keys(colorbrewer); i$1 < list$1.length; i$1 += 1) { + var key = list$1[i$1]; + + colorbrewer[key.toLowerCase()] = colorbrewer[key]; + } + + var colorbrewer_1 = colorbrewer; + + // feel free to comment out anything to rollup + // a smaller chroma.js built + + // io --> convert colors + + + + + + + + + + + + + + + + // operators --> modify existing Colors + + + + + + + + + + + // interpolators + + + + + + + + + + + // generators -- > create new colors + chroma_1.average = average; + chroma_1.bezier = bezier_1; + chroma_1.blend = blend_1; + chroma_1.cubehelix = cubehelix; + chroma_1.mix = chroma_1.interpolate = mix; + chroma_1.random = random_1; + chroma_1.scale = scale; + + // other utility methods + chroma_1.analyze = analyze_1.analyze; + chroma_1.contrast = contrast; + chroma_1.deltaE = deltaE; + chroma_1.distance = distance; + chroma_1.limits = analyze_1.limits; + chroma_1.valid = valid; + + // scale + chroma_1.scales = scales; + + // colors + chroma_1.colors = w3cx11_1; + chroma_1.brewer = colorbrewer_1; + + var chroma_js = chroma_1; + + return chroma_js; + +}))); + +},{}],60:[function(require,module,exports){ +"use strict" + +var createThunk = require("./lib/thunk.js") + +function Procedure() { + this.argTypes = [] + this.shimArgs = [] + this.arrayArgs = [] + this.arrayBlockIndices = [] + this.scalarArgs = [] + this.offsetArgs = [] + this.offsetArgIndex = [] + this.indexArgs = [] + this.shapeArgs = [] + this.funcName = "" + this.pre = null + this.body = null + this.post = null + this.debug = false +} + +function compileCwise(user_args) { + //Create procedure + var proc = new Procedure() + + //Parse blocks + proc.pre = user_args.pre + proc.body = user_args.body + proc.post = user_args.post + + //Parse arguments + var proc_args = user_args.args.slice(0) + proc.argTypes = proc_args + for(var i=0; i0) { + throw new Error("cwise: pre() block may not reference array args") + } + if(i < proc.post.args.length && proc.post.args[i].count>0) { + throw new Error("cwise: post() block may not reference array args") + } + } else if(arg_type === "scalar") { + proc.scalarArgs.push(i) + proc.shimArgs.push("scalar" + i) + } else if(arg_type === "index") { + proc.indexArgs.push(i) + if(i < proc.pre.args.length && proc.pre.args[i].count > 0) { + throw new Error("cwise: pre() block may not reference array index") + } + if(i < proc.body.args.length && proc.body.args[i].lvalue) { + throw new Error("cwise: body() block may not write to array index") + } + if(i < proc.post.args.length && proc.post.args[i].count > 0) { + throw new Error("cwise: post() block may not reference array index") + } + } else if(arg_type === "shape") { + proc.shapeArgs.push(i) + if(i < proc.pre.args.length && proc.pre.args[i].lvalue) { + throw new Error("cwise: pre() block may not write to array shape") + } + if(i < proc.body.args.length && proc.body.args[i].lvalue) { + throw new Error("cwise: body() block may not write to array shape") + } + if(i < proc.post.args.length && proc.post.args[i].lvalue) { + throw new Error("cwise: post() block may not write to array shape") + } + } else if(typeof arg_type === "object" && arg_type.offset) { + proc.argTypes[i] = "offset" + proc.offsetArgs.push({ array: arg_type.array, offset:arg_type.offset }) + proc.offsetArgIndex.push(i) + } else { + throw new Error("cwise: Unknown argument type " + proc_args[i]) + } + } + + //Make sure at least one array argument was specified + if(proc.arrayArgs.length <= 0) { + throw new Error("cwise: No array arguments specified") + } + + //Make sure arguments are correct + if(proc.pre.args.length > proc_args.length) { + throw new Error("cwise: Too many arguments in pre() block") + } + if(proc.body.args.length > proc_args.length) { + throw new Error("cwise: Too many arguments in body() block") + } + if(proc.post.args.length > proc_args.length) { + throw new Error("cwise: Too many arguments in post() block") + } + + //Check debug flag + proc.debug = !!user_args.printCode || !!user_args.debug + + //Retrieve name + proc.funcName = user_args.funcName || "cwise" + + //Read in block size + proc.blockSize = user_args.blockSize || 64 + + return createThunk(proc) +} + +module.exports = compileCwise + +},{"./lib/thunk.js":62}],61:[function(require,module,exports){ +"use strict" + +var uniq = require("uniq") + +// This function generates very simple loops analogous to how you typically traverse arrays (the outermost loop corresponds to the slowest changing index, the innermost loop to the fastest changing index) +// TODO: If two arrays have the same strides (and offsets) there is potential for decreasing the number of "pointers" and related variables. The drawback is that the type signature would become more specific and that there would thus be less potential for caching, but it might still be worth it, especially when dealing with large numbers of arguments. +function innerFill(order, proc, body) { + var dimension = order.length + , nargs = proc.arrayArgs.length + , has_index = proc.indexArgs.length>0 + , code = [] + , vars = [] + , idx=0, pidx=0, i, j + for(i=0; i 0) { + code.push("var " + vars.join(",")) + } + //Scan loop + for(i=dimension-1; i>=0; --i) { // Start at largest stride and work your way inwards + idx = order[i] + code.push(["for(i",i,"=0;i",i," 0) { + code.push(["index[",pidx,"]-=s",pidx].join("")) + } + code.push(["++index[",idx,"]"].join("")) + } + code.push("}") + } + return code.join("\n") +} + +// Generate "outer" loops that loop over blocks of data, applying "inner" loops to the blocks by manipulating the local variables in such a way that the inner loop only "sees" the current block. +// TODO: If this is used, then the previous declaration (done by generateCwiseOp) of s* is essentially unnecessary. +// I believe the s* are not used elsewhere (in particular, I don't think they're used in the pre/post parts and "shape" is defined independently), so it would be possible to make defining the s* dependent on what loop method is being used. +function outerFill(matched, order, proc, body) { + var dimension = order.length + , nargs = proc.arrayArgs.length + , blockSize = proc.blockSize + , has_index = proc.indexArgs.length > 0 + , code = [] + for(var i=0; i0;){"].join("")) // Iterate back to front + code.push(["if(j",i,"<",blockSize,"){"].join("")) // Either decrease j by blockSize (s = blockSize), or set it to zero (after setting s = j). + code.push(["s",order[i],"=j",i].join("")) + code.push(["j",i,"=0"].join("")) + code.push(["}else{s",order[i],"=",blockSize].join("")) + code.push(["j",i,"-=",blockSize,"}"].join("")) + if(has_index) { + code.push(["index[",order[i],"]=j",i].join("")) + } + } + for(var i=0; i 0) { + allEqual = allEqual && summary[i] === summary[i-1] + } + } + if(allEqual) { + return summary[0] + } + return summary.join("") +} + +//Generates a cwise operator +function generateCWiseOp(proc, typesig) { + + //Compute dimension + // Arrays get put first in typesig, and there are two entries per array (dtype and order), so this gets the number of dimensions in the first array arg. + var dimension = (typesig[1].length - Math.abs(proc.arrayBlockIndices[0]))|0 + var orders = new Array(proc.arrayArgs.length) + var dtypes = new Array(proc.arrayArgs.length) + for(var i=0; i 0) { + vars.push("shape=SS.slice(0)") // Makes the shape over which we iterate available to the user defined functions (so you can use width/height for example) + } + if(proc.indexArgs.length > 0) { + // Prepare an array to keep track of the (logical) indices, initialized to dimension zeroes. + var zeros = new Array(dimension) + for(var i=0; i 0) { + code.push("var " + vars.join(",")) + } + for(var i=0; i 3) { + code.push(processBlock(proc.pre, proc, dtypes)) + } + + //Process body + var body = processBlock(proc.body, proc, dtypes) + var matched = countMatches(loopOrders) + if(matched < dimension) { + code.push(outerFill(matched, loopOrders[0], proc, body)) // TODO: Rather than passing loopOrders[0], it might be interesting to look at passing an order that represents the majority of the arguments for example. + } else { + code.push(innerFill(loopOrders[0], proc, body)) + } + + //Inline epilog + if(proc.post.body.length > 3) { + code.push(processBlock(proc.post, proc, dtypes)) + } + + if(proc.debug) { + console.log("-----Generated cwise routine for ", typesig, ":\n" + code.join("\n") + "\n----------") + } + + var loopName = [(proc.funcName||"unnamed"), "_cwise_loop_", orders[0].join("s"),"m",matched,typeSummary(dtypes)].join("") + var f = new Function(["function ",loopName,"(", arglist.join(","),"){", code.join("\n"),"} return ", loopName].join("")) + return f() +} +module.exports = generateCWiseOp + +},{"uniq":135}],62:[function(require,module,exports){ +"use strict" + +// The function below is called when constructing a cwise function object, and does the following: +// A function object is constructed which accepts as argument a compilation function and returns another function. +// It is this other function that is eventually returned by createThunk, and this function is the one that actually +// checks whether a certain pattern of arguments has already been used before and compiles new loops as needed. +// The compilation passed to the first function object is used for compiling new functions. +// Once this function object is created, it is called with compile as argument, where the first argument of compile +// is bound to "proc" (essentially containing a preprocessed version of the user arguments to cwise). +// So createThunk roughly works like this: +// function createThunk(proc) { +// var thunk = function(compileBound) { +// var CACHED = {} +// return function(arrays and scalars) { +// if (dtype and order of arrays in CACHED) { +// var func = CACHED[dtype and order of arrays] +// } else { +// var func = CACHED[dtype and order of arrays] = compileBound(dtype and order of arrays) +// } +// return func(arrays and scalars) +// } +// } +// return thunk(compile.bind1(proc)) +// } + +var compile = require("./compile.js") + +function createThunk(proc) { + var code = ["'use strict'", "var CACHED={}"] + var vars = [] + var thunkName = proc.funcName + "_cwise_thunk" + + //Build thunk + code.push(["return function ", thunkName, "(", proc.shimArgs.join(","), "){"].join("")) + var typesig = [] + var string_typesig = [] + var proc_args = [["array",proc.arrayArgs[0],".shape.slice(", // Slice shape so that we only retain the shape over which we iterate (which gets passed to the cwise operator as SS). + Math.max(0,proc.arrayBlockIndices[0]),proc.arrayBlockIndices[0]<0?(","+proc.arrayBlockIndices[0]+")"):")"].join("")] + var shapeLengthConditions = [], shapeConditions = [] + // Process array arguments + for(var i=0; i0) { // Gather conditions to check for shape equality (ignoring block indices) + shapeLengthConditions.push("array" + proc.arrayArgs[0] + ".shape.length===array" + j + ".shape.length+" + (Math.abs(proc.arrayBlockIndices[0])-Math.abs(proc.arrayBlockIndices[i]))) + shapeConditions.push("array" + proc.arrayArgs[0] + ".shape[shapeIndex+" + Math.max(0,proc.arrayBlockIndices[0]) + "]===array" + j + ".shape[shapeIndex+" + Math.max(0,proc.arrayBlockIndices[i]) + "]") + } + } + // Check for shape equality + if (proc.arrayArgs.length > 1) { + code.push("if (!(" + shapeLengthConditions.join(" && ") + ")) throw new Error('cwise: Arrays do not all have the same dimensionality!')") + code.push("for(var shapeIndex=array" + proc.arrayArgs[0] + ".shape.length-" + Math.abs(proc.arrayBlockIndices[0]) + "; shapeIndex-->0;) {") + code.push("if (!(" + shapeConditions.join(" && ") + ")) throw new Error('cwise: Arrays do not all have the same shape!')") + code.push("}") + } + // Process scalar arguments + for(var i=0; i + * @license MIT + */ + +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) +} + +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) +} + +},{}],65:[function(require,module,exports){ +var encode = require('./lib/encoder'), + decode = require('./lib/decoder'); + +module.exports = { + encode: encode, + decode: decode +}; + +},{"./lib/decoder":66,"./lib/encoder":67}],66:[function(require,module,exports){ +(function (Buffer){(function (){ +/* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* + Copyright 2011 notmasteryet + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// - The JPEG specification can be found in the ITU CCITT Recommendation T.81 +// (www.w3.org/Graphics/JPEG/itu-t81.pdf) +// - The JFIF specification can be found in the JPEG File Interchange Format +// (www.w3.org/Graphics/JPEG/jfif3.pdf) +// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters +// in PostScript Level 2, Technical Note #5116 +// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) + +var JpegImage = (function jpegImage() { + "use strict"; + var dctZigZag = new Int32Array([ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 + ]); + + var dctCos1 = 4017 // cos(pi/16) + var dctSin1 = 799 // sin(pi/16) + var dctCos3 = 3406 // cos(3*pi/16) + var dctSin3 = 2276 // sin(3*pi/16) + var dctCos6 = 1567 // cos(6*pi/16) + var dctSin6 = 3784 // sin(6*pi/16) + var dctSqrt2 = 5793 // sqrt(2) + var dctSqrt1d2 = 2896 // sqrt(2) / 2 + + function constructor() { + } + + function buildHuffmanTable(codeLengths, values) { + var k = 0, code = [], i, j, length = 16; + while (length > 0 && !codeLengths[length - 1]) + length--; + code.push({children: [], index: 0}); + var p = code[0], q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + while (p.index > 0) { + if (code.length === 0) + throw new Error('Could not recreate Huffman Table'); + p = code.pop(); + } + p.index++; + code.push(p); + while (code.length <= i) { + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + k++; + } + if (i + 1 < length) { + // p here points to last code + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + } + return code[0].children; + } + + function decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successivePrev, successive, opts) { + var precision = frame.precision; + var samplesPerLine = frame.samplesPerLine; + var scanLines = frame.scanLines; + var mcusPerLine = frame.mcusPerLine; + var progressive = frame.progressive; + var maxH = frame.maxH, maxV = frame.maxV; + + var startOffset = offset, bitsData = 0, bitsCount = 0; + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData == 0xFF) { + var nextByte = data[offset++]; + if (nextByte) { + throw new Error("unexpected marker: " + ((bitsData << 8) | nextByte).toString(16)); + } + // unstuff 0 + } + bitsCount = 7; + return bitsData >>> 7; + } + function decodeHuffman(tree) { + var node = tree, bit; + while ((bit = readBit()) !== null) { + node = node[bit]; + if (typeof node === 'number') + return node; + if (typeof node !== 'object') + throw new Error("invalid huffman sequence"); + } + return null; + } + function receive(length) { + var n = 0; + while (length > 0) { + var bit = readBit(); + if (bit === null) return; + n = (n << 1) | bit; + length--; + } + return n; + } + function receiveAndExtend(length) { + var n = receive(length); + if (n >= 1 << (length - 1)) + return n; + return n + (-1 << length) + 1; + } + function decodeBaseline(component, zz) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : receiveAndExtend(t); + zz[0]= (component.pred += diff); + var k = 1; + while (k < 64) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) + break; + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + zz[z] = receiveAndExtend(s); + k++; + } + } + function decodeDCFirst(component, zz) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); + zz[0] = (component.pred += diff); + } + function decodeDCSuccessive(component, zz) { + zz[0] |= readBit() << successive; + } + var eobrun = 0; + function decodeACFirst(component, zz) { + if (eobrun > 0) { + eobrun--; + return; + } + var k = spectralStart, e = spectralEnd; + while (k <= e) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; + } + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + zz[z] = receiveAndExtend(s) * (1 << successive); + k++; + } + } + var successiveACState = 0, successiveACNextValue; + function decodeACSuccessive(component, zz) { + var k = spectralStart, e = spectralEnd, r = 0; + while (k <= e) { + var z = dctZigZag[k]; + var direction = zz[z] < 0 ? -1 : 1; + switch (successiveACState) { + case 0: // initial state + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r); + successiveACState = 4; + } else { + r = 16; + successiveACState = 1; + } + } else { + if (s !== 1) + throw new Error("invalid ACn encoding"); + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (zz[z]) + zz[z] += (readBit() << successive) * direction; + else { + r--; + if (r === 0) + successiveACState = successiveACState == 2 ? 3 : 0; + } + break; + case 3: // set value for a zero item + if (zz[z]) + zz[z] += (readBit() << successive) * direction; + else { + zz[z] = successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (zz[z]) + zz[z] += (readBit() << successive) * direction; + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) + successiveACState = 0; + } + } + function decodeMcu(component, decode, mcu, row, col) { + var mcuRow = (mcu / mcusPerLine) | 0; + var mcuCol = mcu % mcusPerLine; + var blockRow = mcuRow * component.v + row; + var blockCol = mcuCol * component.h + col; + // If the block is missing and we're in tolerant mode, just skip it. + if (component.blocks[blockRow] === undefined && opts.tolerantDecoding) + return; + decode(component, component.blocks[blockRow][blockCol]); + } + function decodeBlock(component, decode, mcu) { + var blockRow = (mcu / component.blocksPerLine) | 0; + var blockCol = mcu % component.blocksPerLine; + // If the block is missing and we're in tolerant mode, just skip it. + if (component.blocks[blockRow] === undefined && opts.tolerantDecoding) + return; + decode(component, component.blocks[blockRow][blockCol]); + } + + var componentsLength = components.length; + var component, i, j, k, n; + var decodeFn; + if (progressive) { + if (spectralStart === 0) + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + else + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + } else { + decodeFn = decodeBaseline; + } + + var mcu = 0, marker; + var mcuExpected; + if (componentsLength == 1) { + mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; + } else { + mcuExpected = mcusPerLine * frame.mcusPerColumn; + } + if (!resetInterval) resetInterval = mcuExpected; + + var h, v; + while (mcu < mcuExpected) { + // reset interval stuff + for (i = 0; i < componentsLength; i++) + components[i].pred = 0; + eobrun = 0; + + if (componentsLength == 1) { + component = components[0]; + for (n = 0; n < resetInterval; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < resetInterval; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + + // If we've reached our expected MCU's, stop decoding + if (mcu === mcuExpected) break; + } + } + + if (mcu === mcuExpected) { + // Skip trailing bytes at the end of the scan - until we reach the next marker + do { + if (data[offset] === 0xFF) { + if (data[offset + 1] !== 0x00) { + break; + } + } + offset += 1; + } while (offset < data.length - 2); + } + + // find marker + bitsCount = 0; + marker = (data[offset] << 8) | data[offset + 1]; + if (marker < 0xFF00) { + throw new Error("marker was not found"); + } + + if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx + offset += 2; + } + else + break; + } + + return offset - startOffset; + } + + function buildComponentData(frame, component) { + var lines = []; + var blocksPerLine = component.blocksPerLine; + var blocksPerColumn = component.blocksPerColumn; + var samplesPerLine = blocksPerLine << 3; + // Only 1 used per invocation of this function and garbage collected after invocation, so no need to account for its memory footprint. + var R = new Int32Array(64), r = new Uint8Array(64); + + // A port of poppler's IDCT method which in turn is taken from: + // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, + // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", + // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, + // 988-991. + function quantizeAndInverse(zz, dataOut, dataIn) { + var qt = component.quantizationTable; + var v0, v1, v2, v3, v4, v5, v6, v7, t; + var p = dataIn; + var i; + + // dequant + for (i = 0; i < 64; i++) + p[i] = zz[i] * qt[i]; + + // inverse DCT on rows + for (i = 0; i < 8; ++i) { + var row = 8 * i; + + // check for all-zero AC coefficients + if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 && + p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 && + p[7 + row] == 0) { + t = (dctSqrt2 * p[0 + row] + 512) >> 10; + p[0 + row] = t; + p[1 + row] = t; + p[2 + row] = t; + p[3 + row] = t; + p[4 + row] = t; + p[5 + row] = t; + p[6 + row] = t; + p[7 + row] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; + v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; + v2 = p[2 + row]; + v3 = p[6 + row]; + v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; + v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; + v5 = p[3 + row] << 4; + v6 = p[5 + row] << 4; + + // stage 3 + t = (v0 - v1+ 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0 + row] = v0 + v7; + p[7 + row] = v0 - v7; + p[1 + row] = v1 + v6; + p[6 + row] = v1 - v6; + p[2 + row] = v2 + v5; + p[5 + row] = v2 - v5; + p[3 + row] = v3 + v4; + p[4 + row] = v3 - v4; + } + + // inverse DCT on columns + for (i = 0; i < 8; ++i) { + var col = i; + + // check for all-zero AC coefficients + if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 && + p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 && + p[7*8 + col] == 0) { + t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14; + p[0*8 + col] = t; + p[1*8 + col] = t; + p[2*8 + col] = t; + p[3*8 + col] = t; + p[4*8 + col] = t; + p[5*8 + col] = t; + p[6*8 + col] = t; + p[7*8 + col] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12; + v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12; + v2 = p[2*8 + col]; + v3 = p[6*8 + col]; + v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12; + v5 = p[3*8 + col]; + v6 = p[5*8 + col]; + + // stage 3 + t = (v0 - v1 + 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0*8 + col] = v0 + v7; + p[7*8 + col] = v0 - v7; + p[1*8 + col] = v1 + v6; + p[6*8 + col] = v1 - v6; + p[2*8 + col] = v2 + v5; + p[5*8 + col] = v2 - v5; + p[3*8 + col] = v3 + v4; + p[4*8 + col] = v3 - v4; + } + + // convert to 8-bit integers + for (i = 0; i < 64; ++i) { + var sample = 128 + ((p[i] + 8) >> 4); + dataOut[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample; + } + } + + requestMemoryAllocation(samplesPerLine * blocksPerColumn * 8); + + var i, j; + for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + var scanLine = blockRow << 3; + for (i = 0; i < 8; i++) + lines.push(new Uint8Array(samplesPerLine)); + for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { + quantizeAndInverse(component.blocks[blockRow][blockCol], r, R); + + var offset = 0, sample = blockCol << 3; + for (j = 0; j < 8; j++) { + var line = lines[scanLine + j]; + for (i = 0; i < 8; i++) + line[sample + i] = r[offset++]; + } + } + } + return lines; + } + + function clampTo8bit(a) { + return a < 0 ? 0 : a > 255 ? 255 : a; + } + + constructor.prototype = { + load: function load(path) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", path, true); + xhr.responseType = "arraybuffer"; + xhr.onload = (function() { + // TODO catch parse error + var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); + this.parse(data); + if (this.onload) + this.onload(); + }).bind(this); + xhr.send(null); + }, + parse: function parse(data) { + var maxResolutionInPixels = this.opts.maxResolutionInMP * 1000 * 1000; + var offset = 0, length = data.length; + function readUint16() { + var value = (data[offset] << 8) | data[offset + 1]; + offset += 2; + return value; + } + function readDataBlock() { + var length = readUint16(); + var array = data.subarray(offset, offset + length - 2); + offset += array.length; + return array; + } + function prepareComponents(frame) { + var maxH = 0, maxV = 0; + var component, componentId; + for (componentId in frame.components) { + if (frame.components.hasOwnProperty(componentId)) { + component = frame.components[componentId]; + if (maxH < component.h) maxH = component.h; + if (maxV < component.v) maxV = component.v; + } + } + var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / maxH); + var mcusPerColumn = Math.ceil(frame.scanLines / 8 / maxV); + for (componentId in frame.components) { + if (frame.components.hasOwnProperty(componentId)) { + component = frame.components[componentId]; + var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / maxH); + var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / maxV); + var blocksPerLineForMcu = mcusPerLine * component.h; + var blocksPerColumnForMcu = mcusPerColumn * component.v; + var blocksToAllocate = blocksPerColumnForMcu * blocksPerLineForMcu; + var blocks = []; + + // Each block is a Int32Array of length 64 (4 x 64 = 256 bytes) + requestMemoryAllocation(blocksToAllocate * 256); + + for (var i = 0; i < blocksPerColumnForMcu; i++) { + var row = []; + for (var j = 0; j < blocksPerLineForMcu; j++) + row.push(new Int32Array(64)); + blocks.push(row); + } + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + component.blocks = blocks; + } + } + frame.maxH = maxH; + frame.maxV = maxV; + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; + } + var jfif = null; + var adobe = null; + var pixels = null; + var frame, resetInterval; + var quantizationTables = [], frames = []; + var huffmanTablesAC = [], huffmanTablesDC = []; + var fileMarker = readUint16(); + this.comments = []; + if (fileMarker != 0xFFD8) { // SOI (Start of Image) + throw new Error("SOI not found"); + } + + fileMarker = readUint16(); + while (fileMarker != 0xFFD9) { // EOI (End of image) + var i, j, l; + switch(fileMarker) { + case 0xFF00: break; + case 0xFFE0: // APP0 (Application Specific) + case 0xFFE1: // APP1 + case 0xFFE2: // APP2 + case 0xFFE3: // APP3 + case 0xFFE4: // APP4 + case 0xFFE5: // APP5 + case 0xFFE6: // APP6 + case 0xFFE7: // APP7 + case 0xFFE8: // APP8 + case 0xFFE9: // APP9 + case 0xFFEA: // APP10 + case 0xFFEB: // APP11 + case 0xFFEC: // APP12 + case 0xFFED: // APP13 + case 0xFFEE: // APP14 + case 0xFFEF: // APP15 + case 0xFFFE: // COM (Comment) + var appData = readDataBlock(); + + if (fileMarker === 0xFFFE) { + var comment = String.fromCharCode.apply(null, appData); + this.comments.push(comment); + } + + if (fileMarker === 0xFFE0) { + if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && + appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' + jfif = { + version: { major: appData[5], minor: appData[6] }, + densityUnits: appData[7], + xDensity: (appData[8] << 8) | appData[9], + yDensity: (appData[10] << 8) | appData[11], + thumbWidth: appData[12], + thumbHeight: appData[13], + thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xFFE1) { + if (appData[0] === 0x45 && + appData[1] === 0x78 && + appData[2] === 0x69 && + appData[3] === 0x66 && + appData[4] === 0) { // 'EXIF\x00' + this.exifBuffer = appData.subarray(5, appData.length); + } + } + + if (fileMarker === 0xFFEE) { + if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && + appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' + adobe = { + version: appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11] + }; + } + } + break; + + case 0xFFDB: // DQT (Define Quantization Tables) + var quantizationTablesLength = readUint16(); + var quantizationTablesEnd = quantizationTablesLength + offset - 2; + while (offset < quantizationTablesEnd) { + var quantizationTableSpec = data[offset++]; + requestMemoryAllocation(64 * 4); + var tableData = new Int32Array(64); + if ((quantizationTableSpec >> 4) === 0) { // 8 bit values + for (j = 0; j < 64; j++) { + var z = dctZigZag[j]; + tableData[z] = data[offset++]; + } + } else if ((quantizationTableSpec >> 4) === 1) { //16 bit + for (j = 0; j < 64; j++) { + var z = dctZigZag[j]; + tableData[z] = readUint16(); + } + } else + throw new Error("DQT: invalid table spec"); + quantizationTables[quantizationTableSpec & 15] = tableData; + } + break; + + case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) + case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) + case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) + readUint16(); // skip data length + frame = {}; + frame.extended = (fileMarker === 0xFFC1); + frame.progressive = (fileMarker === 0xFFC2); + frame.precision = data[offset++]; + frame.scanLines = readUint16(); + frame.samplesPerLine = readUint16(); + frame.components = {}; + frame.componentsOrder = []; + + var pixelsInFrame = frame.scanLines * frame.samplesPerLine; + if (pixelsInFrame > maxResolutionInPixels) { + var exceededAmount = Math.ceil((pixelsInFrame - maxResolutionInPixels) / 1e6); + throw new Error(`maxResolutionInMP limit exceeded by ${exceededAmount}MP`); + } + + var componentsCount = data[offset++], componentId; + var maxH = 0, maxV = 0; + for (i = 0; i < componentsCount; i++) { + componentId = data[offset]; + var h = data[offset + 1] >> 4; + var v = data[offset + 1] & 15; + var qId = data[offset + 2]; + frame.componentsOrder.push(componentId); + frame.components[componentId] = { + h: h, + v: v, + quantizationIdx: qId + }; + offset += 3; + } + prepareComponents(frame); + frames.push(frame); + break; + + case 0xFFC4: // DHT (Define Huffman Tables) + var huffmanLength = readUint16(); + for (i = 2; i < huffmanLength;) { + var huffmanTableSpec = data[offset++]; + var codeLengths = new Uint8Array(16); + var codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) { + codeLengthSum += (codeLengths[j] = data[offset]); + } + requestMemoryAllocation(16 + codeLengthSum); + var huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) + huffmanValues[j] = data[offset]; + i += 17 + codeLengthSum; + + ((huffmanTableSpec >> 4) === 0 ? + huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = + buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xFFDD: // DRI (Define Restart Interval) + readUint16(); // skip data length + resetInterval = readUint16(); + break; + + case 0xFFDA: // SOS (Start of Scan) + var scanLength = readUint16(); + var selectorsCount = data[offset++]; + var components = [], component; + for (i = 0; i < selectorsCount; i++) { + component = frame.components[data[offset++]]; + var tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + var spectralStart = data[offset++]; + var spectralEnd = data[offset++]; + var successiveApproximation = data[offset++]; + var processed = decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successiveApproximation >> 4, successiveApproximation & 15, this.opts); + offset += processed; + break; + + case 0xFFFF: // Fill bytes + if (data[offset] !== 0xFF) { // Avoid skipping a valid marker. + offset--; + } + break; + + default: + if (data[offset - 3] == 0xFF && + data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { + // could be incorrect encoding -- last 0xFF byte of the previous + // block was eaten by the encoder + offset -= 3; + break; + } + throw new Error("unknown JPEG marker " + fileMarker.toString(16)); + } + fileMarker = readUint16(); + } + if (frames.length != 1) + throw new Error("only single frame JPEGs supported"); + + // set each frame's components quantization table + for (var i = 0; i < frames.length; i++) { + var cp = frames[i].components; + for (var j in cp) { + cp[j].quantizationTable = quantizationTables[cp[j].quantizationIdx]; + delete cp[j].quantizationIdx; + } + } + + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (var i = 0; i < frame.componentsOrder.length; i++) { + var component = frame.components[frame.componentsOrder[i]]; + this.components.push({ + lines: buildComponentData(frame, component), + scaleX: component.h / frame.maxH, + scaleY: component.v / frame.maxV + }); + } + }, + getData: function getData(width, height) { + var scaleX = this.width / width, scaleY = this.height / height; + + var component1, component2, component3, component4; + var component1Line, component2Line, component3Line, component4Line; + var x, y; + var offset = 0; + var Y, Cb, Cr, K, C, M, Ye, R, G, B; + var colorTransform; + var dataLength = width * height * this.components.length; + requestMemoryAllocation(dataLength); + var data = new Uint8Array(dataLength); + switch (this.components.length) { + case 1: + component1 = this.components[0]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + + data[offset++] = Y; + } + } + break; + case 2: + // PDF might compress two component data in custom colorspace + component1 = this.components[0]; + component2 = this.components[1]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + data[offset++] = Y; + Y = component2Line[0 | (x * component2.scaleX * scaleX)]; + data[offset++] = Y; + } + } + break; + case 3: + // The default transform for three components is true + colorTransform = true; + // The adobe transform marker overrides any previous setting + if (this.adobe && this.adobe.transformCode) + colorTransform = true; + else if (typeof this.opts.colorTransform !== 'undefined') + colorTransform = !!this.opts.colorTransform; + + component1 = this.components[0]; + component2 = this.components[1]; + component3 = this.components[2]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)]; + component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + if (!colorTransform) { + R = component1Line[0 | (x * component1.scaleX * scaleX)]; + G = component2Line[0 | (x * component2.scaleX * scaleX)]; + B = component3Line[0 | (x * component3.scaleX * scaleX)]; + } else { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + Cb = component2Line[0 | (x * component2.scaleX * scaleX)]; + Cr = component3Line[0 | (x * component3.scaleX * scaleX)]; + + R = clampTo8bit(Y + 1.402 * (Cr - 128)); + G = clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + B = clampTo8bit(Y + 1.772 * (Cb - 128)); + } + + data[offset++] = R; + data[offset++] = G; + data[offset++] = B; + } + } + break; + case 4: + if (!this.adobe) + throw new Error('Unsupported color mode (4 components)'); + // The default transform for four components is false + colorTransform = false; + // The adobe transform marker overrides any previous setting + if (this.adobe && this.adobe.transformCode) + colorTransform = true; + else if (typeof this.opts.colorTransform !== 'undefined') + colorTransform = !!this.opts.colorTransform; + + component1 = this.components[0]; + component2 = this.components[1]; + component3 = this.components[2]; + component4 = this.components[3]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)]; + component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)]; + component4Line = component4.lines[0 | (y * component4.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + if (!colorTransform) { + C = component1Line[0 | (x * component1.scaleX * scaleX)]; + M = component2Line[0 | (x * component2.scaleX * scaleX)]; + Ye = component3Line[0 | (x * component3.scaleX * scaleX)]; + K = component4Line[0 | (x * component4.scaleX * scaleX)]; + } else { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + Cb = component2Line[0 | (x * component2.scaleX * scaleX)]; + Cr = component3Line[0 | (x * component3.scaleX * scaleX)]; + K = component4Line[0 | (x * component4.scaleX * scaleX)]; + + C = 255 - clampTo8bit(Y + 1.402 * (Cr - 128)); + M = 255 - clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + Ye = 255 - clampTo8bit(Y + 1.772 * (Cb - 128)); + } + data[offset++] = 255-C; + data[offset++] = 255-M; + data[offset++] = 255-Ye; + data[offset++] = 255-K; + } + } + break; + default: + throw new Error('Unsupported color mode'); + } + return data; + }, + copyToImageData: function copyToImageData(imageData, formatAsRGBA) { + var width = imageData.width, height = imageData.height; + var imageDataArray = imageData.data; + var data = this.getData(width, height); + var i = 0, j = 0, x, y; + var Y, K, C, M, R, G, B; + switch (this.components.length) { + case 1: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + Y = data[i++]; + + imageDataArray[j++] = Y; + imageDataArray[j++] = Y; + imageDataArray[j++] = Y; + if (formatAsRGBA) { + imageDataArray[j++] = 255; + } + } + } + break; + case 3: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + R = data[i++]; + G = data[i++]; + B = data[i++]; + + imageDataArray[j++] = R; + imageDataArray[j++] = G; + imageDataArray[j++] = B; + if (formatAsRGBA) { + imageDataArray[j++] = 255; + } + } + } + break; + case 4: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + C = data[i++]; + M = data[i++]; + Y = data[i++]; + K = data[i++]; + + R = 255 - clampTo8bit(C * (1 - K / 255) + K); + G = 255 - clampTo8bit(M * (1 - K / 255) + K); + B = 255 - clampTo8bit(Y * (1 - K / 255) + K); + + imageDataArray[j++] = R; + imageDataArray[j++] = G; + imageDataArray[j++] = B; + if (formatAsRGBA) { + imageDataArray[j++] = 255; + } + } + } + break; + default: + throw new Error('Unsupported color mode'); + } + } + }; + + + // We cap the amount of memory used by jpeg-js to avoid unexpected OOMs from untrusted content. + var totalBytesAllocated = 0; + var maxMemoryUsageBytes = 0; + function requestMemoryAllocation(increaseAmount = 0) { + var totalMemoryImpactBytes = totalBytesAllocated + increaseAmount; + if (totalMemoryImpactBytes > maxMemoryUsageBytes) { + var exceededAmount = Math.ceil((totalMemoryImpactBytes - maxMemoryUsageBytes) / 1024 / 1024); + throw new Error(`maxMemoryUsageInMB limit exceeded by at least ${exceededAmount}MB`); + } + + totalBytesAllocated = totalMemoryImpactBytes; + } + + constructor.resetMaxMemoryUsage = function (maxMemoryUsageBytes_) { + totalBytesAllocated = 0; + maxMemoryUsageBytes = maxMemoryUsageBytes_; + }; + + constructor.getBytesAllocated = function () { + return totalBytesAllocated; + }; + + constructor.requestMemoryAllocation = requestMemoryAllocation; + + return constructor; +})(); + +if (typeof module !== 'undefined') { + module.exports = decode; +} else if (typeof window !== 'undefined') { + window['jpeg-js'] = window['jpeg-js'] || {}; + window['jpeg-js'].decode = decode; +} + +function decode(jpegData, userOpts = {}) { + var defaultOpts = { + // "undefined" means "Choose whether to transform colors based on the image’s color model." + colorTransform: undefined, + useTArray: false, + formatAsRGBA: true, + tolerantDecoding: true, + maxResolutionInMP: 100, // Don't decode more than 100 megapixels + maxMemoryUsageInMB: 512, // Don't decode if memory footprint is more than 512MB + }; + + var opts = {...defaultOpts, ...userOpts}; + var arr = new Uint8Array(jpegData); + var decoder = new JpegImage(); + decoder.opts = opts; + // If this constructor ever supports async decoding this will need to be done differently. + // Until then, treating as singleton limit is fine. + JpegImage.resetMaxMemoryUsage(opts.maxMemoryUsageInMB * 1024 * 1024); + decoder.parse(arr); + + var channels = (opts.formatAsRGBA) ? 4 : 3; + var bytesNeeded = decoder.width * decoder.height * channels; + try { + JpegImage.requestMemoryAllocation(bytesNeeded); + var image = { + width: decoder.width, + height: decoder.height, + exifBuffer: decoder.exifBuffer, + data: opts.useTArray ? + new Uint8Array(bytesNeeded) : + new Buffer(bytesNeeded) + }; + if(decoder.comments.length > 0) { + image["comments"] = decoder.comments; + } + } catch (err){ + if (err instanceof RangeError){ + throw new Error("Could not allocate enough memory for the image. " + + "Required: " + bytesNeeded); + } else { + throw err; + } + } + + decoder.copyToImageData(image, opts.formatAsRGBA); + + return image; +} + +}).call(this)}).call(this,require("buffer").Buffer) +},{"buffer":147}],67:[function(require,module,exports){ +(function (Buffer){(function (){ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* +JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + +Basic GUI blocking jpeg encoder +*/ + +var btoa = btoa || function(buf) { + return new Buffer(buf).toString('base64'); +}; + +function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + //byteout.push(clt[value]); // write char directly instead of converting later + byteout.push(value); + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeAPP1(exifBuffer) { + if (!exifBuffer) return; + + writeWord(0xFFE1); // APP1 marker + + if (exifBuffer[0] === 0x45 && + exifBuffer[1] === 0x78 && + exifBuffer[2] === 0x69 && + exifBuffer[3] === 0x66) { + // Buffer already starts with EXIF, just use it directly + writeWord(exifBuffer.length + 2); // length is buffer + length itself! + } else { + // Buffer doesn't start with EXIF, write it for them + writeWord(exifBuffer.length + 5 + 2); // length is buffer + EXIF\0 + length itself! + writeByte(0x45); // E + writeByte(0x78); // X + writeByte(0x69); // I + writeByte(0x66); // F + writeByte(0); // = "EXIF",'\0' + } + + for (var i = 0; i < exifBuffer.length; i++) { + writeByte(exifBuffer[i]); + } + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeAPP1(image.exifBuffer); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + if (typeof module === 'undefined') return new Uint8Array(byteout); + return new Buffer(byteout); + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + var duration = new Date().getTime() - time_start; + //console.log('Encoding time: '+ duration + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + //console.log('Quality set to: '+quality +'%'); + } + + function init(){ + var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + var duration = new Date().getTime() - time_start; + //console.log('Initialization '+ duration + 'ms'); + } + + init(); + +}; + +if (typeof module !== 'undefined') { + module.exports = encode; +} else if (typeof window !== 'undefined') { + window['jpeg-js'] = window['jpeg-js'] || {}; + window['jpeg-js'].encode = encode; +} + +function encode(imgData, qu) { + if (typeof qu === 'undefined') qu = 50; + var encoder = new JPEGEncoder(qu); + var data = encoder.encode(imgData, qu); + return { + data: data, + width: imgData.width, + height: imgData.height + }; +} + +// helper function to get the imageData of an existing image on the current page. +function getImageDataFromImage(idOrElement){ + var theImg = (typeof(idOrElement)=='string')? document.getElementById(idOrElement):idOrElement; + var cvs = document.createElement('canvas'); + cvs.width = theImg.width; + cvs.height = theImg.height; + var ctx = cvs.getContext("2d"); + ctx.drawImage(theImg,0,0); + + return (ctx.getImageData(0, 0, cvs.width, cvs.height)); +} + +}).call(this)}).call(this,require("buffer").Buffer) +},{"buffer":147}],68:[function(require,module,exports){ +/* + Lazyness + + Copyright (c) 2018 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +function Lazyness( require_ ) { + if ( ! this || ! ( this instanceof Lazyness ) ) { return new Lazyness( require_ ) ; } + this.require = require_ ; +} + +module.exports = Lazyness ; + + + +// Define a lazy property +Lazyness.property = ( object , property , fn , enumerable ) => { + Object.defineProperty( object , property , { + configurable: true , + enumerable: !! enumerable , + + // Must be a function, not => + get: function() { + var value = fn( object , property ) ; + + Object.defineProperty( object , property , { + configurable: true , + enumerable: !! enumerable , + writable: false , + value: value + } ) ; + + return value ; + } + } ) ; + + return object ; +} ; + +Lazyness.prototype.property = Lazyness.property ; + + + +// Multiple properties at once in a property object +Lazyness.properties = ( object , properties , enumerable ) => { + Object.keys( properties ).forEach( property => { + Lazyness.property( object , property , properties[ property ] , enumerable ) ; + } ) ; + + return object ; +} ; + +Lazyness.prototype.properties = Lazyness.properties ; + + + +// Define a lazy property for instances (call it on a prototype) +// Almost the same than .property() except than it use 'this' in the getter instead of 'object' +Lazyness.instanceProperty = ( object , property , fn , enumerable ) => { + Object.defineProperty( object , property , { + configurable: true , + enumerable: !! enumerable , + + // Must be a function, not => + get: function() { + var value = fn( this , property ) ; + + Object.defineProperty( this , property , { + configurable: true , + enumerable: !! enumerable , + writable: false , + value: value + } ) ; + + return value ; + } + } ) ; + + return object ; +} ; + +Lazyness.prototype.instanceProperty = Lazyness.instanceProperty ; + + + +// Multiple properties for instances (call it on a prototype) +Lazyness.instanceProperties = ( object , properties , enumerable ) => { + Object.keys( properties ).forEach( property => { + Lazyness.property( object , property , properties[ property ] , enumerable ) ; + } ) ; + + return object ; +} ; + +Lazyness.prototype.instanceProperties = Lazyness.instanceProperties ; + + + +// Lazy value +Lazyness.value = fn => { + var firstTime = true , value ; + + // Must be a function, not => + return function() { + if ( firstTime ) { + value = fn() ; + firstTime = false ; + } + + return value ; + } ; +} ; + +Lazyness.prototype.value = Lazyness.value ; + + + +// Lazy require, return a Proxy +Lazyness.require = function( require_ , moduleId ) { + var firstTime = true , module_ ; + + return new Proxy( ( () => {} ) , { + construct: ( target , args ) => { + if ( firstTime ) { + module_ = require_( moduleId ) ; + firstTime = false ; + } + + return Reflect.construct( module_ , args ) ; + } , + apply: ( target , thisArg , args ) => { + if ( firstTime ) { + module_ = require_( moduleId ) ; + firstTime = false ; + } + + return Reflect.apply( module_ , thisArg , args ) ; + } , + get: ( target , property ) => { + if ( firstTime ) { + module_ = require_( moduleId ) ; + firstTime = false ; + } + + return Reflect.get( module_ , property ) ; + } + } ) ; +} ; + +Lazyness.prototype.require = function( moduleId ) { + return Lazyness.require( this.require , moduleId ) ; +} ; + + + +Lazyness.requireProperty = function( require_ , object , property , moduleId , enumerable ) { + Object.defineProperty( object , property , { + configurable: true , + enumerable: !! enumerable , + + // Must be a function, not => + get: function() { + var module_ = require_( moduleId ) ; + + Object.defineProperty( object , property , { + configurable: true , + enumerable: !! enumerable , + writable: false , + value: module_ + } ) ; + + return module_ ; + } + } ) ; + + return object ; +} ; + +Lazyness.prototype.requireProperty = function( object , property , moduleId , enumerable ) { + return Lazyness.requireProperty( this.require , object , property , moduleId , enumerable ) ; +} ; + + + +// Multiple properties at once in a property object +Lazyness.requireProperties = function( require_ , object , properties , enumerable ) { + Object.keys( properties ).forEach( property => { + Lazyness.requireProperty( require_ , object , property , properties[ property ] , enumerable ) ; + } ) ; + + return object ; +} ; + +Lazyness.prototype.requireProperties = function( object , properties , enumerable ) { + return Lazyness.requireProperties( this.require , object , properties , enumerable ) ; +} ; + + +},{}],69:[function(require,module,exports){ +"use strict" + +var ndarray = require("ndarray") +var do_convert = require("./doConvert.js") + +module.exports = function convert(arr, result) { + var shape = [], c = arr, sz = 1 + while(Array.isArray(c)) { + shape.push(c.length) + sz *= c.length + c = c[0] + } + if(shape.length === 0) { + return ndarray() + } + if(!result) { + result = ndarray(new Float64Array(sz), shape) + } + do_convert(result, arr) + return result +} + +},{"./doConvert.js":70,"ndarray":71}],70:[function(require,module,exports){ +module.exports=require('cwise-compiler')({"args":["array","scalar","index"],"pre":{"body":"{}","args":[],"thisVars":[],"localVars":[]},"body":{"body":"{\nvar _inline_1_v=_inline_1_arg1_,_inline_1_i\nfor(_inline_1_i=0;_inline_1_i<_inline_1_arg2_.length-1;++_inline_1_i) {\n_inline_1_v=_inline_1_v[_inline_1_arg2_[_inline_1_i]]\n}\n_inline_1_arg0_=_inline_1_v[_inline_1_arg2_[_inline_1_arg2_.length-1]]\n}","args":[{"name":"_inline_1_arg0_","lvalue":true,"rvalue":false,"count":1},{"name":"_inline_1_arg1_","lvalue":false,"rvalue":true,"count":1},{"name":"_inline_1_arg2_","lvalue":false,"rvalue":true,"count":4}],"thisVars":[],"localVars":["_inline_1_i","_inline_1_v"]},"post":{"body":"{}","args":[],"thisVars":[],"localVars":[]},"funcName":"convert","blockSize":64}) + +},{"cwise-compiler":60}],71:[function(require,module,exports){ +var iota = require("iota-array") +var isBuffer = require("is-buffer") + +var hasTypedArrays = ((typeof Float64Array) !== "undefined") + +function compare1st(a, b) { + return a[0] - b[0] +} + +function order() { + var stride = this.stride + var terms = new Array(stride.length) + var i + for(i=0; iMath.abs(this.stride[1]))?[1,0]:[0,1]}})") + } else if(dimension === 3) { + code.push( +"var s0=Math.abs(this.stride[0]),s1=Math.abs(this.stride[1]),s2=Math.abs(this.stride[2]);\ +if(s0>s1){\ +if(s1>s2){\ +return [2,1,0];\ +}else if(s0>s2){\ +return [1,2,0];\ +}else{\ +return [1,0,2];\ +}\ +}else if(s0>s2){\ +return [2,0,1];\ +}else if(s2>s1){\ +return [0,1,2];\ +}else{\ +return [0,2,1];\ +}}})") + } + } else { + code.push("ORDER})") + } + } + + //view.set(i0, ..., v): + code.push( +"proto.set=function "+className+"_set("+args.join(",")+",v){") + if(useGetters) { + code.push("return this.data.set("+index_str+",v)}") + } else { + code.push("return this.data["+index_str+"]=v}") + } + + //view.get(i0, ...): + code.push("proto.get=function "+className+"_get("+args.join(",")+"){") + if(useGetters) { + code.push("return this.data.get("+index_str+")}") + } else { + code.push("return this.data["+index_str+"]}") + } + + //view.index: + code.push( + "proto.index=function "+className+"_index(", args.join(), "){return "+index_str+"}") + + //view.hi(): + code.push("proto.hi=function "+className+"_hi("+args.join(",")+"){return new "+className+"(this.data,"+ + indices.map(function(i) { + return ["(typeof i",i,"!=='number'||i",i,"<0)?this.shape[", i, "]:i", i,"|0"].join("") + }).join(",")+","+ + indices.map(function(i) { + return "this.stride["+i + "]" + }).join(",")+",this.offset)}") + + //view.lo(): + var a_vars = indices.map(function(i) { return "a"+i+"=this.shape["+i+"]" }) + var c_vars = indices.map(function(i) { return "c"+i+"=this.stride["+i+"]" }) + code.push("proto.lo=function "+className+"_lo("+args.join(",")+"){var b=this.offset,d=0,"+a_vars.join(",")+","+c_vars.join(",")) + for(var i=0; i=0){\ +d=i"+i+"|0;\ +b+=c"+i+"*d;\ +a"+i+"-=d}") + } + code.push("return new "+className+"(this.data,"+ + indices.map(function(i) { + return "a"+i + }).join(",")+","+ + indices.map(function(i) { + return "c"+i + }).join(",")+",b)}") + + //view.step(): + code.push("proto.step=function "+className+"_step("+args.join(",")+"){var "+ + indices.map(function(i) { + return "a"+i+"=this.shape["+i+"]" + }).join(",")+","+ + indices.map(function(i) { + return "b"+i+"=this.stride["+i+"]" + }).join(",")+",c=this.offset,d=0,ceil=Math.ceil") + for(var i=0; i=0){c=(c+this.stride["+i+"]*i"+i+")|0}else{a.push(this.shape["+i+"]);b.push(this.stride["+i+"])}") + } + code.push("var ctor=CTOR_LIST[a.length+1];return ctor(this.data,a,b,c)}") + + //Add return statement + code.push("return function construct_"+className+"(data,shape,stride,offset){return new "+className+"(data,"+ + indices.map(function(i) { + return "shape["+i+"]" + }).join(",")+","+ + indices.map(function(i) { + return "stride["+i+"]" + }).join(",")+",offset)}") + + //Compile procedure + var procedure = new Function("CTOR_LIST", "ORDER", code.join("\n")) + return procedure(CACHED_CONSTRUCTORS[dtype], order) +} + +function arrayDType(data) { + if(isBuffer(data)) { + return "buffer" + } + if(hasTypedArrays) { + switch(Object.prototype.toString.call(data)) { + case "[object Float64Array]": + return "float64" + case "[object Float32Array]": + return "float32" + case "[object Int8Array]": + return "int8" + case "[object Int16Array]": + return "int16" + case "[object Int32Array]": + return "int32" + case "[object Uint8Array]": + return "uint8" + case "[object Uint16Array]": + return "uint16" + case "[object Uint32Array]": + return "uint32" + case "[object Uint8ClampedArray]": + return "uint8_clamped" + case "[object BigInt64Array]": + return "bigint64" + case "[object BigUint64Array]": + return "biguint64" + } + } + if(Array.isArray(data)) { + return "array" + } + return "generic" +} + +var CACHED_CONSTRUCTORS = { + "float32":[], + "float64":[], + "int8":[], + "int16":[], + "int32":[], + "uint8":[], + "uint16":[], + "uint32":[], + "array":[], + "uint8_clamped":[], + "bigint64": [], + "biguint64": [], + "buffer":[], + "generic":[] +} + +;(function() { + for(var id in CACHED_CONSTRUCTORS) { + CACHED_CONSTRUCTORS[id].push(compileConstructor(id, -1)) + } +}); + +function wrappedNDArrayCtor(data, shape, stride, offset) { + if(data === undefined) { + var ctor = CACHED_CONSTRUCTORS.array[0] + return ctor([]) + } else if(typeof data === "number") { + data = [data] + } + if(shape === undefined) { + shape = [ data.length ] + } + var d = shape.length + if(stride === undefined) { + stride = new Array(d) + for(var i=d-1, sz=1; i>=0; --i) { + stride[i] = sz + sz *= shape[i] + } + } + if(offset === undefined) { + offset = 0 + for(var i=0; i { + this.listeners[ eventName ] = from.listeners[ eventName ].slice() ; + } ) ; + + // Copy all contexts + Object.keys( from.contexts ).forEach( contextName => { + var context = from.contexts[ contextName ] ; + this.contexts[ contextName ] = { + nice: context.nice , + ready: true , + status: context.status , + serial: context.serial , + scopes: {} + } ; + } ) ; + } +} ; + + + +NextGenEvents.initFrom = function( from ) { + if ( ! from.__ngev ) { NextGenEvents.init.call( from ) ; } + + Object.defineProperty( this , '__ngev' , { + configurable: true , + value: new NextGenEvents.Internal( from.__ngev ) + } ) ; +} ; + + + +/* + Merge listeners of duplicated event bus: + * listeners that are present locally but not in all foreigner are removed (one of the foreigner has removed it) + * listeners that are not present locally but present in at least one foreigner are copied + + Not sure if it will ever go public, it was a very specific use-case (Spellcast). +*/ +NextGenEvents.mergeListeners = function( foreigners ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + + // Backup the current listeners... + var oldListeners = this.__ngev.listeners ; + + + // Reset listeners... + this.__ngev.listeners = {} ; + + Object.keys( oldListeners ).forEach( eventName => { + this.__ngev.listeners[ eventName ] = [] ; + } ) ; + + foreigners.forEach( foreigner => { + if ( ! foreigner.__ngev ) { NextGenEvents.init.call( foreigner ) ; } + + Object.keys( foreigner.__ngev.listeners ).forEach( eventName => { + if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } + } ) ; + } ) ; + + + // Now we can scan by eventName first + Object.keys( this.__ngev.listeners ).forEach( eventName => { + var i , iMax , blacklist = [] ; + + // First pass: find all removed listeners and add them to the blacklist + if ( oldListeners[ eventName ] ) { + oldListeners[ eventName ].forEach( listener => { + for ( i = 0 , iMax = foreigners.length ; i < iMax ; i ++ ) { + if ( + ! foreigners[ i ].__ngev.listeners[ eventName ] || + foreigners[ i ].__ngev.listeners[ eventName ].indexOf( listener ) === -1 + ) { + blacklist.push( listener ) ; + break ; + } + } + } ) ; + } + + // Second pass: add all listeners still not present and that are not blacklisted + foreigners.forEach( foreigner => { + + foreigner.__ngev.listeners[ eventName ].forEach( listener => { + if ( this.__ngev.listeners[ eventName ].indexOf( listener ) === -1 && blacklist.indexOf( listener ) === -1 ) { + this.__ngev.listeners[ eventName ].push( listener ) ; + } + } ) ; + } ) ; + } ) ; +} ; + + + +// Use it with .bind() +NextGenEvents.filterOutCallback = function( what , currentElement ) { return what !== currentElement ; } ; + + + +// .addListener( eventName , [fn] , [options] ) +NextGenEvents.prototype.addListener = function( eventName , fn , options ) { + var listener , newListenerListeners ; + + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } + + // Argument management + if ( ! eventName || typeof eventName !== 'string' ) { + throw new TypeError( ".addListener(): argument #0 should be a non-empty string" ) ; + } + + if ( typeof fn === 'function' ) { + listener = {} ; + + if ( ! options || typeof options !== 'object' ) { options = {} ; } + } + else if ( options === true && fn && typeof fn === 'object' ) { + // We want to use the current object as the listener object (used by Spellcast's serializer) + options = listener = fn ; + fn = undefined ; + } + else { + options = fn ; + + if ( ! options || typeof options !== 'object' ) { + throw new TypeError( ".addListener(): a function or an object with a 'fn' property which value is a function should be provided" ) ; + } + + fn = undefined ; + listener = {} ; + } + + + listener.fn = fn || options.fn ; + listener.id = options.id !== undefined ? options.id : listener.fn ; + + if ( options.unique ) { + if ( this.__ngev.listeners[ eventName ].find( e => e.id === listener.id ) ) { + // Not unique! Return now! + return ; + } + } + + listener.once = !! options.once ; + listener.async = !! options.async ; + listener.eventObject = !! options.eventObject ; + listener.nice = options.nice !== undefined ? Math.floor( options.nice ) : NextGenEvents.SYNC ; + listener.priority = + options.priority || 0 ; + listener.context = options.context && ( typeof options.context === 'string' || typeof options.context === 'object' ) ? options.context : null ; + + if ( typeof listener.fn !== 'function' ) { + throw new TypeError( ".addListener(): a function or an object with a 'fn' property which value is a function should be provided" ) ; + } + + // Implicit context creation + if ( typeof listener.context === 'string' ) { + listener.context = this.__ngev.contexts[ listener.context ] || this.addListenerContext( listener.context ) ; + } + + // Note: 'newListener' and 'removeListener' event return an array of listener, but not the event name. + // So the event's name can be retrieved in the listener itself. + listener.event = eventName ; + + if ( this.__ngev.listeners.newListener.length ) { + // Extra care should be taken with the 'newListener' event, we should avoid recursion + // in the case that eventName === 'newListener', but inside a 'newListener' listener, + // .listenerCount() should report correctly + newListenerListeners = this.__ngev.listeners.newListener.slice() ; + + this.__ngev.listeners[ eventName ].push( listener ) ; + + // Return an array, because one day, .addListener() may support multiple event addition at once, + // e.g.: .addListener( { request: onRequest, close: onClose, error: onError } ) ; + NextGenEvents.emitEvent( { + emitter: this , + name: 'newListener' , + args: [ [ listener ] ] , + listeners: newListenerListeners + } ) ; + + if ( this.__ngev.states[ eventName ] ) { NextGenEvents.emitToOneListener( this.__ngev.states[ eventName ] , listener ) ; } + + return this ; + } + + this.__ngev.listeners[ eventName ].push( listener ) ; + + if ( this.__ngev.hasListenerPriority ) { + // order higher priority first + this.__ngev.listeners[ eventName ].sort( ( a , b ) => b.priority - a.priority ) ; + } + + if ( this.__ngev.listeners[ eventName ].length === this.__ngev.maxListeners + 1 ) { + process.emitWarning( + "Possible NextGenEvents memory leak detected. " + this.__ngev.listeners[ eventName ].length + ' ' + + eventName + " listeners added. Use emitter.setMaxListeners() to increase limit" , + { type: "MaxListenersExceededWarning" } + ) ; + } + + if ( this.__ngev.states[ eventName ] ) { NextGenEvents.emitToOneListener( this.__ngev.states[ eventName ] , listener ) ; } + + return this ; +} ; + +NextGenEvents.prototype.on = NextGenEvents.prototype.addListener ; + + + +// Short-hand +// .once( eventName , [fn] , [options] ) +NextGenEvents.prototype.once = function( eventName , fn , options ) { + if ( fn && typeof fn === 'object' ) { fn.once = true ; } + else if ( options && typeof options === 'object' ) { options.once = true ; } + else { options = { once: true } ; } + + return this.addListener( eventName , fn , options ) ; +} ; + + + +// .waitFor( eventName ) +// A Promise-returning .once() variant, only the first arg is returned +NextGenEvents.prototype.waitFor = function( eventName ) { + return new Promise( resolve => { + this.addListener( eventName , ( firstArg ) => resolve( firstArg ) , { once: true } ) ; + } ) ; +} ; + + + +// .waitForAll( eventName ) +// A Promise-returning .once() variant, all args are returned as an array +NextGenEvents.prototype.waitForAll = function( eventName ) { + return new Promise( resolve => { + this.addListener( eventName , ( ... args ) => resolve( args ) , { once: true } ) ; + } ) ; +} ; + + + +NextGenEvents.prototype.removeListener = function( eventName , id ) { + if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".removeListener(): argument #0 should be a non-empty string" ) ; } + + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + + var listeners = this.__ngev.listeners[ eventName ] ; + if ( ! listeners || ! listeners.length ) { return this ; } + + var i , removedListeners , removeCount = 0 , + length = listeners.length , + hasRemoveListener = this.__ngev.listeners.removeListener.length ; + + if ( hasRemoveListener ) { removedListeners = [] ; } + + // In-place remove (from the listener array) + for ( i = 0 ; i < length ; i ++ ) { + if ( listeners[ i ].id === id ) { + removeCount ++ ; + if ( hasRemoveListener ) { removedListeners.push( listeners[ i ] ) ; } + } + else if ( removeCount ) { + listeners[ i - removeCount ] = listeners[ i ] ; + } + } + + // Adjust the length + if ( removeCount ) { listeners.length -= removeCount ; } + + if ( hasRemoveListener && removedListeners.length ) { + this.emit( 'removeListener' , removedListeners ) ; + } + + return this ; +} ; + +NextGenEvents.prototype.off = NextGenEvents.prototype.removeListener ; + + + +NextGenEvents.prototype.removeAllListeners = function( eventName ) { + var removedListeners ; + + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + + if ( eventName ) { + // Remove all listeners for a particular event + + if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".removeAllListeners(): argument #0 should be undefined or a non-empty string" ) ; } + + if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } + + removedListeners = this.__ngev.listeners[ eventName ] ; + this.__ngev.listeners[ eventName ] = [] ; + + if ( removedListeners.length && this.__ngev.listeners.removeListener.length ) { + this.emit( 'removeListener' , removedListeners ) ; + } + } + else { + // Remove all listeners for any events + // 'removeListener' listeners cannot be triggered: they are already deleted + this.__ngev.listeners = { + // Special events + error: [] , + interrupt: [] , + newListener: [] , + removeListener: [] + } ; + } + + return this ; +} ; + + + +NextGenEvents.listenerWrapper = function( listener , event , contextScope , serial , nice ) { + var returnValue , listenerCallback , + eventMaster = event.master || event , + interruptible = !! event.master || event.emitter.__ngev.interruptible ; + + if ( eventMaster.interrupt ) { return ; } + + if ( listener.async ) { + if ( contextScope ) { + contextScope.ready = ! serial ; + } + + if ( nice < 0 ) { + if ( globalData.recursions >= -nice ) { + event.emitter.__ngev.desync( NextGenEvents.listenerWrapper.bind( undefined , listener , event , contextScope , serial , NextGenEvents.SYNC ) ) ; + return ; + } + } + else { + setTimeout( NextGenEvents.listenerWrapper.bind( undefined , listener , event , contextScope , serial , NextGenEvents.SYNC ) , nice ) ; + return ; + } + + listenerCallback = ( arg ) => { + + eventMaster.listenersDone ++ ; + + // Async interrupt + if ( arg && interruptible && ! eventMaster.interrupt && event.name !== 'interrupt' ) { + eventMaster.interrupt = arg ; + + if ( eventMaster.callback ) { NextGenEvents.emitCallback( event ) ; } + + event.emitter.emit( 'interrupt' , eventMaster.interrupt ) ; + } + else if ( eventMaster.listenersDone >= eventMaster.listeners.length && eventMaster.callback ) { + NextGenEvents.emitCallback( event ) ; + } + + // Process the queue if serialized + if ( serial ) { NextGenEvents.processScopeQueue( contextScope , true , true ) ; } + } ; + + if ( listener.eventObject ) { listener.fn( event , listenerCallback ) ; } + else { returnValue = listener.fn( ... event.args , listenerCallback ) ; } + } + else { + if ( nice < 0 ) { + if ( globalData.recursions >= -nice ) { + event.emitter.__ngev.desync( NextGenEvents.listenerWrapper.bind( undefined , listener , event , contextScope , serial , NextGenEvents.SYNC ) ) ; + return ; + } + } + else { + setTimeout( NextGenEvents.listenerWrapper.bind( undefined , listener , event , contextScope , serial , NextGenEvents.SYNC ) , nice ) ; + return ; + } + + if ( listener.eventObject ) { listener.fn( event ) ; } + else { returnValue = listener.fn( ... event.args ) ; } + + eventMaster.listenersDone ++ ; + } + + // Interrupt if non-falsy return value, if the emitter is interruptible, not already interrupted (emit once), + // and not within an 'interrupt' event. + if ( returnValue && interruptible && ! eventMaster.interrupt && event.name !== 'interrupt' ) { + eventMaster.interrupt = returnValue ; + + if ( eventMaster.callback ) { NextGenEvents.emitCallback( event ) ; } + + event.emitter.emit( 'interrupt' , eventMaster.interrupt ) ; + } + else if ( eventMaster.listenersDone >= eventMaster.listeners.length && eventMaster.callback ) { + NextGenEvents.emitCallback( event ) ; + } +} ; + + + +// A unique event ID +var nextEventId = 0 ; + + + +/* + emit( [nice] , eventName , [arg1] , [arg2] , [...] , [emitCallback] ) +*/ +NextGenEvents.prototype.emit = function( ... args ) { + var event = NextGenEvents.createEvent( this , ... args ) ; + return NextGenEvents.emitEvent( event ) ; +} ; + + + +// For performance, do not emit if there is no listener for that event, +// do not even return an Event object, do not throw if the event name is error, +// or whatever the .emit() process could do when there is no listener. +NextGenEvents.prototype.emitIfListener = function( ... args ) { + var eventName = typeof args[ 0 ] === 'number' ? args[ 1 ] : args[ 0 ] ; + if ( ! this.__ngev || ! this.__ngev.listeners[ eventName ] || ! this.__ngev.listeners[ eventName ].length ) { return null ; } + var event = NextGenEvents.createEvent( this , ... args ) ; + return NextGenEvents.emitEvent( event ) ; +} ; + + + +NextGenEvents.prototype.waitForEmit = function( ... args ) { + return new Promise( resolve => { + this.emit( ... args , ( interrupt ) => resolve( interrupt ) ) ; + } ) ; +} ; + + + +// Create an event object +NextGenEvents.createEvent = function( emitter , ... args ) { + var event = { + emitter: emitter , + interrupt: null , + master: null , // For grouped-correlated events + sync: true + } ; + + // Arguments handling + if ( typeof args[ 0 ] === 'number' ) { + event.nice = Math.floor( args[ 0 ] ) ; + event.name = args[ 1 ] ; + + if ( ! event.name || typeof event.name !== 'string' ) { + throw new TypeError( ".emit(): when argument #0 is a number, argument #1 should be a non-empty string" ) ; + } + + if ( typeof args[ args.length - 1 ] === 'function' ) { + event.callback = args[ args.length - 1 ] ; + event.args = args.slice( 2 , -1 ) ; + } + else { + event.args = args.slice( 2 ) ; + } + } + else { + //event.nice = emitter.__ngev.nice ; + event.name = args[ 0 ] ; + + if ( ! event.name || typeof event.name !== 'string' ) { + throw new TypeError( ".emit(): argument #0 should be an number or a non-empty string" ) ; + } + + if ( typeof args[ args.length - 1 ] === 'function' ) { + event.callback = args[ args.length - 1 ] ; + event.args = args.slice( 1 , -1 ) ; + } + else { + event.args = args.slice( 1 ) ; + } + } + + return event ; +} ; + + + +/* + At this stage, 'event' should be an object having those properties: + * emitter: the event emitter + * name: the event name + * args: array, the arguments of the event + * nice: (optional) nice value + * callback: (optional) a callback for emit + * listeners: (optional) override the listeners array stored in __ngev +*/ +NextGenEvents.emitEvent = function( event ) { + // /!\ Any change here *MUST* be reflected to NextGenEvents.emitIntricatedEvents() /!\ + var self = event.emitter , + i , iMax , count = 0 , state , removedListeners ; + + if ( ! self.__ngev ) { NextGenEvents.init.call( self ) ; } + + state = self.__ngev.states[ event.name ] ; + + // This is a state event, register it now! + if ( state !== undefined ) { + if ( state && event.args.length === state.args.length && + event.args.every( ( arg , index ) => arg === state.args[ index ] ) ) { + // The emitter is already in this exact state, skip it now! + return ; + } + + // Unset all states of that group + self.__ngev.stateGroups[ event.name ].forEach( ( eventName ) => { + self.__ngev.states[ eventName ] = null ; + } ) ; + + self.__ngev.states[ event.name ] = event ; + } + + if ( ! self.__ngev.listeners[ event.name ] ) { self.__ngev.listeners[ event.name ] = [] ; } + + event.id = nextEventId ++ ; + event.listenersDone = 0 ; + + if ( event.nice === undefined || event.nice === null ) { event.nice = self.__ngev.nice ; } + + // Trouble arise when a listener is removed from another listener, while we are still in the loop. + // So we have to COPY the listener array right now! + if ( ! event.listeners ) { event.listeners = self.__ngev.listeners[ event.name ].slice() ; } + + // Increment globalData.recursions + globalData.recursions ++ ; + event.depth = self.__ngev.depth ++ ; + removedListeners = [] ; + + try { + // Emit the event to all listeners! + for ( i = 0 , iMax = event.listeners.length ; i < iMax ; i ++ ) { + count ++ ; + NextGenEvents.emitToOneListener( event , event.listeners[ i ] , removedListeners ) ; + } + } + catch ( error ) { + // Catch error, just to decrement globalData.recursions, re-throw after that... + globalData.recursions -- ; + self.__ngev.depth -- ; + throw error ; + } + + // Decrement globalData.recursions + globalData.recursions -- ; + if ( ! event.callback ) { self.__ngev.depth -- ; } + + // Emit 'removeListener' after calling listeners + if ( removedListeners.length && self.__ngev.listeners.removeListener.length ) { + self.emit( 'removeListener' , removedListeners ) ; + } + + + // 'error' event is a special case: it should be listened for, or it will throw an error + if ( ! count ) { + if ( event.name === 'error' ) { + if ( event.args[ 0 ] ) { throw event.args[ 0 ] ; } + else { throw Error( "Uncaught, unspecified 'error' event." ) ; } + } + + if ( event.callback ) { NextGenEvents.emitCallback( event ) ; } + } + + // Leaving sync mode + event.sync = false ; + + return event ; +} ; + + + +/* + Spellcast-specific: + Send interruptible events with listener-priority across multiple emitters. + If an event is interrupted, all event are interrupted too. + It has limited feature-support: no state-event, no builtin-event (not even 'error'). +*/ +NextGenEvents.emitIntricatedEvents = function( array , callback ) { + var i , iMax , count = 0 , removedListeners ; + + if ( ! Array.isArray( array ) ) { + throw new TypeError( '.emitCorrelatedEvents() argument should be an array' ) ; + } + + var listenerEventRows = [] , + context = { + nice: NextGenEvents.DESYNC , + ready: true , + status: NextGenEvents.CONTEXT_ENABLED , + serial: true , + scopes: {} + } , + master = { + sync: false , + nice: NextGenEvents.DESYNC , + context , + interrupt: null , + listeners: listenerEventRows , // because we need eventMaster.listeners.length + listenersDone: 0 , + depth: 0 , + callback + } ; + + array.forEach( eventParams => { + var event = NextGenEvents.createEvent( ... eventParams ) ; + event.master = master ; + + if ( ! event.emitter.__ngev ) { NextGenEvents.init.call( event.emitter ) ; } + + if ( ! event.emitter.__ngev.listeners[ event.name ] ) { event.emitter.__ngev.listeners[ event.name ] = [] ; } + event.listeners = event.emitter.__ngev.listeners[ event.name ].slice() ; + + event.id = nextEventId ++ ; + //event.listenersDone = 0 ; + //event.nice = master.nice ; + + event.listeners.forEach( listener => listenerEventRows.push( { event , listener } ) ) ; + } ) ; + + + // Sort listeners + listenerEventRows.sort( ( a , b ) => b.listener.priority - a.listener.priority ) ; + + // Increment globalData.recursions + globalData.recursions ++ ; + + removedListeners = [] ; + + try { + // Emit the event to all listeners! + for ( i = 0 , iMax = listenerEventRows.length ; i < iMax ; i ++ ) { + count ++ ; + NextGenEvents.emitToOneListener( listenerEventRows[ i ].event , listenerEventRows[ i ].listener , removedListeners ) ; + } + } + catch ( error ) { + // Catch error, just to decrement globalData.recursions, re-throw after that... + globalData.recursions -- ; + throw error ; + } + + // Decrement globalData.recursions + globalData.recursions -- ; + + if ( ! count && master.callback ) { NextGenEvents.emitCallback( event ) ; } + + // Leaving sync mode + master.sync = false ; +} ; + + + +// If removedListeners is not given, then one-time listener emit the 'removeListener' event, +// if given: that's the caller business to do it +NextGenEvents.emitToOneListener = function( event , listener , removedListeners ) { + var self = event.emitter , + eventMaster = event.master || event , + context = event.master ? event.master.context : listener.context , + contextScope , serial , currentNice , emitRemoveListener = false ; + + if ( context ) { + // If the listener context is disabled... + if ( context.status === NextGenEvents.CONTEXT_DISABLED ) { return ; } + + // The nice value for this listener... + currentNice = Math.max( eventMaster.nice , listener.nice , context.nice ) ; + serial = context.serial ; + contextScope = NextGenEvents.getContextScope( context , eventMaster.depth ) ; + } + else { + currentNice = Math.max( eventMaster.nice , listener.nice ) ; + } + + + if ( listener.once ) { + // We should remove the current listener RIGHT NOW because of recursive .emit() issues: + // one listener may eventually fire this very same event synchronously during the current loop. + self.__ngev.listeners[ event.name ] = self.__ngev.listeners[ event.name ].filter( + NextGenEvents.filterOutCallback.bind( undefined , listener ) + ) ; + + if ( removedListeners ) { removedListeners.push( listener ) ; } + else { emitRemoveListener = true ; } + } + + if ( context && ( context.status === NextGenEvents.CONTEXT_QUEUED || ! contextScope.ready ) ) { + // Almost all works should be done by .emit(), and little few should be done by .processScopeQueue() + contextScope.queue.push( { event: event , listener: listener , nice: currentNice } ) ; + } + else { + NextGenEvents.listenerWrapper( listener , event , contextScope , serial , currentNice ) ; + } + + // Emit 'removeListener' after calling the listener + if ( emitRemoveListener && self.__ngev.listeners.removeListener.length ) { + self.emit( 'removeListener' , [ listener ] ) ; + } +} ; + + + +NextGenEvents.emitCallback = function( event ) { + var callback ; + + if ( event.master ) { + callback = event.master.callback ; + delete event.master.callback ; + + if ( event.master.sync ) { + nextTick( () => callback( event.master.interrupt , event ) ) ; + } + else { + callback( event.master.interrupt , event ) ; + } + + return ; + } + + callback = event.callback ; + delete event.callback ; + + if ( event.sync && event.emitter.__ngev.nice !== NextGenEvents.SYNC ) { + // Force desync if global nice value is not SYNC + event.emitter.__ngev.desync( () => { + event.emitter.__ngev.depth -- ; + callback( event.interrupt , event ) ; + } ) ; + } + else { + event.emitter.__ngev.depth -- ; + callback( event.interrupt , event ) ; + } +} ; + + + +NextGenEvents.prototype.listeners = function( eventName ) { + if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".listeners(): argument #0 should be a non-empty string" ) ; } + + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } + + // Do not return the array, shallow copy it + return this.__ngev.listeners[ eventName ].slice() ; +} ; + + + +NextGenEvents.listenerCount = function( emitter , eventName ) { + if ( ! emitter || ! ( emitter instanceof NextGenEvents ) ) { throw new TypeError( ".listenerCount(): argument #0 should be an instance of NextGenEvents" ) ; } + return emitter.listenerCount( eventName ) ; +} ; + + + +NextGenEvents.prototype.listenerCount = function( eventName ) { + if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".listenerCount(): argument #1 should be a non-empty string" ) ; } + if ( ! this.__ngev || ! this.__ngev.listeners[ eventName ] ) { return 0 ; } + return this.__ngev.listeners[ eventName ].length ; +} ; + + + +NextGenEvents.prototype.setNice = function( nice ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + this.__ngev.nice = Math.floor( + nice || 0 ) ; +} ; + + + +NextGenEvents.prototype.desyncUseNextTick = function( useNextTick ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + this.__ngev.desync = useNextTick ? nextTick : setImmediate ; +} ; + + + +NextGenEvents.prototype.setInterruptible = function( isInterruptible ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + this.__ngev.interruptible = !! isInterruptible ; +} ; + + + +NextGenEvents.prototype.setListenerPriority = function( hasListenerPriority ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + this.__ngev.hasListenerPriority = !! hasListenerPriority ; +} ; + + + +// Make two objects share the same event bus +NextGenEvents.share = function( source , target ) { + if ( ! ( source instanceof NextGenEvents ) || ! ( target instanceof NextGenEvents ) ) { + throw new TypeError( 'NextGenEvents.share() arguments should be instances of NextGenEvents' ) ; + } + + if ( ! source.__ngev ) { NextGenEvents.init.call( source ) ; } + + Object.defineProperty( target , '__ngev' , { + configurable: true , + value: source.__ngev + } ) ; +} ; + + + +NextGenEvents.reset = function( emitter ) { + Object.defineProperty( emitter , '__ngev' , { + configurable: true , + value: null + } ) ; +} ; + + + +NextGenEvents.prototype.getMaxListeners = function() { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + return this.__ngev.maxListeners ; +} ; + + + +NextGenEvents.prototype.setMaxListeners = function( n ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + this.__ngev.maxListeners = typeof n === 'number' && ! Number.isNaN( n ) ? Math.floor( n ) : NextGenEvents.defaultMaxListeners ; + return this ; +} ; + + + +// Sometime useful as a no-op callback... +NextGenEvents.noop = () => undefined ; + + + + + +/* Next Gen feature: states! */ + + + +// .defineStates( exclusiveState1 , [exclusiveState2] , [exclusiveState3] , ... ) +NextGenEvents.prototype.defineStates = function( ... states ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + + states.forEach( ( state ) => { + this.__ngev.states[ state ] = null ; + this.__ngev.stateGroups[ state ] = states ; + } ) ; +} ; + + + +NextGenEvents.prototype.hasState = function( state ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + return !! this.__ngev.states[ state ] ; +} ; + + + +NextGenEvents.prototype.getAllStates = function() { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + return Object.keys( this.__ngev.states ).filter( e => this.__ngev.states[ e ] ) ; +} ; + + + + + +/* Next Gen feature: groups! */ + + + +NextGenEvents.groupAddListener = function( emitters , eventName , fn , options ) { + // Manage arguments + if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + fn = fn || options.fn ; + delete options.fn ; + + // Preserve the listener ID, so groupRemoveListener() will work as expected + options.id = options.id || fn ; + + emitters.forEach( ( emitter ) => { + emitter.addListener( eventName , fn.bind( undefined , emitter ) , options ) ; + } ) ; +} ; + +NextGenEvents.groupOn = NextGenEvents.groupAddListener ; + + + +// Once per emitter +NextGenEvents.groupOnce = function( emitters , eventName , fn , options ) { + if ( fn && typeof fn === 'object' ) { fn.once = true ; } + else if ( options && typeof options === 'object' ) { options.once = true ; } + else { options = { once: true } ; } + + return this.groupAddListener( emitters , eventName , fn , options ) ; +} ; + + + +// A Promise-returning .groupOnce() variant, it returns an array with only the first arg for each emitter's event +NextGenEvents.groupWaitFor = function( emitters , eventName ) { + return Promise.all( emitters.map( emitter => emitter.waitFor( eventName ) ) ) ; +} ; + + + +// A Promise-returning .groupOnce() variant, it returns an array of array for each emitter's event +NextGenEvents.groupWaitForAll = function( emitters , eventName ) { + return Promise.all( emitters.map( emitter => emitter.waitForAll( eventName ) ) ) ; +} ; + + + +// Globally once, only one event could be emitted, by the first emitter to emit +NextGenEvents.groupOnceFirst = function( emitters , eventName , fn , options ) { + var fnWrapper , triggered = false ; + + // Manage arguments + if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + fn = fn || options.fn ; + delete options.fn ; + + // Preserve the listener ID, so groupRemoveListener() will work as expected + options.id = options.id || fn ; + + fnWrapper = ( ... args ) => { + if ( triggered ) { return ; } + triggered = true ; + NextGenEvents.groupRemoveListener( emitters , eventName , options.id ) ; + fn( ... args ) ; + } ; + + emitters.forEach( ( emitter ) => { + emitter.once( eventName , fnWrapper.bind( undefined , emitter ) , options ) ; + } ) ; +} ; + + + +// A Promise-returning .groupOnceFirst() variant, only the first arg is returned +NextGenEvents.groupWaitForFirst = function( emitters , eventName ) { + return new Promise( resolve => { + NextGenEvents.groupOnceFirst( emitters , eventName , ( firstArg ) => resolve( firstArg ) ) ; + } ) ; +} ; + + + +// A Promise-returning .groupOnceFirst() variant, all args are returned as an array +NextGenEvents.groupWaitForFirstAll = function( emitters , eventName ) { + return new Promise( resolve => { + NextGenEvents.groupOnceFirst( emitters , eventName , ( ... args ) => resolve( args ) ) ; + } ) ; +} ; + + + +// Globally once, only one event could be emitted, by the last emitter to emit +NextGenEvents.groupOnceLast = function( emitters , eventName , fn , options ) { + var fnWrapper , triggered = false , count = emitters.length ; + + // Manage arguments + if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + fn = fn || options.fn ; + delete options.fn ; + + // Preserve the listener ID, so groupRemoveListener() will work as expected + options.id = options.id || fn ; + + fnWrapper = ( ... args ) => { + if ( triggered ) { return ; } + if ( -- count ) { return ; } + + // So this is the last emitter... + + triggered = true ; + // No need to remove listeners: there are already removed anyway + //NextGenEvents.groupRemoveListener( emitters , eventName , options.id ) ; + fn( ... args ) ; + } ; + + emitters.forEach( ( emitter ) => { + emitter.once( eventName , fnWrapper.bind( undefined , emitter ) , options ) ; + } ) ; +} ; + + + +// A Promise-returning .groupGlobalWaitFor() variant, only the first arg is returned +NextGenEvents.groupWaitForLast = function( emitters , eventName ) { + return new Promise( resolve => { + NextGenEvents.groupOnceLast( emitters , eventName , ( firstArg ) => resolve( firstArg ) ) ; + } ) ; +} ; + + + +// A Promise-returning .groupGlobalWaitFor() variant, all args are returned as an array +NextGenEvents.groupWaitForLastAll = function( emitters , eventName ) { + return new Promise( resolve => { + NextGenEvents.groupOnceLast( emitters , eventName , ( ... args ) => resolve( args ) ) ; + } ) ; +} ; + + + +NextGenEvents.groupRemoveListener = function( emitters , eventName , id ) { + emitters.forEach( ( emitter ) => { + emitter.removeListener( eventName , id ) ; + } ) ; +} ; + +NextGenEvents.groupOff = NextGenEvents.groupRemoveListener ; + + + +NextGenEvents.groupRemoveAllListeners = function( emitters , eventName ) { + emitters.forEach( ( emitter ) => { + emitter.removeAllListeners( eventName ) ; + } ) ; +} ; + + + +NextGenEvents.groupEmit = function( emitters , ... args ) { + var eventName , nice , argStart = 1 , argEnd , count = emitters.length , + callback , callbackWrapper , callbackTriggered = false ; + + if ( typeof args[ args.length - 1 ] === 'function' ) { + argEnd = -1 ; + callback = args[ args.length - 1 ] ; + + callbackWrapper = ( interruption ) => { + if ( callbackTriggered ) { return ; } + + if ( interruption ) { + callbackTriggered = true ; + callback( interruption ) ; + } + else if ( ! -- count ) { + callbackTriggered = true ; + callback() ; + } + } ; + } + + if ( typeof args[ 0 ] === 'number' ) { + argStart = 2 ; + nice = typeof args[ 0 ] ; + } + + eventName = args[ argStart - 1 ] ; + args = args.slice( argStart , argEnd ) ; + + emitters.forEach( ( emitter ) => { + NextGenEvents.emitEvent( { + emitter: emitter , + name: eventName , + args: args , + nice: nice , + callback: callbackWrapper + } ) ; + } ) ; +} ; + + + +NextGenEvents.groupWaitForEmit = function( emitters , ... args ) { + return new Promise( resolve => { + NextGenEvents.groupEmit( emitters , ... args , ( interrupt ) => resolve( interrupt ) ) ; + } ) ; +} ; + + + +NextGenEvents.groupDefineStates = function( emitters , ... args ) { + emitters.forEach( ( emitter ) => { + emitter.defineStates( ... args ) ; + } ) ; +} ; + + + +// Bad names, but since they make their way through the API documentation, +// it should be kept for backward compatibility, but they are DEPRECATED. +NextGenEvents.groupGlobalOnce = NextGenEvents.groupOnceFirst ; +NextGenEvents.groupGlobalOnceAll = NextGenEvents.groupOnceLast ; + + + + + +/* Next Gen feature: contexts! */ + + + +NextGenEvents.CONTEXT_ENABLED = 0 ; +NextGenEvents.CONTEXT_DISABLED = 1 ; +NextGenEvents.CONTEXT_QUEUED = 2 ; + + + +NextGenEvents.prototype.addListenerContext = function( contextName , options ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".addListenerContext(): argument #0 should be a non-empty string" ) ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + + var context = this.__ngev.contexts[ contextName ] ; + + if ( ! context ) { + context = this.__ngev.contexts[ contextName ] = { + nice: NextGenEvents.SYNC , + ready: true , + status: NextGenEvents.CONTEXT_ENABLED , + serial: false , + scopes: {} + } ; + } + + if ( options.nice !== undefined ) { context.nice = Math.floor( options.nice ) ; } + if ( options.status !== undefined ) { context.status = options.status ; } + if ( options.serial !== undefined ) { context.serial = !! options.serial ; } + + return context ; +} ; + + + +NextGenEvents.prototype.getListenerContext = function( contextName ) { + return this.__ngev.contexts[ contextName ] ; +} ; + + + +NextGenEvents.getContextScope = function( context , scopeName ) { + var scope = context.scopes[ scopeName ] ; + + if ( ! scope ) { + scope = context.scopes[ scopeName ] = { + ready: true , + queue: [] + } ; + } + + return scope ; +} ; + + + +NextGenEvents.prototype.disableListenerContext = function( contextName ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".disableListenerContext(): argument #0 should be a non-empty string" ) ; } + if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } + + this.__ngev.contexts[ contextName ].status = NextGenEvents.CONTEXT_DISABLED ; + + return this ; +} ; + + + +NextGenEvents.prototype.enableListenerContext = function( contextName ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".enableListenerContext(): argument #0 should be a non-empty string" ) ; } + if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } + + var context = this.__ngev.contexts[ contextName ] ; + + context.status = NextGenEvents.CONTEXT_ENABLED ; + + Object.values( context.scopes ).forEach( contextScope => { + if ( contextScope.queue.length > 0 ) { NextGenEvents.processScopeQueue( contextScope , context.serial ) ; } + } ) ; + + return this ; +} ; + + + +NextGenEvents.prototype.queueListenerContext = function( contextName ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".queueListenerContext(): argument #0 should be a non-empty string" ) ; } + if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } + + this.__ngev.contexts[ contextName ].status = NextGenEvents.CONTEXT_QUEUED ; + + return this ; +} ; + + + +NextGenEvents.prototype.serializeListenerContext = function( contextName , value ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".serializeListenerContext(): argument #0 should be a non-empty string" ) ; } + if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } + + this.__ngev.contexts[ contextName ].serial = value === undefined ? true : !! value ; + + return this ; +} ; + + + +NextGenEvents.prototype.setListenerContextNice = function( contextName , nice ) { + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".setListenerContextNice(): argument #0 should be a non-empty string" ) ; } + if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } + + this.__ngev.contexts[ contextName ].nice = Math.floor( nice ) ; + + return this ; +} ; + + + +NextGenEvents.prototype.destroyListenerContext = function( contextName ) { + var i , length , context , eventName , newListeners , removedListeners = [] ; + + if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".disableListenerContext(): argument #0 should be a non-empty string" ) ; } + + if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } + + context = this.__ngev.contexts[ contextName ] ; + if ( ! context ) { return ; } + + for ( eventName in this.__ngev.listeners ) { + newListeners = null ; + length = this.__ngev.listeners[ eventName ].length ; + + for ( i = 0 ; i < length ; i ++ ) { + if ( this.__ngev.listeners[ eventName ][ i ].context === context ) { + newListeners = [] ; + removedListeners.push( this.__ngev.listeners[ eventName ][ i ] ) ; + } + else if ( newListeners ) { + newListeners.push( this.__ngev.listeners[ eventName ][ i ] ) ; + } + } + + if ( newListeners ) { this.__ngev.listeners[ eventName ] = newListeners ; } + } + + delete this.__ngev.contexts[ contextName ] ; + + if ( removedListeners.length && this.__ngev.listeners.removeListener.length ) { + this.emit( 'removeListener' , removedListeners ) ; + } + + return this ; +} ; + + + +NextGenEvents.processScopeQueue = function( contextScope , serial , isCompletionCallback ) { + var job , event , eventMaster , emitter ; + + if ( isCompletionCallback ) { contextScope.ready = true ; } + + // Increment recursion + globalData.recursions ++ ; + + while ( contextScope.ready && contextScope.queue.length ) { + job = contextScope.queue.shift() ; + event = job.event ; + eventMaster = event.master || event ; + emitter = event.emitter ; + + // This event has been interrupted, drop it now! + if ( eventMaster.interrupt ) { continue ; } + + NextGenEvents.listenerWrapper( job.listener , event , contextScope , serial , job.nice ) ; + } + + // Decrement recursion + globalData.recursions -- ; +} ; + + + +// Backup for the AsyncTryCatch +NextGenEvents.on = NextGenEvents.prototype.on ; +NextGenEvents.once = NextGenEvents.prototype.once ; +NextGenEvents.off = NextGenEvents.prototype.off ; + + + +if ( global.AsyncTryCatch ) { + NextGenEvents.prototype.asyncTryCatchId = global.AsyncTryCatch.NextGenEvents.length ; + global.AsyncTryCatch.NextGenEvents.push( NextGenEvents ) ; + + if ( global.AsyncTryCatch.substituted ) { + global.AsyncTryCatch.substitute() ; + } +} + + + +// Load Proxy AT THE END (circular require) +NextGenEvents.Proxy = require( './Proxy.js' ) ; + + +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) +},{"../package.json":74,"./Proxy.js":73,"_process":179,"timers":197}],73:[function(require,module,exports){ +/* + Next-Gen Events + + Copyright (c) 2015 - 2019 Cédric Ronvel + + The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +"use strict" ; + + + +function Proxy() { + this.localServices = {} ; + this.remoteServices = {} ; + this.nextAckId = 1 ; +} + +module.exports = Proxy ; + +var NextGenEvents = require( './NextGenEvents.js' ) ; +var MESSAGE_TYPE = 'NextGenEvents/message' ; + +function noop() {} + + + +// Backward compatibility +Proxy.create = ( ... args ) => new Proxy( ... args ) ; + + + +// Add a local service accessible remotely +Proxy.prototype.addLocalService = function( id , emitter , options ) { + this.localServices[ id ] = LocalService.create( this , id , emitter , options ) ; + return this.localServices[ id ] ; +} ; + + + +// Add a remote service accessible locally +Proxy.prototype.addRemoteService = function( id ) { + this.remoteServices[ id ] = RemoteService.create( this , id ) ; + return this.remoteServices[ id ] ; +} ; + + + +// Destroy the proxy +Proxy.prototype.destroy = function() { + Object.keys( this.localServices ).forEach( ( id ) => { + this.localServices[ id ].destroy() ; + delete this.localServices[ id ] ; + } ) ; + + Object.keys( this.remoteServices ).forEach( ( id ) => { + this.remoteServices[ id ].destroy() ; + delete this.remoteServices[ id ] ; + } ) ; + + this.receive = this.send = noop ; +} ; + + + +// Push an event message. +Proxy.prototype.push = function( message ) { + if ( + message.__type !== MESSAGE_TYPE || + ! message.service || typeof message.service !== 'string' || + ! message.event || typeof message.event !== 'string' || + ! message.method + ) { + return ; + } + + switch ( message.method ) { + // Those methods target a remote service + case 'event' : + return this.remoteServices[ message.service ] && this.remoteServices[ message.service ].receiveEvent( message ) ; + case 'ackEmit' : + return this.remoteServices[ message.service ] && this.remoteServices[ message.service ].receiveAckEmit( message ) ; + + // Those methods target a local service + case 'emit' : + return this.localServices[ message.service ] && this.localServices[ message.service ].receiveEmit( message ) ; + case 'listen' : + return this.localServices[ message.service ] && this.localServices[ message.service ].receiveListen( message ) ; + case 'ignore' : + return this.localServices[ message.service ] && this.localServices[ message.service ].receiveIgnore( message ) ; + case 'ackEvent' : + return this.localServices[ message.service ] && this.localServices[ message.service ].receiveAckEvent( message ) ; + + default : + return ; + } +} ; + + + +// This is the method to receive and decode data from the other side of the communication channel, most of time another proxy. +// In most case, this should be overwritten. +Proxy.prototype.receive = function( raw ) { + this.push( raw ) ; +} ; + + + +// This is the method used to send data to the other side of the communication channel, most of time another proxy. +// This MUST be overwritten in any case. +Proxy.prototype.send = function() { + throw new Error( 'The send() method of the Proxy MUST be extended/overwritten' ) ; +} ; + + + +/* Local Service */ + + + +function LocalService( proxy , id , emitter , options ) { return LocalService.create( proxy , id , emitter , options ) ; } +Proxy.LocalService = LocalService ; + + + +LocalService.create = function( proxy , id , emitter , options ) { + var self = Object.create( LocalService.prototype , { + proxy: { value: proxy , enumerable: true } , + id: { value: id , enumerable: true } , + emitter: { value: emitter , writable: true , enumerable: true } , + internalEvents: { value: Object.create( NextGenEvents.prototype ) , writable: true , enumerable: true } , + events: { value: {} , enumerable: true } , + canListen: { value: !! options.listen , writable: true , enumerable: true } , + canEmit: { value: !! options.emit , writable: true , enumerable: true } , + canAck: { value: !! options.ack , writable: true , enumerable: true } , + canRpc: { value: !! options.rpc , writable: true , enumerable: true } , + destroyed: { value: false , writable: true , enumerable: true } + } ) ; + + return self ; +} ; + + + +// Destroy a service +LocalService.prototype.destroy = function() { + Object.keys( this.events ).forEach( ( eventName ) => { + this.emitter.off( eventName , this.events[ eventName ] ) ; + delete this.events[ eventName ] ; + } ) ; + + this.emitter = null ; + this.destroyed = true ; +} ; + + + +// Remote want to emit on the local service +LocalService.prototype.receiveEmit = function( message ) { + if ( this.destroyed || ! this.canEmit || ( message.ack && ! this.canAck ) ) { return ; } + + var event = { + emitter: this.emitter , + name: message.event , + args: message.args || [] + } ; + + if ( message.ack ) { + event.callback = ( interruption ) => { + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'ackEmit' , + ack: message.ack , + event: message.event , + interruption: interruption + } ) ; + } ; + } + + NextGenEvents.emitEvent( event ) ; +} ; + + + +// Remote want to listen to an event of the local service +LocalService.prototype.receiveListen = function( message ) { + if ( this.destroyed || ! this.canListen || ( message.ack && ! this.canAck ) ) { return ; } + + if ( message.ack ) { + if ( this.events[ message.event ] ) { + if ( this.events[ message.event ].ack ) { return ; } + + // There is already an event, but not featuring ack, remove that listener now + this.emitter.off( message.event , this.events[ message.event ] ) ; + } + + this.events[ message.event ] = LocalService.forwardWithAck.bind( this ) ; + this.events[ message.event ].ack = true ; + this.emitter.on( message.event , this.events[ message.event ] , { eventObject: true , async: true } ) ; + } + else { + if ( this.events[ message.event ] ) { + if ( ! this.events[ message.event ].ack ) { return ; } + + // Remote want to downgrade: + // there is already an event, but featuring ack so we remove that listener now + this.emitter.off( message.event , this.events[ message.event ] ) ; + } + + this.events[ message.event ] = LocalService.forward.bind( this ) ; + this.events[ message.event ].ack = false ; + this.emitter.on( message.event , this.events[ message.event ] , { eventObject: true } ) ; + } +} ; + + + +// Remote do not want to listen to that event of the local service anymore +LocalService.prototype.receiveIgnore = function( message ) { + if ( this.destroyed || ! this.canListen ) { return ; } + + if ( ! this.events[ message.event ] ) { return ; } + + this.emitter.off( message.event , this.events[ message.event ] ) ; + this.events[ message.event ] = null ; +} ; + + + +// +LocalService.prototype.receiveAckEvent = function( message ) { + if ( + this.destroyed || ! this.canListen || ! this.canAck || ! message.ack || + ! this.events[ message.event ] || ! this.events[ message.event ].ack + ) { + return ; + } + + this.internalEvents.emit( 'ack' , message ) ; +} ; + + + +// Send an event from the local service to remote +LocalService.forward = function( event ) { + if ( this.destroyed ) { return ; } + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'event' , + event: event.name , + args: event.args + } ) ; +} ; + +LocalService.forward.ack = false ; + + + +// Send an event from the local service to remote, with ACK +LocalService.forwardWithAck = function( event , callback ) { + if ( this.destroyed ) { return ; } + + if ( ! event.callback ) { + // There is no emit callback, no need to ack this one + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'event' , + event: event.name , + args: event.args + } ) ; + + callback() ; + return ; + } + + var triggered = false ; + var ackId = this.proxy.nextAckId ++ ; + + var onAck = ( message ) => { + if ( triggered || message.ack !== ackId ) { return ; } // Not our ack... + //if ( message.event !== event ) { return ; } // Do we care? + triggered = true ; + this.internalEvents.off( 'ack' , onAck ) ; + callback() ; + } ; + + this.internalEvents.on( 'ack' , onAck ) ; + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'event' , + event: event.name , + ack: ackId , + args: event.args + } ) ; +} ; + +LocalService.forwardWithAck.ack = true ; + + + +/* Remote Service */ + + + +function RemoteService( proxy , id ) { return RemoteService.create( proxy , id ) ; } +//RemoteService.prototype = Object.create( NextGenEvents.prototype ) ; +//RemoteService.prototype.constructor = RemoteService ; +Proxy.RemoteService = RemoteService ; + + + +var EVENT_NO_ACK = 1 ; +var EVENT_ACK = 2 ; + + + +RemoteService.create = function( proxy , id ) { + var self = Object.create( RemoteService.prototype , { + proxy: { value: proxy , enumerable: true } , + id: { value: id , enumerable: true } , + // This is the emitter where everything is routed to + emitter: { value: Object.create( NextGenEvents.prototype ) , writable: true , enumerable: true } , + internalEvents: { value: Object.create( NextGenEvents.prototype ) , writable: true , enumerable: true } , + events: { value: {} , enumerable: true } , + destroyed: { value: false , writable: true , enumerable: true } + + /* Useless for instance, unless some kind of service capabilities discovery mechanism exists + canListen: { value: !! options.listen , writable: true , enumerable: true } , + canEmit: { value: !! options.emit , writable: true , enumerable: true } , + canAck: { value: !! options.ack , writable: true , enumerable: true } , + canRpc: { value: !! options.rpc , writable: true , enumerable: true } , + */ + } ) ; + + return self ; +} ; + + + +// Destroy a service +RemoteService.prototype.destroy = function() { + this.emitter.removeAllListeners() ; + this.emitter = null ; + Object.keys( this.events ).forEach( ( eventName ) => { delete this.events[ eventName ] ; } ) ; + this.destroyed = true ; +} ; + + + +// Local code want to emit to remote service +RemoteService.prototype.emit = function( eventName , ... args ) { + if ( this.destroyed ) { return ; } + + var callback , ackId , triggered ; + + if ( typeof eventName === 'number' ) { throw new TypeError( 'Cannot emit with a nice value on a remote service' ) ; } + + if ( typeof args[ args.length - 1 ] !== 'function' ) { + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'emit' , + event: eventName , + args: args + } ) ; + + return ; + } + + callback = args.pop() ; + ackId = this.proxy.nextAckId ++ ; + triggered = false ; + + var onAck = ( message ) => { + if ( triggered || message.ack !== ackId ) { return ; } // Not our ack... + //if ( message.event !== event ) { return ; } // Do we care? + triggered = true ; + this.internalEvents.off( 'ack' , onAck ) ; + callback( message.interruption ) ; + } ; + + this.internalEvents.on( 'ack' , onAck ) ; + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'emit' , + ack: ackId , + event: eventName , + args: args + } ) ; +} ; + + + +// Local code want to listen to an event of remote service +RemoteService.prototype.addListener = function( eventName , fn , options ) { + if ( this.destroyed ) { return ; } + + // Manage arguments the same way NextGenEvents#addListener() does + if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } + if ( ! options || typeof options !== 'object' ) { options = {} ; } + options.fn = fn || options.fn ; + + this.emitter.addListener( eventName , options ) ; + + // No event was added... + if ( ! this.emitter.__ngev.listeners[ eventName ] || ! this.emitter.__ngev.listeners[ eventName ].length ) { return ; } + + // If the event is successfully listened to and was not remotely listened... + if ( options.async && this.events[ eventName ] !== EVENT_ACK ) { + // We need to listen to or upgrade this event + this.events[ eventName ] = EVENT_ACK ; + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'listen' , + ack: true , + event: eventName + } ) ; + } + else if ( ! options.async && ! this.events[ eventName ] ) { + // We need to listen to this event + this.events[ eventName ] = EVENT_NO_ACK ; + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'listen' , + event: eventName + } ) ; + } +} ; + +RemoteService.prototype.on = RemoteService.prototype.addListener ; + +// This is a shortcut to this.addListener() +RemoteService.prototype.once = NextGenEvents.prototype.once ; + + + +// Local code want to ignore an event of remote service +RemoteService.prototype.removeListener = function( eventName , id ) { + if ( this.destroyed ) { return ; } + + this.emitter.removeListener( eventName , id ) ; + + // If no more listener are locally tied to with event and the event was remotely listened... + if ( + ( ! this.emitter.__ngev.listeners[ eventName ] || ! this.emitter.__ngev.listeners[ eventName ].length ) && + this.events[ eventName ] + ) { + this.events[ eventName ] = 0 ; + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'ignore' , + event: eventName + } ) ; + } +} ; + +RemoteService.prototype.off = RemoteService.prototype.removeListener ; + + + +// A remote service sent an event we are listening to, emit on the service representing the remote +RemoteService.prototype.receiveEvent = function( message ) { + if ( this.destroyed || ! this.events[ message.event ] ) { return ; } + + var event = { + emitter: this.emitter , + name: message.event , + args: message.args || [] + } ; + + if ( message.ack ) { + event.callback = () => { + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'ackEvent' , + ack: message.ack , + event: message.event + } ) ; + } ; + } + + NextGenEvents.emitEvent( event ) ; + + var eventName = event.name ; + + // Here we should catch if the event is still listened to ('once' type listeners) + //if ( this.events[ eventName ] ) // not needed, already checked at the begining of the function + if ( ! this.emitter.__ngev.listeners[ eventName ] || ! this.emitter.__ngev.listeners[ eventName ].length ) { + this.events[ eventName ] = 0 ; + + this.proxy.send( { + __type: MESSAGE_TYPE , + service: this.id , + method: 'ignore' , + event: eventName + } ) ; + } +} ; + + + +// +RemoteService.prototype.receiveAckEmit = function( message ) { + if ( this.destroyed || ! message.ack || this.events[ message.event ] !== EVENT_ACK ) { + return ; + } + + this.internalEvents.emit( 'ack' , message ) ; +} ; + + +},{"./NextGenEvents.js":72}],74:[function(require,module,exports){ +module.exports={ + "name": "nextgen-events", + "version": "1.4.0", + "description": "The next generation of events handling for javascript! New: abstract away the network!", + "main": "lib/NextGenEvents.js", + "engines": { + "node": ">=6.0.0" + }, + "directories": { + "test": "test" + }, + "dependencies": {}, + "devDependencies": { + "browserify": "^16.2.2", + "uglify-js-es6": "^2.8.9", + "ws": "^5.1.1" + }, + "scripts": { + "test": "tea-time -R dot" + }, + "repository": { + "type": "git", + "url": "https://github.com/cronvel/nextgen-events.git" + }, + "keywords": [ + "events", + "async", + "emit", + "listener", + "context", + "series", + "serialize", + "namespace", + "proxy", + "network" + ], + "author": "Cédric Ronvel", + "license": "MIT", + "bugs": { + "url": "https://github.com/cronvel/nextgen-events/issues" + }, + "config": { + "tea-time": { + "coverDir": [ + "lib" + ] + } + }, + "copyright": { + "title": "Next-Gen Events", + "years": [ + 2015, + 2019 + ], + "owner": "Cédric Ronvel" + } +} + +},{}],75:[function(require,module,exports){ +module.exports = require('./lib/bitmap'); + +},{"./lib/bitmap":76}],76:[function(require,module,exports){ +(function (Buffer){(function (){ +var Bitmap = module.exports = exports = function(buffer){ + this.buffer = buffer; + this.initialized = false; + + this.fileHeader = null; + this.infoHeader = null; + this.coreHeader = null; + this.colorPalette = null; + this.dataPos = -1; +}; +Bitmap.prototype.CORE_TYPE_WINDOWS_V3 = 40; +Bitmap.prototype.CORE_TYPE_WINDOWS_V4 = 108; +Bitmap.prototype.CORE_TYPE_WINDOWS_V5 = 124; +Bitmap.prototype.CORE_TYPE_OS2_V1 = 12; +Bitmap.prototype.CORE_TYPE_OS2_V2 = 64; +Bitmap.prototype.BITMAPCOREHEADER = Bitmap.prototype.CORE_TYPE_OS2_V1; +Bitmap.prototype.BITMAPINFOHEADER = Bitmap.prototype.CORE_TYPE_WINDOWS_V3; +Bitmap.prototype.BITMAPINFOHEADER2 = Bitmap.prototype.CORE_TYPE_OS2_V2; +Bitmap.prototype.BITMAPV4HEADER = Bitmap.prototype.CORE_TYPE_WINDOWS_V4; +Bitmap.prototype.BITMAPV5HEADER = Bitmap.prototype.CORE_TYPE_WINDOWS_V5; +Bitmap.prototype.COMPRESSION_BI_RGB = 0; +Bitmap.prototype.COMPRESSION_BI_RLE8 = 1; +Bitmap.prototype.COMPRESSION_BI_RLE4 = 2; +Bitmap.prototype.COMPRESSION_BI_BITFIELDS = 3; +Bitmap.prototype.COMPRESSION_BI_JPEG = 4; +Bitmap.prototype.COMPRESSION_BI_PNG = 5; +Bitmap.prototype.BITCOUNT_2 = 1; +Bitmap.prototype.BITCOUNT_16 = 4; +Bitmap.prototype.BITCOUNT_256 = 8; +Bitmap.prototype.BITCOUNT_16bit = 16; +Bitmap.prototype.BITCOUNT_24bit = 24; +Bitmap.prototype.BITCOUNT_32bit = 32; +Bitmap.prototype.init = function(){ + this.readFileHeader(); + this.readInfoHeader(); + this.readCoreHeader(); + this.readColorPalette(); + + this.initDataPos(); + this.initialized = true; +}; +Bitmap.prototype.checkInit = function (){ + if(!this.initialized){ + throw new Error('not initialized'); + } + /* nop */ +}; +Bitmap.prototype.isBitmap = function(){ + this.checkInit(); + + if('BM' == this.fileHeader.bfType){ + return true; + } + return false; +}; +Bitmap.prototype.getData = function (){ + this.checkInit(); + + if(this.COMPRESSION_BI_RGB !== this.coreHeader.__copmression__){ + throw new Error('not supported compression: ' + this.coreHeader.__copmression__); + } + + var bitCount = this.coreHeader.__bitCount__; + var width = this.getWidth(); + var height = this.getHeight(); + + var line = (width * bitCount) / 8; + if(0 != (line % 4)){ + line = ((line / 4) + 1) * 4; + } + + var rgbaData = []; + var dataPos = this.dataPos; + for(var i = 0; i < height; ++i) { + var pos = dataPos + (line * (height - (i + 1))); + var buf = this.buffer.slice(pos, pos + line); + var color = this.mapColor(buf, bitCount); + rgbaData.push(color); + } + return rgbaData; +}; +Bitmap.prototype.getWidth = function (){ + this.checkInit(); + + return this.coreHeader.__width__; +}; +Bitmap.prototype.getHeight = function (){ + this.checkInit(); + + return this.coreHeader.__height__; +}; +Bitmap.prototype.read = function(buf, offset, limit){ + var read = []; + for(var i = offset, len = offset + limit; i < len; ++i){ + read.push(buf.readInt8(i)); + } + return new Buffer(read); +}; +Bitmap.prototype.readFileHeader = function(){ + var bfType = this.read(this.buffer, 0, 2); + var bfSize = this.read(this.buffer, 2, 4); + var bfReserved1 = this.read(this.buffer, 6, 2); + var bfReserved2 = this.read(this.buffer, 8, 2); + var bfOffBits = this.read(this.buffer, 10, 4); + + this.fileHeader = { + bfType: bfType.toString('ascii'), + _bfType: bfType, + bfSize: bfSize.readUInt16LE(0), + _bfSize: bfSize, + bfReserved1: 0, + bfReserved2: 0, + bfOffBits: bfOffBits.readUInt16LE(0), + _bfOffBits: bfOffBits + }; +}; +Bitmap.prototype.readInfoHeader = function (){ + this.infoHeader = this.read(this.buffer, 14, 4); +}; +Bitmap.prototype.readCoreHeader = function (){ + var coreType = this.infoHeader.readUInt16LE(0); + switch(coreType){ + case this.BITMAPCOREHEADER: + return this.readCoreHeaderOS2_V1(); + case this.BITMAPINFOHEADER2: + return this.readCoreHeaderOS2_V2(); + case this.BITMAPV4HEADER: + return this.readCoreHeaderWINDOWS_V4(); + case this.BITMAPV5HEADER: + return this.readCoreHeaderWINDOWS_V5(); + case this.BITMAPINFOHEADER: + return this.readCoreHeaderWINDOWS_V3(); + default: + throw new Error('unknown coreType: ' + coreType); + } +}; +Bitmap.prototype.readCoreHeaderWINDOWS_V3 = function (){ + var biWidth = this.read(this.buffer, 0x12, 4); + var biHeight = this.read(this.buffer, 0x16, 4); + var biPlanes = this.read(this.buffer, 0x1a, 2); + var biBitCount = this.read(this.buffer, 0x1c, 2); + var biCopmression = this.read(this.buffer, 0x1e, 4); + var biSizeImage = this.read(this.buffer, 0x22, 4); + var biXPixPerMeter = this.read(this.buffer, 0x26, 4); + var biYPixPerMeter = this.read(this.buffer, 0x2a, 4); + var biClrUsed = this.read(this.buffer, 0x2e, 4); + var biCirImportant = this.read(this.buffer, 0x32, 4); + + this.coreHeader = { + __copmression__: biCopmression.readUInt16LE(0), + __bitCount__: biBitCount.readUInt8(0), + __width__: biWidth.readUInt16LE(0), + __height__: biHeight.readUInt16LE(0), + biWidth: biWidth.readUInt16LE(0), + _biWidth: biWidth, + biHeight: biHeight.readUInt16LE(0), + _biHeight: biHeight, + biPlanes: biPlanes.readUInt8(0), + _biPlanes: biPlanes, + biBitCount: biBitCount.readUInt8(0), + _biBitCount: biBitCount, + biCopmression: biCopmression.readUInt16LE(0), + _biCopmression: biCopmression, + biSizeImage: biSizeImage.readUInt16LE(0), + _biSizeImage: biSizeImage, + biXPixPerMeter: biXPixPerMeter.readUInt16LE(0), + _biXPixPerMeter: biXPixPerMeter, + biYPixPerMeter: biYPixPerMeter.readUInt16LE(0), + _biYPixPerMeter: biYPixPerMeter, + biClrUsed: biClrUsed.readUInt16LE(0), + _biClrUsed: biClrUsed, + biCirImportant: biCirImportant.readUInt16LE(0), + _biCirImportant: biCirImportant + }; +}; +Bitmap.prototype.readCoreHeaderWINDOWS_V4 = function (){ + throw new Error('not yet impl'); + + var bV4Width = this.read(this.buffer, 0x12, 4); + var bV4Height = this.read(this.buffer, 0x16, 4); + var bV4Planes = this.read(this.buffer, 0x1a, 2); + var bV4BitCount = this.read(this.buffer, 0x1c, 2); + var bV4Compression = this.read(this.buffer, 0x1e, 4); + var bV4SizeImage = this.read(this.buffer, 0x22, 4); + var bV4XPelsPerMeter = this.read(this.buffer, 0x26, 4); + var bV4YPelsPerMeter = this.read(this.buffer, 0x2a, 4); + var bV4ClrUsed = this.read(this.buffer, 0x2e, 4); + var bV4ClrImportant = this.read(this.buffer, 0x32, 4); + var bV4RedMask = this.read(this.buffer, 0x36, 4); + var bV4GreenMask = this.read(this.buffer, 0x3a, 4); + var bV4BlueMask = this.read(this.buffer, 0x3e, 4); + var bV4AlphaMask = this.read(this.buffer, 0x42, 4); + var bV4CSType = this.read(this.buffer, 0x46, 4); + var bV4Endpoints = this.read(this.buffer, 0x6a, 36); + var bV4GammaRed = this.read(this.buffer, 0x6e, 4); + var bV4GammaGreen = this.read(this.buffer, 0x72, 4); + var bV4GammaBlue = this.read(this.buffer, 0x76, 4); + + this.coreHeader = { + __compression__: bV4Compression.readUInt16LE(0), + __bitCount__: bV4BitCount.readUInt8(0), + __width__: bV4Width.readUInt16LE(0), + __height__: bV4Height.readUInt16LE(0), + bV4Width: bV4Width.readUInt16LE(0), + _bV4Width: bV4Width, + bV4Height: bV4Height.readUInt16LE(0), + _bV4Height: bV4Height, + bV4Planes: bV4Planes.readUInt8(0), + _bV4Planes: bV4Planes, + bV4BitCount: bV4BitCount.readUInt8(0), + _bV4BitCount: bV4BitCount, + bV4Compression: bV4Compression.readUInt16LE(0), + _bV4Compression: bV4Compression, + bV4SizeImage: bV4SizeImage.readUInt16LE(0), + _bV4SizeImage: bV4SizeImage, + bV4XPelsPerMeter: bV4XPelsPerMeter.readUInt16LE(0), + _bV4XPelsPerMeter: bV4XPelsPerMeter, + bV4YPelsPerMeter: bV4YPelsPerMeter.readUInt16LE(0), + _bV4YPelsPerMeter: bV4YPelsPerMeter, + bV4ClrUsed: bV4ClrUsed.readUInt16LE(0), + _bV4ClrUsed: bV4ClrUsed, + bV4ClrImportant: bV4ClrImportant.readUInt16LE(0), + _bV4ClrImportant: bV4ClrImportant, + bV4RedMask: bV4RedMask.readUInt16LE(0), + _bV4RedMask: bV4RedMask, + bV4GreenMask: bV4GreenMask.readUInt16LE(0), + _bV4GreenMask: bV4GreenMask, + bV4BlueMask: bV4BlueMask.readUInt16LE(0), + _bV4BlueMask: bV4BlueMask, + bV4AlphaMask: bV4AlphaMask.readUInt16LE(0), + _bV4AlphaMask: bV4AlphaMask, + bV4CSType: bV4CSType.readUInt16LE(0), + _bV4CSType: bV4CSType, + bV4Endpoints: null, + _bV4Endpoints: bV4Endpoints, + bV4GammaRed: bV4GammaRed.readUInt16LE(0), + _bV4GammaRed: bV4GammaRed, + bV4GammaGreen: bV4GammaGreen.readUInt16LE(0), + _bV4GammaGreen: bV4GammaGreen, + bV4GammaBlue: bV4GammaBlue.readUInt16LE(0), + _bV4GammaBlue: bV4GammaBlue + }; +}; +Bitmap.prototype.readCoreHeaderWINDOWS_V5 = function (){ + throw new Error('not yet impl'); + + var bV5Width = this.read(this.buffer, 0x12, 4); + var bV5Height = this.read(this.buffer, 0x16, 4); + var bV5Planes = this.read(this.buffer, 0x1a, 2); + var bV5BitCount = this.read(this.buffer, 0x1c, 2); + var bV5Compression = this.read(this.buffer, 0x1e, 4); + var bV5SizeImage = this.read(this.buffer, 0x22, 4); + var bV5XPelsPerMeter = this.read(this.buffer, 0x26, 4); + var bV5YPelsPerMeter = this.read(this.buffer, 0x2a, 4); + var bV5ClrUsed = this.read(this.buffer, 0x2e, 4); + var bV5ClrImportant = this.read(this.buffer, 0x32, 4); + var bV5RedMask = this.read(this.buffer, 0x36, 4); + var bV5GreenMask = this.read(this.buffer, 0x3a, 4); + var bV5BlueMask = this.read(this.buffer, 0x3e, 4); + var bV5AlphaMask = this.read(this.buffer, 0x42, 4); + var bV5CSType = this.read(this.buffer, 0x46, 4); + var bV5Endpoints = this.read(this.buffer, 0x6a, 36); + var bV5GammaRed = this.read(this.buffer, 0x6e, 4); + var bV5GammaGreen = this.read(this.buffer, 0x72, 4); + var bV5GammaBlue = this.read(this.buffer, 0x76, 4); + var bV5Intent = this.read(this.buffer, 0x7a, 4); + var bV5ProfileData = this.read(this.buffer, 0x7e, 4); + var bV5ProfileSize = this.read(this.buffer, 0x82, 4); + var bV5Reserved = this.read(this.buffer, 0x86, 4); + + this.coreHeader = { + __compression__: bV5Compression.readUInt16LE(0), + __bitCount__: bV5BitCount.readUInt8(0), + __width__: bV5Width.readUInt16LE(0), + __height__: bV5Height.readUInt16LE(0), + bV5Width: bV5Width.readUInt16LE(0), + _bV5Width: bV5Width, + bV5Height: bV5Height.readUInt16LE(0), + _bV5Height: bV5Height, + bV5Planes: bV5Planes.readUInt8(0), + _bV5Planes: bV5Planes, + bV5BitCount: bV5BitCount.readUInt8(0), + _bV5BitCount: bV5BitCount, + bV5Compression: bV5Compression.readUInt16LE(0), + _bV5Compression: bV5Compression, + bV5SizeImage: bV5SizeImage.readUInt16LE(0), + _bV5SizeImage: bV5SizeImage, + bV5XPelsPerMeter: bV5XPelsPerMeter.readUInt16LE(0), + _bV5XPelsPerMeter: bV5XPelsPerMeter, + bV5YPelsPerMeter: bV5YPelsPerMeter.readUInt16LE(0), + _bV5YPelsPerMeter: bV5YPelsPerMeter, + bV5ClrUsed: bV5ClrUsed.readUInt16LE(0), + _bV5ClrUsed: bV5ClrUsed, + bV5ClrImportant: bV5ClrImportant.readUInt16LE(0), + _bV5ClrImportant: bV5ClrImportant, + bV5RedMask: bV5RedMask.readUInt16LE(0), + _bV5RedMask: bV5RedMask, + bV5GreenMask: bV5GreenMask.readUInt16LE(0), + _bV5GreenMask: bV5GreenMask, + bV5BlueMask: bV5BlueMask.readUInt16LE(0), + _bV5BlueMask: bV5BlueMask, + bV5AlphaMask: bV5AlphaMask.readUInt16LE(0), + _bV5AlphaMask: bV5AlphaMask, + bV5CSType: bV5CSType.readUInt16LE(0), + _bV5CSType: bV5CSType, + bV5Endpoints: null, + _bV5Endpoints: bV5Endpoints, + bV5GammaRed: bV5GammaRed.readUInt16LE(0), + _bV5GammaRed: bV5GammaRed, + bV5GammaGreen: bV5GammaGreen.readUInt16LE(0), + _bV5GammaGreen: bV5GammaGreen, + bV5GammaBlue: bV5GammaBlue.readUInt16LE(0), + _bV5GammaBlue: bV5GammaBlue, + bV5Intent: bV5Intent.readUInt16LE(0), + _bV5Intent: bV5Intent, + bV5ProfileData: bV5ProfileData.readUInt16LE(0), + _bV5ProfileData: bV5ProfileData, + bV5ProfileSize: bV5ProfileSize.readUInt16LE(0), + _bV5ProfileSize: bV5ProfileSize, + bV5Reserved: 0, + _bV5Reserved: bV5Reserved + }; +}; +Bitmap.prototype.readCoreHeaderOS2_V1 = function (){ + throw new Error('not yet impl'); + + var bcWidth = this.read(this.buffer, 0x12, 2); + var bcHeight = this.read(this.buffer, 0x14, 2); + var bcPlanes = this.read(this.buffer, 0x16, 2); + var bcBitCount = this.read(this.buffer, 0x18, 2); + + this.coreHeader = { + __compression__: 0, + __bitCount__: bcBitCount.readUInt8(0), + __width__: bcWidth.readUInt8(0), + __height__: bcHeight.readUInt8(0), + bcWidth: bcWidth.readUInt8(0), + _bcWidth: bcWidth, + bcHeight: bcHeight.readUInt8(0), + _bcHeight: bcHeight, + bcPlanes: bcPlanes.readUInt8(0), + _bcPlanes: bcPlanes, + bcBitCount: bcBitCount.readUInt8(0), + _bcBitCount: bcBitCount + }; +}; +Bitmap.prototype.readCoreHeaderOS2_V2 = function (){ + throw new Error('not yet impl'); + + var cx = this.read(this.buffer, 0x12, 4); + var cy = this.read(this.buffer, 0x16, 4); + var cPlanes = this.read(this.buffer, 0x1a, 2); + var cBitCount = this.read(this.buffer, 0x1c, 2); + var ulCompression = this.read(this.buffer, 0x1e, 4); + var cbImage = this.read(this.buffer, 0x22, 4); + var cxResolution = this.read(this.buffer, 0x26, 4); + var cyResolution = this.read(this.buffer, 0x2a, 4); + var cclrUsed = this.read(this.buffer, 0x2e, 4); + var cclrImportant = this.read(this.buffer, 0x32, 4); + var usUnits = this.read(this.buffer, 0x36, 2); + var usReserved = this.read(this.buffer, 0x38, 2); + var usRecording = this.read(this.buffer, 0x3a, 2); + var usRendering = this.read(this.buffer, 0x3c, 2); + var cSize1 = this.read(this.buffer, 0x3e, 4); + var cSize2 = this.read(this.buffer, 0x42, 4); + var ulColorEncoding = this.read(this.buffer, 0x46, 4); + var ulIdentifier = this.read(this.buffer, 0x4a, 4); + + this.coreHeader = { + __compression__: ulCompression.readUInt16LE(0), + __bitCount__: cBitCount.readUInt8(0), + __width__: cx.readUInt16LE(0), + __height__: cy.readUInt16LE(0), + cx: cx.readUInt16LE(0), + _cx: cx, + cy: cy.readUInt16LE(0), + _cy: cy, + cPlanes: cPlanes.readUInt8(0), + _cPlanes: cPlanes, + cBitCount: cBitCount.readUInt8(0), + _cBitCount: cBitCount, + ulCompression: ulCompression.readUInt16LE(0), + _ulCompression: ulCompression, + cbImage: cbImage.readUInt16LE(0), + _cbImage: cbImage, + cxResolution: cxResolution.readUInt16LE(0), + _cxResolution: cxResolution, + cyResolution: cyResolution.readUInt16LE(0), + _cyResolution: cyResolution, + cclrUsed: cclrUsed.readUInt16LE(0), + _cclrUsed: cclrUsed, + cclrImportant: cclrImportant.readUInt16LE(0), + _cclrImportant: cclrImportant, + usUnits: usUnits.readUInt8(0), + _usUnits: usUnits, + usReserved: usReserved.readUInt8(0), + _usReserved: usReserved, + usRecording: usRecording.readUInt8(0), + _usRecording: usRecording, + usRendering: usRendering.readUInt8(0), + _usRendering: usRendering, + cSize1: cSize1.readUInt16LE(0), + _cSize1: cSize1, + cSize2: cSize2.readUInt16LE(0), + _cSize1: cSize1, + ulColorEncoding: ulColorEncoding.readUInt16LE(0), + _ulColorEncoding: ulColorEncoding, + ulIdentifier: ulIdentifier.readUInt16LE(0), + _ulIdentifier: ulIdentifier + }; +}; +Bitmap.prototype.readColorPalette = function (){ + var bitCount = this.coreHeader.__bitCount__; + if(this.BITCOUNT_16bit == bitCount){ + return /* nop */; + } + if(this.BITCOUNT_24bit == bitCount){ + return /* nop */; + } + if(this.BITCOUNT_32bit == bitCount){ + return /* nop */; + } + + var coreType = this.infoHeader.readUInt16LE(0); + switch(coreType){ + case this.BITMAPCOREHEADER: + return this.readColorPalette_RGBTRIPLE(bitCount, 0x1a); + case this.BITMAPINFOHEADER2: + return this.readColorPalette_RGBTRIPLE(bitCount, 0x4e); + case this.BITMAPV4HEADER: + return this.readColorPalette_RGBQUAD(bitCount, 0x7a); + case this.BITMAPV5HEADER: + return this.readColorPalette_RGBQUAD(bitCount, 0x8a); + case this.BITMAPINFOHEADER: + return this.readColorPalette_RGBQUAD(bitCount, 0x36); + default: + throw new Error('unknown colorPalette: ' + coreType + ',' + bitCount); + } +}; +Bitmap.prototype.readColorPalette_RGBTRIPLE = function (bitCount, startPos){ + throw new Error('not yet impl'); +}; +Bitmap.prototype.readColorPalette_RGBQUAD = function (bitCount, startPos){ + if(this.BITCOUNT_2 == bitCount){ + return this.readRGBQUAD(1 << this.BITCOUNT_2, startPos); + } + if(this.BITCOUNT_16 == bitCount){ + return this.readRGBQUAD(1 << this.BITCOUNT_16, startPos); + } + if(this.BITCOUNT_256 == bitCount){ + return this.readRGBQUAD(1 << this.BITCOUNT_256, startPos); + } + throw new Error('unknown bitCount: ' + bitCount); +}; +Bitmap.prototype.readRGBQUAD = function(count, startPos){ + var palette = []; + for(var i = startPos, len = startPos + (4 * count); i < len; i += 4){ + palette.push({ + rgbBlue: this.read(this.buffer, i, 1).readUInt8(0), + rgbGreen: this.read(this.buffer, i + 1, 1).readUInt8(0), + rgbRed: this.read(this.buffer, i + 2, 1).readUInt8(0), + rgbReserved: this.read(this.buffer, i + 3, 1).readUInt8(0) + }); + } + this.colorPalette = palette; +}; +Bitmap.prototype.initDataPos = function(){ + var bitCount = this.coreHeader.__bitCount__; + var hasPalette = true; + if(this.BITCOUNT_16bit == bitCount){ + hasPalette = true; + } + if(this.BITCOUNT_24bit == bitCount){ + hasPalette = true; + } + if(this.BITCOUNT_32bit == bitCount){ + hasPalette = true; + } + + var coreType = this.infoHeader.readUInt16LE(0); + switch(coreType){ + case this.BITMAPCOREHEADER: + this.dataPos = 0x1a; + if(hasPalette){ + this.dataPos = this.dataPos + (3 * (1 << bitCount)); + } + break; + case this.BITMAPINFOHEADER2: + this.dataPos = 0x4e; + if(hasPalette){ + this.dataPos = this.dataPos + (3 * (1 << bitCount)); + } + break; + case this.BITMAPV4HEADER: + this.dataPos = 0x7a; + if(hasPalette){ + this.dataPos = this.dataPos + (4 * (1 << bitCount)); + } + break; + case this.BITMAPV5HEADER: + this.dataPos = 0x8a; + if(hasPalette){ + this.dataPos = this.dataPos + (4 * (1 << bitCount)); + } + case this.BITMAPINFOHEADER: + this.dataPos = 0x36; + if(hasPalette){ + this.dataPos = this.dataPos + (4 * (1 << bitCount)); + } + break; + default: + throw new Error('unknown colorPalette: ' + coreType + ',' + bitCount); + } +}; +Bitmap.prototype.mapRGBA = function(r, g, b, a){ + var hex = []; + + var padHex = function(value){ + var h = value.toString(16); + if(value < 0x0f){ + return '0' + h; + } + return h; + }; + + hex.push(padHex(r)); + hex.push(padHex(g)); + hex.push(padHex(b)); + + return '#' + hex.join(''); +}; +Bitmap.prototype.mapColor = function(bmpBuf, bitCount){ + var b, g, r, a; + var length = bmpBuf.length; + var colorData = []; + + if(this.BITCOUNT_2 == bitCount){ + for(var i = 0; i < length; ++i){ + var paletteValue = bmpBuf[i]; + var bin = paletteValue.toString(2); + bin = new Array(8 - bin.length).join('0') + bin; + + for(var j = 0; j < bin.length; ++j){ + var paletteIndex = parseInt(bin.substring(j, j + 1), 10); + var palette = this.colorPalette[paletteIndex]; + colorData.push(this.mapRGBA(palette.rgbRed, palette.rgbGreen, palette.rgbBlue, -1)); + } + } + return colorData; + } + if(this.BITCOUNT_16 == bitCount){ + for(var i = 0; i < length; i += 2){ + var paletteHigh = bmpBuf.readUInt8(i); + var paletteLow = bmpBuf.readUInt8(i + 1); + var indexes = [paletteHigh, paletteLow]; + indexes.forEach(function(paletteIndex){ + var palette = this.colorPalette[paletteIndex]; + colorData.push(this.mapRGBA(palette.rgbRed, palette.rgbGreen, palette.rgbBlue, -1)); + }); + } + + return colorData; + } + if(this.BITCOUNT_256 == bitCount){ + for(var i = 0; i < length; ++i){ + var paletteIndex = bmpBuf.readUInt16LE(i); + var palette = this.colorPalette[paletteIndex]; + colorData.push(this.mapRGBA(palette.rgbRed, palette.rgbGreen, palette.rgbBlue, -1)); + } + return colorData; + } + if(this.BITCOUNT_16bit == bitCount){ + for(var i = 0; i < length; i += 3){ + b = bmpBuf[i]; + g = bmpBuf[i + 1]; + r = bmpBuf[i + 2]; + colorData.push(this.mapRGBA(r, g, b, -1)); + } + return colorData; + } + if(this.BITCOUNT_24bit == bitCount){ + for(var i = 0; i < length; i += 3){ + b = bmpBuf[i]; + g = bmpBuf[i + 1]; + r = bmpBuf[i + 2]; + colorData.push(this.mapRGBA(r, g, b, -1)); + } + return colorData; + } + if(this.BITCOUNT_32bit == bitCount){ + for(var i = 0; i < length; i += 4){ + b = bmpBuf[i]; + g = bmpBuf[i + 1]; + r = bmpBuf[i + 2]; + a = bmpBuf[i + 3]; + colorData.push(this.mapRGBA(r, g, b, a)); + } + return colorData; + } + throw new Error('unknown bitCount: ' + bitCount); +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"buffer":147}],77:[function(require,module,exports){ +// (c) Dean McNamee , 2013. +// +// https://github.com/deanm/omggif +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +// omggif is a JavaScript implementation of a GIF 89a encoder and decoder, +// including animation and compression. It does not rely on any specific +// underlying system, so should run in the browser, Node, or Plask. + +"use strict"; + +function GifWriter(buf, width, height, gopts) { + var p = 0; + + var gopts = gopts === undefined ? { } : gopts; + var loop_count = gopts.loop === undefined ? null : gopts.loop; + var global_palette = gopts.palette === undefined ? null : gopts.palette; + + if (width <= 0 || height <= 0 || width > 65535 || height > 65535) + throw new Error("Width/Height invalid."); + + function check_palette_and_num_colors(palette) { + var num_colors = palette.length; + if (num_colors < 2 || num_colors > 256 || num_colors & (num_colors-1)) { + throw new Error( + "Invalid code/color length, must be power of 2 and 2 .. 256."); + } + return num_colors; + } + + // - Header. + buf[p++] = 0x47; buf[p++] = 0x49; buf[p++] = 0x46; // GIF + buf[p++] = 0x38; buf[p++] = 0x39; buf[p++] = 0x61; // 89a + + // Handling of Global Color Table (palette) and background index. + var gp_num_colors_pow2 = 0; + var background = 0; + if (global_palette !== null) { + var gp_num_colors = check_palette_and_num_colors(global_palette); + while (gp_num_colors >>= 1) ++gp_num_colors_pow2; + gp_num_colors = 1 << gp_num_colors_pow2; + --gp_num_colors_pow2; + if (gopts.background !== undefined) { + background = gopts.background; + if (background >= gp_num_colors) + throw new Error("Background index out of range."); + // The GIF spec states that a background index of 0 should be ignored, so + // this is probably a mistake and you really want to set it to another + // slot in the palette. But actually in the end most browsers, etc end + // up ignoring this almost completely (including for dispose background). + if (background === 0) + throw new Error("Background index explicitly passed as 0."); + } + } + + // - Logical Screen Descriptor. + // NOTE(deanm): w/h apparently ignored by implementations, but set anyway. + buf[p++] = width & 0xff; buf[p++] = width >> 8 & 0xff; + buf[p++] = height & 0xff; buf[p++] = height >> 8 & 0xff; + // NOTE: Indicates 0-bpp original color resolution (unused?). + buf[p++] = (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag. + gp_num_colors_pow2; // NOTE: No sort flag (unused?). + buf[p++] = background; // Background Color Index. + buf[p++] = 0; // Pixel aspect ratio (unused?). + + // - Global Color Table + if (global_palette !== null) { + for (var i = 0, il = global_palette.length; i < il; ++i) { + var rgb = global_palette[i]; + buf[p++] = rgb >> 16 & 0xff; + buf[p++] = rgb >> 8 & 0xff; + buf[p++] = rgb & 0xff; + } + } + + if (loop_count !== null) { // Netscape block for looping. + if (loop_count < 0 || loop_count > 65535) + throw new Error("Loop count invalid.") + // Extension code, label, and length. + buf[p++] = 0x21; buf[p++] = 0xff; buf[p++] = 0x0b; + // NETSCAPE2.0 + buf[p++] = 0x4e; buf[p++] = 0x45; buf[p++] = 0x54; buf[p++] = 0x53; + buf[p++] = 0x43; buf[p++] = 0x41; buf[p++] = 0x50; buf[p++] = 0x45; + buf[p++] = 0x32; buf[p++] = 0x2e; buf[p++] = 0x30; + // Sub-block + buf[p++] = 0x03; buf[p++] = 0x01; + buf[p++] = loop_count & 0xff; buf[p++] = loop_count >> 8 & 0xff; + buf[p++] = 0x00; // Terminator. + } + + + var ended = false; + + this.addFrame = function(x, y, w, h, indexed_pixels, opts) { + if (ended === true) { --p; ended = false; } // Un-end. + + opts = opts === undefined ? { } : opts; + + // TODO(deanm): Bounds check x, y. Do they need to be within the virtual + // canvas width/height, I imagine? + if (x < 0 || y < 0 || x > 65535 || y > 65535) + throw new Error("x/y invalid.") + + if (w <= 0 || h <= 0 || w > 65535 || h > 65535) + throw new Error("Width/Height invalid.") + + if (indexed_pixels.length < w * h) + throw new Error("Not enough pixels for the frame size."); + + var using_local_palette = true; + var palette = opts.palette; + if (palette === undefined || palette === null) { + using_local_palette = false; + palette = global_palette; + } + + if (palette === undefined || palette === null) + throw new Error("Must supply either a local or global palette."); + + var num_colors = check_palette_and_num_colors(palette); + + // Compute the min_code_size (power of 2), destroying num_colors. + var min_code_size = 0; + while (num_colors >>= 1) ++min_code_size; + num_colors = 1 << min_code_size; // Now we can easily get it back. + + var delay = opts.delay === undefined ? 0 : opts.delay; + + // From the spec: + // 0 - No disposal specified. The decoder is + // not required to take any action. + // 1 - Do not dispose. The graphic is to be left + // in place. + // 2 - Restore to background color. The area used by the + // graphic must be restored to the background color. + // 3 - Restore to previous. The decoder is required to + // restore the area overwritten by the graphic with + // what was there prior to rendering the graphic. + // 4-7 - To be defined. + // NOTE(deanm): Dispose background doesn't really work, apparently most + // browsers ignore the background palette index and clear to transparency. + var disposal = opts.disposal === undefined ? 0 : opts.disposal; + if (disposal < 0 || disposal > 3) // 4-7 is reserved. + throw new Error("Disposal out of range."); + + var use_transparency = false; + var transparent_index = 0; + if (opts.transparent !== undefined && opts.transparent !== null) { + use_transparency = true; + transparent_index = opts.transparent; + if (transparent_index < 0 || transparent_index >= num_colors) + throw new Error("Transparent color index."); + } + + if (disposal !== 0 || use_transparency || delay !== 0) { + // - Graphics Control Extension + buf[p++] = 0x21; buf[p++] = 0xf9; // Extension / Label. + buf[p++] = 4; // Byte size. + + buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0); + buf[p++] = delay & 0xff; buf[p++] = delay >> 8 & 0xff; + buf[p++] = transparent_index; // Transparent color index. + buf[p++] = 0; // Block Terminator. + } + + // - Image Descriptor + buf[p++] = 0x2c; // Image Seperator. + buf[p++] = x & 0xff; buf[p++] = x >> 8 & 0xff; // Left. + buf[p++] = y & 0xff; buf[p++] = y >> 8 & 0xff; // Top. + buf[p++] = w & 0xff; buf[p++] = w >> 8 & 0xff; + buf[p++] = h & 0xff; buf[p++] = h >> 8 & 0xff; + // NOTE: No sort flag (unused?). + // TODO(deanm): Support interlace. + buf[p++] = using_local_palette === true ? (0x80 | (min_code_size-1)) : 0; + + // - Local Color Table + if (using_local_palette === true) { + for (var i = 0, il = palette.length; i < il; ++i) { + var rgb = palette[i]; + buf[p++] = rgb >> 16 & 0xff; + buf[p++] = rgb >> 8 & 0xff; + buf[p++] = rgb & 0xff; + } + } + + p = GifWriterOutputLZWCodeStream( + buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels); + + return p; + }; + + this.end = function() { + if (ended === false) { + buf[p++] = 0x3b; // Trailer. + ended = true; + } + return p; + }; + + this.getOutputBuffer = function() { return buf; }; + this.setOutputBuffer = function(v) { buf = v; }; + this.getOutputBufferPosition = function() { return p; }; + this.setOutputBufferPosition = function(v) { p = v; }; +} + +// Main compression routine, palette indexes -> LZW code stream. +// |index_stream| must have at least one entry. +function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) { + buf[p++] = min_code_size; + var cur_subblock = p++; // Pointing at the length field. + + var clear_code = 1 << min_code_size; + var code_mask = clear_code - 1; + var eoi_code = clear_code + 1; + var next_code = eoi_code + 1; + + var cur_code_size = min_code_size + 1; // Number of bits per code. + var cur_shift = 0; + // We have at most 12-bit codes, so we should have to hold a max of 19 + // bits here (and then we would write out). + var cur = 0; + + function emit_bytes_to_buffer(bit_block_size) { + while (cur_shift >= bit_block_size) { + buf[p++] = cur & 0xff; + cur >>= 8; cur_shift -= 8; + if (p === cur_subblock + 256) { // Finished a subblock. + buf[cur_subblock] = 255; + cur_subblock = p++; + } + } + } + + function emit_code(c) { + cur |= c << cur_shift; + cur_shift += cur_code_size; + emit_bytes_to_buffer(8); + } + + // I am not an expert on the topic, and I don't want to write a thesis. + // However, it is good to outline here the basic algorithm and the few data + // structures and optimizations here that make this implementation fast. + // The basic idea behind LZW is to build a table of previously seen runs + // addressed by a short id (herein called output code). All data is + // referenced by a code, which represents one or more values from the + // original input stream. All input bytes can be referenced as the same + // value as an output code. So if you didn't want any compression, you + // could more or less just output the original bytes as codes (there are + // some details to this, but it is the idea). In order to achieve + // compression, values greater then the input range (codes can be up to + // 12-bit while input only 8-bit) represent a sequence of previously seen + // inputs. The decompressor is able to build the same mapping while + // decoding, so there is always a shared common knowledge between the + // encoding and decoder, which is also important for "timing" aspects like + // how to handle variable bit width code encoding. + // + // One obvious but very important consequence of the table system is there + // is always a unique id (at most 12-bits) to map the runs. 'A' might be + // 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship + // can be used for an effecient lookup strategy for the code mapping. We + // need to know if a run has been seen before, and be able to map that run + // to the output code. Since we start with known unique ids (input bytes), + // and then from those build more unique ids (table entries), we can + // continue this chain (almost like a linked list) to always have small + // integer values that represent the current byte chains in the encoder. + // This means instead of tracking the input bytes (AAAABCD) to know our + // current state, we can track the table entry for AAAABC (it is guaranteed + // to exist by the nature of the algorithm) and the next character D. + // Therefor the tuple of (table_entry, byte) is guaranteed to also be + // unique. This allows us to create a simple lookup key for mapping input + // sequences to codes (table indices) without having to store or search + // any of the code sequences. So if 'AAAA' has a table entry of 12, the + // tuple of ('AAAA', K) for any input byte K will be unique, and can be our + // key. This leads to a integer value at most 20-bits, which can always + // fit in an SMI value and be used as a fast sparse array / object key. + + // Output code for the current contents of the index buffer. + var ib_code = index_stream[0] & code_mask; // Load first input index. + var code_table = { }; // Key'd on our 20-bit "tuple". + + emit_code(clear_code); // Spec says first code should be a clear code. + + // First index already loaded, process the rest of the stream. + for (var i = 1, il = index_stream.length; i < il; ++i) { + var k = index_stream[i] & code_mask; + var cur_key = ib_code << 8 | k; // (prev, k) unique tuple. + var cur_code = code_table[cur_key]; // buffer + k. + + // Check if we have to create a new code table entry. + if (cur_code === undefined) { // We don't have buffer + k. + // Emit index buffer (without k). + // This is an inline version of emit_code, because this is the core + // writing routine of the compressor (and V8 cannot inline emit_code + // because it is a closure here in a different context). Additionally + // we can call emit_byte_to_buffer less often, because we can have + // 30-bits (from our 31-bit signed SMI), and we know our codes will only + // be 12-bits, so can safely have 18-bits there without overflow. + // emit_code(ib_code); + cur |= ib_code << cur_shift; + cur_shift += cur_code_size; + while (cur_shift >= 8) { + buf[p++] = cur & 0xff; + cur >>= 8; cur_shift -= 8; + if (p === cur_subblock + 256) { // Finished a subblock. + buf[cur_subblock] = 255; + cur_subblock = p++; + } + } + + if (next_code === 4096) { // Table full, need a clear. + emit_code(clear_code); + next_code = eoi_code + 1; + cur_code_size = min_code_size + 1; + code_table = { }; + } else { // Table not full, insert a new entry. + // Increase our variable bit code sizes if necessary. This is a bit + // tricky as it is based on "timing" between the encoding and + // decoder. From the encoders perspective this should happen after + // we've already emitted the index buffer and are about to create the + // first table entry that would overflow our current code bit size. + if (next_code >= (1 << cur_code_size)) ++cur_code_size; + code_table[cur_key] = next_code++; // Insert into code table. + } + + ib_code = k; // Index buffer to single input k. + } else { + ib_code = cur_code; // Index buffer to sequence in code table. + } + } + + emit_code(ib_code); // There will still be something in the index buffer. + emit_code(eoi_code); // End Of Information. + + // Flush / finalize the sub-blocks stream to the buffer. + emit_bytes_to_buffer(1); + + // Finish the sub-blocks, writing out any unfinished lengths and + // terminating with a sub-block of length 0. If we have already started + // but not yet used a sub-block it can just become the terminator. + if (cur_subblock + 1 === p) { // Started but unused. + buf[cur_subblock] = 0; + } else { // Started and used, write length and additional terminator block. + buf[cur_subblock] = p - cur_subblock - 1; + buf[p++] = 0; + } + return p; +} + +function GifReader(buf) { + var p = 0; + + // - Header (GIF87a or GIF89a). + if (buf[p++] !== 0x47 || buf[p++] !== 0x49 || buf[p++] !== 0x46 || + buf[p++] !== 0x38 || (buf[p++]+1 & 0xfd) !== 0x38 || buf[p++] !== 0x61) { + throw new Error("Invalid GIF 87a/89a header."); + } + + // - Logical Screen Descriptor. + var width = buf[p++] | buf[p++] << 8; + var height = buf[p++] | buf[p++] << 8; + var pf0 = buf[p++]; // . + var global_palette_flag = pf0 >> 7; + var num_global_colors_pow2 = pf0 & 0x7; + var num_global_colors = 1 << (num_global_colors_pow2 + 1); + var background = buf[p++]; + buf[p++]; // Pixel aspect ratio (unused?). + + var global_palette_offset = null; + var global_palette_size = null; + + if (global_palette_flag) { + global_palette_offset = p; + global_palette_size = num_global_colors; + p += num_global_colors * 3; // Seek past palette. + } + + var no_eof = true; + + var frames = [ ]; + + var delay = 0; + var transparent_index = null; + var disposal = 0; // 0 - No disposal specified. + var loop_count = null; + + this.width = width; + this.height = height; + + while (no_eof && p < buf.length) { + switch (buf[p++]) { + case 0x21: // Graphics Control Extension Block + switch (buf[p++]) { + case 0xff: // Application specific block + // Try if it's a Netscape block (with animation loop counter). + if (buf[p ] !== 0x0b || // 21 FF already read, check block size. + // NETSCAPE2.0 + buf[p+1 ] == 0x4e && buf[p+2 ] == 0x45 && buf[p+3 ] == 0x54 && + buf[p+4 ] == 0x53 && buf[p+5 ] == 0x43 && buf[p+6 ] == 0x41 && + buf[p+7 ] == 0x50 && buf[p+8 ] == 0x45 && buf[p+9 ] == 0x32 && + buf[p+10] == 0x2e && buf[p+11] == 0x30 && + // Sub-block + buf[p+12] == 0x03 && buf[p+13] == 0x01 && buf[p+16] == 0) { + p += 14; + loop_count = buf[p++] | buf[p++] << 8; + p++; // Skip terminator. + } else { // We don't know what it is, just try to get past it. + p += 12; + while (true) { // Seek through subblocks. + var block_size = buf[p++]; + // Bad block size (ex: undefined from an out of bounds read). + if (!(block_size >= 0)) throw Error("Invalid block size"); + if (block_size === 0) break; // 0 size is terminator + p += block_size; + } + } + break; + + case 0xf9: // Graphics Control Extension + if (buf[p++] !== 0x4 || buf[p+4] !== 0) + throw new Error("Invalid graphics extension block."); + var pf1 = buf[p++]; + delay = buf[p++] | buf[p++] << 8; + transparent_index = buf[p++]; + if ((pf1 & 1) === 0) transparent_index = null; + disposal = pf1 >> 2 & 0x7; + p++; // Skip terminator. + break; + + case 0xfe: // Comment Extension. + while (true) { // Seek through subblocks. + var block_size = buf[p++]; + // Bad block size (ex: undefined from an out of bounds read). + if (!(block_size >= 0)) throw Error("Invalid block size"); + if (block_size === 0) break; // 0 size is terminator + // console.log(buf.slice(p, p+block_size).toString('ascii')); + p += block_size; + } + break; + + default: + throw new Error( + "Unknown graphic control label: 0x" + buf[p-1].toString(16)); + } + break; + + case 0x2c: // Image Descriptor. + var x = buf[p++] | buf[p++] << 8; + var y = buf[p++] | buf[p++] << 8; + var w = buf[p++] | buf[p++] << 8; + var h = buf[p++] | buf[p++] << 8; + var pf2 = buf[p++]; + var local_palette_flag = pf2 >> 7; + var interlace_flag = pf2 >> 6 & 1; + var num_local_colors_pow2 = pf2 & 0x7; + var num_local_colors = 1 << (num_local_colors_pow2 + 1); + var palette_offset = global_palette_offset; + var palette_size = global_palette_size; + var has_local_palette = false; + if (local_palette_flag) { + var has_local_palette = true; + palette_offset = p; // Override with local palette. + palette_size = num_local_colors; + p += num_local_colors * 3; // Seek past palette. + } + + var data_offset = p; + + p++; // codesize + while (true) { + var block_size = buf[p++]; + // Bad block size (ex: undefined from an out of bounds read). + if (!(block_size >= 0)) throw Error("Invalid block size"); + if (block_size === 0) break; // 0 size is terminator + p += block_size; + } + + frames.push({x: x, y: y, width: w, height: h, + has_local_palette: has_local_palette, + palette_offset: palette_offset, + palette_size: palette_size, + data_offset: data_offset, + data_length: p - data_offset, + transparent_index: transparent_index, + interlaced: !!interlace_flag, + delay: delay, + disposal: disposal}); + break; + + case 0x3b: // Trailer Marker (end of file). + no_eof = false; + break; + + default: + throw new Error("Unknown gif block: 0x" + buf[p-1].toString(16)); + break; + } + } + + this.numFrames = function() { + return frames.length; + }; + + this.loopCount = function() { + return loop_count; + }; + + this.frameInfo = function(frame_num) { + if (frame_num < 0 || frame_num >= frames.length) + throw new Error("Frame index out of range."); + return frames[frame_num]; + } + + this.decodeAndBlitFrameBGRA = function(frame_num, pixels) { + var frame = this.frameInfo(frame_num); + var num_pixels = frame.width * frame.height; + var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices. + GifReaderLZWOutputIndexStream( + buf, frame.data_offset, index_stream, num_pixels); + var palette_offset = frame.palette_offset; + + // NOTE(deanm): It seems to be much faster to compare index to 256 than + // to === null. Not sure why, but CompareStub_EQ_STRICT shows up high in + // the profile, not sure if it's related to using a Uint8Array. + var trans = frame.transparent_index; + if (trans === null) trans = 256; + + // We are possibly just blitting to a portion of the entire frame. + // That is a subrect within the framerect, so the additional pixels + // must be skipped over after we finished a scanline. + var framewidth = frame.width; + var framestride = width - framewidth; + var xleft = framewidth; // Number of subrect pixels left in scanline. + + // Output indicies of the top left and bottom right corners of the subrect. + var opbeg = ((frame.y * width) + frame.x) * 4; + var opend = ((frame.y + frame.height) * width + frame.x) * 4; + var op = opbeg; + + var scanstride = framestride * 4; + + // Use scanstride to skip past the rows when interlacing. This is skipping + // 7 rows for the first two passes, then 3 then 1. + if (frame.interlaced === true) { + scanstride += width * 4 * 7; // Pass 1. + } + + var interlaceskip = 8; // Tracking the row interval in the current pass. + + for (var i = 0, il = index_stream.length; i < il; ++i) { + var index = index_stream[i]; + + if (xleft === 0) { // Beginning of new scan line + op += scanstride; + xleft = framewidth; + if (op >= opend) { // Catch the wrap to switch passes when interlacing. + scanstride = framestride * 4 + width * 4 * (interlaceskip-1); + // interlaceskip / 2 * 4 is interlaceskip << 1. + op = opbeg + (framewidth + framestride) * (interlaceskip << 1); + interlaceskip >>= 1; + } + } + + if (index === trans) { + op += 4; + } else { + var r = buf[palette_offset + index * 3]; + var g = buf[palette_offset + index * 3 + 1]; + var b = buf[palette_offset + index * 3 + 2]; + pixels[op++] = b; + pixels[op++] = g; + pixels[op++] = r; + pixels[op++] = 255; + } + --xleft; + } + }; + + // I will go to copy and paste hell one day... + this.decodeAndBlitFrameRGBA = function(frame_num, pixels) { + var frame = this.frameInfo(frame_num); + var num_pixels = frame.width * frame.height; + var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices. + GifReaderLZWOutputIndexStream( + buf, frame.data_offset, index_stream, num_pixels); + var palette_offset = frame.palette_offset; + + // NOTE(deanm): It seems to be much faster to compare index to 256 than + // to === null. Not sure why, but CompareStub_EQ_STRICT shows up high in + // the profile, not sure if it's related to using a Uint8Array. + var trans = frame.transparent_index; + if (trans === null) trans = 256; + + // We are possibly just blitting to a portion of the entire frame. + // That is a subrect within the framerect, so the additional pixels + // must be skipped over after we finished a scanline. + var framewidth = frame.width; + var framestride = width - framewidth; + var xleft = framewidth; // Number of subrect pixels left in scanline. + + // Output indicies of the top left and bottom right corners of the subrect. + var opbeg = ((frame.y * width) + frame.x) * 4; + var opend = ((frame.y + frame.height) * width + frame.x) * 4; + var op = opbeg; + + var scanstride = framestride * 4; + + // Use scanstride to skip past the rows when interlacing. This is skipping + // 7 rows for the first two passes, then 3 then 1. + if (frame.interlaced === true) { + scanstride += width * 4 * 7; // Pass 1. + } + + var interlaceskip = 8; // Tracking the row interval in the current pass. + + for (var i = 0, il = index_stream.length; i < il; ++i) { + var index = index_stream[i]; + + if (xleft === 0) { // Beginning of new scan line + op += scanstride; + xleft = framewidth; + if (op >= opend) { // Catch the wrap to switch passes when interlacing. + scanstride = framestride * 4 + width * 4 * (interlaceskip-1); + // interlaceskip / 2 * 4 is interlaceskip << 1. + op = opbeg + (framewidth + framestride) * (interlaceskip << 1); + interlaceskip >>= 1; + } + } + + if (index === trans) { + op += 4; + } else { + var r = buf[palette_offset + index * 3]; + var g = buf[palette_offset + index * 3 + 1]; + var b = buf[palette_offset + index * 3 + 2]; + pixels[op++] = r; + pixels[op++] = g; + pixels[op++] = b; + pixels[op++] = 255; + } + --xleft; + } + }; +} + +function GifReaderLZWOutputIndexStream(code_stream, p, output, output_length) { + var min_code_size = code_stream[p++]; + + var clear_code = 1 << min_code_size; + var eoi_code = clear_code + 1; + var next_code = eoi_code + 1; + + var cur_code_size = min_code_size + 1; // Number of bits per code. + // NOTE: This shares the same name as the encoder, but has a different + // meaning here. Here this masks each code coming from the code stream. + var code_mask = (1 << cur_code_size) - 1; + var cur_shift = 0; + var cur = 0; + + var op = 0; // Output pointer. + + var subblock_size = code_stream[p++]; + + // TODO(deanm): Would using a TypedArray be any faster? At least it would + // solve the fast mode / backing store uncertainty. + // var code_table = Array(4096); + var code_table = new Int32Array(4096); // Can be signed, we only use 20 bits. + + var prev_code = null; // Track code-1. + + while (true) { + // Read up to two bytes, making sure we always 12-bits for max sized code. + while (cur_shift < 16) { + if (subblock_size === 0) break; // No more data to be read. + + cur |= code_stream[p++] << cur_shift; + cur_shift += 8; + + if (subblock_size === 1) { // Never let it get to 0 to hold logic above. + subblock_size = code_stream[p++]; // Next subblock. + } else { + --subblock_size; + } + } + + // TODO(deanm): We should never really get here, we should have received + // and EOI. + if (cur_shift < cur_code_size) + break; + + var code = cur & code_mask; + cur >>= cur_code_size; + cur_shift -= cur_code_size; + + // TODO(deanm): Maybe should check that the first code was a clear code, + // at least this is what you're supposed to do. But actually our encoder + // now doesn't emit a clear code first anyway. + if (code === clear_code) { + // We don't actually have to clear the table. This could be a good idea + // for greater error checking, but we don't really do any anyway. We + // will just track it with next_code and overwrite old entries. + + next_code = eoi_code + 1; + cur_code_size = min_code_size + 1; + code_mask = (1 << cur_code_size) - 1; + + // Don't update prev_code ? + prev_code = null; + continue; + } else if (code === eoi_code) { + break; + } + + // We have a similar situation as the decoder, where we want to store + // variable length entries (code table entries), but we want to do in a + // faster manner than an array of arrays. The code below stores sort of a + // linked list within the code table, and then "chases" through it to + // construct the dictionary entries. When a new entry is created, just the + // last byte is stored, and the rest (prefix) of the entry is only + // referenced by its table entry. Then the code chases through the + // prefixes until it reaches a single byte code. We have to chase twice, + // first to compute the length, and then to actually copy the data to the + // output (backwards, since we know the length). The alternative would be + // storing something in an intermediate stack, but that doesn't make any + // more sense. I implemented an approach where it also stored the length + // in the code table, although it's a bit tricky because you run out of + // bits (12 + 12 + 8), but I didn't measure much improvements (the table + // entries are generally not the long). Even when I created benchmarks for + // very long table entries the complexity did not seem worth it. + // The code table stores the prefix entry in 12 bits and then the suffix + // byte in 8 bits, so each entry is 20 bits. + + var chase_code = code < next_code ? code : prev_code; + + // Chase what we will output, either {CODE} or {CODE-1}. + var chase_length = 0; + var chase = chase_code; + while (chase > clear_code) { + chase = code_table[chase] >> 8; + ++chase_length; + } + + var k = chase; + + var op_end = op + chase_length + (chase_code !== code ? 1 : 0); + if (op_end > output_length) { + console.log("Warning, gif stream longer than expected."); + return; + } + + // Already have the first byte from the chase, might as well write it fast. + output[op++] = k; + + op += chase_length; + var b = op; // Track pointer, writing backwards. + + if (chase_code !== code) // The case of emitting {CODE-1} + k. + output[op++] = k; + + chase = chase_code; + while (chase_length--) { + chase = code_table[chase]; + output[--b] = chase & 0xff; // Write backwards. + chase >>= 8; // Pull down to the prefix code. + } + + if (prev_code !== null && next_code < 4096) { + code_table[next_code++] = prev_code << 8 | k; + // TODO(deanm): Figure out this clearing vs code growth logic better. I + // have an feeling that it should just happen somewhere else, for now it + // is awkward between when we grow past the max and then hit a clear code. + // For now just check if we hit the max 12-bits (then a clear code should + // follow, also of course encoded in 12-bits). + if (next_code >= code_mask+1 && cur_code_size < 12) { + ++cur_code_size; + code_mask = code_mask << 1 | 1; + } + } + + prev_code = code; + } + + if (op !== output_length) { + console.log("Warning, gif stream shorter than expected."); + } + + return output; +} + +// CommonJS. +try { exports.GifWriter = GifWriter; exports.GifReader = GifReader } catch(e) {} + +},{}],78:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let interlaceUtils = require("./interlace"); + +let pixelBppMapper = [ + // 0 - dummy entry + function () {}, + + // 1 - L + // 0: 0, 1: 0, 2: 0, 3: 0xff + function (pxData, data, pxPos, rawPos) { + if (rawPos === data.length) { + throw new Error("Ran out of data"); + } + + let pixel = data[rawPos]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = 0xff; + }, + + // 2 - LA + // 0: 0, 1: 0, 2: 0, 3: 1 + function (pxData, data, pxPos, rawPos) { + if (rawPos + 1 >= data.length) { + throw new Error("Ran out of data"); + } + + let pixel = data[rawPos]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = data[rawPos + 1]; + }, + + // 3 - RGB + // 0: 0, 1: 1, 2: 2, 3: 0xff + function (pxData, data, pxPos, rawPos) { + if (rawPos + 2 >= data.length) { + throw new Error("Ran out of data"); + } + + pxData[pxPos] = data[rawPos]; + pxData[pxPos + 1] = data[rawPos + 1]; + pxData[pxPos + 2] = data[rawPos + 2]; + pxData[pxPos + 3] = 0xff; + }, + + // 4 - RGBA + // 0: 0, 1: 1, 2: 2, 3: 3 + function (pxData, data, pxPos, rawPos) { + if (rawPos + 3 >= data.length) { + throw new Error("Ran out of data"); + } + + pxData[pxPos] = data[rawPos]; + pxData[pxPos + 1] = data[rawPos + 1]; + pxData[pxPos + 2] = data[rawPos + 2]; + pxData[pxPos + 3] = data[rawPos + 3]; + }, +]; + +let pixelBppCustomMapper = [ + // 0 - dummy entry + function () {}, + + // 1 - L + // 0: 0, 1: 0, 2: 0, 3: 0xff + function (pxData, pixelData, pxPos, maxBit) { + let pixel = pixelData[0]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = maxBit; + }, + + // 2 - LA + // 0: 0, 1: 0, 2: 0, 3: 1 + function (pxData, pixelData, pxPos) { + let pixel = pixelData[0]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = pixelData[1]; + }, + + // 3 - RGB + // 0: 0, 1: 1, 2: 2, 3: 0xff + function (pxData, pixelData, pxPos, maxBit) { + pxData[pxPos] = pixelData[0]; + pxData[pxPos + 1] = pixelData[1]; + pxData[pxPos + 2] = pixelData[2]; + pxData[pxPos + 3] = maxBit; + }, + + // 4 - RGBA + // 0: 0, 1: 1, 2: 2, 3: 3 + function (pxData, pixelData, pxPos) { + pxData[pxPos] = pixelData[0]; + pxData[pxPos + 1] = pixelData[1]; + pxData[pxPos + 2] = pixelData[2]; + pxData[pxPos + 3] = pixelData[3]; + }, +]; + +function bitRetriever(data, depth) { + let leftOver = []; + let i = 0; + + function split() { + if (i === data.length) { + throw new Error("Ran out of data"); + } + let byte = data[i]; + i++; + let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1; + switch (depth) { + default: + throw new Error("unrecognised depth"); + case 16: + byte2 = data[i]; + i++; + leftOver.push((byte << 8) + byte2); + break; + case 4: + byte2 = byte & 0x0f; + byte1 = byte >> 4; + leftOver.push(byte1, byte2); + break; + case 2: + byte4 = byte & 3; + byte3 = (byte >> 2) & 3; + byte2 = (byte >> 4) & 3; + byte1 = (byte >> 6) & 3; + leftOver.push(byte1, byte2, byte3, byte4); + break; + case 1: + byte8 = byte & 1; + byte7 = (byte >> 1) & 1; + byte6 = (byte >> 2) & 1; + byte5 = (byte >> 3) & 1; + byte4 = (byte >> 4) & 1; + byte3 = (byte >> 5) & 1; + byte2 = (byte >> 6) & 1; + byte1 = (byte >> 7) & 1; + leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8); + break; + } + } + + return { + get: function (count) { + while (leftOver.length < count) { + split(); + } + let returner = leftOver.slice(0, count); + leftOver = leftOver.slice(count); + return returner; + }, + resetAfterLine: function () { + leftOver.length = 0; + }, + end: function () { + if (i !== data.length) { + throw new Error("extra data found"); + } + }, + }; +} + +function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) { + // eslint-disable-line max-params + let imageWidth = image.width; + let imageHeight = image.height; + let imagePass = image.index; + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + let pxPos = getPxPos(x, y, imagePass); + pixelBppMapper[bpp](pxData, data, pxPos, rawPos); + rawPos += bpp; //eslint-disable-line no-param-reassign + } + } + return rawPos; +} + +function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) { + // eslint-disable-line max-params + let imageWidth = image.width; + let imageHeight = image.height; + let imagePass = image.index; + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + let pixelData = bits.get(bpp); + let pxPos = getPxPos(x, y, imagePass); + pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit); + } + bits.resetAfterLine(); + } +} + +exports.dataToBitMap = function (data, bitmapInfo) { + let width = bitmapInfo.width; + let height = bitmapInfo.height; + let depth = bitmapInfo.depth; + let bpp = bitmapInfo.bpp; + let interlace = bitmapInfo.interlace; + let bits; + + if (depth !== 8) { + bits = bitRetriever(data, depth); + } + let pxData; + if (depth <= 8) { + pxData = Buffer.alloc(width * height * 4); + } else { + pxData = new Uint16Array(width * height * 4); + } + let maxBit = Math.pow(2, depth) - 1; + let rawPos = 0; + let images; + let getPxPos; + + if (interlace) { + images = interlaceUtils.getImagePasses(width, height); + getPxPos = interlaceUtils.getInterlaceIterator(width, height); + } else { + let nonInterlacedPxPos = 0; + getPxPos = function () { + let returner = nonInterlacedPxPos; + nonInterlacedPxPos += 4; + return returner; + }; + images = [{ width: width, height: height }]; + } + + for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { + if (depth === 8) { + rawPos = mapImage8Bit( + images[imageIndex], + pxData, + getPxPos, + bpp, + data, + rawPos + ); + } else { + mapImageCustomBit( + images[imageIndex], + pxData, + getPxPos, + bpp, + bits, + maxBit + ); + } + } + if (depth === 8) { + if (rawPos !== data.length) { + throw new Error("extra data found"); + } + } else { + bits.end(); + } + + return pxData; +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./interlace":88,"buffer":147}],79:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let constants = require("./constants"); + +module.exports = function (dataIn, width, height, options) { + let outHasAlpha = + [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf( + options.colorType + ) !== -1; + if (options.colorType === options.inputColorType) { + let bigEndian = (function () { + let buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true /* littleEndian */); + // Int16Array uses the platform's endianness. + return new Int16Array(buffer)[0] !== 256; + })(); + // If no need to convert to grayscale and alpha is present/absent in both, take a fast route + if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) { + return dataIn; + } + } + + // map to a UInt16 array if data is 16bit, fix endianness below + let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer); + + let maxValue = 255; + let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType]; + if (inBpp === 4 && !options.inputHasAlpha) { + inBpp = 3; + } + let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType]; + if (options.bitDepth === 16) { + maxValue = 65535; + outBpp *= 2; + } + let outData = Buffer.alloc(width * height * outBpp); + + let inIndex = 0; + let outIndex = 0; + + let bgColor = options.bgColor || {}; + if (bgColor.red === undefined) { + bgColor.red = maxValue; + } + if (bgColor.green === undefined) { + bgColor.green = maxValue; + } + if (bgColor.blue === undefined) { + bgColor.blue = maxValue; + } + + function getRGBA() { + let red; + let green; + let blue; + let alpha = maxValue; + switch (options.inputColorType) { + case constants.COLORTYPE_COLOR_ALPHA: + alpha = data[inIndex + 3]; + red = data[inIndex]; + green = data[inIndex + 1]; + blue = data[inIndex + 2]; + break; + case constants.COLORTYPE_COLOR: + red = data[inIndex]; + green = data[inIndex + 1]; + blue = data[inIndex + 2]; + break; + case constants.COLORTYPE_ALPHA: + alpha = data[inIndex + 1]; + red = data[inIndex]; + green = red; + blue = red; + break; + case constants.COLORTYPE_GRAYSCALE: + red = data[inIndex]; + green = red; + blue = red; + break; + default: + throw new Error( + "input color type:" + + options.inputColorType + + " is not supported at present" + ); + } + + if (options.inputHasAlpha) { + if (!outHasAlpha) { + alpha /= maxValue; + red = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), + maxValue + ); + green = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), + maxValue + ); + blue = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), + maxValue + ); + } + } + return { red: red, green: green, blue: blue, alpha: alpha }; + } + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let rgba = getRGBA(data, inIndex); + + switch (options.colorType) { + case constants.COLORTYPE_COLOR_ALPHA: + case constants.COLORTYPE_COLOR: + if (options.bitDepth === 8) { + outData[outIndex] = rgba.red; + outData[outIndex + 1] = rgba.green; + outData[outIndex + 2] = rgba.blue; + if (outHasAlpha) { + outData[outIndex + 3] = rgba.alpha; + } + } else { + outData.writeUInt16BE(rgba.red, outIndex); + outData.writeUInt16BE(rgba.green, outIndex + 2); + outData.writeUInt16BE(rgba.blue, outIndex + 4); + if (outHasAlpha) { + outData.writeUInt16BE(rgba.alpha, outIndex + 6); + } + } + break; + case constants.COLORTYPE_ALPHA: + case constants.COLORTYPE_GRAYSCALE: { + // Convert to grayscale and alpha + let grayscale = (rgba.red + rgba.green + rgba.blue) / 3; + if (options.bitDepth === 8) { + outData[outIndex] = grayscale; + if (outHasAlpha) { + outData[outIndex + 1] = rgba.alpha; + } + } else { + outData.writeUInt16BE(grayscale, outIndex); + if (outHasAlpha) { + outData.writeUInt16BE(rgba.alpha, outIndex + 2); + } + } + break; + } + default: + throw new Error("unrecognised color Type " + options.colorType); + } + + inIndex += inBpp; + outIndex += outBpp; + } + } + + return outData; +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./constants":81,"buffer":147}],80:[function(require,module,exports){ +(function (process,Buffer){(function (){ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); + +let ChunkStream = (module.exports = function () { + Stream.call(this); + + this._buffers = []; + this._buffered = 0; + + this._reads = []; + this._paused = false; + + this._encoding = "utf8"; + this.writable = true; +}); +util.inherits(ChunkStream, Stream); + +ChunkStream.prototype.read = function (length, callback) { + this._reads.push({ + length: Math.abs(length), // if length < 0 then at most this length + allowLess: length < 0, + func: callback, + }); + + process.nextTick( + function () { + this._process(); + + // its paused and there is not enought data then ask for more + if (this._paused && this._reads && this._reads.length > 0) { + this._paused = false; + + this.emit("drain"); + } + }.bind(this) + ); +}; + +ChunkStream.prototype.write = function (data, encoding) { + if (!this.writable) { + this.emit("error", new Error("Stream not writable")); + return false; + } + + let dataBuffer; + if (Buffer.isBuffer(data)) { + dataBuffer = data; + } else { + dataBuffer = Buffer.from(data, encoding || this._encoding); + } + + this._buffers.push(dataBuffer); + this._buffered += dataBuffer.length; + + this._process(); + + // ok if there are no more read requests + if (this._reads && this._reads.length === 0) { + this._paused = true; + } + + return this.writable && !this._paused; +}; + +ChunkStream.prototype.end = function (data, encoding) { + if (data) { + this.write(data, encoding); + } + + this.writable = false; + + // already destroyed + if (!this._buffers) { + return; + } + + // enqueue or handle end + if (this._buffers.length === 0) { + this._end(); + } else { + this._buffers.push(null); + this._process(); + } +}; + +ChunkStream.prototype.destroySoon = ChunkStream.prototype.end; + +ChunkStream.prototype._end = function () { + if (this._reads.length > 0) { + this.emit("error", new Error("Unexpected end of input")); + } + + this.destroy(); +}; + +ChunkStream.prototype.destroy = function () { + if (!this._buffers) { + return; + } + + this.writable = false; + this._reads = null; + this._buffers = null; + + this.emit("close"); +}; + +ChunkStream.prototype._processReadAllowingLess = function (read) { + // ok there is any data so that we can satisfy this request + this._reads.shift(); // == read + + // first we need to peek into first buffer + let smallerBuf = this._buffers[0]; + + // ok there is more data than we need + if (smallerBuf.length > read.length) { + this._buffered -= read.length; + this._buffers[0] = smallerBuf.slice(read.length); + + read.func.call(this, smallerBuf.slice(0, read.length)); + } else { + // ok this is less than maximum length so use it all + this._buffered -= smallerBuf.length; + this._buffers.shift(); // == smallerBuf + + read.func.call(this, smallerBuf); + } +}; + +ChunkStream.prototype._processRead = function (read) { + this._reads.shift(); // == read + + let pos = 0; + let count = 0; + let data = Buffer.alloc(read.length); + + // create buffer for all data + while (pos < read.length) { + let buf = this._buffers[count++]; + let len = Math.min(buf.length, read.length - pos); + + buf.copy(data, pos, 0, len); + pos += len; + + // last buffer wasn't used all so just slice it and leave + if (len !== buf.length) { + this._buffers[--count] = buf.slice(len); + } + } + + // remove all used buffers + if (count > 0) { + this._buffers.splice(0, count); + } + + this._buffered -= read.length; + + read.func.call(this, data); +}; + +ChunkStream.prototype._process = function () { + try { + // as long as there is any data and read requests + while (this._buffered > 0 && this._reads && this._reads.length > 0) { + let read = this._reads[0]; + + // read any data (but no more than length) + if (read.allowLess) { + this._processReadAllowingLess(read); + } else if (this._buffered >= read.length) { + // ok we can meet some expectations + + this._processRead(read); + } else { + // not enought data to satisfy first request in queue + // so we need to wait for more + break; + } + } + + if (this._buffers && !this.writable) { + this._end(); + } + } catch (ex) { + this.emit("error", ex); + } +}; + +}).call(this)}).call(this,require('_process'),require("buffer").Buffer) +},{"_process":179,"buffer":147,"stream":181,"util":202}],81:[function(require,module,exports){ +"use strict"; + +module.exports = { + PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], + + TYPE_IHDR: 0x49484452, + TYPE_IEND: 0x49454e44, + TYPE_IDAT: 0x49444154, + TYPE_PLTE: 0x504c5445, + TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase + TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase + + // color-type bits + COLORTYPE_GRAYSCALE: 0, + COLORTYPE_PALETTE: 1, + COLORTYPE_COLOR: 2, + COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha + + // color-type combinations + COLORTYPE_PALETTE_COLOR: 3, + COLORTYPE_COLOR_ALPHA: 6, + + COLORTYPE_TO_BPP_MAP: { + 0: 1, + 2: 3, + 3: 1, + 4: 2, + 6: 4, + }, + + GAMMA_DIVISION: 100000, +}; + +},{}],82:[function(require,module,exports){ +"use strict"; + +let crcTable = []; + +(function () { + for (let i = 0; i < 256; i++) { + let currentCrc = i; + for (let j = 0; j < 8; j++) { + if (currentCrc & 1) { + currentCrc = 0xedb88320 ^ (currentCrc >>> 1); + } else { + currentCrc = currentCrc >>> 1; + } + } + crcTable[i] = currentCrc; + } +})(); + +let CrcCalculator = (module.exports = function () { + this._crc = -1; +}); + +CrcCalculator.prototype.write = function (data) { + for (let i = 0; i < data.length; i++) { + this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8); + } + return true; +}; + +CrcCalculator.prototype.crc32 = function () { + return this._crc ^ -1; +}; + +CrcCalculator.crc32 = function (buf) { + let crc = -1; + for (let i = 0; i < buf.length; i++) { + crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); + } + return crc ^ -1; +}; + +},{}],83:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let paethPredictor = require("./paeth-predictor"); + +function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) { + for (let x = 0; x < byteWidth; x++) { + rawData[rawPos + x] = pxData[pxPos + x]; + } +} + +function filterSumNone(pxData, pxPos, byteWidth) { + let sum = 0; + let length = pxPos + byteWidth; + + for (let i = pxPos; i < length; i++) { + sum += Math.abs(pxData[i]); + } + return sum; +} + +function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let val = pxData[pxPos + x] - left; + + rawData[rawPos + x] = val; + } +} + +function filterSumSub(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let val = pxData[pxPos + x] - left; + + sum += Math.abs(val); + } + + return sum; +} + +function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) { + for (let x = 0; x < byteWidth; x++) { + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - up; + + rawData[rawPos + x] = val; + } +} + +function filterSumUp(pxData, pxPos, byteWidth) { + let sum = 0; + let length = pxPos + byteWidth; + for (let x = pxPos; x < length; x++) { + let up = pxPos > 0 ? pxData[x - byteWidth] : 0; + let val = pxData[x] - up; + + sum += Math.abs(val); + } + + return sum; +} + +function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - ((left + up) >> 1); + + rawData[rawPos + x] = val; + } +} + +function filterSumAvg(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - ((left + up) >> 1); + + sum += Math.abs(val); + } + + return sum; +} + +function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let upleft = + pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; + let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); + + rawData[rawPos + x] = val; + } +} + +function filterSumPaeth(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let upleft = + pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; + let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); + + sum += Math.abs(val); + } + + return sum; +} + +let filters = { + 0: filterNone, + 1: filterSub, + 2: filterUp, + 3: filterAvg, + 4: filterPaeth, +}; + +let filterSums = { + 0: filterSumNone, + 1: filterSumSub, + 2: filterSumUp, + 3: filterSumAvg, + 4: filterSumPaeth, +}; + +module.exports = function (pxData, width, height, options, bpp) { + let filterTypes; + if (!("filterType" in options) || options.filterType === -1) { + filterTypes = [0, 1, 2, 3, 4]; + } else if (typeof options.filterType === "number") { + filterTypes = [options.filterType]; + } else { + throw new Error("unrecognised filter types"); + } + + if (options.bitDepth === 16) { + bpp *= 2; + } + let byteWidth = width * bpp; + let rawPos = 0; + let pxPos = 0; + let rawData = Buffer.alloc((byteWidth + 1) * height); + + let sel = filterTypes[0]; + + for (let y = 0; y < height; y++) { + if (filterTypes.length > 1) { + // find best filter for this line (with lowest sum of values) + let min = Infinity; + + for (let i = 0; i < filterTypes.length; i++) { + let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp); + if (sum < min) { + sel = filterTypes[i]; + min = sum; + } + } + } + + rawData[rawPos] = sel; + rawPos++; + filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp); + rawPos += byteWidth; + pxPos += byteWidth; + } + return rawData; +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./paeth-predictor":92,"buffer":147}],84:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let util = require("util"); +let ChunkStream = require("./chunkstream"); +let Filter = require("./filter-parse"); + +let FilterAsync = (module.exports = function (bitmapInfo) { + ChunkStream.call(this); + + let buffers = []; + let that = this; + this._filter = new Filter(bitmapInfo, { + read: this.read.bind(this), + write: function (buffer) { + buffers.push(buffer); + }, + complete: function () { + that.emit("complete", Buffer.concat(buffers)); + }, + }); + + this._filter.start(); +}); +util.inherits(FilterAsync, ChunkStream); + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./chunkstream":80,"./filter-parse":86,"buffer":147,"util":202}],85:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let SyncReader = require("./sync-reader"); +let Filter = require("./filter-parse"); + +exports.process = function (inBuffer, bitmapInfo) { + let outBuffers = []; + let reader = new SyncReader(inBuffer); + let filter = new Filter(bitmapInfo, { + read: reader.read.bind(reader), + write: function (bufferPart) { + outBuffers.push(bufferPart); + }, + complete: function () {}, + }); + + filter.start(); + reader.process(); + + return Buffer.concat(outBuffers); +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./filter-parse":86,"./sync-reader":99,"buffer":147}],86:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let interlaceUtils = require("./interlace"); +let paethPredictor = require("./paeth-predictor"); + +function getByteWidth(width, bpp, depth) { + let byteWidth = width * bpp; + if (depth !== 8) { + byteWidth = Math.ceil(byteWidth / (8 / depth)); + } + return byteWidth; +} + +let Filter = (module.exports = function (bitmapInfo, dependencies) { + let width = bitmapInfo.width; + let height = bitmapInfo.height; + let interlace = bitmapInfo.interlace; + let bpp = bitmapInfo.bpp; + let depth = bitmapInfo.depth; + + this.read = dependencies.read; + this.write = dependencies.write; + this.complete = dependencies.complete; + + this._imageIndex = 0; + this._images = []; + if (interlace) { + let passes = interlaceUtils.getImagePasses(width, height); + for (let i = 0; i < passes.length; i++) { + this._images.push({ + byteWidth: getByteWidth(passes[i].width, bpp, depth), + height: passes[i].height, + lineIndex: 0, + }); + } + } else { + this._images.push({ + byteWidth: getByteWidth(width, bpp, depth), + height: height, + lineIndex: 0, + }); + } + + // when filtering the line we look at the pixel to the left + // the spec also says it is done on a byte level regardless of the number of pixels + // so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back + // a pixel rather than just a different byte part. However if we are sub byte, we ignore. + if (depth === 8) { + this._xComparison = bpp; + } else if (depth === 16) { + this._xComparison = bpp * 2; + } else { + this._xComparison = 1; + } +}); + +Filter.prototype.start = function () { + this.read( + this._images[this._imageIndex].byteWidth + 1, + this._reverseFilterLine.bind(this) + ); +}; + +Filter.prototype._unFilterType1 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + unfilteredLine[x] = rawByte + f1Left; + } +}; + +Filter.prototype._unFilterType2 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f2Up = lastLine ? lastLine[x] : 0; + unfilteredLine[x] = rawByte + f2Up; + } +}; + +Filter.prototype._unFilterType3 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f3Up = lastLine ? lastLine[x] : 0; + let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + let f3Add = Math.floor((f3Left + f3Up) / 2); + unfilteredLine[x] = rawByte + f3Add; + } +}; + +Filter.prototype._unFilterType4 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f4Up = lastLine ? lastLine[x] : 0; + let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; + let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); + unfilteredLine[x] = rawByte + f4Add; + } +}; + +Filter.prototype._reverseFilterLine = function (rawData) { + let filter = rawData[0]; + let unfilteredLine; + let currentImage = this._images[this._imageIndex]; + let byteWidth = currentImage.byteWidth; + + if (filter === 0) { + unfilteredLine = rawData.slice(1, byteWidth + 1); + } else { + unfilteredLine = Buffer.alloc(byteWidth); + + switch (filter) { + case 1: + this._unFilterType1(rawData, unfilteredLine, byteWidth); + break; + case 2: + this._unFilterType2(rawData, unfilteredLine, byteWidth); + break; + case 3: + this._unFilterType3(rawData, unfilteredLine, byteWidth); + break; + case 4: + this._unFilterType4(rawData, unfilteredLine, byteWidth); + break; + default: + throw new Error("Unrecognised filter type - " + filter); + } + } + + this.write(unfilteredLine); + + currentImage.lineIndex++; + if (currentImage.lineIndex >= currentImage.height) { + this._lastLine = null; + this._imageIndex++; + currentImage = this._images[this._imageIndex]; + } else { + this._lastLine = unfilteredLine; + } + + if (currentImage) { + // read, using the byte width that may be from the new current image + this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this)); + } else { + this._lastLine = null; + this.complete(); + } +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./interlace":88,"./paeth-predictor":92,"buffer":147}],87:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +function dePalette(indata, outdata, width, height, palette) { + let pxPos = 0; + // use values from palette + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let color = palette[indata[pxPos]]; + + if (!color) { + throw new Error("index " + indata[pxPos] + " not in palette"); + } + + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = color[i]; + } + pxPos += 4; + } + } +} + +function replaceTransparentColor(indata, outdata, width, height, transColor) { + let pxPos = 0; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let makeTrans = false; + + if (transColor.length === 1) { + if (transColor[0] === indata[pxPos]) { + makeTrans = true; + } + } else if ( + transColor[0] === indata[pxPos] && + transColor[1] === indata[pxPos + 1] && + transColor[2] === indata[pxPos + 2] + ) { + makeTrans = true; + } + if (makeTrans) { + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = 0; + } + } + pxPos += 4; + } + } +} + +function scaleDepth(indata, outdata, width, height, depth) { + let maxOutSample = 255; + let maxInSample = Math.pow(2, depth) - 1; + let pxPos = 0; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = Math.floor( + (indata[pxPos + i] * maxOutSample) / maxInSample + 0.5 + ); + } + pxPos += 4; + } + } +} + +module.exports = function (indata, imageData) { + let depth = imageData.depth; + let width = imageData.width; + let height = imageData.height; + let colorType = imageData.colorType; + let transColor = imageData.transColor; + let palette = imageData.palette; + + let outdata = indata; // only different for 16 bits + + if (colorType === 3) { + // paletted + dePalette(indata, outdata, width, height, palette); + } else { + if (transColor) { + replaceTransparentColor(indata, outdata, width, height, transColor); + } + // if it needs scaling + if (depth !== 8) { + // if we need to change the buffer size + if (depth === 16) { + outdata = Buffer.alloc(width * height * 4); + } + scaleDepth(indata, outdata, width, height, depth); + } + } + return outdata; +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"buffer":147}],88:[function(require,module,exports){ +"use strict"; + +// Adam 7 +// 0 1 2 3 4 5 6 7 +// 0 x 6 4 6 x 6 4 6 +// 1 7 7 7 7 7 7 7 7 +// 2 5 6 5 6 5 6 5 6 +// 3 7 7 7 7 7 7 7 7 +// 4 3 6 4 6 3 6 4 6 +// 5 7 7 7 7 7 7 7 7 +// 6 5 6 5 6 5 6 5 6 +// 7 7 7 7 7 7 7 7 7 + +let imagePasses = [ + { + // pass 1 - 1px + x: [0], + y: [0], + }, + { + // pass 2 - 1px + x: [4], + y: [0], + }, + { + // pass 3 - 2px + x: [0, 4], + y: [4], + }, + { + // pass 4 - 4px + x: [2, 6], + y: [0, 4], + }, + { + // pass 5 - 8px + x: [0, 2, 4, 6], + y: [2, 6], + }, + { + // pass 6 - 16px + x: [1, 3, 5, 7], + y: [0, 2, 4, 6], + }, + { + // pass 7 - 32px + x: [0, 1, 2, 3, 4, 5, 6, 7], + y: [1, 3, 5, 7], + }, +]; + +exports.getImagePasses = function (width, height) { + let images = []; + let xLeftOver = width % 8; + let yLeftOver = height % 8; + let xRepeats = (width - xLeftOver) / 8; + let yRepeats = (height - yLeftOver) / 8; + for (let i = 0; i < imagePasses.length; i++) { + let pass = imagePasses[i]; + let passWidth = xRepeats * pass.x.length; + let passHeight = yRepeats * pass.y.length; + for (let j = 0; j < pass.x.length; j++) { + if (pass.x[j] < xLeftOver) { + passWidth++; + } else { + break; + } + } + for (let j = 0; j < pass.y.length; j++) { + if (pass.y[j] < yLeftOver) { + passHeight++; + } else { + break; + } + } + if (passWidth > 0 && passHeight > 0) { + images.push({ width: passWidth, height: passHeight, index: i }); + } + } + return images; +}; + +exports.getInterlaceIterator = function (width) { + return function (x, y, pass) { + let outerXLeftOver = x % imagePasses[pass].x.length; + let outerX = + ((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 + + imagePasses[pass].x[outerXLeftOver]; + let outerYLeftOver = y % imagePasses[pass].y.length; + let outerY = + ((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 + + imagePasses[pass].y[outerYLeftOver]; + return outerX * 4 + outerY * width * 4; + }; +}; + +},{}],89:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); +let constants = require("./constants"); +let Packer = require("./packer"); + +let PackerAsync = (module.exports = function (opt) { + Stream.call(this); + + let options = opt || {}; + + this._packer = new Packer(options); + this._deflate = this._packer.createDeflate(); + + this.readable = true; +}); +util.inherits(PackerAsync, Stream); + +PackerAsync.prototype.pack = function (data, width, height, gamma) { + // Signature + this.emit("data", Buffer.from(constants.PNG_SIGNATURE)); + this.emit("data", this._packer.packIHDR(width, height)); + + if (gamma) { + this.emit("data", this._packer.packGAMA(gamma)); + } + + let filteredData = this._packer.filterData(data, width, height); + + // compress it + this._deflate.on("error", this.emit.bind(this, "error")); + + this._deflate.on( + "data", + function (compressedData) { + this.emit("data", this._packer.packIDAT(compressedData)); + }.bind(this) + ); + + this._deflate.on( + "end", + function () { + this.emit("data", this._packer.packIEND()); + this.emit("end"); + }.bind(this) + ); + + this._deflate.end(filteredData); +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./constants":81,"./packer":91,"buffer":147,"stream":181,"util":202}],90:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let hasSyncZlib = true; +let zlib = require("zlib"); +if (!zlib.deflateSync) { + hasSyncZlib = false; +} +let constants = require("./constants"); +let Packer = require("./packer"); + +module.exports = function (metaData, opt) { + if (!hasSyncZlib) { + throw new Error( + "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" + ); + } + + let options = opt || {}; + + let packer = new Packer(options); + + let chunks = []; + + // Signature + chunks.push(Buffer.from(constants.PNG_SIGNATURE)); + + // Header + chunks.push(packer.packIHDR(metaData.width, metaData.height)); + + if (metaData.gamma) { + chunks.push(packer.packGAMA(metaData.gamma)); + } + + let filteredData = packer.filterData( + metaData.data, + metaData.width, + metaData.height + ); + + // compress it + let compressedData = zlib.deflateSync( + filteredData, + packer.getDeflateOptions() + ); + filteredData = null; + + if (!compressedData || !compressedData.length) { + throw new Error("bad png - invalid compressed data response"); + } + chunks.push(packer.packIDAT(compressedData)); + + // End + chunks.push(packer.packIEND()); + + return Buffer.concat(chunks); +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./constants":81,"./packer":91,"buffer":147,"zlib":146}],91:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let constants = require("./constants"); +let CrcStream = require("./crc"); +let bitPacker = require("./bitpacker"); +let filter = require("./filter-pack"); +let zlib = require("zlib"); + +let Packer = (module.exports = function (options) { + this._options = options; + + options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; + options.deflateLevel = + options.deflateLevel != null ? options.deflateLevel : 9; + options.deflateStrategy = + options.deflateStrategy != null ? options.deflateStrategy : 3; + options.inputHasAlpha = + options.inputHasAlpha != null ? options.inputHasAlpha : true; + options.deflateFactory = options.deflateFactory || zlib.createDeflate; + options.bitDepth = options.bitDepth || 8; + // This is outputColorType + options.colorType = + typeof options.colorType === "number" + ? options.colorType + : constants.COLORTYPE_COLOR_ALPHA; + options.inputColorType = + typeof options.inputColorType === "number" + ? options.inputColorType + : constants.COLORTYPE_COLOR_ALPHA; + + if ( + [ + constants.COLORTYPE_GRAYSCALE, + constants.COLORTYPE_COLOR, + constants.COLORTYPE_COLOR_ALPHA, + constants.COLORTYPE_ALPHA, + ].indexOf(options.colorType) === -1 + ) { + throw new Error( + "option color type:" + options.colorType + " is not supported at present" + ); + } + if ( + [ + constants.COLORTYPE_GRAYSCALE, + constants.COLORTYPE_COLOR, + constants.COLORTYPE_COLOR_ALPHA, + constants.COLORTYPE_ALPHA, + ].indexOf(options.inputColorType) === -1 + ) { + throw new Error( + "option input color type:" + + options.inputColorType + + " is not supported at present" + ); + } + if (options.bitDepth !== 8 && options.bitDepth !== 16) { + throw new Error( + "option bit depth:" + options.bitDepth + " is not supported at present" + ); + } +}); + +Packer.prototype.getDeflateOptions = function () { + return { + chunkSize: this._options.deflateChunkSize, + level: this._options.deflateLevel, + strategy: this._options.deflateStrategy, + }; +}; + +Packer.prototype.createDeflate = function () { + return this._options.deflateFactory(this.getDeflateOptions()); +}; + +Packer.prototype.filterData = function (data, width, height) { + // convert to correct format for filtering (e.g. right bpp and bit depth) + let packedData = bitPacker(data, width, height, this._options); + + // filter pixel data + let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; + let filteredData = filter(packedData, width, height, this._options, bpp); + return filteredData; +}; + +Packer.prototype._packChunk = function (type, data) { + let len = data ? data.length : 0; + let buf = Buffer.alloc(len + 12); + + buf.writeUInt32BE(len, 0); + buf.writeUInt32BE(type, 4); + + if (data) { + data.copy(buf, 8); + } + + buf.writeInt32BE( + CrcStream.crc32(buf.slice(4, buf.length - 4)), + buf.length - 4 + ); + return buf; +}; + +Packer.prototype.packGAMA = function (gamma) { + let buf = Buffer.alloc(4); + buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); + return this._packChunk(constants.TYPE_gAMA, buf); +}; + +Packer.prototype.packIHDR = function (width, height) { + let buf = Buffer.alloc(13); + buf.writeUInt32BE(width, 0); + buf.writeUInt32BE(height, 4); + buf[8] = this._options.bitDepth; // Bit depth + buf[9] = this._options.colorType; // colorType + buf[10] = 0; // compression + buf[11] = 0; // filter + buf[12] = 0; // interlace + + return this._packChunk(constants.TYPE_IHDR, buf); +}; + +Packer.prototype.packIDAT = function (data) { + return this._packChunk(constants.TYPE_IDAT, data); +}; + +Packer.prototype.packIEND = function () { + return this._packChunk(constants.TYPE_IEND, null); +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./bitpacker":79,"./constants":81,"./crc":82,"./filter-pack":83,"buffer":147,"zlib":146}],92:[function(require,module,exports){ +"use strict"; + +module.exports = function paethPredictor(left, above, upLeft) { + let paeth = left + above - upLeft; + let pLeft = Math.abs(paeth - left); + let pAbove = Math.abs(paeth - above); + let pUpLeft = Math.abs(paeth - upLeft); + + if (pLeft <= pAbove && pLeft <= pUpLeft) { + return left; + } + if (pAbove <= pUpLeft) { + return above; + } + return upLeft; +}; + +},{}],93:[function(require,module,exports){ +"use strict"; + +let util = require("util"); +let zlib = require("zlib"); +let ChunkStream = require("./chunkstream"); +let FilterAsync = require("./filter-parse-async"); +let Parser = require("./parser"); +let bitmapper = require("./bitmapper"); +let formatNormaliser = require("./format-normaliser"); + +let ParserAsync = (module.exports = function (options) { + ChunkStream.call(this); + + this._parser = new Parser(options, { + read: this.read.bind(this), + error: this._handleError.bind(this), + metadata: this._handleMetaData.bind(this), + gamma: this.emit.bind(this, "gamma"), + palette: this._handlePalette.bind(this), + transColor: this._handleTransColor.bind(this), + finished: this._finished.bind(this), + inflateData: this._inflateData.bind(this), + simpleTransparency: this._simpleTransparency.bind(this), + headersFinished: this._headersFinished.bind(this), + }); + this._options = options; + this.writable = true; + + this._parser.start(); +}); +util.inherits(ParserAsync, ChunkStream); + +ParserAsync.prototype._handleError = function (err) { + this.emit("error", err); + + this.writable = false; + + this.destroy(); + + if (this._inflate && this._inflate.destroy) { + this._inflate.destroy(); + } + + if (this._filter) { + this._filter.destroy(); + // For backward compatibility with Node 7 and below. + // Suppress errors due to _inflate calling write() even after + // it's destroy()'ed. + this._filter.on("error", function () {}); + } + + this.errord = true; +}; + +ParserAsync.prototype._inflateData = function (data) { + if (!this._inflate) { + if (this._bitmapInfo.interlace) { + this._inflate = zlib.createInflate(); + + this._inflate.on("error", this.emit.bind(this, "error")); + this._filter.on("complete", this._complete.bind(this)); + + this._inflate.pipe(this._filter); + } else { + let rowSize = + ((this._bitmapInfo.width * + this._bitmapInfo.bpp * + this._bitmapInfo.depth + + 7) >> + 3) + + 1; + let imageSize = rowSize * this._bitmapInfo.height; + let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); + + this._inflate = zlib.createInflate({ chunkSize: chunkSize }); + let leftToInflate = imageSize; + + let emitError = this.emit.bind(this, "error"); + this._inflate.on("error", function (err) { + if (!leftToInflate) { + return; + } + + emitError(err); + }); + this._filter.on("complete", this._complete.bind(this)); + + let filterWrite = this._filter.write.bind(this._filter); + this._inflate.on("data", function (chunk) { + if (!leftToInflate) { + return; + } + + if (chunk.length > leftToInflate) { + chunk = chunk.slice(0, leftToInflate); + } + + leftToInflate -= chunk.length; + + filterWrite(chunk); + }); + + this._inflate.on("end", this._filter.end.bind(this._filter)); + } + } + this._inflate.write(data); +}; + +ParserAsync.prototype._handleMetaData = function (metaData) { + this._metaData = metaData; + this._bitmapInfo = Object.create(metaData); + + this._filter = new FilterAsync(this._bitmapInfo); +}; + +ParserAsync.prototype._handleTransColor = function (transColor) { + this._bitmapInfo.transColor = transColor; +}; + +ParserAsync.prototype._handlePalette = function (palette) { + this._bitmapInfo.palette = palette; +}; + +ParserAsync.prototype._simpleTransparency = function () { + this._metaData.alpha = true; +}; + +ParserAsync.prototype._headersFinished = function () { + // Up until this point, we don't know if we have a tRNS chunk (alpha) + // so we can't emit metadata any earlier + this.emit("metadata", this._metaData); +}; + +ParserAsync.prototype._finished = function () { + if (this.errord) { + return; + } + + if (!this._inflate) { + this.emit("error", "No Inflate block"); + } else { + // no more data to inflate + this._inflate.end(); + } +}; + +ParserAsync.prototype._complete = function (filteredData) { + if (this.errord) { + return; + } + + let normalisedBitmapData; + + try { + let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo); + + normalisedBitmapData = formatNormaliser(bitmapData, this._bitmapInfo); + bitmapData = null; + } catch (ex) { + this._handleError(ex); + return; + } + + this.emit("parsed", normalisedBitmapData); +}; + +},{"./bitmapper":78,"./chunkstream":80,"./filter-parse-async":84,"./format-normaliser":87,"./parser":95,"util":202,"zlib":146}],94:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let hasSyncZlib = true; +let zlib = require("zlib"); +let inflateSync = require("./sync-inflate"); +if (!zlib.deflateSync) { + hasSyncZlib = false; +} +let SyncReader = require("./sync-reader"); +let FilterSync = require("./filter-parse-sync"); +let Parser = require("./parser"); +let bitmapper = require("./bitmapper"); +let formatNormaliser = require("./format-normaliser"); + +module.exports = function (buffer, options) { + if (!hasSyncZlib) { + throw new Error( + "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" + ); + } + + let err; + function handleError(_err_) { + err = _err_; + } + + let metaData; + function handleMetaData(_metaData_) { + metaData = _metaData_; + } + + function handleTransColor(transColor) { + metaData.transColor = transColor; + } + + function handlePalette(palette) { + metaData.palette = palette; + } + + function handleSimpleTransparency() { + metaData.alpha = true; + } + + let gamma; + function handleGamma(_gamma_) { + gamma = _gamma_; + } + + let inflateDataList = []; + function handleInflateData(inflatedData) { + inflateDataList.push(inflatedData); + } + + let reader = new SyncReader(buffer); + + let parser = new Parser(options, { + read: reader.read.bind(reader), + error: handleError, + metadata: handleMetaData, + gamma: handleGamma, + palette: handlePalette, + transColor: handleTransColor, + inflateData: handleInflateData, + simpleTransparency: handleSimpleTransparency, + }); + + parser.start(); + reader.process(); + + if (err) { + throw err; + } + + //join together the inflate datas + let inflateData = Buffer.concat(inflateDataList); + inflateDataList.length = 0; + + let inflatedData; + if (metaData.interlace) { + inflatedData = zlib.inflateSync(inflateData); + } else { + let rowSize = + ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; + let imageSize = rowSize * metaData.height; + inflatedData = inflateSync(inflateData, { + chunkSize: imageSize, + maxLength: imageSize, + }); + } + inflateData = null; + + if (!inflatedData || !inflatedData.length) { + throw new Error("bad png - invalid inflate data response"); + } + + let unfilteredData = FilterSync.process(inflatedData, metaData); + inflateData = null; + + let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData); + unfilteredData = null; + + let normalisedBitmapData = formatNormaliser(bitmapData, metaData); + + metaData.data = normalisedBitmapData; + metaData.gamma = gamma || 0; + + return metaData; +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./bitmapper":78,"./filter-parse-sync":85,"./format-normaliser":87,"./parser":95,"./sync-inflate":98,"./sync-reader":99,"buffer":147,"zlib":146}],95:[function(require,module,exports){ +(function (Buffer){(function (){ +"use strict"; + +let constants = require("./constants"); +let CrcCalculator = require("./crc"); + +let Parser = (module.exports = function (options, dependencies) { + this._options = options; + options.checkCRC = options.checkCRC !== false; + + this._hasIHDR = false; + this._hasIEND = false; + this._emittedHeadersFinished = false; + + // input flags/metadata + this._palette = []; + this._colorType = 0; + + this._chunks = {}; + this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this); + this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this); + this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this); + this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this); + this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this); + this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this); + + this.read = dependencies.read; + this.error = dependencies.error; + this.metadata = dependencies.metadata; + this.gamma = dependencies.gamma; + this.transColor = dependencies.transColor; + this.palette = dependencies.palette; + this.parsed = dependencies.parsed; + this.inflateData = dependencies.inflateData; + this.finished = dependencies.finished; + this.simpleTransparency = dependencies.simpleTransparency; + this.headersFinished = dependencies.headersFinished || function () {}; +}); + +Parser.prototype.start = function () { + this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this)); +}; + +Parser.prototype._parseSignature = function (data) { + let signature = constants.PNG_SIGNATURE; + + for (let i = 0; i < signature.length; i++) { + if (data[i] !== signature[i]) { + this.error(new Error("Invalid file signature")); + return; + } + } + this.read(8, this._parseChunkBegin.bind(this)); +}; + +Parser.prototype._parseChunkBegin = function (data) { + // chunk content length + let length = data.readUInt32BE(0); + + // chunk type + let type = data.readUInt32BE(4); + let name = ""; + for (let i = 4; i < 8; i++) { + name += String.fromCharCode(data[i]); + } + + //console.log('chunk ', name, length); + + // chunk flags + let ancillary = Boolean(data[4] & 0x20); // or critical + // priv = Boolean(data[5] & 0x20), // or public + // safeToCopy = Boolean(data[7] & 0x20); // or unsafe + + if (!this._hasIHDR && type !== constants.TYPE_IHDR) { + this.error(new Error("Expected IHDR on beggining")); + return; + } + + this._crc = new CrcCalculator(); + this._crc.write(Buffer.from(name)); + + if (this._chunks[type]) { + return this._chunks[type](length); + } + + if (!ancillary) { + this.error(new Error("Unsupported critical chunk type " + name)); + return; + } + + this.read(length + 4, this._skipChunk.bind(this)); +}; + +Parser.prototype._skipChunk = function (/*data*/) { + this.read(8, this._parseChunkBegin.bind(this)); +}; + +Parser.prototype._handleChunkEnd = function () { + this.read(4, this._parseChunkEnd.bind(this)); +}; + +Parser.prototype._parseChunkEnd = function (data) { + let fileCrc = data.readInt32BE(0); + let calcCrc = this._crc.crc32(); + + // check CRC + if (this._options.checkCRC && calcCrc !== fileCrc) { + this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc)); + return; + } + + if (!this._hasIEND) { + this.read(8, this._parseChunkBegin.bind(this)); + } +}; + +Parser.prototype._handleIHDR = function (length) { + this.read(length, this._parseIHDR.bind(this)); +}; +Parser.prototype._parseIHDR = function (data) { + this._crc.write(data); + + let width = data.readUInt32BE(0); + let height = data.readUInt32BE(4); + let depth = data[8]; + let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha + let compr = data[10]; + let filter = data[11]; + let interlace = data[12]; + + // console.log(' width', width, 'height', height, + // 'depth', depth, 'colorType', colorType, + // 'compr', compr, 'filter', filter, 'interlace', interlace + // ); + + if ( + depth !== 8 && + depth !== 4 && + depth !== 2 && + depth !== 1 && + depth !== 16 + ) { + this.error(new Error("Unsupported bit depth " + depth)); + return; + } + if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) { + this.error(new Error("Unsupported color type")); + return; + } + if (compr !== 0) { + this.error(new Error("Unsupported compression method")); + return; + } + if (filter !== 0) { + this.error(new Error("Unsupported filter method")); + return; + } + if (interlace !== 0 && interlace !== 1) { + this.error(new Error("Unsupported interlace method")); + return; + } + + this._colorType = colorType; + + let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType]; + + this._hasIHDR = true; + + this.metadata({ + width: width, + height: height, + depth: depth, + interlace: Boolean(interlace), + palette: Boolean(colorType & constants.COLORTYPE_PALETTE), + color: Boolean(colorType & constants.COLORTYPE_COLOR), + alpha: Boolean(colorType & constants.COLORTYPE_ALPHA), + bpp: bpp, + colorType: colorType, + }); + + this._handleChunkEnd(); +}; + +Parser.prototype._handlePLTE = function (length) { + this.read(length, this._parsePLTE.bind(this)); +}; +Parser.prototype._parsePLTE = function (data) { + this._crc.write(data); + + let entries = Math.floor(data.length / 3); + // console.log('Palette:', entries); + + for (let i = 0; i < entries; i++) { + this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]); + } + + this.palette(this._palette); + + this._handleChunkEnd(); +}; + +Parser.prototype._handleTRNS = function (length) { + this.simpleTransparency(); + this.read(length, this._parseTRNS.bind(this)); +}; +Parser.prototype._parseTRNS = function (data) { + this._crc.write(data); + + // palette + if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) { + if (this._palette.length === 0) { + this.error(new Error("Transparency chunk must be after palette")); + return; + } + if (data.length > this._palette.length) { + this.error(new Error("More transparent colors than palette size")); + return; + } + for (let i = 0; i < data.length; i++) { + this._palette[i][3] = data[i]; + } + this.palette(this._palette); + } + + // for colorType 0 (grayscale) and 2 (rgb) + // there might be one gray/color defined as transparent + if (this._colorType === constants.COLORTYPE_GRAYSCALE) { + // grey, 2 bytes + this.transColor([data.readUInt16BE(0)]); + } + if (this._colorType === constants.COLORTYPE_COLOR) { + this.transColor([ + data.readUInt16BE(0), + data.readUInt16BE(2), + data.readUInt16BE(4), + ]); + } + + this._handleChunkEnd(); +}; + +Parser.prototype._handleGAMA = function (length) { + this.read(length, this._parseGAMA.bind(this)); +}; +Parser.prototype._parseGAMA = function (data) { + this._crc.write(data); + this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION); + + this._handleChunkEnd(); +}; + +Parser.prototype._handleIDAT = function (length) { + if (!this._emittedHeadersFinished) { + this._emittedHeadersFinished = true; + this.headersFinished(); + } + this.read(-length, this._parseIDAT.bind(this, length)); +}; +Parser.prototype._parseIDAT = function (length, data) { + this._crc.write(data); + + if ( + this._colorType === constants.COLORTYPE_PALETTE_COLOR && + this._palette.length === 0 + ) { + throw new Error("Expected palette not found"); + } + + this.inflateData(data); + let leftOverLength = length - data.length; + + if (leftOverLength > 0) { + this._handleIDAT(leftOverLength); + } else { + this._handleChunkEnd(); + } +}; + +Parser.prototype._handleIEND = function (length) { + this.read(length, this._parseIEND.bind(this)); +}; +Parser.prototype._parseIEND = function (data) { + this._crc.write(data); + + this._hasIEND = true; + this._handleChunkEnd(); + + if (this.finished) { + this.finished(); + } +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./constants":81,"./crc":82,"buffer":147}],96:[function(require,module,exports){ +"use strict"; + +let parse = require("./parser-sync"); +let pack = require("./packer-sync"); + +exports.read = function (buffer, options) { + return parse(buffer, options || {}); +}; + +exports.write = function (png, options) { + return pack(png, options); +}; + +},{"./packer-sync":90,"./parser-sync":94}],97:[function(require,module,exports){ +(function (process,Buffer){(function (){ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); +let Parser = require("./parser-async"); +let Packer = require("./packer-async"); +let PNGSync = require("./png-sync"); + +let PNG = (exports.PNG = function (options) { + Stream.call(this); + + options = options || {}; // eslint-disable-line no-param-reassign + + // coerce pixel dimensions to integers (also coerces undefined -> 0): + this.width = options.width | 0; + this.height = options.height | 0; + + this.data = + this.width > 0 && this.height > 0 + ? Buffer.alloc(4 * this.width * this.height) + : null; + + if (options.fill && this.data) { + this.data.fill(0); + } + + this.gamma = 0; + this.readable = this.writable = true; + + this._parser = new Parser(options); + + this._parser.on("error", this.emit.bind(this, "error")); + this._parser.on("close", this._handleClose.bind(this)); + this._parser.on("metadata", this._metadata.bind(this)); + this._parser.on("gamma", this._gamma.bind(this)); + this._parser.on( + "parsed", + function (data) { + this.data = data; + this.emit("parsed", data); + }.bind(this) + ); + + this._packer = new Packer(options); + this._packer.on("data", this.emit.bind(this, "data")); + this._packer.on("end", this.emit.bind(this, "end")); + this._parser.on("close", this._handleClose.bind(this)); + this._packer.on("error", this.emit.bind(this, "error")); +}); +util.inherits(PNG, Stream); + +PNG.sync = PNGSync; + +PNG.prototype.pack = function () { + if (!this.data || !this.data.length) { + this.emit("error", "No data provided"); + return this; + } + + process.nextTick( + function () { + this._packer.pack(this.data, this.width, this.height, this.gamma); + }.bind(this) + ); + + return this; +}; + +PNG.prototype.parse = function (data, callback) { + if (callback) { + let onParsed, onError; + + onParsed = function (parsedData) { + this.removeListener("error", onError); + + this.data = parsedData; + callback(null, this); + }.bind(this); + + onError = function (err) { + this.removeListener("parsed", onParsed); + + callback(err, null); + }.bind(this); + + this.once("parsed", onParsed); + this.once("error", onError); + } + + this.end(data); + return this; +}; + +PNG.prototype.write = function (data) { + this._parser.write(data); + return true; +}; + +PNG.prototype.end = function (data) { + this._parser.end(data); +}; + +PNG.prototype._metadata = function (metadata) { + this.width = metadata.width; + this.height = metadata.height; + + this.emit("metadata", metadata); +}; + +PNG.prototype._gamma = function (gamma) { + this.gamma = gamma; +}; + +PNG.prototype._handleClose = function () { + if (!this._parser.writable && !this._packer.readable) { + this.emit("close"); + } +}; + +PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) { + // eslint-disable-line max-params + // coerce pixel dimensions to integers (also coerces undefined -> 0): + /* eslint-disable no-param-reassign */ + srcX |= 0; + srcY |= 0; + width |= 0; + height |= 0; + deltaX |= 0; + deltaY |= 0; + /* eslint-enable no-param-reassign */ + + if ( + srcX > src.width || + srcY > src.height || + srcX + width > src.width || + srcY + height > src.height + ) { + throw new Error("bitblt reading outside image"); + } + + if ( + deltaX > dst.width || + deltaY > dst.height || + deltaX + width > dst.width || + deltaY + height > dst.height + ) { + throw new Error("bitblt writing outside image"); + } + + for (let y = 0; y < height; y++) { + src.data.copy( + dst.data, + ((deltaY + y) * dst.width + deltaX) << 2, + ((srcY + y) * src.width + srcX) << 2, + ((srcY + y) * src.width + srcX + width) << 2 + ); + } +}; + +PNG.prototype.bitblt = function ( + dst, + srcX, + srcY, + width, + height, + deltaX, + deltaY +) { + // eslint-disable-line max-params + + PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY); + return this; +}; + +PNG.adjustGamma = function (src) { + if (src.gamma) { + for (let y = 0; y < src.height; y++) { + for (let x = 0; x < src.width; x++) { + let idx = (src.width * y + x) << 2; + + for (let i = 0; i < 3; i++) { + let sample = src.data[idx + i] / 255; + sample = Math.pow(sample, 1 / 2.2 / src.gamma); + src.data[idx + i] = Math.round(sample * 255); + } + } + } + src.gamma = 0; + } +}; + +PNG.prototype.adjustGamma = function () { + PNG.adjustGamma(this); +}; + +}).call(this)}).call(this,require('_process'),require("buffer").Buffer) +},{"./packer-async":89,"./parser-async":93,"./png-sync":96,"_process":179,"buffer":147,"stream":181,"util":202}],98:[function(require,module,exports){ +(function (process,Buffer){(function (){ +"use strict"; + +let assert = require("assert").ok; +let zlib = require("zlib"); +let util = require("util"); + +let kMaxLength = require("buffer").kMaxLength; + +function Inflate(opts) { + if (!(this instanceof Inflate)) { + return new Inflate(opts); + } + + if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) { + opts.chunkSize = zlib.Z_MIN_CHUNK; + } + + zlib.Inflate.call(this, opts); + + // Node 8 --> 9 compatibility check + this._offset = this._offset === undefined ? this._outOffset : this._offset; + this._buffer = this._buffer || this._outBuffer; + + if (opts && opts.maxLength != null) { + this._maxLength = opts.maxLength; + } +} + +function createInflate(opts) { + return new Inflate(opts); +} + +function _close(engine, callback) { + if (callback) { + process.nextTick(callback); + } + + // Caller may invoke .close after a zlib error (which will null _handle). + if (!engine._handle) { + return; + } + + engine._handle.close(); + engine._handle = null; +} + +Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) { + if (typeof asyncCb === "function") { + return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb); + } + + let self = this; + + let availInBefore = chunk && chunk.length; + let availOutBefore = this._chunkSize - this._offset; + let leftToInflate = this._maxLength; + let inOff = 0; + + let buffers = []; + let nread = 0; + + let error; + this.on("error", function (err) { + error = err; + }); + + function handleChunk(availInAfter, availOutAfter) { + if (self._hadError) { + return; + } + + let have = availOutBefore - availOutAfter; + assert(have >= 0, "have should not go down"); + + if (have > 0) { + let out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + + if (out.length > leftToInflate) { + out = out.slice(0, leftToInflate); + } + + buffers.push(out); + nread += out.length; + leftToInflate -= out.length; + + if (leftToInflate === 0) { + return false; + } + } + + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + inOff += availInBefore - availInAfter; + availInBefore = availInAfter; + + return true; + } + + return false; + } + + assert(this._handle, "zlib binding closed"); + let res; + do { + res = this._handle.writeSync( + flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore + ); // out_len + // Node 8 --> 9 compatibility check + res = res || this._writeState; + } while (!this._hadError && handleChunk(res[0], res[1])); + + if (this._hadError) { + throw error; + } + + if (nread >= kMaxLength) { + _close(this); + throw new RangeError( + "Cannot create final Buffer. It would be larger than 0x" + + kMaxLength.toString(16) + + " bytes" + ); + } + + let buf = Buffer.concat(buffers, nread); + _close(this); + + return buf; +}; + +util.inherits(Inflate, zlib.Inflate); + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } + if (!(buffer instanceof Buffer)) { + throw new TypeError("Not a string or buffer"); + } + + let flushFlag = engine._finishFlushFlag; + if (flushFlag == null) { + flushFlag = zlib.Z_FINISH; + } + + return engine._processChunk(buffer, flushFlag); +} + +function inflateSync(buffer, opts) { + return zlibBufferSync(new Inflate(opts), buffer); +} + +module.exports = exports = inflateSync; +exports.Inflate = Inflate; +exports.createInflate = createInflate; +exports.inflateSync = inflateSync; + +}).call(this)}).call(this,require('_process'),require("buffer").Buffer) +},{"_process":179,"assert":138,"buffer":147,"util":202,"zlib":146}],99:[function(require,module,exports){ +"use strict"; + +let SyncReader = (module.exports = function (buffer) { + this._buffer = buffer; + this._reads = []; +}); + +SyncReader.prototype.read = function (length, callback) { + this._reads.push({ + length: Math.abs(length), // if length < 0 then at most this length + allowLess: length < 0, + func: callback, + }); +}; + +SyncReader.prototype.process = function () { + // as long as there is any data and read requests + while (this._reads.length > 0 && this._buffer.length) { + let read = this._reads[0]; + + if ( + this._buffer.length && + (this._buffer.length >= read.length || read.allowLess) + ) { + // ok there is any data so that we can satisfy this request + this._reads.shift(); // == read + + let buf = this._buffer; + + this._buffer = buf.slice(read.length); + + read.func.call(this, buf.slice(0, read.length)); + } else { + break; + } + } + + if (this._reads.length > 0) { + return new Error("There are some read requests waitng on finished stream"); + } + + if (this._buffer.length > 0) { + return new Error("unrecognised content at end of stream"); + } +}; + +},{}],100:[function(require,module,exports){ +(function (process,global){(function (){ +(function (global, undefined) { + "use strict"; + + if (global.setImmediate) { + return; + } + + var nextHandle = 1; // Spec says greater than zero + var tasksByHandle = {}; + var currentlyRunningATask = false; + var doc = global.document; + var registerImmediate; + + function setImmediate(callback) { + // Callback can either be a function or a string + if (typeof callback !== "function") { + callback = new Function("" + callback); + } + // Copy function arguments + var args = new Array(arguments.length - 1); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i + 1]; + } + // Store and register the task + var task = { callback: callback, args: args }; + tasksByHandle[nextHandle] = task; + registerImmediate(nextHandle); + return nextHandle++; + } + + function clearImmediate(handle) { + delete tasksByHandle[handle]; + } + + function run(task) { + var callback = task.callback; + var args = task.args; + switch (args.length) { + case 0: + callback(); + break; + case 1: + callback(args[0]); + break; + case 2: + callback(args[0], args[1]); + break; + case 3: + callback(args[0], args[1], args[2]); + break; + default: + callback.apply(undefined, args); + break; + } + } + + function runIfPresent(handle) { + // From the spec: "Wait until any invocations of this algorithm started before this one have completed." + // So if we're currently running a task, we'll need to delay this invocation. + if (currentlyRunningATask) { + // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a + // "too much recursion" error. + setTimeout(runIfPresent, 0, handle); + } else { + var task = tasksByHandle[handle]; + if (task) { + currentlyRunningATask = true; + try { + run(task); + } finally { + clearImmediate(handle); + currentlyRunningATask = false; + } + } + } + } + + function installNextTickImplementation() { + registerImmediate = function(handle) { + process.nextTick(function () { runIfPresent(handle); }); + }; + } + + function canUsePostMessage() { + // The test against `importScripts` prevents this implementation from being installed inside a web worker, + // where `global.postMessage` means something completely different and can't be used for this purpose. + if (global.postMessage && !global.importScripts) { + var postMessageIsAsynchronous = true; + var oldOnMessage = global.onmessage; + global.onmessage = function() { + postMessageIsAsynchronous = false; + }; + global.postMessage("", "*"); + global.onmessage = oldOnMessage; + return postMessageIsAsynchronous; + } + } + + function installPostMessageImplementation() { + // Installs an event handler on `global` for the `message` event: see + // * https://developer.mozilla.org/en/DOM/window.postMessage + // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages + + var messagePrefix = "setImmediate$" + Math.random() + "$"; + var onGlobalMessage = function(event) { + if (event.source === global && + typeof event.data === "string" && + event.data.indexOf(messagePrefix) === 0) { + runIfPresent(+event.data.slice(messagePrefix.length)); + } + }; + + if (global.addEventListener) { + global.addEventListener("message", onGlobalMessage, false); + } else { + global.attachEvent("onmessage", onGlobalMessage); + } + + registerImmediate = function(handle) { + global.postMessage(messagePrefix + handle, "*"); + }; + } + + function installMessageChannelImplementation() { + var channel = new MessageChannel(); + channel.port1.onmessage = function(event) { + var handle = event.data; + runIfPresent(handle); + }; + + registerImmediate = function(handle) { + channel.port2.postMessage(handle); + }; + } + + function installReadyStateChangeImplementation() { + var html = doc.documentElement; + registerImmediate = function(handle) { + // Create a