-- PressOut.mesa
-- Written by Joe Maleson
-- Last changed by Doug Wyatt, November 6, 1980  9:33 AM
-- Last changed by Schmidt (Pilot Conversion),  1-Jul-81 18:40:17

	DIRECTORY
	DCSFileTypes USING [tLeaderPage],
	Directory USING[CreateFile, Error, ignore, Lookup, UpdateDates],
	Environment USING [bytesPerPage, wordsPerPage],
	File USING [Capability, delete, grow, Permissions, read, shrink, write],
	FileStream USING [Create, GetIndex, SetIndex],
	Heap USING [systemZone],
	Inline USING [BITAND, BITXOR, COPY, HighHalf, LDIVMOD, LongDiv, 
		LongMult, LowByte, LowHalf],
	LongString USING[AppendString, EqualString, EquivalentStrings],
	PressDefs USING [DLDotsFollow, DLDrawCurve, DLDrawTo, 
		DLGetDotsFromFile, DLMoveTo, DLSetCoding, DLSetMode, 
		DLSetSamplingProperties, DLSetSize, DLSetWindow, 
		DLSSPInputIntensity, DLSSPScreen, DocDir, ELAlternative, 
		ELCommand, ELFont, ELNop, ELOnlyOnCopy, ELResetSpace, 
		ELSetBrightness, ELSetHue, ELSetSaturation, ELSetSpaceX, 
		ELSetSpaceXShort, ELSetSpaceY, ELSetSpaceYShort, ELSetX, 
		ELSetY, ELShowCharacterImmediate, ELShowCharacters, 
		ELShowCharactersAndSkip, ELShowCharactersShort, ELShowDots, 
		ELShowDotsOpaque, ELShowObject, ELShowRectangle, 
		ELSkipCharacters, ELSkipCharactersShort, ELSkipControlBytes, 
		ELSkipControlBytesImmediate, ELSpace, EntityCommandTrailer, 
		ExternalFileDirectory, Font, FontDir, FontEntry, MicaHeight, 
		MicaWidth, Page, PartDir, PressDotsData, 
		PressFileDescriptor, PressPassword],
	Profile USING [userName],
	Stream USING [Delete, GetBlock, Handle, PutBlock, PutByte, 
		PutChar, PutWord, SendNow],
	Time USING [Append, Current, Unpack];

PressOut: PROGRAM 
IMPORTS  Directory, FileStream, Heap, Inline, LongString, Profile, Stream, Time 
EXPORTS PressDefs = {

OPEN PressDefs;

--these guys should be per PressFileDescriptor
entitySegment: TYPE = RECORD[
	link: LONG POINTER TO entitySegment,
	length: CARDINAL,
	beginByte,endByte: LONG CARDINAL
 	];
maxEntitySegment: CARDINAL = 16000;

-- MDS Usage
entitySegmentHead: LONG POINTER TO entitySegment;
lastSetX, lastSetY, lastFont, lastSpaceX,lastSpaceY: CARDINAL ← 0;
externalFileStrings: ARRAY[0..100) OF LONG STRING;
nExternalFileStrings: CARDINAL ← 0;
nw: CARDINAL;	-- used by ScanInitMem
sl: LONG POINTER;-- used by ScanInitMem
-- end of MDS Usage



CopyBCPLString: PUBLIC PROC  [
	BCPLString: POINTER TO PACKED ARRAY [0..100) OF CHARACTER, 
	mesaString: LONG STRING] =
	{
	i: CARDINAL;
	BCPLString[0]←LOOPHOLE[mesaString.length];
	FOR i IN [0..mesaString.length)
		DO
		BCPLString[i+1]←mesaString[i];
		ENDLOOP;
	};

MulDiv: PUBLIC PROC [a,b,c:CARDINAL] RETURNS [CARDINAL] =
{
	al: LONG CARDINAL ← Inline.LongMult[a,b];
	RETURN[Inline.LongDiv[al+c/2,c]];
};

SignedMulDiv: PUBLIC PROC [a,b,c:INTEGER] RETURNS [INTEGER] =
{ 
	rslt: INTEGER;
	sgn: INTEGER ← Inline.BITXOR[Inline.BITXOR[a,b],c];	--Sign bit;
	rslt ← MulDiv[ABS[a],ABS[b],ABS[c]];
	IF sgn < 0 THEN RETURN[-rslt];
	RETURN[rslt];
};

SetBlock: PUBLIC PROC [buf: LONG POINTER TO ARRAY [0..0) OF INTEGER,
	val,len: INTEGER] = {
	buf[0]←val;
	Inline.COPY[to: @buf+1,from: @buf,nwords: len-1];
};

WriteCommand: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	Command: CARDINAL,Param: POINTER TO ARRAY [0..6) OF CARDINAL] =
{
es: Stream.Handle ← LOOPHOLE[p.EntityCommands];
i: CARDINAL;

	Stuff1: PROC [Argument: CARDINAL] =
	{
		p.EntityCommandLen ← p.EntityCommandLen+1;
		PutLowerByte[es,Argument];
		--p.EntityCommands[p.EntityCommandLen]←Argument;
	};

	Stuff2: PROC [Argument: CARDINAL] =
	{
		PutLowerByte[es,Argument/256];
			--p.EntityCommands[p.EntityCommandLen+1]←Argument/256;
		p.EntityCommandLen ← p.EntityCommandLen + 2;
		PutLowerByte[es,Argument]; --p.EntityCommands[p.EntityCommandLen]←Argument;
	};

	SELECT Command FROM
		--standard "short" command
		ELFont	=>	
			{ lastFont ← Param[0];Stuff1[Command+Param[0]];};

		--standard "short-1" commands
		ELShowCharactersShort,
			ELSkipCharactersShort,
			ELShowCharactersAndSkip=>
			Stuff1[Command+Param[0]-1];

		--strange and puzzling set space short stuff
		ELSetSpaceXShort =>
			{ lastSpaceX ← Param[0];Stuff2[Command*256+Param[0]];};
		ELSetSpaceYShort =>
			{ lastSpaceY ← Param[0];Stuff2[Command*256+Param[0]];};

		--commands with no parameters
		ELResetSpace =>
			{ lastSpaceX ← lastSpaceY ← 0;Stuff1[Command];};
		ELSpace,ELNop	=>
			Stuff1[Command];

		--commands with a single byte parameter
		ELShowCharacters,
			ELShowCharacterImmediate,
			ELSkipCharacters,
			ELSkipControlBytesImmediate,
			ELSetBrightness,
			ELSetHue,ELSetSaturation,
			ELOnlyOnCopy	=>	
			{
			Stuff1[Command];
			Stuff1[Param[0]];
			};

		--commands with two bytes of parameters
		ELSetSpaceX =>
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			lastSpaceX ← Param[0];
			};
		ELSetSpaceY =>
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			lastSpaceY ← Param[0];
			};
		ELSetX =>
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			lastSetX ← Param[0];
			};
		ELSetY =>
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			lastSetY ← Param[0];
			};
		ELShowObject =>
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			};

		--commands with [2 bytes] [1 byte]
		ELSkipControlBytes =>
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			Stuff1[Param[1]];
			};

		--commands with 4 bytes
		ELShowRectangle,ELShowDots,
			ELShowDotsOpaque =>	
			{
			Stuff1[Command];
			Stuff2[Param[0]];
			Stuff2[Param[1]];
			};

		--and the real long stuff
		ELAlternative	=>	
			{
			Stuff1[Command];
			FOR i IN [0..4] DO Stuff2[Param[i]]; ENDLOOP;
			};
		ENDCASE;

