-- GFigToJaM.mesa, Cedar version
-- Last changed by M. Stone, 9-Sep-81 18:14:37

DIRECTORY
	PairList USING [Relation,NotFound,CreateRelation,AddPair,
		Right,ForAllPairs,DestroyRelation, EqualProc],
	Real USING [RoundI],
	Rope USING [Ref,Concat,Match],
	IOStream,
	Convert USING [ValueToRope],
	GriffinFig,
	Vector USING[Vec],
	Cubic USING[Bezier];

GFigToJaM: PROGRAM
	IMPORTS PairList, Rope, Convert, IOStream, Real
	EXPORTS GriffinFig =
{OPEN GriffinFig, PairList, Vector;

NoFileOpen: PUBLIC SIGNAL = CODE;
FileNotClosed: PUBLIC SIGNAL[name: Rope.Ref] = CODE;
NotAValidColorID: PUBLIC SIGNAL = CODE;
NotAValidFontID: PUBLIC SIGNAL = CODE;
NotAValidStyleID: PUBLIC SIGNAL[type: StyleType] = CODE;
NotAValidObjectID: PUBLIC SIGNAL = CODE;
NotAValidPathID: PUBLIC SIGNAL = CODE;
NoCurrentPath: PUBLIC SIGNAL = CODE;
PathAlreadyStarted: PUBLIC SIGNAL = CODE;
NotAValidClusterID: PUBLIC SIGNAL = CODE;
NoCurrentCluster: PUBLIC SIGNAL = CODE;
ClusterAlreadyStarted: PUBLIC SIGNAL = CODE;

Stream: IOStream.Handle ← NIL;
FileName: Rope.Ref ← NIL;

-- Conversion constants:  Press -> Alto
PressToAltoScaleFactor: REAL = 0.03125;
	
--files

NewFile: PUBLIC PROC[name: Rope.Ref] = {
	IF Stream#NIL THEN SIGNAL FileNotClosed[FileName];
	InitDataStructures[];
	FileName ← NIL;
	FileName ← Rope.Concat[FileName,name];
	IF ~Rope.Match["*.jam", FileName, FALSE] THEN FileName ← Rope.Concat[FileName,".jam"];
	Stream ← IOStream.CreateFileStream[FileName,overwrite];
	WName[name]; WS[" /begin"];
	WS["\n% The following definition includes a circumlocution for LineWidth"];
	WS["\n% to always have the value 0. Remove the .pop when CGStrokeImpl"];
	WS["\n% accepts non-unit line widths."];
	WS["\n(linewidth) (.pop 0 (LineWidth) .exch .store).cvx .def"];
	};

CloseFile: PUBLIC PROC = {
	wn: PROC[leftPart, rightPart: REF ANY] = {WS[NARROW[rightPart]]; WS[" "]};
	IF Stream=NIL THEN SIGNAL NoFileOpen;
--ForAllPairs gives inverse chronological order.  We need the opposite
	ReverseObjects[];
	WS["\n(refresh) ("];
	ForAllPairs[ObjectRelation,wn];
	WS[") .cvx .def\n"];
	IOStream.Close[Stream,TRUE];
	Stream ← NIL;
	};

--to call from debugger
DebugCloseFile: PUBLIC PROC = {
	IOStream.Close[Stream,TRUE];
	Stream ← NIL;
	};

Comment: PUBLIC PROC[string: Rope.Ref] = {
	WName[string];
	WS[" .pop\n"];
	};

--style information
Color: PUBLIC PROC[h,s,b: [0..255], texture: CARDINAL] RETURNS [ColorID] = {
--relation will have a pointer to this string
	id: CARDINAL ← TestAndAdd[color];
	WN[h]; WN[s]; WN[b];
	WS[" .sethsb"];
	WUN[texture];	 
	WS[" .setcolor) .cvx .def"];
	RETURN[id];
	};

Font: PUBLIC PROC[name: Rope.Ref, charOrientation: REAL] RETURNS [FontID] = {
	id: CARDINAL ← TestAndAdd[font];
	WName[name];
	WS[" .setfont"];
	WR[charOrientation];
	WS[" .charorientation) .cvx .def"];
	RETURN[id];
	};

LineStyle: PUBLIC PROC[colorID: ColorID, width: REAL] RETURNS [LineStyleID] = {
	id: CARDINAL ← TestAndAdd[linestyle];
	colorName: Rope.Ref ← CheckID[colorID,color];
	WS[colorName];
	WR[width];
	WS[" linewidth) .cvx .def"];
	RETURN[id];
	};

FillStyle: PUBLIC PROC[colorID: ColorID] RETURNS [FillStyleID] = {
	id: CARDINAL ← TestAndAdd[fillstyle];
	colorName: Rope.Ref ← CheckID[colorID,color];
	WS[colorName];
	WS[") .cvx .def"];
	RETURN[id];
	};

TextStyle: PUBLIC PROC[colorID: ColorID, fontID: FontID] RETURNS [TextStyleID] = {
	id: CARDINAL ← TestAndAdd[textstyle];
	colorName: Rope.Ref ← CheckID[colorID,color];
	fontName: Rope.Ref ← CheckID[fontID,font];
	WS[colorName];
	WS[" "];
	WS[fontName];
	WS[") .cvx .def"];
	RETURN[id];
	};


--paths
newBoundary: BOOLEAN ← FALSE;
CurrentPathID: CARDINAL ←0;
StartPath: PUBLIC PROC = {
	IF CurrentPathID#0 THEN SIGNAL PathAlreadyStarted;
	CurrentPathID ← TestAndAdd[path];
	newBoundary ← TRUE;
	};

EnterPoint: PUBLIC PROC[p: Vec] = {
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	IF CurrentPathID=0 THEN SIGNAL NoCurrentPath;
	WP[p];
	};

EnterCubic: PUBLIC PROC[c: Cubic.Bezier] = {
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	IF CurrentPathID=0 THEN SIGNAL NoCurrentPath;
	WC[c];
	};

NewBoundary: PUBLIC PROC = {
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	IF CurrentPathID=0 THEN SIGNAL NoCurrentPath;
	newBoundary ← TRUE;
	};

--To use the path in the file, need .linepath, .areapath or .closedlinepath
--following in the stream
EndPath: PUBLIC PROC RETURNS [PathID] = {
	return: PathID;
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	IF CurrentPathID=0 THEN SIGNAL NoCurrentPath;
	WS[") .cvx .def"];
	return ← CurrentPathID;
	CurrentPathID ← 0;
	RETURN[return];
	};


--objects

LineObject: PUBLIC PROC[lineStyleID: LineStyleID, pathID: PathID] RETURNS [ObjectID] = {
	id: CARDINAL ← TestAndAdd[object];
	styleName: Rope.Ref ← CheckID[lineStyleID,linestyle];
	pathName: Rope.Ref ← CheckID[pathID,path];
	WS[styleName]; WS[" "]; WS[pathName]; WS[ " LineWidth .drawpath) .cvx .def"];
	RETURN[id];
	};

AreaObject: PUBLIC PROC[fillStyleID: FillStyleID, lineStyleID: LineStyleID, pathID: PathID] RETURNS [ObjectID] = {
	id: CARDINAL ← TestAndAdd[object];
	pathName: Rope.Ref ← CheckID[pathID,path];
	styleName: Rope.Ref;
	IF fillStyleID#NullStyle THEN {
		styleName ← CheckID[fillStyleID,fillstyle];
		WS[styleName]; WS[" "]; WS[pathName]; WS[" "]; WS[".drawarea "];
		};
	IF lineStyleID#NullStyle THEN {
		styleName ← CheckID[lineStyleID,linestyle];
		WS[styleName]; WS[" "]; WS[pathName]; WS[" LineWidth .drawpath"];
		};
	WS[ ") .cvx .def"];
	RETURN[id];
	};

