{%MainUnit ../graphics.pp}
{******************************************************************************
                                     TFONT
 ******************************************************************************
 
 *****************************************************************************
 *                                                                           *
 *  This file is part of the Lazarus Component Library (LCL)                 *
 *                                                                           *
 *  See the file COPYING.modifiedLGPL.txt, included in this distribution,    *
 *  for details about the copyright.                                         *
 *                                                                           *
 *  This program is distributed in the hope that it will be useful,          *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
 *                                                                           *
 *****************************************************************************
}


{ TFontHandleCache }

type
  TLogFontAndName = record
    LogFont: TLogFont;
    LongFontName: string;
  end;
  PLogFontAndName = ^TLogFontAndName;

function CompareLogFontAndNameWithResDesc(Key: PLogFontAndName; Desc: TFontHandleCacheDescriptor): integer;
begin
  Result := CompareStr(Key^.LongFontName, Desc.LongFontName);
  //debugln('CompareLogFontAndNameWithResDesc A ',Key^.LongFontName,' ',Desc.LongFontName,' ',DbgS(Desc),' Result=',Result);
  if Result = 0 then
    Result := CompareMemRange(@Key^.LogFont, @Desc.LogFont, SizeOf(Desc.LogFont));
  //debugln('CompareLogFontAndNameWithResDesc END Result=',Result);
end;

procedure TFontHandleCache.RemoveItem(Item: TResourceCacheItem);
begin
  DeleteObject(HGDIOBJ(Item.Handle));
  inherited RemoveItem(Item);
end;

constructor TFontHandleCache.Create;
begin
  inherited Create;
  FResourceCacheDescriptorClass := TFontHandleCacheDescriptor;
end;

function TFontHandleCache.CompareDescriptors(Tree: TAvgLvlTree; Desc1,
  Desc2: Pointer): integer;
var
  Descriptor1: TFontHandleCacheDescriptor absolute Desc1;
  Descriptor2: TFontHandleCacheDescriptor absolute Desc2;
begin
  Result := CompareStr(Descriptor1.LongFontName, Descriptor2.LongFontName);
  if Result <> 0 then
    Exit;
  Result := CompareMemRange(@Descriptor1.LogFont, @Descriptor2.LogFont,
                          SizeOf(Descriptor1.LogFont));
end;

function TFontHandleCache.FindFont(TheFont: TLCLHandle): TResourceCacheItem;
var
  ANode: TAvgLvlTreeNode;
begin
  ANode := FItems.FindKey(@TheFont,
                          TListSortCompare(@ComparePHandleWithResourceCacheItem));
  if ANode <> nil then
    Result := TResourceCacheItem(ANode.Data)
  else
    Result := nil;
end;

function TFontHandleCache.FindFontDesc(const LogFont: TLogFont;
  const LongFontName: string): TFontHandleCacheDescriptor;
var
  LogFontAndName: TLogFontAndName;
  ANode: TAvgLvlTreeNode;
begin
  LogFontAndName.LogFont := LogFont;
  LogFontAndName.LongFontName := LongFontName;
  ANode := FDescriptors.Findkey(@LogFontAndName,
                           TListSortCompare(@CompareLogFontAndNameWithResDesc));
  if ANode <> nil then
    Result := TFontHandleCacheDescriptor(ANode.Data)
  else
    Result := nil;
end;

function TFontHandleCache.Add(TheFont: TLCLHandle; const LogFont: TLogFont;
  const LongFontName: string): TFontHandleCacheDescriptor;
var
  Item: TResourceCacheItem;
begin
  if FindFontDesc(LogFont, LongFontName) <> nil then
    RaiseGDBException('TFontHandleCache.Add font desc added twice');

  // find cache item with TheFont
  Item := FindFont(TheFont);
  if Item = nil then
  begin
    // create new item
    Item := TResourceCacheItem.Create(Self, TheFont);
    FItems.Add(Item);
  end;

  // create descriptor
  Result := TFontHandleCacheDescriptor.Create(Self, Item);
  Result.LongFontName := LongFontName;
  Result.LogFont := LogFont;
  FDescriptors.Add(Result);
  if FindFontDesc(LogFont, LongFontName) = nil then
  begin
    DebugLn('TFontHandleCache.Add Added: %p LongFontName=%s', [Pointer(Result), Result.LongFontName]);
    RaiseGDBException('');
  end;
end;

{ TFont }

procedure GetCharsetValues(Proc: TGetStrProc);
var
  I: Integer;
begin
  for I := Low(FontCharsets) to High(FontCharsets) do
    Proc(FontCharsets[I].Name);
end;

function CharsetToIdent(Charset: Longint; out Ident: string): Boolean;
begin
  Result := IntToIdent(Charset, Ident, FontCharsets);
end;

function IdentToCharset(const Ident: string; out Charset: Longint): Boolean;
begin
  Result := IdentToInt(Ident, CharSet, FontCharsets);
end;

function GetFontData(Font: HFont): TFontData;
var
  ALogFont: TLogFont;
