% This is the cweb file hbf2gf.w of the CJK Package Ver. 2.5  10-Apr-1995
%
% To print this CWEB file you should (but not must) use the CWEAVE of the
% c2cweb-package (found at the CTAN archives, e.g. pip.shsu.edu) and then say
%
%           cweave +ai hbf2gf.w
%
% This (fully compatible) CWEAVE can transform CWEB-files with alternative
% output rules (look at the position of braces below!) the author (it's me
% too :-) prefer. Otherwise this file will be formatted traditionally.

\font\meta=logo10
\def\mf{{\meta METAFONT}}

\def\title{hbf2gf (CJK Version 2.5)}

\def\topofcontents{
  \null\vfill
  \centerline{\titlefont The {\ttitlefont hbf2gf} program}
  \vskip 20pt
  \centerline{(CJK Version 2.5)}
  \vfill}

\def\botofcontents{
  \vfill
  \noindent
  Copyright \copyright\ 1995 by Werner Lemberg
  \bigskip\noindent
  Permission is granted to make and distribute verbatim copies of this
  document provided that the copyright notice and this permission notice
  are preserved on all copies.

  \smallskip\noindent
  Permission is granted to copy and distribute modified versions of this
  document under the conditions for verbatim copying, provided that the
  entire resulting derived work is distributed under the terms of a
  permission notice identical to this one.}

\pageno=\contentspagenumber \advance\pageno by 1
\let\maybe=\iftrue
\fullpageheight=240mm
\pageheight=223mm
\pagewidth=158mm
\setpage
\frenchspacing


\def\msdos{\.{msdos}}
@s msdos TeX

@s HBF int
@s HBF_CHAR int
@s HBF_BBOX int

@s inline int


@* Introduction.
This is the \.{hbf2gf} program by Werner Lemberg
(\.{a7621gac@@awiuni11.bitnet}).

The ``banner line'' defined here should be changed whenever \.{hbf2gf} is
modified.

@d banner "\nThis is hbf2gf (CJK Ver. 2.5)  (c) 1995 by Werner Lemberg\n\n"


@

\.{hbf2gf} is intended to convert Hanzi Bitmap Fonts (HBF) into \TeX\ generic
font files (\.{.gf}--files) according to the \\{CJK}--package, which
\.{hbf2gf} is part of.

The outline of \.{hbf2gf} is simple: a CJK (Chinese/Japanese/Korean) bitmap
file will be scaled and written in at most |nmb_files| \.{.gf}--files, each
file containing 256 characters (except the last and possibly the first one).
In the normal case it's not necessary to compute the right value of
|nmb_files| because \.{hbf2gf} will do this; you should use |-1| instead to
indicate this. See the last section for an example.

The characters in the input font files are completely described through the
HBF header file. This program uses the HBF API implementation of Ross Paterson
(with small extensions). You will find a description of the HBF standard at
\.{ftp.ifcss.org}.

Note that \.{hbf2gf} can only process complete CJK fonts; in the unlikely case
that you need to compute, say, only the Hiragana characters of a Chinese font,
you must create (or modify) a HBF header file which describes the whole code
set of the specified encoding where the unused code ranges point to (existing
but empty) dummy files. This procedure is necessary to get subfonts which have
character offsets as defined in the \\{CJK} macro files.

A batch file created by \.{hbf2gf} too will convert the \.{.gf}--files to
\.{.pk}--files using \.{GFtoPK}, a part of every \TeX--package.

@d TRUE  1
@d FALSE 0@#

@d STRING_LENGTH 255
         /* the maximal length of an input string in the configuration file */
@d FILE_NAME_LENGTH 1024
                 /* the maximal length (including the path) of a filename */@#

@<Global variables@>=
int nmb_files = -1; /* create all files by default */
int unicode = FALSE; /* whether Unicode fonts should be processed */@#

char config_file[FILE_NAME_LENGTH + 1];
char hbf_header[STRING_LENGTH + 1];
char output_name[STRING_LENGTH + 1];@#

FILE *config, *out;
HBF *hbf;@#

#ifdef msdos /* if we compile under DOS or OS/2 */
#define WRITE_BIN   "wb"
#define WRITE_TXT   "wt"
#define READ_BIN    "rb"
#define READ_TXT    "rt"
#else
#define WRITE_BIN   "w"
#define WRITE_TXT   "w"
#define READ_BIN    "r"
#define READ_TXT    "r"
#endif@#

int end_of_file = FALSE;


@
Additionally one \.{.pl} file will be created, which describes the font
metrics in a readable way. Because all CJK characters have the same size, one
metrics file is enough---the batch job created by \.{hbf2gf} calls \.{PLtoTF}
to produce this \.{.tfm}--file and then copy it into |nmb_files| metrics
files. There usually will be a discrepancy between the number of characters in
the last \.{.gf}--file and the \.{.tfm}--file, but this does not harm.



@* The main routine.
The main routine takes |config_file| as the only argument. |read_config()|
scans the configuration file and fills the global variables, |write_file()|
writes the \.{.gf}--files, |write_pl()| the \.{.pl}--file, and |write_job()|
the batch file.

@c
@<Include files@>;
@<Prototypes@>;
@<Global variables@>;@#


int main(argc, argv)
  int argc; /* argument count */
  char *argv[]; /* argument values */

   {if(argc != 2)
       {fprintf(stderr, "\nUsage: hbf2gf configurationfile\n");
        exit(-1);
       }@#

    printf(banner);@#

    strncpy(config_file, argv[1], FILE_NAME_LENGTH);
    config_file[FILE_NAME_LENGTH] = '\0';@#

    read_config(); /* will call |exit()| on errors */@#

    @<Initialize arrays@>;
    @<Write files@>;@#

    hbfClose(hbf);@#

    if(tfm_files)
        write_pl();
    write_job();@#

    return 0;
   }


@
If |unicode| is |TRUE|, the start value of the running number appended to the
base name of the output font files is taken from the HBF header file,
otherwise it starts with \.{01}. |min_char| represents the lower bound of the
code range.

@<Write files@>=
   {int i, j, max_numb;


    i = (unicode == TRUE ? (min_char >> 8) : 1);
    if(nmb_files == -1)
        max_numb = (unicode == TRUE ? 0x100 : 100);
    else
        max_numb = nmb_files;@#

    for(j = 0; (j < max_numb) && !end_of_file; i++, j++)
        write_file(i);@#

    nmb_files = j; /* the real number of output font files */
   }


@
@<Include files@>=
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "hbf.h"


@* The functions.
The first function to be described is |write_file()|. Each \.{.gf}--file
consists of three sections: a preamble, a data section, and a postamble. The
functions |write_pre()|, |write_data()|, and |write_post()| handle this.

