// Map to Postscript-1.0 translator for VGA Planets 4 // Program copyright Peter Chambers 2001 // This program is licensed under the GPL. // See http://www.hightown.demon.co.uk/planets/map2ps.html /***************************************************************** $Author: peter $ $Date: 2001/08/16 19:03:57 $ $Header: /usr/local/cvsrep/map2ps/map2ps.c,v 1.9 2001/08/16 19:03:57 peter Exp $ $Name: $ $Revision: 1.9 $ $Log: map2ps.c,v $ Revision 1.9 2001/08/16 19:03:57 peter Modified de-confliction algorithm, added sensible dX component. Revision 1.8 2001/08/09 19:53:51 peter Added quadrants 2, 4, 5, 6 & 8 as geometrically valid. Revision 1.7 2001/07/06 17:40:36 peter Added -l option for Greek character legend and -g# option for grid. Revision 1.6 2001/07/01 10:56:34 peter Added Greek alphabet list and e-mail address in credits. Revision 1.5 2001/06/27 19:22:33 peter Fixed bug in emit postscript string routine. Revision 1.4 2001/06/15 16:50:53 peter Fixed range check on nonant/quadrant 9 problem. Revision 1.3 2001/04/12 14:47:13 peter Re-commit to check CVS mechanism. Revision 1.2 2001/04/12 14:42:20 peter Updated CVS header Revision 1.1 2001/01/01 00:0:00 peter Disabled "landscape" code due to problems Changed scaling to joint XY scaling to preserve distances *****************************************************************/ // VGA Planets 4 (including MAP format) is copyright Tim Wisseman // See http://www.vgaplanets.com/v4beta.htm /* The MAP file structure consists of : *** LONG = 4 Byte Signed Integer *** INTEGER = 2 Byte Signed Integer *** First Byte of file = Byte 1 (Not Byte 0) '*** V4 Map File *** '// 1 to 100 Header info '// First 22 bytes = "VGA Planets 4 Map File" '// 23 to 96 First 84 charactors given in the header line between the < > '// 97 to 100 Four spaces (for version 0 files) '// 101 LONG Count of Number of Planets '// 105 ptXYSTAR LONG Pointer to start of (X,Y,STAR) '// 109 ptNAME LONG Pointer to start of NAME LIST '// '// X INTEGER < ptXYSTAR points here '// Y INTEGER '// STAR INTEGER '// X INTEGER < Second record (+ 6 bytes ) '// Y INTEGER '// STAR INTEGER '// X INTEGER < Third record (+ 12 bytes ) '// Y INTEGER '// STAR INTEGER '// X INTEGER < Forth record (+18 bytes ) '// Y INTEGER '// STAR INTEGER '// . . . '// '// NAME String * 40 bytes < ptNAME points here '// NAME String * 40 bytes < second record ( + 40 bytes ) '// NAME String * 40 bytes < third record ( + 80 bytes ) '// NAME String * 40 bytes < forth record ( + 120 bytes ) '// . . . */ #include #include #include #include #include #include #include #define MSTRLEN 22 #define VSTRLEN (96 - 22) #define FSTRLEN 100 #define SSTRLEN 40 #define NSRECS 2000 #define NGREEKS 24 // start of Greek lower case alphabet in Unicode #define UBGREEK 0x03b1 #define NUL '\0' #define WNUL L'\0' #define offset(x) ((x) - 1) #ifndef __STDC_ISO_10646__ #error No ISO 10646 support! #endif // provide handy enumerations for ISO A paper sizes enum { A0, A1, A2, A3, A4, A5, A6 }; static const char whoami[] = "map2ps $Revision: 1.9 $ by Peter Chambers "; static const char mfString[] = "VGA Planets 4 Map File"; static char tfString[MSTRLEN]; static char mString[VSTRLEN+1]; static char sString[SSTRLEN+1]; static char finName[FSTRLEN] = ""; static char foutName[FSTRLEN] = ""; static FILE *fin = NULL; static FILE *fin2 = NULL; static FILE *fout = NULL; static long nStars = 0; static long ptXY = 0; static long ptName = 0; static int minX = 300000, minY = 300000, maxX = 0, maxY = 0; static int distX = 100, distY = 100; static int paper = A3; static int quadrant = 0; // plot a quadrant static int nonant = 0; // like a quadrant but ninths static int hasGrid = 0; // plot grid overlay static int legend = 0; // Greek character legend static double devX, devY; // device coordinates static double sofX, sofY; // "soft" coordinates of viewport static double oriX, oriY; // viewport origin coordinates in window static double scaleXY = 1.0; static const double a0X = 2384.0, a0Y = 3380.0; // A0 in points static const double margin = 0.04; // fraction of side in margin static const double HM = 1.0; static double textpt = 4.0; // text size in points static int gridPitch = 100; // liars between grid lines typedef struct { int x; int y; double ptX; double ptY; double plX; double plY; } StarData; static StarData starData[NSRECS]; // mapping between Symbol and ASCII/ISO-Latin-1 font encodings // for lower case Greek, assumes system is in ASCII (as is PostScript) static const char gMaps[NGREEKS] = { 'a', 'b', 'g', 'd', 'e', 'z', 'h', 'q', 'i', 'k', 'l', 'm', 'n', 'x', 'o', 'p', 'r', 's', 't', 'u', 'f', 'c', 'y', 'w' }; static const char * const gLetter[NGREEKS] = { "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda","mu", "nu", "xi", "omicron", "pi", "rho", "sigma","tau", "upsilon", "phi", "chi", "psi", "omega" }; static void straddchr(char *m, char c) { int i = strlen(m); m[i] = c; m[i + 1] = NUL; } static double sgn(double x) { return(x >= 0.0 ? +1.0 : -1.0); } static void panic(char *s) { fprintf(stderr, "%s\n", s); exit(1); } static void setPaperAn(int n) { const double scale = 0.7071068; // 1 / sqrt(2) if ((n < 0) || ( n > 6)) { n = A4; paper = n; } devX = a0X; devY = a0Y; for (; n-- ;) { devX *= scale; devY *= scale; } // end for // soft scale in paper to allow margins sofX = devX * (1.0 - 2 * margin); sofY = devY * (1.0 - 2 * margin); oriX = devX * margin; oriY = devY * margin; // choose scaling to keep smallest side inside paper margin limits if ((sofX / distX) < (sofY / distY)) scaleXY = sofX / distX; else scaleXY = sofY / distY; } static double tx(int x) { return(oriX + scaleXY * (x - minX)); } static double ty(int y) { return(oriY + scaleXY * (y - minY)); } static void sClean(char *out, const char *in) { while (*in) { if ((*in == '(') || (*in == ')')) *out++ = '\\'; *out++ = *in++; } *out = NUL; } static int ctoi(char c) { // adapt for signed or unsigned char int ic = (int)c; return(ic < 0 ? (256 + ic) : ic); } static int ispostr(char c) // basically ASCII { int ic = ctoi(c); return((ic < 128) && (ic >= ' ')); } static void emitPostString(FILE *fp, char *s) { char buffer[2*SSTRLEN]; int gc, len, st, i = 0; sClean(buffer, s); len = strlen(buffer); while (i < len) // emit ASCII strings interspersed with translated Greek chars { for (st = i ; ispostr(buffer[i]) ; i++) /* scan */; gc = ctoi(buffer[i]); buffer[i] = NUL; if (st != i) fprintf(fp, "(%s) show\n", &buffer[st]); if (gc != 0) fprintf(fp, "Greek (%c) show Roman\n", gMaps[gc-128]); } } static void starName(char *s) { int i, g, glet = 0, ind=0, slen = 0; char lowString[SSTRLEN+1], *pGreek = NULL; // get a lower case copy of the string strcpy(lowString, s); for (i = 0 ; lowString[i] != NUL ; i++) { lowString[i] = tolower(lowString[i]); } // check for greek characters spellt out for (g = 0 ; g < NGREEKS ; g++) { char *wGreek = strstr(lowString, gLetter[g]); if (wGreek) { int len = strlen(gLetter[g]); if (len > slen) { pGreek = wGreek; ind = pGreek - lowString; slen = len; glet = g; } } } // Greek character found, not totally alone, not embedded if (pGreek && (strlen(s) != slen) && !isalpha(pGreek[slen]) && (!ind || !isalnum(s[ind-1]))) { if (ind) // non-Greek part first, cap and print { s[ind] = NUL; emitPostString(fout, s); } // now the greek letter, where alpha is encoded with 'a' // need to map the greek letter encoding here!!! fprintf(fout, "Greek (%c) show Roman\n", gMaps[glet]); // finally any Roman letters after the Greek s += ind + slen; if (*s) { emitPostString(fout, s); } // end if } else // no spellt out Greek { emitPostString(fout, s); } } static void markStar(double xf, double yf, double lx, double ly, char *s) { // make a cross symbol for a star fprintf(fout, "%7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f star\n", xf - HM, yf, xf + HM, yf, xf, yf - HM, xf, yf + HM); fprintf(fout, "%7.2f %7.2f moveto\n", lx, ly); starName(s); } static void writeBasInt(int val, FILE *fp) { /* 2 byte integer, little endian */ if ((val <= 0xffff) && (val >= -32768)) { fputc((val >> 0) & 255, fp); fputc((val >> 8) & 255, fp); } else { panic("2 byte INT error"); } } static void writeBasLong(int val, FILE *fp) { /* 4 byte integer, little endian */ fputc((val >> 0) & 255, fp); fputc((val >> 8) & 255, fp); fputc((val >> 16) & 255, fp); fputc((val >> 24) & 255, fp); } static int readBasInt(FILE *f) { // note int is signed and problems can occur with fgetc() int res = fgetc(f) << 16; res |= fgetc(f) << 24; res >>= 16; return(res); } static int readBasLong(FILE *f) { // note int is signed and problems can occur with fgetc() int res = fgetc(f); res |= fgetc(f) << 8; res |= fgetc(f) << 16; res |= fgetc(f) << 24; return(res); } static void readBasString(FILE *f, char str[], int len) { int i; for (i = 0 ; i < len ; i++) str[i] = fgetc(f); for (i = len-1 ; (i >= 0) && (str[i] == ' ') ; i--) if (str[i] == ' ') str[i] = NUL; } static void readUniString(FILE *f, char str[], int len) { wchar_t wstr[SSTRLEN+1]; int i; if (len > SSTRLEN) { wstr[0] = L'\0'; return; } for (i = 0 ; i < len ; i++) str[i] = fgetc(f); // trim back Basic spaces on end of string for (i = len-1 ; (i >= 0) && (str[i] == ' ') ; i--) if (str[i] == ' ') str[i] = NUL; // convert from UTF-8 to wide char Unicode if (-1 == mbstowcs(wstr, str, len)) { str[0] = NUL; return; } for (i = 0 ; wstr[i] != WNUL ; i++) { if (wstr[i] < 128) str[i] = (char)wstr[i]; else if ((wstr[i] >= UBGREEK) && (wstr[i] < (UBGREEK+NGREEKS))) { // put translated Greek chars in top bit set char space str[i] = (char)(wstr[i] - UBGREEK + 128); } else str[i] = '?'; } str[i] = NUL; } static void listGreekAlphabet(void) { // gMaps are characters in symbol, gLetter are strings in ASCII // there are NGREEKS=24 characters int group; const int groups = (NGREEKS/4); // print character and ASCII name for (group = 0 ; group < groups ; group++) { int goff; for (goff = 0 ; goff < (NGREEKS/groups) ; goff++) { int glet = goff + group * NGREEKS / groups; fprintf(fout, "%7.2f %7.2f moveto Greek (%c ) show Roman (%s) show\n", oriX + textpt * goff * 12, ty(maxY) + textpt * (groups + 2 - group), gMaps[glet], gLetter[glet]); } } } static void addGrid(void) { // add a grid of about 100 liars gridPitch int x, y; int endX = minX + (distX / gridPitch) * gridPitch; int endY = minY + (distY / gridPitch) * gridPitch; fprintf(fout, "0.1 setlinewidth\n"); // set narrow line width for (x = minX ; x <= endX ; x += gridPitch) { fprintf(fout, "%7.2f %7.2f moveto %7.2f %7.2f lineto stroke\n", tx(x), ty(minY), tx(x), ty(endY)); } for (y = minY ; y <= endY ; y += gridPitch) { fprintf(fout, "%7.2f %7.2f moveto %7.2f %7.2f lineto stroke\n", tx(minX), ty(y), tx(endX), ty(y)); } } static void usage(void) { fprintf(stderr, "usage: %s [options] filename-without-extension\n", whoami); fprintf(stderr, "extensions .map and .eps are assumed by the program\n"); } static int parseArgs(int argc, char *argv[], char *name) { int argno; // return 0 for OK if (argc < 2) { usage(); return(1); } for (argno = 1 ; argno < argc ; argno++) { if (argv[argno][0] == '-') // switch { char c = argv[argno][1]; if (c != NUL) // possibly known switch { switch (c) { case 'h': usage(); return(1); break; case 'g': // has a grid { int num = atoi(&argv[argno][2]); if ((num >= 20) && (num <= 500)) gridPitch = num; hasGrid = 1; } break; case 'l': // has legend legend = 1; break; case 'a': // paper size designator { char num = argv[argno][2]; if ((num >= '0') && (num < '6')) { char snum[2] = "4"; snum[0] = num; paper = atoi(snum); } } break; case 'p': // points size designator { char num = argv[argno][2]; if ((num >= '0') && (num < '6')) { char snum[2] = "4"; snum[0] = num; textpt = (double)atoi(snum); } } break; case 'n': // ninths { char num = argv[argno][2]; if ((num >= '1') && (num <= '9')) { char snum[2] = "0"; snum[0] = num; nonant = atoi(snum); quadrant = 0; } } break; case 'q': // quadrants { char num = argv[argno][2]; if ((num >= '1') && (num <= '9')) { char snum[2] = "0"; snum[0] = num; quadrant = atoi(snum); nonant = 0; } } break; default: break; } } } else // filename { if (name[0] == NUL) strcpy(name, argv[argno]); else { usage(); panic("too many filename parameters"); } } } return(0); } int main(int argc, char *argv[]) { char argname[FSTRLEN] = ""; long l; int bin = 0; // DOS binary format? Put it in the bin!!! int eps = 0; // EPS rather than PS? int swapOn = 0; // allow portrait/landscape swap ? // set UTF-8 active to match Tim's VB locale setlocale(LC_CTYPE, "en_US.UTF-8"); for (l = 0 ; l < SSTRLEN ; l++) sString[l] = L'\0'; // check args if (parseArgs(argc, argv, argname)) return(1); // open input file strcpy(finName, argname); strcat(finName, ".map"); fin = fopen(finName, "rb"); if (!fin) panic("Cannot open input file"); // open output file strcpy(foutName, argname); if (nonant) straddchr(foutName, '0' + (char)nonant); else if (quadrant) straddchr(foutName, '0' + (char)quadrant); strcat(foutName, ".eps"); fout = fopen(foutName, "wb"); if (!fout) panic("Cannot open output file"); // check map file string readBasString(fin, tfString, MSTRLEN); if (strncmp(tfString, mfString, strlen(mfString))) { panic("VGAP MAP string not found\n"); } // get MAP name string readBasString(fin, mString, VSTRLEN); if (nonant) { strcat(mString, " (nonant "); straddchr(mString, '0' + (char)nonant); strcat(mString, ")"); } else if (quadrant) { strcat(mString, " (quadrant "); straddchr(mString, '0' + (char)quadrant); strcat(mString, ")"); } // read number of stars if (fseek(fin, offset(101L), SEEK_SET)) panic("Cannot fseek() input 1"); nStars = readBasLong(fin); // read XY and Name offsets ptXY = readBasLong(fin); ptName = readBasLong(fin); fprintf(stderr, "Filepos XY: %lx\n", offset(ptXY)); // seek XY section of file if (fseek(fin, offset(ptXY), SEEK_SET)) panic("Cannot fseek() input 2"); // find min and max X, Y values for (l = 0 ; l < nStars ; l++) { int x = readBasInt(fin); int y = readBasInt(fin); (void)readBasInt(fin); /* read three ints always here */ if (x < minX) minX = x; if (y < minY) minY = y; if (x > maxX) maxX = x; if (y > maxY) maxY = y; } if (maxX < minX) panic("X max and min faulty"); if (maxY < minY) panic("Y max and min faulty"); minX -= 20; minY -= 20; maxX += 50; maxY += 50; fprintf(stderr, "minX: %d minY: %d maxX: %d maxY: %d nStars %ld\n", minX, minY, maxX, maxY, nStars); // work out paper numbers ready for normal computation distX = maxX - minX; distY = maxY - minY; if (quadrant != 0) // using quadants { distX /= 2; distY /= 2; switch (quadrant) { case 1: maxX = minX + distX; maxY = minY + distY; break; case 2: minX += distX/2; maxX = minX + distX; maxY = minY + distY; break; case 3: minX += distX; maxY = minY + distY; break; case 4: minY += distY/2; maxX = minX + distX; maxY = minY + distY; break; case 5: minX += distX/2; minY += distY/2; maxX = minX + distX; maxY = minY + distY; break; case 6: minX += distX; minY += distY/2; maxY = minY + distY; break; case 7: maxX = minX + distX; minY += distY; break; case 8: minX += distX/2; maxX = minX + distX; minY += distY; break; case 9: minX += distX; minY += distY; break; default: panic("Illegal Quadrant"); break; } // end switch } else if (nonant != 0) // using nonants { distX /= 3; distY /= 3; switch (nonant) { case 1: maxX = minX + distX; maxY = minY + distY; break; case 2: maxX = minX + 2 * distX; minX += distX; maxY = minY + distY; break; case 3: minX += 2 * distX; maxY = minY + distY; break; case 4: maxX = minX + distX; maxY = minY + 2 * distY; minY += distY; break; case 5: maxX = minX + 2 * distX; minX += distX; maxY = minY + 2 * distY; minY += distY; break; case 6: minX += 2 * distX; maxY = minY + 2 * distY; minY += distY; break; case 7: maxX = minX + distX; minY += 2 * distY; break; case 8: maxX = minX + 2 * distX; minX += distX; minY += 2 * distY; break; case 9: minX += 2 * distX; minY += 2 * distY; break; default: panic("Illegal Nonant!"); break; } // end switch } setPaperAn(paper); // now set paper ISO An size, e.g. A3 or A4 // emit EPSF DOS binary header if (bin) { writeBasLong(0xc6d3d0c5, fout); writeBasLong(30, fout); // EPS start writeBasLong(0, fout); // EPS length - overwrite later writeBasLong(0, fout); // Metafile start writeBasLong(0, fout); // Metafile length writeBasLong(0, fout); // TIFF start writeBasLong(0, fout); // TIFF length writeBasInt(0xffff, fout); // dummy checksum } // emit (E)PS ASCII header if (eps) fprintf(fout, "%s\n", "%!PS-Adobe-1.0 ESPF-1.0"); else fprintf(fout, "%s\n", "%!PS-Adobe-1.0"); fprintf(fout, "%s %s\n", "%%Creator:", whoami); fprintf(fout, "%s %s\n", "%%Title:", finName); { time_t t = time(NULL); fprintf(fout, "%%%%CreationDate: %s", ctime(&t)); } fprintf(fout, "%s\n", "%%Pages: 1"); fprintf(fout, "%s\n", "%%DocumentFonts: Helvetica Symbol"); fprintf(fout, "%%%%BoundingBox: 0 0 %d %d\n", (int)devX, (int)devY); fprintf(fout, "%%%%DocumentPaperSizes: a%d\n", paper); fprintf(fout, "%s\n", "%%EndComments"); fprintf(fout, "%s\n", "%%BeginProlog"); if (swapOn && (distX > distY)) // swap X and Y axes in postscript { double temp; fprintf(fout, "%f 0 translate\n", devX); fprintf(fout, "90 rotate\n"); temp = devX; devX = devY; devY = temp; } // set fonts fprintf(fout, "/Roman {/Helvetica findfont %3.1f scalefont setfont} def\n", textpt); fprintf(fout, "/Greek {/Symbol findfont %3.1f scalefont setfont} def\n", textpt); fprintf(fout, "Roman\n"); // define star function fprintf(fout, "/star {moveto lineto stroke} def\n"); fprintf(fout, "%s\n", "%%EndProlog"); fprintf(fout, "%s\n", "%%Page: 1"); // open input again for name reading fin2 = fopen(finName, "rb"); if (!fin2) panic("Cannot open input file 2"); if (fseek(fin2, offset(ptName), SEEK_SET)) panic("Cannot fseek() input 3"); if (fseek(fin, offset(ptXY), SEEK_SET)) panic("Cannot fseek() input 2a"); // read stars for (l = 0 ; l < nStars ; l++) { int x = readBasInt(fin); int y = readBasInt(fin); (void)readBasInt(fin); /* read three ints always here */ starData[l].x = x; starData[l].y = y; starData[l].ptX = tx(x); starData[l].ptY = ty(y); } // translate star label positions for (l = 0 ; l < nStars ; l++) { long o; static const double maxmove = 1.5; double dX = 0.0, dY = 0.0; for (o = 0 ; o < nStars ; o++) { if ((l != o) && (fabs(starData[l].ptX - starData[o].ptX) < 20.0) && (fabs(starData[l].ptY - starData[o].ptY) < 5.0)) { dX += sgn(starData[l].ptX - starData[o].ptX); } // end if if ((l != o) && (fabs(starData[l].ptX - starData[o].ptX) < 15.0) && (fabs(starData[l].ptY - starData[o].ptY) < 10.0)) { dY += 1.0 / (starData[l].ptY - starData[o].ptY); } // end if } // end for if (dX > maxmove) dX = maxmove * sgn(dX); // safety trip starData[l].plX = starData[l].ptX - HM + dX; if (dY >= 0.0) dY = HM; else dY = -1.0 * HM - textpt; starData[l].plY = starData[l].ptY + dY; } // write stars for (l = 0 ; l < nStars ; l++) { readBasString(fin2, sString, SSTRLEN); // test print the data //fprintf(stderr, "%d %d %s\n", starData[l].x, starData[l].y, sString); // put the PS commands to mark the star if not clipped out if ((starData[l].x > minX) && (starData[l].x < maxX) && (starData[l].y > minY) && (starData[l].y < maxY)) markStar(starData[l].ptX, starData[l].ptY, starData[l].plX, starData[l].plY, sString); } // print MAP name and credits down in the margin fprintf(fout, "%7.2f %7.2f moveto (%s translated using %s) show\n", oriX + 2.0, oriY - 3.8, mString, whoami); // crib for non-Greek speakers if (legend) listGreekAlphabet(); // optionally plot a grid if (hasGrid) addGrid(); if (!eps) fprintf(fout, "showpage\n"); // not EPS fprintf(fout, "%s\n", "%%Trailer"); //fprintf(fout, "%s\n", "end"); fprintf(fout, "%s\n", "%%EOF"); if (bin) { long l = ftell(fout); if (l < 31) panic("Output file size incredibly short"); if (fseek(fout, 8L, SEEK_SET)) panic("Cannot fseek() output"); writeBasLong(l - 30, fout); // EPS length } if (fin) fclose(fin); if (fin2) fclose(fin2); return(0); }