diff options
author | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
commit | 487ca4806cc046076293cf6cc5fbba0db282bac7 (patch) | |
tree | 847b412ab5158dd7bdd7ed7e5a4cc3fbca94be32 /Build/source/texk/xdvik/hypertex.c | |
parent | a3d3111bfe26b8e5f5bc6049dfb2a4ca2edc7881 (diff) |
texk 1
git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/xdvik/hypertex.c')
-rw-r--r-- | Build/source/texk/xdvik/hypertex.c | 2165 |
1 files changed, 2165 insertions, 0 deletions
diff --git a/Build/source/texk/xdvik/hypertex.c b/Build/source/texk/xdvik/hypertex.c new file mode 100644 index 00000000000..7db7f69527f --- /dev/null +++ b/Build/source/texk/xdvik/hypertex.c @@ -0,0 +1,2165 @@ +/* + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + * Original copyright: + * + * Hypertex modifications to DVI previewer for X. + * This portion of xhdvi is completely in the public domain. The + * author renounces any copyright claims. It may be freely used for + * commercial or non-commercial purposes. The author makes no claims + * or guarantees - use this at your own risk. + * + * Arthur Smith, U. of Washington, 1994 + * + * 5/1994 code written from scratch, probably inspired by (but + * incompatible with) the CERN WWW library. + * + * 3/1995 CERN WWW library called to do document fetching. + * + * 5/2002 SU: rewritten from scratch to make it work with the new + * event handling in xdvi >= 22.41: Anchors are now saved + * per page instead of per document, making it more robust + * wrt. interrupting the drawing and skipping to another page. + * Only drawback is that links breaking over pages are more + * difficult to handle (but they didn't work properly before either). + * Clicking on a link now performs a search for the + * corresponding anchor similiar to `forward search'. + * + * Removed libwww, since it has become too bloated + * and buggy, and had only rudimentary support for GUI + * interaction to begin with (e.g., interrupting a + * download). And since this is 2002, we might just as well + * use a web browser to fetch remote documents ;-) + */ + +/* TODO: + + - implement popup with list of visited links, as specified in + http://xdvi.sourceforge.net/gui.html#navigation-openLinks + (maybe this should be a simple menu list, not a popup window). + */ + +#define COPY_TMP_FILE 0 /* see comments below */ + +/* #define DEBUG */ + +/* #define DEBUG_MEMORY_HANDLING */ +#include "alloc-debug.h" + +#include "xdvi-config.h" +#include "xdvi.h" + +#include <string.h> + +#include <ctype.h> +#include "kpathsea/c-fopen.h" +#include "kpathsea/c-stat.h" +#include <X11/StringDefs.h> /* for XtNwidth, XtNheight */ + +#include "events.h" +#include "dvi-init.h" +#include "message-window.h" +#include "util.h" +#include "x_util.h" +#include "mime.h" +#include "mag.h" +#include "dvi-draw.h" +#include "statusline.h" +#include "browser.h" +#include "hypertex.h" +#include "special.h" +#include "string-utils.h" +#include "xm_toolbar.h" +#include "my-snprintf.h" +#include "pagehist.h" + +/* globals */ +/* rgb specifications to translate resource.{visited_}link_color into */ +char *g_link_color_rgb = NULL; +char *g_visited_link_color_rgb = NULL; +char *g_anchor_pos = NULL; +size_t g_anchor_len = 0; + +static const int DEFAULT_MARKER_X_OFFSET = 2; /* horizontal offset of anchor marker to edge of screen */ +static const int HTEX_ALLOC_STEP = 32; + +/* distance of underline from lower edge of character bbox */ +static const int ANCHOR_XTRA_V_BBOX = 6; + +/* info whether we're dealing with hypertex or hdvips type links */ +typedef enum { HYPERTEX_LINK, HDVIPS_LINK } hrefLinkT; +static hrefLinkT m_href_type; + +typedef enum { + A_HREF = 0, + A_NAME, + A_HREF_FILE, /* hdvips file ref */ + A_HREF_URL, /* hdvips URL ref */ + A_HDVIPS_INTERNAL, /* internal ref */ + A_HDVIPS_HREF, /* hdvips href */ + A_HDVIPS_NAME, /* hdvips name */ + A_OTHER, + A_END, + A_NONE, + A_MISMATCH +} htexAnchorT; + +/****************************** helper routines ******************************/ + +static Boolean +htex_is_href(htexAnchorT type) +{ + return type == A_HREF + || type == A_HREF_FILE + || type == A_HREF_URL + || type == A_HDVIPS_HREF + || type == A_HDVIPS_INTERNAL; +} + +static Boolean +htex_is_name(htexAnchorT type) +{ + return type == A_NAME || type == A_HDVIPS_NAME; +} + +static void +parse_html_special(const char *special, size_t len, const char **beg, const char **end) +{ + *beg = special; + while (isspace((int)**beg) || **beg == '=') { + (*beg)++; + len--; + } + *end = *beg + len - 1; + + while (isspace((int)**end) || **end == '>') + (*end)--; + + /* remove quote pairs */ + if (**beg == '"') { + (*beg)++; + if (**end == '"') { + (*end)--; + } + } + /* now end points to last anchor char, move behind that */ + (*end)++; +} + +/* + Anchor prescanning stuff: We make a pass through all pages in the document + before displaying them to collect information about mismatched anchors + (anchors where the opening tag is on some previous page). + The information is stored in the following struct: +*/ +struct prescan_info { + int anchor_depth; + size_t anchor_num; + size_t anchor_list_size; + int *anchor_list; + /* pointers to either NULL, or to mismatched anchor strings + (the contents of '<a href="...">' on some previous page), + for each page */ + size_t pagelist_len; + char **pagelist; +}; + +static struct prescan_info m_prescan_info = { 0, 0, 0, NULL, 0, NULL }; +static struct prescan_info m_save_prescan_info = { 0, 0, 0, NULL, 0, NULL }; + + +/* + Save prescan info from previous page, in case we only have a partial + scan of the current page and need to start all over. +*/ +void +htex_prescan_save(void) +{ + m_save_prescan_info.anchor_depth = m_prescan_info.anchor_depth; + m_save_prescan_info.anchor_num = m_prescan_info.anchor_num; + /* copy over anchor list */ + while (m_save_prescan_info.anchor_depth >= (int)m_save_prescan_info.anchor_list_size) { + int i; + m_save_prescan_info.anchor_list_size += HTEX_ALLOC_STEP; + m_save_prescan_info.anchor_list = XREALLOC(m_save_prescan_info.anchor_list, + m_save_prescan_info.anchor_list_size + * sizeof *(m_save_prescan_info.anchor_list)); + for (i = 0; i < m_save_prescan_info.anchor_depth; i++) { + m_save_prescan_info.anchor_list[i] = m_prescan_info.anchor_list[i]; + } + } + /* the page list of anchor strings can only have had its current position modified; + we'll simply delete that position in htex_prescan_restore(). + */ +} + +/* + Restore prescan info from m_save_prescan_info. +*/ +void +htex_prescan_restore(int pageno) +{ + m_prescan_info.anchor_depth = m_save_prescan_info.anchor_depth; + m_prescan_info.anchor_num = m_save_prescan_info.anchor_num; + /* copy back anchor list */ + if (m_save_prescan_info.anchor_depth > 0) { + int i; + /* old list might have been free()d in htex_prescan_reset_firstpass */ + while (m_save_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) { + m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP; + m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list, + m_prescan_info.anchor_list_size + * sizeof *(m_prescan_info.anchor_list)); + } + for (i = 0; i < m_save_prescan_info.anchor_depth; i++) { + m_prescan_info.anchor_list[i] = m_save_prescan_info.anchor_list[i]; + } + } + /* reset anchor string info for this page */ + if ((int)m_prescan_info.pagelist_len > pageno) { + ASSERT(m_prescan_info.pagelist != NULL, "m_prescan_info.pagelist not properly allocated"); + FREE(m_prescan_info.pagelist[pageno]); + m_prescan_info.pagelist[pageno] = NULL; + } +} + +/* + Reset prescan info for first pass (anchor numbers only) to its initial state, + freeing up allocated resources for anchor_list. + +*/ +void +htex_prescan_reset_firstpass(void) +{ + MYTRACE((stderr, "resetting anchor_depth to 0!!!!!!!!!!!!")); + m_prescan_info.anchor_depth = 0; + m_prescan_info.anchor_num = 0; + m_prescan_info.anchor_list_size = 0; + FREE(m_prescan_info.anchor_list); + m_prescan_info.anchor_list = NULL; +} + +/* + Reset the prescan info, freeing up all allocated memory. + Used when e.g. switching to a different file via Ctrl-F. +*/ +static void +htex_prescan_reset(void) +{ + size_t i; + m_prescan_info.anchor_depth = 0; + m_prescan_info.anchor_num = 0; + m_prescan_info.anchor_list_size = 0; + + free(m_prescan_info.anchor_list); + m_prescan_info.anchor_list = NULL; + + for (i = 0; i < m_prescan_info.pagelist_len; i++) { + free(m_prescan_info.pagelist[i]); + } + free(m_prescan_info.pagelist); + m_prescan_info.pagelist = NULL; + m_prescan_info.pagelist_len = 0; +} + +/* + Initialize m_prescan_info for scanning a new page. +*/ +void +htex_prescan_initpage(void) +{ + m_prescan_info.anchor_num = 0; +} + + +Boolean +htex_prescan_special(const char *cp, int cp_len, struct htex_prescan_data *data) +{ + UNUSED(cp_len); + ASSERT(data != NULL, "data argument to htex_prescan_special() mustn't be NULL"); + if (data->pageno + 1 < (int)m_prescan_info.pagelist_len) { /* already scanned this page */ + MYTRACE((stderr, "already scanned page %d", data->pageno+1)); + return False; + } + + /* resize pagelist */ + while (data->pageno >= (int)m_prescan_info.pagelist_len) { + size_t old_len = m_prescan_info.pagelist_len; + size_t i; + MYTRACE((stderr, "============ resizing pagelist to %d", data->pageno + 1)); + m_prescan_info.pagelist_len = data->pageno + 1; + m_prescan_info.pagelist = XREALLOC(m_prescan_info.pagelist, + m_prescan_info.pagelist_len * sizeof *m_prescan_info.pagelist); + /* initialize with NULL values */ + for (i = old_len; i < m_prescan_info.pagelist_len; i++) { + MYTRACE((stderr, "============ initializing pagelist %d", i)); + m_prescan_info.pagelist[i] = NULL; + } + } + + if (data->scan_type == HTEX_ANCHOR_STRING) { + if ((int)m_prescan_info.anchor_num == data->anchor_num) { + char *anchor; + const char *beg, *end; + const char *cp1 = cp; + const char *ptr = NULL; + const char *pptr = NULL; + + if (memicmp(cp1, "<a href", strlen("<a href")) == 0) { + cp1 += strlen("<a href"); + parse_html_special(cp1, strlen(cp1), &beg, &end); + anchor = xmalloc(end - beg + 1); + memcpy(anchor, beg, end - beg); + anchor[end - beg] = '\0'; + /* save the anchor string in m_prescan_info.pagelist[<current_page>] */ + m_prescan_info.pagelist[data->pageno] = anchor; + } + else if (memcmp(cp1, "/A", 2) == 0) { + if ((ptr = strstr(cp1 + 2, "/GoToR")) != NULL /* external file */ + && (ptr = strchr(ptr, '(')) != NULL + && (pptr = strchr(ptr + 1, '(')) != NULL) { + anchor = xmalloc(pptr - ptr); + memcpy(anchor, ptr + 1, pptr - ptr - 1); + anchor[pptr - ptr - 1] = '\0'; + /* save the anchor string in m_prescan_info.pagelist[<current_page>] */ + m_prescan_info.pagelist[data->pageno] = anchor; + } + else if ((ptr = strstr(cp1 + 2, "/URI")) != NULL /* external file */ + && (ptr = strchr(ptr, '(')) != NULL + && (pptr = strchr(ptr + 1, '(')) != NULL) { + anchor = xmalloc(pptr - ptr); + memcpy(anchor, ptr + 1, pptr - ptr - 1); + anchor[pptr - ptr - 1] = '\0'; + /* save the anchor string in m_prescan_info.pagelist[<current_page>] */ + m_prescan_info.pagelist[data->pageno] = anchor; + } + } + else if (memcmp(cp, "/L", 2) == 0) { + if ((ptr = strstr(cp1 + 2, "/Dest")) != NULL /* internal link */ + && (ptr = strchr(ptr, '(')) != NULL + && (pptr = strchr(ptr + 1, '(')) != NULL) { + anchor = xmalloc(pptr - ptr); + memcpy(anchor, ptr + 1, pptr - ptr - 1); + anchor[pptr - ptr - 1] = '\0'; + /* save the anchor string in m_prescan_info.pagelist[<current_page>] */ + m_prescan_info.pagelist[data->pageno] = anchor; + } + } + } + } + if (memicmp(cp, "<a ", 3) == 0) { + while (m_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) { + m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP; + m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list, + m_prescan_info.anchor_list_size + * sizeof *(m_prescan_info.anchor_list)); + } + ASSERT(m_prescan_info.anchor_depth >= 0, "List should contain previous anchor info"); + m_prescan_info.anchor_list[m_prescan_info.anchor_depth] = m_prescan_info.anchor_num; + m_prescan_info.anchor_depth++; + m_prescan_info.anchor_num++; + } + else if (memicmp(cp, "</a", 3) == 0) { + if (m_prescan_info.anchor_depth < 1) { + /* this can happen when stuff had been prescanned before */ + return False; + } + m_prescan_info.anchor_depth--; + m_prescan_info.anchor_num++; + } + else if (memcmp(cp, "H.S end", strlen("H.S end")) == 0) { /* start of anchor */ + while (m_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) { + m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP; + m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list, + m_prescan_info.anchor_list_size + * sizeof *(m_prescan_info.anchor_list)); + } + ASSERT(m_prescan_info.anchor_depth >= 0, "List should contain previous anchor info"); + m_prescan_info.anchor_list[m_prescan_info.anchor_depth] = m_prescan_info.anchor_num; + m_prescan_info.anchor_depth++; + m_prescan_info.anchor_num++; + + } + else if (memcmp(cp, "H.R end", strlen("H.R end")) == 0 /* end of rect */ + || memcmp(cp, "H.A end", strlen("H.A end")) == 0 /* end of anchor */ + || memcmp(cp, "H.L end", strlen("H.L end")) == 0 /* end of link */ + ) { + if (m_prescan_info.anchor_depth < 1) { + /* this can happen when stuff had been prescanned before */ + return False; + } + m_prescan_info.anchor_depth--; + m_prescan_info.anchor_num++; + } + return False; +} + +int +htex_prescan_get_depth(void) +{ + return m_prescan_info.anchor_depth; +} + +/* copy over pagelist from old_page to new_page */ +void +htex_prescan_carry_over(int old_page, int new_page) +{ + ASSERT(old_page >= 0, "old_page out of range"); + ASSERT(old_page < (int)m_prescan_info.pagelist_len, "old_page out of range"); + + /* resize if needed */ + if (new_page >= (int)m_prescan_info.pagelist_len) { + size_t old_len = m_prescan_info.pagelist_len; + size_t i; + m_prescan_info.pagelist_len = new_page + 1; + m_prescan_info.pagelist = XREALLOC(m_prescan_info.pagelist, + m_prescan_info.pagelist_len * sizeof *m_prescan_info.pagelist); + /* initialize with NULL values */ + for (i = old_len; i < m_prescan_info.pagelist_len; i++) { + m_prescan_info.pagelist[i] = NULL; + } + } + free(m_prescan_info.pagelist[new_page]); + /* don't share pointers here */ + m_prescan_info.pagelist[new_page] = xstrdup(m_prescan_info.pagelist[old_page]); +} + +size_t +htex_prescan_get_mismatched_anchor_num(size_t depth) +{ + ASSERT((int)depth <= m_prescan_info.anchor_depth, "depth too large"); + ASSERT(depth <= m_prescan_info.anchor_list_size, "depth too large for lookup list"); + + return m_prescan_info.anchor_list[m_prescan_info.anchor_depth - 1]; +} + +/******************************* end of prescan stuff ******************************/ + +struct anchor_marker { + int page; /* page on which marker is located */ + char *filename; /* file in which the marker is located */ + int y_pos; /* vertical position for marker */ + int x_pos; /* horizontal position for marker */ +} g_anchormarker = { -1, NULL, -1, -1 }; + +static XtIntervalId m_href_timeout_id = 0; + + +struct history_info { + char *anchor; /* anchor name */ + char *filename; /* name of file in which this anchor is located */ + int page; /* pagenumber on which anchor is located */ +}; + +struct anchor_info { + /* anchor contents as strings */ + char *a_name; + char *a_href; + htexObjectT object_type; + /* bounding box info for this anchor */ + int lrx, lry; /* lower-right corner */ + int ulx, uly; /* upper-left corner */ +/* int refpage; /\* page in DVI file for stack of visited anchors *\/ */ +/* char *filename; /\* name of file in which this anchor is located, for visited anchors *\/ */ + int prev_wrapped; /* index of prev elem, for wrapped hrefs, or -1 */ + int next_wrapped; /* index of next elem, for wrapped hrefs, or -1 */ +}; + +struct htex_page_info { + struct anchor_info *anchors; /* anchor info */ + int tot_cnt; /* anchor info size */ + int curr_cnt; /* current number of anchor on page */ + int page; /* page in DVI file this anchor info refers to */ + int have_wrapped; /* -1 if no wrapped anchor on beginning of page, or index of wrapped anchor */ +}; + +/* file-scope globals ... */ +/* record x and y positions of current anchor, to recognize linebreaks in anchors */ +static int x_pos_bak = 0; +static int y_pos_bak = 0; +/* holds all anchors on current page */ +static struct htex_page_info htex_page_info = { NULL, 0, 0, -1, -1 }; + +/* + double linked list with history of clicked links, for htex_forward/htex_back + */ +static struct dl_list *htex_history = NULL; /* current list insertion point */ + +struct htex_anchor_stack_elem { + htexAnchorT type; /* type of anchor (href, anchor, ...) */ + int anchor_num; /* number of this anchor on page (same as curr_cnt in struct htex_page_info) */ +}; + +/* + * stack datatype and access functions; used for nested anchors + */ +struct htex_anchor_stack { + size_t size; + size_t depth; + struct htex_anchor_stack_elem *types; +}; + +struct visited_anchor { + int *list; + size_t list_size; +}; + +struct visited_anchors { + struct visited_anchor *anchors; + size_t size; +}; + +static struct visited_anchors visited_links = { NULL, 0 }; + +static struct htex_anchor_stack stack = { 0, 0, NULL }; + + + +/* the following list is from + http://www.iana.org/assignments/uri-schemes + re-sorted for likeliness. All protocols except for `file' + are considered remote. + (i.e. not accessible via the ordinary Unix file system) +*/ +static char *const remote_URL_schemes[] = { + "http:", /* Hypertext Transfer Protocol [RFC2068] */ + "ftp:", /* File Transfer Protocol [RFC1738] */ + "https:", /* Hypertext Transfer Protocol Secure [RFC2818] */ + "mailto:", /* Electronic mail address [RFC2368] */ + "news:", /* USENET news [RFC1738] */ + "nntp:", /* USENET news using NNTP access [RFC1738] */ + "telnet:", /* Reference to interactive sessions [RFC1738] */ + "nfs:", /* network file system protocol [RFC2224] */ + "gopher:", /* The Gopher Protocol [RFC1738] */ + "wais:", /* Wide Area Information Servers [RFC1738] */ + /* the only exception: */ + /* "file:", */ /* Host-specific file names [RFC1738] */ + "prospero:", /* Prospero Directory Service [RFC1738] */ + "z39.50s", /* Z39.50 Session [RFC2056] */ + "z39.50r", /* Z39.50 Retrieval [RFC2056] */ + "cid:", /* content identifier [RFC2392] */ + "mid:", /* message identifier [RFC2392] */ + "vemmi:", /* versatile multimedia interface [RFC2122] */ + "service:", /* service location [RFC2609] */ + "imap:", /* internet message access protocol [RFC2192] */ + "acap:", /* application configuration access protocol [RFC2244] */ + "rtsp:", /* real time streaming protocol [RFC2326] */ + "tip:", /* Transaction Internet Protocol [RFC2371] */ + "pop:", /* Post Office Protocol v3 [RFC2384] */ + "data:", /* data [RFC2397] */ + "dav:", /* dav [RFC2518] */ + "opaquelocktoken:", /* opaquelocktoken [RFC2518] */ + "sip:", /* session initiation protocol [RFC2543] */ + "tel:", /* telephone [RFC2806] */ + "fax:", /* fax [RFC2806] */ + "modem:", /* modem [RFC2806] */ + "ldap:", /* Lightweight Directory Access Protocol [RFC2255] */ + "soap.beep:", /* soap.beep [RFCSOAP] */ + "soap.beeps:", /* soap.beeps [RFCSOAP] */ + /* Reserved URI Scheme Names: */ + "afs:", /* Andrew File System global file names */ + "tn3270:", /* Interactive 3270 emulation sessions */ + "mailserver:", /* Access to data available from mail servers */ + NULL +}; + +/* prototypes */ +static void htex_erase_anchormarker(XtPointer client_data, XtIntervalId *id); +static void htex_draw_anchormarker(int y); + + +static void +resize_info_if_needed(struct htex_page_info *info) +{ + /* resize info if needed */ + if (info->curr_cnt + 2 >= info->tot_cnt) { + int i; + while (info->curr_cnt + 2 >= info->tot_cnt) { + info->tot_cnt += HTEX_ALLOC_STEP; + } + info->anchors = XREALLOC(info->anchors, info->tot_cnt * sizeof *(info->anchors)); + for (i = info->curr_cnt; i < info->tot_cnt; i++) { +/* fprintf(stderr, "initializing info at index %d\n", i); */ + info->anchors[i].a_href = NULL; + info->anchors[i].a_name = NULL; +/* info->anchors[i].filename = NULL; */ + info->anchors[i].ulx = INT_MAX; + info->anchors[i].uly = INT_MAX; + info->anchors[i].lrx = 0; + info->anchors[i].lry = 0; + info->anchors[i].object_type = HTEX_TEXT; + info->anchors[i].prev_wrapped = -1; + info->anchors[i].next_wrapped = -1; + } + } +} + +static void +init_visited_links(struct visited_anchors *links, int total_pages, Boolean new_dvi_file) +{ + size_t i, old_size = links->size; + if (new_dvi_file) { + /* free old list */ + for (i = 0; i < old_size; i++) { + FREE(links->anchors[i].list); + links->anchors[i].list = NULL; + links->anchors[i].list_size = 0; + } + } + if (links->size <= (size_t)total_pages) { + MYTRACE((stderr, "resetting visited links (%d != %d)", links->size, total_pages)); + links->size = total_pages + 1; + links->anchors = XREALLOC(links->anchors, (links->size + 1) * sizeof *(links->anchors)); + for (i = old_size; i < links->size; i++) { + MYTRACE((stderr, "+++ initializing visited links for page %d", i)); + links->anchors[i].list = NULL; + links->anchors[i].list_size = 0; + } + } +} + +#if 0 +static void +show_visited(struct visited_anchors *links, int pageno) +{ + size_t i; + fprintf(stderr, "visited links on page %d:\n", pageno); + for (i = 0; i < links->anchors[pageno].list_size; i++) { + fprintf(stderr, "%d ", links->anchors[pageno].list[i]); + } + fprintf(stderr, "\n"); +} +#endif + +static void +save_in_list(struct visited_anchors *links, int pageno, int idx) +{ + links->anchors[pageno].list + = XREALLOC(links->anchors[pageno].list, + (links->anchors[pageno].list_size + 1) + * sizeof *(links->anchors[pageno].list)); + links->anchors[pageno].list[links->anchors[pageno].list_size] = idx; + links->anchors[pageno].list_size++; +} + +static void +set_visited(struct visited_anchors *links, int pageno, int anchor_num) +{ + int i; + /* is it already present? */ + for (i = 0; i < (int)links->anchors[pageno].list_size; i++) { + if (links->anchors[pageno].list[i] == anchor_num) + return; + } + save_in_list(links, pageno, anchor_num); + i = anchor_num; + /* also set previous/next of this anchor to visited */ + while ((i = htex_page_info.anchors[i].prev_wrapped) != -1) { + TRACE_HTEX((stderr, "set_visited: setting prev_wrapped %d to visited too", i)); + save_in_list(links, pageno, i); + } + i = anchor_num; + while ((i = htex_page_info.anchors[i].next_wrapped) != -1) { + TRACE_HTEX((stderr, "set_visited: setting next_wrapped %d to visited too", i)); + save_in_list(links, pageno, i); + } +#if 0 + show_visited(links, pageno); +#endif +} + +static Boolean +is_visited(struct visited_anchors *links, int pageno, int anchor_num) +{ + size_t i; + + ASSERT((size_t)pageno < links->size, "too few elements in links structure"); + + for (i = 0; i < links->anchors[pageno].list_size; i++) { + if (links->anchors[pageno].list[i] == anchor_num) { + return True; + } + } + return False; +} + +static void +push_stack(struct htex_anchor_stack *stack, htexAnchorT type, int anchor_num) +{ + size_t i = 0; + if (stack->depth >= stack->size) { + stack->size += HTEX_ALLOC_STEP; + stack->types = XREALLOC(stack->types, stack->size * sizeof *(stack->types)); + for (i = stack->depth; i < stack->size; i++) { + stack->types[i].type = A_NONE; + stack->types[i].anchor_num = -1; + } + } + stack->types[stack->depth].type = type; + stack->types[stack->depth].anchor_num = anchor_num; + stack->depth++; +#if 0 + { + fprintf(stderr, "PUSH - stack is now: \n"); + for (i = 0; i < stack->depth; i++) + fprintf(stderr, "%d:%d ", i, stack->types[i]); + MYTRACE(stderr, "\n"); + } +#endif +} + +static htexAnchorT +pop_stack(struct htex_anchor_stack *stack, int *anchor_num) +{ + htexAnchorT ret; + + if (stack->depth < 1) { + return A_MISMATCH; + } + +#if 0 + { + int i; + MYTRACE((stderr, "POP - stack is now: ")); + for (i = 0; i < stack->depth; i++) + MYTRACE((stderr, "%d:%d ", i, stack->types[i])); + MYTRACE((stderr, "")); + } +#endif + stack->depth--; + ret = stack->types[stack->depth].type; + *anchor_num = stack->types[stack->depth].anchor_num; + stack->types[stack->depth].type = A_NONE; + stack->types[stack->depth].anchor_num = -1; + return ret; +} + + +/* return True if stack contains an A_HREF, False else */ +static Boolean +get_href_depth(const struct htex_anchor_stack *stack) +{ + size_t i; + for (i = 0; i <= stack->depth; i++) { + ASSERT(stack->types != NULL, "types musn't be NULL!"); + if (htex_is_href(stack->types[i].type)) + return True; + } + return False; +} + +static htexAnchorT +peek_stack(struct htex_anchor_stack *stack, int *anchor_num) +{ + if (stack->depth < 1) { + MYTRACE((stderr, "Xdvi warning: wrong nesting of anchors on page %d", current_page)); + *anchor_num = -1; + return A_NONE; + } + + *anchor_num = stack->types[stack->depth - 1].anchor_num; + return stack->types[stack->depth - 1].type; +} + +/* routines for coloring the anchors */ +static void +push_colorspecial(void) +{ + int i; + + i = htex_page_info.curr_cnt - 1; + /* apply color if needed */ + if (resource.link_style > 1) { /* colored links are wanted */ + Boolean visited = False; + + ASSERT(i >= 0, "i mustn't be negative"); + if (is_visited(&visited_links, current_page, i)) {/* || wrapped_anchor_is_visited(i)) { */ + visited = True; + } + + MYTRACE((stderr, "anchor %d, %s is %s\n", + htex_page_info.curr_cnt - 1, htex_page_info.anchors[htex_page_info.curr_cnt - 1].a_href, + visited ? "******* visited ****** " : "not visited")); + if ((visited && resource.visited_link_color != NULL) + || (! visited && resource.link_color != NULL)) { + color_special(visited ? g_visited_link_color_rgb : g_link_color_rgb); + } + } +} + +static void +pop_colorspecial(void) +{ + color_special("pop"); +} + +/* return filename if it's a local file, NULL else */ +const char * +is_local_file(const char *filename) +{ + int i; + if (strchr(filename, ':') != NULL) { + if (memicmp(filename, "file:", strlen("file:")) == 0) { + TRACE_HTEX((stderr, "%s uses file scheme", filename)); + filename += strlen("file:"); + /* + skip over `//localhost' part, and skip first `/' iff the + absolute path starts with `//' (as required by RFC2396, + but in the past most browsers/applications didn't support + this). + */ + if (memicmp(filename, "//localhost", strlen("//localhost")) == 0) { + filename += strlen("//localhost"); + } + if (memicmp(filename, "//", 2) == 0) { + filename += 1; + } + return filename; + } + + /* check remote schemes */ + for (i = 0; remote_URL_schemes[i] != NULL; i++) { + if (memicmp(filename, remote_URL_schemes[i], strlen(remote_URL_schemes[i])) == 0) { + TRACE_HTEX((stderr, "%s is a remote scheme", filename)); + return NULL; + } + } + } + /* in all other cases, treat it as an ordinary filename */ + TRACE_HTEX((stderr, "%s is an ordinary filename", filename)); + return filename; +} + + +static char * +parse_anchortext(const char *input, int len) +{ + char *anchor = NULL; + const char *beg, *end; + + parse_html_special(input, len, &beg, &end); + + anchor = XMALLOC(anchor, end - beg + 1); + memcpy(anchor, beg, end - beg); + anchor[end - beg] = '\0'; + return anchor; +} + +static void +add_anchor(struct htex_page_info *info, htexAnchorT type, + const char *str, size_t len, + int pageno, char *filename) +{ + UNUSED(pageno); + UNUSED(filename); + + resize_info_if_needed(info); + + ASSERT(htex_is_name(type) || htex_is_href(type), "This doesn't look like a valid anchor"); + /* add an anchor or a href, depending on `type' */ + if (type == A_HREF) { + if (info->anchors[info->curr_cnt].a_href == NULL) { + info->anchors[info->curr_cnt].a_href = parse_anchortext(str, len); + } + TRACE_HTEX((stderr, "adding HREF %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href)); + } + else if (type == A_HREF_URL) { + if (info->anchors[info->curr_cnt].a_href == NULL) { + info->anchors[info->curr_cnt].a_href = xmalloc(len + 1); + strncpy(info->anchors[info->curr_cnt].a_href, str, len); + info->anchors[info->curr_cnt].a_href[len] = '\0'; + } + TRACE_HTEX((stderr, "adding HREF_URL %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href)); + } + else if (type == A_HDVIPS_INTERNAL) { + if (info->anchors[info->curr_cnt].a_href == NULL) { + /* dynamically add a `#' prefix */ + if (str[0] != '#') { + info->anchors[info->curr_cnt].a_href = xmalloc(len + 2); + strcpy(info->anchors[info->curr_cnt].a_href, "#"); + strncat(info->anchors[info->curr_cnt].a_href, str, len); + info->anchors[info->curr_cnt].a_href[len + 1] = '\0'; + } + else { + info->anchors[info->curr_cnt].a_href = xmalloc(len + 1); + strncpy(info->anchors[info->curr_cnt].a_href, str, len); + info->anchors[info->curr_cnt].a_href[len] = '\0'; + } + } + TRACE_HTEX((stderr, "adding HREF_URL %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href)); + } + else if (type == A_HREF_FILE) { + if (info->anchors[info->curr_cnt].a_href == NULL) { + /* dynamically add a `file:' extension */ + if (memcmp(str, "file:", strlen("file:")) == 0) { + info->anchors[info->curr_cnt].a_href = xmalloc(len + 1); + strncpy(info->anchors[info->curr_cnt].a_href, str, len); + info->anchors[info->curr_cnt].a_href[len] = '\0'; + } + else { + info->anchors[info->curr_cnt].a_href = xmalloc(len + strlen("file:") + 1); + strcpy(info->anchors[info->curr_cnt].a_href, "file:"); + strncat(info->anchors[info->curr_cnt].a_href, str, len); + info->anchors[info->curr_cnt].a_href[len + strlen("file:")] = '\0'; + } + } + TRACE_HTEX((stderr, "adding HREF_FILE %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href)); + } + else if (type == A_NAME) { + if (info->anchors[info->curr_cnt].a_name == NULL) { + info->anchors[info->curr_cnt].a_name = parse_anchortext(str, len); + } + TRACE_HTEX((stderr, "adding NAME %d: %s", info->curr_cnt, info->anchors[info->curr_cnt].a_name)); + } + else if (type == A_HDVIPS_HREF) { + if (info->anchors[info->curr_cnt].a_href == NULL) { + info->anchors[info->curr_cnt].a_href = xmalloc(len + 1); + strncpy(info->anchors[info->curr_cnt].a_href, str, len); + info->anchors[info->curr_cnt].a_href[len] = '\0'; + } + TRACE_HTEX((stderr, "adding HDVIPS_HREF %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_name)); + } + else if (type == A_HDVIPS_NAME) { + if (info->anchors[info->curr_cnt].a_name == NULL) { + info->anchors[info->curr_cnt].a_name = xmalloc(len + 1); + strncpy(info->anchors[info->curr_cnt].a_name, str, len); + info->anchors[info->curr_cnt].a_name[len] = '\0'; + } + TRACE_HTEX((stderr, "adding HDVIPS_NAME %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_name)); + } +} + +static void +set_anchor_size(struct htex_page_info *info, int index, + int ulx, int uly, int lrx, int lry) +{ + struct anchor_info *anchor; + + ASSERT(info->anchors != NULL, "info->anchors should have been allocated before"); + ASSERT(index < info->curr_cnt, "info too small"); + + anchor = &(info->anchors[index]); + + anchor->ulx = ulx; + anchor->uly = uly; + anchor->lrx = lrx; + anchor->lry = lry; +} + +void +htex_set_objecttype(htexObjectT type) +{ + htex_page_info.anchors[htex_page_info.curr_cnt - 1].object_type = type; +} + +void +htex_set_anchorsize(int ulx, int uly, int lrx, int lry) +{ + set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, + ulx, uly, lrx, lry); +} + +static void +enlarge_anchor_size(struct htex_page_info *info, int index, + int ulx, int uly, int lrx, int lry) +{ + struct anchor_info *anchor; + + ASSERT(info->anchors != NULL, "info->anchors should have been allocated before"); + ASSERT(index < info->curr_cnt, "info too small"); + +/* fprintf(stderr, "enlarging anchor at index %d; %s\n", index, info->anchors[index].a_href); */ + anchor = &(info->anchors[index]); + + if (ulx < anchor->ulx) { + anchor->ulx = ulx; + } + if (uly < anchor->uly) { + anchor->uly = uly; + } + if (lrx > anchor->lrx) { + anchor->lrx = lrx; + } + /* set lry only for first character, since this will be used + to position underline */ +/* if (lry > anchor->lry && anchor->lry == 0) { */ + if (lry > anchor->lry) { + anchor->lry = lry; + } +} + +static void +reset_page_info(struct htex_page_info *info, int pageno, Boolean force_init) +{ + int i, dummy; + + if (force_init || pageno != info->page) { +#if 0 + fprintf(stderr, "%d or %d != %d: resetting anchorinfo for page %d (%d anchors)\n", + force_init, pageno, info->page, current_page, info->curr_cnt); +#endif + ASSERT(info->curr_cnt == 0 || info->anchors != NULL, "inconsistency in info structure"); + /* re-initialize all values */ + for (i = 0; i < info->curr_cnt; i++) { + TRACE_HTEX((stderr, "----- resetting info for anchor %d", i)); + FREE(info->anchors[i].a_name); + FREE(info->anchors[i].a_href); + info->anchors[i].a_name = NULL; + info->anchors[i].a_href = NULL; + info->anchors[i].ulx = INT_MAX; + info->anchors[i].uly = INT_MAX; + info->anchors[i].lrx = 0; + info->anchors[i].lry = 0; + info->anchors[i].object_type = HTEX_TEXT; + info->anchors[i].prev_wrapped = -1; + info->anchors[i].next_wrapped = -1; + } + } + if (pageno != info->page) { /* reset info */ + free(info->anchors); + info->anchors = NULL; + info->tot_cnt = 0; + } + TRACE_HTEX((stderr, "---------------- setting curr_cnt to 0, and emptying stack")); + info->page = pageno; + info->curr_cnt = 0; + info->have_wrapped = -1; + while (stack.depth > 0) + pop_stack(&stack, &dummy); +} + +/* + * htex_initpage does what's neccessary at the beginning of a page: + * - re-initialize geometry info if file or page size has changed (according to size_changed), + * - re-initialize visited links info if file has changed (according to new_dvi_file), + * - take care of mismatched anchors at the beginning of the page. + */ +void +htex_initpage(Boolean new_dvi_file, Boolean size_changed, int pageno) +{ + reset_page_info(&htex_page_info, pageno, size_changed | new_dvi_file); + init_visited_links(&visited_links, total_pages, new_dvi_file); + +#if 0 + show_visited(&visited_links, pageno); +#endif + + if (pageno > 0 + && (int)m_prescan_info.pagelist_len > pageno /* pagelist_len will be 0 for file with no hyperlinks */ + && m_prescan_info.pagelist[pageno - 1] != NULL) { + add_anchor(&htex_page_info, A_HREF, + m_prescan_info.pagelist[pageno - 1], + strlen(m_prescan_info.pagelist[pageno - 1]), + pageno, NULL); + htex_page_info.curr_cnt++; + MYTRACE((stderr, "++++++++++ mismatched anchor text (at %d): |%s|", + htex_page_info.curr_cnt - 1, m_prescan_info.pagelist[pageno - 1])); + /* x_pos_bak = y_pos_bak = INT_MAX; */ + x_pos_bak = y_pos_bak = INT_MAX; + set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, 0, 0, 1, 1); + htex_page_info.have_wrapped = htex_page_info.curr_cnt - 1; + if (bg_current != NULL) { /* only if it has been initialized by redraw_page in events.c */ + push_colorspecial(); + } +/* else { */ +/* fprintf(stderr, "----------------------- NOT pushing color, waiting ...\n"); */ +/* } */ + push_stack(&stack, A_HREF, htex_page_info.curr_cnt - 1); + } +} + +static void +hdvips_add_anchor(struct htex_page_info *info, htexAnchorT type, const char *str) +{ + char *ptr; + if ((ptr = strchr(str, ')')) != NULL) { + int curr_cnt_bak = info->curr_cnt; + /* overwrite previously created dummy/wrapped anchors */ + ASSERT(info->curr_cnt > 0, "hdvips_add_anchor must be called after add_anchor()!"); + + while (info->curr_cnt > 0 + && info->anchors[info->curr_cnt - 1].a_href != NULL + && (strcmp(info->anchors[info->curr_cnt - 1].a_href, "__WRAPPED__") == 0 + || strcmp(info->anchors[info->curr_cnt - 1].a_href, "__DUMMY__") == 0)) { + info->curr_cnt--; + free(info->anchors[info->curr_cnt].a_href); + free(info->anchors[info->curr_cnt].a_name); + info->anchors[info->curr_cnt].a_href = info->anchors[info->curr_cnt].a_name = NULL; + add_anchor(info, type, str, ptr - str, 0, NULL); + } + info->curr_cnt = curr_cnt_bak; + } + else { + MYTRACE((stderr, "Xdvi warning: skipping malformed hdvips link `%s'", str)); + } +} + +/* + * htex_reset_page: invoked when drawing was interrupted, + * assumes that htex_page_info is in an inconsistent state and resets it. + */ +void +htex_reset_page(int pageno) +{ + reset_page_info(&htex_page_info, pageno, True); +} + +/* returns True iff inside a href tag */ +Boolean +htex_scan_anchor(const char *cp, size_t len) +{ + char *ptr; + + if (memicmp(cp, "</a>", 4) == 0) { /* end tag */ + /* struct anchor_info *anchor; */ + int num; + htexAnchorT type = pop_stack(&stack, &num); + if (type == A_MISMATCH) + return False; + + /* ASSERT(htex_page_info.curr_cnt - 1 >= 0, "Index out of range"); */ + /* anchor = &(htex_page_info.anchors[htex_page_info.curr_cnt - 1]); */ + + /* reset color if needed */ + if (resource.link_style > 1 && resource.link_color != NULL && htex_is_href(type)) + pop_colorspecial(); + } + else if (memicmp(cp, "<a ", 3) == 0) { + m_href_type = HYPERTEX_LINK; + cp += 3; /* skip over `<a ' part */ + len -= 3; + TRACE_HTEX((stderr, "scan_anchor: |%s|", cp)); + if (memicmp(cp, "name", 4) == 0) { + add_anchor(&htex_page_info, A_NAME, cp + 4, len - 4, 0, NULL); + htex_page_info.curr_cnt++; + push_stack(&stack, A_NAME, htex_page_info.curr_cnt); + } + else if (memicmp(cp, "href", 4) == 0) { + add_anchor(&htex_page_info, A_HREF, cp + 4, len - 4, 0, NULL); + htex_page_info.curr_cnt++; + push_stack(&stack, A_HREF, htex_page_info.curr_cnt); + + /* MYTRACE((stderr, "NON-WRAPPED ANCHOR at %d,%d!", PXL_H, PXL_V)); */ + push_colorspecial(); + x_pos_bak = PXL_H; + y_pos_bak = PXL_V; +/* set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, PXL_H, PXL_V, PXL_H + 10, PXL_V + 10); */ + } + else { + XDVI_WARNING((stderr, "Skipping unimplemented htex special `%s'", cp)); + push_stack(&stack, A_OTHER, htex_page_info.curr_cnt); + } + } + else if (memcmp(cp, "H.S end", strlen("H.S end")) == 0) { + /* start of anchor, link or rect. We just assume that the link + target will have length 0, so when we see text between H.S + and H.R, assume that it's the link text. + */ + m_href_type = HDVIPS_LINK; + /* add dummy */ + add_anchor(&htex_page_info, A_HDVIPS_HREF, "__DUMMY__", strlen("__DUMMY__"), 0, NULL); + htex_page_info.curr_cnt++; + push_stack(&stack, A_HREF, htex_page_info.curr_cnt - 1); + push_colorspecial(); + x_pos_bak = PXL_H; + y_pos_bak = PXL_V; + return True; + } + else if (memcmp(cp, "H.R end", strlen("H.R end")) == 0 /* end of rect */ + || memcmp(cp, "H.A end", strlen("H.A end")) == 0 /* end of anchor */ + || memcmp(cp, "H.L end", strlen("H.L end")) == 0 /* end of link */ + ) { + int num; + htexAnchorT type = pop_stack(&stack, &num); + if (type == A_MISMATCH) + return False; + if (resource.link_style > 1 && resource.link_color != NULL) + pop_colorspecial(); + return False; + } + /* add anchor texts for hdvips links */ + else if (memcmp(cp, "/A", 2) == 0) { /* possibly an hdvips external link */ +/* fprintf(stderr, "+++++ EXT: |%s|\n", cp); */ + if ((ptr = strstr(cp + 2, "/GoToR")) != NULL /* external file */ + && (ptr = strchr(ptr, '(')) != NULL) { + hdvips_add_anchor(&htex_page_info, A_HREF_FILE, ptr + 1); + } + else if ((ptr = strstr(cp + 2, "/URI")) != NULL /* URL */ + && (ptr = strchr(ptr, '(')) != NULL) { + hdvips_add_anchor(&htex_page_info, A_HREF_URL, ptr + 1); + } + return False; + } + else if (memcmp(cp, "/L", 2) == 0) { /* possibly an hdvips internal link */ + if ((ptr = strstr(cp + 2, "/Dest")) != NULL + && (ptr = strchr(ptr, '(')) != NULL) { + hdvips_add_anchor(&htex_page_info, A_HDVIPS_INTERNAL, ptr + 1); + } + return False; + } + else if (memcmp(cp, "/V", 2) == 0) { /* possibly an hdvips anchor */ + if ((ptr = strstr(cp + 2, "/Dest")) != NULL + && (ptr = strchr(ptr, '(')) != NULL) { + hdvips_add_anchor(&htex_page_info, A_HDVIPS_NAME, ptr + 1); + } + return False; + } + else { /* not a start tag */ + MYTRACE((stderr, "Xdvi warning: skipping malformed hyperref special `%s'", cp)); + } + return get_href_depth(&stack); +} + +void +htex_record_position(int ulx, int uly, int w, int h) +{ + int lrx, lry; + int y_delta, x_delta; + + if (!INSIDE_MANE_WIN) /* this would give wrong values */ + return; + + lrx = ulx + w; + lry = uly + h; + + y_delta = lry - uly; + x_delta = lrx - ulx; + + /* heuristics for creating new bounding box at what might be linebreaks */ + if (lrx < x_pos_bak /* ordinary linebreak */ + || lry + y_delta < y_pos_bak) { /* column break (from bottom of 1st to top 2nd column) */ + htexAnchorT type; + int idx, prev, curr; + /* get anchor index of topmost stack item, to find the matching open tag */ + if ((type = peek_stack(&stack, &idx)) == A_NONE) { +/* fprintf(stderr, "!!!!!!!!!!!! couldn't find opening tag for this wrapped link!"); */ + return; + } + ASSERT(idx >= 0, "Index out of range"); +/* fprintf(stderr, "wrapped link: index %d, type %d\n", idx, type); */ + /* get correct idx */ + if (m_href_type == HYPERTEX_LINK) { + while (htex_page_info.anchors[idx].a_href == NULL && idx > 0) { + idx--; + } + if (htex_page_info.anchors[idx].a_href == NULL && idx == 0) { + XDVI_ERROR((stderr, "Couldn't find wrapped anchor for idx %d, page %d!", idx, current_page)); + return; + } + add_anchor(&htex_page_info, A_HREF, + htex_page_info.anchors[idx].a_href, + strlen(htex_page_info.anchors[idx].a_href), + 0, NULL); + } + else { + add_anchor(&htex_page_info, A_HREF, "__WRAPPED__", strlen("__WRAPPED__"), 0, NULL); + } + htex_page_info.curr_cnt++; + /* add wrapping info */ + if (htex_page_info.have_wrapped >= 0) { + /* this is the only case where some other material might have come between + this and the previous anchor */ + prev = htex_page_info.have_wrapped; + htex_page_info.have_wrapped = -1; + } + else { + prev = htex_page_info.curr_cnt - 2; + } + curr = htex_page_info.curr_cnt - 1; + ASSERT(prev >= 0, "Index out of range"); + +/* fprintf(stderr, "setting prev to %d, curr to %d\n", prev, curr); */ + htex_page_info.anchors[prev].next_wrapped = curr; + htex_page_info.anchors[curr].prev_wrapped = prev; + /* initialize it to cover current character */ + set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, ulx, uly, lrx, lry); + } + else { + int prev_idx = htex_page_info.curr_cnt - 1; + + if (prev_idx >= 0) { + enlarge_anchor_size(&htex_page_info, prev_idx, ulx, uly, lrx, lry); + } + else { + MYTRACE((stderr, "!!!!!!!!!!!! Bug? prev_idx < 0")); + } + } + x_pos_bak = lrx; + y_pos_bak = uly; +} + +#if 0 +static void +show_history(void) +{ + struct dl_list *ptr = htex_history; + struct history_info *info = ptr->item; + int i; + + fprintf(stderr, "**************** history: %s, %d, %s\n", info->filename, info->page, info->anchor); + for (; ptr->prev != NULL; ptr = ptr->prev) { ; } + for (i = 0; ptr != NULL; ptr = ptr->next, i++) { + info = ptr->item; + fprintf(stderr, "elem %d: %s, %d, %s\n", i, info->filename, info->page, info->anchor); + MYTRACE((stderr, "elem: %p, prev: %p, next: %p", (void *)ptr, (void *)ptr->prev, (void *)ptr->next)); + } +} +#endif + +/* check for names like foo/bar.dvi#link, return copy of + link truncated at the `#' if found (NULL else), and + save `link' into resource.anchor_pos for later use. + */ +static char * +check_relative_name(const char *link) +{ + char *ptr; + TRACE_HTEX((stderr, "check_relative_name: |%s|", link)); + if ((ptr = strchr(link, '#')) != NULL + && ptr > link + 4 + && (memicmp(ptr - 4, ".dvi", 4) == 0)) { + char *new_link = xstrdup(link); + new_link[ptr - link] = '\0'; /* truncate the copy */ + free(g_anchor_pos); + g_anchor_pos = xstrdup(ptr + 1); + g_anchor_len = strlen(g_anchor_pos); + + return new_link; + } + else { /* reset g_anchor_pos */ + free(g_anchor_pos); + g_anchor_pos = NULL; + } + return NULL; +} + +static void +htex_update_toolbar_navigation(void) +{ +#if 0 + show_history(); +#endif +#if defined(MOTIF) && HAVE_XPM + tb_set_htex_back_sensitivity(htex_history->prev != NULL); + tb_set_htex_forward_sensitivity(htex_history->next != NULL); +#endif +} + +void +htex_back(void) +{ + struct history_info *info; + + if (htex_history == NULL) { + XBell(DISP, 0); + statusline_print(STATUS_SHORT, "Hyperref history is empty"); + return; + } + info = htex_history->item; + + if (htex_history->prev == NULL) { + XBell(DISP, 0); + statusline_print(STATUS_SHORT, "At begin of history"); + htex_update_toolbar_navigation(); + return; + } + + if (strcmp(info->filename, globals.dvi_name) != 0) { /* new filename different */ + Boolean tried_dvi_ext = False; + char *new_dvi_name = NULL; + + if ((new_dvi_name = open_dvi_file_wrapper(info->filename, False, False, + &tried_dvi_ext, False)) + == NULL) { + + statusline_print(STATUS_MEDIUM, "Re-opening file \"%s\" failed!\n", info->filename); + return; + } + else { + set_dvi_name(new_dvi_name); + globals.ev.flags |= EV_NEWDOC; + globals.ev.flags |= EV_PAGEHIST_INSERT; +/* statusline_print(STATUS_MEDIUM, "Back to file: \"%s\"", globals.dvi_name); */ + } + } + else { + page_history_insert(info->page); + } + goto_page(info->page, resource.keep_flag ? NULL : home, False); + + MYTRACE((stderr, "curr page now: %d", current_page)); + htex_history = htex_history->prev; + + info = htex_history->item; + +#if 0 + MYTRACE((stderr, "------ before skipping: %d, %s", info->page, info->anchor)); + show_history(); + while (info->page == current_page && htex_history->prev != NULL) { + /* skip identical locations */ + MYTRACE((stderr, "+++++++ skipping identical page %d, %s", current_page, info->anchor)); + htex_history = htex_history->prev; + info = htex_history->item; + } + MYTRACE((stderr, "------ after skipping: %d, %s", info->page, info->anchor)); +#endif + + htex_update_toolbar_navigation(); +} + + +void +htex_forward(void) +{ + struct history_info *info; + char *link; + + if (htex_history == NULL) { + XBell(DISP, 0); + statusline_print(STATUS_SHORT, "Hyperref history is empty"); + return; + } + if (htex_history->next == NULL) { + XBell(DISP, 0); + statusline_print(STATUS_SHORT, "At end of history"); + return; + } + + htex_history = htex_history->next; + info = htex_history->item; + link = info->anchor; + /* go there */ + if (*link == '#') { /* it's a relative link */ + MYTRACE((stderr, "XXXXXXXX %s:%d: setting anchor to |%s|+1", __FILE__, __LINE__, info->anchor)); + free(g_anchor_pos); + g_anchor_pos = xstrdup(info->anchor + 1); + g_anchor_len = strlen(g_anchor_pos); + globals.ev.flags |= EV_ANCHOR; + } + else { /* it's an absolute link */ + char *new_linkname = NULL; + char *new_dvi_name = NULL; + Boolean tried_dvi_ext = False; + + if ((new_linkname = check_relative_name(link)) != NULL) + link = new_linkname; + + if ((new_dvi_name = open_dvi_file_wrapper(link, False, False, + &tried_dvi_ext, False)) == NULL) { + statusline_print(STATUS_MEDIUM, "Re-opening file \"%s\" failed!\n", info->filename); + free(new_linkname); + return; + } + else { + set_dvi_name(new_dvi_name); + + globals.ev.flags |= EV_NEWDOC; + goto_page(0, resource.keep_flag ? NULL : home, False); + globals.ev.flags |= EV_PAGEHIST_INSERT; + + if (g_anchor_pos != NULL) + globals.ev.flags |= EV_ANCHOR; + +/* statusline_print(STATUS_MEDIUM, "Loaded file: \"%s\"", globals.dvi_name); */ + free(new_linkname); + } + } + + htex_update_toolbar_navigation(); +} + +/* save this anchor in jump_history, current location in goback_history, + and update Motif toolbar icons +*/ +static void +history_save_anchor(const char *anchor, int current_page, const char *dvi_name) +{ + struct history_info *item; + + if (htex_history == NULL) { + /* Insert dummy start elem. We need this to prevent dropping + off from the begin of the list, since otherwise a 1-elem + list couldn't encode the information whether we can still go + back. This way it will point to the dummy elem when there's + no way to go back. */ + struct history_info *start_marker = XMALLOC(start_marker, sizeof *start_marker); + start_marker->anchor = NULL; + start_marker->filename = NULL; + start_marker->page = -1; + htex_history = dl_list_insert(htex_history, start_marker); + } + item = XMALLOC(item, sizeof *item); + item->anchor = xstrdup(anchor); + item->filename = xstrdup(dvi_name); + item->page = current_page; + htex_history = dl_list_truncate(dl_list_insert(htex_history, item)); + +#if 0 + MYTRACE((stderr, "==================== after inserting ")); + show_history(); +#endif + + htex_update_toolbar_navigation(); + +} + +/* + * Return anchor string at coordinates x, y, and anchor count in 3rd argument, + * or NULL if there's no anchor at these coordinates. +*/ +static char * +get_anchor_at_index(int x, int y, int *count) +{ + int i; + + for (i = 0; i < htex_page_info.curr_cnt; i++) { + struct anchor_info anchor = htex_page_info.anchors[i]; +/* fprintf(stderr, "getting anchor at index %d; %s\n", i, anchor.a_href); */ + if (anchor.a_href == NULL) { + continue; + } + if (anchor.lry + 2 > y && anchor.uly - 1 < y /* allow some vertical fuzz */ + && anchor.lrx > x && anchor.ulx < x) { + *count = i; + return anchor.a_href; + } + } + *count = -1; + return NULL; +} + + +/* + generate exposure events for anchor at index idx, and all anchors wrapping to/from it, + so that they will be redrawn in `visited' color +*/ +static void +redraw_anchors(int idx) +{ + struct anchor_info anchor = htex_page_info.anchors[idx]; + int other; + int i = idx; +#define DO_EXPOSE(lx,ly,rx,ry) (clearexpose(&mane, lx - 20, ly - 3, rx - lx + 26, ry - ly + 6)) + + /* HACK ALERT: This is a workaround for a coloring problem with wrapped anchors + (see regression/href/002/url-wrap-test2.dvi): + We can't change the color on-the-fly for a wrapped anchor, since the special + is pushed only at the opening tag. So for wrapped anchors, the visited colour + wouldn't be set correctly. Redrawing the entire page ensures this (but it's ugly). + Currently this hack overrides the algorithm below for all anchors that have a + `prev_wrapped' pointer. + */ + if (htex_page_info.anchors[i].prev_wrapped != -1) { + globals.ev.flags |= EV_NEWPAGE; + XSync(DISP, False); + return; + } + + DO_EXPOSE(anchor.ulx, anchor.uly, anchor.lrx, anchor.lry); + + /* previous parts of wrapped anchors: */ + for (i = idx; + i >= 0 && (other = htex_page_info.anchors[i].prev_wrapped) != -1; + i--) { + DO_EXPOSE(htex_page_info.anchors[other].ulx, htex_page_info.anchors[other].uly, + htex_page_info.anchors[other].lrx, htex_page_info.anchors[other].lry); + } + + /* later parts of wrapped anchors: */ + for (i = idx; + i < htex_page_info.curr_cnt && (other = htex_page_info.anchors[i].next_wrapped) != -1; + i++) { + DO_EXPOSE(htex_page_info.anchors[other].ulx, htex_page_info.anchors[other].uly, + htex_page_info.anchors[other].lrx, htex_page_info.anchors[other].lry); + } +#undef DO_EXPOSE +} + + +/* + * Invoked when clicking on a link. + * newwindow == True means open new window, + * newwindow == False means jump to link in current window. + * With newwindow == True, don't update the toolbar navigation icons, since there's nothing to go back. + */ +Boolean +htex_handleref(int x, int y, Boolean newwindow) +{ + char *link; + int link_num; + +/* fprintf(stderr, "---------- htex_handleref!\n"); */ + if (!(globals.cursor.flags & CURSOR_LINK)) + /* When no link cursor is shown, clicking shouldn't jump to link */ + return False; + + if ((link = get_anchor_at_index(x, y, &link_num)) != NULL) { + set_visited(&visited_links, current_page, link_num); + + if (*link == '#') { /* it's a relative link */ + if (newwindow) { + launch_xdvi(globals.dvi_name, link + 1); + redraw_anchors(link_num); + return True; + } + else { + history_save_anchor(link, current_page, globals.dvi_name); + free(g_anchor_pos); + g_anchor_pos = xstrdup(link + 1); + g_anchor_len = strlen(g_anchor_pos); + globals.ev.flags |= EV_ANCHOR; + redraw_anchors(link_num); + return True; + } + } + else { /* it's an absolute link */ + char *new_dvi_name = NULL; + int pageno_bak = current_page; + char *new_linkname = NULL; + char *orig_link = link; + Boolean tried_dvi_ext = False; + + MYTRACE((stderr, "OLD FILE: |%s|", globals.dvi_name)); + + if ((new_linkname = check_relative_name(link)) != NULL) + link = new_linkname; + + if ((new_dvi_name = open_dvi_file_wrapper(link, False, newwindow, + &tried_dvi_ext, False)) != NULL) { + /* only save link in history if opening succeeds */ + history_save_anchor(orig_link, pageno_bak, globals.dvi_name); + set_dvi_name(new_dvi_name); + if (!newwindow) { + globals.ev.flags |= EV_NEWDOC; + goto_page(0, resource.keep_flag ? NULL : home, False); + globals.ev.flags |= EV_PAGEHIST_INSERT; + + MYTRACE((stderr, "NEW FILE: |%s|", globals.dvi_name)); + if (g_anchor_pos != NULL) + globals.ev.flags |= EV_ANCHOR; +/* statusline_print(STATUS_MEDIUM, "Loaded file: \"%s\"", dvi_name); */ + } + else { + redraw_anchors(link_num); + } + } + free(new_linkname); + redraw_anchors(link_num); + return True; + } + } + return False; +} + +/* change cursor and display anchor text in statusline */ +void +htex_displayanchor(int x, int y) +{ + static int id_bak = -1; + static int pageno_bak = -1; + int id_curr; + char *anchor_text; + + anchor_text = get_anchor_at_index(x, y, &id_curr); + + if (anchor_text != NULL) { /* found an anchor */ + if (!(globals.cursor.flags & CURSOR_LINK) && !dragging_text_selection()) { + globals.cursor.flags |= CURSOR_LINK; + globals.ev.flags |= EV_CURSOR; + } + /* to prevent flicker, print only when it differs from previous one, + i.e. when it either has a different ID or is on a different page: */ + if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0 + && (id_curr != id_bak || current_page != pageno_bak)) { + statusline_print(STATUS_FOREVER, "%s", anchor_text); + id_bak = id_curr; + pageno_bak = current_page; + } + } + else { /* not over anchor text, reset cursor and statusline */ + if (globals.cursor.flags & CURSOR_LINK) { + globals.cursor.flags &= ~CURSOR_LINK; + globals.ev.flags |= EV_CURSOR; + } + + if (id_bak != -1 && (resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) { + statusline_clear(); + id_bak = -1; + } + } +} + +/* + * Invoked from all commands that change the shrink factor; + * forces re-computation of the anchor markers. +*/ +void +htex_resize_page(void) +{ + htex_initpage(False, True, current_page); +} + +/* + * Invoked from all commands that reread the DVI file; + * resets all link infos. + * Note: visited_links and anchor stack data + * are preserved over files, so we don't need to free() them. + */ +void +htex_reinit(void) +{ + htex_prescan_reset(); + htex_initpage(True, True, current_page); +} + +/* + Underline/highlight anchors if the appropriate resource is set. + Since this is done after the entire page is parsed, we can't use + the checks for fg_current and do_color_change(), but need to + use a separate GC instead. + */ +void +htex_draw_anchormarkers(void) +{ + int i; + int rule_height = (globals.page.h / 1000.0); + XRectangle rect = { -1, -1, 0, 0 }; + + if (rule_height == 0) + rule_height = 1; + + if (current_page == g_anchormarker.page + && strcmp(globals.dvi_name, g_anchormarker.filename) == 0 + && g_anchormarker.y_pos > 0) { + htex_draw_anchormarker(g_anchormarker.y_pos); + } + + if (resource.link_style == 0 || resource.link_style == 2) + return; + + for (i = 0; i < htex_page_info.curr_cnt; i++) { + struct anchor_info anchor = htex_page_info.anchors[i]; + + if (anchor.a_href != NULL) { + int offset; + Boolean visited = False; + +#if 0 + XDrawRectangle(DISP, mane.win, globals.gc.high, + anchor.ulx, anchor.uly, + anchor.lrx - anchor.ulx, anchor.lry - anchor.uly); + +#endif + + if (is_visited(&visited_links, current_page, i)) {/* || wrapped_anchor_is_visited(i)) { */ + visited = True; + } + offset = ANCHOR_XTRA_V_BBOX / (double)currwin.shrinkfactor + 0.5; + + TRACE_HTEX((stderr, "UNDERLINE: %d, %s is %s", i, anchor.a_href, + visited ? "******* visited ****** " : "not visited")); + anchor.ulx -= currwin.base_x; + anchor.lrx -= currwin.base_x; + anchor.lry -= currwin.base_y; + anchor.uly -= currwin.base_y; + + /* BUG ALERT: Don't use put_rule here, since this will cause a segfault + * with the new color code when switching density (#616920)! + */ + MYTRACE((stderr, "UNDERLINE: checking if %d is visited: %d!", i, visited)); + +/* if (clip_region_to_rect(&rect)) { */ + if (anchor.object_type == HTEX_IMG) { + rect.x = anchor.ulx - 1; + rect.y = anchor.uly - 1; + rect.width = anchor.lrx - anchor.ulx + 2; + rect.height = anchor.lry - anchor.uly + 2; + if (clip_region_to_rect(&rect)) { + XDrawRectangle(DISP, mane.win, + visited ? globals.gc.visited_linkcolor : globals.gc.linkcolor, + rect.x, rect.y, + rect.width, rect.height); + } + } + else { + rect.x = anchor.ulx - 1; + rect.y = anchor.lry + offset; + rect.width = anchor.lrx - anchor.ulx + 2; + rect.height = rule_height; + if (clip_region_to_rect(&rect)) { + XFillRectangle(DISP, mane.win, + visited ? globals.gc.visited_linkcolor : globals.gc.linkcolor, + rect.x, rect.y, + rect.width, rect.height); + } + } + } + } +} + +void +launch_xdvi(const char *filename, const char *anchor_name) +{ +#define ARG_LEN 32 + int i = 0; + char *argv[ARG_LEN]; + char *shrink_arg = NULL; + + ASSERT(filename != NULL, "filename argument to launch_xdvi() mustn't be NULL"); + + argv[i++] = program_invocation_name; + /* FIXME: there's something broken with this and invoking xdvi.bin. + To reproduce the problem, invoke from the shell: + xdvi.bin -geometry 829x1172 /usr/share/texmf/doc/programs/kpathsea.dvi + this will run at 300dpi, i.e. ignore an .Xdefaults setting as opposed to: + xdvi.bin /usr/share/texmf/doc/programs/kpathsea.dvi + + Also, how about the other command-line switches that might have + been passed to the parent instance? How about things that have been changed + at run-time, like shrink factor - should they be converted to command-line + options? + */ + + argv[i++] = "-name"; + argv[i++] = "xdvi"; + + /* start the new instance with the same debug flags as the current instance */ + if (globals.debug != 0) { + char debug_flag[128]; + SNPRINTF(debug_flag, 128, "%lu", globals.debug); + argv[i++] = "-debug"; + argv[i++] = debug_flag; + } + + if (anchor_name != NULL) { + argv[i++] = "-anchorposition"; + argv[i++] = (char *)anchor_name; + } + + argv[i++] = "-s"; + shrink_arg = XMALLOC(shrink_arg, LENGTH_OF_INT + 1); + sprintf(shrink_arg, "%d", currwin.shrinkfactor); + argv[i++] = shrink_arg; + + argv[i++] = (char *)filename; /* FIXME */ + + argv[i++] = NULL; + ASSERT(i <= ARG_LEN, "Too few elements in argv[]"); + + if (globals.debug & DBG_HTEX) { + fprintf(stderr, "Invoking:\n"); + for (i = 0; argv[i]; i++) { + fprintf(stderr, "%s\n", argv[i]); + } + } + + /* + FIXME: using fork_process here hangs the new xdvi process when it tries + printing to stderr, so we use plain fork/exec (see comments in util.c) + */ +#if 0 + fork_process(argv[0], False, NULL, NULL, NULL, argv); +#else + { + int pid; + switch (pid = fork()) { + case -1: + perror("fork"); + case 0: + execvp(argv[0], argv); + MYTRACE((stderr, "%s: Execution of %s failed.", globals.program_name, argv[0])); + _exit(EXIT_FAILURE); + default: + FREE(shrink_arg); + } + } +#endif +#undef ARG_LEN +} + +#if COPY_TMP_FILE +/* callback to remove temporary file after helper application has terminated. + Currently unused. Note the possible danger of a temp race if the application + does an fclose() on the file before ... +*/ +static void +remove_temp_file(int status, struct xchild *this) +{ + char *pathname = (char *)this->data; + if (WEXITSTATUS(status) == 0) { + unlink(pathname); + } + else { + popup_message(globals.widgets.top_level, + MSG_WARN, NULL, + "Warning: Calling `%s' on `%s' terminated with non-zero status (%d)" + "\n(Not removing `%s')", + this->name, pathname, status, pathname); + } + free(pathname); +} +#endif + +void +launch_program(const char *filename) +{ + const char *format_string = NULL; + char *content_type = NULL; + char *viewer = NULL; + char *argv[4]; + struct stat statbuf; + const char *local_filename_sans_prefix; + char *path, *syscmd, *tmp; +#if COPY_TMP_FILE + int tmp_fd; +#endif + const char *ptr; + char *fullpath = NULL; + char canonical_path[MAXPATHLEN + 1]; + Boolean needs_terminal; + + TRACE_HTEX((stderr, "launch_program called with |%s|", filename)); + + /* is it a local file? */ + local_filename_sans_prefix = is_local_file(filename); + if (local_filename_sans_prefix == NULL) { /* not local */ + launch_browser(filename); + return; + } + + /* expand the filename if it contains a relative path */ + path = find_file(local_filename_sans_prefix, &statbuf, kpse_program_text_format); + if (path != NULL) { + /* fully canonicalize path before passing it to helper applications */ + fullpath = REALPATH(path, canonical_path); + if (fullpath == NULL) { + XDVI_WARNING((stderr, "Couldn't canonicalize %s to full path - returning unexpanded.", + path)); + fullpath = path; + } + else + FREE(path); + } + else { + XDVI_WARNING((stderr, "Couldn't find file %s; passing to application unchanged.", + local_filename_sans_prefix)); + /* if it doesn't exist, maybe the user has encoded some magic in the + filename; in that case, pass it to the application unchanged. */ + fullpath = (char *)local_filename_sans_prefix; + } + + TRACE_HTEX((stderr, "fullpath: |%s|", fullpath)); + content_type = figure_mime_type(fullpath); + ASSERT(content_type != NULL, "figure_mime_type() should have returned a valid type (eventually a fallback)"); + + /* make it safe to pass the argument to system() or `sh -c', + by escaping all `dangerous' characters: */ + tmp = shell_escape_string(fullpath); + fullpath = tmp; + + if ((viewer = figure_viewer(content_type, &format_string, &needs_terminal, fullpath)) == NULL) { + /* warn user if no application has been found. Help text needs to be constructed at + run-time, since we want the correct content-type in it. */ +#if 0 + char *errmsg = NULL; +#else + char *errmsg = xstrdup("Please assign an application to the MIME type `"); + errmsg = xstrcat(errmsg, content_type); + errmsg = xstrcat(errmsg, "' in your ~/.mailcap file. " + "E.g. if you want to view the file with netscape, add the following line to your ~/.mailcap:\n"); + errmsg = xstrcat(errmsg, content_type); + errmsg = xstrcat(errmsg, "; netscape -raise -remote 'openURL(%%s,new-window)'\n\n"); +#endif + popup_message(globals.widgets.top_level, + MSG_WARN, + errmsg, + "Could not determine an application for the file %s, MIME type `%s'.", + fullpath, content_type); + /* FIXME: something's wrong with the memory allocation scheme here - + can't free errmsg, it's already free'd inside popup_message() ?? */ + /* free(errmsg); */ + free(fullpath); + return; + } + +#if COPY_TMP_FILE + /* copy the file to a temporary location before passing it to the + viewer. This is e.g. how Acroread does it. This should fix the + problem with xdvizilla without -no-rm for DVI files (see comment + in figure_viewer(), mime.c). + SU 2003/10/02: currently I can't reproduce the xdvizilla problem + any more - it seems that for xdvi files, argv[0] will be invoked + anyway? Also, copying the files to tmp may break figure locations + and relative links for DVI files. Suspended this for the time being. + */ + tmp = NULL; + if ((tmp_fd = xdvi_temp_fd(&tmp)) == -1) { + XDVI_ERROR((stderr, "couldn't create temporary file; not calling helper application.")); + return; + } + TRACE_HTEX((stderr, "copying to temporary location: |%s->%s|", fullpath, tmp)); + if (!copy_file(fullpath, tmp)) { + XDVI_ERROR((stderr, "couldn't copy %s to temporary file %s; not invoking helper application.", + fullpath, tmp)); + return; + } + free(fullpath); + fullpath = tmp; +#endif + + if (strlen(format_string) > 0) { + ptr = find_format_str(viewer, format_string); + } + else { /* countrary to RFC 1343, we don't pass stuff via pipes (too bothersome) */ + ptr = strchr(viewer, '\0'); + } + + if (needs_terminal) { + size_t offset = strlen("xterm -e "); + syscmd = xmalloc(offset + strlen(viewer) + strlen(fullpath) + 1); + strcpy(syscmd, "xterm -e "); + memcpy(syscmd + offset, viewer, ptr - viewer); + strcpy(syscmd + offset + (ptr - viewer), fullpath); + strcpy(syscmd + offset + (ptr - viewer) + strlen(fullpath), ptr + strlen(format_string)); + + } + else { + syscmd = xmalloc(strlen(viewer) + strlen(fullpath) + 1); + memcpy(syscmd, viewer, ptr - viewer); + strcpy(syscmd + (ptr - viewer), fullpath); + strcpy(syscmd + (ptr - viewer) + strlen(fullpath), ptr + strlen(format_string)); + } + + /* + mailcap(4) says that the mailcap command shall be passed to system(), + so we musn't use fork_process() directly here. Instead, we pass the command + to `/bin/sh -c', but via fork_process so that + + - xdvi doesn't hang if the process doesn't return; + - we can still catch the return value if `sh -c' exits with an error + (e.g. if the viewer command is not found). + + Note however that this doesn't mimick most of POSIX's system() semantics. + */ + TRACE_HTEX((stderr, "execv(\"/bin/sh -c %s\")", syscmd)); + argv[0] = "/bin/sh"; + argv[1] = "-c"; + argv[2] = syscmd; + argv[3] = NULL; +#if COPY_TMP_FILE + fork_process("/bin/sh", False, globals.dvi_file.dirname, remove_temp_file, fullpath, argv); +#else + fork_process("/bin/sh", False, globals.dvi_file.dirname, NULL, NULL, argv); +#endif + + FREE(viewer); +#if COPY_TMP_FILE +#else + FREE(fullpath); +#endif + FREE(syscmd); +} + +void +htex_set_anchormarker(int y) +{ + Position drawing_x; + + /* erase old marker if it's on the same page as the new marker, after + cancelling the old timeout so that it won't affect the new marker */ + if (m_href_timeout_id) + XtRemoveTimeOut(m_href_timeout_id); + htex_erase_anchormarker(NULL, NULL); + XFlush(DISP); + + XtVaGetValues(globals.widgets.draw_widget, XtNx, &drawing_x, NULL); + g_anchormarker.page = current_page; + free(g_anchormarker.filename); + g_anchormarker.filename = xstrdup(globals.dvi_name); + g_anchormarker.y_pos = y; + g_anchormarker.x_pos = DEFAULT_MARKER_X_OFFSET + -drawing_x; + m_href_timeout_id = XtAppAddTimeOut(app, STATUS_SHORT * 1000, htex_erase_anchormarker, (XtPointer)NULL); +} + +static void +htex_draw_anchormarker(int y) +{ + int x; + XPoint points[3]; + + Position drawing_x, clip_w; + + XtVaGetValues(globals.widgets.clip_widget, XtNwidth, &clip_w, NULL); + XtVaGetValues(globals.widgets.draw_widget, XtNx, &drawing_x, NULL); + + /* compute offset to draw into visible region */ + x = DEFAULT_MARKER_X_OFFSET + -drawing_x; + + /* Eventually erase old marker, to avoid `smearing' on horizontal scrolls. */ + if (x != g_anchormarker.x_pos) { + clearexpose(&mane, g_anchormarker.x_pos - 1, y - 3, 20, 10); + } + g_anchormarker.x_pos = x; + + points[0].x = x + 10; + points[1].x = x + 19; + points[2].x = x + 10; + points[0].y = y - 3; + points[1].y = y + 2; + points[2].y = y + 7; + + XFillRectangle(DISP, mane.win, globals.gc.visited_linkcolor, x, y, 10, 4); + XFillPolygon(DISP, mane.win, globals.gc.visited_linkcolor, points, 3, Convex, CoordModeOrigin); + /* -1 indicates that no horizontal scrolling is wanted, since the + anchormarker will always be horizontally positioned inside the + visible area. + */ + scroll_page_if_needed(-1, -1, y + 3, y - 3); +} + + +static void +htex_erase_anchormarker(XtPointer client_data, XtIntervalId *id) +{ + Position clip_w; + + UNUSED(client_data); + UNUSED(id); + + if (globals.debug & DBG_EVENT) { + fprintf(stderr, "htex_erase_anchormarker called!\n"); + } + + if (m_href_timeout_id == (XtIntervalId)0) { /* timeout was removed but callback happened anyway */ + return; + } + + m_href_timeout_id = (XtIntervalId)0; + /* clear the mark if we're in the same file and on the same page as the mark */ + if (g_anchormarker.filename != NULL + && strcmp(globals.dvi_name, g_anchormarker.filename) == 0 + && g_anchormarker.page == current_page) { + XtVaGetValues(globals.widgets.clip_widget, XtNwidth, &clip_w, NULL); + clearexpose(&mane, 0, g_anchormarker.y_pos - 3, clip_w, 10); + } + g_anchormarker.y_pos = -1; +} + |