+
+ if (ldata->first) {
+ /*
+ * There might be a cross-reference carried over from a
+ * previous line.
+ */
+ if (dest->type != NONE) {
+ xr = mknew(xref);
+ xr->next = NULL;
+ xr->dest = *dest; /* structure copy */
+ if (ldata->page->last_xref)
+ ldata->page->last_xref->next = xr;
+ else
+ ldata->page->first_xref = xr;
+ ldata->page->last_xref = xr;
+ xr->lx = xr->rx = left_x + ldata->xpos;
+ xr->by = top_y - ldata->ypos;
+ xr->ty = top_y - ldata->ypos + ldata->line_height;
+ } else
+ xr = NULL;
+
+ {
+ int extra_indent, shortfall, spaces;
+ int just = ldata->pdata->justification;
+
+ /*
+ * All forms of justification become JUST when we have
+ * to squeeze the paragraph.
+ */
+ if (ldata->hshortfall < 0)
+ just = JUST;
+
+ switch (just) {
+ case JUST:
+ shortfall = ldata->hshortfall;
+ spaces = ldata->nspaces;
+ extra_indent = 0;
+ break;
+ case LEFT:
+ shortfall = spaces = extra_indent = 0;
+ break;
+ case RIGHT:
+ shortfall = spaces = 0;
+ extra_indent = ldata->real_shortfall;
+ break;
+ }
+
+ ret = render_text(ldata->page, ldata->pdata, ldata,
+ left_x + ldata->xpos + extra_indent,
+ top_y - ldata->ypos, ldata->first, ldata->end,
+ &xr, shortfall, spaces, &nspace,
+ keywords, idx);
+ }
+
+ if (xr) {
+ /*
+ * There's a cross-reference continued on to the next line.
+ */
+ *dest = xr->dest;
+ } else
+ dest->type = NONE;
+ }
+
+ return ret;
+}
+
+static void render_para(para_data *pdata, paper_conf *conf,
+ keywordlist *keywords, indexdata *idx,
+ paragraph *index_placeholder, page_data *index_page)
+{
+ int last_x;
+ xref *cxref;
+ page_data *cxref_page;
+ xref_dest dest;
+ para_data *target;
+ line_data *ldata;
+
+ dest.type = NONE;
+ cxref = NULL;
+ cxref_page = NULL;
+
+ for (ldata = pdata->first; ldata; ldata = ldata->next) {
+ /*
+ * If this is a contents entry, we expect to have a single
+ * enormous cross-reference rectangle covering the whole
+ * thing. (Unless, of course, it spans multiple pages.)
+ */
+ if (pdata->contents_entry && ldata->page != cxref_page) {
+ cxref_page = ldata->page;
+ cxref = mknew(xref);
+ cxref->next = NULL;
+ cxref->dest.type = PAGE;
+ if (pdata->contents_entry == index_placeholder) {
+ cxref->dest.page = index_page;
+ } else {
+ assert(pdata->contents_entry->private_data);
+ target = (para_data *)pdata->contents_entry->private_data;
+ cxref->dest.page = target->first->page;
+ }
+ cxref->dest.url = NULL;
+ if (ldata->page->last_xref)
+ ldata->page->last_xref->next = cxref;
+ else
+ ldata->page->first_xref = cxref;
+ ldata->page->last_xref = cxref;
+ cxref->lx = conf->left_margin;
+ cxref->rx = conf->paper_width - conf->right_margin;
+ cxref->ty = conf->paper_height - conf->top_margin
+ - ldata->ypos + ldata->line_height;
+ }
+ if (pdata->contents_entry) {
+ assert(cxref != NULL);
+ cxref->by = conf->paper_height - conf->top_margin
+ - ldata->ypos;
+ }
+
+ last_x = render_line(ldata, conf->left_margin,
+ conf->paper_height - conf->top_margin,
+ &dest, keywords, idx);
+ if (ldata == pdata->last)
+ break;
+ }
+
+ /*
+ * If this is a contents entry, add leaders and a page
+ * number.
+ */
+ if (pdata->contents_entry) {
+ word *w;
+ wchar_t *num;
+ int wid;
+ int x;
+
+ if (pdata->contents_entry == index_placeholder) {
+ num = index_page->number;
+ } else {
+ assert(pdata->contents_entry->private_data);
+ target = (para_data *)pdata->contents_entry->private_data;
+ num = target->first->page->number;
+ }
+
+ w = fake_word(num);
+ wid = paper_width_simple(pdata, w);
+ sfree(w);
+
+ for (x = 0; x < conf->base_width; x += conf->leader_separation)
+ if (x - conf->leader_separation > last_x - conf->left_margin &&
+ x + conf->leader_separation < conf->base_width - wid)
+ render_string(pdata->last->page,
+ pdata->fonts[FONT_NORMAL],
+ pdata->sizes[FONT_NORMAL],
+ conf->left_margin + x,
+ (conf->paper_height - conf->top_margin -
+ pdata->last->ypos), L".");
+
+ render_string(pdata->last->page,
+ pdata->fonts[FONT_NORMAL],
+ pdata->sizes[FONT_NORMAL],
+ conf->paper_width - conf->right_margin - wid,
+ (conf->paper_height - conf->top_margin -
+ pdata->last->ypos), num);
+ }
+
+ /*
+ * Render any rectangle (chapter title underline or rule)
+ * that goes with this paragraph.
+ */
+ switch (pdata->rect_type) {
+ case RECT_CHAPTER_UNDERLINE:
+ add_rect_to_page(pdata->last->page,
+ conf->left_margin,
+ (conf->paper_height - conf->top_margin -
+ pdata->last->ypos -
+ conf->chapter_underline_depth),
+ conf->base_width,
+ conf->chapter_underline_thickness);
+ break;
+ case RECT_RULE:
+ add_rect_to_page(pdata->first->page,
+ conf->left_margin + pdata->first->xpos,
+ (conf->paper_height - conf->top_margin -
+ pdata->last->ypos -
+ pdata->last->line_height),
+ conf->base_width - pdata->first->xpos,
+ pdata->last->line_height);
+ break;
+ default: /* placate gcc */
+ break;
+ }
+}
+
+static para_data *code_paragraph(int indent, word *words, paper_conf *conf)
+{
+ para_data *pdata = mknew(para_data);
+
+ /*
+ * For code paragraphs, I'm going to hack grievously and
+ * pretend the three normal fonts are the three code paragraph
+ * fonts.
+ */
+ pdata->fonts[FONT_NORMAL] = conf->cb;
+ pdata->fonts[FONT_EMPH] = conf->co;
+ pdata->fonts[FONT_CODE] = conf->cr;
+ pdata->sizes[FONT_NORMAL] =
+ pdata->sizes[FONT_EMPH] =
+ pdata->sizes[FONT_CODE] = 12;
+
+ pdata->first = pdata->last = NULL;
+ pdata->outline_level = -1;
+ pdata->rect_type = RECT_NONE;
+ pdata->contents_entry = NULL;
+ pdata->justification = LEFT;
+
+ for (; words; words = words->next) {
+ wchar_t *t, *e, *start;
+ word *lhead = NULL, *ltail = NULL, *w;
+ line_data *ldata;
+ int prev = -1, curr;
+
+ t = words->text;
+ if (words->next && words->next->type == word_Emph) {
+ e = words->next->text;
+ words = words->next;
+ } else
+ e = NULL;
+
+ start = t;
+
+ while (*start) {
+ while (*t) {
+ if (!e || !*e)
+ curr = 0;
+ else if (*e == L'i')
+ curr = 1;
+ else if (*e == L'b')
+ curr = 2;
+ else
+ curr = 0;
+
+ if (prev < 0)
+ prev = curr;
+
+ if (curr != prev)
+ break;
+
+ t++;
+ if (e && *e)
+ e++;
+ }
+
+ /*
+ * We've isolated a maximal subsequence of the line
+ * which has the same emphasis. Form it into a word
+ * structure.
+ */
+ w = mknew(word);
+ w->next = NULL;
+ w->alt = NULL;
+ w->type = (prev == 0 ? word_WeakCode :
+ prev == 1 ? word_Emph : word_Normal);
+ w->text = mknewa(wchar_t, t-start+1);
+ memcpy(w->text, start, (t-start) * sizeof(wchar_t));
+ w->text[t-start] = '\0';
+ w->breaks = FALSE;
+
+ if (ltail)
+ ltail->next = w;
+ else
+ lhead = w;
+ ltail = w;
+
+ start = t;
+ prev = -1;
+ }
+
+ ldata = mknew(line_data);
+
+ ldata->pdata = pdata;
+ ldata->first = lhead;
+ ldata->end = NULL;
+ ldata->line_height = conf->base_font_size * 4096;
+
+ ldata->xpos = indent;
+
+ if (pdata->last) {
+ pdata->last->next = ldata;
+ ldata->prev = pdata->last;
+ } else {
+ pdata->first = ldata;
+ ldata->prev = NULL;
+ }
+ ldata->next = NULL;
+ pdata->last = ldata;
+
+ ldata->hshortfall = 0;
+ ldata->nspaces = 0;
+ ldata->aux_text = NULL;
+ ldata->aux_text_2 = NULL;
+ ldata->aux_left_indent = 0;
+ /* General opprobrium for breaking in a code paragraph. */
+ ldata->penalty_before = ldata->penalty_after = 50000;
+ }
+
+ standard_line_spacing(pdata, conf);
+
+ return pdata;
+}
+
+static para_data *rule_paragraph(int indent, paper_conf *conf)
+{
+ para_data *pdata = mknew(para_data);
+ line_data *ldata;
+
+ ldata = mknew(line_data);
+
+ ldata->pdata = pdata;
+ ldata->first = NULL;
+ ldata->end = NULL;
+ ldata->line_height = conf->rule_thickness;
+
+ ldata->xpos = indent;
+
+ ldata->prev = NULL;
+ ldata->next = NULL;
+
+ ldata->hshortfall = 0;
+ ldata->nspaces = 0;
+ ldata->aux_text = NULL;
+ ldata->aux_text_2 = NULL;
+ ldata->aux_left_indent = 0;
+
+ /*
+ * Better to break after a rule than before it
+ */
+ ldata->penalty_after += 100000;
+ ldata->penalty_before += -100000;
+
+ pdata->first = pdata->last = ldata;
+ pdata->outline_level = -1;
+ pdata->rect_type = RECT_RULE;
+ pdata->contents_entry = NULL;
+ pdata->justification = LEFT;
+
+ standard_line_spacing(pdata, conf);
+
+ return pdata;
+}
+
+/*
+ * Plain-text-like formatting for outline titles.
+ */
+static void paper_rdaddw(rdstring *rs, word *text) {
+ 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))
+ rdadd(rs, L'_'); /* FIXME: configurability */
+ else if (towordstyle(text->type) == word_Code &&
+ (attraux(text->aux) == attr_First ||
+ attraux(text->aux) == attr_Only))
+ rdadd(rs, L'\''); /* FIXME: configurability */
+ if (removeattr(text->type) == word_Normal) {
+ rdadds(rs, text->text);
+ } else if (removeattr(text->type) == word_WhiteSpace) {
+ rdadd(rs, L' ');
+ } else if (removeattr(text->type) == word_Quote) {
+ rdadd(rs, L'\''); /* fixme: configurability */
+ }
+ if (towordstyle(text->type) == word_Emph &&
+ (attraux(text->aux) == attr_Last ||
+ attraux(text->aux) == attr_Only))
+ rdadd(rs, L'_'); /* FIXME: configurability */
+ else if (towordstyle(text->type) == word_Code &&
+ (attraux(text->aux) == attr_Last ||
+ attraux(text->aux) == attr_Only))
+ rdadd(rs, L'\''); /* FIXME: configurability */
+ break;
+ }
+}
+
+static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
+ word *second)
+{
+ rdstring rs = {0, 0, NULL};
+
+ if (first)
+ paper_rdaddw(&rs, first);
+ if (separator)
+ rdadds(&rs, separator);
+ if (second)
+ paper_rdaddw(&rs, second);
+
+ return rs.text;
+}
+
+static word *fake_word(wchar_t *text)
+{
+ word *ret = mknew(word);
+ ret->next = NULL;
+ ret->alt = NULL;
+ ret->type = word_Normal;
+ ret->text = ustrdup(text);
+ ret->breaks = FALSE;
+ ret->aux = 0;
+ return ret;
+}
+
+static word *fake_space_word(void)
+{
+ word *ret = mknew(word);
+ ret->next = NULL;
+ ret->alt = NULL;
+ ret->type = word_WhiteSpace;
+ ret->text = NULL;
+ ret->breaks = TRUE;
+ ret->aux = 0;
+ return ret;
+}
+
+static word *fake_page_ref(page_data *page)
+{
+ word *ret = mknew(word);
+ ret->next = NULL;
+ ret->alt = NULL;
+ ret->type = word_PageXref;
+ ret->text = NULL;
+ ret->breaks = FALSE;
+ ret->aux = 0;
+ ret->private_data = page;
+ return ret;
+}
+
+static word *fake_end_ref(void)
+{
+ word *ret = mknew(word);
+ ret->next = NULL;
+ ret->alt = NULL;
+ ret->type = word_XrefEnd;
+ ret->text = NULL;
+ ret->breaks = FALSE;
+ ret->aux = 0;
+ return ret;
+}
+
+static word *prepare_contents_title(word *first, wchar_t *separator,
+ word *second)
+{
+ word *ret;
+ word **wptr, *w;
+
+ wptr = &ret;
+
+ if (first) {
+ w = dup_word_list(first);
+ *wptr = w;
+ while (w->next)
+ w = w->next;
+ wptr = &w->next;
+ }
+
+ if (separator) {
+ w = fake_word(separator);
+ *wptr = w;
+ wptr = &w->next;
+ }
+
+ if (second) {
+ *wptr = dup_word_list(second);
+ }
+
+ return ret;
+}
+
+static void fold_into_page(page_data *dest, page_data *src, int right_shift)
+{
+ line_data *ldata;
+
+ if (!src->first_line)
+ return;
+
+ if (dest->last_line) {
+ dest->last_line->next = src->first_line;
+ src->first_line->prev = dest->last_line;
+ }
+ dest->last_line = src->last_line;
+
+ for (ldata = src->first_line; ldata; ldata = ldata->next) {
+ ldata->page = dest;
+ ldata->xpos += right_shift;
+
+ if (ldata == src->last_line)
+ break;
+ }