-- CGPrivateImpl.mesa
-- Last edited by Doug Wyatt, September 20, 1982 4:21 pm

DIRECTORY
CGArea USING [Insert, InsertBox, Ref],
CGClipper USING [Bounds, Empty, GenerateBox, GenerateLine, IsBox, Load,
Ref, SetArea, TestBox, TestPoint],
CGCubic USING [Bezier, Flat, Split],
CGContext USING [Ref, TouchClipper, TouchCP, TouchFill, TouchMatrix],
CGDevice USING [Fill, Generator, Ref, Rep],
CGDummyDevice USING [Get],
CGMatrix USING [Concat, Inv, InvRel, Map, MapRel, Ref, SetTrans],
CGPath USING [Bounds, Empty, MapAndFilter],
CGPrivate USING [],
CGReducer USING [Close, Generate, Ref, Vertex],
CGVector USING [Add],
Graphics USING [Context, Warning],
GraphicsBasic USING [Box, Color, PaintMode, Path, Trap, Vec, YMode],
SpecialReal USING [Round];

CGPrivateImpl: CEDAR PROGRAM
IMPORTS CGArea, CGClipper, CGContext, CGCubic, CGDummyDevice,
CGMatrix, CGPath, CGReducer, CGVector, Graphics, SpecialReal
EXPORTS CGContext, CGPrivate, GraphicsBasic = { OPEN GraphicsBasic;

Context: TYPE = Graphics.Context;
Data: TYPE = CGContext.Ref;
Path: TYPE = GraphicsBasic.Path;

DeviceObject: PUBLIC TYPE = CGDevice.Rep; -- export to GraphicsBasic

-- Procedures dependent on data representation --

GetCP: PUBLIC PROC[self: Context, rounded: BOOLEAN] RETURNS[x,y: REAL] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
cp: Vec ← d.cp;
p: Vec;
IF rounded THEN { cp.x ← SpecialReal.Round[cp.x]; cp.y ← SpecialReal.Round[cp.y] };
p ← CGMatrix.Inv[m,cp];
RETURN[p.x,p.y];
};

SetCP: PUBLIC PROC[self: Context, x,y: REAL, rel: BOOLEAN] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
p: Vec ← [x,y];
CGContext.TouchCP[d];
IF rel THEN d.cp ← CGVector.Add[d.cp,CGMatrix.MapRel[m,p]]
ELSE d.cp ← CGMatrix.Map[m,p];
};

DrawTo: PUBLIC PROC[self: Context, x,y: REAL, rel: BOOLEAN] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
clipper: CGClipper.Ref ← d.clipper;
device: CGDevice.Ref ← d.device;
fill: CGDevice.Fill ← d.fill;
p, q: Vec;
Gen: CGDevice.Generator = {
CGClipper.GenerateLine[clipper, p, q, showTrap];
};
p ← d.cp;
CGContext.TouchCP[d];
IF rel THEN q ← CGVector.Add[p,CGMatrix.MapRel[m,[x,y]]]
ELSE q ← CGMatrix.Map[m,[x,y]];
fill.fat ← TRUE;
device.ShowConst[device, Gen, fill];
d.cp ← q;
};

DrawArea: PUBLIC PROC[self: Context, path: Path, parityFill: BOOLEAN] = {
d: Data ← NARROW[self.data];
device: CGDevice.Ref ← d.device;
Gen: CGDevice.Generator = {
GenPath[d: d, trap: showTrap,
path: path, parityFill: parityFill, exclude: FALSE];
};
device.ShowConst[device, Gen, d.fill];
};

DrawBox: PUBLIC PROC[self: Context, box: Box] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
device: CGDevice.Ref ← d.device;
Gen: CGDevice.Generator = { GenBox[d, showTrap, showBox, box, m] };
device.ShowConst[device, Gen, d.fill];
};

Translate: PUBLIC PROC[self: Context, tx,ty: REAL, round: BOOLEAN] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
t: Vec ← CGMatrix.Map[m,[tx,ty]];
CGContext.TouchMatrix[d];
IF round THEN {
t.x ← SpecialReal.Round[t.x];
t.y ← SpecialReal.Round[t.y];
};
CGMatrix.SetTrans[m,t];
};

Concat: PUBLIC PROC[self: Context, m11,m12,m21,m22: REAL] = {
d: Data ← NARROW[self.data];
CGContext.TouchMatrix[d];
CGMatrix.Concat[d.matrix,m11,m12,m21,m22];
};