--this is complicated because of the left, right, center anchor
--on griffin text.  
TextObject: PUBLIC PROC[styleID: TextStyleID, text: Rope.Ref, anchorType: AnchorType, anchor: Vec, rot: REAL] RETURNS [ObjectID] = {
	id: CARDINAL ← TestAndAdd[object];
	styleName: Rope.Ref ← CheckID[styleID,textstyle];
	WS[".pushdc "]; WS[styleName];
	WP[anchor]; WS[" .moveto"];
	WR[rot]; WS[" .rotate "]; WName[text]; WS[" altotoobject"];
--these procedures should use the .textbox function to recompute
--the origin for the .drawtext
	SELECT anchorType FROM
		left => WS[" left"];
		right => WS[" right"];
		center => WS[" center"];
		ENDCASE => ERROR;
	WS[" .drawtext .popdc) .cvx .def"];
	RETURN[id];
	};


--clusters

CurrentClusterID: CARDINAL ←0;
StartCluster: PUBLIC PROC = {
	IF CurrentClusterID#0 THEN SIGNAL ClusterAlreadyStarted;
	CurrentClusterID ← TestAndAdd[cluster];
	};

EnterObject: PUBLIC PROC[objectID: ObjectID] = {
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	WS[CheckID[objectID, object]];
	WS[" "];
	};

