-- GraphicsImpl.mesa
-- Last changed by Doug Wyatt, September 17, 1980 1:04 PM

DIRECTORY
Graphics,
Vector USING [Vec, Matrix, Add, Sub, Mul],
Cubic USING [Coeffs, Bezier, CoeffsToBezier, BezierPolygon],
Device USING [Handle, Object, NewPipe, NewText,
ApplyBaseTransform, Boundary, Ref, Free],
OpaqueDevice USING [],
AltoDevice USING [ScreenDevice],
ImageObj USING [Handle,Reset, EnumerateBoundaries],
Style USING [Data],
Font USING [Id, nullId, Fam, Face, EncodeFam, EncodeFace, EncodeTexFace],
Text USING [Handle, Object, Info, CharInfo, StringInfo, FontInfo,
DrawChar, DrawString, Free],
Mapper USING [Handle, NewMapper, Map, MapDelta,InverseMap,InverseMapDelta,
Translate, Concat, Ortho, Read, Copy, Free],
Clipper USING [Handle, NewClipper, NewPipe, Push, Pop, Test,
NewRegion, Copy, Free],
Area USING [Rec, Handle, Rectangle, Free],
Poly USING [Handle, New, Put, NewArea, Free, NewRec],
Pipe USING [Handle, Put, Free],
Boxer USING [Handle, sink, New, Include, Expand, Rectangle, Free],
Path USING [Handle, EnterPoint, EnterCubic, Close, Boundary, Generate, Free],
AreaPath USING [New],
Memory USING [zone],
Real USING [FixC],
RealFns USING [CosDeg, SinDeg, SqRt];

