-- GriffinToTAImpl.mesa
-- Read Griffin files and convert to Tioga Artwork files
-- borrowed liberally from ReadGriffinImpl.mesa and GriffinFile.mesa
-- Rick Beach, April 15, 1983 11:34 am
-- Maureen Stone May 5, 1983 4:52 pm

DIRECTORY
 Cubic USING [Bezier, CoeffsToBezier, Coeffs],
 Environment USING [charsPerPage, Byte],
 GFileFormatDefs,
 GriffinToTA,
 GriffinToTANodes,
 GriffinToTAPrivate,
 GriffinToJaM,
 Inline USING [LongCOPY, BytePair],
 Graphics USING [Path, MoveTo, LineTo, CurveTo, NewPath],
 CGPath USING [Empty],
IO,
 FileIO,
 PairList,
 RealConvert USING [Mesa5ToIeee],
 Rope,
 SplineDefs,
 StyleDefs,
 Vector USING [Vec];


GriffinToTAImpl: PROGRAM
IMPORTS Cubic, GriffinToTANodes, GriffinToTAPrivate, Inline, IO, PairList, RealConvert, Rope, SplineDefs, FileIO, Graphics, CGPath, GriffinToJaM
EXPORTS GriffinToTA = {

ROPE: TYPE = Rope.ROPE;

GriffinFileError: PUBLIC SIGNAL[why: ROPE] = CODE;

ViewType: TYPE = GriffinToTA.ViewType;
FigureNumber: TYPE = CARDINAL[1 .. 100];
FontRef: TYPE = GriffinToTA.FontRef;
StyleRef: TYPE = GriffinToTA.StyleRef;
ObjectRef: TYPE = GriffinToTA.ObjectRef;
PathRef: TYPE = GriffinToTA.PathRef;
CaptionRef: TYPE = GriffinToTA.CaptionRef;
CaptionRec: TYPE = GriffinToTA.CaptionRec;
ColorRef: TYPE = GriffinToTA.ColorRef;
PathStyleRef: TYPE = GriffinToTA.PathStyleRef;
PathStyleRec: TYPE = GriffinToTA.PathStyleRec;
CaptionStyleRef: TYPE = GriffinToTA.CaptionStyleRef;
CaptionStyleRec: TYPE = GriffinToTA.CaptionStyleRec;

IntegerVec: TYPE = RECORD[
 x, y: INTEGER];

-- now our own data structures
fontMap: PUBLIC PairList.Relation; -- <fontNumber, FontRef>
styleMap: PUBLIC PairList.Relation; -- <styleNumber, StyleRef>
objectMap: PUBLIC PairList.Relation; -- <objectNumber, ObjectRef>
pathMap: PUBLIC PairList.Relation; -- <objectNumber, PathRef>
captionMap: PUBLIC PairList.Relation; -- <objectNumber, CaptionRef>
clusterMap: PUBLIC PairList.Relation; -- <clusterNumber, objectNumber>
objectsReversed: BOOLEANFALSE;
firstCluster, lastCluster: PUBLIC CARDINAL ← 0;
colors: PUBLIC ColorRef ← NIL;
colorNumber: PUBLIC CARDINAL ← 0;
pathStyles: PUBLIC PathStyleRef ← NIL;
pathStyleNumber: PUBLIC CARDINAL ← 0;
captionStyles: PUBLIC CaptionStyleRef ← NIL;
captionStyleNumber: PUBLIC CARDINAL ← 0;

objectNumber: CARDINAL ← 1; -- count of objects in this view
inThisView: BOOLEANFALSE;
diskHandle: IO.STREAM;
maxCPairCount: CARDINAL = 2000; -- just for safety
majorVersion: Environment.Byte = 1;
minorVersion: Environment.Byte = 4;
realConvert: BOOLEANFALSE; -- convert reals if older than Griffin version 1.4


ConvertFile: PUBLIC PROCEDURE[fileName: ROPE, view: ViewType] = {
IF view = both THEN view ← main;
 fileName ← FixFileName[fileName, ".Griffin"];
 diskHandle ← FileIO.Open[fileName ! FileIO.OpenFailed => CHECKED {GOTO BadCreate}];
 InitDataStructures[];
 ReadFigure[1, view];
IF ~objectsReversed THEN {
  clusterMap ← ReverseRelation[clusterMap];
  objectsReversed ← TRUE};
 GriffinToTANodes.PutClusters[FixFileName[fileName, ".Artwork"]];
 GriffinToTANodes.PutStyles[FixFileName[fileName, ".Style"]];
 DestroyDataStructures[];
IO.Close[diskHandle];
EXITS BadCreate => {SIGNAL GriffinFileError[Rope.Cat["file ", fileName, " not found"]]};
 };

ConvertFileToJaM: PUBLIC PROCEDURE[fileName: ROPE, view: ViewType] = {
IF view = both THEN view ← main;
 fileName ← FixFileName[fileName, ".Griffin"];
 diskHandle ← FileIO.Open[fileName ! FileIO.OpenFailed => CHECKED {GOTO BadCreate}];
 InitDataStructures[];
 ReadFigure[1, view];
IF ~objectsReversed THEN {
  clusterMap ← ReverseRelation[clusterMap];
  objectsReversed ← TRUE};
 GriffinToJaM.PutClusters[FixFileName[fileName, ".JaM"]];
 DestroyDataStructures[];
IO.Close[diskHandle];
EXITS BadCreate => {SIGNAL GriffinFileError[Rope.Cat["file ", fileName, " not found"]]};
 };

InitDataStructures: PROCEDURE = {
 colors ← GriffinToTAPrivate.InitColors[];
 fontMap ← PairList.CreateRelation[];
 styleMap ← PairList.CreateRelation[];
 objectMap ← PairList.CreateRelation[];
 pathMap ← PairList.CreateRelation[];
 captionMap ← PairList.CreateRelation[];
 clusterMap ← PairList.CreateRelation[];
 objectsReversed ← FALSE;
 firstCluster ← 0;
 lastCluster ← 0;
 pathStyles ← NIL;
 pathStyleNumber ← 0;
 captionStyles ← NIL;
 captionStyleNumber ← 0;
 };

DestroyDataStructures: PROCEDURE = {
 PairList.DestroyRelation[fontMap];
 PairList.DestroyRelation[styleMap];
 PairList.DestroyRelation[objectMap];
 PairList.DestroyRelation[pathMap];
 PairList.DestroyRelation[captionMap];
 PairList.DestroyRelation[clusterMap];
 pathStyles ← NIL;
 captionStyles ← NIL;
 colors ← NIL;
 };

ReadFigure: PROCEDURE[fignum: FigureNumber, view: ViewType] = {
 fileheader: GFileFormatDefs.GFileHeader;
 hcontrol: GFileFormatDefs.GFileHardcopyController;
 dcontrol: GFileFormatDefs.GFileDisplayController;
 figurename: GFileFormatDefs.GFileFigureName;
 fontCount, styleCount, objectCount, f, s: CARDINAL;

 ReadHeader[@fileheader];
 MoveToSector[fileheader.figure[fignum]]; -- position to the right sector
 ReadStructure[@figurename, GFileFormatDefs.lGFileFigureName];
 ReadStructure[@hcontrol, GFileFormatDefs.lGFileHardcopyController];
 ReadStructure[@dcontrol, GFileFormatDefs.lGFileDisplayController];
--control pairs not implemented
IF dcontrol.numcontrolpairs > maxCPairCount THEN SIGNAL GriffinFileError["too many control pairs"];
 fontCount ← ReadWord[];
FOR f IN [1 .. fontCount] DO
  ReadFont[f, fontMap];
  ENDLOOP;
 styleCount ← ReadWord[];
FOR s IN [1 .. styleCount] DO
  ReadStyle[s, styleMap];
  ENDLOOP;
 objectCount ← ReadWord[];
THROUGH [1 .. objectCount] DO
  ReadObject[objectMap, view];
  ENDLOOP;
 };

ReadHeader: PROCEDURE[h: POINTER TO GFileFormatDefs.GFileHeader] = {
IO.SetIndex[diskHandle,0];
 ReadStructure[h, GFileFormatDefs.lGFileHeader];
 realConvert ← FALSE;
IF h.majversion#majorVersion OR h.minversion#minorVersion THEN {
  --version 1.4 is the first with the Ieee floating point format.
  --we need to set up to convert the reals for older versions
  IF h.majversion=1 AND h.minversion IN [0..3]
   THEN realConvert ← TRUE;
  };
 };

