// February 21, 1978  11:22 AM				*** overlay B ***
//Edited by Lyle Ramshaw September 8, 1980  9:01 PM:
// On to version 5.0, with a new file format to get text positioning
// done reasonably (and consistent with ReDRaw).

 
// Compile with STATS/M to get STATISTICS code  [command <ctrl>Y]
// Compile with BITMAP/M to get BITMAP code  [command <ctrl>B]


get "zpDefs.bcpl"


// outgoing procedures:

external [
	readPicture
	writePicture
	writeStatistics
	writeBitmap
	changeTextMode
	readHelp
	readFont
	]

// outgoing static:

external [
	@help
	]

static [
	@help=0
	]


// incoming procedures:

external [
	Gets			// SYSTEM
	Puts
	Endofs
	Resets
	Closes
	OpenFile
	OpenFileFromFp
	FindFdEntry
	FileLength
	ReadBlock
	WriteBlock
	Zero
	MoveBlock

	giveUp			// ZPUTIL
	confirm
	sTypeForm
	typeForm
	getLine
	openRead
	openWrite
	abortMessage
	capitalize
	equal

	makeSpline		// ZPMAKE

	makeText		// ZPTEXT
	writeText
	eraseText
	showText

	obtainBlock		// ZPBLOCK
	putBlock
	flushDTTstack

	MakeFontEntry		// ZPFONTIO

	adjustText		// ZPADJUST
	]


// incoming statics:

external [
	fpSysDir		// SYSTEM
	keys

	@splineTable		// ZPINIT
	@textTable
	@maxSplineID
	@maxTextID
	@fontDefTable
	@fontFile
	@font
	@dspFont
	@bitmap
	@height
	@width
	@scanlineWidth
	@bitmapSize

	FLDI; FST		// FLOAT

	@posTextMode		// ZPEDIT
	@colorOn
	]


// local definitions:
  
manifest [
	getStatistics= not newname STATS
	getBitmap= not newname BITMAP
	]


// local statics:

static [
	@BMheight=0
	@BMwordWidth=0
	]


// local definitions

structure CHAIN [
	run↑1, 1000 byte
	]

structure RUN [
	blank byte
	octant bit 3
	count bit 5
	]

// old file format

structure OFfirstWord [
	fp bit
	M bit 15
	]

structure OFheader [
	dashed bit
	cyclic bit
	blank bit 2
	shape bit 2
	thickness bit 2
	nKnots byte
	]

// new file formats (after version 3.0, first two words are 0;
//   after version 5.0, first two words are -1)

structure NFheader1 [
	blank bit 3
	[ dashed bit
	  shape bit 2
	  thickness bit 2 ] = [ brush bit 5 ]
	blank bit 5
	color bit 3
	]

structure NFheader2 [
	cyclic bit
	nKnots bit 15
	]
	

//****************************************************************
// Special commands: statistics & bitmap output
//****************************************************************