IF p.EntityCommandLen > maxEntitySegment THEN {
	h: CARDINAL ← p.CurHue;
	s: CARDINAL ← p.CurSat;
	b: CARDINAL ← p.CurBright;
	NewEntitySegment[p];
	WriteCommand[p,ELSetX,LOOPHOLE[@lastSetX]];
	WriteCommand[p,ELSetY,LOOPHOLE[@lastSetY]];
	WriteCommand[p,ELSetHue,LOOPHOLE[@h]];
	WriteCommand[p,ELSetSaturation,LOOPHOLE[@s]];
	WriteCommand[p,ELSetBrightness,LOOPHOLE[@b]];
	WriteCommand[p,ELFont,LOOPHOLE[@lastFont]];
	IF lastSpaceX#0 THEN WriteCommand[p,ELSetSpaceX,LOOPHOLE[@lastSpaceX]];
	IF lastSpaceY#0 THEN WriteCommand[p,ELSetSpaceY,LOOPHOLE[@lastSpaceY]];
	};

};


WriteDirectory: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor] = {
	d: DocDir;
	dayTime: LONG CARDINAL ← Time.Current[];
	Zero[@d,SIZE[DocDir]];
	d.Password←PressPassword;
	d.nRecs←p.RecordStart+p.fontLen+p.partLen+1;
	d.nParts←p.numParts;
	d.partDirStart←p.RecordStart+p.fontLen;
	d.partLength←p.partLen;
	d.date ← Inline.LowHalf[dayTime]*200000B + Inline.HighHalf[dayTime];
	d.firstCopy←p.FirstCopy;
	d.lastCopy←p.LastCopy;
	d.firstPage←-1;
	d.lastPage←-1;
	d.solidCode←p.solidCode;
	CopyBCPLString[LOOPHOLE[@d.fileName],p.PressFileName];
	CopyBCPLString[LOOPHOLE[@d.userName],p.UserName];
	CopyBCPLString[LOOPHOLE[@d.dateString],p.DateString];
	[]←WriteBlock[p.outStream,@d,SIZE[DocDir]];
};

SetSpaceX: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, micaX: CARDINAL] =
{
param: ARRAY [0..6) OF CARDINAL;
IF p.CurSpaceX = micaX THEN RETURN;
param[0] ← micaX;
WriteCommand[p,ELSetSpaceX,@param];
p.CurSpaceX ← micaX;
};

SetSpaceY: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, micaY: CARDINAL] =
{
param: ARRAY [0..6) OF CARDINAL;
IF p.CurSpaceY = micaY THEN RETURN;
param[0] ← micaY;
WriteCommand[p,ELSetSpaceY,@param];
p.CurSpaceY ← micaY;
};

