SynEdit: Reimplemented detection of CharAdvandce/Width; fix issue #13651; Also better display for proportional fonts, through better detection of widest char (often "@", not "M")

git-svn-id: trunk@19907 -
This commit is contained in:
martin 2009-05-10 14:56:32 +00:00
parent c6f0f92021
commit e747489eff

View File

@ -75,7 +75,7 @@ uses
{$ELSE}
Windows,
{$ENDIF}
SysUtils, Classes, Graphics;
SysUtils, Classes, Graphics, Types;
type
TheStockFontPatterns = 0..(1 shl (1 + Ord(High(TFontStyle))));
@ -87,6 +87,7 @@ type
Handle: HFont;
CharAdv: Integer; // char advance of single-byte code
CharHeight: Integer;
NeedETO: Boolean;
end;
PheFontsData = ^TheFontsData;
@ -147,10 +148,11 @@ type
function GetBaseFont: TFont;
function GetIsDBCSFont: Boolean;
function GetIsTrueType: Boolean;
function GetNeedETO: Boolean;
protected
function InternalGetDC: HDC; virtual;
procedure InternalReleaseDC(Value: HDC); virtual;
function CalcFontAdvance(DC: HDC; pCharHeight: PInteger): Integer; virtual;
Procedure CalcFontAdvance(DC: HDC; FontData: PheFontData; FontHeight: integer);
function GetCharAdvance: Integer; virtual;
function GetCharHeight: Integer; virtual;
function GetFontData(idx: Integer): PheFontData; virtual;
@ -171,6 +173,7 @@ type
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;
@ -557,54 +560,137 @@ end;
// CalcFontAdvance : Calculation a advance of a character of a font.
// [*]hCalcFont will be selected as FDC's font if FDC wouldn't be zero.
function TheFontStock.CalcFontAdvance(DC: HDC; pCharHeight: PInteger): Integer;
Procedure TheFontStock.CalcFontAdvance(DC: HDC; FontData: PheFontData;
FontHeight: integer);
procedure GetWHOForChar(s: char; out w, h ,o : Integer; var eto: Boolean);
var
s1, s2, s3: String;
Size1, Size2, Size3: TSize;
w2: Integer;
begin
s1 := s;
s2 := s1 + s;
s3 := s2 + s;
if not(GetTextExtentPoint(DC, PChar(s1), 1, Size1) and
GetTextExtentPoint(DC, PChar(s2), 2, Size2) and
GetTextExtentPoint(DC, PChar(s3), 3, Size3)) then
begin
debugln('SynTextDrawer: Can not us GetTextExtentPoint');
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
// Calculate the Width of 1 char, with NO overhang
w := Size2.cx - Size1.cx;
o := Size1.cx - w;
// And doublecheck it
w2 := Size3.cx - Size2.cx;
if w <> w2 then begin
debugln(['SynTextDrawer: Failed on checking CharWidth with 3 char w=',w, ' w2=',w2]);
w := Max(w, w2);
eto := True;
end;
if o < 0 then begin
debugln('SynTextDrawer: Negative Overhang');
w := Size1.cx;
eto := True;
end
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;
ABC: TABC;
ABC2: TABC;
w: Integer;
HasABC: Boolean;
//Size: TSize;
Height, Width, OverHang: Integer;
ETO: Boolean;
Size1: TSize;
begin
// Calculate advance of a character.
// The following code uses ABC widths instead TextMetric.tmAveCharWidth
// because ABC widths always tells truth but tmAveCharWidth does not.
// A true-type font will have ABC widths but others like raster type will not
// so if the function fails then use TextMetric.tmAveCharWidth.
//debugln('TheFontStock.CalcFontAdvance A ',dbgs(pCharHeight));
// 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 witdh 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) then
if Size1.cx < 2 * Width then begin
// debugln(['SynTextDrawer: Negative Overhang for "Ta" Width=', Width, ' Overh=',OverHang, ' Ta.cx=',Size1.cx]);
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);
//GetTextExtentPoint(DC,'ABCgjp',6,Size);
//debugln('TheFontStock.CalcFontAdvance B ',dbgs(pCharHeight),' TM.tmHeight=',dbgs(TM.tmHeight),' TM.tmAscent=',dbgs(TM.tmAscent),' TM.tmDescent=',dbgs(TM.tmDescent),' "',BaseFont.Name,'" ',dbgs(BaseFont.height),' ',dbgs(Size.cx),',',dbgs(Size.cy));
{$IFDEF FPC}
// the next two lines are only to suppress the stupid FPC warnings:
ABC.abcA:=0;
ABC2.abcA:=0;
{$ENDIF}
HasABC := GetCharABCWidths(DC, Ord('M'), Ord('M'), ABC);
if not HasABC then
begin
with ABC do
begin
abcA := 0;
abcB := TM.tmAveCharWidth;
abcC := 0;
end;
TM.tmOverhang := 0;
if Width = 0 then begin
debugln('SynTextDrawer: No Width from GetTextExtentPoint');
Width := TM.tmMaxCharWidth + Max(TM.tmOverhang,0);
end
else if (Width > TM.tmMaxCharWidth) and (TM.tmMaxCharWidth > 0) then begin
debugln('SynTextDrawer: Width > tmMaxWidth');
// take a guess, this is probably a broken font
Width := Min(Width, round((TM.tmMaxCharWidth + Max(TM.tmOverhang,0)) * 1.2));
ETO := True;
end;
// Result(CharWidth)
with ABC do
Result := abcA + Integer(abcB) + abcC + TM.tmOverhang;
{$IFDEF SYN_LAZARUS}
// SynEdit would crash if a (defect) font returns 0.
if Result <= 0 then result := TM.tmAveCharWidth + Max(TM.tmOverhang,0);
if Result <= 0 then result := 1 + CharHeight * 8 div 10;
{$ENDIF}
if Height = 0 then begin
debugln('SynTextDrawer: No Height from GetTextExtentPoint');
Height := TM.tmHeight;
end
else if Height < TM.tmHeight then begin
debugln('SynTextDrawer: Height from GetTextExtentPoint to low');
Height := TM.tmHeight;
end;
if Height = 0 then begin
debugln('SynTextDrawer: Fallback on FontHeight');
Height := FontHeight;
end;
// pCharHeight
if Assigned(pCharHeight) then
pCharHeight^ := Abs(TM.tmHeight) {+ TM.tmInternalLeading};
// If we have a broken font, make sure we return a positive value
if Width <= 0 then Width := 1 + Height * 8 div 10;
//if OverHang >0 then debugln(['SynTextDrawer: Overhang=', OverHang]);;
FontData^.CharAdv := Width;
FontData^.CharHeight := Height;
FontData^.NeedETO := ETO;
end;
constructor TheFontStock.Create(InitialFont: TFont);
@ -652,6 +738,11 @@ begin
Result := FpInfo^.IsTrueType
end;
function TheFontStock.GetNeedETO: Boolean;
begin
Result := FpCrntFontData^.NeedETO;
end;
function TheFontStock.InternalGetDC: HDC;
begin
if FDCRefCount = 0 then
@ -781,11 +872,9 @@ begin
FpInfo^.IsDBCSFont := (0 <> (GCP_DBCS and GetFontLanguageInfo(DC)));
//debugln('TheFontStock.SetStyle A IsDBCSFont=',IsDBCSFont);
{$ENDIF}
with FpCrntFontData^ do
begin
Handle := FCrntFont;
CharAdv := CalcFontAdvance(DC, @CharHeight);
end;
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) ]);
{$IFDEF SYN_LAZARUS}
hOldFont:=SelectObject(DC, hOldFont);
@ -1103,8 +1192,8 @@ begin
fuOptions := 0;
end;
NeedDistArray:= (FCharExtra > 0) or (not MonoSpace)
or (not FFontStock.MonoSpace) or (FBaseCharWidth <> FFontStock.CharAdvance);
NeedDistArray:= (FCharExtra > 0) or
(FBaseCharWidth <> FFontStock.CharAdvance) or FFontStock.NeedETO;
//DebugLn(['TheTextDrawer.ExtTextOut NeedDistArray=',NeedDistArray]);
if NeedDistArray then begin
if (FETOSizeInChar < Length) then