-- 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]0 THEN 1 ELSE -1) }; IF ABS[cost]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]; }; }; }.(670)