-- Presser.mesa; edit by Johnsson; June 26, 1980  2:33 PM
-- Converted to Laurel by Ken Pier, July 29, 1981  10:03 PM 
-- Last Edited by Pier,  August 10, 1981  8:16 PM  

DIRECTORY
  csD: FROM "CoreStreamDefs",
  InlineDefs USING [LongDiv, LongMult, HighByte, LowByte],
  MiscDefs USING [SetBlock, Zero],
  Press USING [
    defaultBottom, defaultCharWidth, defaultHeight, defaultLeft,
    defaultLineHeight, defaultLineLeading, defaultRight, defaultTabSpacing,
    defaultTop, defaultWidth, FontIndex, FontSlope, FontWeight,
    magicNonPrintingWidth, Mica, Mode, numberOfFonts, pageHeight, pageWidth,
    Points, pointsPerInch],
  PressFormat USING [
    BYTE, DDV, EFont, ENop, EResetSpace, ESetX, ESetY, EShow, EShowRectangle,
    EShowShort, ESpaceX, ESpaceXShort, ESpaceY, ESpaceYShort, FE, LCToDouble,
    Mica, micasPerInch, PartType, PE, PETypeFont, PETypePage, PressPasswd],
  PressUtilities USING [FindFontWidths],
  StringDefs USING [MesaToBcplString, AppendDecimal, AppendString],
  SystemDefs USING [
    AllocateHeapNode, AllocateHeapString, AllocateSegment, FreeHeapNode,
    FreeHeapString, FreeSegment],
  PrintDefs USING[PError],
  TimeDefs USING [
    AppendDayTime, UnpackDT, DefaultTime, PackedTime, CurrentDayTime];