begin
  Result := DefFontData;
  if Font <> 0 then
  begin
    if GetObject(Font, SizeOf(ALogFont), @ALogFont) <> 0 then
      with Result, ALogFont do
      begin
        Height := lfHeight;
        if lfWeight >= FW_BOLD then
          Include(Style, fsBold);
        if lfItalic = 1 then
          Include(Style, fsItalic);
        if lfUnderline = 1 then
          Include(Style, fsUnderline);
        if lfStrikeOut = 1 then
          Include(Style, fsStrikeOut);
        Charset := TFontCharset(lfCharSet);
        Name := lfFaceName;
        case lfPitchAndFamily and $F of
          VARIABLE_PITCH: Pitch := fpVariable;
          FIXED_PITCH: Pitch := fpFixed;
        else
          Pitch := fpDefault;
        end;
        Orientation := lfOrientation;
        Handle := Font;
      end;
  end;
end;

function GetDefFontCharSet: TFontCharSet;
begin
  Result := DEFAULT_CHARSET;
end;

{------------------------------------------------------------------------------
  function:  FindXLFDItem
  Params:  const XLFDName: string; Index: integer;
           var ItemStart, ItemEnd: integer
  Returns: boolean

  Searches the XLFD item on position Index. Index starts from 0.
  Returns true on sucess.
  ItemStart will be on the first character and ItemEnd after the last character.
 ------------------------------------------------------------------------------}
function FindXLFDItem(const XLFDName: string; Index: integer;
  var ItemStart, ItemEnd: integer): boolean;
begin
  if Index<0 then
  begin
    Result := False;
    exit;
  end;
  ItemStart := 1;
  ItemEnd := ItemStart;
  while true do
  begin
    if (ItemEnd>length(XLFDName)) then
    begin
      dec(Index);
      break;
    end;
    if XLFDName[ItemEnd] = '-' then
    begin
      dec(Index);
      if Index < 0 then break;
      ItemStart := ItemEnd + 1;
    end;
    inc(ItemEnd);
  end;
  Result := (Index = -1);
end;

