|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T U
Length: 41245 (0xa11d) Types: TextFile Notes: Uncompressed file
└─⟦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⟧
% 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.