summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Tatham <anakin@pobox.com>2004-04-13 15:05:03 +0000
committerSimon Tatham <anakin@pobox.com>2004-04-13 15:05:03 +0000
commit7f9a818c2187960bda0cf3cae515ee8a7ad91481 (patch)
treed96b5c2e50bd83ffde685f0bf9a14c9eab229d28
parent5737b1da42ad79dae7f9a4fdcda9ea716846019c (diff)
downloadhalibut-7f9a818c2187960bda0cf3cae515ee8a7ad91481.zip
halibut-7f9a818c2187960bda0cf3cae515ee8a7ad91481.tar.gz
halibut-7f9a818c2187960bda0cf3cae515ee8a7ad91481.tar.bz2
halibut-7f9a818c2187960bda0cf3cae515ee8a7ad91481.tar.xz
Implement PDF link annotations: both internal hyperlinks within the
document, and references to external URLs for which acroread will start a web browser. [originally from svn r4060]
-rw-r--r--bk_paper.c129
-rw-r--r--bk_pdf.c48
-rw-r--r--bk_ps.c20
-rw-r--r--paper.h19
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.
*/