{------------------------------------------------------------------------------
  function:  ExtractXLFDItem
  Params:  const XLFDName: string; Index: integer
  Returns: string

  Parses a font name in XLFD format and extracts one item.
  (see http://wwwinfo.cern.ch/umtf/working-groups/X11/fonts/hp_xlfd.html)

  An XLFD name is
  FontNameRegistry-Foundry-FamilyName-WeightName-Slant-SetwidthName
  -AddStyleName-PixelSize-PointSize-ResolutionX-ResolutionY-Spacing
  -AverageWidth-CharSetRegistry-CharSetCoding

 ------------------------------------------------------------------------------}
function ExtractXLFDItem(const XLFDName: string; Index: integer): string;
var StartPos, EndPos: integer;
begin
  if FindXLFDItem(XLFDName, Index, StartPos, EndPos) then
    Result := copy(XLFDName, StartPos, EndPos - StartPos)
  else
    Result := '';
end;

{------------------------------------------------------------------------------
  function:  ExtractFamilyFromXLFDName
  Params:  const XLFDName: string
  Returns: string

  Parses a font name in XLFD format and extracts the FamilyName.
  (see http://wwwinfo.cern.ch/umtf/working-groups/X11/fonts/hp_xlfd.html)

  An XLFD name is
  FontNameRegistry-Foundry-FamilyName-WeightName-Slant-SetwidthName
  -AddStyleName-PixelSize-PointSize-ResolutionX-ResolutionY-Spacing
  -AverageWidth-CharSetRegistry-CharSetCoding

 ------------------------------------------------------------------------------}
function ExtractFamilyFromXLFDName(const XLFDName: string): string;
var StartPos, EndPos: integer;
begin
  if FindXLFDItem(XLFDName, 2, StartPos, EndPos) then
    Result:=copy(XLFDName, StartPos, EndPos - StartPos)
  else
    Result := '';
end;

{------------------------------------------------------------------------------
  Method:  XLFDNameToLogFont
  Params:  const XLFDName: string
  Returns: TLogFont

  Parses a font name in XLFD format and creates a TLogFont record from it.
  (see http://wwwinfo.cern.ch/umtf/working-groups/X11/fonts/hp_xlfd.html)

  An XLFD name is
  FontNameRegistry-Foundry-FamilyName-WeightName-Slant-SetwidthName
  -AddStyleName-PixelSize-PointSize-ResolutionX-ResolutionY-Spacing
  -AverageWidth-CharSetRegistry-CharSetCoding

 ------------------------------------------------------------------------------}
function XLFDNameToLogFont(const XLFDName: string): TLogFont;
type
  TWeightMapEntry = record
    Name: string;
    Weight: integer;
  end;
const
  WeightMap: array[1..15] of TWeightMapEntry = (
    (Name: 'DONTCARE'; Weight: FW_DONTCARE),
    (Name: 'THIN'; Weight: FW_THIN),
    (Name: 'EXTRALIGHT'; Weight: FW_EXTRALIGHT),
    (Name: 'LIGHT'; Weight: FW_LIGHT),
    (Name: 'NORMAL'; Weight: FW_NORMAL),
    (Name: 'MEDIUM'; Weight: FW_MEDIUM),
    (Name: 'SEMIBOLD'; Weight: FW_SEMIBOLD),
    (Name: 'BOLD'; Weight: FW_BOLD),
    (Name: 'EXTRABOLD'; Weight: FW_EXTRABOLD),
    (Name: 'HEAVY'; Weight: FW_HEAVY),
    (Name: 'ULTRALIGHT'; Weight: FW_ULTRALIGHT),
    (Name: 'REGULAR'; Weight: FW_REGULAR),
    (Name: 'DEMIBOLD'; Weight: FW_DEMIBOLD),
    (Name: 'ULTRABOLD'; Weight: FW_ULTRABOLD),
    (Name: 'BLACK'; Weight: FW_BLACK)
    );
var
  ItemStart, ItemEnd: integer;
  Item: string;

  procedure GetNextItem;
  begin
    ItemStart:=ItemEnd+1;
    ItemEnd:=ItemStart;
    while (ItemEnd<=length(XLFDName)) and (XLFDName[ItemEnd]<>'-') do
      inc(ItemEnd);
    Item:=copy(XLFDName,ItemStart,ItemEnd-ItemStart);
  end;
  
  function WeightNameToWeightID(const WeightName: string): integer;
  var i: integer;
  begin
    for i:=Low(WeightMap) to High(WeightMap) do begin
      if AnsiCompareText(WeightMap[i].Name,WeightName)=0 then begin
        Result:=WeightMap[i].Weight;
        exit;
      end;
    end;
    Result:=FW_DONTCARE;
  end;

var l, FaceNameMax, PixelSize, PointSize, Resolution, AverageWidth: integer;
begin
  FillChar(Result,SizeOf(TLogFont),0);
  ItemEnd:=0;
  GetNextItem; // 1. read FontNameRegistry
  // ToDo
  
  GetNextItem; // 2. read Foundry
  // ToDo

  GetNextItem; // 3. read FamilyName
  l:=length(Item);
  FaceNameMax:=High(Result.lfFaceName)-Low(Result.lfFaceName); // max without #0
  if l>FaceNameMax then l:=FaceNameMax;
  if l>0 then Move(Item[1],Result.lfFaceName[Low(Result.lfFaceName)],l);
  Result.lfFaceName[Low(Result.lfFaceName)+l]:=#0;

  GetNextItem; // 4. read WeightName
  Result.lfWeight:=WeightNameToWeightID(Item);

  GetNextItem; // 5. read Slant
  if (AnsiCompareText(Item,'I')=0) or (AnsiCompareText(Item,'RI')=0)
  or (AnsiCompareText(Item,'O')=0) then
    // I = italic, RI = reverse italic, O = oblique
    Result.lfItalic:=1
  else
    Result.lfItalic:=0;

  GetNextItem; // 6. read SetwidthName
  // ToDO: NORMAL, CONDENSED, NARROW, WIDE, EXPANDED

  GetNextItem; // 7. read AddStyleName
  // calculate Style name extentions (=rotation)
  //        API                 XLFD
  // --------------------- --------------
  // Orientation 1/10 deg  1/64 deg
  Result.lfOrientation:=(StrToIntDef(Item,0)*10) div 64;
      
  GetNextItem; // 8. read PixelSize
  PixelSize:=StrToIntDef(Item,0);
  GetNextItem; // 9. read PointSize
  PointSize:=StrToIntDef(Item,0) div 10;
  GetNextItem; // 10. read ResolutionX
  Resolution:=StrToIntDef(Item,0);
  if Resolution<=0 then Resolution:=72;

  if PixelSize=0 then begin
    if PointSize<=0 then
      Result.lfHeight:=(12*Resolution) div 72
    else
      Result.lfHeight:=(PointSize*Resolution) div 72;
  end else begin
    Result.lfHeight:=PixelSize;
  end;

  GetNextItem; // 11. read ResolutionY
  Resolution:=StrToIntDef(Item,0);
  if Resolution<=0 then Resolution:=72;

  GetNextItem; // 12. read Spacing
  {M       Monospaced (fixed pitch)
   P       Proportional spaced (variable pitch)
   C       Character cell.  The glyphs of the font can be thought of as
           "boxes" of the same width and height that are stacked side by
           side or top to bottom.}
  if AnsiCompareText(Item,'M')=0 then
    Result.lfPitchAndFamily:=FIXED_PITCH
  else if AnsiCompareText(Item,'P')=0 then
    Result.lfPitchAndFamily:=VARIABLE_PITCH
  else if AnsiCompareText(Item,'C')=0 then
    Result.lfPitchAndFamily:=FIXED_PITCH;

  GetNextItem; // 13. read AverageWidth
  AverageWidth := StrToIntDef(Item,0);
  Result.lfWidth := AverageWidth div 10;

  GetNextItem; // 14. read CharSetRegistry
  // ToDo

  GetNextItem; // 15. read CharSetCoding
  // ToDo

end;

{------------------------------------------------------------------------------
  function: ClearXLFDItem
  Params:   const LongFontName: string; Index: integer
  Returns:  string

  Replaces an item of a font name in XLFD format with a '*'.
 ------------------------------------------------------------------------------}
function ClearXLFDItem(const LongFontName: string; Index: integer): string;
var ItemStart, ItemEnd: integer;
begin
  if FindXLFDItem(LongFontName,Index,ItemStart,ItemEnd)
  and ((ItemEnd-ItemStart<>1) or (LongFontName[ItemStart]<>'*')) then
    Result:=LeftStr(LongFontName,ItemStart-1)+'*'
            +RightStr(LongFontName,length(LongFontName)-ItemEnd+1)
  else
    Result:=LongFontName;
end;

{------------------------------------------------------------------------------
  function: ClearXLFDHeight
  Params:   const LongFontName: string
  Returns:  string

  Replaces the PixelSize, PointSize, ResolutionX, ResolutionY and AverageWidth
  of a font name in XLFD format with '*'.
  
  An XLFD name is
  FontNameRegistry-Foundry-FamilyName-WeightName-Slant-SetwidthName
  -AddStyleName-PixelSize-PointSize-ResolutionX-ResolutionY-Spacing
  -AverageWidth-CharSetRegistry-CharSetCoding
 ------------------------------------------------------------------------------}
function ClearXLFDHeight(const LongFontName: string): string;
begin
  Result:=ClearXLFDItem(LongFontName,7); // PixelSize
  Result:=ClearXLFDItem(Result,8);       // PointSize
  Result:=ClearXLFDItem(Result,9);       // ResolutionX
  Result:=ClearXLFDItem(Result,10);      // ResolutionY
  Result:=ClearXLFDItem(Result,12);      // AverageWidth
end;

{------------------------------------------------------------------------------
  function: ClearXLFDPitch
  Params:   const LongFontName: string
  Returns:  string

  Replaces the spacing a font name in XLFD format with a '*'.
 ------------------------------------------------------------------------------}
function ClearXLFDPitch(const LongFontName: string): string;
begin
  Result:=ClearXLFDItem(LongFontName,11);
end;

{------------------------------------------------------------------------------
  function: ClearXLFDStyle
  Params:   const LongFontName: string
  Returns:  string

  Replaces the WeightName, Slant and SetwidthName of a font name in XLFD format
  with '*'.
 ------------------------------------------------------------------------------}
function ClearXLFDStyle(const LongFontName: string): string;
begin
  Result:=ClearXLFDItem(ClearXLFDItem(ClearXLFDItem(LongFontName,3),4),5);
end;

function XLFDHeightIsSet(const LongFontName: string): boolean;
begin
  Result:=(ExtractXLFDItem(LongFontName,7)<>'')
       or (ExtractXLFDItem(LongFontName,8)<>'')
       or (ExtractXLFDItem(LongFontName,9)<>'')
       or (ExtractXLFDItem(LongFontName,10)<>'');
end;

{------------------------------------------------------------------------------
  function: IsFontNameXLogicalFontDesc
  Params:   const LongFontName: string
  Returns:  boolean

  Checks if font name is in X Logical Font Description format.
  (see http://wwwinfo.cern.ch/umtf/working-groups/X11/fonts/hp_xlfd.html)

  An XLFD name is
  FontNameRegistry-Foundry-FamilyName-WeightName-Slant-SetwidthName
  -AddStyleName-PixelSize-PointSize-ResolutionX-ResolutionY-Spacing
  -AverageWidth-CharSetRegistry-CharSetCoding
 ------------------------------------------------------------------------------}
function IsFontNameXLogicalFontDesc(const LongFontName: string): boolean;
// Quick test: check if LongFontName contains 14 times the char '-'
var MinusCnt, p: integer;
begin
  MinusCnt:=0;
  for p:=1 to length(LongFontName) do
    if LongFontName[p]='-' then inc(MinusCnt);
  Result:=(MinusCnt=14);
end;

// split a given fontName into Pango Font description components
// font name is supposed to follow this layout:
// [FAMILY-LIST][STYLE-LIST][SIZE]
// where:
// [FAMILY-LIST]  is a comma separated list of families optionally
//                ended by a comma
// [STYLE-LIST]   is white space separated list of words where each word
//                describe one of style, variant, slant, weight or stretch
// [SIZE]         is a decimal number (size in points) (... and points in PANGO_UNITS)
// any of these options may be absent.
procedure FontNameToPangoFontDescStr(const LongFontName: string;
  out aFamily,aStyle: string; out aSize: Integer);

var
  ParsePos: Integer;
  
  procedure addStyle(const s: string);
  begin
    if (s<>'') and (s<>'*') and (s<>'r') then begin
      // 'r' is regular
      if aStyle<>'' then
        aStyle := aStyle + ' ' + s
      else
        aStyle := s;
    end;
  end;

  function GetSize: string;
  var
    c: char;
    validblank: boolean;
    
    function IsBlank: boolean;
    begin
      result := c in [#0..' '];
    end;
    
    function IsDigit: boolean;
    begin
      result := c in ['0'..'9'];
    end;
    
  begin
    Result := '';
    validblank := true;
    ParsePos := Length(LongFontname);
    while ParsePos>0 do begin
      c := longFontName[ParsePos];
      if IsBlank then
        if ValidBlank then begin
          dec(ParsePos);
          continue
        end else
          break;
      ValidBlank := false;
      if IsDigit then begin
        Result := C + Result;
        dec(ParsePos);
      end else
        break;
    end;
  end;
  
begin
  aStyle := '';
  aFamily := '';
  aSize := 0;
  if IsFontNameXLogicalFontDesc(LongFontName) then begin
    aFamily := ExtractXLFDItem(LongFontName, XLFD_FAMILY);
    if aFamily='*' then
      aFamily:='';
    aSize := StrToIntDef(ExtractXLFDItem(LongFontName, XLFD_POINTSIZE),0) div 10;
    addStyle( ExtractXLFDItem(LongFontName, XLFD_STYLENAME ));
    addStyle( ExtractXLFDItem(LongFontname, XLFD_WEIGHTNAME));
    addStyle( ExtractXLFDItem(LongFontname, XLFD_SLANT));
    addStyle( ExtractXLFDItem(LongFontname, XLFD_WidthName));
  end else begin
    // this could go through, but we want to know at least the pointSize from
    // the fontname
    aSize := StrToIntDef(GetSize,0);
    aFamily := Copy(LongFontName, 1, ParsePos);
    // todo: parse aFamily to separate Family and Style
  end;
end;

{ TFont }

{------------------------------------------------------------------------------
  Method:  TFont.Create
  Params:  none
  Returns: Nothing

  Constructor for the class.
 ------------------------------------------------------------------------------}
constructor TFont.Create;
begin
  inherited Create;
  FColor := {$ifdef UseCLDefault}clDefault{$else}clWindowText{$endif};
  FPixelsPerInch := ScreenInfo.PixelsPerInchY;
  FPitch := DefFontData.Pitch;
  FCharSet := DefFontData.CharSet;
  FQuality := DefFontData.Quality;
  FHeight := DefFontData.Height;
  inherited SetSize(-MulDiv(FHeight, 72, FPixelsPerInch));
  DelayAllocate := True;
  inherited SetName(DefFontData.Name);
  inherited SetFPColor(colBlack);
end;

{------------------------------------------------------------------------------
  Method: TFont.Assign
  Params: Source: Another font
  Returns:  nothing

  Copies the Source font to itself
 ------------------------------------------------------------------------------}
procedure TFont.Assign(Source: TPersistent);
begin
  if Source is TFont then
  begin
    //TODO:lock;
    try
      //TODO: TFont(Source).Lock;
      try
        BeginUpdate;
        try
          CharSet := TFont(Source).CharSet;
          SetColor(TFont(Source).Color, TFPCanvasHelper(Source).FPColor);
          if TFont(Source).PixelsPerInch <> FPixelsPerInch then
            // use size to convert source height pixels to current resolution
            Size := TFont(Source).Size
          else
            // use height which users could have changed directly
            Height := TFont(Source).Height;
          Name := TFont(Source).Name;
          Orientation := TFont(Source).Orientation;
          Pitch := TFont(Source).Pitch;
          Style := TFont(Source).Style;
          Quality := TFont(Source).Quality;
        finally
          EndUpdate;
        end;
      finally
        //TODO: TFont(Source).UnLock;
      end;
    finally
      //TODO: UnLock;
    end;
    Exit;
  end;

  inherited Assign(Source);
end;

{------------------------------------------------------------------------------
  Method: TFont.Assign
  Params: ALogFont: TLogFont
  Returns:  nothing

  Copies the logfont settings to itself
 ------------------------------------------------------------------------------}
procedure TFont.Assign(const ALogFont: TLogFont);
var
  AStyle: TFontStyles;
begin
  BeginUpdate;
  try
    with ALogFont do
    begin
      Height := ALogFont.lfHeight;
      Charset := TFontCharset(ALogFont.lfCharSet);
      AStyle := [];
      with ALogFont do
      begin
        if lfWeight >= FW_SEMIBOLD then Include(AStyle, fsBold);
        if lfItalic <> 0 then Include(AStyle, fsItalic);
        if lfUnderline <> 0 then Include(AStyle, fsUnderline);
        if lfStrikeOut <> 0 then Include(AStyle, fsStrikeOut);
      end;
      if (FIXED_PITCH and lfPitchAndFamily) <> 0 then
        Pitch := fpFixed
      else if (VARIABLE_PITCH and lfPitchAndFamily) <> 0 then
        Pitch := fpVariable
      else
        Pitch := fpDefault;
      Style := AStyle;
      Quality := TFontQuality(ALogFont.lfQuality);
      Name := ALogFont.lfFaceName;
    end;
  finally
    EndUpdate;
  end;
end;

function TFont.IsEqual(AFont: TFont): boolean;
begin
  if (AFont = Self) then Exit(true);
  if (AFont=nil)
  or (CharSet<>AFont.CharSet)
  or (Color<>AFont.Color)
  or (PixelsPerInch<>AFont.PixelsPerInch)
  or (Size<>AFont.Size)
  or (Height<>AFont.Height)
  or (Name<>AFont.Name)
  or (Pitch<>AFont.Pitch)
  or (Quality<>AFont.Quality)
  or (Style<>AFont.Style) then
    Result := False
  else
    Result := True;
end;

procedure TFont.BeginUpdate;
begin
  inc(FUpdateCount);
end;

procedure TFont.EndUpdate;
begin
  if FUpdateCount=0 then exit;
  dec(FUpdateCount);
  if (FUpdateCount=0) and FChanged then Changed;
end;

{------------------------------------------------------------------------------
  Method: TFont.HandleAllocated
  Params: none
  Returns: boolean

  Resturns True on handle allocated.
 ------------------------------------------------------------------------------}
function TFont.HandleAllocated: boolean;
begin
  Result := FReference.Allocated;
end;

{------------------------------------------------------------------------------
  function TFont.IsDefault: boolean;
 ------------------------------------------------------------------------------}
function TFont.IsDefault: boolean;
begin
  Result:=(CharSet=DEFAULT_CHARSET)
         and (Color={$ifdef UseCLDefault}clDefault{$else}clWindowText{$endif})
         and (Height=0)
         and (not IsNameStored)
         and (Pitch=fpDefault)
         and (Size=0)
         and (Quality=fqDefault)
         and (Style=[]);
end;

{------------------------------------------------------------------------------
  procedure TFont.SetDefault;

  Set Font properties to default.
 ------------------------------------------------------------------------------}
procedure TFont.SetDefault;
begin
  BeginUpdate;
  try
    Name := DefFontData.Name;
    Charset := DefFontData.CharSet;
    Height := DefFontData.Height;
    Pitch := DefFontData.Pitch;
    Quality := DefFontData.Quality;
    Style := DefFontData.Style;
    Color := {$ifdef UseCLDefault}clDefault{$else}clWindowText{$endif};
  finally
    EndUpdate;
  end;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetSize
  Params: AValue: the new value
  Returns:  nothing

 ------------------------------------------------------------------------------}
procedure TFont.SetSize(AValue: Integer);
begin
  if Size <> AValue then
  begin
    BeginUpdate;
    try
      FreeReference;
      inherited SetSize(AValue);
      FHeight := -MulDiv(AValue, FPixelsPerInch, 72);
      if IsFontNameXLogicalFontDesc(Name) then
        Name := ClearXLFDHeight(Name);
      Changed;
    finally
      EndUpdate;
    end;
  end;
end;

{------------------------------------------------------------------------------
  Function: TFont.GetSize
  Params: none
  Returns:  The font size

  Calculates the size based on height
 ------------------------------------------------------------------------------}
function TFont.GetSize: Integer;
begin
  Result := inherited Size;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetPitch
  Params: Value: the new value
  Returns:  nothing

  Sets the pitch of a font
 ------------------------------------------------------------------------------}
procedure TFont.SetPitch(Value : TFontPitch);
Begin
  if FPitch <> Value then
  begin
    BeginUpdate;
    FreeReference;
    FPitch := Value;
    if IsFontNameXLogicalFontDesc(Name) then
      Name := ClearXLFDPitch(Name);
    Changed;
    EndUpdate;
  end;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetHeight
  Params: Value: the new value
  Returns:  nothing

  Sets the height of a font
 ------------------------------------------------------------------------------}
procedure TFont.SetHeight(AValue: Integer);
begin
  // Don't update Size only. The LogFont contains a lfHeight value and on Windows,
  // Qt and Carbon it is the main parameter which determins the font height.
  if Height <> AValue then
  begin
    BeginUpdate;
    try
      FreeReference;
      FHeight := AValue;
      // update size to equivalent value
      inherited SetSize(-MulDiv(AValue, 72, FPixelsPerInch));
      if IsFontNameXLogicalFontDesc(Name) then
        Name := ClearXLFDHeight(Name);
      Changed;
    finally
      EndUpdate;
    end;
  end;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetStyle
  Params: Value: the new value
  Returns:  nothing

  Sets the style of a font
 ------------------------------------------------------------------------------}
procedure TFont.SetStyle(value : TFontStyles);
begin
  if FStyle <> Value then
  begin
    BeginUpdate;
    FreeReference;
    FStyle := Value;
    inherited SetFlags(5, fsBold in FStyle);
    inherited SetFlags(6, fsItalic in FStyle);
    inherited SetFlags(7, fsUnderline in FStyle);
    inherited SetFlags(8, fsStrikeOut in FStyle);
    if IsFontNameXLogicalFontDesc(Name) then
      Name := ClearXLFDStyle(Name);
    Changed;
    EndUpdate;
  end;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetColor
  Params: Value: the new value
  Returns:  nothing

  Sets the pencolor of a font
 ------------------------------------------------------------------------------}
