www.pudn.com > FaxConvert.zip > faxfield.pas


unit FaxField; 
 
{$I AWDEFINE.INC} 
 
interface 
 
uses 
  {$IFDEF Win32} Windows, {$ELSE} WinTypes, WinProcs, {$ENDIF} 
  Messages, Classes, Graphics, Controls, Forms, StdCtrls, ExtCtrls, 
  {$IFDEF DELPHI3} ExtDlgs, {$ENDIF} 
  Dialogs, Ruler,jpeg,menus; 
 
 
type 
  {Records used for saving cover page to a disk file} 
  TUserDataArray = array[0..1023] of Byte; 
 
  TPageRecord = packed record 
    prVersionNum       : string[11]; 
    prPageWidthPixels  : LongInt; 
    prPageHeightPixels : LongInt; 
    prPageWidthInches  : Double; 
    prPageHeightInches : Double; 
    prIsMetric         : Boolean; 
    {Extra field for storing miscellaneous additional data} 
    prUserData         : TUserDataArray; 
  end; 
 
  TFieldRecord = packed record 
    frLeftInches   : Double; 
    frTopInches    : Double; 
    frWidthInches  : Double; 
    frHeightInches : Double; 
  end; 
 
  TFontRecord = packed record 
    frCharSet       : Byte; 
    frColor         : LongInt; 
    frHeight        : LongInt; 
    frName          : string[255]; 
    frPitch         : Byte; 
    frSize          : LongInt; 
    frFontBold      : Boolean; 
    frFontItalic    : Boolean; 
    frFontUnderline : Boolean; 
    frFontStrikeout : Boolean; 
  end; 
 
  TStretchModes = (smNone, smDrag, smE, smW, smS, smN, smNE, smSW, smSE, smNW); 
 
  TStretchHandle = class(TPaintBox) 
  private 
    FHandlePosition : TStretchModes; 
  protected 
    procedure Paint; override; 
  public 
    constructor Create(AOwner : TComponent); override; 
    property HandlePosition : TStretchModes 
      read FHandlePosition write FHandlePosition; 
  end; 
 
  TStretchHandleArray = array[0..7] of TStretchHandle; 
 
  TBaseField = class(TShape) 
  private 
    FSelected        : Boolean; 
    FStretchMode     : TStretchModes; 
    bfStretchHandles : TStretchHandleArray; 
  protected 
    procedure bfMouseDown(Sender: TObject; Button: TMouseButton; 
                          Shift: TShiftState; X, Y: Integer); 
    procedure bfMouseUp(Sender: TObject; Button: TMouseButton; 
                        Shift: TShiftState; X, Y: Integer); 
    procedure bfMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); 
    procedure SetParent(AParent: TWinControl); override; 
    procedure SetSelected(IsSelected : Boolean); 
    function GetStretchHandleCoords(WhichHandle : TStretchModes) : TPoint; 
      {-Returns the coordinates (Left, Top) where the StretchHandle should be drawn} 
    procedure Write(Stream : TStream); virtual; 
      {-Writes all necessary TBaseField properties out to Stream} 
    procedure Read(Stream : TStream); virtual; 
      {-Reads BaseField properties from Stream and assigns those properties to Self} 
    procedure Draw(ACanvas : TCanvas); virtual; abstract; 
      {-Draws Self on ACanvas} 
  public 
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; 
    constructor Create(AOwner: TComponent); override; 
    property Selected : Boolean read FSelected write SetSelected; 
    property StretchMode : TStretchModes read FStretchMode write FStretchMode; 
  end; 
 
  TTextField = class(TBaseField) 
  protected 
    FMemo : TMemo; 
    FPopupMenu:TPopupMenu; 
    procedure SetParent(AParent: TWinControl); override; 
    function GetTextHeight : Integer; 
      {-Returns the height of one row of text, including external leading, given the 
        current font assigned to the field} 
    function GetText : string; 
    procedure Write(Stream : TStream); override; 
      {-Writes all necessary properties out to Stream} 
    procedure Read(Stream : TStream); override; 
      {-Reads properties from Stream and assigns those properties to Self} 
    procedure Draw(ACanvas : TCanvas); override; 
      {-Draws Self on ACanvas} 
    procedure tfEnter(Sender : TObject); 
    procedure tfExit(Sender : TObject); 
 
  public 
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; 
    constructor Create(AOwner: TComponent); override; 
    procedure MemoDblClick(Sender: TObject); 
    procedure OnLoadFromFile(Sender: TObject); 
    procedure SetFocus; 
    property Text : string read GetText; 
  end; 
 
  TImageField = class(TBaseField) 
  protected 
    FImage : TImage; 
    procedure SetParent(AParent: TWinControl); override; 
    function GetPicture : TPicture; 
    procedure Write(Stream : TStream); override; 
      {-Writes all necessary properties out to Stream} 
    procedure Read(Stream : TStream); override; 
      {-Reads properties from Stream and assigns those properties to Self} 
    procedure Draw(ACanvas : TCanvas); override; 
      {-Draws Self on ACanvas} 
  public 
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; 
    constructor Create(AOwner: TComponent); override; 
    procedure ImageDblClick(Sender: TObject); 
    property Picture : TPicture read GetPicture; 
  end; 
 
  TSelectionChangeEvent = procedure(IsFieldSelected : Boolean) of object; 
  TPositionChangeEvent = procedure(Left, Top, Width, Height : Integer) of object; 
 
  TFaxPanel = class(TPanel) 
  private 
    FShowGrid               : Boolean; 
    FSnapToGrid             : Boolean; 
    FGridSpacingX           : Integer; 
    FGridSpacingY           : Integer; 
    FPageWidthInches        : Double; 
    FPageHeightInches       : Double; 
    FEditMode               : Boolean; {Are we in Edit Mode or Design Mode} 
    FStretchMode            : TStretchModes; 
    FOnFieldSelectionChange : TSelectionChangeEvent; 
    FOnFieldPositionChange  : TPositionChangeEvent; 
    FNeedsSaving            : Boolean; 
 
    FPageCount              : Integer; 
    FPageNumber             : Integer; 
    FSender                 : string; 
    FRecipient              : string; 
    FPageTitle              : string; 
    FStationID              : string; 
 
    fpDragging              : Boolean; 
    fpMaxGridLine           : TPoint; 
    fpHorzPixelsPerInch     : Double; 
    fpVertPixelsPerInch     : Double; 
    fpMouseAnchor           : TPoint; 
    fpIsMouseDown           : Boolean; 
    fpFieldList             : TList; 
 
    function GetFieldCount : Integer; 
    function GetField(Index : Integer) : TBaseField; 
    function GetSelectedField : TBaseField; 
    procedure SetEditMode(Value : Boolean); 
    procedure SetPageWidthInches(AWidth : Double); 
    procedure SetPageHeightInches(AHeight : Double); 
    procedure SetShowGrid(AShowGrid : Boolean); 
    procedure SetSnapToGrid(ASnapToGrid : Boolean); 
    procedure SetGridSpacingX(GridSpacing : Integer); 
    procedure SetGridSpacingY(GridSpacing : Integer); 
    procedure AdjustLeftToGrid(var ALeft : Integer); 
      {-If SnapToGrid is True, adjusts ALeft to be on the nearest grid line} 
    procedure AdjustTopToGrid(var ATop : Integer); 
      {-If SnapToGrid is True, adjusts ATop to be on the nearest grid line} 
    procedure AdjustWidthToGrid(ALeft : Integer; var AWidth : Integer); 
      {-If SnapToGrid is True, adjusts AWidth to be on the nearest grid line. 
        Caller should ensure that ALeft is already on a grid line, possibly by 
        calling AdjustLeftToGrid.} 
    procedure AdjustHeightToGrid(ATop : Integer; var AHeight : Integer); 
      {-If SnapToGrid is True, adjusts AHeight to be on the nearest grid line. 
        Caller should ensure that ATop is already on a grid line, possibly by 
        calling AdjustTopToGrid.} 
    function GetDrawAdjustFactor : Double; 
    function GetDrawWidth : Integer; 
      {-Returns the width that the TCanvas passed to the Draw method should be} 
    function GetDrawHeight : Integer; 
      {-Returns the height that the TCanvas passed to the Draw method should be} 
    procedure SetStretchMode(NewStretchMode : TStretchModes); 
    procedure DeselectAllFields; 
    procedure DeleteAllFields; 
    procedure AddField(Field : TBaseField); 
 
  protected 
    procedure Paint; override; 
    procedure fpResize(Sender : TObject); 
    procedure fpMouseUp(Sender: TObject; Button: TMouseButton; 
      Shift: TShiftState; X, Y: Integer); 
    procedure fpMouseDown(Sender: TObject; Button: TMouseButton; 
      Shift: TShiftState; X, Y: Integer); 
    procedure fpMouseMove(Sender: TObject; Shift: TShiftState; X, 
      Y: Integer); 
    procedure FieldSelectionChange(IsFieldSelected : Boolean); 
      {-Calls OnFieldSelectionChange event handler when a field becomes 
        deselected or when a new field becomes selected} 
    procedure FieldPositionChange(ALeft, ATop, AWidth, AHeight : Integer); 
      {-Calls OnFieldPositionChange event handler when the location or size of 
        the currently-selected field changes} 
    procedure FieldChange(Sender : TObject); 
    property Canvas; 
    property StretchMode : TStretchModes read FStretchMode write SetStretchMode; 
    property PageWidthInches : Double read FPageWidthInches write SetPageWidthInches; 
    property PageHeightInches : Double read FPageHeightInches write SetPageHeightInches; 
    property DrawAdjustFactor : Double read GetDrawAdjustFactor; 
  public 
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; 
    constructor Create(AOwner : TComponent); override; 
    destructor Destroy; override; 
    function HorzPixelsToInches(P : Integer) : Double; 
      {-Returns the value of P converted to inches given the current values of 
        Width and PageWidthInches} 
    function VertPixelsToInches(P : Integer) : Double; 
      {-Returns the value of P converted to inches given the current values of 
        Height and PageHeightInches} 
    function HorzInchesToPixels(Inches : Double) : Integer; 
      {-Returns the value of Inches converted to pixels given the current values 
        of Width and PageWidthInches} 
    function VertInchesToPixels(Inches : Double) : Integer; 
      {-Returns the value of Inches converted to pixels given the current values 
        of Height and PageHeightInches} 
    procedure SizeMove(Sender : TObject; Key : Word; Shift : TShiftState); 
      {-move/size the field} 
    function AddTextField : TTextField; 
    function AddImageField : TImageField; 
    procedure DeleteSelectedField; 
      {-Deletes currently selected field} 
    procedure CenterSelectedField(IsHorizontal : Boolean); 
      {-Centers the currently-selected field within the panel. IsHorizontal 
        specifies whether the field will be centered vertically or horizontally.} 
    function SelectedFieldsExist : Boolean; 
      {-Returns True if the panel contains at least one selected field} 
    procedure FieldPositionChangeForSelectedField; 
      {-If a selected field exists, calls FieldPositionChange with that field's 
        coordinates} 
    procedure Write(Stream : TStream); 
      {-Writes all defining information out to Stream} 
    procedure Read(Stream : TStream); 
      {-Reads Stream and loads its properties into Self} 
    procedure Draw(ACanvas : TCanvas); 
      {-Draws an image of Self, including all fields, on ACanvas} 
    property ShowGrid : Boolean read FShowGrid write SetShowGrid; 
    property SnapToGrid : Boolean read FSnapToGrid write SetSnapToGrid; 
    property GridSpacingX : Integer read FGridSpacingX write SetGridSpacingX; 
    property GridSpacingY : Integer read FGridSpacingY write SetGridSpacingY; 
    property EditMode : Boolean 
      read FEditMode 
      write SetEditMode; 
    property NeedsSaving : Boolean read FNeedsSaving write FNeedsSaving; 
    property OnFieldSelectionChange : TSelectionChangeEvent 
      read FOnFieldSelectionChange write FOnFieldSelectionChange; 
    property OnFieldPositionChange : TPositionChangeEvent 
      read FOnFieldPositionChange write FOnFieldPositionChange; 
 
    {When creating a bitmap for use in creating an APF file, the bitmap's width 
     should be set to TFaxPanel.DrawWidth, and the bitmap's height should be 
     set to TFaxPanel.DrawHeight.} 
    property DrawWidth : Integer read GetDrawWidth; 
      {-Returns the width that the TCanvas passed to the Draw method should be} 
    property DrawHeight : Integer read GetDrawHeight; 
      {-Returns the height that the TCanvas passed to the Draw method should be} 
    property FieldCount : Integer 
      read GetFieldCount; 
    property Field[Index : Integer] : TBaseField 
      read GetField; 
    property SelectedField : TBaseField 
      read GetSelectedField; 
 
    {These properties are the values that are substituted for replacement tags 
     when the cover page is saved as an APF file} 
    property PageCount : Integer read FPageCount write FPageCount; 
      {-Value substituted for $N replacement tag} 
    property PageNumber : Integer read FPageNumber write FPageNumber; 
      {-Value substituted for $P replacement tag} 
    property Sender : string read FSender write FSender; 
      {-Value substituted for $F replacement tag} 
    property Recipient : string read FRecipient write FRecipient; 
      {-Value substituted for $R replacement tag} 
    property PageTitle : string read FPageTitle write FPageTitle; 
      {-Value substituted for $S replacement tag} 
    property StationID : string read FStationID write FStationID; 
      {-Value substituted for $I replacement tag} 
  end; 
 
  TFaxScrollBox = class(TScrollBox) 
  private 
    FOnHorzScroll : TScrollEvent; 
    FOnVertScroll : TScrollEvent; 
    procedure WMHScroll(var Message : TWMHScroll); message WM_HSCROLL; 
    procedure WMVScroll(var Message : TWMVScroll); message WM_VSCROLL; 
  public 
    property OnHorzScroll : TScrollEvent read FOnHorzScroll write FOnHorzScroll; 
    property OnVertScroll : TScrollEvent read FOnVertScroll write FOnVertScroll; 
  end; 
 
  TFaxDesigner = class(TPanel) 
  private 
    FFaxPanel   : TFaxPanel; 
    FIsNew      : Boolean; 
    FIsMetric   : Boolean; 
    FUserData   : TUserDataArray; 
    fdHorzRuler : TRuler; 
    fdVertRuler : TRuler; 
    fdScrollBox : TFaxScrollBox; 
    procedure HorzScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); 
    procedure VertScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); 
    function GetPageWidthPixels : Integer; 
    procedure SetPageWidthPixels(AWidth : Integer); 
    function GetPageHeightPixels : Integer; 
    procedure SetPageHeightPixels(AHeight : Integer); 
    function GetPageWidthInches : Double; 
    procedure SetPageWidthInches(AWidth : Double); 
    function GetPageHeightInches : Double; 
    procedure SetPageHeightInches(AHeight : Double); 
    procedure SetIsMetric(AIsMetric : Boolean); 
    procedure SetMarkPositions(ALeft, ATop, AWidth, AHeight : Integer); 
      {-Sets the position of the red position marks on the Ruler bars. To 
        suppress drawing of the marks, set to a negative value.} 
  protected 
    procedure SetParent(AParent: TWinControl); override; 
  public 
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; 
    constructor Create(AOwner : TComponent); override; 
    procedure Write(Stream : TStream); 
      {-Writes all defining information out to Stream} 
    procedure Read(Stream : TStream); 
      {-Reads Stream and loads its properties into Self} 
    property FaxPanel : TFaxPanel read FFaxPanel; 
    property PageWidthPixels  : Integer read GetPageWidthPixels write SetPageWidthPixels; 
    property PageHeightPixels : Integer read GetPageHeightPixels write SetPageHeightPixels; 
    property PageWidthInches  : Double read GetPageWidthInches write SetPageWidthInches; 
    property PageHeightInches : Double read GetPageHeightInches write SetPageHeightInches; 
    property IsMetric : Boolean read FIsMetric write SetIsMetric; 
    property UserData : TUserDataArray read FUserData write FUserData; 
      {-Misc data field. Gets written to and read from the Stream when Write or 
        Read are called} 
    property IsNew : Boolean read FIsNew write FIsNew; 
      {-Returns True if this is a new cover page that hasn't been given a real 
        name yet. Returns False if this cover page was read in using the Read 
        method or if it was written out using the Write method.} 
  end; 
 
 
