ImagerStateImpl.mesa
Copyright Ó 1984, 1985, 1986, 1987, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved.
Michael Plass, January 21, 1992 11:43 am PST
Doug Wyatt, May 7, 1986 5:56:33 pm PDT
DIRECTORY
Imager USING [Clip, Context, DoSave, DoSaveAll, Error, MakeGray, MaskRectangle, Rectangle, SetXY, StateRep, Trans, Warning],
ImagerBackdoor USING [Clipper, IntKey, RealKey],
ImagerClipper USING [Clipper],
ImagerColor USING [Color, ColorOperator, MakeSampledBlack, MakeSampledColor],
ImagerDeviceVector USING [DVec, DVecRep],
ImagerFont USING [Font, XChar, XStringProc],
ImagerPath USING [Outline, OutlineFromPath, PathProc],
ImagerPixelArray USING [PixelArray],
ImagerState USING [ChangeFlags, NonPersistentVariables, notChanged, OrChangeFlags, PersistentVariables, State, StateRep],
ImagerTransformation USING [ApplyPostConcat, ApplyPreConcat, ApplyPreRotate, ApplyPreScale2, ApplyPreTranslate, ApplyTranslateTo, Copy, Destroy, DRound, InverseTransform, InverseTransformVec, Invert, Scale, Transform, Transformation, TransformVec],
Real USING [LargestNumber],
RealFns USING [SqRt],
Rope USING [ROPE],
ImagerScaled USING [Float],
Vector2 USING [Add, Div, InlineAdd, InlineMulC, InlineSquare, Length, Mul, Sub, VEC];
ImagerStateImpl: CEDAR PROGRAM
IMPORTS Imager, ImagerColor, ImagerPath, ImagerState, ImagerTransformation, RealFns, ImagerScaled, Vector2
EXPORTS Imager, ImagerState
~ BEGIN OPEN ImagerState;
Context: TYPE ~ Imager.Context;
State: TYPE ~ ImagerState.State;
StateRep: PUBLIC TYPE ~ ImagerState.StateRep; -- export to Imager.StateRep
ROPE: TYPE ~ Rope.ROPE;
VEC: TYPE ~ Vector2.VEC;
Transformation: TYPE ~ ImagerTransformation.Transformation;
Rectangle: TYPE ~ Imager.Rectangle;
Font: TYPE ~ ImagerFont.Font;
XChar: TYPE ~ ImagerFont.XChar;
XStringProc: TYPE ~ ImagerFont.XStringProc;
Color: TYPE ~ ImagerColor.Color;
ColorOperator: TYPE ~ ImagerColor.ColorOperator;
PixelArray: TYPE ~ ImagerPixelArray.PixelArray;
PathProc: TYPE ~ ImagerPath.PathProc;
Outline: TYPE ~ ImagerPath.Outline;
Clipper: TYPE ~ ImagerClipper.Clipper;
Changed: PROC [state: State, changed: ChangeFlags] ~ INLINE {
state.changed ¬ OrChangeFlags[state.changed, changed];
state.np.changed ¬ OrChangeFlags[state.np.changed, changed];
};
refChanges: ChangeFlags ~ [T: TRUE, font: TRUE, color: TRUE, clipper: TRUE];
CreateState: PUBLIC PROC RETURNS [state: State] ~ {
state ¬ NEW[StateRep];
state.clientToDevice ¬ ImagerTransformation.Scale[1];
state.viewToDevice ¬ ImagerTransformation.Scale[1];
state.cp ¬ NEW[ImagerDeviceVector.DVecRep];
};
StateSave: PUBLIC PROC [context: Context, all: BOOL] RETURNS [REF] ~ {
savedState: State = context.state;
newState: State ¬ savedState.avail;
IF newState = NIL
THEN {
newState ¬ NEW[StateRep];
newState.clientToDevice ¬ ImagerTransformation.Copy[savedState.clientToDevice];
newState.viewToDevice ¬ ImagerTransformation.Copy[savedState.viewToDevice];
newState.cp ¬ NEW[ImagerDeviceVector.DVecRep ¬ savedState.cp­];
newState.avail ¬ NIL;
}
ELSE {
savedState.avail ¬ NIL;
newState.clientToDevice­ ¬ savedState.clientToDevice­;
newState.viewToDevice­ ¬ savedState.viewToDevice­;
newState.cp­ ¬ savedState.cp­;
};
newState.changed ¬ savedState.changed;
newState.p ¬ savedState.p;
newState.np ¬ savedState.np;
newState.np.changed ¬ notChanged;
newState.font ¬ savedState.font;
newState.color ¬ savedState.color;
newState.clipper ¬ savedState.clipper;
newState.previous ¬ savedState;
newState.restoreAll ¬ all;
newState.rasterData ¬ savedState.rasterData;
context.state ¬ newState;
RETURN [newState.previous]
};
StateRestore: PUBLIC PROC [context: Context, ref: REF] ~ {
currentState: State ~ context.state;
savedState: State ¬ currentState.previous;
IF ref # NIL AND ref # savedState THEN {
Imager.Warning[[$saveRestoreMismatch, "Save-restore mismatch"]];
savedState ¬ NARROW[ref];
};
IF savedState = NIL THEN Imager.Error[[$stackUnderflow, "State stack underflow"]];
savedState.changed ¬ OrChangeFlags[currentState.changed, currentState.np.changed];
IF NOT currentState.restoreAll THEN { savedState.p ¬ currentState.p; savedState.cp­ ¬ currentState.cp­ };
savedState.avail ¬ currentState;
currentState.previous ¬ NIL;
context.state ¬ savedState;
};
StateSetInt: PUBLIC PROC [context: Context, key: ImagerBackdoor.IntKey, val: INT] ~ {
state: State ~ context.state;
SELECT key FROM
priorityImportant => { state.np.priorityImportant ¬ val; Changed[state, [priority: TRUE]] };
noImage => state.np.noImage ¬ val;
strokeEnd => state.np.strokeEnd ¬ val;
strokeJoint => state.np.strokeJoint ¬ val;
correctPass => state.np.correctPass ¬ val;
raiseWarnings => state.np.warn ¬ val;
ENDCASE => ERROR Imager.Error[[$unimplemented, "Unknown IntKey"]];
};
Realify: PROC [dVec: ImagerDeviceVector.DVec] = {
IF dVec.scaled THEN {
dVec.fv.x ¬ ImagerScaled.Float[dVec.sv.s];
dVec.fv.y ¬ ImagerScaled.Float[dVec.sv.f];
dVec.scaled ¬ FALSE;
};
};
StateSetReal: PUBLIC PROC [context: Context, key: ImagerBackdoor.RealKey, val: REAL] ~ {
state: State ~ context.state;
SELECT key FROM
DCScpx => {Realify[state.cp]; state.cp.fv.x ¬ val};
DCScpy => {Realify[state.cp]; state.cp.fv.y ¬ val};
correctMX => {
v: VEC ¬ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.p.correctMeasure];
v.x ¬ val;
state.p.correctMeasure ¬ ImagerTransformation.TransformVec[state.viewToDevice, v];
};
correctMY => {
v: VEC ¬ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.p.correctMeasure];
v.y ¬ val;
state.p.correctMeasure ¬ ImagerTransformation.TransformVec[state.viewToDevice, v];
};
mediumXSize => state.np.mediumSize.x ¬ val;
mediumYSize => state.np.mediumSize.y ¬ val;
fieldXMin => state.np.fieldMin.x ¬ val;
fieldYMin => state.np.fieldMin.y ¬ val;
fieldXMax => state.np.fieldMax.x ¬ val;
fieldYMax => state.np.fieldMax.y ¬ val;
strokeWidth => state.np.strokeWidth ¬ val;
underlineStart => state.np.underlineStart ¬ val;
amplifySpace => state.np.amplifySpace ¬ val;
correctShrink => state.np.correctShrink ¬ val;
correctTX => {
v: VEC ¬ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.np.correctTolerance];
v.x ¬ val;
state.np.correctTolerance ¬ ImagerTransformation.TransformVec[state.viewToDevice, v];
};
correctTY => {
v: VEC ¬ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.np.correctTolerance];
v.y ¬ val;
state.np.correctTolerance ¬ ImagerTransformation.TransformVec[state.viewToDevice, v];
};
miterLimit => state.np.miterLimit ¬ val;
correctStretch => state.np.correctStretch ¬ val;
ENDCASE => ERROR Imager.Error[[$unimplemented, "Unknown RealKey"]];
};
StateSetT: PUBLIC PROC [context: Context, m: Transformation] ~ {
state: State ~ context.state;
T: Transformation ~ state.clientToDevice; -- reuse storage
T­ ¬ m­;
ImagerTransformation.ApplyPostConcat[T, state.viewToDevice];
Changed[state, [T: TRUE]];
};
StateSetClipper: PUBLIC PROC [context: Context, clipper: ImagerBackdoor.Clipper] ~ {
state: State ~ context.state;
state.clipper ¬ ClipperFromBackdoor[clipper];
Changed[state, [clipper: TRUE]];
};
StateSetFont: PUBLIC PROC [context: Context, font: Font] ~ {
state: State ~ context.state;
IF state.font # font THEN Changed[state, [font: TRUE]];
state.font ¬ font;
};
StateSetColor: PUBLIC PROC [context: Context, color: Color] ~ {
state: State ~ context.state;
Note; some of the context implementations do a SetColor to force a re-validation of the color, so we cannot check for state.color=color here.
state.color ¬ color;
Changed[state, [color: TRUE]];
};
StateGetInt: PUBLIC PROC [context: Context, key: ImagerBackdoor.IntKey] RETURNS[INT] ~ {
state: State ~ context.state;
SELECT key FROM
priorityImportant => RETURN[state.np.priorityImportant];
noImage => RETURN[state.np.noImage];
strokeEnd => RETURN[state.np.strokeEnd];
strokeJoint => RETURN[state.np.strokeJoint];
correctPass => RETURN[state.np.correctPass];
raiseWarnings => RETURN[state.np.warn];
ENDCASE => ERROR Imager.Error[[$unimplemented, "Unknown IntKey"]];
};
StateGetReal: PUBLIC PROC [context: Context, key: ImagerBackdoor.RealKey] RETURNS[REAL] ~ {
state: State ~ context.state;
SELECT key FROM
DCScpx => {Realify[state.cp]; RETURN[state.cp.fv.x]};
DCScpy => {Realify[state.cp]; RETURN[state.cp.fv.y]};
correctMX => {
v: VEC ~ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.p.correctMeasure];
RETURN[v.x]
};
correctMY => {
v: VEC ~ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.p.correctMeasure];
RETURN[v.y]
};
mediumXSize => RETURN[state.np.mediumSize.x];
mediumYSize => RETURN[state.np.mediumSize.y];
fieldXMin => RETURN[state.np.fieldMin.x];
fieldYMin => RETURN[state.np.fieldMin.y];
fieldXMax => RETURN[state.np.fieldMax.x];
fieldYMax => RETURN[state.np.fieldMax.y];
strokeWidth => RETURN[state.np.strokeWidth];
underlineStart => RETURN[state.np.underlineStart];
amplifySpace => RETURN[state.np.amplifySpace];
correctShrink => RETURN[state.np.correctShrink];
correctTX => {
v: VEC ~ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.np.correctTolerance];
RETURN[v.x]
};
correctTY => {
v: VEC ~ ImagerTransformation.InverseTransformVec[state.viewToDevice, state.np.correctTolerance];
RETURN[v.y]
};
miterLimit => RETURN[state.np.miterLimit];
correctStretch => RETURN[state.np.correctStretch];
ENDCASE => ERROR Imager.Error[[$unimplemented, "Unknown RealKey"]];
};
StateGetT: PUBLIC PROC [context: Context] RETURNS[Transformation] ~ {
state: State ~ context.state;
m: Transformation ~ ImagerTransformation.Invert[state.viewToDevice];
ImagerTransformation.ApplyPreConcat[m, state.clientToDevice];
RETURN[m];
};
BackdoorFromClipper: PROC [clipper: ImagerClipper.Clipper] RETURNS [ImagerBackdoor.Clipper] ~ { -- temporary until these types are changed to be the same.
RETURN [IF clipper = NIL THEN NIL ELSE CONS[[outline: clipper.first.outline, oddWrap: clipper.first.oddWrap, exclude: clipper.first.exclude], BackdoorFromClipper[clipper.rest]]]
};
ClipperFromBackdoor: PROC [clipper: ImagerBackdoor.Clipper] RETURNS [ImagerClipper.Clipper] ~ { -- temporary until these types are changed to be the same.
RETURN [IF clipper = NIL THEN NIL ELSE CONS[[outline: clipper.first.outline, oddWrap: clipper.first.oddWrap, exclude: clipper.first.exclude], ClipperFromBackdoor[clipper.rest]]]
};
StateGetClipper: PUBLIC PROC [context: Context] RETURNS[ImagerBackdoor.Clipper] ~ {
state: State ~ context.state;
RETURN[BackdoorFromClipper[state.clipper]];
};
StateGetFont: PUBLIC PROC [context: Context] RETURNS[Font] ~ {
state: State ~ context.state;
RETURN[state.font];
};
StateGetColor: PUBLIC PROC [context: Context] RETURNS[Color] ~ {
state: State ~ context.state;
RETURN[state.color];
};
StateGetCP: PUBLIC PROC [context: Context, rounded: BOOL] RETURNS[VEC] ~ {
state: State ~ context.state;
p: VEC;
Realify[state.cp];
p ¬ state.cp.fv;
IF rounded THEN p ¬ ImagerTransformation.DRound[p];
RETURN[ImagerTransformation.InverseTransform[state.clientToDevice, p]];
};
StateConcatT: PUBLIC PROC [context: Context, m: Transformation] ~ {
state: State ~ context.state;
ImagerTransformation.ApplyPreConcat[state.clientToDevice, m];
Changed[state, [T: TRUE]];
};
StateScale2T: PUBLIC PROC [context: Context, s: VEC] ~ {
state: State ~ context.state;
ImagerTransformation.ApplyPreScale2[state.clientToDevice, s];
Changed[state, [T: TRUE]];
};
StateRotateT: PUBLIC PROC [context: Context, a: REAL] ~ {
state: State ~ context.state;
ImagerTransformation.ApplyPreRotate[state.clientToDevice, a];
Changed[state, [T: TRUE]];
};
StateTranslateT: PUBLIC PROC [context: Context, t: VEC] ~ {
state: State ~ context.state;
ImagerTransformation.ApplyPreTranslate[state.clientToDevice, t];
Changed[state, [T: TRUE]];
};
StateMove: PUBLIC PROC [context: Context, rounded: BOOL ¬ TRUE] ~ {
state: State ~ context.state;
p: VEC;
Realify[state.cp];
p ¬ state.cp.fv;
IF rounded THEN p ¬ ImagerTransformation.DRound[p];
ImagerTransformation.ApplyTranslateTo[state.clientToDevice, p];
Changed[state, [T: TRUE]];
};
StateSetXY: PUBLIC PROC [context: Context, p: VEC] ~ {
state: State ~ context.state;
state.cp.fv ¬ ImagerTransformation.Transform[state.clientToDevice, p];
state.cp.scaled ¬ FALSE;
};
StateSetXYRel: PUBLIC PROC [context: Context, v: VEC] ~ {
state: State ~ context.state;
Realify[state.cp];
state.cp.fv ¬ state.cp.fv.InlineAdd[ImagerTransformation.TransformVec[state.clientToDevice, v]];
};
StateSetGray: PUBLIC PROC [context: Context, f: REAL] ~ {
state: State ~ context.state;
state.color ¬ Imager.MakeGray[f];
Changed[state, [color: TRUE]];
};
StateSetSampledColor: PUBLIC PROC [context: Context,
pa: PixelArray, m: Transformation, colorOperator: ColorOperator] ~ {
state: State ~ context.state;
um: Transformation ¬ StateGetT[context]; -- client to view
ImagerTransformation.ApplyPreConcat[um, m];-- color to view
state.color ¬ ImagerColor.MakeSampledColor[pa: pa, um: um, colorOperator: colorOperator];
Changed[state, [color: TRUE]];
};
StateSetSampledBlack: PUBLIC PROC [context: Context,
pa: PixelArray, m: Transformation, clear: BOOL] ~ {
state: State ~ context.state;
um: Transformation ¬ StateGetT[context]; -- client to view
ImagerTransformation.ApplyPreConcat[um, m];-- color to view
state.color ¬ ImagerColor.MakeSampledBlack[pa: pa, um: um, clear: clear];
Changed[state, [color: TRUE]];
};
StateStartUnderline: PUBLIC PROC [context: Context] ~ {
state: State ~ context.state;
Realify[state.cp];
state.np.underlineStart ¬ ImagerTransformation.InverseTransform[state.clientToDevice, state.cp.fv].x;
};
StateMaskUnderline: PUBLIC PROC [context: Context, dy, h: REAL] ~ {
state: State ~ context.state;
p2: VEC ~ StateGetCP[context: context, rounded: FALSE]; -- current position (client coords)
p1: VEC ~ [state.np.underlineStart, p2.y-dy-h]; -- starting corner (client coords)
underline: PROC ~ {
Imager.SetXY[context, p1];
Imager.Trans[context];
Imager.MaskRectangle[context, [0, 0, p2.x-p1.x, h]];
};
Imager.DoSaveAll[context, underline];
};
StateClip: PUBLIC PROC [context: Context, path: PathProc, oddWrap: BOOL, exclude: BOOL] ~ {
state: State ~ context.state;
T: Transformation ~ StateGetT[context];
outline: Outline ~ ImagerPath.OutlineFromPath[path: path, m: T];
ImagerTransformation.Destroy[T];
state.clipper ¬ CONS[[outline: outline, oddWrap: oddWrap, exclude: exclude], state.clipper];
Changed[state, [clipper: TRUE]];
};
StateClipRectangle: PUBLIC PROC [context: Context, r: Rectangle, exclude: BOOL] ~ {
path: PathProc ~ {
moveTo[[r.x, r.y]];
lineTo[[r.x+r.w, r.y]];
lineTo[[r.x+r.w, r.y+r.h]];
lineTo[[r.x, r.y+r.h]];
};
Imager.Clip[context: context, path: path, oddWrap: FALSE, exclude: exclude];
};
StateClipRectangleI: PUBLIC PROC [context: Context, x, y, w, h: INTEGER, exclude: BOOL] ~ {
path: PathProc ~ {
moveTo[[x, y]];
lineTo[[x+w, y]];
lineTo[[x+w, y+h]];
lineTo[[x, y+h]];
};
Imager.Clip[context: context, path: path, oddWrap: FALSE, exclude: exclude];
};
StateCorrectMask: PUBLIC PROC [context: Context] ~ {
state: State ~ context.state;
IF state.np.correctPass#0 THEN {
SELECT state.np.correctPass FROM
1 => state.p.correctMaskCount ¬ state.p.correctMaskCount+1;
2 => IF state.p.correctMaskCount#0 THEN {
Realify[state.cp];
state.cp.fv ¬ state.cp.fv.InlineAdd[state.p.correctMask];
state.p.correctMaskCount ¬ state.p.correctMaskCount-1;
};
ENDCASE;
};
};
StateCorrectSpace: PUBLIC PROC [context: Context, v: VEC] ~ {
state: State ~ context.state;
IF state.np.correctPass#0 THEN {
s: VEC ~ ImagerTransformation.TransformVec[state.clientToDevice, v];
SELECT state.np.correctPass FROM
1 => state.p.correctSum ¬ state.p.correctSum.InlineAdd[s];
2 => {
Realify[state.cp];
state.cp.fv ¬ state.cp.fv.InlineAdd[state.p.correctSpace.InlineMulC[s]];
};
ENDCASE;
};
};
StateSpace: PUBLIC PROC [context: Context, x: REAL] ~ {
state: State ~ context.state;
s: VEC ~ ImagerTransformation.TransformVec[state.clientToDevice, [x, 0]];
Realify[state.cp];
state.cp.fv ¬ state.cp.fv.InlineAdd[s];
SELECT state.np.correctPass FROM
0 => NULL;
1 => state.p.correctSum ¬ state.p.correctSum.InlineAdd[s];
2 => state.cp.fv ¬ state.cp.fv.InlineAdd[state.p.correctSpace.InlineMulC[s]];
ENDCASE;
};
StateSetCorrectMeasure: PUBLIC PROC [context: Context, v: VEC] ~ {
state: State ~ context.state;
state.p.correctMeasure ¬ ImagerTransformation.TransformVec[state.clientToDevice, v];
};
StateSetCorrectTolerance: PUBLIC PROC [context: Context, v: VEC] ~ {
state: State ~ context.state;
state.np.correctTolerance ¬ ImagerTransformation.TransformVec[state.clientToDevice, v];
};
largeReal: REAL ¬ RealFns.SqRt[Real.LargestNumber]; -- for overflow protection.
StateCorrect: PUBLIC PROC [context: Context, action: PROC] ~ {
state: State ~ context.state;
tolerance: VEC ~ state.np.correctTolerance;
start, end, measure, target, correction: VEC;
mask, space: VEC ¬ [0, 0];
state.p.correctMaskCount ¬ 0;
state.p.correctSum ¬ [0, 0];
state.np.noImage ¬ 1;
state.np.correctPass ¬ 1;
Realify[state.cp];
start ¬ state.cp.fv; -- starting position
Imager.DoSave[context, action]; -- pass 1
Realify[state.cp];
end ¬ state.cp.fv; -- ending position
measure ¬ state.p.correctMeasure; -- desired measure (note: may be set during pass 1)
target ¬ start.Add[measure]; -- target position
correction ¬ target.Sub[end]; -- amount of correction needed (end + correction = target)
SELECT TRUE FROM
(correction.Length <= tolerance.Length) => NULL; -- close enough
(end.Sub[start].Length < measure.Length) => { -- must expand
space ¬ correction;
space.Length cannot be zero, because that would have been close enough
IF state.np.correctStretch < largeReal AND correction.Length > state.np.correctStretch*state.p.correctSum.Length THEN {
mask ¬ correction.Sub[state.p.correctSum.Mul[state.np.correctStretch]];
space ¬ correction.Sub[mask];
};
};
ENDCASE => { -- must shrink
space ¬ correction;
IF correction.Length > (state.np.correctShrink*state.p.correctSum.Length) THEN {
mask ¬ correction.Add[state.p.correctSum.Mul[state.np.correctShrink]];
space ¬ correction.Sub[mask];
};
};
IF state.p.correctSum.x#0
THEN { space.x ¬ space.x/state.p.correctSum.x }
ELSE IF space.x#0 THEN { mask.x ¬ mask.x+space.x; space.x ¬ 0 };
IF state.p.correctSum.y#0
THEN { space.y ¬ space.y/state.p.correctSum.y }
ELSE IF space.y#0 THEN { mask.y ¬ mask.y+space.y; space.y ¬ 0 };
IF state.p.correctMaskCount#0 THEN {
IF mask.x=0 AND mask.y=0
THEN state.p.correctMaskCount ¬ 0
ELSE IF state.p.correctMaskCount>1 THEN {
state.p.correctMaskCount ¬ state.p.correctMaskCount-1;
mask ¬ mask.Div[state.p.correctMaskCount];
};
};
state.p.correctMask ¬ mask;
state.p.correctSpace ¬ space;
state.np.noImage ¬ 0;
state.np.correctPass ¬ 2;
{state.cp.fv ¬ start; state.cp.scaled ¬ FALSE};
Imager.DoSave[context, action]; -- pass 2
state.np.correctPass ¬ 0;
Realify[state.cp];
end ¬ state.cp.fv; -- ending position
state.cp.fv ¬ target;
IF state.np.warn#0 AND target.Sub[end].InlineSquare>tolerance.InlineSquare THEN
SIGNAL Imager.Warning[[$unableToProperlyAdjustMaskPositions, "CORRECT was unable to properly adjust mask positions to specified tolerance"]];
};
StateDontCorrect: PUBLIC PROC [context: Context, action: PROC, saveCP: BOOL] ~ {
state: State ~ context.state;
correctPass: INT ~ state.np.correctPass;
cp: VEC;
Restore: PROC ~ INLINE { state.np.correctPass ¬ correctPass; IF saveCP THEN {state.cp.scaled ¬ FALSE; state.cp.fv ¬ cp} };
Realify[state.cp];
cp ¬ state.cp.fv;
action[! UNWIND => Restore[]];
Restore[];
};
END.
Michael Plass, October 4, 1988:
Added StateSave, StateRestore, removed StateDoSave.