// C O N V E R T  -- read SD, create AC  (PREPRESS,PRESS)
//1/23/80:  The bounding box calculation in ConvertAWidth and ConvertAChar
// now checks the value of the flag  convertThicken, so that non-thickened
// rasters are no longer are given a bounding box one unit too tall.

//12/13/77: ConvertAChar now puts out ORbit format characters	J.Maleson
// catalog number ???

//ConvertAWidth(ps,pc,p)
//	Convert the widths of a single character.
//	ps -> SplineWidth.  Fills up pc -> CharWidth.
//	p -> Convert structure.
// returns true if bitmap for character is large
//  (only routines which are prepared to deal with spline
//		output check the conservative return from ConvertAWidth)
//ConvertAChar(w,wo,pc,p,FSGet,FSPut)
//	Converts a single character. p -> Convert structure,
//	which describes some parameters.
//	w is window to read splines from; wo window to put
//	scan-converted characters into.
//	pc -> CharWidth structure to return goodies.
//	Returns an error code:
//		0 = All OK
//		1= not enough core in SCV
//		2,3 = SCV errors in splines
//		4 = too big to scan convert, try ScaleAChar
//		10= Spline description file illegal
//		11= Illegal Press font object
//		12= Scan convert screwed up -- not even # intersections
//		13= Bit intersection out of range
//		14= Conversion did not terminate properly.
//	FSGet,FSPut are routines for getting, releasing storage.
//ScaleAChar(w,wo,pc)
//	Converts a single character into scaled spline representation
//	w is window to read splines from; wo window to put
//	scaled splines into.
//	pc -> CharWidth structure to return goodies.

//SetSCVTransform(siz,rotation,incline,resolutionx,resolutiony)
//	Set up SCV transformation matrix.
//Cos(theta,lvsign,lvmag)
//	Computes the cosine of the angle "theta" (in minutes) and
//	returns sign (0 if positive, -1 if negative) and magnitude
//	(0 to #177777)


get "ix.dfs"
get "scv.dfs"

// outgoing procedures
external
	[
	ConvertAChar
	ConvertAWidth
	ScaleAChar
	SetSCVTransform
	Cos
	]

// outgoing statics
external
	[
	@convertThicken
	@convertOrbitized
	]
static
	[
	@convertThicken=false
	@convertOrbitized=false
	]

// incoming procedures
external
	[
//In SCV package
	SCVInit
	SCVBeginObject
	SCVEndObject
	SCVMoveToF
	SCVDrawToF
	SCVDrawCurve
	SCVMatrix
	SCVReadRuns
	SCVTransformF
	SCVFlush
	Floor


//Window package
	WindowSetPosition
	WindowGetPosition
	WindowReadBlock
	WindowWriteBlock
	WindowRead
	WindowWrite
	WindowCopy

//Block move, store
	Zero; SetBlock; MoveBlock

//Misc
	MulDiv
	MulFull;DivFull
	DoubleAddV

//Floating point
	FLD;FST;FLDI;FNEG;FAD;FML;FDV
	FCM;FLDV;FSTDP;FTR
	]

// incoming statics

// internal statics

// File-wide structure and manifest declarations.

// Procedures

let