implementation 
 
uses SysUtils; 
 
 
const 
  ctVersionNum   = '1.00'; 
  ftTextField    = 0; 
  ftImageField   = 1; 
 
  ctGridStart    = 1; 
  ctGridSpacingX = 20; 
  ctGridSpacingY = 20; 
  ctDefaultWidthPixels  = 600; 
  ctDefaultHeightPixels = 776; 
  ctDefaultWidthInches  = 8.5; 
  ctDefaultHeightInches = 11.0; 
  ctStretchHandleSize   = 5; {Stretch handles are 5 x 5 pixels} 
 
 
procedure Constrain(var X : Integer; MinVal, MaxVal : Integer); 
  {-Forces an integer between two values} 
begin 
  if X > MaxVal then 
    X := MaxVal 
  else if X < MinVal then 
    X := MinVal; 
end;  { Constrain } 
 
procedure ConvertCoords(Source, Target : TControl; var X, Y : Integer); 
  {-Converts Source coordinates X, Y to Target coordinates} 
var 
  P : TPoint; 
begin 
  P.X := X; 
  P.Y := Y; 
  P   := Target.ScreenToClient(Source.ClientToScreen(P)); 
  X   := P.X; 
  Y   := P.Y; 
end; 
 
 
{*** TStretchHandle *} 
 
constructor TStretchHandle.Create(AOwner: TComponent); 
begin 
  inherited Create(AOwner); 
  Canvas.Brush.Color := clBlack; 
  Canvas.Brush.Style := bsSolid; 
  SetBounds(Top, Left, ctStretchHandleSize, ctStretchHandleSize); 
end; 
 
procedure TStretchHandle.Paint; 
begin 
  Canvas.FillRect(Rect(0, 0, Width, Height)); 
end; 
 
{------------------------------ TBaseField ---------------------------------} 
 
constructor TBaseField.Create(AOwner: TComponent); 
begin 
  inherited Create(AOwner); 
  Brush.Color := clWindow; 
  Brush.Style := bsClear; 
  DragCursor  := crCross; 
  DragMode    := dmManual; 
  Pen.Mode    := pmCopy; 
  Pen.Style   := psDashDot; 
  Pen.Color   := clBlack; 
  Pen.Width   := 1; 
  Shape       := stRectangle; 
  Visible     := False; {Caller must make visible after setting size and position} 
  SetSelected(False); 
end; 
 
procedure TBaseField.bfMouseDown(Sender: TObject; Button: TMouseButton; 
                                 Shift: TShiftState; X, Y: Integer); 
begin 
  if Parent is TPanel then begin 
    {If Sender is one of the StretchHandles, convert its coordinates to our own} 
    if Sender is TStretchHandle then 
      ConvertCoords(Sender as TStretchHandle, Self, X, Y); 
    (Parent as TPanel).OnMouseDown(Self, Button, Shift, X, Y); 
  end; 
end; 
 
procedure TBaseField.bfMouseUp(Sender: TObject; Button: TMouseButton; 
                               Shift: TShiftState; X, Y: Integer); 
begin 
  if Parent is TPanel then begin 
    {If Sender is one of the StretchHandles, convert its coordinates to our own} 
    if Sender is TStretchHandle then 
      ConvertCoords(Sender as TStretchHandle, Self, X, Y); 
    (Parent as TPanel).OnMouseUp(Self, Button, Shift, X, Y); 
  end; 
end; 
 
procedure TBaseField.bfMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); 
begin 
  if Parent is TPanel then begin 
    {If Sender is one of the StretchHandles, convert its coordinates to our own} 
    if Sender is TStretchHandle then begin 
      ConvertCoords(Sender as TStretchHandle, Self, X, Y); 
      if not (ssLeft in Shift) then 
        StretchMode := (Sender as TStretchHandle).HandlePosition; 
    end else 
      StretchMode := smDrag; 
    (Parent as TPanel).OnMouseMove(Self, Shift, X, Y); 
  end; 
end; 
 
procedure TBaseField.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); 
var 
  I : Integer; 
  P : TPoint; 
begin 
  inherited SetBounds(ALeft, ATop, AWidth, AHeight); 
 
  {Move all StretchHandles to the proper positions} 
  for I := Low(bfStretchHandles) to High(bfStretchHandles) do 
    if Assigned(bfStretchHandles[I]) then 
      with bfStretchHandles[I] do begin 
        P := GetStretchHandleCoords(HandlePosition); 
        SetBounds(P.X, P.Y, Width, Height); 
      end; 
end; 
 
procedure TBaseField.SetParent(AParent: TWinControl); 
 
  function CreateStretchHandle(WhichHandle : TStretchModes) : TStretchHandle; 
  var 
    P : TPoint; 
  begin 
    P := GetStretchHandleCoords(WhichHandle); 
    Result := TStretchHandle.Create(Self); 
    with Result do begin 
      HandlePosition := WhichHandle; 
      Parent         := AParent; 
      Visible        := Selected; 
      OnMouseDown    := bfMouseDown; 
      OnMouseUp      := bfMouseUp; 
      OnMouseMove    := bfMouseMove; 
      SetBounds(P.X, P.Y, Width, Height); 
    end; 
  end; 
 
const 
  ctStretchHandleCorners : 
    array[Low(TStretchHandleArray)..High(TStretchHandleArray)] of TStretchModes = 
      (smNW, smN, smNE, smE, smSE, smS, smSW, smW); 
var 
  I : Integer; 
begin 
  if AParent <> Parent then begin 
    inherited SetParent(AParent); 
    if Assigned(AParent) then begin 
      OnMouseDown := (AParent as TPanel).OnMouseDown; 
      OnMouseUp   := (AParent as TPanel).OnMouseUp; 
      OnMouseMove := (AParent as TPanel).OnMouseMove; 
      {If StretchHandles already exist, destroy them} 
      for I := Low(bfStretchHandles) to High(bfStretchHandles) do 
        if Assigned(bfStretchHandles[I]) then begin 
          bfStretchHandles[I].Free; 
          bfStretchHandles[I] := nil; 
        end; 
      {Create new StretchHandles} 
      for I := Low(bfStretchHandles) to High(bfStretchHandles) do 
        bfStretchHandles[I] := CreateStretchHandle(ctStretchHandleCorners[I]); 
    end else begin 
      OnMouseDown := nil; 
      OnMouseUp   := nil; 
      OnMouseMove := nil; 
    end; 
  end; 