ResetSpace: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor] =
{
IF p.CurSpaceX = 177777B AND p.CurSpaceY = 177777B THEN RETURN;
WriteCommand[p,ELResetSpace,NIL];
p.CurSpaceX ← p.CurSpaceY ← 177777B;
};

PutSpace: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor] =
{
WriteCommand[p,ELSpace,NIL];
};

SetColor: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, 
	hue,sat,bright: [0..256)] = {
param: ARRAY [0..6) OF CARDINAL;

IF hue#p.CurHue THEN
	{
	param[0]←hue;
	p.CurHue←hue;
	WriteCommand[p,ELSetHue,@param];
	};
IF sat#p.CurSat THEN
	{
	param[0]←sat;
	WriteCommand[p,ELSetSaturation,@param];
	p.CurSat←sat;
	};
IF bright#p.CurBright THEN
	{
	param[0]←bright;
	WriteCommand[p,ELSetBrightness,@param];
	p.CurBright←bright;
	};
};

SetHue: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, hue: [0..256)] =
{
param: ARRAY [0..6) OF CARDINAL;

IF hue#p.CurHue THEN
	{
	param[0]←hue;
	p.CurHue←hue;
	WriteCommand[p,ELSetHue,@param];
	};
};

SetSaturation: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, 
	sat: [0..256)] = {
param: ARRAY [0..6) OF CARDINAL;

IF sat#p.CurSat THEN
	{
	param[0]←sat;
	WriteCommand[p,ELSetSaturation,@param];
	p.CurSat←sat;
	};
};

SetBrightness: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, 
	bright: [0..256)] = {
param: ARRAY [0..6) OF CARDINAL;

IF bright#p.CurBright THEN
	{
	param[0]←bright;
	WriteCommand[p,ELSetBrightness,@param];
	p.CurBright←bright;
	};
};

SetFont: PUBLIC PROC [p: LONG POINTER TO PressDefs.PressFileDescriptor,
	Name: LONG STRING,PointSize: CARDINAL,Face: CARDINAL ← 0,
	Rotation: CARDINAL ← 0] =
{
UC: ARRAY CHARACTER ['a..'z] OF CHARACTER =
	['A,'B,'C,'D,'E,'F,'G,'H,'I,'J,'K,'L,'M,'N,'O,'P,'Q,'R,'S,'T,'U,'V,'W,'X,'Y,'Z];
FBuf: LONG POINTER TO PressDefs.FontEntry;
s: LONG STRING;
param: ARRAY [0..6) OF CARDINAL;

FOR i: CARDINAL IN [0..Name.length) 
	DO IF Name[i] IN ['a..'z] THEN Name[i] ← UC[Name[i]]; ENDLOOP;
FOR i: CARDINAL IN [0..p.numFonts) DO
	FBuf ← p.fontDir[i];
	IF LongString.EqualString[Name,FBuf.Family] AND
		Face = FBuf.Face AND
		PointSize = FBuf.Size AND
		Rotation = FBuf.Rotation THEN 
			{
			param[0] ← i;
			WriteCommand[p,ELFont,@param];
			RETURN;
			};
	ENDLOOP;
IF p.numFonts = 16 THEN ERROR; --whoops, no space

--grab free storage, and go
FBuf ← AllocateSegment[SIZE[PressDefs.FontEntry]];
s ← AllocateString[20];
FBuf.EntryLength←16;
FBuf.FontSet←0;
FBuf.Font←p.numFonts;
FBuf.M←0;FBuf.N←127;
LongString.AppendString[s,Name];
FBuf.Family←s;
FBuf.Face←Face;
FBuf.Source←0;
FBuf.Size←PointSize;
FBuf.Rotation←Rotation;
p.fontDir[p.numFonts]←FBuf;
p.numFonts←p.numFonts+1;

param[0] ← p.numFonts-1;
WriteCommand[p,ELFont,@param];
};

PutText: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor,
	str: LONG STRING,xleft,ybase: CARDINAL] =
{
s: Stream.Handle ← p.outStream;
param: ARRAY [0..6) OF CARDINAL;

param[0]←xleft;WriteCommand[p,ELSetX,@param];
param[0]←ybase;WriteCommand[p,ELSetY,@param];
IF p.numFonts = 0 THEN SetFont[p,"Gacha"L,8];
FOR i: CARDINAL IN [0..str.length) DO 
	Stream.PutChar[s,str[i]]; 
	ENDLOOP;
param[0] ← str.length;
WriteCommand[p,ELShowCharacters,@param];
};

PutRectangle: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, 
	xstart,ystart,xlen,ylen: CARDINAL] =
{
param: ARRAY [0..6) OF CARDINAL;

param[0]←xstart;WriteCommand[p,ELSetX,@param];
param[0]←ystart;WriteCommand[p,ELSetY,@param];
param[0]←xlen;
param[1]←ylen;
WriteCommand[p,ELShowRectangle,@param];
};