@<Prototypes@>=
void write_file(int);


@
@c
void write_file(file_number)
  int file_number; /* this number will be appended to |output_name| */

   {char output_file[FILE_NAME_LENGTH + 1];


    if(unicode)
        sprintf(output_file, "%s%02x.gf", output_name, file_number);
    else
        sprintf(output_file, "%s%02i.gf", output_name, file_number);
    if(!(out = fopen(output_file, WRITE_BIN)))
       {fprintf(stderr, "Couldn't open %s\n", output_file);
        exit(-1);
       }
    printf("Writing %s ", output_file);@#

    write_pre();
    write_data();
    write_post();@#

    printf("\n");@#

    fclose(out);
   }


@
The preamble has two bytes at the very beginning, |PRE| and |GF_ID|. |PRE|
starts the preamble, and |GF_ID| is the Generic Font Identity Byte. The next
bytes are a string in Pascal format containing a header, the date and the
time. Strings in Pascal format start with the length of the string and have no
terminating NULL byte.

@d GF_ID 131
@d PRE   247@#

@d header " hbf2gf output "

@<Prototypes@>=
void write_pre(void);


@
@s tm int

@c
void write_pre(void)
   {char out_s[40], s[20];
    time_t secs_now;
    struct tm *time_now;


    strcpy(out_s, header);@#

    secs_now = time(NULL); /* system date and time */
    time_now = localtime(&secs_now);
    strftime(s, 20, "%Y.%m.%d:%H.%M", time_now);
    strcat(out_s, s);@#

    fputc(PRE, out);
    fputc(GF_ID, out);
    fputc(strlen(out_s), out);
    fputs(out_s, out);
   }


@
|write_data()| produces the middle part of the \.{.gf}--file. It first sets
|char_adr_p| equal to the address of |char_adr[]| which will contain file
offsets of the compressed characters.

|input_size_x| and |input_size_y| reflect the original dimensions of the
bitmap font, |pk_output_size_x| and |pk_output_size_y| contain the width and
height of the output character box (in pixels), |pk_offset_x| and
|pk_offset_y| define the baseline of the font. The same names starting with
\\{tfm\_} instead of \\{pk\_} are used for \.{.tfm}--files (values are
multiples of designsize). |mag_x| and |mag_y| hold the scaling factors which
are needed to reach |design_size|. |target_size| will be the final size;
|magstep = target_size / design_size| is \TeX's \.{\\magstep}.

@<Global...@>=
long char_adr[256];
long *char_adr_p;@#

int pk_offset_x;
      /* horizontal offset (increase character width a bit; will be applied on
         both the left and the right side) */
float tfm_offset_x;
int pk_offset_y;
             /* vertical offset (must be configured to desired font size) */
float tfm_offset_y;@#

int input_size_x;
int input_size_y;
int pk_output_size_x;
                     /* the output character box dimensions without offsets */
float tfm_output_size_x;
int pk_output_size_y;
float tfm_output_size_y;@#

float design_size = 10.0; /* in points */
float target_size; /* in points */
float magstep;@#

float mag_x; /* horizontal and vertical magnification values */
float mag_y;@#

int empty_char; /* a flag whether the character does not exist or is empty */
int last_char; /* the last valid character in a \.{.gf}--file */@#

int dot_count; /* this counts the processed characters;
                  every ten characters a dot is output to the screen */


@
@<Prototypes@>=
void write_data(void);


@
@c
void write_data(void)
   {dot_count = 0;
    char_adr_p = char_adr;@#

    for(last_char = 0; (last_char < 256) && !end_of_file; last_char++)
        @<Write character@>;
   }


@
The code in this section saves the current file position first and calls
|make_pixel_array()|, which expands and scales the character bitmap.

|BOC| (and |BOC1|), the Begin Of Character command byte, must be followed by
the character code and the dimensions of the character as explained in
``\mf\---the program'' (corrected by vertical and horizontal offsets).

|write_coding()| compresses and outputs the bitmap; |EOC| (End Of Character)
finishes the current character.

@d BOC  67
@d BOC1 68 /* simplified version of |BOC| */
@d EOC  69

@<Write character@>=
       {if(dot_count++ % 10 == 0) /* a progress report for impatient users */
           {printf(".");
            fflush(stdout);
           }@#

        empty_char = FALSE;
        make_pixel_array();
        if(end_of_file)
            return;@#

        *char_adr_p = ftell(out);
        char_adr_p++;@#

        if(empty_char)
           {fputc(BOC1, out);
            fputc((unsigned char)last_char, out);
            fputc(0, out);
            fputc(0, out);
            fputc(0, out);
            fputc(0, out);
            fputc(EOC, out);
           }
        else
           {fputc(BOC, out);
            fputl(last_char, out);
            fputl(-1L, out);
            fputl(pk_offset_x, out);
            fputl(pk_output_size_x + pk_offset_x, out);
            fputl(pk_offset_y, out);
            fputl(pk_output_size_y + pk_offset_y, out);@#

            write_coding();@#

            fputc(EOC, out);
           }
       }


@
The current \.{.gf}--file will be completed with data written by
|write_post()|. The end consists of three sections: ``special'', ``post'', and
``postpost''. The first contains material not used by \TeX\ itself but which
can be used by other programs like \.{GFtoDVI} or for documentary purposes
(|coding[]| and |comment[]|). The second describes the font as a whole, and
the last marks the end of the file.

|pk_total_min_x| up to |pk_total_max_y| define the greatest bounding box of
this file (including offsets); the horizontal character escapement after
drawing the character is |pk_dx|. |tfm_width| is the width in multiples of the
designsize ignoring the target size.

@d _2_16   65536.0 /* $2^{16}$ */
@d _2_20 1048576.0 /* $2^{20}$ */

@<Global...@>=
char coding[STRING_LENGTH + 1]; /* a comment describing the font encoding */
char comment[STRING_LENGTH + 1]; /* a comment describing the font */@#

unsigned long checksum;@#

long pk_total_min_x;
long pk_total_max_x;
long pk_total_min_y;
long pk_total_max_y;@#

int dpi_x = 300; /* printer resolution */
int dpi_y = 300;
float ppp_x; /* pixels per point */
float ppp_y;


@
To clarify the meaning of these values see the sections about the metrics and
configuration file also.

\TeX\ defines that 72.27 points are exactly 1 inch.

@<Prototypes@>=
void write_post(void);