procedure TFont.SetColor(Value : TColor);
begin
  if FColor <> Value then
    SetColor(Value, TColorToFPColor(Value));
end;

function TFont.GetColor: TColor;
begin
  Result := Color;
  if (Result = clDefault) and Assigned(Canvas) and (Canvas is TCanvas) then
    Result := TCanvas(Canvas).GetDefaultColor(dctFont);
end;

{------------------------------------------------------------------------------
  Function: TFont.GetName
  Params: none
  Returns:  The font name

  Returns the name of the font
 ------------------------------------------------------------------------------}
function TFont.GetName: string;
begin
  Result := inherited Name;
end;

{------------------------------------------------------------------------------
  Returns the orientation of the font

  The orientation is defined as the angle, in tenths of degrees,
  between the X axis of the Canvas and the baseline of the font.

  The property and it's setter/getter pair are compatible with Delphi
 ------------------------------------------------------------------------------}
function TFont.GetOrientation: Integer;
begin
  Result := FOrientation;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetName
  Params: Value: the new value
  Returns:  nothing

  Sets the name of a font
 ------------------------------------------------------------------------------}
procedure TFont.SetName(AValue: string);
begin
  if Name <> AValue then
  begin
    FreeReference;
    inherited SetName(AValue);
    Changed;
  end;