PutTextHere: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor,
	str: LONG STRING] = {
s: Stream.Handle ← p.outStream;
param: ARRAY [0..6) OF CARDINAL;

IF p.numFonts = 0 THEN SetFont[p,"Gacha"L,8];
FOR i: CARDINAL IN [0..str.length) DO 
	Stream.PutChar[s,str[i]]; 
	ENDLOOP;
param[0] ← str.length;
WriteCommand[p,ELShowCharacters,@param];
};

PutRectangleHere: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor, 
	xlen,ylen: CARDINAL] =
{
param: ARRAY [0..6) OF CARDINAL;

param[0]←xlen;
param[1]←ylen;
WriteCommand[p,ELShowRectangle,@param];
};

ScanInitMem: PROC [mem: LONG POINTER, nWordsPerLine: CARDINAL ]={
nw ← nWordsPerLine;
sl ← mem;
};

nextScanLineMem: PROC RETURNS [LONG POINTER] = {
mem: LONG POINTER ← sl;
sl ← sl+nw;
RETURN[mem];
};

PutAltoDots: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	x,y,nDots,nScanLines: CARDINAL,dots: LONG POINTER] = {
PutDots[p,x,y,nDots,nScanLines,0,nDots*32,nScanLines*32,dots];
};

PutDots: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	x,y,nPixels,nScanLines,bitsPerPixel,width,height: CARDINAL,dots: LONG POINTER,
	screenFrequency: CARDINAL ← 0,screenAngle: CARDINAL ← 45] =
{
pdd: PressDefs.PressDotsData ←
 [	nBitsPerPixel: bitsPerPixel,nPixels: nPixels,nLines: nScanLines,
	passPixels: 0,displayPixels: nPixels,passLines: 0,displayLines: nScanLines,
	micaWidth: width,micaHeight: height,
	min: 0,max: 0,
	angle: screenAngle,frequency: screenFrequency,
	opaque: FALSE,haveDot: FALSE,mode: 3,fileName: NIL,
	dotPosition: ,diskPosition:
 ];
ScanInitMem[dots,nPixels/(16/MIN[1,bitsPerPixel])];
PutPressDotsData[p,x,y,@pdd,nextScanLineMem];
};

PutComputedDots: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	x,y,nPixels,nScanLines,bitsPerPixel,width,height: CARDINAL,
	nextScanLine: PROC RETURNS [LONG POINTER],
	screenFrequency: CARDINAL ← 0,screenAngle: CARDINAL ← 45,
	min,max: CARDINAL ← 0]=
{
pdd: PressDefs.PressDotsData ←
 [	nBitsPerPixel: bitsPerPixel,nPixels: nPixels,nLines: nScanLines,
	passPixels: 0,displayPixels: nPixels,passLines: 0,displayLines: nScanLines,
	micaWidth: width,micaHeight: height,
	min: min,max: max,
	angle: screenAngle,frequency: screenFrequency,
	opaque: FALSE,haveDot: FALSE,mode: 3,fileName: NIL,
	dotPosition: ,diskPosition:
 ];
PutPressDotsData[p,x,y,@pdd,nextScanLine];
};

PutDotsFromFile: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	x,y,nPixels,nScanLines,bitsPerPixel,width,height: CARDINAL,dots: LONG STRING,
	screenFrequency: CARDINAL ← 0,screenAngle: CARDINAL ← 45,
	min,max: CARDINAL ← 0] =
{
pdd: PressDefs.PressDotsData ←
 [	nBitsPerPixel: bitsPerPixel,nPixels: nPixels,nLines: nScanLines,
	passPixels: 0,displayPixels: nPixels,passLines: 0,displayLines: nScanLines,
	micaWidth: width,micaHeight: height,
	min: min,max: max,
	angle: screenAngle,frequency: screenFrequency,
	opaque: FALSE,haveDot: FALSE,mode: 3,fileName: dots,
	dotPosition: ,diskPosition:
 ];
PutPressDotsData[p,x,y,@pdd,];
};