@
@c
void write_post(void)
   {long special_adr;
    long post_adr;@#

    long designsize = design_size * _2_20; /* designsize $* 2^{20}$ */@#

    int pk_dx;
    long tfm_width;@#

    int i;
    long temp;


    ppp_x = dpi_x / 72.27 * magstep;
    ppp_y = dpi_y / 72.27 * magstep;@#

    pk_total_min_x = pk_offset_x;
    pk_total_max_x = pk_output_size_x + 2 * pk_offset_x;
    pk_total_min_y = pk_offset_y;
    pk_total_max_y = pk_output_size_y + pk_offset_y;@#

    pk_dx = pk_total_max_x;
    tfm_width = (tfm_output_size_x + 2 * tfm_offset_x) * _2_20;
                           /* width in multiples of designsize $* 2^{20}$ */@#

    @<Special section@>;
    @<Post section@>;
    @<Postpost section@>;
   }


@
\.{XXXn} will be followed by n~bytes representing the length of a string which
follows immediately. |YYY| is a 32~bit integer which is normally connected
with the preceding string (but not used here). |special_adr| contains the
address of the ``special section''. All items here are optional.

@d XXX1 239 /* these are all special command bytes */
@d XXX2 240 /* not used */
@d XXX3 241 /* not used */
@d XXX4 242 /* not used */@#

@d YYY  243 /* not used */

@<Special section@>=
    special_adr = ftell(out);@#

    if(*coding)
       {fputc(XXX1, out); /* |XXX1| implies a string length |< 256| */
        fputc(strlen(coding), out);
        fputs(coding, out);
       }@#

    if(*comment)
       {fputc(XXX1, out);
        fputc(strlen(comment), out);
        fputs(comment, out);
       }


@
Each character offset collected in |char_adr| will be written to the output
file. |fputl()| writes a 32~bit integer into a file.

|CHAR_LOC0| (and |CHAR_LOC|) is the first byte of a character locator
(i.e.~offset, character code and width information). |POST| starts the
postamble, and |post_adr| points to the beginning byte of the postamble.

@d POST 248@#

@d CHAR_LOC 245
@d CHAR_LOC0 246 /* simplified version of |CHAR_LOC| */

@<Post section@>=
    post_adr = ftell(out);
    fputc(POST, out);
    fputl(special_adr, out);@#

    fputl(designsize, out);
    fputl(checksum, out);
    fputl(ppp_x * _2_16, out);
    fputl(ppp_y * _2_16, out);
    fputl(pk_total_min_x, out);
    fputl(pk_total_max_x, out);
    fputl(pk_total_min_y, out);
    fputl(pk_total_max_y, out);@#

    char_adr_p = char_adr;@#

    if(pk_dx < 256)
       {for(i = 0; i < last_char; i++) /* the character locators */
           {fputc(CHAR_LOC0, out);
            fputc(i, out);
            fputc(pk_dx, out);
            fputl(tfm_width, out);
            fputl(*char_adr_p++, out);
           }
       }
    else /* will only happen if |MAX_CHAR_SIZE >= 256| */
       {for(i = 0; i < last_char; i++)
           {fputc(CHAR_LOC, out);
            fputc(i, out);
            fputl(pk_dx * _2_16, out);
            fputl(0, out);
            fputl(tfm_width, out);
            fputl(*char_adr_p++, out);
           }
       }


@
|POSTPOST| starts the section after the postamble. To get all information in a
\.{.gf}--file, you must start here. The very last bytes of the file have the
value |POSTPOST_ID| (the file is filled with at least~4 of these bytes until a
file length of a multiple of~4 is reached). Going backwards a |GF_ID| will be
the next, then comes the address of the postamble section.

Jumping to the postamble, first comes the |POST| byte, then the address of the
special section, and afterwards all character offsets. These offsets and
addresses describe the whole file.

@d POSTPOST    249
@d POSTPOST_ID 223

@<Postpost section@>=
    fputc(POSTPOST, out);
    fputl(post_adr, out);
    fputc(GF_ID, out);
    temp = ftell(out);
    i = (int)(temp % 4) + 4;
    while(i--)
        fputc(POSTPOST_ID, out);


@
\TeX\ wants the most significant byte first.

@<Prototypes@>=
void fputl(long, FILE *);


@
@c
void fputl(num, f)
  long num;
  FILE *f;

   {fputc(num >> 24, f);
    fputc(num >> 16, f);
    fputc(num >> 8, f);
    fputc(num, f);
   }


@
|make_pixel_array()| scales a character into the array |out_char[]| where each
\\{byte} represents one pixel, contrary to the input file where each \\{bit}
is used to store the character bitmap. |BLACK| indicates a black pixel.

The scaling routine was modeled after the program \.{pnmscale} of the
\.{pbmplus} package. \.{pbmplus} was designed to handle arbitrary pictures,
and bitmaps are only a special case of a graymap with values from 0 for black
up to 255 for white. To avoid confusion, |PIXEL_MAXVAL| will be used here for
white.

If |EOF| is encountered, |end_of_file| is set and the function returns
immediately.

@d BLACK 1
@d WHITE 0@#

@d PIXEL_MAXVAL  255
@d SCALE        4096
@d HALFSCALE    2048@#

@d MAX_CHAR_SIZE 255

@<Global...@>=
HBF_CHAR code;
const unsigned char *bitmap;
            /* a proper input bitmap array will be allocated by the HBF API */
unsigned char *bP;@#

unsigned char out_char[MAX_CHAR_SIZE * MAX_CHAR_SIZE + 1];
                                                 /* the output bitmap array */
unsigned char *out_char_p;@#

unsigned char pixelrow[MAX_CHAR_SIZE];
unsigned char temp_pixelrow[MAX_CHAR_SIZE];
unsigned char new_pixelrow[MAX_CHAR_SIZE];@#

long grayrow[MAX_CHAR_SIZE];@#

long s_mag_x, s_mag_y;


@
@<Initialize arrays@>=
   {int col;


    for(col = 0; col < input_size_x; ++col)
        grayrow[col] = HALFSCALE;@#

    code = (min_char & 0xFF00) + min_2_byte;@#

    s_mag_x = mag_x * magstep * SCALE;
    s_mag_y = mag_y * magstep * SCALE;
   }


@
All arrays of the \\{pixelrow}--family contain gray values. While scaling with
non integer values a pixel of the input bitmap will normally not align with
the pixel grid of the output bitmap (geometrically spoken). In this case we
first compute the fractions of input pixel rows scaled vertically and add the
corresponding gray values until a temporary row is produced. Then we repeat
this procedure horizontally pixel by pixel and write the result into an output
array.


@<Prototypes@>=
void make_pixel_array(void);


