{GENERAL METHODS USED BY TWAIN DELPHI}
{december 2001®, made by Gustavo Daud}

{This unit contains general methods used by Delphi}
{Twain component. Some of the methods bellow aren't}
{directly related to Twain, but are pieces needed}
{to implement the component.}

unit DelphiTwainUtils;

{$INCLUDE DELPHITWAIN.INC}

interface

uses
  Twain;

type
  {Kinds of directories to be obtained with GetCustomDirectory}
  TDirectoryKind = (dkWindows, dkSystem, dkCurrent, dkApplication, dkTemp);

  {Class to store a list of pointers}
  TPointerList = class
  private
    {Stores pointer to the allocated data}
    Data: Pointer;
    {Contains number of additional items allocated every time}
    {it needs more data to store}
    fAdditionalBlock: Integer;
    {Contains the number of items in the list}
    fCount: Integer;
    {Contains number of allocated items}
    fAllocated: Integer;
    {Allocate/deallocate memory to have enough memory}
    {to hold the new number of items}
    procedure SetAllocated(const Value: Integer);
    {Sets the AdditionalBlock property}
    procedure SetAdditionalBlock(const Value: Integer);
    {Set the number of items in the list}
    procedure SetCount(const Value: Integer);
    function GetItem(Index: Integer): Pointer;
    procedure PutItem(Index: Integer; const Value: Pointer);
  public
    {Add a new item}
    procedure Add(Value: Pointer);
    {Clear all the items in the list}
    procedure Clear;
    {Object being created or destroyed}
    constructor Create;
    destructor Destroy; override;
    {Returns/sets an item value}
    property Item[Index: Integer]: Pointer read GetItem write PutItem; default;
    {Returns the number of items}
    property Count: Integer read fCount write SetCount;
    {Number of allocated items}
    property Allocated: Integer read fAllocated write SetAllocated;
    {Additional items to alloc when it needs more memory}
    property AdditionalBlock: Integer read fAdditionalBlock write
      SetAdditionalBlock;
  end;

{Returns custom Microsoft Windows® directories}
function GetCustomDirectory(const DirectoryKind: TDirectoryKind): String;
{Returns the last error string from Microsoft Windows®}
function GetLastErrorText(): String;
{Returns if the directory exists}
function DirectoryExists(const Directory: String): Boolean;
{Returns if the file exists}
function FileExists(const FilePath: String): Boolean;
{Extracts the file directory part}
function ExtractDirectory(const FilePath: String): String;
{Convert from integer to string}
{$IFDEF DONTUSEVCL}function IntToStr(Value: Integer): String;{$ENDIF}
{$IFDEF DONTUSEVCL}function StrToIntDef(Value: String;
  Default: Integer): Integer;{$ENDIF}
{$IFDEF DONTUSEVCL}function CompareMem(P1, P2: pChar;
  Size: Integer): Boolean;{$ENDIF}
{Convert from twain Fix32 to extended}
function Fix32ToFloat(Value: TW_FIX32): Extended;
{Convert from extended to Fix32}
function FloatToFix32 (floater: extended): TW_FIX32;

implementation

{Units used bellow}
uses
  Windows;

{$IFDEF DONTUSEVCL}
  function CompareMem(P1, P2: pChar; Size: Integer): Boolean;
  var
    i: Integer;
  begin
    {Default result}
    Result := TRUE;
    {Search each byte}
    FOR i := 1 TO Size DO
    begin
      {Compare booth bytes}
      if P1^ <> P2^ then
      begin
        Result := FALSE;
        Exit;
      end; {if P1^ <> P2^}
      {Move to next byte}
      Inc(P1); Inc(P2);
    end {FOR i}
  end {function};
{$ENDIF}

{$IFDEF DONTUSEVCL}
  function IntToStr(Value: Integer): String;
  begin
    Str(Value, Result);
  end;
{$ENDIF}

{$IFDEF DONTUSEVCL}
  function StrToIntDef(Value: String; Default: Integer): Integer;
  var Code: Integer;
  begin
    {Try converting from string to integer}
    Val(Value, Result, Code);
    {If any error ocurred, returns default value}
    if Code <> 0 then Result := Default;
  end;
{$ENDIF}


{Convert from extended to Fix32}
function FloatToFix32 (floater: extended): TW_FIX32;
var
  fracpart : extended;
begin
  //Obtain numerical part by truncating the float number
  Result.Whole := trunc(floater);
  //Obtain fracional part by subtracting float number by
  //numerical part. Also we make sure the number is not
  //negative by multipling by -1 if it is negative
  fracpart := floater - result.Whole;
  if fracpart < 0 then fracpart := fracpart * -1;
  //Multiply by 10 until there is no fracional part any longer
  while FracPart - trunc(FracPart) <> 0 do fracpart := fracpart * 10;
  //Return fracional part
  Result.Frac := trunc(fracpart);
end;

{Convert from twain Fix32 to extended}
function Fix32ToFloat(Value: TW_FIX32): Extended;
begin
  Result := Value.Whole + (Value.Frac / 65536.0);
end;

{Returns the last position for any of the characters in the parameter}
function LastPosition(const Text, characters: String): Integer;
var
  x, y: Integer;  {For loop variables}
begin
  Result := Length(Text);  {Initial result}

  {Search each character in the text}
  FOR x := 1 TO Length(Text) DO
  begin
    {Test for each character}
    FOR y := 1 TO Length(characters) DO
      if Text[x] = characters[y] then
        Result := x;
  end {for x}
end;

{Extracts the file directory}
function ExtractDirectory(const FilePath: String): String;
begin
  {Searches for the last \ or : characters}
  {ex: c:\windows\system32\yfile.ext or c:autoexec.bat}
  Result := Copy(FilePath, 1, LastPosition(FilePath, '\:'));
end;

{Returns if the file exists}
function FileExists(const FilePath: String): Boolean;
var
  FindData  : TWin32FindData;
  FindHandle: THandle;
begin
  {Searches for the file}
  FindHandle := FindFirstFile(PChar(FilePath), FindData);
  Result := (FindHandle <> INVALID_HANDLE_VALUE);
  {In case it found, closes the FindFirstFile handle}
  if Result then FindClose(FindHandle);
end;

{Returns if the directory exists}
function DirectoryExists(const Directory: String): Boolean;
var
  Attr: DWORD;
begin
  {Calls GetFileAttributes to verify}
  Attr := GetFileAttributes(PChar(Directory));
  Result := (Attr <> $FFFFFFFF) and (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0);
end;

{Makes an language identifier using the two ids}
function MAKELANGID(p, s: WORD): DWORD;
begin
  Result := (s shl 10) or p;
end;

{Returns the last error string from Microsoft Windows®}}
function GetLastErrorText(): String;
var
  Buffer: Array[Byte] of Char;
  Len   : DWORD;
