-- file: IntHardcopyCom.Mesa
-- edited by Brotz, March 7, 1983 4:01 PM
-- edited by Schroeder, November 21, 1980 11:48 AM
-- edited by Levin, January 16, 1981 10:55 AM.

DIRECTORY
Ascii USING [CR, DEL, SP, TAB],
dsD: FROM "DisplayDefs" USING [ClearRectangle, GetCharProperty, GetCharRightX,
GetStaticCharWidth, GetStringWidth, lineHeight, PutStringInBitMap, ScreenXCoord,
ScreenYCoord],
Editor USING [cancelCode],
exD: FROM "ExceptionDefs" USING [AppendExceptionString, cancelHardcopy,
ClearExceptionsRegion, completed, DisplayBothExceptionLines, DisplayException,
DisplayExceptionLine, DisplayExceptionStringOnLine, errorsDuringInstall,
GetExceptionString, hardcopyCanceled, hardcopyCompleted, hardcopyTo, nil,
noCurrentFile, noSelectedEntries, onePagePrinted, pagesPrinted, printing, SysBug],
inD: FROM "InteractorDefs" USING [CaretIsBlinking, ChangeCommandMenu, CharIndex,
CommandNbrPtr, CommandProcedure, ConfirmBrackets, digitWidth, HousePtr,
IndicateCommandBusy, IndicateCommandFinished, leftMargin, MakeCommandsCallable,
ScreenXCoord, ScreenYCoord, SetCaretBlinking, TOCTextNbrPtr],
Inline USING [BITAND],
intCommon USING [copiesBracketsHouse, defaultHardCopies, defaultHardcopyFormName,
displayCommandHouse, duplexBracketsHouse, exceptionsRegion, formBracketsHouse,
hardCopies, hardcopyCommandHouse, hardcopyFormTable, hardcopyHost,
hardcopyInstallError, hardcopyMenuSegment, hardcopyWidthTableSegment, keystream,
passwordPrinting, passwordPrintingDefault, printerBracketsHouse, target, tocCommandNbr,
TOCCommandRegion, tocTextNbr, twoSidedPrinting, twoSidedPrintingDefault],
LaurelHardcopyDefs USING [ClosePressStreams, Column, ColumnNIL, ColumnRec,
ColumnRelPtr, FindPrinter, FinishDiabloPage, FinishPressFile, FinishPressPage,
FontNumber, HardcopyAbortCode, HardcopyForm, HardcopyFormTable, InitPressPage,
LineSegmentTable, magicNonPrintingWidth, Mica, micasPerDiabloY, NewDiabloPage,
OpenPressStreams, Option, OptionNIL, OptionRec, OptionRelPtr, postColonSpacing,
PrintDiabloString, PrintPressString, Row, RowNIL, RowRelPtr, SendPressFile,
SetCurrentPressFont, TimeToSend, WidthTable],
lmD: FROM "LaurelMenuDefs" USING [copiesBracketsNumber, duplexBracketsNumber,
MapHouseNumberToHousePtr, overrideFormBracketsNumber,
passwordPrintingBracketsNumber, printerBracketsNumber, ReleaseMenu, SwapInMenu],
lsD: FROM "LaurelStateDefs" USING [ReleaseStateSegment, StateSegment,
SwapInStateSegment],
MailParseDefs USING [endOfInput, FinalizeParse, GetFieldBody, GetFieldName,
InitializeParse, maxFieldNameSize, ParseError, ParseHandle],
ovD: FROM "OverviewDefs" USING [CharMask],
Storage USING [FreeString, String],
String USING [AppendChar, AppendDecimal, AppendString, EquivalentString,
InvalidNumber, StringToDecimal],
tsD: FROM "TOCSelectionDefs" USING [FirstSelectedEntry, NextSelectedEntry,
TOCSelectionEmpty],
vmD: FROM "VirtualMgrDefs" USING [AllocateDisplayMessageObject, CharIndex,
DisplayMessagePtr, FlushDisplayMessage, FreeVirtualMessageObject, GetMessageChar,
GetMessageSize, LoadDisplayMessage, TOCHandle, TOCIndex, UnlockTOC, WaitForLock];

IntHardcopyCom: PROGRAM
IMPORTS dsD, exD, inD, Inline, intC: intCommon, LaurelHardcopyDefs, lmD, lsD,
MailParseDefs, Storage, String, tsD, vmD
EXPORTS inD, LaurelHardcopyDefs =

BEGIN
OPEN inD, LaurelHardcopyDefs;


AbortHardcopy: PUBLIC ERROR = CODE;

widthTable: PUBLIC WidthTable; -- exported variable.
aborted: PUBLIC HardcopyAbortCode; -- exported variable.