end; 
 
procedure TBaseField.SetSelected(IsSelected : Boolean); 
var 
  I : Integer; 
begin 
  if IsSelected <> FSelected then begin 
    FSelected := IsSelected; 
    for I := Low(bfStretchHandles) to High(bfStretchHandles) do 
      with bfStretchHandles[I] do begin 
        Visible := FSelected; 
        {BringToFront to ensure that if this is a TImageField, StretchHandle 
         isn't partially hidden behind the image} 
        if FSelected then 
          BringToFront; 
      end; 
    Refresh; 
  end; 
end; 
 
function TBaseField.GetStretchHandleCoords(WhichHandle : TStretchModes) : TPoint; 
  {-Returns the coordinates (Left, Top) where the StretchHandle should be drawn} 
var 
  Offset : Integer; 
begin 
  with Result do 
    case WhichHandle of 
      smNW : begin 
               Offset := ctStretchHandleSize div 2; 
               X      := Left - Offset; 
               Y      := Top - Offset; 
             end; 
      smN  : begin 
               Offset := ctStretchHandleSize div 2; 
               X      := Left + (Width div 2) - Offset; 
               Y      := Top - Offset; 
             end; 
      smNE : begin 
               Offset := (ctStretchHandleSize + 1) div 2; 
               X      := Left + Width - Offset; 
               Offset := ctStretchHandleSize div 2; 
               Y      := Top - Offset; 
             end; 
      smE  : begin 
               Offset := (ctStretchHandleSize + 1) div 2; 
               X      := Left + Width - Offset; 
               Offset := ctStretchHandleSize div 2; 
               Y      := Top + (Height div 2) - Offset; 
             end; 
      smSE : begin 
               Offset := (ctStretchHandleSize + 1) div 2; 
               X      := Left + Width - Offset; 
               Y      := Top + Height - Offset; 
             end; 
      smS  : begin 
               Offset := ctStretchHandleSize div 2; 
               X      := Left + (Width div 2) - Offset; 
               Offset := (ctStretchHandleSize + 1) div 2; 
               Y      := Top + Height - Offset; 
             end; 
      smSW : begin 
               Offset := ctStretchHandleSize div 2; 
               X      := Left - Offset; 
               Offset := (ctStretchHandleSize + 1) div 2; 
               Y      := Top + Height - Offset; 
             end; 
      smW  : begin 
               Offset := ctStretchHandleSize div 2; 
               X      := Left - Offset; 
               Y      := Top + (Height div 2) - Offset; 
             end; 
      else begin 
        X := 0; 
        Y := 0; 
      end; 
    end; 
end; 
 
procedure TBaseField.Read(Stream : TStream); 
var 
  FieldRec : TFieldRecord; 
begin 
  Stream.ReadBuffer(FieldRec, SizeOf(FieldRec)); 
  if Parent is TFaxPanel then 
    with (Parent as TFaxPanel), FieldRec do begin 
      Self.Left   := HorzInchesToPixels(frLeftInches); 
      Self.Top    := VertInchesToPixels(frTopInches); 
      Self.Width  := HorzInchesToPixels(frWidthInches); 
      Self.Height := VertInchesToPixels(frHeightInches); 
    end; 
end; 
 
procedure TBaseField.Write(Stream : TStream); 
var 
  FieldRec : TFieldRecord; 
begin 
  FillChar(FieldRec, SizeOf(FieldRec), 0); 
  if Parent is TFaxPanel then 
    with (Parent as TFaxPanel), FieldRec do begin 
      frLeftInches   := HorzPixelsToInches(Self.Left); 
      frTopInches    := VertPixelsToInches(Self.Top); 
      frWidthInches  := HorzPixelsToInches(Self.Width); 
      frHeightInches := VertPixelsToInches(Self.Height); 
    end; 
  Stream.WriteBuffer(FieldRec, SizeOf(FieldRec)); 
end; 
 
 
{*** TTextField ***} 
 
constructor TTextField.Create(AOwner: TComponent); 
const 
  ctDefWidth = 200; 
var 
  Items1:TMenuItem; 
begin 
  inherited Create(AOwner); 
 
  Pen.Style := psClear;  {Don't need the TShape border because FMemo will have a border} 
  FpopupMenu:=TPopupMenu.Create(self); 
  Items1:=TMenuItem.Create(self); 
  Items1.Caption:='载入文本'; 
  Items1.OnClick:=OnLoadFromFile; 
  FPopupmenu.Items.Add(items1); 
  FMemo := TMemo.Create(Self); 
  FMemo.PopupMenu:=FPopupMenu; 
  with FMemo do begin 
    Ctl3D       := False; 
    ParentCtl3D := False; 
    WordWrap    := True; 
    OnMouseDown := bfMouseDown; 
    OnMouseUp   := bfMouseUp; 
    OnMouseMove := bfMouseMove; 
    OnDblClick  := MemoDblClick; 
    OnEnter     := tfEnter; 
    OnExit      := tfExit; 
  end; 
  FMemo.Font.Name:='宋体'; 
  FMemo.Font.Size:=11; 
  SetBounds(Left, Top, ctDefWidth, Height); 
end; 
 
procedure TTextField.Draw(ACanvas : TCanvas); 
 
  procedure ReplaceTags(TagStr     : string; 
                  const ReplaceStr : string; 
                    var TargetStr  : string); 
  var 
    Posn    : Integer; 
    TempStr : string; 
  begin 
    TagStr := UpperCase(TagStr); 
    repeat 
      TempStr := UpperCase(TargetStr); 
      Posn    := Pos(TagStr, TempStr); 
      if Posn > 0 then begin 
        Delete(TargetStr, Posn, Length(TagStr)); 
        Insert(ReplaceStr, TargetStr, Posn); 
      end; 
    until Posn = 0; 
  end; 
 
var 
  I          : Integer; 
  X, Y       : Integer; 
  TextHeight : Integer; 
  S          : string; 
  DateStr    : string; 
  TimeStr    : string; 
begin 
  with FMemo do begin 
    ACanvas.Font := Font; 
    TextHeight := GetTextHeight; 
 
    {Format date string to use for $D replacement tag} 
    DateStr := DateToStr(Date); 
    {Format time string to use for $T replacement tag} 
    TimeStr := TimeToStr(Time); 
    Delete(TimeStr, Length(TimeStr) - 5, 4);  {Strip off the seconds} 
    TimeStr := LowerCase(TimeStr); {Convert AM or PM to lower case} 
 
    X := Round((Parent as TFaxPanel).DrawAdjustFactor * Self.Left); 
    for I := 0 to Lines.Count - 1 do begin 
      S := Lines[I]; 
      {Look for replaceable tags and do replacements as required} 
      ReplaceTags('$D', DateStr, S); 
      ReplaceTags('$T', TimeStr, S); 
      ReplaceTags('$N', IntToStr((Parent as TFaxPanel).PageCount), S); 
      ReplaceTags('$P', IntToStr((Parent as TFaxPanel).PageNumber), S); 
      ReplaceTags('$F', (Parent as TFaxPanel).Sender, S); 
      ReplaceTags('$R', (Parent as TFaxPanel).Recipient, S); 
      ReplaceTags('$S', (Parent as TFaxPanel).PageTitle, S); 
      ReplaceTags('$I', (Parent as TFaxPanel).StationID, S); 
 
      Y := Round((Parent as TFaxPanel).DrawAdjustFactor * (Self.Top + (I * TextHeight))); 
      ACanvas.TextOut(X, Y, S); 
    end; 
  end; 
end; 
 
procedure TTextField.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); 
begin 
  inherited SetBounds(ALeft, ATop, AWidth, AHeight); 
 
  if Assigned(FMemo) then 
    FMemo.SetBounds(ALeft, ATop, AWidth, AHeight); 
end; 
 
procedure TTextField.SetParent(AParent: TWinControl); 
var 
  NewHeight : Integer; 
begin 
  inherited SetParent(AParent); 
 
  if Assigned(FMemo) then begin 
    FMemo.Parent := AParent; 
    {If no text has yet been entered, get the height of one row of text for the 
     current font, and adjust the field height to match} 
    if (FMemo.Text = '') and Assigned(AParent) then begin 
      NewHeight := GetTextHeight + 4; 
      {If SnapToGrid is enabled, adjust height to fall on a grid line} 
      with Parent as TFaxPanel do 
        if SnapToGrid then 
          AdjustHeightToGrid(Top, NewHeight); 
      SetBounds(Left, Top, Width, NewHeight); 
    end; 
    if AParent is TFaxPanel then 
      FMemo.OnChange := (AParent as TFaxPanel).FieldChange; 
  end; 
end; 
 
procedure TTextField.SetFocus; 
begin 
  FMemo.SetFocus; 
end; 
 
function TTextField.GetTextHeight : Integer; 
var 
  Canvas     : TCanvas; 
  TextMetric : TTextMetric; 
begin 
  Canvas := TCanvas.Create; 
  try 
    Canvas.Handle := GetDC(FMemo.Handle); 
    try 
      Canvas.Font := FMemo.Font; 
      GetTextMetrics(Canvas.Handle, TextMetric); 
      with TextMetric do 
        Result := tmHeight + tmExternalLeading; 
    finally 
      ReleaseDC(FMemo.Handle, Canvas.Handle); 
    end; 
  finally 
    Canvas.Free; 
  end; 
end; 
 
procedure TTextField.MemoDblClick(Sender: TObject); 
var 
  NewHeight  : Integer; 
  LineCount  : Integer; 
  FontDialog : TFontDialog; 
begin 
  FontDialog := TFontDialog.Create(nil); 
  try 
    FontDialog.Font := FMemo.Font; 
    if FontDialog.Execute then begin 
      FMemo.Font := FontDialog.Font; 
 
      {Adjust field height to allow for the new font size} 
      LineCount := FMemo.Lines.Count; 
      if LineCount < 1 then 
        LineCount := 1; 
      NewHeight := GetTextHeight * LineCount + 4; 
      Constrain(NewHeight, 0, (Parent as TWinControl).Height - Top); 
      {If SnapToGrid is enabled, adjust height to fall on a grid line} 
      if (Parent as TFaxPanel).SnapToGrid then 
        (Parent as TFaxPanel).AdjustHeightToGrid(Top, NewHeight); 
      SetBounds(Left, Top, Width, NewHeight); 
 
      if FSelected then begin 
        (Parent as TFaxPanel).FieldPositionChange(Left, Top, Width, Height); 
        {Set Ruler position marks to the new coordinates} 
        if (Parent as TFaxPanel).Owner is TFaxDesigner then 
          ((Parent as TFaxPanel).Owner as TFaxDesigner).SetMarkPositions(Left, Top, Width, Height); 
      end; 
    end; 
 
    if Parent is TFaxPanel then 
      (Parent as TFaxPanel).FieldChange(nil); 
  finally 
    FontDialog.Free; 
  end; 
end; 
 
procedure TTextField.OnLoadFromFile(Sender: TObject); 
var 
  OpenDialog : TOpenDialog; 
begin 
  OpenDialog := TOpenDialog.Create(nil); 
  OpenDialog.Filter:='文本文件 (*.txt)|*.TXT|所有文件(*.*)|*.*'; 
  if OpenDialog.Execute then 
  begin 
    FMemo.WordWrap:=true; 
    FMemo.Lines.LoadFromFile(OpenDialog.FileName); 
  end; 
end; 
function TTextField.GetText : string; 
begin 
  if Assigned(FMemo) then 
    Result := FMemo.Text 
  else 
    Result := ''; 
end; 
 
type 
  TLocalMemo = class(TMemo); 
 
procedure TTextField.tfEnter(Sender : TObject); 
var 
  PF : {$IFDEF DELPHI3}TCustomForm{$ELSE}TForm{$ENDIF}; 
begin 
  if (Parent as TFaxPanel).EditMode then begin 
    TLocalMemo(FMemo).SetDesigning(False); 
  end else begin 
    PF := GetParentForm(FMemo); 
    PF.DefocusControl(FMemo, False); 
    TLocalMemo(FMemo).SetDesigning(True); 
  end; 
end; 
 
procedure TTextField.tfExit(Sender : TObject); 
begin 
  TLocalMemo(FMemo).SetDesigning(True); 
end; 
 
procedure TTextField.Read(Stream : TStream); 
var 
  BufSize : LongInt; 
  Buffer  : PChar; 
  FontRec : TFontRecord; 
begin 
  {Read BaseField properties} 
  inherited Read(Stream); 
 
  {Read the font properties and assign them to TMemo.Font} 
  Stream.ReadBuffer(FontRec, SizeOf(FontRec)); 
  with FMemo.Font, FontRec do begin 
    {$IFDEF DELPHI3} 
    CharSet := TFontCharSet(frCharSet); 
    {$ENDIF} 
    Color   := TColor(frColor); 
    Height  := frHeight; 
    Name    := frName; 
    Pitch   := TFontPitch(frPitch); 
    Size    := frSize; 
    Style   := []; 
    if frFontBold then 
      Style := Style + [fsBold]; 
    if frFontItalic then 
      Style := Style + [fsItalic]; 
    if frFontUnderline then 
      Style := Style + [fsUnderline]; 
    if frFontStrikeout then 
      Style := Style + [fsStrikeout]; 
  end; 
 
  {Read the buffer size needed to store the text} 
  Stream.ReadBuffer(BufSize, SizeOf(BufSize)); 
 
  {If text exists, read it into the buffer and assign it to the TMemo} 
  if BufSize > 1 then begin 
    GetMem(Buffer, BufSize); 
    try 
      FillChar(Buffer^, BufSize, 0); 
      Stream.ReadBuffer(Buffer^, BufSize); 
      FMemo.Text := StrPas(Buffer); 
    finally 
      FreeMem(Buffer, BufSize); 
    end; 
  end; 
end; 
 
procedure TTextField.Write(Stream : TStream); 
var 
  FieldType : Byte; 
  BufSize   : LongInt; 
  Buffer    : PChar; 
  FontRec   : TFontRecord; 
begin 
  {First thing to write out is the field type} 
  FieldType := ftTextField; 
  Stream.WriteBuffer(FieldType, SizeOf(FieldType)); 
 
  {Write out BaseField properties} 
  inherited Write(Stream); 
 
  {Initialize FontRec with the font properties and write it out} 
  with FMemo.Font, FontRec do begin 
    {$IFDEF DELPHI3} 
    frCharSet       := Ord(CharSet); 
    {$ELSE} 
    frCharSet       := 0; 
    {$ENDIF} 
    frColor         := Color; 
    frHeight        := Height; 
    frName          := Name; 
    frPitch         := Ord(Pitch); 
    frSize          := Size; 
    frFontBold      := fsBold in Style; 
    frFontItalic    := fsItalic in Style; 
    frFontUnderline := fsUnderline in Style; 
    frFontStrikeout := fsStrikeout in Style; 
  end; 
  Stream.WriteBuffer(FontRec, SizeOf(FontRec)); 
 
  {Find out how big a buffer we need, and write out the buffer size} 
  BufSize := FMemo.GetTextLen + 1; {Add one to allow for null character} 
  Stream.WriteBuffer(BufSize, SizeOf(BufSize)); 
 
  {If the buffer isn't empty, get the memo text and write it out} 
  if BufSize > 1 then begin 
    GetMem(Buffer, BufSize); 
    try 
      FillChar(Buffer^, BufSize, 0); 
      FMemo.GetTextBuf(Buffer, BufSize); 
      Stream.WriteBuffer(Buffer^, BufSize); 
    finally 
      FreeMem(Buffer, BufSize); 
    end; 
  end; 
end; 
 
{------------------------------ TImageField --------------------------------} 
 
constructor TImageField.Create(AOwner: TComponent); 
const 
  DefWidth  = 120; 
  DefHeight = 120; 
begin 
  inherited Create(AOwner); 
  FImage := TImage.Create(Self); 
  with FImage do begin 
    Stretch     := True; 
    OnMouseDown := bfMouseDown; 
    OnMouseUp   := bfMouseUp; 
    OnMouseMove := bfMouseMove; 
    OnDblClick  := ImageDblClick; 
  end; 
  SetBounds(Left, Top, DefWidth, DefHeight); 
end;   
 
procedure TImageField.Draw(ACanvas : TCanvas); 
var 
  AdjustFactor : Double; 
begin 
  if not FImage.Picture.Bitmap.Empty then begin 
    AdjustFactor := (Parent as TFaxPanel).DrawAdjustFactor; 
    ACanvas.StretchDraw(Rect(Round(Left * AdjustFactor), Round(Top * AdjustFactor), 
                             Round((Left + Width) * AdjustFactor), 
                             Round((Top + Height) * AdjustFactor)), 
                        FImage.Picture.Bitmap); 
  end; 
end; 
 
function TImageField.GetPicture : TPicture; 
begin 
  if Assigned(FImage) then 
    Result := FImage.Picture 
  else 
    Result := nil; 
end; 
 
procedure TImageField.ImageDblClick(Sender: TObject); 
var 
  {$IFDEF DELPHI3} 
  PictureDialog : TOpenPictureDialog; 
  {$ELSE} 
  PictureDialog : TOpenDialog; 
  {$ENDIF} 
  I             : Integer; 
  Ext:String; 
  image1:TImage; 
  bmp:TBitmap; 
begin 
  {$IFDEF DELPHI3} 
  PictureDialog := TOpenPictureDialog.Create(nil); 
  {$ELSE} 
  PictureDialog := TOpenDialog.Create(nil); 
  {$ENDIF} 
  try 
    {$IFNDEF DELPHI3} 
    PictureDialog.Filter := 'Bitmap files|*.BMP'; 
    {$ENDIF} 
    PictureDialog.Options := [ofHideReadOnly, ofFileMustExist, 
                              ofPathMustExist, ofNoChangeDir]; 
    if PictureDialog.Execute then begin 
      Ext:=Uppercase(ExtractFileExt(PictureDialog.FileName)); 
       if (Ext='.JPG') or (Ext='.JPEG') then 
        begin 
          image1:=Timage.Create(self); 
          image1.picture.loadfromfile(pictureDialog.filename); 
          bmp:=TBitmap.create; 
          bmp.assign(TJPEGImage(image1.picture.Graphic)); 
          Fimage.Picture.Bitmap:=bmp; 
        end 
        else 
        FImage.Picture.LoadFromFile(PictureDialog.FileName); 
        FImage.Visible := True; 
        {Bring all StretchHandles to front so they draw on top of the image} 
        for I := Low(bfStretchHandles) to High(bfStretchHandles) do 
          bfStretchHandles[I].BringToFront; 
        if Parent is TFaxPanel then 
          (Parent as TFaxPanel).FieldChange(nil); 
    end; 
  finally 
    PictureDialog.Free; 
  end; 
end; 
 
procedure TImageField.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); 
begin 
  inherited SetBounds(ALeft, ATop, AWidth, AHeight); 
  if Assigned(FImage) then 
    FImage.SetBounds(ALeft, ATop, AWidth, AHeight); 
end; 
 
procedure TImageField.SetParent(AParent: TWinControl); 
begin 
  inherited SetParent(AParent); 
  if Assigned(FImage) then 
    FImage.Parent := AParent; 
end; 
 
type 
  TLocalBitmap = class(TBitmap); 
 
procedure TImageField.Read(Stream : TStream); 
var 
  IsEmpty : Boolean; 
begin 
  {Read BaseField properties} 
  inherited Read(Stream); 
 
  {Read the IsEmpty value to determine if a bitmap exists} 
  Stream.ReadBuffer(IsEmpty, SizeOf(IsEmpty)); 
 
  {If we have a bitmap, read it in} 
  if not IsEmpty then 
    TLocalBitmap(FImage.Picture.Bitmap).ReadData(Stream); 
    {ReadData is used because when using SaveToStream/LoadFromStream, 
     LoadFromStream assumes that the bitmap occupies the remaining data 
     in the stream, therefor no other items can be stored after the bitmap. 
     ReadData first reads in the size of the bitmap. 
     The WriteData/ReadData routines are protected, but the type-cast using 
     a local class alias allow us to access them anyway} 
    {FImage.Picture.Bitmap.LoadFromStream(Stream);} 
end; 
 
procedure TImageField.Write(Stream : TStream); 
var 
  FieldType : Byte; 
  IsEmpty   : Boolean; 
begin 
  {First thing to write out is the field type} 
  FieldType := ftImageField; 
  Stream.WriteBuffer(FieldType, SizeOf(FieldType)); 
 
  {Write out BaseField properties} 
  inherited Write(Stream); 
 
  {Determine whether a Bitmap is assigned and write this boolean value out} 
  IsEmpty := FImage.Picture.Bitmap.Empty; 
  Stream.WriteBuffer(IsEmpty, SizeOf(IsEmpty)); 
 
  {If we have a bitmap, write it out} 
  if not IsEmpty then 
    TLocalBitmap(FImage.Picture.Bitmap).WriteData(Stream); 
    {WriteData is used because when using SaveToStream/LoadFromStream, 
     LoadFromStream assumes that the bitmap occupies the remaining data 
     in the stream, therefor no other items can be stored after the bitmap. 
     WriteData first writes out the size of the bitmap data. 
     The WriteData/ReadData routines are protected, but the type-cast using 
     a local class alias allow us to access them anyway} 
    {FImage.Picture.Bitmap.SaveToStream(Stream);} 
end; 
 
 
{*** TFaxPanel ***} 
 
constructor TFaxPanel.Create(AOwner : TComponent); 
begin 
  inherited Create(AOwner); 
  FGridSpacingX     := ctGridSpacingX; 
  FGridSpacingY     := ctGridSpacingY; 
  OnResize          := fpResize; 
  OnMouseDown       := fpMouseDown; 
  OnMouseUp         := fpMouseUp; 
  OnMouseMove       := fpMouseMove; 
  fpFieldList       := TList.Create; 
end; 
 
destructor TFaxPanel.Destroy; 
begin 
  {Destroy all items in fpFieldList} 
  DeleteAllFields; 
 
  {Now destroy the list itself} 
  fpFieldList.Free; 
  inherited Destroy; 
end; 
 
function TFaxPanel.GetFieldCount : Integer; 
begin 
  Result := fpFieldList.Count; 
end; 
 
function TFaxPanel.GetField(Index : Integer) : TBaseField; 
begin 
  Result := TBaseField(fpFieldList[Index]); 
end; 
 
function TFaxPanel.GetSelectedField : TBaseField; 
var 
  I      : Integer; 
begin 
  for I := fpFieldList.Count - 1 downto 0 do begin 
    Result := fpFieldList[I]; 
    if Result.Selected then 
      Exit; 
  end; 
  Result := nil; 
end; 
 
procedure TFaxPanel.SetEditMode(Value : Boolean); 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  if Value <> FEditMode then begin 
    FEditMode := Value; 
    for I := fpFieldList.Count - 1 downto 0 do begin 
      Field := fpFieldList[I]; 
      if Field.Selected and (Field is TTextField) then begin 
        TTextField(Field).tfEnter(nil); 
        if Value then 
          TTextField(Field).SetFocus; 
      end; 
    end; 
  end; 
end; 
 
procedure TFaxPanel.SetPageWidthInches(AWidth : Double); 
begin 
  if AWidth <> FPageWidthInches then begin 
    FPageWidthInches := AWidth; 
    {Recalc pixels per inch and post position messages if necessary} 
    SetBounds(Left, Top, Width, Height); 
    FNeedsSaving := True; 
  end; 
end; 
 
procedure TFaxPanel.SetPageHeightInches(AHeight : Double); 
begin 
  if AHeight <> FPageHeightInches then begin 
    FPageHeightInches := AHeight; 
    {SetBounds recalcs pixels per inch and calls OnFieldPositionChange if 
     necessary} 
    SetBounds(Left, Top, Width, Height); 
    FNeedsSaving := True; 
  end; 
end;   
 
procedure TFaxPanel.SetShowGrid(AShowGrid : Boolean); 
begin 
  if AShowGrid <> FShowGrid then begin 
    FShowGrid := AShowGrid; 
    Invalidate; 
  end; 
end; 
 
procedure TFaxPanel.SetSnapToGrid(ASnapToGrid : Boolean); 
var 
  I         : Integer; 
  NewLeft   : Integer; 
  NewTop    : Integer; 
  NewWidth  : Integer; 
  NewHeight : Integer; 
  Field     : TBaseField; 
begin 
  if ASnapToGrid <> FSnapToGrid then begin 
    FSnapToGrid := ASnapToGrid; 
    {If SnapToGrid was just turned on, force all existing fields to snap to the grid} 
    if FSnapToGrid then begin 
      for I := 0 to fpFieldList.Count - 1 do begin 
        Field     := fpFieldList[I]; 
        NewLeft   := Field.Left; 
        NewTop    := Field.Top; 
        NewWidth  := Field.Width; 
        NewHeight := Field.Height; 
        {Adjust coordinates to be on grid lines} 
        AdjustLeftToGrid(NewLeft); 
        AdjustTopToGrid(NewTop); 
        AdjustWidthToGrid(NewLeft, NewWidth); 
        AdjustHeightToGrid(NewTop, NewHeight); 
        Field.SetBounds(NewLeft, NewTop, NewWidth, NewHeight); 
      end; 
      FNeedsSaving := True; 
    end; 
  end; 
end; 
 
procedure TFaxPanel.SetGridSpacingX(GridSpacing : Integer); 
begin 
  if (GridSpacing > 0) and (GridSpacing <> FGridSpacingX) then begin 
    FGridSpacingX := GridSpacing; 
    fpResize(nil);  {Recalculate fpMaxGridLine} 
    if FSnapToGrid then begin 
      {Turn SnapToGrid off and back on to force all fields to align to the new grid size} 
      SetSnapToGrid(False); 
      SetSnapToGrid(True); 
    end; 
    if FShowGrid then 
      Invalidate; 
  end; 
end; 
 
procedure TFaxPanel.SetGridSpacingY(GridSpacing : Integer); 
begin 
  if (GridSpacing > 0) and (GridSpacing <> FGridSpacingY) then begin 
    FGridSpacingY := GridSpacing; 
    fpResize(nil);  {Recalculate fpMaxGridLine} 
    if FSnapToGrid then begin 
      {Turn SnapToGrid off and back on to force all fields to align to the new grid size} 
      SetSnapToGrid(False); 
      SetSnapToGrid(True); 
    end; 
    if FShowGrid then 
      Invalidate; 
  end; 
end; 
 
procedure TFaxPanel.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); 
 
  procedure UpdateFieldPositionsAndSizes(OldWidth, OldHeight : Integer); 
  var 
    I           : Integer; 
    NewLeft     : Integer; 
    NewTop      : Integer; 
    NewWidth    : Integer; 
    NewHeight   : Integer; 
    Field       : TBaseField; 
    WidthRatio  : Double; 
    HeightRatio : Double; 
  begin 
    if OldWidth = 0 then 
      WidthRatio := 0.0 
    else 
      WidthRatio  := Width / OldWidth; 
    if OldHeight = 0 then 
      HeightRatio := 0.0 
    else 
      HeightRatio  := Height / OldHeight; 
    for I := fpFieldList.Count - 1 downto 0 do begin 
      Field := fpFieldList[I]; 
      with Field do begin 
        NewLeft   := Round(Left * WidthRatio); 
        NewTop    := Round(Top * HeightRatio); 
        NewWidth  := Round(Width * WidthRatio); 
        NewHeight := Round(Height * HeightRatio); 
        AdjustLeftToGrid(NewLeft); 
        AdjustWidthToGrid(NewLeft, NewWidth); 
        AdjustTopToGrid(NewTop); 
        AdjustHeightToGrid(NewTop, NewHeight); 
        SetBounds(NewLeft, NewTop, NewWidth, NewHeight); 
        if Selected then begin 
          FieldPositionChange(Left, Top, Width, Height); 
          {Set Ruler position marks to the new coordinates} 
          if Self.Owner is TFaxDesigner then 
            (Self.Owner as TFaxDesigner).SetMarkPositions(Left, Top, Width, Height); 
        end; 
      end; 
    end; 
  end;   
 