end;

{------------------------------------------------------------------------------
  Changes the orientation of the font

  The orientation is defined as the angle, in tenths of degrees,
  between the X axis of the Canvas and the baseline of the font.

  The property and it's setter/getter pair are compatible with Delphi
 ------------------------------------------------------------------------------}
procedure TFont.SetOrientation(AValue: Integer);
begin
  if FOrientation <> AValue then
  begin
    FreeReference;
    FOrientation := AValue;
    Changed;
  end;
end;

procedure TFont.DoAllocateResources;
begin
  inherited DoAllocateResources;
  GetReference;
end;

procedure TFont.DoDeAllocateResources;
begin
  FreeReference;
  inherited DoDeAllocateResources;
end;

procedure TFont.DoCopyProps(From: TFPCanvasHelper);
var
  SrcFont: TFont;
begin
  BeginUpdate;
  try
    inherited DoCopyProps(From);
    if From is TFont then
    begin
      SrcFont := TFont(From);
      Pitch := SrcFont.Pitch;
      CharSet := SrcFont.CharSet;
      Quality := SrcFont.Quality;
      Style := SrcFont.Style;
    end;
  finally
    EndUpdate;
  end;
end;

procedure TFont.SetFlags(Index: integer; AValue: boolean);

  procedure SetStyleFlag(Flag: TFontStyle; NewValue: boolean);
  begin
    BeginUpdate;
    FreeReference;
    if NewValue then
      Include(FStyle, Flag)
    else
      Exclude(FStyle, Flag);
    if IsFontNameXLogicalFontDesc(Name) then
      Name := ClearXLFDStyle(Name);
    Changed;
    EndUpdate;
  end;

