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