ReadFont: PROCEDURE[fontNumber: CARDINAL, fontMap: PairList.Relation] = {
 font: FontRef ← NEW[GFileFormatDefs.GFileFont];
 refFontId: REF CARDINALNEW[CARDINAL ← fontNumber];
 ReadStructure[LOOPHOLE[font], GFileFormatDefs.lGFileFont];
 PairList.AddPair[fontMap, refFontId, font];
 };

ReadStyle: PROCEDURE[styleNumber: CARDINAL, styleMap: PairList.Relation] = {
 style: StyleRef ← NEW[GFileFormatDefs.GFileStyle];
 refStyleId: REF CARDINALNEW[CARDINAL ← styleNumber];
 ReadStructure[LOOPHOLE[style], GFileFormatDefs.lGFileStyle];
 style.thickness ← GetReal[style.thickness];
 PairList.AddPair[styleMap, refStyleId, style];
 };

ReadObject: PROCEDURE[objectMap: PairList.Relation, view: ViewType] = {
 fileObjectRef: ObjectRef ← NEW[GFileFormatDefs.GFileObject];
 topScreenCoord: INTEGER = 808;
 origin: IntegerVec;
 path: PathRef ← Graphics.NewPath[];
 refCaption: CaptionRef ← NIL;
 ReadStructure[LOOPHOLE[fileObjectRef], GFileFormatDefs.lGFileObject];
 inThisView ← ((view=main) AND (~fileObjectRef^.hidewindow)) OR ((view=alternate) AND (fileObjectRef^.hidewindow));
 origin.x ← fileObjectRef^.bleft;
 origin.y ← topScreenCoord - fileObjectRef^.bbottom;
SELECT fileObjectRef^.objtype FROM
  GFileFormatDefs.typeCurveObject,
  GFileFormatDefs.typeAreaObject => {
   nlinks: CARDINAL ← ReadWord[];
   splinetype: SplineDefs.SplineType ← SELECT fileObjectRef^.splinetype FROM
    GFileFormatDefs.typeNUMSpline => naturalUM,
    GFileFormatDefs.typeCUMSpline => cyclicUM,
    GFileFormatDefs.typeNALSpline => naturalAL,
    GFileFormatDefs.typeCALSpline => cyclicAL,
    GFileFormatDefs.typeBEZSpline => bezier,
    GFileFormatDefs.typeBSISpline => bsplineInterp,
    GFileFormatDefs.typeBSpline => bspline,
    GFileFormatDefs.typeCRSpline => crspline,
    ENDCASE => bspline;
   SELECT fileObjectRef^.trajtype FROM
    GFileFormatDefs.typeLinkedTraj =>
     AppendPathLinks[nlinks, splinetype, FALSE, origin, path];
    GFileFormatDefs.typeCSTraj => {
     IF nlinks#1 THEN SIGNAL GriffinFileError["imposible cyclic spline"];
     splinetype ← (SELECT splinetype FROM
      naturalUM => cyclicUM,
      naturalAL => cyclicAL,
      ENDCASE => splinetype);
     AppendPathLinks[1, splinetype, TRUE, origin, path];
     };
    ENDCASE => SIGNAL GriffinFileError["invalid trajectory"];
    };
  GFileFormatDefs.typeCaptionObject => {
   fcaptiontrailer: GFileFormatDefs.GFileCaptionTrailer;
   captionText: ROPE;
   pt: Vector.Vec;
   ReadStructure[@fcaptiontrailer, GFileFormatDefs.lGFileCaptionTrailer];
   pt.x ← GetReal[fcaptiontrailer.xanchor];
   pt.y ← GetReal[fcaptiontrailer.yanchor];
   pt.x ← GriffinToTAPrivate.ScalePressToScreenCoord[pt.x];
   pt.y ← GriffinToTAPrivate.ScalePressToScreenCoord[pt.y];
   captionText ← ReadString[];
   refCaption ← NEW[CaptionRec ← [pt, captionText]];
   };
  ENDCASE => SIGNAL GriffinFileError["bad object"];
IF inThisView THEN {
  refObjectId: REF CARDINALNEW[CARDINAL ← objectNumber];
  refClusterId: REF CARDINALNEW[CARDINAL ← fileObjectRef^.cluster];
  PairList.AddPair[objectMap, refObjectId, fileObjectRef];
  objectNumber ← objectNumber+1;
  SELECT fileObjectRef^.objtype FROM
   GFileFormatDefs.typeCurveObject,
   GFileFormatDefs.typeAreaObject =>
    PairList.AddPair[pathMap, refObjectId, path];
   GFileFormatDefs.typeCaptionObject =>
    PairList.AddPair[captionMap, refObjectId, refCaption];
   ENDCASE;
  firstCluster ← MIN[firstCluster, fileObjectRef^.cluster];
  lastCluster ← MAX[lastCluster, fileObjectRef^.cluster];
  PairList.AddPair[clusterMap, refClusterId, refObjectId];
  };
 };

