-- file: IntHardcopyCom.Mesa
-- edited by Brotz, May 4, 1981 2:39 PM
-- edited by Schroeder, November 21, 1980 11:48 AM
-- edited by Levin, January 16, 1981 10:55 AM.

DIRECTORY
Ascii,
dsD: FROM "DisplayDefs",
displayCommon: FROM "DisplayCommon",
Editor,
exD: FROM "ExceptionDefs",
inD: FROM "InteractorDefs",
intCommon: FROM "IntCommon",
LaurelHardcopyDefs,
lmD: FROM "LaurelMenuDefs",
lsD: FROM "LaurelStateDefs",
MailParse,
opD: FROM "OperationsDefs",
ovD: FROM "OverviewDefs",
Storage,
String,
tsD: FROM "TOCSelectionDefs",
vmD: FROM "VirtualMgrDefs";

IntHardcopyCom: PROGRAM
IMPORTS disC: displayCommon, dsD, exD, inD, intC: intCommon, LaurelHardcopyDefs,
lmD, lsD, MailParse, Storage, String, tsD, vmD
EXPORTS inD, LaurelHardcopyDefs = PUBLIC

BEGIN
OPEN inD, LaurelHardcopyDefs;


AbortHardcopy: ERROR = CODE;

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


HardcopyCommand: 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
IF MessagesToPrint[] THEN
BEGIN
IndicateCommandBusy[hp];
HardcopyOperation[];
ResetHardcopyParameters[];
END;
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;
END; -- of ResetHardcopyParameters --


SetPrinterCommand: 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: 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: 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
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: CommandProcedure =
-- Complements intC.twoSidedPrinting and Yes/No contents of following brackets.
BEGIN
ComplementYesOrNo[bool: @intC.twoSidedPrinting, hp: hp];
END; -- of SetDuplexCommand --


SetPasswordProtectedCommand: 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: CommandProcedure =
-- Restores original command menu and continues hardcopy processing.
BEGIN
RestoreTocCommandMenu[];
HardcopyCommand[intC.hardcopyCommandHouse, TRUE];
END; -- of HardcopyConfirmCommand --


HardcopyCancelCommand: 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 RETURNS [canPrint: BOOLEAN] =
-- Returns TRUE iff there are selected messages to print.
BEGIN
fixedPart: vmD.TOCFixedPart;
i: vmD.TOCIndex;
canPrint ← FALSE;

IF ~intC.haveMailFile THEN {exD.DisplayException[exD.noCurrentFile]; RETURN[FALSE]};
IF tsD.TOCSelectionEmpty[] THEN
{exD.DisplayException[exD.noSelectedEntries]; RETURN[FALSE]};

-- Check that there is at least one message to print.
FOR i ← tsD.FirstSelectedEntry[], tsD.NextSelectedEntry[i] UNTIL i = 0 DO
vmD.GetTOCFixedPart[i, @fixedPart];
IF ~fixedPart.deleted THEN RETURN[TRUE];
REPEAT
FINISHED => {exD.DisplayException[exD.noUndeletedEntries]; RETURN[FALSE]};
ENDLOOP;
END; -- of MessagesToPrint --


HardcopyOperation: PROCEDURE =
BEGIN
error: ovD.ErrorCode;
currentFont: CARDINAL;
currentY: Mica;
messageNumber: vmD.TOCIndex;
fixedPart: vmD.TOCFixedPart;
message: vmD.DisplayMessagePtr;
formSegment: lsD.StateSegment ← NIL;
form: HardcopyForm;
otherFieldName: STRING ← [MailParse.maxFieldNameSize];
lineBuffer: STRING ← [160];
messageLength, charIndex: ovD.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[]; 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
BEGIN
currentY ← savedCurrentY;
EXIT;
END;
firstLine ← FALSE;
IF rowFinished THEN RETURN
ELSE BEGIN
currentY ← currentY - lineHeight - lineLeading;
IF currentY < form.bottom THEN
BEGIN
FlushPage[];
currentY ← currentY - lineHeight - lineLeading;
END;
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 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
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
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 BEGIN
IF sfc.start = sfc.end THEN RETURN[FALSE, TRUE]
ELSE left ← sfc.textLeft;
END;
sfc.start ← GetLineOfText
[sfc.start, sfc.end, left, sfc.right, sfc.font, sfc.breakOnComma];
PrintString[lineBuffer, sfc.font, left, currentY, TRUE];
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;
IF firstLine THEN
BEGIN
IF ~SetUpOtherField[ofc] THEN RETURN[FALSE, TRUE]
ELSE ofc.newField ← TRUE;
END;
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 ← GetLineOfText[ofc.start, ofc.end, left, ofc.right, ofc.font, FALSE];
PrintString[lineBuffer, ofc.font, left, currentY, TRUE];
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];
IF firstLine THEN
BEGIN
-- find message start.
charIndex ← 0;
IF parsable THEN
BEGIN
pH: MailParse.ParseHandle = MailParse.InitializeParse[NextChar, BackupChar];
DO
IF ~MailParse.GetFieldName[pH, dummyString] THEN EXIT;
MailParse.GetFieldBody[pH, dummyString];
ENDLOOP;
MailParse.FinalizeParse[pH];
END;
column.start ← charIndex;
column.end ← messageLength;
END;
column.start ← GetLineOfText
[column.start, column.end, column.left, column.right, column.font, FALSE];
PrintString[lineBuffer, column.font, column.left, currentY, TRUE];
RETURN[TRUE, (column.start >= column.end)];
END; -- of PrintLineOfBody --


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


