% $Id$ % MetaPost command-line program, by Taco Hoekwater. Public domain. \def\title{Creating mpx files} \def\hang{\hangindent 3em\indent\ignorespaces} \def\MP{MetaPost} \def\LaTeX{{\rm L\kern-.36em\raise.3ex\hbox{\sc a}\kern-.15em T\kern-.1667em\lower.7ex\hbox{E}\kern-.125emX}} \def\(#1){} % this is used to make section names sort themselves better \def\9#1{} % this is used for sort keys in the index \def\[#1]{#1.} \pdfoutput=1 @* \[1] Makempx overview. This source file implements the makempx functionality for the new \MP. It includes all of the functional code from the old standalone programs \item{}mpto \item{}dmp \item{}dvitomp \item{}makempx combined into one, with many changes to make all of the code cooperate nicely. The |banner| string defined here should be changed whenever \.{makempx} gets modified. @d banner "%% Written by makempx, Version 1.006\n" /* the first line of any output file */ @ Header files The local C preprocessor definitions have to come after the C includes in order to prevent name clashes. @c #include "config.h" #include #include #include #include #include #include #include /* TODO autoconf ? */ /* unistd.h is needed for every non-Win32 platform, and we assume * that implies that sys/types.h is also present */ #ifndef WIN32 #include #include #endif /* processes */ #ifdef WIN32 #include #include #else #if HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #endif /* directories */ #ifdef WIN32 #include #else #if HAVE_DIRENT_H # include #else # define dirent direct # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif #endif #if HAVE_SYS_STAT_H #include #endif #include #include #include #define trunc(x) ((integer) (x)) #define fabs(x) ((x)<0?(-(x)):(x)) #define floor(x) ((integer) (fabs(x))) #ifndef PI #define PI 3.14159265358979323846 #endif #include "avl.h" #include "mpxout.h" @h @ Data types From the Pascal code of DVItoMP two implicit types are inherited: |boolean| and |integer|. The more complex datatypes are defined in the following sections. @d true 1 @d false 0 @c typedef signed int integer; typedef signed int boolean; @ @ @ The single most important data structure is the structure |mpx_data|. It contains all of the global state for a specific |makempx| run. A pointer to it is passed as the first argument to just about every function call. One of the fields is a bit special because it is so important: |mode| is the decider between running \TeX\ or Troff as the typesetting engine. @(mpxout.h@>= typedef enum { mpx_tex_mode=0, mpx_troff_mode=1 } mpx_modes; typedef struct mpx_data * MPX; @ @= @ typedef struct mpx_data { int mode; @ } mpx_data ; @ Here are some macros for common programming idioms. @d MAXINT 0x7FFFFF /* somewhat arbitrary */ @d incr(A) (A)=(A)+1 /* increase a variable by unity */ @d decr(A) (A)=(A)-1 /* decrease a variable by unity */ @ Once an MPX object is allocated, the memory it occupies needs to be initialized to a usable state. This procedure gets things started properly. This function is not allowed to run |mpx_abort| because at this point the jump buffer is not yet initialized, so it should only be used for things that cannot go wrong! @c static void mpx_initialize (MPX mpx) { memset(mpx,0,sizeof(struct mpx_data)); @@/ } @ A global variable |history| keeps track of what type of errors have occurred with the hope that that \MP\ can be warned of any problems. @= enum mpx_history_states { mpx_spotless=0, /* |history| value when no problems have been found */ mpx_cksum_trouble, /* |history| value there have been font checksum mismatches */ mpx_warning_given, /* |history| value after a recoverable error */ mpx_fatal_error /* |history| value if processing had to be aborted */ }; @ @= int history; @ @= mpx->history=mpx_spotless; @ The structure has room for the names and the |FILE *| for the input and output files. The most important ones are listed here, the variables for the intermediate files are declared where they are needed. @= char *mpname; FILE *mpfile; char *mpxname; FILE *mpxfile; FILE *errfile; int lnno ; /* current line number */ @ A set of basic reporting functions. @c static void mpx_printf(MPX mpx, char *header, char *msg, va_list ap) { fprintf(mpx->errfile, "makempx %s: %s:", header, mpx->mpname); if (mpx->lnno!=0) fprintf(mpx->errfile, "%d:", mpx->lnno); fprintf(mpx->errfile, " "); vfprintf(mpx->errfile, msg, ap); fprintf(mpx->errfile, "\n"); } @ @c static void mpx_report(MPX mpx, char *msg, ...) { va_list ap; if (!mpx->debug) return; va_start(ap, msg); mpx_printf(mpx, "debug", msg, ap); va_end(ap); if ( mpx->history < mpx_warning_given ) mpx->history=mpx_cksum_trouble; } @ @c static void mpx_warn(MPX mpx, char *msg, ...) { va_list ap; va_start(ap, msg); mpx_printf(mpx, "warning", msg, ap); va_end(ap); if ( mpx->history < mpx_warning_given ) mpx->history=mpx_cksum_trouble; } @ @c static void mpx_error(MPX mpx, char *msg, ...) { va_list ap; va_start(ap, msg); mpx_printf(mpx, "error", msg, ap); va_end(ap); mpx->history=mpx_warning_given; } @ The program uses a |jump_buf| to handle non-local returns, this is initialized at a single spot: the start of |mp_makempx|. @d mpx_jump_out longjmp(mpx->jump_buf,1) @= jmp_buf jump_buf; @ @c static void mpx_abort(MPX mpx, char *msg, ...) { va_list ap; va_start(ap, msg); mpx_printf(mpx, "fatal", msg, ap); va_end(ap); mpx->history=mpx_fatal_error; mpx_erasetmp(mpx); mpx_jump_out; } @ @= if (setjmp(mpx->jump_buf) != 0) { int h = mpx->history; xfree(mpx->buf); xfree(mpx->maincmd); xfree(mpx->mpname); xfree(mpx->mpxname); xfree(mpx); return h; } @ @c static FILE *mpx_xfopen (MPX mpx, char *fname, char *fmode) { FILE *f = fopen(fname,fmode); if (f == NULL) mpx_abort(mpx,"File open error for %s in mode %s", fname, fmode); return f; } static void mpx_fclose (MPX mpx, FILE *file) { (void)mpx; (void)fclose(file); } @ @d xfree(A) do { mpx_xfree(A); A=NULL; } while (0) @d xrealloc(P,A,B) mpx_xrealloc(mpx,P,A,B) @d xmalloc(A,B) mpx_xmalloc(mpx,A,B) @d xstrdup(A) mpx_xstrdup(mpx,A) @= static void mpx_xfree (void *x); static void *mpx_xrealloc (MPX mpx, void *p, size_t nmem, size_t size) ; static void *mpx_xmalloc (MPX mpx, size_t nmem, size_t size) ; static char *mpx_xstrdup(MPX mpX, const char *s); @ The |max_size_test| guards against overflow, on the assumption that |size_t| is at least 31bits wide. @d max_size_test 0x7FFFFFFF @c static void mpx_xfree (void *x) { if (x!=NULL) free(x); } static void *mpx_xrealloc (MPX mpx, void *p, size_t nmem, size_t size) { void *w ; if ((max_size_test/size) T.st_mtim.tv_sec || \ (S.st_mtim.tv_sec == T.st_mtim.tv_sec && \ S.st_mtim.tv_nsec >= T.st_mtim.tv_nsec)) #else #define ISNEWER(S,T) (S.st_mtime >= T.st_mtime) #endif static int mpx_newer(char *source, char *target) { struct stat source_stat, target_stat; #if HAVE_SYS_STAT_H if (stat(target, &target_stat) < 0) return 0; /* true */ if (stat(source, &source_stat) < 0) return 1; /* false */ if (ISNEWER(source_stat, target_stat)) return 0; #endif return 1; } @* Extracting data from \MP\ input. This part of the program transforms a \MP\ input file into a \TeX\ or troff input file by stripping out \.{btex}$\ldots$\.{etex} and \.{verbatimtex}$\ldots$\.{etex} sections. Leading and trailing spaces and tabs are removed from the extracted material and it is surrounded by the preceding and following strings defined immediately below. The input file should be given as argument 1 and the resulting \TeX\ or troff file is written on standard output. John Hobby wrote the original version, which has since been extensively altered. The current implementation is a bit trickier than I would like, but changing it will take careful study and will likely make it run slower, so I've left it as-is for now. @= int texcnt ; /* btex..etex blocks so far */ int verbcnt ; /* verbatimtex..etex blocks so far */ char *bb, *tt, *aa; /* start of before, token, and after strings */ char *buf; /* the input line */ unsigned bufsize; @ @= mpx->bufsize = 1000; @ This function returns NULL on EOF, otherwise it returns |buf|. TODO: It fails to detect a partial last line (missing newline) @c static char *mpx_getline(MPX mpx, FILE *mpfile) { int c; unsigned loc = 0; if (mpx->buf==NULL) mpx->buf = xmalloc(mpx->bufsize,1); while ((c = getc(mpfile)) != EOF && c != '\n' && c != '\r') { mpx->buf[loc++] = c; if (loc == mpx->bufsize) { char *temp = mpx->buf; unsigned n = mpx->bufsize + (mpx->bufsize>>4); if (n>MAXINT) mpx_abort(mpx,"Line is too long"); mpx->buf = xmalloc(n,1); memcpy(mpx->buf,temp,mpx->bufsize); free(temp); mpx->bufsize = n; } } if (c == EOF) return NULL; mpx->buf[loc] = 0; if (c == '\r') { c = getc(mpfile); if (c != '\n') ungetc(c, mpfile); } mpx->lnno++; return mpx->buf; } @ Return nonzero if a prefix of string $s$ matches the null-terminated string $t$ and the next character is not a letter or an underscore. @c static int mpx_match_str(char *s, char *t) { while (*t != 0) { if (*s != *t) return 0; s++; t++; } if ((*s>= 'a' && *s<='z') || (*s>= 'A' && *s<='Z') || *s == '_') return 0; return 1; } @ This function tries to express $s$ as the concatenation of three strings $b$, $t$, $a$, with the global pointers $bb$, $tt$, and $aa$ set to the start of the corresponding strings. String $t$ is either a quote mark, a percent sign, or an alphabetic token \.{btex}, \.{etex}, or \.{verbatimtex}. (An alphabetic token is a maximal sequence of letters and underscores.) If there are several possible substrings $t$, we choose the leftmost one. If there is no such $t$, we set $b=s$ and return 0. @c static int mpx_getbta(MPX mpx, char *s) { int ok = 1; /* zero if last character was |a-z|, |A-Z|, or |_| */ mpx->bb = s; for (mpx->tt = mpx->bb; *(mpx->tt) != 0; mpx->tt++) { switch (*(mpx->tt)) { case '"': case '%': mpx->aa = mpx->tt + 1; return 1; case 'b': if (ok && mpx_match_str(mpx->tt, "btex")) { mpx->aa = mpx->tt + 4; return 1; } else ok = 0; break; case 'e': if (ok && mpx_match_str(mpx->tt, "etex")) { mpx->aa = mpx->tt + 4; return 1; } else ok = 0; break; case 'v': if (ok && mpx_match_str(mpx->tt, "verbatimtex")) { mpx->aa = mpx->tt + 11; return 1; } else ok = 0; break; default: if ((*(mpx->tt) >= 'a' && *(mpx->tt) <= 'z') || (*(mpx->tt) >= 'A' && *(mpx->tt) <= 'Z') || (*(mpx->tt) == '_')) ok = 0; else ok = 1; } } mpx->aa = mpx->tt; return 0; } @ @c static void mpx_copy_mpto (MPX mpx, FILE *outfile) { char *s; /* where a string to print stops */ char *t; /* for finding start of last line */ char c; char *res = NULL; do { if (*mpx->aa == 0) { if ((mpx->aa = mpx_getline(mpx,mpx->mpfile)) == NULL) { mpx_error(mpx,"btex section does not end"); } } if (mpx_getbta(mpx, mpx->aa) && *(mpx->tt) == 'e') { s = mpx->tt; } else { if (*(mpx->tt) == 'b') mpx_error(mpx,"btex in TeX mode"); if (*(mpx->tt) == 'v') mpx_error(mpx,"verbatimtex in TeX mode"); s = mpx->aa; } c = *s; *s = 0; if (res==NULL) { res = xmalloc(strlen(mpx->bb)+2,1); res = strncpy(res,mpx->bb,(strlen(mpx->bb)+1)); } else { res = xrealloc(res,strlen(res)+strlen(mpx->bb)+2,1); res = strncat(res,mpx->bb, strlen(mpx->bb)); } if (c == '\0') res = strncat(res, "\n", 1); *s = c; } while (*(mpx->tt) != 'e'); /* whitespace at the end */ for (s = res + strlen(res) - 1; s >= res && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'); s--); t = s; *(++s) = '\0'; /* whitespace at the start */ for (s = res; s < (res + strlen(res)) && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'); s++); for (; *t != '\n' && t > s; t--); fprintf(outfile,"%s", s); /* put no |%| at end if it's only 1 line total, starting with |%|; * this covers the special case |%&format| in a single line. */ if (t != s || *t != '%') fprintf(outfile,"%%"); free(res); } @ Static strings for mpto @c static const char *mpx_predoc[] = {"", ".po 0\n"}; static const char *mpx_postdoc[] = { "\\end{document}\n", ""}; static const char *mpx_pretex1[] = { "\\gdef\\mpxshipout{\\shipout\\hbox\\bgroup%\n" " \\setbox0=\\hbox\\bgroup}%\n" "\\gdef\\stopmpxshipout{\\egroup" " \\dimen0=\\ht0 \\advance\\dimen0\\dp0\n" " \\dimen1=\\ht0 \\dimen2=\\dp0\n" " \\setbox0=\\hbox\\bgroup\n" " \\box0\n" " \\ifnum\\dimen0>0 \\vrule width1sp height\\dimen1 depth\\dimen2 \n" " \\else \\vrule width1sp height1sp depth0sp\\relax\n" " \\fi\\egroup\n" " \\ht0=0pt \\dp0=0pt \\box0 \\egroup}\n" "\\mpxshipout%% line %d %s\n", ".lf %d %s\n" }; static const char *mpx_pretex[] = { "\\mpxshipout%% line %d %s\n", ".bp\n.lf %d %s\n" }; static const char *mpx_posttex[] = { "\n\\stopmpxshipout\n", "\n" }; static const char *mpx_preverb1[] = {"", ".lf %d %s\n" }; /* if very first instance */ static const char *mpx_preverb[] = { "%% line %d %s\n", ".lf %d %s\n"}; /* all other instances */ static const char *mpx_postverb[] = { "\n", "\n" } ; @ @c static void mpx_mpto(MPX mpx, char *tmpname, char *mptexpre) { FILE *outfile; int mode = mpx->mode; char *mpname = mpx->mpname; if (mode==mpx_tex_mode) { TMPNAME_EXT(mpx->tex,".tex"); } else { TMPNAME_EXT(mpx->tex,".i"); } outfile = mpx_xfopen(mpx,mpx->tex, "wb"); if (mode==mpx_tex_mode) { FILE *fr; if ((fr = fopen(mptexpre, "r"))!= NULL) { while (mpx_getline(mpx, fr) != NULL) fputs(mpx->buf, outfile); mpx_fclose(mpx,fr); } } mpx->mpfile = mpx_xfopen(mpx,mpname, "r"); fprintf(outfile,"%s", mpx_predoc[mode]); while (mpx_getline(mpx, mpx->mpfile) != NULL) @; fprintf(outfile,"%s", mpx_postdoc[mode]); mpx_fclose(mpx,mpx->mpfile); mpx_fclose(mpx,outfile); mpx->lnno = 0; } @ @= { mpx->aa = mpx->buf; while (mpx_getbta(mpx, mpx->aa)) { if (*(mpx->tt) == '%') { break; } else if (*(mpx->tt) == '"') { do { if (!mpx_getbta(mpx, mpx->aa)) mpx_error(mpx,"string does not end"); } while (*(mpx->tt) != '"'); } else if (*(mpx->tt) == 'b') { if (mpx->texcnt++ == 0) fprintf(outfile,mpx_pretex1[mode], mpx->lnno, mpname); else fprintf(outfile,mpx_pretex[mode], mpx->lnno, mpname); mpx_copy_mpto(mpx, outfile); fprintf(outfile,"%s", mpx_posttex[mode]); } else if (*(mpx->tt) == 'v') { if (mpx->verbcnt++ == 0 && mpx->texcnt == 0) fprintf(outfile,mpx_preverb1[mode], mpx->lnno, mpname); else fprintf(outfile,mpx_preverb[mode], mpx->lnno, mpname); mpx_copy_mpto(mpx, outfile); fprintf(outfile,"%s", mpx_postverb[mode]); } else { mpx_error(mpx,"unmatched etex"); } } } @ @= mpx_mpto(mpx, tmpname, mpxopt->mptexpre) @* DVItoMP Processing. The \.{DVItoMP} program reads binary device-independent (``\.{DVI}'') files that are produced by document compilers such as \TeX, and converts them into a symbolic form understood by \MP. It is loosely based on the \.{DVItype} utility program that produces a more faithful symbolic form of a \.{DVI} file. The output file is a sequence of \MP\ picture expressions, one for every page in the \.{DVI} file. It makes no difference to \.{DVItoMP} where the \.{DVI} file comes from, but it is intended to process the result of running \TeX\ or \LaTeX\ on the output of the extraction process that is defined above. Such a \.{DVI} file will contain one page for every \.{btex}$\ldots$\.{etex} block in the original input. Processing with \.{DVItoMP} creates a corresponding sequence of \MP\ picture expressions for use as an auxiliary input file. Since \MP\ expects such files to have the extension \.{.MPX}, the output of \.{DVItoMP} is sometimes called an ``\.{MPX}'' file. @ The following parameters can be changed at compile time to extend or reduce \.{DVItoMP}'s capacity. @d virtual_space 1000000 /* maximum total bytes of typesetting commands for virtual fonts */ @d max_fonts 1000 /* maximum number of distinct fonts per \.{DVI} file */ @d max_fnums 3000 /* maximum number of fonts plus fonts local to virtual fonts */ @d max_widths (256*max_fonts) /* maximum number of different characters among all fonts */ @d line_length 79 /* maximum output line length (must be at least 60) */ @d stack_size 100 /* \.{DVI} files shouldn't |push| beyond this depth */ @d font_tolerance 0.00001 /* font sizes should match to within this multiple of $2^{20}$ \.{DVI} units */ @ If the \.{DVI} file is badly malformed, the whole process must be aborted; \.{DVItoMP} will give up, after issuing an error message about the symptoms that were noticed. @d bad_dvi(A) mpx_abort(mpx,"Bad DVI file: " A "!") @d bad_dvi_two(A,B) mpx_abort(mpx,"Bad DVI file: %s !", A, B) @.Bad DVI file@> @* The character set. Like all programs written with the \.{WEB} system, \.{DVItoMP} can be used with any character set. It an identify transfrom internally, because the programming for portable input-output is easier when a fixed internal code is used, and because \.{DVI} files use ASCII code for file names. In the conversion from Pascal to C, the |xchr| array has been removed. Because some systems may still want to change the input--output character set, the accesses to |xchr| and |printable| are replaced by macro calls. @d printable(c) (isprint(c) && c < 128 && c!='"') @d xchr(A) (A) @ @c static void mpx_open_mpxfile (MPX mpx) { /* prepares to write text on |mpxfile| */ mpx->mpxfile = mpx_xfopen (mpx,mpx->mpxname, "wb"); } @* Device-independent file format. The format of \.{DVI} files is described in many places including \.{dvitype.web} and Volume~B of D.~E. Knuth's {\sl Computers and Typesetting}. This program refers to the following command codes. @d id_byte 2 /* identifies the kind of \.{DVI} files described here */ @# @d set_char_0 0 /* typeset character 0 and move right */ @d set1 128 /* typeset a character and move right */ @d set_rule 132 /* typeset a rule and move right */ @d put1 133 /* typeset a character */ @d put_rule 137 /* typeset a rule */ @d nop 138 /* no operation */ @d bop 139 /* beginning of page */ @d eop 140 /* ending of page */ @d push 141 /* save the current positions */ @d pop 142 /* restore previous positions */ @d right1 143 /* move right */ @d w0 147 /* move right by |w| */ @d w1 148 /* move right and set |w| */ @d x0 152 /* move right by |x| */ @d x1 153 /* move right and set |x| */ @d down1 157 /* move down */ @d y0 161 /* move down by |y| */ @d y1 162 /* move down and set |y| */ @d z0 166 /* move down by |z| */ @d z1 167 /* move down and set |z| */ @d fnt_num_0 171 /* set current font to 0 */ @d fnt1 235 /* set current font */ @d xxx1 239 /* extension to \.{DVI} primitives */ @d xxx4 242 /* potentially long extension to \.{DVI} primitives */ @d fnt_def1 243 /* define the meaning of a font number */ @d pre 247 /* preamble */ @d post 248 /* postamble beginning */ @d post_post 249 /* postamble ending */ @d undefined_commands 250: case 251: case 252: case 253: case 254: case 255 @* Input from binary files. @ The program deals with two binary file variables: |dvi_file| is the main input file that we are translating into symbolic form, and |tfm_file| is the current font metric file from which character-width information is being read. It is convenient to have a throw-away variable for function results when reading parts of the files that are being skipped. @= FILE * dvi_file; /* the input file */ FILE * tfm_file; /* a font metric file */ FILE * vf_file; /* a virtual font file */ @ Prepares to read packed bytes in |dvi_file| @c static void mpx_open_dvi_file (MPX mpx) { mpx->dvi_file = fopen(mpx->dviname,"rb"); if (mpx->dvi_file==NULL) mpx_abort(mpx,"DVI generation failed"); } @ Prepares to read packed bytes in |tfm_file| @c static boolean mpx_open_tfm_file (MPX mpx) { mpx->tfm_file = mpx_fsearch(mpx, mpx->cur_name, mpx_tfm_format); if (mpx->tfm_file == NULL) mpx_abort(mpx,"Cannot find TFM %s", mpx->cur_name); free (mpx->cur_name); /* We |xmalloc|'d this before we got called. */ return true; /* If we get here, we succeeded. */ } @ Prepares to read packed bytes in |vf_file|. It's ok if the \.{VF} file doesn't exist. @c static boolean mpx_open_vf_file (MPX mpx) { mpx->vf_file = mpx_fsearch(mpx, mpx->cur_name, mpx_vf_format); if (mpx->vf_file) { free (mpx->cur_name); return true; } return false; } @ If you looked carefully at the preceding code, you probably asked, ``What is |cur_name|?'' Good question. It's a global variable: |cur_name| is a string variable that will be set to the current font metric file name before |open_tfm_file| or |open_vf_file| is called. @= char *cur_name; /* external name */ @ It turns out to be convenient to read four bytes at a time, when we are inputting from \.{TFM} files. The input goes into global variables |b0|, |b1|, |b2|, and |b3|, with |b0| getting the first byte and |b3| the fourth. @= int b0, b1, b2, b3; /* four bytes input at once */ @ The |read_tfm_word| procedure sets |b0| through |b3| to the next four bytes in the current \.{TFM} file. @c static void mpx_read_tfm_word (MPX mpx) { mpx->b0 = getc(mpx->tfm_file); mpx->b1 = getc(mpx->tfm_file); mpx->b2 = getc(mpx->tfm_file); mpx->b3 = getc(mpx->tfm_file); } @ Input can come from from three different sources depending on the settings of global variables. When |vf_reading| is true, we read from the \.{VF} file. Otherwise, input can either come directly from |dvi_file| or from a buffer |cmd_buf|. The latter case applies whenever |buf_ptr= boolean vf_reading; /* should input come from |vf_file|? */ unsigned char cmd_buf[(virtual_space+1)]; /* commands for virtual characters */ unsigned int buf_ptr; /* |cmd_buf| index for the next byte */ @ @= mpx->vf_reading=false; mpx->buf_ptr=virtual_space; @ We shall use a set of simple functions to read the next byte or bytes from the current input source. There are seven possibilities, each of which is treated as a separate function in order to minimize the overhead for subroutine calls. @c static integer mpx_get_byte (MPX mpx) { /* returns the next byte, unsigned */ unsigned char b; @; return b; } static integer mpx_signed_byte (MPX mpx) { /* returns the next byte, signed */ unsigned char b; @; return ( b<128 ? b : (b-256)); } static integer mpx_get_two_bytes (MPX mpx) { /* returns the next two bytes, unsigned */ unsigned char a,b; a=0; b=0; /* for compiler warnings */ @; return (a*(int)(256)+b); } static integer mpx_signed_pair (MPX mpx) { /* returns the next two bytes, signed */ unsigned char a,b; a=0; b=0; /* for compiler warnings */ @; if ( a<128 ) return (a*256+b); else return ((a-256)*256+b); } static integer mpx_get_three_bytes (MPX mpx) { /* returns the next three bytes, unsigned */ unsigned char a,b,c; a=0; b=0; c=0; /* for compiler warnings */ @; return ((a*(int)(256)+b)*256+c); } static integer mpx_signed_trio (MPX mpx) { /* returns the next three bytes, signed */ unsigned char a,b,c; a=0; b=0; c=0; /* for compiler warnings */ @; if ( a<128 ) return ((a*(int)(256)+b)*256+c); else return (((a-(int)(256))*256+b)*256+c); } static integer mpx_signed_quad (MPX mpx) { /* returns the next four bytes, signed */ unsigned char a,b,c,d; a=0; b=0; c=0; d=0; /* for compiler warnings */ @; if ( a<128 ) return (((a*(int)(256)+b)*256+c)*256+d); else return ((((a-256)*(int)(256)+b)*256+c)*256+d); } @ @= if ( mpx->vf_reading ) { b = getc(mpx->vf_file); } else if ( mpx->buf_ptr==virtual_space ) { b = getc(mpx->dvi_file); } else { b=mpx->cmd_buf[mpx->buf_ptr]; incr(mpx->buf_ptr); } @ @= if ( mpx->vf_reading ) { a = getc(mpx->vf_file); b = getc(mpx->vf_file); } else if ( mpx->buf_ptr==virtual_space ) { a = getc(mpx->dvi_file); b = getc(mpx->dvi_file); } else if ( mpx->buf_ptr+2>mpx->n_cmds ) { mpx_abort(mpx,"Error detected while interpreting a virtual font"); @.Error detected while...@> } else { a=mpx->cmd_buf[mpx->buf_ptr]; b=mpx->cmd_buf[mpx->buf_ptr+1]; mpx->buf_ptr+=2; } @ @= if ( mpx->vf_reading ) { a = getc(mpx->vf_file); b = getc(mpx->vf_file); c = getc(mpx->vf_file); } else if ( mpx->buf_ptr==virtual_space ) { a = getc(mpx->dvi_file); b = getc(mpx->dvi_file); c = getc(mpx->dvi_file); } else if ( mpx->buf_ptr+3>mpx->n_cmds ) { mpx_abort(mpx,"Error detected while interpreting a virtual font"); @.Error detected while...@> } else { a=mpx->cmd_buf[mpx->buf_ptr]; b=mpx->cmd_buf[mpx->buf_ptr+1]; c=mpx->cmd_buf[mpx->buf_ptr+2]; mpx->buf_ptr+=3; } @ @= if ( mpx->vf_reading ) { a = getc(mpx->vf_file); b = getc(mpx->vf_file); c = getc(mpx->vf_file); d = getc(mpx->vf_file); } else if ( mpx->buf_ptr==virtual_space ) { a = getc(mpx->dvi_file); b = getc(mpx->dvi_file); c = getc(mpx->dvi_file); d = getc(mpx->dvi_file); } else if ( mpx->buf_ptr+4>mpx->n_cmds ) { mpx_abort(mpx,"Error detected while interpreting a virtual font"); @.Error detected while...@> } else { a=mpx->cmd_buf[mpx->buf_ptr]; b=mpx->cmd_buf[mpx->buf_ptr+1]; c=mpx->cmd_buf[mpx->buf_ptr+2]; d=mpx->cmd_buf[mpx->buf_ptr+3]; mpx->buf_ptr+=4; } @* Data structures for fonts. \.{DVI} file format does not include information about character widths, since that would tend to make the files a lot longer. But a program that reads a \.{DVI} file is supposed to know the widths of the characters that appear in \\{set\_char} commands. Therefore \.{DVItoMP} looks at the font metric (\.{TFM}) files for the fonts that are involved. @.TFM {\rm files}@> @ For purposes of this program, the only thing we need to know about a given character |c| in a non-virtual font |f| is the width. For the font as a whole, all we need is the symbolic name to use in the \.{MPX} file. This information appears implicitly in the following data structures. The current number of fonts defined is |nf|. Each such font has an internal number |f|, where |0<=fwidth[mpx->info_base[(A)]+(B)] @d start_cmd(A,B) mpx->cmd_ptr[mpx->info_base[(A)]+(B)] @= integer font_num[(max_fnums+1)]; /* external font numbers */ integer internal_num[(max_fnums+1)]; /* internal font numbers */ boolean local_only[(max_fnums+1)]; /* |font_num| meaningless? */ char *font_name[(max_fonts+1)]; /* starting positions of external font names */ double font_scaled_size[(max_fonts+1)]; /* scale factors over $2^{20}$ */ double font_design_size[(max_fonts+1)]; /* design sizes over $2^{20}$ */ integer font_check_sum[(max_fonts+1)]; /* check sum from the |font_def| */ integer font_bc[(max_fonts+1)]; /* beginning characters in fonts */ integer font_ec[(max_fonts+1)]; /* ending characters in fonts */ integer info_base[(max_fonts+1)]; /* index into |width| and |cmd_ptr| tables */ integer width[(max_widths+1)]; /* character widths, in units $2^{-20}$ of design size */ integer fbase[(max_fonts+1)]; /* index into |font_num| for local fonts */ integer ftop[(max_fonts+1)]; /* |font_num| index where local fonts stop */ integer cmd_ptr[(max_widths+1)]; /* starting positions in |cmd_buf| */ unsigned int nfonts; /* the number of known fonts */ unsigned int vf_ptr; /* next |font_num| entry for virtual font font tables */ unsigned int info_ptr; /* allocation pointer for |width| and |cmd_ptr| tables */ unsigned int n_cmds; /* number of occupied cells in |cmd_buf| */ unsigned int cur_fbase, cur_ftop; /* currently applicable part of the |font_num| table */ @ @= mpx->nfonts=0; mpx->info_ptr=0; mpx->font_name[0]=0; mpx->vf_ptr=max_fnums; mpx->cur_fbase=0; mpx->cur_ftop=0; @ Printing the name of a given font is easy except that a procedure |print_char| is needed to actually send an |ASCII_code| to the \.{MPX} file. @c @@; static void mpx_print_font (MPX mpx, integer f) { /* |f| is an internal font number */ if ( (f<0)||(f>=(int)mpx->nfonts) ) { bad_dvi("Undefined font"); } else { char *s = mpx->font_name[f]; while (*s) { mpx_print_char(mpx,*s); s++; } } } @ Sometimes a font name is needed as part of an error message. @d font_warn(A,B) mpx_warn (mpx,"%s %s",A,mpx->font_name[(B)]) @d font_error(A,B) mpx_error(mpx,"%s %s",A,mpx->font_name[(B)]) @d font_abort(A,B) mpx_abort(mpx,"%s %s",A,mpx->font_name[(B)]) @ When we encounter a font definition, we save the name, checksum, and size information, but we don't actually read the \.{TFM} or \.{VF} file until we are about to use the font. If a matching font is not already defined, we then allocate a new internal font number. The following subroutine does the necessary things when a \\{fnt\_def} command is encountered in the \.{DVI} file or in a \.{VF} file. It assumes that the first argument has already been parsed and is given by the parameter~|e|. @c @@; static void mpx_define_font (MPX mpx, integer e) { /* |e| is an external font number */ integer i; /* index into |font_num| and |internal_num| */ integer n; /* length of the font name and area */ integer k; /* general purpose loop counter */ integer x; /* a temporary value for scaled size computation */ if ( mpx->nfonts==max_fonts ) mpx_abort(mpx,"DVItoMP capacity exceeded (max fonts=%d)!", max_fonts); @.DVItoMP capacity exceeded...@> @; @; mpx->internal_num[i]=mpx_match_font(mpx, mpx->nfonts,true); if ( mpx->internal_num[i]==(int)mpx->nfonts ) { mpx->info_base[mpx->nfonts]=max_widths; /* indicate that the info isn't loaded yet */ mpx->local_only[mpx->nfonts]=mpx->vf_reading; incr(mpx->nfonts); } } @ @= if ( mpx->vf_ptr==mpx->nfonts ) mpx_abort(mpx,"DVItoMP capacity exceeded (max font numbers=%d)", max_fnums); @.DVItoMP capacity exceeded...@> if ( mpx->vf_reading ) { mpx->font_num[mpx->nfonts]=0; i=mpx->vf_ptr; decr(mpx->vf_ptr); } else { i=mpx->nfonts; } mpx->font_num[i]=e @ @= mpx->font_check_sum[mpx->nfonts]=mpx_signed_quad(mpx); @; n=mpx_get_byte(mpx); /* that is the area */ n=n+mpx_get_byte(mpx); mpx->font_name[mpx->nfonts]=xmalloc(n+1,1); for (k=0;kfont_name[mpx->nfonts][k]=mpx_get_byte(mpx); mpx->font_name[mpx->nfonts][k]=0 @ The scaled size and design size are stored in \.{DVI} units divided by $2^{20}$. The units for scaled size are a little different if we are reading a virtual font, but this will be corrected when the scaled size is used. The scaled size also needs to be truncated to at most 23 significant bits in order to make the character width calculation match what \TeX\ does. @= x=mpx_signed_quad(mpx); k=1; while ( mpx->x>040000000 ) { x= x / 2; k=k+k; } mpx->font_scaled_size[mpx->nfonts]=x*k/1048576.0; if ( mpx->vf_reading ) mpx->font_design_size[mpx->nfonts]=mpx_signed_quad(mpx)*mpx->dvi_per_fix/1048576.0; else mpx->font_design_size[mpx->nfonts]=mpx_signed_quad(mpx)/1048576.0; @ @= double dvi_per_fix; /* converts points scaled $2^{20}$ to \.{DVI} units */ @ The |match_font| function tries to find a match for the font with internal number~|ff|, returning |nf| or the number of the matching font. If |exact=true|, the name and scaled size should match. Otherwise the scaled size need not match but the font found must be already loaded, not just defined. @= static integer mpx_match_font (MPX mpx, unsigned ff, boolean exact) { unsigned f; /* font number being tested */ for (f=0; fnfonts ; f++) { if ( f!=ff ) { @; if ( exact ) { if ( fabs(mpx->font_scaled_size[f]-mpx->font_scaled_size[ff])<= font_tolerance ) { if ( ! mpx->vf_reading ) { if ( mpx->local_only[f] ) { mpx->font_num[f]=mpx->font_num[ff]; mpx->local_only[f]=false; } else if ( mpx->font_num[f]!=mpx->font_num[ff] ) { continue; } } break; } } else if ( mpx->info_base[f]!=max_widths ) { break; } } } if ( fnfonts ) { @; } return f; } @ @= if (strcmp(mpx->font_name[f],mpx->font_name[ff])) continue @ @= if ( fabs(mpx->font_design_size[f]-mpx->font_design_size[ff]) > font_tolerance ) { font_error("Inconsistent design sizes given for ",ff); @.Inconsistent design sizes@> } else if ( mpx->font_check_sum[f]!=mpx->font_check_sum[ff] ) { font_warn("Checksum mismatch for ", ff); @.Checksum mismatch@> } @* Reading ordinary fonts. An auxiliary array |in_width| is used to hold the widths as they are input. The global variable |tfm_check_sum| is set to the check sum that appears in the current \.{TFM} file. @= integer in_width[256]; /* \.{TFM} width data in \.{DVI} units */ integer tfm_check_sum; /* check sum found in |tfm_file| */ @ Here is a procedure that absorbs the necessary information from a \.{TFM} file, assuming that the file has just been successfully reset so that we are ready to read its first byte. (A complete description of \.{TFM} file format appears in the documentation of \.{TFtoPL} and will not be repeated here.) The procedure does not check the \.{TFM} file for validity, nor does it give explicit information about what is wrong with a \.{TFM} file that proves to be invalid. The procedure simply aborts the program if it detects anything amiss in the \.{TFM} data. @c static void mpx_in_TFM (MPX mpx,integer f) { /* input \.{TFM} data for font |f| or abort */ integer k; /* index for loops */ int lh; /* length of the header data, in four-byte words */ int nw; /* number of words in the width table */ int wp; /* new value of |info_ptr| after successful input */ @; @; @; @; mpx->fbase[f]=0; mpx->ftop[f]=0; mpx->info_ptr=wp; return; } @ @= mpx_read_tfm_word(mpx); lh=mpx->b2*(int)(256)+mpx->b3; mpx_read_tfm_word(mpx); mpx->font_bc[f]=mpx->b0*(int)(256)+mpx->b1; mpx->font_ec[f]=mpx->b2*(int)(256)+mpx->b3; if ( mpx->font_ec[f]font_bc[f] ) mpx->font_bc[f]=mpx->font_ec[f]+1; if ( mpx->info_ptr+mpx->font_ec[f]-mpx->font_bc[f]+1>max_widths ) mpx_abort(mpx,"DVItoMP capacity exceeded (width table size=%d)!",max_widths); @.DVItoMP capacity exceeded...@> wp=mpx->info_ptr+mpx->font_ec[f]-mpx->font_bc[f]+1; mpx_read_tfm_word(mpx); nw=mpx->b0*256+mpx->b1; if ( (nw==0)||(nw>256) ) font_abort("Bad TFM file for ",f); @.Bad TFM file@> for (k=1;k<=3+lh;k++) { if ( feof(mpx->tfm_file) ) font_abort("Bad TFM file for ",f); @.Bad TFM file@> mpx_read_tfm_word(mpx); if ( k==4 ) { if ( mpx->b0<128 ) mpx->tfm_check_sum=((mpx->b0*(int)(256)+mpx->b1)*256+mpx->b2)*256+mpx->b3; else mpx->tfm_check_sum=(((mpx->b0-256)*(int)(256)+mpx->b1)*256+mpx->b2)*256+mpx->b3; } } @ @= if ( wp>0 ) { for (k=mpx->info_ptr;k<=wp-1;k++ ) { mpx_read_tfm_word(mpx); if ( mpx->b0>nw ) font_abort("Bad TFM file for ",f); @.Bad TFM file@> mpx->width[k]=mpx->b0; } } @ No fancy width calculation is needed here because \.{DVItoMP} stores widths in their raw form as multiples of the design size scaled by $2^{20}$. The |font_scaled_size| entries have been computed so that the final width compution can be done in floating point if enough precision is available. @= for (k=0;k<=nw-1;k++) { mpx_read_tfm_word(mpx); if ( mpx->b0>127 ) mpx->b0=mpx->b0-256; mpx->in_width[k]=((mpx->b0*0400+mpx->b1)*0400+mpx->b2)*0400+mpx->b3; } @ The width compution uses a scale factor |dvi_scale| that will be introduced later. It is equal to one when not typesetting a character from a virtual font. In that case, the following expressions do the width computation that is so important in \.{DVItype}. It is less important here because it is impractical to guarantee precise character positioning in \MP\ output. Nevertheless, the width compution will be precise if reals have at least 46-bit mantissas and |round(x-.5)| is equivalent to $\lfloor x\rfloor$. It may be a good idea to modify this computation if these conditions are not met. @^system dependencies@> @= floor(mpx->dvi_scale*mpx->font_scaled_size[f]*char_width(f,c)) @ @= floor(mpx->dvi_scale*mpx->font_scaled_size[cur_font]*char_width(cur_font,p)) @ @= if ( mpx->in_width[0]!=0 ) font_abort("Bad TFM file for ",f); /* the first width should be zero */ @.Bad TFM file@> mpx->info_base[f]=mpx->info_ptr-mpx->font_bc[f]; if ( wp>0 ) { for (k=mpx->info_ptr;k<=wp-1;k++) { mpx->width[k]=mpx->in_width[mpx->width[k]]; } } @* Reading virtual fonts. The |in_VF| procedure absorbs the necessary information from a \.{VF} file that has just been reset so that we are ready to read the first byte. (A complete description of \.{VF} file format appears in the documention of \.{VFtoVP}). Like |in_TFM|, this procedure simply aborts the program if it detects anything wrong with the \.{VF} file. @c @@; static void mpx_in_VF (MPX mpx, integer f) { /* read \.{VF} data for font |f| or abort */ integer p; /* a byte from the \.{VF} file */ boolean was_vf_reading; /* old value of |vf_reading| */ integer c; /* the current character code */ integer limit; /* space limitations force character codes to be less than this */ integer w; /* a \.{TFM} width being read */ was_vf_reading=mpx->vf_reading; mpx->vf_reading=true; @;@/ @;@/ p=mpx_get_byte(mpx); while ( mpx->p>=fnt_def1 ) { if ( mpx->p>fnt_def1+3 ) font_abort("Bad VF file for ",f); mpx_define_font(mpx, mpx_first_par(mpx, p)); p=mpx_get_byte(mpx); } while ( p<=242 ) { if ( feof(mpx->vf_file) ) font_abort("Bad VF file for ",f); @; @; p=mpx_get_byte(mpx); } if ( mpx->p==post ) { @; mpx->vf_reading=was_vf_reading; return; } } @ @= p=mpx_get_byte(mpx); if ( p!=pre ) font_abort("Bad VF file for ",f); p=mpx_get_byte(mpx); /* fetch the identification byte */ if ( p!=202 ) font_abort("Bad VF file for ",f); p=mpx_get_byte(mpx); /* fetch the length of the introductory comment */ while ( p-->0 ) (void)mpx_get_byte(mpx); mpx->tfm_check_sum=mpx_signed_quad(mpx); (void)mpx_signed_quad(mpx); /* skip over the design size */ @ @= mpx->ftop[f]=mpx->vf_ptr; if ( mpx->vf_ptr==mpx->nfonts ) mpx_abort(mpx,"DVItoMP capacity exceeded (max font numbers=%d)", max_fnums); @.DVItoMP capacity exceeded...@> decr(mpx->vf_ptr); mpx->info_base[f]=mpx->info_ptr; limit=max_widths-mpx->info_base[f];@/ mpx->font_bc[f]=limit; mpx->font_ec[f]=0 @ @= if ( p==242 ) { p=mpx_signed_quad(mpx); c=mpx_signed_quad(mpx); w=mpx_signed_quad(mpx); if ( c<0 ) font_abort("Bad VF file for ",f); } else { c=mpx_get_byte(mpx); w=mpx_get_three_bytes(mpx); } if ( c>=limit ) mpx_abort(mpx,"DVItoMP capacity exceeded (max widths=%d)", max_widths); @.DVItoMP capacity exceeded...@> if ( cfont_bc[f] ) mpx->font_bc[f]=c; if ( c>mpx->font_ec[f] ) mpx->font_ec[f]=c; char_width(f,c)=w @ @= if ( mpx->n_cmds+p>=virtual_space ) mpx_abort(mpx,"DVItoMP capacity exceeded (virtual font space=%d)",virtual_space); @.DVItoMP capacity exceeded...@> start_cmd(f,c)=mpx->n_cmds; while ( p>0 ) { mpx->cmd_buf[mpx->n_cmds]=mpx_get_byte(mpx); incr(mpx->n_cmds); decr(p); } mpx->cmd_buf[mpx->n_cmds]=eop; /* add the end-of-packet marker */ incr(mpx->n_cmds) @ There are unused |width| and |cmd_ptr| entries if |font_bc[f]>0| but it isn't worthwhile to slide everything down just to save a little space. @= mpx->fbase[f]=mpx->vf_ptr+1; mpx->info_ptr=mpx->info_base[f]+mpx->font_ec[f]+1 @* Loading fonts. The character width information for a font is loaded when the font is selected for the first time. This information might already be loaded if the font has already been used at a different scale factor. Otherwise, we look for a \.{VF} file, or failing that, a \.{TFM} file. All this is done by the |select_font| function that takes an external font number~|e| and returns the corresponding internal font number with the width information loaded. @c static integer mpx_select_font (MPX mpx, integer e) { int f; /* the internal font number */ int ff; /* internal font number for an existing version */ integer k; /* general purpose loop counter */ @; if ( mpx->info_base[f]==max_widths ) { ff=mpx_match_font(mpx, f,false); if ( ff<(int)mpx->nfonts ) { @; } else { @; if ( mpx_open_vf_file(mpx) ) { mpx_in_VF(mpx, f); } else { if ( ! mpx_open_tfm_file(mpx) ) font_abort("No TFM file found for ",f); @.no TFM file found@> mpx_in_TFM(mpx, f); } @; } @; } return f; } @ @= if ( mpx->cur_ftop<=mpx->nfonts ) mpx->cur_ftop=mpx->nfonts; mpx->font_num[mpx->cur_ftop]=e; k=mpx->cur_fbase; while ((mpx->font_num[k]!=e)|| mpx->local_only[k] ) incr(k); if ( k==(int)mpx->cur_ftop ) mpx_abort(mpx,"Undefined font selected"); f=mpx->internal_num[k] @ @= { mpx->font_bc[f]=mpx->font_bc[ff]; mpx->font_ec[f]=mpx->font_ec[ff]; mpx->info_base[f]=mpx->info_base[ff]; mpx->fbase[f]=mpx->fbase[ff]; mpx->ftop[f]=mpx->ftop[ff]; } @ The string |cur_name| is supposed to be set to the external name of the \.{VF} file for the current font. @^system dependencies@> @= mpx->cur_name = xstrdup (mpx->font_name[f]) @ @= { if ( (mpx->font_check_sum[f]!=0)&&(mpx->tfm_check_sum!=0)&&@| (mpx->font_check_sum[f]!=mpx->tfm_check_sum) ) { font_warn("Checksum mismatch for ",f); @.Checksum mismatch@> } } @* Low level output routines. One of the basic output operations is to write a \MP\ string expression for a sequence of characters to be typeset. The main difficulties are that such strings can contain arbitrary eight-bit bytes and there is no fixed limit on the length of the string that needs to be produced. In extreme cases this can lead to expressions such as $$\vcenter{ \hbox{\.{char7\&char15\&char31\&"?FWayzz"}} \hbox{\.{\&"zzaF"\&char15\&char3\&char31}} \hbox{\.{\&"Nxzzzzzzzwvtsqo"}}} $$ @ A global variable |state| keeps track of the output process. When |state=normal| we have begun a quoted string and the next character should be a printable character or a closing quote. When |state=special| the last thing printed was a ``\.{char}'' construction or a closing quote and an ampersand should come next. The starting condition |state=initial| is a lot like |state=special|, except no ampersand is required. @d special 0 /* the |state| after printing a ``\.{char}'' expression */ @d normal 1 /* the |state| value in a quoted string */ @d initial 2 /* initial |state| */ @= int state; /* controls the process of printing a string */ int print_col; /* there are at most this many characters on the current line */ @ @= mpx->state = initial; mpx->print_col = 0; /* there are at most this many characters on the current line */ @ To print a string on the \.{MPX} file, initialize |print_col|, ensure that |state=initial|, and pass the characters one-at-a-time to |print_char|. @= static void mpx_print_char (MPX mpx, unsigned char c) { integer l; /* number of characters to print |c| or the \.{char} expression */ if ( printable(c) ) l=1; else if ( c<10 ) l=5; else if ( c<100 ) l=6; else l=7; if ( mpx->print_col+l>line_length-2 ) { if ( mpx->state==normal ) { fprintf(mpx->mpxfile,"\""); mpx->state=special; } fprintf(mpx->mpxfile,"\n"); mpx->print_col=0; } @; } @ @= if ( mpx->state==normal ) { if ( printable(c) ) { fprintf(mpx->mpxfile,"%c",xchr(c)); } else { fprintf(mpx->mpxfile,"\"&char%d",c); mpx->print_col +=2; } } else { if ( mpx->state==special ) { fprintf(mpx->mpxfile,"&"); incr(mpx->print_col); } if ( printable(c) ) { fprintf(mpx->mpxfile,"\"%c",xchr(c)); incr(mpx->print_col); } else { fprintf(mpx->mpxfile,"char%d",c); } } mpx->print_col += l; if ( printable(c) ) mpx->state=normal; else mpx->state=special @ The |end_char_string| procedure gets the string ended properly and ensures that there is room for |l| more characters on the output line. @= static void mpx_end_char_string (MPX mpx,integer l) { while ( mpx->state>special ){ fprintf(mpx->mpxfile,"\""); incr(mpx->print_col); decr(mpx->state); } if ( mpx->print_col+l>line_length ) { fprintf(mpx->mpxfile,"\n "); mpx->print_col=0; } mpx->state=initial; /* get ready to print the next string */ } @ Since |end_char_string| resets |state:=initial|, all we have to do is set |state:=initial| once at the beginning. @= mpx->state=initial; @ Characters and rules are positioned according to global variables |h| and~|v| as will be explained later. We also need scale factors that convert quantities to the right units when they are printed in the \.{MPX} file. Even though all variable names in the \MP\ output are made local via \.{save} commands, it is still desirable to preceed them with underscores. This makes the output more likely to work when used in a macro definition, since the generated variables names must not collide with formal parameters in such cases. @= integer h; integer v; /* the current position in \.{DVI} units */ double conv; /* converts \.{DVI} units to \MP\ points */ double mag; /* magnification factor times 1000 */ @ @c @@; static void mpx_do_set_char (MPX mpx,integer f, integer c) { if ( (cfont_bc[f])||(c>mpx->font_ec[f]) ) mpx_abort(mpx,"attempt to typeset invalid character %d",c); @.attempt to typeset...@> if ((mpx->h!=mpx->str_h2)||(mpx->v!=mpx->str_v)|| (f!=mpx->str_f)||(mpx->dvi_scale!=mpx->str_scale) ) { if ( mpx->str_f>=0 ) { mpx_finish_last_char(mpx); } else if ( ! mpx->fonts_used ) { @; } if ( ! mpx->font_used[f] ) @; fprintf(mpx->mpxfile,"_s("); mpx->print_col=3;@/ mpx->str_scale=mpx->dvi_scale; mpx->str_f=f; mpx->str_v=mpx->v; mpx->str_h1=mpx->h; } mpx_print_char(mpx, c); mpx->str_h2=mpx->h+@; } @ @= boolean font_used[(max_fonts+1)]; /* has this font been used on this page? */ boolean fonts_used; /* has any font been used on this page? */ boolean rules_used; /* has any rules been set on this page? */ integer str_h1; integer str_v; /* starting position for current output string */ integer str_h2; /* where the current output string ends */ integer str_f; /* internal font number for the current output string */ double str_scale; /* value of |dvi_scale| for the current output string */ @ Before using any fonts we need to define a MetaPost macro for typesetting character strings. The |font_used| array is not initialized until it is actually time to output a character. @= static void mpx_prepare_font_use(MPX mpx); @ @c static void mpx_prepare_font_use(MPX mpx) { unsigned k; for (k=0; knfonts;k++ ) mpx->font_used[k]=false; mpx->fonts_used=true; fprintf(mpx->mpxfile,"string _n[];\n"); fprintf(mpx->mpxfile,"vardef _s(expr _t,_f,_m,_x,_y)(text _c)=\n"); fprintf(mpx->mpxfile, " addto _p also _t infont _f scaled _m shifted (_x,_y) _c; enddef;\n"); } @ @= mpx_prepare_font_use(mpx) @ @= mpx->font_used[f]=false; @ Do what is necessary when the font with internal number f is used for the first time on a page. @= static void mpx_first_use(MPX mpx, int f) ; @ @c static void mpx_first_use(MPX mpx, int f) { mpx->font_used[f]=true; fprintf(mpx->mpxfile,"_n%d=",f); mpx->print_col=6; mpx_print_font(mpx, f); mpx_end_char_string(mpx, 1); fprintf(mpx->mpxfile,";\n"); } @ @= mpx_first_use(mpx,f); @ We maintain the invariant that |str_f=-1| when there is no output string under construction. @= static void mpx_finish_last_char (MPX mpx) { double m,x,y; /* font scale factor and \MP\ coordinates of reference point */ if ( mpx->str_f>=0 ) { if (mpx->mode==mpx_tex_mode) { m=mpx->str_scale*mpx->font_scaled_size[mpx->str_f]* mpx->mag/mpx->font_design_size[mpx->str_f]; x=mpx->conv*mpx->str_h1; y=mpx->conv*(-mpx->str_v); if ( (fabs(x)>=4096.0)||(fabs(y)>=4096.0)||(m>=4096.0)||(m<0) ) { mpx_warn(mpx,"text is out of range"); mpx_end_char_string(mpx, 60); } else { mpx_end_char_string(mpx, 40); } fprintf(mpx->mpxfile,",_n%d,%1.5f,%1.4f,%1.4f,",mpx->str_f,m,x,y); @@/ fprintf(mpx->mpxfile,");\n"); } else { m = mpx->str_size / mpx->font_design_size[mpx->str_f]; x = mpx->dmp_str_h1 * mpx->unit; y = YCORR - mpx->dmp_str_v * mpx->unit; if (fabs(x) >= 4096.0 || fabs(y) >= 4096.0 || m >= 4096.0 || m < 0) { mpx_warn(mpx,"text out of range ignored"); mpx_end_char_string(mpx,67); } else { mpx_end_char_string(mpx,47); } fprintf(mpx->mpxfile, ")infont n%d", mpx->font_num[mpx->str_f]); mpx_slant_and_ht(mpx); fprintf(mpx->mpxfile, ",%.5f,%.4f,%.4f);\n", (m*1.00375), x, y); } mpx->str_f=-1; } } @ Setting rules is fairly simple. @c static void mpx_do_set_rule (MPX mpx,integer ht, integer wd) { double xx1,yy1,xx2,yy2,ww; /* \MP\ coordinates of lower-left and upper-right corners */ if ( wd==1 ) { @ } else if ( (ht>0)||(wd>0) ) { if ( mpx->str_f>=0 ) mpx_finish_last_char(mpx); if ( ! mpx->rules_used ) { mpx->rules_used=true; fprintf(mpx->mpxfile, "interim linecap:=0;\n" "vardef _r(expr _a,_w)(text _t) =\n" " addto _p doublepath _a withpen pencircle scaled _w _t enddef;"); } @; if ( (fabs(xx1)>=4096.0)||(fabs(yy1)>=4096.0)||@| (fabs(xx2)>=4096.0)||(fabs(yy2)>=4096.0)||(ww>=4096.0) ) mpx_warn(mpx,"hrule or vrule is out of range"); fprintf(mpx->mpxfile,"_r((%1.4f,%1.4f)..(%1.4f,%1.4f), %1.4f,",xx1,yy1,xx2,yy2,ww); @@/ fprintf(mpx->mpxfile,");\n"); } } @ @= xx1=mpx->conv*mpx->h; yy1=mpx->conv*(-mpx->v); if ( wd>ht ){ xx2=xx1+mpx->conv*wd; ww=mpx->conv*ht; yy1=yy1+0.5*ww; yy2=yy1; } else { yy2=yy1+mpx->conv*ht; ww=mpx->conv*wd; xx1=xx1+0.5*ww; xx2=xx1; } @ Rules of width one dvi unit are not typeset since \.{MPtoTeX} adds an extraneous rule of this width in order to allow \.{DVItoMP} to deduce the dimensions of the boxes it ships out. The box width is the left edge of the last such rule; the height and depth are at the top and bottom of the rule. There should be only one special rule per picture but there could be more if the user tries to typeset his own one-dvi-unit rules. In this case the dimension-determining rule is the last one in the picture. @= { mpx->pic_wd=mpx->h; mpx->pic_dp=mpx->v; mpx->pic_ht=ht-mpx->v; } @ @= integer pic_dp; integer pic_ht; integer pic_wd; /* picture dimensions from special rule */ @ The following initialization and clean-up is required. We do a little more initialization than is absolutely necessary since some compilers might complain if the variables are uninitialized when |do_set_char| tests them. @c static void mpx_start_picture (MPX mpx) { mpx->fonts_used=false; mpx->rules_used=false; mpx->graphics_used=false; mpx->str_f=-1; mpx->str_v=0; mpx->str_h2=0; mpx->str_scale=1.0; /* values don't matter */ mpx->dmp_str_v = 0.0; mpx->dmp_str_h2 = 0.0; mpx->str_size = 0.0; fprintf(mpx->mpxfile, "begingroup save %s_p,_r,_s,_n; picture _p; _p=nullpicture;\n", (mpx->mode == mpx_tex_mode ? "" : "_C,_D,")); } static void mpx_stop_picture (MPX mpx) { double w,h,dd; /* width, height, negative depth in PostScript points */ if ( mpx->str_f>=0 ) mpx_finish_last_char(mpx); if (mpx->mode==mpx_tex_mode) { @; } fprintf(mpx->mpxfile,"_p endgroup\n"); } @ @= dd=-mpx->pic_dp*mpx->conv; w=mpx->conv*mpx->pic_wd; h=mpx->conv*mpx->pic_ht; fprintf(mpx->mpxfile, "setbounds _p to (0,%1.4f)--(%1.4f,%1.4f)--\n" " (%1.4f,%1.4f)--(0,%1.4f)--cycle;\n",dd,w,dd,w,h,h) @* Translation to symbolic form. The main work of \.{DVItoMP} is accomplished by the |do_dvi_commands| procedure, which produces the output for an entire page, assuming that the |bop| command for that page has already been processed. This procedure is essentially an interpretive routine that reads and acts on the \.{DVI} commands. It is also capable of executing the typesetting commands for a character in a virtual font. @ The definition of \.{DVI} files refers to six registers, $(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units. These units come directly from the input file except they need to be rescaled when typesetting characters from a virtual font. The stack of $(h,v,w,x,y,z)$ values is represented by six arrays called |hstack|, \dots, |zstack|. @= integer w;integer x;integer y;integer z; /* current state values (|h| and |v| have already been declared) */ integer hstack[(stack_size+1)]; integer vstack[(stack_size+1)]; integer wstack[(stack_size+1)]; integer xstack[(stack_size+1)]; integer ystack[(stack_size+1)]; integer zstack[(stack_size+1)]; /* pushed down values in \.{DVI} units */ integer stk_siz; /* the current stack size */ double dvi_scale; /* converts units of current input source to \.{DVI} units */ @ @= mpx->dvi_scale=1.0; mpx->stk_siz=0; mpx->h=0; mpx->v=0; mpx->Xslant = 0.0; mpx->Xheight = 0.0 @ Next, we need procedures to handle |push| and |pop| commands. @c @ static void mpx_do_push (MPX mpx) { if ( mpx->stk_siz==stack_size ) mpx_abort(mpx,"DVItoMP capacity exceeded (stack size=%d)",stack_size); @.DVItoMP capacity exceeded...@> mpx->hstack[mpx->stk_siz]=mpx->h; mpx->vstack[mpx->stk_siz]=mpx->v; mpx->wstack[mpx->stk_siz]=mpx->w; mpx->xstack[mpx->stk_siz]=mpx->x; mpx->ystack[mpx->stk_siz]=mpx->y; mpx->zstack[mpx->stk_siz]=mpx->z; incr(mpx->stk_siz); } static void mpx_do_pop (MPX mpx) { if ( mpx->stk_siz==0 ) bad_dvi("attempt to pop empty stack"); else { decr(mpx->stk_siz); mpx->h=mpx->hstack[mpx->stk_siz]; mpx->v=mpx->vstack[mpx->stk_siz]; mpx->w=mpx->wstack[mpx->stk_siz]; mpx->x=mpx->xstack[mpx->stk_siz]; mpx->y=mpx->ystack[mpx->stk_siz]; mpx->z=mpx->zstack[mpx->stk_siz]; } } @ The |set_virtual_char| procedure is mutually recursive with |do_dvi_commands|. This is really a supervisory @^recursion@> procedure that calls |do_set_char| or adjusts the input source to read typesetting commands for a character in a virtual font. @c static void mpx_do_dvi_commands (MPX mpx); static void mpx_set_virtual_char (MPX mpx,integer f, integer c) { double old_scale; /* original value of |dvi_scale| */ int old_buf_ptr; /* original value of the input pointer |buf_ptr| */ int old_fbase,old_ftop; /* originally applicable part of the |font_num| table */ if ( mpx->fbase[f]==0 ) mpx_do_set_char(mpx, f,c); else { old_fbase=mpx->cur_fbase; old_ftop=mpx->cur_ftop; mpx->cur_fbase=mpx->fbase[f]; mpx->cur_ftop=mpx->ftop[f]; old_scale=mpx->dvi_scale; mpx->dvi_scale=mpx->dvi_scale*mpx->font_scaled_size[f]; old_buf_ptr=mpx->buf_ptr; mpx->buf_ptr=start_cmd(f,c); mpx_do_push(mpx); mpx_do_dvi_commands(mpx); mpx_do_pop(mpx);@/ mpx->buf_ptr=old_buf_ptr; mpx->dvi_scale=old_scale; mpx->cur_fbase=old_fbase; mpx->cur_ftop=old_ftop; } } @ Before we get into the details of |do_dvi_commands|, it is convenient to consider a simpler routine that computes the first parameter of each opcode. @d four_cases(A) (A): case (A)+1: case (A)+2: case (A)+3 @d eight_cases(A) four_cases((A)): case four_cases((A)+4) @d sixteen_cases(A) eight_cases((A)): case eight_cases((A)+8) @d thirty_two_cases(A) sixteen_cases((A)): case sixteen_cases((A)+16) @d sixty_four_cases(A) thirty_two_cases((A)): case thirty_two_cases((A)+32) @= static integer mpx_first_par (MPX mpx, unsigned int o) { switch (o) { case sixty_four_cases(set_char_0): case sixty_four_cases(set_char_0+64): return (o-set_char_0); break; case set1: case put1: case fnt1: case xxx1: case fnt_def1: return mpx_get_byte(mpx); break; case set1+1: case put1+1: case fnt1+1: case xxx1+1: case fnt_def1+1: return mpx_get_two_bytes(mpx); break; case set1+2: case put1+2: case fnt1+2: case xxx1+2: case fnt_def1+2: return mpx_get_three_bytes(mpx); break; case right1: case w1: case x1: case down1: case y1: case z1: return mpx_signed_byte(mpx); break; case right1+1: case w1+1: case x1+1: case down1+1: case y1+1: case z1+1: return mpx_signed_pair(mpx); break; case right1+2: case w1+2: case x1+2: case down1+2: case y1+2: case z1+2: return mpx_signed_trio(mpx); break; case set1+3: case set_rule: case put1+3: case put_rule: case right1+3: case w1+3: case x1+3: case down1+3: case y1+3: case z1+3: case fnt1+3: case xxx1+3: case fnt_def1+3: return mpx_signed_quad(mpx); break; case nop: case bop: case eop: case push: case pop: case pre: case post: case post_post: case undefined_commands: return 0; break; case w0: return mpx->w; break; case x0: return mpx->x; break; case y0: return mpx->y; break; case z0: return mpx->z; break; case sixty_four_cases(fnt_num_0): return (o-fnt_num_0); break; } return 0; /* compiler warning */ } @ Here is the |do_dvi_commands| procedure. @c static void mpx_do_dvi_commands (MPX mpx) { unsigned int o; /* operation code of the current command */ integer p,q; /* parameters of the current command */ integer cur_font; /* current internal font number */ if ( (mpx->cur_fbasecur_ftop) && (mpx->buf_ptrfont_num[mpx->cur_ftop-1]); /* select first local font */ else cur_font=max_fnums+1; /* current font is undefined */ mpx->w=0; mpx->x=0; mpx->y=0; mpx->z=0; /* initialize the state variables */ while ( true ) { @; } } @ The multiway switch in |first_par|, above, was organized by the length of each command; the one in |do_dvi_commands| is organized by the semantics. @ @= { o=mpx_get_byte(mpx); p=mpx_first_par(mpx, o); if ( feof(mpx->dvi_file) ) bad_dvi("the DVI file ended prematurely"); @.the DVI file ended prematurely@> if ( omax_fnums ) { if ( mpx->vf_reading ) mpx_abort(mpx,"no font selected for character %d in virtual font", p); else bad_dvi_two("no font selected for character %d",p); } @.no font selected@> mpx_set_virtual_char(mpx, cur_font,p); mpx->h += @; } else { switch(o) { case four_cases(put1): mpx_set_virtual_char(mpx, cur_font, p); break; case set_rule: q=trunc(mpx_signed_quad(mpx)*mpx->dvi_scale); mpx_do_set_rule(mpx, trunc(p*mpx->dvi_scale),q); mpx->h += q; break; case put_rule: q=trunc(mpx_signed_quad(mpx)*mpx->dvi_scale); mpx_do_set_rule(mpx, trunc(p*mpx->dvi_scale),q); break; @@; case undefined_commands: bad_dvi_two("undefined command %d",o); @.undefined command@> break; } /* all cases have been enumerated */ } } @ @= case four_cases(xxx1): mpx_do_xxx(mpx, p); break; case pre: case post: case post_post: bad_dvi("preamble or postamble within a page!"); @.preamble or postamble within a page@> break; @ @= case nop: break; case bop: bad_dvi("bop occurred before eop"); @.bop occurred before eop@> break; case eop: if ( mpx->stk_siz!=0 ) bad_dvi("stack not empty at end of page"); @.stack not empty...@> return; break; case push: mpx_do_push(mpx); break; case pop: mpx_do_pop(mpx); break; @ @= case four_cases(right1): mpx->h += trunc(p*mpx->dvi_scale); break; case w0: case four_cases(w1): mpx->w = trunc(p*mpx->dvi_scale); mpx->h += mpx->w; break; case x0: case four_cases(x1): mpx->x = trunc(p*mpx->dvi_scale); mpx->h += mpx->x; break; case four_cases(down1): mpx->v += trunc(p*mpx->dvi_scale); break; case y0: case four_cases(y1): mpx->y = trunc(p*mpx->dvi_scale); mpx->v += mpx->y; break; case z0: case four_cases(z1): mpx->z = trunc(p*mpx->dvi_scale); mpx->v += mpx->z; break; @ @= case sixty_four_cases(fnt_num_0): case four_cases(fnt1): cur_font = mpx_select_font(mpx, p); break; case four_cases(fnt_def1): mpx_define_font(mpx, p); break; @* The main program. Now we are ready to put it all together. This is where \.{DVItoMP} starts, and where it ends. @c static int mpx_dvitomp (MPX mpx, char *dviname) { int k; mpx->dviname = dviname; mpx_open_dvi_file(mpx); @; mpx_open_mpxfile(mpx); fprintf (mpx->mpxfile,banner); while ( true ) { @; for (k=0;k<=10;k++) (void)mpx_signed_quad(mpx); @; mpx_start_picture(mpx); mpx_do_dvi_commands(mpx); mpx_stop_picture(mpx); fprintf(mpx->mpxfile,"mpxbreak\n"); } if ( mpx->history<=mpx_cksum_trouble ) return 0; else return mpx->history; } @ The main program needs a few global variables in order to do its work. @= integer k;integer p; /* general purpose registers */ integer numerator;integer denominator; /* stated conversion ratio */ @ @= { int p; p=mpx_get_byte(mpx); /* fetch the first byte */ if ( p!=pre ) bad_dvi("First byte isn""t start of preamble!"); @.First byte isn't...@> p=mpx_get_byte(mpx); /* fetch the identification byte */ if ( p!=id_byte ) mpx_warn(mpx,"identification in byte 1 should be %d!", id_byte); @.identification...should be n@> @; p=mpx_get_byte(mpx); /* fetch the length of the introductory comment */ while (p>0 ){ decr(p); (void)mpx_get_byte(mpx); } } @ The conversion factor |conv| is figured as follows: There are exactly |n/d| decimicrons per \.{DVI} unit, and 254000 decimicrons per inch, and |resolution| pixels per inch. Then we have to adjust this by the stated amount of magnification. No such adjustment is needed for |dvi_per_fix| since it is used to convert design sizes. @= mpx->numerator=mpx_signed_quad(mpx); mpx->denominator=mpx_signed_quad(mpx); if ( (mpx->numerator<=0)||(mpx->denominator<=0) ) bad_dvi("bad scale ratio in preamble"); @.bad scale ratio@> mpx->mag=mpx_signed_quad(mpx)/1000.0; if ( mpx->mag<=0.0 ) bad_dvi("magnification isn't positive"); @.magnification isn't positive@> mpx->conv=(mpx->numerator/254000.0)*(72.0/mpx->denominator)*mpx->mag; mpx->dvi_per_fix=(254000.0/mpx->numerator)*(mpx->denominator/72.27)/1048576.0; @ @= do { int p; k=mpx_get_byte(mpx); if ( (k>=fnt_def1)&&(k @ Global filenames. @= char *dviname; @* Color support. These changes support \.{dvips}-style ``\.{color push NAME}'' and ``\.{color pop}'' specials. We store a list of named colors, sorted by name, and decorate the relevant drawing commands with ``\.{withcolor (r,g,b)}'' specifiers while a color is defined. @ A constant bounding the size of the named-color array. @d max_named_colors 100 /* maximum number of distinct named colors */ @ Then we declare a record for color types. @= typedef struct named_color_record { char *name; /* color name */ char *value; /* text to pass to MetaPost */ } named_color_record; @ Declare the named-color array itself. @= named_color_record named_colors[(max_named_colors+1)]; /* stores information about named colors, in sorted order by name */ integer num_named_colors; /* number of elements of |named_colors| that are valid */ @ This function, used only during initialization, defines a named color. @c static void mpx_def_named_color (MPX mpx, char *n, char *v) { mpx->num_named_colors++; assert(mpx->num_named_colorsnamed_colors[mpx->num_named_colors].name = n; mpx->named_colors[mpx->num_named_colors].value = v; } @ @= static void mpx_def_named_color (MPX mpx, char *n, char *v); @ During the initialization phase, we define values for all the named colors defined in \.{colordvi.tex}. CMYK-to-RGB conversion by GhostScript. This list has to be sorted alphabetically! @= mpx->num_named_colors = 0; mpx_def_named_color(mpx, "Apricot", "(1.0, 0.680006, 0.480006)"); mpx_def_named_color(mpx, "Aquamarine", "(0.180006, 1.0, 0.7)"); mpx_def_named_color(mpx, "Bittersweet", "(0.760012, 0.0100122, 0.0)"); mpx_def_named_color(mpx, "Black", "(0.0, 0.0, 0.0)"); mpx_def_named_color(mpx, "Blue", "(0.0, 0.0, 1.0)"); mpx_def_named_color(mpx, "BlueGreen", "(0.15, 1.0, 0.669994)"); mpx_def_named_color(mpx, "BlueViolet", "(0.1, 0.05, 0.960012)"); mpx_def_named_color(mpx, "BrickRed", "(0.719994, 0.0, 0.0)"); mpx_def_named_color(mpx, "Brown", "(0.4, 0.0, 0.0)"); mpx_def_named_color(mpx, "BurntOrange", "(1.0, 0.489988, 0.0)"); mpx_def_named_color(mpx, "CadetBlue", "(0.380006, 0.430006, 0.769994)"); mpx_def_named_color(mpx, "CarnationPink", "(1.0, 0.369994, 1.0)"); mpx_def_named_color(mpx, "Cerulean", "(0.0600122, 0.889988, 1.0)"); mpx_def_named_color(mpx, "CornflowerBlue", "(0.35, 0.869994, 1.0)"); mpx_def_named_color(mpx, "Cyan", "(0.0, 1.0, 1.0)"); mpx_def_named_color(mpx, "Dandelion", "(1.0, 0.710012, 0.160012)"); mpx_def_named_color(mpx, "DarkOrchid", "(0.6, 0.2, 0.8)"); mpx_def_named_color(mpx, "Emerald", "(0.0, 1.0, 0.5)"); mpx_def_named_color(mpx, "ForestGreen", "(0.0, 0.880006, 0.0)"); mpx_def_named_color(mpx, "Fuchsia", "(0.45, 0.00998169, 0.919994)"); mpx_def_named_color(mpx, "Goldenrod", "(1.0, 0.9, 0.160012)"); mpx_def_named_color(mpx, "Gray", "(0.5, 0.5, 0.5)"); mpx_def_named_color(mpx, "Green", "(0.0, 1.0, 0.0)"); mpx_def_named_color(mpx, "GreenYellow", "(0.85, 1.0, 0.310012)"); mpx_def_named_color(mpx, "JungleGreen", "(0.0100122, 1.0, 0.480006)"); mpx_def_named_color(mpx, "Lavender", "(1.0, 0.519994, 1.0)"); mpx_def_named_color(mpx, "LimeGreen", "(0.5, 1.0, 0.0)"); mpx_def_named_color(mpx, "Magenta", "(1.0, 0.0, 1.0)"); mpx_def_named_color(mpx, "Mahogany", "(0.65, 0.0, 0.0)"); mpx_def_named_color(mpx, "Maroon", "(0.680006, 0.0, 0.0)"); mpx_def_named_color(mpx, "Melon", "(1.0, 0.539988, 0.5)"); mpx_def_named_color(mpx, "MidnightBlue", "(0.0, 0.439988, 0.569994)"); mpx_def_named_color(mpx, "Mulberry", "(0.640018, 0.0800061, 0.980006)"); mpx_def_named_color(mpx, "NavyBlue", "(0.0600122, 0.460012, 1.0)"); mpx_def_named_color(mpx, "OliveGreen", "(0.0, 0.6, 0.0)"); mpx_def_named_color(mpx, "Orange", "(1.0, 0.389988, 0.130006)"); mpx_def_named_color(mpx, "OrangeRed", "(1.0, 0.0, 0.5)"); mpx_def_named_color(mpx, "Orchid", "(0.680006, 0.360012, 1.0)"); mpx_def_named_color(mpx, "Peach", "(1.0, 0.5, 0.3)"); mpx_def_named_color(mpx, "Periwinkle", "(0.430006, 0.45, 1.0)"); mpx_def_named_color(mpx, "PineGreen", "(0.0, 0.75, 0.160012)"); mpx_def_named_color(mpx, "Plum", "(0.5, 0.0, 1.0)"); mpx_def_named_color(mpx, "ProcessBlue", "(0.0399878, 1.0, 1.0)"); mpx_def_named_color(mpx, "Purple", "(0.55, 0.139988, 1.0)"); mpx_def_named_color(mpx, "RawSienna", "(0.55, 0.0, 0.0)"); mpx_def_named_color(mpx, "Red", "(1.0, 0.0, 0.0)"); mpx_def_named_color(mpx, "RedOrange", "(1.0, 0.230006, 0.130006)"); mpx_def_named_color(mpx, "RedViolet", "(0.590018, 0.0, 0.660012)"); mpx_def_named_color(mpx, "Rhodamine", "(1.0, 0.180006, 1.0)"); mpx_def_named_color(mpx, "RoyalBlue", "(0.0, 0.5, 1.0)"); mpx_def_named_color(mpx, "RoyalPurple", "(0.25, 0.1, 1.0)"); mpx_def_named_color(mpx, "RubineRed", "(1.0, 0.0, 0.869994)"); mpx_def_named_color(mpx, "Salmon", "(1.0, 0.469994, 0.619994)"); mpx_def_named_color(mpx, "SeaGreen", "(0.310012, 1.0, 0.5)"); mpx_def_named_color(mpx, "Sepia", "(0.3, 0.0, 0.0)"); mpx_def_named_color(mpx, "SkyBlue", "(0.380006, 1.0, 0.880006)"); mpx_def_named_color(mpx, "SpringGreen", "(0.739988, 1.0, 0.239988)"); mpx_def_named_color(mpx, "Tan", "(0.860012, 0.580006, 0.439988)"); mpx_def_named_color(mpx, "TealBlue", "(0.119994, 0.980006, 0.640018)"); mpx_def_named_color(mpx, "Thistle", "(0.880006, 0.410012, 1.0)"); mpx_def_named_color(mpx, "Turquoise", "(0.15, 1.0, 0.8)"); mpx_def_named_color(mpx, "Violet", "(0.210012, 0.119994, 1.0)"); mpx_def_named_color(mpx, "VioletRed", "(1.0, 0.189988, 1.0)"); mpx_def_named_color(mpx, "White", "(1.0, 1.0, 1.0)"); mpx_def_named_color(mpx, "WildStrawberry", "(1.0, 0.0399878, 0.610012)"); mpx_def_named_color(mpx, "Yellow", "(1.0, 1.0, 0.0)"); mpx_def_named_color(mpx, "YellowGreen", "(0.560012, 1.0, 0.260012)"); mpx_def_named_color(mpx, "YellowOrange", "(1.0, 0.580006, 0.0)"); @ Color commands get a separate warning procedure. |warn| sets |history := mpx_warning_given|, which causes a nonzero exit status; but color errors are trivial and should leave the exit status zero. @d color_warn(A) mpx_warn(mpx,A) @d color_warn_two(A,B) mpx_warn(mpx,"%s%s",A,B) @ The |do_xxx| procedure handles DVI specials (defined with the |xxx1...xxx4| commands). @d XXX_BUF 256 @= static void mpx_do_xxx (MPX mpx, integer p) { unsigned char buf[(XXX_BUF+1)]; /* FIXME: Fixed size buffer. */ integer l, r, m, k, len; boolean found; int bufsiz = XXX_BUF; len = 0; while ( ( p > 0) && (len < bufsiz) ) { buf[len] = mpx_get_byte(mpx); decr(p); incr(len); } @ if ( p > 0 ) { color_warn("long \"color\" special ignored"); goto XXXX; } if ( @<|buf| contains a color pop command@> ) { @ } else if ( @<|buf| contains a color push command@> ) { @ } else { color_warn("unknown \"color\" special ignored"); goto XXXX; } XXXX: for (k = 1;k<=p;k++) (void)mpx_get_byte(mpx); } @ @= if ( (len <= 5) || (buf[0] != 'c') || (buf[1] != 'o') || (buf[2] != 'l') || (buf[3] != 'o') || (buf[4] != 'r') || (buf[5] != ' ') ) goto XXXX; @ @<|buf| contains a color push command@>= (len >= 11) && (buf[6] == 'p') && (buf[7] == 'u') && (buf[8] == 's') && (buf[9] == 'h') && (buf[10] == ' ') @ @<|buf| contains a color pop command@>= (len == 9) && (buf[6] == 'p') && (buf[7] == 'o') && (buf[8] == 'p') @ The \.{color push} and \.{pop} commands imply a color stack, so we need a global variable to hold that stack. @d max_color_stack_depth 10 /* maximum depth of saved color stack */ @ Here's the actual stack variables. @= integer color_stack_depth; /* current depth of saved color stack */ char *color_stack[(max_color_stack_depth+1)]; /* saved color stack */ @ Initialize the stack to empty. @= mpx->color_stack_depth = 0; @ \.{color pop} just pops the stack. @= mpx_finish_last_char(mpx); if (mpx->color_stack_depth > 0 ) { free(mpx->color_stack[mpx->color_stack_depth]); decr(mpx->color_stack_depth); } else { color_warn("color stack underflow"); } @ \.{color push} pushes a color onto the stack. @= mpx_finish_last_char(mpx); if ( mpx->color_stack_depth >= max_color_stack_depth ) mpx_abort(mpx,"color stack overflow"); incr(mpx->color_stack_depth); /* I don't know how to do string operations in Pascal. */ /* Skip over extra spaces after 'color push'. */ l = 11; while ( (l < len - 1) && (buf[l] == ' ') ) incr(l); if ( @<|buf[l]| contains an rgb command@> ) { @ } else if ( @<|buf[l]| contains a cmyk command@> ) { @ } else if ( @<|buf[l]| contains a gray command@> ) { @ } else { @ } @ @<|buf[l]| contains an rgb command@>= (l + 4 < len) && (buf[l] == 'r') && (buf[l+1] == 'g') && (buf[l+2] == 'b') && (buf[l+3] == ' ') @ @= l = l + 4; while ( (l < len) && (buf[l] == ' ') ) incr(l); /* Remove spaces at end of buf */ while ( (len > l) && (buf[len - 1] == ' ') ) decr(len); mpx->color_stack[mpx->color_stack_depth]=xmalloc(len-l+3,1); k = 0; @ @ @<|buf[l]| contains a gray command@>= (l + 5 < len) && (buf[l] == 'g') && (buf[l+1] == 'r') && (buf[l+2] == 'a') && (buf[l+3] == 'y') && (buf[l+4] == ' ') @ @= l = l + 5; while ( (l < len) && (buf[l] == ' ') ) incr(l); /* Remove spaces at end of buf */ while ( (len > l) && (buf[len - 1] == ' ') ) decr(len); mpx->color_stack[mpx->color_stack_depth]=xmalloc(len-l+9,1); strcpy(mpx->color_stack[mpx->color_stack_depth],"white*"); k = 6; @ @ @<|buf[l]| contains a cmyk command@>= (l + 5 < len) && (buf[l] == 'c') && (buf[l+1] == 'm') && (buf[l+2] == 'y') && (buf[l+3] == 'k') && (buf[l+4] == ' ') @ @= l = l + 5; while ( (l < len) && (buf[l] == ' ') ) incr(l); /* Remove spaces at end of buf */ while ( (len > l) && (buf[len - 1] == ' ') ) decr(len); mpx->color_stack[mpx->color_stack_depth]=xmalloc(len-l+7,1); strcpy(mpx->color_stack[mpx->color_stack_depth],"cmyk"); k = 4; @ @ @= mpx->color_stack[mpx->color_stack_depth][k] = '('; incr(k); while ( l < len ) { if ( buf[l] == ' ' ) { mpx->color_stack[mpx->color_stack_depth][k] = ','; while ( (l < len) && (buf[l] == ' ') ) incr(l); incr(k); } else { mpx->color_stack[mpx->color_stack_depth][k] = buf[l]; incr(l); incr(k); } } mpx->color_stack[mpx->color_stack_depth][k] = ')'; mpx->color_stack[mpx->color_stack_depth][k+1] = 0; @ Binary-search the |named_colors| array, then push the found color onto the stack. @= for (k = l;k<=len - 1;k++) { buf[k - l] = xchr(buf[k]); } buf[len - l] = 0; len = len - l; l = 1; r = mpx->num_named_colors; found = false; while ( (l <= r) && ! found ) { m = (l + r) / 2; k = strcmp((char *)(buf), mpx->named_colors[m].name); if ( k == 0 ) { mpx->color_stack[mpx->color_stack_depth]=xstrdup(mpx->named_colors[m].value); found = true; } else if ( k < 0 ) { r = m - 1; } else { l = m + 1; } } if (! found ) { color_warn_two("non-hardcoded color \"%s\" in \"color push\" command", buf); mpx->color_stack[mpx->color_stack_depth]=xstrdup((char *)(buf)); } @ Last but not least, this code snippet prints a \.{withcolor} specifier for the top of the color stack, if the stack is nonempty. @= if ( mpx->color_stack_depth > 0 ) { fprintf(mpx->mpxfile," withcolor %s\n",mpx->color_stack[mpx->color_stack_depth]); } @* \[4] Dmp. This program reads device-independent troff output files, and converts them into a symbolic form understood by MetaPost. Some of the code was borrowed from DVItoMP. It understands all the D? graphics functions that dpost does but it ignores `x X' device control functions such as `x X SetColor:...', `x X BeginPath:', and `x X DrawPath:...'. The output file is a sequence of MetaPost picture expressions, one for every page in the input file. It makes no difference where the input file comes from, but it is intended to process the result of running eqn and troff on the output of MPtoTR. Such a file contains one page for every btex...etex block in the original input. This program then creates a corresponding sequence of MetaPost picture expressions for use as an auxiliary input file. Since MetaPost expects such files to have the extension .mpx, the output is sometimes called an `mpx' file. @d SHIFTS 100 /* maximum number of characters with special shifts */ @d MAXCHARS 256 /* character codes fall in the range 0..MAXCHARS-1 */ @d is_specchar(c) (!mpx->gflag && (c)<=2) /* does charcode c identify a special char? */ @d LWscale 0.03 /* line width for graphics as a fraction of pointsize */ @d YCORR 12.0 /* V coordinate of reference point in (big) points */ @= int next_specfnt[(max_fnums+1)]; /* used to link special fonts together */ int shiftchar[SHIFTS]; /* charcode of character to shift, else -1 */ float shifth[SHIFTS]; float shiftv[SHIFTS]; /* shift vals/fontsize (y is upward) */ int shiftptr; /* number of entries in shift tables */ int shiftbase[(max_fnums+1)]; /* initial index into shifth,shiftv,shiftchar */ int specfnt; /* int. num. of first special font (or FCOUNT) */ int *specf_tail ; /* tail of specfnt list |(*specf_tail==FCOUNT)| */ float cursize; /* current type size in (big) points */ unsigned int curfont; /* internal number for current font */ float Xslant; /* degrees additional slant for all fonts */ float Xheight; /* yscale fonts to this height if nonzero */ float sizescale; /* groff font size scaling factor */ int gflag; /* non-zero if using groff fonts */ float unit; /* (big) points per troff unit (0 when unset) */ @ @= mpx->shiftptr = 0; mpx->specfnt = (max_fnums+1); mpx->specf_tail = &(mpx->specfnt); mpx->unit = 0.0; mpx->lnno = 0; /* this is a reset */ mpx->gflag = 0; mpx->h = 0; mpx->v = 0; @ @(mpxout.h@>= typedef char *(*mpx_file_finder)(MPX, const char *, const char *, int); enum mpx_filetype { mpx_tfm_format, /* kpse_tfm_format */ mpx_vf_format, /* kpse_vf_format */ mpx_trfontmap_format, /* kpse_mpsupport_format */ mpx_trcharadj_format, /* kpse_mpsupport_format */ mpx_desc_format, /* kpse_troff_font_format */ mpx_fontdesc_format, /* kpse_troff_font_format */ mpx_specchar_format /* kpse_mpsupport_format */ }; @ @= mpx_file_finder find_file; @ @= static char *mpx_find_file (MPX mpx, const char *nam, const char *mode, int ftype); @ @c static char *mpx_find_file (MPX mpx, const char *nam, const char *mode, int ftype) { (void) mpx; if (mode[0] != 'r' || (! access (nam,R_OK)) || ftype) { return strdup(nam); } return NULL; } @ @= mpx->find_file = mpx_find_file; @ @= static FILE *mpx_fsearch(MPX mpx, char *nam, int format); @ @c static FILE *mpx_fsearch(MPX mpx, char *nam, int format) { FILE *f = NULL; char *fname = (mpx->find_file)(mpx, nam, "r", format); if (fname) { f = fopen(fname, "rb"); mpx_report(mpx,"%p = fopen(%s,\"rb\")",f, fname); } return f; } @ Hash tables (or rather: AVL lists) @ @= typedef struct { char *name; int num; } avl_entry; @ @c static int mpx_comp_name (const void *pa, const void *pb, void *p) { (void)p; return strcmp (((const avl_entry *) pa)->name, ((const avl_entry *) pb)->name); } @ @c static struct avl_table *mpx_avl_create (MPX mpx) { struct avl_table *t; t = avl_create(mpx_comp_name, NULL, NULL); if (t==NULL) mpx_abort(mpx, "Memory allocation failure"); return t; } @ The only two operations on AVL lists are finding already existing items, or interning new items. Finding is handled by explicit |avl_find| calls where needed, but it is wise to have a wrapper around |avl_probe| to check for memory errors. @c static avl_entry * mpx_avl_probe(MPX mpx, struct avl_table *tab, avl_entry *p) { avl_entry *r = (avl_entry *)avl_probe(tab,p); if (r==NULL) mpx_abort(mpx,"Memory allocation failure"); return r; } @ Scanning Numbers The standard functions atoi(), atof(), and sscanf() provide ways of reading numbers from strings but they give no indication of how much of the string is consumed. These homemade versions don't parse scientific notation. @= char *arg_tail; /* char after the number just gotten; NULL on failure */ @ @c static int mpx_get_int(MPX mpx, char *s) { register int i, d, neg; if (s == NULL) goto BAD; for (neg = 0;; s++) { if (*s == '-') neg = !neg; else if (*s != ' ' && *s != '\t') break; } if (i = *s - '0', 0 > i || i > 9) goto BAD; while (d = *++s - '0', 0 <= d && d <= 9) i = 10 * i + d; mpx->arg_tail = s; return neg ? -i : i; BAD: mpx->arg_tail = NULL; return 0; } @ GROFF font description files use octal character codes |groff_font(5)|: The code can be any integer. If it starts with a 0 it will be interpreted as octal; if it starts with 0x or 0X it will be intepreted as hexadecimal. @c static int mpx_get_int_map(MPX mpx, char *s) { register int i; if (s == NULL) goto BAD; i = strtol(s, &(mpx->arg_tail), 0); if (s == mpx->arg_tail) goto BAD; return i; BAD: mpx->arg_tail = NULL; return 0; } @ Troff output files contain few if any non-integers, but this program is prepared to read floats whenever they seem reasonable; i.e., when the number is not being used for character positioning. (For non-PostScript applications h and v are usually in pixels and should be integers.) @c static float mpx_get_float(MPX mpx, char *s) { register int d, neg, digits; register float x, y; digits = 0; if (s != NULL) { for (neg = 0;; s++) { if (*s == '-') neg = !neg; else if (*s != ' ' && *s != '\t') break; } x = 0.0; while (d = *s - '0', 0 <= d && d <= 9) { x = 10.0 * x + d; digits++; s++; } if (*s == '.') { y = 1.0; while (d = *++s - '0', 0 <= d && d <= 9) { y /= 10.0; x += y * d; digits++; } } } if (digits == 0) { mpx->arg_tail = NULL; return 0.0; } mpx->arg_tail = s; return neg ? -x : x; } @ GROFF font description files have metrics field of comma-separated integers. Traditional troff have a float in this position. The value is not used anyway - thus just skip the value, eat all non-space chars. @c static float mpx_get_float_map(MPX mpx, char *s) { if (s != NULL) { while (isspace(*s)) s++; while (!isspace(*s) && *s) s++; } mpx->arg_tail = s; return 0; } @ Reading Initialization Files Read the database file, reserve internal font numbers and set the |font_name| entries. Each line in the database file contains |\t,PostScript-name>\t| or just |\t,PostScript-name>| if the \TeX\ name matches the PostScript name. (|\t| means one or more tabs.) @= struct avl_table *trfonts; @ @c static void mpx_read_fmap(MPX mpx, char *dbase) { FILE *fin; avl_entry *tmp; char *nam; /* a font name being read */ char *buf; mpx->nfonts = 0; fin = mpx_fsearch(mpx,dbase, mpx_trfontmap_format); if (fin==NULL) mpx_abort(mpx,"Cannot find %s", dbase); mpx->trfonts = mpx_avl_create (mpx); while ((buf = mpx_getline(mpx,fin)) != NULL) { if (mpx->nfonts == (max_fnums+1)) mpx_abort(mpx,"Need to increase max_fnums"); nam = buf; while (*buf && *buf != '\t') buf++; tmp = xmalloc(sizeof(avl_entry),1); tmp->name = nam; tmp->num = mpx->nfonts++; (void)mpx_avl_probe (mpx,mpx->trfonts, tmp) ; if (*buf) { *buf = 0; buf++; do buf++; while (*buf == '\t'); while (*buf && *buf != '\t'); buf++; /* skip over psname */ do buf++; while (*buf == '\t'); if (*buf) nam = buf; while (*buf); buf++; } mpx->font_name[tmp->num] = xstrdup(nam); mpx->font_num[tmp->num] = -1; /* indicate font is not mounted */ mpx->nfonts++; } mpx_fclose(mpx,fin); } @ Some characters need their coordinates shifted in order to agree with troff's view of the world. Logically, this information belongs in the font description files but it actually resides in a PostScript prolog that the troff output processor dpost reads. Since that file is in PostScript and subject to change, we read the same information from a small auxiliary file that gives shift amounts relative to the font size with y upward. GROFF NOTE: The PostScript prologue in GNU groff's font directory does not contain any character shift information, so the following function becomes redundant. Simply keeping an empty "trchars.adj" file around will do fine without requiring any changes to this program. @c static void mpx_read_char_adj(MPX mpx, char *adjfile) { FILE *fin; char buf[200]; avl_entry tmp, *p; unsigned int i; fin = mpx_fsearch(mpx,adjfile, mpx_trcharadj_format); if (fin==NULL) mpx_abort(mpx,"Cannot find %s", adjfile); for (i = 0; i < mpx->nfonts; i++) mpx->shiftbase[i] = 0; while (fgets(buf, 200, fin) != NULL) { if (mpx->shiftptr == SHIFTS - 1) mpx_abort(mpx,"Need to increase SHIFTS"); if (buf[0] != ' ' && buf[0] != '\t') { for (i = 0; buf[i] != '\0'; i++) if (buf[i] == '\n') buf[i] = '\0'; mpx->shiftchar[mpx->shiftptr++] = -1; tmp.name = buf; p = (avl_entry *)avl_find (mpx->trfonts, &tmp); if (p==NULL) mpx_abort(mpx,"%s refers to unknown font %s", adjfile, buf); mpx->shiftbase[p->num] = mpx->shiftptr; } else { mpx->shiftchar[mpx->shiftptr] = mpx_get_int(mpx,buf); mpx->shifth[mpx->shiftptr] = mpx_get_float(mpx,mpx->arg_tail); mpx->shiftv[mpx->shiftptr] = -mpx_get_float(mpx,mpx->arg_tail); if (mpx->arg_tail == NULL) mpx_abort(mpx,"Bad shift entry : \"%s\"", buf); mpx->shiftptr++; } } mpx->shiftchar[mpx->shiftptr++] = -1; mpx_fclose(mpx,fin); } @ Read the DESC file of the troff device to gather information about sizescale and whether running under groff. Ignore all commands not specially handled. This relieves of collecting commands without arguments here and also makes the program more robust in case of future DESC extensions. @c static void mpx_read_desc(MPX mpx) { const char *const k1[] = { "res", "hor", "vert", "unitwidth", "paperwidth", "paperlength", "biggestfont", "spare2", "encoding", NULL }; const char *const g1[] = { "family", "paperheight", "postpro", "prepro", "print", "image_generator", "broken", NULL }; char cmd[200]; FILE *fp; int i, n; fp = mpx_fsearch(mpx,"DESC", mpx_desc_format); if (fp==NULL) mpx_abort(mpx,"Cannot find DESC"); while (fscanf(fp, "%199s", cmd) != EOF) { if (*cmd == '#') { while ((i = getc(fp)) != EOF && i != '\n'); continue; } if (strcmp(cmd, "fonts") == 0) { if (fscanf(fp, "%d", &n) != 1) return; for (i = 0; i < n; i++) if (fscanf(fp, "%*s") == EOF) return; } else if (strcmp(cmd, "sizes") == 0) { while (fscanf(fp, "%d", &n) == 1 && n != 0); } else if (strcmp(cmd, "styles") == 0 || strcmp(cmd, "papersize") == 0) { mpx->gflag++; while ((i = getc(fp)) != EOF && i != '\n'); } else if (strcmp(cmd, "sizescale") == 0) { if (fscanf(fp, "%d", &n) == 1) mpx->sizescale = n; mpx->gflag++; } else if (strcmp(cmd, "charset") == 0) { return; } else { for (i = 0; k1[i]; i++) if (strcmp(cmd, k1[i]) == 0) { if (fscanf(fp, "%*s") == EOF) return; break; } if (k1[i] == 0) for (i = 0; g1[i]; i++) if (strcmp(cmd, g1[i]) == 0) { if (fscanf(fp, "%*s") == EOF) return; mpx->gflag = 1; break; } } } } @ Given one line from the character description file for the font with internal number f, save the appropriate data in the charcodes[f] table. A return value of zero indicates a syntax error. GROFF: GNU groff uses an extended font description file format documented in |groff_font(5)|. In order to allow parsing of groff's font files, this function needs to be rewritten as follows: \item{1.}The `metrics' field parsed by |mpx_get_float(lin);| may include a comma-separated list of up to six decimal integers rather than just a single floating-point number. \item{2.}The `charcode' field parsed by |lastcode = mpx_get_int(arg_tail);| may be given either in decimal, octal, or hexadecimal format. @ @= struct avl_table *charcodes[(max_fnums+1)]; /* hash tables for translating char names */ @ @c static int mpx_scan_desc_line(MPX mpx, int f, char *lin) { static int lastcode; avl_entry *tmp; char *s, *t; t = lin; while (*lin != ' ' && *lin != '\t' && *lin != '\0') lin++; s = xmalloc(lin-t+1,1); strncpy(s,t,lin-t); while (*lin == ' ' || *lin == '\t') lin++; if (*lin == '"') { if (lastcode < MAXCHARS) { tmp = xmalloc(sizeof(avl_entry),1); tmp->name = s ; tmp->num = lastcode; (void)mpx_avl_probe (mpx, mpx->charcodes[f],tmp); } } else { (void) mpx_get_float_map(mpx,lin); (void) mpx_get_int(mpx,mpx->arg_tail); lastcode = mpx_get_int_map(mpx,mpx->arg_tail); if (mpx->arg_tail == NULL) return 0; if (lastcode < MAXCHARS) { tmp = xmalloc(sizeof(avl_entry),1); tmp->name = s ; tmp->num = lastcode; (void)mpx_avl_probe (mpx, mpx->charcodes[f],tmp); } } return 1; } @ Read the font description file for the font with the given troff name and update the data structures. The result is the internal font number. @c static int mpx_read_fontdesc(MPX mpx, char *nam) { /* troff name */ char buf[200]; avl_entry tmp, *p; FILE *fin; /* input file */ int f; /* internal font number */ if (mpx->unit == 0.0) mpx_abort(mpx, "Resolution is not set soon enough"); tmp.name = nam; p = (avl_entry *)avl_find (mpx->trfonts, &tmp); if (p == NULL) mpx_abort(mpx, "Font was not in map file"); f = p->num; fin = mpx_fsearch(mpx, nam, mpx_fontdesc_format); if (fin==NULL) mpx_abort(mpx,"Cannot find %s", nam); for (;;) { if (fgets(buf, 200, fin) == NULL) mpx_abort(mpx, "Description file for %s ends unexpectedly", nam); if (strncmp(buf, "special", 7) == 0) { *(mpx->specf_tail) = f; mpx->next_specfnt[f] = (max_fnums+1); mpx->specf_tail = &(mpx->next_specfnt[f]); } else if (strncmp(buf, "charset", 7) == 0) break; } mpx->charcodes[f] = mpx_avl_create (mpx); while (fgets(buf, 200, fin) != NULL) if (mpx_scan_desc_line(mpx, f, buf) == 0) mpx_abort(mpx, "%s has a bad line in its description file: %s", nam, buf); mpx_fclose(mpx,fin); return f; } @ Page and Character Output @= boolean graphics_used; /* nonzero if any graphics seen on this page */ float dmp_str_h1; float dmp_str_v; /* corrected start pos for current out string */ float dmp_str_h2; /* where the current output string ends */ float str_size; /* point size for this text string */ @ Print any transformations required by the current Xslant and Xheight settings. @= static void mpx_slant_and_ht(MPX mpx); @ @c static void mpx_slant_and_ht(MPX mpx) { int i = 0; if (mpx->Xslant != 0.0) { fprintf(mpx->mpxfile, " slanted%.5f", mpx->Xslant); i++; } if (mpx->Xheight != mpx->cursize && mpx->Xheight != 0.0 && mpx->cursize != 0.0) { fprintf(mpx->mpxfile, " yscaled%.4f", mpx->Xheight / mpx->cursize); i++; } if (i > 0) fprintf(mpx->mpxfile, "\n "); } @ Output character number c in the font with internal number f. @c static void mpx_set_num_char(MPX mpx, int f, int c) { float hh, vv; /* corrected versions of h, v */ int i; hh = mpx->h; vv = mpx->v; for (i = mpx->shiftbase[f]; mpx->shiftchar[i] >= 0; i++) if (mpx->shiftchar[i] == c) { hh += (mpx->cursize / mpx->unit) * mpx->shifth[i]; vv += (mpx->cursize / mpx->unit) * mpx->shiftv[i]; break; } if (hh - mpx->dmp_str_h2 >= 1.0 || mpx->dmp_str_h2 - hh >= 1.0 || vv - mpx->dmp_str_v >= 1.0 || mpx->dmp_str_v - vv >= 1.0 || f != mpx->str_f || mpx->cursize != mpx->str_size) { if (mpx->str_f >= 0) mpx_finish_last_char(mpx); else if (!mpx->fonts_used) mpx_prepare_font_use(mpx); /* first font usage on this page */ if (!mpx->font_used[f]) mpx_first_use(mpx,f); /* first use of font f on this page */ fprintf(mpx->mpxfile, "_s(("); mpx->print_col = 3; mpx->str_f = f; mpx->dmp_str_v = vv; mpx->dmp_str_h1 = hh; mpx->str_size = mpx->cursize; } mpx_print_char(mpx,c); mpx->dmp_str_h2 = hh + char_width(f,c); } @ Output a string. @c static void mpx_set_string(MPX mpx, char *cname) { float hh; /* corrected version of h, current horisontal position */ if (!*cname) return; hh = mpx->h; mpx_set_num_char(mpx,mpx->curfont, *cname); hh += char_width(mpx->curfont,(int)*cname); while (*++cname) { mpx_print_char(mpx,*cname); hh += char_width(mpx->curfont,(int)*cname); } mpx->h = (double)floor(hh+0.5); mpx_finish_last_char(mpx); } @ Special Characters Given the troff name of a special character, this routine finds its definition and copies it to the MPX file. It also finds the name of the vardef macro and returns that name. The name should be C.. @ TH: A bit of trickery is added here for case-insensitive file systems. This aliasing allows the CHARLIB directory to exist on DVDs, for example. It is a hack, I know. I've stuck to names on TeXLive. @d test_redo_search do { if (deff==NULL) deff = mpx_fsearch(mpx, cname, mpx_specchar_format); } while (0) @c static char *mpx_copy_spec_char(MPX mpx, char *cname) { FILE *deff; int c; char *s, *t; char specintro[] = "vardef "; /* MetaPost name follows this */ unsigned k = 0; /* how much of specintro so far */ if (strcmp(cname, "ao") == 0) { deff = mpx_fsearch(mpx, "ao.x", mpx_specchar_format); test_redo_search; } else if (strcmp(cname, "lh") == 0) { deff = mpx_fsearch(mpx, "lh.x", mpx_specchar_format); test_redo_search; } else if (strcmp(cname, "~=") == 0) { deff = mpx_fsearch(mpx, "twiddle", mpx_specchar_format); test_redo_search; } else { deff = mpx_fsearch(mpx, cname, mpx_specchar_format); } if (deff==NULL) mpx_abort(mpx, "No vardef in charlib/%s", cname); while (k < strlen(specintro)) { if ((c = getc(deff)) == EOF) mpx_abort(mpx, "No vardef in charlib/%s", cname); putc(c, mpx->mpxfile); if (c == specintro[k]) k++; else k = 0; } s = xmalloc(mpx->bufsize,1); t = s ; while ((c = getc(deff)) != '(') { if (c == EOF) mpx_abort(mpx, "vardef in charlib/%s has no arguments", cname); putc(c, mpx->mpxfile); *t++ = c; } putc(c, mpx->mpxfile); *t++ = '\0'; while ((c = getc(deff)) != EOF); putc(c, mpx->mpxfile); return s; } @ When given a character name instead of a number, we need to check if it is a special character and download the definition if necessary. If the character is not in the current font we have to search the special fonts. @= struct avl_table *spec_tab; @ The |spec_tab| avl table combines character names with macro names. @= typedef struct { char *name; char *mac; } spec_entry; @ @c static void mpx_set_char(MPX mpx, char *cname) { int f, c; avl_entry tmp, *p; spec_entry *sp; if (*cname == ' ' || *cname == '\t') return; f = mpx->curfont; tmp.name = cname; p = avl_find(mpx->charcodes[f], &tmp); if (p==NULL) { for (f = mpx->specfnt; f != (max_fnums+1); f = mpx->next_specfnt[f]) { p = avl_find(mpx->charcodes[f], &tmp); if (p!=NULL) goto OUT; } mpx_abort(mpx, "There is no character %s", cname); } OUT: c = p->num; if (!is_specchar(c)) { mpx_set_num_char(mpx, f, c); } else { if (mpx->str_f >= 0) mpx_finish_last_char(mpx); if (!mpx->fonts_used) mpx_prepare_font_use(mpx); if (!mpx->font_used[f]) mpx_first_use(mpx, f); if (mpx->spec_tab) mpx->spec_tab = mpx_avl_create (mpx); sp = xmalloc(sizeof(spec_entry),1); sp->name = cname; sp->mac = NULL; sp = (spec_entry *)avl_probe(mpx->spec_tab,sp); if (sp==NULL) mpx_abort(mpx,"Memory allocation failure"); if (sp->mac == NULL) { sp->mac = mpx_copy_spec_char(mpx, cname); /* this won't be NULL */ } fprintf(mpx->mpxfile, "_s(%s(n%d)", sp->mac, mpx->font_num[f]); mpx_slant_and_ht(mpx); fprintf(mpx->mpxfile, ",%.5f,%.4f,%.4f);\n", (mpx->cursize/mpx->font_design_size[f])*1.00375, mpx->h*mpx->unit, YCORR-mpx->v*mpx->unit); } } @ Font Definitions Mount the font with troff name nam at external font number n and read any necessary font files. @c static void mpx_do_font_def(MPX mpx, int n, char *nam) { int f; unsigned k; avl_entry tmp, *p; tmp.name = nam; p = (avl_entry *) avl_find (mpx->trfonts, &tmp); if (p==NULL) mpx_abort(mpx, "Font %s was not in map file", nam); f = p->num; if ( mpx->info_base[f]==max_widths ) { mpx_read_fontdesc(mpx, nam); mpx->cur_name = xstrdup(mpx->font_name[f]); if (! mpx_open_tfm_file(mpx) ) font_abort("No TFM file found for ",f); @.no TFM file found@> mpx_in_TFM(mpx, f); } for (k = 0; k < mpx->nfonts; k++) if (mpx->font_num[k] == n) mpx->font_num[k] = -1; mpx->font_num[f] = n; @; } @ Time on `makepath pencircle' Given the control points of a cubic Bernstein polynomial, evaluate it at t. @d Speed ((float) (PI/4.0)) @c static float mpx_b_eval(const float *xx, float t) { float zz[4]; register int i, j; for (i = 0; i <= 3; i++) zz[i] = xx[i]; for (i = 3; i > 0; i--) for (j = 0; j < i; j++) zz[j] += t * (zz[j + 1] - zz[j]); return zz[0]; } @ Find the direction angle at time t on the path `makepath pencircle'. The tables below give the Bezier control points for MetaPost's cubic approximation to the first octant of a unit circle. @c static const float xx[] = { 1.0, 1.0, 0.8946431597, 0.7071067812 }; static const float yy[] = { 0.0, 0.2652164899, 0.5195704026, 0.7071067812 }; @ @c static float mpx_circangle(float t) { float ti; ti = floor(t); t -= ti; return (float) atan(mpx_b_eval(yy, t) / mpx_b_eval(xx, t)) + ti * Speed; } @ Find the spline parameter where `makepath pencircle' comes closest to (cos(a)/2,sin(a)/2). @c static float mpx_circtime(float a) { int i; float t; t = a / Speed; for (i = 2; --i >= 0;) t += (a - mpx_circangle(t)) / Speed; return t; } @ Troff Graphics @= float gx; float gy; /* current point for graphics (init. (h,YCORR/mpx->unit-v) */ @ @c static void mpx_prepare_graphics(MPX mpx) { fprintf(mpx->mpxfile, "vardef _D(expr _d)expr _q =\n"); fprintf(mpx->mpxfile, " addto _p doublepath _q withpen pencircle scaled _d; enddef;\n"); mpx->graphics_used = true; } @ This function prints the current position (gx,gy). Then if it can read dh dv from string s, it increments (gx,gy) and prints "--". By returning the rest of the string s or NULL if nothing could be read from s, it provides the argument for the next iteration. @c static char *mpx_do_line(MPX mpx, char *s) { float dh, dv; fprintf(mpx->mpxfile, "(%.3f,%.3f)", mpx->gx * mpx->unit, mpx->gy * mpx->unit); dh = mpx_get_float(mpx, s); dv = mpx_get_float(mpx, mpx->arg_tail); if (mpx->arg_tail == NULL) return NULL; mpx->gx += dh; mpx->gy -= dv; fprintf(mpx->mpxfile, "--\n"); return mpx->arg_tail; } @ Function |spline_seg()| reads two pairs of (dh,dv) increments and prints the corresponding quadratic B-spline segment, leaving the ending point to be printed next time. The return value is the string with the first (dh,dv) pair lopped off. If only one pair of increments is found, we prepare to terminate the iteration by printing last time's ending point and returning NULL. @c static char * mpx_spline_seg(MPX mpx, char *s) { float dh1, dv1, dh2, dv2; dh1 = mpx_get_float(mpx, s); dv1 = mpx_get_float(mpx, mpx->arg_tail); if (mpx->arg_tail == NULL) mpx_abort(mpx, "Missing spline increments"); s = mpx->arg_tail; fprintf(mpx->mpxfile, "(%.3f,%.3f)", (mpx->gx + .5 * dh1) * mpx->unit, (mpx->gy - .5 * dv1) * mpx->unit); mpx->gx += dh1; mpx->gy -= dv1; dh2 = mpx_get_float(mpx, s); dv2 = mpx_get_float(mpx, mpx->arg_tail); if (mpx->arg_tail == NULL) return NULL; fprintf(mpx->mpxfile, "..\ncontrols (%.3f,%.3f) and (%.3f,%.3f)..\n", (mpx->gx - dh1 / 6.0) * mpx->unit, (mpx->gy + dv1 / 6.0) * mpx->unit, (mpx->gx + dh2 / 6.0) * mpx->unit, (mpx->gy - dv2 / 6.0) * mpx->unit); return s; } @ Draw an ellipse with the given major and minor axes. @c static void mpx_do_ellipse(MPX mpx, float a, float b) { fprintf(mpx->mpxfile, "makepath(pencircle xscaled %.3f\n yscaled %.3f", a * mpx->unit, b * mpx->unit); fprintf(mpx->mpxfile, " shifted (%.3f,%.3f));\n", (mpx->gx + .5 * a) * mpx->unit, mpx->gy * mpx->unit); mpx->gx += a; } @ Draw a counter-clockwise arc centered at (cx,cy) with initial and final radii (ax,ay) and (bx,by) respectively. @c static void mpx_do_arc(MPX mpx, float cx, float cy, float ax, float ay, float bx, float by) { float t1, t2; t1 = mpx_circtime(atan2(ay, ax)); t2 = mpx_circtime(atan2(by, bx)); if (t2 < t1) t2 += 8.0; fprintf(mpx->mpxfile, "subpath (%.5f,%.5f) of\n", t1, t2); fprintf(mpx->mpxfile, " makepath(pencircle scaled %.3f shifted (%.3f,%.3f));\n", 2.0 * sqrt(ax * ax + ay * ay) * mpx->unit, cx * mpx->unit, cy * mpx->unit); mpx->gx = cx + bx; mpx->gy = cy + by; } @ String s is everything following the initial `D' in a troff graphics command. @c static void mpx_do_graphic(MPX mpx, char *s) { float h1, v1, h2, v2; mpx_finish_last_char(mpx); /* GROFF uses Fd to set fill color for solid drawing objects to the default, so just ignore that. */ if (s[0] == 'F' && s[1] == 'd') return; mpx->gx = (float) mpx->h; mpx->gy = YCORR / mpx->unit - ((float) mpx->v); if (!mpx->graphics_used) mpx_prepare_graphics(mpx); fprintf(mpx->mpxfile, "D(%.4f) ", LWscale * mpx->cursize); switch (*s++) { case 'c': h1 = mpx_get_float(mpx,s); if (mpx->arg_tail == NULL) mpx_abort(mpx,"Bad argument in %s", s-2); mpx_do_ellipse(mpx,h1, h1); break; case 'e': h1 = mpx_get_float(mpx,s); v1 = mpx_get_float(mpx,mpx->arg_tail); if (mpx->arg_tail == NULL) mpx_abort(mpx,"Bad argument in %s", s - 2); mpx_do_ellipse(mpx,h1, v1); break; case 'A': fprintf(mpx->mpxfile, "reverse "); /* fall through */ case 'a': h1 = mpx_get_float(mpx,s); v1 = mpx_get_float(mpx,mpx->arg_tail); h2 = mpx_get_float(mpx,mpx->arg_tail); v2 = mpx_get_float(mpx,mpx->arg_tail); if (mpx->arg_tail == NULL) mpx_abort(mpx,"Bad argument in %s", s - 2); mpx_do_arc(mpx,mpx->gx + h1, mpx->gy - v1, -h1, v1, h2, -v2); break; case 'l': case 'p': while (s != NULL) s = mpx_do_line(mpx,s); fprintf(mpx->mpxfile, ";\n"); break; case 'q': do s = mpx_spline_seg(mpx,s); while (s != NULL); fprintf(mpx->mpxfile, ";\n"); break; case '~': fprintf(mpx->mpxfile, "(%.3f,%.3f)--", mpx->gx * mpx->unit, mpx->gy * mpx->unit); do s = mpx_spline_seg(mpx,s); while (s != NULL); fprintf(mpx->mpxfile, "--(%.3f,%.3f);\n", mpx->gx * mpx->unit, mpx->gy * mpx->unit); break; default: mpx_abort(mpx,"Unknown drawing function %s", s - 2); } mpx->h = (int) floor(mpx->gx + .5); mpx->v = (int) floor(YCORR / mpx->unit + .5 - mpx->gy); } @ Interpreting Troff Output @c static void mpx_change_font(MPX mpx, int f) { for (mpx->curfont = 0; mpx->curfont < mpx->nfonts; mpx->curfont++) if (mpx->font_num[mpx->curfont] == f) return; mpx_abort(mpx,"Bad font setting"); } @ String s0 is everything following the initial `x' in a troff device control command. A zero result indicates a stop command. @c static int mpx_do_x_cmd(MPX mpx, char *s0) { float x; int n; char *s; s = s0; while (*s == ' ' || *s == '\t') s++; switch (*s++) { case 'r': if (mpx->unit != 0.0) mpx_abort(mpx,"Attempt to reset resolution"); while (*s != ' ' && *s != '\t') s++; mpx->unit = mpx_get_float(mpx,s); if (mpx->unit <= 0.0) mpx_abort(mpx,"Bad resolution: x %s", s0); mpx->unit = 72.0 / mpx->unit; break; case 'f': while (*s != ' ' && *s != '\t') s++; n = mpx_get_int(mpx,s); if (mpx->arg_tail == NULL) mpx_abort(mpx,"Bad font def: x %s", s0); s = mpx->arg_tail; while (*s == ' ' || *s == '\t') s++; mpx_do_font_def(mpx,n, s); break; case 's': return 0; case 'H': while (*s != ' ' && *s != '\t') s++; mpx->Xheight = mpx_get_float(mpx,s); /* GROFF troff output is scaled |groff_out(5)|: The argument to the s command is in scaled points (units of points/n, where n is the argument to the sizescale command in the DESC file.) The argument to the x Height command is also in scaled points. sizescale for groff devps is 1000 */ if (mpx->sizescale) { if (mpx->unit != 0.0) mpx->Xheight *= mpx->unit; /* ??? */ else mpx->Xheight /= mpx->sizescale; } if (mpx->Xheight == mpx->cursize) mpx->Xheight = 0.0; break; case 'S': while (*s != ' ' && *s != '\t') s++; mpx->Xslant = mpx_get_float(mpx,s) * (PI / 180.0); x = cos(mpx->Xslant); if (-1e-4 < x && x < 1e-4) mpx_abort(mpx,"Excessive slant"); mpx->Xslant = sin(mpx->Xslant) / x; break; default: /* do nothing */ ; } return 1; } @ This routine reads commands from the troff output file up to and including the next `p' or `x s' command. It also calls |set_num_char()| and |set_char()| to generate output when appropriate. A zero result indicates that there are no more pages to do. GROFF: GNU groff uses an extended device-independent output file format documented in |groff_out(5)|. In order to allow parsing of groff's output files, this function either needs to be extended to support the new command codes, or else the use of the "t" and "u" commands must be disabled by removing the line "tcommand" from the DESC file in the \$(prefix)/lib/groff/devps directory. @c static int mpx_do_page (MPX mpx, FILE *trf) { char *buf; char a, *c, *cc; mpx->h = mpx->v = 0; while ((buf = mpx_getline(mpx, trf)) != NULL) { mpx->lnno++; c = buf; while (*c != '\0') { switch (*c) { case ' ': case '\t': case 'w': c++; break; case 's': mpx->cursize = mpx_get_float(mpx,c + 1); /* GROFF troff output is scaled |groff_out(5)|: The argument to the s command is in scaled points (units of points/n, where n is the argument to the sizescale command in the DESC file.) The argument to the x Height command is also in scaled points. sizescale for groff devps is 1000 */ if (mpx->sizescale) { if (mpx->unit != 0.0) mpx->cursize *= mpx->unit; /* ??? */ else mpx->cursize /= mpx->sizescale; } goto iarg; case 'f': mpx_change_font(mpx, mpx_get_int(mpx,c + 1)); goto iarg; case 'c': if (c[1] == '\0') mpx_abort(mpx, "Bad c command in troff output"); cc = c + 2; goto set; case 'C': cc = c; do cc++; while (*cc != ' ' && *cc != '\t' && *cc != '\0'); goto set; case 'N': mpx_set_num_char(mpx, mpx->curfont, mpx_get_int(mpx,c + 1)); goto iarg; case 'H': mpx->h = mpx_get_int(mpx, c + 1); goto iarg; case 'V': mpx->v = mpx_get_int(mpx, c + 1); goto iarg; case 'h': mpx->h += mpx_get_int(mpx, c + 1); goto iarg; case 'v': mpx->v += mpx_get_int(mpx, c + 1); goto iarg; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (c[1] < '0' || c[1] > '9' || c[2] == '\0') mpx_abort(mpx, "Bad nnc command in troff output"); mpx->h += 10 * (c[0] - '0') + c[1] - '0'; c++; cc = c + 2; goto set; case 'p': return 1; case 'n': (void) mpx_get_int(mpx, c + 1); (void) mpx_get_int(mpx, mpx->arg_tail); goto iarg; case 'D': mpx_do_graphic(mpx, c + 1); goto eoln; case 'x': if (!mpx_do_x_cmd(mpx, c + 1)) return 0; goto eoln; case '#': goto eoln; case 'F': /* GROFF uses this command to report filename */ goto eoln; case 'm': /* GROFF uses this command to control color */ goto eoln; case 'u': /* GROFF uses this command to output a word with additional white space between characters, not implemented */ mpx_abort(mpx, "Bad command in troff output\n" "change the DESC file for your GROFF PostScript device, remove tcommand"); case 't': /* GROFF uses this command to output a word */ cc = c; do cc++; while (*cc != ' ' && *cc != '\t' && *cc != '\0'); a = *cc; *cc = '\0'; mpx_set_string(mpx, ++c); c = cc; *c = a; continue; default: mpx_abort(mpx, "Bad command in troff output"); } continue; set: a = *cc; *cc = '\0'; mpx_set_char(mpx, ++c); c = cc; *c = a; continue; iarg: c = mpx->arg_tail; } eoln: /* do nothing */ ; } return 0; } @ Main Dmp Program @d dbname "trfonts.map" /* file for table of troff \& TFM font names */ @d adjname "trchars.adj" /* file for character shift amounts */ @c static int mpx_dmp(MPX mpx, char *infile) { int more; FILE *trf = mpx_xfopen(mpx,infile, "r"); mpx_open_mpxfile(mpx); fprintf(mpx->mpxfile, banner); mpx_read_desc(mpx); mpx_read_fmap(mpx,dbname); if (!mpx->gflag) mpx_read_char_adj(mpx,adjname); if (mpx_do_page(mpx, trf)) { do { @; mpx_start_picture(mpx); more = mpx_do_page(mpx,trf); mpx_stop_picture(mpx); fprintf(mpx->mpxfile, "mpxbreak\n"); } while (more); } mpx_fclose(mpx,trf); mpx_fclose(mpx,mpx->mpxfile); if ( mpx->history<=mpx_cksum_trouble ) return 0; else return mpx->history; } @* \[5] Makempx. Make an MPX file from the labels in a MetaPost source file, using mpto and either dvitomp (TeX) or dmp (troff). Started from a shell script initially based on John Hobby's original version, that was then translated to C by Akira Kakuto (Aug 1997, Aug 2001), and updated and largely rewritten by Taco Hoekwater (Nov 2006). Differences between the script and this C version: The script trapped HUP, INT, QUIT and TERM for cleaning up temporary files. This is a refinement, and not portable. The script put its own directory in front of the executable search PATH. This is not portable either, and it seems a safe bet that normal users do not have 'mpto', 'dvitomp', or 'dmp' commands in their path. The command-line '-troff' now also accepts an optional argument. The troff infile for error diagnostics is renamed "mpxerr.i", not plain "mpxerr". The original script deleted mpx*.* in the cleanup process. That is a bit harder in C, because it requires reading the contents of the current directory. The current program assumes that opendir(), readdir() and closedir() are known everywhere where the function getcwd() exists (except on WIN32, where it uses |_findfirst| \& co). If this assumption is false, you can define |NO_GETCWD|, and makempx will revert to trying to delete only a few known extensions There is a -debug switch, preventing the removal of tmp files @d TMPNAME_EXT(a,b) { strcpy(a,tmpname); strcat(a,b); } @c #define TEXERR "mpxerr.tex" #define DVIERR "mpxerr.dvi" #define TROFF_INERR "mpxerr.i" #define TROFF_OUTERR "mpxerr.t" @ @c static void mpx_rename (MPX mpx, char *a, char *b) { mpx_report(mpx,"renaming %s to %s",a,b); rename(a,b); } @ @= char tex[15] ; int debug ; char *progname; @ Cleaning up @c static void mpx_default_erasetmp(MPX mpx) { char *wrk; char *p; if (mpx->mode==mpx_tex_mode) { wrk = xstrdup(mpx->tex); p = strrchr(wrk, '.'); *p = '\0'; strcat(wrk, ".aux"); remove(wrk); *p = '\0'; strcat(wrk, ".pdf"); remove(wrk); *p = '\0'; strcat(wrk, ".toc"); remove(wrk); *p = '\0'; strcat(wrk, ".idx"); remove(wrk); *p = '\0'; strcat(wrk, ".ent"); remove(wrk); *p = '\0'; strcat(wrk, ".out"); remove(wrk); *p = '\0'; strcat(wrk, ".nav"); remove(wrk); *p = '\0'; strcat(wrk, ".snm"); remove(wrk); *p = '\0'; strcat(wrk, ".tui"); remove(wrk); free(wrk); } } @ @= static void mpx_erasetmp(MPX mpx); @ @c static void mpx_cleandir(MPX mpx, char *cur_path) { char *wrk, *p; #ifdef _WIN32 struct _finddata_t c_file; long hFile; #else struct dirent *entry; DIR *d; #endif wrk = xstrdup(mpx->tex); p = strrchr(wrk, '.'); *p = '\0'; /* now wrk is identical to tmpname */ #ifdef _WIN32 strcat(cur_path,"/*"); if ((hFile = _findfirst (cur_path, &c_file)) == -1L) { mpx_default_erasetmp(mpx); } else { if (strstr(c_file.name,wrk)==c_file.name) remove(c_file.name); while (_findnext (hFile, &c_file) != -1L) { if (strstr(c_file.name,wrk)==c_file.name) remove(c_file.name); } _findclose (hFile); /* no more entries => close directory */ } #else if ((d = opendir(cur_path)) == NULL) { mpx_default_erasetmp(mpx); } else { while ((entry = readdir (d)) != NULL) { if (strstr(entry->d_name,wrk)==entry->d_name) remove(entry->d_name); } closedir(d); } #endif } @ It is important that |mpx_erasetmp| remains silent. If it find trouble, it should just ignore it. The string |cur_path| is a little bit larger than needed, because that allows the win32 code in |cleandir| to add the slash and asterisk for globbing without having to reallocate the variable first. @c #ifdef WIN32 #define GETCWD _getcwd #else #define GETCWD getcwd #endif static void mpx_erasetmp(MPX mpx) { char cur_path[1024]; if (mpx->debug) return; if (mpx->tex[0] != '\0') { remove(mpx->tex); if(GETCWD(cur_path,1020) == NULL) { mpx_default_erasetmp(mpx); /* don't know where we are */ } else { mpx_cleandir(mpx,cur_path); } } } @* Running the external typesetters. First, here is a helper for messaging. @c static char *mpx_print_command (MPX mpx, int cmdlength, char **cmdline) { char *s, *t; int i,l; (void)mpx; l = 0; for (i = 0; i < cmdlength ; i++) { l += strlen(cmdline[i])+1; } s = xmalloc(l,1); t=s; for (i = 0; i < cmdlength ; i++) { if (i>0) *t++ = ' '; t = strcpy(t,cmdline[i]); t += strlen(cmdline[i]); } return s; } @ This function unifies the external program calling across Posix-like and Win32 systems. @c static int do_spawn (MPX mpx, char *cmd, char **options) { #ifndef WIN32 int retcode = -1; pid_t child; child = fork(); if (child < 0) mpx_abort(mpx, "fork failed: %s", strerror(errno)); if (child == 0) { if(execvp(cmd, options)) mpx_abort(mpx, "exec failed: %s", strerror(errno)); } else { if (wait(&retcode)==child) { retcode = (WIFEXITED(retcode) ? WEXITSTATUS(retcode) : -1); } else { mpx_abort(mpx, "wait failed: %s", strerror(errno)); } } return retcode; #else return spawnvp(P_WAIT, cmd, options); #endif } @ @c #ifdef WIN32 #define nuldev "nul" #else #define nuldev "/dev/null" #endif static int mpx_run_command(MPX mpx, char *inname, char *outname, int count, char **cmdl) { int retcode; int sav_o, sav_i; /* for I/O redirection */ FILE *fr, *fw; /* read and write streams for the command */ if (count < 1 || cmdl == NULL || cmdl[0] == NULL) return -1; /* return non-zero by default, signalling an error */ mpx_report(mpx,"running command %s", mpx_print_command(mpx,count, cmdl)); fr = mpx_xfopen(mpx,(inname ? inname : nuldev), "r"); fw = mpx_xfopen(mpx,(outname ? outname : nuldev), "wb"); @; retcode = do_spawn(mpx,cmdl[0], cmdl); @; mpx_fclose(mpx,fr); mpx_fclose(mpx,fw); return retcode; } @ @ Running Troff is more likely than not a series of pipes that feed input to each other. Makempx does all of this itself by using temporary files inbetween. That means we have to juggle about with |stdin| and |stdout|. This is the only non-ansi C bit of makempx. @^system dependencies@> @= #ifdef WIN32 #define DUP _dup #define DUPP _dup2 #else #define DUP dup #define DUPP dup2 #endif sav_i = DUP(fileno(stdin)); sav_o = DUP(fileno(stdout)); DUPP(fileno(fr), fileno(stdin)); DUPP(fileno(fw), fileno(stdout)) @ @= DUPP(sav_i, fileno(stdin)); close(sav_i); DUPP(sav_o, fileno(stdout)); close(sav_o) @ The allocation of the array pointed to by |cmdline_addr| is of course much larger than is really needed, but it will still only be a few hunderd bytes at the most, and this ensures that the separate parts of the |maincmd| will all fit. @d split_command(a,b) mpx_do_split_command(mpx,a,&b,' ') @d split_pipes(a,b) mpx_do_split_command(mpx,a,&b,'|') @c static int mpx_do_split_command(MPX mpx, char *maincmd, char ***cmdline_addr, char target) { char *piece; char *cmd; char **cmdline; unsigned int i; int ret = 0; int in_string = 0; if (strlen(maincmd) == 0) return 0; i = sizeof(char *)*(strlen(maincmd)+1); cmdline = xmalloc(i,1); memset(cmdline,0,i); *cmdline_addr = cmdline; i = 0; while (maincmd[i] == ' ') i++; cmd = xstrdup(maincmd); piece = cmd; for (; i <= strlen(maincmd); i++) { if (in_string == 1) { if (cmd[i] == '"') { in_string = 0; } } else if (in_string == 2) { if (cmd[i] == '\'') { in_string = 0; } } else { if (cmd[i] == '"') { in_string = 1; } else if (cmd[i] == '\'') { in_string = 2; } else if (cmd[i] == target) { cmd[i] = 0; cmdline[ret++] = piece; while (i < strlen(maincmd) && cmd[(i + 1)] == ' ') i++; piece = cmd + i + 1; } } } if (*piece) { cmdline[ret++] = piece; } return ret; } @ @= char *maincmd; /* TeX command name */ @ @c static void mpx_command_cleanup (MPX mpx, char **cmdline) { (void)mpx; xfree(cmdline[0]); xfree(cmdline); } @ @c static void mpx_command_error (MPX mpx, int cmdlength, char **cmdline) { char *s = mpx_print_command(mpx, cmdlength, cmdline); mpx_command_cleanup(mpx, cmdline); mpx_abort(mpx, "Command failed: %s; see mpxerr.log", s); } @ @(mpxout.h@>= typedef struct makempx_options { int mode; char *cmd; char *mptexpre; char *mpname; char *mpxname; int debug; mpx_file_finder find_file; } makempx_options; int mp_makempx (makempx_options *mpxopt) ; @ @d ERRLOG "mpxerr.log" @d MPXLOG "makempx.log" @c int mp_makempx (makempx_options *mpxopt) { MPX mpx; char **cmdline, **cmdbits; char infile[15]; int retcode, i ; char tmpname[] = "mpXXXXXX"; int cmdlength = 1; int cmdbitlength = 1; if (!mpxopt->debug) { @; } mpx = xmalloc(sizeof(struct mpx_data),1); mpx_initialize(mpx); mpx->mode = mpxopt->mode; mpx->debug = mpxopt->debug; if (mpxopt->find_file!=NULL) mpx->find_file = mpxopt->find_file; if (mpxopt->cmd!=NULL) mpx->maincmd = xstrdup(mpxopt->cmd); mpx->mpname = xstrdup(mpxopt->mpname); mpx->mpxname = xstrdup(mpxopt->mpxname); @; if (mpx->debug) { mpx->errfile = stderr; } else { mpx->errfile = mpx_xfopen(mpx,MPXLOG, "wb"); } mpx->progname = "makempx"; @; if (mpxopt->mptexpre == NULL) mpxopt->mptexpre = xstrdup("mptexpre.tex"); @; if (mpxopt->cmd==NULL) return mpx->history; if (mpx->mode == mpx_tex_mode) { @; if (mpx_dvitomp(mpx, infile)) { mpx_rename(mpx, infile,DVIERR); if (!mpx->debug) remove(mpx->mpxname); mpx_abort(mpx, "Dvi conversion failed: %s %s\n", DVIERR, mpx->mpxname); } } else if (mpx->mode == mpx_troff_mode) { @; if (mpx_dmp(mpx, infile)) { mpx_rename(mpx,infile, TROFF_OUTERR); mpx_rename(mpx,mpx->tex, TROFF_INERR); if (!mpx->debug) remove(mpx->mpxname); mpx_abort(mpx, "Troff conversion failed: %s %s\n", TROFF_OUTERR, mpx->mpxname); } } mpx_fclose(mpx,mpx->mpxfile); if (!mpx->debug) mpx_fclose(mpx,mpx->errfile); if (!mpx->debug) { remove(MPXLOG); remove(ERRLOG); remove(infile); } mpx_erasetmp(mpx); retcode = mpx->history; free(mpx); if (retcode == mpx_cksum_trouble) retcode = 0; return retcode; } @ \TeX\ has to operate on an actual input file, so we have to append that to the command line. @= { char log[15]; mpx->maincmd = xrealloc(mpx->maincmd,strlen(mpx->maincmd)+strlen(mpx->tex)+2,1); strcat(mpx->maincmd, " "); strcat(mpx->maincmd, mpx->tex); cmdlength = split_command(mpx->maincmd, cmdline); retcode = mpx_run_command(mpx, NULL, NULL, cmdlength, cmdline); TMPNAME_EXT(log, ".log"); if (!retcode) { TMPNAME_EXT(infile, ".dvi"); remove(log); } else { mpx_rename(mpx,mpx->tex, TEXERR); mpx_rename(mpx,log, ERRLOG); mpx_command_error(mpx, cmdlength, cmdline); } mpx_command_cleanup(mpx, cmdline); } @ @= { char *cur_in, *cur_out; char tmp_a[15], tmp_b[15]; TMPNAME_EXT(tmp_a, ".t"); TMPNAME_EXT(tmp_b, ".tmp"); cur_in = mpx->tex; cur_out = tmp_a; /* split the command in bits */ cmdbitlength = split_pipes(mpx->maincmd, cmdbits); for (i = 0; i < cmdbitlength; i++) { if (cmdline!=NULL) free(cmdline); cmdlength = split_command(cmdbits[i], cmdline); retcode = mpx_run_command(mpx, cur_in, cur_out, cmdlength, cmdline); if (retcode) { mpx_rename(mpx,mpx->tex, TROFF_INERR); mpx_command_error(mpx, cmdlength, cmdline); } if (i < cmdbitlength - 1) { if (i % 2 == 0) { cur_in = tmp_a; cur_out = tmp_b; } else { cur_in = tmp_b; cur_out = tmp_a; } strcpy(infile,cur_in); } } if (tmp_a!=infile) { remove(tmp_a); } if (tmp_b!=infile) { remove(tmp_b); } } @ If MPX file is up-to-date or if MP file does not exist, do nothing. @= if (mpx_newer(mpxopt->mpname, mpxopt->mpxname)) return 0 @ @= #ifdef HAVE_MKSTEMP i = mkstemp(tmpname); if (i == -1) { sprintf(tmpname, "mp%06d", (int)(time(NULL) % 1000000)); } else { close(i); remove(tmpname); } #else #ifdef HAVE_MKTEMP { char *tmpstring = mktemp(tmpname); if ((tmpstring == NULL) || strlen(tmpname) == 0) { sprintf(tmpname, "mp%06d", (int)(time(NULL) % 1000000)); } else { /* this should not really be needed, but better safe than sorry. */ if (tmpstring != tmpname) { i = strlen(tmpstring); if (i > 8) i = 8; strncpy(tmpname, tmpstring, i); } } #else sprintf(tmpname, "mp%06d", (int)(time(NULL) % 1000000)); #endif #endif