-- PrintControl.mesa; edited by Johnsson on November 3, 1980  8:49 AM
-- Converted to Laurel by Pier, 24-Jul-81 14:56:53 
-- Edited by Brotz, 27-Jul-81 13:17:27
-- Edited by Pier,  August 14, 1981  2:07 PM
-- Converted to Laurel 6.1 by Pier, 17-May-83 10:24:07
-- Edited by Taft, May 17, 1983  3:34 PM

DIRECTORY
  AltoDefs USING [CharsPerWord, PageSize],
  Ascii USING [ESC, ControlZ, CR, DEL, FF, NUL, SP, TAB],
  ImageDefs USING [BcdTime],
  IODefs USING [WriteChar, WriteDecimal, WriteLine, WriteString, ReadChar,
					  ReadEditedString, Rubout, LineOverflow],
  intCommon,
  LaurelExecDefs,
  opD: FROM "OperationsDefs",
  ovD: FROM "OverviewDefs",
  csD: FROM "CoreStreamDefs",
  Core: FROM "Core",
  Press USING [
    Abort, Character, Finish, FlushFontBuffers, FontIndex,
    FontSlope, FontWeight, GetCurrentPageNumber, GetCurrentPosition,
    GetWidthOfCharacter, Initialize, Mica, micasPerInch, Mode,
    pageHeight, pageWidth, PieceOfLine, Points, PutFontInTable, SetCurrentFont,
    SetCurrentPageNumber, SetCurrentTabWidth, SetHeaderText, SetMargins, SetMode,
    SkipSomeSpace, SetTrailerText, Start],
  PressUtilities USING [
    hardcopyHost, IsPressFile, SendPressStream, ServerBusy, ServerTimeout,
    ServerTrouble, SetupFontsForBravo, SetupHardCopyOptions],
  PrintDefs USING [BravoIt, GetStatus, FinishPrintStorage, PError,
							PErrorCode],
  ProcessDefs USING [Yield],
  String USING [AppendChar, AppendString, EquivalentStrings, StringBoundsFault],
  Storage USING [String, Pages, FreeString, FreePages],
  StreamDefs USING [StreamHandle],
  Time USING [Append, Unpack],
  VMDefs;

PrintControl: PROGRAM
  IMPORTS
    ImageDefs, IODefs, Press, LaurelExecDefs, PressUtilities,
    PrintDefs, ProcessDefs, intCommon, Core, VMDefs, csD, opD,  
    String, Storage,  Time
  EXPORTS PrintDefs =
  BEGIN
  
PError: PUBLIC ERROR[code: PrintDefs.PErrorCode] = CODE;

  NUL: CHARACTER = Ascii.NUL;
  Mica: TYPE = Press.Mica;
  Inch: Mica = Press.micasPerInch;
  CharsPerWord: CARDINAL = AltoDefs.CharsPerWord;
  
