/* The rest of the code has the following copyright:

Copyright (c) 1990-2004  Paul Vojta and others

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE:
	xdvi is based on prior work, as noted in the modification history
	in xdvi.c.

\*========================================================================*/

#include "xdvi-config.h"
#include "xdvi.h"

#include <stdarg.h>

#include <stdlib.h>
#include <ctype.h>

#include <setjmp.h>

#define USE_HASH

#include <ctype.h>
#include "kpathsea/c-fopen.h"
#include "kpathsea/c-stat.h"
#include "kpathsea/magstep.h"
#include "kpathsea/tex-file.h"
#include "kpathsea/c-vararg.h"

#include <string.h>

#include "dvi.h"
#include "string-utils.h"
#include "util.h"
#include "x_util.h"
#include "events.h"
#include "dvi-init.h"
#include "statusline.h"
#include "hypertex.h"
#include "special.h"
#include "tfmload.h"
#include "read-mapfile.h"
#include "my-snprintf.h"
#include "kpathsea/tex-file.h"
#include "mag.h"
#include "message-window.h"

#include "dvi-draw.h"
#include "search-internal.h"
#include "encodings.h"
#include "pagesel.h"
#include "pagehist.h"

#ifdef RGB_ANTI_ALIASING
#define TRACE_AA 0
#define TRACE_AA1 1
#define TRACE_AA3 0
#else
#define TRACE_AA 0
#define TRACE_AA1 0
#define TRACE_AA3 0
#endif

#define MY_DEBUG 1

#if MY_DEBUG
# define TRACE_FIND_VERBOSE(x) TRACE_FIND(x)
#else
# define TRACE_FIND_VERBOSE(x) /* as nothing */
#endif

#if PS && PS_GS
#include "psgs.h"
#endif /* PS && PS_GS */

#ifdef	DOPRNT	/* define this if vfprintf gives you trouble */
# define vfprintf(stream, message, args)	_doprnt(message, args, stream)
#endif

#define BUF_SIZE 1024

/*
 *	All calculations are done with shrink factor = 1, so we re-do some
 *	macros accordingly.  Many of these are also defined in special.c.
 */

#define	xpixel_conv(x)	((int) ((x) >> 16))
#define	xpixel_round(x)	((int) ROUNDUP(x, 1 << 16))

#define	G_PXL_H		xpixel_conv(currinf.data.dvi_h)
#define	G_OFFSET_X	(resource.xoffset_int << 16) + (3 << 15)
#define	G_OFFSET_Y	(resource.yoffset_int << 16) + (3 << 15)

#ifdef T1LIB
/* hashing stuff to speed up checking whether a font or tfminfo file
   has already been loaded.
*/
#define T1FONTS_INITIAL_HASHTABLE_SIZE 1031
static hashTableT t1fonts_hash;
static hashTableT tfminfo_hash;
static hashTableT fontmaps_hash;
static hashTableT font_warn_hash;

typedef enum { FAILURE_BLANK = -2, FAILURE_PK = -1, SUCCESS = 0 } t1FontLoadStatusT;
#endif

#if PS
int scanned_page_ps;
int scanned_page_ps_bak;
#endif

#if COLOR
int scanned_page_color;
#endif

int scanned_page_reset, scanned_page;

struct drawinf currinf;
Boolean htex_inside_href = False; /* whether we're inside a href */

Boolean	drawing_mag = False;

#ifdef T1LIB

# include "t1lib.h"

/* Datastructures:

   encodings[n]: Loaded from map file
   [0] = 8r,	<vector>
   [1] = 8c,	<vector>

   The vectors are demand-loaded in 'load_encoded_font'
*/

struct encoding {
    char *enc;	/* This may be NULL.  From dvips maps we get
		   encodings loaded by filename, and no name given */
    char *file;
    char **vector;
};

static struct encoding *encodings = NULL;	/* Dynamic array = realloc when it gets full */
static size_t enclidx = 0;			/* global, for communication with find_T1_font */

/*

   t1fonts[n]: Built as xdvi loads t1 fonts
   idx	 file			short	t1id	loaded
   [0] = /path/cmr10.pfb,	cmr10	0	1
   [1] = /path/prmr.pfa,	prmr	1	0
   [2] = /path/pcrr8a.pfa,	pcrr8a	2	0

   This array enumerates the loaded t1 fonts by full path name.
   The integer `t1id' is the t1lib font id.  The font will not be
   extended/slanted/encoded.  The `loaded' field indicates if
   T1_LoadFont has been called for the font yet.  Texfonts that need a
   modified version of the t1 font must use T1_CopyFont to copy the
   raw font and obtain the id of a new t1 font which can then be
   modified.  As described in the t1 docs a font must be loaded before
   it can be copied.

   Fonts that are copies of other fonts have NULL filenames.  If any
   font files are to be reused based on the file name, the raw font
   must be reused, not a copy.

   The `short' field contains the font as it was identified in the
   fontmap.  This is used to save searching; if two fonts are
   identified the same way in the fontmap, they are the same font.

*/

struct t1font_info {
    char *file;
    char *shortname;
    int t1id;
    int loaded;
};

static struct t1font_info *t1fonts = NULL;	/* Dynamic array */

/*
   fontmaps[n]: Loaded from map file, and extended with 'implied'
	fonts as needed.
   idx	 texname enc	exten.	slant	filena.	t1libid	pathn.	tfmidx
   [0] = pcrr8rn 0	750	0	pcrr8a	2	...	2
   [1] = putro8r 0	0	0	putro8r 3	...	3

   The first 5 fields of this table are loaded from the font map
   files, or filled out with default values when implied fonts are
   set-up.  The t1libid is -1 until the font is loaded or copied as
   described under t1fonts.  Once the dvi file reveals that the font
   is needed, it is located on disk to ensure it exists, and the
   pathname field is filled out.  The t1 font is not loaded or
   copied before it's needed because set_t1_char has been called to
   draw a glyph from the font.  The late loading is necessiated by the
   high cost of loading fonts.  If the font loading is concentrated at
   startup of the program the startup-time becomes ecessively high due
   to the high number of fonts used by the average LaTeX document.

   A value of 0 for `extension'/`slant' means no extension/slant.  If
   the input values are decimal (less than 10) they're multiplied by
   1000 and 10000 respectively to obtain fixed-point integer values.
   (This means that xdvi will translate entries like `0.81' to 810.)
   Integer values have the advantage that they can be tested for
   exact equality on all architectures.
*/

struct fontmap {
    char *texname;
    int enc;	/* Index in encoding array */
    int extension;	/* Fixed point, *1000 */
    int slant;	/* Fixed point, *10000, some font slantings have 4
		   significant digits, all after the decimalpoint */
    char *filename;	/* Name of the t1 font file as given in map/dvi */

    int t1libid;	/* The t1lib id of this font, or -1 if not set up */
    char *pathname;	/* Full path of the font, once needed and located */
    int tfmidx;	/* Index in tfminfo array, or -1 */
    Boolean warned_about;	/* whether a warning message about this font has been displayed yet */
    /*
      if the font file was not readable for some reason (e.g. corrupt,
      or encrypted for proprietry distribution), load_font_now() will
      try to load a PK version via load_font() instead. If this is
      successful, it will set force_pk to true, which will tell
      get_t1_glyph() to return immediately, and just use the standard
      set_char() routine to set the PK glyph. If even that fails,
      get_t1_glyph() returns an error status in a special flag that's
      evaluated by set_t1_char().
    */
    Boolean force_pk;		
};

static struct fontmap *fontmaps = NULL;
static size_t g_maplidx = 0; /* global, for communication with find_texfont */

/*
   widthinfo[n]: TFM width info, loaded on demand as the fonts are used.
   idx	 texname	widths
   [0] = cmr10		...
   [1] = ptmr8r		...
   [2] = pcrr8rn	...
   [3] = putro8r	...

   The widthinfo is loaded from the font tfm file because the type1
   width information isn't precise enough to avoid accumulating
   rounding errors that make things visibly misaligned.  This is
   esp. noticeable in tables (for the lines separating the columns).

   For other than "design" sizes the widths are scaled up or down.

*/

struct tfminfos {
    const char *texname;
    long designsize;
    long widths[256];
    long fontdimen2; /* width of word spacing in this font */
};

static struct tfminfos *tfminfo = NULL;

/* For file reading */
# define BUFFER_SIZE 1024

/* This is the conversion factor from TeXs pt to TeXs bp (big point).
   Maximum machine precision is needed to keep it accurate. */
/* UNUSED */
/* static double bp_fac=72.0/72.27; */

/* Try to convert from PS charspace units to DVI units with minimal
   loss of significant digits */
# define t1_dvi_conv(x)	((((int) (x)) << 16)/1000)
/* Convert from DVI units to TeX pt */
# define dvi_pt_conv(x) (((long) ((x)*dimconv)) >> 19)

/* Convert from TFM units to DVI units */
# define tfm_dvi_conv(x) (((int) (x)) >> 1)

static Boolean padMismatch = False; /* Does t1lib padding match xdvi required padding? */
static int archpad;
# define T1PAD(bits, pad)  (((bits) + (pad) - 1) & -(pad))

static struct glyph *get_t1_glyph(
# ifdef TEXXET
				  wide_ubyte cmd,
# endif
				  wide_ubyte ch, t1FontLoadStatusT *flag,
				  Boolean is_geom_scan);

# ifdef WORDS_BIGENDIAN
unsigned char rbits[] = {
    0x00,   /* 00000000 -> 00000000 */
    0x80,   /* 00000001 -> 10000000 */
    0x40,   /* 00000010 -> 01000000 */
    0xC0,   /* 00000011 -> 11000000 */
    0x20,   /* 00000100 -> 00100000 */
    0xA0,   /* 00000101 -> 10100000 */
    0x60,   /* 00000110 -> 01100000 */
    0xE0,   /* 00000111 -> 11100000 */
    0x10,   /* 00001000 -> 00010000 */
    0x90,   /* 00001001 -> 10010000 */
    0x50,   /* 00001010 -> 01010000 */
    0xD0,   /* 00001011 -> 11010000 */
    0x30,   /* 00001100 -> 00110000 */
    0xB0,   /* 00001101 -> 10110000 */
    0x70,   /* 00001110 -> 01110000 */
    0xF0,   /* 00001111 -> 11110000 */
    0x08,   /* 00010000 -> 00001000 */
    0x88,   /* 00010001 -> 10001000 */
    0x48,   /* 00010010 -> 01001000 */
    0xC8,   /* 00010011 -> 11001000 */
    0x28,   /* 00010100 -> 00101000 */
    0xA8,   /* 00010101 -> 10101000 */
    0x68,   /* 00010110 -> 01101000 */
    0xE8,   /* 00010111 -> 11101000 */
    0x18,   /* 00011000 -> 00011000 */
    0x98,   /* 00011001 -> 10011000 */
    0x58,   /* 00011010 -> 01011000 */
    0xD8,   /* 00011011 -> 11011000 */
    0x38,   /* 00011100 -> 00111000 */
    0xB8,   /* 00011101 -> 10111000 */
    0x78,   /* 00011110 -> 01111000 */
    0xF8,   /* 00011111 -> 11111000 */
    0x04,   /* 00100000 -> 00000100 */
    0x84,   /* 00100001 -> 10000100 */
    0x44,   /* 00100010 -> 01000100 */
    0xC4,   /* 00100011 -> 11000100 */
    0x24,   /* 00100100 -> 00100100 */
    0xA4,   /* 00100101 -> 10100100 */
    0x64,   /* 00100110 -> 01100100 */
    0xE4,   /* 00100111 -> 11100100 */
    0x14,   /* 00101000 -> 00010100 */
    0x94,   /* 00101001 -> 10010100 */
    0x54,   /* 00101010 -> 01010100 */
    0xD4,   /* 00101011 -> 11010100 */
    0x34,   /* 00101100 -> 00110100 */
    0xB4,   /* 00101101 -> 10110100 */
    0x74,   /* 00101110 -> 01110100 */
    0xF4,   /* 00101111 -> 11110100 */
    0x0C,   /* 00110000 -> 00001100 */
    0x8C,   /* 00110001 -> 10001100 */
    0x4C,   /* 00110010 -> 01001100 */
    0xCC,   /* 00110011 -> 11001100 */
    0x2C,   /* 00110100 -> 00101100 */
    0xAC,   /* 00110101 -> 10101100 */
    0x6C,   /* 00110110 -> 01101100 */
    0xEC,   /* 00110111 -> 11101100 */
    0x1C,   /* 00111000 -> 00011100 */
    0x9C,   /* 00111001 -> 10011100 */
    0x5C,   /* 00111010 -> 01011100 */
    0xDC,   /* 00111011 -> 11011100 */
    0x3C,   /* 00111100 -> 00111100 */
    0xBC,   /* 00111101 -> 10111100 */
    0x7C,   /* 00111110 -> 01111100 */
    0xFC,   /* 00111111 -> 11111100 */
    0x02,   /* 01000000 -> 00000010 */
    0x82,   /* 01000001 -> 10000010 */
    0x42,   /* 01000010 -> 01000010 */
    0xC2,   /* 01000011 -> 11000010 */
    0x22,   /* 01000100 -> 00100010 */
    0xA2,   /* 01000101 -> 10100010 */
    0x62,   /* 01000110 -> 01100010 */
    0xE2,   /* 01000111 -> 11100010 */
    0x12,   /* 01001000 -> 00010010 */
    0x92,   /* 01001001 -> 10010010 */
    0x52,   /* 01001010 -> 01010010 */
    0xD2,   /* 01001011 -> 11010010 */
    0x32,   /* 01001100 -> 00110010 */
    0xB2,   /* 01001101 -> 10110010 */
    0x72,   /* 01001110 -> 01110010 */
    0xF2,   /* 01001111 -> 11110010 */
    0x0A,   /* 01010000 -> 00001010 */
    0x8A,   /* 01010001 -> 10001010 */
    0x4A,   /* 01010010 -> 01001010 */
    0xCA,   /* 01010011 -> 11001010 */
    0x2A,   /* 01010100 -> 00101010 */
    0xAA,   /* 01010101 -> 10101010 */
    0x6A,   /* 01010110 -> 01101010 */
    0xEA,   /* 01010111 -> 11101010 */
    0x1A,   /* 01011000 -> 00011010 */
    0x9A,   /* 01011001 -> 10011010 */
    0x5A,   /* 01011010 -> 01011010 */
    0xDA,   /* 01011011 -> 11011010 */
    0x3A,   /* 01011100 -> 00111010 */
    0xBA,   /* 01011101 -> 10111010 */
    0x7A,   /* 01011110 -> 01111010 */
    0xFA,   /* 01011111 -> 11111010 */
    0x06,   /* 01100000 -> 00000110 */
    0x86,   /* 01100001 -> 10000110 */
    0x46,   /* 01100010 -> 01000110 */
    0xC6,   /* 01100011 -> 11000110 */
    0x26,   /* 01100100 -> 00100110 */
    0xA6,   /* 01100101 -> 10100110 */
    0x66,   /* 01100110 -> 01100110 */
    0xE6,   /* 01100111 -> 11100110 */
    0x16,   /* 01101000 -> 00010110 */
    0x96,   /* 01101001 -> 10010110 */
    0x56,   /* 01101010 -> 01010110 */
    0xD6,   /* 01101011 -> 11010110 */
    0x36,   /* 01101100 -> 00110110 */
    0xB6,   /* 01101101 -> 10110110 */
    0x76,   /* 01101110 -> 01110110 */
    0xF6,   /* 01101111 -> 11110110 */
    0x0E,   /* 01110000 -> 00001110 */
    0x8E,   /* 01110001 -> 10001110 */
    0x4E,   /* 01110010 -> 01001110 */
    0xCE,   /* 01110011 -> 11001110 */
    0x2E,   /* 01110100 -> 00101110 */
    0xAE,   /* 01110101 -> 10101110 */
    0x6E,   /* 01110110 -> 01101110 */
    0xEE,   /* 01110111 -> 11101110 */
    0x1E,   /* 01111000 -> 00011110 */
    0x9E,   /* 01111001 -> 10011110 */
    0x5E,   /* 01111010 -> 01011110 */
    0xDE,   /* 01111011 -> 11011110 */
    0x3E,   /* 01111100 -> 00111110 */
    0xBE,   /* 01111101 -> 10111110 */
    0x7E,   /* 01111110 -> 01111110 */
    0xFE,   /* 01111111 -> 11111110 */
    0x01,   /* 10000000 -> 00000001 */
    0x81,   /* 10000001 -> 10000001 */
    0x41,   /* 10000010 -> 01000001 */
    0xC1,   /* 10000011 -> 11000001 */
    0x21,   /* 10000100 -> 00100001 */
    0xA1,   /* 10000101 -> 10100001 */
    0x61,   /* 10000110 -> 01100001 */
    0xE1,   /* 10000111 -> 11100001 */
    0x11,   /* 10001000 -> 00010001 */
    0x91,   /* 10001001 -> 10010001 */
    0x51,   /* 10001010 -> 01010001 */
    0xD1,   /* 10001011 -> 11010001 */
    0x31,   /* 10001100 -> 00110001 */
    0xB1,   /* 10001101 -> 10110001 */
    0x71,   /* 10001110 -> 01110001 */
    0xF1,   /* 10001111 -> 11110001 */
    0x09,   /* 10010000 -> 00001001 */
    0x89,   /* 10010001 -> 10001001 */
    0x49,   /* 10010010 -> 01001001 */
    0xC9,   /* 10010011 -> 11001001 */
    0x29,   /* 10010100 -> 00101001 */
    0xA9,   /* 10010101 -> 10101001 */
    0x69,   /* 10010110 -> 01101001 */
    0xE9,   /* 10010111 -> 11101001 */
    0x19,   /* 10011000 -> 00011001 */
    0x99,   /* 10011001 -> 10011001 */
    0x59,   /* 10011010 -> 01011001 */
    0xD9,   /* 10011011 -> 11011001 */
    0x39,   /* 10011100 -> 00111001 */
    0xB9,   /* 10011101 -> 10111001 */
    0x79,   /* 10011110 -> 01111001 */
    0xF9,   /* 10011111 -> 11111001 */
    0x05,   /* 10100000 -> 00000101 */
    0x85,   /* 10100001 -> 10000101 */
    0x45,   /* 10100010 -> 01000101 */
    0xC5,   /* 10100011 -> 11000101 */
    0x25,   /* 10100100 -> 00100101 */
    0xA5,   /* 10100101 -> 10100101 */
    0x65,   /* 10100110 -> 01100101 */
    0xE5,   /* 10100111 -> 11100101 */
    0x15,   /* 10101000 -> 00010101 */
    0x95,   /* 10101001 -> 10010101 */
    0x55,   /* 10101010 -> 01010101 */
    0xD5,   /* 10101011 -> 11010101 */
    0x35,   /* 10101100 -> 00110101 */
    0xB5,   /* 10101101 -> 10110101 */
    0x75,   /* 10101110 -> 01110101 */
    0xF5,   /* 10101111 -> 11110101 */
    0x0D,   /* 10110000 -> 00001101 */
    0x8D,   /* 10110001 -> 10001101 */
    0x4D,   /* 10110010 -> 01001101 */
    0xCD,   /* 10110011 -> 11001101 */
    0x2D,   /* 10110100 -> 00101101 */
    0xAD,   /* 10110101 -> 10101101 */
    0x6D,   /* 10110110 -> 01101101 */
    0xED,   /* 10110111 -> 11101101 */
    0x1D,   /* 10111000 -> 00011101 */
    0x9D,   /* 10111001 -> 10011101 */
    0x5D,   /* 10111010 -> 01011101 */
    0xDD,   /* 10111011 -> 11011101 */
    0x3D,   /* 10111100 -> 00111101 */
    0xBD,   /* 10111101 -> 10111101 */
    0x7D,   /* 10111110 -> 01111101 */
    0xFD,   /* 10111111 -> 11111101 */
    0x03,   /* 11000000 -> 00000011 */
    0x83,   /* 11000001 -> 10000011 */
    0x43,   /* 11000010 -> 01000011 */
    0xC3,   /* 11000011 -> 11000011 */
    0x23,   /* 11000100 -> 00100011 */
    0xA3,   /* 11000101 -> 10100011 */
    0x63,   /* 11000110 -> 01100011 */
    0xE3,   /* 11000111 -> 11100011 */
    0x13,   /* 11001000 -> 00010011 */
    0x93,   /* 11001001 -> 10010011 */
    0x53,   /* 11001010 -> 01010011 */
    0xD3,   /* 11001011 -> 11010011 */
    0x33,   /* 11001100 -> 00110011 */
    0xB3,   /* 11001101 -> 10110011 */
    0x73,   /* 11001110 -> 01110011 */
    0xF3,   /* 11001111 -> 11110011 */
    0x0B,   /* 11010000 -> 00001011 */
    0x8B,   /* 11010001 -> 10001011 */
    0x4B,   /* 11010010 -> 01001011 */
    0xCB,   /* 11010011 -> 11001011 */
    0x2B,   /* 11010100 -> 00101011 */
    0xAB,   /* 11010101 -> 10101011 */
    0x6B,   /* 11010110 -> 01101011 */
    0xEB,   /* 11010111 -> 11101011 */
    0x1B,   /* 11011000 -> 00011011 */
    0x9B,   /* 11011001 -> 10011011 */
    0x5B,   /* 11011010 -> 01011011 */
    0xDB,   /* 11011011 -> 11011011 */
    0x3B,   /* 11011100 -> 00111011 */
    0xBB,   /* 11011101 -> 10111011 */
    0x7B,   /* 11011110 -> 01111011 */
    0xFB,   /* 11011111 -> 11111011 */
    0x07,   /* 11100000 -> 00000111 */
    0x87,   /* 11100001 -> 10000111 */
    0x47,   /* 11100010 -> 01000111 */
    0xC7,   /* 11100011 -> 11000111 */
    0x27,   /* 11100100 -> 00100111 */
    0xA7,   /* 11100101 -> 10100111 */
    0x67,   /* 11100110 -> 01100111 */
    0xE7,   /* 11100111 -> 11100111 */
    0x17,   /* 11101000 -> 00010111 */
    0x97,   /* 11101001 -> 10010111 */
    0x57,   /* 11101010 -> 01010111 */
    0xD7,   /* 11101011 -> 11010111 */
    0x37,   /* 11101100 -> 00110111 */
    0xB7,   /* 11101101 -> 10110111 */
    0x77,   /* 11101110 -> 01110111 */
    0xF7,   /* 11101111 -> 11110111 */
    0x0F,   /* 11110000 -> 00001111 */
    0x8F,   /* 11110001 -> 10001111 */
    0x4F,   /* 11110010 -> 01001111 */
    0xCF,   /* 11110011 -> 11001111 */
    0x2F,   /* 11110100 -> 00101111 */
    0xAF,   /* 11110101 -> 10101111 */
    0x6F,   /* 11110110 -> 01101111 */
    0xEF,   /* 11110111 -> 11101111 */
    0x1F,   /* 11111000 -> 00011111 */
    0x9F,   /* 11111001 -> 10011111 */
    0x5F,   /* 11111010 -> 01011111 */
    0xDF,   /* 11111011 -> 11011111 */
    0x3F,   /* 11111100 -> 00111111 */
    0xBF,   /* 11111101 -> 10111111 */
    0x7F,   /* 11111110 -> 01111111 */
    0xFF    /* 11111111 -> 11111111 */
};
# endif /* WORDS_BIGENDIAN */
#endif /* T1LIB */

static struct frame frame0;	/* dummy head of list */
#ifdef	TEXXET
static struct frame *scan_frame;	/* head frame for scanning */
#endif

static const char *const reverse_search_helptext =
"Forward/reverse search allows you to jump from a point "
"in the DVI file to the corresponding location in the .tex source file, and vice versa. "
"To make this possible, the .tex file needs to be compiled with source special support. "
"This can be done by using either a package like \"srcltx.sty\" or \"srctex.sty\", "
"or a command-line switch like \"-src\" for the TeX executable. "
"See the xdvi man page (section SOURCE SPECIALS) "
"for more information about this.";

static ubyte dvi_buffer[DVI_BUFFER_LEN];
ubyte *G_dvi_buf_ptr = dvi_buffer;
static struct frame *current_frame;

/* Points to drawinf record containing current dvi file location (for update by
   geom_scan).  */
static	struct drawinf	*dvi_pointer_frame = NULL;

#ifdef TEXXET
#define	DIR	currinf.dir
#else
#define	DIR	1
#endif

/*
 *	Explanation of the following constant:
 *	offset_[xy]   << 16:	margin (defaults to one inch)
 *	currwin.shrinkfactor << 16:	one pixel page border
 *	currwin.shrinkfactor << 15:	rounding for pixel_conv
 */
#define OFFSET_X	(resource.xoffset_int << 16) + (currwin.shrinkfactor * 3 << 15)
#define OFFSET_Y	(resource.yoffset_int << 16) + (currwin.shrinkfactor * 3 << 15)

#if (BMBYTES == 1)
bmUnitT bit_masks[] = {
    0x0, 0x1, 0x3, 0x7,
    0xf, 0x1f, 0x3f, 0x7f,
    0xff
};
#else
#if (BMBYTES == 2)
bmUnitT bit_masks[] = {
    0x0, 0x1, 0x3, 0x7,
    0xf, 0x1f, 0x3f, 0x7f,
    0xff, 0x1ff, 0x3ff, 0x7ff,
    0xfff, 0x1fff, 0x3fff, 0x7fff,
    0xffff
};
#else /* BMBYTES == 4 */
bmUnitT bit_masks[] = {
    0x0, 0x1, 0x3, 0x7,
    0xf, 0x1f, 0x3f, 0x7f,
    0xff, 0x1ff, 0x3ff, 0x7ff,
    0xfff, 0x1fff, 0x3fff, 0x7fff,
    0xffff, 0x1ffff, 0x3ffff, 0x7ffff,
    0xfffff, 0x1fffff, 0x3fffff, 0x7fffff,
    0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff,
    0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff,
    0xffffffff
};
#endif
#endif

#ifdef	VMS
#define	off_t	int
#endif
extern off_t lseek();

#ifndef	SEEK_SET	/* if <unistd.h> is not provided (or for <X11R5) */
#define	SEEK_SET	0
#define	SEEK_CUR	1
#define	SEEK_END	2
#endif

static void draw_part(FILE *fp, struct frame *minframe, double current_dimconv);

static void source_fwd_draw_box(void);



/*
 *	X routines.
 */

/*
 *	Put a rectangle on the screen.
 */
static void
put_rule(int x, int y, unsigned int w, unsigned int h)
{
    if (htex_inside_href)
	htex_record_position(x, y, w, h);
    if (x < globals.win_expose.max_x && x + (int)w >= globals.win_expose.min_x && y < globals.win_expose.max_y && y + (int)h >= globals.win_expose.min_y) {
	if (--globals.ev.ctr == 0) {
	    if (read_events(EV_NOWAIT) & EV_GE_MAG_GONE) {
/* 		fprintf(stderr, "longjmp1!\n"); */
		longjmp(globals.ev.canit, 1);
	    }
	}
#if COLOR
	if (fg_active != fg_current)
	    do_color_change();
#endif
	XFillRectangle(DISP, currwin.win,
		       globals.gc.rule,
		       x - currwin.base_x,
		       y - currwin.base_y,
		       w ? w : 1,
		       h ? h : 1);
    }
}

static void
put_bitmap(struct bitmap *bitmap, int x, int y)
{
    if (globals.debug & DBG_BITMAP)
	printf("X(%d,%d)\n", x - currwin.base_x, y - currwin.base_y);
    if (htex_inside_href)
	htex_record_position(x, y, bitmap->w, bitmap->h);
    if (x < globals.win_expose.max_x && x + (int)bitmap->w >= globals.win_expose.min_x &&
	y < globals.win_expose.max_y && y + (int)bitmap->h >= globals.win_expose.min_y) {
	if (--globals.ev.ctr == 0)
	    if (read_events(EV_NOWAIT) & EV_GE_MAG_GONE) {
/* 		fprintf(stderr, "longjmp2!\n"); */
		longjmp(globals.ev.canit, 1);
	    }
#if COLOR
	if (fg_active != fg_current)
	    do_color_change();
#endif
	G_image->width = bitmap->w;
	G_image->height = bitmap->h;
	G_image->data = bitmap->bits;
	G_image->bytes_per_line = bitmap->bytes_wide;
	XPutImage(DISP, currwin.win, globals.gc.fore, G_image,
		  0, 0,
		  x - currwin.base_x, y - currwin.base_y, bitmap->w, bitmap->h);
	if (globals.gc.fore2) {
	    XPutImage(DISP, currwin.win, globals.gc.fore2, G_image,
		      0, 0,
		      x - currwin.base_x, y - currwin.base_y,
		      bitmap->w, bitmap->h);
	}
    }
}

#if GREY

/* Anti-aliasing stuff.
   The method used here is supersampling of the unshrunk glyph (this
   also means that no anti-aliasing happens at shrink 1). A sample
   of the number of bits that are `on' in the unsrunk glyph determines
   the grey level of the shrunk image.
*/

/* Pixel lookup tables for anti-aliasing: These store all possible
   supersampling values (i.e. number of bits set in the unshrunk
   image) for the given shrink factor. E.g. at shrink level 2, the
   size of the pixel table is 4; in other words, 1 pixel in the shrunk
   image corresponds to 4 pixels in the unshrunk image. Thus, the possible
   values for shink level 2 are: black, 1/4 (0x404040), 1/2 (0x808080),
   3/4 (0xc0c0c0) and white (0xffffff).
*/
static Pixel *pixeltbl;
static Pixel *pixeltbl_gc2; /* drawing to globals.gc.fore2 (compare pixmap2_gc2) */

static void shrink_glyph_grey(struct glyph *);

