DIRECTORY BiAxialMenu, BiAxials, BiAxialsPrivate, BiScrollers, FS, Geom2D, Imager, ImagerBox, ImagerFont, ImagerInterpress, ImagerTransformation, IO, Menus, PopUpButtons, Real, RealFns, Rope, RopeExtras, SimpleFeedback, Vector2, VFonts, ViewerClasses, ViewerTools;

BiAxialsImpl: CEDAR PROGRAM
IMPORTS BiScrollers, FS, Geom2D, Imager, ImagerBox, ImagerFont, ImagerInterpress, ImagerTransformation, IO, Menus, PopUpButtons, Real, RealFns, Rope, RopeExtras, SimpleFeedback, Vector2, VFonts, ViewerTools
EXPORTS BiAxials, BiAxialMenu
= BEGIN OPEN BiAxials, BS:BiScrollers, PUB:PopUpButtons;

Viewer: TYPE ~ ViewerClasses.Viewer;
BiScrollerStyle: TYPE ~ BiScrollers.BiScrollerStyle;

ClassPrivate: TYPE ~ REF ClassPrivateRep;
ClassPrivateRep: PUBLIC TYPE ~ BiAxialsPrivate.ClassPrivateRep;

BiAxialSpecific: TYPE ~ REF BiAxialSpecificRec;
BiAxialSpecificRec: TYPE ~ RECORD [
class: Class,
labelPolicies: LabelPolicies,
clientData: REF ANY,
parms: ARRAY Axis OF AxisParms,
state: ARRAY Axis OF AxisState _ []
];
AxisParms: TYPE ~ RECORD [
labelPara, labelPerp: REAL, --label size, parallel and perp to axis, in viewer coords
axisSep: REAL--viewer sep. betw. axis ctr. and client clip region-- _ 1.5,
axisRad: REAL--viewer-- _ 0.5,
tickLen: REAL--viewer-- _ 5.0,
tickWid: REAL--viewer-- _ 2.0,
tlSep: REAL--viewer-- _ 2.0,
edgeDistv: REAL--viewer, labelPerp+tlSep+tickLen+axisRad+axisSep-- _ 0
];
AxisState: TYPE ~ RECORD [
bMin, bMax: REAL _ 0.0, --bounds available to client, in client coords
aMin, aMax: REAL _ 0.0, --intersection with client extent
edgeDistc: REAL _ 0.0, --client width of edge crud
clientVal: REAL _ 0.0, --client other coord of axis
viewerVal: REAL _ 0.0 --viewer other coord of axis, incl axisSep effect
];

CreateClass: PUBLIC PROC [bsStyle: BS.BiScrollerStyle, bcc: ClassCommon, classData: REF ANY _ NIL] RETURNS [Class] ~ {
cp: ClassPrivate;
c: Class;
IF bcc.vanilla=NIL THEN bcc.vanilla _ BS.GenID;
IF bcc.menu=NIL THEN bcc.menu _ baMenu;
cp _ NEW [ClassPrivateRep _ [bcc]];
cp.bsClass _ bsStyle.NewBiScrollerClass[[
flavor: bcc.flavor,
extrema: Extrema,
notify: bcc.notify,
bsUserAction: BSUserAction,
paint: Paint,
modify: bcc.modify,
destroy: bcc.destroy,
copy: bcc.copy,
set: bcc.set,
get: bcc.get,
init: bcc.init,
finish: bcc.finish,
save: bcc.save,
caption: bcc.caption,
adjust: bcc.adjust,
menu: bcc.menu,
tipTable: bcc.tipTable,
icon: bcc.icon,
cursor: bcc.cursor,
mayStretch: bcc.mayStretch,
offsetsMustBeIntegers: bcc.offsetsMustBeIntegers,
preferIntegerCoefficients: bcc.preferIntegerCoefficients,
vanilla: Vanilla,
preserve: bcc.preserve
]];
c _ NEW [ClassRep _ [cp, classData]];
RETURN [c]};

DecomposeClass: PUBLIC PROC [c: Class] RETURNS [bsClass: BiScrollerClass, bcc: ClassCommon, classData: REF ANY] ~ {
RETURN [c.private.bsClass, c.private.bcc, c.classData]};

Create: PUBLIC PROC [class: Class, labelPolicies: LabelPolicies, info: ViewerClasses.ViewerRec, paint: BOOL _ TRUE] RETURNS [ba: BiAxial] ~ {
bsClass: BiScrollerClass ~ class.private.bsClass;
lszx: VEC ~ labelPolicies[X].EstimateLabelSize[labelPolicies[X], X];
lszy: VEC ~ labelPolicies[Y].EstimateLabelSize[labelPolicies[Y], Y];
bas: BiAxialSpecific ~ NEW [BiAxialSpecificRec _ [class, labelPolicies, info.data, [X: [lszx.x, lszx.y], Y: [lszy.y, lszy.x]] ]];
FOR axis: Axis IN Axis DO
bas.parms[axis].edgeDistv _ bas.parms[axis].labelPerp + bas.parms[axis].tlSep + bas.parms[axis].tickLen + bas.parms[axis].axisRad + bas.parms[axis].axisSep;
ENDLOOP;
info.data _ bas;
ba _ bsClass.style.CreateBiScroller[bsClass, info, paint];
RETURN};

ClientDataOf: PUBLIC PROC [bs: BiAxial] RETURNS [REF ANY] ~ {
bas: BiAxialSpecific ~ NARROW[bs.ClientDataOf];
RETURN [bas.clientData]};