GraphicsImpl: PROGRAM
IMPORTS Memory,Vector,Cubic,Device,ImageObj,AltoDevice,Font,Text,
Mapper,Clipper,Area,Poly,Pipe,Boxer,Path,AreaPath,
Real,RealFns
EXPORTS Graphics,OpaqueDevice = {
OPEN Graphics;

zone: UNCOUNTED ZONE = Memory.zone;

signal: BOOLEAN=FALSE;
NotYetImplemented: PUBLIC SIGNAL = CODE;
NotYet: PROC = INLINE { IF signal THEN SIGNAL NotYetImplemented };

DeviceObject: PUBLIC TYPE = Device.Object;
TextObject: PUBLIC TYPE = Text.Object;

DisplayData: PUBLIC TYPE = RECORD [
device: Device.Handle,
world: Mapper.Handle,
clipper: Clipper.Handle,
paths: PathNodeRef,
boxer: Boxer.Handle,
text: Text.Handle,
top: DataRef -- top of stack
];
Context: TYPE = LONG POINTER TO DisplayData;

PathNode: TYPE = RECORD [
link: PathNodeRef,
path: Path.Handle
];
PathNodeRef: TYPE = LONG POINTER TO PathNode;

Data: PUBLIC TYPE = RECORD [
link: DataRef,
style: Style.Data,
position: Vector.Vec,
linewidth: REAL,
fontid: Font.Id,
fontsize: REAL,
mapper: Mapper.Handle
];
DataRef: TYPE = LONG POINTER TO Data;

NewContext: PUBLIC PROC[device: Device.Handle] RETURNS[Context] = {
dc: Context;
world: Mapper.Handle=Mapper.NewMapper[];
IF device=NIL THEN device←AltoDevice.ScreenDevice[];
Device.ApplyBaseTransform[device,world];
dc ← zone.NEW[DisplayData ← [
device: Device.Ref[device],
world: world,
clipper: Clipper.NewClipper[],
paths: NIL, boxer: Boxer.sink, text: NIL, top: NIL]];
InitContext[dc];
RETURN[dc];
};

defaultLineWidth: REAL=1.5;

InitContext: PUBLIC PROC[dc: Context] = {
FreeStack[dc];
FreePaths[dc];
dc.top←zone.NEW[Data ← [
link: NIL,
style: [paint: paint, texture: black, color: ],
position: [0,0],
linewidth: defaultLineWidth,
fontid: Font.nullId, fontsize: 0,
mapper: Mapper.Copy[dc.world]
]];
GenerateScreen[dc,Clipper.NewRegion[dc.clipper]];
Boxer.Free[@dc.boxer];
ResetText[dc];
};

GenerateScreen: PROC[dc: Context, pipe: Pipe.Handle] = {
Pipe.Put[pipe,Device.Boundary[dc.device]]; Pipe.Free[@pipe];
};

PushContext: PUBLIC PROC[dc: Context] = {
d: DataRef=dc.top;
dd: DataRef = zone.NEW[Data ← [
link: d,
style: d.style,
position: d.position,
linewidth: d.linewidth,
fontid: d.fontid, fontsize: d.fontsize,
mapper: Mapper.Copy[d.mapper]
]];
dc.top←dd;
};

PushBaseContext: PUBLIC PROC[dc: Context] = {
d: DataRef=dc.top;
dd: DataRef;
dt: DataRef←dc.top;
UNTIL dt.link= NIL DO
dt←dt.link;
ENDLOOP;
dd ← zone.NEW[Data ← [
link: d,
style: d.style,
position: d.position,
linewidth: d.linewidth,
fontid: d.fontid, fontsize: d.fontsize,
mapper: Mapper.Copy[dt.mapper]
]];
dc.top←dd;
ResetText[dc];
};

PopContext: PUBLIC PROC[dc: Context] = {
d: DataRef←dc.top;
IF d.link=NIL THEN RETURN;
dc.top←d.link;
Mapper.Free[@d.mapper]; zone.FREE[@d];
ResetText[dc];
};

CopyContext: PUBLIC PROC[dc: Context] RETURNS[Context] = {
d: DataRef=dc.top;
dd: DataRef = zone.NEW[Data ← [
link: NIL,
style: d.style,
position: d.position,
linewidth: d.linewidth,
fontid: d.fontid, fontsize: d.fontsize,
mapper: Mapper.Copy[d.mapper]
]];
RETURN[zone.NEW[DisplayData ← [
device: Device.Ref[dc.device],
world: Mapper.Copy[dc.world],
clipper: Clipper.Copy[dc.clipper],
paths: NIL,
boxer: Boxer.sink,
text: NIL, top: dd]]];
};

FreePaths: PROC[dc: Context] = {
p: PathNodeRef←dc.paths;
dc.paths←NIL;
UNTIL p=NIL DO
next: PathNodeRef←p.link;
Path.Free[@p.path]; zone.FREE[@p];
p←next ENDLOOP;
};

FreeStack: PROC[dc: Context] = {
d: DataRef←dc.top;
dc.top←NIL;
UNTIL d=NIL DO
next: DataRef←d.link;
Mapper.Free[@d.mapper]; zone.FREE[@d];
d←next ENDLOOP;
};

FreeContext: PUBLIC PROC[dcPtr: LONG POINTER TO DisplayContext] = {
dc: Context←dcPtr↑;
d: DataRef←dc.top;
dcPtr↑←NIL;
FreeStack[dc];
FreePaths[dc];
Device.Free[@dc.device];
Mapper.Free[@dc.world];
Clipper.Free[@dc.clipper];
Boxer.Free[@dc.boxer];
zone.FREE[@dc];
};

TransformDelta: PUBLIC PROC[dc: Context,x,y:REAL] RETURNS[xp,yp:REAL] = {
v,w:Vec;
d: DataRef=dc.top;
v.x←x; v.y←y;
w←Mapper.MapDelta[d.mapper,v];
RETURN[w.x,w.y];
};

InverseTransformDelta: PUBLIC PROC[dc: Context,x,y:REAL] RETURNS[xp,yp:REAL] =
{v,w:Vec;
d: DataRef=dc.top;
v.x←x; v.y←y;
w←Mapper.InverseMapDelta[d.mapper,v];
RETURN[w.x,w.y];
};
GetPosition: PUBLIC PROC[dc: Context] RETURNS[Vec] = {
d: DataRef=dc.top;
RETURN[Mapper.InverseMap[d.mapper,d.position]];
};

GetDevicePosition: PUBLIC PROC[dc: Context] RETURNS[x,y:REAL] = {
d: DataRef=dc.top;
RETURN[d.position.x,d.position.y];
};

SetPaint: PUBLIC PROC[dc: Context, p: PaintingFunction] = {
d: DataRef=dc.top;
d.style.paint←p;
ResetText[dc];
};
GetPaint: PUBLIC PROC[dc: Context] RETURNS[PaintingFunction] = {
d: DataRef=dc.top;
RETURN[d.style.paint];
};

SetTexture: PUBLIC PROC[dc: Context, t: Texture] = {
d: DataRef=dc.top;
d.style.texture←t;
ResetText[dc];
};
GetTexture: PUBLIC PROC[dc: Context] RETURNS[Texture] = {
d: DataRef=dc.top;
RETURN[d.style.texture];
};

SetColor: PUBLIC PROC[dc: Context, c: Color] = {
d: DataRef=dc.top;
d.style.color←c;
ResetText[dc];
};
GetColor: PUBLIC PROC[dc: Context] RETURNS[Color] = {
d: DataRef=dc.top;
RETURN[d.style.color];
};

DisplaySize: PUBLIC PROC[dc: Context] RETURNS[Vec] = {
d: DataRef=dc.top;
area: Area.Handle←Device.Boundary[dc.device];
r: Area.Rec=Area.Rectangle[area];
Area.Free[@area]; RETURN[r.ur];
};

Translate: PUBLIC PROC[dc: Context, v: Vec] = {
d: DataRef=dc.top;
Mapper.Translate[d.mapper,v];
};

Scale: PUBLIC PROC[dc: Context, v: Vec] = {
d: DataRef=dc.top;
Mapper.Concat[d.mapper,[v.x,0,0,v.y]];
ResetText[dc];
};

Rotate: PUBLIC PROC[dc: Context, angle: REAL] = {
d: DataRef=dc.top;
cost: REAL←RealFns.CosDeg[angle];
sint: REAL←RealFns.SinDeg[angle];
eps: REAL=1E-6;
IF ABS[sint]<eps THEN { sint←0; cost←(IF cost>0 THEN 1 ELSE -1) };
IF ABS[cost]<eps THEN { cost←0; sint←(IF sint>0 THEN 1 ELSE -1) };
Mapper.Concat[d.mapper,[cost,-sint,sint,cost]];
ResetText[dc];
};

Concatenate: PUBLIC PROC[dc: Context, m: Vector.Matrix] = {
d: DataRef=dc.top;
Mapper.Concat[d.mapper,m];
ResetText[dc];
};

Map: PUBLIC PROC[sdc,ddc: Context, sp: Vec] RETURNS[dp: Vec] = {
sd: DataRef=sdc.top;
dd: DataRef=ddc.top;
IF sdc.device=ddc.device THEN RETURN[
Mapper.InverseMap[dd.mapper,
Mapper.Map[sd.mapper,sp]]]
ELSE RETURN[
Mapper.InverseMap[dd.mapper,
Mapper.Map[ddc.world,
Mapper.InverseMap[sdc.world,
Mapper.Map[sd.mapper,sp]]]]];
};

ScreenPoint: PUBLIC PROC[dc: Context, v: Vec] RETURNS[Vec] = {
d: DataRef=dc.top;
RETURN[Mapper.InverseMap[d.mapper,v]];
};

SetLineWidth: PUBLIC PROC[dc: Context, w: REAL] = {
d: DataRef=dc.top;
d.linewidth←w;
};
GetLineWidth: PUBLIC PROC[dc: Context] RETURNS[REAL] = {
d: DataRef=dc.top;
RETURN[d.linewidth];
};

MoveTo: PUBLIC PROC[dc: Context, v: Vec] = {
d: DataRef=dc.top;
d.position←Mapper.Map[d.mapper,v];
};

RelMoveTo: PUBLIC PROC[dc: Context, v: Vec] = {
d: DataRef=dc.top;
d.position←Vector.Add[d.position,Mapper.MapDelta[d.mapper,v]];
};

DrawTo: PUBLIC PROC[dc: Context, v: Vec] = {
d: DataRef=dc.top;
p: Vec=Mapper.InverseMap[d.mapper,d.position];
DrawLine[dc,p,v];
MoveTo[dc,v];
};

RelDrawTo: PUBLIC PROC[dc: Context, v: Vec] = {
d: DataRef=dc.top;
p: Vec=Mapper.InverseMap[d.mapper,d.position];
DrawLine[dc,p,Vector.Add[p,v]];
RelMoveTo[dc,v];
};

Norm: PROC[v: Vec] RETURNS[REAL] =
INLINE { RETURN[RealFns.SqRt[v.x*v.x+v.y*v.y]] };

DrawLine: PROC[dc: Context, u,v: Vec] = {
OPEN Vector;
d: DataRef=dc.top;
h: REAL=d.linewidth/2; -- half line width
w: Vec=Sub[v,u];
l: REAL=Norm[w];
a: Vec=IF l=0 THEN [h,0] ELSE Mul[w,(h/l)];
b: Vec=[-a.y,a.x];
s: Vec=Sub[u,a];
t: Vec=Add[v,a];
StartAreaPath[dc];
EnterPoint[dc,Sub[s,b]];
EnterPoint[dc,Add[s,b]];
EnterPoint[dc,Add[t,b]];
EnterPoint[dc,Sub[t,b]];
DrawArea[dc];
};

DrawCubic: PUBLIC PROC[dc: Context, c: POINTER TO Cubic.Coeffs] = {
d: DataRef=dc.top;
b: Cubic.Bezier←Cubic.CoeffsToBezier[c↑];
Proc: PROC[v: Vec] = { DrawTo[dc,v] };
MoveTo[dc,b.b0]; Cubic.BezierPolygon[b,2,Proc];
};

PushPath: PROC[dc: Context, path: Path.Handle] = INLINE {
p: PathNodeRef=zone.NEW[PathNode ← [link: dc.paths, path: path]];
dc.paths←p;
};

PopPath: PROC[dc: Context] = INLINE {
p: PathNodeRef←dc.paths;
IF p#NIL THEN { dc.paths←p.link; Path.Free[@p.path]; zone.FREE[@p] }
};

StartLinePath: PUBLIC PROC[dc: Context, width: REAL] = {
d: DataRef=dc.top;
NotYet;
};

StartSplinePath: PUBLIC PROC[dc: Context, width: REAL] = {
d: DataRef=dc.top;
NotYet;
};

StartAreaPath: PUBLIC PROC[dc: Context, oddeven: BOOLEAN←FALSE] = {
PushPath[dc,AreaPath.New[oddeven]];
};

EnterPoint: PUBLIC PROC[dc: Context, v: Vec] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN {
d: DataRef=dc.top;
Path.EnterPoint[p.path,Mapper.Map[d.mapper,v]]
};
};