SetUpField: PROCEDURE [fieldName, aliasFieldName: STRING, searchStart: ovD.CharIndex]
RETURNS [foundField: BOOLEAN, start, end: ovD.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 ← [MailParse.maxFieldNameSize];
pH: MailParse.ParseHandle;
charIndex ← searchStart;
pH ← MailParse.InitializeParse[NextChar, BackupChar];
foundField ← FALSE;
start ← end ← 0;
DO
IF ~MailParse.GetFieldName[pH, messageField ! MailParse.ParseError => EXIT]
THEN EXIT;
IF String.EquivalentString[fieldName, messageField]
OR (aliasFieldName # NIL
AND String.EquivalentString[aliasFieldName, messageField])
THEN BEGIN
SkipWhiteSpace[];
start ← charIndex;
MailParse.GetFieldBody[pH, messageField
! MailParse.ParseError => {start ← 0; EXIT}];
end ← charIndex;
foundField ← TRUE;
EXIT;
END
ELSE MailParse.GetFieldBody[pH, messageField ! MailParse.ParseError => EXIT];
ENDLOOP;
MailParse.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: MailParse.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 ← MailParse.InitializeParse[NextChar, BackupChar];
DO
IF ~MailParse.GetFieldName[pH, otherFieldName ! MailParse.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;
MailParse.GetFieldBody[pH, fieldBody ! MailParse.ParseError => EXIT];
column.end ← charIndex;
IF otherField THEN {foundField ← TRUE; EXIT};
ENDLOOP;
MailParse.FinalizeParse[pH];
END; -- of SetUpOtherField --


GetHardcopyForm: PROCEDURE RETURNS [HardcopyForm] =
BEGIN
pH: MailParse.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 ← MailParse.InitializeParse[NextChar, BackupChar];
DO
IF ~MailParse.GetFieldName[pH, fieldName
! MailParse.ParseError => GO TO NotParsable]
THEN EXIT;
IF String.EquivalentString[fieldName, "PrintForm"L] THEN
BEGIN
SkipWhiteSpace[];
MailParse.GetFieldBody[pH, formName
! MailParse.ParseError => GO TO NotParsable];
END
ELSE MailParse.GetFieldBody[pH, dummy
! MailParse.ParseError => GO TO NotParsable];
REPEAT
NotParsable => parsable ← FALSE;
ENDLOOP;
MailParse.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 0C
ELSE vmD.GetMessageChar[message, charIndex];
charIndex ← charIndex + 1;
END; -- of NextChar --


BackupChar: PROCEDURE =
BEGIN
charIndex ← charIndex - 1;
END; -- of BackupChar --


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: ovD.CharIndex, left, right: Mica, font: FontNumber,
breakOnComma: BOOLEAN] RETURNS [i: ovD.CharIndex] =
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];
FOR i ← start, i + 1 UNTIL i >= end DO
IF (char ← vmD.GetMessageChar[message, i]) = Ascii.CR THEN {i ← i + 1; EXIT};
IF breakOnComma AND char = ’, THEN
BEGIN
i ← i + 1;
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);
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: 0];
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: BOOLEAN] =
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, fontNumber, 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;

error ← ovD.ok;
aborted ← no;
IF ~diabloHardcopy AND FindPrinter[] # ovD.ok 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[], tsD.NextSelectedEntry[messageNumber]
UNTIL messageNumber = 0 DO
vmD.GetTOCFixedPart[messageNumber, @fixedPart];
IF ~fixedPart.deleted THEN
BEGIN
IF (error ← vmD.VirtualizeMessage[messageNumber, message]) ~= ovD.ok THEN EXIT;
PrintMessage[ ! AbortHardcopy => EXIT];
END;
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 error FROM
ovD.ok =>
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;
ENDCASE => exD.DisplayBothExceptionLines[NIL, exD.internalError, NIL,
exD.hardcopyCanceled];
END; -- of HardcopyOperation --


CheckForAbort: 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 --