summaryrefslogtreecommitdiff
path: root/Build/source/texk/xdvik/hypertex.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/xdvik/hypertex.c')
-rw-r--r--Build/source/texk/xdvik/hypertex.c2165
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;
+}
+