HardcopyCommand: PUBLIC CommandProcedure =
-- Calls HardcopyOperation with current mail file.
BEGIN
IF intC.hardcopyInstallError THEN
BEGIN
exD.DisplayException[exD.errorsDuringInstall];
exD.DisplayExceptionLine[exD.hardcopyCanceled, 2];
RETURN;
END;
IF confirmed THEN
BEGIN
tnp: TOCTextNbrPtr = intC.tocTextNbr;
toc: vmD.TOCHandle = tnp.toc;
key: CARDINAL ← vmD.WaitForLock[toc];
IF MessagesToPrint[tnp, key] THEN
BEGIN
IndicateCommandBusy[hp];
HardcopyOperation[toc, key];
ResetHardcopyParameters[];
END;
vmD.UnlockTOC[toc, key];
IndicateCommandFinished[hp];
IF CaretIsBlinking[] THEN SetCaretBlinking[intC.target.point, intC.target.mnp];
END
ELSE BEGIN
hp, h: HousePtr;
cnp: CommandNbrPtr ← intC.tocCommandNbr;
MakeCommandsCallable[FALSE];
hp ← lmD.SwapInMenu[intC.hardcopyMenuSegment];
intC.printerBracketsHouse
← lmD.MapHouseNumberToHousePtr[hp, 0, lmD.printerBracketsNumber];
intC.printerBracketsHouse.text.length ← 0;
String.AppendString[intC.printerBracketsHouse.text, intC.hardcopyHost];
intC.copiesBracketsHouse
← lmD.MapHouseNumberToHousePtr[hp, 0, lmD.copiesBracketsNumber];
intC.copiesBracketsHouse.text.length ← 0;
String.AppendDecimal[intC.copiesBracketsHouse.text, intC.hardCopies];
intC.duplexBracketsHouse
← lmD.MapHouseNumberToHousePtr[hp, 0, lmD.duplexBracketsNumber];
intC.duplexBracketsHouse.text.length ← 0;
String.AppendString
[intC.duplexBracketsHouse.text, IF intC.twoSidedPrinting THEN "Yes"L ELSE "No"L];
intC.formBracketsHouse
← lmD.MapHouseNumberToHousePtr[hp, 0, lmD.overrideFormBracketsNumber];
intC.formBracketsHouse.text.length ← 0;
h ← lmD.MapHouseNumberToHousePtr[hp, 0, lmD.passwordPrintingBracketsNumber];
h.text.length ← 0;
String.AppendString[h.text, IF intC.passwordPrinting THEN "Yes"L ELSE "No"L];
cnp.houses ← hp;
ChangeCommandMenu[cnp: cnp, region: intC.TOCCommandRegion, linesToKeep: 0];
END;
END; -- of HardcopyCommand --


ResetHardcopyParameters: PRIVATE PROCEDURE =
BEGIN
intC.hardCopies ← intC.defaultHardCopies;
intC.passwordPrinting ← intC.passwordPrintingDefault;
intC.twoSidedPrinting ← intC.twoSidedPrintingDefault;
intC.formBracketsHouse ← NIL;
END; -- of ResetHardcopyParameters --


SetPrinterCommand: PUBLIC CommandProcedure =
-- Sets intC.hardcopyHost to contents of following brackets.
BEGIN
IF ConfirmBrackets[hp.nextHouse] THEN
BEGIN
Storage.FreeString[intC.hardcopyHost];
intC.hardcopyHost ← Storage.String[hp.nextHouse.text.length];
String.AppendString[intC.hardcopyHost, hp.nextHouse.text];
END;
END; -- of SetPrinterCommand --


SetCopiesCommand: PUBLIC CommandProcedure =
-- Sets intC.hardCopies to contents of following brackets.
BEGIN
IF ConfirmBrackets[hp.nextHouse] THEN
BEGIN
n: CARDINAL;
n ← String.StringToDecimal[hp.nextHouse.text ! String.InvalidNumber => GO TO BadInput];
IF n IN [1 .. 99] THEN intC.hardCopies ← n ELSE GO TO BadInput;
EXITS
BadInput =>
BEGIN
hp.nextHouse.text.length ← 0;
String.AppendDecimal[hp.nextHouse.text, intC.hardCopies];
hp.nextHouse.houseRefresher[hp.nextHouse];
END;
END;
END; -- of SetCopiesCommand --


SetOverrideFormCommand: PUBLIC CommandProcedure =
-- Sets form to use for subsequent hardcopies to contents of following brackets.
BEGIN
newFormIndex: CARDINAL ← 0;
table: HardcopyFormTable ← intC.hardcopyFormTable;
text: STRING ← hp.nextHouse.text;
BEGIN -- for EXITS --
IF text.length = 0 THEN GO TO SetText;
FOR newFormIndex IN [1 .. table.nForms) DO
IF String.EquivalentString[text, table.formTable[newFormIndex - 1].name] THEN
GO TO SetText;
REPEAT
FINISHED => text.length ← 0;
ENDLOOP;
EXITS
SetText => {text.length ← 0; String.AppendString[text, table.formTable[newFormIndex].name]};
END;
hp.nextHouse.houseRefresher[hp.nextHouse];
END; -- of SetOverrideFormCommand --


SetDuplexCommand: PUBLIC CommandProcedure =
-- Complements intC.twoSidedPrinting and Yes/No contents of following brackets.
BEGIN
ComplementYesOrNo[bool: @intC.twoSidedPrinting, hp: hp];
END; -- of SetDuplexCommand --


SetPasswordProtectedCommand: PUBLIC CommandProcedure =
-- Complements intC.passwordPrinting and Yes/No contents of following brackets.
BEGIN
ComplementYesOrNo[bool: @intC.passwordPrinting, hp: hp];
END; -- of SetPasswordProtectedCommand --