@
@c
void make_pixel_array(void)
   {unsigned char *prP;
    unsigned char *temp_prP;
    unsigned char *new_prP;
    long *grP;@#

    register unsigned char *xP;
    register unsigned char *nxP;@#

    register int row, col;
    int rows_read = 0;
    register int need_to_read_row = 1;@#

    long frac_row_to_fill = SCALE;
    long frac_row_left = s_mag_y;@#

    int no_code = FALSE;


    prP = pixelrow;
    temp_prP = temp_pixelrow;
    new_prP = new_pixelrow;
    grP = grayrow;
    out_char_p = out_char; /* will be increased by |write_row()| */@#

again:
    if(b2_codes[code & 0xFF]) /* a valid second byte? */
       {bitmap = hbfGetBitmap(hbf, code);
        bP = (unsigned char *)bitmap;
                                     /* will be increased by |read_row()| */@#

        if(!bitmap)
            empty_char = TRUE;
        else
           {@<Scale row by row@>;
           }
       }
    else
        no_code = TRUE;@#

    if((code & 0xFF) == max_2_byte)
        code += 0xFF - (max_2_byte - min_2_byte); /* go to next plane */
    if(code >= max_char)
       {end_of_file = TRUE;
        return;
       }@#

    code++;@#

    if(no_code)
       {no_code = FALSE;
        goto again;
       }
   }


@
@<Scale row by row@>=
    if(pk_output_size_y == input_size_y)  /* shortcut Y scaling if possible */
        temp_prP = prP;@#

    for(row = 0; row < pk_output_size_y; ++row)
       {@<Scale Y from |pixelrow[]| into |temp_pixelrow[]|@>;
        @<Scale X from |temp_pixelrow[]| into |new_pixelrow[]|
          and write it into |out_char[]|@>;
       }


@
@<Scale Y from |pixelrow[]| into |temp_pixelrow[]|@>=
        if(pk_output_size_y == input_size_y)
                                          /* shortcut Y scaling if possible */
            read_row(prP);
        else
           {while(frac_row_left < frac_row_to_fill)
               {if(need_to_read_row)
                    if(rows_read < input_size_y)
                       {read_row(prP);
                        ++rows_read;
                       }@#

                for(col = 0, xP = prP; col < input_size_x; ++col, ++xP)
                    grP[col] += frac_row_left * (*xP);@#

                frac_row_to_fill -= frac_row_left;
                frac_row_left = s_mag_y;
                need_to_read_row = 1;
               }@#

            @<Produce a temporary row@>;
           }


@
Now |frac_row_left >= frac_row_to_fill|, so we can produce a row.

@<Produce a temporary row@>=
            if(need_to_read_row)
                if(rows_read < input_size_y)
                   {read_row(prP);
                    ++rows_read;
                    need_to_read_row = 0;
                   }@#

            for(col = 0, xP = prP, nxP = temp_prP;
                col < input_size_x; ++col, ++xP, ++nxP)
               {register long g;


                g = grP[col] + frac_row_to_fill * (*xP);
                g /= SCALE;
                if(g > PIXEL_MAXVAL)
                    g = PIXEL_MAXVAL;@#

                *nxP = g;
                grP[col] = HALFSCALE;
               }@#

            frac_row_left -= frac_row_to_fill;
            if(frac_row_left == 0)
               {frac_row_left = s_mag_y;
                need_to_read_row = 1;
               }
            frac_row_to_fill = SCALE;


@
@<Scale X from |temp_pixelrow[]| into |new_pixelrow[]|
  and write it into |out_char[]|@>=
        if(pk_output_size_x == input_size_x)
                                          /* shortcut X scaling if possible */
            write_row(temp_prP);
        else
           {register long g = HALFSCALE;
            register long frac_col_to_fill = SCALE;
            register long frac_col_left;
            register int need_col = 0;


            nxP = new_prP;@#

            for(col = 0, xP = temp_prP; col < input_size_x; ++col, ++xP)
               {frac_col_left = s_mag_x;
                while(frac_col_left >= frac_col_to_fill)
                   {if(need_col)
                       {++nxP;
                        g = HALFSCALE;
                       }@#

                    g += frac_col_to_fill * (*xP);
                    g /= SCALE;
                    if(g > PIXEL_MAXVAL)
                        g = PIXEL_MAXVAL;@#

                    *nxP = g;
                    frac_col_left -= frac_col_to_fill;
                    frac_col_to_fill = SCALE;
                    need_col = 1;
                   }@#

                if(frac_col_left > 0)
                   {if(need_col)
                       {++nxP;
                        g = HALFSCALE;
                        need_col = 0;
                       }@#

                    g += frac_col_left * (*xP);
                    frac_col_to_fill -= frac_col_left;
                   }
               }@#

            @<Write out a row@>;
           }


@
@<Write out a row@>=
            if(frac_col_to_fill > 0)
               {--xP;
                g += frac_col_to_fill * (*xP);
               }@#

            if(!need_col)
               {g /= SCALE;
                if(g > PIXEL_MAXVAL)
                    g = PIXEL_MAXVAL;
                *nxP = g;
               }@#

            write_row(new_prP);


@
|read_row()| reads a row from |bitmap[]| and converts it into a graymap row in
\.{pbmplus}--style (white~255 and black~0).

@<Prototypes@>=
#ifdef __GNUC__
inline
#endif
void read_row(unsigned char *);


@
@c
#ifdef __GNUC__
inline
#endif
void read_row(pixelrow)
  unsigned char *pixelrow;

   {register int col, bitshift;
    register unsigned char *xP;
    register unsigned char item = 0;


    bitshift = -1;@#

    for(col = 0, xP = pixelrow; col < input_size_x; ++col, ++xP)
       {if(bitshift == -1)
           {item = *(bP++); /* increase input bitmap pointer */
            bitshift = 7;
           }
        *xP = ((item >> bitshift) & 1) == 1 ? 0 : PIXEL_MAXVAL;
        --bitshift;
       }
   }


@
|write_row()| converts the graymap back into a bitmap using a simple
threshold.

@<Global...@>=
int threshold = 128;


@
@<Prototypes@>=
#ifdef __GNUC__
inline
#endif
void write_row(unsigned char *);


@
@c
#ifdef __GNUC__
inline
#endif
void write_row(pixelrow)
  unsigned char *pixelrow;

   {register int col;
    register unsigned char *xP;


    for(col = 0, xP = pixelrow; col < pk_output_size_x; ++col, ++xP)
        *(out_char_p++) = (*xP >= threshold) ? 0 : 1;
                                          /* increase output bitmap pointer */
   }


@
Now comes the most interesting routine. The pixel array will be compressed in
sequences of black and white pixels.

|SKIP0| and |SKIP1| indicate how many blank lines will be skipped. |PAINT_(x)|
means that the next x~pixels will have the same color, then the color changes.
|NEW_ROW_(x)| is the first black pixel in the next row.