var 
  OldWidth  : Integer; 
  OldHeight : Integer; 
begin 
  OldWidth  := Width; 
  Oldheight := Height; 
  inherited SetBounds(ALeft, ATop, AWidth, AHeight); 
 
  if FPageWidthInches = 0.0 then 
    fpHorzPixelsPerInch := 0.0 
  else 
    fpHorzPixelsPerInch := Width / FPageWidthInches; 
  if FPageHeightInches = 0.0 then 
    fpVertPixelsPerInch := 0.0 
  else 
    fpVertPixelsPerInch := Height / FPageHeightInches; 
 
  {Move and resize all fields so they retain the same relative positions and 
   sizes in relation to the FaxPanel size} 
  if Assigned(fpFieldList) then 
    UpdateFieldPositionsAndSizes(OldWidth, OldHeight); 
end; 
 
procedure TFaxPanel.Paint; 
var 
  X, Y : Integer; 
begin 
  inherited Paint; 
  if FShowGrid then begin 
    X := ctGridStart; 
    with Canvas do 
      while X < Width do begin 
        {To improve painting performance, don't draw anything that isn't 
         within the current ClipRect} 
        if (ClipRect.Left <= X) and (X <= ClipRect.Right) then begin 
          Y := ctGridStart; 
          while Y < Height do begin 
            {To improve painting performance, don't draw anything that isn't 
             within the current ClipRect} 
            if (ClipRect.Top <= Y) and (Y <= ClipRect.Bottom) then 
              Canvas.Pixels[X,Y] := clBlack; 
            Y := Y + FGridSpacingY; 
          end; 
        end; 
        X := X + FGridSpacingX; 
      end; 
  end; 