PutPressDotsData: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	x,y: CARDINAL,dotsData: LONG POINTER TO PressDefs.PressDotsData,
	nextScanLine: PROC RETURNS[LONG POINTER]] = {
s: Stream.Handle ← p.outStream;
pixelsPerWord: CARDINAL ← 16/MAX[dotsData.nBitsPerPixel,1];
displayPixels: CARDINAL ← IF dotsData.fileName=NIL THEN dotsData.displayPixels
	ELSE dotsData.nPixels; 
displayLines: CARDINAL ← IF dotsData.fileName=NIL THEN dotsData.displayLines
	ELSE dotsData.nLines; 
passPixels: CARDINAL ← IF dotsData.fileName#NIL THEN dotsData.passPixels
	ELSE dotsData.passPixels MOD pixelsPerWord; 
passLines: CARDINAL ← IF dotsData.fileName#NIL THEN dotsData.passLines
	ELSE 0; 
nWordsPerScanline: CARDINAL ← 
	((passPixels+displayPixels)*MAX[dotsData.nBitsPerPixel,1]+15)/16;
nWords: LONG CARDINAL ← LONG[nWordsPerScanline]*displayLines;
additionalWords: CARDINAL ← 0;
MaxTable: ARRAY [0..8] OF CARDINAL ← [1,1,3,7,15,31,63,127,255];
param: ARRAY [0..6) OF CARDINAL;

--set up scan lines to end on word boundaries
nPixels: CARDINAL ←
	Inline.BITAND[passPixels+displayPixels+pixelsPerWord-1,-pixelsPerWord];

param[0]←x;WriteCommand[p,ELSetX,@param];
param[0]←y;WriteCommand[p,ELSetY,@param];

IF dotsData.displayPixels = 0 OR dotsData.displayLines = 0 THEN RETURN;
IF Inline.BITAND[Inline.LowHalf[FileStream.GetIndex[s]],1] = 1 THEN 
	{ PutLowerByte[s,0];param[0]←1;WriteCommand[p,ELSkipCharactersShort,@param];};

PutLowerByte[s,DLSetCoding];PutLowerByte[s,dotsData.nBitsPerPixel];
[] ← WriteBlock[s,@nPixels,1];[] ← WriteBlock[s,@displayLines,1];
PutLowerByte[s,DLSetMode];PutLowerByte[s,dotsData.mode];
PutLowerByte[s,0];PutLowerByte[s,DLSetSize];
[] ← WriteBlock[s,@dotsData.micaWidth,1];
[] ← WriteBlock[s,@dotsData.micaHeight,1];
IF dotsData.frequency # 0 THEN
	{
	PutLowerByte[s,0];PutLowerByte[s,DLSetSamplingProperties];
	PutLowerByte[s,0];PutLowerByte[s,7];	--7 words of property stuff
	PutLowerByte[s,0];PutLowerByte[s,DLSSPScreen];
	[] ← WriteBlock[s,@dotsData.angle,1];
	PutLowerByte[s,0];PutLowerByte[s,100];
	[] ← WriteBlock[s,@dotsData.frequency,1];
	PutLowerByte[s,0];PutLowerByte[s,DLSSPInputIntensity];
	[] ← WriteBlock[s,@dotsData.min,1];
	[] ← WriteBlock[s,IF dotsData.max=0 THEN @MaxTable[dotsData.nBitsPerPixel] 
		ELSE @dotsData.max,1];
	additionalWords ← additionalWords + 9;
	};

PutLowerByte[s,0];PutLowerByte[s,DLSetWindow];
[] ← WriteBlock[s,@passPixels,1];
[] ← WriteBlock[s,@dotsData.displayPixels,1];
[] ← WriteBlock[s,@passLines,1];
[] ← WriteBlock[s,@dotsData.displayLines,1];
additionalWords ← additionalWords + 5;

PutLowerByte[s,0];
IF dotsData.fileName=NIL THEN
	{
	PutLowerByte[s,DLDotsFollow];
	THROUGH [0..dotsData.passLines) DO [] ← nextScanLine[];ENDLOOP;
	THROUGH[0..dotsData.displayLines) DO
		[] ← WriteBlock[s,nextScanLine[]+dotsData.passPixels/pixelsPerWord,
				nWordsPerScanline];
	ENDLOOP;
	param[0] ← Inline.HighHalf[nWords+8+additionalWords];
	param[1] ← Inline.LowHalf[nWords+8+additionalWords];
	}
ELSE
	{
	dots: LONG STRING ← dotsData.fileName;
	nBytes: CARDINAL ← dots.length + (1-Inline.BITAND[dots.length,1]);
	found: BOOLEAN ← FALSE;
	
	FOR i: CARDINAL IN [0..nExternalFileStrings) DO
		IF LongString.EquivalentStrings[externalFileStrings[i],dots] THEN
			{ found ← TRUE;EXIT;};
	ENDLOOP;
	
	IF NOT found THEN
		{
		externalFileStrings[nExternalFileStrings] ←
					AllocateString[dots.length];
		LongString.AppendString[from: dots,
			to: externalFileStrings[nExternalFileStrings]];
		nExternalFileStrings ← nExternalFileStrings + 1;
		};
	PutLowerByte[s,DLGetDotsFromFile];	
	PutLowerByte[s,0];PutLowerByte[s,4];		--record offset = 4 (1024 word header)
	PutLowerByte[s,dots.length];	
	FOR i: CARDINAL IN [0..nBytes) DO 
		Stream.PutChar[s,dots[i]] 
		ENDLOOP;
	param[0] ← 0;
	param[1] ← ((nBytes+1)/2)+9+additionalWords;
	};

WriteCommand[p,
	IF dotsData.opaque THEN ELShowDotsOpaque ELSE ELShowDots,@param];
};

NewEntitySegment: PROC [p: LONG POINTER TO PressFileDescriptor] = {
esh,segmentQ: LONG POINTER TO entitySegment;
offset: LONG CARDINAL ← LONG[p.RecordStart]*Environment.bytesPerPage;
es: Stream.Handle ← LOOPHOLE[p.EntityCommands];
IF (p.EntityCommandLen MOD 2)=1 THEN 
	{ PutLowerByte[es,ELNop];p.EntityCommandLen←p.EntityCommandLen+1;};
esh ← AllocateSegment[SIZE[entitySegment]];
esh↑ ← [link: NIL,length: p.EntityCommandLen,beginByte:,
	endByte: FileStream.GetIndex[p.outStream]-offset];
IF entitySegmentHead = NIL THEN 
	{
	esh.beginByte ← 0;
	entitySegmentHead ← esh;
	}
ELSE
	{
	segmentQ ← entitySegmentHead;
	UNTIL segmentQ.link = NIL DO segmentQ ← segmentQ.link;ENDLOOP;
	segmentQ.link ← esh;
	esh.beginByte ← segmentQ.endByte;
	};
p.EntityCommandLen ← 0;
};

