DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T U

⟦0875c294a⟧ TextFile

    Length: 41245 (0xa11d)
    Types: TextFile
    Notes: Uncompressed file

Derivation

└─⟦060c9c824⟧ Bits:30007080 DKUUG TeX 2/12/89
    └─⟦0539b5426⟧ »./tex82/Unsupported/MFpxl/pxlware/pxtoch.web.Z« 
└─⟦52210d11f⟧ Bits:30007239 EUUGD2: TeX 3 1992-12
    └─⟦beba6c409⟧ »unix3.0/Unsupported.tar.Z« 
        └─⟦25c524ae4⟧ 
            └─⟦0539b5426⟧ »Unsupported/MFpxl/pxlware/pxtoch.web.Z« 
                └─⟦this⟧ 

TextFile

% This program is not copyrighted and can be used freely.
% Version 0 was implemented in January 1983.
% No indication of when version 1 was released.
% Version 1.1 incorporates a change supplied by William LeFebvre of Rice
%	University.  This allows pxtoch to admit that a pxl file with
%	all blank rasters (invisible font) is correct, even though
% 	useless from the standpoint of pxtoch.  PAM (13/DEC/1986)

% Here is TeX material that gets inserted after \input webhdr
\def\hang{\hangindent 3em\indent\ignorespaces}
\def\TeX{T\hbox{\hskip-.1667em\lower.424ex\hbox{E}\hskip-.125em X}}
\font\ninerm=cmr9
\let\mc=\ninerm % medium caps for names like PASCAL
\def\PASCAL{{\mc PASCAL}}

