1 
2 /*
3  * Permission is hereby granted, free of charge, to any person obtaining a copy
4  * of this software and associated documentation files (the "Software"), to
5  * deal in the Software without restriction, including without limitation the
6  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7  * sell copies of the Software, and to permit persons to whom the Software is
8  * furnished to do so, subject to the following conditions:
9  *
10  * The above copyright notice and this permission notice shall be included in
11  * all copies or substantial portions of the Software.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16  * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
17  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20  */
21 
22 /*
23  * Original copyright:
24  *
25  * Hypertex modifications to DVI previewer for X.
26  * This portion of xhdvi is completely in the public domain. The
27  * author renounces any copyright claims. It may be freely used for
28  * commercial or non-commercial purposes. The author makes no claims
29  * or guarantees - use this at your own risk.
30  *
31  * Arthur Smith, U. of Washington, 1994
32  *
33  * 5/1994       code written from scratch, probably inspired by (but
34  *                incompatible with) the CERN WWW library.
35  *
36  * 3/1995       CERN WWW library called to do document fetching.
37  *
38  * 5/2002	SU: rewritten from scratch to make it work with the new
39  *		event handling in xdvi >= 22.41: Anchors are now saved
40  *		per page instead of per document, making it more robust
41  *		wrt. interrupting the drawing and skipping to another page.
42  *		Only drawback is that links breaking over pages are more
43  *		difficult to handle (but they didn't work properly before either).
44  *		Clicking on a link now performs a search for the
45  *		corresponding anchor similiar to `forward search'.
46  *
47  *		Removed libwww, since it has become too bloated
48  *		and buggy, and had only rudimentary support for GUI
49  *		interaction to begin with (e.g., interrupting a
50  *		download). And since this is 2002, we might just as well
51  *		use a web browser to fetch remote documents ;-)
52  */
53 
54 /* TODO:
55 
56 - implement popup with list of visited links, as specified in
57 http://xdvi.sourceforge.net/gui.html#navigation-openLinks
58 (maybe this should be a simple menu list, not a popup window).
59 */
60 
61 #define COPY_TMP_FILE 0 /* see comments below */
62 
63     /* #define DEBUG */
64 
65     /*  #define DEBUG_MEMORY_HANDLING */
66 #include "alloc-debug.h"
67 
68 #include "xdvi-config.h"
69 #include "xdvi.h"
70 
71 #include <string.h>
72 
73 #include <ctype.h>
74 #include "kpathsea/c-fopen.h"
75 #include "kpathsea/c-stat.h"
76 #include <X11/StringDefs.h> /* for XtNwidth, XtNheight */
77 
78 #include "events.h"
79 #include "dvi-init.h"
80 #include "message-window.h"
81 #include "util.h"
82 #include "dl_list.h"
83 #include "x_util.h"
84 #include "mime.h"
85 #include "mag.h"
86 #include "dvi-draw.h"
87 #include "statusline.h"
88 #include "browser.h"
89 #include "hypertex.h"
90 #include "special.h"
91 #include "string-utils.h"
92 #include "xm_toolbar.h"
93 #include "my-snprintf.h"
94 #include "pagehist.h"
95 
96     /* globals */
97     /* rgb specifications to translate resource.{visited_}link_color into */
98     char *g_link_color_rgb = NULL;
99 char *g_visited_link_color_rgb = NULL;
100 char *g_anchor_pos = NULL;
101 size_t g_anchor_len = 0;
102 
103 static const int DEFAULT_MARKER_X_OFFSET = 2; /* horizontal offset of anchor marker to edge of screen */
104 static const int HTEX_ALLOC_STEP = 32;
105 
106 /* distance of underline from lower edge of character bbox */
107 static const int ANCHOR_XTRA_V_BBOX = 6;
108 
109 /* info whether we're dealing with hypertex or hdvips type links */
110 typedef enum { HYPERTEX_LINK, HDVIPS_LINK } hrefLinkT;
111 static hrefLinkT m_href_type;
112 
113 typedef enum {
114     A_HREF = 0,
115     A_NAME,
116     A_HREF_FILE,	/* hdvips file ref */
117     A_HREF_URL,		/* hdvips URL ref */
118     A_HDVIPS_INTERNAL,	/* internal ref */
119     A_HDVIPS_HREF,	/* hdvips href */
120     A_HDVIPS_NAME,	/* hdvips name */
121     A_OTHER,
122     A_END,
123     A_NONE,
124     A_MISMATCH
125 } htexAnchorT;
126 
127 /****************************** helper routines ******************************/
128 
129 static Boolean
htex_is_href(htexAnchorT type)130 htex_is_href(htexAnchorT type)
131 {
132     return type == A_HREF
133 	|| type == A_HREF_FILE
134 	|| type == A_HREF_URL
135 	|| type == A_HDVIPS_HREF
136 	|| type == A_HDVIPS_INTERNAL;
137 }
138 
139 static Boolean
htex_is_name(htexAnchorT type)140 htex_is_name(htexAnchorT type)
141 {
142     return type == A_NAME || type == A_HDVIPS_NAME;
143 }
144 
145 static void
parse_html_special(const char * special,size_t len,const char ** beg,const char ** end)146 parse_html_special(const char *special, size_t len, const char **beg, const char **end)
147 {
148     *beg = special;
149     while (isspace((int)**beg) || **beg == '=') {
150 	(*beg)++;
151 	len--;
152     }
153     *end = *beg + len - 1;
154 
155     while (isspace((int)**end) || **end == '>')
156 	(*end)--;
157 
158     /* remove quote pairs */
159     if (**beg == '"') {
160 	(*beg)++;
161 	if (**end == '"') {
162 	    (*end)--;
163 	}
164     }
165     /* now end points to last anchor char, move behind that */
166     (*end)++;
167 }
168 
169 /*
170   Anchor prescanning stuff: We make a pass through all pages in the document
171   before displaying them to collect information about mismatched anchors
172   (anchors where the opening tag is on some previous page).
173   The information is stored in the following struct:
174 */
175 struct prescan_info {
176     int anchor_depth;
177     size_t anchor_num;
178     size_t anchor_list_size;
179     int *anchor_list;
180     /* pointers to either NULL, or to mismatched anchor strings
181        (the contents of '<a href="...">' on some previous page),
182        for each page */
183     size_t pagelist_len;
184     char **pagelist;
185 };
186 
187 static struct prescan_info m_prescan_info = { 0, 0, 0, NULL, 0, NULL };
188 static struct prescan_info m_save_prescan_info = { 0, 0, 0, NULL, 0, NULL };
189 
190 
191 /*
192   Save prescan info from previous page, in case we only have a partial
193   scan of the current page and need to start all over.
194 */
195 void
htex_prescan_save(void)196 htex_prescan_save(void)
197 {
198     m_save_prescan_info.anchor_depth = m_prescan_info.anchor_depth;
199     m_save_prescan_info.anchor_num = m_prescan_info.anchor_num;
200     /* copy over anchor list */
201     while (m_save_prescan_info.anchor_depth >= (int)m_save_prescan_info.anchor_list_size) {
202 	int i;
203 	m_save_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
204 	m_save_prescan_info.anchor_list = XREALLOC(m_save_prescan_info.anchor_list,
205 						   m_save_prescan_info.anchor_list_size
206 						   * sizeof *(m_save_prescan_info.anchor_list));
207 	for (i = 0; i < m_save_prescan_info.anchor_depth; i++) {
208 	    m_save_prescan_info.anchor_list[i] = m_prescan_info.anchor_list[i];
209 	}
210     }
211     /* the page list of anchor strings can only have had its current position modified;
212        we'll simply delete that position in htex_prescan_restore().
213     */
214 }
215 
216 /*
217   Restore prescan info from m_save_prescan_info.
218 */
219 void
htex_prescan_restore(int pageno)220 htex_prescan_restore(int pageno)
221 {
222     m_prescan_info.anchor_depth = m_save_prescan_info.anchor_depth;
223     m_prescan_info.anchor_num = m_save_prescan_info.anchor_num;
224     /* copy back anchor list */
225     if (m_save_prescan_info.anchor_depth > 0) {
226 	int i;
227 	/* old list might have been free()d in htex_prescan_reset_firstpass */
228 	while (m_save_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) {
229 	    m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
230 	    m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list,
231 						  m_prescan_info.anchor_list_size
232 						  * sizeof *(m_prescan_info.anchor_list));
233 	}
234 	for (i = 0; i < m_save_prescan_info.anchor_depth; i++) {
235 	    m_prescan_info.anchor_list[i] = m_save_prescan_info.anchor_list[i];
236 	}
237     }
238     /* reset anchor string info for this page */
239     if ((int)m_prescan_info.pagelist_len > pageno) {
240 	ASSERT(m_prescan_info.pagelist != NULL, "m_prescan_info.pagelist not properly allocated");
241 	FREE(m_prescan_info.pagelist[pageno]);
242 	m_prescan_info.pagelist[pageno] = NULL;
243     }
244 }
245 
246 /*
247   Reset prescan info for first pass (anchor numbers only) to its initial state,
248   freeing up allocated resources for anchor_list.
249 
250 */
251 void
htex_prescan_reset_firstpass(void)252 htex_prescan_reset_firstpass(void)
253 {
254     MYTRACE((stderr, "resetting anchor_depth to 0!!!!!!!!!!!!"));
255     m_prescan_info.anchor_depth = 0;
256     m_prescan_info.anchor_num = 0;
257     m_prescan_info.anchor_list_size = 0;
258     FREE(m_prescan_info.anchor_list);
259     m_prescan_info.anchor_list = NULL;
260 }
261 
262 /*
263   Reset the prescan info, freeing up all allocated memory.
264   Used when e.g. switching to a different file via Ctrl-F.
265 */
266 static void
htex_prescan_reset(void)267 htex_prescan_reset(void)
268 {
269     size_t i;
270     m_prescan_info.anchor_depth = 0;
271     m_prescan_info.anchor_num = 0;
272     m_prescan_info.anchor_list_size = 0;
273 
274     free(m_prescan_info.anchor_list);
275     m_prescan_info.anchor_list = NULL;
276 
277     for (i = 0; i < m_prescan_info.pagelist_len; i++) {
278  	free(m_prescan_info.pagelist[i]);
279     }
280     free(m_prescan_info.pagelist);
281     m_prescan_info.pagelist = NULL;
282     m_prescan_info.pagelist_len = 0;
283 }
284 
285 /*
286   Initialize m_prescan_info for scanning a new page.
287 */
288 void
htex_prescan_initpage(void)289 htex_prescan_initpage(void)
290 {
291     m_prescan_info.anchor_num = 0;
292 }
293 
294 
295 Boolean
htex_prescan_special(const char * cp,int cp_len,struct htex_prescan_data * data)296 htex_prescan_special(const char *cp, int cp_len, struct htex_prescan_data *data)
297 {
298     UNUSED(cp_len);
299     ASSERT(data != NULL, "data argument to htex_prescan_special() mustn't be NULL");
300     if (data->pageno + 1 < (int)m_prescan_info.pagelist_len) { /* already scanned this page */
301 	MYTRACE((stderr, "already scanned page %d", data->pageno+1));
302 	return False;
303     }
304 
305     /* resize pagelist */
306     while (data->pageno >= (int)m_prescan_info.pagelist_len) {
307 	size_t old_len = m_prescan_info.pagelist_len;
308 	size_t i;
309 	MYTRACE((stderr, "============ resizing pagelist to %d", data->pageno + 1));
310 	m_prescan_info.pagelist_len = data->pageno + 1;
311 	m_prescan_info.pagelist = XREALLOC(m_prescan_info.pagelist,
312 					   m_prescan_info.pagelist_len * sizeof *m_prescan_info.pagelist);
313 	/* initialize with NULL values */
314 	for (i = old_len; i < m_prescan_info.pagelist_len; i++) {
315 	    MYTRACE((stderr, "============ initializing pagelist %d", i));
316 	    m_prescan_info.pagelist[i] = NULL;
317 	}
318     }
319 
320     if (data->scan_type == HTEX_ANCHOR_STRING) {
321 	if ((int)m_prescan_info.anchor_num == data->anchor_num) {
322 	    char *anchor;
323 	    const char *beg, *end;
324 	    const char *cp1 = cp;
325 	    const char *ptr = NULL;
326 	    const char *pptr = NULL;
327 
328 	    if (memicmp(cp1, "<a href", strlen("<a href")) == 0) {
329 		cp1 += strlen("<a href");
330 		parse_html_special(cp1, strlen(cp1), &beg, &end);
331 		anchor = xmalloc(end - beg + 1);
332 		memcpy(anchor, beg, end - beg);
333 		anchor[end - beg] = '\0';
334 		/* save the anchor string in m_prescan_info.pagelist[<current_page>] */
335 		m_prescan_info.pagelist[data->pageno] = anchor;
336 	    }
337 	    else if (memcmp(cp1, "/A", 2) == 0) {
338 		if ((ptr = strstr(cp1 + 2, "/GoToR")) != NULL /* external file */
339 		    && (ptr = strchr(ptr, '(')) != NULL
340 		    && (pptr = strchr(ptr + 1, '(')) != NULL) {
341 		    anchor = xmalloc(pptr - ptr);
342 		    memcpy(anchor, ptr + 1, pptr - ptr - 1);
343 		    anchor[pptr - ptr - 1] = '\0';
344 		    /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
345 		    m_prescan_info.pagelist[data->pageno] = anchor;
346 		}
347 		else if ((ptr = strstr(cp1 + 2, "/URI")) != NULL /* external file */
348 			 && (ptr = strchr(ptr, '(')) != NULL
349 			 && (pptr = strchr(ptr + 1, '(')) != NULL) {
350 		    anchor = xmalloc(pptr - ptr);
351 		    memcpy(anchor, ptr + 1, pptr - ptr - 1);
352 		    anchor[pptr - ptr - 1] = '\0';
353 		    /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
354 		    m_prescan_info.pagelist[data->pageno] = anchor;
355 		}
356 	    }
357 	    else if (memcmp(cp, "/L", 2) == 0) {
358 		if ((ptr = strstr(cp1 + 2, "/Dest")) != NULL /* internal link */
359 		    && (ptr = strchr(ptr, '(')) != NULL
360 		    && (pptr = strchr(ptr + 1, '(')) != NULL) {
361 		    anchor = xmalloc(pptr - ptr);
362 		    memcpy(anchor, ptr + 1, pptr - ptr - 1);
363 		    anchor[pptr - ptr - 1] = '\0';
364 		    /* save the anchor string in m_prescan_info.pagelist[<current_page>] */
365 		    m_prescan_info.pagelist[data->pageno] = anchor;
366 		}
367 	    }
368 	}
369     }
370     if (memicmp(cp, "<a ", 3) == 0) {
371 	while (m_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) {
372 	    m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
373 	    m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list,
374 						  m_prescan_info.anchor_list_size
375 						  * sizeof *(m_prescan_info.anchor_list));
376 	}
377 	ASSERT(m_prescan_info.anchor_depth >= 0, "List should contain previous anchor info");
378 	m_prescan_info.anchor_list[m_prescan_info.anchor_depth] = m_prescan_info.anchor_num;
379 	m_prescan_info.anchor_depth++;
380 	m_prescan_info.anchor_num++;
381     }
382     else if (memicmp(cp, "</a", 3) == 0) {
383 	if (m_prescan_info.anchor_depth < 1) {
384 	    /* this can happen when stuff had been prescanned before */
385 	    return False;
386 	}
387 	m_prescan_info.anchor_depth--;
388 	m_prescan_info.anchor_num++;
389     }
390     else if (memcmp(cp, "H.S end", strlen("H.S end")) == 0) { /* start of anchor */
391 	while (m_prescan_info.anchor_depth >= (int)m_prescan_info.anchor_list_size) {
392 	    m_prescan_info.anchor_list_size += HTEX_ALLOC_STEP;
393 	    m_prescan_info.anchor_list = XREALLOC(m_prescan_info.anchor_list,
394 						  m_prescan_info.anchor_list_size
395 						  * sizeof *(m_prescan_info.anchor_list));
396 	}
397 	ASSERT(m_prescan_info.anchor_depth >= 0, "List should contain previous anchor info");
398 	m_prescan_info.anchor_list[m_prescan_info.anchor_depth] = m_prescan_info.anchor_num;
399 	m_prescan_info.anchor_depth++;
400 	m_prescan_info.anchor_num++;
401 
402     }
403     else if (memcmp(cp, "H.R end", strlen("H.R end")) == 0 /* end of rect */
404 	     || memcmp(cp, "H.A end", strlen("H.A end")) == 0 /* end of anchor */
405 	     || memcmp(cp, "H.L end", strlen("H.L end")) == 0 /* end of link */
406 	     ) {
407 	if (m_prescan_info.anchor_depth < 1) {
408 	    /* this can happen when stuff had been prescanned before */
409 	    return False;
410 	}
411 	m_prescan_info.anchor_depth--;
412 	m_prescan_info.anchor_num++;
413     }
414     return False;
415 }
416 
417 int
htex_prescan_get_depth(void)418 htex_prescan_get_depth(void)
419 {
420     return m_prescan_info.anchor_depth;
421 }
422 
423 /* copy over pagelist from old_page to new_page */
424 void
htex_prescan_carry_over(int old_page,int new_page)425 htex_prescan_carry_over(int old_page, int new_page)
426 {
427     ASSERT(old_page >= 0, "old_page out of range");
428     ASSERT(old_page < (int)m_prescan_info.pagelist_len, "old_page out of range");
429 
430     /* resize if needed */
431     if (new_page >= (int)m_prescan_info.pagelist_len) {
432 	size_t old_len = m_prescan_info.pagelist_len;
433 	size_t i;
434 	m_prescan_info.pagelist_len = new_page + 1;
435 	m_prescan_info.pagelist = XREALLOC(m_prescan_info.pagelist,
436 					   m_prescan_info.pagelist_len * sizeof *m_prescan_info.pagelist);
437 	/* initialize with NULL values */
438 	for (i = old_len; i < m_prescan_info.pagelist_len; i++) {
439 	    m_prescan_info.pagelist[i] = NULL;
440 	}
441     }
442     free(m_prescan_info.pagelist[new_page]);
443     /* don't share pointers here */
444     if (m_prescan_info.pagelist[old_page] != NULL) {
445 	m_prescan_info.pagelist[new_page] = xstrdup(m_prescan_info.pagelist[old_page]);
446     }
447     else {
448 	m_prescan_info.pagelist[new_page] = NULL;
449     }
450 }
451 
452 size_t
htex_prescan_get_mismatched_anchor_num(size_t depth)453 htex_prescan_get_mismatched_anchor_num(size_t depth)
454 {
455     ASSERT((int)depth <= m_prescan_info.anchor_depth, "depth too large");
456     ASSERT(depth <= m_prescan_info.anchor_list_size, "depth too large for lookup list");
457 
458     return m_prescan_info.anchor_list[m_prescan_info.anchor_depth - 1];
459 }
460 
461 /******************************* end of prescan stuff ******************************/
462 
463 struct anchor_marker {
464     int page;	/* page on which marker is located */
465     char *filename; /* file in which the marker is located */
466     int y_pos;  /* vertical position for marker */
467     int x_pos;  /* horizontal position for marker */
468 } g_anchormarker = { -1, NULL, -1, -1 };
469 
470 static XtIntervalId m_href_timeout_id = 0;
471 
472 
473 struct history_info {
474     char *anchor;	/* anchor name */
475     char *filename;	/* name of file in which this anchor is located */
476     int page;		/* pagenumber on which anchor is located */
477 };
478 
479 struct anchor_info {
480     /* anchor contents as strings */
481     char *a_name;
482     char *a_href;
483     htexObjectT object_type;
484     /* bounding box info for this anchor */
485     int lrx, lry;	/* lower-right corner */
486     int ulx, uly;	/* upper-left corner */
487     /*     int refpage;	/\* page in DVI file for stack of visited anchors *\/ */
488     /*     char *filename;		/\* name of file in which this anchor is located, for visited anchors *\/ */
489     int prev_wrapped;	/* index of prev elem, for wrapped hrefs, or -1 */
490     int next_wrapped;	/* index of next elem, for wrapped hrefs, or -1 */
491 };
492 
493 struct htex_page_info {
494     struct anchor_info *anchors;	/* anchor info */
495     int tot_cnt;		/* anchor info size */
496     int curr_cnt;		/* current number of anchor on page */
497     int page;			/* page in DVI file this anchor info refers to */
498     int have_wrapped;		/* -1 if no wrapped anchor on beginning of page, or index of wrapped anchor */
499 };
500 
501 /* file-scope globals ... */
502 /* record x and y positions of current anchor, to recognize linebreaks in anchors */
503 static int x_pos_bak = 0;
504 static int y_pos_bak = 0;
505 /* holds all anchors on current page */
506 static struct htex_page_info htex_page_info = { NULL, 0, 0, -1, -1 };
507 
508 /*
509   double linked list with history of clicked links, for htex_forward/htex_back
510 */
511 static struct dl_list *htex_history = NULL;	/* current list insertion point */
512 
513 struct htex_anchor_stack_elem {
514     htexAnchorT type;	/* type of anchor (href, anchor, ...) */
515     int anchor_num;		/* number of this anchor on page (same as curr_cnt in struct htex_page_info) */
516 };
517 
518 /*
519  * stack datatype and access functions; used for nested anchors
520  */
521 struct htex_anchor_stack {
522     size_t size;
523     size_t depth;
524     struct htex_anchor_stack_elem *types;
525 };
526 
527 struct visited_anchor {
528     int *list;
529     size_t list_size;
530 };
531 
532 struct visited_anchors {
533     struct visited_anchor *anchors;
534     size_t size;
535 };
536 
537 static struct visited_anchors visited_links = { NULL, 0 };
538 
539 static struct htex_anchor_stack stack = { 0, 0, NULL };
540 
541 
542 
543 /* the following list is from
544    http://www.iana.org/assignments/uri-schemes
545    re-sorted for likeliness. All protocols except for `file'
546    are considered remote.
547    (i.e. not accessible via the ordinary Unix file system)
548 */
549 static const char *remote_URL_schemes[] = {
550     "http:",            /* Hypertext Transfer Protocol                    [RFC2068] */
551     "ftp:",             /* File Transfer Protocol                         [RFC1738] */
552     "https:",           /* Hypertext Transfer Protocol Secure             [RFC2818] */
553     "mailto:",          /* Electronic mail address                        [RFC2368] */
554     "news:",            /* USENET news                                    [RFC1738] */
555     "nntp:",            /* USENET news using NNTP access                  [RFC1738] */
556     "telnet:",          /* Reference to interactive sessions              [RFC1738] */
557     "nfs:",             /* network file system protocol                   [RFC2224] */
558     "gopher:",          /* The Gopher Protocol                            [RFC1738] */
559     "wais:",            /* Wide Area Information Servers                  [RFC1738] */
560     /* the only exception: */
561     /* "file:", */      /* Host-specific file names                       [RFC1738] */
562     "prospero:",        /* Prospero Directory Service                     [RFC1738] */
563     "z39.50s",          /* Z39.50 Session                                 [RFC2056] */
564     "z39.50r",          /* Z39.50 Retrieval                               [RFC2056] */
565     "cid:",             /* content identifier                             [RFC2392] */
566     "mid:",             /* message identifier                             [RFC2392] */
567     "vemmi:",           /* versatile multimedia interface                 [RFC2122] */
568     "service:",         /* service location                               [RFC2609] */
569     "imap:",            /* internet message access protocol               [RFC2192] */
570     "acap:",            /* application configuration access protocol      [RFC2244] */
571     "rtsp:",            /* real time streaming protocol                   [RFC2326] */
572     "tip:",             /* Transaction Internet Protocol                  [RFC2371] */
573     "pop:",             /* Post Office Protocol v3                        [RFC2384] */
574     "data:",            /* data                                           [RFC2397] */
575     "dav:",             /* dav                                            [RFC2518] */
576     "opaquelocktoken:", /* opaquelocktoken                                [RFC2518] */
577     "sip:",             /* session initiation protocol                    [RFC2543] */
578     "tel:",             /* telephone                                      [RFC2806] */
579     "fax:",             /* fax                                            [RFC2806] */
580     "modem:",           /* modem                                          [RFC2806] */
581     "ldap:",            /* Lightweight Directory Access Protocol          [RFC2255] */
582     "soap.beep:",       /* soap.beep                                      [RFCSOAP] */
583     "soap.beeps:",      /* soap.beeps                                     [RFCSOAP] */
584     /* Reserved URI Scheme Names: */
585     "afs:",             /* Andrew File System global file names			 */
586     "tn3270:",          /* Interactive 3270 emulation sessions			 */
587     "mailserver:",      /* Access to data available from mail servers		 */
588     NULL
589 };
590 
591 /* prototypes */
592 static void htex_erase_anchormarker(XtPointer client_data, XtIntervalId *id);
593 static void htex_draw_anchormarker(int y);
594 
595 
596 static void
resize_info_if_needed(struct htex_page_info * info)597 resize_info_if_needed(struct htex_page_info *info)
598 {
599     /* resize info if needed */
600     if (info->curr_cnt + 2 >= info->tot_cnt) {
601 	int i;
602 	while (info->curr_cnt + 2 >= info->tot_cnt) {
603 	    info->tot_cnt += HTEX_ALLOC_STEP;
604 	}
605 	info->anchors = XREALLOC(info->anchors, info->tot_cnt * sizeof *(info->anchors));
606 	for (i = info->curr_cnt; i < info->tot_cnt; i++) {
607 	    /*  	    fprintf(stderr, "initializing info at index %d\n", i); */
608 	    info->anchors[i].a_href = NULL;
609 	    info->anchors[i].a_name = NULL;
610 	    /* 	    info->anchors[i].filename = NULL; */
611 	    info->anchors[i].ulx = INT_MAX;
612 	    info->anchors[i].uly = INT_MAX;
613 	    info->anchors[i].lrx = 0;
614 	    info->anchors[i].lry = 0;
615 	    info->anchors[i].object_type = HTEX_TEXT;
616 	    info->anchors[i].prev_wrapped = -1;
617 	    info->anchors[i].next_wrapped = -1;
618 	}
619     }
620 }
621 
622 static void
init_visited_links(struct visited_anchors * links,int total_pages,Boolean new_dvi_file)623 init_visited_links(struct visited_anchors *links, int total_pages, Boolean new_dvi_file)
624 {
625     size_t i, old_size = links->size;
626     if (new_dvi_file) {
627 	/* free old list */
628 	for (i = 0; i < old_size; i++) {
629 	    FREE(links->anchors[i].list);
630 	    links->anchors[i].list = NULL;
631 	    links->anchors[i].list_size = 0;
632 	}
633     }
634     if (links->size <= (size_t)total_pages) {
635 	MYTRACE((stderr, "resetting visited links (%d != %d)", links->size, total_pages));
636 	links->size = total_pages + 1;
637 	links->anchors = XREALLOC(links->anchors, (links->size + 1) * sizeof *(links->anchors));
638 	for (i = old_size; i < links->size; i++) {
639 	    MYTRACE((stderr, "+++ initializing visited links for page %d", i));
640 	    links->anchors[i].list = NULL;
641 	    links->anchors[i].list_size = 0;
642 	}
643     }
644 }
645 
646 #if 0
647 static void
648 show_visited(struct visited_anchors *links, int pageno)
649 {
650     size_t i;
651     fprintf(stderr, "visited links on page %d:\n", pageno);
652     for (i = 0; i < links->anchors[pageno].list_size; i++) {
653 	fprintf(stderr, "%d ", links->anchors[pageno].list[i]);
654     }
655     fprintf(stderr, "\n");
656 }
657 #endif
658 
659 static void
save_in_list(struct visited_anchors * links,int pageno,int idx)660 save_in_list(struct visited_anchors *links, int pageno, int idx)
661 {
662     links->anchors[pageno].list
663 	= XREALLOC(links->anchors[pageno].list,
664 		   (links->anchors[pageno].list_size + 1)
665 		   * sizeof *(links->anchors[pageno].list));
666     links->anchors[pageno].list[links->anchors[pageno].list_size] = idx;
667     links->anchors[pageno].list_size++;
668 }
669 
670 static void
set_visited(struct visited_anchors * links,int pageno,int anchor_num)671 set_visited(struct visited_anchors *links, int pageno, int anchor_num)
672 {
673     int i;
674     /* is it already present? */
675     for (i = 0; i < (int)links->anchors[pageno].list_size; i++) {
676 	if (links->anchors[pageno].list[i] == anchor_num)
677 	    return;
678     }
679     save_in_list(links, pageno, anchor_num);
680     i = anchor_num;
681     /* also set previous/next of this anchor to visited */
682     while ((i = htex_page_info.anchors[i].prev_wrapped) != -1) {
683 	TRACE_HTEX((stderr, "set_visited: setting prev_wrapped %d to visited too", i));
684 	save_in_list(links, pageno, i);
685     }
686     i = anchor_num;
687     while ((i = htex_page_info.anchors[i].next_wrapped) != -1) {
688 	TRACE_HTEX((stderr, "set_visited: setting next_wrapped %d to visited too", i));
689 	save_in_list(links, pageno, i);
690     }
691 #if 0
692     show_visited(links, pageno);
693 #endif
694 }
695 
696 static Boolean
is_visited(struct visited_anchors * links,int pageno,int anchor_num)697 is_visited(struct visited_anchors *links, int pageno, int anchor_num)
698 {
699     size_t i;
700 
701     ASSERT((size_t)pageno < links->size, "too few elements in links structure");
702 
703     for (i = 0; i < links->anchors[pageno].list_size; i++) {
704 	if (links->anchors[pageno].list[i] == anchor_num) {
705 	    return True;
706 	}
707     }
708     return False;
709 }
710 
711 static void
push_stack(struct htex_anchor_stack * stack,htexAnchorT type,int anchor_num)712 push_stack(struct htex_anchor_stack *stack, htexAnchorT type, int anchor_num)
713 {
714     size_t i = 0;
715     if (stack->depth >= stack->size) {
716     	stack->size += HTEX_ALLOC_STEP;
717 	stack->types = XREALLOC(stack->types, stack->size * sizeof *(stack->types));
718 	for (i = stack->depth; i < stack->size; i++) {
719 	    stack->types[i].type = A_NONE;
720 	    stack->types[i].anchor_num = -1;
721 	}
722     }
723     stack->types[stack->depth].type = type;
724     stack->types[stack->depth].anchor_num = anchor_num;
725     stack->depth++;
726 #if 0
727     {
728 	fprintf(stderr, "PUSH - stack is now: \n");
729 	for (i = 0; i < stack->depth; i++)
730 	    fprintf(stderr, "%d:%d ", i, stack->types[i]);
731 	MYTRACE(stderr, "\n");
732     }
733 #endif
734 }
735 
736 static htexAnchorT
pop_stack(struct htex_anchor_stack * stack,int * anchor_num)737 pop_stack(struct htex_anchor_stack *stack, int *anchor_num)
738 {
739     htexAnchorT ret;
740 
741     if (stack->depth < 1) {
742 	return A_MISMATCH;
743     }
744 
745 #if 0
746     {
747 	int i;
748 	MYTRACE((stderr, "POP - stack is now: "));
749 	for (i = 0; i < stack->depth; i++)
750 	    MYTRACE((stderr, "%d:%d ", i, stack->types[i]));
751 	MYTRACE((stderr, ""));
752     }
753 #endif
754     stack->depth--;
755     ret = stack->types[stack->depth].type;
756     *anchor_num = stack->types[stack->depth].anchor_num;
757     stack->types[stack->depth].type = A_NONE;
758     stack->types[stack->depth].anchor_num = -1;
759     return ret;
760 }
761 
762 
763 /* return True if stack contains an A_HREF, False else */
764 static Boolean
get_href_depth(const struct htex_anchor_stack * stack)765 get_href_depth(const struct htex_anchor_stack *stack)
766 {
767     size_t i;
768     for (i = 0; i <= stack->depth; i++) {
769 	ASSERT(stack->types != NULL, "types musn't be NULL!");
770 	if (htex_is_href(stack->types[i].type))
771 	    return True;
772     }
773     return False;
774 }
775 
776 static htexAnchorT
peek_stack(struct htex_anchor_stack * stack,int * anchor_num)777 peek_stack(struct htex_anchor_stack *stack, int *anchor_num)
778 {
779     if (stack->depth < 1) {
780 	MYTRACE((stderr, "Xdvi warning: wrong nesting of anchors on page %d", current_page));
781 	*anchor_num = -1;
782 	return A_NONE;
783     }
784 
785     *anchor_num = stack->types[stack->depth - 1].anchor_num;
786     return stack->types[stack->depth - 1].type;
787 }
788 
789 /* routines for coloring the anchors */
790 static void
push_colorspecial(void)791 push_colorspecial(void)
792 {
793     int i;
794 
795     i = htex_page_info.curr_cnt - 1;
796     /* apply color if needed */
797     if (resource.link_style > 1) { /* colored links are wanted */
798 	Boolean visited = False;
799 
800 	ASSERT(i >= 0, "i mustn't be negative");
801 	if (is_visited(&visited_links, current_page, i)) {/*  || wrapped_anchor_is_visited(i)) { */
802 	    visited = True;
803 	}
804 
805 	MYTRACE((stderr, "anchor %d, %s is %s\n",
806 		 htex_page_info.curr_cnt - 1, htex_page_info.anchors[htex_page_info.curr_cnt - 1].a_href,
807 		 visited ? "******* visited ****** " : "not visited"));
808 	if ((visited && resource.visited_link_color != NULL)
809 	    || (! visited && resource.link_color != NULL)) {
810 	    color_special(visited ? g_visited_link_color_rgb : g_link_color_rgb);
811 	}
812     }
813 }
814 
815 static void
pop_colorspecial(void)816 pop_colorspecial(void)
817 {
818     color_special("pop");
819 }
820 
821 /* return filename if it's a local file, NULL else */
822 const char *
is_local_file(const char * filename)823 is_local_file(const char *filename)
824 {
825     int i;
826     if (strchr(filename, ':') != NULL) {
827 	if (memicmp(filename, "file:", strlen("file:")) == 0) {
828 	    TRACE_HTEX((stderr, "%s uses file scheme", filename));
829 	    filename += strlen("file:");
830 	    /*
831 	      skip over `//localhost' part, and skip first `/' iff the
832 	      absolute path starts with `//' (as required by RFC2396,
833 	      but in the past most browsers/applications didn't support
834 	      this).
835 	    */
836 	    if (memicmp(filename, "//localhost", strlen("//localhost")) == 0) {
837 		filename += strlen("//localhost");
838 	    }
839 	    if (memicmp(filename, "//", 2) == 0) {
840 		filename += 1;
841 	    }
842 	    return filename;
843 	}
844 
845 	/* check remote schemes */
846 	for (i = 0; remote_URL_schemes[i] != NULL; i++) {
847 	    if (memicmp(filename, remote_URL_schemes[i], strlen(remote_URL_schemes[i])) == 0) {
848 		TRACE_HTEX((stderr, "%s is a remote scheme", filename));
849 		return NULL;
850 	    }
851 	}
852     }
853     /* in all other cases, treat it as an ordinary filename */
854     TRACE_HTEX((stderr, "%s is an ordinary filename", filename));
855     return filename;
856 }
857 
858 
859 static char *
parse_anchortext(const char * input,int len)860 parse_anchortext(const char *input, int len)
861 {
862     char *anchor = NULL;
863     const char *beg, *end;
864 
865     parse_html_special(input, len, &beg, &end);
866 
867     anchor = XMALLOC(anchor, end - beg + 1);
868     memcpy(anchor, beg, end - beg);
869     anchor[end - beg] = '\0';
870     return anchor;
871 }
872 
873 static void
add_anchor(struct htex_page_info * info,htexAnchorT type,const char * str,size_t len,int pageno,char * filename)874 add_anchor(struct htex_page_info *info, htexAnchorT type,
875 	   const char *str, size_t len,
876 	   int pageno, char *filename)
877 {
878     UNUSED(pageno);
879     UNUSED(filename);
880 
881     resize_info_if_needed(info);
882 
883     ASSERT(htex_is_name(type) || htex_is_href(type), "This doesn't look like a valid anchor");
884     /* add an anchor or a href, depending on `type' */
885     if (type == A_HREF) {
886 	if (info->anchors[info->curr_cnt].a_href == NULL) {
887 	    info->anchors[info->curr_cnt].a_href = parse_anchortext(str, len);
888 	}
889 	TRACE_HTEX((stderr, "adding HREF %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
890     }
891     else if (type == A_HREF_URL) {
892 	if (info->anchors[info->curr_cnt].a_href == NULL) {
893 	    info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
894 	    strncpy(info->anchors[info->curr_cnt].a_href, str, len);
895 	    info->anchors[info->curr_cnt].a_href[len] = '\0';
896 	}
897 	TRACE_HTEX((stderr, "adding HREF_URL %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
898     }
899     else if (type == A_HDVIPS_INTERNAL) {
900 	if (info->anchors[info->curr_cnt].a_href == NULL) {
901 	    /* dynamically add a `#' prefix */
902 	    if (str[0] != '#') {
903 		info->anchors[info->curr_cnt].a_href = xmalloc(len + 2);
904 		strcpy(info->anchors[info->curr_cnt].a_href, "#");
905 		strncat(info->anchors[info->curr_cnt].a_href, str, len);
906 		info->anchors[info->curr_cnt].a_href[len + 1] = '\0';
907 	    }
908 	    else {
909 		info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
910 		strncpy(info->anchors[info->curr_cnt].a_href, str, len);
911 		info->anchors[info->curr_cnt].a_href[len] = '\0';
912 	    }
913 	}
914 	TRACE_HTEX((stderr, "adding HREF_URL %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
915     }
916     else if (type == A_HREF_FILE) {
917 	if (info->anchors[info->curr_cnt].a_href == NULL) {
918 	    /* dynamically add a `file:' extension */
919 	    if (memcmp(str, "file:", strlen("file:")) == 0) {
920 		info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
921 		strncpy(info->anchors[info->curr_cnt].a_href, str, len);
922 		info->anchors[info->curr_cnt].a_href[len] = '\0';
923 	    }
924 	    else {
925 		info->anchors[info->curr_cnt].a_href = xmalloc(len + strlen("file:") + 1);
926 		strcpy(info->anchors[info->curr_cnt].a_href, "file:");
927 		strncat(info->anchors[info->curr_cnt].a_href, str, len);
928 		info->anchors[info->curr_cnt].a_href[len + strlen("file:")] = '\0';
929 	    }
930 	}
931 	TRACE_HTEX((stderr, "adding HREF_FILE %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_href));
932     }
933     else if (type == A_NAME) {
934 	if (info->anchors[info->curr_cnt].a_name == NULL) {
935 	    info->anchors[info->curr_cnt].a_name = parse_anchortext(str, len);
936 	}
937 	TRACE_HTEX((stderr, "adding NAME %d: %s", info->curr_cnt, info->anchors[info->curr_cnt].a_name));
938     }
939     else if (type == A_HDVIPS_HREF) {
940 	if (info->anchors[info->curr_cnt].a_href == NULL) {
941 	    info->anchors[info->curr_cnt].a_href = xmalloc(len + 1);
942 	    strncpy(info->anchors[info->curr_cnt].a_href, str, len);
943 	    info->anchors[info->curr_cnt].a_href[len] = '\0';
944 	}
945 	TRACE_HTEX((stderr, "adding HDVIPS_HREF %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_name));
946     }
947     else if (type == A_HDVIPS_NAME) {
948 	if (info->anchors[info->curr_cnt].a_name == NULL) {
949 	    info->anchors[info->curr_cnt].a_name = xmalloc(len + 1);
950 	    strncpy(info->anchors[info->curr_cnt].a_name, str, len);
951 	    info->anchors[info->curr_cnt].a_name[len] = '\0';
952 	}
953 	TRACE_HTEX((stderr, "adding HDVIPS_NAME %d: |%s|", info->curr_cnt, info->anchors[info->curr_cnt].a_name));
954     }
955 }
956 
957 static void
set_anchor_size(struct htex_page_info * info,int index,int ulx,int uly,int lrx,int lry)958 set_anchor_size(struct htex_page_info *info, int index,
959 		int ulx, int uly, int lrx, int lry)
960 {
961     struct anchor_info *anchor;
962 
963     ASSERT(info->anchors != NULL, "info->anchors should have been allocated before");
964     ASSERT(index < info->curr_cnt, "info too small");
965 
966     anchor = &(info->anchors[index]);
967 
968     anchor->ulx = ulx;
969     anchor->uly = uly;
970     anchor->lrx = lrx;
971     anchor->lry = lry;
972 }
973 
974 void
htex_set_objecttype(htexObjectT type)975 htex_set_objecttype(htexObjectT type)
976 {
977     htex_page_info.anchors[htex_page_info.curr_cnt - 1].object_type = type;
978 }
979 
980 void
htex_set_anchorsize(int ulx,int uly,int lrx,int lry)981 htex_set_anchorsize(int ulx, int uly, int lrx, int lry)
982 {
983     set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1,
984 		    ulx, uly, lrx, lry);
985 }
986 
987 static void
enlarge_anchor_size(struct htex_page_info * info,int index,int ulx,int uly,int lrx,int lry)988 enlarge_anchor_size(struct htex_page_info *info, int index,
989 		    int ulx, int uly, int lrx, int lry)
990 {
991     struct anchor_info *anchor;
992 
993     ASSERT(info->anchors != NULL, "info->anchors should have been allocated before");
994     ASSERT(index < info->curr_cnt, "info too small");
995 
996     /*     fprintf(stderr, "enlarging anchor at index %d; %s\n", index, info->anchors[index].a_href); */
997     anchor = &(info->anchors[index]);
998 
999     if (ulx < anchor->ulx) {
1000 	anchor->ulx = ulx;
1001     }
1002     if (uly < anchor->uly) {
1003 	anchor->uly = uly;
1004     }
1005     if (lrx > anchor->lrx) {
1006 	anchor->lrx = lrx;
1007     }
1008     /* set lry only for first character, since this will be used
1009        to position underline */
1010     /*      if (lry > anchor->lry && anchor->lry == 0) { */
1011     if (lry > anchor->lry) {
1012 	anchor->lry = lry;
1013     }
1014 }
1015 
1016 static void
reset_page_info(struct htex_page_info * info,int pageno,Boolean force_init)1017 reset_page_info(struct htex_page_info *info, int pageno, Boolean force_init)
1018 {
1019     int i, dummy;
1020 
1021     if (force_init || pageno != info->page) {
1022 #if 0
1023 	fprintf(stderr, "%d or %d != %d: resetting anchorinfo for page %d (%d anchors)\n",
1024 		force_init, pageno, info->page, current_page, info->curr_cnt);
1025 #endif
1026 	ASSERT(info->curr_cnt == 0 || info->anchors != NULL, "inconsistency in info structure");
1027 	/* re-initialize all values */
1028 	for (i = 0; i < info->curr_cnt; i++) {
1029 	    TRACE_HTEX((stderr, "----- resetting info for anchor %d", i));
1030 	    FREE(info->anchors[i].a_name);
1031 	    FREE(info->anchors[i].a_href);
1032 	    info->anchors[i].a_name = NULL;
1033 	    info->anchors[i].a_href = NULL;
1034 	    info->anchors[i].ulx = INT_MAX;
1035 	    info->anchors[i].uly = INT_MAX;
1036 	    info->anchors[i].lrx = 0;
1037 	    info->anchors[i].lry = 0;
1038 	    info->anchors[i].object_type = HTEX_TEXT;
1039 	    info->anchors[i].prev_wrapped = -1;
1040 	    info->anchors[i].next_wrapped = -1;
1041 	}
1042     }
1043     if (pageno != info->page) { /* reset info */
1044 	free(info->anchors);
1045 	info->anchors = NULL;
1046 	info->tot_cnt = 0;
1047     }
1048     TRACE_HTEX((stderr, "---------------- setting curr_cnt to 0, and emptying stack"));
1049     info->page = pageno;
1050     info->curr_cnt = 0;
1051     info->have_wrapped = -1;
1052     while (stack.depth > 0)
1053 	pop_stack(&stack, &dummy);
1054 }
1055 
1056 /*
1057  * htex_initpage does what's neccessary at the beginning of a page:
1058  * - re-initialize geometry info if file or page size has changed (according to size_changed),
1059  * - re-initialize visited links info if file has changed (according to new_dvi_file),
1060  * - take care of mismatched anchors at the beginning of the page.
1061  */
1062 void
htex_initpage(Boolean new_dvi_file,Boolean size_changed,int pageno)1063 htex_initpage(Boolean new_dvi_file, Boolean size_changed, int pageno)
1064 {
1065     reset_page_info(&htex_page_info, pageno, size_changed | new_dvi_file);
1066     init_visited_links(&visited_links, total_pages, new_dvi_file);
1067 
1068 #if 0
1069     show_visited(&visited_links, pageno);
1070 #endif
1071 
1072     if (pageno > 0
1073 	&& (int)m_prescan_info.pagelist_len > pageno /* pagelist_len will be 0 for file with no hyperlinks */
1074 	&& m_prescan_info.pagelist[pageno - 1] != NULL) {
1075 	add_anchor(&htex_page_info, A_HREF,
1076 		   m_prescan_info.pagelist[pageno - 1],
1077 		   strlen(m_prescan_info.pagelist[pageno - 1]),
1078 		   pageno, NULL);
1079 	htex_page_info.curr_cnt++;
1080 	MYTRACE((stderr, "++++++++++ mismatched anchor text (at %d): |%s|",
1081 		 htex_page_info.curr_cnt - 1, m_prescan_info.pagelist[pageno - 1]));
1082 	/*  	x_pos_bak = y_pos_bak = INT_MAX; */
1083 	x_pos_bak = y_pos_bak = INT_MAX;
1084 	set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, 0, 0, 1, 1);
1085 	htex_page_info.have_wrapped = htex_page_info.curr_cnt - 1;
1086 	if (bg_current != NULL) { /* only if it has been initialized by redraw_page in events.c */
1087 	    push_colorspecial();
1088 	}
1089 	/* 	else { */
1090 	/* 	    fprintf(stderr, "----------------------- NOT pushing color, waiting ...\n"); */
1091 	/* 	} */
1092 	push_stack(&stack, A_HREF, htex_page_info.curr_cnt - 1);
1093     }
1094 }
1095 
1096 /* Returns a PostScript literal text string inside parentheses.  The
1097    scanner works according to "PostScript language reference, third
1098    edition" - Sec. 3.2.2. The specification is implemented completely:
1099    balanced parentheses and all escape sequences are considered.
1100 
1101    The argument STR is expected to be a pointer to the first character
1102    after the opening parenthesis. The function returns NULL if the
1103    text string is malformed. Otherwise the return value points to
1104    an allocated string which should be freed by the calling function.
1105 */
1106 static char *
scan_ps_literal_text_string(const char * str)1107 scan_ps_literal_text_string(const char *str)
1108 {
1109     const char *s;
1110     char *cp, *c;
1111     int count = 1;
1112 
1113     /* Search end of string by considering balanced parentheses. */
1114     for (s = str; *s; s++)
1115 	if (*s == '\\' && *(s+1))
1116 	    s++;
1117 	else if (*s == '(')
1118 	    count++;
1119 	else if (*s == ')')
1120 	    if (! --count)
1121 		break;
1122 
1123     if (!*s)
1124 	return NULL;
1125 
1126 
1127     if ((cp = malloc((s - str + 1) * sizeof(*cp))) == NULL) {
1128 	XDVI_ERROR((stderr, "Not enough memory"));
1129 	return NULL;
1130     }
1131 
1132     /* copy string while translating escape sequences */
1133     for (c = cp; str < s; str++) {
1134 	if (*str == '\\') {
1135 	    switch (*++str) {
1136 	    case 'n': *c++ = '\n';  break;
1137 	    case 'r': *c++ = '\r';  break;
1138 	    case 't': *c++ = '\t';  break;
1139 	    case 'b': *c++ = '\b';  break;
1140 	    case 'f': *c++ = '\f';  break;
1141 	    case '\n': break;
1142 	    case '\r': if (*(str + 1) == '\n') str++; break;
1143 	    default:
1144 		if (isdigit(*str)) {
1145 		    char number[] = "xxx";
1146 		    char *end;
1147 		    strncpy(number, str, 3);
1148 		    *c++ = strtoul(number, &end, 8);
1149 		    str += end - number - 1;
1150 		}
1151 		else {
1152 		    *c++ = *str; /* ignore \ if followed by another char,
1153 				    including ( and ) and \ */
1154 		}
1155 	    }
1156 	}
1157 	else {
1158 	    *c++ = *str;
1159 	}
1160     }
1161     *c = '\0';
1162 
1163     return cp;
1164 }
1165 
1166 static void
hdvips_add_anchor(struct htex_page_info * info,htexAnchorT type,const char * str)1167 hdvips_add_anchor(struct htex_page_info *info, htexAnchorT type, const char *str)
1168 {
1169     char *ptr;
1170     if ((ptr = scan_ps_literal_text_string(str)) != NULL) {
1171 	int curr_cnt_bak = info->curr_cnt;
1172 	/* overwrite previously created dummy/wrapped anchors */
1173 	ASSERT(info->curr_cnt > 0, "hdvips_add_anchor must be called after add_anchor()!");
1174 
1175 	while (info->curr_cnt > 0
1176 	       && info->anchors[info->curr_cnt - 1].a_href != NULL
1177 	       && (strcmp(info->anchors[info->curr_cnt - 1].a_href, "__WRAPPED__") == 0
1178 		   || strcmp(info->anchors[info->curr_cnt - 1].a_href, "__DUMMY__") == 0)) {
1179 	    info->curr_cnt--;
1180 	    free(info->anchors[info->curr_cnt].a_href);
1181 	    free(info->anchors[info->curr_cnt].a_name);
1182 	    info->anchors[info->curr_cnt].a_href = info->anchors[info->curr_cnt].a_name = NULL;
1183 	    add_anchor(info, type, ptr, strlen(ptr), 0, NULL);
1184 	}
1185 	info->curr_cnt = curr_cnt_bak;
1186 	free(ptr);
1187     }
1188     else {
1189 	MYTRACE((stderr, "Xdvi warning: skipping malformed hdvips link `%s'", str));
1190     }
1191 }
1192 
1193 /*
1194  * htex_reset_page: invoked when drawing was interrupted,
1195  * assumes that htex_page_info is in an inconsistent state and resets it.
1196  */
1197 void
htex_reset_page(int pageno)1198 htex_reset_page(int pageno)
1199 {
1200     reset_page_info(&htex_page_info, pageno, True);
1201 }
1202 
1203 /* returns True iff inside a href tag */
1204 Boolean
htex_scan_anchor(const char * cp,size_t len)1205 htex_scan_anchor(const char *cp, size_t len)
1206 {
1207     char *ptr;
1208 
1209     if (memicmp(cp, "</a>", 4) == 0) { /* end tag */
1210 	/* 	struct anchor_info *anchor; */
1211 	int num;
1212 	htexAnchorT type = pop_stack(&stack, &num);
1213 	if (type == A_MISMATCH)
1214 	    return False;
1215 
1216 	/* ASSERT(htex_page_info.curr_cnt - 1 >= 0, "Index out of range"); */
1217 	/* anchor = &(htex_page_info.anchors[htex_page_info.curr_cnt - 1]); */
1218 
1219 	/* reset color if needed */
1220 	if (resource.link_style > 1 && resource.link_color != NULL && htex_is_href(type))
1221 	    pop_colorspecial();
1222     }
1223     else if (memicmp(cp, "<a ", 3) == 0) {
1224 	m_href_type = HYPERTEX_LINK;
1225 	cp += 3; /* skip over `<a ' part */
1226 	len -= 3;
1227 	TRACE_HTEX((stderr, "scan_anchor: |%s|", cp));
1228 	if (memicmp(cp, "name", 4) == 0) {
1229 	    add_anchor(&htex_page_info, A_NAME, cp + 4, len - 4, 0, NULL);
1230 	    htex_page_info.curr_cnt++;
1231 	    push_stack(&stack, A_NAME, htex_page_info.curr_cnt);
1232 	}
1233 	else if (memicmp(cp, "href", 4) == 0) {
1234 	    add_anchor(&htex_page_info, A_HREF, cp + 4, len - 4, 0, NULL);
1235 	    htex_page_info.curr_cnt++;
1236 	    push_stack(&stack, A_HREF, htex_page_info.curr_cnt);
1237 
1238 	    /* MYTRACE((stderr, "NON-WRAPPED ANCHOR at %d,%d!", PXL_H, PXL_V)); */
1239 	    push_colorspecial();
1240 	    x_pos_bak = PXL_H;
1241 	    y_pos_bak = PXL_V;
1242 	    /* 	    set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, PXL_H, PXL_V, PXL_H + 10, PXL_V + 10); */
1243 	}
1244 	else {
1245 	    XDVI_WARNING((stderr, "Skipping unimplemented htex special `%s'", cp));
1246 	    push_stack(&stack, A_OTHER, htex_page_info.curr_cnt);
1247 	}
1248     }
1249     else if (memcmp(cp, "H.S end", strlen("H.S end")) == 0) {
1250 	/* start of anchor, link or rect. We just assume that the link
1251 	   target will have length 0, so when we see text between H.S
1252 	   and H.R, assume that it's the link text.
1253 	*/
1254 	m_href_type = HDVIPS_LINK;
1255 	/* add dummy */
1256 	add_anchor(&htex_page_info, A_HDVIPS_HREF, "__DUMMY__", strlen("__DUMMY__"), 0, NULL);
1257 	htex_page_info.curr_cnt++;
1258 	push_stack(&stack, A_HREF, htex_page_info.curr_cnt - 1);
1259 	push_colorspecial();
1260 	x_pos_bak = PXL_H;
1261 	y_pos_bak = PXL_V;
1262 	return True;
1263     }
1264     else if (memcmp(cp, "H.R end", strlen("H.R end")) == 0 /* end of rect */
1265 	     || memcmp(cp, "H.A end", strlen("H.A end")) == 0 /* end of anchor */
1266 	     || memcmp(cp, "H.L end", strlen("H.L end")) == 0 /* end of link */
1267 	     ) {
1268 	int num;
1269 	htexAnchorT type = pop_stack(&stack, &num);
1270 	if (type == A_MISMATCH)
1271 	    return False;
1272 	if (resource.link_style > 1 && resource.link_color != NULL)
1273 	    pop_colorspecial();
1274 	return False;
1275     }
1276     /* add anchor texts for hdvips links */
1277     else if (memcmp(cp, "/A", 2) == 0) { /* possibly an hdvips external link */
1278 	/* 	fprintf(stderr, "+++++ EXT: |%s|\n", cp); */
1279 	if ((ptr = strstr(cp + 2, "/GoToR")) != NULL /* external file */
1280 	    && (ptr = strchr(ptr, '(')) != NULL) {
1281 	    hdvips_add_anchor(&htex_page_info, A_HREF_FILE, ptr + 1);
1282 	}
1283 	else if ((ptr = strstr(cp + 2, "/URI")) != NULL /* URL */
1284 		 && (ptr = strchr(ptr, '(')) != NULL) {
1285 	    hdvips_add_anchor(&htex_page_info, A_HREF_URL, ptr + 1);
1286 	}
1287 	return False;
1288     }
1289     else if (memcmp(cp, "/L", 2) == 0) { /* possibly an hdvips internal link */
1290 	if ((ptr = strstr(cp + 2, "/Dest")) != NULL
1291 	    && (ptr = strchr(ptr, '(')) != NULL) {
1292 	    hdvips_add_anchor(&htex_page_info, A_HDVIPS_INTERNAL, ptr + 1);
1293 	}
1294 	return False;
1295     }
1296     else if (memcmp(cp, "/V", 2) == 0) { /* possibly an hdvips anchor */
1297 	if ((ptr = strstr(cp + 2, "/Dest")) != NULL
1298 	    && (ptr = strchr(ptr, '(')) != NULL) {
1299 	    hdvips_add_anchor(&htex_page_info, A_HDVIPS_NAME, ptr + 1);
1300 	}
1301 	return False;
1302     }
1303     else { /* unrecognized tag */
1304 	if (globals.warn_spec_now) {
1305 	    XDVI_WARNING((stderr, "Ignoring unknown hyperref special `%s'", cp));
1306 	}
1307 	return False;
1308     }
1309     return get_href_depth(&stack);
1310 }
1311 
1312 void
htex_record_position(int ulx,int uly,int w,int h)1313 htex_record_position(int ulx, int uly, int w, int h)
1314 {
1315     int lrx, lry;
1316     int y_delta, x_delta;
1317 
1318     if (!INSIDE_MANE_WIN) /* this would give wrong values */
1319 	return;
1320 
1321     lrx = ulx + w;
1322     lry = uly + h;
1323 
1324     y_delta = lry - uly;
1325     x_delta = lrx - ulx;
1326 
1327     /* heuristics for creating new bounding box at what might be linebreaks */
1328     if (lrx < x_pos_bak /* ordinary linebreak */
1329 	|| lry + y_delta < y_pos_bak) { /* column break (from bottom of 1st to top 2nd column) */
1330 	htexAnchorT type;
1331 	int idx, prev, curr;
1332 	/* get anchor index of topmost stack item, to find the matching open tag */
1333 	if ((type = peek_stack(&stack, &idx)) == A_NONE) {
1334 	    /* 	    fprintf(stderr, "!!!!!!!!!!!! couldn't find opening tag for this wrapped link!"); */
1335 	    return;
1336 	}
1337 	ASSERT(idx >= 0, "Index out of range");
1338 	/* 	fprintf(stderr, "wrapped link: index %d, type %d\n", idx, type); */
1339 	/* get correct idx */
1340 	if (m_href_type == HYPERTEX_LINK) {
1341 	    while (htex_page_info.anchors[idx].a_href == NULL && idx > 0) {
1342 		idx--;
1343 	    }
1344 	    if (htex_page_info.anchors[idx].a_href == NULL && idx == 0) {
1345 		XDVI_ERROR((stderr, "Couldn't find wrapped anchor for idx %d, page %d!", idx, current_page));
1346 		return;
1347 	    }
1348 	    add_anchor(&htex_page_info, A_HREF,
1349 		       htex_page_info.anchors[idx].a_href,
1350 		       strlen(htex_page_info.anchors[idx].a_href),
1351 		       0, NULL);
1352 	}
1353 	else {
1354 	    add_anchor(&htex_page_info, A_HREF, "__WRAPPED__", strlen("__WRAPPED__"), 0, NULL);
1355 	}
1356 	htex_page_info.curr_cnt++;
1357 	/* add wrapping info */
1358 	if (htex_page_info.have_wrapped >= 0) {
1359 	    /* this is the only case where some other material might have come between
1360 	       this and the previous anchor */
1361 	    prev = htex_page_info.have_wrapped;
1362 	    htex_page_info.have_wrapped = -1;
1363 	}
1364 	else {
1365 	    prev = htex_page_info.curr_cnt - 2;
1366 	}
1367 	curr = htex_page_info.curr_cnt - 1;
1368 	ASSERT(prev >= 0, "Index out of range");
1369 
1370 	/* 	fprintf(stderr, "setting prev to %d, curr to %d\n", prev, curr); */
1371 	htex_page_info.anchors[prev].next_wrapped = curr;
1372 	htex_page_info.anchors[curr].prev_wrapped = prev;
1373 	/* initialize it to cover current character */
1374 	set_anchor_size(&htex_page_info, htex_page_info.curr_cnt - 1, ulx, uly, lrx, lry);
1375     }
1376     else {
1377 	int prev_idx = htex_page_info.curr_cnt - 1;
1378 
1379 	if (prev_idx >= 0) {
1380 	    enlarge_anchor_size(&htex_page_info, prev_idx, ulx, uly, lrx, lry);
1381 	}
1382 	else {
1383 	    MYTRACE((stderr, "!!!!!!!!!!!! Bug? prev_idx < 0"));
1384 	}
1385     }
1386     x_pos_bak = lrx;
1387     y_pos_bak = uly;
1388 }
1389 
1390 #if 0
1391 static void
1392 show_history(void)
1393 {
1394     struct dl_list *ptr = htex_history;
1395     struct history_info *info = ptr->item;
1396     int i;
1397 
1398     fprintf(stderr, "**************** history: %s, %d, %s\n", info->filename, info->page, info->anchor);
1399     for (; ptr->prev != NULL; ptr = ptr->prev) { ; }
1400     for (i = 0; ptr != NULL; ptr = ptr->next, i++) {
1401 	info = ptr->item;
1402 	fprintf(stderr, "elem %d: %s, %d, %s\n", i, info->filename, info->page, info->anchor);
1403 	MYTRACE((stderr, "elem: %p, prev: %p, next: %p", (void *)ptr, (void *)ptr->prev, (void *)ptr->next));
1404     }
1405 }
1406 #endif
1407 
1408 /* check for names like foo/bar.dvi#link, return copy of
1409    link truncated at the `#' if found (NULL else), and
1410    save `link' into resource.anchor_pos for later use.
1411 */
1412 static char *
check_relative_name(const char * link)1413 check_relative_name(const char *link)
1414 {
1415     char *ptr;
1416     TRACE_HTEX((stderr, "check_relative_name: |%s|", link));
1417     if ((ptr = strchr(link, '#')) != NULL
1418 	&& ptr > link + 4
1419 	&& (memicmp(ptr - 4, ".dvi", 4) == 0)) {
1420 	char *new_link = xstrdup(link);
1421 	new_link[ptr - link] = '\0'; /* truncate the copy */
1422 	free(g_anchor_pos);
1423 	g_anchor_pos = xstrdup(ptr + 1);
1424 	g_anchor_len = strlen(g_anchor_pos);
1425 
1426 	return new_link;
1427     }
1428     else { /* reset g_anchor_pos */
1429 	free(g_anchor_pos);
1430 	g_anchor_pos = NULL;
1431     }
1432     return NULL;
1433 }
1434 
1435 static void
htex_update_toolbar_navigation(void)1436 htex_update_toolbar_navigation(void)
1437 {
1438 #if 0
1439     show_history();
1440 #endif
1441 #if defined(MOTIF) && HAVE_XPM
1442     tb_set_htex_back_sensitivity(htex_history->prev != NULL);
1443     tb_set_htex_forward_sensitivity(htex_history->next != NULL);
1444 #endif
1445 }
1446 
1447 void
htex_back(void)1448 htex_back(void)
1449 {
1450     struct history_info *info;
1451 
1452     if (htex_history == NULL) {
1453 	xdvi_bell();
1454 	statusline_info(STATUS_SHORT, "Hyperref history is empty");
1455 	return;
1456     }
1457     info = htex_history->item;
1458 
1459     if (htex_history->prev == NULL) {
1460 	xdvi_bell();
1461 /* 	statusline_info(STATUS_SHORT, "At begin of history"); */
1462 	htex_update_toolbar_navigation();
1463 	return;
1464     }
1465 
1466     if (strcmp(info->filename, globals.dvi_name) != 0) { /* new filename different */
1467 	Boolean tried_dvi_ext = False;
1468 	char *new_dvi_name = NULL;
1469 
1470 	if ((new_dvi_name = open_dvi_file_wrapper(info->filename, False, False,
1471 						  &tried_dvi_ext, False))
1472 	    == NULL) {
1473 
1474 	    statusline_error(STATUS_MEDIUM, "Re-opening file \"%s\" failed!\n", info->filename);
1475 	    return;
1476 	}
1477 	else {
1478 	    set_dvi_name(new_dvi_name);
1479 	    globals.ev.flags |= EV_NEWDOC;
1480 	    globals.ev.flags |= EV_PAGEHIST_INSERT;
1481 	    /*  	    statusline_info(STATUS_MEDIUM, "Back to file: \"%s\"", globals.dvi_name); */
1482 	}
1483     }
1484     else {
1485 	page_history_insert(info->page);
1486     }
1487     goto_page(info->page, resource.keep_flag ? NULL : home, False);
1488 
1489     MYTRACE((stderr, "curr page now: %d", current_page));
1490     htex_history = htex_history->prev;
1491 
1492     info = htex_history->item;
1493 
1494 #if 0
1495     MYTRACE((stderr, "------ before skipping: %d, %s", info->page, info->anchor));
1496     show_history();
1497     while (info->page == current_page && htex_history->prev != NULL) {
1498 	/* skip identical locations */
1499 	MYTRACE((stderr, "+++++++ skipping identical page %d, %s", current_page, info->anchor));
1500 	htex_history = htex_history->prev;
1501 	info = htex_history->item;
1502     }
1503     MYTRACE((stderr, "------ after skipping: %d, %s", info->page, info->anchor));
1504 #endif
1505 
1506     htex_update_toolbar_navigation();
1507 }
1508 
1509 
1510 void
htex_forward(void)1511 htex_forward(void)
1512 {
1513     struct history_info *info;
1514     char *link;
1515 
1516     if (htex_history == NULL) {
1517 	xdvi_bell();
1518 	statusline_info(STATUS_SHORT, "Hyperref history is empty");
1519 	return;
1520     }
1521     if (htex_history->next == NULL) {
1522 	xdvi_bell();
1523 /* 	statusline_info(STATUS_SHORT, "At end of history"); */
1524 	return;
1525     }
1526 
1527     htex_history = htex_history->next;
1528     info = htex_history->item;
1529     link = info->anchor;
1530     /* go there */
1531     if (*link == '#') { /* it's a relative link */
1532 	MYTRACE((stderr, "XXXXXXXX %s:%d: setting anchor to |%s|+1", __FILE__, __LINE__, info->anchor));
1533 	free(g_anchor_pos);
1534 	g_anchor_pos = xstrdup(info->anchor + 1);
1535 	g_anchor_len = strlen(g_anchor_pos);
1536 	globals.ev.flags |= EV_ANCHOR;
1537     }
1538     else { /* it's an absolute link */
1539 	char *new_linkname = NULL;
1540 	char *new_dvi_name = NULL;
1541 	Boolean tried_dvi_ext = False;
1542 
1543 	if ((new_linkname = check_relative_name(link)) != NULL)
1544 	    link = new_linkname;
1545 
1546 	if ((new_dvi_name = open_dvi_file_wrapper(link, False, False,
1547 						  &tried_dvi_ext, False)) == NULL) {
1548 	    statusline_error(STATUS_MEDIUM, "Re-opening file \"%s\" failed!\n", info->filename);
1549 	    free(new_linkname);
1550 	    return;
1551 	}
1552 	else {
1553 	    set_dvi_name(new_dvi_name);
1554 
1555 	    globals.ev.flags |= EV_NEWDOC;
1556 	    goto_page(0, resource.keep_flag ? NULL : home, False);
1557 	    globals.ev.flags |= EV_PAGEHIST_INSERT;
1558 
1559 	    if (g_anchor_pos != NULL)
1560 		globals.ev.flags |= EV_ANCHOR;
1561 
1562 	    /*  	    statusline_info(STATUS_MEDIUM, "Loaded file: \"%s\"", globals.dvi_name); */
1563 	    free(new_linkname);
1564 	}
1565     }
1566 
1567     htex_update_toolbar_navigation();
1568 }
1569 
1570 /* save this anchor in jump_history, current location in goback_history,
1571    and update Motif toolbar icons
1572 */
1573 static void
history_save_anchor(const char * anchor,int current_page,const char * dvi_name)1574 history_save_anchor(const char *anchor, int current_page, const char *dvi_name)
1575 {
1576     struct history_info *item;
1577 
1578     if (htex_history == NULL) {
1579 	/* Insert dummy start elem. We need this to prevent dropping
1580 	   off from the begin of the list, since otherwise a 1-elem
1581 	   list couldn't encode the information whether we can still go
1582 	   back. This way it will point to the dummy elem when there's
1583 	   no way to go back.  */
1584 	struct history_info *start_marker = XMALLOC(start_marker, sizeof *start_marker);
1585 	start_marker->anchor = NULL;
1586 	start_marker->filename = NULL;
1587 	start_marker->page = -1;
1588 	htex_history = dl_list_insert(htex_history, start_marker);
1589     }
1590     item = XMALLOC(item, sizeof *item);
1591     item->anchor = xstrdup(anchor);
1592     item->filename = xstrdup(dvi_name);
1593     item->page = current_page;
1594     htex_history = dl_list_truncate(dl_list_insert(htex_history, item));
1595 
1596 #if 0
1597     MYTRACE((stderr, "==================== after inserting "));
1598     show_history();
1599 #endif
1600 
1601     htex_update_toolbar_navigation();
1602 
1603 }
1604 
1605 /*
1606  * Return anchor string at coordinates x, y, and anchor count in 3rd argument,
1607  * or NULL if there's no anchor at these coordinates.
1608  */
1609 static char *
get_anchor_at_index(int x,int y,int * count)1610 get_anchor_at_index(int x, int y, int *count)
1611 {
1612     int i;
1613 
1614     for (i = 0; i < htex_page_info.curr_cnt; i++) {
1615 	struct anchor_info anchor = htex_page_info.anchors[i];
1616 	/*  	fprintf(stderr, "getting anchor at index %d; %s\n", i, anchor.a_href); */
1617 	if (anchor.a_href == NULL) {
1618 	    continue;
1619 	}
1620 	if (anchor.lry + 2 > y && anchor.uly - 1 < y /* allow some vertical fuzz */
1621 	    && anchor.lrx > x && anchor.ulx < x) {
1622 	    *count = i;
1623 	    return anchor.a_href;
1624 	}
1625     }
1626     *count = -1;
1627     return NULL;
1628 }
1629 
1630 
1631 /*
1632   generate exposure events for anchor at index idx, and all anchors wrapping to/from it,
1633   so that they will be redrawn in `visited' color
1634 */
1635 static void
redraw_anchors(int idx)1636 redraw_anchors(int idx)
1637 {
1638     struct anchor_info anchor = htex_page_info.anchors[idx];
1639     int other;
1640     int i = idx;
1641 #define DO_EXPOSE(lx,ly,rx,ry) (clearexpose(&mane, lx - 20, ly - 3, rx - lx + 26, ry - ly + 6))
1642 
1643     /* HACK ALERT: This is a workaround for a coloring problem with wrapped anchors
1644        (see regression/href/002/url-wrap-test2.dvi):
1645        We can't change the color on-the-fly for a wrapped anchor, since the special
1646        is pushed only at the opening tag. So for wrapped anchors, the visited colour
1647        wouldn't be set correctly. Redrawing the entire page ensures this (but it's ugly).
1648        Currently this hack overrides the algorithm below for all anchors that have a
1649        `prev_wrapped' pointer.
1650     */
1651     if (htex_page_info.anchors[i].prev_wrapped != -1) {
1652 	globals.ev.flags |= EV_NEWPAGE;
1653 	XSync(DISP, False);
1654 	return;
1655     }
1656 
1657     DO_EXPOSE(anchor.ulx, anchor.uly, anchor.lrx, anchor.lry);
1658 
1659     /* previous parts of wrapped anchors: */
1660     for (i = idx;
1661 	 i >= 0 && (other = htex_page_info.anchors[i].prev_wrapped) != -1;
1662 	 i--) {
1663 	DO_EXPOSE(htex_page_info.anchors[other].ulx, htex_page_info.anchors[other].uly,
1664 		  htex_page_info.anchors[other].lrx, htex_page_info.anchors[other].lry);
1665     }
1666 
1667     /* later parts of wrapped anchors: */
1668     for (i = idx;
1669 	 i < htex_page_info.curr_cnt && (other = htex_page_info.anchors[i].next_wrapped) != -1;
1670 	 i++) {
1671 	DO_EXPOSE(htex_page_info.anchors[other].ulx, htex_page_info.anchors[other].uly,
1672 		  htex_page_info.anchors[other].lrx, htex_page_info.anchors[other].lry);
1673     }
1674 #undef DO_EXPOSE
1675 }
1676 
1677 
1678 /*
1679  * Invoked when clicking on a link.
1680  * newwindow == True means open new window,
1681  * newwindow == False means jump to link in current window.
1682  * With newwindow == True, don't update the toolbar navigation icons, since there's nothing to go back.
1683  */
1684 Boolean
htex_handleref(int x,int y,Boolean newwindow)1685 htex_handleref(int x, int y, Boolean newwindow)
1686 {
1687     char *link;
1688     int link_num;
1689 
1690     /*      fprintf(stderr, "---------- htex_handleref!\n"); */
1691     if (!(globals.cursor.flags & CURSOR_LINK))
1692 	/* When no link cursor is shown, clicking shouldn't jump to link */
1693 	return False;
1694 
1695     if ((link = get_anchor_at_index(x, y, &link_num)) != NULL) {
1696 	set_visited(&visited_links, current_page, link_num);
1697 
1698 	if (*link == '#') { /* it's a relative link */
1699 	    if (newwindow) {
1700 		launch_xdvi(globals.dvi_name, link + 1);
1701 		redraw_anchors(link_num);
1702 		return True;
1703 	    }
1704 	    else {
1705 		history_save_anchor(link, current_page, globals.dvi_name);
1706 		free(g_anchor_pos);
1707 		g_anchor_pos = xstrdup(link + 1);
1708 		g_anchor_len = strlen(g_anchor_pos);
1709 		globals.ev.flags |= EV_ANCHOR;
1710 		redraw_anchors(link_num);
1711 		return True;
1712 	    }
1713 	}
1714 	else { /* it's an absolute link */
1715 	    char *new_dvi_name = NULL;
1716 	    int pageno_bak = current_page;
1717 	    char *new_linkname = NULL;
1718 	    char *orig_link = link;
1719 	    Boolean tried_dvi_ext = False;
1720 
1721 	    MYTRACE((stderr, "OLD FILE: |%s|", globals.dvi_name));
1722 
1723 	    if ((new_linkname = check_relative_name(link)) != NULL)
1724 		link = new_linkname;
1725 
1726 	    if ((new_dvi_name = open_dvi_file_wrapper(link, False, newwindow,
1727 						      &tried_dvi_ext, False)) != NULL) {
1728 		/* only save link in history if opening succeeds */
1729 		history_save_anchor(orig_link, pageno_bak, globals.dvi_name);
1730 		set_dvi_name(new_dvi_name);
1731 		if (!newwindow) {
1732 		    globals.ev.flags |= EV_NEWDOC;
1733 		    goto_page(0, resource.keep_flag ? NULL : home, False);
1734 		    globals.ev.flags |= EV_PAGEHIST_INSERT;
1735 
1736 		    MYTRACE((stderr, "NEW FILE: |%s|", globals.dvi_name));
1737 		    if (g_anchor_pos != NULL)
1738 			globals.ev.flags |= EV_ANCHOR;
1739 		    /*  		    statusline_info(STATUS_MEDIUM, "Loaded file: \"%s\"", dvi_name); */
1740 		}
1741 		else {
1742 		    redraw_anchors(link_num);
1743 		}
1744 	    }
1745 	    free(new_linkname);
1746 	    redraw_anchors(link_num);
1747 	    return True;
1748 	}
1749     }
1750     return False;
1751 }
1752 
1753 /* change cursor and display anchor text in statusline */
1754 void
htex_displayanchor(int x,int y)1755 htex_displayanchor(int x, int y)
1756 {
1757     static int id_bak = -1;
1758     static int pageno_bak = -1;
1759     int id_curr;
1760     char *anchor_text;
1761 
1762     anchor_text = get_anchor_at_index(x, y, &id_curr);
1763 
1764     if (anchor_text != NULL) { /* found an anchor */
1765 	if (!(globals.cursor.flags & CURSOR_LINK) && !(globals.cursor.flags & CURSOR_TEXT)) {
1766 	    globals.cursor.flags |= CURSOR_LINK;
1767 	    globals.ev.flags |= EV_CURSOR;
1768 	}
1769 	/* to prevent flicker, print only when it differs from previous one,
1770 	   i.e. when it either has a different ID or is on a different page: */
1771 	if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0
1772 	    && (id_curr != id_bak || current_page != pageno_bak)) {
1773 	    statusline_info(STATUS_FOREVER, "%s", anchor_text);
1774 	    id_bak = id_curr;
1775 	    pageno_bak = current_page;
1776 	}
1777     }
1778     else { /* not over anchor text, reset cursor and statusline */
1779 	if (globals.cursor.flags & CURSOR_LINK) {
1780 	    globals.cursor.flags &= ~CURSOR_LINK;
1781 	    globals.ev.flags |= EV_CURSOR;
1782 	}
1783 
1784 	if (id_bak != -1 && (resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) {
1785 	    statusline_clear();
1786 	    id_bak = -1;
1787 	}
1788     }
1789 }
1790 
1791 /*
1792  * Invoked from all commands that change the shrink factor;
1793  * forces re-computation of the anchor markers.
1794  */
1795 void
htex_resize_page(void)1796 htex_resize_page(void)
1797 {
1798     htex_initpage(False, True, current_page);
1799 }
1800 
1801 /*
1802  * Invoked from all commands that reread the DVI file;
1803  * resets all link infos.
1804  * Note: visited_links and anchor stack data
1805  * are preserved across multiple files, so we don't need to free() them.
1806  */
1807 void
htex_reinit(void)1808 htex_reinit(void)
1809 {
1810     htex_prescan_reset();
1811     htex_initpage(True, True, current_page);
1812 }
1813 
1814 /*
1815   Underline/highlight anchors if the appropriate resource is set.
1816   Since this is done after the entire page is parsed, we can't use
1817   the checks for fg_current and do_color_change(), but need to
1818   use a separate GC instead.
1819 */
1820 void
htex_draw_anchormarkers(void)1821 htex_draw_anchormarkers(void)
1822 {
1823     int i;
1824     int rule_height = (globals.page.h / 1000.0);
1825     XRectangle rect = { -1, -1, 0, 0 };
1826 
1827     if (rule_height == 0)
1828 	rule_height = 1;
1829 
1830     if (current_page == g_anchormarker.page
1831 	&& strcmp(globals.dvi_name, g_anchormarker.filename) == 0
1832 	&&  g_anchormarker.y_pos > 0) {
1833 	htex_draw_anchormarker(g_anchormarker.y_pos);
1834     }
1835 
1836     if (resource.link_style == 0 || resource.link_style == 2)
1837 	return;
1838 
1839     for (i = 0; i < htex_page_info.curr_cnt; i++) {
1840 	struct anchor_info anchor = htex_page_info.anchors[i];
1841 
1842 	if (anchor.a_href != NULL) {
1843 	    int offset;
1844 	    Boolean visited = False;
1845 
1846 #if 0
1847 	    XDrawRectangle(DISP, mane.win, globals.gc.high,
1848 			   anchor.ulx, anchor.uly,
1849 			   anchor.lrx - anchor.ulx, anchor.lry - anchor.uly);
1850 
1851 #endif
1852 
1853 	    if (is_visited(&visited_links, current_page, i)) {/*  || wrapped_anchor_is_visited(i)) { */
1854 		visited = True;
1855 	    }
1856 	    offset = ANCHOR_XTRA_V_BBOX / (double)currwin.shrinkfactor + 0.5;
1857 
1858 	    TRACE_HTEX((stderr, "UNDERLINE: %d, %s is %s", i, anchor.a_href,
1859 			visited ? "******* visited ****** " : "not visited"));
1860 	    anchor.ulx -= currwin.base_x;
1861 	    anchor.lrx -= currwin.base_x;
1862 	    anchor.lry -= currwin.base_y;
1863 	    anchor.uly -= currwin.base_y;
1864 
1865 	    /* BUG ALERT: Don't use put_rule here, since this will cause a segfault
1866 	     * with the new color code when switching density (#616920)!
1867 	     */
1868 	    MYTRACE((stderr, "UNDERLINE: checking if %d is visited: %d!", i, visited));
1869 
1870 	    /* 	    if (clip_region_to_rect(&rect)) { */
1871 	    if (anchor.object_type == HTEX_IMG) {
1872 		rect.x = anchor.ulx - 1;
1873 		rect.y = anchor.uly - 1;
1874 		rect.width = anchor.lrx - anchor.ulx + 2;
1875 		rect.height = anchor.lry - anchor.uly + 2;
1876 		if (clip_region_to_rect(&rect)) {
1877 		    XDrawRectangle(DISP, mane.win,
1878 				   visited ? globals.gc.visited_linkcolor : globals.gc.linkcolor,
1879 				   rect.x, rect.y,
1880 				   rect.width, rect.height);
1881 		}
1882 	    }
1883 	    else {
1884 		rect.x = anchor.ulx - 1;
1885 		rect.y = anchor.lry + offset;
1886 		rect.width = anchor.lrx - anchor.ulx + 2;
1887 		rect.height = rule_height;
1888 		if (clip_region_to_rect(&rect)) {
1889 		    XFillRectangle(DISP, mane.win,
1890 				   visited ? globals.gc.visited_linkcolor : globals.gc.linkcolor,
1891 				   rect.x, rect.y,
1892 				   rect.width, rect.height);
1893 		}
1894 	    }
1895 	}
1896     }
1897 }
1898 
1899 void
launch_xdvi(const char * filename,const char * anchor_name)1900 launch_xdvi(const char *filename, const char *anchor_name)
1901 {
1902 #define ARG_LEN 32
1903     int i = 0;
1904     const char *argv[ARG_LEN];
1905     char *shrink_arg = NULL;
1906 
1907     ASSERT(filename != NULL, "filename argument to launch_xdvi() mustn't be NULL");
1908 
1909     argv[i++] = kpse_invocation_name;
1910     /* FIXME: there's something broken with this and invoking xdvi.bin.
1911        To reproduce the problem, invoke from the shell:
1912        xdvi.bin -geometry 829x1172 /usr/share/texmf/doc/programs/kpathsea.dvi
1913        this will run at 300dpi, i.e. ignore an .Xdefaults setting as opposed to:
1914        xdvi.bin /usr/share/texmf/doc/programs/kpathsea.dvi
1915 
1916        Also, how about the other command-line switches that might have
1917        been passed to the parent instance? How about things that have been changed
1918        at run-time, like shrink factor - should they be converted to command-line
1919        options?
1920     */
1921 
1922     argv[i++] = "-name";
1923     argv[i++] = "xdvi";
1924 
1925     /* start the new instance with the same debug flags as the current instance */
1926     if (globals.debug != 0) {
1927 	argv[i++] = "-debug";
1928 	argv[i++] = resource.debug_arg;
1929     }
1930 
1931     if (anchor_name != NULL) {
1932 	argv[i++] = "-anchorposition";
1933 	argv[i++] = anchor_name;
1934     }
1935 
1936     argv[i++] = "-s";
1937     shrink_arg = XMALLOC(shrink_arg, LENGTH_OF_INT + 1);
1938     sprintf(shrink_arg, "%d", currwin.shrinkfactor);
1939     argv[i++] = shrink_arg;
1940 
1941     argv[i++] = filename; /* FIXME */
1942 
1943     argv[i++] = NULL;
1944     ASSERT(i <= ARG_LEN, "Too few elements in argv[]");
1945 
1946     if (globals.debug & DBG_HTEX) {
1947 	fprintf(stderr, "Invoking:\n");
1948 	for (i = 0; argv[i]; i++) {
1949 	    fprintf(stderr, "%s\n", argv[i]);
1950 	}
1951     }
1952 
1953     /*
1954       FIXME: using fork_process here hangs the new xdvi process when it tries
1955       printing to stderr, so we use plain fork/exec (see comments in util.c)
1956     */
1957 #if 0
1958     fork_process(argv[0], False, NULL, NULL, NULL, argv);
1959 #else
1960     {
1961 	int pid;
1962 	switch (pid = fork()) {
1963 	case -1:
1964 	    perror("fork");
1965 	case 0:
1966 	    execvp(argv[0], (char **)argv);
1967 	    XDVI_ERROR((stderr, "%s: Execution of %s failed.", globals.program_name, argv[0]));
1968 	    _exit(EXIT_FAILURE);
1969 	default:
1970 	    FREE(shrink_arg);
1971 	}
1972     }
1973 #endif
1974 #undef ARG_LEN
1975 }
1976 
1977 #if COPY_TMP_FILE
1978 /* callback to remove temporary file after helper application has terminated.
1979    Currently unused. Note the possible danger of a temp race if the application
1980    does an fclose() on the file before ...
1981 */
1982 static void
remove_temp_file(int status,struct xchild * this)1983 remove_temp_file(int status, struct xchild *this)
1984 {
1985     char *pathname = (char *)this->data;
1986     if (WEXITSTATUS(status) == 0) {
1987 	unlink(pathname);
1988     }
1989     else {
1990 	popup_message(globals.widgets.top_level,
1991 		      MSG_WARN, NULL,
1992 		      "Warning: Calling `%s' on `%s' terminated with non-zero status (%d)"
1993 		      "\n(Not removing `%s')",
1994 		      this->name, pathname, status, pathname);
1995     }
1996     free(pathname);
1997 }
1998 #endif
1999 
2000 void
launch_program(const char * filename)2001 launch_program(const char *filename)
2002 {
2003     const char *format_string = NULL;
2004     char *content_type = NULL;
2005     char *viewer = NULL;
2006     char *argv[4];
2007     struct stat statbuf;
2008     const char *local_filename_sans_prefix;
2009     char *path, *syscmd, *tmp;
2010 #if COPY_TMP_FILE
2011     int tmp_fd;
2012 #endif
2013     const char *ptr;
2014     char *fullpath = NULL;
2015     char canonical_path[MAXPATHLEN + 1];
2016     Boolean needs_terminal;
2017     size_t offset;
2018 
2019     TRACE_HTEX((stderr, "launch_program called with |%s|", filename));
2020 
2021     /* is it a local file? */
2022     local_filename_sans_prefix = is_local_file(filename);
2023     if (local_filename_sans_prefix == NULL) { /* not local */
2024 	launch_browser(filename);
2025 	return;
2026     }
2027 
2028     /* expand the filename if it contains a relative path */
2029     path = find_file(local_filename_sans_prefix, &statbuf, kpse_program_text_format);
2030     if (path != NULL) {
2031 	/* fully canonicalize path before passing it to helper applications */
2032 	fullpath = REALPATH(path, canonical_path);
2033 	if (fullpath == NULL) {
2034 	    XDVI_WARNING((stderr, "Couldn't canonicalize %s to full path - returning unexpanded.",
2035 			  path));
2036 	    fullpath = path;
2037 	}
2038 	else
2039 	    FREE(path);
2040     }
2041     else {
2042 	XDVI_WARNING((stderr, "Couldn't find file %s; passing to application unchanged.",
2043 		      local_filename_sans_prefix));
2044 	/* if it doesn't exist, maybe the user has encoded some magic in the
2045 	   filename; in that case, pass it to the application unchanged. */
2046 	fullpath = (char *)local_filename_sans_prefix;
2047     }
2048 
2049     TRACE_HTEX((stderr, "fullpath: |%s|", fullpath));
2050     content_type = figure_mime_type(fullpath);
2051     ASSERT(content_type != NULL, "figure_mime_type() should have returned a valid type (eventually a fallback)");
2052 
2053     /* make it safe to pass the argument to system() or `sh -c',
2054        by escaping all `dangerous' characters: */
2055     tmp = shell_escape_string(fullpath);
2056     fullpath = tmp;
2057 
2058     if ((viewer = figure_viewer(content_type, &format_string, &needs_terminal, fullpath)) == NULL) {
2059 	/* warn user if no application has been found. Help text needs to be constructed at
2060 	   run-time, since we want the correct content-type in it. */
2061 #if 0
2062 	char *helptext = NULL;
2063 #else
2064 	char *helptext = get_string_va("Please assign an application to the "
2065 			 "MIME type `%s' in your ~/.mailcap file. "
2066 			 "E.g. if you want to view the file with firefox, "
2067 			 "add the following line to your ~/.mailcap:\n"
2068 			 "%s; firefox '%%s'\n\n",
2069 			 content_type, content_type);
2070 #endif
2071 	popup_message(globals.widgets.top_level,
2072 		      MSG_WARN,
2073 		      helptext,
2074 		      "Could not determine an application for the file %s, MIME type `%s'.",
2075 		      fullpath, content_type);
2076 	/* FIXME: We can't free helptext here, because it won't be used until
2077 	 * the popup comes up and the user clicks on "Help".  This doesn't
2078 	 * happen until after this function returns.  */
2079 	/* free(helptext); */
2080 	free(fullpath);
2081   	return;
2082     }
2083 
2084 #if COPY_TMP_FILE
2085     /* copy the file to a temporary location before passing it to the
2086        viewer.  This is e.g. how Acroread does it. This should fix the
2087        problem with xdvizilla without -no-rm for DVI files (see comment
2088        in figure_viewer(), mime.c).
2089        SU 2003/10/02: currently I can't reproduce the xdvizilla problem
2090        any more - it seems that for xdvi files, argv[0] will be invoked
2091        anyway? Also, copying the files to tmp may break figure locations
2092        and relative links for DVI files. Suspended this for the time being.
2093     */
2094     tmp = NULL;
2095     if ((tmp_fd = xdvi_temp_fd(&tmp)) == -1) {
2096 	XDVI_ERROR((stderr, "couldn't create temporary file; not calling helper application."));
2097 	return;
2098     }
2099     TRACE_HTEX((stderr, "copying to temporary location: |%s->%s|", fullpath, tmp));
2100     if (!copy_file(fullpath, tmp)) {
2101 	XDVI_ERROR((stderr, "couldn't copy %s to temporary file %s; not invoking helper application.",
2102 		    fullpath, tmp));
2103 	return;
2104     }
2105     free(fullpath);
2106     fullpath = tmp;
2107 #endif
2108 
2109     if (strlen(format_string) > 0) {
2110 	ptr = find_format_str(viewer, format_string);
2111     }
2112     else {
2113 	/*
2114 	  Contrary to RFC 1343, we don't pass stuff via pipes (too bothersome).
2115 	  Instead, pass it as normal argument by appending it to the command.
2116 	 */
2117 	ptr = strchr(viewer, '\0');
2118     }
2119 
2120     offset = 0;
2121     if (needs_terminal) {
2122 	offset += strlen("xterm -e ");
2123     }
2124     /* allocate extra byte for optional whitespace after viewer command */
2125     syscmd = xmalloc(offset + strlen(viewer) + strlen(fullpath) + 2);
2126     if (needs_terminal) {
2127 	strcpy(syscmd, "xterm -e ");
2128     }
2129     /* viewer command */
2130     memcpy(syscmd + offset, viewer, ptr - viewer);
2131     /* bugfix for #2931447: add space separator after viewer command if no format string is present */
2132     if (strlen(format_string) == 0) {
2133 	strcpy(syscmd + offset + (ptr - viewer), " ");
2134 	offset += 1;
2135     }
2136     /* viewer argument */
2137     strcpy(syscmd + offset + (ptr - viewer), fullpath);
2138     /* rest of command */
2139     strcpy(syscmd + offset + (ptr - viewer) + strlen(fullpath), ptr + strlen(format_string));
2140 
2141     /*
2142       mailcap(4) says that the mailcap command shall be passed to system(),
2143       so we musn't use fork_process() directly here. Instead, we pass the command
2144       to `/bin/sh -c', but via fork_process so that
2145 
2146       - xdvi doesn't hang if the process doesn't return;
2147       - we can still catch the return value if `sh -c' exits with an error
2148       (e.g. if the viewer command is not found).
2149 
2150       Note however that this doesn't mimick most of POSIX's system() semantics.
2151     */
2152     TRACE_HTEX((stderr, "execv(\"/bin/sh -c %s\")", syscmd));
2153     argv[0] = "/bin/sh";
2154     argv[1] = "-c";
2155     argv[2] = syscmd;
2156     argv[3] = NULL;
2157 #if COPY_TMP_FILE
2158     fork_process("/bin/sh", False, globals.dvi_file.dirname, remove_temp_file, fullpath, argv);
2159 #else
2160     fork_process("/bin/sh", False, globals.dvi_file.dirname, NULL, NULL, argv);
2161 #endif
2162 
2163     FREE(viewer);
2164 #if COPY_TMP_FILE
2165 #else
2166     FREE(fullpath);
2167 #endif
2168     FREE(syscmd);
2169 }
2170 
2171 void
htex_set_anchormarker(int y)2172 htex_set_anchormarker(int y)
2173 {
2174     Position drawing_x;
2175 
2176     /* erase old marker if it's on the same page as the new marker, after
2177        cancelling the old timeout so that it won't affect the new marker */
2178     if (m_href_timeout_id)
2179 	XtRemoveTimeOut(m_href_timeout_id);
2180     htex_erase_anchormarker(NULL, NULL);
2181     XFlush(DISP);
2182 
2183     XtVaGetValues(globals.widgets.draw_widget, XtNx, &drawing_x, NULL);
2184     g_anchormarker.page = current_page;
2185     free(g_anchormarker.filename);
2186     g_anchormarker.filename = xstrdup(globals.dvi_name);
2187     g_anchormarker.y_pos = y;
2188     g_anchormarker.x_pos = DEFAULT_MARKER_X_OFFSET + -drawing_x;
2189     m_href_timeout_id = XtAppAddTimeOut(globals.app, STATUS_SHORT * 1000, htex_erase_anchormarker, (XtPointer)NULL);
2190 }
2191 
2192 static void
htex_draw_anchormarker(int y)2193 htex_draw_anchormarker(int y)
2194 {
2195     int x;
2196     XPoint points[3];
2197 
2198     Position drawing_x, clip_w;
2199 
2200     XtVaGetValues(globals.widgets.clip_widget, XtNwidth, &clip_w, NULL);
2201     XtVaGetValues(globals.widgets.draw_widget, XtNx, &drawing_x, NULL);
2202 
2203     /* compute offset to draw into visible region */
2204     x = DEFAULT_MARKER_X_OFFSET + -drawing_x;
2205 
2206     /* Eventually erase old marker, to avoid `smearing' on horizontal scrolls. */
2207     if (x != g_anchormarker.x_pos) {
2208 	clearexpose(&mane, g_anchormarker.x_pos - 1, y - 3, 20, 10);
2209     }
2210     g_anchormarker.x_pos = x;
2211 
2212     points[0].x = x + 10;
2213     points[1].x = x + 19;
2214     points[2].x = x + 10;
2215     points[0].y = y - 3;
2216     points[1].y = y + 2;
2217     points[2].y = y + 7;
2218 
2219     XFillRectangle(DISP, mane.win, globals.gc.visited_linkcolor, x, y, 10, 4);
2220     XFillPolygon(DISP, mane.win, globals.gc.visited_linkcolor, points, 3, Convex, CoordModeOrigin);
2221     /* -1 indicates that no horizontal scrolling is wanted, since the
2222        anchormarker will always be horizontally positioned inside the
2223        visible area.
2224     */
2225     scroll_page_if_needed(-1, -1, y + 3, y - 3);
2226 }
2227 
2228 
2229 static void
htex_erase_anchormarker(XtPointer client_data,XtIntervalId * id)2230 htex_erase_anchormarker(XtPointer client_data, XtIntervalId *id)
2231 {
2232     Position clip_w;
2233 
2234     UNUSED(client_data);
2235     UNUSED(id);
2236 
2237     if (globals.debug & DBG_EVENT) {
2238 	fprintf(stderr, "htex_erase_anchormarker called!\n");
2239     }
2240 
2241     if (m_href_timeout_id == (XtIntervalId)0) { /* timeout was removed but callback happened anyway */
2242 	return;
2243     }
2244 
2245     m_href_timeout_id = (XtIntervalId)0;
2246     /* clear the mark if we're in the same file and on the same page as the mark */
2247     if (g_anchormarker.filename != NULL
2248 	&& strcmp(globals.dvi_name, g_anchormarker.filename) == 0
2249 	&& g_anchormarker.page == current_page) {
2250 	XtVaGetValues(globals.widgets.clip_widget, XtNwidth, &clip_w, NULL);
2251 	clearexpose(&mane, 0, g_anchormarker.y_pos - 3, clip_w, 10);
2252     }
2253     g_anchormarker.y_pos = -1;
2254 }
2255 
2256