Extrema: PROC [clientData: REF ANY, direction: VEC] RETURNS [min, max: VEC] --BS.ExtremaProc-- ~ {
bas: BiAxialSpecific ~ NARROW[clientData];
[min, max] _ bas.class.private.bcc.extrema[bas.clientData, direction];
NULL--It's slightly bogus to use values (..edgeDistc) only calculated last time Paint was executed, but that's probably good enough--;
IF direction.x < 0 THEN max.x _ max.x - bas.state[Y].edgeDistc ELSE min.x _ min.x - bas.state[Y].edgeDistc;
IF direction.y < 0 THEN max.y _ max.y - bas.state[X].edgeDistc ELSE min.y _ min.y - bas.state[X].edgeDistc;
RETURN};

Vanilla: PROC [bs: BiScroller] RETURNS [t: BS.Transform] --BS.TransformGenerator-- ~ {
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[bs]];
cp: ClassPrivate ~ bas.class.private;
t _ cp.bcc.vanilla[bs];
t _ t.PostTranslate[[bas.parms[Y].edgeDistv, bas.parms[X].edgeDistv]];
RETURN};

BSUserAction: PROC [bs: BiScroller, input: LORA] ~ {
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[bs]];
orgInput: LORA _ input;
paint: BOOL _ TRUE;
age: BS.AgeOp _ remember;
SELECT input.first FROM
$First => {paint _ FALSE; input _ input.rest};
$Last => {age _ ignore; input _ input.rest};
$Mid => {paint _ FALSE; age _ ignore; input _ input.rest};
ENDCASE => NULL;
SELECT input.first FROM
$FitXY => Fit[bs, paint, FALSE];
$FitUniformly => Fit[bs, paint, TRUE];
$AlignFracs => {
v: Viewer ~ BS.QuaViewer[bs, TRUE];
clientFrac: VEC ~ BeVec[input.rest.first];
inrViewer: VEC ~ BeVec[input.rest.rest.first];
doX: BOOL ~ BeBool[input.rest.rest.rest.first];
doY: BOOL ~ BeBool[input.rest.rest.rest.rest.first];
viewer: BS.Location ~ [coord[
bas.parms[Y].edgeDistv + inrViewer.x*(v.cw-bas.parms[Y].edgeDistv),
bas.parms[X].edgeDistv + inrViewer.y*(v.ch-bas.parms[X].edgeDistv) ]];
cx, cy: REAL _ 0.0;
min, max: VEC;
IF doX THEN {
[min, max] _ bas.class.private.bcc.extrema[bas.clientData, [1.0, 0.0]];
cx _ Blend[clientFrac.x, min.x, max.x]};
IF doY THEN {
[min, max] _ bas.class.private.bcc.extrema[bas.clientData, [0.0, 1.0]];
cy _ Blend[clientFrac.y, min.y, max.y]};
BS.Align[bs, [coord[cx, cy]], viewer, doX, doY, paint, age]};
ENDCASE => BS.DoBSUserAction[bs, orgInput];
RETURN};

Fit: PROC [bs: BiScroller, paint, uniformly: BOOL] ~ {
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[bs]];
v: Viewer ~ BS.QuaViewer[bs, TRUE];
limits: Box;
[limits.xmin, limits.xmax] _ ViewLimitsOfImage[bs, X];
[limits.ymin, limits.ymax] _ ViewLimitsOfImage[bs, Y];
BS.BoxScale[bs, limits.RectangleFromBox, ImagerBox.RectangleFromBox[[bas.parms[Y].edgeDistv, bas.parms[X].edgeDistv, v.cw, v.ch]], paint, uniformly];
RETURN};

ViewLimitsOfImage: PROC [ba: BiAxial, axis: Axis] RETURNS [vmin, vmax: REAL] = {
bs: BiScroller ~ ba;
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[bs]];
t: BS.Transform _ bs.style.GetTransforms[bs].clientToViewer;
tn: Geom2D.Trans _ Geom2D.ToTrans[t];
norm, min, max: VEC;
SELECT axis FROM
X => norm _ [tn.dxdx, tn.dxdy];
Y => norm _ [tn.dydx, tn.dydy];
ENDCASE => ERROR;
[min, max] _ bas.class.private.bcc.extrema[bas.clientData, norm];
min _ t.Transform[min];
max _ t.Transform[max];
SELECT axis FROM
X => {vmin _ min.x; vmax _ max.x};
Y => {vmin _ min.y; vmax _ max.y};
ENDCASE => ERROR;
RETURN};

Paint: PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] --ViewerClasses.PaintProc--
~ {quit _ InnerPaint[self, context, screen, whatChanged, clear]};

