summaryrefslogtreecommitdiff
path: root/obsolete/systems/knuth/pxl/chtopx.web
blob: 32372247d52a8b261f2b6f600bd544d6428bcfe7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
% This program is not copyrighted and can be used freely.
% Version 0 was implemented in May 1983.

% 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=amr9
\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{CH\lowercase{to}PX}
\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 CHtoPX} processor}
  \vskip 15pt
  \centerline{(Version 1, October 1983)}
  \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 \.{CHtoPX} utility program converts \TeX\ raster pixel (``\.{CHR}'')
files into character pixel (``\.{PXL}'') files. 
The \.{CHR} file input by this program can be edited with
a normal text editor, and the result can be converted back to \.{PXL}
format. The companion program \.{PXtoCH} will convert a \.{PXL} file
into a \.{CHR} file.

The first \.{CHtoPX} program was designed by Lynn Ruggles in the spring of
1983.  The programs \.{TFtoPL} and \.{PLtoTF} had a significant effect on
the evolution of the present code.

A \.{CHR} 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 \.{CHtoPX}.
@^oriental characters@>@^Chinese characters@>@^Japanese characters@>

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

@d banner=='This is CHtoPX, Version 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 |chr_file|, and the output is written on |pxl_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.

Each character ends with an |page_marker|, which
is set to chr(12) (cntl-L). This character can be changed for systems which
do not use cntl-L's in file formats. Whatever character is chosen should be
one that will not appear at the beginning of a line. This restriction
excludes all of the letters, and the symbols +, -, :, and blank.

