Implement PDF link annotations: both internal hyperlinks within the
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 13 Apr 2004 15:05:03 +0000 (15:05 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 13 Apr 2004 15:05:03 +0000 (15:05 +0000)
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
bk_pdf.c
bk_ps.c
paper.h

index aff3975..cecb1d7 100644 (file)
@@ -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;
 }
index 44f7486..44a9699 100644 (file)
--- 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 (file)
--- 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 (file)
--- 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.
  */