HighlightDummy.mesa
Copyright Ó 1991, 1992, 1993 by Xerox Corporation. All rights reserved.
Derived from DummyPrint on August 11, 1991
Russ Atkinson (RRA) August 29, 1991 3:57 pm PDT
Michael Plass, July 6, 1993 9:49 am PDT
DIRECTORY RefTab, DecomposerRegistry, Atom, Basics, BasicTime, Commander, CommanderOps, Convert, CountedVM, Imager, ImagerError, ImagerBackdoor, ImagerColor, ImagerHighlightContext, ImagerHighlightContextBackdoor, ImagerInterpress, ImagerPixel, ImagerPixelArray, ImagerSample, ImagerTransformation, IO, ImagerSys, Process, Rope, SF, XeroxCompress;
HighlightDummy: CEDAR MONITOR
IMPORTS RefTab, DecomposerRegistry, Atom, BasicTime, Commander, CommanderOps, Convert, CountedVM, Imager, ImagerError, ImagerBackdoor, ImagerColor, ImagerHighlightContext, ImagerHighlightContextBackdoor, ImagerInterpress, ImagerPixel, ImagerPixelArray, ImagerSample, IO, ImagerSys, Process, Rope, XeroxCompress
= {
Context: TYPE = Imager.Context;
RasterSampleMap: TYPE = ImagerSample.RasterSampleMap;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Transformation: TYPE = ImagerTransformation.Transformation;
fastDimX10: NAT ¬ 110;
The "fast" dimension in tenths of an inch
slowDimX10: NAT ¬ 85;
The "slow" dimension in tenths of an inch
maxCopies: NAT ¬ 100;
The maximum number of copies to allow
Highlight Things
ByteTable: TYPE = PACKED ARRAY BYTE OF BYTE;
blackByteTable: REF ByteTable ¬ MakeByteTable[TRUE];
highlightByteTable: REF ByteTable ¬ MakeByteTable[FALSE];
MakeByteTable: PROC [black: BOOL] RETURNS [REF ByteTable] = {
new: REF ByteTable ¬ NEW[ByteTable ¬ ALL[0]];
FOR b: BYTE IN BYTE DO
bit: WORD ¬ 1;
bb: BYTE ¬ IF black THEN bb ¬ b / 2 ELSE b;
acc: BYTE ¬ (bb MOD 2)
+ ((bb / 4) MOD 2) * 2
+ ((bb / 16) MOD 2) * 4
+ ((bb / 64) MOD 2) * 8;
new[b] ¬ acc;
ENDLOOP;
RETURN [new];
};
scanMode: Imager.ScanMode ¬ [slow: right, fast: up];
Someday make this alterable
ImageSeparation: PROC
[context: Imager.Context, black: BOOL, srcMap: RasterSampleMap, dstMap: RasterSampleMap] = TRUSTED {
BytesPtr: TYPE = POINTER TO Basics.RawBytes;
box: SF.Box ¬ ImagerSample.GetBox[dstMap];
bitmapAsPixelArray: ImagerPixelArray.PixelArray = ImagerPixelArray.FromPixelMap[
pixelMap: ImagerPixel.MakePixelMap[dstMap],
box: ImagerSample.GetBox[dstMap],
scanMode: scanMode,
immutable: FALSE];
srcPtr: BytesPtr ¬ LOOPHOLE[ImagerSample.GetBase[srcMap].word];
dstPtr: BytesPtr ¬ LOOPHOLE[ImagerSample.GetBase[dstMap].word];
srcBitsPerLine: NAT = ImagerSample.GetBitsPerLine[srcMap];
srcBytesPerLine: NAT = srcBitsPerLine/8;
srcInc: CARDINAL = SIZE[Basics.RawBytes[srcBytesPerLine]];
dstBitsPerLine: NAT = ImagerSample.GetBitsPerLine[dstMap];
dstBytesPerLine: NAT = dstBitsPerLine/8;
dstInc: CARDINAL = SIZE[Basics.RawBytes[dstBytesPerLine]];
table: REF ByteTable = IF black THEN blackByteTable ELSE highlightByteTable;
lines: NAT = box.max.s - box.min.s;
IF dstBytesPerLine*2 > srcBytesPerLine THEN ERROR;
IF srcBitsPerLine MOD BITS[WORD] # 0 THEN ERROR;
IF dstBitsPerLine MOD BITS[WORD] # 0 THEN ERROR;
ImagerSample.Clear[dstMap];
FOR line: CARDINAL IN [0..lines) DO
nextSrcPtr: BytesPtr = srcPtr + srcInc;
nextDstPtr: BytesPtr = dstPtr + dstInc;
FOR bx: CARDINAL IN [0..dstBytesPerLine) DO
sByteL: BYTE ¬ srcPtr[bx*2];
sByteR: BYTE ¬ srcPtr[bx*2+1];
dByte: BYTE ¬ (table[sByteL]*16+table[sByteR]) MOD 256;
dstPtr[bx] ¬ dByte;
ENDLOOP;
srcPtr ¬ nextSrcPtr;
dstPtr ¬ nextDstPtr;
ENDLOOP;
Imager.MaskPixel[context: context, pa: XeroxCompress.CompressPixelArray[bitmapAsPixelArray]];
};
SwitchColors: PROC [map: RasterSampleMap] = {
<<table: >>
};
Buffer Things
AllocBuffer: ENTRY PROC [words: INT] RETURNS [CountedVM.Handle] = {
handle: CountedVM.Handle = CountedVM.SimpleAllocate[words];
RETURN [handle];
};
FreeBuffer: ENTRY PROC [handle: CountedVM.Handle]
RETURNS [CountedVM.Handle] = TRUSTED {
IF handle # NIL THEN CountedVM.Free[handle];
RETURN [NIL];
};
Imager Things
MakeSampleMap: ENTRY PROC
[handle: CountedVM.Handle, words: CARD, scans: NAT, items: NAT, bps: [1..2]]
RETURNS [RasterSampleMap] ~ TRUSTED {
map: RasterSampleMap ¬ NIL;
fSize: NAT ~ items;
sSize: NAT ~ scans;
wordsPerLine: CARD ~ (fSize * bps + BITS[WORD] - 1) / BITS[WORD];
pointer: POINTER ¬ handle.pointer;
IF (wordsPerLine * sSize) > words THEN ERROR;
map ¬ ImagerSample.UnsafeNewSampleMap[
box: [min: [0, 0], max: [sSize, fSize]],
bitsPerSample: bps, bitsPerLine: fSize*bps,
base: [word: LOOPHOLE[pointer, POINTER TO WORD], bit: 0], ref: handle,
words: words];
ImagerSample.Clear[map];
RETURN [map];
};
HighlightContextFromSampleMap: PROC [sampleMap: RasterSampleMap, spi: NAT, pph: REAL, highlight: Imager.ConstantColor, colorLookupTable: ImagerHighlightContextBackdoor.ColorLookupTable, iotparam: ROPE]
RETURNS [context: Context] = {
context ¬ ImagerHighlightContext.Create[
deviceSpaceSize: ImagerSample.GetSize[sampleMap],
scanMode: scanMode,
surfaceUnitsPerInch: [spi, spi],
pixelUnits: FALSE,
fontCacheName: $Print,
highlight: highlight];
ImagerSample.Clear[sampleMap];
ImagerHighlightContext.SetSampleMap[context, sampleMap];
ImagerHighlightContextBackdoor.SetColorLookupTable[context, colorLookupTable];
Imager.SetGray[context, 1];
Imager.SetWarn[context: context, warn: TRUE];
Imager.ClipRectangle[context, ImagerBackdoor.GetBounds[context]];
IF iotparam # NIL THEN {
Imager.PutProp[context, $IOTSpecificPSSetup, iotparam]; -- royal crock!
};
};
InstanceData: TYPE ~ DecomposerRegistry.InstanceData;
gunk: LIST OF REF ¬ NIL;
NVFeedbackProc: DecomposerRegistry.FeedbackProc ~ {
PROC [instance: InstanceData, key: ATOM, severity: Severity, info: REF];
err: IO.STREAM ~ instance.sequencer.err;
WITH info SELECT FROM
rope: ROPE => {err.PutRope["\n *** "]; err.PutRope[rope]; err.PutRope["\n"]};
text: REF TEXT => {err.PutRope["\n *** "]; err.PutText[text]; err.PutRope["\n"]};
ENDCASE => IF info # NIL THEN {
gunk ¬ CONS[info, gunk];
err.PutF[" !(%L%08xH%L) ", [rope["b"]], [cardinal[LOOPHOLE[info]]], [rope["B"]]];
};
IF severity = fatal THEN ERROR ABORTED;
};
NVGetAttrProc: DecomposerRegistry.GetAttrProc ~ {
PROC [sd: SequencerData, key: Attribute] RETURNS [ValueSeq];
WITH sd.private SELECT FROM
x: RefTab.Ref => RETURN [NARROW[RefTab.Fetch[x, key].val]];
ENDCASE => RETURN [NIL];
};
NVSetAttrProc: DecomposerRegistry.SetAttrProc ~ {
PROC [sd: SequencerData, key: Attribute, values: ValueSeq];
WITH sd.private SELECT FROM
x: RefTab.Ref => [] ¬ RefTab.Store[x, key, values];
ENDCASE;
};
PrintCommand: Commander.CommandProc = {
FileKind: TYPE = {unknown, interpress, postscript, genoa};
startPulses: BasicTime.Pulses ¬ BasicTime.GetClockPulses[];
showTimes: BOOL ¬ FALSE;
forceKind: FileKind ¬ unknown;
useMonitor: BOOL ¬ FALSE;
combined: BOOL ¬ FALSE;
switch: BOOL ¬ FALSE;
highlight: Imager.ConstantColor ¬ ImagerColor.ColorFromRGB[ [R: 1, G: 0, B: 0] ];
colorLookupTable: ImagerHighlightContextBackdoor.ColorLookupTable ¬ NIL;
spi: INT ¬ 300;
iotparam: ROPE ¬ NIL;
pph: REAL ¬ 10.0;
copies: INT ¬ 1;
debugging: BOOL ¬ FALSE;
allowCopiesToChange: BOOL ¬ TRUE;
ShiftArgs: PROC = {
IF argMax > 0 THEN {
FOR i: NAT IN [2..argMax) DO argv[i-1] ¬ argv[i]; ENDLOOP;
argMax ¬ argMax - 1;
argv[argMax] ¬ NIL;
};
};
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd
! CommanderOps.Failed => {msg ¬ errorMsg; GO TO failed};
];
argMax: NAT ¬ argv.argc;
buffer: REF TEXT = NEW[TEXT[256]];
outerHandle1: CountedVM.Handle ¬ NIL;
outerHandle2: CountedVM.Handle ¬ NIL;
ipOut: ImagerInterpress.Ref ¬ NIL;
Bye: PROC = {
IF ipOut # NIL THEN ImagerInterpress.Close[ipOut];
ipOut ¬ NIL;
outerHandle1 ¬ FreeBuffer[outerHandle1];
outerHandle2 ¬ FreeBuffer[outerHandle2];
};
WHILE argMax > 1 DO
arg: ROPE = argv[1];
ShiftArgs[];
IF Rope.Length[arg] = 0 THEN LOOP;
IF Rope.Match["-*", arg] THEN {
Switch processing
SELECT TRUE FROM
Rope.Match["-debug", arg, FALSE] => debugging ¬ TRUE;
Rope.Match["-times", arg, FALSE] => showTimes ¬ TRUE;
Rope.Match["-notimes", arg, FALSE] => showTimes ¬ FALSE;
Rope.Match["-ps", arg, FALSE] => forceKind ¬ postscript;
Rope.Match["-ip", arg, FALSE] => forceKind ¬ interpress;
Rope.Match["-genoa", arg, FALSE] => forceKind ¬ genoa;
Rope.Match["-auto", arg, FALSE] => forceKind ¬ unknown;
Rope.Match["-monitor", arg, FALSE] => useMonitor ¬ TRUE;
Rope.Match["-Hb", arg, FALSE] =>
highlight ¬ ImagerColor.ColorFromRGB[ [R: 0, G: 0, B: 1] ];
Rope.Match["-Hg", arg, FALSE] =>
highlight ¬ ImagerColor.ColorFromRGB[ [R: 0, G: 1, B: 0] ];
Rope.Match["-Hr", arg, FALSE] =>
highlight ¬ ImagerColor.ColorFromRGB[ [R: 1, G: 0, B: 0] ];
Rope.Match["-table", arg, FALSE] => TRUSTED {
name: ROPE ~ argv[1];
stream: IO.STREAM = ImagerSys.OpenInputFile[name ! ImagerSys.FileError => CommanderOps.Failed[explanation]];
IF stream = NIL THEN CommanderOps.Failed[Rope.Concat[name, " file missing"]] ELSE {
t: REF ImagerHighlightContextBackdoor.ColorLookupArray ~ NEW[ImagerHighlightContextBackdoor.ColorLookupArray]; -- should use the right zone for this.
nBytes: NAT = BYTES[ImagerHighlightContextBackdoor.ColorLookupArray];
bytesRead: INT ~ IO.UnsafeGetBlock[stream, [base: LOOPHOLE[t], startIndex: 0, count: nBytes]];
IF bytesRead # nBytes THEN {
CommanderOps.Failed[Rope.Concat[name, ": table file is too short"]];
};
colorLookupTable ¬ NEW[ImagerHighlightContextBackdoor.ColorLookupTableRep ¬ [
toner: Atom.MakeAtom[name],
tablePointer: LOOPHOLE[t],
ref: t
]];
IO.Close[stream];
ShiftArgs[];
};
};
Rope.Match["-combined", arg, FALSE] => combined ¬ TRUE;
Rope.Match["-separate", arg, FALSE] => combined ¬ FALSE;
Rope.Match["-switch", arg, FALSE] => switch ¬ TRUE;
Rope.Match["-out", arg, FALSE] => IF argMax >= 1 THEN {
name: ROPE ¬ argv[1];
ShiftArgs[];
IF ipOut # NIL THEN {ImagerInterpress.Close[ipOut]; ipOut ¬ NIL};
ipOut ¬ ImagerInterpress.Create[name !
ImagerSys.FileError => {msg ¬ explanation; GO TO failed};
Imager.Error => {msg ¬ error.explanation; GO TO failed};
];
};
Rope.Match["-copies", arg, FALSE] => {
syntax: BOOL ¬ FALSE;
copies ¬ 0;
IF argMax >= 1 THEN
copies ¬ Convert.CardFromRope[argv[1]
! Convert.Error => {syntax ¬ TRUE; CONTINUE}];
IF copies NOT IN [1..maxCopies] THEN {
IF syntax
THEN IO.PutRope[cmd.out, "Can't parse nCopies number, 1 used\n"]
ELSE IO.PutF1[cmd.out, "Unacceptable nCopies (%g), 1 used\n", [integer[copies]] ];
copies ¬ 1;
};
ShiftArgs[];
allowCopiesToChange ¬ FALSE;
};
Rope.Match["-spi", arg, FALSE] => {
syntax: BOOL ¬ FALSE;
spi ¬ 0;
IF argMax >= 1 THEN
spi ¬ Convert.CardFromRope[argv[1]
! Convert.Error => {syntax ¬ TRUE; CONTINUE}];
IF spi < 1 OR spi > 1200 THEN {
IF syntax
THEN IO.PutRope[cmd.out, "Can't parse spi number, 300 used\n"]
ELSE IO.PutF1[cmd.out, "Unacceptable spi (%g), 300 used\n", [integer[spi]] ];
spi ¬ 300;
};
ShiftArgs[];
};
Rope.Match["-iot", arg, FALSE] => {
syntax: BOOL ¬ FALSE;
iotparam ¬ NIL;
IF argMax >= 1 THEN iotparam ¬ argv[1] ELSE syntax ¬ TRUE;
ShiftArgs[];
};
Rope.Match["-pph", arg, FALSE] => {
syntax: BOOL ¬ FALSE;
pph ¬ 0.0;
IF argMax >= 1 THEN
pph ¬ Convert.RealFromRope[argv[1]
! Convert.Error => {syntax ¬ TRUE; CONTINUE}];
IF syntax OR pph < 0.1 OR pph > 1000.0 THEN {
IF syntax
THEN IO.PutRope[cmd.out, "Can't parse pph number, 10.0 used\n"]
ELSE IO.PutF1[cmd.out, "Unacceptable pph (%g), 10.0 used\n", [real[spi]] ];
pph ¬ 10.0;
};
ShiftArgs[];
};
ENDCASE => IO.PutF1[cmd.out, "Ignoring invalid switch: %g\n", [rope[arg]]];
LOOP;
};
{
ENABLE ImagerSys.FileError => {msg ¬ explanation; GO TO oops};
fastItems: NAT = (spi*fastDimX10+9)/10;
slowItems: NAT = (spi*slowDimX10+9)/10;
scanLines: NAT = slowItems;
dstWordsWide: NAT = (fastItems+BITS[WORD]-1) / BITS[WORD];
dstBitsWide: NAT = dstWordsWide*BITS[WORD];
srcWordsWide: NAT = 2*dstWordsWide;
srcBitsWide: NAT = srcWordsWide*BITS[WORD];
srcWords: INT ~ srcWordsWide*scanLines;
dstWords: INT ~ dstWordsWide*scanLines;
handle1: CountedVM.Handle ¬ outerHandle1 ¬ AllocBuffer[srcWords];
handle2: CountedVM.Handle ¬ outerHandle2 ¬ AllocBuffer[dstWords];
out: STREAM = cmd.out;
fileName: Rope.ROPE ¬ arg;
map: RasterSampleMap
¬ MakeSampleMap[handle1, srcWords, scanLines, dstBitsWide, 2];
dMap: RasterSampleMap
¬ MakeSampleMap[handle2, dstWords, scanLines, dstBitsWide, 1];
context: Context ¬ HighlightContextFromSampleMap[map, spi, pph, highlight, colorLookupTable, iotparam];
fileStream: IO.STREAM ¬ NIL;
fileKind: FileKind ¬ unknown;
pixelsToMeters: ImagerTransformation.Transformation = ImagerBackdoor.GetTransformation[context: context, from: device, to: client];
DoSeparations: PROC = {
PageAction1: PROC [context: Imager.Context] ~ {
Imager.ConcatT[context, pixelsToMeters];
Imager.SetGray[context, 1];
ImageSeparation[context, TRUE, map, dMap];
Imager.SetColor[context, highlight];
ImageSeparation[context, FALSE, map, dMap];
};
IF switch THEN SwitchColors[map];
SELECT TRUE FROM
ipOut = NIL => {};
Just for timing
combined =>
For shipping to a Siena as a combined map
ImagerInterpress.DoPage[ipOut, PageAction1, 1];
ENDCASE => {
PageAction2: PROC [context: Imager.Context] ~ {
Imager.ConcatT[context, pixelsToMeters];
Imager.SetPriorityImportant[context, FALSE];
ImageSeparation[context, isBlack, map, dMap];
};
isBlack: BOOL ¬ TRUE;
For shipping to IP printers as separations
ImagerInterpress.DoPage[ipOut, PageAction2, 1];
isBlack ¬ FALSE;
ImagerInterpress.DoPage[ipOut, PageAction2, 1];
};
};
Require: PROC [rope: ROPE] ~ {
[] ¬ CommanderOps.DoCommand[Rope.Cat["Require Cedar ", rope, " ", rope], cmd];
};
instanceData: DecomposerRegistry.InstanceData ¬ NIL;
best: DecomposerRegistry.DecomposerData ¬ NIL;
prob: REAL ¬ 0.0;
sequencerProcs: REF DecomposerRegistry.SequencerProcs ~ NEW[DecomposerRegistry.SequencerProcs ¬ [
feedback: NVFeedbackProc,
getAttr: NVGetAttrProc,
setAttr: NVSetAttrProc
]];
streamIn: IO.STREAM = ImagerSys.OpenInputFile[fileName];
seq: DecomposerRegistry.SequencerData ¬ NEW[DecomposerRegistry.SequencerDataRep ¬ [
in: streamIn,
out: cmd.out,
err: cmd.err,
title: fileName,
file: ImagerSys.StreamFileName[streamIn],
procs: sequencerProcs,
private: RefTab.Create[]
]];
IF Rope.Match["*.ip*", fileName, FALSE] THEN Require["IPRegister"];
IF Rope.Match["*.interpress*", fileName, FALSE] THEN Require["IPRegister"];
IF Rope.Match["*.cip*", fileName, FALSE] THEN Require["IPRegister"];
IF Rope.Match["*.ps*", fileName, FALSE] THEN Require["PSRegister"];
FOR tail: LIST OF DecomposerRegistry.DecomposerData ¬ DecomposerRegistry.Enumerate[], tail.rest UNTIL tail = NIL DO
d: DecomposerRegistry.DecomposerData ~ tail.first;
p: REAL ~ d.procs.guess[d, seq];
IF p > prob THEN {prob ¬ p; best ¬ d};
ENDLOOP;
IF best = NIL THEN {CommanderOps.Failed[Rope.Concat["could not guess file type of ", fileName]]} ELSE {
instanceData ¬ best.procs.open[best, seq];
IF instanceData = NIL THEN CommanderOps.Failed["Failed to open"];
IF instanceData # NIL THEN {
instanceData.copies ¬ 1;
instanceData.copy ¬ 1;
instanceData.pages ¬ -1;
instanceData.procs.attributes[instanceData];
};
};
{
seconds: REAL = BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[] - startPulses];
IO.PutF1[out, "%4.2f elapsed seconds to alloc map and open", [real[seconds]]];
IO.PutF1[out, " %g\n", [rope[fileName]]];
startPulses ¬ BasicTime.GetClockPulses[];
};
SELECT fileKind FROM
unknown => {
ENABLE {
UNWIND => Bye[];
Imager.Warning => {
IO.PutF[cmd.out, "-- Imager.Warning: %g, %g\n",
[atom[ImagerError.AtomFromErrorCode[error.code]]], [rope[error.explanation]]];
RESUME;
};
Imager.Error => {
IO.PutF[cmd.out, "\n** Imager.Error: %g, %g\n",
[atom[ImagerError.AtomFromErrorCode[error.code]]], [rope[error.explanation]]];
IF NOT debugging THEN GO TO Out;
};
IO.EndOfStream, IO.Error => {
IO.PutRope[out, "\n** Unknown IO Error\n"];
IF NOT debugging THEN GO TO Out;
};
ImagerSys.FileError => {
IO.PutF[cmd.out, "\n** ImagerSys.FileError inside of master: %g, %g\n",
[atom[code]], [rope[explanation]]];
IF NOT debugging THEN GO TO Out;
};
};
IO.PutF1[out, "file: %g", [rope[fileName]] ];
IO.PutF1[out, ", spi: %g", [cardinal[spi]] ];
IO.PutF1[out, ", copies: %g\n", [cardinal[copies]] ];
FOR j: INT IN [1 .. copies] DO
pageCount: INT ¬ 0;
done: BOOL ¬ FALSE;
IO.PutF1[out, "Copy %g: ", [cardinal[j]] ];
instanceData.copy ¬ j;
FOR i: INT IN [1..999999) UNTIL done DO
pageFlags: DecomposerRegistry.PageFlags;
pagePulses: BasicTime.Pulses ¬ BasicTime.GetClockPulses[];
Process.CheckForAbort[];
ImagerSample.Clear[map];
instanceData.page ¬ i;
{P: PROC ~ {
instanceData.context ¬ context;
pageFlags ¬ instanceData.procs.page[instanceData, instanceData.page, instanceData.copy];
}; Imager.DoSaveAll[context, P] };
IF pageFlags.last THEN done ¬ TRUE;
IF pageFlags.imaged THEN {
IO.PutF1[out, " [%g", [cardinal[i]] ];
pageCount ¬ pageCount + 1;
DoSeparations[];
IO.PutChar[out, ']];
IF showTimes THEN {
endPagePulses: BasicTime.Pulses ¬ BasicTime.GetClockPulses[];
seconds: REAL = BasicTime.PulsesToSeconds[endPagePulses-pagePulses];
IO.PutF1[out, " (%4.2f)", [real[seconds]] ];
};
};
ENDLOOP;
IO.PutChar[out, '\n];
{
nextTime: BasicTime.Pulses = BasicTime.GetClockPulses[];
PerfLine[out, nextTime-startPulses, pageCount];
startPulses ¬ nextTime;
};
ENDLOOP;
instanceData.procs.close[instanceData];
GO TO Out;
EXITS Out => Bye[];
};
ENDCASE => {msg ¬ "Unknown page description language"; GO TO oops};
Bye[];
EXITS oops => {Bye[]; GO TO failed};
};
ENDLOOP;
EXITS failed => result ¬ $Failure;
};
PerfLine: PROC [out: STREAM, pulses: BasicTime.Pulses, pages: INT] = {
seconds: REAL = BasicTime.PulsesToSeconds[pulses];
IO.PutRope[out, "Done"];
IF seconds > 0.0 THEN {
IO.PutF1[out, " in %4.2f seconds", [real[seconds]] ];
IF pages > 0 THEN IO.PutF1[out, " (ppm: %3.1f)", [real[(pages*60) / seconds]] ];
};
IO.PutRope[out, ".\n\n"];
};
Commander.Register[key: "HighlightDummy", proc: PrintCommand,
doc: "HighlightDummy {switch | filename}
-spi # of spots per inch (default: 300)
-copies # of copies to make (default: 1)
-times show per page times
-notimes don't show per page times
-ps force PostScript interpretation
-ip force Interpress interpretation
-out <F> specify output file name as <F>
-combined combined black & highlight output
-separate put out separations (default)
-switch switch black & highlight
-Hr make highlight color red (default)
-Hg make highlight color green
-Hb make highlight color blue
-table <filename> use table-driven mapping
-auto automatically determine file type (default)
-debug allow errors to remain uncaught
-iot ($(cat LIB/IOTSpecific.ps)) use IOT-specific PS setup
"];
}.