@^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 CHtoPX(@!chr_file,@!pxl_file,@!error_file);
const @<Constants@>@/
type @<Types@>@/
var@?@<Global Variables@>@/
procedure initialize;
begin
@<Open input and output files@>@/
print_ln(banner);@/
end_of_chr:=chr(12);
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}
@d do_nothing == {empty statement}
@d shift(#) == #:=#*2 {multiply by 2}
@d shift_and_add(#) == #:=#*2+1 {multiply by 2 and add one}


@* Font metric data.
The \.{CHR} file is in a format which allows it to be edited with a text
editor.  Once it has been edited, it needs to be converted back
into a \.{PXL} file which contains information that the \TeX\ typesetting
routines can use.  These \.{PXL} files store the information needed by
\TeX\ in a more compact way.

The character raster in the \.{CHR} file sits in an almost invisible bounding
box. This box is |almost| invisible in that all of the positions in the box
are represented by blank spaces, except for the position of the |x_offset|, the
|y_offset|, and the |origin|. These three positions are represented 
respectively
by a dash (-), a bar (?), and a plus sign (+). The character raster sits at
the lower right corner of the bounding box, which means that there may be
space above or to the left of the raster, but not to the right or below it.
None of the dimensions in the following information include the bounding 
box, but instead refer only to the inside box containing the character raster.

In the following information, the symbol (*) indicates that the
information is used by the \.{PXL} file, a (+) indicates that it is
information about the total dimensions of the character raster, and a (-)
indicates that it is information about the character dimensions within the
raster.  Items marked with (*) should not be changed when editing a
character. If after editing the height or width changes, the \.{CHtoPX}
program will detect this and change the values before writing the new
\.{PXL} file.  Note also that there is a lot of redundant information.
This may or may not be eliminated in later versions of the two programs
\.{PXtoCH} and \.{CHtoPX}, once it is determined that the information is
truly redundant or unnecessary.

A \.{CHR} file contains:\par
Font file information (see the \.{PXtoCH} documentation for more details
of these fields):
\yskip\hang |total_char|:total number of characters\par
\hang |checksum| (*):matches the checksum in any \.{DVI} file that refers to 
this font.\par
\hang |magnification| (*): 1000 times the magnification factor at which this 
font was produced.\par
\hang |design_size| (*): matches the design size in the 
\.{TFM} file.\par
\hang |directory_pointer| (*): pointer to the directory information. \par
\hang |pixel_id| (*): currently $= 1001$ decimal.\par
\hang |max_height|: not currently used, but available. The height of the 
tallest raster in the file.\par
\hang |max_width|: also not used. The width of the widest raster in the file.
\par
\yskip\noindent

Character information: 
\yskip\hang |chr_code|:ascii code of the character in octal.\par
\hang |pxl_height| (*)(+): total height of the raster\par
\hang |pxl_width| (*)(+): total width of the raster\par
\hang |x_offset| (*): offset from upper left hand corner of bounding box\par
\hang |y_offset| (*): offset from upper left hand corner of bounding box\par
\hang |tfm_width| (*): width that the \.{TFM} file thinks the character is 
in \.{FIX}es\par
\hang |right| (-): rightmost pixel in character \par
\hang |left| (-): leftmost pixel in character\par
\hang |depth| (-): number of pixels below baseline\par
\hang |raster_width| (-): actual width of the raster from the x offset\par
\hang |internal_width| (-): width of character from x offset\par
\hang |right_kerning| (-): position where right kerning begins\par
\hang |top| (-): top row of the character\par
\hang |data_row_count| (-): height of the character within the raster\par
\yskip\noindent

Raster information:
\yskip\hang + - origin (-1,-1)\par
\hang - - x-offset\par
\hang : - y-offset\par
\hang . - white pixels in the raster\par
\hang * - black pixels in the raster\par
\yskip\noindent

\hbox {Sample raster: \hskip 1in

\vtop{\tt
      \hbox {+   -             \hfill}
      \hbox { .......*........ \hfill}
      \hbox { ......*.*....... \hfill}
      \hbox { .....*...*...... \hfill}
      \hbox { ....*.....*..... \hfill}
      \hbox {:...*********.... \hfill}
      \hbox { .......*........ \hfill}
      \hbox { .......*........ \hfill}
      \hbox { .......*........ \hfill}
}}

@ @<Type...@>=
@!font_rec=record@/
  @!pxl_height:integer;
  @!pxl_width:integer;
  @!x_offset:integer;
  @!y_offset:integer;
  @!raster_addr:integer;
  @!tfm_width:integer;
  @!chr_code:integer;
  end;
@!font_tbl_type= packed array [1..tbl_size] of font_rec;

@ @<Glob...@>=
@!end_of_chr:char;    {end of character marker}
@!font_info:font_tbl_type;
@!save_right:integer;    {value read in from |chr_file|}
@!save_left:integer;    {value read in from |chr_file|}
@!save_depth:integer;    {value read in from |chr_file|}
@!save_internal_width:integer;    {value read in from |chr_file|}
@!old_raster_addr:integer;    {value read in from |chr_file|}
@!save_kerning:integer;    {value read in from |chr_file|}
@!save_top:integer;    {value read in from |chr_file|}
@!save_bottom:integer;    {value read in from |chr_file|}
@!save_data_row_count:integer;    {value read in from |chr_file|}
@!save_raster_width:integer;    {value read in from |chr_file|}

@!total_char,@!checksum,@!magnification,@!design_size,@!directory_ptr,
@!pixel_id,@!max_height,@!max_width:integer;

@ 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;
@!page_marker:char;
@!file_ok:boolean;

@ 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;


@ Detailed information about the |pxl_file| appears in the documentation
of the companion program, \.{PXtoCH}, so it will not be repeated here.

@<Type...@>=
@!byte=0..255;

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

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

@<Open input...@>=
rewrite(pxl_file);@/
reset(chr_file);@/
rewrite(error_file);

@^system dependencies@>

@ The input may, of course, be all screwed up and not a \.{CHR} file
at all, so we provide a means of escape.

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

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


@* Useful subroutines.
@ |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;

@ |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;

@ If we are not at the end of the row, read the next character from
|chr_file|. If we are at the end of the row, set |ch| to a predefined
|end_of_line| character.  If this is the end of the character (and also
the end of the page), set an |end_of_page| flag. If the character read in
matches the |end_of_line| character, it is an error, so change it to a 
period and print out a message.

@<Glob...@>=
@!end_of_page:boolean;  {end of page flag}
@!raster_found:boolean; {end of raster flag}
@!ch:char;  {next character read in}

@ @p procedure get_next_ch (var ch:char);
begin
ch := end_of_line;
if not eoln(chr_file) then read(chr_file,ch);
if ch=end_of_chr then begin 
end_of_page:= true;@/
ch:=end_of_line;@/
end
end;

@ If the header information is missing for a character, or the raster
image is too messed up to continue processing this character, then we will
skip it.  |Skip_to_next_page| prints an error message, then skips to the
beginning of the next character in the file. Usually, each character 
appears on a separate page.

@p procedure skip_to_next_pg;
var @!ch:char;
begin
repeat
read (chr_file,ch);
until (ch=end_of_chr) or eof(chr_file);
if eof(chr_file) then print_ln ('End of file.');
end;

@ We want to write out the information to the \.{PXL} file in 8-bit bytes. 
Here we convert a 32 bit number into 4 eight-bit bytes and write them out
in Big Endian order (highest byte first).

@p procedure write_pxl(@!oneword:integer);
var k,l,m,n:byte;
begin
k:= oneword div @'100000000;@/
l:=(oneword div @'200000) mod 256;@/
m:=(oneword div 256) mod 256;@/
n:=oneword mod 256;@/
write (pxl_file,k,l,m,n);
end;

@ Here we convert a 16 bit number into 2 eight-bit bytes and write them out
in Big Endian order (highest byte first).

@p procedure write_half_pxl(oneword:integer);
var m,n:byte;
begin
if oneword < 0 then 
oneword:=(oneword + 65536);
m:=(oneword div 256);@/
n:=(oneword mod 256);@/
write(pxl_file,m,n);
end;


@* Read font information from CHR file.
The first step in processing the \.{CHR} file is to read in the font file
information. Certain fields must be found or the processing can't continue.
These fields originally came from the \.{PXL} file, and should not be
modified in any way if the \.{CHR} file is changed. They include:
|checksum|, |magnification|, |design_size|, and |pixel_id|. The other fields 
will be checked by \.{CHtoPX}, and if they don't match the current information
in the file, will be updated when the new \.{PXL} file is written out.
|Found_info| is a flag indicating whether a particular field was found, 
and |found_all| keeps track of whether the necessary fields were found.
|File_ok| checks to see if any information was found at all.

First we declare the constants, types and global variables we will need.
There is nothing special about the length of |info_size|; sixteen was
chosen simply because all of the information needed fit into that length.

@<Const...@>=
@!info_size = 16;@/
@!screen_width = 14;   {max. no of char that will fit on one screen / 6 }

@ @<Type...@>=
@!infotype = packed array [1..info_size] of char;

@ @<Glob...@>=
@!info : infotype;  {header we're looking for}
@!found_info,@!found_all,@!done : boolean;  {flags to indicate what we found}
@!num:integer;    {numeric value associated with the current header}
@!i,@!j:integer; {we always need these somewhere}

@ @<Read font info@>=
file_ok:=false;@/
found_all:=true;@/
found_info:=true;@/
read_hdr('Font file info :',found_info);@/
read_info('Number of char :',total_char,found_info,file_ok);@/
read_info('Checksum       :',checksum,found_info,file_ok);@/
found_all:=found_all and found_info;@/
read_info('Magnification  :',magnification,found_info,file_ok);@/
found_all:=found_all and found_info;@/
read_info('Design size    :',design_size,found_info,file_ok);@/
found_all:=found_all and found_info;@/
read_info('Directory ptr  :',directory_ptr,found_info,file_ok);@/
found_all:=found_all and found_info;@/
read_info('Pixel Id       :',pixel_id,found_info,file_ok);@/
found_all:=found_all and found_info;@/
read_info('Max height     :',max_height,found_info,file_ok);@/
read_info('Max width      :',max_width,found_info,file_ok);@/
@<Can we continue?@>

@ Check to see if the needed information was found, and whether we can 
continue. If everything was ok, write out the |pixel_id| to the |pxl_file|.

@<Can we ...@>=
if not file_ok then abort ('No font header information found.')
else if not found_all then 
abort ('Font header fields missing.')
else begin@/
write_pxl(pixel_id);@/
skip_to_next_pg;@/
end;


@ This procedure reads in the header information and the value of the
field associated with the header. If the header information does not match
the expected header, a switch is set so that the next time this procedure
is called, the same field will be checked and no new one read in.  This
will work fine if there are lines missing; it will not work if there is an
extra line or the lines are out of order. If the value is missing, zero is
assumed.

@p procedure read_info(info:infotype; var val:integer; var found_info:boolean;
           var ok:boolean);
var i:integer;
@!name:infotype;  {header we're reading in}
begin

if found_info then
 begin
  for i := 1 to info_size do
    if not eoln (chr_file) then read(chr_file,name[i]) else name[i] := blank;
  if not eoln (chr_file) then readln (chr_file,num) else num:=0;
 end;
found_info := name=info;@/
ok:=found_info or ok;
if not found_info then 
  begin 
  val:=0;
  print(info);@/
  error(' value missing. Substituting 0.');
  end
else val:=num;
end;

@ This procedure reads in the file header. If
the header is missing, that's ok, but we'll print out an error message
anyway. We also set |found| to false, and just in case this is really the
next header, we will try to read in
a number following the header, and save it for the next go round.


@p 
procedure read_hdr(info:infotype;var found_info:boolean);

var i:integer;
@!name:infotype;  {header we're reading in}

begin
for i := 1 to info_size do
    if not eoln (chr_file) then read(chr_file,name[i]) 
    else name[i] := blank;

if name <> info then 
    begin
      error ('Font file header missing. Is this really a CHR file?');
      error ('I will try to continue anyway.');
      found_info:=false; 
      if not eoln(chr_file) then read (chr_file,num) else num:=0;
    end;
readln (chr_file);
end;


@* Read character header.
Now, for each character, the character header is read in, then if the
header is ok, the character raster is read, converted to binary, and
written out. The information about the character is saved in |font_info|.
If the header information is missing the character is skipped.

@<Read and save character information@>=
@<Read character header@>@/
if not chr_ok then begin error ('No character header information found.');@/
        skip_to_next_pg; @/
end
else
@<Read character raster@>@/

@ @<Glob...@>=
@!chr_index,@!temp_chr:integer;
@!chr_code:integer;    {value read in from |chr_file|}
@!chr_ok:boolean;
@!chr_count:integer;    {keeps track of whether we need to start a new line}

@ Read in the character information using the same procedure as was used for
the font information header. Note again, this will work if there are headers
missing, but not if they are out of order or there are extra headers.

The only field that is required in the header is the |chr_code|, because
without it, we have no idea what character we are processing!  If the
|chr_code| is missing, we will skip this character and go on to the next.
Note that the |chr_code| must be the first line of the header, or we
assume that it isn't there.  The other fields will be checked by
\.{CHtoPX}, and if they don't match the current information in the file,
then will be updated when the new \.{PXL} file is written out.
If no value can be calculated, such as for |tfm_width|, it will be set to zero.
|Found_info| is a flag indicating whether a particular field was found,
and |chr_ok| checks to see if any information was found at all.

@<Read character header@>=
chr_ok:=false;@/
found_info:=true;@/

read_info('chrcode        :',chr_code,found_info,chr_ok);@/
if found_info then begin print('[',chr_code:1,']');@/
incr(chr_count); @/
if chr_count > screen_width then begin 
    print_ln(' ');@/
    chr_count := 0;@/
  end;
@<Convert |chr_code| into decimal@>@/
read_info('height         :',font_info[chr_index].pxl_height,found_info,
chr_ok);
read_info('width          :',font_info[chr_index].pxl_width,found_info,chr_ok);
read_info('right          :',save_right,found_info,chr_ok);
read_info('left           :',save_left,found_info,chr_ok);
read_info('depth          :',save_depth,found_info,chr_ok);
read_info('x offset       :',font_info[chr_index].x_offset,found_info,chr_ok);
read_info('y offset       :',font_info[chr_index].y_offset,found_info,chr_ok);
read_info('tfm width      :',font_info[chr_index].tfm_width,found_info,chr_ok);
read_info('raster width   :',save_raster_width,found_info,chr_ok);
read_info('internal width :',save_internal_width,found_info,chr_ok);@/
read_info('right kerning  :',save_kerning,found_info,chr_ok);
read_info('top            :',save_top,found_info,chr_ok);
read_info('bottom         :',save_bottom,found_info,chr_ok);
read_info('data row count :',save_data_row_count,found_info,chr_ok);
end
else error ('Didn''t find chr_code.');

@ @<Convert |chr_code| into decimal@>=
chr_index:=(chr_code div 100) *64;@/
temp_chr:=chr_code mod 100;@/
chr_index:=chr_index+(temp_chr div 10) * 8;@/
temp_chr:=temp_chr mod 10;@/
chr_index:=chr_index + temp_chr + 1;
font_info[chr_index].chr_code := chr_code;


@* Find raster image.
The character raster sits inside a bounding box which contains information 
about
the position of the character within the raster. This outside box may contain
any number of rows or columns above or to the left of the character, 
any or all of
which may be blank (but hopefully not, since we are looking for something 
in these rows and columns). Our first
task, therefore, is to find out where the raster is inside this box, and at
the same time, to save any information the outside box contains pertaining 
to the |x_offset|(indicated by a dash), the |y_offset|(a colon), or the 
|origin|(a plus) of the raster.

@<Const...@>=
@!tbl_size=128;    {current maximum number of characters allowed in file}
@!asterisk='*';    {represents a pixel that is `on', or black}
@!blank=' ';    {filler for bounding box}
@!period='.';    {represents a pixel that is `off', or white}
@!plus='+';    {the (0,0) position in the raster}
@!dash='-';    {the |x_offset|}
@!bar=':';    {the |y_offset|}
@!end_of_line='!';  {marker to signal when we've reached the end of a line}

@ @<Glob...@>=
@!found : (both,origin,x_off,none,letter); {what did we find?}
@!raster_addr:integer;  {keeps track of how many words have been written 
on the |pxl_file|}
@!x_offset:integer;  {position of |x_offset|}
@!y_offset:integer;  {position of |y_offset|}
@!first_column:integer;  {position of the first column of the character raster}
@!bar_row:integer;  {row where we found the bar}
@!col_index:integer;  {current column}
@!row_index:integer;  {current row}
@!dash_column:integer;  {column where dash found}
@!plus_column:integer;  {column where plus found}
@!plus_row:integer;  {row where plus found}

@ It is here that things get sticky. There is no knowing whether either the
offset information or the raster itself
has been modified since being created by
\.{PXtoCH}, and there could be all sorts of strange data in it. We will
hope for the best, yet proceed with caution.

@<Read character raster@>=
begin
end_of_page:=false;@/
row_index:=0;@/
col_index:=0;@/
found:=none;@/
@<Skip to beginning of character@>@/
if end_of_page then begin
error ('No raster found.');@/
@<Zero all fields@>@/
end
else begin 
@<Process raster@>@/
@<Compare old with new values@>@/
end;
end;

@ If no raster information was found, no character information will be saved, 
so all the fields for that character are zeroed out in the |font_info|.

@<Zero all fields@>=
font_info[chr_index].pxl_height:=0;@/
font_info[chr_index].pxl_width:=0;@/
font_info[chr_index].x_offset:=0;@/
font_info[chr_index].y_offset:=0;@/
font_info[chr_index].raster_addr:=0;@/
font_info[chr_index].tfm_width:=0;@/


@ Search for the raster.
Read |chr_file| until we reach the first period or asterisk, signalling the
beginning of the character itself. Look for 
a plus, dash, or colon, which will indicate the position of the origin,
x-offset, 
and y-offset, respectively. If no period or asterisk is found, the character
doesn't exist, and no information will be written out to the |pxl_file|.

@<Skip to beginning...@>=
dash_column:=-1;@/
plus_column:=-1;@/
plus_row:=-1;@/
bar_row:=-1;@/
x_offset:=-1;@/
y_offset:=-1;@/
first_column:=-1;@/
repeat
get_next_ch(ch);@/
@<What is it?@>@/
incr(col_index);
until (end_of_page) or (found=letter);


@ After we read in the next character, check it to see what it is. If it 
is a |plus|, it is the origin; if a |dash|, it is the x-offset; if it is 
a |colon|,
it is the y-offset; if it is a |blank|, it is a place holder; if it is a
|period| or |asterisk|, it is the beginning of the character raster. 
At the |end_of_line| we increment the row count and reset the column count.
@<What is it?@>=
case ch of
plus: begin if (found=origin) or (found=both) then 
  error ('duplicate origin (+) found')
  else begin
  if found=x_off then found := both else found := origin;
  plus_column:=col_index;@/
  plus_row:=row_index;
        end;
      end;
dash: begin if (found=x_off) or (found=both) then 
  error('duplicate x offset (-) found.')
  else begin if found=origin then found:=both else found :=x_off;
  dash_column:=col_index;
        end;
      end;
bar: bar_row:=row_index;
blank: do_nothing;
asterisk, period: @<Save offset information@>;
end_of_line: begin
  readln(chr_file);@/
  incr(row_index);@/
  col_index:=-1;@/
    end;
end;

@ When we find the first period or asterisk, check to see what state we
are in.  If we have found the x-offset (dash) or y-offset (colon), save
their position, relative to the origin. If the x-offset or origin wasn't
found, assume that they are both zero. If the y-offset wasn't found,
that's ok, since it could be that we just haven't gotten to it yet.
There will be some number of spaces to the left of the raster (minimum of one)
and we will save this number in |first_column|, so that when we start to
process
the raster image, we know how many spaces to skip before beginning processing.

@<Save offset information@>=
begin  
case found of
    both,letter:do_nothing;
    origin:dash_column:=plus_column;
    x_off: error('origin (+) not found. Substituting 0.');@/
    none: error ('origin(+) and x offset (-) not found. Using 0 for both.');@/
  end;@/
first_column:=col_index;@/
found:=letter;@/
x_offset:=dash_column-plus_column - 1;@/
y_offset:=bar_row-plus_row - 1;@/
end

@* Read character raster.
Here we read in each row of the character, and convert it
into an equivalent binary number. This number must have four bytes of eight
bits for a total of 32 positions, so if the row we are reading in does
not have a multiple of 32 positions, we have to fill it with zeros so that 
it does. Each 8 bit number is then written out to the |pxl_file|. This 
process is repeated
for each row in the character. Hopefully, each row will have the same number
of characters in it; if they don't, then the raster is not rectangular, 
which could indicate a problem. The raster is processed as is, but a warning
message is printed out. The character raster is read until either an 
|end_of_page| marker or a blank line is found.

@d out(#) == write (pxl_file,(# mod 256));@/

@<Glob...@>=
@!row,@!save_byte,@!left,@!right,@!top,@!bottom,@!num_words:integer;
@!bit:integer;
@!first_line:boolean;  {is this the first row of the raster?}
@!last_col:boolean;   {is this the last col of the raster?}
@!top_found:boolean;  {have we found the first asterisk?}
@!line_width:integer;


@ @<Process raster@>=
top_found:=false;@/
left:=max_width;@/
right:=0;@/
top:=0;@/
bottom:=0;@/
old_raster_addr:=raster_addr;@/
save_byte:=0;@/
end_of_page:=false;@/
row:=row_index-1;@/    
line_width:=0;@/
last_col:=true;@/
first_line:=true;@/
repeat
num_words:=0;@/
row := row + 1;
repeat@/
    incr(num_words);
    raster_found := false;@/
    for i := 1 to 4 do  {4 bytes of}
  begin
        for j := 1 to 8  do  {8 bits each}
      @<Convert character raster@>@/
  if raster_found then 
      begin 
    out (save_byte);@/
          save_byte:=0;@/
      end;
  end;@/
    if raster_found then incr(raster_addr);@/
until ch=end_of_line;
if not end_of_page and raster_found then @<Find beginning of raster@>@/
until end_of_page or not raster_found;
if not raster_found then skip_to_next_pg;

@ Read the next character and convert it. If it is a period, it becomes a
zero; if it is an asterisk, it becomes a one; if it is anything else, it
is treated as a period. What we are doing here is converting the raster
made up of periods and asterisks into a binary number. This number will be
saved by writing it out to the |pxl_file|. We are converting it bit by bit,
so for each character we read in, we either add a zero or a one onto the
end of the number we have calculated so far.

When we get to the end of the line, we also check to see if the line width
matches the width of the first line of the character. If it doesn't, then
the character raster is not rectangular, so an error message is printed, but
processing continues.

@<Convert character raster@>=
begin
bit:=(j+(i-1)*8)+(num_words-1)*32;
    case ch of
  asterisk: begin 
      shift_and_add(save_byte);@/
      raster_found := true;
      @<Check extremes@>
      end;
  period: begin
    shift(save_byte);@/
    raster_found := true;
    end;
  end_of_line: begin
         shift(save_byte);
         if first_line then begin first_line:=false;@/
          last_col:=false;@/
          line_width:=bit-1;@/
          end
         else if last_col then begin
          if bit <> line_width 
      then error('Character has uneven width. Processing as is.');@/
             last_col:=false;@/
             end;
         end;
  blank: shift(save_byte);
  others: begin
    error ('Unrecognized character. Assuming period.');@/
    shift(save_byte);
    end
      end;@/
    get_next_ch(ch);@/
end;


@ We want to keep track of the size of the character within the raster, 
so we can compare it to the values read in in the header. |Left|, |right|,
|top|, and |bottom| will be used for comparison.
@<Check extremes@>=
left := min(bit,left);@/
right :=max(bit,right);@/
if not top_found then begin
  top_found:=true;@/
  top:=row;@/
  end;
bottom:=row;@/

@ Skip over the space to the left of the raster image which is part of the
outside box. If a colon is found, save the row number as the |y_offset|.

@<Find beginning of raster@>=
begin
readln(chr_file);@/
for i := 0 to first_column do
begin
get_next_ch(ch);
if ch = bar then y_offset:=row;
end;
end;

@ Check to see if the two fields, |num1| and |num2|, match.  If not,
replace the second with the first and set a flag so we'll know a
replacement has been done.
@p procedure check_match (var num1:integer; var num2:integer; 
			  var flag:boolean);
begin@/
if num1<> num2 then flag:=false;@/
num2:=num1;@/
end;

@ @<Glob...@>=
@!width:integer;  {width of the raster we just read in}
@!height:integer;  {height of the raster we just read in}
@!all_match:boolean;  {used to see if everything matched the values read in}

@ Check to see if the values read in really match the raster. If not, save the 
raster values and print an error message.
@<Compare old with new values@>=
all_match:=true;@/
width:=right-left+1;@/
height:=bottom-top+1;@/
check_match (width,font_info[chr_index].pxl_width,all_match);@/
check_match (height,font_info[chr_index].pxl_height,all_match);@/
check_match (x_offset,font_info[chr_index].x_offset,all_match);@/
check_match (y_offset,font_info[chr_index].y_offset,all_match);@/
font_info[chr_index].raster_addr:=old_raster_addr;@/
if not all_match then
begin @/
error ('');   {skip to next line}@/
error ('Character raster does not match saved information.');@/
error ('Correct information has been saved.');@/
end@/


@* Write PXL file information.
First, write out all of the font directory information to the |pxl_file|.

@<Write directory information to \.{PXL} file@>=
for i := 1 to tbl_size do
begin@/
write_half_pxl (font_info[i].pxl_width);@/
write_half_pxl (font_info[i].pxl_height);@/
write_half_pxl (font_info[i].x_offset);@/
write_half_pxl (font_info[i].y_offset);@/
write_pxl (font_info[i].raster_addr);@/
write_pxl (font_info[i].tfm_width);@/
end;

@ Finally, write out the font information to the |pxl_file|, and we are done.
@<Write font information to \.{PXL} file@>=
write_pxl (checksum);@/
write_pxl (magnification);@/
write_pxl (design_size);@/
all_match:=true;@/
check_match(raster_addr,directory_ptr,all_match);
if not all_match then error('Saved new directory pointer.');
write_pxl (directory_ptr);@/
write_pxl (pixel_id);@/

print_ln('All done.');


@* Main program.
@ First read in the \.{CHR} file and process the characters.
@<Read |chr_file|@>=
  repeat@/
  @<Read and save character information@>@/
  until eof(chr_file);@/

@ Then write out the \.{PXL} directory information and font information.
@<Write |pxl_file|@>=
@<Write directory information to \.{PXL} file@>@/
@<Write font information to \.{PXL} file@>@/

@ Main program. Here's where we make it, or don't.

@p
begin@/
initialize;@/
@<Read font info@>@/
raster_addr:=1;@/
chr_count := 0;
if file_ok then begin @<Read |chr_file|@>@/
@<Write |pxl_file|@>@/
end;

end.


@* System-dependent changes.
This module should be replaced, if necessary, by changes to the program
that are necessary to make \.{CHtoPX} 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.