static void
put_image(struct glyph *g, int x, int y)
{
    XImage *img = g->image2;

    if (htex_inside_href)
	htex_record_position(x, y, img->width, img->height);
    if (x < globals.win_expose.max_x && x + img->width >= globals.win_expose.min_x &&
	y < globals.win_expose.max_y && y + img->height >= globals.win_expose.min_y) {
	if (--globals.ev.ctr == 0)
	    if (read_events(EV_NOWAIT) & EV_GE_MAG_GONE) {
/* 		fprintf(stderr, "longjmp3!\n"); */
		longjmp(globals.ev.canit, 1);
	    }

#if COLOR
	if (g->fg != fg_current)	/* if color change since last use */
	    shrink_glyph_grey(g);
	else if (fg_active != fg_current)	/* if GCs need updating */
	    do_color_change();
#endif
	/* TODO: Can we increase gamma locally to make the inverted text more readable?
	   
	   and to draw the background, so something like this:
	   XFillRectangle(DISP, currwin.win, globals.gc.fore, x - currwin.base_x, y - currwin.base_y,
	   (unsigned int)img->width * 2, (unsigned int)img->height * 2);
	   
	   test this with color changes!!
	*/
	/*TEST_DELAY("check point 1 ...")*/
	XPutImage(DISP, currwin.win, globals.gc.fore, img,
		  0, 0,
		  x - currwin.base_x, y - currwin.base_y,
		  (unsigned int)img->width, (unsigned int)img->height);
	/*TEST_DELAY("check point 2 ...")*/

	if (globals.gc.fore2 != NULL) {
	    img->data = g->pixmap2_gc2;
	    XPutImage(DISP, currwin.win, globals.gc.fore2, img,
		      0, 0,
		      x - currwin.base_x, y - currwin.base_y,
		      (unsigned int)img->width, (unsigned int)img->height);
	    img->data = g->pixmap2;
	}
	/*TEST_DELAY("check point 3 ...")*/
    }
}
#endif /* GREY */

/*
 *	Draw the border of a rectangle on the screen.
 *	This should be replaced by a grey background both in Xaw and Motif
 *	as soon as #470325 is fixed (see also FIXED_FLUSHING_PAGING).
 */
static void
draw_border(int x, int y, unsigned int width, unsigned int height, GC ourGC)
{
    --width;
    --height;
    XDrawRectangle(DISP, currwin.win, ourGC, x, y, width, height);
}


/*
 *	Byte reading routines for dvi file.
 */

#define	xtell(fp, pos)	(lseek(fileno(fp), 0L, SEEK_CUR) - \
			    (currinf.end - (pos)))

static ubyte
xxone(FILE *fp)
{
    if (currinf.virtual) {
	++currinf.pos;
	return EOP;
    }

    currinf.end = G_dvi_buf_ptr
	+ read(fileno(fp), (char *)(currinf.pos = G_dvi_buf_ptr), DVI_BUFFER_LEN);
    return currinf.end > G_dvi_buf_ptr ? *(currinf.pos)++ : EOF;
}

#define	xone(fp)  (currinf.pos < currinf.end ? *(currinf.pos)++ : xxone(fp))

static unsigned long
xnum(FILE *fp, ubyte size)
{
    long x = 0;

    while (size--)
	x = (x << 8) | xone(fp);
    return x;
}

static long
xsnum(FILE *fp, ubyte size)
{
    long x;

#if	__STDC__
    x = (signed char)xone(fp);
#else
    x = xone(fp);
    if (x & 0x80)
	x -= 0x100;
#endif
    while (--size)
	x = (x << 8) | xone(fp);
    return x;
}

#define	xsfour(fp)	xsnum(fp, 4)

static void
xskip(FILE *fp, long offset)
{
    currinf.pos += offset;
    if (!currinf.virtual && currinf.pos > currinf.end)
	(void)lseek(fileno(fp), (long)(currinf.pos - currinf.end), SEEK_CUR);
}

void
dvi_fmt_error(const char *message, ...)
{
    
    va_list args;
    va_start(args, message);
    fprintf(stderr, "%s: ", globals.program_name);
    (void)vfprintf(stderr, message, args);
    va_end(args);
    if (currinf.virtual)
	fprintf(stderr, " in virtual font %s\n", currinf.virtual->fontname);
    else
	fprintf(stderr, ", offset %ld\n", (long)xtell(globals.dvi_file.bak_fp, currinf.pos - 1));
    /* #ifndef NDEBUG */
    /*     xdvi_exit(EXIT_FAILURE); */
    /* #else */
    XDVI_ABORT((stderr, "I'll abort now, to help you debugging this."));
    /* #endif */
}


/*
 *	Code for debugging options.
 */

static void
print_bitmap(struct bitmap *bitmap)
{
    bmUnitT *ptr = (bmUnitT *)bitmap->bits;
    int x, y, i;

    ASSERT(ptr != NULL, "Invalid bitmap bits");

    printf("w = %d, h = %d, bytes wide = %d\n",
	   bitmap->w, bitmap->h, bitmap->bytes_wide);
    for (y = 0; y < (int)bitmap->h; ++y) {
	for (x = bitmap->bytes_wide; x > 0; x -= BMBYTES) {
#ifdef WORDS_BIGENDIAN
	    for (i = BMBITS - 1; i >= 0; --i)
		putchar((*ptr & (1 << i)) ? '@' : '.');
#else
	    for (i = 0; i < BMBITS; ++i)
		putchar((*ptr & (1 << i)) ? '@' : '.');
#endif
	    ++ptr;
	}
	putchar('\n');
    }
}

static void
print_char(ubyte ch, struct glyph *g)
{
    printf("char %d", ch);
    if (isprint(ch))
	printf(" (%c)", ch);
    putchar('\n');
    printf("x = %d, y = %d, dvi = %ld\n", g->x, g->y, g->dvi_adv);
    print_bitmap(&g->bitmap);
}

static const char *dvi_table1[] = {
    "SET1", "SET2", NULL, NULL, "SETRULE", "PUT1", "PUT2", NULL,
    NULL, "PUTRULE", "NOP", "BOP", "EOP", "PUSH", "POP", "RIGHT1",
    "RIGHT2", "RIGHT3", "RIGHT4", "W0", "W1", "W2", "W3", "W4",
    "X0", "X1", "X2", "X3", "X4", "DOWN1", "DOWN2", "DOWN3",
    "DOWN4", "Y0", "Y1", "Y2", "Y3", "Y4", "Z0", "Z1",
    "Z2", "Z3", "Z4"
};

static const char *dvi_table2[] = {
    "FNT1", "FNT2", "FNT3", "FNT4", "XXX1", "XXX2", "XXX3", "XXX4",
    "FNTDEF1", "FNTDEF2", "FNTDEF3", "FNTDEF4", "PRE", "POST", "POSTPOST",
    "SREFL", "EREFL", NULL, NULL, NULL, NULL
};

static void
print_dvi(ubyte ch)
{
    const char *s;

    fprintf(stderr, "%4d %4d ", PXL_H, PXL_V);
    if (ch <= (ubyte) (SETCHAR0 + 127)) {
	fprintf(stderr, "SETCHAR%-3d", ch - SETCHAR0);
	if (isprint(ch))
	    fprintf(stderr, " (%c)", ch);
	fputc('\n', stderr);
	return;
    }
    else if (ch < FNTNUM0)
	s = dvi_table1[ch - 128];
    else if (ch <= (ubyte) (FNTNUM0 + 63)) {
	fprintf(stderr, "FNTNUM%d\n", ch - FNTNUM0);
	return;
    }
    else
	s = dvi_table2[ch - (FNTNUM0 + 64)];
    if (s) {
	fputs(s, stderr);
	fputc('\n', stderr);
    }
    else
	XDVI_FATAL((stderr, "print_dvi: unknown op-code %d", ch));
}


/*
 *	Count the number of set bits in a 4x4-region of the bitmap
 */

static char sample_count[] = {
    0, 1, 1, 2,
    1, 2, 2, 3,
    1, 2, 2, 3,
    2, 3, 3, 4
};

/*
 * For greyscaling, return the number of bits that are set in a given region
 * of width w and height h of the bitmap `bits', starting horizontally after
 * `bit_skip' bits, where `bytes_wide' is the same as the `bytes_wide' field
 * in the bitmap struct (scan line width in bytes).
 * Note that `bits' is really a one-dimensional array, i.e. all the rows
 * are put in sequence into one single row.
 */
static int
sample(bmUnitT *bits, int bytes_wide, int bit_skip, int w, int h)
{
    bmUnitT *beg_ptr, *end_ptr, *curr_ptr;
    int bits_left;
    int n, bit_shift, wid;

#if TRACE_AA
    fprintf(stderr, "sample: %d bytes wide, %d skip, %d w, %d h\n", bytes_wide, bit_skip, w, h);
#endif

    beg_ptr = bits + bit_skip / BMBITS;
    end_ptr = ADD(bits, h * bytes_wide);
    
#if TRACE_AA
    fprintf(stderr, "beg_ptr: %p, end: %p\n", (void *)beg_ptr, (void *)end_ptr);
#endif
    
    bits_left = w;
#ifdef	WORDS_BIGENDIAN
    bit_shift = BMBITS - bit_skip % BMBITS;
#else
    bit_shift = bit_skip % BMBITS;
#endif
#if TRACE_AA
    fprintf(stderr, "shift: %d\n", bit_shift);
#endif
    n = 0;
	
    while (bits_left) {
#ifdef	WORDS_BIGENDIAN
	wid = bit_shift;
#else
	wid = BMBITS - bit_shift;
#endif
	if (wid > bits_left)
	    wid = bits_left;
	if (wid > 4) /* why? */
	    wid = 4;
#ifdef	WORDS_BIGENDIAN
	bit_shift -= wid;
#endif
	for (curr_ptr = beg_ptr;
	     curr_ptr < end_ptr;
	     curr_ptr = ADD(curr_ptr, bytes_wide)) {
#if TRACE_AA
	    int i;
	    for (i = 0; i < bytes_wide; i++) {
		fprintf(stderr, "%d ", *(curr_ptr + i));
	    }
	    fprintf(stderr, "\nMask: %d; count: %d\n", wid,
		    sample_count[ (*curr_ptr >> bit_shift) & bit_masks[wid] ]);
#endif
	    n += sample_count[ (*curr_ptr >> bit_shift) & bit_masks[wid] ];
	}
#ifdef	WORDS_BIGENDIAN
	if (bit_shift == 0) {
	    bit_shift = BMBITS;
	    ++beg_ptr;
	}
#else
	bit_shift += wid;
	if (bit_shift == BMBITS) {
	    bit_shift = 0;
	    ++beg_ptr;
	}
#endif
	bits_left -= wid;
	/*  	    fprintf(stderr, "bits_left: %d\n", bits_left); */
    }
    return n;
}

static void
shrink_glyph(struct glyph *g)
{
    int shrunk_bytes_wide, shrunk_height;
    int rows_left, rows, init_cols;
    int cols_left;
    int cols;
    bmUnitT *unshrunk_ptr, *shrunk_ptr;
    bmUnitT m, *cp;
    /* threshold for which a bit will be set to `on' in the shrunken bitmap */
    int min_sample = currwin.shrinkfactor * currwin.shrinkfactor * resource.density / 100;
    int row_num;

    /* These machinations ensure that the character is shrunk according to
       its hot point, rather than its upper left-hand corner. */
    g->x2 = g->x / currwin.shrinkfactor;
    init_cols = g->x - g->x2 * currwin.shrinkfactor;
    if (init_cols <= 0)
	init_cols += currwin.shrinkfactor;
    else
	++g->x2;
    g->bitmap2.w = g->x2 + ROUNDUP((int)g->bitmap.w - g->x, currwin.shrinkfactor);
    /* include row zero with the positively numbered rows */
    row_num = g->y + 1;
    g->y2 = row_num / currwin.shrinkfactor;
    rows = row_num - g->y2 * currwin.shrinkfactor;
    if (rows <= 0) {
	rows += currwin.shrinkfactor;
	--g->y2;
    }
    g->bitmap2.h = shrunk_height
	= g->y2 + ROUNDUP((int)g->bitmap.h - row_num, currwin.shrinkfactor) + 1;
    alloc_bitmap(&g->bitmap2);

    unshrunk_ptr = (bmUnitT *) g->bitmap.bits;
    shrunk_ptr = (bmUnitT *) g->bitmap2.bits;
    shrunk_bytes_wide = g->bitmap2.bytes_wide;
    rows_left = g->bitmap.h;
    memset((char *)shrunk_ptr, '\0', shrunk_bytes_wide * shrunk_height);
    
    while (rows_left) {
	if (rows > rows_left)
	    rows = rows_left;
	cols_left = g->bitmap.w;
#ifndef	WORDS_BIGENDIAN
	m = 1; /* XXX was (1 << 0) */
#else
	m = ((bmUnitT)1 << (BMBITS - 1));
#endif
	cp = shrunk_ptr;
	cols = init_cols;
	while (cols_left) {
	    /* ??? */
	    if (cols > cols_left)
		cols = cols_left;
	    /* set a bit to `on' if it's over the threshold */
	    if (sample(unshrunk_ptr, g->bitmap.bytes_wide,
		       (int)g->bitmap.w - cols_left, cols, rows)
		>= min_sample)
		*cp |= m;
#ifndef	WORDS_BIGENDIAN
	    if (m == ((bmUnitT)1 << (BMBITS - 1))) {
		m = 1; /* XXX was (1 << 0) */
		++cp;
	    }
	    else
		m <<= 1;
#else
	    if (m == 1) { /* XXX was (1 << 0) */
		m = ((bmUnitT)1 << (BMBITS - 1));
		++cp;
	    }
	    else
		m >>= 1;
#endif
	    cols_left -= cols;
	    cols = currwin.shrinkfactor;
	}
	shrunk_ptr += shrunk_bytes_wide / sizeof(bmUnitT);
	unshrunk_ptr += rows * g->bitmap.bytes_wide / sizeof(bmUnitT);
/* 	*((char **)&shrunk_ptr) += shrunk_bytes_wide; */
/* 	*((char **)&unshrunk_ptr) += rows * g->bitmap.bytes_wide; */
	rows_left -= rows;
	rows = currwin.shrinkfactor;
    }
    g->y2 = g->y / currwin.shrinkfactor;
    if (globals.debug & DBG_BITMAP)
	print_bitmap(&g->bitmap2);
}

#ifdef	GREY

#ifdef RGB_ANTI_ALIASING
/* void */
/* filter_colors(Pixel *p1, Pixel *p2) */
/* { */
/*     Pixel res1, res2; */

/*     int res1_r = *p1 & G_visual->red_mask; */
/*     int res1_g = *p1 & G_visual->green_mask; */
/*     int res1_b = *p1 & G_visual->blue_mask; */

/*     int res2_r = *p2 & G_visual->red_mask; */
/*     int res2_g = *p2 & G_visual->green_mask; */
/*     int res2_b = *p2 & G_visual->blue_mask; */
/* } */

static void
color_filter_image(struct glyph *g)
{
    int rows = g->bitmap2.h;
    int cols = g->bitmap2.w;
    int i, j, k;
    int arr_size = rows * cols * 3;
    unsigned char *arr = xmalloc(arr_size * sizeof *arr);
    unsigned char *arr2 = xmalloc(arr_size * sizeof *arr2);

    fprintf(stderr, "total: %d\n", arr_size);
    
    for (i = 0, j = 0; j < rows; j++) {
	for (k = 0; k < cols; k++) {
	    Pixel p = XGetPixel(g->image2, k, j);
#if TRACE_AA3
	    fprintf(stderr, "k: %d, j: %d, idx: %d\n", k, j, i);
#endif
	    arr[i] = (p & G_visual->red_mask) >> 16;
	    arr[i + 1] = (p & G_visual->green_mask) >> 8;
	    arr[i + 2] = (p & G_visual->blue_mask);
#if TRACE_AA3
	    fprintf(stderr, "0x%.6lX -> %d, %d, %d\n", p, arr[i], arr[i + 1], arr[i + 2]);
#endif
	    i += 3;
	}
    }

    for (i = 0; i < arr_size; i++) {
	if (i == 0) { /* merge two elems */
	    if (i == arr_size - 1) /* only one elem, do nothing */
		break;
	    arr2[i] = (int)(resource.subpixel_energy[0] * arr[i] +
			    resource.subpixel_energy[1] * arr[i + 1] + 0.5);
	}
	else if (i == arr_size - 1) { /* merge two elems */
	    if (i == 0) /* only one elem, do nothing */
		break;
	    arr2[i] = (int)(resource.subpixel_energy[1] * arr[i - 1] +
			    resource.subpixel_energy[0] * arr[i] + 0.5);
	}
	else { /* merge three elems */
	    arr2[i] = (int)(resource.subpixel_energy[1] * arr[i - 1] +
			    resource.subpixel_energy[0] * arr[i] +
			    resource.subpixel_energy[1] * arr[i + 1] + 0.5);
#if TRACE_AA3
	    fprintf(stderr, "%d, %d, %d -> %d\n", arr[i - 1], arr[i], arr[i + 1], arr2[i]);
#endif
	}
    }

    for (i = 0, j = 0; j < rows; j++) {
	for (k = 0; k < cols; k++) {
	    Pixel p = arr2[i] << 16 | (arr2[i + 1] << 8) | arr2[i + 2];
#if TRACE_AA3
	    fprintf(stderr, "%d, %d, %d -> 0x%.6lX\n", arr2[i], arr2[i + 1], arr2[i + 2], p);
#endif
	    XPutPixel(g->image2, k, j, p);
	    i += 3;
	}
    }
    
    free(arr);
    free(arr2);
}

#endif

static void
shrink_glyph_grey(struct glyph *g)
{
    int rows_left, rows, init_cols;
    int cols_left;
    int cols;
    int x, y;
    long thesample;
/*     int min_sample = currwin.shrinkfactor * currwin.shrinkfactor * resource.density / 100; */
    Pixel onoff, onoff2;
    bmUnitT *unshrunk_ptr;
    unsigned int size;
    int row_num;
    int actual_w;

#if COLOR
    if (fg_active != fg_current) {
	do_color_change();
    }
#endif

    /* TODO: rounding errors causing color fringing (see HACK comment below):
       
       \documentclass{article}
       \pagestyle{empty}

       \begin{document}
       l
       \end{document}

       With ./xdvi-xaw.bin -name xdvi -subpixel rgb -s 4 ./test.dvi:

       subpixel order: rgb = 1
       g->x2: 0, init_cols: -2
       AFTER: g->x2: 0, init_cols: 2

       but with ./xdvi-xaw.bin -name xdvi -s 4 ./test.dvi:

       g->x2: 0, init_cols: -3
       AFTER: g->x2: 0, init_cols: 1
    */
    
    /* These machinations ensure that the character is shrunk according to
       its hot point, rather than its upper left-hand corner. */
#ifdef RGB_ANTI_ALIASING
    if (resource.subpixel_order == SUBPIXEL_NONE)
	g->x2 = g->x / currwin.shrinkfactor;
    else {
	if (g->x < 0)
	    g->x2 = (int)(g->x / 3.0 - 0.5) / currwin.shrinkfactor;
	else
	    g->x2 = (int)(g->x / 3.0 + 0.5) / currwin.shrinkfactor;
    }
    
    if (resource.subpixel_order == SUBPIXEL_NONE)
	init_cols = g->x - g->x2 * currwin.shrinkfactor;
    else {
	if (g->x < 0)
	    init_cols = (int)(g->x / 3.0 - 0.5) - g->x2 * currwin.shrinkfactor;
	else
	    init_cols = (int)(g->x / 3.0 + 0.5) - g->x2 * currwin.shrinkfactor;
	fprintf(stderr, "g->x: %d, g->x2: %d, init_cols: %d\n", g->x, g->x2, init_cols);
    }
#else
    g->x2 = g->x / currwin.shrinkfactor;
    init_cols = g->x - g->x2 * currwin.shrinkfactor;
#endif
    
    if (init_cols <= 0)
	init_cols += currwin.shrinkfactor;
    else
	++(g->x2);

#ifdef RGB_ANTI_ALIASING
    
    if (resource.subpixel_order == SUBPIXEL_NONE)
	g->bitmap2.w = g->x2 + ROUNDUP((int)g->bitmap.w - g->x, currwin.shrinkfactor);
    else {
	fprintf(stderr, "AFTER: g->x2: %d, init_cols: %d\n", g->x2, init_cols);
/* 	fprintf(stderr, "g->bitmap.w / 3.0 + 0.5: %d; g->x / 3.0: %d; all: %d, %d\n", */
/* 		(int)(g->bitmap.w / 3.0 + 0.5), */
/* 		(int)(g->x / 3.0), */
/* 		(int)((g->bitmap.w / 3.0 + 0.5) - g->x / 3.0), */
/* 		g->x2 + (int)((((g->bitmap.w / 3.0 + 0.5) - g->x / 3.0) + currwin.shrinkfactor - 1) / currwin.shrinkfactor)); */
	g->bitmap2.w = g->x2 + ROUNDUP((int)((g->bitmap.w / 3.0 + 0.5) - g->x / 3.0), currwin.shrinkfactor);
/* 	fprintf(stderr, "g->bitmap.w: %d\n", g->bitmap2.w); */
    }
#else
    g->bitmap2.w = g->x2 + ROUNDUP((int)g->bitmap.w - g->x, currwin.shrinkfactor);
#endif

    /* include row zero with the positively numbered rows */
    row_num = g->y + 1;
#ifdef DBG_AA
    fprintf(stderr, "row_num: %d\n", row_num);
#endif /* DBG_AA */
    /* g->y2 is the new height of the image: */
    g->y2 = row_num / currwin.shrinkfactor;
#ifdef DBG_AA
    fprintf(stderr, "g->y2: %d\n", g->y2);
#endif /* DBG_AA */
    /* in C89 and before, result of division can `truncate towards negative infinity'
       (i.e., round to the larger digit) for negative quotients, hence we need
       to test for rows < 0. OTOH, if rows = 0, use currwin.shrinkfactor instead
       (why?)
    */
    if ((rows = row_num - g->y2 * currwin.shrinkfactor) <= 0) {
	rows += currwin.shrinkfactor;
	--(g->y2);
    }
    g->bitmap2.h = g->y2 + ROUNDUP((int)g->bitmap.h - row_num, currwin.shrinkfactor) + 1;

#if TRACE_AA1
    if (resource.subpixel_order != SUBPIXEL_NONE) {
	fprintf(stderr, "\nbitmap.h: %d, bitmap.w: %d\n", g->bitmap2.h, g->bitmap2.w);
    }
#endif /* DBG_AA */

    /* allocate pixmap for antialiasing ... */
    if (g->pixmap2 == NULL) {
	g->image2 = XCreateImage(DISP, G_visual, G_depth, ZPixmap,
				 0, (char *)NULL, g->bitmap2.w, g->bitmap2.h,
				 BMBITS, 0);
	size = g->image2->bytes_per_line * g->bitmap2.h;
	g->pixmap2 = g->image2->data = xmalloc(size != 0 ? size : 1);
    }
    /* ... and the pixmap used for globals.gc.fore2: */
    if (globals.gc.fore2 != NULL && g->pixmap2_gc2 == NULL) {
	size = g->image2->bytes_per_line * g->bitmap2.h;
	g->pixmap2_gc2 = xmalloc(size != 0 ? size : 1);
    }

#if 0
    if (resource.subpixel_order != SUBPIXEL_NONE) {
	fprintf(stderr, "\n============= BITMAP ==============\n");
	print_bitmap(&g->bitmap);
    }
#endif
    
    unshrunk_ptr = (bmUnitT *)g->bitmap.bits;
    actual_w = g->bitmap.w;
    rows_left = g->bitmap.h;
    y = 0;
    /* the basic algorithm is the same as in the nogrey code, with the main
       exception that the return value of sample() is used. */
    while (rows_left) {
#ifdef RGB_ANTI_ALIASING
	Pixel pixel = 0;
	Pixel pixel2 = 0;
#endif
	x = 0;
	if (rows > rows_left) /* why - extra safety? */
	    rows = rows_left;
	cols_left = g->bitmap.w;
/*  	fprintf(stderr, "init_cols: %d\n", init_cols); */
	cols = init_cols;
	while (cols_left) {
	    if (cols > cols_left) /* why - extra safety? */
		cols = cols_left;
	    
	    thesample = sample(unshrunk_ptr, g->bitmap.bytes_wide,
			       (int)g->bitmap.w - cols_left, cols, rows);
	    
/* 	    if (resource.subpixel_order != SUBPIXEL_NONE && resource.subpixel_energy[2] != 0) */
/* 		onoff = thesample >= min_sample ? 0xffffff : 0; */
/* 	    else */
	    onoff = pixeltbl[thesample];

#ifdef XSERVER_INFO
	    if (globals.debug & DBG_PK) {
		int c;
/* 		fprintf(stderr, "onoff: %d\n", onoff); */
		if (onoff > 65536)
		    c = onoff / 65536;
		else if (onoff > 256)
		    c = onoff / 256;
		else
		    c = onoff;
		if (c == 0)
		    fprintf(stdout, ",..");
		else
		    fprintf(stdout, ",%.2x", c);
	    }
#endif

#ifdef RGB_ANTI_ALIASING
	    if (resource.subpixel_order != SUBPIXEL_NONE) {
		int div = x / 3;
		int rest = x % 3;

		if (resource.subpixel_order == SUBPIXEL_RGB) {
		    if (rest == 0)
			pixel = onoff & G_visual->red_mask;
		    else if (rest == 1)
			pixel |= onoff & G_visual->green_mask;
		    else
			pixel |= onoff & G_visual->blue_mask;
		}
		else { /* SUBPIXEL_BGR */
		    if (rest == 0)
			pixel = onoff & G_visual->blue_mask;
		    else if (rest == 1)
			pixel |= onoff & G_visual->green_mask;
		    else
			pixel |= onoff & G_visual->red_mask;
		}
		
#if 0 && TRACE_AA1
		fprintf(stderr, "sample: %ld; row %d, col %d, left %d: 0x%.6lx; pixel: 0x%.6lx at pos %d,%d (x:%d)\n",
			thesample, rows, cols, cols_left, onoff, pixel, div, y, x);
#endif /* TRACE_AA */
		fprintf(stderr, "pixel at %d\n", div);
		XPutPixel(g->image2, div, y, pixel);

		/* HACK to fix color fringing problem */
		if (div + 1 < g->bitmap2.w) {
		    fprintf(stderr, "rest pixel at %d\n", div);
		    XPutPixel(g->image2, div + 1, y, 0);
		}
	    }
	    else {
#if 0 && TRACE_AA1
		fprintf(stderr, "sample: %ld; row %d, col %d, left %d: 0x%.6lx at pos %d, %d\n",
			thesample, rows, cols, cols_left, onoff, x, y);
#endif /* TRACE_AA */
		XPutPixel(g->image2, x, y, onoff);
	    }
#else
	    XPutPixel(g->image2, x, y, onoff);
#endif /* RGB_ANTI_ALIASING */
	    if (globals.gc.fore2 != NULL) {
		onoff2 = pixeltbl_gc2[thesample];
#ifdef RGB_ANTI_ALIASING
		if (resource.subpixel_order != SUBPIXEL_NONE) {
		    int div = x / 3;
		    int rest = x % 3;
		    
		    if (resource.subpixel_order == SUBPIXEL_RGB) {
			if (rest == 0)
			    pixel2 = onoff2 & G_visual->red_mask;
			else if (rest == 1)
			    pixel2 |= onoff2 & G_visual->green_mask;
			else
			pixel2 |= onoff2 & G_visual->blue_mask;
		    }
		    else { /* SUBPIXEL_BGR */
			if (rest == 0)
			    pixel2 = onoff2 & G_visual->blue_mask;
			else if (rest == 1)
			    pixel2 |= onoff2 & G_visual->green_mask;
			else
			    pixel2 |= onoff2 & G_visual->red_mask;
		    }
		    
		    g->image2->data = g->pixmap2_gc2;
#if TRACE_AA1
		    fprintf(stderr, "fore2 at %d, %d\n", div, y);
#endif
		    XPutPixel(g->image2, div, y, pixel2);
		    g->image2->data = g->pixmap2;
		}
		else {
#else /* RGB_ANTI_ALIASING */
		    g->image2->data = g->pixmap2_gc2;
		    XPutPixel(g->image2, x, y, onoff2);
		    g->image2->data = g->pixmap2;
#endif
#ifdef RGB_ANTI_ALIASING
		}
#endif
	    }
	    
#if 0 && TRACE_AA1
	    fprintf(stderr, "subtracting %d from %d: %d\n", cols, cols_left, cols_left - cols);
#endif
	    cols_left -= cols;
	    cols = currwin.shrinkfactor;
	    x++;
	}
	/* advance pointer by the number of rows covered */
/* 	fprintf(stderr, "++: %d; %d, %d\n", rows * g->bitmap.bytes_wide, g->bitmap.bytes_wide, sizeof(bmUnitT)); */
	unshrunk_ptr += rows * g->bitmap.bytes_wide / sizeof(bmUnitT);
/* 	*((char **)&unshrunk_ptr) += rows * g->bitmap.bytes_wide; */
	rows_left -= rows;
	rows = currwin.shrinkfactor;
	y++;
#ifdef XSERVER_INFO
	if (globals.debug & DBG_PK)
	    fprintf(stdout, "\n");
#endif
    }
#ifdef XSERVER_INFO
    if (globals.debug & DBG_PK)
	fprintf(stdout, "\n");
#endif

#if 0
    fprintf(stderr, "y: %d, bitmap.h: %d\n", y, (int)g->bitmap2.h);
#endif /* DBG_AA */

    /* fill remaining rows not covered before (how?) */
    while (y < (int)g->bitmap2.h) {
	for (x = 0; x < (int)g->bitmap2.w; x++) {
/*  	    int c = *pixeltbl; */
#if TRACE_AA1
	    fprintf(stderr, "Remaining at %d, %d: 0x%.6lx\n", x, y, *pixeltbl);
#endif
	    XPutPixel(g->image2, x, y, *pixeltbl);
/*  	    if (c == 0) */
/*  		fprintf(stdout, ",.."); */
/*  	    else */
/*  		fprintf(stdout, ",%.2x", c); */
	    if (globals.gc.fore2 != NULL) {
		g->image2->data = g->pixmap2_gc2;
#if TRACE_AA1
		fprintf(stderr, "image2 at %d, %d: 0x%.6lx\n", x, y, *pixeltbl_gc2);
#endif
		XPutPixel(g->image2, x, y, *pixeltbl_gc2);
		g->image2->data = g->pixmap2;
	    }
	}
	y++;
    }

#ifdef RGB_ANTI_ALIASING
    if (resource.subpixel_order != SUBPIXEL_NONE && resource.subpixel_energy[1] != 0)
	color_filter_image(g);
    if (resource.subpixel_order != SUBPIXEL_NONE && resource.subpixel_energy[2] != 0)
	color_filter_image(g);
#endif
    
    g->y2 = g->y / currwin.shrinkfactor;
#if COLOR
    g->fg = fg_current;
#endif
}
#endif /* GREY */