An example: the pixel sequence 111100011001 [new row] 000111011110 will be
output as 4 3 2 2 1 77 3 1 4 1.

Commands with an ending~`n' in its name indicate that the next n~bytes should
be read as the counter. Example: |SKIP1|~26 means `skip the next 26~rows'.

For further details please refer to ``\mf---the program''.

@d PAINT_(x)    (x)        /* $0 \le x \le 63$ */
@d PAINT1       64
@d PAINT2       65         /* not used */
@d PAINT3       66         /* not used */@#

@d SKIP0        70
@d SKIP1        71
@d SKIP2        72         /* not used */
@d SKIP3        73         /* not used */@#

@d NEW_ROW_(x)  ((x) + 74) /* $0 \le x \le 164$ */@#

@d NOOP        244         /* not used */

@<Prototypes@>=
void write_coding(void);


@
The |goto start| instruction causes some compilers to complain about
``Unreachable code $\ldots$'' or something similar.

@c
void write_coding(void)
   {register int count, skip;
    register unsigned char paint;
    register int x, y;
    register unsigned char *cp;


    x = 0;
    y = 0;
    cp = out_char + y * pk_output_size_x + x;
    count = skip = 0;
    paint = WHITE;
    goto start;@#

    while (y < pk_output_size_y)
       {@<Search blank lines@>;
start:
        @<Process rest of line@>;
        y++;
       }
   }@#


@
@<Search blank lines@>=
        count = 0;
        x = 0;
        cp = out_char + y * pk_output_size_x + x;@#

        while(x < pk_output_size_x)
           {if(*cp == paint)
                count++;
            else
               {if(skip == 0)
                    if(count <= 164)
                        fputc(NEW_ROW_(count), out);
                    else
                       {fputc(NEW_ROW_(0), out);
                        fputc(PAINT_(0), out);
                        fputc(PAINT1, out);
                        fputc(count, out);
                       }
                else
                   {if(skip == 1)
                        fputc(SKIP0, out);
                    else
                       {fputc(SKIP1, out);
                        fputc(skip, out);
                       }
                    skip = 0;
                    if(count < 64)
                        fputc(PAINT_(count), out);
                    else
                       {fputc(PAINT1, out);
                        fputc(count, out);
                       }
                   }
                count = 0;
                paint = BLACK;
                break;
               }
            x++;
            cp++;
           }
        if(x >= pk_output_size_x)
           {skip++;
            y++;
            continue;
           }


@
@<Process rest of line@>=
        while(x < pk_output_size_x)
           {if(*cp == paint)
                count++;
            else
               {if(count < 64)
                    fputc(PAINT_(count), out);
                else
                   {fputc(PAINT1, out);
                    fputc(count, out);
                   }
                count = 1;
                paint = BLACK - paint;
               }
            x++;
            cp++;
           }
        if(paint == BLACK)
           {if(count < 64)
                fputc(PAINT_(count), out);
            else
               {fputc(PAINT1, out);
                fputc(count, out);
               }
            paint = WHITE;
           }


@* The font metrics file.
This routine creates one \.{.pl}--file with the font properties. None of the
font dimensions are needed because you never will use the CJK fonts directly,
and intercharacter stretching is handled by the \\{CJK} macro \.{\\CJKglue}.
(Other packages may define similar commands.)

@<Prototypes@>=
void write_pl(void);


@
@c
void write_pl(void)
   {int i;
    char output_file[FILE_NAME_LENGTH + 1];


    sprintf(output_file, "%s.pl", output_name);
    if(!(out = fopen(output_file, WRITE_TXT)))
       {fprintf(stderr, "Couldn't open %s\n", output_file);
        exit(-1);
       }
    printf("\nWriting %s\n", output_file);@#

    fprintf(out,@/
            "\n(DESIGNSIZE R %.6f)"@/
            "\n(COMMENT DESIGNSIZE IS IN POINTS)"@/
            "\n(COMMENT OTHER SIZES ARE MULTIPLES OF DESIGNSIZE)"@/
            "\n(CHECKSUM O %lo)"@/
            "\n(FONTDIMEN"@/
            "\n   (SLANT R 0.0)"@/
            "\n   (SPACE R 0.0)"@/
            "\n   (STRETCH R 0.0)"@/
            "\n   (SHRINK R 0.0)"@/
            "\n   (XHEIGHT R 1.0)"@/
            "\n   (QUAD R 1.0)"@/
            "\n   (EXTRASPACE R 0.0)"@/
            "\n   )", design_size, checksum);@#

    for(i = 0; i < 256; i++)
       {fprintf(out,@/
                "\n(CHARACTER O %o"@/
                "\n   (CHARWD R %.6f)"@/
                "\n   (CHARHT R %.6f)"@/
                "\n   (CHARDP R %.6f)"@/
                "\n   )",@/
                i,
                tfm_output_size_x + 2 * tfm_offset_x,
                tfm_output_size_y + tfm_offset_y,
                -tfm_offset_y);
       }@#

    fclose(out);
   }


@* The job file.
This routine is the most system specific one. If your operating system needs a
different outline, make appropriate changes here.

You have to call this batch file after \.{hbf2gf} has finished. It will
transform the \.{.gf}--files into \.{.pk}--files and delete the now
unnecessary \.{.gf}--files, then transform the \.{.pl}--file into a
\.{.tfm}--file and copy it |nmb_files| times. The name of the job file is
|output_name|.

@d EXTENSION_LENGTH 8 /* the maximal length of a file extension */@#

@d GFTOPK_NAME "gftopk"
@d PLTOTF_NAME "pltotf"

@<Global...@>=
char job_extension[EXTENSION_LENGTH + 1];
char rm_command[STRING_LENGTH + 1];
char cp_command[STRING_LENGTH + 1];
char pk_directory[STRING_LENGTH + 1];
char tfm_directory[STRING_LENGTH + 1];@#

int long_extension = TRUE;
int tfm_files = TRUE;


@
@<Prototypes@>=
void write_job(void);


