manpp: handle groff \N[dd] escape sequence
[rofl0r-hardcore-utils.git] / man.c
blobd51f8f9d785a23a40cf08ff44f09358c8bd7e7c0
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;
52 static int line[256]; /* Buffer for building output line */
53 static char whitespace[256];
54 static char line_header[256]; /* Page header line */
55 static char doc_footer[256]; /* Document footer line */
56 static char man_file[256];
57 static char word[80]; /* Current word */
59 static int right_margin = 65; /* Don't print past this column */
60 static int verbose = 1; /* print warnings about unknown macros */
61 static int no_nl = 1; /* Next NL in input file is ignored */
62 static int catmode = 1; /* Have we seen a '.XX' command ? */
63 static int cur_font = 0x100; /* Current font, 1 == Roman */
64 static int next_line_indent = -1; /* Indent after next line_break */
65 static int input_tab = 8; /* Tab width in input file */
66 static int right_adjust = 1; /* Adjust right margin */
67 static int standard_tab = 5; /* Amount left margin stepped by */
70 static int find_page(char *name, char *sect);
71 static void step(char **pcurr, char **pnext);
72 static int open_page(char *name);
73 static void close_page(void);
74 static void do_file(void);
75 static int fetch_word(void);
76 static int do_command(void);
77 static void do_skipeol(void);
78 static int do_fontwords(int this_font, int other_font, int early_exit);
79 static int do_noargs(int cmd_id);
80 static int do_argvcmd(int cmd_id);
81 static void build_headers(void);
82 static void print_word(char *pword);
83 static void line_break(void);
84 static void page_break(void);
85 static void print_header(void);
86 static void print_doc_footer(void);
88 static void usage(void)
90 fprintf(stderr, "man [-w] [-v|-q] [section-number] page\n"
91 "-w\tonly print the location of matching manpages\n"
92 "-v\tForce verbose output. (default)\n"
93 "-q\tForce quiet output.\n"
94 "`page` may be specified as `-`, in which case stdin is used.\n");
95 exit(1);
98 /****************************************************************************
99 * Main routine, hunt down the manpage.
101 int main(int argc, char **argv) {
102 ofd = 0;
103 ifd = 0;
104 int do_pclose_ofd = 0;
105 int ar;
106 char *mansect = 0;
107 char *manname = 0;
109 for(ar = 1; ar < argc; ar++)
110 if(argv[ar][0] == '-' && argv[ar][1]) {
111 char *p;
112 for(p = argv[ar] + 1; *p; p++)
113 switch (*p) {
114 case 'w':
115 flg_w = 1;
116 break;
117 case 'v':
118 verbose = 1;
119 break;
120 case 'q':
121 verbose = 0;
122 break;
124 } else if(argv[ar][0] == '-') {
125 manname = "<stdin>";
126 ifd = stdin;
127 } else if(isdigit(argv[ar][0]))
128 mansect = argv[ar];
129 else if(manname == 0)
130 manname = argv[ar];
131 else if(mansect == 0) {
132 mansect = manname;
133 manname = argv[ar];
134 } else {
135 fprintf(stderr, "Ignoring argument %s\n", argv[ar]);
136 break;
139 if(manname == 0) usage();
141 if(!ifd && find_page(manname, mansect) < 0) {
142 if(mansect)
143 fprintf(stderr, "No entry for %s in section %s of the manual.\n", manname, mansect);
144 else
145 fprintf(stderr, "No manual entry for %s\n", manname);
146 exit(1);
148 if(flg_w)
149 exit(0);
151 /* ifd is now the file - display it */
152 if(isatty(1)) { /* If writing to a tty do it to a pager */
153 is_tty = 1;
154 char *pager = getenv("PAGER");
155 if(pager) ofd = popen(pager, "w");
156 if(!ofd) ofd = popen("less", "w");
157 if(!ofd) ofd = popen("more", "w");
158 if(!ofd) ofd = stdout;
159 else {
160 do_pclose_ofd = 1;
161 #ifdef TIOCGWINSZ
162 struct winsize ws;
163 if(!ioctl(0, TIOCGWINSZ, &ws))
164 right_margin = ws.ws_col>251 ? 250 : ws.ws_col-2;
165 else
166 #endif
167 right_margin = 78;
169 } else
170 ofd = stdout;
172 do_file();
174 /* Close files */
175 if(do_pclose_ofd)
176 pclose(ofd);
177 close_page();
178 exit(0);
181 static int find_page(char *name, char *sect) {
182 static char defpath[] = "/usr/local/share/man:/usr/share/man";
183 static char defsect[] = "1p:1:1perl:2:3p:3:3perl:4:5:6:7:8:9:0p";
184 static char defsuff[] = ":.gz:.xz";
185 static char manorcat[] = "man:cat";
187 char fbuf[256];
188 char *manpath;
189 char *mansect = sect;
190 char *mansuff;
191 char *mc, *mp, *ms, *su, *nmc, *nmp, *nms, *nsu;
192 int rv = -1;
194 manpath = getenv("MANPATH");
195 if(!manpath)
196 manpath = defpath;
197 if(!mansect)
198 mansect = getenv("MANSECT");
199 if(!mansect)
200 mansect = defsect;
201 mansuff = defsuff;
203 if(strchr(name, '/')) {
204 for(su = nsu = mansuff, step(&su, &nsu); su; step(&su, &nsu)) {
205 snprintf(fbuf, sizeof(fbuf), "%s%s", name, su);
206 if((rv = open_page(fbuf)) >= 0)
207 break;
209 *man_file = 0;
210 return rv;
213 /* SEARCH!! */
214 for(mc = nmc = manorcat, step(&mc, &nmc); mc; step(&mc, &nmc))
215 for(ms = nms = mansect, step(&ms, &nms); ms; step(&ms, &nms))
216 for(mp = nmp = manpath, step(&mp, &nmp); mp; step(&mp, &nmp))
217 for(su = nsu = mansuff, step(&su, &nsu); su; step(&su, &nsu)) {
218 snprintf(fbuf, sizeof fbuf, "%s/%s%s/%s.%s%s", mp, mc, ms, name, ms, su);
220 /* Got it ? */
221 if(access(fbuf, 0) < 0)
222 continue;
223 if(flg_w) {
224 printf("%s\n", fbuf);
225 rv = 0;
226 continue;
229 /* Try it ! */
230 if((rv = open_page(fbuf)) >= 0) {
231 char *p;
232 snprintf(man_file, sizeof man_file, "%s", fbuf);
233 p = strrchr(man_file, '/');
234 if(p)
235 *p = 0;
236 p = strrchr(man_file, '/');
237 if(p)
238 p[1] = 0;
239 return 0;
243 return rv;
246 static void step(char **pcurr, char **pnext) {
247 char *curr = *pcurr;
248 char *next = *pnext;
250 if(curr == 0)
251 return;
252 if(curr == next) {
253 next = strchr(curr, ':');
254 if(next)
255 *next++ = 0;
256 } else {
257 curr = next;
258 if(curr) {
259 curr[-1] = ':';
260 next = strchr(curr, ':');
261 if(next)
262 *next++ = 0;
266 *pcurr = curr;
267 *pnext = next;
270 static char* which(const char* prog, char* buf, size_t buf_size) {
271 char* path = getenv("PATH");
272 if(!path) return 0;
273 while(1) {
274 path += strspn(path, ":");
275 size_t l = strcspn(path, ":");
276 if(!l) break;
277 if (snprintf(buf, buf_size, "%.*s/%s", (int)l, path, prog) >= (ssize_t) buf_size)
278 continue;
279 if(!access(buf, X_OK)) return buf;
280 path += l;
282 return 0;
285 static int program_exists_in_path(const char* prog) {
286 char buf[256];
287 return !!which(prog, buf, sizeof buf);
290 #define PREPROC "manpp"
291 static int preprocessor_exists(void) {
292 return program_exists_in_path(PREPROC);
295 static int open_page(char *name) {
296 char *p, *command = 0;
297 char buf[256];
299 if(access(name, 0) < 0)
300 return -1;
302 if((p = strrchr(name, '.'))) {
303 if(!strcmp(p, ".gz")) command = "gzip -dc ";
304 else if(!strcmp(p, ".xz")) command = "xzcat -dc ";
306 if(!command) command = "cat ";
308 snprintf(buf, sizeof buf, "%s%s%s", command, name, preprocessor_exists() ? " | " PREPROC : "");
309 if(!(ifd = popen(buf, "r"))) return -1;
310 return 0;
313 static void close_page(void) {
314 if(ifd) {
315 pclose(ifd);
316 ifd = 0;
320 /****************************************************************************
321 * ifd is the manual page, ofd is the 'output' file or pipe, format it!
323 static void do_file(void) {
324 int nl;
325 ungetc('\r', ifd);
327 while((nl = fetch_word()) >= 0) {
328 #ifdef SPLATTER
329 fprintf(ofd, ">WS='%s',", whitespace);
330 fprintf(ofd, "catmode=%d,", catmode);
331 fprintf(ofd, "nl=%d,", nl);
332 fprintf(ofd, "no_nl=%d,", no_nl);
333 fprintf(ofd, "no_fill=%d,", no_fill);
334 fprintf(ofd, "keep_nl=%d,", keep_nl);
335 fprintf(ofd, "opt_keep=%d,", optional_keep);
336 fprintf(ofd, "WD='%s',", word);
337 fprintf(ofd, "\n");
338 #endif
340 if(catmode) {
341 if(strcmp(word, "'\\\"") == 0 || strcmp(word, "'''") == 0) {
342 /* This is a marker sometimes used for opening subprocesses like
343 * tbl and equ; this program ignores it.
345 do_skipeol();
346 } else if(*whitespace == '\r')
347 fprintf(ofd, "%s%s", whitespace + 1, word);
348 else
349 fprintf(ofd, "%s%s", whitespace, word);
350 } else {
351 if(keep_nl && nl && !no_nl) {
352 if(optional_keep) {
353 optional_keep = 0;
354 if(line_ptr == 0 || next_line_indent < 0 ||
355 left_indent + (line_ptr - line) + 1 > next_line_indent)
356 line_break();
357 else if(line_ptr != 0 && next_line_indent > 0) {
358 while(left_indent + (line_ptr - line) + 1 <= next_line_indent)
359 *line_ptr++ = cur_font + ' ';
361 } else
362 line_break();
363 if(keep_nl > 0)
364 keep_nl--;
367 if(nl == 1 && (word[0] == '.' ||
368 (word[0] == '\'' && strcmp(word, "'\\\"") == 0) ||
369 (word[0] == '\'' && strcmp(word, "'''") == 0)
370 )) {
371 no_nl = 1;
372 if(do_command() < 0)
373 break;
374 } else {
375 if(nl == 1 && no_fill)
376 line_break();
377 if(*whitespace)
378 print_word(whitespace);
379 print_word(word);
380 no_nl = 0;
385 print_doc_footer();
388 static int fetch_word(void) {
389 static int col = 0;
390 char *p;
391 int ch, nl;
393 nl = 0;
394 *(p = whitespace) = 0;
396 if(!catmode && !no_fill)
397 p++;
399 while((ch = fgetc(ifd)) != EOF && isspace(ch)) {
400 if(nl && no_fill && ch != '.' && ch != '\n')
401 break;
402 if(nl && !catmode && ch == '\n') {
403 *whitespace = 0;
404 strcpy(word, ".sp");
405 ungetc(ch, ifd);
406 return 1;
408 nl = (ch == '\n' || ch == '\r');
409 if(nl)
410 col = 0;
411 else
412 col++;
414 if(no_fill && nl && *whitespace) {
415 *word = 0;
416 ungetc(ch, ifd);
417 return 0;
420 if(p < whitespace + sizeof(whitespace) - 1 && (!nl || catmode))
421 *p++ = ch;
423 if(ch == '\t' && !catmode) {
424 p[-1] = ' ';
425 while(col % input_tab) {
426 if(p < whitespace + sizeof(whitespace) - 1)
427 *p++ = ' ';
428 col++;
432 if(!catmode && !no_fill && nl)
433 *(p = whitespace) = 0;
435 *p = 0;
437 if(catmode && ch == '.' && nl)
438 catmode = 0;
440 *(p = word) = 0;
441 if(ch == EOF || p > word + sizeof(word) / 2) {
442 if(p != word) {
443 ungetc(ch, ifd);
444 *p = 0;
445 return nl;
447 return -1;
449 ungetc(ch, ifd);
451 while((ch = fgetc(ifd)) != EOF && !isspace(ch)) {
452 if(p < word + sizeof(word) - 1)
453 *p++ = ch;
454 col++;
455 if(ch == '\\') {
456 if((ch = fgetc(ifd)) == EOF)
457 break;
458 // if( ch == ' ' ) ch = ' ' + 0x80; /* XXX Is this line needed? */
459 if(p < word + sizeof(word) - 1)
460 *p++ = ch;
461 col++;
464 *p = 0;
465 ungetc(ch, ifd);
467 return (nl != 0);
470 /****************************************************************************
471 * Accepted nroff commands and executors.
474 enum cmd_class {
475 CCLASS_NONE = 0,
476 CCLASS_PARAMETERED,
477 CCLASS_FONTCHANGER,
478 CCLASS_SO,
481 static const struct cmd_list_s {
482 char cmd[3];
483 char class; /* enum cmd_class */
484 char id;
485 } cmd_list[] = {
486 {"\\\"", CCLASS_NONE, 0},
487 {"nh", CCLASS_NONE, 0}, /* This program never inserts hyphens */
488 {"hy", CCLASS_NONE, 0}, /* This program never inserts hyphens */
489 {"PD", CCLASS_NONE, 0}, /* Inter-para distance is 1 line */
490 {"DT", CCLASS_NONE, 0}, /* Default tabs, they can't be non-default! */
491 {"IX", CCLASS_NONE, 0}, /* Indexing for some weird package */
492 {"Id", CCLASS_NONE, 0}, /* Line for RCS tokens */
493 {"BY", CCLASS_NONE, 0}, /* I wonder where this should go ? */
494 {"nf", CCLASS_NONE, 1}, /* Line break, Turn line fill off */
495 {"fi", CCLASS_NONE, 2}, /* Line break, Turn line fill on */
496 {"sp", CCLASS_NONE, 3}, /* Line break, line space (arg for Nr lines) */
497 {"br", CCLASS_NONE, 4}, /* Line break */
498 {"bp", CCLASS_NONE, 5}, /* Page break */
499 {"PP", CCLASS_NONE, 6},
500 {"LP", CCLASS_NONE, 6},
501 {"P", CCLASS_NONE, 6}, /* Paragraph */
502 {"RS", CCLASS_NONE, 7}, /* New Para + Indent start */
503 {"RE", CCLASS_NONE, 8}, /* New Para + Indent end */
504 {"HP", CCLASS_NONE, 9}, /* Begin hanging indent (TP without arg?) */
505 {"ad", CCLASS_NONE, 10}, /* Line up right margin */
506 {"na", CCLASS_NONE, 11}, /* Leave right margin unaligned */
507 {"ta", CCLASS_NONE, 12}, /* Changes _input_ tab spacing, right? */
508 {"TH", CCLASS_PARAMETERED, 1},/* Title and headers */
509 {"SH", CCLASS_PARAMETERED, 2},/* Section */
510 {"SS", CCLASS_PARAMETERED, 3},/* Subsection */
511 {"IP", CCLASS_PARAMETERED, 4},/* New para, indent except argument 1 */
512 {"TP", CCLASS_PARAMETERED, 5},/* New para, indent except line 1 */
513 {"B", CCLASS_FONTCHANGER, 22},/* Various font fiddles */
514 {"BI", CCLASS_FONTCHANGER, 23},
515 {"BR", CCLASS_FONTCHANGER, 21},
516 {"I", CCLASS_FONTCHANGER, 33},
517 {"IB", CCLASS_FONTCHANGER, 32},
518 {"IR", CCLASS_FONTCHANGER, 31},
519 {"RB", CCLASS_FONTCHANGER, 12},
520 {"RI", CCLASS_FONTCHANGER, 13},
521 {"SB", CCLASS_FONTCHANGER, 42},
522 {"SM", CCLASS_FONTCHANGER, 44},
523 {"C", CCLASS_FONTCHANGER, 22},/* PH-UX manual pages! */
524 {"CI", CCLASS_FONTCHANGER, 23},
525 {"CR", CCLASS_FONTCHANGER, 21},
526 {"IC", CCLASS_FONTCHANGER, 32},
527 {"RC", CCLASS_FONTCHANGER, 12},
528 {"so", CCLASS_SO, 0},
529 {"\0\0", CCLASS_NONE, 0}
532 static int do_command(void) {
533 char *cmd;
534 int i;
535 char lbuf[10];
537 cmd = word + 1;
539 /* Comments don't need the space */
540 if(strncmp(cmd, "\\\"", 2) == 0)
541 cmd = "\\\"";
543 for(i = 0; cmd_list[i].cmd[0]; i++) {
544 if(strcmp(cmd_list[i].cmd, cmd) == 0)
545 break;
548 if(cmd_list[i].cmd[0] == 0) {
549 if(verbose) {
550 strncpy(lbuf, cmd, 3);
551 lbuf[3] = 0;
552 line_break();
553 i = left_indent;
554 left_indent = 0;
555 snprintf(word, sizeof word, "**** Unknown formatter command: .%s", lbuf);
556 print_word(word);
557 line_break();
558 left_indent = i;
561 i = 0; /* Treat as comment */
564 switch (cmd_list[i].class) {
565 case CCLASS_PARAMETERED: /* Parametered commands */
566 return do_argvcmd(cmd_list[i].id);
568 case CCLASS_FONTCHANGER: /* Font changers */
569 return do_fontwords(cmd_list[i].id / 10, cmd_list[i].id % 10, 0);
571 case CCLASS_SO: /* .so */
572 fetch_word();
573 if(strlen(man_file) + 4 < sizeof man_file)
574 strcat(man_file, word);
575 close_page();
576 if(find_page(man_file, (char *) 0) < 0) {
577 fprintf(stderr, "Cannot open .so file %s\n", word);
578 return -1;
580 ungetc('\r', ifd);
581 break;
583 case CCLASS_NONE:
584 default:
585 do_skipeol();
586 if(cmd_list[i].id)
587 return do_noargs(cmd_list[i].id);
589 return 0;
592 static void do_skipeol(void) {
593 int ch;
594 char *p = word;
596 while((ch = fgetc(ifd)) != EOF && ch != '\n')
597 if(p < word + sizeof(word) - 1)
598 *p++ = ch;;
599 *p = 0;
600 ungetc(ch, ifd);
603 static void flush_word(char **p) {
604 memcpy(*p, "\\fR", 4);
605 print_word(word);
606 *p = word;
609 static void insert_font(char **p, int font) {
610 const char ftab[] = " RBIS";
611 memcpy(*p, "\\f", 2);
612 (*p)[2] = ftab[font];
613 *p += 3;
616 static int do_fontwords(int this_font, int other_font, int early_exit) {
617 char *p = word;
618 int ch;
619 int in_quote = 0;
621 no_nl = 0; /* Line is effectivly been reprocessed so NL is visible */
622 for(;;) {
623 if(p == word) insert_font(&p, this_font);
624 /* at each turn, at most 5 bytes are appended to word
625 * in order to flush the buffer, 4 more bytes are required to stay free */
626 if(p+5+4 >= word+sizeof word) {
627 assert(p+4<word+sizeof word);
628 flush_word(&p);
629 continue;
631 if((ch = fgetc(ifd)) == EOF || ch == '\n')
632 break;
633 if(ch == '"') {
634 in_quote = !in_quote;
635 continue;
637 if(in_quote || !isspace(ch)) {
638 if(isspace(ch) && p > word + 3) {
639 flush_word(&p);
640 if(no_fill) print_word(" ");
641 continue;
643 *p++ = ch;
644 if(ch == '\\') {
645 if((ch = fgetc(ifd)) == EOF || ch == '\n') break;
646 *p++ = ch;
648 continue;
651 if(p != word + 3) {
652 if(early_exit) break;
654 if(this_font == other_font) flush_word(&p);
655 int i = this_font;
656 this_font = other_font;
657 other_font = i;
658 insert_font(&p, this_font);
661 ungetc(ch, ifd);
663 if(p > word + 3) flush_word(&p);
665 return 0;
668 static int do_noargs(int cmd_id) {
669 if(cmd_id < 10)
670 line_break();
671 switch (cmd_id) {
672 case 1:
673 no_fill = 1;
674 break;
675 case 2:
676 no_fill = 0;
677 break;
678 case 3:
679 pending_nl = 1;
680 break;
681 case 4:
682 break;
683 case 5:
684 page_break();
685 break;
686 case 6:
687 left_indent = old_para_indent;
688 pending_nl = 1;
689 break;
690 case 7:
691 pending_nl = 1;
692 left_indent += standard_tab;
693 old_para_indent += standard_tab;
694 break;
695 case 8:
696 pending_nl = 1;
697 left_indent -= standard_tab;
698 old_para_indent -= standard_tab;
699 break;
701 case 10:
702 right_adjust = 1;
703 break;
704 case 11:
705 right_adjust = 0;
706 break;
707 case 12:
708 input_tab = atoi(word);
709 if(input_tab <= 0)
710 input_tab = 8;
711 break;
713 return 0;
716 static int do_argvcmd(int cmd_id) {
717 int ch;
719 line_break();
720 while((ch = fgetc(ifd)) != EOF && (ch == ' ' || ch == '\t')) ;
721 ungetc(ch, ifd);
723 switch (cmd_id + 10 * (ch == '\n')) {
724 case 1: /* Title and headers */
725 page_break();
726 left_indent = old_para_indent = standard_tab;
727 build_headers();
728 break;
730 case 2: /* Section */
731 left_indent = 0;
732 next_line_indent = old_para_indent = standard_tab;
733 no_nl = 0;
734 keep_nl = 1;
735 pending_nl = 1;
737 do_fontwords(2, 2, 0);
738 return 0;
739 case 3: /* Subsection */
740 left_indent = standard_tab / 2;
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 break;
749 case 15:
750 case 5: /* New para, indent except line 1 */
751 do_skipeol();
752 next_line_indent = old_para_indent + standard_tab;
753 left_indent = old_para_indent;
754 pending_nl = 1;
755 keep_nl = 1;
756 optional_keep = 1;
757 break;
759 case 4: /* New para, indent except argument 1 */
760 next_line_indent = old_para_indent + standard_tab;
761 left_indent = old_para_indent;
762 pending_nl = 1;
763 keep_nl = 1;
764 optional_keep = 1;
765 do_fontwords(1, 1, 1);
766 do_skipeol();
767 break;
769 case 14:
770 pending_nl = 1;
771 left_indent = old_para_indent + standard_tab;
772 break;
775 return 0;
778 static void build_headers(void) {
779 char buffer[5][80];
780 int strno = 0;
781 unsigned stroff = 0;
782 int last_ch = 0, ch, in_quote = 0;
784 for(ch = 0; ch < 5; ch++)
785 buffer[ch][0] = 0;
787 for(;;) {
788 if((ch = fgetc(ifd)) == EOF || ch == '\n')
789 break;
790 if(ch == '"') {
791 if(last_ch == '\\') {
792 assert(stroff > 0);
793 stroff--;
794 break;
796 in_quote = !in_quote;
797 continue;
799 last_ch = ch;
800 if(in_quote || !isspace(ch)) {
801 /* Nb, this does nothing about backslashes, perhaps it should */
802 if(stroff < sizeof(buffer[strno]) - 1)
803 buffer[strno][stroff++] = ch;
804 continue;
806 buffer[strno][stroff] = 0;
808 if(buffer[strno][0]) {
809 strno++;
810 stroff = 0;
811 if(strno == 5)
812 break;
815 if(strno < 5)
816 buffer[strno][stroff] = 0;
817 ungetc(ch, ifd);
819 /* Ok we should have upto 5 arguments build the header and footer */
821 size_t l0 = strlen(buffer[0]),
822 l1 = strlen(buffer[1]),
823 l2 = strlen(buffer[2]),
824 l3 = strlen(buffer[3]),
825 l4 = strlen(buffer[4]),
826 l01 = l0 + l1 + 2;
827 snprintf(line_header, sizeof line_header, "%s(%s)%*s%*s(%s)", buffer[0],
828 buffer[1], (int) (right_margin/2-l01+l4/2+(l4&1)), buffer[4],
829 (int) (right_margin/2-l4/2-l01+l0-(l4&1)), buffer[0], buffer[1]);
830 snprintf(doc_footer, sizeof doc_footer, "%s%*s%*s(%s)", buffer[3],
831 (int) (right_margin/2-l3+l2/2+(l2&1)), buffer[2],
832 (int) (right_margin/2-l2/2-l01+l0-(l2&1)), buffer[0], buffer[1]);
834 do_skipeol();
837 static void print_word(char *pword) {
838 /* Eat \& \a .. \z and \A .. \Z
839 * \fX Switch to font X (R B I S etc)
840 * \(XX Special character XX
841 * \X Print as X
843 #define checkw(X) assert(d+X<wword+(sizeof wword/sizeof wword[0]))
844 #define checkl(X) assert(line_ptr+X<line+(sizeof line/sizeof line[0]))
845 char *s;
846 int *d, ch = 0;
847 int length = 0;
848 int wword[256];
849 int sp_font = cur_font;
851 /* Eat and translate characters. */
852 for(s = pword, d = wword; *s; s++) {
853 ch = 0;
854 if(*s == '\n')
855 continue;
856 if(*s != '\\') {
857 checkw(1);
858 *d++ = (ch = *s) + cur_font;
859 length++;
860 } else {
861 if(s[1] == 0)
862 break;
863 s++;
864 if(*s == 'f') {
865 if(!is_tty) s++;
866 else if(s[1]) {
867 static char fnt[] = " RBI";
868 char *p = strchr(fnt, *++s);
869 if(p == 0)
870 cur_font = 0x100;
871 else
872 cur_font = 0x100 * (p - fnt);
874 continue;
875 } else if(*s == 's') {
876 /* Font size adjusts - strip */
877 while(s[1] && strchr("+-0123456789", s[1]))
878 s++;
879 continue;
880 } else if(isalpha(*s) || strchr("!&^[]|~", *s))
881 continue;
882 else if(*s == '(' || *s == '*') {
883 /* XXX Humm character xlate - http://mdocml.bsd.lv/man/mandoc_char.7.html */
884 int out = '*';
885 if(*s == '*') {
886 if(s[1])
887 ++s;
888 } else if(s[1] == 'm' && s[2] == 'i') {
889 out = '-';
890 s+=2;
891 goto K;
893 if(s[1])
894 ++s;
895 if(s[1])
896 ++s;
898 checkw(1);
899 *d++ = out + cur_font;
900 length++;
901 continue;
904 checkw(1);
905 *d++ = *s + cur_font;
906 length++;
910 checkw(1);
911 *d = 0;
912 #ifdef SPLATTER
914 int *x;
915 fprintf(ofd, ">WORD:");
916 for(x = wword; *x; x++)
917 fputc(*x, ofd);
918 fprintf(ofd, ":\n");
920 #endif
922 if(*wword == 0)
923 return;
925 if(line_ptr)
926 if(line_ptr + ((line_ptr[-1] & 0xFF) == '.') - line + length >= right_margin - left_indent) {
927 right_adjust = -right_adjust;
928 line_break();
931 if(line_ptr == 0)
932 line_ptr = line;
933 else {
934 assert(line_ptr > line);
935 if(!no_fill && (line_ptr[-1] & 0xFF) > ' ') {
936 if((line_ptr[-1] & 0xFF) == '.') {
937 checkl(1);
938 *line_ptr++ = cur_font + ' ';
940 checkl(1);
941 *line_ptr++ = sp_font;
942 gaps_on_line++;
946 checkl(length);
947 memcpy(line_ptr, wword, length * sizeof(int));
948 line_ptr += length;
949 #undef checkw
950 #undef checkl
953 static void line_break(void) {
954 int *d, ch;
955 int spg = 1, rspg = 1, spgs = 0, gap = 0;
957 if(line_ptr == 0)
958 return;
960 if(current_line == 0)
961 print_header();
963 if(current_line)
964 current_line += 1 + pending_nl;
965 for(; pending_nl > 0; pending_nl--)
966 fprintf(ofd, "\n");
968 if(right_adjust < 0) {
969 int over = right_margin - left_indent - (line_ptr - line);
970 #ifdef SPLATTER
971 fprintf(ofd, ">Gaps=%d, Over=%d, ", gaps_on_line, over);
972 #endif
973 if(gaps_on_line && over) {
974 spg = rspg = 1 + over / gaps_on_line;
975 over = over % gaps_on_line;
976 if(over) {
977 if(current_line % 2) {
978 spgs = over;
979 spg++;
980 } else {
981 spgs = gaps_on_line - over;
982 rspg++;
986 #ifdef SPLATTER
987 fprintf(ofd, " (%d,%d) sw=%d\n", spg, rspg, spgs);
988 #endif
989 right_adjust = 1;
992 *line_ptr = 0;
993 if(*line)
994 for(ch = left_indent; ch > 0; ch--)
995 fputc(' ', ofd);
997 for(d = line; *d; d++) {
998 ch = *d;
999 if((ch & 0xFF) == 0) {
1000 int i;
1001 if(gap++ < spgs)
1002 i = spg;
1003 else
1004 i = rspg;
1005 for(; i > 0; i--)
1006 fputc(' ', ofd);
1007 } else
1008 switch (ch >> 8) {
1009 case 2:
1010 fputc(ch & 0xFF, ofd);
1011 fputc('\b', ofd);
1012 fputc(ch & 0xFF, ofd);
1013 break;
1014 case 3:
1015 fputc('_', ofd);
1016 fputc('\b', ofd);
1017 fputc(ch & 0xFF, ofd);
1018 break;
1019 default:
1020 fputc(ch & 0xFF, ofd);
1021 break;
1024 fputc('\n', ofd);
1026 line_ptr = 0;
1028 if(next_line_indent > 0)
1029 left_indent = next_line_indent;
1030 next_line_indent = -1;
1031 gaps_on_line = 0;
1034 static void page_break(void) {
1035 line_break();
1038 static void print_header(void) {
1039 pending_nl = 0;
1040 if(*line_header) {
1041 current_line = 1;
1042 fprintf(ofd, "%s\n\n", line_header);
1046 static void print_doc_footer(void) {
1047 line_break();
1048 int i;
1049 for(i = 0; i < 3; i++) fputc('\n', ofd);
1050 fprintf(ofd, "%s", doc_footer);