mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-07-20 11:26:08 +02:00
1089 lines
31 KiB
ObjectPascal
1089 lines
31 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
This file is part of the LazEdit package from the Lazarus IDE.
|
|
|
|
This content of this file is licensensed: Modified LGPL-2
|
|
Or at the users choice: Modified LGPL-3
|
|
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
|
|
for details about the license.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of the
|
|
Mozilla Public License Version 1.1 http://www.mozilla.org/MPL/
|
|
|
|
A copy used under either License can have the other Licenses removed from this
|
|
header. A note should be added that the original file is available with the
|
|
above choice of License.
|
|
*****************************************************************************
|
|
Written by Martin Friebe
|
|
}
|
|
unit LazEditTextGridPainter;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, fgl, Types, Math,
|
|
// LclBase
|
|
Graphics, LCLType, LCLIntf, GraphUtil,
|
|
// LazUtils
|
|
{$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy{$else} LazLoggerBase{$endif},
|
|
LazClasses, LazEditTextAttributes;
|
|
|
|
|
|
type
|
|
|
|
{ TLazEditTextGridPainterFontInfo }
|
|
|
|
TLazEditTextGridPainterFontInfo = class(TRefCountedObject)
|
|
strict private const
|
|
HIGH_ORD_FONTSTYLES = (2 << ord(High(TFontStyle)) ) - 1;
|
|
strict private type
|
|
TLazEditTextGridPainterFontStyleInfo = record
|
|
Font: TFont;
|
|
CharWidth, CharHeight: integer;
|
|
NeedEto: boolean;
|
|
end;
|
|
PLazEditTextGridPainterFontStyleInfo = ^ TLazEditTextGridPainterFontStyleInfo;
|
|
TLazEditTextGridPainterFontStyleInfos = array [0..HIGH_ORD_FONTSTYLES] of TLazEditTextGridPainterFontStyleInfo;
|
|
strict private
|
|
FFontStyleInfos: TLazEditTextGridPainterFontStyleInfos;
|
|
private
|
|
function GetFontHandle(AFontStyles: TFontStyles): HFONT;
|
|
function GetCharWidth(AFontStyles: TFontStyles): integer;
|
|
function GetCharHeight(AFontStyles: TFontStyles): integer;
|
|
function GetNeedEto(AFontStyles: TFontStyles): Boolean;
|
|
procedure InitWidthAndEto(AFontStyles: TFontStyles);
|
|
procedure ClearFonts;
|
|
{%H-}constructor CreateFor(AFont: TFont);
|
|
public
|
|
class function Create(AFont: TFont): TLazEditTextGridPainterFontInfo;
|
|
destructor Destroy; override;
|
|
function IsInfoFor(AFont: TFont): Boolean;
|
|
|
|
property FontHandle[AFontStyles: TFontStyles]: HFONT read GetFontHandle;
|
|
property CharWidth [AFontStyles: TFontStyles]: integer read GetCharWidth;
|
|
property CharHeight[AFontStyles: TFontStyles]: integer read GetCharHeight;
|
|
property NeedEto [AFontStyles: TFontStyles]: Boolean read GetNeedEto;
|
|
end;
|
|
|
|
{ TEtoBuffer }
|
|
|
|
TEtoBuffer = class
|
|
public
|
|
EtoData: Array of Integer;
|
|
function Eto: PInteger;
|
|
function Len: Integer;
|
|
procedure Clear;
|
|
procedure SetMinLength(ALen: Integer);
|
|
procedure Fill(ALen, AVal: Integer);
|
|
end;
|
|
|
|
TFrameColors = array[TLazTextAttrBorderSide] of TColor;
|
|
TFrameStyles = array[TLazTextAttrBorderSide] of TLazTextAttrLineStyle;
|
|
|
|
{ TLazEditTextDecorationPainter }
|
|
|
|
TLazEditTextDecorationPainter = class
|
|
strict private const
|
|
MAX_PEN_CACHE = 9;
|
|
WAVE_RAD = 3;
|
|
strict private type
|
|
PPenCache = ^TPenCache;
|
|
PPPenCache = ^PPenCache;
|
|
TPenCache = record
|
|
Allocated: Boolean;
|
|
Color: TColor;
|
|
Style: TLazTextAttrLineStyle;
|
|
Pen: HPen;
|
|
Next: PPenCache;
|
|
end;
|
|
strict private
|
|
FDC: HDC;
|
|
|
|
FCachedPens: array[0..MAX_PEN_CACHE-1] of TPenCache;
|
|
FCachedPenList: PPenCache;
|
|
FCachedPenCount: integer;
|
|
|
|
FHasSelectedPen: boolean;
|
|
FOrigPen, FSelectedPen: HPen;
|
|
|
|
function GetPenFor(AColor: TColor; AStyle: TLazTextAttrLineStyle): HPen;
|
|
procedure FreeHandles;
|
|
procedure InternalDrawLine(X, Y, X2, Y2: Integer; AWave: Boolean); inline;
|
|
public
|
|
procedure BeginPaint(ADC: HDC);
|
|
procedure EndPaint;
|
|
procedure UnselectPen;
|
|
|
|
procedure FillRect(const ARect: TRect; AColor: TColor);
|
|
procedure DrawLine(X, Y, X2, Y2: Integer; AColor: TColor; AStyle: TLazTextAttrLineStyle); inline;
|
|
procedure DrawFrame(X, Y, X2, Y2: Integer; const AColor: TFrameColors; const AStyle: TFrameStyles);
|
|
procedure DrawFrame(const ARect: TRect; const AColor: TFrameColors; const AStyle: TFrameStyles; AFillColor: TColor); inline;
|
|
end;
|
|
|
|
TLazEditTextGridPainter = class
|
|
strict private
|
|
FInPaint: boolean;
|
|
FDC: HDC;
|
|
FSaveDCIndex: Integer;
|
|
|
|
FHasSelectedFont: Boolean;
|
|
FSelectedFontStyle: TFontStyles;
|
|
FOrigFont: HGDIOBJ;
|
|
|
|
FCurForeColor, FCurBackColor: TColor;
|
|
FCurFontStyle: TFontStyles;
|
|
|
|
private
|
|
FMainCanvas: TCanvas;
|
|
FFontInfo: TLazEditTextGridPainterFontInfo;
|
|
FCharHeight, FCharWidth, FCharExtraWidth: integer;
|
|
|
|
FEto: TEtoBuffer;
|
|
FDecorator: TLazEditTextDecorationPainter;
|
|
|
|
FBackColor: TColor;
|
|
FForeColor: TColor;
|
|
FFontStyle: TFontStyles;
|
|
FFrameColors: TFrameColors;
|
|
FFrameStyles: TFrameStyles;
|
|
|
|
function GetCharExtra: Integer;
|
|
function GetCharWidth: Integer;
|
|
function GetEto: TEtoBuffer;
|
|
function GetFrameColor(ASide: TLazTextAttrBorderSide): TColor;
|
|
function GetFrameStyle(ASide: TLazTextAttrBorderSide): TLazTextAttrLineStyle;
|
|
procedure SetCharExtra(AValue: Integer);
|
|
procedure SetFrameColor(ASide: TLazTextAttrBorderSide; AValue: TColor);
|
|
procedure SetFrameStyle(ASide: TLazTextAttrBorderSide; AValue: TLazTextAttrLineStyle);
|
|
public
|
|
constructor Create(ACanvas: TCanvas; AFont: TFont);
|
|
destructor Destroy; override;
|
|
|
|
procedure SetBaseFont(AFont: TFont);
|
|
procedure AddBaseStyle(const AStyle: TFontStyles);
|
|
|
|
procedure BeginPaint; inline;
|
|
procedure EndPaint; inline;
|
|
(* BeginCustomCanvas includes BeginPaint *)
|
|
procedure BeginCustomCanvas(ACanvas: TCanvas);
|
|
procedure EndCustomCanvas;
|
|
|
|
procedure FillRect(const aRect: TRect);
|
|
procedure DrawLine(X, Y, X2, Y2: Integer; AColor: TColor);
|
|
procedure DrawFrame(const ARect: TRect);
|
|
|
|
procedure ExtTextOut(X, Y: Integer; AnOptions: UINT; const ARect: TRect;
|
|
AText: PChar; ALength: Integer); deprecated;
|
|
procedure NewTextOut(X, Y: Integer; AnOptions: UINT; const ARect: TRect;
|
|
AText: PChar; ALength: Integer; AnEto: TEtoBuffer);
|
|
|
|
procedure ClearColors;
|
|
property ForeColor: TColor read FForeColor write FForeColor;
|
|
property BackColor: TColor read FBackColor write FBackColor;
|
|
property Style: TFontStyles read FFontStyle write FFontStyle;
|
|
|
|
procedure SetFrame(AColor: TColor; AStyle: TLazTextAttrLineStyle; AnEdges: TLazTextAttrFrameEdges = sfeAround);
|
|
property FrameColor[ASide: TLazTextAttrBorderSide]: TColor read GetFrameColor write SetFrameColor;
|
|
property FrameStyle[ASide: TLazTextAttrBorderSide]: TLazTextAttrLineStyle read GetFrameStyle write SetFrameStyle;
|
|
|
|
function NeedsEto: boolean; // for current style
|
|
property Eto: TEtoBuffer read GetEto;
|
|
|
|
property CharWidth: Integer read GetCharWidth;
|
|
property CharHeight: Integer read FCharHeight;
|
|
property CharExtra: Integer read GetCharExtra write SetCharExtra; // included in CharWidth
|
|
end;
|
|
|
|
implementation
|
|
|
|
type
|
|
|
|
{ TLazEditTextGridPainterFontInfoList }
|
|
|
|
TLazEditTextGridPainterFontInfoList = class(specialize TFPGObjectList<TLazEditTextGridPainterFontInfo>)
|
|
public
|
|
function FindFontInfo(AFont: TFont): TLazEditTextGridPainterFontInfo;
|
|
end;
|
|
|
|
var
|
|
DBG_FONTMETRIC: PLazLoggerLogGroup;
|
|
DBG_FontName: string;
|
|
LazEditTextGridPainterFontInfoList: TLazEditTextGridPainterFontInfoList;
|
|
|
|
|
|
function OrdFontStyles(AFontStyles: TFontStyles): Integer; inline;
|
|
begin
|
|
Result := Integer(AFontStyles);
|
|
end;
|
|
|
|
procedure GetCharMetrics(DC: HDC; out AWidth, AHeight: Integer; out ANeedEto: Boolean);
|
|
Procedure DebugFont(s: String; a: array of const); inline;
|
|
begin
|
|
DebugLn(DBG_FONTMETRIC, DBG_FontName + 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 LazEditDebugFont}
|
|
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
|
|
DebugFont('Overhang w=%d w2=%d w3=%d', [w, w2, w3]);
|
|
o := w - Min(w2, w3);
|
|
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 LazEditDebugFont}
|
|
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;
|
|
OverHang: Integer;
|
|
Size1: TSize;
|
|
tmw: Integer;
|
|
begin
|
|
// Calculate advance of a character.
|
|
|
|
// TextMetric may fail, because:
|
|
// tmMaxCharWidth may be the Awidth of a single AWidth (Latin) char, like "M"
|
|
// or a double AWidth (Chinese) char
|
|
// tmAveCharWidth is to small for proprtional fonts, as we need he Awidth of the
|
|
// widest Latin char ("M").
|
|
// Even Monospace fonts, may have a smaller tmAveCharWidth (seen with Japanese)
|
|
|
|
// take several samples
|
|
ANeedEto := False;
|
|
GetWHOForChar('M', AWidth, AHeight, OverHang, ANeedEto);
|
|
AdjustWHOForChar('W', AWidth, AHeight, OverHang, ANeedEto);
|
|
AdjustWHOForChar('@', AWidth, AHeight, OverHang, ANeedEto);
|
|
AdjustWHOForChar('X', AWidth, AHeight, OverHang, ANeedEto);
|
|
AdjustWHOForChar('m', AWidth, AHeight, OverHang, ANeedEto);
|
|
// Small Chars to detect proportional fonts
|
|
AdjustWHOForChar('i', AWidth, AHeight, OverHang, ANeedEto);
|
|
AdjustWHOForChar(':', AWidth, AHeight, OverHang, ANeedEto);
|
|
AdjustWHOForChar('''', AWidth, AHeight, OverHang, ANeedEto);
|
|
|
|
// Negative Overhang ?
|
|
if (not ANeedEto) and GetTextExtentPoint(DC, PChar('Ta'), 2, Size1{%H-}) then
|
|
if Size1.cx < 2 * AWidth then begin
|
|
DebugFont('Negative Overhang for "Ta" cx=%d AWidth=%d Overhang=%d', [Size1.cx, AWidth, OverHang]);
|
|
ANeedEto := True;
|
|
end;
|
|
|
|
// Make sure we get the correct AHeight
|
|
if GetTextExtentPoint(DC, PChar('Tgq[_|^'), 7, Size1) then
|
|
AHeight := Max(AHeight, Size1.cy);
|
|
|
|
// DoubleCheck the result with GetTextMetrics
|
|
GetTextMetrics(DC, TM{%H-});
|
|
{$IFDEF LazEditDebugFont}
|
|
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 AWidth = 0 then begin
|
|
DebugFont('No AWidth from GetTextExtentPoint', []);
|
|
AWidth := tmw;
|
|
end
|
|
else if (AWidth > tmw) and (TM.tmMaxCharWidth > 0) then begin
|
|
DebugFont('AWidth(%d) > tmMaxWidth+Over(%d)', [AWidth, tmw]);
|
|
// take a guess, this is probably a broken font
|
|
AWidth := Min(AWidth, round((TM.tmMaxCharWidth + Max(TM.tmOverhang,0)) * 1.2));
|
|
ANeedEto := True;
|
|
end;
|
|
|
|
if AHeight = 0 then begin
|
|
DebugFont('No AHeight from GetTextExtentPoint, tmHeight=%d', [TM.tmHeight]);
|
|
AHeight := TM.tmHeight;
|
|
end
|
|
else if AHeight < TM.tmHeight then begin
|
|
DebugFont('AHeight from GetTextExtentPoint to low AHeight=%d, tmHeight=%d', [AHeight, TM.tmHeight]);
|
|
AHeight := TM.tmHeight;
|
|
end;
|
|
|
|
// If we have a broken font, make sure we return a positive value
|
|
if AWidth <= 0 then begin
|
|
DebugFont('SynTextDrawer: Fallback on AWidth', []);
|
|
AWidth := 1 + AHeight * 8 div 10;
|
|
end;
|
|
end;
|
|
|
|
{ TLazEditTextGridPainterFontInfoList }
|
|
|
|
function TLazEditTextGridPainterFontInfoList.FindFontInfo(AFont: TFont
|
|
): TLazEditTextGridPainterFontInfo;
|
|
var
|
|
i: Integer;
|
|
fnt: TFont;
|
|
begin
|
|
Result := nil;
|
|
if Self = nil then
|
|
exit;
|
|
|
|
fnt := AFont;
|
|
if AFont.Style <> [] then begin
|
|
fnt := TFont.Create;
|
|
fnt.Assign(AFont);
|
|
fnt.Style := [];
|
|
end;
|
|
|
|
for i := 0 to Count - 1 do begin
|
|
Result := Items[i];
|
|
if Result.IsInfoFor(fnt) then begin
|
|
if fnt <> AFont then fnt.Destroy;
|
|
exit;
|
|
end;
|
|
end;
|
|
Result := nil;
|
|
if fnt <> AFont then fnt.Destroy;
|
|
end;
|
|
|
|
{ TLazEditTextGridPainterFontInfo }
|
|
|
|
function TLazEditTextGridPainterFontInfo.GetFontHandle(AFontStyles: TFontStyles): HFONT;
|
|
var
|
|
fnt: TFont;
|
|
begin
|
|
fnt := FFontStyleInfos[OrdFontStyles(AFontStyles)].Font;
|
|
if fnt = nil then begin
|
|
assert(AFontStyles <> [], 'TLazEditTextGridPainterFontInfo.GetCharWidth: AFontStyles <> []');
|
|
fnt := TFont.Create;
|
|
fnt.Assign(FFontStyleInfos[0].Font);
|
|
fnt.Style := AFontStyles;
|
|
FFontStyleInfos[OrdFontStyles(AFontStyles)].Font := fnt;
|
|
end;
|
|
Result := fnt.Handle;
|
|
end;
|
|
|
|
function TLazEditTextGridPainterFontInfo.GetCharWidth(AFontStyles: TFontStyles): integer;
|
|
begin
|
|
Result := FFontStyleInfos[OrdFontStyles(AFontStyles)].CharWidth;
|
|
if Result <= 0 then begin
|
|
InitWidthAndEto(AFontStyles);
|
|
Result := FFontStyleInfos[OrdFontStyles(AFontStyles)].CharWidth;
|
|
end;
|
|
end;
|
|
|
|
function TLazEditTextGridPainterFontInfo.GetCharHeight(AFontStyles: TFontStyles): integer;
|
|
begin
|
|
Result := FFontStyleInfos[OrdFontStyles(AFontStyles)].CharHeight;
|
|
if Result <= 0 then begin
|
|
InitWidthAndEto(AFontStyles);
|
|
Result := FFontStyleInfos[OrdFontStyles(AFontStyles)].CharHeight;
|
|
end;
|
|
end;
|
|
|
|
function TLazEditTextGridPainterFontInfo.GetNeedEto(AFontStyles: TFontStyles): Boolean;
|
|
begin
|
|
if FFontStyleInfos[OrdFontStyles(AFontStyles)].CharWidth <= 0 then
|
|
InitWidthAndEto(AFontStyles);
|
|
Result := FFontStyleInfos[OrdFontStyles(AFontStyles)].NeedEto;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainterFontInfo.InitWidthAndEto(AFontStyles: TFontStyles);
|
|
var
|
|
DC: HDC;
|
|
OldFntHndl: HGDIOBJ;
|
|
info: PLazEditTextGridPainterFontStyleInfo;
|
|
begin
|
|
DC := GetDC(0);
|
|
OldFntHndl := SelectObject(DC, FontHandle[AFontStyles]);
|
|
try
|
|
info := @FFontStyleInfos[OrdFontStyles(AFontStyles)];
|
|
DBG_FontName := 'Font=' + info^.Font.Name + ' Size=' + IntToStr(info^.Font.Size) + ' [' + dbgs(info^.Font.Style) + ']: '; // for debugln
|
|
GetCharMetrics(DC, info^.CharWidth, info^.CharHeight, info^.NeedEto);
|
|
DBG_FontName := '';
|
|
finally
|
|
SelectObject(DC, OldFntHndl);
|
|
ReleaseDC(0, DC);
|
|
end;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainterFontInfo.ClearFonts;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 1 to HIGH_ORD_FONTSTYLES do
|
|
FreeAndNil(FFontStyleInfos[i].Font);
|
|
end;
|
|
|
|
constructor TLazEditTextGridPainterFontInfo.CreateFor(AFont: TFont);
|
|
var
|
|
fnt: TFont;
|
|
begin
|
|
inherited Create;
|
|
|
|
fnt := TFont.Create;
|
|
fnt.Assign(AFont);
|
|
fnt.Style := [];
|
|
FFontStyleInfos[0].Font := fnt;
|
|
|
|
if LazEditTextGridPainterFontInfoList = nil then
|
|
LazEditTextGridPainterFontInfoList := TLazEditTextGridPainterFontInfoList.Create(False);
|
|
LazEditTextGridPainterFontInfoList.Add(Self);
|
|
end;
|
|
|
|
class function TLazEditTextGridPainterFontInfo.Create(AFont: TFont
|
|
): TLazEditTextGridPainterFontInfo;
|
|
begin
|
|
if LazEditTextGridPainterFontInfoList <> nil then begin
|
|
Result := LazEditTextGridPainterFontInfoList.FindFontInfo(AFont);
|
|
if Result <> nil then
|
|
exit;
|
|
end;
|
|
Result := CreateFor(AFont);
|
|
end;
|
|
|
|
destructor TLazEditTextGridPainterFontInfo.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
|
|
if LazEditTextGridPainterFontInfoList <> nil then begin
|
|
LazEditTextGridPainterFontInfoList.Remove(Self);
|
|
if LazEditTextGridPainterFontInfoList.Count = 0 then
|
|
FreeAndNil(LazEditTextGridPainterFontInfoList);
|
|
end;
|
|
|
|
ClearFonts;
|
|
FreeAndNil(FFontStyleInfos[0].Font);
|
|
end;
|
|
|
|
function TLazEditTextGridPainterFontInfo.IsInfoFor(AFont: TFont): Boolean;
|
|
begin
|
|
Result := AFont.IsEqual(FFontStyleInfos[0].Font);
|
|
end;
|
|
|
|
{ TEtoBuffer }
|
|
|
|
function TEtoBuffer.Eto: PInteger;
|
|
begin
|
|
if Self = nil then
|
|
exit(nil);
|
|
|
|
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;
|
|
|
|
procedure TEtoBuffer.Fill(ALen, AVal: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
SetMinLength(ALen);
|
|
for i := 0 to ALen - 1 do
|
|
EtoData[i] := AVal;
|
|
end;
|
|
|
|
{ TLazEditTextDecorationPainter }
|
|
|
|
function TLazEditTextDecorationPainter.GetPenFor(AColor: TColor; AStyle: TLazTextAttrLineStyle
|
|
): HPen;
|
|
var
|
|
CachePtr, PrevCachePtr: PPenCache;
|
|
CachePtrPtr, PrevCachePtrPtr: PPPenCache;
|
|
LogBrush: TLogBrush;
|
|
s: DWord;
|
|
begin
|
|
AColor := ColorToRGB(AColor);
|
|
if AStyle = slsWaved then
|
|
AStyle := slsSolid;
|
|
|
|
CachePtr := FCachedPenList;
|
|
CachePtrPtr := nil;
|
|
PrevCachePtr := nil;
|
|
PrevCachePtrPtr := nil;
|
|
while CachePtr <> nil do begin
|
|
if (CachePtr^.Color = AColor) and (CachePtr^.Style = AStyle) then begin
|
|
if CachePtrPtr <> nil then begin
|
|
// move to first in list
|
|
CachePtrPtr^ := CachePtr^.Next;
|
|
CachePtr^.Next := FCachedPenList;
|
|
FCachedPenList := CachePtr;
|
|
end;
|
|
Result := CachePtr^.Pen;
|
|
exit;
|
|
end;
|
|
PrevCachePtr := CachePtr;
|
|
PrevCachePtrPtr := CachePtrPtr;
|
|
CachePtrPtr := @CachePtr^.Next;
|
|
CachePtr := CachePtr^.Next;
|
|
end;
|
|
|
|
|
|
case AStyle of
|
|
slsSolid: s := PS_SOLID + PS_GEOMETRIC + PS_ENDCAP_FLAT + PS_JOIN_MITER;
|
|
slsDashed: s := PS_DASH + PS_GEOMETRIC + PS_ENDCAP_FLAT + PS_JOIN_MITER;
|
|
slsDotted: s := PS_DOT + PS_GEOMETRIC + PS_ENDCAP_FLAT + PS_JOIN_MITER;
|
|
end;
|
|
|
|
LogBrush := Default(TLogBrush);
|
|
LogBrush.lbStyle := BS_SOLID;
|
|
LogBrush.lbColor := ColorToRGB(AColor);
|
|
|
|
Result := ExtCreatePen(s, 1, LogBrush, 0, nil);
|
|
|
|
if FCachedPenCount < MAX_PEN_CACHE then begin
|
|
PrevCachePtr := @FCachedPens[FCachedPenCount];
|
|
inc(FCachedPenCount);
|
|
end
|
|
else begin
|
|
PrevCachePtrPtr^ := nil;
|
|
DeleteObject(PrevCachePtr^.Pen);
|
|
end;
|
|
|
|
PrevCachePtr^.Pen := Result;
|
|
PrevCachePtr^.Allocated := True;
|
|
PrevCachePtr^.Color := AColor;
|
|
PrevCachePtr^.Style := AStyle;
|
|
PrevCachePtr^.Next := FCachedPenList;
|
|
FCachedPenList := PrevCachePtr;
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.FreeHandles;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
UnselectPen;
|
|
for i := 0 to MAX_PEN_CACHE-1 do
|
|
if FCachedPens[i].Allocated then begin
|
|
DeleteObject(FCachedPens[i].Pen);
|
|
FCachedPens[i].Allocated := False;
|
|
end;
|
|
FCachedPenList := nil;
|
|
FCachedPenCount := 0;
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.InternalDrawLine(X, Y, X2, Y2: Integer; AWave: Boolean);
|
|
var
|
|
dummy: TPoint;
|
|
begin
|
|
MoveToEx(FDC, X, Y, @dummy);
|
|
if AWave then
|
|
WaveTo(FDC, x2, Y2, WAVE_RAD)
|
|
else
|
|
LineTo(FDC, X2, Y2);
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.BeginPaint(ADC: HDC);
|
|
begin
|
|
assert(FDC = 0, 'TLazEditTextDecorationPainter.BeginPaint: FDC = 0');
|
|
FDC := ADc;
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.EndPaint;
|
|
begin
|
|
FreeHandles;
|
|
FDC := 0;
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.UnselectPen;
|
|
begin
|
|
if not FHasSelectedPen then
|
|
exit;
|
|
|
|
SelectObject(FDC, FOrigPen);
|
|
FHasSelectedPen := False;
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.FillRect(const ARect: TRect; AColor: TColor);
|
|
begin
|
|
if AColor = clNone then
|
|
exit;
|
|
//LCLIntf.FillRect(FDC, aRect, brush);
|
|
// TODO: Current BK Color
|
|
if AColor <> clDefault then // color already set
|
|
SetBkColor(FDC, ColorToRGB(AColor));
|
|
ExtTextOut(FDC, 0, 0, ETO_OPAQUE, @aRect, nil, 0, nil);
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.DrawLine(X, Y, X2, Y2: Integer; AColor: TColor;
|
|
AStyle: TLazTextAttrLineStyle);
|
|
var
|
|
p: HPEN;
|
|
begin
|
|
p := GetPenFor(AColor, AStyle);
|
|
if FHasSelectedPen then begin
|
|
if p <> FSelectedPen then
|
|
SelectObject(FDC, p);
|
|
end
|
|
else begin
|
|
FOrigPen := SelectObject(FDC, p);
|
|
FHasSelectedPen := True;
|
|
end;
|
|
|
|
InternalDrawLine(X, Y, X2, Y2, AStyle = slsWaved);
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.DrawFrame(X, Y, X2, Y2: Integer;
|
|
const AColor: TFrameColors; const AStyle: TFrameStyles);
|
|
var
|
|
Todo: TLazTextAttrBorderSides;
|
|
col: TColor;
|
|
st, st2: TLazTextAttrLineStyle;
|
|
wav: Boolean;
|
|
o: Integer;
|
|
begin
|
|
Todo := [bsLeft, bsTop, bsRight, bsBottom];
|
|
|
|
while Todo <> [] do begin
|
|
col := clNone;
|
|
|
|
if (bsLeft in Todo) then begin
|
|
Exclude(Todo, bsLeft);
|
|
if col = clNone then begin
|
|
col := AColor[bsLeft];
|
|
if col <> clNone then begin
|
|
st := AStyle[bsLeft];
|
|
st2 := st;
|
|
wav := st2 = slsWaved;
|
|
if wav then st2 := slsSolid;
|
|
DrawLine(X, Y, X, Y2, col, st);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if (bsTop in Todo) then begin
|
|
if col = clNone then begin
|
|
Exclude(Todo, bsTop);
|
|
col := AColor[bsTop];
|
|
if col <> clNone then begin
|
|
st := AStyle[bsTop];
|
|
st2 := st;
|
|
wav := st2 = slsWaved;
|
|
if wav then st2 := slsSolid;
|
|
DrawLine(X, Y, X2, Y, col, st);
|
|
end;
|
|
end
|
|
else
|
|
if (AColor[bsTop] = col) and
|
|
( (AStyle[bsTop] = st2) or
|
|
( wav and (AStyle[bsTop] = slsWaved) )
|
|
)
|
|
then begin
|
|
Exclude(Todo, bsTop);
|
|
InternalDrawLine(X, Y, X2, Y, AStyle[bsTop] = slsWaved);
|
|
end;
|
|
end;
|
|
|
|
if (bsRight in Todo) then begin
|
|
if col = clNone then begin
|
|
Exclude(Todo, bsRight);
|
|
col := AColor[bsRight];
|
|
if col <> clNone then begin
|
|
st := AStyle[bsRight];
|
|
st2 := st;
|
|
wav := st2 = slsWaved;
|
|
if wav then st2 := slsSolid;
|
|
o := 1;
|
|
if wav then o := WAVE_RAD;
|
|
DrawLine(X2 - o, Y, X2 - o, Y2, col, st);
|
|
end;
|
|
end
|
|
else
|
|
if (AColor[bsRight] = col) and
|
|
( (AStyle[bsRight] = st2) or
|
|
( wav and (AStyle[bsRight] = slsWaved) )
|
|
)
|
|
then begin
|
|
Exclude(Todo, bsRight);
|
|
o := 1;
|
|
if AStyle[bsRight] = slsWaved then o := WAVE_RAD;
|
|
InternalDrawLine(X2 - o, Y, X2 - o, Y2, AStyle[bsRight] = slsWaved);
|
|
end;
|
|
end;
|
|
|
|
if (bsBottom in Todo) then begin
|
|
if col = clNone then begin
|
|
Exclude(Todo, bsBottom);
|
|
col := AColor[bsBottom];
|
|
if col <> clNone then begin
|
|
st := AStyle[bsBottom];
|
|
st2 := st;
|
|
wav := st2 = slsWaved;
|
|
if wav then st2 := slsSolid;
|
|
o := 1;
|
|
if wav then o := WAVE_RAD;
|
|
DrawLine(X, Y2 - o, X2, Y2 - o, col, st);
|
|
end;
|
|
end
|
|
else
|
|
if (AColor[bsBottom] = col) and
|
|
( (AStyle[bsBottom] = st2) or
|
|
( wav and (AStyle[bsBottom] = slsWaved) )
|
|
)
|
|
then begin
|
|
Exclude(Todo, bsBottom);
|
|
o := 1;
|
|
if AStyle[bsBottom] = slsWaved then o := WAVE_RAD;
|
|
InternalDrawLine(X, Y2 - o, X2, Y2 - o, AStyle[bsBottom] = slsWaved);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TLazEditTextDecorationPainter.DrawFrame(const ARect: TRect; const AColor: TFrameColors;
|
|
const AStyle: TFrameStyles; AFillColor: TColor);
|
|
begin
|
|
FillRect(ARect, AFillColor);
|
|
DrawFrame(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, AColor, AStyle);
|
|
end;
|
|
|
|
{ TLazEditTextGridPainter }
|
|
|
|
function TLazEditTextGridPainter.GetCharExtra: Integer;
|
|
begin
|
|
Result := Max(FCharExtraWidth, -FCharWidth + 1);
|
|
end;
|
|
|
|
function TLazEditTextGridPainter.GetCharWidth: Integer;
|
|
begin
|
|
Result := Max(1, FCharWidth + FCharExtraWidth);
|
|
end;
|
|
|
|
function TLazEditTextGridPainter.GetEto: TEtoBuffer;
|
|
begin
|
|
if FEto = nil then
|
|
FEto := TEtoBuffer.Create;
|
|
Result := FEto;
|
|
end;
|
|
|
|
function TLazEditTextGridPainter.GetFrameColor(ASide: TLazTextAttrBorderSide): TColor;
|
|
begin
|
|
Result := FFrameColors[ASide];
|
|
end;
|
|
|
|
function TLazEditTextGridPainter.GetFrameStyle(ASide: TLazTextAttrBorderSide
|
|
): TLazTextAttrLineStyle;
|
|
begin
|
|
Result := FFrameStyles[ASide];
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.SetCharExtra(AValue: Integer);
|
|
begin
|
|
FCharExtraWidth := AValue;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.SetFrameColor(ASide: TLazTextAttrBorderSide; AValue: TColor);
|
|
begin
|
|
FFrameColors[ASide] := AValue;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.SetFrameStyle(ASide: TLazTextAttrBorderSide;
|
|
AValue: TLazTextAttrLineStyle);
|
|
begin
|
|
FFrameStyles[ASide] := AValue;
|
|
end;
|
|
|
|
constructor TLazEditTextGridPainter.Create(ACanvas: TCanvas; AFont: TFont);
|
|
begin
|
|
inherited Create;
|
|
FMainCanvas := ACanvas;
|
|
SetBaseFont(AFont); // TODO: don't alloc the font, synedit send a dummy at first
|
|
FDecorator := TLazEditTextDecorationPainter.Create;
|
|
ClearColors;
|
|
end;
|
|
|
|
destructor TLazEditTextGridPainter.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FEto.Free;
|
|
FDecorator.Free;
|
|
FFontInfo.ReleaseReference;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.SetBaseFont(AFont: TFont);
|
|
begin
|
|
FFontInfo.ReleaseReference;
|
|
FFontInfo := LazEditTextGridPainterFontInfoList.FindFontInfo(AFont);
|
|
if FFontInfo = nil then
|
|
FFontInfo := TLazEditTextGridPainterFontInfo.Create(AFont);
|
|
FFontInfo.AddReference;
|
|
|
|
FCharWidth := FFontInfo.CharWidth[AFont.Style];
|
|
FCharHeight := FFontInfo.CharHeight[AFont.Style];
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.AddBaseStyle(const AStyle: TFontStyles);
|
|
begin
|
|
assert(FFontInfo<>nil, 'TLazEditTextGridPainter.AddBaseStyle: FFontInfo<>nil');
|
|
FCharWidth := max(FCharWidth, FFontInfo.CharWidth[AStyle]);
|
|
FCharHeight := max(FCharHeight, FFontInfo.CharHeight[AStyle]);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.BeginPaint;
|
|
begin
|
|
BeginCustomCanvas(FMainCanvas);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.EndPaint;
|
|
begin
|
|
EndCustomCanvas;
|
|
FDC := 0;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.BeginCustomCanvas(ACanvas: TCanvas);
|
|
begin
|
|
assert(FDC = 0, 'TLazEditTextGridPainter.BeginCustomCanvas: FDC = 0');
|
|
assert(not FInPaint, 'TLazEditTextGridPainter.BeginCustomCanvas: not FInPaint');
|
|
FInPaint := True;
|
|
ClearColors;
|
|
FHasSelectedFont := False;
|
|
FCurForeColor := clNone;
|
|
FCurBackColor := clNone;
|
|
FDC := ACanvas.Handle;
|
|
FSaveDCIndex := SaveDC(FDC);
|
|
FDecorator.BeginPaint(FDC);
|
|
LCLIntf.SetBkMode(FDC, TRANSPARENT);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.EndCustomCanvas;
|
|
begin
|
|
assert(FInPaint, 'TLazEditTextGridPainter.EndCustomCanvas: FInPaint');
|
|
FInPaint := False;
|
|
FDecorator.EndPaint;
|
|
RestoreDC(FDC, FSaveDCIndex);
|
|
FDC := 0;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.FillRect(const aRect: TRect);
|
|
begin
|
|
if FCurBackColor <> FBackColor then begin
|
|
FCurBackColor := FBackColor;
|
|
LCLIntf.SetBkColor(FDC, ColorToRGB(FCurBackColor));
|
|
end;
|
|
FDecorator.FillRect(aRect, clDefault);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.DrawLine(X, Y, X2, Y2: Integer; AColor: TColor);
|
|
begin
|
|
FDecorator.DrawLine(X, Y, X2, Y2, AColor, slsSolid);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.DrawFrame(const ARect: TRect);
|
|
begin
|
|
FDecorator.DrawFrame(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, FFrameColors, FFrameStyles);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.ExtTextOut(X, Y: Integer; AnOptions: UINT; const ARect: TRect;
|
|
AText: PChar; ALength: Integer);
|
|
|
|
function HasFrame: Boolean;
|
|
var
|
|
Side: TLazTextAttrBorderSide;
|
|
begin
|
|
for Side := Low(TLazTextAttrBorderSide) to High(TLazTextAttrBorderSide) do
|
|
if FFrameColors[Side] <> clNone then
|
|
Exit(True);
|
|
Result := False;
|
|
end;
|
|
|
|
var
|
|
RectFrame: TRect;
|
|
e: TEtoBuffer;
|
|
begin
|
|
if HasFrame then // draw background // TODO: only if not default bg color
|
|
begin
|
|
if FCurBackColor <> FBackColor then begin
|
|
FCurBackColor := FBackColor;
|
|
LCLIntf.SetBkColor(FDC, ColorToRGB(FCurBackColor));
|
|
end;
|
|
FDecorator.DrawFrame(ARect, FFrameColors, FFrameStyles, clDefault);
|
|
AnOptions := 0;
|
|
end;
|
|
|
|
e := nil;
|
|
if NeedsEto then begin
|
|
e := Eto;
|
|
e.Fill(ALength, CharWidth);
|
|
end;
|
|
|
|
NewTextOut(X, Y, AnOptions, ARect, AText, ALength, e);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.NewTextOut(X, Y: Integer; AnOptions: UINT; const ARect: TRect;
|
|
AText: PChar; ALength: Integer; AnEto: TEtoBuffer);
|
|
{$IFDEF WINDOWS_LIGATURE}
|
|
var
|
|
W: WideString;
|
|
Glyphs: array of WideChar;
|
|
CharPlaceInfo: GCP_RESULTSW;
|
|
{$ENDIF}
|
|
begin
|
|
//if FCurBackColor <> FBackColor then begin
|
|
if true then begin // Textarea still changes bk color too
|
|
FCurBackColor := FBackColor;
|
|
LCLIntf.SetBkColor(FDC, ColorToRGB(FCurBackColor));
|
|
end;
|
|
|
|
if AText <> nil then begin
|
|
if FCurForeColor <> FForeColor then begin
|
|
FCurForeColor := FForeColor;
|
|
LCLIntf.SetTextColor(FDC, ColorToRGB(FCurForeColor));
|
|
end;
|
|
|
|
if FHasSelectedFont then begin
|
|
if FCurFontStyle <> FFontStyle then begin
|
|
SelectObject(FDC, FFontInfo.FontHandle[FFontStyle]);
|
|
FCurFontStyle := FFontStyle;
|
|
end;
|
|
end
|
|
else begin
|
|
FHasSelectedFont := True;
|
|
FOrigFont := SelectObject(FDC, FFontInfo.FontHandle[FFontStyle]);
|
|
FCurFontStyle := FFontStyle;
|
|
end;
|
|
end;
|
|
|
|
{$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, AnEto.Eto);
|
|
exit;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
LCLIntf.ExtUTF8Out(FDC, X, Y, AnOptions, @ARect, AText, ALength, AnEto.Eto);
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.ClearColors;
|
|
var
|
|
i: TLazTextAttrBorderSide;
|
|
begin
|
|
FBackColor := clNone;
|
|
FForeColor := clNone;
|
|
FFontStyle := [];
|
|
for i := low(TLazTextAttrBorderSide) to High(TLazTextAttrBorderSide) do begin
|
|
FFrameColors[i] := clNone;
|
|
FFrameStyles[i] := slsSolid;
|
|
end;
|
|
end;
|
|
|
|
procedure TLazEditTextGridPainter.SetFrame(AColor: TColor; AStyle: TLazTextAttrLineStyle;
|
|
AnEdges: TLazTextAttrFrameEdges);
|
|
var
|
|
i: TLazTextAttrBorderSide;
|
|
sides: TLazTextAttrBorderSides;
|
|
begin
|
|
sides := LazTextFrameEdgeToSides[AnEdges];
|
|
for i := low(TLazTextAttrBorderSide) to High(TLazTextAttrBorderSide) do begin
|
|
if i in sides then begin
|
|
FFrameColors[i] := AColor;
|
|
FFrameStyles[i] := AStyle;
|
|
end
|
|
else begin
|
|
FFrameStyles[i] := AStyle;
|
|
FFrameStyles[i] := slsSolid;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TLazEditTextGridPainter.NeedsEto: boolean;
|
|
begin
|
|
Result := (FCharExtraWidth <> 0) or
|
|
(FCharWidth <> FFontInfo.CharWidth[FFontStyle]) or
|
|
FFontInfo.NeedEto[FFontStyle];
|
|
end;
|
|
|
|
initialization
|
|
DBG_FONTMETRIC := DebugLogger.RegisterLogGroup('LazEditDebugFont' {$IFDEF LazEditDebugFont} , True {$ENDIF} );
|
|
|
|
finalization
|
|
FreeAndNil(LazEditTextGridPainterFontInfoList);
|
|
end.
|
|
|