@
@c
void write_job(void)
   {FILE *out;
    int i, j;
    char buffer[FILE_NAME_LENGTH + 1];


    strcpy(buffer, output_name);
    strcat(buffer, job_extension);
    if(!(out = fopen(buffer, WRITE_TXT)))
       {fprintf(stderr, "Couldn't open %s\n", buffer);
        exit(-1);
       }
    printf("\nWriting %s\n", buffer);@#

    if(unicode)
       {for(i = (min_char >> 8), j = 0; j < nmb_files; i++, j++)
            fprintf(out,@/
                   "%s %s%02x.gf %s%s%02x.%.0ipk\n"@/
                   "%s %s%02x.gf\n",@/
                   GFTOPK_NAME, output_name, i,@/
                   pk_directory, output_name, i,
                    long_extension ? (int)(dpi_x * magstep + 0.5) : 0,@/
                   rm_command, output_name, i);
       }
    else
       {for(i = 1; i <= nmb_files; i++)
            fprintf(out,@/
                   "%s %s%02i.gf %s%s%02i.%.0ipk\n"@/
                   "%s %s%02i.gf\n",@/
                   GFTOPK_NAME, output_name, i,@/
                   pk_directory, output_name, i,
                    long_extension ? (int)(dpi_x * magstep + 0.5) : 0,@/
                   rm_command, output_name, i);
       }@#

    if(tfm_files)
       {fprintf(out,@/
                "\n"@/
                "%s %s.pl %s.tfm\n"@/
                "%s %s.pl\n"@/
                "\n",@/
                PLTOTF_NAME, output_name, output_name,@/
                rm_command, output_name);@#

        if(unicode)
           {for(i = (min_char >> 8), j = 0; j < nmb_files; i++, j++)
                fprintf(out,@/
                        "%s %s.tfm %s%s%02x.tfm\n",@/
                        cp_command, output_name,
                        tfm_directory, output_name, i);
           }
        else
           {for(i = 1; i <= nmb_files; i++)
                fprintf(out,@/
                        "%s %s.tfm %s%s%02i.tfm\n",@/
                        cp_command, output_name,
                        tfm_directory, output_name, i);
           }@#

        fprintf(out,@/
                "\n"@/
                "%s %s.tfm",@/
                rm_command, output_name);
       }@#

    fclose(out);
   }


