// PLMaker.bcpl

// edited by Ramshaw  December 23, 1980  1:57 PM remove f-kerning
//   for italic fonts, since italic correction takes care of it
// edited by Ramshaw  December 14, 1980  5:04 PM new PL and TFM format,
//    more data about fonts.  Added PLMakerData module
// edited by Ramshaw  July 11, 1980  11:58 AM ignore chars above #177, 
//     plus new families
// edited by Guibas  October 18, 1979  8:54 AM new OS plus more families
// edited by Wyatt  September 4, 1979  1:59 PM (NEW PL FORMAT)
// edited by Wyatt  May 3, 1979  3:02 PM (changed SPACE computation)
// edited by Wyatt  April 29, 1979  5:43 PM (added TIMESROMANSC,HELVETICASC)
// edited by Wyatt  February 19, 1979  3:18 PM
// edited by Guibas  February 18, 1979  1:13 PM to add trident code
// modified 2/12/79 to create .PL files for an entire character dictionary


// load with: Bldr PLMaker PLMaker1 PLMakerData gp tfs* loadram triconmc
// (tfs* from <alto>tfs.dm)

// read a prepress character (.ac) file, and produce a property list (.PL)
// file suitable for consumption by a sail program which produces
// "TFP" files for TEX

get "altodefs.d"; // from [maxc]<alto>
get "streams.d"; // from [maxc]<alto>
get "ix.dfs"; // from [ivy]<tex>
get "plmaker.d"; // from [ivy]<tex>

manifest
[
cr=$*n
quadchar=#17 // em space in newer PARC fonts
]

external
[
// from GP
SetupReadParam; ReadParam;

// from OS
GetFixed; FixedLeft;
InitializeZone; Allocate; Free;
OpenFile; ReadBlock; WriteBlock;
Gets; Puts; Endofs; Resets; Closes;
Ws; Wo; Wns; Wss;
SetFilePos; FilePos;
DoubleAdd; MoveBlock; SetBlock; Usc; Min; Max;
fpComCm; dsp; SetEndCode;
lvUserFinishProc; lvSwatContextProc;

// Trident disk stuff
LoadRam; // from LoadRam
DiskRamImage; // from TriConMc
TFSSwatContextProc; TFSClose; TFSInit; TFSSilentBoot; // from TFS*
TFSzone; TFSdisk;

// from PLMaker1
Str; Chr; Cr; Dec; UDec; Oct; DNum; ONum; XNum; Char; RNumOver10; Begin; End;
SetPos; GetPos; IncPos; DoubleSub; MulDiv;
WordsForString; CopyString; StringMatch; AppendChar; AppendNum; AppendStr;
DefaultExtension; StripExtension;

//from PLMakerData
InitTables

// outgoing statics
outstream; lev; z; flags; tflags; names; tnames; nt; cwvec;

// outgoing proc (to PlMakerData)
MakeStringCopy
];

static
[
z // free storage zone
logstream // stream for display and log file
infilename
outfilename
instream1=0 // (word) stream on the input file
instream2=0 // another (word) stream on the input file
outstream=0 // (byte) stream on the output file
bc; ec; // beginning and ending chars
nc; // number of characters
fam; face; siz; rot; // family code, face code, size in micas, rotation
sl; wt; ex; // slope, weight, expansion (e.g., $I, $M, $R)
resx; resy; // resolutions (10*number of bits per inch)
cwvec // pointer to CharWidth array from input file
dirvec // pointer to directory array from input file
cwLen // length of cwvec
dirLen // length of dirvec
dirfp // file position of directory
lev=0 // indentation level
fixedPitch // true if font has fixed pitch
fLigs // true if font has "f" ligatures
dLigs // true if font has en-dash and em-dash
mQuad // true if font has em-quad
s40  // true if #40 is a word space
CScode // codingscheme code number
pts  // size of font in PARC points from the dictionary file
texStyle=true // true if PL file is being produced for TEX.
names // pointer to array of names from dictionary
flags // pointer to array of flags associated with names
tnames // pointer to array of family names to be considered
tflags // pointer to array of flags associated with tnames
nt // number of entries in tnames
savedUFP; savedSCP;
TFSzone; TFSdisk = 0 // for the Trident
];