--global command line storage instead of comCM stream
  comLine: STRING = [512];
  comPos: CARDINAL ← 0; --current position in comLine

  CopyString: PROCEDURE [old: STRING] RETURNS [new: STRING] =
    BEGIN
    IF old = NIL THEN RETURN[NIL];
    new ← Storage.String[old.length];
    String.AppendString[new, old];
    RETURN
    END;
    
  FreeString: PROCEDURE [old: STRING] =
    BEGIN IF old # NIL THEN Storage.FreeString[old]; RETURN END;
    
  debugging: BOOLEAN ← FALSE;
  
  SetDebugging: PROCEDURE [d: BOOLEAN] =
    BEGIN
    debugging ← d;
    IODefs.WriteString["Debugging "L];
    IODefs.WriteLine[IF debugging THEN "on"L ELSE "off"L];
    IF debugging THEN
      BEGIN FreeString[bufferFile]; bufferFile ← CopyString["Buffer.press$"]; END;
    RETURN
    END;
    
  SetFont: PROCEDURE [f: STRING, p: POINTER TO Parameters] =
    BEGIN
    IF font # NIL AND String.EquivalentStrings[f, font] THEN RETURN;
    FreeString[font];
    font ← CopyString[f];
    fontChanged ← TRUE;
    RETURN
    END;
    
  SetHost: PROCEDURE [h: STRING, p: POINTER TO Parameters] =
    BEGIN
    IF outputFile # NIL THEN FreeString[outputFile];
    outputFile ← NIL;
    transmitting ← TRUE;
    IF printerName # NIL AND ~String.EquivalentStrings[h, printerName] THEN
      FinishFile[];
    IF printerName # NIL THEN FreeString[printerName];
    printerName ← CopyString[h];
    haveStatus ← FALSE;
    RETURN
    END;
    
  SetOutputFile: PROCEDURE [f: STRING, p: POINTER TO Parameters] =
    BEGIN OPEN String;
    userName: STRING = [40];
    FinishFile[];
    IF printerName # NIL THEN FreeString[printerName];
    printerName ← NIL;
    IF outputFile # NIL THEN FreeString[outputFile];
    FOR i: CARDINAL IN [0..f.length) DO
      IF f[i] = '. THEN {IF i = f.length - 1 THEN AppendString[f, "press"L]; EXIT}
      REPEAT FINISHED => {AppendChar[f, '.]; AppendString[f, "press"L]};
      ENDLOOP;
    outputFile ← CopyString[f];
    String.AppendString[userName, intCommon.user.name !
		          String.StringBoundsFault => GO TO sbF];
    transmitting ← FALSE;
    IODefs.WriteString["Output to "L];
    IODefs.WriteLine[outputFile];
    RETURN
    EXITS sbF => SBFault[];
    END;
    
  SetLandscape: PROCEDURE [c: CARDINAL, p: POINTER TO Parameters] =
    BEGIN
    p.mode ← landscape;
    IF ~fontSpecified THEN SetFont[lDefault.font, p];
    p.columns ← c;
    p.margins ← lDefault.margins;
    RETURN
    END;
    
  SetPortrait: PROCEDURE [c: CARDINAL, p: POINTER TO Parameters] =
    BEGIN
    p.mode ← portrait;
    IF ~fontSpecified THEN SetFont[pDefault.font, p];
    p.columns ← c;
    p.margins ← pDefault.margins;
    END;
    
  SetTabWidth: PROCEDURE [c: CARDINAL, p: POINTER TO Parameters] =
    BEGIN p.tab ← c; END;
    
  SetCopies: PROCEDURE [c: CARDINAL, p: POINTER TO Parameters] =
    BEGIN p.copies ← c; END;
    
  SetBravo: PROCEDURE [p: POINTER TO Parameters, xx: BOOLEAN] =
    BEGIN p.bravo ← xx; END;
    
  sides: CARDINAL ← 0;
  
  PropList: PROCEDURE [POINTER] RETURNS [CARDINAL] ← NIL;
  
  SetSides: PROCEDURE [s: CARDINAL, p: POINTER TO Parameters] =
    BEGIN sides ← s; PropList ← IF sides = 0 THEN NIL ELSE MakePropertyList; END;
    
  AppendBooleanProperty: PROCEDURE [s, prop: STRING, b: BOOLEAN] =
    BEGIN OPEN String;
    AppendChar[s, '(];
    AppendString[s, prop];
    AppendChar[s, Ascii.SP];
    AppendString[s, IF b THEN "TRUE"L ELSE "FALSE"L];
    AppendChar[s, ')];
    RETURN
    END;
    
  MakePropertyList: PROCEDURE [p: POINTER] RETURNS [bytes: CARDINAL] =
    BEGIN OPEN String;
    passwordPtr: TYPE = POINTER TO MACHINE DEPENDENT RECORD [a, b: CARDINAL];
    s: STRING ← p;
    s↑ ← [length: 0, maxlength: 508, text:];
    AppendChar[s, '(];
    AppendBooleanProperty[s, "DUPLEX"L, sides = 2];
    AppendChar[s, ')];
    bytes ← s.length + 4;
    LOOPHOLE[p, passwordPtr]↑ ← [125314B, 170377B];
    RETURN
    END;
    
pressFileActive: BOOLEAN ← FALSE;
  
PressThisFile: PROCEDURE [file: STRING] =
 BEGIN

 fh: VMDefs.FileHandle ← NIL;

   BEGIN
   ENABLE  Abort, UNWIND => fh ← CloseF[fh];

MyFileError: PROCEDURE [reason: opD.FileErrorReason, fileName, errorString: STRING] =
 BEGIN OPEN IODefs, opD;
  WriteString[" Can't access "L];
  WriteString[fileName];
  WriteString[": "L];
  WriteString[SELECT reason FROM
    notFound => "file not found"L,
    cantConnect => "can't connect to server"L,
    illegalName => "illegal name"L,
    diskFull => "disk full"L,
    ftpError => "FTP error"L,
    ENDCASE => ""L];
  IF errorString#NIL THEN {WriteString[" -- "L]; WriteString[errorString]};
  WriteLine[""L];
  END;

overWriteTrue: PROCEDURE RETURNS [BOOLEAN] =
 BEGIN RETURN[TRUE]; END;

    c: CHARACTER;
    createTime: LONG CARDINAL ← 0;
    isPressFile: BOOLEAN;
    lastPage: CARDINAL ← 0;
    header: STRING ← [120];
    Core.Login[@intCommon.user];
    IF file[0]='[ THEN BEGIN
      [] ← opD.Copy[file, "PRINT.TEMP$"L, overWriteTrue ! opD.FileError => {MyFileError[reason: reason, fileName: file, errorString: errorString]; GOTO exit;};];
      fh ← Core.Open[filename:"PRINT.TEMP$"L, mode: read];
      printTempted ← TRUE;
      END
    ELSE
     fh ← Core.Open[filename: file, mode: read ! VMDefs.CantOpen, VMDefs.Error => {MyFileError[reason: notFound, fileName: file, errorString: NIL];  GOTO exit;};];
    --Core.FreeCacheEntry[file];--
    createTime ← VMDefs.GetFileTimes[fh].create;
    IF transmitting AND ~haveStatus AND ~PrintDefs.GetStatus[] THEN
      BEGIN
      IODefs.WriteString["Type DEL to exit"L];
      UNTIL (c ← IODefs.ReadChar[]) = Ascii.DEL DO ENDLOOP;
      SIGNAL Abort;
      END;
    haveStatus ← TRUE;
    String.AppendString[header, file ! String.StringBoundsFault => GO TO sbF;];
    String.AppendString[header, "  "L];
    Time.Append[header, Time.Unpack[createTime]];
    IF pressFileActive AND
	outputFile = NIL AND
	csD.MapPositionToPageByte[csD.GetPosition[outputStream]].page > 200
	THEN FinishFile[];
    IF writeMessages THEN
      {IODefs.WriteString[IF cParameters.bravo THEN "Bravoing "L ELSE "Pressing "L];
       IODefs.WriteString[file];
       ShowParameters[];
       IODefs.WriteString["..."L];} ELSE IODefs.WriteChar['*];

    [isPressFile, lastPage] ← PressUtilities.IsPressFile[fh];
    IF isPressFile THEN
      BEGIN
      IF writeMessages THEN IODefs.WriteString["already in Press format..."L];
      IF transmitting THEN
	BEGIN OPEN IODefs, csD, PressUtilities;
	s: csD.StreamHandle ← csD.Open[fh, byte, read];
        aborted: BOOLEAN ← FALSE;
	WriteString["sending to "L];
	WriteString[printerName];
	WriteString["..."L];
	SendPressStream[
	  s, lastPage, printerName, cParameters.copies, PropList !
	ServerBusy => {aborted ← Wait["busy"L, 8];
						IF aborted THEN CONTINUE ELSE RESUME};
	ServerTimeout => {aborted ← Wait["not responding"L, 0];
					   	IF aborted THEN CONTINUE ELSE RESUME};
	  ServerTrouble => {
	    IF message # NIL THEN WriteString[message];
	    aborted ← TRUE;
	    CONTINUE};
	  UNWIND => {Press.Abort[]; s ← DestroyS[s]; fh ← CloseF[fh];}];
	WriteLine[IF aborted THEN "Aborted"L ELSE "Done"L];
	s ← DestroyS[s];
	END
      ELSE IF writeMessages THEN IODefs.WriteString["skipped"L];
      fh ← CloseF[fh];
      RETURN
      END;
    Press.SetHeaderText[NIL, FALSE];
    IF pressFileActive THEN
      THROUGH [0..neededFFs) DO
	Press.Character[Ascii.FF];
	Press.SetTrailerText[NIL, FALSE]; -- keep trailer on last page
	ENDLOOP;
    Press.SetCurrentPageNumber[1];
    IF cParameters.headers THEN Press.SetHeaderText[header, TRUE];
    IF cParameters.trailers THEN Press.SetTrailerText[header, TRUE];
    IF cParameters.bravo THEN
      BEGIN
      IF ~bravoFonts THEN PressUtilities.SetupFontsForBravo[];
      bravoFonts ← TRUE;
      IF ~pressFileActive THEN StartFile[file];
      PrintDefs.BravoIt[fh];
      neededFFs ← 1;
      END
    ELSE
      BEGIN OPEN Press;
      pages: CARDINAL;
      IF fontChanged OR bravoFonts THEN InstallFont[font];
      SetMode[cParameters.columns, betweenColumns, cParameters.mode];
      SetMargins[
	cParameters.margins[left], cParameters.margins[right],
	cParameters.margins[top], cParameters.margins[bottom]];
      IF ~pressFileActive THEN StartFile[file];
      SetCurrentFont[defaultFont, cParameters.weight, cParameters.slope];
      SetCurrentTabWidth[GetWidthOfCharacter[' ]*cParameters.tab];
      pages←PrintFile[fh];
    IF writeMessages THEN
      {IODefs.WriteDecimal[pages];
      IODefs.WriteString[" page"L];
      IF pages # 1 THEN IODefs.WriteChar['s];};
      END;
    fh ← CloseF[fh];
    IF writeMessages THEN IODefs.WriteChar[Ascii.CR];
    RETURN
    EXITS sbF => SBFault[];
    END;--of ENABLE block
   EXITS
    exit=> NULL;
   END;--of PressThisFile
    
  transmitting: BOOLEAN ← TRUE;
  
  outputStream: csD.StreamHandle ← NIL;
  
  StartFile: PROCEDURE [name: STRING] =
    BEGIN
    Core.Login[@intCommon.user];
    IF outputFile = NIL THEN
      BEGIN
      IF outputStream = NIL THEN
        BEGIN
        outputStream ← csD.OpenFromName[bufferFile, byte, write];
        --Core.FreeCacheEntry[bufferFile];--
        END;
      END
    ELSE
      BEGIN
      outputStream ← csD.OpenFromName[outputFile, byte, write];
      --Core.FreeCacheEntry[outputFile];--
      END;
    Press.Start[name, outputStream];
    pressFileActive ← TRUE;
    END;
    
  FinishFile: PROCEDURE =
    BEGIN OPEN IODefs;
    aborted: BOOLEAN ← FALSE;
    lastPage: CARDINAL ← 177777B;
    IF ~pressFileActive THEN RETURN;
    Press.Finish[];
    IF outputStream # NIL THEN
                [lastPage, ] ← csD.MapPositionToPageByte[csD.GetPosition[outputStream]];
    IF transmitting AND lastPage # 177777B THEN
      BEGIN OPEN PressUtilities;
      WriteString["sending to "L];
      WriteString[printerName];
      WriteString["..."L];
      csD.SetPosition[outputStream, 0];-- reset press file stream to zero
      SendPressStream[
	outputStream, lastPage, printerName, cParameters.copies, PropList !
	ServerBusy => {aborted ← Wait["busy"L, 8];
						IF aborted THEN CONTINUE ELSE RESUME};
	ServerTimeout => {aborted ← Wait["not responding"L, 0];
					   	IF aborted THEN CONTINUE ELSE RESUME};
	ServerTrouble => {
	  IF message # NIL THEN WriteString[message];
	  aborted ← TRUE;
	  CONTINUE}];
      END ELSE IF transmitting THEN aborted ← TRUE;
    outputStream ← DestroyS[outputStream];
    WriteLine[IF aborted THEN "...Aborted"L ELSE "Done"L];
    pressFileActive ← FALSE;
    END;
    
  ComputeLineWidth: PROCEDURE [p: POINTER TO Parameters] RETURNS [width: Mica] =
    BEGIN
    SELECT p.mode FROM
      portrait => width ← Press.pageWidth - p.margins[right] - p.margins[left];
      landscape => width ← Press.pageHeight - p.margins[top] - p.margins[bottom];
      ENDCASE => PrintDefs.PError[PageModeError];
    width ← width - (p.columns - 1)*(betweenColumns);
    width ← LOOPHOLE[width, CARDINAL]/p.columns;
    RETURN
    END;
    
  neededFFs: CARDINAL ← 0;
  
  bufferPages: CARDINAL = 10;
  bufferWords: CARDINAL = bufferPages * AltoDefs.PageSize;
  bufferChars: CARDINAL = bufferWords * AltoDefs.CharsPerWord;
  
  PrintFile: PROCEDURE [fh: VMDefs.FileHandle] RETURNS [lastPage: CARDINAL] =
    BEGIN OPEN p: cParameters, Press;
    s: csD.StreamHandle ← csD.Open[fh, byte, read];
    b: STRING ← [250];
    bol: BOOLEAN ← TRUE;
    lineWidth: Mica = ComputeLineWidth[@cParameters];
    i, whiteLength: CARDINAL ← 0;
    curX, bWidth: Mica ← 0;
    indentWidth, charWidth, whiteWidth: Mica;
    spaceWidth: Mica = GetWidthOfCharacter[Ascii.SP];
    tabWidth: Mica = spaceWidth*cParameters.tab;
    char: CHARACTER;
    ok: BOOLEAN;
    iBufIndex: CARDINAL ← 0;
    buffer: RECORD [
      numChars: CARDINAL,
      p: POINTER TO PACKED ARRAY [0..bufferChars) OF CHARACTER];

    NextChar: PROCEDURE RETURNS [c: CHARACTER, ok: BOOLEAN ← TRUE] =
      BEGIN
      IF iBufIndex = buffer.numChars THEN {
	buffer.numChars ← csD.ReadBlock[
	  s, buffer.p, 0, bufferChars];
	IF buffer.numChars = 0 THEN RETURN[c: , ok: FALSE];
	iBufIndex ← 0};
      c ← buffer.p[iBufIndex];
      iBufIndex ← iBufIndex + 1;
      RETURN
      END;
      
    PutPiece: PROCEDURE =
      BEGIN
      IF i # 0 THEN
	BEGIN
	Press.PieceOfLine[b, bWidth];
	bWidth ← 0;
	b.length ← i ← whiteLength ← 0;
	END;
      END;
      
    Overflow: PROCEDURE =
      BEGIN
      IF whiteLength = 0 OR whiteLength = b.length THEN
	BEGIN PutPiece[]; Press.Character[Ascii.CR] END
      ELSE
	BEGIN
	b.length ← whiteLength;
	Press.PieceOfLine[b, whiteWidth];
	Press.Character[Ascii.CR];
	bWidth ← bWidth - whiteWidth - spaceWidth;
	b.length ← i - whiteLength - 1;
	FOR j: CARDINAL IN [0..b.length) DO
	  b[j] ← b[whiteLength + j + 1]; ENDLOOP;
	i ← b.length;
	whiteLength ← 0;
	END;
      IF indentWidth # 0 THEN Press.SkipSomeSpace[indentWidth];
      curX ← indentWidth + bWidth;
      END;
      
    Cleanup: PROCEDURE =
      BEGIN
      Storage.FreePages[buffer.p];
      s ← DestroyS[s];
      RETURN
      END;
      
    buffer.p ← Storage.Pages[bufferPages];
    buffer.numChars ← 0;
    DO
      ENABLE UNWIND => Cleanup[];
      [char,ok] ← NextChar[];
      IF ~ok THEN EXIT;
      SELECT char FROM
	Ascii.SP =>
	  BEGIN
	  IF bol THEN BEGIN curX ← curX + spaceWidth; LOOP END;
	  whiteLength ← i;
	  whiteWidth ← bWidth;
	  IF curX + spaceWidth > lineWidth THEN BEGIN Overflow[]; LOOP END;
	  END;
	Ascii.CR, Ascii.FF =>
	  BEGIN
	  PutPiece[];
	  Press.Character[char];
	  bol ← TRUE;
	  indentWidth ← curX ← 0;
	  LOOP
	  END;
	Ascii.TAB =>
	  BEGIN
	  IF bol THEN
	    BEGIN curX ← (((curX + spaceWidth)/tabWidth) + 1)*tabWidth; LOOP END;
	  PutPiece[];
	  Press.Character[char];
	  curX ← GetCurrentPosition[].x;
	  LOOP
	  END;
	Ascii.ControlZ =>
	  BEGIN
	  PutPiece[];
	  UNTIL char = Ascii.CR DO
	      [char, ok] ← NextChar[];
              IF ~ok THEN EXIT;
              ENDLOOP;
	  Press.Character[char];
	  bol ← TRUE;
	  indentWidth ← curX ← 0;
	  LOOP
	  END;
	ENDCASE =>
	  IF bol THEN
	    BEGIN
	    IF (indentWidth ← curX) # 0 THEN Press.SkipSomeSpace[indentWidth];
	    bol ← FALSE;
	    END;
      charWidth ← GetWidthOfCharacter[char];
      IF curX + charWidth > lineWidth THEN Overflow[];
      IF i = b.maxlength THEN PutPiece[];
      b[i] ← char;
      b.length ← i ← i + 1;
      bWidth ← bWidth + charWidth;
      curX ← curX + charWidth;
      ENDLOOP;
    PutPiece[];
    Cleanup[];
    lastPage ← GetCurrentPageNumber[];
    BEGIN
    s: CARDINAL = IF sides = 2 THEN 2 ELSE 1;
    neededFFs ← (p.columns*s) - ((lastPage - 1) MOD (p.columns*s));
    END;
    RETURN
    END;
    
  defaultFont: Press.FontIndex = 0;
  
  InstallFont: PROCEDURE [f: STRING] =
    BEGIN
    name: STRING ← [40];
    b: BufferItem ← [0, f];
    c: CHARACTER;
    size: Press.Points;
    FinishFile[];
    DO
      c ← GetChar[@b];
      IF c = NUL THEN EXIT;
      IF c IN ['0..'9] THEN BEGIN Backup[@b]; EXIT END;
      String.AppendChar[name, c ! String.StringBoundsFault => GO TO sbF;];
      ENDLOOP;
    size ← GetNumber[@b, 8];
    cParameters.weight ← medium;
    cParameters.slope ← regular;
    UNTIL (c ← GetChar[@b]) = NUL DO
      IF c = 'b THEN cParameters.weight ← bold
      ELSE IF c = 'i THEN cParameters.slope ← italic;
      ENDLOOP;
    Press.FlushFontBuffers[];
    Press.PutFontInTable[defaultFont, name, size];
    bravoFonts ← fontChanged ← FALSE;
    RETURN
    EXITS sbF => SBFault[];
    END;
    
  Abort: SIGNAL = CODE;
  TicksPerSec: CARDINAL = 25;
  clock: POINTER TO INTEGER = LOOPHOLE[430B];
  keys: StreamDefs.StreamHandle;

  Wait: PROCEDURE [why: STRING, howlong: INTEGER] RETURNS[abort: BOOLEAN ← FALSE] =
    BEGIN OPEN IODefs;
    start: INTEGER;
    WriteString["
Server "];
    WriteString[why];
    WriteString["...will retry...type DEL to abort"];
    start ← clock↑;
    UNTIL clock↑ - start > howlong*TicksPerSec DO
      IF ~keys.endof[keys] AND
	keys.get[keys] = Ascii.DEL THEN RETURN[TRUE];
      ProcessDefs.Yield[];
      ENDLOOP;
    WriteString["..."];
    END;
    
  fontChanged, fontSpecified: BOOLEAN;
  bravoFonts: BOOLEAN ← FALSE;
  
  Defaults: TYPE = RECORD [
    font: STRING, columns: CARDINAL, margins: ARRAY Direction OF Press.Mica];
  
  lDefault: Defaults = ["Gacha6", 2, [Inch*3/4, Inch/2, Inch/2, Inch/2]];
  pDefault: Defaults = ["Gacha8", 1, [Inch*3/4, Inch/2, Inch*3/4, Inch/2]];
  betweenColumns: Mica = Inch/2;
  
  Direction: TYPE = {left, right, top, bottom};
  
  Parameters: TYPE = RECORD [
    copies, tab: CARDINAL,
    margins: ARRAY Direction OF Press.Mica,
    columns: CARDINAL,
    bravo: BOOLEAN,
    headers: BOOLEAN ← TRUE,
    trailers: BOOLEAN ← TRUE,
    weight: Press.FontWeight,
    slope: Press.FontSlope,
    mode: Press.Mode];
  
  font, bufferFile, outputFile: STRING;
  printerName: PUBLIC STRING;
  haveStatus: BOOLEAN ← FALSE;
  
  cParameters, dParameters: Parameters;
  
  ShowParameters: PROCEDURE =
    BEGIN OPEN IODefs;
    WriteChar['/];
    IF cParameters.bravo THEN {WriteChar['b]; RETURN};
    WriteChar[IF cParameters.mode = landscape THEN 'l ELSE 'p];
    WriteDecimal[cParameters.columns];
    IF ~cParameters.headers THEN WriteString["~a"L];
    IF ~cParameters.trailers THEN WriteString["~z"L];
    IF sides = 1 OR sides = 2 THEN {WriteChar['s]; WriteDecimal[sides]};
    END;

  InitGlobalParameters: PROCEDURE =
    BEGIN OPEN PressUtilities;
    fontSpecified ← FALSE;
    dParameters ←
      [copies: 1, tab: 8, margins: lDefault.margins, columns: lDefault.columns,
	bravo: FALSE, weight: medium, slope: regular, mode: landscape];
    Press.Initialize[];
    SetupHardCopyOptions[];
    printerName ← CopyString[hardcopyHost];
    font ← CopyString[lDefault.font];
    fontChanged ← TRUE;
    fontSpecified ← FALSE;
    bufferFile ← CopyString["Swatee"L];
    outputFile ← NIL;
    keys ← intCommon.keystream;
    END;
    
  SetGlobalParameters: PROCEDURE [b: Buffer] =
    BEGIN
    d: POINTER TO Parameters = @dParameters;
    sense: BOOLEAN ← TRUE;
    sc: CHARACTER;
    UNTIL (sc ← GetChar[b]) = NUL DO
      SELECT sc FROM
	'b => SetBravo[d, TRUE];
	'c => SetCopies[GetNumber[b, 1], d];
	't => SetTabWidth[GetNumber[b, 8], d];
	'l => SetLandscape[GetNumber[b, 2], d];
	'p => SetPortrait[GetNumber[b, 1], d];
	's => SetSides[GetNumber[b, 0], d];
	'd => SetDebugging[~debugging];
	'm => writeMessages ← sense;
	'a => d.headers ← sense;
	'z => d.trailers ← sense;
	'-, '~ => {sense ← FALSE; LOOP};
	ENDCASE;
      sense ← TRUE;
      ENDLOOP;
    cParameters ← dParameters;
    END;
    
  InitCurrentParameters: PROCEDURE =
    BEGIN fontSpecified ← FALSE; cParameters ← dParameters; END;
    
  BufferItem: TYPE = RECORD [p: CARDINAL, s: STRING];
  
  Buffer: TYPE = POINTER TO BufferItem;
  
  GetChar: PROCEDURE [b: Buffer] RETURNS [c: CHARACTER] =
    BEGIN OPEN b;
    c ← Ascii.NUL;
    IF p < s.length THEN
      BEGIN
      c ← s[p];
      p ← p + 1;
      IF c IN ['A..'Z] THEN c ← LOOPHOLE[LOOPHOLE[c, CARDINAL] + 40B];
      END;
    RETURN
    END;
    
  Backup: PROCEDURE [b: Buffer] = BEGIN IF b.p # 0 THEN b.p ← b.p - 1; END;
    
  GetNumber: PROCEDURE [b: Buffer, default: CARDINAL] RETURNS [v: CARDINAL] =
    BEGIN
    c: CHARACTER;
    usedefault: BOOLEAN ← TRUE;
    v ← 0;
    WHILE (c ← GetChar[b]) IN ['0..'9] DO
      usedefault ← FALSE; v ← v*10 + (c - 60C); ENDLOOP;
    IF c # NUL THEN Backup[b];
    IF usedefault THEN RETURN[default];
    END;
    
writeMessages: BOOLEAN ← TRUE;

  ProcessItem: PROCEDURE [arg, switches: STRING] =
    BEGIN
    b: BufferItem ← [0, switches];
    c: POINTER TO Parameters = @cParameters;
    sc: CHARACTER;
    sense: BOOLEAN ← TRUE;
    UNTIL arg.length = 0 OR (sc ← GetChar[@b]) = NUL DO
      SELECT sc FROM
	'f => {SetFont[arg, c]; fontSpecified ← TRUE; arg.length ← 0};
	'h => {SetHost[arg, c]; arg.length ← 0};
	'o => {SetOutputFile[arg, c]; arg.length ← 0};
	'c => SetCopies[GetNumber[@b, 1], c];
	't => SetTabWidth[GetNumber[@b, 8], c];
	'l => SetLandscape[GetNumber[@b, 2], c];
	'p => SetPortrait[GetNumber[@b, 1], c];
	's => SetSides[GetNumber[@b, 0], c];
	'b => SetBravo[c, TRUE];
	'd => SetDebugging[~debugging];
	'a => c.headers ← sense;
	'z => c.trailers ← sense;
	'm => writeMessages ← sense;
	'-, '~ => {sense ← FALSE; LOOP};
	ENDCASE =>
	  BEGIN OPEN IODefs;
	  WriteString["Unknown switch = "L];
	  WriteChar[sc];
	  WriteChar[Ascii.CR];
	  END;
      sense ← TRUE;
      ENDLOOP;
    IF arg.length = 0 THEN SetGlobalParameters[@b]
    ELSE BEGIN PressThisFile[arg]; InitCurrentParameters[]; END;
    RETURN
    END;
    
  GetToken: PROCEDURE [token, switches: STRING]
    RETURNS [found: BOOLEAN ← FALSE] =
    --uses global string comLine with current position comPos
    BEGIN
    s: STRING;
    c: CHARACTER;
    token.length ← switches.length ← 0;
    s ← token;
    WHILE comPos < comLine.length DO
      SELECT (c ← comLine[comPos]) FROM
	Ascii.CR, Ascii.SP => IF found THEN {comPos ← comPos + 1; EXIT;};
	'/ => s ← switches;
	'; =>  EXIT;
	ENDCASE => {found ← TRUE;
		    String.AppendChar[s, c ! String.StringBoundsFault => GO TO sbF;]};
      comPos ← comPos + 1;
      ENDLOOP;
    RETURN
    EXITS sbF => SBFault[];
    END;
    
  escTrue: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = 
    {RETURN [c = Ascii.ESC]};

  Command: PROCEDURE =
    BEGIN 
    arg: STRING ← [100];--large enough for 99 character file name
    switches: STRING ← [20];
    b: BufferItem ← [0, switches];
    InitGlobalParameters[];
    SetGlobalParameters[@b];
    InitCurrentParameters[];
    IODefs.WriteLine["Type command line, terminate with ESC"L];
    IODefs.WriteChar[Ascii.CR];
    comLine.length ← 0;
    [] ← IODefs.ReadEditedString[comLine, escTrue, FALSE !
         IODefs.Rubout => GO TO rubout;
         IODefs.LineOverflow => {IODefs.WriteChar[Ascii.CR];
                                       IODefs.WriteLine["***Command line truncated"L];
                                       CONTINUE;};];
    IODefs.WriteChar[Ascii.CR];
    WHILE GetToken[arg, switches] AND NoDEL[] DO
      ProcessItem[arg, switches]; ENDLOOP;
    FinishFile[];
    outputStream ← DestroyS[outputStream];
    EXITS rubout => SIGNAL Abort;
    END;
    
NoDEL: PROCEDURE RETURNS [BOOLEAN] = 
  BEGIN
  IF ~keys.endof[keys] AND
	keys.get[keys] = Ascii.DEL THEN SIGNAL Abort;
  RETURN[TRUE];
  END;

      
  Init: PROCEDURE =
    BEGIN
    t: STRING ← [20];
    Time.Append[t, Time.Unpack[ImageDefs.BcdTime[]]];
    t.length ← t.length - 9;
    IODefs.WriteString["Print of "L];
    IODefs.WriteLine[t];
      BEGIN OPEN LaurelExecDefs;
      MakeMenuCommandCallable[user];
      MakeMenuCommandCallable[newMail];
      MakeMenuCommandCallable[mailFile];
      MakeMenuCommandCallable[display];
      MakeMenuCommandCallable[delete];
      MakeMenuCommandCallable[undelete];
      MakeMenuCommandCallable[moveTo];
      MakeMenuCommandCallable[copy];
      END;
    END;
    
  SayPError: PROCEDURE[code: PrintDefs.PErrorCode] =
    BEGIN OPEN PrintDefs;
    IODefs.WriteChar[Ascii.CR];
    IODefs.WriteString["PRINT ERROR: "L];
    SELECT code FROM
     BadParameters => IODefs.WriteLine["BadParameters"L];
     InternalError => IODefs.WriteLine["InternalError"L];
     MoreThan16Fonts => IODefs.WriteLine["MoreThan16Fonts"L];
     ELBufferOverflow => IODefs.WriteLine["ELBufferOverflow"L];
     PartBufferOverflow => IODefs.WriteLine["PartBufferOverflow"L];
     UserCmMixup => IODefs.WriteLine["UserCmMixup"L];
     FontNotInFontsDotWidths => IODefs.WriteLine["FontNotInFontsDotWidths"L];
     ErrorReadingFontWidths => IODefs.WriteLine["ErrorReadingFontWidths"L];
     FileNotPressFormat => IODefs.WriteLine["FileNotPressFormat"L];
     PressThisFileError => IODefs.WriteLine["ErrorPressingFile"L];
     InputFileError => IODefs.WriteLine["InputFileError"L];
     ENDCASE => IODefs.WriteLine["UnknownError"L];
    END;
 
  DestroyS: PUBLIC PROCEDURE[s: csD.StreamHandle] RETURNS [csD.StreamHandle] =
    BEGIN
    IF s # NIL THEN csD.Destroy[s];
    RETURN[NIL];
    END;
 
  CloseF: PUBLIC PROCEDURE[f: VMDefs.FileHandle] RETURNS [VMDefs.FileHandle] =
    BEGIN
    IF f # NIL THEN [] ← Core.Close[f];
    RETURN[NIL];
    END;
  
  SBFault: PROCEDURE =
    BEGIN
    IODefs.WriteChar[Ascii.CR];
    IODefs.WriteLine["StringBoundsFault"L]; SIGNAL Abort;
    END;
 

  -- Main body
  
  printTempted: BOOLEAN ← FALSE;
  pTempHandle: VMDefs.FileHandle ← NIL;
  --swEC: ovD.ErrorCode;--
  --swPage: crD.PageNumber ← 0;--
  --swByte: crD.PageByte ← 0;--
  --[swEC, swPage, swByte] ← GetSwatee[];----use Swatee as backing file--
  Init[];
  Command[ ! Abort => {IODefs.WriteLine["Aborted"L]; CONTINUE;};
             PrintDefs.PError => {SayPError[code]; CONTINUE;};];
  outputStream ← DestroyS[outputStream];
  PrintDefs.FinishPrintStorage[];
  IF ~debugging THEN
    { --RestoreSwatee[swEC, swPage, swByte];--
      IF printTempted THEN {
         Core.Login[@intCommon.user];
         pTempHandle ← Core.Open["PRINT.TEMP$"L, update];
         Core.Delete[pTempHandle];
       };
    };
  END...




GetSwatee: PROCEDURE RETURNS[swatEC: ovD.ErrorCode, 
                               swatlastPage: crD.PageNumber ←0,
                               swatbyteFF: crD.PageByte ←0]  =
    BEGIN
    swatfh: crD.UFileHandle;
    [swatEC, swatfh] ← crD.OpenFile[intCommon.user, "Swatee"L, update];
    IF swatfh#NIL THEN [ , swatlastPage, swatbyteFF] ← crD.UFileLength[swatfh];
    [] ← crD.CloseFile[swatfh];--noop if handle is NIL
    END;
    
  RestoreSwatee: PROCEDURE [swatEC: ovD.ErrorCode, lastPage: crD.PageNumber,
                            byteFF: crD.PageByte]  =
    BEGIN
    swatfh: crD.UFileHandle;
    ec: ovD.ErrorCode;
    [ec, swatfh] ← crD.OpenFile[intCommon.user, "Swatee"L, update];
    IF ec = ovD.ok AND swatEC = ovD.ok THEN []←crD.UFileTruncate[lastPage, byteFF, swatfh];
    [] ← crD.CloseFile[swatfh];--noop if handle is NIL
    END;