EnterCubic: PUBLIC PROC[dc: Context, c: POINTER TO Cubic.Coeffs] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN {
d: DataRef=dc.top;
m: Mapper.Handle=d.mapper;
cc: Cubic.Coeffs ← [
c0: Mapper.Map[m,c.c0],
c1: Mapper.MapDelta[m,c.c1],
c2: Mapper.MapDelta[m,c.c2],
c3: Mapper.MapDelta[m,c.c3]
];
Path.EnterCubic[p.path,cc];
};
};

NewBoundary: PUBLIC PROC[dc: Context] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN Path.Close[p.path];
};

DrawArea: PUBLIC PROC[dc: Context] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN {
path: Path.Handle=p.path;
clipper: Clipper.Handle=dc.clipper;
d: DataRef=dc.top;
area: Area.Handle←Path.Boundary[path];
Boxer.Include[dc.boxer,area];
Clipper.Push[clipper,area];
IF Clipper.Test[clipper].in THEN
Path.Generate[p.path,clipper,Device.NewPipe[dc.device,d.style]];
Clipper.Pop[clipper];
DestroyPath[dc];
};
};

DrawImage: PUBLIC PROC[dc:Context,image:ImageObj.Handle] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN {
path: Path.Handle=p.path;
d: DataRef=dc.top;
ib: Path.Handle;
oldclipper: Clipper.Handle←dc.clipper;
ImageObj.Reset[image];
WHILE (ib←ImageObj.EnumerateBoundaries[image,d.mapper]) # NIL DO
area: Area.Handle←Path.Boundary[path];
-- clipper is the current clipper from the display context
dc.clipper←Clipper.NewClipper[]; -- context gets a new clipper
-- new clipping area is the path clipped to the old clipping area
Path.Generate[ib,oldclipper,Clipper.NewRegion[dc.clipper]];
Path.Free[@ib];
Boxer.Include[dc.boxer,area];
Clipper.Push[dc.clipper,area];
IF Clipper.Test[dc.clipper].in THEN
{StartAreaPath[dc];
Path.Generate[p.path,dc.clipper,
Device.NewPipe[dc.device,d.style,d.mapper,image]];
};
Clipper.Pop[dc.clipper];
Clipper.Free[@dc.clipper];
ENDLOOP;
dc.clipper←oldclipper;
DestroyPath[dc];
};
};

