TiogaToInterpressImpl.mesa
Copyright Ó 1984, 1985, 1986, 1987, 1992 by Xerox Corporation. All rights reserved.
Michael Plass, March 10, 1992 2:27 pm PST
Bloomenthal -- background and slidewide options, January 13, 1988 11:58:00 am PST
Bier, April 27, 1988 12:20:43 pm PDT
JKF February 24, 1989 2:35:03 pm PST
Last tweaked by Mike Spreitzer on April 17, 1990 8:23:44 am PDT
Bill Jackson (bj) April 19, 1989 5:40:33 pm PDT
Doug Wyatt, July 17, 1992 4:06 pm PDT
DIRECTORY
Atom, CedarProcess, Commander, Convert, FileNames, FS, Imager, ImagerBackdoor, ImagerColor, ImagerFont, ImagerInterpress, ImagerPrivate, ImagerSample, IO, NodeProps, PFS, Real, Rope, TextEdit, TextNode, TiogaImager, TiogaIO, UserProfile, Vector2;
TiogaToInterpressImpl: CEDAR PROGRAM
IMPORTS CedarProcess, Commander, Convert, FileNames, FS, Imager, ImagerBackdoor, ImagerColor, ImagerInterpress, ImagerSample, IO, NodeProps, PFS, Real, Rope, TextEdit, TextNode, TiogaImager, TiogaIO, UserProfile
EXPORTS Imager
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
VEC: TYPE ~ Vector2.VEC;
ActionProc: TYPE ~ PROC [data: ParsedCommand, msgStream: IO.STREAM];
LocalError: ERROR [rope: ROPE] ~ CODE;
AppendFix: PROC [node: TextNode.Ref, name: ATOM, value: ROPE] ~ {
old: ROPE ~ NodeProps.GetSpecs[name: name, value: NodeProps.GetProp[n: node, name: name]];
new: ROPE ~ Rope.Cat[old, " ", value];
NodeProps.PutProp[n: node, name: name, value: NodeProps.DoSpecs[name: name, specs: new]];
};
GetWithStyle: PROC [inputName: ROPE, styleName: ROPE ¬ NIL, rootPrefix, rootPostfix: ROPE ¬ NIL] RETURNS [root: TextNode.Ref] ~ {
fullFName: ROPE ¬ NIL;
root ¬ TiogaIO.FromFile[PFS.PathFromRope[inputName]].root;
IF Rope.Size[styleName]#0 THEN TextEdit.ChangeStyle[node: root, name: styleName, event: NIL];
IF rootPrefix # NIL THEN AppendFix[root, $Prefix, rootPrefix];
IF rootPostfix # NIL THEN AppendFix[root, $Postfix, rootPostfix];
};
ptpermeter: REAL ¬ 72.27/0.0254;
ptperbp: REAL ¬ 72.27/72;
MakeHeader: PROC [version: REAL] RETURNS [ROPE] ~ {
header: ROPE ~ IO.PutFR1["Interpress/Xerox/%g ", [real[version]]];
RETURN [header]
};
PredeclareSetFont: PROC [context: Imager.Context, font: ImagerFont.Font] = {
refMaster: REF ImagerInterpress.Ref ~ NARROW[Imager.GetProp[context: context, key: $Master]];
oldClass: Class ~ NARROW[Imager.GetProp[context: context, key: $OldClass]];
oldClass.SetFont[context, font];
ImagerInterpress.DeclareFont[self: refMaster­, font: font];
};
Class: TYPE ~ REF ClassRep;
ClassRep: PUBLIC TYPE ~ ImagerPrivate.ClassRep;
PredeclareFonts: PROC [master: ImagerInterpress.Ref, paint: PROC [context: Imager.Context]] ~ {
context: Imager.Context ~ ImagerBackdoor.BitmapContext[bitmap: ImagerSample.NewSampleMap[box: [[0,0], [0,0]]]];
oldClass: Class ~ context.class;
newClass: Class ~ NEW[ClassRep ¬ oldClass­];
newClass.SetFont ¬ PredeclareSetFont;
context.class ¬ newClass;
Imager.SetNoImage[context, TRUE];
Imager.PutProp[context: context, key: $Master, val: NEW[ImagerInterpress.Ref ¬ master]];
Imager.PutProp[context: context, key: $OldClass, val: oldClass];
paint[context];
};
TiogaToInterpressAction: PROC [data: ParsedCommand, msgStream: IO.STREAM] ~ {
master: ImagerInterpress.Ref ~ ImagerInterpress.Create[data.outputName, MakeHeader[data.version]];
pageIndexFileName: ROPE ~ ExtendName[data.outputName, "pageIndex"];
pageIndexStream: IO.STREAM ~
IF data.pageIndexing THEN FS.StreamOpen[pageIndexFileName, $create] ELSE NIL;
pageCount: INT ¬ 0;
prevCharIndex: INT ¬ 0;
FOR inputs: LIST OF ROPE ¬ data.inputNames, inputs.rest WHILE inputs#NIL AND pageCount-data.skipPages < data.nPages DO
marks: Atom.PropList ¬ NIL;
root: TextNode.Ref ~ GetWithStyle[inputs.first, data.style, data.rootPrefix, data.rootPostfix];
loc: TextNode.Location ¬ [node: TextNode.StepForward[root], where: 0];
IF data.pageIndexing THEN IO.PutF1[pageIndexStream, "(\"%g\"\n ", [rope[inputs.first]]];
IF inputs # data.inputNames THEN {
msgStream.PutRope[" . . . "];
msgStream.PutRope[inputs.first];
msgStream.PutRope[" . . . "];
};
WHILE loc.node # NIL AND pageCount-data.skipPages < data.nPages DO
levelClipFilter: TiogaImager.FilterProc ~ { IF level>data.levelClip THEN skip ¬ TRUE };
filter: TiogaImager.FilterProc ~ IF data.levelClip#0 THEN levelClipFilter ELSE NIL;
page: TiogaImager.FormattedPage;
paint: PROC [context: Imager.Context] ~ {
Context is initially set for 1 unit = 1 meter.
Imager.ScaleT[context, 0.0254]; -- now we've got inches
Imager.TranslateT[context, data.translate];
Imager.ScaleT[context, 1.0/72.27]; -- now we've got Tioga screen dots
Imager.ScaleT[context, data.magnify];
Imager.RotateT[context, data.rotate];
IF data.background#NIL THEN {
Imager.SetColor[context, data.background];
Imager.MaskRectangle[context, [-10000, -10000, 20000, 20000]];
Imager.SetColor[context, Imager.black];
};
TiogaImager.Render[page.box, context, [0, 0]];
};
IF data.verbose AND pageCount >= data.skipPages THEN {
msgStream.PutRope["\n (Location "];
msgStream.PutRope[Convert.RopeFromInt[TextNode.LocNumber[at: loc, skipCommentNodes: TRUE]]];
msgStream.PutRope[") "];
};
page ¬ TiogaImager.FormatPage[pageCounter: pageCount, startLoc: loc, filter: filter, marks: marks, styleKind: IF data.screenFormat THEN screen ELSE print];
IF pageCount = 0 THEN {
PredeclareFonts[master, paint];
};
IF pageCount >= data.skipPages THEN {
charIndex: INT ~ IF page.nextLoc.node # NIL THEN TextNode.LocNumber[at: page.nextLoc, skipCommentNodes: FALSE] - 1 ELSE TextNode.LocNumber[at: TextNode.LastLocWithin[n: root], skipCommentNodes: FALSE];
figure: ROPE ~ Convert.RopeFromInt[page.pageFigure];
msgStream.PutRope["["];
msgStream.PutRope[figure];
ImagerInterpress.DoPage[master, paint, data.magnify];
ImagerInterpress.DoPage[master, paint, 1.0];
msgStream.PutRope["] "];
IF pageCount = data.skipPages THEN prevCharIndex ¬ TextNode.LocNumber[at: loc, skipCommentNodes: FALSE];
IF data.pageIndexing THEN IO.PutF[pageIndexStream, "(\"%g\" (%g %g))\n ", [rope[figure]], [integer[prevCharIndex]], [integer[charIndex]]];
prevCharIndex ¬ charIndex+1;
};
TiogaImager.Destroy[page.box];
pageCount ¬ pageCount + 1;
marks ¬ page.marks;
loc ¬ page.nextLoc;
ENDLOOP;
IF data.pageIndexing THEN IO.PutRope[pageIndexStream, ")\n"];
ENDLOOP;
ImagerInterpress.Close[master];
IF data.pageIndexing THEN IO.Close[pageIndexStream];
};
FindFullName: PROC [inputName: ROPE] RETURNS [ROPE] ~ {
fullFName: ROPE ¬ NIL;
fullFName ¬ FS.FileInfo[inputName].fullFName;
RETURN [fullFName]
};
ExtendName: PROC [inputName, ext: ROPE] RETURNS [ROPE] ~ {
fullFName: ROPE ¬ NIL;
cp: FS.ComponentPositions;
[fullFName, cp] ¬ FS.ExpandName[inputName];
RETURN [Rope.Cat[Rope.Substr[fullFName, cp.base.start, cp.base.length], ".", ext]];
};
CmdTokenBreak: 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];
};
GetCmdToken: PROC [stream: IO.STREAM] RETURNS [rope: ROPE] = {
rope ¬ NIL;
rope ¬ stream.GetTokenRope[CmdTokenBreak ! IO.EndOfStream => CONTINUE].token;
rope ¬ FileNames.ResolveRelativePath[rope]; -- Bloomenthal, February 2, 1989
};
ParseError: ERROR [msg: ROPE, index: INT] ~ CODE;
RaiseParseError: PROC [stream: IO.STREAM, msg: ROPE] ~ {
ERROR ParseError[msg, IO.GetIndex[stream]];
};
ParsedCommand: TYPE ~ REF ParsedCommandRep;
ParsedCommandRep: TYPE ~ RECORD [
outputName: ROPE ¬ NIL,
inputNames: LIST OF ROPE ¬ NIL,
style: ROPE,
rootPrefix: ROPE,
rootPostfix: ROPE,
skipPages: INT,
nPages: INT,
magnify, rotate: REAL,
translate: VEC,
verbose: BOOL,
screenFormat: BOOL,
background: ImagerColor.ConstantColor,
version: REAL ¬ 3.0,
pageIndexing: BOOL ¬ TRUE,
levelClip: INTEGER ¬ 0
];
ExpandStars: PROC [names: LIST OF ROPE] RETURNS [LIST OF ROPE] ~ {
new: LIST OF ROPE ¬ LIST[NIL];
last: LIST OF ROPE ¬ new;
NameAction: PROC [fullFName: ROPE] RETURNS [continue: BOOL ¬ TRUE] ~ {
last.rest ¬ LIST[fullFName];
last ¬ last.rest;
};
FOR p: LIST OF ROPE ¬ names, p.rest UNTIL p = NIL DO
r: ROPE ¬ p.first;
IF Rope.Find[s1: r, s2: "*"] >= 0
THEN {
IF Rope.Find[s1: r, s2: "!"] < 0 THEN r ¬ Rope.Concat[r, "!H"];
FS.EnumerateForNames[r, NameAction]
}
ELSE [] ¬ NameAction[FindFullName[r]];
ENDLOOP;
RETURN [new.rest]
};
Parse: PROC [stream: IO.STREAM] RETURNS [data: ParsedCommand] ~ {
outputName: ROPE ¬ GetCmdToken[stream];
secondTokenIndex: INT ¬ stream.GetIndex;
gets: ROPE ¬ GetCmdToken[stream];
inputNames: LIST OF ROPE ¬ NIL;
inputNamesTail: LIST OF ROPE ¬ NIL;
keySeen: BOOL ¬ FALSE;
IF NOT (gets.Equal["←"] OR gets.Equal["¬"]) THEN {
inputNames ¬ inputNamesTail ¬ LIST[outputName];
outputName ¬ NIL;
stream.SetIndex[secondTokenIndex];
}
ELSE {inputNames ¬ inputNamesTail ¬ LIST[GetCmdToken[stream]]};
IF inputNames = NIL THEN RaiseParseError[stream, usage];
DO
index: INT ¬ IO.GetIndex[stream];
name: ROPE ¬ GetCmdToken[stream];
bad: BOOL ¬ FALSE;
c: CHAR;
IF Rope.Size[name] = 0 THEN EXIT;
IF (c ¬ Rope.Fetch[name, 0]) IN ['0..'9] OR c = '- OR c='. OR c='$ THEN bad ¬ TRUE
ELSE IF IsKeyword[name] THEN bad ¬ TRUE
ELSE name ¬ FS.ExpandName[name ! FS.Error => {bad ¬ TRUE; CONTINUE}].fullFName;
IF bad THEN {IO.SetIndex[stream, index]; EXIT};
inputNamesTail.rest ¬ LIST[name];
inputNamesTail ¬ inputNamesTail.rest;
ENDLOOP;
inputNames ¬ ExpandStars[inputNames];
IF Rope.Size[outputName] = 0
THEN outputName ¬ ExtendName[inputNames.first, "interpress"];
data ¬ NEW[ParsedCommandRep ¬ default­];
data.outputName ¬ outputName;
data.inputNames ¬ inputNames;
ParseParameters[stream, data];
};
ParamName: TYPE ~ {nil,
these take (prefix) parameters:
style, prefix, postfix, device, skipPages, nPages, magnify, rotate, translate, version, background, levelClip, in, pt, cm, mm, bp,
these take no parameters, and hence are keywords:
verbose, terse, screenFormat, printFormat, pageIndexing, noPageIndexing
};
Keywords: TYPE ~ ParamName[verbose..noPageIndexing];
RopeForParamName: REF ARRAY ParamName OF ROPE ¬ NEW [
ARRAY ParamName OF ROPE ¬ [nil: NIL, style: "style", prefix: "prefix", postfix: "postfix", device: "device", skipPages: "skipPages", nPages: "nPages", magnify: "magnify", rotate: "rotate", translate: "translate", version: "version", background: "background", levelClip: "levelClip", in: "in", pt: "pt", cm: "cm", mm: "mm", bp: "bp", verbose: "verbose", terse: "terse", screenFormat: "screenFormat", printFormat: "printFormat", pageIndexing: "pageIndexing", noPageIndexing: "noPageIndexing"]
];
ParamNameForRefText: PROC [text: REF TEXT] RETURNS [ParamName] ~ TRUSTED {
FOR p: ParamName IN (nil..ParamName.LAST] DO
IF Rope.Equal[LOOPHOLE[text], RopeForParamName[p], FALSE] THEN RETURN [p]
ENDLOOP;
RETURN [nil]
};
IsKeyword: PROC [name: ROPE] RETURNS [BOOL] ~ {
FOR p: ParamName IN Keywords DO
IF Rope.Equal[name, RopeForParamName[p], FALSE] THEN RETURN [TRUE]
ENDLOOP;
RETURN [FALSE]
};
ParseParameters: PROC [stream: IO.STREAM, data: ParsedCommand] ~ {
stackSize: NAT ~ 3;
Type: TYPE ~ {number, dimension, string, negate};
stack: ARRAY [0..stackSize) OF REAL;
stringStack: ARRAY [0..stackSize) OF ROPE ¬ ALL[NIL];
stackType: ARRAY [0..stackSize) OF Type ¬ ALL[number];
stackTop: NAT ¬ 0;
token: REF TEXT ¬ NEW[TEXT[30]];
tokenKind: IO.TokenKind ¬ tokenERROR;
CheckStack: PROC ~ {
IF stackTop = stackSize THEN RaiseParseError[stream, "Too many consecutive parameters "];
IF stackTop > 0 AND stackType[stackTop-1] = negate THEN RaiseParseError[stream, "Misplaced minus sign "];
};
CheckN: PROC [size: NAT] ~ {
IF stackTop # size THEN RaiseParseError[stream, "Wrong number of parameters "];
};
PopReal: PROC RETURNS [r: REAL ¬ 0] ~ {
IF stackTop = 0 THEN RaiseParseError[stream, "Missing parameter "];
IF stackType[stackTop-1] # number THEN RaiseParseError[stream, "Number expected "];
r ¬ stack[stackTop-1];
stackTop ¬ stackTop - 1;
};
PopDimn: PROC RETURNS [r: REAL ¬ 0] ~ {
IF stackTop = 0 THEN RaiseParseError[stream, "Missing parameter "];
IF stackType[stackTop-1] # dimension THEN RaiseParseError[stream, "Dimension expected "];
r ¬ stack[stackTop-1];
stackTop ¬ stackTop - 1;
};
MakeMeters: PROC [multiplier: REAL] ~ {
r: REAL ¬ PopReal[];
stack[stackTop] ¬ r*multiplier;
stackType[stackTop] ¬ dimension;
stackTop ¬ stackTop + 1;
};
PopInt: PROC RETURNS [INT] ~ {
r: REAL ¬ PopReal[];
i: INT ¬ Real.Round[r];
IF i#r THEN RaiseParseError[stream, "Integer expected "];
RETURN [i]
};
GetTok: PROC RETURNS [BOOL] ~ {
tokenError: IO.TokenError ¬ none;
charsSkipped: INT ¬ 0;
[tokenKind: tokenKind, token: token, charsSkipped: charsSkipped, error: tokenError] ¬ IO.GetCedarToken[stream: stream, buffer: token, flushComments: TRUE];
SELECT tokenKind FROM
tokenEOF => RETURN [FALSE];
tokenID, tokenDECIMAL, tokenREAL, tokenROPE, tokenATOM, tokenSINGLE => RETURN [TRUE];
tokenERROR, tokenOCTAL, tokenHEX, tokenCHAR, tokenDOUBLE => RaiseParseError[stream, Rope.Concat["Unknown token: ", Rope.FromRefText[token]]];
ENDCASE => ERROR;
RETURN [FALSE]
};
SetUserProfileOptions[data];
WHILE GetTok[] DO
SELECT tokenKind FROM
tokenDECIMAL, tokenREAL => TRUSTED {
real: REAL ¬ Convert.RealFromRope[LOOPHOLE[token]];
IF stackTop > 0 AND stackType[stackTop-1] = negate THEN {
real ¬ -real;
stackTop ¬ stackTop-1;
};
CheckStack[];
stack[stackTop] ¬ real;
stackType[stackTop] ¬ number;
stackTop ¬ stackTop + 1;
};
tokenSINGLE => {
IF token[0] = '-
THEN {
CheckStack[];
stackType[stackTop] ¬ negate;
stackTop ¬ stackTop + 1;
}
ELSE {RaiseParseError[stream, IO.PutFR1[format: "Illegal token: %g", value: [text[token]]]]};
};
tokenROPE => {
CheckStack[];
stringStack[stackTop] ¬ Convert.RopeFromLiteral[Rope.FromRefText[token]];
stackType[stackTop] ¬ string;
stackTop ¬ stackTop + 1;
};
tokenATOM => {
CheckStack[];
IF token[0] # '$ THEN ERROR;
stringStack[stackTop] ¬ Rope.FromRefText[s: token, start: 1];
stackType[stackTop] ¬ string;
stackTop ¬ stackTop + 1;
};
tokenID => {
SELECT ParamNameForRefText[token] FROM
style => {
CheckN[1];
IF stackType[stackTop-1]#string THEN RaiseParseError[stream, "style needs string or atom parameter "];
stackTop ¬ stackTop-1;
data.style ¬ stringStack[stackTop];
};
prefix => {
CheckN[1];
IF stackType[stackTop-1]#string THEN RaiseParseError[stream, "prefix needs string or atom parameter "];
stackTop ¬ stackTop-1;
data.rootPrefix ¬ Rope.Cat[data.rootPrefix, " ", stringStack[stackTop]];
};
postfix => {
CheckN[1];
IF stackType[stackTop-1]#string THEN RaiseParseError[stream, "postfix needs string or atom parameter "];
stackTop ¬ stackTop-1;
data.rootPostfix ¬ Rope.Cat[data.rootPostfix, " ", stringStack[stackTop]];
};
device => {
CheckN[1];
IF stackType[stackTop-1]#string THEN RaiseParseError[stream, "device needs string or atom parameter "];
stackTop ¬ stackTop-1;
data.rootPrefix ¬ Rope.Cat[data.rootPrefix, " (", stringStack[stackTop], ") device"];
};
skipPages => {data.skipPages ¬ PopInt[]; CheckN[0]};
nPages => {data.nPages ¬ PopInt[]; CheckN[0]};
magnify => {data.magnify ¬ PopReal[]; CheckN[0]};
rotate => {data.rotate ¬ PopReal[]; CheckN[0]};
translate => {data.translate.y ¬ PopReal[]; data.translate.x ¬ PopReal[]; CheckN[0]};
terse => {data.verbose ¬ FALSE; CheckN[0]};
verbose => {data.verbose ¬ TRUE; CheckN[0]};
version => {data.version ¬ PopReal[]; CheckN[0]};
screenFormat => {data.screenFormat ¬ TRUE; CheckN[0]};
printFormat => {data.screenFormat ¬ FALSE; CheckN[0]};
background => {
b: REAL ¬ PopReal[];
g: REAL ¬ PopReal[];
r: REAL ¬ PopReal[];
IF MIN[r, g, b] < 1.0 THEN
data.background ¬ ImagerColor.ColorFromRGB[[r, g, b]];
};
levelClip => {data.levelClip ¬ PopInt[]; CheckN[0]};
in => {MakeMeters[0.0254]};
pt => {MakeMeters[0.0254/72.27]};
cm => {MakeMeters[0.01]};
mm => {MakeMeters[0.001]};
bp => {MakeMeters[0.0254/72.0]};
noPageIndexing => {data.pageIndexing ¬ FALSE};
pageIndexing => {data.pageIndexing ¬ TRUE};
ENDCASE => RaiseParseError[stream, "Unknown keyword parameter: "];
};
ENDCASE => ERROR;
ENDLOOP;
CheckStack[];
CheckN[0];
};
SetUserProfileOptions: PROC [data: ParsedCommand] ~ {
data.pageIndexing ¬ UserProfile.Boolean["TiogaToInterpress.PageIndexing", TRUE];
};
Command: Commander.CommandProc ~ {
stream: IO.STREAM ¬ IO.RIS[cmd.commandLine];
refAction: REF ActionProc ~ NARROW[cmd.procData.clientData];
backgroundTask: PROC ~ {
data: ParsedCommand;
data ¬ Parse[stream !
ParseError => {
start: INT ¬ MAX[index-10, 0];
cmd.out.PutRope[msg];
IF start > 0 THEN cmd.out.PutRope["..."];
cmd.out.PutRope[cmd.commandLine.Substr[start, index-start]];
IF index > 1 THEN cmd.out.PutRope["..."];
cmd.out.PutRope["\n"];
GOTO Quit
};
FS.Error => {
cmd.out.PutRope[error.explanation];
cmd.out.PutRope["\n"];
GOTO Quit
};
];
cmd.out.PutRope["Reading "];
cmd.out.PutRope[data.inputNames.first];
cmd.out.PutRope[" . . . "];
refAction­[data, cmd.out ! LocalError => {
cmd.out.PutRope[rope];
cmd.out.PutRope["\n"];
GOTO Quit
}];
IF data.outputName # NIL THEN {
data.outputName ¬ FindFullName[data.outputName];
cmd.out.PutRope[data.outputName];
cmd.out.PutRope[" written.\n"];
}
ELSE cmd.out.PutRope[" ok.\n"];
EXITS Quit => {}
};
CedarProcess.DoWithPriority[background, backgroundTask];
};
default: ParsedCommand ¬ NEW[ParsedCommandRep ¬ [style: NIL, rootPrefix: NIL, rootPostfix: NIL, skipPages: 0, nPages: 0, magnify: 0.0, rotate: 0.0, translate: [0.0, 0.0], verbose: FALSE, screenFormat: FALSE, background: NIL, version: 3.0, pageIndexing: TRUE, levelClip: 0]];
paramRope: ROPE ~ " \"\" style 0 skipPages 999999 nPages printFormat--screenFormat-- terse--verbose-- 3.0 version 1.0 magnify 0.0 rotate 1.0 1.0 1.0 background";
usage: ROPE ~ "
Convert a Tioga file to an Interpress file:
TiogaToInterpress [<output> ←] <input> [option*]; options include:
 <rope> style       (default: NIL)
 <integer> skipPages     (default: 0)
 <integer> nPages     (default: 999999)
 <integer.integer> version   (default: 3.0>
 <real> magnify      (default: 1.0)
 <real (degrees)> rotate    (default: 0.0)
 <tx ty: real (inches)> translate  (default: 0.0 0.0)
 <r g b: real> background   (default: 1.0 1.0 1.0)
 <printFormat | screenFormat>  (default: printFormat)
 <integer> levelClip     (default: none)
 <terse | verbose>      (default: terse)
 <noPageIndexing | pageIndexing> (default: page indexing)";
ParseParameters[IO.RIS[paramRope], default];
Commander.Register[
"TiogaToInterpress", Command, usage, NEW[ActionProc ¬ TiogaToInterpressAction]];
END.
bj - Changed 9999999 to 999999 for REAL precision reasons - PCedar doesn't guarantee faithful 24 bits worth of integer/real conversion.
MJS - added the ability to rotate
MJS - changed usage ROPE to use \n instead of a literal linebreak, for source-sharing (don't whine about it being ugly source-code; you can use the EditTool to make it pretty while you edit it).