end; 
 
procedure TFaxPanel.fpResize(Sender : TObject); 
var 
  Extent      : Integer; 
  NrGridLines : Integer; 
begin 
  {Calculate the coordinates of the rightmost and bottommost grid lines given 
   the current panel size, and store the results in fpMaxGridLine} 
  Extent := Width - ctGridStart; 
  NrGridLines := Extent div FGridSpacingX; 
  fpMaxGridLine.X := (NrGridLines * FGridSpacingX) + ctGridStart; 
  Extent := Height - ctGridStart; 
  NrGridLines := Extent div FGridSpacingY; 
  fpMaxGridLine.Y := (NrGridLines * FGridSpacingY) + ctGridStart; 
end; 
 
procedure TFaxPanel.fpMouseDown(Sender: TObject; 
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
begin 
  {If user clicked on a field, translate the coordinates to MainPanel 
   coordinates and set fpMouseAnchor to those coordinates} 
  if Sender is TBaseField then begin 
    ConvertCoords(Sender as TControl, Self, X, Y); 
    fpMouseAnchor := Point(X, Y); 
    if (Button = mbLeft) and not (ssDouble in Shift) then 
      fpIsMouseDown := True; 
  end else 
    fpMouseAnchor := Point(X, Y); 
end; 
 
procedure TFaxPanel.fpMouseUp(Sender: TObject; Button: TMouseButton; 
  Shift: TShiftState; X, Y: Integer); 
var 
  IsFieldSelected : Boolean; 
begin 
  case Button of 
    mbRight : if Sender is TControl then begin 
                ConvertCoords(Sender as TControl, Self, X, Y); 
                fpMouseAnchor := Point(X, Y); 
              end; 
    mbLeft : 
      begin 
        DeselectAllFields; 
        IsFieldSelected := False;  {No fields are currently selected} 
 
        {If user clicked on a field, mark it as selected} 
        if Sender is TBaseField then begin 
          IsFieldSelected := True; 
          fpIsMouseDown   := False; 
          with Sender as TBaseField do begin 
            Selected := True; 
            {Set Ruler position marks to the new coordinates} 
            (Self.Owner as TFaxDesigner).SetMarkPositions(Left, Top, Width, Height); 
          end; 
        end else 
          {Turn Ruler position marks off since no fields are selected} 
          (Self.Owner as TFaxDesigner).SetMarkPositions(-1, -1, -1, -1); 
 
        FieldSelectionChange(IsFieldSelected); 
        if IsFieldSelected then 
          with Sender as TBaseField do 
            FieldPositionChange(Left, Top, Width, Height); 
      end; 
  end; 
end; 
 
procedure TFaxPanel.fpMouseMove(Sender: TObject; Shift: TShiftState; 
  X, Y: Integer); 
var 
  XDiff     : Integer; 
  YDiff     : Integer; 
  OldLeft   : Integer; 
  OldTop    : Integer; 
  NewLeft   : Integer; 
  NewTop    : Integer; 
  NewWidth  : Integer; 
  NewHeight : Integer; 
begin 
  if Sender is TBaseField then begin 
    if fpIsMouseDown and (ssLeft in Shift) then begin 
      if fpDragging then 
        Exit; 
      fpDragging := True; 
      try 
        case (Sender as TBaseField).StretchMode of 
          smDrag : 
            begin 
              {TextFields can't be moved while in Edit Mode} 
              if FEditMode and (Sender is TTextField) then 
                Exit; 
              ConvertCoords(Sender as TControl, Self, X, Y); 
              Constrain(X, 0, Width); 
              Constrain(Y, 0, Height); 
              XDiff := X - fpMouseAnchor.X; 
              YDiff := Y - fpMouseAnchor.Y; 
              with Sender as TBaseField do begin 
                NewLeft := Left + XDiff; 
                NewTop  := Top + YDiff; 
                {Ensure field remains entirely within Self} 
                Constrain(NewLeft, 0, Self.Width  - Width); 
                Constrain(NewTop,  0, Self.Height - Height); 
 
                if FSnapToGrid then begin 
                  {Adjust NewLeft and NewTop to be on grid lines if necessary} 
                  AdjustLeftToGrid(NewLeft); 
                  AdjustTopToGrid(NewTop); 
 
                  {Make sure we haven't moved past the rightmost or bottommost grid line} 
                  if NewLeft + Width - 1 > fpMaxGridLine.X then 
                    NewLeft := NewLeft - FGridSpacingX; 
                  if NewTop + Height - 1 > fpMaxGridLine.Y then 
                    NewTop := NewTop - FGridSpacingY; 
                end; 
 
                OldLeft := Left; 
                OldTop  := Top; 
                SetBounds(NewLeft, NewTop, Width, Height); 
              end; 
              {Set fpMouseAnchor to new mouse position, but ONLY if the field 
               position has changed. If SnapToGrid is enabled, the field position 
               might not have changed even though the mouse position did.} 
              fpMouseAnchor.X := fpMouseAnchor.X + NewLeft - OldLeft; 
              fpMouseAnchor.Y := fpMouseAnchor.Y + NewTop - OldTop; 
            end; 
          smE : 
            with Sender as TBaseField do begin 
              NewWidth := X; 
              Constrain(NewWidth, 0, Self.Width - Left); 
              AdjustWidthToGrid(Left, NewWidth); 
 
              SetBounds(Left, Top, NewWidth, Height); 
              if Width <= 1 then 
                StretchMode := smW; 
            end; 
          smW : 
            with Sender as TBaseField do begin 
              NewLeft := Left + X; 
              {Prevent creeping to right when switching from smW to smE} 
              Constrain(NewLeft, 0, Left + Width); 
              AdjustLeftToGrid(NewLeft); 
 
              NewWidth := Width + Left - NewLeft; 
              Constrain(NewWidth, 0, Self.Width - NewLeft); 
              AdjustWidthToGrid(NewLeft, NewWidth); 
 
              SetBounds(NewLeft, Top, NewWidth, Height); 
              if Width <= 1 then 
                StretchMode := smE; 
            end; 
          smS : 
            with Sender as TBaseField do begin 
              NewHeight := Y; 
              Constrain(NewHeight, 0, Self.Height - Top); 
              AdjustHeightToGrid(Top, NewHeight); 
 
              SetBounds(Left, Top, Width, NewHeight); 
              if Height <= 1 then 
                StretchMode := smN; 
            end; 
          smN : 
            with Sender as TBaseField do begin 
              NewTop := Top + Y; 
              {Prevent creeping down when switching from smN to smS} 
              Constrain(NewTop, 0, Top + Height); 
              AdjustTopToGrid(NewTop); 
 
              NewHeight := Height + Top - NewTop; 
              Constrain(NewHeight, 0, Self.Height - NewTop); 
              AdjustHeightToGrid(NewTop, NewHeight); 
 
              SetBounds(Left, NewTop, Width, NewHeight); 
              if Height <= 1 then 
                StretchMode := smS; 
            end; 
          smNE : 
            with Sender as TBaseField do begin 
              NewTop := Top + Y; 
              {Prevent creeping down when switching from smN? to smS?} 
              Constrain(NewTop, 0, Top + Height); 
              AdjustTopToGrid(NewTop); 
 
              NewWidth := X; 
              Constrain(NewWidth, 0, Self.Width - Left); 
              AdjustWidthToGrid(Left, NewWidth); 
 
              NewHeight := Height + Top - NewTop; 
              Constrain(NewHeight, 0, Self.Height - NewTop); 
              AdjustHeightToGrid(NewTop, NewHeight); 
 
              SetBounds(Left, NewTop, NewWidth, NewHeight); 
              if Width <= 1 then begin 
                if Height <= 1 then 
                  StretchMode := smSW 
                else 
                  StretchMode := smNW; 
              end else if Height <= 1 then 
                StretchMode := smSE; 
            end; 
          smSW : 
            with Sender as TBaseField do begin 
              NewLeft := Left + X; 
              {Prevent creeping to right when switching from smW to smE} 
              Constrain(NewLeft, 0, Left + Width); 
              AdjustLeftToGrid(NewLeft); 
 
              NewWidth := Width + Left - NewLeft; 
              Constrain(NewWidth, 0, Self.Width - NewLeft); 
              AdjustWidthToGrid(NewLeft, NewWidth); 
 
              NewHeight := Y; 
              Constrain(NewHeight, 0, Self.Height - Top); 
              AdjustHeightToGrid(Top, NewHeight); 
 
              SetBounds(NewLeft, Top, NewWidth, NewHeight); 
              if Width <= 1 then begin 
                if Height <= 1 then 
                  StretchMode := smNE 
                else 
                  StretchMode := smSE; 
              end else if Height <= 1 then 
                StretchMode := smNW; 
            end; 
          smSE : 
            with Sender as TBaseField do begin 
              NewWidth := X; 
              Constrain(NewWidth, 0, Self.Width - Left); 
              AdjustWidthToGrid(Left, NewWidth); 
 
              NewHeight := Y; 
              Constrain(NewHeight, 0, Self.Height - Top); 
              AdjustHeightToGrid(Top, NewHeight); 
 
              SetBounds(Left, Top, NewWidth, NewHeight); 
              if Width <= 1 then begin 
                if Height <= 1 then 
                  StretchMode := smNW 
                else 
                  StretchMode := smSW; 
              end else if Height <= 1 then 
                StretchMode := smNE; 
            end; 
          smNW : 
            with Sender as TBaseField do begin 
              NewLeft := Left + X; 
              {Prevent creeping to right when switching from sm?W to sm?E} 
              Constrain(NewLeft, 0, Left + Width); 
              AdjustLeftToGrid(NewLeft); 
 
              NewWidth := Width + Left - NewLeft; 
              Constrain(NewWidth, 0, Self.Width - NewLeft); 
              AdjustWidthToGrid(NewLeft, NewWidth); 
 
              NewTop := Top + Y; 
              {Prevent creeping down when switching from smN? to smS?} 
              Constrain(NewTop, 0, Top + Height); 
              AdjustTopToGrid(NewTop); 
 
              NewHeight := Height + Top - NewTop; 
              Constrain(NewHeight, 0, Self.Height - NewTop); 
              AdjustHeightToGrid(NewTop, NewHeight); 
 
              SetBounds(NewLeft, NewTop, NewWidth, NewHeight); 
              if Width <= 1 then begin 
                if Height <= 1 then 
                  StretchMode := smSE 
                else 
                  StretchMode := smNE; 
              end else if Height <= 1 then 
                StretchMode := smSW; 
            end; 
        end; 
        Application.ProcessMessages; 
      finally 
        fpDragging := False; 
      end; 
 
      with Sender as TBaseField do 
        if Selected then begin 
          FieldPositionChange(Left, Top, Width, Height); 
          {Set Ruler position marks to the new coordinates} 
          if Self.Owner is TFaxDesigner then 
            (Self.Owner as TFaxDesigner).SetMarkPositions(Left, Top, Width, Height); 
        end; 
      FNeedsSaving := True; 
    end; 
    StretchMode := (Sender as TBaseField).StretchMode; 
 
  end else 
    StretchMode := smNone; 
end; 
 
procedure TFaxPanel.SizeMove(Sender : TObject; Key : Word; Shift : TShiftState); 
var 
  X, Y      : Integer; 
  NewWidth  : Integer; 
  NewHeight : Integer; 
  Delta     : TPoint; 
  I, J      : Integer; 
  BF        : TBaseField; 
begin 
  if Sender is TBaseField then begin 
    case Key of 
      VK_UP    : Delta := Point(0, -1); 
      VK_DOWN  : Delta := Point(0, 1); 
      VK_RIGHT : Delta := Point(1, 0); 
      VK_LEFT  : Delta := Point(-1, 0); 
    else 
      if (Key = VK_TAB) and (Shift = []) then begin 
        {select next object} 
        J := -1; 
        for I := 0 to FieldCount-1 do 
          if Field[I].Selected then begin 
            J := I; 
            Break; 
          end; 
        if J >= 0 then begin 
          Field[J].Selected := False; 
          Inc(J); 
          if J >= FieldCount then 
            J := 0; 
          Field[J].Selected := True; 
          Invalidate; 
        end; 
      end else if (Key = VK_TAB) and (Shift = [ssShift]) then begin 
        {select previous object} 
        J := -1; 
        for I := 0 to FieldCount-1 do 
          if Field[I].Selected then begin 
            J := I; 
            Break; 
          end; 
        if J >= 0 then begin 
          Field[J].Selected := False; 
          Dec(J); 
          if J < 0 then 
            J := FieldCount-1; 
          Field[J].Selected := True; 
          Invalidate; 
        end; 
      end; 
      Exit; 
    end; 
 
    BF := Sender as TBaseField; 
    if (ssShift in Shift) then begin 
      {size} 
      X := BF.Width + Delta.X; 
      Y := BF.Height + Delta.Y; 
      NewWidth := X; 
      NewHeight := Y; 
      Constrain(NewWidth, 0, Self.Width - BF.Left); 
      Constrain(NewHeight, 0, Self.Height - BF.Top); 
      BF.SetBounds(BF.Left, BF.Top, NewWidth, NewHeight); 
    end else if (ssCtrl in Shift) then begin 
      {move} 
      X := BF.Left + Delta.X; 
      Y := BF.Top + Delta.Y; 
      Constrain(X, 0, Width); 
      Constrain(Y, 0, Height); 
      {Ensure field remains entirely within Self} 
      Constrain(X, 0, Self.Width  - BF.Width); 
      Constrain(Y,  0, Self.Height - BF.Height); 
      BF.SetBounds(X, Y, BF.Width, BF.Height); 
    end; 
     
    with Sender as TBaseField do 
      if Selected then begin 
        FieldPositionChange(Left, Top, Width, Height); 
        {Set Ruler position marks to the new coordinates} 
        if Self.Owner is TFaxDesigner then 
          (Self.Owner as TFaxDesigner).SetMarkPositions(Left, Top, Width, Height); 
      end; 
    FNeedsSaving := True; 
  end; 
end; 
 
procedure TFaxPanel.FieldSelectionChange(IsFieldSelected : Boolean); 
  {-Calls OnFieldSelectionChange event handler when a field becomes 
    deselected or when a new field becomes selected} 
begin 
  if Assigned(FOnFieldSelectionChange) then 
    FOnFieldSelectionChange(IsFieldSelected); 
end; 
 
procedure TFaxPanel.FieldPositionChange(ALeft, ATop, AWidth, AHeight : Integer); 
  {-Calls OnFieldPositionChange event handler when the location or size of 
    the currently-selected field changes} 
begin 
  if Assigned(FOnFieldPositionChange) then 
    FOnFieldPositionChange(ALeft, ATop, AWidth, AHeight); 
end; 
 
procedure TFaxPanel.FieldChange(Sender : TObject); 
begin 
  FNeedsSaving := True; 
end; 
 
procedure TFaxPanel.AdjustLeftToGrid(var ALeft : Integer); 
begin 
  if FSnapToGrid then begin 
    if ALeft < ctGridStart then 
      ALeft := ctGridStart 
    else if ALeft > fpMaxGridLine.X then 
      ALeft := fpMaxGridLine.X 
    else 
      ALeft := (Round((ALeft - ctGridStart) / FGridSpacingX) * FGridSpacingX) + ctGridStart; 
  end; 
end; 
 
procedure TFaxPanel.AdjustTopToGrid(var ATop : Integer); 
begin 
  if FSnapToGrid then begin 
    if ATop < ctGridStart then 
      ATop := ctGridStart 
    else if ATop > fpMaxGridLine.Y then 
      ATop := fpMaxGridLine.Y 
    else 
      ATop := (Round((ATop - ctGridStart) / FGridSpacingY) * FGridSpacingY) + ctGridStart; 
  end; 
end; 
 
procedure TFaxPanel.AdjustWidthToGrid(ALeft : Integer; var AWidth : Integer); 
begin 
  if FSnapToGrid then begin 
    if AWidth < 0 then 
      AWidth := 0 
    else if ALeft + AWidth > fpMaxGridLine.X then 
      AWidth := fpMaxGridLine.X - ALeft 
    else 
      AWidth := Round((AWidth) / FGridSpacingX) * FGridSpacingX + 1; 
  end; 
end; 
 
procedure TFaxPanel.AdjustHeightToGrid(ATop : Integer; var AHeight : Integer); 
begin 
  if FSnapToGrid then begin 
    if AHeight < 0 then 
      AHeight := 0 
    else if ATop + AHeight > fpMaxGridLine.Y then 
      AHeight := fpMaxGridLine.Y - ATop 
    else 
      AHeight := Round((AHeight) / FGridSpacingY) * FGridSpacingY + 1; 
  end; 
end; 
 
function TFaxPanel.GetDrawAdjustFactor : Double; 
const 
  ctFaxWidthInPixels = 1728;  {Faxes are 1728 pixels in width} 
begin 
  if Width = 0 then 
    Result := 0.0 
  else 
    Result := ((ctFaxWidthInPixels / 2) - 10) / Width; 
end; 
 
function TFaxPanel.GetDrawWidth : Integer; 
begin 
  Result := Round(Width * DrawAdjustFactor); 
end; 
 
function TFaxPanel.GetDrawHeight : Integer; 
begin 
  Result := Round(Height * DrawAdjustFactor); 
end; 
 
procedure TFaxPanel.SetStretchMode(NewStretchMode : TStretchModes); 
begin 
  if NewStretchMode <> FStretchMode then begin 
    FStretchMode := NewStretchMode; 
    case FStretchMode of 
      smN, smS   : Cursor := crSizeNS; 
      smE, smW   : Cursor := crSizeWE; 
      smNW, smSE : Cursor := crSizeNWSE; 
      smNE, smSW : Cursor := crSizeNESW; 
      else Cursor := crDefault; 
    end; 
  end; 
end; 
 
procedure TFaxPanel.FieldPositionChangeForSelectedField; 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  for I := 0 to fpFieldList.Count - 1 do begin 
    Field := fpFieldList[I]; 
    with Field do 
      if Selected then begin 
        FieldPositionChange(Left, Top, Width, Height); 
        Break; {Only one field can be selected at a time and we just found it, so exit loop} 
      end; 
  end; 
end; 
 
procedure TFaxPanel.Write(Stream : TStream); 
var 
  I         : Integer; 
  NumFields : LongInt; 
  Field     : TBaseField; 
begin 
  {Write the number of fields to the stream} 
  NumFields := fpFieldList.Count; 
  Stream.WriteBuffer(NumFields, SizeOf(NumFields)); 
 
  {Write out each field's information} 
  for I := 0 to fpFieldList.Count - 1 do begin 
    Field := fpFieldList[I]; 
    Field.Write(Stream); 
  end; 
 
  {We just saved, so set NeedsSaving to False} 
  FNeedsSaving := False; 
end; 
 
procedure TFaxPanel.Read(Stream : TStream); 
var 
  FieldType : Byte; 
  I         : Integer; 
  NumFields : LongInt; 
  Field     : TBaseField; 
begin 
  {Clear out fpFieldList to ensure we're starting off with an empty FaxPanel} 
  DeleteAllFields; 
 
  {Read the number of fields that were written out to the stream} 
  Stream.ReadBuffer(NumFields, SizeOf(NumFields)); 
 
  {For each field, create a new field of the proper type, and then let it read 
   itself in} 
  for I := 1 to NumFields do begin 
    Field := nil; 
    Stream.ReadBuffer(FieldType, SizeOf(FieldType)); 
    case FieldType of 
      ftTextField  : Field := AddTextField; 
      ftImageField : Field := AddImageField; 
    end; 
    if Assigned(Field) then 
      Field.Read(Stream); 
  end; 
 
  {No changes have been made yet, so set NeedsSaving to False} 
  FNeedsSaving := False; 
end; 
 
procedure TFaxPanel.Draw(ACanvas : TCanvas); 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  {Draw each field} 
  for I := 0 to fpFieldList.Count - 1 do begin 
    Field := fpFieldList[I]; 
    Field.Draw(ACanvas); 
  end; 
end; 
 
function TFaxPanel.HorzPixelsToInches(P : Integer) : Double; 
begin 
  if fpHorzPixelsPerInch = 0.0 then 
    Result := 0.0 
  else 
    Result := P / fpHorzPixelsPerInch; 
end; 
 
function TFaxPanel.VertPixelsToInches(P : Integer) : Double; 
begin 
  if fpVertPixelsPerInch = 0.0 then 
    Result := 0.0 
  else 
    Result := P / fpVertPixelsPerInch; 
end; 
 
function TFaxPanel.HorzInchesToPixels(Inches : Double) : Integer; 
begin 
  Result := Round(Inches * fpHorzPixelsPerInch); 
end; 
 
function TFaxPanel.VertInchesToPixels(Inches : Double) : Integer; 
begin 
  Result := Round(Inches * fpVertPixelsPerInch); 
end; 
 
procedure TFaxPanel.DeselectAllFields; 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  for I := fpFieldList.Count - 1 downto 0 do begin 
    Field := fpFieldList[I]; 
    Field.Selected := False; 
  end; 
end; 
 
procedure TFaxPanel.DeleteAllFields; 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  for I := fpFieldList.Count - 1 downto 0 do begin 
    Field := fpFieldList[I]; 
    Field.Free; 
    fpFieldList.Remove(fpFieldList[I]); 
  end; 
  FNeedsSaving := True; 
end; 
 
procedure TFaxPanel.AddField(Field : TBaseField); 
var 
  NewLeft   : Integer; 
  NewTop    : Integer; 
  NewWidth  : Integer; 
  NewHeight : Integer; 
begin 
  DeselectAllFields; 
 
  NewLeft := fpMouseAnchor.X; 
  NewTop  := fpMouseAnchor.Y; 
  with Field do begin 
    {Update NewLeft and NewTop to ensure new field will be entirely within MainPanel} 
    Constrain(NewLeft, 0, Self.Width  - Width); 
    Constrain(NewTop,  0, Self.Height - Height); 
    NewWidth  := Width; 
    NewHeight := Height; 
    {If SnapToGrid is enabled, adjust coordinates to be on grid lines} 
    if FSnapToGrid then begin 
      AdjustLeftToGrid(NewLeft); 
      AdjustTopToGrid(NewTop); 
      AdjustWidthToGrid(NewLeft, NewWidth); 
      AdjustHeightToGrid(NewTop, NewHeight); 
    end; 
    SetBounds(NewLeft, NewTop, NewWidth, NewHeight); 
    Parent   := Self; 
    Visible  := True; 
    Selected := True; {New fields start out Selected} 
  end; 
  if Field is TTextField then 
    (Field as TTextField).SetFocus; 
  fpFieldList.Add(Field); 
 
  FieldSelectionChange(True); 
  with Field do 
    FieldPositionChange(Left, Top, Width, Height); 
 
  FNeedsSaving := True; 
 
  Inc(fpMouseAnchor.X, 5); 
  Inc(fpMouseAnchor.Y, 5); 
end; 
 
function TFaxPanel.AddTextField : TTextField; 
begin 
  Result := TTextField.Create(nil); 
  AddField(Result); 
end; 
 
function TFaxPanel.AddImageField : TImageField; 
begin 
  Result := TImageField.Create(nil); 
  AddField(Result); 
end; 
 
procedure TFaxPanel.DeleteSelectedField; 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  {Delete all fields that are currently selected} 
  for I := fpFieldList.Count - 1 downto 0 do begin 
    Field := fpFieldList[I]; 
    if Field.Selected then begin 
      Field.Free; 
      fpFieldList.Remove(fpFieldList[I]); 
      FNeedsSaving := True; 
    end; 
  end; 
  {Turn Ruler position marks off since no fields are selected} 
  if Self.Owner is TFaxDesigner then 
    (Self.Owner as TFaxDesigner).SetMarkPositions(-1, -1, -1, -1); 
end;   
 
procedure TFaxPanel.CenterSelectedField(IsHorizontal : Boolean); 
var 
  I      : Integer; 
  NewPos : Integer; 
  Field  : TBaseField; 
begin 
  for I := fpFieldList.Count - 1 downto 0 do begin 
    Field := fpFieldList[I]; 
    if Field.Selected then begin 
      if IsHorizontal then begin 
        NewPos := Round((Width - Field.Width) / 2); 
        AdjustLeftToGrid(NewPos);  {Align to grid if SnapToGrid is enabled} 
        Field.Left := NewPos; 
      end else begin 
        NewPos := Round((Height - Field.Height) / 2); 
        AdjustTopToGrid(NewPos);  {Align to grid if SnapToGrid is enabled} 
        Field.Top := NewPos; 
      end; 
      with Field do begin 
        FieldPositionChange(Left, Top, Width, Height); 
        {Set Ruler position marks to the new coordinates} 
        if Self.Owner is TFaxDesigner then 
          (Self.Owner as TFaxDesigner).SetMarkPositions(Left, Top, Width, Height); 
      end; 
      FNeedsSaving := True; 
      Break; 
    end; 
  end; 
end; 
 
function TFaxPanel.SelectedFieldsExist : Boolean; 
var 
  I     : Integer; 
  Field : TBaseField; 
begin 
  Result := False; 
  for I := 0 to fpFieldList.Count - 1 do begin 
    Field := fpFieldList[I]; 
    if Field.Selected then begin 
      Result := True; 
      Break; 
    end; 
  end; 
end; 
 
 
{------------------------------- TFaxScrollBox ------------------------------} 
 
procedure TFaxScrollBox.WMHScroll(var Message : TWMHScroll); 
var 
  Dummy : Integer; 
begin 
  inherited; 
 
  if Assigned(FOnHorzScroll) and (Message.ScrollBar = 0) and HorzScrollBar.Visible then 
    {Doesn't matter what parameters we pass because they aren't used} 
    FOnHorzScroll(nil, scTop, Dummy); 
end; 
 
procedure TFaxScrollBox.WMVScroll(var Message : TWMVScroll); 
var 
  Dummy : Integer; 
begin 
  inherited; 
  if Assigned(FOnVertScroll) and (Message.ScrollBar = 0) and VertScrollBar.Visible then 
    {Doesn't matter what parameters we pass because they aren't used} 
    FOnVertScroll(nil, scTop, Dummy); 
end; 
 
 
{*** TFaxDesigner ***} 
 
constructor TFaxDesigner.Create(AOwner : TComponent); 
begin 
  inherited Create(AOwner); 
  Align       := alClient; 
  BevelInner  := bvNone; 
  BevelOuter  := bvRaised; 
  BorderStyle := bsNone; 
  FIsNew      := True; 
 
  fdScrollBox := TFaxScrollBox.Create(Self); 
  with fdScrollBox do begin 
    BorderStyle := bsNone; 
    Parent      := Self; 
    {$IFDEF Win32} 
    HorzScrollBar.Tracking := True; 
    VertScrollBar.Tracking := True; 
    {$ENDIF} 
    OnHorzScroll := HorzScroll; 
    OnVertScroll := VertScroll; 
  end; 
 
  fdHorzRuler := TRuler.Create(Self); 
  with fdHorzRuler do begin 
    IsHorizontal := True; 
    Parent       := Self; 
  end; 
 
  fdVertRuler := TRuler.Create(Self); 
  with fdVertRuler do begin 
    IsHorizontal := False; 
    Parent       := Self; 
  end; 
 
  FFaxPanel := TFaxPanel.Create(Self); 
  with FFaxPanel do begin 
    Color  := clWindow; 
    Parent := fdScrollBox; 
  end; 
 
  {These access FFaxPanel, so they must be called AFTER FFaxPanel is created} 
  SetPageWidthPixels(ctDefaultWidthPixels); 
  SetPageHeightPixels(ctDefaultHeightPixels); 
  SetPageWidthInches(ctDefaultWidthInches); 
  SetPageHeightInches(ctDefaultHeightInches); 
 
  {FaxPanel.NeedsSaving will have been changed to True when we set the Width and 
   Height in Inches. No changes have actually been made, so reset it to False.} 
  FFaxPanel.NeedsSaving := False; 
end; 
 
procedure TFaxDesigner.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); 
var 
  Length    : Integer; 
  OldWidth  : Integer; 
  OldHeight : Integer; 
  Dummy     : Integer; 
begin 
  OldWidth  := Width; 
  OldHeight := Height; 
  inherited SetBounds(ALeft, ATop, AWidth, AHeight); 
  if Parent <> nil then begin 
    fdScrollBox.SetBounds(ALeft + fdVertRuler.Width, ATop + fdHorzRuler.Height, 
                          Self.ClientWidth - fdVertRuler.Width, 
                          Self.ClientHeight - fdHorzRuler.Height); 
 
    {Ensure that HorzRuler doesn't get longer than the width of the FaxPanel} 
    if Self.ClientHeight - fdHorzRuler.Height < FFaxPanel.Height then 
      Length := Self.ClientHeight - fdHorzRuler.Height 
    else 
      Length := FFaxPanel.Height; 
    fdVertRuler.SetBounds(0, fdHorzRuler.Height, fdVertRuler.Width, Length); 
 
    {Ensure that VertRuler doesn't get taller than the height of the FaxPanel} 
    if Self.ClientWidth - fdVertRuler.Width < FFaxPanel.Width then 
      Length := Self.ClientWidth - fdVertRuler.Width 
    else 
      Length := FFaxPanel.Width; 
    fdHorzRuler.SetBounds(fdVertRuler.Width, 0, Length, fdHorzRuler.Height); 
 
    {If the form has been made wider or taller, its possible that the one of the 
     rulers might need to "scroll" to match, so call HorzScroll or VertScroll 
     as needed. It doesn't matter what parameters we pass because the parameters 
     aren't used.} 
    if Width <> OldWidth then 
      HorzScroll(nil, scTop, Dummy); 
    if Height <> OldHeight then 
      VertScroll(nil, scTop, Dummy); 
  end; 
end;   
 
procedure TFaxDesigner.SetParent(AParent: TWinControl); 
begin 
  inherited SetParent(AParent); 
  if Assigned(AParent) then 
    {Now that a Parent is set, call SetBounds to put the Rulers in the correct positions} 
    SetBounds(Left, Top, Width, Height); 
end; 
 
procedure TFaxDesigner.HorzScroll(Sender: TObject; ScrollCode: TScrollCode; 
                              var ScrollPos: Integer); 
var 
  X, Y : Integer; 
begin 
  {Find the leftmost FFaxPanel point that is displaying within fdScrollBox} 
  X := 0; 
  Y := 0; 
  ConvertCoords(fdScrollBox, FFaxPanel, X, Y); 
 
  {Scroll fdHorzRuler to match the scroll position of FFaxPanel} 
  fdHorzRuler.StartPosition := X; 
end;  
 
procedure TFaxDesigner.VertScroll(Sender: TObject; ScrollCode: TScrollCode; 
                              var ScrollPos: Integer); 
var 
  X, Y : Integer; 
begin 
  {Find the topmost FFaxPanel point that is displaying within fdScrollBox} 
  X := 0; 
  Y := 0; 
  ConvertCoords(fdScrollBox, FFaxPanel, X, Y); 
 
  {Scroll fdVertRuler to match the scroll position of FFaxPanel} 
  fdVertRuler.StartPosition := Y; 
end; 
 
function TFaxDesigner.GetPageWidthPixels : Integer; 
begin 
  Result := FFaxPanel.Width; 
end; 
 
procedure TFaxDesigner.SetPageWidthPixels(AWidth : Integer); 
var 
  Dummy : Integer; 
begin 
  if FFaxPanel.Width <> AWidth then begin 
    FFaxPanel.Width := AWidth; 
    if Assigned(fdHorzRuler) then begin 
      fdHorzRuler.SizePixels := AWidth; 
      {Call SetBounds to force fdHorzRuler to the correct size} 
      SetBounds(Left, Top, Width, Height); 
      if Parent <> nil then 
        HorzScroll(nil, scTop, Dummy);  {Update ruler position} 
    end; 
  end; 
end; 
 
function TFaxDesigner.GetPageHeightPixels : Integer; 
begin 
  Result := FFaxPanel.Height; 
end; 
 
procedure TFaxDesigner.SetPageHeightPixels(AHeight : Integer); 
var 
  Dummy : Integer; 
begin 
  if FFaxPanel.Height <> AHeight then begin 
    FFaxPanel.Height := AHeight; 
    if Assigned(fdVertRuler) then begin 
      fdVertRuler.SizePixels := AHeight; 
      //设为正确的大小 
      SetBounds(Left, Top, Width, Height); 
      if Parent <> nil then 
        VertScroll(nil, scTop, Dummy);//更新尺的位置 
    end; 
  end; 
end; 
 
function TFaxDesigner.GetPageWidthInches : Double; 
begin 
  Result := FFaxPanel.PageWidthInches; 
end; 
 
procedure TFaxDesigner.SetPageWidthInches(AWidth : Double); 
begin 
  if Assigned(fdHorzRuler) then 
    fdHorzRuler.SizeInches := AWidth; 
  //调整页度 
  FFaxPanel.PageWidthInches := AWidth; 
  if PageWidthInches <> 0.0 then 
    SetPageHeightPixels(Round(PageHeightInches * PageWidthPixels / PageWidthInches)); 
end; 
 
function TFaxDesigner.GetPageHeightInches : Double; 
begin 
  Result := FFaxPanel.PageHeightInches; 
end; 
 
procedure TFaxDesigner.SetPageHeightInches(AHeight : Double); 
begin 
  if Assigned(fdVertRuler) then 
    fdVertRuler.SizeInches := AHeight; 
  FFaxPanel.PageHeightInches := AHeight; 
  //调整页高 
  if PageWidthInches <> 0.0 then 
    SetPageHeightPixels(Round(PageHeightInches * PageWidthPixels / PageWidthInches)); 
end; 
 
procedure TFaxDesigner.SetIsMetric(AIsMetric : Boolean); 
begin 
  if AIsMetric <> FIsMetric then begin 
    FIsMetric := AIsMetric; 
    if Assigned(fdHorzRuler) then 
      fdHorzRuler.IsMetric := AIsMetric; 
    if Assigned(fdVertRuler) then 
      fdVertRuler.IsMetric := AIsMetric; 
    // 
    FFaxPanel.FieldPositionChangeForSelectedField; 
  end; 
end; 
 
procedure TFaxDesigner.SetMarkPositions(ALeft, ATop, AWidth, AHeight : Integer); 
begin 
  if Assigned(fdHorzRuler) then 
    fdHorzRuler.SetMarkPositions(ALeft, ALeft + AWidth); 
  if Assigned(fdVertRuler) then 
    fdVertRuler.SetMarkPositions(ATop, ATop + AHeight); 
end; 
 
procedure TFaxDesigner.Read(Stream : TStream); 
var 
  PageRec : TPageRecord; 
begin 
   Stream.ReadBuffer(PageRec, SizeOf(PageRec)); 
  with PageRec do begin 
    if prVersionNum <> ctVersionNum then begin 
      MessageDlg('Version mismatch! Unable to read Fax Cover Page!', mtError, [mbOK], 0); 
      Exit; 
    end; 
    SetPageWidthPixels(prPageWidthPixels); 
    SetPageHeightPixels(prPageHeightPixels); 
    SetPageWidthInches(prPageWidthInches); 
    SetPageHeightInches(prPageHeightInches); 
    SetIsMetric(prIsMetric); 
    FUserData := prUserData; 
  end; 
   FFaxPanel.Read(Stream); 
 
  //设置为已存在封面页 
  FIsNew := False; 
end; 
 
procedure TFaxDesigner.Write(Stream : TStream); 
var 
  PageRec : TPageRecord; 
begin 
  //初始化pageRec 
  FillChar(PageRec, SizeOf(PageRec), 0); 
  with PageRec do begin 
    prVersionNum       := ctVersionNum; 
    prPageWidthPixels  := GetPageWidthPixels; 
    prPageHeightPixels := GetPageHeightPixels; 
    prPageWidthInches  := GetPageWidthInches; 
    prPageHeightInches := GetPageHeightInches; 
    prIsMetric         := FIsMetric; 
    prUserData         := FUserData; 
  end; 
  Stream.WriteBuffer(PageRec, SizeOf(PageRec)); 
 
  FFaxPanel.Write(Stream); 
  
  FIsNew := False; 
end; 
 
end.