let writeStatistics() be [writeStatistics
compileif getStatistics then [
	let statFile=openWrite("*NWrite statistics on text file: ", charItem)
	unless statFile return
	let histVec= vec 256
	Zero(histVec, 256)
	typeForm(0, "Type comments terminated with 2 <return>s:*N")
	[ let c=getLine()
	  unless c break
	  sTypeForm(statFile, 0, c, 1, $*N)
	  putBlock(c)
	  ] repeat
	let k, c=0, 0
	for id=1 to maxSplineID do [
		let splinePointer=splineTable!id
		unless splinePointer loop
		let nKnots=splinePointer>>SPLINE.nKnots
		k=k+SPLINEknotBase+4*nKnots
		let nBeads=splinePointer>>SPLINE.nBeads
		unless nBeads loop
		sTypeForm(statFile, 0, "*NSPLINE ", 10, id, 1, $*N, 
			10, nKnots, 0, " knots*N", 10, nBeads, 0, " beads*N")
		let chainPointer=splinePointer>>SPLINE.chain
		let chainCountPointer=chainPointer+nBeads*(BEADsize+2)
		let runCount=@(chainCountPointer-1)
		let countBlockSize=(runCount+1)/2
		sTypeForm(statFile, 0, "chain storage: ", 10, BEADsize*nBeads, 1, $+, 
			10, 2*nBeads, 1, $+, 10, countBlockSize)
		let s=nBeads*(BEADsize+2)+countBlockSize
		c=c+s
		sTypeForm(statFile, 1, $=, 10, s, 1, $*N)
		let r=(chainCountPointer>>CHAIN.run↑1)<<RUN.count
		let q=(chainCountPointer>>CHAIN.run↑1)<<RUN.octant
		for k=2 to runCount do [
			let r1=(chainCountPointer>>CHAIN.run↑k)<<RUN.count
			let q1=(chainCountPointer>>CHAIN.run↑k)<<RUN.octant
			test q1 eq q
			ifso r=r+r1
			ifnot [
				if r gr 255 then r=255
				histVec!r=histVec!r+1
				r=r1
				q=q1
				]
			]
		if r gr 255 then r=255
		histVec!r=histVec!r+1
		]
	let t=0
	for id=1 to maxTextID do [
		let textPointer=textTable!id
		unless textPointer loop
		t=TEXTblockSize+(textPointer+TEXTblockSize)>>STRING.length/2+1
		]
	sTypeForm(statFile, 0, "*N*NTotal storage:*NKnots: ", 10, k, 
		0, "*NChain: ", 10, c, 0, "*NText: ", 10, t)
	sTypeForm(statFile, 0, "*NTotal: ", 10, k+c+t, 1, $*N)
	Closes(statFile)

	let histFile=openWrite("*NWrite histogram on binary file: ", wordItem)
	unless histFile return
	WriteBlock(histFile, histVec, 256)
	Closes(histFile)
	typeForm(0, "Done*N")
	]
	]writeStatistics



and writeBitmap() be [writeBitmap
compileif getBitmap then [
	typeForm(0, "[Set statics BMheight & BMwordWidth]")
	let file=openWrite("*NWrite BITMAP on file: ", wordItem)
	unless file return
	let bm=bitmap+margin+(height-BMheight)*scanlineWidth
	Puts(file, BMheight)
	Puts(file, BMwordWidth)
	for s=1 to BMheight do [
		WriteBlock(file, bm, BMwordWidth)
		bm=bm+scanlineWidth
		]
	Closes(file)
	typeForm(0, "Done*N")
	]
	]writeBitmap



//****************************************************************
// Standard spline input/output
//****************************************************************