ComplementYesOrNo: PROCEDURE [bool: POINTER TO BOOLEAN, hp: HousePtr] =
-- Complements both bool↑ and corresponding Yes/No contents of hp.nextHouse.text.
BEGIN
nextHouse: HousePtr = hp.nextHouse;
s: STRING = nextHouse.text;
bool↑ ← ~bool↑;
s.length ← 0;
String.AppendString[s, IF bool↑ THEN "Yes"L ELSE "No"L];
nextHouse.houseRefresher[nextHouse];
END; -- of ComplementYesOrNo --


HardcopyConfirmCommand: PUBLIC CommandProcedure =
-- Restores original command menu and continues hardcopy processing.
BEGIN
RestoreTocCommandMenu[];
HardcopyCommand[intC.hardcopyCommandHouse, TRUE];
END; -- of HardcopyConfirmCommand --


HardcopyCancelCommand: PUBLIC CommandProcedure =
-- Cancels hardcopy processing and restores original command menu.
BEGIN
RestoreTocCommandMenu[];
ResetHardcopyParameters[];
IF CaretIsBlinking[] THEN SetCaretBlinking[intC.target.point, intC.target.mnp];
END; -- of HardcopyCancelCommand --


RestoreTocCommandMenu: PROCEDURE =
BEGIN
cnp: CommandNbrPtr ← intC.tocCommandNbr;
lmD.ReleaseMenu[intC.hardcopyMenuSegment];
cnp.houses ← intC.displayCommandHouse;
ChangeCommandMenu[cnp: cnp, region: intC.TOCCommandRegion, linesToKeep: 0];
MakeCommandsCallable[TRUE];
END; -- of RestoreTocCommandMenu --


MessagesToPrint: PROCEDURE [tnp: TOCTextNbrPtr, key: CARDINAL]
RETURNS [canPrint: BOOLEAN] =
-- Returns TRUE iff there are selected messages to print.
BEGIN
IF ~tnp.haveToc THEN {exD.DisplayException[exD.noCurrentFile]; RETURN[FALSE]};
IF tsD.TOCSelectionEmpty[tnp.toc, key] THEN
{exD.DisplayException[exD.noSelectedEntries]; RETURN[FALSE]};
RETURN[TRUE];
END; -- of MessagesToPrint --


HardcopyOperation: PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL] =
BEGIN
currentFont: CARDINAL;
currentY: Mica;
messageNumber: vmD.TOCIndex;
message: vmD.DisplayMessagePtr;
formSegment: lsD.StateSegment ← NIL;
form: HardcopyForm;
otherFieldName: STRING ← [MailParseDefs.maxFieldNameSize];
lineBuffer: STRING ← [160];
messageLength, charIndex: CharIndex;
currentPage: CARDINAL;
diabloHardcopy: BOOLEAN = String.EquivalentString[intC.hardcopyHost, "Local"L];
parsable: BOOLEAN;
havePageOpen: BOOLEAN ← FALSE;
lineSegmentTable: LineSegmentTable;

-- many local procedures follow

InitPage: PROCEDURE =
-- Initializes output of a press page.
BEGIN
totalPages ← totalPages + 1;
totalPagesString.length ← 0;
String.AppendDecimal[totalPagesString, totalPages];
dsD.ClearRectangle[totalPagesLeftX, totalPagesRightX, totalPagesTopY, totalPagesBottomY];
[] ← dsD.PutStringInBitMap[totalPagesRightX - totalPagesString.length * inD.digitWidth,
totalPagesTopY, totalPagesString, plainFace];
IF diabloHardcopy THEN
{NewDiabloPage[totalPages = 1]; exD.DisplayExceptionLine[exD.printing, 2]}
ELSE InitPressPage[];
havePageOpen ← TRUE;
currentY ← form.top;
SetCurrentFont[0];
fileSent ← FALSE;
PrintOptions[];
END; -- of InitPage --


InitMessageProcessing: PROCEDURE =
BEGIN
messageLength ← vmD.GetMessageSize[message];
form ← GetHardcopyForm[];
IF ~diabloHardcopy AND intC.twoSidedPrinting AND totalPages MOD 2 = 1
AND form.startOnNewPage THEN
BEGIN
IF havePageOpen THEN FlushPage[FALSE];
InitPressPage[];
FinishPressPage[];
totalPages ← totalPages + 1;
END;
IF form.startOnNewPage OR ~havePageOpen THEN
BEGIN
IF havePageOpen THEN FlushPage[FALSE];
currentPage ← 1;
InitPage[];
END;
END; -- of InitMessageProcessing --


PrintMessage: PROCEDURE =
BEGIN
rowRelPtr: RowRelPtr;
row: Row;
InitMessageProcessing[];
FOR rowRelPtr ← form.rows, row.nextRow UNTIL rowRelPtr = RowNIL DO
row ← @form[rowRelPtr];
PrintRow[row];
ENDLOOP;
FinishMessageProcessing[];
END; -- of PrintMessage --


PrintRow: PROCEDURE [row: Row] =
BEGIN
columnRelPtr: ColumnRelPtr;
column: Column;
rowPrinted, printed, rowFinished, finished, firstLine: BOOLEAN;
lineHeight: Mica ← 0;
savedCurrentY: Mica ← currentY;
rowLeading: Mica ← row.rowLeading;
lineLeading: Mica ← row.lineLeading;

