ColorizeViewPointImplA.mesa
Copyright Ó 1988, 1989, 1990 by Xerox Corporation. All rights reserved.
Eric Nickell, May 22, 1989 4:09:31 pm PDT
Bob Coleman, September 6, 1990 12:14:03 pm PDT
Dave Rumph, January 23, 1990 1:11:41 pm PST
DIRECTORY
ColorizeViewPoint, ColorizeViewPointBackdoor,
ColorizeViewPointSweep, Convert, FS, IO, IPMaster, IPScan, Process, Profiles, Real, Rope, RopeFile, Vector2;
ColorizeViewPointImplA: CEDAR PROGRAM
IMPORTS ColorizeViewPointBackdoor, ColorizeViewPointSweep, Convert, FS, IO, IPMaster, IPScan, Process, Profiles, Real, Rope, RopeFile
EXPORTS ColorizeViewPoint, ColorizeViewPointBackdoor
~ BEGIN
OPEN ColorizeViewPoint, ColorizeViewPointBackdoor;
Profile: TYPE ~ Profiles.Profile;
VEC: TYPE ~ Vector2.VEC;
ASSERTION: TYPE ~ BOOL [TRUE..TRUE];
Errors
Warning: PUBLIC SIGNAL [class: ATOM, explanation: ROPE] ~ CODE;
Error: PUBLIC ERROR [class: ATOM, explanation: ROPE] ~ CODE;
class describes the general type of error (e.g., $MalformedPaletteEntry)
explanation is a user-readable ROPE describing the problem
In general, clients should RESUME Warning.
Standard Colorization
Do: PUBLIC PROC [fromFile, toFile: ROPE, palette: Profiles.Profile, checkSystemSetting: CheckSystemSettingProc] ~ {
fromIP: ROPE ~ RopeFile.Create[name: fromFile];
toIP: ROPE ~ DoRope[fromIP: fromIP, palette: palette, checkSystemSetting: checkSystemSetting];
out: IO.STREAM ~ FS.StreamOpen[fileName: toFile, accessOptions: create];
out.PutRope[toIP];
out.Close;
};
DoRope: PUBLIC PROC [fromIP: ROPE, palette: Profiles.Profile, checkSystemSetting: CheckSystemSettingProc] RETURNS [toIP: ROPE] ~ {
If: PROC [option: ROPE] RETURNS [BOOL] ~ {
Default: PROC RETURNS [BOOL] ~ {
FOR each: LIST OF SettingData ← settings, each.rest UNTIL each=NIL DO
IF Rope.Equal[s1: each.first.setting.key, s2: option, case: FALSE] THEN RETURN [each.first.setting.default];
ENDLOOP;
ERROR; --System error!
};
RETURN [Profiles.Boolean[profile: palette, key: option, default: checkSystemSetting[key: option, default: Default[]]]];
};
enable: BOOL;
header, ipToColorize: ROPE;
[header: header, ip: ipToColorize] ← IPRopeFromRope[fromIP];
SELECT TRUE FROM
Rope.Match["Interpress/Xerox/1.*", header] => enable ← If["Colorize1"];
Rope.Match["Interpress/Xerox/2.*", header] => enable ← If["Colorize2"];
Rope.Match["Interpress/Xerox/3.0", header] => enable ← If["Colorize3"];
ENDCASE => ERROR Error[$UnknownIPVersion, IO.PutFR[format: "Unknown encoding of Interpress: [%g]", v1: [rope[header]]]];
IF enable THEN { --Enable any colorization
paletteSlices: LIST OF Profiles.Slice ← palette.GetProfileSlices; --a profile is defined with "file slices" and "rope slices"
clientFileSlices: LIST OF Profiles.Slice ~ GetProfileFileSlices[paletteSlices]; --default profile from client
clientRopeSlice: ROPE ~ CleanupUserCommands[RopeFromProfileRopeSlices[paletteSlices], palette]; --custom commands from the client
finalIP: ROPENIL; --final output from Concat of each colorized piece of doc
useIP2: BOOLTRUE; --set false almost always (for color colorizations, use 3.0 header). But "colorizations" done with certain palettes (like "BW" or some "Grey") should get a 2.2 header to be printable on widest range of printers, not just color printers.
stillToColorize: ROPE ← DoPrePass[ipToColorize]; --if more than one CustomColorsPage, each chunk of document is ruled by its CustomColorsPage; stillToColorize is the rest of document. DoPrePass does any necessary operations before colorization.
DO
customColorsFound: BOOLFALSE;
documentRopeSlice: ROPENIL;--custom cmds from document's current CustomColorsPage
curPalette: Profiles.Profile ← palette; --stores the current palette for each doc section
1. Get & clean the next CustomColorsPage in the document, if any
IF If["FindCustomCommands"] THEN [ipToColorize: ipToColorize, documentRopeSlice: documentRopeSlice, stillToColorize: stillToColorize] ← ObtainEmbeddedProfile[stillToColorize];
IF ipToColorize.Equal[""] THEN {IF stillToColorize=NIL THEN EXIT ELSE LOOP};
documentRopeSlice ← CleanupUserCommands[documentRopeSlice, palette];
2. If either a CustomColorsPage or commands from the client, construct a new palette which includes them
IF customColorsFound ← (clientRopeSlice#NIL OR documentRopeSlice#NIL) THEN
curPalette ← Profiles.CreateFromSlices[slices: JoinLists[clientFileSlices, LIST[[rope[documentRopeSlice.Concat[clientRopeSlice]]]]], keepFresh: FALSE];--the order documentRopeSlice before clientRopeSlice is important: Profiles.mesa layers later slices over earlier; this is a decision that clientRopeSlice (the incoming rope slices) take precedence over the document custom color commands (documentRopeSlice), so that a printer msg or a commandtool cmd may, for example, temporarily suspend the document-embedded specs
3. Using the new palette, perform all requested colorizations
IF customColorsFound OR ~ If["ColorizeOnlyIfCustomColors"] THEN {
If["ColorizeOnlyIfCustomColors"] => that colorization should only take place for documents where an embedded palette or printer messages are found.
mapData: MapData ~ GetMappings[curPalette];
FOR each: LIST OF SettingData ← settings, each.rest UNTIL each=NIL DO
Process.CheckForAbort[];
IF each.first.colorization#NIL THEN {
ENABLE Error => { --Map Error to Warning, and abandon colorization
SIGNAL Warning[class, IO.PutFR[format: "%g in colorization %g, which has been abandoned", v1: [rope[explanation]], v2: [rope[each.first.setting.key]]]];
GOTO AbandonColorization;
};
IF If[each.first.setting.key] THEN ipToColorize ← each.first.colorization[ip: ipToColorize, palette: curPalette, checkSystemSetting: checkSystemSetting, mapData: mapData];
EXITS AbandonColorization => NULL;
};
ENDLOOP;
};
finalIP ← finalIP.Concat[ipToColorize];
useIP2 ← MIN[useIP2, Profiles.Boolean[profile: curPalette, key: "IP2", default: FALSE]];--if each section of the doc allows an IP2 header, then useIP2, otherwise IP3
IF stillToColorize=NIL THEN EXIT;
ENDLOOP;
4. Finally, output the new colorized ip with the correct header
header ← IF useIP2 THEN "Interpress/Xerox/2.2 " ELSE "Interpress/Xerox/3.0 ";
RETURN [Rope.Concat[header, finalIP]]; --the colorized ip
};
RETURN [fromIP]; --the uncolorized ip if enable FALSE
};
SettingData: TYPE ~ RECORD [setting: Setting, colorization: Colorization ← NIL];
settings: LIST OF SettingData ← NIL;
InstallNewColorization: PUBLIC PROC [colorization: PROC [ip: ROPE, palette: Profiles.Profile, checkSystemSetting: CheckSystemSettingProc, mapData: MapData] RETURNS [newIP: ROPE], setting: Setting] ~ {
We just add this stuff as a setting which happens to have an associated colorization
New [colorization, setting] pairs are added to the end of the list.
new: LIST OF SettingData ~ LIST[[setting, colorization]];
IF settings=NIL THEN {
settings ← new;
RETURN;
};
FOR each: LIST OF SettingData ← settings, each.rest DO
IF each.rest=NIL THEN {
each.rest ← new;
EXIT;
};
ENDLOOP;
};
InstallStandardSettings: PROC ~ {
Note: The first colorizations installed are the first to be performed!
InstallNewColorization[colorization: NIL, setting: ["ColorizeOnlyIfCustomColors", "Colorization will occur only if custom color pages are found in the document or if custom color instructions are passed in (eg, thru printer message)", FALSE]];
InstallNewColorization[colorization: NIL, setting: ["FindCustomCommands", "Search interpress master for colorization profile pages", TRUE]];
InstallNewColorization[colorization: NIL, setting: ["Colorize3", "Enable colorization of Interpress 3.0 masters", FALSE]];
InstallNewColorization[colorization: NIL, setting: ["Colorize2", "Enable colorization of Interpress 2.x masters", TRUE]];
InstallNewColorization[colorization: NIL, setting: ["Colorize1", "Enable colorization of Interpress 1.x masters", TRUE]];
};
ExaminesTheseSettings: PUBLIC PROC RETURNS [list: LIST OF Setting ← NIL] ~ {
FOR each: LIST OF SettingData ← settings, each.rest UNTIL each=NIL DO
list ← CONS[each.first.setting, list];
ENDLOOP;
};
DoPrePass: PROC [ip: ROPE] RETURNS [newIP: ROPE] ~ {--setup before colorization
PerOp: IPScan.ScanProc ~ {
[min: INT, max: INT, op: IPMaster.Op ← nil, seq: IPScan.Seq ← nil, num: INTEGER ← 0, punt: BOOL ← FALSE] RETURNS [quit: BOOL ← FALSE]
IF max=newIP.Length THEN RETURN; --protects bounds fault at last endBody
IF Rope.Equal[s1: newIP.Substr[start: max, len: 2], s2: "\240j"--{ (beginBody)--] THEN newIP ← newIP.Replace[start: max, len: 2, with: "\240j\017\241\241\250" --{ 1 SETGRAY--] --explicitly insert initial default color setting (1 SetGray) at beginning of each page; page beginning found by endBody immed. followed by beginBody
};
newIP ← ip;
IPScan.ScanRope[ip: newIP, ops: LIST[endBody], seqs: NIL, action: PerOp];
};
Utilities
IPRopeFromName: PUBLIC PROC [xeroxName: ROPE] RETURNS [header, ip: ROPE] ~ {
[header: header, ip: ip] ← IPScan.IPRopeFromName[xeroxName: xeroxName];
};
IPRopeFromRope: PUBLIC PROC [xeroxRope: ROPE] RETURNS [header, ip: ROPE] ~ {
pos: INT ~ Rope.Find[s1: xeroxRope, s2: " "];
RETURN [
header: xeroxRope.Substr[len: pos],
ip: xeroxRope.Substr[start: pos+1]
];
};
IPFragmentForColorSetting: PUBLIC PROC [def: LIST OF ROPE, palette: Profiles.Profile] RETURNS [frag: REF] ~ {
frag ← IPFragmentForColorDefinition[def: def, palette: palette];
IF frag=NIL THEN RETURN;
WITH frag SELECT FROM
frag: ROPE => RETURN [frag.Concat["\017\255\223" --13 (color) ISET--]];
frag: REF SampledColorIPFragments => {
RETURN [NEW[SampledColorIPFragments ← [
beforeTransform: frag.beforeTransform,
afterTransform: frag.afterTransform.Concat["\017\255\223" --13 (color) ISET-- ],
sweepAngleMod360: frag.sweepAngleMod360,
removeDefiningObject: frag.removeDefiningObject
]]];
};
ENDCASE => ERROR; --System error!
};
IPFragmentForColorDefinition: PUBLIC PROC [def: LIST OF ROPE, palette: Profiles.Profile] RETURNS [frag: REF] ~ {
SELECT TRUE FROM
def=NIL OR def.first=NIL => RETURN [NIL];
Rope.Equal[s1: def.first.Substr[0,5], s2: "Sweep", case: FALSE] => {--Sweeps
iSize: NAT;
beforeTransform, afterTransform: ROPE;
sweepAngleMod360: [0..360) ← 0;
removeDefiningObject: BOOLFALSE;
sweepAngle: ROPE ← def.first.Substr[start: 5]; --eg, Sweep15 => sweepAngle="15"
dummyHead: LIST OF ROPELIST[NIL]; --mechanism to add elements to end of a list
tail: LIST OF ROPE ← dummyHead;
FOR each: LIST OF ROPE ← def, each.rest UNTIL each=NIL DO
IF Rope.Match[pattern: "Remove*", object: each.first, case: FALSE] THEN removeDefiningObject ← TRUE
ELSE tail ← (tail.rest ← LIST[each.first]); --keep all tokens but "Remove*"
ENDLOOP;
def ← dummyHead.rest;
IF ~sweepAngle.Equal[""] THEN {
realSweep: REAL ← Convert.RealFromRope[sweepAngle ! Convert.Error => GOTO Oops];
temp: INTEGER ← ColorizeViewPointBackdoor.AltRound[realSweep] MOD 360;
sweepAngleMod360 ← IF temp<0 THEN temp+360 ELSE temp;
We do it this way because MOD's behavior wrt negative arguments is not well defined, and therefore not portable.
EXITS Oops => SIGNAL Warning[$MalformedPaletteEntry, IO.PutFR[format: "Need a number following \"Sweep\" , eg Sweep15, but found \"Sweep%g\". Sweep angle being set to 0.", v1: [rope[sweepAngle]] ]];
};
def ← def.rest;
[iSize, beforeTransform] ← ColorizeViewPointSweep.ConstructSweepPixelArray[def: def, palette: palette];
afterTransform ← Rope.Cat["\240\240\017\244\222\240\245" --MAKET 4 IGET CONCAT--, BuildColorOperator[i: iSize, scaleFactor: 255 ! Error => { SIGNAL Warning[class: class, explanation: explanation]; GOTO Barf }], "\241\253" --MAKESAMPLEDCOLOR--];
frag ← NEW[SampledColorIPFragments ← [
beforeTransform: beforeTransform,
afterTransform: afterTransform,
sweepAngleMod360: sweepAngleMod360,
removeDefiningObject: removeDefiningObject
]];
SetProfileBoolean[profile: palette, key: "IP2", val: FALSE]; --InterPress 2.0 can't do sweeps
};
Rope.Equal[s1: def.first, s2: "IPFrag", case: FALSE] => {--Pre-defined IP fragment follows
IF def.rest#NIL THEN RETURN [def.rest.first];
};
ENDCASE => { --Constant
scaleFactor: NAT ~ 1000;
scaledVector: ROPE;
vectorSize: NAT;
[scaledVector, vectorSize] ← BuildScaledVector[def, scaleFactor ! Convert.Error => {
SIGNAL Warning[$MalformedPaletteEntry, "Non-number found in following color definition:"]; GOTO Barf }];
IF vectorSize=1 THEN {
fragStream: IO.STREAM ~ IO.ROS[];
IPMaster.PutReal[stream: fragStream, val: Convert.RealFromRope[r: def.first]];
IPMaster.PutOp[stream: fragStream, op: makegray];
frag ← IO.RopeFromROS[self: fragStream];
}
ELSE frag ← scaledVector.Cat[BuildColorOperator[i: vectorSize, scaleFactor: scaleFactor
! Error => { SIGNAL Warning[class: class, explanation: explanation]; GOTO Barf }], "\240\347" --DO--];
};
EXITS Barf =>
IF def=NIL OR def.first=NIL THEN RETURN [NIL]
ELSE {
colorEntry: ROPENIL;
FOR each: LIST OF ROPE ← def, each.rest UNTIL each=NIL DO
colorEntry ← colorEntry.Cat[each.first, " "];
ENDLOOP;
SIGNAL Warning[$MalformedPaletteEntry, IO.PutFR[format: "Problem color definition: \"%g\". Objects with that entry will not be colorized.", v1: [rope[colorEntry]] ]];
RETURN [NIL];
};
};
BuildScaledVector: PROC [list: LIST OF ROPE, scaleFactor: NAT] RETURNS [vec: ROPENIL, size: NAT ← 0] ~ {
vecStream: IO.STREAM ~ IO.ROS[];
FOR each: LIST OF ROPE ← list, each.rest UNTIL each=NIL DO
int: INT;
real: REAL;
real ← MIN[1, MAX[0, Convert.RealFromRope[r: each.first]]];
int ← ColorizeViewPointBackdoor.AltRound[real * scaleFactor];
IPMaster.PutInt[stream: vecStream, n: int];
size ← size+1;
ENDLOOP;
IPMaster.PutShortNumber[stream: vecStream, n: size];
IPMaster.PutOp[stream: vecStream, op: makevec];
vec ← IO.RopeFromROS[self: vecStream];
};
BuildColorOperator: PROC [i, scaleFactor: NAT] RETURNS [ip: ROPE] ~ {
ip ← Rope.Cat[BuildColorOperatorScaleInfo[i, scaleFactor], BuildColorModelOperatorFinder[i], "\240\347" --DO--];
eg, ip ← scaleInformation colorModelOperator DO
};
BuildColorOperatorScaleInfo: PROC [i, scaleFactor: NAT] RETURNS [ip: ROPE] ~ {
out: IO.STREAM ~ IO.ROS[];
IPMaster.PutShortNumber[stream: out, n: scaleFactor];
SELECT i FROM
1 => ip ← Rope.Cat["\017\240", out.RopeFromROS, "\017\240\017\243\241\033"]; -- 0 scaleFactor 0 3 MAKEVEC. This creates a GrayLinear which maps to SETGRAY (white=0, black=scaleFactor). The second 0 defaults an optional LookUpTable for the gray values to NIL
3, 4 => ip ← out.RopeFromROS.Cat["\017\241\241\033"]; -- scaleFactor 1 MAKEVEC
ENDCASE => ERROR Error[$MalformedPaletteEntry, IO.PutFR[format: "The following color definition has %g numbers (only 1, 3, or 4 allowed):", v1: [cardinal[i]] ]];
};
BuildColorModelOperatorFinder: PROC [i: NAT] RETURNS [ip: ROPE] ~ {
SELECT i FROM
1 => ip ← "\305\005Xerox\305\012GrayLinear\017\242\241\033\241\246"; -- Xerox GrayLinear 2 MAKEVEC FINDCOLORMODELOPERATOR
3 => ip ← "\305\005Xerox\305\bResearch\305\tRGBLinear\017\243\241\033\241\246"; -- Xerox Research RGBLinear 3 MAKEVEC FINDCOLORMODELOPERATOR
4 => ip ← "\305\005Xerox\305\bResearch\305\004CMYK\017\243\241\033\241\246"; -- Xerox Research CMYK 3 MAKEVEC FINDCOLORMODELOPERATOR
ENDCASE => NULL;
};
RopeFromProfileRopeSlices: PROC [sliceList: LIST OF Profiles.Slice] RETURNS [sliceRope: ROPENIL] ~ { --Turns a list of Profiles.Slice.rope into a single rope with a space between each slice
FOR each: LIST OF Profiles.Slice ← sliceList, each.rest UNTIL each=NIL DO
WITH each.first SELECT FROM
ropeSlice: Profiles.Slice.rope => sliceRope ← sliceRope.Cat[ropeSlice.text, " "];
ENDCASE => NULL;
ENDLOOP;
};
GetProfileFileSlices: PROC [allSlices: LIST OF Profiles.Slice] RETURNS [fileSlices: LIST OF Profiles.Slice] ~ { RETURN [
IF allSlices=NIL THEN NIL
ELSE WITH allSlices.first SELECT FROM
fileSlice: Profiles.Slice.file => CONS[fileSlice, GetProfileFileSlices[allSlices.rest]],
ENDCASE => GetProfileFileSlices[allSlices.rest] ];
};
JoinLists: PROC [start: LIST OF Profiles.Slice, addAtEnd: LIST OF Profiles.Slice] RETURNS [joined: LIST OF Profiles.Slice] ~ {RETURN [IF start=NIL THEN addAtEnd ELSE CONS[start.first, JoinLists[start.rest, addAtEnd]]]};
Embedded Palettes (Custom Colors)
embeddedProfilePassword: ROPE ~ "UseThisPageAsColorizerProfileEntries";
embeddedProfilePassword: ROPE ~ "CustomColor"; --actually "CustomColor*" will match
keepPassword: ROPE ~ "Keep"; --use embedded profile but don't delete it from ip master
endPassword: ROPE ~ "EndCustom"; --use to end CustomColor commands before page end
ObtainEmbeddedProfile: PROC [oldIp: ROPE] RETURNS [ipToColorize, documentRopeSlice, stillToColorize: ROPENIL] ~ {
PerOp: IPScan.ScanProc = {
[min: INT, max: INT, op: IPMaster.Op ← nil, seq: IPScan.Seq ← nil, num: INTEGER ← 0, punt: BOOL ← FALSE] RETURNS [quit: BOOL ← FALSE]
FoundKeep: PROC [] RETURNS [BOOL] ~ { --searches for uncommented keepPassword
seq: ROPE ← oldIp.Substr[start: min+2, len: max-min-2];
commentPos: INTMIN[seq.Index[s2: "--"], seq.Index[s2: "<<"], seq.Index[s2: "\377\041\076\076\377\000" --extended char set dbldash--]];
RETURN [seq.Index[s2: keepPassword, case: FALSE] < commentPos];
};
IF punt THEN RETURN;
SELECT op FROM
beginBody => {
IF bodyNest=0 THEN {
bodyStart ← min;
enabled ← enabledKeep ← FALSE;
};
bodyNest ← bodyNest+1;
};
endBody => {
IF (bodyNest ← bodyNest-1)=0 THEN SELECT TRUE FROM
enabled, enabledKeep, everEnabled => {--CustomColors found; abort scan, colorize, then go on
ipToColorize ← ipToColorize.Concat[oldIp.Substr[start: flushFrom, len: IF enabled THEN bodyStart-flushFrom ELSE --include CCPage-- max-flushFrom]];
IF oldIp.Substr[start: max].Equal["\240g"--END--] THEN ipToColorize ← ipToColorize.Concat["\240g"] ELSE stillToColorize ← oldIp.Substr[start: max];
quit ← TRUE; --will cause early abort of IPScan
profileAddition.PutChar['\n];
};
ENDCASE => NULL; -- just keep going
};
nil => { --short sequenceString; actual sequence starts at min+2
IF oldIp.Fetch[index: min]#'\301 -- short sequenceString-- THEN ERROR; --System error!
SELECT TRUE FROM
enabled OR enabledKeep => {
IF lookForKeep AND FoundKeep[] THEN {enabledKeep ← TRUE; enabled ← FALSE} --if <tab> is used, ("CustomColors <tab> (Keep)") VP uses SETXY for tabs and we catch SETXYs as newline; so lookForKeep allows us to check one more line for the keepPassword before it is reset to FALSE
ELSE SELECT TRUE FROM
endPassword.Run[s2: oldIp, pos2: min+2, case: FALSE]=endPasswordLength --"EndCustom*"-- AND (enabled OR enabledKeep) => {
ipToColorize ← ipToColorize.Concat[oldIp.Substr[start: flushFrom, len: IF enabled THEN passStart-flushFrom ELSE --include CC section-- max+1-flushFrom]];
enabled ← enabledKeep ← FALSE; --collect no more CC cmds
flushFrom ← max+1 --get past SHOW--
};
ENDCASE => { --this line is a custom color command
profileAddition.PutRope[oldIp.Substr[start: min+2, len: max-min-2]];
profileAddition.PutRope["\377\000" --\377\000 ensures this is char set 0 since a hyphen sometimes changes the char set--];
};
lookForKeep ← FALSE;
};
embeddedProfilePassword.Run[s2: oldIp, pos2: min+2, case: FALSE]=embeddedProfilePasswordLength --"CustomColor*"-- => {
enabled ← lookForKeep ← ~(enabledKeep ← FoundKeep[]);
everEnabled ← TRUE;
passStart ← min;
};
ENDCASE => NULL;
};
setxy => IF enabled OR enabledKeep THEN {--sameY=tab(add space); diffY=newLine(\n)
testY: INT ← ExtractY[oldIp.Substr[start: min, len: max-min]];
IF curY=testY THEN profileAddition.PutRope[" "]
ELSE {
prevY ← curY;
curY ← testY;
IF DifLineSpace[] THEN profileAddition.PutRope["\f"] ELSE profileAddition.PutRope["\n"]; --obscure problem. ViewPoint uses SETXY to do a newLine & also to spill a long line over. This is a hint to whether this is a completely newLine farther down on the page, like a footer, or a spill over (same y offset as used on prev line).
};
};
setxyrel, setyrel => IF enabled OR enabledKeep THEN {
testY: INTIF op=setxyrel THEN ExtractY[oldIp.Substr[start: min, len: max-min]] ELSE ExtractY[setxyFrag: oldIp.Substr[start: min, len: max-min], noX: TRUE];
IF testY=0 --same line-- THEN profileAddition.PutRope[" "]
ELSE { --diff line
prevY ← curY;
curY ← curY+testY;
IF DifLineSpace[] THEN profileAddition.PutRope["\f"] ELSE profileAddition.PutRope["\n"];
};
};
ENDCASE => ERROR; --System error!
};
DifLineSpace: PROC [] RETURNS [diff: BOOL]~ {
curJump: INTABS[curY-prevY];
diff ← prevJump#0 AND (curJump>prevJump+10); --10 is fudge allowed for line spacing
prevJump ← IF diff THEN 0 ELSE curJump; --every time there's a big jump, start over
};
embeddedProfilePasswordLength: INT ~ embeddedProfilePassword.Size;
endPasswordLength: INT ~ endPassword.Size;
lookForKeep: BOOLFALSE; --TRUE => found enabled, see if (Keep) on next line
enabled: BOOLFALSE;  --TRUE => have found the password on this page
enabledKeep: BOOLFALSE; --TRUE => have found the keep password on this page
everEnabled: BOOLFALSE; --TRUE => at least one page had profile entries
bodyNest: INT ← 0;
flushFrom, bodyStart, passStart: INT ← 0;
curY, prevY: INT ← 0; --used to distinguish Tab SETXYs from \n SETXYs; used as vague hint to detect a big jump on page like a footer
prevJump: INT ← 0; --tracks the y-diff in the prev. line
profileAddition: IO.STREAM ~ IO.ROS[];
IPScan.ScanRope[ip: oldIp, ops: LIST[beginBody, endBody, setxy, setxyrel, setyrel], seqs: LIST[sequenceString], action: PerOp];
IF everEnabled THEN RETURN [ipToColorize: ipToColorize, documentRopeSlice: profileAddition.RopeFromROS, stillToColorize: stillToColorize]
ELSE RETURN [ipToColorize: oldIp, documentRopeSlice: NIL, stillToColorize: NIL];
};
ExtractY: PROC [setxyFrag: ROPE, noX: BOOLFALSE] RETURNS [y: INT ← 0] ~ {
startY: NATIF noX THEN 0 ELSE 2; --2 is normal start of y in setxyFrag, if x encoded as short number
IF ~noX THEN SELECT setxyFrag.Fetch[0] --start of x-- FROM
<= '\177 --using 2 byte short number encoding for x-- => NULL; --startY correct
'\302 --using multi-byte seqInteger encoding for x, # bytes found in next byte-- => startY ← startY + ORD[setxyFrag.Fetch[1]];
ENDCASE => ERROR; --system error; unknown encoding
SELECT setxyFrag.Fetch[startY] --start of y-- FROM
<= '\177 --using 2 byte short number encoding for y, biased by 4000-- =>
y ← ORD[setxyFrag.Fetch[startY]]*256 + ORD[setxyFrag.Fetch[startY+1]] - 4000;
y ← IPMaster.IntFromSequenceData[text: Rope.ToRefText[setxyFrag], start: startY, len: 2];
'\302 --using multi-byte seqInteger encoding for y, # bytes found in next byte-- => {
bytes: NATORD[setxyFrag.Fetch[startY ← startY+1]]; --#bytes in y
IF bytes>4 THEN ERROR; --system error; would overflow INT boundaries
FOR i: NAT IN [1..bytes] DO
y ← y+ORD[setxyFrag.Fetch[startY+i]]*Real.Round[RealFns.Power[base: 256.0, exponent: bytes-i]];
ENDLOOP;
y ← IPMaster.IntFromSequenceData[text: Rope.ToRefText[setxyFrag], start: startY+1, len: bytes];
};
ENDCASE => ERROR; --system error; unknown encoding
};
Initialization
InstallStandardSettings[];
RegisterKeywords[keywordsList: LIST["Sweep", "Remove"]]; --Reserved colorizing keywords. These can be a pattern (ie, Sweep45 will be found as a keyword).
END.