/*
 *	Find font #n.
 */

static void
change_font(unsigned long n)
{
    struct tn *tnp;

    if (n < currinf.tn_table_len)
	currinf.fontp = currinf.tn_table[n];
    else {
	currinf.fontp = NULL;
	for (tnp = currinf.tn_head; tnp != NULL; tnp = tnp->next)
	    if (tnp->TeXnumber == n) {
		currinf.fontp = tnp->fontp;
		break;
	    }
    }
    if (currinf.fontp == NULL)
	XDVI_FATAL((stderr, "Non-existent font number %ld", n));
    if (currinf.fontp->set_char_p == NULL)
	XDVI_FATAL((stderr, "No procedure to set font %ld, %s", n, currinf.fontp->fontname));
    maxchar = currinf.fontp->maxchar;
    currinf.set_char_p = currinf.fontp->set_char_p;
}


/*
 *	Open a font file.
 */

static void
open_font_file(struct font *fontp)
{
    if (fontp->file == NULL) {
	fontp->file = XFOPEN(fontp->filename, OPEN_MODE);
	if (fontp->file == NULL)
	    XDVI_FATAL((stderr, "Couldn't re-locate font file `%s'", fontp->filename));
    }
}

/*
 * Read and return a 0-terminated special string allocated in static memory
 * (i.e. contents will be overwritten by next call of this function).
 */

static char *
read_special(FILE *fp, long nbytes)
{
    static char *spcl = NULL;
    static long spcl_len = -1;
    char *p;

    if (nbytes > spcl_len) {
	spcl = xrealloc(spcl, (unsigned)nbytes + 1);
	spcl_len = nbytes;
    }
    p = spcl;
    for (;;) {
	int i = currinf.end - currinf.pos;

	if (i > nbytes)
	    i = nbytes;
	memcpy(p, (char *)currinf.pos, i);
	currinf.pos += i;
	p += i;
	nbytes -= i;
	if (nbytes == 0)
	    break;
	(void)xxone(fp);
	--currinf.pos;
    }
    *p = '\0';
    return spcl;
}

/*
 *	Table used for scanning.  If >= 0, then skip that many bytes.
 *	M1 means end of page, M2 means special, M3 means FNTDEF,
 *	M4 means unrecognizable, and M5 means doesn't belong here.
 */

#define	M1	255
#define	M2	254
#define	M3	253
#define	M4	252
#define	M5	251
#define	MM	251

static ubyte scantable[256] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* chars 0 - 127 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 2,	/* SET1,SET2 (128,129) */
    /* -,-,SETRULE,PUT1,PUT2,-,-,PUTRULE,NOP,BOP (130-139) */
    M4, M4, 8, 1, 2, M4, M4, 8, 0, 44,
    M1, 0, 0, 1, 2, 3, 4, 0, 1, 2,	/* EOP,PUSH,POP,RIGHT1-4,W0M2 (140-149) */
    3, 4, 0, 1, 2, 3, 4, 1, 2, 3,	/* W3-4,X0-4,DOWN1-3 (150-159) */
    4, 0, 1, 2, 3, 4, 0, 1, 2, 3,	/* DOWN4,Y0-4,Z0-3 (160-169) */
    4,	/* Z4 (170) */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* change font 171 - 234 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 2, 3, 4, M2,	/* FNT1-4,XXX1 (235-239) */
    /* XXX2-4,FNTDEF1-4,PRE,POST,POSTPOST (240-249) */
    M2, M2, M2, M3, M3, M3, M3, M5, M5, M5,
    0, 0, M4, M4, M4, M4
};	/* SREFL,EREFL,-,-,-,- (250-255) */

/*
 *	This is the generic scanning routine.  It assumes that currinf, etc.
 *	are ready to go at the start of the page to be scanned.
 */



Boolean
spcl_scan(Boolean (*spcl_proc)(char *str, int str_len, void *data), void *data, Boolean return_if_found, FILE *fp)
{
    ubyte ch;
    ubyte n;
    long a;

    for (;;) {
	ch = xone(fp);
/*  	print_dvi(ch); */
	n = scantable[ch];
	if (n < MM)
	    while (n-- != 0)
		(void)xone(fp);
	else if (n == M1)
	    break;	/* end of page */
	else
	    switch (n) {
	    case M2:	/* special */
		a = xnum(fp, ch - XXX1 + 1);
		if (a > 0) {
		    if (spcl_proc(read_special(fp, a), a, data) && return_if_found) {
			return True;
		    }
		}
		break;
	    case M3:	/* FNTDEF */
		xskip(fp, (long)(12 + ch - FNTDEF1 + 1));
		ch = xone(fp);
		xskip(fp, (long)ch + (long)xone(fp));
		break;
	    case M4:	/* unrecognizable */
		XDVI_FATAL((stderr, "unknown op-code %d", ch));
		break;
	    case M5:	/* doesn't belong */
		dvi_fmt_error("spcl_scan: shouldn't happen: %s encountered",
			      dvi_table2[ch - (FNTNUM0 + 64)]);
		break;
	    }
    }
    return False;
}

#define	xspell_conv(n)	spell_conv0(n, current_dimconv)

/*
 *	Prescanning routine for dvi file.  This looks for specials like
 *	`header=' and `!'.
 */

void
prescan(FILE *fp)
{
    if (fp == NULL) {
	return;
    }

    TRACE_FILES((stderr, "prescan on %p", (void *)fp));
    
    (void)lseek(fileno(fp), pageinfo_get_offset(scanned_page + 1), SEEK_SET);
    G_dvi_buf_ptr = dvi_buffer;
    currinf.pos = currinf.end = G_dvi_buf_ptr;
    for (;;) {
	if (scanned_page == -1) { /* on first page */
	    TRACE_FILES((stderr, "prescan on page 1"));
	    pageinfo_set_page_width(scanned_page + 1, pageinfo_get_page_width(total_pages));
	    pageinfo_set_page_height(scanned_page + 1, pageinfo_get_page_height(total_pages));
	    pageinfo_set_window_width(scanned_page + 1, pageinfo_get_window_width(total_pages));
	    pageinfo_set_window_height(scanned_page + 1, pageinfo_get_window_height(total_pages));
	}
	else {
	    TRACE_FILES((stderr, "prescan on page %d", scanned_page));
	    pageinfo_set_page_width(scanned_page + 1, pageinfo_get_page_width(scanned_page));
	    pageinfo_set_page_height(scanned_page + 1, pageinfo_get_page_height(scanned_page));
	    pageinfo_set_window_width(scanned_page + 1, pageinfo_get_window_width(scanned_page));
	    pageinfo_set_window_height(scanned_page + 1, pageinfo_get_window_height(scanned_page));
	}
	if (read_events(EV_NOWAIT) & EV_GE_NEWPAGE) {
	    break;
	}
	/* NOTE:  longjmp(globals.ev.canit) should not be done within
	   read_events(). */
	htex_prescan_save();
	htex_prescan_initpage();

	if (!setjmp(globals.ev.canit)) {
	    struct htex_prescan_data data;
	    int pre_depth, depth;
	    data.pageno = scanned_page + 1;
	    data.scan_type = HTEX_ANCHOR_NUM; /* just for the anchor numbers */
	    pre_depth = htex_prescan_get_depth();
	    (void)spcl_scan(scan_special, &data, False, fp);
	    
	    depth = htex_prescan_get_depth();

	    if (depth > pre_depth) {
		/* we have a mismatched anchor. We currently don't deal with
		   _nested_ mismatched anchors (ugh), so there's only one
		   anchor string that can be used as info on the next page. */
		int anchor_num = htex_prescan_get_mismatched_anchor_num(depth);
		/* scan again to find the anchor string at anchor number `anchor_num' */
		(void)lseek(fileno(fp), pageinfo_get_offset(scanned_page + 1), SEEK_SET);
		currinf.pos = currinf.end = G_dvi_buf_ptr;
		data.anchor_num = anchor_num;
		data.scan_type = HTEX_ANCHOR_STRING;
		htex_prescan_reset_firstpass();
		(void)spcl_scan(scan_special, &data, False, fp);
		depth = htex_prescan_get_depth();
	    }
	    else if (depth > 0 && scanned_page >= 0) { /* mismatch was on a previous page */
		htex_prescan_carry_over(scanned_page, scanned_page + 1);
	    }
	}
	else { /* if interrupted */
	    htex_prescan_restore(scanned_page + 1);
#if PS
	    psp.interrupt();
#endif
	    break;
	}
	if (globals.ev.flags & EV_GE_NEWPAGE)
	    break;
	++scanned_page;
#if COLOR
	if (scanned_page_color < scanned_page) {
	    scan_color_eop();
	    scanned_page_color = scanned_page;
	}
#endif
#if PS
	if (scanned_page_ps < scanned_page)
	    scanned_page_ps = scanned_page;
#endif
	if (scanned_page >= current_page)
	    break;
    }
    
#if PS
    if (!(globals.ev.flags & EV_GE_NEWPAGE))
	psp.endheader();
#endif
}

/*
 *	Routines to print characters.
 */

setcharRetvalT
set_char(
#ifdef TEXXET
	 wide_ubyte cmd,
#endif
	 wide_ubyte ch)
{
    struct glyph *g;
#ifdef TEXXET
    long dvi_h_sav;
#endif

    if (ch > maxchar)
	realloc_font(currinf.fontp, (wide_ubyte)ch);
    if ((g = &currinf.fontp->glyph[ch])->bitmap.bits == NULL) {
	if (g->addr == 0) {
	    if (!resource.hush_chars)
		XDVI_WARNING((stderr, "Character %d not defined in font %s", ch, currinf.fontp->fontname));
	    g->addr = -1;
#ifdef TEXXET
	    return;
#else
	    return 0L;
#endif
	}
	if (g->addr == -1) {
#ifdef TEXXET
	    return;
#else
	    return 0L; /* previously flagged missing char */
#endif
	}
	open_font_file(currinf.fontp);
	fseek(currinf.fontp->file, g->addr, SEEK_SET);
	(*currinf.fontp->read_char) (currinf.fontp, ch);
	if (globals.debug & DBG_BITMAP)
	    print_char((ubyte) ch, g);
	currinf.fontp->timestamp = ++current_timestamp;
    }

#ifdef TEXXET
    dvi_h_sav = DVI_H;
    if (currinf.dir < 0)
	DVI_H -= g->dvi_adv;

    if (scan_frame == NULL) {
#endif
	
#ifdef RGB_ANTI_ALIASING
	if (currwin.shrinkfactor == -1) {
	    put_bitmap(&g->bitmap, PXL_H - g->x, PXL_V - g->y);
	}
#ifdef __GNUC__
#warning TODO: implement horizontal AA at shrink 1
#endif
#else
	if (currwin.shrinkfactor == 1) {
	    put_bitmap(&g->bitmap, PXL_H - g->x, PXL_V - g->y);
	}
#endif
	else {
#ifdef	GREY
	    if (resource.use_grey) {
		if (g->pixmap2 == NULL) {
#ifdef DBG_AA
		    fprintf(stderr, "shinking the bitmap!\n");
#endif /* DBG_AA */
/*  		    print_bitmap(&g->bitmap); */
		    shrink_glyph_grey(g);
		}
		put_image(g, PXL_H - g->x2, PXL_V - g->y2);
	    }
	    else {
		if (g->bitmap2.bits == NULL) {
		    shrink_glyph(g);
		}
		put_bitmap(&g->bitmap2, PXL_H - g->x2, PXL_V - g->y2);
	    }
#else
	    if (g->bitmap2.bits == NULL) {
		shrink_glyph(g);
	    }
	    put_bitmap(&g->bitmap2, PXL_H - g->x2, PXL_V - g->y2);
#endif
	}
#ifdef TEXXET
    }
    if (cmd == PUT1 || (resource.omega && cmd == PUT2))
	DVI_H = dvi_h_sav;
    else if (currinf.dir > 0)
	DVI_H += g->dvi_adv;
    return;
#else
    return g->dvi_adv;
#endif
}

/*
 *	Routines to print characters.
 */

static setcharRetvalT
set_empty_char(
#ifdef TEXXET
	       wide_ubyte cmd,
#endif
	       wide_ubyte ch)
{
#ifdef TEXXET
    UNUSED(cmd);
#endif
    UNUSED(ch);
#ifdef TEXXET
    return;
#else
    return 0L;
#endif
}

setcharRetvalT
load_n_set_char(
#ifdef TEXXET
		wide_ubyte cmd,
#endif
		wide_ubyte ch)
{
    if (!load_font(currinf.fontp, resource.t1lib)) {	/* if not found */
	if (globals.ev.flags & EV_GE_NEWDOC) {	/* if abort */
	    longjmp(globals.ev.canit, 1);
	}

	/* FIXME: does this need to be replaced by GUI warning, or has this case already been covered? */
	XDVI_WARNING((stderr, "load_n_set_char: Character(s) will be left blank."));
	currinf.set_char_p = currinf.fontp->set_char_p = set_empty_char;

#ifdef TEXXET
	return;
#else
	return 0L;
#endif
    }
    maxchar = currinf.fontp->maxchar;
    currinf.set_char_p = currinf.fontp->set_char_p;
#ifdef TEXXET
    (*currinf.set_char_p) (cmd, ch);
    return;
#else
    return (*currinf.set_char_p) (ch);
#endif
}

setcharRetvalT
set_vf_char(
#ifdef TEXXET
	    wide_ubyte cmd,
#endif
	    wide_ubyte ch)
{
    struct macro *m;
    struct drawinf oldinfo;
    wide_ubyte oldmaxchar;
    static ubyte c;
#ifdef TEXXET
    long dvi_h_sav;
#endif

    if (ch > maxchar)
	realloc_virtual_font(currinf.fontp, ch);
    if ((m = &currinf.fontp->macro[ch])->pos == NULL) {
	if (!resource.hush_chars)
	    XDVI_WARNING((stderr, "Character %d not defined in font %s", ch, currinf.fontp->fontname));
	m->pos = m->end = &c;
#ifdef TEXXET
	return;
#else
	return 0L;
#endif
    }
#ifdef TEXXET
    dvi_h_sav = DVI_H;
    if (currinf.dir < 0)
	DVI_H -= m->dvi_adv;
    if (scan_frame == NULL) {
#endif
	oldinfo = currinf;
	if (!currinf.virtual)
	    dvi_pointer_frame = &oldinfo;
	oldmaxchar = maxchar;
	WW = XX = YY = ZZ = 0;
	currinf.tn_table_len = VFTABLELEN;
	currinf.tn_table = currinf.fontp->vf_table;
	currinf.tn_head = currinf.fontp->vf_chain;
	currinf.pos = m->pos;
	currinf.end = m->end;
	currinf.virtual = currinf.fontp;

	draw_part(globals.dvi_file.bak_fp, current_frame, currinf.fontp->dimconv);
	if (currinf.pos != currinf.end + 1)
	    dvi_fmt_error("virtual character macro does not end correctly");
	currinf = oldinfo;
	if (!currinf.virtual)
	    dvi_pointer_frame = &currinf;
	maxchar = oldmaxchar;
#ifdef TEXXET
    }
    if (cmd == PUT1 || (resource.omega && cmd == PUT2))
	DVI_H = dvi_h_sav;
    else if (currinf.dir > 0)
	DVI_H += m->dvi_adv;
    return;
#else
    return m->dvi_adv;
#endif
}

static setcharRetvalT
set_no_char(
#ifdef TEXXET
	    wide_ubyte cmd,
#endif
	    wide_ubyte ch)
{
    if (currinf.virtual) {
	currinf.fontp = currinf.virtual->first_font;
	if (currinf.fontp != NULL) {
	    maxchar = currinf.fontp->maxchar;
	    currinf.set_char_p = currinf.fontp->set_char_p;
#ifdef TEXXET
	    (*currinf.set_char_p) (cmd, ch);
	    return;
#else
	    return (*currinf.set_char_p) (ch);
#endif
	}
    }
    dvi_fmt_error("set_no_char: attempt to set character of unknown font");
    exit(0);
    /* NOTREACHED */
}


/*
 *	Set rule.  Arguments are coordinates of lower left corner.
 */

static void
set_rule(int h, int w)
{
#ifdef TEXXET
    put_rule(PXL_H - (currinf.dir < 0 ? w - 1 : 0), PXL_V - h + 1,
	     (unsigned int)w, (unsigned int)h);
#else
    put_rule(PXL_H, PXL_V - h + 1, (unsigned int)w, (unsigned int)h);
#endif
}


/*
 *	Interpret a sequence of dvi bytes (either the page from the dvi file,
 *	or a character from a virtual font).
 */

static void
draw_part(FILE *fp, struct frame *minframe, double current_dimconv)
{
    ubyte ch = 0;
#ifdef TEXXET
    struct drawinf oldinfo;
    ubyte oldmaxchar = 0;
    off_t file_pos = 0;
    int refl_count = 0;
#endif
    int pause_cnt = 0;

    globals.pausing.flag = False;

    currinf.fontp = NULL;
    currinf.set_char_p = set_no_char;
#ifdef TEXXET
    currinf.dir = 1;
    scan_frame = NULL;	/* indicates we're not scanning */
#endif

    for (;;) {
	ch = xone(fp);
	if (globals.debug & DBG_DVI) {
	    print_dvi(ch);
	}
	if (ch <= (ubyte)(SETCHAR0 + 127)) {
#ifdef TEXXET
	    (*currinf.set_char_p) (ch, ch);
#else
	    DVI_H += (*currinf.set_char_p) (ch);
#endif
	}
	else if (FNTNUM0 <= ch && ch <= (ubyte) (FNTNUM0 + 63)) {
	    change_font((unsigned long)(ch - FNTNUM0));
	}
	else {
	    long a, b;

	    switch (ch) {
	    case SET1:
	    case PUT1:
#ifdef TEXXET
		(*currinf.set_char_p) (ch, xone(fp));
#else
		a = (*currinf.set_char_p) (xone(fp));
		if (ch != PUT1)
		    DVI_H += a;
#endif
		break;

	    case SET2:
	    case PUT2:
		if (!resource.omega)
		    dvi_fmt_error("%s:%d: draw_part: op-code %d only works with the \"-omega\" option",
				  __FILE__, __LINE__, ch);
		else {
#ifdef TEXXET
		    (*currinf.set_char_p) (ch, xnum(fp, 2));
#else
		    a = (*currinf.set_char_p) (xnum(fp, 2));
		    if (ch != PUT2)
			DVI_H += a;
#endif
		}
		break;

	    case SETRULE:
		/* Be careful, dvicopy outputs rules with
		   height = 0x80000000.  We don't want any
		   SIGFPE here. */
		a = xsfour(fp);
		b = xspell_conv(xsfour(fp));
		if (a > 0 && b > 0
#ifdef TEXXET
		    && scan_frame == NULL
#endif
		    ) {
		    set_rule(pixel_round(xspell_conv(a)), pixel_round(b));
		}
		DVI_H += DIR * b;
		break;

	    case PUTRULE:
		a = xspell_conv(xsfour(fp));
		b = xspell_conv(xsfour(fp));
		if (a > 0 && b > 0
#ifdef TEXXET
		    && scan_frame == NULL
#endif
		    ) {
		    set_rule(pixel_round(a), pixel_round(b));
		}
		break;

	    case NOP:
		break;

	    case BOP:
		xskip(fp, (long)11 * 4);
		DVI_H = OFFSET_X;
		DVI_V = OFFSET_Y;
		PXL_V = pixel_conv(DVI_V);
		WW = XX = YY = ZZ = 0;
		break;

	    case EOP:
		if (current_frame != minframe)
		    dvi_fmt_error("%s:%d: draw_part: stack not empty at EOP", __FILE__, __LINE__);
		return;

	    case PUSH:
		if (current_frame->next == NULL) {
		    struct frame *newp = xmalloc(sizeof *newp);

		    current_frame->next = newp;
		    newp->prev = current_frame;
		    newp->next = NULL;
		}
		current_frame = current_frame->next;
		current_frame->data = currinf.data;
		break;

	    case POP:
		if (current_frame == minframe)
		    dvi_fmt_error("%s:%d: draw_part: more POPs than PUSHes", __FILE__, __LINE__);
		currinf.data = current_frame->data;
		current_frame = current_frame->prev;
		break;

#ifdef TEXXET
	    case SREFL:
		if (scan_frame == NULL) {
		    /* we're not scanning:  save some info. */
		    oldinfo = currinf;
		    oldmaxchar = maxchar;
		    if (!currinf.virtual)
			file_pos = xtell(fp, currinf.pos);
		    scan_frame = current_frame;	/* now we're scanning */
		    refl_count = 0;
		    break;
		}
		/* we are scanning */
		if (current_frame == scan_frame)
		    ++refl_count;
		break;

	    case EREFL:
		if (scan_frame != NULL) {	/* if we're scanning */
		    if (current_frame == scan_frame && --refl_count < 0) {
			/* we've hit the end of our scan */
			scan_frame = NULL;
			/* first:  push */
			if (current_frame->next == NULL) {
			    struct frame *newp = xmalloc(sizeof *newp);

			    current_frame->next = newp;
			    newp->prev = current_frame;
			    newp->next = NULL;
			}
			current_frame = current_frame->next;
			current_frame->data = currinf.data;
			/* next:  restore old file position, XX, etc. */
			if (!currinf.virtual) {
			    off_t bgn_pos = xtell(fp, G_dvi_buf_ptr);

			    if (file_pos >= bgn_pos) {
				oldinfo.pos = dvi_buffer + (file_pos - bgn_pos);
				oldinfo.end = currinf.end;
			    }
			    else {
				(void)lseek(fileno(fp), file_pos, SEEK_SET);
				oldinfo.pos = oldinfo.end;
			    }
			}
			currinf = oldinfo;
			maxchar = oldmaxchar;
			/* and then:  recover position info. */
			DVI_H = current_frame->data.dvi_h;
			DVI_V = current_frame->data.dvi_v;
			PXL_V = current_frame->data.pxl_v;
			/* and finally, reverse direction */
			currinf.dir = -currinf.dir;
		    }
		    break;
		}
		/* we're not scanning, */
		/* so just reverse direction and then pop */
		currinf.dir = -currinf.dir;
		currinf.data = current_frame->data;
		current_frame = current_frame->prev;
		break;
#endif /* TEXXET */

	    case RIGHT1:
	    case RIGHT2:
	    case RIGHT3:
	    case RIGHT4:
		DVI_H += DIR * xspell_conv(xsnum(fp, ch - RIGHT1 + 1));
		break;

	    case W1:
	    case W2:
	    case W3:
	    case W4:
		WW = xspell_conv(xsnum(fp, ch - W0));
	    case W0:
		DVI_H += DIR * WW;
		break;

	    case X1:
	    case X2:
	    case X3:
	    case X4:
		XX = xspell_conv(xsnum(fp, ch - X0));
	    case X0:
		DVI_H += DIR * XX;
		break;

	    case DOWN1:
	    case DOWN2:
	    case DOWN3:
	    case DOWN4:
		DVI_V += xspell_conv(xsnum(fp, ch - DOWN1 + 1));
		PXL_V = pixel_conv(DVI_V);
		break;

	    case Y1:
	    case Y2:
	    case Y3:
	    case Y4:
		YY = xspell_conv(xsnum(fp, ch - Y0));
	    case Y0:
		DVI_V += YY;
		PXL_V = pixel_conv(DVI_V);
		break;

	    case Z1:
	    case Z2:
	    case Z3:
	    case Z4:
		ZZ = xspell_conv(xsnum(fp, ch - Z0));
	    case Z0:
		DVI_V += ZZ;
		PXL_V = pixel_conv(DVI_V);
		break;

	    case FNT1:
	    case FNT2:
	    case FNT3:
	    case FNT4:
		change_font(xnum(fp, ch - FNT1 + 1));
		break;

	    case XXX1:
	    case XXX2:
	    case XXX3:
	    case XXX4:
		a = xnum(fp, ch - XXX1 + 1);
		if (a > 0) {
		    char *p = read_special(fp, a);
		    if (resource.pause && strcmp(p, resource.pause_special) == 0) {
			if (++pause_cnt > globals.pausing.num) {
			    globals.pausing.flag = True;
			    /* can't use longjmp(globals.ev.canit, 1); */
			    return;
			}
		    }
		    applicationDoSpecial(p, a);
		}
		break;

	    case FNTDEF1:
	    case FNTDEF2:
	    case FNTDEF3:
	    case FNTDEF4:
		xskip(fp, (long)(12 + ch - FNTDEF1 + 1));
		a = (long)xone(fp);
		xskip(fp, a + (long)xone(fp));
		break;

#ifndef TEXXET
	    case SREFL:
	    case EREFL:
#endif
	    case PRE:
	    case POST:
	    case POSTPOST:
		dvi_fmt_error("%s:%d: draw_part: shouldn't happen: %s encountered",
			      __FILE__, __LINE__, dvi_table2[ch - (FNTNUM0 + 64)]);
		break;

	    default:
		dvi_fmt_error("%s:%d: draw_part: unknown op-code %d", __FILE__, __LINE__, ch);
	    }	/* end switch */
	}	/* end else (ch not a SETCHAR or FNTNUM) */
    }	/* end for */
}

extern int waiting_for_anchor;

static void
warn_raw_postscript(void)
{
    static int *pagelist = NULL;
    static int pagelist_size = 0;
#ifdef PS
    if (!resource.hush_stdout) {
	if (total_pages >= pagelist_size) {
	    pagelist_size = total_pages + 1;
	    pagelist = xrealloc(pagelist, pagelist_size * sizeof *pagelist);
	    memset(pagelist, 0, pagelist_size * sizeof *pagelist);
	}
	
	ASSERT(pagelist_size > current_page, "pagelist_size too small");
	if (pagelist[current_page] == 0) {
	    XDVI_WARNING((stderr, "Raw Postscript commands on page %d may be rendered incorrectly.",
			  current_page + 1));
	    pagelist[current_page] = 1;
	}
	
	/* too likely to overdraw important information */
	/* 	statusline_print(STATUS_MEDIUM, */
	/* 			 "Warning: Postscript commands on this page may not display correctly."); */
    }
#endif /* PS */
}

void
draw_page(void)
{
#if 0
    volatile double save_gamma = 0.0;
#endif /* 0 */
    /* Check for changes in dvi file. */
    if (dvi_file_changed()) {
	return;
    }

    if (globals.dvi_file.bak_fp == NULL) {
	return;
    }

#ifdef PS
    have_raw_postscript = False;
#endif
    
#if COLOR
    color_bottom = &fg_initial;
    color_bot_size = 1;

    if (page_colors.stack != NULL && current_page > 0) {
	color_bottom = page_colors.stack[current_page - 1].colorstack;
	color_bot_size = page_colors.stack[current_page - 1].stacksize;
    }
    rcs_top = NULL;
    ASSERT(color_bot_size > 0, "color_bot_size mustn't become negative!");
    ASSERT(color_bottom != NULL, "color_bottom mustn't become negative!");
    set_fg_color(&color_bottom[color_bot_size - 1]);
#endif /* COLOR */

#if !FIXED_FLUSHING_PAGING    
    draw_border(-currwin.base_x, -currwin.base_y,
		ROUNDUP(pageinfo_get_page_width(current_page), currwin.shrinkfactor) + 2,
		ROUNDUP(pageinfo_get_page_height(current_page), currwin.shrinkfactor) + 2, globals.gc.high);
#endif /* MOTIF */

    (void) lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(current_page), SEEK_SET);

    memset((char *)&currinf.data, '\0', sizeof currinf.data);
    currinf.tn_table_len = TNTABLELEN;
    currinf.tn_table = tn_table;
    currinf.tn_head = tn_head;
    currinf.pos = currinf.end = dvi_buffer;
    currinf.virtual = NULL;
    dvi_pointer_frame = &currinf;
    drawing_mag = (currwin.win == magnifier.win);
    psfig_begun = False;

    htex_initpage(False, False, current_page);

    if (currwin.win == mane.win) {
	XRectangle rect;
	rect.x = globals.win_expose.min_x;
	rect.y = globals.win_expose.min_y;
	rect.width = globals.win_expose.max_x - globals.win_expose.min_x;
	rect.height = globals.win_expose.max_y - globals.win_expose.min_y;
/*  	fprintf(stderr, "clip: %d, %d, %d, %d\n", */
/* 		globals.win_expose.min_x, */
/* 		globals.win_expose.min_y, */
/* 		globals.win_expose.max_x - globals.win_expose.min_x, */
/* 		globals.win_expose.max_y - globals.win_expose.min_y); */
#define SET_CLIP(gc)	if (gc != NULL) XSetClipRectangles(DISP, gc, 0, 0, &rect, 1, Unsorted)
#define CLEAR_CLIP(gc)  if (gc != NULL) XSetClipMask(DISP, gc, None)
	/* Set clip masks for all GC's */
	SET_CLIP(globals.gc.fore);
	SET_CLIP(globals.gc.fore2);
	SET_CLIP(globals.gc.fore2_bak);
	SET_CLIP(globals.gc.fore2_bak1);
	SET_CLIP(globals.gc.rule);
	SET_CLIP(globals.gc.high);
	SET_CLIP(globals.gc.linkcolor);
	SET_CLIP(globals.gc.copy);
    }
    
    if (!setjmp(globals.ev.canit)) {
	/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! BUG ALERT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

	   ALL GLYPH DRAWING/RULE SETTING COMMANDS THAT MIGHT INVOKE
	   longjmp(globals.ev.canit)
	   MUST GO INSIDE THIS IF CASE, AND MUST NOT BE INVOKED FROM
	   SOMEWHERE ELSE!
	   
	   Failure to check whether a command could (indirectly) invoke
	   such a drawing routine (like e.g. put_rule()) will result
	   in *really* strange bugs (see e.g. #616920, and probably also #471021).
	   
	   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! BUG ALERT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	*/
	/* generate an expose event */
	if (search_have_match(current_page)) {
	    search_erase_highlighting(False);
	}
	if (resource.mouse_mode == MOUSE_TEXT_MODE) {
	    text_change_region(TEXT_SEL_ERASE, NULL);
	}
	draw_part(globals.dvi_file.bak_fp, current_frame = &frame0, dimconv);

	if (have_raw_postscript) {
	    warn_raw_postscript();
	}
    }
    else {
	/* If we were interrupted, put the expose event back, so that the
	 * region gets redrawn.  The if statement is necessary because the
	 * magnifier may have been destroyed as part of the interrupt.  */
	if (currwin.win == mane.win || currwin.win == magnifier.win) {
	    expose(currwin.win == mane.win ? &mane : &magnifier,
		   globals.win_expose.min_x - currwin.base_x, globals.win_expose.min_y - currwin.base_y,
		   globals.win_expose.max_x - globals.win_expose.min_x, globals.win_expose.max_y - globals.win_expose.min_y);
	}

#ifdef PS
	psp.interrupt();
	/* reset this flag too, just to make sure ... */
#if GS_PIXMAP_CLEARING_HACK
	had_ps_specials = False;
#endif
#endif
	globals.ev.flags &= ~EV_MAG_GONE;
#if 0
	if (search_have_match() && save_gamma != 0.0) {
	    resource.gamma = save_gamma;
	    do_color_change();
	    reset_fonts();
	}
#endif /* 0 */
    }

    drawing_mag = False;
    dvi_pointer_frame = NULL;
    if (currwin.win == mane.win) {
	if (globals.src.fwd_box_page >= 0) {
	    source_fwd_draw_box(); /* draw box showing found source line */
	}
	htex_draw_anchormarkers();
    }
#ifdef PS
    psp.endpage();
#endif
    if (currwin.win == mane.win && resource.postscript != 1) {
	display_bboxes();
    }
    if (search_have_match(current_page)) {
	/* highlight search match */
	search_draw_inverted_regions();
    }
    if (currwin.win == mane.win && resource.mouse_mode == MOUSE_TEXT_MODE) {
	/* highlight selection */
	text_change_region(TEXT_SEL_REDRAW, NULL);
    }
    if (resource.mouse_mode == MOUSE_RULER_MODE) {
	redraw_ruler();
    }

    clear_bboxes();

    if (currwin.win == mane.win) {
	CLEAR_CLIP(globals.gc.fore);
	CLEAR_CLIP(globals.gc.fore2);
	CLEAR_CLIP(globals.gc.fore2_bak);
	CLEAR_CLIP(globals.gc.fore2_bak1);
	CLEAR_CLIP(globals.gc.rule);
	CLEAR_CLIP(globals.gc.high);
	CLEAR_CLIP(globals.gc.linkcolor);
	CLEAR_CLIP(globals.gc.copy);
    }
#undef SET_CLIP
#undef CLEAR_CLIP

    currwin.win = (Window) 0;
}