DrawRectangle: PUBLIC PROC[dc: Context, ll,ur: Vec] = {
d: DataRef=dc.top;
mapper: Mapper.Handle=d.mapper;
IF Mapper.Ortho[mapper] THEN {
u: Vec=Mapper.Map[mapper,ll];
v: Vec=Mapper.Map[mapper,ur];
r: Area.Rec=[[MIN[u.x,v.x],MIN[u.y,v.y]],[MAX[u.x,v.x],MAX[u.y,v.y]]];
area: Area.Handle←Poly.NewRec[r];
pipe: Pipe.Handle←Clipper.NewPipe[dc.clipper,
Device.NewPipe[dc.device,d.style]];
Boxer.Include[dc.boxer,area];
Pipe.Put[pipe,area]; Pipe.Free[@pipe];
}
ELSE {
StartAreaPath[dc];
EnterPoint[dc,ll];
EnterPoint[dc,[ll.x,ur.y]];
EnterPoint[dc,ur];
EnterPoint[dc,[ur.x,ll.y]];
DrawArea[dc];
};
};

DestroyPath: PUBLIC PROC[dc: Context] = {
PopPath[dc];
};

DrawScreenArea: PUBLIC PROC[dc: Context] = {
d: DataRef=dc.top;
GenerateScreen[dc,Clipper.NewPipe[dc.clipper,
Device.NewPipe[dc.device,d.style]]];
};