structure S: // BCPL string
[
length byte
body↑0,255 byte
];

let main() be
[
OpenLog("PLMaker.log");

Log("PLMaker of December 18, 1980:*n");

InitStorage();

ReadComCm();

Log("Reading file: "); Log(infilename); Log(".*n*n");

OpenInputFile();

InitTables();

ScanDictionary();

CloseFiles();
];

and
InitStorage() be
[
let res = LoadRam(DiskRamImage,true);
let AltoVersion=(table [ #61014; #1401 ] )()
let eng=AltoVersion<<VERS.eng
if (res ls 0) & (eng ls 4) then [ Ws("Cannot load the RAM."); finish ];
//don't worry if the RAM won't load on a Dolphin or Dorado
savedUFP=@lvUserFinishProc;
@lvUserFinishProc=MyFinish;
savedSCP=@lvSwatContextProc;
@lvSwatContextProc=TFSSwatContextProc;
// make a storage zone
manifest maxzlength=77777b; // max length for a zone
let zlength=FixedLeft()-2500; // maximum available space
if Usc(zlength, maxzlength) gr 0 do zlength=maxzlength;
z=InitializeZone(GetFixed(zlength), zlength);
TFSdisk=TFSInit(z, true, (eng eq 5?1,0)); //drive number is 1 on Dorado
];

and
OpenLog(logname) be
[
logstream=OpenFile(logname, ksTypeWriteOnly, charItem);
if logstream eq 0 do [ Ws("Can't open log file"); abort ];
];

and
Log(s) be [ Ws(s); Wss(logstream,s) ];
// writes string s on both display and log file

and
ReadComCm() be
[
let sv=vec 50; // space for file names
let swv=vec 50; // space for switches
// prepare to read Com.Cm
SetupReadParam(sv, swv);

// interpret global switches
for i=1 to swv!0 do switchon swv!i into
	[
	case $T: case $t: texStyle=true; endcase;
	];

// get input file name from command line
ReadParam($P, "Dictionary file: ");
infilename=MakeStringCopy(sv);
];

and
MyFinish() be
 [ if TFSdisk ne 0 then TFSClose(TFSdisk)
   @lvUserFinishProc=savedUFP
   @lvSwatContextProc=savedSCP
   TFSSilentBoot()
   finish
 ]

and
OpenInputFile() be
[
if TFSdisk then
 [ instream1=OpenFile(infilename,ksTypeReadOnly,wordItem,0,0,0,z,0,TFSdisk)
   instream2=OpenFile(infilename,ksTypeReadOnly,wordItem,0,0,0,z,0,TFSdisk)
   // SetEndCode(TFSInit);
 ]
//now, try to find it on 31
if instream1 eq 0 then
	[
	instream1=OpenFile(infilename, ksTypeReadOnly, wordItem);
	instream2=OpenFile(infilename, ksTypeReadOnly, wordItem);
	]
if instream1 eq 0 do Punt("Can't open input file");
];

and
CloseFiles() be
[
Closes(logstream);
unless instream1 eq 0 do Closes(instream1);
unless instream2 eq 0 do Closes(instream2);
unless outstream eq 0 do Closes(outstream);
];

and
ScanDictionary() be
[
// scan through the dictionary index
	[
	let ix=vec IXLMax; // space for index entry
	ix!0=Gets(instream1); // first word of ix contains type and length
	if ix>>IX.Type eq IXTypeEnd break; // end of index
	ReadBlock(instream1, ix+1, ix>>IX.Length-1); // read rest of index entry
	switchon ix>>IX.Type into
		[
		case IXTypeName:
			EnterName(lv ix>>IXN.Name, ix>>IXN.Code);
			endcase;
		case IXTypeChars:
		case IXTypeOrbitChars:
		case IXTypeMultiChars:
			ProcessChars(ix);
		default: endcase; // just ignore it
		];
	] repeat
];

and
EnterName(name, code) be
[
names!code=MakeStringCopy(name);
// look up the name in tnames
for i=0 to nt-1 do
	[
	if StringMatch(name,tnames!i) do
		[
		flags!code=tflags!i;
		(flags!code)<<incl=1;
		return;
		];
	];
// name not found in tnames
(flags!code)<<incl=0;
];

and
ProcessChars(ix) be
[
// construct font name
let psa=nil; // POINTER TO segment starting address
switchon ix>>IX.Type into
	[
	case IXTypeChars:
	case IXTypeOrbitChars:
		fam=ix>>IX.fam;
		face=ix>>IX.face;
		siz=ix>>IX.siz;
		bc=ix>>IX.bc; ec=ix>>IX.ec;
		rot=ix>>IX.rotation;
		resx=ix>>IX.resolutionx; resy=ix>>IX.resolutiony;
		psa=lv ix>>IX.sa;
		endcase;
	case IXTypeMultiChars:
		fam=ix>>IXM.fam;
		face=ix>>IXM.face;
		siz=ix>>IXM.siz;
		bc=ix>>IXM.bc; ec=ix>>IXM.ec;
		rot=ix>>IXM.rotation;
		resx=ix>>IXM.resolutionx; resy=ix>>IXM.resolutiony;
		psa=lv ix>>IXM.segs↑1.sa; // most recent widths are in 1st seg
		endcase;
	default: Log("[??? IX.Type not chars]*n"); return;
	];
pts=MulDiv(siz,72,2540); // point size
if PARCface(face) then DecodeFace(face, lv wt, lv sl, lv ex);
let name=vec 30; // space for the name
CopyString(name, names!fam); // first, the family name
if (flags!fam)<<ptsize eq 0 then AppendNum(name, pts); // font doesn't
		//scale, so point size should be in file name
//now, put the face into the file name:
test PARCface(face)
 ifso	[
	if wt ne $M do AppendChar(name, wt); // now the face
	if sl ne $R do AppendChar(name, sl);
	if ex ne $R do AppendChar(name, ex);
	]
 ifnot	[
	AppendNum(name,(254-face)/2); //design size in points
	if (face&1) ne 0 then AppendChar(name, $H); // and a half 
	]

Log(name);
if (flags!fam)<<ptsize ne 0 then 
	[
	let s=vec 20
	CopyString(s, " at ")
	AppendNum(s, pts)
	AppendStr(s, "pts")
	Log(s)
	]
if rot ne 0 do
	[
	let s=vec 20;
	CopyString(s, " [rotation ");
	AppendNum(s, (rot+30)/60); // rotation in degrees
	AppendStr(s, "]");
	Log(s);
	];
let f=flags!fam;
if (f<<incl eq 0) % (rot ne 0) % ((f<<ptsize ne 0)&(f<<ptsize ne pts)) do
	[ Log(" ...skipped*n"); return ];
if not PARCface(face) then 
	[ Log("[??? Can't handle non-PARC style fonts!]*n"); return ]
fixedPitch=f<<fixed ne 0;
fLigs=f<<fligs ne 0;
dLigs=f<<dligs ne 0;
mQuad=f<<mquad ne 0;
s40=f<<s40 ne 0;
CScode=f<<CS;

AppendStr(name, ".PL"); // now name is full filename
outfilename=name;
outstream=OpenFile(outfilename, ksTypeWriteOnly, charItem);
if outstream eq 0 do [ Log("[??? Can't open output file]*n"); return ];

SetPos(instream2, psa); // position stream to beginning of segment

// read charwidth array
let nc=ec-bc+1; // number of chars
cwLen=nc*CharWidthsize;
ReadBlock(instream2, cwvec, cwLen);

Log(" ...");

MakePLfile();

Closes(outstream); outstream=0;
Log("OK*n");
];

and
MakePLfile() be
[
// the routine that does the real work
let reso=resx; // TENTHS of pixels per inch

Begin(); Str("FAMILY "); Str(names!fam); End();
Begin(); Str("FACE F "); Chr(wt); Chr(sl); Chr(ex); End();
Begin(); Str("DESIGNSIZE"); DNum(pts); End();
Begin(); Str("CHECKSUM"); DNum(0); End();
Begin(); Str("CODINGSCHEME "); Str(CodeToString(CScode)); End();
Begin(); Str("SEVENBITSAFEFLAG "); Str("TRUE"); End();
Begin(); Str("RESOLUTION"); RNumOver10(reso); End();
Begin(); Str("MICASIZE"); DNum(siz); End();
Begin(); Str("UNITS "); Str("PIXELS"); End();

Begin(); Str("TEXINFO"); Cr();
let italic=sl eq $I;
// for italic fonts, make slant 16%
Begin();
Str("SLANT R "); Str(italic?"0.160","0.000"); // pardon my kludge
End();
let xtrspace=vec 1
if s40 do
	[
	let space,str,shr=vec 1,vec 1,vec 1;
	let cw=Cwp(#40);
	let wx=lv cw>>CharWidth.WX;
	let zero=vec 1;
	zero!0,zero!1=0,0;
	test fixedPitch
	ifnot
		[
		let half=vec 1; // half ← 1/2 wx
		half!1=(wx!1)rshift 1 + (wx!0)lshift 15;
		half!0=(wx!0)rshift 1;
		space!0,space!1=wx!0,wx!1; // space ← wx
		DoubleAdd(space, half); // space ← space + 1/2 wx  (space = 3/2 wx)
		str!0,str!1=wx!0,wx!1; // str ← wx
		shr!0,shr!1=half!0,half!1; // shr ← 1/2 wx
		xtrspace!0,xtrspace!1=half!0,half!1; // xtrspace ← 1/2 wx
		]
	ifso
		[
		space!0,space!1=wx!0,wx!1; // space ← wx
		xtrspace!0,xtrspace!1=wx!0,wx!1; // xtrspace ← wx
		str!0,str!1=0,0; // str ← 0
		shr!0,shr!1=0,0; // shr ← 0
		];
	Begin(); Str("SPACE"); XNum(space); End();
	Begin(); Str("STRETCH"); XNum(str); End();
	Begin(); Str("SHRINK"); XNum(shr); End();
	]
if InFont($x) do
	[
	let cw=Cwp($x);
	let xh=cw>>CharWidth.H+cw>>CharWidth.YB;
	Begin(); Str("XHEIGHT"); DNum(xh); End();
	]
Begin(); Str("QUAD");
test mQuad
	ifso XNum(lv Cwp(quadchar)>>CharWidth.WX)
	ifnot RNumOver10(MulDiv(reso,siz,2540)); //remember reso=10*resolution
End();
if s40 do [ Begin(); Str("EXTRASPACE"); XNum(xtrspace); End() ];
End(); // of TEXINFO

if fLigs % dLigs do MakeLigtable();

for c=bc to ec do if InFont(c) do
	[
	Begin();
	Str("CHARACTER"); Char(c); Cr();
	OutCharInfo(c);
	End();
	];

]

and
Label(c) be
[
Begin(); Str("LABEL"); Char(c); End();
]

and
Lig(c1,c2) be
[
Begin(); Str("LIG"); Char(c1); Char(c2); End();
]

and
Kern(c,d) be
[
Begin(); Str("KRN"); Char(c); DNum(d); End();
]

and
Stop() be
[
Begin(); Str("STOP"); End();
]

and
MakeLigtable() be
[
Begin(); Str("LIGTABLE"); Cr();
if fLigs do
	[
	Label($f);
	let cw=Cwp($f)
	let wx=lv cw>>CharWidth.WX
	let wid=wx!0;
	let corr=cw>>CharWidth.W+cw>>CharWidth.XL-wid
	if (corr gr 0)&(sl eq $R) then 
		[
		if InFont($') then Kern($',corr)
		if InFont($)) then Kern($),corr)
		if InFont($]) then Kern($],corr)
		if InFont($!) then Kern($!,corr)
		if InFont($?) then Kern($?,corr)
		]
	Lig($f,#6); Lig($i,#24); Lig($l,#25); Stop(); // ff, fi, fl
	Label(#6);
	cw=Cwp(#6)
	wx=lv cw>>CharWidth.WX
	wid=wx!0;
	corr=cw>>CharWidth.W+cw>>CharWidth.XL-wid
	if (corr gr 0)&(sl eq $R) then 
		[
		if InFont($') then Kern($',corr)
		if InFont($)) then Kern($),corr)
		if InFont($]) then Kern($],corr)
		if InFont($!) then Kern($!,corr)
		if InFont($?) then Kern($?,corr)
		]
	Lig($i,#21); Lig($l,#22); Stop(); // ffi, ffl
	]
