unit TTProfile; {$mode objfpc}{$H+} interface uses {$IFDEF VIRTUALPASCAL} Use32, {$ENDIF} TTTypes, SysUtils; const PoolMaxCapacity = 512*1024 div 4; { = 512 Ko } PoolIdleCapacity = 64*1024 div 4; { = 64 Ko } type TCurveDirection = ( GoingUnknown, GoingUp, GoingDown, GoingHoriz ); TPoint = record x, y : long; end; TBezierStack = packed array[0..64*2] of TPoint; PBezierStack = ^TBezierStack; TProfile = class; TProfileCollection = class; { TRenderPool } TRenderPool = class protected Precision, PrecisionHalf, BezierPrecision: integer; BoundsMinY,BoundsMaxY: integer; scaleShift: integer; function Bezier_Down(miny, maxy: Long): boolean; function Bezier_State(y1, y2, y3: TT_F26Dot6): TCurveDirection; function Bezier_To(x, y, Cx, Cy: LongInt): boolean; function Bezier_Up(miny, maxy: Long): boolean; function CEILING(x: Long): Long; function DecomposeCurve(first, last: Int; flipped: Boolean; points: TT_Points; flags: PByte): boolean; function FLOOR(x: Long): Long; function FRAC(x: Long): Int; function GetCapacity: integer; function Line_Down(x1, y1, x2, y2, miny, maxy: LongInt): boolean; function Line_To(x, y: LongInt): boolean; function Line_Up(x1, y1, x2, y2, miny, maxy: LongInt): boolean; procedure Move_To(x, y: Longint); procedure PushBezier(x1, y1, x2, y2, x3, y3: LongInt); function SCALED(x: Long): Long; procedure Split_Bezier(base: PBezierStack); function TRUNC(x: Long): Long; function RequireCapacity(count: integer): boolean; procedure PushValue(AValue: Long); inline; property Capacity: integer read GetCapacity; public Joint : Boolean; (* Indicates that the last arc stopped sharp *) (* on a scan-line. Important to get rid of *) (* doublets *) Fresh : Boolean; (* Indicates a new Profile which 'Start' field *) (* must be set *) cProfile : TProfile; (* current Profile *) ProfileColl: TProfileCollection; LastX, (* Last and extrema coordinates during *) LastY : longint; (* rendering *) CurveDir : TCurveDirection; (* State of current trace *) Arcs : TBezierStack; CurArc : Int; (* stack's top *) data: packed array of Long; position: integer; constructor Create(APrecision, ABezierPrecision: integer); destructor Destroy; override; procedure SetPrecision(APrecision, ABezierPrecision: integer); procedure SetBounds(MinY,MaxY: integer); procedure SetScaleShift(value: integer); procedure Clear; procedure ReduceCapacity; function Convert_Glyph(flipped: Boolean; points: TT_Points; flags: PByte; Outs: TT_PConStarts; nContours : Int): boolean; end; TProfile = class Pool : TRenderPool; Flow : Int; (* ascending or descending Profile *) Height : Int; (* Profile's height in scanlines *) Start : Int; (* Profile's starting scanline *) Offset : integer; (* offset of first coordinate in *) (* render pool *) X : Longint; (* current coordinate during sweep *) nextInContour : TProfile; (* next Profile of the same contour*) nextInColl: TProfile; (* next Profile in collection *) nextInList, prevInList : TProfile; (* in linked list *) constructor Create(APool: TRenderPool; ADirection: TCurveDirection); end; { TProfileCollection } TProfileCollection = class protected procedure Remove_Profile; public Pool : TRenderPool; prevProfile : TProfile; fProfile : TProfile; (* head of Profiles linked list *) gProfile : TProfile; (* last Profile in case of impact *) nProfs : Int; (* current number of Profiles *) constructor Create(APool: TRenderPool); procedure Clear; procedure New_Profile(ADirection: TCurveDirection); procedure End_Profile; destructor Destroy; override; end; procedure ProfileList_Init( out L : TProfile ); procedure ProfileList_InsertFirstElement( var L: TProfile; Element: TProfile); procedure ProfileList_AppendToList( var ListOrElement: TProfile; ElementOrListToAppend: TProfile ); procedure ProfileList_Remove( var List: TProfile; ElementToRemove: TProfile); procedure ProfileList_SortByX( var L: TProfile); procedure ProfileList_SortByStart( var L: TProfile); function ProfileList_Count( L: TProfile): integer; procedure ProfileList_Split( List : TProfile; out Part1, Part2: TProfile); implementation uses TTCalc, { used for MulDiv } TTError ; (************************************************) (* *) (* ProfileList_Init *) (* *) (* Init an empty profile linked list. *) (* *) (************************************************) procedure ProfileList_Init( out L : TProfile ); begin L := nil; end; (************************************************) (* *) (* ProfileList_Append : *) (* *) (* Inserts a new Profile in a linked list. *) (* *) (************************************************) procedure ProfileList_InsertFirstElement(var L: TProfile; Element: TProfile); begin Element.nextInList := L; if L <> nil then L.prevInList := Element; L := Element; end; procedure ProfileList_AppendToList( var ListOrElement: TProfile; ElementOrListToAppend: TProfile ); var cur,next: TProfile; begin if ElementOrListToAppend = nil then exit; if ListOrElement = nil then ListOrElement := ElementOrListToAppend else begin cur := ListOrElement; next := cur.nextInList; while next <> nil do begin cur := next; next := cur.nextInList; end; ElementOrListToAppend.prevInList := cur; cur.nextInList := ElementOrListToAppend; end; end; (************************************************) (* *) (* ProfileList_Remove : *) (* *) (* Removes an old Profile from a linked list *) (* *) (************************************************) procedure ProfileList_Remove( var List: TProfile; ElementToRemove: TProfile); var prev,next: TProfile; begin if ElementToRemove = nil then exit; prev := ElementToRemove.prevInList; next := ElementToRemove.nextInList; if prev <> nil then begin prev.nextInList := next; ElementToRemove.prevInList := nil; end; if next <> nil then begin next.prevInList := prev; ElementToRemove.nextInList := nil; end; if ElementToRemove = List then List := next; end; procedure ProfileList_SortByX(var L: TProfile); function Merge(P1,P2: TProfile): TProfile; var cur: TProfile; begin if P1 = nil then result := P2 else if P2 = nil then result := P1 else begin cur := nil; while (P1 <> nil) and (P2 <> nil) do begin if P1.X <= P2.X then begin if cur = nil then begin result := P1; result.prevInList := nil; cur := result; end else begin cur.nextInList := P1; P1.prevInList := cur; cur := P1; end; P1 := P1.nextInList; end else begin if cur = nil then begin result := P2; result.prevInList := nil; cur := result; end else begin cur.nextInList := P2; P2.prevInList := cur; cur := P2; end; P2 := P2.nextInList; end; end; //append end of list if P1 <> nil then begin cur.nextInList := P1; P1.prevInList := cur; end else if P2 <> nil then begin cur.nextInList := P2; P2.prevInList := cur; end; end; end; var L1,L2: TProfile; begin if (L = nil) or (L.nextInList = nil) then exit; ProfileList_Split(L, L1,L2); ProfileList_SortByX(L1); ProfileList_SortByX(L2); L := Merge(L1, L2); end; procedure ProfileList_SortByStart(var L: TProfile); function Merge(P1,P2: TProfile): TProfile; var cur: TProfile; begin if P1 = nil then result := P2 else if P2 = nil then result := P1 else begin cur := nil; while (P1 <> nil) and (P2 <> nil) do begin if P1.Start <= P2.Start then begin if cur = nil then begin result := P1; result.prevInList := nil; cur := result; end else begin cur.nextInList := P1; P1.prevInList := cur; cur := P1; end; P1 := P1.nextInList; end else begin if cur = nil then begin result := P2; result.prevInList := nil; cur := result; end else begin cur.nextInList := P2; P2.prevInList := cur; cur := P2; end; P2 := P2.nextInList; end; end; //append end of list if P1 <> nil then begin cur.nextInList := P1; P1.prevInList := cur; end else if P2 <> nil then begin cur.nextInList := P2; P2.prevInList := cur; end; end; end; var L1,L2: TProfile; begin if (L = nil) or (L.nextInList = nil) then exit; ProfileList_Split(L, L1,L2); ProfileList_SortByStart(L1); ProfileList_SortByStart(L2); L := Merge(L1, L2); end; function ProfileList_Count(L: TProfile): integer; begin result := 0; While L <> nil do begin inc(result); L := L.nextInList; end; end; procedure ProfileList_Split(List: TProfile; out Part1, Part2: TProfile); var n,m: integer; p: TProfile; begin if List = nil then begin Part1 := nil; Part2 := nil; exit; end; n := 0; p := List; while p <> nil do begin inc(n); p := p.nextInList; end; m := (n+1) shr 1; p := List; while m > 0 do begin p := p.nextInList; dec(m); end; Part1 := List; Part2 := p; if p <> nil then begin if p.prevInList = nil then raise Exception.Create('Incoherent list'); p.prevInList.nextInList := nil; p.prevInList := nil; end; end; {* integer computation *} function TRenderPool.TRUNC( x : Long ) : Long; inline; begin Trunc := (x and -Precision) div Precision; end; function TRenderPool.FRAC( x : Long ) : Int; inline; begin Frac := x and (Precision-1); end; function TRenderPool.FLOOR( x : Long ) : Long; inline; begin Floor := x and -Precision; end; function TRenderPool.CEILING( x : Long ) : Long; inline; begin Ceiling := (x + Precision-1) and -Precision; end; function TRenderPool.SCALED( x : Long ) : Long; inline; begin SCALED := (x shl scaleShift) - precisionHalf; end; { TProfileCollection } constructor TProfileCollection.Create(APool: TRenderPool); begin Pool := APool; nProfs:= 0; prevProfile := nil; Pool.cProfile := nil; fProfile := nil; gProfile := nil; Pool.ProfileColl := self; end; procedure TProfileCollection.Clear; var p,p2: TProfile; begin p := fProfile; while p <> nil do begin p2 := p.nextInColl; p.Free; p := p2; end; nProfs:= 0; prevProfile := nil; Pool.cProfile := nil; fProfile := nil; gProfile := nil; end; procedure TProfileCollection.New_Profile(ADirection: TCurveDirection); var nProfile: TProfile; begin nProfile := TProfile.Create(Pool, ADirection); if fProfile = nil then fProfile := nProfile; if gProfile = nil then gProfile := nProfile; if Pool.cProfile <> nil then begin Pool.cProfile.nextInColl := nProfile; Pool.cProfile.nextInContour := nProfile; //by default same contour end; prevProfile := Pool.cProfile; Pool.cProfile := nProfile; Pool.CurveDir := ADirection; Pool.Fresh := True; Pool.Joint := False; inc(nProfs); end; procedure TProfileCollection.Remove_Profile; begin if prevProfile <> nil then begin prevProfile.nextInColl := nil; if prevProfile.nextInContour = Pool.cProfile then prevProfile.nextInContour := nil; if gProfile = Pool.cProfile then gProfile := nil; Pool.cProfile.Free; Pool.cProfile := prevProfile; prevProfile := nil; dec(nProfs); end; end; procedure TProfileCollection.End_Profile; var H: integer; begin Pool.CurveDir := GoingUnknown; Pool.Fresh := False; Pool.Joint := False; if (Pool.cProfile = nil) or (Pool.cProfile.Height <> 0) then exit; H := Pool.position - Pool.cProfile.Offset; if H = 0 then Remove_Profile else Pool.cProfile.Height := H; end; destructor TProfileCollection.Destroy; begin Pool.ProfileColl := nil; inherited Destroy; end; { TRenderPool } function TRenderPool.GetCapacity: integer; begin result := length(data); end; constructor TRenderPool.Create(APrecision, ABezierPrecision: integer); begin position := 0; SetPrecision(APrecision, ABezierPrecision); setlength(data,64); CurveDir := GoingUnknown; Joint := false; Fresh := False; cProfile := nil; BoundsMinY := 0; BoundsMaxY := 65535; ProfileColl := TProfileCollection.Create(self); end; destructor TRenderPool.Destroy; begin ProfileColl.Free; inherited Destroy; end; procedure TRenderPool.SetPrecision(APrecision, ABezierPrecision: integer); begin precision := APrecision; precisionHalf := APrecision shr 1; BezierPrecision := ABezierPrecision; end; procedure TRenderPool.SetBounds(MinY, MaxY: integer); begin BoundsMinY := MinY; BoundsMaxY := MaxY; end; procedure TRenderPool.SetScaleShift(value: integer); begin scaleShift := value; end; procedure TRenderPool.Clear; begin position := 0; CurveDir := GoingUnknown; Joint := false; Fresh := False; cProfile := nil; ProfileColl.Clear; end; procedure TRenderPool.ReduceCapacity; begin if Capacity > PoolIdleCapacity then setLength(data, PoolIdleCapacity); end; function TRenderPool.RequireCapacity(count: integer): boolean; begin if count > PoolMaxCapacity then result := false else begin if length(data) < count then begin count := count*2; if count > PoolMaxCapacity then count := PoolMaxCapacity; setlength(data, count); end; result := true; end; end; procedure TRenderPool.PushValue(AValue: Long); begin if cProfile = nil then raise Exception.Create('Out of profile'); data[position] := AValue; inc( position ); end; (****************************************************************************) (* *) (* Function: TProfile.Create *) (* *) (* Description: Creates a new Profile in the render pool *) (* *) (* Input: ADirection state/orientation of the new Profile *) (* *) (****************************************************************************) constructor TProfile.Create(APool: TRenderPool; ADirection : TCurveDirection ); begin Case ADirection of GoingUp : Flow := TT_Flow_Up; GoingDown : Flow := TT_Flow_Down; else raise exception.Create('ERROR : Inconsistent Profile' ); end; Pool := APool; Start := 0; Height := 0; if APool <> nil then Offset := APool.position; nextInContour := nil; nextInColl := nil; nextInList := nil; prevInList := nil; end; (****************************************************************************) (* *) (* Function: Split_Bezier *) (* *) (* Description: Subdivises one Bezier arc into two joint *) (* sub-arcs in the Bezier stack. *) (* *) (* Input: None ( subdivised bezier is taken from the top of the *) (* stack ) *) (* *) (* Returns: Nada *) (* *) (****************************************************************************) procedure TRenderPool.Split_Bezier( base : PBezierStack ); var arc : PBezierStack; a, b : Long; begin {$IF defined(CPUI386) and defined(CPU32) and not defined(NO_ASM)} {$asmmode intel} asm push esi push ebx push ecx mov esi, base mov eax, [esi+2*8] (* arc^[4].x := arc^[2].x *) mov ebx, [esi+1*8] (* b := arc^[1].x *) mov ecx, [esi+0*8] (* b := (arc^[0].x+b) div 2 *) mov [esi+4*8], eax add eax, ebx (* a := (arc^[2].x+b) div 2 *) add ebx, ecx mov edx, eax mov ecx, ebx sar edx, 31 sar ecx, 31 sub eax, edx sub ebx, ecx sar eax, 1 sar ebx, 1 mov [esi+3*8], eax (* arc^[3].x := a *) mov [esi+1*8], ebx add eax, ebx (* arc[2].x := (a+b) div 2 *) mov edx, eax sar edx, 31 sub eax, edx sar eax, 1 mov [esi+2*8], eax add esi, 4 mov eax, [esi+2*8] (* arc^[4].x := arc^[2].x *) mov ebx, [esi+1*8] (* b := arc^[1].x *) mov ecx, [esi+0*8] (* b := (arc^[0].x+b) div 2 *) mov [esi+4*8], eax add eax, ebx (* a := (arc^[2].x+b) div 2 *) add ebx, ecx mov edx, eax mov ecx, ebx sar edx, 31 sar ecx, 31 sub eax, edx sub ebx, ecx sar eax, 1 sar ebx, 1 mov [esi+3*8], eax (* arc^[3].x := a *) mov [esi+1*8], ebx add eax, ebx (* arc[2].x := (a+b) div 2 *) mov edx, eax sar edx, 31 sub eax, edx sar eax, 1 mov [esi+2*8], eax pop ecx pop ebx pop esi end; {$ELSE} arc := base; arc^[4].x := arc^[2].x; b := arc^[1].x; a := (arc^[2].x + b) div 2; arc^[3].x := a; b := (arc^[0].x + b) div 2; arc^[1].x := b; arc^[2].x := (a+b) div 2; arc^[4].y := arc^[2].y; b := arc^[1].y; a := (arc^[2].y + b) div 2; arc^[3].y := a; b := (arc^[0].y + b) div 2; arc^[1].y := b; arc^[2].y := (a+b) div 2; {$ENDIF} end; (****************************************************************************) (* *) (* Function: Push_Bezier *) (* *) (* Description: Clears the Bezier stack and pushes a new Arc on top of it. *) (* *) (* Input: x1,y1 x2,y2 x3,y3 new Bezier arc *) (* *) (* Returns: nada *) (* *) (****************************************************************************) procedure TRenderPool.PushBezier( x1, y1, x2, y2, x3, y3 : LongInt ); begin curArc:=0; with Arcs[CurArc+2] do begin x:=x1; y:=y1; end; with Arcs[CurArc+1] do begin x:=x2; y:=y2; end; with Arcs[ CurArc ] do begin x:=x3; y:=y3; end; end; (****************************************************************************) (* *) (* Function: Line_Up *) (* *) (* Description: Compute the x-coordinates of an ascending line segment *) (* and stores them in the render pool. *) (* *) (* Input: x1,y1 x2,y2 Segment start (x1,y1) and end (x2,y2) points *) (* *) (* Returns: True on success *) (* False if Render Pool overflow. *) (* *) (****************************************************************************) function TRenderPool.Line_Up( x1, y1, x2, y2, miny, maxy : LongInt ) : boolean; var Dx, Dy : LongInt; e1, e2, f1, f2, size : Int; Ix, Rx, Ax : LongInt; begin Line_Up := True; Dx := x2-x1; Dy := y2-y1; if (Dy <= 0) or (y2 < MinY) or (y1 > MaxY) then exit; if y1 < MinY then begin x1 := x1 + MulDiv( Dx, MinY-y1, Dy ); e1 := Trunc(MinY); f1 := 0; end else begin e1 := Trunc(y1); f1 := Frac(y1); end; if y2 > MaxY then begin x2 := x2 + MulDiv( Dx, MaxY-y2, Dy ); e2 := Trunc(MaxY); f2 := 0; end else begin e2 := Trunc(y2); f2 := Frac(y2); end; if f1 > 0 then if e1 = e2 then exit else begin inc( x1, MulDiv( Dx, precision-f1, Dy ) ); inc( e1 ); end else if Joint then dec( self.Position ); Joint := (f2 = 0); (* Indicates that the segment stopped sharp on a ScanLine *) if Fresh then begin cProfile.Start := e1; Fresh := False; end; size := ( e2-e1 )+1; if not RequireCapacity( self.Position+size ) then begin Line_Up := False; Error := Err_Ras_Overflow; exit; end; if Dx > 0 then begin Ix := (Precision*Dx) div Dy; Rx := (Precision*Dx) mod Dy; Dx := 1; end else begin Ix := -((Precision*-Dx) div Dy); Rx := (Precision*-Dx) mod Dy; Dx := -1; end; Ax := -Dy; while size > 0 do begin PushValue(x1); inc( x1, Ix ); inc( ax, rx ); if ax >= 0 then begin dec( ax, dy ); inc( x1, dx ); end; dec( size ); end; end; (****************************************************************************) (* *) (* Function: Line_Down *) (* *) (* Description: Compute the x-coordinates of a descending line segment *) (* and stores them in the render pool. *) (* *) (* Input: x1,y1 x2,y2 Segment start (x1,y1) and end (x2,y2) points *) (* *) (* Returns: True on success *) (* False if Render Pool overflow. *) (* *) (****************************************************************************) function TRenderPool.Line_Down( x1, y1, x2, y2, miny, maxy : LongInt ): boolean; var _fresh : Boolean; begin _fresh := fresh; Line_Down := Line_Up( x1, -y1, x2, -y2, -maxy, -miny ); if _fresh and not fresh then cProfile.start := -cProfile.start; end; (****************************************************************************) (* *) (* Function: Bezier_Up *) (* *) (* Description: Compute the x-coordinates of an ascending bezier arc *) (* and stores them in the render pool. *) (* *) (* Input: None.The arc is taken from the top of the Bezier stack. *) (* *) (* Returns: True on success *) (* False if Render Pool overflow. *) (* *) (****************************************************************************) function TRenderPool.Bezier_Up( miny, maxy : Long ) : boolean; var y1, y2, e, e2, e0 : LongInt; carc, debArc, f1 : Int; base : PBezierStack; maxArc : Int; label Fin; begin Bezier_Up := True; carc := curArc; maxArc := length(Arcs)-cArc; base := @Arcs[cArc]; y1 := base^[2].y; y2 := base^[0].y; if ( y2 < MinY ) or ( y1 > MaxY ) then goto Fin; e2 := FLOOR(y2); if e2 > MaxY then e2 := MaxY; e0 := MinY; if y1 < MinY then e := MinY else begin e := CEILING(y1); f1 := FRAC(y1); e0 := e; if f1 = 0 then begin //avoid duplicates if Joint then begin dec(self.position); Joint:=False; end; PushValue(base^[2].x); inc( e, Precision ); end end; if Fresh then begin cProfile.Start := TRUNC(e0); Fresh := False; end; if e2 < e then goto Fin; (* overflow ? *) if not RequireCapacity( self.position + TRUNC(e2-e)+ 1) then begin Bezier_Up := False; Error := Err_Ras_Overflow; exit; end; debArc := cArc; while ( cArc >= debArc ) and ( e <= e2 ) do begin Joint := False; y2 := base^[0].y; if y2 > e then begin y1 := base^[2].y; if ( y2-y1 >= BezierPrecision ) and (cArc + 2 < maxArc) then begin Split_Bezier( base ); inc( cArc, 2 ); base := @base^[2]; end else begin PushValue( base^[2].x + MulDiv( base^[0].x - base^[2].x, e - y1, y2 - y1 ) ); dec( cArc, 2 ); base := @Arcs[cArc]; inc( e, Precision ); end; end else begin if y2 = e then begin joint := True; PushValue( Arcs[cArc].x); inc( e, Precision ); end; dec( cArc, 2 ); base := @Arcs[cArc]; end end; Fin: dec( curArc, 2); exit; end; (****************************************************************************) (* *) (* Function: Bezier_Down *) (* *) (* Description: Compute the x-coordinates of a descending bezier arc *) (* and stores them in the render pool. *) (* *) (* Input: None. Arc is taken from the top of the Bezier stack. *) (* *) (* Returns: True on success *) (* False if Render Pool overflow. *) (* *) (****************************************************************************) function TRenderPool.Bezier_Down( miny, maxy : Long ) : boolean; var base : PBezierStack; _fresh : Boolean; begin _fresh := fresh; base := @Arcs[curArc]; base^[0].y := -base^[0].y; base^[1].y := -base^[1].y; base^[2].y := -base^[2].y; Bezier_Down := Bezier_Up( -maxy, -miny ); if _fresh and not fresh then cProfile.start := -cProfile.start; base^[0].y := -base^[0].y; end; procedure TRenderPool.Move_To( x,y: Longint); begin LastX := x; LastY := y; end; (****************************************************************************) (* *) (* Function: Line_To *) (* *) (* Description: Injects a new line segment and adjust Profiles list. *) (* *) (* Input: x, y : segment endpoint ( start point with Move_To ) *) (* *) (* Returns: True on success *) (* False if Render Pool overflow or Incorrect Profile *) (* *) (****************************************************************************) function TRenderPool.Line_To( x, y : LongInt ) : boolean; var LineDir: TCurveDirection; begin Line_To := False; if y > LastY then LineDir := GoingUp else if y < lastY then LineDir := GoingDown else LineDir := GoingHoriz; if (CurveDir <> LineDir) and (LineDir <> GoingHoriz) then begin if CurveDir <> GoingUnknown then ProfileColl.End_Profile; if LineDir <> GoingHoriz then ProfileColl.New_Profile( LineDir); end; Case CurveDir of GoingUp : if not Line_Up ( LastX, LastY, X, Y, BoundsMiny, BoundsMaxy ) then exit; GoingDown : if not Line_Down( LastX, LastY, X, Y, BoundsMiny, BoundsMaxy ) then exit; end; LastX := x; LastY := y; Line_To := True; end; (****************************************************************************) (* *) (* Function: Bezier_State *) (* *) (* Description: Determines the state (ascending/descending/flat/undet) *) (* of a Bezier arc, along one given axis. *) (* *) (* Input: y1, y2, y3 : coordinates of the Bezier arc. *) (* along the concerned axis. *) (* *) (* Returns: State, i.e. Ascending, Descending, Flat or Undetermined *) (* *) (****************************************************************************) function TRenderPool.Bezier_State( y1, y2, y3 : TT_F26Dot6 ) : TCurveDirection; begin (* determine orientation of a Bezier arc *) if y1 = y2 then if y2 = y3 then Bezier_State := GoingHoriz else if y2 > y3 then Bezier_State := GoingDown else Bezier_State := GoingUp else if y1 > y2 then if y2 >= y3 then Bezier_State := GoingDown else Bezier_State := GoingUnknown else if y2 <= y3 then Bezier_State := GoingUp else Bezier_State := GoingUnknown; end; (****************************************************************************) (* *) (* Function: Bezier_To *) (* *) (* Description: Injects a new bezier arc and adjust Profiles list. *) (* *) (* Input: x, y : arc endpoint ( start point with Move_To ) *) (* Cx, Cy : control point *) (* *) (* Returns: True on success *) (* False if Render Pool overflow or Incorrect Profile *) (* *) (****************************************************************************) function TRenderPool.Bezier_To( x, y, Cx, Cy : LongInt ) : boolean; var y3, x3 : LongInt; BezierDir : TCurveDirection; begin Bezier_To := False; PushBezier( LastX, LastY, Cx, Cy, X, Y ); while ( curArc >= 0 ) do begin y3 := Arcs[curArc].y; x3 := Arcs[curArc].x; BezierDir := Bezier_State( Arcs[curArc+2].y, Arcs[curArc+1].y, y3 ); case BezierDir of GoingHoriz : dec( curArc, 2 ); GoingUnknown : if curArc + 2 < length(arcs) then begin Split_Bezier( @Arcs[curArc] ); inc( curArc, 2 ); end else raise exception.Create('Bezier overflow'); else if CurveDir <> BezierDir then begin if CurveDir <> GoingUnknown then ProfileColl.End_Profile; ProfileColl.New_Profile( BezierDir ); end; case CurveDir of GoingUp : if not Bezier_Up( BoundsMiny, BoundsMaxy ) then exit; GoingDown : if not Bezier_Down( BoundsMiny, BoundsMaxy ) then exit; end; end; end; LastX := x3; LastY := y3; Bezier_To := True; end; (****************************************************************************) (* *) (* Function: DecomposeCurve *) (* *) (* Description: This functions scans the outline arrays in order to *) (* emit individual segments and beziers by calling the *) (* functions Line_To and Bezier_To. It handles all weird *) (* cases, like when the first point is off the curve, or *) (* when there are simply no "on" points in the contour ! *) (* *) (* Input: xCoord, yCoord : array coordinates to use. *) (* first, last : indexes of first and last point in *) (* contour. *) (* *) (* Returns: True on success *) (* False if case of error. *) (* *) (* Notes: The function assumes that 'first' < 'last' *) (* *) (****************************************************************************) procedure swap( var x, y : Long ); inline; var s : Long; begin s := x; x := y; y := s; end; function TRenderPool.DecomposeCurve( first, last : Int; flipped : Boolean; points: TT_Points; flags: PByte) : boolean; var index : Int; x, y : Long; (* current point *) cx, cy : Long; (* current Bezier control point *) mx, my : Long; (* middle point *) x_first, y_first : Long; (* first point coordinates *) x_last, y_last : Long; (* last point coordinates *) on_curve : Boolean; begin DecomposeCurve := False; with points^[first] do begin x_first := SCALED( x ); y_first := SCALED( y ); end; if flipped then swap( x_first, y_first ); with points^[last] do begin x_last := SCALED( x ); y_last := SCALED( y ); end; if flipped then swap( x_last, y_last ); Move_To(x_first, y_first); cx := x_first; cy := y_first; index := first; on_curve := Flags^[first] and 1 <> 0; (* check first point, and set origin *) if not on_curve then begin (* first point is off the curve - yes, this happens !! *) if Flags^[last] and 1 <> 0 then Move_To(x_last,y_last) (* start at last point if it is *) else (* on the curve *) begin (* if both first and last point *) (* are off the curve, start midway *) Move_To( (LastX + x_last) div 2, (LastY + y_last) div 2); (* record midpoint in x_last,y_last *) x_last := LastX; y_last := LastY; end; end; (* now process each contour point *) while ( index < last ) do begin inc( index ); x := SCALED( points^[index].x ); y := SCALED( points^[index].y ); if flipped then swap( x, y ); if on_curve then begin (* the previous point was on the curve *) on_curve := Flags^[index] and 1 <> 0; if on_curve then begin (* two successive on points -> emit segment *) if not Line_To( x, y ) then exit; end else begin (* else, keep current point as control for next bezier *) cx := x; cy := y; end; end else begin (* the previous point was off the curve *) on_curve := Flags^[index] and 1 <> 0; if on_curve then begin (* reaching on point -> emit Bezier *) if not Bezier_To( x, y, cx, cy ) then exit; end else begin (* two successive off points -> create middle point *) (* then emit Bezier *) mx := (cx + x) div 2; my := (cy + y) div 2; if not Bezier_To( mx, my, cx, cy ) then exit; cx := x; cy := y; end; end; end; (* end of contour, close curve cleanly *) if ( Flags^[first] and 1 <> 0 ) then if on_curve then if not Line_To( x_first, y_first ) then exit else else if not Bezier_To( x_first, y_first, cx, cy ) then exit else else if not on_curve then if not Bezier_To( x_last, y_last, cx, cy ) then exit; DecomposeCurve := True; end; (****************************************************************************) (* *) (* Function: Convert_Glyph *) (* *) (* Description: Converts a glyph into a series of segments and arcs *) (* and make a Profiles list with them. *) (* *) (* Returns: True on success *) (* False if any error was encountered during render. *) (* *) (****************************************************************************) Function TRenderPool.Convert_Glyph( flipped : Boolean; points: TT_Points; flags: PByte; Outs : TT_PConStarts; nContours : Int ) : boolean; var i, j : Int; begin result := False; try j := 0; for i := 0 to nContours-1 do begin // assign to nil to know first profile in contour ProfileColl.gProfile := nil; (* decompose a single contour into individual segments and *) (* beziers *) if not DecomposeCurve( j, outs^[i], flipped, points, flags) then exit; j := outs^[i] + 1; ProfileColl.End_Profile; (* We _must_ take care of the case when the first and last arcs join *) (* while having the same orientation *) if ( Frac(lastY) = 0 ) and ( lastY >= BoundsMinY ) and ( lastY <= BoundsMaxY ) then if ( ProfileColl.gProfile <> nil ) and (* gProfile can be nil *) ( cProfile <> nil) and ( cProfile.Height > 0) and ( ProfileColl.gProfile.Start = cProfile.Start + (cProfile.Height-1)*cProfile.Flow) and ( ProfileColl.gProfile.Flow = cProfile.Flow ) then (* if the contour was *) (* too small to be drawn *) begin dec( self.position ); dec( cProfile.Height); end; //if (cProfile <> nil) and (cProfile.Height > 1) then dec (cProfile.Height); // close contour if ProfileColl.gProfile <> nil then cProfile.nextInContour := ProfileColl.gProfile; end; result := true; except on ex: Exception do begin end; end; end; end.