begin
  {Calls format message to translate from the error code ID to}
  {a text understandable error}
  Len := Windows.FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or
    FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), Buffer, sizeof(Buffer), nil);
  {Remove this chars from the ending of the result}
  while (Len > 0) and (Buffer[Len - 1] in [#0..#32, '.']) do Dec(Len);
  {Fills result}
  SetString(Result, Buffer, Len);
end;

{Includes a trailing backslash in the end of the directory; if necessary}
procedure IncludeTrailingBackslash(var Directory: String);
begin
  {If there isn't already a backslash, add one}
  if Directory[Length(Directory)] <> '\' then
    Directory := Directory + '\'
end;

{Returns custom Microsoft Windows® directories}
function GetCustomDirectory(const DirectoryKind: TDirectoryKind): String;
const
  {Default maximum size for directories}
  DEF_DIRLEN = MAX_PATH;

  {Calls appropriate method and returns necessary size}
  function CallDirectoryMethod(Buffer: Pointer; Size: UINT): UINT;
  begin
    {Test the directory needed by the parameter}
    case DirectoryKind of
      {Windows directory}
      dkWindows: Result := Windows.GetWindowsDirectory(Buffer, Size);
      {System directory}
      dkSystem : Result := Windows.GetSystemDirectory(Buffer, Size);
      {Current directory}
      dkCurrent: Result := Windows.GetCurrentDirectory(Size, Buffer);
      {Application directory}
      dkApplication: Result := Windows.GetModuleFileName(0, Buffer, Size);
      {Temp directory}
      dkTemp   : Result := Windows.GetTempPath(Size, Buffer);
      {Unknown directory}
      else Result := 0;
    end {case}
  end;

var
  DirectoryLen: UINT;
begin
  {Set length of the resulting buffer to MAX_PATH to try to hold}
  {windows directory}
  SetLength(Result, DEF_DIRLEN + 1);
  {Tries to obtain the windows directory and stores the size}
  DirectoryLen := CallDirectoryMethod(@Result[1], DEF_DIRLEN);

  {In case it was not enough to hold windows directory, enlarge}
  if DirectoryLen > DEF_DIRLEN then
  begin
    {Try again, now with the right size}
    SetLength(Result, DirectoryLen + 1);
    CallDirectoryMethod(@Result[1], DirectoryLen);
  end
  else {Otherwise, adjust the result to excluded unused data}
    SetLength(Result, DirectoryLen);

  {In case the user searched for the application directory}
  {extracts just the directory part}
  if DirectoryKind = dkApplication then
    Result := ExtractDirectory(Result);
  {Add a trailing backslash to end of the directory name}
  IncludeTrailingBackslash(Result);
end;

{ TPointerList object implementation }

{Add a new item}
procedure TPointerList.Add(Value: Pointer);
begin
  {Increase number of items and update new item}
  Count := Count + 1;
  Item[Count - 1] := Value;
end;

{Clear all the items in the list}
procedure TPointerList.Clear;
begin
  {Set number of items to 0 and initialize again allocated items}
  Count := 0;
  Allocated := AdditionalBlock;
end;

{TPointerList being created}
constructor TPointerList.Create;
begin
  {Let ancestor receive the call}
  inherited Create;

  {Allocate a number of items}
  fAdditionalBlock := 10;
  fAllocated := fAdditionalBlock;
  GetMem(Data, (fAllocated * sizeof(Pointer)));
end;

{TPointerList being destroyed}
destructor TPointerList.Destroy;
begin
  {Deallocate data}
  FreeMem(Data, (fAllocated * sizeof(Pointer)));

  {Let ancestor receive and finish}
  inherited Destroy;
end;

{Returns an item from the list}
function TPointerList.GetItem(Index: Integer): Pointer;
begin
  {Check item bounds and return item}
  if Index in [0..Count - 1] then
    Longint(Result) := pLongint(Longint(Data) + (Index * sizeof(Pointer)))^
  else Result := nil; {Otherwise returns nil}
end;

{Sets an item from the list}
procedure TPointerList.PutItem(Index: Integer; const Value: Pointer);
begin
  {Check item bounds and sets item}
  if Index in [0..Count - 1] then
    pLongint(Longint(Data) + (Index * sizeof(Pointer)))^ := Longint(Value);
end;

{Sets the AdditionalBlock property}
procedure TPointerList.SetAdditionalBlock(const Value: Integer);
begin
  {Value must be a positive number greater than 0}
  if (Value > 0) then
    fAdditionalBlock := Value;
end;

{Allocate/deallocate memory to have enough memory to hold}
{the new number of items}
procedure TPointerList.SetAllocated(const Value: Integer);
begin
  {Must be always greater than 0 the number of allocated items}
  {And it also should not be smaller than count}
  if (Value > 0) and (Value <= Count) then
  begin
    {Just realloc memory and update property variable}
    ReallocMem(Data, (Value * sizeof(Pointer)));
    fAllocated := Value;
  end {if (Value <> 0)}
end;

{Set the number of items in the list}
procedure TPointerList.SetCount(const Value: Integer);
begin
  {Value must be 0 or greater}
  if (Value >= 0) then
  begin
    {If there is no more memory to hold data, allocate some more}
    while (Value > fAllocated) do
      Allocated := Allocated + fAdditionalBlock;
    {Update property}
    fCount := Value;
  end {if (Value >= 0)}
end;

end.