AppendPathLinks: PROCEDURE[nlinks: CARDINAL, splinetype: SplineDefs.SplineType, cyclic: BOOLEAN, origin: IntegerVec, path: PathRef] = {
THROUGH [1 .. nlinks] DO
  AddLink[splinetype, cyclic, origin, path];
  ENDLOOP;
 };

AddLink: PROCEDURE[splinetype: SplineDefs.SplineType, cyclic: BOOLEAN, origin: IntegerVec, path: PathRef] = {
 knotword: GFileFormatDefs.GFileKnotWord;
 knots: SplineDefs.KnotSequence;
 coeffs: SplineDefs.CoeffsSequence;
CoeffsLength: PROCEDURE [coeffs: SplineDefs.CoeffsSequence] RETURNS [CARDINAL] = {
RETURN[IF coeffs=NIL THEN 0 ELSE coeffs.length]
  };
 num, j: CARDINAL;
 fpoint: GFileFormatDefs.GFilePoint;
 ReadStructure[@knotword, GFileFormatDefs.lGFileKnotWord];
 num ← knotword.knotcount;
IF num=0 THEN SIGNAL GriffinFileError["bad link"];
SELECT knotword.knottype FROM
  GFileFormatDefs.typeFD3Knot => {
   knots ← NEW[SplineDefs.KnotSequenceRec[num]];
   FOR j IN [0 .. num) DO
    ReadStructure[@fpoint, GFileFormatDefs.lGFilePoint];
    knots[j] ← SplineDefs.FPCoords[GetReal[fpoint.x], GetReal[fpoint.y]];
    ENDLOOP;
   IF cyclic THEN knots ← MakeCyclicKnots[knots,splinetype];
   coeffs ← SplineDefs.MakeSpline[knots,splinetype];
   FOR j IN [0..CoeffsLength[coeffs]) DO
    c: Cubic.Coeffs;
    c.c0 ← [coeffs[j].t0[SplineDefs.X],coeffs[j].t0[SplineDefs.Y]];
    c.c1 ← [coeffs[j].t1[SplineDefs.X],coeffs[j].t1[SplineDefs.Y]];
    c.c2 ← [coeffs[j].t2[SplineDefs.X],coeffs[j].t2[SplineDefs.Y]];
    c.c3 ← [coeffs[j].t3[SplineDefs.X],coeffs[j].t3[SplineDefs.Y]];
    IF inThisView THEN
     AppendCubic[Cubic.CoeffsToBezier[c], origin, path];
    ENDLOOP;
   };
  GFileFormatDefs.typeFD0Knot,
  GFileFormatDefs.typeFD1Knot,
  GFileFormatDefs.typeFD2Knot =>
   FOR j IN [0 .. num) DO
    ReadStructure[@fpoint, GFileFormatDefs.lGFilePoint];
    IF inThisView THEN
     AppendPoint[[GetReal[fpoint.x], GetReal[fpoint.y]], origin, path];
    ENDLOOP;
  ENDCASE => SIGNAL GriffinFileError["bad link"];
 };