-- Initialize line height and start, end points for each column
FOR columnRelPtr ← row.columns, column.nextColumn
UNTIL columnRelPtr = ColumnNIL DO
column ← @form[columnRelPtr];
column.start ← column.end ← 0;
lineHeight ← MAX[lineHeight, widthTable[column.font][0C]];
IF column.columnType = field THEN
lineHeight ← MAX[lineHeight,
widthTable[WITH c: column SELECT FROM field => c.fieldFont, ENDCASE => 0][0C]];
ENDLOOP;
IF diabloHardcopy THEN
BEGIN
rowLeading ← (rowLeading / micasPerDiabloY) * micasPerDiabloY;
lineLeading ← (lineLeading / micasPerDiabloY) * micasPerDiabloY;
lineHeight ← (lineHeight / micasPerDiabloY) * micasPerDiabloY;
END;
currentY ← currentY - rowLeading;
IF row.verticalTab # 0 THEN currentY ← MIN[currentY, row.verticalTab];
currentY ← currentY - lineHeight;
IF currentY < form.bottom THEN
BEGIN
savedCurrentY ← form.top;
FlushPage[];
currentY ← currentY - lineHeight;
END;
firstLine ← TRUE;
rowPrinted ← FALSE;
DO -- for each line to be printed in the row.
rowFinished ← TRUE;
FOR columnRelPtr ← row.columns, column.nextColumn
UNTIL columnRelPtr = ColumnNIL DO
column ← @form[columnRelPtr];
[printed, finished] ← PrintLineOfColumn[column, firstLine];
rowPrinted ← rowPrinted OR printed;
rowFinished ← rowFinished AND finished;
ENDLOOP;
IF ~rowPrinted THEN {currentY ← savedCurrentY; EXIT};
firstLine ← FALSE;
IF rowFinished THEN RETURN
ELSE BEGIN
currentY ← currentY - lineHeight - lineLeading;
IF currentY < form.bottom THEN
{FlushPage[]; currentY ← currentY - lineHeight - lineLeading};
END;
ENDLOOP;
END; -- of PrintRow --


PrintLineOfColumn: PROCEDURE [column: Column, firstLine: BOOLEAN]
RETURNS [printed, finished: BOOLEAN] =
-- Formats and prints one line of a column, beginning at the CharIndex
-- at which printing stopped the last time this procedure was called on this column.
BEGIN
WITH c: column SELECT FROM
field =>
WITH fc: c SELECT FROM
specific => [printed, finished] ← PrintLineOfSpecificField[@fc, firstLine];
other => [printed, finished] ← PrintLineOfOtherFields[@fc, firstLine];
ENDCASE => exD.SysBug[];
caption =>
BEGIN
IF firstLine THEN PrintString[@form[c.text], c.font, c.left, currentY, FALSE];
printed ← finished ← TRUE;
END;
body => [printed, finished] ← PrintLineOfBody[@c, firstLine];
everything => [printed, finished] ← PrintLineOfEverything[@c, firstLine];
ENDCASE => exD.SysBug[];
END; -- of PrintLineOfColumn --


PrintLineOfSpecificField: PROCEDURE [sfc: POINTER TO specific field ColumnRec,
firstLine: BOOLEAN] RETURNS [printed, finished: BOOLEAN] =
BEGIN
justifyOk: BOOLEAN;
fieldName: STRING ← @form[form[form.fieldTable][sfc.fieldIndex]];
aliasFieldName: STRING ← IF sfc.aliasFieldIndex = LAST[CARDINAL]
THEN NIL ELSE @form[form[form.fieldTable][sfc.aliasFieldIndex]];
foundField: BOOLEAN;
left: Mica;
IF sfc.suppress THEN RETURN[FALSE, TRUE];
IF firstLine THEN
BEGIN
[foundField, sfc.start, sfc.end] ← SetUpField[fieldName, aliasFieldName, 0];
IF sfc.required OR foundField THEN
BEGIN
IF sfc.printFieldName THEN
BEGIN
PrintString[fieldName, sfc.fieldFont, sfc.left, currentY, FALSE];
left ← sfc.left + PrintWidth[fieldName, sfc.fieldFont];
IF sfc.colonAfterFieldName THEN
PrintString[":"L, sfc.fieldFont, left, currentY, FALSE];
left ← MIN[MAX[left + postColonSpacing, sfc.textLeft], sfc.right];
END
ELSE left ← sfc.left;
IF sfc.fieldNameAbove THEN RETURN[TRUE, ~foundField];
END
ELSE RETURN[FALSE, TRUE];
END
ELSE {IF sfc.start = sfc.end THEN RETURN[FALSE, TRUE] ELSE left ← sfc.textLeft};
[sfc.start, justifyOk]
← GetLineOfText[sfc.start, sfc.end, left, sfc.right, sfc.font, sfc.breakOnComma];
PrintString[lineBuffer, sfc.font, left, currentY, TRUE, justifyOk AND sfc.justify];
IF sfc.start = sfc.end THEN
BEGIN
[foundField, sfc.start, sfc.end] ← SetUpField[fieldName, aliasFieldName, sfc.start];
RETURN[TRUE, ~foundField];
END
ELSE RETURN[TRUE, FALSE];
END; -- of PrintLineOfSpecificField --