begin
  if GetFlags(Index) = AValue then Exit;
  inherited SetFlags(Index, AValue);
  case Index of
    5: SetStyleFlag(fsBold, AValue);
    6: SetStyleFlag(fsItalic, AValue);
    7: SetStyleFlag(fsUnderline, AValue);
    8: SetStyleFlag(fsStrikeOut, AValue);
  end;
end;

{------------------------------------------------------------------------------
  procedure TFont.SetFPColor(const AValue: TFPColor);

  Set FPColor and Color
 ------------------------------------------------------------------------------}
procedure TFont.SetFPColor(const AValue: TFPColor);
begin
  if FPColor <> AValue then
    SetColor(FPColorToTColor(AValue), AValue);
end;

procedure TFont.SetColor(const NewColor: TColor; const NewFPColor: TFPColor);
begin
  if (NewColor = Color) and (NewFPColor = FPColor) then Exit;
  FColor := NewColor;
  inherited SetFPColor(NewFPColor);
  Changed;
end;

{------------------------------------------------------------------------------
  Method: TFont.Destroy
  Params:  None
  Returns: Nothing

  Destructor for the class.
 ------------------------------------------------------------------------------}
destructor TFont.Destroy;
begin
  FreeReference;
  inherited Destroy;
end;

{------------------------------------------------------------------------------
  Method: TFont.SetHandle
  Params:   a font handle
  Returns:  nothing

  sets the font to an external created font 
 ------------------------------------------------------------------------------}
