Subject: Hershey fonts This submission is being sent in response to a question in texhax [#70]. I (jok@gpu.utcs.toronto.edu) am posting this submission for my father, Oliver. Consequently, everything after this paragraph is written by him. I will accept any responses and forward them to him. His interest in TeX revolves around multi-lingual (hobby) desktop publishing. Hershey fonts for the IBM PC are available from SoftCraft, Inc., and from Austin Code Works. The SoftCraft set consists of four separate databases: HERSHEY.CHR 1594 characters ORIENT.CHR 758 char. PERSIAN.CHR 135 char. HEBREW.CHR 49 char. The HERSHEY.CHR database includes, besides several Roman typefaces, Greek, Russian, German Fraktur, and a variety of graphic symbols; the ORIENT.CHR database has hiragana, katakana, and 623 kanji characters. The format of the SoftCraft Hershey databases is given in their "Font Editing: EFONT/CFONT User's Manual" on p. A5-2. Using this description, I wrote the following Turbo Pascal 4.0 program to generate METAFONT source code from the Hershey plotter directives: HERSHEY.PAS ----------- {$R-} {Range checking off} {$B-} {Short-circuit Boolean evaluation} {$S-} {Stack checking off} {$I+} {I/O checking on} {$N-} {Numeric coprocessor absent} {$M 65500, 16384, 655360} {Turbo 3 default stack and heap} program Hershey; {generates METAFONT source code from a Hershey character database in SoftCraft format} const NMax = 157; {maximum number of plotter pen movement commands for one character in the databases} PenUp = $4000; EndOfChar = 0; var H : file of integer; {Hershey database input} Log : text; {METAFONT source output} Code, Depth, EndChar, Height, I, J, K, N, StartChar, Width, XMax, XMin, Y0, YMax, YMin : integer; XY : array[0..NMax] of integer; {(x, y) coordinates} Z : array[1..NMax] of byte; {METAFONT variables} Database : string[7]; {database filename without extension} procedure ReadH; {reads a plotter directive from the input file} begin if Eof(H) then begin Writeln('End of file at char = ', Code); Close(H); Close(Log); Halt; end; Inc(N); Read(H, XY[N]); end; {ReadH} begin if ParamCount <> 3 then begin Writeln('Usage: HERSHEY database start end'); Halt; end; Database := ParamStr(1); for Code := 1 to 7 do Database[Code] := UpCase(Database[Code]); Val(ParamStr(2), StartChar, Code); if Code <> 0 then begin Writeln('Starting character not an integer'); Halt; end; Val(ParamStr(3), EndChar, Code); if Code <> 0 then begin Writeln('Ending character not an integer'); Halt; end; Assign(H, Database + '.CHR'); Reset(H); Assign(Log, Database + '.LOG'); Rewrite(Log); Writeln(Log, '% ', Database + '.CHR ', StartChar, ' to ', EndChar); for Code := 0 to StartChar do begin N := -1; repeat ReadH; until XY[N] = EndOfChar; end; {skip forward to specified start character} Z[1] := 1; for Code := StartChar to EndChar do begin Writeln(Log); Writeln(Log, 'cmchar "', Database, ' ', Code, '";'); N := -1; ReadH; {the width bytes} XMin := Lo(XY[0]); {minimum x coordinate} XMax := Hi(XY[0]); {maximum x coordinate} Width := XMax - XMin; {width of character} Y0 := 73; {default y bias} if Database = 'HERSHEY' then case Code of 0..88: Y0 := 68; 397..672: Y0 := 70; end; {Find minimum and maximum y values.} YMax := 0; YMin := 256; repeat ReadH; if (XY[N] <> PenUp) and (XY[N] <> EndOfChar) then begin if Hi(XY[N]) > YMax then YMax := Hi(XY[N]); if Hi(XY[N]) < YMin then YMin := Hi(XY[N]); end; {if a real coordinate} until XY[N] = EndOfChar; Height := Y0 - YMin; if YMax > Y0 then Depth := YMax - Y0 else Depth := 0; Writeln(Log, 'beginchar(', Code mod 128, ', ', Width, 'u#, ', Height, 'u#, ', Depth, 'u#);'); Writeln(Log, 'adjust_fit(0, 0);'); Writeln(Log, 'pickup plotter_pen;'); for I := 2 to Pred(N) do begin {enumerate unique coordinates} Z[I] := 0; if XY[I] <> PenUp then begin for J := Pred(I) downto 1 do if XY[I] = XY[J] then Z[I] := J; if Z[I] = 0 then Z[I] := I; end; end; for I := 1 to Pred(N) do if Z[I] = I then Writeln(Log, 'z', I, ' = (', Lo(XY[I]) - XMin, 'u, ', Y0 - Hi(XY[I]), 'u);'); I := 0; J := 1; repeat Inc(I); if (XY[I] = PenUp) or (XY[I] = EndOfChar) then begin case I - J of {number of points joined by consecutive line segments} 0: ; 1: Writeln(Log, 'drawdot z', Z[J], ';'); 2: if Z[J] = Z[Succ(J)] then Writeln(Log, 'drawdot z', Z[J], ';') else Writeln(Log, 'draw z', Z[J], '--z', Z[Succ(J)], ';'); else begin Write(Log, 'draw z', Z[J]); for K := Succ(J) to Pred(I) do Write(Log, '--z', Z[K]); Writeln(Log, ';'); end; {3 or more} end; {case} J := Succ(I); end; {if a real coordinate} until I = N; Writeln(Log, 'endchar;'); end; {specified range of characters} Close(H); Close(Log); end. As a test case I used the DOS command hershey orient 0 1 to produce the following output: ORIENT.LOG ---------- % ORIENT.CHR 0 to 1 cmchar "ORIENT 0"; beginchar(0, 27u#, 12u#, 0u#); adjust_fit(0, 0); pickup plotter_pen; z1 = (3u, 10u); z2 = (25u, 10u); z3 = (22u, 12u); z4 = (20u, 10u); z7 = (23u, 11u); draw z1--z2--z3--z4; draw z4--z7; endchar; cmchar "ORIENT 1"; beginchar(1, 27u#, 22u#, 0u#); adjust_fit(0, 0); pickup plotter_pen; z1 = (3u, 20u); z2 = (25u, 20u); z3 = (23u, 22u); z4 = (21u, 20u); z7 = (24u, 21u); z9 = (13u, 20u); z10 = (12u, 13u); z11 = (11u, 9u); z12 = (10u, 7u); z13 = (8u, 4u); z14 = (6u, 2u); z15 = (3u, 0u); z17 = (14u, 20u); z18 = (13u, 13u); z19 = (12u, 9u); z20 = (10u, 5u); z21 = (8u, 3u); z22 = (5u, 1u); z25 = (13u, 14u); z26 = (21u, 14u); z28 = (20u, 14u); z29 = (21u, 15u); z30 = (23u, 14u); z31 = (21u, 11u); z34 = (20u, 7u); z35 = (19u, 3u); z36 = (18u, 2u); z37 = (16u, 1u); z38 = (13u, 2u); z40 = (19u, 2u); z41 = (18u, 1u); z42 = (17u, 1u); z44 = (22u, 14u); z45 = (21u, 7u); z46 = (20u, 3u); z47 = (19u, 1u); z48 = (18u, 0u); z49 = (17u, 0u); draw z1--z2--z3--z4; draw z4--z7; draw z9--z10--z11--z12--z13--z14--z15; draw z17--z18--z19--z20--z21--z22--z15; draw z25--z26; draw z28--z29--z30--z31; draw z26--z34--z35--z36--z37--z38; draw z40--z41--z42; draw z44--z45--z46--z47--z48--z49--z37; endchar; I used the following METAFONT parameter file: ORIENT.MF --------- % This is ORIENT.MF in text format, as of May 6, 1988. % Based on Computer Modern Roman 10 point if unknown cmbase: input cmbase fi font_identifier:="ORIENT"; font_size 10pt#; u#:=20/36pt#; % unit width width_adj#:=0pt#; % width adjustment for certain characters serif_fit#:=0pt#; % extra sidebar near lowercase serifs cap_serif_fit#:=5/36pt#; % extra sidebar near uppercase serifs letter_fit#:=0pt#; % extra space added to all sidebars body_height#:=270/36pt#; % height of tallest characters asc_height#:=250/36pt#; % height of lowercase ascenders cap_height#:=246/36pt#; % height of caps fig_height#:=232/36pt#; % height of numerals x_height#:=155/36pt#; % height of lowercase without ascenders math_axis#:=90/36pt#; % axis of symmetry for math symbols bar_height#:=87/36pt#; % height of crossbar in lowercase e comma_depth#:=70/36pt#; % depth of comma below baseline desc_depth#:=70/36pt#; % depth of lowercase descenders crisp#:=0pt#; % diameter of serif corners tiny#:=8/36pt#; % diameter of rounded corners fine#:=7/36pt#; % diameter of sharply rounded corners thin_join#:=7/36pt#; % width of extrafine details hair#:=9/36pt#; % lowercase hairline breadth stem#:=25/36pt#; % lowercase stem breadth curve#:=30/36pt#; % lowercase curve breadth ess#:=27/36pt#; % breadth in middle of lowercase s flare#:=33/36pt#; % diameter of bulbs or breadth of terminals dot_size#:=38/36pt#; % diameter of dots cap_hair#:=11/36pt#; % uppercase hairline breadth cap_stem#:=32/36pt#; % uppercase stem breadth cap_curve#:=37/36pt#; % uppercase curve breadth cap_ess#:=35/36pt#; % breadth in middle of uppercase s rule_thickness#:=.4pt#; % thickness of lines in math symbols dish#:=1/36pt#; % amount erased at top or bottom of serifs bracket#:=20/36pt#; % vertical distance from serif base to tangent jut#:=28/36pt#; % protrusion of lowercase serifs cap_jut#:=37/36pt#; % protrusion of uppercase serifs beak_jut#:=10/36pt#; % horizontal protrusion of beak serifs beak#:=70/36pt#; % vertical protrusion of beak serifs vair#:=8/36pt#; % vertical diameter of hairlines notch_cut#:=10pt#; % maximum breadth above or below notches bar#:=11/36pt#; % lowercase bar thickness slab#:=11/36pt#; % serif and arm thickness cap_bar#:=11/36pt#; % uppercase bar thickness cap_band#:=11/36pt#; % uppercase thickness above/below lobes cap_notch_cut#:=10pt#; % max breadth above/below uppercase notches serif_drop#:=4/36pt#; % vertical drop of sloped serifs stem_corr#:=1/36pt#; % for small refinements of stem breadth vair_corr#:=1/36pt#; % for small refinements of hairline height apex_corr#:=0pt#; % extra width at diagonal junctions o#:=8/36pt#; % amount of overshoot for curves apex_o#:=8/36pt#; % amount of overshoot for diagonal junctions slant:=0; % tilt ratio $(\Delta x/\Delta y)$ fudge:=1; % factor applied to weights of heavy characters math_spread:=0; % extra openness of math symbols superness:=1/sqrt2; % parameter for superellipses superpull:=1/6; % extra openness inside bowls beak_darkness:=11/30; % fraction of triangle inside beak serifs ligs:=2; % level of ligatures to be included square_dots:=false; % should dots be square? hefty:=false; % should we try hard not to be overweight? serifs:=true; % should serifs and bulbs be attached? monospace:=false; % should all characters have the same width? variant_g:=false; % should an italic-style g be used? low_asterisk:=false; % should the asterisk be centered at the axis? math_fitting:=false; % should math-mode spacing be used? font_coding_scheme:="Hershey ORIENT.CHR"; mode_setup; font_setup; px#:=rule_thickness#; % horizontal thickness of pen py#:=.9px#; % vertical pen thickness define_blacker_pixels(px,py); pickup pencircle xscaled px yscaled py; plotter_pen:=savepen; input \ff\orient.log font_slant slant; font_x_height x_height#; if monospace: font_normal_space 9u#; % no stretching or shrinking font_quad 18u#; font_extra_space 9u#; else: font_normal_space 6u#+2letter_fit#; font_normal_stretch 3u#; font_normal_shrink 2u#; font_quad 18u#+4letter_fit#; font_extra_space 2u#; fi bye. The following sequence of DOS commands cd\ff hershey orient 0 127 cd\pcmf mf &cm \mode=epson; \mag=magstep 0; input orient /a=99/t produced ORIENT.240 and ORIENT.TFM in \pcmf for my Epson FX-85 printer, containing the first 128 characters of ORIENT.CHR. The characters in the Austin Code Works Hershey database are numbered 1--4326, with gaps, for a total of 1,377 different alphabetic and graphic characters. The format is described in Norman M. Wolcott and Joseph Hilsenrath, "A Contribution to Computer Typesetting Techniques: Tables of Coordinates for Hershey's Repertory of Occidental Type Fonts and Graphic Symbols," U.S. Department of Commerce, National Bureau of Standards, April 1976. In any case, the following Turbo Pascal 4.0 program converts the ACW Hershey database to SoftCraft format for further processing. ACWTOSC.PAS ----------- {$R-} {Range checking off} {$B-} {Short-circuit Boolean evaluation} {$S-} {Stack checking off} {$I+} {I/O checking on} {$N-} {No numeric coprocessor} {$M 65500, 16384, 655360} {Turbo 3 default stack and heap} program ACWtoSC; {converts the Hershey font tables distributed by The Austin Code Works to the format expected by the Cfont program of SoftCraft, Inc.} const PenUp : integer = $4000; type S3 = string[3]; var Buf1 : array[1..$2000] of char; F1 : text; H : file of integer; L1 : string[80]; Fn : array[1..2] of string[64]; Code, I, L, M, XY : integer; X, Y : array[1..144] of integer; Eoc : boolean; function V(S : S3) : integer; var I : integer; begin Val(S, I, Code); if (Code <> 0) or ((I < -49) and (I <> -64)) or (I > 49) then begin Writeln(L1); Writeln('Code = ', Code); Halt(3); end; V := I; end; begin if ParamCount <> 2 then begin Writeln('Usage: ACWTOSC file1 file2'); Halt(3); end; for L := 1 to 2 do begin Fn[L] := ParamStr(L); for M := 1 to Length(Fn[L]) do Fn[L][M] := UpCase(Fn[L][M]); end; Assign(F1, Fn[1]); SetTextBuf(F1, Buf1); {$I-} Reset(F1); {$I+} if IOResult <> 0 then begin Writeln('Cannot open ', Fn[1]); Halt(3); end; Assign(H, Fn[2]); {$I-} Rewrite(H); {$I+} if IOResult <> 0 then begin Writeln('Cannot open ', Fn[2]); Close(F1); Halt(3); end; while not Eof(F1) do begin Readln(F1, L1); if Copy(L1, 5, 1) <> ' ' then begin M := 0; Eoc := false; end; L := 0; repeat Inc(L); Inc(M); X[M] := V(Copy(L1, 8 * L + 1, 3)); Y[M] := V(Copy(L1, 8 * L + 5, 3)); if (X[M] = -64) and (Y[M] = -64) then begin Eoc := true; Write(H, Code); {two bytes of zeros before each character} XY := 256 * (Y[1] + 64) + X[1] + 64; {width of character} Write(H, XY); for I := 2 to Pred(M) do if X[I] = -64 then Write(H, PenUp) else begin XY := 256 * (73 + Y[I]) + X[I] + 64; Write(H, XY); end; end; until (L = 8) or Eoc; end; Write(H, Code); {two bytes of zeros at end of file} Close(F1); Close(H); end. -------