1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | "use strict" ;
|
28 |
|
29 |
|
30 |
|
31 | const Element = require( './Element.js' ) ;
|
32 | const TextBox = require( './TextBox.js' ) ;
|
33 | const boxesChars = require( '../spChars.js' ).box ;
|
34 |
|
35 |
|
36 |
|
37 | function TextTable( options ) {
|
38 |
|
39 | options = ! options ? {} : options.internal ? options : Object.create( options ) ;
|
40 | options.internal = true ;
|
41 |
|
42 | Element.call( this , options ) ;
|
43 |
|
44 | this.cellContents = options.cellContents ;
|
45 | this.contentHasMarkup = options.contentHasMarkup ;
|
46 |
|
47 | this.textBoxes = null ;
|
48 | this.rowCount = 0 ;
|
49 | this.columnCount = 0 ;
|
50 |
|
51 | this.rowHeights = [] ;
|
52 | this.columnWidths = [] ;
|
53 |
|
54 | this.textAttr = options.textAttr || { bgColor: 'default' } ;
|
55 | this.voidAttr = options.voidAttr || options.emptyAttr || null ;
|
56 |
|
57 | this.firstRowTextAttr = options.firstRowTextAttr || null ;
|
58 | this.firstRowVoidAttr = options.firstRowVoidAttr || null ;
|
59 | this.evenRowTextAttr = options.evenRowTextAttr || null ;
|
60 | this.evenRowVoidAttr = options.evenRowVoidAttr || null ;
|
61 |
|
62 | this.firstColumnTextAttr = options.firstColumnTextAttr || null ;
|
63 | this.firstColumnVoidAttr = options.firstColumnVoidAttr || null ;
|
64 | this.evenColumnTextAttr = options.evenColumnTextAttr || null ;
|
65 | this.evenColumnVoidAttr = options.evenColumnVoidAttr || null ;
|
66 |
|
67 | this.firstCellTextAttr = options.firstCellTextAttr || null ;
|
68 | this.firstCellVoidAttr = options.firstCellVoidAttr || null ;
|
69 |
|
70 |
|
71 | this.evenCellTextAttr = options.evenCellTextAttr || null ;
|
72 | this.evenCellVoidAttr = options.evenCellVoidAttr || null ;
|
73 |
|
74 |
|
75 | this.checkerEvenCellTextAttr = options.checkerEvenCellTextAttr || null ;
|
76 | this.checkerEvenCellVoidAttr = options.checkerEvenCellVoidAttr || null ;
|
77 |
|
78 | this.expandToWidth = options.expandToWidth !== undefined ? !! options.expandToWidth : !! options.fit ;
|
79 | this.shrinkToWidth = options.shrinkToWidth !== undefined ? !! options.shrinkToWidth : !! options.fit ;
|
80 | this.expandToHeight =
|
81 | options.expandToHeight !== undefined ? !! options.expandToHeight :
|
82 | ! options.height ? false :
|
83 | !! options.fit ;
|
84 | this.shrinkToHeight =
|
85 | options.shrinkToHeight !== undefined ? !! options.shrinkToHeight :
|
86 | ! options.height ? false :
|
87 | !! options.fit ;
|
88 | this.wordWrap = options.wordWrap !== undefined || options.wordwrap !== undefined ?
|
89 | !! ( options.wordWrap || options.wordwrap ) : !! options.fit ;
|
90 | this.lineWrap = this.wordWrap || ( options.lineWrap !== undefined ? !! options.lineWrap : !! options.fit ) ;
|
91 |
|
92 | this.hasBorder = options.hasBorder !== undefined ? !! options.hasBorder : true ;
|
93 | this.borderAttr = options.borderAttr || this.textAttr ;
|
94 | this.borderChars = options.borderChars || boxesChars.light ;
|
95 |
|
96 | if ( options.borderChars ) {
|
97 | if ( typeof options.borderChars === 'object' ) {
|
98 | this.borderChars = options.borderChars ;
|
99 | }
|
100 | else if ( typeof options.borderChars === 'string' && boxesChars[ options.borderChars ] ) {
|
101 | this.borderChars = boxesChars[ options.borderChars ] ;
|
102 | }
|
103 | }
|
104 |
|
105 | if ( options.textBoxKeyBindings ) { this.textBoxKeyBindings = options.textBoxKeyBindings ; }
|
106 |
|
107 | this.initChildren() ;
|
108 | this.computeCells() ;
|
109 |
|
110 | if ( ! options.width ) { this.outputWidth = this.contentWidth ; }
|
111 | if ( ! options.height ) { this.outputHeight = this.contentHeight ; }
|
112 |
|
113 |
|
114 | if ( this.elementType === 'TextTable' && ! options.noDraw ) { this.draw() ; }
|
115 | }
|
116 |
|
117 | module.exports = TextTable ;
|
118 |
|
119 | TextTable.prototype = Object.create( Element.prototype ) ;
|
120 | TextTable.prototype.constructor = TextTable ;
|
121 | TextTable.prototype.elementType = 'TextTable' ;
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | TextTable.prototype.strictInlineSupport = true ;
|
127 |
|
128 |
|
129 |
|
130 | TextTable.prototype.destroy = function( isSubDestroy ) {
|
131 | Element.prototype.destroy.call( this , isSubDestroy ) ;
|
132 | } ;
|
133 |
|
134 |
|
135 |
|
136 | TextTable.prototype.textBoxKeyBindings = TextBox.prototype.keyBindings ;
|
137 |
|
138 |
|
139 |
|
140 | TextTable.prototype.initChildren = function() {
|
141 | var row , cellContent , textAttr , voidAttr ;
|
142 |
|
143 | this.rowCount = this.cellContents.length ;
|
144 | this.columnCount = 0 ;
|
145 | this.textBoxes = [] ;
|
146 |
|
147 | var x = 0 , y = 0 ;
|
148 |
|
149 | for ( row of this.cellContents ) {
|
150 | this.textBoxes[ y ] = [] ;
|
151 | x = 0 ;
|
152 |
|
153 | for ( cellContent of row ) {
|
154 | if ( x >= this.columnCount ) { this.columnCount = x + 1 ; }
|
155 |
|
156 | textAttr =
|
157 | this.firstCellTextAttr && ! x && ! y ? this.firstCellTextAttr :
|
158 | this.firstRowTextAttr && ! y ? this.firstRowTextAttr :
|
159 | this.firstColumnTextAttr && ! x ? this.firstColumnTextAttr :
|
160 | this.evenCellTextAttr && ! ( x % 2 ) && ! ( y % 2 ) ? this.evenCellTextAttr :
|
161 | this.checkerEvenCellTextAttr && ! ( ( x + y ) % 2 ) ? this.checkerEvenCellTextAttr :
|
162 | this.evenRowTextAttr && ! ( y % 2 ) ? this.evenRowTextAttr :
|
163 | this.evenColumnTextAttr && ! ( y % 2 ) ? this.evenColumnTextAttr :
|
164 | this.textAttr ;
|
165 |
|
166 | voidAttr =
|
167 | this.firstCellVoidAttr && ! x && ! y ? this.firstCellVoidAttr :
|
168 | this.firstRowVoidAttr && ! y ? this.firstRowVoidAttr :
|
169 | this.firstColumnVoidAttr && ! x ? this.firstColumnVoidAttr :
|
170 | this.evenCellVoidAttr && ! ( x % 2 ) && ! ( y % 2 ) ? this.evenCellVoidAttr :
|
171 | this.checkerEvenCellVoidAttr && ! ( ( x + y ) % 2 ) ? this.checkerEvenCellVoidAttr :
|
172 | this.evenRowVoidAttr && ! ( y % 2 ) ? this.evenRowVoidAttr :
|
173 | this.evenColumnVoidAttr && ! ( y % 2 ) ? this.evenColumnVoidAttr :
|
174 | this.voidAttr || textAttr ;
|
175 |
|
176 | this.textBoxes[ y ][ x ] = new TextBox( {
|
177 | internal: true ,
|
178 | parent: this ,
|
179 | content: cellContent ,
|
180 | contentHasMarkup: this.contentHasMarkup ,
|
181 |
|
182 | x: this.outputX ,
|
183 | y: this.outputY ,
|
184 | width: this.outputWidth ,
|
185 | height: this.outputHeight ,
|
186 | lineWrap: this.lineWrap ,
|
187 | wordWrap: this.wordWrap ,
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | textAttr ,
|
193 | voidAttr ,
|
194 | keyBindings: this.textBoxKeyBindings ,
|
195 | noDraw: true
|
196 | } ) ;
|
197 |
|
198 | x ++ ;
|
199 | }
|
200 |
|
201 | y ++ ;
|
202 | }
|
203 | } ;
|
204 |
|
205 |
|
206 |
|
207 | TextTable.prototype.computeCells = function() {
|
208 | var shrinked = this.computeColumnWidths() ;
|
209 |
|
210 | if ( shrinked ) {
|
211 | this.textBoxesWordWrap() ;
|
212 |
|
213 | }
|
214 |
|
215 | this.computeRowHeights() ;
|
216 | this.textBoxesSizeAndPosition() ;
|
217 | } ;
|
218 |
|
219 |
|
220 |
|
221 | TextTable.prototype.computeColumnWidths = function() {
|
222 | var x , y , textBox , max , width ;
|
223 |
|
224 | this.contentWidth = + this.hasBorder ;
|
225 |
|
226 | for ( x = 0 ; x < this.columnCount ; x ++ ) {
|
227 | max = 0 ;
|
228 | for ( y = 0 ; y < this.rowCount ; y ++ ) {
|
229 | textBox = this.textBoxes[ y ][ x ] ;
|
230 | if ( ! textBox ) { continue ; }
|
231 | width = textBox.getContentSize().width || 1 ;
|
232 | if ( width > max ) { max = width ; }
|
233 | }
|
234 | this.columnWidths[ x ] = max ;
|
235 | this.contentWidth += max + this.hasBorder ;
|
236 | }
|
237 |
|
238 | if ( this.expandToWidth && this.contentWidth < this.outputWidth ) {
|
239 | this.expand( this.contentWidth , this.outputWidth , this.columnWidths ) ;
|
240 | }
|
241 | else if ( this.shrinkToWidth && this.contentWidth > this.outputWidth ) {
|
242 | this.shrink( this.contentWidth , this.outputWidth , this.columnWidths ) ;
|
243 | return true ;
|
244 | }
|
245 |
|
246 | return false ;
|
247 | } ;
|
248 |
|
249 |
|
250 |
|
251 | TextTable.prototype.computeRowHeights = function() {
|
252 | var x , y , textBox , max , height ;
|
253 |
|
254 | this.contentHeight = + this.hasBorder ;
|
255 | for ( y = 0 ; y < this.rowCount ; y ++ ) {
|
256 | max = 0 ;
|
257 | for ( x = 0 ; x < this.columnCount ; x ++ ) {
|
258 | textBox = this.textBoxes[ y ][ x ] ;
|
259 | if ( ! textBox ) { continue ; }
|
260 | height = textBox.getContentSize().height || 1 ;
|
261 | if ( height > max ) { max = height ; }
|
262 | }
|
263 | this.rowHeights[ y ] = max ;
|
264 | this.contentHeight += max + this.hasBorder ;
|
265 | }
|
266 |
|
267 | if ( this.expandToHeight && this.contentHeight < this.outputHeight ) {
|
268 | this.expand( this.contentHeight , this.outputHeight , this.rowHeights ) ;
|
269 | }
|
270 | else if ( this.shrinkToHeight && this.contentHeight > this.outputHeight ) {
|
271 | this.shrink( this.contentHeight , this.outputHeight , this.rowHeights ) ;
|
272 | return true ;
|
273 | }
|
274 | } ;
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | TextTable.prototype.expand = function( contentSize , outputSize , sizeArray ) {
|
280 | var x ,
|
281 | floatSize = 0 ,
|
282 | remainder = 0 ,
|
283 | count = sizeArray.length ,
|
284 | noBorderWantedSize = outputSize - ( this.hasBorder ? count + 1 : 0 ) ;
|
285 |
|
286 | if ( noBorderWantedSize <= 0 ) { return ; }
|
287 |
|
288 | var noBorderSize = contentSize - ( this.hasBorder ? count + 1 : 0 ) ,
|
289 | rate = noBorderWantedSize / noBorderSize ;
|
290 |
|
291 |
|
292 | for ( x = 0 ; x < count ; x ++ ) {
|
293 | floatSize = sizeArray[ x ] * rate + remainder ;
|
294 | sizeArray[ x ] = Math.max( 1 , Math.round( floatSize ) ) ;
|
295 | remainder = floatSize - sizeArray[ x ] ;
|
296 | }
|
297 | } ;
|
298 |
|
299 |
|
300 |
|
301 |
|
302 | TextTable.prototype.shrink = function( contentSize , outputSize , sizeArray ) {
|
303 | var x , max ,
|
304 | secondMax = 0 ,
|
305 | maxIndexes = [] ,
|
306 | count = sizeArray.length ,
|
307 | floatColumnDelta , columnDelta , partialColumn ,
|
308 | delta = contentSize - outputSize ;
|
309 |
|
310 |
|
311 |
|
312 | while ( delta > 0 ) {
|
313 | max = 0 ;
|
314 | secondMax = 0 ;
|
315 | maxIndexes.length = 0 ;
|
316 |
|
317 | for ( x = 0 ; x < count ; x ++ ) {
|
318 | if ( sizeArray[ x ] > max ) {
|
319 | secondMax = max ;
|
320 | max = sizeArray[ x ] ;
|
321 | maxIndexes.length = 0 ;
|
322 | maxIndexes.push( x ) ;
|
323 | }
|
324 | else if ( sizeArray[ x ] === max ) {
|
325 | maxIndexes.push( x ) ;
|
326 | }
|
327 | else if ( sizeArray[ x ] > secondMax ) {
|
328 | secondMax = sizeArray[ x ] ;
|
329 | }
|
330 | }
|
331 |
|
332 |
|
333 |
|
334 | if ( ! max ) { return ; }
|
335 |
|
336 | floatColumnDelta = Math.min( max - secondMax , delta / maxIndexes.length ) ;
|
337 | columnDelta = Math.floor( floatColumnDelta ) ;
|
338 |
|
339 | if ( columnDelta >= 0 ) {
|
340 | for ( let index of maxIndexes ) {
|
341 | sizeArray[ index ] -= columnDelta ;
|
342 | delta -= columnDelta ;
|
343 | }
|
344 | }
|
345 |
|
346 | if ( columnDelta !== floatColumnDelta ) {
|
347 | partialColumn = delta % maxIndexes.length ;
|
348 | for ( let i = 0 ; i < maxIndexes.length && i < partialColumn ; i ++ ) {
|
349 | sizeArray[ maxIndexes[ i ] ] -- ;
|
350 | }
|
351 | delta -= partialColumn ;
|
352 | }
|
353 | }
|
354 | } ;
|
355 |
|
356 |
|
357 |
|
358 | TextTable.prototype.textBoxesWordWrap = function() {
|
359 | var x , y , textBox ;
|
360 |
|
361 | for ( y = 0 ; y < this.rowCount ; y ++ ) {
|
362 | for ( x = 0 ; x < this.columnCount ; x ++ ) {
|
363 | textBox = this.textBoxes[ y ][ x ] ;
|
364 |
|
365 | if ( textBox ) {
|
366 | textBox.setSizeAndPosition( {
|
367 | outputX: this.outputX ,
|
368 | outputY: this.outputY ,
|
369 | outputWidth: this.columnWidths[ x ] ,
|
370 | outputHeight: this.outputHeight
|
371 | } ) ;
|
372 | }
|
373 | }
|
374 | }
|
375 | } ;
|
376 |
|
377 |
|
378 |
|
379 | TextTable.prototype.textBoxesSizeAndPosition = function() {
|
380 | var x , y , outputX , outputY , textBox ;
|
381 |
|
382 | outputY = this.outputY + this.hasBorder ;
|
383 |
|
384 | for ( y = 0 ; y < this.rowCount ; y ++ ) {
|
385 | outputX = this.outputX + this.hasBorder ;
|
386 |
|
387 | for ( x = 0 ; x < this.columnCount ; x ++ ) {
|
388 | textBox = this.textBoxes[ y ][ x ] ;
|
389 |
|
390 | if ( textBox ) {
|
391 | textBox.setSizeAndPosition( {
|
392 | outputX ,
|
393 | outputY ,
|
394 | outputWidth: this.columnWidths[ x ] ,
|
395 | outputHeight: this.rowHeights[ y ]
|
396 | } ) ;
|
397 | }
|
398 |
|
399 | outputX += this.columnWidths[ x ] + this.hasBorder ;
|
400 | }
|
401 |
|
402 | outputY += this.rowHeights[ y ] + this.hasBorder ;
|
403 | }
|
404 | } ;
|
405 |
|
406 |
|
407 |
|
408 | TextTable.prototype.preDrawSelf = function() {
|
409 |
|
410 | if ( ! this.hasBorder ) { return ; }
|
411 |
|
412 | var i , j , x , y ;
|
413 |
|
414 |
|
415 |
|
416 | y = this.outputY ;
|
417 |
|
418 | for ( j = 0 ; j < this.rowHeights.length ; j ++ ) {
|
419 | x = this.outputX ;
|
420 |
|
421 | for ( i = 0 ; i < this.columnWidths.length ; i ++ ) {
|
422 |
|
423 |
|
424 |
|
425 | this.outputDst.put( { x , y , attr: this.borderAttr } ,
|
426 | j ?
|
427 | ( i ? this.borderChars.cross : this.borderChars.leftTee ) :
|
428 | ( i ? this.borderChars.topTee : this.borderChars.topLeft )
|
429 | ) ;
|
430 |
|
431 |
|
432 | this.outputDst.put( {
|
433 | x , y: y + 1 , direction: 'down' , attr: this.borderAttr
|
434 | } , this.borderChars.vertical.repeat( this.rowHeights[ j ] ) ) ;
|
435 | x ++ ;
|
436 |
|
437 |
|
438 | this.outputDst.put( { x , y , attr: this.borderAttr } , this.borderChars.horizontal.repeat( this.columnWidths[ i ] ) ) ;
|
439 | x += this.columnWidths[ i ] ;
|
440 | }
|
441 |
|
442 |
|
443 | this.outputDst.put( { x , y , attr: this.borderAttr } , j ? this.borderChars.rightTee : this.borderChars.topRight ) ;
|
444 |
|
445 |
|
446 | this.outputDst.put( {
|
447 | x , y: y + 1 , direction: 'down' , attr: this.borderAttr
|
448 | } , this.borderChars.vertical.repeat( this.rowHeights[ j ] ) ) ;
|
449 | y += this.rowHeights[ j ] + 1 ;
|
450 | }
|
451 |
|
452 |
|
453 |
|
454 | x = this.outputX ;
|
455 |
|
456 | for ( i = 0 ; i < this.columnWidths.length ; i ++ ) {
|
457 |
|
458 |
|
459 |
|
460 | this.outputDst.put( { x , y , attr: this.borderAttr } , i ? this.borderChars.bottomTee : this.borderChars.bottomLeft ) ;
|
461 | x ++ ;
|
462 |
|
463 |
|
464 | this.outputDst.put( { x , y , attr: this.borderAttr } , this.borderChars.horizontal.repeat( this.columnWidths[ i ] ) ) ;
|
465 | x += this.columnWidths[ i ] ;
|
466 | }
|
467 |
|
468 |
|
469 | this.outputDst.put( { x , y , attr: this.borderAttr } , this.borderChars.bottomRight ) ;
|
470 | } ;
|
471 |
|