WritePage: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor] = {
s: Stream.Handle ← p.outStream;
es: Stream.Handle ← LOOPHOLE[p.EntityCommands];
ECommand: EntityCommandTrailer;
startPos,byteLen,entityLen: LONG INTEGER;
padding,recordLen: INTEGER;
segmentQWordLen: CARDINAL;
segmentQ: LONG POINTER TO entitySegment;
buffer: ARRAY [0..Environment.wordsPerPage) OF UNSPECIFIED;

IF (FileStream.GetIndex[s] MOD 2)=1 THEN PutLowerByte[s,0];
startPos←p.RecordStart;
startPos←startPos*Environment.bytesPerPage;
byteLen ← FileStream.GetIndex[s] - startPos;
--first, insert a zero word
PutLowerByte[s,0];PutLowerByte[s,0];
entityLen←(byteLen/2)+1;

NewEntitySegment[p];
Stream.SendNow[es];
FileStream.SetIndex[es, 0];

FOR segmentQ ← entitySegmentHead,segmentQ.link UNTIL segmentQ = NIL DO
	segmentQWordLen ← segmentQ.length/2;
	UNTIL segmentQWordLen = 0 DO
		[] ← ReadBlock[es,@buffer,MIN[Environment.wordsPerPage,segmentQWordLen]];
		[] ← WriteBlock[s,@buffer,MIN[Environment.wordsPerPage,segmentQWordLen]];
		segmentQWordLen ← segmentQWordLen-MIN[Environment.wordsPerPage,segmentQWordLen];
	ENDLOOP;
	--THROUGH [1..segmentQ.length] DO PutLowerByte[s,Stream.GetWord[es]];ENDLOOP;
	
	startPos ← segmentQ.beginByte;
	byteLen ← segmentQ.endByte - startPos;
	ECommand ←
		[Type: 0, FontSet: 0,
			BeginByte: [Inline.HighHalf[startPos],Inline.LowHalf[startPos]],
			ByteLen: [Inline.HighHalf[byteLen],Inline.LowHalf[byteLen]],Xe: 0,Ye: 0,
			Left: 0,Bottom: 0,Width: MicaWidth,Height: MicaHeight,
			EntityLen: SIZE[EntityCommandTrailer] + segmentQ.length/2
		];	
	[]←WriteBlock[s,@ECommand,SIZE[EntityCommandTrailer]];
	entityLen←entityLen+ECommand.EntityLen;
ENDLOOP;
DO
	segmentQ ← entitySegmentHead;
	entitySegmentHead ← segmentQ.link;
	FreeSegment[segmentQ];
	IF entitySegmentHead = NIL THEN EXIT;
	ENDLOOP;
FileStream.SetIndex[es, 0];

--now, compute (and write) padding, update part directory
padding←Inline.BITAND[Environment.wordsPerPage-
	Inline.BITAND[Inline.LowHalf[entityLen],377B],377B];
[recordLen,]←Inline.LDIVMOD[numlow: Inline.LowHalf[entityLen],
	numhigh: Inline.HighHalf[entityLen],den: Environment.wordsPerPage];
IF padding # 0 THEN recordLen←recordLen+1;
[]←WriteBlock[s,p,padding]; --write garbage padding
p.partDir[p.numParts].Type←Page;	--printed page
p.partDir[p.numParts].RecordStart←p.RecordStart;
p.partDir[p.numParts].RecordLength←recordLen;
p.partDir[p.numParts].PaddingLength←padding;
lastSetX←lastSetY←lastFont←lastSpaceX←lastSpaceY←p.CurHue←p.CurSat←p.CurBright←0;
p.numParts←p.numParts+1;

p.RecordStart←p.RecordStart+recordLen;
};

WritePartDirectory: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor] = {
padding,temp: INTEGER;

FOR i: CARDINAL IN [0..p.numParts) DO
	[]←WriteBlock[p.outStream,@p.partDir[i],4];
	ENDLOOP;
padding←(Environment.wordsPerPage-LOOPHOLE[(p.numParts MOD 64)*4,INTEGER]) MOD Environment.wordsPerPage;
[]←WriteBlock[p.outStream,p,padding];
temp←p.numParts;
UNTIL temp <= 0 DO
	p.partLen←p.partLen+1;
	temp←temp-64;
	ENDLOOP;
};