@* The configuration file.
Here is a list with all necessary keywords (and parameters):
\medskip
\halign{\quad\.{#}\hfil&\quad#\hfil\cr
        hbf\_header   & the HBF header file name of the input font(s). \cr
        output\_name  & the name stem of the output files. \cr
                      & A running two digit decimal number starting with
                        \.{01} will be appended. \cr
                      & (For Unicode fonts see the keyword \.{unicode}
                        below.) \cr
       }
\bigskip
And now all optional keywords:
\medskip
\halign{\quad\.{#}\hfil&\quad#\hfil\cr
        x\_offset            & increases the character width. \cr
                             & Will be applied on both sides; \cr 
                             & default is the value given in the HBF header
                               (\.{HBF\_BITMAP\_BOUNDING\_BOX}) \cr
                             & scaled to \\{design\/}--size (in pixels). \cr
        y\_offset            & shifts all characters up or down; \cr
                             & default is the value given in the HBF header
                               (\.{HBF\_BITMAP\_BOUNDING\_BOX}) \cr
                             & scaled to \\{design\/}--size (in pixels). \cr
        design\_size         & the designsize (in points) of the font. \cr
                             & \.{x\_offset} and \.{y\_offset} refer to this
                               size. \cr
                             & Default is 10.0 \cr
        mag\_x               & \cr
        mag\_y               & scaling values of the characters to reach
                               designsize. \cr
                             & If only one magnification is given, x and y
                               values are assumed to be equal. \cr
                             & Default is \.{mag\_x} $=$ \.{mag\_y} $=$ 1.0
                               \cr
        target\_size         & the final size (in points) of the font. \cr
                             & The factor \.{target\_size} $/$
                               \.{design\_size} is \TeX's \.{\\magstep}. \cr
                             & Default is \.{design\_size} \cr
                             & \cr
        threshold            & A value between 1 and 254 defining a threshold
                               for converting the internal \cr
                             & graymap into the output bitmap; lower values
                               cut more pixels. \cr
                             & Default value is 128. \cr
                             & \cr
        comment              & a comment describing the font; \cr
                             & default is none. \cr
                             & \cr
        nmb\_fonts           & the number of the fonts. \cr
                             & Default value is~|-1| for creating all
                               fonts. \cr
        unicode              & if `yes', a two digit hexadecimal number will
                               be used as a running number, \cr
                             & starting with the value of the first byte of
                               the first code range. \cr
                             & Default is `no'. \cr
                             & \cr
        dpi\_x               & \cr
        dpi\_y               & the horizontal and vertical resolution (in dpi)
                               of the printer. \cr
                             & If only one resolution is given, x and y values
                               are assumed to be equal. \cr
                             & Default is 300. \cr
        checksum             & a checksum to identify the \.{.gf}--files with
                               the appropriate \.{.tfm}--files. \cr
                             & The default of this 32~bit unsigned integer is
                               the system time (in seconds). \cr
        coding               & a comment describing the coding scheme; \cr
                             & default is none. \cr
                             & \cr
        pk\_directory        & the destination directory of the
                               \.{.pk}--files; \cr
                             & default: none. \cr
                             & Attention! The batch file will not create this
                               directory. \cr
        tfm\_directory       & the destination directory of the
                               \.{.tfm}--files; \cr
                             & default: none. \cr
                             & Attention! The batch file will not create this
                               directory. \cr
        tfm\_files           & whether to create \.{.tfm}--files or not; \cr
                             & default is `yes'. \cr
        long\_extension      & if `yes', \.{.pk}--files will include the
                               resolution in the extension \cr
                             & (e.g. \.{gsso1201.300pk}). \cr
                             & This affects the batch file only (default is
                               `yes'). \cr
        rm\_command          & This shell command removes files; \cr
                             & default: `rm'. \cr
        cp\_command          & This shell command copies files; \cr
                             & default: `cp'. \cr
        job\_extension       & the extension of the batch file which calls
                               \.{GFtoPK} and \.{PLtoTF} \cr
                             & to convert the \.{.gf}-- and the \.{.pl}--files
                               into \.{.pk}-- and \.{.tfm}--files; \cr
                             & default is none. \cr
       }
\bigskip

The searching algorithm (for the keywords) of \.{hbf2gf} is case insensitive;
it makes no difference if you write for example \.{comment} or \.{CommenT}.
The keywords must start a line (be in the first column), and the corresponding
parameters must be on the same line with the keyword and separated by at least
one space or tabulator stop. Lines starting not with a keyword are ignored.

Key values \\{are} case sensitive (except \.{yes} and \.{no}).

The default system dependent values are for \UNIX/--like operating systems; if
you use for example DOS, you must write
\medskip
\halign{\quad\.{#}\hfil&\quad\.{#}\hfil\cr
        long\_extension     & no  \cr
        rm\_command         & del  \cr
        cp\_command         & copy \cr
        job\_extension      & .bat \cr
       }
\medskip

Both the values |pk_output_size_x| and |pk_output_size_y| must not exceed
|MAX_CHAR_SIZE|; \.{x\_offset} and \.{y\_offset} are related to the
designsize (and not to the target size).


@
@d PRINTER_MIN_RES_X    50
@d PRINTER_MIN_RES_Y    50

@<Global...@>=
char Buffer[STRING_LENGTH + 1];


@
@<Prototypes@>=
void read_config(void);


@
@c
void read_config(void)
   {HBF_BBOX *boxp;


    if(!(config = fopen(config_file, READ_TXT)))
       {fprintf(stderr, "Couldn't open %s\n", config_file);
        exit(-1);
       }@#

    @<Necessary parameters@>;
    @<Optional parameters@>;@#

    @<Get code range@>;
    @<Get sub--code range@>;@#

    fclose(config);
   }


@
@<Necess...@>=
    if(!fsearch("hbf_header"))
        config_error("hbf_header");
    else
        strcpy(hbf_header, Buffer);@#

    hbfDebug = 1;            /* we activate error messages of the HBF API
                                       while scanning the HBF header file */@#

    if(!(hbf = hbfOpen(hbf_header)))
        exit(-1);@#

    hbfDebug = 0;

    boxp = hbfBitmapBBox(hbf);
    input_size_x = boxp->hbf_width;
    input_size_y = boxp->hbf_height;@#

    if(!fsearch("output_name"))
        config_error("output_name");
    else
        strcpy(output_name, Buffer);


@
@<Opt...@>=
       {int offset_x;
        int offset_y;


        if(fsearch("nmb_files"))
            nmb_files = atoi(Buffer);
        if(fsearch("unicode"))
            if(Buffer[0] == 'y' || Buffer[0] == 'Y')
                unicode = TRUE;@#

        if(fsearch("tfm_files"))
            if(Buffer[0] == 'n' || Buffer[0] == 'N')
                tfm_files = FALSE;@#

        if(fsearch("mag_x"))
            mag_x = atof(Buffer);
        if(fsearch("mag_y"))
            mag_y = atof(Buffer);
        if(mag_x && !mag_y)
            mag_y = mag_x;
        if(mag_y && !mag_x)
            mag_x = mag_y;
        if(mag_x <= 0)
           {fprintf(stderr, "Invalid horizontal magnification\n");
            exit(-1);
           }
        if(mag_y <= 0)
           {fprintf(stderr, "Invalid vertical magnification\n");
            exit(-1);
           }@#

        if(fsearch("design_size"))
            design_size = atof(Buffer);
        if(fsearch("target_size"))
            target_size = atof(Buffer);
	else
            target_size = design_size;
        magstep = target_size / design_size;@#

        if(fsearch("dpi_x"))
            dpi_x = atoi(Buffer);
        if(fsearch("dpi_y"))
            dpi_y = atoi(Buffer);
        if(dpi_x && !dpi_y)
            dpi_y = dpi_x;
        if(dpi_y && !dpi_x)
            dpi_x = dpi_y;
        if(dpi_x <= PRINTER_MIN_RES_X)
           {fprintf(stderr, "Invalid horizontal printer resolution\n");
            exit(-1);
           }
        if(dpi_y <= PRINTER_MIN_RES_Y)
           {fprintf(stderr, "Invalid vertical printer resolution\n");
            exit(-1);
           }@#

        if(fsearch("x_offset"))
            offset_x = atoi(Buffer);
        else
            offset_x = boxp->hbf_xDisplacement * mag_x + 0.5;
        if(fsearch("y_offset"))
            offset_y = atoi(Buffer);
        else
            offset_y = boxp->hbf_yDisplacement * mag_y + 0.5;
        pk_offset_x = offset_x * magstep + 0.5;
        pk_offset_y = offset_y * magstep + 0.5;
        tfm_offset_x = offset_x / (dpi_x / 72.27) / design_size;
        tfm_offset_y = offset_y / (dpi_y / 72.27) / design_size;@#

        pk_output_size_x = input_size_x * mag_x * magstep + 0.5;
        pk_output_size_y = input_size_y * mag_y * magstep + 0.5;
        tfm_output_size_x = input_size_x * mag_x /
                              (dpi_x / 72.27) / design_size;
        tfm_output_size_y = input_size_y * mag_y /
                              (dpi_y / 72.27) / design_size;
        if(pk_output_size_x > MAX_CHAR_SIZE)
           {fprintf(stderr, "Output character box width too big\n");
            exit(-1);
           }
        if(pk_output_size_y > MAX_CHAR_SIZE)
           {fprintf(stderr, "Output character box height too big\n");
            exit(-1);
           }@#

        if(!fsearch("comment"))
            comment[0] = '\0';
        else
            strcpy(comment, Buffer);@#

        if(fsearch("threshold"))
            threshold = atoi(Buffer);
        if(threshold <= 0 || threshold >= 255)
           {fprintf(stderr, "Invalid threshold\n");
            exit(-1);
           }@#

        if(!fsearch("checksum"))
            checksum = (unsigned long)time(NULL);
                            /* use the system time counter value as default */
        else
           {char *dummy;


            checksum = strtoul(Buffer, &dummy, 10);
           }@#

        if(!fsearch("coding"))
            coding[0] = '\0';
        else
            strcpy(coding, Buffer);@#

        if(!fsearch("pk_directory"))
            pk_directory[0] = '\0';
        else
            strcpy(pk_directory, Buffer);@#

        if(!fsearch("tfm_directory"))
            tfm_directory[0] = '\0';
        else
            strcpy(tfm_directory, Buffer);@#

        if(fsearch("long_extension"))
            if(Buffer[0] == 'n' || Buffer[0] == 'N')
                long_extension = FALSE;@#

        if(fsearch("rm_command"))
            strcpy(rm_command, Buffer);
        else
            strcpy(rm_command, "rm");@#

        if(fsearch("cp_command"))
            strcpy(cp_command, Buffer);
        else
            strcpy(cp_command, "cp");@#

        if(!fsearch("job_extension"))
            job_extension[0] = '\0';
        else
           {strncpy(job_extension, Buffer, EXTENSION_LENGTH);
            job_extension[EXTENSION_LENGTH] = '\0';
           }
       }


@
The function |hbfGetCodeRange()| is an extension to the HBF API.

Successive calls return the code ranges in ascending order; we only need the
extrema of the whole code range.

@<Global...@>=
HBF_CHAR min_char, max_char;


@
@<Get code range@>=
   {const void *cp;
    HBF_CHAR dummy;


    cp = hbfGetCodeRange(hbf, NULL, &min_char, &max_char);
    for(; cp != NULL; cp = hbfGetCodeRange(hbf, cp, &dummy, &max_char))
        ;
   }


@
The function |hbfGetByte2Range()| is an extension to the HBF API.

Successive calls return the byte--2 ranges in ascending order. We raise
|VALID_SUBCODE| in the array |b2_codes[]| for all characters in subcode
ranges.

@d VALID_SUBCODE    1

@<Global...@>=
char b2_codes[256];
unsigned char min_2_byte, max_2_byte;


@
@<Get sub--code range@>=
   {const void *b2r;
    unsigned char dummy;
    int i;


    for(i = 0; i < 256; i++)
        b2_codes[i] = 0;@#

    b2r = hbfGetByte2Range(hbf, NULL, &min_2_byte, &max_2_byte);
    dummy = min_2_byte;
    for(; b2r != NULL; b2r = hbfGetByte2Range(hbf, b2r, &dummy, &max_2_byte))
       {for(i = dummy; i <= max_2_byte; i++)
            b2_codes[i] = VALID_SUBCODE;
       }
   }


@
This search routine is case insignificant. Each keyword must start a line; the
function checks whether the character before the keyword is a newline
character (|'\n'|). It also checks the presence of a parameter and fills
|Buffer| if existent. |fsearch()| returns~1 on success.

@<Prototypes@>=
int fsearch(char *);


@
@c
int fsearch(search_string)
  char *search_string;

   {char *P, p;
    int Ch, ch, old_ch = '\n';
    int count = STRING_LENGTH;


    rewind(config); /* we start at offset~0 */@#

    do
       {P = search_string;
        p = tolower(*P);
        Ch = fgetc(config);
        ch = tolower(Ch);
        while(!(ch == p && old_ch == '\n') && Ch != EOF)
                              /* search first character of |search_string|;
                                        |'\n'| must be the character before */
           {old_ch = ch;
            Ch = fgetc(config);
            ch = tolower(Ch);
           }@#

        for(;;)
           {if(*(++P) == '\0')
                if((Ch = fgetc(config)) == ' ' || Ch == '\t')
                   /* there must be a space or a tab stop after the keyword */
                    goto success;
            Ch = fgetc(config);
            if(tolower(Ch) != tolower(*P))
                break;
           }
       }
    while(Ch != EOF);@#

    return 0;@#

success:
    P = Buffer;@#

    while((Ch = fgetc(config)) == ' ' || Ch == '\t')
                                          /* remove leading blanks and tabs */
        ;
    while(Ch != '\n' && --count > 0 && Ch != EOF) /* fill |Buffer| */
       {*P++ = Ch;
        Ch = fgetc(config);
       }
    *P = '\0';@#

    return (*Buffer) ? 1 : 0; /* is there something in the buffer? */
   }


@
If an error occurs, |config_error()| will leave the program with an error
message.

@<Prototypes@>=
void config_error(char *);


@
@c
void config_error(message)
  char *message;

   {fprintf(stderr, "Couldn't find \"%s\" entry in configuration file\n",
            message);
    exit(-1);
   }


@* An example.
This is an example configuration file (for use with DOS or OS/2):
\bigskip
\halign{\quad\.{#}\hfil&\quad\.{#}\hfil\cr
        hbf\_header          & et24.hbf \cr
        mag\_x               & 2.076 \cr
        x\_offset            & 3 \cr
        y\_offset            & -8 \cr
        comment              & fanti songti 24x24 pixel font scaled and
                               adapted to 12 pt \cr
                             & \cr
        design\_size         & 12.0 \cr
        target\_size         & 17.28 \cr
                             & \cr
        nmb\_fonts           & -1 \cr
                             & \cr
        output\_name         & b5so12 \cr
                             & \cr
        dpi\_x               & 300 \cr
        checksum             & 123456789 \cr
        coding               & codingscheme Big 5 encoded TeX text \cr
                             & \cr
        long\_extension      & no \cr
        job\_extension       & .cmd \cr
        rm\_command          & del \cr
        cp\_command          & copy \cr
        pk\_directory        & c:\\emtex\\texfonts\\pk\\432dpi\\ \cr
        tfm\_directory       & c:\\emtex\\texfonts\\tfm\\ \cr
       }
\bigskip
The call
\medskip
\quad\.{hbf2gf hbf2gf.cfg}
\medskip
creates the files
\medskip
\quad \.{b5so1201.gf}, \.{b5so1202.gf}, $\ldots$ , \.{b5so1255.gf},
\.{b5so12.pl}, and \.{b5so12.cmd}
\bigskip
After calling
\medskip
\quad\.{b5so12.cmd}
\medskip
you will find the \.{.pk}--files in the
\.{c:\\emtex\\texfonts\\pk\\432dpi}--directory and the \.{.tfm}--files in the
\.{c:\\emtex\\texfonts\\tfm}--directory; all \.{.gf}--files and \.{b5so12.pl}
will be deleted.

It is possible to convert bitmap fonts to \.{.pk}--files almost automatically.
The HBF header file already has the entry \.{HBF\_BITMAP\_BOUNDING\_BOX} which
defines vertical and horizontal offsets (in pixels), but these values are not
in all cases optimal for the desired target size. If you omit \.{x\_offset}
and \.{y\_offset} in the \.{.cfg} file, the third and fourth parameter of
\.{HBF\_BITMAP\_BOUNDING\_BOX} is used, scaled to design size and magnified
to target size (to say it with other words: \.{x\_offset} and \.{y\_offset}
will always apply to the designsize to be synchronous with the \.{.tfm}
files).

In the sample, you have a $24 \times 24$ bitmap font which will be scaled to
12~pt and then magnified to 17.28~pt having a resolution of 300~dpi (in the
ordinary case you would suppress the creation of \.{.tfm} files for magnified
fonts):
\medskip
\quad 1 pt are $300 / 72.27 = 4.1511$ pixel;

\quad 12 pt are $4.1511 * 12 = 49.813$ pixel;

\quad thus the theoretical magnification value is $49.813 / 24 = 2.076$.
\medskip
But especially for small sizes, this may not be the best value if the font
should harmonize with, say, Knuth's Computer Modern fonts. I recommend to set
\.{nmb\_files} to a value of~5 (or something similar) to compute 5~\.{.pk}
fonts, then check the CJK font with different \TeX\ fonts to see whether the
offsets and/or the magnification value is good. If all is OK, set
\.{nmb\_files} to |-1| to get all \.{.pk} fonts. The greater the designsize
the finer you can control the offsets---as an example you could use a
designsize of 30~pt and a target size of 12~pt (nevertheless there is a
compile--time constant |MAX_CHAR_SIZE| which limits the maximal character
size; default is 255~pixels).

If you have found optimal offsets, you can produce many different
magnifications of the CJK font using the same set of \.{.tfm} files analogous
to ordinary \TeX\ fonts; just change \.{target\_size} to the desired font
size (and, if necessary, \.{threshold}).


@* Index.