EnterCluster: PUBLIC PROC[clusterID: ClusterID] = {
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	WS[CheckID[clusterID, cluster]];
	};

--finishes current cluster
Cluster: PUBLIC PROC RETURNS [ClusterID] = {
	return: ClusterID;
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	IF CurrentClusterID=0 THEN SIGNAL NoCurrentCluster;
	WS[") .cvx .def"];
	return ← CurrentClusterID;
	CurrentClusterID ← 0;
	RETURN[return];
	};

--private procedures

InitDataStructures: PROC = {
	DestroyRelation[ColorRelation];
	DestroyRelation[FontRelation];
	DestroyRelation[LineStyleRelation];
	DestroyRelation[FillStyleRelation];
	DestroyRelation[TextStyleRelation];
	DestroyRelation[PathRelation];
	DestroyRelation[ObjectRelation];
	DestroyRelation[ClusterRelation];
	ColorRelation ← FontRelation ← LineStyleRelation ← FillStyleRelation ← TextStyleRelation ← PathRelation ← ObjectRelation ← ClusterRelation ← NIL;
	ColorNum ← FontNum ← LStyleNum ← FStyleNum ← TStyleNum ←  PathNum ← ObjectNum ← ClusterNum ← 1;
	FileName ← NIL;
	};

--relation handling procs
RelType: TYPE =
	{color,font,linestyle,fillstyle,textstyle,path,object,cluster};
ColorRelation: Relation ← NIL;
FontRelation: Relation ← NIL;
LineStyleRelation: Relation ← NIL;
FillStyleRelation: Relation ← NIL;
TextStyleRelation: Relation ← NIL;
PathRelation: Relation ← NIL;
ObjectRelation: Relation ← NIL;
ClusterRelation: Relation ← NIL;

--so ForAllPairs in generating refresh command gives chronological order
ReverseObjects: PROC= {
	new: Relation ← CreateRelation[];
	add: PROC [leftPart, rightPart: REF ANY] = {
		AddPair[new,leftPart,rightPart]};
	IF ObjectRelation=NIL THEN RETURN;
	ForAllPairs[ObjectRelation,add];
	ObjectRelation ← new;
	};

TestAndAdd: PROC [type: RelType] RETURNS [id: CARDINAL] = {
--relation will have a pointer to this string
	name: Rope.Ref;
	refID: REF CARDINAL;
	relation: Relation ← NIL;
	IF Stream=NIL THEN SIGNAL NoFileOpen;
	SELECT type FROM
	  color => relation ← ColorRelation ← EnsureNotNil[ColorRelation];
	  font => relation ← FontRelation ← EnsureNotNil[FontRelation];
	  linestyle => relation ← LineStyleRelation ← EnsureNotNil[LineStyleRelation];
	  fillstyle => relation ← FillStyleRelation ← EnsureNotNil[FillStyleRelation];
	  textstyle => relation ← TextStyleRelation ← EnsureNotNil[TextStyleRelation];
	  path => relation ← PathRelation ← EnsureNotNil[PathRelation];
	  object => relation ← ObjectRelation ← EnsureNotNil[ObjectRelation];
	  cluster => relation ← ClusterRelation ← EnsureNotNil[ClusterRelation];
	  ENDCASE => ERROR;
	[name,id] ← NextID[type];
	refID ← NEW[CARDINAL ← id];
	AddPair[relation,refID,name];
	WS["\n"]; WName[name]; WS[" ("];
	};

EnsureNotNil: PROC [r: Relation] RETURNS [Relation] = {
  RETURN [IF r = NIL THEN CreateRelation[] ELSE r];
  };
  