PrintLineOfOtherFields: PROCEDURE [ofc: POINTER TO other field ColumnRec,
firstLine: BOOLEAN] RETURNS [printed, finished: BOOLEAN] =
BEGIN
left: Mica;
justifyOk: BOOLEAN;
IF firstLine THEN
{IF ~SetUpOtherField[ofc] THEN RETURN[FALSE, TRUE] ELSE ofc.newField ← TRUE};
IF ofc.newField THEN
BEGIN
ofc.newField ← FALSE;
PrintString[otherFieldName, ofc.fieldFont, ofc.left, currentY, FALSE];
left ← ofc.left + PrintWidth[otherFieldName, ofc.fieldFont];
IF ofc.colonAfterFieldName THEN PrintString[":"L, ofc.fieldFont, left, currentY, FALSE];
left ← MIN[MAX[left + postColonSpacing, ofc.textLeft], ofc.right];
IF ofc.fieldNameAbove THEN RETURN[TRUE, FALSE];
END
ELSE left ← ofc.textLeft;
[ofc.start, justifyOk] ← GetLineOfText[ofc.start, ofc.end, left, ofc.right, ofc.font, FALSE];
PrintString[lineBuffer, ofc.font, left, currentY, TRUE, justifyOk AND ofc.justify];
IF ofc.start = ofc.end THEN
BEGIN
IF SetUpOtherField[ofc] THEN
BEGIN
fieldLeading: Mica ← ofc.fieldLeading;
IF diabloHardcopy THEN
fieldLeading ← (fieldLeading / micasPerDiabloY) * micasPerDiabloY;
currentY ← currentY - fieldLeading;
ofc.newField ← TRUE;
RETURN[TRUE, FALSE];
END
ELSE RETURN[TRUE, TRUE];
END
ELSE RETURN[TRUE, FALSE];
END; -- of PrintLineOfOtherFields --


PrintLineOfBody: PROCEDURE [column: POINTER TO body ColumnRec,
firstLine: BOOLEAN] RETURNS [printed, finished: BOOLEAN] =
BEGIN
dummyString: STRING ← [0];
justifyOk: BOOLEAN;
IF firstLine THEN
BEGIN
-- find message start.
charIndex ← 0;
IF parsable THEN
BEGIN
pH: MailParseDefs.ParseHandle = MailParseDefs.InitializeParse[NextChar];
DO
IF ~MailParseDefs.GetFieldName[pH, dummyString] THEN EXIT;
MailParseDefs.GetFieldBody[pH, dummyString];
ENDLOOP;
MailParseDefs.FinalizeParse[pH];
END;
column.start ← charIndex;
column.end ← messageLength;
END;
[column.start, justifyOk] ← GetLineOfText
[column.start, column.end, column.left, column.right, column.font, FALSE];
PrintString[lineBuffer, column.font, column.left, currentY, TRUE,
justifyOk AND column.justify];
RETURN[TRUE, (column.start >= column.end)];
END; -- of PrintLineOfBody --


PrintLineOfEverything: PROCEDURE [column: POINTER TO everything ColumnRec,
firstLine: BOOLEAN] RETURNS [printed, finished: BOOLEAN] =
BEGIN
justifyOk: BOOLEAN;
IF firstLine THEN {column.start ← 0; column.end ← messageLength};
[column.start, justifyOk] ← GetLineOfText
[column.start, column.end, column.left, column.right, column.font, FALSE];
PrintString
[lineBuffer, column.font, column.left, currentY, TRUE, justifyOk AND column.justify];
RETURN[TRUE, (column.start >= column.end)];
END; -- of PrintLineOfEverything --