/* this sets the file-scope htex_anchor_type to the type of the anchor just scanned,
   which is used by the drawing routines to determine whether current position is inside
   an anchor. Called from special.c.
 */
void
htex_do_special(const char *str, size_t len)
{
    if (INSIDE_MANE_WIN) {
	htex_inside_href = htex_scan_anchor(str, len);
    }
}

/*
 *	General dvi scanning routines.  These are used for:
 *	    o	source special lookups and
 *	    o	finding the dimensions of links (if compiling with support for
 *		hypertext specials).
 *	This routine can be a bit slower than draw_page()/draw_part(), since
 *	it is not run that often; that is why it is a separate routine in
 *	spite of much duplication.
 *
 *	Note that it does not use a separate copy of define_font().
 */

/*
 *	This set of routines can be called while draw_part() is active,
 *	so the global variables must be separate.
 */

static struct frame geom_frame0;	/* dummy head of list */

#ifdef TEXXET
static struct frame *geom_scan_frame;	/* head frame for scanning */
#endif

static struct frame *geom_current_frame;

static uint32_t
get_unicode_char(wide_ubyte ch, struct drawinf currinf, char *retbuf)
{
    /* attempt 1: if it's a Type1 font, get the Adobe character name
       from T1lib's T1_GetCharName */
    if (currinf.set_char_p == set_t1_char) {
	int id = currinf.fontp->t1id;
	int t1libid = fontmaps[id].t1libid;
	char *char_name;
	ASSERT(t1libid != -1, "Font must have been loaded at this point!");
	char_name = T1_GetCharName(t1libid, ch);
	TRACE_FIND_VERBOSE((stderr, "T1 char: %d = `%s' (id: %d; font: %s; enc: %d)",
			    ch, char_name,
			    currinf.fontp->t1id, fontmaps[currinf.fontp->t1id].texname,
			    fontmaps[currinf.fontp->t1id].enc));
	return adobe2unicode_name(char_name);
    }
    else {
	/* attempt 2: try to derive the encoding from the font name, by
	   looking it up in a list of known font names.
	*/
	return guess_encoding(ch, currinf.fontp->fontname, retbuf);
    }
}

#define MAX_CHARS 16 /* maximum number of unicode characters that one do_char can produce (rounded up ...) */
static const size_t ALLOC_STEP = 1024;

/* create a page break at BOP, unless we're at the start of the file */
static void
do_newpage(struct scan_info *info)
{
    struct word_info *w_info = (struct word_info *)info->data;
    if (w_info->curr_buf_idx > 0) { /* not at start of file */
	/* resize buffer if needed */
	while (w_info->curr_buf_idx + MAX_CHARS >= w_info->txt_buf_size) {
	    w_info->txt_buf_size += ALLOC_STEP;
	    w_info->txt_buf = xrealloc(w_info->txt_buf, w_info->txt_buf_size);
	}
	w_info->txt_buf[w_info->curr_buf_idx++] = '\n';
    }
}

static void
reset_bboxes(struct word_info *info)
{
    size_t i;
    for (i = 0; i < info->bboxes_size; i++) {
	info->bboxes[i].ulx = INT_MAX;
	info->bboxes[i].uly = INT_MAX;
	info->bboxes[i].lrx = 0;
	info->bboxes[i].lry = 0;
    }
}

static void
delete_last_bbox(struct word_info *info)
{
    info->bboxes[info->bboxes_idx].ulx = INT_MAX;
    info->bboxes[info->bboxes_idx].uly = INT_MAX;
    info->bboxes[info->bboxes_idx].lrx = 0;
    info->bboxes[info->bboxes_idx].lry = 0;
}

static void
finish_bbox(struct word_info *info)
{
/*     if (info->bboxes_idx == 0) /\* nothing to do *\/ */
/* 	return; */
    
    info->bboxes_idx++;
    while (info->bboxes_idx + 1 > info->bboxes_size) {
	/* re-allocate info */
	info->bboxes_size += 32;
	info->bboxes = xrealloc(info->bboxes, info->bboxes_size * sizeof *(info->bboxes));
    }
    info->bboxes[info->bboxes_idx].ulx = INT_MAX;
    info->bboxes[info->bboxes_idx].uly = INT_MAX;
    info->bboxes[info->bboxes_idx].lrx = 0;
    info->bboxes[info->bboxes_idx].lry = 0;

#if 0
    fprintf(stderr, "========= finish_bbox: index=%d, boxes:\n", info->bboxes_idx);
    {
	int i;
	for (i = 0; i < info->bboxes_idx; i++) {
	    fprintf(stderr, "%d: x %d, y %d, w %d, h %d\n",
		    i, info->bboxes[i].ulx, info->bboxes[i].uly, info->bboxes[i].lrx, info->bboxes[i].lry);
	}
    }
#endif /* 0 */
}

static void
create_bbox(struct word_info *info, int x, int y, int w, int h)
{
#if 0
    fprintf(stderr, "++++++++++++++ inside match: %d,%d,%d,%d!\n", x,y,w,h);
    XDrawRectangle(DISP, mane.win, globals.gc.high,
		   x / (double)currwin.shrinkfactor + 0.5,
		   y / (double)currwin.shrinkfactor + 0.5,
		   w / (double)currwin.shrinkfactor + 0.5,
		   h / (double)currwin.shrinkfactor + 0.5);
#endif
    while (info->bboxes_idx + 1 > info->bboxes_size) {
	/* re-allocate info */
	size_t old_size = info->bboxes_size;
	size_t i;
	info->bboxes_size += 32;
	info->bboxes = xrealloc(info->bboxes, info->bboxes_size * sizeof *(info->bboxes));
	for (i = old_size; i < info->bboxes_size; i++) {
	    info->bboxes[i].ulx = INT_MAX;
	    info->bboxes[i].uly = INT_MAX;
	    info->bboxes[i].lrx = 0;
	    info->bboxes[i].lry = 0;
	}
	
    }
    /* adjust size of box */
    if (x < info->bboxes[info->bboxes_idx].ulx)
	info->bboxes[info->bboxes_idx].ulx = x;
    if (y < info->bboxes[info->bboxes_idx].uly)
	info->bboxes[info->bboxes_idx].uly = y;
    if (x + w > info->bboxes[info->bboxes_idx].lrx)
	info->bboxes[info->bboxes_idx].lrx = x + w;
    if (y + h > info->bboxes[info->bboxes_idx].lry)
	info->bboxes[info->bboxes_idx].lry = y + h;
#if 0
    fprintf(stderr, "dimens: %d, %d, %d, %d\n",
	    info->bboxes[info->bboxes_idx].ulx,
	    info->bboxes[info->bboxes_idx].uly,
	    info->bboxes[info->bboxes_idx].lrx,
	    info->bboxes[info->bboxes_idx].lry);
    fprintf(stderr, "============ create_bbox: index=%d, boxes:\n", info->bboxes_idx);
    {
	int i;
	for (i = 0; i <= info->bboxes_idx; i++) {
	    fprintf(stderr, "%d: x %d, y %d, w %d, h %d\n",
		    i, info->bboxes[i].ulx, info->bboxes[i].uly, info->bboxes[i].lrx, info->bboxes[i].lry);
	}
    }
#endif /* 0 */
}

static void
map_index_positions(const struct search_info *searchinfo,
		    const struct page_mapping *page_mapping,
		    int *from, int *to)
{
    *from = searchinfo->from_pos;
    *to = searchinfo->to_pos;

    if (*from >= page_mapping[0].offset) { /* on second page of scan */
#if 0
	fprintf(stderr, "current_page: %d, from: %d, to: %d; subtracting %d for pageinfo at index 0\n",
		current_page, *from, *to, page_mapping[0].offset);
#endif /* 0 */
	if (page_mapping[0].offset != -1) {
	    ASSERT(page_mapping[0].offset != -1, "page_mapping not properly initialized?");
	    (*from) -= page_mapping[0].offset;
	    (*to) -= page_mapping[0].offset;
	}
	ASSERT(*from >= 0, "index must be > 0");
	ASSERT(*to >= 0, "index must be > 0");
	
    }
}

/* decrement curr_buf_idx so that the previous character in info
 * (which might be a multi-byte character) is erased.
 */
static void
erase_prev_char(struct word_info *info)
{
    /* erase the hyphen, which might be a multibyte character */
    if ((unsigned char)(info->txt_buf[info->curr_buf_idx - 1]) < 0x80) { /* single-byte */
	TRACE_FIND_VERBOSE((stderr, "1 XXX: %d",
			    (unsigned char)(info->txt_buf[info->curr_buf_idx - 1])));
	info->txt_buf[--(info->curr_buf_idx)] = '\0';
    }
    else { /* skip back over multi-byte sequence */
	while ((unsigned char)(info->txt_buf[info->curr_buf_idx - 1]) >= 0x80
	       && (unsigned char)(info->txt_buf[info->curr_buf_idx - 1]) < 0xC0) { /* 10xxxxxx bytes */
	    TRACE_FIND_VERBOSE((stderr, "2 XXX: %d",
				(unsigned char)(info->txt_buf[info->curr_buf_idx - 1])));
	    info->txt_buf[--(info->curr_buf_idx)] = '\0';
	}
	/* first byte (TODO: sanity check?) */
	info->txt_buf[--(info->curr_buf_idx)] = '\0';
    }
}


static Boolean
inside_text_match(int curr_pos, int from, int to)
{
    return from < curr_pos && curr_pos <= to;
}

static Boolean reinit_scan = False;

void
reinit_text_scan(void) {
    reinit_scan = True;
}

static Boolean
inside_bbox(int x, int y, int w, int h,
	    struct bbox *bboxes)
{
    int mid_x, mid_y;
#if 0
    XDrawRectangle(DISP, mane.win, globals.gc.high,
		   x / (double)currwin.shrinkfactor + 0.5,
		   y / (double)currwin.shrinkfactor + 0.5,
		   w / (double)currwin.shrinkfactor + 0.5,
		   h / (double)currwin.shrinkfactor + 0.5);
#endif
    /* already treat it as inside if there's an overlap of more than 50% */
    mid_x = x + w / 2.0;
    mid_y = y + h / 2.0;
#if 0
    fprintf(stderr, "------- check:\n%d, %d\n%d, %d, %d, %d\n",
	    mid_x, mid_y, bboxes->ulx, bboxes->uly, bboxes->lrx, bboxes->lry);
#endif
    if (bboxes->ulx <= mid_x &&
	bboxes->uly <= mid_y &&
	bboxes->lrx >= mid_x &&
	bboxes->lry >= mid_y) {
#if 0
	fprintf(stderr, "====== MATCH:\n%d, %d\n%d, %d, %d, %d\n",
		mid_x, mid_y, bboxes->ulx, bboxes->uly, bboxes->lrx, bboxes->lry);
#endif
	return True;
    }
    return False;
}

static Boolean
inside_bbox_line(int y, int h,
		 struct bbox *bboxes)
{
    int mid_y = y + h / 2.0;
    if (bboxes->uly <= mid_y &&
	bboxes->lry >= mid_y) {
#if 0
	fprintf(stderr, "MATCH:\n%d, %d, %d, %d\n%d, %d, %d, %d\n",
		x, y, w, h, bboxes->ulx, bboxes->uly, bboxes->lrx, bboxes->lry);
#endif
	return True;
    }
    return False;
}

static void
do_char(wide_ubyte ch,
	struct drawinf currinf,
	struct scan_info *info,
	long pxl_v1, long pxl_v2,
	long x1, long x2,
	struct glyph *g)
{
    struct word_info *w_info = (struct word_info *)info->data;
    struct search_settings *settings = NULL;
    const struct page_mapping *page_mapping = NULL;
    uint32_t u_glyph = 0, new_glyph = 0;
    float fsize = dvi_pt_conv(currinf.fontp->scale); /* font size in pt */
    Boolean convert_to_lowercase = False;
    /* if set to true, ignore whitespace before/after character (for CJK characters) */
    Boolean ignore_whitespace = False;
    
    /* default min space between words, in DVI units */
    /* TODO: for T1 fonts, should we look at fontdimen2 / fontdimen4?
       (e.g. tfminfo[fontmaps[currinf.fontp->t1id].tfmidx].fontdimen2)
     */
    long min_delta = (int)(1.5 * fsize + 0.5) << 16;

    size_t buf_offset = w_info->buffer_offset;

    size_t i;
    /* for delayed insertion of newlines in text selection mode */
    static Boolean had_newline = False;
    /* for whitespace insertion in text selection mode */
    static Boolean had_chars_in_line = False;
    static long last_dvi_h1 = 0, last_pxl_v = 0;
    static long last_dvi_h2 = 0;
    static long last_x = 0;
    static uint32_t last_u_glyph = 0;
    static int last_page = -1;
    static int page_bak = -1;
    const char *expanded_lig = NULL;
    const uint32_t UNKNOWN_GLYPH_CODE = 0x003F; /* question mark */
    char retbuf[MAX_CHARS];
    retbuf[0] = '\0';
    
    if (w_info != NULL && w_info->settings != NULL) {
	settings = w_info->settings;
	if (!settings->case_sensitive)
	    convert_to_lowercase = True;
    }
    
    if (w_info->bbox_pass) {
	ASSERT(w_info != NULL, "");
	ASSERT(w_info->page_mapping != NULL, "");
	page_mapping = w_info->page_mapping;
    }
    
    if (reinit_scan || last_page != current_page) { /* reinit */
	last_dvi_h1 = last_pxl_v = last_dvi_h2 = last_x = 0;
	last_page = current_page;
	last_u_glyph = 0;
	w_info->bboxes_idx = 0;
	reset_bboxes(w_info);
/* 	w_info->curr_buf_idx  = 0; */
	reinit_scan = False;
	had_newline = False;
	had_chars_in_line = False;
    }
    
/*      TRACE_FIND((stderr, "\n--------- POSITIONS: %ld, %ld, %ld, %ld", x1, pxl_v2, x2, y2)); */

    if ((u_glyph = get_unicode_char(ch, currinf, retbuf)) == 0) {
	if (retbuf[0] == '\0') {
	    TRACE_FIND((stderr, "unknown glyph `%lu'\n", (unsigned long)ch));
	    u_glyph = UNKNOWN_GLYPH_CODE;
	}
	else /* several characters in retbuf, will be evaluated later */
	    u_glyph = 0;
    }

    TRACE_FIND_VERBOSE((stderr, "UNICODE: 0x%.4X; idx: %ld",
			(unsigned int)u_glyph,
			(unsigned long)w_info->curr_buf_idx));
    
    /* resize buffer if needed */
    while (w_info->curr_buf_idx + MAX_CHARS >= w_info->txt_buf_size) {
	w_info->txt_buf_size += ALLOC_STEP;
	w_info->txt_buf = xrealloc(w_info->txt_buf, w_info->txt_buf_size);
    }

    /* Copy text into buffer, applying heuristics for special glyphs.
       A `\0' is always appended, so that we have a valid C string;
       if we have more text, this '\0' will be overwritten in the next call. */

    /* apply accent/linebreak heuristics */
/*     fprintf(stderr, "Checking: %ld > 0, %ld > 0\n", last_dvi_h1, last_pxl_v); */
    if (last_dvi_h1 > 0 && last_pxl_v > 0) { /* had at least 1 character */
	TRACE_FIND_VERBOSE((stderr, "++++ dvi_h: %ld, last_dvi_h1: %ld, w_info->curr_buf_idx: %ld",
			    DVI_H, last_dvi_h1, (unsigned long)w_info->curr_buf_idx));
	
	/* spaces after/before ideographic characters are ignored */
	if (is_ideograph(last_u_glyph) || is_ideograph(u_glyph))
	    ignore_whitespace = true;
	
	/* first, check for linebreaks since accents are also triggered by negative hspace.
	 * Usually, a linebreak is signalled by vertical movement down. However, in multicolumn
	 * mode, it can also be a movement up, which we try to catch with the second condition. */
	if (pxl_v2 > last_pxl_v + (int)(1.2 * fsize + 0.5)
	    || (page_bak == current_page && pxl_v2 + (int)(6 * fsize + 0.5) < last_pxl_v)) {
	    TRACE_FIND_VERBOSE((stderr, "linebreak (%ld > %ld + %d || %ld < %ld)!\n",
				pxl_v2, last_pxl_v, (int)(1.2 * fsize + 0.5),
				pxl_v2 + (int)(6 * fsize + 0.5), last_pxl_v));
	    
	    /* remove hyphen followed by newline if ignore_hyphens option is set,
	       and we're scanning for string search: */
	    if ((w_info->search_scan_pass || w_info->bbox_pass)
		&& settings->ignore_hyphens && w_info->curr_buf_idx > 0
		&& is_hyphenchar(last_u_glyph)) {
		
		erase_prev_char(w_info);
		TRACE_FIND_VERBOSE((stderr, "%d > %d + %d? offset: %lu",
				    settings->searchinfo->from_pos,
				    (int)w_info->curr_buf_idx,
				    settings->hyphen_delta, (unsigned long)buf_offset));
		if (!w_info->bbox_pass
		    && settings->searchinfo->from_pos
		    > (int)(w_info->curr_buf_idx + buf_offset + settings->hyphen_delta)) {
		    settings->hyphen_delta += 2;
		}
		TRACE_FIND((stderr, "erasing hyphen %lu at pos %lu with char %c; hyphen_delta is: %d",
			    (unsigned long)last_u_glyph, (unsigned long)w_info->curr_buf_idx, ch, settings->hyphen_delta));
		/* If the position of this hyphen had been at the start of the bounding
		   box, this bounding box info is now invalid - delete it */
		if (w_info->bbox_pass) {
		    int from, to;
		    map_index_positions(settings->searchinfo, page_mapping, &from, &to);
		    if ((int)w_info->curr_buf_idx == from) {
			delete_last_bbox(w_info);
		    }
		}
	    }
	    else { /* ignore_hyphens not set, insert newline */
		/* Also save hyphen_delta, in case we need to update from_pos and to_pos
		   if user switched from ignore hyphens to don't ignore hyphens */
		if (w_info->search_scan_pass && !w_info->bbox_pass
		    && is_hyphenchar(last_u_glyph)
		    && settings->searchinfo->from_pos + settings->hyphen_delta
		    >= (int)(w_info->curr_buf_idx + buf_offset)) {
		    settings->hyphen_delta += 2;
		    TRACE_FIND((stderr, "updating delta: %d at pos %d, curr_idx %d, offset: %lu",
				settings->hyphen_delta, settings->searchinfo->from_pos,
				(int)w_info->curr_buf_idx, (unsigned long)buf_offset));
		}
		if (w_info->text_selection_pass) {
		    if (inside_bbox(last_x, last_pxl_v, 5, 5, w_info->bboxes)
			|| (inside_bbox_line(last_pxl_v, 5, w_info->bboxes))) {
			had_newline = True;
			had_chars_in_line = False;
		    }
		}
		else if (w_info->search_scan_pass && settings->ignore_linebreaks && !ignore_whitespace)
		    w_info->txt_buf[w_info->curr_buf_idx++] = ' ';
		else if (!ignore_whitespace)
		    w_info->txt_buf[w_info->curr_buf_idx++] = '\n';
	    }
	    if (w_info->bbox_pass) {
		int from, to;
		map_index_positions(settings->searchinfo, page_mapping, &from, &to);
		if (inside_text_match((int)w_info->curr_buf_idx, from, to)) {
		    finish_bbox(w_info);
		}
	    }
	}
	else if (w_info->curr_buf_idx > 0 && last_u_glyph != 0 && last_x > 0
		 && (!w_info->text_selection_pass
		     || (w_info->text_selection_pass
			 && inside_bbox(last_x, pxl_v2, x1 - last_x, pxl_v1 - pxl_v2, w_info->bboxes)))
		 && x1 < last_x /* overlapping glyphs: check for diacritics */
		 && ((new_glyph = get_accented_glyph(last_u_glyph, u_glyph)) != 0)) {
	    erase_prev_char(w_info);
	    /* use new glyph for next writing operation: */
	    u_glyph = new_glyph;
	}
	else if (!ignore_whitespace && last_dvi_h2 > 0 && DVI_H > last_dvi_h2 + min_delta) {
	    TRACE_FIND_VERBOSE((stderr, "space (%ld > %ld + %ld)!", DVI_H, last_dvi_h2, min_delta));
	    if (!w_info->text_selection_pass
		|| (w_info->text_selection_pass
		    && had_chars_in_line
		    && inside_bbox(x1, pxl_v2, x2 - x1, g->bitmap.h, w_info->bboxes))) {
		w_info->txt_buf[w_info->curr_buf_idx++] = ' ';
		w_info->txt_buf[w_info->curr_buf_idx] = '\0';
		if (w_info->bbox_pass) {
		    int from, to;
		    map_index_positions(settings->searchinfo, page_mapping, &from, &to);
		    if (inside_text_match((int)w_info->curr_buf_idx, from, to)) {
			TRACE_FIND_VERBOSE((stderr, "bounding box for space: %ld, %ld, %ld, %ld",
					    last_x, last_pxl_v, x1 - last_x, pxl_v1 - pxl_v2));
			create_bbox(w_info, last_x, pxl_v2, x1 - last_x, pxl_v1 - pxl_v2);
		    }
		}
	    }
	}
    }

    last_page = current_page;
    if (retbuf[0] != '\0'
	|| (expanded_lig = expand_ligature(u_glyph)) != NULL
	/* when in search pass, normalize more characters */
	|| ((w_info->bbox_pass || w_info->search_scan_pass)
	    && (expanded_lig = search_normalize_chars(u_glyph)) != NULL)) {
	/* expanded ligature, which is always in 7-bit ASCII -> copy into buffer */
	size_t len;
	if (retbuf[0] != '\0')
	    expanded_lig = retbuf;
	len = strlen(expanded_lig);
	TRACE_FIND_VERBOSE((stderr, "I: %lu to %lu",
			    (unsigned long)w_info->curr_buf_idx,
			    (unsigned long)(w_info->curr_buf_idx + len)));

	if (!w_info->text_selection_pass
	    || (w_info->text_selection_pass && inside_bbox(x1, pxl_v2, x2 - x1, g->bitmap.h,
							   w_info->bboxes))) {

/* 	    fprintf(stderr, "inserting1 %s\n", expanded_lig); */
	    if (had_newline) {
		w_info->txt_buf[w_info->curr_buf_idx++] = '\n';
		had_newline = False;
	    }
	    memcpy(w_info->txt_buf + w_info->curr_buf_idx, expanded_lig, len);
	    had_chars_in_line = True;
	    for (i = 0; i < len; i++) {
		w_info->curr_buf_idx++;
/* 		fprintf(stderr, "setting index to: %d\n", w_info->curr_buf_idx); */
		if (w_info->bbox_pass) {
		    int from, to;
		    map_index_positions(settings->searchinfo, page_mapping, &from, &to);
		    if (inside_text_match((int)w_info->curr_buf_idx, from, to)) {
			create_bbox(w_info, x1, pxl_v2, x2 - x1, g->bitmap.h);
		    }
		}
	    }
	    w_info->txt_buf[w_info->curr_buf_idx] = '\0';
	}
    }
    else if (!w_info->text_selection_pass
	  || (w_info->text_selection_pass && inside_bbox(x1, pxl_v2, x2 - x1, g->bitmap.h, w_info->bboxes))) {
	/* convert to utf8 */
	char utf8_buf[MAX_CHARS]; /* ample ... */
	size_t len;
	/* convert to utf8, eventually lowercasing */
	ucs4_to_utf8(u_glyph, utf8_buf, &len, convert_to_lowercase);
/* 	fprintf(stderr, "inserting2 %lu\n", u_glyph); */
	if (had_newline) {
	    w_info->txt_buf[w_info->curr_buf_idx++] = '\n';
	    had_newline = False;
	}
	memcpy(w_info->txt_buf + w_info->curr_buf_idx, utf8_buf, len);
	had_chars_in_line = True;
	for (i = 0; i < len; i++) {
	    w_info->curr_buf_idx++;
/* 	    fprintf(stderr, "setting index2 to: %d\n", w_info->curr_buf_idx); */
	    if (w_info->bbox_pass) {
		int from, to;
#if 0
		int j;
		fprintf(stderr, "current page_mapping:\n");
		for (j = 0; j <= total_pages; j++) {
		    fprintf(stderr, "%d: %d\n", j, page_mapping[j]);
		}
#endif /* 0 */
/* 		fprintf(stderr, "mapping!\n"); */
		map_index_positions(settings->searchinfo, page_mapping, &from, &to);
		if (inside_text_match((int)w_info->curr_buf_idx, from, to)) {
		    create_bbox(w_info, x1, pxl_v2, x2 - x1, g->bitmap.h);
		}
	    }
	}
	w_info->txt_buf[w_info->curr_buf_idx] = '\0'; /* ensure termination */
    }

    last_dvi_h1 = DVI_H;
    last_u_glyph = u_glyph;
    last_pxl_v = pxl_v1;
    last_dvi_h2 = DVI_H + currinf.fontp->glyph[ch].dvi_adv;
    last_x = x2;
}

