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