InitPressFileDescriptor: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor,
	filename: LONG STRING] =
{
entityCommandArray: Stream.Handle ← NewStream["EntityCommandList.$"L,
	File.read + File.write + File.grow + File.shrink + File.delete];
temp: STRING ← [40];
dateString: LONG STRING ← AllocateString[40];
userString: LONG STRING ← AllocateString[20];
pd: LONG POINTER TO PartDir ← AllocateSegment[SIZE[PartDir]];
fd: LONG POINTER TO FontDir ← AllocateSegment[SIZE[FontDir]];

nExternalFileStrings ← 0;	--should be in PressFileDescriptor
entitySegmentHead ← NIL;
p.outStream←NewStream[filename,File.write + File.grow + File.shrink + File.delete];
p.EntityCommandLen←0;
p.EntityCommands←LOOPHOLE[entityCommandArray];
p.RecordStart←0;
p.fontLen←0;
p.partLen←0;
p.numParts←0;
p.numFonts←0;
p.fontDir←fd;
p.partDir←pd;
p.FirstCopy←1;
p.LastCopy←1;
p.solidCode←'t;	--undefined: only 's or 't are meaningful
p.PressFileName←filename;
p.UserName←AllocateString[Profile.userName.length];
LongString.AppendString[p.UserName, Profile.userName];
Time.Append[temp,Time.Unpack[Time.Current[]]];
LongString.AppendString[dateString, temp];
p.DateString←dateString;
p.CurSpaceX ← p.CurSpaceY ← 177777B;
};

ClosePressFile: PUBLIC PROC [p: LONG POINTER TO PressFileDescriptor] = {
i,j: CARDINAL;
zero: CARDINAL ← 0;
s: Stream.Handle ← p.outStream;
es: Stream.Handle ← LOOPHOLE[p.EntityCommands];
FBuf: LONG POINTER TO PressDefs.FontEntry;

IF p.EntityCommandLen # 0 THEN WritePage[p];

IF nExternalFileStrings > 0 THEN
	{
	nBytesOut: CARDINAL ← 0;
	str: LONG STRING;
	FOR i IN [0..nExternalFileStrings) DO
		str ← externalFileStrings[i];
		PutLowerByte[s,str.length];
		FOR j IN [0..str.length+1-Inline.BITAND[str.length,1]) DO 
			Stream.PutChar[s,str[j]] 
			ENDLOOP;
		nBytesOut ← nBytesOut + 1 + str.length+1-Inline.BITAND[str.length,1];
	ENDLOOP;
	[] ← WriteBlock[s,@zero,(Environment.bytesPerPage-nBytesOut)/2];
	p.partDir[p.numParts].Type←ExternalFileDirectory;
	p.partDir[p.numParts].RecordStart←p.RecordStart;
	p.partDir[p.numParts].RecordLength←1;
	p.partDir[p.numParts].PaddingLength←255;
	p.numParts←p.numParts+1;
	p.RecordStart ← p.RecordStart + 1;
	};

--WriteFontDirectory;
FOR i IN [0..p.numFonts) DO
	FBuf ← p.fontDir[i];
	Stream.PutWord[s,FBuf.EntryLength];
	Stream.PutByte[s,FBuf.FontSet];
	Stream.PutByte[s,FBuf.Font];
	Stream.PutByte[s,FBuf.M];
	Stream.PutByte[s,FBuf.N];
	PutLowerByte[s,FBuf.Family.length];
	FOR j IN [0..19) DO 
		Stream.PutChar[s,
			IF j<FBuf.Family.length THEN FBuf.Family[j] ELSE 0C]; 
		ENDLOOP;
	Stream.PutByte[s,FBuf.Face];
	Stream.PutByte[s,FBuf.Source];
	Stream.PutWord[s,FBuf.Size];
	Stream.PutWord[s,FBuf.Rotation];
ENDLOOP;
[] ← WriteBlock[s,@zero,Environment.wordsPerPage-(p.numFonts*16)];	--padding
p.partDir[p.numParts].Type←Font;
p.partDir[p.numParts].RecordStart←p.RecordStart;
p.partDir[p.numParts].RecordLength←IF p.numFonts < 16 THEN 1 ELSE 2;
p.partDir[p.numParts].PaddingLength←255;
p.numParts←p.numParts+1;
p.fontLen←1;

WritePartDirectory[p];
WriteDirectory[p];

Stream.Delete[s];
Stream.Delete[es]; --SystemDefs.FreeSegment[p.EntityCommands];
FreeString[p.DateString];
FreeString[p.UserName];
FreeSegment[p.partDir];
};

--object commands, Written by Martin Newell, January 1980
Count: CARDINAL ← 0; --ought to be in the individual PressFileDescriptors

StartOutline: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor,
	x0,y0: CARDINAL] = {
s: Stream.Handle ← p.outStream;
param: ARRAY [0..6) OF CARDINAL;
IF Count#0 THEN	{
	EndOutline[p];
	Count ← 0;
	};
IF Inline.BITAND[Inline.LowHalf[FileStream.GetIndex[s]],1] = 1 THEN 
	{ 
	PutLowerByte[s,0];
	param[0]←1;
	WriteCommand[p,ELSkipCharactersShort,@param];
	};
PutMoveTo[p,x0,y0];
};

PutMoveTo: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor,
	xend,yend: CARDINAL] = {
s: Stream.Handle ← p.outStream;
cmd: CARDINAL ← DLMoveTo;
[] ← WriteBlock[s,@cmd,1];
[] ← WriteBlock[s,@xend,1];
[] ← WriteBlock[s,@yend,1];
Count ← Count + 3;
};