/* handle a character in the text scanning routine */
long
text_do_char(FILE *fp, struct scan_info *info, wide_ubyte ch)
{
    if (currinf.set_char_p == set_no_char) {
	if (currinf.virtual == NULL
	    || (currinf.fontp = currinf.virtual->first_font) == NULL)
	    return 0;	/* error; we'll catch it later */
	maxchar = currinf.fontp->maxchar;
	currinf.set_char_p = currinf.fontp->set_char_p;
    }

    if (currinf.set_char_p == set_empty_char)
	return 0;	/* error; we'll catch it later */

    if (currinf.set_char_p == load_n_set_char) {
	if (globals.ev.flags & EV_GE_NEWDOC)	/* if abort */
	    return 0;
	if (!load_font(currinf.fontp, resource.t1lib)) {	/* if not found */
	    if (globals.ev.flags & EV_GE_NEWDOC)	/* if abort */
		return 0;

	    /* FIXME: replace by GUI warning! */
	    fputs("geom_do_char: Character(s) will be left blank.\n", stderr);
	    currinf.set_char_p = currinf.fontp->set_char_p = set_empty_char;
	    return 0;
	}
	maxchar = currinf.fontp->maxchar;
	currinf.set_char_p = currinf.fontp->set_char_p;
    }

    if (currinf.set_char_p == set_char) {
	struct glyph *g;
	long x, y;

	if (ch > maxchar)
	    return 0;	/* catch the error later */
	if ((g = &currinf.fontp->glyph[ch])->bitmap.bits == NULL) {
	    if (g->addr == 0)
		return 0;	/* catch the error later */
	    if (g->addr == -1)
		return 0;	/* previously flagged missing char */
	    open_font_file(currinf.fontp);
	    fseek(currinf.fontp->file, g->addr, SEEK_SET);
	    (*currinf.fontp->read_char) (currinf.fontp, ch);
	    if (globals.debug & DBG_BITMAP)
		print_char((ubyte) ch, g);
	    currinf.fontp->timestamp = ++current_timestamp;
	}
#ifdef TEXXET
	if (geom_scan_frame == NULL) {
	    long dvi_h_sav = DVI_H;
	    if (currinf.dir < 0)
		DVI_H -= g->dvi_adv;
#endif
	    x = G_PXL_H - g->x;
	    y = PXL_V - g->y;
	    do_char(ch, currinf, info, PXL_V, y, x, x + g->bitmap.w - 1, g);
#ifdef TEXXET
	    DVI_H = dvi_h_sav;
	}
#endif
	return DIR * g->dvi_adv;
    }
    else if (currinf.set_char_p == set_vf_char) {
	struct macro *m;
	struct drawinf oldinfo;
	ubyte oldmaxchar;
#ifdef TEXXET
	long dvi_h_sav;
#endif

	if (ch > maxchar)
	    return 0;	/* catch the error later */
	if ((m = &currinf.fontp->macro[ch])->pos == NULL)
	    return 0;	/* catch the error later */
#ifdef TEXXET
	dvi_h_sav = DVI_H;
	if (currinf.dir < 0)
	    DVI_H -= m->dvi_adv;
	if (geom_scan_frame == NULL) {
#endif
	    oldinfo = currinf;
	    oldmaxchar = maxchar;
	    WW = XX = YY = ZZ = 0;
	    currinf.tn_table_len = VFTABLELEN;
	    currinf.tn_table = currinf.fontp->vf_table;
	    currinf.tn_head = currinf.fontp->vf_chain;
	    currinf.pos = m->pos;
	    currinf.end = m->end;
	    currinf.virtual = currinf.fontp;
	    geom_scan_part(text_do_char, fp, info, geom_current_frame, currinf.fontp->dimconv);
	    currinf = oldinfo;
	    maxchar = oldmaxchar;
#ifdef TEXXET
	    DVI_H = dvi_h_sav;
	}
#endif
	return DIR * m->dvi_adv;
    }
#ifdef T1LIB
    else if (currinf.set_char_p == set_t1_char) {
	struct glyph *g ;
	long x, y;
	t1FontLoadStatusT status;

#ifdef TEXXET
	g = get_t1_glyph(0, ch, &status, True);
	if (status == FAILURE_BLANK)
	    return 0;
	if (geom_scan_frame == NULL) {
	    long dvi_h_sav = DVI_H;
	    if (currinf.dir < 0)
		DVI_H -= g->dvi_adv;
	    x = G_PXL_H - g->x;
	    y = PXL_V - g->y;
	    do_char(ch, currinf, info, PXL_V, y, x, x + g->bitmap.w - 1, g);
	    DVI_H = dvi_h_sav;
	}
#else
	g = get_t1_glyph(ch, &status, True);
	if (status == FAILURE_BLANK)
	    return 0;
	x = G_PXL_H - g->x;
	y = PXL_V - g->y;
	do_char(ch, currinf, info, PXL_V, y, x, x + g->bitmap.w - 1, g);
#endif
	return DIR * g->dvi_adv;
    }
#endif /* T1LIB */
    else {
	XDVI_FATAL((stderr, "currinf.set_char_p not a registered routine!"));
    }
    /* NOTREACHED */
    return 0;
}

/*
 *	Handle a character in geometric scanning routine.
 */

static long
geom_do_char(FILE *fp, struct scan_info *info, wide_ubyte ch)
{
    struct geom_info *g_info = info->data;
    
    if (currinf.set_char_p == set_no_char) {
	if (currinf.virtual == NULL
	    || (currinf.fontp = currinf.virtual->first_font) == NULL)
	    return 0;	/* error; we'll catch it later */
	maxchar = currinf.fontp->maxchar;
	currinf.set_char_p = currinf.fontp->set_char_p;
    }

    if (currinf.set_char_p == set_empty_char)
	return 0;	/* error; we'll catch it later */

    if (currinf.set_char_p == load_n_set_char) {
	if (globals.ev.flags & EV_GE_NEWDOC)	/* if abort */
	    return 0;
	if (!load_font(currinf.fontp, resource.t1lib)) {	/* if not found */
	    if (globals.ev.flags & EV_GE_NEWDOC)	/* if abort */
		return 0;

	    /* FIXME: replace by GUI warning! */
	    fputs("geom_do_char: Character(s) will be left blank.\n", stderr);
	    currinf.set_char_p = currinf.fontp->set_char_p = set_empty_char;
	    return 0;
	}
	maxchar = currinf.fontp->maxchar;
	currinf.set_char_p = currinf.fontp->set_char_p;
    }

    if (currinf.set_char_p == set_char) {
	struct glyph *g;
	long x, y;

	if (ch > maxchar)
	    return 0;	/* catch the error later */
	if ((g = &currinf.fontp->glyph[ch])->bitmap.bits == NULL) {
	    if (g->addr == 0)
		return 0;	/* catch the error later */
	    if (g->addr == -1)
		return 0;	/* previously flagged missing char */
	    open_font_file(currinf.fontp);
	    fseek(currinf.fontp->file, g->addr, SEEK_SET);
	    (*currinf.fontp->read_char) (currinf.fontp, ch);
	    if (globals.debug & DBG_BITMAP)
		print_char((ubyte) ch, g);
	    currinf.fontp->timestamp = ++current_timestamp;
	}
#ifdef TEXXET
	if (geom_scan_frame == NULL) {
	    long dvi_h_sav = DVI_H;
	    if (currinf.dir < 0)
		DVI_H -= g->dvi_adv;
#endif
	    x = G_PXL_H - g->x;
	    y = PXL_V - g->y;
	    g_info->geom_box(info, x, y,
			     x + g->bitmap.w - 1, y + g->bitmap.h - 1);

#ifdef TEXXET
	    DVI_H = dvi_h_sav;
	}
#endif
	return DIR * g->dvi_adv;
    }
    else if (currinf.set_char_p == set_vf_char) {
	struct macro *m;
	struct drawinf oldinfo;
	ubyte oldmaxchar;
#ifdef TEXXET
	long dvi_h_sav;
#endif

	if (ch > maxchar)
	    return 0;	/* catch the error later */
	if ((m = &currinf.fontp->macro[ch])->pos == NULL)
	    return 0;	/* catch the error later */
#ifdef TEXXET
	dvi_h_sav = DVI_H;
	if (currinf.dir < 0)
	    DVI_H -= m->dvi_adv;
	if (geom_scan_frame == NULL) {
#endif
	    oldinfo = currinf;
	    oldmaxchar = maxchar;
	    WW = XX = YY = ZZ = 0;
	    currinf.tn_table_len = VFTABLELEN;
	    currinf.tn_table = currinf.fontp->vf_table;
	    currinf.tn_head = currinf.fontp->vf_chain;
	    currinf.pos = m->pos;
	    currinf.end = m->end;
	    currinf.virtual = currinf.fontp;
	    geom_scan_part(geom_do_char, fp, info, geom_current_frame, currinf.fontp->dimconv);
	    currinf = oldinfo;
	    maxchar = oldmaxchar;
#ifdef TEXXET
	    DVI_H = dvi_h_sav;
	}
#endif
	return DIR * m->dvi_adv;
    }
#ifdef T1LIB
    else if (currinf.set_char_p == set_t1_char) {
	struct glyph *g ;
	long x, y;
	t1FontLoadStatusT status;

#ifdef TEXXET
	g = get_t1_glyph(0, ch, &status, False);
	if (status == FAILURE_BLANK)
	    return 0;
	if (geom_scan_frame == NULL) {
	    long dvi_h_sav = DVI_H;
	    if (currinf.dir < 0)
		DVI_H -= g->dvi_adv;
	    x = G_PXL_H - g->x;
	    y = PXL_V - g->y;
	    g_info->geom_box(info, x, y,
			     x + g->bitmap.w - 1, y + g->bitmap.h - 1);
	    DVI_H = dvi_h_sav;
	}
#else
	g = get_t1_glyph(ch, &status, False);
	if (status == FAILURE_BLANK)
	    return 0;
	x = G_PXL_H - g->x;
	y = PXL_V - g->y;
	g_info->geom_box(info, x, y,
			 x + g->bitmap.w - 1, y + g->bitmap.h - 1);
#endif
	return DIR * g->dvi_adv;
    }
#endif /* T1LIB */
    else {
	XDVI_FATAL((stderr, "currinf.set_char_p not a registered routine!"));
    }
    /* NOTREACHED */
    return 0;
}

/*
 *	Do a rule in the geometry-scanning routine.
 */

static void
geom_do_rule(struct scan_info *info, long h, long w)
{
    long x, y;
#ifdef TEXXET
    long dvi_h_save = DVI_H;
#endif
    struct geom_info *g_info = info->data;
    
#ifdef TEXXET
    if (currinf.dir < 0)
	DVI_H -= w - 1;
#endif
    x = G_PXL_H;
    y = PXL_V;
    g_info->geom_box(info, x, y - xpixel_round(h) + 1,
		     x + xpixel_round(w) - 1, y);
#ifdef TEXXET
    DVI_H = dvi_h_save;
#endif
}

/*
 *	Geometric dvi scanner work routine.  This does most of the work
 *	(a) reading from a page, and (b) executing vf macros.
 */

void
geom_scan_part(long(*char_proc)(FILE *, struct scan_info *, wide_ubyte),
	       FILE *fp, struct scan_info *info, struct frame *minframe, double current_dimconv)
{
    ubyte ch;
#ifdef TEXXET
    struct drawinf oldinfo;
    ubyte oldmaxchar = 0;
    off_t file_pos = 0;
    int refl_count = 0;
#endif

    currinf.fontp = NULL;
    currinf.set_char_p = set_no_char;
#ifdef TEXXET
    currinf.dir = 1;
    geom_scan_frame = NULL;	/* indicates we're not scanning */
#endif
    for (;;) {
	ch = xone(fp);
	if (ch <= (ubyte)(SETCHAR0 + 127))
	    DVI_H += char_proc(fp, info, ch);
	else if (FNTNUM0 <= ch && ch <= (ubyte) (FNTNUM0 + 63)) {
	    change_font((unsigned long)(ch - FNTNUM0));
	}
	else {
	    long a, b;

	    switch (ch) {
	    case SET1:
	    case PUT1:
		a = char_proc(fp, info, xone(fp));
		if (ch != PUT1)
		    DVI_H += a;
		break;

	    case SET2:
	    case PUT2:
		if (!resource.omega)
		    dvi_fmt_error("%s:%d: draw_part: op-code %d only works with the \"-omega\" option",
				  __FILE__, __LINE__, ch);
		else {
#ifdef TEXXET
		    char_proc(fp, info, xnum(fp, 2));
#else
		    a = char_proc(fp, info, xnum(fp, 2));
		    if (ch != PUT2)
			DVI_H += a;
#endif
		}
		break;

	    case SETRULE:
		/* Be careful, dvicopy outputs rules with
		   height = 0x80000000.  We don't want any
		   SIGFPE here. */
		a = xsfour(fp);
		b = xspell_conv(xsfour(fp));
		if (a >= 0 && b >= 0
#ifdef TEXXET
		    && geom_scan_frame == NULL
#endif
		    ) {
		    /* is this a geom scan? */
		    if (info->geom_special != NULL)
			geom_do_rule(info, xspell_conv(a), b);
		}
		DVI_H += DIR * b;
		break;

	    case PUTRULE:
		a = xspell_conv(xsfour(fp));
		b = xspell_conv(xsfour(fp));
		if (a >= 0 && b >= 0
#ifdef TEXXET
		    && geom_scan_frame == NULL
#endif
		    ) {
		    /* is this a geom scan? */
		    if (info->geom_special != NULL)
			geom_do_rule(info, a, b);
		}
		break;

	    case NOP:
		break;

	    case BOP:
		xskip(fp, (long)11 * 4);
		DVI_H = G_OFFSET_X;
		DVI_V = G_OFFSET_Y;
		PXL_V = xpixel_conv(DVI_V);
		WW = XX = YY = ZZ = 0;
		/* create pagebreak in character scan */
		if (info->geom_special == NULL) {
		    do_newpage(info);
		}
		break;

	    case PUSH:
		if (geom_current_frame->next == NULL) {
		    struct frame *newp = xmalloc(sizeof *newp);

		    geom_current_frame->next = newp;
		    newp->prev = geom_current_frame;
		    newp->next = NULL;
		}
		geom_current_frame = geom_current_frame->next;
		geom_current_frame->data = currinf.data;
		break;

	    case POP:
		if (geom_current_frame == minframe)
		    dvi_fmt_error("more POPs than PUSHes");
		currinf.data = geom_current_frame->data;
		geom_current_frame = geom_current_frame->prev;
		break;

#ifdef TEXXET
	    case SREFL:
		if (geom_scan_frame == NULL) {
		    /* we're not scanning:  save some info. */
		    oldinfo = currinf;
		    oldmaxchar = maxchar;
		    if (!currinf.virtual)
			file_pos = xtell(fp, currinf.pos);
		    geom_scan_frame = geom_current_frame;	/* start scanning */
		    refl_count = 0;
		    break;
		}
		/* we are scanning */
		if (geom_current_frame == geom_scan_frame)
		    ++refl_count;
		break;

	    case EREFL:
		if (geom_scan_frame != NULL) {	/* if we're scanning */
		    if (geom_current_frame == geom_scan_frame
			&& --refl_count < 0) {
			/* we've hit the end of our scan */
			geom_scan_frame = NULL;
			/* first:  push */
			if (geom_current_frame->next == NULL) {
			    struct frame *newp = xmalloc(sizeof *newp);

			    geom_current_frame->next = newp;
			    newp->prev = geom_current_frame;
			    newp->next = NULL;
			}
			geom_current_frame = geom_current_frame->next;
			geom_current_frame->data = currinf.data;
			/* next:  restore old file position, XX, etc. */
			if (!currinf.virtual) {
			    off_t bgn_pos = xtell(fp, G_dvi_buf_ptr);

			    if (file_pos >= bgn_pos) {
				oldinfo.pos = dvi_buffer + (file_pos - bgn_pos);
				oldinfo.end = currinf.end;
			    }
			    else {
				(void)lseek(fileno(fp), file_pos,
					    SEEK_SET);
				oldinfo.pos = oldinfo.end;
			    }
			}
			currinf = oldinfo;
			maxchar = oldmaxchar;
			/* and then:  recover position info. */
			DVI_H = geom_current_frame->data.dvi_h;
			DVI_V = geom_current_frame->data.dvi_v;
			PXL_V = geom_current_frame->data.pxl_v;
			/* and finally, reverse direction */
			currinf.dir = -currinf.dir;
		    }
		    break;
		}
		/* we're not scanning, */
		/* so just reverse direction and then pop */
		currinf.dir = -currinf.dir;
		currinf.data = geom_current_frame->data;
		geom_current_frame = geom_current_frame->prev;
		break;
#endif /* TEXXET */

	    case RIGHT1:
	    case RIGHT2:
	    case RIGHT3:
	    case RIGHT4:
		DVI_H += DIR * xspell_conv(xsnum(fp, ch - RIGHT1 + 1));
		break;

	    case W1:
	    case W2:
	    case W3:
	    case W4:
		WW = xspell_conv(xsnum(fp, ch - W0));
	    case W0:
		DVI_H += DIR * WW;
		break;

	    case X1:
	    case X2:
	    case X3:
	    case X4:
		XX = xspell_conv(xsnum(fp, ch - X0));
	    case X0:
		DVI_H += DIR * XX;
		break;

	    case DOWN1:
	    case DOWN2:
	    case DOWN3:
	    case DOWN4:
		DVI_V += xspell_conv(xsnum(fp, ch - DOWN1 + 1));
		PXL_V = xpixel_conv(DVI_V);
		break;

	    case Y1:
	    case Y2:
	    case Y3:
	    case Y4:
		YY = xspell_conv(xsnum(fp, ch - Y0));
	    case Y0:
		DVI_V += YY;
		PXL_V = xpixel_conv(DVI_V);
		break;

	    case Z1:
	    case Z2:
	    case Z3:
	    case Z4:
		ZZ = xspell_conv(xsnum(fp, ch - Z0));
	    case Z0:
		DVI_V += ZZ;
		PXL_V = xpixel_conv(DVI_V);
		break;

	    case FNT1:
	    case FNT2:
	    case FNT3:
	    case FNT4:
		change_font(xnum(fp, ch - FNT1 + 1));
		break;

	    case XXX1:
	    case XXX2:
	    case XXX3:
	    case XXX4:
		a = xnum(fp, ch - XXX1 + 1);
		if (a > 0) {
		    char *str = read_special(fp, a);

		    /* is this a geom scan? */
		    if (info->geom_special != NULL) {
			/* process the bounding box */
			geom_do_special(info, str, current_dimconv);
			/* process the specials we're looking for */
			info->geom_special(info, str, a);
		    }
		}
		break;

	    case FNTDEF1:
	    case FNTDEF2:
	    case FNTDEF3:
	    case FNTDEF4:
		xskip(fp, (long)(12 + ch - FNTDEF1 + 1));
		a = (long)xone(fp);
		xskip(fp, a + (long)xone(fp));
		break;

#ifndef TEXXET
	    case SREFL:
	    case EREFL:
#endif
	    case PRE:
	    case POST:
	    case POSTPOST:
	    case EOP:
	    default:
		return;

	    }	/* end switch */
	}	/* end else (ch not a SETCHAR or FNTNUM) */
    }	/* end for */
}


/*
 *	Main scanning routine.
 */

void
geom_scan(long(*char_proc)(FILE *, struct scan_info *, wide_ubyte),
	  FILE *fp, struct scan_info *info, int pageno)
{
    volatile off_t pos_save = 0;
    struct drawinf currinf_save;
    ubyte maxchar_save;

#if PS
    if (info->geom_special != NULL && scanned_page < current_page) {
	fprintf(stderr, "shouldn't happen: %d >= %d!\n", scanned_page, current_page);
	return;	/* should not happen */
    }
#endif

    if (dvi_pointer_frame != NULL)
	pos_save = lseek(fileno(fp), 0L, SEEK_CUR) - (dvi_pointer_frame->end - dvi_pointer_frame->pos);

    (void) lseek(fileno(fp), pageinfo_get_offset(pageno), SEEK_SET);
    
    currinf_save = currinf;
    maxchar_save = maxchar;

    memset((char *)&currinf.data, '\0', sizeof currinf.data);
    currinf.tn_table_len = TNTABLELEN;
    currinf.tn_table = tn_table;
    currinf.tn_head = tn_head;
    currinf.pos = currinf.end = dvi_buffer;
    currinf.virtual = NULL;

    if (!setjmp(info->done_env)) {
	geom_scan_part(char_proc, fp, info, geom_current_frame = &geom_frame0, dimconv);
    }

    maxchar = maxchar_save;
    currinf = currinf_save;

    if (dvi_pointer_frame != NULL) {
	(void)lseek(fileno(fp), pos_save, SEEK_SET);
	dvi_pointer_frame->pos = dvi_pointer_frame->end = dvi_buffer;
    }
}


/*
 *	Routines for source special lookup
 */

struct src_spec_data {
    long x, y;				/* coordinates we're looking for */
    unsigned long distance;		/* best distance so far */
    Boolean recent_in_best;		/* most recent string == XXX->best */
    struct src_parsed_special best;	/* best special so far */
    struct src_parsed_special recent;	/* most recent special */
};

void
src_parse(const char *str, int str_len, struct src_parsed_special *parsed)
{
    const char *p = str;

    if (*p >= '0' && *p <= '9') {
	parsed->line = atoi(p);
	do {
	    ++p;
	    str_len--;
	}
	while (*p >= '0' && *p <= '9');
    }

    parsed->col = 0;
    if (*p == ':') {
	++p;
	str_len--;
	parsed->col = atoi(p);
	while (*p >= '0' && *p <= '9') {
	    ++p;
	    str_len--;
	}
    }

    if (*p == ' ') {
	++p;
	str_len--;
    }

    if (*p != '\0') {
	size_t len = str_len + 1;

	if (len > parsed->filename_len) {
	    if (parsed->filename_len != 0)
		free(parsed->filename);
	    parsed->filename_len = (len & -8) + 64; /* rounding? */
	    parsed->filename = xmalloc(parsed->filename_len);
	}
	memcpy(parsed->filename, p, len);
    }
}

static void
src_spec_box(struct scan_info *info, long ulx, long uly, long lrx, long lry)
{
    struct geom_info *g_info = (struct geom_info *)info->data;
    struct src_spec_data *data = g_info->geom_data;
    unsigned long distance;

    distance = 0;

    if (data->x < ulx)
	distance += (ulx - data->x) * (ulx - data->x);
    else if (data->x > lrx)
	distance += (data->x - lrx) * (data->x - lrx);

    if (data->y < uly)
	distance += (uly - data->y) * (uly - data->y);
    else if (data->y > lry)
	distance += (data->y - lry) * (data->y - lry);

    if (distance < data->distance) {
	data->distance = distance;

	/* Copy it over */
	if (!data->recent_in_best) {
	    data->best.line = data->recent.line;
	    data->best.col = data->recent.col;
	    if (data->recent.filename_len != 0) {
		if (data->best.filename_len < data->recent.filename_len) {
		    if (data->best.filename_len != 0)
			free(data->best.filename);
		    data->best.filename_len = data->recent.filename_len;
		    data->best.filename = xmalloc(data->best.filename_len);
		}
		memcpy(data->best.filename, data->recent.filename,
		       data->recent.filename_len);
	    }

	    data->recent_in_best = True;
	}

	/* Quit early if we've found our glyph.  */
	if (distance == 0 && data->best.filename_len != 0) {
	    longjmp(info->done_env, 1);
	}
    }
}

static void
src_spec_special(struct scan_info *info, const char *str, int str_len)
{
    struct geom_info *g_info = (struct geom_info *)info->data;
    struct src_spec_data *data = g_info->geom_data;

    if (memcmp(str, "src:", 4) != 0)
	return;

    src_parse(str + 4, str_len - 4, &data->recent);

    /*
     * If this is the first special on the page, we may already have
     * spotted the nearest box.
     */

    if (data->best.filename_len == 0) {
	data->best.line = data->recent.line;
	data->best.col = data->recent.col;
	if (data->recent.filename_len != 0) {
	    if (data->best.filename_len < data->recent.filename_len) {
		if (data->best.filename_len != 0)
		    free(data->best.filename);
		data->best.filename_len = data->recent.filename_len;
		data->best.filename = xmalloc(data->best.filename_len);
	    }
	    memcpy(data->best.filename, data->recent.filename,
		   data->recent.filename_len);

	    data->recent_in_best = True;
	}

	if (data->distance == 0) {
	    longjmp(info->done_env, 1);
	}
    }
    else
	data->recent_in_best = False;
}

/*
 *	Routines for reverse searches on other pages.
 */

static struct src_parsed_special found;
/* static jmp_buf scan_env; */

static Boolean
scan_first_src_spcl(char *str, int str_len, void *data)
{
    UNUSED(data);
    if (memcmp(str, "src:", 4) != 0)
	return False;

    src_parse(str + 4, str_len - 4, &found);

    return True;
}

static Boolean
scan_last_src_spcl(char *str, int str_len, void *data)
{
    UNUSED(data);
    if (memcmp(str, "src:", 4) != 0)
	return False;

    src_parse(str + 4, str_len - 4, &found);

    return True;
}


/* Replace (pseudo-)format arguments in NULL-terminated argv list as follows:
 * %f -> filename, %l -> linenumber, %c -> column number.
 * If %f or %l are not specified, they are appended as %f and +%l.
 * If colno == 0, no %c argument is provided.
 */
static char **
src_format_arguments(char **argv, const char *filename, int lineno, int colno)
{
    size_t i;
    Boolean found_filename = False;
    Boolean found_lineno = False;
    /* index of argv elem that contained a deleted column number specifier */
    int colno_deleted = -1;
    
    for (i = 0; argv[i] != NULL; i++) {
	char *ptr, *curr = argv[i];
	while ((ptr = strchr(curr, '%')) != NULL) {
	    char *p1;
	    if ((p1 = strchr("flc", ptr[1])) != NULL) { /* we have a formatting char */
		char digit_arg[LENGTH_OF_INT];
		const char *new_elem = NULL;
		/* remember offsets and lengths */
		size_t l_init = ptr - argv[i];
		size_t l_rest = strlen(ptr + 2) + 1;
		size_t l_mid;
		
		if (*p1 == 'f') {
		    found_filename = True;
		    new_elem = filename;
		}
		else if (*p1 == 'l') {
		    found_lineno = True;
		    sprintf(digit_arg, "%d", lineno);
		    new_elem = digit_arg;
		}
		else if (*p1 == 'c') {
		    if (colno == 0) { /* if we have no column information, don't provide this argument */
			memmove(ptr, ptr + 2, l_rest);
			curr = ptr;
			colno_deleted = i;
			continue;
		    }
		    sprintf(digit_arg, "%d", colno);
		    new_elem = digit_arg;
		}
		
		l_mid = strlen(new_elem);
		
		argv[i] = xrealloc(argv[i], strlen(argv[i]) + l_mid + 1);
		curr = argv[i] + l_init; /* need to reinitialize it because of realloc */
		memmove(curr + l_mid, curr + 2, l_rest);
		memcpy(curr, new_elem, l_mid);
		curr += l_mid;
	    }
	    else if (ptr[1] == '%') { /* escaped %, skip both */
		curr = ptr + 2;
	    }
	    else {
		curr = ptr + 1;
	    }
	}
    }

    /* append line number and file name arguments if they were not specified */
    if (!found_lineno) {
	i++;
	argv = xrealloc(argv, (i + 1) * sizeof *argv);
	argv[i - 1] = xmalloc(LENGTH_OF_INT + 2);
	sprintf(argv[i - 1], "+%d", lineno);
	argv[i] = NULL;
    }
    
    if (!found_filename) {
	i++;
	argv = xrealloc(argv, (i + 1) * sizeof *argv);
	argv[i - 1] = xstrdup(filename);
	argv[i] = NULL;
    }

    /* if we deleted %c, remove from argv if it's empty now */
    if (colno_deleted > -1 && strlen(argv[colno_deleted]) == 0) {
	for (i = colno_deleted; argv[i] != NULL; i++) {
	    argv[i] = argv[i + 1];
	    
	}
    }
    return argv;
}


static void
src_spawn_editor(const struct src_parsed_special *parsed)
{
    char *expanded_filename;
    char **argv;
    const char *p;
    char *q;
    int i;

    struct stat buf;

    /* first, determine the editor if necessary */
    if (resource.editor == NULL || *resource.editor == '\0') {
	p = getenv("XEDITOR");
	if (p != NULL)
	    resource.editor = xstrdup(p);
	else {

	    p = getenv("VISUAL");
	    if (p == NULL) {
		p = getenv("EDITOR");
		if (p == NULL) {
		    popup_message(globals.widgets.top_level,
				  MSG_WARN,
				  /* help text */
				  "Use the \"-editor\" command-line opion, the X resource "
				  "\"xdvi.editor\" or one of the following environment variables "
				  "to select the editor for source specials: "
				  "\"XEDITOR\", \"VISUAL\" or \"EDITOR\".\n"
				  "See the xdvi man page for more information on source specials "
				  "and the editor options.",
				  /* message */
				  "No custom editor set - using vi as default.");
		    p = "vi";
		}
	    }
	    q = xmalloc(strlen(p) + 10);
	    memcpy(q, "xterm -e ", 9);
	    strcpy(q + 9, p);
	    resource.editor = q;
	}
    }

    /* now try to open the file; find_file allocates space for expanded_filename */
    if ((expanded_filename = find_file(parsed->filename, &buf, kpse_tex_format)) == NULL) {
	popup_message(globals.widgets.top_level,
		      MSG_ERR, NULL,
		      "File \"%s\" not found, couldn't jump to special\n\"%s:%d\"\n",
		      parsed->filename, parsed->filename, parsed->line);
    }
    else {
	TRACE_SRC((stderr, "source file \"%s\" expanded to \"%s\"\n", parsed->filename, expanded_filename));
	if (buf.st_mtime > globals.dvi_file.time) {
	    statusline_print(STATUS_FOREVER,
			     "Warning: TeX file is newer than dvi file - "
			     "source special information might be wrong.");
	}

	/* this allocates argv */
	argv = src_format_arguments(get_separated_list(resource.editor, " \t", True),
				    expanded_filename, parsed->line, parsed->col);
	
	fork_process(argv[0], False, NULL, NULL, NULL, argv);

	free(expanded_filename);
	for (i = 0; argv[i] != NULL; i++)
	    free(argv[i]);	
	free(argv);
    }
}

off_t
save_file_status(FILE *fp, struct drawinf *currinf_save, ubyte *maxchar_save)
{
    off_t pos_save = 0;
    if (dvi_pointer_frame != NULL)
	pos_save = lseek(fileno(fp), 0L, SEEK_CUR)
	    - (dvi_pointer_frame->end - dvi_pointer_frame->pos);
    
    *currinf_save = currinf;
    *maxchar_save = maxchar;
    return pos_save;
}

void
restore_file_status(FILE *fp, struct drawinf currinf_save, ubyte maxchar_save, off_t pos_save)
{
    maxchar = maxchar_save;
    currinf = currinf_save;
    
    if (dvi_pointer_frame != NULL) {
	(void)lseek(fileno(fp), pos_save, SEEK_SET);
	dvi_pointer_frame->pos = dvi_pointer_frame->end = dvi_buffer;
    }
}



/*
 *	The main routine for source specials (reverse search).
 */