InnerPaint: PROC [self: Viewer, context: Imager.Context, dest: ImageDestination, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] --ViewerClasses.PaintProc-- ~ {
bs: BiScroller ~ BS.QuaBiScroller[self];
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[bs]];
class: Class ~ bas.class;
clipBox: Box;
DrawAxes: PROC ~ {
t, u: BS.Transform;
clientVptMin, clientVptMax: VEC;
Prepare: PROC [axis, other: Axis, vw: INTEGER, scale, offset: REAL, Cons: PROC [REAL, REAL] RETURNS [VEC], Uncons: PROC [VEC] RETURNS [a, o: REAL]] ~ {
clientExtMin, clientExtMax: VEC;
[clientExtMin, clientExtMax] _ bas.class.private.bcc.extrema[bas.clientData, Cons[1.0, 0.0]];
bas.state[axis].bMax _ Uncons[clientVptMax].a;
bas.state[other].clientVal _ bas.state[axis].aMin _ bas.state[axis].bMin _ MAX[Uncons[clientExtMin].a, Uncons[clientVptMin].a];
bas.state[axis].aMax _ MIN[Uncons[clientExtMax].a, bas.state[axis].bMax];
RETURN};
PaintAxis: PROC [axis, other: Axis, vw: INTEGER, invScale, scale, offset: REAL, Cons: PROC [REAL, REAL] RETURNS [VEC], Uncons: PROC [VEC] RETURNS [a, o: REAL]] ~ {
axdelt: VEC ~ Cons[0.0, bas.parms[axis].axisSep+bas.parms[axis].axisRad];
maxTick: REAL _ bas.state[axis].aMax;
ConsumeTick: PROC [
coord: REAL--in client cordinates--,
labelBounds: Box--Viewer scale--,
DrawLabel: PROC [org: VEC] _ NIL
] ~ {
clientTick: VEC ~ Cons[coord, bas.state[axis].clientVal];
viewerTick: VEC--at edge of axis-- ~ t.Transform[clientTick].Sub[axdelt];
tickEnd: VEC ~ viewerTick.Sub[Cons[0.0, bas.parms[axis].tickLen]];
boxCtr: VEC ~ [(labelBounds.xmin + labelBounds.xmax)*0.5, (labelBounds.ymin + labelBounds.ymax)*0.5];
boxOff: VEC ~ Cons[Uncons[boxCtr].a, Uncons[[labelBounds.xmax, labelBounds.ymax]].o];
context.MaskVector[p1: viewerTick, p2: tickEnd];
maxTick _ MAX[maxTick, coord];
IF DrawLabel#NIL THEN DrawLabel[tickEnd.Sub[Cons[0.0, bas.parms[axis].tlSep]].Sub[boxOff]];
RETURN};
context.SetStrokeWidth[bas.parms[axis].tickWid];
bas.labelPolicies[axis].EnumerateTicks[bas.labelPolicies[axis], bs, axis, dest, bas.state[axis].bMin, bas.state[axis].bMax, bas.state[axis].aMin, bas.state[axis].aMax, bas.parms[axis].labelPara*invScale, context, ConsumeTick];
IF maxTick >= bas.state[other].clientVal THEN {
axendc: VEC ~ Cons[maxTick, bas.state[axis].clientVal];
axendv: VEC ~ t.Transform[axendc].Sub[Cons[0.0, bas.parms[axis].axisSep]];
context.SetStrokeWidth[bas.parms[axis].axisRad*2.0];
context.MaskVector[p1: [bas.state[Y].viewerVal, bas.state[X].viewerVal], p2: axendv]};
RETURN};
[t, u] _ bs.style.GetTransforms[bs];
[[bas.state[Y].edgeDistc, bas.state[X].edgeDistc]] _ u.TransformVec[[bas.parms[Y].edgeDistv, bas.parms[X].edgeDistv]];
clientVptMin _ u.Transform[[bas.parms[Y].edgeDistv, bas.parms[X].edgeDistv]];
clientVptMax _ u.Transform[[self.cw, self.ch]];
Prepare[X, Y, self.cw, t.a, t.c, ConsX, UnconsX];
Prepare[Y, X, self.ch, t.e, t.f, ConsY, UnconsY];
[[bas.state[Y].viewerVal, bas.state[X].viewerVal]] _ t
.Transform[[bas.state[Y].clientVal, bas.state[X].clientVal]]
.Sub[[bas.parms[Y].axisSep, bas.parms[X].axisSep]];
context.ConcatT[u];
PaintAxis[X, Y, self.cw, u.a, t.a, t.c, ConsX, UnconsX];
PaintAxis[Y, X, self.ch, u.e, t.e, t.f, ConsY, UnconsY];
RETURN};
IF clear THEN context.DoSave[DrawAxes];
clipBox _ [xmin: bas.state[X].bMin, xmax: bas.state[X].bMax, ymin: bas.state[Y].bMin, ymax: bas.state[Y].bMax];
context.ClipRectangle[ImagerBox.RectangleFromBox[clipBox]];
quit _ class.private.bcc.paint[self, context, clipBox, dest, whatChanged, clear];
RETURN};

ConsX: PROC [a, o: REAL] RETURNS [VEC] ~ {RETURN [[a, o]]};
ConsY: PROC [a, o: REAL] RETURNS [VEC] ~ {RETURN [[o, a]]};
UnconsX: PROC [v: VEC] RETURNS [a, o: REAL] ~ {RETURN [v.x, v.y]};
UnconsY: PROC [v: VEC] RETURNS [a, o: REAL] ~ {RETURN [v.y, v.x]};

CreateDrawingButton: PUBLIC PROC [viewerInfo: ViewerClasses.ViewerRec, ba: BiAxial, font: Font _ NIL] RETURNS [button: Viewer] = {
IF viewerInfo.name = NIL THEN viewerInfo.name _ "Draw";
button _ drawClass.Instantiate[viewerInfo, ba, PUB.ImageForRope[rope: viewerInfo.name, font: font]];
RETURN};

drawClass: PUB.Class _ PUB.MakeClass[[
proc: DrawCtl,
choices: LIST[
[$ToIP, "Create an interpress master of viewer contents"] ],
doc: "Drawing control operations"]];