SetUpField: PROCEDURE [fieldName, aliasFieldName: STRING, searchStart: CharIndex]
RETURNS [foundField: BOOLEAN, start, end: CharIndex] =
-- Starting at column.start, search for fieldName or, if non-NIL, aliasFieldName. If
-- found, set column.start, column.end with range of CharIndexes covered by the field
-- value.
BEGIN
messageField: STRING ← [MailParseDefs.maxFieldNameSize];
pH: MailParseDefs.ParseHandle;
charIndex ← searchStart;
pH ← MailParseDefs.InitializeParse[NextChar];
foundField ← FALSE;
start ← end ← 0;
DO
IF ~MailParseDefs.GetFieldName[pH, messageField ! MailParseDefs.ParseError => EXIT]
THEN EXIT;
IF String.EquivalentString[fieldName, messageField]
OR (aliasFieldName # NIL AND String.EquivalentString[aliasFieldName, messageField])
THEN BEGIN
SkipWhiteSpace[];
start ← charIndex;
MailParseDefs.GetFieldBody
[pH, messageField ! MailParseDefs.ParseError => {start ← 0; EXIT}];
end ← charIndex - 1;
foundField ← TRUE;
EXIT;
END
ELSE MailParseDefs.GetFieldBody[pH, messageField ! MailParseDefs.ParseError => EXIT];
ENDLOOP;
MailParseDefs.FinalizeParse[pH];
END; -- of SetUpField --


SetUpOtherField: PROCEDURE [column: Column] RETURNS [foundField: BOOLEAN] =
-- Starting at column.start, search for a field name other than one listed in the field
-- table. If found, set column.start, column.end with range of CharIndexes covered
-- by the field value.
BEGIN
pH: MailParseDefs.ParseHandle;
fieldIndex: CARDINAL;
otherField: BOOLEAN;
fieldBody: STRING ← [4]; -- Most chars will be thrown away; we are only interested
-- in the field body’s length.
foundField ← FALSE;
IF ~parsable THEN RETURN;
charIndex ← column.start;
pH ← MailParseDefs.InitializeParse[NextChar];
DO
IF ~MailParseDefs.GetFieldName[pH, otherFieldName ! MailParseDefs.ParseError => EXIT]
THEN EXIT;
otherField ← TRUE;
FOR fieldIndex IN [0 .. form.nFields) DO
IF String.EquivalentString[@form[form[form.fieldTable][fieldIndex]], otherFieldName]
THEN {otherField ← FALSE; EXIT};
ENDLOOP;
SkipWhiteSpace[];
column.start ← charIndex;
MailParseDefs.GetFieldBody[pH, fieldBody ! MailParseDefs.ParseError => EXIT];
column.end ← charIndex - 1;
IF otherField THEN {foundField ← TRUE; EXIT};
ENDLOOP;
MailParseDefs.FinalizeParse[pH];
END; -- of SetUpOtherField --


GetHardcopyForm: PROCEDURE RETURNS [HardcopyForm] =
BEGIN
pH: MailParseDefs.ParseHandle;
formName: STRING ← [25];
dummy: STRING ← [0];
fieldName: STRING ← [20];
blank: STRING ← "Blank"L;
override: STRING = intC.formBracketsHouse.text;

FindFormSegment: PROCEDURE [formName: STRING] =
BEGIN
i: CARDINAL;
table: HardcopyFormTable ← intC.hardcopyFormTable;
FOR i IN [0 .. table.nForms) DO
IF String.EquivalentString[table.formTable[i].name, formName] THEN
{formSegment ← table.formTable[i].segment; RETURN};
ENDLOOP;
END; -- of FindFormSegment --

parsable ← TRUE;
charIndex ← 0;
pH ← MailParseDefs.InitializeParse[NextChar];
DO
IF ~MailParseDefs.GetFieldName
[pH, fieldName ! MailParseDefs.ParseError => GO TO NotParsable]
THEN EXIT;
IF String.EquivalentString[fieldName, "PrintForm"L] THEN
BEGIN
SkipWhiteSpace[];
MailParseDefs.GetFieldBody
[pH, formName ! MailParseDefs.ParseError => GO TO NotParsable];
END
ELSE MailParseDefs.GetFieldBody
[pH, dummy ! MailParseDefs.ParseError => GO TO NotParsable];
REPEAT
NotParsable => parsable ← FALSE;
ENDLOOP;
MailParseDefs.FinalizeParse[pH];
IF diabloHardcopy THEN formName ← "HyType"L;
IF ~parsable THEN formName ← blank;
IF override # NIL AND override.length > 0 THEN FindFormSegment[override];
IF formSegment = NIL AND formName.length > 0 THEN FindFormSegment[formName];
IF formSegment = NIL THEN FindFormSegment[intC.defaultHardcopyFormName];
IF formSegment = NIL THEN FindFormSegment[blank]; -- must be found --
RETURN[lsD.SwapInStateSegment[formSegment]]
END; -- of GetHardcopyForm --

NextChar: PROCEDURE RETURNS [c: CHARACTER] =
BEGIN
c ← IF charIndex >= messageLength
THEN MailParseDefs.endOfInput ELSE vmD.GetMessageChar[message, charIndex];
charIndex ← charIndex + 1;
END; -- of NextChar --

FinishMessageProcessing: PROCEDURE =
BEGIN
lsD.ReleaseStateSegment[formSegment];
formSegment ← NIL;
END; -- of FinishMessageProcessing --

PrintOptions: PROCEDURE =
BEGIN
option: Option;
optionRelPtr: OptionRelPtr;
FOR optionRelPtr ← form.options, option.nextOption UNTIL optionRelPtr = OptionNIL DO
option ← @form[optionRelPtr];
WITH opt: option SELECT FROM
heading => PrintHeadingOption[@opt];
caption => PrintCaptionOption[@opt];
pageNumber => PrintPageNumberOption[@opt];
ENDCASE => exD.SysBug[];
ENDLOOP;
END; -- of PrintOptions --

PrintHeadingOption: PROCEDURE [option: POINTER TO heading OptionRec] =
BEGIN
IF ~parsable THEN RETURN;
IF currentPage = 1 THEN
[ , option.start, option.end] ← SetUpField[@form[option.fieldName], NIL, 0];
IF (currentPage = 1) = option.onFirstPage THEN
BEGIN
[ , ] ← GetLineOfText[option.start, option.end, option.x, option.right, option.font, FALSE];
PrintString[lineBuffer, option.font, option.x, option.y, TRUE];
END;
END; -- of PrintHeadingOption --

PrintCaptionOption: PROCEDURE [option: POINTER TO caption OptionRec] =
BEGIN
IF (currentPage = 1) = option.onFirstPage THEN
PrintString[@form[option.text], option.font, option.x, option.y, FALSE];
END; -- of PrintCaptionOption --

PrintPageNumberOption: PROCEDURE [option: POINTER TO pageNumber OptionRec] =
BEGIN
pageNumberString: STRING ← [5];
IF (currentPage = 1) = option.onFirstPage THEN
BEGIN
String.AppendDecimal[pageNumberString, currentPage];
PrintString[pageNumberString, option.font,
option.x - pageNumberString.length * widthTable[option.font][’0], option.y, FALSE];
END;
END; -- of PrintPageNumberOption --

PrintWidth: PROCEDURE [s: STRING, font: FontNumber] RETURNS [width: Mica] =
BEGIN
i: CARDINAL;
charWidth: Mica;
width ← 0;
FOR i IN [0 .. s.length) DO
IF (charWidth ← widthTable[font][s[i]]) # magicNonPrintingWidth THEN
width ← width + charWidth;
ENDLOOP;
END; -- of PrintWidth --

FlushPage: PROCEDURE[startNewPage: BOOLEAN ← TRUE] =
BEGIN
IF diabloHardcopy THEN FinishDiabloPage[] ELSE FinishPressPage[];
havePageOpen ← FALSE;
CheckForAbort[];
IF ~diabloHardcopy AND (~intC.twoSidedPrinting OR totalPages MOD 2 = 0)
AND TimeToSend[] THEN
BEGIN
FinishPressFile[nChunks ← nChunks + 1];
SendPressFile[];
CheckForAbort[];
fileSent ← TRUE;
exD.DisplayExceptionStringOnLine[formattingMessage, 1];
END;
IF startNewPage THEN {currentPage ← currentPage + 1; InitPage[]};
END; -- of FlushPage --

SkipWhiteSpace: PROCEDURE =
BEGIN
c: CHARACTER;
DO
c ← NextChar[];
IF c # Ascii.SP AND c # Ascii.TAB THEN EXIT;
ENDLOOP;
charIndex ← charIndex - 1;
END; -- of SkipWhiteSpace --

GetLineOfText: PROCEDURE [start, end: CharIndex, left, right: Mica, font: FontNumber,
breakOnComma: BOOLEAN] RETURNS [i: CharIndex, justifyOk: BOOLEAN] =
BEGIN
screenTabWidth: CARDINAL = 40; -- in terms of Alto bitmap points.
hardcopyTabWidth: Mica = widthTable[font][’0] * 15 / 2;
si: CARDINAL ← 0;
char: CHARACTER;
curX: Mica ← left;
screenX: dsD.ScreenXCoord ← inD.leftMargin;
lineSegmentTableIndex: CARDINAL ← 0;
charWidth, newPageX: Mica;
lineBuffer.length ← 0;
lineSegmentTable[0] ← [index: 0, x: left];
justifyOk ← TRUE;
FOR i ← start, i + 1 UNTIL i >= end DO
IF (char ← Inline.BITAND[vmD.GetMessageChar[message, i], ovD.CharMask]) = Ascii.CR
THEN {i ← i + 1; justifyOk ← FALSE; EXIT};
IF breakOnComma AND char = ’, THEN
BEGIN
i ← i + 1; justifyOk ← FALSE;
UNTIL i >= end DO
IF ~dsD.GetCharProperty[vmD.GetMessageChar[message, i], white] THEN EXIT;
i ← i + 1;
REPEAT
FINISHED => i ← end;
ENDLOOP;
EXIT;
END;
charWidth ← widthTable[font][char];
screenX ← dsD.GetCharRightX[char, screenX];
IF char = Ascii.TAB THEN
BEGIN
newPageX
← form.left + hardcopyTabWidth * ((screenX - inD.leftMargin) / screenTabWidth);
justifyOk ← FALSE;
SELECT newPageX FROM
> right => {i ← MIN[i + 1, end]; EXIT};
> curX =>
BEGIN
lineSegmentTable[(lineSegmentTableIndex ← lineSegmentTableIndex + 1)]
← [index: si, x: (curX ← newPageX)];
LOOP;
END;
ENDCASE => char ← Ascii.SP;
END;
curX ← curX + (IF charWidth = magicNonPrintingWidth THEN 0 ELSE charWidth);
IF curX > right THEN
BEGIN
savedI: CARDINAL ← i;
savedSi: CARDINAL ← si;
FOR si DECREASING IN [lineSegmentTable[lineSegmentTableIndex].index .. si) DO
IF lineBuffer[si] = Ascii.SP THEN EXIT;
i ← i - 1;
REPEAT
FINISHED =>
BEGIN
IF lineSegmentTableIndex = 0 THEN {i ← savedI; si ← savedSi}
ELSE si ← lineSegmentTable[lineSegmentTableIndex].index;
EXIT;
END;
ENDLOOP;
EXIT;
END
ELSE IF si < lineBuffer.maxlength THEN {lineBuffer[si] ← char; si ← si + 1};
ENDLOOP;
lineBuffer.length ← si;
lineSegmentTable[lineSegmentTableIndex + 1] ← [index: si, x: right];
IF i = start THEN i ← end; -- Flush this field if first character doesn’t fit.
END; -- of GetLineOfText --

PrintString: PROCEDURE [string: STRING, fontNumber: CARDINAL, x, y: Mica,
useTable, justify: BOOLEAN ← FALSE] =
BEGIN
IF string.length = 0 THEN RETURN;
IF currentFont ~= fontNumber THEN SetCurrentFont[fontNumber];
IF diabloHardcopy THEN PrintDiabloString
[string, fontNumber, x, y, IF useTable THEN @lineSegmentTable ELSE NIL]
ELSE PrintPressString
[string, justify, x, y, IF useTable THEN @lineSegmentTable ELSE NIL];
END; -- of PrintString --

SetCurrentFont: PROCEDURE [font: FontNumber] =
BEGIN
currentFont ← font;
IF ~diabloHardcopy THEN SetCurrentPressFont[font];
END; -- of SetCurrentFont --

-- ************************
-- Main Body of HardcopyOperation
-- ************************

formattingMessage: STRING = "Formatting page ."L;
nChunks, totalPages: CARDINAL ← 0;
totalPagesString: STRING ← [4];
totalPagesRightX: ScreenXCoord ← inD.leftMargin
+ dsD.GetStringWidth[formattingMessage, plainFace] - dsD.GetStaticCharWidth[’.];
totalPagesLeftX: ScreenXCoord ← totalPagesRightX - dsD.GetStringWidth[" "L, plainFace];
totalPagesTopY: ScreenYCoord ← intC.exceptionsRegion.topY + dsD.lineHeight;
totalPagesBottomY: ScreenYCoord ← totalPagesTopY + dsD.lineHeight;
completedMessage: STRING ← [60];
summaryMessage: STRING ← [35];
fileSent: BOOLEAN ← TRUE;

aborted ← no;
IF ~diabloHardcopy AND ~FindPrinter[] THEN RETURN;

widthTable ← lsD.SwapInStateSegment[intC.hardcopyWidthTableSegment];

IF ~diabloHardcopy THEN OpenPressStreams[];
message ← vmD.AllocateDisplayMessageObject[];
exD.DisplayBothExceptionLines
[formattingMessage, exD.nil, NIL, exD.cancelHardcopy, FALSE];

-- Main Loop --

FOR messageNumber ← tsD.FirstSelectedEntry[toc, key],
tsD.NextSelectedEntry[toc, key, messageNumber] UNTIL messageNumber = 0 DO
vmD.LoadDisplayMessage[toc, key, messageNumber, message];
PrintMessage[ ! AbortHardcopy => {vmD.FlushDisplayMessage[message, key]; EXIT}];
vmD.FlushDisplayMessage[message, key];
REPEAT
FINISHED =>
BEGIN
IF havePageOpen THEN FlushPage[FALSE ! AbortHardcopy => CONTINUE];
IF aborted = no AND ~diabloHardcopy AND ~fileSent THEN
BEGIN
FinishPressFile[nChunks ← nChunks + 1];
SendPressFile[ ! AbortHardcopy => CONTINUE];
END;
END;
ENDLOOP;

-- Cleanup

vmD.FreeVirtualMessageObject[message];
IF ~diabloHardcopy THEN ClosePressStreams[];
lsD.ReleaseStateSegment[intC.hardcopyWidthTableSegment];
IF formSegment # NIL THEN lsD.ReleaseStateSegment[formSegment];

SELECT aborted FROM
no =>
BEGIN
OPEN String;
IF diabloHardcopy THEN
BEGIN
exD.ClearExceptionsRegion[];
exD.DisplayExceptionLine[exD.hardcopyCompleted, 1];
RETURN;
END;
exD.GetExceptionString[exD.hardcopyTo, completedMessage];
AppendString[completedMessage, intC.hardcopyHost];
exD.AppendExceptionString[exD.completed, completedMessage];
summaryMessage.length ← 0;
IF totalPages = 1 THEN exD.GetExceptionString[exD.onePagePrinted, summaryMessage]
ELSE BEGIN
AppendString[summaryMessage, totalPagesString];
exD.AppendExceptionString[exD.pagesPrinted, summaryMessage];
END;
IF nChunks > 1 THEN
BEGIN
AppendString[summaryMessage, " in "L];
AppendDecimal[summaryMessage, nChunks];
AppendString[summaryMessage, " parts"L];
END;
AppendChar[summaryMessage, ’.];
exD.DisplayBothExceptionLines
[completedMessage, exD.nil, summaryMessage, exD.nil, FALSE];
END;
user => exD.DisplayBothExceptionLines[NIL, exD.hardcopyCanceled, NIL, exD.nil, FALSE];
ENDCASE;
END; -- of HardcopyOperation --


CheckForAbort: PUBLIC PROCEDURE =
-- Checks keystream for cancel character (DEL or CANCEL). Raises AbortHardcopy error if
-- cancel character is seen. Flushes any other characters.
BEGIN
char: CHARACTER;
UNTIL intC.keystream.endof[intC.keystream] DO
IF (char ← intC.keystream.get[intC.keystream]) = Ascii.DEL OR char = Editor.cancelCode
THEN {aborted ← user; ERROR AbortHardcopy};
ENDLOOP;
END; -- of CheckForAbort --


END. -- of IntHardcopyCom --