void
source_reverse_search(int x, int y, wide_bool call_editor)
{
    struct scan_info info;
    struct geom_info g_info;
    struct src_spec_data data;
    struct src_parsed_special *foundp;
    
    info.geom_special = src_spec_special;

    g_info.geom_box = src_spec_box;
    g_info.geom_data = &data;

    info.data = &g_info;
    
    data.x = x;
    data.y = y;
    data.distance = 0xffffffff;
    data.recent_in_best = True;
    data.best.filename_len = data.recent.filename_len = 0;
    foundp = &data.best;

    geom_scan(geom_do_char, globals.dvi_file.bak_fp, &info, current_page);

    if (data.best.filename_len == 0) {
	/*
	 * nothing found on current page;
	 * scan next and previous pages with increasing offset
	 */
	volatile int upper, lower;
	volatile off_t pos_save;
	struct drawinf currinf_save;
	ubyte maxchar_save;

	/* Save file position */
	pos_save = save_file_status(globals.dvi_file.bak_fp, &currinf_save, &maxchar_save);
	
	upper = lower = current_page;
	found.filename_len = 0;	/* mark it as empty */
	for (;;) {
	    if (++upper < total_pages) {
		(void)lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(upper), SEEK_SET);
		memset((char *)&currinf.data, '\0', sizeof currinf.data);
		currinf.tn_table_len = TNTABLELEN;
		currinf.tn_table = tn_table;
		currinf.tn_head = tn_head;
		currinf.pos = currinf.end = dvi_buffer;
		currinf.virtual = NULL;

		if (spcl_scan(scan_first_src_spcl, NULL, True, globals.dvi_file.bak_fp)) {
		    lower = upper;
		    break;
		}
	    }
	    else if (lower < 0)
		break;

	    if (--lower >= 0) {
		(void)lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(lower), SEEK_SET);

		memset((char *)&currinf.data, '\0', sizeof currinf.data);
		currinf.tn_table_len = TNTABLELEN;
		currinf.tn_table = tn_table;
		currinf.tn_head = tn_head;
		currinf.pos = currinf.end = dvi_buffer;
		currinf.virtual = NULL;

		(void)spcl_scan(scan_last_src_spcl, NULL, False, globals.dvi_file.bak_fp);
		if (found.filename_len != 0)
		    break;
	    }
	}

	if (found.filename_len != 0)
	    statusline_print(STATUS_MEDIUM,
			     "No source specials on this page - nearest on page %d",
			     lower + globals.pageno_correct);
	else {
	    /* nothing found at all; complain */
	    XBell(DISP, 0);
	    popup_message(globals.widgets.top_level,
			  MSG_ERR,
			  /* helptext */
			  reverse_search_helptext,
			  /* popup */
			  "No source specials in this DVI file - couldn't do reverse search.");
	}

	/* Restore file status.  */
	restore_file_status(globals.dvi_file.bak_fp, currinf_save, maxchar_save, pos_save);

	foundp = &found;
    }

    if (data.recent.filename_len != 0)
	free(data.recent.filename);

    if (foundp->filename_len != 0) {
	if (call_editor) {
	    src_spawn_editor(foundp);
	}
	else {
	    statusline_print(STATUS_MEDIUM, "nearest special at (%d,%d): \"%s:%d\"",
			     x / currwin.shrinkfactor, y / currwin.shrinkfactor, foundp->filename, foundp->line);
	}
	free(foundp->filename);
    }
}


/*
 *	Debug routines for source special display (highlight the first box
 *	after each source special, or highlight each box).
 */

struct src_spec_show_data {
    Boolean do_this_one;	/* flag set after source special */
    Boolean do_them_all;	/* flag to reset the above to */
};

static void
src_spec_show_box(struct scan_info *info, long ulx, long uly, long lrx, long lry)
{
    struct geom_info *g_info = (struct geom_info *)info->data;
    struct src_spec_show_data *data = g_info->geom_data;

    if (data->do_this_one) {
	long x = ulx / mane.shrinkfactor;
	long y = uly / mane.shrinkfactor;

	XDrawRectangle(DISP, mane.win, globals.gc.high,
		       x - mane_base_x, y - mane_base_y,
		       lrx / mane.shrinkfactor - x,
		       lry / mane.shrinkfactor - y);
	data->do_this_one = data->do_them_all;
    }
}

static void
src_spec_show_special(struct scan_info *info, const char *str, int str_len)
{
    /*     if (memcmp(str, "src:", 4) != 0) */
    /* 	return; */
    struct geom_info *g_info = (struct geom_info *)info->data;
    UNUSED(str_len);
    XDVI_INFO((stdout, "special: %s", str));
    ((struct src_spec_show_data *)g_info->geom_data)->do_this_one = True;
}

void
source_special_show(wide_bool do_them_all)
{
    struct scan_info info;
    struct geom_info g_info;
    struct src_spec_show_data src_data;

    info.geom_special = src_spec_show_special;

    g_info.geom_box = src_spec_show_box;
    g_info.geom_data = &src_data;

    info.data = &g_info;
    
    src_data.do_this_one = src_data.do_them_all = do_them_all;

    geom_scan(geom_do_char, globals.dvi_file.bak_fp, &info, current_page);
}


/*
 *	Routines for forward search (look up a source line).
 *
 *	The general procedure is:
 *	   (1)	Use spcl_scan() to find the page containing the line (or at
 *		least the closest line).  This could be optimized further.
 *	   (2)	Use geom_scan_part() to find the exact location of the source
 *		special, and the box to highlight.
 */

/* These variables are referenced by src_scan_special().  */

static int src_this_line;
static Boolean src_this_file_equal;
static int src_line;
static int src_col;
static const char *src_file;
static int src_page;
/* static jmp_buf src_env; */
static Boolean found_src;
static unsigned long best_distance;
static unsigned long best_col_dist;
static int best_line;
static int best_page;
static off_t best_offset;
static off_t max_offset;

/* Some of the above, plus these below, are used by geom_scan_part().  */

static Boolean src_fwd_active;

#define BBOX_INFO_MAXIDX 8 /* maximum number of bounding boxes */

/* list of bounding boxes */
struct bbox_info {
    long min_x, max_x, min_y, max_y;
    long spcl_min_x, spcl_max_x, spcl_min_y, spcl_max_y;
} g_bbox_info[BBOX_INFO_MAXIDX];

/* current index in g_bbox_info[] */
static size_t bbox_info_idx = 0;

static Boolean
src_scan_special(char *str, int str_len, void *data)
{
    char *p;
    int col = 0;
    unsigned long distance;
    unsigned long col_dist;
    
    UNUSED(data);
    if (memcmp(str, "src:", 4) != 0)
	return False;

    found_src = True;

    p = str + 4;
    str_len -= 4;
    
    if (*p >= '0' && *p <= '9') {
	src_this_line = atoi(p);
	do {
	    ++p;
	    str_len--;
	}
	while (*p >= '0' && *p <= '9');
    }

    if (*p == ':') {
	++p;
	str_len--;
	col = atoi(p);
	while (*p >= '0' && *p <= '9') {
	    ++p;
	    str_len--;
	}
    }

    if (*p == ' ') {
	++p;
	str_len--;
    }

    /* src specials might omit the file name when it had already
       been specified on the page; so skip the test when the
       special ends right here:
    */
    if (*p != '\0') {
	ASSERT(globals.dvi_file.dirname != NULL, "DVI name should be set here");
	src_this_file_equal = src_compare(p, str_len, src_file, globals.dvi_file.dirname, globals.dvi_file.dirlen);
    }
    
    if (!src_this_file_equal)
	return False;
    
    distance = (src_line > src_this_line
		? src_line - src_this_line
		: 2 * (src_this_line - src_line));	/* favor earlier lines */

    if (distance < best_distance) {	/* found a better line */
	best_distance = distance;
	best_line = src_this_line;
	best_page = src_page;
	max_offset = best_offset = xtell(globals.dvi_file.bak_fp, currinf.pos);
    }
    else if (distance == best_distance) { /* still on a good line, remember diff */
	max_offset = xtell(globals.dvi_file.bak_fp, currinf.pos);
    }

    if (distance == 0 && best_distance == 0) {	/* found a better col */
	col_dist = (src_col > col ? src_col - col : 2 * (col - src_col));

	if (col_dist < best_col_dist) {
	    best_col_dist = col_dist;
	    best_page = src_page;
	    max_offset = best_offset = xtell(globals.dvi_file.bak_fp, currinf.pos);
	}
	else if (col_dist == best_col_dist) {
	    max_offset = xtell(globals.dvi_file.bak_fp, currinf.pos);
	}
    }
    return True;
}

static Boolean
htex_scan_special(char *str, int str_len, void *data)
{
    UNUSED(data);

    if (g_anchor_pos == NULL)
	return False;
    
    /* first, hypertex specials */
    if (memicmp(str, "html:", strlen("html:")) == 0) {
	size_t offset = strlen("html:");
	str += offset;
	str_len -= offset;
	
	while (*str == ' ' || *str == '\t') {
	    str++;
	    str_len--;
	}

	if (memicmp(str, "<a name", strlen("<a name")) != 0)
	    return False;

	offset = strlen("<a name");
	str += offset;
	str_len -= offset;
	str_len--; /* for the closing > */
	
	/* skip over spaces, = and open quotes */
	while (*str == ' ' || *str == '=') {
	    str++;
	    str_len--;
	}
	if (*str == '"') {
	    str++;
	    str_len -= 2; /* don't compare closing quote */
	}

	/* fprintf(stderr, "comparing: |%s|%s|%d\n", str, g_anchor_pos, MAX((size_t)str_len, g_anchor_len)); */
	if (memcmp(str, g_anchor_pos, MAX((size_t)str_len, g_anchor_len)) == 0)
	    return True;
	else
	    return False;
    }
    /* then, hdvips specials */
    else if (memicmp(str, "ps:", 3) == 0 && g_anchor_pos != NULL) {
	str += 3;
	if (memicmp(str, "sdict begin ", strlen("sdict begin ")) == 0) {
	    char *ptr = NULL, *pptr = NULL;
	    str += strlen("sdict begin ");
	    if ((ptr = strstr(str, "/View")) != NULL
		&& (ptr = strchr(ptr, '(')) != NULL
		&& (pptr = strchr(ptr + 1, ')')) != NULL) {
		if (memcmp(ptr + 1, g_anchor_pos, pptr - ptr - 1) == 0
		    && g_anchor_pos[pptr - ptr - 1] == '\0') {
		    return True;
		}
	    }
	}
	return False;
    }
    else {
	return False;
    }
}

static void
htex_scan_special_noreturn(struct scan_info *info, const char *str, int str_len)
{
    struct geom_info *g_info = (struct geom_info *)info->data;

    /* first, hypertex specials */
    if (memcmp(str, "html:", strlen("html:")) == 0) {
	size_t offset = strlen("html:");
	str += offset;
	str_len -= offset;

	while (*str == ' ' || *str == '\t') {
	    str++;
	    str_len--;
	}
	
	if (memicmp(str, "<a name", strlen("<a name")) != 0)
	    return;
	
	offset = strlen("<a name");
	str += offset;
	str_len -= offset;
	str_len--; /* for the closing > */
	
	/* skip over spaces, = and open quotes */
	while (*str == ' ' || *str == '=') {
	    str++;
	    str_len--;
	}
	if (*str == '"') {
	    str++;
	    str_len -= 2; /* don't compare closing quote */
	}

	if (g_anchor_pos != NULL
	    && memcmp(str, g_anchor_pos, MAX((size_t)str_len, g_anchor_len)) == 0) {
	    *((int *)g_info->geom_data) = pixel_conv(DVI_V);
	    longjmp(info->done_env, 1);
	}
    }
    /* then, hdvips specials */
    else if (memicmp(str, "ps:", 3) == 0 && g_anchor_pos != NULL) {
	str += 3;
	if (memicmp(str, "sdict begin ", strlen("sdict begin ")) == 0) {
	    char *ptr = NULL, *pptr = NULL;
	    str += strlen("sdict begin ");
	    if ((ptr = strstr(str, "/View")) != NULL
		&& (ptr = strchr(ptr, '(')) != NULL
		&& (pptr = strchr(ptr + 1, ')')) != NULL) {
		if (memcmp(ptr + 1, g_anchor_pos, pptr - ptr - 1) == 0
		    && g_anchor_pos[pptr - ptr - 1] == '\0') {
		    *((int *)g_info->geom_data) = pixel_conv(DVI_V);
		    longjmp(info->done_env, 1);
		}
	    }
	}
    }
}

static void
src_spec_fwd_box(struct scan_info *info, long ulx, long uly, long lrx, long lry)
{
    /* Heuristic value to detect column breaks: A negative vertical
       offset to the next glyph larger than BBOX_OFF will create a new
       bounding box. The amount is in unshrunken pixels, i.e. 120
       \approx 1 Line of 12pt Text. */
    static const int BBOX_OFF = 360;
    
    UNUSED(info);
    if (src_fwd_active) {
	/* probably a column break, create new box */
	if (lry < g_bbox_info[bbox_info_idx].max_y - BBOX_OFF
	    && lrx > g_bbox_info[bbox_info_idx].max_x + 50) {
	    if (bbox_info_idx < BBOX_INFO_MAXIDX - 1) {
		bbox_info_idx++;

		g_bbox_info[bbox_info_idx].min_y = g_bbox_info[bbox_info_idx].min_x = LONG_MAX;
		g_bbox_info[bbox_info_idx].max_x = g_bbox_info[bbox_info_idx].max_y = 0;
	    }
	}
	if (ulx < g_bbox_info[bbox_info_idx].min_x) {
	    g_bbox_info[bbox_info_idx].min_x = ulx;
	}
	if (uly < g_bbox_info[bbox_info_idx].min_y) {
	    g_bbox_info[bbox_info_idx].min_y = uly;
	}
	if (lrx > g_bbox_info[bbox_info_idx].max_x)
	    g_bbox_info[bbox_info_idx].max_x = lrx;
	if (lry > g_bbox_info[bbox_info_idx].max_y) {
	    g_bbox_info[bbox_info_idx].max_y = lry;
	}
    }
}

/* dummy procedure for hyperlinks; we don't need geometry info for these. */
static void
htex_dummy_box(struct scan_info *info, long ulx, long uly, long lrx, long lry)
{
    UNUSED(info);
    UNUSED(ulx);
    UNUSED(uly);
    UNUSED(lrx);
    UNUSED(lry);
    return;
}

static void
src_spec_fwd_special(struct scan_info *info, const char *str, int str_len)
{
    long pos;
    UNUSED(str_len);
    
    if (memcmp(str, "src:", 4) != 0)	/* if no source special */
	return;

    pos = xtell(globals.dvi_file.bak_fp, currinf.pos);
    if (pos >= best_offset)
	src_fwd_active = True;

    if (src_fwd_active) {
	if (pos > max_offset)
	    longjmp(info->done_env, 1);
	
	if (G_PXL_H < g_bbox_info[bbox_info_idx].spcl_min_x)
	    g_bbox_info[bbox_info_idx].spcl_min_x = G_PXL_H;
	if (G_PXL_H > g_bbox_info[bbox_info_idx].spcl_max_x)
	    g_bbox_info[bbox_info_idx].spcl_max_x = G_PXL_H;
	if (PXL_V < g_bbox_info[bbox_info_idx].spcl_min_y)
	    g_bbox_info[bbox_info_idx].spcl_min_y = PXL_V;
	if (PXL_V > g_bbox_info[bbox_info_idx].spcl_max_y)
	    g_bbox_info[bbox_info_idx].spcl_max_y = PXL_V;

    }
}

/*
 *	Routine to actually draw the box.
 */

static void
source_fwd_draw_box(void)
{
    if (globals.src.fwd_box_page != current_page) {
	globals.src.fwd_box_page = -1;	/* different page---clear it */
    }
    else {
	size_t i;
	static const int padding = 15;

	/* fix oversplitting by heuristics, by merging box n with box n+1 if they overlap */
	for (i = 0; i < bbox_info_idx; i++) {
	    if (g_bbox_info[i].min_x == LONG_MAX)
		continue;
	    if ((g_bbox_info[i+1].min_x < g_bbox_info[i].max_x
		 && g_bbox_info[i+1].min_y < g_bbox_info[i].max_y) ||
		(g_bbox_info[i].min_x < g_bbox_info[i+1].max_x
		 && g_bbox_info[i].min_y < g_bbox_info[i+1].max_y)) { /* overlap */		
		g_bbox_info[i].min_x = MIN(g_bbox_info[i].min_x, g_bbox_info[i+1].min_x);
		g_bbox_info[i].min_y = MIN(g_bbox_info[i].min_y, g_bbox_info[i+1].min_y);
		g_bbox_info[i].max_x = MAX(g_bbox_info[i].max_x, g_bbox_info[i+1].max_x);
		g_bbox_info[i].max_y = MAX(g_bbox_info[i].max_y, g_bbox_info[i+1].max_y);
		bbox_info_idx--;
	    }
	}
	
	for (i = 0; i <= bbox_info_idx; i++) {
	    int min_x, min_y, max_x, max_y;
	    
	    volatile XPoint ul, ur, ll, lr;
	    
	    if (g_bbox_info[i].min_x == LONG_MAX) {
		/* If no glyphs or rules, use hot point of special instead.  */
		g_bbox_info[i].min_x = g_bbox_info[i].spcl_min_x;
		g_bbox_info[i].min_y = g_bbox_info[i].spcl_min_y;
		g_bbox_info[i].max_x = g_bbox_info[i].spcl_max_x;
		g_bbox_info[i].max_y = g_bbox_info[i].spcl_max_y;
	    }

	    min_x = (g_bbox_info[i].min_x - padding) / mane.shrinkfactor - mane_base_x;
	    min_y = (g_bbox_info[i].min_y - padding) / mane.shrinkfactor - mane_base_y;
	    max_x = (g_bbox_info[i].max_x + padding) / mane.shrinkfactor - mane_base_x;
	    max_y = (g_bbox_info[i].max_y + padding) / mane.shrinkfactor - mane_base_y;

	    ul.x = min_x;
	    ul.y = min_y;

	    ur.x = max_x;
	    ur.y = min_y;

	    ll.x = min_x;
	    ll.y = max_y;

	    lr.x = max_x;
	    lr.y = max_y;
	    
	    if (i == 0 && bbox_info_idx == 0) { /* only 1 bounding box */
		XDrawRectangle(DISP, mane.win, globals.gc.high,
			       min_x, min_y, max_x - min_x, max_y - min_y);
	    }
	    else if (i == 0) { /* draw first bbox with open bottom */
		XPoint points[4];
		points[0] = ll;
		points[1] = ul;
		points[2] = ur;
		points[3] = lr;
		XDrawLines(DISP, mane.win, globals.gc.high, points, 4, CoordModeOrigin);
	    }
	    else if (i > 0 && i < bbox_info_idx) { /* draw middle box with open top and open bottom */
		XPoint points[2];
		points[0] = ul;
		points[1] = ll;
		XDrawLines(DISP, mane.win, globals.gc.high, points, 2, CoordModeOrigin);
		points[0] = ur;
		points[1] = lr;
		XDrawLines(DISP, mane.win, globals.gc.high, points, 2, CoordModeOrigin);
	    }
	    else { /* draw last box with open top */
		XPoint points[4];
		points[0] = ul;
		points[1] = ll;
		points[2] = lr;
		points[3] = ur;
		XDrawLines(DISP, mane.win, globals.gc.high, points, 4, CoordModeOrigin);
	    }
	}
    }
}

#if 0
#include<asm/msr.h>
unsigned long time_start=0, time_end=0;
#endif

void
source_forward_search(const char *str)
{
    volatile off_t pos_save = 0;
    struct drawinf currinf_save;
    ubyte maxchar_save;
    struct scan_info info;
    struct geom_info g_info;
    
    TRACE_CLIENT((stderr, "Entering source_forward_search(%s)", str));

    max_offset = 0;
    src_file = str;
    while (*src_file == '0')
	++src_file;
    if (*src_file < '1' || *src_file > '9') {
	XDVI_WARNING((stderr, "Ignoring malformed source special \"%s\"", str));
	return;
    }
    src_line = atoi(src_file);
    while (*src_file >= '0' && *src_file <= '9')
	++src_file;

    src_col = 0;
    if (*src_file == ':') {
	++src_file;
	src_col = atoi(src_file);
	while (*src_file >= '0' && *src_file <= '9')
	    ++src_file;
    }

    if (*src_file == ' ')
	++src_file;

    TRACE_CLIENT((stderr, "File = \"%s\", line = %d, col = %d", src_file, src_line, src_col));

    /* Save status of dvi_file reading (in case we hit an error and resume
       drawing).  */

    if (dvi_pointer_frame != NULL)
	pos_save = lseek(fileno(globals.dvi_file.bak_fp), 0L, SEEK_CUR) - (dvi_pointer_frame->end - dvi_pointer_frame->pos);
    (void)lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(0), SEEK_SET);

    currinf_save = currinf;
    maxchar_save = maxchar;

    memset((char *)&currinf.data, '\0', sizeof currinf.data);
    currinf.tn_table_len = TNTABLELEN;
    currinf.tn_table = tn_table;
    currinf.tn_head = tn_head;
    currinf.pos = currinf.end = dvi_buffer;
    currinf.virtual = NULL;

    /* Start search over pages */
#if 0
    rdtscl(time_start);
#endif

    found_src = False;
    best_distance = best_col_dist = ULONG_MAX;
    src_this_line = 0;	/* initialize things that are kept as defaults */
    src_this_file_equal = False;

    /* These two lines do the actual scanning (first pass).  */
    for (src_page = 0; src_page < total_pages; ++src_page)
	(void)spcl_scan(src_scan_special, NULL, False, globals.dvi_file.bak_fp);

    if (best_distance == ULONG_MAX) {
	if (!found_src) {
	    popup_message(globals.widgets.top_level,
			  MSG_WARN,
			  /* helptext */
			  reverse_search_helptext,
			  /* popup */
			  "No source specials in this DVI file - couldn't do reverse search.");
	}
	else {
	    popup_message(globals.widgets.top_level,
			  MSG_WARN,
			  /* helptext */
			  "To enable reverse search, the TeX file has to be compiled with source specials. "
			  "See the xdvi man page (section SOURCE SPECIALS) for details.",
			  /* popup */
			  "No references to source file \"%s\" in DVI file.",
			  src_file);
	}

	/* Restore file position.  */
	maxchar = maxchar_save;
	currinf = currinf_save;

	if (dvi_pointer_frame != NULL) {
	    (void)lseek(fileno(globals.dvi_file.bak_fp), pos_save, SEEK_SET);
	    dvi_pointer_frame->pos = dvi_pointer_frame->end = dvi_buffer;
	}

	return;
    }
    TRACE_CLIENT((stderr, "Match:  line %d on page %d, offset %lu",
		  best_line, best_page + globals.pageno_correct, (unsigned long)best_offset));

    /*
     * In this case we don't need to restore maxchar and currinf, since
     * we won't resume drawing -- we'll jump to a new page instead.
     */

    /* Move to that page.  */
    goto_page(best_page, resource.keep_flag ? NULL : home, False);
    page_history_insert(best_page);
    globals.ev.flags |= EV_NEWPAGE; /* so that existing mark gets erased */
    /* Now search that particular page.  */

    info.geom_special = src_spec_fwd_special;

    g_info.geom_box = src_spec_fwd_box;
    g_info.geom_data = NULL;

    src_fwd_active = False;
    bbox_info_idx = 0;

    g_bbox_info[bbox_info_idx].min_x =
	g_bbox_info[bbox_info_idx].min_y =
	g_bbox_info[bbox_info_idx].spcl_min_x =
	g_bbox_info[bbox_info_idx].spcl_min_y = LONG_MAX;
    g_bbox_info[bbox_info_idx].max_x =
	g_bbox_info[bbox_info_idx].max_y =
	g_bbox_info[bbox_info_idx].spcl_max_x =
	g_bbox_info[bbox_info_idx].spcl_max_y = 0;
    globals.src.fwd_box_page = -1;	/* in case of error, suppress box */

    (void)lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(best_page), SEEK_SET);
    currinf.tn_table_len = TNTABLELEN;
    currinf.tn_table = tn_table;
    currinf.tn_head = tn_head;
    currinf.pos = currinf.end = dvi_buffer;
    currinf.virtual = NULL;

    info.data = &g_info;
    
    if (!setjmp(info.done_env))
	geom_scan_part(geom_do_char, globals.dvi_file.bak_fp, &info, geom_current_frame = &geom_frame0, dimconv);

    if (!src_fwd_active) {
	XDVI_ERROR((stderr, "%s:%d: shouldn't happen: geom_scan_part() failed to re-find the special.", __FILE__, __LINE__));
    }
    else {
	long x_min = g_bbox_info[bbox_info_idx].min_x;
	long y_min = g_bbox_info[bbox_info_idx].min_y;
	long x_max = g_bbox_info[bbox_info_idx].max_x;
	long y_max = g_bbox_info[bbox_info_idx].max_y;
	do_autoscroll = True;
	globals.src.fwd_box_page = current_page;
	if (x_min == LONG_MAX || x_max == LONG_MAX) {
	    /* If no glyphs or rules, use hot point of special instead.  */
	    x_min = g_bbox_info[bbox_info_idx].spcl_min_x;
	    y_min = g_bbox_info[bbox_info_idx].spcl_min_y;
	    x_max = g_bbox_info[bbox_info_idx].spcl_max_x;
	    y_max = g_bbox_info[bbox_info_idx].spcl_max_y;
	}
	scroll_page_if_needed((int)(x_min / currwin.shrinkfactor) + 2, (int)(x_max / currwin.shrinkfactor) - 2,
			      (int)(y_min / currwin.shrinkfactor) + 10, (int)(y_max / currwin.shrinkfactor) - 10);
    }
#if 0
    rdtscl(time_end);
    printf("time search: %lu\n", time_end - time_start);
#endif
}


void
anchor_search(const char *str)
{
    off_t pos_save = 0;
    struct drawinf currinf_save;
    ubyte maxchar_save;
    volatile int test_page = 0;
    Boolean found_anchor = False;
    int y_pos = -1;
    struct scan_info info;
    struct geom_info g_info;

    ASSERT(str != NULL, "Argument to anchor_search() musn't be NULL");
    TRACE_HTEX((stderr, "Entering anchor_search(%s)", str));

    /* Save status of dvi_file reading (in case we hit an error and resume drawing).  */
    if (dvi_pointer_frame != NULL)
	pos_save = lseek(fileno(globals.dvi_file.bak_fp), 0L, SEEK_CUR) - (dvi_pointer_frame->end - dvi_pointer_frame->pos);
    (void)lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(0), SEEK_SET);

    currinf_save = currinf;
    maxchar_save = maxchar;

    memset((char *)&currinf.data, '\0', sizeof currinf.data);
    currinf.tn_table_len = TNTABLELEN;
    currinf.tn_table = tn_table;
    currinf.tn_head = tn_head;
    currinf.pos = currinf.end = dvi_buffer;
    currinf.virtual = NULL;

    /* Start search over pages */
    for (test_page = 0; test_page < total_pages; test_page++) {
	if (spcl_scan(htex_scan_special, NULL, True, globals.dvi_file.bak_fp)) {
	    found_anchor = True;
	    break;
	}
    }

    if (!found_anchor) {
	/* Restore file position.  */
	maxchar = maxchar_save;
	currinf = currinf_save;
	
	if (dvi_pointer_frame != NULL) {
	    (void)lseek(fileno(globals.dvi_file.bak_fp), pos_save, SEEK_SET);
	    dvi_pointer_frame->pos = dvi_pointer_frame->end = dvi_buffer;
	}
	XBell(DISP, 0);
	statusline_print(STATUS_MEDIUM, "Error: Anchor \"%s\" not found.", str);
	return;
    }

    /*
     * In this case we don't need to restore maxchar and currinf, since
     * we won't resume drawing -- we'll jump to a new page instead.
     */

    /* Move to that page.  */
    (void)lseek(fileno(globals.dvi_file.bak_fp), pageinfo_get_offset(test_page), SEEK_SET);
    currinf.tn_table_len = TNTABLELEN;
    currinf.tn_table = tn_table;
    currinf.tn_head = tn_head;
    currinf.pos = currinf.end = dvi_buffer;
    currinf.virtual = NULL;

    info.geom_special = htex_scan_special_noreturn;

    g_info.geom_box = htex_dummy_box;
    g_info.geom_data = &y_pos;

    info.data = &g_info;
    
    if (!setjmp(info.done_env))
	geom_scan_part(geom_do_char, globals.dvi_file.bak_fp, &info, geom_current_frame = &geom_frame0, dimconv);

    if (y_pos == -1) { /* not found */
	XDVI_ERROR((stderr, "%s:%d: shouldn't happen: geom_scan_part() failed to re-find the link.", __FILE__, __LINE__));
    }
    else {
	goto_page(test_page, resource.keep_flag ? NULL : home, False);
	page_history_insert(test_page);
	do_autoscroll = True;
	htex_set_anchormarker(y_pos);
    }
    /* reset info */
    free(g_anchor_pos);
    g_anchor_pos = NULL;
    g_anchor_len = 0;
}



#ifdef T1LIB

/* ********************** FONT AND ENCODING LOADING ******************** */

static char **
load_vector(const char *enc)
{
    char *filename;

    TRACE_T1((stderr, "loading vector: |%s|", enc));
    /*
     * With TDS 1.0/kpathsea 3.5.2(?), encoding files are in texmf/fonts/enc and accessed
     * via kpse_enc_format; see e.g.:
     * http://tug.org/mailman/htdig/tex-live/2004-January/004734.html
     *
     * The lookups under kpse_program_text_format and kpse_tex_ps_header_format
     * are kept for backwards compatibility.
     */
    do {
	filename = kpse_find_file(enc, kpse_enc_format, 0);
	if (filename != NULL)
	    break;
	filename = kpse_find_file(enc, kpse_program_text_format, 0);
	if (filename != NULL)
	    break;
	/* If that fails we try the standard place: postscript headers */
	filename = kpse_find_file(enc, kpse_tex_ps_header_format, 1);
    } while (0);
    
    if (filename != NULL) {
	return T1_LoadEncoding(filename);
    }
    return NULL;
}