CheckID: PROC [id: CARDINAL, type: RelType] RETURNS [Rope.Ref] = {
	name: Rope.Ref;
	refID: REF CARDINAL ← NEW[CARDINAL ← id];
	equalCardinal: PairList.EqualProc = {
		IF ISTYPE[right, REF CARDINAL]
			THEN IF NARROW[right,REF CARDINAL]↑ = NARROW[left,REF CARDINAL]↑
				THEN RETURN[TRUE]
		ELSE RETURN[FALSE];
		};
	SELECT type FROM
		color =>
			{ENABLE NotFound => {SIGNAL NotAValidColorID; CONTINUE};
			name ← NARROW[Right[ColorRelation,equalCardinal,refID]]};
		font =>
			{ENABLE NotFound => {SIGNAL NotAValidFontID; CONTINUE};
			name ← NARROW[Right[FontRelation,equalCardinal,refID]]};
		linestyle =>
			{ENABLE NotFound => {SIGNAL NotAValidStyleID[line]; CONTINUE};
			name ← NARROW[Right[LineStyleRelation,equalCardinal,refID]]};
		fillstyle =>
			{ENABLE NotFound => {SIGNAL NotAValidStyleID[fill]; CONTINUE};
			name ← NARROW[Right[FillStyleRelation,equalCardinal,refID]]};
		textstyle =>
			{ENABLE NotFound => {SIGNAL NotAValidStyleID[text]; CONTINUE};
			name ← NARROW[Right[TextStyleRelation,equalCardinal,refID]]};
		path =>
			{ENABLE NotFound => {SIGNAL NotAValidPathID; CONTINUE};
			name ← NARROW[Right[PathRelation,equalCardinal,refID]]};
		object =>
			{ENABLE NotFound => {SIGNAL NotAValidObjectID; CONTINUE};
			name ← NARROW[Right[ObjectRelation,equalCardinal,refID]]};
		cluster =>
			{ENABLE NotFound => {SIGNAL NotAValidClusterID; CONTINUE};
			name ← NARROW[Right[ClusterRelation,equalCardinal,refID]]};
		ENDCASE => ERROR;
	RETURN[name];
	};

--generate id's.  Keep it simple for now

ColorName: Rope.Ref ← "c";
FontName: Rope.Ref ← "f";
LineStyleName: Rope.Ref ← "ls";
FillStyleName: Rope.Ref ← "fs";
TextStyleName: Rope.Ref ← "ts";
PathName: Rope.Ref ← "p";
ObjectName: Rope.Ref ← "obj";
ClusterName: Rope.Ref ← "clu";
ColorNum,FontNum,LStyleNum,FStyleNum,TStyleNum: CARDINAL ← 1;
PathNum,ObjectNum,ClusterNum: CARDINAL ← 1;
NextID: PROC [type: RelType] RETURNS [name: Rope.Ref, id: CARDINAL] = {
	typeName: Rope.Ref;
	SELECT type FROM
		color => {id ← ColorNum; ColorNum ← ColorNum+1};
		font => {id ← FontNum; FontNum ← FontNum+1};
		linestyle => {id ← LStyleNum; LStyleNum ← LStyleNum+1};
		fillstyle => {id ← FStyleNum; FStyleNum ← FStyleNum+1};
		textstyle => {id ← TStyleNum; TStyleNum ← TStyleNum+1};
		path => {id ← PathNum; PathNum ← PathNum+1};
		object => {id ← ObjectNum; ObjectNum ← ObjectNum+1};
		cluster => {id ← ClusterNum; ClusterNum ← ClusterNum+1};
		ENDCASE => ERROR;
	typeName ← (SELECT type FROM
		color => ColorName,
		font => FontName,
		linestyle => LineStyleName,
		fillstyle => FillStyleName,
		textstyle => TextStyleName,
		path => PathName,
		object => ObjectName,
		cluster => ClusterName,
		ENDCASE => ERROR);
	name ← typeName;
	name ← Rope.Concat[name,Convert.ValueToRope[[signed[id,10]]]];
	};

--file writing procs

WS: PROC [s: Rope.Ref] = {
	IOStream.PutRope[Stream,s];
	};

WName: PROC [s: Rope.Ref] = {OPEN IOStream;
	Put[Stream,char['(], rope[s], char[')]];
	};

--write it unsigned, base 8
WUN: PROC [n: CARDINAL] = {OPEN IOStream;
	PutF[Stream," %bB",card[n]];
	};

--write it signed, base 10
WN: PROC [n: INTEGER] = {OPEN IOStream;
	Put[Stream,char[SP],int[n] ];
	};

WR: PROC [r: REAL] = {OPEN IOStream;
	Put[Stream,char[SP],int[Real.RoundI[r*PressToAltoScaleFactor]] ];
	};

WP: PROC [p: Vec] = {
	WR[p.x];
	WR[p.y];
	IF newBoundary THEN WS[" .moveto\n"] 
	ELSE WS[" .lineto\n"];
	newBoundary ← FALSE;
	};


WriteBB: PROC [ll,ur: Vec] = {
	WR[ll.x];
	WR[ll.y];
	WR[ur.x];
	WR[ur.y];
	WS[" bb"];
	};

WC: PROC [c: Cubic.Bezier] = {
	IF newBoundary THEN WP[c.b0];
	WR[c.b1.x];
	WR[c.b1.y];
	WR[c.b2.x];
	WR[c.b2.y];
	WR[c.b3.x];
	WR[c.b3.y];
	WS[ " .curveto\n"];
	};

}.