Presser: PROGRAM
  IMPORTS
    PrintDefs, InlineDefs, MiscDefs, PressFormat,
    PressUtilities, csD, StringDefs, SystemDefs, TimeDefs
  EXPORTS Press =
  BEGIN OPEN Press, PressFormat;
  
  wppr: CARDINAL = 256; -- words per press record
  
  bppr: CARDINAL = 2*wppr;
  
  recordsPerEntityList: CARDINAL = 10;
  wordsPerEntityList: CARDINAL = recordsPerEntityList*wppr;
  bytesPerEntityList: CARDINAL = recordsPerEntityList*bppr;
  recordsPerItemList: CARDINAL = 2;
  wordsPerItemList: CARDINAL = recordsPerItemList*wppr;
  
  BYTE: TYPE = [0..377B];
  
  CR: CHARACTER = 15C;
  FF: CHARACTER = 14C;
  SP: CHARACTER = 40C;
  TAB: CHARACTER = 11C;
  
  onePoint: Mica = micasPerInch/pointsPerInch;
  
  -- big buffers
  
  EntityIndex: TYPE = [0..bytesPerEntityList);
  entityList: POINTER TO PACKED ARRAY EntityIndex OF BYTE ← NIL;
  entityIndex: EntityIndex;
  partsPerRecord: CARDINAL = wppr/SIZE[PressFormat.PE];
  
  partsPerDocument: CARDINAL = recordsPerItemList*partsPerRecord;
  PartIndex: TYPE = [0..partsPerDocument); -- is 127 pages/doc enough?
  partList: POINTER TO ARRAY PartIndex OF PressFormat.PE ← NIL;
  partIndex: PartIndex ← 0;
  
  UserFontInfo: TYPE = RECORD [
    family: STRING, -- mostly for debugging
    name: FamilyName, -- BCPL string body in caps
    size: Points,
    -- pointers below are NIL if not used yet
    portrait, landscape: ARRAY FontFace OF POINTER TO PressFontInfo];
  
  WidthArray: TYPE = ARRAY CHARACTER OF Mica;
  
  PressFontInfo: TYPE = RECORD [
    index: FontIndex, -- the one we feed to press
    face: FontFace,
    fBBox, fBBoy, width, height: Mica ← NULL,
    widths: POINTER TO WidthArray, -- NIL if not used yet
    rotation: CARDINAL,
    user: POINTER TO UserFontInfo]; -- to build Font Directory
  
  FamilyName: TYPE = PACKED ARRAY [0..20) OF BYTE;
  FontFace: TYPE = [0..2*2); -- should really be 2*3*3, See FontFormats memo
  
  initialized: BOOLEAN ← FALSE;
  
  Initialize: PUBLIC PROCEDURE =
    BEGIN
    i: FontIndex;
    Reset[];
    userFonts ← SystemDefs.AllocateHeapNode[numberOfFonts*SIZE[UserFontInfo]];
    pressFonts ← SystemDefs.AllocateHeapNode[numberOfFonts*SIZE[PressFontInfo]];
    FOR i IN FontIndex DO
      userFonts[i] ← [NIL, , 0, [NIL, NIL, NIL, NIL], [NIL, NIL, NIL, NIL]];
      pressFonts[i] ← [0, 0, 0, 0, 0, 0, NIL, 0, NIL];
      ENDLOOP;
    entityList ← SystemDefs.AllocateSegment[wordsPerEntityList];
    MiscDefs.Zero[entityList, wordsPerEntityList];
    partList ← SystemDefs.AllocateSegment[wordsPerItemList];
    MiscDefs.Zero[partList, wordsPerItemList];
    initialized ← TRUE;
    END;
    
  Reset: PUBLIC PROCEDURE =
    BEGIN OPEN SystemDefs;
    i: FontIndex;
    active ← FALSE;
    KillString[@pageHeader];
    KillString[@pageTrailer];
    KillString[@documentFileName];
    KillString[@documentUserName];
    KillString[@documentCreationDate];
    IF userFonts # NIL THEN
      BEGIN
      FOR i IN FontIndex DO KillString[@userFonts[i].family]; ENDLOOP;
      FreeHeapNode[userFonts];
      userFonts ← NIL;
      END;
    IF pressFonts # NIL THEN
      BEGIN FlushFontBuffers[]; FreeHeapNode[pressFonts]; pressFonts ← NIL; END;
    IF entityList # NIL THEN BEGIN FreeSegment[entityList]; entityList ← NIL; END;
    IF partList # NIL THEN BEGIN FreeSegment[partList]; partList ← NIL; END;
    pageActive ← paperActive ← active ← initialized ← FALSE;
    END;
    
  lineLeading: Mica ← defaultLineLeading;
  tabWidth: Mica ← defaultTabSpacing;
  
  vSpaceWidth: BOOLEAN ← FALSE; -- TRUE after SetWidthOfSpace
  
  spaceWidth: Mica;
  
  documentFileName: STRING ← NIL; -- max length 51
  
  documentUserName: STRING ← NIL; -- max length 31
  
  documentCreationDate: STRING ← NIL; -- max length 39
  
  pageHeader, pageTrailer: STRING ← NIL;
  headerPageNumbers, trailerPageNumbers: BOOLEAN ← FALSE;
  numberOfCopies: CARDINAL ← 1;
  
  SetDocumentCreationDate: PUBLIC PROCEDURE [date: STRING] =
    {MakeMeACopy[@documentCreationDate, date]};
    
  SetDocumentUserName: PUBLIC PROCEDURE [user: STRING] =
    {MakeMeACopy[@documentUserName, user]};
    
  SetHeaderText: PUBLIC PROCEDURE [header: STRING, pageNumbers: BOOLEAN] =
    {MakeMeACopy[@pageHeader, header]; headerPageNumbers ← pageNumbers};
    
  SetTrailerText: PUBLIC PROCEDURE [trailer: STRING, pageNumbers: BOOLEAN] =
    {MakeMeACopy[@pageTrailer, trailer]; trailerPageNumbers ← pageNumbers};
    
  SetNumberOfCopies: PUBLIC PROCEDURE [copies: CARDINAL] =
    {numberOfCopies ← copies};
    
  PutFontInTable: PUBLIC PROCEDURE [
    index: FontIndex, family: STRING, size: Points] =
    BEGIN
    IF userFonts = NIL THEN PrintDefs.PError[BadParameters];
    IF index~ IN FontIndex THEN PrintDefs.PError[BadParameters];
    IF family.length~ IN [1..19] THEN PrintDefs.PError[BadParameters]; -- only 20 bytes words
    MakeMeACopy[@userFonts[index].family, family];
    userFonts[index].size ← size;
    FOR i: CARDINAL IN [0..20) DO userFonts[index].name[i] ← 0; ENDLOOP;
    userFonts[index].name[0] ← family.length;
    FOR i: CARDINAL IN [0..family.length) DO
      SELECT family[i] FROM
	IN ['A..'Z], IN ['0..'9] =>
	  userFonts[index].name[i + 1] ← LOOPHOLE[family[i]];
	IN ['a..'z] =>
	  userFonts[index].name[i + 1] ← LOOPHOLE[family[i], BYTE] - 40B;
	ENDCASE => PrintDefs.PError[BadParameters];
      ENDLOOP;
    END;
    
  SetMargins: PUBLIC PROCEDURE [l, r, t, b: Mica] =
    BEGIN
    IF pageActive THEN PrintDefs.PError[BadParameters];
    leftMargin ← l;
    rightMargin ← r;
    topMargin ← t;
    bottomMargin ← b;
    IF landscape THEN
      BEGIN
      height ← pageWidth - rightMargin - leftMargin;
      width ← pageHeight - topMargin - bottomMargin;
      END
    ELSE
      BEGIN
      height ← pageHeight - topMargin - bottomMargin;
      width ← pageWidth - rightMargin - leftMargin;
      END;
    width ← width - (numberOfColumns - 1)*(spaceBetweenColumns);
    width ← LOOPHOLE[width, CARDINAL]/numberOfColumns;
    END;
    
  SetCurrentTabWidth: PUBLIC PROCEDURE [tab: Mica] = {tabWidth ← tab};
    
  SetCurrentLineLeading: PUBLIC PROCEDURE [lead: Mica] =
    BEGIN lineLeading ← lead; END;
    
  active: BOOLEAN ← FALSE;
  
  pressFile: csD.StreamHandle ← NIL;
  
  Start: PUBLIC PROCEDURE [docName: STRING, file: csD.StreamHandle] =
    BEGIN
    IF active THEN RETURN;
    IF ~initialized THEN Initialize[];
    pressFile ← file;
    MakeMeACopy[@documentFileName, docName];
    IF documentCreationDate = NIL THEN
      BEGIN OPEN TimeDefs;
      time: STRING = [18];
      AppendDayTime[time, UnpackDT[DefaultTime]];
      MakeMeACopy[@documentCreationDate, time];
      END;
    IF documentUserName = NIL THEN MakeMeACopy[@documentUserName, "NoName"L];
    IF documentFileName = NIL THEN MakeMeACopy[@documentFileName, "NoName"L];
    IF documentFileName.length > 51 THEN documentFileName.length ← 51;
    currentRecordNumber ← 0;
    csD.Reset[pressFile];
    entityIndex ← partIndex ← 0;
    FOR i: FontIndex IN FontIndex DO
      userFonts[i].portrait ← [NIL, NIL, NIL, NIL];
      userFonts[i].landscape ← [NIL, NIL, NIL, NIL];
      ENDLOOP;
    currentPageNumber ← 1;
    lineActive ← pageActive ← paperActive ← FALSE;
    currentX ← 0;
    currentY ← height;
    BeSureFontZeroExists[];
    active ← TRUE;
    END;
    
  Finish: PUBLIC PROCEDURE =
    BEGIN
    fd: POINTER = entityList; -- build Font Directory in random buffer
    fp: POINTER TO PressFormat.FE ← fd;
    dd: POINTER TO PressFormat.DDV = fd;
    -- build Document Directory in random buffer
    now: TimeDefs.PackedTime ← TimeDefs.CurrentDayTime[];
    numberOfPartRecords, firstPartRecord: CARDINAL;
    IF lineActive THEN EndCurrentLine[];
    IF pageActive THEN EndCurrentPage[];
    IF paperActive THEN EndCurrentPaper[];
    -- send Font Directory
    MiscDefs.Zero[fd, wppr];
    firstDataRecordOfPaper ← currentRecordNumber; -- for AppendPartItem
    FOR i: CARDINAL IN FontIndex DO
      IF pressFonts[i].user # NIL THEN
	BEGIN
	fp↑ ← PressFormat.FE[
	  length: SIZE[PressFormat.FE], set: 0, -- we only use one font set
	  fno: pressFonts[i].index, destm: 0, destn: 177B,
	  fam: pressFonts[i].user.name, face: pressFonts[i].face, source: 0,
	  siz: pressFonts[i].user.size, rotn: pressFonts[i].rotation];
	fp ← fp + SIZE[PressFormat.FE];
	IF fp = fd + wppr THEN -- opps, record exactly full
	  BEGIN
	  [] ← csD.WriteBlock[pressFile, fd, 0, bppr];
	  MiscDefs.Zero[fd, wppr];
	  fp ← fd;
	  END;
	END;
      ENDLOOP;
    [] ← csD.WriteBlock[pressFile, fd, 0, bppr];
    AppendPartItem[PressFormat.PETypeFont, 0];
    -- send off Part Directory
    firstPartRecord ← currentRecordNumber;
    numberOfPartRecords ← (partIndex*SIZE[PressFormat.PE] + wppr - 1)/wppr;
    [] ← csD.WriteBlock[pressFile, partList, 0, numberOfPartRecords*bppr];
    -- send off Document Directory - use entity buffer
    MiscDefs.SetBlock[p: dd, l: wppr, v: -1];
    dd.Passwd ← PressFormat.PressPasswd; -- General Password
    dd.nRecs ← firstPartRecord + numberOfPartRecords + 1;
    -- total number of records
    dd.nParts ← partIndex;
    dd.pdStart ← firstPartRecord;
    dd.pdRecs ← numberOfPartRecords;
    dd.Backp ← 0; --  ?? funny backpointer
    dd.date ← PressFormat.LCToDouble[now];
    dd.fCopy ← 1;
    dd.lCopy ← numberOfCopies; -- first, last copy
    dd.fPage ← 1;
    dd.lPage ← 0; -- first, last page
    StringDefs.MesaToBcplString[documentFileName, LOOPHOLE[@dd.FileStr]];
    StringDefs.MesaToBcplString[documentUserName, LOOPHOLE[@dd.CreatStr]];
    StringDefs.MesaToBcplString[documentCreationDate, LOOPHOLE[@dd.DateStr]];
    currentRecordNumber ← currentRecordNumber + 1;
    [] ← csD.WriteBlock[pressFile, dd, 0, bppr];
    csD.Checkpoint[pressFile];--flush everything to the disk
    active ← FALSE;
    END;
    
  Abort: PUBLIC PROCEDURE = {active ← FALSE};
    
  String: PUBLIC PROCEDURE [s: STRING] =
    {FOR i: CARDINAL IN [0..s.length) DO Character[s[i]] ENDLOOP};
    
  PieceOfLine: PUBLIC PROCEDURE [s: STRING, width: Mica] =
    BEGIN
    IF ~lineActive THEN
      BEGIN
      -- If we switch to a taller font, this test might miss.
      IF pageActive AND currentY < (currentFontPointer.height + lineLeading) THEN
	DoPageOverflow[];
      BeginLine[];
      END;
    FOR i: CARDINAL IN [0..s.length) DO csD.Write[pressFile, s[i]]; ENDLOOP;
    lineCharacters ← lineCharacters + s.length;
    currentX ← currentX + width;
    END;
    
  GetWidthOfString: PUBLIC PROCEDURE [s: STRING] RETURNS [w: Mica] =
    BEGIN
    w ← 0;
    FOR i: CARDINAL IN [0..s.length) DO
      w ← w + GetWidthOfCharacter[s[i]]; ENDLOOP;
    END;
    
  GetWidthOfCharacter: PUBLIC PROCEDURE [c: CHARACTER] RETURNS [w: Mica] =
    BEGIN
    IF vSpaceWidth AND c = SP THEN RETURN[spaceWidth];
    w ← currentFontPointer.widths[c];
    IF w = magicNonPrintingWidth THEN w ← 0;
    END;
    
  Character: PUBLIC PROCEDURE [c: CHARACTER] =
    BEGIN
    charWidth: Mica;
    SELECT c FROM
      CR => DoCR[];
      FF => DoFF[];
      TAB => DoTAB[];
      ENDCASE =>
	BEGIN
	IF vSpaceWidth AND c = SP THEN charWidth ← spaceWidth
	ELSE charWidth ← currentFontPointer.widths[c];
	IF charWidth = magicNonPrintingWidth THEN charWidth ← 0;
	IF ~lineActive THEN
	  BEGIN
	  -- If we switch to a taller font, this test might miss.
	  IF pageActive AND currentY < (currentFontPointer.height + lineLeading)
	    THEN DoPageOverflow[];
	  BeginLine[];
	  END;
	IF (currentX + charWidth) > width AND c # SP THEN DoLineOverflow[];
	csD.Write[pressFile, c];
	lineCharacters ← lineCharacters + 1;
	currentX ← currentX + charWidth;
	END;
    END;
    
  DoTAB: PROCEDURE =
    BEGIN
    IF ~lineActive AND currentY < (currentFontPointer.height + lineLeading) THEN
      DoPageOverflow[]; -- else this TAB gets lost
    SkipSomeSpace[(((currentX + 20)/tabWidth) + 1)*tabWidth - currentX];
    END;
    
  SkipSomeSpace: PUBLIC PROCEDURE [mica: Mica] =
    BEGIN
    IF mica = 0 THEN RETURN;
    IF ~pageActive THEN BeginPage[];
    IF lineActive THEN FlushBuffer[]
    ELSE
      IF currentY < (currentFontPointer.height + lineLeading) THEN
	DoPageOverflow[];
    currentX ← currentX + mica;
    IF currentX > width THEN BEGIN DoLineOverflow[]; RETURN; END;
    IF ~lineActive THEN RETURN; -- BeginLine will set position
    -- TAB - TAB will generate an extra Set-?
    IF landscape THEN AppendEntityByte[ESetY] ELSE AppendEntityByte[ESetX];
    AppendEntityWord[currentX];
    END;
    
  DoCR: PROCEDURE =
    BEGIN
    IF ~pageActive THEN BeginPage[];
    IF lineActive THEN EndCurrentLine[];
    currentX ← 0;
    currentY ← currentY - lineHeight - lineLeading;
    END;
    
  DoLineOverflow: PROCEDURE =
    BEGIN
    Character[CR];
    -- Leave line active, but overflow test is done by PrintCharacter
    String["**"];
    END;
    
  DoFF: PROCEDURE = 
    -- FF, FF will get you an empty page
    {IF ~pageActive THEN BeginPage[]; EndCurrentPage[]};
    
  DoPageOverflow: PROCEDURE = {EndCurrentPage[]; BeginPage[]};
    
   -- leave page active
  
  landscape: BOOLEAN ← FALSE;
  numberOfColumns: CARDINAL ← 1;
  spaceBetweenColumns: Mica ← 0;
  
  height: Mica ← defaultHeight;
  width: Mica ← defaultWidth;
  
  currentX, currentY: Mica ← 0;
  
  leftMargin: Mica ← defaultLeft;
  rightMargin: Mica ← defaultRight;
  topMargin: Mica ← defaultTop;
  bottomMargin: Mica ← defaultBottom;
  
  SetMode: PUBLIC PROCEDURE [columns: CARDINAL, between: Mica, mode: Mode] =
    BEGIN
    numberOfColumns ← columns;
    spaceBetweenColumns ← between;
    SELECT mode FROM
      portrait =>
	BEGIN
	landscape ← FALSE;
	height ← pageHeight - topMargin - bottomMargin;
	width ← pageWidth - rightMargin - leftMargin;
	END;
      landscape =>
	BEGIN
	landscape ← TRUE;
	height ← pageWidth - rightMargin - leftMargin;
	width ← pageHeight - topMargin - bottomMargin;
	END;
      ENDCASE => PrintDefs.PError[PageModeError];
    width ← width - (numberOfColumns - 1)*(spaceBetweenColumns);
    width ← LOOPHOLE[width, CARDINAL]/numberOfColumns;
    END;
    
  SetCurrentPosition: PUBLIC PROCEDURE [x, y: Mica] =
    BEGIN
    IF ~pageActive THEN BeginPage[];
    IF lineActive THEN EndCurrentLine[];
    currentX ← x;
    currentY ← y;
    END;
    
  GetCurrentPageNumber: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    {RETURN[currentPageNumber]};
    
  SetCurrentPageNumber: PUBLIC PROCEDURE [pn: CARDINAL] =
    {currentPageNumber ← pn};
    
  GetCurrentPosition: PUBLIC PROCEDURE RETURNS [x, y: Mica] =
    {IF ~pageActive THEN BeginPage[]; RETURN[currentX, currentY]};
    
  SetWidthOfSpace: PUBLIC PROCEDURE [w: Mica] =
    BEGIN
    IF ~pageActive THEN BeginPage[];
    IF lineActive THEN EndCurrentLine[];
    IF w < 2048 THEN
      AppendEntityWord[
	(IF landscape THEN ESpaceYShort ELSE ESpaceXShort)*400B + w]
    ELSE
      BEGIN
      AppendEntityByte[IF landscape THEN ESpaceY ELSE ESpaceX];
      AppendEntityWord[w];
      END;
    vSpaceWidth ← TRUE;
    spaceWidth ← w;
    END;
    
  ResetWidthOfSpace: PUBLIC PROCEDURE =
    BEGIN
    IF ~pageActive THEN BeginPage[];
    IF lineActive THEN EndCurrentLine[];
    AppendEntityByte[EResetSpace];
    vSpaceWidth ← FALSE;
    END;
    
  DrawRectangle: PUBLIC PROCEDURE [w, h: Mica] =
    BEGIN
    IF ~pageActive THEN BeginPage[];
    IF lineActive THEN EndCurrentLine[];
    AppendEntityByte[EShowRectangle];
    AppendEntityWord[IF landscape THEN h ELSE w];
    AppendEntityWord[IF landscape THEN w ELSE h];
    END;
    
  -- lineActive means that we don't have to do a Set-x and Set-y
  -- lineCharacters#0 means that we need to do a Show-characters
  -- We don't know how high the line is until we have seen it go past.
  -- This kludge remembers the entityIndex for the Y data word so we can fix it later.
  -- When we switch fonts, we save the height of the tallest one.
  
  lineHeight: Mica ← defaultLineHeight;
  fixupXIndex, fixupYIndex: CARDINAL ← 0;
  
  lineActive: BOOLEAN ← FALSE;
  lineCharacters: CARDINAL ← 0;
  
  BeginLine: PROCEDURE =
    BEGIN
    IF ~pageActive THEN BeginPage[];
    lineCharacters ← 0;
    lineHeight ← currentFontPointer.height;
    AppendEntityByte[ESetX];
    fixupXIndex ← entityIndex;
    AppendEntityWord[IF landscape THEN height - currentY ELSE currentX];
    AppendEntityByte[ESetY];
    fixupYIndex ← entityIndex;
    AppendEntityWord[IF landscape THEN currentX ELSE currentY];
    lineActive ← TRUE;
    END;
    
  -- called by SkipSomeSpace, EndCurrentLine, BeginPage (headers), and SetCurrentFont
  
  FlushBuffer: PROCEDURE =
    BEGIN
    SELECT lineCharacters FROM
      0 => RETURN;
      IN [1..40B] => BEGIN AppendEntityByte[EShowShort + lineCharacters - 1]; END;
      IN [40B..400B] =>
	BEGIN AppendEntityByte[EShow]; AppendEntityByte[lineCharacters]; END;
      ENDCASE => PrintDefs.PError[InternalError];
    paperCharacters ← paperCharacters + lineCharacters;
    pageCharacters ← pageCharacters + lineCharacters;
    lineCharacters ← 0;
    END;
    
  EndCurrentLine: PROCEDURE =
    BEGIN
    temp: INTEGER;
    IF ~lineActive THEN PrintDefs.PError[InternalError];
    FlushBuffer[];
    temp ← currentY - lineHeight - lineLeading;
    IF landscape THEN
      BEGIN
      temp ← height - currentY;
      entityList[fixupXIndex] ← InlineDefs.HighByte[temp];
      entityList[fixupXIndex + 1] ← InlineDefs.LowByte[temp];
      END
    ELSE
      BEGIN
      entityList[fixupYIndex] ← InlineDefs.HighByte[temp];
      entityList[fixupYIndex + 1] ← InlineDefs.LowByte[temp];
      END;
    lineActive ← FALSE;
    END;
    
  pageActive: BOOLEAN ← FALSE;
  firstEntityIndexOfPage: CARDINAL ← 0;
  firstPageCharacter, pageCharacters: CARDINAL ← 0;
  currentPageNumber: CARDINAL ← 0;
  currentColumn: CARDINAL ← 0; -- starts at 0
  
  
  BeginPage: PROCEDURE =
    BEGIN
    IF pageActive THEN PrintDefs.PError[InternalError];
    IF ~paperActive THEN BeginPaper[];
    firstEntityIndexOfPage ← entityIndex;
    firstPageCharacter ← paperCharacters;
    pageCharacters ← 0;
    pageActive ← TRUE;
    Headers[];
    currentX ← 0;
    currentY ← height;
    IF currentFontPointer.index~ IN FontIndex THEN
						PrintDefs.PError[InternalError];
    IF currentFontPointer.index # 0 THEN
      AppendEntityByte[EFont + currentFontPointer.index];
    END;
    
  EndCurrentPage: PROCEDURE =
    BEGIN
    cardinal: CARDINAL;
    header, trailer: Mica ← 0; -- fudge for headers/trailers
    IF ~pageActive THEN PrintDefs.PError[InternalError];
    IF lineActive THEN EndCurrentLine[];
    Trailers[];
    pageActive ← FALSE;
    -- header must start on a word boundry
    IF (entityIndex MOD 2) = 1 THEN AppendEntityByte[ENop];
    -- build entity header
    AppendEntityWord[0]; -- type and font-set
    AppendEntityWord[0];
    AppendEntityWord[firstPageCharacter]; -- begin byte
    AppendEntityWord[0];
    AppendEntityWord[pageCharacters]; -- length
    IF landscape THEN
      BEGIN -- Xe, Ye
      AppendEntityWord[leftMargin];
      AppendEntityWord[
	bottomMargin + currentColumn*(width + spaceBetweenColumns)];
      IF pageHeader # NIL THEN header ← 3*heightOfHeaderLine;
      IF pageTrailer # NIL THEN trailer ← 3*heightOfHeaderLine;
      AppendEntityWord[-header];
      AppendEntityWord[0]; -- left, bottom
      AppendEntityWord[height + header + trailer]; -- width
      AppendEntityWord[width]; -- height
      
      END
    ELSE
      BEGIN -- Xe, Ye
      AppendEntityWord[leftMargin + currentColumn*(width + spaceBetweenColumns)];
      AppendEntityWord[bottomMargin];
      IF pageHeader # NIL THEN header ← 3*heightOfHeaderLine;
      IF pageTrailer # NIL THEN trailer ← 3*heightOfHeaderLine;
      AppendEntityWord[0];
      AppendEntityWord[-trailer]; -- left, bottom
      AppendEntityWord[width]; -- width
      AppendEntityWord[height + header + trailer]; -- height
      
      END;
    cardinal ← entityIndex - firstEntityIndexOfPage;
    AppendEntityWord[1 + cardinal/2]; -- entity-length
    currentPageNumber ← currentPageNumber + 1;
    IF (currentColumn ← currentColumn + 1) = numberOfColumns THEN
      EndCurrentPaper[];
    END;
    
  Headers: PROCEDURE =
    BEGIN
    buffer: STRING = [20];
    fontPointer: POINTER TO PressFontInfo ← currentFontPointer;
    -- Each new page starts out in font 0, but we have to switch the pointer so that the width calculations work out ok.  If not, and headers are in a bigger font than the current font, the page number will overflow its line.
    IF pageHeader = NIL THEN RETURN;
    currentFontPointer ← fontZeroPointer;
    currentX ← 0;
    currentY ← height + 3*heightOfHeaderLine;
    BeginLine[];
    String[pageHeader];
    FlushBuffer[];
    StringDefs.AppendDecimal[buffer, currentPageNumber];
    SkipSomeSpace[width - buffer.length*widthOfHeaderDigit - currentX];
    String[buffer];
    EndCurrentLine[];
    currentFontPointer ← fontPointer;
    END;
    
  Trailers: PROCEDURE =
    BEGIN
    buffer: STRING = [20];
    fontPointer: POINTER TO PressFontInfo ← currentFontPointer;
    IF pageTrailer = NIL THEN RETURN;
    IF currentFontPointer.index # 0 THEN AppendEntityByte[EFont + 0]; -- font 0
    currentFontPointer ← fontZeroPointer;
    currentX ← 0;
    currentY ← 0 - 2*heightOfHeaderLine;
    BeginLine[];
    String[pageTrailer];
    FlushBuffer[];
    StringDefs.AppendDecimal[buffer, currentPageNumber];
    SkipSomeSpace[width - buffer.length*widthOfHeaderDigit - currentX];
    String[buffer];
    EndCurrentLine[];
    currentFontPointer ← fontPointer;
    END;
    
  paperActive: BOOLEAN ← FALSE;
  currentRecordNumber: CARDINAL ← 0;
  firstDataRecordOfPaper: CARDINAL ← 0;
  paperCharacters: CARDINAL ← 0;
  
  BeginPaper: PROCEDURE =
    BEGIN
    IF paperActive THEN PrintDefs.PError[InternalError];
    MiscDefs.Zero[entityList, wordsPerEntityList];
    entityIndex ← 0;
    AppendEntityWord[0]; -- marker
    firstDataRecordOfPaper ← currentRecordNumber;
    paperActive ← TRUE;
    paperCharacters ← 0;
    currentColumn ← 0;
    END;
    
  EndCurrentPaper: PROCEDURE =
    BEGIN
    pad: CARDINAL;
    pb: RECORD[page: CARDINAL, byte: [0..777B]];
    IF pageActive THEN EndCurrentPage[];
    [pb.page, pb.byte] ← csD.MapPositionToPageByte[csD.GetPosition[pressFile]];
    THROUGH [0..2 + (pb.byte MOD 2)) DO 
      -- word boundary + 2 zeros
      csD.Write[pressFile, 0]
      ENDLOOP;
    [] ← csD.WriteBlock[pressFile, entityList, 0, entityIndex];
    [pb.page, pb.byte] ← csD.MapPositionToPageByte[csD.GetPosition[pressFile]];
    pad ← wppr - pb.byte/2;
    THROUGH [0..pad) DO
      csD.Write[pressFile, 0]; csD.Write[pressFile, 0]; ENDLOOP;
    AppendPartItem[PressFormat.PETypePage, pad];
    paperActive ← FALSE;
    END;
    
  userFonts: POINTER TO ARRAY FontIndex OF UserFontInfo ← NIL;
  pressFonts: POINTER TO ARRAY FontIndex OF PressFontInfo ← NIL;
  
  currentFontPointer: POINTER TO PressFontInfo ← NIL;
  
  SetCurrentFont: PUBLIC PROCEDURE [
    font: FontIndex, w: FontWeight, s: FontSlope] =
    BEGIN
    ff: FontFace ← 0;
    new: POINTER TO PressFontInfo;
    IF ~initialized THEN PrintDefs.PError[BadParameters];
    IF font~ IN FontIndex OR userFonts[font].family = NIL THEN
      PrintDefs.PError[BadParameters];
    SELECT w FROM
      medium => ff ← ff + 0;
      bold => ff ← ff + 2;
      --light => ff ← ff+4;
      
      ENDCASE => PrintDefs.PError[BadParameters];
    SELECT s FROM
      regular => ff ← ff + 0;
      italic => ff ← ff + 1;
      ENDCASE => PrintDefs.PError[BadParameters];
    --SELECT expansion FROM
    --  regular => ff ← ff+0;
    --  condensed => ff ← ff+6;
    --  expanded => ff ← ff+12;
    --  ENDCASE => PrintDefs.PError[BadParameters];
    IF ~landscape THEN
      BEGIN
      IF userFonts[font].portrait[ff] = NIL THEN FindPressSlot[font, ff, w, s];
      new ← userFonts[font].portrait[ff];
      END
    ELSE
      BEGIN
      IF userFonts[font].landscape[ff] = NIL THEN FindPressSlot[font, ff, w, s];
      new ← userFonts[font].landscape[ff];
      END;
    IF new = currentFontPointer THEN RETURN;
    currentFontPointer ← new;
    IF lineActive THEN FlushBuffer[];
    IF currentFontPointer.index~ IN FontIndex THEN
				PrintDefs.PError[InternalError];
    IF ~pageActive THEN RETURN;
    AppendEntityByte[EFont + currentFontPointer.index]; -- Font
    lineHeight ← MAX[lineHeight, currentFontPointer.height];
    END;
    
  FindPressSlot: PROCEDURE [
    font: FontIndex, ff: FontFace, w: FontWeight, s: FontSlope] =
    BEGIN
    i: FontIndex;
    pf: POINTER TO PressFontInfo;
    rot: CARDINAL ← IF landscape THEN 60*90 ELSE 0;
    family: STRING = userFonts[font].family;
    points: Points = userFonts[font].size;
    FOR i IN FontIndex DO
      pf ← @pressFonts[i];
      IF pf.user = NIL THEN EXIT; -- empty slot - use it
      IF pf.user = @userFonts[font] AND pf.face = ff AND pf.rotation = rot THEN
	EXIT;
      REPEAT FINISHED => PrintDefs.PError[MoreThan16Fonts];
      ENDLOOP;
    IF pf.user = NIL THEN
      BEGIN OPEN pf;
      pf↑ ←
	[index: i, face: ff, widths: SystemDefs.AllocateSegment[SIZE[WidthArray]],
	  rotation: rot, user: @userFonts[font]];
      -- initialize to something legal
      height ← PointsToMicas[userFonts[font].size];
      width ← PointsToMicas[userFonts[font].size];
      pressFonts[i].widths↑ ← ALL[magicNonPrintingWidth];
      [fBBox, fBBoy, width, ] ← PressUtilities.FindFontWidths[
	family, points, w, s, pf.widths];
      END;
    IF landscape THEN userFonts[font].landscape[ff] ← pf
    ELSE userFonts[font].portrait[ff] ← pf;
    END;
    
  PointsToMicas: PROCEDURE [p: Points] RETURNS [Mica] =
    BEGIN OPEN InlineDefs;
    RETURN[LongDiv[LongMult[micasPerInch, p], pointsPerInch]];
    END;
    
  heightOfHeaderLine: Mica;
  widthOfHeaderDigit: Mica;
  fontZeroPointer: POINTER TO PressFontInfo ← NIL;
  
  BeSureFontZeroExists: PROCEDURE =
    BEGIN
    fontZero: FontIndex = FIRST[FontIndex];
    i: FontIndex = FIRST[FontIndex];
    c: CHARACTER;
    ff: FontFace ← 0; -- medium, regular
    IF userFonts[fontZero].family # NIL THEN -- try for normal font 0
      BEGIN
      -- The client has already specified a font 0.
      -- Activate it now so it will be the press font 0 that we can use for page headers.
      SetCurrentFont[fontZero, medium, regular];
      fontZeroPointer ← currentFontPointer;
      heightOfHeaderLine ← currentFontPointer.height;
      widthOfHeaderDigit ← currentFontPointer.widths['0];
      RETURN;
      END;
    -- ARGH!  The idiot user didn't give us any font 0.  The default is Gacha 8.
    -- We brew up the constants so it will work without Fonts.widhts.
    PutFontInTable[fontZero, "Gacha", 8];
    pressFonts[i] ←
      [i, ff, , , , , SystemDefs.AllocateSegment[200B], , @userFonts[fontZero]];
    pressFonts[i].height ← defaultLineHeight;
    pressFonts[i].width ← defaultCharWidth;
    FOR c IN [40C..176C] DO pressFonts[i].widths[c] ← defaultCharWidth; ENDLOOP;
    IF landscape THEN
      BEGIN
      userFonts[fontZero].landscape[ff] ← @pressFonts[i];
      pressFonts[i].rotation ← 90*60;
      END
    ELSE
      BEGIN
      userFonts[fontZero].portrait[ff] ← @pressFonts[i];
      pressFonts[i].rotation ← 0;
      END;
    currentFontPointer ← @pressFonts[i];
    fontZeroPointer ← currentFontPointer;
    heightOfHeaderLine ← currentFontPointer.height;
    widthOfHeaderDigit ← currentFontPointer.widths['0];
    AppendEntityByte[EFont + i]; -- Font
    
    END;
    
  FlushFontBuffers: PUBLIC PROCEDURE =
    BEGIN
    FOR i: FontIndex IN FontIndex DO
      IF pressFonts[i].widths # NIL THEN
	SystemDefs.FreeSegment[pressFonts[i].widths];
      pressFonts[i] ← [0, 0, 0, 0, 0, 0, NIL, 0, NIL];
      ENDLOOP;
    END;
    
  AppendEntityByte: PROCEDURE [b: BYTE] =
    BEGIN
    IF entityIndex = bytesPerEntityList THEN
			PrintDefs.PError[ELBufferOverflow];
    entityList[entityIndex] ← b;
    entityIndex ← entityIndex + 1;
    END;
    
  AppendEntityWord: PROCEDURE [w: INTEGER] =
    {AppendEntityByte[InlineDefs.HighByte[w]]; AppendEntityByte[InlineDefs.LowByte[w]]};
    
  AppendPartItem: PROCEDURE [type: PressFormat.PartType, last: CARDINAL] =
    BEGIN
    pb: RECORD[page: CARDINAL, byte: [0..777B]];
    [pb.page, pb.byte] ← csD.MapPositionToPageByte[csD.GetPosition[pressFile]];
    IF partIndex = partsPerDocument THEN PrintDefs.PError[ELBufferOverflow];
    partList[partIndex] ←
      [Type: type, pStart: firstDataRecordOfPaper,
	pRecs: pb.page - firstDataRecordOfPaper, Padding: last];
    partIndex ← partIndex + 1;
    currentRecordNumber ← pb.page;
    END;
    
  KillString: PROCEDURE [where: POINTER TO STRING] =
    {IF where↑ # NIL THEN {SystemDefs.FreeHeapString[where↑]; where↑ ← NIL}};
    
  MakeMeACopy: PROCEDURE [where: POINTER TO STRING, newString: STRING] =
    BEGIN OPEN SystemDefs;
    KillString[where];
    IF newString # NIL THEN
      BEGIN
      where↑ ← AllocateHeapString[newString.length];
      StringDefs.AppendString[where↑, newString];
      END;
    END;
    
  -- initialization
  
  
  END.

LOG

--Former errors
  MoreThan16Fonts: PUBLIC ERROR = CODE;
  ELBufferOverflow: PUBLIC ERROR = CODE;
  PartBufferOverflow: PUBLIC ERROR = CODE;
  BadParameters: PUBLIC ERROR = CODE;
  InternalError: PUBLIC ERROR = CODE;

August 10, 1981  8:19 PM
Defined EntityIndex and PartIndex, fixing bugs in index types
Used InlineDefs.High/LowByte for AppendEntityWord