static int
add_tfm(const char *texname)
{
    /* Load font metrics if not already loaded.  Return index into
       fontmetrics array. If it fails, fallback metrics must be
       loaded instead; if that fails as well, exit. */

    size_t i;
    int idx;
    static size_t wlidx = 0;
    static size_t wlidx_max = 0;
    static const size_t wlidx_step = 1024;

#ifdef USE_HASH
    if (tfminfo_hash.size == 0)
	tfminfo_hash = hash_create(T1FONTS_INITIAL_HASHTABLE_SIZE);

    if (find_str_int_hash(&tfminfo_hash, texname, &i)) {
	return i;
    }
#else
    for (i = 0; i <= wlidx; i++)
	if (strcmp(tfminfo[i].texname, texname) == 0)
	    return i;
#endif /* USE_HASH */
    
    while (wlidx >= wlidx_max) {
	wlidx_max += wlidx_step;
	tfminfo = xrealloc(tfminfo, wlidx_max * sizeof *tfminfo);
    }

    tfminfo[wlidx].texname = texname;
#ifdef USE_HASH
    put_str_int_hash(&tfminfo_hash, tfminfo[wlidx].texname, wlidx);
#endif /* USE_HASH */
    
    if (!tfmload(texname, &tfminfo[wlidx].designsize, tfminfo[wlidx].widths,
		 &(tfminfo[wlidx].fontdimen2))) {
	XDVI_FATAL((stderr, "Cannot find font metrics file %s.tfm, "
		    "and fallback metrics cmr10.tfm is missing as well - exiting.\n",
		    texname));
    }

    idx = wlidx++;

    if (fallbacktfm == 1) {
	fallbacktfm = 2;
	popup_message(globals.widgets.top_level,
		      MSG_ERR,
		      /* helptext */
		      "A font metrics file is missing and cmr10.tfm is used instead.  Either "
		      "you don't have the file, or your TeX system cannot find it.  Try to "
		      "invoke \"kpsewhich\" with the filename mentioned in the error "
		      "message and the \"-debug 2\" option to find out where Xdvik is "
		      "searching for your files.\n"
		      "To enable automatic creation of .tfm files for "
		      "Metafont fonts, "
		      /* FIXME: how about Type1 fonts?? */
		      "set the environment variable MKTEXTFM "
		      "to 1 and re-start xdvi. See the file texmf.cnf for the default settings "
		      "of MKTEXTFM and related variables.",
		      /* message */
		      "Could not find font metrics file %s.tfm; using cmr10.tfm instead. "
		      "Expect ugly output.",
		      texname);
    }

    return idx;
}

static int
add_t1font(const char *fontname, const char *filename)
{
    /* Add t1 font to list if it's not already there; return the
       (newly created, or already existing) t1lib font id.  The
       filename argument is optional, but is assumed to be a full path
       if it is given.
    */

    int id, idx;
    size_t i;
    char *path = NULL;
    
    static size_t t1lidx = 0;
    static size_t t1lidx_max = 0;
    static const size_t t1lidx_step = 1024;

    
#ifdef USE_HASH
    /* Already set up by that name? */
    if (t1fonts_hash.size == 0)
	t1fonts_hash = hash_create(T1FONTS_INITIAL_HASHTABLE_SIZE);
    
    if (find_str_int_hash(&t1fonts_hash, fontname, &i)) {
	return i;
    }
#else
    for (i = 0; i <= t1lidx; i++) {
	if (strcmp(t1fonts[i].shortname, fontname) == 0 ||
	    strcmp(t1fonts[i].file, fontname) == 0) {
	    TRACE_T1((stderr, "Type1 font %s already loaded from %s", fontname, filename));
	    return i;
	}
    }
#endif /* USE_HASH */

    /* Insert and set up new t1 font */
    while (t1lidx >= t1lidx_max) {
	t1lidx_max += t1lidx_step;
	t1fonts = xrealloc(t1fonts, t1lidx_max * sizeof *t1fonts);

	TRACE_T1((stderr, "Enlarged t1 table from %lu to %lu entries",
		  (unsigned long)t1lidx, (unsigned long)t1lidx_max));
    }

    if (filename == NULL) {
	/* We don't know the full path name yet, find it */
	path = kpse_find_file(fontname, kpse_type1_format, 0);
	if (path == NULL)
	    return -1;	/* xdvi will use substitution font */
    }
    else {
	path = xstrdup(filename);
    }

    t1fonts[t1lidx].file = xstrdup(path);
    t1fonts[t1lidx].t1id = id = T1_AddFont(path);
    t1fonts[t1lidx].shortname = xstrdup(fontname);
    t1fonts[t1lidx].loaded = 0;

    TRACE_T1((stderr, "Loaded font %lu/%s (%d) from %s", (unsigned long)t1lidx, fontname, id, path));

#ifdef USE_HASH
    /* also save the info in the hashtable (note: no duplication of the string here!) */
    put_str_int_hash(&t1fonts_hash, t1fonts[t1lidx].shortname, t1lidx);
#endif /* USE_HASH */

    idx = t1lidx++;
    
    free(path);

#if USE_AFM
    /* BZZT! AFM files are useless to us, their metric information is
       too inacurate for TeX use.  Never define USE_AFM */
    /* Set the afm filename.  Always before loading.  If no afm file
       found t1lib will use fallback code.  In fact the fallback code
       has been deactivated entierly, it's dog slow. */
    path = kpse_find_file(fontname, kpse_afm_format, 0);
    if (path != NULL) {
	TRACE_T1((stderr, "found afm file: %s", path));
	T1_SetAfmFileName(id, path);
	free(path);
    }
#endif

    return idx;
}

static int
find_texfont(const char *texname)
{
    /* Find fontmap index of texfont */
    size_t i;

#ifdef USE_HASH
    if (fontmaps_hash.size == 0)
	fontmaps_hash = hash_create(T1FONTS_INITIAL_HASHTABLE_SIZE);
    
    if (find_str_int_hash(&fontmaps_hash, texname, &i)) {
	return i;
    }
#else
    for (i = 0; i < g_maplidx; i++) {
	if (strcmp(fontmaps[i].texname, texname) == 0) {
	    TRACE_T1((stderr, "Type1 font already loaded at index %d: %s\n", i, texname));
	    return i;
	}
    }
#endif /* USE_HASH */
    return -1;
}


static int
setup_encoded_T1_font(const char *mapfile,
		      int lineno,
		      const char *texname,
		      const char *alias,
		      const char *filename,
		      int enc,
		      int ext,
		      int sl)
{
    /* xdvi T1 Font loading is done in two steps:

       1. At xdvi startup two things happen:

       a. The fontmaps are read.

       b. the dvi file is (partialy) scanned, and fonts are set up.
       In the case of t1 fonts we only set up the data structures,
       they are not actually loaded until they are used.

       2. At the time a T1 font is used it is loaded, encoded, extended
       and slanted as prescribed.

       This procedure takes care of step 1a.  It locates the font and
       sets up the data-structures so it may be assumed, by xdvi, to be
       loaded.  Return the fontmaps array index.

       The 'texname' param should be the texname of the font, such as
       ptmr8r or cmr10.

       The 'alias' param should be the file name of the font if it is
       different from the texname.  This is often the case with fonts
       defined in fontmaps.

       If, for some reason, the full filename of the font has already
       been looked up before we get here it is passed in the filename
       param so we don't have to look it up again.

       Implied encodings are not handled here.

       REMEMBER: THIS PROC IS CALLED FOR EACH FONT IN THE FONTMAPS, WE
       CANNOT DO ANY EXPENSIVE OPERATIONS HERE!!!

     */

    int test_idx;
    size_t curr_idx;
    static size_t maplidx_max = 0;
    static const size_t maplidx_step = 1024;

    /* fprintf(stderr, "setup font: |%s|, %s\n", texname, mapfile); */
    
    /* Already setup by that name? */
    test_idx = find_texfont(texname);
    ASSERT(test_idx == -1 || (test_idx > -1 && test_idx < (int)g_maplidx), "Result of find_texfont() out of range");
    
    if (mapfile != NULL && test_idx != -1) {
	/* font is already set up, and we're scanning the map file: replace existing font with new one */
	curr_idx = test_idx;
	XDVI_INFO((stdout, "%s: Entry for font \"%s\" on line %d overrides previous entry.",
		   mapfile, texname, lineno));
	
	free(fontmaps[curr_idx].filename);
	free(fontmaps[curr_idx].pathname);
    }
    else if (test_idx != -1) {
	/* font is already set up, but we're not scanning the map file - return font index */
	TRACE_T1_VERBOSE((stderr, "font |%s| already set up at index %d", texname, test_idx));
	return test_idx;
    }
    else { /* Not set up yet, do it. */
	curr_idx = g_maplidx++;		/* increment global index count */
	while (curr_idx >= maplidx_max) {
	    maplidx_max += maplidx_step;
	    fontmaps = xrealloc(fontmaps, maplidx_max * sizeof *fontmaps);
	    
	    TRACE_T1((stderr, "Enlarged the fontmap from %lu to %lu entries",
		      (unsigned long)curr_idx, (unsigned long)maplidx_max));
	}
	fontmaps[curr_idx].texname = xstrdup(texname);
    }

    if (alias == NULL)
	alias = texname;

#ifdef USE_HASH
    if (test_idx == -1) {
	if (fontmaps_hash.size == 0)
	    fontmaps_hash = hash_create(T1FONTS_INITIAL_HASHTABLE_SIZE);
	put_str_int_hash(&fontmaps_hash, fontmaps[curr_idx].texname, curr_idx);
    }
#endif /* USE_HASH */
    
    fontmaps[curr_idx].enc = enc;
    fontmaps[curr_idx].extension = ext;
    fontmaps[curr_idx].slant = sl;

    fontmaps[curr_idx].filename = xstrdup(alias);
    fontmaps[curr_idx].t1libid = -1;
    TRACE_T1((stderr,
	      "fontmaps[%lu]: \"%s\", enc=%d, ext=%d, sl=%d, alias \"%s\"",
	      (unsigned long)curr_idx, texname, enc, ext, sl, alias));

    if (filename != NULL) {
	fontmaps[curr_idx].pathname = xstrdup(filename);
    }
    else {
	fontmaps[curr_idx].pathname = NULL;
    }
    fontmaps[curr_idx].tfmidx = -1;
    fontmaps[curr_idx].warned_about = False;
    fontmaps[curr_idx].force_pk = False;

    return curr_idx;
}


static t1FontLoadStatusT
try_pk_fallback(int idx, struct font *fontp)
{
    Boolean success;

    success = load_font(fontp, False); /* loading non-t1 version of font */
    fontmaps[idx].force_pk = True;
    TRACE_T1((stderr,
	      "setting force_pk for %d to true; success for PK fallback: %d\n",
	      idx, success));
    
    if (!success) {
	/* this is probably serious enough for a GUI warning */
	popup_message(globals.widgets.top_level,
		      MSG_ERR,
		      /* helptext */
		      "Xdvi tries all of the following possibilities in turn, and all of them have failed:\n\n"
		      "  (1) If the resource t1lib is set, try a Postscript Type1 version of a font.\n\n"
		      "  (2) Otherwise, or if the Type1 version hasn't been found, try to "
		      "locate, or generate via mktexpk, a TeX Pixel (PK) version of the font.\n\n"
		      "  (3) Use the fallback font defined via the \"altfont\" resource (cmr10 by default), "
		      "both as Type1 and as PK version, at various resolutions.\n\n"
		      "It seems that your font setup is defective.\n",
		      /* errmsg */
		      "Error loading font %s: Neither a Type1 version nor "
		      "a pixel version could be found. The character(s) "
		      "will be left blank.",
		      fontmaps[idx].texname);
	currinf.set_char_p = currinf.fontp->set_char_p = set_empty_char;
	return FAILURE_BLANK;
    }
    /* free unneeded resources. We mustn't free
       fontmaps[idx].texname,
       else find_T1_font will try to load the font all over again.
    */
    free(fontmaps[idx].filename);
    free(fontmaps[idx].pathname);
    currinf.set_char_p = currinf.fontp->set_char_p = set_char;
    return FAILURE_PK;
}

static t1FontLoadStatusT
load_font_now(int idx, struct font *fontp)
{
    /* At this point xdvi needs to draw a glyph from this font.  But
       first it must be loaded/copied and modified if needed. */

    int t1idx;	/* The fontmap entry number */
    int t1id;	/* The id of the unmodified font */
    int cid;	/* The id of the copied font */
    int enc, sl, ext;

/*      statusline_print(STATUS_SHORT, "Loading T1 font %s", fontmaps[idx].filename); */
    TRACE_T1((stderr, "adding %s %s",
	      fontmaps[idx].filename, fontmaps[idx].pathname));

    t1idx = add_t1font(fontmaps[idx].filename, fontmaps[idx].pathname);

    t1id = fontmaps[idx].t1libid = t1fonts[t1idx].t1id;

    errno = 0;
    if (!t1fonts[t1idx].loaded && T1_LoadFont(t1id) == -1) {
	/* this error is somehow better kept at stderr instead of
	   a popup (too annoying) or the statusline (might disappear
	   too fast) ... */
	XDVI_ERROR((stderr, "Could not load Type1 font %s from %s: %s %s (T1_errno = %d); "
		    "will try pixel version instead.\nPlease see the T1lib documentation for details about this.\n",
		    fontmaps[idx].texname, fontmaps[idx].pathname,
		    T1_StrError(T1_errno),
		    errno != 0 ? strerror(errno) : "",
		    T1_errno));
	return try_pk_fallback(idx, fontp);
    }

    fontmaps[idx].tfmidx = add_tfm(fontmaps[idx].texname);

    t1fonts[t1idx].loaded = 1;

    /* If there is nothing further to do, just return */
    enc = fontmaps[idx].enc;
    ext = fontmaps[idx].extension;
    sl = fontmaps[idx].slant;
    TRACE_T1((stderr, "lookup at idx[%d] yields: %d, %d, %d", idx, enc, ext, sl));

    if (enc == -1 && ext == 0 && sl == 0)
	return SUCCESS;

    /* The fontmap entry speaks of a modified font.  Copy it first. */
    cid = T1_CopyFont(t1id);

    fontmaps[idx].t1libid = cid;

    if (enc != -1) {
	TRACE_T1_VERBOSE((stderr, "trying to load vector from encodings index %d", enc));
	/* Demand load vector */
	if (encodings[enc].vector == NULL)
	    encodings[enc].vector = load_vector(encodings[enc].file);

	if (encodings[enc].vector == NULL) {
	    /* this error is somehow better kept at stderr instead of
	       a popup (too annoying) or the statusline (might disappear
	       too fast) ... */
	    XDVI_ERROR((stderr, "Could not load load encoding file %s for vector %s (font %s): %s (T1_errno = %d); "
			"will try pixel version instead.\n"
			"Please see the T1lib documentation for details about this.\n",
			encodings[enc].file, encodings[enc].enc, fontmaps[idx].texname,
			T1_StrError(T1_errno), T1_errno));
	    return try_pk_fallback(idx, fontp);
	}
	else {
	    if (T1_ReencodeFont(cid, encodings[enc].vector) != 0) {
		XDVI_ERROR((stderr, "Re-encoding of %s failed: %s (T1_errno = %d); "
			    "will try pixel version instead.\n"
			    "Please see the T1lib documentation for details about this.\n",
			    fontmaps[idx].texname,
			    T1_StrError(T1_errno), T1_errno));
		return try_pk_fallback(idx, fontp);
	    }
	}
    }

    if (ext)
	T1_ExtendFont(cid, ext / 1000.0);

    if (sl)
	T1_SlantFont(cid, sl / 10000.0);

    return SUCCESS;
}


int
find_T1_font(const char *texname)
{
    /* Step 1b in the scenario above.  xdvi knows that this font is
       needed.  Check if it is available, but do not load it yet.
       Return the fontmap index at which the font was found */

    int idx;	/* Iterator, t1 font id */
    char *filename;
    int fl, el;	/* Fontname length, encoding name length */
    int encoded = -1;
    char *mname;	/* Modified font name */
    size_t i;
    
    encoded = 0;

    /* First: Check the maps */
    idx = find_texfont(texname);

    if (idx != -1) {
	if (fontmaps[idx].force_pk) {
	    return idx;
	}
	if (fontmaps[idx].pathname == NULL) {
	    filename = kpse_find_file(fontmaps[idx].filename, kpse_type1_format, 0);
	    /* It should have been on disk according to the map, but never
	       fear, xdvi will try to find it other ways. */
	    if (filename == NULL) {
		if (!fontmaps[idx].warned_about) {
		    fontmaps[idx].warned_about = True;
		    XDVI_WARNING((stderr, "Font map calls for %s, but it was not found (will try PK version instead).",
				  fontmaps[idx].filename));
		}
		return -1;
	    }
	    fontmaps[idx].pathname = filename;
	}
	TRACE_T1((stderr, "find_T1_font map: %s, entered as #%d",texname,idx));
	return idx;
    }

#if 0
    /* Second: the bare name */
    filename = kpse_find_file(texname, kpse_type1_format, 0);

    if (filename != NULL) {
	idx = setup_encoded_T1_font(NULL, 0, texname, NULL, filename, -1, 0, 0);
	TRACE_T1((stderr, "find_T1_font bare enc: %s, entered as #%d", texname, idx));
	return idx;
    }
#endif

    /* Third: Implied encoding? */
    fl = strlen(texname);

    for (i = 0; i < enclidx; i++) {
	if (encodings[i].enc == NULL)
	    continue;

	el = strlen(encodings[i].enc);
	
	/* Note: the encodings map is short, so the strcmp doesn't hurt here */
	if (strcmp(texname + (fl - el), encodings[i].enc) == 0) {
	    /* Found a matching encoding */
	    TRACE_T1((stderr, "Encoding match: %s - %s", texname,
		      encodings[i].enc));

	    mname = malloc(fl + 2);
	    strcpy(mname, texname);
	    mname[fl - el] = '\0';
	    /* If we had 'ptmr8r' the we now look for 'ptmr' */
	    filename = kpse_find_file(mname, kpse_type1_format, 0);
	    if (filename == NULL) {
		/* No? Look for ptmr8a. 8a postfix is oft used on raw fonts */
		strcat(mname, "8a");
		filename = kpse_find_file(mname, kpse_type1_format, 0);
	    }
	    if (filename != NULL) {
		idx = setup_encoded_T1_font(NULL, 0, texname, mname, filename, i, 0, 0);
		TRACE_T1((stderr, "find_T1_font implied enc: %s, is #%d", texname, idx));
		return idx;
	    }

	    free(mname);
	    /* Now we have tried everything.  Time to give up. */
	    return -1;

	}	/* If (strcmp...) */
    }	/* for */
    return -1;
}

/* ************************* CONFIG FILE READING ************************ */


static int
new_encoding(const char *enc, const char *file)
{
    /* (Possibly) new encoding entered from .cfg file, or from dvips
       map.  When entered from dvips map the enc is null/anonymous */

    size_t i;
    static size_t enclidx_max = 0;
    static const size_t enclidx_step = 16;

    /* Shirley!  You're jesting! */
    if (file == NULL)
	return -1;

    if (enc == NULL) {
	/* Enter by file name.  First check if the file has been entered
	   already. */
	for (i = 0; i < enclidx; i++) { 
	    if (strcmp(encodings[i].file, file) == 0) {
		return i;
	    }
	}
    }
    else {
	/* Enter by encoding name.  Check if already loaded first. */
	for (i = 0; i < enclidx; i++) {
	    if (encodings[i].enc != NULL && strcmp(encodings[i].enc, enc) == 0)
		return i;
	}
    }

    /* Bonafide new encoding */

    while (enclidx >= enclidx_max) {
	enclidx_max += enclidx_step;
	encodings = xrealloc(encodings, enclidx_max * sizeof *encodings);

	TRACE_T1((stderr, "Enlarged encoding map from %lu to %lu entries",
		  (unsigned long)enclidx, (unsigned long)enclidx_max));
    }

    TRACE_T1((stderr, "New encoding #%lu: '%s' -> '%s'", (unsigned long)enclidx, enc ? enc : "<NULL>", file));

    /* The encoding name is optional */
    encodings[enclidx].enc = NULL;
    if (enc != NULL)
	encodings[enclidx].enc = xstrdup(enc);

    /* The file name is required */
    encodings[enclidx].file = xstrdup(file);
    encodings[enclidx].vector = NULL;	/* Demand load later */

    return enclidx++;
}


void
add_T1_mapentry(int lineno,
		const char *mapfile,
		const char *name,
		const char *file,
		const char *vec,
		char *spec)
{
    /* This is called from read_map_file, once for each fontmap line `lineno'.  We
       will dutifully enter the information into our fontmap table */

    static char delim[] = "\t ";
    char *last, *current;
    float number;
    int extend = 0;
    int slant = 0;

    TRACE_T1_VERBOSE((stderr, "%s:%d: %s -> %s Enc: %s Spec: %s",
		      mapfile, lineno,
		      name, file,
		      vec == NULL ? "<none>" : vec,
		      spec == NULL ? "<none>" : spec));

    if (spec != NULL) {
	/* Try to analyze the postscript string.  We recognize two things:
	   "n ExtendFont" and "m SlantFont".  n can be a decimal number in
	   which case it's an extension factor, or a integer, in which
	   case it's a charspace unit? In any case 850 = .85 */

	last = strtok(spec, delim);
	current = strtok(NULL, delim);
	while (current != NULL) {
	    if (strcmp(current, "ExtendFont") == 0) {
		sscanf(last, "%f", &number);
		if (number < 10.0)
		    extend = number * 1000.0;
		else
		    extend = number;
	    }
	    else if (strcmp(current, "SlantFont") == 0) {
		sscanf(last, "%f", &number);
		slant = number * 10000.0;
	    }
	    last = current;
	    current = strtok(NULL, delim);
	}
    }

    setup_encoded_T1_font(mapfile, lineno,
			  name, file, NULL, new_encoding(NULL, vec), extend,
			  slant);
}

static void
read_cfg_file(const char *file)
{
    char *filename;
    FILE *fp;
    int len;
    char *keyword;
    char *ptr;
    char *buffer;
    char *enc;
    char *name;
    int i;
    static const char delim[] = "\t \n\r";
    Boolean found_no_map_files = True;
    
    if (file == NULL)
	file = "xdvi.cfg";
    
    filename = kpse_find_file(file, kpse_program_text_format, 1);
    if (filename == NULL) {
	statusline_print(STATUS_MEDIUM, "Warning: Unable to find \"%s\"!", file);
	return;
    }

    if ((fp = XFOPEN(filename, "r")) == NULL) {
	XDVI_ERROR((stderr, "Cannot open config file `%s' for reading: %s", filename, strerror(errno)));
	return;
    }

    TRACE_T1((stderr, "Reading cfg file %s", filename));

    buffer = xmalloc(BUFFER_SIZE);

    while (fgets(buffer, BUFFER_SIZE, fp) != NULL) {
	len = strlen(buffer);
	if (buffer[len - 1] != '\n') {
	    int c;
	    /* this really shouldn't happen ... */
	    XDVI_WARNING((stderr, "Skipping overlong line (> %d characters) in config file `%s':\n%s ...",
			  BUFFER_SIZE, filename, buffer));
	    /* read until end of this line */
	    while((c = fgetc(fp)) != '\n' && c != EOF) { ; }
	    continue;
	}

	keyword = buffer;

	/* Skip leading whitespace */
	while (keyword[0] != '\0' && (keyword[0] == ' ' || keyword[0] == '\t'))
	    keyword++;

	/* % in first column is a correct comment */
	if (keyword[0] == '%' || keyword[0] == '\0' || keyword[0] == '\n')
	    continue;

	keyword = strtok(keyword, delim);

	if (strcmp(keyword, "dvipsmap") == 0) {
	    if ((ptr = strtok(NULL, delim)) == NULL) {
		XDVI_WARNING((stderr, "Syntax error in entry \"%s\"", buffer));
		continue;
	    }
	    TRACE_T1((stderr, "DVIPSMAP: '%s'", ptr));

	    if (read_map_file(ptr)) {
		found_no_map_files = False;
	    }
	    else {
		TRACE_T1((stderr, "Read map file: '%s'", ptr));
	    }
	}
	else if (strcmp(keyword, "encmap") == 0) {
	    popup_message(globals.widgets.top_level,
			  MSG_ERR,
			  "Your xdvi.cfg file is for a previous version of xdvik. Please replace "
			  "it by the xdvi.cfg file in the current xdvik distribution.",
			  "Keyword \"encmap\" in xdvi.cfg is no longer supported, "
			  "please update the config file %s.",
			  filename);
	}
	else if (strcmp(keyword, "enc") == 0) {
	    enc = strtok(NULL, delim);
	    name = strtok(NULL, delim);
	    if ((ptr = strtok(NULL, delim)) == NULL) {
		XDVI_WARNING((stderr, "Syntax error in entry \"%s\" (skipping line)", buffer));
		continue;
	    }
	    i = new_encoding(enc, ptr);
	    TRACE_T1((stderr, "Encoding[%d]: '%s' = '%s' -> '%s'", i, enc, name, ptr));
	} else {
	    /* again, nag them with a popup so that they'll do something about this ... */
	    popup_message(globals.widgets.top_level,
			  MSG_ERR,
			  "Please check the syntax of your config file. "
			  "Valid keywords are: \"enc\" and \"dvipsmap\".",
			  "Skipping unknown keyword \"%s\" in config file %s.",
			  keyword, filename);
	}
    }

    if (found_no_map_files) {
	/* nag 'em with a popup so that they'll do something about this ... */
	popup_message(globals.widgets.top_level,
		      MSG_ERR,
		      "Direct Type 1 font rendering via T1lib gives you many benefits, such as:\n"
		      " - quicker startup time, since no bitmap fonts need to be generated;\n"
		      " - saving disk space for storing the bitmap fonts.\n"
		      "To fix this error, check that the file `ps2pk.map' is located somewhere "
		      "in your XDVIINPUTS path. Have a look at the xdvi wrapper shell script "
		      "(type \"which xdvi\" to locate that shell script) for the current setting "
		      "of XDVIINPUTS.",
		      "Could not load any of the map files listed in xdvi.cfg - disabling T1lib.");
	resource.t1lib = False;
    }
    fclose(fp);

    free(buffer);
    free(filename);
}


/* **************************** GLYPH DRAWING *************************** */


/* Set character# ch */

static struct glyph *
get_t1_glyph(
#ifdef TEXXET
	     wide_ubyte cmd,
#endif
	     wide_ubyte ch, t1FontLoadStatusT *flag,
	     Boolean is_geom_scan)
{

    /*
      A problem is that there are some gaps in math modes tall braces '{',
      (see for example the amstex users guide).  These gaps are seen less if a
      small size factor is applied, 1.03 seems nice.  This does not seem like
      the Right Thing though.  Also gaps do not show up in the fullsize
      window.

      All in all the factor has been dropped.  Despite the beauty flaw.
    */

    float size = dvi_pt_conv(currinf.fontp->scale);

    int id = currinf.fontp->t1id;
    int t1libid = fontmaps[id].t1libid;

    GLYPH *G;		/* t1lib glyph */
    struct glyph *g;	/* xdvi glyph */

#ifdef TEXXET
    UNUSED(cmd);
#endif
    *flag = SUCCESS;

#if COLOR
    if (!is_geom_scan && fg_active != fg_current)
	do_color_change();
#endif

    /* return immediately if we need to use the fallback PK */
    if (fontmaps[id].force_pk)
	return NULL;

    TRACE_T1((stderr, "scale: %ld, ppi %d, sf: %d, size: %f",
	      currinf.fontp->scale, resource.pixels_per_inch,
	      currwin.shrinkfactor, size));

    if (t1libid == -1) {
	TRACE_T1((stderr, "trying to load font %d", id));
	*flag = load_font_now(id, currinf.fontp);
	if (*flag < 0) {
	    TRACE_T1((stderr, "load_font_now failed for T1 font %d", id));
	    return NULL;
	}
	t1libid = fontmaps[id].t1libid;
    }
    
    TRACE_T1((stderr, "Setting 0x%x `%c' of %d, at %ld(%.2fpt), shrinkage is %d",
	      ch,
	      isprint(ch) ? ch : '?',
	      t1libid, currinf.fontp->scale,
	      size, currwin.shrinkfactor));

    /* Check if the glyph already has been rendered */
    if ((g = &currinf.fontp->glyph[ch])->bitmap.bits == NULL) {
	int bitmapbytes;
	int h;
	unsigned char *f;
	unsigned char *t;
	/* Not rendered: Generate xdvi glyph from t1font */

	g->addr = 1; /* Dummy, should not provoke errors other places in xdvi */

	/* Use the width from the tfm file */
	g->dvi_adv = tfminfo[fontmaps[id].tfmidx].widths[ch] * currinf.fontp->dimconv;
	TRACE_T1((stderr, "0x%x, dvi_adv = %ld; dimconv: %f",
		  ch,
		  tfminfo[fontmaps[id].tfmidx].widths[ch],
		  currinf.fontp->dimconv));

	/* Render the glyph.  Size here should be postscript bigpoints */
	G = T1_SetChar(t1libid, ch, size, NULL);
	if (G == NULL) {
	    /* This can happen e.g. if font is too small; example with plain TeX:
	       \magnification=50 Hello, world!\end
	    */
	    statusline_print(STATUS_FOREVER,
			     "Error rendering character 0x%x `%c' - replacing by whitespace",
			     ch,
			     isprint(ch) ? ch : '?');
	    XDVI_ERROR((stderr, "T1lib failed for character 0x%x `%c': %s. Replacing by whitespace.",
			ch, isprint(ch) ? ch : '?',
			T1_StrError(T1_errno)));
	    g->bitmap.w = 1;
	    g->bitmap.h = 1;
	    g->bitmap.bytes_wide = T1PAD(g->bitmap.w, archpad) / 8;
	    alloc_bitmap(&g->bitmap);
	    memset(g->bitmap.bits, 0, g->bitmap.h * g->bitmap.bytes_wide);
	    g->x = 0;
	    g->y = 0;
#if COLOR
	    g->fg = fg_current;
#endif
	    return g;
	}

	if (G->bits == NULL) {
	    if (ch != ' ') {
		/* -- janl:
		   Blank glyph != .notdef (e.g., it could be a space char).
		   Drop message until I find out how .notdef can be detected.
		   -- SU 2003/02/24:
		   I changed this, since dropped characters is an important
		   thing to warn users about. We try to catch the space char
		   case by checking for space above (rather lame, since it won't
		   catch all fonts, but ...)
		   The only case in which TeX actually uses a space character seems
		   to be the output of `tex testfont', or in some CJK fonts
		   (e.g. gbksong25).
		*/
		char index[128];
		char *fontname = xstrdup(currinf.fontp->fontname);
		size_t dummy = 0;
		
		SNPRINTF(index, sizeof index, "%d", ch);
		fontname = xstrcat(fontname, index);
		if (font_warn_hash.size == 0) {
		    /* TODO: print to statusline */
		    /* 		    popup_message(MSG_WARN, */
		    /* 				     NULL, */
		    /* 				     "Unexpected blank character (.notdef) in font %s, " */
		    /* 				     "replacing with whitespace (see output to stderr for details). " */
		    /* 				     "This might indicate that the font %s is missing " */
		    /* 				     "from the font map, or that it has been specified " */
		    /* 				     "with a wrong encoding.", */
		    /* 				     currinf.fontp->fontname, currinf.fontp->fontname); */
		    font_warn_hash = hash_create(T1FONTS_INITIAL_HASHTABLE_SIZE);
		}
		
		if (!find_str_int_hash(&font_warn_hash, fontname, &dummy)) {
		    if (!resource.hush_chars)
			XDVI_WARNING((stderr, "Character %d is mapped to .notdef in font %s (page %d), "
				      "replacing by whitespace.",
				      ch, currinf.fontp->fontname, current_page + 1));
		    put_str_int_hash(&font_warn_hash, fontname, dummy);
		}
		else {
		    free(fontname);
		}
	    }
	    /* *flag = FAILURE_BLANK; */
	    /* g->addr = -1; */
	    /* return NULL; */
	    
	    /* FIXME: g->dvi_adv is only used when invoking set_char(), not with
	       set_empty_char(), and the former apparently needs a valid bitmap.
	       Check how this is done without t1lib.
	    */
	    TRACE_T1((stderr, "replacing .notdef char 0x%02x by space.\n", ch));
	    g->bitmap.w = 1;
	    g->bitmap.h = 1;
	    g->bitmap.bytes_wide = T1PAD(g->bitmap.w, archpad) / 8;
	    alloc_bitmap(&g->bitmap);
	    memset(g->bitmap.bits, 0, g->bitmap.h * g->bitmap.bytes_wide);
	    g->x = 0;
	    g->y = 0;
#if COLOR
	    g->fg = fg_current;
#endif
	    return g;
	}

	g->bitmap.w = abs(G->metrics.rightSideBearing - G->metrics.leftSideBearing);
	g->bitmap.h = abs(G->metrics.descent - G->metrics.ascent);
	g->bitmap.bytes_wide = T1PAD(g->bitmap.w, archpad) / 8;
	alloc_bitmap(&g->bitmap);

	bitmapbytes = g->bitmap.bytes_wide * g->bitmap.h;

	if (padMismatch) {
	    /* Fix alignment mismatch */
	    int from_bytes = T1PAD(g->bitmap.w, 8) / 8;

	    f = (unsigned char *)G->bits;
	    t = (unsigned char *)g->bitmap.bits;

	    memset(t, 0, bitmapbytes); /* OR ELSE! you get garbage */

	    for (h = 0 ; h < g->bitmap.h; h++) {
		memcpy(t, f, from_bytes);
		f += from_bytes;
		t += g->bitmap.bytes_wide;
	    }


	} else {
	    /* t1lib and arch alignment matches */
	    memcpy(g->bitmap.bits, G->bits, bitmapbytes);
	}

#ifdef WORDS_BIGENDIAN
	/* xdvi expects the bits to be reversed according to
	   endianness.  t1lib expects no such thing.  This loop reverses
	   the bit order in all the bytes. -janl 18/5/2001 */
	t = (unsigned char *)g->bitmap.bits;

	for (h = 0; h < bitmapbytes; h++) {
	    *t = rbits[*t];
	    t++;
	}
#endif

	g->x = -G->metrics.leftSideBearing;  /* Opposed x-axis */
	g->y = G->metrics.ascent;
#if COLOR
	g->fg = fg_current;
#endif
    }
    return g;
}

