From 138d7ffb158595a29130949de7a01ac5fdb5c352 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 13 Apr 2004 15:05:03 +0000 Subject: [PATCH] Implement PDF link annotations: both internal hyperlinks within the document, and references to external URLs for which acroread will start a web browser. git-svn-id: svn://svn.tartarus.org/sgt/halibut@4060 cda61777-01e9-0310-a592-d414129be87e --- bk_paper.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- bk_pdf.c | 48 +++++++++++++++++++++++ bk_ps.c | 20 ++++++++++ paper.h | 19 +++++++++ 4 files changed, 200 insertions(+), 16 deletions(-) diff --git a/bk_paper.c b/bk_paper.c index aff3975..cecb1d7 100644 --- a/bk_paper.c +++ b/bk_paper.c @@ -15,8 +15,6 @@ * - set up contents section now we know what sections begin on * which pages * - * - do cross-reference rectangles - * * - do PDF outline * * - all the missing features in text rendering (code paragraphs, @@ -51,7 +49,8 @@ static void wrap_paragraph(para_data *pdata, word *words, int w, int i1, int i2); static page_data *page_breaks(line_data *first, line_data *last, int page_height); -static void render_line(line_data *ldata, int left_x, int top_y); +static void render_line(line_data *ldata, int left_x, int top_y, + xref_dest *dest, keywordlist *keywords); void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords, indexdata *idx) { @@ -257,8 +256,11 @@ void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords, pdata = (para_data *)p->private_data; if (pdata) { + xref_dest dest; + dest.type = NONE; for (ldata = pdata->first; ldata; ldata = ldata->next) { - render_line(ldata, left_margin, paper_height - top_margin); + render_line(ldata, left_margin, paper_height - top_margin, + &dest, keywords); if (ldata == pdata->last) break; } @@ -660,6 +662,8 @@ static page_data *page_breaks(line_data *first, line_data *last, page->first_text = page->last_text = NULL; + page->first_xref = page->last_xref = NULL; + /* * Now assign a y-coordinate to each line on the page. */ @@ -783,20 +787,77 @@ static int render_string(page_data *page, font_data *font, int fontsize, /* * Returns the updated x coordinate. */ -static int render_text(page_data *page, para_data *pdata, int x, int y, - word *text, word *text_end, - int shortfall, int nspaces, int *nspace) +static int render_text(page_data *page, para_data *pdata, line_data *ldata, + int x, int y, word *text, word *text_end, xref **xr, + int shortfall, int nspaces, int *nspace, + keywordlist *keywords) { while (text && text != text_end) { int style, type, findex, errs; wchar_t *str; + xref_dest dest; switch (text->type) { + /* + * Start a cross-reference. + */ case word_HyperLink: - case word_HyperEnd: case word_UpperXref: case word_LowerXref: + + if (text->type == word_HyperLink) { + dest.type = URL; + dest.url = utoa_dup(text->text); + dest.page = NULL; + } else { + keyword *kwl = kw_lookup(keywords, text->text); + para_data *pdata; + + if (kwl) { + assert(kwl->para->private_data); + pdata = (para_data *) kwl->para->private_data; + dest.type = PAGE; + dest.page = pdata->first->page; + dest.url = NULL; + } else { + /* + * Shouldn't happen, but *shrug* + */ + dest.type = NONE; + dest.page = NULL; + dest.url = NULL; + } + } + if (dest.type != NONE) { + *xr = mknew(xref); + (*xr)->dest = dest; /* structure copy */ + if (page->last_xref) + page->last_xref->next = *xr; + else + page->first_xref = *xr; + page->last_xref = *xr; + + /* + * FIXME: Ideally we should have, and use, some + * vertical font metric information here so that + * our cross-ref rectangle can take account of + * descenders and the font's cap height. This will + * do for the moment, but it isn't ideal. + */ + (*xr)->lx = (*xr)->rx = x; + (*xr)->by = y; + (*xr)->ty = y + ldata->line_height; + } + goto nextword; + + /* + * Finish extending a cross-reference box. + */ + case word_HyperEnd: case word_XrefEnd: + *xr = NULL; + goto nextword; + case word_IndexRef: goto nextword; /* @@ -835,12 +896,15 @@ static int render_text(page_data *page, para_data *pdata, int x, int y, (void) string_width(pdata->fonts[findex], str, &errs); if (errs && text->alt) - x = render_text(page, pdata, x, y, text->alt, NULL, - shortfall, nspaces, nspace); + x = render_text(page, pdata, ldata, x, y, text->alt, NULL, + xr, shortfall, nspaces, nspace, keywords); else x = render_string(page, pdata->fonts[findex], pdata->sizes[findex], x, y, str); + if (*xr) + (*xr)->rx = x; + nextword: text = text->next; } @@ -848,16 +912,49 @@ static int render_text(page_data *page, para_data *pdata, int x, int y, return x; } -static void render_line(line_data *ldata, int left_x, int top_y) +static void render_line(line_data *ldata, int left_x, int top_y, + xref_dest *dest, keywordlist *keywords) { int nspace; + xref *xr; + if (ldata->aux_text) { + xr = NULL; nspace = 0; - render_text(ldata->page, ldata->pdata, left_x + ldata->aux_left_indent, - top_y - ldata->ypos, ldata->aux_text, NULL, 0, 0, &nspace); + render_text(ldata->page, ldata->pdata, ldata, + left_x + ldata->aux_left_indent, + top_y - ldata->ypos, + ldata->aux_text, NULL, &xr, 0, 0, &nspace, keywords); } nspace = 0; - render_text(ldata->page, ldata->pdata, left_x + ldata->xpos, - top_y - ldata->ypos, ldata->first, ldata->end, - ldata->hshortfall, ldata->nspaces, &nspace); + + /* + * There might be a cross-reference carried over from a + * previous line. + */ + if (dest->type != NONE) { + xr = mknew(xref); + 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; + + render_text(ldata->page, ldata->pdata, ldata, left_x + ldata->xpos, + top_y - ldata->ypos, ldata->first, ldata->end, &xr, + ldata->hshortfall, ldata->nspaces, &nspace, keywords); + + if (xr) { + /* + * There's a cross-reference continued on to the next line. + */ + *dest = xr->dest; + } else + dest->type = NONE; } diff --git a/bk_pdf.c b/bk_pdf.c index 44f7486..44a9699 100644 --- a/bk_pdf.c +++ b/bk_pdf.c @@ -250,6 +250,54 @@ void pdf_backend(paragraph *sourceform, keywordlist *keywords, } objstream(cstr, "ET"); + /* + * Also, we want an annotation dictionary containing the + * cross-references from this page. + */ + if (page->first_xref) { + xref *xr; + objtext(opage, "/Annots [\n"); + + for (xr = page->first_xref; xr; xr = xr->next) { + object *annot; + char buf[256]; + + annot = new_object(&olist); + objref(opage, annot); + objtext(opage, "\n"); + + objtext(annot, "<<\n/Type /Annot\n/Subtype /Link\n/Rect ["); + sprintf(buf, "%g %g %g %g", + xr->lx / 4096.0, xr->by / 4096.0, + xr->rx / 4096.0, xr->ty / 4096.0); + objtext(annot, buf); + objtext(annot, "]\n/Border [0 0 0]\n"); + + if (xr->dest.type == PAGE) { + objtext(annot, "/Dest ["); + objref(annot, (object *)xr->dest.page->spare); + objtext(annot, " /XYZ null null null]\n"); + } else { + char *p; + + objtext(annot, "/A <<\n/Type /Action\n/S /URI\n/URI ("); + for (p = xr->dest.url; *p; p++) { + char c[2]; + c[0] = *p; + c[1] = '\0'; + if (*p == '(' || *p == ')' || *p == '\\') + objtext(annot, "\\"); + objtext(annot, c); + } + objtext(annot, ")\n>>\n"); + } + + objtext(annot, ">>\n"); + } + + objtext(opage, "]\n"); + } + objtext(opage, ">>\n"); } diff --git a/bk_ps.c b/bk_ps.c index 8fe61c4..ae1feae 100644 --- a/bk_ps.c +++ b/bk_ps.c @@ -117,6 +117,26 @@ void ps_backend(paragraph *sourceform, keywordlist *keywords, fprintf(fp, "%%%%BeginPageSetup\n"); fprintf(fp, "%%%%EndPageSetup\n"); +#if 0 + { + xref *xr; + /* + * I used this diagnostic briefly to ensure that + * cross-reference rectangles were being put where they + * should be. + */ + for (xr = page->first_xref; xr; xr = xr->next) { + fprintf(fp, "gsave 0.7 setgray %g %g moveto", + xr->lx/4096.0, xr->ty/4096.0); + fprintf(fp, " %g %g lineto %g %g lineto", + xr->lx/4096.0, xr->by/4096.0, + xr->rx/4096.0, xr->by/4096.0); + fprintf(fp, " %g %g lineto closepath fill grestore\n", + xr->rx/4096.0, xr->ty/4096.0); + } + } +#endif + for (frag = page->first_text; frag; frag = frag->next) { char *c; diff --git a/paper.h b/paper.h index 3b4a944..4ba0908 100644 --- a/paper.h +++ b/paper.h @@ -17,6 +17,8 @@ typedef struct line_data_Tag line_data; typedef struct page_data_Tag page_data; typedef struct subfont_map_entry_Tag subfont_map_entry; typedef struct text_fragment_Tag text_fragment; +typedef struct xref_Tag xref; +typedef struct xref_dest_Tag xref_dest; /* * This data structure represents the overall document, in the form @@ -215,6 +217,11 @@ struct page_data_Tag { text_fragment *first_text; text_fragment *last_text; /* + * Cross-references. + */ + xref *first_xref; + xref *last_xref; + /* * This spare pointer field is for use by the client backends. */ void *spare; @@ -228,6 +235,18 @@ struct text_fragment_Tag { char *text; }; +struct xref_dest_Tag { + enum { NONE, PAGE, URL } type; + page_data *page; + char *url; +}; + +struct xref_Tag { + xref *next; + int lx, rx, ty, by; + xref_dest dest; +}; + /* * Functions and data exported from psdata.c. */ -- 2.11.0