The Emacs and Jed info readers don't like my index format: Info menu
[sgt/halibut] / bk_info.c
1 /*
2 * info backend for Halibut
3 *
4 * Possible future work:
5 *
6 * - configurable indentation, bullets, emphasis, quotes etc?
7 *
8 * - configurable choice of how to allocate node names?
9 * + possibly a template-like approach, choosing node names to
10 * be the full section title or perhaps the internal keyword?
11 * + neither of those seems quite right. Perhaps instead a
12 * Windows Help-like mechanism, where a magic config
13 * directive allows user choice of name for every node.
14 * + Only trouble with that is, now what happens to the section
15 * numbers? Do they become completely vestigial and just sit
16 * in the title text of each node? Or do we keep them in the
17 * menus somehow? I think people might occasionally want to
18 * go to a section by number, if only because all the _other_
19 * formats of the same document will reference the numbers
20 * all the time. So our menu lines could look like one of
21 * these:
22 * * Nodename: Section 1.2. Title of section.
23 * * Section 1.2: Nodename. Title of section.
24 *
25 * - might be helpful to diagnose duplicate node names!
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <assert.h>
31 #include "halibut.h"
32
33 typedef struct {
34 char *filename;
35 int maxfilesize;
36 } infoconfig;
37
38 typedef struct node_tag node;
39 struct node_tag {
40 node *listnext;
41 node *up, *prev, *next, *lastchild;
42 int pos, started_menu, filenum;
43 char *name;
44 rdstringc text;
45 };
46
47 typedef struct {
48 char *text;
49 int nnodes, nodesize;
50 node **nodes;
51 } info_idx;
52
53 static int info_convert(wchar_t *, char **);
54
55 static void info_heading(rdstringc *, word *, word *, int);
56 static void info_rule(rdstringc *, int, int);
57 static void info_para(rdstringc *, word *, char *, word *, keywordlist *,
58 int, int, int);
59 static void info_codepara(rdstringc *, word *, int, int);
60 static void info_versionid(rdstringc *, word *);
61 static void info_menu_item(rdstringc *, node *, paragraph *);
62 static word *info_transform_wordlist(word *, keywordlist *);
63 static int info_check_index(word *, node *, indexdata *);
64
65 static void info_rdaddwc(rdstringc *, word *, word *, int);
66
67 static node *info_node_new(char *name);
68 static char *info_node_name(paragraph *p);
69
70 static infoconfig info_configure(paragraph *source) {
71 infoconfig ret;
72
73 /*
74 * Defaults.
75 */
76 ret.filename = dupstr("output.info");
77 ret.maxfilesize = 64 << 10;
78
79 for (; source; source = source->next) {
80 if (source->type == para_Config) {
81 if (!ustricmp(source->keyword, L"info-filename")) {
82 sfree(ret.filename);
83 ret.filename = utoa_dup(uadv(source->keyword));
84 } else if (!ustricmp(source->keyword, L"info-max-file-size")) {
85 ret.maxfilesize = utoi(uadv(source->keyword));
86 }
87 }
88 }
89
90 return ret;
91 }
92
93 paragraph *info_config_filename(char *filename)
94 {
95 paragraph *p;
96 wchar_t *ufilename, *up;
97 int len;
98
99 p = mknew(paragraph);
100 memset(p, 0, sizeof(*p));
101 p->type = para_Config;
102 p->next = NULL;
103 p->fpos.filename = "<command line>";
104 p->fpos.line = p->fpos.col = -1;
105
106 ufilename = ufroma_dup(filename);
107 len = ustrlen(ufilename) + 2 + lenof(L"info-filename");
108 p->keyword = mknewa(wchar_t, len);
109 up = p->keyword;
110 ustrcpy(up, L"info-filename");
111 up = uadv(up);
112 ustrcpy(up, ufilename);
113 up = uadv(up);
114 *up = L'\0';
115 assert(up - p->keyword < len);
116 sfree(ufilename);
117
118 return p;
119 }
120
121 void info_backend(paragraph *sourceform, keywordlist *keywords,
122 indexdata *idx) {
123 paragraph *p;
124 infoconfig conf;
125 word *prefix, *body, *wp;
126 word spaceword;
127 char *prefixextra;
128 int nesting, nestindent;
129 int indentb, indenta;
130 int filepos;
131 int has_index;
132 rdstringc intro_text = { 0, 0, NULL };
133 node *topnode, *currnode;
134 word bullet;
135 FILE *fp;
136
137 /*
138 * FIXME
139 */
140 int width = 70, listindentbefore = 1, listindentafter = 3;
141 int indent_code = 2, index_width = 40;
142
143 IGNORE(keywords); /* we don't happen to need this */
144 IGNORE(idx); /* or this */
145
146 conf = info_configure(sourceform);
147
148 /*
149 * Go through and create a node for each section.
150 */
151 topnode = info_node_new("Top");
152 currnode = topnode;
153 for (p = sourceform; p; p = p->next) switch (p->type) {
154 /*
155 * Chapter titles.
156 */
157 case para_Chapter:
158 case para_Appendix:
159 case para_UnnumberedChapter:
160 case para_Heading:
161 case para_Subsect:
162 {
163 node *newnode, *upnode;
164 char *nodename;
165
166 nodename = info_node_name(p);
167 newnode = info_node_new(nodename);
168 sfree(nodename);
169
170 p->private_data = newnode;
171
172 if (p->parent)
173 upnode = (node *)p->parent->private_data;
174 else
175 upnode = topnode;
176 assert(upnode);
177 newnode->up = upnode;
178
179 currnode->next = newnode;
180 newnode->prev = currnode;
181
182 currnode->listnext = newnode;
183 currnode = newnode;
184 }
185 break;
186 }
187
188 /*
189 * Set up the display form of each index entry.
190 */
191 {
192 int i;
193 indexentry *entry;
194
195 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
196 info_idx *ii = mknew(info_idx);
197 rdstringc rs = { 0, 0, NULL };
198
199 ii->nnodes = ii->nodesize = 0;
200 ii->nodes = NULL;
201
202 info_rdaddwc(&rs, entry->text, NULL, FALSE);
203
204 ii->text = rs.text;
205
206 entry->backend_data = ii;
207 }
208 }
209
210 /*
211 * An Info file begins with a piece of introductory text which
212 * is apparently never shown anywhere. This seems to me to be a
213 * good place to put the copyright notice and the version IDs.
214 * Also, Info directory entries are expected to go here.
215 */
216
217 rdaddsc(&intro_text,
218 "This Info file generated by Halibut, ");
219 rdaddsc(&intro_text, version);
220 rdaddsc(&intro_text, "\n\n");
221
222 for (p = sourceform; p; p = p->next)
223 if (p->type == para_Config &&
224 !ustricmp(p->keyword, L"info-dir-entry")) {
225 wchar_t *section, *shortname, *longname, *kw;
226 char *s;
227
228 section = uadv(p->keyword);
229 shortname = *section ? uadv(section) : NULL;
230 longname = *shortname ? uadv(shortname) : NULL;
231 kw = *longname ? uadv(longname) : NULL;
232
233 if (!*longname) {
234 error(err_infodirentry, &p->fpos);
235 continue;
236 }
237
238 rdaddsc(&intro_text, "INFO-DIR-SECTION ");
239 s = utoa_dup(section);
240 rdaddsc(&intro_text, s);
241 sfree(s);
242 rdaddsc(&intro_text, "\nSTART-INFO-DIR-ENTRY\n* ");
243 s = utoa_dup(shortname);
244 rdaddsc(&intro_text, s);
245 sfree(s);
246 rdaddsc(&intro_text, ": (");
247 s = dupstr(conf.filename);
248 if (strlen(s) > 5 && !strcmp(s+strlen(s)-5, ".info"))
249 s[strlen(s)-5] = '\0';
250 rdaddsc(&intro_text, s);
251 sfree(s);
252 rdaddsc(&intro_text, ")");
253 if (*kw) {
254 keyword *kwl = kw_lookup(keywords, kw);
255 if (kwl && kwl->para->private_data) {
256 node *n = (node *)kwl->para->private_data;
257 rdaddsc(&intro_text, n->name);
258 }
259 }
260 rdaddsc(&intro_text, ". ");
261 s = utoa_dup(longname);
262 rdaddsc(&intro_text, s);
263 sfree(s);
264 rdaddsc(&intro_text, "\nEND-INFO-DIR-ENTRY\n\n");
265 }
266
267 for (p = sourceform; p; p = p->next)
268 if (p->type == para_Copyright)
269 info_para(&intro_text, NULL, NULL, p->words, keywords,
270 0, 0, width);
271
272 for (p = sourceform; p; p = p->next)
273 if (p->type == para_VersionID)
274 info_versionid(&intro_text, p->words);
275
276 if (intro_text.text[intro_text.pos-1] != '\n')
277 rdaddc(&intro_text, '\n');
278
279 /* Do the title */
280 for (p = sourceform; p; p = p->next)
281 if (p->type == para_Title)
282 info_heading(&topnode->text, NULL, p->words, width);
283
284 nestindent = listindentbefore + listindentafter;
285 nesting = 0;
286
287 currnode = topnode;
288
289 /* Do the main document */
290 for (p = sourceform; p; p = p->next) switch (p->type) {
291
292 case para_QuotePush:
293 nesting += 2;
294 break;
295 case para_QuotePop:
296 nesting -= 2;
297 assert(nesting >= 0);
298 break;
299
300 case para_LcontPush:
301 nesting += nestindent;
302 break;
303 case para_LcontPop:
304 nesting -= nestindent;
305 assert(nesting >= 0);
306 break;
307
308 /*
309 * Things we ignore because we've already processed them or
310 * aren't going to touch them in this pass.
311 */
312 case para_IM:
313 case para_BR:
314 case para_Biblio: /* only touch BiblioCited */
315 case para_VersionID:
316 case para_NoCite:
317 case para_Title:
318 break;
319
320 /*
321 * Chapter titles.
322 */
323 case para_Chapter:
324 case para_Appendix:
325 case para_UnnumberedChapter:
326 case para_Heading:
327 case para_Subsect:
328 currnode = p->private_data;
329 assert(currnode);
330 assert(currnode->up);
331
332 if (!currnode->up->started_menu) {
333 rdaddsc(&currnode->up->text, "* Menu:\n\n");
334 currnode->up->started_menu = TRUE;
335 }
336 info_menu_item(&currnode->up->text, currnode, p);
337
338 has_index |= info_check_index(p->words, currnode, idx);
339 info_heading(&currnode->text, p->kwtext, p->words, width);
340 nesting = 0;
341 break;
342
343 case para_Rule:
344 info_rule(&currnode->text, nesting, width - nesting);
345 break;
346
347 case para_Normal:
348 case para_Copyright:
349 case para_DescribedThing:
350 case para_Description:
351 case para_BiblioCited:
352 case para_Bullet:
353 case para_NumberedList:
354 has_index |= info_check_index(p->words, currnode, idx);
355 if (p->type == para_Bullet) {
356 bullet.next = NULL;
357 bullet.alt = NULL;
358 bullet.type = word_Normal;
359 bullet.text = L"-"; /* FIXME: configurability */
360 prefix = &bullet;
361 prefixextra = NULL;
362 indentb = listindentbefore;
363 indenta = listindentafter;
364 } else if (p->type == para_NumberedList) {
365 prefix = p->kwtext;
366 prefixextra = "."; /* FIXME: configurability */
367 indentb = listindentbefore;
368 indenta = listindentafter;
369 } else if (p->type == para_Description) {
370 prefix = NULL;
371 prefixextra = NULL;
372 indentb = listindentbefore;
373 indenta = listindentafter;
374 } else {
375 prefix = NULL;
376 prefixextra = NULL;
377 indentb = indenta = 0;
378 }
379 if (p->type == para_BiblioCited) {
380 body = dup_word_list(p->kwtext);
381 for (wp = body; wp->next; wp = wp->next);
382 wp->next = &spaceword;
383 spaceword.next = p->words;
384 spaceword.alt = NULL;
385 spaceword.type = word_WhiteSpace;
386 spaceword.text = NULL;
387 } else {
388 wp = NULL;
389 body = p->words;
390 }
391 info_para(&currnode->text, prefix, prefixextra, body, keywords,
392 nesting + indentb, indenta,
393 width - nesting - indentb - indenta);
394 if (wp) {
395 wp->next = NULL;
396 free_word_list(body);
397 }
398 break;
399
400 case para_Code:
401 info_codepara(&currnode->text, p->words,
402 nesting + indent_code,
403 width - nesting - 2 * indent_code);
404 break;
405 }
406
407 /*
408 * Create an index node if required.
409 */
410 if (has_index) {
411 node *newnode;
412 int i, j, k;
413 indexentry *entry;
414
415 newnode = info_node_new("Index");
416 newnode->up = topnode;
417
418 currnode->next = newnode;
419 newnode->prev = currnode;
420 currnode->listnext = newnode;
421
422 rdaddsc(&newnode->text, "Index\n-----\n\n");
423
424 info_menu_item(&topnode->text, newnode, NULL);
425
426 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
427 info_idx *ii = (info_idx *)entry->backend_data;
428
429 for (j = 0; j < ii->nnodes; j++) {
430 int pos0 = newnode->text.pos;
431 /*
432 * When we have multiple references for a single
433 * index term, we only display the actual term on
434 * the first line, to make it clear that the terms
435 * really are the same.
436 */
437 if (j == 0)
438 rdaddsc(&newnode->text, ii->text);
439 for (k = newnode->text.pos - pos0; k < index_width; k++)
440 rdaddc(&newnode->text, ' ');
441 rdaddsc(&newnode->text, " *Note ");
442 rdaddsc(&newnode->text, ii->nodes[j]->name);
443 rdaddsc(&newnode->text, "::\n");
444 }
445 }
446 }
447
448 /*
449 * Finalise the text of each node, by adding the ^_ delimiter
450 * and the node line at the top.
451 */
452 for (currnode = topnode; currnode; currnode = currnode->listnext) {
453 char *origtext = currnode->text.text;
454 currnode->text.text = NULL;
455 currnode->text.pos = currnode->text.size = 0;
456 rdaddsc(&currnode->text, "\037\nFile: ");
457 rdaddsc(&currnode->text, conf.filename);
458 rdaddsc(&currnode->text, ", Node: ");
459 rdaddsc(&currnode->text, currnode->name);
460 if (currnode->prev) {
461 rdaddsc(&currnode->text, ", Prev: ");
462 rdaddsc(&currnode->text, currnode->prev->name);
463 }
464 rdaddsc(&currnode->text, ", Up: ");
465 rdaddsc(&currnode->text, (currnode->up ?
466 currnode->up->name : "(dir)"));
467 if (currnode->next) {
468 rdaddsc(&currnode->text, ", Next: ");
469 rdaddsc(&currnode->text, currnode->next->name);
470 }
471 rdaddsc(&currnode->text, "\n\n");
472 rdaddsc(&currnode->text, origtext);
473 /*
474 * Just make _absolutely_ sure we end with a newline.
475 */
476 if (currnode->text.text[currnode->text.pos-1] != '\n')
477 rdaddc(&currnode->text, '\n');
478
479 sfree(origtext);
480 }
481
482 /*
483 * Compute the offsets for the tag table.
484 */
485 filepos = intro_text.pos;
486 for (currnode = topnode; currnode; currnode = currnode->listnext) {
487 currnode->pos = filepos;
488 filepos += currnode->text.pos;
489 }
490
491 /*
492 * Split into sub-files.
493 */
494 if (conf.maxfilesize > 0) {
495 int currfilesize = intro_text.pos, currfilenum = 1;
496 for (currnode = topnode; currnode; currnode = currnode->listnext) {
497 if (currfilesize > intro_text.pos &&
498 currfilesize + currnode->text.pos > conf.maxfilesize) {
499 currfilenum++;
500 currfilesize = intro_text.pos;
501 }
502 currnode->filenum = currfilenum;
503 currfilesize += currnode->text.pos;
504 }
505 }
506
507 /*
508 * Write the primary output file.
509 */
510 fp = fopen(conf.filename, "w");
511 if (!fp) {
512 error(err_cantopenw, conf.filename);
513 return;
514 }
515 fputs(intro_text.text, fp);
516 if (conf.maxfilesize == 0) {
517 for (currnode = topnode; currnode; currnode = currnode->listnext)
518 fputs(currnode->text.text, fp);
519 } else {
520 int filenum = 0;
521 fprintf(fp, "\037\nIndirect:\n");
522 for (currnode = topnode; currnode; currnode = currnode->listnext)
523 if (filenum != currnode->filenum) {
524 filenum = currnode->filenum;
525 fprintf(fp, "%s-%d: %d\n", conf.filename, filenum,
526 currnode->pos);
527 }
528 }
529 fprintf(fp, "\037\nTag Table:\n");
530 if (conf.maxfilesize > 0)
531 fprintf(fp, "(Indirect)\n");
532 for (currnode = topnode; currnode; currnode = currnode->listnext)
533 fprintf(fp, "Node: %s\177%d\n", currnode->name, currnode->pos);
534 fprintf(fp, "\037\nEnd Tag Table\n");
535 fclose(fp);
536
537 /*
538 * Write the subfiles.
539 */
540 if (conf.maxfilesize > 0) {
541 int filenum = 0;
542 fp = NULL;
543
544 for (currnode = topnode; currnode; currnode = currnode->listnext) {
545 if (filenum != currnode->filenum) {
546 char *fname;
547
548 filenum = currnode->filenum;
549
550 if (fp)
551 fclose(fp);
552 fname = mknewa(char, strlen(conf.filename) + 40);
553 sprintf(fname, "%s-%d", conf.filename, filenum);
554 fp = fopen(fname, "w");
555 if (!fp) {
556 error(err_cantopenw, fname);
557 return;
558 }
559 sfree(fname);
560 fputs(intro_text.text, fp);
561 }
562 fputs(currnode->text.text, fp);
563 }
564
565 if (fp)
566 fclose(fp);
567 }
568 }
569
570 static int info_check_index(word *w, node *n, indexdata *idx)
571 {
572 int ret = 0;
573
574 for (; w; w = w->next) {
575 if (w->type == word_IndexRef) {
576 indextag *tag;
577 int i;
578
579 tag = index_findtag(idx, w->text);
580 if (!tag)
581 break;
582
583 for (i = 0; i < tag->nrefs; i++) {
584 indexentry *entry = tag->refs[i];
585 info_idx *ii = (info_idx *)entry->backend_data;
586
587 if (ii->nnodes > 0 && ii->nodes[ii->nnodes-1] == n) {
588 /*
589 * If the same index term is indexed twice
590 * within the same section, we only want to
591 * mention it once in the index. So do nothing
592 * here.
593 */
594 continue;
595 }
596
597 if (ii->nnodes >= ii->nodesize) {
598 ii->nodesize += 32;
599 ii->nodes = resize(ii->nodes, ii->nodesize);
600 }
601
602 ii->nodes[ii->nnodes++] = n;
603
604 ret = 1;
605 }
606 }
607 }
608
609 return ret;
610 }
611
612 /*
613 * Convert a wide string into a string of chars. If `result' is
614 * non-NULL, mallocs the resulting string and stores a pointer to
615 * it in `*result'. If `result' is NULL, merely checks whether all
616 * characters in the string are feasible for the output character
617 * set.
618 *
619 * Return is nonzero if all characters are OK. If not all
620 * characters are OK but `result' is non-NULL, a result _will_
621 * still be generated!
622 */
623 static int info_convert(wchar_t *s, char **result) {
624 /*
625 * FIXME. Currently this is ISO8859-1 only.
626 */
627 int doing = (result != 0);
628 int ok = TRUE;
629 char *p = NULL;
630 int plen = 0, psize = 0;
631
632 for (; *s; s++) {
633 wchar_t c = *s;
634 char outc;
635
636 if ((c >= 32 && c <= 126) ||
637 (c >= 160 && c <= 255)) {
638 /* Char is OK. */
639 outc = (char)c;
640 } else {
641 /* Char is not OK. */
642 ok = FALSE;
643 outc = 0xBF; /* approximate the good old DEC `uh?' */
644 }
645 if (doing) {
646 if (plen >= psize) {
647 psize = plen + 256;
648 p = resize(p, psize);
649 }
650 p[plen++] = outc;
651 }
652 }
653 if (doing) {
654 p = resize(p, plen+1);
655 p[plen] = '\0';
656 *result = p;
657 }
658 return ok;
659 }
660
661 static word *info_transform_wordlist(word *words, keywordlist *keywords)
662 {
663 word *ret = dup_word_list(words);
664 word *w;
665 keyword *kwl;
666
667 for (w = ret; w; w = w->next) {
668 w->private_data = NULL;
669 if (w->type == word_UpperXref || w->type == word_LowerXref) {
670 kwl = kw_lookup(keywords, w->text);
671 if (kwl) {
672 if (kwl->para->type == para_NumberedList ||
673 kwl->para->type == para_BiblioCited) {
674 /*
675 * In Info, we do nothing special for xrefs to
676 * numbered list items or bibliography entries.
677 */
678 break;
679 } else {
680 /*
681 * An xref to a different section has its text
682 * completely replaced.
683 */
684 word *w2, *w3, *w4;
685 w2 = w3 = w->next;
686 w4 = NULL;
687 while (w2) {
688 if (w2->type == word_XrefEnd) {
689 w4 = w2->next;
690 w2->next = NULL;
691 break;
692 }
693 w2 = w2->next;
694 }
695 free_word_list(w3);
696
697 /*
698 * Now w is the UpperXref / LowerXref we
699 * started with, and w4 is the next word after
700 * the corresponding XrefEnd (if any). The
701 * simplest thing is just to stick a pointer to
702 * the target node structure in the private
703 * data field of the xref word, and let
704 * info_rdaddwc and friends read the node name
705 * out from there.
706 */
707 w->next = w4;
708 w->private_data = kwl->para->private_data;
709 assert(w->private_data);
710 }
711 }
712 }
713 }
714
715 return ret;
716 }
717
718 static void info_rdaddwc(rdstringc *rs, word *words, word *end, int xrefs) {
719 char *c;
720
721 for (; words && words != end; words = words->next) switch (words->type) {
722 case word_HyperLink:
723 case word_HyperEnd:
724 case word_XrefEnd:
725 case word_IndexRef:
726 break;
727
728 case word_Normal:
729 case word_Emph:
730 case word_Code:
731 case word_WeakCode:
732 case word_WhiteSpace:
733 case word_EmphSpace:
734 case word_CodeSpace:
735 case word_WkCodeSpace:
736 case word_Quote:
737 case word_EmphQuote:
738 case word_CodeQuote:
739 case word_WkCodeQuote:
740 assert(words->type != word_CodeQuote &&
741 words->type != word_WkCodeQuote);
742 if (towordstyle(words->type) == word_Emph &&
743 (attraux(words->aux) == attr_First ||
744 attraux(words->aux) == attr_Only))
745 rdaddc(rs, '_'); /* FIXME: configurability */
746 else if (towordstyle(words->type) == word_Code &&
747 (attraux(words->aux) == attr_First ||
748 attraux(words->aux) == attr_Only))
749 rdaddc(rs, '`'); /* FIXME: configurability */
750 if (removeattr(words->type) == word_Normal) {
751 if (info_convert(words->text, &c))
752 rdaddsc(rs, c);
753 else
754 info_rdaddwc(rs, words->alt, NULL, FALSE);
755 sfree(c);
756 } else if (removeattr(words->type) == word_WhiteSpace) {
757 rdaddc(rs, ' ');
758 } else if (removeattr(words->type) == word_Quote) {
759 rdaddc(rs, quoteaux(words->aux) == quote_Open ? '`' : '\'');
760 /* FIXME: configurability */
761 }
762 if (towordstyle(words->type) == word_Emph &&
763 (attraux(words->aux) == attr_Last ||
764 attraux(words->aux) == attr_Only))
765 rdaddc(rs, '_'); /* FIXME: configurability */
766 else if (towordstyle(words->type) == word_Code &&
767 (attraux(words->aux) == attr_Last ||
768 attraux(words->aux) == attr_Only))
769 rdaddc(rs, '\''); /* FIXME: configurability */
770 break;
771
772 case word_UpperXref:
773 case word_LowerXref:
774 if (xrefs && words->private_data) {
775 rdaddsc(rs, "*Note ");
776 rdaddsc(rs, ((node *)words->private_data)->name);
777 rdaddsc(rs, "::");
778 }
779 break;
780 }
781 }
782
783 static int info_width_internal(word *words, int xrefs);
784
785 static int info_width_internal_list(word *words, int xrefs) {
786 int w = 0;
787 while (words) {
788 w += info_width_internal(words, xrefs);
789 words = words->next;
790 }
791 return w;
792 }
793
794 static int info_width_internal(word *words, int xrefs) {
795 switch (words->type) {
796 case word_HyperLink:
797 case word_HyperEnd:
798 case word_XrefEnd:
799 case word_IndexRef:
800 return 0;
801
802 case word_Normal:
803 case word_Emph:
804 case word_Code:
805 case word_WeakCode:
806 return (((words->type == word_Emph ||
807 words->type == word_Code)
808 ? (attraux(words->aux) == attr_Only ? 2 :
809 attraux(words->aux) == attr_Always ? 0 : 1)
810 : 0) +
811 (info_convert(words->text, NULL) ?
812 ustrlen(words->text) :
813 info_width_internal_list(words->alt, xrefs)));
814
815 case word_WhiteSpace:
816 case word_EmphSpace:
817 case word_CodeSpace:
818 case word_WkCodeSpace:
819 case word_Quote:
820 case word_EmphQuote:
821 case word_CodeQuote:
822 case word_WkCodeQuote:
823 assert(words->type != word_CodeQuote &&
824 words->type != word_WkCodeQuote);
825 return (((towordstyle(words->type) == word_Emph ||
826 towordstyle(words->type) == word_Code)
827 ? (attraux(words->aux) == attr_Only ? 2 :
828 attraux(words->aux) == attr_Always ? 0 : 1)
829 : 0) + 1);
830
831 case word_UpperXref:
832 case word_LowerXref:
833 if (xrefs && words->private_data) {
834 /* "*Note " plus "::" comes to 8 characters */
835 return 8 + strlen(((node *)words->private_data)->name);
836 }
837 break;
838 }
839 return 0; /* should never happen */
840 }
841
842 static int info_width_noxrefs(word *words)
843 {
844 return info_width_internal(words, FALSE);
845 }
846 static int info_width_xrefs(word *words)
847 {
848 return info_width_internal(words, TRUE);
849 }
850
851 static void info_heading(rdstringc *text, word *tprefix,
852 word *words, int width) {
853 rdstringc t = { 0, 0, NULL };
854 int margin, length;
855 int firstlinewidth, wrapwidth;
856 int i;
857 wrappedline *wrapping, *p;
858
859 if (tprefix) {
860 info_rdaddwc(&t, tprefix, NULL, FALSE);
861 rdaddsc(&t, ": "); /* FIXME: configurability */
862 }
863 margin = length = (t.text ? strlen(t.text) : 0);
864
865 margin = 0;
866 firstlinewidth = width - length;
867 wrapwidth = width;
868
869 wrapping = wrap_para(words, firstlinewidth, wrapwidth, info_width_noxrefs);
870 for (p = wrapping; p; p = p->next) {
871 info_rdaddwc(&t, p->begin, p->end, FALSE);
872 length = (t.text ? strlen(t.text) : 0);
873 for (i = 0; i < margin; i++)
874 rdaddc(text, ' ');
875 rdaddsc(text, t.text);
876 rdaddc(text, '\n');
877 for (i = 0; i < margin; i++)
878 rdaddc(text, ' ');
879 while (length--)
880 rdaddc(text, '-');
881 rdaddc(text, '\n');
882 margin = 0;
883 sfree(t.text);
884 t = empty_rdstringc;
885 }
886 wrap_free(wrapping);
887 rdaddc(text, '\n');
888
889 sfree(t.text);
890 }
891
892 static void info_rule(rdstringc *text, int indent, int width) {
893 while (indent--) rdaddc(text, ' ');
894 while (width--) rdaddc(text, '-');
895 rdaddc(text, '\n');
896 rdaddc(text, '\n');
897 }
898
899 static void info_para(rdstringc *text, word *prefix, char *prefixextra,
900 word *input, keywordlist *keywords,
901 int indent, int extraindent, int width) {
902 wrappedline *wrapping, *p;
903 word *words;
904 rdstringc pfx = { 0, 0, NULL };
905 int e;
906 int i;
907 int firstlinewidth = width;
908
909 words = info_transform_wordlist(input, keywords);
910
911 if (prefix) {
912 info_rdaddwc(&pfx, prefix, NULL, FALSE);
913 if (prefixextra)
914 rdaddsc(&pfx, prefixextra);
915 for (i = 0; i < indent; i++)
916 rdaddc(text, ' ');
917 rdaddsc(text, pfx.text);
918 /* If the prefix is too long, shorten the first line to fit. */
919 e = extraindent - strlen(pfx.text);
920 if (e < 0) {
921 firstlinewidth += e; /* this decreases it, since e < 0 */
922 if (firstlinewidth < 0) {
923 e = indent + extraindent;
924 firstlinewidth = width;
925 rdaddc(text, '\n');
926 } else
927 e = 0;
928 }
929 sfree(pfx.text);
930 } else
931 e = indent + extraindent;
932
933 wrapping = wrap_para(words, firstlinewidth, width, info_width_xrefs);
934 for (p = wrapping; p; p = p->next) {
935 for (i = 0; i < e; i++)
936 rdaddc(text, ' ');
937 info_rdaddwc(text, p->begin, p->end, TRUE);
938 rdaddc(text, '\n');
939 e = indent + extraindent;
940 }
941 wrap_free(wrapping);
942 rdaddc(text, '\n');
943
944 free_word_list(words);
945 }
946
947 static void info_codepara(rdstringc *text, word *words,
948 int indent, int width) {
949 int i;
950
951 for (; words; words = words->next) if (words->type == word_WeakCode) {
952 char *c;
953 info_convert(words->text, &c);
954 if (strlen(c) > (size_t)width) {
955 /* FIXME: warn */
956 }
957 for (i = 0; i < indent; i++)
958 rdaddc(text, ' ');
959 rdaddsc(text, c);
960 rdaddc(text, '\n');
961 sfree(c);
962 }
963
964 rdaddc(text, '\n');
965 }
966
967 static void info_versionid(rdstringc *text, word *words) {
968 rdaddc(text, '['); /* FIXME: configurability */
969 info_rdaddwc(text, words, NULL, FALSE);
970 rdaddsc(text, "]\n");
971 }
972
973 static node *info_node_new(char *name)
974 {
975 node *n;
976
977 n = mknew(node);
978 n->text.text = NULL;
979 n->text.pos = n->text.size = 0;
980 n->up = n->next = n->prev = n->lastchild = n->listnext = NULL;
981 n->name = dupstr(name);
982 n->started_menu = FALSE;
983
984 return n;
985 }
986
987 static char *info_node_name(paragraph *par)
988 {
989 rdstringc rsc = { 0, 0, NULL };
990 char *p, *q;
991 info_rdaddwc(&rsc, par->kwtext ? par->kwtext : par->words, NULL, FALSE);
992
993 /*
994 * We cannot have commas or colons in a node name. Remove any
995 * that we find, with a warning.
996 */
997 p = q = rsc.text;
998 while (*p) {
999 if (*p == ':' || *p == ',') {
1000 error(err_infonodechar, &par->fpos, *p);
1001 } else {
1002 *q++ = *p;
1003 }
1004 p++;
1005 }
1006 *p = '\0';
1007
1008 return rsc.text;
1009 }
1010
1011 static void info_menu_item(rdstringc *text, node *n, paragraph *p)
1012 {
1013 /*
1014 * FIXME: Depending on how we're doing node names in this info
1015 * file, we might want to do
1016 *
1017 * * Node name:: Chapter title
1018 *
1019 * _or_
1020 *
1021 * * Chapter number: Node name.
1022 *
1023 *
1024 */
1025 rdaddsc(text, "* ");
1026 rdaddsc(text, n->name);
1027 rdaddsc(text, "::");
1028 if (p) {
1029 rdaddc(text, ' ');
1030 info_rdaddwc(text, p->words, NULL, FALSE);
1031 }
1032 rdaddc(text, '\n');
1033 }