summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--bk_whlp.c432
-rw-r--r--buttress.h2
-rw-r--r--main.c1
-rw-r--r--winhelp.c79
5 files changed, 487 insertions, 30 deletions
diff --git a/Makefile b/Makefile
index 09232e5..17cec01 100644
--- a/Makefile
+++ b/Makefile
@@ -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;
+}
diff --git a/buttress.h b/buttress.h
index e9cff87..a233bdb 100644
--- a/buttress.h
+++ b/buttress.h
@@ -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 */
diff --git a/main.c b/main.c
index b7be46f..96bbefd 100644
--- a/main.c
+++ b/main.c
@@ -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);
diff --git a/winhelp.c b/winhelp.c
index 0932a4b..cd9df00 100644
--- a/winhelp.c
+++ b/winhelp.c
@@ -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)
{