WorldToUser: PUBLIC PROC[self: Context, wx,wy: REAL] RETURNS[x,y: REAL] = {
d: Data ← NARROW[self.data];
device: CGDevice.Ref ← d.device;
world: CGMatrix.Ref ← device.GetMatrix[device];
m: CGMatrix.Ref ← d.matrix;
u: Vec ← CGMatrix.Inv[m,CGMatrix.Map[world,[wx,wy]]];
RETURN[u.x,u.y];
};

UserToWorld: PUBLIC PROC[self: Context, x,y: REAL] RETURNS[wx,wy: REAL] = {
d: Data ← NARROW[self.data];
device: CGDevice.Ref ← d.device;
world: CGMatrix.Ref ← device.GetMatrix[device];
m: CGMatrix.Ref ← d.matrix;
w: Vec ← CGMatrix.Inv[world,CGMatrix.Map[m,[x,y]]];
RETURN[w.x,w.y];
};

SetColor: PUBLIC PROC[self: Context, color: Color] = {
d: Data ← NARROW[self.data];
CGContext.TouchFill[d];
d.fill.color ← color;
};

GetColor: PUBLIC PROC[self: Context] RETURNS[Color] = {
d: Data ← NARROW[self.data];
RETURN[d.fill.color];
};

SetPaintMode: PUBLIC PROC[self: Context, mode: PaintMode] RETURNS[PaintMode] = {
d: Data ← NARROW[self.data];
old: PaintMode ← d.fill.mode;
CGContext.TouchFill[d];
d.fill.mode ← mode;
RETURN[old];
};

SetFat: PUBLIC PROC[self: Context, fat: BOOLEAN] RETURNS[BOOLEAN] = {
d: Data ← NARROW[self.data];
f: BOOLEAN ← d.fill.fat;
CGContext.TouchFill[d];
d.fill.fat ← fat; RETURN[f];
};

ClipArea: PUBLIC PROC[self: Context, path: Path,
parityFill: BOOLEAN, exclude: BOOLEAN] = {
d: Data ← NARROW[self.data];
clipper: CGClipper.Ref ← d.clipper;
area: CGArea.Ref ← d.area;
Add: PROC[trap: Trap] = { CGArea.Insert[area, trap] };
CGContext.TouchClipper[d];
GenPath[d, Add, path, parityFill, exclude];
CGClipper.SetArea[self: clipper, area: area];
};

ClipBox: PUBLIC PROC[self: Context, box: Box, exclude: BOOLEAN] = {
d: Data ← NARROW[self.data];
area: CGArea.Ref ← d.area;
AddTrap: PROC[trap: Trap] = { CGArea.Insert[area, trap] };
AddBox: PROC[box: Box] = { CGArea.InsertBox[area, box] };
CGContext.TouchClipper[d];
GenBox[d,AddTrap,AddBox,box,d.matrix,exclude];
CGClipper.SetArea[d.clipper,area];
};

IsPointVisible: PUBLIC PROC[self: Context, x,y: REAL] RETURNS[BOOLEAN] = {
d: Data ← NARROW[self.data];
clipper: CGClipper.Ref ← d.clipper;
m: CGMatrix.Ref ← d.matrix;
p: Vec ← CGMatrix.Map[m,[x,y]];
RETURN[CGClipper.TestPoint[clipper,p]];
};

IsRectangular: PUBLIC PROC[self: Context] RETURNS[BOOLEAN] = {
d: Data ← NARROW[self.data];
clipper: CGClipper.Ref ← d.clipper;
RETURN[CGClipper.IsBox[clipper]];
};

GetBounds: PUBLIC PROC[self: Context] RETURNS[Box] = {
data: Data ← NARROW[self.data];
clipper: CGClipper.Ref ← data.clipper;
m: CGMatrix.Ref ← data.matrix;
box: Box ← CGClipper.Bounds[clipper];
result: Box;
a,b,c,d: Vec;
IF m.rectangular THEN {
a ← CGMatrix.Inv[m,[box.xmin,box.ymin]];
c ← CGMatrix.Inv[m,[box.xmax,box.ymax]];
result.xmin ← MIN[a.x,c.x];
result.xmax ← MAX[a.x,c.x];
result.ymin ← MIN[a.y,c.y];
result.ymax ← MAX[a.y,c.y];
}
ELSE {
a ← CGMatrix.Inv[m,[box.xmin,box.ymin]];
b ← CGMatrix.Inv[m,[box.xmax,box.ymin]];
c ← CGMatrix.Inv[m,[box.xmax,box.ymax]];
d ← CGMatrix.Inv[m,[box.xmin,box.ymax]];
result.xmin ← MIN[a.x,b.x,c.x,d.x];
result.xmax ← MAX[a.x,b.x,c.x,d.x];
result.ymin ← MIN[a.y,b.y,c.y,d.y];
result.ymax ← MAX[a.y,b.y,c.y,d.y];
};
RETURN[result];
};