if dLigs do
	[
	Label($-);
	Lig($-,#26); Stop(); // -- = <en dash>
	Label(#26);
	Lig($-,#23); Stop(); // <en dash>- = <em dash>
	]
End(); // of LIGTABLE
]

and
Cwp(c)=cwvec+(c-bc)*CharWidthsize; // pointer to CharWidth structure for c

and
InFont(c) = bc le c & c le ec & Cwp(c)>>CharWidth.H ne HNonExCode;

and
OutCharInfo(c) be
[
let cw=Cwp(c);
let wx=lv cw>>CharWidth.WX;
Begin(); Str("CHARWD"); XNum(wx); End();
let ht=cw>>CharWidth.H+cw>>CharWidth.YB;
Begin(); Str("CHARHT"); DNum(Max(ht,0)); End();
let dp=-cw>>CharWidth.YB;
Begin(); Str("CHARDP"); DNum(Max(dp,0)); End();
let wid=wx!0; // truncate, to round italic correction up
let corr=cw>>CharWidth.W+cw>>CharWidth.XL-wid;
if corr gr 0 do [ Begin(); Str("CHARIC"); DNum(corr); End() ];
];

// from fontwidths.bcpl
and
DecodeFace(face,w,s,e) be [
	@s=(table [ $R; $I ])!(face&1)
	face=face rshift 1
	@w=(table [ $M; $B; $L ])!(face rem 3)
	face=face/3
	@e=(table [ $R; $C; $E ])!(face rem 3)
]

and
PARCface(face) = valof
[
if face ls 0 then resultis false
if face ge 18 then resultis false
resultis true
]

and
CodeToString(code) =valof
[
switchon code into
	[
	  case CSxeroxtext: resultis "XEROX TEXT"
	  case CSalphabetic: resultis "ALPHABETIC"
	  case CSpi: resultis "PI"
	  case CSgraphic: resultis "GRAPHIC"
	  case CSxeroxgreek: resultis "XEROX GREEK"
	  case CSxeroxcyrillic: resultis "XEROX CYRILLIC"
	  case CSsail: resultis "SUAI"
	  case CSsanitizedsail: resultis "SANITIZED SUAI"
	]
]

and
// allocate space for a new string and copy s into it
MakeStringCopy(s) = valof
[
let nw=WordsForString(s); // number of words required
let ss=Allocate(z, nw);
MoveBlock(ss, s, nw);
resultis ss;
];

and
Punt(string) be
[
Log("*nPunt: "); Log(string); Log("*n");
CloseFiles();
abort;
];