and readPicture(file; numargs n) be [readPicture
	let M,T=nil,nil
	let newIdTable=0
	flushDTTstack()
	unless n then
		file=openRead("*NRead picture from file: ", wordItem)
	unless file return
	let word1=Gets(file)
	let word2=Gets(file)
	let convertText=true	//iff text must be converted to >=5.0 format
	if (word1 eq -1) & (word2 eq -1) then convertText=false
	test (word1 eq word2) & ((word2 eq 0)%(word2 eq -1))
	ifso [
		// new file format
		M=Gets(file)
		for m=1 to M do [
			let header1=Gets(file)
			let header2=Gets(file)
			let n=header2<<NFheader2.nKnots
			unless n gr 0 loop
			let xTable=obtainBlock(4*n)
			test xTable ne 0
			ifso [
				ReadBlock(file, xTable, 4*n)
				makeSpline(n, xTable, xTable+2*n,
						header1<<NFheader1.brush,
						header1<<NFheader1.color,
						header2<<NFheader2.cyclic)
				putBlock(xTable)
				]
			ifnot [
				giveUp("[readPicture.1]")
				for i=1 to 4*n do Gets(file)
				]
			]
		T= Endofs(file) ? 0, Gets(file)
		if T ne 0 then [
			newIdTable=obtainBlock(T)
			if newIdTable eq 0 then [
				giveUp("[readPicture.2]")
				Closes(file)
				return
				]
			]
		for t=0 to T-1 do [
			let textString= vec maxChar
			let left=Gets(file)
			let top=Gets(file)
			let font=Gets(file)
			let color=Gets(file)
			let s=Gets(file)
			ReadBlock(file, textString, s)
			newIdTable!t=
				makeText(textString, left, top, font, color, false)
			]
		]
	ifnot [
		// old file format
		Resets(file)
		word1=Gets(file)
		let fp=word1<<OFfirstWord.fp
		M=word1<<OFfirstWord.M
		if M then [
			let hTable=obtainBlock(M)
			unless hTable then [
				giveUp("[readPicture.3]")
				Closes(file)
				return
				]
			ReadBlock(file, hTable, M)
			for i=0 to M-1 do [
				let header=hTable!i
				let n=header<<OFheader.nKnots
				unless n gr 0 loop
				let xTable=obtainBlock(4*n)
				test xTable ne 0
				ifso [
					test fp
					ifso ReadBlock(file, xTable, 4*n)
						//for old integer format files!
					ifnot for k=0 to 2*n-1 do
						FST(FLDI(0, Gets(file)), xTable+2*k)
					let brush=0
					brush<<BRUSH.dashed=header<<OFheader.dashed
					brush<<BRUSH.shape=header<<OFheader.shape
					brush<<BRUSH.thickness=header<<OFheader.thickness
					makeSpline(n, xTable, xTable+2*n, brush, black,
							header<<OFheader.cyclic)
					putBlock(xTable)
					]
				ifnot [
					giveUp("[readSpline.4]")
					for i=1 to (fp ? 4*n, 2*n) do Gets(file)
					]
				]
			putBlock(hTable)
			]
		// then read text
		T= Endofs(file) ? 0, Gets(file)
		if T then [
			newIdTable=obtainBlock(T)
			unless newIdTable then [
				giveUp("[readPicture.5]")
				Closes(file)
				return
				]
			ReadBlock(file, newIdTable, T)
			for t=0 to T-1 do [
				let tTable=vec (maxChar+4)
				ReadBlock(file, tTable, newIdTable!t+4)
				newIdTable!t= makeText(tTable+4, tTable!0,
						tTable!1, tTable!2, black, false)
				]
			]
		]
	Closes(file)
	// now, display text!
	if T ne 0 then for f=0 to maxFont-1 do [
		for t=0 to T-1 do [
			let textPointer=textTable!(newIdTable!t)
			unless textPointer loop
			unless textPointer>>TEXT.font eq f loop
			if convertText then adjustText(textPointer)
			writeText(textPointer)
			]
		]
	putBlock(newIdTable)
	typeForm(0, "Done!*N")
	]readPicture



and writePicture(file; numargs n) be [writePicture
	let M=splineTable!0
	let T=textTable!0
	unless M % T return
	unless n then
		file=openWrite("*NWrite picture on file: ", wordItem)
	unless file return
	Puts(file, -1)
	Puts(file, -1)
	// splines
	Puts(file, M)
	if M then for id=1 to maxSplineID do [
		let splinePointer=splineTable!id
		unless splinePointer loop
		let header1=0
		header1<<NFheader1.brush=splinePointer>>SPLINE.brush
		header1<<NFheader1.color=splinePointer>>SPLINE.color
		Puts(file, header1)
		let header2=splinePointer>>SPLINE.nKnots
		header2<<NFheader2.cyclic=splinePointer>>SPLINE.cyclic
		Puts(file, header2)
		WriteBlock(file, splinePointer+SPLINEknotBase, 
				4*splinePointer>>SPLINE.nKnots)
		]
	// text
	Puts(file, T)
	if T then for f=0 to maxFont-1 do [
		for t=1 to maxTextID do [
			let textPointer=textTable!t
			unless textPointer loop
			if textPointer>>TEXT.font ne f loop
			Puts(file, textPointer>>TEXT.left)
			Puts(file, textPointer>>TEXT.top)
			Puts(file, textPointer>>TEXT.font)
			Puts(file, textPointer>>TEXT.color)
			let s=((textPointer+TEXTblockSize)>>STRING.length)/2 + 1
			Puts(file, s)
			WriteBlock(file, textPointer+TEXTblockSize, s)
			]
		]

	Closes(file)
	typeForm(0, "Done!*N")
	]writePicture



//****************************************************************
// Text centering mode
//****************************************************************


and changeTextMode() be [changeTextMode
	typeForm(0, "Text positioning mode [Center, Top, Left, Bottom, Right]: ")
	posTextMode=0
	until posTextMode do posTextMode=selecton capitalize(Gets(keys)) into [
		case $B: posTextBottom;
		case $C: posTextCenter;
		case $L: posTextLeft;
		case $R: posTextRight;
		case $T: posTextTop;
		default:  0
		]
	typeForm(0, selecton posTextMode into [
		case posTextCenter: "Center*N";
		case posTextTop: "Top*N";
		case posTextBottom: "Bottom*N";
		case posTextLeft: "Left*N";
		case posTextRight: "Right*N"
		])
	]changeTextMode