FontId: PUBLIC TYPE = Font.Id;

MakeFont: PUBLIC PROC[family: STRING, bold,italic: BOOLEAN]
RETURNS[Font.Id] = {
fam: Font.Fam=Font.EncodeFam[family];
face: Font.Face=Font.EncodeFace[
w: IF bold THEN bold ELSE medium,
s: IF italic THEN italic ELSE regular,
e: regular];
RETURN[[fam,face]];
};

MakeTexFont: PUBLIC PROC[family: STRING, logicalSize: REAL]
RETURNS[Font.Id] = {
fam: Font.Fam=Font.EncodeFam[family];
hpts: REAL=MAX[0,MIN[200,2*logicalSize]]; -- half points
face: Font.Face=Font.EncodeTexFace[Real.FixC[hpts+0.5]];
RETURN[[fam,face]];
};

SetFont: PUBLIC PROC[dc: Context, font: Font.Id, size: REAL] = {
d: DataRef=dc.top;
ResetText[dc];
d.fontid←font; d.fontsize←size;
};

DisplayChar: PUBLIC PROC[dc: Context, c: CHARACTER] = {
d: DataRef=dc.top;
text: Text.Handle←GetText[dc];
d.position←Text.DrawChar[text,c,d.position,dc.clipper];
};

DisplayString: PUBLIC PROC[dc: Context, s: LONG STRING] = {
d: DataRef=dc.top;
text: Text.Handle←GetText[dc];
d.position←Text.DrawString[text,s,d.position,dc.clipper];
};

