{============================================================================== Content: TheTextDrawer, a helper class for drawing of fixed-pitched font characters ============================================================================== The contents of this file are subject to the Mozilla Public License Ver. 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. ============================================================================== The Original Code is HANAI Tohru's private delphi library. ============================================================================== The Initial Developer of the Original Code is HANAI Tohru (Japan) Portions created by HANAI Tohru are Copyright (C) 1999. All Rights Reserved. ============================================================================== Contributor(s): HANAI Tohru ============================================================================== History: 01/19/1999 HANAI Tohru Initial Version 02/13/1999 HANAI Tohru Changed default intercharacter spacing 09/09/1999 HANAI Tohru Redesigned all. Simplified interfaces. When drawing text now it uses TextOut + SetTextCharacter- Extra insted ExtTextOut since ExtTextOut has a little heavy behavior. 09/10/1999 HANAI Tohru Added code to call ExtTextOut because there is a problem when TextOut called with italicized raster type font. After this changing, ExtTextOut is called without the last parameter `lpDx' and be with SetTextCharacterExtra. This pair performs faster than with `lpDx'. 09/14/1999 HANAI Tohru Changed code for saving/restoring DC 09/15/1999 HANAI Tohru Added X/Y parameters to ExtTextOut. 09/16/1999 HANAI Tohru Redesigned for multi-bytes character drawing. 09/19/1999 HANAI Tohru Since TheTextDrawer grew fat it was split into three classes - TheFontStock, TheTextDrawer and TheTextDrawerEx. Currently it should avoid TheTextDrawer because it is slower than TheTextDrawer. 09/25/1999 HANAI Tohru Added internally definition of LeadBytes for Delphi 2 10/01/1999 HANAI Tohru To save font resources, now all fonts data are shared among all of TheFontStock instances. With this changing, there added a new class `TheFontsInfoManager' to manage those shared data. 10/09/1999 HANAI Tohru Added BaseStyle property to TheFontFont class. ==============================================================================} // $Id$ // SynEdit note: The name had to be changed to get SynEdit to install // together with mwEdit into the same Delphi installation unit SynTextDrawer; {$mode objfpc}{$H+} interface uses {$IFDEF WINDOWS}windows, {$ENDIF} Classes, Types, SysUtils, // LCL LCLType, LCLIntf, Graphics, GraphUtil, // LazUtils LazMethodList, LazLoggerBase, LazTracer, LazUTF8, // SynEdit SynEditTypes, SynEditMiscProcs; type TheStockFontPatterns = 0..(1 shl (1 + Ord(High(TFontStyle)))); PheFontData = ^TheFontData; TheFontData = record Style: TFontStyles; Font: TFont; Handle: HFont; CharAdv: Integer; // char advance of single-byte code CharHeight: Integer; NeedETO: Boolean; end; PheFontsData = ^TheFontsData; TheFontsData = array[TheStockFontPatterns] of TheFontData; PheSharedFontsInfo = ^TheSharedFontsInfo; TheSharedFontsInfo = record // reference counters RefCount: Integer; LockCount: Integer; // font information BaseFont: TFont; IsDBCSFont: Boolean; IsTrueType: Boolean; FontsData: TheFontsData; end; { TheFontsInfoManager } TheFontsInfoManager = class private FFontsInfo: TList; function CreateFontsInfo(ABaseFont: TFont): PheSharedFontsInfo; function FindFontsInfo(const BFont: TFont): PheSharedFontsInfo; procedure DestroyFontHandles(pFontsInfo: PheSharedFontsInfo); public constructor Create; destructor Destroy; override; procedure LockFontsInfo(pFontsInfo: PheSharedFontsInfo); procedure UnLockFontsInfo(pFontsInfo: PheSharedFontsInfo); function GetFontsInfo(ABaseFont: TFont): PheSharedFontsInfo; procedure ReleaseFontsInfo(var pFontsInfo: PheSharedFontsInfo); end; { TheFontStock } TheExtTextOutProc = procedure (X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer) of object; EheFontStockException = class(Exception); TheFontStock = class private // private DC FDC: HDC; FDCRefCount: Integer; // Shared fonts FpInfo: PheSharedFontsInfo; FUsingFontHandles: Boolean; // Current font FCrntFont: HFONT; FCrntStyle: TFontStyles; FpCrntFontData: PheFontData; // local font info function GetBaseFont: TFont; function GetIsDBCSFont: Boolean; function GetIsTrueType: Boolean; function GetNeedETO: Boolean; protected function InternalGetDC: HDC; virtual; procedure InternalReleaseDC(Value: HDC); virtual; Procedure CalcFontAdvance(DC: HDC; FontData: PheFontData; FontHeight: integer); function GetCharAdvance: Integer; virtual; function GetCharHeight: Integer; virtual; function GetFontData(idx: Integer): PheFontData; virtual; procedure UseFontHandles; procedure ReleaseFontsInfo; procedure SetBaseFont(Value: TFont); virtual; procedure SetStyle(Value: TFontStyles); virtual; property FontData[idx: Integer]: PheFontData read GetFontData; property FontsInfo: PheSharedFontsInfo read FpInfo; public constructor Create(InitialFont: TFont); virtual; destructor Destroy; override; procedure ReleaseFontHandles; virtual; public // Info from the current font (per Style) function MonoSpace: Boolean; property Style: TFontStyles read FCrntStyle write SetStyle; property FontHandle: HFONT read FCrntFont; property CharAdvance: Integer read GetCharAdvance; property CharHeight: Integer read GetCharHeight; property NeedETO: Boolean read GetNeedETO; public // Info from the BaseFont property BaseFont: TFont read GetBaseFont; property IsDBCSFont: Boolean read GetIsDBCSFont; property IsTrueType: Boolean read GetIsTrueType; end; { TEtoBuffer } TEtoBuffer = class public EtoData: Array of Integer; function Eto: PInteger; function Len: Integer; procedure Clear; procedure SetMinLength(ALen: Integer); end; { TheTextDrawer } EheTextDrawerException = class(Exception); TheTextDrawer = class(TObject) private FDC: HDC; FSaveDC: Integer; FSavedFont: HFont; // Font information FFontStock: TheFontStock; FCalcExtentBaseStyle: TFontStyles; FBaseCharWidth: Integer; FBaseCharHeight: Integer; // current font and properties FCrntFont: HFONT; FEtoInitLen: Integer; FEto: TEtoBuffer; // current font attributes FColor: TColor; FBkColor: TColor; FFrameColor: array[TLazSynBorderSide] of TColor; FFrameStyle: array[TLazSynBorderSide] of TSynLineStyle; FCharExtra: Integer; // Begin/EndDrawing calling count FDrawingCount: Integer; ForceEto: Boolean; FOnFontChangedHandlers: TMethodList; FOnFontChangedLock: Integer; function GetCharExtra: Integer; function GetEto: TEtoBuffer; protected procedure ReleaseETODist; virtual; procedure AfterStyleSet; virtual; function GetUseUTF8: boolean; function GetMonoSpace: boolean; function CreateColorPen(AColor: TColor; AStyle: LongWord = PS_SOLID): HPen; property DrawingCount: Integer read FDrawingCount; property FontStock: TheFontStock read FFontStock; property BaseCharWidth: Integer read FBaseCharWidth; property BaseCharHeight: Integer read FBaseCharHeight; public constructor Create(CalcExtentBaseStyle: TFontStyles; ABaseFont: TFont); virtual; destructor Destroy; override; function GetCharWidth: Integer; virtual; function GetCharHeight: Integer; virtual; procedure BeginDrawing(DC: HDC); virtual; procedure EndDrawing; virtual; procedure TextOut(X, Y: Integer; Text: PChar; Length: Integer); virtual; procedure ExtTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer; FrameBottom: Integer = -1); virtual; procedure NewTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; ALength: Integer; AnEto: TEtoBuffer); procedure DrawFrame(const ARect: TRect); procedure ForceNextTokenWithEto; function NeedsEto: boolean; procedure DrawLine(X, Y, X2, Y2: Integer; AColor: TColor); procedure FillRect(const aRect: TRect); procedure SetBaseFont(Value: TFont); virtual; procedure SetBaseStyle(const Value: TFontStyles); virtual; procedure SetStyle(Value: TFontStyles); virtual; procedure SetForeColor(Value: TColor); virtual; procedure SetBackColor(Value: TColor); virtual; procedure SetFrameColor(Side: TLazSynBorderSide; AValue: TColor); virtual; overload; procedure SetFrameColor(AValue: TColor); virtual; overload; //deprecated; procedure SetFrameStyle(Side: TLazSynBorderSide; AValue: TSynLineStyle); virtual; overload; //procedure SetFrameStyle(AValue: TSynLineStyle); virtual; overload; procedure SetCharExtra(Value: Integer); virtual; procedure ReleaseTemporaryResources; virtual; procedure RegisterOnFontChangeHandler(AHandlerProc: TNotifyEvent); procedure UnRegisterOnFontChangeHandler(AHandlerProc: TNotifyEvent); property Eto: TEtoBuffer read GetEto; property CharWidth: Integer read GetCharWidth; property CharHeight: Integer read GetCharHeight; property BaseFont: TFont write SetBaseFont; property BaseStyle: TFontStyles write SetBaseStyle; property ForeColor: TColor write SetForeColor; property BackColor: TColor read FBkColor write SetBackColor; property FrameColor[Side: TLazSynBorderSide]: TColor write SetFrameColor; property FrameStyle[Side: TLazSynBorderSide]: TSynLineStyle write SetFrameStyle; property Style: TFontStyles write SetStyle; property CharExtra: Integer read GetCharExtra write SetCharExtra; property UseUTF8: boolean read GetUseUTF8; property MonoSpace: boolean read GetMonoSpace; property StockDC: HDC read FDC; end; { TheTextDrawerEx } TheTextDrawerEx = class(TheTextDrawer) private // current font properties FCrntDx: Integer; FCrntDBDx: Integer; // for a double-byte character // Text drawing procedure reference for optimization FExtTextOutProc: TheExtTextOutProc; protected procedure AfterStyleSet; override; procedure TextOutOrExtTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); virtual; procedure ExtTextOutFixed(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); virtual; procedure ExtTextOutWithETO(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); virtual; procedure ExtTextOutForDBCS(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); virtual; public procedure ExtTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer; FrameBottom: Integer = -1); override; end; function GetFontsInfoManager: TheFontsInfoManager; (* {$DEFINE HE_ASSERT} {$DEFINE HE_LEADBYTES} {$DEFINE HE_COMPAREMEM} *) {$IFNDEF HE_LEADBYTES} type TheLeadByteChars = set of Char; function SetLeadBytes(const Value: TheLeadByteChars): TheLeadByteChars; {$ENDIF} implementation const DBCHAR_CALCULATION_FALED = $7FFFFFFF; var gFontsInfoManager: TheFontsInfoManager; SynTextDrawerFinalization: boolean; {$IFNDEF HE_LEADBYTES} LeadBytes: TheLeadByteChars; {$ENDIF} { utility routines } function GetFontsInfoManager: TheFontsInfoManager; begin if (not Assigned(gFontsInfoManager)) and (not SynTextDrawerFinalization) then gFontsInfoManager := TheFontsInfoManager.Create; Result := gFontsInfoManager; end; function Min(x, y: integer): integer; begin if x < y then Result := x else Result := y; end; {$IFNDEF HE_ASSERT} procedure ASSERT(Expression: Boolean); begin if not Expression then raise EheTextDrawerException.Create('Assertion failed.'); end; {$ENDIF} {$IFNDEF HE_LEADBYTES} function SetLeadBytes(const Value: TheLeadByteChars): TheLeadByteChars; begin Result := LeadBytes; LeadBytes := Value; end; {$ENDIF} {$IFNDEF HE_COMPAREMEM} function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; begin Result := CompareByte(P1^, P2^, Length) = 0; end; {$ENDIF} function GetStyleIndex(Value: TFontStyles): Integer; var item: TFontStyle; begin result := 0; for item := low (TFontStyle) to high(TFontStyle) do if item in Value then result := result + 1 shl ord(item); end; { TEtoBuffer } function TEtoBuffer.Eto: PInteger; begin if Length(EtoData) > 0 then Result := PInteger(@EtoData[0]) else Result := nil; end; function TEtoBuffer.Len: Integer; begin Result := Length(EtoData); end; procedure TEtoBuffer.Clear; begin SetLength(EtoData, 0); end; procedure TEtoBuffer.SetMinLength(ALen: Integer); const EtoBlockSize = $80; begin if Length(EtoData) >= ALen then exit; SetLength(EtoData, ((not (EtoBlockSize - 1)) and ALen) + EtoBlockSize); end; { TheFontsInfoManager } procedure TheFontsInfoManager.LockFontsInfo( pFontsInfo: PheSharedFontsInfo); begin Inc(pFontsInfo^.LockCount); end; constructor TheFontsInfoManager.Create; begin inherited Create; FFontsInfo := TList.Create; end; procedure TheFontsInfoManager.UnlockFontsInfo( pFontsInfo: PheSharedFontsInfo); begin with pFontsInfo^ do begin if LockCount>0 then begin Dec(LockCount); if 0 = LockCount then DestroyFontHandles(pFontsInfo); end; end; end; destructor TheFontsInfoManager.Destroy; var APheSharedFontsInfo:PheSharedFontsInfo; begin if Assigned(FFontsInfo) then begin while FFontsInfo.Count > 0 do begin ASSERT(1 = PheSharedFontsInfo(FFontsInfo[FFontsInfo.Count - 1])^.RefCount); APheSharedFontsInfo:=PheSharedFontsInfo(FFontsInfo[FFontsInfo.Count - 1]); ReleaseFontsInfo(APheSharedFontsInfo); end; FFontsInfo.Free; FFontsInfo:=nil; end; inherited Destroy; gFontsInfoManager := nil; end; procedure TheFontsInfoManager.DestroyFontHandles( pFontsInfo: PheSharedFontsInfo); var i: Integer; begin with pFontsInfo^ do for i := Low(TheStockFontPatterns) to High(TheStockFontPatterns) do with FontsData[i] do if Handle <> 0 then begin FreeAndNil(Font); Handle := 0; end; end; function TheFontsInfoManager.CreateFontsInfo(ABaseFont: TFont): PheSharedFontsInfo; var DC: HDC; hOldFont: HFont; begin New(Result); FillChar(Result^, SizeOf(TheSharedFontsInfo), 0); with Result^ do try BaseFont := TFont.Create; BaseFont.Assign(ABaseFont); IsTrueType := False; // TODO: The old code returned always false too: (0 <> (TRUETYPE_FONTTYPE and LF.lfPitchAndFamily)); // find out whether the font `IsDBCSFont' DC := GetDC(0); hOldFont := SelectObject(DC, ABaseFont.Reference.Handle); IsDBCSFont := (0 <> (GCP_DBCS and GetFontLanguageInfo(DC))); //debugln('TheFontsInfoManager.CreateFontsInfo IsDBCSFont=',IsDBCSFont); SelectObject(DC, hOldFont); ReleaseDC(0, DC); except Result^.BaseFont.Free; Dispose(Result); raise; end; end; function TheFontsInfoManager.FindFontsInfo(const BFont: TFont): PheSharedFontsInfo; var i: Integer; begin for i := 0 to FFontsInfo.Count - 1 do begin Result := PheSharedFontsInfo(FFontsInfo[i]); if Result^.BaseFont.IsEqual(BFont) then Exit; end; Result := nil; end; function TheFontsInfoManager.GetFontsInfo(ABaseFont: TFont): PheSharedFontsInfo; begin ASSERT(Assigned(ABaseFont)); Result := FindFontsInfo(ABaseFont); if not Assigned(Result) then begin Result := CreateFontsInfo(ABaseFont); FFontsInfo.Add(Result); end; if Assigned(Result) then Inc(Result^.RefCount); end; procedure TheFontsInfoManager.ReleaseFontsInfo(var pFontsInfo: PheSharedFontsInfo); begin ASSERT(Assigned(pFontsInfo)); with pFontsInfo^ do begin {$IFDEF HE_ASSERT} ASSERT(LockCount < RefCount, 'Call DeactivateFontsInfo before calling this.'); {$ELSE} ASSERT(LockCount < RefCount); {$ENDIF} if RefCount > 1 then Dec(RefCount) else begin FFontsInfo.Remove(pFontsInfo); // free all objects BaseFont.Free; Dispose(pFontsInfo); end; end; pFontsInfo:=nil; if SynTextDrawerFinalization and (FFontsInfo.Count=0) then // the program is in the finalization phase // and this object is not used anymore -> destroy it Free; end; { TheFontStock } // CalcFontAdvance : Calculation a advance of a character of a font. // [*]hCalcFont will be selected as FDC's font if FDC wouldn't be zero. Procedure TheFontStock.CalcFontAdvance(DC: HDC; FontData: PheFontData; FontHeight: integer); Procedure DebugFont(s: String; a: array of const); begin if FontData^.Font <> nil then begin if FontData^.Font.Size = 0 then exit; s := 'Font=' + FontData^.Font.Name + ' Size=' + IntToStr(FontData^.Font.Size) + ' ' + s; end; s := 'TheFontStock.CalcFontAdvance: ' + s; DebugLn(Format(s, a)); end; procedure GetWHOForChar(s: char; out w, h ,o : Integer; var eto: Boolean); var s1, s2, s3: String; Size1, Size2, Size3: TSize; w2, w3: Integer; begin s1 := s; s2 := s1 + s; s3 := s2 + s; if not(GetTextExtentPoint(DC, PChar(s1), 1, Size1{%H-}) and GetTextExtentPoint(DC, PChar(s2), 2, Size2{%H-}) and GetTextExtentPoint(DC, PChar(s3), 3, Size3{%H-})) then begin DebugFont('Failed to get GetTextExtentPoint for %s', [s1]); w := 0; h := 0; o := 0; eto := True; exit; end; h := Size1.cy; // Size may contain overhang (italic, bold) // Size1 contains the size of 1 char + 1 overhang // Size2 contains the width of 2 chars, with only 1 overhang // Start simple w := size1.cx; o := 0; w2 := Size2.cx - Size1.cx; w3 := Size3.cx - Size2.cx; {$IFDEF SYNFONTDEBUG} DebugFont('Got TextExtends for %s=%d, %s=%d, %s=%d Height=%d', [s1, Size1.cx, s2, Size2.cx, s3, Size3.cx, h]); {$ENDIF} if (w2 = w) and (w3 = w) then exit; if (w2 <= w) and (w3 <= w) then begin // w includes overhang (may be fractional if w2 <> w3 then begin {$IFNDEF SYNFONTDEBUG} if abs(w3-w2) > 1 then {$ENDIF} DebugFont('Variable Overhang w=%d w2=%d w3=%d', [w, w2, w3]); w2 := Max(w2, w3); end; o := w - w2; w := w2; eto := True; end else if (w2 >= w) or (w3 >= w) then begin // Width may be fractional, check sanity and keep w o := 1; eto := True; if Max(w2, w3) > w + 1 then begin DebugFont('Size diff to bi for fractioanl (greater 1) w=%d w2=%d w3=%d', [w, w2, w3]); // Take a guess/average w2 := Max(w2, w3); o := w2 - w; w := Max(w, (w+w2-1) div 2); end; end else begin // broken font? one of w2/w3 is smaller, the other wider than w w := Max(w, (w+w2+w3-1) div 3); o := w div 2; eto := True; end; {$IFDEF SYNFONTDEBUG} DebugFont('Final result for %s Width=%d Overhang=%d eto=%s', [s1, w, o, dbgs(eto)]); {$ENDIF} end; procedure AdjustWHOForChar(s: char; var w, h ,o : Integer; var eto: Boolean); var h2, w2, o2: Integer; begin GetWHOForChar(s, w2, h2, o2, eto); h := Max(h, h2); o := Max(o, o2); if w <> w2 then begin w := Max(w, w2); eto := True; end; end; var TM: TTextMetric; Height, Width, OverHang: Integer; ETO: Boolean; Size1: TSize; tmw: Integer; begin // Calculate advance of a character. // TextMetric may fail, because: // tmMaxCharWidth may be the width of a single Width (Latin) char, like "M" // or a double Width (Chinese) char // tmAveCharWidth is to small for proprtional fonts, as we need he width of the // widest Latin char ("M"). // Even Monospace fonts, may have a smaller tmAveCharWidth (seen with Japanese) // take several samples ETO := False; GetWHOForChar('M', Width, Height, OverHang, ETO); AdjustWHOForChar('W', Width, Height, OverHang, ETO); AdjustWHOForChar('@', Width, Height, OverHang, ETO); AdjustWHOForChar('X', Width, Height, OverHang, ETO); AdjustWHOForChar('m', Width, Height, OverHang, ETO); // Small Chars to detect proportional fonts AdjustWHOForChar('i', Width, Height, OverHang, ETO); AdjustWHOForChar(':', Width, Height, OverHang, ETO); AdjustWHOForChar('''', Width, Height, OverHang, ETO); // Negative Overhang ? if (not ETO) and GetTextExtentPoint(DC, PChar('Ta'), 2, Size1{%H-}) then if Size1.cx < 2 * Width then begin {$IFDEF SYNFONTDEBUG} DebugFont('Negative Overhang for "Ta" cx=%d Width=%d Overhang=%d', [Size1.cx, Width, OverHang]); {$ENDIF} ETO := True; end; // Make sure we get the correct Height if GetTextExtentPoint(DC, PChar('Tgq[_|^'), 7, Size1) then Height := Max(Height, Size1.cy); // DoubleCheck the result with GetTextMetrics GetTextMetrics(DC, TM{%H-}); {$IFDEF SYNFONTDEBUG} DebugFont('TextMetrics tmHeight=%d, tmAve=%d, tmMax=%d, tmOver=%d', [TM.tmHeight, TM.tmAveCharWidth, TM.tmMaxCharWidth, TM.tmOverhang]); {$ENDIF} tmw := TM.tmMaxCharWidth + Max(TM.tmOverhang,0); if Width = 0 then begin DebugFont('No Width from GetTextExtentPoint', []); Width := tmw; end else if (Width > tmw) and (TM.tmMaxCharWidth > 0) then begin DebugFont('Width(%d) > tmMaxWidth+Over(%d)', [Width, tmw]); // take a guess, this is probably a broken font Width := Min(Width, round((TM.tmMaxCharWidth + Max(TM.tmOverhang,0)) * 1.2)); ETO := True; end; if Height = 0 then begin DebugFont('No Height from GetTextExtentPoint, tmHeight=%d', [TM.tmHeight]); Height := TM.tmHeight; end else if Height < TM.tmHeight then begin DebugFont('Height from GetTextExtentPoint to low Height=%d, tmHeight=%d', [Height, TM.tmHeight]); Height := TM.tmHeight; end; if Height = 0 then begin DebugFont('SynTextDrawer: Fallback on FontHeight', []); Height := FontHeight; end; // If we have a broken font, make sure we return a positive value if Width <= 0 then begin DebugFont('SynTextDrawer: Fallback on Width', []); Width := 1 + Height * 8 div 10; end; //if OverHang >0 then debugln(['SynTextDrawer: Overhang=', OverHang]);; FontData^.CharAdv := Width; FontData^.CharHeight := Height; FontData^.NeedETO := ETO; end; constructor TheFontStock.Create(InitialFont: TFont); begin inherited Create; SetBaseFont(InitialFont); end; destructor TheFontStock.Destroy; begin ReleaseFontsInfo; ASSERT(FDCRefCount = 0); inherited; end; function TheFontStock.GetBaseFont: TFont; begin Result := FpInfo^.BaseFont; end; function TheFontStock.GetCharAdvance: Integer; begin Result := FpCrntFontData^.CharAdv; end; function TheFontStock.GetCharHeight: Integer; begin Result := FpCrntFontData^.CharHeight; end; function TheFontStock.GetFontData(idx: Integer): PheFontData; begin Result := @FpInfo^.FontsData[idx]; end; function TheFontStock.GetIsDBCSFont: Boolean; begin Result := FpInfo^.IsDBCSFont; end; function TheFontStock.GetIsTrueType: Boolean; begin Result := FpInfo^.IsTrueType end; function TheFontStock.GetNeedETO: Boolean; begin Result := FpCrntFontData^.NeedETO; end; function TheFontStock.InternalGetDC: HDC; begin if FDCRefCount = 0 then begin ASSERT(FDC = 0); FDC := GetDC(0); end; Inc(FDCRefCount); Result := FDC; end; procedure TheFontStock.InternalReleaseDC(Value: HDC); begin Dec(FDCRefCount); if FDCRefCount <= 0 then begin ASSERT((FDC <> 0) and (FDC = Value)); ReleaseDC(0, FDC); FDC := 0; ASSERT(FDCRefCount = 0); end; end; procedure TheFontStock.ReleaseFontHandles; begin if FUsingFontHandles then with GetFontsInfoManager do begin UnlockFontsInfo(FpInfo); FUsingFontHandles := False; end; end; function TheFontStock.MonoSpace: Boolean; begin FpCrntFontData^.Font.Reference; Result := FpCrntFontData^.Font.IsMonoSpace; end; procedure TheFontStock.ReleaseFontsInfo; begin if Assigned(FpInfo) then with GetFontsInfoManager do begin if FUsingFontHandles then begin UnlockFontsInfo(FpInfo); FUsingFontHandles := False; end; ReleaseFontsInfo(FpInfo); end; end; procedure TheFontStock.SetBaseFont(Value: TFont); var pInfo: PheSharedFontsInfo; begin if Assigned(Value) then begin pInfo := GetFontsInfoManager.GetFontsInfo(Value); if pInfo = FpInfo then begin // GetFontsInfo has increased the refcount, but we already have the font // -> decrease the refcount GetFontsInfoManager.ReleaseFontsInfo(pInfo); end else begin ReleaseFontsInfo; FpInfo := pInfo; // clear styles SetStyle(Value.Style); end; end else raise EheFontStockException.Create('SetBaseFont: ''Value'' must be specified.'); end; procedure TheFontStock.SetStyle(Value: TFontStyles); var idx: Integer; DC: HDC; hOldFont: HFONT; p: PheFontData; begin idx := GetStyleIndex(Value); {$IFDEF HE_ASSERT} ASSERT(idx <= High(TheStockFontPatterns)); {$ENDIF} UseFontHandles; p := FontData[idx]; if FpCrntFontData = p then Exit; FpCrntFontData := p; with p^ do if Handle <> 0 then begin FCrntFont := Handle; FCrntStyle := Style; Exit; end; // create font FpCrntFontData^.Font := TFont.Create; FpCrntFontData^.Font.Assign(BaseFont); FpCrntFontData^.Font.Style := Value; FCrntFont := FpCrntFontData^.Font.Reference.Handle; DC := InternalGetDC; hOldFont := SelectObject(DC, FCrntFont); // retrieve height and advances of new font FpInfo^.IsDBCSFont := (0 <> (GCP_DBCS and GetFontLanguageInfo(DC))); //debugln('TheFontStock.SetStyle A IsDBCSFont=',IsDBCSFont); FpCrntFontData^.Handle := FCrntFont; CalcFontAdvance(DC, FpCrntFontData, Max(BaseFont.Size, BaseFont.Height)); //if FpCrntFontData^.NeedETO then debugln(['Needing ETO fot Font=',BaseFont.Name, ' Height=', BaseFont.Height, ' Style=', integer(Value) ]); hOldFont:=SelectObject(DC, hOldFont); if hOldFont<>FCrntFont then RaiseGDBException('TheFontStock.SetStyle LCL interface lost the font'); InternalReleaseDC(DC); end; procedure TheFontStock.UseFontHandles; begin if not FUsingFontHandles then with GetFontsInfoManager do begin LockFontsInfo(FpInfo); FUsingFontHandles := True; end; end; { TheTextDrawer } constructor TheTextDrawer.Create(CalcExtentBaseStyle: TFontStyles; ABaseFont: TFont); var Side: TLazSynBorderSide; begin inherited Create; FEto := TEtoBuffer.Create; FFontStock := TheFontStock.Create(ABaseFont); FCalcExtentBaseStyle := CalcExtentBaseStyle; SetBaseFont(ABaseFont); FColor := clWindowText; FBkColor := clWindow; for Side := Low(TLazSynBorderSide) to High(TLazSynBorderSide) do begin FFrameColor[Side] := clNone; FFrameStyle[Side] := slsSolid; end; FOnFontChangedHandlers := TMethodList.Create; FOnFontChangedLock := 0; end; destructor TheTextDrawer.Destroy; begin FreeANdNil(FOnFontChangedHandlers); FFontStock.Free; ReleaseETODist; FreeAndNil(FEto); inherited; end; function TheTextDrawer.GetUseUTF8: boolean; begin FFontStock.BaseFont.Reference; Result:=True; //debugln('TheTextDrawer.GetUseUTF8 ',FFontStock.BaseFont.Name,' ',dbgs(FFontStock.BaseFont.HandleAllocated)); end; function TheTextDrawer.GetMonoSpace: boolean; begin FFontStock.BaseFont.Reference; Result:=FFontStock.BaseFont.IsMonoSpace; //debugln('TheTextDrawer.GetMonoSpace ',FFontStock.BaseFont.Name,' ',dbgs(FFontStock.BaseFont.IsMonoSpace),' ',dbgs(FFontStock.BaseFont.HandleAllocated)); end; function TheTextDrawer.CreateColorPen(AColor: TColor; AStyle: LongWord = PS_SOLID): HPen; var ALogBrush: TLogBrush; begin AStyle := AStyle + PS_ENDCAP_FLAT + PS_GEOMETRIC + PS_JOIN_MITER; ALogBrush.lbStyle := BS_SOLID; ALogBrush.lbColor := ColorToRGB(AColor); ALogBrush.lbHatch := 0; Result := ExtCreatePen(AStyle, 1, ALogBrush, 0, nil); end; procedure TheTextDrawer.SetFrameStyle(Side: TLazSynBorderSide; AValue: TSynLineStyle); begin if FFrameStyle[Side] <> AValue then begin FFrameStyle[Side] := AValue; end; end; //procedure TheTextDrawer.SetFrameStyle(AValue: TSynLineStyle); //var // Side: TLazSynBorderSide; //begin // for Side := Low(TLazSynBorderSide) to High(TLazSynBorderSide) do // SetFrameStyle(Side, AValue); //end; function TheTextDrawer.GetEto: TEtoBuffer; begin Result := FEto; FEtoInitLen := 0; end; function TheTextDrawer.GetCharExtra: Integer; begin Result := Max(FCharExtra, -FBaseCharWidth + 1); end; procedure TheTextDrawer.ReleaseETODist; begin FEto.Clear; end; procedure TheTextDrawer.BeginDrawing(DC: HDC); begin if (FDC = DC) then ASSERT(FDC <> 0) else begin ASSERT((FDC = 0) and (DC <> 0) and (FDrawingCount = 0)); FDC := DC; FSaveDC := SaveDC(DC); FSavedFont := SelectObject(DC, FCrntFont); LCLIntf.SetTextColor(DC, TColorRef(FColor)); LCLIntf.SetBkColor(DC, TColorRef(FBkColor)); end; Inc(FDrawingCount); end; procedure TheTextDrawer.EndDrawing; begin ASSERT(FDrawingCount >= 1); Dec(FDrawingCount); if FDrawingCount <= 0 then begin if FDC <> 0 then begin if FSavedFont <> 0 then SelectObject(FDC, FSavedFont); RestoreDC(FDC, FSaveDC); end; FSaveDC := 0; FDC := 0; FDrawingCount := 0; end; end; function TheTextDrawer.GetCharWidth: Integer; begin Result := FBaseCharWidth + CharExtra; end; function TheTextDrawer.GetCharHeight: Integer; begin Result := FBaseCharHeight; end; procedure TheTextDrawer.SetBaseFont(Value: TFont); begin if Assigned(Value) then begin inc(FOnFontChangedLock); try {$IFDEF SYNFONTDEBUG} Debugln(['TheTextDrawer.SetBaseFont Name=', Value.Name, ' Size=', Value.Size, 'Style=', Integer(Value.Style)]); {$ENDIF} ReleaseETODist; with FFontStock do begin SetBaseFont(Value); //debugln('TheTextDrawer.SetBaseFont B ',Value.Name); FBaseCharWidth := 0; FBaseCharHeight := 0; end; BaseStyle := Value.Style; SetStyle(Value.Style); finally dec(FOnFontChangedLock); end; FOnFontChangedHandlers.CallNotifyEvents(Self); end else raise EheTextDrawerException.Create('SetBaseFont: ''Value'' must be specified.'); end; procedure TheTextDrawer.SetBaseStyle(const Value: TFontStyles); begin if (FCalcExtentBaseStyle <> Value) or (FBaseCharWidth = 0) then begin FCalcExtentBaseStyle := Value; ReleaseETODist; with FFontStock do begin Style := Value; FBaseCharWidth := Max(FBaseCharWidth, CharAdvance); FBaseCharHeight := Max(FBaseCharHeight, CharHeight); {$IFDEF SYNFONTDEBUG} Debugln(['TheTextDrawer.SetBaseStyle =', Integer(Value), ' CharAdvance=', CharAdvance, ' CharHeight=',CharHeight, ' FBaseCharWidth=', FBaseCharWidth, ' FBaseCharHeight=',FBaseCharHeight]); {$ENDIF} end; if FOnFontChangedLock = 0 then FOnFontChangedHandlers.CallNotifyEvents(Self); end; end; procedure TheTextDrawer.SetStyle(Value: TFontStyles); begin with FFontStock do begin SetStyle(Value); Self.FCrntFont := FontHandle; end; AfterStyleSet; end; procedure TheTextDrawer.AfterStyleSet; begin if FDC <> 0 then SelectObject(FDC, FCrntFont); end; procedure TheTextDrawer.SetForeColor(Value: TColor); begin if FColor <> Value then begin FColor := Value; if FDC <> 0 then SetTextColor(FDC, TColorRef(Value)); end; end; procedure TheTextDrawer.SetBackColor(Value: TColor); begin if FBkColor <> Value then begin FBkColor := Value; if FDC <> 0 then LCLIntf.SetBkColor(FDC, TColorRef(Value)); end; end; procedure TheTextDrawer.SetFrameColor(Side: TLazSynBorderSide; AValue: TColor); begin if FFrameColor[Side] <> AValue then begin FFrameColor[Side] := AValue; end; end; procedure TheTextDrawer.SetFrameColor(AValue: TColor); var Side: TLazSynBorderSide; begin for Side := Low(TLazSynBorderSide) to High(TLazSynBorderSide) do SetFrameColor(Side, AValue); end; procedure TheTextDrawer.SetCharExtra(Value: Integer); begin if FCharExtra <> Value then begin FCharExtra := Value; FEtoInitLen := 0; end; end; procedure TheTextDrawer.TextOut(X, Y: Integer; Text: PChar; Length: Integer); begin LCLIntf.TextOut(FDC, X, Y, Text, Length); end; procedure TheTextDrawer.ExtTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer; FrameBottom: Integer = -1); procedure InitETODist(InitValue: Integer); var i: Integer; begin FEto.SetMinLength(Length); for i := FEtoInitLen to FEto.Len-1 do FEto.EtoData[i] := InitValue; FEtoInitLen := FEto.Len; end; function HasFrame: Boolean; var Side: TLazSynBorderSide; begin for Side := Low(TLazSynBorderSide) to High(TLazSynBorderSide) do if FFrameColor[Side] <> clNone then Exit(True); Result := False; end; var NeedDistArray: Boolean; DistArray: PInteger; RectFrame: TRect; begin if HasFrame then // draw background // TODO: only if not default bg color begin InternalFillRect(FDC, ARect); if (fuOptions and ETO_OPAQUE) > 0 then fuOptions := fuOptions - ETO_OPAQUE; fuOptions := 0; RectFrame := ARect; if FrameBottom >= 0 then RectFrame.Bottom := FrameBottom; DrawFrame(RectFrame); end; NeedDistArray:= ForceEto or (CharExtra <> 0) or (FBaseCharWidth <> FFontStock.CharAdvance) or FFontStock.NeedETO; ForceEto := False; //DebugLn(['TheTextDrawer.ExtTextOut NeedDistArray=',NeedDistArray]); if NeedDistArray then begin if (FEtoInitLen < Length) then InitETODist(GetCharWidth); DistArray := FEto.Eto; end else begin DistArray:=nil; end; if UseUTF8 then LCLIntf.ExtUTF8Out(FDC, X, Y, fuOptions, @ARect, Text, Length, DistArray) else LCLIntf.ExtTextOut(FDC, X, Y, fuOptions, @ARect, Text, Length, DistArray); end; {$IFDEF WINDOWS_LIGATURE} function GetCharacterPlacementW(hdc: HDC; lpString: LPCWSTR; nCount, nMaxExtend: Integer; var lpResults: GCP_RESULTSW; dwFlags: DWORD): DWORD; stdcall; external 'gdi32' name 'GetCharacterPlacementW'; {$ENDIF} procedure TheTextDrawer.NewTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; ALength: Integer; AnEto: TEtoBuffer); var EtoArray: PInteger; {$IFDEF WINDOWS_LIGATURE} W: WideString; Glyphs: array of WideChar; CharPlaceInfo: GCP_RESULTSW; {$ENDIF} begin if AnEto <> nil then EtoArray := AnEto.Eto else EtoArray := nil; {$IFDEF WINDOWS_LIGATURE} if ALength > 0 then begin W := UTF8ToUTF16(Text, ALength); ZeroMemory(@CharPlaceInfo, SizeOf(CharPlaceInfo)); CharPlaceInfo.lStructSize:= SizeOf(CharPlaceInfo); SetLength(Glyphs, Length(W)); CharPlaceInfo.lpGlyphs:= @Glyphs[0]; CharPlaceInfo.nGlyphs:= Length(Glyphs); Glyphs[0] := #0; if GetCharacterPlacementW(FDC, PWChar(W), Length(W), 0, CharPlaceInfo, GCP_LIGATE or GCP_REORDER or GCP_GLYPHSHAPE)<> 0 then begin Windows.ExtTextOutW(FDC, X, Y, fuOptions or ETO_GLYPH_INDEX, @ARect, Pointer(Glyphs), CharPlaceInfo.nGlyphs, EtoArray); exit; end; end; {$ENDIF} if UseUTF8 then LCLIntf.ExtUTF8Out(FDC, X, Y, fuOptions, @ARect, Text, ALength, EtoArray) else LCLIntf.ExtTextOut(FDC, X, Y, fuOptions, @ARect, Text, ALength, EtoArray); end; procedure TheTextDrawer.DrawFrame(const ARect: TRect); const WaveRadius = 3; PenStyle: array[TSynLineStyle] of LongWord = ( { slsSolid } PS_SOLID, { slsDashed } PS_DASH, { slsDotted } PS_DOT, { slsWaved } PS_SOLID // we draw a wave using solid pen ); var Pen, OldPen: HPen; old: TPoint; Side: TLazSynBorderSide; LastColor: TColor; LastStyle: LongWord; begin OldPen := 0; LastColor := clNone; LastStyle := PS_NULL; for Side := Low(TLazSynBorderSide) to High(TLazSynBorderSide) do begin if FFrameColor[Side] <> clNone then begin if (OldPen = 0) or (FFrameColor[Side] <> LastColor) or (PenStyle[FFrameStyle[Side]] <> LastStyle) then begin LastColor := FFrameColor[Side]; LastStyle := PenStyle[FFrameStyle[Side]]; if OldPen <> 0 then DeleteObject(SelectObject(FDC, OldPen)); Pen := CreateColorPen(LastColor, LastStyle); OldPen := SelectObject(FDC, Pen); end; case Side of bsLeft: begin MoveToEx(FDC, ARect.Left, ARect.Top, @old); if FFrameStyle[Side] = slsWaved then WaveTo(FDC, ARect.Left, ARect.Bottom, WaveRadius) else LineTo(FDC, ARect.Left, ARect.Bottom); end; bsTop: begin MoveToEx(FDC, ARect.Left, ARect.Top, @old); if FFrameStyle[Side] = slsWaved then WaveTo(FDC, ARect.Right, ARect.Top, WaveRadius) else LineTo(FDC, ARect.Right, ARect.Top); end; bsRight: begin if FFrameStyle[Side] = slsWaved then begin MoveToEx(FDC, ARect.Right - WaveRadius, ARect.Top, @old); WaveTo(FDC, ARect.Right - WaveRadius, ARect.Bottom, WaveRadius) end else begin MoveToEx(FDC, ARect.Right - 1, ARect.Top, @old); LineTo(FDC, ARect.Right - 1, ARect.Bottom); end; end; bsBottom: begin if FFrameStyle[Side] = slsWaved then begin MoveToEx(FDC, ARect.Left, ARect.Bottom - WaveRadius, @old); WaveTo(FDC, ARect.Right, ARect.Bottom - WaveRadius, WaveRadius) end else begin MoveToEx(FDC, ARect.Left, ARect.Bottom - 1, @old); LineTo(FDC, ARect.Right, ARect.Bottom - 1); end; end; end; MoveToEx(FDC, ARect.Left, ARect.Top, @old); end; end; DeleteObject(SelectObject(FDC, OldPen)); end; procedure TheTextDrawer.ForceNextTokenWithEto; begin ForceEto := True; end; function TheTextDrawer.NeedsEto: boolean; begin Result := (CharExtra <> 0) or (FBaseCharWidth <> FFontStock.CharAdvance) or FFontStock.NeedETO; end; procedure TheTextDrawer.DrawLine(X, Y, X2, Y2: Integer; AColor: TColor); var Pen, OldPen: HPen; old : TPoint; begin Pen := CreateColorPen(AColor); OldPen := SelectObject(FDC, Pen); MoveToEx(FDC, X, Y, @old); LineTo(FDC, X2, Y2); DeleteObject(SelectObject(FDC, OldPen)); end; procedure TheTextDrawer.FillRect(const aRect: TRect); begin InternalFillRect(FDC, aRect); end; procedure TheTextDrawer.ReleaseTemporaryResources; begin FFontStock.ReleaseFontHandles; end; procedure TheTextDrawer.RegisterOnFontChangeHandler(AHandlerProc: TNotifyEvent); begin FOnFontChangedHandlers.Add(TMethod(AHandlerProc)); end; procedure TheTextDrawer.UnRegisterOnFontChangeHandler(AHandlerProc: TNotifyEvent); begin FOnFontChangedHandlers.Remove(TMethod(AHandlerProc)); end; { TheTextDrawerEx } procedure TheTextDrawerEx.AfterStyleSet; begin inherited; with FontStock do begin FCrntDx := BaseCharWidth - CharAdvance; case IsDBCSFont of False: begin if StockDC <> 0 then SetTextCharacterExtra(StockDC, CharExtra + FCrntDx); if IsTrueType or (not (fsItalic in Style)) then FExtTextOutProc := @TextOutOrExtTextOut else FExtTextOutProc := @ExtTextOutFixed; end; True: begin FCrntDBDx := DBCHAR_CALCULATION_FALED; FExtTextOutProc := @ExtTextOutWithETO; end; end; end; end; procedure TheTextDrawerEx.ExtTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer; FrameBottom: Integer = -1); begin FExtTextOutProc(X, Y, fuOptions, ARect, Text, Length); end; procedure TheTextDrawerEx.ExtTextOutFixed(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); begin LCLIntf.ExtTextOut(StockDC, X, Y, fuOptions, @ARect, Text, Length, nil); end; procedure TheTextDrawerEx.ExtTextOutForDBCS(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); var pCrnt: PChar; pTail: PChar; pRun: PChar; procedure GetSBCharRange; begin while (pRun <> pTail) and (not (pRun^ in LeadBytes)) do Inc(pRun); end; procedure GetDBCharRange; begin while (pRun <> pTail) and (pRun^ in LeadBytes) do Inc(pRun, 2); end; var TmpRect: TRect; Len: Integer; n: Integer; begin pCrnt := Text; pRun := Text; pTail := PChar(Pointer(Text) + Length); TmpRect := ARect; while pCrnt < pTail do begin GetSBCharRange; if pRun <> pCrnt then begin SetTextCharacterExtra(StockDC, CharExtra + FCrntDx); Len := PtrUInt(pRun) - PtrUInt(pCrnt); with TmpRect do begin n := GetCharWidth * Len; Right := Min(Left + n + GetCharWidth, ARect.Right); LCLIntf.ExtTextOut(StockDC, X, Y, fuOptions, @TmpRect, pCrnt, Len, nil); Inc(X, n); Inc(Left, n); end; end; pCrnt := pRun; if pRun = pTail then break; GetDBCharRange; SetTextCharacterExtra(StockDC, CharExtra + FCrntDBDx); Len := PtrUInt(pRun) - PtrUInt(pCrnt); with TmpRect do begin n := GetCharWidth * Len; Right := Min(Left + n + GetCharWidth, ARect.Right); LCLIntf.ExtTextOut(StockDC, X, Y, fuOptions, @TmpRect, pCrnt, Len, nil); Inc(X, n); Inc(Left, n); end; pCrnt := pRun; end; if (pCrnt = Text) or // maybe Text is not assigned or Length is 0 (TmpRect.Right < ARect.Right) then begin SetTextCharacterExtra(StockDC, CharExtra + FCrntDx); LCLIntf.ExtTextOut(StockDC, X, Y, fuOptions, @TmpRect, nil, 0, nil); end; end; procedure TheTextDrawerEx.ExtTextOutWithETO(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); begin inherited ExtTextOut(X, Y, fuOptions, ARect, Text, Length); end; procedure TheTextDrawerEx.TextOutOrExtTextOut(X, Y: Integer; fuOptions: UINT; const ARect: TRect; Text: PChar; Length: Integer); begin // this function may be used when: // a. the text does not containing any multi-byte characters // AND // a-1. current font is TrueType. // a-2. current font is RasterType and it is not italic. with ARect do if Assigned(Text) and (Length > 0) and (Left = X) and (Top = Y) and ((Bottom - Top) = GetCharHeight) and (Left + GetCharWidth * (Length + 1) > Right) then LCLIntf.TextOut(StockDC, X, Y, Text, Length) else LCLIntf.ExtTextOut(StockDC, X, Y, fuOptions, @ARect, Text, Length, nil) end; {$IFNDEF HE_LEADBYTES} procedure InitializeLeadBytes; var c: Char; begin for c := Low(Char) to High(Char) do if IsDBCSLeadByte(Byte(c)) then Include(LeadBytes, c); end; {$ENDIF} // HE_LEADBYTES initialization SynTextDrawerFinalization:=false; {$IFNDEF HE_LEADBYTES} InitializeLeadBytes; {$ENDIF} finalization // MG: We can't free the gFontsInfoManager here, because the synedit // components need it and will be destroyed with the Application object in // the lcl after this finalization section. // So, the flag SynTextDrawerFinalization is set and the gFontsInfoManager // will destroy itself, as soon, as it is not used anymore. SynTextDrawerFinalization:=true; if Assigned(gFontsInfoManager) and (gFontsInfoManager.FFontsInfo.Count=0) then FreeAndNil(gFontsInfoManager); end.