DrawCtl: PROC [view, instanceData, classData, key: REF ANY] --PUB.PopUpButtonProc-- = {
ba: BiAxial ~ NARROW[instanceData];
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[ba]];
SELECT key FROM
$ToIP => {
fileName: ROPE ~ ViewerTools.GetSelectionContents[];
writtenName: ROPE;
width, height: REAL;
[writtenName, width, height] _ ToIP[ba, fileName, ".ip" !FS.Error => {
SimpleFeedback.Append[$BiAxials, oneLiner, $Error, Rope.Cat["File create error: ", error.explanation]];
GOTO Dun}];
SimpleFeedback.PutF[$BiAxials, oneLiner, $Error, "%g is %g by %g", [rope[FS.ExpandName[writtenName].fullFName]], [real[width]], [real[height]] ];
key _ key};
ENDCASE => ERROR;
RETURN
EXITS Dun => key _ key};

DrawButt: PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: ViewerClasses.MouseButton _ red, shift, control: BOOL _ FALSE] ~ {
ba: BiAxial ~ BS.QuaBiScroller[parent];
DrawCtl[parent, ba, NIL, $ToIP];
RETURN};

ToIP: PUBLIC PROC [ba: BiAxial, fileName, defaultExtension: ROPE _ NIL] RETURNS [writtenName: ROPE, width, height: REAL] ~ {
asBS: BiScroller ~ ba;
self: Viewer ~ BS.QuaViewer[asBS, FALSE];
bas: BiAxialSpecific ~ NARROW[BS.ClientDataOf[asBS]];
xfm: BS.Transform ~ asBS.style.GetTransforms[asBS].clientToViewer;
IF fileName.Length=0 THEN fileName _ self.name.Concat[defaultExtension]
ELSE IF fileName.Find["."]<0 THEN fileName _ fileName.Concat[defaultExtension];
{file: ImagerInterpress.Ref ~ ImagerInterpress.Create[fileName];
PaintPage: PROC [context: Imager.Context] ~ {
context.ConcatT[xfm];
[] _ InnerPaint[self, context, print, NIL, TRUE];
RETURN};
width _ self.cw/ppi;
height _ self.ch/ppi;
file.DoPage[PaintPage, Imager.metersPerInch/ppi];
file.Close[];
RETURN [fileName, width, height]}};
ppi: REAL _ 72.0;

baMenu: PUBLIC Menus.Menu _ Menus.CopyMenu[BS.bsMenu];

LinearSpecific: TYPE ~ REF LinearSpecificRec;
LinearSpecificRec: TYPE ~ RECORD [
axis: Axis,
format: ROPE,
labelChars: NAT,
font: ARRAY ImageDestination OF Font,
log: BOOL,
bt, bc, cc, zc: REAL,
lnbt, lnbc, lncc: REAL _ 1.0,
labelSizeEstv: VEC _ [0.0, 0.0],
labelWidthEstc: REAL _ 0.0, --for which below are valid
bMin, bMax: REAL _ 0.0, --visible area bounds, for which below are valid
aMin, aMax: REAL _ 0.0, --intersection with client bounds, for which below are valid
imin, imax: INT _ 0,
period: REAL _ 0.0,
invert: BOOL _ FALSE];

ln10: REAL ~ RealFns.Ln[10.0];

nMax: INTEGER _ 10;
decimalCoefs: RealSeq _ NEW [RealSequence[4]];
linearDefaultScreenFont, linearDefaultPrintFont: Font _ NIL;