MakeCyclicKnots: PROCEDURE[knots: SplineDefs.KnotSequence, type: SplineDefs.SplineType] RETURNS[SplineDefs.KnotSequence] = {
 cycknots: SplineDefs.KnotSequence;
 numknots: INTEGERIF knots=NIL THEN 0 ELSE knots.length;
 newLength,i: INTEGER;
IF numknots <= 0 THEN RETURN[NIL];
SELECT type FROM
  cyclicUM,
  cyclicAL,
  bezier=> newLength ← numknots+1;
  bspline,
  crspline=> newLength ← numknots+3;
  ENDCASE;
 cycknots ← NEW[SplineDefs.KnotSequenceRec[newLength]];
FOR i IN [0..numknots) DO
  cycknots[i] ← knots[i];
  ENDLOOP;
 cycknots[numknots] ← cycknots[0];
SELECT type FROM
  naturalUM => type ← cyclicUM;
  naturalAL => type ← cyclicAL;
  bspline => {
   cycknots[numknots+1] ← cycknots[1];
   cycknots[numknots+2] ← cycknots[2];
   };
  crspline => {
   cycknots[numknots+1] ← cycknots[1];
   cycknots[numknots+2] ← cycknots[2];
   };
  ENDCASE;
RETURN[cycknots];
 };

