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