CreateLinearLabelPolicy: PUBLIC PROC
[
axis: Axis,
format: ROPE, --produces at most labelChars chars
labelChars: NAT,
ctl: LinearCoordToLabel,
font: ARRAY ImageDestination OF Font _ ALL[NIL] --NIL means pick a standard default
]
RETURNS [lp: LabelPolicy]
~ {
ls: LinearSpecific ~ NEW [LinearSpecificRec _ [axis, format, labelChars, font, ctl.log, ctl.bt, ctl.bc, ctl.cc, ctl.zc]];
charEst: ROPE ~ RopeExtras.MakeConstantRope['7, labelChars];
screenEst, printEst: Imager.Rectangle;
IF ls.font[screen] = NIL THEN ls.font[screen] _ linearDefaultScreenFont;
IF ls.font[print] = NIL THEN ls.font[print] _ linearDefaultPrintFont;
IF ls.log THEN {
ls.lnbt _ RealFns.Ln[ctl.bt]; ls.lnbc _ RealFns.Ln[ctl.bc]; ls.lncc _ RealFns.Ln[ctl.cc]};
screenEst _ ls.font[screen].RopeBoundingBox[charEst] .BoxFromExtents .RectangleFromBox;
printEst _ ls.font[print].RopeBoundingBox[charEst] .BoxFromExtents .RectangleFromBox;
ls.labelSizeEstv _ [MAX[screenEst.w, printEst.w], MAX[screenEst.h, printEst.h]];
lp _ NEW [LabelPolicyRep _ [LinearEstimateLabelSize, LinearEnumerateTicks, NIL, ls]];
RETURN};

LinearEstimateLabelSize: PROC [lp: LabelPolicy, axis: Axis] RETURNS [VEC] ~ {
ls: LinearSpecific ~ NARROW[lp.data];
RETURN [ls.labelSizeEstv]};

LinearPrepare: PROC [ls: LinearSpecific, ba: BiAxial, bMin, bMax, aMin, aMax, labelSize: REAL--in client cordinates--] ~ {
minLabelSpacing: REAL ~ labelSize*1.1;
aWidth, bWidth, rough: REAL;
n: INTEGER;
ls.bMin _ bMin; ls.bMax _ bMax; ls.aMin _ aMin; ls.aMax _ aMax;
aWidth _ aMax - aMin;
bWidth _ bMax - bMin;
ls.labelWidthEstc _ labelSize;
IF aWidth = 0.0 THEN {
ls.imin _ ls.imax _ 1;
IF ls.log THEN { --n ln bt = ln cc + x ln bc
ls.period _ (ls.lncc + aMin * ls.lnbc) / ls.lnbt;
ls.invert _ FALSE;
}
ELSE ls.period _ aMin;
RETURN};
n _ Real.Floor[MAX[MIN[aWidth/minLabelSpacing, nMax], 1.0]];
rough _ aWidth/n;
IF ls.log THEN {
tp: REAL ~ ls.lnbt / ls.lnbc;
offset, fact: REAL;
smooth: REAL _ HarmonicCeiling[rough/tp]*tp;
DO
offset _ ls.lncc / (ls.lnbc * smooth);
fact _ 1.0 / smooth;
ls.imin _ MAX[Real.Ceiling[offset + fact*bMin], Real.Floor[offset + fact*aMin]];
ls.imax _ MIN[Real.Floor[offset + fact*bMax], Real.Ceiling[offset + fact*aMax]];
IF ls.imax < ls.imin
THEN smooth _ HarmonicFloor[smooth*0.999/tp]*tp
ELSE EXIT;
ENDLOOP;
ls.period _ smooth/tp;
IF ls.period < 1.0
THEN {ls.period _ Real.Round[1.0/ls.period]; ls.invert _ TRUE}
ELSE {ls.period _ Real.Round[ls.period]; ls.invert _ FALSE};
RETURN}
ELSE {
smooth: REAL _ PositionalCeiling[rough*ls.bc, 10.0, ln10, decimalCoefs]/ls.bc;
aFirst, aLast, bFirst, bLast: REAL;
PickOne: PROC ~ {
ls.imin _ ls.imax _ 1;
ls.period _ aMin;
RETURN};
DO
tmin, tmax: BOOL _ FALSE;
aFirst _ aMin/smooth;
aLast _ aMax/smooth;
bFirst _ bMin/smooth;
bLast _ bMax/smooth;
IF ABS[aFirst]>INT.LAST OR ABS[aLast]>INT.LAST OR ABS[bFirst]>INT.LAST OR ABS[bLast]>INT.LAST THEN --aWidth is tiny compared to min & max-- {PickOne[]; RETURN};
ls.imin _ MAX[Real.Floor[aFirst], Real.Ceiling[bFirst]];
ls.imax _ MIN[Real.Ceiling[aLast], Real.Floor[bLast]];
SELECT TRUE FROM
ls.imax < ls.imin => {rough _ rough*0.5;
smooth _ PositionalCeiling[rough*ls.bc, 10.0, ln10, decimalCoefs]/ls.bc;
ls.period _ smooth};
ls.imax = ls.imin => EXIT;
smooth*ls.imin = smooth*(REAL[ls.imin]+1.0) => {PickOne[]; RETURN};
smooth*ls.imax = smooth*(REAL[ls.imax]-1.0) => {PickOne[]; RETURN};
smooth < minLabelSpacing => {ls.imax _ ls.imin; EXIT};
ENDCASE => EXIT;
ENDLOOP;
ls.period _ smooth;
RETURN};
};

LinearEnumerateTicks: PROC [
labelPolicy: LabelPolicy,
ba: BiAxial,
axis: Axis,
imageDest: ImageDestination,
bMin, bMax: REAL, --bounds of visible area for ticks, client cordinates--
aMin, aMax: REAL, --intersection of above with extent of client data--
labelWidthEstc: REAL, --part of the above estimate, appropriately scaled
ctx: Imager.Context--Viewer coordinates--,
ConsumeTick: PROC [
coord: REAL--in client cordinates--,
labelBounds: Box--Viewer scale--,
DrawLabel: PROC [org: VEC] _ NIL
]]
~ {
ls: LinearSpecific ~ NARROW[labelPolicy.data];
font: Font ~ ls.font[imageDest];
IF aMax < aMin THEN RETURN;
IF aMin # ls.aMin OR aMax # ls.aMax OR bMin # ls.bMin OR bMax # ls.bMax OR labelWidthEstc # ls.labelWidthEstc THEN LinearPrepare[ls, ba, bMin, bMax, aMin, aMax, labelWidthEstc];
ctx.SetFont[font];
IF ls.log THEN {
invert: BOOL ~ ls.invert;
n: REAL ~ ls.period;
bt: REAL ~ ls.bt;
bc: REAL ~ ls.bc;
cc: REAL ~ ls.cc;
zc: REAL ~ ls.zc;
FOR i: INT IN [ls.imin .. ls.imax] DO
exp: REAL ~ IF invert THEN i/n ELSE i*n;
labX: REAL ~ RealFns.Power[bt, exp];
cX: REAL ~ RealFns.Log[bc, labX/cc];
label: ROPE ~ IO.PutFR[ls.format, [real[IF cX>zc THEN labX ELSE 0.0]] ];
ext: ImagerFont.Extents ~ font.RopeBoundingBox[label];
DrawLogLabel: PROC [org: VEC] ~ {
ctx.SetXY[org];
ctx.ShowRope[label];
RETURN};
ConsumeTick[cX, ImagerBox.BoxFromExtents[ext], DrawLogLabel];
ENDLOOP;
}
ELSE {
FOR i: INT IN [ls.imin .. ls.imax] DO
cx: REAL ~ i*ls.period;
lx: REAL ~ ls.cc + cx*ls.bc;
label: ROPE ~ IO.PutFR[ls.format, [real[lx]] ];
ext: ImagerFont.Extents ~ font.RopeBoundingBox[label];
DrawLinearLabel: PROC [org: VEC] ~ {
ctx.SetXY[org];
ctx.ShowRope[label];
RETURN};
ConsumeTick[cx, ImagerBox.BoxFromExtents[ext], DrawLinearLabel];
ENDLOOP;
};
RETURN};

PositionalCeiling: PUBLIC PROC [x, base, lnBase: REAL, coefs: RealSeq] RETURNS [REAL] ~ {
lnx: REAL ~ RealFns.Ln[x];
ilog: INTEGER _ Real.Round[lnx/lnBase];
basen: REAL _ RIExp[base, ilog];
seekc: REAL;
IF RealFns.AlmostEqual[x, basen, pcDist] THEN RETURN [basen];
IF basen > x THEN {ilog _ ilog - 1; basen _ basen/base};
seekc _ x/basen;
FOR i: NAT IN (0..coefs.length) DO --basen * coefs[i-1] <= x
c: REAL ~ coefs[i];
IF c > seekc OR RealFns.AlmostEqual[c, seekc, pcDist] THEN RETURN [basen * c];
ENDLOOP;
RETURN [basen*base]};
pcDist: INTEGER _ -5;

HarmonicCeiling: PUBLIC PROC [x: REAL] RETURNS [REAL] ~ {
IF x >= INT.LAST THEN RETURN [x];
IF x >= 1.0 THEN {n: INT ~ Real.Ceiling[x]; RETURN [n]};
{y: REAL ~ 1.0 / x;
IF y = 1.0 THEN RETURN [y];
RETURN [1.0 / HarmonicFloor[y] ]}};

HarmonicFloor: PUBLIC PROC [x: REAL] RETURNS [REAL] ~ {
IF x >= INT.LAST THEN RETURN [x];
IF x >= 1.0 THEN {n: INT ~ Real.Floor[x]; RETURN [n]};
{y: REAL ~ 1.0 / x;
IF y = 1.0 THEN RETURN [y];
RETURN [1.0 / HarmonicCeiling[y] ]}};

RIExp: PROC [base: REAL, exp: INTEGER] RETURNS [ans: REAL] ~ {
inv: BOOL ~ exp < 0;
pexp: NAT _ ABS[exp];
ans _ 1.0;
WHILE pexp#0 DO
IF (pexp MOD 2) # 0 THEN ans _ ans*base;
IF (pexp _ pexp/2) # 0 THEN base _ base*base;
ENDLOOP;
IF inv THEN ans _ 1.0/ans;
RETURN};

BeVec: PROC [ra: REF ANY] RETURNS [VEC]
~ {RETURN [NARROW[ra, REF VEC]^]};

BeBool: PROC [ra: REF ANY] RETURNS [BOOL] ~ {RETURN [SELECT ra FROM
$FALSE => FALSE,
$TRUE => TRUE,
ENDCASE => ERROR]};

Blend: PROC [a: REAL, b0, b1: REAL] RETURNS [c: REAL]
= {c _ (1-a)*b0 + a*b1};

Start: PROC ~ {
decimalCoefs[0] _ 1.0;
decimalCoefs[1] _ 2.0;
decimalCoefs[2] _ 3.0;
decimalCoefs[3] _ 5.0;
linearDefaultPrintFont _ ImagerFont.Find["xerox/xc1-2-2/classic", substituteWithWarning !
Imager.Warning => {SimpleFeedback.PutF[$BiAxialsImpl, oneLiner, $Warning, "Imager.Warning[%g, %g] trying to Find the default print font for linear label policies.", [atom[error.code]], [rope[error.explanation]] ]; RESUME};
Imager.Error => {SimpleFeedback.PutF[$BiAxialsImpl, oneLiner, $Error, "Imager.Error[%g, %g] trying to Find the default print font for linear label policies; VFonts.defaultFont will be used instead.", [atom[error.code]], [rope[error.explanation]] ]; linearDefaultPrintFont _ VFonts.defaultFont; CONTINUE}
].Scale[10];
linearDefaultScreenFont _ VFonts.defaultFont;
Menus.ReplaceMenuEntry[baMenu, Menus.FindEntry[baMenu, "Rotate"], NIL];
Menus.ReplaceMenuEntry[baMenu, Menus.FindEntry[baMenu, "Draw"], Menus.CreateEntry["Draw", DrawButt]];
RETURN};

Start[];

END.
��`��
BiAxialsImpl.mesa
Copyright Ó 1991 by Xerox Corporation.  All rights reserved.
Spreitze, October 23, 1991 10:48 am PDT
When logarithmic:
period = n, and invert=(p<0) (see comment on LinearAxisSpec).
bt ^ (mi * np) = cc * bc ^ x   <=>   mi * np ln bt = ln cc + x ln bc
<=> mi = (ln cc + x ln bc) / (np ln bt)
between ticks,   np ln bt = ln bc Dx
Ê)��•NewlineDelimiter
™�code™K™<K™'—K˜�KšÏk	œ6œQœt˜ˆK˜�šÏnœœ˜KšœœQœd˜ÎKšœ˜—Kš	œœœœœ˜8K˜�Kšœœ˜$Kšœœ˜4K˜�Kšœœœ˜)Kšœœœ#˜?K˜�Kšœœœ˜/šœœœ˜#K˜
Kšœ˜Kšœœœ˜Kšœœœ˜Kšœœœ˜#Kšœ˜—šœœœ˜KšœœÏc9˜UKšœ	Ÿ6œ˜JKšœ	Ÿ
œ˜Kšœ	Ÿ
œ˜Kšœ	Ÿ
œ˜KšœŸ
œ˜KšœÏdœŸ3œ˜FK˜—šœœœ˜KšœœŸ.˜FKšœœŸ!˜9Kšœ œœŸ˜2KšœœŸ˜3KšœœŸ1˜GK˜—K˜�šžœœœœ/œœœœ˜vKšœ˜Kšœ	˜	Kšœ
œœœ˜/Kšœ
œœ˜'Kšœœ˜#šœ)˜)Kšœ˜K˜Kšœ˜Kšœ˜Kšœ
˜
Kšœ˜Kšœ˜Kšœ˜Kšœ
˜
Kšœ
˜
Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜K˜Kšœ˜Kšœ1˜1Kšœ9˜9Kšœ˜K˜K˜—Kšœœ˜%Kšœ˜—K˜�šžœœœœ9œœ˜sKšœ2˜8—K˜�šžœœœTœœœ˜Kšœ1˜1Kšœ œœ;˜DKšœ œœ;˜DKšœœA œ œ œ œ˜šœœ˜Kšœ œƒ˜œKšœ˜—K˜Kšœ:˜:Kšœ˜—K˜�šžœœœœœœ˜=Kšœœ˜/Kšœ˜—K˜�šžœœœœ
œœœŸœ˜bKšœœ
˜*K˜FKšŸ/ÐcdŸQœ˜†Kš
œœ& œœ& œ˜kKš
œœ& œœ& œ˜kKšœ˜—K˜�š
žœœœœŸœ˜VKšœœœ˜3K˜%K˜Kšœ* œ œ˜FKšœ˜—K˜�šžœœœ˜4Kšœœœ˜3Kšœ
œ	˜Kšœœœ˜Kšœœ˜šœ
˜Kšœœ˜.K˜,Kšœœ$˜:Kšœœ˜—šœ
˜Kšœœ˜ Kšœ œ˜&˜Kšœœœ˜#Kšœœ˜*Kšœœ ˜.Kšœœ&˜/Kšœœ+˜4šœœ˜Kšœ œ* œ˜CKšœ œ* œ˜F—Kšœœ˜Kšœ
œ˜šœœ˜
KšœG˜GKšœ(˜(—šœœ˜
KšœG˜GKšœ(˜(—Kšœ;˜=—Kšœœ˜+—Kšœ˜—K˜�šžœœ$œ˜6Kšœœœ˜3Kšœœœ˜#K˜Kšœ6˜6Kšœ6˜6KšœX œ œ"˜•Kšœ˜—K˜�šžœœœœ˜PKšœ˜Kšœœœ˜3Kšœœ7˜<K˜%Kšœœ˜šœ˜Kšœ˜Kšœ˜Kšœœ˜—KšœA˜AKšœ˜Kšœ˜šœ˜Kšœ"˜"Kšœ"˜"Kšœœ˜—Kšœ˜—K˜�šžœœ6œœ	œœœœŸ˜KšœA˜A—K˜�šž
œœNœœ	œœœœŸœ˜°Kšœœ˜(Kšœœœ˜3K˜K˜
šžœœ˜Kšœœ˜Kšœœ˜ šžœœœœžœœœœœœžœœœœœ˜—Kšœœ˜ K˜]Kšœ.˜.KšœKœ1˜Kšœœ/˜IKšœ˜—šž	œœœœžœœœœœœžœœœœœ˜£Kšœœ>˜IKšœ	œ˜%šžœœ˜KšœŸœ˜$KšœŸœ˜!Kšž	œœœ˜ Kšœ˜Kšœœ*˜9KšœŸœ'˜IKšœ	œ6˜BKšœœZ˜eKšœœJ˜UKšœ0˜0Kšœ
œ˜KšœœœF˜[Kšœ˜—Kšœ0˜0Kšœâ˜âšœ'œ˜/Kšœ œœ,˜7Kšœ œœ œ*˜JK˜4KšœR œ˜V—Kšœ˜—Kšœ$˜$Kš	œ œ œ* œ œ˜vKšœ1 œ œ˜MK˜/K˜1K˜1šœ6˜6Kšœ<˜<Kšœ3˜3—K˜K˜8K˜8Kšœ˜—Kšœœ˜'Kšœo˜oKšœ;˜;KšœQ˜QKšœ˜—K˜�Kšžœœœœœœ˜;Kšžœœœœœœ˜;Kšžœœœœœœ
˜BKšžœœœœœœ
˜BK˜�š
žœœœAœœ˜‚Kšœœœ˜7Kšœ/œ2˜dKšœ˜—K˜�šœœ	œ˜&K˜šœ	œ˜Kšœ<˜<—K˜$—K˜�š
žœœ&œœŸœ˜WKšœœ˜#Kšœœœ˜3šœ˜˜
Kšœ
œ&˜4Kšœ
œ˜Kšœœ˜šœ9œ˜FKšœg˜gKšœ˜—KšœIœF˜‘K˜—Kšœœ˜—Kš˜Kšœ˜—K˜�šžœœœœœ@œœ˜ŠKšœœ˜'Kšœœ	˜ Kšœ˜—K˜�šžœœœ+œœœœœ˜|Kšœ˜Kšœœœ˜)Kšœœœ˜5Kšœœ;˜BKšœœ.˜GKšœœœ.˜OK˜@šž	œœ˜-K˜Kšœ&œœ˜1Kšœ˜—Kšœ˜Kšœ˜Kšœ1˜1K˜
Kšœ˜#Kšœœ˜—K˜�Kšœœœ	˜6K˜�Kšœœœ˜-šœœœ˜"K˜Kšœœ˜
Kšœœ˜Kšœœœ˜%Kšœœ˜
Kšœ œ œ œ œœ˜Kš	œ œ œ œœ˜Kšœ œœ˜ Kšœ
 œœŸ˜7KšœœŸ0˜HKšœœŸ<˜TKšœœ˜Kšœœ˜Kšœœœ˜šœ™Kšœ=™=šœ œ œÏuœ œ œ œ¢œ œ œ	 ™DKšœ œ œ	 œ¢œ œ™'—Kš	œ¢œ œ œÏgœ™$——K˜�Kšœœ˜K˜�Kšœœ˜Kšœœ˜.Kšœ8œ˜<K˜�šžœœ˜$šœ˜K˜KšœœŸ#˜1Kšœœ˜Kšœ˜Kš
œœœœœŸ#˜SKšœ˜—Kšœ˜K˜KšœœE œ œ œ œ˜yKšœ	œ/˜<K˜&Kšœœœ+˜HKšœœœ)˜Ešœœ˜Kš
œ œ œ	 œ œ	 œ œ˜Z—KšœW˜WKšœU˜UKšœ œœœ˜PKšœœCœ˜UKšœ˜—K˜�šžœœœœ˜MKšœœ
˜%Kšœ œ˜—K˜�šž
œœFŸœ˜zKšœœ˜&Kšœœ˜Kšœœ˜K˜?Kšœ˜Kšœ˜Kšœ œ
˜šœœ˜Kšœ˜š
œœŸ¡Ÿ¡Ÿ	¡˜,Kšœ œ œ
 œ˜1Kšœœ˜K˜—Kšœ˜Kšœ˜—Kšœœœ&˜<Kšœ˜šœœ˜Kšœœ	 œ	 œ˜Kšœœ˜Kšœœ ˜,š˜Kšœ œ
 œ˜&Kšœ˜Kšœ
œC˜PKšœ
œC˜Pšœ˜Kšœ+˜/Kšœœ˜
—Kšœ˜—Kšœ˜šœ˜Kšœ5œ˜>Kšœ1œ˜<—Kšœ˜—šœ˜Kšœœ œ  œ˜NKšœœ˜#šžœœ˜K˜K˜Kšœ˜—š˜Kšœœœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kš&œœ	œœœœœœœœ	œœœœœœœŸ(œ
œ˜ Kšœ
œ+˜8Kšœ
œ)˜6šœœ˜˜(Kšœ% œ  œ˜HKšœ˜—Kšœœ˜Kšœœœ˜CKšœœœ˜CKšœ0œ˜6Kšœœ˜—Kšœ˜—K˜Kšœ˜—Kšœ˜—K˜�šžœœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜KšœœŸ7˜IKšœœŸ4˜FKšœ
 œœŸ2˜HKšœŸœ˜*šžœœ˜KšœŸœ˜$KšœŸœ˜!Kšž	œœœ˜ Kšœ˜—K˜Kšœœ˜.K˜ Kšœ
œœ˜Kšœœœœœ œ œœ< œ˜±K˜šœœ˜Kšœœ
˜Kšœœ
˜Kšœ œœ œ˜Kšœ œœ œ˜Kšœ œœ œ˜Kšœ œœ œ˜šœœœ˜%Kš	œœœœœ˜(Kšœœ œ˜$Kšœœ œ œ˜$Kš
œœœœ œœœ	˜HKšœ6˜6šžœœœ˜!K˜K˜Kšœ˜—Kšœ=˜=Kšœ˜—K˜—šœ˜šœœœ˜%Kšœœ˜Kšœœ œ
 œ˜Kšœœœ˜/Kšœ6˜6šžœœœ˜$K˜K˜Kšœ˜—Kšœ@˜@Kšœ˜—K˜—Kšœ˜—K˜�šžœœœœœœ˜YKšœœ˜Kšœœ˜'Kšœ¢œœ˜ Kšœœ˜Kš
œ¢œ
œœ¢œ˜=Kš
œ¢œœ¢œ¢œ˜8Kšœ¢œ˜šœœœœŸÐcuŸ˜<Kšœœ˜Kš
œœ'œœ¢œ˜NKšœ˜—Kšœ¢œ˜Kšœœ˜—K˜�šžœœœœœœ˜9Kš
œœœœœ˜!Kšœ
œœœ˜8Kšœœ˜Kšœ	œœ˜Kšœ˜#—K˜�šž
œœœœœœ˜7Kš
œœœœœ˜!Kšœ
œœœ˜6Kšœœ˜Kšœ	œœ˜Kšœ˜%—K˜�šžœœœœœœ˜>Kšœœ˜Kšœœœ˜K˜
šœ˜Kšœœœ˜(Kšœœ˜-Kšœ˜—Kšœœ˜Kšœ˜—K˜�šžœœœœœœ˜'Kš	œœœœœ˜"—K˜�šžœœœœœœœœ˜CKšœ
œ˜Kšœ	œ˜Kšœœ˜—K˜�šžœœ£œœ
œœœ˜5Kšœ
£œ£œ˜—K˜�šžœœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜šœY˜YKšœÖœ˜ÞKšœ¦œ˜¯Kšœ˜—K˜-KšœBœ˜GKšœe˜eKšœ˜—K˜�K˜K˜�Kšœ˜—�…—����Sø��n��