InterpressPageImpl.mesa
Copyright Ó 1986, 1989, 1990, 1992 by Xerox Corporation. All rights reserved.
Michael Plass, April 15, 1992 3:53 pm PDT
Last tweaked by Mike Spreitzer on May 23, 1989 9:28:53 am PDT
Tim Diebert: November 12, 1990 8:58 am PST
Provides commands for making an Interpress package by splitting out and combining pages of other Interpress masters.
DIRECTORY Commander, CommanderOps, Convert, FS, Imager, ImagerInterpress, ImagerTransformation, InterpressInterpreter, IO, Rope, ProcessProps, RasterEncodingStandardIO, ImagerRES;
InterpressPageImpl: CEDAR PROGRAM
IMPORTS Commander, CommanderOps, Convert, FS, Imager, ImagerInterpress, ImagerTransformation, InterpressInterpreter, IO, Rope, ProcessProps, RasterEncodingStandardIO, ImagerRES
~ BEGIN
Copied Types
ROPE: TYPE ~ Rope.ROPE;
Command parsing aids
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;
};
ExpandedName: TYPE ~ RECORD [fullFName: ROPE, cp: FS.ComponentPositions];
ExpandName: PROC [inputName: ROPE] RETURNS [e: ExpandedName] ~ INLINE {
[e.fullFName, e.cp] ¬ FS.ExpandName[inputName];
};
Extracting pages
PageRange: TYPE ~ RECORD [startPage, nPages: INT];
SpecError: PUBLIC ERROR [offset: INT] ~ CODE;
DoExtract: PUBLIC PROC [inputFile, outputFile: ROPE, pageSpec: LIST OF PageRange, log: IO.STREAM] RETURNS [success: BOOLEAN ¬ TRUE] ~ {
logProc: InterpressInterpreter.LogProc ~ {
IO.PutRope[log, "\n *** Interpress Error "];
IO.PutRope[log, explanation];
success ¬ FALSE;
};
n: ExpandedName ~ ExpandName[FS.FileInfo[inputFile].fullFName];
master: InterpressInterpreter.Master ~ InterpressInterpreter.Open[n.fullFName, logProc];
out: ImagerInterpress.Ref ~ ImagerInterpress.Create[outputFile];
IO.PutRope[log, "Reading "];
IO.PutRope[log, n.fullFName];
IO.PutRope[log, " . . . "];
FOR each: LIST OF PageRange ¬ pageSpec, each.rest UNTIL each=NIL DO
pageRange: PageRange ~ each.first;
FOR i: INT IN [pageRange.startPage..MIN[pageRange.startPage+pageRange.nPages-1, master.pages]] DO
OnePage: PROC [context: Imager.Context] ~ {InterpressInterpreter.DoPage[master, i, context, logProc]};
IO.PutF1[log, " [%g", [integer[i]]];
ImagerInterpress.DoPage[out, OnePage];
IO.PutRope[self: log, r: "]"];
ENDLOOP;
ENDLOOP;
ImagerInterpress.Close[out];
IO.PutRope[log, " written.\n"];
};
InterpressExtractCommand: Commander.CommandProc ~ {
GetToken: PROC [stream: IO.STREAM] RETURNS [token: ROPE ¬ NIL] = {
Break: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '← THEN RETURN [break];
IF char = ' OR char = '  OR char = ', OR char = '; OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
token ¬ stream.GetTokenRope[Break ! IO.EndOfStream => CONTINUE].token;
};
ParsePageSpec: PUBLIC PROC [pageSpecRope: ROPE] RETURNS [pageSpec: LIST OF PageRange, charsParsed: INT] ~ {
Reverse: PROC [pageSpec: LIST OF PageRange] RETURNS [reversed: LIST OF PageRange] ~ {
WHILE pageSpec # NIL DO
t: LIST OF PageRange ¬ pageSpec;
pageSpec ¬ t.rest;
t.rest ¬ reversed;
reversed ¬ t;
ENDLOOP;
};
text: REF TEXT ~ pageSpecRope.ToRefText;
i: NAT ¬ 0;
c: CHAR;
SkipSpaces: PROC ~ {c ¬ ' ; WHILE i < text.length AND ((c ¬ text[i]) = ', OR c = ' OR c = ' OR c = '\n) DO i ¬ i+1 ENDLOOP};
GetChar: PROC RETURNS [CHAR] ~ {IF i < text.length THEN {i ¬ i+1; RETURN [text[i-1]]} ELSE RETURN ['\000]};
Int: PROC RETURNS [value: INT¬0] ~ {
SkipSpaces[];
IF NOT c IN ['0..'9] THEN ERROR SpecError[i];
WHILE i < text.length AND (c ¬ text[i]) IN ['0..'9] DO
value ¬ value * 10 + (c-'0);
i ¬ i+1;
ENDLOOP;
};
spec: LIST OF PageRange ¬ NIL;
SkipSpaces[];
WHILE i < text.length DO
SELECT text[i] FROM
IN ['0..'9] => spec ¬ CONS[[Int[], 1], spec];
'[, '( => {
open: CHAR ¬ GetChar[];
start: INT ¬ Int[];
end: INT;
SkipSpaces[];
IF i < text.length AND text[i] = '. THEN i ¬ i+1 ELSE ERROR SpecError[i];
IF i < text.length AND text[i] = '. THEN i ¬ i+1 ELSE ERROR SpecError[i];
end ¬ Int[];
SkipSpaces[];
IF (c ¬ GetChar[]) = '] OR c = ') THEN {
IF open = '( THEN start ¬ start + 1;
IF c = '] THEN end ¬ end + 1;
IF end > start THEN spec ¬ CONS[[start, end-start], spec]
}
ELSE ERROR SpecError[i];
};
ENDCASE => EXIT;
SkipSpaces[];
ENDLOOP;
RETURN [Reverse[spec], i]
};
stream: IO.STREAM ¬ IO.RIS[cmd.commandLine];
outputName: ROPE ¬ GetToken[stream];
inputName: ROPE;
pagesToken: ROPE;
pageSpec: LIST OF PageRange;
IF outputName.Length = 0 THEN {cmd.out.PutRope["Output file missing.\n"]; RETURN};
IF NOT GetToken[stream].Equal["←"] THEN {cmd.out.PutRope["Missing \"←\".\n"]; RETURN};
inputName ¬ GetToken[stream];
pagesToken ¬ GetToken[stream];
IF pagesToken.Equal["PAGE", FALSE] OR pagesToken.Equal["PAGES", FALSE] THEN {
skip: INT;
[pageSpec, skip] ¬ ParsePageSpec[cmd.commandLine.Substr[stream.GetIndex]];
stream.SetIndex[stream.GetIndex+skip];
}
ELSE pageSpec ¬ LIST[[1, 1000000]];
IF NOT DoExtract[inputName, outputName, pageSpec, cmd.out] THEN {
cmd.out.PutRope["Unable to extract any pages from "];
cmd.out.PutRope[inputName];
cmd.out.PutChar['\n];
}
ELSE {cmd.out.PutRope[outputName]; cmd.out.PutRope[" Written.\n"]};
IF NOT stream.EndOf THEN {cmd.out.PutRope["Ignored: "]; cmd.out.PutRope[cmd.commandLine.Substr[stream.GetIndex]]; cmd.out.PutChar['\n]};
};
Breaking into single pages
BreakupStyle: TYPE ~ {pageCount, numFiles};
DoBreakup: PROC [inputName: ROPE, log: IO.STREAM, style: BreakupStyle, div: INT] ~ {
logProc: InterpressInterpreter.LogProc ~ {
IO.PutRope[log, "\n *** Interpress Error "];
IO.PutRope[log, explanation];
};
n: ExpandedName ~ ExpandName[FS.FileInfo[inputName].fullFName];
base: ROPE ~ Rope.Substr[n.fullFName, n.cp.base.start, n.cp.base.length];
ext: ROPE ~ Rope.Substr[n.fullFName, n.cp.ext.start, n.cp.ext.length];
logPages: BOOL ~ div#1 OR style#pageCount;
master: InterpressInterpreter.Master;
nFiles, j: INT ¬ 0;
IO.PutRope[log, "Reading "];
IO.PutRope[log, n.fullFName];
IO.PutRope[log, " . . . "];
master ¬ InterpressInterpreter.Open[n.fullFName, logProc];
IO.PutF1[log, IF logPages THEN " %g pages\n" ELSE " %g pages; ", [integer[master.pages]]];
SELECT style FROM
pageCount => nFiles ¬ (master.pages+div-1)/div;
numFiles => nFiles ¬ div;
ENDCASE => ERROR;
FOR i: INT IN [1..nFiles] DO
lim: INT ~ SELECT style FROM
pageCount => MIN[j+div, master.pages],
numFiles => (i*master.pages + div/2)/div,
ENDCASE => ERROR;
name: ROPE ~ Rope.Cat[base, "-", Convert.RopeFromInt[i], ".", ext];
out: ImagerInterpress.Ref ~ ImagerInterpress.Create[name];
IO.PutRope[log, FS.ExpandName[name].fullFName];
WHILE j<lim DO
onePage: PROC [context: Imager.Context] ~ {
IF logPages THEN IO.PutF1[log, " [%g", [integer[j]]];
InterpressInterpreter.DoPage[master, j, context, logProc];
IF logPages THEN IO.PutRope[log, "]"];
};
j ¬ j+1;
ImagerInterpress.DoPage[out, onePage];
ENDLOOP;
IO.PutRope[log, IF logPages THEN "\n" ELSE " "];
ImagerInterpress.Close[out];
ENDLOOP;
IO.PutRope[log, "done.\n"];
};
InterpressBreakupCommand: Commander.CommandProc ~ {
ENABLE {
Convert.Error => CommanderOps.Failed[cmd.procData.doc];
FS.Error => IF error.group = user THEN CommanderOps.Failed[error.explanation];
};
style: BreakupStyle ¬ pageCount;
div: INT ¬ 1;
inputName: ROPE ¬ CommanderOps.NextArgument[cmd];
IF inputName.Equal["-by"] THEN {
div ¬ Convert.IntFromRope[CommanderOps.NextArgument[cmd]];
IF div<=0 THEN RETURN [result: $Failure, msg: IO.PutFR1["Must divide by a positive number of pages, not %g", [integer[div]] ]];
inputName ¬ CommanderOps.NextArgument[cmd];
}
ELSE IF inputName.Equal["-into"] THEN {
style ¬ numFiles;
div ¬ Convert.IntFromRope[CommanderOps.NextArgument[cmd]];
IF div<=1 THEN RETURN [result: $Failure, msg: IO.PutFR1["Number of output files must be greater than 1, not %g", [integer[div]] ]];
inputName ¬ CommanderOps.NextArgument[cmd];
};
IF inputName = NIL THEN GOTO SyntaxError;
DoBreakup[inputName, cmd.out, style, div ! FS.Error => IF error.group = user THEN {result ¬ $Failure; msg ¬ error.explanation; CONTINUE}];
EXITS SyntaxError => RETURN[result: $Failure, msg: cmd.procData.doc]};
Concatenating masters together
DoConcatenate: PROC [outputName: ROPE, cmdLineStream: IO.STREAM, log: IO.STREAM] ~ {
pageNumber: INT ¬ 0;
logProc: InterpressInterpreter.LogProc ~ {
IO.PutRope[log, "\n *** Interpress Error "];
IO.PutRope[log, explanation];
};
n: ExpandedName ~ ExpandName[outputName];
extendedName: ROPE ~ IF n.cp.ext.length = 0
THEN Rope.Substr[n.fullFName, 0, n.cp.base.start+n.cp.base.length].Concat[".interpress"]
ELSE n.fullFName;
tempName: ROPE ¬ Rope.Cat["/vux/tmp/", Rope.Substr[n.fullFName, 0, n.cp.base.start+n.cp.base.length], ".interpress"];
Write it with a temp name to avoid lock conflicts if it is also an input.
out: ImagerInterpress.Ref ~ ImagerInterpress.Create[tempName];
IO.PutRope[log, "Reading "];
FOR inputName: ROPE ¬ GetCmdToken[cmdLineStream], GetCmdToken[cmdLineStream] UNTIL inputName = NIL DO
fullInputName: ROPE ~ FS.FileInfo[inputName].fullFName;
master: InterpressInterpreter.Master ~ InterpressInterpreter.Open[fullInputName, logProc];
IO.PutRope[log, fullInputName];
IO.PutRope[log, " "];
FOR i: INT IN [1..master.pages] DO
onePage: PROC [context: Imager.Context] ~ {InterpressInterpreter.DoPage[master, i, context, logProc]};
IO.PutRope[log, "["];
IO.PutRope[log, Convert.RopeFromInt[pageNumber ¬ pageNumber + 1]];
ImagerInterpress.DoPage[out, onePage];
IO.PutRope[log, "] "];
ENDLOOP;
ENDLOOP;
ImagerInterpress.Close[out];
[] ¬ FS.Copy[from: tempName, to: extendedName];
FS.Delete[tempName];
IO.PutRope[log, FS.FileInfo[extendedName].fullFName];
IO.PutRope[log, " written.\n"];
};
InterpressConcatenateCommand: Commander.CommandProc ~ {
stream: IO.STREAM ¬ IO.RIS[cmd.commandLine];
outputName: ROPE ¬ GetCmdToken[stream];
gets: ROPE ¬ GetCmdToken[stream];
IF outputName = NIL OR NOT (gets.Equal["←"] OR gets.Equal["¬"]) THEN RETURN[result: $Failure, msg: cmd.procData.doc];
DoConcatenate[outputName, stream, cmd.out ! FS.Error => IF error.group = user THEN {result ¬ $Failure; msg ¬ error.explanation; CONTINUE}];
};
Page Counting
InterpressLS: Commander.CommandProc ~ {
ENABLE FS.Error => IF error.group = user THEN CommanderOps.Failed[error.explanation];
log: IO.STREAM ~ cmd.out;
args: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd: cmd];
logProc: InterpressInterpreter.LogProc ~ {
IO.PutRope[log, "\n *** Interpress Error "];
IO.PutRope[log, explanation];
};
FOR i: NAT IN [1 .. args.argc) DO
name: ROPE ~ args[i];
fullInputName: ROPE ~ FS.FileInfo[name].fullFName;
master: InterpressInterpreter.Master;
IO.PutF1[log, "%g... ", [rope[fullInputName]]];
master ¬ InterpressInterpreter.Open[fullInputName, logProc];
IO.PutF1[log, " %g pages\n", [integer[master.pages]]];
InterpressInterpreter.Close[master];
ENDLOOP;
};
InterpressOverlay: PROC [output, input1, input2: ROPE, m: ImagerTransformation.Transformation] ~ {
prevMsg: ROPE ¬ NIL;
matchCount: INT ¬ 0;
Log: InterpressInterpreter.LogProc ~ {
WITH ProcessProps.GetProp[$ErrOut] SELECT FROM
errOut: IO.STREAM => {
IF Rope.Equal[explanation, prevMsg]
THEN {matchCount ¬ matchCount + 1}
ELSE {
IF matchCount > 0 THEN {
IO.PutF1[errOut, " (and %g more)\n", IO.int[matchCount]];
};
IO.PutRope[errOut, explanation];
IF explanation # NIL THEN IO.PutChar[errOut, '\n];
prevMsg ¬ explanation;
matchCount ¬ 0;
};
};
ENDCASE => NULL;
};
ref: ImagerInterpress.Ref ~ ImagerInterpress.Create[fileName: output];
master1: InterpressInterpreter.Master ~ InterpressInterpreter.Open[fileName: input1, log: Log];
master2: InterpressInterpreter.Master ~ InterpressInterpreter.Open[fileName: input2, log: Log];
FOR page: INT IN [1..master1.pages] DO
WriteAction: PROC [context: Imager.Context] ~ {
Write out the corresponding page of the first input, then apply the transformation, then write out the corresponding page of the second input.
context.SetGray [1];
InterpressInterpreter.DoPage[master: master1, page: page, context: context, log: Log];
Imager.ConcatT[context: context, m: m];
InterpressInterpreter.DoPage[master: master2, page: page2, context: context, log: Log];
};
page2: INT ~ ((page-1) MOD master2.pages)+1; --Cycle through the second master as needed
ImagerInterpress.DoPage[self: ref, action: WriteAction];
ENDLOOP;
Log[-1, ok, NIL, NIL];
ImagerInterpress.Close[self: ref];
};
InterpressOverlayCmd: Commander.CommandProc ~ {
ENABLE FS.Error => IF error.group = user THEN CommanderOps.Failed[error.explanation];
IsIt: PROC [rope: ROPE, parmsNeeded: INT] RETURNS [BOOLEAN] ~ {
RETURN [Rope.Equal[rope, tokens[tokenIndex], FALSE] AND tokens.argc > tokenIndex+parmsNeeded];
};
SkipArgs: PROC [args: INT] ~ INLINE {tokenIndex ¬ args+tokenIndex+1};
Real: PROC [index: INT] RETURNS [real: REAL] ~ {
RETURN [Convert.RealFromRope[r: tokens[index]]];
};
tokens: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd: cmd];
Arg 0 .../InterpressOverlay
Arg 1 output
Arg 2 ←
Arg 3 input1
Arg 4 input2
Arg 5+ Transformation options...
tokenIndex: INT ¬ 5;  --Initialized to start of transformation options
m: ImagerTransformation.Transformation ¬ ImagerTransformation.Scale[s: 1];
IF tokens.argc < 5 OR ~Rope.Equal[s1: "←", s2: tokens[2]] THEN CommanderOps.Failed[cmd.procData.doc];
Go through the transformation options...
WHILE tokenIndex<tokens.argc DO
SELECT TRUE FROM
IsIt["translate", 2] => {
m ¬ ImagerTransformation.PreTranslate[m: m, t: [Real[tokenIndex+1], Real[tokenIndex+2]]];
SkipArgs[2];
};
IsIt["rotate", 1] => {
m ¬ ImagerTransformation.PreRotate[m: m, r: Real[tokenIndex+1]];
SkipArgs[1];
};
IsIt["scale", 1] => {
m ¬ ImagerTransformation.PreScale[m: m, s: Real[tokenIndex+1]];
SkipArgs[1];
};
IsIt["scale2", 2] => {
m ¬ ImagerTransformation.PreScale2[m: m, s: [Real[tokenIndex+1], Real[tokenIndex+2]]];
SkipArgs[2];
};
ENDCASE => CommanderOps.Failed[cmd.procData.doc];
ENDLOOP;
InterpressOverlay[output: tokens[1], input1: tokens[3], input2: tokens[4], m: m];
};
InterpressFromRESAction: PROC [inputName: ROPE, outputName: ROPE, cmd: Commander.Handle, cmds: IO.STREAM] ~ {
res: RasterEncodingStandardIO.RES ~ RasterEncodingStandardIO.Read[inputName];
OnePage: PROC [context: Imager.Context] ~ {
ImagerRES.ShowRES[context: context, res: res, useImageScale: TRUE];
};
out: ImagerInterpress.Ref ~ ImagerInterpress.Create[outputName];
ImagerInterpress.DoPage[out, OnePage];
ImagerInterpress.Close[out];
};
MakeOutputName: PROC [inputName: ROPE, doc: ROPE] RETURNS [ROPE] ~ {
start: INT ¬ Rope.Index[s1: doc, s2: " to "]+4;
end: INT ¬ Rope.Index[s1: doc, pos1: start, s2: " "];
space: INT;
cp: FS.ComponentPositions;
[inputName, cp] ¬ FS.ExpandName[inputName];
space ¬ Rope.Index[s1: Rope.Substr[inputName, cp.base.start, cp.base.length], s2: " "]; -- = cp.base.length if no spaces in the base
RETURN [Rope.Cat[Rope.Substr[inputName, cp.base.start, space], ".", Rope.Substr[doc, start, end-start]]] --if spaces were in the base, this uses just the first word
};
FindFullName: PROC [inputName: ROPE] RETURNS [ROPE] ~ {
fullFName: ROPE ¬ NIL;
fullFName ¬ FS.FileInfo[inputName].fullFName;
RETURN [fullFName]
};
QuotedTokenBreak: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '" THEN RETURN [break];
RETURN [other];
};
GetQuotedToken: PROC [stream: IO.STREAM] RETURNS [ROPE] = {
rope: ROPE ¬ NIL;
rope ¬ stream.GetTokenRope[QuotedTokenBreak ! IO.EndOfStream => CONTINUE].token;
[] ¬ stream.GetTokenRope[QuotedTokenBreak ! IO.EndOfStream => CONTINUE]; --skip over final quote
RETURN [rope];
};
InterpressFromRESCommand: Commander.CommandProc ~ {
stream: IO.STREAM ¬ IO.RIS[cmd.commandLine];
outputName: ROPE ¬ GetCmdToken[stream];
secondTokenIndex: INT ¬ IO.GetIndex[stream];
gets: ROPE ¬ GetCmdToken[stream];
inputName: ROPE ¬ NIL;
IF outputName.Equal["\""] THEN { --reset and do it again, allowing spaces in name
stream.SetIndex[secondTokenIndex];
outputName ¬ GetQuotedToken[stream];
secondTokenIndex ¬ IO.GetIndex[stream];
gets ¬ GetCmdToken[stream];
};
IF NOT (gets.Equal["←"] OR gets.Equal["¬"])
THEN {
inputName ¬ outputName;
outputName ¬ NIL;
stream.SetIndex[secondTokenIndex];
}
ELSE {
inputName ¬ GetCmdToken[stream];
IF inputName.Equal["\""] THEN inputName ¬ GetQuotedToken[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[" . . . "];
InterpressFromRESAction[inputName, outputName, cmd, stream];
outputName ¬ FindFullName[outputName];
cmd.out.PutRope[outputName];
cmd.out.PutRope[" written.\n"];
EXITS Quit => NULL
};
Command Registration
Commander.Register[key: "InterpressExtract", proc: InterpressExtractCommand, doc: "Extract pages from interpress files, e.g. result.ip ← source.ip PAGES 1, 3, [10..15]"];
Commander.Register[key: "IPExtract", proc: InterpressExtractCommand, doc: "Extract pages from interpress files, e.g. result.ip ← source.ip PAGES 1, 3, [10..15] (alias for InterpressExtract)"];
Commander.Register[key: "InterpressBreakup", proc: InterpressBreakupCommand, doc: "Breaks an interpress master into a collection of masters: InterpressBreakup [-by <pages> | -into <files>] <input>"];
Commander.Register[key: "IPBreakup", proc: InterpressBreakupCommand, doc: "Breaks an interpress master into a collection of masters (alias for InterpressBreakup)"];
Commander.Register[key: "InterpressConcatenate", proc: InterpressConcatenateCommand, doc: "Concatenates a collection of Interpress masters into one. (output ← input1 input2 . . .)"];
Commander.Register[key: "IPConcatenate", proc: InterpressConcatenateCommand, doc: "Concatenates a collection of Interpress masters into one. (output ← input1 input2 . . .) (alias for InterpressConcatenate)"];
Commander.Register[key: "InterpressLS", proc: InterpressLS, doc: "List page count of files, e.g. InterpressLS foo.ip bar.ip"];
Commander.Register[key: "IPLS", proc: InterpressLS, doc: "List page count of files (alias for InterpressLS)"];
Commander.Register["InterpressOverlay", InterpressOverlayCmd, "Merge two interpress masters (<output> ← <input1> <input2> {translate <x> <y> | rotate <angle> | scale <scale> | scale2 <sx> <sy>})"];
Commander.Register["InterpressFromRES", InterpressFromRESCommand, "Convert an RES file to Interpress"];
END.