File: PlotViewer.mesa, Copyright (C) 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, August 1, 1985 7:23:18 pm PDT
DIRECTORY
BasicTime USING [GMT, nullGMT],
Convert USING [Error, RealFromRope],
FS USING [Error],
Icons USING [IconFlavor, NewIconFromFile],
Imager USING [Box, ClipRectangle, ConstantColor, Context, DoSave, MaskBox, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, Scale2T, ScaleT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC],
ImagerBackdoor USING [GetBounds, GetCP],
ImagerColor USING [GrayFromColor, RGB],
ImagerColorMap USING [LoadEntries, MapEntry, SetStandardColorMap],
ImagerDitheredDevice USING [ColorFromSpecialRGB],
ImagerFont USING [Extents, Find, Font, FontBoundingBox, RopeBoundingBox, Scale],
ImagerInterpress USING [Close, Create, DoPage, Ref],
ImagerPress USING [Close, SimpleCreate],
IO USING [int, PutFR, real, rope, time],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
MessageWindow USING [Append, Blink],
Plot USING [Curves, PlotSpec, PlotSpecRec, ReadPlotFile, RopeSequence, SavePlot, Vector],
PlotOps USING [BackgroundType, ColorType, Handle, HandleData, MaxStep, LineState, LineStateRec, LineStep, Lock, StateSequence, Unlock],
PopUpMenu USING [RequestSelection],
Real USING [FixI, RoundC, RoundI, RoundLI],
RealFns USING [Log, Power, SqRt],
Rope USING [Cat, Equal, Find, IsEmpty, ROPE, Substr],
Terminal USING [ColorCursorPresentation, Current, GetColorCursorPresentation, SetColorCursorPresentation, Virtual],
UserProfile USING [CallWhenProfileChanges, ListOfTokens, ProfileChangedProc, Token],
VFonts USING [EstablishFont],
ViewerClasses USING [Column, PaintProc, PaintRectangle, Viewer, ViewerClass, ViewerClassRec],
ViewerOps USING [CreateViewer, EnumerateViewers, EnumProc, OpenIcon, PaintViewer, RegisterViewerClass, SwapIconAndViewer],
ViewerTools USING [GetSelectionContents],
WindowManager USING [colorDisplayOn, RestoreCursor];
PlotViewer: CEDAR PROGRAM
IMPORTS BasicTime, Convert, FS, Imager, ImagerBackdoor, ImagerColor, ImagerColorMap, ImagerDitheredDevice, ImagerFont, ImagerInterpress, ImagerPress, Icons, IO, Menus, MessageWindow, Plot, PlotOps, PopUpMenu, Real, RealFns, Rope, Terminal, UserProfile, VFonts, ViewerOps, ViewerTools, WindowManager
EXPORTS Plot = {
OPEN Plot, PlotOps;
types
OutputType: TYPE = {screen, press, interpress};
FontType: TYPE = {title, normal};
VectorPair: TYPE = RECORD[start, end: Vector];
NColors: TYPE = [0..MaxNumerOfColors);
constants
TextHeightFudge: REAL = 4.0;
TextWidthFudge: REAL = 4.0;
minLineWidth: REAL = 1.0;
MaxNumerOfColors: INTEGER = 16;
coordRatio: REAL = 0.25/600.0; -- 0.25 m ~ 600 pixels
(constant) variables
plotIcon: Icons.IconFlavor ← Icons.NewIconFromFile["Plot.icons", 0];
plotViewerClass: ViewerClasses.ViewerClass ← NEW [ViewerClasses.ViewerClassRec ←
[paint: Display, icon: plotIcon, cursor: textPointer]];
t12b: ImagerFont.Font = VFonts.EstablishFont["TimesRoman", 12, TRUE]; -- bold
t12bp: ImagerFont.Font = ImagerFont.Scale[
ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"], 13.0];
h8: ImagerFont.Font = VFonts.EstablishFont["Helvetica", 8];
h8p: ImagerFont.Font = ImagerFont.Scale[
ImagerFont.Find["Xerox/PressFonts/Helvetica-mrr"], 9.0];
textFont: ARRAY FontType OF ARRAY OutputType OF ImagerFont.Font = [[t12b, t12bp, t12bp], [h8, h8p, h8p]];
fontHeight: ARRAY FontType OF ARRAY OutputType OF REAL;
virtual: Terminal.Virtual = Terminal.Current[];
parameters initialized or updated by ProcessUserProfile
initialColumn: ViewerClasses.Column; -- {static, left, right, color}
createOption: {closeOtherViewers, swapLastViewer, iconic, badCreateOption};
backgroundType: BackgroundType; -- {white, gray, darkGray, black, unknown}
color: ARRAY NColors OF Imager.ConstantColor;
public procedures
CreateViewer: PUBLIC PROC [spec: PlotSpec ← NIL, iconic, inhibitDestroy: BOOLFALSE] RETURNS [viewer: ViewerClasses.Viewer ← NIL] = {
handle
handle: Handle ← NEW[HandleData ← [
plotSpec: spec,
background: backgroundType
] ];
viewer ← ViewerOps.CreateViewer[
flavor: $Plot,
info: [
name: spec.file,
menu: MakeMenu[],
icon: plotIcon,
column: initialColumn,
iconic: TRUE,
inhibitDestroy: inhibitDestroy,
data: handle],
paint: FALSE];
IF iconic OR (createOption = iconic) THEN ViewerOps.PaintViewer[viewer, all, TRUE, NIL]
ELSE {
SELECT createOption FROM
closeOtherViewers => ViewerOps.OpenIcon[
icon: viewer,
closeOthers: TRUE,
bottom: TRUE,
paint: TRUE];
swapLastViewer => {
vLast: ViewerClasses.Viewer ← NIL;
LastViewer: ViewerOps.EnumProc = {
IF v.column = initialColumn AND NOT v.iconic THEN {vLast ← v; RETURN[FALSE]};
}; -- LastViewer
ViewerOps.EnumerateViewers[LastViewer];
IF vLast = NIL THEN ViewerOps.OpenIcon[
icon: viewer,
closeOthers: TRUE,
bottom: TRUE,
paint: TRUE]
ELSE {
ViewerOps.SwapIconAndViewer[
icon: viewer,
openViewer: vLast,
paint: TRUE]; -- this paints the new viewer only.
ViewerOps.PaintViewer[vLast, all, TRUE, NIL]; -- paint the last viewer.
};
};
ENDCASE;
};
}; -- CreateViewer
AddVector: PUBLIC PROC [viewer: ViewerClasses.Viewer ← NIL, vector: Vector ← NIL] = {
IF vector = NIL OR NOT IsPlotViewer[viewer] THEN RETURN ELSE {
shouldPaint: BOOLFALSE;
expectedSize: CARDINAL;
handle: Handle ← NARROW[viewer.data];
IF handle # NIL THEN {
Lock[handle];
expectedSize ← handle.plotSpec.nCurvesMax + 1;
IF vector.size # expectedSize THEN MessageWindow.Append[
IO.PutFR["Only %g elements in vector when %g expected.",
IO.int[vector.size], IO.int[expectedSize]], TRUE]
ELSE {
IF handle.curves = NIL THEN {
handle.lineStates ← NEW[StateSequence[handle.plotSpec.nCurvesMax]];
Note: lineStates[n] is the state of curve # n+1.
FOR i: CARDINAL IN[0..handle.plotSpec.nCurvesMax) DO
handle.lineStates[i] ← NEW[LineStateRec ← []];
ENDLOOP;
}
ELSE IF handle.curves.first # NIL THEN shouldPaint ← TRUE;
handle.curves ← CONS[vector, handle.curves]
};
Unlock[handle];
};
IF shouldPaint THEN ViewerOps.PaintViewer[viewer, client, FALSE, handle.curves];
};
}; -- AddVector
CreateSpec: PUBLIC PROC[file, title: Rope.ROPENIL,
time: BasicTime.GMT ← BasicTime.nullGMT,
bounds: Imager.Box ← [0, 0, 0, 0], -- [xmin, xmax, ymin, ymax]
nCurvesMax: CARDINAL ← 0,
legendEntries: REF RopeSequence ← NIL
] RETURNS[spec: PlotSpec ← NIL] = {
spec ← NEW[PlotSpecRec ← [file, title, time, bounds, nCurvesMax, legendEntries]];
}; -- CreateSpec
SetSpec: PUBLIC PROC [viewer: ViewerClasses.Viewer ← NIL, newSpec: PlotSpec ← NIL] = {
Note: if original nCurvesMax # new one, then old data will be cleared.
IF IsPlotViewer[viewer] THEN {
handle: Handle ← NARROW[viewer.data];
IF handle = NIL THEN RETURN;
Lock[handle];
handle.curves ← NIL;
handle.plotSpec ← newSpec;
viewer.name ← handle.plotSpec.file;
Unlock[handle];
ViewerOps.PaintViewer[viewer, caption, FALSE, NIL];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; -- update the display.
};
}; -- SetSpec
Clear: PUBLIC PROC [viewer: ViewerClasses.Viewer] = {
IF IsPlotViewer[viewer] THEN {
handle: Handle ← NARROW[viewer.data];
Lock[handle];
handle.plotSpec^ ← [];
handle.curves ← NIL;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- Clear
Zoom: PUBLIC PROC [viewer: ViewerClasses.Viewer, newBounds: Imager.Box] = {
IF IsPlotViewer[viewer] THEN {
handle: Handle ← NARROW[viewer.data];
Lock[handle];
handle.plotSpec.bounds ← newBounds;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- Zoom
IsPlotViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [BOOL] = {
IF viewer = NIL THEN RETURN[FALSE];
RETURN[viewer.class.flavor = $Plot];
}; -- IsPlotViewer
menu commands
SetColor: Menus.MenuProc = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
IF handle # NIL THEN {
newColorType: ColorType = SELECT PopUpMenu.RequestSelection[
label: "Options",
choice: LIST["my colors", "black and white", "Cedar default colors"],
default: 1] FROM
1 => mine, 2 => bw, 3 => cedar, ENDCASE => handle.colorType;
IF handle.colorType = newColorType THEN RETURN;
Lock[handle];
handle.colorType ← newColorType;
SELECT newColorType FROM
mine => SetMyColors[];
cedar => SetCedarColors[];
ENDCASE;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- SetColor
CursorColor: Menus.MenuProc = {
SELECT PopUpMenu.RequestSelection[
label: "Options",
choice: LIST["black", "white"],
default: 1] FROM
1 => [] ← Terminal.SetColorCursorPresentation[virtual, onesAreBlack];
2 => [] ← Terminal.SetColorCursorPresentation[virtual, onesAreWhite];
ENDCASE;
}; -- CursorColor
BackgroundColor: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
IF handle # NIL THEN {
newBkGndType: BackgroundType = SELECT PopUpMenu.RequestSelection[
label: "Options",
choice: LIST["white", "gray", "dark gray", "black"],
default: 1] FROM
1 => white,
2 => gray,
3 => darkGray,
4 => black,
ENDCASE => handle.background;
IF handle.background = newBkGndType THEN RETURN;
Lock[handle];
handle.background ← newBkGndType;
SetCursorForBackgroundType[handle.background];
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- BackgroundColor
Get: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
msg: Rope.ROPE
IF name.IsEmpty[] THEN "Please select a plot file name."
ELSE IF handle = NIL THEN "Not a good plot viewer."
ELSE ReadPlotFile[name: name, viewer: viewer, iconic: FALSE];
IF msg # NIL THEN {
MessageWindow.Blink[];
MessageWindow.Append[msg, TRUE];
};
}; -- Get
Store: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
msg: Rope.ROPE ← SavePlot[viewer, name]; -- viewer type checked there
IF msg # NIL THEN {
MessageWindow.Blink[];
MessageWindow.Append[message: msg, clearFirst: TRUE];
};
}; -- Store
AppendExtension: PROC[old, ext: Rope.ROPENIL] RETURNS [Rope.ROPE]= {
excl: INT ← old.Find["!"];
RETURN[Rope.Cat[
SELECT excl FROM
< 0 => old,
> 0 => old.Substr[0, excl],
ENDCASE => "Plot", -- unlikely, though.
".",
ext]];
}; -- AppendExtension
GetPrintingColor: PROC[] RETURNS [type: ColorType, ok: BOOLTRUE] = {
colorChoice: NAT = PopUpMenu.RequestSelection[
label: "Options",
choice: LIST["color", "black and white"],
default: 1];
SELECT colorChoice FROM
1 => type ← mine;
2 => type ← bw;
ENDCASE => {
MessageWindow.Blink[];
MessageWindow.Append[
"You must choose either color or black-and-white.", TRUE];
ok ← FALSE;
};
}; -- GetPrintingColor
Print: PROC[viewer: ViewerClasses.Viewer, output: OutputType ← interpress] = {
handle: Handle ← NARROW[viewer.data];
colorToPrint: ColorType;
okToPrint: BOOL;
[colorToPrint, okToPrint] ← GetPrintingColor[];
IF okToPrint THEN {
context: Imager.Context ← NIL;
interpress: ImagerInterpress.Ref ← NIL;
fileName: Rope.ROPE;
colorSave: ColorType;
Lock[handle]; {
colorSave ← handle.colorType;
handle.colorType ← colorToPrint;
SELECT output FROM
interpress => {
InterpressDrawMe: PROC[context: Imager.Context] ~ {
context.RotateT[90];
context.ScaleT[coordRatio];
DrawMe[context, handle, NIL, interpress];
};
fileName ← AppendExtension[handle.plotSpec.file, "ip"];
interpress ← ImagerInterpress.Create[fileName
! FS.Error => {
MessageWindow.Append[error.explanation, TRUE];
CONTINUE;
}
];
ImagerInterpress.DoPage[interpress, InterpressDrawMe];
MessageWindow.Append[Rope.Cat[fileName, " is written."]];
};
press => {
fileName ← AppendExtension[handle.plotSpec.file, "press"];
context ← ImagerPress.SimpleCreate[fileName
! FS.Error => {
MessageWindow.Append[error.explanation, TRUE];
CONTINUE;
}
];
context.RotateT[90];
context.ScaleT[coordRatio];
DrawMe[context, handle, NIL, press];
MessageWindow.Append[Rope.Cat[fileName, " is written."]];
};
ENDCASE; -- (error)
IF output = interpress AND interpress # NIL THEN ImagerInterpress.Close[interpress]
ELSE IF output = press AND context # NIL THEN ImagerPress.Close[context];
handle.colorType ← colorSave;
}; Unlock[handle];
};
}; -- Print
Press: Menus.MenuProc = {Print[NARROW[parent], press]}; -- Press
Interpress: Menus.MenuProc = {Print[NARROW[parent], interpress]}; -- Interpress
SetCedarColors: PROC [] = {
IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN {
ImagerColorMap.SetStandardColorMap[virtual];
WindowManager.RestoreCursor[];
IF Terminal.GetColorCursorPresentation[virtual] # onesAreBlack THEN
[] ← Terminal.SetColorCursorPresentation[virtual, onesAreBlack];
};
}; -- SetCedarColors
PaintAllPlotViewers: PROC [] = {
PaintEachPlotViewer: ViewerOps.EnumProc = {
IF v.column = color AND NOT v.iconic AND v.class.flavor = $Plot THEN
ViewerOps.PaintViewer[v, all, TRUE, NIL];
};
ViewerOps.EnumerateViewers[PaintEachPlotViewer];
}; -- PaintAllPlotViewers
SetMyColors: PROC [] = {
defaultRGB: ARRAY NColors OF ImagerColor.RGB ← [
white
[R: 1.0, G: 1.0, B: 1.0],
forgeround colors, all vivid
[R: 1.0, G: 0.0, B: 0.8], -- purple red
[R: 0.4, G: 0.0, B: 1.0], -- purple
[R: 0.0, G: 0.0, B: 1.0], -- blue
[R: 0.0, G: 0.56, B: 1.0], -- greenish blue
[R: 0.0, G: 1.0, B: 1.0], -- green blue
[R: 0.0, G: 1.0, B: 0.0], -- green
[R: 0.52, G: 1.0, B: 0.0], -- yellow green
[R: 1.0, G: 1.0, B: 0.0], -- yellow
[R: 1.0, G: 0.60, B: 0.0], -- orange yellow
[R: 1.0, G: 0.42, B: 0.0], -- yellowish orange
[R: 1.0, G: 0.25, B: 0.0], -- orange
[R: 1.0, G: 0.0, B: 0.0], -- red
gray
[R: 0.5, G: 0.5, B: 0.5], -- gray
[R: 0.17, G: 0.17, B: 0.17], -- dark gray
black
[R: 0.0, G: 0.0, B: 0.0] -- black
];
GetColor: PROC [num: NColors] RETURNS [ImagerColor.RGB] = {
get color spec from user profile, or return default
ColorSpec: TYPE = LIST OF Rope.ROPE;
list: ColorSpec ← UserProfile.ListOfTokens[
key: IO.PutFR["Plot.Color%g", IO.int[num]],
default: NIL
];
c: ARRAY[1..3] OF REAL ← [defaultRGB[num].R, defaultRGB[num].G, defaultRGB[num].B];
i: CARDINAL ← 1;
FOR lr: ColorSpec ← list, list.rest UNTIL list = NIL OR i > 3 DO
c[i] ← Convert.RealFromRope[list.first ! Convert.Error => EXIT];
c[i] ← MAX[0, MIN[1.0, c[i]]];
i ← i + 1;
ENDLOOP;
RETURN[[R: c[1], G: c[2], B: c[3]]];
}; -- GetColor
FOR i: NColors IN NColors DO
c: ImagerColor.RGB ← GetColor[i];
entry: ImagerColorMap.MapEntry ← [
mapIndex: i+100,
red: Real.RoundC[c.R*255],
green: Real.RoundC[c.G*255],
blue: Real.RoundC[c.B*255]
];
ImagerColorMap.LoadEntries[
vt: virtual,
mapEntries: LIST[entry],
shared: FALSE];
color[i] ← ImagerDitheredDevice.ColorFromSpecialRGB[
specialPixel: [i+100, null], rgb: c];
ENDLOOP;
};
};
}; -- SetMyColors
other private procedures
Display: ViewerClasses.PaintProc = {
[self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL]
IF IsPlotViewer[self] AND NOT self.iconic THEN {
there will be no checking of the viewer class in DrawMe.
handle: Handle ← NARROW[self.data];
IF handle # NIL THEN {
Lock[handle];
DrawMe[context, handle, whatChanged, screen];
Unlock[handle];
};
};
}; -- Display
DrawMe: PROC [context: Imager.Context, handle: Handle, whatChanged: REF ANY, output: OutputType] = {
press context has been rotated
BoundsValid: PROC [box: Imager.Box] RETURNS [BOOL] = INLINE {
RETURN[box.xmax > box.xmin AND box.ymax > box.ymin];
}; -- BoundsValid
rect: Imager.Rectangle ← IF output = interpress THEN [0.0, -518.16, 670.56, 518.16]
ELSE ImagerBackdoor.GetBounds[context];
box: Imager.Box ← [rect.x, rect.y, rect.x + rect.w, rect.y + rect.h];
foreground: Imager.ConstantColor ← IF output # screen THEN color[15] ELSE
IF handle.background = black OR handle.background = darkGray THEN color[0]
ELSE color[15];
IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN {
background: Imager.ConstantColor ← IF output # screen THEN color[0] ELSE
SELECT handle.background FROM
gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0];
width: REAL ← rect.w;
height: REAL ← rect.h;
marginRatio: REAL = 0.05;
innerRatio: REAL = 0.9;
marginX: REAL ← width*marginRatio;
marginY: REAL ← height*marginRatio;
maxX: REAL ← box.xmax - marginX;
maxY: REAL ← box.ymax - marginY;
minX: REAL ← box.xmin + marginX;
minY: REAL ← box.ymin + marginY;
innerWidth: REAL ← width * innerRatio;
innerHeight: REAL ← height * innerRatio;
tenthHeight: REAL ← innerHeight/10.0;
textLineHeight: REAL ← fontHeight[normal][output] + TextHeightFudge;
windowTooLow: BOOL ← tenthHeight < textLineHeight;
legendTop: REALIF windowTooLow THEN minY ELSE minY + tenthHeight*1.8;
axesTop: REALIF windowTooLow THEN maxY ELSE maxY - tenthHeight;
titleBox: Imager.Box ← [minX, axesTop, maxX, maxY];
axesBox: Imager.Box ← [minX, legendTop, maxX, axesTop];
legendBox: Imager.Box ← [minX, minY, maxX, legendTop];
IF output = screen THEN { -- clear the screen
context.SetColor[background];
context.MaskBox[box];
};
title / legend / footnote
context.SetColor[foreground];
IF NOT windowTooLow THEN {
excl: INT ← handle.plotSpec.file.Find["!"];
pattern: Rope.ROPE = IF excl < 0 THEN handle.plotSpec.file ELSE handle.plotSpec.file.Substr[0, excl];
footNote: Rope.ROPEIO.PutFR["File: %g; time: %g.", IO.rope[pattern], IO.time[handle.plotSpec.time]];
DrawTitle[context, titleBox, handle.plotSpec.title, output];
DrawLegendFootnote[context, legendBox, handle.plotSpec.legendEntries,
footNote, output, handle.colorType];
};
axes and curves
IF BoundsValid[handle.plotSpec.bounds] THEN {
c: Curves ← NIL;
[handle.curvesBox, handle.realBounds] ← DrawAxes[context, axesBox, handle.plotSpec.bounds, output];
context.ClipRectangle[
[handle.curvesBox.xmin,
handle.curvesBox.ymin,
handle.curvesBox.xmax - handle.curvesBox.xmin,
handle.curvesBox.ymax - handle.curvesBox.ymin]];
FOR l: Curves ← handle.curves, l.rest UNTIL l = NIL DO
c ← CONS[l.first, c];
ENDLOOP;
DrawCurves[context, handle, c, output];
};
}
ELSE IF ISTYPE[whatChanged, Curves] THEN { OPEN handle;
cv: Curves ← NARROW[whatChanged];
IF cv # NIL AND BoundsValid[handle.realBounds] THEN {
m0, m1: Vector;
m1 ← cv.first;
IF m1 = NIL OR cv.rest = NIL THEN RETURN;
m0 ← cv.rest.first;
IF m0 = NIL THEN RETURN;
IF m0.size = m1.size THEN {
tMin: REAL = realBounds.xmin;
tFactor: REAL = (curvesBox.xmax - curvesBox.xmin + 1)/(realBounds.xmax - tMin);
vMin: REAL = realBounds.ymin;
vFactor: REAL = (curvesBox.ymax - curvesBox.ymin + 1)/(realBounds.ymax - vMin);
t0: REAL = (m0[0] - tMin)*tFactor;
t1: REAL = (m1[0] - tMin)*tFactor;
context.ClipRectangle[
[handle.curvesBox.xmin,
handle.curvesBox.ymin,
handle.curvesBox.xmax - handle.curvesBox.xmin,
handle.curvesBox.ymax - handle.curvesBox.ymin]];
context.TranslateT[[curvesBox.xmin, curvesBox.ymin]];
context.SetStrokeEnd[round];
context.SetStrokeWidth[minLineWidth];
IF handle.colorType = bw THEN {
context.SetColor[foreground];
FOR index: CARDINAL IN [1..m0.size) DO
ipath: CARDINAL ← index - 1;
DrawLineSeg[context,
[t0, (m0[index]-vMin)*vFactor], [t1, (m1[index]-vMin)*vFactor],
ipath MOD 12, handle.lineStates[ipath]];
ENDLOOP;
}
ELSE FOR index: CARDINAL IN [1..m0.size) DO
ipath: CARDINAL ← index - 1;
context.SetColor[color[(ipath MOD 12) + 1]];
context.MaskVector[
[t0, (m0[index]-vMin)*vFactor], [t1, (m1[index]-vMin)*vFactor]];
ENDLOOP;
};
};
};
}; -- DrawMe
DrawTitle: PROC [context: Imager.Context, box: Imager.Box, title: Rope.ROPE, output: OutputType] = {
xPos: REAL ← box.xmin + (box.xmax - box.xmin) / 2.0;
boxH: REAL ← box.ymax - box.ymin - fontHeight[normal][output];
IF title.IsEmpty[] THEN RETURN;
IF boxH >= fontHeight[title][output] THEN
[] ← MyDrawText[context, title, xPos, box.ymax, center, top, title, output]
ELSE IF boxH >= fontHeight[normal][output] THEN
[] ← MyDrawText[context, title, xPos, box.ymax, center, top, normal, output];
}; -- DrawTitle
DrawAxes: PROC [context: Imager.Context, box, bounds: Imager.Box, output: OutputType]
RETURNS [innerBox, realBounds: Imager.Box] = {
Border: PROC = {
context.SetStrokeEnd[square];
context.SetStrokeJoint[round];
context.SetStrokeWidth[minLineWidth*2.0];
IF output = press THEN {
-- Press doesn't support MaskStroke.
context.MaskVector[[innerBox.xmin, innerBox.ymin], [innerBox.xmin, innerBox.ymax]];
context.MaskVector[[innerBox.xmin, innerBox.ymax], [innerBox.xmax, innerBox.ymax]];
context.MaskVector[[innerBox.xmax, innerBox.ymax], [innerBox.xmax, innerBox.ymin]];
context.MaskVector[[innerBox.xmax, innerBox.ymin], [innerBox.xmin, innerBox.ymin]];
}
ELSE {
borderPath: Imager.PathProc = {
moveTo[[innerBox.xmin, innerBox.ymin]];
lineTo[[innerBox.xmin, innerBox.ymax]];
lineTo[[innerBox.xmax, innerBox.ymax]];
lineTo[[innerBox.xmax, innerBox.ymin]];
};
context.MaskStroke[path: borderPath, closed: TRUE];
};
}; -- Border
HorizontalAxisLabels: PROC[context: Imager.Context, box: Imager.Box,
min, max, scale, step: REAL] = {
external vars referenced: output.
tickLen: REAL ← (box.ymax - box.ymin)/50.0;
tickCount: CARDINAL = Real.RoundI[(max - min)/step];
textTop: REAL ← box.ymin - TextHeightFudge;
tick: REAL;
FOR i: CARDINAL IN [0..tickCount] DO
tick ← step*scale*i;
[] ← MyDrawText[context,
IO.PutFR["%g", IO.real[step*i + min]],
box.xmin + tick, textTop,
center, top, normal, output];
IF i # 0 AND i # tickCount THEN {
xPos: REAL = box.xmin + tick;
yStart: REAL ← box.ymin;
yEnd: REAL ← box.ymin + tickLen;
Tick: Imager.PathProc = {moveTo[[xPos, yStart]]; lineTo[[xPos, yEnd]]};
proc: PROC = {context.MaskStroke[path: Tick, closed: FALSE]};
IF output = press THEN context.MaskVector[[xPos, yStart], [xPos, yEnd]]
ELSE context.DoSave[proc];
yStart ← box.ymax; yEnd ← box.ymax - tickLen;
IF output = press THEN context.MaskVector[[xPos, yStart], [xPos, yEnd]]
ELSE context.DoSave[proc];
};
ENDLOOP;
}; -- HorizontalAxisLabels
VerticalAxisLabels: PROC[context: Imager.Context, box: Imager.Box,
min, max, scale, step: REAL] = {
external vars referenced: output.
tickLen: REAL ← (box.ymax - box.ymin)/50.0;
tickCount: CARDINAL = Real.RoundI[(max - min)/step];
textRight: REAL ← box.xmin - TextWidthFudge;
tick: REAL;
FOR i: CARDINAL IN [0..tickCount] DO
tick ← step*scale*i;
[] ← MyDrawText[context,
IO.PutFR["%g", IO.real[step*i + min]],
textRight, box.ymin + tick,
right, center, normal, output];
IF i # 0 AND i # tickCount THEN {
yPos: REAL = box.ymin + tick;
xStart: REAL ← box.xmin;
xEnd: REAL ← box.xmin + tickLen;
Tick: Imager.PathProc = {moveTo[[xStart, yPos]]; lineTo[[xEnd, yPos]]};
proc: PROC = {context.MaskStroke[path: Tick, closed: FALSE]};
IF output = press THEN context.MaskVector[[xStart, yPos], [xEnd, yPos]]
ELSE context.DoSave[proc];
xStart ← box.xmax; xEnd ← box.xmax - tickLen;
IF output = press THEN context.MaskVector[[xStart, yPos], [xEnd, yPos]]
ELSE context.DoSave[proc];
};
ENDLOOP;
}; -- VerticalAxisLabels
xSize: REAL = box.xmax-box.xmin;
ySize: REAL = box.ymax-box.ymin;
xStep, yStep: REAL; -- between ticks
xScale, yScale: REAL;
nLabelsX, nLabelsY: CARDINAL;
hLabelWidth, hLabelHeight, vLabelWidth, vLabelHeight, tLabelWidth, tLabelHeight: REAL;
inBoxW, inBoxH: REAL;
[tLabelWidth, tLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmin]],
normal, output];
[hLabelWidth, hLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmax]],
normal, output];
hLabelWidth ← MAX[tLabelWidth, hLabelWidth];
hLabelHeight ← MAX[tLabelHeight, hLabelHeight];
[tLabelWidth, tLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymin]],
normal, output];
[vLabelWidth, vLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymax]],
normal, output];
vLabelWidth ← MAX[tLabelWidth, vLabelWidth];
vLabelHeight ← MAX[tLabelHeight, vLabelHeight];
innerBox ← [
xmin: box.xmin + vLabelWidth + TextWidthFudge,
ymin: box.ymin + hLabelHeight + TextHeightFudge*2,
xmax: box.xmax,
ymax: box.ymax];
IF innerBox.xmin > innerBox.xmax THEN innerBox.xmin ← box.xmin;
IF innerBox.ymin > innerBox.ymax THEN innerBox.ymin ← box.ymin;
inBoxW ← innerBox.xmax - innerBox.xmin;
inBoxH ← innerBox.ymax - innerBox.ymin;
context.DoSave[Border];
nLabelsX ← MAX[1, MIN[10, Real.RoundI[inBoxW/hLabelWidth*5/8]]];
nLabelsY ← MAX[1, MIN[10, Real.RoundI[inBoxH/vLabelHeight*5/8]]];
[realBounds.xmin, realBounds.xmax, xStep, xScale] ← ScaleAxis[
bounds.xmin, bounds.xmax, inBoxW, nLabelsX];
[realBounds.ymin, realBounds.ymax, yStep, yScale] ← ScaleAxis[
bounds.ymin, bounds.ymax, inBoxH, nLabelsY];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[minLineWidth];
HorizontalAxisLabels[context, innerBox, realBounds.xmin, realBounds.xmax, xScale, xStep];
VerticalAxisLabels[context, innerBox, realBounds.ymin, realBounds.ymax, yScale, yStep];
}; -- DrawAxes
GetRopeSize: PROC [text: Rope.ROPE, fontType: FontType ← normal, output: OutputType ← screen] RETURNS [dx, dy: REAL] = {
font: ImagerFont.Font← textFont[fontType][output];
extents: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, text];
dx ← extents.rightExtent - extents.leftExtent;
dy ← extents.descent + extents.ascent;
}; -- GetRopeSize
ScaleAxis: PROC [minDataValue, maxDataValue, innerBoxSize: REAL, nLabels: CARDINAL]
RETURNS [min, max, step, scale: REAL] = {
step ← FindStepSize[maxDataValue - minDataValue, nLabels];
min ← AlignEnd[minDataValue, step, FALSE];
max ← AlignEnd[maxDataValue, step, TRUE];
IF Almost[min, max] THEN {max ← max + 50.0; min ← min - 50.0};
scale ← innerBoxSize/(max - min);
}; -- ScaleAxis
FindStepSize: PROC [range: REAL, nSteps: CARDINAL] RETURNS[step: REAL] = {
logRange: REAL;
mantissa, minStep: REAL;
characteristic: INTEGER;
steps: ARRAY [0..6) OF REAL = [0.2, 0.5, 1.0, 2.0, 5.0, 10.0];
IF Almost[range, 0.0] THEN range ← 100.0;
logRange ← RealFns.Log[10.0, range];
characteristic ← Real.FixI[logRange];
mantissa ← logRange - characteristic;
IF logRange < 0.0 THEN {
characteristic ← characteristic - 1;
mantissa ← mantissa + 1.0};
minStep ← RealFns.Power[10.0, mantissa]/nSteps;
FOR i: CARDINAL IN [0..5) DO
step ← steps[i];
IF step > minStep OR Almost[step, minStep] THEN EXIT
ENDLOOP;
IF characteristic >= 0 THEN THROUGH [1..characteristic] DO step ← step*10.0 ENDLOOP
ELSE THROUGH [1..-characteristic] DO step ← step/10.0 ENDLOOP;
step ← MAX[1.0, step];
}; -- FindStepSize
AlignEnd: PROC [e, step: REAL, roundUp: BOOL] RETURNS [ae: REAL] = {
absE: REAL = ABS[e];
nSteps: INTEGER;
xend: REAL;
IF e = 0.0 THEN RETURN[0.0];
IF e < 0.0 THEN roundUp ← ~roundUp;
nSteps ← Real.RoundLI[absE/step - 0.5];
xend ← step*nSteps;
IF Almost[nSteps, absE/step] THEN ae ← xend
ELSE IF roundUp
THEN ae ← IF xend >= absE THEN xend ELSE step*(nSteps + 1)
ELSE ae ← IF xend <= absE THEN xend ELSE step*(nSteps - 1);
IF e < 0.0 THEN ae ← -ae;
}; -- AlignEnd
Almost: PROC [p, q: REAL] RETURNS [a: BOOLTRUE] = {
max: REALMAX[ABS[p], ABS[q]];
IF max # 0.0 THEN a ← (ABS[p - q]/max) < 0.00001;
}; -- Almost
MyDrawText: PROC[context: Imager.Context,
text: Rope.ROPE, x0, y0: REAL,
xJustification: {left, center, right} ← left,
yJustification: {top, center, bottom} ← bottom,
fontType: FontType ← normal,
output: OutputType ← screen,
xScale, yScale: REAL ← 1.0]
RETURNS [Imager.VEC] = {
proc: PROC = {
context.TranslateT[[x0, y0]];
context.SetXY[[xmin, ymin]];
context.SetFont[font];
context.Scale2T[[xScale, yScale]];
context.ShowRope[text];
}; -- proc
extents: ImagerFont.Extents;
xmin, ymin, width, height: REAL;
font: ImagerFont.Font = textFont[fontType][output];
IF text.IsEmpty[] THEN RETURN[[x0, y0]];
extents ← ImagerFont.RopeBoundingBox[font: font, rope: text];
extents ← [extents.leftExtent*xScale, extents.rightExtent*xScale,
extents.descent*yScale, extents.ascent*yScale];
width ← extents.rightExtent - extents.leftExtent;
heightextents.descent + extents.ascent;
xmin← SELECT xJustification FROM
right => - width - extents.leftExtent,
center => - width/2.0 - extents.leftExtent,
ENDCASE => - extents.leftExtent;
ymin← SELECT yJustification FROM
top => - height + extents.descent,
center => - height/2.0 + extents.descent,
ENDCASE => extents.descent;
context.DoSave[proc];
RETURN[IF output = interpress THEN [x0 + width, y0 + height]
ELSE ImagerBackdoor.GetCP[context]];
}; -- MyDrawText
DrawCurves: PROC [context: Imager.Context, handle: Handle,
curves: Curves, output: OutputType] = {
box: Imager.Box ← handle.curvesBox;
bounds: Imager.Box ← handle.realBounds;
colorType: ColorType ← handle.colorType;
IF curves # NIL THEN {
nCurvesMax: CARDINAL = curves.first.size - 1;
tFactor: REAL ← (box.xmax - box.xmin)/(bounds.xmax - bounds.xmin);
vFactor: REAL ← (box.ymax - box.ymin)/(bounds.ymax - bounds.ymin);
drawThem: PROC = {
context.TranslateT[[box.xmin, box.ymin]];
context.SetStrokeWidth[minLineWidth];
context.SetStrokeJoint[mitered];
IF colorType = bw THEN {
context.SetStrokeEnd[butt];
FOR i: CARDINAL IN [1..nCurvesMax] DO
firstPoint: BOOLTRUE;
lastVec, newVec: Imager.VEC;
index: CARDINAL ← i - 1;
handle.lineStates[index]^ ← []; -- cf. AddVector.
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
newVec ← [
(graph.first[0] - bounds.xmin)*tFactor,
(graph.first[i] - bounds.ymin)*vFactor];
IF firstPoint THEN firstPoint ← FALSE
ELSE DrawLineSeg[context, lastVec, newVec,
index MOD 12, handle.lineStates[index]];
lastVec ← newVec;
ENDLOOP;
ENDLOOP;
}
ELSE {
context.SetStrokeEnd[round];
FOR i: CARDINAL IN [1..nCurvesMax] DO
context.SetColor[color[((i-1) MOD 12)+1]];
IF output = press THEN {
Press doesn't support MaskStroke.
normal: BOOLFALSE;
lastVec: Imager.VEC;
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
t: REAL ← (graph.first[0] - bounds.xmin)*tFactor;
v: REAL ← (graph.first[i] - bounds.ymin)*vFactor;
IF normal THEN context.MaskVector[lastVec, [t, v]] ELSE normal ← TRUE;
lastVec ← [t, v];
ENDLOOP;
}
ELSE {
oneCurve: Imager.PathProc = {
start: BOOLTRUE;
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
t: REAL ← (graph.first[0] - bounds.xmin)*tFactor;
v: REAL ← (graph.first[i] - bounds.ymin)*vFactor;
IF start THEN moveTo[[t, v]] ELSE lineTo[[t, v]];
start ← FALSE;
ENDLOOP;
}; -- oneCurve
context.MaskStroke[path: oneCurve, closed: FALSE];
};
ENDLOOP;
};
}; -- drawThem
IF nCurvesMax > 0 THEN context.DoSave[drawThem];
};
}; -- DrawCurves
LineShape: TYPE = {solid, dot, dash, longDash};
StepSize: ARRAY LineShape[dot..longDash] OF REAL = [3.0, 6.0, 9.0];
LineLimit: ARRAY LineShape[dot..longDash] OF REAL = [1.0, 4.0, 7.0]; -- beyond which is space.
LineStyle: TYPE = NColors[0..12);
shape: ARRAY LineStyle OF ARRAY LineStep OF LineShape = [
[solid, solid, solid, solid, solid, solid], -- solid, useless
[dot, dot, dot, dot, dot, dot], -- dot
[dash, dash, dash, dash, dash, dash], -- dash
[longDash, longDash, longDash, longDash, longDash, longDash], -- longDash
[dot, dash, dot, dash, dot, dash], -- dot-dash
[dot, longDash, dot, longDash, dot, longDash], -- dot-longDash
[dot, dot, dash, dot, dot, dash], -- dot-dot-dash
[dot, dot, longDash, dot, dot, longDash], -- dot-dot-longDash
[dot, dash, dash, dot, dash, dash], -- dot-dash-dash
[dot, longDash, longDash, dot, longDash, longDash], -- dot-longDash-longDash
[dash, longDash, dash, longDash, dash, longDash], -- dash-longDash
[dot, dash, longDash, dot, dash, longDash] -- dot-dash-longDash
];
DrawLineSeg: PROC[context: Imager.Context,
v0, v1: Imager.VEC ← [0.0, 0.0],
style: LineStyle ← 0,
state: LineState ← NIL] = {
IF Almost[v0.x, v1.x] AND Almost[v0.y, v1.y] THEN RETURN;
IF state = NIL THEN state ← NEW[LineStateRec ← []];
IF style = 0 THEN context.MaskVector[v0, v1]
ELSE { -- Note that normally state.step and state.progress will be updated.
dx: REAL = v1.x - v0.x;
dy: REAL = v1.y - v0.y;
lengthToGo: REAL ← RealFns.SqRt[dx*dx + dy*dy];
cosine: REAL ← dx/lengthToGo;
sine: REAL ← dy/lengthToGo;
newVec, oldVec: Imager.VEC ← v0;
stepSize, lineLimit, increment: REAL;
drawIt: BOOL;
UNTIL Almost[lengthToGo, 0.0] DO
stepSize← StepSize[shape[style][state.step]];
lineLimit← LineLimit[shape[style][state.step]];
IF state.progress < lineLimit THEN {
drawIt ← TRUE;
increment ← MIN[lengthToGo, lineLimit - state.progress];
}
ELSE {
drawIt ← FALSE;
increment ← MIN[lengthToGo, stepSize - state.progress];
};
lengthToGo ← lengthToGo - increment;
state.progress ← state.progress + increment;
newVec ← [oldVec.x + cosine*increment, oldVec.y + sine*increment];
IF drawIt THEN context.MaskVector[oldVec, newVec]
ELSE IF Almost[state.progress, stepSize] THEN {
state.step ← (state.step + 1) MOD MaxStep;
state.progress ← 0.0;
};
oldVec ← newVec;
ENDLOOP;
};
}; -- DrawLineSeg
DrawLegendFootnote: PROC [context: Imager.Context, box: Imager.Box,
legendEntries: REF RopeSequence, footNote: Rope.ROPE,
output: OutputType, colorType: ColorType] = {
nEntries: CARDINAL ← legendEntries.size;
MaxEntriesPerColumn: CARDINAL = 5;
xmin, xmax: REAL ← 0;
yIncPerRow: REALMAX[-fontHeight[normal][output], (box.ymin - box.ymax) / MaxEntriesPerColumn]; -- note: they are negative.
yScale: REAL = -yIncPerRow / fontHeight[normal][output];
colorSampleLineLength: REAL = 34.0;
sampleLineLength: ARRAY[0..12) OF REAL = [
34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 31.0, 31.0, 34.0, 34.0];
state: LineState ← NEW[LineStateRec];
proc: PROC = {
context.TranslateT[[box.xmin, box.ymax]];
IF yScale >= 0.99 THEN
[] ← MyDrawText[context, footNote, 0, -(box.ymax-box.ymin), left, top, normal, output];
FOR i: CARDINAL IN [0..nEntries) DO
row: CARDINAL ← i MOD MaxEntriesPerColumn;
col: CARDINAL ← i / MaxEntriesPerColumn;
index: CARDINAL ← i MOD 12;
textStartX, textStartY, lineY, newX: REAL;
IF row = 0 AND col > 0 THEN {
xmin ← xmax + TextWidthFudge;
IF xmin >= box.xmax THEN EXIT;
};
textStartX ← xmin + 36.0;
textStartY ← row*yIncPerRow;
lineY ← textStartY + (yIncPerRow/2.0);
context.SetStrokeWidth[minLineWidth];
context.SetStrokeEnd[butt];
IF colorType = bw THEN {
context.SetColor[foreground]; -- already done. ... bad!
state^ ← [];
DrawLineSeg[context, [xmin, lineY], [xmin+sampleLineLength[index], lineY],
index, state];
}
ELSE {
context.SetColor[color[index+1]];
context.MaskVector[[xmin, lineY], [xmin+colorSampleLineLength, lineY]];
};
[[newX, ]] ← MyDrawText [
context,
legendEntries[i],
textStartX, textStartY,
left, top, normal, output, 1.0, yScale];
IF newX > xmax THEN xmax ← newX;
ENDLOOP;
}; -- proc
context.DoSave[proc];
}; -- DrawLegendFootnote
MakeMenu: PROC [] RETURNS [menu: Menus.Menu] = {
menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Color", SetColor]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Cursor", CursorColor]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Background", BackgroundColor]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry[name: "Get", proc: Get, guarded: TRUE]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Store", Store]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Press", Press]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Interpress", Interpress]];
}; -- MakeMenus
ProcessUserProfile: UserProfile.ProfileChangedProc = {
initialColumn
rope: Rope.ROPE ← UserProfile.Token[key: "Plot.Column", default: "color"];
initialColumn ← SELECT TRUE FROM
rope.Equal["color", FALSE] => color,
rope.Equal["left", FALSE] => left,
rope.Equal["right", FALSE] => right,
ENDCASE => static;
IF initialColumn = static THEN {
MessageWindow.Append[
Rope.Cat["Warning: Illegal entry in user profile, Plot.Column: ", rope, "."],
TRUE];
initialColumn ← color;
};
IF initialColumn = color AND NOT WindowManager.colorDisplayOn THEN initialColumn ← left;
createOption
rope ← UserProfile.Token[key: "Plot.CreateOption", default: "closeOtherViewers"];
createOption ← SELECT TRUE FROM
rope.Equal["closeOtherViewers", FALSE] => closeOtherViewers,
rope.Equal["swapLastViewer", FALSE] => swapLastViewer,
rope.Equal["iconic", FALSE] => iconic,
ENDCASE => badCreateOption;
IF createOption = badCreateOption THEN {
MessageWindow.Append[
Rope.Cat["Warning: Illegal entry in user profile, Plot.CreateOption: ", rope, "."],
TRUE];
createOption ← closeOtherViewers;
};
backgroundType
rope ← UserProfile.Token[key: "Plot.Background", default: "white"];
backgroundType ← SELECT TRUE FROM
rope.Equal["white", FALSE] => white,
rope.Equal["gray", FALSE] => gray,
rope.Equal["darkGray", FALSE] => darkGray,
rope.Equal["black", FALSE] => black,
ENDCASE => unknown;
IF backgroundType = unknown THEN {
MessageWindow.Append[
Rope.Cat["Warning: Illegal entry in user profile, Plot.Background: ", rope, "."],
TRUE];
backgroundType ← white;
};
colors
SetMyColors[];
SetCursorForBackgroundType[backgroundType];
apply
PaintAllPlotViewers[];
}; -- ProcessUserProfile
SetCursorForBackgroundType: PROC [bkgnd: BackgroundType ← white] = {
bkGndColor: Imager.ConstantColor ← SELECT bkgnd FROM
gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0];
cursorType: Terminal.ColorCursorPresentation ←
IF ImagerColor.GrayFromColor[bkGndColor] > 0.5 THEN onesAreWhite ELSE onesAreBlack;
IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN
[] ← Terminal.SetColorCursorPresentation[virtual, cursorType];
}; -- SetCursorForBackgroundType
Init: PROC [] = {
ViewerOps.RegisterViewerClass[$Plot, plotViewerClass];
FOR f: FontType IN FontType DO
FOR o: OutputType IN OutputType DO
extents: ImagerFont.Extents ← ImagerFont.FontBoundingBox[textFont[f][o]];
fontHeight[f][o] ← extents.descent+extents.ascent;
ENDLOOP;
ENDLOOP;
ProcessUserProfile[firstTime];
UserProfile.CallWhenProfileChanges[ProcessUserProfile];
}; -- Init
Init[];
}.
CHANGE LOG.
Created by: SChen in Cedar5.
SChen, May 14, 1985 5:39:02 pm PDT, custom icon.
SChen, July 11, 1985 11:03:15 pm PDT, => Cedar6.0 (CG, press => Imager, Interpress).
SChen, cleaned up some stuff related to color display.