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