% GF-Magick % version 0.1 % Licensed under GNU GPL v3 or later version \def\title{GF-Magick} @mp@- ``u{YJ"@= qJA"; J"@ "@= B" { @) @s FILE int @*Introduction. This program can read GF files created with METAFONT and can convert it to process with ImageMagick. @^METAFONT@> @^ImageMagick@> @^Generic Font Format@> @c @@; @h @@; @@; @@; @@; @*The Main Program. @= char gf_filename[1024]; char out_filename[1024]; @ @= #include #include #include #include @ So, here is the main function of what this program does. @-p int main(int argc,char**argv) { @; @; @; @; @; return 0; } @ The command-line arguments indicate the filenames. If any filenames are omitted, it will use standard input and standard output. @= { if(argc==2) { fprintf(stderr,"Wrong number of command-line arguments\n"); return 2; } else if(argc>=3) { strcpy(gf_filename,argv[1]); strcpy(out_filename,argv[2]); } else { *gf_filename=0; strcpy(out_filename,"miff:-"); } } @*Reading GF Files. This chapter of the program involves reading a GF file and converting the data into this program's format. The preamble and postamble are not used by this program, but they must be read anyways, and the postamble inidicates the end of what this program cares about. Here is a list of the GF commands that this program cares about: @d paint_0 0 // Single-byte paint lines commands @d paint_63 63 @d paint1 64 // Paint with a one-byte parameter @d paint3 66 @d boc 67 // Beginning of a character @d boc1 68 // A shorter kind of |boc| command @d eoc 69 // End of a character @d skip0 70 // Skip rows with zero-byte parameter @d skip3 73 @d new_row_0 74 // Single-byte new row commands @d new_row_164 238 @d xxx1 239 // Special command with one-byte string length @d xxx4 242 @d yyy 243 // Numeric parameter for specials @d no_op 244 // No operation @d pre 247 // Preamble (not used by this program) @d post 248 // Postamble (not used by this program) @= { FILE*fp; @; @; @; @; } @ @= { if(*gf_filename) { fp=fopen(gf_filename,"rb"); } else { fp=stdin; } if(!fp) { fprintf(stderr,"Cannot open input file: %s\n",gf_filename); return 1; } } @ @= { if(*gf_filename) fclose(fp); } @ Numbers in a GF file are in big-endian order. Reading a number can be done using this simple procedure easily. Four byte numbers are signed. @= typedef signed long long int gfnumber; @ @-p gfnumber read_num(FILE*fp,int size) { unsigned long long int n=0; while(size--) n=(n<<8)+fgetc(fp); return (gfnumber)n; } @ Strings are stored as counted strings. They are read using this procedure. This code will add the null-terminator. @-p void read_str(FILE*fp,char*ptr,gfnumber length) { while(length--) *ptr++=fgetc(fp); *ptr=0; } @ Commands are read using this very simple macro. @d read_cmd(_1) fgetc(_1) @ The preamble is of the format |(pre,i[1],k[1],x[k])|. None of this information is relevant. @= { gfnumber q; if(read_cmd(fp)!=pre) { fprintf(stderr,"This is not a proper GF file\n"); return 1; } read_num(fp,1); q=read_num(fp,1); while(q--) read_cmd(fp); } @ Now the commands inside the GF file must be processed. It is assumed that the file will be correct, because this program does not check for incorrect files. (If this is not a proper GF file, it will most likely complain about an invalid command and abort.) @= { int cmd; process_next: cmd=read_cmd(fp); if(cmd>=paint_0 && cmd<=paint_63) { @; } @+else if(cmd>=paint1 && cmd<=paint3) { @; } @+else if(cmd==boc) { @; } @+else if(cmd==boc1) { @; } @+else if(cmd==eoc) { @; } @+else if(cmd>=skip0 && cmd<=skip3) { @; } @+else if(cmd>=new_row_0 && cmd<=new_row_164) { @; } @+else if(cmd>=xxx1 && cmd<=xxx4) { @; } @+else if(cmd==yyy) { @; } @+else if(cmd!=no_op && cmd!=post) { @; } if(cmd!=post) goto process_next; } @ @= { fprintf(stderr,"Invalid GF command: %u\n",cmd); return 1; } @*Reading Character Rasters. Now is the part of the program where the character bitmaps are read into the grid. @d white 0 @d black 1 @= char*raster; // beginning of raster char*rptr; // current position in raster int paint_switch; // |white| or |black| gfnumber min_m,max_m,min_n,max_n; gfnumber char_num; @ @= { char_num=read_num(fp,4); (void)(read_num(fp,4)); // the pointer is unused min_m=read_num(fp,4); max_m=read_num(fp,4); min_n=read_num(fp,4); max_n=read_num(fp,4); init_raster(); } @ @= { gfnumber del_m,del_n; char_num=read_num(fp,1); del_m=read_num(fp,1); max_m=read_num(fp,1); del_n=read_num(fp,1); max_n=read_num(fp,1); min_m=max_m-del_m; min_n=max_n-del_n; init_raster(); } @ This is the procedure that initializes the raster array, and initializes the other variables to the starting values for a character. @-p void init_raster(void) { unsigned long long int size=img_width*img_height; rptr=raster=malloc(size); memset(raster,white,size); paint_switch=white; row_remain=img_width; } @ Now to do the actual character painting. To do so, we keep track of a variable |row_remain| which is how much of the rest of the row must be skipped for the next row. @= gfnumber row_remain; @ @= { gfnumber n=cmd-paint_0; memset(rptr,paint_switch,n); rptr+=n; row_remain-=n; paint_switch^=1; } @ @= { gfnumber n=read_num(fp,cmd+1-paint1); memset(rptr,paint_switch,n); rptr+=n; row_remain-=n; paint_switch^=1; } @ @= { rptr+=row_remain; row_remain=img_width-(cmd-new_row_0); paint_switch=black; rptr+=cmd-new_row_0; } @ @= { gfnumber n=read_num(fp,cmd-skip0); rptr+=row_remain; row_remain=img_width; paint_switch=white; rptr+=img_width*n; } @ After the character is done, it should copy it to a file and place a reference to the file in the ImageMagick command-line arguments. @= { @; @; free(raster); } @ @= { } @*Magick Image File Format. This chapter involves the codes required for writing a MIFF file. @^MIFF@> @= { FILE*fp; char buf[1024]; sprintf(buf,"gfmagick.%lld.miff",char_num); fp=fopen(buf,"wb"); if(!fp) { fprintf(stderr,"Unable to write to file: %s\n",buf); exit(1); } @; @; fclose(fp); } @ The MIFF header consists of key=value pairs, and ends with a colon followed by CTRL+Z. @d img_width (max_m-min_m+1) @d img_height (max_n-min_n+1) @= { fprintf(fp,"id=ImageMagick\n"); fprintf(fp,"class=DirectClass\n"); // true color (not paletted) fprintf(fp,"columns=%lld\n",img_width); fprintf(fp,"rows=%lld\n",img_height); fprintf(fp,"depth=8\n"); // eight bits per pixel fprintf(fp,"colorspace=RGB\n"); fprintf(fp,"matte=True\n"); // enable alpha channel fprintf(fp,":\x1A"); // end of header } @ Now the image data. It is stored in RGBA order (but the alpha channel is 255 for opaque). Some global variables are used which are set by specials, which indicate the colors which are used. @= unsigned char imgcolors[8]; // background RGBA, foreground RGBA @ @= { char*rend=raster+(img_width*img_height); char*p; for(p=raster;p @d normal_num 0 @d plusminus_num 1 @d decimal_num 2 @d unscaled_num 4 @# @d no_waiting 0x00 @d numeric_parameter 0x01 @= char special_in[1024]; char special_out[1024]; char waiting_special; char waiting_special_sub; @ @= { char buf[1024]; gfnumber size=read_num(fp,cmd+1-xxx1); read_str(fp,buf,size); if(*buf=='=' && !buf[2]) { waiting_special=buf[1]; waiting_special_sub=0; } else if(*buf=='=') { strcpy(special_in,buf+1); *special_out=0; waiting_special=numeric_parameter; waiting_special_sub=0; } else if(*buf) { add_parameter(buf); } } @ @= { gfnumber num=read_num(fp,4); switch(waiting_special) { case no_waiting: @;@+break; case numeric_parameter: @;@+break; case 'C': @;@+break; } } @ @= { fprintf(stderr,"Unexpected yyy command\n"); exit(1); } @ This special copying involves things... @= { } @ @= { imgcolors[waiting_special_sub++]=num>>16; if(waiting_special_sub==8) waiting_special=0; } @*Executing ImageMagick. Some variables are used to keep track of the text currently in the command-line, and the environment variable \.{IMCONVERT} should have the path to the \.{convert} program. When there is not enough memory, it will be doubled and reallocated. When it is done, it uses the |execv| function to switch to the ImageMagick program (this program will be done by that time). @^ImageMagick@> @= char**cmdline; int cmdline_size; int cmdline_index; @ @= { } @ @-p void add_parameter(char*s) { // TODO } @*Index.