<> <> <> <> <> <<>> 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]; <> Warning: PUBLIC SIGNAL [class: ATOM, explanation: ROPE] ~ CODE; Error: PUBLIC ERROR [class: ATOM, explanation: ROPE] ~ CODE; <> <> <> <> 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: ROPE _ NIL; --final output from Concat of each colorized piece of doc useIP2: BOOL _ TRUE; --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: BOOL _ FALSE; documentRopeSlice: ROPE _ NIL;--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 { < 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] ~ { <> <> 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 ~ { <> 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]; }; <> 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: BOOL _ FALSE; sweepAngle: ROPE _ def.first.Substr[start: 5]; --eg, Sweep15 => sweepAngle="15" dummyHead: LIST OF ROPE _ LIST[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; <> 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: ROPE _ NIL; 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: ROPE _ NIL, 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--]; <> }; 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: ROPE _ NIL] ~ { --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]]]}; <> <> 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: ROPE _ NIL] ~ { 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: INT _ MIN[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 is used, ("CustomColors (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: INT _ IF 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: INT _ ABS[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: BOOL _ FALSE; --TRUE => found enabled, see if (Keep) on next line enabled: BOOL _ FALSE; --TRUE => have found the password on this page enabledKeep: BOOL _ FALSE; --TRUE => have found the keep password on this page everEnabled: BOOL _ FALSE; --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: BOOL _ FALSE] RETURNS [y: INT _ 0] ~ { startY: NAT _ IF 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 _ IPMaster.IntFromSequenceData[text: Rope.ToRefText[setxyFrag], start: startY, len: 2]; '\302 --using multi-byte seqInteger encoding for y, # bytes found in next byte-- => { bytes: NAT _ ORD[setxyFrag.Fetch[startY _ startY+1]]; --#bytes in y IF bytes>4 THEN ERROR; --system error; would overflow INT boundaries <> <> <> y _ IPMaster.IntFromSequenceData[text: Rope.ToRefText[setxyFrag], start: startY+1, len: bytes]; }; ENDCASE => ERROR; --system error; unknown encoding }; <> InstallStandardSettings[]; RegisterKeywords[keywordsList: LIST["Sweep", "Remove"]]; --Reserved colorizing keywords. These can be a pattern (ie, Sweep45 will be found as a keyword). END.