ConvertAWidth(s,c,p) = valof [

// s -> SplineWidth structure of char to compute widths.
// c -> CharWidth structure to receive results.
// p -> Convert structure that governs how things are done.
// Returns true if spline will be needed.

	let spline=false
	let pw=lv s>>SplineWidth.WX
	test pw!0 eq 0 & pw!1 eq -1 then
	   [					//Non existent char
		c>>CharWidth.H=HNonExCode
	   ]
	or
	   [					//Transform widths
		SCVTransformF(lv s>>SplineWidth.WX,lv s>>SplineWidth.WY)
		FSTDP(8,lv c>>CharWidth.WX)	//and save for AC file
		FSTDP(9,lv c>>CharWidth.WY)	//as double-precision

//Calculate bounding box.

//Warning: the calculation for the bounding box is really not good enough.
// This is because the bounding box kept with the SD description is calculated
// a little differently than will be the endpoints of the splines when passed
// to SCV during conversion.  As a result, small round-off errors will occur
// (partly because SCVDrawCurve uses ->relative<- numbers!).  This can change
// 2E-9 into -2E-9, which will cause a different bounding box to be calculated.

	test p>>Convert.BBGood then
		[			//Transform bounding box
		SCVTransformF(lv s>>SplineWidth.XL,lv s>>SplineWidth.YB)
		FLD(1,8)			//Save left edge
		let yb=Floor(9)		//Y bottom

		SCVTransformF(lv s>>SplineWidth.XR,lv s>>SplineWidth.YT)
		let yh=Floor(9)

//Now swap ybottom and yheight if inverted (will happen if character is rotated).
		if yb gr yh then [ let t=yb; yb=yh; yh=t ]
		yh=yh-yb+(convertThicken? 1,0)

//And same for x, but (1) check for empty character, and (2)
// in scan direction, things are assymetric, because of the kind of
// scan conversion we are doing. 

		let xl,xw=nil,nil
		let sg=FCM(1,8)
		switchon sg into
		  [
		  case 0:	xl=0; xw=0; yb=0; yh=0	//Character is empty
		    endcase
		  case 1:	FLD(2,1);FLD(1,8);FLD(8,2) //Swap -- ac 1 < ac 8
		  case -1:	xl=Floor(1)+1		//Assymetry
		    xw=Floor(8)-xl+1
		    endcase
		  ]
		c>>CharWidth.XL=xl
		c>>CharWidth.YB=yb
		c>>CharWidth.H=yh
		c>>CharWidth.W=xw

		if p>>Convert.SplineOk & SplineNeeded(c,1) then
			[
			c>>CharWidth.H=HSplineCode
			spline=true
			]
		] //end of "test BBGood"
	    or //rotated, can't get bounding box, BUT: we can tell if it looks like
		//splines are needed (since DP WX,WY are computed already)
		[ if ((lv c>>CharWidth.WX)!0 gr 50)%
			((lv c>>CharWidth.WY)!0 gr 50) then spline=true
		]
	   ] //end of good character
	resultis spline
]

and

