add netbsd nl(1)
[rofl0r-hardcore-utils.git] / man.c
blob53b1056e0df0e15bd3a4b06b751406f617d90a0b
1 /* (C) 1997 Robert de Bath
2 * (C) 2013 rofl0r
3 * under the terms of the GPL.
5 * This is a manual pager program, it will search for and format manual
6 * pages which it then pipes to more.
8 * The program understands manual pages that have been compressed with
9 * either 'compress' or 'gzip' and will decompress them on the fly.
11 * The environment is checked for these variables:
12 * MANSECT=1:2:3:4:5:6:7:8:9 # Manual section search order.
13 * MANPATH=/usr/local/man:/usr/man # Directorys to search for man tree.
14 * PAGER=more # pager progam to use.
15 * PATH=... # Search for gzip/uncompress
17 * The program will display documents that are either in it's own "nroff -man"
18 * like format or in "catman" format, it will not correctly display pages in
19 * the BSD '-mdoc' format.
21 * Neither nroff nor any similar program is needed as this program has it's
22 * own built in _man_ _page_ _formatter_. This is NOT an nroff clone and will
23 * not (for instance) understand macros or tbl constructs.
25 * Unlike groff this is small, fast and picky!
28 #define DONT_SPLATTER /* Lots of messages out */
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <ctype.h>
34 #include <string.h>
35 #include <assert.h>
36 #include <sys/ioctl.h>
38 static FILE *ofd, *ifd;
39 static int *line_ptr;
41 static int keep_nl; /* How many nl to keep til eof */
42 static int optional_keep; /* Is the next keep optional ? */
43 static int pending_nl; /* Is there a pending newline on output? */
44 static int no_fill; /* Disable 'filling' of lines */
45 static int left_indent; /* Current step of left margin */
46 static int old_para_indent; /* Indent to go back to after this paragraph */
47 static int is_tty;
48 static int current_line; /* Line number = ? */
49 static int gaps_on_line; /* Gaps on line for adjustments */
50 static int flg_w;
51 static int disable_pp;
53 static int line[256]; /* Buffer for building output line */
54 static char whitespace[256];
55 static char line_header[256]; /* Page header line */
56 static char doc_footer[256]; /* Document footer line */
57 static char man_file[256];
58 static char word[80]; /* Current word */
60 static int right_margin = 65; /* Don't print past this column */
61 static int verbose = 1; /* print warnings about unknown macros */
62 static int no_nl = 1; /* Next NL in input file is ignored */
63 static int catmode = 1; /* Have we seen a '.XX' command ? */
64 static int cur_font = 0x100; /* Current font, 1 == Roman */
65 static int next_line_indent = -1; /* Indent after next line_break */
66 static int input_tab = 8; /* Tab width in input file */
67 static int right_adjust = 1; /* Adjust right margin */
68 static int standard_tab = 5; /* Amount left margin stepped by */
71 static int lineno = 1;
73 static int find_page(char *name, char *sect);
74 static void step(char **pcurr, char **pnext);
75 static int open_page(char *name);
76 static void close_page(void);
77 static void do_file(void);
78 static int fetch_word(void);
79 static int do_command(void);
80 static void do_skipeol(void);
81 static int do_fontwords(int this_font, int other_font, int early_exit);
82 static int do_noargs(int cmd_id);
83 static int do_argvcmd(int cmd_id);
84 static void build_headers(void);
85 static void print_word(char *pword);
86 static void line_break(void);
87 static void page_break(void);
88 static void print_header(void);
89 static void print_doc_footer(void);
91 static void usage(void)
93 fprintf(stderr, "man [-w] [-v|-q] [-P] [section-number] page\n"
94 "-w\tonly print the location of matching manpages\n"
95 "-v\tForce verbose output. (default)\n"
96 "-q\tForce quiet output.\n"
97 "-P\tDisable usage of manpp preprocessor\n"
98 "`page` may be specified as `-`, in which case stdin is used.\n");
99 exit(1);
102 /****************************************************************************
103 * Main routine, hunt down the manpage.
105 int main(int argc, char **argv) {
106 ofd = 0;
107 ifd = 0;
108 int do_pclose_ofd = 0;
109 int ar;
110 char *mansect = 0;
111 char *manname = 0;
113 for(ar = 1; ar < argc; ar++)
114 if(argv[ar][0] == '-' && argv[ar][1]) {
115 char *p;
116 for(p = argv[ar] + 1; *p; p++)
117 switch (*p) {
118 case 'w':
119 flg_w = 1;
120 break;
121 case 'v':
122 verbose = 1;
123 break;
124 case 'q':
125 verbose = 0;
126 break;
127 case 'P':
128 disable_pp = 1;
129 break;
131 } else if(argv[ar][0] == '-') {
132 manname = "<stdin>";
133 ifd = stdin;
134 } else if(isdigit(argv[ar][0]))
135 mansect = argv[ar];
136 else if(manname == 0)
137 manname = argv[ar];
138 else if(mansect == 0) {
139 mansect = manname;
140 manname = argv[ar];
141 } else {
142 fprintf(stderr, "Ignoring argument %s\n", argv[ar]);
143 break;
146 if(manname == 0) usage();
148 if(!ifd && find_page(manname, mansect) < 0) {
149 if(mansect)
150 fprintf(stderr, "No entry for %s in section %s of the manual.\n", manname, mansect);
151 else
152 fprintf(stderr, "No manual entry for %s\n", manname);
153 exit(1);
155 if(flg_w)
156 exit(0);
158 /* ifd is now the file - display it */
159 if(isatty(1)) { /* If writing to a tty do it to a pager */
160 is_tty = 1;
161 char *pager = getenv("PAGER");
162 if(pager) ofd = popen(pager, "w");
163 if(!ofd) ofd = popen("less", "w");
164 if(!ofd) ofd = popen("more", "w");
165 if(!ofd) ofd = stdout;
166 else {
167 do_pclose_ofd = 1;
168 #ifdef TIOCGWINSZ
169 struct winsize ws;
170 if(!ioctl(0, TIOCGWINSZ, &ws))
171 right_margin = ws.ws_col>251 ? 250 : ws.ws_col-2;
172 else
173 #endif
174 right_margin = 78;
176 } else
177 ofd = stdout;
179 do_file();
181 /* Close files */
182 if(do_pclose_ofd)
183 pclose(ofd);
184 close_page();
185 exit(0);
188 static int find_page(char *name, char *sect) {
189 static char defpath[] = "/usr/local/share/man:/usr/share/man";
190 static char defsect[] = "1p:1:1perl:2:3p:3:3perl:4:5:6:7:8:9:0p";
191 static char defsuff[] = ":.gz:.xz";
192 static char manorcat[] = "man:cat";
194 char fbuf[256];
195 char *manpath;
196 char *mansect = sect;
197 char *mansuff;
198 char *mc, *mp, *ms, *su, *nmc, *nmp, *nms, *nsu;
199 int rv = -1;
201 manpath = getenv("MANPATH");
202 if(!manpath)
203 manpath = defpath;
204 if(!mansect)
205 mansect = getenv("MANSECT");
206 if(!mansect)
207 mansect = defsect;
208 mansuff = defsuff;
210 if(strchr(name, '/')) {
211 for(su = nsu = mansuff, step(&su, &nsu); su; step(&su, &nsu)) {
212 snprintf(fbuf, sizeof(fbuf), "%s%s", name, su);
213 if((rv = open_page(fbuf)) >= 0)
214 break;
216 *man_file = 0;
217 return rv;
220 /* SEARCH!! */
221 for(mc = nmc = manorcat, step(&mc, &nmc); mc; step(&mc, &nmc))
222 for(ms = nms = mansect, step(&ms, &nms); ms; step(&ms, &nms))
223 for(mp = nmp = manpath, step(&mp, &nmp); mp; step(&mp, &nmp))
224 for(su = nsu = mansuff, step(&su, &nsu); su; step(&su, &nsu)) {
225 snprintf(fbuf, sizeof fbuf, "%s/%s%s/%s.%s%s", mp, mc, ms, name, ms, su);
227 /* Got it ? */
228 if(access(fbuf, 0) < 0)
229 continue;
230 if(flg_w) {
231 printf("%s\n", fbuf);
232 rv = 0;
233 continue;
236 /* Try it ! */
237 if((rv = open_page(fbuf)) >= 0) {
238 char *p;
239 snprintf(man_file, sizeof man_file, "%s", fbuf);
240 p = strrchr(man_file, '/');
241 if(p)
242 *p = 0;
243 p = strrchr(man_file, '/');
244 if(p)
245 p[1] = 0;
246 return 0;
250 return rv;
253 static void step(char **pcurr, char **pnext) {
254 char *curr = *pcurr;
255 char *next = *pnext;
257 if(curr == 0)
258 return;
259 if(curr == next) {
260 next = strchr(curr, ':');
261 if(next)
262 *next++ = 0;
263 } else {
264 curr = next;
265 if(curr) {
266 curr[-1] = ':';
267 next = strchr(curr, ':');
268 if(next)
269 *next++ = 0;
273 *pcurr = curr;
274 *pnext = next;
277 static char* which(const char* prog, char* buf, size_t buf_size) {
278 char* path = getenv("PATH");
279 if(!path) return 0;
280 while(1) {
281 path += strspn(path, ":");
282 size_t l = strcspn(path, ":");
283 if(!l) break;
284 if (snprintf(buf, buf_size, "%.*s/%s", (int)l, path, prog) >= (ssize_t) buf_size)
285 continue;
286 if(!access(buf, X_OK)) return buf;
287 path += l;
289 return 0;
292 static int program_exists_in_path(const char* prog) {
293 char buf[256];
294 return !!which(prog, buf, sizeof buf);
297 #define PREPROC "manpp"
298 static int preprocessor_exists(void) {
299 return disable_pp ? 0 : program_exists_in_path(PREPROC);
302 static int open_page(char *name) {
303 char *p, *command = 0;
304 char buf[256];
306 if(access(name, 0) < 0)
307 return -1;
309 if((p = strrchr(name, '.'))) {
310 if(!strcmp(p, ".gz")) command = "gzip -dc ";
311 else if(!strcmp(p, ".xz")) command = "xzcat -dc ";
313 if(!command) command = "cat ";
315 snprintf(buf, sizeof buf, "%s%s%s", command, name, preprocessor_exists() ? " | " PREPROC : "");
316 if(!(ifd = popen(buf, "r"))) return -1;
317 return 0;
320 static void close_page(void) {
321 if(ifd) {
322 pclose(ifd);
323 ifd = 0;
327 /****************************************************************************
328 * ifd is the manual page, ofd is the 'output' file or pipe, format it!
330 static void do_file(void) {
331 int nl;
332 ungetc('\r', ifd);
334 while((nl = fetch_word()) >= 0) {
335 #ifdef SPLATTER
336 fprintf(ofd, ">WS='%s',", whitespace);
337 fprintf(ofd, "catmode=%d,", catmode);
338 fprintf(ofd, "nl=%d,", nl);
339 fprintf(ofd, "no_nl=%d,", no_nl);
340 fprintf(ofd, "no_fill=%d,", no_fill);
341 fprintf(ofd, "keep_nl=%d,", keep_nl);
342 fprintf(ofd, "opt_keep=%d,", optional_keep);
343 fprintf(ofd, "WD='%s',", word);
344 fprintf(ofd, "\n");
345 #endif
347 if(catmode) {
348 if(strcmp(word, "'\\\"") == 0 || strcmp(word, "'''") == 0) {
349 /* This is a marker sometimes used for opening subprocesses like
350 * tbl and equ; this program ignores it.
352 do_skipeol();
353 } else if(*whitespace == '\r')
354 fprintf(ofd, "%s%s", whitespace + 1, word);
355 else
356 fprintf(ofd, "%s%s", whitespace, word);
357 } else {
358 if(keep_nl && nl && !no_nl) {
359 if(optional_keep) {
360 optional_keep = 0;
361 if(line_ptr == 0 || next_line_indent < 0 ||
362 left_indent + (line_ptr - line) + 1 > next_line_indent)
363 line_break();
364 else if(line_ptr != 0 && next_line_indent > 0) {
365 while(left_indent + (line_ptr - line) + 1 <= next_line_indent)
366 *line_ptr++ = cur_font + ' ';
368 } else
369 line_break();
370 if(keep_nl > 0)
371 keep_nl--;
374 if(nl == 1 && no_fill)
375 line_break();
377 if(nl == 1 && (word[0] == '.' ||
378 (word[0] == '\'' && strcmp(word, "'\\\"") == 0) ||
379 (word[0] == '\'' && strcmp(word, "'''") == 0)
380 )) {
381 no_nl = 1;
382 if(do_command() < 0)
383 break;
384 } else {
385 if(*whitespace)
386 print_word(whitespace);
387 print_word(word);
388 no_nl = 0;
393 print_doc_footer();
396 static int fetch_word(void) {
397 static int col = 0;
398 char *p;
399 int ch, nl;
401 nl = 0;
402 *(p = whitespace) = 0;
404 if(!catmode && !no_fill)
405 p++;
407 while((ch = fgetc(ifd)) != EOF && isspace(ch)) {
408 if(ch == '\n') lineno++;
409 if(nl && no_fill && ch != '.' && ch != '\n')
410 break;
411 if(nl && !catmode && ch == '\n') {
412 *whitespace = 0;
413 strcpy(word, ".sp");
414 ungetc(ch, ifd);
415 return 1;
417 nl = (ch == '\n' || ch == '\r');
418 if(nl)
419 col = 0;
420 else
421 col++;
423 if(no_fill && nl && *whitespace) {
424 *word = 0;
425 ungetc(ch, ifd);
426 return 0;
429 if(p < whitespace + sizeof(whitespace) - 1 && (!nl || catmode))
430 *p++ = ch;
432 if(ch == '\t' && !catmode) {
433 p[-1] = ' ';
434 while(col % input_tab) {
435 if(p < whitespace + sizeof(whitespace) - 1)
436 *p++ = ' ';
437 col++;
441 if(!catmode && !no_fill && nl)
442 *(p = whitespace) = 0;
444 *p = 0;
446 if(catmode && ch == '.' && nl)
447 catmode = 0;
449 *(p = word) = 0;
450 if(ch == EOF || p > word + sizeof(word) / 2) {
451 if(p != word) {
452 ungetc(ch, ifd);
453 *p = 0;
454 return nl;
456 return -1;
458 ungetc(ch, ifd);
460 while((ch = fgetc(ifd)) != EOF && !isspace(ch)) {
461 if(p < word + sizeof(word) - 1)
462 *p++ = ch;
463 col++;
464 if(ch == '\\') {
465 if((ch = fgetc(ifd)) == EOF)
466 break;
467 // if( ch == ' ' ) ch = ' ' + 0x80; /* XXX Is this line needed? */
468 if(p < word + sizeof(word) - 1)
469 *p++ = ch;
470 col++;
473 *p = 0;
474 ungetc(ch, ifd);
476 return (nl != 0);
479 /****************************************************************************
480 * Accepted nroff commands and executors.
483 enum cmd_class {
484 CCLASS_NONE = 0,
485 CCLASS_PARAMETERED,
486 CCLASS_FONTCHANGER,
487 CCLASS_SO,
490 static const struct cmd_list_s {
491 char cmd[3];
492 char class; /* enum cmd_class */
493 char id;
494 } cmd_list[] = {
495 {"\\\"", CCLASS_NONE, 0},
496 {"nh", CCLASS_NONE, 0}, /* This program never inserts hyphens */
497 {"hy", CCLASS_NONE, 0}, /* This program never inserts hyphens */
498 {"PD", CCLASS_NONE, 0}, /* Inter-para distance is 1 line */
499 {"DT", CCLASS_NONE, 0}, /* Default tabs, they can't be non-default! */
500 {"IX", CCLASS_NONE, 0}, /* Indexing for some weird package */
501 {"Id", CCLASS_NONE, 0}, /* Line for RCS tokens */
502 {"BY", CCLASS_NONE, 0}, /* I wonder where this should go ? */
503 {"nf", CCLASS_NONE, 1}, /* Line break, Turn line fill off */
504 {"fi", CCLASS_NONE, 2}, /* Line break, Turn line fill on */
505 {"sp", CCLASS_NONE, 3}, /* Line break, line space (arg for Nr lines) */
506 {"br", CCLASS_NONE, 4}, /* Line break */
507 {"bp", CCLASS_NONE, 5}, /* Page break */
508 {"PP", CCLASS_NONE, 6},
509 {"LP", CCLASS_NONE, 6},
510 {"P", CCLASS_NONE, 6}, /* Paragraph */
511 {"RS", CCLASS_NONE, 7}, /* New Para + Indent start */
512 {"RE", CCLASS_NONE, 8}, /* New Para + Indent end */
513 {"HP", CCLASS_NONE, 9}, /* Begin hanging indent (TP without arg?) */
514 {"ad", CCLASS_NONE, 10}, /* Line up right margin */
515 {"na", CCLASS_NONE, 11}, /* Leave right margin unaligned */
516 {"ta", CCLASS_NONE, 12}, /* Changes _input_ tab spacing, right? */
517 {"TH", CCLASS_PARAMETERED, 1},/* Title and headers */
518 {"SH", CCLASS_PARAMETERED, 2},/* Section */
519 {"SS", CCLASS_PARAMETERED, 3},/* Subsection */
520 {"IP", CCLASS_PARAMETERED, 4},/* New para, indent except argument 1 */
521 {"TP", CCLASS_PARAMETERED, 5},/* New para, indent except line 1 */
522 {"B", CCLASS_FONTCHANGER, 22},/* Various font fiddles */
523 {"BI", CCLASS_FONTCHANGER, 23},
524 {"BR", CCLASS_FONTCHANGER, 21},
525 {"I", CCLASS_FONTCHANGER, 33},
526 {"IB", CCLASS_FONTCHANGER, 32},
527 {"IR", CCLASS_FONTCHANGER, 31},
528 {"RB", CCLASS_FONTCHANGER, 12},
529 {"RI", CCLASS_FONTCHANGER, 13},
530 {"SB", CCLASS_FONTCHANGER, 42},
531 {"SM", CCLASS_FONTCHANGER, 44},
532 {"C", CCLASS_FONTCHANGER, 22},/* PH-UX manual pages! */
533 {"CI", CCLASS_FONTCHANGER, 23},
534 {"CR", CCLASS_FONTCHANGER, 21},
535 {"IC", CCLASS_FONTCHANGER, 32},
536 {"RC", CCLASS_FONTCHANGER, 12},
537 {"so", CCLASS_SO, 0},
538 {"\0\0", CCLASS_NONE, 0}
541 static int do_command(void) {
542 char *cmd;
543 int i;
544 char lbuf[10];
546 cmd = word + 1;
548 /* Comments don't need the space */
549 if(strncmp(cmd, "\\\"", 2) == 0)
550 cmd = "\\\"";
552 for(i = 0; cmd_list[i].cmd[0]; i++) {
553 if(strcmp(cmd_list[i].cmd, cmd) == 0)
554 break;
557 if(cmd_list[i].cmd[0] == 0) {
558 if(verbose) {
559 strncpy(lbuf, cmd, 3);
560 lbuf[3] = 0;
561 line_break();
562 i = left_indent;
563 left_indent = 0;
564 snprintf(word, sizeof word, "**** Unknown formatter command: .%s @%d", lbuf, lineno);
565 print_word(word);
566 line_break();
567 left_indent = i;
570 i = 0; /* Treat as comment */
573 switch (cmd_list[i].class) {
574 case CCLASS_PARAMETERED: /* Parametered commands */
575 return do_argvcmd(cmd_list[i].id);
577 case CCLASS_FONTCHANGER: /* Font changers */
578 return do_fontwords(cmd_list[i].id / 10, cmd_list[i].id % 10, 0);
580 case CCLASS_SO: /* .so */
581 fetch_word();
582 if(strlen(man_file) + 4 < sizeof man_file)
583 strcat(man_file, word);
584 close_page();
585 if(find_page(man_file, (char *) 0) < 0) {
586 fprintf(stderr, "Cannot open .so file %s\n", word);
587 return -1;
589 ungetc('\r', ifd);
590 break;
592 case CCLASS_NONE:
593 default:
594 do_skipeol();
595 if(cmd_list[i].id)
596 return do_noargs(cmd_list[i].id);
598 return 0;
601 static void do_skipeol(void) {
602 int ch;
603 char *p = word;
605 while((ch = fgetc(ifd)) != EOF && ch != '\n')
606 if(p < word + sizeof(word) - 1)
607 *p++ = ch;;
608 *p = 0;
609 ungetc(ch, ifd);
612 static void flush_word(char **p) {
613 memcpy(*p, "\\fR", 4);
614 print_word(word);
615 *p = word;
618 static void insert_font(char **p, int font) {
619 const char ftab[] = " RBIS";
620 memcpy(*p, "\\f", 2);
621 (*p)[2] = ftab[font];
622 *p += 3;
625 static int do_fontwords(int this_font, int other_font, int early_exit) {
626 char *p = word;
627 int ch;
628 int in_quote = 0;
630 no_nl = 0; /* Line is effectivly been reprocessed so NL is visible */
631 for(;;) {
632 if(p == word) insert_font(&p, this_font);
633 /* at each turn, at most 5 bytes are appended to word
634 * in order to flush the buffer, 4 more bytes are required to stay free */
635 if(p+5+4 >= word+sizeof word) {
636 assert(p+4<word+sizeof word);
637 flush_word(&p);
638 continue;
640 if((ch = fgetc(ifd)) == EOF || ch == '\n')
641 break;
642 if(ch == '"') {
643 in_quote = !in_quote;
644 continue;
646 if(in_quote || !isspace(ch)) {
647 if(isspace(ch) && p > word + 3) {
648 flush_word(&p);
649 if(no_fill) print_word(" ");
650 continue;
652 *p++ = ch;
653 if(ch == '\\') {
654 if((ch = fgetc(ifd)) == EOF || ch == '\n') break;
655 *p++ = ch;
657 continue;
660 if(p != word + 3) {
661 if(early_exit) break;
663 if(this_font == other_font) flush_word(&p);
664 int i = this_font;
665 this_font = other_font;
666 other_font = i;
667 insert_font(&p, this_font);
670 ungetc(ch, ifd);
672 if(p > word + 3) flush_word(&p);
674 return 0;
677 static int do_noargs(int cmd_id) {
678 if(cmd_id < 10)
679 line_break();
680 switch (cmd_id) {
681 case 1:
682 no_fill = 1;
683 break;
684 case 2:
685 no_fill = 0;
686 break;
687 case 3:
688 pending_nl = 1;
689 break;
690 case 4:
691 break;
692 case 5:
693 page_break();
694 break;
695 case 6:
696 left_indent = old_para_indent;
697 pending_nl = 1;
698 break;
699 case 7:
700 pending_nl = 1;
701 left_indent += standard_tab;
702 old_para_indent += standard_tab;
703 break;
704 case 8:
705 pending_nl = 1;
706 left_indent -= standard_tab;
707 old_para_indent -= standard_tab;
708 break;
710 case 10:
711 right_adjust = 1;
712 break;
713 case 11:
714 right_adjust = 0;
715 break;
716 case 12:
717 input_tab = atoi(word);
718 if(input_tab <= 0)
719 input_tab = 8;
720 break;
722 return 0;
725 static int do_argvcmd(int cmd_id) {
726 int ch;
728 line_break();
729 while((ch = fgetc(ifd)) != EOF && (ch == ' ' || ch == '\t')) ;
730 ungetc(ch, ifd);
732 switch (cmd_id + 10 * (ch == '\n')) {
733 case 1: /* Title and headers */
734 page_break();
735 left_indent = old_para_indent = standard_tab;
736 build_headers();
737 break;
739 case 2: /* Section */
740 left_indent = 0;
741 next_line_indent = old_para_indent = standard_tab;
742 no_nl = 0;
743 keep_nl = 1;
744 pending_nl = 1;
746 do_fontwords(2, 2, 0);
747 return 0;
748 case 3: /* Subsection */
749 left_indent = standard_tab / 2;
750 next_line_indent = old_para_indent = standard_tab;
751 no_nl = 0;
752 keep_nl = 1;
753 pending_nl = 1;
755 do_fontwords(2, 2, 0);
756 break;
758 case 15:
759 case 5: /* New para, indent except line 1 */
760 do_skipeol();
761 next_line_indent = old_para_indent + standard_tab;
762 left_indent = old_para_indent;
763 pending_nl = 1;
764 keep_nl = 1;
765 optional_keep = 1;
766 break;
768 case 4: /* New para, indent except argument 1 */
769 next_line_indent = old_para_indent + standard_tab;
770 left_indent = old_para_indent;
771 pending_nl = 1;
772 keep_nl = 1;
773 optional_keep = 1;
774 do_fontwords(1, 1, 1);
775 do_skipeol();
776 break;
778 case 14:
779 pending_nl = 1;
780 left_indent = old_para_indent + standard_tab;
781 break;
784 return 0;
787 static void build_headers(void) {
788 char buffer[5][80];
789 int strno = 0;
790 unsigned stroff = 0;
791 int last_ch = 0, ch, in_quote = 0;
793 for(ch = 0; ch < 5; ch++)
794 buffer[ch][0] = 0;
796 for(;;) {
797 if((ch = fgetc(ifd)) == EOF || ch == '\n')
798 break;
799 if(ch == '"') {
800 if(last_ch == '\\') {
801 assert(stroff > 0);
802 stroff--;
803 break;
805 in_quote = !in_quote;
806 continue;
808 last_ch = ch;
809 if(in_quote || !isspace(ch)) {
810 /* Nb, this does nothing about backslashes, perhaps it should */
811 if(stroff < sizeof(buffer[strno]) - 1)
812 buffer[strno][stroff++] = ch;
813 continue;
815 buffer[strno][stroff] = 0;
817 if(buffer[strno][0]) {
818 strno++;
819 stroff = 0;
820 if(strno == 5)
821 break;
824 if(strno < 5)
825 buffer[strno][stroff] = 0;
826 ungetc(ch, ifd);
828 /* Ok we should have upto 5 arguments build the header and footer */
830 size_t l0 = strlen(buffer[0]),
831 l1 = strlen(buffer[1]),
832 l2 = strlen(buffer[2]),
833 l3 = strlen(buffer[3]),
834 l4 = strlen(buffer[4]),
835 l01 = l0 + l1 + 2;
836 snprintf(line_header, sizeof line_header, "%s(%s)%*s%*s(%s)", buffer[0],
837 buffer[1], (int) (right_margin/2-l01+l4/2+(l4&1)), buffer[4],
838 (int) (right_margin/2-l4/2-l01+l0-(l4&1)), buffer[0], buffer[1]);
839 snprintf(doc_footer, sizeof doc_footer, "%s%*s%*s(%s)", buffer[3],
840 (int) (right_margin/2-l3+l2/2+(l2&1)), buffer[2],
841 (int) (right_margin/2-l2/2-l01+l0-(l2&1)), buffer[0], buffer[1]);
843 do_skipeol();
846 static void print_word(char *pword) {
847 /* Eat \& \a .. \z and \A .. \Z
848 * \fX Switch to font X (R B I S etc)
849 * \(XX Special character XX
850 * \X Print as X
852 #define checkw(X) assert(d+X<wword+(sizeof wword/sizeof wword[0]))
853 #define checkl(X) assert(line_ptr+X<line+(sizeof line/sizeof line[0]))
854 char *s;
855 int *d, ch = 0;
856 int length = 0;
857 int wword[256];
858 int sp_font = cur_font;
860 /* Eat and translate characters. */
861 for(s = pword, d = wword; *s; s++) {
862 ch = 0;
863 if(*s == '\n')
864 continue;
865 if(*s != '\\') {
866 checkw(1);
867 *d++ = (ch = *s) + cur_font;
868 length++;
869 } else {
870 if(s[1] == 0)
871 break;
872 s++;
873 if(*s == 'f') {
874 if(!is_tty) s++;
875 else if(s[1]) {
876 static char fnt[] = " RBI";
877 char *p = strchr(fnt, *++s);
878 if(p == 0)
879 cur_font = 0x100;
880 else
881 cur_font = 0x100 * (p - fnt);
883 continue;
884 } else if(*s == 's') {
885 /* Font size adjusts - strip */
886 while(s[1] && strchr("+-0123456789", s[1]))
887 s++;
888 continue;
889 } else if(isalpha(*s) || strchr("!&^[]|~", *s))
890 continue;
891 else if(*s == '(' || *s == '*') {
892 /* XXX Humm character xlate - http://mdocml.bsd.lv/man/mandoc_char.7.html */
893 int out = '*';
894 if(*s == '*') {
895 if(s[1])
896 ++s;
897 } else if(s[1] == 'm' && s[2] == 'i') {
898 out = '-';
899 s+=2;
900 goto K;
902 if(s[1])
903 ++s;
904 if(s[1])
905 ++s;
907 checkw(1);
908 *d++ = out + cur_font;
909 length++;
910 continue;
913 checkw(1);
914 *d++ = *s + cur_font;
915 length++;
919 checkw(1);
920 *d = 0;
921 #ifdef SPLATTER
923 int *x;
924 fprintf(ofd, ">WORD:");
925 for(x = wword; *x; x++)
926 fputc(*x, ofd);
927 fprintf(ofd, ":\n");
929 #endif
931 if(*wword == 0)
932 return;
934 if(line_ptr)
935 if(line_ptr + ((line_ptr[-1] & 0xFF) == '.') - line + length >= right_margin - left_indent) {
936 right_adjust = -right_adjust;
937 line_break();
940 if(line_ptr == 0)
941 line_ptr = line;
942 else {
943 assert(line_ptr > line);
944 if(!no_fill && (line_ptr[-1] & 0xFF) > ' ') {
945 if((line_ptr[-1] & 0xFF) == '.') {
946 checkl(1);
947 *line_ptr++ = cur_font + ' ';
949 checkl(1);
950 *line_ptr++ = sp_font;
951 gaps_on_line++;
955 checkl(length);
956 memcpy(line_ptr, wword, length * sizeof(int));
957 line_ptr += length;
958 #undef checkw
959 #undef checkl
962 static void line_break(void) {
963 int *d, ch;
964 int spg = 1, rspg = 1, spgs = 0, gap = 0;
966 if(line_ptr == 0)
967 return;
969 if(current_line == 0)
970 print_header();
972 if(current_line)
973 current_line += 1 + pending_nl;
974 for(; pending_nl > 0; pending_nl--)
975 fprintf(ofd, "\n");
977 if(right_adjust < 0) {
978 int over = right_margin - left_indent - (line_ptr - line);
979 #ifdef SPLATTER
980 fprintf(ofd, ">Gaps=%d, Over=%d, ", gaps_on_line, over);
981 #endif
982 if(gaps_on_line && over) {
983 spg = rspg = 1 + over / gaps_on_line;
984 over = over % gaps_on_line;
985 if(over) {
986 if(current_line % 2) {
987 spgs = over;
988 spg++;
989 } else {
990 spgs = gaps_on_line - over;
991 rspg++;
995 #ifdef SPLATTER
996 fprintf(ofd, " (%d,%d) sw=%d\n", spg, rspg, spgs);
997 #endif
998 right_adjust = 1;
1001 *line_ptr = 0;
1002 if(*line)
1003 for(ch = left_indent; ch > 0; ch--)
1004 fputc(' ', ofd);
1006 for(d = line; *d; d++) {
1007 ch = *d;
1008 if((ch & 0xFF) == 0) {
1009 int i;
1010 if(gap++ < spgs)
1011 i = spg;
1012 else
1013 i = rspg;
1014 for(; i > 0; i--)
1015 fputc(' ', ofd);
1016 } else
1017 switch (ch >> 8) {
1018 case 2:
1019 fputc(ch & 0xFF, ofd);
1020 fputc('\b', ofd);
1021 fputc(ch & 0xFF, ofd);
1022 break;
1023 case 3:
1024 fputc('_', ofd);
1025 fputc('\b', ofd);
1026 fputc(ch & 0xFF, ofd);
1027 break;
1028 default:
1029 fputc(ch & 0xFF, ofd);
1030 break;
1033 fputc('\n', ofd);
1035 line_ptr = 0;
1037 if(next_line_indent > 0)
1038 left_indent = next_line_indent;
1039 next_line_indent = -1;
1040 gaps_on_line = 0;
1043 static void page_break(void) {
1044 line_break();
1047 static void print_header(void) {
1048 pending_nl = 0;
1049 if(*line_header) {
1050 current_line = 1;
1051 fprintf(ofd, "%s\n\n", line_header);
1055 static void print_doc_footer(void) {
1056 line_break();
1057 int i;
1058 for(i = 0; i < 3; i++) fputc('\n', ofd);
1059 fprintf(ofd, "%s", doc_footer);