GetText: PROC[dc: Context] RETURNS[Text.Handle] = INLINE {
IF dc.text=NIL THEN CreateText[dc]; RETURN[dc.text];
};
CreateText: PROC[dc: Context] = {
d: DataRef=dc.top;
dc.text←Device.NewText[dc.device,d.fontid,d.fontsize,
Mapper.Read[d.mapper].m,d.style];
};
ResetText: PROC[dc: Context] = INLINE {
IF dc.text#NIL THEN Text.Free[@dc.text]
};


GetCharBox: PUBLIC PROC[dc: Context, c: CHARACTER,
data: POINTER TO CharData] = {
d: DataRef=dc.top;
text: Text.Handle←GetText[dc];
info: Text.Info;
Text.CharInfo[text,c,@info];
data↑←[size: info.size, origin: info.origin, width: info.width];
};

GetStringBox: PUBLIC PROC[dc: Context, s: LONG STRING,
data: POINTER TO CharData] = {
d: DataRef=dc.top;
text: Text.Handle←GetText[dc];
info: Text.Info;
Text.StringInfo[text,s,@info];
data↑←[size: info.size, origin: info.origin, width: info.width];
};

GetFontBox: PUBLIC PROC[dc: Context,
data: POINTER TO CharData] = {
d: DataRef=dc.top;
text: Text.Handle←GetText[dc];
info: Text.Info;
Text.FontInfo[text,@info];
data↑←[size: info.size, origin: info.origin, width: info.width];
};

InitBoxer: PUBLIC PROC[dc: Context] = {
d: DataRef=dc.top;
Boxer.Free[@dc.boxer]; dc.boxer←Boxer.New[];
};

StopBoxer: PUBLIC PROC[dc: Context, bbox: POINTER TO BoundingBox] = {
d: DataRef=dc.top;
r: Area.Rec;
Boxer.Expand[dc.boxer,2]; -- just to be safe
r←Boxer.Rectangle[dc.boxer];
Boxer.Free[@dc.boxer];
bbox[0]←Mapper.InverseMap[d.mapper,r.ll];
bbox[1]←Mapper.InverseMap[d.mapper,[r.ur.x,r.ll.y]];
bbox[2]←Mapper.InverseMap[d.mapper,r.ur];
bbox[3]←Mapper.InverseMap[d.mapper,[r.ll.x,r.ur.y]];
};

PushClipBox: PUBLIC PROC[dc: Context, bbox: POINTER TO BoundingBox] = {
d: DataRef=dc.top;
poly: Poly.Handle←Poly.New[];
FOR i: CARDINAL IN[0..4) DO
Poly.Put[poly,Mapper.Map[d.mapper,bbox[i]]];
ENDLOOP;
Clipper.Push[dc.clipper,Poly.NewArea[poly]]; Poly.Free[@poly];
};

PopClipBox: PUBLIC PROC[dc: Context] = {
Clipper.Pop[dc.clipper];
};

Visible: PUBLIC PROC[dc: Context] RETURNS[BOOLEAN] = {
RETURN[Clipper.Test[dc.clipper].in];
};

SetClipArea: PUBLIC PROC[dc: Context] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN {
screenclip: Clipper.Handle←Clipper.NewClipper[];
GenerateScreen[dc,Clipper.NewRegion[screenclip]];
-- screenclip will clip to the device boundary
Clipper.Free[@dc.clipper]; dc.clipper←Clipper.NewClipper[];
-- new clipping area is the path clipped to the screen edges
Path.Generate[p.path,screenclip,Clipper.NewRegion[dc.clipper]];
Clipper.Free[@screenclip]; DestroyPath[dc];
};
};

IntersectClipArea: PUBLIC PROC[dc: Context] = {
p: PathNodeRef=dc.paths;
IF p#NIL THEN {
oldclipper: Clipper.Handle←dc.clipper;
-- clipper is the current clipper from the display context
dc.clipper←Clipper.NewClipper[]; -- context gets a new clipper
-- new clipping area is the path clipped to the old clipping area
Path.Generate[p.path,oldclipper,Clipper.NewRegion[dc.clipper]];
Clipper.Free[@oldclipper]; DestroyPath[dc];
};
};

}.