1 /*
2  * Source file:
3  *	output.c
4  *
5  * Contains the formatting and output functions.
6  */
7 
8 #include "config.h"
9 
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include "trueprint.h"
16 #include "main.h"
17 #include "diffs.h"
18 #include "headers.h"
19 #include "index.h"
20 #include "postscript.h"
21 #include "language.h"
22 #include "print_prompt.h"
23 #include "debug.h"
24 #include "options.h"
25 #include "utils.h"
26 
27 #include "output.h"
28 
29 /******************************************************************************
30  * Public part
31  */
32 long	file_page_number;
33 long	page_number;
34 
35 /******************************************************************************
36  * Private part
37  */
38 
39 typedef enum {
40   INSERT,
41   DELETE,
42   NORMAL
43 } diff_states;
44 
45 static void	add_char(short position,char character,char_status status,char *line,char_status line_status[]);
46 static int	line_end(char *input_line, int last_char_printed);
47 static stream_status printnextline(void);
48 static boolean	blank_page(boolean print_page);
49 
50 /*
51  * Local variables
52  */
53 static boolean	no_clever_wrap;
54 static short	min_line_length;
55 static short	tabsize;
56 static boolean	no_function_page_breaks;
57 static boolean	new_sheet_after_file;
58 static boolean	no_expand_page_break;
59 static long	line_number;
60 static boolean	reached_end_of_sheet;
61 
62 static char *segment_ends[4][3] = {
63   /*     INSERT               DELETE             NORMAL       */
64   /* NORMAL */    { ") BF setfont show ",   ") CF setfont So show ",   ") CF setfont show " },
65 		  /* ITALIC */    { ") IF setfont Bs ",     ") IF setfont So show ",   ") IF setfont show " },
66 		  /* BOLD */      { ") BF setfont show ",   ") BF setfont So show ",   ") BF setfont show " },
67 		  /* UNDERLINE */ { ") BF setfont Ul show", ") CF setfont So Ul show ",") CF setfont Ul show " },
68 };
69 
70 /******************************************************************************
71  * Function:
72  *	init_output
73  */
74 void
init_output(void)75 init_output(void)
76 {
77   line_number = 0;
78   page_number = 0;
79 }
80 
81 /******************************************************************************
82  * Function:
83  *	setup_output
84  */
85 void
setup_output(void)86 setup_output(void)
87 {
88   boolean_option("b", "no-page-break-after-function", "page-break-after-function", TRUE, &no_function_page_breaks, NULL, NULL,
89 		 OPT_TEXT_FORMAT,
90 		 "don't print page breaks at the end of functions",
91 		 "print page breaks at the end of functions");
92 
93   boolean_option(NULL, "new-sheet-after-file", "no-new-sheet-after-file", TRUE, &new_sheet_after_file, NULL, NULL, OPT_TEXT_FORMAT,
94 		 "Print each file on a new sheet of paper",
95 		 "Don't print each file on a new sheet of paper");
96 
97   boolean_option("W", "no-intelligent-line-wrap", "intelligent-line-wrap", FALSE, &no_clever_wrap, NULL, NULL,
98 		 OPT_TEXT_FORMAT,
99 		 "Wrap lines at exactly the line-wrap column",
100 		 "Wrap lines intelligently at significant characters, such\n"
101 		 "    as a space");
102 
103   short_option("L", "minimum-line-length", 10, NULL, 0, 5, 4096, &min_line_length, NULL, NULL,
104 	       OPT_TEXT_FORMAT,
105 	       "minimum line length permitted by intelligent line wrap (default 10)",
106 	       NULL);
107   short_option("T", "tabsize", 8, NULL, 0, 1, 20, &tabsize, NULL, NULL,
108 	       OPT_TEXT_FORMAT,
109 	       "set tabsize (default 8)", NULL);
110 
111   boolean_option("E", "ignore-form-feeds", "form-feeds", FALSE, &no_expand_page_break, NULL, NULL,
112 		 OPT_TEXT_FORMAT,
113 		 "don't expand form feed characters to new page",
114 		 "expand form feed characters to new page");
115 }
116 
117 /******************************************************************************
118  * Function:
119  *	add_char
120  *
121  * Simply adds a character plus status to a couple of arrays
122  */
123 void
add_char(short position,char character,char_status status,char * line,char_status line_status[])124 add_char(short position,char character,char_status status,char *line,char_status line_status[])
125 
126 {
127   if (position >= MAXLINELENGTH)
128     fprintf(stderr, gettext(CMD_NAME ": line too long!  Are you sure this is a program listing?\n"));
129   line[position] = character;
130   line_status[position] = status;
131 }
132 
133 /******************************************************************************
134  * Function:
135  *	getnextline
136  * Gets the next full line of input and expands tabs.
137  * Always returns data, even if it is a blank line.
138  */
139 stream_status
getnextline(stream_status (* get_input_char)(char *,char_status *),boolean * blank_line,char input_line[],char_status input_status[])140 getnextline(stream_status (*get_input_char)(char *,char_status *), boolean *blank_line, char input_line[], char_status input_status[])
141 
142 {
143   char	input_char;
144   short	line_position=0;
145   stream_status	retval = STREAM_OK;
146   char_status status;
147 
148   *blank_line = TRUE;
149 
150   do
151     {
152       retval |= get_input_char(&input_char,&status);
153 
154       if (!isspace(input_char)) *blank_line = FALSE;
155 
156       if (input_char == '\t')
157 	{
158 	  /*
159 	   * if we read a tab then expand it out.
160 	   */
161 	  short tab_stop = tabsize + line_position - line_position%tabsize;
162 	  short cursor;
163 
164 	  for (cursor=line_position;cursor < tab_stop;cursor++)
165 	    add_char(cursor,' ',status,input_line,input_status);
166 	  line_position = tab_stop;
167 
168 	}
169       else if (input_char == '\n')
170 	{
171 	  break;
172 
173 	}
174       else if (input_char == '\014')
175 	{
176 	  /*
177 	   * if a newpage character then convert to space
178 	   */
179 	  if (no_expand_page_break == FALSE)
180 	    {
181 	      retval |= STREAM_PAGE_END;
182 	      add_char(line_position++,' ',status,input_line,input_status);
183 	    }
184 	  else
185 	    {
186 	      add_char(line_position++,'_',status,input_line,input_status);
187 	    }
188 
189 	}
190       else if (iscntrl(input_char))
191 	{
192 
193 	  /*
194 	   * if a control character then convert to underscore
195 	   */
196 	  add_char(line_position++,'_',status,input_line,input_status);
197 
198 	}
199       else
200 	{
201 	  add_char(line_position++,input_char,status,input_line,input_status);
202 	}
203 
204     } while (!(retval & (STREAM_FILE_END)));
205 
206   /*
207    * put a null at the end of the line
208    */
209   input_line[line_position] = 0;
210 
211   return(retval);
212 }
213 
214 /*
215  * function:
216  *	line_end
217  *
218  * we have a line that may need to be broken.
219  * search from the end of the line backwards, looking for a suitable
220  * place to break the line.
221  */
222 int
line_end(char * input_line,int last_char_printed)223 line_end(char *input_line, int last_char_printed)
224 
225 {
226   int	input_line_length;
227   boolean	got_end=FALSE;
228   short	break_index;
229   int	output_line_end;
230 
231   input_line_length = (int)strlen(input_line);
232   dm('O',4,"output.c:line_end() line length is %d\n",input_line_length);
233 
234   if (page_width == 0)
235     {
236       /*
237        * The line doesn't need to be broken
238        */
239       output_line_end = input_line_length - 1;
240       return(output_line_end);
241     }
242 
243   if ((input_line_length - last_char_printed) > page_width)
244     {
245       if (no_clever_wrap == TRUE)
246 	{
247 	  /*
248 	   * Don't need to do anything clever - just return the
249 	   * point in the string which is "page_width" plus
250 	   * the last point printed.
251 	   */
252 	  return (last_char_printed + page_width);
253 	}
254 
255       /*
256        * the line needs to be broken.  there is an array, breaks,
257        * containing valid symbols that a line can be broken on.
258        * what now needs to be done is to work through the array,
259        * starting with the most desirable break character, until
260        * one of the break characters is found in the line and then
261        * the line can be broken there.
262        */
263 
264       for (break_index=0; break_index < BREAKSLENGTH; break_index++)
265 	{
266 	  for (output_line_end = last_char_printed + page_width;
267 	       output_line_end > last_char_printed + min_line_length;
268 	       output_line_end--)
269 	    if (input_line[output_line_end] == BREAKS[break_index])
270 	      {
271 		got_end=TRUE;
272 		break;
273 	      }
274 	  if (got_end == TRUE) break;
275 	}
276       if (got_end == FALSE)
277 	output_line_end = last_char_printed+page_width;
278 
279       if (output_line_end >= input_line_length)
280 	output_line_end = input_line_length;
281     }
282   else
283     /* the line doesn't need to be broken at all. */
284     output_line_end = input_line_length - 1;
285 
286   return(output_line_end);
287 }
288 
289 /*
290  * function:
291  *	printnextline
292  *
293  * prints a line of code on stdout, splitting at appropriate locations.
294  * behaves very similarly for pass 0 and for pass 1, except it doesn't
295  * print anything out during pass 0.
296  */
297 stream_status
printnextline()298 printnextline()
299 
300 {
301   static char	input_line[MAXLINELENGTH];
302   static char_status	input_status[MAXLINELENGTH];
303   static int	last_char_printed;
304   static int	input_line_length=0;
305   int		output_line_end;
306   static stream_status	retval=STREAM_OK;
307   int		output_char_idx;
308   boolean		first_line_segment=FALSE;
309   short		count;
310   static diff_states	diff_state=NORMAL;
311   boolean blank_line;
312   char_status	last_char_status;
313 
314   /*
315    * See if we need to read in a new line
316    */
317   if (input_line_length == 0)
318     {
319 
320       /*
321        * we do - check to see if there might be a deleted
322        * line that needs to be printed before the current
323        * line.  if diff_state is delete then there is already
324        * a line waiting in input_line...
325        *
326        * diff_state:
327        * NORMAL	need another line, retval set to return of previous
328        *		getnextline() or LINE
329        * DELETE	line waiting in input_line, retval set to return of
330        *		previous getnextline() or LINE
331        * INSERT	should never happen
332        */
333 
334       dm('O',4,"output.c:printnextline() Need new line\n");
335 
336       switch (diff_state)
337 	{
338 	case NORMAL:
339 	  dm('O',4,"output.c:printnextline() diff_state is NORMAL\n");
340 	  if (getdelline(line_number+1,input_line,input_status)){
341 	    dm('O',4,"output.c:printnextline() Found deleted line - diff_state is now DELETE\n");
342 	    diff_state = DELETE;
343 	  }
344 	  else
345 	    {
346 	      /*
347 	       * there isn't a deleted line - get
348 	       * the next line and check if this is
349 	       * an inserted line.
350 	       */
351 	      line_number += 1;
352 	      retval = getnextline(get_char,&blank_line,input_line,input_status);
353 	      if (line_inserted(line_number))
354 		{
355 		  dm('O',4,"output.c:printnextline() This line is inserted - diff_state is now INSERT\n");
356 		  diff_state = INSERT;
357 		}
358 	      else
359 		{
360 		  dm('O',4,"output.c:printnextline() Nothing unusual - diff_state is still NORMAL\n");
361 		  diff_state = NORMAL;
362 		}
363 	    }
364 	  break;
365 
366 	case DELETE:
367 	  dm('O',4,"output.c:printnextline() diff_state is DELETE\n");
368 	  /*
369 	   * There is already a line in input_line, put there at the end
370 	   * of the previous call to print_page()
371 	   */
372 	  break;
373 
374 	case INSERT:
375 	default:
376 	  abort();
377 	}
378       input_line_length = (int)strlen(input_line);
379       last_char_printed = -1;
380       first_line_segment = TRUE;
381 
382       /*
383        * At this point
384        * diff_state
385        * NORMAL		print the line, retval set to return of getnextline()
386        * DELETE		print the deleted line, retval is set to return of
387        *			previous getnextline() or LINE
388        * INSERT		print the line as an inserted line, retval is set to
389        *			return of getnextline()
390        */
391 
392       /*
393        * page_has_changed() flags this page and function as having changed
394        */
395       if (diff_state != NORMAL) page_has_changed(page_number);
396 
397       if (pass == 1)
398 	{
399 	  /*
400 	   * a new line of source file is about to be printed - so
401 	   * the appropriate line start, including line number and
402 	   * a mark to show insertions or deletions, needs to be
403 	   * printed.
404 	   */
405 	  if (diff_state == DELETE)
406 	    {
407 	      dm('O',4,"output.c:printnextline() Printing line with diff state DELETE\n");
408 	      if ((no_show_line_number == FALSE) || (no_show_indent_level == FALSE))
409 		PUTS("Lpt (-          ) CFs setfont show (");
410 	      else
411 		PUTS("Lpt (-) CFs setfont show (");
412 	    }
413 	  else
414 	    {
415 	      if (diff_state == INSERT)
416 		{
417 		  dm('O',4,"output.c:printnextline() Printing line with diff state INSERT\n");
418 		  PUTS("Lpt BFs setfont (+");
419 		}
420 	      else
421 		{
422 		  dm('O',4,"output.c:printnextline() Printing line with diff state NORMAL\n");
423 		  PUTS("Lpt CFs setfont ( ");
424 		}
425 
426 	      if (blank_line)
427 		PUTS("          ");
428 	      else if ((no_show_line_number == FALSE) || (no_show_indent_level == FALSE))
429 		{
430 		  if (no_show_line_number == FALSE)
431 		    printf("%5ld ",line_number);
432 		  else
433 		    PUTS("      ");
434 		  if ((no_show_indent_level == FALSE) && (braces_depth != 0))
435 		    printf("%2d  ",braces_depth);
436 		  else
437 		    PUTS("    ");
438 		}
439 	      PUTS(") show (");
440 	    }
441 	}
442     }
443   else
444     {
445       if (pass == 1)
446 	{
447 	  if (diff_state == DELETE)
448 	    {
449 	      dm('O',4,"output.c:printnextline() Printing line with diff state DELETE\n");
450 	      PUTS("Lpt CFs setfont ( ");
451 	    }
452 	  else if (diff_state == INSERT)
453 	    {
454 	      dm('O',4,"output.c:printnextline() Printing line with diff state INSERT\n");
455 	      PUTS("Lpt BFs setfont ( ");
456 	    }
457 	  else
458 	    {
459 	      dm('O',4,"output.c:printnextline() Printing line with diff state NORMAL\n");
460 	      PUTS("Lpt CFs setfont ( ");
461 	    }
462 	  if ((no_show_line_number == FALSE) || (no_show_indent_level == FALSE))
463 	    PUTS("          ");
464 	  PUTS(") show (");
465 	}
466     }
467 
468   /*
469    * now work out where (and if) the line needs to be split...
470    */
471   output_line_end = line_end(input_line,last_char_printed);
472 
473   if (pass == 1)
474     {
475       output_char_idx = last_char_printed;
476 
477       if ((no_clever_wrap == FALSE) && !first_line_segment)
478 	{
479 	  /* want to pad the outgoing line with leading spaces */
480 	  int leading_spaces =
481 	    page_width - (output_line_end - last_char_printed);
482 
483 	  for (count=1; count < leading_spaces; count++)
484 	    putchar(' ');
485 	}
486 
487       /*
488        * now print the line a character at a time...  with some clever
489        * coding this could probably be speeded up by printing sections
490        * of the line instead of single characters.
491        */
492 
493       last_char_status = input_status[output_char_idx+1];
494 
495       while (output_char_idx < output_line_end)
496 	{
497 
498 	  output_char_idx += 1;
499 
500 	  /*
501 	   * Check to see if anything should be printed
502 	   * out before printing the character
503 	   */
504 	  if (last_char_status != input_status[output_char_idx])
505 	    {
506 	      PUTS(segment_ends[last_char_status][diff_state]);
507 	      PUTS("(");
508 	    }
509 
510 	  /*
511 	   * Print the character....
512 	   */
513 	  switch(input_line[output_char_idx])
514 	    {
515 	    case '(':
516 	    case ')':
517 	    case '\\': putchar('\\'); break;
518 	    default:
519 	      ;
520 	    }
521 	  putchar(input_line[output_char_idx]);
522 
523 	  last_char_status = input_status[output_char_idx];
524 	}
525 
526       PUTS(segment_ends[last_char_status][diff_state]);
527       PUTS(" Nl\n");
528       output_char_idx++;
529     }
530   last_char_printed = output_line_end;
531 
532   /*
533    * Have we reached the end of the line to be printed?
534    */
535   if (last_char_printed == input_line_length-1)
536     {
537       /*
538        * At this point:
539        * diff_state
540        * NORMAL	line printed, retval set to return of getnextline()
541        * INSERT	line printed, retval is set to return of getnextline()
542        * DELETE	deleted line printed, retval is set to return of
543        *		previous getnextline() or STREAM_OK
544        *
545        * We should now check to see if there are
546        * any deleted lines to be printed.  If there are then set diff_state
547        * and start printing them next time round.  Otherwise return retval.
548        */
549 
550       input_line_length = 0;
551 
552       if (getdelline(line_number+1,input_line,input_status))
553 	{
554 	  diff_state = DELETE;
555 	  dm('O',3,"output.c:printnextline() Return value is STREAM_OK\n");
556 	  return(STREAM_OK);
557 	}
558 
559       diff_state = NORMAL;
560 
561       if (retval & STREAM_FILE_END)
562 	{
563 	  /*
564 	   * Set everything up for the next file
565 	   */
566 	  line_number = 0;
567 	  retval = STREAM_OK;
568 	  dm('O',3,"output.c:printnextline() Return value is STREAM_FILE_END\n");
569 	  return (STREAM_FILE_END);
570 	}
571 
572       dm('O',3,"output.c:printnextline() Return value is %x\n", retval);
573 
574       return(retval);
575 
576     }
577   else
578     {
579       dm('O',3,"output.c:printnextline() Not fully printed this line - return value is LINE\n");
580       return(STREAM_OK);
581     }
582 }
583 
584 /******************************************************************************
585  * function:
586  *	blank_page
587  * prints out a blank page (well, what did you expect!).
588  */
589 boolean
blank_page(boolean output_page)590 blank_page(boolean output_page)
591 
592 {
593   file_page_number += 1;
594   page_number += 1;
595 
596   print_text_header(page_number, total_pages);
597   return PS_endpage(output_page);
598 }
599 
600 /******************************************************************************
601  * function:
602  *	print_page
603  *
604  * prints out a single page of a file.
605  */
606 boolean
print_page(void)607 print_page(void)
608 
609 {
610   short	page_line_number = 0;
611   stream_status	retval=STREAM_OK;
612 
613   file_page_number += 1;
614   page_number += 1;
615 
616   dm('O',2,"output.c:print_page() Printing file %d page %d, filepage %d, pass %d\n", file_number, page_number, file_page_number, pass);
617 
618 
619   /*
620    * Print a page header
621    */
622   print_text_header(page_number, total_pages);
623 
624   /*
625    * now print out enough lines to fill the page or until we've reached
626    * a page break (which is caused by end of input, file, function or
627    * a newpage character).
628    */
629 
630   while (page_line_number < page_length)
631     {
632       dm('O',3,"output.c:print_page() Printing line %d\n", page_line_number);
633       retval = printnextline();
634       page_line_number += 1;
635       if ((retval & (STREAM_PAGE_END|STREAM_FILE_END))
636 	  || ((retval & STREAM_FUNCTION_END) && (no_function_page_breaks == FALSE)))
637 	break;
638     }
639 
640   /*
641    * And finally end the page
642    */
643   reached_end_of_sheet = PS_endpage(print_prompt(PAGE_BODY, file_page_number, file_name(file_number)));
644 
645   dm('O',2,"output.c:print_page() retval %x\n", retval);
646   return (!(retval & STREAM_FILE_END));
647 }
648 
649 /*
650  * function:
651  *	print_file
652  *
653  * prints out a page at a time, calling blank_page to ensure an even number
654  * of pages at the end of the file.
655  */
656 void
print_file(void)657 print_file(void)
658 
659 {
660   file_page_number = 0;
661 
662   /*
663    * set get_char appropriately
664    */
665   set_get_char(current_filename);
666 
667   while (print_page());
668 
669   /*
670    * we want to ensure that each file starts on an odd page (i.e. a
671    * new sheet of paper, and also that the whole output has an even
672    * number of pages in total, so print any blank pages necessary.
673    */
674 
675   if (new_sheet_after_file)
676     {
677       fill_sheet_with_blank_pages();
678     }
679 }
680 
681 /*
682  * Print blank pages until the last page printed was the last page
683  * on a physical sheet.
684  */
685 void
fill_sheet_with_blank_pages(void)686 fill_sheet_with_blank_pages(void)
687 {
688   while (!reached_end_of_sheet)
689     {
690       dm('O',3,"output.c:print_page() Printing a blank page\n");
691       reached_end_of_sheet = blank_page(print_prompt(PAGE_BLANK, 0, NULL));
692     }
693 }
694