-- CGPathImpl.mesa
-- Last changed by Doug Wyatt, September 15, 1982 5:41 pm

DIRECTORY
CGPath USING [],
CGStorage USING [pZone, qZone],
GraphicsBasic USING [Box, Vec];

CGPathImpl: CEDAR PROGRAM
IMPORTS CGStorage
EXPORTS CGPath, GraphicsBasic = { OPEN GraphicsBasic;

Ref: TYPE = REF Rep;
Rep: TYPE = RECORD[
size: NAT, -- current size
data: Data, -- the list of nodes
box: REF Box -- bounding box
];
PathRep: PUBLIC TYPE = Rep; -- exported to GraphicsBasic

Data: TYPE = REF DataRep;
DataRep: TYPE = RECORD[SEQUENCE space: NAT OF Node];

Node: TYPE = REF NodeRep;
NodeRep: TYPE = RECORD[
p: Vec, -- coordinates of the point
start: BOOLEANFALSE, -- marks the beginning of a new trajectory
control: BOOLEANFALSE -- marks a control point for a curve segment
];

Error: PUBLIC ERROR = CODE;

dataZone: ZONE = CGStorage.pZone;
nodeZone: ZONE = CGStorage.qZone;
boxZone: ZONE = CGStorage.qZone;
repZone: ZONE = CGStorage.qZone;

nullNode: NodeRep = [p: [0,0], start: FALSE, control: FALSE];
nullBox: Box = [0,0,0,0];

New: PUBLIC PROC[size: NAT] RETURNS[Ref] = {
self: Ref ← repZone.NEW[Rep ← [size: 0, data: NIL, box: NIL]];
[] ← GetData[self,size];
self.box ← boxZone.NEW[Box ← nullBox];
RETURN[self];
};

GetData: PROC[self: Ref, size: NAT, bump: NAT ← 0] RETURNS[Data] = INLINE {
data: Data ← self.data;
space: NATIF data=NIL THEN 0 ELSE data.space;
IF space<size THEN data ← Grow[self,MAX[size,space+bump]];
RETURN[data];
};

Grow: PROC[self: Ref, size: NAT] RETURNS[Data] = {
old: Data ← self.data;
space: NATIF old=NIL THEN 0 ELSE old.space;
new: Data ← dataZone.NEW[DataRep[size]];
FOR i: NAT IN[0..space) DO new[i] ← old[i] ENDLOOP;
FOR i: NAT IN[space..size) DO new[i] ← nodeZone.NEW[NodeRep ← nullNode] ENDLOOP;
self.data ← new;
RETURN[new];
};

Reset: PUBLIC PROC[self: Ref] = {
IF self.size>0 THEN { self.size ← 0; self.box^ ← nullBox };
};

Empty: PUBLIC PROC[self: Ref] RETURNS[BOOLEAN] = { RETURN[self.size=0] };

LastPoint: PUBLIC PROC[self: Ref] RETURNS[Vec] = {
size: NAT ← self.size;
IF size>0 THEN RETURN[self.data[size-1].p]
ELSE RETURN[[0,0]];
};

MoveTo: PUBLIC PROC[self: Ref, p: Vec] = {
Enter[self, [p: p, start: TRUE]];
};

LineTo: PUBLIC PROC[self: Ref, p: Vec] = {
IF self.size=0 THEN Enter[self, [p: [0,0], start: TRUE]];
Enter[self, [p: p]];
};

CurveTo: PUBLIC PROC[self: Ref, b1, b2, b3: Vec] = {
IF self.size=0 THEN Enter[self, [p: [0,0], start: TRUE]];
Enter[self, [p: b1, control: TRUE]];
Enter[self, [p: b2, control: TRUE]];
Enter[self, [p: b3]];
};

Rectangle: PUBLIC PROC[self: Ref, p, q: Vec] = {
Enter[self, [p: p, start: TRUE]];
Enter[self, [p: [q.x, p.y]]];
Enter[self, [p: q]];
Enter[self, [p: [p.x, q.y]]];
};

Enter: PROC[self: Ref, node: NodeRep] = {
i: NAT ← self.size;
size: NAT ← i + 1;
data: Data ← GetData[self,size,MAX[8,MIN[size/2,1024]]];
box: REF Box ← self.box;
p: Vec ← node.p;
data[i]^ ← node;
IF i=0 AND node.start THEN {
box.xmin ← box.xmax ← p.x;
box.ymin ← box.ymax ← p.y;
}
ELSE {
IF p.x<box.xmin THEN box.xmin ← p.x
ELSE IF p.x>box.xmax THEN box.xmax ← p.x;
IF p.y<box.ymin THEN box.ymin ← p.y
ELSE IF p.y>box.ymax THEN box.ymax ← p.y;
};
self.size ← size;
};

Bounds: PUBLIC PROC[self: Ref] RETURNS[Box] = { RETURN[self.box^] };
-- return a bounding box for the current path

Generate: PUBLIC PROC[self: Ref,
move: PROC[Vec], line: PROC[Vec], curve: PROC[Vec, Vec, Vec]] = {
size: NAT ← self.size;
data: Data ← self.data;
State: TYPE = {state0, state1, state2, state3};
state: State ← state0;
b1, b2: Vec;
FOR i: NAT IN[0..size) DO
node: Node ← data[i];
p: Vec ← node.p;
IF node.start AND state#state0 THEN {
IF state=state3 THEN state ← state0
ELSE ERROR Error;
};
SELECT state FROM
state0 => {
IF node.control THEN ERROR Error
ELSE { move[p]; state ← state3 };
};
state1 => {
IF node.control THEN { b2 ← p; state ← state2 }
ELSE ERROR Error;
};
state2 => {
IF node.control THEN ERROR Error
ELSE { curve[b1, b2, p]; state ← state3 };
};
state3 => {
IF node.control THEN { b1 ← p; state ← state1 }
ELSE line[p];
};
ENDCASE => ERROR Error;
ENDLOOP;
IF NOT(state=state3 OR state=state0) THEN ERROR Error;
};