Visible: PUBLIC PROC[self: Context] RETURNS[BOOLEAN] = {
d: Data ← NARROW[self.data];
RETURN[NOT CGClipper.Empty[d.clipper]];
};

UserToDevice: PUBLIC PROC[self: Context, x, y: REAL, rel: BOOLEAN] RETURNS[tx, ty: REAL] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
w: Vec;
IF rel THEN w ← CGMatrix.MapRel[m,[x,y]]
ELSE w ← CGMatrix.Map[m,[x,y]];
RETURN[w.x, w.y];
};

DeviceToUser: PUBLIC PROC[self: Context, tx, ty: REAL, rel: BOOLEAN] RETURNS[x, y: REAL] = {
d: Data ← NARROW[self.data];
m: CGMatrix.Ref ← d.matrix;
w: Vec;
IF rel THEN w ← CGMatrix.InvRel[m,[tx,ty]]
ELSE w ← CGMatrix.Inv[m,[tx,ty]];
RETURN[w.x, w.y];
};

GetYMode: PUBLIC PROC[self: Context] RETURNS[YMode] = {
d: Data ← NARROW[self.data];
RETURN[IF d.yUp THEN bottomUp ELSE topDown];
};

SetYMode: PUBLIC PROC[self: Context, mode: YMode] = {
d: Data ← NARROW[self.data];
yUp: BOOLEAN ← (mode=bottomUp);
IF d.yUp#yUp THEN {
CGContext.TouchMatrix[d];
CGMatrix.Concat[d.matrix,1,0,0,-1];
d.yUp ← yUp;
};
};

Disable: PUBLIC PROC[self: Context] = {
d: Data ← NARROW[self.data];
d.device ← CGDummyDevice.Get[];
d.dbase ← NIL; d.drast ← 0; d.haveRaster ← FALSE;
};

MoveDeviceRectangle: PUBLIC PROC[self: Context,
width, height, fromX, fromY, toX, toY: NAT] = {
d: Data ← NARROW[self.data];
device: CGDevice.Ref ← d.device;
IF device.MoveBlock#NIL THEN device.MoveBlock[self: device,
width: width, height: height, fromX: fromX, fromY: fromY, toX: toX, toY: toY]
ELSE SIGNAL Graphics.Warning[notImplemented];
};


-- Miscellaneous utility procedures --

GenPath: PROC[d: Data, trap: PROC[Trap], path: Path, parityFill, exclude: BOOLEAN] = {
IF NOT CGPath.Empty[path] THEN {
matrix: CGMatrix.Ref ← d.matrix;
box: Box ← MapBox[matrix, CGPath.Bounds[path]];
clipper: CGClipper.Ref ← d.clipper;
reducer: CGReducer.Ref ← d.reducer;
IF CGClipper.TestBox[clipper, box, reducer] THEN {
EnterPath[path, matrix, reducer];
CGReducer.Generate[self: reducer, proc: trap, exclude: exclude, oddwrap: parityFill];
};
};
};

EnterPath: PROC[path: Path, m: CGMatrix.Ref, reducer: CGReducer.Ref] = {
lp: Vec;
Map: PROC[v: Vec] RETURNS[Vec] = { RETURN[CGMatrix.Map[m, v]] };
Vertex: PROC[v: Vec] = { CGReducer.Vertex[reducer, lp ← v] };
Curve: PROC[v1, v2, v3: Vec] = {
eps: REAL = 1.5;
maxdepth: NAT = 10;
Divide: PROC[b: CGCubic.Bezier, depth: NAT ← 0] = {
IF depth>=maxdepth OR CGCubic.Flat[b, eps] THEN CGReducer.Vertex[reducer, b.b3]
ELSE { b1, b2: CGCubic.Bezier; [b1, b2] ← CGCubic.Split[b];
Divide[b1, depth+1]; Divide[b2, depth+1] };
};
Divide[[lp, v1, v2, v3]]; lp ← v3;
};
Close: PROC = { CGReducer.Close[reducer] };
CGPath.MapAndFilter[path, Map, Vertex, Vertex, Curve, Close];
};