procedure TFont.SetHandle(const Value: HFONT);
begin
  SetData(GetFontData(Value));
end;

procedure TFont.ReferenceNeeded;
const
  LF_BOOL: array[Boolean] of Byte = (0, 255);
  LF_WEIGHT: array[Boolean] of Integer = (FW_NORMAL, FW_BOLD);
  LF_QUALITY: array[TFontQuality] of Integer = (DEFAULT_QUALITY,
    DRAFT_QUALITY, PROOF_QUALITY, NONANTIALIASED_QUALITY, ANTIALIASED_QUALITY,
    CLEARTYPE_QUALITY, CLEARTYPE_NATURAL_QUALITY);
var
  ALogFont: TLogFont;
  CachedFont: TFontHandleCacheDescriptor;

  procedure SetLogFontName(const NewName: string);
  var
    l: integer;
    aName: string;
  begin
    if IsFontNameXLogicalFontDesc(NewName) then
      aName := ExtractFamilyFromXLFDName(NewName)
    else
      aName := NewName;
    l := High(ALogFont.lfFaceName) - Low(ALogFont.lfFaceName);
    if l > length(aName) then
      l := length(aName);
    if l > 0 then
      Move(aName[1], ALogFont.lfFaceName[Low(ALogFont.lfFaceName)], l);
    ALogFont.lfFaceName[Low(ALogFont.lfFaceName) + l] := #0;
  end;

begin
  if FReference.Allocated then Exit;

  FillChar(ALogFont, SizeOf(ALogFont), 0);
  with ALogFont do
  begin
    lfHeight := Height;
    lfWidth := 0;
    lfEscapement := FOrientation;
    lfOrientation := FOrientation;
    lfWeight := LF_WEIGHT[fsBold in Style];
    lfItalic := LF_BOOL[fsItalic in Style];
    lfUnderline := LF_BOOL[fsUnderline in Style];
    lfStrikeOut := LF_BOOL[fsStrikeOut in Style];
    lfCharSet := Byte(FCharset);
    SetLogFontName(Name);

    lfQuality := LF_QUALITY[FQuality];
    lfOutPrecision := OUT_DEFAULT_PRECIS;
    lfClipPrecision := CLIP_DEFAULT_PRECIS;
    case Pitch of
      fpVariable: lfPitchAndFamily := VARIABLE_PITCH;
      fpFixed: lfPitchAndFamily := FIXED_PITCH;
    else
      lfPitchAndFamily := DEFAULT_PITCH;
    end;
  end;
  FontResourceCache.Lock;
  try
    // ask the font cache for the nearest font
    CachedFont := FontResourceCache.FindFontDesc(ALogFont, Name);
    //DebugLn(['TFont.GetHandle in cache: ',CachedFont<>nil]);
    if CachedFont <> nil then
    begin
      CachedFont.Item.IncreaseRefCount;
      FReference._lclHandle := CachedFont.Item.Handle;
    end else
    begin
      // ask the interface for the nearest font
      FReference._lclHandle := TLCLHandle(CreateFontIndirectEx(ALogFont, Name));
      FontResourceCache.Add(FReference.Handle, ALogFont, Name);
    end;
    FFontHandleCached := True;
  finally
    FontResourceCache.Unlock;
  end;
  FCanUTF8Valid := False;
  FIsMonoSpaceValid := False;