AppendCubic: PROCEDURE[c: Cubic.Bezier, origin: IntegerVec, path: PathRef] = {
 b0, b1, b2, b3: Vector.Vec;
 b0 ← XFormPt[c.b0,origin];
 b1 ← XFormPt[c.b1,origin];
 b2 ← XFormPt[c.b2,origin];
 b3 ← XFormPt[c.b3,origin];
IF CGPath.Empty[path] THEN Graphics.MoveTo[path,b0.x,b0.y];
 Graphics.CurveTo[path,b1.x,b1.y,b2.x,b2.y,b3.x,b3.y];
 };

AppendPoint: PROCEDURE[p: Vector.Vec, origin: IntegerVec, path: PathRef] = {
 v: Vector.Vec ← XFormPt[p,origin];
IF CGPath.Empty[path] THEN Graphics.MoveTo[path,v.x,v.y]
 ELSE Graphics.LineTo[path,v.x,v.y];
 };

XFormPt: PROC [p: Vector.Vec, origin: IntegerVec] RETURNS [Vector.Vec] = {
 RETURN[[GriffinToTAPrivate.ScalePressToScreenCoord[p.x]-origin.x,
   GriffinToTAPrivate.ScalePressToScreenCoord[p.y]-origin.y]];
 };

--so ForAllPairs in generating objects does so in chronological order

ReverseRelation: PROCEDURE[map: PairList.Relation] RETURNS[PairList.Relation] = {
 new: PairList.Relation ← PairList.CreateRelation[];
 add: PROC[leftPart, rightPart: REF ANY] = {
  PairList.AddPair[new, leftPart, rightPart]};
IF map=NIL THEN RETURN[map];
 PairList.ForAllPairs[map, add];
 map ← new;
 PairList.DestroyRelation[new];
RETURN[map];
 };


-- private low-level disk access procedures

ReadWord: PROCEDURE RETURNS[CARDINAL] = {
 high, low: CHARACTER;
 word: Inline.BytePair;
 high ← IO.GetChar[diskHandle];
 low ← IO.GetChar[diskHandle];
 word.high ← LOOPHOLE[high]; word.low ← LOOPHOLE[low];
RETURN[LOOPHOLE[word]];
 };

ReadString: PROCEDURE RETURNS[r: ROPE] = {
 t: REF TEXT;
 length: CARDINAL;
 length ← LOOPHOLE[IO.GetChar[diskHandle], CARDINAL];
 t ← NEW[TEXT[length]];
IF IO.GetBlock[diskHandle,t,0,length] # length
  THEN SIGNAL GriffinFileError["Possible disk problem"];
 r ← Rope.FromRefText[t];
--always read an even number of bytes: string length + string chars MOD 2 = 0
IF (length+1) MOD 2 # 0 THEN [] ← IO.GetChar[diskHandle];
 };

MoveToSector: PROCEDURE[si: GFileFormatDefs.SectorIndex] = {
IO.SetIndex[diskHandle, INTEGER[si*Environment.charsPerPage]];
 };

ReadStructure: PROCEDURE[p: LONG POINTER, l: CARDINAL] = {
 from: LONG POINTER;
 b: REF TEXTNEW[TEXT[l*2]];
IF IO.GetBlock[diskHandle, b, 0, 2*l] # 2*l
  THEN SIGNAL GriffinFileError["Possible disk problem"];
 from ← LOOPHOLE[b,LONG POINTER]+2; --start of bytes
 Inline.LongCOPY[from,l,p];
 };

GetReal: PROCEDURE[r: LONG UNSPECIFIED] RETURNS[out: REAL] = {
IF realConvert THEN RETURN[RealConvert.Mesa5ToIeee[r]] ELSE RETURN[r];
 };

FixFileName: PROCEDURE[oldname, extension: ROPE] RETURNS [newname:ROPE] = {
 dotPosition: INTEGER ← Rope.Find[oldname, "."];
IF dotPosition < 0 THEN
  newname ← Rope.Cat[oldname, extension]
ELSE
  newname ← Rope.Cat[Rope.Substr[oldname, 0, dotPosition], extension];
 };

}.