Back to index

tetex-bin  3.0
hypertex.c
Go to the documentation of this file.
00001 /*
00002  * Permission is hereby granted, free of charge, to any person obtaining a copy
00003  * of this software and associated documentation files (the "Software"), to
00004  * deal in the Software without restriction, including without limitation the
00005  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00006  * sell copies of the Software, and to permit persons to whom the Software is
00007  * furnished to do so, subject to the following conditions:
00008  * 
00009  * The above copyright notice and this permission notice shall be included in
00010  * all copies or substantial portions of the Software.
00011  * 
00012  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00013  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00014  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00015  * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
00016  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
00017  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
00018  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00019 */
00020 
00021 /*
00022  * Original copyright:
00023  *
00024  * Hypertex modifications to DVI previewer for X.
00025  * This portion of xhdvi is completely in the public domain. The
00026  * author renounces any copyright claims. It may be freely used for
00027  * commercial or non-commercial purposes. The author makes no claims
00028  * or guarantees - use this at your own risk.
00029  *
00030  * Arthur Smith, U. of Washington, 1994
00031  *
00032  * 5/1994       code written from scratch, probably inspired by (but
00033  *                incompatible with) the CERN WWW library.
00034  *
00035  * 3/1995       CERN WWW library called to do document fetching.
00036  *
00037  * 5/2002     SU: rewritten from scratch to make it work with the new
00038  *            event handling in xdvi >= 22.41: Anchors are now saved
00039  *            per page instead of per document, making it more robust
00040  *            wrt. interrupting the drawing and skipping to another page.
00041  *            Only drawback is that links breaking over pages are more
00042  *            difficult to handle (but they didn't work properly before either).
00043  *            Clicking on a link now performs a search for the
00044  *            corresponding anchor similiar to `forward search'.
00045  *
00046  *            Removed libwww, since it has become too bloated
00047  *            and buggy, and had only rudimentary support for GUI
00048  *            interaction to begin with (e.g., interrupting a
00049  *            download). And since this is 2002, we might just as well
00050  *            use a web browser to fetch remote documents ;-)
00051  */
00052 
00053 /* TODO:
00054    
00055    - implement popup with list of visited links, as specified in
00056      http://xdvi.sourceforge.net/gui.html#navigation-openLinks
00057      (maybe this should be a simple menu list, not a popup window).
00058  */
00059 
00060 #define COPY_TMP_FILE 0 /* see comments below */
00061 
00062 /* #define DEBUG */
00063 
00064 /*  #define DEBUG_MEMORY_HANDLING */
00065 #include "alloc-debug.h"
00066 
00067 #include "xdvi-config.h"
00068 #include "xdvi.h"
00069 
00070 #include <string.h>
00071 
00072 #include <ctype.h>
00073 #include "kpathsea/c-fopen.h"
00074 #include "kpathsea/c-stat.h"
00075 #include <X11/StringDefs.h> /* for XtNwidth, XtNheight */
00076 
00077 #include "events.h"
00078 #include "dvi-init.h"
00079 #include "message-window.h"
00080 #include "util.h"
00081 #include "x_util.h"
00082 #include "mime.h"
00083 #include "mag.h"
00084 #include "dvi-draw.h"
00085 #include "statusline.h"
00086 #include "browser.h"
00087 #include "hypertex.h"
00088 #include "special.h"
00089 #include "string-utils.h"
00090 #include "xm_toolbar.h"
00091 #include "my-snprintf.h"
00092 #include "pagehist.h"
00093 
00094 /* globals */
00095 /* rgb specifications to translate resource.{visited_}link_color into */
00096 char *g_link_color_rgb = NULL;
00097 char *g_visited_link_color_rgb = NULL;
00098 char *g_anchor_pos = NULL;
00099 size_t g_anchor_len = 0;
00100 
00101 static const int DEFAULT_MARKER_X_OFFSET = 2; /* horizontal offset of anchor marker to edge of screen */
00102 static const int HTEX_ALLOC_STEP = 32;
00103 
00104 /* distance of underline from lower edge of character bbox */
00105 static const int ANCHOR_XTRA_V_BBOX = 6;
00106 
00107 /* info whether we're dealing with hypertex or hdvips type links */
00108 typedef enum { HYPERTEX_LINK, HDVIPS_LINK } hrefLinkT;
00109 static hrefLinkT m_href_type;
00110 
00111 typedef enum {
00112     A_HREF = 0,
00113     A_NAME,
00114     A_HREF_FILE,     /* hdvips file ref */
00115     A_HREF_URL,             /* hdvips URL ref */
00116     A_HDVIPS_INTERNAL,      /* internal ref */
00117     A_HDVIPS_HREF,   /* hdvips href */
00118     A_HDVIPS_NAME,   /* hdvips name */
00119     A_OTHER,
00120     A_END,
00121     A_NONE,
00122     A_MISMATCH
00123 } htexAnchorT;
00124 
00125 /****************************** helper routines ******************************/
00126 
00127 static Boolean
00128 htex_is_href(htexAnchorT type)
00129 {
00130     return type == A_HREF
00131        || type == A_HREF_FILE
00132        || type == A_HREF_URL
00133        || type == A_HDVIPS_HREF
00134        || type == A_HDVIPS_INTERNAL;
00135 }
00136 
00137 static Boolean
00138 htex_is_name(htexAnchorT type)
00139 {
00140     return type == A_NAME || type == A_HDVIPS_NAME;
00141 }
00142 
00143 static void
00144 parse_html_special(const char *special, size_t len, const char **beg, const char **end)
00145 {
00146     *beg = special;
00147     while (isspace((int)**beg) || **beg == '=') {
00148        (*beg)++;
00149        len--;
00150     }
00151     *end = *beg + len - 1;
00152 
00153     while (isspace((int)**end) || **end == '>')
00154        (*end)--;
00155 
00156     /* remove quote pairs */
00157     if (**beg == '"') {
00158        (*beg)++;
00159        if (**end == '"') {
00160            (*end)--;
00161        }
00162     }
00163     /* now end points to last anchor char, move behind that */
00164     (*end)++;
00165 }
00166 
00167 /*
00168   Anchor prescanning stuff: We make a pass through all pages in the document
00169   before displaying them to collect information about mismatched anchors
00170   (anchors where the opening tag is on some previous page).
00171   The information is stored in the following struct:
00172 */
00173 struct prescan_info {
00174     int anchor_depth;
00175     size_t anchor_num;
00176     size_t anchor_list_size;
00177     int *anchor_list;
00178     /* pointers to either NULL, or to mismatched anchor strings
00179        (the contents of '<a href="...">' on some previous page),
00180        for each page */
00181     size_t pagelist_len;
00182     char **pagelist;
00183 };
00184 
00185 static struct prescan_info m_prescan_info = { 0, 0, 0, NULL, 0, NULL };
00186 static struct prescan_info m_save_prescan_info = { 0, 0, 0, NULL, 0, NULL };
00187 
00188 
00189 /*
00190   Save prescan info from previous page, in case we only have a partial
00191   scan of the current page and need to start all over.
00192 */
00193 void
00194 htex_prescan_save(void)
00195 {
00196     m_save_prescan_info.anchor_depth = m_prescan_info.anchor_depth;
00197     m_save_prescan_info.anchor_num = m_prescan_info.anchor_num;
00198     /* copy over anchor list */
00199     while (m_save_prescan_info.anchor_depth >= (int)m_save_prescan_info.anchor_list_size) {
00200        int i;
00201        m_save_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
00202        m_save_prescan_info.anchor_list = XREALLOC(m_save_prescan_info.anchor_list,
00203                                              m_save_prescan_info.anchor_list_size
00204                                              * sizeof *(m_save_prescan_info.anchor_list));
00205        for (i = 0; i < m_save_prescan_info.anchor_depth; i++) {
00206            m_save_prescan_info.anchor_list[i] = m_prescan_info.anchor_list[i];
00207        }
00208     }
00209     /* the page list of anchor strings can only have had its current position modified;
00210        we'll simply delete that position in htex_prescan_restore().
00211     */
00212 }
00213 
00214 /*
00215   Restore prescan info from m_save_prescan_info.
00216 */
00217 void
00218 htex_prescan_restore(int pageno)
00219 {
00220     m_prescan_info.anchor_depth = m_save_prescan_info.anchor_depth;
00221     m_prescan_info.anchor_num = m_save_prescan_info.anchor_num;
00222     /* copy back anchor list */
00223     if (m_save_prescan_info.anchor_depth > 0) {
00224        int i;
00225        /* old list might have been free()d in htex_prescan_reset_firstpass */
00226        while (m_save_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) {
00227            m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
00228            m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list,
00229                                             m_prescan_info.anchor_list_size
00230                                             * sizeof *(m_prescan_info.anchor_list));
00231        }
00232        for (i = 0; i < m_save_prescan_info.anchor_depth; i++) {
00233            m_prescan_info.anchor_list[i] = m_save_prescan_info.anchor_list[i];
00234        }
00235     }
00236     /* reset anchor string info for this page */
00237     if ((int)m_prescan_info.pagelist_len > pageno) {
00238        ASSERT(m_prescan_info.pagelist != NULL, "m_prescan_info.pagelist not properly allocated");
00239        FREE(m_prescan_info.pagelist[pageno]);
00240        m_prescan_info.pagelist[pageno] = NULL;
00241     }
00242 }
00243 
00244 /*
00245   Reset prescan info for first pass (anchor numbers only) to its initial state,
00246   freeing up allocated resources for anchor_list.
00247   
00248 */
00249 void
00250 htex_prescan_reset_firstpass(void)
00251 {
00252     MYTRACE((stderr, "resetting anchor_depth to 0!!!!!!!!!!!!"));
00253     m_prescan_info.anchor_depth = 0;
00254     m_prescan_info.anchor_num = 0;
00255     m_prescan_info.anchor_list_size = 0;
00256     FREE(m_prescan_info.anchor_list);
00257     m_prescan_info.anchor_list = NULL;
00258 }
00259 
00260 /*
00261   Reset the prescan info, freeing up all allocated memory.
00262   Used when e.g. switching to a different file via Ctrl-F.
00263 */
00264 static void
00265 htex_prescan_reset(void)
00266 {
00267     size_t i;
00268     m_prescan_info.anchor_depth = 0;
00269     m_prescan_info.anchor_num = 0;
00270     m_prescan_info.anchor_list_size = 0;
00271     
00272     free(m_prescan_info.anchor_list);
00273     m_prescan_info.anchor_list = NULL;
00274     
00275     for (i = 0; i < m_prescan_info.pagelist_len; i++) {
00276        free(m_prescan_info.pagelist[i]);
00277     }
00278     free(m_prescan_info.pagelist);
00279     m_prescan_info.pagelist = NULL;
00280     m_prescan_info.pagelist_len = 0;
00281 }
00282 
00283 /*
00284   Initialize m_prescan_info for scanning a new page.
00285 */
00286 void
00287 htex_prescan_initpage(void)
00288 {
00289     m_prescan_info.anchor_num = 0;
00290 }
00291 
00292 
00293 Boolean
00294 htex_prescan_special(const char *cp, int cp_len, struct htex_prescan_data *data)
00295 {
00296     UNUSED(cp_len);
00297     ASSERT(data != NULL, "data argument to htex_prescan_special() mustn't be NULL");
00298     if (data->pageno + 1 < (int)m_prescan_info.pagelist_len) { /* already scanned this page */
00299        MYTRACE((stderr, "already scanned page %d", data->pageno+1));
00300        return False;
00301     }
00302 
00303     /* resize pagelist */
00304     while (data->pageno >= (int)m_prescan_info.pagelist_len) {
00305        size_t old_len = m_prescan_info.pagelist_len;
00306        size_t i;
00307        MYTRACE((stderr, "============ resizing pagelist to %d", data->pageno + 1));
00308        m_prescan_info.pagelist_len = data->pageno + 1;
00309        m_prescan_info.pagelist = XREALLOC(m_prescan_info.pagelist,
00310                                       m_prescan_info.pagelist_len * sizeof *m_prescan_info.pagelist);
00311        /* initialize with NULL values */
00312        for (i = old_len; i < m_prescan_info.pagelist_len; i++) {
00313            MYTRACE((stderr, "============ initializing pagelist %d", i));
00314            m_prescan_info.pagelist[i] = NULL;
00315        }
00316     }
00317     
00318     if (data->scan_type == HTEX_ANCHOR_STRING) {
00319        if ((int)m_prescan_info.anchor_num == data->anchor_num) {
00320            char *anchor;
00321            const char *beg, *end;
00322            const char *cp1 = cp;
00323            const char *ptr = NULL;
00324            const char *pptr = NULL;
00325            
00326            if (memicmp(cp1, "<a href", strlen("<a href")) == 0) {
00327               cp1 += strlen("<a href");
00328               parse_html_special(cp1, strlen(cp1), &beg, &end);
00329               anchor = xmalloc(end - beg + 1);
00330               memcpy(anchor, beg, end - beg);
00331               anchor[end - beg] = '\0';
00332               /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
00333               m_prescan_info.pagelist[data->pageno] = anchor;
00334            }
00335            else if (memcmp(cp1, "/A", 2) == 0) {
00336               if ((ptr = strstr(cp1 + 2, "/GoToR")) != NULL /* external file */
00337                   && (ptr = strchr(ptr, '(')) != NULL
00338                   && (pptr = strchr(ptr + 1, '(')) != NULL) {
00339                   anchor = xmalloc(pptr - ptr);
00340                   memcpy(anchor, ptr + 1, pptr - ptr - 1);
00341                   anchor[pptr - ptr - 1] = '\0';
00342                   /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
00343                   m_prescan_info.pagelist[data->pageno] = anchor;
00344               }
00345               else if ((ptr = strstr(cp1 + 2, "/URI")) != NULL /* external file */
00346                   && (ptr = strchr(ptr, '(')) != NULL
00347                   && (pptr = strchr(ptr + 1, '(')) != NULL) {
00348                   anchor = xmalloc(pptr - ptr);
00349                   memcpy(anchor, ptr + 1, pptr - ptr - 1);
00350                   anchor[pptr - ptr - 1] = '\0';
00351                   /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
00352                   m_prescan_info.pagelist[data->pageno] = anchor;
00353               }
00354            }
00355            else if (memcmp(cp, "/L", 2) == 0) {
00356               if ((ptr = strstr(cp1 + 2, "/Dest")) != NULL /* internal link */
00357                   && (ptr = strchr(ptr, '(')) != NULL
00358                   && (pptr = strchr(ptr + 1, '(')) != NULL) {
00359                   anchor = xmalloc(pptr - ptr);
00360                   memcpy(anchor, ptr + 1, pptr - ptr - 1);
00361                   anchor[pptr - ptr - 1] = '\0';
00362                   /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
00363                   m_prescan_info.pagelist[data->pageno] = anchor;
00364               }
00365            }
00366        }
00367     }
00368     if (memicmp(cp, "<a ", 3) == 0) {
00369        while (m_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) {
00370            m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
00371            m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list,
00372                                             m_prescan_info.anchor_list_size
00373                                             * sizeof *(m_prescan_info.anchor_list));
00374        }
00375        ASSERT(m_prescan_info.anchor_depth >= 0, "List should contain previous anchor info");
00376        m_prescan_info.anchor_list[m_prescan_info.anchor_depth] = m_prescan_info.anchor_num;
00377        m_prescan_info.anchor_depth++;
00378        m_prescan_info.anchor_num++;
00379     }
00380     else if (memicmp(cp, "</a", 3) == 0) {
00381        if (m_prescan_info.anchor_depth < 1) {
00382            /* this can happen when stuff had been prescanned before */
00383            return False;
00384        }
00385        m_prescan_info.anchor_depth--;
00386        m_prescan_info.anchor_num++;
00387     }
00388     else if (memcmp(cp, "H.S end", strlen("H.S end")) == 0) { /* start of anchor */
00389        while (m_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) {
00390            m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
00391            m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list,
00392                                             m_prescan_info.anchor_list_size
00393                                             * sizeof *(m_prescan_info.anchor_list));
00394        }
00395        ASSERT(m_prescan_info.anchor_depth >= 0, "List should contain previous anchor info");
00396        m_prescan_info.anchor_list[m_prescan_info.anchor_depth] = m_prescan_info.anchor_num;
00397        m_prescan_info.anchor_depth++;
00398        m_prescan_info.anchor_num++;
00399 
00400     }
00401     else if (memcmp(cp, "H.R end", strlen("H.R end")) == 0 /* end of rect */
00402             || memcmp(cp, "H.A end", strlen("H.A end")) == 0 /* end of anchor */
00403             || memcmp(cp, "H.L end", strlen("H.L end")) == 0 /* end of link */
00404             ) {
00405        if (m_prescan_info.anchor_depth < 1) {
00406            /* this can happen when stuff had been prescanned before */
00407            return False;
00408        }
00409        m_prescan_info.anchor_depth--;
00410        m_prescan_info.anchor_num++;
00411     }
00412     return False;
00413 }
00414 
00415 int
00416 htex_prescan_get_depth(void)
00417 {
00418     return m_prescan_info.anchor_depth;
00419 }
00420 
00421 /* copy over pagelist from old_page to new_page */
00422 void
00423 htex_prescan_carry_over(int old_page, int new_page)
00424 {
00425     ASSERT(old_page >= 0, "old_page out of range");
00426     ASSERT(old_page < (int)m_prescan_info.pagelist_len, "old_page out of range");
00427 
00428     /* resize if needed */
00429     if (new_page >= (int)m_prescan_info.pagelist_len) {
00430        size_t old_len = m_prescan_info.pagelist_len;
00431        size_t i;
00432        m_prescan_info.pagelist_len = new_page + 1;
00433        m_prescan_info.pagelist = XREALLOC(m_prescan_info.pagelist,
00434                                       m_prescan_info.pagelist_len * sizeof *m_prescan_info.pagelist);
00435        /* initialize with NULL values */
00436        for (i = old_len; i < m_prescan_info.pagelist_len; i++) {
00437            m_prescan_info.pagelist[i] = NULL;
00438        }
00439     }
00440     free(m_prescan_info.pagelist[new_page]);
00441     /* don't share pointers here */
00442     m_prescan_info.pagelist[new_page] = xstrdup(m_prescan_info.pagelist[old_page]);
00443 }
00444 
00445 size_t
00446 htex_prescan_get_mismatched_anchor_num(size_t depth)
00447 {
00448     ASSERT((int)depth <= m_prescan_info.anchor_depth, "depth too large");
00449     ASSERT(depth <= m_prescan_info.anchor_list_size, "depth too large for lookup list");
00450     
00451     return m_prescan_info.anchor_list[m_prescan_info.anchor_depth - 1];
00452 }
00453 
00454 /******************************* end of prescan stuff ******************************/
00455 
00456 struct anchor_marker {
00457     int page; /* page on which marker is located */
00458     char *filename; /* file in which the marker is located */
00459     int y_pos;  /* vertical position for marker */
00460     int x_pos;  /* horizontal position for marker */
00461 } g_anchormarker = { -1, NULL, -1, -1 };
00462 
00463 static XtIntervalId m_href_timeout_id = 0;
00464 
00465 
00466 struct history_info {
00467     char *anchor;    /* anchor name */
00468     char *filename;  /* name of file in which this anchor is located */
00469     int page;        /* pagenumber on which anchor is located */
00470 };
00471 
00472 struct anchor_info {
00473     /* anchor contents as strings */
00474     char *a_name;
00475     char *a_href;
00476     htexObjectT object_type;
00477     /* bounding box info for this anchor */
00478     int lrx, lry;    /* lower-right corner */
00479     int ulx, uly;    /* upper-left corner */
00480 /*     int refpage;  /\* page in DVI file for stack of visited anchors *\/ */
00481 /*     char *filename;             /\* name of file in which this anchor is located, for visited anchors *\/ */
00482     int prev_wrapped;       /* index of prev elem, for wrapped hrefs, or -1 */
00483     int next_wrapped;       /* index of next elem, for wrapped hrefs, or -1 */
00484 };
00485 
00486 struct htex_page_info {
00487     struct anchor_info *anchors;   /* anchor info */
00488     int tot_cnt;            /* anchor info size */
00489     int curr_cnt;           /* current number of anchor on page */
00490     int page;               /* page in DVI file this anchor info refers to */
00491     int have_wrapped;              /* -1 if no wrapped anchor on beginning of page, or index of wrapped anchor */
00492 };
00493 
00494 /* file-scope globals ... */
00495 /* record x and y positions of current anchor, to recognize linebreaks in anchors */
00496 static int x_pos_bak = 0;
00497 static int y_pos_bak = 0;
00498 /* holds all anchors on current page */
00499 static struct htex_page_info htex_page_info = { NULL, 0, 0, -1, -1 };
00500 
00501 /*
00502   double linked list with history of clicked links, for htex_forward/htex_back
00503  */
00504 static struct dl_list *htex_history = NULL;      /* current list insertion point */
00505 
00506 struct htex_anchor_stack_elem {
00507     htexAnchorT type;       /* type of anchor (href, anchor, ...) */
00508     int anchor_num;         /* number of this anchor on page (same as curr_cnt in struct htex_page_info) */
00509 };
00510 
00511 /*
00512  * stack datatype and access functions; used for nested anchors
00513  */
00514 struct htex_anchor_stack {
00515     size_t size;
00516     size_t depth;
00517     struct htex_anchor_stack_elem *types;
00518 };
00519 
00520 struct visited_anchor {
00521     int *list;
00522     size_t list_size;
00523 };
00524 
00525 struct visited_anchors {
00526     struct visited_anchor *anchors;
00527     size_t size;
00528 };
00529 
00530 static struct visited_anchors visited_links = { NULL, 0 };
00531 
00532 static struct htex_anchor_stack stack = { 0, 0, NULL };
00533 
00534 
00535 
00536 /* the following list is from
00537    http://www.iana.org/assignments/uri-schemes
00538    re-sorted for likeliness. All protocols except for `file'
00539    are considered remote.
00540    (i.e. not accessible via the ordinary Unix file system)
00541 */
00542 static char *const remote_URL_schemes[] = {
00543     "http:",            /* Hypertext Transfer Protocol                    [RFC2068] */
00544     "ftp:",             /* File Transfer Protocol                         [RFC1738] */
00545     "https:",           /* Hypertext Transfer Protocol Secure             [RFC2818] */
00546     "mailto:",          /* Electronic mail address                        [RFC2368] */
00547     "news:",            /* USENET news                                    [RFC1738] */
00548     "nntp:",            /* USENET news using NNTP access                  [RFC1738] */
00549     "telnet:",          /* Reference to interactive sessions              [RFC1738] */
00550     "nfs:",             /* network file system protocol                   [RFC2224] */
00551     "gopher:",          /* The Gopher Protocol                            [RFC1738] */
00552     "wais:",            /* Wide Area Information Servers                  [RFC1738] */
00553     /* the only exception: */
00554     /* "file:", */      /* Host-specific file names                       [RFC1738] */
00555     "prospero:",        /* Prospero Directory Service                     [RFC1738] */
00556     "z39.50s",          /* Z39.50 Session                                 [RFC2056] */
00557     "z39.50r",          /* Z39.50 Retrieval                               [RFC2056] */
00558     "cid:",             /* content identifier                             [RFC2392] */
00559     "mid:",             /* message identifier                             [RFC2392] */
00560     "vemmi:",           /* versatile multimedia interface                 [RFC2122] */
00561     "service:",         /* service location                               [RFC2609] */
00562     "imap:",            /* internet message access protocol               [RFC2192] */
00563     "acap:",            /* application configuration access protocol      [RFC2244] */
00564     "rtsp:",            /* real time streaming protocol                   [RFC2326] */
00565     "tip:",             /* Transaction Internet Protocol                  [RFC2371] */ 
00566     "pop:",             /* Post Office Protocol v3                        [RFC2384] */
00567     "data:",            /* data                                           [RFC2397] */
00568     "dav:",             /* dav                                            [RFC2518] */
00569     "opaquelocktoken:", /* opaquelocktoken                                [RFC2518] */
00570     "sip:",             /* session initiation protocol                    [RFC2543] */
00571     "tel:",             /* telephone                                      [RFC2806] */
00572     "fax:",             /* fax                                            [RFC2806] */
00573     "modem:",           /* modem                                          [RFC2806] */
00574     "ldap:",            /* Lightweight Directory Access Protocol          [RFC2255] */
00575     "soap.beep:",       /* soap.beep                                      [RFCSOAP] */
00576     "soap.beeps:",      /* soap.beeps                                     [RFCSOAP] */
00577     /* Reserved URI Scheme Names: */
00578     "afs:",             /* Andrew File System global file names                      */    
00579     "tn3270:",          /* Interactive 3270 emulation sessions                */
00580     "mailserver:",      /* Access to data available from mail servers         */
00581     NULL
00582 };
00583 
00584 /* prototypes */
00585 static void htex_erase_anchormarker(XtPointer client_data, XtIntervalId *id);
00586 static void htex_draw_anchormarker(int y);
00587 
00588 
00589 static void
00590 resize_info_if_needed(struct htex_page_info *info)
00591 {
00592     /* resize info if needed */
00593     if (info->curr_cnt + 2 >= info->tot_cnt) {
00594        int i;
00595        while (info->curr_cnt + 2 >= info->tot_cnt) {
00596            info->tot_cnt += HTEX_ALLOC_STEP;
00597        }
00598        info->anchors = XREALLOC(info->anchors, info->tot_cnt * sizeof *(info->anchors));
00599        for (i = info->curr_cnt; i < info->tot_cnt; i++) {
00600 /*         fprintf(stderr, "initializing info at index %d\n", i); */
00601            info->anchors[i].a_href = NULL;
00602            info->anchors[i].a_name = NULL;
00603 /*         info->anchors[i].filename = NULL; */
00604            info->anchors[i].ulx = INT_MAX;
00605            info->anchors[i].uly = INT_MAX;
00606            info->anchors[i].lrx = 0;
00607            info->anchors[i].lry = 0;
00608            info->anchors[i].object_type = HTEX_TEXT;
00609            info->anchors[i].prev_wrapped = -1;
00610            info->anchors[i].next_wrapped = -1;
00611        }
00612     }
00613 }
00614 
00615 static void
00616 init_visited_links(struct visited_anchors *links, int total_pages, Boolean new_dvi_file)
00617 {
00618     size_t i, old_size = links->size;
00619     if (new_dvi_file) {
00620        /* free old list */
00621        for (i = 0; i < old_size; i++) {
00622            FREE(links->anchors[i].list);
00623            links->anchors[i].list = NULL;
00624            links->anchors[i].list_size = 0;
00625        }
00626     }
00627     if (links->size <= (size_t)total_pages) {
00628        MYTRACE((stderr, "resetting visited links (%d != %d)", links->size, total_pages));
00629        links->size = total_pages + 1;
00630        links->anchors = XREALLOC(links->anchors, (links->size + 1) * sizeof *(links->anchors));
00631        for (i = old_size; i < links->size; i++) {
00632            MYTRACE((stderr, "+++ initializing visited links for page %d", i));
00633            links->anchors[i].list = NULL;
00634            links->anchors[i].list_size = 0;
00635        }
00636     }
00637 }
00638 
00639 #if 0
00640 static void
00641 show_visited(struct visited_anchors *links, int pageno)
00642 {
00643     size_t i;
00644     fprintf(stderr, "visited links on page %d:\n", pageno);
00645     for (i = 0; i < links->anchors[pageno].list_size; i++) {
00646        fprintf(stderr, "%d ", links->anchors[pageno].list[i]);
00647     }
00648     fprintf(stderr, "\n");
00649 }
00650 #endif
00651 
00652 static void
00653 save_in_list(struct visited_anchors *links, int pageno, int idx)
00654 {
00655     links->anchors[pageno].list
00656        = XREALLOC(links->anchors[pageno].list,
00657                  (links->anchors[pageno].list_size + 1)
00658                  * sizeof *(links->anchors[pageno].list));
00659     links->anchors[pageno].list[links->anchors[pageno].list_size] = idx;
00660     links->anchors[pageno].list_size++;
00661 }
00662 
00663 static void
00664 set_visited(struct visited_anchors *links, int pageno, int anchor_num)
00665 {
00666     int i;
00667     /* is it already present? */
00668     for (i = 0; i < (int)links->anchors[pageno].list_size; i++) {
00669        if (links->anchors[pageno].list[i] == anchor_num)
00670            return;
00671     }
00672     save_in_list(links, pageno, anchor_num);
00673     i = anchor_num;
00674     /* also set previous/next of this anchor to visited */
00675     while ((i = htex_page_info.anchors[i].prev_wrapped) != -1) {
00676        TRACE_HTEX((stderr, "set_visited: setting prev_wrapped %d to visited too", i));
00677        save_in_list(links, pageno, i);
00678     }
00679     i = anchor_num;
00680     while ((i = htex_page_info.anchors[i].next_wrapped) != -1) {
00681        TRACE_HTEX((stderr, "set_visited: setting next_wrapped %d to visited too", i));
00682        save_in_list(links, pageno, i);
00683     }
00684 #if 0
00685     show_visited(links, pageno);
00686 #endif
00687 }
00688 
00689 static Boolean
00690 is_visited(struct visited_anchors *links, int pageno, int anchor_num)
00691 {
00692     size_t i;
00693     
00694     ASSERT((size_t)pageno < links->size, "too few elements in links structure");
00695 
00696     for (i = 0; i < links->anchors[pageno].list_size; i++) {
00697        if (links->anchors[pageno].list[i] == anchor_num) {
00698            return True;
00699        }
00700     }
00701     return False;
00702 }
00703 
00704 static void
00705 push_stack(struct htex_anchor_stack *stack, htexAnchorT type, int anchor_num)
00706 {
00707     size_t i = 0;
00708     if (stack->depth >= stack->size) {
00709        stack->size += HTEX_ALLOC_STEP;
00710        stack->types = XREALLOC(stack->types, stack->size * sizeof *(stack->types));
00711        for (i = stack->depth; i < stack->size; i++) {
00712            stack->types[i].type = A_NONE;
00713            stack->types[i].anchor_num = -1;
00714        }
00715     }
00716     stack->types[stack->depth].type = type;
00717     stack->types[stack->depth].anchor_num = anchor_num;
00718     stack->depth++;
00719 #if 0
00720     {
00721        fprintf(stderr, "PUSH - stack is now: \n");
00722        for (i = 0; i < stack->depth; i++)
00723            fprintf(stderr, "%d:%d ", i, stack->types[i]);
00724        MYTRACE(stderr, "\n");
00725     }
00726 #endif
00727 }
00728 
00729 static htexAnchorT
00730 pop_stack(struct htex_anchor_stack *stack, int *anchor_num)
00731 {
00732     htexAnchorT ret;
00733     
00734     if (stack->depth < 1) {
00735        return A_MISMATCH;
00736     }
00737     
00738 #if 0
00739     {
00740        int i;
00741        MYTRACE((stderr, "POP - stack is now: "));
00742        for (i = 0; i < stack->depth; i++)
00743            MYTRACE((stderr, "%d:%d ", i, stack->types[i]));
00744        MYTRACE((stderr, ""));
00745     }
00746 #endif
00747     stack->depth--;
00748     ret = stack->types[stack->depth].type;
00749     *anchor_num = stack->types[stack->depth].anchor_num;
00750     stack->types[stack->depth].type = A_NONE;
00751     stack->types[stack->depth].anchor_num = -1;
00752     return ret;
00753 }
00754 
00755 
00756 /* return True if stack contains an A_HREF, False else */
00757 static Boolean
00758 get_href_depth(const struct htex_anchor_stack *stack)
00759 {
00760     size_t i;
00761     for (i = 0; i <= stack->depth; i++) {
00762        ASSERT(stack->types != NULL, "types musn't be NULL!");
00763        if (htex_is_href(stack->types[i].type))
00764            return True;
00765     }
00766     return False;
00767 }
00768 
00769 static htexAnchorT
00770 peek_stack(struct htex_anchor_stack *stack, int *anchor_num)
00771 {
00772     if (stack->depth < 1) {
00773        MYTRACE((stderr, "Xdvi warning: wrong nesting of anchors on page %d", current_page));
00774        *anchor_num = -1;
00775        return A_NONE;
00776     }
00777     
00778     *anchor_num = stack->types[stack->depth - 1].anchor_num;
00779     return stack->types[stack->depth - 1].type;
00780 }
00781 
00782 /* routines for coloring the anchors */
00783 static void
00784 push_colorspecial(void)
00785 {
00786     int i;
00787 
00788     i = htex_page_info.curr_cnt - 1;
00789     /* apply color if needed */
00790     if (resource.link_style > 1) { /* colored links are wanted */
00791        Boolean visited = False;
00792 
00793        ASSERT(i >= 0, "i mustn't be negative");
00794        if (is_visited(&visited_links, current_page, i)) {/*  || wrapped_anchor_is_visited(i)) { */
00795            visited = True;
00796        }
00797 
00798        MYTRACE((stderr, "anchor %d, %s is %s\n",
00799                htex_page_info.curr_cnt - 1, htex_page_info.anchors[htex_page_info.curr_cnt - 1].a_href,
00800                visited ? "******* visited ****** " : "not visited"));
00801        if ((visited && resource.visited_link_color != NULL)
00802            || (! visited && resource.link_color != NULL)) {
00803            color_special(visited ? g_visited_link_color_rgb : g_link_color_rgb);
00804        }
00805     }
00806 }
00807 
00808 static void
00809 pop_colorspecial(void)
00810 {
00811     color_special("pop");
00812 }
00813 
00814 /* return filename if it's a local file, NULL else */
00815 const char *
00816 is_local_file(const char *filename)
00817 {
00818     int i;
00819     if (strchr(filename, ':') != NULL) {
00820        if (memicmp(filename, "file:", strlen("file:")) == 0) {
00821            TRACE_HTEX((stderr, "%s uses file scheme", filename));
00822            filename += strlen("file:");
00823            /*
00824              skip over `//localhost' part, and skip first `/' iff the
00825              absolute path starts with `//' (as required by RFC2396,
00826              but in the past most browsers/applications didn't support
00827              this).
00828             */
00829            if (memicmp(filename, "//localhost", strlen("//localhost")) == 0) {
00830               filename += strlen("//localhost");
00831            }
00832            if (memicmp(filename, "//", 2) == 0) {
00833               filename += 1;
00834            }
00835            return filename;
00836        }
00837        
00838        /* check remote schemes */
00839        for (i = 0; remote_URL_schemes[i] != NULL; i++) {
00840            if (memicmp(filename, remote_URL_schemes[i], strlen(remote_URL_schemes[i])) == 0) {
00841               TRACE_HTEX((stderr, "%s is a remote scheme", filename));
00842               return NULL;
00843            }
00844        }
00845     }
00846     /* in all other cases, treat it as an ordinary filename */
00847     TRACE_HTEX((stderr, "%s is an ordinary filename", filename));
00848     return filename;
00849 }
00850 
00851 
00852 static char *
00853 parse_anchortext(const char *input, int len)
00854 {
00855     char *anchor = NULL;
00856     const char *beg, *end;
00857 
00858     parse_html_special(input, len, &beg, &end);
00859 
00860     anchor = XMALLOC(anchor, end - beg + 1);
00861     memcpy(anchor, beg, end - beg);
00862     anchor[end - beg] = '\0';
00863     return anchor;
00864 }
00865 
00866 static void
00867 add_anchor(struct htex_page_info *info, htexAnchorT type,
00868           const char *str, size_t len,
00869           int pageno, char *filename)
00870 {
00871     UNUSED(pageno);
00872     UNUSED(filename);
00873 
00874     resize_info_if_needed(info);
00875 
00876     ASSERT(htex_is_name(type) || htex_is_href(type), "This doesn't look like a valid anchor");
00877     /* add an anchor or a href, depending on `type' */
00878     if (type == A_HREF) {
00879        if (info->anchors[info->curr_cnt].a_href == NULL) {
00880            info->anchors[info->curr_cnt].a_href = parse_anchortext(str, len);
00881        }
00882        TRACE_HTEX((stderr, "adding HREF %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
00883     }
00884     else if (type == A_HREF_URL) {
00885        if (info->anchors[info->curr_cnt].a_href == NULL) {
00886            info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
00887            strncpy(info->anchors[info->curr_cnt].a_href, str, len);
00888            info->anchors[info->curr_cnt].a_href[len] = '\0';
00889        }
00890        TRACE_HTEX((stderr, "adding HREF_URL %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
00891     }
00892     else if (type == A_HDVIPS_INTERNAL) {
00893        if (info->anchors[info->curr_cnt].a_href == NULL) {
00894            /* dynamically add a `#' prefix */
00895            if (str[0] != '#') {
00896               info->anchors[info->curr_cnt].a_href = xmalloc(len + 2);
00897               strcpy(info->anchors[info->curr_cnt].a_href, "#");
00898               strncat(info->anchors[info->curr_cnt].a_href, str, len);
00899               info->anchors[info->curr_cnt].a_href[len + 1] = '\0';
00900            }
00901            else {
00902               info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
00903               strncpy(info->anchors[info->curr_cnt].a_href, str, len);
00904               info->anchors[info->curr_cnt].a_href[len] = '\0';
00905            }
00906        }
00907        TRACE_HTEX((stderr, "adding HREF_URL %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
00908     }
00909     else if (type == A_HREF_FILE) {
00910        if (info->anchors[info->curr_cnt].a_href == NULL) {
00911            /* dynamically add a `file:' extension */
00912            if (memcmp(str, "file:", strlen("file:")) == 0) {
00913               info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
00914               strncpy(info->anchors[info->curr_cnt].a_href, str, len);
00915               info->anchors[info->curr_cnt].a_href[len] = '\0';
00916            }
00917            else {
00918               info->anchors[info->curr_cnt].a_href = xmalloc(len + strlen("file:") + 1);
00919               strcpy(info->anchors[info->curr_cnt].a_href, "file:");
00920               strncat(info->anchors[info->curr_cnt].a_href, str, len);
00921               info->anchors[info->curr_cnt].a_href[len + strlen("file:")] = '\0';
00922            }
00923        }
00924        TRACE_HTEX((stderr, "adding HREF_FILE %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
00925     }
00926     else if (type == A_NAME) {
00927        if (info->anchors[info->curr_cnt].a_name == NULL) {
00928            info->anchors[info->curr_cnt].a_name = parse_anchortext(str, len);
00929        }
00930        TRACE_HTEX((stderr, "adding NAME %d: %s", info->curr_cnt, info->anchors[info->curr_cnt].a_name));
00931     }
00932     else if (type == A_HDVIPS_HREF) {
00933        if (info->anchors[info->curr_cnt].a_href == NULL) {
00934            info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
00935            strncpy(info->anchors[info->curr_cnt].a_href, str, len);
00936            info->anchors[info->curr_cnt].a_href[len] = '\0';
00937        }
00938        TRACE_HTEX((stderr, "adding HDVIPS_HREF %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_name));
00939     }
00940     else if (type == A_HDVIPS_NAME) {
00941        if (info->anchors[info->curr_cnt].a_name == NULL) {
00942            info->anchors[info->curr_cnt].a_name = xmalloc(len + 1);
00943            strncpy(info->anchors[info->curr_cnt].a_name, str, len);
00944            info->anchors[info->curr_cnt].a_name[len] = '\0';
00945        }
00946        TRACE_HTEX((stderr, "adding HDVIPS_NAME %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_name));
00947     }
00948 }
00949 
00950 static void
00951 set_anchor_size(struct htex_page_info *info, int index,
00952               int ulx, int uly, int lrx, int lry)
00953 {
00954     struct anchor_info *anchor;
00955     
00956     ASSERT(info->anchors != NULL, "info->anchors should have been allocated before");
00957     ASSERT(index < info->curr_cnt, "info too small");
00958     
00959     anchor = &(info->anchors[index]);
00960 
00961     anchor->ulx = ulx;
00962     anchor->uly = uly;
00963     anchor->lrx = lrx;
00964     anchor->lry = lry;
00965 }
00966 
00967 void
00968 htex_set_objecttype(htexObjectT type)
00969 {
00970     htex_page_info.anchors[htex_page_info.curr_cnt - 1].object_type = type;
00971 }
00972 
00973 void
00974 htex_set_anchorsize(int ulx, int uly, int lrx, int lry)
00975 {
00976     set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1,
00977                   ulx, uly, lrx, lry);
00978 }
00979 
00980 static void
00981 enlarge_anchor_size(struct htex_page_info *info, int index,
00982                   int ulx, int uly, int lrx, int lry)
00983 {
00984     struct anchor_info *anchor;
00985     
00986     ASSERT(info->anchors != NULL, "info->anchors should have been allocated before");
00987     ASSERT(index < info->curr_cnt, "info too small");
00988 
00989 /*     fprintf(stderr, "enlarging anchor at index %d; %s\n", index, info->anchors[index].a_href); */
00990     anchor = &(info->anchors[index]);
00991 
00992     if (ulx < anchor->ulx) {
00993        anchor->ulx = ulx;
00994     }
00995     if (uly < anchor->uly) {
00996        anchor->uly = uly;
00997     }
00998     if (lrx > anchor->lrx) {
00999        anchor->lrx = lrx;
01000     }
01001     /* set lry only for first character, since this will be used
01002        to position underline */
01003 /*      if (lry > anchor->lry && anchor->lry == 0) { */
01004     if (lry > anchor->lry) {
01005        anchor->lry = lry;
01006     }
01007 }
01008 
01009 static void
01010 reset_page_info(struct htex_page_info *info, int pageno, Boolean force_init)
01011 {
01012     int i, dummy;
01013 
01014     if (force_init || pageno != info->page) {
01015 #if 0
01016        fprintf(stderr, "%d or %d != %d: resetting anchorinfo for page %d (%d anchors)\n",
01017               force_init, pageno, info->page, current_page, info->curr_cnt);
01018 #endif
01019        ASSERT(info->curr_cnt == 0 || info->anchors != NULL, "inconsistency in info structure");
01020        /* re-initialize all values */
01021        for (i = 0; i < info->curr_cnt; i++) {
01022            TRACE_HTEX((stderr, "----- resetting info for anchor %d", i));
01023            FREE(info->anchors[i].a_name);
01024            FREE(info->anchors[i].a_href);
01025            info->anchors[i].a_name = NULL;
01026            info->anchors[i].a_href = NULL;
01027            info->anchors[i].ulx = INT_MAX;
01028            info->anchors[i].uly = INT_MAX;
01029            info->anchors[i].lrx = 0;
01030            info->anchors[i].lry = 0;
01031            info->anchors[i].object_type = HTEX_TEXT;
01032            info->anchors[i].prev_wrapped = -1;
01033            info->anchors[i].next_wrapped = -1;
01034        }
01035     }
01036     if (pageno != info->page) { /* reset info */
01037        free(info->anchors);
01038        info->anchors = NULL;
01039        info->tot_cnt = 0;
01040     }
01041     TRACE_HTEX((stderr, "---------------- setting curr_cnt to 0, and emptying stack"));
01042     info->page = pageno;
01043     info->curr_cnt = 0;
01044     info->have_wrapped = -1;
01045     while (stack.depth > 0)
01046        pop_stack(&stack, &dummy);
01047 }
01048 
01049 /*
01050  * htex_initpage does what's neccessary at the beginning of a page:
01051  * - re-initialize geometry info if file or page size has changed (according to size_changed),
01052  * - re-initialize visited links info if file has changed (according to new_dvi_file),
01053  * - take care of mismatched anchors at the beginning of the page.
01054  */
01055 void
01056 htex_initpage(Boolean new_dvi_file, Boolean size_changed, int pageno)
01057 {
01058     reset_page_info(&htex_page_info, pageno, size_changed | new_dvi_file);
01059     init_visited_links(&visited_links, total_pages, new_dvi_file);
01060 
01061 #if 0
01062     show_visited(&visited_links, pageno);
01063 #endif
01064     
01065     if (pageno > 0
01066        && (int)m_prescan_info.pagelist_len > pageno /* pagelist_len will be 0 for file with no hyperlinks */
01067        && m_prescan_info.pagelist[pageno - 1] != NULL) {
01068        add_anchor(&htex_page_info, A_HREF,
01069                  m_prescan_info.pagelist[pageno - 1],
01070                  strlen(m_prescan_info.pagelist[pageno - 1]),
01071                  pageno, NULL);
01072        htex_page_info.curr_cnt++;
01073        MYTRACE((stderr, "++++++++++ mismatched anchor text (at %d): |%s|",
01074               htex_page_info.curr_cnt - 1, m_prescan_info.pagelist[pageno - 1]));
01075        /*     x_pos_bak = y_pos_bak = INT_MAX; */
01076        x_pos_bak = y_pos_bak = INT_MAX;
01077        set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, 0, 0, 1, 1);
01078        htex_page_info.have_wrapped = htex_page_info.curr_cnt - 1;
01079        if (bg_current != NULL) { /* only if it has been initialized by redraw_page in events.c */
01080            push_colorspecial();
01081        }
01082 /*     else { */
01083 /*         fprintf(stderr, "----------------------- NOT pushing color, waiting ...\n"); */
01084 /*     } */
01085        push_stack(&stack, A_HREF, htex_page_info.curr_cnt - 1);
01086     }
01087 }
01088 
01089 static void
01090 hdvips_add_anchor(struct htex_page_info *info, htexAnchorT type, const char *str)
01091 {
01092     char *ptr;
01093     if ((ptr = strchr(str, ')')) != NULL) {
01094        int curr_cnt_bak = info->curr_cnt;
01095        /* overwrite previously created dummy/wrapped anchors */
01096        ASSERT(info->curr_cnt > 0, "hdvips_add_anchor must be called after add_anchor()!");
01097 
01098        while (info->curr_cnt > 0
01099               && info->anchors[info->curr_cnt - 1].a_href != NULL
01100               && (strcmp(info->anchors[info->curr_cnt - 1].a_href, "__WRAPPED__") == 0
01101                  || strcmp(info->anchors[info->curr_cnt - 1].a_href, "__DUMMY__") == 0)) {
01102            info->curr_cnt--;
01103            free(info->anchors[info->curr_cnt].a_href);
01104            free(info->anchors[info->curr_cnt].a_name);
01105            info->anchors[info->curr_cnt].a_href = info->anchors[info->curr_cnt].a_name = NULL;
01106            add_anchor(info, type, str, ptr - str, 0, NULL);
01107        }
01108        info->curr_cnt = curr_cnt_bak;
01109     }
01110     else {
01111        MYTRACE((stderr, "Xdvi warning: skipping malformed hdvips link `%s'", str));
01112     }
01113 }
01114 
01115 /*
01116  * htex_reset_page: invoked when drawing was interrupted,
01117  * assumes that htex_page_info is in an inconsistent state and resets it.
01118  */
01119 void
01120 htex_reset_page(int pageno)
01121 {
01122     reset_page_info(&htex_page_info, pageno, True);
01123 }
01124 
01125 /* returns True iff inside a href tag */
01126 Boolean
01127 htex_scan_anchor(const char *cp, size_t len)
01128 {
01129     char *ptr;
01130     
01131     if (memicmp(cp, "</a>", 4) == 0) { /* end tag */
01132        /*     struct anchor_info *anchor; */
01133        int num;
01134        htexAnchorT type = pop_stack(&stack, &num);
01135        if (type == A_MISMATCH)
01136            return False;
01137 
01138        /* ASSERT(htex_page_info.curr_cnt - 1 >= 0, "Index out of range"); */
01139        /* anchor = &(htex_page_info.anchors[htex_page_info.curr_cnt - 1]); */
01140 
01141        /* reset color if needed */
01142        if (resource.link_style > 1 && resource.link_color != NULL && htex_is_href(type))
01143            pop_colorspecial();
01144     }
01145     else if (memicmp(cp, "<a ", 3) == 0) {
01146        m_href_type = HYPERTEX_LINK;
01147        cp += 3; /* skip over `<a ' part */
01148        len -= 3;
01149        TRACE_HTEX((stderr, "scan_anchor: |%s|", cp));
01150        if (memicmp(cp, "name", 4) == 0) {
01151            add_anchor(&htex_page_info, A_NAME, cp + 4, len - 4, 0, NULL);
01152            htex_page_info.curr_cnt++;
01153            push_stack(&stack, A_NAME, htex_page_info.curr_cnt);
01154        }
01155        else if (memicmp(cp, "href", 4) == 0) {
01156            add_anchor(&htex_page_info, A_HREF, cp + 4, len - 4, 0, NULL);
01157            htex_page_info.curr_cnt++;
01158            push_stack(&stack, A_HREF, htex_page_info.curr_cnt);
01159 
01160            /* MYTRACE((stderr, "NON-WRAPPED ANCHOR at %d,%d!", PXL_H, PXL_V)); */
01161            push_colorspecial();
01162            x_pos_bak = PXL_H;
01163            y_pos_bak = PXL_V;
01164 /*         set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, PXL_H, PXL_V, PXL_H + 10, PXL_V + 10); */
01165        }
01166        else {
01167            XDVI_WARNING((stderr, "Skipping unimplemented htex special `%s'", cp));
01168            push_stack(&stack, A_OTHER, htex_page_info.curr_cnt);
01169        }
01170     }
01171     else if (memcmp(cp, "H.S end", strlen("H.S end")) == 0) {
01172        /* start of anchor, link or rect. We just assume that the link
01173           target will have length 0, so when we see text between H.S
01174           and H.R, assume that it's the link text.
01175        */
01176        m_href_type = HDVIPS_LINK;
01177        /* add dummy */
01178        add_anchor(&htex_page_info, A_HDVIPS_HREF, "__DUMMY__", strlen("__DUMMY__"), 0, NULL);
01179        htex_page_info.curr_cnt++;
01180        push_stack(&stack, A_HREF, htex_page_info.curr_cnt - 1);
01181        push_colorspecial();
01182        x_pos_bak = PXL_H;
01183        y_pos_bak = PXL_V;
01184        return True;
01185     }
01186     else if (memcmp(cp, "H.R end", strlen("H.R end")) == 0 /* end of rect */
01187             || memcmp(cp, "H.A end", strlen("H.A end")) == 0 /* end of anchor */
01188             || memcmp(cp, "H.L end", strlen("H.L end")) == 0 /* end of link */
01189             ) {
01190        int num;
01191        htexAnchorT type = pop_stack(&stack, &num);
01192        if (type == A_MISMATCH)
01193            return False;
01194        if (resource.link_style > 1 && resource.link_color != NULL)
01195            pop_colorspecial();
01196        return False;
01197     }
01198     /* add anchor texts for hdvips links */
01199     else if (memcmp(cp, "/A", 2) == 0) { /* possibly an hdvips external link */
01200 /*     fprintf(stderr, "+++++ EXT: |%s|\n", cp); */
01201        if ((ptr = strstr(cp + 2, "/GoToR")) != NULL /* external file */
01202            && (ptr = strchr(ptr, '(')) != NULL) {
01203            hdvips_add_anchor(&htex_page_info, A_HREF_FILE, ptr + 1);
01204        }
01205        else if ((ptr = strstr(cp + 2, "/URI")) != NULL /* URL */
01206                && (ptr = strchr(ptr, '(')) != NULL) {
01207            hdvips_add_anchor(&htex_page_info, A_HREF_URL, ptr + 1);
01208        }
01209        return False;
01210     }
01211     else if (memcmp(cp, "/L", 2) == 0) { /* possibly an hdvips internal link */
01212        if ((ptr = strstr(cp + 2, "/Dest")) != NULL
01213            && (ptr = strchr(ptr, '(')) != NULL) {
01214            hdvips_add_anchor(&htex_page_info, A_HDVIPS_INTERNAL, ptr + 1);
01215        }
01216        return False;
01217     }
01218     else if (memcmp(cp, "/V", 2) == 0) { /* possibly an hdvips anchor */
01219        if ((ptr = strstr(cp + 2, "/Dest")) != NULL
01220            && (ptr = strchr(ptr, '(')) != NULL) {
01221            hdvips_add_anchor(&htex_page_info, A_HDVIPS_NAME, ptr + 1);
01222        }
01223        return False;
01224     }
01225     else { /* not a start tag */
01226        MYTRACE((stderr, "Xdvi warning: skipping malformed hyperref special `%s'", cp));
01227     }
01228     return get_href_depth(&stack);
01229 }
01230 
01231 void
01232 htex_record_position(int ulx, int uly, int w, int h)
01233 {
01234     int lrx, lry;
01235     int y_delta, x_delta;
01236 
01237     if (!INSIDE_MANE_WIN) /* this would give wrong values */
01238        return;
01239 
01240     lrx = ulx + w;
01241     lry = uly + h;
01242 
01243     y_delta = lry - uly;
01244     x_delta = lrx - ulx;
01245 
01246     /* heuristics for creating new bounding box at what might be linebreaks */
01247     if (lrx < x_pos_bak /* ordinary linebreak */
01248        || lry + y_delta < y_pos_bak) { /* column break (from bottom of 1st to top 2nd column) */
01249        htexAnchorT type;
01250        int idx, prev, curr;
01251        /* get anchor index of topmost stack item, to find the matching open tag */
01252        if ((type = peek_stack(&stack, &idx)) == A_NONE) {
01253 /*         fprintf(stderr, "!!!!!!!!!!!! couldn't find opening tag for this wrapped link!"); */
01254            return;
01255        }
01256        ASSERT(idx >= 0, "Index out of range");
01257 /*     fprintf(stderr, "wrapped link: index %d, type %d\n", idx, type); */
01258        /* get correct idx */
01259        if (m_href_type == HYPERTEX_LINK) {
01260            while (htex_page_info.anchors[idx].a_href == NULL && idx > 0) {
01261               idx--;
01262            }
01263            if (htex_page_info.anchors[idx].a_href == NULL && idx == 0) {
01264               XDVI_ERROR((stderr, "Couldn't find wrapped anchor for idx %d, page %d!", idx, current_page));
01265               return;
01266            }
01267            add_anchor(&htex_page_info, A_HREF,
01268                      htex_page_info.anchors[idx].a_href,
01269                      strlen(htex_page_info.anchors[idx].a_href),
01270                      0, NULL);
01271        }
01272        else {
01273            add_anchor(&htex_page_info, A_HREF, "__WRAPPED__", strlen("__WRAPPED__"), 0, NULL);
01274        }
01275        htex_page_info.curr_cnt++;
01276        /* add wrapping info */
01277        if (htex_page_info.have_wrapped >= 0) {
01278            /* this is the only case where some other material might have come between
01279               this and the previous anchor */
01280            prev = htex_page_info.have_wrapped;
01281            htex_page_info.have_wrapped = -1;
01282        }
01283        else {
01284            prev = htex_page_info.curr_cnt - 2;
01285        }
01286        curr = htex_page_info.curr_cnt - 1;
01287        ASSERT(prev >= 0, "Index out of range");
01288 
01289 /*     fprintf(stderr, "setting prev to %d, curr to %d\n", prev, curr); */
01290        htex_page_info.anchors[prev].next_wrapped = curr;
01291        htex_page_info.anchors[curr].prev_wrapped = prev;
01292        /* initialize it to cover current character */
01293        set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, ulx, uly, lrx, lry);
01294     }
01295     else {
01296        int prev_idx = htex_page_info.curr_cnt - 1;
01297 
01298        if (prev_idx >= 0) {
01299            enlarge_anchor_size(&htex_page_info, prev_idx, ulx, uly, lrx, lry);
01300        }
01301        else {
01302            MYTRACE((stderr, "!!!!!!!!!!!! Bug? prev_idx < 0"));
01303        }
01304     }
01305     x_pos_bak = lrx;
01306     y_pos_bak = uly;
01307 }
01308 
01309 #if 0
01310 static void
01311 show_history(void)
01312 {
01313     struct dl_list *ptr = htex_history;
01314     struct history_info *info = ptr->item;
01315     int i;
01316     
01317     fprintf(stderr, "**************** history: %s, %d, %s\n", info->filename, info->page, info->anchor);
01318     for (; ptr->prev != NULL; ptr = ptr->prev) { ; }
01319     for (i = 0; ptr != NULL; ptr = ptr->next, i++) {
01320        info = ptr->item;
01321        fprintf(stderr, "elem %d: %s, %d, %s\n", i, info->filename, info->page, info->anchor);
01322        MYTRACE((stderr, "elem: %p, prev: %p, next: %p", (void *)ptr, (void *)ptr->prev, (void *)ptr->next));
01323     }
01324 }
01325 #endif
01326 
01327 /* check for names like foo/bar.dvi#link, return copy of
01328    link truncated at the `#' if found (NULL else), and
01329    save `link' into resource.anchor_pos for later use.
01330  */
01331 static char *
01332 check_relative_name(const char *link)
01333 {
01334     char *ptr;
01335     TRACE_HTEX((stderr, "check_relative_name: |%s|", link));
01336     if ((ptr = strchr(link, '#')) != NULL
01337        && ptr > link + 4
01338        && (memicmp(ptr - 4, ".dvi", 4) == 0)) {
01339        char *new_link = xstrdup(link);
01340        new_link[ptr - link] = '\0'; /* truncate the copy */
01341        free(g_anchor_pos);
01342        g_anchor_pos = xstrdup(ptr + 1);
01343        g_anchor_len = strlen(g_anchor_pos);
01344        
01345        return new_link;
01346     }
01347     else { /* reset g_anchor_pos */
01348        free(g_anchor_pos);
01349        g_anchor_pos = NULL;
01350     }
01351     return NULL;
01352 }
01353 
01354 static void
01355 htex_update_toolbar_navigation(void)
01356 {
01357 #if 0
01358     show_history();
01359 #endif
01360 #if defined(MOTIF) && HAVE_XPM
01361     tb_set_htex_back_sensitivity(htex_history->prev != NULL);
01362     tb_set_htex_forward_sensitivity(htex_history->next != NULL);
01363 #endif
01364 }
01365 
01366 void
01367 htex_back(void)
01368 {
01369     struct history_info *info;
01370 
01371     if (htex_history == NULL) {
01372        XBell(DISP, 0);
01373        statusline_print(STATUS_SHORT, "Hyperref history is empty");
01374        return;
01375     }
01376     info = htex_history->item;
01377 
01378     if (htex_history->prev == NULL) {
01379        XBell(DISP, 0);
01380        statusline_print(STATUS_SHORT, "At begin of history");
01381        htex_update_toolbar_navigation();
01382        return;
01383     }
01384 
01385     if (strcmp(info->filename, globals.dvi_name) != 0) { /* new filename different */
01386        Boolean tried_dvi_ext = False;
01387        char *new_dvi_name = NULL;
01388        
01389        if ((new_dvi_name = open_dvi_file_wrapper(info->filename, False, False,
01390                                             &tried_dvi_ext, False))
01391            == NULL) {
01392            
01393            statusline_print(STATUS_MEDIUM, "Re-opening file \"%s\" failed!\n", info->filename);
01394            return;
01395        }
01396        else {
01397            set_dvi_name(new_dvi_name);
01398            globals.ev.flags |= EV_NEWDOC;
01399            globals.ev.flags |= EV_PAGEHIST_INSERT;
01400 /*         statusline_print(STATUS_MEDIUM, "Back to file: \"%s\"", globals.dvi_name); */
01401        }
01402     }
01403     else {
01404        page_history_insert(info->page);
01405     }
01406     goto_page(info->page, resource.keep_flag ? NULL : home, False);
01407 
01408     MYTRACE((stderr, "curr page now: %d", current_page));
01409     htex_history = htex_history->prev;
01410 
01411     info = htex_history->item;
01412 
01413 #if 0
01414     MYTRACE((stderr, "------ before skipping: %d, %s", info->page, info->anchor));
01415     show_history();
01416     while (info->page == current_page && htex_history->prev != NULL) {
01417        /* skip identical locations */
01418        MYTRACE((stderr, "+++++++ skipping identical page %d, %s", current_page, info->anchor));
01419        htex_history = htex_history->prev;
01420        info = htex_history->item;
01421     }
01422     MYTRACE((stderr, "------ after skipping: %d, %s", info->page, info->anchor));
01423 #endif
01424 
01425     htex_update_toolbar_navigation();
01426 }
01427 
01428 
01429 void
01430 htex_forward(void)
01431 {
01432     struct history_info *info;
01433     char *link;
01434     
01435     if (htex_history == NULL) {
01436        XBell(DISP, 0);
01437        statusline_print(STATUS_SHORT, "Hyperref history is empty");
01438        return;
01439     }
01440     if (htex_history->next == NULL) {
01441        XBell(DISP, 0);
01442        statusline_print(STATUS_SHORT, "At end of history");
01443        return;
01444     }
01445     
01446     htex_history = htex_history->next;
01447     info = htex_history->item;
01448     link = info->anchor;
01449     /* go there */
01450     if (*link == '#') { /* it's a relative link */
01451        MYTRACE((stderr, "XXXXXXXX %s:%d: setting anchor to |%s|+1", __FILE__, __LINE__, info->anchor));
01452        free(g_anchor_pos);
01453        g_anchor_pos = xstrdup(info->anchor + 1);
01454        g_anchor_len = strlen(g_anchor_pos);
01455        globals.ev.flags |= EV_ANCHOR;
01456     }
01457     else { /* it's an absolute link */
01458        char *new_linkname = NULL;
01459        char *new_dvi_name = NULL;
01460        Boolean tried_dvi_ext = False;
01461        
01462        if ((new_linkname = check_relative_name(link)) != NULL)
01463            link = new_linkname;
01464 
01465        if ((new_dvi_name = open_dvi_file_wrapper(link, False, False,
01466                                             &tried_dvi_ext, False)) == NULL) {
01467            statusline_print(STATUS_MEDIUM, "Re-opening file \"%s\" failed!\n", info->filename);
01468            free(new_linkname);
01469            return;
01470        }
01471        else {
01472            set_dvi_name(new_dvi_name);
01473            
01474            globals.ev.flags |= EV_NEWDOC;
01475            goto_page(0, resource.keep_flag ? NULL : home, False);
01476            globals.ev.flags |= EV_PAGEHIST_INSERT;
01477 
01478            if (g_anchor_pos != NULL)
01479               globals.ev.flags |= EV_ANCHOR;
01480 
01481 /*         statusline_print(STATUS_MEDIUM, "Loaded file: \"%s\"", globals.dvi_name); */
01482            free(new_linkname);
01483        }
01484     }
01485 
01486     htex_update_toolbar_navigation();
01487 }
01488 
01489 /* save this anchor in jump_history, current location in goback_history,
01490    and update Motif toolbar icons
01491 */
01492 static void
01493 history_save_anchor(const char *anchor, int current_page, const char *dvi_name)
01494 {
01495     struct history_info *item;
01496 
01497     if (htex_history == NULL) {
01498        /* Insert dummy start elem. We need this to prevent dropping
01499           off from the begin of the list, since otherwise a 1-elem
01500           list couldn't encode the information whether we can still go
01501           back. This way it will point to the dummy elem when there's
01502           no way to go back.  */
01503        struct history_info *start_marker = XMALLOC(start_marker, sizeof *start_marker);
01504        start_marker->anchor = NULL;
01505        start_marker->filename = NULL;
01506        start_marker->page = -1;
01507        htex_history = dl_list_insert(htex_history, start_marker);
01508     }
01509     item = XMALLOC(item, sizeof *item);
01510     item->anchor = xstrdup(anchor);
01511     item->filename = xstrdup(dvi_name);
01512     item->page = current_page;
01513     htex_history = dl_list_truncate(dl_list_insert(htex_history, item));
01514 
01515 #if 0
01516     MYTRACE((stderr, "==================== after inserting "));
01517     show_history();
01518 #endif
01519     
01520     htex_update_toolbar_navigation();
01521 
01522 }
01523 
01524 /*
01525  * Return anchor string at coordinates x, y, and anchor count in 3rd argument,
01526  * or NULL if there's no anchor at these coordinates.
01527 */
01528 static char *
01529 get_anchor_at_index(int x, int y, int *count)
01530 {
01531     int i;
01532 
01533     for (i = 0; i < htex_page_info.curr_cnt; i++) {
01534        struct anchor_info anchor = htex_page_info.anchors[i];
01535 /*     fprintf(stderr, "getting anchor at index %d; %s\n", i, anchor.a_href); */
01536        if (anchor.a_href == NULL) {
01537            continue;
01538        }
01539        if (anchor.lry + 2 > y && anchor.uly - 1 < y /* allow some vertical fuzz */
01540            && anchor.lrx > x && anchor.ulx < x) {
01541            *count = i;
01542            return anchor.a_href;
01543        }
01544     }
01545     *count = -1;
01546     return NULL;
01547 }
01548 
01549 
01550 /*
01551   generate exposure events for anchor at index idx, and all anchors wrapping to/from it,
01552   so that they will be redrawn in `visited' color
01553 */
01554 static void
01555 redraw_anchors(int idx)
01556 {
01557     struct anchor_info anchor = htex_page_info.anchors[idx];
01558     int other;
01559     int i = idx;
01560 #define DO_EXPOSE(lx,ly,rx,ry) (clearexpose(&mane, lx - 20, ly - 3, rx - lx + 26, ry - ly + 6))    
01561 
01562     /* HACK ALERT: This is a workaround for a coloring problem with wrapped anchors
01563        (see regression/href/002/url-wrap-test2.dvi):
01564        We can't change the color on-the-fly for a wrapped anchor, since the special
01565        is pushed only at the opening tag. So for wrapped anchors, the visited colour
01566        wouldn't be set correctly. Redrawing the entire page ensures this (but it's ugly).
01567        Currently this hack overrides the algorithm below for all anchors that have a
01568        `prev_wrapped' pointer.
01569      */
01570     if (htex_page_info.anchors[i].prev_wrapped != -1) {
01571        globals.ev.flags |= EV_NEWPAGE;
01572        XSync(DISP, False);
01573        return;
01574     }
01575 
01576     DO_EXPOSE(anchor.ulx, anchor.uly, anchor.lrx, anchor.lry);
01577 
01578     /* previous parts of wrapped anchors: */
01579     for (i = idx;
01580         i >= 0 && (other = htex_page_info.anchors[i].prev_wrapped) != -1;
01581         i--) {
01582        DO_EXPOSE(htex_page_info.anchors[other].ulx, htex_page_info.anchors[other].uly,
01583                 htex_page_info.anchors[other].lrx, htex_page_info.anchors[other].lry);
01584     }
01585 
01586     /* later parts of wrapped anchors: */
01587     for (i = idx;
01588         i < htex_page_info.curr_cnt && (other = htex_page_info.anchors[i].next_wrapped) != -1;
01589         i++) {
01590        DO_EXPOSE(htex_page_info.anchors[other].ulx, htex_page_info.anchors[other].uly,
01591                 htex_page_info.anchors[other].lrx, htex_page_info.anchors[other].lry);
01592     }
01593 #undef DO_EXPOSE
01594 }
01595 
01596 
01597 /*
01598  * Invoked when clicking on a link.
01599  * newwindow == True means open new window,
01600  * newwindow == False means jump to link in current window.
01601  * With newwindow == True, don't update the toolbar navigation icons, since there's nothing to go back.
01602  */
01603 Boolean
01604 htex_handleref(int x, int y, Boolean newwindow)
01605 {
01606     char *link;
01607     int link_num;
01608 
01609 /*      fprintf(stderr, "---------- htex_handleref!\n"); */
01610     if (!(globals.cursor.flags & CURSOR_LINK))
01611        /* When no link cursor is shown, clicking shouldn't jump to link */
01612        return False;
01613     
01614     if ((link = get_anchor_at_index(x, y, &link_num)) != NULL) {
01615        set_visited(&visited_links, current_page, link_num);
01616 
01617        if (*link == '#') { /* it's a relative link */
01618            if (newwindow) {
01619               launch_xdvi(globals.dvi_name, link + 1);
01620               redraw_anchors(link_num);
01621               return True;
01622            }
01623            else {
01624               history_save_anchor(link, current_page, globals.dvi_name);
01625               free(g_anchor_pos);
01626               g_anchor_pos = xstrdup(link + 1);
01627               g_anchor_len = strlen(g_anchor_pos);
01628               globals.ev.flags |= EV_ANCHOR;
01629               redraw_anchors(link_num);
01630               return True;
01631            }
01632        }
01633        else { /* it's an absolute link */
01634            char *new_dvi_name = NULL;
01635            int pageno_bak = current_page;
01636            char *new_linkname = NULL;
01637            char *orig_link = link;
01638            Boolean tried_dvi_ext = False;
01639            
01640            MYTRACE((stderr, "OLD FILE: |%s|", globals.dvi_name));
01641 
01642            if ((new_linkname = check_relative_name(link)) != NULL)
01643               link = new_linkname;
01644            
01645            if ((new_dvi_name = open_dvi_file_wrapper(link, False, newwindow,
01646                                                 &tried_dvi_ext, False)) != NULL) {
01647               /* only save link in history if opening succeeds */
01648               history_save_anchor(orig_link, pageno_bak, globals.dvi_name);
01649               set_dvi_name(new_dvi_name);
01650               if (!newwindow) {
01651                   globals.ev.flags |= EV_NEWDOC;
01652                   goto_page(0, resource.keep_flag ? NULL : home, False);
01653                   globals.ev.flags |= EV_PAGEHIST_INSERT;
01654                   
01655                   MYTRACE((stderr, "NEW FILE: |%s|", globals.dvi_name));
01656                   if (g_anchor_pos != NULL)
01657                      globals.ev.flags |= EV_ANCHOR;
01658 /*                statusline_print(STATUS_MEDIUM, "Loaded file: \"%s\"", dvi_name); */
01659               }
01660               else {
01661                   redraw_anchors(link_num);
01662               }
01663            }
01664            free(new_linkname);
01665            redraw_anchors(link_num);
01666            return True;
01667        }
01668     }
01669     return False;
01670 }
01671 
01672 /* change cursor and display anchor text in statusline */
01673 void
01674 htex_displayanchor(int x, int y)
01675 {
01676     static int id_bak = -1;
01677     static int pageno_bak = -1;
01678     int id_curr;
01679     char *anchor_text;
01680 
01681     anchor_text = get_anchor_at_index(x, y, &id_curr);
01682 
01683     if (anchor_text != NULL) { /* found an anchor */
01684        if (!(globals.cursor.flags & CURSOR_LINK) && !dragging_text_selection()) {
01685            globals.cursor.flags |= CURSOR_LINK;
01686            globals.ev.flags |= EV_CURSOR;
01687        }
01688        /* to prevent flicker, print only when it differs from previous one,
01689           i.e. when it either has a different ID or is on a different page: */
01690        if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0
01691            && (id_curr != id_bak || current_page != pageno_bak)) {
01692            statusline_print(STATUS_FOREVER, "%s", anchor_text);
01693            id_bak = id_curr;
01694            pageno_bak = current_page;
01695        }
01696     }
01697     else { /* not over anchor text, reset cursor and statusline */
01698        if (globals.cursor.flags & CURSOR_LINK) {
01699            globals.cursor.flags &= ~CURSOR_LINK;
01700            globals.ev.flags |= EV_CURSOR;
01701        }
01702 
01703        if (id_bak != -1 && (resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) {
01704            statusline_clear();
01705            id_bak = -1;
01706        }
01707     }
01708 }
01709 
01710 /*
01711  * Invoked from all commands that change the shrink factor;
01712  * forces re-computation of the anchor markers.
01713 */
01714 void
01715 htex_resize_page(void)
01716 {
01717     htex_initpage(False, True, current_page);
01718 }
01719 
01720 /*
01721  * Invoked from all commands that reread the DVI file;
01722  * resets all link infos.
01723  * Note: visited_links and anchor stack data
01724  * are preserved over files, so we don't need to free() them.
01725  */
01726 void
01727 htex_reinit(void)
01728 {
01729     htex_prescan_reset();
01730     htex_initpage(True, True, current_page);
01731 }
01732 
01733 /*
01734   Underline/highlight anchors if the appropriate resource is set.
01735   Since this is done after the entire page is parsed, we can't use
01736   the checks for fg_current and do_color_change(), but need to
01737   use a separate GC instead.
01738  */
01739 void
01740 htex_draw_anchormarkers(void)
01741 {
01742     int i;
01743     int rule_height = (globals.page.h / 1000.0);
01744     XRectangle rect = { -1, -1, 0, 0 };
01745     
01746     if (rule_height == 0)
01747        rule_height = 1;
01748 
01749     if (current_page == g_anchormarker.page
01750        && strcmp(globals.dvi_name, g_anchormarker.filename) == 0
01751        &&  g_anchormarker.y_pos > 0) {
01752        htex_draw_anchormarker(g_anchormarker.y_pos);
01753     }
01754     
01755     if (resource.link_style == 0 || resource.link_style == 2)
01756        return;
01757     
01758     for (i = 0; i < htex_page_info.curr_cnt; i++) {
01759        struct anchor_info anchor = htex_page_info.anchors[i];
01760 
01761        if (anchor.a_href != NULL) {
01762            int offset;
01763            Boolean visited = False;
01764 
01765 #if 0
01766            XDrawRectangle(DISP, mane.win, globals.gc.high,
01767                         anchor.ulx, anchor.uly,
01768                         anchor.lrx - anchor.ulx, anchor.lry - anchor.uly);
01769                         
01770 #endif
01771            
01772            if (is_visited(&visited_links, current_page, i)) {/*  || wrapped_anchor_is_visited(i)) { */
01773               visited = True;
01774            }
01775            offset = ANCHOR_XTRA_V_BBOX / (double)currwin.shrinkfactor + 0.5;
01776 
01777            TRACE_HTEX((stderr, "UNDERLINE: %d, %s is %s", i, anchor.a_href,
01778                      visited ? "******* visited ****** " : "not visited"));
01779            anchor.ulx -= currwin.base_x;
01780            anchor.lrx -= currwin.base_x;
01781            anchor.lry -= currwin.base_y;
01782            anchor.uly -= currwin.base_y;
01783            
01784            /* BUG ALERT: Don't use put_rule here, since this will cause a segfault
01785             * with the new color code when switching density (#616920)!
01786             */
01787            MYTRACE((stderr, "UNDERLINE: checking if %d is visited: %d!", i, visited));
01788 
01789 /*         if (clip_region_to_rect(&rect)) { */
01790            if (anchor.object_type == HTEX_IMG) {
01791               rect.x = anchor.ulx - 1;
01792               rect.y = anchor.uly - 1;
01793               rect.width = anchor.lrx - anchor.ulx + 2;
01794               rect.height = anchor.lry - anchor.uly + 2;
01795               if (clip_region_to_rect(&rect)) {
01796                   XDrawRectangle(DISP, mane.win,
01797                                visited ? globals.gc.visited_linkcolor : globals.gc.linkcolor,
01798                                rect.x, rect.y,
01799                                rect.width, rect.height);
01800               }
01801            }
01802            else {
01803               rect.x = anchor.ulx - 1;
01804               rect.y = anchor.lry + offset;
01805               rect.width = anchor.lrx - anchor.ulx + 2;
01806               rect.height = rule_height;
01807               if (clip_region_to_rect(&rect)) {
01808                   XFillRectangle(DISP, mane.win,
01809                                visited ? globals.gc.visited_linkcolor : globals.gc.linkcolor,
01810                                rect.x, rect.y,
01811                                rect.width, rect.height);
01812               }
01813            }
01814        }
01815     }
01816 }
01817 
01818 void
01819 launch_xdvi(const char *filename, const char *anchor_name)
01820 {
01821 #define ARG_LEN 32
01822     int i = 0;
01823     char *argv[ARG_LEN];
01824     char *shrink_arg = NULL;
01825 
01826     ASSERT(filename != NULL, "filename argument to launch_xdvi() mustn't be NULL");
01827 
01828     argv[i++] = program_invocation_name;
01829     /* FIXME: there's something broken with this and invoking xdvi.bin.
01830        To reproduce the problem, invoke from the shell:
01831        xdvi.bin -geometry 829x1172 /usr/share/texmf/doc/programs/kpathsea.dvi
01832        this will run at 300dpi, i.e. ignore an .Xdefaults setting as opposed to:
01833        xdvi.bin /usr/share/texmf/doc/programs/kpathsea.dvi
01834 
01835        Also, how about the other command-line switches that might have
01836        been passed to the parent instance? How about things that have been changed
01837        at run-time, like shrink factor - should they be converted to command-line
01838        options?
01839     */
01840 
01841     argv[i++] = "-name";
01842     argv[i++] = "xdvi";
01843 
01844     /* start the new instance with the same debug flags as the current instance */
01845     if (globals.debug != 0) {
01846        char debug_flag[128];
01847        SNPRINTF(debug_flag, 128, "%lu", globals.debug);
01848        argv[i++] = "-debug";
01849        argv[i++] = debug_flag;
01850     }
01851     
01852     if (anchor_name != NULL) {
01853        argv[i++] = "-anchorposition";
01854        argv[i++] = (char *)anchor_name;
01855     }
01856 
01857     argv[i++] = "-s";
01858     shrink_arg = XMALLOC(shrink_arg, LENGTH_OF_INT + 1);
01859     sprintf(shrink_arg, "%d", currwin.shrinkfactor);
01860     argv[i++] = shrink_arg;
01861 
01862     argv[i++] = (char *)filename; /* FIXME */
01863     
01864     argv[i++] = NULL;
01865     ASSERT(i <= ARG_LEN, "Too few elements in argv[]");
01866     
01867     if (globals.debug & DBG_HTEX) {
01868        fprintf(stderr, "Invoking:\n");
01869        for (i = 0; argv[i]; i++) {
01870            fprintf(stderr, "%s\n", argv[i]);
01871        }
01872     }
01873 
01874     /*
01875       FIXME: using fork_process here hangs the new xdvi process when it tries
01876       printing to stderr, so we use plain fork/exec (see comments in util.c)
01877     */
01878 #if 0
01879     fork_process(argv[0], False, NULL, NULL, NULL, argv);
01880 #else
01881     {
01882        int pid;
01883        switch (pid = fork()) {
01884        case -1:
01885            perror("fork");
01886        case 0:
01887            execvp(argv[0], argv);
01888            MYTRACE((stderr, "%s: Execution of %s failed.", globals.program_name, argv[0]));
01889            _exit(EXIT_FAILURE);
01890        default:
01891            FREE(shrink_arg);
01892        }
01893     }
01894 #endif
01895 #undef ARG_LEN
01896 }
01897 
01898 #if COPY_TMP_FILE
01899 /* callback to remove temporary file after helper application has terminated.
01900    Currently unused. Note the possible danger of a temp race if the application
01901    does an fclose() on the file before ...
01902 */
01903 static void
01904 remove_temp_file(int status, struct xchild *this)
01905 {
01906     char *pathname = (char *)this->data;
01907     if (WEXITSTATUS(status) == 0) {
01908        unlink(pathname);
01909     }
01910     else {
01911        popup_message(globals.widgets.top_level,
01912                     MSG_WARN, NULL,
01913                     "Warning: Calling `%s' on `%s' terminated with non-zero status (%d)"
01914                     "\n(Not removing `%s')",
01915                     this->name, pathname, status, pathname);
01916     }
01917     free(pathname);
01918 }
01919 #endif
01920 
01921 void
01922 launch_program(const char *filename)
01923 {
01924     const char *format_string = NULL;
01925     char *content_type = NULL;
01926     char *viewer = NULL;
01927     char *argv[4];
01928     struct stat statbuf;
01929     const char *local_filename_sans_prefix;
01930     char *path, *syscmd, *tmp;
01931 #if COPY_TMP_FILE
01932     int tmp_fd;
01933 #endif
01934     const char *ptr;
01935     char *fullpath = NULL;
01936     char canonical_path[MAXPATHLEN + 1];
01937     Boolean needs_terminal;
01938     
01939     TRACE_HTEX((stderr, "launch_program called with |%s|", filename));
01940 
01941     /* is it a local file? */
01942     local_filename_sans_prefix = is_local_file(filename);
01943     if (local_filename_sans_prefix == NULL) { /* not local */
01944        launch_browser(filename);
01945        return;
01946     }
01947 
01948     /* expand the filename if it contains a relative path */
01949     path = find_file(local_filename_sans_prefix, &statbuf, kpse_program_text_format);
01950     if (path != NULL) {
01951        /* fully canonicalize path before passing it to helper applications */
01952        fullpath = REALPATH(path, canonical_path);
01953        if (fullpath == NULL) {
01954            XDVI_WARNING((stderr, "Couldn't canonicalize %s to full path - returning unexpanded.",
01955                        path));
01956            fullpath = path;
01957        }
01958        else
01959            FREE(path);
01960     }
01961     else {
01962        XDVI_WARNING((stderr, "Couldn't find file %s; passing to application unchanged.",
01963                     local_filename_sans_prefix));
01964        /* if it doesn't exist, maybe the user has encoded some magic in the
01965           filename; in that case, pass it to the application unchanged. */
01966        fullpath = (char *)local_filename_sans_prefix;
01967     }
01968 
01969     TRACE_HTEX((stderr, "fullpath: |%s|", fullpath));
01970     content_type = figure_mime_type(fullpath);
01971     ASSERT(content_type != NULL, "figure_mime_type() should have returned a valid type (eventually a fallback)");
01972     
01973     /* make it safe to pass the argument to system() or `sh -c',
01974        by escaping all `dangerous' characters: */
01975     tmp = shell_escape_string(fullpath);
01976     fullpath = tmp;
01977     
01978     if ((viewer = figure_viewer(content_type, &format_string, &needs_terminal, fullpath)) == NULL) {
01979        /* warn user if no application has been found. Help text needs to be constructed at
01980           run-time, since we want the correct content-type in it. */
01981 #if 0
01982        char *errmsg = NULL;
01983 #else
01984        char *errmsg = xstrdup("Please assign an application to the MIME type `");
01985        errmsg = xstrcat(errmsg, content_type);
01986        errmsg = xstrcat(errmsg, "' in your ~/.mailcap file. "
01987                             "E.g. if you want to view the file with netscape, add the following line to your ~/.mailcap:\n");
01988        errmsg = xstrcat(errmsg, content_type);
01989        errmsg = xstrcat(errmsg, "; netscape -raise  -remote 'openURL(%%s,new-window)'\n\n");
01990 #endif
01991        popup_message(globals.widgets.top_level,
01992                     MSG_WARN,
01993                     errmsg,
01994                     "Could not determine an application for the file %s, MIME type `%s'.",
01995                     fullpath, content_type);
01996        /* FIXME: something's wrong with the memory allocation scheme here -
01997           can't free errmsg, it's already free'd inside popup_message() ?? */
01998        /* free(errmsg); */
01999        free(fullpath);
02000        return;
02001     }
02002     
02003 #if COPY_TMP_FILE
02004     /* copy the file to a temporary location before passing it to the
02005        viewer.  This is e.g. how Acroread does it. This should fix the
02006        problem with xdvizilla without -no-rm for DVI files (see comment
02007        in figure_viewer(), mime.c).
02008        SU 2003/10/02: currently I can't reproduce the xdvizilla problem
02009        any more - it seems that for xdvi files, argv[0] will be invoked
02010        anyway? Also, copying the files to tmp may break figure locations
02011        and relative links for DVI files. Suspended this for the time being.
02012     */
02013     tmp = NULL;
02014     if ((tmp_fd = xdvi_temp_fd(&tmp)) == -1) {
02015        XDVI_ERROR((stderr, "couldn't create temporary file; not calling helper application."));
02016        return;
02017     }
02018     TRACE_HTEX((stderr, "copying to temporary location: |%s->%s|", fullpath, tmp));
02019     if (!copy_file(fullpath, tmp)) {
02020        XDVI_ERROR((stderr, "couldn't copy %s to temporary file %s; not invoking helper application.",
02021                   fullpath, tmp));
02022        return;
02023     }
02024     free(fullpath);
02025     fullpath = tmp;
02026 #endif
02027     
02028     if (strlen(format_string) > 0) {
02029        ptr = find_format_str(viewer, format_string);
02030     }
02031     else { /* countrary to RFC 1343, we don't pass stuff via pipes (too bothersome) */
02032        ptr = strchr(viewer, '\0');
02033     }
02034 
02035     if (needs_terminal) {
02036        size_t offset = strlen("xterm -e ");
02037        syscmd = xmalloc(offset + strlen(viewer) + strlen(fullpath) + 1);
02038        strcpy(syscmd, "xterm -e ");
02039        memcpy(syscmd + offset, viewer, ptr - viewer);
02040        strcpy(syscmd + offset + (ptr - viewer), fullpath);
02041        strcpy(syscmd + offset + (ptr - viewer) + strlen(fullpath), ptr + strlen(format_string));
02042        
02043     }
02044     else {
02045        syscmd = xmalloc(strlen(viewer) + strlen(fullpath) + 1);
02046        memcpy(syscmd, viewer, ptr - viewer);
02047        strcpy(syscmd + (ptr - viewer), fullpath);
02048        strcpy(syscmd + (ptr - viewer) + strlen(fullpath), ptr + strlen(format_string));
02049     }
02050 
02051     /*
02052       mailcap(4) says that the mailcap command shall be passed to system(),
02053       so we musn't use fork_process() directly here. Instead, we pass the command
02054       to `/bin/sh -c', but via fork_process so that
02055       
02056       - xdvi doesn't hang if the process doesn't return;
02057       - we can still catch the return value if `sh -c' exits with an error
02058         (e.g. if the viewer command is not found).
02059 
02060       Note however that this doesn't mimick most of POSIX's system() semantics.
02061     */
02062     TRACE_HTEX((stderr, "execv(\"/bin/sh -c %s\")", syscmd));
02063     argv[0] = "/bin/sh";
02064     argv[1] = "-c";
02065     argv[2] = syscmd;
02066     argv[3] = NULL;
02067 #if COPY_TMP_FILE
02068     fork_process("/bin/sh", False, globals.dvi_file.dirname, remove_temp_file, fullpath, argv);
02069 #else
02070     fork_process("/bin/sh", False, globals.dvi_file.dirname, NULL, NULL, argv);
02071 #endif
02072 
02073     FREE(viewer);
02074 #if COPY_TMP_FILE
02075 #else
02076     FREE(fullpath);
02077 #endif
02078     FREE(syscmd);
02079 }
02080 
02081 void
02082 htex_set_anchormarker(int y)
02083 {
02084     Position drawing_x;
02085 
02086     /* erase old marker if it's on the same page as the new marker, after
02087        cancelling the old timeout so that it won't affect the new marker */
02088     if (m_href_timeout_id)
02089        XtRemoveTimeOut(m_href_timeout_id);
02090     htex_erase_anchormarker(NULL, NULL);
02091     XFlush(DISP);
02092     
02093     XtVaGetValues(globals.widgets.draw_widget, XtNx, &drawing_x, NULL);
02094     g_anchormarker.page = current_page;
02095     free(g_anchormarker.filename);
02096     g_anchormarker.filename = xstrdup(globals.dvi_name);
02097     g_anchormarker.y_pos = y;
02098     g_anchormarker.x_pos = DEFAULT_MARKER_X_OFFSET + -drawing_x;
02099     m_href_timeout_id = XtAppAddTimeOut(app, STATUS_SHORT * 1000, htex_erase_anchormarker, (XtPointer)NULL);
02100 }
02101 
02102 static void
02103 htex_draw_anchormarker(int y)
02104 {
02105     int x;
02106     XPoint points[3];
02107 
02108     Position drawing_x, clip_w;
02109 
02110     XtVaGetValues(globals.widgets.clip_widget, XtNwidth, &clip_w, NULL);
02111     XtVaGetValues(globals.widgets.draw_widget, XtNx, &drawing_x, NULL);
02112 
02113     /* compute offset to draw into visible region */
02114     x = DEFAULT_MARKER_X_OFFSET + -drawing_x;
02115 
02116     /* Eventually erase old marker, to avoid `smearing' on horizontal scrolls. */
02117     if (x != g_anchormarker.x_pos) {
02118        clearexpose(&mane, g_anchormarker.x_pos - 1, y - 3, 20, 10);
02119     }
02120     g_anchormarker.x_pos = x;
02121 
02122     points[0].x = x + 10;
02123     points[1].x = x + 19;
02124     points[2].x = x + 10;
02125     points[0].y = y - 3;
02126     points[1].y = y + 2;
02127     points[2].y = y + 7;
02128 
02129     XFillRectangle(DISP, mane.win, globals.gc.visited_linkcolor, x, y, 10, 4);
02130     XFillPolygon(DISP, mane.win, globals.gc.visited_linkcolor, points, 3, Convex, CoordModeOrigin);
02131     /* -1 indicates that no horizontal scrolling is wanted, since the
02132        anchormarker will always be horizontally positioned inside the
02133        visible area.
02134      */
02135     scroll_page_if_needed(-1, -1, y + 3, y - 3);
02136 }
02137 
02138 
02139 static void
02140 htex_erase_anchormarker(XtPointer client_data, XtIntervalId *id)
02141 {
02142     Position clip_w;
02143     
02144     UNUSED(client_data);
02145     UNUSED(id);
02146 
02147     if (globals.debug & DBG_EVENT) {
02148        fprintf(stderr, "htex_erase_anchormarker called!\n");
02149     }
02150 
02151     if (m_href_timeout_id == (XtIntervalId)0) { /* timeout was removed but callback happened anyway */
02152        return;
02153     }
02154 
02155     m_href_timeout_id = (XtIntervalId)0;
02156     /* clear the mark if we're in the same file and on the same page as the mark */
02157     if (g_anchormarker.filename != NULL
02158        && strcmp(globals.dvi_name, g_anchormarker.filename) == 0
02159        && g_anchormarker.page == current_page) {
02160        XtVaGetValues(globals.widgets.clip_widget, XtNwidth, &clip_w, NULL);
02161        clearexpose(&mane, 0, g_anchormarker.y_pos - 3, clip_w, 10);
02162     }
02163     g_anchormarker.y_pos = -1;
02164 }
02165