MapAndFilter: PUBLIC PROC[self: Ref, map: PROC[Vec] RETURNS[Vec] ← NIL,
move: PROC[Vec], line: PROC[Vec], curve: PROC[Vec, Vec, Vec], close: PROC] = {
first: BOOLEANTRUE;
lp: Vec;
Move: PROC[v: Vec] = {
IF map#NIL THEN v ← map[v];
IF first THEN first ← FALSE ELSE close[];
move[v];
lp ← v;
};
Line: PROC[v: Vec] = {
IF map#NIL THEN v ← map[v];
IF first THEN Move[[0,0]];
IF v#lp THEN line[v];
lp ← v;
};
Curve: PROC[v1, v2, v3: Vec] = {
matches: NAT ← 0;
IF map#NIL THEN { v1 ← map[v1]; v2 ← map[v2]; v3 ← map[v3] };
IF first THEN Move[[0,0]];
IF lp=v1 THEN matches ← matches+1;
IF v1=v2 THEN matches ← matches+1;
IF v2=v3 THEN matches ← matches+1;
IF matches<2 THEN curve[v1, v2, v3]
ELSE IF matches<3 THEN line[v3];
lp ← v3;
};
Generate[self, Move, Line, Curve];
IF NOT first THEN close[];
};

Copy
: PUBLIC PROC[self: Ref] RETURNS[Ref] = {
size: NAT ← self.size;
old: Data ← self.data;
copy: Ref ← New[size];
new: Data ← copy.data;
FOR i: NAT IN[0..size) DO new[i]^ ← old[i]^ ENDLOOP;
copy.box^ ← self.box^;
copy.size ← size;
RETURN[copy];
};

Assign: PUBLIC PROC[self: Ref, copy: Ref] = {
size: NAT ← copy.size;
new: Data ← copy.data;
old: Data ← GetData[self,size];
FOR i: NAT IN[0..size) DO old[i]^ ← new[i]^ ENDLOOP;
self.box^ ← copy.box^;
self.size ← size;
};

}.