diff options
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | bk_whlp.c | 432 | ||||
| -rw-r--r-- | buttress.h | 2 | ||||
| -rw-r--r-- | main.c | 1 | ||||
| -rw-r--r-- | winhelp.c | 79 |
5 files changed, 487 insertions, 30 deletions
@@ -53,7 +53,8 @@ SRC := ../ MODULES := main malloc ustring error help licence version misc tree234 MODULES += input keywords contents index style biblio -MODULES += bk_text bk_xhtml +MODULES += bk_text bk_xhtml bk_whlp +MODULES += winhelp OBJECTS := $(addsuffix .o,$(MODULES)) DEPS := $(addsuffix .d,$(MODULES)) diff --git a/bk_whlp.c b/bk_whlp.c new file mode 100644 index 0000000..f049585 --- /dev/null +++ b/bk_whlp.c @@ -0,0 +1,432 @@ +/* + * Windows Help backend for Buttress + * + * TODO: + * + * - rules + * - work out whether we can make an xref to a biblio entry jump + * to the topic containing the citation itself? + * - section macros are broken (can't do Up) + * - need menus at the bottom of every non-leaf section. + * - indexing + * - allow user to specify section contexts. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "buttress.h" +#include "winhelp.h" + +struct bk_whlp_state { + WHLP h; + keywordlist *keywords; + paragraph *biblio; +}; + +static void whlp_rdaddwc(rdstringc *rs, word *text); +static int whlp_convert(wchar_t *s, char **result, int hard_spaces); +static void whlp_mkparagraph(struct bk_whlp_state *state, + int font, word *text); + +void whlp_backend(paragraph *sourceform, keywordlist *keywords, index *idx) { + WHLP h; + char *filename; + paragraph *p; + struct bk_whlp_state state; + WHLP_TOPIC contents_topic, curr_topic; + + filename = "output.hlp"; /* FIXME: configurability */ + + h = state.h = whlp_new(); + state.keywords = keywords; + + whlp_start_macro(h, "CB(\"btn_about\",\"&About\",\"About()\")"); + whlp_start_macro(h, "CB(\"btn_up\",\"&Up\",\"Contents()\")"); + whlp_start_macro(h, "BrowseButtons()"); + + /* + * Register topics for everything. + */ + contents_topic = whlp_register_topic(h, "Top", NULL); + whlp_primary_topic(h, contents_topic); + for (p = sourceform; p; p = p->next) { + if (p->type == para_Chapter || + p->type == para_Appendix || + p->type == para_UnnumberedChapter || + p->type == para_Heading || + p->type == para_Subsect) { + p->private_data = whlp_register_topic(h, NULL, NULL); + } + } + + whlp_prepare(h); + + /* ------------------------------------------------------------------ + * Do the contents page, containing title, preamble and + * copyright. + */ + + whlp_begin_topic(h, contents_topic, "Contents", "DB(\"btn_up\")", NULL); + + /* + * The manual title goes in the non-scroll region, and also + * goes into the system title slot. + */ + { + rdstringc rs = {0, 0, NULL}; + for (p = sourceform; p; p = p->next) { + if (p->type == para_Title) { + whlp_begin_para(h, WHLP_PARA_NONSCROLL); + whlp_mkparagraph(&state, WHLP_FONT_TITLE, p->words); + whlp_rdaddwc(&rs, p->words); + whlp_end_para(h); + } + } + if (rs.text) { + whlp_title(h, rs.text); + sfree(rs.text); + } + } + + /* + * Next comes the preamble, which just goes into the ordinary + * scrolling region. + */ + for (p = sourceform; p; p = p->next) { + if (p->type == para_Preamble) { + whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); + whlp_begin_para(h, WHLP_PARA_SCROLL); + whlp_mkparagraph(&state, WHLP_FONT_NORMAL, p->words); + whlp_end_para(h); + } + } + + /* + * The copyright goes to two places, again: into the contents + * page and also into the system section. + */ + { + rdstringc rs = {0, 0, NULL}; + for (p = sourceform; p; p = p->next) { + if (p->type == para_Copyright) { + whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); + whlp_begin_para(h, WHLP_PARA_SCROLL); + whlp_mkparagraph(&state, WHLP_FONT_NORMAL, p->words); + whlp_end_para(h); + whlp_rdaddwc(&rs, p->words); + } + } + if (rs.text) { + whlp_copyright(h, rs.text); + sfree(rs.text); + } + } + + curr_topic = contents_topic; + + /* ------------------------------------------------------------------ + * Now we've done the contents page, we're ready to go through + * and do the main manual text. Ooh. + */ + for (p = sourceform; p; p = p->next) switch (p->type) { + /* + * Things we ignore because we've already processed them or + * aren't going to touch them in this pass. + */ + case para_IM: + case para_BR: + case para_Biblio: /* only touch BiblioCited */ + case para_VersionID: + case para_Copyright: + case para_Preamble: + case para_NoCite: + case para_Title: + break; + + /* + * Chapter and section titles: start a new Help topic. + */ + case para_Chapter: + case para_Appendix: + case para_UnnumberedChapter: + case para_Heading: + case para_Subsect: + { + rdstringc rs = {0, 0, NULL}; + WHLP_TOPIC new_topic; + + new_topic = p->private_data; + whlp_browse_link(h, curr_topic, new_topic); + curr_topic = new_topic; + + if (p->kwtext) { + whlp_rdaddwc(&rs, p->kwtext); + rdaddsc(&rs, ": "); /* FIXME: configurability */ + } + whlp_rdaddwc(&rs, p->words); + /* FIXME: change the macro to point at the parent topic. */ + /* FIXME: check if rs.text is NULL */ + whlp_begin_topic(h, new_topic, rs.text, "DB(\"btn_up\")", NULL); + sfree(rs.text); + + whlp_begin_para(h, WHLP_PARA_NONSCROLL); + if (p->kwtext) { + whlp_mkparagraph(&state, WHLP_FONT_TITLE, p->kwtext); + whlp_set_font(h, WHLP_FONT_TITLE); + whlp_text(h, ": "); /* FIXME: configurability */ + } + whlp_mkparagraph(&state, WHLP_FONT_TITLE, p->words); + whlp_end_para(h); + } + break; + + case para_Rule: + /* FIXME: what do we do about rules? */ + break; + + case para_Normal: + case para_BiblioCited: + case para_Bullet: + case para_NumberedList: + whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); + if (p->type == para_Bullet || p->type == para_NumberedList) { + whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72); + whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36); + whlp_set_tabstop(h, 72, WHLP_ALIGN_LEFT); + whlp_begin_para(h, WHLP_PARA_SCROLL); + if (p->type == para_Bullet) { + whlp_text(h, "\x95"); + } else { + whlp_mkparagraph(&state, WHLP_FONT_NORMAL, p->kwtext); + whlp_text(h, "."); + } + whlp_tab(h); + } else { + whlp_begin_para(h, WHLP_PARA_SCROLL); + } + + if (p->type == para_BiblioCited) { + whlp_mkparagraph(&state, WHLP_FONT_NORMAL, p->kwtext); + whlp_text(h, " "); + } + + whlp_mkparagraph(&state, WHLP_FONT_NORMAL, p->words); + whlp_end_para(h); + break; + + case para_Code: + /* + * In a code paragraph, each individual word is a line. For + * Help files, we will have to output this as a set of + * paragraphs, all but the last of which don't set + * SPACEBELOW. + */ + { + word *w; + char *c; + for (w = p->words; w; w = w->next) { + if (!w->next) + whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12); + whlp_begin_para(h, WHLP_PARA_SCROLL); + whlp_set_font(h, WHLP_FONT_FIXED); + whlp_convert(w->text, &c, FALSE); + whlp_text(h, c); + sfree(c); + whlp_end_para(h); + } + } + break; + } + + whlp_close(h, filename); +} + +static void whlp_mkparagraph(struct bk_whlp_state *state, + int font, word *text) { + keyword *kwl; + int deffont = font; + int currfont = -1; + int newfont; + char *c; + paragraph *xref_target = NULL; + + for (; text; text = text->next) switch (text->type) { + case word_HyperLink: + case word_HyperEnd: + case word_IndexRef: + break; + + case word_UpperXref: + case word_LowerXref: + kwl = kw_lookup(state->keywords, text->text); + assert(xref_target == NULL); + if (kwl->para->type == para_NumberedList) { + break; /* don't xref to numbered list items */ + } else if (kwl->para->type == para_BiblioCited) { + /* Bibliography items: perhaps we should xref them to the + * Bibliography section they're in? Can we even do + * this? FIXME: for the moment we leave them out. */ + break; + } else { + xref_target = kwl->para; + } + whlp_start_hyperlink(state->h, (WHLP_TOPIC)xref_target->private_data); + break; + + case word_XrefEnd: + if (xref_target) + whlp_end_hyperlink(state->h); + xref_target = NULL; + break; + + case word_Normal: + case word_Emph: + case word_Code: + case word_WeakCode: + case word_WhiteSpace: + case word_EmphSpace: + case word_CodeSpace: + case word_WkCodeSpace: + case word_Quote: + case word_EmphQuote: + case word_CodeQuote: + case word_WkCodeQuote: + if (towordstyle(text->type) == word_Emph) + newfont = WHLP_FONT_ITALIC; + else if (towordstyle(text->type) == word_Code || + towordstyle(text->type) == word_WeakCode) + newfont = WHLP_FONT_FIXED; + else + newfont = deffont; + if (newfont != currfont) { + currfont = newfont; + whlp_set_font(state->h, newfont); + } + if (removeattr(text->type) == word_Normal) { + if (whlp_convert(text->text, &c, TRUE)) + whlp_text(state->h, c); + else + whlp_mkparagraph(state, deffont, text->alt); + sfree(c); + } else if (removeattr(text->type) == word_WhiteSpace) { + whlp_text(state->h, " "); + } else if (removeattr(text->type) == word_Quote) { + whlp_text(state->h, + quoteaux(text->aux) == quote_Open ? "\x91" : "\x92"); + /* FIXME: configurability */ + } + break; + } +} + +static void whlp_rdaddwc(rdstringc *rs, word *text) { + char *c; + + for (; text; text = text->next) switch (text->type) { + case word_HyperLink: + case word_HyperEnd: + case word_UpperXref: + case word_LowerXref: + case word_XrefEnd: + case word_IndexRef: + break; + + case word_Normal: + case word_Emph: + case word_Code: + case word_WeakCode: + case word_WhiteSpace: + case word_EmphSpace: + case word_CodeSpace: + case word_WkCodeSpace: + case word_Quote: + case word_EmphQuote: + case word_CodeQuote: + case word_WkCodeQuote: + assert(text->type != word_CodeQuote && + text->type != word_WkCodeQuote); + if (towordstyle(text->type) == word_Emph && + (attraux(text->aux) == attr_First || + attraux(text->aux) == attr_Only)) + rdaddc(rs, '_'); /* FIXME: configurability */ + else if (towordstyle(text->type) == word_Code && + (attraux(text->aux) == attr_First || + attraux(text->aux) == attr_Only)) + rdaddc(rs, '\x91'); /* FIXME: configurability */ + if (removeattr(text->type) == word_Normal) { + if (whlp_convert(text->text, &c, FALSE)) + rdaddsc(rs, c); + else + whlp_rdaddwc(rs, text->alt); + sfree(c); + } else if (removeattr(text->type) == word_WhiteSpace) { + rdaddc(rs, ' '); + } else if (removeattr(text->type) == word_Quote) { + rdaddc(rs, quoteaux(text->aux) == quote_Open ? '\x91' : '\x92'); + /* FIXME: configurability */ + } + if (towordstyle(text->type) == word_Emph && + (attraux(text->aux) == attr_Last || + attraux(text->aux) == attr_Only)) + rdaddc(rs, '_'); /* FIXME: configurability */ + else if (towordstyle(text->type) == word_Code && + (attraux(text->aux) == attr_Last || + attraux(text->aux) == attr_Only)) + rdaddc(rs, '\x92'); /* FIXME: configurability */ + break; + } +} + +/* + * Convert a wide string into a string of chars. If `result' is + * non-NULL, mallocs the resulting string and stores a pointer to + * it in `*result'. If `result' is NULL, merely checks whether all + * characters in the string are feasible for the output character + * set. + * + * Return is nonzero if all characters are OK. If not all + * characters are OK but `result' is non-NULL, a result _will_ + * still be generated! + */ +static int whlp_convert(wchar_t *s, char **result, int hard_spaces) { + /* + * FIXME. Currently this is ISO8859-1 only. + */ + int doing = (result != 0); + int ok = TRUE; + char *p = NULL; + int plen = 0, psize = 0; + + for (; *s; s++) { + wchar_t c = *s; + char outc; + + if ((c >= 32 && c <= 126) || + (c >= 160 && c <= 255)) { + /* Char is OK. */ + if (c == 32 && hard_spaces) + outc = '\240'; + else + outc = (char)c; + } else { + /* Char is not OK. */ + ok = FALSE; + outc = 0xBF; /* approximate the good old DEC `uh?' */ + } + if (doing) { + if (plen >= psize) { + psize = plen + 256; + p = resize(p, psize); + } + p[plen++] = outc; + } + } + if (doing) { + p = resize(p, plen+1); + p[plen] = '\0'; + *result = p; + } + return ok; +} @@ -82,6 +82,8 @@ struct paragraph_Tag { word *kwtext; /* chapter/section indication */ word *kwtext2; /* numeric-only form of kwtext */ filepos fpos; + + void *private_data; /* for temp use in backends */ }; enum { para_IM, /* index merge */ @@ -216,6 +216,7 @@ int main(int argc, char **argv) { text_backend(sourceform, keywords, idx); xhtml_backend(sourceform, keywords, idx); + whlp_backend(sourceform, keywords, idx); free_para_list(sourceform); free_keywords(keywords); @@ -90,6 +90,7 @@ #define resize(array, len) ( srealloc ((array), (len) * sizeof (*(array))) ) #define lenof(array) ( sizeof(array) / sizeof(*(array)) ) char *dupstr(char *s) { char *r = mknewa(char, 1+strlen(s)); strcpy(r,s); return r; } +#define UNUSEDARG(x) ( (x) = (x) ) /* ------------------------------------------------------------------- */ #define GET_32BIT_LSB_FIRST(cp) \ @@ -396,6 +397,7 @@ WHLP_TOPIC whlp_register_topic(WHLP h, char *context_name, char **clash) * C libraries. */ ctx->index = h->ncontexts++; + ctx->browse_prev = ctx->browse_next = NULL; if (context_name) { /* @@ -505,6 +507,8 @@ void whlp_begin_topic(WHLP h, WHLP_TOPIC topic, char *title, ...) void whlp_browse_link(WHLP h, WHLP_TOPIC before, WHLP_TOPIC after) { + UNUSEDARG(h); + /* * See if the `before' topic is already linked to another one, * and break the link to that if so. Likewise the `after' @@ -794,14 +798,18 @@ void whlp_end_para(WHLP h) * Manage the layout and generation of the |TOPIC section. */ -static void whlp_topicsect_write(WHLP h, struct file *f, void *data, int len) +static void whlp_topicsect_write(WHLP h, struct file *f, void *data, int len, + int can_break) { unsigned char *p = (unsigned char *)data; - if (h->topicblock_remaining <= 0) { + if (h->topicblock_remaining <= 0 || + h->topicblock_remaining < can_break) { /* * Start a new block. */ + if (h->topicblock_remaining > 0) + whlp_file_fill(f, h->topicblock_remaining); whlp_file_add_long(f, h->lasttopiclink); h->firsttopiclink_offset = whlp_file_offset(f); whlp_file_add_long(f, -1L); /* this will be filled in later */ @@ -869,9 +877,19 @@ static void whlp_topic_layout(WHLP h) nlinks = count234(h->text); for (i = 0; i < nlinks; i++) { link = index234(h->text, i); + size = 21 + link->len1 + link->len2; + /* + * We can't split within the topicblock header or within + * linkdata1. So if the split would fall in that area, + * start a new block _now_. + */ + if (TOPIC_BLKSIZE - pos < 21 + link->len1) { + block++; + offset = 0; + pos = 12; + } link->topicoffset = block * 0x8000 + offset; link->topicpos = block * 0x4000 + pos; - size = 21 + link->len1 + link->len2; pos += size; if (link->recordtype != 2) /* TOPICOFFSET doesn't count titles */ offset += link->len2; @@ -931,7 +949,7 @@ static void whlp_topic_layout(WHLP h) h->lasttopicstart = 0L; f = whlp_new_file(h, "|TOPIC"); h->topicblock_remaining = -1; - whlp_topicsect_write(h, f, NULL, 0); /* start the first block */ + whlp_topicsect_write(h, f, NULL, 0, 0); /* start the first block */ for (i = 0; i < nlinks; i++) { unsigned char header[21]; struct topiclink *otherlink; @@ -939,25 +957,6 @@ static void whlp_topic_layout(WHLP h) link = index234(h->text, i); /* - * Fill in the `first topiclink' pointer in the block - * header if appropriate. - */ - if (h->firsttopiclink_offset > 0) { - whlp_file_seek(f, h->firsttopiclink_offset, 0); - whlp_file_add_long(f, link->topicpos); - h->firsttopiclink_offset = 0; - whlp_file_seek(f, 0, 2); - } - - /* - * Update the `last topiclink', and possibly `last - * topicstart', pointers. - */ - h->lasttopiclink = link->topicpos; - if (link->recordtype == 2) - h->lasttopicstart = link->topicpos; - - /* * Create and output the TOPICLINK header. */ PUT_32BIT_LSB_FIRST(header + 0, 21 + link->len1 + link->len2); @@ -976,13 +975,35 @@ static void whlp_topic_layout(WHLP h) } PUT_32BIT_LSB_FIRST(header + 16, 21 + link->len1); header[20] = link->recordtype; - whlp_topicsect_write(h, f, header, 21); + whlp_topicsect_write(h, f, header, 21, 21 + link->len1); /* + * Fill in the `first topiclink' pointer in the block + * header if appropriate. (We do this _after_ outputting + * the header because then we can be sure we'll be in the + * same block as we think we are.) + */ + if (h->firsttopiclink_offset > 0) { + whlp_file_seek(f, h->firsttopiclink_offset, 0); + whlp_file_add_long(f, link->topicpos); + h->firsttopiclink_offset = 0; + whlp_file_seek(f, 0, 2); + } + + /* + * Update the `last topiclink', and possibly `last + * topicstart', pointers. + */ + h->lasttopiclink = link->topicpos; + if (link->recordtype == 2) + h->lasttopicstart = link->topicpos; + + + /* * Output LinkData1 and LinkData2. */ - whlp_topicsect_write(h, f, link->data1, link->len1); - whlp_topicsect_write(h, f, link->data2, link->len2); + whlp_topicsect_write(h, f, link->data1, link->len1, link->len1); + whlp_topicsect_write(h, f, link->data2, link->len2, 0); /* * Output the block header. @@ -1159,7 +1180,7 @@ static void whlp_standard_fontsection(struct file *f) /* * Font names. */ - for (i = 0; i < lenof(fontnames); i++) { + for (i = 0; i < (int)lenof(fontnames); i++) { char data[32]; memset(data, i, sizeof(data)); strncpy(data, fontnames[i], sizeof(data)); @@ -1169,7 +1190,7 @@ static void whlp_standard_fontsection(struct file *f) /* * Font descriptors. */ - for (i = 0; i < lenof(fontdescriptors); i++) { + for (i = 0; i < (int)lenof(fontdescriptors); i++) { whlp_file_add_char(f, fontdescriptors[i].flags); whlp_file_add_char(f, fontdescriptors[i].halfpoints); whlp_file_add_char(f, fontdescriptors[i].facetype); @@ -1718,7 +1739,7 @@ void whlp_abandon(WHLP h) sfree(h); } -#ifndef NOT_TESTMODE_FIXME_FLIP_SENSE_OF_THIS +#ifdef TESTMODE int main(void) { |