IPConvertersCommand.mesa
Copyright Ó 1984, 1985, 1986, 1989, 1992, 1993 by Xerox Corporation. All rights reserved.
Michael Plass, June 29, 1993 11:12 am PDT
Tim Diebert: February 18, 1986 10:36:06 am PST
Pier, September 21, 1987 5:29:33 pm PDT
Eric Nickell February 19, 1986 2:56:26 pm PST
Doug Wyatt, July 22, 1986 6:28:45 pm PDT
Maureen Stone, January 8, 1988 5:21:44 pm PST
Jean-Marc Frailong January 20, 1988 12:18:56 pm PST
Bloomenthal, July 23, 1991 2:38 pm PDT
Beretta:PARC:Xerox (8*923-4484) August 31, 1989 4:31:06 pm PDT
Last tweaked by Mike Spreitzer on April 17, 1990 11:22:01 am PDT
Willie-s, June 28, 1993 2:04 pm PDT
DIRECTORY Commander, Convert, CountedVM, FileNames, FS, ImagerBrick, Imager, ImagerBackdoor, ImagerError, ImagerFontFilter, ImagerInterpress, ImagerPixel, ImagerPixelArray, ImagerPrintContext, ImagerSample, ImagerTransformation, InterpressInterpreter, IO, IPConverters, IPConvertersPrivate, PFS, PrintColor, Process, Real, RealFns, Rope, RuntimeError, SF, XeroxCompress;
IPConvertersCommand: CEDAR PROGRAM
IMPORTS Commander, Convert, FileNames, FS, ImagerBackdoor, ImagerBrick, Imager, ImagerError, ImagerPrintContext, ImagerInterpress, ImagerPixel, ImagerPixelArray, ImagerSample, ImagerTransformation, InterpressInterpreter, IO, PFS, Process, Real, RealFns, Rope, RuntimeError, XeroxCompress
EXPORTS IPConverters, IPConvertersPrivate
~ BEGIN OPEN IPConverters;
Types
ROPE: TYPE ~ Rope.ROPE;
Transformation: TYPE ~ ImagerTransformation.Transformation;
Constants
inch: REAL = 0.0254; -- inches->meters conversion factor
ravenPPI: REAL = 300.0; -- pixels per inch on a Raven printer for compression
ravenScanMode: ImagerTransformation.ScanMode = [slow: right, fast: up];
defaultPageWidth: REAL ¬ 8.5*inch; -- for normal sized paper
defaultPageHeight: REAL ¬ 11*inch; -- for normal sized paper
aisMargin: REAL ¬ 0.25*inch; -- offset of AIS images on regular-sized paper
headerSampled: ROPE ¬ "Interpress/Xerox/3.0 "; -- IP header for sampled images
aisCaptionFont: ROPE ¬ "xerox/pressfonts/helvetica-mir"; -- to put caption in AIS files
aisCaptionLoc: Imager.VEC ¬ [72, 9]; -- where the caption should be in AIS files
xcFontBase: Rope.ROPE ¬ "Xerox/xc1-2-2/"; -- prefix to be used for Xerox product fonts
Client interface to Interpress files
BrickValue: TYPE ~ ARRAY [0..4) OF PACKED ARRAY [0..4) OF [0..16);
coarseBrickValues: BrickValue ¬ [
-- for compressed IP masters
[00, 01, 13, 14],
[08, 02, 03, 15],
[09, 10, 04, 05],
[07, 11, 12, 06]
];
MakeSimpleBrick:
PROC [t: BrickValue]
RETURNS [ImagerBrick.Brick] ~ {
b: ImagerSample.SampleMap ¬ ImagerSample.NewSampleMap[box: [max: [4, 4]], bitsPerSample: 8];
FOR s:
NAT
IN [0..4)
DO
FOR f:
NAT
IN [0..4)
DO
ImagerSample.Put[b, [s, f], t[s][f]];
ENDLOOP;
ENDLOOP;
RETURN [[maxSample: 15, sampleMap: b, phase: 0]]
};
InterpressToCompressedIP:
PUBLIC
PROC [inputName: Rope.
ROPE, interpress: ImagerInterpress.Ref, beginPage, endPage: ProgressProc, msg:
IO.
STREAM, pageWidth, pageHeight:
REAL, screen: Screen ¬ dot]
RETURNS [failed:
BOOL ¬
FALSE] ~ {
RETURN [NewInterpressToCompressedIP[inputName, interpress, beginPage, endPage, msg, pageWidth, pageHeight, screen]]
};
tonerUniverse: PrintColor.TonerUniverse
~ [black: TRUE, cyan: FALSE, magenta: FALSE, yellow: FALSE];
xerox300spot: ImagerBrick.FilterProc ~ {
shape: REAL ~ 0.45;
tx: REAL ¬ RealFns.CosDeg[x*180+0.314156];
ty: REAL ¬ RealFns.CosDeg[y*180+0.271828];
sym: REAL ¬ 0.5 - 0.25 * (tx + ty);
asym: REAL ¬ 0.5 - 0.25 * (shape*tx + (1.0-shape)*ty);
mix: REAL ¬ (4 * sym * (1.0-sym)) ** 2;
result: REAL ¬ mix * asym + (1.0-mix) * sym;
RETURN [-result]
};
MakeDotBrick:
PROC [pixelsPerDot:
REAL, degrees:
REAL, allowedRelativeError:
REAL ¬ 0.05, minLevels:
CARD ¬ 16]
RETURNS [ImagerBrick.Brick] = {
m: Transformation ~ ImagerTransformation.Cat[ImagerTransformation.Scale[pixelsPerDot*0.5], ImagerTransformation.Rotate[degrees]];
brickSpec: ImagerBrick.BrickSpec ~ ImagerBrick.BrickSpecFromTransformedRectangle[2, 2, m, allowedRelativeError, minLevels];
brick: ImagerBrick.Brick = ImagerBrick.BrickFromFilter[brickSpec: brickSpec, filter: xerox300spot];
RETURN [brick]
};
NewInterpressToCompressedIP:
PROC [
inputName: ROPE,
interpress: ImagerInterpress.Ref,
beginPage, endPage: ProgressProc,
msg: IO.STREAM,
pageWidth, pageHeight: REAL,
screen: Screen ¬ dot,
dotsPerInch: REAL ¬ 0.0, -- default causes pixelsPerDot ¬ 5.525
screenAngleInDegrees: REAL ¬ 45.0,
ppi: REAL ¬ ravenPPI,
scanMode: ImagerTransformation.ScanMode ¬ ravenScanMode]
RETURNS [failed: BOOL ¬ FALSE]
~ {
Generate a compressed master. IP master should be version 2.0 (otherwise why compress it ?). Errors are printed on msg if non-NIL, else raise errors. pageWidth and pageHeight (in meters) should be given as there is no way of recovering them from the source IP master (????).
pixelsPerDot: REAL ¬ IF dotsPerInch = 0.0 THEN 5.525 ELSE ppi/dotsPerInch;
ppm: REAL ¬ ppi/inch;
Log: InterpressInterpreter.LogProc ~
{ IPReadLog[msg, class, ImagerError.AtomFromErrorCode[code], explanation] };
input: InterpressInterpreter.Master = InterpressInterpreter.Open[FileNames.ResolveRelativePath[inputName], Log];
sIn: REAL = SELECT scanMode.slow FROM up, down => pageHeight, ENDCASE => pageWidth;
fIn: REAL = SELECT scanMode.fast FROM up, down => pageHeight, ENDCASE => pageWidth;
size: SF.Vec = [s: Real.Round[sIn*ppm], f: Real.Round[fIn*ppm]];
bitmap: ImagerSample.RasterSampleMap = ImagerSample.ObtainScratchMap[[max: size]];
bitmapAsPixelArray: ImagerPixelArray.PixelArray = ImagerPixelArray.FromPixelMap[pixelMap: ImagerPixel.MakePixelMap[bitmap], box: ImagerSample.GetBox[bitmap], scanMode: [slow: right, fast: up], immutable: FALSE];
halftoneProperties: PrintColor.HalftoneProperties ~
IF screen = line
THEN (
LIST[[type: $linescreen, toner: black, brick: MakeSimpleBrick[coarseBrickValues]]]
)
ELSE (
LIST[[type: $dotscreen, toner: black, brick: MakeDotBrick[pixelsPerDot: pixelsPerDot, degrees: screenAngleInDegrees, allowedRelativeError: 0.1, minLevels: 60]]]
);
bitmapContext: Imager.Context = ImagerPrintContext.Create[deviceSpaceSize: size, scanMode: scanMode, surfaceUnitsPerInch: [ppi, ppi], logicalDevice: 0, halftoneProperties: halftoneProperties];
pixelsToMeters: Transformation = ImagerBackdoor.GetTransformation[context: bitmapContext, from: device, to: client];
ImagerPrintContext.SetBitmap[context: bitmapContext, bitmap: bitmap];
ImagerPrintContext.SetSeparation[bitmapContext, black];
FOR i:
INT
IN [1..input.pages]
DO
PageAction:
PROC [context: Imager.Context] ~ {
Imager.ConcatT[context, pixelsToMeters];
Imager.SetPriorityImportant[context, FALSE];
Imager.MaskPixel[context: context, pa: XeroxCompress.CompressPixelArray[bitmapAsPixelArray]]
};
Process.CheckForAbort[];
failed ¬ beginPage[i, input.pages];
IF failed THEN EXIT;
ImagerSample.Clear[bitmap];
InterpressInterpreter.DoPage[master: input, page: i, context: bitmapContext, log: Log];
Process.CheckForAbort[];
ImagerInterpress.DoPage[interpress, PageAction, 1];
Process.CheckForAbort[];
failed ¬ endPage[i, input.pages];
IF failed THEN EXIT;
ENDLOOP;
ImagerSample.ReleaseScratchMap[bitmap];
};
Client interface from Interpress files
IPReadError:
PUBLIC
ERROR [class:
INT, code:
ATOM, explanation: Rope.
ROPE] ~
CODE;
Raised in all procs that read IP masters and have no message stream when the error is not trivial (# classMasterWarning, classAppearanceWarning, classComment)
IPReadLog:
PROC [msg:
IO.
STREAM, class:
INT, code:
ATOM, explanation:
ROPE] ~ {
Log the error in clear if msg stream is present otherwise convert into an error.
IF msg=
NIL
THEN {
SELECT class
FROM
InterpressInterpreter.classMasterError, InterpressInterpreter.classAppearanceError => ERROR IPReadError[class, code, explanation];
InterpressInterpreter.classMasterWarning, InterpressInterpreter.classAppearanceWarning, InterpressInterpreter.classComment => NULL; -- ignore those
ENDCASE => ERROR IPReadError[class, code, explanation];
}
ELSE {
msg.PutRope[
SELECT class
FROM
InterpressInterpreter.classMasterError => "Master Error: ",
InterpressInterpreter.classMasterWarning => "Master Warning: ",
InterpressInterpreter.classAppearanceError => "Appearance Error: ",
InterpressInterpreter.classAppearanceWarning => "Appearance Warning: ",
InterpressInterpreter.classComment => "Comment: ",
ENDCASE => Rope.Cat["Class ", Convert.RopeFromInt[class], " Error: "]
];
msg.PutRope[explanation];
msg.PutRope[" . . . "];
};
};
XC Font translation
CH:
PROC [char:
CHAR]
RETURNS [
WORD] ~
INLINE {
RETURN [
ORD[char]]};
XC:
PROC [set: [0..256), code: [0..256)]
RETURNS [
WORD] ~ {
RETURN [set*256+code]};
C1:
PROC [c:
CHAR, set: [0..256), code: [0..256)]
RETURNS [ImagerFontFilter.CharRangeMap] ~ {
RETURN [[bc: CH[c], ec: CH[c], newbc: XC[set, code]]]
};
classicModernEtAl: LIST OF ROPE ¬ LIST["Classic", "Modern"];
timesRomanEtAl:
LIST
OF
LIST
OF
ROPE ¬
LIST[
LIST["TimesRoman", "Classic"],
LIST["Helvetica", "Modern"],
LIST["Gacha", "XeroxBook"],
LIST["Tioga", "Classic"],
LIST["Laurel", "Classic"]];
mrrEtAl:
LIST
OF
ROPE ¬
LIST["-mrr", "-mir-italic", "-bir-bold-italic", "-brr-bold"];
alphaMap: ImagerFontFilter.CharacterCodeMap ~
LIST [
[bc: CH[' ], ec: CH['~], newbc: CH[' ]]
];
mathMap: ImagerFontFilter.CharacterCodeMap ¬
LIST [
C1['©, 0, 323B],
C1['®, 0, 322B]
];
oisMap: ImagerFontFilter.CharacterCodeMap ¬
LIST [
[bc: CH['a], ec: CH['~], newbc: CH['a]],
[bc: CH['.], ec: CH[']], newbc: CH['.]],
[bc: CH['%], ec: CH[',], newbc: CH['%]],
[bc: CH['-], ec: CH['-], newbc: XC[357B, 42B]],
[bc: CH[' ], ec: CH['!], newbc: CH[' ]],
[bc: CH['\"], ec: CH['\"], newbc: XC[0, 271B]],
[bc: CH['#], ec: CH['#], newbc: CH['#]],
[bc: CH['$], ec: CH['$], newbc: XC[0, 244B]],
[bc: CH['^], ec: CH['^], newbc: XC[0, 255B]],
[bc: CH['←], ec: CH['←], newbc: XC[0, 254B]],
C1['\030, 357B, 45B],
C1['\267, 357B, 146B],
C1['\265, 41B, 172B],
C1['\140, 0, 140B],
C1[', 357B, 064B],
C1[', 357B, 065B],
];
xc1Map: ImagerFontFilter.FontMap ¬ MakeXC1map[];
This is what all this section is about...
MakeXC1map:
PROC
RETURNS [f: ImagerFontFilter.FontMap] ~ {
Enter: PROC [e: ImagerFontFilter.FontMapEntry] ~ {f ¬ CONS[e, f]};
FOR family:
LIST
OF
ROPE ¬ classicModernEtAl, family.rest
UNTIL family =
NIL
DO
FOR face:
LIST
OF
ROPE ¬ mrrEtAl, face.rest
UNTIL face =
NIL
DO
Enter[[
inputName: Rope.Cat["Xerox/Pressfonts/", family.first, face.first.Substr[0, 4]],
output: LIST[[newName: Rope.Cat[xcFontBase, family.first, face.first.Substr[4]], charMap: oisMap]]
]];
ENDLOOP;
ENDLOOP;
FOR family:
LIST
OF
LIST
OF
ROPE ¬ timesRomanEtAl, family.rest
UNTIL family =
NIL
DO
FOR face:
LIST
OF
ROPE ¬ mrrEtAl, face.rest
UNTIL face =
NIL
DO
Enter[[
inputName: Rope.Cat["Xerox/Pressfonts/", family.first.first, face.first.Substr[0, 4]],
output: LIST[[newName: Rope.Cat[xcFontBase, family.first.rest.first, face.first.Substr[4]], charMap: oisMap]],
warn: TRUE
]];
ENDLOOP;
ENDLOOP;
Enter[[
inputName: "Xerox/Pressfonts/Logo-mrr",
output: LIST[[newName: Rope.Concat[xcFontBase, "Logotypes-Xerox"], charMap: alphaMap]]
]];
Enter[[
inputName: "Xerox/Pressfonts/Math-mrr",
output: LIST[[newName: Rope.Concat[xcFontBase, "Modern"], charMap: mathMap]]
]];
Enter[[
inputName: "Xerox/Pressfonts/Math-mir",
output: LIST[[newName: Rope.Concat[xcFontBase, "Modern-italic"], charMap: mathMap]]
]];
};
Command level
InterpressToCompressedIPAction:
PUBLIC ActionProc ~ {
BeginPage: ProgressProc ~ {cmd.out.PutF1["[%g", IO.int[pageNumber]]};
EndPage: ProgressProc ~ {cmd.out.PutRope["] "]};
output: ImagerInterpress.Ref ~ ImagerInterpress.Create[outputName, "Interpress/Xerox/2.0 "];
stream: IO.STREAM ¬ IO.RIS[cmd.commandLine];
ppi: REAL ¬ ravenPPI;
scanMode: ImagerTransformation.ScanMode ¬ ravenScanMode;
screen: Screen ¬ dot;
screenAngle: REAL ¬ 45.0;
dotsPerInch: REAL ¬ 0.0;
FOR tok:
ROPE ¬ GetFileNameToken[stream], GetFileNameToken[stream]
UNTIL tok =
NIL
DO
SELECT
TRUE
FROM
Rope.Equal[tok, "-ppi", FALSE] => {ppi ¬ IO.GetReal[stream]};
Rope.Equal[tok, "-portrait", FALSE] => {scanMode ¬ [slow: down, fast: right]};
Rope.Equal[tok, "-landscape", FALSE] => {scanMode ¬ [slow: right, fast: up]};
Rope.Equal[tok, "-lineScreen", FALSE] => {screen ¬ line};
Rope.Equal[tok, "-dotScreen", FALSE] => {screen ¬ dot};
Rope.Equal[tok, "-screenAngle", FALSE] => screenAngle ¬ IO.GetReal[stream];
Rope.Equal[tok, "-dotsPerInch", FALSE] => dotsPerInch ¬ IO.GetReal[stream];
ENDCASE => NULL;
ENDLOOP;
[] ¬ NewInterpressToCompressedIP[inputName, output, BeginPage, EndPage, cmd.out, defaultPageWidth, defaultPageHeight, screen, dotsPerInch, screenAngle, ppi, scanMode];
ImagerInterpress.Close[output];
};
FindFullName:
PROC [inputName:
ROPE]
RETURNS [
ROPE] ~ {
fullFName: ROPE ¬ NIL;
fullFName ¬ FS.FileInfo[inputName].fullFName;
RETURN [fullFName]
};
GetCmdToken:
PROC [stream:
IO.
STREAM]
RETURNS [rope:
ROPE ¬
NIL] = {
CmdTokenBreak:
PROC [char:
CHAR]
RETURNS [
IO.CharClass] = {
IF char = '← OR char = '[ OR char = '] THEN RETURN [break];
IF char = ' OR char = '\t OR char = ', OR char = '; OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
rope ¬ stream.GetTokenRope[CmdTokenBreak ! IO.EndOfStream => CONTINUE].token;
};
GetFileNameToken:
PROC [stream:
IO.
STREAM]
RETURNS [rope:
ROPE ¬
NIL] = {
FileNameTokenBreak:
PROC [char:
CHAR]
RETURNS [
IO.CharClass] = {
IF char = '← THEN RETURN [break];
IF char = ' OR char = '\t OR char = ', OR char = '; OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
rope ¬ stream.GetTokenRope[FileNameTokenBreak ! IO.EndOfStream => CONTINUE].token;
};
RealFromRope:
PROC [rope:
ROPE]
RETURNS [real:
REAL] = {
oops: BOOL ¬ FALSE;
real ¬ Convert.RealFromRope[rope ! Convert.Error => {oops ¬ TRUE; CONTINUE}];
IF oops THEN {oops ¬ FALSE; real ¬ Convert.IntFromRope[rope ! Convert.Error => {oops ¬ TRUE; CONTINUE}]};
IF oops THEN Complain[Rope.Concat["Number expected: ", rope]];
};
Complain:
PUBLIC
ERROR [complaint:
ROPE] ~
CODE;
MakeOutputName:
PROC [inputName:
ROPE, doc:
ROPE]
RETURNS [
ROPE] ~ {
Relying on doc, and having the install files register commands seems unsafe. Jules
start: INT ¬ Rope.Index[s1: doc, s2: " to "]+4;
end: INT ¬ Rope.SkipTo[s: doc, pos: start, skip: " \n\t"];
cp: FS.ComponentPositions;
isAIS: BOOL ¬ Rope.Equal[Rope.Substr[doc, start, end-start], "ais", FALSE];
[inputName, cp] ¬ FS.ExpandName[inputName];
RETURN [Rope.Cat[
Rope.Substr[inputName, cp.base.start, cp.base.length],
IF isAIS THEN NIL ELSE ".",
IF isAIS THEN NIL ELSE Rope.Substr[doc, start, end-start]
]]
};
Command:
PUBLIC Commander.CommandProc ~ {
refAction: REF ActionProc ~ NARROW[cmd.procData.clientData];
stream: IO.STREAM ¬ IO.RIS[cmd.commandLine];
firstToken: ROPE ¬ GetFileNameToken[stream];
quiet: BOOL ¬ Rope.Equal[firstToken, "-q", FALSE];
outputName:
ROPE ¬
FileNames.ResolveRelativePath[IF quiet THEN GetFileNameToken[stream] ELSE firstToken];
secondTokenIndex: INT ¬ IO.GetIndex[stream];
gets: ROPE ¬ GetFileNameToken[stream];
inputName: ROPE ¬ NIL;
IF
NOT ( gets.Equal["¬"]
OR gets.Equal["←"] )
THEN {
inputName ¬ outputName;
outputName ¬ NIL;
stream.SetIndex[secondTokenIndex];
}
ELSE {inputName ¬ FileNames.ResolveRelativePath[GetFileNameToken[stream]]};
IF inputName = NIL THEN RETURN[result: $Failure, msg: cmd.procData.doc];
inputName ¬ FindFullName[inputName !
FS.Error => {
IF error.group = user THEN {result ¬ $Failure; msg ¬ error.explanation; GOTO Quit}
}];
IF outputName = NIL THEN outputName ¬ MakeOutputName[inputName, cmd.procData.doc];
cmd.out.PutRope["Reading "];
cmd.out.PutRope[inputName];
cmd.out.PutRope[" . . . "];
IF quiet THEN cmd.commandLine ¬ NIL;
refAction[inputName, outputName, cmd, stream !
Complain => {result ¬ $Failure; msg ¬ complaint; GOTO Quit};
FS.Error => {
IF error.group = user THEN {result ¬ $Failure; msg ¬ error.explanation; GOTO Quit}
}
];
outputName ¬ FindFullName[outputName !
FS.Error => {
outputName ¬ "Output file(s)"; CONTINUE};
];
cmd.out.PutRope[outputName];
cmd.out.PutRope[" written.\n"];
EXITS Quit => NULL
};
Not used anywhere (but not removed in case...)
Bound: TYPE ~ RECORD [first, last: INT];
int: Bound ~ [INT.FIRST, INT.LAST];
nat: Bound ~ [NAT.FIRST, NAT.LAST];
RealToNum:
PROC [real:
REAL, bounds: Bound ¬ int, name:
ROPE ¬
NIL]
RETURNS [number:
INT ¬ 0] ~ {
IF name=NIL THEN name ¬ "Value";
number ¬ Real.Round[real ! RuntimeError.UNCAUGHT => CONTINUE];
IF real#number THEN Complain[IO.PutFR[format: "%g (%g) should be integral.", v1: [rope [name]], v2: [real [real]]]];
IF number NOT IN [bounds.first .. bounds.last] THEN Complain[IO.PutFR[format: "Value (%g) should be in range [%g .. %g]", v1: [rope [name]], v2: [integer [bounds.first]], v3: [integer [bounds.last]]]];
};
xStar: ROPE ~ FS.ExpandName["*"].fullFName;
regDir: ROPE ~ Rope.Substr[xStar, 0, Rope.Size[xStar]-1];
wDirList:
LIST
OF
ROPE ¬
LIST[
NIL, regDir];
FileContents:
PROC [base, ext:
ROPE]
RETURNS [
ROPE] ~ {
FOR each:
LIST
OF
ROPE ¬ wDirList, each.rest
UNTIL each =
NIL
DO
name: ROPE ~ FS.ExpandName[name: Rope.Concat[base, ext], wDir: each.first].fullFName;
RETURN [PFS.RopeOpen[fileName: PFS.PathFromRope[name], includeFormatting: FALSE ! PFS.Error => {IF error.group = user THEN CONTINUE}].rope];
ENDLOOP;
Complain[IO.PutFR1["Device type %g undefined.", [rope [base]]]];
};
Require:
PROC [stream:
IO.
STREAM, rope:
ROPE, case:
BOOLEAN ¬
FALSE] ~ {
IF NOT Rope.Equal[s1: rope, s2: GetCmdToken[stream], case: case] THEN Complain[Rope.Cat["Expected \"", rope, "\"."]];
};
Command Registration
usage:
ROPE ~
"Convert Interpress file to
IP master of compressed page bitmaps (output ← input) <switch> ...
switches are:
-lineScreen to use line screen for halftones
-dotScreen to use dot screen for halftones (default)
-portrait for short-edge-feed printers
-landscape for long-edge-feed printers (default)
-ppi <r> to use a resolution of r pixels per inch (default -ppi 300)
-angle <degrees> half tone screen angle (default 45)
-dotsPerInch <d> use d dots per inch
";
action:
REF
ANY ¬
NEW[ActionProc ¬ InterpressToCompressedIPAction];
FOR l:
LIST
OF
ROPE ¬
LIST["InterpressToCompressedInterpress",
"InterpressToCompressedIP",
"IPToCompressedInterpress",
"IPToCompressedIP",
"IPToCIP"], l.rest WHILE l # NIL DO
Commander.Register[l.first, Command, usage, action];
ENDLOOP;
END.