end;

procedure TFont.SetQuality(const AValue: TFontQuality);
begin
  if FQuality <> AValue then
  begin
    BeginUpdate;
    FreeReference;
    FQuality := AValue;
    if IsFontNameXLogicalFontDesc(Name) then
      Name := ClearXLFDStyle(Name);
    Changed;
    EndUpdate;
  end;
end;

{------------------------------------------------------------------------------
  Function: TFont.GetHandle
  Params:   none
  Returns:  a handle to a font gdiobject

  Creates a font if needed
 ------------------------------------------------------------------------------}
function TFont.GetHandle: HFONT;
begin
  Result := HFONT(Reference.Handle);
end;

{------------------------------------------------------------------------------
  Method:  TFont.FreeReference
  Params:  none
  Returns: Nothing

  Frees a font handle if needed
 ------------------------------------------------------------------------------}

procedure TFont.FreeReference;
begin
  if not FReference.Allocated then Exit;

  // Changing triggers deselecting the current handle
  Changing;
  if FFontHandleCached then
  begin
    if Assigned(FontResourceCache) then
    begin
      FontResourceCache.Lock;
      try
        FontResourceCache.FindFont(FReference.Handle).DecreaseRefCount;
        FFontHandleCached := False;
      finally
        FontResourceCache.Unlock;
      end;
    end;
  end else
    DeleteObject(HGDIOBJ(FReference.Handle));
  FReference._lclHandle := 0;
end;

function TFont.GetCanUTF8: boolean;
begin
  if not FCanUTF8Valid then
  begin
    FCanUTF8 := {%H-}FontCanUTF8(HFONT(Reference.Handle));
    FCanUTF8Valid := True;
  end;
  Result := FCanUTF8;
end;

function TFont.GetCharSet: TFontCharSet;
begin
  Result := FCharSet;
end;

procedure TFont.SetCharSet(const AValue: TFontCharSet);
begin
  if FCharSet <> AValue then
  begin
    FreeReference;
    FCharSet := AValue;
    Changed;
  end;
end;

function TFont.GetData: TFontData;
begin
  Result := DefFontData;
  if HandleAllocated then
    Result.Handle := Reference.Handle
  else
    Result.Handle := 0;
  Result.Height := Height;
  Result.Pitch := Pitch;
  Result.Style := Style;
  Result.CharSet := CharSet;
  Result.Quality := Quality;
  Result.Name := LeftStr(Name, SizeOf(Result.Name) - 1);
  Result.Orientation := Orientation;
end;

function TFont.GetIsMonoSpace: boolean;
begin
  if not FIsMonoSpaceValid then
  begin
    FIsMonoSpace := FontIsMonoSpace(HFONT(Reference.Handle));
    FIsMonoSpaceValid := True;
  end;
  Result := FIsMonoSpace;
end;

function TFont.GetReference: TWSFontReference;
begin
  ReferenceNeeded;
  Result := FReference;
end;

function TFont.IsHeightStored: boolean;
begin
  Result := DefFontData.Height <> Height;
end;

function TFont.IsNameStored: boolean;
begin
  Result := DefFontData.Name <> Name;
end;

procedure TFont.SetData(const FontData: TFontData);
var
  OldStyle: TFontStylesbase;
begin
  if (HFONT(FReference.Handle) <> FontData.Handle) or not FReference.Allocated then
  begin
    OldStyle := FStyle;
    FreeReference;
    FReference._lclHandle := TLCLHandle(FontData.Handle);
    inherited SetSize(-MulDiv(FontData.Height, 72, FPixelsPerInch));
    FHeight := FontData.Height;
    FPitch := FontData.Pitch;
    FStyle := FontData.Style;
    FCharSet := FontData.CharSet;
    FQuality := FontData.Quality;
    inherited SetName(FontData.Name);
    Bold; // it calls GetFlags
    if (fsBold in OldStyle)<>(fsBold in FStyle) then
      inherited SetFlags(5, fsBold in FStyle);
    if (fsItalic in OldStyle)<>(fsItalic in FStyle) then
      inherited SetFlags(6, fsItalic in FStyle);
    if (fsUnderline in OldStyle)<>(fsUnderline in FStyle) then
      inherited SetFlags(7, fsUnderline in FStyle);
    if (fsStrikeOut in OldStyle)<>(fsStrikeOut in FStyle) then
      inherited SetFlags(8, fsStrikeOut in FStyle);
    FOrientation := FontData.Orientation;
    Changed;
  end;
end;

function TFont.GetHeight: Integer;
begin
  Result := FHeight;
end;

function TFont.GetPitch: TFontPitch;
begin
  Result := FPitch;
end;

function TFont.GetStyle: TFontStyles;
begin
  Result := FStyle;
end;

procedure TFont.Changed;
begin
  if FUpdateCount > 0 then
  begin
    FChanged := True;
    exit;
  end;
  FChanged := False;
  inherited Changed;
  // ToDo: we need interfaces:
  // if FNotify <> nil then FNotify.Changed;
end;

// included by graphics.pp