\def\(#1){} % this is used to make module names sort themselves better
\def\9#1{} % this is used for sort keys in the index

\def\title{PX\lowercase{to}CH}
\def\contentspagenumber{1}
\def\topofcontents{\null
  \def\titlepage{F} % include headline on the contents page
  \def\rheader{\mainfont\hfil \contentspagenumber}
  \vfill
  \centerline{\titlefont The {\ttitlefont PXtoCH} processor}
  \vskip 15pt
  \centerline{(Version 1.1, December 1986)}
  \vfill}
\def\botofcontents{\vfill
  \centerline{\hsize 5in\baselineskip9pt
    \vbox{\ninerm\noindent
    The preparation of this report
    was supported in part by the National Science
    Foundation under grants IST-8201926 and MCS-7723738,
    and by the System Development Foundation. `\TeX' is a
    trademark of the American Mathematical Society.}}}
\pageno=\contentspagenumber \advance\pageno by 1

@* Introduction.  
The \.{PXtoCH} utility program converts \TeX\ raster
pixel (``\.{PXL}'') files into character pixel (``\.{CHR}'') files.  A
\.{CHR} file output by this program can be edited with a normal text
editor, and the result can be converted back to \.{PXL} format using the
companion program \.{CHtoPX}.

The first \.{PXtoCH} program was designed by Lynn Ruggles in the winter of
1983. The programs \.{TFtoPL} and \.{PLtoTF} had a significant effect on
the evolution of the present code.  Most of the documentation is from
David Fuchs' description of PXL files.

Currently, only 128 characters are allowed in a font. 

The |banner| string defined here should be changed whenever \.{PXtoCH}
gets modified.

@d banner=='This is PXtoCH, Version 1.1' {printed when the program starts}

@ This program is written entirely in standard \PASCAL, except that
it occasionally has lower case letters in strings that are output.
Such letters can be converted to upper case if necessary. The input is read
from |pxl_file|, and the output is written on |chr_file|; error messages and
other remarks are written on the |error_file|, which the user may
choose to assign to the terminal if the system permits it.
@^system dependencies@>

The term |print| is used instead of |write| when this program writes on
the |error_file|, so that all such output can be easily deflected.

@d print(#)==write(error_file,#)
@d print_ln(#)==write_ln(error_file,#)

@p program PXtoCH(@!pxl_file,@!chr_file,error_file);
const @<Constants@>@/
type @<Types@>@/
var@?@<Global Variables@>@/
procedure initialize;
begin
print_ln(banner);@/
@<Reset input file@>@/
@<Reset error file@>@/
file_ok:=true;
end;

@ Here are some macros for common programming idioms.

@d incr(#) == #:=#+1 {increase a variable by unity}
@d decr(#) == #:=#-1 {decrease a variable by unity}


@* Font metric data.
The idea behind \.{PXL} files is that typesetting routines like \TeX\
need a compact way to store the relevant information about several
dozen fonts, and computer centers need a compact way to store the
relevant information about several hundred fonts. \.{PXL} files are
compact, and most of the information they contain is highly relevant,
so they provide a solution to the problem.

However, sometimes Metafont does not produce very good fonts at a low
resolution and the characters need some editing to make them readable, so
the \.{CHR} files are also provided. The \.{CHR} file can be edited with a
text editor and then converted back into a \.{PXL} file.

@ A \.{PXL} file is considered
to be a series of 32-bit words (on 36-bit machines, the four low-order
bits of each word are always zero).  In the discussion below, ``left
half word'' means the highest order 16 bits in a word, and ``right half
word'' means the 16 next-highest order bits in the word (which are
exactly the lowest order 16 bits on 32 bit machines).

The information in a \.{PXL} file appears in a sequence of 8-bit bytes.
Since the number of bytes is always a multiple of 4, we could
also regard the file as a sequence of 32-bit words; but \TeX\ uses the
byte interpretation, and so does \.{PXtoCH}. Note that the bytes
are all nonnegative and less than $2^{15}$.

A \.{PXL} file may contain raster information for as many as 128 characters 
and as few as 0 characters. An exception to these rules is
planned for oriental fonts;
such fonts, however, are not allowed by this version of \.{PXtoCH}.
@^oriental characters@>@^Chinese characters@>@^Japanese characters@>

Incidentally, when two or more 8-bit bytes are combined to form an integer of
16 or more bits, the most significant bytes appear first in the file.
This is called BigEndian order.

@ @<Types...@>=
@!byte=0..255; {unsigned eight-bit quantity}

@ @<Glob...@>=
@!pxl_file:packed file of 0..255;

@ On some systems you may have to do something special to read a
packed file of bytes. For example, the following code didn't work
when it was first tried at Stanford, because packed files have to be
opened with a special switch setting on the \PASCAL\ that was used.

@<Reset input...@>=
reset(pxl_file);

@^system dependencies@>

@ Additionally, an error file is provided which contains all error messages
for each character. This file can be specified to be the terminal, in which
case all the error messages will be displayed when the program is run.

@<Glob...@>=
@!error_file:text;

@ @<Reset error file@>=
rewrite(error_file);

@* The input file contains the following fields.
\yskip\hang\.{PXLID1} (1 word) is currently equal to 1001 decimal.

\yskip\hang\.{RASTERINFO}  (many words)
All of the \.{PXL} file from word 1 up to the Font Directory contains
raster descriptions.  The raster description of a character is
contained in consecutive words.  Bits containing a 1 correspond to
black pixels.  The leftmost pixel of the top row of a character's
pixel representation corresponds to the most significant bit in the
first word of its raster description.  The next most significant bit
in the first word corresponds to the next to leftmost pixel in its top
row, and so on.  If the character's pixel width is greater than 32,
the $33^{\rm rd}$ bit in the top row corresponds to the most significant bit in
the second word of its raster description.  Each new raster row begins
in a new word, so the final word for each row probably will not be
full (unless the pixel width of the character is evenly divisible by
32).  The most significant bits are the ones that are valid, and the
unused low order bits will be zero.  From this information, it can be
seen that a character with pixel width $w$ and pixel height $h$
requires exactly $\lceil w/32 \rceil \times h$ words for its raster 
description.
As an example of the case where the pixel width is greater than 32,
consider a character whose pixel width is 40 and whose pixel height is 30.  The
first word of its raster description contains the the leftmost 32
pixels of the top row in the character.  The next word of the Raster
Description contains the remaining 8 pixels of the first row of the
character in its most significant 8 bits, with all remaining bits
zero.  The third word contains the left 32 pixels of the second row of
the character, etc.  So, each row takes 2 words, and there are 30
rows, so the raster description of this character requires 60 words.

\yskip\hang\.{FONTDIRECTORY} (512 words)
contains the directory information
for all of the 128 possible characters.  The first four words
of the |font_directory| have Directory Information about the character with
ascii value of zero, the next four words are for the character with
ascii value of one, etc.  Any character not present in the font will
have all four of its Directory Information words set to zero, so the
Directory Information for the character with ascii value $x$ will always
be in words $4 \times x$ through $4 \times x+3$ of the |font_directory|.  
For example, if
the second to last word in a \.{PXL} file contained the value 12000, then
words 12324 through 12327 of the \.{PXL} file contain information about
the ascii character ``Q'', since ascii ``Q'' = '121 octal = 81 decimal,
and $4 \times 81=324$.  The meanings of a character's four Directory words are
described later in this document.

\yskip\hang\.{CHECKSUM} (1 word) should match the checksum in any \.{DVI}
file that refers to this font (otherwise \TeX\ prepared the \.{DVI} file
under the wrong assumptions--it got the checksum from a \.{TFM} file that
doesn't match this \.{PXL} file). However, if this word is zero, no
validity check will be made.  In general, this number will appear to
contain 32 bits of nonsense.

\yskip\hang\.{MAGNIFICATION} (1 word) is an integer representing 
1000 times the
magnification factor at which this font was produced.  If the
magnification factor is \.{XXXX}, the extension to the name of this 
\.{PXL} file should be \.{XXXXPXL}.

\yskip\hang\.{DESIGNSIZE} (1 word) Just as in the \.{TFM} file, it is 
in units
of \.{FIX}es $(2^{-20})$ unmagnified points; remember that there are 72.27
points in an inch.  The design size should also be indicated by the
last characters of the \.{PXL}'s file name.  The design size is not
affected by the magnification.  For instance, if the example font is
CMR5 at 1.5 times regular size, then the \.{PXL} file
would be called \.{CMR5.1500PXL}, word 12513 would contain 1500, and word
12514 would contain $5 \times 2^{20}$.

\yskip\hang\.{DIRECTORYPOINTER} (1 word) is a pointer to the Directory
Information.  If the number of words in the \.{PXL} file is $p$ (i.e. word
numbers zero through $p-1$) then Directory Information Pointer should equal
$p-512-5$.  Note that all pointers are relative to the first word in the
file, which is word zero.

\yskip\hang\.{PXLID2} (1 word) is currently = 1001 decimal (just like 
|pxl_id1|).

@<Glob...@>=
@!pxl_id1,@!checksum,@!magnification,@!design_size,@!directory_ptr,
@!pxl_id2:integer;


@* The Font Directory.
The Font Directory contains four words
of information per character. There are six fields packed into these
four words as follows:

\yskip\hang\.{WORD1}: |pxl_width| (16 bits), plus |pxl_height| (16 bits)
The character's pixel width is in the left half-word, and its pixel height
is in the right half-word.  These numbers have no connection with the
|height| and |width| that \TeX\ thinks the character has (from the \.{TFM}
file); rather, they are the size of the smallest bounding-box that fits
around the black pixels that form the character's raster representation,
i.e. the number of pixels wide and high that the character is.
The pixel width is just large enough to encompass the
leftmost and rightmost black pixels in the character.  Likewise, the
pixel height is just large enough to encompass the topmost and
bottommost black pixels.  In our Q example, its pixel width is 15 and its 
pixel height is 16, so word 12324 in the example \.{PXL} file contains 
$15 \times 2^{16}+16$.

\yskip\hang\.{WORD2}: |x_offset| (16 bits), plus |y_offset| (16 bits) 
This word contains the
offset of the character's reference point from the upper-left-hand
corner of the bounding box: the |x_offset| in the left half-word,
|y_offset| in the right half-word.  These numbers may be negative, and
two's complement representation is used.  Remember that the positive x
direction means `rightward' and positive y is `downward' on the page.
The offsets are in units of pixels.  For the Q, the |x_offset|
is 2 and the |y_offset| is 12, so word 12325 of the example \.{PXL} file
contains $2 \times 2^{16}+12$.

\yskip\hang\.{WORD3}: |raster_address| (32 bits) 
The third word contains the
number of the word in this \.{PXL} file where the raster description for
this character begins.  This number is relative to the beginning of
the \.{PXL} file, the first word of which is numbered zero.  The layout of
raster descriptions is explained below.  The raster descriptions of
consecutive characters need not be in order within a \.{PXL} file--for
instance the raster description for character ``Q'' might be followed by
the raster description of the character ``A''.  Of course, a single
character's raster description is always contained in consecutive
words.

\yskip\hang\. If a character is totally white then the third word contains 
a zero.  (The
|pxl_width|, |pxl_height|, |x_offset| and |y_offset| of a totally white
character must be zero too, although the \.{TFM} Width may be non-zero.  A
non-zero \.{TFM} Width would mean that the character is a fixed width
space.  \TeX 's standard Computer Modern fonts do not contain any such
characters.)  For the ``Q'' example, word 12326 might contain any number
from 0 thru $12000-16$ (since Q's raster description is 16 words long),
let's say it is 600.

\yskip\hang\.{WORD4}: |tfm_width| (32 bits) The fourth word contains the
|tfm_width| of the character.  That is the width that \TeX\ thinks the
character is (exactly as in the \.{TFM} file).  The width is expressed in
\.{FIX}es, which are $1/(2^{20})^{\rm th}$ of the design size.  The
|tfm_width| does not take into account the magnification at which the
\.{PXL} file was prepared.  Thus, if ``Q'' had a width of 7 points in a 12
point font, word 327 in the \.{PXL} file would contain $\lfloor(7/12)
\times 2^{20}\rfloor$.  See the \.{TFM} documentation for more information
on \.{FIX}es.

@ So that is what \.{PXL} files hold. The next question is, ``What about
\.{CHR} files?'' A complete answer to that question appears in the
documentation of the companion program, \.{CHtoPX}, so it will not be
repeated here. Suffice it to say that a \.{CHR} file is an ordinary text
file.  The \.{CHR} format is almost self-explanatory when you see an
example or two. 

@<Glob...@>=
@!chr_file:text;


@* Useful subroutines.
Here we put some subroutines which will be used later in the program.

First we define some functions to simplify reading in the |pxl_file| one byte
at a time and converting it to words or halfwords.
|Read_halfword| reads in two bytes from |pxl_file|.

@p function read_halfword: integer;
var @!word1,@!word2:byte;
begin@/
read(pxl_file,word1);@/
read(pxl_file,word2);@/
read_halfword:=word1*two_8+word2;@/
end;

@ |Read_word| reads in four bytes from |pxl_file|.

@p function read_word:integer;

var i:integer; @!word1:byte; @!word2:integer;
begin@/
word2:=0;
for i := 1 to 4 do
    begin@/
  read (pxl_file,word1);@/
  word2 := word2*two_8 + word1;
    end;

read_word:=word2;

end;

@ |Read_byte| reads in one byte from |pxl_file|.

@p function read_byte:integer;
var word1:integer;
begin@/
read (pxl_file,word1);@/
read_byte:=word1;
end;

@ Here we also define two other procedures, |max| and |min|, which will be used
later. As the name implies, |max| returns the larger of two integers.

@p function max (m,n:integer):integer;

begin
if m>n then max:=m
else max:=n;
end;

@ And |min| returns the smaller of two integers.

@p function min (m,n:integer):integer;

begin
if m>n then min:=n
else min:=m;
end;

@ One other useful routine returns the |ceiling| of ($num / divisor$).
@p function @!ceiling (@!num:integer; @!divisor:integer):integer;
begin
if num mod divisor >0 then ceiling :=num div divisor+1
else ceiling :=num div divisor;
end;


@* The first pass on a PXL file.
This is the first of three sections. It
reads in the |pxl_file| and checks for errors and returns a flag,
|file_ok|, indicating whether anything is wrong with the file. If the file
is ok, the font information is saved. Because the |pxl_file| contains all
of the size information needed by this program at the end of the file, the
file must be read in twice; the first time, the raster information is
ignored and the font information is saved. 


@d field_size = 4  { number of fields per character }
@d num_chrs = 128  { number of characters }
@d font_fields = 512  { |field_size| $\times$ |num_chrs| }
@d pxl_num = 1034  { (|font_fields| $+$ |end_num|) $\times 2$ }
@d init_num = 1    { words of information at beginning of file }
@d end_num = 5    { words of information at end of file }
@d save_end_num = 10  { |end_num| $\times 2$}


@<Const...@>=
@!tbl_size=font_fields;  {size of table needed to hold font info}
@!init_size=init_num;  {words of info at beginning of file}
@!end_size=save_end_num;{halfwords of info at end of file}
@!pxl_size=pxl_num;  {size of |pxl| array}
@!font_size=num_chrs;  {size of |fnt_info| array}
@!two_7=@'200;@/
@!two_8=@'400;@/
@!two_15=@'100000;@/
@!two_16=@'200000;@/
@!two_20=@'4000000;

@ @<Types...@>=
@!halfword=0..@'77777;  {unsigned sixteen-bit quantity}
@!pxl_index=1..pxl_size; {address of a half word in |pxl|}

@ @<Glob...@>=
@!pxl:array [pxl_index] of integer; 
{used to store the last 517 words of |pxl_file| (in halfwords)}
@!pxl_ptr:integer; {an index into |pxl|}
@!file_size,@!max_width,@!max_height:integer; {save extreme values from 
|pxl_file|}
@!file_ok: boolean;    {was the file ok?}
@!too_short:boolean;    {or was it too short?}

@ The input may, of course, be all screwed up and not a \.{PXL} file
at all. So we begin cautiously.

@d abort(#)==begin print_ln(#);
  print_ln('Sorry, but I can''t go on. Are you sure this is a PXL file?');@/
  file_ok := false;
  end

@d error(#)==print_ln(#)

@ Now, we read in the first word in the file which should be equal to
1001 decimal.  If it isn't, we could be reading the wrong type of file.
The program will attempt to continue after printing out a warning message.

@<Read header info@>=
pxl_id1:=read_word; { read and check first word in file }
if pxl_id1 <> 1001 then 
    error ('Wrong file id. Are you sure this is a PXL file?');


@ Next, we read in 517 words (128 characters $\times$ 4 words $+$ 5 words of
font information) from the |pxl_file| and save them in |pxl|.
(For efficiency later on, we read and store them a halfword at a time.)
There should be at least this many words in the file. If there aren't,
then there is something wrong with the file and the program terminates. If
there are no raster descriptions in the file, then these will be not only
the next 517 words, they will also be the last 517 words, which is what we
want. If there are more than 517 words left in the file, we continue
reading the file, circulating the |pxl_ptr| through the array so that
the $517^{\rm th}$ from last word is the next word to be overwritten.  
When the end
of file is reached, the |pxl_ptr| will actually be pointing to the word which
was $517^{\rm th}$ from the end of the file.

@<Read to end of file@>=
pxl_ptr:=1;@/
file_size:=1;@/
too_short:=true;@/
if eof (pxl_file) then error ('The input file was only one byte long!')
    else
  repeat@/
      pxl[pxl_ptr]:=read_halfword;@/
      incr (pxl_ptr);@/
      incr (file_size);@/
too_short:=eof(pxl_file);@/
until (pxl_ptr > pxl_size) or too_short;

if too_short then {it is either too short or is the minimum length}
  if (pxl_ptr <= pxl_size) then abort ('The file is too short!')
  else {minimum length, file contains no glyphs}
{else continue reading until end of file}
else repeat  
if pxl_ptr > pxl_size then pxl_ptr:=1;
pxl[pxl_ptr]:=read_halfword;@/
incr (pxl_ptr); @/
incr (file_size);@/
until eof (pxl_file);
file_size:=file_size div 2;

@ Read in the file for the first pass.
@p procedure check_file (var pxl_ptr : integer);

begin@/
@<Read header info@>@/
@<Read to end of file@>
end;

@ Here, the file information is saved. This information was the last info
read in and so is stored in the 10 halfwords in |pxl| prior to |pxl_ptr|.
(Remember, we are using |pxl| as a circular list.) If this pointer is
somewhere in the middle of the file, all we need to do is look at the
previous ten halfwords. If the pointer is at the beginning or at the end
of the file, however, the font information is split between the end of the
array and the beginning, so we need to extract information from both
places.

@p procedure save_font_info (var pxl_ptr:integer);
var i,j,index:integer;@/
tbl:packed array [1..end_size] of integer;
begin@/
{ save pointers to information in |tbl|}
for i := 1 to end_size do     
    begin@/
      tbl[i] := pxl_ptr-i;@/
      if tbl[i] < 1 then tbl[i] := tbl[i] + pxl_size;
    end;@/
{ then use |tbl| to index pxl }
  checksum:=pxl[tbl[10]]*two_16 + pxl[tbl[9]];  @/
  magnification:=pxl[tbl[8]]*two_16 + pxl[tbl[7]];@/
  design_size:=pxl[tbl[6]]*two_16 + pxl[tbl[5]];@/
  directory_ptr:=pxl[tbl[4]]*two_16 + pxl[tbl[3]];@/
  pxl_id2:=pxl[tbl[2]]*two_16 + pxl[tbl[1]];@/
end;

@ Here is the first pass on the |pxl_file|.

@p procedure first_pass;
begin
check_file (pxl_ptr);@/
if file_ok then save_font_info (pxl_ptr);
end;



@* Sorting the addresses.
The second section of the program is simply a sort procedure which sorts
the font information by the raster address saved for each character.

@<Const...@>=
@!raster_offset=8;  { halfwords stored for each character }
@!raster_begin=4;   { halfwords away from first that the raster 
			address is stored}

@ @<Type...@>=
@!font_type=packed array [1..font_size] of integer; 

@ @<Glob...@>=
@!addr_tbl:font_type;  { array for saving the raster addresses}
@!rank_tbl:font_type;  { array for saving the rank of each address}
@!tbl_ptr2:integer;

@ The raster images in the file are not stored in any particular order,
but each character has stored in its font information, a pointer to its
raster image. In order to process the raster information sequentially (the
order in which it is stored in the file) we need to sort the font
information by the raster addresses. In order to sort the addresses, we
need to extract them from the rest of the font information. This should be
(and is) easy: each raster address is a constant offset from the previous
one. The addresses can be saved by simply cycling through the |pxl| array
and scooping up every $n^{\rm th}$ word, where $n$ is the number of words 
stored in
the array for each character. Along with each address, a rank is stored.
Initially, this rank is just the order in which the addresses are saved.
In the sort procedure, it is the ranks that will be sorted; the addresses
will not actually be shifted around. See details of the sort procedure for
why the sort is done this way.

@<Extract raster address@>=
total_ch:=1;@/
tbl_ptr:=pxl_ptr+raster_begin;   {index of first address}
if tbl_ptr > pxl_size then tbl_ptr:=tbl_ptr -pxl_size;@/
repeat@/
tbl_ptr2:=tbl_ptr+1; if tbl_ptr2>pxl_size then tbl_ptr2:=1;
rank_tbl[total_ch]:=total_ch;@/
addr_tbl[total_ch]:=pxl[tbl_ptr]*two_16 + pxl[tbl_ptr2];@/
incr(total_ch);
tbl_ptr:=tbl_ptr+raster_offset;@/
if tbl_ptr > pxl_size then tbl_ptr:=tbl_ptr -pxl_size;@/
until tbl_ptr+end_size-raster_begin=pxl_ptr;
total_ch:=total_ch-1;

@ Rather than sort the addresses themselves, a rank is sorted.  (there is
a reason for this, stay tuned). If the addresses are sorted, we then have
no way of knowing which address matches which position in the Font
Directory.  If the addresses are not switched around within |addr_tbl|,
then they always stay in the same order in which they were loaded into
|addr_tbl|, and thus correspond to the order in the Font Directory.
Instead, the ranks are switched around, and the sorted order of the
addresses can be determined by the rank associated with the address after
the sort is done. Contrariwise, the addresses could have been sorted and a
pointer to which address it was could have been saved with the address,
but the rank method will be used to good advantage later on, so bear with
us.

Switch switches two integer values for the sort routine.

@p procedure switch (var i,j : integer);
var
temp   : integer;
begin      
temp := i;
i := j;
j := temp;
end;  

@ The sort procedure (a quick sort). Note that we are comparing addresses 
(|addr_tbl|), yet exchanging ranks (|rank_tbl|).

@p procedure sort (m, n  : integer);
var
@!key : integer; @!done : boolean; i,j : integer;
begin      
if m < n then
    begin@/
  { initialize starting points }
  i := m;@/
  j := n + 1;@/
  { set key for comparison }
  key := addr_tbl[rank_tbl[m]];@/
  done := false;@/
  { loop until start counter > finish counter }
  repeat
     { increment start until value in array >= key or
       start = end of array }
      repeat@/
         i := i + 1@/
            until (addr_tbl[rank_tbl[i]] >= key) or (i = n);
      { decrement finish counter until value in array < key
        or finish = beginning of array }
            repeat@/
               j := j - 1@/
            until (addr_tbl[rank_tbl[j]] < key) or (j = m);
     { if start < finish, switch ranks, otherwise 
       loop is done }
            if i < j then switch (rank_tbl[i],rank_tbl[j])
                     else done := true;
  until done;

        switch (rank_tbl[m],rank_tbl[j]);@/
  { list now has all values less than key in one part, all values
    greater than key in the other. now sort the two parts }
        if (j > 1) and (m < (j - 1)) then sort (m,j-1);
        if (j + 1) < n then sort (j+1,n);
      end;
end;       

@ Here is where we use those ranks. We can now go back to |pxl| and move
the Font Directory information to a more useable structure. Each rank in
the address file specifies where the font information should go in the
array |fnt_info|. For example, the information for the first character in
|pxl| is placed into |fnt_info| in the position specified by the rank of
the first address in the sorted address file.
If the rank is 10, then the first character in the |font_directory| is
the tenth character in the raster information. The font information for this
character is saved in bucket 10 in the |fnt_info| array.
If the raster address is zero, no information is saved.

@<Const...@>=
@!two_17=@'400000;

@ @<Types...@>=
@!fnt_rec=record@/
  @!pxl_height:integer;
  @!pxl_width:integer;
  @!x_offset:integer;
  @!y_offset:integer;
  @!raster_addr:integer;
  @!tfm_width:integer;
  @!chr_code:integer
  end;
@!fnt_tbl_type= packed array [1..font_size] of fnt_rec;

@ @<Glob...@>=
@!fnt_info:fnt_tbl_type;  {array for saving character information}
@!total_ch:integer;    {keep track of how many characters there are}
@!index1,@!index2,@!index3:integer;

@ The character with the first raster image in |pxl_file| will be put
into the first position of |fnt_info|, and so on for the entire table.

@<Save font info by rank@>=
max_height:=0;
max_width:=0;

for rnk :=1 to total_ch do
  begin
@<Calculate indexes@>@/
@<Move font information@>
end;

@ @<Glob...@>=
tbl:array[1..raster_offset] of integer;

@ Calculate pointers to the information for this character. If we are in
the middle of the table, we simply look at the next $n$ number of fields,
where $n$ is the number of fields saved for each character. If we are at
the end of the table, and there aren't enough fields left at the end of
the array to contain the information for this character, then we have to
continue looking at the beginning of the array (treating the array as a
circular list). This section determines where the information is in the
table for this character, and saves the pointers to the fields in |tbl|, so
that the first field appears in position |tbl[1]|, the second in position
|tbl[2]|, etc.

@<Calculate indexes@>=
tbl[1]:=(rank_tbl[rnk]-1) * raster_offset + pxl_ptr;
if tbl[1] > pxl_size then tbl[1]:=tbl[1]-pxl_size;
if tbl[1] < pxl_size-raster_offset+1 then
    for i := 2 to raster_offset do
  tbl[i]:=tbl[1] +i-1
else
    for i := 2 to raster_offset do
 begin
    tbl[i]:=tbl[1] +i-1;
    if tbl[i] > pxl_size then tbl[i]:=tbl[i]-pxl_size;
  end;

@ Here we actually move the information stored in |pxl| to the |fnt_info|
table.  This transfer is straightforward for most of the fields, but for
the |x_| and |y_offset| we have to check to see if the fields are
negative, and if so, adjust them before we save them (what we actually do
is check to see if the number is larger than $2^{15}$, and if so, adjust
it so that it is really the two's complement of that number. We want
the value to fall within the range of $-2^{15}$ to $+2^{15}$). Note that the
|pxl_width|, |pxl_height|, |x_offset| and |y_offset| are each contained in one
halfword, and thus one bucket of |pxl|, but the |tfm_width| and the
|raster_addr| are contained in two. We also convert the rank to octal and
save it in the table as the |chr_code|.
@<Move font information@>=
    fnt_info[rnk].pxl_width:=pxl[tbl[1]];@/
    fnt_info[rnk].pxl_height:=pxl[tbl[2]];@/
    if pxl[tbl[3]] > two_15 then 
    fnt_info[rnk].x_offset:=pxl[tbl[3]] -two_16
    else fnt_info[rnk].x_offset:=pxl[tbl[3]];@/
    if pxl[tbl[4]] > two_15 then 
    fnt_info[rnk].y_offset:=pxl[tbl[4]]-two_16
    else fnt_info[rnk].y_offset:=pxl[tbl[4]];@/
    fnt_info[rnk].tfm_width:=pxl[tbl[7]]*two_16 + pxl[tbl[8]];
    fnt_info[rnk].raster_addr:=pxl[tbl[5]]*two_16 + pxl[tbl[6]];@/
    fnt_info[rnk].chr_code:=convert_chrcode(rank_tbl[rnk]);@/
    max_height:=max(fnt_info[rnk].pxl_height,max_height);
    max_width:=max(fnt_info[rnk].pxl_width,max_width);


@ Convert rank to octal for chrcode.
@p function convert_chrcode (var num:integer): integer;
var @!i,@!j:integer;
begin
    j:= (num-1)div 64;@/
    i:= ((num-1)-j*64) div 8;@/
    convert_chrcode:=j*100 + i*10 + ((num-1) mod 8);@/
end;

@ The second section of the program which does the sort.

@p procedure sort_addresses;
var @!tbl_ptr:integer; @!rnk: integer; i: integer;
begin@/
@<Extract raster address@>@/
sort (1,total_ch);@/
@<Save font info by rank@>
end;



@* Second pass. 
The |pxl_file| is read a second time and the information
at the beginning of the file (the raster images) 
is converted into a pictoral representation using periods and
asterisks which is output along with information about the character
including size information, kerning, and position within the raster.

@p procedure second_pass;
var @!index,@!chrptr: integer;
i,j,h,w : integer;

begin@/
@<Reset input file@>@/
@<Set output file@>@/
@<Read first word@>@/
@<Output character file header@>@/
@<Convert raster info to \.{CHR} format@>@/
@<Save final info@>
end;

@ @<Set output file@>=
rewrite (chr_file);

@ Read first word of file for second pass.

@<Read first ...@>=
pxl_id1:=read_word;
byte_count:=0;

@ The first page of the output file contains information about the entire
font. This information comes from the end of the |pxl_file| and needs to 
be saved somewhere, so we'll put it here.
@<Output character file header@>=
write_ln (chr_file,'Font file info :');@/
write_ln (chr_file,'Number of char :',total_ch:1);
write_ln (chr_file,'Checksum       :',checksum:1);
write_ln (chr_file,'Magnification  :',magnification:1);
write_ln (chr_file,'Design size    :',design_size:1);
write_ln (chr_file,'Directory ptr  :',directory_ptr:1);
write_ln (chr_file,'Pixel Id       :',pxl_id1:1);
write_ln (chr_file,'Max height     :',max_height:1);
write_ln (chr_file,'Max width      :',max_width:1);@/


@ We will use these variables when we are converting the binary raster to
the character raster, so we will declare them here.
@<Glob...@>=
@!width,@!height,@!word_width,@!next_byte,@!top,@!bottom,@!left,@!right,
@!offset,@!index,@!byte_count : integer;
i,j,h,w : integer;
@!first_one,@!top_found : boolean;

@ If there is a raster image for this character (its |raster_addr| is not 0),
then we convert it to characters and write it out. While we are doing the 
conversion we will need to save information about the character such as its
position in the raster, the boundaries of the character (its leftmost, 
rightmost, topmost and bottommost pixel), etc.
@<Convert raster info to \.{CHR} format@>=

for index := 1 to font_size do
  begin@/
{check for correct raster address}
  height:=fnt_info[index].pxl_height;@/
  if (height <> 0) and (fnt_info[index].raster_addr <> 0) then begin
   width:=fnt_info[index].pxl_width;@/
  word_width:=ceiling (width,word_size);@/
  right:=0;@/
  left:=word_size+1;@/
  top_found:=false;@/
  @<Read raster info@>@/
  @<Save character info@>@/
  @<Output character info@>@/
  @<Output raster@>
  end;
  end;

@ Here are the characters we will use to print out the character image.
@<Const...@>=
@!asterisk='*';    { a black or 'on' pixel}@/
@!blank=' ';    { a position marker }@/
@!period='.';      { a white or 'off' pixel}@/
@!dash='-';    { the x-offset }
@!bar=':';    { the y-offset}
@!plus='+';    { the origin}
@!word_size=32;@/
@!max_pxl_size=10000;  { big number large enough to accommodate 
           $height \times width \times resolution$ for one character }

@ More variables needed to save the raster image.
@<Glob...@>=
@!chr : packed array [1..max_pxl_size] of char; {this is where we save 
the raster until we print it out}
@!bit_on : boolean;    {is this pixel black or white?}
@!two_power: integer;    {used to decriment the binary number representing 
			  the raster}
@!b:integer;      {which byte of the word we are processing}
@!bit:integer;      {location of the current pixel within the word}


@ For the height of the character, read |word_width| words and process them.
The value of |word_width| is equal to the number of 32-bit words needed 
to store the width of the character. The words will actually be processed 
one byte at a time.
@<Read raster info@>=

for h := 1 to height do
    begin
  first_one:=true;@/
  for w := 1 to word_width do@/
    begin
        for b := 1 to 4 do
          begin
    next_byte :=read_byte;@/
    incr(byte_count);@/
    @<Create star raster@>    
          end
    end;
    end;

@ To create the character raster, we will process each byte from left to right,
or from its highest order digit to its lowest. After each digit is converted to
either a period or a star, it is saved in |chr|, which is actually a very long
one dimensional array. Since we don't know in advance how high and wide a 
particular character is going to be, we can't allocate a two dimensional 
array of the right size, so a one dimensional one will have to do. 
@<Create star raster@>=
two_power:=two_7;@/
i := 1;@/
offset:=(word_width * (h-1) + (w-1)) * word_size;@/
bit_on := false;@/
if next_byte <> 0 then bottom := h;@/
while next_byte <> 0 do
      begin@/
  bit_on := next_byte div two_power = 1;@/
  next_byte := next_byte mod two_power;@/
  two_power := two_power div 2;@/
  bit:=i+(b-1)*8;@/
  @<Save location of character extremes@>@/
  incr(i);
    end;@/

{fill up rest of buffer with blanks}
for j := i to 8 do 
    begin
  chr [offset+j+(b-1)*8] := period;
    end;

@ After we determine whether this bit is on or off, we first of all need to 
save it as either a period or an asterisk, then if it is an asterisk, we need
to check and see whether the pixel we have just processed is at the extremes
of the raster. If so, we want to update our raster information.
@<Save location of character extremes@>=
  if bit_on
      then
    begin@/
        chr [offset+bit] := asterisk;
        if not top_found then
      begin
          top_found := true;
          top := h;
      end;
        if next_byte = 0 then
                        right:=max(right,bit+(w-1)*word_size);
        if first_one then 
      if (bit+(w-1)*word_size)< left then
          begin
              left:=min(left,bit+(w-1)*word_size);
        first_one:=false;
          end;
    end
      else
    chr [offset+bit] := period;

@ Variables used to save character information.
@<Glob...@>=
@!data_end,@!data_row_count,@!rows_from_top,@!internal_width,@!raster_width,
@!right_kerning,@!depth: integer;
@!x_limit,@!y_limit,x,y:integer;



@ Once we have finished reading in the binary raster, we can calculate
some information about the character we have just processed.
@<Save character info@>=
data_row_count:=bottom-top+1;@/
depth:=bottom-top-fnt_info[index].y_offset + 1;@/
right_kerning:=right-fnt_info[index].tfm_width div two_20;@/
raster_width:=right-fnt_info[index].x_offset;@/
internal_width:=right-left+1;@/
rows_from_top:=top;@/
data_end:=bottom;@/

@ Each character will appear on a separate page in the output file. 
The first thing that we want to output are statistics for the character.

@<Output character info@>=
page(chr_file);@/
write_ln(chr_file,'chrcode        :',fnt_info[index].chr_code:1);
write_ln(chr_file,'height         :',fnt_info[index].pxl_height:1);
write_ln(chr_file,'width          :',fnt_info[index].pxl_width:1);
write_ln(chr_file,'right          :',right:1);@/
write_ln(chr_file,'left           :',left:1);@/
write_ln(chr_file,'depth          :',depth:1);@/
write_ln(chr_file,'x offset       :',fnt_info[index].x_offset:1);
write_ln(chr_file,'y offset       :',fnt_info[index].y_offset:1);
write_ln(chr_file,'tfm width      :',fnt_info[index].tfm_width:1);
write_ln(chr_file,'raster width   :',raster_width:1);
write_ln(chr_file,'internal width :',internal_width:1);
write_ln(chr_file,'right kerning  :',right_kerning:1);
write_ln(chr_file,'top            :',top:1);@/
write_ln(chr_file,'bottom         :',bottom:1);@/
write_ln(chr_file,'data row count :',data_row_count:1);

@ @<Glob...@>=
max_raster,max_diff:integer;

@ Then we output the character itself. Blanks to the right of the
character's raster width will not be output (what you see is what you
get). Using the information saved for this character, we will output
a raster only large enough to hold the character. There is some additional
information that appears to the top and left of the character raster, 
notably the |x_offset| position, the |y_offset| position, and the origin 
(the point where the x and y offsets cross).

@<Output raster@>=

@<Write out reference point@>@/
max_raster:=max(height,fnt_info[index].y_offset);
max_diff:=max(fnt_info[index].y_offset-height+1,0);
for h := 1 to max_diff do
    begin@/
  for x:=x_limit to (width - 1) do write (chr_file,blank);
  write_ln(chr_file);
    end;
for h := 0 to height - 1 do
    begin@/
  offset:=h*word_width*word_size+1;@/
  chrptr:=offset;@/
  for x := x_limit to -1 do
      if (fnt_info[index].y_offset = (h + max_diff)) and (x=-1) then
    write(chr_file,bar) else write(chr_file,blank);
  while chrptr <= right+offset-1 do
      begin@/
    write (chr_file,chr[chrptr]);@/
    incr(chrptr);
      end;@/
        write_ln (chr_file);
    end;
write_ln (chr_file);


@ It is a little tricky to figure out where the reference points are to be
printed. 
Basically we want to print from the smaller of the |y_offset| or -1 down
the page, and from the smaller of the |x_offset| or -1 across the page.
If either one lies on the origin (-1), then we want to print the origin instead
of the reference point. The |y_offset| will always be printed in column -1 and
the |x_offset| will always be printed in row -1. The origin appears at the 
point where the two would intersect $(-1,-1)$.

@<Write out reference point@>=
x_limit:=min(fnt_info[index].x_offset,-1);@/
y_limit:=min(fnt_info[index].y_offset,-1);
if y_limit <-1 then
for y := y_limit to -2 do
begin
for x:= x_limit to width do
   if (x=-1) and (y=y_limit) then write (chr_file,bar) 
			     else write(chr_file,blank);
write_ln (chr_file);
end;
for x := x_limit to width do
if (x=-1) then write (chr_file,plus) 
else if (x=fnt_info[index].x_offset) then write (chr_file,dash) 
else write(chr_file,blank);
write_ln (chr_file);

@ Finally, we print out some statistics on the file, and we are done.
@<Save final...@>=
print_ln('There are ',total_ch:1,' characters.');@/
print_ln('The maximum height is ',max_height:1,'.');@/
print_ln('The maximum width is ',max_width:1,'.');@/
print_ln('The magnification is ',magnification:1,'.');@/
print_ln('The design size is ',design_size:1,'.');@/
print_ln('The pixel id is ',pxl_id1:1,'.');@/
print_ln('All done.');


@* The main program.
Initialize the files, read the |pxl_file| once to extract the file information
and the font table, sort the font table, then read it again to extract
the raster information.

Here is where \.{PXtoCH} begins and ends.

@p begin@/
initialize;@/
first_pass;@/
file_ok:=pxl_id1 = pxl_id2;
if file_ok then
    begin@/
  sort_addresses;@/
  second_pass;
    end
else 
    if pxl_id1 <> pxl_id2 then 
  begin
   error ('Pxl Id at beginning of file does not match Pxl Id at end of file.');
   error ('No processing done.');
  end;
end.

@* System-dependent changes.
This module should be replaced, if necessary, by changes to the program
that are necessary to make \.{PXtoCH} work at a particular installation.
It is usually best to design your change file so that all changes to
previous modules preserve the module numbering; then everybody's version
will be consistent with the printed program. More extensive changes,
which introduce new modules, can be inserted here; then only the index
itself will get a new module number.
@^system dependencies@>




@* Index.
Pointers to error messages appear here together with the section numbers
where each ident\-i\-fier is used.