{ Author Mazen NEIFER Licence LGPL } unit FreeBIDI; {$mode objfpc}{$H+} interface type TCharacter = WideChar; TString = WideString; TDirection=( drNONE, drRTL, drLTR ); TVisualToLogical = Array[Byte]Of Byte; TFontInfoPtr = Pointer; TCharWidthRoutine = function(Character:TCharacter;FontInfo:TFontInfoPtr):Integer; var FontInfoPtr:TFontInfoPtr; CharWidth:TCharWidthRoutine; {****************************Logical aspects***********************************} {Returns the number of logical characters} function LLength(const Src:TString):Cardinal; {Converts visual position to logical position} function LPos(const Src:TString; vp:Integer; pDir:TDirection):Cardinal; {****************************Visual aspects************************************} {Returns the number of visual characters} function VLength(const Src:TString; pDir:TDirection):Cardinal; {Converts a logical position to a visual position} function VPos(const Src:TString; lp:Integer; pDir, cDir:TDirection):Cardinal; function VPos(UTF8Char:PChar; Len:integer; BytePos:integer):Cardinal; {Returns character at a given visual position according to paragraph direction} function VCharOf(Src:TString; vp:Integer; dir:TDirection):TCharacter; {Inserts a string into an other paying attention of RTL/LTR direction} procedure VInsert(const Src:TString; var Dest:TString; vp:Integer; pDir:TDirection); {Deletes a string into an other paying attention of RTL/LTR direction} procedure VDelete(var str:TString; vp, len:Integer; pDir:TDirection); {Resturns a sub string of source string} //function VCopy(const Src:TString; vStart, vWidth:Integer):TString; {Resturns the visual image of current string} function VStr(const Src:TString; pDir:TDirection):TString; {****************************Helper routines***********************************} {Returns direction of a character} function DirectionOf(Character:TCharacter):TDirection; {Returns contextual direction of caracter in a string} function DirectionOf(Src:TString; lp:Integer; pDir:TDirection):TDirection; {Inserts a char as if it was typed using keyboard in the most user friendly way. Returns the new cursor position after insersion depending on the new visual text} function InsertChar(Src:TCharacter; var Dest:TString; vp:Integer; pDir:TDirection):Integer; {Returns a table mapping each visual position to its logical position in an UTF8* string} function VisualToLogical(const Src:TString; pDir:TDirection):TVisualToLogical; implementation function DefaultCharWidth(Character:TCharacter; FontInfoPtr:TFontInfoPtr):Integer; begin case Character of #9: Result := 8; else Result := 1; end; end; function DumpStr(const Src:TString):String; var i:Integer; begin Result := ''; for i:= 1 to Length(Src) do case Src[i] of #0..#127: Result := Result + Src[i]; else Result := Result + '$' + HexStr(Ord(Src[i]),4); end; end; function ComputeCharLength(p:PChar):Cardinal; begin if ord(p^)<%11000000 then {regular single byte character (#0 is a normal char, this is UTF8Charascal ;)} Result:=1 else if ((ord(p^) and %11100000) = %11000000) then if (ord(p[1]) and %11000000) = %10000000 then Result:=2 else Result:=1 else if ((ord(p^) and %11110000) = %11100000) then if ((ord(p[1]) and %11000000) = %10000000) and ((ord(p[2]) and %11000000) = %10000000) then Result:=3 else Result:=1 else if ((ord(p^) and %11111000) = %11110000) then if ((ord(p[1]) and %11000000) = %10000000) and ((ord(p[2]) and %11000000) = %10000000) and ((ord(p[3]) and %11000000) = %10000000) then Result:=4 else Result:=1 else Result:=1 end; {****************************Logical aspects***********************************} function LLength(const Src:TString):Cardinal; begin Result := Length(Src); end; function LPos(const Src:TString; vp:Integer; pDir:TDirection):Cardinal; var v2l:TVisualToLogical; i:integer; begin v2l := VisualToLogical(Src, pDir); if vp <= v2l[0] then Result := v2l[vp] else Result := Length(Src) + 1; end; {****************************Visual aspects************************************} function VLength(const Src:TString; pDir:TDirection):Cardinal; var Count:Integer; begin Result := 0; Count := Length(Src); while (Count > 0) do begin Result += CharWidth(Src[Count], FontInfoPtr); Count -= 1; end; end; function VPos(const Src:TString; lp:Integer; pDir, cDir:TDirection):Cardinal; var v2l:TVisualToLogical; vp:Integer; begin v2l := VisualToLogical(Src, pDir); for vp := 1 to v2l[0] do if lp = v2l[vp] then begin Exit(vp); end; Result := v2l[0]; end; function VPos(UTF8Char:PChar; Len:integer; BytePos:integer):Cardinal; begin end; function VCharOf(Src:TString; vp:Integer; dir:TDirection):TCharacter; var CharLen: LongInt; begin Result := Src[LPos(Src, vp, dir)]; end; {****************************Helper routines***********************************} function DirectionOf(Character:TCharacter):TDirection; begin case Character of #9,#32, '/', '{','}', '[',']', '(',')': Result := drNONE; #$0590..#$05FF, //Hebrew #$0600..#$06FF: //Arabic Result := drRTL; else Result := drLTR; end; end; function DirectionOf(Src:TString; lp:Integer; pDir:TDirection):TDirection; var c:TCharacter; lDir,rDir:TDirection; p:Integer; begin if(lp <= 0) then lp := 1; {Seek for proper character direction} c := Src[lp]; lDir := DirectionOf(c); {Seek for left character direction if it is neutral} p := lp; while(p > 1) and (lDir = drNONE)do begin c := Src[p - 1]; lDir := DirectionOf(c); p := p - Length(c); end; {Seek for right character direction if it is neutral} p := lp; repeat c := Src[p]; rDir := DirectionOf(c); p := p + Length(c); until(p > Length(Src)) or (rDir <> drNONE); if(lDir = rDir) then Result := rDir else Result := pDir; end; function VisualToLogical(const Src:TString; pDir:TDirection):TVisualToLogical; procedure Insert(value:Byte; var v2l:TVisualToLogical; InsPos:Byte); var l:Byte; begin if v2l[0] < 255 then Inc(InsPos); if InsPos > v2l[0] then InsPos := v2l[0]; for l := v2l[0] downto InsPos do v2l[l] := v2l[l-1]; v2l[InsPos] := Value; end; var lp, vp : Integer; cDir,lDir:TDirection; Character:TCharacter; i:Integer; begin Result[0] := 0; lp := 1; vp := 1; lDir := drNONE; while lp <= Length(Src) do begin Character := Src[lp]; cDir := DirectionOf(Src, lp, pDir); Inc(Result[0]); case cDir of drRTL: begin lDir := drRTL; end; drLTR: begin lDir := drLTR; vp := Result[0]; end; else vp := Result[0]; end; Insert(lp, Result, vp); lp += 1; end; end; function InsertChar(Src:TCharacter; var Dest:TString; vp:Integer; pDir:TDirection):Integer; var vSrc,vDest:TString; begin vSrc := VStr(Src,pDir); vDest := VStr(Dest,pDir); Insert(vSrc, vDest, vp); Dest := VStr(vDest, pDir); case DirectionOf(Src) of drRTL: Result := vp; drLTR: Result := vp + 1; else if(vp < Length(vDest)) and (DirectionOf(vDest[vp + 1]) = drRTL) then Result := vp else Result := vp + 1; end; end; procedure VInsert(const Src:TString;var Dest:TString; vp:Integer; pDir:TDirection); var vSrc,vDest:TString; begin vSrc := VStr(Src,pDir); vDest := VStr(Dest,pDir); Insert(vSrc, vDest, vp); Dest := VStr(vDest, pDir); end; procedure VDelete(var str:TString; vp, len:Integer; pDir:TDirection); var v2l:TVisualToLogical; i:Integer; begin v2l := VisualToLogical(str, pDir); for i := 1 to v2l[0] do if(v2l[i] >= vp) and (v2l[i] < vp + len) then Delete(str, v2l[i], 1); end; function VStr(const Src:TString; pDir:TDirection):TString; var v2lSrc:TVisualToLogical; vp:Integer; begin v2lSrc := VisualToLogical(Src,pDir); SetLength(Result, v2lSrc[0]); for vp := 1 to v2lSrc[0] do Result[vp] := Src[v2lSrc[vp]]; end; initialization CharWidth := @DefaultCharWidth; end.