//****************************************************************
// HELP!
//****************************************************************


and readHelp() be [readHelp
	unless help return
	let fileName=vec 8
	let nextFileName=vec 8
	manualPage(help, fileName)
	help=help+1
	manualPage(help, nextFileName)
	let systemDir=OpenFileFromFp(fpSysDir, ksTypeReadOnly)
	if FindFdEntry(systemDir, nextFileName) eq -1 then help=1
	Closes(systemDir)
	let file=OpenFile(fileName, ksTypeReadOnly)
	test file
	ifso [
		readPicture(file)
		typeForm(0, "*N*N*NTo obtain next manual page (", 
			10, help, 0, ") type line-feed (<LF>)*N", 
			0, "To disable on-line manual, type <ctrl>? again.*N")
		]
	ifnot typeForm(0, "On-line manual is not there! Disable help mode with <ctrl>?*N")
	]readHelp


and manualPage(n, fileName) be [manualPage
	let moreThan10= n ge 10
	MoveBlock(fileName, (moreThan10 ? "MANUAL10.DRAW", "MANUAL0.DRAW"), 8)
	fileName>>STRING.char↑(moreThan10 ? 8, 7)=$0+(moreThan10 ? (n-10), n)
	]manualPage



//****************************************************************
// Font
//****************************************************************


and readFont() = valof [readFont
	typeForm(0, "Load font ")
	for f=0 to maxFont-1 do if f ne dspFont then
		typeForm(8, f, 0, ((f eq maxFont-1) ? " ? ", ", "))
	let f=nil
	[ f=Gets(keys)-$0
	  if (f ne dspFont) & (f ge 0) & (f le 3) break
	  if (f ls 0) % (f gr 9) resultis abortMessage()
	  ] repeat
	typeForm(10, f)
	if not readFontFile(f) & (f eq font) then font=dspFont
	for id=1 to maxTextID do [
		let textPointer=textTable!id
		unless textPointer loop
		if textPointer>>TEXT.font eq f then [
			eraseText(id)
			showText(id)
			]
		]
	typeForm(0, "Done!*N")
	]readFont



and readFontFile(f) = valof [readFontFile
	let numberCodeTable= table [
		#400+$0; #400+$1; #400+$2; #400+$3 ]
	fontFile>>FONTFILE.current=-1
	fontFile>>FONTFILE.length↑f=0
	let fontName=0
	let fontFileFp=vec lFP
	let file=openRead("*NRead font file: ", wordItem, lv fontName, fontFileFp)
	if file ne 0 then [
		let fontLength=FileLength(file)/2+1
		Resets(file)
		let ALheader=vec 2
		ReadBlock(file, ALheader, 2)
		Closes(file)
		fontFile>>FONTFILE.height↑f=ALheader>>AL.height
		fontFile>>FONTFILE.baseline↑f=ALheader>>AL.baseline
		fontFile>>FONTFILE.length↑f=fontLength
		MoveBlock(lv(fontFile>>FONTFILE.fp↑f), fontFileFp, lFP)
		]

	// get new buffer, as appropriate
	let maxLength=0
	let oldLength=fontFile>>FONTFILE.bufferLength
	for i=0 to maxFont-1 do
		if maxLength ls fontFile>>FONTFILE.length↑i then
			maxLength=fontFile>>FONTFILE.length↑i
	if maxLength ne oldLength then [
		putBlock(fontFile>>FONTFILE.buffer)
		let newBuffer=0
		if maxLength ne 0 then [
			newBuffer=obtainBlock(maxLength)
			if newBuffer eq 0 then [
				// forget it, keep old buffer
				giveUp("[readFont]")
				fontFile>>FONTFILE.length↑f=0
				newBuffer=obtainBlock(oldLength)
				maxLength=oldLength
				]
			]
		fontFile>>FONTFILE.buffer=newBuffer
		fontFile>>FONTFILE.bufferLength=maxLength
		]

	let r= file eq 0 ? 0, MakeFontEntry(fontName, fontDefTable!f, f)
	putBlock(fontName)
	resultis r
	]readFontFile