delwin() and dereferencing issue in viewer, #70
[smdp.git] / src / viewer.c
1 /*
2  * Functions necessary to display a deck of slides in different color modes
3  * using ncurses. Only white, red, and blue are supported, as they can be
4  * faded in 256 color mode.
5  * Copyright (C) 2014 Michael Goehler
6  *
7  * This file is part of mdp.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23
24 #include <ctype.h>  // isalnum
25 #include <locale.h> // setlocale
26 #include <string.h> // strchr
27 #include <unistd.h> // usleep
28
29 #include "viewer.h"
30
31 // color ramp for fading from black to color
32 static short white_ramp[24] = { 16, 232, 233, 234, 235, 236,
33                                237, 238, 239, 240, 241, 242,
34                                244, 245, 246, 247, 248, 249,
35                                250, 251, 252, 253, 254, 255 };
36
37 static short blue_ramp[24]  = { 16,  17,  17,  18,  18,  19,
38                                 19,  20,  20,  21,  27,  33,
39                                 32,  39,  38,  45,  44,  44,
40                                 81,  81,  51,  51, 123, 123 };
41
42 static short red_ramp[24]   = { 16,  52,  52,  53,  53,  89,
43                                 89,  90,  90, 126, 127, 127,
44                                163, 163, 164, 164, 200, 200,
45                                201, 201, 207, 207, 213, 213 };
46
47 // color ramp for fading from white to color
48 static short white_ramp_invert[24] = { 15, 255, 254, 254, 252, 251,
49                                       250, 249, 248, 247, 246, 245,
50                                       243, 242, 241, 240, 239, 238,
51                                       237, 236, 235, 234, 233, 232};
52
53 static short blue_ramp_invert[24]  = { 15, 231, 231, 195, 195, 159,
54                                       159, 123, 123,  87,  51,  44,
55                                        45,  38,  39,  32,  33,  33,
56                                        26,  26,  27,  27,  21,  21};
57
58 static short red_ramp_invert[24]   = { 15, 231, 231, 224, 224, 225,
59                                       225, 218, 218, 219, 212, 213,
60                                       206, 207, 201, 200, 199, 199,
61                                       198, 198, 197, 197, 196, 196};
62
63 int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) {
64
65     int c = 0;          // char
66     int i = 0;          // iterate
67     int l = 0;          // line number
68     int lc = 0;         // line count
69     int sc = 1;         // slide count
70     int colors = 0;     // amount of colors supported
71     int fade = 0;       // disable color fading by default
72     int trans = -1;     // enable transparency if term supports it
73     int max_lines = 0;  // max lines per slide
74     int max_cols = 0;   // max columns per line
75     int offset;         // text offset
76
77     // header line 1 is displayed at the top
78     int bar_top = (deck->headers > 0) ? 1 : 0;
79     // header line 2 is displayed at the bottom
80     // anyway we display the slide number at the bottom
81     int bar_bottom = 1;
82
83     slide_t *slide = deck->slide;
84     line_t *line;
85
86     // set locale to display UTF-8 correctly in ncurses
87     setlocale(LC_CTYPE, "");
88
89     // init ncurses
90     initscr();
91
92     while(slide) {
93         lc = 0;
94         line = slide->line;
95
96         while(line && line->text) {
97
98             if (line->text->text)
99                 lc += url_count_inline(line->text->text);
100
101             if (line->text->text)
102                 line->length -= url_len_inline(line->text->text);
103
104             if(line->length > COLS) {
105                 i = line->length;
106                 offset = 0;
107                 while(i > COLS) {
108
109                     i = prev_blank(line->text, offset + COLS) - offset;
110
111                     // single word is > COLS
112                     if(!i) {
113                         // calculate min_width
114                         i = next_blank(line->text, offset + COLS) - offset;
115
116                         // disable ncurses
117                         endwin();
118
119                         // print error
120                         fprintf(stderr, "Error: Terminal width (%i columns) too small. Need at least %i columns.\n", COLS, i);
121                         fprintf(stderr, "You may need to shorten some lines by inserting line breaks.\n");
122
123                         return 1;
124                     }
125
126                     // set max_cols
127                     max_cols = MAX(i, max_cols);
128
129                     // iterate to next line
130                     offset = prev_blank(line->text, offset + COLS);
131                     i = line->length - offset;
132                     lc++;
133                 }
134                 // set max_cols one last time
135                 max_cols = MAX(i, max_cols);
136             } else {
137                 // set max_cols
138                 max_cols = MAX(line->length, max_cols);
139             }
140             lc++;
141             line = line->next;
142         }
143
144         max_lines = MAX(lc, max_lines);
145
146         slide = slide->next;
147     }
148
149     // not enough lines
150     if(max_lines + bar_top + bar_bottom > LINES) {
151
152         // disable ncurses
153         endwin();
154
155         // print error
156         fprintf(stderr, "Error: Terminal height (%i lines) too small. Need at least %i lines.\n", LINES, max_lines + bar_top + bar_bottom);
157         fprintf(stderr, "You may need to add additional horizontal rules ('***') to split your file in shorter slides.\n");
158
159         return 1;
160     }
161
162     // disable cursor
163     curs_set(0);
164
165     // disable output of keyboard typing
166     noecho();
167
168     // make getch() process one char at a time
169     cbreak();
170
171     // enable arrow keys
172     keypad(stdscr,TRUE);
173
174     // set colors
175     if(has_colors() == TRUE) {
176         start_color();
177         use_default_colors();
178
179         // 256 color mode
180         if(COLORS == 256) {
181
182             if(notrans) {
183                 if(invert) {
184                     trans = 15; // white in 256 color mode
185                 } else {
186                     trans = 16; // black in 256 color mode
187                 }
188             }
189
190             if(invert) {
191                 init_pair(CP_WHITE, 232, trans);
192                 init_pair(CP_BLUE, 21, trans);
193                 init_pair(CP_RED, 196, trans);
194                 init_pair(CP_BLACK, 15, 232);
195             } else {
196                 init_pair(CP_WHITE, 255, trans);
197                 init_pair(CP_BLUE, 123, trans);
198                 init_pair(CP_RED, 213, trans);
199                 init_pair(CP_BLACK, 16, 255);
200             }
201             init_pair(CP_YELLOW, 208, trans);
202
203             // enable color fading
204             if(!nofade)
205                 fade = true;
206
207         // 8 color mode
208         } else {
209
210             if(notrans) {
211                 if(invert) {
212                     trans = 7; // white in 8 color mode
213                 } else {
214                     trans = 0; // black in 8 color mode
215                 }
216             }
217
218             if(invert) {
219                 init_pair(CP_WHITE, 0, trans);
220                 init_pair(CP_BLACK, 7, 0);
221             } else {
222                 init_pair(CP_WHITE, 7, trans);
223                 init_pair(CP_BLACK, 0, 7);
224             }
225             init_pair(CP_BLUE, 4, trans);
226             init_pair(CP_RED, 1, trans);
227             init_pair(CP_YELLOW, 3, trans);
228         }
229
230         colors = 1;
231     }
232
233     // set background color of main window
234     if(colors)
235         wbkgd(stdscr, COLOR_PAIR(CP_YELLOW));
236
237     // setup main window
238     WINDOW *content = newwin(LINES - bar_top - bar_bottom, COLS, 0 + bar_top, 0);
239     if(colors)
240         wbkgd(content, COLOR_PAIR(CP_WHITE));
241
242     slide = deck->slide;
243     while(slide) {
244
245         url_init();
246
247         // clear windows
248         werase(content);
249         werase(stdscr);
250
251         // always resize window in case terminal geometry has changed
252         wresize(content, LINES - bar_top - bar_bottom, COLS);
253
254         // setup header
255         if(bar_top) {
256             line = deck->header;
257             offset = next_blank(line->text, 0) + 1;
258             // add text to header
259             mvwprintw(stdscr,
260                       0, (COLS - line->length + offset) / 2,
261                       "%s", &line->text->text[offset]);
262         }
263
264         // setup footer
265         if(deck->headers > 1) {
266             line = deck->header->next;
267             offset = next_blank(line->text, 0) + 1;
268             // add text to left footer
269             mvwprintw(stdscr,
270                       LINES - 1, 3,
271                       "%s", &line->text->text[offset]);
272         }
273         // add slide number to right footer
274         mvwprintw(stdscr,
275                   LINES - 1, COLS - int_length(deck->slides) - int_length(sc) - 6,
276                   "%d / %d", sc, deck->slides);
277
278         // make header + fooder visible
279         wrefresh(content);
280         wrefresh(stdscr);
281
282         line = slide->line;
283         l = 0;
284
285         // print lines
286         while(line) {
287             add_line(content, l, (COLS - max_cols) / 2, line, max_cols, colors);
288             l += (line->length / COLS) + 1;
289             line = line->next;
290         }
291
292         int i, ymax;
293         getmaxyx( content, ymax, i );
294         for (i = 0; i < url_get_amount(); i++) {
295             mvwprintw(content, ymax - url_get_amount() - 1 + i, 3,
296                       "[%d] %s", i, url_get_target(i));
297         }
298
299         // make content visible
300         wrefresh(content);
301
302         // fade in
303         if(fade)
304             fade_in(content, trans, colors, invert);
305
306         // re-enable fading after any undefined key press
307         if(COLORS == 256 && !nofade)
308             fade = true;
309
310         // wait for user input
311         c = getch();
312
313         // evaluate user input
314         i = 0;
315         switch(c) {
316
317             // show previous slide
318             case KEY_UP:
319             case KEY_LEFT:
320             case KEY_PPAGE:
321             case 8:   // BACKSPACE (ascii)
322             case 127: // BACKSPACE (xterm)
323             case 263: // BACKSPACE (getty)
324             case 'h':
325             case 'k':
326                 if(slide->prev) {
327                     slide = slide->prev;
328                     sc--;
329                 } else {
330                     fade = false;
331                 }
332                 break;
333
334             // show next slide
335             case KEY_DOWN:
336             case KEY_RIGHT:
337             case KEY_NPAGE:
338             case '\n': // ENTER
339             case ' ':  // SPACE
340             case 'j':
341             case 'l':
342                 if(slide->next) {
343                     slide = slide->next;
344                     sc++;
345                 } else {
346                     fade = false;
347                 }
348                 break;
349
350             // show slide n
351             case '9': i++;
352             case '8': i++;
353             case '7': i++;
354             case '6': i++;
355             case '5': i++;
356             case '4': i++;
357             case '3': i++;
358             case '2': i++;
359             case '1': i++;
360                 if(i <= deck->slides) {
361                     while(sc != i) {
362                         // search forward
363                         if(sc < i) {
364                             if(slide->next) {
365                                 slide = slide->next;
366                                 sc++;
367                             }
368                         // search backward
369                         } else {
370                             if(slide->prev) {
371                                 slide = slide->prev;
372                                 sc--;
373                             }
374                         }
375                     }
376                 } else {
377                     // disable fading if slide n doesn't exist
378                     fade = false;
379                 }
380                 break;
381
382             // show first slide
383             case KEY_HOME:
384                 slide = deck->slide;
385                 sc = 1;
386                 break;
387
388             // show last slide
389             case KEY_END:
390                 for(i = sc; i <= deck->slides; i++) {
391                     if(slide->next) {
392                             slide = slide->next;
393                             sc++;
394                     }
395                 }
396                 break;
397
398             // quit
399             case 'q':
400                 // do not fade out on exit
401                 fade = false;
402                 slide = NULL;
403                 break;
404
405             default:
406                 // disable fading on undefined key press
407                 fade = false;
408                 break;
409         }
410
411         // fade out
412         if(fade)
413             fade_out(content, trans, colors, invert);
414
415         url_purge();
416     }
417
418     // disable ncurses
419     endwin();
420
421     // free ncurses memory
422     delwin(content);
423     delwin(stdscr);
424
425     return 0;
426 }
427
428 void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colors) {
429
430     if(!line->text->text) {
431         return;
432     }
433
434     int i; // increment
435     int offset = 0; // text offset
436
437     // move the cursor in position
438     wmove(window, y, x);
439
440     // IS_UNORDERED_LIST_3
441     if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
442         offset = next_nonblank(line->text, 0);
443         char prompt[13];
444         strcpy(&prompt[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " |  " : "    ");
445         strcpy(&prompt[4], CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? " |  " : "    ");
446
447         if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
448             strcpy(&prompt[8], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? " |  " : "    ");
449         } else {
450             strcpy(&prompt[8], " +- ");
451             offset += 2;
452         }
453
454         wprintw(window,
455                 "%s", prompt);
456
457         if(!CHECK_BIT(line->bits, IS_CODE))
458             inline_display(window, &line->text->text[offset], colors);
459
460     // IS_UNORDERED_LIST_2
461     } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
462         offset = next_nonblank(line->text, 0);
463         char prompt[9];
464         strcpy(&prompt[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " |  " : "    ");
465
466         if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
467             strcpy(&prompt[4], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? " |  " : "    ");
468         } else {
469             strcpy(&prompt[4], " +- ");
470             offset += 2;
471         }
472
473         wprintw(window,
474                 "%s", prompt);
475
476         if(!CHECK_BIT(line->bits, IS_CODE))
477             inline_display(window, &line->text->text[offset], colors);
478
479     // IS_UNORDERED_LIST_1
480     } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
481         offset = next_nonblank(line->text, 0);
482         char prompt[5];
483
484         if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
485             strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? " |  " : "    ");
486         } else {
487             strcpy(&prompt[0], " +- ");
488             offset += 2;
489         }
490
491         wprintw(window,
492                 "%s", prompt);
493
494         if(!CHECK_BIT(line->bits, IS_CODE))
495             inline_display(window, &line->text->text[offset], colors);
496     }
497
498     // IS_CODE
499     if(CHECK_BIT(line->bits, IS_CODE)) {
500
501         // set static offset for code
502         offset = CODE_INDENT;
503
504         // reverse color for code blocks
505         if(colors)
506             wattron(window, COLOR_PAIR(CP_BLACK));
507
508         // print whole lines
509         wprintw(window,
510                 "%s", &line->text->text[offset]);
511     }
512
513     if(!CHECK_BIT(line->bits, IS_UNORDERED_LIST_1) &&
514        !CHECK_BIT(line->bits, IS_UNORDERED_LIST_2) &&
515        !CHECK_BIT(line->bits, IS_UNORDERED_LIST_3) &&
516        !CHECK_BIT(line->bits, IS_CODE)) {
517
518         // IS_QUOTE
519         if(CHECK_BIT(line->bits, IS_QUOTE)) {
520             while(line->text->text[offset] == '>') {
521                 // print a reverse color block
522                 if(colors) {
523                     wattron(window, COLOR_PAIR(CP_BLACK));
524                     wprintw(window, "%s", " ");
525                     wattron(window, COLOR_PAIR(CP_WHITE));
526                     wprintw(window, "%s", " ");
527                 } else {
528                     wprintw(window, "%s", ">");
529                 }
530
531                 // find next quote or break
532                 offset++;
533                 if(line->text->text[offset] == ' ')
534                     offset = next_word(line->text, offset);
535             }
536
537             inline_display(window, &line->text->text[offset], colors);
538         } else {
539
540             // IS_CENTER
541             if(CHECK_BIT(line->bits, IS_CENTER)) {
542                 if(line->length < max_cols) {
543                     wmove(window, y, x + ((max_cols - line->length) / 2));
544                 }
545             }
546
547             // IS_H1 || IS_H2
548             if(CHECK_BIT(line->bits, IS_H1) || CHECK_BIT(line->bits, IS_H2)) {
549
550                 // set headline color
551                 if(colors)
552                     wattron(window, COLOR_PAIR(CP_BLUE));
553
554                 // enable underline for H1
555                 if(CHECK_BIT(line->bits, IS_H1))
556                     wattron(window, A_UNDERLINE);
557
558                 // skip hashes
559                 while(line->text->text[offset] == '#')
560                     offset = next_word(line->text, offset);
561
562                 // print whole lines
563                 wprintw(window,
564                         "%s", &line->text->text[offset]);
565
566                 wattroff(window, A_UNDERLINE);
567
568             // no line-wide markdown
569             } else {
570
571                 inline_display(window, &line->text->text[offset], colors);
572             }
573         }
574     }
575
576     // fill rest off line with spaces
577     for(i = getcurx(window) - x; i < max_cols; i++)
578         wprintw(window, "%s", " ");
579
580     // reset to default color
581     if(colors)
582         wattron(window, COLOR_PAIR(CP_WHITE));
583     wattroff(window, A_UNDERLINE);
584 }
585
586 void inline_display(WINDOW *window, const char *c, const int colors) {
587     const static char *special = "\\*_`!["; // list of interpreted chars
588     const char *i = c; // iterator
589     const char *start_link_name, *start_url;
590     int length_link_name, url_num;
591     cstack_t *stack = cstack_init();
592
593
594     // for each char in line
595     for(; *i; i++) {
596
597         // if char is in special char list
598         if(strchr(special, *i)) {
599
600             // closing special char (or second backslash)
601             // only if not followed by :alnum:
602             if((stack->top)(stack, *i) &&
603                (!isalnum((int)i[1]) || *(i + 1) == '\0' || *i == '\\')) {
604
605                 switch(*i) {
606                     // print escaped backslash
607                     case '\\':
608                         wprintw(window, "%c", *i);
609                         break;
610                     // disable highlight
611                     case '*':
612                         if(colors)
613                             wattron(window, COLOR_PAIR(CP_WHITE));
614                         break;
615                     // disable underline
616                     case '_':
617                         wattroff(window, A_UNDERLINE);
618                         break;
619                     // disable inline code
620                     case '`':
621                         if(colors)
622                             wattron(window, COLOR_PAIR(CP_WHITE));
623                         break;
624                 }
625
626                 // remove top special char from stack
627                 (stack->pop)(stack);
628
629             // treat special as regular char
630             } else if((stack->top)(stack, '\\')) {
631                 wprintw(window, "%c", *i);
632
633                 // remove backslash from stack
634                 (stack->pop)(stack);
635
636             // opening special char
637             } else {
638
639                 // emphasis or code span can start after new-line or space only
640                 // and of cause after another emphasis markup
641                 //TODO this condition looks ugly
642                 if(i == c ||
643                    *(i - 1) == ' ' ||
644                    ((*(i - 1) == '_' || *(i - 1) == '*') && ((i - 1) == c || *(i - 2) == ' ')) ||
645                    *i == '\\') {
646
647                     // url in pandoc style
648                     if ((*i == '[' && strchr(i, ']')) ||
649                         (*i == '!' && *(i + 1) == '[' && strchr(i, ']'))) {
650
651                         if (*i == '!') i++;
652
653                         if (strchr(i, ']')[1] == '(') {
654                             i++;
655
656                             // turn higlighting and underlining on
657                             if (colors)
658                                 wattron(window, COLOR_PAIR(CP_BLUE));
659                             wattron(window, A_UNDERLINE);
660
661                             start_link_name = i;
662
663                             // print the content of the label
664                             // the label is printed as is
665                             do {
666                                 wprintw(window, "%c", *i);
667                                 i++;
668                             } while (*i != ']');
669
670                             length_link_name = i - 1 - start_link_name;
671
672                             i++;
673                             i++;
674
675                             start_url = i;
676
677                             while (*i != ')') i++;
678
679                             url_num = url_add(start_link_name, length_link_name, start_url, i - start_url, 0,0);
680
681                             wprintw(window, " [%d]", url_num);
682
683                             // turn highlighting and underlining off
684                             wattroff(window, A_UNDERLINE);
685                             wattron(window, COLOR_PAIR(CP_WHITE));
686
687                         } else {
688                             wprintw(window, "[");
689                         }
690
691                     } else switch(*i) {
692                         // enable highlight
693                         case '*':
694                             if(colors)
695                                 wattron(window, COLOR_PAIR(CP_RED));
696                             break;
697                         // enable underline
698                         case '_':
699                             wattron(window, A_UNDERLINE);
700                             break;
701                         // enable inline code
702                         case '`':
703                             if(colors)
704                                 wattron(window, COLOR_PAIR(CP_BLACK));
705                             break;
706                         // do nothing for backslashes
707                     }
708
709                     // push special char to stack
710                     (stack->push)(stack, *i);
711
712                 } else {
713                     wprintw(window, "%c", *i);
714                 }
715             }
716
717         } else {
718             // remove backslash from stack
719             if((stack->top)(stack, '\\'))
720                 (stack->pop)(stack);
721
722             // print regular char
723             wprintw(window, "%c", *i);
724         }
725     }
726
727     // pop stack until empty to prevent formated trailing spaces
728     while(!(stack->empty)(stack)) {
729         switch((stack->pop)(stack)) {
730             // disable highlight
731             case '*':
732                 if(colors)
733                     wattron(window, COLOR_PAIR(CP_WHITE));
734                 break;
735             // disable underline
736             case '_':
737                 wattroff(window, A_UNDERLINE);
738                 break;
739             // disable inline code
740             case '`':
741                 if(colors)
742                     wattron(window, COLOR_PAIR(CP_WHITE));
743                 break;
744             // do nothing for backslashes
745         }
746     }
747
748     (stack->delete)(stack);
749 }
750
751 void fade_out(WINDOW *window, int trans, int colors, int invert) {
752     int i; // increment
753     if(colors && COLORS == 256) {
754         for(i = 22; i >= 0; i--) {
755
756             // dim color pairs
757             if(invert) {
758                 init_pair(CP_WHITE, white_ramp_invert[i], trans);
759                 init_pair(CP_BLUE, blue_ramp_invert[i], trans);
760                 init_pair(CP_RED, red_ramp_invert[i], trans);
761                 init_pair(CP_BLACK, 15, white_ramp_invert[i]);
762             } else {
763                 init_pair(CP_WHITE, white_ramp[i], trans);
764                 init_pair(CP_BLUE, blue_ramp[i], trans);
765                 init_pair(CP_RED, red_ramp[i], trans);
766                 init_pair(CP_BLACK, 16, white_ramp[i]);
767             }
768
769             // refresh window with new color
770             wrefresh(window);
771
772             // delay for our eyes to recognize the change
773             usleep(FADE_DELAY);
774         }
775     }
776 }
777
778 void fade_in(WINDOW *window, int trans, int colors, int invert) {
779     int i; // increment
780     if(colors && COLORS == 256) {
781         for(i = 0; i <= 23; i++) {
782
783             // brighten color pairs
784             if(invert) {
785                 init_pair(CP_WHITE, white_ramp_invert[i], trans);
786                 init_pair(CP_BLUE, blue_ramp_invert[i], trans);
787                 init_pair(CP_RED, red_ramp_invert[i], trans);
788                 init_pair(CP_BLACK, 15, white_ramp_invert[i]);
789             } else {
790                 init_pair(CP_WHITE, white_ramp[i], trans);
791                 init_pair(CP_BLUE, blue_ramp[i], trans);
792                 init_pair(CP_RED, red_ramp[i], trans);
793                 init_pair(CP_BLACK, 16, white_ramp[i]);
794             }
795
796             // refresh window with new color
797             wrefresh(window);
798
799             // delay for our eyes to recognize the change
800             usleep(FADE_DELAY);
801         }
802     }
803 }
804
805 int int_length (int val) {
806     int l = 1;
807     while(val > 9) {
808         l++;
809         val /= 10;
810     }
811     return l;
812 }