setcharRetvalT
set_t1_char(
#ifdef TEXXET
	    wide_ubyte cmd,
#endif
	    wide_ubyte ch)
{
    t1FontLoadStatusT flag;
    
    /*
      get_t1_glyph() will itself set currinf.fontp->set_char_p
      to set_empty_char() or set_char() if it failed to load the
      T1 version, but for the *current* call of set_t1_char,
      we still need to know whether (1) or (2) holds:
      (1) Type1 font hasn't been found but PK is working,
      use set_char() to set the PK version
      (2) neither Type1 nor PK have been found (this is the
      FAILURE_BLANK case), use set_empty_char().
      So we need the flag to pass this additional information.
    */
#ifdef TEXXET      
    (void)get_t1_glyph(cmd, ch, &flag, False);
    if (flag == FAILURE_BLANK)
	set_empty_char(cmd, ch);
    else
	set_char(cmd, ch);
    return;
#else
    (void)get_t1_glyph(ch, &flag, False);
    if (flag == FAILURE_BLANK)
	return set_empty_char(ch);
    else
	return set_char(ch);
#endif
}

void read_T1_char(struct font *fontp, wide_ubyte ch)
{
    UNUSED(fontp);
    UNUSED(ch);
    /* Should never be called */
    XDVI_ABORT((stderr, "%s:%d: asked to load %c", __FILE__, __LINE__, ch));
}

void init_t1(void)
{
    int i;
    void *success;
    /* Attempt to set needed padding.

       FIXME: This is not really the required alignment/padding.  On
       ix86 the requirement for int * is 8 bits, on sparc on the other
       hand it's 32 bits.  Even so, some operations will be faster if
       the bitmaps lines are alligned "better".  But on the other hand
       this requires a more complex glyph copying operation...

       - janl 16/5/2001
    */
    archpad = BMBYTES * 8;
    i = T1_SetBitmapPad(archpad);
    if (i == -1) {
	/* Failed, revert to 8 bits and compilicated bitmap copying */
	padMismatch = True;
	T1_SetBitmapPad(8);
    } else {
	padMismatch = False;
    }

    /* Initialize t1lib, use LOGFILE to get logging, NO_LOGFILE otherwise */
    /* Under NO circumstances remove T1_NO_AFM, it slows us down a LOT */

    if (globals.debug & DBG_T1) {
	XDVI_INFO((stdout, "Additional t1lib messages may be found in \"t1lib.log\"."));
	success = T1_InitLib(LOGFILE | T1_NO_AFM | IGNORE_CONFIGFILE | IGNORE_FONTDATABASE);
    }
    else
	success = T1_InitLib(NO_LOGFILE | T1_NO_AFM | IGNORE_CONFIGFILE | IGNORE_FONTDATABASE);
    if (success == NULL) {
	XDVI_FATAL((stderr, "Initialization of t1lib failed!"));
    }
    T1_SetLogLevel(T1LOG_DEBUG);

    if (resource.subpixel_order == SUBPIXEL_NONE)
	T1_SetDeviceResolutions(BDPI, BDPI);
    else
	T1_SetDeviceResolutions(3 * BDPI, BDPI);

    read_cfg_file(NULL);
}

#endif /* T1LIB */


#if GREY

extern	double	pow();

static void
mask_shifts(Pixel mask, int *pshift1, int *pshift2)
{
    int k, l;

    for (k = 0; (mask & 1) == 0; ++k)
	mask >>= 1;
    for (l = 0; (mask & 1) == 1; ++l)
	mask >>= 1;
    *pshift1 = sizeof(short) * 8 - l;
    *pshift2 = k;
}

/*
 *	Try to allocate 4 color planes for 16 colors (for GXor drawing).
 *	Not called if the visual type is TrueColor.
 *	When color specials are active, this is called exactly once.
 *	It is used for the first foreground/background pair displayed in
 *	the document.  For other documents, we act as if this call had failed.
 */


void
init_plane_masks(void)
{
    Pixel pixel;

    if (globals.gc.do_copy || plane_masks[0] != 0)
	return;

    if (XAllocColorCells(DISP, G_colormap, False, plane_masks, 4, &pixel, 1)) {
	/* Make sure fore_Pixel and back_Pixel are a part of the palette */
	resource.back_Pixel = pixel;
	resource.fore_Pixel = pixel | plane_masks[0] | plane_masks[1]
	    | plane_masks[2] | plane_masks[3];
	if (mane.win != (Window) 0)
	    XSetWindowBackground(DISP, mane.win, resource.back_Pixel);
    }
    else {
	globals.gc.do_copy = True;
	warn_overstrike();
    }
}

#endif /* GREY */

#if COLOR

/*
 *	Insert into list of colors to be freed upon opening new document.
 */

static	void
color_list_insert(Pixel pixel)
{
    if (color_list_len >= color_list_max) {
	if (color_list_max == 0)
	    color_list = xmalloc((color_list_max += 16) * sizeof *color_list);
	else
	    color_list = xrealloc(color_list,
				  (color_list_max += 16) * sizeof *color_list);
    }
    color_list[color_list_len++] = pixel;
}


/*
 *	Warn about colors not being correct.
 */

static	void
color_warn(void)
{
    if (!color_warned) {
	color_warned = True;
	popup_message(globals.widgets.top_level,
		      MSG_WARN,
		      /* helptext */
		      "Either this document is using too many "
		      "colors, or some other application is. "
		      "In the latter case, try to close that "
		      "application and re-read the DVI file.",
		      /* msg */
		      "Cannot allocate colormap entry, "
		      "displayed colors will not be exact.");
    }
}


/*
 *	Allocate a color and add it to our list of pixels to be returned
 *	upon opening a new document.
 */

#define	SHIFTIFY(x, shift1, shift2)	((((Pixel)(x)) >> (shift1)) << (shift2))

static	int	shift1_r, shift1_g, shift1_b;
static	int	shift2_r, shift2_g, shift2_b;
static	Boolean	shifts_good	= False;

Pixel
alloc_color(const struct rgb *colorp, Pixel fallback_pixel)
{
    XColor xcol;

    if (G_visual->class == TrueColor) {
	if (!shifts_good) {
	    mask_shifts(G_visual->red_mask,   &shift1_r, &shift2_r);
	    mask_shifts(G_visual->green_mask, &shift1_g, &shift2_g);
	    mask_shifts(G_visual->blue_mask,  &shift1_b, &shift2_b);
	    shifts_good = True;
	}
	return SHIFTIFY(colorp->r, shift1_r, shift2_r)
	    | SHIFTIFY(colorp->g, shift1_g, shift2_g)
	    | SHIFTIFY(colorp->b, shift1_b, shift2_b);
    }
    else {
	xcol.red = colorp->r;
	xcol.green = colorp->g;
	xcol.blue = colorp->b;
	xcol.flags = DoRed | DoGreen | DoBlue;
	if (XAllocColor(DISP, G_colormap, &xcol)) {
	    color_list_insert(xcol.pixel);
	    if (globals.debug & DBG_DVI)
		printf("alloc_color%6d%6d%6d --> %ld\n",
		       xcol.red, xcol.green, xcol.blue, xcol.pixel);
	    return xcol.pixel;
	}
	else {
	    if (globals.debug & DBG_DVI)
		printf("alloc_color%6d%6d%6d --> failed\n",
		       xcol.red, xcol.green, xcol.blue);
	    color_warn();
	    return fallback_pixel;
	}
    }
}

#undef SHIFTIFY


/*
 *	Switch colors of GCs, etc.  Called when we're about to use a GC.
 */

void
do_color_change(void)
{
    static int shrink_allocated_for = 0;
    Pixel set_bits;
    Pixel clr_bits;
    
    ASSERT(fg_current != NULL, "Current foreground mustn't be NULL");
    ASSERT(bg_current != NULL, "Current background mustn't be NULL");
#if GREY
    if (resource.use_grey) {
	if (G_visual->class == TrueColor) {
	    if (!fg_current->pixel_good) {
		fg_current->pixel = alloc_color(&fg_current->color,
						color_data[0].pixel);
		fg_current->pixel_good = True;
	    }

	    set_bits = fg_current->pixel & ~bg_current->pixel;
	    clr_bits = bg_current->pixel & ~fg_current->pixel;

	    if (set_bits & G_visual->red_mask)
		set_bits |= G_visual->red_mask;
	    if (clr_bits & G_visual->red_mask)
		clr_bits |= G_visual->red_mask;
	    if (set_bits & G_visual->green_mask)
		set_bits |= G_visual->green_mask;
	    if (clr_bits & G_visual->green_mask)
		clr_bits |= G_visual->green_mask;
	    if (set_bits & G_visual->blue_mask)
		set_bits |= G_visual->blue_mask;
	    if (clr_bits & G_visual->blue_mask)
		clr_bits |= G_visual->blue_mask;

	    /*
	     * Make the GCs
	     */

	    globals.gc.rule = set_or_make_gc(globals.gc.rule, GXcopy, fg_current->pixel, bg_current->pixel);
	    globals.gc.fore2 = NULL;

	    if (resource.copy
		|| (set_bits && clr_bits && !resource.thorough)) {
		if (!resource.copy) {
		    warn_overstrike();
		}
		globals.gc.fore = set_or_make_gc(globals.gc.fore, GXcopy, fg_current->pixel, bg_current->pixel);
	    }
	    else {
		if (set_bits) {
		    globals.gc.fore = set_or_make_gc(globals.gc.fore, GXor, fg_current->pixel & set_bits, 0);
		    if (clr_bits) {
			globals.gc.fore2 = globals.gc.fore2_bak = set_or_make_gc(globals.gc.fore2_bak, GXandInverted,
							       ~fg_current->pixel & clr_bits, 0);
		    }
		}
		else {
		    globals.gc.fore = set_or_make_gc(globals.gc.fore, GXandInverted,
					    ~fg_current->pixel & clr_bits, 0);
		}
	    }

	    if (globals.debug & DBG_DVI)
		printf("do_color_change: fg = %lx, bg = %lx, with%s globals.gc.fore2\n",
		       fg_current->pixel, bg_current->pixel,
		       globals.gc.fore2 == NULL ? "out" : "");

	    if (mane.shrinkfactor > 1) {
		unsigned int i;
		unsigned int sf_squared;

		sf_squared = mane.shrinkfactor * mane.shrinkfactor;

		if (shrink_allocated_for < mane.shrinkfactor) {
		    if (pixeltbl != NULL) {
			free((char *) pixeltbl);
			if (pixeltbl_gc2 != NULL) {
			    free((char *) pixeltbl_gc2);
			    pixeltbl_gc2 = NULL;
			}
		    }
		    pixeltbl = xmalloc((sf_squared + 1) * sizeof *pixeltbl);
		    shrink_allocated_for = mane.shrinkfactor;
		}
		if (globals.gc.fore2 != NULL && pixeltbl_gc2 == NULL) {
		    /* Can't use sf_squared (or mane.shrinkfactor) here */
		    pixeltbl_gc2 = xmalloc((shrink_allocated_for * shrink_allocated_for + 1) * sizeof *pixeltbl_gc2);
		}

		/*
		 * Initialize the pixel lookup table according to the gamma values.
		 */
#define	SHIFTIFY(x, shift1, shift2)	((((Pixel)(x)) >> (shift1)) << (shift2))

		for (i = 0; i <= sf_squared; ++i) {
		    double frac = resource.gamma > 0
			? pow((double)i / sf_squared, 1 / resource.gamma)
			: 1 - pow((double) (sf_squared - i) / sf_squared, -resource.gamma);
		    unsigned int red, green, blue;
		    Pixel pixel;
		    /*  		    fprintf(stderr, "frac: %f\n", frac); */
/* 		    fprintf(stderr, "fg_current: %d, bg_current: %d\n", fg_current->color.r, bg_current->color.r); */
		    red = frac
			* ((double) fg_current->color.r - bg_current->color.r)
			+ bg_current->color.r;
		    green = frac
			* ((double) fg_current->color.g - bg_current->color.g)
			+ bg_current->color.g;
		    blue = frac
			* ((double) fg_current->color.b - bg_current->color.b)
			+ bg_current->color.b;

		    pixel = SHIFTIFY(red,   shift1_r, shift2_r)
			| SHIFTIFY(green, shift1_g, shift2_g)
			| SHIFTIFY(blue,  shift1_b, shift2_b);

		    if (globals.gc.fore2 != NULL) {	/* if thorough */
			/*	fprintf(stderr, "++++++ pixeltable at %d: %ld\n", i, pixel & ~bg_current->pixel); */
			pixeltbl[i] = pixel & ~bg_current->pixel;
			pixeltbl_gc2[i] = ~pixel & bg_current->pixel;
		    }
		    else if (resource.copy || (set_bits && clr_bits)) {
			/*	fprintf(stderr, "++++++ pixeltable2 at %d: %ld\n", i, pixel); */
			pixeltbl[i] = pixel;
		    }
		    else {
			/*	fprintf(stderr, "++++++ pixeltable3 at %d: 0x%lx\n", i, ~pixel & clr_bits); */
			/*	fprintf(stderr, "++++++ pixeltable3 at %d: %ld\n", i, pixel & set_bits); */
			pixeltbl[i] = set_bits ? pixel & set_bits : ~pixel & clr_bits;
		    }
		}
#undef	SHIFTIFY
	    }

	}
	else {	/* not TrueColor */
	    int	i;
	    Boolean	using_planes;

	    using_planes = (fg_current == bg_head->fg_head && !globals.gc.do_copy);
	    if (!fg_current->palette_good) {
		XColor color;

		/*
		 * Initialize the pixel lookup table according to the gamma values.
		 */
		for (i = 0; i < 16; ++i) {
		    double frac;

		    frac = resource.gamma > 0
			? pow((double) i / 15, 1 / resource.gamma)
			: 1 - pow((double) (15 - i) / 15, -resource.gamma);
		    color.red = frac
			* ((double) fg_current->color.r - bg_current->color.r)
			+ bg_current->color.r;
		    color.green = frac
			* ((double) fg_current->color.g - bg_current->color.g)
			+ bg_current->color.g;
		    color.blue = frac
			* ((double) fg_current->color.b - bg_current->color.b)
			+ bg_current->color.b;

		    color.flags = DoRed | DoGreen | DoBlue;

		    if (using_planes) {
			color.pixel = resource.back_Pixel;	/* start of block */
			if (i & 1) color.pixel |= plane_masks[0];
			if (i & 2) color.pixel |= plane_masks[1];
			if (i & 4) color.pixel |= plane_masks[2];
			if (i & 8) color.pixel |= plane_masks[3];
			XStoreColor(DISP, G_colormap, &color);
			fg_current->palette[i] = color.pixel;
		    }
		    else {
			if (XAllocColor(DISP, G_colormap, &color)) {
			    fg_current->palette[i] = color.pixel;
			    color_list_insert(color.pixel);
			}
			else {
			    color_warn();
			    fg_current->palette[i] =
				(i * 100 >= resource.density * 15)
				? resource.fore_Pixel : bg_current->pixel;
			}
		    }
		}

		if (using_planes && bg_current->pixel != resource.back_Pixel) {
		    bg_current->pixel = resource.back_Pixel;
/* 		    XSetWindowBackground(DISP, mane.win, bg_current->pixel); */
#if MOTIF
		    fprintf(stderr, "setting window background!\n");
		    XSetWindowBackground(DISP, XtWindow(globals.widgets.main_window), bg_current->pixel);
		    XtVaSetValues(globals.widgets.main_window, XmNbackground, bg_current->pixel, NULL);
#else
		    XSetWindowBackground(DISP, mane.win, bg_current->pixel);
#endif
		    XClearWindow(DISP, mane.win);
		}

		fg_current->palette_good = True;
	    }

	    if (globals.debug & DBG_DVI)
		printf("do_color_change: fg = %ld, bg = %ld, using_planes = %d\n",
		       fg_current->palette[15], bg_current->pixel, using_planes);

	    if (using_planes) {
		globals.gc.rule = set_or_make_gc(globals.gc.rule, GXor, fg_current->palette[15],
					bg_current->pixel);
		globals.gc.fore = set_or_make_gc(globals.gc.fore, GXor, fg_current->palette[15],
					bg_current->pixel);
	    }
	    else {
		globals.gc.rule = set_or_make_gc(globals.gc.rule, GXcopy, fg_current->palette[15],
					bg_current->pixel);
		globals.gc.fore = set_or_make_gc(globals.gc.fore, GXcopy, fg_current->palette[15],
					bg_current->pixel);
	    }

	    globals.gc.fore2 = NULL;

	    if (mane.shrinkfactor > 1) {
		if (shrink_allocated_for < mane.shrinkfactor) {
		    if (pixeltbl != NULL)
			free((char *) pixeltbl);
		    pixeltbl = xmalloc((unsigned)(mane.shrinkfactor * mane.shrinkfactor + 1)
				       * sizeof *pixeltbl);
		    shrink_allocated_for = mane.shrinkfactor;
		}

		for (i = 0; i <= mane.shrinkfactor * mane.shrinkfactor; ++i) {
		    pixeltbl[i] = fg_current->palette[(i * 30 + mane.shrinkfactor * mane.shrinkfactor)
						     / (2 * mane.shrinkfactor * mane.shrinkfactor)];
		}
	    }
	}
    }	/* end if resource.use_grey */
    else
#endif /* GREY */
	{
	    if (!fg_current->pixel_good) {
		fg_current->pixel = alloc_color(&fg_current->color,
						color_data[0].pixel);
		fg_current->pixel_good = True;
	    }

	    if (globals.debug & DBG_DVI)
		printf("do_color_change: fg = %lx, bg = %lx\n",
		       fg_current->pixel, bg_current->pixel);

	    globals.gc.rule = set_or_make_gc(globals.gc.rule, GXcopy, fg_current->pixel, bg_current->pixel);

	    set_bits = (Pixel) (fg_current->pixel & ~bg_current->pixel);
	    clr_bits = (Pixel) (bg_current->pixel & ~fg_current->pixel);
	    globals.gc.fore2 = NULL;

	    if (resource.copy
		|| (set_bits && clr_bits && !resource.thorough)) {
		if (!resource.copy) {
		    /* I used to get a warning here which I didn't get for
		       xdvi-22.64/events.c, l.1330, but I can't reproduce
		       it any more ...
		    */
		    warn_overstrike();
		}
		globals.gc.fore = set_or_make_gc(globals.gc.fore, GXcopy, fg_current->pixel, bg_current->pixel);
	    }
	    else {
		if (set_bits) {
		    globals.gc.fore = set_or_make_gc(globals.gc.fore, GXor, set_bits, 0);
		    if (clr_bits) {
			globals.gc.fore2 = globals.gc.fore2_bak1 = set_or_make_gc(globals.gc.fore2_bak1, GXandInverted, clr_bits, 0);
		    }
		}
		else
		    globals.gc.fore = set_or_make_gc(globals.gc.fore, GXandInverted, clr_bits, 0);
	    }
	}

    fg_active = fg_current;
}

#elif GREY /* if COLOR */

void
init_pix(void)
{
    static int shrink_allocated_for = 0;
    static float oldgamma = 0.0;
    static Pixel palette[17];
    int i;

    if (G_visual->class == TrueColor) {
	/* This mirrors the non-grey code in xdvi.c */
	static int shift1_r, shift1_g, shift1_b;
	static int shift2_r, shift2_g, shift2_b;
	static Pixel set_bits;
	static Pixel clr_bits;
	unsigned int sf_squared;

	if (oldgamma == 0.0) {
	    mask_shifts(G_visual->red_mask, &shift1_r, &shift2_r);
	    mask_shifts(G_visual->green_mask, &shift1_g, &shift2_g);
	    mask_shifts(G_visual->blue_mask, &shift1_b, &shift2_b);

	    set_bits = color_data[0].pixel & ~(color_data[1].pixel);
	    clr_bits = color_data[1].pixel & ~(color_data[0].pixel);

	    if (set_bits & G_visual->red_mask)
		set_bits |= G_visual->red_mask;
	    if (clr_bits & G_visual->red_mask)
		clr_bits |= G_visual->red_mask;
	    if (set_bits & G_visual->green_mask)
		set_bits |= G_visual->green_mask;
	    if (clr_bits & G_visual->green_mask)
		clr_bits |= G_visual->green_mask;
	    if (set_bits & G_visual->blue_mask)
		set_bits |= G_visual->blue_mask;
	    if (clr_bits & G_visual->blue_mask)
		clr_bits |= G_visual->blue_mask;

	    /*
	     * Make the GCs
	     */

	    globals.gc.fore = globals.gc.fore2 = globals.gc.rule = 0;
	    globals.gc.copy = set_or_make_gc(NULL, GXcopy, resource.fore_Pixel, resource.back_Pixel);
	    if (globals.gc.do_copy || (set_bits && clr_bits)) {
		globals.gc.rule = globals.gc.copy;
		if (!resource.thorough)
		    globals.gc.do_copy = True;
	    }
	    if (globals.gc.do_copy) {
		globals.gc.fore = globals.gc.rule;
		if (!resource.copy) {
		    warn_overstrike();
		}
	    }
	    else {
		if (set_bits) {
		    globals.gc.fore = set_or_make_gc(NULL, GXor, set_bits & color_data[0].pixel, 0);
		}
		if (clr_bits || !set_bits) {
/* 		    fprintf(stderr, "using GXandInverted!\n"); */
		    *(globals.gc.fore ? &globals.gc.fore2 : &globals.gc.fore) =
			set_or_make_gc(NULL, GXandInverted, clr_bits & ~(color_data[0].pixel), 0);
		}
		if (!globals.gc.rule)
		    globals.gc.rule = globals.gc.fore;
	    }

	    oldgamma = resource.gamma;
	}

	if (mane.shrinkfactor == 1)
	    return;
	sf_squared = mane.shrinkfactor * mane.shrinkfactor;

	if (shrink_allocated_for < mane.shrinkfactor) {
	    if (pixeltbl != NULL) {
		free((char *)pixeltbl);
		if (pixeltbl_gc2 != NULL)
		    free((char *)pixeltbl_gc2);
	    }
	    pixeltbl = xmalloc((sf_squared + 1) * sizeof *pixeltbl);
	    shrink_allocated_for = mane.shrinkfactor;
	    if (globals.gc.fore2 != NULL) {
		pixeltbl_gc2 = xmalloc((sf_squared + 1) * sizeof *pixeltbl_gc2);
	    }
	}

	/*
	 * Initialize the pixel lookup table according to the gamma values.
	 */
#define	SHIFTIFY(x, shift1, shift2)	((((Pixel)(x)) >> (shift1)) << (shift2))

	for (i = 0; i <= sf_squared; ++i) {
	    double frac = resource.gamma > 0
		? pow((double)i / sf_squared, 1 / resource.gamma)
		: 1 - pow((double)(sf_squared - i) / sf_squared, -resource.gamma);
	    unsigned int red, green, blue;
	    Pixel pixel;

	    red = frac * ((double)color_data[0].red - color_data[1].red)
		+ color_data[1].red;
	    green = frac
		* ((double)color_data[0].green - color_data[1].green)
		+ color_data[1].green;
	    blue = frac * ((double)color_data[0].blue - color_data[1].blue)
		+ color_data[1].blue;

	    pixel = SHIFTIFY(red, shift1_r, shift2_r) |
		SHIFTIFY(green, shift1_g, shift2_g) |
		SHIFTIFY(blue, shift1_b, shift2_b);

	    if (globals.gc.do_copy) {
		pixeltbl[i] = pixel;
	    }
	    else if (globals.gc.fore2 != NULL) {	/* if thorough */
		pixeltbl[i] = pixel & ~(color_data[1].pixel);
		pixeltbl_gc2[i] = ~pixel & color_data[1].pixel;
	    }
	    else {
		pixeltbl[i] = set_bits ? pixel & set_bits : ~pixel & clr_bits;
	    }
	}

#undef	SHIFTIFY

	return;
    }

    /* if not TrueColor ... */

    if (resource.gamma != oldgamma) {
	XColor color;

	for (i = 0; i < 16; ++i) {
	    double frac = resource.gamma > 0
		? pow((double)i / 15, 1 / resource.gamma)
		: 1 - pow((double)(15 - i) / 15, -resource.gamma);

	    color.red = frac
		* ((double)color_data[0].red - color_data[1].red)
		+ color_data[1].red;
	    color.green = frac
		* ((double)color_data[0].green - color_data[1].green)
		+ color_data[1].green;
	    color.blue = frac
		* ((double)color_data[0].blue - color_data[1].blue)
		+ color_data[1].blue;

	    color.pixel = resource.back_Pixel;
	    color.flags = DoRed | DoGreen | DoBlue;

	    if (!globals.gc.do_copy) {
		if (i & 1)
		    color.pixel |= plane_masks[0];
		if (i & 2)
		    color.pixel |= plane_masks[1];
		if (i & 4)
		    color.pixel |= plane_masks[2];
		if (i & 8)
		    color.pixel |= plane_masks[3];
		XStoreColor(DISP, G_colormap, &color);
		palette[i] = color.pixel;
	    }
	    else {
		if (XAllocColor(DISP, G_colormap, &color))
		    palette[i] = color.pixel;
		else
		    palette[i] = (i * 100 >= resource.density * 15)
			? resource.fore_Pixel : resource.back_Pixel;
	    }
	}

	globals.gc.copy = set_or_make_gc(NULL, GXcopy, resource.fore_Pixel, resource.back_Pixel);
	globals.gc.rule = globals.gc.do_copy
	    ? globals.gc.copy
	    : set_or_make_gc(NULL, GXor, resource.fore_Pixel, resource.back_Pixel);
	globals.gc.fore = globals.gc.rule;
	globals.gc.fore2 = NULL;
	oldgamma = resource.gamma;
    }

    if (mane.shrinkfactor == 1)
	return;

    if (shrink_allocated_for < mane.shrinkfactor) {
	if (pixeltbl != NULL)
	    free((char *)pixeltbl);
	pixeltbl = xmalloc((unsigned)
			   (mane.shrinkfactor * mane.shrinkfactor + 1) * sizeof *pixeltbl);
	shrink_allocated_for = mane.shrinkfactor;
    }

    for (i = 0; i <= mane.shrinkfactor * mane.shrinkfactor; ++i) {
	pixeltbl[i] = palette[(i * 30 + mane.shrinkfactor * mane.shrinkfactor)
			     / (2 * mane.shrinkfactor * mane.shrinkfactor)];
    }
}

#endif /* COLOR */