MapBox: PUBLIC PROC[m: CGMatrix.Ref, box: Box] RETURNS[Box] = {
mbox: Box;
IF m.rectangular THEN {
a: Vec ← CGMatrix.Map[m,[box.xmin,box.ymin]];
b: Vec ← CGMatrix.Map[m,[box.xmax,box.ymax]];
IF a.x<=b.x THEN { mbox.xmin ← a.x; mbox.xmax ← b.x }
ELSE { mbox.xmin ← b.x; mbox.xmax ← a.x };
IF a.y<=b.y THEN { mbox.ymin ← a.y; mbox.ymax ← b.y }
ELSE { mbox.ymin ← b.y; mbox.ymax ← a.y };
}
ELSE {
a: Vec ← CGMatrix.Map[m,[box.xmin,box.ymin]];
b: Vec ← CGMatrix.Map[m,[box.xmax,box.ymin]];
c: Vec ← CGMatrix.Map[m,[box.xmax,box.ymax]];
d: Vec ← CGMatrix.Map[m,[box.xmin,box.ymax]];
mbox ← [xmin: MIN[a.x, b.x, c.x, d.x], ymin: MIN[a.y, b.y, c.y, d.y],
xmax: MAX[a.x, b.x, c.x, d.x], ymax: MAX[a.y, b.y, c.y, d.y]];
};
RETURN[mbox];
};

InvBox: PUBLIC PROC[m: CGMatrix.Ref, box: Box] RETURNS[Box] = {
mbox: Box;
IF m.rectangular THEN {
a: Vec ← CGMatrix.Inv[m,[box.xmin,box.ymin]];
b: Vec ← CGMatrix.Inv[m,[box.xmax,box.ymax]];
IF a.x<=b.x THEN { mbox.xmin ← a.x; mbox.xmax ← b.x }
ELSE { mbox.xmin ← b.x; mbox.xmax ← a.x };
IF a.y<=b.y THEN { mbox.ymin ← a.y; mbox.ymax ← b.y }
ELSE { mbox.ymin ← b.y; mbox.ymax ← a.y };
}
ELSE {
a: Vec ← CGMatrix.Inv[m,[box.xmin,box.ymin]];
b: Vec ← CGMatrix.Inv[m,[box.xmax,box.ymin]];
c: Vec ← CGMatrix.Inv[m,[box.xmax,box.ymax]];
d: Vec ← CGMatrix.Inv[m,[box.xmin,box.ymax]];
mbox ← [xmin: MIN[a.x, b.x, c.x, d.x], ymin: MIN[a.y, b.y, c.y, d.y],
xmax: MAX[a.x, b.x, c.x, d.x], ymax: MAX[a.y, b.y, c.y, d.y]];
};
RETURN[mbox];
};

GenBox: PUBLIC PROC[d: Data, emitTrap: PROC[Trap], emitBox: PROC[Box],
box: Box, m: CGMatrix.Ref, exclude: BOOLEANFALSE] = {
clipper: CGClipper.Ref ← d.clipper;
reducer: CGReducer.Ref ← d.reducer;
IF m.rectangular AND NOT exclude THEN {
mbox: Box;
a: Vec ← CGMatrix.Map[m,[box.xmin,box.ymin]];
b: Vec ← CGMatrix.Map[m,[box.xmax,box.ymax]];
IF a.x<=b.x THEN { mbox.xmin ← a.x; mbox.xmax ← b.x }
ELSE { mbox.xmin ← b.x; mbox.xmax ← a.x };
IF a.y<=b.y THEN { mbox.ymin ← a.y; mbox.ymax ← b.y }
ELSE { mbox.ymin ← b.y; mbox.ymax ← a.y };
CGClipper.GenerateBox[clipper,mbox,reducer,emitTrap,emitBox];
}
ELSE {
CGClipper.Load[clipper,reducer];
CGReducer.Vertex[reducer,CGMatrix.Map[m,[box.xmin,box.ymin]]];
CGReducer.Vertex[reducer,CGMatrix.Map[m,[box.xmax,box.ymin]]];
CGReducer.Vertex[reducer,CGMatrix.Map[m,[box.xmax,box.ymax]]];
CGReducer.Vertex[reducer,CGMatrix.Map[m,[box.xmin,box.ymax]]];
CGReducer.Close[reducer];
CGReducer.Generate[self: reducer, proc: emitTrap, exclude: exclude];
};
};


}.