PutDrawTo: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor,
	xend,yend: CARDINAL] = {
s: Stream.Handle ← p.outStream;
cmd: CARDINAL ← DLDrawTo;
[] ← WriteBlock[s,@cmd,1];
[] ← WriteBlock[s,@xend,1];
[] ← WriteBlock[s,@yend,1];
Count ← Count + 3;
};

PutCubic: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor,
	x1,y1,x2,y2,x3,y3: REAL] = {
s: Stream.Handle ← p.outStream;
cmd: CARDINAL ← DLDrawCurve;
[] ← WriteBlock[s,@cmd,1];
[] ← WriteBlock[s,@x1,2];
[] ← WriteBlock[s,@y1,2];
[] ← WriteBlock[s,@x2,2];
[] ← WriteBlock[s,@y2,2];
[] ← WriteBlock[s,@x3,2];
[] ← WriteBlock[s,@y3,2];
Count ← Count + 13;
};

EndOutline: PUBLIC PROC[p: LONG POINTER TO PressFileDescriptor] =
{
param: ARRAY [0..6) OF CARDINAL;
param[0] ← Count;
WriteCommand[p,ELShowObject,@param];
Count ← 0;
};

-- compatibility routines for Pilot
AllocateString: PUBLIC PROC[nchars: CARDINAL] RETURNS[s: LONG STRING] = {
s ← Heap.systemZone.NEW[StringBody[nchars]
	← StringBody[length: 0, maxlength: nchars, text:]];
};

FreeString: PUBLIC PROC[str: LONG STRING] = {
IF str = NIL THEN RETURN;
Heap.systemZone.FREE[@str];
};

AllocateSegment: PROC[nwords: CARDINAL] RETURNS[ptr: LONG POINTER] = {
seq: TYPE = RECORD[
	body: SEQUENCE maxsize: CARDINAL OF WORD
	];
IF nwords > 32000 THEN ERROR;
ptr ← Heap.systemZone.NEW[seq[nwords]];
};

FreeSegment: PUBLIC PROC[ptr: LONG POINTER] = {
IF ptr = NIL THEN RETURN;
Heap.systemZone.FREE[@ptr];
};

NewStream: PROC [name: LONG STRING, access: File.Permissions]
	RETURNS [Stream.Handle] = {
cap: File.Capability;
old: BOOLEAN ← FALSE;
IF access ~= File.read THEN 
	cap ← Directory.CreateFile[name, DCSFileTypes.tLeaderPage, 0
		! Directory.Error => {
			IF type = fileAlreadyExists THEN old ← TRUE 
			ELSE ERROR;
			CONTINUE
		}]
ELSE old ← TRUE;
IF old THEN cap ← Directory.Lookup[fileName: name, permissions: Directory.ignore
	! Directory.Error => ERROR];
cap ← Directory.UpdateDates[cap, access];
RETURN[FileStream.Create[cap]];
};

WriteBlock: PROC[sh: Stream.Handle, p: LONG POINTER, nwords: CARDINAL] = {
Stream.PutBlock[sh, [p, 0, nwords * 2]];
};

ReadBlock: PROC[sh: Stream.Handle, p: LONG POINTER, nwords: CARDINAL] 
	RETURNS[nbytesread: CARDINAL] = {
[bytesTransferred: nbytesread] ← Stream.GetBlock[sh, [p, 0, nwords*2]];
};

Zero: PROC[p: LONG POINTER, nwords: CARDINAL] = {
FOR i: CARDINAL IN [0 .. nwords) DO
	(p+i)↑ ← 0;
	ENDLOOP;
};

PutLowerByte: PROC[sh: Stream.Handle, w: CARDINAL] = {
Stream.PutByte[sh, Inline.LowByte[w]];
};

-- this testprogram may be called to test out changes to this Module
TestProgram: PROC = {
p: PressFileDescriptor;
InitPressFileDescriptor[@p,"Play.PRESS"L];
PutText[@p,"Default font"L,2540,2540*9];
SetFont[@p,"Helvetica"L,10];
PutText[@p,"Helvetica 10 here"L,2540,2540*8];
SetFont[@p,"Gacha"L,8];
PutText[@p,"Gacha 8 here"L,2540,2540*7];
SetFont[@p,"HelVetiCa"L,10];
PutText[@p,"Helvetica 10 here"L,2540,2540*6];
IF TRUE THEN {
	xstart,ystart,xlen,ylen: CARDINAL;
	MicaMarg: INTEGER = 2540/2;
	FOR i: CARDINAL IN [0..6) DO
		SetColor[@p,i*40,255,255];
		xlen←(MicaWidth-3*MicaMarg)/2;
		ylen←(MicaHeight-4*MicaMarg)/3;
		xstart←MicaMarg+(i MOD 2)*(xlen+MicaMarg);
		ystart←MicaMarg+(i/2)*(ylen+MicaMarg);
		PutRectangle[@p,xstart,ystart,xlen,ylen];
		ENDLOOP;
	};
ClosePressFile[@p];
};

}.