All pastes #1982128 Raw Edit

GF-Magick (version 0.1)

public text v1 · immutable
#1982128 ·published 2010-11-04 20:13 UTC
rendered paste body
% GF-Magick
% version 0.1
% Licensed under GNU GPL v3 or later version

\def\title{GF-Magick}

@mp@-
``u{YJ"@<Predeclaration of procedures@>=
qJA";
J"@
"@<Procedure codes@>=
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
@<Include files@>@;
@h
@<Typedefs@>@;
@<Global variables@>@;
@<Predeclaration of procedures@>@;
@<Procedure codes@>@;

@*The Main Program.

@<Global variables@>=
char gf_filename[1024];
char out_filename[1024];

@ @<Include files@>=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

@ So, here is the main function of what this program does.

@-p int main(int argc,char**argv) {
  @<Read the command-line arguments@>;
  @<Initialize the ImageMagick command-line@>;
  @<Read the GF file@>;
  @<Finalize the ImaegMagick command-line@>;
  @<Switch to the ImageMagick executable@>;
  return 0;
}

@ The command-line arguments indicate the filenames. If any filenames are
omitted, it will use standard input and standard output.

@<Read the command-line arguments@>= {
  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)

@<Read the GF file@>= {
  FILE*fp;
  @<Open the GF file@>;
  @<Skip the preamble@>;
  @<Process the commands inside the GF file@>;
  @<Close the GF file@>;
}

@ @<Open the GF file@>= {
  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;
  }
}

@ @<Close the GF file@>= {
  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.

@<Typedefs@>=
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.

@<Skip the preamble@>= {
  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.)

@<Process the commands inside the GF file@>= {
  int cmd;
process_next:
  cmd=read_cmd(fp);
  if(cmd>=paint_0 && cmd<=paint_63) {
    @<Process a short paint command@>;
  } @+else if(cmd>=paint1 && cmd<=paint3) {
    @<Process a long paint command@>;
  } @+else if(cmd==boc) {
    @<Process a |boc| command@>;
  } @+else if(cmd==boc1) {
    @<Process a |boc1| command@>;
  } @+else if(cmd==eoc) {
    @<Process a |eoc| command@>;
  } @+else if(cmd>=skip0 && cmd<=skip3) {
    @<Process a skip command@>;
  } @+else if(cmd>=new_row_0 && cmd<=new_row_164) {
    @<Process a row command@>;
  } @+else if(cmd>=xxx1 && cmd<=xxx4) {
    @<Process a special command@>;
  } @+else if(cmd==yyy) {
    @<Process a numeric special command@>;
  } @+else if(cmd!=no_op && cmd!=post) {
    @<Complain about invalid command and abort@>;
  }
  if(cmd!=post) goto process_next;
}

@ @<Complain about invalid command and abort@>= {
  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

@<Global variables@>=
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;

@ @<Process a |boc| command@>= {
  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();
}

@ @<Process a |boc1| command@>= {
  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.

@<Global variables@>=
gfnumber row_remain;

@ @<Process a short paint command@>= {
  gfnumber n=cmd-paint_0;
  memset(rptr,paint_switch,n);
  rptr+=n;
  row_remain-=n;
  paint_switch^=1;
}

@ @<Process a long paint command@>= {
  gfnumber n=read_num(fp,cmd+1-paint1);
  memset(rptr,paint_switch,n);
  rptr+=n;
  row_remain-=n;
  paint_switch^=1;
}

@ @<Process a row command@>= {
  rptr+=row_remain;
  row_remain=img_width-(cmd-new_row_0);
  paint_switch=black;
  rptr+=cmd-new_row_0;
}

@ @<Process a skip command@>= {
  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.

@<Process a |eoc| command@>= {
  @<Write the |raster| array to a MIFF file@>;
  @<Add this character to the ImageMagick command-line@>;
  free(raster);
}

@ @<Add this character to the ImageMagick command-lien@>= {
}

@*Magick Image File Format. This chapter involves the codes required for
writing a MIFF file.
@^MIFF@>

@<Write the |raster| array to a MIFF file@>= {
  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);
  }
  @<Write the MIFF header@>;
  @<Write the MIFF data@>;
  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)

@<Write the MIFF header@>= {
  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.

@<Global variables@>=
unsigned char imgcolors[8]; // background RGBA, foreground RGBA

@ @<Write the MIFF data@>= {
  char*rend=raster+(img_width*img_height);
  char*p;
  for(p=raster;p<rend;p++) fwrite(imgcolors+((*p)<<2),1,4,fp);
}

@*Specials. This program uses specials to control ImageMagick.
@^Specials@>

@d normal_num 0
@d plusminus_num 1
@d decimal_num 2
@d unscaled_num 4
@#
@d no_waiting 0x00
@d numeric_parameter 0x01

@<Global variables@>=
char special_in[1024];
char special_out[1024];
char waiting_special;
char waiting_special_sub;

@ @<Process a special command@>= {
  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);
  }
}

@ @<Process a numeric special command@>= {
  gfnumber num=read_num(fp,4);
  switch(waiting_special) {
    case no_waiting: @<Error about unexpected special number@>;@+break;
    case numeric_parameter: @<Copy |special_in| to ...@>;@+break;
    case 'C': @<Set the background and foreground colors@>;@+break;
  }
}

@ @<Error about unexpected special number@>= {
  fprintf(stderr,"Unexpected yyy command\n");
  exit(1);
}

@ This special copying involves things...

@<Copy |special_in| to |special_out| and include numbers@>= {
}

@ @<Set the background and foreground colors@>= {
  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@>

@<Global variables@>=
char**cmdline;
int cmdline_size;
int cmdline_index;

@ @<Initialize the ImageMagick command-line@>= {
}

@ @-p void add_parameter(char*s) {
  // TODO
}

@*Index.