ConvertAChar(w,wo,pc,p,FSGet,FSPut) = valof [

//Assume w positioned at spline, wo positioned to receive it.
// pc -> CharWidth structure to receive results (bounding box only)
// p  -> Convert structure that governs how things are done.

	let originallen=32000		//Generous estimate!
	if p>>Convert.PressFontPart then originallen=p>>Convert.Len

	SCVInit(FSGet,FSPut)
	SCVBeginObject(not p>>Convert.Monotone,p>>Convert.Monotone)

	let opos=vec 3			//Remember old pos for spline
	WindowGetPosition(w,opos)
	WindowGetPosition(wo,opos+2)

	let stuff=vec 12
	let len=originallen

[	if len le 0 then break
	let op=WindowRead(w)
	switchon op into [

	case DSplineFontMoveTo:			//MoveTo
		WindowReadBlock(w,stuff,4)
		SCVMoveToF(stuff,stuff+2)
		len=len-5
		endcase;
	case DSplineFontDrawTo:			//DrawTo
		WindowReadBlock(w,stuff,4)
		SCVDrawToF(stuff,stuff+2)
		len=len-5
		endcase;
	case DSplineFontDrawCurve:			//DrawCurve
		WindowReadBlock(w,stuff,12)
		SCVDrawCurve(stuff,stuff+2,stuff+4,stuff+6,
				stuff+8,stuff+10)
		len=len-13
		endcase;
	case DSplineFontEndObjects: 		//End
		break
		endcase
	default:	resultis 10			//Illegal file format
	]
] repeat

	let v=vec (size SCV/16)
	SCVEndObject(v)				//Finish off
	if v>>SCV.Error then [		//Error -- return code
			SCVFlush(v)
			resultis v>>SCV.Error
			]

//Compute offsets (ox,oy), width (ns) and height (nb)
	let ns,nb=nil,nil
	let ox=v>>SCV.Smin
	test ox gr v>>SCV.Smax then [ ox=0;ns=0 ] or
		ns=v>>SCV.Smax-ox+1
	let oy=v>>SCV.Rmin
	test oy gr v>>SCV.Rmax then [ oy=0;nb=0 ] or
		nb=v>>SCV.Rmax-oy+(convertThicken? 1,0)

	if p>>Convert.PressFontPart then
		[
		if len ne 0 then
			[
			SCVFlush(v)
			resultis 11
			] 
		SCVTransformF(stuff,stuff+2)	//Use last MOVETO
		FSTDP(8,lv pc>>CharWidth.WX)	// to compute widths
		FSTDP(9,lv pc>>CharWidth.WY)
		]
//Salt away goodies in the structure
	pc>>CharWidth.XL=ox
	pc>>CharWidth.YB=oy
	pc>>CharWidth.W=ns
	pc>>CharWidth.H=nb

//figure out how much space needed: if not enough, use splines
   unless convertOrbitized do nb=(nb+15)&#177760
   let bitsNeeded=vec 1
	MulFull(ns,nb,bitsNeeded)
	DoubleAddV(bitsNeeded,15)	//take care of odd bit lengths
	let sizeNeeded=DivFull(bitsNeeded,16)+1	//add fudge for final tempPBits!1
	let pbits=FSGet(sizeNeeded)

//Now decide if splines needed.
	test p>>Convert.SplineOk&((pbits eq 0)%SplineNeeded(pc)) then
	[ unless pbits eq 0 do FSPut(pbits)
	  WindowSetPosition(w,opos)	//Restart
	  SCVFlush(v)		//Release any storage
	  resultis 4	//return error, so that caller will call ScaleAChar
	]
or
	[				//Can convert OK

//The character can be converted into a bit matrix, and stored in
// the character definition file as such.

	let sl=vec 2
   test convertOrbitized then
	 [	sl!0=-nb	//height
		sl!1=ns-1	//width
		WindowWriteBlock(wo,sl,2)
	 ]
	or
	 [ sl<<FHEAD.hw=nb rshift 4
		sl<<FHEAD.ns=ns			//Make up font header
		WindowWrite(wo,sl)
	 ]

	if sizeNeeded eq 1 then [ FSPut(pbits);resultis 0] //null character

	let Masks= table [
		#177777; #077777; #037777; #017777; #007777;
		#003777; #001777; #000777; #000377; #000177;
		#000077; #000037; #000017; #000007; #000003;
		#000001; #000000 ]

	let Cycle=table [ #60000;#1401]
	let CMasks=
	table [	#177777;#177776;#177774
		#177770;#177760;#177740
		#177700;#177600;#177400
		#177000;#176000;#174000
		#170000;#160000;#140000
		#100000;0
	     ]

	let slbuf=vec 100		//For making up scan lines
	Zero(slbuf,100)
	
//Prepare to call SCVReadRuns to obtain all runs until the character
// exhausts.

	let hw=(nb+15)/16
	let ob=0
	let tempPBits=pbits
	let buf=vec 1000
	let sl=v>>SCV.Smin
	v>>SCV.Send=sl-1
[	v>>SCV.Sbegin=v>>SCV.Send+1	//Move right
	v>>SCV.Send=v>>SCV.Smax		//Optimistic
	SCVReadRuns(v,buf,1000)
	let n=v>>SCV.IntCnt
	if n eq 0 then break		//No more intersections
	let p=v>>SCV.IntPtr

	for i=1 to n by 2 do
		[
		while sl ne p!0 do	//Going to new scan line.
			[
			let leftMask=CMasks!(16-ob)
			let rightMask=not leftMask
			for j=0 to hw-1 do
			 [ let w=Cycle(slbuf!j,16-ob)
			   tempPBits!0=(tempPBits!0&(not Masks!ob))+(w&rightMask)
			   tempPBits!1=(tempPBits!1&(Masks!ob))+(w&leftMask)
			   tempPBits=tempPBits+1
			 ] //end of "for j"
			ob=ob+nb
			tempPBits=tempPBits-hw+ob/16
			ob=ob&#17
			Zero(slbuf,hw)
			sl=sl+1
			] //end of "while sl"
		if p!2 ne sl then resultis 12
		let yb=p!1-oy			//Bottom y
		let yt=p!3-oy+(convertThicken? 1,0)		//Top y+1
		p=p+4			//Bump to next intersection

// Turn on bits from yb to yt-1 (inclusive)
		if yb ls 0 % yt gr nb then resultis 13
		if yt ge yb then		//Only show non-zero runs
		[
		let LeftMask=(Masks!(yb&#17))
		let RightMask= not (Masks!(yt&#17))
		yb=yb rshift 4
		let wc=(yt rshift 4)-yb	//Word count
		let w=slbuf+yb		//Word address
		let bw=(@w & (not LeftMask))%(-1 & LeftMask)
		for i=0 to wc-1 do
			[ w!i=bw; bw=-1 ]
		w!wc=(w!wc & (not RightMask))%(bw & RightMask)
		] //end of "if yt ge yb"
		] //end of "for i"

] repeat

	//and finish putting out the last line...
	let leftMask=CMasks!(16-ob)
	let rightMask=not leftMask
	for j=0 to hw-1 do
	 [ let w=Cycle(slbuf!j,16-ob)
	   tempPBits!0=(tempPBits!0&(not Masks!ob))+(w&rightMask)
	   tempPBits!1=(tempPBits!1&(Masks!ob))+(w&leftMask)
	   tempPBits=tempPBits+1
	 ]
	WindowWriteBlock(wo,pbits,sizeNeeded)	//Last line....
	FSPut(pbits)
	if sl ne v>>SCV.Smax then resultis 14
	] //end of doing scan conversion

	resultis 0
]

//The character will occupy too much space if scan-converted and
// saved in a character file as bits.  We return an error code, and
// the calling program will have the smarts to call ScaleAChar,
// which will simply scale the spline definition,
// and place it in the character file.  In addition,
// the CharWidth.H entry is changed to have a special code that
// indicates this character is described by splines, and XL and YB
// have in them the file position of the spline encoding.
and ScaleAChar(w,wo,pc) = valof 
 [ WindowGetPosition(wo,lv pc>>CharWidth.XL)	//Save pos in XL,YB
   pc>>CharWidth.H=HSplineCode
   let outLen=0
   let stuff=vec 13

	[	let op=WindowRead(w)
		stuff!0=op-1	//GAAAAAAA!!! PreObjects uses different codes!!!!
		switchon op into
		[
		case DSplineFontMoveTo:
		case DSplineFontDrawTo:
			WindowReadBlock(w,stuff+1,4)
			SCVTransformF(stuff+1,stuff+3)
			stuff!1=FTR(8);stuff!2=FTR(9)
			WindowWriteBlock(wo,stuff,3)
			outLen=outLen+3
			endcase
		case DSplineFontDrawCurve: 
			WindowReadBlock(w,stuff+1,12)
			for p=1 to 12 by 4 do 
			[
			SCVTransformF(stuff+p,stuff+p+2)
			FST(8,stuff+p)
			FST(9,stuff+p+2)
			]
			WindowWriteBlock(wo,stuff,13)
			outLen=outLen+13
			endcase
		case DSplineFontNewObject:
		case DSplineFontEndObjects: 
			break//done
		] //end of "switchon op"
	] repeat
		pc>>CharWidth.W=outLen
   resultis 0 //no errors
 ]

and

//Set up the SCV transformation matrix from parameters that are
// supplied for converting a font:
// siz		Size of the font in micas
// rotation	Rotation of the font in minutes
// incline	Incline of the font in percent
// resx,resy	Resolution of the output device
//		(if not supplied, FPAC's 3&4 assumed set up)

SetSCVTransform(siz,rotation,incline,resx,resy; numargs n) be
 [ 	FLDI(5,25400)
	FLDI(1,siz); FLDI(2,siz)
	if n gr 3 then [ FLDI(3,resx); FLDI(4,resy) ]
	FML(1,3); FML(2,4)
	FDV(1,5); FDV(2,5)		//x and y scales.
	test rotation ne 0 then
		[			//Get sine and cosine
		GetFloatingCos(rotation,3)
		GetFloatingCos(rotation-90*60,4)
		]
	or
		[ 
		FLDI(3,1); FLDI(4,0)
		]
	FLD(5,3); FML(5,1)		//m[1,1]
	FLD(6,4); FML(6,1)		//m[1,2]
	FNEG(4)
	FLDI(7,incline);FLDI(0,100);FDV(7,0)
	FML(7,3);FAD(4,7);FML(4,2)	//m[2,1]
	FML(3,2)			//m[2,2]
	SCVMatrix(5,6,4,3)
]

and

//Return floating cosine in ac. Argument is minutes of arc.

GetFloatingCos(min,ac) be [
	let v=vec 4
	v!1=0			//Exponent
	v!3=0			//Low mantissa
	let one=table [ 0;0;#177777;0 ] //Normalization constant

	Cos(min,v,v+2)
// Now normalize the number
	if v!2 then 
	   while ((v!2)&#100000) eq 0 do [ v!2=v!2 lshift 1; v!1=v!1-1 ]
	FLDV(ac,v)
	FLDV(0,one)
	FDV(ac,0)
]

and

Cos(theta,lvsign,lvmag) be [
//Calculate the cosine of the given angle, and return the
// magnitude as a fraction of #177777 (largest number)
// Also return sign (0 if positive, -1 if negative)

	if theta ls 0 then theta=-theta
	@lvsign=-(((theta+90*60)/(180*60))&1)
	let d=theta rem 90*60
	if ((theta/(90*60))&1) ne 0 then d=90*60-d
	let min=d rem 60			//Minutes part
	d=d/60				//Degrees part
//Now d in range 0-90 degrees

	let retrievecos(d,min) =valof [	//0 le d le 45
		let cosar=table [
		#177777;
		#177765; #177727; #177645; #177537; #177405; 
		#177227; #177026; #176601; #176330; #176033; 
		#175512; #175146; #174557; #174144; #173505; 
		#173024; #172317; #171567; #171014; #170216; 
		#167376; #166532; #165645; #164735; #164002; 
		#163026; #162030; #161007; #157746; #156662; 
		#155556; #154430; #153262; #152072; #150663; 
		#147432; #146162; #144672; #143362; #142032; 
		#140463; #137075; #135471; #134045; #132405; 
		#130743;	//46 degrees because of interpolation
		]

		let a=cosar!d		//First answer
		if min ne 0 then	//Must interpolate
		  [
		  let b=cosar!(d+1)
		  a=a-MulDiv(a-b,min,60)	//Careful about signs
		  ]
		resultis a
	]

	test d gr 45 then
		[			//Use half-angle formulae
		if (d&1) ne 0 then min=min+60 //Divide angle by 2
		let a=retrievecos(d rshift 1,min rshift 1)
		a=MulDiv(a,a,#177777)	// cos↑2(theta/2)
		a=a-#100000		// cos↑2 -1/2
		@lvmag=a lshift 1	//2 cos↑2 -1
		]
	or	@lvmag=retrievecos(d,min)
	
]


and

//Return true if character described by pc>>CharWidth...
//  will be too big for a scan-converted form.

SplineNeeded(pc,boundFactor;numargs na) = valof
[	if na eq 1 then boundFactor=10
	let nbw=(pc>>CharWidth.H+15)/16
	resultis (nbw gr 10*boundFactor) %
			   (nbw*pc>>CharWidth.W gr 200*boundFactor)
]